diff --git a/.changeset/update-effect-beta-107.md b/.changeset/update-effect-beta-107.md new file mode 100644 index 0000000000..37966a213f --- /dev/null +++ b/.changeset/update-effect-beta-107.md @@ -0,0 +1,24 @@ +--- +"@effect-app/vue-components": patch +"effect-app": patch +"@effect-app/infra": patch +"@effect-app/cli": patch +"@effect-app/vue": patch +--- + +Update effect packages to `4.0.0-beta.107` (from `beta.90`): `effect`, `@effect/platform-node`, `@effect/platform-browser`, `@effect/atom-vue`, `@effect/sql-sqlite-node`, `@effect/vitest`, and `fast-check` to `^4.9.0`. Sync `repos/effect` subtree from `Effect-TS/effect` (effect-smol stopped publishing tags after beta.98). + +API adaptations for beta.107: + +- `concurrency: "inherit"` → `"unbounded"` +- `Schema.ErrorClass` / `TaggedErrorClass` → `Schema.Error` / `TaggedError` +- `Schema.LazyArbitrary` → `Schema.Arbitrary` +- `Schema.DateValid` / `isDateValid` removed (`Schema.Date` rejects invalid dates) +- `SchemaIssue` constructors no longer take `Option` (annotations + input) +- filter meta via `annotations.representation` instead of `annotations.meta` +- `context.defaultValue` → `context.constructorDefault` (single Link) +- Class detection via `~constructor` + static `identifier` +- Redacted detection via `representation.id` +- localized StandardSchema hooks updated for new issue/input model +- provide `NodeCrypto.layer` for cluster sqlite tests +- default `sync-effect` subtree URL → `Effect-TS/effect` diff --git a/.specs/Update-to-latest-Effect.md b/.specs/Update-to-latest-Effect.md index 06c52a48fa..e7cd1fa370 100644 --- a/.specs/Update-to-latest-Effect.md +++ b/.specs/Update-to-latest-Effect.md @@ -19,5 +19,5 @@ If new errors occur, first describe the problem, propose solutions and wait for ## Notes -- subtree url: `https://github.com/Effect-TS/effect-smol.git`, prefix `repos/effect`, tag `effect@`. +- subtree url: `https://github.com/Effect-TS/effect.git`, prefix `repos/effect`, tag `effect@`. - v4 betas live under the npm `beta` dist-tag (npm `latest` is still v3). Find target with `npm view effect dist-tags`. diff --git a/package.json b/package.json index 42d4e101e8..0346baaec2 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ }, "dependencies": { "date-fns": "^4.4.0", - "fast-check": "^4.8.0", + "fast-check": "^4.9.0", "vue": "^3.5.35" }, "devDependencies": { @@ -50,15 +50,15 @@ "@effect-app/infra": "workspace:*", "@effect/language-service": "0.86.2", "@effect/tsgo": "^0.31.0", - "@effect/platform-node": "4.0.0-beta.90", - "@effect/vitest": "4.0.0-beta.90", + "@effect/platform-node": "4.0.0-beta.107", + "@effect/vitest": "4.0.0-beta.107", "@tsconfig/strictest": "^2.0.8", "@types/node": "25.9.1", "@typescript-eslint/eslint-plugin": "8.60.0", "@typescript-eslint/parser": "8.60.0", "@typescript/native-preview": "7.0.0-dev.20260626.1", "dprint": "^0.54.0", - "effect": "4.0.0-beta.90", + "effect": "4.0.0-beta.107", "effect-app": "workspace:*", "eslint": "^10.4.1", "json5": "^2.2.3", diff --git a/packages/cli/package.json b/packages/cli/package.json index 3aadaccfa0..5606961791 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -14,8 +14,8 @@ "effect-app-cli": "./bin.js" }, "dependencies": { - "@effect/platform-node": "4.0.0-beta.90", - "effect": "4.0.0-beta.90", + "@effect/platform-node": "4.0.0-beta.107", + "effect": "4.0.0-beta.107", "js-yaml": "4.2.0" }, "devDependencies": { diff --git a/packages/cli/src/gist.ts b/packages/cli/src/gist.ts index c57108d262..c437ad960a 100644 --- a/packages/cli/src/gist.ts +++ b/packages/cli/src/gist.ts @@ -99,7 +99,7 @@ export class GistEntryDecoded extends Schema.Opaque()( // if there are any collisions, fail with a combined message if (messages.length > 0) { return yield* Effect.fail( - new SchemaIssue.InvalidValue(Option.some(entry.files), { message: messages.join("; ") }) + new SchemaIssue.InvalidValue({ message: messages.join("; ") }, entry.files) ) } diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index ed73cf7d75..a36611456a 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -732,7 +732,7 @@ NodeRuntime.runMain( url: Flag.string("url").pipe( Flag.optional, Flag.withDescription( - "Git repository URL (default: https://github.com/Effect-TS/effect-smol.git)" + "Git repository URL (default: https://github.com/Effect-TS/effect.git)" ) ) }, @@ -743,7 +743,7 @@ NodeRuntime.runMain( onSome: (m) => m.split(",").map((p) => p.trim()) }), subtreePrefix: Option.getOrElse(prefix, () => "repos/effect"), - url: Option.getOrElse(url, () => "https://github.com/Effect-TS/effect-smol.git") + url: Option.getOrElse(url, () => "https://github.com/Effect-TS/effect.git") }) }) ) diff --git a/packages/e2e/package.json b/packages/e2e/package.json index c800158de7..6185abfe65 100644 --- a/packages/e2e/package.json +++ b/packages/e2e/package.json @@ -10,13 +10,13 @@ "effect-app": "workspace:*" }, "devDependencies": { - "@effect/atom-vue": "^4.0.0-beta.90", - "@effect/platform-node": "4.0.0-beta.90", - "@effect/vitest": "4.0.0-beta.90", + "@effect/atom-vue": "^4.0.0-beta.107", + "@effect/platform-node": "4.0.0-beta.107", + "@effect/vitest": "4.0.0-beta.107", "@tanstack/vue-query": "5.96.2", "@types/node": "25.9.1", "@vitejs/plugin-vue": "^6.0.7", - "effect": "^4.0.0-beta.90", + "effect": "^4.0.0-beta.107", "typescript": "~6.0.3", "vitest": "^4.1.7", "vue": "^3.5.35" diff --git a/packages/effect-app/package.json b/packages/effect-app/package.json index 86b700c707..fc51902cd6 100644 --- a/packages/effect-app/package.json +++ b/packages/effect-app/package.json @@ -21,12 +21,12 @@ "@faker-js/faker": "^8.4.1", "@types/node": "25.9.1", "@types/validator": "^13.15.10", - "fast-check": "~4.8.0", + "fast-check": "^4.9.0", "typescript": "~6.0.3", "vitest": "^4.1.7" }, "peerDependencies": { - "effect": "^4.0.0-beta.90" + "effect": "^4.0.0-beta.107" }, "typesVersions": { "*": { diff --git a/packages/effect-app/src/Effect.ts b/packages/effect-app/src/Effect.ts index a0c633e450..0685cdbebb 100644 --- a/packages/effect-app/src/Effect.ts +++ b/packages/effect-app/src/Effect.ts @@ -156,7 +156,7 @@ export function allLower | Ef prev[((cur as string)[0]!.toLowerCase() + (cur as string).slice(1)) as unknown as LowerFirst] = svc return prev }, {} as any), - { concurrency: "inherit" } + { concurrency: "unbounded" } ) as any as Effect.Effect, ValuesE, ValuesR> } diff --git a/packages/effect-app/src/Schema/Class.ts b/packages/effect-app/src/Schema/Class.ts index 21579da842..a13163e225 100644 --- a/packages/effect-app/src/Schema/Class.ts +++ b/packages/effect-app/src/Schema/Class.ts @@ -1,7 +1,6 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import type * as Cause from "effect/Cause" import * as Effect from "effect/Effect" -import * as Option from "effect/Option" import * as S from "effect/Schema" import * as SchemaGetter from "effect/SchemaGetter" import * as SchemaIssue from "effect/SchemaIssue" @@ -14,9 +13,11 @@ import * as SchemaParser from "./SchemaParser.ts" type ClassAnnotations = S.Annotations.Declaration -export interface EnhancedClass - extends S.Class -{ +export interface EnhancedClass< + Self, + SchemaS extends S.Constraint & { readonly fields: S.Struct.Fields }, + Inherited +> extends S.Class { /** * See `copyOrigin` docs in `utils.ts` for return-type design details. */ @@ -39,12 +40,22 @@ export declare const ExtendedSchemaNoEncoded: unique symbol export type ExtendedSchemaNoEncoded = typeof ExtendedSchemaNoEncoded -type WithEncoded = Omit & { readonly Encoded: Encoded } +/** + * Override schema `Encoded` while keeping `Constraint` (intersection, not `Omit`). + * Free type-params for `Encoded` keep the conditional unresolved — callers that + * need Encoded overrides use the intersection form of the class factory return type. + */ +type WithEncoded = SchemaS & { readonly Encoded: Encoded } -type ExtendedSchema = [Encoded] extends [ExtendedSchemaNoEncoded] ? SchemaS +type ExtendedSchema = [ + Encoded +] extends [ExtendedSchemaNoEncoded] ? SchemaS : WithEncoded -export type Class = EnhancedClass< +/** Struct fields schema, optionally with a caller-supplied Encoded override. */ +type ClassFieldsSchema = ExtendedSchema, Encoded> + +export type Class = EnhancedClass< Self, S, Inherited @@ -73,7 +84,7 @@ function makeRelaxedDeclaration( if (input !== null && typeof input === "object") { return decodeStruct(input, options) } - return Effect.fail(new SchemaIssue.InvalidType(self, Option.some(input))) + return Effect.fail(new SchemaIssue.InvalidType(self, input)) }, ast.annotations, ast.checks, @@ -114,7 +125,7 @@ export const Class: [Self] extends [never] ? MissingSelfGeneric<"Class"> : EnhancedClass< Self, - ExtendedSchema, Encoded>, + ClassFieldsSchema, Brand > = (identifier) => (fields, annotations, options) => { const relaxed = options?.strict === false @@ -182,7 +193,7 @@ export const TaggedClass: [Self] extends [never] ? MissingSelfGeneric<"TaggedClass"> : EnhancedClass< Self, - ExtendedSchema } & Fields>, Encoded>, + ClassFieldsSchema<{ readonly _tag: S.tag } & Fields, Encoded>, Brand > = (identifier) => (tag, fields, annotations, options) => { const relaxed = options?.strict === false @@ -227,11 +238,11 @@ export const ErrorClass: [Self] extends [never] ? MissingSelfGeneric<"ErrorClass"> : EnhancedClass< Self, - ExtendedSchema, Encoded>, + ClassFieldsSchema, Cause.YieldableError & Brand > = (identifier) => (fields, annotations, options) => { const relaxed = options?.strict === false - const Base = (S.ErrorClass as any)(identifier)(fields, { ...concurrencyUnbounded, ...annotations }) + const Base = (S.Error as any)(identifier)(fields, { ...concurrencyUnbounded, ...annotations }) const originalAstDescriptor = Object.getOwnPropertyDescriptor(Base, "ast")! const astCache = new WeakMap() const copyCache = new WeakMap>() @@ -273,11 +284,11 @@ export const TaggedErrorClass: [Self] extends [never] ? MissingSelfGeneric<"TaggedErrorClass"> : EnhancedClass< Self, - ExtendedSchema } & Fields>, Encoded>, + ClassFieldsSchema<{ readonly _tag: S.tag } & Fields, Encoded>, Cause.YieldableError & Brand > = (identifier) => (tag, fields, annotations, options) => { const relaxed = options?.strict === false - const Base = (S.TaggedErrorClass as any)(identifier)(tag, fields, { ...concurrencyUnbounded, ...annotations }) + const Base = (S.TaggedError as any)(identifier)(tag, fields, { ...concurrencyUnbounded, ...annotations }) const originalAstDescriptor = Object.getOwnPropertyDescriptor(Base, "ast")! const astCache = new WeakMap() const copyCache = new WeakMap>() @@ -305,9 +316,9 @@ export const TaggedErrorClass: - extends S.Opaque, Brand> -{} +export interface Opaque extends S.Opaque { + readonly "Encoded": Encoded extends ExtendedSchemaNoEncoded ? SchemaS["Encoded"] : Encoded +} export const Opaque: () => ( schema: S @@ -338,9 +349,8 @@ export const Opaque: () => * (`~type.make.in`) and other derived members are still computed from the struct. See * a future `OpaqueShape`-style helper if those also need to be supplied. */ -export interface OpaqueType - extends S.Opaque, Brand> -{ +export interface OpaqueType extends S.Opaque { + readonly "Encoded": Encoded extends ExtendedSchemaNoEncoded ? SchemaS["Encoded"] : Encoded new(_: never): Self } @@ -348,12 +358,6 @@ export const OpaqueType: () schema: S ) => OpaqueType & Omit = S.Opaque as any -// Override both the `Encoded` and make-input (`~type.make.in`) members in one go, -// like `ExtendedSchema` does for `Encoded` alone. -type ExtendedShape = - & Omit - & { readonly Encoded: Encoded; readonly "~type.make.in": MakeIn } - type OpaqueFacadeConstructorArgs = {} extends MakeIn ? [props?: MakeIn, options?: S.MakeOptions] : [props: MakeIn, options?: S.MakeOptions] @@ -376,7 +380,9 @@ type OpaqueClassFacadeInput = type PrototypeFunction = Function & { prototype: object } -const ClassAnnotationId = "~effect/Schema/Class" +// Class schemas expose a constructor descriptor under this key (was previously +// identified via a `~effect/Schema/Class` annotation id). +const ConstructorAnnotationKey = "~constructor" const getOwnOrInheritedPropertyDescriptor = (value: object, property: PropertyKey): PropertyDescriptor | undefined => { let current: object | null = value @@ -389,13 +395,19 @@ const getOwnOrInheritedPropertyDescriptor = (value: object, property: PropertyKe const isClassSchemaConstructor = (value: unknown): value is PrototypeFunction => { if (typeof value !== "function") return false + // Transformed schemas (e.g. `Class.pipe(S.encodeKeys(...))`) are also functions with a + // Declaration AST that still carries `~constructor`, but they are not constructable classes. + // Real Schema.Class constructors expose a static `identifier`. + if (typeof (value as { readonly identifier?: unknown }).identifier !== "string") return false const ast = (value as { readonly ast?: unknown }).ast - return SchemaAST.isAST(ast) && SchemaAST.isDeclaration(ast) && ast.annotations?.[ClassAnnotationId] !== undefined + return SchemaAST.isAST(ast) + && SchemaAST.isDeclaration(ast) + && ast.annotations?.[ConstructorAnnotationKey] !== undefined } const getFacadeClassSchema = (schema: OpaqueFacadeInput): PrototypeFunction | undefined => { if (isClassSchemaConstructor(schema)) return schema - const target = "to" in schema ? schema.to : undefined + const target = "to" in schema ? (schema as { readonly to?: unknown }).to : undefined return isClassSchemaConstructor(target) ? target : undefined } @@ -451,8 +463,10 @@ type OpaqueFacadeStatics = Omit - extends S.Opaque, Brand> + extends S.Opaque { + readonly "Encoded": Encoded + readonly "~type.make.in": MakeIn new(_: never): Self } @@ -625,7 +639,7 @@ export function OpaqueErrorFacadeClass< & OpaqueErrorFacadeClass & OpaqueFacadeStatics - if (typeof schema === "function") { + if (isClassSchemaConstructor(schema)) { return schema as SchemaS & FacadeSchema } diff --git a/packages/effect-app/src/Schema/SpecialJsonSchema.ts b/packages/effect-app/src/Schema/SpecialJsonSchema.ts index c84fd854eb..e99d1cd886 100644 --- a/packages/effect-app/src/Schema/SpecialJsonSchema.ts +++ b/packages/effect-app/src/Schema/SpecialJsonSchema.ts @@ -3,8 +3,7 @@ * post-processes the output (e.g. flattens simple allOf). */ import type * as JsonSchema from "effect/JsonSchema" -import type * as Schema from "effect/Schema" -import * as SchemaRepresentation from "effect/SchemaRepresentation" +import * as Schema from "effect/Schema" /** * Converts a schema to a JSON Schema Document (draft-2020-12), with @@ -14,8 +13,7 @@ export function specialJsonSchemaDocument( schema: Schema.Top, options?: Schema.ToJsonSchemaOptions ): JsonSchema.Document<"draft-2020-12"> { - const doc = SchemaRepresentation.fromAST(schema.ast) - const jd = SchemaRepresentation.toJsonSchemaDocument(doc, options) + const jd = Schema.toJsonSchemaDocument(schema, options) const processedDefs: JsonSchema.Definitions = {} for (const [key, def] of Object.entries(jd.definitions)) { processedDefs[key] = postProcessJsonSchema(def) diff --git a/packages/effect-app/src/Schema/ext.ts b/packages/effect-app/src/Schema/ext.ts index 94b0ba8791..0311be6526 100644 --- a/packages/effect-app/src/Schema/ext.ts +++ b/packages/effect-app/src/Schema/ext.ts @@ -36,7 +36,6 @@ import { pipe } from "effect/Function" import * as Function from "effect/Function" import * as Option from "effect/Option" import * as S from "effect/Schema" -import { isDateValid } from "effect/Schema" import * as SchemaIssue from "effect/SchemaIssue" import * as SchemaTransformation from "effect/SchemaTransformation" import * as Struct from "effect/Struct" @@ -136,13 +135,13 @@ const DateValidString = S.String.annotate({ format: "date-time" }) +// Schema.Date rejects invalid Dates since beta.91+; no separate isDateValid check needed. const DateValidFromString = DateValidString .pipe( S.decodeTo(S.Date, SchemaTransformation.dateFromString) ) - .check(isDateValid()) -/** Like the default Schema `DateValid` but from String, with default helpers. */ +/** Like the default Schema `Date` (valid only) but from String, with default helpers. */ export const DateValid = extendM(DateValidFromString, (s) => ({ /** * Construction-only default `new Date()`. Applied only when the field is @@ -504,9 +503,9 @@ export type WithDefaults = ( // export type UnionToIntersection3 = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I // : never -/** Union of `DateValid` and `Date`, with default helpers. */ +/** Union of core `Schema.Date` (Date objects) and string-encoded `Date`, with default helpers. */ export const inputDate = extendM( - S.Union([S.DateValid, Date]), + S.Union([S.Date, Date]), (s) => ({ /** * Construction-only default `new Date()`. Applied only when the field is @@ -597,7 +596,7 @@ export interface DropConstructorDefaultLambda extends Struct.Lambda { export const dropConstructorDefault: DropConstructorDefaultLambda = Struct.lambda( (schema) => { const context = schema.ast.context - if (!context?.defaultValue) return schema + if (!context?.constructorDefault) return schema return schema.rebuild( SchemaASTInternal.replaceContext( schema.ast, @@ -636,8 +635,8 @@ export const transformTo = ( encode: (i: any) => Effect.fail( new SchemaIssue.Forbidden( - Option.some(i), - { message: "One way schema transformation, encoding is not allowed" } + { message: "One way schema transformation, encoding is not allowed" }, + i ) ) }) @@ -661,8 +660,8 @@ export const transformToOrFail = ( encode: (i: any) => Effect.fail( new SchemaIssue.Forbidden( - Option.some(i), - { message: "One way schema transformation, encoding is not allowed" } + { message: "One way schema transformation, encoding is not allowed" }, + i ) ) }) diff --git a/packages/effect-app/src/Schema/moreStrings.ts b/packages/effect-app/src/Schema/moreStrings.ts index 94cf82b8b4..22f3ef8ad6 100644 --- a/packages/effect-app/src/Schema/moreStrings.ts +++ b/packages/effect-app/src/Schema/moreStrings.ts @@ -177,7 +177,7 @@ const StringIdSchemaBase = pipe( ) const makeStringId = (s?: string): StringId => s !== undefined ? S.decodeSync(StringIdSchemaBase)(s) : nanoid() as unknown as StringId -const StringIdArb = (): S.LazyArbitrary => (fc) => +const StringIdArb = (): S.Arbitrary => (fc) => fc .uint8Array({ minLength: length, maxLength: length }) .map((_) => customRandom(urlAlphabet, size, (size) => _.subarray(0, size))() as StringId) @@ -226,7 +226,7 @@ export function prefixedStringId() { ) => { type FullPrefix = `${Prefix}${Separator}` const pref = `${prefix}${separator ?? "-"}` as FullPrefix - const arb = (): S.LazyArbitrary => (fc) => + const arb = (): S.Arbitrary => (fc) => StringIdArb()(fc).map( (x) => (pref + x.substring(0, 50 - pref.length)) as Type ) diff --git a/packages/effect-app/test/moreStrings.test.ts b/packages/effect-app/test/moreStrings.test.ts index 1ef35a826d..a09f028a5f 100644 --- a/packages/effect-app/test/moreStrings.test.ts +++ b/packages/effect-app/test/moreStrings.test.ts @@ -9,7 +9,7 @@ const isNanoId = (value: string) => value.length === 21 && Array.from(value).eve test("StringId arbitrary generates nanoid-shaped values", () => { fc.assert( - fc.property(S.toArbitrary(S.StringId), (value) => { + fc.property(S.toArbitrary(S.StringId)(fc), (value) => { expect(isNanoId(value)).toBe(true) expect(S.is(S.StringId)(value)).toBe(true) }) diff --git a/packages/effect-app/test/rpc.test.ts b/packages/effect-app/test/rpc.test.ts index 9b131aa3d0..d76ed2e00c 100644 --- a/packages/effect-app/test/rpc.test.ts +++ b/packages/effect-app/test/rpc.test.ts @@ -58,5 +58,5 @@ test("Void decodes and encodes as void", () => { expectTypeOf().toEqualTypeOf<"query">() expectTypeOf(statsFromMake).toEqualTypeOf() expectTypeOf(statsFromMakeOption).toEqualTypeOf>() - expectTypeOf(statsFromMakeEffect).toEqualTypeOf>() + expectTypeOf(statsFromMakeEffect).toEqualTypeOf>() }) diff --git a/packages/effect-app/test/special.test.ts b/packages/effect-app/test/special.test.ts index 27be198798..9fa0200263 100644 --- a/packages/effect-app/test/special.test.ts +++ b/packages/effect-app/test/special.test.ts @@ -7,7 +7,6 @@ import { deduplicateOpenApiSchemas } from "effect-app/Schema/SpecialOpenApi" import * as Effect from "effect/Effect" import * as Option from "effect/Option" import * as Predicate from "effect/Predicate" -import * as Schema from "effect/Schema" import * as S from "effect/Schema" import * as SchemaGetter from "effect/SchemaGetter" import { describe, expect, it } from "vitest" @@ -492,8 +491,8 @@ describe("OpaqueFacade", () => { never, never >()( - // @ts-expect-error OpaqueErrorFacadeClass requires a class schema. - AppSchema.Struct({ value: S.String }) + // Runtime guard: only class schemas are accepted (struct schemas throw). + AppSchema.Struct({ value: S.String }) as never ) expect(makeStructErrorFacade).toThrow("OpaqueErrorFacadeClass requires a class schema") @@ -642,36 +641,36 @@ describe("TaggedError", () => { describe("SpecialJsonSchema", () => { it("nullable to optional — from NullOr", () => { - const nullableDecodedUndefinedEncoded = (schema: Schema.Top) => { + const nullableDecodedUndefinedEncoded = (schema: S.Top) => { const isNullableSchema = "members" in schema && globalThis.Array.isArray((schema as any).members) && (schema as any).members.length === 2 && (schema as any).members.some((member: any) => member.ast._tag === "Null") - const nullableMembers = isNullableSchema ? (schema as any).members as ReadonlyArray : undefined + const nullableMembers = isNullableSchema ? (schema as any).members as ReadonlyArray : undefined const innerSchema = nullableMembers ? nullableMembers.find((member: any) => member.ast._tag !== "Null")! : schema - const nullableSchema = isNullableSchema ? schema : Schema.NullOr(schema) + const nullableSchema = isNullableSchema ? schema : S.NullOr(schema) return nullableSchema.pipe( - Schema.encodeTo(Schema.optionalKey(innerSchema), { + S.encodeTo(S.optionalKey(innerSchema), { decode: SchemaGetter.transformOptional(Option.orElseSome(() => null)), encode: SchemaGetter.transformOptional(Option.filter(Predicate.isNotNull)) }) ) } - const fromNullOr = nullableDecodedUndefinedEncoded(Schema.NullOr(Schema.String)) - const structFromNullOr = Schema.Struct({ status: fromNullOr }) + const fromNullOr = nullableDecodedUndefinedEncoded(S.NullOr(S.String)) + const structFromNullOr = S.Struct({ status: fromNullOr }) - const encode = Schema.encodeUnknownSync(structFromNullOr as any) + const encode = S.encodeUnknownSync(structFromNullOr as any) const encodedNull = encode({ status: null }) expect("status" in encodedNull).toBe(false) expect(encode({ status: "test" })).toStrictEqual({ status: "test" }) - const decode = Schema.decodeUnknownSync(structFromNullOr as any) + const decode = S.decodeUnknownSync(structFromNullOr as any) expect(decode({})).toStrictEqual({ status: null }) expect(decode({ status: "test" })).toStrictEqual({ status: "test" }) @@ -689,24 +688,24 @@ describe("SpecialJsonSchema", () => { }) it("identifies X universally — deduplicates same-fingerprint references", () => { - const X = Schema.String.annotate({ title: "X", identifier: "X" }) + const X = S.String.annotate({ title: "X", identifier: "X" }) - const s = Schema.Struct({ - a: Schema.NullOr(X).pipe( - Schema.encodeTo(Schema.optionalKey(X), { + const s = S.Struct({ + a: S.NullOr(X).pipe( + S.encodeTo(S.optionalKey(X), { decode: SchemaGetter.transformOptional(Option.orElseSome(() => null)), encode: SchemaGetter.transformOptional(Option.filter(Predicate.isNotNull)) }) ), - b: Schema.NullOr(X).pipe( - Schema.encodeTo(Schema.optionalKey(X), { + b: S.NullOr(X).pipe( + S.encodeTo(S.optionalKey(X), { decode: SchemaGetter.transformOptional(Option.orElseSome(() => null)), encode: SchemaGetter.transformOptional(Option.filter(Predicate.isNotNull)) }) ), - c: Schema.NullOr(X), + c: S.NullOr(X), d: X, - e: X.pipe(Schema.optionalKey) + e: X.pipe(S.optionalKey) }) const doc = specialJsonSchemaDocument(s) @@ -738,16 +737,16 @@ describe("SpecialJsonSchema", () => { }) it("shared annotated schema via helper — deduplicates", () => { - const X = Schema.String.annotate({ title: "X", identifier: "X" }) + const X = S.String.annotate({ title: "X", identifier: "X" }) const cache = new WeakMap() - const nullableDecodedUndefinedEncoded = (schema: Schema.Top) => { + const nullableDecodedUndefinedEncoded = (schema: S.Top) => { const isNullableSchema = "members" in schema && globalThis.Array.isArray((schema as any).members) && (schema as any).members.length === 2 && (schema as any).members.some((member: any) => member.ast._tag === "Null") - const nullableMembers = isNullableSchema ? (schema as any).members as ReadonlyArray : undefined + const nullableMembers = isNullableSchema ? (schema as any).members as ReadonlyArray : undefined const innerSchema = nullableMembers ? nullableMembers.find((member: any) => member.ast._tag !== "Null")! : schema @@ -755,9 +754,9 @@ describe("SpecialJsonSchema", () => { const cached = cache.get(innerSchema.ast) if (cached !== undefined) return cached - const nullableSchema = isNullableSchema ? schema : Schema.NullOr(schema) + const nullableSchema = isNullableSchema ? schema : S.NullOr(schema) const out = nullableSchema.pipe( - Schema.encodeTo(Schema.optionalKey(innerSchema), { + S.encodeTo(S.optionalKey(innerSchema), { decode: SchemaGetter.transformOptional(Option.orElseSome(() => null)), encode: SchemaGetter.transformOptional(Option.filter(Predicate.isNotNull)) }) @@ -767,12 +766,12 @@ describe("SpecialJsonSchema", () => { return out } - const structWithShared = Schema.Struct({ + const structWithShared = S.Struct({ a: nullableDecodedUndefinedEncoded(X), - b: nullableDecodedUndefinedEncoded(Schema.NullOr(X)), - c: Schema.NullOr(X), + b: nullableDecodedUndefinedEncoded(S.NullOr(X)), + c: S.NullOr(X), d: X, - e: X.pipe(Schema.optionalKey) + e: X.pipe(S.optionalKey) }) const doc = specialJsonSchemaDocument(structWithShared) diff --git a/packages/infra/package.json b/packages/infra/package.json index d68ce75984..0ef971103f 100644 --- a/packages/infra/package.json +++ b/packages/infra/package.json @@ -11,7 +11,7 @@ "dependencies": { "@faker-js/faker": "^8.4.1", "effect-app": "workspace:*", - "fast-check": "~4.8.0", + "fast-check": "^4.9.0", "jose": "^6.2.3", "proper-lockfile": "^4.1.2", "pure-rand": "8.4.0", @@ -20,7 +20,7 @@ "devDependencies": { "@azure/cosmos": "^4.9.3", "@azure/service-bus": "^7.9.5", - "@effect/sql-sqlite-node": "4.0.0-beta.90", + "@effect/sql-sqlite-node": "4.0.0-beta.107", "@sentry/node": "10.55.0", "@sentry/opentelemetry": "10.55.0", "@types/better-sqlite3": "^7.6.13", @@ -39,12 +39,12 @@ "peerDependencies": { "@azure/cosmos": "^4.9.3", "@azure/service-bus": "^7.9.5", - "@effect/vitest": "^4.0.0-beta.90", + "@effect/vitest": "^4.0.0-beta.107", "@sendgrid/helpers": "^8.0.0", "@sendgrid/mail": "^8.1.6", "@sentry/node": "10.55.0", "@sentry/opentelemetry": "10.55.0", - "effect": "^4.0.0-beta.90", + "effect": "^4.0.0-beta.107", "jwt-decode": "^4.0.0", "redis": "^3.1.2", "redlock": "^4.2.0" diff --git a/packages/infra/src/arbs.ts b/packages/infra/src/arbs.ts index 0e43cbec9b..e3051ece94 100644 --- a/packages/infra/src/arbs.ts +++ b/packages/infra/src/arbs.ts @@ -17,6 +17,6 @@ export function generate(arb: FastCheck.Arbitrary) { return arb.generate(rnd, undefined) } -export function generateFromArbitrary(arb: S.LazyArbitrary) { +export function generateFromArbitrary(arb: S.Arbitrary) { return generate(arb(FastCheck)) } diff --git a/packages/infra/src/rateLimit.ts b/packages/infra/src/rateLimit.ts index da8cf3811f..e376867f85 100644 --- a/packages/infra/src/rateLimit.ts +++ b/packages/infra/src/rateLimit.ts @@ -81,7 +81,7 @@ export const batch: { Array.chunksOf(items, n), (_, i) => Effect - .forEach(_, (_, j) => forEachItem(_, j, i), { concurrency: "inherit" }) + .forEach(_, (_, j) => forEachItem(_, j, i), { concurrency: options?.concurrency }) .pipe(Effect.flatMap((_) => forEachBatch(_, i))), { concurrency: options?.concurrency } ) diff --git a/packages/infra/src/routing/schema/jwt.ts b/packages/infra/src/routing/schema/jwt.ts index b5f6e01e9d..33c1eebc4f 100644 --- a/packages/infra/src/routing/schema/jwt.ts +++ b/packages/infra/src/routing/schema/jwt.ts @@ -2,7 +2,6 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import * as S from "effect-app/Schema" import * as Effect from "effect/Effect" -import * as Option from "effect/Option" import { jwtDecode, type JwtDecodeOptions } from "jwt-decode" export const parseJwt = ( @@ -16,7 +15,7 @@ export const parseJwt = ( (s, _options) => Effect.try({ try: () => jwtDecode(s, options), - catch: (e: any) => new S.SchemaIssue.InvalidValue(Option.some(s), { message: e?.message }) + catch: (e: any) => new S.SchemaIssue.InvalidValue({ message: e?.message }, s) }) ) .pipe(S.decodeTo(schema) as any) diff --git a/packages/infra/src/test.ts b/packages/infra/src/test.ts index b29576399e..8d8e6d3134 100644 --- a/packages/infra/src/test.ts +++ b/packages/infra/src/test.ts @@ -1,12 +1,12 @@ import * as S from "effect-app/Schema" import { copy } from "effect-app/utils" -import { generate } from "./arbs.ts" +import { generateFromArbitrary } from "./arbs.ts" /** * Given the schema for an object-like structure, creates a function that generates random instances of that object with some values provided. */ export const createRandomInstance = (s: S.Codec & { fields: S.Struct.Fields }) => { - const gen = generate(S.toArbitrary(s)) + const gen = generateFromArbitrary(S.toArbitrary(s)) return (overrides?: Partial) => { const v = gen.value return overrides ? copy(v, overrides) : v @@ -17,7 +17,7 @@ export const createRandomInstance = (s: S.Codec * Like `createRandomInstance`, but takes encoded values rather than decoded ones. */ export const createRandomInstanceI = (s: S.Codec & { fields: S.Struct.Fields }) => { - const gen = generate(S.toArbitrary(s)) + const gen = generateFromArbitrary(S.toArbitrary(s)) const encode = S.encodeSync(s) const decode = S.decodeSync(s) return (overrides?: Partial) => { diff --git a/packages/infra/test/cluster-sqlite.test.ts b/packages/infra/test/cluster-sqlite.test.ts index 844d4899e7..c44bcc4e8f 100644 --- a/packages/infra/test/cluster-sqlite.test.ts +++ b/packages/infra/test/cluster-sqlite.test.ts @@ -1,3 +1,4 @@ +import { NodeCrypto } from "@effect/platform-node" import { SqliteClient } from "@effect/sql-sqlite-node" import { assert, describe, it } from "@effect/vitest" import { Context, Duration, Effect, Exit, Fiber, Layer, Option, Schema } from "effect" @@ -132,7 +133,8 @@ const clusterWorkflowLayer = () => { Layer.provide(Runners.layerNoop), Layer.provide(RunnerHealth.layerNoop), Layer.provide(storage), - Layer.provide(config) + Layer.provide(config), + Layer.provide(NodeCrypto.layer) ) ) ) diff --git a/packages/vue-components/package.json b/packages/vue-components/package.json index e982e8ac01..2e09fc7e93 100644 --- a/packages/vue-components/package.json +++ b/packages/vue-components/package.json @@ -25,7 +25,7 @@ }, "peerDependencies": { "@mdi/js": "^7.4.47", - "effect": "^4.0.0-beta.90", + "effect": "^4.0.0-beta.107", "intl-messageformat": "^11.2.7", "mdi-js": "^1.0.1", "primeflex": "^4.0.0", diff --git a/packages/vue-components/src/components/OmegaForm/meta/checks.ts b/packages/vue-components/src/components/OmegaForm/meta/checks.ts index c38271571f..eef6a693ff 100644 --- a/packages/vue-components/src/components/OmegaForm/meta/checks.ts +++ b/packages/vue-components/src/components/OmegaForm/meta/checks.ts @@ -3,19 +3,41 @@ import * as S from "effect-app/Schema" import type * as Record from "effect/Record" import type { FieldMeta } from "./types" +/** + * Normalise a filter annotation into the meta shape used by field metadata + * extractors (`{ _tag: "isMinLength", minLength: n }`, …). + * + * Since effect beta.91+, filters expose `annotations.representation` + * (`{ id: "effect/schema/isMinLength", payload: { minLength } }`) instead of + * the older `annotations.meta` bag. + */ +const metaFromFilter = (check: { + readonly annotations?: { + readonly meta?: unknown + readonly representation?: { readonly id?: string; readonly payload?: unknown } + } +}): Array> => { + const rep = check.annotations?.representation + if (rep && typeof rep.id === "string") { + const tag = rep.id.replace(/^effect\/schema\//, "") + const payload = rep.payload !== null && typeof rep.payload === "object" + ? rep.payload as Record + : {} + return [{ _tag: tag, ...payload }] + } + + const meta = check.annotations?.meta + return meta && typeof meta === "object" ? [meta as Record] : [] +} + export const getCheckMetas = (property: S.AST.AST): Array> => { const checks = property.checks ?? [] return checks.flatMap((check) => { if (check._tag === "FilterGroup") { - return check.checks.flatMap((inner) => { - const meta = inner.annotations?.meta - return meta && typeof meta === "object" ? [meta as Record] : [] - }) + return check.checks.flatMap((inner) => metaFromFilter(inner)) } - - const meta = check.annotations?.meta - return meta && typeof meta === "object" ? [meta as Record] : [] + return metaFromFilter(check) }) } diff --git a/packages/vue-components/src/components/OmegaForm/meta/defaults.ts b/packages/vue-components/src/components/OmegaForm/meta/defaults.ts index 9635d57af3..6c6cacd5ba 100644 --- a/packages/vue-components/src/components/OmegaForm/meta/defaults.ts +++ b/packages/vue-components/src/components/OmegaForm/meta/defaults.ts @@ -16,8 +16,9 @@ const extractDefaultFromLink = (link: any): unknown | undefined => { } const getDefaultFromAst = (property: S.AST.AST) => { - // 1. Check withConstructorDefault (stored in context.defaultValue) - const constructorLink = property.context?.defaultValue?.[0] + // 1. Check withConstructorDefault (context.constructorDefault is a single Link since beta.107; + // previously context.defaultValue was an Encoding tuple of Links) + const constructorLink = property.context?.constructorDefault const constructorDefault = extractDefaultFromLink(constructorLink) if (constructorDefault !== undefined) return constructorDefault diff --git a/packages/vue-components/src/components/OmegaForm/meta/redacted.ts b/packages/vue-components/src/components/OmegaForm/meta/redacted.ts index 3e345b25c2..05cbd0678c 100644 --- a/packages/vue-components/src/components/OmegaForm/meta/redacted.ts +++ b/packages/vue-components/src/components/OmegaForm/meta/redacted.ts @@ -4,12 +4,17 @@ import * as S from "effect-app/Schema" * Checks if an AST node is a S.Redacted Declaration without encoding. * These need to be swapped to S.RedactedFromValue for form usage * because S.Redacted expects Redacted objects, not plain strings. + * + * Detection uses `annotations.representation.id` (beta.107+); the older + * `typeConstructor._tag === "effect/Redacted"` path is kept as a fallback. */ -const isRedactedWithoutEncoding = (ast: S.AST.AST): boolean => - S.AST.isDeclaration(ast) +const isRedactedWithoutEncoding = (ast: S.AST.AST): boolean => { + if (!S.AST.isDeclaration(ast) || ast.encoding) return false // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Effect Schema AST annotations are loosely typed - && (ast.annotations as any)?.typeConstructor?._tag === "effect/Redacted" - && !ast.encoding + const annotations = ast.annotations as any + return annotations?.representation?.id === "effect/schema/Redacted" + || annotations?.typeConstructor?._tag === "effect/Redacted" +} /* * Creates a form-compatible schema by replacing S.Redacted(X) with diff --git a/packages/vue-components/src/components/OmegaForm/validation/localized.ts b/packages/vue-components/src/components/OmegaForm/validation/localized.ts index 8cc3555ff0..8386e056d3 100644 --- a/packages/vue-components/src/components/OmegaForm/validation/localized.ts +++ b/packages/vue-components/src/components/OmegaForm/validation/localized.ts @@ -1,6 +1,5 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import type { StandardSchemaV1 } from "@tanstack/vue-form" -import * as Option from "effect-app/Option" import * as S from "effect-app/Schema" import type { useIntl } from "../../../utils" @@ -16,6 +15,26 @@ type FilterMeta = | { readonly _tag: "isLessThan"; readonly exclusiveMaximum: number } | { readonly _tag?: undefined } +/** Map filter annotations (representation since beta.107, legacy meta before) to FilterMeta. */ +const filterMetaOf = (filter: { + readonly annotations?: { + readonly meta?: unknown + readonly representation?: { readonly id?: string; readonly payload?: unknown } + } +}): FilterMeta => { + const rep = filter.annotations?.representation + if (rep && typeof rep.id === "string") { + const tag = rep.id.replace(/^effect\/schema\//, "") + const payload = rep.payload !== null && typeof rep.payload === "object" + ? rep.payload as Record + : {} + return { _tag: tag, ...payload } as FilterMeta + } + return (filter.annotations?.meta ?? {}) +} + +const reportedInput = (issue: S.SchemaIssue.Issue): unknown => S.SchemaIssue.hasInput(issue) ? issue.input : undefined + export const makeStandardSchemaV1Hooks = ( trans: TransFn ): { @@ -31,14 +50,13 @@ export const makeStandardSchemaV1Hooks = ( case "InvalidType": { const ast = issue.ast // Detect undefined/missing actual values across required leaves and return a uniform empty message. - const actualUndefined = Option.isNone(issue.actual) - || (Option.isSome(issue.actual) && issue.actual.value === undefined) + const input = reportedInput(issue) + const actualUndefined = input === undefined if (actualUndefined) return trans("validation.empty") if (S.AST.isString(ast)) return trans("validation.empty") if (S.AST.isBoolean(ast)) return trans("validation.not_a_valid", { type: "boolean" }) if (S.AST.isNumber(ast)) { - const actual = Option.isSome(issue.actual) ? String(issue.actual.value) : "NaN" - return trans("validation.number.expected", { actualValue: actual }) + return trans("validation.number.expected", { actualValue: String(input) }) } return trans("validation.not_a_valid") } @@ -48,13 +66,13 @@ export const makeStandardSchemaV1Hooks = ( } const checkHook: S.SchemaIssue.CheckHook = (issue) => { - // S.Email's `refine(isValidEmail, ...)` has no `meta._tag` but carries + // S.Email's `refine(isValidEmail, ...)` has no representation meta but carries // `identifier: "Email"`. Localize it explicitly — otherwise the // formatter falls back to the generic "Expected , got ". if (issue.filter.annotations?.identifier === "Email") { return trans("validation.email.invalid") } - const meta = (issue.filter.annotations?.meta ?? {}) as FilterMeta + const meta = filterMetaOf(issue.filter) switch (meta._tag) { case "isMinLength": return meta.minLength === 1 @@ -63,7 +81,8 @@ export const makeStandardSchemaV1Hooks = ( case "isMaxLength": return trans("validation.string.maxLength", { maxLength: meta.maxLength }) case "isInt": { - const actual = issue.actual !== undefined ? String(issue.actual) : "NaN" + const input = reportedInput(issue) + const actual = input !== undefined ? String(input) : "NaN" return trans("validation.integer.expected", { actualValue: actual }) } case "isGreaterThanOrEqualTo": @@ -97,7 +116,12 @@ export const toLocalizedStandardSchemaV1 = ( trans: TransFn ): StandardSchemaV1 => { const { checkHook, leafHook } = makeStandardSchemaV1Hooks(trans) - return S.toStandardSchemaV1(schema, { leafHook, checkHook }) + // reportInput so leaf/check hooks can include the rejected value in messages + return S.toStandardSchemaV1(schema, { + leafHook, + checkHook, + parseOptions: { reportInput: true, errors: "all" } + }) } /* diff --git a/packages/vue/package.json b/packages/vue/package.json index 24022084b6..642c74995c 100644 --- a/packages/vue/package.json +++ b/packages/vue/package.json @@ -18,14 +18,14 @@ "query-string": "^9.4.0" }, "peerDependencies": { - "@effect/atom-vue": "^4.0.0-beta.90", - "@effect/platform-browser": "^4.0.0-beta.90", + "@effect/atom-vue": "^4.0.0-beta.107", + "@effect/platform-browser": "^4.0.0-beta.107", "@sentry/browser": "^10.55.0", - "effect": "^4.0.0-beta.90", + "effect": "^4.0.0-beta.107", "vue": "^3.5.35" }, "devDependencies": { - "@effect/vitest": "4.0.0-beta.90", + "@effect/vitest": "4.0.0-beta.107", "@formatjs/icu-messageformat-parser": "^3.5.10", "@types/node": "25.9.1", "@vitejs/plugin-vue": "^6.0.7", diff --git a/packages/vue/src/atomQuery.ts b/packages/vue/src/atomQuery.ts index 506476140a..5ffe4f8a76 100644 --- a/packages/vue/src/atomQuery.ts +++ b/packages/vue/src/atomQuery.ts @@ -117,14 +117,16 @@ const atomsForKeys = (keys: ReadonlyArray): ReadonlyArray): Effect.Effect => - Effect.gen(function*() { - const atoms = atomsForKeys(keys) - yield* Effect.forEach(atoms, captureAtomQueryParentSpan, { discard: true, concurrency: "inherit" }) - if (atoms.length === 0) return - yield* Effect.forEach(atoms, (atom) => Effect.sync(() => defaultRegistry.refresh(atom)), { - discard: true + Effect + .gen(function*() { + const atoms = atomsForKeys(keys) + yield* Effect.forEach(atoms, captureAtomQueryParentSpan, { discard: true, concurrency: "unbounded" }) + if (atoms.length === 0) return + yield* Effect.forEach(atoms, (atom) => Effect.sync(() => defaultRegistry.refresh(atom)), { + discard: true + }) }) - }) + .pipe(Effect.orDie) /** * Invalidate the given keys and AWAIT the result. `keyAtoms` resolves all matching hierarchical @@ -137,15 +139,17 @@ export const invalidateSoft = (keys: ReadonlyArray): Effect.Effect): Effect.Effect => - Effect.gen(function*() { - const atoms = atomsForKeys(keys) - yield* Effect.forEach(atoms, captureAtomQueryParentSpan, { discard: true, concurrency: "inherit" }) - if (atoms.length === 0) return - yield* Effect.forEach(atoms, (atom) => Effect.sync(() => defaultRegistry.refresh(atom)), { - discard: true + Effect + .gen(function*() { + const atoms = atomsForKeys(keys) + yield* Effect.forEach(atoms, captureAtomQueryParentSpan, { discard: true, concurrency: "unbounded" }) + if (atoms.length === 0) return + yield* Effect.forEach(atoms, (atom) => Effect.sync(() => defaultRegistry.refresh(atom)), { + discard: true + }) + yield* Effect.forEach(atoms, (a) => awaitAtomResult(defaultRegistry, a).pipe(Effect.exit)) }) - yield* Effect.forEach(atoms, (a) => awaitAtomResult(defaultRegistry, a).pipe(Effect.exit)) - }) + .pipe(Effect.orDie) const isPlainObject = (o: unknown): o is Record => { if (typeof o !== "object" || o === null) return false diff --git a/packages/vue/src/form.ts b/packages/vue/src/form.ts index b477e73525..6348d2eb65 100644 --- a/packages/vue/src/form.ts +++ b/packages/vue/src/form.ts @@ -289,20 +289,22 @@ function buildFieldInfo( return custom(err, e, v) } - // parse specific error types for better translation support - const integerMatch = err.match(/Expected.*integer.*(?:actual|got)\s+([^)]+)/i) + // parse specific error types for better translation support. + // beta.107 formatters often omit ", actual/got " from the issue string; + // fall back to the rejected input `v` when the regex has no capture. + const integerMatch = err.match(/Expected.*integer(?:.*(?:actual|got)\s+([^)\s]+))?/i) if (integerMatch) { return translate.value( { defaultMessage: "Expected an integer, actual {actualValue}", id: "validation.integer.expected" }, - { actualValue: integerMatch[1] } + { actualValue: integerMatch[1] ?? String(v) } ) } - const numberMatch = err.match(/Expected.*number.*(?:actual|got)\s+([^)]+)/i) + const numberMatch = err.match(/Expected.*number(?:.*(?:actual|got)\s+([^)\s]+))?/i) if (numberMatch) { return translate.value( { defaultMessage: "Expected a number, actual {actualValue}", id: "validation.number.expected" }, - { actualValue: numberMatch[1] } + { actualValue: numberMatch[1] ?? String(v) } ) } diff --git a/packages/vue/src/internal/tanstackQuery.ts b/packages/vue/src/internal/tanstackQuery.ts index ec43a26eac..e829ffb1a2 100644 --- a/packages/vue/src/internal/tanstackQuery.ts +++ b/packages/vue/src/internal/tanstackQuery.ts @@ -101,26 +101,30 @@ export const makeTanstackQueryClient = () => new QueryClient() export const makeTanstackQueryInvalidator = (queryClient: QueryClient): QueryInvalidator => ({ invalidateAndAwait: (keys) => - Effect.gen(function*() { - const span = yield* Effect.currentParentSpan.pipe(Effect.orElseSucceed(() => undefined)) - yield* Effect.forEach( - keys, - (queryKey) => { - const options = { updateMeta: { span } } - return Effect.promise(() => queryClient.invalidateQueries({ queryKey }, options)) - }, - { discard: true, concurrency: "inherit" } - ) - }), + Effect + .gen(function*() { + const span = yield* Effect.currentParentSpan.pipe(Effect.orElseSucceed(() => undefined)) + yield* Effect.forEach( + keys, + (queryKey) => { + const options = { updateMeta: { span } } + return Effect.promise(() => queryClient.invalidateQueries({ queryKey }, options)) + }, + { discard: true, concurrency: "unbounded" } + ) + }) + .pipe(Effect.orDie), invalidateSoft: (keys) => - Effect.gen(function*() { - const span = yield* Effect.currentParentSpan.pipe(Effect.orElseSucceed(() => undefined)) - yield* Effect.forEach( - keys, - (queryKey) => Effect.promise(() => queryClient.invalidateQueries({ queryKey }, { updateMeta: { span } })), - { discard: true, concurrency: "inherit" } - ) - }) + Effect + .gen(function*() { + const span = yield* Effect.currentParentSpan.pipe(Effect.orElseSucceed(() => undefined)) + yield* Effect.forEach( + keys, + (queryKey) => Effect.promise(() => queryClient.invalidateQueries({ queryKey }, { updateMeta: { span } })), + { discard: true, concurrency: "unbounded" } + ) + }) + .pipe(Effect.orDie) }) const fullQueryKey = ( diff --git a/packages/vue/src/mutate.ts b/packages/vue/src/mutate.ts index abf6329a73..edc09d2198 100644 --- a/packages/vue/src/mutate.ts +++ b/packages/vue/src/mutate.ts @@ -135,13 +135,13 @@ export const combineQueryInvalidators = ( Effect.forEach( invalidators, (invalidator) => invalidator.invalidateAndAwait(keys), - { discard: true, concurrency: "inherit" } + { discard: true, concurrency: "unbounded" } ), invalidateSoft: (keys) => Effect.forEach( invalidators, (invalidator) => invalidator.invalidateSoft?.(keys) ?? Effect.void, - { discard: true, concurrency: "inherit" } + { discard: true, concurrency: "unbounded" } ) }) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a62835a1c7..04496f0885 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6,7 +6,7 @@ settings: overrides: date-fns: ^4.4.0 - fast-check: ^4.8.0 + fast-check: ^4.9.0 vue: ^3.5.35 patchedDependencies: @@ -23,8 +23,8 @@ importers: specifier: ^4.4.0 version: 4.4.0 fast-check: - specifier: ^4.8.0 - version: 4.8.0 + specifier: ^4.9.0 + version: 4.9.0 vue: specifier: ^3.5.35 version: 3.5.35(typescript@6.0.3(patch_hash=6a20b8df080eb8a9c34008e1802ecfd839f4e90e07f1ee97f78694dbd95a520e)) @@ -45,14 +45,14 @@ importers: specifier: 0.86.2 version: 0.86.2 "@effect/platform-node": - specifier: 4.0.0-beta.90 - version: 4.0.0-beta.90(effect@4.0.0-beta.90)(ioredis@5.9.3(supports-color@8.1.1)) + specifier: 4.0.0-beta.107 + version: 4.0.0-beta.107(effect@4.0.0-beta.107)(ioredis@5.9.3(supports-color@8.1.1)) "@effect/tsgo": specifier: ^0.31.0 version: 0.31.0 "@effect/vitest": - specifier: 4.0.0-beta.90 - version: 4.0.0-beta.90(effect@4.0.0-beta.90)(vitest@4.1.7(@opentelemetry/api@1.9.1)(@types/node@25.9.1)(jsdom@29.1.1)(vite@8.0.15(@types/node@25.9.1)(esbuild@0.28.0)(sass@1.100.0)(terser@5.45.0)(tsx@4.22.4)(yaml@2.9.0))) + specifier: 4.0.0-beta.107 + version: 4.0.0-beta.107(effect@4.0.0-beta.107)(vitest@4.1.7(@opentelemetry/api@1.9.1)(@types/node@25.9.1)(jsdom@29.1.1)(vite@8.0.15(@types/node@25.9.1)(esbuild@0.28.0)(sass@1.100.0)(terser@5.45.0)(tsx@4.22.4)(yaml@2.9.0))) "@tsconfig/strictest": specifier: ^2.0.8 version: 2.0.8 @@ -72,8 +72,8 @@ importers: specifier: ^0.54.0 version: 0.54.0 effect: - specifier: 4.0.0-beta.90 - version: 4.0.0-beta.90 + specifier: 4.0.0-beta.107 + version: 4.0.0-beta.107 effect-app: specifier: workspace:* version: link:packages/effect-app @@ -117,11 +117,11 @@ importers: packages/cli: dependencies: "@effect/platform-node": - specifier: 4.0.0-beta.90 - version: 4.0.0-beta.90(effect@4.0.0-beta.90)(ioredis@5.9.3(supports-color@8.1.1)) + specifier: 4.0.0-beta.107 + version: 4.0.0-beta.107(effect@4.0.0-beta.107)(ioredis@5.9.3(supports-color@8.1.1)) effect: - specifier: 4.0.0-beta.90 - version: 4.0.0-beta.90 + specifier: 4.0.0-beta.107 + version: 4.0.0-beta.107 js-yaml: specifier: 4.2.0 version: 4.2.0 @@ -153,14 +153,14 @@ importers: version: link:../effect-app devDependencies: "@effect/atom-vue": - specifier: ^4.0.0-beta.90 - version: 4.0.0-beta.90(effect@4.0.0-beta.90)(vue@3.5.35(typescript@6.0.3(patch_hash=6a20b8df080eb8a9c34008e1802ecfd839f4e90e07f1ee97f78694dbd95a520e))) + specifier: ^4.0.0-beta.107 + version: 4.0.0-beta.107(effect@4.0.0-beta.107)(vue@3.5.35(typescript@6.0.3(patch_hash=6a20b8df080eb8a9c34008e1802ecfd839f4e90e07f1ee97f78694dbd95a520e))) "@effect/platform-node": - specifier: 4.0.0-beta.90 - version: 4.0.0-beta.90(effect@4.0.0-beta.90)(ioredis@5.9.3(supports-color@8.1.1)) + specifier: 4.0.0-beta.107 + version: 4.0.0-beta.107(effect@4.0.0-beta.107)(ioredis@5.9.3(supports-color@8.1.1)) "@effect/vitest": - specifier: 4.0.0-beta.90 - version: 4.0.0-beta.90(effect@4.0.0-beta.90)(vitest@4.1.7(@opentelemetry/api@1.9.1)(@types/node@25.9.1)(jsdom@29.1.1)(vite@8.0.15(@types/node@25.9.1)(esbuild@0.28.0)(sass@1.100.0)(terser@5.45.0)(tsx@4.22.4)(yaml@2.9.0))) + specifier: 4.0.0-beta.107 + version: 4.0.0-beta.107(effect@4.0.0-beta.107)(vitest@4.1.7(@opentelemetry/api@1.9.1)(@types/node@25.9.1)(jsdom@29.1.1)(vite@8.0.15(@types/node@25.9.1)(esbuild@0.28.0)(sass@1.100.0)(terser@5.45.0)(tsx@4.22.4)(yaml@2.9.0))) "@tanstack/vue-query": specifier: 5.96.2 version: 5.96.2(vue@3.5.35(typescript@6.0.3(patch_hash=6a20b8df080eb8a9c34008e1802ecfd839f4e90e07f1ee97f78694dbd95a520e))) @@ -171,8 +171,8 @@ importers: specifier: ^6.0.7 version: 6.0.7(vite@8.0.15(@types/node@25.9.1)(esbuild@0.28.0)(sass@1.100.0)(terser@5.45.0)(tsx@4.22.4)(yaml@2.9.0))(vue@3.5.35(typescript@6.0.3(patch_hash=6a20b8df080eb8a9c34008e1802ecfd839f4e90e07f1ee97f78694dbd95a520e))) effect: - specifier: ^4.0.0-beta.90 - version: 4.0.0-beta.90 + specifier: ^4.0.0-beta.107 + version: 4.0.0-beta.107 typescript: specifier: ~6.0.3 version: 6.0.3(patch_hash=6a20b8df080eb8a9c34008e1802ecfd839f4e90e07f1ee97f78694dbd95a520e) @@ -192,8 +192,8 @@ importers: specifier: ^4.4.0 version: 4.4.0 effect: - specifier: ^4.0.0-beta.90 - version: 4.0.0-beta.90 + specifier: ^4.0.0-beta.107 + version: 4.0.0-beta.107 nanoid: specifier: ^5.1.11 version: 5.1.11 @@ -220,8 +220,8 @@ importers: specifier: ^13.15.10 version: 13.15.10 fast-check: - specifier: ^4.8.0 - version: 4.8.0 + specifier: ^4.9.0 + version: 4.9.0 typescript: specifier: ~6.0.3 version: 6.0.3(patch_hash=6a20b8df080eb8a9c34008e1802ecfd839f4e90e07f1ee97f78694dbd95a520e) @@ -331,8 +331,8 @@ importers: packages/infra: dependencies: "@effect/vitest": - specifier: ^4.0.0-beta.90 - version: 4.0.0-beta.90(effect@4.0.0-beta.90)(vitest@4.1.7(@opentelemetry/api@1.9.1)(@types/node@25.9.1)(jsdom@29.1.1)(vite@8.0.15(@types/node@25.9.1)(esbuild@0.28.0)(sass@1.100.0)(terser@5.45.0)(tsx@4.22.4)(yaml@2.9.0))) + specifier: ^4.0.0-beta.107 + version: 4.0.0-beta.107(effect@4.0.0-beta.107)(vitest@4.1.7(@opentelemetry/api@1.9.1)(@types/node@25.9.1)(jsdom@29.1.1)(vite@8.0.15(@types/node@25.9.1)(esbuild@0.28.0)(sass@1.100.0)(terser@5.45.0)(tsx@4.22.4)(yaml@2.9.0))) "@faker-js/faker": specifier: ^8.4.1 version: 8.4.1 @@ -343,14 +343,14 @@ importers: specifier: ^8.1.6 version: 8.1.6(patch_hash=3d2dd1cd5f52d3eef1772e4be48776258a596f9277c00adf0a471f54fcd3e21a)(debug@4.4.3(supports-color@8.1.1)) effect: - specifier: ^4.0.0-beta.90 - version: 4.0.0-beta.90 + specifier: ^4.0.0-beta.107 + version: 4.0.0-beta.107 effect-app: specifier: workspace:* version: link:../effect-app fast-check: - specifier: ^4.8.0 - version: 4.8.0 + specifier: ^4.9.0 + version: 4.9.0 jose: specifier: ^6.2.3 version: 6.2.3 @@ -371,8 +371,8 @@ importers: specifier: ^7.9.5 version: 7.9.5(supports-color@8.1.1) "@effect/sql-sqlite-node": - specifier: 4.0.0-beta.90 - version: 4.0.0-beta.90(effect@4.0.0-beta.90) + specifier: 4.0.0-beta.107 + version: 4.0.0-beta.107(effect@4.0.0-beta.107) "@sentry/node": specifier: 10.55.0 version: 10.55.0(supports-color@8.1.1) @@ -420,11 +420,11 @@ importers: packages/vue: dependencies: "@effect/atom-vue": - specifier: ^4.0.0-beta.90 - version: 4.0.0-beta.90(effect@4.0.0-beta.90)(vue@3.5.35(typescript@6.0.3(patch_hash=6a20b8df080eb8a9c34008e1802ecfd839f4e90e07f1ee97f78694dbd95a520e))) + specifier: ^4.0.0-beta.107 + version: 4.0.0-beta.107(effect@4.0.0-beta.107)(vue@3.5.35(typescript@6.0.3(patch_hash=6a20b8df080eb8a9c34008e1802ecfd839f4e90e07f1ee97f78694dbd95a520e))) "@effect/platform-browser": - specifier: ^4.0.0-beta.90 - version: 4.0.0-beta.90(effect@4.0.0-beta.90) + specifier: ^4.0.0-beta.107 + version: 4.0.0-beta.107(effect@4.0.0-beta.107) "@formatjs/intl": specifier: ^4.1.12 version: 4.1.12 @@ -441,8 +441,8 @@ importers: specifier: ^5.4.4 version: 5.4.4 effect: - specifier: ^4.0.0-beta.90 - version: 4.0.0-beta.90 + specifier: ^4.0.0-beta.107 + version: 4.0.0-beta.107 effect-app: specifier: workspace:* version: link:../effect-app @@ -454,8 +454,8 @@ importers: version: 3.5.35(typescript@6.0.3(patch_hash=6a20b8df080eb8a9c34008e1802ecfd839f4e90e07f1ee97f78694dbd95a520e)) devDependencies: "@effect/vitest": - specifier: 4.0.0-beta.90 - version: 4.0.0-beta.90(effect@4.0.0-beta.90)(vitest@4.1.7(@opentelemetry/api@1.9.1)(@types/node@25.9.1)(jsdom@29.1.1)(vite@8.0.15(@types/node@25.9.1)(esbuild@0.28.0)(sass@1.100.0)(terser@5.45.0)(tsx@4.22.4)(yaml@2.9.0))) + specifier: 4.0.0-beta.107 + version: 4.0.0-beta.107(effect@4.0.0-beta.107)(vitest@4.1.7(@opentelemetry/api@1.9.1)(@types/node@25.9.1)(jsdom@29.1.1)(vite@8.0.15(@types/node@25.9.1)(esbuild@0.28.0)(sass@1.100.0)(terser@5.45.0)(tsx@4.22.4)(yaml@2.9.0))) "@formatjs/icu-messageformat-parser": specifier: ^3.5.10 version: 3.5.10 @@ -494,8 +494,8 @@ importers: specifier: ^1.32.0 version: 1.32.0(vue@3.5.35(typescript@6.0.3(patch_hash=6a20b8df080eb8a9c34008e1802ecfd839f4e90e07f1ee97f78694dbd95a520e))) effect: - specifier: ^4.0.0-beta.90 - version: 4.0.0-beta.90 + specifier: ^4.0.0-beta.107 + version: 4.0.0-beta.107 effect-app: specifier: workspace:* version: link:../effect-app @@ -1188,12 +1188,12 @@ packages: } hasBin: true - "@effect/atom-vue@4.0.0-beta.90": + "@effect/atom-vue@4.0.0-beta.107": resolution: { - integrity: sha512-pZDLJ5mpQS9JepHhnDJ7SuR4G/tGJzaKbro6z58/8diFeqxWRQ6TiSbUZjrpneLtmKP5sGggttQhsG5Ghui5Hg==, + integrity: sha512-RQh5iZapR3rTgO6KHBLUTj4bJh8LkYPWomwIXHzMu8JkRh7JD7+IctVfyKAz/YD1hNur6XvCoQmcS/CI0EQAqw==, } peerDependencies: - effect: ^4.0.0-beta.90 + effect: ^4.0.0-beta.107 vue: ^3.5.35 "@effect/language-service@0.86.2": @@ -1202,12 +1202,20 @@ packages: } hasBin: true - "@effect/platform-browser@4.0.0-beta.90": + "@effect/platform-browser@4.0.0-beta.107": resolution: { - integrity: sha512-1/Q1a2rze/74B23dsh3CM5/JVlM9wmIwg+0+fsmaGeFDr8V3Tl/y2U3aS//JG2HaBUqb0n/e7C2LUewWHrfyag==, + integrity: sha512-PGNHndbY0tRyqWfZZJWQMdQdh8Q1D9qfOnlI2EV2claAmNG7iGs+qWSUTBoCQo7MBgfJOYIC8M/AKq1ZfFWJuw==, } peerDependencies: - effect: ^4.0.0-beta.90 + effect: ^4.0.0-beta.107 + + "@effect/platform-node-shared@4.0.0-beta.107": + resolution: { + integrity: sha512-y6BqcRi86BfTJv+tvDrob4ozYVHxxlHYcn/zIQqZjXI9CvKnkgD6ng+38G1o45c4f2ucU+6HRI9POCmFdMoVGA==, + } + engines: { node: ">=18.0.0" } + peerDependencies: + effect: ^4.0.0-beta.107 "@effect/platform-node-shared@4.0.0-beta.86": resolution: { @@ -1217,13 +1225,14 @@ packages: peerDependencies: effect: ^4.0.0-beta.86 - "@effect/platform-node-shared@4.0.0-beta.90": + "@effect/platform-node@4.0.0-beta.107": resolution: { - integrity: sha512-0UlZgqJdAjcEr6RtLSqeOtTBeuLO03cTX4FKpzW1nTCScz3wGpELgcaU/T+fURf4LBiD0QQS3tcOYTN/T+qFsw==, + integrity: sha512-k+6YNbV4Ck0L6YXtlgkvEnuP5tlxWD8EeWOrpn46PDqbGEwt4ONpRltTwm3tn2cyBXD0i+2P11cUH/6sdFagTA==, } engines: { node: ">=18.0.0" } peerDependencies: - effect: ^4.0.0-beta.90 + effect: ^4.0.0-beta.107 + ioredis: ">=5.7.0 <6.0.0" "@effect/platform-node@4.0.0-beta.86": resolution: { @@ -1234,21 +1243,12 @@ packages: effect: ^4.0.0-beta.86 ioredis: ^5.7.0 - "@effect/platform-node@4.0.0-beta.90": + "@effect/sql-sqlite-node@4.0.0-beta.107": resolution: { - integrity: sha512-c7/1+ULH3Np8vhOQdYU9iP2RoXnyGqFxbyitccs5YDQgRGNU24isvdhtI/1gCRjZJXDXOmLPi36IKlPoKxwefA==, + integrity: sha512-hfYptkItEs/AdEIPEDkziC1TbnC7FyGISowKIvaZtscoWyxPaUtWsxM8EYgfOJ61aZwGgKQxZlXIgJ0fw25Lng==, } - engines: { node: ">=18.0.0" } peerDependencies: - effect: ^4.0.0-beta.90 - ioredis: ^5.7.0 - - "@effect/sql-sqlite-node@4.0.0-beta.90": - resolution: { - integrity: sha512-u+ATyOKz203kFrazeBnEUglFQmX8tyQRs1FJmq8RtOd4VYK9Qn6MlQO0cnyTOxmUxWjannv4qRoU33B8Om/Dbg==, - } - peerDependencies: - effect: ^4.0.0-beta.90 + effect: ^4.0.0-beta.107 "@effect/tsgo-darwin-arm64@0.31.0": resolution: { @@ -1305,13 +1305,13 @@ packages: } hasBin: true - "@effect/vitest@4.0.0-beta.90": + "@effect/vitest@4.0.0-beta.107": resolution: { - integrity: sha512-eWSkDg34TjsrSVfrU0PkR8YN/AeeM0kt792/RCobCVTx5SGO/iYwyqyjl/73nP8Q5rMM6UTepaBCzK7D/H1tlg==, + integrity: sha512-n4/qsx4DnT4dEI/wNgMivxyUeJoeiU1TCSz0WnoHWk/dny40Oxjip2P9IXGQDgPb9fsYVnerF0QRA6nPUuExQA==, } peerDependencies: - effect: ^4.0.0-beta.90 - vitest: ^3.0.0 || ^4.0.0 + effect: ^4.0.0-beta.107 + vitest: ">=4.1.0 <5.0.0" "@emnapi/core@1.10.0": resolution: { @@ -2167,6 +2167,13 @@ packages: cpu: [arm64] os: [darwin] + "@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.4": + resolution: { + integrity: sha512-LCkGo6JDfaBhgST7UpPWgNgLINpcpabaHfyz5OBx75nUYxBsaEPxjnyNjWpeb/xBup/682QnBfRBy2/LvPutZQ==, + } + cpu: [arm64] + os: [darwin] + "@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.3": resolution: { integrity: sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw==, @@ -2174,6 +2181,13 @@ packages: cpu: [x64] os: [darwin] + "@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.4": + resolution: { + integrity: sha512-zExlW9zUJKZH/tOtVMttwjKa4Xm/3KcNjnE3dPN92uCktwavMxpgCA3MoJK/DOnTWsQgo224OaST27/mPNAf+w==, + } + cpu: [x64] + os: [darwin] + "@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.3": resolution: { integrity: sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg==, @@ -2181,6 +2195,13 @@ packages: cpu: [arm64] os: [linux] + "@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.4": + resolution: { + integrity: sha512-dgX0P/9wGPJeHFBG+ZmhgE6bmtMt7NP5CRBGyyktpopdk/mW4POnrpQsSLtKI1dwpc+pPLuXHDh6vvskyQE/sw==, + } + cpu: [arm64] + os: [linux] + "@msgpackr-extract/msgpackr-extract-linux-arm@3.0.3": resolution: { integrity: sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw==, @@ -2188,6 +2209,13 @@ packages: cpu: [arm] os: [linux] + "@msgpackr-extract/msgpackr-extract-linux-arm@3.0.4": + resolution: { + integrity: sha512-Tg3yX65f5GbtXLkrYEHE5oibZG9epyYWas7FogTTEJeDEF9JlXJzKgXaNhT3UXlTOeA+AfZpYZYZ0uPj7Cfquw==, + } + cpu: [arm] + os: [linux] + "@msgpackr-extract/msgpackr-extract-linux-x64@3.0.3": resolution: { integrity: sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg==, @@ -2195,6 +2223,13 @@ packages: cpu: [x64] os: [linux] + "@msgpackr-extract/msgpackr-extract-linux-x64@3.0.4": + resolution: { + integrity: sha512-8TNXMEjJc3QEy7R/x1INhgiU+XakDAFUzBhaz7+Rbrs8NH5UQeHQxxmzsSBJGyV6I1jW79undiQm8tOI+D+8FQ==, + } + cpu: [x64] + os: [linux] + "@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3": resolution: { integrity: sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ==, @@ -2202,6 +2237,13 @@ packages: cpu: [x64] os: [win32] + "@msgpackr-extract/msgpackr-extract-win32-x64@3.0.4": + resolution: { + integrity: sha512-CmCXPQrkbwExx3j946/PtHWHbYJiCRBRDl4BlkRQcJB/YOwQxJRTpoo7aTsortjgoJ1x7opzTSxn7C+ASSLVjQ==, + } + cpu: [x64] + os: [win32] + "@napi-rs/wasm-runtime@1.1.4": resolution: { integrity: sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==, @@ -5688,14 +5730,14 @@ packages: engines: { node: ">=14" } hasBin: true - effect@4.0.0-beta.86: + effect@4.0.0-beta.107: resolution: { - integrity: sha512-+Y16hy3LhAju/FM3dz3Xp+aGi8dw9AtEzswcvwxREpeEPq+rL8TFS1PgWAO1+/3kKKH4bq9NdBGtfhHzM2K7Ow==, + integrity: sha512-OoBAv8eF+yanc+C6xhgEUnWeXUSHA6ynnscYqpkAY9GSnzZWystsIjBowVqCkLpHGlnRtdIqYT3wHwpOY6JDnQ==, } - effect@4.0.0-beta.90: + effect@4.0.0-beta.86: resolution: { - integrity: sha512-A0U3OE+2oyK/iFG6VYbFj9gwjJ7rFXjgP7qV+m7n/4lOREp9Lfk1///SlGCpX7HRueOCZO1l7aW0KByXuJeiPA==, + integrity: sha512-+Y16hy3LhAju/FM3dz3Xp+aGi8dw9AtEzswcvwxREpeEPq+rL8TFS1PgWAO1+/3kKKH4bq9NdBGtfhHzM2K7Ow==, } electron-to-chromium@1.5.267: @@ -6050,9 +6092,9 @@ packages: integrity: sha512-UOiS2in6/Q0FK0R0q6UY9vYpQ21mr/Qn1KOnte7vsACuNJf514WvCCUHSRCPcgjPT2bAhNIJdlE6bVap1GKmeg==, } - fast-check@4.8.0: + fast-check@4.9.0: resolution: { - integrity: sha512-GOJ158CUMnN6cSahsv4+ExARvIDuzzinFjkp0E9WtiBa5zcVeLozVkWaE4IzFcc+Y48Wp1EDlUZsXRyAztQcSg==, + integrity: sha512-7ms6T7SybUev/PQITciI0yLM2pOSFy5zpG8Ty7tQofcVaQUvrMXp6CBwqF6fThLCLOrfBtuHAtwq6Yu4XPCllg==, } engines: { node: ">=12.17.0" } @@ -7444,11 +7486,22 @@ packages: } hasBin: true + msgpackr-extract@3.0.4: + resolution: { + integrity: sha512-4kmO/MdyUIkLIvTPr8VHLil4AtoKIoniWPIEk5+CDy0xnWC84azhSFmuJ7PxZdsYtiP5kEeQsORAVIeMgxT+Hw==, + } + hasBin: true + msgpackr@2.0.1: resolution: { integrity: sha512-9J+tqTEsbHqY8YohazYgty7LgerFIWxvMLpUjqETSmjHojtJm2WnX2kK/2a1fLI7CO7ERP1YSEUXMucz4j+yBA==, } + msgpackr@2.0.5: + resolution: { + integrity: sha512-cef05H/dSYpLpqp3sj/qyZh5vhUYCalnaLO7j1yOmpsR0y/XwLVtK7r5gn+U/F7CTEfMowcGhlUQJDLcLf7jcA==, + } + muggle-string@0.4.1: resolution: { integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==, @@ -9057,6 +9110,12 @@ packages: } engines: { node: ">=20.18.1" } + undici@8.10.0: + resolution: { + integrity: sha512-HvltHd7avK13QIw/oLe4qoOLyoVSoafqJ2jYOrtMRBkbYT31eiBQ8O0ehRKZiEZCMEyLFQNIADpgCWC5fALvYQ==, + } + engines: { node: ">=22.19.0" } + undici@8.3.0: resolution: { integrity: sha512-TkUDgb6tl7KOGZ+7e8E3d2FYgUQgF6z5YypqjWmixVQSQERFcVrVg0ySADm2LVLRh5ljAaHTCR5Fmz3Q34rB7Q==, @@ -9148,6 +9207,12 @@ packages: } hasBin: true + uuid@14.0.1: + resolution: { + integrity: sha512-6ZxzVpzDXDa3bJWaHilVayA+BH/1zmxCJoVgvmqJnid/gPoKHxUrS/aC/T6LGQtNHT+XHG9fXPJB4d+IrU30Ew==, + } + hasBin: true + validator@13.15.35: resolution: { integrity: sha512-TQ5pAGhd5whStmqWvYF4OjQROlmv9SMFVt37qoCBdqRffuuklWYQlCNnEs2ZaIBD1kZRNnikiZOS1eqgkar0iw==, @@ -9598,6 +9663,20 @@ packages: utf-8-validate: optional: true + ws@8.21.3: + resolution: { + integrity: sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==, + } + engines: { node: ">=10.0.0" } + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ">=5.0.2" + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + wsl-utils@0.1.0: resolution: { integrity: sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==, @@ -10247,51 +10326,50 @@ snapshots: - ioredis - utf-8-validate - "@effect/atom-vue@4.0.0-beta.90(effect@4.0.0-beta.90)(vue@3.5.35(typescript@6.0.3(patch_hash=6a20b8df080eb8a9c34008e1802ecfd839f4e90e07f1ee97f78694dbd95a520e)))": + "@effect/atom-vue@4.0.0-beta.107(effect@4.0.0-beta.107)(vue@3.5.35(typescript@6.0.3(patch_hash=6a20b8df080eb8a9c34008e1802ecfd839f4e90e07f1ee97f78694dbd95a520e)))": dependencies: - effect: 4.0.0-beta.90 + effect: 4.0.0-beta.107 vue: 3.5.35(typescript@6.0.3(patch_hash=6a20b8df080eb8a9c34008e1802ecfd839f4e90e07f1ee97f78694dbd95a520e)) "@effect/language-service@0.86.2": {} - "@effect/platform-browser@4.0.0-beta.90(effect@4.0.0-beta.90)": + "@effect/platform-browser@4.0.0-beta.107(effect@4.0.0-beta.107)": dependencies: - effect: 4.0.0-beta.90 - multipasta: 0.2.7 + effect: 4.0.0-beta.107 - "@effect/platform-node-shared@4.0.0-beta.86(effect@4.0.0-beta.86)": + "@effect/platform-node-shared@4.0.0-beta.107(effect@4.0.0-beta.107)": dependencies: "@types/ws": 8.18.1 - effect: 4.0.0-beta.86 - ws: 8.20.0 + effect: 4.0.0-beta.107 + ws: 8.21.3 transitivePeerDependencies: - bufferutil - utf-8-validate - "@effect/platform-node-shared@4.0.0-beta.90(effect@4.0.0-beta.90)": + "@effect/platform-node-shared@4.0.0-beta.86(effect@4.0.0-beta.86)": dependencies: "@types/ws": 8.18.1 - effect: 4.0.0-beta.90 + effect: 4.0.0-beta.86 ws: 8.20.0 transitivePeerDependencies: - bufferutil - utf-8-validate - "@effect/platform-node@4.0.0-beta.86(effect@4.0.0-beta.86)(ioredis@5.9.3(supports-color@8.1.1))": + "@effect/platform-node@4.0.0-beta.107(effect@4.0.0-beta.107)(ioredis@5.9.3(supports-color@8.1.1))": dependencies: - "@effect/platform-node-shared": 4.0.0-beta.86(effect@4.0.0-beta.86) - effect: 4.0.0-beta.86 + "@effect/platform-node-shared": 4.0.0-beta.107(effect@4.0.0-beta.107) + effect: 4.0.0-beta.107 ioredis: 5.9.3(supports-color@8.1.1) mime: 4.1.0 - undici: 8.3.0 + undici: 8.10.0 transitivePeerDependencies: - bufferutil - utf-8-validate - "@effect/platform-node@4.0.0-beta.90(effect@4.0.0-beta.90)(ioredis@5.9.3(supports-color@8.1.1))": + "@effect/platform-node@4.0.0-beta.86(effect@4.0.0-beta.86)(ioredis@5.9.3(supports-color@8.1.1))": dependencies: - "@effect/platform-node-shared": 4.0.0-beta.90(effect@4.0.0-beta.90) - effect: 4.0.0-beta.90 + "@effect/platform-node-shared": 4.0.0-beta.86(effect@4.0.0-beta.86) + effect: 4.0.0-beta.86 ioredis: 5.9.3(supports-color@8.1.1) mime: 4.1.0 undici: 8.3.0 @@ -10299,10 +10377,9 @@ snapshots: - bufferutil - utf-8-validate - "@effect/sql-sqlite-node@4.0.0-beta.90(effect@4.0.0-beta.90)": + "@effect/sql-sqlite-node@4.0.0-beta.107(effect@4.0.0-beta.107)": dependencies: - better-sqlite3: 12.10.0 - effect: 4.0.0-beta.90 + effect: 4.0.0-beta.107 "@effect/tsgo-darwin-arm64@0.31.0": optional: true @@ -10335,9 +10412,9 @@ snapshots: "@effect/tsgo-win32-arm64": 0.31.0 "@effect/tsgo-win32-x64": 0.31.0 - "@effect/vitest@4.0.0-beta.90(effect@4.0.0-beta.90)(vitest@4.1.7(@opentelemetry/api@1.9.1)(@types/node@25.9.1)(jsdom@29.1.1)(vite@8.0.15(@types/node@25.9.1)(esbuild@0.28.0)(sass@1.100.0)(terser@5.45.0)(tsx@4.22.4)(yaml@2.9.0)))": + "@effect/vitest@4.0.0-beta.107(effect@4.0.0-beta.107)(vitest@4.1.7(@opentelemetry/api@1.9.1)(@types/node@25.9.1)(jsdom@29.1.1)(vite@8.0.15(@types/node@25.9.1)(esbuild@0.28.0)(sass@1.100.0)(terser@5.45.0)(tsx@4.22.4)(yaml@2.9.0)))": dependencies: - effect: 4.0.0-beta.90 + effect: 4.0.0-beta.107 vitest: 4.1.7(@opentelemetry/api@1.9.1)(@types/node@25.9.1)(jsdom@29.1.1)(vite@8.0.15(@types/node@25.9.1)(esbuild@0.28.0)(sass@1.100.0)(terser@5.45.0)(tsx@4.22.4)(yaml@2.9.0)) "@emnapi/core@1.10.0": @@ -10752,21 +10829,39 @@ snapshots: "@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3": optional: true + "@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.4": + optional: true + "@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.3": optional: true + "@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.4": + optional: true + "@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.3": optional: true + "@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.4": + optional: true + "@msgpackr-extract/msgpackr-extract-linux-arm@3.0.3": optional: true + "@msgpackr-extract/msgpackr-extract-linux-arm@3.0.4": + optional: true + "@msgpackr-extract/msgpackr-extract-linux-x64@3.0.3": optional: true + "@msgpackr-extract/msgpackr-extract-linux-x64@3.0.4": + optional: true + "@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3": optional: true + "@msgpackr-extract/msgpackr-extract-win32-x64@3.0.4": + optional: true + "@napi-rs/wasm-runtime@1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)": dependencies: "@emnapi/core": 1.10.0 @@ -12948,23 +13043,18 @@ snapshots: minimatch: 9.0.1 semver: 7.7.3 - effect@4.0.0-beta.86: + effect@4.0.0-beta.107: dependencies: "@standard-schema/spec": 1.1.0 - fast-check: 4.8.0 - find-my-way-ts: 0.1.6 - ini: 7.0.0 + fast-check: 4.9.0 kubernetes-types: 1.30.0 - msgpackr: 2.0.1 - multipasta: 0.2.7 - toml: 4.1.1 - uuid: 14.0.0 - yaml: 2.9.0 + msgpackr: 2.0.5 + uuid: 14.0.1 - effect@4.0.0-beta.90: + effect@4.0.0-beta.86: dependencies: "@standard-schema/spec": 1.1.0 - fast-check: 4.8.0 + fast-check: 4.9.0 find-my-way-ts: 0.1.6 ini: 7.0.0 kubernetes-types: 1.30.0 @@ -13367,7 +13457,7 @@ snapshots: extendable-error@0.1.7: {} - fast-check@4.8.0: + fast-check@4.9.0: dependencies: pure-rand: 8.4.0 @@ -14294,10 +14384,26 @@ snapshots: "@msgpackr-extract/msgpackr-extract-win32-x64": 3.0.3 optional: true + msgpackr-extract@3.0.4: + dependencies: + node-gyp-build-optional-packages: 5.2.2 + optionalDependencies: + "@msgpackr-extract/msgpackr-extract-darwin-arm64": 3.0.4 + "@msgpackr-extract/msgpackr-extract-darwin-x64": 3.0.4 + "@msgpackr-extract/msgpackr-extract-linux-arm": 3.0.4 + "@msgpackr-extract/msgpackr-extract-linux-arm64": 3.0.4 + "@msgpackr-extract/msgpackr-extract-linux-x64": 3.0.4 + "@msgpackr-extract/msgpackr-extract-win32-x64": 3.0.4 + optional: true + msgpackr@2.0.1: optionalDependencies: msgpackr-extract: 3.0.3 + msgpackr@2.0.5: + optionalDependencies: + msgpackr-extract: 3.0.4 + muggle-string@0.4.1: {} multipasta@0.2.7: {} @@ -15597,6 +15703,8 @@ snapshots: undici@7.25.0: {} + undici@8.10.0: {} + undici@8.3.0: {} unist-util-is@6.0.1: @@ -15669,6 +15777,8 @@ snapshots: uuid@14.0.0: {} + uuid@14.0.1: {} + validator@13.15.35: {} vfile-message@4.0.3: @@ -16047,6 +16157,8 @@ snapshots: ws@8.20.0: {} + ws@8.21.3: {} + wsl-utils@0.1.0: dependencies: is-wsl: 3.1.0 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 48ae2f1dfd..af34629485 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -18,7 +18,7 @@ minimumReleaseAgeExclude: - "@effect/tsgo@0.31.0" overrides: date-fns: ^4.4.0 - fast-check: ^4.8.0 + fast-check: ^4.9.0 vue: ^3.5.35 patchedDependencies: ts-plugin-sort-import-suggestions: patches/ts-plugin-sort-import-suggestions.patch diff --git a/repos/effect/.agents/AGENTS.md b/repos/effect/.agents/AGENTS.md new file mode 100644 index 0000000000..c7bb67c037 --- /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/grill-me/SKILL.md b/repos/effect/.agents/skills/grill-me/SKILL.md deleted file mode 100644 index d65f67b937..0000000000 --- a/repos/effect/.agents/skills/grill-me/SKILL.md +++ /dev/null @@ -1,43 +0,0 @@ ---- -name: grill-me -description: Interview the user about a plan or design until reaching shared understanding, resolving each branch of the decision tree. Use when user wants to stress-test a plan, get grilled on their design, or mentions "grill me". ---- - -Interview me about every aspect of this plan until we reach a shared understanding and a defensible design. - -Ask exactly one question at a time, then wait for my answer before asking the next question. - -Use each answer to choose the next highest-leverage unresolved question. Maintain an implicit decision tree of resolved decisions, open questions, assumptions, dependencies, risks, and rejected alternatives. - -For each question, include: - -- clear answer options when appropriate -- your recommended answer, marked as recommended -- a brief reason for the recommendation - -Use open-ended questions when fixed options would prematurely constrain the design space. - -Challenge vague, inconsistent, risky, or unsupported assumptions. If an answer creates a contradiction or unresolved dependency, ask a follow-up before moving on. - -Cover, as relevant: - -- goals and non-goals -- users and stakeholders -- constraints -- alternatives -- APIs and interfaces -- data model -- error handling -- security -- observability -- testing -- migration and rollout -- failure modes -- operational ownership -- success criteria - -If repository facts are needed, inspect the codebase instead of asking the user. Do not ask me to provide information that can be determined locally. - -When an available user-input tool such as `request_user_input` fits the question, use it to ask one short question with a small set of mutually exclusive options. Otherwise, ask in plain text and present clear possible answers as a numbered list when that helps me answer quickly. Include your recommended option and mark it as recommended. - -Stop when the major branches of the design tree have been resolved. Then summarize the agreed design, remaining risks, assumptions, rejected alternatives, and next steps. diff --git a/repos/effect/.agents/skills/jsdocs/SKILL.md b/repos/effect/.agents/skills/jsdocs/SKILL.md index 10b368bfef..a2972d8d87 100644 --- a/repos/effect/.agents/skills/jsdocs/SKILL.md +++ b/repos/effect/.agents/skills/jsdocs/SKILL.md @@ -39,7 +39,7 @@ Use a normal multiline JSDoc comment in TypeScript source: * * Optional prose explaining the example. * - * ```ts + * ```ts import.meta.vitest * const result = example() * ``` * @@ -126,6 +126,63 @@ Use a normal multiline JSDoc comment in TypeScript source: - 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: @@ -198,6 +255,6 @@ When refining an existing public API module, always do a dedicated `**Gotchas**` Run the narrowest validation that matches the change: -- For JSDoc or example changes in a package with generated docs, run `pnpm docgen` from that package directory. +- 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/.changeset/config.json b/repos/effect/.changeset/config.json index b45ea0c70c..0a755e5f06 100644 --- a/repos/effect/.changeset/config.json +++ b/repos/effect/.changeset/config.json @@ -1,7 +1,8 @@ { - "$schema": "https://unpkg.com/@changesets/config@1.6.4/schema.json", - "changelog": ["@changesets/changelog-github", { "repo": "Effect-TS/effect-smol" }], + "$schema": "https://unpkg.com/@changesets/config@4.0.0-next.8/schema.json", + "changelog": ["@changesets/changelog-github", { "repo": "Effect-TS/effect" }], "commit": false, + "format": false, "linked": [], "access": "restricted", "baseBranch": "main", @@ -14,7 +15,35 @@ "fixed": [ [ "effect", - "@effect/*" + "@effect/ai-anthropic", + "@effect/ai-openai", + "@effect/ai-openai-compat", + "@effect/ai-openrouter", + "@effect/atom-react", + "@effect/atom-solid", + "@effect/atom-vue", + "@effect/docgen", + "@effect/doctest", + "@effect/openapi-generator", + "@effect/opentelemetry", + "@effect/platform-browser", + "@effect/platform-bun", + "@effect/platform-deno", + "@effect/platform-node", + "@effect/platform-node-shared", + "@effect/sql-clickhouse", + "@effect/sql-d1", + "@effect/sql-libsql", + "@effect/sql-mssql", + "@effect/sql-mysql2", + "@effect/sql-pg", + "@effect/sql-pglite", + "@effect/sql-sqlite-bun", + "@effect/sql-sqlite-do", + "@effect/sql-sqlite-node", + "@effect/sql-sqlite-react-native", + "@effect/sql-sqlite-wasm", + "@effect/vitest" ] ], "snapshot": { diff --git a/repos/effect/.changeset/fix-otel-logger-clock-skew.md b/repos/effect/.changeset/fix-otel-logger-clock-skew.md deleted file mode 100644 index 8e6ae29679..0000000000 --- a/repos/effect/.changeset/fix-otel-logger-clock-skew.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"@effect/opentelemetry": patch ---- - -Use monotonic clock for log timestamps to match span timestamps. - -The Logger used `Date.now()` (wall clock) for log `timestamp` while the Tracer used `clock.currentTimeNanosUnsafe()` (monotonic clock) for span `startTime`. This caused logs to appear before their parent span due to clock drift between the two sources. Both now use the same monotonic clock via `nanosToHrTime(clock.currentTimeNanosUnsafe())`. diff --git a/repos/effect/.changeset/pre.json b/repos/effect/.changeset/pre.json index 1f26858d16..f6799931c5 100644 --- a/repos/effect/.changeset/pre.json +++ b/repos/effect/.changeset/pre.json @@ -1,630 +1,4 @@ { "mode": "pre", - "tag": "beta", - "initialVersions": { - "@effect/ai-anthropic": "3.0.0", - "@effect/ai-openai": "3.0.0", - "@effect/ai-openrouter": "3.0.0", - "@effect/atom-react": "3.0.0", - "@effect/atom-solid": "3.0.0", - "@effect/atom-vue": "3.0.0", - "effect": "3.0.0", - "@effect/opentelemetry": "3.0.0", - "@effect/platform-browser": "3.0.0", - "@effect/platform-bun": "3.0.0", - "@effect/platform-node": "3.0.0", - "@effect/platform-node-shared": "3.0.0", - "@effect/sql-clickhouse": "3.0.0", - "@effect/sql-d1": "3.0.0", - "@effect/sql-libsql": "3.0.0", - "@effect/sql-mssql": "3.0.0", - "@effect/sql-mysql2": "3.0.0", - "@effect/sql-pg": "3.0.0", - "@effect/sql-sqlite-bun": "3.0.0", - "@effect/sql-sqlite-do": "3.0.0", - "@effect/sql-sqlite-node": "3.0.0", - "@effect/sql-sqlite-react-native": "3.0.0", - "@effect/sql-sqlite-wasm": "3.0.0", - "@effect/openapi-generator": "3.0.0", - "@effect/oxc": "3.0.0", - "@effect/utils": "3.0.0", - "@effect/vitest": "3.0.0", - "scratchpad": "0.0.0", - "scripts": "0.0.0", - "@effect/ai-openai-compat": "3.0.0", - "@effect/ai-codegen": "0.0.0", - "@effect/bundle": "0.0.0", - "ai-docs": "0.0.0", - "@effect/ai-docgen": "0.0.0", - "@effect/sql-pglite": "4.0.0-beta.52", - "@effect/jsdocs": "0.0.0" - }, - "changesets": [ - "add-adaptive-rate-limiter-store", - "add-bigdecimal-sumall-multiplyall", - "add-chunk-schema", - "add-command-hidden", - "add-config-nested", - "add-flag-hidden", - "add-from-string-schemas", - "add-headers-remove-many", - "add-indexeddb-kvs-layer", - "add-make-msgpack", - "add-make-option", - "add-missing-tx-modules", - "add-newtype-module", - "add-scalar-show-operation-id", - "add-schedule-tap", - "add-schema-annotate-encoded", - "add-schema-array-ensure", - "add-schema-bigdecimal", - "add-schema-datetime", - "add-schema-option-from-optional-nullor", - "add-schema-option-from-undefined-nullish", - "add-schema-string-encoding", - "add-sql-pglite", - "add-standard-jsdoc-rule", - "add-stream-broadcastn", - "add-unstable-encoding-export", - "add-values-unprepared", - "afraid-cobras-like", - "ai-openai-config-field-leak", - "ai-openai-file-nullable-fields", - "anthropic-4-6-structured-output", - "asyncresult-exhaustive", - "atom-stream-error-type", - "beige-paths-sort", - "better-apples-nail", - "better-rocks-arrive", - "big-pans-look", - "blue-dingos-greet", - "blue-ligers-cheat", - "blue-onions-smile", - "blue-ravens-type", - "blue-trams-kiss", - "bold-chairs-yawn", - "bold-planets-shout", - "breezy-meals-see", - "bright-bugs-bow", - "bright-canyons-clean", - "bright-dogs-fail", - "bright-flags-stand", - "bright-laws-teach", - "bright-lemons-dance", - "bright-planes-smash", - "bright-rats-attend", - "bright-toes-rush", - "bumpy-boxes-teach", - "busy-lions-sneeze", - "busy-maps-attend", - "calm-buckets-own", - "calm-carrots-march", - "calm-cars-rest", - "calm-panthers-nail", - "calm-seas-smile", - "calm-squids-hug", - "calm-tracers-sample", - "chatty-poets-type", - "chilled-mice-wash", - "chilly-pumas-rule", - "chubby-buckets-feel", - "chubby-parents-flow", - "chubby-planets-fall", - "clean-balloons-tan", - "clean-bulldogs-care", - "clean-dryers-sneeze", - "clean-geese-work", - "clean-goats-wave", - "clean-needles-shake", - "clean-tires-guess", - "clear-spies-boil", - "cli-help-choices", - "cold-knives-lie", - "cold-rooms-show", - "cold-sloths-wave", - "compact-json-schema-enum", - "config-withdefault-eager", - "consolidate-encoding", - "consolidate-sql-error", - "crisp-seas-warn", - "cuddly-rooms-bet", - "curly-poems-talk", - "curly-spies-relax", - "curvy-apples-float", - "curvy-birds-float", - "custom-http-security-openapi-generator", - "cute-heads-thank", - "cyan-loops-grow", - "cyan-radios-switch", - "deep-rivers-spend", - "dirty-lamps-trade", - "dirty-laws-wear", - "duration-temporal-object-input", - "eager-coats-cheat", - "early-birds-dream", - "early-donuts-argue", - "early-peaches-check", - "eff-691-default-logger-ordering", - "eff-693-rpcgroup-handler-deps", - "eff-694-cli-completions-module", - "eff-695-layer-mock-dual-api", - "eff-697-rpcserialization-json-array-decode", - "eff-698-rpcserialization-unreachable-branch", - "eff-700-httpapi-middleware-errors", - "eff-701-httpapierror-respondable", - "eff-704-stream-merge-predicate", - "eff-705-layer-tap-apis", - "eff-706-servicemap-mutate", - "eff-716-response-id-tracker-map", - "eff-717-openai-socket-cancel", - "eff-718-embedding-model-surface", - "eff-725-fix-catch-jsdoc", - "eff-726-model-dimensions", - "eff-727-cli-help-alignment", - "eff-730-language-model-incremental-fallback", - "eff-736-cached-with-ttl", - "eff-738-cron-prev", - "eff-739-openai-function-call-done", - "eff-740-missing-summary-parts", - "eff-742-http-client-request-web", - "eff-744-sqlite-migrator-lock", - "eff-746-fixed-iteration-catchup", - "eff-747-unify-effect", - "eff-754-url-builder-any", - "eff-755-references-core", - "eff-769-select-text-highlight", - "eff-774-mutable-list-append-all-empty-array", - "eff-777-schema-make-effect", - "eff-778-http-middleware-path-logger", - "eff-779-keyvaluestore-layer-sql", - "eff-780-layer-unify", - "eff-781-fix-stream-toqueue-types", - "eff-782-httpapi-status-literals", - "eff-783-atom-http-api-errors", - "eff-819-cluster-workflow-shard-groups", - "eff-849-transpose-option", - "eight-turkeys-own", - "eighty-lies-deny", - "eighty-poets-draw", - "eighty-swans-scream", - "eighty-teeth-sniff", - "eleven-apes-share", - "eleven-numbers-bake", - "empty-gifts-beg", - "empty-http-rpc-client", - "eventlog-unencrypted", - "every-olives-burn", - "expand-schema-filter-output", - "export-schema-encode-keys-interface", - "extract-semaphore-latch", - "fair-bees-relax", - "fair-buttons-share", - "fair-cooks-stop", - "fair-cups-train", - "fair-dryers-speak", - "fair-forks-shake", - "fair-pandas-prove", - "fair-pants-float", - "fair-poems-visit", - "famous-wolves-lead", - "fancy-glasses-grow", - "fast-times-camp", - "few-birds-matter", - "few-cougars-dig", - "few-foxes-grin", - "few-mirrors-pull", - "few-socks-poke", - "fiber-runtime-start-metrics", - "fiery-jokes-care", - "fiery-mammals-call", - "fine-walls-decide", - "first-success-of", - "five-parents-relax", - "five-worms-rhyme", - "fix-1332", - "fix-1917", - "fix-1927", - "fix-1940", - "fix-1947", - "fix-2002", - "fix-2012", - "fix-2015", - "fix-2260", - "fix-2268", - "fix-2271", - "fix-2384", - "fix-2414", - "fix-2419", - "fix-ai-empty-params-structured-output", - "fix-ai-text-toolkit-typing", - "fix-anthropic-caller-toolid", - "fix-catch-orelse-error-erasure", - "fix-class-constructor-defaults", - "fix-cli-mixed-global-flag-context", - "fix-config-array-default", - "fix-config-withDefault", - "fix-config-withdefault-filter", - "fix-cron-next-missing-day-overflow", - "fix-datetime-gmt", - "fix-devtools-flush-on-teardown", - "fix-duration-symmetric-rounding", - "fix-entity-manager-defect-replay", - "fix-entity-proxy-rpc-handler-context", - "fix-entity-proxy-server-path-params", - "fix-from-readable-stream-cancel-defect", - "fix-graph-undirected-traversal", - "fix-has-interrupts-only-empty", - "fix-hashmap-bit31-ordering", - "fix-headers-proto-enumerability", - "fix-http-incoming-message-parse-options", - "fix-http-tracer-response-cause", - "fix-httpapi-endpoint-error-inference", - "fix-httpapi-schema-types", - "fix-httpapi-security-middleware-cache", - "fix-is-json-dag", - "fix-json-schema-anyof-oneof-siblings", - "fix-json-schema-import-json", - "fix-keepalive-blocked-timers", - "fix-mcp-param-name-resolution", - "fix-mermaid-escape-special-chars", - "fix-mutable-list-filter-length", - "fix-object-keyword-json-schema", - "fix-openai-mcp-tool-names", - "fix-openapi-generator-form-urlencoded", - "fix-openapi-generator-swagger2openapi", - "fix-openapi-preserve-multiple-response-content-types", - "fix-openrouter-sparse-array", - "fix-otel-logger-clock-skew", - "fix-otel-logger-severity-number", - "fix-queue-collect-duplication", - "fix-random-string-seeds", - "fix-ratelimiter-tokenbucket-redis-ttl", - "fix-remainder-scientific-notation", - "fix-request-resolver-pending-batches-leak", - "fix-retry-transient-autocomplete", - "fix-rpc-http-requestids-finalizer", - "fix-rpc-json-id-edges", - "fix-rpc-unknown-tag-isolation", - "fix-schedule-fixed-double-exec", - "fix-schedule-reduce-sync-state", - "fix-schema-arbitrary-exclusive-bounds", - "fix-schema-defect-message", - "fix-schema-encode-keys-property-keys", - "fix-schema-encodekeys-class", - "fix-schema-encodekeys-struct", - "fix-schema-encoding-checks", - "fix-schema-identifier-expected-message", - "fix-schema-is-uuid", - "fix-schema-option-non-schema-failures", - "fix-schema-parser-checks", - "fix-searchparam-initial-decode", - "fix-serializable-wire-transfer", - "fix-stream-grouped-within-flush", - "fix-stream-run-for-each-while", - "fix-stream-scan-effect", - "fix-stream-scoped-scope", - "fix-strip-approval-artifacts-multi-round", - "fix-struct-utility-types-simplify", - "fix-structwithrest-index-signatures", - "fix-tagged-union-class-sentinels", - "fix-tagged-union-match-unify", - "fix-to-tagged-union-isanyof-custom-tags", - "fix-tuple-with-rest-post-rest-index-drift", - "fix-tuple-with-rest-post-rest-validation", - "fix-types-voidifempty", - "fix-void-response-encoding", - "fix-workflow-defect-reply-serialization", - "fix-workflow-proxy-rpc-handler-context", - "flat-chicken-remain", - "floppy-cows-spend", - "floppy-items-admire", - "floppy-pigs-kiss", - "fluffy-meals-matter", - "fluffy-pumas-push", - "forked-memo-maps", - "forty-hounds-cheer", - "forty-otters-cry", - "forty-rings-film", - "forty-signs-stay", - "forty-swans-divide", - "forty-trees-pay", - "four-papayas-bow", - "four-points-repeat", - "fresh-cats-smash", - "fresh-emus-cheat", - "fresh-monkeys-smoke", - "frozen-intrinsics-stack-trace-limit", - "fruity-houses-learn", - "full-adults-double", - "funny-crabs-hang", - "funny-forks-move", - "fuzzy-camels-hunt", - "fuzzy-crews-fold", - "fuzzy-dodos-help", - "fuzzy-lions-perform", - "fuzzy-planets-sneeze", - "gold-meteors-move", - "gold-readers-hug", - "gold-rings-start", - "good-tools-work", - "good-trees-pull", - "graph-algorithm-fixes", - "great-trains-mate", - "great-trams-report", - "green-beds-unref", - "green-chips-wash", - "green-moons-smile", - "green-pugs-play", - "green-rings-prove", - "happy-mirrors-dream", - "heavy-loops-cut", - "heavy-trams-fix", - "hip-socks-travel", - "honest-pens-thank", - "honest-rivers-notice", - "hot-taxis-fry", - "httpapi-endpoint-relax-constraints", - "httpapi-schema-service-types", - "huge-moons-rhyme", - "humble-pigs-dig", - "icy-flies-cross", - "itchy-radios-poke", - "itchy-results-bet", - "itchy-shrimps-deny", - "itchy-toes-promise", - "k8s-last-transition-null", - "khaki-cats-learn", - "khaki-melons-appear", - "kind-hounds-float", - "kind-windows-fall", - "late-hotels-rule", - "late-lamps-care", - "late-rivers-applaud", - "layer-map-dynamic-idle-ttl", - "lazy-queens-rush", - "lazy-recursive-forward-refs", - "lazy-timers-exist", - "legal-pants-drop", - "lemon-taxis-sin", - "light-kids-sneeze", - "little-dryers-allow", - "long-cameras-think", - "lovely-cobras-change", - "lovely-frogs-rescue", - "lucky-buttons-jump", - "lucky-phones-listen", - "lucky-worms-type", - "many-badgers-obey", - "mean-dingos-share", - "mean-trains-smash", - "metal-parts-yell", - "mighty-games-matter", - "modern-carrots-see", - "modern-uuid-guid-filter", - "nasty-geese-grow", - "neat-goats-wave", - "neat-kings-chew", - "neat-lions-rest", - "neat-snails-wash", - "neat-taxis-notice", - "neat-windows-buy", - "new-dogs-swim", - "new-toes-stop", - "ninety-geese-exist", - "odd-boats-think", - "odd-bulldogs-sleep", - "odd-fans-glow", - "odd-forks-talk", - "odd-laws-draw", - "odd-owls-smoke", - "odd-socks-boil", - "odd-suns-dance", - "old-brooms-cry", - "old-facts-stand", - "old-mirrors-float", - "olive-poems-visit", - "open-hotels-remain", - "openai-compat-nullable-tool-name", - "openai-compat-reasoning", - "openapi-generator-sse-constraint-decoder", - "openrouter-input-audio", - "otel-resource-env-precedence", - "perfect-buckets-tickle", - "petite-months-allow", - "platform-crypto-service", - "platform-node-shared-barrel", - "plenty-moons-pull", - "polite-brooms-tickle", - "polite-pigs-speak", - "polite-tables-kneel", - "port-react-hydration", - "public-deer-ring", - "public-jeans-stop", - "pubsub-publish-false", - "puny-pens-clap", - "purple-bars-prove", - "purple-schools-float", - "purple-turtles-draw", - "quick-dragons-fix", - "quick-falcons-travel", - "quick-geese-relax", - "quick-lamps-dig", - "quick-lizards-fall", - "quick-trees-join", - "quiet-carpets-grin", - "quiet-files-hunt", - "quiet-lamps-jam", - "quiet-radios-wave", - "quiet-redis-scripts", - "quiet-tigers-yell", - "random-choice", - "ready-olives-divide", - "real-trains-ring", - "red-pigs-repair", - "redacted-representation-options", - "refactor-cli-global-flags", - "refactor-config-provider", - "refactor-representation-references", - "remove-effect-transactionwith", - "remove-http-span-counter", - "remove-nullor", - "remove-openapi-fromapi-options", - "remove-schema-stringtree-keep-declarations", - "remove-types-mergerecord", - "remove-unused-utils-apis", - "rename-rebuild-out", - "restore-schema-parse-options", - "rich-dots-push", - "rich-hoops-nail", - "rich-sloths-draw", - "ripe-lies-battle", - "rpc-client-http-early-close", - "rpc-middleware-provides-fix", - "scalar-custom-fetch", - "schema-as-class", - "schema-asserts-signature", - "schema-clean-up-additionalProperties", - "schema-datetime-utc-from-string", - "schema-decoding-defaults-services", - "schema-defaults-issue-channel", - "schema-dollar-prefix", - "schema-lazy-bottom", - "schema-missing-self-generic", - "schema-ordered-arbitrary-constraints", - "schema-parser-adapter-errors", - "schema-refactor-toCodecJson", - "schema-remove-annotate-in", - "schema-rename-makeUnsafe-to-make", - "schema-rename-parser-makeUnsafe", - "schema-result-combinators", - "schema-struct-simplify", - "semantic-matching", - "seven-mugs-marry", - "shaggy-birds-stay", - "shaggy-cities-push", - "shaggy-numbers-accept", - "sharp-emus-applaud", - "sharp-goats-wink", - "sharp-pandas-care", - "sharp-peas-march", - "sharp-rules-draw", - "sharp-singers-sort", - "shiny-trains-hug", - "short-cows-relate", - "short-foxes-admire", - "short-stamps-throw", - "shy-cycles-flow", - "shy-geckos-sniff", - "silent-geckos-matter", - "silent-needles-design", - "silent-plants-matter", - "silent-spoons-stare", - "silly-loops-tickle", - "silver-bulk-indexeddb", - "silver-emus-smoke", - "silver-kings-poke", - "silver-snails-sqlite", - "silver-wings-watch", - "six-cups-taste", - "sixty-mails-shout", - "sixty-socks-yell", - "slick-signs-wish", - "slick-toes-rush", - "slimy-planets-divide", - "slimy-turtles-juggle", - "slow-beans-battle", - "slow-berries-enjoy", - "small-bugs-hunt", - "small-crabs-care", - "small-pets-sit", - "smart-ducks-jump", - "smart-pillows-buy", - "smart-timers-fly", - "smart-tips-sort", - "social-pumas-prove", - "soft-comics-wink", - "soft-delete-sqlmodel", - "soft-seals-allow", - "solid-cougars-attack", - "solid-doors-ring", - "solid-items-tease", - "solid-towns-smoke", - "sour-canyons-rescue", - "sparkly-bears-act", - "sparkly-coins-sit", - "spotty-comics-fry", - "sql-migrator-mjs-mts", - "sqlite-bun-prepare-error-channel", - "sqlite-do-durable-object-transactions", - "stale-dots-tell", - "stale-snakes-know", - "strict-areas-end", - "strict-buckets-hug", - "strip-resolved-approvals", - "strong-balloons-tickle", - "strong-bees-queue", - "struct-record", - "sunny-ads-hang", - "sunny-bikes-sleep", - "sunny-rooms-invent", - "sweet-donuts-bet", - "sweet-hotels-give", - "sweet-views-learn", - "swift-spiders-unpack", - "swift-symbols-stand", - "tagged-error-class-optional-empty-props", - "tall-hairs-return", - "tall-mails-listen", - "tall-queens-cheer", - "tall-wombats-wave", - "tangy-colts-lose", - "tasty-comics-send", - "ten-kings-fry", - "thick-pandas-wait", - "thin-ducks-wonder", - "thirty-ducks-go", - "thirty-pans-love", - "three-corners-sort", - "three-ravens-jam", - "three-tomatoes-wave", - "tidy-foxes-own", - "tidy-icons-glow", - "tidy-stacks-encode", - "tidy-stars-drive", - "tiny-buckets-wave", - "tiny-lilies-flash", - "tiny-rabbits-smile", - "to-codec-json-schema", - "tocodecjson-return-json-type", - "tool-get-json-schema-tests", - "true-actors-battle", - "try-promise-catch-defect", - "twelve-dragons-move", - "twenty-buttons-cheer", - "two-roses-double", - "unify-error-defect-stack-options", - "update-schema-arbitrary-report", - "upset-colts-stick", - "use-url-can-parse", - "vast-bananas-send", - "vast-deserts-travel", - "violet-peaches-feel", - "vitest-layer-top-level-options", - "wacky-grapes-poke", - "wacky-rice-add", - "warm-dolls-brake", - "warm-friends-tie", - "warm-snails-shop", - "wet-news-invent", - "wild-readers-clean", - "wild-suns-bearer-space", - "wise-ants-wave", - "wise-flags-shift", - "wise-oranges-stay", - "witty-lobsters-share", - "yellow-adults-study", - "yellow-clocks-dance", - "yellow-dingos-jump", - "young-doors-change" - ] + "tag": "beta" } diff --git a/repos/effect/.changeset/add-adaptive-rate-limiter-store.md b/repos/effect/.changeset/pre/add-adaptive-rate-limiter-store.md similarity index 100% rename from repos/effect/.changeset/add-adaptive-rate-limiter-store.md rename to repos/effect/.changeset/pre/add-adaptive-rate-limiter-store.md 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 0000000000..c2600bfa50 --- /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/add-bigdecimal-sumall-multiplyall.md b/repos/effect/.changeset/pre/add-bigdecimal-sumall-multiplyall.md similarity index 100% rename from repos/effect/.changeset/add-bigdecimal-sumall-multiplyall.md rename to repos/effect/.changeset/pre/add-bigdecimal-sumall-multiplyall.md diff --git a/repos/effect/.changeset/add-chunk-schema.md b/repos/effect/.changeset/pre/add-chunk-schema.md similarity index 100% rename from repos/effect/.changeset/add-chunk-schema.md rename to repos/effect/.changeset/pre/add-chunk-schema.md diff --git a/repos/effect/.changeset/add-command-hidden.md b/repos/effect/.changeset/pre/add-command-hidden.md similarity index 100% rename from repos/effect/.changeset/add-command-hidden.md rename to repos/effect/.changeset/pre/add-command-hidden.md diff --git a/repos/effect/.changeset/add-config-nested.md b/repos/effect/.changeset/pre/add-config-nested.md similarity index 100% rename from repos/effect/.changeset/add-config-nested.md rename to repos/effect/.changeset/pre/add-config-nested.md 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 0000000000..6bd6a73dff --- /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 0000000000..2bf5753f8c --- /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 0000000000..b396bfd8c4 --- /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 0000000000..d726624284 --- /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 0000000000..da98c6486a --- /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/add-flag-hidden.md b/repos/effect/.changeset/pre/add-flag-hidden.md similarity index 100% rename from repos/effect/.changeset/add-flag-hidden.md rename to repos/effect/.changeset/pre/add-flag-hidden.md diff --git a/repos/effect/.changeset/add-from-string-schemas.md b/repos/effect/.changeset/pre/add-from-string-schemas.md similarity index 100% rename from repos/effect/.changeset/add-from-string-schemas.md rename to repos/effect/.changeset/pre/add-from-string-schemas.md diff --git a/repos/effect/.changeset/add-headers-remove-many.md b/repos/effect/.changeset/pre/add-headers-remove-many.md similarity index 100% rename from repos/effect/.changeset/add-headers-remove-many.md rename to repos/effect/.changeset/pre/add-headers-remove-many.md 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 0000000000..391894aa6a --- /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 0000000000..44d8044679 --- /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 0000000000..e91cccacc5 --- /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/add-indexeddb-kvs-layer.md b/repos/effect/.changeset/pre/add-indexeddb-kvs-layer.md similarity index 100% rename from repos/effect/.changeset/add-indexeddb-kvs-layer.md rename to repos/effect/.changeset/pre/add-indexeddb-kvs-layer.md 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 0000000000..440b16628c --- /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/add-make-msgpack.md b/repos/effect/.changeset/pre/add-make-msgpack.md similarity index 100% rename from repos/effect/.changeset/add-make-msgpack.md rename to repos/effect/.changeset/pre/add-make-msgpack.md diff --git a/repos/effect/.changeset/add-make-option.md b/repos/effect/.changeset/pre/add-make-option.md similarity index 100% rename from repos/effect/.changeset/add-make-option.md rename to repos/effect/.changeset/pre/add-make-option.md diff --git a/repos/effect/.changeset/add-missing-tx-modules.md b/repos/effect/.changeset/pre/add-missing-tx-modules.md similarity index 100% rename from repos/effect/.changeset/add-missing-tx-modules.md rename to repos/effect/.changeset/pre/add-missing-tx-modules.md diff --git a/repos/effect/.changeset/add-newtype-module.md b/repos/effect/.changeset/pre/add-newtype-module.md similarity index 100% rename from repos/effect/.changeset/add-newtype-module.md rename to repos/effect/.changeset/pre/add-newtype-module.md 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 0000000000..fbbd9b16aa --- /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 0000000000..9237eee680 --- /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/add-scalar-show-operation-id.md b/repos/effect/.changeset/pre/add-scalar-show-operation-id.md similarity index 100% rename from repos/effect/.changeset/add-scalar-show-operation-id.md rename to repos/effect/.changeset/pre/add-scalar-show-operation-id.md diff --git a/repos/effect/.changeset/add-schedule-tap.md b/repos/effect/.changeset/pre/add-schedule-tap.md similarity index 100% rename from repos/effect/.changeset/add-schedule-tap.md rename to repos/effect/.changeset/pre/add-schedule-tap.md diff --git a/repos/effect/.changeset/add-schema-annotate-encoded.md b/repos/effect/.changeset/pre/add-schema-annotate-encoded.md similarity index 100% rename from repos/effect/.changeset/add-schema-annotate-encoded.md rename to repos/effect/.changeset/pre/add-schema-annotate-encoded.md diff --git a/repos/effect/.changeset/add-schema-array-ensure.md b/repos/effect/.changeset/pre/add-schema-array-ensure.md similarity index 100% rename from repos/effect/.changeset/add-schema-array-ensure.md rename to repos/effect/.changeset/pre/add-schema-array-ensure.md diff --git a/repos/effect/.changeset/add-schema-bigdecimal.md b/repos/effect/.changeset/pre/add-schema-bigdecimal.md similarity index 100% rename from repos/effect/.changeset/add-schema-bigdecimal.md rename to repos/effect/.changeset/pre/add-schema-bigdecimal.md diff --git a/repos/effect/.changeset/add-schema-datetime.md b/repos/effect/.changeset/pre/add-schema-datetime.md similarity index 100% rename from repos/effect/.changeset/add-schema-datetime.md rename to repos/effect/.changeset/pre/add-schema-datetime.md 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 0000000000..d28c2c29a7 --- /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/add-schema-option-from-optional-nullor.md b/repos/effect/.changeset/pre/add-schema-option-from-optional-nullor.md similarity index 100% rename from repos/effect/.changeset/add-schema-option-from-optional-nullor.md rename to repos/effect/.changeset/pre/add-schema-option-from-optional-nullor.md diff --git a/repos/effect/.changeset/add-schema-option-from-undefined-nullish.md b/repos/effect/.changeset/pre/add-schema-option-from-undefined-nullish.md similarity index 100% rename from repos/effect/.changeset/add-schema-option-from-undefined-nullish.md rename to repos/effect/.changeset/pre/add-schema-option-from-undefined-nullish.md diff --git a/repos/effect/.changeset/add-schema-string-encoding.md b/repos/effect/.changeset/pre/add-schema-string-encoding.md similarity index 100% rename from repos/effect/.changeset/add-schema-string-encoding.md rename to repos/effect/.changeset/pre/add-schema-string-encoding.md 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 0000000000..5930ed631f --- /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 0000000000..8cb5182cc2 --- /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/add-sql-pglite.md b/repos/effect/.changeset/pre/add-sql-pglite.md similarity index 100% rename from repos/effect/.changeset/add-sql-pglite.md rename to repos/effect/.changeset/pre/add-sql-pglite.md diff --git a/repos/effect/.changeset/add-standard-jsdoc-rule.md b/repos/effect/.changeset/pre/add-standard-jsdoc-rule.md similarity index 100% rename from repos/effect/.changeset/add-standard-jsdoc-rule.md rename to repos/effect/.changeset/pre/add-standard-jsdoc-rule.md diff --git a/repos/effect/.changeset/add-stream-broadcastn.md b/repos/effect/.changeset/pre/add-stream-broadcastn.md similarity index 100% rename from repos/effect/.changeset/add-stream-broadcastn.md rename to repos/effect/.changeset/pre/add-stream-broadcastn.md 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 0000000000..959b435799 --- /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/add-unstable-encoding-export.md b/repos/effect/.changeset/pre/add-unstable-encoding-export.md similarity index 100% rename from repos/effect/.changeset/add-unstable-encoding-export.md rename to repos/effect/.changeset/pre/add-unstable-encoding-export.md 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 0000000000..95f49061ac --- /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/add-values-unprepared.md b/repos/effect/.changeset/pre/add-values-unprepared.md similarity index 100% rename from repos/effect/.changeset/add-values-unprepared.md rename to repos/effect/.changeset/pre/add-values-unprepared.md diff --git a/repos/effect/.changeset/afraid-cobras-like.md b/repos/effect/.changeset/pre/afraid-cobras-like.md similarity index 100% rename from repos/effect/.changeset/afraid-cobras-like.md rename to repos/effect/.changeset/pre/afraid-cobras-like.md diff --git a/repos/effect/.changeset/ai-openai-config-field-leak.md b/repos/effect/.changeset/pre/ai-openai-config-field-leak.md similarity index 100% rename from repos/effect/.changeset/ai-openai-config-field-leak.md rename to repos/effect/.changeset/pre/ai-openai-config-field-leak.md diff --git a/repos/effect/.changeset/ai-openai-file-nullable-fields.md b/repos/effect/.changeset/pre/ai-openai-file-nullable-fields.md similarity index 100% rename from repos/effect/.changeset/ai-openai-file-nullable-fields.md rename to repos/effect/.changeset/pre/ai-openai-file-nullable-fields.md diff --git a/repos/effect/.changeset/anthropic-4-6-structured-output.md b/repos/effect/.changeset/pre/anthropic-4-6-structured-output.md similarity index 100% rename from repos/effect/.changeset/anthropic-4-6-structured-output.md rename to repos/effect/.changeset/pre/anthropic-4-6-structured-output.md 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 0000000000..b5ab0393d6 --- /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 0000000000..f7e83ff6e0 --- /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 0000000000..ce9d74cbab --- /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/asyncresult-exhaustive.md b/repos/effect/.changeset/pre/asyncresult-exhaustive.md similarity index 100% rename from repos/effect/.changeset/asyncresult-exhaustive.md rename to repos/effect/.changeset/pre/asyncresult-exhaustive.md diff --git a/repos/effect/.changeset/atom-stream-error-type.md b/repos/effect/.changeset/pre/atom-stream-error-type.md similarity index 100% rename from repos/effect/.changeset/atom-stream-error-type.md rename to repos/effect/.changeset/pre/atom-stream-error-type.md 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 0000000000..3409edfb68 --- /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 0000000000..22cef1f7fb --- /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/beige-paths-sort.md b/repos/effect/.changeset/pre/beige-paths-sort.md similarity index 100% rename from repos/effect/.changeset/beige-paths-sort.md rename to repos/effect/.changeset/pre/beige-paths-sort.md diff --git a/repos/effect/.changeset/better-apples-nail.md b/repos/effect/.changeset/pre/better-apples-nail.md similarity index 100% rename from repos/effect/.changeset/better-apples-nail.md rename to repos/effect/.changeset/pre/better-apples-nail.md diff --git a/repos/effect/.changeset/better-rocks-arrive.md b/repos/effect/.changeset/pre/better-rocks-arrive.md similarity index 100% rename from repos/effect/.changeset/better-rocks-arrive.md rename to repos/effect/.changeset/pre/better-rocks-arrive.md 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 0000000000..95cb596409 --- /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/big-pans-look.md b/repos/effect/.changeset/pre/big-pans-look.md similarity index 100% rename from repos/effect/.changeset/big-pans-look.md rename to repos/effect/.changeset/pre/big-pans-look.md diff --git a/repos/effect/.changeset/blue-dingos-greet.md b/repos/effect/.changeset/pre/blue-dingos-greet.md similarity index 100% rename from repos/effect/.changeset/blue-dingos-greet.md rename to repos/effect/.changeset/pre/blue-dingos-greet.md diff --git a/repos/effect/.changeset/blue-ligers-cheat.md b/repos/effect/.changeset/pre/blue-ligers-cheat.md similarity index 100% rename from repos/effect/.changeset/blue-ligers-cheat.md rename to repos/effect/.changeset/pre/blue-ligers-cheat.md diff --git a/repos/effect/.changeset/blue-onions-smile.md b/repos/effect/.changeset/pre/blue-onions-smile.md similarity index 100% rename from repos/effect/.changeset/blue-onions-smile.md rename to repos/effect/.changeset/pre/blue-onions-smile.md 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 0000000000..7cb74e13f9 --- /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/blue-ravens-type.md b/repos/effect/.changeset/pre/blue-ravens-type.md similarity index 100% rename from repos/effect/.changeset/blue-ravens-type.md rename to repos/effect/.changeset/pre/blue-ravens-type.md diff --git a/repos/effect/.changeset/blue-trams-kiss.md b/repos/effect/.changeset/pre/blue-trams-kiss.md similarity index 100% rename from repos/effect/.changeset/blue-trams-kiss.md rename to repos/effect/.changeset/pre/blue-trams-kiss.md diff --git a/repos/effect/.changeset/bold-chairs-yawn.md b/repos/effect/.changeset/pre/bold-chairs-yawn.md similarity index 100% rename from repos/effect/.changeset/bold-chairs-yawn.md rename to repos/effect/.changeset/pre/bold-chairs-yawn.md diff --git a/repos/effect/.changeset/bold-planets-shout.md b/repos/effect/.changeset/pre/bold-planets-shout.md similarity index 100% rename from repos/effect/.changeset/bold-planets-shout.md rename to repos/effect/.changeset/pre/bold-planets-shout.md 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 0000000000..f9358594f9 --- /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 0000000000..21931bcb5e --- /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/breezy-meals-see.md b/repos/effect/.changeset/pre/breezy-meals-see.md similarity index 100% rename from repos/effect/.changeset/breezy-meals-see.md rename to repos/effect/.changeset/pre/breezy-meals-see.md diff --git a/repos/effect/.changeset/bright-bugs-bow.md b/repos/effect/.changeset/pre/bright-bugs-bow.md similarity index 100% rename from repos/effect/.changeset/bright-bugs-bow.md rename to repos/effect/.changeset/pre/bright-bugs-bow.md diff --git a/repos/effect/.changeset/bright-canyons-clean.md b/repos/effect/.changeset/pre/bright-canyons-clean.md similarity index 100% rename from repos/effect/.changeset/bright-canyons-clean.md rename to repos/effect/.changeset/pre/bright-canyons-clean.md 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 0000000000..8f3df8ee01 --- /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/bright-dogs-fail.md b/repos/effect/.changeset/pre/bright-dogs-fail.md similarity index 100% rename from repos/effect/.changeset/bright-dogs-fail.md rename to repos/effect/.changeset/pre/bright-dogs-fail.md diff --git a/repos/effect/.changeset/bright-flags-stand.md b/repos/effect/.changeset/pre/bright-flags-stand.md similarity index 100% rename from repos/effect/.changeset/bright-flags-stand.md rename to repos/effect/.changeset/pre/bright-flags-stand.md 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 0000000000..5c312a18f5 --- /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/bright-laws-teach.md b/repos/effect/.changeset/pre/bright-laws-teach.md similarity index 100% rename from repos/effect/.changeset/bright-laws-teach.md rename to repos/effect/.changeset/pre/bright-laws-teach.md diff --git a/repos/effect/.changeset/bright-lemons-dance.md b/repos/effect/.changeset/pre/bright-lemons-dance.md similarity index 100% rename from repos/effect/.changeset/bright-lemons-dance.md rename to repos/effect/.changeset/pre/bright-lemons-dance.md diff --git a/repos/effect/.changeset/bright-planes-smash.md b/repos/effect/.changeset/pre/bright-planes-smash.md similarity index 100% rename from repos/effect/.changeset/bright-planes-smash.md rename to repos/effect/.changeset/pre/bright-planes-smash.md diff --git a/repos/effect/.changeset/bright-rats-attend.md b/repos/effect/.changeset/pre/bright-rats-attend.md similarity index 100% rename from repos/effect/.changeset/bright-rats-attend.md rename to repos/effect/.changeset/pre/bright-rats-attend.md 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 0000000000..edead0f682 --- /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/bright-toes-rush.md b/repos/effect/.changeset/pre/bright-toes-rush.md similarity index 100% rename from repos/effect/.changeset/bright-toes-rush.md rename to repos/effect/.changeset/pre/bright-toes-rush.md 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 0000000000..2779fa9efc --- /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/bumpy-boxes-teach.md b/repos/effect/.changeset/pre/bumpy-boxes-teach.md similarity index 100% rename from repos/effect/.changeset/bumpy-boxes-teach.md rename to repos/effect/.changeset/pre/bumpy-boxes-teach.md diff --git a/repos/effect/.changeset/busy-lions-sneeze.md b/repos/effect/.changeset/pre/busy-lions-sneeze.md similarity index 100% rename from repos/effect/.changeset/busy-lions-sneeze.md rename to repos/effect/.changeset/pre/busy-lions-sneeze.md diff --git a/repos/effect/.changeset/busy-maps-attend.md b/repos/effect/.changeset/pre/busy-maps-attend.md similarity index 100% rename from repos/effect/.changeset/busy-maps-attend.md rename to repos/effect/.changeset/pre/busy-maps-attend.md 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 0000000000..ca77256cef --- /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/calm-buckets-own.md b/repos/effect/.changeset/pre/calm-buckets-own.md similarity index 100% rename from repos/effect/.changeset/calm-buckets-own.md rename to repos/effect/.changeset/pre/calm-buckets-own.md 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 0000000000..e7fe600cea --- /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/calm-carrots-march.md b/repos/effect/.changeset/pre/calm-carrots-march.md similarity index 100% rename from repos/effect/.changeset/calm-carrots-march.md rename to repos/effect/.changeset/pre/calm-carrots-march.md diff --git a/repos/effect/.changeset/calm-cars-rest.md b/repos/effect/.changeset/pre/calm-cars-rest.md similarity index 100% rename from repos/effect/.changeset/calm-cars-rest.md rename to repos/effect/.changeset/pre/calm-cars-rest.md 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 0000000000..9737718554 --- /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-dates-view.md b/repos/effect/.changeset/pre/calm-dates-view.md new file mode 100644 index 0000000000..b9e963edb8 --- /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 0000000000..d98e1edd6e --- /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-heads-close.md b/repos/effect/.changeset/pre/calm-heads-close.md new file mode 100644 index 0000000000..c717945ada --- /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 0000000000..8f7a1640a7 --- /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 0000000000..82f669b0eb --- /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 0000000000..b70fc9ae85 --- /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-pandas-retry.md b/repos/effect/.changeset/pre/calm-pandas-retry.md new file mode 100644 index 0000000000..b2372c6bd4 --- /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/calm-panthers-nail.md b/repos/effect/.changeset/pre/calm-panthers-nail.md similarity index 100% rename from repos/effect/.changeset/calm-panthers-nail.md rename to repos/effect/.changeset/pre/calm-panthers-nail.md 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 0000000000..a83413eeae --- /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-ravens-reflect.md b/repos/effect/.changeset/pre/calm-ravens-reflect.md new file mode 100644 index 0000000000..ba31e47c2b --- /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 0000000000..a707eadd98 --- /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 0000000000..896040c70d --- /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 0000000000..6bec17848a --- /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/calm-seas-smile.md b/repos/effect/.changeset/pre/calm-seas-smile.md similarity index 100% rename from repos/effect/.changeset/calm-seas-smile.md rename to repos/effect/.changeset/pre/calm-seas-smile.md 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 0000000000..504a98ce55 --- /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 0000000000..400dade634 --- /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/calm-squids-hug.md b/repos/effect/.changeset/pre/calm-squids-hug.md similarity index 100% rename from repos/effect/.changeset/calm-squids-hug.md rename to repos/effect/.changeset/pre/calm-squids-hug.md 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 0000000000..779bde415f --- /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 0000000000..3fdf6410e0 --- /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/calm-tracers-sample.md b/repos/effect/.changeset/pre/calm-tracers-sample.md similarity index 100% rename from repos/effect/.changeset/calm-tracers-sample.md rename to repos/effect/.changeset/pre/calm-tracers-sample.md 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 0000000000..b62d5bdc4b --- /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 0000000000..0422f5814a --- /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 0000000000..1b25564566 --- /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 0000000000..9b9ce459e9 --- /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 0000000000..96fceebf42 --- /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 0000000000..4f67ff43b2 --- /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/chatty-poets-type.md b/repos/effect/.changeset/pre/chatty-poets-type.md similarity index 100% rename from repos/effect/.changeset/chatty-poets-type.md rename to repos/effect/.changeset/pre/chatty-poets-type.md diff --git a/repos/effect/.changeset/chilled-mice-wash.md b/repos/effect/.changeset/pre/chilled-mice-wash.md similarity index 100% rename from repos/effect/.changeset/chilled-mice-wash.md rename to repos/effect/.changeset/pre/chilled-mice-wash.md diff --git a/repos/effect/.changeset/chilly-pumas-rule.md b/repos/effect/.changeset/pre/chilly-pumas-rule.md similarity index 100% rename from repos/effect/.changeset/chilly-pumas-rule.md rename to repos/effect/.changeset/pre/chilly-pumas-rule.md diff --git a/repos/effect/.changeset/chubby-buckets-feel.md b/repos/effect/.changeset/pre/chubby-buckets-feel.md similarity index 100% rename from repos/effect/.changeset/chubby-buckets-feel.md rename to repos/effect/.changeset/pre/chubby-buckets-feel.md diff --git a/repos/effect/.changeset/chubby-parents-flow.md b/repos/effect/.changeset/pre/chubby-parents-flow.md similarity index 100% rename from repos/effect/.changeset/chubby-parents-flow.md rename to repos/effect/.changeset/pre/chubby-parents-flow.md diff --git a/repos/effect/.changeset/chubby-planets-fall.md b/repos/effect/.changeset/pre/chubby-planets-fall.md similarity index 100% rename from repos/effect/.changeset/chubby-planets-fall.md rename to repos/effect/.changeset/pre/chubby-planets-fall.md diff --git a/repos/effect/.changeset/clean-balloons-tan.md b/repos/effect/.changeset/pre/clean-balloons-tan.md similarity index 100% rename from repos/effect/.changeset/clean-balloons-tan.md rename to repos/effect/.changeset/pre/clean-balloons-tan.md diff --git a/repos/effect/.changeset/clean-bulldogs-care.md b/repos/effect/.changeset/pre/clean-bulldogs-care.md similarity index 100% rename from repos/effect/.changeset/clean-bulldogs-care.md rename to repos/effect/.changeset/pre/clean-bulldogs-care.md 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 0000000000..b2d26821d8 --- /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/clean-dryers-sneeze.md b/repos/effect/.changeset/pre/clean-dryers-sneeze.md similarity index 100% rename from repos/effect/.changeset/clean-dryers-sneeze.md rename to repos/effect/.changeset/pre/clean-dryers-sneeze.md diff --git a/repos/effect/.changeset/clean-geese-work.md b/repos/effect/.changeset/pre/clean-geese-work.md similarity index 100% rename from repos/effect/.changeset/clean-geese-work.md rename to repos/effect/.changeset/pre/clean-geese-work.md diff --git a/repos/effect/.changeset/clean-goats-wave.md b/repos/effect/.changeset/pre/clean-goats-wave.md similarity index 100% rename from repos/effect/.changeset/clean-goats-wave.md rename to repos/effect/.changeset/pre/clean-goats-wave.md 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 0000000000..f725e3d199 --- /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/clean-needles-shake.md b/repos/effect/.changeset/pre/clean-needles-shake.md similarity index 100% rename from repos/effect/.changeset/clean-needles-shake.md rename to repos/effect/.changeset/pre/clean-needles-shake.md diff --git a/repos/effect/.changeset/clean-tires-guess.md b/repos/effect/.changeset/pre/clean-tires-guess.md similarity index 100% rename from repos/effect/.changeset/clean-tires-guess.md rename to repos/effect/.changeset/pre/clean-tires-guess.md 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 0000000000..ac42927cd3 --- /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/clear-spies-boil.md b/repos/effect/.changeset/pre/clear-spies-boil.md similarity index 100% rename from repos/effect/.changeset/clear-spies-boil.md rename to repos/effect/.changeset/pre/clear-spies-boil.md 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 0000000000..51de317462 --- /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 0000000000..6e0dbcd406 --- /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/cli-help-choices.md b/repos/effect/.changeset/pre/cli-help-choices.md similarity index 100% rename from repos/effect/.changeset/cli-help-choices.md rename to repos/effect/.changeset/pre/cli-help-choices.md 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 0000000000..0b7c9f2d6e --- /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 0000000000..2242910a88 --- /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/cold-knives-lie.md b/repos/effect/.changeset/pre/cold-knives-lie.md similarity index 100% rename from repos/effect/.changeset/cold-knives-lie.md rename to repos/effect/.changeset/pre/cold-knives-lie.md diff --git a/repos/effect/.changeset/cold-rooms-show.md b/repos/effect/.changeset/pre/cold-rooms-show.md similarity index 100% rename from repos/effect/.changeset/cold-rooms-show.md rename to repos/effect/.changeset/pre/cold-rooms-show.md diff --git a/repos/effect/.changeset/cold-sloths-wave.md b/repos/effect/.changeset/pre/cold-sloths-wave.md similarity index 100% rename from repos/effect/.changeset/cold-sloths-wave.md rename to repos/effect/.changeset/pre/cold-sloths-wave.md 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 0000000000..5108982921 --- /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/compact-json-schema-enum.md b/repos/effect/.changeset/pre/compact-json-schema-enum.md similarity index 100% rename from repos/effect/.changeset/compact-json-schema-enum.md rename to repos/effect/.changeset/pre/compact-json-schema-enum.md 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 0000000000..a671ebb39f --- /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/config-withdefault-eager.md b/repos/effect/.changeset/pre/config-withdefault-eager.md similarity index 100% rename from repos/effect/.changeset/config-withdefault-eager.md rename to repos/effect/.changeset/pre/config-withdefault-eager.md 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 0000000000..76c3172e7b --- /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/consolidate-encoding.md b/repos/effect/.changeset/pre/consolidate-encoding.md similarity index 100% rename from repos/effect/.changeset/consolidate-encoding.md rename to repos/effect/.changeset/pre/consolidate-encoding.md diff --git a/repos/effect/.changeset/consolidate-sql-error.md b/repos/effect/.changeset/pre/consolidate-sql-error.md similarity index 100% rename from repos/effect/.changeset/consolidate-sql-error.md rename to repos/effect/.changeset/pre/consolidate-sql-error.md 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 0000000000..52e90cd081 --- /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/crisp-seas-warn.md b/repos/effect/.changeset/pre/crisp-seas-warn.md similarity index 100% rename from repos/effect/.changeset/crisp-seas-warn.md rename to repos/effect/.changeset/pre/crisp-seas-warn.md 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 0000000000..9b84a1ced5 --- /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 0000000000..02a9fff682 --- /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 0000000000..3aea00e898 --- /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/cuddly-rooms-bet.md b/repos/effect/.changeset/pre/cuddly-rooms-bet.md similarity index 100% rename from repos/effect/.changeset/cuddly-rooms-bet.md rename to repos/effect/.changeset/pre/cuddly-rooms-bet.md 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 0000000000..aa2a20ade4 --- /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/curly-poems-talk.md b/repos/effect/.changeset/pre/curly-poems-talk.md similarity index 100% rename from repos/effect/.changeset/curly-poems-talk.md rename to repos/effect/.changeset/pre/curly-poems-talk.md 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 0000000000..f8af5ea829 --- /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/curly-spies-relax.md b/repos/effect/.changeset/pre/curly-spies-relax.md similarity index 100% rename from repos/effect/.changeset/curly-spies-relax.md rename to repos/effect/.changeset/pre/curly-spies-relax.md 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 0000000000..e2a08a9df1 --- /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/curvy-apples-float.md b/repos/effect/.changeset/pre/curvy-apples-float.md similarity index 100% rename from repos/effect/.changeset/curvy-apples-float.md rename to repos/effect/.changeset/pre/curvy-apples-float.md diff --git a/repos/effect/.changeset/curvy-birds-float.md b/repos/effect/.changeset/pre/curvy-birds-float.md similarity index 100% rename from repos/effect/.changeset/curvy-birds-float.md rename to repos/effect/.changeset/pre/curvy-birds-float.md 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 0000000000..c9dd275767 --- /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/custom-http-security-openapi-generator.md b/repos/effect/.changeset/pre/custom-http-security-openapi-generator.md similarity index 100% rename from repos/effect/.changeset/custom-http-security-openapi-generator.md rename to repos/effect/.changeset/pre/custom-http-security-openapi-generator.md diff --git a/repos/effect/.changeset/cute-heads-thank.md b/repos/effect/.changeset/pre/cute-heads-thank.md similarity index 100% rename from repos/effect/.changeset/cute-heads-thank.md rename to repos/effect/.changeset/pre/cute-heads-thank.md diff --git a/repos/effect/.changeset/cyan-loops-grow.md b/repos/effect/.changeset/pre/cyan-loops-grow.md similarity index 100% rename from repos/effect/.changeset/cyan-loops-grow.md rename to repos/effect/.changeset/pre/cyan-loops-grow.md diff --git a/repos/effect/.changeset/cyan-radios-switch.md b/repos/effect/.changeset/pre/cyan-radios-switch.md similarity index 100% rename from repos/effect/.changeset/cyan-radios-switch.md rename to repos/effect/.changeset/pre/cyan-radios-switch.md 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 0000000000..96d0ce0d7c --- /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 0000000000..779a545b75 --- /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/deduplicate-json-schema-fallbacks.md b/repos/effect/.changeset/pre/deduplicate-json-schema-fallbacks.md new file mode 100644 index 0000000000..1c5aa33498 --- /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/deep-rivers-spend.md b/repos/effect/.changeset/pre/deep-rivers-spend.md similarity index 100% rename from repos/effect/.changeset/deep-rivers-spend.md rename to repos/effect/.changeset/pre/deep-rivers-spend.md 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 0000000000..2b93ed38e6 --- /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/dirty-lamps-trade.md b/repos/effect/.changeset/pre/dirty-lamps-trade.md similarity index 100% rename from repos/effect/.changeset/dirty-lamps-trade.md rename to repos/effect/.changeset/pre/dirty-lamps-trade.md diff --git a/repos/effect/.changeset/dirty-laws-wear.md b/repos/effect/.changeset/pre/dirty-laws-wear.md similarity index 100% rename from repos/effect/.changeset/dirty-laws-wear.md rename to repos/effect/.changeset/pre/dirty-laws-wear.md 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 0000000000..cd06cfddb1 --- /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 0000000000..f28d728816 --- /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 0000000000..723f24935a --- /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/duration-temporal-object-input.md b/repos/effect/.changeset/pre/duration-temporal-object-input.md similarity index 100% rename from repos/effect/.changeset/duration-temporal-object-input.md rename to repos/effect/.changeset/pre/duration-temporal-object-input.md diff --git a/repos/effect/.changeset/eager-coats-cheat.md b/repos/effect/.changeset/pre/eager-coats-cheat.md similarity index 100% rename from repos/effect/.changeset/eager-coats-cheat.md rename to repos/effect/.changeset/pre/eager-coats-cheat.md diff --git a/repos/effect/.changeset/early-birds-dream.md b/repos/effect/.changeset/pre/early-birds-dream.md similarity index 100% rename from repos/effect/.changeset/early-birds-dream.md rename to repos/effect/.changeset/pre/early-birds-dream.md diff --git a/repos/effect/.changeset/early-donuts-argue.md b/repos/effect/.changeset/pre/early-donuts-argue.md similarity index 100% rename from repos/effect/.changeset/early-donuts-argue.md rename to repos/effect/.changeset/pre/early-donuts-argue.md 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 0000000000..f62abe0151 --- /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/early-peaches-check.md b/repos/effect/.changeset/pre/early-peaches-check.md similarity index 100% rename from repos/effect/.changeset/early-peaches-check.md rename to repos/effect/.changeset/pre/early-peaches-check.md 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 0000000000..f0d120781b --- /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 0000000000..9480039381 --- /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 0000000000..4576cfec24 --- /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 0000000000..0d587a2b83 --- /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 0000000000..d3f164a895 --- /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 0000000000..1ea8d267af --- /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 0000000000..746c3ff424 --- /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 0000000000..b7eb5a35ad --- /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 0000000000..c46576d615 --- /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 0000000000..c9ef160845 --- /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 0000000000..62766839ff --- /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 0000000000..292d3fed17 --- /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 0000000000..a2e2611642 --- /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 0000000000..1f1142a068 --- /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 0000000000..36d0328958 --- /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 0000000000..b079d9319f --- /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 0000000000..fb93845c99 --- /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 0000000000..ccc6b55a9f --- /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 0000000000..7050ceb33a --- /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 0000000000..f662266f92 --- /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 0000000000..fbfe9fde49 --- /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 0000000000..be71140d55 --- /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 0000000000..ea7b27d883 --- /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 0000000000..7073b7cb3b --- /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 0000000000..831d4f07b7 --- /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 0000000000..903a8ed19b --- /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 0000000000..847d5b566c --- /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 0000000000..dc4262d0cf --- /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 0000000000..be6a9a20e9 --- /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 0000000000..eae1e3bec8 --- /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 0000000000..a9bbb3d9cf --- /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 0000000000..bc9dba25a6 --- /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 0000000000..3d36a756c9 --- /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 0000000000..2b7a28424f --- /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 0000000000..992ca220f2 --- /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 0000000000..606ead5b46 --- /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 0000000000..a7e8aeb364 --- /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 0000000000..67198957ee --- /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 0000000000..a2ae20dd51 --- /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 0000000000..5bab011f5f --- /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 0000000000..8e02adb773 --- /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 0000000000..a7581b87b2 --- /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 0000000000..e4027e6b4a --- /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/eff-691-default-logger-ordering.md b/repos/effect/.changeset/pre/eff-691-default-logger-ordering.md similarity index 100% rename from repos/effect/.changeset/eff-691-default-logger-ordering.md rename to repos/effect/.changeset/pre/eff-691-default-logger-ordering.md diff --git a/repos/effect/.changeset/eff-693-rpcgroup-handler-deps.md b/repos/effect/.changeset/pre/eff-693-rpcgroup-handler-deps.md similarity index 100% rename from repos/effect/.changeset/eff-693-rpcgroup-handler-deps.md rename to repos/effect/.changeset/pre/eff-693-rpcgroup-handler-deps.md diff --git a/repos/effect/.changeset/eff-694-cli-completions-module.md b/repos/effect/.changeset/pre/eff-694-cli-completions-module.md similarity index 100% rename from repos/effect/.changeset/eff-694-cli-completions-module.md rename to repos/effect/.changeset/pre/eff-694-cli-completions-module.md diff --git a/repos/effect/.changeset/eff-695-layer-mock-dual-api.md b/repos/effect/.changeset/pre/eff-695-layer-mock-dual-api.md similarity index 100% rename from repos/effect/.changeset/eff-695-layer-mock-dual-api.md rename to repos/effect/.changeset/pre/eff-695-layer-mock-dual-api.md diff --git a/repos/effect/.changeset/eff-697-rpcserialization-json-array-decode.md b/repos/effect/.changeset/pre/eff-697-rpcserialization-json-array-decode.md similarity index 100% rename from repos/effect/.changeset/eff-697-rpcserialization-json-array-decode.md rename to repos/effect/.changeset/pre/eff-697-rpcserialization-json-array-decode.md diff --git a/repos/effect/.changeset/eff-698-rpcserialization-unreachable-branch.md b/repos/effect/.changeset/pre/eff-698-rpcserialization-unreachable-branch.md similarity index 100% rename from repos/effect/.changeset/eff-698-rpcserialization-unreachable-branch.md rename to repos/effect/.changeset/pre/eff-698-rpcserialization-unreachable-branch.md diff --git a/repos/effect/.changeset/eff-700-httpapi-middleware-errors.md b/repos/effect/.changeset/pre/eff-700-httpapi-middleware-errors.md similarity index 100% rename from repos/effect/.changeset/eff-700-httpapi-middleware-errors.md rename to repos/effect/.changeset/pre/eff-700-httpapi-middleware-errors.md diff --git a/repos/effect/.changeset/eff-701-httpapierror-respondable.md b/repos/effect/.changeset/pre/eff-701-httpapierror-respondable.md similarity index 100% rename from repos/effect/.changeset/eff-701-httpapierror-respondable.md rename to repos/effect/.changeset/pre/eff-701-httpapierror-respondable.md diff --git a/repos/effect/.changeset/eff-704-stream-merge-predicate.md b/repos/effect/.changeset/pre/eff-704-stream-merge-predicate.md similarity index 100% rename from repos/effect/.changeset/eff-704-stream-merge-predicate.md rename to repos/effect/.changeset/pre/eff-704-stream-merge-predicate.md diff --git a/repos/effect/.changeset/eff-705-layer-tap-apis.md b/repos/effect/.changeset/pre/eff-705-layer-tap-apis.md similarity index 100% rename from repos/effect/.changeset/eff-705-layer-tap-apis.md rename to repos/effect/.changeset/pre/eff-705-layer-tap-apis.md diff --git a/repos/effect/.changeset/eff-706-servicemap-mutate.md b/repos/effect/.changeset/pre/eff-706-servicemap-mutate.md similarity index 100% rename from repos/effect/.changeset/eff-706-servicemap-mutate.md rename to repos/effect/.changeset/pre/eff-706-servicemap-mutate.md diff --git a/repos/effect/.changeset/eff-716-response-id-tracker-map.md b/repos/effect/.changeset/pre/eff-716-response-id-tracker-map.md similarity index 100% rename from repos/effect/.changeset/eff-716-response-id-tracker-map.md rename to repos/effect/.changeset/pre/eff-716-response-id-tracker-map.md diff --git a/repos/effect/.changeset/eff-717-openai-socket-cancel.md b/repos/effect/.changeset/pre/eff-717-openai-socket-cancel.md similarity index 100% rename from repos/effect/.changeset/eff-717-openai-socket-cancel.md rename to repos/effect/.changeset/pre/eff-717-openai-socket-cancel.md diff --git a/repos/effect/.changeset/eff-718-embedding-model-surface.md b/repos/effect/.changeset/pre/eff-718-embedding-model-surface.md similarity index 100% rename from repos/effect/.changeset/eff-718-embedding-model-surface.md rename to repos/effect/.changeset/pre/eff-718-embedding-model-surface.md diff --git a/repos/effect/.changeset/eff-725-fix-catch-jsdoc.md b/repos/effect/.changeset/pre/eff-725-fix-catch-jsdoc.md similarity index 100% rename from repos/effect/.changeset/eff-725-fix-catch-jsdoc.md rename to repos/effect/.changeset/pre/eff-725-fix-catch-jsdoc.md diff --git a/repos/effect/.changeset/eff-726-model-dimensions.md b/repos/effect/.changeset/pre/eff-726-model-dimensions.md similarity index 100% rename from repos/effect/.changeset/eff-726-model-dimensions.md rename to repos/effect/.changeset/pre/eff-726-model-dimensions.md diff --git a/repos/effect/.changeset/eff-727-cli-help-alignment.md b/repos/effect/.changeset/pre/eff-727-cli-help-alignment.md similarity index 100% rename from repos/effect/.changeset/eff-727-cli-help-alignment.md rename to repos/effect/.changeset/pre/eff-727-cli-help-alignment.md diff --git a/repos/effect/.changeset/eff-730-language-model-incremental-fallback.md b/repos/effect/.changeset/pre/eff-730-language-model-incremental-fallback.md similarity index 100% rename from repos/effect/.changeset/eff-730-language-model-incremental-fallback.md rename to repos/effect/.changeset/pre/eff-730-language-model-incremental-fallback.md diff --git a/repos/effect/.changeset/eff-736-cached-with-ttl.md b/repos/effect/.changeset/pre/eff-736-cached-with-ttl.md similarity index 100% rename from repos/effect/.changeset/eff-736-cached-with-ttl.md rename to repos/effect/.changeset/pre/eff-736-cached-with-ttl.md diff --git a/repos/effect/.changeset/eff-738-cron-prev.md b/repos/effect/.changeset/pre/eff-738-cron-prev.md similarity index 100% rename from repos/effect/.changeset/eff-738-cron-prev.md rename to repos/effect/.changeset/pre/eff-738-cron-prev.md diff --git a/repos/effect/.changeset/eff-739-openai-function-call-done.md b/repos/effect/.changeset/pre/eff-739-openai-function-call-done.md similarity index 100% rename from repos/effect/.changeset/eff-739-openai-function-call-done.md rename to repos/effect/.changeset/pre/eff-739-openai-function-call-done.md diff --git a/repos/effect/.changeset/eff-740-missing-summary-parts.md b/repos/effect/.changeset/pre/eff-740-missing-summary-parts.md similarity index 100% rename from repos/effect/.changeset/eff-740-missing-summary-parts.md rename to repos/effect/.changeset/pre/eff-740-missing-summary-parts.md diff --git a/repos/effect/.changeset/eff-742-http-client-request-web.md b/repos/effect/.changeset/pre/eff-742-http-client-request-web.md similarity index 100% rename from repos/effect/.changeset/eff-742-http-client-request-web.md rename to repos/effect/.changeset/pre/eff-742-http-client-request-web.md diff --git a/repos/effect/.changeset/eff-744-sqlite-migrator-lock.md b/repos/effect/.changeset/pre/eff-744-sqlite-migrator-lock.md similarity index 100% rename from repos/effect/.changeset/eff-744-sqlite-migrator-lock.md rename to repos/effect/.changeset/pre/eff-744-sqlite-migrator-lock.md diff --git a/repos/effect/.changeset/eff-746-fixed-iteration-catchup.md b/repos/effect/.changeset/pre/eff-746-fixed-iteration-catchup.md similarity index 100% rename from repos/effect/.changeset/eff-746-fixed-iteration-catchup.md rename to repos/effect/.changeset/pre/eff-746-fixed-iteration-catchup.md diff --git a/repos/effect/.changeset/eff-747-unify-effect.md b/repos/effect/.changeset/pre/eff-747-unify-effect.md similarity index 100% rename from repos/effect/.changeset/eff-747-unify-effect.md rename to repos/effect/.changeset/pre/eff-747-unify-effect.md diff --git a/repos/effect/.changeset/eff-754-url-builder-any.md b/repos/effect/.changeset/pre/eff-754-url-builder-any.md similarity index 100% rename from repos/effect/.changeset/eff-754-url-builder-any.md rename to repos/effect/.changeset/pre/eff-754-url-builder-any.md diff --git a/repos/effect/.changeset/eff-755-references-core.md b/repos/effect/.changeset/pre/eff-755-references-core.md similarity index 100% rename from repos/effect/.changeset/eff-755-references-core.md rename to repos/effect/.changeset/pre/eff-755-references-core.md diff --git a/repos/effect/.changeset/eff-769-select-text-highlight.md b/repos/effect/.changeset/pre/eff-769-select-text-highlight.md similarity index 100% rename from repos/effect/.changeset/eff-769-select-text-highlight.md rename to repos/effect/.changeset/pre/eff-769-select-text-highlight.md diff --git a/repos/effect/.changeset/eff-774-mutable-list-append-all-empty-array.md b/repos/effect/.changeset/pre/eff-774-mutable-list-append-all-empty-array.md similarity index 100% rename from repos/effect/.changeset/eff-774-mutable-list-append-all-empty-array.md rename to repos/effect/.changeset/pre/eff-774-mutable-list-append-all-empty-array.md diff --git a/repos/effect/.changeset/eff-777-schema-make-effect.md b/repos/effect/.changeset/pre/eff-777-schema-make-effect.md similarity index 100% rename from repos/effect/.changeset/eff-777-schema-make-effect.md rename to repos/effect/.changeset/pre/eff-777-schema-make-effect.md diff --git a/repos/effect/.changeset/eff-778-http-middleware-path-logger.md b/repos/effect/.changeset/pre/eff-778-http-middleware-path-logger.md similarity index 100% rename from repos/effect/.changeset/eff-778-http-middleware-path-logger.md rename to repos/effect/.changeset/pre/eff-778-http-middleware-path-logger.md diff --git a/repos/effect/.changeset/eff-779-keyvaluestore-layer-sql.md b/repos/effect/.changeset/pre/eff-779-keyvaluestore-layer-sql.md similarity index 100% rename from repos/effect/.changeset/eff-779-keyvaluestore-layer-sql.md rename to repos/effect/.changeset/pre/eff-779-keyvaluestore-layer-sql.md diff --git a/repos/effect/.changeset/eff-780-layer-unify.md b/repos/effect/.changeset/pre/eff-780-layer-unify.md similarity index 100% rename from repos/effect/.changeset/eff-780-layer-unify.md rename to repos/effect/.changeset/pre/eff-780-layer-unify.md diff --git a/repos/effect/.changeset/eff-781-fix-stream-toqueue-types.md b/repos/effect/.changeset/pre/eff-781-fix-stream-toqueue-types.md similarity index 100% rename from repos/effect/.changeset/eff-781-fix-stream-toqueue-types.md rename to repos/effect/.changeset/pre/eff-781-fix-stream-toqueue-types.md diff --git a/repos/effect/.changeset/eff-782-httpapi-status-literals.md b/repos/effect/.changeset/pre/eff-782-httpapi-status-literals.md similarity index 100% rename from repos/effect/.changeset/eff-782-httpapi-status-literals.md rename to repos/effect/.changeset/pre/eff-782-httpapi-status-literals.md diff --git a/repos/effect/.changeset/eff-783-atom-http-api-errors.md b/repos/effect/.changeset/pre/eff-783-atom-http-api-errors.md similarity index 100% rename from repos/effect/.changeset/eff-783-atom-http-api-errors.md rename to repos/effect/.changeset/pre/eff-783-atom-http-api-errors.md diff --git a/repos/effect/.changeset/eff-819-cluster-workflow-shard-groups.md b/repos/effect/.changeset/pre/eff-819-cluster-workflow-shard-groups.md similarity index 100% rename from repos/effect/.changeset/eff-819-cluster-workflow-shard-groups.md rename to repos/effect/.changeset/pre/eff-819-cluster-workflow-shard-groups.md diff --git a/repos/effect/.changeset/eff-849-transpose-option.md b/repos/effect/.changeset/pre/eff-849-transpose-option.md similarity index 100% rename from repos/effect/.changeset/eff-849-transpose-option.md rename to repos/effect/.changeset/pre/eff-849-transpose-option.md 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 0000000000..1ae7fabb1e --- /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 0000000000..5c72f8d56a --- /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 0000000000..aeca222b2f --- /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 0000000000..22865608b6 --- /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 0000000000..8f892c153c --- /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/eight-turkeys-own.md b/repos/effect/.changeset/pre/eight-turkeys-own.md similarity index 100% rename from repos/effect/.changeset/eight-turkeys-own.md rename to repos/effect/.changeset/pre/eight-turkeys-own.md diff --git a/repos/effect/.changeset/eighty-lies-deny.md b/repos/effect/.changeset/pre/eighty-lies-deny.md similarity index 100% rename from repos/effect/.changeset/eighty-lies-deny.md rename to repos/effect/.changeset/pre/eighty-lies-deny.md diff --git a/repos/effect/.changeset/eighty-poets-draw.md b/repos/effect/.changeset/pre/eighty-poets-draw.md similarity index 100% rename from repos/effect/.changeset/eighty-poets-draw.md rename to repos/effect/.changeset/pre/eighty-poets-draw.md diff --git a/repos/effect/.changeset/eighty-swans-scream.md b/repos/effect/.changeset/pre/eighty-swans-scream.md similarity index 100% rename from repos/effect/.changeset/eighty-swans-scream.md rename to repos/effect/.changeset/pre/eighty-swans-scream.md diff --git a/repos/effect/.changeset/eighty-teeth-sniff.md b/repos/effect/.changeset/pre/eighty-teeth-sniff.md similarity index 100% rename from repos/effect/.changeset/eighty-teeth-sniff.md rename to repos/effect/.changeset/pre/eighty-teeth-sniff.md diff --git a/repos/effect/.changeset/eleven-apes-share.md b/repos/effect/.changeset/pre/eleven-apes-share.md similarity index 100% rename from repos/effect/.changeset/eleven-apes-share.md rename to repos/effect/.changeset/pre/eleven-apes-share.md diff --git a/repos/effect/.changeset/eleven-numbers-bake.md b/repos/effect/.changeset/pre/eleven-numbers-bake.md similarity index 100% rename from repos/effect/.changeset/eleven-numbers-bake.md rename to repos/effect/.changeset/pre/eleven-numbers-bake.md 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 0000000000..cd894f95d7 --- /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 0000000000..e082b11e56 --- /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/empty-gifts-beg.md b/repos/effect/.changeset/pre/empty-gifts-beg.md similarity index 100% rename from repos/effect/.changeset/empty-gifts-beg.md rename to repos/effect/.changeset/pre/empty-gifts-beg.md diff --git a/repos/effect/.changeset/empty-http-rpc-client.md b/repos/effect/.changeset/pre/empty-http-rpc-client.md similarity index 100% rename from repos/effect/.changeset/empty-http-rpc-client.md rename to repos/effect/.changeset/pre/empty-http-rpc-client.md 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 0000000000..94ae6a96ff --- /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/eventlog-unencrypted.md b/repos/effect/.changeset/pre/eventlog-unencrypted.md similarity index 100% rename from repos/effect/.changeset/eventlog-unencrypted.md rename to repos/effect/.changeset/pre/eventlog-unencrypted.md diff --git a/repos/effect/.changeset/every-olives-burn.md b/repos/effect/.changeset/pre/every-olives-burn.md similarity index 100% rename from repos/effect/.changeset/every-olives-burn.md rename to repos/effect/.changeset/pre/every-olives-burn.md diff --git a/repos/effect/.changeset/expand-schema-filter-output.md b/repos/effect/.changeset/pre/expand-schema-filter-output.md similarity index 100% rename from repos/effect/.changeset/expand-schema-filter-output.md rename to repos/effect/.changeset/pre/expand-schema-filter-output.md 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 0000000000..2045bbd205 --- /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 0000000000..a34030e336 --- /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/export-schema-encode-keys-interface.md b/repos/effect/.changeset/pre/export-schema-encode-keys-interface.md similarity index 100% rename from repos/effect/.changeset/export-schema-encode-keys-interface.md rename to repos/effect/.changeset/pre/export-schema-encode-keys-interface.md 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 0000000000..b29af5e941 --- /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/extract-semaphore-latch.md b/repos/effect/.changeset/pre/extract-semaphore-latch.md similarity index 100% rename from repos/effect/.changeset/extract-semaphore-latch.md rename to repos/effect/.changeset/pre/extract-semaphore-latch.md 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 0000000000..8e806105d7 --- /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/fair-bees-relax.md b/repos/effect/.changeset/pre/fair-bees-relax.md similarity index 100% rename from repos/effect/.changeset/fair-bees-relax.md rename to repos/effect/.changeset/pre/fair-bees-relax.md 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 0000000000..c72957459c --- /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/fair-buttons-share.md b/repos/effect/.changeset/pre/fair-buttons-share.md similarity index 100% rename from repos/effect/.changeset/fair-buttons-share.md rename to repos/effect/.changeset/pre/fair-buttons-share.md 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 0000000000..49f8e0ab12 --- /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/fair-cooks-stop.md b/repos/effect/.changeset/pre/fair-cooks-stop.md similarity index 100% rename from repos/effect/.changeset/fair-cooks-stop.md rename to repos/effect/.changeset/pre/fair-cooks-stop.md diff --git a/repos/effect/.changeset/fair-cups-train.md b/repos/effect/.changeset/pre/fair-cups-train.md similarity index 100% rename from repos/effect/.changeset/fair-cups-train.md rename to repos/effect/.changeset/pre/fair-cups-train.md diff --git a/repos/effect/.changeset/fair-dryers-speak.md b/repos/effect/.changeset/pre/fair-dryers-speak.md similarity index 100% rename from repos/effect/.changeset/fair-dryers-speak.md rename to repos/effect/.changeset/pre/fair-dryers-speak.md diff --git a/repos/effect/.changeset/fair-forks-shake.md b/repos/effect/.changeset/pre/fair-forks-shake.md similarity index 100% rename from repos/effect/.changeset/fair-forks-shake.md rename to repos/effect/.changeset/pre/fair-forks-shake.md 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 0000000000..e985873345 --- /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 0000000000..dacdd7b02d --- /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 0000000000..6608c22367 --- /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/fair-pandas-prove.md b/repos/effect/.changeset/pre/fair-pandas-prove.md similarity index 100% rename from repos/effect/.changeset/fair-pandas-prove.md rename to repos/effect/.changeset/pre/fair-pandas-prove.md diff --git a/repos/effect/.changeset/fair-pants-float.md b/repos/effect/.changeset/pre/fair-pants-float.md similarity index 100% rename from repos/effect/.changeset/fair-pants-float.md rename to repos/effect/.changeset/pre/fair-pants-float.md diff --git a/repos/effect/.changeset/fair-poems-visit.md b/repos/effect/.changeset/pre/fair-poems-visit.md similarity index 100% rename from repos/effect/.changeset/fair-poems-visit.md rename to repos/effect/.changeset/pre/fair-poems-visit.md 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 0000000000..d88205d8f6 --- /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 0000000000..6ea07ee3e4 --- /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 0000000000..ac6581223f --- /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/famous-wolves-lead.md b/repos/effect/.changeset/pre/famous-wolves-lead.md similarity index 100% rename from repos/effect/.changeset/famous-wolves-lead.md rename to repos/effect/.changeset/pre/famous-wolves-lead.md diff --git a/repos/effect/.changeset/fancy-glasses-grow.md b/repos/effect/.changeset/pre/fancy-glasses-grow.md similarity index 100% rename from repos/effect/.changeset/fancy-glasses-grow.md rename to repos/effect/.changeset/pre/fancy-glasses-grow.md 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 0000000000..66d188167e --- /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/fast-times-camp.md b/repos/effect/.changeset/pre/fast-times-camp.md similarity index 100% rename from repos/effect/.changeset/fast-times-camp.md rename to repos/effect/.changeset/pre/fast-times-camp.md diff --git a/repos/effect/.changeset/few-birds-matter.md b/repos/effect/.changeset/pre/few-birds-matter.md similarity index 100% rename from repos/effect/.changeset/few-birds-matter.md rename to repos/effect/.changeset/pre/few-birds-matter.md diff --git a/repos/effect/.changeset/few-cougars-dig.md b/repos/effect/.changeset/pre/few-cougars-dig.md similarity index 100% rename from repos/effect/.changeset/few-cougars-dig.md rename to repos/effect/.changeset/pre/few-cougars-dig.md diff --git a/repos/effect/.changeset/few-foxes-grin.md b/repos/effect/.changeset/pre/few-foxes-grin.md similarity index 100% rename from repos/effect/.changeset/few-foxes-grin.md rename to repos/effect/.changeset/pre/few-foxes-grin.md diff --git a/repos/effect/.changeset/few-mirrors-pull.md b/repos/effect/.changeset/pre/few-mirrors-pull.md similarity index 100% rename from repos/effect/.changeset/few-mirrors-pull.md rename to repos/effect/.changeset/pre/few-mirrors-pull.md diff --git a/repos/effect/.changeset/few-socks-poke.md b/repos/effect/.changeset/pre/few-socks-poke.md similarity index 100% rename from repos/effect/.changeset/few-socks-poke.md rename to repos/effect/.changeset/pre/few-socks-poke.md 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 0000000000..ac883aedce --- /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/fiber-runtime-start-metrics.md b/repos/effect/.changeset/pre/fiber-runtime-start-metrics.md similarity index 100% rename from repos/effect/.changeset/fiber-runtime-start-metrics.md rename to repos/effect/.changeset/pre/fiber-runtime-start-metrics.md diff --git a/repos/effect/.changeset/fiery-jokes-care.md b/repos/effect/.changeset/pre/fiery-jokes-care.md similarity index 100% rename from repos/effect/.changeset/fiery-jokes-care.md rename to repos/effect/.changeset/pre/fiery-jokes-care.md diff --git a/repos/effect/.changeset/fiery-mammals-call.md b/repos/effect/.changeset/pre/fiery-mammals-call.md similarity index 100% rename from repos/effect/.changeset/fiery-mammals-call.md rename to repos/effect/.changeset/pre/fiery-mammals-call.md diff --git a/repos/effect/.changeset/fine-walls-decide.md b/repos/effect/.changeset/pre/fine-walls-decide.md similarity index 100% rename from repos/effect/.changeset/fine-walls-decide.md rename to repos/effect/.changeset/pre/fine-walls-decide.md diff --git a/repos/effect/.changeset/first-success-of.md b/repos/effect/.changeset/pre/first-success-of.md similarity index 100% rename from repos/effect/.changeset/first-success-of.md rename to repos/effect/.changeset/pre/first-success-of.md diff --git a/repos/effect/.changeset/five-parents-relax.md b/repos/effect/.changeset/pre/five-parents-relax.md similarity index 100% rename from repos/effect/.changeset/five-parents-relax.md rename to repos/effect/.changeset/pre/five-parents-relax.md diff --git a/repos/effect/.changeset/five-worms-rhyme.md b/repos/effect/.changeset/pre/five-worms-rhyme.md similarity index 100% rename from repos/effect/.changeset/five-worms-rhyme.md rename to repos/effect/.changeset/pre/five-worms-rhyme.md diff --git a/repos/effect/.changeset/fix-1332.md b/repos/effect/.changeset/pre/fix-1332.md similarity index 100% rename from repos/effect/.changeset/fix-1332.md rename to repos/effect/.changeset/pre/fix-1332.md diff --git a/repos/effect/.changeset/fix-1917.md b/repos/effect/.changeset/pre/fix-1917.md similarity index 100% rename from repos/effect/.changeset/fix-1917.md rename to repos/effect/.changeset/pre/fix-1917.md diff --git a/repos/effect/.changeset/fix-1927.md b/repos/effect/.changeset/pre/fix-1927.md similarity index 100% rename from repos/effect/.changeset/fix-1927.md rename to repos/effect/.changeset/pre/fix-1927.md diff --git a/repos/effect/.changeset/fix-1940.md b/repos/effect/.changeset/pre/fix-1940.md similarity index 100% rename from repos/effect/.changeset/fix-1940.md rename to repos/effect/.changeset/pre/fix-1940.md diff --git a/repos/effect/.changeset/fix-1947.md b/repos/effect/.changeset/pre/fix-1947.md similarity index 100% rename from repos/effect/.changeset/fix-1947.md rename to repos/effect/.changeset/pre/fix-1947.md diff --git a/repos/effect/.changeset/fix-2002.md b/repos/effect/.changeset/pre/fix-2002.md similarity index 100% rename from repos/effect/.changeset/fix-2002.md rename to repos/effect/.changeset/pre/fix-2002.md diff --git a/repos/effect/.changeset/fix-2012.md b/repos/effect/.changeset/pre/fix-2012.md similarity index 100% rename from repos/effect/.changeset/fix-2012.md rename to repos/effect/.changeset/pre/fix-2012.md diff --git a/repos/effect/.changeset/fix-2015.md b/repos/effect/.changeset/pre/fix-2015.md similarity index 100% rename from repos/effect/.changeset/fix-2015.md rename to repos/effect/.changeset/pre/fix-2015.md diff --git a/repos/effect/.changeset/fix-2260.md b/repos/effect/.changeset/pre/fix-2260.md similarity index 100% rename from repos/effect/.changeset/fix-2260.md rename to repos/effect/.changeset/pre/fix-2260.md diff --git a/repos/effect/.changeset/fix-2268.md b/repos/effect/.changeset/pre/fix-2268.md similarity index 100% rename from repos/effect/.changeset/fix-2268.md rename to repos/effect/.changeset/pre/fix-2268.md diff --git a/repos/effect/.changeset/fix-2271.md b/repos/effect/.changeset/pre/fix-2271.md similarity index 100% rename from repos/effect/.changeset/fix-2271.md rename to repos/effect/.changeset/pre/fix-2271.md diff --git a/repos/effect/.changeset/fix-2384.md b/repos/effect/.changeset/pre/fix-2384.md similarity index 100% rename from repos/effect/.changeset/fix-2384.md rename to repos/effect/.changeset/pre/fix-2384.md diff --git a/repos/effect/.changeset/fix-2414.md b/repos/effect/.changeset/pre/fix-2414.md similarity index 100% rename from repos/effect/.changeset/fix-2414.md rename to repos/effect/.changeset/pre/fix-2414.md diff --git a/repos/effect/.changeset/fix-2419.md b/repos/effect/.changeset/pre/fix-2419.md similarity index 100% rename from repos/effect/.changeset/fix-2419.md rename to repos/effect/.changeset/pre/fix-2419.md diff --git a/repos/effect/.changeset/pre/fix-2497.md b/repos/effect/.changeset/pre/fix-2497.md new file mode 100644 index 0000000000..4425db712b --- /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 0000000000..9e26f847e8 --- /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 0000000000..13d7b52d21 --- /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 0000000000..a37e2d0cc7 --- /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 0000000000..ca7c85ad32 --- /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/fix-ai-empty-params-structured-output.md b/repos/effect/.changeset/pre/fix-ai-empty-params-structured-output.md similarity index 100% rename from repos/effect/.changeset/fix-ai-empty-params-structured-output.md rename to repos/effect/.changeset/pre/fix-ai-empty-params-structured-output.md diff --git a/repos/effect/.changeset/fix-ai-text-toolkit-typing.md b/repos/effect/.changeset/pre/fix-ai-text-toolkit-typing.md similarity index 100% rename from repos/effect/.changeset/fix-ai-text-toolkit-typing.md rename to repos/effect/.changeset/pre/fix-ai-text-toolkit-typing.md 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 0000000000..42d87284ad --- /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 0000000000..36309f8325 --- /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/fix-anthropic-caller-toolid.md b/repos/effect/.changeset/pre/fix-anthropic-caller-toolid.md similarity index 100% rename from repos/effect/.changeset/fix-anthropic-caller-toolid.md rename to repos/effect/.changeset/pre/fix-anthropic-caller-toolid.md 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 0000000000..a50a451a25 --- /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 0000000000..3a8bd80b72 --- /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 0000000000..f9bd9843fa --- /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 0000000000..3a9a9cdbc8 --- /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 0000000000..5d6c5740e1 --- /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 0000000000..dcfd9db0fd --- /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 0000000000..5c1ad70711 --- /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 0000000000..2227e7aed3 --- /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 0000000000..220407192d --- /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 0000000000..c43cfc220c --- /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 0000000000..bfffa04847 --- /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 0000000000..be4cf636d8 --- /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/fix-catch-orelse-error-erasure.md b/repos/effect/.changeset/pre/fix-catch-orelse-error-erasure.md similarity index 100% rename from repos/effect/.changeset/fix-catch-orelse-error-erasure.md rename to repos/effect/.changeset/pre/fix-catch-orelse-error-erasure.md 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 0000000000..efc7e791fe --- /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-schema-decode-unknown.md b/repos/effect/.changeset/pre/fix-channel-schema-decode-unknown.md new file mode 100644 index 0000000000..c7ee9a2ba3 --- /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 0000000000..0fae65c5a1 --- /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/fix-class-constructor-defaults.md b/repos/effect/.changeset/pre/fix-class-constructor-defaults.md similarity index 100% rename from repos/effect/.changeset/fix-class-constructor-defaults.md rename to repos/effect/.changeset/pre/fix-class-constructor-defaults.md 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 0000000000..0dfb373f9a --- /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/fix-cli-mixed-global-flag-context.md b/repos/effect/.changeset/pre/fix-cli-mixed-global-flag-context.md similarity index 100% rename from repos/effect/.changeset/fix-cli-mixed-global-flag-context.md rename to repos/effect/.changeset/pre/fix-cli-mixed-global-flag-context.md 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 0000000000..563526731f --- /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 0000000000..2f6b775cd9 --- /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 0000000000..41a80e3690 --- /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 0000000000..c9037d3d5c --- /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 0000000000..62f0d84727 --- /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 0000000000..50649c4a0c --- /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 0000000000..886d22f7e4 --- /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 0000000000..7c54c63815 --- /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/fix-config-array-default.md b/repos/effect/.changeset/pre/fix-config-array-default.md similarity index 100% rename from repos/effect/.changeset/fix-config-array-default.md rename to repos/effect/.changeset/pre/fix-config-array-default.md 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 0000000000..ced02896e0 --- /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/fix-config-withDefault.md b/repos/effect/.changeset/pre/fix-config-withDefault.md similarity index 100% rename from repos/effect/.changeset/fix-config-withDefault.md rename to repos/effect/.changeset/pre/fix-config-withDefault.md diff --git a/repos/effect/.changeset/fix-config-withdefault-filter.md b/repos/effect/.changeset/pre/fix-config-withdefault-filter.md similarity index 100% rename from repos/effect/.changeset/fix-config-withdefault-filter.md rename to repos/effect/.changeset/pre/fix-config-withdefault-filter.md 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 0000000000..f05f8bb404 --- /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 0000000000..064eeb3eec --- /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 0000000000..5f9e350f02 --- /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/fix-cron-next-missing-day-overflow.md b/repos/effect/.changeset/pre/fix-cron-next-missing-day-overflow.md similarity index 100% rename from repos/effect/.changeset/fix-cron-next-missing-day-overflow.md rename to repos/effect/.changeset/pre/fix-cron-next-missing-day-overflow.md 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 0000000000..840d6f06b7 --- /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 0000000000..8085ddc1df --- /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 0000000000..e4b8307497 --- /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 0000000000..6ee572d046 --- /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/fix-datetime-gmt.md b/repos/effect/.changeset/pre/fix-datetime-gmt.md similarity index 100% rename from repos/effect/.changeset/fix-datetime-gmt.md rename to repos/effect/.changeset/pre/fix-datetime-gmt.md diff --git a/repos/effect/.changeset/fix-devtools-flush-on-teardown.md b/repos/effect/.changeset/pre/fix-devtools-flush-on-teardown.md similarity index 100% rename from repos/effect/.changeset/fix-devtools-flush-on-teardown.md rename to repos/effect/.changeset/pre/fix-devtools-flush-on-teardown.md 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 0000000000..ca381c8d5b --- /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 0000000000..d0a7390c84 --- /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-race-replay.md b/repos/effect/.changeset/pre/fix-durable-race-replay.md new file mode 100644 index 0000000000..2b0e3b242b --- /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 0000000000..2afc2db772 --- /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/fix-duration-symmetric-rounding.md b/repos/effect/.changeset/pre/fix-duration-symmetric-rounding.md similarity index 100% rename from repos/effect/.changeset/fix-duration-symmetric-rounding.md rename to repos/effect/.changeset/pre/fix-duration-symmetric-rounding.md 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 0000000000..8a8910026e --- /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/fix-entity-manager-defect-replay.md b/repos/effect/.changeset/pre/fix-entity-manager-defect-replay.md similarity index 100% rename from repos/effect/.changeset/fix-entity-manager-defect-replay.md rename to repos/effect/.changeset/pre/fix-entity-manager-defect-replay.md diff --git a/repos/effect/.changeset/fix-entity-proxy-rpc-handler-context.md b/repos/effect/.changeset/pre/fix-entity-proxy-rpc-handler-context.md similarity index 100% rename from repos/effect/.changeset/fix-entity-proxy-rpc-handler-context.md rename to repos/effect/.changeset/pre/fix-entity-proxy-rpc-handler-context.md diff --git a/repos/effect/.changeset/fix-entity-proxy-server-path-params.md b/repos/effect/.changeset/pre/fix-entity-proxy-server-path-params.md similarity index 100% rename from repos/effect/.changeset/fix-entity-proxy-server-path-params.md rename to repos/effect/.changeset/pre/fix-entity-proxy-server-path-params.md 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 0000000000..dc215a8b37 --- /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-fiberhandle-clear-race.md b/repos/effect/.changeset/pre/fix-fiberhandle-clear-race.md new file mode 100644 index 0000000000..567ba19e04 --- /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 0000000000..158b963c14 --- /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 0000000000..0d550e711d --- /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 0000000000..15053389d9 --- /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 0000000000..5312f9d8e2 --- /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 0000000000..b2e478fe7e --- /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/fix-from-readable-stream-cancel-defect.md b/repos/effect/.changeset/pre/fix-from-readable-stream-cancel-defect.md similarity index 100% rename from repos/effect/.changeset/fix-from-readable-stream-cancel-defect.md rename to repos/effect/.changeset/pre/fix-from-readable-stream-cancel-defect.md 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 0000000000..d796cad079 --- /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 0000000000..07dcc32a6a --- /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 0000000000..4d79479201 --- /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-edge-transforms.md b/repos/effect/.changeset/pre/fix-graph-edge-transforms.md new file mode 100644 index 0000000000..e0c410ee86 --- /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 0000000000..171304d070 --- /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 0000000000..62463090cf --- /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 0000000000..8a929bde8b --- /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-topo-types.md b/repos/effect/.changeset/pre/fix-graph-topo-types.md new file mode 100644 index 0000000000..adab4eb464 --- /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/fix-graph-undirected-traversal.md b/repos/effect/.changeset/pre/fix-graph-undirected-traversal.md similarity index 100% rename from repos/effect/.changeset/fix-graph-undirected-traversal.md rename to repos/effect/.changeset/pre/fix-graph-undirected-traversal.md 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 0000000000..25c1ccd982 --- /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 0000000000..35bd254d67 --- /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/fix-has-interrupts-only-empty.md b/repos/effect/.changeset/pre/fix-has-interrupts-only-empty.md similarity index 100% rename from repos/effect/.changeset/fix-has-interrupts-only-empty.md rename to repos/effect/.changeset/pre/fix-has-interrupts-only-empty.md diff --git a/repos/effect/.changeset/fix-hashmap-bit31-ordering.md b/repos/effect/.changeset/pre/fix-hashmap-bit31-ordering.md similarity index 100% rename from repos/effect/.changeset/fix-hashmap-bit31-ordering.md rename to repos/effect/.changeset/pre/fix-hashmap-bit31-ordering.md 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 0000000000..ca36cabf55 --- /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/fix-headers-proto-enumerability.md b/repos/effect/.changeset/pre/fix-headers-proto-enumerability.md similarity index 100% rename from repos/effect/.changeset/fix-headers-proto-enumerability.md rename to repos/effect/.changeset/pre/fix-headers-proto-enumerability.md 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 0000000000..12f66418c1 --- /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/fix-http-incoming-message-parse-options.md b/repos/effect/.changeset/pre/fix-http-incoming-message-parse-options.md similarity index 100% rename from repos/effect/.changeset/fix-http-incoming-message-parse-options.md rename to repos/effect/.changeset/pre/fix-http-incoming-message-parse-options.md 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 0000000000..66eeb8b702 --- /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 0000000000..148559e4a2 --- /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 0000000000..90f2278179 --- /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/fix-http-tracer-response-cause.md b/repos/effect/.changeset/pre/fix-http-tracer-response-cause.md similarity index 100% rename from repos/effect/.changeset/fix-http-tracer-response-cause.md rename to repos/effect/.changeset/pre/fix-http-tracer-response-cause.md 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 0000000000..b41cb0662e --- /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 0000000000..2de77be2c6 --- /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/fix-httpapi-endpoint-error-inference.md b/repos/effect/.changeset/pre/fix-httpapi-endpoint-error-inference.md similarity index 100% rename from repos/effect/.changeset/fix-httpapi-endpoint-error-inference.md rename to repos/effect/.changeset/pre/fix-httpapi-endpoint-error-inference.md 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 0000000000..2f704e0e85 --- /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 0000000000..a8c9bd8690 --- /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/fix-httpapi-schema-types.md b/repos/effect/.changeset/pre/fix-httpapi-schema-types.md similarity index 100% rename from repos/effect/.changeset/fix-httpapi-schema-types.md rename to repos/effect/.changeset/pre/fix-httpapi-schema-types.md diff --git a/repos/effect/.changeset/fix-httpapi-security-middleware-cache.md b/repos/effect/.changeset/pre/fix-httpapi-security-middleware-cache.md similarity index 100% rename from repos/effect/.changeset/fix-httpapi-security-middleware-cache.md rename to repos/effect/.changeset/pre/fix-httpapi-security-middleware-cache.md 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 0000000000..a8e9deaeed --- /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/fix-is-json-dag.md b/repos/effect/.changeset/pre/fix-is-json-dag.md similarity index 100% rename from repos/effect/.changeset/fix-is-json-dag.md rename to repos/effect/.changeset/pre/fix-is-json-dag.md 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 0000000000..54894d0c6b --- /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/fix-json-schema-anyof-oneof-siblings.md b/repos/effect/.changeset/pre/fix-json-schema-anyof-oneof-siblings.md similarity index 100% rename from repos/effect/.changeset/fix-json-schema-anyof-oneof-siblings.md rename to repos/effect/.changeset/pre/fix-json-schema-anyof-oneof-siblings.md diff --git a/repos/effect/.changeset/fix-json-schema-import-json.md b/repos/effect/.changeset/pre/fix-json-schema-import-json.md similarity index 100% rename from repos/effect/.changeset/fix-json-schema-import-json.md rename to repos/effect/.changeset/pre/fix-json-schema-import-json.md 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 0000000000..0365d9dc50 --- /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/fix-keepalive-blocked-timers.md b/repos/effect/.changeset/pre/fix-keepalive-blocked-timers.md similarity index 100% rename from repos/effect/.changeset/fix-keepalive-blocked-timers.md rename to repos/effect/.changeset/pre/fix-keepalive-blocked-timers.md 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 0000000000..491b4fdcd6 --- /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 0000000000..c36527840c --- /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 0000000000..6b72607345 --- /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 0000000000..952f6d0da9 --- /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 0000000000..82c30fc894 --- /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/fix-mcp-param-name-resolution.md b/repos/effect/.changeset/pre/fix-mcp-param-name-resolution.md similarity index 100% rename from repos/effect/.changeset/fix-mcp-param-name-resolution.md rename to repos/effect/.changeset/pre/fix-mcp-param-name-resolution.md 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 0000000000..7da53c303c --- /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 0000000000..ae2c2ddd57 --- /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 0000000000..f515118f08 --- /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 0000000000..cee740fdd8 --- /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 0000000000..77a0e9ce88 --- /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/fix-mermaid-escape-special-chars.md b/repos/effect/.changeset/pre/fix-mermaid-escape-special-chars.md similarity index 100% rename from repos/effect/.changeset/fix-mermaid-escape-special-chars.md rename to repos/effect/.changeset/pre/fix-mermaid-escape-special-chars.md 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 0000000000..f8d75f32a7 --- /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 0000000000..3d00cc8e7c --- /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 0000000000..00e1854300 --- /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 0000000000..d5a0219a45 --- /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 0000000000..a2f1d2450c --- /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 0000000000..8c870fa224 --- /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 0000000000..fd4d227f45 --- /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 0000000000..d481f7a747 --- /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 0000000000..4352c73143 --- /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/fix-mutable-list-filter-length.md b/repos/effect/.changeset/pre/fix-mutable-list-filter-length.md similarity index 100% rename from repos/effect/.changeset/fix-mutable-list-filter-length.md rename to repos/effect/.changeset/pre/fix-mutable-list-filter-length.md 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 0000000000..4474398b48 --- /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 0000000000..b1049b4753 --- /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 0000000000..d1cb74f621 --- /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 0000000000..b551d51dad --- /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 0000000000..9b43310d21 --- /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/fix-object-keyword-json-schema.md b/repos/effect/.changeset/pre/fix-object-keyword-json-schema.md similarity index 100% rename from repos/effect/.changeset/fix-object-keyword-json-schema.md rename to repos/effect/.changeset/pre/fix-object-keyword-json-schema.md 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 0000000000..9908168c1b --- /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 0000000000..1357ea76b8 --- /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/fix-openai-mcp-tool-names.md b/repos/effect/.changeset/pre/fix-openai-mcp-tool-names.md similarity index 100% rename from repos/effect/.changeset/fix-openai-mcp-tool-names.md rename to repos/effect/.changeset/pre/fix-openai-mcp-tool-names.md 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 0000000000..dbe8abb998 --- /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 0000000000..ac215a2691 --- /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 0000000000..830f6d00e1 --- /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 0000000000..d0488f9d6b --- /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 0000000000..ac95aedf19 --- /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/fix-openapi-generator-form-urlencoded.md b/repos/effect/.changeset/pre/fix-openapi-generator-form-urlencoded.md similarity index 100% rename from repos/effect/.changeset/fix-openapi-generator-form-urlencoded.md rename to repos/effect/.changeset/pre/fix-openapi-generator-form-urlencoded.md diff --git a/repos/effect/.changeset/fix-openapi-generator-swagger2openapi.md b/repos/effect/.changeset/pre/fix-openapi-generator-swagger2openapi.md similarity index 100% rename from repos/effect/.changeset/fix-openapi-generator-swagger2openapi.md rename to repos/effect/.changeset/pre/fix-openapi-generator-swagger2openapi.md diff --git a/repos/effect/.changeset/fix-openapi-preserve-multiple-response-content-types.md b/repos/effect/.changeset/pre/fix-openapi-preserve-multiple-response-content-types.md similarity index 100% rename from repos/effect/.changeset/fix-openapi-preserve-multiple-response-content-types.md rename to repos/effect/.changeset/pre/fix-openapi-preserve-multiple-response-content-types.md diff --git a/repos/effect/.changeset/fix-openrouter-sparse-array.md b/repos/effect/.changeset/pre/fix-openrouter-sparse-array.md similarity index 100% rename from repos/effect/.changeset/fix-openrouter-sparse-array.md rename to repos/effect/.changeset/pre/fix-openrouter-sparse-array.md 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 0000000000..5783219d71 --- /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/fix-otel-logger-severity-number.md b/repos/effect/.changeset/pre/fix-otel-logger-severity-number.md similarity index 100% rename from repos/effect/.changeset/fix-otel-logger-severity-number.md rename to repos/effect/.changeset/pre/fix-otel-logger-severity-number.md 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 0000000000..d811098636 --- /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 0000000000..444ba3ce49 --- /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 0000000000..a461bfa335 --- /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 0000000000..e0a8494e48 --- /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 0000000000..2a1b8732fb --- /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 0000000000..8737be085d --- /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 0000000000..070620a8c1 --- /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 0000000000..1d960ffa4a --- /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 0000000000..632765fdcf --- /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/fix-queue-collect-duplication.md b/repos/effect/.changeset/pre/fix-queue-collect-duplication.md similarity index 100% rename from repos/effect/.changeset/fix-queue-collect-duplication.md rename to repos/effect/.changeset/pre/fix-queue-collect-duplication.md diff --git a/repos/effect/.changeset/fix-random-string-seeds.md b/repos/effect/.changeset/pre/fix-random-string-seeds.md similarity index 100% rename from repos/effect/.changeset/fix-random-string-seeds.md rename to repos/effect/.changeset/pre/fix-random-string-seeds.md 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 0000000000..73633920be --- /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/fix-ratelimiter-tokenbucket-redis-ttl.md b/repos/effect/.changeset/pre/fix-ratelimiter-tokenbucket-redis-ttl.md similarity index 100% rename from repos/effect/.changeset/fix-ratelimiter-tokenbucket-redis-ttl.md rename to repos/effect/.changeset/pre/fix-ratelimiter-tokenbucket-redis-ttl.md 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 0000000000..97070d9b0c --- /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 0000000000..6617c533bd --- /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 0000000000..8ec5249674 --- /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/fix-remainder-scientific-notation.md b/repos/effect/.changeset/pre/fix-remainder-scientific-notation.md similarity index 100% rename from repos/effect/.changeset/fix-remainder-scientific-notation.md rename to repos/effect/.changeset/pre/fix-remainder-scientific-notation.md diff --git a/repos/effect/.changeset/fix-request-resolver-pending-batches-leak.md b/repos/effect/.changeset/pre/fix-request-resolver-pending-batches-leak.md similarity index 100% rename from repos/effect/.changeset/fix-request-resolver-pending-batches-leak.md rename to repos/effect/.changeset/pre/fix-request-resolver-pending-batches-leak.md diff --git a/repos/effect/.changeset/fix-retry-transient-autocomplete.md b/repos/effect/.changeset/pre/fix-retry-transient-autocomplete.md similarity index 100% rename from repos/effect/.changeset/fix-retry-transient-autocomplete.md rename to repos/effect/.changeset/pre/fix-retry-transient-autocomplete.md diff --git a/repos/effect/.changeset/fix-rpc-http-requestids-finalizer.md b/repos/effect/.changeset/pre/fix-rpc-http-requestids-finalizer.md similarity index 100% rename from repos/effect/.changeset/fix-rpc-http-requestids-finalizer.md rename to repos/effect/.changeset/pre/fix-rpc-http-requestids-finalizer.md diff --git a/repos/effect/.changeset/fix-rpc-json-id-edges.md b/repos/effect/.changeset/pre/fix-rpc-json-id-edges.md similarity index 100% rename from repos/effect/.changeset/fix-rpc-json-id-edges.md rename to repos/effect/.changeset/pre/fix-rpc-json-id-edges.md diff --git a/repos/effect/.changeset/fix-rpc-unknown-tag-isolation.md b/repos/effect/.changeset/pre/fix-rpc-unknown-tag-isolation.md similarity index 100% rename from repos/effect/.changeset/fix-rpc-unknown-tag-isolation.md rename to repos/effect/.changeset/pre/fix-rpc-unknown-tag-isolation.md 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 0000000000..414fefe90d --- /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 0000000000..c881221afe --- /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/fix-schedule-fixed-double-exec.md b/repos/effect/.changeset/pre/fix-schedule-fixed-double-exec.md similarity index 100% rename from repos/effect/.changeset/fix-schedule-fixed-double-exec.md rename to repos/effect/.changeset/pre/fix-schedule-fixed-double-exec.md diff --git a/repos/effect/.changeset/fix-schedule-reduce-sync-state.md b/repos/effect/.changeset/pre/fix-schedule-reduce-sync-state.md similarity index 100% rename from repos/effect/.changeset/fix-schedule-reduce-sync-state.md rename to repos/effect/.changeset/pre/fix-schedule-reduce-sync-state.md diff --git a/repos/effect/.changeset/fix-schema-arbitrary-exclusive-bounds.md b/repos/effect/.changeset/pre/fix-schema-arbitrary-exclusive-bounds.md similarity index 100% rename from repos/effect/.changeset/fix-schema-arbitrary-exclusive-bounds.md rename to repos/effect/.changeset/pre/fix-schema-arbitrary-exclusive-bounds.md 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 0000000000..f570f34ca0 --- /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/fix-schema-defect-message.md b/repos/effect/.changeset/pre/fix-schema-defect-message.md similarity index 100% rename from repos/effect/.changeset/fix-schema-defect-message.md rename to repos/effect/.changeset/pre/fix-schema-defect-message.md diff --git a/repos/effect/.changeset/fix-schema-encode-keys-property-keys.md b/repos/effect/.changeset/pre/fix-schema-encode-keys-property-keys.md similarity index 100% rename from repos/effect/.changeset/fix-schema-encode-keys-property-keys.md rename to repos/effect/.changeset/pre/fix-schema-encode-keys-property-keys.md diff --git a/repos/effect/.changeset/fix-schema-encodekeys-class.md b/repos/effect/.changeset/pre/fix-schema-encodekeys-class.md similarity index 100% rename from repos/effect/.changeset/fix-schema-encodekeys-class.md rename to repos/effect/.changeset/pre/fix-schema-encodekeys-class.md diff --git a/repos/effect/.changeset/fix-schema-encodekeys-struct.md b/repos/effect/.changeset/pre/fix-schema-encodekeys-struct.md similarity index 100% rename from repos/effect/.changeset/fix-schema-encodekeys-struct.md rename to repos/effect/.changeset/pre/fix-schema-encodekeys-struct.md diff --git a/repos/effect/.changeset/fix-schema-encoding-checks.md b/repos/effect/.changeset/pre/fix-schema-encoding-checks.md similarity index 100% rename from repos/effect/.changeset/fix-schema-encoding-checks.md rename to repos/effect/.changeset/pre/fix-schema-encoding-checks.md diff --git a/repos/effect/.changeset/fix-schema-identifier-expected-message.md b/repos/effect/.changeset/pre/fix-schema-identifier-expected-message.md similarity index 100% rename from repos/effect/.changeset/fix-schema-identifier-expected-message.md rename to repos/effect/.changeset/pre/fix-schema-identifier-expected-message.md 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 0000000000..5a71fc1cc9 --- /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/fix-schema-is-uuid.md b/repos/effect/.changeset/pre/fix-schema-is-uuid.md similarity index 100% rename from repos/effect/.changeset/fix-schema-is-uuid.md rename to repos/effect/.changeset/pre/fix-schema-is-uuid.md 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 0000000000..cbf16877bd --- /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 0000000000..cc53b35a44 --- /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/fix-schema-option-non-schema-failures.md b/repos/effect/.changeset/pre/fix-schema-option-non-schema-failures.md similarity index 100% rename from repos/effect/.changeset/fix-schema-option-non-schema-failures.md rename to repos/effect/.changeset/pre/fix-schema-option-non-schema-failures.md diff --git a/repos/effect/.changeset/fix-schema-parser-checks.md b/repos/effect/.changeset/pre/fix-schema-parser-checks.md similarity index 100% rename from repos/effect/.changeset/fix-schema-parser-checks.md rename to repos/effect/.changeset/pre/fix-schema-parser-checks.md 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 0000000000..2a321daada --- /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 0000000000..f4a8a2391d --- /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-tuple-post-rest-indexing.md b/repos/effect/.changeset/pre/fix-schema-tuple-post-rest-indexing.md new file mode 100644 index 0000000000..8546cc8be5 --- /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 0000000000..61876f469f --- /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 0000000000..c1029ae56f --- /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 0000000000..5caffbbcaa --- /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/fix-searchparam-initial-decode.md b/repos/effect/.changeset/pre/fix-searchparam-initial-decode.md similarity index 100% rename from repos/effect/.changeset/fix-searchparam-initial-decode.md rename to repos/effect/.changeset/pre/fix-searchparam-initial-decode.md 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 0000000000..aba537cb54 --- /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/fix-serializable-wire-transfer.md b/repos/effect/.changeset/pre/fix-serializable-wire-transfer.md similarity index 100% rename from repos/effect/.changeset/fix-serializable-wire-transfer.md rename to repos/effect/.changeset/pre/fix-serializable-wire-transfer.md 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 0000000000..9d06bc18df --- /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 0000000000..19a98949be --- /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-sse-last-event-id.md b/repos/effect/.changeset/pre/fix-sse-last-event-id.md new file mode 100644 index 0000000000..69109fa35f --- /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 0000000000..c1b7716c88 --- /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 0000000000..e80c35962f --- /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 0000000000..96744190cf --- /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 0000000000..0174fbabec --- /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/fix-stream-grouped-within-flush.md b/repos/effect/.changeset/pre/fix-stream-grouped-within-flush.md similarity index 100% rename from repos/effect/.changeset/fix-stream-grouped-within-flush.md rename to repos/effect/.changeset/pre/fix-stream-grouped-within-flush.md 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 0000000000..8cb383c063 --- /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 0000000000..c67a81799c --- /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 0000000000..cc28d66a7b --- /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/fix-stream-run-for-each-while.md b/repos/effect/.changeset/pre/fix-stream-run-for-each-while.md similarity index 100% rename from repos/effect/.changeset/fix-stream-run-for-each-while.md rename to repos/effect/.changeset/pre/fix-stream-run-for-each-while.md diff --git a/repos/effect/.changeset/fix-stream-scan-effect.md b/repos/effect/.changeset/pre/fix-stream-scan-effect.md similarity index 100% rename from repos/effect/.changeset/fix-stream-scan-effect.md rename to repos/effect/.changeset/pre/fix-stream-scan-effect.md diff --git a/repos/effect/.changeset/fix-stream-scoped-scope.md b/repos/effect/.changeset/pre/fix-stream-scoped-scope.md similarity index 100% rename from repos/effect/.changeset/fix-stream-scoped-scope.md rename to repos/effect/.changeset/pre/fix-stream-scoped-scope.md 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 0000000000..5aaa2cd158 --- /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/fix-strip-approval-artifacts-multi-round.md b/repos/effect/.changeset/pre/fix-strip-approval-artifacts-multi-round.md similarity index 100% rename from repos/effect/.changeset/fix-strip-approval-artifacts-multi-round.md rename to repos/effect/.changeset/pre/fix-strip-approval-artifacts-multi-round.md diff --git a/repos/effect/.changeset/fix-struct-utility-types-simplify.md b/repos/effect/.changeset/pre/fix-struct-utility-types-simplify.md similarity index 100% rename from repos/effect/.changeset/fix-struct-utility-types-simplify.md rename to repos/effect/.changeset/pre/fix-struct-utility-types-simplify.md 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 0000000000..eae88bf89a --- /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/fix-structwithrest-index-signatures.md b/repos/effect/.changeset/pre/fix-structwithrest-index-signatures.md similarity index 100% rename from repos/effect/.changeset/fix-structwithrest-index-signatures.md rename to repos/effect/.changeset/pre/fix-structwithrest-index-signatures.md 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 0000000000..d231f9c6c9 --- /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 0000000000..4983ed0b0e --- /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/fix-tagged-union-class-sentinels.md b/repos/effect/.changeset/pre/fix-tagged-union-class-sentinels.md similarity index 100% rename from repos/effect/.changeset/fix-tagged-union-class-sentinels.md rename to repos/effect/.changeset/pre/fix-tagged-union-class-sentinels.md diff --git a/repos/effect/.changeset/fix-tagged-union-match-unify.md b/repos/effect/.changeset/pre/fix-tagged-union-match-unify.md similarity index 100% rename from repos/effect/.changeset/fix-tagged-union-match-unify.md rename to repos/effect/.changeset/pre/fix-tagged-union-match-unify.md diff --git a/repos/effect/.changeset/fix-to-tagged-union-isanyof-custom-tags.md b/repos/effect/.changeset/pre/fix-to-tagged-union-isanyof-custom-tags.md similarity index 100% rename from repos/effect/.changeset/fix-to-tagged-union-isanyof-custom-tags.md rename to repos/effect/.changeset/pre/fix-to-tagged-union-isanyof-custom-tags.md 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 0000000000..6306c054d1 --- /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 0000000000..00d3707328 --- /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 0000000000..bf422ac6d4 --- /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 0000000000..12a9e3f138 --- /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/fix-tuple-with-rest-post-rest-index-drift.md b/repos/effect/.changeset/pre/fix-tuple-with-rest-post-rest-index-drift.md similarity index 100% rename from repos/effect/.changeset/fix-tuple-with-rest-post-rest-index-drift.md rename to repos/effect/.changeset/pre/fix-tuple-with-rest-post-rest-index-drift.md diff --git a/repos/effect/.changeset/fix-tuple-with-rest-post-rest-validation.md b/repos/effect/.changeset/pre/fix-tuple-with-rest-post-rest-validation.md similarity index 100% rename from repos/effect/.changeset/fix-tuple-with-rest-post-rest-validation.md rename to repos/effect/.changeset/pre/fix-tuple-with-rest-post-rest-validation.md 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 0000000000..390063b2ba --- /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 0000000000..41b0208f88 --- /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 0000000000..e6f159cf19 --- /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/fix-types-voidifempty.md b/repos/effect/.changeset/pre/fix-types-voidifempty.md similarity index 100% rename from repos/effect/.changeset/fix-types-voidifempty.md rename to repos/effect/.changeset/pre/fix-types-voidifempty.md 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 0000000000..4449cb1754 --- /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 0000000000..a6b5ecbf22 --- /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 0000000000..840708d753 --- /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 0000000000..1f714c770e --- /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 0000000000..307bb38379 --- /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 0000000000..5febf7769d --- /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/fix-void-response-encoding.md b/repos/effect/.changeset/pre/fix-void-response-encoding.md similarity index 100% rename from repos/effect/.changeset/fix-void-response-encoding.md rename to repos/effect/.changeset/pre/fix-void-response-encoding.md 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 0000000000..ea8d118e06 --- /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/fix-workflow-defect-reply-serialization.md b/repos/effect/.changeset/pre/fix-workflow-defect-reply-serialization.md similarity index 100% rename from repos/effect/.changeset/fix-workflow-defect-reply-serialization.md rename to repos/effect/.changeset/pre/fix-workflow-defect-reply-serialization.md 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 0000000000..b682dc9217 --- /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/fix-workflow-proxy-rpc-handler-context.md b/repos/effect/.changeset/pre/fix-workflow-proxy-rpc-handler-context.md similarity index 100% rename from repos/effect/.changeset/fix-workflow-proxy-rpc-handler-context.md rename to repos/effect/.changeset/pre/fix-workflow-proxy-rpc-handler-context.md 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 0000000000..5ede2477bb --- /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 0000000000..28bc21691e --- /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/flat-chicken-remain.md b/repos/effect/.changeset/pre/flat-chicken-remain.md similarity index 100% rename from repos/effect/.changeset/flat-chicken-remain.md rename to repos/effect/.changeset/pre/flat-chicken-remain.md diff --git a/repos/effect/.changeset/floppy-cows-spend.md b/repos/effect/.changeset/pre/floppy-cows-spend.md similarity index 100% rename from repos/effect/.changeset/floppy-cows-spend.md rename to repos/effect/.changeset/pre/floppy-cows-spend.md 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 0000000000..4f4f40cd4c --- /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/floppy-items-admire.md b/repos/effect/.changeset/pre/floppy-items-admire.md similarity index 100% rename from repos/effect/.changeset/floppy-items-admire.md rename to repos/effect/.changeset/pre/floppy-items-admire.md diff --git a/repos/effect/.changeset/floppy-pigs-kiss.md b/repos/effect/.changeset/pre/floppy-pigs-kiss.md similarity index 100% rename from repos/effect/.changeset/floppy-pigs-kiss.md rename to repos/effect/.changeset/pre/floppy-pigs-kiss.md 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 0000000000..cb520a1fdb --- /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 0000000000..3687163633 --- /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/fluffy-meals-matter.md b/repos/effect/.changeset/pre/fluffy-meals-matter.md similarity index 100% rename from repos/effect/.changeset/fluffy-meals-matter.md rename to repos/effect/.changeset/pre/fluffy-meals-matter.md diff --git a/repos/effect/.changeset/fluffy-pumas-push.md b/repos/effect/.changeset/pre/fluffy-pumas-push.md similarity index 100% rename from repos/effect/.changeset/fluffy-pumas-push.md rename to repos/effect/.changeset/pre/fluffy-pumas-push.md diff --git a/repos/effect/.changeset/forked-memo-maps.md b/repos/effect/.changeset/pre/forked-memo-maps.md similarity index 100% rename from repos/effect/.changeset/forked-memo-maps.md rename to repos/effect/.changeset/pre/forked-memo-maps.md diff --git a/repos/effect/.changeset/forty-hounds-cheer.md b/repos/effect/.changeset/pre/forty-hounds-cheer.md similarity index 100% rename from repos/effect/.changeset/forty-hounds-cheer.md rename to repos/effect/.changeset/pre/forty-hounds-cheer.md diff --git a/repos/effect/.changeset/forty-otters-cry.md b/repos/effect/.changeset/pre/forty-otters-cry.md similarity index 100% rename from repos/effect/.changeset/forty-otters-cry.md rename to repos/effect/.changeset/pre/forty-otters-cry.md diff --git a/repos/effect/.changeset/forty-rings-film.md b/repos/effect/.changeset/pre/forty-rings-film.md similarity index 100% rename from repos/effect/.changeset/forty-rings-film.md rename to repos/effect/.changeset/pre/forty-rings-film.md diff --git a/repos/effect/.changeset/forty-signs-stay.md b/repos/effect/.changeset/pre/forty-signs-stay.md similarity index 100% rename from repos/effect/.changeset/forty-signs-stay.md rename to repos/effect/.changeset/pre/forty-signs-stay.md diff --git a/repos/effect/.changeset/forty-swans-divide.md b/repos/effect/.changeset/pre/forty-swans-divide.md similarity index 100% rename from repos/effect/.changeset/forty-swans-divide.md rename to repos/effect/.changeset/pre/forty-swans-divide.md diff --git a/repos/effect/.changeset/forty-trees-pay.md b/repos/effect/.changeset/pre/forty-trees-pay.md similarity index 100% rename from repos/effect/.changeset/forty-trees-pay.md rename to repos/effect/.changeset/pre/forty-trees-pay.md diff --git a/repos/effect/.changeset/four-papayas-bow.md b/repos/effect/.changeset/pre/four-papayas-bow.md similarity index 100% rename from repos/effect/.changeset/four-papayas-bow.md rename to repos/effect/.changeset/pre/four-papayas-bow.md diff --git a/repos/effect/.changeset/four-points-repeat.md b/repos/effect/.changeset/pre/four-points-repeat.md similarity index 100% rename from repos/effect/.changeset/four-points-repeat.md rename to repos/effect/.changeset/pre/four-points-repeat.md 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 0000000000..603a16fd71 --- /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/fresh-cats-smash.md b/repos/effect/.changeset/pre/fresh-cats-smash.md similarity index 100% rename from repos/effect/.changeset/fresh-cats-smash.md rename to repos/effect/.changeset/pre/fresh-cats-smash.md 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 0000000000..f588122cd9 --- /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/fresh-emus-cheat.md b/repos/effect/.changeset/pre/fresh-emus-cheat.md similarity index 100% rename from repos/effect/.changeset/fresh-emus-cheat.md rename to repos/effect/.changeset/pre/fresh-emus-cheat.md 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 0000000000..51fb444455 --- /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 0000000000..8b64d908d4 --- /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-images-generate.md b/repos/effect/.changeset/pre/fresh-images-generate.md new file mode 100644 index 0000000000..e8914d5f06 --- /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 0000000000..d064c998e6 --- /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/fresh-monkeys-smoke.md b/repos/effect/.changeset/pre/fresh-monkeys-smoke.md similarity index 100% rename from repos/effect/.changeset/fresh-monkeys-smoke.md rename to repos/effect/.changeset/pre/fresh-monkeys-smoke.md 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 0000000000..121cea53ea --- /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/frozen-intrinsics-stack-trace-limit.md b/repos/effect/.changeset/pre/frozen-intrinsics-stack-trace-limit.md similarity index 100% rename from repos/effect/.changeset/frozen-intrinsics-stack-trace-limit.md rename to repos/effect/.changeset/pre/frozen-intrinsics-stack-trace-limit.md diff --git a/repos/effect/.changeset/fruity-houses-learn.md b/repos/effect/.changeset/pre/fruity-houses-learn.md similarity index 100% rename from repos/effect/.changeset/fruity-houses-learn.md rename to repos/effect/.changeset/pre/fruity-houses-learn.md 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 0000000000..2e9ef2b1d7 --- /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/full-adults-double.md b/repos/effect/.changeset/pre/full-adults-double.md similarity index 100% rename from repos/effect/.changeset/full-adults-double.md rename to repos/effect/.changeset/pre/full-adults-double.md diff --git a/repos/effect/.changeset/funny-crabs-hang.md b/repos/effect/.changeset/pre/funny-crabs-hang.md similarity index 100% rename from repos/effect/.changeset/funny-crabs-hang.md rename to repos/effect/.changeset/pre/funny-crabs-hang.md 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 0000000000..00b7994c7d --- /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/funny-forks-move.md b/repos/effect/.changeset/pre/funny-forks-move.md similarity index 100% rename from repos/effect/.changeset/funny-forks-move.md rename to repos/effect/.changeset/pre/funny-forks-move.md 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 0000000000..0dbd54ab99 --- /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 0000000000..5e511fe65a --- /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/fuzzy-camels-hunt.md b/repos/effect/.changeset/pre/fuzzy-camels-hunt.md similarity index 100% rename from repos/effect/.changeset/fuzzy-camels-hunt.md rename to repos/effect/.changeset/pre/fuzzy-camels-hunt.md 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 0000000000..35bae2e9f8 --- /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 0000000000..eb5b34707f --- /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/fuzzy-crews-fold.md b/repos/effect/.changeset/pre/fuzzy-crews-fold.md similarity index 100% rename from repos/effect/.changeset/fuzzy-crews-fold.md rename to repos/effect/.changeset/pre/fuzzy-crews-fold.md 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 0000000000..54923cc70d --- /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/fuzzy-dodos-help.md b/repos/effect/.changeset/pre/fuzzy-dodos-help.md similarity index 100% rename from repos/effect/.changeset/fuzzy-dodos-help.md rename to repos/effect/.changeset/pre/fuzzy-dodos-help.md 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 0000000000..8fde69fcc8 --- /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-hornets-wish.md b/repos/effect/.changeset/pre/fuzzy-hornets-wish.md new file mode 100644 index 0000000000..d9d06218b2 --- /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/fuzzy-lions-perform.md b/repos/effect/.changeset/pre/fuzzy-lions-perform.md similarity index 100% rename from repos/effect/.changeset/fuzzy-lions-perform.md rename to repos/effect/.changeset/pre/fuzzy-lions-perform.md 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 0000000000..eb647e0ab2 --- /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 0000000000..835fefafdf --- /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/fuzzy-planets-sneeze.md b/repos/effect/.changeset/pre/fuzzy-planets-sneeze.md similarity index 100% rename from repos/effect/.changeset/fuzzy-planets-sneeze.md rename to repos/effect/.changeset/pre/fuzzy-planets-sneeze.md 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 0000000000..4060d9c86d --- /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 0000000000..1858474d81 --- /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 0000000000..a286099ccc --- /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/icy-flies-cross.md b/repos/effect/.changeset/pre/fuzzy-stamps-care.md similarity index 100% rename from repos/effect/.changeset/icy-flies-cross.md rename to repos/effect/.changeset/pre/fuzzy-stamps-care.md 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 0000000000..874eef0065 --- /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/giant-jeans-float.md b/repos/effect/.changeset/pre/giant-jeans-float.md new file mode 100644 index 0000000000..3f0eb7ef10 --- /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/gold-meteors-move.md b/repos/effect/.changeset/pre/gold-meteors-move.md similarity index 100% rename from repos/effect/.changeset/gold-meteors-move.md rename to repos/effect/.changeset/pre/gold-meteors-move.md diff --git a/repos/effect/.changeset/gold-readers-hug.md b/repos/effect/.changeset/pre/gold-readers-hug.md similarity index 100% rename from repos/effect/.changeset/gold-readers-hug.md rename to repos/effect/.changeset/pre/gold-readers-hug.md diff --git a/repos/effect/.changeset/gold-rings-start.md b/repos/effect/.changeset/pre/gold-rings-start.md similarity index 100% rename from repos/effect/.changeset/gold-rings-start.md rename to repos/effect/.changeset/pre/gold-rings-start.md 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 0000000000..344af728ac --- /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/good-tools-work.md b/repos/effect/.changeset/pre/good-tools-work.md similarity index 100% rename from repos/effect/.changeset/good-tools-work.md rename to repos/effect/.changeset/pre/good-tools-work.md diff --git a/repos/effect/.changeset/good-trees-pull.md b/repos/effect/.changeset/pre/good-trees-pull.md similarity index 100% rename from repos/effect/.changeset/good-trees-pull.md rename to repos/effect/.changeset/pre/good-trees-pull.md 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 0000000000..5bb98abbb5 --- /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/graph-algorithm-fixes.md b/repos/effect/.changeset/pre/graph-algorithm-fixes.md similarity index 100% rename from repos/effect/.changeset/graph-algorithm-fixes.md rename to repos/effect/.changeset/pre/graph-algorithm-fixes.md 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 0000000000..4f628c3269 --- /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 0000000000..da96b1a562 --- /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 0000000000..c006de9f87 --- /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 0000000000..25212ae68b --- /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 0000000000..a44ad46554 --- /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/great-trains-mate.md b/repos/effect/.changeset/pre/great-trains-mate.md similarity index 100% rename from repos/effect/.changeset/great-trains-mate.md rename to repos/effect/.changeset/pre/great-trains-mate.md diff --git a/repos/effect/.changeset/great-trams-report.md b/repos/effect/.changeset/pre/great-trams-report.md similarity index 100% rename from repos/effect/.changeset/great-trams-report.md rename to repos/effect/.changeset/pre/great-trams-report.md 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 0000000000..aa04104820 --- /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/green-beds-unref.md b/repos/effect/.changeset/pre/green-beds-unref.md similarity index 100% rename from repos/effect/.changeset/green-beds-unref.md rename to repos/effect/.changeset/pre/green-beds-unref.md 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 0000000000..e21ba8438f --- /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/green-chips-wash.md b/repos/effect/.changeset/pre/green-chips-wash.md similarity index 100% rename from repos/effect/.changeset/green-chips-wash.md rename to repos/effect/.changeset/pre/green-chips-wash.md diff --git a/repos/effect/.changeset/green-moons-smile.md b/repos/effect/.changeset/pre/green-moons-smile.md similarity index 100% rename from repos/effect/.changeset/green-moons-smile.md rename to repos/effect/.changeset/pre/green-moons-smile.md diff --git a/repos/effect/.changeset/green-pugs-play.md b/repos/effect/.changeset/pre/green-pugs-play.md similarity index 100% rename from repos/effect/.changeset/green-pugs-play.md rename to repos/effect/.changeset/pre/green-pugs-play.md diff --git a/repos/effect/.changeset/green-rings-prove.md b/repos/effect/.changeset/pre/green-rings-prove.md similarity index 100% rename from repos/effect/.changeset/green-rings-prove.md rename to repos/effect/.changeset/pre/green-rings-prove.md diff --git a/repos/effect/.changeset/happy-mirrors-dream.md b/repos/effect/.changeset/pre/happy-mirrors-dream.md similarity index 100% rename from repos/effect/.changeset/happy-mirrors-dream.md rename to repos/effect/.changeset/pre/happy-mirrors-dream.md 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 0000000000..774dbe3291 --- /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 0000000000..9e386e0193 --- /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/heavy-loops-cut.md b/repos/effect/.changeset/pre/heavy-loops-cut.md similarity index 100% rename from repos/effect/.changeset/heavy-loops-cut.md rename to repos/effect/.changeset/pre/heavy-loops-cut.md diff --git a/repos/effect/.changeset/heavy-trams-fix.md b/repos/effect/.changeset/pre/heavy-trams-fix.md similarity index 100% rename from repos/effect/.changeset/heavy-trams-fix.md rename to repos/effect/.changeset/pre/heavy-trams-fix.md 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 0000000000..a5c66356c1 --- /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/hip-socks-travel.md b/repos/effect/.changeset/pre/hip-socks-travel.md similarity index 100% rename from repos/effect/.changeset/hip-socks-travel.md rename to repos/effect/.changeset/pre/hip-socks-travel.md diff --git a/repos/effect/.changeset/honest-pens-thank.md b/repos/effect/.changeset/pre/honest-pens-thank.md similarity index 100% rename from repos/effect/.changeset/honest-pens-thank.md rename to repos/effect/.changeset/pre/honest-pens-thank.md diff --git a/repos/effect/.changeset/honest-rivers-notice.md b/repos/effect/.changeset/pre/honest-rivers-notice.md similarity index 100% rename from repos/effect/.changeset/honest-rivers-notice.md rename to repos/effect/.changeset/pre/honest-rivers-notice.md diff --git a/repos/effect/.changeset/hot-taxis-fry.md b/repos/effect/.changeset/pre/hot-taxis-fry.md similarity index 100% rename from repos/effect/.changeset/hot-taxis-fry.md rename to repos/effect/.changeset/pre/hot-taxis-fry.md 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 0000000000..c737634e78 --- /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-server-websocket-options.md b/repos/effect/.changeset/pre/http-server-websocket-options.md new file mode 100644 index 0000000000..177d15b7e9 --- /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/httpapi-endpoint-relax-constraints.md b/repos/effect/.changeset/pre/httpapi-endpoint-relax-constraints.md similarity index 100% rename from repos/effect/.changeset/httpapi-endpoint-relax-constraints.md rename to repos/effect/.changeset/pre/httpapi-endpoint-relax-constraints.md diff --git a/repos/effect/.changeset/httpapi-schema-service-types.md b/repos/effect/.changeset/pre/httpapi-schema-service-types.md similarity index 100% rename from repos/effect/.changeset/httpapi-schema-service-types.md rename to repos/effect/.changeset/pre/httpapi-schema-service-types.md diff --git a/repos/effect/.changeset/huge-moons-rhyme.md b/repos/effect/.changeset/pre/huge-moons-rhyme.md similarity index 100% rename from repos/effect/.changeset/huge-moons-rhyme.md rename to repos/effect/.changeset/pre/huge-moons-rhyme.md diff --git a/repos/effect/.changeset/humble-pigs-dig.md b/repos/effect/.changeset/pre/humble-pigs-dig.md similarity index 100% rename from repos/effect/.changeset/humble-pigs-dig.md rename to repos/effect/.changeset/pre/humble-pigs-dig.md 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 0000000000..602fb34220 --- /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/stale-snakes-know.md b/repos/effect/.changeset/pre/icy-flies-cross.md similarity index 100% rename from repos/effect/.changeset/stale-snakes-know.md rename to repos/effect/.changeset/pre/icy-flies-cross.md 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 0000000000..2a5ab3549f --- /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 0000000000..f45018a577 --- /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/itchy-radios-poke.md b/repos/effect/.changeset/pre/itchy-radios-poke.md similarity index 100% rename from repos/effect/.changeset/itchy-radios-poke.md rename to repos/effect/.changeset/pre/itchy-radios-poke.md diff --git a/repos/effect/.changeset/itchy-results-bet.md b/repos/effect/.changeset/pre/itchy-results-bet.md similarity index 100% rename from repos/effect/.changeset/itchy-results-bet.md rename to repos/effect/.changeset/pre/itchy-results-bet.md diff --git a/repos/effect/.changeset/itchy-shrimps-deny.md b/repos/effect/.changeset/pre/itchy-shrimps-deny.md similarity index 100% rename from repos/effect/.changeset/itchy-shrimps-deny.md rename to repos/effect/.changeset/pre/itchy-shrimps-deny.md diff --git a/repos/effect/.changeset/itchy-toes-promise.md b/repos/effect/.changeset/pre/itchy-toes-promise.md similarity index 100% rename from repos/effect/.changeset/itchy-toes-promise.md rename to repos/effect/.changeset/pre/itchy-toes-promise.md diff --git a/repos/effect/.changeset/k8s-last-transition-null.md b/repos/effect/.changeset/pre/k8s-last-transition-null.md similarity index 100% rename from repos/effect/.changeset/k8s-last-transition-null.md rename to repos/effect/.changeset/pre/k8s-last-transition-null.md 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 0000000000..a4f9793841 --- /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/khaki-cats-learn.md b/repos/effect/.changeset/pre/khaki-cats-learn.md similarity index 100% rename from repos/effect/.changeset/khaki-cats-learn.md rename to repos/effect/.changeset/pre/khaki-cats-learn.md diff --git a/repos/effect/.changeset/khaki-melons-appear.md b/repos/effect/.changeset/pre/khaki-melons-appear.md similarity index 100% rename from repos/effect/.changeset/khaki-melons-appear.md rename to repos/effect/.changeset/pre/khaki-melons-appear.md 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 0000000000..ecf9af6d25 --- /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/kind-hounds-float.md b/repos/effect/.changeset/pre/kind-hounds-float.md similarity index 100% rename from repos/effect/.changeset/kind-hounds-float.md rename to repos/effect/.changeset/pre/kind-hounds-float.md diff --git a/repos/effect/.changeset/kind-windows-fall.md b/repos/effect/.changeset/pre/kind-windows-fall.md similarity index 100% rename from repos/effect/.changeset/kind-windows-fall.md rename to repos/effect/.changeset/pre/kind-windows-fall.md diff --git a/repos/effect/.changeset/late-hotels-rule.md b/repos/effect/.changeset/pre/late-hotels-rule.md similarity index 100% rename from repos/effect/.changeset/late-hotels-rule.md rename to repos/effect/.changeset/pre/late-hotels-rule.md diff --git a/repos/effect/.changeset/late-lamps-care.md b/repos/effect/.changeset/pre/late-lamps-care.md similarity index 100% rename from repos/effect/.changeset/late-lamps-care.md rename to repos/effect/.changeset/pre/late-lamps-care.md diff --git a/repos/effect/.changeset/late-rivers-applaud.md b/repos/effect/.changeset/pre/late-rivers-applaud.md similarity index 100% rename from repos/effect/.changeset/late-rivers-applaud.md rename to repos/effect/.changeset/pre/late-rivers-applaud.md diff --git a/repos/effect/.changeset/layer-map-dynamic-idle-ttl.md b/repos/effect/.changeset/pre/layer-map-dynamic-idle-ttl.md similarity index 100% rename from repos/effect/.changeset/layer-map-dynamic-idle-ttl.md rename to repos/effect/.changeset/pre/layer-map-dynamic-idle-ttl.md 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 0000000000..e1bd0703a8 --- /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/lazy-queens-rush.md b/repos/effect/.changeset/pre/lazy-queens-rush.md similarity index 100% rename from repos/effect/.changeset/lazy-queens-rush.md rename to repos/effect/.changeset/pre/lazy-queens-rush.md diff --git a/repos/effect/.changeset/lazy-recursive-forward-refs.md b/repos/effect/.changeset/pre/lazy-recursive-forward-refs.md similarity index 100% rename from repos/effect/.changeset/lazy-recursive-forward-refs.md rename to repos/effect/.changeset/pre/lazy-recursive-forward-refs.md diff --git a/repos/effect/.changeset/lazy-timers-exist.md b/repos/effect/.changeset/pre/lazy-timers-exist.md similarity index 100% rename from repos/effect/.changeset/lazy-timers-exist.md rename to repos/effect/.changeset/pre/lazy-timers-exist.md diff --git a/repos/effect/.changeset/legal-pants-drop.md b/repos/effect/.changeset/pre/legal-pants-drop.md similarity index 100% rename from repos/effect/.changeset/legal-pants-drop.md rename to repos/effect/.changeset/pre/legal-pants-drop.md diff --git a/repos/effect/.changeset/lemon-taxis-sin.md b/repos/effect/.changeset/pre/lemon-taxis-sin.md similarity index 100% rename from repos/effect/.changeset/lemon-taxis-sin.md rename to repos/effect/.changeset/pre/lemon-taxis-sin.md diff --git a/repos/effect/.changeset/light-kids-sneeze.md b/repos/effect/.changeset/pre/light-kids-sneeze.md similarity index 100% rename from repos/effect/.changeset/light-kids-sneeze.md rename to repos/effect/.changeset/pre/light-kids-sneeze.md diff --git a/repos/effect/.changeset/little-dryers-allow.md b/repos/effect/.changeset/pre/little-dryers-allow.md similarity index 100% rename from repos/effect/.changeset/little-dryers-allow.md rename to repos/effect/.changeset/pre/little-dryers-allow.md diff --git a/repos/effect/.changeset/long-cameras-think.md b/repos/effect/.changeset/pre/long-cameras-think.md similarity index 100% rename from repos/effect/.changeset/long-cameras-think.md rename to repos/effect/.changeset/pre/long-cameras-think.md 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 0000000000..fedc375142 --- /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/lovely-cobras-change.md b/repos/effect/.changeset/pre/lovely-cobras-change.md similarity index 100% rename from repos/effect/.changeset/lovely-cobras-change.md rename to repos/effect/.changeset/pre/lovely-cobras-change.md diff --git a/repos/effect/.changeset/lovely-frogs-rescue.md b/repos/effect/.changeset/pre/lovely-frogs-rescue.md similarity index 100% rename from repos/effect/.changeset/lovely-frogs-rescue.md rename to repos/effect/.changeset/pre/lovely-frogs-rescue.md diff --git a/repos/effect/.changeset/lucky-buttons-jump.md b/repos/effect/.changeset/pre/lucky-buttons-jump.md similarity index 100% rename from repos/effect/.changeset/lucky-buttons-jump.md rename to repos/effect/.changeset/pre/lucky-buttons-jump.md 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 0000000000..96d9a4ab3d --- /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/lucky-phones-listen.md b/repos/effect/.changeset/pre/lucky-phones-listen.md similarity index 100% rename from repos/effect/.changeset/lucky-phones-listen.md rename to repos/effect/.changeset/pre/lucky-phones-listen.md diff --git a/repos/effect/.changeset/lucky-worms-type.md b/repos/effect/.changeset/pre/lucky-worms-type.md similarity index 100% rename from repos/effect/.changeset/lucky-worms-type.md rename to repos/effect/.changeset/pre/lucky-worms-type.md 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 0000000000..6e00b3ce65 --- /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 0000000000..1b4fe0b7c0 --- /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/many-badgers-obey.md b/repos/effect/.changeset/pre/many-badgers-obey.md similarity index 100% rename from repos/effect/.changeset/many-badgers-obey.md rename to repos/effect/.changeset/pre/many-badgers-obey.md 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 0000000000..3c0399361a --- /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/mean-dingos-share.md b/repos/effect/.changeset/pre/mean-dingos-share.md similarity index 100% rename from repos/effect/.changeset/mean-dingos-share.md rename to repos/effect/.changeset/pre/mean-dingos-share.md diff --git a/repos/effect/.changeset/mean-trains-smash.md b/repos/effect/.changeset/pre/mean-trains-smash.md similarity index 100% rename from repos/effect/.changeset/mean-trains-smash.md rename to repos/effect/.changeset/pre/mean-trains-smash.md 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 0000000000..30c655d7c0 --- /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/metal-nails-sneeze.md b/repos/effect/.changeset/pre/metal-nails-sneeze.md new file mode 100644 index 0000000000..148d8c62b6 --- /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/metal-parts-yell.md b/repos/effect/.changeset/pre/metal-parts-yell.md similarity index 100% rename from repos/effect/.changeset/metal-parts-yell.md rename to repos/effect/.changeset/pre/metal-parts-yell.md diff --git a/repos/effect/.changeset/mighty-games-matter.md b/repos/effect/.changeset/pre/mighty-games-matter.md similarity index 100% rename from repos/effect/.changeset/mighty-games-matter.md rename to repos/effect/.changeset/pre/mighty-games-matter.md 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 0000000000..9ad8a9d4d3 --- /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/modern-carrots-see.md b/repos/effect/.changeset/pre/modern-carrots-see.md similarity index 100% rename from repos/effect/.changeset/modern-carrots-see.md rename to repos/effect/.changeset/pre/modern-carrots-see.md diff --git a/repos/effect/.changeset/modern-uuid-guid-filter.md b/repos/effect/.changeset/pre/modern-uuid-guid-filter.md similarity index 100% rename from repos/effect/.changeset/modern-uuid-guid-filter.md rename to repos/effect/.changeset/pre/modern-uuid-guid-filter.md 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 0000000000..93f37d8f1e --- /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 0000000000..621db72844 --- /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 0000000000..d625129223 --- /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 0000000000..02150bcc48 --- /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/nasty-geese-grow.md b/repos/effect/.changeset/pre/nasty-geese-grow.md similarity index 100% rename from repos/effect/.changeset/nasty-geese-grow.md rename to repos/effect/.changeset/pre/nasty-geese-grow.md diff --git a/repos/effect/.changeset/neat-goats-wave.md b/repos/effect/.changeset/pre/neat-goats-wave.md similarity index 100% rename from repos/effect/.changeset/neat-goats-wave.md rename to repos/effect/.changeset/pre/neat-goats-wave.md diff --git a/repos/effect/.changeset/neat-kings-chew.md b/repos/effect/.changeset/pre/neat-kings-chew.md similarity index 91% rename from repos/effect/.changeset/neat-kings-chew.md rename to repos/effect/.changeset/pre/neat-kings-chew.md index 1986ccb6c3..57a18a6c69 100644 --- a/repos/effect/.changeset/neat-kings-chew.md +++ b/repos/effect/.changeset/pre/neat-kings-chew.md @@ -1,5 +1,5 @@ --- -"effect": minor +"effect": patch --- Add a new `effect/unstable/http/HttpStaticServer` module for static file serving with MIME resolution, directory index fallback, SPA fallback, and safe path resolution. diff --git a/repos/effect/.changeset/neat-lions-rest.md b/repos/effect/.changeset/pre/neat-lions-rest.md similarity index 100% rename from repos/effect/.changeset/neat-lions-rest.md rename to repos/effect/.changeset/pre/neat-lions-rest.md 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 0000000000..f78f49222d --- /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/neat-snails-wash.md b/repos/effect/.changeset/pre/neat-snails-wash.md similarity index 100% rename from repos/effect/.changeset/neat-snails-wash.md rename to repos/effect/.changeset/pre/neat-snails-wash.md diff --git a/repos/effect/.changeset/neat-taxis-notice.md b/repos/effect/.changeset/pre/neat-taxis-notice.md similarity index 100% rename from repos/effect/.changeset/neat-taxis-notice.md rename to repos/effect/.changeset/pre/neat-taxis-notice.md 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 0000000000..526502b748 --- /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/neat-windows-buy.md b/repos/effect/.changeset/pre/neat-windows-buy.md similarity index 100% rename from repos/effect/.changeset/neat-windows-buy.md rename to repos/effect/.changeset/pre/neat-windows-buy.md diff --git a/repos/effect/.changeset/new-dogs-swim.md b/repos/effect/.changeset/pre/new-dogs-swim.md similarity index 100% rename from repos/effect/.changeset/new-dogs-swim.md rename to repos/effect/.changeset/pre/new-dogs-swim.md diff --git a/repos/effect/.changeset/new-toes-stop.md b/repos/effect/.changeset/pre/new-toes-stop.md similarity index 100% rename from repos/effect/.changeset/new-toes-stop.md rename to repos/effect/.changeset/pre/new-toes-stop.md diff --git a/repos/effect/.changeset/ninety-geese-exist.md b/repos/effect/.changeset/pre/ninety-geese-exist.md similarity index 100% rename from repos/effect/.changeset/ninety-geese-exist.md rename to repos/effect/.changeset/pre/ninety-geese-exist.md 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 0000000000..71ac7237fb --- /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 0000000000..297b295aae --- /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 0000000000..ae6a0a78b3 --- /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/o8drprcu-sqlite-node-node-sqlite.md b/repos/effect/.changeset/pre/o8drprcu-sqlite-node-node-sqlite.md new file mode 100644 index 0000000000..b1840466fe --- /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/odd-boats-think.md b/repos/effect/.changeset/pre/odd-boats-think.md similarity index 100% rename from repos/effect/.changeset/odd-boats-think.md rename to repos/effect/.changeset/pre/odd-boats-think.md diff --git a/repos/effect/.changeset/odd-bulldogs-sleep.md b/repos/effect/.changeset/pre/odd-bulldogs-sleep.md similarity index 100% rename from repos/effect/.changeset/odd-bulldogs-sleep.md rename to repos/effect/.changeset/pre/odd-bulldogs-sleep.md diff --git a/repos/effect/.changeset/odd-fans-glow.md b/repos/effect/.changeset/pre/odd-fans-glow.md similarity index 100% rename from repos/effect/.changeset/odd-fans-glow.md rename to repos/effect/.changeset/pre/odd-fans-glow.md diff --git a/repos/effect/.changeset/odd-forks-talk.md b/repos/effect/.changeset/pre/odd-forks-talk.md similarity index 100% rename from repos/effect/.changeset/odd-forks-talk.md rename to repos/effect/.changeset/pre/odd-forks-talk.md diff --git a/repos/effect/.changeset/odd-laws-draw.md b/repos/effect/.changeset/pre/odd-laws-draw.md similarity index 100% rename from repos/effect/.changeset/odd-laws-draw.md rename to repos/effect/.changeset/pre/odd-laws-draw.md diff --git a/repos/effect/.changeset/odd-owls-smoke.md b/repos/effect/.changeset/pre/odd-owls-smoke.md similarity index 100% rename from repos/effect/.changeset/odd-owls-smoke.md rename to repos/effect/.changeset/pre/odd-owls-smoke.md diff --git a/repos/effect/.changeset/odd-socks-boil.md b/repos/effect/.changeset/pre/odd-socks-boil.md similarity index 100% rename from repos/effect/.changeset/odd-socks-boil.md rename to repos/effect/.changeset/pre/odd-socks-boil.md diff --git a/repos/effect/.changeset/odd-suns-dance.md b/repos/effect/.changeset/pre/odd-suns-dance.md similarity index 100% rename from repos/effect/.changeset/odd-suns-dance.md rename to repos/effect/.changeset/pre/odd-suns-dance.md diff --git a/repos/effect/.changeset/old-brooms-cry.md b/repos/effect/.changeset/pre/old-brooms-cry.md similarity index 100% rename from repos/effect/.changeset/old-brooms-cry.md rename to repos/effect/.changeset/pre/old-brooms-cry.md diff --git a/repos/effect/.changeset/old-facts-stand.md b/repos/effect/.changeset/pre/old-facts-stand.md similarity index 100% rename from repos/effect/.changeset/old-facts-stand.md rename to repos/effect/.changeset/pre/old-facts-stand.md diff --git a/repos/effect/.changeset/old-mirrors-float.md b/repos/effect/.changeset/pre/old-mirrors-float.md similarity index 100% rename from repos/effect/.changeset/old-mirrors-float.md rename to repos/effect/.changeset/pre/old-mirrors-float.md diff --git a/repos/effect/.changeset/olive-poems-visit.md b/repos/effect/.changeset/pre/olive-poems-visit.md similarity index 100% rename from repos/effect/.changeset/olive-poems-visit.md rename to repos/effect/.changeset/pre/olive-poems-visit.md 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 0000000000..47e27a0b92 --- /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/open-hotels-remain.md b/repos/effect/.changeset/pre/open-hotels-remain.md similarity index 100% rename from repos/effect/.changeset/open-hotels-remain.md rename to repos/effect/.changeset/pre/open-hotels-remain.md 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 0000000000..5286f106e7 --- /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 0000000000..8426aa01fd --- /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/openai-compat-nullable-tool-name.md b/repos/effect/.changeset/pre/openai-compat-nullable-tool-name.md similarity index 100% rename from repos/effect/.changeset/openai-compat-nullable-tool-name.md rename to repos/effect/.changeset/pre/openai-compat-nullable-tool-name.md 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 0000000000..d2b2ce4412 --- /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/openai-compat-reasoning.md b/repos/effect/.changeset/pre/openai-compat-reasoning.md similarity index 100% rename from repos/effect/.changeset/openai-compat-reasoning.md rename to repos/effect/.changeset/pre/openai-compat-reasoning.md 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 0000000000..c2bf786a24 --- /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/openapi-generator-sse-constraint-decoder.md b/repos/effect/.changeset/pre/openapi-generator-sse-constraint-decoder.md similarity index 100% rename from repos/effect/.changeset/openapi-generator-sse-constraint-decoder.md rename to repos/effect/.changeset/pre/openapi-generator-sse-constraint-decoder.md 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 0000000000..d9b9a67956 --- /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/openrouter-input-audio.md b/repos/effect/.changeset/pre/openrouter-input-audio.md similarity index 100% rename from repos/effect/.changeset/openrouter-input-audio.md rename to repos/effect/.changeset/pre/openrouter-input-audio.md 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 0000000000..41da9fcad8 --- /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 0000000000..4ebcd6ef38 --- /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/optimize-array-equality.md b/repos/effect/.changeset/pre/optimize-array-equality.md new file mode 100644 index 0000000000..1ae1dede64 --- /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 0000000000..af3799598d --- /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 0000000000..1cb193cf59 --- /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 0000000000..61b14ad4cb --- /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 0000000000..beb5997ae0 --- /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 0000000000..eebdd1ca0e --- /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/otel-resource-env-precedence.md b/repos/effect/.changeset/pre/otel-resource-env-precedence.md similarity index 100% rename from repos/effect/.changeset/otel-resource-env-precedence.md rename to repos/effect/.changeset/pre/otel-resource-env-precedence.md diff --git a/repos/effect/.changeset/perfect-buckets-tickle.md b/repos/effect/.changeset/pre/perfect-buckets-tickle.md similarity index 100% rename from repos/effect/.changeset/perfect-buckets-tickle.md rename to repos/effect/.changeset/pre/perfect-buckets-tickle.md diff --git a/repos/effect/.changeset/petite-months-allow.md b/repos/effect/.changeset/pre/petite-months-allow.md similarity index 100% rename from repos/effect/.changeset/petite-months-allow.md rename to repos/effect/.changeset/pre/petite-months-allow.md 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 0000000000..3eed009726 --- /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 0000000000..3671583479 --- /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/platform-crypto-service.md b/repos/effect/.changeset/pre/platform-crypto-service.md similarity index 100% rename from repos/effect/.changeset/platform-crypto-service.md rename to repos/effect/.changeset/pre/platform-crypto-service.md diff --git a/repos/effect/.changeset/platform-node-shared-barrel.md b/repos/effect/.changeset/pre/platform-node-shared-barrel.md similarity index 100% rename from repos/effect/.changeset/platform-node-shared-barrel.md rename to repos/effect/.changeset/pre/platform-node-shared-barrel.md diff --git a/repos/effect/.changeset/plenty-moons-pull.md b/repos/effect/.changeset/pre/plenty-moons-pull.md similarity index 100% rename from repos/effect/.changeset/plenty-moons-pull.md rename to repos/effect/.changeset/pre/plenty-moons-pull.md diff --git a/repos/effect/.changeset/polite-brooms-tickle.md b/repos/effect/.changeset/pre/polite-brooms-tickle.md similarity index 100% rename from repos/effect/.changeset/polite-brooms-tickle.md rename to repos/effect/.changeset/pre/polite-brooms-tickle.md 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 0000000000..348f740e99 --- /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 0000000000..7ae08b5bc1 --- /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/polite-pigs-speak.md b/repos/effect/.changeset/pre/polite-pigs-speak.md similarity index 100% rename from repos/effect/.changeset/polite-pigs-speak.md rename to repos/effect/.changeset/pre/polite-pigs-speak.md diff --git a/repos/effect/.changeset/polite-tables-kneel.md b/repos/effect/.changeset/pre/polite-tables-kneel.md similarity index 100% rename from repos/effect/.changeset/polite-tables-kneel.md rename to repos/effect/.changeset/pre/polite-tables-kneel.md 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 0000000000..36632f7c28 --- /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/port-react-hydration.md b/repos/effect/.changeset/pre/port-react-hydration.md similarity index 100% rename from repos/effect/.changeset/port-react-hydration.md rename to repos/effect/.changeset/pre/port-react-hydration.md 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 0000000000..afe1d8a1ea --- /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/preserve-command-hidden-metadata.md b/repos/effect/.changeset/pre/preserve-command-hidden-metadata.md new file mode 100644 index 0000000000..ceb6c68686 --- /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 0000000000..bd8b92ad8b --- /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-otel-parent-context.md b/repos/effect/.changeset/pre/preserve-otel-parent-context.md new file mode 100644 index 0000000000..d436bd9715 --- /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/pretty-moments-clap.md b/repos/effect/.changeset/pre/pretty-moments-clap.md new file mode 100644 index 0000000000..0dd186ea21 --- /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 0000000000..0a1c82a3b2 --- /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/provider-executed-tool-results.md b/repos/effect/.changeset/pre/provider-executed-tool-results.md new file mode 100644 index 0000000000..a589416b38 --- /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/public-deer-ring.md b/repos/effect/.changeset/pre/public-deer-ring.md similarity index 100% rename from repos/effect/.changeset/public-deer-ring.md rename to repos/effect/.changeset/pre/public-deer-ring.md diff --git a/repos/effect/.changeset/public-jeans-stop.md b/repos/effect/.changeset/pre/public-jeans-stop.md similarity index 100% rename from repos/effect/.changeset/public-jeans-stop.md rename to repos/effect/.changeset/pre/public-jeans-stop.md diff --git a/repos/effect/.changeset/pubsub-publish-false.md b/repos/effect/.changeset/pre/pubsub-publish-false.md similarity index 100% rename from repos/effect/.changeset/pubsub-publish-false.md rename to repos/effect/.changeset/pre/pubsub-publish-false.md diff --git a/repos/effect/.changeset/puny-pens-clap.md b/repos/effect/.changeset/pre/puny-pens-clap.md similarity index 100% rename from repos/effect/.changeset/puny-pens-clap.md rename to repos/effect/.changeset/pre/puny-pens-clap.md diff --git a/repos/effect/.changeset/purple-bars-prove.md b/repos/effect/.changeset/pre/purple-bars-prove.md similarity index 100% rename from repos/effect/.changeset/purple-bars-prove.md rename to repos/effect/.changeset/pre/purple-bars-prove.md diff --git a/repos/effect/.changeset/purple-schools-float.md b/repos/effect/.changeset/pre/purple-schools-float.md similarity index 100% rename from repos/effect/.changeset/purple-schools-float.md rename to repos/effect/.changeset/pre/purple-schools-float.md diff --git a/repos/effect/.changeset/purple-turtles-draw.md b/repos/effect/.changeset/pre/purple-turtles-draw.md similarity index 100% rename from repos/effect/.changeset/purple-turtles-draw.md rename to repos/effect/.changeset/pre/purple-turtles-draw.md diff --git a/repos/effect/.changeset/quick-dragons-fix.md b/repos/effect/.changeset/pre/quick-dragons-fix.md similarity index 100% rename from repos/effect/.changeset/quick-dragons-fix.md rename to repos/effect/.changeset/pre/quick-dragons-fix.md diff --git a/repos/effect/.changeset/quick-falcons-travel.md b/repos/effect/.changeset/pre/quick-falcons-travel.md similarity index 100% rename from repos/effect/.changeset/quick-falcons-travel.md rename to repos/effect/.changeset/pre/quick-falcons-travel.md diff --git a/repos/effect/.changeset/quick-geese-relax.md b/repos/effect/.changeset/pre/quick-geese-relax.md similarity index 100% rename from repos/effect/.changeset/quick-geese-relax.md rename to repos/effect/.changeset/pre/quick-geese-relax.md 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 0000000000..2c1b55d09a --- /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/quick-lamps-dig.md b/repos/effect/.changeset/pre/quick-lamps-dig.md similarity index 100% rename from repos/effect/.changeset/quick-lamps-dig.md rename to repos/effect/.changeset/pre/quick-lamps-dig.md diff --git a/repos/effect/.changeset/quick-lizards-fall.md b/repos/effect/.changeset/pre/quick-lizards-fall.md similarity index 100% rename from repos/effect/.changeset/quick-lizards-fall.md rename to repos/effect/.changeset/pre/quick-lizards-fall.md diff --git a/repos/effect/.changeset/quick-trees-join.md b/repos/effect/.changeset/pre/quick-trees-join.md similarity index 100% rename from repos/effect/.changeset/quick-trees-join.md rename to repos/effect/.changeset/pre/quick-trees-join.md diff --git a/repos/effect/.changeset/quiet-carpets-grin.md b/repos/effect/.changeset/pre/quiet-carpets-grin.md similarity index 100% rename from repos/effect/.changeset/quiet-carpets-grin.md rename to repos/effect/.changeset/pre/quiet-carpets-grin.md 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 0000000000..2fc507ec82 --- /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 0000000000..d29a3bd233 --- /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 0000000000..81ca36e683 --- /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/quiet-files-hunt.md b/repos/effect/.changeset/pre/quiet-files-hunt.md similarity index 100% rename from repos/effect/.changeset/quiet-files-hunt.md rename to repos/effect/.changeset/pre/quiet-files-hunt.md 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 0000000000..fb549eb54d --- /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/quiet-lamps-jam.md b/repos/effect/.changeset/pre/quiet-lamps-jam.md similarity index 100% rename from repos/effect/.changeset/quiet-lamps-jam.md rename to repos/effect/.changeset/pre/quiet-lamps-jam.md 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 0000000000..c54d96dde9 --- /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 0000000000..d547f9aac5 --- /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 0000000000..7673c47025 --- /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 0000000000..6a85ac0b3c --- /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 0000000000..879a8c4425 --- /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 0000000000..b52a3f1c36 --- /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/quiet-radios-wave.md b/repos/effect/.changeset/pre/quiet-radios-wave.md similarity index 100% rename from repos/effect/.changeset/quiet-radios-wave.md rename to repos/effect/.changeset/pre/quiet-radios-wave.md diff --git a/repos/effect/.changeset/quiet-redis-scripts.md b/repos/effect/.changeset/pre/quiet-redis-scripts.md similarity index 100% rename from repos/effect/.changeset/quiet-redis-scripts.md rename to repos/effect/.changeset/pre/quiet-redis-scripts.md 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 0000000000..e27e942f13 --- /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 0000000000..6d2d9ab73b --- /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 0000000000..9e574309a3 --- /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/quiet-tigers-yell.md b/repos/effect/.changeset/pre/quiet-tigers-yell.md similarity index 100% rename from repos/effect/.changeset/quiet-tigers-yell.md rename to repos/effect/.changeset/pre/quiet-tigers-yell.md 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 0000000000..d14443e6e0 --- /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 0000000000..ef13d5bf91 --- /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/random-choice.md b/repos/effect/.changeset/pre/random-choice.md similarity index 100% rename from repos/effect/.changeset/random-choice.md rename to repos/effect/.changeset/pre/random-choice.md 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 0000000000..9037b67ad0 --- /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/ready-olives-divide.md b/repos/effect/.changeset/pre/ready-olives-divide.md similarity index 100% rename from repos/effect/.changeset/ready-olives-divide.md rename to repos/effect/.changeset/pre/ready-olives-divide.md diff --git a/repos/effect/.changeset/real-trains-ring.md b/repos/effect/.changeset/pre/real-trains-ring.md similarity index 100% rename from repos/effect/.changeset/real-trains-ring.md rename to repos/effect/.changeset/pre/real-trains-ring.md 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 0000000000..13fc30740d --- /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/red-pigs-repair.md b/repos/effect/.changeset/pre/red-pigs-repair.md similarity index 100% rename from repos/effect/.changeset/red-pigs-repair.md rename to repos/effect/.changeset/pre/red-pigs-repair.md diff --git a/repos/effect/.changeset/redacted-representation-options.md b/repos/effect/.changeset/pre/redacted-representation-options.md similarity index 100% rename from repos/effect/.changeset/redacted-representation-options.md rename to repos/effect/.changeset/pre/redacted-representation-options.md diff --git a/repos/effect/.changeset/refactor-cli-global-flags.md b/repos/effect/.changeset/pre/refactor-cli-global-flags.md similarity index 100% rename from repos/effect/.changeset/refactor-cli-global-flags.md rename to repos/effect/.changeset/pre/refactor-cli-global-flags.md diff --git a/repos/effect/.changeset/refactor-config-provider.md b/repos/effect/.changeset/pre/refactor-config-provider.md similarity index 100% rename from repos/effect/.changeset/refactor-config-provider.md rename to repos/effect/.changeset/pre/refactor-config-provider.md diff --git a/repos/effect/.changeset/refactor-representation-references.md b/repos/effect/.changeset/pre/refactor-representation-references.md similarity index 100% rename from repos/effect/.changeset/refactor-representation-references.md rename to repos/effect/.changeset/pre/refactor-representation-references.md 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 0000000000..bd2a1c58c7 --- /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 0000000000..501d714fd8 --- /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 0000000000..511689a84b --- /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 0000000000..fe2aa1ba1b --- /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 0000000000..55d8b1a8cd --- /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/remove-effect-transactionwith.md b/repos/effect/.changeset/pre/remove-effect-transactionwith.md similarity index 100% rename from repos/effect/.changeset/remove-effect-transactionwith.md rename to repos/effect/.changeset/pre/remove-effect-transactionwith.md diff --git a/repos/effect/.changeset/remove-http-span-counter.md b/repos/effect/.changeset/pre/remove-http-span-counter.md similarity index 100% rename from repos/effect/.changeset/remove-http-span-counter.md rename to repos/effect/.changeset/pre/remove-http-span-counter.md diff --git a/repos/effect/.changeset/remove-nullor.md b/repos/effect/.changeset/pre/remove-nullor.md similarity index 100% rename from repos/effect/.changeset/remove-nullor.md rename to repos/effect/.changeset/pre/remove-nullor.md diff --git a/repos/effect/.changeset/remove-openapi-fromapi-options.md b/repos/effect/.changeset/pre/remove-openapi-fromapi-options.md similarity index 100% rename from repos/effect/.changeset/remove-openapi-fromapi-options.md rename to repos/effect/.changeset/pre/remove-openapi-fromapi-options.md 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 0000000000..79d641e92f --- /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 0000000000..ee0be86341 --- /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 0000000000..1863f14962 --- /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 0000000000..18572cb536 --- /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 0000000000..05add770fe --- /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/remove-schema-stringtree-keep-declarations.md b/repos/effect/.changeset/pre/remove-schema-stringtree-keep-declarations.md similarity index 100% rename from repos/effect/.changeset/remove-schema-stringtree-keep-declarations.md rename to repos/effect/.changeset/pre/remove-schema-stringtree-keep-declarations.md 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 0000000000..cb605c7008 --- /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/remove-types-mergerecord.md b/repos/effect/.changeset/pre/remove-types-mergerecord.md similarity index 100% rename from repos/effect/.changeset/remove-types-mergerecord.md rename to repos/effect/.changeset/pre/remove-types-mergerecord.md diff --git a/repos/effect/.changeset/remove-unused-utils-apis.md b/repos/effect/.changeset/pre/remove-unused-utils-apis.md similarity index 100% rename from repos/effect/.changeset/remove-unused-utils-apis.md rename to repos/effect/.changeset/pre/remove-unused-utils-apis.md 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 0000000000..d1d820b426 --- /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/rename-rebuild-out.md b/repos/effect/.changeset/pre/rename-rebuild-out.md similarity index 100% rename from repos/effect/.changeset/rename-rebuild-out.md rename to repos/effect/.changeset/pre/rename-rebuild-out.md 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 0000000000..f4ebfb8fe3 --- /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 0000000000..cb8229f8a8 --- /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 0000000000..038f5d9363 --- /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 0000000000..3c81a40508 --- /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/resource-subscriptions.md b/repos/effect/.changeset/pre/resource-subscriptions.md new file mode 100644 index 0000000000..c91274c3b8 --- /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/restore-schema-parse-options.md b/repos/effect/.changeset/pre/restore-schema-parse-options.md similarity index 100% rename from repos/effect/.changeset/restore-schema-parse-options.md rename to repos/effect/.changeset/pre/restore-schema-parse-options.md 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 0000000000..dbcacc774d --- /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 0000000000..c4017e4130 --- /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 0000000000..1c7d4c0878 --- /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/rich-dots-push.md b/repos/effect/.changeset/pre/rich-dots-push.md similarity index 100% rename from repos/effect/.changeset/rich-dots-push.md rename to repos/effect/.changeset/pre/rich-dots-push.md diff --git a/repos/effect/.changeset/rich-hoops-nail.md b/repos/effect/.changeset/pre/rich-hoops-nail.md similarity index 100% rename from repos/effect/.changeset/rich-hoops-nail.md rename to repos/effect/.changeset/pre/rich-hoops-nail.md diff --git a/repos/effect/.changeset/rich-sloths-draw.md b/repos/effect/.changeset/pre/rich-sloths-draw.md similarity index 100% rename from repos/effect/.changeset/rich-sloths-draw.md rename to repos/effect/.changeset/pre/rich-sloths-draw.md diff --git a/repos/effect/.changeset/ripe-lies-battle.md b/repos/effect/.changeset/pre/ripe-lies-battle.md similarity index 100% rename from repos/effect/.changeset/ripe-lies-battle.md rename to repos/effect/.changeset/pre/ripe-lies-battle.md diff --git a/repos/effect/.changeset/rpc-client-http-early-close.md b/repos/effect/.changeset/pre/rpc-client-http-early-close.md similarity index 100% rename from repos/effect/.changeset/rpc-client-http-early-close.md rename to repos/effect/.changeset/pre/rpc-client-http-early-close.md diff --git a/repos/effect/.changeset/rpc-middleware-provides-fix.md b/repos/effect/.changeset/pre/rpc-middleware-provides-fix.md similarity index 100% rename from repos/effect/.changeset/rpc-middleware-provides-fix.md rename to repos/effect/.changeset/pre/rpc-middleware-provides-fix.md 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 0000000000..ab10a6aa00 --- /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/scalar-custom-fetch.md b/repos/effect/.changeset/pre/scalar-custom-fetch.md similarity index 100% rename from repos/effect/.changeset/scalar-custom-fetch.md rename to repos/effect/.changeset/pre/scalar-custom-fetch.md 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 0000000000..d0802c815b --- /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/schema-as-class.md b/repos/effect/.changeset/pre/schema-as-class.md similarity index 100% rename from repos/effect/.changeset/schema-as-class.md rename to repos/effect/.changeset/pre/schema-as-class.md diff --git a/repos/effect/.changeset/schema-asserts-signature.md b/repos/effect/.changeset/pre/schema-asserts-signature.md similarity index 100% rename from repos/effect/.changeset/schema-asserts-signature.md rename to repos/effect/.changeset/pre/schema-asserts-signature.md diff --git a/repos/effect/.changeset/schema-clean-up-additionalProperties.md b/repos/effect/.changeset/pre/schema-clean-up-additionalProperties.md similarity index 100% rename from repos/effect/.changeset/schema-clean-up-additionalProperties.md rename to repos/effect/.changeset/pre/schema-clean-up-additionalProperties.md 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 0000000000..c47989842e --- /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 0000000000..a7fbee4e9f --- /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/schema-datetime-utc-from-string.md b/repos/effect/.changeset/pre/schema-datetime-utc-from-string.md similarity index 100% rename from repos/effect/.changeset/schema-datetime-utc-from-string.md rename to repos/effect/.changeset/pre/schema-datetime-utc-from-string.md diff --git a/repos/effect/.changeset/schema-decoding-defaults-services.md b/repos/effect/.changeset/pre/schema-decoding-defaults-services.md similarity index 100% rename from repos/effect/.changeset/schema-decoding-defaults-services.md rename to repos/effect/.changeset/pre/schema-decoding-defaults-services.md diff --git a/repos/effect/.changeset/schema-defaults-issue-channel.md b/repos/effect/.changeset/pre/schema-defaults-issue-channel.md similarity index 100% rename from repos/effect/.changeset/schema-defaults-issue-channel.md rename to repos/effect/.changeset/pre/schema-defaults-issue-channel.md 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 0000000000..43c457b1a0 --- /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/schema-dollar-prefix.md b/repos/effect/.changeset/pre/schema-dollar-prefix.md similarity index 100% rename from repos/effect/.changeset/schema-dollar-prefix.md rename to repos/effect/.changeset/pre/schema-dollar-prefix.md diff --git a/repos/effect/.changeset/schema-lazy-bottom.md b/repos/effect/.changeset/pre/schema-lazy-bottom.md similarity index 100% rename from repos/effect/.changeset/schema-lazy-bottom.md rename to repos/effect/.changeset/pre/schema-lazy-bottom.md diff --git a/repos/effect/.changeset/schema-missing-self-generic.md b/repos/effect/.changeset/pre/schema-missing-self-generic.md similarity index 100% rename from repos/effect/.changeset/schema-missing-self-generic.md rename to repos/effect/.changeset/pre/schema-missing-self-generic.md diff --git a/repos/effect/.changeset/schema-ordered-arbitrary-constraints.md b/repos/effect/.changeset/pre/schema-ordered-arbitrary-constraints.md similarity index 100% rename from repos/effect/.changeset/schema-ordered-arbitrary-constraints.md rename to repos/effect/.changeset/pre/schema-ordered-arbitrary-constraints.md diff --git a/repos/effect/.changeset/schema-parser-adapter-errors.md b/repos/effect/.changeset/pre/schema-parser-adapter-errors.md similarity index 100% rename from repos/effect/.changeset/schema-parser-adapter-errors.md rename to repos/effect/.changeset/pre/schema-parser-adapter-errors.md diff --git a/repos/effect/.changeset/schema-refactor-toCodecJson.md b/repos/effect/.changeset/pre/schema-refactor-toCodecJson.md similarity index 100% rename from repos/effect/.changeset/schema-refactor-toCodecJson.md rename to repos/effect/.changeset/pre/schema-refactor-toCodecJson.md diff --git a/repos/effect/.changeset/schema-remove-annotate-in.md b/repos/effect/.changeset/pre/schema-remove-annotate-in.md similarity index 100% rename from repos/effect/.changeset/schema-remove-annotate-in.md rename to repos/effect/.changeset/pre/schema-remove-annotate-in.md diff --git a/repos/effect/.changeset/schema-rename-makeUnsafe-to-make.md b/repos/effect/.changeset/pre/schema-rename-makeUnsafe-to-make.md similarity index 100% rename from repos/effect/.changeset/schema-rename-makeUnsafe-to-make.md rename to repos/effect/.changeset/pre/schema-rename-makeUnsafe-to-make.md diff --git a/repos/effect/.changeset/schema-rename-parser-makeUnsafe.md b/repos/effect/.changeset/pre/schema-rename-parser-makeUnsafe.md similarity index 100% rename from repos/effect/.changeset/schema-rename-parser-makeUnsafe.md rename to repos/effect/.changeset/pre/schema-rename-parser-makeUnsafe.md 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 0000000000..fddaef586a --- /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

" * ``` * * @category models @@ -92,10 +95,11 @@ export interface ScopedAtom, Input = never> { * * **Example** (Creating a scoped atom with input) * - * ```ts + * ```ts import.meta.vitest * import { make, useAtomValue } from "@effect/atom-react" * import { Atom } from "effect/unstable/reactivity" * import * as React from "react" + * import { renderToStaticMarkup } from "react-dom/server" * * const User = make((name: string) => Atom.make(name)) * @@ -112,6 +116,8 @@ export interface ScopedAtom, Input = never> { * React.createElement(UserName) * ) * } + * + * renderToStaticMarkup(React.createElement(App)) // => "Ada" * ``` * * @category constructors diff --git a/repos/effect/packages/atom/react/test/index.test.tsx b/repos/effect/packages/atom/react/test/index.test.tsx index df4d8ca9db..06c2668b95 100644 --- a/repos/effect/packages/atom/react/test/index.test.tsx +++ b/repos/effect/packages/atom/react/test/index.test.tsx @@ -135,6 +135,49 @@ describe("atom-react", () => { expect(screen.getByTestId("loading")).toBeInTheDocument() }) + + test("suspense subscriptions are isolated per registry", async () => { + const atom = Atom.make(AsyncResult.initial()) + const firstRegistry = AtomRegistry.make() + const secondRegistry = AtomRegistry.make() + + function TestComponent({ id }: { readonly id: string }) { + const value = useAtomSuspense(atom).value + return
{value}
+ } + + render( + + Loading...}> + + + + ) + render( + + Loading...}> + + + + ) + + act(() => { + secondRegistry.set(atom, AsyncResult.success("second")) + }) + + await waitFor(() => { + expect(screen.getByTestId("second-value")).toHaveTextContent("second") + }) + expect(screen.getByTestId("first-loading")).toBeInTheDocument() + + act(() => { + firstRegistry.set(atom, AsyncResult.success("first")) + }) + + await waitFor(() => { + expect(screen.getByTestId("first-value")).toHaveTextContent("first") + }) + }) }) describe("ScopedAtom", () => { diff --git a/repos/effect/packages/atom/react/tsconfig.json b/repos/effect/packages/atom/react/tsconfig.json index 19a2f5dbca..e2a8ca19a0 100644 --- a/repos/effect/packages/atom/react/tsconfig.json +++ b/repos/effect/packages/atom/react/tsconfig.json @@ -1,5 +1,5 @@ { - "$schema": "http://json.schemastore.org/tsconfig", + "$schema": "https://json.schemastore.org/tsconfig", "extends": "../../../tsconfig.base.json", "include": ["src"], "references": [ diff --git a/repos/effect/packages/atom/react/vitest.config.ts b/repos/effect/packages/atom/react/vitest.config.ts deleted file mode 100644 index d9ba9429e6..0000000000 --- a/repos/effect/packages/atom/react/vitest.config.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { mergeConfig } from "vitest/config" -import shared from "../../../vitest.shared.ts" - -export default mergeConfig(shared, { - test: { - environment: "jsdom", - setupFiles: ["./vitest.setup.ts"] - } -}) diff --git a/repos/effect/packages/atom/solid/CHANGELOG.md b/repos/effect/packages/atom/solid/CHANGELOG.md index b03148bece..f36f787405 100644 --- a/repos/effect/packages/atom/solid/CHANGELOG.md +++ b/repos/effect/packages/atom/solid/CHANGELOG.md @@ -1,5 +1,127 @@ # @effect/atom-solid +## 4.0.0-beta.107 + +### Patch Changes + +- Updated dependencies [[`596f3f9`](https://github.com/Effect-TS/effect/commit/596f3f92d7fe355811b815cb212332b082268ce8), [`9611ed4`](https://github.com/Effect-TS/effect/commit/9611ed42d11300546b339ab13492a0f7bdb1ebfb), [`8b91605`](https://github.com/Effect-TS/effect/commit/8b9160548556e4b0ec7ee2f2707716776be49018), [`d901928`](https://github.com/Effect-TS/effect/commit/d901928efa44f573ed1247f53fdb203a8e4fcede), [`b32bdef`](https://github.com/Effect-TS/effect/commit/b32bdef0d119a1ad1463dc01a46763ffee1f9bd9)]: + - effect@4.0.0-beta.107 + +## 4.0.0-beta.106 + +### Patch Changes + +- Updated dependencies [[`2695168`](https://github.com/Effect-TS/effect/commit/269516851b24916d72771f8a554b88722e3732e7), [`6310a8c`](https://github.com/Effect-TS/effect/commit/6310a8c68c74dcf1d23948ec9243ac5f407a1651), [`c2071b1`](https://github.com/Effect-TS/effect/commit/c2071b1647e2326568c1d0689274ef62b8a7183f), [`7aff81a`](https://github.com/Effect-TS/effect/commit/7aff81a9cefe681483ef8abf717d786fd10e7e8d), [`a1d4057`](https://github.com/Effect-TS/effect/commit/a1d4057711935a544ef441bc2d0ac3565dfa9266), [`abf77b0`](https://github.com/Effect-TS/effect/commit/abf77b04009dcb4d67a258f9d8ada778e9f4ffae), [`6c60375`](https://github.com/Effect-TS/effect/commit/6c60375e68683a32d54554150cc493e16550a06d), [`22f4897`](https://github.com/Effect-TS/effect/commit/22f4897bbae24783d4516f6bef353f1db4ec6d03), [`615d1d5`](https://github.com/Effect-TS/effect/commit/615d1d5d0256ec8160f2e08d0dcf5dc83acb7bf1), [`3a86757`](https://github.com/Effect-TS/effect/commit/3a867573ddeed5888dabdeb3225a9ebbf00491e7), [`f4a9762`](https://github.com/Effect-TS/effect/commit/f4a9762bb9dfad59c215f2e099dcc829d74f4ed1), [`0bcf6ed`](https://github.com/Effect-TS/effect/commit/0bcf6ed57c22e8a36964726b15464101d90f5997), [`ba9cb63`](https://github.com/Effect-TS/effect/commit/ba9cb63b87d45ce2df872dd8ef0905da147cc675), [`42c810d`](https://github.com/Effect-TS/effect/commit/42c810dd372275b822dd99c7d7e774e153f0a752), [`1416ccd`](https://github.com/Effect-TS/effect/commit/1416ccd474bc9da8979f51b72b5e53fb3ac56edf), [`08d0d39`](https://github.com/Effect-TS/effect/commit/08d0d39a225deccb9db213ab5fcf55edb9f9ba5d), [`548908a`](https://github.com/Effect-TS/effect/commit/548908a71d9337cb7defe7fc93b2fba8f6a04b6f), [`4b3460d`](https://github.com/Effect-TS/effect/commit/4b3460daa434ec465a95a50704fe1103a9275999), [`d170596`](https://github.com/Effect-TS/effect/commit/d17059615cca37ca2776654078fe0501ac5202e6), [`aea89d0`](https://github.com/Effect-TS/effect/commit/aea89d0c42ee0ac707a4962cd348fd3158cb469b), [`deed5fb`](https://github.com/Effect-TS/effect/commit/deed5fbdc91cf8bf8c5fce7dfa5d6527ac944726)]: + - effect@4.0.0-beta.106 + +## 4.0.0-beta.105 + +### Patch Changes + +- [#7094](https://github.com/Effect-TS/effect/pull/7094) [`31efc5c`](https://github.com/Effect-TS/effect/commit/31efc5c4eabbe37f9ddab030ef760926e6ff3d24) Thanks @fubhy! - Update peer dependencies +- Updated dependencies [[`0418564`](https://github.com/Effect-TS/effect/commit/04185644dabb8e4169f1ef6cbbc0b36c4db2f7f4), [`d334a85`](https://github.com/Effect-TS/effect/commit/d334a8593aafcd60753995a9449b654c67bfdcc1), [`f0be855`](https://github.com/Effect-TS/effect/commit/f0be8554da6ee00293a6b23869ac46a0b0d97dc8), [`b206fa5`](https://github.com/Effect-TS/effect/commit/b206fa5d7655c1634c9993410a9203f6616a5ca2), [`b938c8a`](https://github.com/Effect-TS/effect/commit/b938c8ad2823bd88493187922f7d9090eff037b6), [`8525f05`](https://github.com/Effect-TS/effect/commit/8525f05d1e14ea12298e9e1a0df497bfaac2ce9a)]: + - effect@4.0.0-beta.105 + +## 4.0.0-beta.104 + +### Patch Changes + +- Updated dependencies [[`1001bcc`](https://github.com/Effect-TS/effect/commit/1001bccb9e874918d59dbb36860f1c5d4499ac20), [`993ba60`](https://github.com/Effect-TS/effect/commit/993ba60ee6c7ca6eb84522040f8b0d268b6ba7d4), [`67faacd`](https://github.com/Effect-TS/effect/commit/67faacd4679242559bee31717c05a5b10b990322), [`b78acdf`](https://github.com/Effect-TS/effect/commit/b78acdf422568f10ae8684fd3f10d52b065f0b56), [`fbb9ce5`](https://github.com/Effect-TS/effect/commit/fbb9ce5e625d1a7d6b7005bda42cbb6cd31476c5), [`722ea48`](https://github.com/Effect-TS/effect/commit/722ea484c9d01364c9242d929c0a564f7831a57c), [`3058fd5`](https://github.com/Effect-TS/effect/commit/3058fd594f5a683034212d71d957017fcc084006), [`62d0575`](https://github.com/Effect-TS/effect/commit/62d057566c241405c23ecf0bf4156186bd2be924), [`99dd6b5`](https://github.com/Effect-TS/effect/commit/99dd6b580434f97c5b40adc919f429e4abc3dfe7), [`7963ce1`](https://github.com/Effect-TS/effect/commit/7963ce1cd95f037fbefea67a29ead49cce4d16cb), [`af14e75`](https://github.com/Effect-TS/effect/commit/af14e752edd65e2b652e960411afafc88975a8d8), [`24e22d2`](https://github.com/Effect-TS/effect/commit/24e22d23a73a2e93ebf6d8edd2246a4a406942c8), [`647d14e`](https://github.com/Effect-TS/effect/commit/647d14e572c8004fa92fba256e00552b42bf34b7), [`1434eec`](https://github.com/Effect-TS/effect/commit/1434eecbd368e00839c24b3950f0b7a69218669a), [`a5278b1`](https://github.com/Effect-TS/effect/commit/a5278b18242011d1b2b08304c7c128151f9a4370), [`6af04a5`](https://github.com/Effect-TS/effect/commit/6af04a50bd019238f6acdb9cbda40439a3c09210), [`cb6c837`](https://github.com/Effect-TS/effect/commit/cb6c8376b2f322d4e7cbfc0973fc3b4f2951ee6e), [`d44cead`](https://github.com/Effect-TS/effect/commit/d44cead7e0e0ce61f0d980906e494f49a07e7899), [`88c7632`](https://github.com/Effect-TS/effect/commit/88c7632c2b59a49fcc40d250865bd8d0dccf31b0), [`abcbb2a`](https://github.com/Effect-TS/effect/commit/abcbb2abe16f1b6c587c15007df14371e1e70e93), [`8f63cce`](https://github.com/Effect-TS/effect/commit/8f63cce636700fde26b140b82e350ef916989d86), [`d56dfcf`](https://github.com/Effect-TS/effect/commit/d56dfcf54c2b9c53c3d098ce4b0ffcc84496c5f7), [`a98cda9`](https://github.com/Effect-TS/effect/commit/a98cda9422e1352f22e81696f759f326ffcfb667), [`6704bb8`](https://github.com/Effect-TS/effect/commit/6704bb84c320547f83cf50e8586ffc4c5e4c3cc5), [`6143de2`](https://github.com/Effect-TS/effect/commit/6143de21ee22038b45a8d4eba86f5aade6238eba), [`936b135`](https://github.com/Effect-TS/effect/commit/936b1358396eb0a1a7c8e0878ba63297e2106812), [`1bbae84`](https://github.com/Effect-TS/effect/commit/1bbae84f88b577a26d04ceb2e76d3143d09c4a20), [`d795ee7`](https://github.com/Effect-TS/effect/commit/d795ee771701ea62bd187ef7c0307d9737f68c1a), [`0a82d88`](https://github.com/Effect-TS/effect/commit/0a82d88b7da73278b6f270118e396d5ed4a64747), [`9215bc5`](https://github.com/Effect-TS/effect/commit/9215bc5da7dd10aa45f07fe44b98f06b6e433d62), [`a1b5df2`](https://github.com/Effect-TS/effect/commit/a1b5df2064d92431cfc6e638af613cc3114313d7), [`92a9ac5`](https://github.com/Effect-TS/effect/commit/92a9ac5ac0aa63d8975b9ba7a094d6a8f59a98f2), [`6bde7f2`](https://github.com/Effect-TS/effect/commit/6bde7f27f3243427203e53fe74472990e5c2a349), [`a712131`](https://github.com/Effect-TS/effect/commit/a7121310dbb60cbd819bbd702f97663098ec7bb8), [`2e6f760`](https://github.com/Effect-TS/effect/commit/2e6f760dcb44e2b984f3311a8af03a1d68a2ec7e), [`aa05804`](https://github.com/Effect-TS/effect/commit/aa0580497e027ed30b756058db0067c3fe07664f), [`badd3bf`](https://github.com/Effect-TS/effect/commit/badd3bf65fac4dd1e66e1f602db43659722dfced), [`02b0265`](https://github.com/Effect-TS/effect/commit/02b02651ede46a5a2dd3ef8081d0ad89648d0cbf), [`3437e21`](https://github.com/Effect-TS/effect/commit/3437e21a56d805781c5e5946a6189795a1dfd411), [`41a550d`](https://github.com/Effect-TS/effect/commit/41a550d1fed31e829929a8f5362b5340303164ac), [`17b5d50`](https://github.com/Effect-TS/effect/commit/17b5d50219ad49533cf9e33d01924a3e16af5eb3), [`96e5e95`](https://github.com/Effect-TS/effect/commit/96e5e9576b0315c747462761a61940ff9fe32dd1), [`e4d589e`](https://github.com/Effect-TS/effect/commit/e4d589e0ea08dc57c4793053b395dc0fcc499f34), [`ae4cf7b`](https://github.com/Effect-TS/effect/commit/ae4cf7b5e2cb5f8c55657e31a61789ad21c38c18), [`6ef5f1a`](https://github.com/Effect-TS/effect/commit/6ef5f1a041f3a40bf03fadd0b1feb275c277c635), [`2235a29`](https://github.com/Effect-TS/effect/commit/2235a29502c3f33cf6468511ad931089013a7916), [`b32f4cb`](https://github.com/Effect-TS/effect/commit/b32f4cb7b2d8ebe817075322622498e3beb05336), [`7f4c095`](https://github.com/Effect-TS/effect/commit/7f4c095b62da43780dd7fc2a5d1785ddfce60edf), [`5f3fb81`](https://github.com/Effect-TS/effect/commit/5f3fb814d18d8a54946c1c1cd0b41459cdb24006), [`17f0b91`](https://github.com/Effect-TS/effect/commit/17f0b91a243ccfe4a38d27debdc983adf434e738), [`0cdadd7`](https://github.com/Effect-TS/effect/commit/0cdadd75bc8abbbcad7956a4bc71f4e7a9b13250), [`39b57d7`](https://github.com/Effect-TS/effect/commit/39b57d7857358040558b67dd33eafc7bb5457830), [`5a6a573`](https://github.com/Effect-TS/effect/commit/5a6a5738e5bfc39e3a37ae7ba99081601fa19ac3), [`59f5e99`](https://github.com/Effect-TS/effect/commit/59f5e9981913b92d7a9beb2214a21d658b999d3a), [`45379d6`](https://github.com/Effect-TS/effect/commit/45379d6179ee4df2cbd3f848bd39ff7149c24a38), [`1949439`](https://github.com/Effect-TS/effect/commit/1949439175809ef81ab9c6411ed5559109edb4c9), [`e443403`](https://github.com/Effect-TS/effect/commit/e443403cf0e4effea14bb6cd950c5ac1c86cc748), [`03af7e8`](https://github.com/Effect-TS/effect/commit/03af7e85551204c605ea2fa2c43c10a4538ac8fb), [`0f721d4`](https://github.com/Effect-TS/effect/commit/0f721d406df8703ea92ca28777b3f09599e2056d), [`130b28d`](https://github.com/Effect-TS/effect/commit/130b28df552d7053407b041a96ff09dae82575e5), [`c987a12`](https://github.com/Effect-TS/effect/commit/c987a12a01b6a52ad53d29edf02613b03574dbcc), [`4158562`](https://github.com/Effect-TS/effect/commit/41585620977de9b84171f76619b72e29cc2284e5), [`306014a`](https://github.com/Effect-TS/effect/commit/306014a1ce4d5cb956c76bdc20e4e28ab3e61a6a), [`729a663`](https://github.com/Effect-TS/effect/commit/729a663275dd31f2357c446fe69664429220a83d), [`caf84b6`](https://github.com/Effect-TS/effect/commit/caf84b660044089e8d7f4067b279b27b8b50e8fd), [`ce067f7`](https://github.com/Effect-TS/effect/commit/ce067f799ea27735d4194345298a216aaf429f01), [`7a41f5a`](https://github.com/Effect-TS/effect/commit/7a41f5aa72d540ecf2746992ecc3fa3e6b40d31f), [`781022a`](https://github.com/Effect-TS/effect/commit/781022acdd3537ca18c88e2fa3681bafa6ef1b21), [`39f1297`](https://github.com/Effect-TS/effect/commit/39f1297acc08864feb12de6b8cf2bf73434f6cf5), [`2db266b`](https://github.com/Effect-TS/effect/commit/2db266b1bfbc81868bc1778c37c76032a267c79f), [`2141e28`](https://github.com/Effect-TS/effect/commit/2141e28903754d72604acf81673ceb2c62a56646), [`3c5e429`](https://github.com/Effect-TS/effect/commit/3c5e429878669ffcf5e0da4ddfbf50bde5bbcaad), [`20ddc63`](https://github.com/Effect-TS/effect/commit/20ddc630584f8fe488162ba384adcae53fc6810a), [`841b3ea`](https://github.com/Effect-TS/effect/commit/841b3ea6ae19a784bc1c20497b02f632af0c91e9), [`82a3fbf`](https://github.com/Effect-TS/effect/commit/82a3fbfce8b9df33e587076b7d7168ecd6799e17), [`eb9ee83`](https://github.com/Effect-TS/effect/commit/eb9ee83b38844a71d1cd5653a229309cfcb04a36), [`64dc7c7`](https://github.com/Effect-TS/effect/commit/64dc7c76dc5c89887b9e7c181d1873dcbb7820d1), [`84dc8ab`](https://github.com/Effect-TS/effect/commit/84dc8ab7accc682bc668c78a97e4a1776b633be8), [`b4463f4`](https://github.com/Effect-TS/effect/commit/b4463f46fc33d3b01ea5eadd7d012a5abda347a3), [`592dd36`](https://github.com/Effect-TS/effect/commit/592dd361645739ac0cd8e6babb084cd27403c172), [`85d2b44`](https://github.com/Effect-TS/effect/commit/85d2b446e3059de4919be730105868f79728308d), [`32e4a69`](https://github.com/Effect-TS/effect/commit/32e4a69b3151b7ec4058af2213b96a41d11e9e06), [`13c5872`](https://github.com/Effect-TS/effect/commit/13c5872ed30830360367ad89af2dab68a003c351), [`3454cdb`](https://github.com/Effect-TS/effect/commit/3454cdb528fdb5d3ed0c5c5c8169bc47de41fbd8), [`e930804`](https://github.com/Effect-TS/effect/commit/e9308045be1d8a00c0b4046f1e8ff22cf68c93da), [`7f12d4b`](https://github.com/Effect-TS/effect/commit/7f12d4b4e731dc3a213ae5c3f60db9edc50292d2), [`181c9ef`](https://github.com/Effect-TS/effect/commit/181c9ef5e5d4ab247bf4aec06424f15b0a1e802e), [`dd9f891`](https://github.com/Effect-TS/effect/commit/dd9f891e23f316abb6192893008f0e33ece9d97d), [`433fb81`](https://github.com/Effect-TS/effect/commit/433fb81ca4c15c681a8ae097ce3ff9bd3a9c9aa5), [`8459cdb`](https://github.com/Effect-TS/effect/commit/8459cdbae8a476dc04b6247fffe6a1668dcb1217), [`6124ab3`](https://github.com/Effect-TS/effect/commit/6124ab39eb64688fbd5d688d24766542f9cb5a2c), [`01bd954`](https://github.com/Effect-TS/effect/commit/01bd9546f142706fca1628f7261e6d1cb9638948), [`ba2c3aa`](https://github.com/Effect-TS/effect/commit/ba2c3aa05eb87ec05d263b960017ecf29746f66a), [`0a45ef3`](https://github.com/Effect-TS/effect/commit/0a45ef3bb4a1ae9b345c43c548db4336a31b3191), [`8459cdb`](https://github.com/Effect-TS/effect/commit/8459cdbae8a476dc04b6247fffe6a1668dcb1217), [`eaa7e71`](https://github.com/Effect-TS/effect/commit/eaa7e71b88bf59b24610128c6115a2a126432731), [`db4c2cc`](https://github.com/Effect-TS/effect/commit/db4c2ccdec77d813b6f4cc72a302ee7c4fe6e39d), [`22f150a`](https://github.com/Effect-TS/effect/commit/22f150a0936cef30517e87eaca73bff1c5e4873a), [`90ffb08`](https://github.com/Effect-TS/effect/commit/90ffb083b3091c211300f50a42ba7bf56536c0ee), [`d517692`](https://github.com/Effect-TS/effect/commit/d517692ef75f45d5f6d9d68b32d41fa0ccc56c99), [`01af079`](https://github.com/Effect-TS/effect/commit/01af079c189d1fc5067d3b1933b2870c4baf2693), [`32a59e8`](https://github.com/Effect-TS/effect/commit/32a59e8058b1ec9738cb083cf1cb116b393ca114)]: + - effect@4.0.0-beta.104 + +## 4.0.0-beta.103 + +### Patch Changes + +- [#6701](https://github.com/Effect-TS/effect/pull/6701) [`9867b9f`](https://github.com/Effect-TS/effect/commit/9867b9fc69f9cc6c443594fc7eccc7be0c674d9c) Thanks @fubhy! - Removed explicit ./index entrypoints + +- Updated dependencies [[`e56cd8f`](https://github.com/Effect-TS/effect/commit/e56cd8f90c3559baccf8fcf2852ea911235d5944), [`f77c120`](https://github.com/Effect-TS/effect/commit/f77c120d8e04779ddeb8bce8e9cde932f268e4b6), [`b2f95a9`](https://github.com/Effect-TS/effect/commit/b2f95a9c2f2581deb89dc3bae9e89cf819e82923), [`04fd44a`](https://github.com/Effect-TS/effect/commit/04fd44a42abfa8dc2642300dcf49ee48c8ef4539), [`b74333d`](https://github.com/Effect-TS/effect/commit/b74333d83e15b9d042e4698ad23040de60454afe), [`1c40b28`](https://github.com/Effect-TS/effect/commit/1c40b2809503d6aa1358777196fc66317906e657), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`b3901d2`](https://github.com/Effect-TS/effect/commit/b3901d29c543fd5bd05ceec669a17896c8e19006), [`4a0984a`](https://github.com/Effect-TS/effect/commit/4a0984af62738fedf4bd3e87adb4d4d641ce9147), [`fffd88b`](https://github.com/Effect-TS/effect/commit/fffd88b3135abdf928ca7c4b0e00e610985091c7), [`f3f6c1e`](https://github.com/Effect-TS/effect/commit/f3f6c1e02cb543423fcffef5dc2db03fac503588), [`ef07642`](https://github.com/Effect-TS/effect/commit/ef07642dfe671d5258b65d1c1480c4d05c495f15), [`f1bc827`](https://github.com/Effect-TS/effect/commit/f1bc8274a608813d7b09d28dcca04adbf62f8c92), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`081f4d8`](https://github.com/Effect-TS/effect/commit/081f4d8cd06a2ac222d2810b46e61efcee26939e), [`5287b24`](https://github.com/Effect-TS/effect/commit/5287b24f5f8fa094ba20e117bfb1a80fba6d2cf5), [`13d31cf`](https://github.com/Effect-TS/effect/commit/13d31cfc2dde46210e94391b5b6767ae9aeaf2c9), [`acee269`](https://github.com/Effect-TS/effect/commit/acee26944bc89ee554d7b9fadab7443f9edc28a9), [`31170c1`](https://github.com/Effect-TS/effect/commit/31170c19b236c37abb5476c821bc6f5bfa2735ab), [`205ebc7`](https://github.com/Effect-TS/effect/commit/205ebc776062012581e98fced7ced19adfc44ee7), [`ed0ebf8`](https://github.com/Effect-TS/effect/commit/ed0ebf8e5c864d46fed1f232e99c0e680f10a58f), [`a3fd084`](https://github.com/Effect-TS/effect/commit/a3fd08482157bd78b089f77c7b173d54ef68b5cd), [`ee29ddf`](https://github.com/Effect-TS/effect/commit/ee29ddf862c3723ad466abc93ab6f6fe723b2319), [`6086309`](https://github.com/Effect-TS/effect/commit/60863090af8e5af0bfa1435f08dc5390f9993e30), [`4a57af2`](https://github.com/Effect-TS/effect/commit/4a57af24011db1d66e947289d2f7ffc2074696d2), [`660875b`](https://github.com/Effect-TS/effect/commit/660875b4325e6eebb3f04513998301cd2a0847ec), [`8e7c706`](https://github.com/Effect-TS/effect/commit/8e7c706b0aca855489b53d987404566d3e9cb5e7), [`5f63adb`](https://github.com/Effect-TS/effect/commit/5f63adbe75fc9d50d23706a52b3e483ad2a1a01c), [`053bc42`](https://github.com/Effect-TS/effect/commit/053bc42e2a964755611a216e78ed214322efee37), [`c0a1534`](https://github.com/Effect-TS/effect/commit/c0a153494484ecf9f0d0f20895a7a648b4be363b), [`f1e3a37`](https://github.com/Effect-TS/effect/commit/f1e3a378c144f974a6122b299f421b75595af20f), [`cedb01a`](https://github.com/Effect-TS/effect/commit/cedb01a025492a1faf9e59eb23eb96bc3b5e2fff), [`1747440`](https://github.com/Effect-TS/effect/commit/1747440de9a51a56ed3660da748cc01b256adce7), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`b4f1ee2`](https://github.com/Effect-TS/effect/commit/b4f1ee238d96aa78c5f040158cb78671d75b381e), [`a4757f1`](https://github.com/Effect-TS/effect/commit/a4757f1c47067d8d016a6c4a2c541bb8ae520f9b), [`cd122b9`](https://github.com/Effect-TS/effect/commit/cd122b90300d995a237993a2edb7a049785ab6a4), [`5de588b`](https://github.com/Effect-TS/effect/commit/5de588b2472fb0f4eb919766eb8472583a044772), [`3895b9c`](https://github.com/Effect-TS/effect/commit/3895b9cf179262cd277a9c6daafe9050dcf8265e), [`89ce5f3`](https://github.com/Effect-TS/effect/commit/89ce5f3e16e23a193daa475dc72ea8133ae1dacd), [`985de09`](https://github.com/Effect-TS/effect/commit/985de097d75906db2aed784841f81e23cc978b43), [`9800e3a`](https://github.com/Effect-TS/effect/commit/9800e3acc8f36530f671bc8b91558cb112f449a7), [`4dc35f6`](https://github.com/Effect-TS/effect/commit/4dc35f64641746366f867ea3dbfedb9cd4685ada), [`e8eb62b`](https://github.com/Effect-TS/effect/commit/e8eb62b3d0ef27e9761cdc2eb93bdec52d6ee204), [`ecd9993`](https://github.com/Effect-TS/effect/commit/ecd99936112cb69efdb02de3a2fd57f47baefdf3), [`5ab9c08`](https://github.com/Effect-TS/effect/commit/5ab9c08463ce049c45f3502676954a7b72c6b024), [`f5cf965`](https://github.com/Effect-TS/effect/commit/f5cf96548afd51f4b3cf1aea11b04d7f8549ce90), [`a94cbed`](https://github.com/Effect-TS/effect/commit/a94cbed84e9e49bea4bff925599c0f19c4e3deab), [`9160ad7`](https://github.com/Effect-TS/effect/commit/9160ad7d146d4376dd12f7510c025e5b2f638a70), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`52494be`](https://github.com/Effect-TS/effect/commit/52494be9e8eb3bb542d06a3dfefc6bca4e168984), [`5441c8e`](https://github.com/Effect-TS/effect/commit/5441c8e656a6418c0d27feb2df67565a3e1155f4), [`c9b56ab`](https://github.com/Effect-TS/effect/commit/c9b56ab507f224426ee8388dc450da447ec4715f), [`8ef7257`](https://github.com/Effect-TS/effect/commit/8ef72577d1f43212cab87951d659e54e3c8d7d91), [`1519406`](https://github.com/Effect-TS/effect/commit/1519406fed6e8b017ae178dc20bcaa2cf318b570), [`9716990`](https://github.com/Effect-TS/effect/commit/97169902eec3c99baa7f0b2c7b45a0a5eae75819), [`733f75b`](https://github.com/Effect-TS/effect/commit/733f75b7125e3016a975fdd251c0179ae5393786), [`48155c8`](https://github.com/Effect-TS/effect/commit/48155c8ccfc12dcca8a00fa358d50b20c30874e4), [`951d06b`](https://github.com/Effect-TS/effect/commit/951d06b83d459d3e8fa9024e727a5db1662d3322), [`d767b65`](https://github.com/Effect-TS/effect/commit/d767b65a7687e38be23f0b0ee3d52ab5f2360cbe), [`5d52d9d`](https://github.com/Effect-TS/effect/commit/5d52d9d148aaa7f736ed8c310fc8bfa9dc81badf), [`f4151e1`](https://github.com/Effect-TS/effect/commit/f4151e1937c26de14f1d64566f8126173f1b5014), [`e02fbb6`](https://github.com/Effect-TS/effect/commit/e02fbb66f5a0f13dba6c33ef63528a37a17a0676), [`724ce09`](https://github.com/Effect-TS/effect/commit/724ce09650a458d4565e5c7331ea92ca04f08e68), [`dbe91f6`](https://github.com/Effect-TS/effect/commit/dbe91f6961ef9f7e8da910ee5758d9c0d385fca8), [`4c008d2`](https://github.com/Effect-TS/effect/commit/4c008d28b370d817f7ae4579db09836fe084c8d2), [`b650832`](https://github.com/Effect-TS/effect/commit/b6508328708a842f3163467b72486bd228f1a289), [`b46c92f`](https://github.com/Effect-TS/effect/commit/b46c92f3b314f4ffd612b831efa55dd856c587a3), [`5335797`](https://github.com/Effect-TS/effect/commit/5335797003076d9c6fd170da98d779696d555596), [`4b3460d`](https://github.com/Effect-TS/effect/commit/4b3460daa434ec465a95a50704fe1103a9275999), [`6301fd7`](https://github.com/Effect-TS/effect/commit/6301fd710b4325718de2c42997dac28a9e9aa250), [`aebc5c6`](https://github.com/Effect-TS/effect/commit/aebc5c61664b89a840465ec65b79ce635a5ceee8), [`52b2d7b`](https://github.com/Effect-TS/effect/commit/52b2d7b5bd3c7cce3bd5b69c6ab3941004da70f3), [`eec5744`](https://github.com/Effect-TS/effect/commit/eec57445dfa0ef3c5977195ad69415b7e7d42bb6), [`24e0e93`](https://github.com/Effect-TS/effect/commit/24e0e93dc307dc2c2ae86caacb7289e1dab3c103), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`1a7ce81`](https://github.com/Effect-TS/effect/commit/1a7ce8150e3977586c44d8ccb9a8384389bb4d49), [`48f22a7`](https://github.com/Effect-TS/effect/commit/48f22a7d16ae57ee2175d450dafbdeb69e187d2a), [`c96b7f6`](https://github.com/Effect-TS/effect/commit/c96b7f6359662053c3e09344f61dddc7a6caf4ac), [`6d2a942`](https://github.com/Effect-TS/effect/commit/6d2a942ed7cd33b8fd79d549edba33bc9e2a7e3e), [`cc27b19`](https://github.com/Effect-TS/effect/commit/cc27b194b9d13fa3a66ab037e853fca9d41700ff), [`8f9499f`](https://github.com/Effect-TS/effect/commit/8f9499f562729f5f7b08d8bcc4db86b4aeff8a21), [`3eeea73`](https://github.com/Effect-TS/effect/commit/3eeea73cfc3e9b126975c2ddbdb7f7c8c92026e2), [`0a532e5`](https://github.com/Effect-TS/effect/commit/0a532e503f165fdea485a5343fc2f420917e8376), [`f398149`](https://github.com/Effect-TS/effect/commit/f398149c134fd9b67b6cdc52eae3f3248d5c7bbe), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`ace903e`](https://github.com/Effect-TS/effect/commit/ace903e09c2549ceebdec380797beb027cd29f3d), [`e8eb62b`](https://github.com/Effect-TS/effect/commit/e8eb62b3d0ef27e9761cdc2eb93bdec52d6ee204), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`48f22a7`](https://github.com/Effect-TS/effect/commit/48f22a7d16ae57ee2175d450dafbdeb69e187d2a), [`d48506d`](https://github.com/Effect-TS/effect/commit/d48506d97525040aa714305e928126df799795b4), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`d48506d`](https://github.com/Effect-TS/effect/commit/d48506d97525040aa714305e928126df799795b4), [`d48506d`](https://github.com/Effect-TS/effect/commit/d48506d97525040aa714305e928126df799795b4), [`52262be`](https://github.com/Effect-TS/effect/commit/52262be2edce0e350c6ac10f8f725678606399c5), [`1284aa1`](https://github.com/Effect-TS/effect/commit/1284aa183451955ad7921bbe01fd0e095695d444), [`9867b9f`](https://github.com/Effect-TS/effect/commit/9867b9fc69f9cc6c443594fc7eccc7be0c674d9c), [`d0f1a22`](https://github.com/Effect-TS/effect/commit/d0f1a2295155c350b04efb46852cb40032805273), [`979ce39`](https://github.com/Effect-TS/effect/commit/979ce3985d7d62ce2bf240681ca19feda3027452), [`b6d3e67`](https://github.com/Effect-TS/effect/commit/b6d3e67c7cc143cd8470cdf704324e79d23954a9), [`adf6c6c`](https://github.com/Effect-TS/effect/commit/adf6c6cd388af8a3c0c546492e71555368556f6a), [`7314d60`](https://github.com/Effect-TS/effect/commit/7314d605284717aaafe7fc34b88c3c93397e865c), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`1acbd8b`](https://github.com/Effect-TS/effect/commit/1acbd8b44c68ebb23735e9810476b870dbe58aea), [`7bde6cc`](https://github.com/Effect-TS/effect/commit/7bde6ccb2b144fe953ff30a7ef5e1ecc97697146), [`a959a8b`](https://github.com/Effect-TS/effect/commit/a959a8bf21cdb976369f494dc949fa00a050d3e0)]: + - effect@4.0.0-beta.103 + +## 4.0.0-beta.102 + +### Patch Changes + +- Updated dependencies [[`b6392e1`](https://github.com/Effect-TS/effect/commit/b6392e119704553edec1b4fd2869ac0dbec621ef), [`7ed9450`](https://github.com/Effect-TS/effect/commit/7ed945044eb56aa9aeaf62d4746a011c96c58628), [`45762bd`](https://github.com/Effect-TS/effect/commit/45762bd78df9ecd87c98b8d3738cdeeac7d81128), [`a6e8391`](https://github.com/Effect-TS/effect/commit/a6e8391cd31acd898fae18b3f8e7ca4c6f14f065), [`4ac7e8b`](https://github.com/Effect-TS/effect/commit/4ac7e8b136c61a26c3e438c013dfd7349b38e999), [`4cd40f5`](https://github.com/Effect-TS/effect/commit/4cd40f5692477783bef84fed3c5ef1c0cf5602e6), [`6956bc0`](https://github.com/Effect-TS/effect/commit/6956bc0e6cb27f53fbec39d9b18545940f9f598f), [`0e50ec7`](https://github.com/Effect-TS/effect/commit/0e50ec7dbb94390666f292cf9120719bf30a7246), [`9fcdade`](https://github.com/Effect-TS/effect/commit/9fcdade4a8af772b9ccd8b8a24fe8cee0e5d8470), [`57367d5`](https://github.com/Effect-TS/effect/commit/57367d54de55047ff0c5fce9685475e236bf354c), [`35c445f`](https://github.com/Effect-TS/effect/commit/35c445ff18029d192900ea0914c993f58d5cf1a5), [`c917bb9`](https://github.com/Effect-TS/effect/commit/c917bb94a4c1c4e0a24372a8ebb8a5ca232e36b5), [`bc1f358`](https://github.com/Effect-TS/effect/commit/bc1f3583e63344cb2c398d9040d9c975488ed123), [`0e0c9d7`](https://github.com/Effect-TS/effect/commit/0e0c9d7922ff463c1093d9e0576fae12cb0698d5), [`73d40aa`](https://github.com/Effect-TS/effect/commit/73d40aacd8fcae1b48c23f5b0a5c542127401d1d), [`4f1e318`](https://github.com/Effect-TS/effect/commit/4f1e3183f7123591c46224e9c587df7594562a5f), [`9d8d85c`](https://github.com/Effect-TS/effect/commit/9d8d85c1bb7da51970845b8ea830e386e777514a), [`6079fda`](https://github.com/Effect-TS/effect/commit/6079fda7b02f2f01ad91c15ab8c307336f3ba252), [`5101e92`](https://github.com/Effect-TS/effect/commit/5101e92c9c149c153423f43dd7a94f6194653c06), [`d0b3265`](https://github.com/Effect-TS/effect/commit/d0b3265c3262670761471ab3518cf933b1b3b20a), [`7a03c89`](https://github.com/Effect-TS/effect/commit/7a03c893ce6492bf94c0ebfb00b63bf25dcbf83e), [`cea1d9c`](https://github.com/Effect-TS/effect/commit/cea1d9c92601e69ebda040af8a1d860d604d885c), [`078e1f5`](https://github.com/Effect-TS/effect/commit/078e1f5636e31b76a86722a636afc37a8cc25580), [`97bafea`](https://github.com/Effect-TS/effect/commit/97bafeab460833b9781527b437d1cb9cbee63260), [`fab0ab8`](https://github.com/Effect-TS/effect/commit/fab0ab8f7ab15ae596faa4ccf75615a494d11b0b), [`c323d8b`](https://github.com/Effect-TS/effect/commit/c323d8b30dbbe85f9df25b67288b93d5332de333), [`6966353`](https://github.com/Effect-TS/effect/commit/69663534d626003eb10a5e55ab1f13e0379fead1), [`0444004`](https://github.com/Effect-TS/effect/commit/04440041989c1785fe4db286379f2be2c15baa85), [`028bbb3`](https://github.com/Effect-TS/effect/commit/028bbb391e161185da10d974ab33381f769940d7), [`ff5d6e2`](https://github.com/Effect-TS/effect/commit/ff5d6e278a1fdff714315dc1a17075012f05c1f0), [`1bfce93`](https://github.com/Effect-TS/effect/commit/1bfce93e6d2bf0794c11733daf51c2390e7de375), [`7ce815c`](https://github.com/Effect-TS/effect/commit/7ce815cd5af6af991dfc13b890fd22345fc77c20), [`7271a7f`](https://github.com/Effect-TS/effect/commit/7271a7faf1080aa75f2f53ca6a0b5ec9334c1d38), [`475fe5c`](https://github.com/Effect-TS/effect/commit/475fe5c12c2d6504c475797c0634f90da01e1797)]: + - effect@4.0.0-beta.102 + +## 4.0.0-beta.101 + +### Patch Changes + +- Updated dependencies [[`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`b35ed29`](https://github.com/Effect-TS/effect/commit/b35ed2904f01536d303b21f288daf343cf740462), [`dd44624`](https://github.com/Effect-TS/effect/commit/dd446245736a0e88c807a02f03c21450bb9340fa), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`2bae1ac`](https://github.com/Effect-TS/effect/commit/2bae1accce9d3b72cf6d5aefc9b2161af6d88436)]: + - effect@4.0.0-beta.101 + +## 4.0.0-beta.100 + +### Patch Changes + +- Updated dependencies [[`c1288dd`](https://github.com/Effect-TS/effect/commit/c1288dd1a52a2811ab7df57fc4ce236c6be4c745), [`2b58a3d`](https://github.com/Effect-TS/effect/commit/2b58a3dab6bc99776dddaf76e27d811e0f47f3d8), [`6dc83f2`](https://github.com/Effect-TS/effect/commit/6dc83f26ddf20d48db28cf761dd8f3716e5273fb), [`c1e2fe0`](https://github.com/Effect-TS/effect/commit/c1e2fe0cf93564f4d919e3998874c3e70b0cf30f), [`f3fbae8`](https://github.com/Effect-TS/effect/commit/f3fbae8d7bae0d77cb4f35a1598b26c58e3bf94d), [`e000f80`](https://github.com/Effect-TS/effect/commit/e000f80fd55bcd8edc699fdbf4cd109004f4f754), [`f4ee765`](https://github.com/Effect-TS/effect/commit/f4ee7655ee052cf9ba726fd602bb87c89c7c62a9), [`510b55f`](https://github.com/Effect-TS/effect/commit/510b55f3e21750685dbfd5f476a130c1c5af9dbd), [`31d3fc4`](https://github.com/Effect-TS/effect/commit/31d3fc4327c50867bb8d881fa7353aeb03ea2826), [`875e618`](https://github.com/Effect-TS/effect/commit/875e618c3764a7b817ac863d0af86924449528f2), [`688d46a`](https://github.com/Effect-TS/effect/commit/688d46afd0ef923d983ad3d7385f52f217b28d70), [`6ff5023`](https://github.com/Effect-TS/effect/commit/6ff502363b9840a5a5ee0a24bc6cae734ac3a3eb), [`c0333e7`](https://github.com/Effect-TS/effect/commit/c0333e7f755f42ddcca7051e029da8b4eed527bf), [`06e7e8c`](https://github.com/Effect-TS/effect/commit/06e7e8c66015ee318f871b9d2218dee82df2b108), [`eb9b102`](https://github.com/Effect-TS/effect/commit/eb9b10256c8558881b441c2fef833b7037174400), [`8b155da`](https://github.com/Effect-TS/effect/commit/8b155da06e0740c354ec562957a45ab65eb4573b), [`3a87335`](https://github.com/Effect-TS/effect/commit/3a8733564c5db35271aa20564ed0d344daa2a79f)]: + - effect@4.0.0-beta.100 + +## 4.0.0-beta.99 + +### Patch Changes + +- Updated dependencies [[`8ce4795`](https://github.com/Effect-TS/effect/commit/8ce4795ccbaebca4292757db568c005a992546a4), [`80b539f`](https://github.com/Effect-TS/effect/commit/80b539f8aba68f478c75c35c2b4140c4ffc4fada), [`88a54cc`](https://github.com/Effect-TS/effect/commit/88a54cc341006e3ebcb13482c618f62a680ce199), [`e6e6dba`](https://github.com/Effect-TS/effect/commit/e6e6dba6e9d86e7c2ad27dcedf289db76a19697f), [`bfb203e`](https://github.com/Effect-TS/effect/commit/bfb203e95aa439f731acad37fc3a9a831a190f1c), [`2e9a34a`](https://github.com/Effect-TS/effect/commit/2e9a34ac2bece4f3a206160480c991e3841dc67a), [`55d4eb3`](https://github.com/Effect-TS/effect/commit/55d4eb34f2c64d54f6a25a305b5c5438ebd7934e), [`bddb010`](https://github.com/Effect-TS/effect/commit/bddb010eac3d4436cb094edbbee7460c5440c162), [`a328835`](https://github.com/Effect-TS/effect/commit/a328835e50d76bc96648a1c1550456e8c9f81210), [`5560d05`](https://github.com/Effect-TS/effect/commit/5560d05aa6abdd29466d9c3412cc5e648b0adbde), [`8f6e3ad`](https://github.com/Effect-TS/effect/commit/8f6e3adb185b16e8820b98c509b308086f7ff1af), [`46997fa`](https://github.com/Effect-TS/effect/commit/46997fa60401f5e3c93daa4b61f7df8e31caaab4), [`9e6e12d`](https://github.com/Effect-TS/effect/commit/9e6e12d75c118cd265496f2880490d1f33a5c8bf), [`3394b93`](https://github.com/Effect-TS/effect/commit/3394b93d97d6f24fc38670641d1490289ffca7f1), [`febeabc`](https://github.com/Effect-TS/effect/commit/febeabc3f7c31094da000a23edeaabfe2ab00a38), [`54161c9`](https://github.com/Effect-TS/effect/commit/54161c98f6f3569e0c31842f54e6a257f9421c4c), [`385f7a4`](https://github.com/Effect-TS/effect/commit/385f7a4ee4a7359928597ea56d151dbaf5eb5802), [`7eea4d0`](https://github.com/Effect-TS/effect/commit/7eea4d0b73ec554915d7066a71f46326ce2ba45f), [`7543afe`](https://github.com/Effect-TS/effect/commit/7543afea6f4d97d1f1ad876224323838a48daadd), [`44b9cf3`](https://github.com/Effect-TS/effect/commit/44b9cf3d240d726997b4bbcd0ede48e074d3c456), [`7eea4d0`](https://github.com/Effect-TS/effect/commit/7eea4d0b73ec554915d7066a71f46326ce2ba45f), [`0a8aa6a`](https://github.com/Effect-TS/effect/commit/0a8aa6acb90a72b91c24d17133c950e4cacd8abd), [`c8d9fcf`](https://github.com/Effect-TS/effect/commit/c8d9fcf7b030f7c474effbab2764ce7aee1c7209), [`9ca7f9a`](https://github.com/Effect-TS/effect/commit/9ca7f9a69363e4485645966d5a93b8f9597c5206), [`e7aca89`](https://github.com/Effect-TS/effect/commit/e7aca894bb32fbb785b5830837e6061c415a6015), [`55d7560`](https://github.com/Effect-TS/effect/commit/55d75609b8acf8a1b54c1b1c7fbbb65ec741aa3e), [`f809189`](https://github.com/Effect-TS/effect/commit/f809189ddf6b6011ba43a9901baaa734e315da2a), [`88a54cc`](https://github.com/Effect-TS/effect/commit/88a54cc341006e3ebcb13482c618f62a680ce199), [`0ebdbe7`](https://github.com/Effect-TS/effect/commit/0ebdbe74463dc84385956d0b1e8c2b79ebab5400), [`7517d09`](https://github.com/Effect-TS/effect/commit/7517d09f12a0b183a81bd425962c4e280a68b05d), [`212493b`](https://github.com/Effect-TS/effect/commit/212493b9a1eb98cd1ef6959c707a2e5784a5ae91), [`88a54cc`](https://github.com/Effect-TS/effect/commit/88a54cc341006e3ebcb13482c618f62a680ce199), [`80ea8cb`](https://github.com/Effect-TS/effect/commit/80ea8cb9222ca73f564c8267ab2f82966fea027a), [`8df19f4`](https://github.com/Effect-TS/effect/commit/8df19f4fe81d90cc33ace88b9a77e5534f82d604)]: + - effect@4.0.0-beta.99 + +## 4.0.0-beta.98 + +### Patch Changes + +- Updated dependencies [[`989603b`](https://github.com/Effect-TS/effect-smol/commit/989603b60ab1197b64acf214208e0d370cd1f842), [`214c458`](https://github.com/Effect-TS/effect-smol/commit/214c458084bb6995d543cd37d1055f24be3d454e), [`a037273`](https://github.com/Effect-TS/effect-smol/commit/a0372736ac34796969b051bbba4717d7983f1ebe), [`97fdaa9`](https://github.com/Effect-TS/effect-smol/commit/97fdaa9c1f522c65e579365d314a07878e2b904f), [`b24d248`](https://github.com/Effect-TS/effect-smol/commit/b24d248c8df44222ce642087cde2bd859a2dc709), [`19c222c`](https://github.com/Effect-TS/effect-smol/commit/19c222cac2353a3d7b7733caecb00556fffe9a5c), [`eec85dd`](https://github.com/Effect-TS/effect-smol/commit/eec85ddba09ea326fd268ee33eeffd47e50d4671), [`0082f4f`](https://github.com/Effect-TS/effect-smol/commit/0082f4f74fb139fd578f87f0a790e845133983dc), [`8849052`](https://github.com/Effect-TS/effect-smol/commit/884905232d1e9a365e046d8dde27bf9c5707f57f), [`c15e16a`](https://github.com/Effect-TS/effect-smol/commit/c15e16ad130d1fbde25d912b7ac55995066cb35b), [`01d00a3`](https://github.com/Effect-TS/effect-smol/commit/01d00a3abfbf1f37996cdbe738ea5137c646cdd7), [`8bd4589`](https://github.com/Effect-TS/effect-smol/commit/8bd458975a1b3a8ed042eccf317b93d28ded91e7), [`0082f4f`](https://github.com/Effect-TS/effect-smol/commit/0082f4f74fb139fd578f87f0a790e845133983dc), [`6e08428`](https://github.com/Effect-TS/effect-smol/commit/6e08428d980501b856f846ad3f3f0e4ea46e7786), [`388dcf9`](https://github.com/Effect-TS/effect-smol/commit/388dcf953f65d317547f34d40e6443c5f264205f), [`2b7ce2b`](https://github.com/Effect-TS/effect-smol/commit/2b7ce2b513e7ec2a77822f1116dc6ffb6ba93f4e), [`87bea7e`](https://github.com/Effect-TS/effect-smol/commit/87bea7e16259246f3bcdf565446394751abca953), [`ce38dc3`](https://github.com/Effect-TS/effect-smol/commit/ce38dc33bda805a684432cca071f4dc3c6b9a1ba), [`a807cd1`](https://github.com/Effect-TS/effect-smol/commit/a807cd170341deca8a1cfb52c4222585f2431bb9), [`fd8a356`](https://github.com/Effect-TS/effect-smol/commit/fd8a356f06a8c9ce4e7e0a13fc4021c178ed31de), [`c2a5edc`](https://github.com/Effect-TS/effect-smol/commit/c2a5edc3abd31ad5bc123362bc1213e03e4095c3), [`5946da3`](https://github.com/Effect-TS/effect-smol/commit/5946da3804a1be5e752b05b96bd058cdba50a1bf), [`4ae0c5f`](https://github.com/Effect-TS/effect-smol/commit/4ae0c5ffcbe6c56ddfcb05c639112a079483539e), [`5b2a0bc`](https://github.com/Effect-TS/effect-smol/commit/5b2a0bceea3a28a33a58555210c90a415dc74a76), [`72ac585`](https://github.com/Effect-TS/effect-smol/commit/72ac585884befde6af9208da738699a93f1bae79), [`5e8c1b8`](https://github.com/Effect-TS/effect-smol/commit/5e8c1b82bfafa121311f987a49ab75395e3647a7), [`0f9c078`](https://github.com/Effect-TS/effect-smol/commit/0f9c07841b04183f485ee6e6458de73b290b09f5)]: + - effect@4.0.0-beta.98 + +## 4.0.0-beta.97 + +### Patch Changes + +- Updated dependencies []: + - effect@4.0.0-beta.97 + +## 4.0.0-beta.96 + +### Patch Changes + +- Updated dependencies [[`1503f45`](https://github.com/Effect-TS/effect-smol/commit/1503f45cb5bb2a74f4705252ec505a1f0ade7e62), [`57fe793`](https://github.com/Effect-TS/effect-smol/commit/57fe79316ffbc380b30626a168981fb26ae97459), [`0c2f78f`](https://github.com/Effect-TS/effect-smol/commit/0c2f78f695ec474e1ff5474da183577975e418f5), [`0c2f78f`](https://github.com/Effect-TS/effect-smol/commit/0c2f78f695ec474e1ff5474da183577975e418f5), [`0c2f78f`](https://github.com/Effect-TS/effect-smol/commit/0c2f78f695ec474e1ff5474da183577975e418f5), [`97f29df`](https://github.com/Effect-TS/effect-smol/commit/97f29df457f7ffd07cfb4b379315c12c086af805)]: + - effect@4.0.0-beta.96 + +## 4.0.0-beta.95 + +### Patch Changes + +- Updated dependencies [[`a482442`](https://github.com/Effect-TS/effect-smol/commit/a482442abdeb490e9652b854ec3495e4aa7273e7), [`fbefa85`](https://github.com/Effect-TS/effect-smol/commit/fbefa850fab2f0a302c20614496aeaaa2a8b5590), [`0b4a32f`](https://github.com/Effect-TS/effect-smol/commit/0b4a32f4260f0d8500942a133001b0d349328102), [`18a49e1`](https://github.com/Effect-TS/effect-smol/commit/18a49e1786679456258002ff9397faf02f678c2d), [`266cb90`](https://github.com/Effect-TS/effect-smol/commit/266cb90bb2c17aabc40563c32db334f09ba3d74b), [`912f095`](https://github.com/Effect-TS/effect-smol/commit/912f095a34572bbd3cedf6edb27878443e3e4a95), [`a6718f9`](https://github.com/Effect-TS/effect-smol/commit/a6718f9e00a15ca903b0732da46116cbf3d6aca7), [`bef5154`](https://github.com/Effect-TS/effect-smol/commit/bef51540a243aa2f872a00c01d0cd58b7a769baa), [`18e0564`](https://github.com/Effect-TS/effect-smol/commit/18e0564bd0f8ebbdfcaf1e2c21529948e9e4a81d), [`fb50f14`](https://github.com/Effect-TS/effect-smol/commit/fb50f14fc3657c1973785aa5b72ecf0b0d28e0b2)]: + - effect@4.0.0-beta.95 + +## 4.0.0-beta.94 + +### Patch Changes + +- Updated dependencies [[`95a0e9b`](https://github.com/Effect-TS/effect-smol/commit/95a0e9bb62797af0e81c9998773405f248f218c5), [`a0a3490`](https://github.com/Effect-TS/effect-smol/commit/a0a3490bbce765f199d8e077aceac504f0462e63), [`f11ce73`](https://github.com/Effect-TS/effect-smol/commit/f11ce73af60823754dc24194f4ffc561b9ea1c2d), [`ff30b6e`](https://github.com/Effect-TS/effect-smol/commit/ff30b6e7c2c63ffc56a4c5818d6d86b01b5ad528), [`1caab3c`](https://github.com/Effect-TS/effect-smol/commit/1caab3cc30f626efbf15e59d74f539a487e5c85c), [`aa80c47`](https://github.com/Effect-TS/effect-smol/commit/aa80c4775a04db87553e5568764cab7e32a72814), [`c2ae4fc`](https://github.com/Effect-TS/effect-smol/commit/c2ae4fce2f03a4cd1861c2b1179da7df656e662d), [`a0a3490`](https://github.com/Effect-TS/effect-smol/commit/a0a3490bbce765f199d8e077aceac504f0462e63)]: + - effect@4.0.0-beta.94 + +## 4.0.0-beta.93 + +### Patch Changes + +- Updated dependencies [[`00652fe`](https://github.com/Effect-TS/effect-smol/commit/00652fe95c18f87208e91343eb8bf218faa2f677), [`6c58167`](https://github.com/Effect-TS/effect-smol/commit/6c5816746eaf91d2a3c7c899c5720809fa230ae3), [`2bc5415`](https://github.com/Effect-TS/effect-smol/commit/2bc541501a7ef89e542d7cb98e96beb53cd205cc), [`e11cccc`](https://github.com/Effect-TS/effect-smol/commit/e11cccc7d5fe631abccc7d6e3bd296938de0fa2e), [`ba7e77e`](https://github.com/Effect-TS/effect-smol/commit/ba7e77e046b8641a3a4e9750bb88ca4a1d063d3f), [`5713ee7`](https://github.com/Effect-TS/effect-smol/commit/5713ee7edbc3054efde407b2286bbfd45bbc6e1c)]: + - effect@4.0.0-beta.93 + +## 4.0.0-beta.92 + +### Patch Changes + +- Updated dependencies [[`affdc13`](https://github.com/Effect-TS/effect-smol/commit/affdc139045cc325dce321a84a580fdc1b2da7b9)]: + - effect@4.0.0-beta.92 + +## 4.0.0-beta.91 + +### Patch Changes + +- Updated dependencies [[`b135b25`](https://github.com/Effect-TS/effect-smol/commit/b135b2517fca9e7839734ace3699a7dfa75b9075), [`aaa21a3`](https://github.com/Effect-TS/effect-smol/commit/aaa21a369a171c600db294f2a4f640583043e150), [`3475ee6`](https://github.com/Effect-TS/effect-smol/commit/3475ee6c2bda6b05c6d7a12ce30c8bb840b5b1a6)]: + - effect@4.0.0-beta.91 + ## 4.0.0-beta.90 ### Patch Changes diff --git a/repos/effect/packages/atom/solid/docgen.json b/repos/effect/packages/atom/solid/docgen.json deleted file mode 100644 index ba9091a04b..0000000000 --- a/repos/effect/packages/atom/solid/docgen.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "$schema": "../../../node_modules/@effect/docgen/schema.json", - "projectHomepage": "https://effect-ts.github.io/effect/docs/atom-solid", - "srcLink": "https://github.com/Effect-TS/effect/tree/main/packages/atom/solid/src/", - "enforceVersion": true, - "exclude": ["src/internal/**/*.ts"], - "tscExecutable": "tsgo", - "examplesCompilerOptions": { - "noEmit": true, - "strict": true, - "skipLibCheck": true, - "moduleResolution": "Bundler", - "module": "ES2022", - "target": "ES2022", - "lib": ["ES2022", "DOM", "DOM.Iterable"], - "rewriteRelativeImportExtensions": true, - "allowImportingTsExtensions": true, - "paths": { - "effect": ["../../../effect/src/index.js"], - "effect/*": ["../../../effect/src/*.js"] - }, - "plugins": [ - { "name": "@effect/language-service", "includeSuggestionsInTsc": false } - ] - } -} diff --git a/repos/effect/packages/atom/solid/package.json b/repos/effect/packages/atom/solid/package.json index 3b3f573f14..efc81d4128 100644 --- a/repos/effect/packages/atom/solid/package.json +++ b/repos/effect/packages/atom/solid/package.json @@ -1,17 +1,17 @@ { "name": "@effect/atom-solid", - "version": "4.0.0-beta.90", + "version": "4.0.0-beta.107", "type": "module", "license": "MIT", "description": "SolidJS bindings for the Effect Atom modules", "homepage": "https://effect.website", "repository": { "type": "git", - "url": "https://github.com/Effect-TS/effect-smol.git", + "url": "https://github.com/Effect-TS/effect.git", "directory": "packages/atom/solid" }, "bugs": { - "url": "https://github.com/Effect-TS/effect-smol/issues" + "url": "https://github.com/Effect-TS/effect/issues" }, "tags": [ "typescript", @@ -29,6 +29,7 @@ ".": "./src/index.ts", "./*": "./src/*.ts", "./internal/*": null, + "./index": null, "./*/index": null }, "files": [ @@ -36,7 +37,10 @@ "dist/**/*.js", "dist/**/*.js.map", "dist/**/*.d.ts", - "dist/**/*.d.ts.map" + "dist/**/*.d.ts.map", + "AGENTS.md", + "CLAUDE.md", + "ai-docs/**/*" ], "publishConfig": { "access": "public", @@ -46,27 +50,25 @@ ".": "./dist/index.js", "./*": "./dist/*.js", "./internal/*": null, + "./index": null, "./*/index": null } }, "scripts": { "build": "tsc -b tsconfig.json && pnpm babel", - "build:tsgo": "tsgo -b tsconfig.json && pnpm babel", "babel": "babel dist --plugins annotate-pure-calls --out-dir dist --source-maps", - "check": "tsc -b tsconfig.json", - "test": "vitest", - "coverage": "vitest --coverage" + "check": "tsc -b tsconfig.json" }, "peerDependencies": { "effect": "workspace:^", - "solid-js": ">=1 <2" + "solid-js": ">=1.9.14 <2.0.0" }, "devDependencies": { "@solidjs/testing-library": "^0.8.0", "@testing-library/dom": "^10.4.1", - "@testing-library/jest-dom": "^6.9.1", + "@testing-library/jest-dom": "^7.0.0", "effect": "workspace:^", - "jsdom": "^29.1.1", - "solid-js": "^1.9.12" + "jsdom": "^30.0.0", + "solid-js": "^1.9.14" } } diff --git a/repos/effect/packages/atom/solid/tsconfig.json b/repos/effect/packages/atom/solid/tsconfig.json index 19a2f5dbca..e2a8ca19a0 100644 --- a/repos/effect/packages/atom/solid/tsconfig.json +++ b/repos/effect/packages/atom/solid/tsconfig.json @@ -1,5 +1,5 @@ { - "$schema": "http://json.schemastore.org/tsconfig", + "$schema": "https://json.schemastore.org/tsconfig", "extends": "../../../tsconfig.base.json", "include": ["src"], "references": [ diff --git a/repos/effect/packages/atom/solid/vitest.config.ts b/repos/effect/packages/atom/solid/vitest.config.ts deleted file mode 100644 index 48bb912d71..0000000000 --- a/repos/effect/packages/atom/solid/vitest.config.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { mergeConfig } from "vitest/config" -import shared from "../../../vitest.shared.ts" - -export default mergeConfig(shared, { - resolve: { - conditions: ["browser"] - }, - esbuild: { - target: "es2022" - }, - test: { - environment: "jsdom", - setupFiles: ["./vitest.setup.ts"] - } -}) diff --git a/repos/effect/packages/atom/vue/CHANGELOG.md b/repos/effect/packages/atom/vue/CHANGELOG.md index e5c9aff92a..f3a8c4baa5 100644 --- a/repos/effect/packages/atom/vue/CHANGELOG.md +++ b/repos/effect/packages/atom/vue/CHANGELOG.md @@ -1,5 +1,127 @@ # @effect/atom-vue +## 4.0.0-beta.107 + +### Patch Changes + +- Updated dependencies [[`596f3f9`](https://github.com/Effect-TS/effect/commit/596f3f92d7fe355811b815cb212332b082268ce8), [`9611ed4`](https://github.com/Effect-TS/effect/commit/9611ed42d11300546b339ab13492a0f7bdb1ebfb), [`8b91605`](https://github.com/Effect-TS/effect/commit/8b9160548556e4b0ec7ee2f2707716776be49018), [`d901928`](https://github.com/Effect-TS/effect/commit/d901928efa44f573ed1247f53fdb203a8e4fcede), [`b32bdef`](https://github.com/Effect-TS/effect/commit/b32bdef0d119a1ad1463dc01a46763ffee1f9bd9)]: + - effect@4.0.0-beta.107 + +## 4.0.0-beta.106 + +### Patch Changes + +- Updated dependencies [[`2695168`](https://github.com/Effect-TS/effect/commit/269516851b24916d72771f8a554b88722e3732e7), [`6310a8c`](https://github.com/Effect-TS/effect/commit/6310a8c68c74dcf1d23948ec9243ac5f407a1651), [`c2071b1`](https://github.com/Effect-TS/effect/commit/c2071b1647e2326568c1d0689274ef62b8a7183f), [`7aff81a`](https://github.com/Effect-TS/effect/commit/7aff81a9cefe681483ef8abf717d786fd10e7e8d), [`a1d4057`](https://github.com/Effect-TS/effect/commit/a1d4057711935a544ef441bc2d0ac3565dfa9266), [`abf77b0`](https://github.com/Effect-TS/effect/commit/abf77b04009dcb4d67a258f9d8ada778e9f4ffae), [`6c60375`](https://github.com/Effect-TS/effect/commit/6c60375e68683a32d54554150cc493e16550a06d), [`22f4897`](https://github.com/Effect-TS/effect/commit/22f4897bbae24783d4516f6bef353f1db4ec6d03), [`615d1d5`](https://github.com/Effect-TS/effect/commit/615d1d5d0256ec8160f2e08d0dcf5dc83acb7bf1), [`3a86757`](https://github.com/Effect-TS/effect/commit/3a867573ddeed5888dabdeb3225a9ebbf00491e7), [`f4a9762`](https://github.com/Effect-TS/effect/commit/f4a9762bb9dfad59c215f2e099dcc829d74f4ed1), [`0bcf6ed`](https://github.com/Effect-TS/effect/commit/0bcf6ed57c22e8a36964726b15464101d90f5997), [`ba9cb63`](https://github.com/Effect-TS/effect/commit/ba9cb63b87d45ce2df872dd8ef0905da147cc675), [`42c810d`](https://github.com/Effect-TS/effect/commit/42c810dd372275b822dd99c7d7e774e153f0a752), [`1416ccd`](https://github.com/Effect-TS/effect/commit/1416ccd474bc9da8979f51b72b5e53fb3ac56edf), [`08d0d39`](https://github.com/Effect-TS/effect/commit/08d0d39a225deccb9db213ab5fcf55edb9f9ba5d), [`548908a`](https://github.com/Effect-TS/effect/commit/548908a71d9337cb7defe7fc93b2fba8f6a04b6f), [`4b3460d`](https://github.com/Effect-TS/effect/commit/4b3460daa434ec465a95a50704fe1103a9275999), [`d170596`](https://github.com/Effect-TS/effect/commit/d17059615cca37ca2776654078fe0501ac5202e6), [`aea89d0`](https://github.com/Effect-TS/effect/commit/aea89d0c42ee0ac707a4962cd348fd3158cb469b), [`deed5fb`](https://github.com/Effect-TS/effect/commit/deed5fbdc91cf8bf8c5fce7dfa5d6527ac944726)]: + - effect@4.0.0-beta.106 + +## 4.0.0-beta.105 + +### Patch Changes + +- [#7094](https://github.com/Effect-TS/effect/pull/7094) [`31efc5c`](https://github.com/Effect-TS/effect/commit/31efc5c4eabbe37f9ddab030ef760926e6ff3d24) Thanks @fubhy! - Update peer dependencies +- Updated dependencies [[`0418564`](https://github.com/Effect-TS/effect/commit/04185644dabb8e4169f1ef6cbbc0b36c4db2f7f4), [`d334a85`](https://github.com/Effect-TS/effect/commit/d334a8593aafcd60753995a9449b654c67bfdcc1), [`f0be855`](https://github.com/Effect-TS/effect/commit/f0be8554da6ee00293a6b23869ac46a0b0d97dc8), [`b206fa5`](https://github.com/Effect-TS/effect/commit/b206fa5d7655c1634c9993410a9203f6616a5ca2), [`b938c8a`](https://github.com/Effect-TS/effect/commit/b938c8ad2823bd88493187922f7d9090eff037b6), [`8525f05`](https://github.com/Effect-TS/effect/commit/8525f05d1e14ea12298e9e1a0df497bfaac2ce9a)]: + - effect@4.0.0-beta.105 + +## 4.0.0-beta.104 + +### Patch Changes + +- Updated dependencies [[`1001bcc`](https://github.com/Effect-TS/effect/commit/1001bccb9e874918d59dbb36860f1c5d4499ac20), [`993ba60`](https://github.com/Effect-TS/effect/commit/993ba60ee6c7ca6eb84522040f8b0d268b6ba7d4), [`67faacd`](https://github.com/Effect-TS/effect/commit/67faacd4679242559bee31717c05a5b10b990322), [`b78acdf`](https://github.com/Effect-TS/effect/commit/b78acdf422568f10ae8684fd3f10d52b065f0b56), [`fbb9ce5`](https://github.com/Effect-TS/effect/commit/fbb9ce5e625d1a7d6b7005bda42cbb6cd31476c5), [`722ea48`](https://github.com/Effect-TS/effect/commit/722ea484c9d01364c9242d929c0a564f7831a57c), [`3058fd5`](https://github.com/Effect-TS/effect/commit/3058fd594f5a683034212d71d957017fcc084006), [`62d0575`](https://github.com/Effect-TS/effect/commit/62d057566c241405c23ecf0bf4156186bd2be924), [`99dd6b5`](https://github.com/Effect-TS/effect/commit/99dd6b580434f97c5b40adc919f429e4abc3dfe7), [`7963ce1`](https://github.com/Effect-TS/effect/commit/7963ce1cd95f037fbefea67a29ead49cce4d16cb), [`af14e75`](https://github.com/Effect-TS/effect/commit/af14e752edd65e2b652e960411afafc88975a8d8), [`24e22d2`](https://github.com/Effect-TS/effect/commit/24e22d23a73a2e93ebf6d8edd2246a4a406942c8), [`647d14e`](https://github.com/Effect-TS/effect/commit/647d14e572c8004fa92fba256e00552b42bf34b7), [`1434eec`](https://github.com/Effect-TS/effect/commit/1434eecbd368e00839c24b3950f0b7a69218669a), [`a5278b1`](https://github.com/Effect-TS/effect/commit/a5278b18242011d1b2b08304c7c128151f9a4370), [`6af04a5`](https://github.com/Effect-TS/effect/commit/6af04a50bd019238f6acdb9cbda40439a3c09210), [`cb6c837`](https://github.com/Effect-TS/effect/commit/cb6c8376b2f322d4e7cbfc0973fc3b4f2951ee6e), [`d44cead`](https://github.com/Effect-TS/effect/commit/d44cead7e0e0ce61f0d980906e494f49a07e7899), [`88c7632`](https://github.com/Effect-TS/effect/commit/88c7632c2b59a49fcc40d250865bd8d0dccf31b0), [`abcbb2a`](https://github.com/Effect-TS/effect/commit/abcbb2abe16f1b6c587c15007df14371e1e70e93), [`8f63cce`](https://github.com/Effect-TS/effect/commit/8f63cce636700fde26b140b82e350ef916989d86), [`d56dfcf`](https://github.com/Effect-TS/effect/commit/d56dfcf54c2b9c53c3d098ce4b0ffcc84496c5f7), [`a98cda9`](https://github.com/Effect-TS/effect/commit/a98cda9422e1352f22e81696f759f326ffcfb667), [`6704bb8`](https://github.com/Effect-TS/effect/commit/6704bb84c320547f83cf50e8586ffc4c5e4c3cc5), [`6143de2`](https://github.com/Effect-TS/effect/commit/6143de21ee22038b45a8d4eba86f5aade6238eba), [`936b135`](https://github.com/Effect-TS/effect/commit/936b1358396eb0a1a7c8e0878ba63297e2106812), [`1bbae84`](https://github.com/Effect-TS/effect/commit/1bbae84f88b577a26d04ceb2e76d3143d09c4a20), [`d795ee7`](https://github.com/Effect-TS/effect/commit/d795ee771701ea62bd187ef7c0307d9737f68c1a), [`0a82d88`](https://github.com/Effect-TS/effect/commit/0a82d88b7da73278b6f270118e396d5ed4a64747), [`9215bc5`](https://github.com/Effect-TS/effect/commit/9215bc5da7dd10aa45f07fe44b98f06b6e433d62), [`a1b5df2`](https://github.com/Effect-TS/effect/commit/a1b5df2064d92431cfc6e638af613cc3114313d7), [`92a9ac5`](https://github.com/Effect-TS/effect/commit/92a9ac5ac0aa63d8975b9ba7a094d6a8f59a98f2), [`6bde7f2`](https://github.com/Effect-TS/effect/commit/6bde7f27f3243427203e53fe74472990e5c2a349), [`a712131`](https://github.com/Effect-TS/effect/commit/a7121310dbb60cbd819bbd702f97663098ec7bb8), [`2e6f760`](https://github.com/Effect-TS/effect/commit/2e6f760dcb44e2b984f3311a8af03a1d68a2ec7e), [`aa05804`](https://github.com/Effect-TS/effect/commit/aa0580497e027ed30b756058db0067c3fe07664f), [`badd3bf`](https://github.com/Effect-TS/effect/commit/badd3bf65fac4dd1e66e1f602db43659722dfced), [`02b0265`](https://github.com/Effect-TS/effect/commit/02b02651ede46a5a2dd3ef8081d0ad89648d0cbf), [`3437e21`](https://github.com/Effect-TS/effect/commit/3437e21a56d805781c5e5946a6189795a1dfd411), [`41a550d`](https://github.com/Effect-TS/effect/commit/41a550d1fed31e829929a8f5362b5340303164ac), [`17b5d50`](https://github.com/Effect-TS/effect/commit/17b5d50219ad49533cf9e33d01924a3e16af5eb3), [`96e5e95`](https://github.com/Effect-TS/effect/commit/96e5e9576b0315c747462761a61940ff9fe32dd1), [`e4d589e`](https://github.com/Effect-TS/effect/commit/e4d589e0ea08dc57c4793053b395dc0fcc499f34), [`ae4cf7b`](https://github.com/Effect-TS/effect/commit/ae4cf7b5e2cb5f8c55657e31a61789ad21c38c18), [`6ef5f1a`](https://github.com/Effect-TS/effect/commit/6ef5f1a041f3a40bf03fadd0b1feb275c277c635), [`2235a29`](https://github.com/Effect-TS/effect/commit/2235a29502c3f33cf6468511ad931089013a7916), [`b32f4cb`](https://github.com/Effect-TS/effect/commit/b32f4cb7b2d8ebe817075322622498e3beb05336), [`7f4c095`](https://github.com/Effect-TS/effect/commit/7f4c095b62da43780dd7fc2a5d1785ddfce60edf), [`5f3fb81`](https://github.com/Effect-TS/effect/commit/5f3fb814d18d8a54946c1c1cd0b41459cdb24006), [`17f0b91`](https://github.com/Effect-TS/effect/commit/17f0b91a243ccfe4a38d27debdc983adf434e738), [`0cdadd7`](https://github.com/Effect-TS/effect/commit/0cdadd75bc8abbbcad7956a4bc71f4e7a9b13250), [`39b57d7`](https://github.com/Effect-TS/effect/commit/39b57d7857358040558b67dd33eafc7bb5457830), [`5a6a573`](https://github.com/Effect-TS/effect/commit/5a6a5738e5bfc39e3a37ae7ba99081601fa19ac3), [`59f5e99`](https://github.com/Effect-TS/effect/commit/59f5e9981913b92d7a9beb2214a21d658b999d3a), [`45379d6`](https://github.com/Effect-TS/effect/commit/45379d6179ee4df2cbd3f848bd39ff7149c24a38), [`1949439`](https://github.com/Effect-TS/effect/commit/1949439175809ef81ab9c6411ed5559109edb4c9), [`e443403`](https://github.com/Effect-TS/effect/commit/e443403cf0e4effea14bb6cd950c5ac1c86cc748), [`03af7e8`](https://github.com/Effect-TS/effect/commit/03af7e85551204c605ea2fa2c43c10a4538ac8fb), [`0f721d4`](https://github.com/Effect-TS/effect/commit/0f721d406df8703ea92ca28777b3f09599e2056d), [`130b28d`](https://github.com/Effect-TS/effect/commit/130b28df552d7053407b041a96ff09dae82575e5), [`c987a12`](https://github.com/Effect-TS/effect/commit/c987a12a01b6a52ad53d29edf02613b03574dbcc), [`4158562`](https://github.com/Effect-TS/effect/commit/41585620977de9b84171f76619b72e29cc2284e5), [`306014a`](https://github.com/Effect-TS/effect/commit/306014a1ce4d5cb956c76bdc20e4e28ab3e61a6a), [`729a663`](https://github.com/Effect-TS/effect/commit/729a663275dd31f2357c446fe69664429220a83d), [`caf84b6`](https://github.com/Effect-TS/effect/commit/caf84b660044089e8d7f4067b279b27b8b50e8fd), [`ce067f7`](https://github.com/Effect-TS/effect/commit/ce067f799ea27735d4194345298a216aaf429f01), [`7a41f5a`](https://github.com/Effect-TS/effect/commit/7a41f5aa72d540ecf2746992ecc3fa3e6b40d31f), [`781022a`](https://github.com/Effect-TS/effect/commit/781022acdd3537ca18c88e2fa3681bafa6ef1b21), [`39f1297`](https://github.com/Effect-TS/effect/commit/39f1297acc08864feb12de6b8cf2bf73434f6cf5), [`2db266b`](https://github.com/Effect-TS/effect/commit/2db266b1bfbc81868bc1778c37c76032a267c79f), [`2141e28`](https://github.com/Effect-TS/effect/commit/2141e28903754d72604acf81673ceb2c62a56646), [`3c5e429`](https://github.com/Effect-TS/effect/commit/3c5e429878669ffcf5e0da4ddfbf50bde5bbcaad), [`20ddc63`](https://github.com/Effect-TS/effect/commit/20ddc630584f8fe488162ba384adcae53fc6810a), [`841b3ea`](https://github.com/Effect-TS/effect/commit/841b3ea6ae19a784bc1c20497b02f632af0c91e9), [`82a3fbf`](https://github.com/Effect-TS/effect/commit/82a3fbfce8b9df33e587076b7d7168ecd6799e17), [`eb9ee83`](https://github.com/Effect-TS/effect/commit/eb9ee83b38844a71d1cd5653a229309cfcb04a36), [`64dc7c7`](https://github.com/Effect-TS/effect/commit/64dc7c76dc5c89887b9e7c181d1873dcbb7820d1), [`84dc8ab`](https://github.com/Effect-TS/effect/commit/84dc8ab7accc682bc668c78a97e4a1776b633be8), [`b4463f4`](https://github.com/Effect-TS/effect/commit/b4463f46fc33d3b01ea5eadd7d012a5abda347a3), [`592dd36`](https://github.com/Effect-TS/effect/commit/592dd361645739ac0cd8e6babb084cd27403c172), [`85d2b44`](https://github.com/Effect-TS/effect/commit/85d2b446e3059de4919be730105868f79728308d), [`32e4a69`](https://github.com/Effect-TS/effect/commit/32e4a69b3151b7ec4058af2213b96a41d11e9e06), [`13c5872`](https://github.com/Effect-TS/effect/commit/13c5872ed30830360367ad89af2dab68a003c351), [`3454cdb`](https://github.com/Effect-TS/effect/commit/3454cdb528fdb5d3ed0c5c5c8169bc47de41fbd8), [`e930804`](https://github.com/Effect-TS/effect/commit/e9308045be1d8a00c0b4046f1e8ff22cf68c93da), [`7f12d4b`](https://github.com/Effect-TS/effect/commit/7f12d4b4e731dc3a213ae5c3f60db9edc50292d2), [`181c9ef`](https://github.com/Effect-TS/effect/commit/181c9ef5e5d4ab247bf4aec06424f15b0a1e802e), [`dd9f891`](https://github.com/Effect-TS/effect/commit/dd9f891e23f316abb6192893008f0e33ece9d97d), [`433fb81`](https://github.com/Effect-TS/effect/commit/433fb81ca4c15c681a8ae097ce3ff9bd3a9c9aa5), [`8459cdb`](https://github.com/Effect-TS/effect/commit/8459cdbae8a476dc04b6247fffe6a1668dcb1217), [`6124ab3`](https://github.com/Effect-TS/effect/commit/6124ab39eb64688fbd5d688d24766542f9cb5a2c), [`01bd954`](https://github.com/Effect-TS/effect/commit/01bd9546f142706fca1628f7261e6d1cb9638948), [`ba2c3aa`](https://github.com/Effect-TS/effect/commit/ba2c3aa05eb87ec05d263b960017ecf29746f66a), [`0a45ef3`](https://github.com/Effect-TS/effect/commit/0a45ef3bb4a1ae9b345c43c548db4336a31b3191), [`8459cdb`](https://github.com/Effect-TS/effect/commit/8459cdbae8a476dc04b6247fffe6a1668dcb1217), [`eaa7e71`](https://github.com/Effect-TS/effect/commit/eaa7e71b88bf59b24610128c6115a2a126432731), [`db4c2cc`](https://github.com/Effect-TS/effect/commit/db4c2ccdec77d813b6f4cc72a302ee7c4fe6e39d), [`22f150a`](https://github.com/Effect-TS/effect/commit/22f150a0936cef30517e87eaca73bff1c5e4873a), [`90ffb08`](https://github.com/Effect-TS/effect/commit/90ffb083b3091c211300f50a42ba7bf56536c0ee), [`d517692`](https://github.com/Effect-TS/effect/commit/d517692ef75f45d5f6d9d68b32d41fa0ccc56c99), [`01af079`](https://github.com/Effect-TS/effect/commit/01af079c189d1fc5067d3b1933b2870c4baf2693), [`32a59e8`](https://github.com/Effect-TS/effect/commit/32a59e8058b1ec9738cb083cf1cb116b393ca114)]: + - effect@4.0.0-beta.104 + +## 4.0.0-beta.103 + +### Patch Changes + +- [#6701](https://github.com/Effect-TS/effect/pull/6701) [`9867b9f`](https://github.com/Effect-TS/effect/commit/9867b9fc69f9cc6c443594fc7eccc7be0c674d9c) Thanks @fubhy! - Removed explicit ./index entrypoints + +- Updated dependencies [[`e56cd8f`](https://github.com/Effect-TS/effect/commit/e56cd8f90c3559baccf8fcf2852ea911235d5944), [`f77c120`](https://github.com/Effect-TS/effect/commit/f77c120d8e04779ddeb8bce8e9cde932f268e4b6), [`b2f95a9`](https://github.com/Effect-TS/effect/commit/b2f95a9c2f2581deb89dc3bae9e89cf819e82923), [`04fd44a`](https://github.com/Effect-TS/effect/commit/04fd44a42abfa8dc2642300dcf49ee48c8ef4539), [`b74333d`](https://github.com/Effect-TS/effect/commit/b74333d83e15b9d042e4698ad23040de60454afe), [`1c40b28`](https://github.com/Effect-TS/effect/commit/1c40b2809503d6aa1358777196fc66317906e657), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`b3901d2`](https://github.com/Effect-TS/effect/commit/b3901d29c543fd5bd05ceec669a17896c8e19006), [`4a0984a`](https://github.com/Effect-TS/effect/commit/4a0984af62738fedf4bd3e87adb4d4d641ce9147), [`fffd88b`](https://github.com/Effect-TS/effect/commit/fffd88b3135abdf928ca7c4b0e00e610985091c7), [`f3f6c1e`](https://github.com/Effect-TS/effect/commit/f3f6c1e02cb543423fcffef5dc2db03fac503588), [`ef07642`](https://github.com/Effect-TS/effect/commit/ef07642dfe671d5258b65d1c1480c4d05c495f15), [`f1bc827`](https://github.com/Effect-TS/effect/commit/f1bc8274a608813d7b09d28dcca04adbf62f8c92), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`081f4d8`](https://github.com/Effect-TS/effect/commit/081f4d8cd06a2ac222d2810b46e61efcee26939e), [`5287b24`](https://github.com/Effect-TS/effect/commit/5287b24f5f8fa094ba20e117bfb1a80fba6d2cf5), [`13d31cf`](https://github.com/Effect-TS/effect/commit/13d31cfc2dde46210e94391b5b6767ae9aeaf2c9), [`acee269`](https://github.com/Effect-TS/effect/commit/acee26944bc89ee554d7b9fadab7443f9edc28a9), [`31170c1`](https://github.com/Effect-TS/effect/commit/31170c19b236c37abb5476c821bc6f5bfa2735ab), [`205ebc7`](https://github.com/Effect-TS/effect/commit/205ebc776062012581e98fced7ced19adfc44ee7), [`ed0ebf8`](https://github.com/Effect-TS/effect/commit/ed0ebf8e5c864d46fed1f232e99c0e680f10a58f), [`a3fd084`](https://github.com/Effect-TS/effect/commit/a3fd08482157bd78b089f77c7b173d54ef68b5cd), [`ee29ddf`](https://github.com/Effect-TS/effect/commit/ee29ddf862c3723ad466abc93ab6f6fe723b2319), [`6086309`](https://github.com/Effect-TS/effect/commit/60863090af8e5af0bfa1435f08dc5390f9993e30), [`4a57af2`](https://github.com/Effect-TS/effect/commit/4a57af24011db1d66e947289d2f7ffc2074696d2), [`660875b`](https://github.com/Effect-TS/effect/commit/660875b4325e6eebb3f04513998301cd2a0847ec), [`8e7c706`](https://github.com/Effect-TS/effect/commit/8e7c706b0aca855489b53d987404566d3e9cb5e7), [`5f63adb`](https://github.com/Effect-TS/effect/commit/5f63adbe75fc9d50d23706a52b3e483ad2a1a01c), [`053bc42`](https://github.com/Effect-TS/effect/commit/053bc42e2a964755611a216e78ed214322efee37), [`c0a1534`](https://github.com/Effect-TS/effect/commit/c0a153494484ecf9f0d0f20895a7a648b4be363b), [`f1e3a37`](https://github.com/Effect-TS/effect/commit/f1e3a378c144f974a6122b299f421b75595af20f), [`cedb01a`](https://github.com/Effect-TS/effect/commit/cedb01a025492a1faf9e59eb23eb96bc3b5e2fff), [`1747440`](https://github.com/Effect-TS/effect/commit/1747440de9a51a56ed3660da748cc01b256adce7), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`b4f1ee2`](https://github.com/Effect-TS/effect/commit/b4f1ee238d96aa78c5f040158cb78671d75b381e), [`a4757f1`](https://github.com/Effect-TS/effect/commit/a4757f1c47067d8d016a6c4a2c541bb8ae520f9b), [`cd122b9`](https://github.com/Effect-TS/effect/commit/cd122b90300d995a237993a2edb7a049785ab6a4), [`5de588b`](https://github.com/Effect-TS/effect/commit/5de588b2472fb0f4eb919766eb8472583a044772), [`3895b9c`](https://github.com/Effect-TS/effect/commit/3895b9cf179262cd277a9c6daafe9050dcf8265e), [`89ce5f3`](https://github.com/Effect-TS/effect/commit/89ce5f3e16e23a193daa475dc72ea8133ae1dacd), [`985de09`](https://github.com/Effect-TS/effect/commit/985de097d75906db2aed784841f81e23cc978b43), [`9800e3a`](https://github.com/Effect-TS/effect/commit/9800e3acc8f36530f671bc8b91558cb112f449a7), [`4dc35f6`](https://github.com/Effect-TS/effect/commit/4dc35f64641746366f867ea3dbfedb9cd4685ada), [`e8eb62b`](https://github.com/Effect-TS/effect/commit/e8eb62b3d0ef27e9761cdc2eb93bdec52d6ee204), [`ecd9993`](https://github.com/Effect-TS/effect/commit/ecd99936112cb69efdb02de3a2fd57f47baefdf3), [`5ab9c08`](https://github.com/Effect-TS/effect/commit/5ab9c08463ce049c45f3502676954a7b72c6b024), [`f5cf965`](https://github.com/Effect-TS/effect/commit/f5cf96548afd51f4b3cf1aea11b04d7f8549ce90), [`a94cbed`](https://github.com/Effect-TS/effect/commit/a94cbed84e9e49bea4bff925599c0f19c4e3deab), [`9160ad7`](https://github.com/Effect-TS/effect/commit/9160ad7d146d4376dd12f7510c025e5b2f638a70), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`52494be`](https://github.com/Effect-TS/effect/commit/52494be9e8eb3bb542d06a3dfefc6bca4e168984), [`5441c8e`](https://github.com/Effect-TS/effect/commit/5441c8e656a6418c0d27feb2df67565a3e1155f4), [`c9b56ab`](https://github.com/Effect-TS/effect/commit/c9b56ab507f224426ee8388dc450da447ec4715f), [`8ef7257`](https://github.com/Effect-TS/effect/commit/8ef72577d1f43212cab87951d659e54e3c8d7d91), [`1519406`](https://github.com/Effect-TS/effect/commit/1519406fed6e8b017ae178dc20bcaa2cf318b570), [`9716990`](https://github.com/Effect-TS/effect/commit/97169902eec3c99baa7f0b2c7b45a0a5eae75819), [`733f75b`](https://github.com/Effect-TS/effect/commit/733f75b7125e3016a975fdd251c0179ae5393786), [`48155c8`](https://github.com/Effect-TS/effect/commit/48155c8ccfc12dcca8a00fa358d50b20c30874e4), [`951d06b`](https://github.com/Effect-TS/effect/commit/951d06b83d459d3e8fa9024e727a5db1662d3322), [`d767b65`](https://github.com/Effect-TS/effect/commit/d767b65a7687e38be23f0b0ee3d52ab5f2360cbe), [`5d52d9d`](https://github.com/Effect-TS/effect/commit/5d52d9d148aaa7f736ed8c310fc8bfa9dc81badf), [`f4151e1`](https://github.com/Effect-TS/effect/commit/f4151e1937c26de14f1d64566f8126173f1b5014), [`e02fbb6`](https://github.com/Effect-TS/effect/commit/e02fbb66f5a0f13dba6c33ef63528a37a17a0676), [`724ce09`](https://github.com/Effect-TS/effect/commit/724ce09650a458d4565e5c7331ea92ca04f08e68), [`dbe91f6`](https://github.com/Effect-TS/effect/commit/dbe91f6961ef9f7e8da910ee5758d9c0d385fca8), [`4c008d2`](https://github.com/Effect-TS/effect/commit/4c008d28b370d817f7ae4579db09836fe084c8d2), [`b650832`](https://github.com/Effect-TS/effect/commit/b6508328708a842f3163467b72486bd228f1a289), [`b46c92f`](https://github.com/Effect-TS/effect/commit/b46c92f3b314f4ffd612b831efa55dd856c587a3), [`5335797`](https://github.com/Effect-TS/effect/commit/5335797003076d9c6fd170da98d779696d555596), [`4b3460d`](https://github.com/Effect-TS/effect/commit/4b3460daa434ec465a95a50704fe1103a9275999), [`6301fd7`](https://github.com/Effect-TS/effect/commit/6301fd710b4325718de2c42997dac28a9e9aa250), [`aebc5c6`](https://github.com/Effect-TS/effect/commit/aebc5c61664b89a840465ec65b79ce635a5ceee8), [`52b2d7b`](https://github.com/Effect-TS/effect/commit/52b2d7b5bd3c7cce3bd5b69c6ab3941004da70f3), [`eec5744`](https://github.com/Effect-TS/effect/commit/eec57445dfa0ef3c5977195ad69415b7e7d42bb6), [`24e0e93`](https://github.com/Effect-TS/effect/commit/24e0e93dc307dc2c2ae86caacb7289e1dab3c103), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`1a7ce81`](https://github.com/Effect-TS/effect/commit/1a7ce8150e3977586c44d8ccb9a8384389bb4d49), [`48f22a7`](https://github.com/Effect-TS/effect/commit/48f22a7d16ae57ee2175d450dafbdeb69e187d2a), [`c96b7f6`](https://github.com/Effect-TS/effect/commit/c96b7f6359662053c3e09344f61dddc7a6caf4ac), [`6d2a942`](https://github.com/Effect-TS/effect/commit/6d2a942ed7cd33b8fd79d549edba33bc9e2a7e3e), [`cc27b19`](https://github.com/Effect-TS/effect/commit/cc27b194b9d13fa3a66ab037e853fca9d41700ff), [`8f9499f`](https://github.com/Effect-TS/effect/commit/8f9499f562729f5f7b08d8bcc4db86b4aeff8a21), [`3eeea73`](https://github.com/Effect-TS/effect/commit/3eeea73cfc3e9b126975c2ddbdb7f7c8c92026e2), [`0a532e5`](https://github.com/Effect-TS/effect/commit/0a532e503f165fdea485a5343fc2f420917e8376), [`f398149`](https://github.com/Effect-TS/effect/commit/f398149c134fd9b67b6cdc52eae3f3248d5c7bbe), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`ace903e`](https://github.com/Effect-TS/effect/commit/ace903e09c2549ceebdec380797beb027cd29f3d), [`e8eb62b`](https://github.com/Effect-TS/effect/commit/e8eb62b3d0ef27e9761cdc2eb93bdec52d6ee204), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`48f22a7`](https://github.com/Effect-TS/effect/commit/48f22a7d16ae57ee2175d450dafbdeb69e187d2a), [`d48506d`](https://github.com/Effect-TS/effect/commit/d48506d97525040aa714305e928126df799795b4), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`d48506d`](https://github.com/Effect-TS/effect/commit/d48506d97525040aa714305e928126df799795b4), [`d48506d`](https://github.com/Effect-TS/effect/commit/d48506d97525040aa714305e928126df799795b4), [`52262be`](https://github.com/Effect-TS/effect/commit/52262be2edce0e350c6ac10f8f725678606399c5), [`1284aa1`](https://github.com/Effect-TS/effect/commit/1284aa183451955ad7921bbe01fd0e095695d444), [`9867b9f`](https://github.com/Effect-TS/effect/commit/9867b9fc69f9cc6c443594fc7eccc7be0c674d9c), [`d0f1a22`](https://github.com/Effect-TS/effect/commit/d0f1a2295155c350b04efb46852cb40032805273), [`979ce39`](https://github.com/Effect-TS/effect/commit/979ce3985d7d62ce2bf240681ca19feda3027452), [`b6d3e67`](https://github.com/Effect-TS/effect/commit/b6d3e67c7cc143cd8470cdf704324e79d23954a9), [`adf6c6c`](https://github.com/Effect-TS/effect/commit/adf6c6cd388af8a3c0c546492e71555368556f6a), [`7314d60`](https://github.com/Effect-TS/effect/commit/7314d605284717aaafe7fc34b88c3c93397e865c), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`1acbd8b`](https://github.com/Effect-TS/effect/commit/1acbd8b44c68ebb23735e9810476b870dbe58aea), [`7bde6cc`](https://github.com/Effect-TS/effect/commit/7bde6ccb2b144fe953ff30a7ef5e1ecc97697146), [`a959a8b`](https://github.com/Effect-TS/effect/commit/a959a8bf21cdb976369f494dc949fa00a050d3e0)]: + - effect@4.0.0-beta.103 + +## 4.0.0-beta.102 + +### Patch Changes + +- Updated dependencies [[`b6392e1`](https://github.com/Effect-TS/effect/commit/b6392e119704553edec1b4fd2869ac0dbec621ef), [`7ed9450`](https://github.com/Effect-TS/effect/commit/7ed945044eb56aa9aeaf62d4746a011c96c58628), [`45762bd`](https://github.com/Effect-TS/effect/commit/45762bd78df9ecd87c98b8d3738cdeeac7d81128), [`a6e8391`](https://github.com/Effect-TS/effect/commit/a6e8391cd31acd898fae18b3f8e7ca4c6f14f065), [`4ac7e8b`](https://github.com/Effect-TS/effect/commit/4ac7e8b136c61a26c3e438c013dfd7349b38e999), [`4cd40f5`](https://github.com/Effect-TS/effect/commit/4cd40f5692477783bef84fed3c5ef1c0cf5602e6), [`6956bc0`](https://github.com/Effect-TS/effect/commit/6956bc0e6cb27f53fbec39d9b18545940f9f598f), [`0e50ec7`](https://github.com/Effect-TS/effect/commit/0e50ec7dbb94390666f292cf9120719bf30a7246), [`9fcdade`](https://github.com/Effect-TS/effect/commit/9fcdade4a8af772b9ccd8b8a24fe8cee0e5d8470), [`57367d5`](https://github.com/Effect-TS/effect/commit/57367d54de55047ff0c5fce9685475e236bf354c), [`35c445f`](https://github.com/Effect-TS/effect/commit/35c445ff18029d192900ea0914c993f58d5cf1a5), [`c917bb9`](https://github.com/Effect-TS/effect/commit/c917bb94a4c1c4e0a24372a8ebb8a5ca232e36b5), [`bc1f358`](https://github.com/Effect-TS/effect/commit/bc1f3583e63344cb2c398d9040d9c975488ed123), [`0e0c9d7`](https://github.com/Effect-TS/effect/commit/0e0c9d7922ff463c1093d9e0576fae12cb0698d5), [`73d40aa`](https://github.com/Effect-TS/effect/commit/73d40aacd8fcae1b48c23f5b0a5c542127401d1d), [`4f1e318`](https://github.com/Effect-TS/effect/commit/4f1e3183f7123591c46224e9c587df7594562a5f), [`9d8d85c`](https://github.com/Effect-TS/effect/commit/9d8d85c1bb7da51970845b8ea830e386e777514a), [`6079fda`](https://github.com/Effect-TS/effect/commit/6079fda7b02f2f01ad91c15ab8c307336f3ba252), [`5101e92`](https://github.com/Effect-TS/effect/commit/5101e92c9c149c153423f43dd7a94f6194653c06), [`d0b3265`](https://github.com/Effect-TS/effect/commit/d0b3265c3262670761471ab3518cf933b1b3b20a), [`7a03c89`](https://github.com/Effect-TS/effect/commit/7a03c893ce6492bf94c0ebfb00b63bf25dcbf83e), [`cea1d9c`](https://github.com/Effect-TS/effect/commit/cea1d9c92601e69ebda040af8a1d860d604d885c), [`078e1f5`](https://github.com/Effect-TS/effect/commit/078e1f5636e31b76a86722a636afc37a8cc25580), [`97bafea`](https://github.com/Effect-TS/effect/commit/97bafeab460833b9781527b437d1cb9cbee63260), [`fab0ab8`](https://github.com/Effect-TS/effect/commit/fab0ab8f7ab15ae596faa4ccf75615a494d11b0b), [`c323d8b`](https://github.com/Effect-TS/effect/commit/c323d8b30dbbe85f9df25b67288b93d5332de333), [`6966353`](https://github.com/Effect-TS/effect/commit/69663534d626003eb10a5e55ab1f13e0379fead1), [`0444004`](https://github.com/Effect-TS/effect/commit/04440041989c1785fe4db286379f2be2c15baa85), [`028bbb3`](https://github.com/Effect-TS/effect/commit/028bbb391e161185da10d974ab33381f769940d7), [`ff5d6e2`](https://github.com/Effect-TS/effect/commit/ff5d6e278a1fdff714315dc1a17075012f05c1f0), [`1bfce93`](https://github.com/Effect-TS/effect/commit/1bfce93e6d2bf0794c11733daf51c2390e7de375), [`7ce815c`](https://github.com/Effect-TS/effect/commit/7ce815cd5af6af991dfc13b890fd22345fc77c20), [`7271a7f`](https://github.com/Effect-TS/effect/commit/7271a7faf1080aa75f2f53ca6a0b5ec9334c1d38), [`475fe5c`](https://github.com/Effect-TS/effect/commit/475fe5c12c2d6504c475797c0634f90da01e1797)]: + - effect@4.0.0-beta.102 + +## 4.0.0-beta.101 + +### Patch Changes + +- Updated dependencies [[`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`b35ed29`](https://github.com/Effect-TS/effect/commit/b35ed2904f01536d303b21f288daf343cf740462), [`dd44624`](https://github.com/Effect-TS/effect/commit/dd446245736a0e88c807a02f03c21450bb9340fa), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`2bae1ac`](https://github.com/Effect-TS/effect/commit/2bae1accce9d3b72cf6d5aefc9b2161af6d88436)]: + - effect@4.0.0-beta.101 + +## 4.0.0-beta.100 + +### Patch Changes + +- Updated dependencies [[`c1288dd`](https://github.com/Effect-TS/effect/commit/c1288dd1a52a2811ab7df57fc4ce236c6be4c745), [`2b58a3d`](https://github.com/Effect-TS/effect/commit/2b58a3dab6bc99776dddaf76e27d811e0f47f3d8), [`6dc83f2`](https://github.com/Effect-TS/effect/commit/6dc83f26ddf20d48db28cf761dd8f3716e5273fb), [`c1e2fe0`](https://github.com/Effect-TS/effect/commit/c1e2fe0cf93564f4d919e3998874c3e70b0cf30f), [`f3fbae8`](https://github.com/Effect-TS/effect/commit/f3fbae8d7bae0d77cb4f35a1598b26c58e3bf94d), [`e000f80`](https://github.com/Effect-TS/effect/commit/e000f80fd55bcd8edc699fdbf4cd109004f4f754), [`f4ee765`](https://github.com/Effect-TS/effect/commit/f4ee7655ee052cf9ba726fd602bb87c89c7c62a9), [`510b55f`](https://github.com/Effect-TS/effect/commit/510b55f3e21750685dbfd5f476a130c1c5af9dbd), [`31d3fc4`](https://github.com/Effect-TS/effect/commit/31d3fc4327c50867bb8d881fa7353aeb03ea2826), [`875e618`](https://github.com/Effect-TS/effect/commit/875e618c3764a7b817ac863d0af86924449528f2), [`688d46a`](https://github.com/Effect-TS/effect/commit/688d46afd0ef923d983ad3d7385f52f217b28d70), [`6ff5023`](https://github.com/Effect-TS/effect/commit/6ff502363b9840a5a5ee0a24bc6cae734ac3a3eb), [`c0333e7`](https://github.com/Effect-TS/effect/commit/c0333e7f755f42ddcca7051e029da8b4eed527bf), [`06e7e8c`](https://github.com/Effect-TS/effect/commit/06e7e8c66015ee318f871b9d2218dee82df2b108), [`eb9b102`](https://github.com/Effect-TS/effect/commit/eb9b10256c8558881b441c2fef833b7037174400), [`8b155da`](https://github.com/Effect-TS/effect/commit/8b155da06e0740c354ec562957a45ab65eb4573b), [`3a87335`](https://github.com/Effect-TS/effect/commit/3a8733564c5db35271aa20564ed0d344daa2a79f)]: + - effect@4.0.0-beta.100 + +## 4.0.0-beta.99 + +### Patch Changes + +- Updated dependencies [[`8ce4795`](https://github.com/Effect-TS/effect/commit/8ce4795ccbaebca4292757db568c005a992546a4), [`80b539f`](https://github.com/Effect-TS/effect/commit/80b539f8aba68f478c75c35c2b4140c4ffc4fada), [`88a54cc`](https://github.com/Effect-TS/effect/commit/88a54cc341006e3ebcb13482c618f62a680ce199), [`e6e6dba`](https://github.com/Effect-TS/effect/commit/e6e6dba6e9d86e7c2ad27dcedf289db76a19697f), [`bfb203e`](https://github.com/Effect-TS/effect/commit/bfb203e95aa439f731acad37fc3a9a831a190f1c), [`2e9a34a`](https://github.com/Effect-TS/effect/commit/2e9a34ac2bece4f3a206160480c991e3841dc67a), [`55d4eb3`](https://github.com/Effect-TS/effect/commit/55d4eb34f2c64d54f6a25a305b5c5438ebd7934e), [`bddb010`](https://github.com/Effect-TS/effect/commit/bddb010eac3d4436cb094edbbee7460c5440c162), [`a328835`](https://github.com/Effect-TS/effect/commit/a328835e50d76bc96648a1c1550456e8c9f81210), [`5560d05`](https://github.com/Effect-TS/effect/commit/5560d05aa6abdd29466d9c3412cc5e648b0adbde), [`8f6e3ad`](https://github.com/Effect-TS/effect/commit/8f6e3adb185b16e8820b98c509b308086f7ff1af), [`46997fa`](https://github.com/Effect-TS/effect/commit/46997fa60401f5e3c93daa4b61f7df8e31caaab4), [`9e6e12d`](https://github.com/Effect-TS/effect/commit/9e6e12d75c118cd265496f2880490d1f33a5c8bf), [`3394b93`](https://github.com/Effect-TS/effect/commit/3394b93d97d6f24fc38670641d1490289ffca7f1), [`febeabc`](https://github.com/Effect-TS/effect/commit/febeabc3f7c31094da000a23edeaabfe2ab00a38), [`54161c9`](https://github.com/Effect-TS/effect/commit/54161c98f6f3569e0c31842f54e6a257f9421c4c), [`385f7a4`](https://github.com/Effect-TS/effect/commit/385f7a4ee4a7359928597ea56d151dbaf5eb5802), [`7eea4d0`](https://github.com/Effect-TS/effect/commit/7eea4d0b73ec554915d7066a71f46326ce2ba45f), [`7543afe`](https://github.com/Effect-TS/effect/commit/7543afea6f4d97d1f1ad876224323838a48daadd), [`44b9cf3`](https://github.com/Effect-TS/effect/commit/44b9cf3d240d726997b4bbcd0ede48e074d3c456), [`7eea4d0`](https://github.com/Effect-TS/effect/commit/7eea4d0b73ec554915d7066a71f46326ce2ba45f), [`0a8aa6a`](https://github.com/Effect-TS/effect/commit/0a8aa6acb90a72b91c24d17133c950e4cacd8abd), [`c8d9fcf`](https://github.com/Effect-TS/effect/commit/c8d9fcf7b030f7c474effbab2764ce7aee1c7209), [`9ca7f9a`](https://github.com/Effect-TS/effect/commit/9ca7f9a69363e4485645966d5a93b8f9597c5206), [`e7aca89`](https://github.com/Effect-TS/effect/commit/e7aca894bb32fbb785b5830837e6061c415a6015), [`55d7560`](https://github.com/Effect-TS/effect/commit/55d75609b8acf8a1b54c1b1c7fbbb65ec741aa3e), [`f809189`](https://github.com/Effect-TS/effect/commit/f809189ddf6b6011ba43a9901baaa734e315da2a), [`88a54cc`](https://github.com/Effect-TS/effect/commit/88a54cc341006e3ebcb13482c618f62a680ce199), [`0ebdbe7`](https://github.com/Effect-TS/effect/commit/0ebdbe74463dc84385956d0b1e8c2b79ebab5400), [`7517d09`](https://github.com/Effect-TS/effect/commit/7517d09f12a0b183a81bd425962c4e280a68b05d), [`212493b`](https://github.com/Effect-TS/effect/commit/212493b9a1eb98cd1ef6959c707a2e5784a5ae91), [`88a54cc`](https://github.com/Effect-TS/effect/commit/88a54cc341006e3ebcb13482c618f62a680ce199), [`80ea8cb`](https://github.com/Effect-TS/effect/commit/80ea8cb9222ca73f564c8267ab2f82966fea027a), [`8df19f4`](https://github.com/Effect-TS/effect/commit/8df19f4fe81d90cc33ace88b9a77e5534f82d604)]: + - effect@4.0.0-beta.99 + +## 4.0.0-beta.98 + +### Patch Changes + +- Updated dependencies [[`989603b`](https://github.com/Effect-TS/effect-smol/commit/989603b60ab1197b64acf214208e0d370cd1f842), [`214c458`](https://github.com/Effect-TS/effect-smol/commit/214c458084bb6995d543cd37d1055f24be3d454e), [`a037273`](https://github.com/Effect-TS/effect-smol/commit/a0372736ac34796969b051bbba4717d7983f1ebe), [`97fdaa9`](https://github.com/Effect-TS/effect-smol/commit/97fdaa9c1f522c65e579365d314a07878e2b904f), [`b24d248`](https://github.com/Effect-TS/effect-smol/commit/b24d248c8df44222ce642087cde2bd859a2dc709), [`19c222c`](https://github.com/Effect-TS/effect-smol/commit/19c222cac2353a3d7b7733caecb00556fffe9a5c), [`eec85dd`](https://github.com/Effect-TS/effect-smol/commit/eec85ddba09ea326fd268ee33eeffd47e50d4671), [`0082f4f`](https://github.com/Effect-TS/effect-smol/commit/0082f4f74fb139fd578f87f0a790e845133983dc), [`8849052`](https://github.com/Effect-TS/effect-smol/commit/884905232d1e9a365e046d8dde27bf9c5707f57f), [`c15e16a`](https://github.com/Effect-TS/effect-smol/commit/c15e16ad130d1fbde25d912b7ac55995066cb35b), [`01d00a3`](https://github.com/Effect-TS/effect-smol/commit/01d00a3abfbf1f37996cdbe738ea5137c646cdd7), [`8bd4589`](https://github.com/Effect-TS/effect-smol/commit/8bd458975a1b3a8ed042eccf317b93d28ded91e7), [`0082f4f`](https://github.com/Effect-TS/effect-smol/commit/0082f4f74fb139fd578f87f0a790e845133983dc), [`6e08428`](https://github.com/Effect-TS/effect-smol/commit/6e08428d980501b856f846ad3f3f0e4ea46e7786), [`388dcf9`](https://github.com/Effect-TS/effect-smol/commit/388dcf953f65d317547f34d40e6443c5f264205f), [`2b7ce2b`](https://github.com/Effect-TS/effect-smol/commit/2b7ce2b513e7ec2a77822f1116dc6ffb6ba93f4e), [`87bea7e`](https://github.com/Effect-TS/effect-smol/commit/87bea7e16259246f3bcdf565446394751abca953), [`ce38dc3`](https://github.com/Effect-TS/effect-smol/commit/ce38dc33bda805a684432cca071f4dc3c6b9a1ba), [`a807cd1`](https://github.com/Effect-TS/effect-smol/commit/a807cd170341deca8a1cfb52c4222585f2431bb9), [`fd8a356`](https://github.com/Effect-TS/effect-smol/commit/fd8a356f06a8c9ce4e7e0a13fc4021c178ed31de), [`c2a5edc`](https://github.com/Effect-TS/effect-smol/commit/c2a5edc3abd31ad5bc123362bc1213e03e4095c3), [`5946da3`](https://github.com/Effect-TS/effect-smol/commit/5946da3804a1be5e752b05b96bd058cdba50a1bf), [`4ae0c5f`](https://github.com/Effect-TS/effect-smol/commit/4ae0c5ffcbe6c56ddfcb05c639112a079483539e), [`5b2a0bc`](https://github.com/Effect-TS/effect-smol/commit/5b2a0bceea3a28a33a58555210c90a415dc74a76), [`72ac585`](https://github.com/Effect-TS/effect-smol/commit/72ac585884befde6af9208da738699a93f1bae79), [`5e8c1b8`](https://github.com/Effect-TS/effect-smol/commit/5e8c1b82bfafa121311f987a49ab75395e3647a7), [`0f9c078`](https://github.com/Effect-TS/effect-smol/commit/0f9c07841b04183f485ee6e6458de73b290b09f5)]: + - effect@4.0.0-beta.98 + +## 4.0.0-beta.97 + +### Patch Changes + +- Updated dependencies []: + - effect@4.0.0-beta.97 + +## 4.0.0-beta.96 + +### Patch Changes + +- Updated dependencies [[`1503f45`](https://github.com/Effect-TS/effect-smol/commit/1503f45cb5bb2a74f4705252ec505a1f0ade7e62), [`57fe793`](https://github.com/Effect-TS/effect-smol/commit/57fe79316ffbc380b30626a168981fb26ae97459), [`0c2f78f`](https://github.com/Effect-TS/effect-smol/commit/0c2f78f695ec474e1ff5474da183577975e418f5), [`0c2f78f`](https://github.com/Effect-TS/effect-smol/commit/0c2f78f695ec474e1ff5474da183577975e418f5), [`0c2f78f`](https://github.com/Effect-TS/effect-smol/commit/0c2f78f695ec474e1ff5474da183577975e418f5), [`97f29df`](https://github.com/Effect-TS/effect-smol/commit/97f29df457f7ffd07cfb4b379315c12c086af805)]: + - effect@4.0.0-beta.96 + +## 4.0.0-beta.95 + +### Patch Changes + +- Updated dependencies [[`a482442`](https://github.com/Effect-TS/effect-smol/commit/a482442abdeb490e9652b854ec3495e4aa7273e7), [`fbefa85`](https://github.com/Effect-TS/effect-smol/commit/fbefa850fab2f0a302c20614496aeaaa2a8b5590), [`0b4a32f`](https://github.com/Effect-TS/effect-smol/commit/0b4a32f4260f0d8500942a133001b0d349328102), [`18a49e1`](https://github.com/Effect-TS/effect-smol/commit/18a49e1786679456258002ff9397faf02f678c2d), [`266cb90`](https://github.com/Effect-TS/effect-smol/commit/266cb90bb2c17aabc40563c32db334f09ba3d74b), [`912f095`](https://github.com/Effect-TS/effect-smol/commit/912f095a34572bbd3cedf6edb27878443e3e4a95), [`a6718f9`](https://github.com/Effect-TS/effect-smol/commit/a6718f9e00a15ca903b0732da46116cbf3d6aca7), [`bef5154`](https://github.com/Effect-TS/effect-smol/commit/bef51540a243aa2f872a00c01d0cd58b7a769baa), [`18e0564`](https://github.com/Effect-TS/effect-smol/commit/18e0564bd0f8ebbdfcaf1e2c21529948e9e4a81d), [`fb50f14`](https://github.com/Effect-TS/effect-smol/commit/fb50f14fc3657c1973785aa5b72ecf0b0d28e0b2)]: + - effect@4.0.0-beta.95 + +## 4.0.0-beta.94 + +### Patch Changes + +- Updated dependencies [[`95a0e9b`](https://github.com/Effect-TS/effect-smol/commit/95a0e9bb62797af0e81c9998773405f248f218c5), [`a0a3490`](https://github.com/Effect-TS/effect-smol/commit/a0a3490bbce765f199d8e077aceac504f0462e63), [`f11ce73`](https://github.com/Effect-TS/effect-smol/commit/f11ce73af60823754dc24194f4ffc561b9ea1c2d), [`ff30b6e`](https://github.com/Effect-TS/effect-smol/commit/ff30b6e7c2c63ffc56a4c5818d6d86b01b5ad528), [`1caab3c`](https://github.com/Effect-TS/effect-smol/commit/1caab3cc30f626efbf15e59d74f539a487e5c85c), [`aa80c47`](https://github.com/Effect-TS/effect-smol/commit/aa80c4775a04db87553e5568764cab7e32a72814), [`c2ae4fc`](https://github.com/Effect-TS/effect-smol/commit/c2ae4fce2f03a4cd1861c2b1179da7df656e662d), [`a0a3490`](https://github.com/Effect-TS/effect-smol/commit/a0a3490bbce765f199d8e077aceac504f0462e63)]: + - effect@4.0.0-beta.94 + +## 4.0.0-beta.93 + +### Patch Changes + +- Updated dependencies [[`00652fe`](https://github.com/Effect-TS/effect-smol/commit/00652fe95c18f87208e91343eb8bf218faa2f677), [`6c58167`](https://github.com/Effect-TS/effect-smol/commit/6c5816746eaf91d2a3c7c899c5720809fa230ae3), [`2bc5415`](https://github.com/Effect-TS/effect-smol/commit/2bc541501a7ef89e542d7cb98e96beb53cd205cc), [`e11cccc`](https://github.com/Effect-TS/effect-smol/commit/e11cccc7d5fe631abccc7d6e3bd296938de0fa2e), [`ba7e77e`](https://github.com/Effect-TS/effect-smol/commit/ba7e77e046b8641a3a4e9750bb88ca4a1d063d3f), [`5713ee7`](https://github.com/Effect-TS/effect-smol/commit/5713ee7edbc3054efde407b2286bbfd45bbc6e1c)]: + - effect@4.0.0-beta.93 + +## 4.0.0-beta.92 + +### Patch Changes + +- Updated dependencies [[`affdc13`](https://github.com/Effect-TS/effect-smol/commit/affdc139045cc325dce321a84a580fdc1b2da7b9)]: + - effect@4.0.0-beta.92 + +## 4.0.0-beta.91 + +### Patch Changes + +- Updated dependencies [[`b135b25`](https://github.com/Effect-TS/effect-smol/commit/b135b2517fca9e7839734ace3699a7dfa75b9075), [`aaa21a3`](https://github.com/Effect-TS/effect-smol/commit/aaa21a369a171c600db294f2a4f640583043e150), [`3475ee6`](https://github.com/Effect-TS/effect-smol/commit/3475ee6c2bda6b05c6d7a12ce30c8bb840b5b1a6)]: + - effect@4.0.0-beta.91 + ## 4.0.0-beta.90 ### Patch Changes diff --git a/repos/effect/packages/atom/vue/docgen.json b/repos/effect/packages/atom/vue/docgen.json deleted file mode 100644 index a02edec828..0000000000 --- a/repos/effect/packages/atom/vue/docgen.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "$schema": "../../../node_modules/@effect/docgen/schema.json", - "srcLink": "https://github.com/Effect-TS/effect/tree/main/packages/atom/vue/src/", - "exclude": ["src/internal/**/*.ts"], - "tscExecutable": "tsgo", - "examplesCompilerOptions": { - "noEmit": true, - "strict": true, - "skipLibCheck": true, - "moduleResolution": "Bundler", - "module": "ES2022", - "target": "ES2022", - "lib": ["ES2022", "DOM", "DOM.Iterable"], - "rewriteRelativeImportExtensions": true, - "allowImportingTsExtensions": true, - "paths": { - "effect": ["../../../effect/src/index.js"], - "effect/*": ["../../../effect/src/*.js"] - }, - "plugins": [ - { "name": "@effect/language-service", "includeSuggestionsInTsc": false } - ] - } -} diff --git a/repos/effect/packages/atom/vue/package.json b/repos/effect/packages/atom/vue/package.json index 392da91b98..05151f06c6 100644 --- a/repos/effect/packages/atom/vue/package.json +++ b/repos/effect/packages/atom/vue/package.json @@ -1,17 +1,17 @@ { "name": "@effect/atom-vue", - "version": "4.0.0-beta.90", + "version": "4.0.0-beta.107", "type": "module", "license": "MIT", "description": "Vue bindings for the Effect Atom modules", "homepage": "https://effect.website", "repository": { "type": "git", - "url": "https://github.com/Effect-TS/effect-smol.git", + "url": "https://github.com/Effect-TS/effect.git", "directory": "packages/atom/vue" }, "bugs": { - "url": "https://github.com/Effect-TS/effect-smol/issues" + "url": "https://github.com/Effect-TS/effect/issues" }, "tags": [ "typescript", @@ -29,6 +29,7 @@ ".": "./src/index.ts", "./*": "./src/*.ts", "./internal/*": null, + "./index": null, "./*/index": null }, "files": [ @@ -36,7 +37,10 @@ "dist/**/*.js", "dist/**/*.js.map", "dist/**/*.d.ts", - "dist/**/*.d.ts.map" + "dist/**/*.d.ts.map", + "AGENTS.md", + "CLAUDE.md", + "ai-docs/**/*" ], "publishConfig": { "access": "public", @@ -46,23 +50,21 @@ ".": "./dist/index.js", "./*": "./dist/*.js", "./internal/*": null, + "./index": null, "./*/index": null } }, "scripts": { "build": "tsc -b tsconfig.json && pnpm babel", - "build:tsgo": "tsgo -b tsconfig.json && pnpm babel", "babel": "babel dist --plugins annotate-pure-calls --out-dir dist --source-maps", - "check": "tsc -b tsconfig.json", - "test": "vitest", - "coverage": "vitest --coverage" + "check": "tsc -b tsconfig.json" }, "devDependencies": { "effect": "workspace:^", - "vue": "^3.5.34" + "vue": "^3.5.39" }, "peerDependencies": { "effect": "workspace:^", - "vue": "^3.5.0" + "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 index 799c9ebd46..a2f4340074 100644 --- a/repos/effect/packages/atom/vue/src/index.ts +++ b/repos/effect/packages/atom/vue/src/index.ts @@ -12,25 +12,25 @@ import { computed, type ComputedRef, inject, type InjectionKey, type Ref, shallo /** * @since 4.0.0 - * @category modules + * @category re-exports */ export * as AtomRegistry from "effect/unstable/reactivity/AtomRegistry" /** * @since 4.0.0 - * @category modules + * @category re-exports */ export * as AsyncResult from "effect/unstable/reactivity/AsyncResult" /** * @since 4.0.0 - * @category modules + * @category re-exports */ export * as Atom from "effect/unstable/reactivity/Atom" /** * @since 4.0.0 - * @category modules + * @category re-exports */ export * as AtomRef from "effect/unstable/reactivity/AtomRef" @@ -42,25 +42,25 @@ export * as AtomHttpApi from "effect/unstable/reactivity/AtomHttpApi" /** * @since 4.0.0 - * @category modules + * @category re-exports */ export * as AtomRpc from "effect/unstable/reactivity/AtomRpc" /** * @since 4.0.0 - * @category registry + * @category symbols */ export const registryKey = Symbol.for("@effect/atom-vue/registryKey") as InjectionKey /** * @since 4.0.0 - * @category registry + * @category constants */ export const defaultRegistry: AtomRegistry.AtomRegistry = AtomRegistry.make() /** * @since 4.0.0 - * @category registry + * @category accessors */ export const injectRegistry = (): AtomRegistry.AtomRegistry => { return inject(registryKey, defaultRegistry) diff --git a/repos/effect/packages/atom/vue/tsconfig.json b/repos/effect/packages/atom/vue/tsconfig.json index d3687aaaae..dc6af14d26 100644 --- a/repos/effect/packages/atom/vue/tsconfig.json +++ b/repos/effect/packages/atom/vue/tsconfig.json @@ -1,5 +1,5 @@ { - "$schema": "http://json.schemastore.org/tsconfig", + "$schema": "https://json.schemastore.org/tsconfig", "extends": "../../../tsconfig.base.json", "include": ["src"], "references": [ diff --git a/repos/effect/packages/atom/vue/vitest.config.ts b/repos/effect/packages/atom/vue/vitest.config.ts deleted file mode 100644 index d5870de3d9..0000000000 --- a/repos/effect/packages/atom/vue/vitest.config.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { mergeConfig } from "vitest/config" -import shared from "../../../vitest.shared.ts" - -export default mergeConfig(shared, { - test: { - environment: "happy-dom" - } -}) diff --git a/repos/effect/packages/effect/CHANGELOG.md b/repos/effect/packages/effect/CHANGELOG.md index b030f25ef5..23f3a40753 100644 --- a/repos/effect/packages/effect/CHANGELOG.md +++ b/repos/effect/packages/effect/CHANGELOG.md @@ -1,5 +1,1421 @@ # effect +## 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 diff --git a/repos/effect/packages/effect/CONFIG.md b/repos/effect/packages/effect/CONFIG.md index b49172b448..8b932b7901 100644 --- a/repos/effect/packages/effect/CONFIG.md +++ b/repos/effect/packages/effect/CONFIG.md @@ -97,11 +97,22 @@ Each constructor reads a single value and decodes it into the appropriate type. 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 Missing Keys +### `Config.withDefault` — Fallback for Absent Input -Only triggers when data is missing. Validation errors (wrong type, out of range) still propagate. +Triggers when the config cannot resolve and none of its relevant provider input is present. Validation errors and partially supplied groups still propagate. ```ts import { Config, ConfigProvider, Effect } from "effect" @@ -114,7 +125,7 @@ Effect.runSync(port.parse(provider)) // 3000 ### `Config.option` — Optional Values -Returns `Option.some(value)` on success and `Option.none()` when data is missing. +Returns `Option.some(value)` on success and `Option.none()` when the config is absent. A successful `undefined` value is still a success, so a schema that accepts missing input produces `Option.some(undefined)`, not `Option.none()`. ```ts import { Config, ConfigProvider, Effect } from "effect" @@ -204,7 +215,7 @@ Effect.runSync(config.parse(provider)) // "localhost" ### `Config.all` — Combine Multiple Configs -Accepts a record or a tuple: +Accepts a record or a tuple. A wholly absent group can be handled by `Config.withDefault` or `Config.option`. If any child reads provider input, every other required child must also resolve; partial groups fail instead of silently replacing user input with a whole-group default. ```ts import { Config } from "effect" @@ -220,6 +231,66 @@ const appConfig = Config.all({ 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`. @@ -236,10 +307,23 @@ For reusable codecs you can pass directly to `Config.schema`: | `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. @@ -358,7 +442,7 @@ const program = Effect.gen(function*() { Requires `Path` and `FileSystem` in the Effect context. -Missing files and directories return `undefined`, so fallback providers can handle the path. Other file-system failures are reported as `SourceError`. +Missing files and directories return `undefined`, so fallback providers can handle the path. Empty files also return `undefined` by default after trimming their contents, while directory listings still report the file names present on disk; pass `{ preserveEmptyStrings: true }` to preserve them as `Value("")`. Other file-system failures are reported as `SourceError`. ### `ConfigProvider.make` — Custom Sources @@ -381,7 +465,10 @@ const provider = ConfigProvider.make((path) => { }) ``` -Return `undefined` for "not found". Only fail with `SourceError` for actual I/O errors. +Return `undefined` for "not found" and a `Node` for a path that exists. Only +fail with `SourceError` when the source itself cannot be read. Providers created +with `make` automatically support the path-transformation behavior used by +`mapInput`, `constantCase`, and `nested`. ## ConfigProvider Combinators @@ -510,6 +597,14 @@ const upper = ConfigProvider.mapInput( ) ``` +Path transformation is a capability of the `ConfigProvider` interface. The +exported `ConfigProvider.mapInput` combinator delegates to that capability, +rather than passing an extra transformation argument to `load`. This keeps +ordinary lookup fixed as `load(path)` and allows composite providers to +preserve their own behavior without exposing representation state. Custom +source providers should normally be constructed with `ConfigProvider.make`, +which implements this capability automatically. + `mapInput` runs after earlier provider transformations, so it sees the full path produced so far: ```ts @@ -602,6 +697,8 @@ const program = Effect.gen(function*() { 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: @@ -629,7 +726,7 @@ const program = Config.int("PORT").parse( ) ``` -**Important**: `Config.withDefault` and `Config.option` only recover from missing-data errors. Validation errors still propagate. +**Important**: `Config.withDefault` and `Config.option` recover only from semantic absence. They do not classify `SchemaIssue` values as “missing.” Validation errors, source failures, and partially supplied groups still propagate. ## Practical Example: Web Server Config diff --git a/repos/effect/packages/effect/HTTPAPI.md b/repos/effect/packages/effect/HTTPAPI.md index a693101dfa..fa6bc465dc 100644 --- a/repos/effect/packages/effect/HTTPAPI.md +++ b/repos/effect/packages/effect/HTTPAPI.md @@ -1550,7 +1550,170 @@ The following encodings are supported: ## Setting Response Headers -To add custom headers to the outgoing response, call `HttpEffect.appendPreResponseHandler` inside your handler. The callback receives the request and response objects and must return the updated response. +Response headers can be declared in the endpoint's schemas, so they are type-checked on the server, rendered in the OpenAPI documentation, and decoded by the derived client. Two mechanisms are available: + +- `HttpApiSchema.WithHeaders(schema, headers)` wraps a response schema together with a headers schema. Handlers return the body and headers as a pair. Recommended for success responses, including streams. +- `HttpApiSchema.encodeToWithHeaders` folds headers into an opaque domain type such as an error class, so handlers keep working with plain domain values. + +Only one response schema carrying headers may be declared for each status, though plain responses with different content types may share that status. + +For headers that are not part of the API contract, `HttpEffect.appendPreResponseHandler` remains available as an untyped escape hatch. + +### Declaring Response Headers with WithHeaders + +Wrap the success schema with `HttpApiSchema.WithHeaders(schema, headers)`. The headers argument accepts a fields shorthand (as below) or any schema, mirroring the request-side `headers` option. The handler then returns a value built with `HttpApiSchema.withHeaders({ body, headers })`. + +**Example** (Declaring a Response Header on a Success Schema) + +```ts +import { NodeHttpServer, NodeRuntime } from "@effect/platform-node" +import { Effect, Layer, Schema } from "effect" +import { HttpRouter } from "effect/unstable/http" +import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiGroup, HttpApiSchema } from "effect/unstable/httpapi" +import { createServer } from "node:http" + +const User = Schema.Struct({ + id: Schema.Int, + name: Schema.String +}) + +const Api = HttpApi.make("MyApi").add( + HttpApiGroup.make("Users").add( + HttpApiEndpoint.get("getUsers", "/users", { + // Wrap the success schema with a response headers schema + success: HttpApiSchema.WithHeaders(Schema.Array(User), { + "x-total-count": Schema.Int + }) + }) + ) +) + +const GroupLive = 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 ApiLive = HttpApiBuilder.layer(Api).pipe( + Layer.provide(GroupLive), + HttpRouter.serve, + Layer.provide(NodeHttpServer.layer(createServer, { port: 3000 })) +) + +Layer.launch(ApiLive).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 GroupLive = 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 ApiLive = HttpApiBuilder.layer(Api).pipe( + Layer.provide(GroupLive), + HttpRouter.serve, + Layer.provide(NodeHttpServer.layer(createServer, { port: 3000 })) +) + +Layer.launch(ApiLive).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) @@ -2024,7 +2187,7 @@ import { import { createServer } from "node:http" // Define a custom error for validation failures -class ValidationError extends Schema.TaggedErrorClass()( +class ValidationError extends Schema.TaggedError()( "ValidationError", { message: Schema.String @@ -2182,6 +2345,63 @@ Layer.launch(ApiLive).pipe(NodeRuntime.runMain) // 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 LoadAuthLive = Layer.effect( + LoadAuth, + Effect.succeed((effect) => + Effect.provideService(effect, AuthInfo, { + userId: "user-1" + }) + ) +) + +const RequireAuthLive = Layer.effect( + RequireAuth, + Effect.succeed( + Effect.fnUntraced(function*(effect) { + const authInfo = yield* AuthInfo + yield* Effect.log(`authenticated user ${authInfo.userId}`) + return yield* effect + }) + ) +) +``` + # Security The `HttpApiSecurity` module lets you declare how an endpoint is protected. These declarations show up in the generated OpenAPI spec and are enforced at runtime through middleware. @@ -2206,7 +2426,7 @@ import { HttpApi, HttpApiEndpoint, HttpApiGroup, HttpApiMiddleware, HttpApiSecur class User extends Schema.Class("User")({ id: Schema.Finite }) {} // Define a schema for the "Unauthorized" error -class Unauthorized extends Schema.TaggedErrorClass()( +class Unauthorized extends Schema.TaggedError()( "Unauthorized", {}, // Specify the HTTP status code for unauthorized errors @@ -2265,7 +2485,7 @@ import { HttpApiMiddleware, HttpApiSecurity } from "effect/unstable/httpapi" class User extends Schema.Class("User")({ id: Schema.Finite }) {} -class Unauthorized extends Schema.TaggedErrorClass()( +class Unauthorized extends Schema.TaggedError()( "Unauthorized", {}, // Specify the HTTP status code for unauthorized errors @@ -2321,7 +2541,7 @@ import { HttpApiMiddleware, HttpApiSecurity, OpenApi } from "effect/unstable/htt class User extends Schema.Class("User")({ id: Schema.Finite }) {} -class Unauthorized extends Schema.TaggedErrorClass()( +class Unauthorized extends Schema.TaggedError()( "Unauthorized", {}, // Specify the HTTP status code for unauthorized errors diff --git a/repos/effect/packages/effect/MCP.md b/repos/effect/packages/effect/MCP.md index ff30493f8c..3668e6a9e9 100644 --- a/repos/effect/packages/effect/MCP.md +++ b/repos/effect/packages/effect/MCP.md @@ -12,7 +12,7 @@ Here is an example of a MCP server implementation: import { NodeRuntime, NodeSink, NodeStream } from "@effect/platform-node" import { Effect, Layer, Logger } from "effect" import { Schema } from "effect/schema" -import { McpServer, Tool, Toolkit } from "effect/unstable/ai" +import { McpProtocol, McpServer, Tool, Toolkit } from "effect/unstable/ai" // Define a simple tool const DemoTool = Tool.make("DemoTool", { @@ -58,6 +58,7 @@ const ServerLayer = Layer.mergeAll( McpServer.layerStdio({ name: "Demo MCP Server", version: "1.0.0", + protocols: [McpProtocol.v2025_06_18], stdin: NodeStream.stdin, stdout: NodeSink.stdout }) @@ -78,8 +79,10 @@ The server exposes three main parts: The part layers are merged into one layer that has a MCP server implementation as dependency. `McpServer.layerStdio` is used to create a standard I/O–based MCP server identified by its name and -version. Because of the layer architecture the server implementation can be easily exchanged with an -HTTP based implementation with `McpServer.layerHttp`. Finally, a logging layer is added with +version. Its ordered, non-empty `protocols` declaration names implemented protocol adapters rather +than arbitrary version strings. This release supports `McpProtocol.v2025_06_18`. Because of the +layer architecture the server implementation can be easily exchanged with an HTTP-based implementation +with `McpServer.layerHttp`. Finally, a logging layer is added with `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. @@ -238,7 +241,7 @@ Here's a complete, copy/pastable MCP server example that combines all the concep ```typescript import { NodeRuntime, NodeStdio } from "@effect/platform-node" import { Effect, Layer, Logger, Schema } from "effect" -import { McpSchema, McpServer, Tool, Toolkit } from "effect/unstable/ai" +import { McpProtocol, McpSchema, McpServer, Tool, Toolkit } from "effect/unstable/ai" // Define tools const GreetTool = Tool.make("GreetTool", { @@ -357,7 +360,8 @@ const ServerLayer = Layer.mergeAll( Layer.provide( McpServer.layerStdio({ name: "Demo MCP Server", - version: "1.0.0" + version: "1.0.0", + protocols: [McpProtocol.v2025_06_18] }) ), Layer.provide(NodeStdio.layer), diff --git a/repos/effect/packages/effect/README.md b/repos/effect/packages/effect/README.md index 513ae19cc2..f98948465f 100644 --- a/repos/effect/packages/effect/README.md +++ b/repos/effect/packages/effect/README.md @@ -4,7 +4,7 @@ The `effect` package is the heart of the Effect framework, providing robust prim ## Requirements -- **TypeScript 5.4 or Newer:** +- **TypeScript 5.9 or Newer:** Ensure you are using a compatible TypeScript version. - **Strict Type-Checking:** @@ -49,5 +49,3 @@ The `effect` package provides a collection of modules designed for functional pr | 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. | - -For a comparison between `effect/Schema` and `zod`, see [Schema vs Zod](https://github.com/Effect-TS/effect/tree/main/packages/effect/schema-vs-zod.md). diff --git a/repos/effect/packages/effect/SCHEMA.md b/repos/effect/packages/effect/SCHEMA.md index 041a31d41e..00095dc0d3 100644 --- a/repos/effect/packages/effect/SCHEMA.md +++ b/repos/effect/packages/effect/SCHEMA.md @@ -37,6 +37,39 @@ Use Schema to: 13. **Integrations** — working examples for TanStack Form and Elysia. 14. **Migration from v3** — API mapping from Schema v3 to v4. +## Runtime Performance + +Effect Schema is benchmarked against the public +[`schema-benchmarks`](https://github.com/open-circle/schema-benchmarks) suite. +It exercises a realistic product schema across validation, parsing, error +reporting, schema creation, and codecs. + +The table below compares Effect Schema with the Valibot and Zod cases available +in the same suite. + +Values are microseconds per operation and lower is better. Results vary between +machines, so they are most useful for understanding relative costs. A dash +means that the library does not provide that benchmark. + +| Scenario | Effect Schema | Valibot | Zod 4 | +| ------------------------------------- | ------------: | ---------: | ---------: | +| Create a schema | 118.23 | **40.24** | 318.56 | +| Create a schema and parser | **130.50** | — | — | +| Validate valid data | **5.415** | 5.63 | — | +| Validate invalid data | 1.348 | **0.2431** | — | +| Parse valid data and collect errors | 5.366 | **5.22** | 7.16 | +| Parse invalid data and collect errors | **9.100** | 15.70 | 41.58 | +| Parse valid data and stop early | **5.294** | 5.37 | — | +| Parse invalid data and stop early | 1.352 | **0.2572** | — | +| Standard Schema, valid data | 5.935 | 5.35 | **3.83** | +| Standard Schema, invalid data | **15.203** | 16.51 | 32.85 | +| Standard Schema, valid, stop early | **5.843** | — | — | +| Standard Schema, invalid, stop early | **2.244** | — | — | +| Encode with a typed codec | 0.3420 | — | **0.0405** | +| Decode with a typed codec | 0.3762 | — | **0.0463** | +| Encode unknown input | **0.3472** | — | — | +| Decode unknown input | **0.3637** | — | — | + # Defining Elementary Schemas Schema provides built-in schemas for all common TypeScript types. These schemas represent a single value — like a string or a number — and they are the building blocks you combine into more complex shapes. @@ -244,9 +277,8 @@ Schema.BigInt.check(isNonPositive) ## Dates -The `Schema.Date` schema matches `Date` objects (even invalid dates). - -If you want to validate only valid dates, use `Schema.DateValid` instead. +The `Schema.Date` schema matches valid `Date` objects and rejects invalid dates +such as `new Date(NaN)`. ## Template literals @@ -282,7 +314,7 @@ Success("a@b.com") console.log(String(Schema.decodeUnknownExit(email)("@b.com"))) /* -Failure(Cause([Fail(SchemaError(Expected a string matching template literal parts, got "@b.com"))])) +Failure(Cause([Fail(SchemaError(Expected a string matching template literal parts))])) */ ``` @@ -308,11 +340,11 @@ console.log(String(Schema.decodeUnknownExit(schema)("aa:1"))) // Success(["aa",":",1]) console.log(String(Schema.decodeUnknownExit(schema)("a:1"))) -// Failure(Cause([Fail(SchemaError(Expected a value with a length of at least 2, got "a" +// Failure(Cause([Fail(SchemaError(Expected a value with a length of at least 2 // at [0]))])) console.log(String(Schema.decodeUnknownExit(schema)("aa:1.2"))) -// Failure(Cause([Fail(SchemaError(Expected an integer, got 1.2 +// Failure(Cause([Fail(SchemaError(Expected an integer // at [2]))])) ``` @@ -1133,7 +1165,7 @@ console.log( }) ) ) -// Failure(Cause([Fail(SchemaError: Expected a === b, got {"a":"a","b":"b","c":"c"})])) +// Failure(Cause([Fail(SchemaError: Expected a === b)])) ``` #### Mapping individual fields @@ -1610,7 +1642,7 @@ import { Schema } from "effect" const schema = Schema.UniqueArray(Schema.String) console.log(String(Schema.decodeUnknownExit(schema)(["a", "b", "a"]))) -// Failure(Cause([Fail(SchemaError: Expected an array with unique items, got ["a","b","a"])])) +// Failure(Cause([Fail(SchemaError: Expected an array with unique items)])) ``` ## Records @@ -1638,9 +1670,12 @@ console.log(Schema.decodeUnknownSync(schema)({ a_b: 1, c_d: 2 })) // { aB: 1, cD: 2 } ``` -By default, if a transformation results in duplicate keys, the last value wins. +When parsing sequentially, transformed keys are applied in selection order, so +the later selected property wins if a transformation produces a duplicate key. +With concurrency greater than `1`, completion order determines which value is +retained. -**Example** (Merging transformed keys by keeping the last one) +**Example** (Keeping the later selected value when parsing sequentially) ```ts import { Schema, SchemaTransformation } from "effect" @@ -1653,35 +1688,6 @@ console.log(Schema.decodeUnknownSync(schema)({ a_b: 1, aB: 2 })) // { aB: 2 } ``` -You can customize how key conflicts are resolved by providing a `combine` function. - -**Example** (Combining values for conflicting keys) - -```ts -import { Schema, SchemaTransformation } from "effect" - -const SnakeToCamel = Schema.String.pipe(Schema.decode(SchemaTransformation.snakeToCamel())) - -const schema = Schema.Record(SnakeToCamel, Schema.Number, { - keyValueCombiner: { - decode: { - // When decoding, combine values of conflicting keys by summing them - combine: ([_, v1], [k2, v2]) => [k2, v1 + v2] // you can pass a Semigroup to combine keys - }, - encode: { - // Same logic applied when encoding - combine: ([_, v1], [k2, v2]) => [k2, v1 + v2] - } - } -}) - -console.log(Schema.decodeUnknownSync(schema)({ a_b: 1, aB: 2 })) -// { aB: 3 } - -console.log(Schema.encodeUnknownSync(schema)({ a_b: 1, aB: 2 })) -// { a_b: 3 } -``` - ### Number Keys Records with number keys are supported. @@ -1700,7 +1706,7 @@ console.log(String(Schema.decodeUnknownExit(schema)({ 1.1: "ignored" }))) // Success({}) console.log(String(Schema.decodeUnknownExit(schema)({ 1: null }))) -// Failure(Cause([Fail(SchemaError(Expected string, got null +// Failure(Cause([Fail(SchemaError(Expected string // at ["1"]))])) ``` @@ -1810,7 +1816,7 @@ import { Schema } from "effect" const schema = Schema.Union([Schema.NonEmptyString, Schema.Number]) console.log(String(Schema.decodeUnknownExit(schema)(""))) -// Failure(Cause([Fail(SchemaError: Expected a value with a length of at least 1, got "")])) +// Failure(Cause([Fail(SchemaError: Expected a value with a length of at least 1)])) ``` If none of the union members match the input, the union fails with a message at the top level. @@ -1823,7 +1829,7 @@ import { Schema } from "effect" const schema = Schema.Union([Schema.NonEmptyString, Schema.Number]) console.log(String(Schema.decodeUnknownExit(schema)(null))) -// Failure(Cause([Fail(SchemaError: Expected string | number, got null)])) +// Failure(Cause([Fail(SchemaError: Expected string | number)])) ``` This behavior is especially helpful when working with literal values. Instead of producing a separate error for each literal (as in version 3), the schema reports a single, clear message. @@ -1836,7 +1842,7 @@ import { Schema } from "effect" const schema = Schema.Literals(["a", "b"]) console.log(String(Schema.decodeUnknownExit(schema)(null))) -// Failure(Cause([Fail(SchemaError: Expected "a" | "b", got null)])) +// Failure(Cause([Fail(SchemaError: Expected "a" | "b")])) ``` ### Exclusive Unions @@ -1853,7 +1859,7 @@ const schema = Schema.Union([Schema.Struct({ a: Schema.String }), Schema.Struct( }) console.log(String(Schema.decodeUnknownExit(schema)({ a: "a", b: 1 }))) -// Failure(Cause([Fail(SchemaError: Expected exactly one member to match the input {"a":"a","b":1})])) +// Failure(Cause([Fail(SchemaError: Expected exactly one member to match)])) ``` ### Deriving Unions @@ -1996,7 +2002,7 @@ The result is a tagged union schema with built-in helpers based on the tag value ### Augmenting Tagged Unions -The `asTaggedUnion` function enhances a tagged union schema by adding helper methods for working with its members. +The `toTaggedUnion` function enhances a tagged union schema by adding helper methods for working with its members. You need to specify the name of the tag field used to differentiate between variants. @@ -2022,6 +2028,8 @@ This helper has some advantages over a dedicated constructor: - You can choose among multiple possible tag fields if present. - It supports unions that include nested unions. +Each member must have a unique discriminant property key. `toTaggedUnion` throws when it encounters a duplicate. Numeric and string values that resolve to the same property key, such as `1` and `"1"`, are considered duplicates. + **Note**. If the tag is the standard `_tag` field, you can use `Schema.TaggedUnion` instead. #### Accessing Members by Tag @@ -2036,6 +2044,18 @@ const B = tagged.cases.B const C = tagged.cases.C ``` +#### Accessing Discriminant Values + +The `discriminants` property contains the decoded discriminant values in the same order as the flattened union members. Its type preserves the exact tuple of values. + +**Example** (Deriving a literal schema from discriminants) + +```ts +const Tags = Schema.Literals(tagged.discriminants) + +// Schema.Literals +``` + #### Checking Membership in a Subset of Tags The `isAnyOf` method lets you check if a value belongs to a selected subset of tags. @@ -2191,7 +2211,7 @@ console.log(String(Schema.decodeUnknownExit(URLSchema)(new URL("https://example. // Success(https://example.com/) console.log(String(Schema.decodeUnknownExit(URLSchema)(null))) -// Failure(Cause([Fail(SchemaError(Expected , got null))])) +// Failure(Cause([Fail(SchemaError(Expected ))])) ``` > **Tip**: For simple `instanceof` checks, prefer `Schema.instanceOf(URL)`, it wraps `Schema.declare` with an `instanceof` guard automatically. @@ -2211,7 +2231,7 @@ const URLSchema = Schema.declare( ) console.log(String(Schema.decodeUnknownExit(URLSchema)(null))) -// Failure(Cause([Fail(SchemaError(Expected URL, got null))])) +// Failure(Cause([Fail(SchemaError(Expected URL))])) // ^^^ // Now the error message shows "URL" instead of "" ``` @@ -2247,7 +2267,7 @@ You build a `Link` using `Schema.link()`, which takes two arguments: **Example** (Making `URL` JSON-serializable) ```ts -import { Effect, Option, Schema, SchemaIssue, SchemaTransformation } from "effect" +import { Effect, Schema, SchemaIssue, SchemaTransformation } from "effect" const URLSchema = Schema.declare( (u): u is URL => u instanceof URL, @@ -2261,10 +2281,10 @@ const URLSchema = Schema.declare( // How to convert between URL and string SchemaTransformation.transformOrFail({ // JSON string -> URL (may fail if the string is not a valid URL) - decode: (s) => + decode: (s, options) => Effect.try({ try: () => new URL(s), - catch: (e) => new SchemaIssue.InvalidValue(Option.some(s), { message: globalThis.String(e) }) + catch: () => new SchemaIssue.InvalidValue({ message: "Invalid URL string" }, s, options) }), // URL -> JSON string (always succeeds) encode: (url) => Effect.succeed(url.href) @@ -2319,7 +2339,7 @@ The parsing function you return from `run` is responsible for: **Example** (A generic `Box` container) ```ts -import { Effect, Option, Schema, SchemaIssue, SchemaParser } from "effect" +import { Effect, Schema, SchemaIssue, SchemaParser } from "effect" // 1. Define the type interface Box { @@ -2340,7 +2360,7 @@ const Box = (item: A) => (u, ast, options) => { // First, check the outer shape if (!isBox(u)) { - return Effect.fail(new SchemaIssue.InvalidType(ast, Option.some(u))) + return Effect.fail(new SchemaIssue.InvalidType(ast, u, options)) } // Then, decode the inner value using the item codec return Effect.mapBothEager( @@ -2361,7 +2381,7 @@ console.log(String(Schema.decodeUnknownExit(schema)({ value: "1" }))) // Success({ value: 1 }) console.log(String(Schema.decodeUnknownExit(schema)({ value: "a" }))) -// Failure(Cause([Fail(SchemaError(Expected a finite number, got NaN +// Failure(Cause([Fail(SchemaError(Expected a finite number // at ["value"]))])) ``` @@ -2384,7 +2404,7 @@ import { Schema } from "effect" const schema = Schema.String.check(Schema.makeFilter((s) => s.length >= 3)) console.log(String(Schema.decodeUnknownExit(schema)(""))) -// Failure(Cause([Fail(SchemaError: Expected , got "")])) +// Failure(Cause([Fail(SchemaError: Expected )])) ``` You can attach annotations and provide a custom error message when defining a filter. @@ -2429,10 +2449,10 @@ import { Schema } from "effect" const Username = Schema.NonEmptyString.annotate({ identifier: "Username" }) console.log(String(Schema.decodeUnknownExit(Username)(null))) -// Failure(Cause([Fail(SchemaError: Expected Username, got null)])) +// Failure(Cause([Fail(SchemaError: Expected Username)])) console.log(String(Schema.decodeUnknownExit(Username)(""))) -// Failure(Cause([Fail(SchemaError: Expected a value with a length of at least 1, got "")])) +// Failure(Cause([Fail(SchemaError: Expected a value with a length of at least 1)])) ``` ### Filter return shapes @@ -2543,7 +2563,7 @@ const schema = Schema.String.check( ) console.log(String(Schema.decodeUnknownExit(schema)(" a"))) -// Failure(Cause([Fail(SchemaError: Expected a value with a length of at least 3, got " a")])) +// Failure(Cause([Fail(SchemaError: Expected a value with a length of at least 3)])) ``` **Example** (Using `isMinLength` with an object that has `length`) @@ -2555,7 +2575,7 @@ import { Schema } from "effect" const schema = Schema.Struct({ length: Schema.Number }).check(Schema.isMinLength(3)) console.log(String(Schema.decodeUnknownExit(schema)({ length: 2 }))) -// Failure(Cause([Fail(SchemaError: Expected a value with a length of at least 3, got {"length":2}])) +// Failure(Cause([Fail(SchemaError: Expected a value with a length of at least 3)])) ``` **Example** (Validating array length) @@ -2567,7 +2587,7 @@ import { Schema } from "effect" const schema = Schema.Array(Schema.String).check(Schema.isMinLength(3)) console.log(String(Schema.decodeUnknownExit(schema)(["a", "b"]))) -// Failure(Cause([Fail(SchemaError: Expected a value with a length of at least 3, got ["a","b"]])) +// Failure(Cause([Fail(SchemaError: Expected a value with a length of at least 3)])) ``` ## Multiple Issues Reporting @@ -2589,8 +2609,8 @@ console.log( ) ) /* -Failure(Cause([Fail(SchemaError: Expected a value with a length of at least 3, got " a" -Expected a string with no leading or trailing whitespace, got " a")])) +Failure(Cause([Fail(SchemaError: Expected a value with a length of at least 3 +Expected a string with no leading or trailing whitespace)])) */ ``` @@ -2615,7 +2635,7 @@ console.log( }) ) ) -// Failure(Cause([Fail(SchemaError: Expected a value with a length of at least 3, got " a")])) +// Failure(Cause([Fail(SchemaError: Expected a value with a length of at least 3)])) ``` ## Filter Groups @@ -2672,16 +2692,16 @@ const branded = Schema.String.pipe(Schema.brand("UserId")) Some filters check the structure of a value rather than its contents — for example, the number of items in an array or the number of keys in an object. These are called **structural filters**. -Structural filters are evaluated separately from item-level filters, which allows multiple issues to be reported when `{ errors: "all" }` is used. Examples include: +Examples include: - `isMinLength` or `isMaxLength` on arrays - `isMinSize` or `isMaxSize` on objects with a `size` property - `isMinProperties` or `isMaxProperties` on objects - any constraint that applies to the "shape" of a value rather than to its nested values -These filters are evaluated separately from item-level filters and allow multiple issues to be reported when `{ errors: "all" }` is used. +Structural filters run only after the base array, object, or declaration and its nested values parse successfully. If a nested value fails, its issue is reported but structural filters on the containing value are not evaluated, even with `{ errors: "all" }`. -**Example** (Validating an array with item and structural constraints) +**Example** (A nested failure prevents the structural filter from running) ```ts import { Schema } from "effect" @@ -2694,10 +2714,8 @@ const schema = Schema.Struct({ console.log(String(Schema.decodeUnknownExit(schema)({ tags: ["a", ""] }, { errors: "all" }))) /* -Failure(Cause([Fail(SchemaError: Expected a value with a length of at least 1, got "" - at ["tags"][1] -Expected a value with a length of at least 3, got ["a",""] - at ["tags"])])) +Failure(Cause([Fail(SchemaError: Expected a value with a length of at least 1 + at ["tags"][1])])) */ ``` @@ -2710,7 +2728,7 @@ Define an effectful filter with `Getter.checkEffect` as part of a transformation **Example** (Asynchronous validation of a numeric value) ```ts -import { Effect, Option, Result, Schema, SchemaGetter, SchemaIssue } from "effect" +import { Effect, Result, Schema, SchemaGetter, SchemaIssue } from "effect" // Simulated API call that fails when userId is 0 const myapi = (userId: number) => @@ -2723,13 +2741,15 @@ const myapi = (userId: number) => const schema = Schema.Finite.pipe( Schema.decode({ - decode: SchemaGetter.checkEffect((n) => + decode: SchemaGetter.checkEffect((n, options) => Effect.gen(function*() { // Call the async API and wrap the result in a Result const user = yield* Effect.result(myapi(n)) // If the result is an error, return a SchemaIssue - return Result.isFailure(user) ? new SchemaIssue.InvalidValue(Option.some(n), { title: "not found" }) : undefined // No issue, value is valid + return Result.isFailure(user) + ? new SchemaIssue.InvalidValue({ message: "not found" }, n, options) + : undefined // No issue, value is valid }) ), encode: SchemaGetter.passthrough() @@ -3192,16 +3212,16 @@ This is useful when you need to validate input or enforce rules that may not alw **Example** (Converting a string URL into a `URL` object) ```ts -import { Effect, Option, Schema, SchemaIssue, SchemaTransformation } from "effect" +import { Effect, Schema, SchemaIssue, SchemaTransformation } from "effect" const URLFromString = Schema.String.pipe( Schema.decodeTo( Schema.instanceOf(URL), SchemaTransformation.transformOrFail({ - decode: (s) => + decode: (s, options) => Effect.try({ try: () => new URL(s), - catch: () => new Issue.InvalidValue(Option.some(s), { message: `Invalid URL string: ${s}` }) + catch: () => new SchemaIssue.InvalidValue({ message: "Invalid URL string" }, s, options) }), encode: (url) => Effect.succeed(url.href) }) @@ -3309,10 +3329,17 @@ Strict mode ensures that decoding and encoding fully match. You can disable it b ```ts import { Schema, SchemaTransformation } from "effect" -const From = Schema.String +const From = Schema.Struct({ + a: Schema.Literals(["a", "b"]), + b: Schema.Number +}) -const To = Schema.Number +const To = Schema.Struct({ + a: Schema.String, + b: Schema.Literals([1, 2]) +}) +// Neither From.Type nor To.Encoded extends the other. const schema = From.pipe(Schema.decodeTo(To, SchemaTransformation.passthrough({ strict: false }))) ``` @@ -3654,7 +3681,7 @@ class Person extends Schema.Opaque()( ) {} console.log(String(Schema.decodeUnknownExit(Person)(null))) -// Failure(Cause([Fail(SchemaError: Expected Person, got null)])) +// Failure(Cause([Fail(SchemaError: Expected Person)])) ``` When you call methods like `annotate` on an opaque struct, you get back the original struct, not a new class. @@ -3819,7 +3846,7 @@ g(A.make({ a: "a" })) // error: Argument of type 'A' is not assignable to parame ## Schema as a Class -`Schema.asClass` turns any schema into a class that can be extended with `extends`. The resulting class inherits the full schema API (e.g. `annotate`) and supports static methods that reference `this`. +Any schema can be extended directly with `extends`. The resulting class inherits the full schema API (e.g. `annotate`) and supports static methods that reference `this`. Unlike `Schema.Opaque`, it does **not** make the decoded type nominally distinct, and unlike `Schema.Class`, it does **not** create prototype-backed instances with methods or constructors. It is a lightweight way to attach custom static helpers to a schema. @@ -3828,7 +3855,7 @@ Unlike `Schema.Opaque`, it does **not** make the decoded type nominally distinct ```ts import { Schema } from "effect" -class MyString extends Schema.asClass(Schema.String) { +class MyString extends Schema.String { static readonly decodeUnknownSync = Schema.decodeUnknownSync(this) } @@ -3841,9 +3868,7 @@ console.log(MyString.decodeUnknownSync("a")) ```ts import { Schema } from "effect" -class MyStruct extends Schema.asClass( - Schema.Struct({ name: Schema.String }) -) { +class MyStruct extends Schema.Struct({ name: Schema.String }) { static readonly decodeUnknownSync = Schema.decodeUnknownSync(this) } @@ -3853,12 +3878,12 @@ console.log(MyStruct.decodeUnknownSync({ name: "a" })) ### Subclassing -You can extend an `asClass` class to layer on more static helpers: +You can extend a schema class to layer on more static helpers: ```ts import { Schema } from "effect" -class MyString extends Schema.asClass(Schema.FiniteFromString) { +class MyString extends Schema.FiniteFromString { static readonly decodeUnknownSync = Schema.decodeUnknownSync(this) } @@ -3902,7 +3927,7 @@ try { } } /* -Expected a finite number, got NaN +Expected a finite number at [1] */ ``` @@ -4016,7 +4041,7 @@ class PersonWithEmail extends Person { **Example** (Extending Data.Error) ```ts -import { Data, Effect, identity, Schema, SchemaTransformation, SchemaUtils } from "effect" +import { Data, Effect, identity, Schema, SchemaTransformation } from "effect" const Props = Schema.Struct({ message: Schema.String @@ -4062,9 +4087,6 @@ const schema = Schema.instanceOf(Err, { json: () => Schema.link()(Props, transformation) } }).pipe(Schema.encodeTo(Props, transformation)) - -// built-in helper? -const builtIn = SchemaUtils.getNativeClassSchema(Err, { encoding: Props }) ``` ### Class API @@ -4111,14 +4133,14 @@ try { } catch (error: any) { console.log(error.message) } -// Expected a === b, got {"a":"a","b":"b"} +// Expected a === b try { Schema.decodeUnknownSync(A)({ a: "a", b: "b" }) } catch (error: any) { console.log(error.message) } -// Expected a === b, got {"a":"a","b":"b"} +// Expected a === b ``` #### Branded Classes @@ -4398,19 +4420,19 @@ console.log(Schema.decodeUnknownSync(Animal)({ _tag: "Cat", lives: 9 })) All features from `Class` are available: `extend`, `annotate`, `check`, branded classes, and recursive definitions. -### ErrorClass +### Error ```ts import { Schema } from "effect" -class E extends Schema.ErrorClass("E")({ +class E extends Schema.Error("E")({ id: Schema.Number }) {} ``` -### TaggedErrorClass +### TaggedError -`TaggedErrorClass` combines `ErrorClass` with an automatic `_tag` field, giving you a tagged error that can be caught with `Effect.catchTag`. +`TaggedError` combines `Error` with an automatic `_tag` field, giving you a tagged error that can be caught with `Effect.catchTag`. Like `TaggedClass`, the tag value doubles as the identifier by default, and you can pass an explicit identifier as the first argument to override it. @@ -4419,7 +4441,7 @@ Like `TaggedClass`, the tag value doubles as the identifier by default, and you ```ts import { Effect, Schema } from "effect" -class HttpError extends Schema.TaggedErrorClass()("HttpError", { +class HttpError extends Schema.TaggedError()("HttpError", { status: Schema.Number, message: Schema.String }) {} @@ -4438,11 +4460,11 @@ const recovered = program.pipe( ```ts import { Effect, Schema } from "effect" -class NotFound extends Schema.TaggedErrorClass()("NotFound", { +class NotFound extends Schema.TaggedError()("NotFound", { path: Schema.String }) {} -class Unauthorized extends Schema.TaggedErrorClass()("Unauthorized", { +class Unauthorized extends Schema.TaggedError()("Unauthorized", { reason: Schema.String }) {} @@ -4463,7 +4485,7 @@ const recovered = program.pipe( ) ``` -All features from `ErrorClass` are available: `extend`, `annotate`, and `check`. +All features from `Error` are available: `extend`, `annotate`, and `check`. # Serialization @@ -5536,9 +5558,9 @@ console.log(JSON.stringify(document, null, 2)) ### Generating an Arbitrary from a Schema -Property-based tests need generators. `Schema.toArbitrary` derives a -`fast-check` `Arbitrary` that generates decoded `Type` values accepted by the -schema. +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: @@ -5551,23 +5573,11 @@ const Person = Schema.Struct({ age: Schema.Int.check(Schema.isBetween({ minimum: 18, maximum: 80 })) }) -const PersonArbitrary = Schema.toArbitrary(Person) +const PersonArbitrary = Schema.toArbitrary(Person)(FastCheck) console.log(FastCheck.sample(PersonArbitrary, 3)) ``` -Use `Schema.toArbitraryLazy` only when you want the caller to provide -`fast-check`: - -```ts -import { Schema } from "effect" -import { FastCheck } from "effect/testing" - -const makeStringArbitrary = Schema.toArbitraryLazy(Schema.String) - -const StringArbitrary = makeStringArbitrary(FastCheck) -``` - `Schema.Never` and declaration schemas without a `toArbitrary` annotation cannot be derived automatically. @@ -5620,35 +5630,6 @@ This works because the final predicate check rejects strings that are not palindromes. It may need many attempts, because the base string generator has no reason to produce mirrored strings. -#### Reports - -Use `{ report: true }` when you want to know which filters did not guide -generation: - -```ts -import { Schema } from "effect" - -const isPalindrome = (s: string) => s === Array.from(s).reverse().join("") - -const Palindrome = Schema.String.check( - Schema.makeFilter(isPalindrome, { - expected: "a palindrome" - }) -) - -const result = Schema.toArbitrary(Palindrome, { report: true }) - -result.value -result.report.warnings -``` - -An `OpaqueFilter` warning means: "this filter is still checked, but it did not -help build the generator." - -Reports contain warnings only. Unsupported schemas, impossible constraints, -invalid candidates, and recursive schemas without a finite terminal path still -fail immediately. - #### Custom Filters With Constraints If part of a custom filter can be described as a normal generation constraint, @@ -5817,7 +5798,7 @@ Generic declarations receive one derivation per type parameter: For an opaque wrapper type, you usually map both sources in the same way: ```ts -import { Effect, Option, Schema, SchemaIssue, SchemaParser } from "effect" +import { Effect, Schema, SchemaIssue, SchemaParser } from "effect" class Box { private constructor(private readonly value: A) {} @@ -5838,7 +5819,7 @@ const BoxSchema = (value: A) => [value], ([valueCodec]) => (input, ast, options) => { if (!isBox(input)) { - return Effect.fail(new SchemaIssue.InvalidType(ast, Option.some(input))) + return Effect.fail(new SchemaIssue.InvalidType(ast, input, options)) } return Effect.map( SchemaParser.decodeUnknownEffect(valueCodec)(Box.unbox(input), options), @@ -5925,7 +5906,7 @@ const Person = Schema.Struct({ company: Company }) -console.log(FastCheck.sample(Schema.toArbitrary(Person), 3)) +console.log(FastCheck.sample(Schema.toArbitrary(Person)(FastCheck), 3)) ``` These overrides are useful because the values have domain shape: names look like @@ -6050,6 +6031,8 @@ console.log(_s.replace("b", new B({ a: new A({ s: "a" }) }))) // B { a: A { s: 'b' } } ``` +Reading through the generated `Iso` encodes the schema value, while replacing through it decodes the new focus. Either direction can throw an `Error` with the generic message `"Schema validation failed"` and a `SchemaIssue.Issue` in its `cause`. Use `SchemaIssue.makeFormatterDefault()` to format that cause when human-readable details are needed. + ### Using the Differ Module for Type-Safe JSON Patches The `Differ` module lets you compute and apply JSON Patch (RFC 6902) changes for any value described by a `Schema`. You give it a schema once, then use the returned differ to produce a patch from an old value to a new value, and to apply that patch. @@ -6129,49 +6112,55 @@ The idea is simple: if you have a `Schema` for a type `T`, you can serialize any This approach keeps patches independent from TypeScript types and uses the schema as the guardrail when turning JSON back into `T`. +Schema conversion failures from `diff` or `patch` throw an `Error` with the generic message `"Schema validation failed"` and a `SchemaIssue.Issue` in its `cause`. Format that cause explicitly with `SchemaIssue.makeFormatterDefault()`. Errors raised while applying an invalid JSON Patch operation are separate `JsonPatch` errors rather than schema validation failures. + # Schema Representation -The `SchemaRepresentation` module converts a `Schema` into a portable data structure and back again. +The `SchemaRepresentation` module exposes the structural form used to inspect, persist, compile, and rebuild schemas. + +A representation is always a projection of one side of a schema. By default, `Schema.toRepresentation` and +`SchemaRepresentation.toRepresentation` project the encoded side. Apply `Schema.toType` or `SchemaAST.toType` first when +you need the decoded type side instead. Use it when you need to: -- store schemas on disk (for example in a cache) -- send schemas over the network -- rebuild runtime schemas later -- convert to JSON Schema (Draft 2020-12) -- generate TypeScript code that recreates schemas +- inspect the structural form of a schema +- store schemas on disk or send them over the network +- rebuild runtime schemas with an explicit set of revivers +- compile live representations to JSON Schema Draft 2020-12 +- generate TypeScript code from live representations At a high level: -- `fromAST` / `fromASTs` turn a schema AST into a `Document` / `MultiDocument` -- `DocumentFromJson` (schema) round-trip that document through JSON -- `toSchema` rebuilds a runtime `Schema` from the stored representation -- `toJsonSchemaDocument` produces a Draft 2020-12 JSON Schema document -- `toCodeDocument` prepares data for code generation (via `toMultiDocument`) +- `Schema.toRepresentation(schema)` converts a schema to a `Document` +- `SchemaRepresentation.toRepresentation(ast)` and `toRepresentations(asts)` convert schema ASTs to a `Document` or + `MultiDocument` +- `toJson` / `fromJson` cross the persistence boundary +- `fromRepresentation` / `fromRepresentations` rebuild runtime schemas using explicit revivers +- `toJsonSchemaDocument` compiles a live `Document` to JSON Schema Draft 2020-12 +- `toCodeDocument` compiles a live `MultiDocument` to runtime and TypeScript source fragments ```mermaid flowchart TD - S[Schema] -->|fromAST|D{"SchemaRepresentation.Document"} - S -->|fromASTs|MD{"SchemaRepresentation.MultiDocument"} - JS["JSON Schema (draft-07, draft-2020-12, openapi-3.0, openapi-3.1)"] -->JSD - JD --> JS - JD["JsonSchema.Document"] -->|fromJsonSchemaDocument|D - D <--> |"DocumentFromJson (schema)"|JSON - D --> |toJsonSchemaDocument|JD - D --> |toSchema|S - MD --> |toCodeDocument|CodeDocument["CodeDocument"] - D --> |toMultiDocument|MD - MD --> |toJsonSchemaMultiDocument|JMD[JsonSchema.MultiDocument] - MD <--> |"MultiDocumentFromJson (schema)"|JSON + S[Schema] -->|Schema.toRepresentation|LD["live Document"] + AST[SchemaAST] -->|SchemaRepresentation.toRepresentation|LD + LD -->|toJson|JSON["JSON value"] + JSON -->|fromJson|PD["persisted Document"] + PD -->|"fromRepresentation + revivers"|S + LD -->|toJsonSchemaDocument|JD["JsonSchema.Document (draft-2020-12)"] + JD -->|fromJsonSchemaDocument|S + LD -->|toMultiDocument|LMD["live MultiDocument"] + LMD -->|toCodeDocument|CodeDocument + LMD -->|toJsonSchemaMultiDocument|JMD[JsonSchema.MultiDocument] + LMD -->|toJsonMultiDocument|JSON ``` ## The data model ### `Representation` -A `Representation` is a tagged object tree (`_tag` fields like `"String"`, `"Objects"`, `"Union"`, ...). It describes the _structure_ of a schema in a JSON-friendly way. - -Only a subset of schema features can be represented. See "Limitations" below. +A `Representation` is a tagged object tree (`_tag` fields like `"String"`, `"Objects"`, `"Union"`, ...). It describes one +structural side of a schema. Named or recursive nodes use `Reference` values instead of duplicating their definitions. ### `Document` @@ -6188,131 +6177,287 @@ A `MultiDocument` stores multiple root representations that share the same `refe This is useful if you want to serialize a set of schemas together, or if you want to generate code for multiple schemas while emitting shared definitions only once. -## Limitations +## Projection and persistence boundaries -`SchemaRepresentation` is meant for schemas that can be described without user code. +### Representations use the encoded side -That has a few consequences. +`toRepresentation` follows a schema's encoding chain and represents its last encoded side. It does not serialize the +transformation functions. -### Transformations are not supported +```ts +import { Schema } from "effect" -The representation format describes the schema's _shape_ and a set of known checks. It does not store transformation logic. +const encoded = Schema.toRepresentation(Schema.NumberFromString) +console.log(encoded.representation._tag) +// "String" -Schemas that rely on transformations cannot be round-tripped, including: +const decoded = Schema.toRepresentation(Schema.toType(Schema.NumberFromString)) +console.log(decoded.representation._tag) +// "Number" +``` -- `Schema.transform(...)` -- `Schema.encodeTo(...)` -- custom codecs or any schema that changes how values are encoded/decoded +Consequently, rebuilding `encoded` produces a schema for the string representation; it does not recreate the original +string-to-number transformation. -If you serialize a transformed schema, the transformation logic will be lost. When you rebuild it with `toSchema`, you will only get the structural schema. +### Live and persisted documents -> **Aside** (Why transformations are excluded) -> -> A transformation is user code (functions). JSON cannot store functions, and serializing functions as strings would not be safe or portable. +A live `Document` can contain functions in its ordinary annotations. These callbacks allow compilers to handle custom +behavior: -### Only built-in checks can be represented +- a check can provide `toJsonSchema` +- a declaration or check can provide `toCode` -Checks are stored as `Filter` / `FilterGroup` nodes with a small `meta` object. +Functions cannot cross the JSON persistence boundary. `toJson` removes them and keeps only JSON-valued ordinary +annotations. Nested JSON arrays and objects are preserved; a complete annotation value is omitted when it contains a +function, `undefined`, `bigint`, a symbol, a cycle, or another non-JSON value. -Only checks that match the built-in meta definitions are supported, such as: +Structural values such as bigint literals and registered unique symbols have dedicated canonical encodings. That does not +make bigint or symbol values valid generic annotations. -- string checks: `isMinLength`, `isPattern`, `isUUID`, ... -- number checks: `isInt`, `isBetween`, `isMultipleOf`, ... -- bigint checks: `isGreaterThanBigInt`, ... -- array checks: `isLength`, `isUnique`, ... -- object checks: `isMinProperties`, ... -- date checks: `isBetweenDate`, ... +### Persistence identities -Custom predicates (for example `Schema.filter((x) => ...)`) are not supported, because the representation has nowhere to store the function. +Opaque declarations and checks need a stable identity before they can be persisted: -### Annotations are filtered +```ts +interface RepresentationAnnotation { + readonly id: string + readonly payload: Schema.Json +} -Annotations are stored as a record, but: +interface CheckRepresentationAnnotation extends RepresentationAnnotation { + readonly schemas?: ReadonlyArray +} +``` -- only values that look like JSON primitives (plus `bigint` and `symbol` in the in-memory form) are kept -- some annotation keys are dropped using an internal blacklist +`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. -In practice, documentation annotations like `title` and `description` are preserved, while complex values (functions, instances, nested objects) are ignored. +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. -### Declarations need a reviver +## Creating representations -Some runtime schemas are represented as `Declaration` nodes. Rebuilding them requires a "reviver" function. +Use `Schema.toRepresentation` when starting from a schema: -`toSchema` ships with a default reviver (`toSchemaDefaultReviver`) that recognizes a fixed set of constructors, including: +```ts +import { Schema } from "effect" -- `effect/Option`, `effect/Result`, `effect/Exit`, ... -- `ReadonlyMap`, `ReadonlySet` -- `RegExp`, `URL`, `Date` -- `FormData`, `URLSearchParams`, `Uint8Array` -- `DateTime.Utc`, `effect/Duration` +const document = Schema.toRepresentation( + Schema.Struct({ name: Schema.NonEmptyString }) +) +``` -If your document contains other declarations, pass a custom `reviver` to `toSchema`. +Use the lower-level functions when working directly with ASTs or several roots: -## JSON round-tripping +```ts +import { Schema, SchemaRepresentation } from "effect" + +const document = SchemaRepresentation.toRepresentation(Schema.String.ast) + +const multiDocument = SchemaRepresentation.toRepresentations([ + Schema.String.ast, + Schema.Number.ast +]) +``` + +Repeated structural nodes, identifiers, and recursive schemas are placed in `references`. Repeated `Suspend` and +`Declaration` nodes are reference candidates as well. For unions, enums, template literals, and string literals, the +converter uses an inexpensive size estimate and creates an anonymous reference only when it expects the reference to be +smaller than repeating the body. `toMultiDocument(document)` wraps a single document when a compiler requires multiple +roots. + +An explicit `identifier` requests a reference name within a conversion. 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, make a node a +reference candidate. Independently constructed ASTs are not canonicalized merely because their other fields contain the +same references. 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. + +## JSON persistence ### `toJson` / `fromJson` -- `toJson(document)` returns JSON-compatible data (safe to `JSON.stringify`) -- `fromJson(unknown)` validates and parses JSON data back into a `Document` +`toJson(document)` projects and validates a live document, then returns a `Schema.Json` value suitable for storage or +transport. `fromJson(input)` validates persisted JSON and returns a `Document`; it does not restore runtime callbacks. -Internally, these functions use a canonical JSON codec for `Document$`. This is why values like `bigint` in annotations are encoded as strings in the JSON form and restored on decode. +The multi-root equivalents are `toJsonMultiDocument` and `fromJsonMultiDocument`. -## Rebuilding runtime schemas +```ts +import { Schema, SchemaRepresentation } from "effect" -### `toSchema` +const live = Schema.toRepresentation( + Schema.String.check(Schema.isMinLength(3)) +) -`toSchema(document)` walks the representation tree and recreates a runtime schema. +const json = SchemaRepresentation.toJson(live) +const persisted = SchemaRepresentation.fromJson(json) +``` -What it does: +Persisted `Declaration` and `Filter` nodes must contain a representation identity. `fromJson` validates the document but +does not require the corresponding revivers until reconstruction. + +## Rebuilding runtime schemas -- rebuilds the structural schema nodes (`Struct`, `Tuple`, `Union`, ...) -- resolves references from `document.references` -- supports recursive references using `Schema.suspend` -- re-attaches stored annotations via `.annotate(...)` and `.annotateKey(...)` -- re-applies supported checks via `.check(...)` +### `fromRepresentation` -If you need custom handling for declarations: +`fromRepresentation` rebuilds structural nodes, resolves references, restores recursion, reattaches annotations, and +reapplies checks. Revivers are resolved by `id`; none are installed implicitly, so the `revivers` array is required even +when it is empty. ```ts -SchemaRepresentation.toSchema(document, { - reviver: (declaration, recur) => { - // Return a runtime schema to override how a Declaration is rebuilt. - // Return undefined to fall back to the default behavior. - return undefined - } +import { Schema, SchemaRepresentation } from "effect" + +const json = SchemaRepresentation.toJson( + Schema.toRepresentation( + Schema.String.check(Schema.isMinLength(3)) + ) +) + +const document = SchemaRepresentation.fromJson(json) +const rebuilt = SchemaRepresentation.fromRepresentation(document, { + revivers: [Schema.isMinLengthReviver] }) + +console.log(Schema.is(rebuilt)("abc")) +// true +console.log(Schema.is(rebuilt)("a")) +// false ``` -## JSON Schema output +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 -### `toJsonSchemaDocument` / `toJsonSchemaMultiDocument` +There are separate reviver contracts for opaque declarations, leaf filters, and opaque filter groups: -These functions convert a `Document` or `MultiDocument` into a Draft 2020-12 JSON Schema document. +- `DeclarationReviver

` +- `FilterReviver

` +- `FilterGroupReviver

` -This is useful for tooling that expects JSON Schema, or for producing OpenAPI-compatible schema pieces (depending on your pipeline). +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. During this high-level conversion, declarations +are not extracted into anonymous references: their JSON Schema body is unconstrained, and leaving it inline preserves +empty-schema simplifications. Explicit and recursive references are unaffected. + +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. + +`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 is best-effort: JSON Schema constructs are translated to Effect schemas where possible, but the result is not a +lossless reconstruction of an original Effect schema. The optional `onEnter` callback can normalize each JSON Schema node +before it is translated. + +Regular expression constraints reached during best-effort translation are rejected by default because imported patterns +use the runtime's native regular expression engine and may block validation for an unbounded amount of time. Set +`patterns: "apply"` only for trusted documents. Set `patterns: "ignore"` to skip reached pattern constraints explicitly; +the resulting schema accepts values that the source document may reject. The policy includes `pattern`, the keys of +`patternProperties`, and patterns nested in `propertyNames`. Ignoring `patternProperties` also skips its value constraints +and `additionalProperties`, because matching keys cannot be determined without evaluating the patterns. ## Code generation ### `toCodeDocument` -`toCodeDocument` converts a `MultiDocument` into a structure that is convenient for generating TypeScript source. +`toCodeDocument` compiles a live `MultiDocument` into runtime and TypeScript source fragments. It: -It: - -- sorts references so non-recursive definitions can be emitted in dependency order -- keeps recursive definitions separate (they must be emitted using `Schema.suspend`) +- returns one `Code` value for each root +- sorts non-recursive references in dependency order +- keeps recursive references separate so callers can emit `Schema.suspend` - sanitizes reference names into valid JavaScript identifiers -- collects extra artifacts that must be emitted (enums, symbols, imports) - -You can customize: +- collects symbol, enum, and import artifacts -- `sanitizeReference` to control how `$ref` strings become identifiers -- `reviver` to generate custom code for `Declaration` nodes +Opaque declarations and checks provide code through their `toCode` callbacks. `toCodeDocument` does not accept a +reviver option. To generate code from persisted JSON, first reconstruct the schemas with `fromRepresentation` or +`fromRepresentations`, then create a new live representation so the revivers can restore the callbacks. # Error Handling and Formatting When validation fails, Schema produces structured error objects that describe what went wrong. Formatters turn those error objects into human-readable messages you can display to users or write to logs. +### Reporting Rejected Inputs + +By default, schema issues neither retain rejected input values nor include them in formatted messages. Pass `{ reportInput: true }` to a parser when the additional diagnostic context is worth the disclosure and retention risk: + +```ts +import { Result, Schema, SchemaIssue, SchemaParser } from "effect" + +const result = SchemaParser.decodeUnknownResult(Schema.String)(1, { reportInput: true }) +const formatIssue = SchemaIssue.makeFormatterDefault() + +if (Result.isFailure(result)) { + SchemaIssue.hasInput(result.failure) // true + if (SchemaIssue.hasInput(result.failure)) { + result.failure.input // 1 + } + formatIssue(result.failure) // "Expected string, got 1" +} +``` + +Value-bearing issues created by the parser then expose an enumerable own `input` field. The input is retained by reference, not copied. Use `SchemaIssue.hasInput(issue)` instead of checking `issue.input !== undefined`, because a present input whose value is `undefined` is distinct from an issue that does not retain input. + +Enabling this option can retain or disclose secrets, personally identifiable information, and large object graphs. Object enumeration, spread, serialization, `SchemaIssue.makeFormatterDefault()`, `SchemaError.message`, and Standard Schema messages may expose the retained value. A Standard Schema failure still contains only its standard `message` and `path` fields; the input can appear inside `message`, but no non-standard `input` field is added. + +User-created `SchemaIssue.Issue` values returned directly by declarations, checks, transformations, or middleware are not modified. To make a custom value-bearing issue honor `reportInput`, pass the callback's input and effective parse options to its constructor, for example `new SchemaIssue.InvalidValue(annotations, input, options)`. + ### Formatters #### StandardSchemaV1 formatter @@ -6390,7 +6535,7 @@ Output: { issues: [ { path: [ 'a' ], message: 'Missing key' }, - { path: [ 'b' ], message: 'Expected a value with a length of at least 1, got ""' } + { path: [ 'b' ], message: 'Expected a value with a length of at least 1' } ] } */ @@ -6586,7 +6731,7 @@ if (r._tag === "Failure") { { issues: [ { - message: 'Expected a value with a length of at least 1, got ""', + message: 'Expected a value with a length of at least 1', path: [ 'a' ] }, { message: 'Missing key', path: [ 'c', 0 ] }, diff --git a/repos/effect/packages/effect/benchmark/http/multipart.ts b/repos/effect/packages/effect/benchmark/http/multipart.ts new file mode 100644 index 0000000000..22ef22378d --- /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/httpapi/sseClientDecoder.ts b/repos/effect/packages/effect/benchmark/httpapi/sseClientDecoder.ts new file mode 100644 index 0000000000..0b41490a86 --- /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/schema/Enums.ts b/repos/effect/packages/effect/benchmark/schema/Enums.ts deleted file mode 100644 index fad7abed0d..0000000000 --- a/repos/effect/packages/effect/benchmark/schema/Enums.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { Schema } from "effect" -import { Bench } from "tinybench" - -/* -┌─────────┬───────────┬──────────────────┬──────────────────┬────────────────────────┬────────────────────────┬──────────┐ -│ (index) │ Task name │ Latency avg (ns) │ Latency med (ns) │ Throughput avg (ops/s) │ Throughput med (ops/s) │ Samples │ -├─────────┼───────────┼──────────────────┼──────────────────┼────────────────────────┼────────────────────────┼──────────┤ -│ 0 │ 'good' │ '79.26 ± 0.73%' │ '83.00 ± 1.00' │ '14443802 ± 0.02%' │ '12048193 ± 143431' │ 12616336 │ -│ 1 │ 'bad' │ '131.52 ± 0.92%' │ '125.00 ± 0.00' │ '7976946 ± 0.01%' │ '8000000 ± 0' │ 7603261 │ -└─────────┴───────────┴──────────────────┴──────────────────┴────────────────────────┴────────────────────────┴──────────┘ -*/ - -const bench = new Bench() - -enum Enum { - A = "a", - B = "b" -} - -const schema = Schema.Enum(Enum) - -const good = "b" -const bad = "c" - -const decodeUnknownExit = Schema.decodeUnknownExit(schema) - -// console.log(decodeUnknownExit(valid)) -// console.log(decodeUnknownExit(invalid)) - -bench - .add("good", function() { - decodeUnknownExit(good) - }) - .add("bad", function() { - decodeUnknownExit(bad) - }) - -await bench.run() - -console.table(bench.table()) diff --git a/repos/effect/packages/effect/benchmark/schema/Optic.ts b/repos/effect/packages/effect/benchmark/schema/Optic.ts index 0581d5fa33..5d47417722 100644 --- a/repos/effect/packages/effect/benchmark/schema/Optic.ts +++ b/repos/effect/packages/effect/benchmark/schema/Optic.ts @@ -1,19 +1,24 @@ import { Optic, Schema } from "effect" import { Bench } from "tinybench" -/* -┌─────────┬──────────────────┬──────────────────┬──────────────────┬────────────────────────┬────────────────────────┬──────────┐ -│ (index) │ Task name │ Latency avg (ns) │ Latency med (ns) │ Throughput avg (ops/s) │ Throughput med (ops/s) │ Samples │ -├─────────┼──────────────────┼──────────────────┼──────────────────┼────────────────────────┼────────────────────────┼──────────┤ -│ 0 │ 'iso get' │ '907.53 ± 1.06%' │ '834.00 ± 1.00' │ '1159005 ± 0.02%' │ '1199041 ± 1439' │ 1101891 │ -│ 1 │ 'optic get' │ '32.79 ± 0.20%' │ '42.00 ± 1.00' │ '25353263 ± 0.00%' │ '23809524 ± 580720' │ 30500447 │ -│ 2 │ 'direct get' │ '23.12 ± 0.48%' │ '41.00 ± 1.00' │ '32734753 ± 0.01%' │ '24390244 ± 580720' │ 43255789 │ -│ 3 │ 'iso replace' │ '2693.0 ± 2.87%' │ '2459.0 ± 41.00' │ '396398 ± 0.03%' │ '406669 ± 6669' │ 371349 │ -│ 4 │ 'direct replace' │ '848.59 ± 0.45%' │ '792.00 ± 1.00' │ '1244301 ± 0.02%' │ '1262626 ± 1596' │ 1178430 │ -└─────────┴──────────────────┴──────────────────┴──────────────────┴────────────────────────┴────────────────────────┴──────────┘ -*/ +// Batching bounds sample storage and keeps sub-microsecond timings above timer resolution. +const batchSize = 1_000 +const bench = new Bench({ + iterations: 1_000, + time: 0, + warmupIterations: 100, + warmupTime: 0, + timestampProvider: "hrtimeNow" +}) +let sink: unknown -const bench = new Bench() +const batch = (run: () => A) => () => { + let value = run() + for (let index = 1; index < batchSize; index++) { + value = run() + } + sink = value +} // Define a class with nested properties class User extends Schema.Class("User")({ @@ -47,33 +52,53 @@ const iso = Schema.toIso(User).key("profile").key("address").key("street") const optic = Optic.id().key("profile").key("address").key("street") bench - .add("iso get", function() { - iso.get(user) - }) - .add("optic get", function() { - optic.get(user) - }) - .add("direct get", function() { - // oxlint-disable-next-line no-unused-expressions - user.profile.address.street - }) - .add("iso replace", function() { - iso.replace("Updated", user) - }) - .add("direct replace", function() { - // oxlint-disable-next-line no-new - new User({ - ...user, - profile: { - ...user.profile, - address: { - ...user.profile.address, - street: "Updated" + .add("iso get", batch(() => iso.get(user))) + .add("optic get", batch(() => optic.get(user))) + .add("direct get", batch(() => user.profile.address.street)) + .add("iso replace", batch(() => iso.replace("Updated", user))) + .add( + "direct replace", + batch(() => + new User({ + ...user, + profile: { + ...user.profile, + address: { + ...user.profile.address, + street: "Updated" + } } - } - }) - }) + }) + ) + ) await bench.run() -console.table(bench.table()) +if (sink === undefined) { + throw new Error("Benchmark did not run") +} + +console.table(bench.table((task) => { + const result = task.result + if (result?.state === "errored") { + return { + "Task name": task.name, + Error: result.error.message + } + } + if (result?.state !== "completed") { + return { + "Task name": task.name, + State: result?.state ?? "missing result" + } + } + const latencyToNs = (value: number) => value * 1_000_000 / batchSize + return { + "Task name": task.name, + "Latency avg (ns/op)": latencyToNs(result.latency.mean).toFixed(2), + "Latency med (ns/op)": latencyToNs(result.latency.p50).toFixed(2), + "Latency RME": `${result.latency.rme.toFixed(2)}%`, + "Throughput avg (ops/s)": Math.round(result.throughput.mean * batchSize), + Samples: result.latency.samplesCount + } +})) diff --git a/repos/effect/packages/effect/benchmark/schema/array.ts b/repos/effect/packages/effect/benchmark/schema/array.ts deleted file mode 100644 index b4596fabe8..0000000000 --- a/repos/effect/packages/effect/benchmark/schema/array.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { type } from "arktype" -import { Schema } from "effect" -import { Bench } from "tinybench" -import * as v from "valibot" -import { z } from "zod/v4-mini" - -/* -┌─────────┬──────────────────┬──────────────────┬──────────────────┬────────────────────────┬────────────────────────┬──────────┐ -│ (index) │ Task name │ Latency avg (ns) │ Latency med (ns) │ Throughput avg (ops/s) │ Throughput med (ops/s) │ Samples │ -├─────────┼──────────────────┼──────────────────┼──────────────────┼────────────────────────┼────────────────────────┼──────────┤ -│ 0 │ 'Schema (good)' │ '218.06 ± 1.54%' │ '208.00 ± 0.00' │ '4904782 ± 0.01%' │ '4807692 ± 0' │ 4585950 │ -│ 1 │ 'Schema (bad)' │ '362.29 ± 3.26%' │ '292.00 ± 1.00' │ '3199501 ± 0.01%' │ '3424658 ± 11769' │ 2760191 │ -│ 2 │ 'Valibot (good)' │ '67.50 ± 3.42%' │ '42.00 ± 1.00' │ '18944492 ± 0.02%' │ '23809524 ± 580720' │ 14872899 │ -│ 3 │ 'Valibot (bad)' │ '129.11 ± 0.74%' │ '125.00 ± 0.00' │ '8244804 ± 0.01%' │ '8000000 ± 0' │ 7745459 │ -│ 4 │ 'Arktype (good)' │ '25.76 ± 6.47%' │ '41.00 ± 1.00' │ '30181117 ± 0.01%' │ '24390244 ± 580720' │ 38824544 │ -│ 5 │ 'Arktype (bad)' │ '1837.1 ± 2.51%' │ '1750.0 ± 41.00' │ '567189 ± 0.02%' │ '571429 ± 13393' │ 544325 │ -│ 6 │ 'Zod (good)' │ '43.74 ± 4.91%' │ '42.00 ± 0.00' │ '23500345 ± 0.00%' │ '23809524 ± 0' │ 22863784 │ -│ 7 │ 'Zod (bad)' │ '5205.5 ± 0.76%' │ '4958.0 ± 83.00' │ '199317 ± 0.04%' │ '201694 ± 3360' │ 192106 │ -└─────────┴──────────────────┴──────────────────┴──────────────────┴────────────────────────┴────────────────────────┴──────────┘ -*/ - -const bench = new Bench() - -const schema = Schema.Array(Schema.String) - -const valibot = v.array(v.string()) - -const arktype = type("string[]") - -const zod = z.array(z.string()) - -const good = ["a", "b"] -const bad = ["a", 1] - -const decodeUnknownExit = Schema.decodeUnknownExit(schema) - -// console.log(decodeUnknownExit(good)) -// console.log(decodeUnknownExit(bad)) -// console.log(v.safeParse(valibot, good)) -// console.log(v.safeParse(valibot, bad)) -// console.log(arktype(good)) -// console.log(arktype(bad)) -// console.log(zod.safeParse(good)) -// console.log(zod.safeParse(bad)) - -bench - .add("Schema (good)", function() { - decodeUnknownExit(good) - }) - .add("Schema (bad)", function() { - decodeUnknownExit(bad) - }) - .add("Valibot (good)", function() { - v.safeParse(valibot, good) - }) - .add("Valibot (bad)", function() { - v.safeParse(valibot, bad) - }) - .add("Arktype (good)", function() { - arktype(good) - }) - .add("Arktype (bad)", function() { - arktype(bad) - }) - .add("Zod (good)", function() { - zod.safeParse(good) - }) - .add("Zod (bad)", function() { - zod.safeParse(bad) - }) - -await bench.run() - -console.table(bench.table()) diff --git a/repos/effect/packages/effect/benchmark/schema/filter.ts b/repos/effect/packages/effect/benchmark/schema/filter.ts deleted file mode 100644 index 6ff05e83ab..0000000000 --- a/repos/effect/packages/effect/benchmark/schema/filter.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { type } from "arktype" -import { Schema } from "effect" -import { Bench } from "tinybench" -import * as v from "valibot" -import { z } from "zod/v4-mini" - -/* -┌─────────┬──────────────────┬──────────────────┬──────────────────┬────────────────────────┬────────────────────────┬──────────┐ -│ (index) │ Task name │ Latency avg (ns) │ Latency med (ns) │ Throughput avg (ops/s) │ Throughput med (ops/s) │ Samples │ -├─────────┼──────────────────┼──────────────────┼──────────────────┼────────────────────────┼────────────────────────┼──────────┤ -│ 0 │ 'Schema (good)' │ '129.33 ± 0.60%' │ '125.00 ± 0.00' │ '8158156 ± 0.01%' │ '8000000 ± 0' │ 7732112 │ -│ 1 │ 'Schema (bad)' │ '239.58 ± 1.44%' │ '209.00 ± 1.00' │ '4413764 ± 0.01%' │ '4784689 ± 23003' │ 4174352 │ -│ 2 │ 'Valibot (good)' │ '49.81 ± 1.01%' │ '42.00 ± 0.00' │ '22751446 ± 0.01%' │ '23809524 ± 1' │ 20075175 │ -│ 3 │ 'Valibot (bad)' │ '70.52 ± 1.11%' │ '83.00 ± 1.00' │ '16762925 ± 0.02%' │ '12048193 ± 143431' │ 14179523 │ -│ 4 │ 'Arktype (good)' │ '23.37 ± 0.14%' │ '41.00 ± 1.00' │ '32327042 ± 0.01%' │ '24390244 ± 580720' │ 42787691 │ -│ 5 │ 'Arktype (bad)' │ '1361.2 ± 2.65%' │ '1333.0 ± 41.00' │ '757323 ± 0.01%' │ '750188 ± 23806' │ 734659 │ -│ 6 │ 'Zod (good)' │ '44.10 ± 0.72%' │ '42.00 ± 0.00' │ '23479006 ± 0.00%' │ '23809524 ± 0' │ 22674716 │ -│ 7 │ 'Zod (bad)' │ '5005.9 ± 1.50%' │ '4834.0 ± 83.00' │ '204249 ± 0.03%' │ '206868 ± 3492' │ 199767 │ -└─────────┴──────────────────┴──────────────────┴──────────────────┴────────────────────────┴────────────────────────┴──────────┘ -*/ - -const bench = new Bench() - -const schema = Schema.String.check(Schema.isNonEmpty()) - -const valibot = v.pipe(v.string(), v.nonEmpty()) - -const arktype = type("string > 0") - -const zod = z.string().check(z.minLength(1)) - -const good = "a" -const bad = "" - -const decodeUnknownExit = Schema.decodeUnknownExit(schema) - -// console.log(decodeUnknownExit(good)) -// console.log(decodeUnknownExit(bad)) -// console.log(v.safeParse(valibot, good)) -// console.log(v.safeParse(valibot, bad)) -// console.log(arktype(good)) -// console.log(arktype(bad)) -// console.log(zod.safeParse(good)) -// console.log(zod.safeParse(bad)) - -bench - .add("Schema (good)", function() { - decodeUnknownExit(good) - }) - .add("Schema (bad)", function() { - decodeUnknownExit(bad) - }) - .add("Valibot (good)", function() { - v.safeParse(valibot, good) - }) - .add("Valibot (bad)", function() { - v.safeParse(valibot, bad) - }) - .add("Arktype (good)", function() { - arktype(good) - }) - .add("Arktype (bad)", function() { - arktype(bad) - }) - .add("Zod (good)", function() { - zod.safeParse(good) - }) - .add("Zod (bad)", function() { - zod.safeParse(bad) - }) - -await bench.run() - -console.table(bench.table()) diff --git a/repos/effect/packages/effect/benchmark/schema/object.ts b/repos/effect/packages/effect/benchmark/schema/object.ts deleted file mode 100644 index 7e164daefa..0000000000 --- a/repos/effect/packages/effect/benchmark/schema/object.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { type } from "arktype" -import { Schema, SchemaParser } from "effect" -import { Bench } from "tinybench" -import * as v from "valibot" -import { z } from "zod/v4-mini" - -/* -┌─────────┬──────────────────┬──────────────────┬──────────────────┬────────────────────────┬────────────────────────┬──────────┐ -│ (index) │ Task name │ Latency avg (ns) │ Latency med (ns) │ Throughput avg (ops/s) │ Throughput med (ops/s) │ Samples │ -├─────────┼──────────────────┼──────────────────┼──────────────────┼────────────────────────┼────────────────────────┼──────────┤ -│ 0 │ 'Schema (good)' │ '124.63 ± 0.14%' │ '125.00 ± 0.00' │ '8434862 ± 0.01%' │ '8000000 ± 0' │ 8023546 │ -│ 1 │ 'Schema (bad)' │ '203.36 ± 6.27%' │ '208.00 ± 41.00' │ '5319764 ± 0.01%' │ '4807692 ± 807692' │ 4917416 │ -│ 2 │ 'Valibot (good)' │ '48.91 ± 0.14%' │ '42.00 ± 0.00' │ '22152363 ± 0.01%' │ '23809524 ± 1' │ 20444356 │ -│ 3 │ 'Valibot (bad)' │ '101.98 ± 0.88%' │ '84.00 ± 1.00' │ '10439854 ± 0.01%' │ '11904762 ± 143431' │ 9806230 │ -│ 4 │ 'Arktype (good)' │ '14.57 ± 1.46%' │ '0.00 ± 0.00' │ '53567410 ± 0.01%' │ '68616078 ± 0' │ 68616080 │ -│ 5 │ 'Arktype (bad)' │ '2001.6 ± 7.05%' │ '1750.0 ± 41.00' │ '554199 ± 0.03%' │ '571429 ± 13393' │ 499602 │ -│ 6 │ 'Zod (good)' │ '33.75 ± 6.13%' │ '41.00 ± 1.00' │ '25375235 ± 0.00%' │ '24390240 ± 580716' │ 29633666 │ -│ 7 │ 'Zod (bad)' │ '5392.2 ± 3.11%' │ '5167.0 ± 42.00' │ '190866 ± 0.03%' │ '193536 ± 1586' │ 185454 │ -└─────────┴──────────────────┴──────────────────┴──────────────────┴────────────────────────┴────────────────────────┴──────────┘ -*/ - -const bench = new Bench() - -const schema = Schema.Struct({ - a: Schema.String -}) - -const valibot = v.object({ - a: v.string() -}) - -const arktype = type({ - a: "string" -}) - -const zod = z.object({ - a: z.string() -}) - -const good = { a: "a" } -const bad = { a: 1 } - -const decodeUnknownExit = SchemaParser.decodeUnknownExit(schema) - -// console.log(decodeUnknownExit(good)) -// console.log(decodeUnknownExit(bad)) -// console.log(v.safeParse(valibot, good)) -// console.log(v.safeParse(valibot, bad)) -// console.log(arktype(good)) -// console.log(arktype(bad)) -// console.log(zod.safeParse(good)) -// console.log(zod.safeParse(bad)) - -bench - .add("Schema (good)", function() { - decodeUnknownExit(good) - }) - .add("Schema (bad)", function() { - decodeUnknownExit(bad) - }) - .add("Valibot (good)", function() { - v.safeParse(valibot, good) - }) - .add("Valibot (bad)", function() { - v.safeParse(valibot, bad) - }) - .add("Arktype (good)", function() { - arktype(good) - }) - .add("Arktype (bad)", function() { - arktype(bad) - }) - .add("Zod (good)", function() { - zod.safeParse(good) - }) - .add("Zod (bad)", function() { - zod.safeParse(bad) - }) - -await bench.run() - -console.table(bench.table()) diff --git a/repos/effect/packages/effect/benchmark/schema/tagged-union.ts b/repos/effect/packages/effect/benchmark/schema/tagged-union.ts deleted file mode 100644 index 2250ea4c07..0000000000 --- a/repos/effect/packages/effect/benchmark/schema/tagged-union.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { Array as RA, Schema } from "effect" -import { Bench } from "tinybench" - -/* -┌─────────┬────────────────────┬──────────────────┬──────────────────┬────────────────────────┬────────────────────────┬─────────┐ -│ (index) │ Task name │ Latency avg (ns) │ Latency med (ns) │ Throughput avg (ops/s) │ Throughput med (ops/s) │ Samples │ -├─────────┼────────────────────┼──────────────────┼──────────────────┼────────────────────────┼────────────────────────┼─────────┤ -│ 0 │ 'Schema (good)' │ '488.43 ± 0.48%' │ '459.00 ± 1.00' │ '2119495 ± 0.01%' │ '2178649 ± 4757' │ 2047396 │ -│ 1 │ 'Schema (bad)' │ '629.17 ± 0.29%' │ '584.00 ± 1.00' │ '1645689 ± 0.01%' │ '1712329 ± 2937' │ 1589395 │ -│ 2 │ 'candidate (good)' │ '327.20 ± 0.27%' │ '292.00 ± 1.00' │ '3205291 ± 0.01%' │ '3424658 ± 11769' │ 3056264 │ -│ 3 │ 'candidate (bad)' │ '449.52 ± 2.41%' │ '417.00 ± 0.00' │ '2372897 ± 0.01%' │ '2398082 ± 0' │ 2224610 │ -└─────────┴────────────────────┴──────────────────┴──────────────────┴────────────────────────┴────────────────────────┴─────────┘ -*/ - -const bench = new Bench({ time: 1000 }) - -const n = 100 -const f = (i: number) => - Schema.Struct({ - kind: Schema.Literal(i), - a: Schema.String, - b: Schema.Number, - c: Schema.Boolean - }) -const members = RA.makeBy(n, f) - -const schema = Schema.Union(members) - -const candidate = f(n - 1) - -const good = { - kind: n - 1, - a: "a", - b: 1, - c: true -} - -const bad = { - kind: n - 1, - a: "a", - b: 1, - c: "c" -} - -const decodeUnknownExit = Schema.decodeUnknownExit(schema) -const decodeUnknownExitCandidate = Schema.decodeUnknownExit(candidate) - -// console.log(decodeUnknownExit(good)) -// console.log(decodeUnknownExit(bad)) -// console.log(decodeUnknownExitCandidate(good)) -// console.log(decodeUnknownExitCandidate(bad)) - -bench - .add("Schema (good)", function() { - decodeUnknownExit(good) - }) - .add("Schema (bad)", function() { - decodeUnknownExit(bad) - }) - .add("candidate (good)", function() { - decodeUnknownExitCandidate(good) - }) - .add("candidate (bad)", function() { - decodeUnknownExitCandidate(bad) - }) - -await bench.run() - -console.table(bench.table()) diff --git a/repos/effect/packages/effect/benchmark/schema/transformation.ts b/repos/effect/packages/effect/benchmark/schema/transformation.ts deleted file mode 100644 index 40cae620f0..0000000000 --- a/repos/effect/packages/effect/benchmark/schema/transformation.ts +++ /dev/null @@ -1,91 +0,0 @@ -import { Schema, SchemaTransformation } from "effect" -import { Bench } from "tinybench" -import { z } from "zod" - -/* -┌─────────┬─────────────────┬──────────────────┬───────────────────┬────────────────────────┬────────────────────────┬─────────┐ -│ (index) │ Task name │ Latency avg (ns) │ Latency med (ns) │ Throughput avg (ops/s) │ Throughput med (ops/s) │ Samples │ -├─────────┼─────────────────┼──────────────────┼───────────────────┼────────────────────────┼────────────────────────┼─────────┤ -│ 0 │ 'Schema (good)' │ '1097.8 ± 1.12%' │ '1042.0 ± 1.00' │ '949296 ± 0.01%' │ '959693 ± 922' │ 910953 │ -│ 1 │ 'Zod (good)' │ '267.92 ± 4.46%' │ '208.00 ± 0.00' │ '4505289 ± 0.02%' │ '4807692 ± 0' │ 3732515 │ -│ 2 │ 'Schema (bad)' │ '683.49 ± 1.54%' │ '625.00 ± 0.00' │ '1593775 ± 0.01%' │ '1600000 ± 0' │ 1463090 │ -│ 3 │ 'Zod (bad)' │ '8172.9 ± 4.43%' │ '6417.0 ± 125.00' │ '152563 ± 0.07%' │ '155836 ± 3096' │ 122357 │ -└─────────┴─────────────────┴──────────────────┴───────────────────┴────────────────────────┴────────────────────────┴─────────┘ -*/ - -const bench = new Bench() - -const schema = Schema.Struct({ - a: Schema.String, - id: Schema.String, - c: Schema.Number.check(Schema.isGreaterThanOrEqualTo(0)), - d: Schema.String -}).pipe(Schema.decodeTo( - Schema.Struct({ - a: Schema.String, - b: Schema.Struct({ id: Schema.String }), - c: Schema.Number.check(Schema.isGreaterThanOrEqualTo(0)), - d: Schema.String - }), - SchemaTransformation.transform({ - decode: ({ id, ...v }) => ({ ...v, b: { id } }), - encode: ({ b: { id }, ...v }) => ({ ...v, id }) - }) -)) - -const zod = z.codec( - z.object({ - a: z.string(), - id: z.string(), - c: z.number().check(z.nonnegative()), - d: z.string() - }), - z.object({ - a: z.string(), - b: z.object({ id: z.string() }), - c: z.number().check(z.nonnegative()), - d: z.string() - }), - { - decode: ({ id, ...v }) => ({ ...v, b: { id } }), - encode: ({ b: { id }, ...v }) => ({ ...v, id }) - } -) - -const good = { - a: "a", - id: "id", - c: 1, - d: "d" -} -const bad = { - a: "a", - id: "id", - c: -1, - d: "d" -} - -const decodeUnknownExit = Schema.decodeUnknownExit(schema) - -// console.log(decodeUnknownExit(good)) -// console.log(String(decodeUnknownExit(bad))) -// console.log(zod.safeDecode(good)) -// console.log(zod.safeDecode(bad)) - -bench - .add("Schema (good)", function() { - decodeUnknownExit(good) - }) - .add("Zod (good)", function() { - zod.safeDecode(good) - }) - .add("Schema (bad)", function() { - decodeUnknownExit(bad) - }) - .add("Zod (bad)", function() { - zod.safeDecode(bad) - }) - -await bench.run() - -console.table(bench.table()) diff --git a/repos/effect/packages/effect/docgen.json b/repos/effect/packages/effect/docgen.json deleted file mode 100644 index 99d82864a5..0000000000 --- a/repos/effect/packages/effect/docgen.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "$schema": "../../node_modules/@effect/docgen/schema.json", - "exclude": ["src/internal/**/*.ts", "src/unstable/**/internal/**/*.ts", "src/schema/StandardSchema.ts"], - "srcLink": "https://github.com/Effect-TS/effect-smol/tree/main/packages/effect/src/", - "tscExecutable": "tsgo", - "examplesCompilerOptions": { - "noEmit": true, - "strict": true, - "skipLibCheck": true, - "moduleResolution": "Bundler", - "module": "ES2022", - "target": "ES2022", - "lib": ["ESNext", "DOM", "DOM.Iterable"], - "rewriteRelativeImportExtensions": true, - "allowImportingTsExtensions": true, - "paths": { - "effect": ["../../../effect/src/index.js"], - "effect/*": ["../../../effect/src/*.js"], - "@effect/platform-node": ["../../../platform-node/src/index.js"], - "@effect/platform-node/*": ["../../../platform-node/src/*.js"] - }, - "plugins": [ - { - "name": "@effect/language-service", - "includeSuggestionsInTsc": false, - "diagnosticSeverity": { - "unusedDirective": "off", - "floatingEffect": "off", - "multipleEffectProvide": "off", - "globalErrorInEffectFailure": "off", - "unknownInEffectCatch": "off", - "globalErrorInEffectCatch": "off", - "missingReturnYieldStar": "off" - } - } - ] - } -} diff --git a/repos/effect/packages/effect/package.json b/repos/effect/packages/effect/package.json index bce406a50b..b4e32f4507 100644 --- a/repos/effect/packages/effect/package.json +++ b/repos/effect/packages/effect/package.json @@ -1,17 +1,17 @@ { "name": "effect", "type": "module", - "version": "4.0.0-beta.90", + "version": "4.0.0-beta.107", "license": "MIT", "description": "The missing standard library for TypeScript, for writing production-grade software.", "homepage": "https://effect.website", "repository": { "type": "git", - "url": "https://github.com/Effect-TS/effect-smol.git", + "url": "https://github.com/Effect-TS/effect.git", "directory": "packages/effect" }, "bugs": { - "url": "https://github.com/Effect-TS/effect-smol/issues" + "url": "https://github.com/Effect-TS/effect/issues" }, "tags": [ "typescript", @@ -49,9 +49,10 @@ "./unstable/workflow": "./src/unstable/workflow/index.ts", "./unstable/workers": "./src/unstable/workers/index.ts", "./*": "./src/*.ts", - "./internal/*": null, "./unstable/cli/internal/*": null, "./unstable/cluster/internal/*": null, + "./internal/*": null, + "./index": null, "./*/index": null }, "files": [ @@ -59,7 +60,10 @@ "dist/**/*.js", "dist/**/*.js.map", "dist/**/*.d.ts", - "dist/**/*.d.ts.map" + "dist/**/*.d.ts.map", + "AGENTS.md", + "CLAUDE.md", + "ai-docs/**/*" ], "publishConfig": { "access": "public", @@ -90,38 +94,30 @@ "./internal/*": null, "./unstable/cli/internal/*": null, "./unstable/cluster/internal/*": null, + "./index": null, "./*/index": null } }, "scripts": { "codegen": "effect-utils codegen", "build": "tsc -b tsconfig.json && pnpm babel", - "build:tsgo": "tsgo -b tsconfig.json && pnpm babel", "babel": "babel dist --plugins annotate-pure-calls --out-dir dist --source-maps", - "check": "tsc -b tsconfig.json", - "test": "vitest --sequence.concurrent=false", - "coverage": "vitest --run --coverage --sequence.concurrent=false" + "check": "tsc -b tsconfig.json" }, "devDependencies": { - "@types/ini": "^4.1.1", - "@types/node": "^25.7.0", + "@types/node": "^26.1.2", "ajv": "^8.20.0", - "arktype": "^2.2.0", + "ajv-draft-04": "^1.0.0", "ast-types": "^0.14.2", - "immer": "^11.1.8", - "tinybench": "^6.0.1", - "valibot": "^1.4.0" + "immer": "^11.1.11", + "tinybench": "^6.0.2", + "valibot": "^1.4.2" }, "dependencies": { "@standard-schema/spec": "^1.1.0", - "fast-check": "^4.8.0", - "find-my-way-ts": "^0.1.6", - "ini": "^7.0.0", + "fast-check": "^4.9.0", "kubernetes-types": "^1.30.0", - "msgpackr": "^2.0.1", - "multipasta": "^0.2.7", - "toml": "^4.1.1", - "uuid": "^14.0.0", - "yaml": "^2.9.0" + "msgpackr": "^2.0.4", + "uuid": "^14.0.1" } } diff --git a/repos/effect/packages/effect/runtimeperf/README.md b/repos/effect/packages/effect/runtimeperf/README.md new file mode 100644 index 0000000000..8ff0458bbd --- /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 0000000000..7c2cf346ea --- /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 0000000000..5118ac2f41 --- /dev/null +++ b/repos/effect/packages/effect/runtimeperf/config.json @@ -0,0 +1,972 @@ +{ + "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 + } + ] + }, + { + "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 0000000000..5635ebe8dd --- /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 0000000000..e1f38f1636 --- /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 0000000000..4f4be2068f --- /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 0000000000..af9d405834 --- /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 0000000000..b21c0dd2d6 --- /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 0000000000..fdff6a1b55 --- /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 0000000000..81e56f4821 --- /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 0000000000..2a84cab07f --- /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 0000000000..8ed02deaf1 --- /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 0000000000..2f62d5ac0f --- /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 0000000000..44f7d64da8 --- /dev/null +++ b/repos/effect/packages/effect/runtimeperf/suites/schema/fixtures/comparison.ts @@ -0,0 +1,71 @@ +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) 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 0000000000..e4331c5103 --- /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 0000000000..e43831bc84 --- /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 0000000000..4114ab6f1d --- /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 0000000000..72cc79f527 --- /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 0000000000..a146ac9dbd --- /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 0000000000..19bc6d1ae0 --- /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 index 32f6a566e5..c35cf612a9 100644 --- a/repos/effect/packages/effect/src/Array.ts +++ b/repos/effect/packages/effect/src/Array.ts @@ -13,9 +13,11 @@ 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" @@ -36,11 +38,10 @@ import type { NoInfer, TupleOf } from "./Types.ts" * * **Example** (Accessing the Array constructor) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * const arr = new Array.Array(3) - * console.log(arr) // [undefined, undefined, undefined] + * Array.Array === globalThis.Array // => true * ``` * * @category constructors @@ -51,7 +52,7 @@ export const Array = globalThis.Array /** * Type lambda for `ReadonlyArray`, used for higher-kinded type operations. * - * @category type lambdas + * @category utility types * @since 2.0.0 */ export interface ReadonlyArrayTypeLambda extends TypeLambda { @@ -68,11 +69,13 @@ export interface ReadonlyArrayTypeLambda extends TypeLambda { * * **Example** (Typing a non-empty array) * - * ```ts + * ```ts import.meta.vitest * import type { Array } from "effect" * * const nonEmpty: Array.NonEmptyReadonlyArray = [1, 2, 3] * const head: number = nonEmpty[0] // guaranteed to exist + * + * head // => 1 * ``` * * @see {@link NonEmptyArray} — mutable counterpart @@ -99,11 +102,13 @@ export type NonEmptyReadonlyArray = readonly [A, ...Array] * * **Example** (Typing a mutable non-empty array) * - * ```ts + * ```ts import.meta.vitest * import type { Array } from "effect" * * const nonEmpty: Array.NonEmptyArray = [1, 2, 3] * nonEmpty.push(4) + * + * nonEmpty // => [1, 2, 3, 4] * ``` * * @see {@link NonEmptyReadonlyArray} — readonly counterpart @@ -128,11 +133,10 @@ export type NonEmptyArray = [A, ...Array] * * **Example** (Creating an array from values) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * const result = Array.make(1, 2, 3) - * console.log(result) // [1, 2, 3] + * Array.make(1, 2, 3) // => [1, 2, 3] * ``` * * @see {@link of} — create a single-element array @@ -158,11 +162,10 @@ export const make = >( * * **Example** (Allocating a fixed-size array) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * const result = Array.allocate(3) - * console.log(result.length) // 3 + * Array.allocate(3).length // => 3 * ``` * * @see {@link makeBy} — create an array by computing each element @@ -187,11 +190,10 @@ export const allocate = (n: number): Array => new Arra * * **Example** (Generating values from indices) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * const result = Array.makeBy(5, (n) => n * 2) - * console.log(result) // [0, 2, 4, 6, 8] + * Array.makeBy(5, (n) => n * 2) // => [0, 2, 4, 6, 8] * ``` * * @see {@link range} — create a range of integers @@ -226,11 +228,10 @@ export const makeBy: { * * **Example** (Creating a range) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * const result = Array.range(1, 3) - * console.log(result) // [1, 2, 3] + * Array.range(1, 3) // => [1, 2, 3] * ``` * * @see {@link makeBy} — generate values from a function @@ -256,11 +257,10 @@ export const range = (start: number, end: number): NonEmptyArray => * * **Example** (Repeating a value) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * const result = Array.replicate("a", 3) - * console.log(result) // ["a", "a", "a"] + * Array.replicate("a", 3) // => ["a", "a", "a"] * ``` * * @see {@link makeBy} — vary values based on index @@ -288,11 +288,10 @@ export const replicate: { * * **Example** (Converting a Set to an array) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * const result = Array.fromIterable(new Set([1, 2, 3])) - * console.log(result) // [1, 2, 3] + * Array.fromIterable(new Set([1, 2, 3])) // => [1, 2, 3] * ``` * * @see {@link ensure} — wrap a single value or return an existing array @@ -320,11 +319,11 @@ export const fromIterable = (collection: Iterable): Array => * * **Example** (Normalizing input) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.ensure("a")) // ["a"] - * console.log(Array.ensure(["a", "b", "c"])) // ["a", "b", "c"] + * Array.ensure("a") // => ["a"] + * Array.ensure(["a", "b", "c"]) // => ["a", "b", "c"] * ``` * * @see {@link of} — always wrap in a single-element array @@ -350,11 +349,10 @@ export const ensure = (self: ReadonlyArray | A): Array => Array.isArray * * **Example** (Converting a record to entries) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * const result = Array.fromRecord({ a: 1, b: 2, c: 3 }) - * console.log(result) // [["a", 1], ["b", 2], ["c", 3]] + * Array.fromRecord({ a: 1, b: 2, c: 3 }) // => [["a", 1], ["b", 2], ["c", 3]] * ``` * * @see {@link Record.toEntries} the equivalent function from the Record module @@ -374,11 +372,11 @@ export const fromRecord: (self: Readonly>) => * * **Example** (Converting an Option to an array) * - * ```ts + * ```ts import.meta.vitest * import { Array, Option } from "effect" * - * console.log(Array.fromOption(Option.some(1))) // [1] - * console.log(Array.fromOption(Option.none())) // [] + * Array.fromOption(Option.some(1)) // => [1] + * Array.fromOption(Option.none()) // => [] * ``` * * @see {@link getSomes} — extract `Some` values from an array of Options @@ -402,15 +400,16 @@ export const fromOption: (self: Option.Option) => Array = Option.toArra * * **Example** (Branching on emptiness) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * * const describe = Array.match({ * onEmpty: () => "empty", * onNonEmpty: ([head, ...tail]) => `head: ${head}, tail: ${tail.length}` * }) - * console.log(describe([])) // "empty" - * console.log(describe([1, 2, 3])) // "head: 1, tail: 2" + * + * describe([]) // => "empty" + * describe([1, 2, 3]) // => "head: 1, tail: 2" * ``` * * @see {@link matchLeft} — destructures into head + tail @@ -456,15 +455,16 @@ export const match: { * * **Example** (Destructuring head and tail) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * * const matchLeft = Array.matchLeft({ * onEmpty: () => "empty", * onNonEmpty: (head, tail) => `head: ${head}, tail: ${tail.length}` * }) - * console.log(matchLeft([])) // "empty" - * console.log(matchLeft([1, 2, 3])) // "head: 1, tail: 2" + * + * matchLeft([]) // => "empty" + * matchLeft([1, 2, 3]) // => "head: 1, tail: 2" * ``` * * @see {@link match} — receives the full non-empty array @@ -510,15 +510,16 @@ export const matchLeft: { * * **Example** (Destructuring init and last) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * * const matchRight = Array.matchRight({ * onEmpty: () => "empty", * onNonEmpty: (init, last) => `init: ${init.length}, last: ${last}` * }) - * console.log(matchRight([])) // "empty" - * console.log(matchRight([1, 2, 3])) // "init: 2, last: 3" + * + * matchRight([]) // => "empty" + * matchRight([1, 2, 3]) // => "init: 2, last: 3" * ``` * * @see {@link match} — receives the full non-empty array @@ -562,11 +563,10 @@ export const matchRight: { * * **Example** (Prepending an element) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * const result = Array.prepend([2, 3, 4], 1) - * console.log(result) // [1, 2, 3, 4] + * Array.prepend([2, 3, 4], 1) // => [1, 2, 3, 4] * ``` * * @see {@link append} — add to the end @@ -593,11 +593,10 @@ export const prepend: { * * **Example** (Prepending multiple elements) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * const result = Array.prependAll([2, 3], [0, 1]) - * console.log(result) // [0, 1, 2, 3] + * Array.prependAll([2, 3], [0, 1]) // => [0, 1, 2, 3] * ``` * * @see {@link prepend} — add a single element to the front @@ -628,11 +627,10 @@ export const prependAll: { * * **Example** (Appending an element) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * const result = Array.append([1, 2, 3], 4) - * console.log(result) // [1, 2, 3, 4] + * Array.append([1, 2, 3], 4) // => [1, 2, 3, 4] * ``` * * @see {@link prepend} — add to the front @@ -660,11 +658,10 @@ export const append: { * * **Example** (Concatenating arrays) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * const result = Array.appendAll([1, 2], [3, 4]) - * console.log(result) // [1, 2, 3, 4] + * Array.appendAll([1, 2], [3, 4]) // => [1, 2, 3, 4] * ``` * * @see {@link append} — add a single element to the end @@ -700,11 +697,10 @@ export const appendAll: { * * **Example** (Running totals) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * const result = Array.scan([1, 2, 3, 4], 0, (acc, value) => acc + value) - * console.log(result) // [0, 1, 3, 6, 10] + * Array.scan([1, 2, 3, 4], 0, (acc, value) => acc + value) // => [0, 1, 3, 6, 10] * ``` * * @see {@link scanRight} — right-to-left scan @@ -741,11 +737,10 @@ export const scan: { * * **Example** (Scanning running totals in reverse) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * const result = Array.scanRight([1, 2, 3, 4], 0, (acc, value) => acc + value) - * console.log(result) // [10, 9, 7, 4, 0] + * Array.scanRight([1, 2, 3, 4], 0, (acc, value) => acc + value) // => [10, 9, 7, 4, 0] * ``` * * @see {@link scan} — left-to-right scan @@ -781,11 +776,11 @@ export const scanRight: { * * **Example** (Type-guarding an unknown value) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.isArray(null)) // false - * console.log(Array.isArray([1, 2, 3])) // true + * Array.isArray(null) // => false + * Array.isArray([1, 2, 3]) // => true * ``` * * @see {@link isArrayEmpty} — check for an empty array @@ -804,11 +799,11 @@ export const isArray: { * * **Example** (Checking for an empty array) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.isArrayEmpty([])) // true - * console.log(Array.isArrayEmpty([1, 2, 3])) // false + * Array.isArrayEmpty([]) // => true + * Array.isArrayEmpty([1, 2, 3]) // => false * ``` * * @see {@link isReadonlyArrayEmpty} — readonly variant @@ -824,11 +819,11 @@ export const isArrayEmpty = (self: Array): self is [] => self.length === 0 * * **Example** (Checking for an empty readonly array) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.isReadonlyArrayEmpty([])) // true - * console.log(Array.isReadonlyArrayEmpty([1, 2, 3])) // false + * Array.isReadonlyArrayEmpty([]) // => true + * Array.isReadonlyArrayEmpty([1, 2, 3]) // => false * ``` * * @see {@link isArrayEmpty} — mutable variant @@ -850,11 +845,11 @@ export const isReadonlyArrayEmpty: (self: ReadonlyArray) => self is readon * * **Example** (Checking for a non-empty array) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.isArrayNonEmpty([])) // false - * console.log(Array.isArrayNonEmpty([1, 2, 3])) // true + * Array.isArrayNonEmpty([]) // => false + * Array.isArrayNonEmpty([1, 2, 3]) // => true * ``` * * @see {@link isReadonlyArrayNonEmpty} — readonly variant @@ -876,11 +871,11 @@ export const isArrayNonEmpty: (self: Array) => self is NonEmptyArray = * * **Example** (Checking for a non-empty readonly array) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.isReadonlyArrayNonEmpty([])) // false - * console.log(Array.isReadonlyArrayNonEmpty([1, 2, 3])) // true + * Array.isReadonlyArrayNonEmpty([]) // => false + * Array.isReadonlyArrayNonEmpty([1, 2, 3]) // => true * ``` * * @see {@link isArrayNonEmpty} — mutable variant @@ -901,10 +896,10 @@ export const isReadonlyArrayNonEmpty: (self: ReadonlyArray) => self is Non * * **Example** (Getting the length) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.length([1, 2, 3])) // 3 + * Array.length([1, 2, 3]) // => 3 * ``` * * @category getters @@ -914,7 +909,7 @@ export const length = (self: ReadonlyArray): number => self.length /** @internal */ export function isOutOfBounds(i: number, as: ReadonlyArray): boolean { - return i < 0 || i >= as.length + return !Number.isFinite(i) || i < 0 || i >= as.length } const clamp = (i: number, as: ReadonlyArray): number => Math.floor(Math.min(Math.max(0, i), as.length)) @@ -934,11 +929,11 @@ const clamp = (i: number, as: ReadonlyArray): number => Math.floor(Math.mi * * **Example** (Accessing indexes safely) * - * ```ts - * import { Array } from "effect" + * ```ts import.meta.vitest + * import { Array, Option } from "effect" * - * console.log(Array.get([1, 2, 3], 1)) // Some(2) - * console.log(Array.get([1, 2, 3], 10)) // None + * Array.get([1, 2, 3], 1) // => Option.some(2) + * Array.get([1, 2, 3], 10) // => Option.none() * ``` * * @see {@link getUnsafe} for indexed access that throws when the index is out of bounds @@ -971,10 +966,10 @@ export const get: { * * **Example** (Accessing indexes unsafely) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.getUnsafe([1, 2, 3], 1)) // 2 + * Array.getUnsafe([1, 2, 3], 1) // => 2 * // Array.getUnsafe([1, 2, 3], 10) // throws Error * ``` * @@ -1008,11 +1003,10 @@ export const getUnsafe: { * * **Example** (Destructuring head and tail) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * const result = Array.unprepend([1, 2, 3, 4]) - * console.log(result) // [1, [2, 3, 4]] + * Array.unprepend([1, 2, 3, 4]) // => [1, [2, 3, 4]] * ``` * * @see {@link unappend} for splitting a non-empty array into init and last @@ -1041,11 +1035,10 @@ export const unprepend = ( * * **Example** (Destructuring init and last) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * const result = Array.unappend([1, 2, 3, 4]) - * console.log(result) // [[1, 2, 3], 4] + * Array.unappend([1, 2, 3, 4]) // => [[1, 2, 3], 4] * ``` * * @see {@link unprepend} for splitting a non-empty array into head and tail @@ -1069,11 +1062,11 @@ export const unappend = ( * * **Example** (Getting the first element) * - * ```ts - * import { Array } from "effect" + * ```ts import.meta.vitest + * import { Array, Option } from "effect" * - * console.log(Array.head([1, 2, 3])) // Some(1) - * console.log(Array.head([])) // None + * Array.head([1, 2, 3]) // => Option.some(1) + * Array.head([]) // => Option.none() * ``` * * @see {@link headNonEmpty} — direct access when array is known non-empty @@ -1095,10 +1088,10 @@ export const head: (self: ReadonlyArray) => Option.Option = get(0) * * **Example** (Getting the head of a non-empty array) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.headNonEmpty([1, 2, 3, 4])) // 1 + * Array.headNonEmpty([1, 2, 3, 4]) // => 1 * ``` * * @see {@link head} — safe version for possibly-empty arrays @@ -1118,11 +1111,11 @@ export const headNonEmpty: (self: NonEmptyReadonlyArray) => A = getUnsafe( * * **Example** (Getting the last element) * - * ```ts - * import { Array } from "effect" + * ```ts import.meta.vitest + * import { Array, Option } from "effect" * - * console.log(Array.last([1, 2, 3])) // Some(3) - * console.log(Array.last([])) // None + * Array.last([1, 2, 3]) // => Option.some(3) + * Array.last([]) // => Option.none() * ``` * * @see {@link lastNonEmpty} — direct access when array is known non-empty @@ -1145,10 +1138,10 @@ export const last = (self: ReadonlyArray): Option.Option => * * **Example** (Getting the last of a non-empty array) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.lastNonEmpty([1, 2, 3, 4])) // 4 + * Array.lastNonEmpty([1, 2, 3, 4]) // => 4 * ``` * * @see {@link last} — safe version for possibly-empty arrays @@ -1171,11 +1164,11 @@ export const lastNonEmpty = (self: NonEmptyReadonlyArray): A => self[self. * * **Example** (Getting the tail) * - * ```ts - * import { Array } from "effect" + * ```ts import.meta.vitest + * import { Array, Option } from "effect" * - * console.log(Array.tail([1, 2, 3, 4])) // Option.some([2, 3, 4]) - * console.log(Array.tail([])) // Option.none() + * Array.tail([1, 2, 3, 4]) // => Option.some([2, 3, 4]) + * Array.tail([]) // => Option.none() * ``` * * @see {@link tailNonEmpty} — when the array is known non-empty @@ -1198,10 +1191,10 @@ export function tail(self: Iterable): Option.Option> { * * **Example** (Getting the tail of a non-empty array) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.tailNonEmpty([1, 2, 3, 4])) // [2, 3, 4] + * Array.tailNonEmpty([1, 2, 3, 4]) // => [2, 3, 4] * ``` * * @see {@link tail} — safe version for possibly-empty arrays @@ -1226,11 +1219,11 @@ export const tailNonEmpty = (self: NonEmptyReadonlyArray): Array => sel * * **Example** (Getting init) * - * ```ts - * import { Array } from "effect" + * ```ts import.meta.vitest + * import { Array, Option } from "effect" * - * console.log(Array.init([1, 2, 3, 4])) // Option.some([1, 2, 3]) - * console.log(Array.init([])) // Option.none() + * Array.init([1, 2, 3, 4]) // => Option.some([1, 2, 3]) + * Array.init([]) // => Option.none() * ``` * * @see {@link initNonEmpty} — when the array is known non-empty @@ -1253,10 +1246,10 @@ export function init(self: Iterable): Option.Option> { * * **Example** (Getting init of a non-empty array) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.initNonEmpty([1, 2, 3, 4])) // [1, 2, 3] + * Array.initNonEmpty([1, 2, 3, 4]) // => [1, 2, 3] * ``` * * @see {@link init} — safe version for possibly-empty arrays @@ -1280,10 +1273,10 @@ export const initNonEmpty = (self: NonEmptyReadonlyArray): Array => sel * * **Example** (Taking from the start) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.take([1, 2, 3, 4, 5], 3)) // [1, 2, 3] + * Array.take([1, 2, 3, 4, 5], 3) // => [1, 2, 3] * ``` * * @see {@link takeRight} for keeping elements from the end @@ -1314,10 +1307,10 @@ export const take: { * * **Example** (Taking from the end) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.takeRight([1, 2, 3, 4, 5], 3)) // [3, 4, 5] + * Array.takeRight([1, 2, 3, 4, 5], 3) // => [3, 4, 5] * ``` * * @see {@link take} — keep from the start @@ -1351,10 +1344,10 @@ export const takeRight: { * * **Example** (Taking while condition holds) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.takeWhile([1, 3, 2, 4, 1, 2], (x) => x < 4)) // [1, 3, 2] + * Array.takeWhile([1, 3, 2, 4, 1, 2], (x) => x < 4) // => [1, 3, 2] * ``` * * @see {@link take} for keeping a fixed number of leading elements @@ -1446,10 +1439,10 @@ const spanIndex = (self: Iterable, predicate: (a: A, i: number) => boolean * * **Example** (Splitting at predicate boundary) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.span([1, 3, 2, 4, 5], (x) => x % 2 === 1)) // [[1, 3], [2, 4, 5]] + * Array.span([1, 3, 2, 4, 5], (x) => x % 2 === 1) // => [[1, 3], [2, 4, 5]] * ``` * * @see {@link takeWhile} for keeping only the matching prefix @@ -1492,10 +1485,10 @@ export const span: { * * **Example** (Dropping from the start) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.drop([1, 2, 3, 4, 5], 2)) // [3, 4, 5] + * Array.drop([1, 2, 3, 4, 5], 2) // => [3, 4, 5] * ``` * * @see {@link dropRight} for removing a fixed number of elements from the end @@ -1526,10 +1519,10 @@ export const drop: { * * **Example** (Dropping from the end) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.dropRight([1, 2, 3, 4, 5], 2)) // [1, 2, 3] + * Array.dropRight([1, 2, 3, 4, 5], 2) // => [1, 2, 3] * ``` * * @see {@link drop} — remove from the start @@ -1559,10 +1552,10 @@ export const dropRight: { * * **Example** (Dropping while condition holds) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.dropWhile([1, 2, 3, 4, 5], (x) => x < 4)) // [4, 5] + * Array.dropWhile([1, 2, 3, 4, 5], (x) => x < 4) // => [4, 5] * ``` * * @see {@link takeWhile} — keep the matching prefix instead @@ -1634,16 +1627,16 @@ export const dropWhileFilter: { * * **Example** (Finding an index) * - * ```ts - * import { Array } from "effect" + * ```ts import.meta.vitest + * import { Array, Option } from "effect" * - * console.log(Array.findFirstIndex([5, 3, 8, 9], (x) => x > 5)) // Option.some(2) + * Array.findFirstIndex([5, 3, 8, 9], (x) => x > 5) // => Option.some(2) * ``` * * @see {@link findLastIndex} — search from the end * @see {@link findFirst} — get the element itself * - * @category elements + * @category searching * @since 2.0.0 */ export const findFirstIndex: { @@ -1670,16 +1663,16 @@ export const findFirstIndex: { * * **Example** (Finding the last matching index) * - * ```ts - * import { Array } from "effect" + * ```ts import.meta.vitest + * import { Array, Option } from "effect" * - * console.log(Array.findLastIndex([1, 3, 8, 9], (x) => x < 5)) // Option.some(1) + * Array.findLastIndex([1, 3, 8, 9], (x) => x < 5) // => Option.some(1) * ``` * * @see {@link findFirstIndex} — search from the start * @see {@link findLast} — get the element itself * - * @category elements + * @category searching * @since 2.0.0 */ export const findLastIndex: { @@ -1712,17 +1705,17 @@ export const findLastIndex: { * * **Example** (Finding the first match) * - * ```ts - * import { Array } from "effect" + * ```ts import.meta.vitest + * import { Array, Option } from "effect" * - * console.log(Array.findFirst([1, 2, 3, 4, 5], (x) => x > 3)) // Option.some(4) + * Array.findFirst([1, 2, 3, 4, 5], (x) => x > 3) // => Option.some(4) * ``` * * @see {@link findLast} — search from the end * @see {@link findFirstIndex} — get the index instead * @see {@link findFirstWithIndex} — get both element and index * - * @category elements + * @category searching * @since 2.0.0 */ export const findFirst: { @@ -1750,16 +1743,16 @@ export const findFirst: { * * **Example** (Finding element with its index) * - * ```ts - * import { Array } from "effect" + * ```ts import.meta.vitest + * import { Array, Option } from "effect" * - * console.log(Array.findFirstWithIndex([1, 2, 3, 4, 5], (x) => x > 3)) // Option.some([4, 3]) + * Array.findFirstWithIndex([1, 2, 3, 4, 5], (x) => x > 3) // => Option.some([4, 3]) * ``` * * @see {@link findFirst} — get only the element * @see {@link findFirstIndex} — get only the index * - * @category elements + * @category searching * @since 3.17.0 */ export const findFirstWithIndex: { @@ -1808,16 +1801,16 @@ export const findFirstWithIndex: { * * **Example** (Finding the last match) * - * ```ts - * import { Array } from "effect" + * ```ts import.meta.vitest + * import { Array, Option } from "effect" * - * console.log(Array.findLast([1, 2, 3, 4, 5], (n) => n % 2 === 0)) // Option.some(4) + * Array.findLast([1, 2, 3, 4, 5], (n) => n % 2 === 0) // => Option.some(4) * ``` * * @see {@link findFirst} — search from the start * @see {@link findLastIndex} — get the index instead * - * @category elements + * @category searching * @since 2.0.0 */ export const findLast: { @@ -1865,16 +1858,16 @@ export const findLast: { * * **Example** (Inserting at an index) * - * ```ts - * import { Array } from "effect" + * ```ts import.meta.vitest + * import { Array, Option } from "effect" * - * console.log(Array.insertAt(["a", "b", "c", "e"], 3, "d")) // Option.some(["a", "b", "c", "d", "e"]) + * Array.insertAt(["a", "b", "c", "e"], 3, "d") // => Option.some(["a", "b", "c", "d", "e"]) * ``` * * @see {@link replace} — replace an existing element * @see {@link modify} — transform an element at an index * - * @category elements + * @category transforming * @since 2.0.0 */ export const insertAt: { @@ -1882,10 +1875,11 @@ export const insertAt: { (self: Iterable, i: number, b: B): Option.Option> } = dual(3, (self: Iterable, i: number, b: B): Option.Option> => { const out: Array = Array.from(self) // copy because `splice` mutates the array - if (i < 0 || i > out.length) { + const index = Math.floor(i) + if (index !== out.length && isOutOfBounds(index, out)) { return Option.none() } - out.splice(i, 0, b) + out.splice(index, 0, b) return Option.some(out as any) }) @@ -1903,16 +1897,16 @@ export const insertAt: { * * **Example** (Replacing an element) * - * ```ts - * import { Array } from "effect" + * ```ts import.meta.vitest + * import { Array, Option } from "effect" * - * console.log(Array.replace([1, 2, 3], 1, 4)) // Option.some([1, 4, 3]) + * Array.replace([1, 2, 3], 1, 4) // => Option.some([1, 4, 3]) * ``` * * @see {@link modify} — transform an element with a function * @see {@link insertAt} — insert without removing * - * @category elements + * @category transforming * @since 2.0.0 */ export const replace: { @@ -1944,18 +1938,21 @@ export const replace: { * * **Example** (Modifying an element) * - * ```ts - * import { Array } from "effect" + * ```ts import.meta.vitest + * import { Array, Option } from "effect" + * + * const values = [1, 2, 3, 4] + * const double = (n: number) => n * 2 * - * console.log(Array.modify([1, 2, 3, 4], 2, (n) => n * 2)) // Option.some([1, 2, 6, 4]) - * console.log(Array.modify([1, 2, 3, 4], 5, (n) => n * 2)) // Option.none() + * Array.modify(values, 2, double) // => Option.some([1, 2, 6, 4]) + * Array.modify(values, 5, double) // => Option.none() * ``` * * @see {@link replace} — set a fixed value at an index * @see {@link modifyHeadNonEmpty} — modify the first element * @see {@link modifyLastNonEmpty} — modify the last element * - * @category elements + * @category transforming * @since 2.0.0 */ export const modify: { @@ -1970,12 +1967,13 @@ export const modify: { ): Option.Option | B>> } = dual(3, (self: Iterable, i: number, f: (a: A) => B): Option.Option> => { const arr = Array.from(self) - if (isOutOfBounds(i, arr)) { + const index = Math.floor(i) + if (isOutOfBounds(index, arr)) { return Option.none() } const out: Array = arr - const b = f(arr[i]) - out[i] = b + const b = f(arr[index]) + out[index] = b return Option.some(out) }) @@ -1990,17 +1988,17 @@ export const modify: { * * **Example** (Removing an element) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.remove([1, 2, 3, 4], 2)) // [1, 2, 4] - * console.log(Array.remove([1, 2, 3, 4], 5)) // [1, 2, 3, 4] + * Array.remove([1, 2, 3, 4], 2) // => [1, 2, 4] + * Array.remove([1, 2, 3, 4], 5) // => [1, 2, 3, 4] * ``` * * @see {@link insertAt} — insert an element * @see {@link filter} — remove elements by predicate * - * @category elements + * @category transforming * @since 2.0.0 */ export const remove: { @@ -2008,10 +2006,11 @@ export const remove: { (self: Iterable, i: number): Array } = dual(2, (self: Iterable, i: number): Array => { const out = Array.from(self) - if (isOutOfBounds(i, out)) { + const index = Math.floor(i) + if (isOutOfBounds(index, out)) { return out } - out.splice(i, 1) + out.splice(index, 1) return out }) @@ -2029,13 +2028,13 @@ export const remove: { * * **Example** (Reversing an array) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.reverse([1, 2, 3, 4])) // [4, 3, 2, 1] + * Array.reverse([1, 2, 3, 4]) // => [4, 3, 2, 1] * ``` * - * @category elements + * @category transforming * @since 2.0.0 */ export const reverse = >( @@ -2057,10 +2056,10 @@ export const reverse = >( * * **Example** (Sorting numbers) * - * ```ts + * ```ts import.meta.vitest * import { Array, Order } from "effect" * - * console.log(Array.sort([3, 1, 4, 1, 5], Order.Number)) // [1, 1, 3, 4, 5] + * Array.sort([3, 1, 4, 1, 5], Order.Number) // => [1, 1, 3, 4, 5] * ``` * * @see {@link sortWith} — sort by a mapping function @@ -2096,17 +2095,16 @@ export const sort: { * * **Example** (Sorting strings by length) * - * ```ts + * ```ts import.meta.vitest * import { Array, Order } from "effect" * - * console.log(Array.sortWith(["aaa", "b", "cc"], (s) => s.length, Order.Number)) - * // ["b", "cc", "aaa"] + * Array.sortWith(["aaa", "b", "cc"], (s) => s.length, Order.Number) // => ["b", "cc", "aaa"] * ``` * * @see {@link sort} for sorting with an `Order` that compares the elements directly * @see {@link sortBy} for sorting with multiple `Order`s applied in sequence * - * @category elements + * @category sorting * @since 2.0.0 */ export const sortWith: { @@ -2138,7 +2136,7 @@ export const sortWith: { * * **Example** (Sorting by multiple keys) * - * ```ts + * ```ts import.meta.vitest * import { Array, Order, pipe } from "effect" * * const users = [ @@ -2147,15 +2145,15 @@ export const sortWith: { * { name: "Charlie", age: 30 } * ] * - * const result = pipe( + * const sortedUsers = pipe( * users, * Array.sortBy( * Order.mapInput(Order.Number, (user: (typeof users)[number]) => user.age), * Order.mapInput(Order.String, (user: (typeof users)[number]) => user.name) * ) * ) - * console.log(result) - * // [{ name: "Bob", age: 25 }, { name: "Alice", age: 30 }, { name: "Charlie", age: 30 }] + * + * sortedUsers.map((user) => user.name).join(",") // => "Bob,Alice,Charlie" * ``` * * @see {@link sort} — sort by a single `Order` @@ -2193,10 +2191,10 @@ export const sortBy = >( * * **Example** (Zipping two arrays) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.zip([1, 2, 3], ["a", "b"])) // [[1, "a"], [2, "b"]] + * Array.zip([1, 2, 3], ["a", "b"]) // => [[1, "a"], [2, "b"]] * ``` * * @see {@link zipWith} — zip with a combiner function @@ -2226,10 +2224,10 @@ export const zip: { * * **Example** (Zipping with addition) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.zipWith([1, 2, 3], [4, 5, 6], (a, b) => a + b)) // [5, 7, 9] + * Array.zipWith([1, 2, 3], [4, 5, 6], (a, b) => a + b) // => [5, 7, 9] * ``` * * @see {@link zip} — zip into tuples @@ -2261,10 +2259,10 @@ export const zipWith: { * * **Example** (Unzipping pairs) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.unzip([[1, "a"], [2, "b"], [3, "c"]])) // [[1, 2, 3], ["a", "b", "c"]] + * Array.unzip([[1, "a"], [2, "b"], [3, "c"]]) // => [[1, 2, 3], ["a", "b", "c"]] * ``` * * @see {@link zip} — combine two arrays into pairs @@ -2304,15 +2302,15 @@ export const unzip: >( * * **Example** (Interspersing a separator) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.intersperse([1, 2, 3], 0)) // [1, 0, 2, 0, 3] + * Array.intersperse([1, 2, 3], 0) // => [1, 0, 2, 0, 3] * ``` * * @see {@link join} — intersperse and join into a string * - * @category elements + * @category transforming * @since 2.0.0 */ export const intersperse: { @@ -2347,16 +2345,16 @@ export const intersperse: { * * **Example** (Modifying the head) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.modifyHeadNonEmpty([1, 2, 3], (n) => n * 10)) // [10, 2, 3] + * Array.modifyHeadNonEmpty([1, 2, 3], (n) => n * 10) // => [10, 2, 3] * ``` * * @see {@link setHeadNonEmpty} — replace with a fixed value * @see {@link modifyLastNonEmpty} — modify the last element * - * @category elements + * @category transforming * @since 4.0.0 */ export const modifyHeadNonEmpty: { @@ -2380,16 +2378,16 @@ export const modifyHeadNonEmpty: { * * **Example** (Setting the head) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.setHeadNonEmpty([1, 2, 3], 10)) // [10, 2, 3] + * Array.setHeadNonEmpty([1, 2, 3], 10) // => [10, 2, 3] * ``` * * @see {@link modifyHeadNonEmpty} — transform the head with a function * @see {@link setLastNonEmpty} — replace the last element * - * @category elements + * @category transforming * @since 4.0.0 */ export const setHeadNonEmpty: { @@ -2411,16 +2409,16 @@ export const setHeadNonEmpty: { * * **Example** (Modifying the last element) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.modifyLastNonEmpty([1, 2, 3], (n) => n * 2)) // [1, 2, 6] + * Array.modifyLastNonEmpty([1, 2, 3], (n) => n * 2) // => [1, 2, 6] * ``` * * @see {@link setLastNonEmpty} — replace with a fixed value * @see {@link modifyHeadNonEmpty} — modify the first element * - * @category elements + * @category transforming * @since 4.0.0 */ export const modifyLastNonEmpty: { @@ -2442,16 +2440,16 @@ export const modifyLastNonEmpty: { * * **Example** (Setting the last element) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.setLastNonEmpty([1, 2, 3], 4)) // [1, 2, 4] + * Array.setLastNonEmpty([1, 2, 3], 4) // => [1, 2, 4] * ``` * * @see {@link modifyLastNonEmpty} — transform the last element with a function * @see {@link setHeadNonEmpty} — replace the first element * - * @category elements + * @category transforming * @since 4.0.0 */ export const setLastNonEmpty: { @@ -2479,16 +2477,16 @@ export const setLastNonEmpty: { * * **Example** (Rotating elements) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.rotate(["a", "b", "c", "d"], 2)) // ["c", "d", "a", "b"] + * Array.rotate(["a", "b", "c", "d"], 2) // => ["c", "d", "a", "b"] * ``` * * @see {@link take} for taking a fixed number of elements from the start * @see {@link drop} for dropping a fixed number of elements from the start * - * @category elements + * @category transforming * @since 2.0.0 */ export const rotate: { @@ -2507,7 +2505,7 @@ export const rotate: { const [f, s] = splitAtNonEmpty(input, -m) return appendAll(s, f) } else { - return rotate(self, m - len) + return rotate(input, m - len) } } return [] @@ -2523,16 +2521,17 @@ export const rotate: { * * **Example** (Checking with custom equality) * - * ```ts + * ```ts import.meta.vitest * import { Array, pipe } from "effect" * * const containsNumber = Array.containsWith((a: number, b: number) => a === b) - * console.log(pipe([1, 2, 3, 4], containsNumber(3))) // true + * + * pipe([1, 2, 3, 4], containsNumber(3)) // => true * ``` * * @see {@link contains} for the `Equal.equivalence()` variant * - * @category elements + * @category predicates * @since 2.0.0 */ export const containsWith = (isEquivalent: (self: A, that: A) => boolean): { @@ -2559,15 +2558,15 @@ export const containsWith = (isEquivalent: (self: A, that: A) => boolean): { * * **Example** (Checking membership) * - * ```ts + * ```ts import.meta.vitest * import { Array, pipe } from "effect" * - * console.log(pipe(["a", "b", "c", "d"], Array.contains("c"))) // true + * pipe(["a", "b", "c", "d"], Array.contains("c")) // => true * ``` * * @see {@link containsWith} — use custom equality * - * @category elements + * @category predicates * @since 2.0.0 */ export const contains: { @@ -2591,20 +2590,16 @@ export const contains: { * * **Example** (Chopping an array) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * const result = Array.chop( - * [1, 2, 3, 4, 5], - * (as): [number, Array] => [as[0] * 2, as.slice(1)] - * ) - * console.log(result) // [2, 4, 6, 8, 10] + * Array.chop([1, 2, 3, 4, 5], (as): [number, Array] => [as[0] * 2, as.slice(1)]) // => [2, 4, 6, 8, 10] * ``` * * @see {@link chunksOf} — split into fixed-size chunks * @see {@link splitAt} — split at an index * - * @category elements + * @category splitting * @since 2.0.0 */ export const chop: { @@ -2652,10 +2647,10 @@ export const chop: { * * **Example** (Splitting at an index) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.splitAt([1, 2, 3, 4, 5], 3)) // [[1, 2, 3], [4, 5]] + * Array.splitAt([1, 2, 3, 4, 5], 3) // => [[1, 2, 3], [4, 5]] * ``` * * @see {@link splitAtNonEmpty} — for non-empty arrays @@ -2690,11 +2685,10 @@ export const splitAt: { * * **Example** (Splitting a non-empty array) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.splitAtNonEmpty(["a", "b", "c", "d", "e"], 3)) - * // [["a", "b", "c"], ["d", "e"]] + * Array.splitAtNonEmpty(["a", "b", "c", "d", "e"], 3) // => [["a", "b", "c"], ["d", "e"]] * ``` * * @see {@link splitAt} — for possibly-empty arrays @@ -2725,10 +2719,10 @@ export const splitAtNonEmpty: { * * **Example** (Splitting into groups) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.split([1, 2, 3, 4, 5, 6, 7, 8], 3)) // [[1, 2, 3], [4, 5, 6], [7, 8]] + * Array.split([1, 2, 3, 4, 5, 6, 7, 8], 3) // => [[1, 2, 3], [4, 5, 6], [7, 8]] * ``` * * @see {@link chunksOf} — split into fixed-size chunks @@ -2755,10 +2749,10 @@ export const split: { * * **Example** (Splitting at a condition) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.splitWhere([1, 2, 3, 4, 5], (n) => n > 3)) // [[1, 2, 3], [4, 5]] + * Array.splitWhere([1, 2, 3, 4, 5], (n) => n > 3) // => [[1, 2, 3], [4, 5]] * ``` * * @see {@link span} — splits at the first element that fails the predicate @@ -2793,18 +2787,19 @@ export const splitWhere: { * * **Example** (Copying an array) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * * const original = [1, 2, 3] * const copied = Array.copy(original) - * console.log(copied) // [1, 2, 3] - * console.log(original === copied) // false + * + * copied // => [1, 2, 3] + * original === copied // => false * ``` * * @see {@link fromIterable} — returns the same reference for arrays * - * @category elements + * @category transforming * @since 2.0.0 */ export const copy: { @@ -2826,16 +2821,16 @@ export const copy: { * * **Example** (Padding an array) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.pad([1, 2, 3], 6, 0)) // [1, 2, 3, 0, 0, 0] + * Array.pad([1, 2, 3], 6, 0) // => [1, 2, 3, 0, 0, 0] * ``` * * @see {@link take} — truncate without padding * @see {@link replicate} — create an array of a single repeated value * - * @category elements + * @category transforming * @since 3.8.4 */ export const pad: { @@ -2872,10 +2867,10 @@ export const pad: { * * **Example** (Chunking an array) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.chunksOf([1, 2, 3, 4, 5], 2)) // [[1, 2], [3, 4], [5]] + * Array.chunksOf([1, 2, 3, 4, 5], 2) // => [[1, 2], [3, 4], [5]] * ``` * * @see {@link split} — split into a given number of groups @@ -2914,11 +2909,13 @@ export const chunksOf: { * * **Example** (Creating sliding windows) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.window([1, 2, 3, 4, 5], 3)) // [[1, 2, 3], [2, 3, 4], [3, 4, 5]] - * console.log(Array.window([1, 2, 3, 4, 5], 6)) // [] + * const values = [1, 2, 3, 4, 5] + * + * Array.window(values, 3) // => [[1, 2, 3], [2, 3, 4], [3, 4, 5]] + * Array.window(values, 6) // => [] * ``` * * @see {@link chunksOf} — non-overlapping chunks @@ -2955,11 +2952,13 @@ export const window: { * * **Example** (Grouping consecutive equal elements) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.groupWith(["a", "a", "b", "b", "b", "c", "a"], (x, y) => x === y)) - * // [["a", "a"], ["b", "b", "b"], ["c"], ["a"]] + * Array.groupWith( + * ["a", "a", "b", "b", "b", "c", "a"], + * (x, y) => x === y + * ) // => [["a", "a"], ["b", "b", "b"], ["c"], ["a"]] * ``` * * @see {@link group} for grouping adjacent elements with `Equal.equivalence()` @@ -3004,10 +3003,10 @@ export const groupWith: { * * **Example** (Grouping adjacent equal elements) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.group([1, 1, 2, 2, 2, 3, 1])) // [[1, 1], [2, 2, 2], [3], [1]] + * Array.group([1, 1, 2, 2, 2, 3, 1]) // => [[1, 1], [2, 2, 2], [3], [1]] * ``` * * @see {@link groupWith} — use custom equality @@ -3035,7 +3034,7 @@ export const group: (self: NonEmptyReadonlyArray) => NonEmptyArray(self: NonEmptyReadonlyArray) => NonEmptyArray person.group) - * console.log(result) - * // { A: [{ name: "Alice", group: "A" }, { name: "Charlie", group: "A" }], B: [{ name: "Bob", group: "B" }] } + * Object.keys(Array.groupBy(people, (person) => person.group)).join(",") // => "A,B" * ``` * * @see {@link group} — group adjacent equal elements @@ -3073,12 +3070,52 @@ export const groupBy: { if (Object.hasOwn(out, k)) { out[k].push(a) } else { - out[k] = [a] + InternalRecord.assignProperty(out, k, [a]) } } return out }) +type HashBuckets = Map> + +const hashBucketsAdd = (buckets: HashBuckets, value: unknown): boolean => { + const hash = Hash.hash(value) + const bucket = buckets.get(hash) + if (bucket === undefined) { + buckets.set(hash, [value]) + return true + } + // Hash collisions still require an Effect equality check. + for (const previous of bucket) { + if (Equal.equals(previous, value)) { + return false + } + } + bucket.push(value) + return true +} + +const makeHashBuckets = (values: Iterable): HashBuckets => { + const buckets: HashBuckets = new Map() + for (const value of values) { + hashBucketsAdd(buckets, value) + } + return buckets +} + +const hashBucketsHas = (buckets: HashBuckets, value: unknown): boolean => { + const bucket = buckets.get(Hash.hash(value)) + if (bucket === undefined) { + return false + } + for (const candidate of bucket) { + if (Equal.equals(candidate, value)) { + return true + } + } + return false +} + /** * Computes the union of two arrays using a custom equivalence, removing * duplicates. @@ -3090,17 +3127,17 @@ export const groupBy: { * * **Example** (Computing unions with custom equality) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.unionWith([1, 2], [2, 3], (a, b) => a === b)) // [1, 2, 3] + * Array.unionWith([1, 2], [2, 3], (a, b) => a === b) // => [1, 2, 3] * ``` * * @see {@link union} for the `Equal.equivalence()` variant * @see {@link intersectionWith} for keeping elements present in both arrays * @see {@link differenceWith} for keeping elements present only in the first array * - * @category elements + * @category set operations * @since 2.0.0 */ export const unionWith: { @@ -3138,17 +3175,17 @@ export const unionWith: { * * **Example** (Computing array unions) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.union([1, 2], [2, 3])) // [1, 2, 3] + * Array.union([1, 2], [2, 3]) // => [1, 2, 3] * ``` * * @see {@link unionWith} — use custom equality * @see {@link intersection} — elements in both arrays * @see {@link difference} — elements only in the first array * - * @category elements + * @category set operations * @since 2.0.0 */ export const union: { @@ -3162,7 +3199,14 @@ export const union: { (self: Iterable, that: Iterable): Array } = dual( 2, - (self: Iterable, that: Iterable): Array => unionWith(self, that, Equal.asEquivalence()) + (self: Iterable, that: Iterable): Array => { + const a = fromIterable(self) + const b = fromIterable(that) + if (isReadonlyArrayNonEmpty(a)) { + return isReadonlyArrayNonEmpty(b) ? dedupe(appendAll(a, b)) : a + } + return b + } ) /** @@ -3176,20 +3220,21 @@ export const union: { * * **Example** (Computing intersections with custom equality) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * * const array1 = [{ id: 1 }, { id: 2 }, { id: 3 }] * const array2 = [{ id: 3 }, { id: 4 }, { id: 1 }] * const isEquivalent = (a: { id: number }, b: { id: number }) => a.id === b.id - * console.log(Array.intersectionWith(isEquivalent)(array2)(array1)) // [{ id: 1 }, { id: 3 }] + * + * Array.intersectionWith(isEquivalent)(array2)(array1) // => [{ id: 1 }, { id: 3 }] * ``` * * @see {@link intersection} for the `Equal.equivalence()` variant * @see {@link unionWith} for keeping values from either array with custom equality * @see {@link differenceWith} for keeping values only from the first array with custom equality * - * @category elements + * @category set operations * @since 2.0.0 */ export const intersectionWith = (isEquivalent: (self: A, that: A) => boolean): { @@ -3217,23 +3262,31 @@ export const intersectionWith = (isEquivalent: (self: A, that: A) => boolean) * * **Example** (Computing array intersections) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.intersection([1, 2, 3], [3, 4, 1])) // [1, 3] + * Array.intersection([1, 2, 3], [3, 4, 1]) // => [1, 3] * ``` * * @see {@link intersectionWith} — use custom equality * @see {@link union} — elements in either array * @see {@link difference} — elements only in the first array * - * @category elements + * @category set operations * @since 2.0.0 */ export const intersection: { (that: Iterable): (self: Iterable) => Array (self: Iterable, that: Iterable): Array -} = intersectionWith(Equal.asEquivalence()) +} = dual(2, (self: Iterable, that: Iterable): Array => { + const thatArray = fromIterable(that) + const selfArray = fromIterable(self) + if (selfArray.length === 0 || thatArray.length === 0) { + return [] + } + const buckets = makeHashBuckets(thatArray) + return selfArray.filter((value): value is A & B => hashBucketsHas(buckets, value)) +}) /** * Computes elements in the first array that are not in the second, using a @@ -3246,18 +3299,17 @@ export const intersection: { * * **Example** (Computing differences with custom equality) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * const diff = Array.differenceWith((a, b) => a === b)([1, 2, 3], [2, 3, 4]) - * console.log(diff) // [1] + * Array.differenceWith((a, b) => a === b)([1, 2, 3], [2, 3, 4]) // => [1] * ``` * * @see {@link difference} for the `Equal.equivalence()` variant * @see {@link unionWith} for keeping values from either array with custom equality * @see {@link intersectionWith} for keeping values present in both arrays with custom equality * - * @category elements + * @category set operations * @since 2.0.0 */ export const differenceWith = (isEquivalent: (self: A, that: A) => boolean): { @@ -3285,23 +3337,34 @@ export const differenceWith = (isEquivalent: (self: A, that: A) => boolean): * * **Example** (Computing array differences) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.difference([1, 2, 3], [2, 3, 4])) // [1] + * Array.difference([1, 2, 3], [2, 3, 4]) // => [1] * ``` * * @see {@link differenceWith} — use custom equality * @see {@link union} — elements in either array * @see {@link intersection} — elements in both arrays * - * @category elements + * @category set operations * @since 2.0.0 */ export const difference: { (that: Iterable): (self: Iterable) => Array (self: Iterable, that: Iterable): Array -} = differenceWith(Equal.asEquivalence()) +} = dual(2, (self: Iterable, that: Iterable): Array => { + const thatArray = fromIterable(that) + const selfArray = fromIterable(self) + if (selfArray.length === 0) { + return [] + } + if (thatArray.length === 0) { + return selfArray.filter(() => true) + } + const buckets = makeHashBuckets(thatArray) + return selfArray.filter((value) => !hashBucketsHas(buckets, value)) +}) /** * Creates an empty array. @@ -3312,11 +3375,10 @@ export const difference: { * * **Example** (Creating an empty array) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * const result = Array.empty() - * console.log(result) // [] + * Array.empty() // => [] * ``` * * @see {@link of} — create a single-element array @@ -3332,10 +3394,10 @@ export const empty: () => Array = () => [] * * **Example** (Creating a single-element array) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.of(1)) // [1] + * Array.of(1) // => [1] * ``` * * @see {@link make} — create from multiple values @@ -3358,14 +3420,14 @@ export declare namespace ReadonlyArray { * * **Example** (Inferring an element type) * - * ```ts + * ```ts import.meta.vitest * import type { Array } from "effect" * * type StringArrayType = Array.ReadonlyArray.Infer> * // StringArrayType is string * ``` * - * @category types + * @category utility types * @since 2.0.0 */ export type Infer> = S extends ReadonlyArray ? A @@ -3377,14 +3439,14 @@ export declare namespace ReadonlyArray { * * **Example** (Preserving non-emptiness) * - * ```ts + * ```ts import.meta.vitest * import type { Array } from "effect" * * type Result = Array.ReadonlyArray.With * // Result is NonEmptyArray * ``` * - * @category types + * @category utility types * @since 2.0.0 */ export type With, A> = S extends NonEmptyReadonlyArray ? NonEmptyArray @@ -3395,7 +3457,7 @@ export declare namespace ReadonlyArray { * * **Example** (Preserving non-emptiness from either input) * - * ```ts + * ```ts import.meta.vitest * import type { Array } from "effect" * * type Result = Array.ReadonlyArray.OrNonEmpty< @@ -3406,7 +3468,7 @@ export declare namespace ReadonlyArray { * // Result is NonEmptyArray * ``` * - * @category types + * @category utility types * @since 2.0.0 */ export type OrNonEmpty< @@ -3422,7 +3484,7 @@ export declare namespace ReadonlyArray { * * **Example** (Preserving non-emptiness from both inputs) * - * ```ts + * ```ts import.meta.vitest * import type { Array } from "effect" * * type Result = Array.ReadonlyArray.AndNonEmpty< @@ -3433,7 +3495,7 @@ export declare namespace ReadonlyArray { * // Result is NonEmptyArray * ``` * - * @category types + * @category utility types * @since 2.0.0 */ export type AndNonEmpty< @@ -3449,7 +3511,7 @@ export declare namespace ReadonlyArray { * * **Example** (Flattening nested array types) * - * ```ts + * ```ts import.meta.vitest * import type { Array } from "effect" * * type Nested = ReadonlyArray> @@ -3457,7 +3519,7 @@ export declare namespace ReadonlyArray { * // Flattened is Array * ``` * - * @category types + * @category utility types * @since 2.0.0 */ export type Flatten>> = T extends @@ -3479,10 +3541,10 @@ export declare namespace ReadonlyArray { * * **Example** (Doubling values) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.map([1, 2, 3], (x) => x * 2)) // [2, 4, 6] + * Array.map([1, 2, 3], (x) => x * 2) // => [2, 4, 6] * ``` * * @see {@link flatMap} — map and flatten @@ -3512,10 +3574,10 @@ export const map: { * * **Example** (Flat mapping an array) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.flatMap([1, 2, 3], (x) => [x, x * 2])) // [1, 2, 2, 4, 3, 6] + * Array.flatMap([1, 2, 3], (x) => [x, x * 2]) // => [1, 2, 2, 4, 3, 6] * ``` * * @see {@link map} — transform without flattening @@ -3557,10 +3619,10 @@ export const flatMap: { * * **Example** (Flattening nested arrays) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.flatten([[1, 2], [], [3, 4], [], [5, 6]])) // [1, 2, 3, 4, 5, 6] + * Array.flatten([[1, 2], [], [3, 4], [], [5, 6]]) // => [1, 2, 3, 4, 5, 6] * ``` * * @see {@link flatMap} — map then flatten in one step @@ -3581,10 +3643,10 @@ export const flatten: >>(self: * * **Example** (Extracting Some values) * - * ```ts + * ```ts import.meta.vitest * import { Array, Option } from "effect" * - * console.log(Array.getSomes([Option.some(1), Option.none(), Option.some(2)])) // [1, 2] + * Array.getSomes([Option.some(1), Option.none(), Option.some(2)]) // => [1, 2] * ``` * * @see {@link fromOption} — convert a single Option @@ -3617,11 +3679,10 @@ export const getSomes: >, X = any>( * * **Example** (Extracting failures) * - * ```ts + * ```ts import.meta.vitest * import { Array, Result } from "effect" * - * console.log(Array.getFailures([Result.succeed(1), Result.fail("err"), Result.succeed(2)])) - * // ["err"] + * Array.getFailures([Result.succeed(1), Result.fail("err"), Result.succeed(2)]) // => ["err"] * ``` * * @see {@link getSuccesses} — extract success values @@ -3654,11 +3715,10 @@ export const getFailures = >>( * * **Example** (Extracting successes) * - * ```ts + * ```ts import.meta.vitest * import { Array, Result } from "effect" * - * console.log(Array.getSuccesses([Result.succeed(1), Result.fail("err"), Result.succeed(2)])) - * // [1, 2] + * Array.getSuccesses([Result.succeed(1), Result.fail("err"), Result.succeed(2)]) // => [1, 2] * ``` * * @see {@link getFailures} — extract failure values @@ -3694,11 +3754,10 @@ export const getSuccesses = >>( * * **Example** (Filtering and transforming) * - * ```ts + * ```ts import.meta.vitest * import { Array, Result } from "effect" * - * console.log(Array.filterMap([1, 2, 3, 4], (n) => n % 2 === 0 ? Result.succeed(n * 10) : Result.failVoid)) - * // [20, 40] + * Array.filterMap([1, 2, 3, 4], (n) => n % 2 === 0 ? Result.succeed(n * 10) : Result.failVoid) // => [20, 40] * ``` * * @see {@link filter} — keep original elements matching a predicate @@ -3737,10 +3796,10 @@ export const filterMap: { * * **Example** (Filtering even numbers) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.filter([1, 2, 3, 4], (x) => x % 2 === 0)) // [2, 4] + * Array.filter([1, 2, 3, 4], (x) => x % 2 === 0) // => [2, 4] * ``` * * @see {@link partition} — split into matching and non-matching @@ -3782,13 +3841,12 @@ export const filter: { * * **Example** (Partitioning with a filter) * - * ```ts + * ```ts import.meta.vitest * import { Array, Result } from "effect" * - * console.log(Array.partition([1, -2, 3], (n, i) => + * Array.partition([1, -2, 3], (n, i) => * n > 0 ? Result.succeed(n + i) : Result.fail(`negative:${n}`) - * )) - * // [["negative:-2"], [1, 5]] + * ) // => [["negative:-2"], [1, 5]] * ``` * * @see {@link filter} — keep only matching elements @@ -3841,14 +3899,10 @@ export const partition: { * * **Example** (Separating Results) * - * ```ts + * ```ts import.meta.vitest * import { Array, Result } from "effect" * - * const [failures, successes] = Array.separate([ - * Result.succeed(1), Result.fail("error"), Result.succeed(2) - * ]) - * console.log(failures) // ["error"] - * console.log(successes) // [1, 2] + * Array.separate([Result.succeed(1), Result.fail("error"), Result.succeed(2)]) // => [["error"], [1, 2]] * ``` * * @see {@link getFailures} — extract only failures @@ -3878,10 +3932,10 @@ export const separate: >>( * * **Example** (Summing an array) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.reduce([1, 2, 3], 0, (acc, n) => acc + n)) // 6 + * Array.reduce([1, 2, 3], 0, (acc, n) => acc + n) // => 6 * ``` * * @see {@link reduceRight} — fold from right to left @@ -3912,10 +3966,10 @@ export const reduce: { * * **Example** (Folding from right to left) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.reduceRight([1, 2, 3], 0, (acc, n) => acc + n)) // 6 + * Array.reduceRight([1, 2, 3], 0, (acc, n) => acc + n) // => 6 * ``` * * @see {@link reduce} — fold from left to right @@ -3939,13 +3993,13 @@ export const reduceRight: { * * **Example** (Wrapping values conditionally) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * const isEven = (n: number) => n % 2 === 0 - * const to = Array.liftPredicate(isEven) - * console.log(to(1)) // [] - * console.log(to(2)) // [2] + * const fromEven = Array.liftPredicate((n: number) => n % 2 === 0) + * + * fromEven(1) // => [] + * fromEven(2) // => [2] * ``` * * @see {@link liftOption} — lift an Option-returning function @@ -3969,15 +4023,16 @@ export const liftPredicate: { // Note: I intentionally avoid using the NoInfer p * * **Example** (Lifting an Option function) * - * ```ts + * ```ts import.meta.vitest * import { Array, Option } from "effect" * * const parseNumber = Array.liftOption((s: string) => { * const n = Number(s) * return isNaN(n) ? Option.none() : Option.some(n) * }) - * console.log(parseNumber("123")) // [123] - * console.log(parseNumber("abc")) // [] + * + * parseNumber("123") // => [123] + * parseNumber("abc") // => [] * ``` * * @see {@link liftPredicate} — lift a boolean predicate @@ -4001,12 +4056,12 @@ export const liftOption = , B>( * * **Example** (Converting nullable values to an array) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.fromNullishOr(1)) // [1] - * console.log(Array.fromNullishOr(null)) // [] - * console.log(Array.fromNullishOr(undefined)) // [] + * Array.fromNullishOr(1) // => [1] + * Array.fromNullishOr(null) // => [] + * Array.fromNullishOr(undefined) // => [] * ``` * * @see {@link liftNullishOr} — lift a nullable-returning function @@ -4023,15 +4078,16 @@ export const fromNullishOr = (a: A): Array> => a == null ? emp * * **Example** (Lifting a nullable function) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * * const parseNumber = Array.liftNullishOr((s: string) => { * const n = Number(s) * return isNaN(n) ? null : n * }) - * console.log(parseNumber("123")) // [123] - * console.log(parseNumber("abc")) // [] + * + * parseNumber("123") // => [123] + * parseNumber("abc") // => [] * ``` * * @see {@link fromNullishOr} — convert a single nullable value @@ -4056,11 +4112,10 @@ export const liftNullishOr = , B>( * * **Example** (Flat mapping with nullable values) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.flatMapNullishOr([1, 2, 3], (n) => (n % 2 === 0 ? null : n))) - * // [1, 3] + * Array.flatMapNullishOr([1, 2, 3], (n) => (n % 2 === 0 ? null : n)) // => [1, 3] * ``` * * @see {@link flatMap} for mapping each element to an array and flattening @@ -4088,7 +4143,7 @@ export const flatMapNullishOr: { * * **Example** (Lifting a Result function) * - * ```ts + * ```ts import.meta.vitest * import { Array, Result } from "effect" * * const parseNumber = (s: string): Result.Result => @@ -4097,8 +4152,9 @@ export const flatMapNullishOr: { * : Result.succeed(Number(s)) * * const liftedParseNumber = Array.liftResult(parseNumber) - * console.log(liftedParseNumber("42")) // [42] - * console.log(liftedParseNumber("not a number")) // [] + * + * liftedParseNumber("42") // => [42] + * liftedParseNumber("not a number") // => [] * ``` * * @see {@link liftOption} — lift an Option-returning function @@ -4126,16 +4182,16 @@ export const liftResult = , E, B>( * * **Example** (Testing all elements) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.every([2, 4, 6], (x) => x % 2 === 0)) // true - * console.log(Array.every([2, 3, 6], (x) => x % 2 === 0)) // false + * Array.every([2, 4, 6], (x) => x % 2 === 0) // => true + * Array.every([2, 3, 6], (x) => x % 2 === 0) // => false * ``` * * @see {@link some} — test if any element matches * - * @category elements + * @category guards * @since 2.0.0 */ export const every: { @@ -4157,17 +4213,17 @@ export const every: { * * **Example** (Testing for any match) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.some([1, 3, 4], (x) => x % 2 === 0)) // true - * console.log(Array.some([1, 3, 5], (x) => x % 2 === 0)) // false + * Array.some([1, 3, 4], (x) => x % 2 === 0) // => true + * Array.some([1, 3, 5], (x) => x % 2 === 0) // => false * ``` * * @see {@link every} — test if all elements match * @see {@link contains} — test for a specific value * - * @category elements + * @category guards * @since 2.0.0 */ export const some: { @@ -4196,10 +4252,10 @@ export const some: { * * **Example** (Computing suffix lengths) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.extend([1, 2, 3], (as) => as.length)) // [3, 2, 1] + * Array.extend([1, 2, 3], (as) => as.length) // => [3, 2, 1] * ``` * * @see {@link scan} for keeping intermediate accumulator values during a fold @@ -4221,16 +4277,16 @@ export const extend: { * * **Example** (Finding the minimum) * - * ```ts + * ```ts import.meta.vitest * import { Array, Order } from "effect" * - * console.log(Array.min([3, 1, 2], Order.Number)) // 1 + * Array.min([3, 1, 2], Order.Number) // => 1 * ``` * * @see {@link max} — find the maximum * @see {@link sort} — sort the entire array * - * @category elements + * @category getters * @since 2.0.0 */ export const min: { @@ -4244,16 +4300,16 @@ export const min: { * * **Example** (Finding the maximum) * - * ```ts + * ```ts import.meta.vitest * import { Array, Order } from "effect" * - * console.log(Array.max([3, 1, 2], Order.Number)) // 3 + * Array.max([3, 1, 2], Order.Number) // => 3 * ``` * * @see {@link min} — find the minimum * @see {@link sort} — sort the entire array * - * @category elements + * @category getters * @since 2.0.0 */ export const max: { @@ -4268,11 +4324,10 @@ export const max: { * * **Example** (Generating a sequence) * - * ```ts + * ```ts import.meta.vitest * import { Array, Option } from "effect" * - * console.log(Array.unfold(1, (n) => n <= 5 ? Option.some([n, n + 1]) : Option.none())) - * // [1, 2, 3, 4, 5] + * Array.unfold(1, (n) => n <= 5 ? Option.some([n, n + 1]) : Option.none()) // => [1, 2, 3, 4, 5] * ``` * * @see {@link makeBy} — generate from index @@ -4303,11 +4358,12 @@ export const unfold = (b: B, f: (b: B) => Option.Option): * * **Example** (Comparing arrays) * - * ```ts + * ```ts import.meta.vitest * import { Array, Order } from "effect" * * const arrayOrder = Array.makeOrder(Order.Number) - * console.log(arrayOrder([1, 2], [1, 3])) // -1 + * + * arrayOrder([1, 2], [1, 3]) // => -1 * ``` * * @see {@link makeEquivalence} — create an equivalence for arrays @@ -4324,11 +4380,12 @@ export const makeOrder: (O: Order.Order) => Order.Order> * * **Example** (Comparing arrays for equality) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * * const eq = Array.makeEquivalence((a, b) => a === b) - * console.log(eq([1, 2, 3], [1, 2, 3])) // true + * + * eq([1, 2, 3], [1, 2, 3]) // => true * ``` * * @see {@link makeOrder} — create an ordering for arrays @@ -4350,15 +4407,18 @@ export const makeEquivalence: ( * * **Example** (Iterating with side-effects) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * Array.forEach([1, 2, 3], (n) => console.log(n)) // 1, 2, 3 + * const visited: Array = [] + * Array.forEach([1, 2, 3], (n) => visited.push(n)) + * + * visited // => [1, 2, 3] * ``` * * @see {@link map} for transforming each element into a new array * - * @category elements + * @category traversing * @since 2.0.0 */ export const forEach: { @@ -4377,16 +4437,16 @@ export const forEach: { * * **Example** (Deduplicating with custom equality) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.dedupeWith([1, 2, 2, 3, 3, 3], (a, b) => a === b)) // [1, 2, 3] + * Array.dedupeWith([1, 2, 2, 3, 3, 3], (a, b) => a === b) // => [1, 2, 3] * ``` * * @see {@link dedupe} — uses default equality * @see {@link dedupeAdjacentWith} — only dedupes consecutive elements * - * @category elements + * @category deduplication * @since 2.0.0 */ export const dedupeWith: { @@ -4424,22 +4484,34 @@ export const dedupeWith: { * * **Example** (Removing duplicates) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.dedupe([1, 2, 1, 3, 2, 4])) // [1, 2, 3, 4] + * Array.dedupe([1, 2, 1, 3, 2, 4]) // => [1, 2, 3, 4] * ``` * * @see {@link dedupeWith} — use custom equality * @see {@link dedupeAdjacent} — only dedupes consecutive elements * - * @category elements + * @category deduplication * @since 2.0.0 */ export const dedupe = >( self: S -): S extends NonEmptyReadonlyArray ? NonEmptyArray : S extends Iterable ? Array : never => - dedupeWith(self, Equal.asEquivalence()) as any +): S extends NonEmptyReadonlyArray ? NonEmptyArray : S extends Iterable ? Array : never => { + const input = fromIterable(self) + if (input.length < 2) { + return [...input] as any + } + const buckets: HashBuckets = new Map() + const out: Array = [] + for (const value of input) { + if (hashBucketsAdd(buckets, value)) { + out.push(value) + } + } + return out as any +} /** * Removes consecutive duplicate elements using a custom equivalence. @@ -4456,17 +4528,16 @@ export const dedupe = >( * * **Example** (Deduplicating adjacent elements) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.dedupeAdjacentWith([1, 1, 2, 2, 3, 3], (a, b) => a === b)) - * // [1, 2, 3] + * Array.dedupeAdjacentWith([1, 1, 2, 2, 3, 3], (a, b) => a === b) // => [1, 2, 3] * ``` * * @see {@link dedupeAdjacent} — uses default equality * @see {@link dedupeWith} — dedupes all duplicates, not just adjacent * - * @category elements + * @category deduplication * @since 2.0.0 */ export const dedupeAdjacentWith: { @@ -4494,16 +4565,16 @@ export const dedupeAdjacentWith: { * * **Example** (Removing adjacent duplicates) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.dedupeAdjacent([1, 1, 2, 2, 3, 3])) // [1, 2, 3] + * Array.dedupeAdjacent([1, 1, 2, 2, 3, 3]) // => [1, 2, 3] * ``` * * @see {@link dedupeAdjacentWith} — use custom equality * @see {@link dedupe} — remove all duplicates * - * @category elements + * @category deduplication * @since 2.0.0 */ export const dedupeAdjacent: (self: Iterable) => Array = dedupeAdjacentWith(Equal.asEquivalence()) @@ -4513,10 +4584,10 @@ export const dedupeAdjacent: (self: Iterable) => Array = dedupeAdjacent * * **Example** (Joining strings) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.join(["a", "b", "c"], "-")) // "a-b-c" + * Array.join(["a", "b", "c"], "-") // => "a-b-c" * ``` * * @see {@link intersperse} — insert separator elements without joining @@ -4546,11 +4617,10 @@ export const join: { * * **Example** (Running sum alongside mapped values) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * const result = Array.mapAccum([1, 2, 3], 0, (acc, n) => [acc + n, acc + n]) - * console.log(result) // [6, [1, 3, 6]] + * Array.mapAccum([1, 2, 3], 0, (acc, n) => [acc + n, acc + n]) // => [6, [1, 3, 6]] * ``` * * @see {@link scan} — when you only need the accumulated results (not the final state) @@ -4601,16 +4671,15 @@ export const mapAccum: { * * **Example** (Combining numbers and letters) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * const result = Array.cartesianWith([1, 2], ["a", "b"], (a, b) => `${a}-${b}`) - * console.log(result) // ["1-a", "1-b", "2-a", "2-b"] + * Array.cartesianWith([1, 2], ["a", "b"], (a, b) => `${a}-${b}`) // => ["1-a", "1-b", "2-a", "2-b"] * ``` * * @see {@link cartesian} for returning tuples instead of applying a combiner * - * @category elements + * @category combining * @since 2.0.0 */ export const cartesianWith: { @@ -4636,16 +4705,15 @@ export const cartesianWith: { * * **Example** (Generating all pairs from two arrays) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * const result = Array.cartesian([1, 2], ["a", "b"]) - * console.log(result) // [[1, "a"], [1, "b"], [2, "a"], [2, "b"]] + * Array.cartesian([1, 2], ["a", "b"]) // => [[1, "a"], [1, "b"], [2, "a"], [2, "b"]] * ``` * * @see {@link cartesianWith} — apply a combiner to each pair * - * @category elements + * @category combining * @since 2.0.0 */ export const cartesian: { @@ -4676,24 +4744,23 @@ export const cartesian: { * * **Example** (Building array comprehensions with do notation) * - * ```ts + * ```ts import.meta.vitest * import { Array, pipe } from "effect" * - * const result = pipe( + * pipe( * Array.Do, * Array.bind("x", () => [1, 3, 5]), * Array.bind("y", () => [2, 4, 6]), * Array.filter(({ x, y }) => x < y), * Array.map(({ x, y }) => [x, y] as const) - * ) - * console.log(result) // [[1, 2], [1, 4], [1, 6], [3, 4], [3, 6], [5, 6]] + * ) // => [[1, 2], [1, 4], [1, 6], [3, 4], [3, 6], [5, 6]] * ``` * * @see {@link bind} — introduce an array variable into the scope * @see {@link bindTo} — start a pipeline by naming the first array * @see {@link let_ let} — introduce a plain computed value * - * @category do notation + * @category constructors * @since 3.2.0 */ export const Do: ReadonlyArray<{}> = of({}) @@ -4714,23 +4781,21 @@ export const Do: ReadonlyArray<{}> = of({}) * * **Example** (Binding two arrays) * - * ```ts + * ```ts import.meta.vitest * import { Array, pipe } from "effect" * - * const result = pipe( + * pipe( * Array.Do, * Array.bind("x", () => [1, 2]), * Array.bind("y", () => ["a", "b"]) - * ) - * console.log(result) - * // [{ x: 1, y: "a" }, { x: 1, y: "b" }, { x: 2, y: "a" }, { x: 2, y: "b" }] + * ) // => [{ x: 1, y: "a" }, { x: 1, y: "b" }, { x: 2, y: "a" }, { x: 2, y: "b" }] * ``` * * @see {@link Do} — start a do-notation pipeline * @see {@link bindTo} — name the first array in a pipeline * @see {@link let_ let} — add a plain computed value * - * @category do notation + * @category sequencing * @since 3.2.0 */ export const bind: { @@ -4762,20 +4827,16 @@ export const bind: { * * **Example** (Naming an existing array) * - * ```ts - * import { Array, pipe } from "effect" + * ```ts import.meta.vitest + * import { Array } from "effect" * - * const result = pipe( - * [1, 2, 3], - * Array.bindTo("x") - * ) - * console.log(result) // [{ x: 1 }, { x: 2 }, { x: 3 }] + * Array.bindTo([1, 2, 3], "x") // => [{ x: 1 }, { x: 2 }, { x: 3 }] * ``` * * @see {@link Do} — start with an empty scope * @see {@link bind} — add another array variable to the scope * - * @category do notation + * @category mapping * @since 3.2.0 */ export const bindTo: { @@ -4812,22 +4873,20 @@ export { * * **Example** (Adding a computed value) * - * ```ts + * ```ts import.meta.vitest * import { Array, pipe } from "effect" * - * const result = pipe( + * pipe( * Array.Do, * Array.bind("x", () => [1, 2, 3]), * Array.let("doubled", ({ x }) => x * 2) - * ) - * console.log(result) - * // [{ x: 1, doubled: 2 }, { x: 2, doubled: 4 }, { x: 3, doubled: 6 }] + * ) // => [{ x: 1, doubled: 2 }, { x: 2, doubled: 4 }, { x: 3, doubled: 6 }] * ``` * * @see {@link Do} — start a do-notation pipeline * @see {@link bind} — introduce an array variable (produces cartesian product) * - * @category do notation + * @category mapping * @since 3.2.0 */ let_ as let @@ -4874,11 +4933,10 @@ export function makeReducerConcat(): Reducer.Reducer> { * * **Example** (Counting even numbers) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * const result = Array.countBy([1, 2, 3, 4, 5], (n) => n % 2 === 0) - * console.log(result) // 2 + * Array.countBy([1, 2, 3, 4, 5], (n) => n % 2 === 0) // => 2 * ``` * * @see {@link filter} — when you need the matching elements, not just the count diff --git a/repos/effect/packages/effect/src/BigDecimal.ts b/repos/effect/packages/effect/src/BigDecimal.ts index 8ca0678d9a..865db4449b 100644 --- a/repos/effect/packages/effect/src/BigDecimal.ts +++ b/repos/effect/packages/effect/src/BigDecimal.ts @@ -34,13 +34,13 @@ const TypeId = "~effect/BigDecimal" * * **Example** (Inspecting BigDecimal storage) * - * ```ts + * ```ts import.meta.vitest * import { BigDecimal } from "effect" * * const d = BigDecimal.fromStringUnsafe("123.45") * - * console.log(d.value) // 12345n - * console.log(d.scale) // 2 + * d.value // => 12345n + * d.scale // => 2 * ``` * * @category models @@ -90,13 +90,14 @@ const BigDecimalProto: Omit = { * * **Example** (Checking BigDecimal values) * - * ```ts + * ```ts import.meta.vitest * import { BigDecimal } from "effect" * * const decimal = BigDecimal.fromNumber(123.45) - * console.log(BigDecimal.isBigDecimal(decimal)) // true - * console.log(BigDecimal.isBigDecimal(123.45)) // false - * console.log(BigDecimal.isBigDecimal("123.45")) // false + * BigDecimal.isBigDecimal(decimal) // => false + * BigDecimal.isBigDecimal(BigDecimal.fromStringUnsafe("123.45")) // => true + * BigDecimal.isBigDecimal(123.45) // => false + * BigDecimal.isBigDecimal("123.45") // => false * ``` * * @category guards @@ -114,16 +115,16 @@ export const isBigDecimal = (u: unknown): u is BigDecimal => hasProperty(u, Type * * **Example** (Creating decimals from bigint and scale) * - * ```ts + * ```ts import.meta.vitest * import { BigDecimal } from "effect" * * // Create 123.45 (12345 with scale 2) * const decimal = BigDecimal.make(12345n, 2) - * console.log(BigDecimal.format(decimal)) // "123.45" + * decimal // => BigDecimal.fromStringUnsafe("123.45") * * // Create 42 (42 with scale 0) * const integer = BigDecimal.make(42n, 0) - * console.log(BigDecimal.format(integer)) // "42" + * integer // => BigDecimal.fromBigInt(42n) * ``` * * @see {@link fromBigInt} for constructing an integer decimal from a `bigint` @@ -173,18 +174,14 @@ const one = makeNormalizedUnsafe(bigint1, 0) * * **Example** (Normalizing trailing zeros) * - * ```ts + * ```ts import.meta.vitest * import { BigDecimal } from "effect" - * import * as assert from "node:assert" - * - * assert.deepStrictEqual( - * BigDecimal.normalize(BigDecimal.fromStringUnsafe("123.00000")), - * BigDecimal.normalize(BigDecimal.make(123n, 0)) - * ) - * assert.deepStrictEqual( - * BigDecimal.normalize(BigDecimal.fromStringUnsafe("12300000")), - * BigDecimal.normalize(BigDecimal.make(123n, -5)) - * ) + * + * const decimal = BigDecimal.normalize(BigDecimal.fromStringUnsafe("123.00000")) + * const decimalStorage = [decimal.value, decimal.scale] // => [123n, 0] + * + * const largeDecimal = BigDecimal.normalize(BigDecimal.fromStringUnsafe("12300000")) + * const largeDecimalStorage = [largeDecimal.value, largeDecimal.scale] // => [123n, -5] * ``` * * @see {@link format} for rendering normalized decimals as strings @@ -236,18 +233,18 @@ export const normalize = (self: BigDecimal): BigDecimal => { * * **Example** (Scaling decimal precision) * - * ```ts + * ```ts import.meta.vitest * import { BigDecimal } from "effect" * * const decimal = BigDecimal.fromNumberUnsafe(123.45) * * // Increase scale (add more precision) * const scaled = BigDecimal.scale(decimal, 4) - * console.log(BigDecimal.format(scaled)) // "123.4500" + * const scaledStorage = [scaled.value, scaled.scale] // => [1234500n, 4] * - * // Decrease scale (reduce precision, rounds down) + * // Decrease scale (reduce precision, truncating toward zero) * const reduced = BigDecimal.scale(decimal, 1) - * console.log(BigDecimal.format(reduced)) // "123.4" + * reduced // => BigDecimal.fromStringUnsafe("123.4") * ``` * * @see {@link round} for changing scale with configurable rounding @@ -280,14 +277,13 @@ export const scale: { * * **Example** (Adding decimals) * - * ```ts + * ```ts import.meta.vitest * import { BigDecimal } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual( - * BigDecimal.sum(BigDecimal.fromStringUnsafe("2"), BigDecimal.fromStringUnsafe("3")), - * BigDecimal.fromStringUnsafe("5") - * ) + * const result = BigDecimal.sum( + * BigDecimal.fromStringUnsafe("2"), + * BigDecimal.fromStringUnsafe("3") + * ) // => BigDecimal.fromBigInt(5n) * ``` * * @see {@link sumAll} for summing an iterable of `BigDecimal` values @@ -328,14 +324,14 @@ export const sum: { * * **Example** (Adding multiple decimals) * - * ```ts + * ```ts import.meta.vitest * import { BigDecimal } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual( - * BigDecimal.sumAll([BigDecimal.fromStringUnsafe("2"), BigDecimal.fromStringUnsafe("3"), BigDecimal.fromStringUnsafe("4")]), - * BigDecimal.fromStringUnsafe("9") - * ) + * const result = BigDecimal.sumAll([ + * BigDecimal.fromStringUnsafe("2"), + * BigDecimal.fromStringUnsafe("3"), + * BigDecimal.fromStringUnsafe("4") + * ]) // => BigDecimal.fromBigInt(9n) * ``` * * @see {@link sum} for adding two `BigDecimal` values @@ -360,14 +356,13 @@ export const sumAll = (collection: Iterable): BigDecimal => { * * **Example** (Multiplying decimals) * - * ```ts + * ```ts import.meta.vitest * import { BigDecimal } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual( - * BigDecimal.multiply(BigDecimal.fromStringUnsafe("2"), BigDecimal.fromStringUnsafe("3")), - * BigDecimal.fromStringUnsafe("6") - * ) + * const result = BigDecimal.multiply( + * BigDecimal.fromStringUnsafe("2"), + * BigDecimal.fromStringUnsafe("3") + * ) // => BigDecimal.fromBigInt(6n) * ``` * * @see {@link multiplyAll} for multiplying an iterable of `BigDecimal` values @@ -395,14 +390,14 @@ export const multiply: { * * **Example** (Multiplying multiple decimals) * - * ```ts + * ```ts import.meta.vitest * import { BigDecimal } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual( - * BigDecimal.multiplyAll([BigDecimal.fromStringUnsafe("2"), BigDecimal.fromStringUnsafe("3"), BigDecimal.fromStringUnsafe("4")]), - * BigDecimal.fromStringUnsafe("24") - * ) + * const result = BigDecimal.multiplyAll([ + * BigDecimal.fromStringUnsafe("2"), + * BigDecimal.fromStringUnsafe("3"), + * BigDecimal.fromStringUnsafe("4") + * ]) // => BigDecimal.fromBigInt(24n) * ``` * * @see {@link multiply} for multiplying two `BigDecimal` values @@ -430,14 +425,13 @@ export const multiplyAll = (collection: Iterable): BigDecimal => { * * **Example** (Subtracting decimals) * - * ```ts + * ```ts import.meta.vitest * import { BigDecimal } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual( - * BigDecimal.subtract(BigDecimal.fromStringUnsafe("2"), BigDecimal.fromStringUnsafe("3")), - * BigDecimal.fromStringUnsafe("-1") - * ) + * const result = BigDecimal.subtract( + * BigDecimal.fromStringUnsafe("2"), + * BigDecimal.fromStringUnsafe("3") + * ) // => BigDecimal.fromBigInt(-1n) * ``` * * @category math @@ -550,33 +544,14 @@ export const roundTerminal = (n: bigint): bigint => { * * **Example** (Dividing decimals safely) * - * ```ts + * ```ts import.meta.vitest * import { BigDecimal, Option } from "effect" * - * console.log( - * Option.getOrThrow( - * BigDecimal.divide( - * BigDecimal.fromStringUnsafe("6"), - * BigDecimal.fromStringUnsafe("3") - * ) - * ) - * ) // BigDecimal(2) - * console.log( - * Option.getOrThrow( - * BigDecimal.divide( - * BigDecimal.fromStringUnsafe("6"), - * BigDecimal.fromStringUnsafe("4") - * ) - * ) - * ) // BigDecimal(1.5) - * console.log( - * Option.isNone( - * BigDecimal.divide( - * BigDecimal.fromStringUnsafe("6"), - * BigDecimal.fromStringUnsafe("0") - * ) - * ) - * ) // true + * const six = BigDecimal.fromBigInt(6n) + * + * BigDecimal.divide(six, BigDecimal.fromBigInt(3n)) // => Option.some(BigDecimal.fromBigInt(2n)) + * BigDecimal.divide(six, BigDecimal.fromBigInt(4n)) // => Option.some(BigDecimal.fromStringUnsafe("1.5")) + * BigDecimal.divide(six, BigDecimal.fromBigInt(0n)) // => Option.none() * ``` * * @see {@link divideUnsafe} for division that throws when the divisor is zero @@ -624,11 +599,11 @@ export const divide: { * * **Example** (Dividing decimals unsafely) * - * ```ts + * ```ts import.meta.vitest * import { BigDecimal } from "effect" * - * console.log(BigDecimal.divideUnsafe(BigDecimal.fromStringUnsafe("6"), BigDecimal.fromStringUnsafe("3"))) // BigDecimal(2) - * console.log(BigDecimal.divideUnsafe(BigDecimal.fromStringUnsafe("6"), BigDecimal.fromStringUnsafe("4"))) // BigDecimal(1.5) + * BigDecimal.divideUnsafe(BigDecimal.fromStringUnsafe("6"), BigDecimal.fromStringUnsafe("3")) // => BigDecimal.fromBigInt(2n) + * BigDecimal.divideUnsafe(BigDecimal.fromStringUnsafe("6"), BigDecimal.fromStringUnsafe("4")) // => BigDecimal.fromStringUnsafe("1.5") * ``` * * @see {@link divide} for division that returns `Option.none` when the divisor is zero @@ -665,16 +640,16 @@ export const divideUnsafe: { * * **Example** (Comparing decimals) * - * ```ts + * ```ts import.meta.vitest * import { BigDecimal } from "effect" * * const a = BigDecimal.fromNumberUnsafe(1.5) * const b = BigDecimal.fromNumberUnsafe(2.3) * const c = BigDecimal.fromNumberUnsafe(1.5) * - * console.log(BigDecimal.Order(a, b)) // -1 (a < b) - * console.log(BigDecimal.Order(b, a)) // 1 (b > a) - * console.log(BigDecimal.Order(a, c)) // 0 (a === c) + * BigDecimal.Order(a, b) // => -1 + * BigDecimal.Order(b, a) // => 1 + * BigDecimal.Order(a, c) // => 0 * ``` * * @category instances @@ -706,22 +681,16 @@ export const Order: order.Order = order.make((self, that) => { * * **Example** (Checking less-than comparisons) * - * ```ts + * ```ts import.meta.vitest * import { BigDecimal } from "effect" - * import * as assert from "node:assert" - * - * assert.deepStrictEqual( - * BigDecimal.isLessThan(BigDecimal.fromStringUnsafe("2"), BigDecimal.fromStringUnsafe("3")), - * true - * ) - * assert.deepStrictEqual( - * BigDecimal.isLessThan(BigDecimal.fromStringUnsafe("3"), BigDecimal.fromStringUnsafe("3")), - * false - * ) - * assert.deepStrictEqual( - * BigDecimal.isLessThan(BigDecimal.fromStringUnsafe("4"), BigDecimal.fromStringUnsafe("3")), - * false - * ) + * + * const two = BigDecimal.fromStringUnsafe("2") + * const three = BigDecimal.fromStringUnsafe("3") + * const four = BigDecimal.fromStringUnsafe("4") + * + * BigDecimal.isLessThan(two, three) // => true + * BigDecimal.isLessThan(three, three) // => false + * BigDecimal.isLessThan(four, three) // => false * ``` * * @category predicates @@ -741,22 +710,16 @@ export const isLessThan: { * * **Example** (Checking less-than-or-equal comparisons) * - * ```ts + * ```ts import.meta.vitest * import { BigDecimal } from "effect" - * import * as assert from "node:assert" - * - * assert.deepStrictEqual( - * BigDecimal.isLessThanOrEqualTo(BigDecimal.fromStringUnsafe("2"), BigDecimal.fromStringUnsafe("3")), - * true - * ) - * assert.deepStrictEqual( - * BigDecimal.isLessThanOrEqualTo(BigDecimal.fromStringUnsafe("3"), BigDecimal.fromStringUnsafe("3")), - * true - * ) - * assert.deepStrictEqual( - * BigDecimal.isLessThanOrEqualTo(BigDecimal.fromStringUnsafe("4"), BigDecimal.fromStringUnsafe("3")), - * false - * ) + * + * const two = BigDecimal.fromStringUnsafe("2") + * const three = BigDecimal.fromStringUnsafe("3") + * const four = BigDecimal.fromStringUnsafe("4") + * + * BigDecimal.isLessThanOrEqualTo(two, three) // => true + * BigDecimal.isLessThanOrEqualTo(three, three) // => true + * BigDecimal.isLessThanOrEqualTo(four, three) // => false * ``` * * @category predicates @@ -776,22 +739,16 @@ export const isLessThanOrEqualTo: { * * **Example** (Checking greater-than comparisons) * - * ```ts + * ```ts import.meta.vitest * import { BigDecimal } from "effect" - * import * as assert from "node:assert" - * - * assert.deepStrictEqual( - * BigDecimal.isGreaterThan(BigDecimal.fromStringUnsafe("2"), BigDecimal.fromStringUnsafe("3")), - * false - * ) - * assert.deepStrictEqual( - * BigDecimal.isGreaterThan(BigDecimal.fromStringUnsafe("3"), BigDecimal.fromStringUnsafe("3")), - * false - * ) - * assert.deepStrictEqual( - * BigDecimal.isGreaterThan(BigDecimal.fromStringUnsafe("4"), BigDecimal.fromStringUnsafe("3")), - * true - * ) + * + * const two = BigDecimal.fromStringUnsafe("2") + * const three = BigDecimal.fromStringUnsafe("3") + * const four = BigDecimal.fromStringUnsafe("4") + * + * BigDecimal.isGreaterThan(two, three) // => false + * BigDecimal.isGreaterThan(three, three) // => false + * BigDecimal.isGreaterThan(four, three) // => true * ``` * * @category predicates @@ -811,22 +768,16 @@ export const isGreaterThan: { * * **Example** (Checking greater-than-or-equal comparisons) * - * ```ts + * ```ts import.meta.vitest * import { BigDecimal } from "effect" - * import * as assert from "node:assert" - * - * assert.deepStrictEqual( - * BigDecimal.isGreaterThanOrEqualTo(BigDecimal.fromStringUnsafe("2"), BigDecimal.fromStringUnsafe("3")), - * false - * ) - * assert.deepStrictEqual( - * BigDecimal.isGreaterThanOrEqualTo(BigDecimal.fromStringUnsafe("3"), BigDecimal.fromStringUnsafe("3")), - * true - * ) - * assert.deepStrictEqual( - * BigDecimal.isGreaterThanOrEqualTo(BigDecimal.fromStringUnsafe("4"), BigDecimal.fromStringUnsafe("3")), - * true - * ) + * + * const two = BigDecimal.fromStringUnsafe("2") + * const three = BigDecimal.fromStringUnsafe("3") + * const four = BigDecimal.fromStringUnsafe("4") + * + * BigDecimal.isGreaterThanOrEqualTo(two, three) // => false + * BigDecimal.isGreaterThanOrEqualTo(three, three) // => true + * BigDecimal.isGreaterThanOrEqualTo(four, three) // => true * ``` * * @category predicates @@ -846,18 +797,17 @@ export const isGreaterThanOrEqualTo: { * * **Example** (Checking decimal ranges) * - * ```ts + * ```ts import.meta.vitest * import { BigDecimal } from "effect" - * import * as assert from "node:assert" * * const between = BigDecimal.between({ * minimum: BigDecimal.fromStringUnsafe("1"), * maximum: BigDecimal.fromStringUnsafe("5") * }) * - * assert.deepStrictEqual(between(BigDecimal.fromStringUnsafe("3")), true) - * assert.deepStrictEqual(between(BigDecimal.fromStringUnsafe("0")), false) - * assert.deepStrictEqual(between(BigDecimal.fromStringUnsafe("6")), false) + * between(BigDecimal.fromStringUnsafe("3")) // => true + * between(BigDecimal.fromStringUnsafe("0")) // => false + * between(BigDecimal.fromStringUnsafe("6")) // => false * ``` * * @see {@link clamp} for forcing a `BigDecimal` into an inclusive range @@ -891,27 +841,17 @@ export const between: { * * **Example** (Clamping decimals to a range) * - * ```ts + * ```ts import.meta.vitest * import { BigDecimal } from "effect" - * import * as assert from "node:assert" * * const clamp = BigDecimal.clamp({ * minimum: BigDecimal.fromStringUnsafe("1"), * maximum: BigDecimal.fromStringUnsafe("5") * }) * - * assert.deepStrictEqual( - * clamp(BigDecimal.fromStringUnsafe("3")), - * BigDecimal.fromStringUnsafe("3") - * ) - * assert.deepStrictEqual( - * clamp(BigDecimal.fromStringUnsafe("0")), - * BigDecimal.fromStringUnsafe("1") - * ) - * assert.deepStrictEqual( - * clamp(BigDecimal.fromStringUnsafe("6")), - * BigDecimal.fromStringUnsafe("5") - * ) + * clamp(BigDecimal.fromStringUnsafe("3")) // => BigDecimal.fromBigInt(3n) + * clamp(BigDecimal.fromStringUnsafe("0")) // => BigDecimal.fromBigInt(1n) + * clamp(BigDecimal.fromStringUnsafe("6")) // => BigDecimal.fromBigInt(5n) * ``` * * @see {@link between} for checking whether a `BigDecimal` is already inside a range @@ -939,14 +879,13 @@ export const clamp: { * * **Example** (Selecting the smaller decimal) * - * ```ts + * ```ts import.meta.vitest * import { BigDecimal } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual( - * BigDecimal.min(BigDecimal.fromStringUnsafe("2"), BigDecimal.fromStringUnsafe("3")), - * BigDecimal.fromStringUnsafe("2") - * ) + * const result = BigDecimal.min( + * BigDecimal.fromStringUnsafe("2"), + * BigDecimal.fromStringUnsafe("3") + * ) // => BigDecimal.fromBigInt(2n) * ``` * * @see {@link max} for selecting the larger value @@ -968,14 +907,13 @@ export const min: { * * **Example** (Selecting the larger decimal) * - * ```ts + * ```ts import.meta.vitest * import { BigDecimal } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual( - * BigDecimal.max(BigDecimal.fromStringUnsafe("2"), BigDecimal.fromStringUnsafe("3")), + * const result = BigDecimal.max( + * BigDecimal.fromStringUnsafe("2"), * BigDecimal.fromStringUnsafe("3") - * ) + * ) // => BigDecimal.fromBigInt(3n) * ``` * * @see {@link min} for selecting the smaller value @@ -997,13 +935,12 @@ export const max: { * * **Example** (Reading decimal signs) * - * ```ts + * ```ts import.meta.vitest * import { BigDecimal } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(BigDecimal.sign(BigDecimal.fromStringUnsafe("-5")), -1) - * assert.deepStrictEqual(BigDecimal.sign(BigDecimal.fromStringUnsafe("0")), 0) - * assert.deepStrictEqual(BigDecimal.sign(BigDecimal.fromStringUnsafe("5")), 1) + * BigDecimal.sign(BigDecimal.fromStringUnsafe("-5")) // => -1 + * BigDecimal.sign(BigDecimal.fromStringUnsafe("0")) // => 0 + * BigDecimal.sign(BigDecimal.fromStringUnsafe("5")) // => 1 * ``` * * @category math @@ -1020,13 +957,12 @@ export const sign = (n: BigDecimal): Ordering => n.value === bigint0 ? 0 : n.val * * **Example** (Calculating absolute values) * - * ```ts + * ```ts import.meta.vitest * import { BigDecimal } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(BigDecimal.abs(BigDecimal.fromStringUnsafe("-5")), BigDecimal.fromStringUnsafe("5")) - * assert.deepStrictEqual(BigDecimal.abs(BigDecimal.fromStringUnsafe("0")), BigDecimal.fromStringUnsafe("0")) - * assert.deepStrictEqual(BigDecimal.abs(BigDecimal.fromStringUnsafe("5")), BigDecimal.fromStringUnsafe("5")) + * BigDecimal.abs(BigDecimal.fromStringUnsafe("-5")) // => BigDecimal.fromBigInt(5n) + * BigDecimal.abs(BigDecimal.fromStringUnsafe("0")) // => BigDecimal.fromBigInt(0n) + * BigDecimal.abs(BigDecimal.fromStringUnsafe("5")) // => BigDecimal.fromBigInt(5n) * ``` * * @category math @@ -1043,12 +979,11 @@ export const abs = (n: BigDecimal): BigDecimal => n.value < bigint0 ? make(-n.va * * **Example** (Negating decimals) * - * ```ts + * ```ts import.meta.vitest * import { BigDecimal } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(BigDecimal.negate(BigDecimal.fromStringUnsafe("3")), BigDecimal.fromStringUnsafe("-3")) - * assert.deepStrictEqual(BigDecimal.negate(BigDecimal.fromStringUnsafe("-6")), BigDecimal.fromStringUnsafe("6")) + * BigDecimal.negate(BigDecimal.fromStringUnsafe("3")) // => BigDecimal.fromBigInt(-3n) + * BigDecimal.negate(BigDecimal.fromStringUnsafe("-6")) // => BigDecimal.fromBigInt(6n) * ``` * * @category math @@ -1071,31 +1006,15 @@ export const negate = (n: BigDecimal): BigDecimal => make(-n.value, n.scale) * * **Example** (Computing remainders safely) * - * ```ts + * ```ts import.meta.vitest * import { BigDecimal, Option } from "effect" - * import * as assert from "node:assert" - * - * assert.deepStrictEqual( - * BigDecimal.remainder( - * BigDecimal.fromStringUnsafe("2"), - * BigDecimal.fromStringUnsafe("2") - * ), - * Option.some(BigDecimal.fromStringUnsafe("0")) - * ) - * assert.deepStrictEqual( - * BigDecimal.remainder( - * BigDecimal.fromStringUnsafe("3"), - * BigDecimal.fromStringUnsafe("2") - * ), - * Option.some(BigDecimal.fromStringUnsafe("1")) - * ) - * assert.deepStrictEqual( - * BigDecimal.remainder( - * BigDecimal.fromStringUnsafe("-4"), - * BigDecimal.fromStringUnsafe("2") - * ), - * Option.some(BigDecimal.fromStringUnsafe("0")) - * ) + * + * const two = BigDecimal.fromStringUnsafe("2") + * const three = BigDecimal.fromStringUnsafe("3") + * const zero = BigDecimal.fromStringUnsafe("0") + * + * BigDecimal.remainder(three, two) // => Option.some(BigDecimal.fromBigInt(1n)) + * BigDecimal.remainder(two, zero) // => Option.none() * ``` * * @see {@link remainderUnsafe} for remainder calculation that throws when the divisor is zero @@ -1131,22 +1050,13 @@ export const remainder: { * * **Example** (Computing remainders unsafely) * - * ```ts + * ```ts import.meta.vitest * import { BigDecimal } from "effect" - * import * as assert from "node:assert" - * - * assert.deepStrictEqual( - * BigDecimal.remainderUnsafe(BigDecimal.fromStringUnsafe("2"), BigDecimal.fromStringUnsafe("2")), - * BigDecimal.fromStringUnsafe("0") - * ) - * assert.deepStrictEqual( - * BigDecimal.remainderUnsafe(BigDecimal.fromStringUnsafe("3"), BigDecimal.fromStringUnsafe("2")), - * BigDecimal.fromStringUnsafe("1") - * ) - * assert.deepStrictEqual( - * BigDecimal.remainderUnsafe(BigDecimal.fromStringUnsafe("-4"), BigDecimal.fromStringUnsafe("2")), - * BigDecimal.fromStringUnsafe("0") - * ) + * + * BigDecimal.remainderUnsafe( + * BigDecimal.fromStringUnsafe("3"), + * BigDecimal.fromStringUnsafe("2") + * ) // => BigDecimal.fromBigInt(1n) * ``` * * @see {@link remainder} for returning `Option.none` when the divisor is zero @@ -1176,15 +1086,15 @@ export const remainderUnsafe: { * * **Example** (Checking decimal equivalence) * - * ```ts + * ```ts import.meta.vitest * import { BigDecimal } from "effect" * * const a = BigDecimal.fromStringUnsafe("1.50") * const b = BigDecimal.fromStringUnsafe("1.5") * const c = BigDecimal.fromStringUnsafe("2.0") * - * console.log(BigDecimal.Equivalence(a, b)) // true (1.50 === 1.5) - * console.log(BigDecimal.Equivalence(a, c)) // false (1.50 !== 2.0) + * BigDecimal.Equivalence(a, b) // => true + * BigDecimal.Equivalence(a, c) // => false * ``` * * @category instances @@ -1211,15 +1121,15 @@ export const Equivalence: Equ.Equivalence = Equ.make((self, that) => * * **Example** (Checking decimal equality) * - * ```ts + * ```ts import.meta.vitest * import { BigDecimal } from "effect" * * const a = BigDecimal.fromStringUnsafe("1.5") * const b = BigDecimal.fromStringUnsafe("1.50") * const c = BigDecimal.fromStringUnsafe("2.0") * - * console.log(BigDecimal.equals(a, b)) // true - * console.log(BigDecimal.equals(a, c)) // false + * BigDecimal.equals(a, b) // => true + * BigDecimal.equals(a, c) // => false * ``` * * @see {@link Equivalence} for passing decimal equality to APIs that require an `Equivalence` @@ -1241,14 +1151,14 @@ export const equals: { * * **Example** (Creating decimals from bigint) * - * ```ts + * ```ts import.meta.vitest * import { BigDecimal } from "effect" * * const decimal = BigDecimal.fromBigInt(123n) - * console.log(BigDecimal.format(decimal)) // "123" + * decimal // => BigDecimal.fromStringUnsafe("123") * * const largeBigInt = BigDecimal.fromBigInt(9007199254740991n) - * console.log(BigDecimal.format(largeBigInt)) // "9007199254740991" + * largeBigInt // => BigDecimal.fromStringUnsafe("9007199254740991") * ``` * * @see {@link make} for constructing a decimal with an explicit scale @@ -1274,12 +1184,11 @@ export const fromBigInt = (n: bigint): BigDecimal => make(n, 0) * * **Example** (Creating decimals from finite numbers) * - * ```ts + * ```ts import.meta.vitest * import { BigDecimal } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(BigDecimal.fromNumberUnsafe(123), BigDecimal.make(123n, 0)) - * assert.deepStrictEqual(BigDecimal.fromNumberUnsafe(123.456), BigDecimal.make(123456n, 3)) + * BigDecimal.fromNumberUnsafe(123) // => BigDecimal.fromBigInt(123n) + * BigDecimal.fromNumberUnsafe(123.456) // => BigDecimal.fromStringUnsafe("123.456") * ``` * * @see {@link fromNumber} for returning `Option.none` when the number is not finite @@ -1310,16 +1219,11 @@ export const fromNumberUnsafe = (n: number): BigDecimal => { * * **Example** (Creating decimals from numbers safely) * - * ```ts + * ```ts import.meta.vitest * import { BigDecimal, Option } from "effect" - * import * as assert from "node:assert" - * - * assert.deepStrictEqual(BigDecimal.fromNumber(123), Option.some(BigDecimal.make(123n, 0))) - * assert.deepStrictEqual( - * BigDecimal.fromNumber(123.456), - * Option.some(BigDecimal.make(123456n, 3)) - * ) - * assert.deepStrictEqual(BigDecimal.fromNumber(Infinity), Option.none()) + * + * BigDecimal.fromNumber(123.456) // => Option.some(BigDecimal.fromStringUnsafe("123.456")) + * BigDecimal.fromNumber(Infinity) // => Option.none() * ``` * * @see {@link fromNumberUnsafe} for throwing when the number is not finite @@ -1357,16 +1261,11 @@ export const fromNumber = (n: number): Option.Option => { * * **Example** (Parsing decimal strings safely) * - * ```ts + * ```ts import.meta.vitest * import { BigDecimal, Option } from "effect" - * import * as assert from "node:assert" - * - * assert.deepStrictEqual(BigDecimal.fromString("123"), Option.some(BigDecimal.make(123n, 0))) - * assert.deepStrictEqual( - * BigDecimal.fromString("123.456"), - * Option.some(BigDecimal.make(123456n, 3)) - * ) - * assert.deepStrictEqual(BigDecimal.fromString("123.abc"), Option.none()) + * + * BigDecimal.fromString("123.456") // => Option.some(BigDecimal.make(123456n, 3)) + * BigDecimal.fromString("123.abc") // => Option.none() * ``` * * @see {@link fromStringUnsafe} for parsing that throws on invalid input @@ -1435,13 +1334,11 @@ export const fromString = (s: string): Option.Option => { * * **Example** (Parsing decimal strings unsafely) * - * ```ts + * ```ts import.meta.vitest * import { BigDecimal } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(BigDecimal.fromStringUnsafe("123"), BigDecimal.make(123n, 0)) - * assert.deepStrictEqual(BigDecimal.fromStringUnsafe("123.456"), BigDecimal.make(123456n, 3)) - * assert.throws(() => BigDecimal.fromStringUnsafe("123.abc")) + * BigDecimal.fromStringUnsafe("123") // => BigDecimal.fromBigInt(123n) + * BigDecimal.fromStringUnsafe("123.456") // => BigDecimal.make(123456n, 3) * ``` * * @see {@link fromString} for returning `Option.none` on invalid input @@ -1468,13 +1365,12 @@ export const fromStringUnsafe = (s: string): BigDecimal => { * * **Example** (Formatting decimals) * - * ```ts + * ```ts import.meta.vitest * import { BigDecimal } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(BigDecimal.format(BigDecimal.fromStringUnsafe("-5")), "-5") - * assert.deepStrictEqual(BigDecimal.format(BigDecimal.fromStringUnsafe("123.456")), "123.456") - * assert.deepStrictEqual(BigDecimal.format(BigDecimal.fromStringUnsafe("-0.00000123")), "-0.00000123") + * BigDecimal.format(BigDecimal.fromStringUnsafe("-5")) // => "-5" + * BigDecimal.format(BigDecimal.fromStringUnsafe("123.456")) // => "123.456" + * BigDecimal.format(BigDecimal.fromStringUnsafe("-0.00000123")) // => "-0.00000123" * ``` * * @see {@link toExponential} for always rendering scientific notation @@ -1522,11 +1418,10 @@ export const format = (n: BigDecimal): string => { * * **Example** (Formatting decimals exponentially) * - * ```ts + * ```ts import.meta.vitest * import { BigDecimal } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(BigDecimal.toExponential(BigDecimal.make(123456n, -5)), "1.23456e+10") + * BigDecimal.toExponential(BigDecimal.make(123456n, -5)) // => "1.23456e+10" * ``` * * @see {@link format} for plain decimal formatting when possible @@ -1569,11 +1464,10 @@ export const toExponential = (n: BigDecimal): string => { * * **Example** (Converting decimals to numbers) * - * ```ts + * ```ts import.meta.vitest * import { BigDecimal } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(BigDecimal.toNumberUnsafe(BigDecimal.fromStringUnsafe("123.456")), 123.456) + * BigDecimal.toNumberUnsafe(BigDecimal.fromStringUnsafe("123.456")) // => 123.456 * ``` * * @see {@link format} for preserving decimal precision as text @@ -1592,13 +1486,12 @@ export const toNumberUnsafe = (n: BigDecimal): number => Number(format(n)) * * **Example** (Checking integer decimals) * - * ```ts + * ```ts import.meta.vitest * import { BigDecimal } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(BigDecimal.isInteger(BigDecimal.fromStringUnsafe("0")), true) - * assert.deepStrictEqual(BigDecimal.isInteger(BigDecimal.fromStringUnsafe("1")), true) - * assert.deepStrictEqual(BigDecimal.isInteger(BigDecimal.fromStringUnsafe("1.1")), false) + * BigDecimal.isInteger(BigDecimal.fromStringUnsafe("0")) // => true + * BigDecimal.isInteger(BigDecimal.fromStringUnsafe("1")) // => true + * BigDecimal.isInteger(BigDecimal.fromStringUnsafe("1.1")) // => false * ``` * * @category predicates @@ -1615,12 +1508,11 @@ export const isInteger = (n: BigDecimal): boolean => normalize(n).scale <= 0 * * **Example** (Checking zero decimals) * - * ```ts + * ```ts import.meta.vitest * import { BigDecimal } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(BigDecimal.isZero(BigDecimal.fromStringUnsafe("0")), true) - * assert.deepStrictEqual(BigDecimal.isZero(BigDecimal.fromStringUnsafe("1")), false) + * BigDecimal.isZero(BigDecimal.fromStringUnsafe("0")) // => true + * BigDecimal.isZero(BigDecimal.fromStringUnsafe("1")) // => false * ``` * * @category predicates @@ -1637,13 +1529,12 @@ export const isZero = (n: BigDecimal): boolean => n.value === bigint0 * * **Example** (Checking negative decimals) * - * ```ts + * ```ts import.meta.vitest * import { BigDecimal } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(BigDecimal.isNegative(BigDecimal.fromStringUnsafe("-1")), true) - * assert.deepStrictEqual(BigDecimal.isNegative(BigDecimal.fromStringUnsafe("0")), false) - * assert.deepStrictEqual(BigDecimal.isNegative(BigDecimal.fromStringUnsafe("1")), false) + * BigDecimal.isNegative(BigDecimal.fromStringUnsafe("-1")) // => true + * BigDecimal.isNegative(BigDecimal.fromStringUnsafe("0")) // => false + * BigDecimal.isNegative(BigDecimal.fromStringUnsafe("1")) // => false * ``` * * @category predicates @@ -1660,13 +1551,12 @@ export const isNegative = (n: BigDecimal): boolean => n.value < bigint0 * * **Example** (Checking positive decimals) * - * ```ts + * ```ts import.meta.vitest * import { BigDecimal } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(BigDecimal.isPositive(BigDecimal.fromStringUnsafe("-1")), false) - * assert.deepStrictEqual(BigDecimal.isPositive(BigDecimal.fromStringUnsafe("0")), false) - * assert.deepStrictEqual(BigDecimal.isPositive(BigDecimal.fromStringUnsafe("1")), true) + * BigDecimal.isPositive(BigDecimal.fromStringUnsafe("-1")) // => false + * BigDecimal.isPositive(BigDecimal.fromStringUnsafe("0")) // => false + * BigDecimal.isPositive(BigDecimal.fromStringUnsafe("1")) // => true * ``` * * @category predicates @@ -1726,18 +1616,14 @@ export type RoundingMode = * * **Example** (Rounding decimals) * - * ```ts + * ```ts import.meta.vitest * import { BigDecimal } from "effect" - * import * as assert from "node:assert" - * - * assert.deepStrictEqual( - * BigDecimal.round(BigDecimal.fromStringUnsafe("145"), { mode: "from-zero", scale: -1 }), - * BigDecimal.fromStringUnsafe("150") - * ) - * assert.deepStrictEqual( - * BigDecimal.round(BigDecimal.fromStringUnsafe("-14.5")), - * BigDecimal.fromStringUnsafe("-15") - * ) + * + * const positive = BigDecimal.round(BigDecimal.fromStringUnsafe("145"), { mode: "from-zero", scale: -1 }) + * positive // => BigDecimal.fromBigInt(150n) + * + * const negative = BigDecimal.round(BigDecimal.fromStringUnsafe("-14.5")) + * negative // => BigDecimal.fromBigInt(-15n) * ``` * * @see {@link ceil} for fixed rounding toward positive infinity @@ -1808,11 +1694,11 @@ export const round: { * * **Example** (Truncating decimals) * - * ```ts + * ```ts import.meta.vitest * import { BigDecimal } from "effect" * - * console.log(BigDecimal.truncate(BigDecimal.fromStringUnsafe("145"), -1)) // BigDecimal(140) - * console.log(BigDecimal.truncate(BigDecimal.fromStringUnsafe("-14.5"))) // BigDecimal(-14) + * BigDecimal.truncate(BigDecimal.fromStringUnsafe("145"), -1) // => BigDecimal.fromBigInt(140n) + * BigDecimal.truncate(BigDecimal.fromStringUnsafe("-14.5")) // => BigDecimal.fromBigInt(-14n) * ``` * * @see {@link round} for configurable rounding modes @@ -1853,15 +1739,11 @@ export const truncate: { * * **Example** (Rounding decimals up) * - * ```ts + * ```ts import.meta.vitest * import { BigDecimal } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual( - * BigDecimal.ceil(BigDecimal.fromStringUnsafe("145"), -1), - * BigDecimal.fromStringUnsafe("150") - * ) - * assert.deepStrictEqual(BigDecimal.ceil(BigDecimal.fromStringUnsafe("-14.5")), BigDecimal.fromStringUnsafe("-14")) + * BigDecimal.ceil(BigDecimal.fromStringUnsafe("145"), -1) // => BigDecimal.fromBigInt(150n) + * BigDecimal.ceil(BigDecimal.fromStringUnsafe("-14.5")) // => BigDecimal.fromBigInt(-14n) * ``` * * @category math @@ -1908,18 +1790,11 @@ export const digitAt: { * * **Example** (Rounding decimals down) * - * ```ts + * ```ts import.meta.vitest * import { BigDecimal } from "effect" - * import * as assert from "node:assert" - * - * assert.deepStrictEqual( - * BigDecimal.floor(BigDecimal.fromStringUnsafe("145"), -1), - * BigDecimal.fromStringUnsafe("140") - * ) - * assert.deepStrictEqual( - * BigDecimal.floor(BigDecimal.fromStringUnsafe("-14.5")), - * BigDecimal.fromStringUnsafe("-15") - * ) + * + * BigDecimal.floor(BigDecimal.fromStringUnsafe("145"), -1) // => BigDecimal.fromBigInt(140n) + * BigDecimal.floor(BigDecimal.fromStringUnsafe("-14.5")) // => BigDecimal.fromBigInt(-15n) * ``` * * @see {@link ceil} for rounding toward positive infinity diff --git a/repos/effect/packages/effect/src/BigInt.ts b/repos/effect/packages/effect/src/BigInt.ts index 34a973647c..18ac6ba68b 100644 --- a/repos/effect/packages/effect/src/BigInt.ts +++ b/repos/effect/packages/effect/src/BigInt.ts @@ -36,14 +36,11 @@ import * as Reducer from "./Reducer.ts" * * **Example** (Constructing bigints) * - * ```ts + * ```ts import.meta.vitest * import { BigInt } from "effect" * - * const bigInt = BigInt.BigInt(123) - * console.log(bigInt) // 123n - * - * const fromString = BigInt.BigInt("456") - * console.log(fromString) // 456n + * BigInt.BigInt(123) // => 123n + * BigInt.BigInt("456") // => 456n * ``` * * @category constructors @@ -64,12 +61,11 @@ const bigint2 = BigInt(2) * * **Example** (Checking for bigints) * - * ```ts + * ```ts import.meta.vitest * import { BigInt } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(BigInt.isBigInt(1n), true) - * assert.deepStrictEqual(BigInt.isBigInt(1), false) + * BigInt.isBigInt(1n) // => true + * BigInt.isBigInt(1) // => false * ``` * * @category guards @@ -87,11 +83,10 @@ export const isBigInt: (u: unknown) => u is bigint = predicate.isBigInt * * **Example** (Adding bigints) * - * ```ts + * ```ts import.meta.vitest * import { BigInt } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(BigInt.sum(2n, 3n), 5n) + * BigInt.sum(2n, 3n) // => 5n * ``` * * @see {@link sumAll} for summing an iterable of `bigint` values @@ -113,11 +108,10 @@ export const sum: { * * **Example** (Multiplying bigints) * - * ```ts + * ```ts import.meta.vitest * import { BigInt } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(BigInt.multiply(2n, 3n), 6n) + * BigInt.multiply(2n, 3n) // => 6n * ``` * * @see {@link multiplyAll} for multiplying an iterable of `bigint` values @@ -139,11 +133,10 @@ export const multiply: { * * **Example** (Subtracting bigints) * - * ```ts + * ```ts import.meta.vitest * import { BigInt } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(BigInt.subtract(2n, 3n), -1n) + * BigInt.subtract(2n, 3n) // => -1n * ``` * * @category math @@ -169,12 +162,11 @@ export const subtract: { * * **Example** (Dividing bigints safely) * - * ```ts + * ```ts import.meta.vitest * import { BigInt, Option } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(BigInt.divide(6n, 3n), Option.some(2n)) - * assert.deepStrictEqual(BigInt.divide(6n, 0n), Option.none()) + * BigInt.divide(6n, 3n) // => Option.some(2n) + * BigInt.divide(6n, 0n) // => Option.none() * ``` * * @see {@link divideUnsafe} for division that throws when the divisor is `0n` @@ -210,12 +202,11 @@ export const divide: { * * **Example** (Dividing bigints unsafely) * - * ```ts + * ```ts import.meta.vitest * import { BigInt } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(BigInt.divideUnsafe(6n, 3n), 2n) - * assert.deepStrictEqual(BigInt.divideUnsafe(6n, 4n), 1n) + * BigInt.divideUnsafe(6n, 3n) // => 2n + * BigInt.divideUnsafe(6n, 4n) // => 1n * ``` * * @see {@link divide} for division that returns `Option.none` when the divisor is `0n` @@ -237,11 +228,10 @@ export const divideUnsafe: { * * **Example** (Incrementing a bigint) * - * ```ts + * ```ts import.meta.vitest * import { BigInt } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(BigInt.increment(2n), 3n) + * BigInt.increment(2n) // => 3n * ``` * * @category math @@ -258,11 +248,10 @@ export const increment = (n: bigint): bigint => n + bigint1 * * **Example** (Decrementing a bigint) * - * ```ts + * ```ts import.meta.vitest * import { BigInt } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(BigInt.decrement(3n), 2n) + * BigInt.decrement(3n) // => 2n * ``` * * @category math @@ -280,16 +269,16 @@ export const decrement = (n: bigint): bigint => n - bigint1 * * **Example** (Comparing bigints with Order) * - * ```ts + * ```ts import.meta.vitest * import { BigInt } from "effect" * * const a = 123n * const b = 456n * const c = 123n * - * console.log(BigInt.Order(a, b)) // -1 (a < b) - * console.log(BigInt.Order(b, a)) // 1 (b > a) - * console.log(BigInt.Order(a, c)) // 0 (a === c) + * BigInt.Order(a, b) // => -1 + * BigInt.Order(b, a) // => 1 + * BigInt.Order(a, c) // => 0 * ``` * * @category instances @@ -307,11 +296,11 @@ export const Order: order.Order = order.BigInt * * **Example** (Comparing bigints for equivalence) * - * ```ts + * ```ts import.meta.vitest * import { BigInt } from "effect" * - * console.log(BigInt.Equivalence(1n, 1n)) // true - * console.log(BigInt.Equivalence(1n, 2n)) // false + * BigInt.Equivalence(1n, 1n) // => true + * BigInt.Equivalence(1n, 2n) // => false * ``` * * @category instances @@ -328,13 +317,12 @@ export const Equivalence: Equ.Equivalence = Equ.BigInt * * **Example** (Checking less-than comparisons) * - * ```ts + * ```ts import.meta.vitest * import { BigInt } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(BigInt.isLessThan(2n, 3n), true) - * assert.deepStrictEqual(BigInt.isLessThan(3n, 3n), false) - * assert.deepStrictEqual(BigInt.isLessThan(4n, 3n), false) + * BigInt.isLessThan(2n, 3n) // => true + * BigInt.isLessThan(3n, 3n) // => false + * BigInt.isLessThan(4n, 3n) // => false * ``` * * @category predicates @@ -354,13 +342,12 @@ export const isLessThan: { * * **Example** (Checking less-than-or-equal comparisons) * - * ```ts + * ```ts import.meta.vitest * import { BigInt } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(BigInt.isLessThanOrEqualTo(2n, 3n), true) - * assert.deepStrictEqual(BigInt.isLessThanOrEqualTo(3n, 3n), true) - * assert.deepStrictEqual(BigInt.isLessThanOrEqualTo(4n, 3n), false) + * BigInt.isLessThanOrEqualTo(2n, 3n) // => true + * BigInt.isLessThanOrEqualTo(3n, 3n) // => true + * BigInt.isLessThanOrEqualTo(4n, 3n) // => false * ``` * * @category predicates @@ -380,13 +367,12 @@ export const isLessThanOrEqualTo: { * * **Example** (Checking greater-than comparisons) * - * ```ts + * ```ts import.meta.vitest * import { BigInt } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(BigInt.isGreaterThan(2n, 3n), false) - * assert.deepStrictEqual(BigInt.isGreaterThan(3n, 3n), false) - * assert.deepStrictEqual(BigInt.isGreaterThan(4n, 3n), true) + * BigInt.isGreaterThan(2n, 3n) // => false + * BigInt.isGreaterThan(3n, 3n) // => false + * BigInt.isGreaterThan(4n, 3n) // => true * ``` * * @category predicates @@ -406,13 +392,12 @@ export const isGreaterThan: { * * **Example** (Checking greater-than-or-equal comparisons) * - * ```ts + * ```ts import.meta.vitest * import { BigInt } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(BigInt.isGreaterThanOrEqualTo(2n, 3n), false) - * assert.deepStrictEqual(BigInt.isGreaterThanOrEqualTo(3n, 3n), true) - * assert.deepStrictEqual(BigInt.isGreaterThanOrEqualTo(4n, 3n), true) + * BigInt.isGreaterThanOrEqualTo(2n, 3n) // => false + * BigInt.isGreaterThanOrEqualTo(3n, 3n) // => true + * BigInt.isGreaterThanOrEqualTo(4n, 3n) // => true * ``` * * @category predicates @@ -432,15 +417,14 @@ export const isGreaterThanOrEqualTo: { * * **Example** (Checking whether a bigint is within bounds) * - * ```ts + * ```ts import.meta.vitest * import { BigInt } from "effect" - * import * as assert from "node:assert" * * const between = BigInt.between({ minimum: 0n, maximum: 5n }) * - * assert.deepStrictEqual(between(3n), true) - * assert.deepStrictEqual(between(-1n), false) - * assert.deepStrictEqual(between(6n), false) + * between(3n) // => true + * between(-1n) // => false + * between(6n) // => false * ``` * * @see {@link clamp} for forcing a `bigint` into an inclusive range @@ -474,15 +458,14 @@ export const between: { * * **Example** (Clamping a bigint to bounds) * - * ```ts + * ```ts import.meta.vitest * import { BigInt } from "effect" - * import * as assert from "node:assert" * * const clamp = BigInt.clamp({ minimum: 1n, maximum: 5n }) * - * assert.equal(clamp(3n), 3n) - * assert.equal(clamp(0n), 1n) - * assert.equal(clamp(6n), 5n) + * clamp(3n) // => 3n + * clamp(0n) // => 1n + * clamp(6n) // => 5n * ``` * * @see {@link between} for checking whether a `bigint` is already inside a range @@ -510,11 +493,10 @@ export const clamp: { * * **Example** (Finding the minimum bigint) * - * ```ts + * ```ts import.meta.vitest * import { BigInt } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(BigInt.min(2n, 3n), 2n) + * BigInt.min(2n, 3n) // => 2n * ``` * * @see {@link max} for selecting the larger value @@ -536,11 +518,10 @@ export const min: { * * **Example** (Finding the maximum bigint) * - * ```ts + * ```ts import.meta.vitest * import { BigInt } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(BigInt.max(2n, 3n), 3n) + * BigInt.max(2n, 3n) // => 3n * ``` * * @see {@link min} for selecting the smaller value @@ -562,13 +543,12 @@ export const max: { * * **Example** (Determining bigint signs) * - * ```ts + * ```ts import.meta.vitest * import { BigInt } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(BigInt.sign(-5n), -1) - * assert.deepStrictEqual(BigInt.sign(0n), 0) - * assert.deepStrictEqual(BigInt.sign(5n), 1) + * BigInt.sign(-5n) // => -1 + * BigInt.sign(0n) // => 0 + * BigInt.sign(5n) // => 1 * ``` * * @category math @@ -585,13 +565,12 @@ export const sign = (n: bigint): Ordering => order.BigInt(n, bigint0) * * **Example** (Calculating absolute values) * - * ```ts + * ```ts import.meta.vitest * import { BigInt } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(BigInt.abs(-5n), 5n) - * assert.deepStrictEqual(BigInt.abs(0n), 0n) - * assert.deepStrictEqual(BigInt.abs(5n), 5n) + * BigInt.abs(-5n) // => 5n + * BigInt.abs(0n) // => 0n + * BigInt.abs(5n) // => 5n * ``` * * @category math @@ -608,13 +587,12 @@ export const abs = (n: bigint): bigint => (n < bigint0 ? -n : n) * * **Example** (Calculating greatest common divisors) * - * ```ts + * ```ts import.meta.vitest * import { BigInt } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(BigInt.gcd(2n, 3n), 1n) - * assert.deepStrictEqual(BigInt.gcd(2n, 4n), 2n) - * assert.deepStrictEqual(BigInt.gcd(16n, 24n), 8n) + * BigInt.gcd(2n, 3n) // => 1n + * BigInt.gcd(2n, 4n) // => 2n + * BigInt.gcd(16n, 24n) // => 8n * ``` * * @see {@link lcm} for computing the least common multiple @@ -631,7 +609,7 @@ export const gcd: { that = self % that self = t } - return self + return abs(self) }) /** @@ -643,13 +621,12 @@ export const gcd: { * * **Example** (Calculating least common multiples) * - * ```ts + * ```ts import.meta.vitest * import { BigInt } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(BigInt.lcm(2n, 3n), 6n) - * assert.deepStrictEqual(BigInt.lcm(2n, 4n), 4n) - * assert.deepStrictEqual(BigInt.lcm(16n, 24n), 48n) + * BigInt.lcm(2n, 3n) // => 6n + * BigInt.lcm(2n, 4n) // => 4n + * BigInt.lcm(16n, 24n) // => 48n * ``` * * @see {@link gcd} for computing the greatest common divisor @@ -660,7 +637,11 @@ export const gcd: { export const lcm: { (that: bigint): (self: bigint) => bigint (self: bigint, that: bigint): bigint -} = dual(2, (self: bigint, that: bigint): bigint => (self * that) / gcd(self, that)) +} = dual( + 2, + (self: bigint, that: bigint): bigint => + self === bigint0 || that === bigint0 ? bigint0 : abs((self * that) / gcd(self, that)) +) /** * Returns the integer square root of a non-negative `bigint`. @@ -682,13 +663,12 @@ export const lcm: { * * **Example** (Calculating square roots unsafely) * - * ```ts + * ```ts import.meta.vitest * import { BigInt } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(BigInt.sqrtUnsafe(4n), 2n) - * assert.deepStrictEqual(BigInt.sqrtUnsafe(9n), 3n) - * assert.deepStrictEqual(BigInt.sqrtUnsafe(16n), 4n) + * BigInt.sqrtUnsafe(4n) // => 2n + * BigInt.sqrtUnsafe(9n) // => 3n + * BigInt.sqrtUnsafe(16n) // => 4n * ``` * * @see {@link sqrt} for returning `Option.none` when the input is negative @@ -726,13 +706,13 @@ export const sqrtUnsafe = (n: bigint): bigint => { * * **Example** (Calculating square roots safely) * - * ```ts - * import { BigInt } from "effect" + * ```ts import.meta.vitest + * import { BigInt, Option } from "effect" * - * BigInt.sqrt(4n) // Option.some(2n) - * BigInt.sqrt(9n) // Option.some(3n) - * BigInt.sqrt(16n) // Option.some(4n) - * BigInt.sqrt(-1n) // Option.none() + * BigInt.sqrt(4n) // => Option.some(2n) + * BigInt.sqrt(9n) // => Option.some(3n) + * BigInt.sqrt(16n) // => Option.some(4n) + * BigInt.sqrt(-1n) // => Option.none() * ``` * * @see {@link sqrtUnsafe} for square root computation that throws on negative input @@ -753,11 +733,10 @@ export const sqrt = (n: bigint): Option.Option => * * **Example** (Summing iterable bigints) * - * ```ts + * ```ts import.meta.vitest * import { BigInt } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(BigInt.sumAll([2n, 3n, 4n]), 9n) + * BigInt.sumAll([2n, 3n, 4n]) // => 9n * ``` * * @see {@link sum} for adding two `bigint` values @@ -783,11 +762,10 @@ export const sumAll = (collection: Iterable): bigint => { * * **Example** (Multiplying iterable bigints) * - * ```ts + * ```ts import.meta.vitest * import { BigInt } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(BigInt.multiplyAll([2n, 3n, 4n]), 24n) + * BigInt.multiplyAll([2n, 3n, 4n]) // => 24n * ``` * * @see {@link multiply} for multiplying two `bigint` values @@ -822,12 +800,12 @@ export const multiplyAll = (collection: Iterable): bigint => { * * **Example** (Converting bigints to numbers) * - * ```ts - * import { BigInt as BI } from "effect" + * ```ts import.meta.vitest + * import { BigInt as BI, Option } from "effect" * - * BI.toNumber(42n) // Option.some(42) - * BI.toNumber(BigInt(Number.MAX_SAFE_INTEGER) + 1n) // Option.none() - * BI.toNumber(BigInt(Number.MIN_SAFE_INTEGER) - 1n) // Option.none() + * BI.toNumber(42n) // => Option.some(42) + * BI.toNumber(9007199254740992n) // => Option.none() + * BI.toNumber(-9007199254740992n) // => Option.none() * ``` * * @see {@link fromNumber} for converting a safe integer number to `bigint` @@ -856,12 +834,12 @@ export const toNumber = (b: bigint): Option.Option => { * * **Example** (Parsing strings as bigints) * - * ```ts - * import { BigInt } from "effect" + * ```ts import.meta.vitest + * import { BigInt, Option } from "effect" * - * BigInt.fromString("42") // Option.some(42n) - * BigInt.fromString(" ") // Option.none() - * BigInt.fromString("a") // Option.none() + * BigInt.fromString("42") // => Option.some(42n) + * BigInt.fromString(" ") // => Option.none() + * BigInt.fromString("a") // => Option.none() * ``` * * @see {@link BigInt} for native constructor coercion that throws on invalid input @@ -894,13 +872,12 @@ export const fromString = (s: string): Option.Option => { * * **Example** (Converting numbers to bigints) * - * ```ts - * import { BigInt } from "effect" - * - * BigInt.fromNumber(42) // Option.some(42n) + * ```ts import.meta.vitest + * import { BigInt, Option } from "effect" * - * BigInt.fromNumber(Number.MAX_SAFE_INTEGER + 1) // Option.none() - * BigInt.fromNumber(Number.MIN_SAFE_INTEGER - 1) // Option.none() + * BigInt.fromNumber(42) // => Option.some(42n) + * BigInt.fromNumber(Number.MAX_SAFE_INTEGER + 1) // => Option.none() + * BigInt.fromNumber(Number.MIN_SAFE_INTEGER - 1) // => Option.none() * ``` * * @see {@link toNumber} for converting `bigint` values back to safe integer numbers @@ -935,12 +912,11 @@ export function fromNumber(n: number): Option.Option { * * **Example** (Calculating remainders) * - * ```ts + * ```ts import.meta.vitest * import { BigInt } from "effect" * - * BigInt.remainder(10n, 3n) // 1n - * - * BigInt.remainder(15n, 4n) // 3n + * BigInt.remainder(10n, 3n) // => 1n + * BigInt.remainder(15n, 4n) // => 3n * ``` * * @see {@link divide} for quotient calculation with division-by-zero represented as `Option.none` diff --git a/repos/effect/packages/effect/src/Boolean.ts b/repos/effect/packages/effect/src/Boolean.ts index d69e52a27f..405e7b35af 100644 --- a/repos/effect/packages/effect/src/Boolean.ts +++ b/repos/effect/packages/effect/src/Boolean.ts @@ -31,17 +31,12 @@ import * as Reducer from "./Reducer.ts" * * **Example** (Coercing values to booleans) * - * ```ts + * ```ts import.meta.vitest * import { Boolean } from "effect" * - * const bool = Boolean.Boolean(1) - * console.log(bool) // true - * - * const fromString = Boolean.Boolean("false") - * console.log(fromString) // true (non-empty string) - * - * const fromZero = Boolean.Boolean(0) - * console.log(fromZero) // false + * Boolean.Boolean(1) // => true + * Boolean.Boolean("false") // => true + * Boolean.Boolean(0) // => false * ``` * * @category constructors @@ -58,12 +53,11 @@ export const Boolean = globalThis.Boolean * * **Example** (Checking for booleans) * - * ```ts + * ```ts import.meta.vitest * import { Boolean } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(Boolean.isBoolean(true), true) - * assert.deepStrictEqual(Boolean.isBoolean("true"), false) + * Boolean.isBoolean(true) // => true + * Boolean.isBoolean("true") // => false * ``` * * @category guards @@ -80,17 +74,13 @@ export const isBoolean: (input: unknown) => input is boolean = predicate.isBoole * * **Example** (Pattern matching on booleans) * - * ```ts + * ```ts import.meta.vitest * import { Boolean } from "effect" - * import * as assert from "node:assert" - * - * assert.deepStrictEqual( - * Boolean.match(true, { - * onFalse: () => "It's false!", - * onTrue: () => "It's true!" - * }), - * "It's true!" - * ) + * + * Boolean.match(true, { + * onFalse: () => "It's false!", + * onTrue: () => "It's true!" + * }) // => "It's true!" * ``` * * @category pattern matching @@ -121,12 +111,12 @@ export const match: { * * **Example** (Comparing booleans) * - * ```ts + * ```ts import.meta.vitest * import { Boolean } from "effect" * - * console.log(Boolean.Order(false, true)) // -1 (false < true) - * console.log(Boolean.Order(true, false)) // 1 (true > false) - * console.log(Boolean.Order(true, true)) // 0 (true === true) + * Boolean.Order(false, true) // => -1 + * Boolean.Order(true, false) // => 1 + * Boolean.Order(true, true) // => 0 * ``` * * @category instances @@ -144,11 +134,11 @@ export const Order: order.Order = order.Boolean * * **Example** (Comparing booleans for equivalence) * - * ```ts + * ```ts import.meta.vitest * import { Boolean } from "effect" * - * console.log(Boolean.Equivalence(true, true)) // true - * console.log(Boolean.Equivalence(true, false)) // false + * Boolean.Equivalence(true, true) // => true + * Boolean.Equivalence(true, false) // => false * ``` * * @category instances @@ -165,12 +155,11 @@ export const Equivalence: Equ.Equivalence = Equ.Boolean * * **Example** (Negating booleans) * - * ```ts + * ```ts import.meta.vitest * import { Boolean } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(Boolean.not(true), false) - * assert.deepStrictEqual(Boolean.not(false), true) + * Boolean.not(true) // => false + * Boolean.not(false) // => true * ``` * * @category combinators @@ -191,14 +180,13 @@ export const not = (self: boolean): boolean => !self * * **Example** (Combining booleans with AND) * - * ```ts + * ```ts import.meta.vitest * import { Boolean } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(Boolean.and(true, true), true) - * assert.deepStrictEqual(Boolean.and(true, false), false) - * assert.deepStrictEqual(Boolean.and(false, true), false) - * assert.deepStrictEqual(Boolean.and(false, false), false) + * Boolean.and(true, true) // => true + * Boolean.and(true, false) // => false + * Boolean.and(false, true) // => false + * Boolean.and(false, false) // => false * ``` * * @category combinators @@ -218,14 +206,13 @@ export const and: { * * **Example** (Combining booleans with NAND) * - * ```ts + * ```ts import.meta.vitest * import { Boolean } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(Boolean.nand(true, true), false) - * assert.deepStrictEqual(Boolean.nand(true, false), true) - * assert.deepStrictEqual(Boolean.nand(false, true), true) - * assert.deepStrictEqual(Boolean.nand(false, false), true) + * Boolean.nand(true, true) // => false + * Boolean.nand(true, false) // => true + * Boolean.nand(false, true) // => true + * Boolean.nand(false, false) // => true * ``` * * @category combinators @@ -245,14 +232,13 @@ export const nand: { * * **Example** (Combining booleans with OR) * - * ```ts + * ```ts import.meta.vitest * import { Boolean } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(Boolean.or(true, true), true) - * assert.deepStrictEqual(Boolean.or(true, false), true) - * assert.deepStrictEqual(Boolean.or(false, true), true) - * assert.deepStrictEqual(Boolean.or(false, false), false) + * Boolean.or(true, true) // => true + * Boolean.or(true, false) // => true + * Boolean.or(false, true) // => true + * Boolean.or(false, false) // => false * ``` * * @category combinators @@ -272,14 +258,13 @@ export const or: { * * **Example** (Combining booleans with NOR) * - * ```ts + * ```ts import.meta.vitest * import { Boolean } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(Boolean.nor(true, true), false) - * assert.deepStrictEqual(Boolean.nor(true, false), false) - * assert.deepStrictEqual(Boolean.nor(false, true), false) - * assert.deepStrictEqual(Boolean.nor(false, false), true) + * Boolean.nor(true, true) // => false + * Boolean.nor(true, false) // => false + * Boolean.nor(false, true) // => false + * Boolean.nor(false, false) // => true * ``` * * @category combinators @@ -299,14 +284,13 @@ export const nor: { * * **Example** (Combining booleans with XOR) * - * ```ts + * ```ts import.meta.vitest * import { Boolean } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(Boolean.xor(true, true), false) - * assert.deepStrictEqual(Boolean.xor(true, false), true) - * assert.deepStrictEqual(Boolean.xor(false, true), true) - * assert.deepStrictEqual(Boolean.xor(false, false), false) + * Boolean.xor(true, true) // => false + * Boolean.xor(true, false) // => true + * Boolean.xor(false, true) // => true + * Boolean.xor(false, false) // => false * ``` * * @category combinators @@ -326,14 +310,13 @@ export const xor: { * * **Example** (Checking boolean equivalence) * - * ```ts + * ```ts import.meta.vitest * import { Boolean } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(Boolean.eqv(true, true), true) - * assert.deepStrictEqual(Boolean.eqv(true, false), false) - * assert.deepStrictEqual(Boolean.eqv(false, true), false) - * assert.deepStrictEqual(Boolean.eqv(false, false), true) + * Boolean.eqv(true, true) // => true + * Boolean.eqv(true, false) // => false + * Boolean.eqv(false, true) // => false + * Boolean.eqv(false, false) // => true * ``` * * @category combinators @@ -353,14 +336,13 @@ export const eqv: { * * **Example** (Checking boolean implication) * - * ```ts + * ```ts import.meta.vitest * import { Boolean } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(Boolean.implies(true, true), true) - * assert.deepStrictEqual(Boolean.implies(true, false), false) - * assert.deepStrictEqual(Boolean.implies(false, true), true) - * assert.deepStrictEqual(Boolean.implies(false, false), true) + * Boolean.implies(true, true) // => true + * Boolean.implies(true, false) // => false + * Boolean.implies(false, true) // => true + * Boolean.implies(false, false) // => true * ``` * * @category combinators @@ -380,12 +362,11 @@ export const implies: { * * **Example** (Checking every boolean) * - * ```ts + * ```ts import.meta.vitest * import { Boolean } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(Boolean.every([true, true, true]), true) - * assert.deepStrictEqual(Boolean.every([true, false, true]), false) + * Boolean.every([true, true, true]) // => true + * Boolean.every([true, false, true]) // => false * ``` * * @see {@link some} for checking whether at least one value is `true` @@ -412,12 +393,11 @@ export const every = (collection: Iterable): boolean => { * * **Example** (Checking some booleans) * - * ```ts + * ```ts import.meta.vitest * import { Boolean } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(Boolean.some([true, false, true]), true) - * assert.deepStrictEqual(Boolean.some([false, false, false]), false) + * Boolean.some([true, false, true]) // => true + * Boolean.some([false, false, false]) // => false * ``` * * @see {@link every} for checking whether all values are `true` diff --git a/repos/effect/packages/effect/src/Brand.ts b/repos/effect/packages/effect/src/Brand.ts index 39789b23af..1cbba59fdf 100644 --- a/repos/effect/packages/effect/src/Brand.ts +++ b/repos/effect/packages/effect/src/Brand.ts @@ -12,7 +12,7 @@ import * as Option from "./Option.ts" import * as Result from "./Result.ts" import type * as Schema from "./Schema.ts" import * as SchemaAST from "./SchemaAST.ts" -import type * as SchemaIssue from "./SchemaIssue.ts" +import * as SchemaIssue from "./SchemaIssue.ts" import type * as Types from "./Types.ts" const TypeId = "~effect/Brand" @@ -92,8 +92,8 @@ export interface Constructor> { * * **Details** * - * The error wraps a `SchemaIssue.Issue`, exposes `message` through - * `issue.toString()`, and formats as `BrandError()`. + * The error wraps a `SchemaIssue.Issue`, renders `message` with the default + * schema issue formatter, and formats as `BrandError()`. * * **Gotchas** * @@ -131,7 +131,7 @@ export class BrandError { * @since 4.0.0 */ get message() { - return this.issue.toString() + return SchemaIssue.defaultFormatter(this.issue) } /** * Formats the brand error together with its validation message. diff --git a/repos/effect/packages/effect/src/Cache.ts b/repos/effect/packages/effect/src/Cache.ts index 21fb2328b3..d1e3091b24 100644 --- a/repos/effect/packages/effect/src/Cache.ts +++ b/repos/effect/packages/effect/src/Cache.ts @@ -9,7 +9,6 @@ * @since 4.0.0 */ import * as Context from "./Context.ts" -import * as Deferred from "./Deferred.ts" import * as Duration from "./Duration.ts" import type * as Effect from "./Effect.ts" import type * as Exit from "./Exit.ts" @@ -33,7 +32,7 @@ const TypeId = "~effect/Cache" * * **Example** (Creating a basic cache) * - * ```ts + * ```ts import.meta.vitest * import { Cache, Effect } from "effect" * * // Basic cache with string keys and number values @@ -50,12 +49,15 @@ const TypeId = "~effect/Cache" * * return [value1, value2, value3] * }) + * + * const actual = await Effect.runPromise(program) + * actual // => [5, 5, 5] * ``` * * **Example** (Handling lookup failures) * - * ```ts - * import { Cache, Effect } from "effect" + * ```ts import.meta.vitest + * import { Cache, Effect, Exit } from "effect" * * // Cache with error handling * const program = Effect.gen(function*() { @@ -68,16 +70,19 @@ const TypeId = "~effect/Cache" * }) * * // Handle successful and failed lookups - * const success = yield* Cache.get(cache, "test") // 4 - * const failure = yield* Effect.exit(Cache.get(cache, "error")) // Exit.fail + * const success = yield* Cache.get(cache, "test") + * const failure = yield* Effect.exit(Cache.get(cache, "error")) * - * return { success, failure } + * return [success, failure] as const * }) + * + * const actual = await Effect.runPromise(program) + * actual // => [4, Exit.fail("Lookup failed")] * ``` * * **Example** (Using complex keys with TTL) * - * ```ts + * ```ts import.meta.vitest * import { Cache, Data, Duration, Effect } from "effect" * * // Cache with complex key types and TTL @@ -93,8 +98,11 @@ const TypeId = "~effect/Cache" * const userId = new UserId({ id: 123 }) * const userName = yield* Cache.get(userCache, userId) * - * return userName // "User-123" + * return userName * }) + * + * const actual = await Effect.runPromise(program) + * actual // => "User-123" * ``` * * @category models @@ -129,7 +137,9 @@ export interface Cache ex */ export interface Entry { expiresAt: number | undefined - readonly deferred: Deferred.Deferred + awaiters: number + readonly fiber: Fiber.Fiber + await(this: Entry): Effect.Effect } /** @@ -147,11 +157,11 @@ export interface Entry { * * **Example** (Configuring dynamic time to live) * - * ```ts + * ```ts import.meta.vitest * import { Cache, Effect, Exit } from "effect" * * // Cache with TTL based on computed value - * const userCache = Effect.gen(function*() { + * const program = Effect.gen(function*() { * const cache = yield* Cache.makeWith( * (id: number) => Effect.succeed({ id, active: id % 2 === 0 }), * { @@ -166,8 +176,11 @@ export interface Entry { * } * ) * - * return cache + * return cache.capacity * }) + * + * const actual = await Effect.runPromise(program) + * actual // => 1000 * ``` * * @see {@link make} for a simpler cache constructor with a fixed time-to-live for all entries @@ -214,7 +227,7 @@ export const makeWith = < * * **Example** (Creating a basic cache) * - * ```ts + * ```ts import.meta.vitest * import { Cache, Effect } from "effect" * * // Basic cache with string keys @@ -226,13 +239,16 @@ export const makeWith = < * * const result1 = yield* Cache.get(cache, "hello") * const result2 = yield* Cache.get(cache, "world") - * console.log({ result1, result2 }) // { result1: 5, result2: 5 } + * return { result1, result2 } * }) + * + * const actual = await Effect.runPromise(program) + * actual // => { result1: 5, result2: 5 } * ``` * * **Example** (Creating a cache with TTL) * - * ```ts + * ```ts import.meta.vitest * import { Cache, Effect } from "effect" * * const program = Effect.gen(function*() { @@ -258,11 +274,12 @@ export const makeWith = < * }) * * const user1 = yield* Cache.get(cache, 123) - * console.log(user1) // { name: "Ada", email: "ada@example.com" } - * * const user2 = yield* Cache.get(cache, 123) - * console.log(user2) // { name: "Ada", email: "ada@example.com" } + * return [user1, user2, user1 === user2] as const * }) + * + * const actual = await Effect.runPromise(program) + * actual // => [{ name: "Ada", email: "ada@example.com" }, { name: "Ada", email: "ada@example.com" }, true] * ``` * * @category constructors @@ -288,7 +305,7 @@ export const make = < > => makeWith(options.lookup, { ...options, - timeToLive: options.timeToLive ? () => options.timeToLive! : defaultTimeToLive + timeToLive: options.timeToLive !== undefined ? () => options.timeToLive! : defaultTimeToLive }) const Proto = { @@ -317,7 +334,7 @@ const defaultTimeToLive = (_: Exit.Exit, _key: unknown): Duration.Du * * **Example** (Getting cached values) * - * ```ts + * ```ts import.meta.vitest * import { Cache, Effect } from "effect" * * const program = Effect.gen(function*() { @@ -328,20 +345,21 @@ const defaultTimeToLive = (_: Exit.Exit, _key: unknown): Duration.Du * * // Cache miss - triggers lookup function * const result1 = yield* Cache.get(cache, "hello") - * console.log(result1) // 5 * * // Cache hit - returns cached value without lookup * const result2 = yield* Cache.get(cache, "hello") - * console.log(result2) // 5 (from cache) * * return { result1, result2 } * }) + * + * const actual = await Effect.runPromise(program) + * actual // => { result1: 5, result2: 5 } * ``` * * **Example** (Handling lookup failures) * - * ```ts - * import { Cache, Effect } from "effect" + * ```ts import.meta.vitest + * import { Cache, Effect, Exit } from "effect" * * // Error handling when lookup fails * const program = Effect.gen(function*() { @@ -355,17 +373,19 @@ const defaultTimeToLive = (_: Exit.Exit, _key: unknown): Duration.Du * * // Successful lookup * const success = yield* Cache.get(cache, "hello") - * console.log(success) // 5 * * // Failed lookup - returns error * const failure = yield* Effect.exit(Cache.get(cache, "error")) - * console.log(failure) // Exit.fail("Lookup failed") + * return [success, failure] as const * }) + * + * const actual = await Effect.runPromise(program) + * actual // => [5, Exit.fail("Lookup failed")] * ``` * * **Example** (Sharing concurrent lookups) * - * ```ts + * ```ts import.meta.vitest * import { Cache, Effect } from "effect" * * // Concurrent access - multiple gets of same key only invoke lookup once @@ -387,9 +407,11 @@ const defaultTimeToLive = (_: Exit.Exit, _key: unknown): Duration.Du * Cache.get(cache, "hello") * ], { concurrency: "unbounded" }) * - * console.log(results) // [5, 5, 5] - * console.log(lookupCount) // 1 (lookup called only once) + * return { results, lookupCount } * }) + * + * const actual = await Effect.runPromise(program) + * actual // => { results: [5, 5, 5], lookupCount: 1 } * ``` * * @category combinators @@ -407,30 +429,58 @@ export const get: { // Move the entry to the end of the map to keep it fresh MutableHashMap.remove(self.map, key) MutableHashMap.set(self.map, key, oentry.value) - return Deferred.await(oentry.value.deferred) - } - const deferred = Deferred.makeUnsafe() - const entry: Entry = { - expiresAt: undefined, - deferred + return oentry.value.await() } - MutableHashMap.set(self.map, key, entry) - if (Number.isFinite(self.capacity)) { - checkCapacity(self) - } - return effect.onExit(self.lookup(key), (exit) => { - Deferred.doneUnsafe(deferred, exit) + const entry = new EntryImpl(fiber, self.lookup(key)) + entry.fiber.addObserver((exit) => { + if (effect.exitHasInterrupts(exit)) { + const current = MutableHashMap.get(self.map, key) + if (Option.isSome(current) && current.value === entry) { + MutableHashMap.remove(self.map, key) + } + return + } const ttl = self.timeToLive(exit, key) if (Duration.isFinite(ttl)) { entry.expiresAt = fiber.getRef(effect.ClockRef).currentTimeMillisUnsafe() + Duration.toMillis(ttl) } else if (Duration.isZero(ttl)) { MutableHashMap.remove(self.map, key) } - return effect.void }) + MutableHashMap.set(self.map, key, entry) + if (Number.isFinite(self.capacity)) { + checkCapacity(self) + } + return entry.await() }) ) +class EntryImpl implements Entry { + expiresAt: number | undefined + awaiters: number + fiber: Fiber.Fiber + + constructor( + parent: Fiber.Fiber, + valueEffect: Effect.Effect + ) { + this.fiber = effect.forkUnsafe(parent, valueEffect, true, true) + this.awaiters = 0 + this.expiresAt = undefined + } + + await(): Effect.Effect { + const exit = this.fiber.pollUnsafe() + if (exit) return exit + this.awaiters++ + return effect.onExit(effect.fiberJoin(this.fiber), () => { + this.awaiters-- + if (this.awaiters > 0 || this.fiber.pollUnsafe()) return effect.void + return effect.fiberInterrupt(this.fiber) + }) + } +} + const hasExpired = (entry: Entry, fiber: Fiber.Fiber): boolean => { if (entry.expiresAt === undefined) { return false @@ -461,8 +511,8 @@ const checkCapacity = (self: Cache) => { * * **Example** (Reading cached values without lookup) * - * ```ts - * import { Cache, Effect } from "effect" + * ```ts import.meta.vitest + * import { Cache, Effect, Option } from "effect" * * const program = Effect.gen(function*() { * const cache = yield* Cache.make({ @@ -472,23 +522,23 @@ const checkCapacity = (self: Cache) => { * * // No value in cache yet - returns None without lookup * const empty = yield* Cache.getOption(cache, "hello") - * console.log(empty) // Option.none() * * // Populate cache using get * yield* Cache.get(cache, "hello") * * // Now getOption returns the cached value * const cached = yield* Cache.getOption(cache, "hello") - * console.log(cached) // Option.some(5) - * - * return { empty, cached } + * return [empty, cached] as const * }) + * + * const actual = await Effect.runPromise(program) + * actual // => [Option.none(), Option.some(5)] * ``` * * **Example** (Skipping expired entries) * - * ```ts - * import { Cache, Effect } from "effect" + * ```ts import.meta.vitest + * import { Cache, Effect, Option } from "effect" * import { TestClock } from "effect/testing" * * // Expired entries return None @@ -504,21 +554,23 @@ const checkCapacity = (self: Cache) => { * * // Value exists before expiration * const beforeExpiry = yield* Cache.getOption(cache, "hello") - * console.log(beforeExpiry) // Option.some(5) * * // Simulate time passing * yield* TestClock.adjust("2 hours") * * // Value expired - returns None * const afterExpiry = yield* Cache.getOption(cache, "hello") - * console.log(afterExpiry) // Option.none() + * return [beforeExpiry, afterExpiry] as const * }) + * + * const actual = await Effect.runPromise(Effect.provide(program, TestClock.layer())) + * actual // => [Option.some(5), Option.none()] * ``` * * **Example** (Waiting for pending lookups) * - * ```ts - * import { Cache, Deferred, Effect, Fiber } from "effect" + * ```ts import.meta.vitest + * import { Cache, Deferred, Effect, Fiber, Option } from "effect" * * // Waits for ongoing computation to complete * const program = Effect.gen(function*() { @@ -538,11 +590,12 @@ const checkCapacity = (self: Cache) => { * yield* Deferred.succeed(deferred, void 0) * * const result = yield* Fiber.join(optionFiber) - * console.log(result) // Option.some(42) - * * const value = yield* Fiber.join(getFiber) - * console.log(value) // 42 + * return [result, value] as const * }) + * + * const actual = await Effect.runPromise(program) + * actual // => [Option.some(42), 42] * ``` * * @category combinators @@ -556,7 +609,7 @@ export const getOption: { (self: Cache, key: Key): Effect.Effect, E> => core.withFiber((fiber) => { const entry = getImpl(self, key, fiber) - return entry ? effect.asSome(Deferred.await(entry.deferred)) : effect.succeedNone + return entry ? effect.asSome(entry.await()) : effect.succeedNone }) ) @@ -602,7 +655,7 @@ export const getSuccess: { 2, (self: Cache, key: Key): Effect.Effect> => core.withFiber((fiber) => { - const exit = getImpl(self, key, fiber)?.deferred.effect as Exit.Exit | undefined + const exit = getImpl(self, key, fiber)?.fiber.pollUnsafe() if (exit && effect.exitIsSuccess(exit)) { return effect.succeedSome(exit.value) } @@ -616,7 +669,7 @@ export const getSuccess: { * * **Example** (Setting values directly) * - * ```ts + * ```ts import.meta.vitest * import { Cache, Effect } from "effect" * * const program = Effect.gen(function*() { @@ -627,14 +680,16 @@ export const getSuccess: { * * // Set a value directly without invoking lookup * yield* Cache.set(cache, "hello", 42) - * const result = yield* Cache.get(cache, "hello") - * console.log(result) // 42 (not 5 from lookup) + * return yield* Cache.get(cache, "hello") * }) + * + * const actual = await Effect.runPromise(program) + * actual // => 42 * ``` * * **Example** (Overwriting cached values) * - * ```ts + * ```ts import.meta.vitest * import { Cache, Effect } from "effect" * * // Overwriting existing cached values @@ -651,13 +706,16 @@ export const getSuccess: { * yield* Cache.set(cache, "test", 999) * const updated = yield* Cache.get(cache, "test") // 999 * - * console.log({ original, updated }) + * return { original, updated } * }) + * + * const actual = await Effect.runPromise(program) + * actual // => { original: 4, updated: 999 } * ``` * * **Example** (Applying TTL to set values) * - * ```ts + * ```ts import.meta.vitest * import { Cache, Effect } from "effect" * import { TestClock } from "effect/testing" * @@ -671,17 +729,21 @@ export const getSuccess: { * * // Set value with TTL applied * yield* Cache.set(cache, "temporary", 123) - * console.log(yield* Cache.has(cache, "temporary")) // true + * const beforeExpiry = yield* Cache.has(cache, "temporary") * * // Advance time past TTL * yield* TestClock.adjust("2 hours") - * console.log(yield* Cache.has(cache, "temporary")) // false + * const afterExpiry = yield* Cache.has(cache, "temporary") + * return [beforeExpiry, afterExpiry] * }) + * + * const actual = await Effect.runPromise(Effect.provide(program, TestClock.layer())) + * actual // => [true, false] * ``` * * **Example** (Enforcing capacity when setting values) * - * ```ts + * ```ts import.meta.vitest * import { Cache, Effect } from "effect" * * // Capacity enforcement with set operations @@ -694,14 +756,18 @@ export const getSuccess: { * // Fill cache to capacity * yield* Cache.set(cache, "a", 1) * yield* Cache.set(cache, "b", 2) - * console.log(yield* Cache.size(cache)) // 2 + * const sizeBeforeEviction = yield* Cache.size(cache) * * // Adding another entry evicts oldest * yield* Cache.set(cache, "c", 3) - * console.log(yield* Cache.size(cache)) // 2 - * console.log(yield* Cache.has(cache, "a")) // false (evicted) - * console.log(yield* Cache.has(cache, "c")) // true + * const sizeAfterEviction = yield* Cache.size(cache) + * const hasOldest = yield* Cache.has(cache, "a") + * const hasNewest = yield* Cache.has(cache, "c") + * return [sizeBeforeEviction, sizeAfterEviction, hasOldest, hasNewest] * }) + * + * const actual = await Effect.runPromise(program) + * actual // => [2, 2, false, true] * ``` * * @category combinators @@ -715,19 +781,16 @@ export const set: { (self: Cache, key: Key, value: A): Effect.Effect => core.withFiber((fiber) => { const exit = core.exitSucceed(value) - const deferred = Deferred.makeUnsafe() - Deferred.doneUnsafe(deferred, exit) + const entry = new EntryImpl(fiber, exit) const ttl = self.timeToLive(exit, key) if (Duration.isZero(ttl)) { MutableHashMap.remove(self.map, key) return effect.void } - MutableHashMap.set(self.map, key, { - deferred, - expiresAt: Duration.isFinite(ttl) - ? fiber.getRef(effect.ClockRef).currentTimeMillisUnsafe() + Duration.toMillis(ttl) - : undefined - }) + entry.expiresAt = Duration.isFinite(ttl) + ? fiber.getRef(effect.ClockRef).currentTimeMillisUnsafe() + Duration.toMillis(ttl) + : undefined + MutableHashMap.set(self.map, key, entry) checkCapacity(self) return effect.void }) @@ -743,7 +806,7 @@ export const set: { * * **Example** (Checking for cached keys) * - * ```ts + * ```ts import.meta.vitest * import { Cache, Effect } from "effect" * * const program = Effect.gen(function*() { @@ -753,17 +816,21 @@ export const set: { * }) * * // Check non-existent key - * console.log(yield* Cache.has(cache, "missing")) // false + * const missing = yield* Cache.has(cache, "missing") * * // Add entry and check existence * yield* Cache.get(cache, "hello") - * console.log(yield* Cache.has(cache, "hello")) // true + * const present = yield* Cache.has(cache, "hello") + * return [missing, present] * }) + * + * const actual = await Effect.runPromise(program) + * actual // => [false, true] * ``` * * **Example** (Checking TTL expiration) * - * ```ts + * ```ts import.meta.vitest * import { Cache, Effect } from "effect" * import { TestClock } from "effect/testing" * @@ -777,21 +844,25 @@ export const set: { * * // Add entry with TTL * yield* Cache.get(cache, "expires") - * console.log(yield* Cache.has(cache, "expires")) // true + * const initial = yield* Cache.has(cache, "expires") * * // Still valid before expiration * yield* TestClock.adjust("30 minutes") - * console.log(yield* Cache.has(cache, "expires")) // true + * const beforeExpiry = yield* Cache.has(cache, "expires") * * // Expired after TTL * yield* TestClock.adjust("31 minutes") - * console.log(yield* Cache.has(cache, "expires")) // false + * const afterExpiry = yield* Cache.has(cache, "expires") + * return [initial, beforeExpiry, afterExpiry] * }) + * + * const actual = await Effect.runPromise(Effect.provide(program, TestClock.layer())) + * actual // => [true, true, false] * ``` * * **Example** (Checking multiple keys) * - * ```ts + * ```ts import.meta.vitest * import { Cache, Effect } from "effect" * * // Checking multiple keys efficiently @@ -807,16 +878,16 @@ export const set: { * * // Check multiple keys * const keys = ["apple", "banana", "cherry", "date"] + * const results: Array = [] * for (const key of keys) { * const exists = yield* Cache.has(cache, key) - * console.log(`${key}: ${exists}`) + * results.push(`${key}: ${exists}`) * } - * // Output: - * // apple: true - * // banana: true - * // cherry: false - * // date: false + * return results * }) + * + * const actual = await Effect.runPromise(program) + * actual // => ["apple: true", "banana: true", "cherry: false", "date: false"] * ``` * * @category combinators @@ -839,7 +910,7 @@ export const has: { * * **Example** (Invalidating cached entries) * - * ```ts + * ```ts import.meta.vitest * import { Cache, Effect } from "effect" * * const program = Effect.gen(function*() { @@ -850,11 +921,11 @@ export const has: { * * // Add a value to the cache * yield* Cache.get(cache, "hello") - * console.log(yield* Cache.has(cache, "hello")) // true + * const beforeInvalidation = yield* Cache.has(cache, "hello") * * // Invalidate the entry * yield* Cache.invalidate(cache, "hello") - * console.log(yield* Cache.has(cache, "hello")) // false + * const afterInvalidation = yield* Cache.has(cache, "hello") * * // Invalidating non-existent keys doesn't error * yield* Cache.invalidate(cache, "nonexistent") @@ -873,7 +944,11 @@ export const has: { * yield* Cache.get(cache2, "test") // lookupCount = 1 * yield* Cache.invalidate(cache2, "test") * yield* Cache.get(cache2, "test") // lookupCount = 2 (lookup called again) + * return { beforeInvalidation, afterInvalidation, lookupCount } * }) + * + * const actual = await Effect.runPromise(program) + * actual // => { beforeInvalidation: true, afterInvalidation: false, lookupCount: 2 } * ``` * * @category combinators @@ -893,7 +968,7 @@ export const invalidate: { * * **Example** (Invalidating entries conditionally) * - * ```ts + * ```ts import.meta.vitest * import { Cache, Effect } from "effect" * * const program = Effect.gen(function*() { @@ -912,8 +987,7 @@ export const invalidate: { * "hello", * (value) => value === 5 * ) - * console.log(invalidated1) // true - * console.log(yield* Cache.has(cache, "hello")) // false + * const hasHello = yield* Cache.has(cache, "hello") * * // Don't invalidate when predicate doesn't match * const invalidated2 = yield* Cache.invalidateWhen( @@ -921,8 +995,7 @@ export const invalidate: { * "hi", * (value) => value === 5 * ) - * console.log(invalidated2) // false - * console.log(yield* Cache.has(cache, "hi")) // true (still present) + * const hasHi = yield* Cache.has(cache, "hi") * * // Returns false for non-existent keys * const invalidated3 = yield* Cache.invalidateWhen( @@ -930,7 +1003,6 @@ export const invalidate: { * "nonexistent", * () => true * ) - * console.log(invalidated3) // false * * // Returns false for failed cached values * const cacheWithErrors = yield* Cache.make({ @@ -945,8 +1017,11 @@ export const invalidate: { * "fail", * () => true * ) - * console.log(invalidated4) // false (can't invalidate failed values) + * return [invalidated1, hasHello, invalidated2, hasHi, invalidated3, invalidated4] * }) + * + * const actual = await Effect.runPromise(program) + * actual // => [true, false, false, true, false, false] * ``` * * @category combinators @@ -963,7 +1038,7 @@ export const invalidateWhen: { if (oentry === undefined) { return effect.succeed(false) } - return Deferred.await(oentry.deferred).pipe( + return oentry.await().pipe( effect.map((value) => { if (f(value)) { MutableHashMap.remove(self.map, key) @@ -986,7 +1061,7 @@ export const invalidateWhen: { * * **Example** (Refreshing cached values) * - * ```ts + * ```ts import.meta.vitest * import { Cache, Effect } from "effect" * * // Force refresh of existing cached values @@ -999,25 +1074,25 @@ export const invalidateWhen: { * * // Initial cache population * const value1 = yield* Cache.get(cache, "user") - * console.log(value1) // "user-1" * * // Get from cache (no lookup) * const value2 = yield* Cache.get(cache, "user") - * console.log(value2) // "user-1" (same value) * * // Force refresh - always calls lookup * const refreshed = yield* Cache.refresh(cache, "user") - * console.log(refreshed) // "user-2" (new value) * * // Subsequent gets return refreshed value * const value3 = yield* Cache.get(cache, "user") - * console.log(value3) // "user-2" + * return [value1, value2, refreshed, value3, counter] * }) + * + * const actual = await Effect.runPromise(program) + * actual // => ["user-1", "user-1", "user-2", "user-2", 2] * ``` * * **Example** (Resetting TTL on refresh) * - * ```ts + * ```ts import.meta.vitest * import { Cache, Effect } from "effect" * import { TestClock } from "effect/testing" * @@ -1033,20 +1108,24 @@ export const invalidateWhen: { * yield* TestClock.adjust("45 minutes") * * // Entry would normally expire in 15 minutes - * console.log(yield* Cache.has(cache, "test")) // true + * const beforeRefresh = yield* Cache.has(cache, "test") * * // Refresh resets the TTL to full 1 hour * yield* Cache.refresh(cache, "test") * yield* TestClock.adjust("30 minutes") * * // Still valid because TTL was reset - * console.log(yield* Cache.has(cache, "test")) // true + * const afterRefresh = yield* Cache.has(cache, "test") + * return [beforeRefresh, afterRefresh] * }) + * + * const actual = await Effect.runPromise(Effect.provide(program, TestClock.layer())) + * actual // => [true, true] * ``` * * **Example** (Refreshing missing keys) * - * ```ts + * ```ts import.meta.vitest * import { Cache, Effect } from "effect" * * // Refresh non-existent keys @@ -1058,11 +1137,14 @@ export const invalidateWhen: { * * // Refresh non-existent key creates new entry * const result = yield* Cache.refresh(cache, "newKey") - * console.log(result) // "value-for-newKey" * * // Verify it's now cached - * console.log(yield* Cache.has(cache, "newKey")) // true + * const cached = yield* Cache.has(cache, "newKey") + * return [result, cached] * }) + * + * const actual = await Effect.runPromise(program) + * actual // => ["value-for-newKey", true] * ``` * * @category combinators @@ -1075,18 +1157,17 @@ export const refresh: { 2, (self: Cache, key: Key): Effect.Effect => core.withFiber((fiber) => { - const deferred = Deferred.makeUnsafe() - const entry: Entry = { - expiresAt: undefined, - deferred - } + const entry = new EntryImpl(fiber, self.lookup(key)) const existing = getImpl(self, key, fiber, false) !== undefined if (!existing) { MutableHashMap.set(self.map, key, entry) checkCapacity(self) } - return effect.onExit(self.lookup(key), (exit) => { - Deferred.doneUnsafe(deferred, exit) + entry.fiber.addObserver((exit) => { + if (effect.exitHasInterrupts(exit)) { + if (!existing) MutableHashMap.remove(self.map, key) + return + } const ttl = self.timeToLive(exit, key) if (Duration.isZero(ttl)) { MutableHashMap.remove(self.map, key) @@ -1098,8 +1179,8 @@ export const refresh: { if (existing) { MutableHashMap.set(self.map, key, entry) } - return effect.void }) + return entry.await() }) ) @@ -1108,7 +1189,7 @@ export const refresh: { * * **Example** (Invalidating all entries) * - * ```ts + * ```ts import.meta.vitest * import { Cache, Effect } from "effect" * * // Clear all cached entries at once @@ -1123,18 +1204,29 @@ export const refresh: { * yield* Cache.get(cache, "banana") * yield* Cache.get(cache, "cherry") * - * console.log(yield* Cache.size(cache)) // 3 - * console.log(yield* Cache.has(cache, "apple")) // true + * const sizeBeforeInvalidation = yield* Cache.size(cache) + * const hasAppleBeforeInvalidation = yield* Cache.has(cache, "apple") * * // Clear all entries * yield* Cache.invalidateAll(cache) * * // Verify cache is empty - * console.log(yield* Cache.size(cache)) // 0 - * console.log(yield* Cache.has(cache, "apple")) // false - * console.log(yield* Cache.has(cache, "banana")) // false - * console.log(yield* Cache.has(cache, "cherry")) // false + * const sizeAfterInvalidation = yield* Cache.size(cache) + * const hasAppleAfterInvalidation = yield* Cache.has(cache, "apple") + * const hasBananaAfterInvalidation = yield* Cache.has(cache, "banana") + * const hasCherryAfterInvalidation = yield* Cache.has(cache, "cherry") + * return [ + * sizeBeforeInvalidation, + * hasAppleBeforeInvalidation, + * sizeAfterInvalidation, + * hasAppleAfterInvalidation, + * hasBananaAfterInvalidation, + * hasCherryAfterInvalidation + * ] * }) + * + * const actual = await Effect.runPromise(program) + * actual // => [3, true, 0, false, false, false] * ``` * * @category combinators @@ -1156,7 +1248,7 @@ export const invalidateAll = (self: Cache): Effect.E * * **Example** (Reading cache size) * - * ```ts + * ```ts import.meta.vitest * import { Cache, Effect } from "effect" * * const program = Effect.gen(function*() { @@ -1167,19 +1259,20 @@ export const invalidateAll = (self: Cache): Effect.E * * // Empty cache has size 0 * const emptySize = yield* Cache.size(cache) - * console.log(emptySize) // 0 * * // Add entries and check size * yield* Cache.get(cache, "hello") * yield* Cache.get(cache, "world") * const sizeAfterAdding = yield* Cache.size(cache) - * console.log(sizeAfterAdding) // 2 * * // Size decreases after invalidation * yield* Cache.invalidate(cache, "hello") * const sizeAfterInvalidation = yield* Cache.size(cache) - * console.log(sizeAfterInvalidation) // 1 + * return [emptySize, sizeAfterAdding, sizeAfterInvalidation] * }) + * + * const actual = await Effect.runPromise(program) + * actual // => [0, 2, 1] * ``` * * @category combinators @@ -1193,7 +1286,7 @@ export const size = (self: Cache): Effect.Effect(self: Cache): Effect.Effect ["cache", "hello", "world"] * ``` * * @category combinators @@ -1236,7 +1331,7 @@ export const keys = (self: Cache): Effect.Effect(self: Cache): Effect.Effect [1, 2, 3] * ``` * * @category combinators @@ -1284,10 +1380,10 @@ export const entries = (self: Cache): Effect.Effect< const now = fiber.getRef(effect.ClockRef).currentTimeMillisUnsafe() return effect.succeed(Iterable.filterMap(self.map, ([key, entry]) => { if (entry.expiresAt === undefined || entry.expiresAt > now) { - const exit = entry.deferred.effect - return !core.isExit(exit) || effect.exitIsFailure(exit) - ? Result.failVoid - : Result.succeed([key, exit.value as A]) + const exit = entry.fiber.pollUnsafe() + return exit && exit._tag === "Success" + ? Result.succeed([key, exit.value]) + : Result.failVoid } MutableHashMap.remove(self.map, key) return Result.failVoid diff --git a/repos/effect/packages/effect/src/Cause.ts b/repos/effect/packages/effect/src/Cause.ts index 5cc96c4739..2511f27bce 100644 --- a/repos/effect/packages/effect/src/Cause.ts +++ b/repos/effect/packages/effect/src/Cause.ts @@ -63,12 +63,10 @@ export const ReasonTypeId: "~effect/Cause/Reason" = core.CauseReasonTypeId * * **Example** (Creating and inspecting a cause) * - * ```ts + * ```ts import.meta.vitest * import { Cause } from "effect" * - * const cause = Cause.fail("Something went wrong") - * console.log(cause.reasons.length) // 1 - * console.log(Cause.isFailReason(cause.reasons[0])) // true + * Cause.fail("Something went wrong") // => Cause.fail("Something went wrong") * ``` * * @category models @@ -84,11 +82,11 @@ export interface Cause extends Pipeable, Inspectable, Equal { * * **Example** (Checking the runtime type) * - * ```ts + * ```ts import.meta.vitest * import { Cause } from "effect" * - * console.log(Cause.isCause(Cause.fail("error"))) // true - * console.log(Cause.isCause("not a cause")) // false + * Cause.isCause(Cause.fail("error")) // => true + * Cause.isCause("not a cause") // => false * ``` * * @category guards @@ -101,12 +99,12 @@ export const isCause: (self: unknown) => self is Cause = core.isCause * * **Example** (Checking the runtime type) * - * ```ts + * ```ts import.meta.vitest * import { Cause } from "effect" * * const reason = Cause.fail("error").reasons[0] - * console.log(Cause.isReason(reason)) // true - * console.log(Cause.isReason("not a reason")) // false + * Cause.isReason(reason) // => true + * Cause.isReason("not a reason") // => false * ``` * * @category guards @@ -131,12 +129,12 @@ export const isReason: (self: unknown) => self is Reason = core.isCause * * **Example** (Narrowing a reason) * - * ```ts + * ```ts import.meta.vitest * import { Cause } from "effect" * * const reason = Cause.fail("error").reasons[0] * if (Cause.isFailReason(reason)) { - * console.log(reason.error) // "error" + * reason.error // => "error" * } * ``` * @@ -155,12 +153,12 @@ export type Reason = Fail | Die | Interrupt * * **Example** (Filtering fail reasons) * - * ```ts + * ```ts import.meta.vitest * import { Cause } from "effect" * * const cause = Cause.fail("error") * const fails = cause.reasons.filter(Cause.isFailReason) - * console.log(fails[0].error) // "error" + * fails[0].error // => "error" * ``` * * @see {@link isDieReason} — narrow to `Die` @@ -181,12 +179,12 @@ export const isFailReason: (self: Reason) => self is Fail = core.isFail * * **Example** (Filtering die reasons) * - * ```ts + * ```ts import.meta.vitest * import { Cause } from "effect" * * const cause = Cause.die("defect") * const dies = cause.reasons.filter(Cause.isDieReason) - * console.log(dies[0].defect) // "defect" + * dies[0].defect // => "defect" * ``` * * @see {@link isFailReason} — narrow to `Fail` @@ -207,12 +205,12 @@ export const isDieReason: (self: Reason) => self is Die = core.isDieReason * * **Example** (Filtering interrupt reasons) * - * ```ts + * ```ts import.meta.vitest * import { Cause } from "effect" * * const cause = Cause.interrupt(123) * const interrupts = cause.reasons.filter(Cause.isInterruptReason) - * console.log(interrupts[0].fiberId) // 123 + * interrupts[0].fiberId // => 123 * ``` * * @see {@link isFailReason} — narrow to `Fail` @@ -234,14 +232,14 @@ export declare namespace Cause { * * **Example** (Extracting the error type) * - * ```ts + * ```ts import.meta.vitest * import type { Cause } from "effect" * * // string * type E = Cause.Cause.Error> * ``` * - * @category models + * @category utility types * @since 4.0.0 */ export type Error = T extends Cause ? E : never @@ -280,14 +278,14 @@ export declare namespace Reason { * * **Example** (Extracting the error type) * - * ```ts + * ```ts import.meta.vitest * import type { Cause } from "effect" * * // string * type E = Cause.Reason.Error> * ``` * - * @category models + * @category utility types * @since 4.0.0 */ export type Error = T extends Reason ? E : never @@ -309,13 +307,13 @@ export declare namespace Reason { * * **Example** (Accessing the defect) * - * ```ts + * ```ts import.meta.vitest * import { Cause } from "effect" * * const cause = Cause.die("Unexpected") * const reason = cause.reasons[0] * if (Cause.isDieReason(reason)) { - * console.log(reason.defect) // "Unexpected" + * reason.defect // => "Unexpected" * } * ``` * @@ -344,13 +342,13 @@ export interface Die extends Cause.ReasonProto<"Die"> { * * **Example** (Accessing the error) * - * ```ts + * ```ts import.meta.vitest * import { Cause } from "effect" * * const cause = Cause.fail("Something went wrong") * const reason = cause.reasons[0] * if (Cause.isFailReason(reason)) { - * console.log(reason.error) // "Something went wrong" + * reason.error // => "Something went wrong" * } * ``` * @@ -374,13 +372,13 @@ export interface Fail extends Cause.ReasonProto<"Fail"> { * * **Example** (Accessing the fiber ID) * - * ```ts + * ```ts import.meta.vitest * import { Cause } from "effect" * * const cause = Cause.interrupt(123) * const reason = cause.reasons[0] * if (Cause.isInterruptReason(reason)) { - * console.log(reason.fiberId) // 123 + * reason.fiberId // => 123 * } * ``` * @@ -412,15 +410,14 @@ export interface Interrupt extends Cause.ReasonProto<"Interrupt"> { * * **Example** (Building a cause from reasons) * - * ```ts + * ```ts import.meta.vitest * import { Cause } from "effect" * * const reasons = [ * Cause.makeFailReason("err1"), * Cause.makeFailReason("err2") * ] - * const cause = Cause.fromReasons(reasons) - * console.log(cause.reasons.length) // 2 + * Cause.fromReasons(reasons) // => Cause.combine(Cause.fail("err1"), Cause.fail("err2")) * ``` * * @see {@link combine} — merge two existing causes @@ -447,13 +444,10 @@ export const fromReasons: ( * * **Example** (Combining with the empty cause) * - * ```ts + * ```ts import.meta.vitest * import { Cause } from "effect" * - * const cause = Cause.combine(Cause.empty, Cause.fail("boom")) - * - * console.log(cause.reasons.length) // 1 - * console.log(Cause.hasFails(cause)) // true + * Cause.combine(Cause.empty, Cause.fail("boom")) // => Cause.fail("boom") * ``` * * @see {@link combine} for merging causes where `empty` acts as the identity @@ -473,12 +467,10 @@ export const empty: Cause = core.causeEmpty * * **Example** (Creating a fail cause) * - * ```ts + * ```ts import.meta.vitest * import { Cause } from "effect" * - * const cause = Cause.fail("Something went wrong") - * console.log(cause.reasons.length) // 1 - * console.log(Cause.isFailReason(cause.reasons[0])) // true + * Cause.fail("Something went wrong") // => Cause.fromReasons([Cause.makeFailReason("Something went wrong")]) * ``` * * @see {@link die} — for untyped defects @@ -499,12 +491,10 @@ export const fail: (error: E) => Cause = core.causeFail * * **Example** (Creating a die cause) * - * ```ts + * ```ts import.meta.vitest * import { Cause } from "effect" * - * const cause = Cause.die("Unexpected") - * console.log(cause.reasons.length) // 1 - * console.log(Cause.isDieReason(cause.reasons[0])) // true + * Cause.die("Unexpected") // => Cause.fromReasons([Cause.makeDieReason("Unexpected")]) * ``` * * @see {@link fail} — for typed errors @@ -521,12 +511,10 @@ export const die: (defect: unknown) => Cause = core.causeDie * * **Example** (Creating an interrupt cause) * - * ```ts + * ```ts import.meta.vitest * import { Cause } from "effect" * - * const cause = Cause.interrupt(123) - * console.log(cause.reasons.length) // 1 - * console.log(Cause.isInterruptReason(cause.reasons[0])) // true + * Cause.interrupt(123) // => Cause.fromReasons([Cause.makeInterruptReason(123)]) * ``` * * @see {@link fail} — for typed errors @@ -547,12 +535,10 @@ export const interrupt: (fiberId?: number | undefined) => Cause = effect. * * **Example** (Creating a Fail reason) * - * ```ts + * ```ts import.meta.vitest * import { Cause } from "effect" * - * const reason = Cause.makeFailReason("error") - * console.log(reason._tag) // "Fail" - * console.log(reason.error) // "error" + * Cause.makeFailReason("error") // => Cause.fail("error").reasons[0] * ``` * * @see {@link makeDieReason} — create a `Die` reason @@ -573,12 +559,10 @@ export const makeFailReason = (error: E): Fail => new core.Fail(error) * * **Example** (Creating a Die reason) * - * ```ts + * ```ts import.meta.vitest * import { Cause } from "effect" * - * const reason = Cause.makeDieReason("bug") - * console.log(reason._tag) // "Die" - * console.log(reason.defect) // "bug" + * Cause.makeDieReason("bug") // => Cause.die("bug").reasons[0] * ``` * * @see {@link makeFailReason} — create a `Fail` reason @@ -600,12 +584,10 @@ export const makeDieReason = (defect: unknown): Die => new core.Die(defect) * * **Example** (Creating an Interrupt reason) * - * ```ts + * ```ts import.meta.vitest * import { Cause } from "effect" * - * const reason = Cause.makeInterruptReason(42) - * console.log(reason._tag) // "Interrupt" - * console.log(reason.fiberId) // 42 + * Cause.makeInterruptReason(42) // => Cause.interrupt(42).reasons[0] * ``` * * @see {@link makeFailReason} — create a `Fail` reason @@ -626,12 +608,12 @@ export const makeInterruptReason: (fiberId?: number | undefined) => Interrupt = * * **Example** (Checking interrupt-only causes) * - * ```ts + * ```ts import.meta.vitest * import { Cause } from "effect" * - * console.log(Cause.hasInterruptsOnly(Cause.interrupt(123))) // true - * console.log(Cause.hasInterruptsOnly(Cause.fail("error"))) // false - * console.log(Cause.hasInterruptsOnly(Cause.empty)) // false + * Cause.hasInterruptsOnly(Cause.interrupt(123)) // => true + * Cause.hasInterruptsOnly(Cause.fail("error")) // => false + * Cause.hasInterruptsOnly(Cause.empty) // => false * ``` * * @see {@link hasInterrupts} — `true` if the cause contains *any* interrupts @@ -659,14 +641,14 @@ export const hasInterruptsOnly: (self: Cause) => boolean = effect.hasInter * * **Example** (Mapping errors to uppercase) * - * ```ts + * ```ts import.meta.vitest * import { Cause } from "effect" * * const cause = Cause.fail("error") * const mapped = Cause.map(cause, (e) => e.toUpperCase()) * const reason = mapped.reasons[0] * if (Cause.isFailReason(reason)) { - * console.log(reason.error) // "ERROR" + * reason.error // => "ERROR" * } * ``` * @@ -694,13 +676,11 @@ export const map: { * * **Example** (Combining two causes) * - * ```ts + * ```ts import.meta.vitest * import { Cause } from "effect" * - * const cause1 = Cause.fail("error1") - * const cause2 = Cause.fail("error2") - * const combined = Cause.combine(cause1, cause2) - * console.log(combined.reasons.length) // 2 + * const combined = Cause.combine(Cause.fail("error1"), Cause.fail("error2")) + * combined // => Cause.fromReasons([Cause.makeFailReason("error1"), Cause.makeFailReason("error2")]) * ``` * * @see {@link fromReasons} — build a cause from an array of reasons @@ -740,11 +720,11 @@ export const combine: { * * **Example** (Squashing a cause) * - * ```ts + * ```ts import.meta.vitest * import { Cause } from "effect" * - * console.log(Cause.squash(Cause.fail("error"))) // "error" - * console.log(Cause.squash(Cause.die("defect"))) // "defect" + * Cause.squash(Cause.fail("error")) // => "error" + * Cause.squash(Cause.die("defect")) // => "defect" * ``` * * @see {@link prettyErrors} — non-lossy conversion to `Array` @@ -765,11 +745,11 @@ export const squash: (self: Cause) => unknown = effect.causeSquash * * **Example** (Checking for typed errors) * - * ```ts + * ```ts import.meta.vitest * import { Cause } from "effect" * - * console.log(Cause.hasFails(Cause.fail("error"))) // true - * console.log(Cause.hasFails(Cause.die("defect"))) // false + * Cause.hasFails(Cause.fail("error")) // => true + * Cause.hasFails(Cause.die("defect")) // => false * ``` * * @see {@link hasDies} — check for defects @@ -793,13 +773,10 @@ export const hasFails: (self: Cause) => boolean = effect.hasFails * * **Example** (Extracting the first Fail reason) * - * ```ts + * ```ts import.meta.vitest * import { Cause, Result } from "effect" * - * const result = Cause.findFail(Cause.fail("error")) - * if (!Result.isFailure(result)) { - * console.log(result.success.error) // "error" - * } + * Cause.findFail(Cause.fail("error")) // => Result.succeed(Cause.makeFailReason("error")) * ``` * * @see {@link findError} — extract the unwrapped `E` value @@ -823,13 +800,10 @@ export const findFail: (self: Cause) => Result.Result, Cause Result.succeed("error") * ``` * * @see {@link findFail} — extract the full `Fail` reason @@ -851,14 +825,11 @@ export const findError: (self: Cause) => Result.Result> = * * **Example** (Extracting an error as Option) * - * ```ts + * ```ts import.meta.vitest * import { Cause, Option } from "effect" * - * const some = Cause.findErrorOption(Cause.fail("error")) - * console.log(Option.isSome(some)) // true - * - * const none = Cause.findErrorOption(Cause.die("defect")) - * console.log(Option.isNone(none)) // true + * Cause.findErrorOption(Cause.fail("error")) // => Option.some("error") + * Cause.findErrorOption(Cause.die("defect")) // => Option.none() * ``` * * @see {@link findError} — `Result`-based variant @@ -878,11 +849,11 @@ export const findErrorOption: (input: Cause) => Option = effect.findErr * * **Example** (Checking for defects) * - * ```ts + * ```ts import.meta.vitest * import { Cause } from "effect" * - * console.log(Cause.hasDies(Cause.die("defect"))) // true - * console.log(Cause.hasDies(Cause.fail("error"))) // false + * Cause.hasDies(Cause.die("defect")) // => true + * Cause.hasDies(Cause.fail("error")) // => false * ``` * * @see {@link hasFails} — check for typed errors @@ -905,13 +876,10 @@ export const hasDies: (self: Cause) => boolean = effect.hasDies * * **Example** (Extracting the first Die reason) * - * ```ts + * ```ts import.meta.vitest * import { Cause, Result } from "effect" * - * const result = Cause.findDie(Cause.die("defect")) - * if (!Result.isFailure(result)) { - * console.log(result.success.defect) // "defect" - * } + * Cause.findDie(Cause.die("defect")) // => Result.succeed(Cause.makeDieReason("defect")) * ``` * * @see {@link findDefect} — extract the unwrapped defect value @@ -934,13 +902,10 @@ export const findDie: (self: Cause) => Result.Result> = effe * * **Example** (Extracting the first defect) * - * ```ts + * ```ts import.meta.vitest * import { Cause, Result } from "effect" * - * const result = Cause.findDefect(Cause.die("defect")) - * if (!Result.isFailure(result)) { - * console.log(result.success) // "defect" - * } + * Cause.findDefect(Cause.die("defect")) // => Result.succeed("defect") * ``` * * @see {@link findDie} — extract the full `Die` reason @@ -956,11 +921,11 @@ export const findDefect: (self: Cause) => Result.Result> * * **Example** (Checking for interruptions) * - * ```ts + * ```ts import.meta.vitest * import { Cause } from "effect" * - * console.log(Cause.hasInterrupts(Cause.interrupt(123))) // true - * console.log(Cause.hasInterrupts(Cause.fail("error"))) // false + * Cause.hasInterrupts(Cause.interrupt(123)) // => true + * Cause.hasInterrupts(Cause.fail("error")) // => false * ``` * * @see {@link hasInterruptsOnly} — `true` only when *all* reasons are interrupts @@ -984,13 +949,10 @@ export const hasInterrupts: (self: Cause) => boolean = effect.hasInterrupt * * **Example** (Extracting the first interrupt) * - * ```ts + * ```ts import.meta.vitest * import { Cause, Result } from "effect" * - * const result = Cause.findInterrupt(Cause.interrupt(42)) - * if (!Result.isFailure(result)) { - * console.log(result.success.fiberId) // 42 - * } + * Cause.findInterrupt(Cause.interrupt(42)) // => Result.succeed(Cause.makeInterruptReason(42)) * ``` * * @see {@link interruptors} — collect all interrupting fiber IDs as a `Set` @@ -1012,7 +974,7 @@ export const findInterrupt: (self: Cause) => Result.Result(self: Cause) => Result.Result new Set([1, 2]) * ``` * * @see {@link filterInterruptors} — `Result`-based variant * - * @category accessors + * @category getters * @since 2.0.0 */ export const interruptors: (self: Cause) => ReadonlySet = effect.causeInterruptors @@ -1048,13 +1010,10 @@ export const interruptors: (self: Cause) => ReadonlySet = effect.c * * **Example** (Extracting interruptors with Result) * - * ```ts + * ```ts import.meta.vitest * import { Cause, Result } from "effect" * - * const result = Cause.filterInterruptors(Cause.interrupt(1)) - * if (!Result.isFailure(result)) { - * console.log(result.success) // Set(1) { 1 } - * } + * Cause.filterInterruptors(Cause.interrupt(1)) // => Result.succeed(new Set([1])) * ``` * * @see {@link interruptors} — always-succeeding variant @@ -1094,21 +1053,21 @@ export const filterInterruptors: (self: Cause) => Result.Result "boom" * ``` * * @see {@link pretty} — renders the cause as a single string * @see {@link squash} — lossy collapse to a single thrown value * - * @category rendering + * @category formatting * @since 3.2.0 */ -export const prettyErrors: (self: Cause) => Array = effect.causePrettyErrors +export const prettyErrors: (self: Cause, options?: { + readonly includeCauseInStack?: boolean | undefined +}) => Array = effect.causePrettyErrors /** * Formats a `Cause` as a human-readable string for logging or debugging. @@ -1142,16 +1101,15 @@ export const prettyErrors: (self: Cause) => Array = effect.causePre * * **Example** (Rendering a cause) * - * ```ts + * ```ts import.meta.vitest * import { Cause } from "effect" * - * const rendered = Cause.pretty(Cause.fail("something went wrong")) - * console.log(rendered.includes("something went wrong")) // true + * Cause.pretty(Cause.fail("something went wrong")).includes("something went wrong") // => true * ``` * * @see {@link prettyErrors} — get the individual `Error` instances * - * @category rendering + * @category formatting * @since 2.0.0 */ export const pretty: (cause: Cause) => string = effect.causePretty @@ -1169,14 +1127,16 @@ export const pretty: (cause: Cause) => string = effect.causePretty * * **Example** (Yielding an error in Effect.gen) * - * ```ts - * import { Cause, Effect } from "effect" + * ```ts import.meta.vitest + * import { Cause, Effect, Exit } from "effect" * * const error = new Cause.NoSuchElementError("not found") * * const program = Effect.gen(function*() { * return yield* error // fails the effect with NoSuchElementError * }) + * + * await Effect.runPromiseExit(program) // => Exit.fail(error) * ``` * * @category errors @@ -1192,11 +1152,11 @@ export interface YieldableError extends Error, Pipeable, Inspectable { * * **Example** (Checking the runtime type) * - * ```ts + * ```ts import.meta.vitest * import { Cause } from "effect" * - * console.log(Cause.isNoSuchElementError(new Cause.NoSuchElementError())) // true - * console.log(Cause.isNoSuchElementError("nope")) // false + * Cause.isNoSuchElementError(new Cause.NoSuchElementError()) // => true + * Cause.isNoSuchElementError("nope") // => false * ``` * * @category guards @@ -1231,16 +1191,6 @@ export const NoSuchElementErrorTypeId: "~effect/Cause/NoSuchElementError" = core * expected case. This error is mainly for APIs that intentionally turn absence * into a thrown value or failed effect. * - * **Example** (Creating and checking a NoSuchElementError) - * - * ```ts - * import { Cause } from "effect" - * - * const error = new Cause.NoSuchElementError("Element not found") - * console.log(error._tag) // "NoSuchElementError" - * console.log(error.message) // "Element not found" - * ``` - * * @category errors * @since 4.0.0 */ @@ -1259,11 +1209,10 @@ export interface NoSuchElementError extends YieldableError { * * **Example** (Creating a NoSuchElementError) * - * ```ts + * ```ts import.meta.vitest * import { Cause } from "effect" * - * const error = new Cause.NoSuchElementError("Element not found") - * console.log(error.message) // "Element not found" + * new Cause.NoSuchElementError("Element not found").message // => "Element not found" * ``` * * @see {@link isNoSuchElementError} for checking unknown values @@ -1278,11 +1227,11 @@ export const NoSuchElementError: new(message?: string) => NoSuchElementError = c * * **Example** (Checking the runtime type) * - * ```ts + * ```ts import.meta.vitest * import { Cause } from "effect" * - * console.log(Cause.isDone(Cause.Done())) // true - * console.log(Cause.isDone("not done")) // false + * Cause.isDone(Cause.Done()) // => true + * Cause.isDone("not done") // => false * ``` * * @category guards @@ -1315,7 +1264,7 @@ export const DoneTypeId: "~effect/Cause/Done" = core.DoneTypeId * * **Example** (Signaling queue completion) * - * ```ts + * ```ts import.meta.vitest * import { Cause, Effect, Queue } from "effect" * * const program = Effect.gen(function*() { @@ -1323,9 +1272,12 @@ export const DoneTypeId: "~effect/Cause/Done" = core.DoneTypeId * yield* Queue.offer(queue, 1) * yield* Queue.end(queue) * + * yield* Queue.take(queue) * const result = yield* Effect.flip(Queue.take(queue)) - * console.log(Cause.isDone(result)) // true + * return Cause.isDone(result) * }) + * + * await Effect.runPromise(program) // => true * ``` * * @category errors @@ -1385,14 +1337,12 @@ export const Done: (value?: A) => Done = core.Done * * **Example** (Failing with Done) * - * ```ts - * import { Cause, Effect } from "effect" + * ```ts import.meta.vitest + * import { Cause, Effect, Exit } from "effect" * * const program = Cause.done("finished") * - * Effect.runPromiseExit(program).then((exit) => { - * console.log(exit._tag) // "Failure" - * }) + * await Effect.runPromiseExit(program) // => Exit.fail(Cause.Done("finished")) * ``` * * @see {@link Done} — create the signal value without an Effect @@ -1415,11 +1365,11 @@ export const TimeoutErrorTypeId: "~effect/Cause/TimeoutError" = effect.TimeoutEr * * **Example** (Checking the runtime type) * - * ```ts + * ```ts import.meta.vitest * import { Cause } from "effect" * - * console.log(Cause.isTimeoutError(new Cause.TimeoutError())) // true - * console.log(Cause.isTimeoutError("nope")) // false + * Cause.isTimeoutError(new Cause.TimeoutError()) // => true + * Cause.isTimeoutError("nope") // => false * ``` * * @category guards @@ -1435,16 +1385,6 @@ export const isTimeoutError: (u: unknown) => u is TimeoutError = effect.isTimeou * Produced by `Effect.timeout` and related APIs. Implements * `YieldableError`. * - * **Example** (Creating and checking a TimeoutError) - * - * ```ts - * import { Cause } from "effect" - * - * const error = new Cause.TimeoutError("Operation timed out") - * console.log(error._tag) // "TimeoutError" - * console.log(error.message) // "Operation timed out" - * ``` - * * @category errors * @since 4.0.0 */ @@ -1458,11 +1398,10 @@ export interface TimeoutError extends YieldableError { * * **Example** (Creating a TimeoutError) * - * ```ts + * ```ts import.meta.vitest * import { Cause } from "effect" * - * const error = new Cause.TimeoutError("Operation timed out") - * console.log(error.message) // "Operation timed out" + * new Cause.TimeoutError("Operation timed out").message // => "Operation timed out" * ``` * * @category constructors @@ -1483,11 +1422,11 @@ export const IllegalArgumentErrorTypeId: "~effect/Cause/IllegalArgumentError" = * * **Example** (Checking the runtime type) * - * ```ts + * ```ts import.meta.vitest * import { Cause } from "effect" * - * console.log(Cause.isIllegalArgumentError(new Cause.IllegalArgumentError())) // true - * console.log(Cause.isIllegalArgumentError("nope")) // false + * Cause.isIllegalArgumentError(new Cause.IllegalArgumentError()) // => true + * Cause.isIllegalArgumentError("nope") // => false * ``` * * @category guards @@ -1503,16 +1442,6 @@ export const isIllegalArgumentError: (u: unknown) => u is IllegalArgumentError = * * Implements `YieldableError`. * - * **Example** (Creating and checking an IllegalArgumentError) - * - * ```ts - * import { Cause } from "effect" - * - * const error = new Cause.IllegalArgumentError("Expected positive number") - * console.log(error._tag) // "IllegalArgumentError" - * console.log(error.message) // "Expected positive number" - * ``` - * * @category errors * @since 4.0.0 */ @@ -1526,11 +1455,10 @@ export interface IllegalArgumentError extends YieldableError { * * **Example** (Creating an IllegalArgumentError) * - * ```ts + * ```ts import.meta.vitest * import { Cause } from "effect" * - * const error = new Cause.IllegalArgumentError("Invalid argument") - * console.log(error.message) // "Invalid argument" + * new Cause.IllegalArgumentError("Invalid argument").message // => "Invalid argument" * ``` * * @category constructors @@ -1543,11 +1471,11 @@ export const IllegalArgumentError: new(message?: string) => IllegalArgumentError * * **Example** (Checking the runtime type) * - * ```ts + * ```ts import.meta.vitest * import { Cause } from "effect" * - * console.log(Cause.isExceededCapacityError(new Cause.ExceededCapacityError())) // true - * console.log(Cause.isExceededCapacityError("nope")) // false + * Cause.isExceededCapacityError(new Cause.ExceededCapacityError()) // => true + * Cause.isExceededCapacityError("nope") // => false * ``` * * @category guards @@ -1576,16 +1504,6 @@ export const ExceededCapacityErrorTypeId: "~effect/Cause/ExceededCapacityError" * * Implements `YieldableError`. * - * **Example** (Creating and checking an ExceededCapacityError) - * - * ```ts - * import { Cause } from "effect" - * - * const error = new Cause.ExceededCapacityError("Queue full") - * console.log(error._tag) // "ExceededCapacityError" - * console.log(error.message) // "Queue full" - * ``` - * * @category errors * @since 4.0.0 */ @@ -1603,11 +1521,10 @@ export interface ExceededCapacityError extends YieldableError { * * **Example** (Creating an ExceededCapacityError) * - * ```ts + * ```ts import.meta.vitest * import { Cause } from "effect" * - * const error = new Cause.ExceededCapacityError("Queue full") - * console.log(error.message) // "Queue full" + * new Cause.ExceededCapacityError("Queue full").message // => "Queue full" * ``` * * @see {@link isExceededCapacityError} for checking unknown values @@ -1631,15 +1548,14 @@ export const AsyncFiberErrorTypeId: "~effect/Cause/AsyncFiberError" = effect.Asy * * **Example** (Checking the runtime type) * - * ```ts - * import { Cause } from "effect" - * import type { Fiber } from "effect" + * ```ts import.meta.vitest + * import { Cause, Effect } from "effect" * - * declare const fiber: Fiber.Fiber + * const fiber = Effect.runFork(Effect.void) * * const error = new Cause.AsyncFiberError(fiber) - * console.log(Cause.isAsyncFiberError(error)) // true - * console.log(Cause.isAsyncFiberError("nope")) // false + * Cause.isAsyncFiberError(error) // => true + * Cause.isAsyncFiberError("nope") // => false * ``` * * @category guards @@ -1662,15 +1578,14 @@ export const isAsyncFiberError: (u: unknown) => u is AsyncFiberError = effect.is * * **Example** (Accessing the fiber) * - * ```ts - * import { Cause } from "effect" - * import type { Fiber } from "effect" + * ```ts import.meta.vitest + * import { Cause, Effect } from "effect" * - * declare const fiber: Fiber.Fiber + * const fiber = Effect.runFork(Effect.void) * - * const error = new Cause.AsyncFiberError(fiber) - * console.log(error._tag) // "AsyncFiberError" - * console.log(error.fiber === fiber) // true + * const value = new Cause.AsyncFiberError(fiber) + * const isSameFiber = value.fiber === fiber + * isSameFiber // => true * ``` * * @category errors @@ -1693,14 +1608,12 @@ export interface AsyncFiberError extends YieldableError { * * **Example** (Creating an AsyncFiberError) * - * ```ts - * import { Cause } from "effect" - * import type { Fiber } from "effect" + * ```ts import.meta.vitest + * import { Cause, Effect } from "effect" * - * declare const fiber: Fiber.Fiber + * const fiber = Effect.runFork(Effect.void) * - * const error = new Cause.AsyncFiberError(fiber) - * console.log(error.message) // "An asynchronous Effect was executed with Effect.runSync" + * new Cause.AsyncFiberError(fiber).message // => "An asynchronous Effect was executed with Effect.runSync" * ``` * * @see {@link isAsyncFiberError} for checking unknown values @@ -1723,11 +1636,11 @@ export const UnknownErrorTypeId: "~effect/Cause/UnknownError" = effect.UnknownEr * * **Example** (Checking the runtime type) * - * ```ts + * ```ts import.meta.vitest * import { Cause } from "effect" * - * console.log(Cause.isUnknownError(new Cause.UnknownError("x"))) // true - * console.log(Cause.isUnknownError("nope")) // false + * Cause.isUnknownError(new Cause.UnknownError("x")) // => true + * Cause.isUnknownError("nope") // => false * ``` * * @category guards @@ -1744,16 +1657,6 @@ export const isUnknownError: (u: unknown) => u is UnknownError = effect.isUnknow * typed error. The original value is stored in the `cause` property inherited * from `Error`. Implements `YieldableError`. * - * **Example** (Creating and checking an UnknownError) - * - * ```ts - * import { Cause } from "effect" - * - * const error = new Cause.UnknownError("original", "Something unknown") - * console.log(error._tag) // "UnknownError" - * console.log(error.message) // "Something unknown" - * ``` - * * @category errors * @since 4.0.0 */ @@ -1769,11 +1672,10 @@ export interface UnknownError extends YieldableError { * * **Example** (Creating an UnknownError) * - * ```ts + * ```ts import.meta.vitest * import { Cause } from "effect" * - * const error = new Cause.UnknownError({ raw: true }, "Unexpected value") - * console.log(error.message) // "Unexpected value" + * new Cause.UnknownError({ raw: true }, "Unexpected value").message // => "Unexpected value" * ``` * * @category constructors @@ -1800,15 +1702,13 @@ export const UnknownError: new(cause: unknown, message?: string) => UnknownError * * **Example** (Annotating a cause) * - * ```ts + * ```ts import.meta.vitest * import { Cause, Context } from "effect" * * class RequestId extends Context.Service()("RequestId") {} * - * const cause = Cause.fail("error") - * const annotated = Cause.annotate(cause, Context.make(RequestId, "req-1")) - * - * console.log(Context.getOrUndefined(Cause.annotations(annotated), RequestId)) // "req-1" + * const annotated = Cause.annotate(Cause.fail("error"), Context.make(RequestId, "req-1")) + * Context.getOrUndefined(Cause.annotations(annotated), RequestId) // => "req-1" * ``` * * @see {@link annotations} for reading merged annotations from a cause @@ -1839,7 +1739,7 @@ export const annotate: { * * **Example** (Reading reason annotations) * - * ```ts + * ```ts import.meta.vitest * import { Cause, Context } from "effect" * * class RequestId extends Context.Service()("RequestId") {} @@ -1847,7 +1747,7 @@ export const annotate: { * const reason = Cause.makeFailReason("error") * const annotated = reason.annotate(Context.make(RequestId, "req-1")) * - * console.log(Context.getOrUndefined(Cause.reasonAnnotations(annotated), RequestId)) // "req-1" + * Context.getOrUndefined(Cause.reasonAnnotations(annotated), RequestId) // => "req-1" * ``` * * @see {@link annotations} — merged annotations from all reasons in a cause @@ -1871,7 +1771,7 @@ export const reasonAnnotations: (self: Reason) => Context.Context = * * **Example** (Reading merged annotations) * - * ```ts + * ```ts import.meta.vitest * import { Cause, Context } from "effect" * * class RequestId extends Context.Service()("RequestId") {} @@ -1881,7 +1781,7 @@ export const reasonAnnotations: (self: Reason) => Context.Context = * Context.make(RequestId, "req-1") * ) * - * console.log(Context.getOrUndefined(Cause.annotations(cause), RequestId)) // "req-1" + * Context.getOrUndefined(Cause.annotations(cause), RequestId) // => "req-1" * ``` * * @see {@link reasonAnnotations} — annotations from a single reason @@ -1909,7 +1809,7 @@ export const annotations: (self: Cause) => Context.Context = effect * @see {@link annotations} for reading merged annotations from a cause * @see {@link InterruptorStackTrace} for the interrupt-specific stack-frame annotation * - * @category annotations + * @category services * @since 4.0.0 */ export class StackTrace extends Context.Service()("effect/Cause/StackTrace") {} @@ -1931,7 +1831,7 @@ export class StackTrace extends Context.Service()("effec * @see {@link reasonAnnotations} for reading annotations from a single reason * @see {@link annotate} for attaching annotations to a cause * - * @category annotations + * @category services * @since 4.0.0 */ export class InterruptorStackTrace diff --git a/repos/effect/packages/effect/src/Channel.ts b/repos/effect/packages/effect/src/Channel.ts index f094d9bad2..a9d09d3c1b 100644 --- a/repos/effect/packages/effect/src/Channel.ts +++ b/repos/effect/packages/effect/src/Channel.ts @@ -26,6 +26,7 @@ import * as Iterable from "./Iterable.ts" import * as Latch from "./Latch.ts" import * as Layer from "./Layer.ts" import type { Severity } from "./LogLevel.ts" +import * as MutableRef from "./MutableRef.ts" import * as Option from "./Option.ts" import type { Pipeable } from "./Pipeable.ts" import { pipeArguments } from "./Pipeable.ts" @@ -67,12 +68,12 @@ export const TypeId: TypeId = "~effect/Channel" * * **Example** (Checking for channels) * - * ```ts + * ```ts import.meta.vitest * import { Channel } from "effect" * * const channel = Channel.succeed(42) - * console.log(Channel.isChannel(channel)) // true - * console.log(Channel.isChannel("not a channel")) // false + * Channel.isChannel(channel) // => true + * Channel.isChannel("not a channel") // => false * ``` * * @category guards @@ -109,8 +110,8 @@ export const isChannel = ( * * **Example** (Typing channels) * - * ```ts - * import type { Channel } from "effect" + * ```ts import.meta.vitest + * import { Channel, Effect } from "effect" * * // A channel that outputs numbers and requires no environment * type NumberChannel = Channel.Channel @@ -128,6 +129,9 @@ export const isChannel = ( * boolean, // InDone - input completion * { db: string } // Env - required environment * > + * + * const channel: NumberChannel = Channel.succeed(1) + * Effect.runSync(Channel.runCollect(channel)) // => [1] * ``` * * @category models @@ -264,12 +268,13 @@ const ChannelProto = { * * **Example** (Creating channels from transforms) * - * ```ts + * ```ts import.meta.vitest * import { Channel, Effect } from "effect" * * const channel = Channel.fromTransform((upstream, scope) => * Effect.succeed(upstream) * ) + * await Effect.runPromise(Channel.runCollect(channel)) // => [] * ``` * * @category constructors @@ -300,7 +305,7 @@ export const fromTransform = value * 2) * ) * ) - * // Outputs: 2, 4, 6 + * await Effect.runPromise(Channel.runCollect(transformedChannel)) // => [2, 4, 6] * ``` * * @category constructors @@ -354,12 +359,18 @@ export const transformPull = < * * **Example** (Creating channels from pulls) * - * ```ts - * import { Channel, Effect } from "effect" + * ```ts import.meta.vitest + * import { Cause, Channel, Effect } from "effect" * - * const channel = Channel.fromPull( - * Effect.succeed(Effect.succeed(42)) - * ) + * const channel = Channel.fromPull(Effect.sync(() => { + * let emitted = false + * return Effect.suspend(() => { + * if (emitted) return Cause.done() + * emitted = true + * return Effect.succeed(42) + * }) + * })) + * await Effect.runPromise(Channel.runCollect(channel)) // => [42] * ``` * * @category constructors @@ -410,12 +421,13 @@ export const fromTransformBracket = "function" + * Effect.runSync(Channel.runCollect(channel)) // => [42] * ``` * * @category destructors @@ -433,10 +445,10 @@ export const toTransform = 4096 * ``` * * @category constants @@ -465,7 +477,7 @@ const asyncQueue = ( * * **Example** (Creating channels from callbacks) * - * ```ts + * ```ts import.meta.vitest * import { Channel, Effect, Queue } from "effect" * * const channel = Channel.callback((queue) => @@ -473,8 +485,10 @@ const asyncQueue = ( * yield* Queue.offer(queue, 1) * yield* Queue.offer(queue, 2) * yield* Queue.offer(queue, 3) + * yield* Queue.end(queue) * }) * ) + * await Effect.runPromise(Channel.runCollect(channel)) // => [1, 2, 3] * ``` * * @category constructors @@ -494,14 +508,15 @@ export const callback = ( * * **Example** (Creating array channels from callbacks) * - * ```ts + * ```ts import.meta.vitest * import { Channel, Effect, Queue } from "effect" * * const channel = Channel.callbackArray(Effect.fn(function*(queue) { * yield* Queue.offer(queue, 1) * yield* Queue.offer(queue, 2) + * yield* Queue.end(queue) * })) - * // Emits arrays of numbers instead of individual numbers + * await Effect.runPromise(Channel.runCollect(channel)) // => [[1, 2]] * ``` * * @category constructors @@ -521,11 +536,11 @@ export const callbackArray = ( * * **Example** (Suspending channel creation) * - * ```ts - * import { Channel } from "effect" + * ```ts import.meta.vitest + * import { Channel, Effect } from "effect" * * const channel = Channel.suspend(() => Channel.succeed(42)) - * // The inner channel is not created until the suspended channel is run + * Effect.runSync(Channel.runCollect(channel)) // => [42] * ``` * * @category constructors @@ -548,14 +563,16 @@ export const suspend = ( * * **Example** (Managing resources with acquire-use-release) * - * ```ts + * ```ts import.meta.vitest * import { Channel, Effect } from "effect" * + * const released: Array = [] * const channel = Channel.acquireUseRelease( * Effect.succeed("resource"), * (resource) => Channel.succeed(resource.toUpperCase()), - * (resource, exit) => Effect.log(`Released: ${resource}`) + * (resource, exit) => Effect.sync(() => released.push(resource)) * ) + * const observed = [await Effect.runPromise(Channel.runCollect(channel)), released] // => [["RESOURCE"], ["resource"]] * ``` * * @category constructors @@ -591,13 +608,15 @@ export const acquireUseRelease = = [] * const channel = Channel.acquireRelease( * Effect.succeed("resource"), - * (resource, exit) => Effect.log(`Released: ${resource}`) + * (resource, exit) => Effect.sync(() => released.push(resource)) * ) + * const observed = [await Effect.runPromise(Channel.runCollect(channel)), released] // => [["resource"], ["resource"]] * ``` * * @category constructors @@ -625,12 +644,12 @@ export const acquireRelease: { * * **Example** (Creating channels from iterators) * - * ```ts - * import { Channel } from "effect" + * ```ts import.meta.vitest + * import { Channel, Effect } from "effect" * * const numbers = [1, 2, 3, 4, 5] * const channel = Channel.fromIterator(() => numbers[Symbol.iterator]()) - * // Emits: 1, 2, 3, 4, 5 + * Effect.runSync(Channel.runCollect(channel)) // => [1, 2, 3, 4, 5] * ``` * * @category constructors @@ -652,11 +671,11 @@ export const fromIterator = (iterator: LazyArg>): Channel [1, 2, 3, 4, 5] * ``` * * @category constructors @@ -673,12 +692,12 @@ export const fromArray = (array: ReadonlyArray): Channel => * * **Example** (Creating channels from chunks) * - * ```ts - * import { Channel, Chunk } from "effect" + * ```ts import.meta.vitest + * import { Channel, Chunk, Effect } from "effect" * * const chunk = Chunk.make(1, 2, 3) * const channel = Channel.fromChunk(chunk) - * // Emits: 1, 2, 3 + * Effect.runSync(Channel.runCollect(channel)) // => [1, 2, 3] * ``` * * @category constructors @@ -691,8 +710,8 @@ export const fromChunk = (chunk: Chunk.Chunk): Channel => fromArray(Chu * * **Example** (Batching iterator output) * - * ```ts - * import { Channel } from "effect" + * ```ts import.meta.vitest + * import { Channel, Effect } from "effect" * * // Create a channel from a simple iterator * const numberIterator = (): Iterator => { @@ -708,13 +727,13 @@ export const fromChunk = (chunk: Chunk.Chunk): Channel => fromArray(Chu * } * * const channel = Channel.fromIteratorArray(() => numberIterator(), 2) - * // This will emit arrays: [0, 1], [2], then complete with "finished" + * Effect.runSync(Channel.runCollect(channel)) // => [[0, 1], [2]] * ``` * * **Example** (Batching generator output) * - * ```ts - * import { Channel } from "effect" + * ```ts import.meta.vitest + * import { Channel, Effect } from "effect" * * // Create channel from a generator function * function* fibonacci(): Generator { @@ -726,7 +745,7 @@ export const fromChunk = (chunk: Chunk.Chunk): Channel => fromArray(Chu * } * * const fibChannel = Channel.fromIteratorArray(() => fibonacci(), 3) - * // Emits: [0, 1, 1], [2, 3], then completes + * Effect.runSync(Channel.runCollect(fibChannel)) // => [[0, 1, 1], [2, 3]] * ``` * * @category constructors @@ -764,12 +783,12 @@ export const fromIteratorArray = ( * * **Example** (Creating channels from iterables) * - * ```ts - * import { Channel } from "effect" + * ```ts import.meta.vitest + * import { Channel, Effect } from "effect" * * const set = new Set([1, 2, 3]) * const channel = Channel.fromIterable(set) - * // Emits: 1, 2, 3 + * Effect.runSync(Channel.runCollect(channel)) // => [1, 2, 3] * ``` * * @category constructors @@ -783,12 +802,12 @@ export const fromIterable = (iterable: Iterable): Channel [[1, 2, 3, 4], [5]] * ``` * * @category constructors @@ -804,11 +823,11 @@ export const fromIterableArray = ( * * **Example** (Creating channels that succeed) * - * ```ts - * import { Channel } from "effect" + * ```ts import.meta.vitest + * import { Channel, Effect } from "effect" * * const channel = Channel.succeed(42) - * // Emits: 42 + * Effect.runSync(Channel.runCollect(channel)) // => [42] * ``` * * @category constructors @@ -821,11 +840,11 @@ export const succeed = (value: A): Channel => fromEffect(Effect.succeed(va * * **Example** (Ending with a value) * - * ```ts - * import { Channel } from "effect" + * ```ts import.meta.vitest + * import { Channel, Effect } from "effect" * * const channel = Channel.end("done") - * // Ends immediately with "done", emits nothing + * Effect.runSync(Channel.runCollect(channel)) // => [] * ``` * * @category constructors @@ -847,8 +866,8 @@ export const endSync = (evaluate: LazyArg): Channel => * * **Example** (Computing values lazily) * - * ```ts - * import { Channel } from "effect" + * ```ts import.meta.vitest + * import { Channel, Effect } from "effect" * * let requests = 0 * @@ -856,7 +875,7 @@ export const endSync = (evaluate: LazyArg): Channel => * requests += 1 * return `request-${requests}` * }) - * // Emits "request-1" when the channel runs for the first time + * Effect.runSync(Channel.runCollect(channel)) // => ["request-1"] * ``` * * @category constructors @@ -869,8 +888,8 @@ export const sync = (evaluate: LazyArg): Channel => fromEffect(Effect.s * * **Example** (Creating empty channels) * - * ```ts - * import { Channel } from "effect" + * ```ts import.meta.vitest + * import { Channel, Effect } from "effect" * * // Create an empty channel * const emptyChannel = Channel.empty @@ -882,6 +901,8 @@ export const sync = (evaluate: LazyArg): Channel => fromEffect(Effect.s * // Empty channel can be used as a no-op in conditional logic * const conditionalChannel = (shouldEmit: boolean) => * shouldEmit ? Channel.succeed("data") : Channel.empty + * + * Effect.runSync(Channel.runCollect(conditionalChannel(true))) // => ["data"] * ``` * * @category constructors @@ -894,7 +915,7 @@ export const empty: Channel = fromPull(Effect.succeed(Cause.done())) * * **Example** (Creating non-terminating channels) * - * ```ts + * ```ts import.meta.vitest * import { Channel } from "effect" * * // Create a channel that never completes @@ -909,6 +930,8 @@ export const empty: Channel = fromPull(Effect.succeed(Cause.done())) * // Never channel is useful for testing or as a placeholder * const conditionalChannel = (shouldComplete: boolean) => * shouldComplete ? Channel.succeed("done") : Channel.never + * + * Channel.isChannel(conditionalChannel(false)) // => true * ``` * * @category constructors @@ -921,26 +944,11 @@ export const never: Channel = fromPull(Effect.succeed(Effec * * **Example** (Failing with an error) * - * ```ts - * import { Channel } from "effect" + * ```ts import.meta.vitest + * import { Channel, Effect, Exit } from "effect" * - * // Create a channel that fails with a string error * const failedChannel = Channel.fail("Something went wrong") - * - * // Create a channel that fails with a custom error - * class CustomError extends Error { - * constructor(message: string) { - * super(message) - * this.name = "CustomError" - * } - * } - * const customErrorChannel = Channel.fail(new CustomError("Custom error")) - * - * // Use in error handling by piping to another channel - * const channelWithFallback = Channel.concatWith( - * failedChannel, - * () => Channel.succeed("fallback value") - * ) + * Effect.runSync(Effect.exit(Channel.runCollect(failedChannel))) // => Exit.fail("Something went wrong") * ``` * * @category constructors @@ -959,27 +967,18 @@ export const fail = (error: E): Channel => fromPull(Effect.s * * **Example** (Failing with a lazy error) * - * ```ts - * import { Channel } from "effect" - * - * // Create a channel that fails with a lazily computed error - * const failedChannel = Channel.failSync(() => { - * console.log("Computing error...") - * return new Error("Computed at runtime") - * }) + * ```ts import.meta.vitest + * import { Channel, Effect, Exit } from "effect" * - * // The error computation is deferred until the channel runs * let attempts = 0 * const conditionalError = Channel.failSync(() => { * attempts += 1 * return `Error after attempt ${attempts}` * }) - * - * // Use with expensive error construction - * const expensiveError = Channel.failSync(() => { - * const requestId = "request-123" - * return new Error(`Failed while processing ${requestId}`) - * }) + * const observed = [ + * Effect.runSync(Effect.exit(Channel.runCollect(conditionalError))), + * attempts + * ] // => [Exit.fail("Error after attempt 1"), 1] * ``` * * @category constructors @@ -997,20 +996,12 @@ export const failSync = (evaluate: LazyArg): Channel => f * * **Example** (Failing with causes) * - * ```ts - * import { Cause, Channel } from "effect" + * ```ts import.meta.vitest + * import { Cause, Channel, Effect, Exit } from "effect" * - * // Create a channel that fails with a simple cause * const simpleCause = Cause.fail("Simple error") * const failedChannel = Channel.failCause(simpleCause) - * - * // Create a channel with a die cause - * const dieCause = Cause.die(new Error("System error")) - * const dieFailure = Channel.failCause(dieCause) - * - * // Create a channel with a simple fail cause - * const failCause = Cause.fail("Simple error") - * const simpleFail = Channel.failCause(failCause) + * Effect.runSync(Effect.exit(Channel.runCollect(failedChannel))) // => Exit.failCause(simpleCause) * ``` * * @category constructors @@ -1024,8 +1015,8 @@ export const failCause = (cause: Cause.Cause): Channel => * * **Example** (Failing with lazy causes) * - * ```ts - * import { Cause, Channel } from "effect" + * ```ts import.meta.vitest + * import { Cause, Channel, Effect, Exit } from "effect" * * // Create a channel that fails with a lazily computed cause * let attempts = 0 @@ -1034,11 +1025,10 @@ export const failCause = (cause: Cause.Cause): Channel => * return Cause.fail(`Runtime error after attempt ${attempts}`) * }) * - * // Create a channel with die cause computation - * const dieCauseChannel = Channel.failCauseSync(() => { - * const operation = "load-profile" - * return Cause.die(`Unexpected defect during ${operation}`) - * }) + * const observed = [ + * Effect.runSync(Effect.exit(Channel.runCollect(failedChannel))), + * attempts + * ] // => [Exit.fail("Runtime error after attempt 1"), 1] * ``` * * @category constructors @@ -1053,20 +1043,12 @@ export const failCauseSync = ( * * **Example** (Dying with defects) * - * ```ts - * import { Channel } from "effect" - * - * // Create a channel that dies with a string defect - * const diedChannel = Channel.die("Unrecoverable error") - * - * // Create a channel that dies with an Error object - * const errorDefect = Channel.die(new Error("System failure")) + * ```ts import.meta.vitest + * import { Cause, Channel, Effect, Exit } from "effect" * - * // Die with any value as a defect - * const objectDefect = Channel.die({ - * code: "SYSTEM_FAILURE", - * details: "Critical system component failed" - * }) + * const defect = "Unrecoverable error" + * const diedChannel = Channel.die(defect) + * Effect.runSync(Effect.exit(Channel.runCollect(diedChannel))) // => Exit.failCause(Cause.die(defect)) * ``` * * @category constructors @@ -1079,33 +1061,13 @@ export const die = (defect: unknown): Channel => failCause( * * **Example** (Creating channels from effects) * - * ```ts - * import { Channel, Data, Effect } from "effect" - * - * class DatabaseError extends Data.TaggedError("DatabaseError")<{ - * readonly message: string - * }> {} + * ```ts import.meta.vitest + * import { Channel, Effect } from "effect" * - * // Create a channel from a successful effect * const successChannel = Channel.fromEffect( * Effect.succeed("Hello from effect!") * ) - * - * // Create a channel from an effect that might fail - * const fetchUserChannel = Channel.fromEffect( - * Effect.tryPromise({ - * try: () => fetch("/api/user").then((res) => res.json()), - * catch: (error) => new DatabaseError({ message: String(error) }) - * }) - * ) - * - * // Channel from effect with async computation - * const asyncChannel = Channel.fromEffect( - * Effect.gen(function*() { - * yield* Effect.sleep("100 millis") - * return "Async result" - * }) - * ) + * Effect.runSync(Channel.runCollect(successChannel)) // => ["Hello from effect!"] * ``` * * @category constructors @@ -1173,35 +1135,17 @@ export const fromEffectTake = ( * * **Example** (Creating channels from queues) * - * ```ts - * import { Channel, Data, Effect, Queue } from "effect" - * - * class QueueError extends Data.TaggedError("QueueError")<{ - * readonly reason: string - * }> {} + * ```ts import.meta.vitest + * import { Cause, Channel, Effect, Queue } from "effect" * * const program = Effect.gen(function*() { - * // Create a bounded queue - * const queue = yield* Queue.bounded(10) - * - * // Add some items to the queue - * yield* Queue.offer(queue, "item1") - * yield* Queue.offer(queue, "item2") - * yield* Queue.offer(queue, "item3") - * - * // Create a channel from the queue + * const queue = yield* Queue.bounded(3) + * yield* Queue.offerAll(queue, ["item1", "item2", "item3"]) + * yield* Queue.end(queue) * const channel = Channel.fromQueue(queue) - * - * // The channel will read items from the queue one by one - * return channel - * }) - * - * // Sliding queue example - * const slidingProgram = Effect.gen(function*() { - * const slidingQueue = yield* Queue.sliding(5) - * yield* Queue.offerAll(slidingQueue, [1, 2, 3, 4, 5, 6]) - * return Channel.fromQueue(slidingQueue) + * return yield* Channel.runCollect(channel) * }) + * await Effect.runPromise(program) // => ["item1", "item2", "item3"] * ``` * * @category constructors @@ -1216,39 +1160,17 @@ export const fromQueue = ( * * **Example** (Creating batched channels from queues) * - * ```ts - * import { Channel, Data, Effect, Queue } from "effect" - * - * class ProcessingError extends Data.TaggedError("ProcessingError")<{ - * readonly stage: string - * }> {} + * ```ts import.meta.vitest + * import { Cause, Channel, Effect, Queue } from "effect" * * const program = Effect.gen(function*() { - * // Create a queue for batch processing - * const queue = yield* Queue.bounded(100) - * - * // Fill queue with data - * yield* Queue.offerAll(queue, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) - * - * // Create a channel that reads arrays from the queue + * const queue = yield* Queue.bounded(4) + * yield* Queue.offerAll(queue, [1, 2, 3, 4]) + * yield* Queue.end(queue) * const arrayChannel = Channel.fromQueueArray(queue) - * - * // This will emit non-empty arrays of elements instead of individual items - * // Useful for batch processing scenarios - * return arrayChannel - * }) - * - * // High-throughput processing example - * const batchProcessor = Effect.gen(function*() { - * const dataQueue = yield* Queue.dropping(1000) - * const batchChannel = Channel.fromQueueArray(dataQueue) - * - * // Process data in batches for better performance - * return Channel.map( - * batchChannel, - * (batch) => batch.map((item) => item.toUpperCase()) - * ) + * return yield* Channel.runCollect(arrayChannel) * }) + * await Effect.runPromise(program) // => [[1, 2, 3, 4]] * ``` * * @category constructors @@ -1273,8 +1195,8 @@ export const identity = (): Channel(): Channel Option.some("Hello") * * // Real-time notifications example * const notificationChannel = Effect.gen(function*() { @@ -1327,8 +1251,8 @@ export const fromSubscription = ( * * **Example** (Batching subscription values) * - * ```ts - * import { Channel, Data, Effect, PubSub } from "effect" + * ```ts import.meta.vitest + * import { Channel, Data, Effect, Option, PubSub } from "effect" * * class StreamError extends Data.TaggedError("StreamError")<{ * readonly message: string @@ -1350,12 +1274,14 @@ export const fromSubscription = ( * // The channel will output arrays like [1, 2, 3] and [4] * return channel * }) + * const result = Effect.scoped(Effect.flatMap(program, Channel.runHead)) + * await Effect.runPromise(result) // => Option.some([1, 2, 3, 4]) * ``` * * **Example** (Processing subscription values in batches) * - * ```ts - * import { Channel, Data, Effect, PubSub } from "effect" + * ```ts import.meta.vitest + * import { Channel, Data, Effect, Option, PubSub } from "effect" * * class BatchProcessingError extends Data.TaggedError("BatchProcessingError")<{ * readonly reason: string @@ -1369,19 +1295,21 @@ export const fromSubscription = ( * const batchChannel = Channel.fromSubscriptionArray(subscription) * * // Transform to process each batch - * const processedChannel = Channel.map(batchChannel, (batch) => { - * console.log(`Processing batch of ${batch.length} items:`, batch) - * return batch.map((item) => item.toUpperCase()) - * }) + * const processedChannel = Channel.map(batchChannel, (batch) => + * batch.map((item) => item.toUpperCase()) + * ) * + * yield* PubSub.publishAll(pubsub, ["one", "two"]) * return processedChannel * }) + * const batch = Effect.scoped(Effect.flatMap(batchProcessor, Channel.runHead)) + * await Effect.runPromise(batch) // => Option.some(["ONE", "TWO"]) * ``` * * **Example** (Aggregating subscription metrics) * - * ```ts - * import { Channel, Effect, PubSub } from "effect" + * ```ts import.meta.vitest + * import { Channel, Effect, Option, PubSub } from "effect" * * const metricsAggregator = Effect.gen(function*() { * const metricsPubSub = yield* PubSub.bounded< @@ -1411,8 +1339,12 @@ export const fromSubscription = ( * } * }) * + * yield* PubSub.publish(metricsPubSub, { timestamp: 1, value: 10 }) * return aggregatedChannel * }) + * const metric = Effect.scoped(Effect.flatMap(metricsAggregator, Channel.runHead)) + * const result = await Effect.runPromise(metric) + * Option.map(result, ({ count, sum, average, min, max }) => ({ count, sum, average, min, max })) // => Option.some({ count: 1, sum: 10, average: 10, min: 10, max: 10 }) * ``` * * @category constructors @@ -1434,15 +1366,15 @@ export const fromSubscriptionArray = ( * * **Example** (Creating channels from PubSubs) * - * ```ts - * import { Channel, Data, Effect, PubSub } from "effect" + * ```ts import.meta.vitest + * import { Channel, Data, Effect, Option, PubSub } from "effect" * * class StreamError extends Data.TaggedError("StreamError")<{ * readonly message: string * }> {} * * const program = Effect.gen(function*() { - * const pubsub = yield* PubSub.bounded(16) + * const pubsub = yield* PubSub.unbounded({ replay: 3 }) * * // Create a channel that reads individual values * const channel = Channel.fromPubSub(pubsub) @@ -1455,15 +1387,17 @@ export const fromSubscriptionArray = ( * // The channel will output: 1, 2, 3 (individual values) * return channel * }) + * const result = Effect.scoped(Effect.flatMap(program, Channel.runHead)) + * await Effect.runPromise(result) // => Option.some(1) * ``` * * **Example** (Streaming PubSub notifications) * - * ```ts - * import { Channel, Effect, PubSub } from "effect" + * ```ts import.meta.vitest + * import { Channel, Effect, Option, PubSub } from "effect" * * const notificationService = Effect.gen(function*() { - * const notificationPubSub = yield* PubSub.bounded(50) + * const notificationPubSub = yield* PubSub.unbounded({ replay: 1 }) * * // Create a channel for real-time notifications * const notificationChannel = Channel.fromPubSub(notificationPubSub) @@ -1476,14 +1410,17 @@ export const fromSubscriptionArray = ( * id: `notification:${message}` * })) * + * yield* PubSub.publish(notificationPubSub, "ready") * return timestampedChannel * }) + * const notification = Effect.scoped(Effect.flatMap(notificationService, Channel.runHead)) + * await Effect.runPromise(notification) // => Option.some({ message: "ready", receivedAt: "2024-01-01T00:00:00.000Z", id: "notification:ready" }) * ``` * * **Example** (Processing PubSub events) * - * ```ts - * import { Channel, Effect, PubSub } from "effect" + * ```ts import.meta.vitest + * import { Channel, Effect, Option, PubSub } from "effect" * * interface DomainEvent { * readonly type: string @@ -1492,7 +1429,7 @@ export const fromSubscriptionArray = ( * } * * const eventProcessor = Effect.gen(function*() { - * const eventPubSub = yield* PubSub.bounded(100) + * const eventPubSub = yield* PubSub.unbounded({ replay: 1 }) * * // Create a channel for processing domain events * const eventChannel = Channel.fromPubSub(eventPubSub) @@ -1509,8 +1446,11 @@ export const fromSubscriptionArray = ( * return event * }) * + * yield* PubSub.publish(eventPubSub, { type: "user.created", payload: {}, timestamp: 1 }) * return processedChannel * }) + * const event = Effect.scoped(Effect.flatMap(eventProcessor, Channel.runHead)) + * const result = await Effect.runPromise(event) // => Option.some({ type: "user.created", payload: {}, timestamp: 1, processed: true, processedAt: 2 }) * ``` * * @category constructors @@ -1531,15 +1471,15 @@ export const fromPubSub = ( * * **Example** (Batching PubSub values) * - * ```ts - * import { Channel, Data, Effect, PubSub } from "effect" + * ```ts import.meta.vitest + * import { Channel, Data, Effect, Option, PubSub } from "effect" * * class BatchError extends Data.TaggedError("BatchError")<{ * readonly message: string * }> {} * * const program = Effect.gen(function*() { - * const pubsub = yield* PubSub.bounded(16) + * const pubsub = yield* PubSub.unbounded({ replay: 4 }) * * // Create a channel that reads arrays of values * const channel = Channel.fromPubSubArray(pubsub) @@ -1553,12 +1493,14 @@ export const fromPubSub = ( * // The channel will output arrays like [1, 2, 3] and [4] * return channel * }) + * const result = Effect.scoped(Effect.flatMap(program, Channel.runHead)) + * await Effect.runPromise(result) // => Option.some([1, 2, 3, 4]) * ``` * * **Example** (Processing PubSub orders in batches) * - * ```ts - * import { Channel, Effect, PubSub } from "effect" + * ```ts import.meta.vitest + * import { Channel, Effect, Option, PubSub } from "effect" * * interface Order { * readonly id: string @@ -1569,7 +1511,7 @@ export const fromPubSub = ( * } * * const orderBatchProcessor = Effect.gen(function*() { - * const orderPubSub = yield* PubSub.bounded(100) + * const orderPubSub = yield* PubSub.unbounded({ replay: 1 }) * * // Create a channel that processes orders in batches * const orderChannel = Channel.fromPubSubArray(orderPubSub) @@ -1590,14 +1532,20 @@ export const fromPubSub = ( * } * }) * + * yield* PubSub.publish(orderPubSub, { + * id: "1", customerId: "a", items: ["book"], total: 10, submittedAt: 1 + * }) * return processedChannel * }) + * const order = Effect.scoped(Effect.flatMap(orderBatchProcessor, Channel.runHead)) + * const result = await Effect.runPromise(order) + * Option.map(result, (batch) => [batch.batchSize, batch.totalRevenue, batch.uniqueCustomers]) // => Option.some([1, 10, 1]) * ``` * * **Example** (Processing PubSub logs in batches) * - * ```ts - * import { Channel, Effect, PubSub } from "effect" + * ```ts import.meta.vitest + * import { Channel, Effect, Option, PubSub } from "effect" * * interface LogEntry { * readonly timestamp: number @@ -1607,7 +1555,7 @@ export const fromPubSub = ( * } * * const logAggregator = Effect.gen(function*() { - * const logPubSub = yield* PubSub.bounded(500) + * const logPubSub = yield* PubSub.unbounded({ replay: 1 }) * * // Create a channel that collects logs in batches * const logChannel = Channel.fromPubSubArray(logPubSub) @@ -1634,8 +1582,17 @@ export const fromPubSub = ( * } * }) * + * yield* PubSub.publish(logPubSub, { + * timestamp: 1, + * level: "info", + * message: "ready", + * source: "app" + * } satisfies LogEntry) * return analysisChannel * }) + * const log = Effect.scoped(Effect.flatMap(logAggregator, Channel.runHead)) + * const result = await Effect.runPromise(log) + * Option.map(result, (batch) => [batch.batchId, batch.totalEntries, batch.infoCount]) // => Option.some(["1-1", 1, 1]) * ``` * * @category constructors @@ -1671,6 +1628,229 @@ export const fromSchedule = ( ): Channel => fromPull(Effect.map(Schedule.toStepWithSleep(schedule), (step) => step(void 0))) +/** + * Creates a channel from a lazily supplied Web `ReadableStream`. + * + * **Example** (Reading from a Web stream) + * + * ```ts import.meta.vitest + * import { Channel, Effect } from "effect" + * + * const channel = Channel.fromReadableStream({ + * evaluate: () => new ReadableStream({ + * start(controller) { + * controller.enqueue(1) + * controller.close() + * } + * }), + * onError: (cause) => new Error(String(cause)) + * }) + * + * await Effect.runPromise(Channel.runCollect(channel)) // => [[1]] + * ``` + * + * @category constructors + * @since 4.0.0 + */ +export const fromReadableStream = (options: { + readonly evaluate: LazyArg> + readonly onError: (error: unknown) => E + readonly releaseLockOnEnd?: boolean | undefined +}): Channel, E> => + fromTransform((_, scope) => + readableStreamToPullUnsafe({ + scope, + readable: options.evaluate(), + onError: options.onError, + releaseLockOnEnd: options.releaseLockOnEnd + }) + ) + +/** @internal */ +export const pullIntoWritableStream = (options: { + readonly pull: Pull.Pull, IE, unknown> + readonly writable: WritableStream + readonly onError: (error: unknown) => E + readonly closeOnDone?: boolean | undefined +}): Pull.Pull => + Effect.acquireUseRelease( + Effect.sync(() => options.writable.getWriter()), + (writer) => { + const loop = options.pull.pipe( + Effect.flatMap((chunk) => + Effect.forEach( + chunk, + (value) => + Effect.tryPromise({ + try: () => writer.ready.then(() => writer.write(value)), + catch: options.onError + }), + { discard: true } + ) + ), + Effect.forever({ disableYield: true }) + ) + const withClose = options.closeOnDone !== false + ? Pull.catchDone(loop, (done) => + Effect.andThen( + Effect.tryPromise({ + try: () => writer.close(), + catch: options.onError + }), + Cause.done(done) + )) + : loop + return Effect.onError( + withClose, + (cause) => + Pull.isDoneCause(cause) + ? Effect.void + : Effect.promise(() => writer.abort(cause).catch(constVoid)) + ) + }, + (writer) => Effect.sync(() => writer.releaseLock()) + ) + +/** + * Creates a channel that writes upstream values to a lazily supplied Web + * `WritableStream`. + * + * **Example** (Writing channel input) + * + * ```ts import.meta.vitest + * import { Channel, Effect } from "effect" + * + * const written: Array = [] + * const sink = Channel.fromWritableStream({ + * evaluate: () => new WritableStream({ + * write(value) { + * written.push(value) + * } + * }), + * onError: (cause) => new Error(String(cause)) + * }) + * + * const program = Channel.fromArray([[1, 2] as [number, number]]).pipe( + * Channel.pipeTo(sink), + * Channel.runDrain + * ) + * + * await Effect.runPromise(program) + * written // => [1, 2] + * ``` + * + * @category constructors + * @since 4.0.0 + */ +export const fromWritableStream = (options: { + readonly evaluate: LazyArg> + readonly onError: (error: unknown) => E + readonly closeOnDone?: boolean | undefined +}): Channel, IE> => + fromTransform((pull: Pull.Pull, IE, unknown>) => { + const writable = options.evaluate() + return Effect.succeed(pullIntoWritableStream({ ...options, writable, pull })) + }) + +/** + * Creates a channel backed by a Web `TransformStream`, writing upstream values + * while emitting transformed values from its readable side. + * + * **Example** (Transforming channel input) + * + * ```ts import.meta.vitest + * import { Channel, Effect } from "effect" + * + * const transform = Channel.fromTransformStream({ + * evaluate: () => new TransformStream({ + * transform(value, controller) { + * controller.enqueue(value * 2) + * } + * }), + * onError: (cause) => new Error(String(cause)) + * }) + * + * const program = Channel.fromArray([[1, 2] as [number, number]]).pipe( + * Channel.pipeTo(transform), + * Channel.runCollect + * ) + * + * await Effect.runPromise(program) // => [[2], [4]] + * ``` + * + * @category constructors + * @since 4.0.0 + */ +export const fromTransformStream = (options: { + readonly evaluate: LazyArg> + readonly onError: (error: unknown) => E + readonly closeOnDone?: boolean | undefined + readonly releaseLockOnEnd?: boolean | undefined +}): Channel, IE | E, void, Arr.NonEmptyReadonlyArray, IE> => + fromTransform((upstream, scope) => { + const transform = options.evaluate() + const exit = MutableRef.make | undefined>(undefined) + return pullIntoWritableStream({ + pull: upstream, + writable: transform.writable, + onError: options.onError, + closeOnDone: options.closeOnDone + }).pipe( + Effect.catchCause((cause) => { + if (!Pull.isDoneCause(cause)) { + exit.current = Exit.failCause(cause as Cause.Cause) + } + return Effect.void + }), + Effect.forkIn(scope), + Effect.flatMap(() => + readableStreamToPullUnsafe({ + scope, + exit, + readable: transform.readable, + onError: options.onError, + releaseLockOnEnd: options.releaseLockOnEnd + }) + ) + ) + }) + +const readableStreamToPullUnsafe = (options: { + readonly scope: Scope.Scope + readonly exit?: MutableRef.MutableRef | undefined> | undefined + readonly readable: ReadableStream + readonly onError: (error: unknown) => E + readonly releaseLockOnEnd?: boolean | undefined +}): Effect.Effect, E | E2>, never> => { + const reader = options.readable.getReader() + const exit = options.exit ?? MutableRef.make(undefined) + const pull = Effect.suspend(() => { + if (exit.current) return exit.current + return Effect.matchCauseEffect( + Effect.tryPromise({ + try: () => reader.read(), + catch: options.onError + }), + { + onFailure: (cause) => exit.current ?? Effect.failCause(cause), + onSuccess: ({ done, value }) => { + if (exit.current) return exit.current + return done ? Cause.done() : Effect.succeed(Arr.of(value)) + } + } + ) + }) + return Effect.as( + Scope.addFinalizer( + options.scope, + options.releaseLockOnEnd + ? Effect.sync(() => reader.releaseLock()) + : Effect.promise(() => reader.cancel().catch(constVoid)) + ), + pull + ) +} + /** * Creates a channel that pulls values from an `AsyncIterable`. * @@ -1725,8 +1905,8 @@ export const fromAsyncIterableArray = ( * * **Example** (Mapping channel output) * - * ```ts - * import { Channel, Data } from "effect" + * ```ts import.meta.vitest + * import { Channel, Data, Effect } from "effect" * * class TransformError extends Data.TaggedError("TransformError")<{ * readonly reason: string @@ -1735,12 +1915,12 @@ export const fromAsyncIterableArray = ( * // Basic mapping of channel values * const numbersChannel = Channel.fromIterable([1, 2, 3, 4, 5]) * const doubledChannel = Channel.map(numbersChannel, (n) => n * 2) - * // Outputs: 2, 4, 6, 8, 10 + * Effect.runSync(Channel.runCollect(doubledChannel)) // => [2, 4, 6, 8, 10] * * // Transform string data * const wordsChannel = Channel.fromIterable(["hello", "world", "effect"]) * const upperCaseChannel = Channel.map(wordsChannel, (word) => word.toUpperCase()) - * // Outputs: "HELLO", "WORLD", "EFFECT" + * Effect.runSync(Channel.runCollect(upperCaseChannel)) // => ["HELLO", "WORLD", "EFFECT"] * * // Complex object transformation * type User = { id: number; name: string } @@ -1754,6 +1934,7 @@ export const fromAsyncIterableArray = ( * displayName: `User: ${user.name}`, * isActive: true * })) + * Effect.runSync(Channel.runCollect(displayChannel)) // => [{ displayName: "User: Alice", isActive: true }, { displayName: "User: Bob", isActive: true }] * ``` * * @category sequencing @@ -1862,40 +2043,15 @@ const concurrencyIsSequential = ( * * **Example** (Mapping channel output with effects) * - * ```ts - * import { Channel, Data, Effect } from "effect" - * - * class NetworkError extends Data.TaggedError("NetworkError")<{ - * readonly url: string - * }> {} - * - * // Transform values using effectful operations - * const urlsChannel = Channel.fromIterable([ - * "/api/users/1", - * "/api/users/2", - * "/api/users/3" - * ]) - * - * const fetchDataChannel = Channel.mapEffect( - * urlsChannel, - * (url) => - * Effect.tryPromise({ - * try: () => fetch(url).then((res) => res.json()), - * catch: () => new NetworkError({ url }) - * }) - * ) + * ```ts import.meta.vitest + * import { Channel, Effect } from "effect" * - * // Concurrent processing with options * const numbersChannel = Channel.fromIterable([1, 2, 3, 4, 5]) * const processedChannel = Channel.mapEffect( * numbersChannel, - * (n) => - * Effect.gen(function*() { - * yield* Effect.sleep("100 millis") // Simulate async work - * return n * n - * }), - * { concurrency: 3, unordered: true } + * (n) => Effect.succeed(n * n) * ) + * await Effect.runPromise(Channel.runCollect(processedChannel)) // => [1, 4, 9, 16, 25] * ``` * * @category sequencing @@ -2124,8 +2280,8 @@ export const mapInputError: { * * **Example** (Tapping channel output) * - * ```ts - * import { Channel, Console, Data } from "effect" + * ```ts import.meta.vitest + * import { Channel, Data, Effect } from "effect" * * class LogError extends Data.TaggedError("LogError")<{ * readonly message: string @@ -2135,13 +2291,13 @@ export const mapInputError: { * const numberChannel = Channel.fromIterable([1, 2, 3]) * * // Tap into each output element to perform side effects + * const processed: Array = [] * const tappedChannel = Channel.tap( * numberChannel, - * (n) => Console.log(`Processing number: ${n}`) + * (n) => Effect.sync(() => processed.push(n)) * ) * - * // The channel still outputs the same elements but logs each one - * // Outputs: 1, 2, 3 (while logging each) + * const observed = [await Effect.runPromise(Channel.runCollect(tappedChannel)), processed] // => [[1, 2, 3], [1, 2, 3]] * ``` * * @category sequencing @@ -2188,8 +2344,8 @@ export const tap: { * * **Example** (Flat mapping channel output) * - * ```ts - * import { Channel, Data } from "effect" + * ```ts import.meta.vitest + * import { Channel, Data, Effect } from "effect" * * class ProcessError extends Data.TaggedError("ProcessError")<{ * readonly cause: string @@ -2205,8 +2361,7 @@ export const tap: { * Channel.fromIterable(Array.from({ length: n }, (_, i) => `item-${n}-${i}`)) * ) * - * // Flattens nested channels into a single stream - * // Outputs: "item-1-0", "item-2-0", "item-2-1", "item-3-0", "item-3-1", "item-3-2" + * Effect.runSync(Channel.runCollect(flatMappedChannel)) // => ["item-1-0", "item-2-0", "item-2-1", "item-3-0", "item-3-1", "item-3-2"] * ``` * * @category sequencing @@ -2394,8 +2549,8 @@ const flatMapConcurrent = < * * **Example** (Concatenating with completion values) * - * ```ts - * import { Channel, Data } from "effect" + * ```ts import.meta.vitest + * import { Channel, Data, Effect } from "effect" * * class ConcatError extends Data.TaggedError("ConcatError")<{ * readonly reason: string @@ -2406,8 +2561,7 @@ const flatMapConcurrent = < * Channel.concatWith((sum: void) => Channel.succeed(`Completed processing`)) * ) * - * // Concatenates additional channel based on completion value - * // Outputs: 1, 2, 3, then "Completed processing" + * Effect.runSync(Channel.runCollect(numberChannel)) // => [1, 2, 3, "Completed processing"] * ``` * * @category sequencing @@ -2507,8 +2661,8 @@ export const concatWith: { * * **Example** (Concatenating channels) * - * ```ts - * import { Channel, Data } from "effect" + * ```ts import.meta.vitest + * import { Channel, Data, Effect } from "effect" * * class ConcatError extends Data.TaggedError("ConcatError")<{ * readonly reason: string @@ -2521,7 +2675,7 @@ export const concatWith: { * // Concatenate them * const concatenatedChannel = Channel.concat(firstChannel, secondChannel) * - * // Outputs: 1, 2, 3, "a", "b", "c" + * Effect.runSync(Channel.runCollect(concatenatedChannel)) // => [1, 2, 3, "a", "b", "c"] * ``` * * @category sequencing @@ -2831,8 +2985,8 @@ export const orElseIfEmpty: { * * **Example** (Flattening nested channels) * - * ```ts - * import { Channel, Data } from "effect" + * ```ts import.meta.vitest + * import { Channel, Data, Effect } from "effect" * * class FlattenError extends Data.TaggedError("FlattenError")<{ * readonly cause: string @@ -2848,7 +3002,7 @@ export const orElseIfEmpty: { * // Flatten the nested channels * const flattenedChannel = Channel.flatten(nestedChannels) * - * // Outputs: 1, 2, 3, 4, 5, 6 + * Effect.runSync(Channel.runCollect(flattenedChannel)) // => [1, 2, 3, 4, 5, 6] * ``` * * @category constructors @@ -2886,8 +3040,8 @@ export const flatten = < * * **Example** (Flattening arrays of channel output) * - * ```ts - * import { Channel, Data } from "effect" + * ```ts import.meta.vitest + * import { Channel, Data, Effect } from "effect" * * class FlattenError extends Data.TaggedError("FlattenError")<{ * readonly message: string @@ -2903,7 +3057,7 @@ export const flatten = < * // Flatten the arrays into individual elements * const flattenedChannel = Channel.flattenArray(arrayChannel) * - * // Outputs: 1, 2, 3, 4, 5, 6, 7, 8, 9 + * Effect.runSync(Channel.runCollect(flattenedChannel)) // => [1, 2, 3, 4, 5, 6, 7, 8, 9] * ``` * * @category transforming @@ -2981,8 +3135,8 @@ export const flattenTake = < * * **Example** (Draining channel output) * - * ```ts - * import { Channel } from "effect" + * ```ts import.meta.vitest + * import { Channel, Effect } from "effect" * * // Create a channel that outputs values * const sourceChannel = Channel.fromIterable([1, 2, 3, 4, 5]) @@ -2990,8 +3144,7 @@ export const flattenTake = < * // Drain all output, keeping only the completion * const drainedChannel = Channel.drain(sourceChannel) * - * // The channel completes but emits no values - * // Useful for consuming side effects without collecting output + * Effect.runSync(Channel.runCollect(drainedChannel)) // => [] * ``` * * @category constructors @@ -3139,15 +3292,15 @@ export const schedule: { * * **Example** (Filtering channel output) * - * ```ts - * import { Channel } from "effect" + * ```ts import.meta.vitest + * import { Channel, Effect } from "effect" * * // Create a channel with mixed numbers * const numbersChannel = Channel.fromIterable([1, 2, 3, 4, 5, 6, 7, 8]) * * // Filter to keep only even numbers * const evenChannel = Channel.filter(numbersChannel, (n) => n % 2 === 0) - * // Outputs: 2, 4, 6, 8 + * Effect.runSync(Channel.runCollect(evenChannel)) // => [2, 4, 6, 8] * * // Filter with type refinement * const mixedChannel = Channel.fromIterable([1, "hello", 2, "world", 3]) @@ -3155,7 +3308,7 @@ export const schedule: { * mixedChannel, * (value): value is number => typeof value === "number" * ) - * // Outputs: 1, 2, 3 (all typed as numbers) + * Effect.runSync(Channel.runCollect(numbersOnlyChannel)) // => [1, 2, 3] * ``` * * @category filtering @@ -3349,8 +3502,8 @@ export const filterMapEffect: { * * **Example** (Filtering array output) * - * ```ts - * import { Array, Channel } from "effect" + * ```ts import.meta.vitest + * import { Array, Channel, Effect } from "effect" * * const nonEmptyArrayPredicate = Array.isReadonlyArrayNonEmpty * @@ -3363,7 +3516,7 @@ export const filterMapEffect: { * * // Filter arrays to keep only even numbers * const evenArraysChannel = Channel.filterArray(arrayChannel, (n) => n % 2 === 0) - * // Outputs: [2, 4], [6, 8, 10], [12, 14] + * Effect.runSync(Channel.runCollect(evenArraysChannel)) // => [[2, 4], [6, 8, 10], [12, 14]] * // Note: Only non-empty filtered arrays are emitted * * // Arrays that would become empty after filtering are discarded entirely @@ -3373,7 +3526,7 @@ export const filterMapEffect: { * Array.make(7, 9) * ]).pipe(Channel.filter(nonEmptyArrayPredicate)) * const filteredOddChannel = Channel.filterArray(oddChannel, (n) => n % 2 === 0) - * // Outputs: [2, 4] (the arrays [1,3,5] and [7,9] are discarded) + * Effect.runSync(Channel.runCollect(filteredOddChannel)) // => [[2, 4]] * ``` * * @category filtering @@ -3554,7 +3707,7 @@ export const filterMapArrayEffect: { * * **Example** (Mapping with accumulated state) * - * ```ts + * ```ts import.meta.vitest * import { Channel, Effect } from "effect" * * // Create a channel with numbers @@ -3570,8 +3723,6 @@ export const filterMapArrayEffect: { * return [newSum, [current, newSum]] as const * } * ) - * // Outputs: 1, 1, 2, 3, 3, 6, 4, 10 - * * // Using with Effect for async processing * const asyncMapAccum = Channel.mapAccum( * numbersChannel, @@ -3582,6 +3733,8 @@ export const filterMapArrayEffect: { * return [newAcc, [`${value}-processed`, newAcc]] as const * }) * ) + * Effect.runSync(Channel.runCollect(runningSum)) // => [1, 1, 2, 3, 3, 6, 4, 10] + * Effect.runSync(Channel.runCollect(asyncMapAccum)) // => ["1-processed", "1", "2-processed", "12", "3-processed", "123", "4-processed", "1234"] * ``` * * @category sequencing @@ -3696,15 +3849,15 @@ export const mapAccum: { * * **Example** (Scanning channel output) * - * ```ts - * import { Channel } from "effect" + * ```ts import.meta.vitest + * import { Channel, Effect } from "effect" * * // Create a channel with numbers * const numbersChannel = Channel.fromIterable([1, 2, 3, 4, 5]) * * // Scan to create running sum * const runningSumChannel = Channel.scan(numbersChannel, 0, (sum, n) => sum + n) - * // Outputs: 0, 1, 3, 6, 10, 15 + * Effect.runSync(Channel.runCollect(runningSumChannel)) // => [0, 1, 3, 6, 10, 15] * // Note: emits the initial value and each intermediate result * * // Scan with string concatenation @@ -3714,7 +3867,7 @@ export const mapAccum: { * "", * (sentence, word) => sentence === "" ? word : `${sentence} ${word}` * ) - * // Outputs: "", "hello", "hello world", "hello world from", "hello world from effect" + * Effect.runSync(Channel.runCollect(sentenceChannel)) // => ["", "hello", "hello world", "hello world from", "hello world from effect"] * ``` * * @category sequencing @@ -3760,7 +3913,7 @@ export const scan: { * * **Example** (Scanning channel output with effects) * - * ```ts + * ```ts import.meta.vitest * import { Channel, Data, Effect } from "effect" * * class ScanError extends Data.TaggedError("ScanError")<{ @@ -3776,12 +3929,10 @@ export const scan: { * "", * (acc, value) => * Effect.gen(function*() { - * // Simulate async work - * yield* Effect.sleep("10 millis") * return acc + value.toString() * }) * ) - * // Outputs: "", "1", "12", "123", "1234" + * await Effect.runPromise(Channel.runCollect(asyncScanChannel)) // => ["", "1", "12", "123", "1234"] * * // Scan with error handling * const errorHandlingScan = Channel.scanEffect( @@ -3794,6 +3945,7 @@ export const scan: { * return Effect.succeed(sum + n) * } * ) + * await Effect.runPromise(Channel.runCollect(errorHandlingScan)) // => [0, 1, 3, 6, 10] * ``` * * @category sequencing @@ -3852,8 +4004,8 @@ export const scanEffect: { * * **Example** (Recovering from failure causes) * - * ```ts - * import { Cause, Channel, Data } from "effect" + * ```ts import.meta.vitest + * import { Cause, Channel, Data, Effect } from "effect" * * class ProcessError extends Data.TaggedError("ProcessError")<{ * readonly reason: string @@ -3876,7 +4028,7 @@ export const scanEffect: { * return Channel.succeed("Recovered from interruption") * }) * - * // The channel recovers gracefully from errors + * Effect.runSync(Channel.runCollect(recoveredChannel)) // => ["Recovered from failure"] * ``` * * @category error handling @@ -5072,8 +5224,8 @@ export const catchTag: { * * **Example** (Recovering from nested reasons) * - * ```ts - * import { Channel, Data } from "effect" + * ```ts import.meta.vitest + * import { Channel, Data, Effect } from "effect" * * class RateLimitError extends Data.TaggedError("RateLimitError")<{ * retryAfter: number @@ -5087,15 +5239,15 @@ export const catchTag: { * reason: RateLimitError | QuotaExceededError * }> {} * - * const channel = Channel.fail( - * new AiError({ reason: new RateLimitError({ retryAfter: 60 }) }) - * ) + * const reason = new RateLimitError({ retryAfter: 60 }) + * const channel = Channel.fail(new AiError({ reason })) * * const recovered = channel.pipe( * Channel.catchReason("AiError", "RateLimitError", (reason) => * Channel.succeed(`retry: ${reason.retryAfter}`) * ) * ) + * Effect.runSync(Channel.runCollect(recovered)) // => ["retry: 60"] * ``` * * @category error handling @@ -5457,8 +5609,8 @@ export const catchReasons: { * * **Example** (Promoting nested reasons) * - * ```ts - * import { Channel, Data } from "effect" + * ```ts import.meta.vitest + * import { Channel, Data, Effect, Exit } from "effect" * * class RateLimitError extends Data.TaggedError("RateLimitError")<{ * retryAfter: number @@ -5472,11 +5624,11 @@ export const catchReasons: { * reason: RateLimitError | QuotaExceededError * }> {} * - * const channel = Channel.fail( - * new AiError({ reason: new RateLimitError({ retryAfter: 60 }) }) - * ) + * const reason = new RateLimitError({ retryAfter: 60 }) + * const channel = Channel.fail(new AiError({ reason })) * * const unwrapped = channel.pipe(Channel.unwrapReason("AiError")) + * Effect.runSync(Effect.exit(Channel.runCollect(unwrapped))) // => Exit.fail(reason) * ``` * * @category error handling @@ -5577,20 +5729,21 @@ export const mapError: { * * **Example** (Converting failures to defects) * - * ```ts - * import { Channel, Data } from "effect" + * ```ts import.meta.vitest + * import { Cause, Channel, Data, Effect, Exit } from "effect" * * class ValidationError extends Data.TaggedError("ValidationError")<{ * readonly field: string * }> {} * * // Create a channel that might fail - * const failingChannel = Channel.fail(new ValidationError({ field: "email" })) + * const error = new ValidationError({ field: "email" }) + * const failingChannel = Channel.fail(error) * * // Convert failures to defects * const fatalChannel = Channel.orDie(failingChannel) * - * // Any failure will now become a defect (uncaught exception) + * Effect.runSync(Effect.exit(Channel.runCollect(fatalChannel))) // => Exit.failCause(Cause.die(error)) * ``` * * @category error handling @@ -5791,8 +5944,8 @@ export const retry: { * * **Example** (Switching mapped channels) * - * ```ts - * import { Channel, Data } from "effect" + * ```ts import.meta.vitest + * import { Channel, Data, Effect } from "effect" * * class SwitchError extends Data.TaggedError("SwitchError")<{ * readonly reason: string @@ -5807,7 +5960,7 @@ export const retry: { * (n) => Channel.fromIterable([`value-${n}`]) * ) * - * // Outputs: "value-1", "value-2", "value-3" + * await Effect.runPromise(Channel.runCollect(switchedChannel)) // => ["value-3"] * ``` * * @category sequencing @@ -5915,8 +6068,8 @@ export const switchMap: { * * **Example** (Merging nested channels) * - * ```ts - * import { Channel, Data } from "effect" + * ```ts import.meta.vitest + * import { Channel, Data, Effect } from "effect" * * class MergeAllError extends Data.TaggedError("MergeAllError")<{ * readonly reason: string @@ -5931,11 +6084,11 @@ export const switchMap: { * * // Merge all channels with bounded concurrency * const mergedChannel = Channel.mergeAll({ - * concurrency: 2, + * concurrency: 1, * bufferSize: 16 * })(nestedChannels) * - * // Outputs: 1, 2, 3, 4, 5, 6 (order may vary due to concurrency) + * await Effect.runPromise(Channel.runCollect(mergedChannel)) // => [1, 2, 3, 4, 5, 6] * ``` * * @category combining @@ -6033,8 +6186,21 @@ export const mergeAll: { yield* Effect.gen(function*() { while (true) { - if (semaphore) yield* semaphore.take(1) - const channel = yield* pull + let pullFiber: Fiber.Fiber, any> | undefined + if (semaphore) { + if (fibers.size < concurrencyN) { + yield* semaphore.take(1) + } else { + pullFiber = yield* Effect.forkChild(pull) + yield* Effect.raceFirst( + semaphore.take(1), + Effect.andThen(Fiber.join(pullFiber), Effect.never) + ) + } + } + const channel = pullFiber === undefined + ? yield* pull + : yield* Fiber.join(pullFiber) const childScope = Scope.forkUnsafe(forkedScope) const childPull = yield* toTransform(channel)(upstream, childScope) @@ -6069,7 +6235,13 @@ export const mergeAll: { fibers.add(fiber) } }).pipe( - Effect.catchCause((cause) => doneLatch.whenOpen(Queue.failCause(queue, cause))), + Effect.catchCause((cause) => { + const halt = Pull.filterDone(cause) + if (Result.isSuccess(halt)) { + return doneLatch.whenOpen(Queue.failCause(queue, cause)) + } + return Queue.failCause(queue, cause) + }), Effect.forkIn(forkedScope) ) @@ -6083,14 +6255,11 @@ export const mergeAll: { * * **Example** (Choosing merge halt strategies) * - * ```ts - * import type { Channel } from "effect" + * ```ts import.meta.vitest + * import { Channel } from "effect" * * // Different halt strategies for channel merging - * const leftFirst: Channel.HaltStrategy = "left" // Stop when left channel halts - * const rightFirst: Channel.HaltStrategy = "right" // Stop when right channel halts - * const both: Channel.HaltStrategy = "both" // Stop when both channels halt - * const either: Channel.HaltStrategy = "either" // Stop when either channel halts + * const strategies: Array = ["left", "right", "both", "either"] // => ["left", "right", "both", "either"] * ``` * * @category models @@ -6104,24 +6273,18 @@ export type HaltStrategy = "left" | "right" | "both" | "either" * * **Example** (Merging channels) * - * ```ts - * import { Channel, Data } from "effect" - * - * class MergeError extends Data.TaggedError("MergeError")<{ - * readonly source: string - * }> {} + * ```ts import.meta.vitest + * import { Channel, Effect } from "effect" * * // Create two channels * const leftChannel = Channel.fromIterable([1, 2, 3]) * const rightChannel = Channel.fromIterable(["a", "b", "c"]) * - * // Merge them with "either" halt strategy - * const mergedChannel = Channel.merge(leftChannel, rightChannel, { - * haltStrategy: "either" - * }) + * // The default "both" strategy waits for both channels to complete + * const mergedChannel = Channel.merge(leftChannel, rightChannel) * - * // Outputs elements from both channels concurrently - * // Order may vary: 1, "a", 2, "b", 3, "c" + * const values = await Effect.runPromise(Channel.runCollect(mergedChannel)) + * values.map(String).sort() // => ["1", "2", "3", "a", "b", "c"] * ``` * * @category combining @@ -6314,19 +6477,16 @@ export const mergeEffect: { * * **Example** (Splitting string chunks into lines) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * - * Effect.runPromise(Effect.gen(function*() { - * const result = yield* Stream.runCollect( - * Stream.splitLines(Stream.make("hel", "lo\r\nwor", "ld\n")) - * ) - * console.log(result) - * // [ 'hello', 'world' ] - * })) + * const result = await Effect.runPromise(Stream.runCollect( + * Stream.splitLines(Stream.make("hel", "lo\r\nwor", "ld\n")) + * )) + * result // => ["hello", "world"] * ``` * - * @category String manipulation + * @category splitting * @since 2.0.0 */ export const splitLines = (): Channel< @@ -6440,7 +6600,7 @@ export const splitLines = (): Channel< * span `Uint8Array` boundaries. The optional `encoding` and `options` are * passed to `TextDecoder`. * - * @category String manipulation + * @category decoding * @since 4.0.0 */ export const decodeText = (encoding?: string, options?: TextDecoderOptions): Channel< @@ -6466,7 +6626,7 @@ export const decodeText = (encoding?: string, options?: TextDecoderOp * * Each string inside an emitted array is encoded independently. * - * @category String manipulation + * @category encoding * @since 4.0.0 */ export const encodeText = (): Channel< @@ -6492,8 +6652,8 @@ export const encodeText = (): Channel< * * **Example** (Piping one channel into another) * - * ```ts - * import { Channel, Data } from "effect" + * ```ts import.meta.vitest + * import { Channel, Data, Effect } from "effect" * * class PipeError extends Data.TaggedError("PipeError")<{ * readonly stage: string @@ -6506,7 +6666,7 @@ export const encodeText = (): Channel< * // Pipe the source into the transform * const pipedChannel = Channel.pipeTo(sourceChannel, transformChannel) * - * // Outputs: 2, 4, 6 + * Effect.runSync(Channel.runCollect(pipedChannel)) // => [2, 4, 6] * ``` * * @category sequencing @@ -6540,21 +6700,22 @@ export const pipeTo: { * * **Example** (Piping while preserving failures) * - * ```ts - * import { Channel, Data } from "effect" + * ```ts import.meta.vitest + * import { Channel, Data, Effect, Exit } from "effect" * * class SourceError extends Data.TaggedError("SourceError")<{ * readonly code: number * }> {} * * // Create a failing source channel - * const failingSource = Channel.fail(new SourceError({ code: 404 })) - * const safeTransform = Channel.succeed("transformed") + * const error = new SourceError({ code: 404 }) + * const failingSource = Channel.fail(error) + * const safeTransform = Channel.identity() * * // Pipe while preserving source failures * const safePipedChannel = Channel.pipeToOrFail(failingSource, safeTransform) * - * // Source errors are preserved and not sent to transform channel + * Effect.runSync(Effect.exit(Channel.runCollect(safePipedChannel))) // => Exit.fail(error) * ``` * * @category sequencing @@ -6602,7 +6763,7 @@ export const pipeToOrFail: { * * **Example** (Unwrapping channel effects) * - * ```ts + * ```ts import.meta.vitest * import { Channel, Data, Effect } from "effect" * * class UnwrapError extends Data.TaggedError("UnwrapError")<{ @@ -6617,7 +6778,7 @@ export const pipeToOrFail: { * // Unwrap the effect to get the channel * const unwrappedChannel = Channel.unwrap(channelEffect) * - * // The resulting channel outputs: 1, 2, 3 + * Effect.runSync(Channel.runCollect(unwrappedChannel)) // => [1, 2, 3] * ``` * * @category constructors @@ -6666,7 +6827,7 @@ export const scoped = ( * * **Example** (Embedding custom input handling) * - * ```ts + * ```ts import.meta.vitest * import { Channel, Effect } from "effect" * * // Create a base channel @@ -6677,13 +6838,11 @@ export const scoped = ( * baseChannel, * (upstream) => * upstream.pipe( - * Effect.tap((message) => - * Effect.sync(() => console.log(message)) - * ), * Effect.forever, * Effect.ignore * ) * ) + * await Effect.runPromise(Channel.runCollect(embeddedChannel)) // => [1, 2, 3] * ``` * * @category sequencing @@ -6741,7 +6900,7 @@ export const embedInput: { * * @see {@link bufferArray} for buffering elements from array outputs * - * @category Buffering + * @category buffering * @since 2.0.0 */ export const buffer: { @@ -6807,7 +6966,7 @@ export const buffer: { * * @see {@link buffer} for buffering output elements without flattening arrays * - * @category Buffering + * @category buffering * @since 4.0.0 */ export const bufferArray: { @@ -6915,17 +7074,16 @@ export const haltWhen: { ): Channel => fromTransformBracket(Effect.fnUntraced(function*(upstream, scope, forkedScope) { const pull = yield* toTransform(self)(upstream, scope) - let haltCause: Cause.Cause> | undefined = undefined - yield* effect.pipe( - Effect.catchCause((cause) => { - haltCause = cause - return Effect.void - }), - Effect.forkIn(forkedScope) - ) - return Effect.suspend((): Pull.Pull => - haltCause ? Effect.failCause(haltCause) : pull - ) + const fiber = yield* Effect.forkIn(effect, forkedScope, { startImmediately: true }) + return Effect.suspend((): Pull.Pull => { + const exit = fiber.pollUnsafe() + return exit === undefined + ? pull + : Exit.match(exit, { + onFailure: Effect.failCause, + onSuccess: Cause.done + }) + }) }))) /** @@ -6961,8 +7119,8 @@ export const onError: { * * **Example** (Running exit finalizers) * - * ```ts - * import { Channel, Console, Data, Exit } from "effect" + * ```ts import.meta.vitest + * import { Channel, Data, Effect, Exit } from "effect" * * class ExitError extends Data.TaggedError("ExitError")<{ * readonly stage: string @@ -6972,13 +7130,12 @@ export const onError: { * const dataChannel = Channel.fromIterable([1, 2, 3]) * * // Attach exit handler + * const exits: Array> = [] * const channelWithExit = Channel.onExit(dataChannel, (exit) => { - * if (Exit.isSuccess(exit)) { - * return Console.log(`Channel completed successfully with: ${exit.value}`) - * } else { - * return Console.log(`Channel failed with: ${exit.cause}`) - * } + * exits.push(exit) + * return Effect.void * }) + * const observed = [await Effect.runPromise(Channel.runCollect(channelWithExit)), exits] // => [[1, 2, 3], [Exit.void]] * ``` * * @category resource management @@ -7109,8 +7266,8 @@ export const onEnd: { * * **Example** (Ensuring cleanup runs) * - * ```ts - * import { Channel, Console, Data } from "effect" + * ```ts import.meta.vitest + * import { Channel, Data, Effect } from "effect" * * class EnsureError extends Data.TaggedError("EnsureError")<{ * readonly operation: string @@ -7120,10 +7277,12 @@ export const onEnd: { * const dataChannel = Channel.fromIterable([1, 2, 3]) * * // Ensure cleanup always runs + * const events: Array = [] * const channelWithCleanup = Channel.ensuring( * dataChannel, - * Console.log("Cleanup executed regardless of success or failure") + * Effect.sync(() => events.push("cleanup")) * ) + * const observed = [await Effect.runPromise(Channel.runCollect(channelWithCleanup)), events] // => [[1, 2, 3], ["cleanup"]] * ``` * * @category resource management @@ -7170,7 +7329,7 @@ const runWith = < /** * Creates a channel from the specified services. * - * @category services + * @category accessors * @since 2.0.0 */ export const contextWith = ( @@ -7184,7 +7343,7 @@ export const contextWith = ( /** * The starting channel for Do notation, emitting an empty object. * - * @category do notation + * @category constructors * @since 4.0.0 */ export const Do: Channel<{}> = succeed({}) @@ -7503,7 +7662,7 @@ export { /** * Adds a computed field to each object emitted by a channel. * - * @category do notation + * @category mapping * @since 4.0.0 */ let_ as let @@ -7519,7 +7678,7 @@ export { * channel's output becomes the value of the new field. `options.concurrency` * and `options.bufferSize` control how derived channels are flattened. * - * @category do notation + * @category sequencing * @since 4.0.0 */ export const bind: { @@ -7625,7 +7784,7 @@ export const bind: { * @see {@link bind} for adding a field produced by another channel * @see {@link let_ let} for adding a computed field * - * @category do notation + * @category mapping * @since 4.0.0 */ export const bindTo: { @@ -7670,8 +7829,8 @@ export const bindTo: { * * **Example** (Counting channel output) * - * ```ts - * import { Channel, Data } from "effect" + * ```ts import.meta.vitest + * import { Channel, Data, Effect } from "effect" * * class CountError extends Data.TaggedError("CountError")<{ * readonly reason: string @@ -7683,23 +7842,23 @@ export const bindTo: { * // Count the elements * const countEffect = Channel.runCount(numbersChannel) * - * // Effect.runSync(countEffect) // Returns: 5 + * Effect.runSync(countEffect) // => 5 * ``` * - * @category execution + * @category running * @since 4.0.0 */ export const runCount = ( self: Channel -): Effect.Effect => runFold(self, () => 0, (acc) => acc + 1) +): Effect.Effect => runFold(self, () => 0, (acc) => acc + 1) /** * Runs a channel and discards all output elements, returning only the final result. * * **Example** (Draining channel output at runtime) * - * ```ts - * import { Channel, Data } from "effect" + * ```ts import.meta.vitest + * import { Channel, Data, Effect } from "effect" * * class DrainError extends Data.TaggedError("DrainError")<{ * readonly stage: string @@ -7707,18 +7866,15 @@ export const runCount = ( * * // Create a channel that outputs elements and completes with a result * const resultChannel = Channel.fromIterable([1, 2, 3]) - * const completedChannel = Channel.concatWith( - * resultChannel, - * () => Channel.succeed("completed") - * ) + * const completedChannel = Channel.concat(resultChannel, Channel.end("completed")) * * // Drain all elements and get only the final result * const drainEffect = Channel.runDrain(completedChannel) * - * // Effect.runSync(drainEffect) // Returns: "completed" + * Effect.runSync(drainEffect) // => "completed" * ``` * - * @category execution + * @category running * @since 2.0.0 */ export const runDrain = ( @@ -7730,8 +7886,8 @@ export const runDrain = ( * * **Example** (Running effects for each output) * - * ```ts - * import { Channel, Console, Data } from "effect" + * ```ts import.meta.vitest + * import { Channel, Data, Effect } from "effect" * * class ForEachError extends Data.TaggedError("ForEachError")<{ * readonly element: unknown @@ -7740,16 +7896,18 @@ export const runDrain = ( * // Create a channel with numbers * const numbersChannel = Channel.fromIterable([1, 2, 3]) * - * // Run forEach to log each element + * // Run forEach to process each element + * const processed: Array = [] * const forEachEffect = Channel.runForEach( * numbersChannel, - * (n) => Console.log(`Processing: ${n}`) + * (n) => Effect.sync(() => processed.push(n)) * ) * - * // Logs: "Processing: 1", "Processing: 2", "Processing: 3" + * await Effect.runPromise(forEachEffect) + * processed // => [1, 2, 3] * ``` * - * @category execution + * @category running * @since 4.0.0 */ export const runForEach: { @@ -7780,7 +7938,7 @@ export const runForEach: { * Returning `true` continues consuming the channel. Returning `false` stops * consumption early. The returned effect completes with `void`. * - * @category execution + * @category running * @since 4.0.0 */ export const runForEachWhile: { @@ -7807,13 +7965,71 @@ export const runForEachWhile: { )) ) +/** + * 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 { Channel, Data } from "effect" + * ```ts import.meta.vitest + * import { Channel, Data, Effect } from "effect" * * class CollectError extends Data.TaggedError("CollectError")<{ * readonly reason: string @@ -7825,10 +8041,10 @@ export const runForEachWhile: { * // Collect all elements into an array * const collectEffect = Channel.runCollect(numbersChannel) * - * // Effect.runSync(collectEffect) // Returns: [1, 2, 3, 4, 5] + * Effect.runSync(collectEffect) // => [1, 2, 3, 4, 5] * ``` * - * @category execution + * @category running * @since 2.0.0 */ export const runCollect = ( @@ -7842,7 +8058,7 @@ export const runCollect = ( /** * Runs a channel and outputs the done value. * - * @category execution + * @category running * @since 4.0.0 */ export const runDone = ( @@ -7858,7 +8074,7 @@ export const runDone = ( * Returns `Option.some` with the first output element, or `Option.none` if the * channel completes without emitting output. * - * @category execution + * @category running * @since 4.0.0 */ export const runHead = ( @@ -7885,7 +8101,7 @@ export const runHead = ( * Returns `Option.some` with the last emitted element, or `Option.none` if the * channel completes without emitting output. * - * @category execution + * @category running * @since 4.0.0 */ export const runLast = ( @@ -7913,8 +8129,8 @@ export const runLast = ( * * **Example** (Folding channel output) * - * ```ts - * import { Channel, Data } from "effect" + * ```ts import.meta.vitest + * import { Channel, Data, Effect } from "effect" * * class FoldError extends Data.TaggedError("FoldError")<{ * readonly operation: string @@ -7926,10 +8142,10 @@ export const runLast = ( * // Fold to calculate sum * const sumEffect = Channel.runFold(numbersChannel, () => 0, (acc, n) => acc + n) * - * // Effect.runSync(sumEffect) // Returns: 15 + * Effect.runSync(sumEffect) // => 15 * ``` * - * @category execution + * @category running * @since 4.0.0 */ export const runFold: { @@ -7979,7 +8195,7 @@ export const runFold: { * the effectful accumulator function. The returned effect succeeds with the * final accumulator value. * - * @category execution + * @category running * @since 4.0.0 */ export const runFoldEffect: { @@ -8029,7 +8245,7 @@ export const runFoldEffect: { * * **Example** (Converting channels to pulls) * - * ```ts + * ```ts import.meta.vitest * import { Channel, Data, Effect } from "effect" * * class PullError extends Data.TaggedError("PullError")<{ @@ -8039,12 +8255,11 @@ export const runFoldEffect: { * // Create a channel * const numbersChannel = Channel.fromIterable([1, 2, 3]) * - * // Convert to Pull within a scope - * const pullEffect = Effect.scoped( - * Channel.toPull(numbersChannel) - * ) - * - * // Use the Pull to manually consume elements + * const program = Effect.scoped(Effect.gen(function*() { + * const pull = yield* Channel.toPull(numbersChannel) + * return [yield* pull, yield* pull, yield* pull] + * })) + * await Effect.runPromise(program) // => [1, 2, 3] * ``` * * @category destructors @@ -8078,7 +8293,7 @@ export const toPull: ( * * **Example** (Converting channels to scoped pulls) * - * ```ts + * ```ts import.meta.vitest * import { Channel, Data, Effect, Scope } from "effect" * * class ScopedPullError extends Data.TaggedError("ScopedPullError")<{ @@ -8090,10 +8305,11 @@ export const toPull: ( * * // Convert to Pull with explicit scope * const scopedPullEffect = Effect.gen(function*() { - * const scope = yield* Scope.make() + * const scope = yield* Effect.scope * const pull = yield* Channel.toPullScoped(numbersChannel, scope) - * return pull + * return [yield* pull, yield* pull, yield* pull] * }) + * await Effect.runPromise(Effect.scoped(scopedPullEffect)) // => [1, 2, 3] * ``` * * @category destructors @@ -8201,8 +8417,8 @@ export const runIntoQueueArray: { * * **Example** (Converting channels to queues) * - * ```ts - * import { Channel, Data } from "effect" + * ```ts import.meta.vitest + * import { Channel, Data, Effect, Queue } from "effect" * * class QueueError extends Data.TaggedError("QueueError")<{ * readonly operation: string @@ -8212,10 +8428,11 @@ export const runIntoQueueArray: { * const dataChannel = Channel.fromIterable([1, 2, 3, 4, 5]) * * // Convert to queue for concurrent processing - * const queueEffect = Channel.toQueue(dataChannel, { capacity: 32 }) - * - * // The queue can be used for concurrent consumption - * // Multiple consumers can read from the queue + * const program = Effect.scoped(Effect.gen(function*() { + * const queue = yield* Channel.toQueue(dataChannel, { capacity: 32 }) + * return yield* Queue.takeBetween(queue, 5, 5) + * })) + * await Effect.runPromise(program) // => [1, 2, 3, 4, 5] * ``` * * @category destructors diff --git a/repos/effect/packages/effect/src/ChannelSchema.ts b/repos/effect/packages/effect/src/ChannelSchema.ts index fb2b4864c4..6f91859ecc 100644 --- a/repos/effect/packages/effect/src/ChannelSchema.ts +++ b/repos/effect/packages/effect/src/ChannelSchema.ts @@ -136,7 +136,7 @@ export const decodeUnknown: ( Arr.NonEmptyReadonlyArray, IE | Schema.SchemaError, Done, - Arr.NonEmptyReadonlyArray, + Arr.NonEmptyReadonlyArray, IE, Done, S["DecodingServices"] diff --git a/repos/effect/packages/effect/src/Chunk.ts b/repos/effect/packages/effect/src/Chunk.ts index 67b2f997af..7a75730a68 100644 --- a/repos/effect/packages/effect/src/Chunk.ts +++ b/repos/effect/packages/effect/src/Chunk.ts @@ -37,12 +37,12 @@ const TypeId = "~effect/collections/Chunk" * * **Example** (Inspecting chunk values) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * * const chunk: Chunk.Chunk = Chunk.make(1, 2, 3) - * console.log(chunk.length) // 3 - * console.log(Chunk.toArray(chunk)) // [1, 2, 3] + * chunk.length // => 3 + * Chunk.toArray(chunk) // => [1, 2, 3] * ``` * * @category models @@ -64,12 +64,12 @@ export interface Chunk extends Iterable, Equal.Equal, Pipeable, Inspec * * **Example** (Working with non-empty chunks) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * * const nonEmptyChunk: Chunk.NonEmptyChunk = Chunk.make(1, 2, 3) - * console.log(Chunk.headNonEmpty(nonEmptyChunk)) // 1 - * console.log(Chunk.lastNonEmpty(nonEmptyChunk)) // 3 + * Chunk.headNonEmpty(nonEmptyChunk) // => 1 + * Chunk.lastNonEmpty(nonEmptyChunk) // => 3 * ``` * * @category models @@ -82,7 +82,7 @@ export interface NonEmptyChunk extends Chunk, NonEmptyIterable {} * * **Example** (Applying the Chunk type lambda) * - * ```ts + * ```ts import.meta.vitest * import type { Chunk, HKT } from "effect" * * // Create a Chunk type using the type lambda @@ -90,7 +90,7 @@ export interface NonEmptyChunk extends Chunk, NonEmptyIterable {} * // Equivalent to: Chunk * ``` * - * @category type lambdas + * @category utility types * @since 2.0.0 */ export interface ChunkTypeLambda extends TypeLambda { @@ -152,7 +152,7 @@ const emptyArray: ReadonlyArray = [] * * **Example** (Comparing chunks for equivalence) * - * ```ts + * ```ts import.meta.vitest * import { Chunk, Equivalence } from "effect" * * const chunk1 = Chunk.make(1, 2, 3) @@ -160,8 +160,8 @@ const emptyArray: ReadonlyArray = [] * const chunk3 = Chunk.make(1, 2, 4) * * const eq = Chunk.makeEquivalence(Equivalence.strictEqual()) - * console.log(eq(chunk1, chunk2)) // true - * console.log(eq(chunk1, chunk3)) // false + * eq(chunk1, chunk2) // => true + * eq(chunk1, chunk3) // => false * ``` * * @category instances @@ -262,18 +262,18 @@ const makeChunk = (backing: Backing): Chunk => { * * **Example** (Checking for chunks) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * * const chunk = Chunk.make(1, 2, 3) * const array = [1, 2, 3] * - * console.log(Chunk.isChunk(chunk)) // true - * console.log(Chunk.isChunk(array)) // false - * console.log(Chunk.isChunk("string")) // false + * Chunk.isChunk(chunk) // => true + * Chunk.isChunk(array) // => false + * Chunk.isChunk("string") // => false * ``` * - * @category constructors + * @category guards * @since 2.0.0 */ export const isChunk: { @@ -288,11 +288,10 @@ const _empty = makeChunk({ _tag: "IEmpty" }) * * **Example** (Creating an empty chunk) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * - * const emptyChunk = Chunk.empty() - * console.log(Chunk.size(emptyChunk)) // 0 + * Chunk.size(Chunk.empty()) // => 0 * ``` * * @category constructors @@ -305,11 +304,10 @@ export const empty: () => Chunk = () => _empty * * **Example** (Creating a non-empty chunk) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * - * const chunk = Chunk.make(1, 2, 3, 4) - * console.log(Chunk.toArray(chunk)) // [1, 2, 3, 4] + * Chunk.toArray(Chunk.make(1, 2, 3, 4)) // => [1, 2, 3, 4] * ``` * * @category constructors @@ -323,11 +321,10 @@ export const make = ]>(...as: As): NonEm * * **Example** (Creating a single-element chunk) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * - * const chunk = Chunk.of("hello") - * console.log(Chunk.toArray(chunk)) // ["hello"] + * Chunk.toArray(Chunk.of("hello")) // => ["hello"] * ``` * * @category constructors @@ -340,11 +337,10 @@ export const of = (a: A): NonEmptyChunk => makeChunk({ _tag: "ISingleton", * * **Example** (Creating chunks from iterables) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * - * const chunk = Chunk.fromIterable([1, 2, 3]) - * console.log(Chunk.toArray(chunk)) // [1, 2, 3] + * Chunk.toArray(Chunk.fromIterable([1, 2, 3])) // => [1, 2, 3] * ``` * * @category constructors @@ -390,17 +386,16 @@ const toArray_ = (self: Chunk): Array => toReadonlyArray(self).slice() * * **Example** (Converting chunks to mutable arrays) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * * const chunk = Chunk.make(1, 2, 3) * const array = Chunk.toArray(chunk) - * console.log(array) // [1, 2, 3] - * console.log(Array.isArray(array)) // true + * array // => [1, 2, 3] + * Array.isArray(array) // => true * * // With empty chunk - * const emptyChunk = Chunk.empty() - * console.log(Chunk.toArray(emptyChunk)) // [] + * Chunk.toArray(Chunk.empty()) // => [] * ``` * * @category converting @@ -440,19 +435,18 @@ const toReadonlyArray_ = (self: Chunk): ReadonlyArray => { * * **Example** (Converting chunks to readonly arrays) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * * const chunk = Chunk.make(1, 2, 3) * const readonlyArray = Chunk.toReadonlyArray(chunk) - * console.log(readonlyArray) // [1, 2, 3] + * readonlyArray // => [1, 2, 3] * * // The result is read-only, modifications would cause TypeScript errors * // readonlyArray[0] = 10 // TypeScript error * * // With empty chunk - * const emptyChunk = Chunk.empty() - * console.log(Chunk.toReadonlyArray(emptyChunk)) // [] + * Chunk.toReadonlyArray(Chunk.empty()) // => [] * ``` * * @category converting @@ -493,16 +487,14 @@ const reverseChunk = (self: Chunk): Chunk => { * * **Example** (Reversing chunks) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * * const chunk = Chunk.make(1, 2, 3) - * const result = Chunk.reverse(chunk) - * - * console.log(Chunk.toArray(result)) // [3, 2, 1] + * Chunk.toArray(Chunk.reverse(chunk)) // => [3, 2, 1] * ``` * - * @category elements + * @category transforming * @since 2.0.0 */ export const reverse: >(self: S) => Chunk.With> = reverseChunk as any @@ -513,21 +505,20 @@ export const reverse: >(self: S) => Chunk.With Option.some("b") + * Chunk.get(chunk, 10) // => Option.none() + * Chunk.get(chunk, -1) // => Option.none() * * // Using pipe syntax - * const result = chunk.pipe(Chunk.get(2)) - * console.log(result) // Option.some("c") + * chunk.pipe(Chunk.get(2)) // => Option.some("c") * ``` * - * @category elements + * @category getters * @since 2.0.0 */ export const get: { @@ -553,16 +544,16 @@ export const get: { * * **Example** (Creating chunks without copying arrays) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * * const array = [1, 2, 3, 4, 5] * const chunk = Chunk.fromArrayUnsafe(array) - * console.log(Chunk.toArray(chunk)) // [1, 2, 3, 4, 5] + * Chunk.toArray(chunk) // => [1, 2, 3, 4, 5] * * // Warning: Since this doesn't copy the array, mutations affect the chunk * array[0] = 999 - * console.log(Chunk.toArray(chunk)) // [999, 2, 3, 4, 5] + * Chunk.toArray(chunk) // => [999, 2, 3, 4, 5] * ``` * * @category unsafe @@ -585,15 +576,15 @@ export const fromArrayUnsafe = (self: ReadonlyArray): Chunk => * * **Example** (Creating non-empty chunks without copying arrays) * - * ```ts + * ```ts import.meta.vitest * import { Array, Chunk } from "effect" * * const nonEmptyArray = Array.make(1, 2, 3, 4, 5) * const chunk = Chunk.fromNonEmptyArrayUnsafe(nonEmptyArray) - * console.log(Chunk.toArray(chunk)) // [1, 2, 3, 4, 5] + * Chunk.toArray(chunk) // => [1, 2, 3, 4, 5] * * // The result is guaranteed to be non-empty - * console.log(Chunk.isNonEmpty(chunk)) // true + * Chunk.isNonEmpty(chunk) // => true * ``` * * @category unsafe @@ -616,16 +607,16 @@ export const fromNonEmptyArrayUnsafe = (self: NonEmptyReadonlyArray): NonE * * **Example** (Accessing elements unsafely) * - * ```ts + * ```ts import.meta.vitest * import { Chunk, Option } from "effect" * * const chunk = Chunk.make("a", "b", "c", "d") * - * console.log(Chunk.getUnsafe(chunk, 1)) // "b" - * console.log(Chunk.getUnsafe(chunk, 3)) // "d" + * Chunk.getUnsafe(chunk, 1) // => "b" + * Chunk.getUnsafe(chunk, 3) // => "d" * * // Use Chunk.get when the index may be out of bounds - * console.log(Option.isNone(Chunk.get(chunk, 10))) // true + * Option.isNone(Chunk.get(chunk, 10)) // => true * ``` * * @category unsafe @@ -673,17 +664,15 @@ export const getUnsafe: { * * **Example** (Appending an element) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * * const chunk = Chunk.make(1, 2, 3) - * const newChunk = Chunk.append(chunk, 4) - * console.log(Chunk.toArray(newChunk)) // [1, 2, 3, 4] + * Chunk.toArray(Chunk.append(chunk, 4)) // => [1, 2, 3, 4] * * // Appending to empty chunk * const emptyChunk = Chunk.empty() - * const singleElement = Chunk.append(emptyChunk, 42) - * console.log(Chunk.toArray(singleElement)) // [42] + * Chunk.toArray(Chunk.append(emptyChunk, 42)) // => [42] * ``` * * @see {@link prepend} for adding one element before the existing elements @@ -702,17 +691,15 @@ export const append: { * * **Example** (Prepending an element) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * * const chunk = Chunk.make(2, 3, 4) - * const newChunk = Chunk.prepend(chunk, 1) - * console.log(Chunk.toArray(newChunk)) // [1, 2, 3, 4] + * Chunk.toArray(Chunk.prepend(chunk, 1)) // => [1, 2, 3, 4] * * // Prepending to empty chunk * const emptyChunk = Chunk.empty() - * const singleElement = Chunk.prepend(emptyChunk, "first") - * console.log(Chunk.toArray(singleElement)) // ["first"] + * Chunk.toArray(Chunk.prepend(emptyChunk, "first")) // => ["first"] * ``` * * @category combining @@ -728,21 +715,21 @@ export const prepend: { * * **Example** (Taking elements from the start) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * * const chunk = Chunk.make(1, 2, 3, 4, 5) - * const result = Chunk.take(chunk, 3) - * console.log(Chunk.toArray(result)) // [1, 2, 3] + * Chunk.toArray(Chunk.take(chunk, 3)) // => [1, 2, 3] * ``` * - * @category elements + * @category filtering * @since 2.0.0 */ export const take: { (n: number): (self: Chunk) => Chunk (self: Chunk, n: number): Chunk -} = dual(2, (self: Chunk, n: number): Chunk => { +} = dual(2, (self: Chunk, _n: number): Chunk => { + const n = Math.floor(_n) if (n <= 0) { return _empty } else if (n >= self.length) { @@ -785,21 +772,21 @@ export const take: { * * **Example** (Dropping elements from the start) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * * const chunk = Chunk.make(1, 2, 3, 4, 5) - * const result = Chunk.drop(chunk, 2) - * console.log(Chunk.toArray(result)) // [3, 4, 5] + * Chunk.toArray(Chunk.drop(chunk, 2)) // => [3, 4, 5] * ``` * - * @category elements + * @category filtering * @since 2.0.0 */ export const drop: { (n: number): (self: Chunk) => Chunk (self: Chunk, n: number): Chunk -} = dual(2, (self: Chunk, n: number): Chunk => { +} = dual(2, (self: Chunk, _n: number): Chunk => { + const n = Math.floor(_n) if (n <= 0) { return self } else if (n >= self.length) { @@ -841,15 +828,14 @@ export const drop: { * * **Example** (Dropping elements from the end) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * * const chunk = Chunk.make(1, 2, 3, 4, 5) - * const result = Chunk.dropRight(chunk, 2) - * console.log(Chunk.toArray(result)) // [1, 2, 3] + * Chunk.toArray(Chunk.dropRight(chunk, 2)) // => [1, 2, 3] * ``` * - * @category elements + * @category filtering * @since 2.0.0 */ export const dropRight: { @@ -862,15 +848,14 @@ export const dropRight: { * * **Example** (Dropping elements while a predicate matches) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * * const chunk = Chunk.make(1, 2, 3, 4, 5) - * const result = Chunk.dropWhile(chunk, (n) => n < 3) - * console.log(Chunk.toArray(result)) // [3, 4, 5] + * Chunk.toArray(Chunk.dropWhile(chunk, (n) => n < 3)) // => [3, 4, 5] * ``` * - * @category elements + * @category filtering * @since 2.0.0 */ export const dropWhile: { @@ -892,16 +877,13 @@ export const dropWhile: { * * **Example** (Prepending all elements) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * - * const result = Chunk.make(1, 2).pipe( + * Chunk.make(1, 2).pipe( * Chunk.prependAll(Chunk.make("a", "b")), * Chunk.toArray - * ) - * - * console.log(result) - * // [ "a", "b", 1, 2 ] + * ) // => ["a", "b", 1, 2] * ``` * * @category combining @@ -927,16 +909,13 @@ export const prependAll: { * * **Example** (Appending all elements) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * - * const result = Chunk.make(1, 2).pipe( + * Chunk.make(1, 2).pipe( * Chunk.appendAll(Chunk.make("a", "b")), * Chunk.toArray - * ) - * - * console.log(result) - * // [ 1, 2, "a", "b" ] + * ) // => [1, 2, "a", "b"] * ``` * * @see {@link prependAll} for concatenating chunks in the opposite order @@ -998,7 +977,7 @@ export const appendAll: { * * **Example** (Filtering and mapping values) * - * ```ts + * ```ts import.meta.vitest * import { Chunk, Result } from "effect" * * const chunk = Chunk.make("1", "2", "hello", "3", "world") @@ -1006,14 +985,14 @@ export const appendAll: { * const num = parseInt(str) * return isNaN(num) ? Result.failVoid : Result.succeed(num) * }) - * console.log(Chunk.toArray(numbers)) // [1, 2, 3] + * Chunk.toArray(numbers) // => [1, 2, 3] * * // With index parameter * const evenIndexNumbers = Chunk.filterMap(chunk, (str, i) => { * const num = parseInt(str) * return isNaN(num) || i % 2 !== 0 ? Result.failVoid : Result.succeed(num) * }) - * console.log(Chunk.toArray(evenIndexNumbers)) // [1] + * Chunk.toArray(evenIndexNumbers) // => [1] * ``` * * @category filtering @@ -1042,17 +1021,17 @@ export const filterMap: { * * **Example** (Filtering values) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * * const chunk = Chunk.make(1, 2, 3, 4, 5, 6) * const evenNumbers = Chunk.filter(chunk, (n) => n % 2 === 0) - * console.log(Chunk.toArray(evenNumbers)) // [2, 4, 6] + * Chunk.toArray(evenNumbers) // => [2, 4, 6] * * // With refinement * const mixed = Chunk.make("hello", 42, "world", 100) * const numbers = Chunk.filter(mixed, (x): x is number => typeof x === "number") - * console.log(Chunk.toArray(numbers)) // [42, 100] + * Chunk.toArray(numbers) // => [42, 100] * ``` * * @category filtering @@ -1073,23 +1052,19 @@ export const filter: { * * **Example** (Filtering and mapping while values match) * - * ```ts + * ```ts import.meta.vitest * import { Chunk, Result } from "effect" * * const chunk = Chunk.make("1", "2", "hello", "3", "4") - * const result = Chunk.filterMapWhile(chunk, (s) => { - * const num = parseInt(s) - * return isNaN(num) ? Result.failVoid : Result.succeed(num) - * }) - * console.log(Chunk.toArray(result)) // [1, 2] - * // Stops at "hello" and doesn't process "3", "4" + * Chunk.toArray(Chunk.filterMapWhile(chunk, (s) => { + * const n = Number(s) + * return Number.isNaN(n) ? Result.failVoid : Result.succeed(n) + * })) // => [1, 2] * - * // Compare with regular filterMap - * const allNumbers = Chunk.filterMap(chunk, (s) => { - * const num = parseInt(s) - * return isNaN(num) ? Result.failVoid : Result.succeed(num) - * }) - * console.log(Chunk.toArray(allNumbers)) // [1, 2, 3, 4] + * Chunk.toArray(Chunk.filterMap(chunk, (s) => { + * const n = Number(s) + * return Number.isNaN(n) ? Result.failVoid : Result.succeed(n) + * })) // => [1, 2, 3, 4] * ``` * * @category filtering @@ -1116,12 +1091,11 @@ export const filterMapWhile: { * * **Example** (Compacting optional values) * - * ```ts + * ```ts import.meta.vitest * import { Chunk, Option } from "effect" * * const chunk = Chunk.make(Option.some(1), Option.none(), Option.some(3)) - * const result = Chunk.compact(chunk) - * console.log(Chunk.toArray(result)) // [1, 3] + * Chunk.toArray(Chunk.compact(chunk)) // => [1, 3] * ``` * * @category filtering @@ -1142,12 +1116,12 @@ export const compact = (self: Chunk>): Chunk => { * * **Example** (Flat mapping chunks) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * * const chunk = Chunk.make(1, 2, 3) * const duplicated = Chunk.flatMap(chunk, (n) => Chunk.make(n, n)) - * console.log(Chunk.toArray(duplicated)) // [1, 1, 2, 2, 3, 3] + * Chunk.toArray(duplicated) // => [1, 1, 2, 2, 3, 3] * * // Flattening nested arrays * const words = Chunk.make("hello", "world") @@ -1155,11 +1129,11 @@ export const compact = (self: Chunk>): Chunk => { * words, * (word) => Chunk.fromIterable(word.split("")) * ) - * console.log(Chunk.toArray(letters)) // ["h", "e", "l", "l", "o", "w", "o", "r", "l", "d"] + * Chunk.toArray(letters).join("") // => "helloworld" * * // With index parameter * const indexed = Chunk.flatMap(chunk, (n, i) => Chunk.make(n + i)) - * console.log(Chunk.toArray(indexed)) // [1, 3, 5] + * Chunk.toArray(indexed) // => [1, 3, 5] * ``` * * @category sequencing @@ -1195,26 +1169,19 @@ export const flatMap: { * * **Example** (Iterating over chunk values) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * * const chunk = Chunk.make(1, 2, 3, 4) * - * // Log each element - * Chunk.forEach(chunk, (n) => console.log(`Value: ${n}`)) - * // Output: - * // Value: 1 - * // Value: 2 - * // Value: 3 - * // Value: 4 + * const values: Array = [] + * Chunk.forEach(chunk, (n) => values.push(`Value: ${n}`)) + * values // => ["Value: 1", "Value: 2", "Value: 3", "Value: 4"] * * // With index parameter - * Chunk.forEach(chunk, (n, i) => console.log(`Index ${i}: ${n}`)) - * // Output: - * // Index 0: 1 - * // Index 1: 2 - * // Index 2: 3 - * // Index 3: 4 + * const indexed: Array = [] + * Chunk.forEach(chunk, (n, i) => indexed.push(`Index ${i}: ${n}`)) + * indexed // => ["Index 0: 1", "Index 1: 2", "Index 2: 3", "Index 3: 4"] * ``` * * @category combinators @@ -1230,7 +1197,7 @@ export const forEach: { * * **Example** (Flattening nested chunks) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * * const nested = Chunk.make( @@ -1238,8 +1205,7 @@ export const forEach: { * Chunk.make(3, 4, 5), * Chunk.make(6) * ) - * const flattened = Chunk.flatten(nested) - * console.log(Chunk.toArray(flattened)) // [1, 2, 3, 4, 5, 6] + * Chunk.toArray(Chunk.flatten(nested)) // => [1, 2, 3, 4, 5, 6] * * // With empty chunks * const withEmpty = Chunk.make( @@ -1247,7 +1213,7 @@ export const forEach: { * Chunk.empty(), * Chunk.make(3, 4) * ) - * console.log(Chunk.toArray(Chunk.flatten(withEmpty))) // [1, 2, 3, 4] + * Chunk.toArray(Chunk.flatten(withEmpty)) // => [1, 2, 3, 4] * ``` * * @category sequencing @@ -1274,25 +1240,23 @@ export const flatten: >>(self: S) => Chunk.Flatten * * **Example** (Splitting into fixed-size chunks) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * * const chunk = Chunk.make(1, 2, 3, 4, 5, 6, 7, 8, 9) * const chunked = Chunk.chunksOf(chunk, 3) * - * console.log(Chunk.toArray(chunked).map(Chunk.toArray)) - * // [[1, 2, 3], [4, 5, 6], [7, 8, 9]] + * Chunk.toArray(chunked).map(Chunk.toArray) // => [[1, 2, 3], [4, 5, 6], [7, 8, 9]] * * // When length is not evenly divisible * const chunk2 = Chunk.make(1, 2, 3, 4, 5) * const chunked2 = Chunk.chunksOf(chunk2, 2) - * console.log(Chunk.toArray(chunked2).map(Chunk.toArray)) - * // [[1, 2], [3, 4], [5]] + * Chunk.toArray(chunked2).map(Chunk.toArray) // => [[1, 2], [3, 4], [5]] * ``` * * @see {@link split} for splitting into a target number of chunks instead of a fixed chunk size * - * @category elements + * @category splitting * @since 2.0.0 */ export const chunksOf: { @@ -1323,26 +1287,25 @@ export const chunksOf: { * * **Example** (Intersecting chunks) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * * const chunk1 = Chunk.make(1, 2, 3, 4) * const chunk2 = Chunk.make(3, 4, 5, 6) - * const result = Chunk.intersection(chunk1, chunk2) - * console.log(Chunk.toArray(result)) // [3, 4] + * Chunk.toArray(Chunk.intersection(chunk1, chunk2)) // => [3, 4] * * // With strings * const words1 = Chunk.make("hello", "world", "foo") * const words2 = Chunk.make("world", "bar", "foo") - * console.log(Chunk.toArray(Chunk.intersection(words1, words2))) // ["world", "foo"] + * Chunk.toArray(Chunk.intersection(words1, words2)) // => ["world", "foo"] * * // No intersection * const chunk3 = Chunk.make(1, 2) * const chunk4 = Chunk.make(3, 4) - * console.log(Chunk.toArray(Chunk.intersection(chunk3, chunk4))) // [] + * Chunk.toArray(Chunk.intersection(chunk3, chunk4)) // => [] * ``` * - * @category elements + * @category set operations * @since 2.0.0 */ export const intersection: { @@ -1359,14 +1322,14 @@ export const intersection: { * * **Example** (Checking for empty chunks) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * - * console.log(Chunk.isEmpty(Chunk.empty())) // true - * console.log(Chunk.isEmpty(Chunk.make(1, 2, 3))) // false + * Chunk.isEmpty(Chunk.empty()) // => true + * Chunk.isEmpty(Chunk.make(1, 2, 3)) // => false * ``` * - * @category elements + * @category predicates * @since 2.0.0 */ export const isEmpty = (self: Chunk): boolean => self.length === 0 @@ -1376,14 +1339,14 @@ export const isEmpty = (self: Chunk): boolean => self.length === 0 * * **Example** (Checking for non-empty chunks) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * - * console.log(Chunk.isNonEmpty(Chunk.empty())) // false - * console.log(Chunk.isNonEmpty(Chunk.make(1, 2, 3))) // true + * Chunk.isNonEmpty(Chunk.empty()) // => false + * Chunk.isNonEmpty(Chunk.make(1, 2, 3)) // => true * ``` * - * @category elements + * @category guards * @since 2.0.0 */ export const isNonEmpty = (self: Chunk): self is NonEmptyChunk => self.length > 0 @@ -1393,14 +1356,14 @@ export const isNonEmpty = (self: Chunk): self is NonEmptyChunk => self. * * **Example** (Getting the first element) * - * ```ts - * import { Chunk } from "effect" + * ```ts import.meta.vitest + * import { Chunk, Option } from "effect" * - * console.log(Chunk.head(Chunk.empty())) // { _tag: "None" } - * console.log(Chunk.head(Chunk.make(1, 2, 3))) // { _tag: "Some", value: 1 } + * Chunk.head(Chunk.empty()) // => Option.none() + * Chunk.head(Chunk.make(1, 2, 3)) // => Option.some(1) * ``` * - * @category elements + * @category getters * @since 2.0.0 */ export const head: (self: Chunk) => Option = get(0) @@ -1419,17 +1382,17 @@ export const head: (self: Chunk) => Option = get(0) * * **Example** (Getting the first element unsafely) * - * ```ts + * ```ts import.meta.vitest * import { Chunk, Option } from "effect" * * const chunk = Chunk.make(1, 2, 3, 4) - * console.log(Chunk.headUnsafe(chunk)) // 1 + * Chunk.headUnsafe(chunk) // => 1 * * const singleElement = Chunk.make("hello") - * console.log(Chunk.headUnsafe(singleElement)) // "hello" + * Chunk.headUnsafe(singleElement) // => "hello" * * // Use Chunk.head when the chunk may be empty - * console.log(Option.isNone(Chunk.head(Chunk.empty()))) // true + * Option.isNone(Chunk.head(Chunk.empty())) // => true * ``` * * @category unsafe @@ -1442,20 +1405,20 @@ export const headUnsafe = (self: Chunk): A => getUnsafe(self, 0) * * **Example** (Getting the first element of a non-empty chunk) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * * const nonEmptyChunk = Chunk.make(1, 2, 3, 4) - * console.log(Chunk.headNonEmpty(nonEmptyChunk)) // 1 + * Chunk.headNonEmpty(nonEmptyChunk) // => 1 * * const singleElement = Chunk.make("hello") - * console.log(Chunk.headNonEmpty(singleElement)) // "hello" + * Chunk.headNonEmpty(singleElement) // => "hello" * * // Type safety: this function only accepts NonEmptyChunk * // Chunk.headNonEmpty(Chunk.empty()) // TypeScript error * ``` * - * @category elements + * @category getters * @since 2.0.0 */ export const headNonEmpty: (self: NonEmptyChunk) => A = headUnsafe @@ -1465,14 +1428,14 @@ export const headNonEmpty: (self: NonEmptyChunk) => A = headUnsafe * * **Example** (Getting the last element) * - * ```ts - * import { Chunk } from "effect" + * ```ts import.meta.vitest + * import { Chunk, Option } from "effect" * - * console.log(Chunk.last(Chunk.empty())) // { _tag: "None" } - * console.log(Chunk.last(Chunk.make(1, 2, 3))) // { _tag: "Some", value: 3 } + * Chunk.last(Chunk.empty()) // => Option.none() + * Chunk.last(Chunk.make(1, 2, 3)) // => Option.some(3) * ``` * - * @category elements + * @category getters * @since 2.0.0 */ export const last = (self: Chunk): Option => get(self, self.length - 1) @@ -1491,17 +1454,17 @@ export const last = (self: Chunk): Option => get(self, self.length - 1) * * **Example** (Getting the last element unsafely) * - * ```ts + * ```ts import.meta.vitest * import { Chunk, Option } from "effect" * * const chunk = Chunk.make(1, 2, 3, 4) - * console.log(Chunk.lastUnsafe(chunk)) // 4 + * Chunk.lastUnsafe(chunk) // => 4 * * const singleElement = Chunk.make("hello") - * console.log(Chunk.lastUnsafe(singleElement)) // "hello" + * Chunk.lastUnsafe(singleElement) // => "hello" * * // Use Chunk.last when the chunk may be empty - * console.log(Option.isNone(Chunk.last(Chunk.empty()))) // true + * Option.isNone(Chunk.last(Chunk.empty())) // => true * ``` * * @category unsafe @@ -1514,20 +1477,20 @@ export const lastUnsafe = (self: Chunk): A => getUnsafe(self, self.length * * **Example** (Getting the last element of a non-empty chunk) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * * const nonEmptyChunk = Chunk.make(1, 2, 3, 4) - * console.log(Chunk.lastNonEmpty(nonEmptyChunk)) // 4 + * Chunk.lastNonEmpty(nonEmptyChunk) // => 4 * * const singleElement = Chunk.make("hello") - * console.log(Chunk.lastNonEmpty(singleElement)) // "hello" + * Chunk.lastNonEmpty(singleElement) // => "hello" * * // Type safety: this function only accepts NonEmptyChunk * // Chunk.lastNonEmpty(Chunk.empty()) // TypeScript error * ``` * - * @category elements + * @category getters * @since 3.4.0 */ export const lastNonEmpty: (self: NonEmptyChunk) => A = lastUnsafe @@ -1537,7 +1500,7 @@ export const lastNonEmpty: (self: NonEmptyChunk) => A = lastUnsafe * * **Example** (Working with Chunk utility types) * - * ```ts + * ```ts import.meta.vitest * import type { Chunk } from "effect" * * // Extract the element type from a Chunk @@ -1557,7 +1520,7 @@ export declare namespace Chunk { * * **Example** (Inferring element types) * - * ```ts + * ```ts import.meta.vitest * import type { Chunk } from "effect" * * declare const numberChunk: Chunk.Chunk @@ -1567,7 +1530,7 @@ export declare namespace Chunk { * type StringType = Chunk.Chunk.Infer // string * ``` * - * @category types + * @category utility types * @since 2.0.0 */ export type Infer> = S extends Chunk ? A : never @@ -1577,7 +1540,7 @@ export declare namespace Chunk { * * **Example** (Preserving non-emptiness) * - * ```ts + * ```ts import.meta.vitest * import type { Chunk } from "effect" * * declare const regularChunk: Chunk.Chunk @@ -1587,7 +1550,7 @@ export declare namespace Chunk { * type WithString2 = Chunk.Chunk.With // Chunk.NonEmptyChunk * ``` * - * @category types + * @category utility types * @since 2.0.0 */ export type With, A> = S extends NonEmptyChunk ? NonEmptyChunk : Chunk @@ -1597,7 +1560,7 @@ export declare namespace Chunk { * * **Example** (Preserving non-emptiness from either input) * - * ```ts + * ```ts import.meta.vitest * import type { Chunk } from "effect" * * declare const emptyChunk: Chunk.Chunk @@ -1620,7 +1583,7 @@ export declare namespace Chunk { * > // Chunk.NonEmptyChunk * ``` * - * @category types + * @category utility types * @since 2.0.0 */ export type OrNonEmpty, T extends Chunk, A> = S extends NonEmptyChunk ? @@ -1633,7 +1596,7 @@ export declare namespace Chunk { * * **Example** (Requiring non-emptiness from both inputs) * - * ```ts + * ```ts import.meta.vitest * import type { Chunk } from "effect" * * declare const emptyChunk: Chunk.Chunk @@ -1656,7 +1619,7 @@ export declare namespace Chunk { * > // Chunk.NonEmptyChunk * ``` * - * @category types + * @category utility types * @since 2.0.0 */ export type AndNonEmpty, T extends Chunk, A> = S extends NonEmptyChunk ? @@ -1669,7 +1632,7 @@ export declare namespace Chunk { * * **Example** (Flattening nested chunk types) * - * ```ts + * ```ts import.meta.vitest * import type { Chunk } from "effect" * * declare const nestedChunk: Chunk.Chunk> @@ -1679,7 +1642,7 @@ export declare namespace Chunk { * type Flattened2 = Chunk.Chunk.Flatten // Chunk.NonEmptyChunk * ``` * - * @category types + * @category utility types * @since 2.0.0 */ export type Flatten>> = T extends NonEmptyChunk> ? NonEmptyChunk @@ -1693,12 +1656,10 @@ export declare namespace Chunk { * * **Example** (Mapping values) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * - * const result = Chunk.map(Chunk.make(1, 2), (n) => n + 1) - * - * console.log(Chunk.toArray(result)) // [2, 3] + * Chunk.toArray(Chunk.map(Chunk.make(1, 2), (n) => n + 1)) // => [2, 3] * ``` * * @category mapping @@ -1718,7 +1679,7 @@ export const map: { * * **Example** (Mapping with accumulated state) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * * const chunk = Chunk.make(1, 2, 3, 4, 5) @@ -1727,8 +1688,8 @@ export const map: { * state + current // output running sum * ]) * - * console.log(finalState) // 15 (final accumulated sum) - * console.log(Chunk.toArray(mapped)) // [1, 3, 6, 10, 15] (running sums) + * finalState // => 15 + * Chunk.toArray(mapped) // => [1, 3, 6, 10, 15] * * // Building a string with indices * const words = Chunk.make("hello", "world", "effect") @@ -1736,8 +1697,8 @@ export const map: { * index + 1, * `${index}: ${word}` * ]) - * console.log(count) // 3 - * console.log(Chunk.toArray(indexed)) // ["0: hello", "1: world", "2: effect"] + * count // => 3 + * Chunk.toArray(indexed) // => ["0: hello", "1: world", "2: effect"] * ``` * * @category folding @@ -1760,15 +1721,15 @@ export const mapAccum: { * * **Example** (Partitioning with a Result) * - * ```ts + * ```ts import.meta.vitest * import { Chunk, Result } from "effect" * * const [excluded, satisfying] = Chunk.partition(Chunk.make(1, -2, 3), (n, i) => * n > 0 ? Result.succeed(n + i) : Result.fail(`negative:${n}`) * ) * - * console.log(Chunk.toArray(excluded)) // ["negative:-2"] - * console.log(Chunk.toArray(satisfying)) // [1, 5] + * Chunk.toArray(excluded) // => ["negative:-2"] + * Chunk.toArray(satisfying) // => [1, 5] * ``` * * @category filtering @@ -1804,7 +1765,7 @@ export const partition: { * * **Example** (Separating failures and successes) * - * ```ts + * ```ts import.meta.vitest * import { Chunk, Result } from "effect" * * const chunk = Chunk.make( @@ -1816,14 +1777,14 @@ export const partition: { * ) * * const [errors, values] = Chunk.separate(chunk) - * console.log(Chunk.toArray(errors)) // ["error1", "error2"] - * console.log(Chunk.toArray(values)) // [1, 2, 3] + * Chunk.toArray(errors) // => ["error1", "error2"] + * Chunk.toArray(values) // => [1, 2, 3] * * // All successes * const allSuccesses = Chunk.make(Result.succeed(1), Result.succeed(2)) * const [noErrors, allValues] = Chunk.separate(allSuccesses) - * console.log(Chunk.toArray(noErrors)) // [] - * console.log(Chunk.toArray(allValues)) // [1, 2] + * Chunk.toArray(noErrors) // => [] + * Chunk.toArray(allValues) // => [1, 2] * ``` * * @category filtering @@ -1840,14 +1801,13 @@ export const separate = (self: Chunk>): [Chunk, Chunk] * * **Example** (Getting chunk size) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * - * const chunk = Chunk.make(1, 2, 3) - * console.log(Chunk.size(chunk)) // 3 + * Chunk.size(Chunk.make(1, 2, 3)) // => 3 * ``` * - * @category elements + * @category getters * @since 2.0.0 */ export const size = (self: Chunk): number => self.length @@ -1857,21 +1817,18 @@ export const size = (self: Chunk): number => self.length * * **Example** (Sorting chunks) * - * ```ts + * ```ts import.meta.vitest * import { Chunk, Order } from "effect" * * const numbers = Chunk.make(3, 1, 4, 1, 5, 9, 2, 6) - * const sorted = Chunk.sort(numbers, Order.Number) - * console.log(Chunk.toArray(sorted)) // [1, 1, 2, 3, 4, 5, 6, 9] + * Chunk.toArray(Chunk.sort(numbers, Order.Number)) // => [1, 1, 2, 3, 4, 5, 6, 9] * * // Reverse order - * const reverseSorted = Chunk.sort(numbers, Order.flip(Order.Number)) - * console.log(Chunk.toArray(reverseSorted)) // [9, 6, 5, 4, 3, 2, 1, 1] + * Chunk.toArray(Chunk.sort(numbers, Order.flip(Order.Number))) // => [9, 6, 5, 4, 3, 2, 1, 1] * * // String sorting * const words = Chunk.make("banana", "apple", "cherry") - * const sortedWords = Chunk.sort(words, Order.String) - * console.log(Chunk.toArray(sortedWords)) // ["apple", "banana", "cherry"] + * Chunk.toArray(Chunk.sort(words, Order.String)) // => ["apple", "banana", "cherry"] * ``` * * @category sorting @@ -1890,7 +1847,7 @@ export const sort: { * * **Example** (Sorting chunks by a derived value) * - * ```ts + * ```ts import.meta.vitest * import { Chunk, Order } from "effect" * * const people = Chunk.make( @@ -1901,18 +1858,15 @@ export const sort: { * * // Sort by age * const byAge = Chunk.sortWith(people, (person) => person.age, Order.Number) - * console.log(Chunk.toArray(byAge)) - * // [{ name: "Bob", age: 25 }, { name: "Alice", age: 30 }, { name: "Charlie", age: 35 }] + * Chunk.toArray(byAge).map((person) => person.name) // => ["Bob", "Alice", "Charlie"] * * // Sort by name * const byName = Chunk.sortWith(people, (person) => person.name, Order.String) - * console.log(Chunk.toArray(byName)) - * // [{ name: "Alice", age: 30 }, { name: "Bob", age: 25 }, { name: "Charlie", age: 35 }] + * Chunk.toArray(byName).map((person) => person.name) // => ["Alice", "Bob", "Charlie"] * * // Sort by string length * const words = Chunk.make("a", "abc", "ab") - * const byLength = Chunk.sortWith(words, (word) => word.length, Order.Number) - * console.log(Chunk.toArray(byLength)) // ["a", "ab", "abc"] + * Chunk.toArray(Chunk.sortWith(words, (word) => word.length, Order.Number)) // => ["a", "ab", "abc"] * ``` * * @category sorting @@ -1931,23 +1885,23 @@ export const sortWith: { * * **Example** (Splitting at an index) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * * const chunk = Chunk.make(1, 2, 3, 4, 5, 6) * const [before, after] = Chunk.splitAt(chunk, 3) - * console.log(Chunk.toArray(before)) // [1, 2, 3] - * console.log(Chunk.toArray(after)) // [4, 5, 6] + * Chunk.toArray(before) // => [1, 2, 3] + * Chunk.toArray(after) // => [4, 5, 6] * * // Split at index 0 * const [empty, all] = Chunk.splitAt(chunk, 0) - * console.log(Chunk.toArray(empty)) // [] - * console.log(Chunk.toArray(all)) // [1, 2, 3, 4, 5, 6] + * Chunk.toArray(empty) // => [] + * Chunk.toArray(all) // => [1, 2, 3, 4, 5, 6] * * // Split beyond length * const [allElements, empty2] = Chunk.splitAt(chunk, 10) - * console.log(Chunk.toArray(allElements)) // [1, 2, 3, 4, 5, 6] - * console.log(Chunk.toArray(empty2)) // [] + * Chunk.toArray(allElements) // => [1, 2, 3, 4, 5, 6] + * Chunk.toArray(empty2) // => [] * ``` * * @category splitting @@ -1970,18 +1924,18 @@ export const splitAt: { * * **Example** (Splitting non-empty chunks at an index) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * * const nonEmptyChunk = Chunk.make(1, 2, 3, 4, 5, 6) * const [before, after] = Chunk.splitNonEmptyAt(nonEmptyChunk, 3) - * console.log(Chunk.toArray(before)) // [1, 2, 3] - * console.log(Chunk.toArray(after)) // [4, 5, 6] + * Chunk.toArray(before) // => [1, 2, 3] + * Chunk.toArray(after) // => [4, 5, 6] * * // Split at 1 (minimum) * const [first, rest] = Chunk.splitNonEmptyAt(nonEmptyChunk, 1) - * console.log(Chunk.toArray(first)) // [1] - * console.log(Chunk.toArray(rest)) // [2, 3, 4, 5, 6] + * Chunk.toArray(first) // => [1] + * Chunk.toArray(rest) // => [2, 3, 4, 5, 6] * * // The first part is guaranteed to be NonEmptyChunk * // while the second part may be empty @@ -2010,24 +1964,21 @@ export const splitNonEmptyAt: { * * **Example** (Splitting chunks into groups) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * * const chunk = Chunk.make(1, 2, 3, 4, 5, 6, 7, 8, 9) * const chunks = Chunk.split(chunk, 3) - * console.log(Chunk.toArray(chunks).map(Chunk.toArray)) - * // [[1, 2, 3], [4, 5, 6], [7, 8, 9]] + * Chunk.toArray(chunks).map(Chunk.toArray) // => [[1, 2, 3], [4, 5, 6], [7, 8, 9]] * * // Uneven split * const chunk2 = Chunk.make(1, 2, 3, 4, 5, 6, 7, 8) * const chunks2 = Chunk.split(chunk2, 3) - * console.log(Chunk.toArray(chunks2).map(Chunk.toArray)) - * // [[1, 2, 3], [4, 5, 6], [7, 8]] + * Chunk.toArray(chunks2).map(Chunk.toArray) // => [[1, 2, 3], [4, 5, 6], [7, 8]] * * // Split into 1 chunk * const chunks3 = Chunk.split(chunk, 1) - * console.log(Chunk.toArray(chunks3).map(Chunk.toArray)) - * // [[1, 2, 3, 4, 5, 6, 7, 8, 9]] + * Chunk.toArray(chunks3).map(Chunk.toArray) // => [[1, 2, 3, 4, 5, 6, 7, 8, 9]] * ``` * * @category splitting @@ -2044,23 +1995,23 @@ export const split: { * * **Example** (Splitting at a matching element) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * * const chunk = Chunk.make(1, 2, 3, 4, 5, 6) * const [before, fromMatch] = Chunk.splitWhere(chunk, (n) => n > 3) - * console.log(Chunk.toArray(before)) // [1, 2, 3] - * console.log(Chunk.toArray(fromMatch)) // [4, 5, 6] + * Chunk.toArray(before) // => [1, 2, 3] + * Chunk.toArray(fromMatch) // => [4, 5, 6] * * // No match found * const [all, empty] = Chunk.splitWhere(chunk, (n) => n > 10) - * console.log(Chunk.toArray(all)) // [1, 2, 3, 4, 5, 6] - * console.log(Chunk.toArray(empty)) // [] + * Chunk.toArray(all) // => [1, 2, 3, 4, 5, 6] + * Chunk.toArray(empty) // => [] * * // Match on first element * const [emptyBefore, allFromFirst] = Chunk.splitWhere(chunk, (n) => n === 1) - * console.log(Chunk.toArray(emptyBefore)) // [] - * console.log(Chunk.toArray(allFromFirst)) // [1, 2, 3, 4, 5, 6] + * Chunk.toArray(emptyBefore) // => [] + * Chunk.toArray(allFromFirst) // => [1, 2, 3, 4, 5, 6] * ``` * * @category splitting @@ -2086,20 +2037,19 @@ export const splitWhere: { * * **Example** (Getting the tail safely) * - * ```ts - * import { Chunk } from "effect" + * ```ts import.meta.vitest + * import { Chunk, Option } from "effect" * * const chunk = Chunk.make(1, 2, 3, 4) - * console.log(Chunk.tail(chunk)) // Option.some(Chunk.make(2, 3, 4)) + * Chunk.tail(chunk) // => Option.some(Chunk.make(2, 3, 4)) * * const singleElement = Chunk.make(1) - * console.log(Chunk.tail(singleElement)) // Option.some(Chunk.empty()) + * Chunk.tail(singleElement) // => Option.some(Chunk.empty()) * - * const empty = Chunk.empty() - * console.log(Chunk.tail(empty)) // Option.none() + * Chunk.tail(Chunk.empty()) // => Option.none() * ``` * - * @category elements + * @category getters * @since 2.0.0 */ export const tail = (self: Chunk): O.Option> => self.length > 0 ? O.some(drop(self, 1)) : O.none() @@ -2109,22 +2059,20 @@ export const tail = (self: Chunk): O.Option> => self.length > 0 ? * * **Example** (Getting the tail of a non-empty chunk) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * * const nonEmptyChunk = Chunk.make(1, 2, 3, 4) - * const result = Chunk.tailNonEmpty(nonEmptyChunk) - * console.log(Chunk.toArray(result)) // [2, 3, 4] + * Chunk.toArray(Chunk.tailNonEmpty(nonEmptyChunk)) // => [2, 3, 4] * * const singleElement = Chunk.make(1) - * const resultSingle = Chunk.tailNonEmpty(singleElement) - * console.log(Chunk.toArray(resultSingle)) // [] + * Chunk.toArray(Chunk.tailNonEmpty(singleElement)) // => [] * * // Type safety: this function only accepts NonEmptyChunk * // Chunk.tailNonEmpty(Chunk.empty()) // TypeScript error * ``` * - * @category elements + * @category getters * @since 2.0.0 */ export const tailNonEmpty = (self: NonEmptyChunk): Chunk => drop(self, 1) @@ -2134,23 +2082,20 @@ export const tailNonEmpty = (self: NonEmptyChunk): Chunk => drop(self, * * **Example** (Taking elements from the end) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * * const chunk = Chunk.make(1, 2, 3, 4, 5, 6) - * const lastThree = Chunk.takeRight(chunk, 3) - * console.log(Chunk.toArray(lastThree)) // [4, 5, 6] + * Chunk.toArray(Chunk.takeRight(chunk, 3)) // => [4, 5, 6] * * // Take more than available - * const all = Chunk.takeRight(chunk, 10) - * console.log(Chunk.toArray(all)) // [1, 2, 3, 4, 5, 6] + * Chunk.toArray(Chunk.takeRight(chunk, 10)) // => [1, 2, 3, 4, 5, 6] * * // Take zero - * const none = Chunk.takeRight(chunk, 0) - * console.log(Chunk.toArray(none)) // [] + * Chunk.toArray(Chunk.takeRight(chunk, 0)) // => [] * ``` * - * @category elements + * @category filtering * @since 2.0.0 */ export const takeRight: { @@ -2163,24 +2108,21 @@ export const takeRight: { * * **Example** (Taking elements while a predicate matches) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * * const chunk = Chunk.make(1, 2, 3, 4, 3, 2, 1) - * const result = Chunk.takeWhile(chunk, (n) => n < 4) - * console.log(Chunk.toArray(result)) // [1, 2, 3] + * Chunk.toArray(Chunk.takeWhile(chunk, (n) => n < 4)) // => [1, 2, 3] * * // Empty if first element doesn't match - * const none = Chunk.takeWhile(chunk, (n) => n > 5) - * console.log(Chunk.toArray(none)) // [] + * Chunk.toArray(Chunk.takeWhile(chunk, (n) => n > 5)) // => [] * * // Takes all if all match * const small = Chunk.make(1, 2, 3) - * const all = Chunk.takeWhile(small, (n) => n < 10) - * console.log(Chunk.toArray(all)) // [1, 2, 3] + * Chunk.toArray(Chunk.takeWhile(small, (n) => n < 10)) // => [1, 2, 3] * ``` * - * @category elements + * @category filtering * @since 2.0.0 */ export const takeWhile: { @@ -2205,22 +2147,20 @@ export const takeWhile: { * * **Example** (Unioning chunks) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * * const chunk1 = Chunk.make(1, 2, 3) * const chunk2 = Chunk.make(3, 4, 5) - * const result = Chunk.union(chunk1, chunk2) - * console.log(Chunk.toArray(result)) // [1, 2, 3, 4, 5] + * Chunk.toArray(Chunk.union(chunk1, chunk2)) // => [1, 2, 3, 4, 5] * * // Handles duplicates within the same chunk * const withDupes1 = Chunk.make(1, 1, 2) * const withDupes2 = Chunk.make(2, 3, 3) - * const unified = Chunk.union(withDupes1, withDupes2) - * console.log(Chunk.toArray(unified)) // [1, 2, 3] + * Chunk.toArray(Chunk.union(withDupes1, withDupes2)) // => [1, 2, 3] * ``` * - * @category elements + * @category set operations * @since 2.0.0 */ export const union: { @@ -2237,25 +2177,22 @@ export const union: { * * **Example** (Removing duplicate values) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * * const chunk = Chunk.make(1, 2, 2, 3, 1, 4, 3) - * const result = Chunk.dedupe(chunk) - * console.log(Chunk.toArray(result)) // [1, 2, 3, 4] + * Chunk.toArray(Chunk.dedupe(chunk)) // => [1, 2, 3, 4] * * // Empty chunk * const empty = Chunk.empty() - * const emptyDeduped = Chunk.dedupe(empty) - * console.log(Chunk.toArray(emptyDeduped)) // [] + * Chunk.toArray(Chunk.dedupe(empty)) // => [] * * // No duplicates * const unique = Chunk.make(1, 2, 3) - * const uniqueDeduped = Chunk.dedupe(unique) - * console.log(Chunk.toArray(uniqueDeduped)) // [1, 2, 3] + * Chunk.toArray(Chunk.dedupe(unique)) // => [1, 2, 3] * ``` * - * @category elements + * @category deduplication * @since 2.0.0 */ export const dedupe = (self: Chunk): Chunk => fromArrayUnsafe(RA.dedupe(toReadonlyArray(self))) @@ -2265,17 +2202,15 @@ export const dedupe = (self: Chunk): Chunk => fromArrayUnsafe(RA.dedupe * * **Example** (Removing adjacent duplicates) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * * const chunk = Chunk.make(1, 1, 2, 2, 2, 3, 1, 1) - * const result = Chunk.dedupeAdjacent(chunk) - * console.log(Chunk.toArray(result)) // [1, 2, 3, 1] + * Chunk.toArray(Chunk.dedupeAdjacent(chunk)) // => [1, 2, 3, 1] * * // Only removes adjacent duplicates, not all duplicates * const mixed = Chunk.make("a", "a", "b", "a", "a") - * const mixedResult = Chunk.dedupeAdjacent(mixed) - * console.log(Chunk.toArray(mixedResult)) // ["a", "b", "a"] + * Chunk.toArray(Chunk.dedupeAdjacent(mixed)) // => ["a", "b", "a"] * ``` * * @category filtering @@ -2292,7 +2227,7 @@ export const dedupeAdjacent = (self: Chunk): Chunk => fromArrayUnsafe(R * * **Example** (Unzipping pairs) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * * const pairs = Chunk.make( @@ -2301,17 +2236,17 @@ export const dedupeAdjacent = (self: Chunk): Chunk => fromArrayUnsafe(R * [3, "c"] as const * ) * const [numbers, letters] = Chunk.unzip(pairs) - * console.log(Chunk.toArray(numbers)) // [1, 2, 3] - * console.log(Chunk.toArray(letters)) // ["a", "b", "c"] + * Chunk.toArray(numbers) // => [1, 2, 3] + * Chunk.toArray(letters) // => ["a", "b", "c"] * * // Empty chunk * const empty = Chunk.empty<[number, string]>() * const [emptyNums, emptyStrs] = Chunk.unzip(empty) - * console.log(Chunk.toArray(emptyNums)) // [] - * console.log(Chunk.toArray(emptyStrs)) // [] + * Chunk.toArray(emptyNums) // => [] + * Chunk.toArray(emptyStrs) // => [] * ``` * - * @category elements + * @category splitting * @since 2.0.0 */ export const unzip = (self: Chunk): [Chunk, Chunk] => { @@ -2324,19 +2259,17 @@ export const unzip = (self: Chunk): [Chunk, Chunk] * * **Example** (Zipping chunks with a function) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * * const numbers = Chunk.make(1, 2, 3) * const letters = Chunk.make("a", "b", "c") - * const result = Chunk.zipWith(numbers, letters, (n, l) => `${n}-${l}`) - * console.log(Chunk.toArray(result)) // ["1-a", "2-b", "3-c"] + * Chunk.toArray(Chunk.zipWith(numbers, letters, (n, l) => `${n}-${l}`)) // => ["1-a", "2-b", "3-c"] * * // Different lengths - takes minimum * const short = Chunk.make(1, 2) * const long = Chunk.make("a", "b", "c", "d") - * const mixed = Chunk.zipWith(short, long, (n, l) => [n, l]) - * console.log(Chunk.toArray(mixed)) // [[1, "a"], [2, "b"]] + * Chunk.toArray(Chunk.zipWith(short, long, (n, l) => [n, l])) // => [[1, "a"], [2, "b"]] * ``` * * @category zipping @@ -2356,19 +2289,17 @@ export const zipWith: { * * **Example** (Zipping chunks) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * * const numbers = Chunk.make(1, 2, 3) * const letters = Chunk.make("a", "b", "c") - * const result = Chunk.zip(numbers, letters) - * console.log(Chunk.toArray(result)) // [[1, "a"], [2, "b"], [3, "c"]] + * Chunk.toArray(Chunk.zip(numbers, letters)) // => [[1, "a"], [2, "b"], [3, "c"]] * * // Different lengths - takes minimum length * const short = Chunk.make(1, 2) * const long = Chunk.make("a", "b", "c", "d") - * const zipped = Chunk.zip(short, long) - * console.log(Chunk.toArray(zipped)) // [[1, "a"], [2, "b"]] + * Chunk.toArray(Chunk.zip(short, long)) // => [[1, "a"], [2, "b"]] * ``` * * @category zipping @@ -2387,23 +2318,20 @@ export const zip: { * * **Example** (Removing an element) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * * const chunk = Chunk.make("a", "b", "c", "d") - * const result = Chunk.remove(chunk, 1) - * console.log(Chunk.toArray(result)) // ["a", "c", "d"] + * Chunk.toArray(Chunk.remove(chunk, 1)) // => ["a", "c", "d"] * * // Remove first element - * const removeFirst = Chunk.remove(chunk, 0) - * console.log(Chunk.toArray(removeFirst)) // ["b", "c", "d"] + * Chunk.toArray(Chunk.remove(chunk, 0)) // => ["b", "c", "d"] * * // Index out of bounds returns same chunk - * const outOfBounds = Chunk.remove(chunk, 10) - * console.log(Chunk.toArray(outOfBounds)) // ["a", "b", "c", "d"] + * Chunk.toArray(Chunk.remove(chunk, 10)) // => ["a", "b", "c", "d"] * ``` * - * @category elements + * @category transforming * @since 2.0.0 */ export const remove: { @@ -2420,23 +2348,20 @@ export const remove: { * * **Example** (Modifying an element) * - * ```ts - * import { Chunk } from "effect" + * ```ts import.meta.vitest + * import { Chunk, Option } from "effect" * * const chunk = Chunk.make(1, 2, 3, 4) - * const result = Chunk.modify(chunk, 1, (n) => n * 10) - * console.log(result) // Option.some(Chunk.make(1, 20, 3, 4)) + * Chunk.modify(chunk, 1, (n) => n * 10) // => Option.some(Chunk.make(1, 20, 3, 4)) * * // Index out of bounds returns None - * const outOfBounds = chunk.pipe(Chunk.modify(10, (n) => n * 10)) - * console.log(outOfBounds) // Option.none() + * chunk.pipe(Chunk.modify(10, (n) => n * 10)) // => Option.none() * * // Negative index returns None - * const negative = chunk.pipe(Chunk.modify(-1, (n) => n * 10)) - * console.log(negative) // Option.none() + * chunk.pipe(Chunk.modify(-1, (n) => n * 10)) // => Option.none() * ``` * - * @category elements + * @category transforming * @since 2.0.0 */ export const modify: { @@ -2454,23 +2379,20 @@ export const modify: { * * **Example** (Replacing an element) * - * ```ts - * import { Chunk } from "effect" + * ```ts import.meta.vitest + * import { Chunk, Option } from "effect" * * const chunk = Chunk.make("a", "b", "c", "d") - * const result = Chunk.replace(chunk, 1, "X") - * console.log(result) // Option.some(Chunk.make("a", "X", "c", "d")) + * Chunk.replace(chunk, 1, "X") // => Option.some(Chunk.make("a", "X", "c", "d")) * * // Index out of bounds returns None - * const outOfBounds = chunk.pipe(Chunk.replace(10, "Y")) - * console.log(outOfBounds) // Option.none() + * chunk.pipe(Chunk.replace(10, "Y")) // => Option.none() * * // Negative index returns None - * const negative = chunk.pipe(Chunk.replace(-1, "Z")) - * console.log(negative) // Option.none() + * chunk.pipe(Chunk.replace(-1, "Z")) // => Option.none() * ``` * - * @category elements + * @category transforming * @since 2.0.0 */ export const replace: { @@ -2487,11 +2409,10 @@ export const replace: { * * **Example** (Generating chunks from indices) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * - * const chunk = Chunk.makeBy(5, (i) => i * 2) - * console.log(Chunk.toArray(chunk)) // [0, 2, 4, 6, 8] + * Chunk.toArray(Chunk.makeBy(5, (i) => i * 2)) // => [0, 2, 4, 6, 8] * ``` * * @category constructors @@ -2513,11 +2434,10 @@ export const makeBy: { * * **Example** (Creating a range) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * - * const chunk = Chunk.range(1, 5) - * console.log(Chunk.toArray(chunk)) // [1, 2, 3, 4, 5] + * Chunk.toArray(Chunk.range(1, 5)) // => [1, 2, 3, 4, 5] * ``` * * @category constructors @@ -2535,24 +2455,23 @@ export const range = (start: number, end: number): NonEmptyChunk => * * **Example** (Checking membership) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * * const chunk = Chunk.make(1, 2, 3, 4, 5) - * console.log(Chunk.contains(chunk, 3)) // true - * console.log(Chunk.contains(chunk, 6)) // false + * Chunk.contains(chunk, 3) // => true + * Chunk.contains(chunk, 6) // => false * * // Works with strings * const words = Chunk.make("apple", "banana", "cherry") - * console.log(Chunk.contains(words, "banana")) // true - * console.log(Chunk.contains(words, "grape")) // false + * Chunk.contains(words, "banana") // => true + * Chunk.contains(words, "grape") // => false * * // Empty chunk - * const empty = Chunk.empty() - * console.log(Chunk.contains(empty, 1)) // false + * Chunk.contains(Chunk.empty(), 1) // => false * ``` * - * @category elements + * @category predicates * @since 2.0.0 */ export const contains: { @@ -2565,7 +2484,7 @@ export const contains: { * * **Example** (Checking membership with custom equivalence) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * * const chunk = Chunk.make({ id: 1, name: "Alice" }, { id: 2, name: "Bob" }) @@ -2574,19 +2493,19 @@ export const contains: { * const containsById = Chunk.containsWith<{ id: number; name: string }>((a, b) => * a.id === b.id * ) - * console.log(containsById(chunk, { id: 1, name: "Different" })) // true - * console.log(containsById(chunk, { id: 3, name: "Charlie" })) // false + * containsById(chunk, { id: 1, name: "Different" }) // => true + * containsById(chunk, { id: 3, name: "Charlie" }) // => false * * // Case-insensitive string comparison * const words = Chunk.make("Apple", "Banana", "Cherry") * const containsCaseInsensitive = Chunk.containsWith((a, b) => * a.toLowerCase() === b.toLowerCase() * ) - * console.log(containsCaseInsensitive(words, "apple")) // true - * console.log(containsCaseInsensitive(words, "grape")) // false + * containsCaseInsensitive(words, "apple") // => true + * containsCaseInsensitive(words, "grape") // => false * ``` * - * @category elements + * @category predicates * @since 2.0.0 */ export const containsWith: ( @@ -2602,17 +2521,14 @@ export const containsWith: ( * * **Example** (Finding the first matching element) * - * ```ts + * ```ts import.meta.vitest * import { Chunk, Option } from "effect" * * const chunk = Chunk.make(1, 2, 3, 4, 5) - * const result = Chunk.findFirst(chunk, (n) => n > 3) - * console.log(Option.isSome(result)) // true - * console.log(Option.getOrElse(result, () => 0)) // 4 + * Chunk.findFirst(chunk, (n) => n > 3) // => Option.some(4) * * // No match found - * const notFound = Chunk.findFirst(chunk, (n) => n > 10) - * console.log(Option.isNone(notFound)) // true + * Chunk.findFirst(chunk, (n) => n > 10) // => Option.none() * * // With type refinement * const mixed = Chunk.make(1, "hello", 2, "world", 3) @@ -2620,10 +2536,10 @@ export const containsWith: ( * mixed, * (x): x is string => typeof x === "string" * ) - * console.log(Option.getOrElse(firstString, () => "")) // "hello" + * firstString // => Option.some("hello") * ``` * - * @category elements + * @category searching * @since 2.0.0 */ export const findFirst: { @@ -2638,23 +2554,20 @@ export const findFirst: { * * **Example** (Finding the first matching index) * - * ```ts - * import { Chunk } from "effect" + * ```ts import.meta.vitest + * import { Chunk, Option } from "effect" * * const chunk = Chunk.make(1, 2, 3, 4, 5) - * const result = Chunk.findFirstIndex(chunk, (n) => n > 3) - * console.log(result) // Option.some(3) + * Chunk.findFirstIndex(chunk, (n) => n > 3) // => Option.some(3) * * // No match found - * const notFound = Chunk.findFirstIndex(chunk, (n) => n > 10) - * console.log(notFound) // Option.none() + * Chunk.findFirstIndex(chunk, (n) => n > 10) // => Option.none() * * // Find first even number - * const firstEven = Chunk.findFirstIndex(chunk, (n) => n % 2 === 0) - * console.log(firstEven) // Option.some(1) + * Chunk.findFirstIndex(chunk, (n) => n % 2 === 0) // => Option.some(1) * ``` * - * @category elements + * @category searching * @since 2.0.0 */ export const findFirstIndex: { @@ -2670,24 +2583,20 @@ export const findFirstIndex: { * * **Example** (Finding the last matching element) * - * ```ts + * ```ts import.meta.vitest * import { Chunk, Option } from "effect" * * const chunk = Chunk.make(1, 2, 3, 4, 5) - * const result = Chunk.findLast(chunk, (n) => n < 4) - * console.log(Option.isSome(result)) // true - * console.log(Option.getOrElse(result, () => 0)) // 3 + * Chunk.findLast(chunk, (n) => n < 4) // => Option.some(3) * * // No match found - * const notFound = Chunk.findLast(chunk, (n) => n > 10) - * console.log(Option.isNone(notFound)) // true + * Chunk.findLast(chunk, (n) => n > 10) // => Option.none() * * // Find last even number - * const lastEven = Chunk.findLast(chunk, (n) => n % 2 === 0) - * console.log(Option.getOrElse(lastEven, () => 0)) // 4 + * Chunk.findLast(chunk, (n) => n % 2 === 0) // => Option.some(4) * ``` * - * @category elements + * @category searching * @since 2.0.0 */ export const findLast: { @@ -2702,23 +2611,20 @@ export const findLast: { * * **Example** (Finding the last matching index) * - * ```ts - * import { Chunk } from "effect" + * ```ts import.meta.vitest + * import { Chunk, Option } from "effect" * * const chunk = Chunk.make(1, 2, 3, 4, 5) - * const result = Chunk.findLastIndex(chunk, (n) => n < 4) - * console.log(result) // Option.some(2) + * Chunk.findLastIndex(chunk, (n) => n < 4) // => Option.some(2) * * // No match found - * const notFound = Chunk.findLastIndex(chunk, (n) => n > 10) - * console.log(notFound) // Option.none() + * Chunk.findLastIndex(chunk, (n) => n > 10) // => Option.none() * * // Find last even number index - * const lastEven = Chunk.findLastIndex(chunk, (n) => n % 2 === 0) - * console.log(lastEven) // Option.some(3) + * Chunk.findLastIndex(chunk, (n) => n % 2 === 0) // => Option.some(3) * ``` * - * @category elements + * @category searching * @since 2.0.0 */ export const findLastIndex: { @@ -2734,26 +2640,24 @@ export const findLastIndex: { * * **Example** (Checking every element) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * * const allPositive = Chunk.make(1, 2, 3, 4, 5) - * console.log(Chunk.every(allPositive, (n) => n > 0)) // true - * console.log(Chunk.every(allPositive, (n) => n > 3)) // false + * Chunk.every(allPositive, (n) => n > 0) // => true + * Chunk.every(allPositive, (n) => n > 3) // => false * * // Empty chunk returns true - * const empty = Chunk.empty() - * console.log(Chunk.every(empty, (n) => n > 0)) // true + * Chunk.every(Chunk.empty(), (n) => n > 0) // => true * * // Type refinement * const mixed = Chunk.make(1, 2, 3) * if (Chunk.every(mixed, (x): x is number => typeof x === "number")) { * // mixed is now typed as Chunk - * console.log("All elements are numbers") * } * ``` * - * @category elements + * @category guards * @since 2.0.0 */ export const every: { @@ -2772,23 +2676,22 @@ export const every: { * * **Example** (Checking for some matching element) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * * const chunk = Chunk.make(1, 2, 3, 4, 5) - * console.log(Chunk.some(chunk, (n) => n > 4)) // true - * console.log(Chunk.some(chunk, (n) => n > 10)) // false + * Chunk.some(chunk, (n) => n > 4) // => true + * Chunk.some(chunk, (n) => n > 10) // => false * * // Empty chunk returns false - * const empty = Chunk.empty() - * console.log(Chunk.some(empty, (n) => n > 0)) // false + * Chunk.some(Chunk.empty(), (n) => n > 0) // => false * * // Check for specific value * const words = Chunk.make("apple", "banana", "cherry") - * console.log(Chunk.some(words, (word) => word.includes("ban"))) // true + * Chunk.some(words, (word) => word.includes("ban")) // => true * ``` * - * @category elements + * @category guards * @since 2.0.0 */ export const some: { @@ -2804,24 +2707,20 @@ export const some: { * * **Example** (Joining chunks into a string) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * * const chunk = Chunk.make("apple", "banana", "cherry") - * const result = Chunk.join(chunk, ", ") - * console.log(result) // "apple, banana, cherry" + * Chunk.join(chunk, ", ") // => "apple, banana, cherry" * * // With different separator - * const withPipe = Chunk.join(chunk, " | ") - * console.log(withPipe) // "apple | banana | cherry" + * Chunk.join(chunk, " | ") // => "apple | banana | cherry" * * // Empty chunk - * const empty = Chunk.empty() - * console.log(Chunk.join(empty, ", ")) // "" + * Chunk.join(Chunk.empty(), ", ") // => "" * * // Single element - * const single = Chunk.make("hello") - * console.log(Chunk.join(single, ", ")) // "hello" + * Chunk.join(Chunk.make("hello"), ", ") // => "hello" * ``` * * @category folding @@ -2837,21 +2736,18 @@ export const join: { * * **Example** (Reducing from the left) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * * const chunk = Chunk.make(1, 2, 3, 4, 5) - * const sum = Chunk.reduce(chunk, 0, (acc, n) => acc + n) - * console.log(sum) // 15 + * Chunk.reduce(chunk, 0, (acc, n) => acc + n) // => 15 * * // String concatenation with index * const words = Chunk.make("a", "b", "c") - * const result = Chunk.reduce(words, "", (acc, word, i) => acc + `${i}:${word} `) - * console.log(result) // "0:a 1:b 2:c " + * Chunk.reduce(words, "", (acc, word, i) => acc + `${i}:${word} `).trimEnd() // => "0:a 1:b 2:c" * * // Find maximum - * const max = Chunk.reduce(chunk, -Infinity, (acc, n) => Math.max(acc, n)) - * console.log(max) // 5 + * Chunk.reduce(chunk, -Infinity, (acc, n) => Math.max(acc, n)) // => 5 * ``` * * @category folding @@ -2867,25 +2763,22 @@ export const reduce: { * * **Example** (Reducing from the right) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * * const chunk = Chunk.make(1, 2, 3, 4) - * const result = Chunk.reduceRight(chunk, 0, (acc, n) => acc + n) - * console.log(result) // 10 + * Chunk.reduceRight(chunk, 0, (acc, n) => acc + n) // => 10 * * // String building (right to left) * const words = Chunk.make("a", "b", "c") - * const reversed = Chunk.reduceRight( + * Chunk.reduceRight( * words, * "", * (acc, word, i) => acc + `${i}:${word} ` - * ) - * console.log(reversed) // "2:c 1:b 0:a " + * ).trim() // => "2:c 1:b 0:a" * * // Subtract from right to left - * const subtraction = Chunk.reduceRight(chunk, 0, (acc, n) => n - acc) - * console.log(subtraction) // -2 (4 - (3 - (2 - (1 - 0)))) + * Chunk.reduceRight(chunk, 0, (acc, n) => n - acc) // => -2 * ``` * * @category folding @@ -2902,7 +2795,7 @@ export const reduceRight: { * * **Example** (Computing difference with custom equivalence) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * * const chunk1 = Chunk.make({ id: 1, name: "Alice" }, { id: 2, name: "Bob" }) @@ -2912,8 +2805,7 @@ export const reduceRight: { * const byId = Chunk.differenceWith<{ id: number; name: string }>((a, b) => * a.id === b.id * ) - * const result = byId(chunk1, chunk2) - * console.log(Chunk.toArray(result)) // [{ id: 2, name: "Bob" }] + * Chunk.toArray(byId(chunk1, chunk2)) // => [{ id: 2, name: "Bob" }] * * // String comparison case-insensitive * const words1 = Chunk.make("Apple", "Banana", "Cherry") @@ -2921,8 +2813,7 @@ export const reduceRight: { * const caseInsensitive = Chunk.differenceWith((a, b) => * a.toLowerCase() === b.toLowerCase() * ) - * const wordDiff = caseInsensitive(words1, words2) - * console.log(Chunk.toArray(wordDiff)) // ["Banana", "Cherry"] + * Chunk.toArray(caseInsensitive(words1, words2)) // => ["Banana", "Cherry"] * ``` * * @category filtering @@ -2944,24 +2835,20 @@ export const differenceWith = (isEquivalent: (self: A, that: A) => boolean): * * **Example** (Computing chunk difference) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * * const chunk1 = Chunk.make(1, 2, 3, 4, 5) * const chunk2 = Chunk.make(3, 4, 6, 7) - * const result = Chunk.difference(chunk1, chunk2) - * console.log(Chunk.toArray(result)) // [1, 2, 5] + * Chunk.toArray(Chunk.difference(chunk1, chunk2)) // => [1, 2, 5] * * // String difference * const words1 = Chunk.make("apple", "banana", "cherry") * const words2 = Chunk.make("banana", "grape") - * const wordDiff = Chunk.difference(words1, words2) - * console.log(Chunk.toArray(wordDiff)) // ["apple", "cherry"] + * Chunk.toArray(Chunk.difference(words1, words2)) // => ["apple", "cherry"] * * // Empty second chunk returns original - * const empty = Chunk.empty() - * const unchanged = Chunk.difference(chunk1, empty) - * console.log(Chunk.toArray(unchanged)) // [1, 2, 3, 4, 5] + * Chunk.toArray(Chunk.difference(chunk1, Chunk.empty())) // => [1, 2, 3, 4, 5] * ``` * * @category filtering diff --git a/repos/effect/packages/effect/src/Clock.ts b/repos/effect/packages/effect/src/Clock.ts index 03a8b210e1..5434197be2 100644 --- a/repos/effect/packages/effect/src/Clock.ts +++ b/repos/effect/packages/effect/src/Clock.ts @@ -1,8 +1,9 @@ /** * Service and helpers for reading time and sleeping inside Effect programs. - * The active `Clock` provides current time in milliseconds or nanoseconds and a - * `sleep` operation for delaying work. Because time is accessed through a - * service, tests can replace the clock with a controlled implementation. + * The active `Clock` provides Unix time, monotonic time for measuring elapsed + * durations, and a `sleep` operation for delaying work. Because time is + * accessed through a service, tests can replace the clock with a controlled + * implementation. * * @since 2.0.0 */ @@ -22,56 +23,121 @@ import * as effect from "./internal/effect.ts" * * **Example** (Reading current time) * - * ```ts + * ```ts import.meta.vitest * import { Clock, Effect } from "effect" * + * const testClock: Clock.Clock = { + * currentTimeMillisUnsafe: () => 1_000, + * currentTimeMillis: Effect.succeed(1_000), + * monotonicTimeNanosUnsafe: () => 1_000_000_000n, + * monotonicTimeNanos: Effect.succeed(1_000_000_000n), + * currentTimeNanosUnsafe: () => 1_000_000_000n, + * currentTimeNanos: Effect.succeed(1_000_000_000n), + * sleep: () => Effect.void + * } + * * const clockOperations = Effect.gen(function*() { * const currentTime = yield* Clock.currentTimeMillis * const currentTimeNanos = yield* Clock.currentTimeNanos - * - * console.log(`Current time (ms): ${currentTime}`) - * console.log(`Current time (ns): ${currentTimeNanos}`) + * return [currentTime, currentTimeNanos] as const * }) + * + * await Effect.runPromise(Effect.provideService(clockOperations, Clock.Clock, testClock)) // => [1_000, 1_000_000_000n] * ``` * - * @category models + * @category services * @since 2.0.0 */ export interface Clock { /** - * Returns the current time in milliseconds unsafely. + * Returns the current Unix time in milliseconds unsafely. * * **When to use** * - * Use to read millisecond time synchronously when you already have a `Clock` - * service and can accept non-effectful access. + * Use to read a wall-clock timestamp synchronously when you already have a + * `Clock` service and can accept non-effectful access. + * + * **Gotchas** + * + * The value can move backward or forward when the system wall clock is + * corrected, so it is not suitable for measuring elapsed time. */ currentTimeMillisUnsafe(): number /** - * Returns the current time in milliseconds. + * Returns the current Unix time in milliseconds. * * **When to use** * - * Use to read millisecond time through this `Clock` service in `Effect`. + * Use to read a wall-clock timestamp through this `Clock` service in + * `Effect`. + * + * **Gotchas** + * + * The value can move backward or forward when the system wall clock is + * corrected, so it is not suitable for measuring elapsed time. */ readonly currentTimeMillis: Effect /** - * Returns the current time in nanoseconds unsafely. + * Returns the current Unix time in nanoseconds unsafely. * * **When to use** * - * Use to read nanosecond time synchronously when you already have a `Clock` - * service and can accept non-effectful access. + * Use to read a wall-clock timestamp synchronously when you already have a + * `Clock` service and can accept non-effectful access. + * + * **Gotchas** + * + * The value can move backward or forward when the system wall clock is + * corrected, so it is not suitable for measuring elapsed time. */ currentTimeNanosUnsafe(): bigint /** - * Returns the current time in nanoseconds. + * Returns the current Unix time in nanoseconds. * * **When to use** * - * Use to read nanosecond time through this `Clock` service in `Effect`. + * Use to read a wall-clock timestamp through this `Clock` service in + * `Effect`. + * + * **Gotchas** + * + * The value can move backward or forward when the system wall clock is + * corrected, so it is not suitable for measuring elapsed time. */ readonly currentTimeNanos: Effect + /** + * Returns the current monotonic time in nanoseconds unsafely. + * + * **When to use** + * + * Use to measure elapsed time synchronously when you already have a `Clock` + * service and can accept non-effectful access. + * + * **Gotchas** + * + * The value has an arbitrary origin and is unsuitable for serialization. Use + * it only to subtract readings produced by the same clock. Whether it + * advances while the host is suspended depends on the runtime. + * + * @since 4.0.0 + */ + monotonicTimeNanosUnsafe(): bigint + /** + * Returns the current monotonic time in nanoseconds. + * + * **When to use** + * + * Use to measure elapsed time through this `Clock` service in `Effect`. + * + * **Gotchas** + * + * The value has an arbitrary origin and is unsuitable for serialization. Use + * it only to subtract readings produced by the same clock. Whether it + * advances while the host is suspended depends on the runtime. + * + * @since 4.0.0 + */ + readonly monotonicTimeNanos: Effect /** * Asynchronously sleeps for the specified duration. * @@ -92,20 +158,32 @@ export interface Clock { * * **Example** (Accessing the Clock service) * - * ```ts + * ```ts import.meta.vitest * import { Clock, Effect } from "effect" * + * const testClock: Clock.Clock = { + * currentTimeMillisUnsafe: () => 1_000, + * currentTimeMillis: Effect.succeed(1_000), + * monotonicTimeNanosUnsafe: () => 1_000_000_000n, + * monotonicTimeNanos: Effect.succeed(1_000_000_000n), + * currentTimeNanosUnsafe: () => 1_000_000_000n, + * currentTimeNanos: Effect.succeed(1_000_000_000n), + * sleep: () => Effect.void + * } + * * const program = Effect.gen(function*() { * const clock = yield* Clock.Clock * return clock.currentTimeMillisUnsafe() * }) + * + * await Effect.runPromise(Effect.provideService(program, Clock.Clock, testClock)) // => 1_000 * ``` * * @see {@link clockWith} for using the current Clock service inside an effect * @see {@link currentTimeMillis} for reading the current time in milliseconds * @see {@link currentTimeNanos} for reading the current time in nanoseconds * - * @category references + * @category services * @since 2.0.0 */ export const Clock: Context.Reference = effect.ClockRef @@ -120,75 +198,129 @@ export const Clock: Context.Reference = effect.ClockRef * * **Example** (Accessing the current Clock service) * - * ```ts + * ```ts import.meta.vitest * import { Clock, Effect } from "effect" * - * const program = Clock.clockWith((clock) => - * Effect.sync(() => { - * const currentTime = clock.currentTimeMillisUnsafe() - * console.log(`Current time: ${currentTime}`) - * return currentTime - * }) - * ) + * const testClock: Clock.Clock = { + * currentTimeMillisUnsafe: () => 1_000, + * currentTimeMillis: Effect.succeed(1_000), + * monotonicTimeNanosUnsafe: () => 1_000_000_000n, + * monotonicTimeNanos: Effect.succeed(1_000_000_000n), + * currentTimeNanosUnsafe: () => 1_000_000_000n, + * currentTimeNanos: Effect.succeed(1_000_000_000n), + * sleep: () => Effect.void + * } + * + * const program = Clock.clockWith((clock) => Effect.sync(() => clock.currentTimeMillisUnsafe())) + * + * await Effect.runPromise(Effect.provideService(program, Clock.Clock, testClock)) // => 1_000 * ``` * * @see {@link Clock} for the service reference * @see {@link currentTimeMillis} for convenience accessor that returns milliseconds * @see {@link currentTimeNanos} for convenience accessor that returns nanoseconds - * @category constructors + * @category accessors * @since 2.0.0 */ export const clockWith: (f: (clock: Clock) => Effect) => Effect = effect.clockWith /** - * Returns an Effect that succeeds with the current time in milliseconds. + * Returns an Effect that succeeds with the current Unix time in milliseconds. * * **When to use** * - * Use to read wall-clock time from the active Clock service with millisecond - * precision. + * Use to create wall-clock timestamps from the active `Clock` service with + * millisecond precision. + * + * **Gotchas** + * + * The value can move backward or forward when the system wall clock is + * corrected, so it is not suitable for measuring elapsed time. * * **Example** (Reading milliseconds) * - * ```ts + * ```ts import.meta.vitest * import { Clock, Effect } from "effect" * - * const program = Effect.gen(function*() { - * const currentTime = yield* Clock.currentTimeMillis - * console.log(`Current time: ${currentTime}ms`) - * return currentTime - * }) + * const testClock: Clock.Clock = { + * currentTimeMillisUnsafe: () => 1_000, + * currentTimeMillis: Effect.succeed(1_000), + * monotonicTimeNanosUnsafe: () => 1_000_000_000n, + * monotonicTimeNanos: Effect.succeed(1_000_000_000n), + * currentTimeNanosUnsafe: () => 1_000_000_000n, + * currentTimeNanos: Effect.succeed(1_000_000_000n), + * sleep: () => Effect.void + * } + * + * await Effect.runPromise(Effect.provideService(Clock.currentTimeMillis, Clock.Clock, testClock)) // => 1_000 * ``` * * @see {@link currentTimeNanos} for nanosecond precision + * @see {@link monotonicTimeNanos} for measuring elapsed time * @see {@link clockWith} for accessing the full Clock service * - * @category constructors + * @category accessors * @since 2.0.0 */ export const currentTimeMillis: Effect = effect.currentTimeMillis /** - * Returns an Effect that succeeds with the current time in nanoseconds. + * Returns an Effect that succeeds with the current Unix time in nanoseconds. * * **When to use** * - * Use to read wall-clock time from the active `Clock` service with nanosecond - * precision. + * Use to create wall-clock timestamps from the active `Clock` service with + * nanosecond precision. + * + * **Gotchas** + * + * The value can move backward or forward when the system wall clock is + * corrected, so it is not suitable for measuring elapsed time. + * The live clock allows up to one second of drift from `Date.now()` before + * re-anchoring. * * **Example** (Reading nanoseconds) * - * ```ts + * ```ts import.meta.vitest * import { Clock, Effect } from "effect" * - * const program = Effect.gen(function*() { - * const currentTime = yield* Clock.currentTimeNanos - * console.log(`Current time: ${currentTime}ns`) - * return currentTime - * }) + * const testClock: Clock.Clock = { + * currentTimeMillisUnsafe: () => 1_000, + * currentTimeMillis: Effect.succeed(1_000), + * monotonicTimeNanosUnsafe: () => 1_000_000_000n, + * monotonicTimeNanos: Effect.succeed(1_000_000_000n), + * currentTimeNanosUnsafe: () => 1_000_000_000n, + * currentTimeNanos: Effect.succeed(1_000_000_000n), + * sleep: () => Effect.void + * } + * + * await Effect.runPromise(Effect.provideService(Clock.currentTimeNanos, Clock.Clock, testClock)) // => 1_000_000_000n * ``` * - * @category constructors + * @see {@link monotonicTimeNanos} for measuring elapsed time + * + * @category accessors * @since 2.0.0 */ export const currentTimeNanos: Effect = effect.currentTimeNanos + +/** + * Returns an Effect that succeeds with the current monotonic time in + * nanoseconds. + * + * **When to use** + * + * Use to measure elapsed time by subtracting two readings. + * + * **Gotchas** + * + * The value has an arbitrary origin and is unsuitable for serialization. Use + * it only to subtract readings produced by the same clock. Whether it advances + * while the host is suspended depends on the runtime. + * + * @see {@link currentTimeNanos} for Unix wall-clock timestamps + * + * @category accessors + * @since 4.0.0 + */ +export const monotonicTimeNanos: Effect = effect.monotonicTimeNanos diff --git a/repos/effect/packages/effect/src/Combiner.ts b/repos/effect/packages/effect/src/Combiner.ts index 1e9ac7c780..75ab2d492c 100644 --- a/repos/effect/packages/effect/src/Combiner.ts +++ b/repos/effect/packages/effect/src/Combiner.ts @@ -26,13 +26,12 @@ import type * as Order from "./Order.ts" * * **Example** (Combining numbers with addition) * - * ```ts + * ```ts import.meta.vitest * import { Combiner } from "effect" * * const Sum = Combiner.make((self, that) => self + that) * - * console.log(Sum.combine(3, 4)) - * // Output: 7 + * Sum.combine(3, 4) // => 7 * ``` * * @see {@link make} – create a `Combiner` from a function @@ -65,13 +64,12 @@ export interface Combiner { * * **Example** (Multiplying numbers) * - * ```ts + * ```ts import.meta.vitest * import { Combiner } from "effect" * * const Product = Combiner.make((self, that) => self * that) * - * console.log(Product.combine(3, 5)) - * // Output: 15 + * Product.combine(3, 5) // => 15 * ``` * * @see {@link Combiner} – the interface this creates @@ -97,13 +95,12 @@ export function make(combine: (self: A, that: A) => A): Combiner { * * **Example** (Reversing string concatenation) * - * ```ts + * ```ts import.meta.vitest * import { Combiner, String } from "effect" * * const Prepend = Combiner.flip(String.ReducerConcat) * - * console.log(Prepend.combine("a", "b")) - * // Output: "ba" + * Prepend.combine("a", "b") // => "ba" * ``` * * @see {@link make} @@ -130,16 +127,13 @@ export function flip(combiner: Combiner): Combiner { * * **Example** (Selecting the minimum of two numbers) * - * ```ts + * ```ts import.meta.vitest * import { Combiner, Number } from "effect" * * const Min = Combiner.min(Number.Order) * - * console.log(Min.combine(3, 1)) - * // Output: 1 - * - * console.log(Min.combine(1, 3)) - * // Output: 1 + * Min.combine(3, 1) // => 1 + * Min.combine(1, 3) // => 1 * ``` * * @see {@link max} @@ -166,16 +160,13 @@ export function min(order: Order.Order): Combiner { * * **Example** (Selecting the maximum of two numbers) * - * ```ts + * ```ts import.meta.vitest * import { Combiner, Number } from "effect" * * const Max = Combiner.max(Number.Order) * - * console.log(Max.combine(3, 1)) - * // Output: 3 - * - * console.log(Max.combine(1, 3)) - * // Output: 3 + * Max.combine(3, 1) // => 3 + * Max.combine(1, 3) // => 3 * ``` * * @see {@link min} @@ -199,13 +190,12 @@ export function max(order: Order.Order): Combiner { * * **Example** (Keeping the first value) * - * ```ts + * ```ts import.meta.vitest * import { Combiner } from "effect" * * const First = Combiner.first() * - * console.log(First.combine(1, 2)) - * // Output: 1 + * First.combine(1, 2) // => 1 * ``` * * @see {@link last} @@ -229,13 +219,12 @@ export function first(): Combiner { * * **Example** (Keeping the last value) * - * ```ts + * ```ts import.meta.vitest * import { Combiner } from "effect" * * const Last = Combiner.last() * - * console.log(Last.combine(1, 2)) - * // Output: 2 + * Last.combine(1, 2) // => 2 * ``` * * @see {@link first} @@ -261,13 +250,12 @@ export function last(): Combiner { * * **Example** (Always returning zero) * - * ```ts + * ```ts import.meta.vitest * import { Combiner } from "effect" * * const Zero = Combiner.constant(0) * - * console.log(Zero.combine(42, 99)) - * // Output: 0 + * Zero.combine(42, 99) // => 0 * ``` * * @see {@link first} @@ -297,13 +285,12 @@ export function constant(a: A): Combiner { * * **Example** (Joining strings with a separator) * - * ```ts + * ```ts import.meta.vitest * import { Combiner, String } from "effect" * * const commaSep = Combiner.intercalate(",")(String.ReducerConcat) * - * console.log(commaSep.combine("a", "b")) - * // Output: "a,b" + * commaSep.combine("a", "b") // => "a,b" * ``` * * @see {@link make} diff --git a/repos/effect/packages/effect/src/Config.ts b/repos/effect/packages/effect/src/Config.ts index e22371cfb0..9de373eb13 100644 --- a/repos/effect/packages/effect/src/Config.ts +++ b/repos/effect/packages/effect/src/Config.ts @@ -11,11 +11,13 @@ import type { Path, SourceError } from "./ConfigProvider.ts" import * as ConfigProvider from "./ConfigProvider.ts" import * as Effect from "./Effect.ts" import * as Effectable from "./Effectable.ts" -import { dual } from "./Function.ts" +import { dual, memoize } from "./Function.ts" +import * as InternalRecord from "./internal/record.ts" import * as LogLevel_ from "./LogLevel.ts" import * as Option from "./Option.ts" import * as Predicate from "./Predicate.ts" import * as Rec from "./Record.ts" +import * as Result from "./Result.ts" import * as Schema from "./Schema.ts" import * as SchemaAST from "./SchemaAST.ts" import * as SchemaGetter from "./SchemaGetter.ts" @@ -35,11 +37,11 @@ const TypeId = "~effect/Config" * * **Example** (Checking Config values) * - * ```ts + * ```ts import.meta.vitest * import { Config } from "effect" * - * console.log(Config.isConfig(Config.string("HOST"))) // true - * console.log(Config.isConfig("not a config")) // false + * Config.isConfig(Config.string("HOST")) // => true + * Config.isConfig("not a config") // => false * ``` * * @category guards @@ -62,7 +64,7 @@ export const isConfig = (u: unknown): u is Config => Predicate.hasPrope * (wrong type, out of range, missing key, etc.). * * @see {@link orElse} – recover from a ConfigError - * @see {@link withDefault} – provide a fallback for missing-data errors + * @see {@link withDefault} – provide a fallback when relevant input is absent * * @category errors * @since 4.0.0 @@ -93,9 +95,7 @@ export class ConfigError { * **Details** * * Key members: - * - `parse(provider, pathPrefix?)` – runs the config against a specific provider. - * The optional path prefix is the logical scope accumulated from outer - * `Config.nested` calls. + * - `parse(provider)` – runs the config against a specific provider. * - Yieldable – can be yielded inside `Effect.gen`, which automatically * resolves the current `ConfigProvider` from the context. * - Pipeable – supports `.pipe(Config.map(...))` etc. @@ -107,10 +107,39 @@ export class ConfigError { */ export interface Config extends Effect.Effect { readonly [TypeId]: typeof TypeId - readonly parse: ( - provider: ConfigProvider.ConfigProvider, - pathPrefix?: Path - ) => Effect.Effect + readonly parse: (provider: ConfigProvider.ConfigProvider) => Effect.Effect +} + +// Config composition needs to distinguish an absent recipe from a hard failure +// before the public Effect error channel is finalized. `hasInput` records +// provider evidence separately from the value, because successful values such +// as `undefined` and values supplied by defaults are not evidence of input. +// Hard failures carry the same evidence so recovery cannot erase it. +interface Resolved { + readonly _tag: "Resolved" + readonly value: T + readonly hasInput: boolean +} + +interface Absent { + readonly _tag: "Absent" + readonly error: ConfigError +} + +type Resolution = Resolved | Absent + +interface EvaluationFailure { + readonly error: ConfigError + readonly hasInput: boolean +} + +type Evaluator = ( + provider: ConfigProvider.ConfigProvider, + pathPrefix: Path +) => Effect.Effect, EvaluationFailure> + +interface ConfigImpl extends Config { + readonly evaluator: Evaluator } const Proto = { @@ -129,13 +158,71 @@ const Proto = { } function make( - parse: (provider: ConfigProvider.ConfigProvider, pathPrefix: Path) => Effect.Effect + evaluator: Evaluator ): Config { const self = Object.create(Proto) - self.parse = (provider: ConfigProvider.ConfigProvider, pathPrefix: Path = []) => parse(provider, pathPrefix) + self.evaluator = evaluator + self.parse = (provider: ConfigProvider.ConfigProvider) => + evaluator(provider, []).pipe( + Effect.mapErrorEager((failure) => failure.error), + Effect.flatMapEager((resolution) => + resolution._tag === "Resolved" ? Effect.succeed(resolution.value) : Effect.fail(resolution.error) + ) + ) return self } +const evaluateAt = ( + self: Config, + provider: ConfigProvider.ConfigProvider, + pathPrefix: Path +): Effect.Effect, EvaluationFailure> => (self as ConfigImpl).evaluator(provider, pathPrefix) + +const resolved = (value: T, hasInput: boolean): Resolution => ({ + _tag: "Resolved", + value, + hasInput +}) + +const absent = (error: ConfigError): Absent => ({ + _tag: "Absent", + error +}) + +const evaluationFailure = (error: ConfigError, hasInput: boolean): EvaluationFailure => ({ + error, + hasInput +}) + +const isSourceError = (u: unknown): u is ConfigProvider.SourceError => Predicate.isTagged(u, "SourceError") + +const catchSourceError = ( + self: Effect.Effect, + hasInput: boolean +): Effect.Effect => + self.pipe( + Effect.catchDefect((defect) => + isSourceError(defect) + ? Effect.fail(evaluationFailure(new ConfigError(defect), hasInput)) + : Effect.die(defect) + ) + ) + +const preserveInputEvidence = ( + self: Effect.Effect, EvaluationFailure>, + hasInput: boolean +): Effect.Effect, EvaluationFailure> => { + if (!hasInput) return self + return self.pipe( + Effect.mapErrorEager((failure) => evaluationFailure(failure.error, true)), + Effect.flatMapEager((resolution) => + resolution._tag === "Resolved" + ? Effect.succeed(resolved(resolution.value, true)) + : Effect.fail(evaluationFailure(resolution.error, true)) + ) + ) +} + /** * Transforms the parsed value of a config with a pure function. * @@ -146,7 +233,7 @@ function make( * * **Example** (Uppercasing a string config) * - * ```ts + * ```ts import.meta.vitest * import { Config, ConfigProvider, Effect } from "effect" * * const upper = Config.string("name").pipe( @@ -154,7 +241,7 @@ function make( * ) * * const provider = ConfigProvider.fromUnknown({ name: "alice" }) - * // Effect.runSync(upper.parse(provider)) // "ALICE" + * Effect.runSync(upper.parse(provider)) // => "ALICE" * ``` * * @see {@link mapOrFail} – when the transformation can fail @@ -166,7 +253,12 @@ export const map: { (f: (a: A) => B): (self: Config) => Config (self: Config, f: (a: A) => B): Config } = dual(2, (self: Config, f: (a: A) => B): Config => { - return make((provider, pathPrefix) => Effect.map(self.parse(provider, pathPrefix), f)) + return make((provider, pathPrefix) => + Effect.map(evaluateAt(self, provider, pathPrefix), (resolution) => + resolution._tag === "Resolved" + ? resolved(f(resolution.value), resolution.hasInput) + : resolution) + ) }) /** @@ -179,12 +271,14 @@ export const map: { * * **Example** (Wrapping a value in an effectful transformation) * - * ```ts - * import { Config, Effect } from "effect" + * ```ts import.meta.vitest + * import { Config, ConfigProvider, Effect } from "effect" * * const trimmed = Config.string("name").pipe( * Config.mapOrFail((s) => Effect.succeed(s.trim())) * ) + * const provider = ConfigProvider.fromUnknown({ name: " Alice " }) + * Effect.runSync(trimmed.parse(provider)) // => "Alice" * ``` * * @see {@link map} – when the transformation cannot fail @@ -196,7 +290,15 @@ 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(self.parse(provider, pathPrefix), f)) + return make((provider, pathPrefix) => + Effect.flatMap(evaluateAt(self, provider, pathPrefix), (resolution) => + resolution._tag === "Resolved" + ? f(resolution.value).pipe( + Effect.mapEager((value) => resolved(value, resolution.hasInput)), + Effect.mapErrorEager((error) => evaluationFailure(error, resolution.hasInput)) + ) + : Effect.succeed(resolution)) + ) }) /** @@ -209,21 +311,30 @@ export const mapOrFail: { * * **Details** * - * Unlike {@link withDefault}, this catches **all** `ConfigError`s (not just - * missing data). The fallback function receives the error and returns a new + * Unlike {@link withDefault}, this handles both semantic absence and **all** + * `ConfigError`s. The fallback function receives the error and returns a new * `Config`. * + * **Gotchas** + * + * Recovery preserves whether the primary config read provider input. When the + * recovered config is composed with {@link all}, invalid input in the primary + * branch still makes the enclosing group partially supplied, so an outer + * {@link withDefault} or {@link option} does not replace the whole group. + * * **Example** (Falling back to a literal) * - * ```ts - * import { Config } from "effect" + * ```ts import.meta.vitest + * import { Config, ConfigProvider, Effect } from "effect" * * const hostConfig = Config.string("HOST").pipe( * Config.orElse(() => Config.succeed("localhost")) * ) + * const provider = ConfigProvider.fromUnknown({}) + * Effect.runSync(hostConfig.parse(provider)) // => "localhost" * ``` * - * @see {@link withDefault} – fallback only on missing data + * @see {@link withDefault} – fallback only on semantic absence * * @category combinators * @since 2.0.0 @@ -232,8 +343,18 @@ export const orElse: { (that: (error: ConfigError) => Config): (self: Config) => Config (self: Config, that: (error: ConfigError) => Config): Config } = dual(2, (self: Config, that: (error: ConfigError) => Config): Config => { - return make((provider, pathPrefix) => - Effect.catch(self.parse(provider, pathPrefix), (error) => that(error).parse(provider, pathPrefix)) + return make((provider, pathPrefix) => + Effect.matchEffect(evaluateAt(self, provider, pathPrefix), { + onFailure: (failure) => + preserveInputEvidence( + evaluateAt(that(failure.error), provider, pathPrefix), + failure.hasInput + ), + onSuccess: (resolution): Effect.Effect, EvaluationFailure> => + resolution._tag === "Absent" + ? evaluateAt(that(resolution.error), provider, pathPrefix) + : Effect.succeed(resolution) + }) ) }) @@ -249,9 +370,19 @@ export const orElse: { * Accepts a tuple (preserves positions), an iterable, or a record of configs. * Returns a config whose parsed value mirrors the input shape. * + * A combined config is absent when at least one child cannot resolve and none + * of the other children read provider input. This lets {@link withDefault} and + * {@link option} handle a wholly absent group. Once any child reads input, a + * missing sibling makes the group incomplete and parsing fails. Values supplied + * by child defaults do not count as provider input. + * + * Unlike a `Schema.Struct` passed to {@link schema}, `all` only considers input + * read by its children. An explicitly present but empty parent container does + * not by itself make the group present. + * * **Example** (Combining configs as a struct) * - * ```ts + * ```ts import.meta.vitest * import { Config, ConfigProvider, Effect } from "effect" * * const dbConfig = Config.all({ @@ -260,8 +391,7 @@ export const orElse: { * }) * * const provider = ConfigProvider.fromUnknown({ host: "localhost", port: 5432 }) - * // Effect.runSync(dbConfig.parse(provider)) - * // { host: "localhost", port: 5432 } + * Effect.runSync(dbConfig.parse(provider)) // => { host: "localhost", port: 5432 } * ``` * * @category combinators @@ -286,46 +416,85 @@ export function all> | Record - Effect.all(configs.map((config) => config.parse(provider, pathPrefix))) + Effect.flatMapEager( + Effect.all(configs.map((config) => Effect.result(evaluateAt(config, provider, pathPrefix)))), + resolveArray + ) ) as any } else { return make((provider, pathPrefix) => - Effect.all(Rec.map(configs, (config) => config.parse(provider, pathPrefix))) + Effect.flatMapEager( + Effect.all(Rec.map(configs, (config) => Effect.result(evaluateAt(config, provider, pathPrefix)))), + resolveRecord + ) ) as any } } -function isMissingDataOnly(issue: SchemaIssue.Issue): boolean { - switch (issue._tag) { - case "MissingKey": - return true - case "InvalidType": - case "InvalidValue": - return Option.isNone(issue.actual) || (Option.isSome(issue.actual) && issue.actual.value === undefined) - case "OneOf": - return issue.actual === undefined - case "Encoding": - return Option.isNone(issue.actual) || (Option.isSome(issue.actual) && issue.actual.value === undefined) - ? true - : isMissingDataOnly(issue.issue) - case "Pointer": - return isMissingDataOnly(issue.issue) - case "Filter": - case "UnexpectedKey": - case "Forbidden": - return false - case "Composite": - return issue.issues.every(isMissingDataOnly) - case "AnyOf": - if (issue.issues.length === 0) { - return issue.actual === undefined - } - return issue.issues.every(isMissingDataOnly) +const resolveArray = ( + results: ReadonlyArray, EvaluationFailure>> +): Effect.Effect>, EvaluationFailure> => { + const values: Array = [] + let firstFailure: EvaluationFailure | undefined + let firstAbsent: Absent | undefined + let hasInput = false + for (const result of results) { + if (Result.isFailure(result)) { + firstFailure ??= result.failure + hasInput = hasInput || result.failure.hasInput + continue + } + const resolution = result.success + if (resolution._tag === "Absent") { + firstAbsent ??= resolution + } else { + values.push(resolution.value) + hasInput = hasInput || resolution.hasInput + } } + if (firstFailure !== undefined) { + return Effect.fail(evaluationFailure(firstFailure.error, hasInput)) + } + if (firstAbsent !== undefined) { + return hasInput ? Effect.fail(evaluationFailure(firstAbsent.error, true)) : Effect.succeed(firstAbsent) + } + return Effect.succeed(resolved(values, hasInput)) +} + +const resolveRecord = ( + results: Record, EvaluationFailure>> +): Effect.Effect>, EvaluationFailure> => { + const values: Record = {} + let firstFailure: EvaluationFailure | undefined + let firstAbsent: Absent | undefined + let hasInput = false + for (const key in results) { + const result = results[key] + if (Result.isFailure(result)) { + firstFailure ??= result.failure + hasInput = hasInput || result.failure.hasInput + continue + } + const resolution = result.success + if (resolution._tag === "Absent") { + firstAbsent ??= resolution + } else { + InternalRecord.assignProperty(values, key, resolution.value) + hasInput = hasInput || resolution.hasInput + } + } + if (firstFailure !== undefined) { + return Effect.fail(evaluationFailure(firstFailure.error, hasInput)) + } + if (firstAbsent !== undefined) { + return hasInput ? Effect.fail(evaluationFailure(firstAbsent.error, true)) : Effect.succeed(firstAbsent) + } + return Effect.succeed(resolved(values, hasInput)) } /** - * Provides a fallback value when the config fails due to missing data. + * Provides a fallback value when the config cannot resolve because none of its + * relevant input is present. * * **When to use** * @@ -333,23 +502,25 @@ function isMissingDataOnly(issue: SchemaIssue.Issue): boolean { * * **Gotchas** * - * Only applies when the error is a `SchemaError` caused exclusively by - * missing data (missing keys, undefined values). Validation errors (wrong - * type, out of range) still propagate. + * Validation errors and partially supplied groups still propagate. A schema + * that successfully decodes absent input also keeps its decoded value instead + * of using the default. Schema configs first represent a missing or + * incompatible provider shape as `undefined`; the default is used only when + * the schema rejects that value and no relevant input was found. * * **Example** (Defaulting a missing port) * - * ```ts + * ```ts import.meta.vitest * import { Config, ConfigProvider, Effect } from "effect" * * const port = Config.number("port").pipe(Config.withDefault(3000)) * * const provider = ConfigProvider.fromUnknown({}) - * // Effect.runSync(port.parse(provider)) // 3000 + * Effect.runSync(port.parse(provider)) // => 3000 * ``` * * @see {@link option} – returns `Option` instead of a default value - * @see {@link orElse} – catches all errors, not just missing data + * @see {@link orElse} – catches all errors, not just absent input * * @category combinators * @since 2.0.0 @@ -358,20 +529,17 @@ export const withDefault: { (defaultValue: A2): (self: Config) => Config (self: Config, defaultValue: A2): Config } = dual(2, (self: Config, defaultValue: A2): Config => { - return orElse(self, (err) => { - if (Schema.isSchemaError(err.cause)) { - const issue = err.cause.issue - if (isMissingDataOnly(issue)) { - return succeed(defaultValue) - } - } - return fail(err.cause) - }) + return make((provider, pathPrefix) => + Effect.mapEager( + evaluateAt(self, provider, pathPrefix), + (resolution) => resolution._tag === "Absent" ? resolved(defaultValue, false) : resolution + ) + ) }) /** - * Makes a config optional: returns `Some(value)` on success and `None` when - * data is missing. + * Makes a config optional: returns `Some(value)` on success and `None` when the + * config cannot resolve because none of its relevant input is present. * * **When to use** * @@ -379,18 +547,21 @@ export const withDefault: { * * **Gotchas** * - * Like {@link withDefault}, only missing-data errors produce `None`. - * Validation errors still propagate. + * Validation errors and partially supplied groups still propagate. Successful + * values are always wrapped in `Some`, including `undefined` when the schema + * explicitly accepts it. Schema configs first represent a missing or + * incompatible provider shape as `undefined`; `None` is returned only when the + * schema rejects that value and no relevant input was found. * * **Example** (Reading optional config) * - * ```ts - * import { Config, ConfigProvider, Effect } from "effect" + * ```ts import.meta.vitest + * import { Config, ConfigProvider, Effect, Option } from "effect" * * const maybePort = Config.option(Config.number("port")) * * const provider = ConfigProvider.fromUnknown({}) - * // Effect.runSync(maybePort.parse(provider)) // { _tag: "None" } + * Effect.runSync(maybePort.parse(provider)) // => Option.none() * ``` * * @see {@link withDefault} – provide a concrete fallback value instead @@ -432,7 +603,7 @@ export type Success = [T] extends [Config] ? A : never * * @see {@link unwrap} – construct a `Config` from a `Wrap` * - * @category Wrap + * @category utility types * @since 2.0.0 */ export type Wrap = [NonNullable] extends [infer T] ? [IsPlainObject] extends [true] ? @@ -460,8 +631,8 @@ type IsPlainObject = [A] extends [Record] * * **Example** (Unwrapping a record of configs) * - * ```ts - * import { Config } from "effect" + * ```ts import.meta.vitest + * import { Config, ConfigProvider, Effect } from "effect" * * interface Options { * key: string @@ -469,128 +640,158 @@ type IsPlainObject = [A] extends [Record] * * const makeConfig = (config: Config.Wrap): Config.Config => * Config.unwrap(config) + * + * const config = makeConfig({ key: Config.string("key") }) + * const provider = ConfigProvider.fromUnknown({ key: "value" }) + * Effect.runSync(config.parse(provider)) // => { key: "value" } * ``` * * @see {@link Wrap} – the utility type accepted by this function * - * @category Wrap + * @category converting * @since 2.0.0 */ export const unwrap = (wrapped: Wrap): Config => { if (isConfig(wrapped)) return wrapped - return make((provider, pathPrefix) => { - const entries = Object.entries(wrapped) - const configs = entries.map(([key, config]) => - unwrap(config as any).parse(provider, pathPrefix).pipe(Effect.map((value) => [key, value] as const)) - ) - return Effect.all(configs).pipe(Effect.map(Object.fromEntries)) - }) + return all(Rec.map(wrapped as Record>, (config) => unwrap(config))) as Config } // ----------------------------------------------------------------------------- // schema // ----------------------------------------------------------------------------- -const dump: ( +interface ConfigCursor { + readonly provider: ConfigProvider.ConfigProvider + readonly path: Path + readonly node: ConfigProvider.Node | undefined + readonly toString: () => string +} + +const cursorToString = (): string => "" + +const loadCursor: ( provider: ConfigProvider.ConfigProvider, path: Path -) => Effect.Effect = Effect.fnUntraced(function*( - provider, - path -) { - const stat = yield* provider.load(path) - if (stat === undefined) return undefined - switch (stat._tag) { - case "Value": - return stat.value - case "Record": { - if (stat.value !== undefined) return stat.value - const out: Record = {} - for (const key of stat.keys) { - const child = yield* dump(provider, [...path, key]) - if (child !== undefined) out[key] = child - } - return out - } - case "Array": { - if (stat.value !== undefined) return stat.value - const out: Array = [] - for (let i = 0; i < stat.length; i++) { - out.push(yield* dump(provider, [...path, i])) - } - return out - } +) => Effect.Effect = (provider, path) => + provider.load(path).pipe( + Effect.orDie, + Effect.mapEager((node) => ({ provider, path, node, toString: cursorToString })) + ) + +const loadChildCursor = (cursor: ConfigCursor, segment: string | number): Effect.Effect => + loadCursor(cursor.provider, [...cursor.path, segment]) + +const getScalar = (node: ConfigProvider.Node | undefined): string | undefined => node?.value + +const decodeFromCursor = ( + ast: SchemaAST.AST, + decode: (cursor: ConfigCursor) => Effect.Effect +): SchemaAST.AST => + SchemaAST.decodeTo( + SchemaAST.unknown, + ast, + new SchemaTransformation.Transformation( + SchemaGetter.transformOrFail((input: unknown) => decode(input as ConfigCursor)), + SchemaGetter.passthrough() + ) + ) + +const isScalarInput = (ast: SchemaAST.AST): boolean => { + switch (ast._tag) { + case "Union": + return ast.types.every(isScalarInput) + case "Objects": + case "Arrays": + case "Suspend": + return false + default: + return true } -}) +} -const recur: ( +const hasProviderInput = ( ast: SchemaAST.AST, - provider: ConfigProvider.ConfigProvider, - path: Path -) => Effect.Effect = Effect.fnUntraced( - function*(ast, provider, path) { + node: ConfigProvider.Node | undefined +): boolean => { + switch (ast._tag) { + case "Objects": + return node?._tag === "Record" + case "Arrays": + return node?._tag === "Array" + case "Union": + return ast.types.some((ast) => hasProviderInput(ast, node)) + case "Suspend": + return hasProviderInput(ast.thunk(), node) + default: + return getScalar(node) !== undefined + } +} + +const toConfigCursorAST = memoize((root: SchemaAST.AST): SchemaAST.AST => { + const seen = new WeakSet() + const recur = SchemaAST.applyToSelfOrLastLinkEncoding((ast) => { + seen.add(ast) switch (ast._tag) { case "Objects": { - const stat = yield* provider.load(path) - if (stat === undefined && path.length > 0) return undefined - const out: Record = {} - for (const ps of ast.propertySignatures) { - const name = ps.name - if (typeof name === "string") { - const value = yield* recur(ps.type, provider, [...path, name]) - if (value !== undefined) out[name] = value + const matchesIndex = ast.indexSignatures.map((is) => SchemaParser._is(is.parameter)) + const materialize = Effect.fnUntraced(function*(cursor: ConfigCursor) { + if (cursor.node?._tag !== "Record") { + return undefined } - } - if (ast.indexSignatures.length > 0) { - if (stat && stat._tag === "Record") { - for (const is of ast.indexSignatures) { - const matches = SchemaParser._is(is.parameter) - for (const key of stat.keys) { - if (!Object.hasOwn(out, key) && matches(key)) { - const value = yield* recur(is.type, provider, [...path, key]) - if (value !== undefined) out[key] = value - } - } + const node = cursor.node + const keys = new Set() + for (const property of ast.propertySignatures) { + if (typeof property.name === "string") keys.add(property.name) + } + if (matchesIndex.length > 0) { + for (const key of node.keys) { + if (matchesIndex.some((matches) => matches(key))) keys.add(key) } } - } - return out + const out: Record = {} + for (const key of keys) { + const child = yield* loadChildCursor(cursor, key) + if (child.node !== undefined) InternalRecord.assignProperty(out, key, child) + } + return out + }) + return decodeFromCursor(ast.recur(recur, (ast) => ast), materialize) } case "Arrays": { - const stat = yield* provider.load(path) - if (stat === undefined) return undefined - if (stat && stat._tag === "Value") return stat.value === "" ? [] : stat.value.split(",") - if (stat && stat._tag === "Array" && stat.value !== undefined) { - return stat.value === "" ? [] : stat.value.split(",") - } - const out: Array = [] - const length = stat && stat._tag === "Array" ? stat.length : ast.elements.length - for (let i = 0; i < length; i++) { - const element = ast.elements[i] ?? ast.rest[0] - if (element !== undefined) { - out.push(yield* recur(element, provider, [...path, i])) + const materialize = Effect.fnUntraced(function*(cursor: ConfigCursor) { + if (cursor.node?._tag !== "Array") { + return undefined } - } - return out + const out: Array = [] + for (let i = 0; i < cursor.node.length; i++) { + out.push(yield* loadChildCursor(cursor, i)) + } + return out + }) + return decodeFromCursor(ast.recur(recur), materialize) } case "Union": - // Let downstream decoding decide; dump can return a string, object, or array. - return yield* dump(provider, path) - case "Suspend": - return yield* recur(ast.thunk(), provider, path) - default: { - // Base primitives / string-like encoded nodes. - const stat = yield* provider.load(path) - if (stat === undefined) return undefined - if (stat._tag === "Value") return stat.value - if (stat._tag === "Record" && stat.value !== undefined) return stat.value - if (stat._tag === "Array" && stat.value !== undefined) return stat.value - // Container without a co-located value cannot satisfy a scalar request. - return undefined + for (const member of ast.types) { + recur(member) + } + return isScalarInput(ast) + ? decodeFromCursor(ast, (cursor) => Effect.succeed(getScalar(cursor.node))) + : ast.recur(recur) + case "Suspend": { + const target = ast.thunk() + // Force new branches so opaque encodings fail when the Config is constructed. + if (!seen.has(target)) recur(target) + return ast.recur(recur) } + case "Declaration": + case "Any": + throw new globalThis.Error("Config.schema does not support opaque StringTree encodings", { cause: ast }) + default: + return decodeFromCursor(ast, (cursor) => Effect.succeed(getScalar(cursor.node))) } - } -) + }) + return recur(root) +}) /** * Creates a `Config` from a `Schema.Codec`. @@ -609,12 +810,47 @@ const recur: ( * Convenience constructors such as `string`, `number`, and `boolean` delegate * to this API. * - * The codec is used to decode the raw `StringTree` produced by the provider - * into `T`. Schema validation errors are wrapped in `ConfigError`. + * The codec is converted to its canonical `StringTree` form. Its encoded shape + * determines how provider data is loaded: scalar schemas read a co-located + * scalar value, object schemas read declared properties and matching record + * keys, and array schemas read indexed children. A mixed-shape union loads each + * member according to that member's shape before applying the union's mode and + * checks. + * + * At the config's lookup path, a missing node or a node that cannot provide the + * representation required by the schema is decoded as `undefined`. Missing + * object properties remain omitted so the schema's property semantics still + * apply. Decoding success always wins, even when no provider input was found. + * For example, + * `Schema.UndefinedOr(Schema.String)` decodes to `undefined` and is not replaced + * by {@link withDefault}. If decoding fails and no relevant representation was + * found, the config is absent. Invalid data in a relevant representation is a + * validation failure. Provider `SourceError`s are always failures. + * + * **Gotchas** + * + * Plain `Schema.Array` and `Schema.Record` schemas use structural provider + * input. Use {@link Array} or {@link Record} when a flat separated string must + * also be accepted. + * + * `Schema.Struct` and {@link all} describe different lookup models. An + * explicitly present empty object is relevant input for a struct and required + * fields are validated. The same empty parent container does not make an + * `all` group present when all of its child configs are absent. + * + * The canonical `StringTree` encoding must expose a concrete scalar, object, + * array, or union shape. Opaque encodings such as `Schema.Any`, + * `Schema.Unknown`, `Schema.ObjectKeyword`, `Schema.Json`, and + * `Schema.MutableJson` are rejected synchronously when this config is + * constructed, including when they are nested in another schema. Suspended + * recursive schemas remain supported when their eventual shape is concrete. + * Declarations such as `Schema.URL` also remain supported when their canonical + * encoding has a concrete shape. To read arbitrary JSON from one scalar value, + * use `Schema.fromJsonString(Schema.Json)`. * * **Example** (Reading a structured config) * - * ```ts + * ```ts import.meta.vitest * import { Config, ConfigProvider, Effect, Schema } from "effect" * * const DbConfig = Config.schema( @@ -629,8 +865,7 @@ const recur: ( * db: { host: "localhost", port: 5432 } * }) * - * // Effect.runSync(DbConfig.parse(provider)) - * // { host: "localhost", port: 5432 } + * Effect.runSync(DbConfig.parse(provider)) // => { host: "localhost", port: 5432 } * ``` * * @see {@link string} / {@link number} / {@link boolean} – shortcuts for @@ -639,22 +874,33 @@ const recur: ( * @category schemas * @since 4.0.0 */ -export function schema(codec: Schema.Codec, path?: string | ConfigProvider.Path): Config { +export function schema(codec: Schema.ConstraintCodec, path?: string | ConfigProvider.Path): Config { const codecStringTree = Schema.toCodecStringTree(codec) - const decodeUnknownEffect = SchemaParser.decodeUnknownEffect(codecStringTree) - const codecStringTreeEncoded = SchemaAST.toEncoded(codecStringTree.ast) + const encodedAst = SchemaAST.toEncoded(codecStringTree.ast) + const decodeCursor = SchemaParser.decodeUnknownEffect( + Schema.make>(toConfigCursorAST(codecStringTree.ast)) + ) const localPath = typeof path === "string" ? [path] : path ?? [] return make((provider, pathPrefix) => { const fullPath = [...pathPrefix, ...localPath] - return recur(codecStringTreeEncoded, provider, fullPath).pipe( - Effect.flatMapEager((tree) => - decodeUnknownEffect(tree).pipe( - Effect.mapErrorEager((issue) => - new Schema.SchemaError(fullPath.length > 0 ? new SchemaIssue.Pointer(fullPath, issue) : issue) - ) + return catchSourceError(loadCursor(provider, fullPath), false).pipe( + Effect.flatMapEager((cursor) => { + const hasInput = hasProviderInput(encodedAst, cursor.node) + return catchSourceError( + decodeCursor(cursor).pipe( + Effect.mapEager((value) => resolved(value, hasInput)), + Effect.catchEager((issue) => { + const error = new ConfigError( + new Schema.SchemaError(fullPath.length > 0 ? new SchemaIssue.Pointer(fullPath, issue) : issue) + ) + return hasInput + ? Effect.fail(evaluationFailure(error, true)) + : Effect.succeed(absent(error)) + }) + ), + hasInput ) - ), - Effect.mapErrorEager((cause) => new ConfigError(cause)) + }) ) }) } @@ -745,7 +991,7 @@ export const LogLevel = Schema.Literals(LogLevel_.values) * * **Example** (Parsing a comma-separated record) * - * ```ts + * ```ts import.meta.vitest * import { Config, ConfigProvider, Effect, Schema } from "effect" * * const schema = Config.Record(Schema.String, Schema.String) @@ -758,14 +1004,14 @@ export const LogLevel = Schema.Literals(LogLevel_.values) * } * }) * - * console.dir(Effect.runSync(config.parse(provider))) - * // { - * // '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 */ @@ -774,35 +1020,31 @@ export const Record = readonly keyValueSeparator?: string | undefined }) => { const record = Schema.Record(key, value) + const split = SchemaTransformation.splitKeyValue(options) const recordString = Schema.String.pipe( - Schema.decodeTo( - Schema.Record(Schema.String, Schema.String), - SchemaTransformation.splitKeyValue(options) - ), - Schema.decodeTo(record) + Schema.decodeTo(Schema.toCodecStringTree(record), { + decode: split.decode, + encode: SchemaGetter.passthrough, Schema.StringTree>({ strict: false }).compose( + split.encode + ) + }) ) return Schema.Union([record, recordString]) } -/** - * @category schemas - * @since 4.0.0 - */ 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.Array(Schema.String), - { - decode: SchemaGetter.split(options), - encode: SchemaGetter.transform((input: ReadonlyArray) => input.join(separator)) - } - ), - Schema.decodeTo(array) + 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]) @@ -822,6 +1064,8 @@ export { * 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 */ @@ -844,7 +1088,7 @@ export { * @since 2.0.0 */ export function fail(err: SourceError | Schema.SchemaError) { - return make(() => Effect.fail(new ConfigError(err))) + return make(() => Effect.fail(evaluationFailure(new ConfigError(err), false))) } /** @@ -858,19 +1102,21 @@ export function fail(err: SourceError | Schema.SchemaError) { * * **Example** (Returning a constant fallback) * - * ```ts - * import { Config } from "effect" + * ```ts import.meta.vitest + * import { Config, ConfigProvider, Effect } from "effect" * * const host = Config.string("HOST").pipe( * Config.orElse(() => Config.succeed("localhost")) * ) + * const provider = ConfigProvider.fromUnknown({}) + * Effect.runSync(host.parse(provider)) // => "localhost" * ``` * * @category constructors * @since 2.0.0 */ export function succeed(value: T) { - return make(() => Effect.succeed(value)) + return make(() => Effect.succeed(resolved(value, false))) } /** @@ -886,13 +1132,13 @@ export function succeed(value: T) { * * **Example** (Reading a string config) * - * ```ts + * ```ts import.meta.vitest * import { Config, ConfigProvider, Effect } from "effect" * * const host = Config.string("HOST") * * const provider = ConfigProvider.fromUnknown({ HOST: "localhost" }) - * // Effect.runSync(host.parse(provider)) // "localhost" + * Effect.runSync(host.parse(provider)) // => "localhost" * ``` * * @see {@link nonEmptyString} – rejects empty strings @@ -1003,10 +1249,12 @@ export function int(name?: string) { * * **Example** (Restricting to a literal) * - * ```ts - * import { Config } from "effect" + * ```ts import.meta.vitest + * import { Config, ConfigProvider, Effect } from "effect" * * const env = Config.literal("production", "ENV") + * const provider = ConfigProvider.fromUnknown({ ENV: "production" }) + * Effect.runSync(env.parse(provider)) // => "production" * ``` * * @see {@link literals} – accepts multiple literal values @@ -1030,10 +1278,12 @@ export function literal(literal: L, name?: str * * **Example** (Restricting to a set of literals) * - * ```ts - * import { Config } from "effect" + * ```ts import.meta.vitest + * import { Config, ConfigProvider, Effect } from "effect" * * const env = Config.literals(["development", "production"], "ENV") + * const provider = ConfigProvider.fromUnknown({ ENV: "development" }) + * Effect.runSync(env.parse(provider)) // => "development" * ``` * * @see {@link literal} for accepting one specific literal value @@ -1062,13 +1312,10 @@ export function literals>( * * **Example** (Reading a boolean flag) * - * ```ts + * ```ts import.meta.vitest * import { Config, ConfigProvider, Effect } from "effect" * - * const program = Effect.gen(function*() { - * const flag = yield* Config.boolean("FEATURE_FLAG") - * console.log(flag) - * }) + * const program = Config.boolean("FEATURE_FLAG") * * const provider = ConfigProvider.fromEnv({ * env: { @@ -1078,8 +1325,7 @@ export function literals>( * * Effect.runSync( * program.pipe(Effect.provideService(ConfigProvider.ConfigProvider, provider)) - * ) - * // Output: true + * ) // => true * ``` * * @see {@link Boolean} for the underlying boolean codec @@ -1108,13 +1354,10 @@ export function boolean(name?: string) { * * **Example** (Reading a duration) * - * ```ts - * import { Config, ConfigProvider, Effect } from "effect" + * ```ts import.meta.vitest + * import { Config, ConfigProvider, Duration, Effect } from "effect" * - * const program = Effect.gen(function*() { - * const duration = yield* Config.duration("DURATION") - * console.log(duration) - * }) + * const program = Config.duration("DURATION").pipe(Effect.map(Duration.toMillis)) * * const provider = ConfigProvider.fromEnv({ * env: { @@ -1124,8 +1367,7 @@ export function boolean(name?: string) { * * Effect.runSync( * program.pipe(Effect.provideService(ConfigProvider.ConfigProvider, provider)) - * ) - * // Output: Duration { _tag: "millis", value: 10000 } + * ) // => 10000 * ``` * * @see {@link schema} for decoding configuration values with a custom codec @@ -1150,13 +1392,10 @@ export function duration(name?: string) { * * **Example** (Reading a port) * - * ```ts + * ```ts import.meta.vitest * import { Config, ConfigProvider, Effect } from "effect" * - * const program = Effect.gen(function*() { - * const port = yield* Config.port("PORT") - * console.log(port) - * }) + * const program = Config.port("PORT") * * const provider = ConfigProvider.fromEnv({ * env: { @@ -1166,8 +1405,7 @@ export function duration(name?: string) { * * Effect.runSync( * program.pipe(Effect.provideService(ConfigProvider.ConfigProvider, provider)) - * ) - * // Output: 8080 + * ) // => 8080 * ``` * * @see {@link int} for integer config values outside the port range @@ -1196,13 +1434,10 @@ export function port(name?: string) { * * **Example** (Reading a log level) * - * ```ts + * ```ts import.meta.vitest * import { Config, ConfigProvider, Effect } from "effect" * - * const program = Effect.gen(function*() { - * const logLevel = yield* Config.logLevel("LOG_LEVEL") - * console.log(logLevel) - * }) + * const program = Config.logLevel("LOG_LEVEL") * * const provider = ConfigProvider.fromEnv({ * env: { @@ -1212,8 +1447,7 @@ export function port(name?: string) { * * Effect.runSync( * program.pipe(Effect.provideService(ConfigProvider.ConfigProvider, provider)) - * ) - * // Output: "Info" + * ) // => "Info" * ``` * * @see {@link LogLevel} for the underlying log-level codec @@ -1240,13 +1474,10 @@ export function logLevel(name?: string) { * * **Example** (Reading a secret) * - * ```ts + * ```ts import.meta.vitest * import { Config, ConfigProvider, Effect } from "effect" * - * const program = Effect.gen(function*() { - * const apiKey = yield* Config.redacted("API_KEY") - * console.log(apiKey) - * }) + * const program = Config.redacted("API_KEY").pipe(Effect.map(String)) * * const provider = ConfigProvider.fromEnv({ * env: { @@ -1256,8 +1487,7 @@ export function logLevel(name?: string) { * * Effect.runSync( * program.pipe(Effect.provideService(ConfigProvider.ConfigProvider, provider)) - * ) - * // Output: + * ) // => "" * ``` * * @see {@link string} for non-secret string settings @@ -1286,13 +1516,10 @@ export function redacted(name?: string) { * * **Example** (Reading a URL) * - * ```ts + * ```ts import.meta.vitest * import { Config, ConfigProvider, Effect } from "effect" * - * const program = Effect.gen(function*() { - * const url = yield* Config.url("URL") - * console.log(url) - * }) + * const program = Config.url("URL").pipe(Effect.map((url) => url.href)) * * const provider = ConfigProvider.fromEnv({ * env: { @@ -1302,22 +1529,7 @@ export function redacted(name?: string) { * * Effect.runSync( * program.pipe(Effect.provideService(ConfigProvider.ConfigProvider, provider)) - * ) - * // Output: - * // URL { - * // href: 'https://example.com/', - * // origin: 'https://example.com', - * // protocol: 'https:', - * // username: '', - * // password: '', - * // host: 'example.com', - * // hostname: 'example.com', - * // port: '', - * // pathname: '/', - * // search: '', - * // searchParams: URLSearchParams {}, - * // hash: '' - * // } + * ) // => "https://example.com/" * ``` * * @see {@link schema} for decoding configuration values with a custom codec @@ -1338,7 +1550,7 @@ export function url(name?: string) { * * **Details** * - * Shortcut for `Config.schema(Schema.DateValid, name)`. + * Shortcut for `Config.schema(Schema.Date, name)`. * * **Gotchas** * @@ -1346,21 +1558,20 @@ export function url(name?: string) { * * **Example** (Reading a date) * - * ```ts + * ```ts import.meta.vitest * import { Config, ConfigProvider, Effect } from "effect" * * const createdAt = Config.date("CREATED_AT") * * const provider = ConfigProvider.fromUnknown({ CREATED_AT: "2024-01-15" }) - * // Effect.runSync(createdAt.parse(provider)) - * // Date("2024-01-15T00:00:00.000Z") + * Effect.runSync(createdAt.parse(provider)).toISOString() // => "2024-01-15T00:00:00.000Z" * ``` * * @category constructors * @since 2.0.0 */ export function date(name?: string) { - return schema(Schema.DateValid, name) + return schema(Schema.Date, name) } /** @@ -1381,7 +1592,7 @@ export function date(name?: string) { * * **Example** (Nesting a struct config under `"database"`) * - * ```ts + * ```ts import.meta.vitest * import { Config, ConfigProvider, Effect } from "effect" * * const dbConfig = Config.all({ @@ -1392,13 +1603,12 @@ export function date(name?: string) { * const provider = ConfigProvider.fromUnknown({ * database: { host: "localhost", port: "5432" } * }) - * // Effect.runSync(dbConfig.parse(provider)) - * // { host: "localhost", port: 5432 } + * Effect.runSync(dbConfig.parse(provider)) // => { host: "localhost", port: 5432 } * ``` * * **Example** (Reading env vars with a nested prefix) * - * ```ts + * ```ts import.meta.vitest * import { Config, ConfigProvider, Effect } from "effect" * * const host = Config.string("host").pipe(Config.nested("database")) @@ -1406,7 +1616,7 @@ export function date(name?: string) { * const provider = ConfigProvider.fromEnv({ * env: { database_host: "localhost" } * }) - * // Effect.runSync(host.parse(provider)) // "localhost" + * Effect.runSync(host.parse(provider)) // => "localhost" * ``` * * @see {@link all} – combine multiple configs into a struct @@ -1421,5 +1631,5 @@ export const nested: { } = dual( 2, (self: Config, name: string): Config => - make((provider, pathPrefix) => self.parse(provider, [...pathPrefix, name])) + make((provider, pathPrefix) => evaluateAt(self, provider, [...pathPrefix, name])) ) diff --git a/repos/effect/packages/effect/src/ConfigProvider.ts b/repos/effect/packages/effect/src/ConfigProvider.ts index ec718c9469..a847dc7438 100644 --- a/repos/effect/packages/effect/src/ConfigProvider.ts +++ b/repos/effect/packages/effect/src/ConfigProvider.ts @@ -41,6 +41,10 @@ import * as Str from "./String.ts" * `value`. `Array` is an indexed container with a known `length` and may also * carry an optional co-located `value`. * + * Provider lookups return `undefined` when no node exists at the requested + * path. Within a node that was found, `value: undefined` has a narrower + * structural meaning: the container exists but has no co-located scalar value. + * * @see {@link makeValue} – construct a `Value` node * @see {@link makeRecord} – construct a `Record` node * @see {@link makeArray} – construct an `Array` node @@ -81,11 +85,10 @@ export type Node = * * **Example** (Creating a value node) * - * ```ts + * ```ts import.meta.vitest * import { ConfigProvider } from "effect" * - * const node = ConfigProvider.makeValue("3000") - * // { _tag: "Value", value: "3000" } + * ConfigProvider.makeValue("3000") // => { _tag: "Value", value: "3000" } * ``` * * @see {@link makeRecord} – for object-like containers @@ -115,11 +118,15 @@ export function makeValue(value: string): Node { * * **Example** (Creating a record node) * - * ```ts + * ```ts import.meta.vitest * import { ConfigProvider } from "effect" * * const node = ConfigProvider.makeRecord(new Set(["host", "port"])) - * // { _tag: "Record", keys: Set(["host", "port"]), value: undefined } + * node._tag // => "Record" + * if (node._tag === "Record") { + * node.keys // => new Set(["host", "port"]) + * node.value // => undefined + * } * ``` * * @see {@link makeValue} – for terminal leaves @@ -148,11 +155,10 @@ export function makeRecord(keys: ReadonlySet, value?: string): Node { * * **Example** (Creating an array node) * - * ```ts + * ```ts import.meta.vitest * import { ConfigProvider } from "effect" * - * const node = ConfigProvider.makeArray(3) - * // { _tag: "Array", length: 3, value: undefined } + * ConfigProvider.makeArray(3) // => { _tag: "Array", length: 3, value: undefined } * ``` * * @see {@link makeValue} – for terminal leaves @@ -180,7 +186,7 @@ export function makeArray(length: number, value?: string): Node { * * **Example** (Failing with a SourceError) * - * ```ts + * ```ts import.meta.vitest * import { ConfigProvider, Effect } from "effect" * * const provider = ConfigProvider.make((_path) => @@ -188,12 +194,14 @@ export function makeArray(length: number, value?: string): Node { * new ConfigProvider.SourceError({ message: "connection refused" }) * ) * ) + * + * Effect.runSync(Effect.flip(provider.load(["host"]))).message // => "connection refused" * ``` * * @see {@link ConfigProvider} – the interface whose `load` may fail with this * error * - * @category models + * @category errors * @since 4.0.0 */ export class SourceError extends Data.TaggedError("SourceError")<{ @@ -213,10 +221,11 @@ export class SourceError extends Data.TaggedError("SourceError")<{ * * **Example** (A typical config path) * - * ```ts + * ```ts import.meta.vitest * import type { ConfigProvider } from "effect" * * const path: ConfigProvider.Path = ["database", "replicas", 0, "host"] + * path.join(".") // => "database.replicas.0.host" * ``` * * @category models @@ -236,29 +245,63 @@ export type Path = ReadonlyArray * * `load(path)` is the semantic lookup operation used by the `Config` module. * It applies provider transformations and composition before consulting the - * underlying source. `undefined` means "not found" and `SourceError` means the - * source itself failed. + * underlying source. `undefined` means "not found", a `Node` means the path + * exists, and `SourceError` means the source itself failed. + * + * `mapInput(f)` is the provider's path-transformation capability. Keeping this + * capability on the provider allows source and composite providers to preserve + * their own lookup behavior without exposing an internal representation. + * Transformations compose in application order: `f` receives the path produced + * by earlier transformations. + * + * `load` deliberately accepts only a `Path`. Path transformation is modeled by + * returning another provider through `mapInput`, rather than by adding a + * transformation callback to every lookup. Custom implementations therefore + * expose lookup and transformation behavior, but no source or composition + * state. * * @see {@link make} – construct a provider from a lookup function * @see {@link orElse} – compose providers with fallback * - * @category models - * @since 2.0.0 + * @category services + * @since 4.0.0 */ export interface ConfigProvider extends Pipeable { /** - * Returns the node found at `path`, or `undefined` if it does not exist. - * Fails with `SourceError` when the underlying source cannot be read. + * Returns a `Node` when `path` exists or `undefined` when it does not. Fails + * with `SourceError` when the underlying source cannot be read. * * **When to use** * * Use to resolve a path through this provider's path transformations before * reading the backing source. + * + * **Details** + * + * Lookup absence controls provider composition, such as whether + * {@link orElse} consults its fallback. An optional `value` inside a found + * `Record` or `Array` node remains `undefined` because it describes the shape + * of that node rather than the outcome of the lookup. */ readonly load: (path: Path) => Effect.Effect - /** @internal */ - readonly state: ProviderState + /** + * Returns a provider that applies `f` to lookup paths after any existing path + * transformations. + * + * **When to use** + * + * Use to implement provider-specific path transformation behavior. Most + * callers should use the pipeable {@link mapInput} combinator. + * + * **Details** + * + * This capability is part of the provider interface so composite providers + * can distribute transformations to their operands while preserving each + * operand's behavior. Providers created with {@link make} implement it + * automatically. + */ + readonly mapInput: (f: (path: Path) => Path) => ConfigProvider } /** @@ -274,7 +317,7 @@ export interface ConfigProvider extends Pipeable { * * **Example** (Providing a custom provider) * - * ```ts + * ```ts import.meta.vitest * import { ConfigProvider, Effect } from "effect" * * const provider = ConfigProvider.fromUnknown({ port: 8080 }) @@ -285,13 +328,15 @@ export interface ConfigProvider extends Pipeable { * }).pipe( * Effect.provideService(ConfigProvider.ConfigProvider, provider) * ) + * + * Effect.runSync(program) === provider // => true * ``` * * @see {@link layer} – install a provider as a Layer * @see {@link layerAdd} – add a fallback provider as a Layer * * @category services - * @since 2.0.0 + * @since 4.0.0 */ export const ConfigProvider: Context.Reference = Context.Reference( "effect/ConfigProvider", @@ -307,29 +352,15 @@ const Proto = { } } -type SourceState = { - readonly _tag: "Source" - readonly get: (path: Path) => Effect.Effect - readonly transform: (path: Path) => Path -} - -type OrElseState = { - readonly _tag: "OrElse" - readonly first: ConfigProvider - readonly second: ConfigProvider -} - -type ProviderState = SourceState | OrElseState - const identityPath = (path: Path): Path => path function makeProvider( - state: ProviderState, - load: (path: Path) => Effect.Effect + load: (path: Path) => Effect.Effect, + mapInput: (f: (path: Path) => Path) => ConfigProvider ): ConfigProvider { const self = Object.create(Proto) - self.state = state self.load = load + self.mapInput = mapInput return self } @@ -337,25 +368,21 @@ function makeSource( get: (path: Path) => Effect.Effect, transform: (path: Path) => Path ): ConfigProvider { - const state: SourceState = { - _tag: "Source", - get, - transform - } - return makeProvider(state, (path) => state.get(state.transform(path))) + return makeProvider( + (path) => get(transform(path)), + (f) => makeSource(get, flow(transform, f)) + ) } function makeOrElse(first: ConfigProvider, second: ConfigProvider): ConfigProvider { - const state: OrElseState = { - _tag: "OrElse", - first, - second - } - return makeProvider(state, (path) => - Effect.flatMap( - state.first.load(path), - (node) => node ? Effect.succeed(node) : state.second.load(path) - )) + return makeProvider( + (path) => + Effect.flatMap( + first.load(path), + (node) => node !== undefined ? Effect.succeed(node) : second.load(path) + ), + (f) => makeOrElse(first.mapInput(f), second.mapInput(f)) + ) } /** @@ -369,12 +396,17 @@ function makeOrElse(first: ConfigProvider, second: ConfigProvider): ConfigProvid * **Details** * * The `get` callback receives a `Path` and must return - * `Effect`. Return `undefined` when the path - * does not exist; fail with `SourceError` only for actual I/O errors. + * `Effect`. Return `undefined` when the path does + * not exist, a `Node` when it does, and fail with `SourceError` only when the + * source cannot be read. + * + * Providers created by `make` also implement the path-transformation + * capability used by {@link mapInput}, {@link constantCase}, and + * {@link nested}. * * **Example** (Creating a simple in-memory provider) * - * ```ts + * ```ts import.meta.vitest * import { ConfigProvider, Effect } from "effect" * * const data: Record = { @@ -389,13 +421,15 @@ function makeOrElse(first: ConfigProvider, second: ConfigProvider): ConfigProvid * value !== undefined ? ConfigProvider.makeValue(value) : undefined * ) * }) + * + * Effect.runSync(provider.load(["host"])) // => ConfigProvider.makeValue("localhost") * ``` * * @see {@link fromEnv} – pre-built provider for environment variables * @see {@link fromUnknown} – pre-built provider for JSON objects * * @category constructors - * @since 2.0.0 + * @since 4.0.0 */ export function make(get: (path: Path) => Effect.Effect): ConfigProvider { return makeSource(get, identityPath) @@ -423,8 +457,8 @@ export function make(get: (path: Path) => Effect.Effect Effect.Effect ["prod.example.com", "3000"] * ``` * * @see {@link layerAdd} – install a fallback provider via a Layer @@ -463,10 +501,14 @@ export const orElse: { * `f` runs. For providers composed with {@link orElse}, the transformation is * applied to each operand. * + * The combinator delegates transformation to the provider itself. Use + * {@link make} for custom sources so this capability is implemented + * automatically. + * * **Example** (Uppercasing path segments) * - * ```ts - * import { ConfigProvider } from "effect" + * ```ts import.meta.vitest + * import { ConfigProvider, Effect } from "effect" * * const provider = ConfigProvider.fromEnv({ * env: { APP_HOST: "localhost" } @@ -477,6 +519,9 @@ export const orElse: { * typeof seg === "string" ? seg.toUpperCase() : seg * ) * ) + * + * const node = Effect.runSync(upper.load(["app_host"])) + * node?.value // => "localhost" * ``` * * @see {@link constantCase} – a preset that converts to `CONSTANT_CASE` @@ -490,15 +535,7 @@ export const mapInput: { (self: ConfigProvider, f: (path: Path) => Path): ConfigProvider } = dual( 2, - (self: ConfigProvider, f: (path: Path) => Path): ConfigProvider => { - const state = self.state - switch (state._tag) { - case "Source": - return makeSource(state.get, flow(state.transform, f)) - case "OrElse": - return makeOrElse(mapInput(state.first, f), mapInput(state.second, f)) - } - } + (self: ConfigProvider, f: (path: Path) => Path): ConfigProvider => self.mapInput(f) ) /** @@ -511,19 +548,22 @@ export const mapInput: { * * **Details** * - * Numeric segments are left unchanged. This is a specialization of - * {@link mapInput}. + * Numeric segments are left unchanged. String segments use `String.configCase` + * so numeric word groups such as `v2` are preserved for environment variable + * names. This is a specialization of {@link mapInput}. * * **Example** (Resolving camelCase keys to env vars) * - * ```ts - * import { ConfigProvider } from "effect" + * ```ts import.meta.vitest + * import { ConfigProvider, Effect } from "effect" * * const provider = ConfigProvider.fromEnv({ * env: { DATABASE_HOST: "localhost" } * }).pipe(ConfigProvider.constantCase) * * // path ["databaseHost"] now resolves to env var DATABASE_HOST + * const node = Effect.runSync(provider.load(["databaseHost"])) + * node?.value // => "localhost" * ``` * * @see {@link mapInput} – for arbitrary path transformations @@ -532,7 +572,7 @@ export const mapInput: { * @since 2.0.0 */ export const constantCase: (self: ConfigProvider) => ConfigProvider = mapInput((path) => - path.map((seg) => typeof seg === "number" ? seg : Str.constantCase(seg)) + path.map((seg) => typeof seg === "number" ? seg : Str.configCase(seg)) ) /** @@ -559,8 +599,8 @@ export const constantCase: (self: ConfigProvider) => ConfigProvider = mapInput(( * * **Example** (Nesting under a prefix) * - * ```ts - * import { ConfigProvider } from "effect" + * ```ts import.meta.vitest + * import { ConfigProvider, Effect } from "effect" * * const provider = ConfigProvider.fromEnv({ * env: { APP_HOST: "localhost", APP_PORT: "3000" } @@ -568,6 +608,8 @@ export const constantCase: (self: ConfigProvider) => ConfigProvider = mapInput(( * * // Lookups for ["HOST"] now resolve to ["APP", "HOST"] * const scoped = ConfigProvider.nested(provider, "APP") + * const node = Effect.runSync(scoped.load(["HOST"])) + * node?.value // => "localhost" * ``` * * @see {@link mapInput} – for arbitrary path transformations @@ -582,13 +624,7 @@ export const nested: { 2, (self: ConfigProvider, prefix: string | Path): ConfigProvider => { const path = typeof prefix === "string" ? [prefix] : prefix - const state = self.state - switch (state._tag) { - case "Source": - return makeSource(state.get, flow(state.transform, (input) => [...path, ...input])) - case "OrElse": - return makeOrElse(nested(state.first, path), nested(state.second, path)) - } + return mapInput(self, (input) => [...path, ...input]) } ) @@ -607,7 +643,7 @@ export const nested: { * * **Example** (Reading config from a JSON object) * - * ```ts + * ```ts import.meta.vitest * import { Config, ConfigProvider, Effect, Layer } from "effect" * * const TestLayer = ConfigProvider.layer( @@ -619,7 +655,7 @@ export const nested: { * return port * }) * - * // Effect.runSync(Effect.provide(program, TestLayer)) // 8080 + * Effect.runSync(Effect.provide(program, TestLayer)) // => 8080 * ``` * * @see {@link layerAdd} – add a provider without replacing the existing one @@ -645,13 +681,13 @@ export const layer = ( * **Details** * * By default, the new provider acts as a fallback and is consulted only when - * the current provider returns `undefined`. Set `asPrimary: true` to make the - * new provider the primary source, with the existing one as fallback. + * the current provider returns `undefined`. Set `asPrimary: true` to make + * the new provider the primary source, with the existing one as fallback. * * **Example** (Adding default values) * - * ```ts - * import { ConfigProvider } from "effect" + * ```ts import.meta.vitest + * import { Config, ConfigProvider, Effect, Layer } from "effect" * * const defaults = ConfigProvider.fromUnknown({ * HOST: "localhost", @@ -660,6 +696,11 @@ export const layer = ( * * // The current env provider is tried first; `defaults` is the fallback * const DefaultsLayer = ConfigProvider.layerAdd(defaults) + * const BaseLayer = ConfigProvider.layer(ConfigProvider.fromUnknown({})) + * const program = Config.string("HOST") + * + * const layer = Layer.provide(DefaultsLayer, BaseLayer) + * Effect.runSync(Effect.provide(program, layer)) // => "localhost" * ``` * * @see {@link layer} – replace the provider entirely @@ -694,15 +735,25 @@ export const layerAdd = ( * **Details** * * Path traversal follows standard JS rules: string segments index into object - * keys, numeric segments index into arrays. Returns `undefined` for any path - * that cannot be resolved. Never fails with `SourceError`. + * keys, numeric segments index into arrays. Returns `undefined` for any + * path that cannot be resolved. Never fails with `SourceError`. * * Primitive values (`number`, `boolean`, `bigint`) are stringified via * `String(...)`. * + * Literal empty strings are treated as missing values when loaded as values by + * default. Pass `{ preserveEmptyStrings: true }` to keep empty strings as + * explicit values. + * + * **Gotchas** + * + * Object keys and array lengths reflect the original input shape. A leaf value + * of `""` is treated as missing when that leaf is loaded, but the parent + * container still reports its original keys or length. + * * **Example** (Providing config from a plain object) * - * ```ts + * ```ts import.meta.vitest * import { Config, ConfigProvider, Effect } from "effect" * * const provider = ConfigProvider.fromUnknown({ @@ -716,20 +767,23 @@ export const layerAdd = ( * provider.pipe(ConfigProvider.nested("database")) * ) * - * // Effect.runSync(host) // "localhost" + * Effect.runSync(host) // => "localhost" * ``` * * @see {@link fromEnv} – for environment variables * @see {@link make} – for custom backing stores * - * @category ConfigProviders + * @category constructors * @since 4.0.0 */ -export function fromUnknown(root: unknown): ConfigProvider { - return make((path) => Effect.succeed(nodeAtJson(root, path))) +export function fromUnknown(root: unknown, options?: { + readonly preserveEmptyStrings?: boolean | undefined +}): ConfigProvider { + const preserveEmptyStrings = options?.preserveEmptyStrings === true + return make((path) => Effect.succeed(nodeAtJson(root, path, preserveEmptyStrings))) } -function nodeAtJson(root: unknown, path: Path): Node | undefined { +function nodeAtJson(root: unknown, path: Path, preserveEmptyStrings: boolean): Node | undefined { let cur: unknown = root for (const seg of path) { @@ -752,12 +806,12 @@ function nodeAtJson(root: unknown, path: Path): Node | undefined { return undefined } - return describeUnknown(cur) + return describeUnknown(cur, preserveEmptyStrings) } -function describeUnknown(u: unknown): Node | undefined { +function describeUnknown(u: unknown, preserveEmptyStrings: boolean): Node | undefined { if (u === undefined || u === null) return undefined - if (typeof u === "string") return makeValue(u) + if (typeof u === "string") return stringNode(u, preserveEmptyStrings) if (typeof u === "number" || typeof u === "boolean" || typeof u === "bigint") { return makeValue(String(u)) } @@ -769,14 +823,59 @@ function describeUnknown(u: unknown): Node | undefined { return makeValue(format(u)) } +function stringNode(value: string, preserveEmptyStrings: boolean): Node | undefined { + const normalized = emptyStringAsMissing(value, preserveEmptyStrings) + return normalized === undefined ? undefined : makeValue(normalized) +} + +function emptyStringAsMissing(value: string | undefined, preserveEmptyStrings: boolean): string | undefined { + return value === "" && !preserveEmptyStrings ? undefined : value +} + +/** + * Creates a `ConfigProvider` backed by an explicit environment record. + * + * **When to use** + * + * Use when a restricted runtime cannot evaluate the automatic environment + * detection performed by {@link fromEnv}, or whenever the environment record + * must be supplied explicitly. + * + * **Details** + * + * `undefined` values are ignored. Path lookup and child discovery otherwise + * use the same environment-variable semantics as {@link fromEnv}. + * + * Environment variable names are captured at construction time to establish + * record keys and array lengths. The supplied record remains live for value + * lookups, so updates to known paths are observed by later loads. Keys added + * after construction can be loaded directly, but do not appear in captured + * parent record keys or array lengths. + * + * Literal empty strings are treated as missing values by default. Pass + * `{ preserveEmptyStrings: true }` to keep empty strings as explicit values. + * + * @see {@link fromEnv} – automatically reads the runtime environment + * + * @category constructors + * @since 4.0.0 + */ +export function fromEnvRecord( + env: Record, + options?: { readonly preserveEmptyStrings?: boolean | undefined } +): ConfigProvider { + const preserveEmptyStrings = options?.preserveEmptyStrings === true + const trie = buildEnvTrie(env) + return make((path) => Effect.succeed(nodeAtEnv(trie, env, path, preserveEmptyStrings))) +} + /** * Creates a `ConfigProvider` backed by environment variables. * * **When to use** * * Use to read configuration from `process.env`, which is the default when no - * provider is explicitly set, or pass a custom env record for testing or - * non-Node runtimes. + * provider is explicitly set, or pass a custom env record for testing. * * **Details** * @@ -790,11 +889,16 @@ function describeUnknown(u: unknown): Node | undefined { * The default environment merges `process.env` and `import.meta.env` (when * available). Override by passing `{ env: { ... } }`. * + * Literal empty strings are treated as missing values when loaded as values by + * default. Pass `{ preserveEmptyStrings: true }` to keep empty strings as + * explicit values. Child discovery still reflects the environment variable + * names present in the source. + * * Never fails with `SourceError` — all lookups are synchronous. * * **Example** (Reading from a custom env record) * - * ```ts + * ```ts import.meta.vitest * import { Config, ConfigProvider, Effect } from "effect" * * const provider = ConfigProvider.fromEnv({ @@ -808,33 +912,35 @@ function describeUnknown(u: unknown): Node | undefined { * provider.pipe(ConfigProvider.nested("DATABASE")) * ) * - * // Effect.runSync(host) // "localhost" + * Effect.runSync(host) // => "localhost" * ``` * * @see {@link fromUnknown} – for JSON objects + * @see {@link fromEnvRecord} – for explicit records in restricted runtimes * @see {@link constantCase} – bridge camelCase keys to SCREAMING_SNAKE_CASE * - * @category ConfigProviders + * @category constructors * @since 2.0.0 */ -export function fromEnv(options?: { readonly env?: Record | undefined }): ConfigProvider { - const env = options?.env ?? { - ...globalThis?.process?.env, +export function fromEnv(options?: { + readonly env?: Record | undefined + readonly preserveEmptyStrings?: boolean | undefined +}): ConfigProvider { + const env: Record = options?.env ?? { + ...(globalThis as { + readonly process?: { readonly env?: Record } + }).process?.env, ...(import.meta as any)?.env } - - const trie = buildEnvTrie(env) - - return make((path) => Effect.succeed(nodeAtEnv(trie, env, path))) + return fromEnvRecord(env, { preserveEmptyStrings: options?.preserveEmptyStrings }) } type EnvTrieNode = { - value?: string children?: Record } function buildEnvTrie(env: Record): EnvTrieNode { - const root: EnvTrieNode = {} + const trie: EnvTrieNode = {} for (const [name, value] of Object.entries(env)) { if (value === undefined) continue @@ -842,24 +948,26 @@ function buildEnvTrie(env: Record): EnvTrieNode { // Split on "_" and keep empty segments (no special handling for "__") const segments = name.split("_") - let node = root + let node = trie for (const seg of segments) { - node.children ??= {} - node = node.children[seg] ??= {} + const children = node.children ??= Object.create(null) + node = children[seg] ??= {} } - - // co-located value at this node - node.value = value } - return root + return trie } const NUMERIC_INDEX = /^(0|[1-9][0-9]*)$/ -function nodeAtEnv(trie: EnvTrieNode, env: Record, path: Path): Node | undefined { +function nodeAtEnv( + trie: EnvTrieNode, + env: Record, + path: Path, + preserveEmptyStrings: boolean +): Node | undefined { const key = path.map(String).join("_") - const leafValue = env[key] + const leafValue = emptyStringAsMissing(Object.hasOwn(env, key) ? env[key] : undefined, preserveEmptyStrings) const trieNode = trieNodeAt(trie, path) const children = trieNode?.children ? Object.keys(trieNode.children) : [] @@ -903,14 +1011,19 @@ function trieNodeAt(root: EnvTrieNode, path: Path): EnvTrieNode | undefined { * and escaped newlines. Variable expansion (for example, `${VAR}`) is disabled * by default; enable with `{ expandVariables: true }`. * + * Literal empty strings are treated as missing values when loaded as values by + * default. Pass `{ preserveEmptyStrings: true }` to keep empty strings as + * explicit values. Child discovery still reflects the keys present in the + * parsed `.env` source. + * * Parsing is based on the `dotenv` / `dotenv-expand` algorithm. * - * Internally delegates to {@link fromEnv} with the parsed key-value pairs. + * Internally delegates to {@link fromEnvRecord} with the parsed key-value pairs. * * **Example** (Parsing .env contents) * - * ```ts - * import { ConfigProvider } from "effect" + * ```ts import.meta.vitest + * import { ConfigProvider, Effect } from "effect" * * const contents = ` * HOST=localhost @@ -919,29 +1032,33 @@ function trieNodeAt(root: EnvTrieNode, path: Path): EnvTrieNode | undefined { * ` * * const provider = ConfigProvider.fromDotEnvContents(contents) + * const port = Effect.runSync(provider.load(["PORT"])) + * port?.value // => "3000" * ``` * * @see {@link fromDotEnv} – loads a `.env` file from disk + * @see {@link fromEnvRecord} – for explicit environment records * @see {@link fromEnv} – for raw environment variable access * - * @category ConfigProviders + * @category constructors * @since 4.0.0 */ export function fromDotEnvContents(lines: string, options?: { readonly expandVariables?: boolean | undefined + readonly preserveEmptyStrings?: boolean | undefined }): ConfigProvider { let env = parseDotEnvContents(lines) if (options?.expandVariables) { env = dotEnvExpand(env) } - return fromEnv({ env }) + return fromEnvRecord(env, { preserveEmptyStrings: options?.preserveEmptyStrings }) } const DOT_ENV_LINE = /(?:^|^)\s*(?:export\s+)?([\w.-]+)(?:\s*=\s*?|:\s+?)(\s*'(?:\\'|[^'])*'|\s*"(?:\\"|[^"])*"|\s*`(?:\\`|[^`])*`|[^#\r\n]+)?\s*(?:#.*)?(?:$|$)/mg function parseDotEnvContents(lines: string): Record { - const obj: Record = {} + const obj: Record = Object.create(null) // Convert line breaks to same format lines = lines.replace(/\r\n?/gm, "\n") @@ -976,9 +1093,9 @@ function parseDotEnvContents(lines: string): Record { } function dotEnvExpand(parsed: Record): Record { - const newParsed: Record = {} + const newParsed: Record = Object.create(null) - for (const configKey in parsed) { + for (const configKey of Object.keys(parsed)) { // resolve escape sequences newParsed[configKey] = interpolate(parsed[configKey], parsed).replace(/\\\$/g, "$") } @@ -1014,9 +1131,12 @@ function interpolate(envValue: string, parsed: Record): string { if (match !== null) { const [_, group, variableName, defaultValue] = match + const value = Object.hasOwn(parsed, variableName) && parsed[variableName] !== "" + ? parsed[variableName] + : defaultValue ?? "" return interpolate( - envValue.replace(group, defaultValue || parsed[variableName] || ""), + envValue.replace(group, value), parsed ) } @@ -1041,19 +1161,35 @@ function searchLast(str: string, rgx: RegExp): number { * * Requires `FileSystem` in the Effect context. Defaults to reading `".env"` in * the current directory; override with `{ path: "/custom/.env" }`. + * Variable expansion (for example, `${VAR}`) is disabled by default; enable + * with `{ expandVariables: true }`. + * + * Literal empty strings are treated as missing values when loaded as values by + * default. Pass `{ preserveEmptyStrings: true }` to keep empty strings as + * explicit values. Child discovery still reflects the keys present in the + * parsed `.env` source. * * Returns an `Effect` that resolves to a `ConfigProvider`. Fails with a * `PlatformError` if the file cannot be read. * * **Example** (Loading a .env file) * - * ```ts - * import { ConfigProvider, Effect } from "effect" + * ```ts import.meta.vitest + * import { ConfigProvider, Effect, FileSystem } from "effect" + * + * const fileSystem = FileSystem.makeNoop({ + * readFileString: () => Effect.succeed("HOST=localhost") + * }) * * const program = Effect.gen(function*() { * const provider = yield* ConfigProvider.fromDotEnv() - * return provider + * return yield* provider.load(["HOST"]) * }) + * + * const node = await Effect.runPromise( + * Effect.provideService(program, FileSystem.FileSystem, fileSystem) + * ) + * node?.value // => "localhost" * ``` * * @see {@link fromDotEnvContents} – parse a `.env` string directly @@ -1065,11 +1201,12 @@ function searchLast(str: string, rgx: RegExp): number { export const fromDotEnv: (options?: { readonly path?: string | undefined readonly expandVariables?: boolean | undefined + readonly preserveEmptyStrings?: boolean | undefined }) => Effect.Effect = Effect.fnUntraced( function*(options) { const fs = yield* FileSystem.FileSystem const content = yield* fs.readFileString(options?.path ?? ".env") - return fromEnv({ env: parseDotEnvContents(content) }) + return fromDotEnvContents(content, options) } ) @@ -1084,36 +1221,57 @@ export const fromDotEnv: (options?: { * * **Details** * - * Resolution tries a regular file first and returns a `Value` node with - * trimmed file contents. If the file read fails, it tries a directory and - * returns a `Record` node with immediate child names as keys. If both fail with - * `NotFound`, it returns `undefined`. Other platform failures return + * Resolution tries a regular file first and returns a `Value` node for + * non-empty trimmed file contents. If the file read fails, it tries a directory + * and returns a `Record` node with immediate child names as keys. If both fail + * with `NotFound`, it returns `undefined`. Other platform failures return * `SourceError`. * * Requires `Path` and `FileSystem` in the Effect context. Defaults to root * path `/`; override with `{ rootPath: "/etc/config" }`. * + * Literal empty strings are treated as missing values by default after file + * contents are trimmed. Pass `{ preserveEmptyStrings: true }` to keep empty + * strings as explicit values. Directory listings still reflect the file names + * present on disk. + * * **Example** (Reading config from a directory) * - * ```ts - * import { ConfigProvider, Effect } from "effect" + * ```ts import.meta.vitest + * import { ConfigProvider, Effect, FileSystem, Path } from "effect" + * + * const fileSystem = FileSystem.makeNoop({ + * readFileString: (path) => + * path === "/etc/myapp/host" + * ? Effect.succeed("localhost") + * : Effect.die("unexpected path") + * }) * * const program = Effect.gen(function*() { * const provider = yield* ConfigProvider.fromDir({ * rootPath: "/etc/myapp" * }) - * return provider + * return yield* provider.load(["host"]) * }) + * + * const node = await Effect.runPromise( + * program.pipe( + * Effect.provide(Path.layer), + * Effect.provideService(FileSystem.FileSystem, fileSystem) + * ) + * ) + * node?.value // => "localhost" * ``` * * @see {@link fromEnv} – for environment variables * @see {@link fromDotEnv} – for `.env` files * - * @category ConfigProviders + * @category constructors * @since 4.0.0 */ export const fromDir: (options?: { readonly rootPath?: string | undefined + readonly preserveEmptyStrings?: boolean | undefined }) => Effect.Effect< ConfigProvider, never, @@ -1122,13 +1280,14 @@ export const fromDir: (options?: { const platformPath = yield* Path_.Path const fs = yield* FileSystem.FileSystem const rootPath = options?.rootPath ?? "/" + const preserveEmptyStrings = options?.preserveEmptyStrings === true return make((path) => { const fullPath = platformPath.join(rootPath, ...path.map(String)) // Try reading as a *file* const asFile = fs.readFileString(fullPath).pipe( - Effect.map((content) => makeValue(content.trim())) + Effect.map((content) => stringNode(content.trim(), preserveEmptyStrings)) ) // If not a file, try reading as a *directory* diff --git a/repos/effect/packages/effect/src/Console.ts b/repos/effect/packages/effect/src/Console.ts index 72f8bcf7fd..83264a74b4 100644 --- a/repos/effect/packages/effect/src/Console.ts +++ b/repos/effect/packages/effect/src/Console.ts @@ -19,7 +19,7 @@ import type { Scope } from "./Scope.ts" /** * Represents a console interface for logging, debugging, timing, and grouping output. * - * @category models + * @category services * @since 2.0.0 */ export interface Console { @@ -58,19 +58,26 @@ export interface Console { * * **Example** (Accessing the current console) * - * ```ts + * ```ts import.meta.vitest * import { Console, Effect } from "effect" * + * const messages: Array = [] + * const testConsole: Console.Console = Object.assign(Object.create(console), { + * log: (...args: ReadonlyArray) => messages.push(...args) + * }) * const program = Console.consoleWith((console) => * Effect.sync(() => { * console.log("Hello from current console!") * }) * ) + * + * Effect.runSync(Effect.provideService(program, Console.Console, testConsole)) + * messages // => ["Hello from current console!"] * ``` * * @see {@link consoleWith} for using the current console service inside an effect * - * @category references + * @category services * @since 2.0.0 */ export const Console: Context.Reference = effect.ConsoleRef @@ -80,15 +87,23 @@ export const Console: Context.Reference = effect.ConsoleRef * * **Example** (Accessing the current console service) * - * ```ts + * ```ts import.meta.vitest * import { Console, Effect } from "effect" * + * const messages: Array = [] + * const testConsole: Console.Console = Object.assign(Object.create(console), { + * log: (...args: ReadonlyArray) => messages.push(...args), + * error: (...args: ReadonlyArray) => messages.push(...args) + * }) * const program = Console.consoleWith((console) => * Effect.sync(() => { * console.log("Hello, world!") * console.error("This is an error message") * }) * ) + * + * Effect.runSync(Effect.provideService(program, Console.Console, testConsole)) + * messages // => ["Hello, world!", "This is an error message"] * ``` * * @category constructors @@ -102,13 +117,22 @@ export const consoleWith = (f: (console: Console) => Effect.Effect = [] + * const testConsole: Console.Console = Object.assign(Object.create(console), { + * assert: (condition: boolean, ...args: ReadonlyArray) => { + * if (!condition) errors.push(...args) + * } + * }) * const program = Effect.gen(function*() { * yield* Console.assert(2 + 2 === 4, "Math is working correctly") * yield* Console.assert(2 + 2 === 5, "This will be logged as an error") * }) + * + * Effect.runSync(Effect.provideService(program, Console.Console, testConsole)) + * errors // => ["This will be logged as an error"] * ``` * * @category accessors @@ -136,14 +160,22 @@ export const assert = (condition: boolean, ...args: ReadonlyArray): Effect. * * **Example** (Clearing console output) * - * ```ts + * ```ts import.meta.vitest * import { Console, Effect } from "effect" * + * const operations: Array = [] + * const testConsole: Console.Console = Object.assign(Object.create(console), { + * log: (message: string) => operations.push(`log:${message}`), + * clear: () => operations.push("clear") + * }) * const program = Effect.gen(function*() { * yield* Console.log("This will be cleared") * yield* Console.clear * yield* Console.log("This appears after clearing") * }) + * + * Effect.runSync(Effect.provideService(program, Console.Console, testConsole)) + * operations // => ["log:This will be cleared", "clear", "log:This appears after clearing"] * ``` * * @category accessors @@ -160,14 +192,26 @@ export const clear: Effect.Effect = consoleWith((console) => * * **Example** (Counting repeated calls) * - * ```ts + * ```ts import.meta.vitest * import { Console, Effect } from "effect" * + * const counters = new Map() + * const messages: Array = [] + * const testConsole: Console.Console = Object.assign(Object.create(console), { + * count: (label = "default") => { + * const count = (counters.get(label) ?? 0) + 1 + * counters.set(label, count) + * messages.push(`${label}: ${count}`) + * } + * }) * const program = Effect.gen(function*() { * yield* Console.count("my-counter") - * yield* Console.count("my-counter") // Will show: my-counter: 2 - * yield* Console.count() // Default counter + * yield* Console.count("my-counter") + * yield* Console.count() * }) + * + * Effect.runSync(Effect.provideService(program, Console.Console, testConsole)) + * messages // => ["my-counter: 1", "my-counter: 2", "default: 1"] * ``` * * @category accessors @@ -185,15 +229,28 @@ export const count = (label?: string): Effect.Effect => * * **Example** (Resetting a counter) * - * ```ts + * ```ts import.meta.vitest * import { Console, Effect } from "effect" * + * const counters = new Map() + * const messages: Array = [] + * const testConsole: Console.Console = Object.assign(Object.create(console), { + * count: (label = "default") => { + * const count = (counters.get(label) ?? 0) + 1 + * counters.set(label, count) + * messages.push(`${label}: ${count}`) + * }, + * countReset: (label = "default") => counters.set(label, 0) + * }) * const program = Effect.gen(function*() { * yield* Console.count("my-counter") - * yield* Console.count("my-counter") // Will show: my-counter: 2 + * yield* Console.count("my-counter") * yield* Console.countReset("my-counter") - * yield* Console.count("my-counter") // Will show: my-counter: 1 + * yield* Console.count("my-counter") * }) + * + * Effect.runSync(Effect.provideService(program, Console.Console, testConsole)) + * messages // => ["my-counter: 1", "my-counter: 2", "my-counter: 1"] * ``` * * @category accessors @@ -217,13 +274,20 @@ export const countReset = (label?: string): Effect.Effect => * * **Example** (Writing debug messages) * - * ```ts + * ```ts import.meta.vitest * import { Console, Effect } from "effect" * + * const messages: Array> = [] + * const testConsole: Console.Console = Object.assign(Object.create(console), { + * debug: (...args: ReadonlyArray) => messages.push(args) + * }) * const program = Effect.gen(function*() { * yield* Console.debug("Debug info:", { userId: 123, action: "login" }) * yield* Console.debug("Processing step", 1, "of", 5) * }) + * + * Effect.runSync(Effect.provideService(program, Console.Console, testConsole)) + * messages // => [["Debug info:", { userId: 123, action: "login" }], ["Processing step", 1, "of", 5]] * ``` * * @category accessors @@ -241,14 +305,25 @@ export const debug = (...args: ReadonlyArray): Effect.Effect => * * **Example** (Inspecting an object) * - * ```ts + * ```ts import.meta.vitest * import { Console, Effect } from "effect" * + * const inspected: Array = [] + * const testConsole: Console.Console = Object.assign(Object.create(console), { + * dir: (item: unknown, options?: unknown) => inspected.push([item, options]) + * }) * const program = Effect.gen(function*() { * const obj = { name: "John", age: 30, nested: { city: "New York" } } * yield* Console.dir(obj) - * yield* Console.dir(obj, { depth: 2, colors: true }) + * yield* Console.dir(obj, { depth: 2 }) * }) + * + * Effect.runSync(Effect.provideService(program, Console.Console, testConsole)) + * const expected = [ + * [{ name: "John", age: 30, nested: { city: "New York" } }, undefined], + * [{ name: "John", age: 30, nested: { city: "New York" } }, { depth: 2 }] + * ] + * inspected // => expected * ``` * * @category accessors @@ -266,15 +341,19 @@ export const dir = (item: any, options?: any): Effect.Effect => * * **Example** (Inspecting XML-like data) * - * ```ts + * ```ts import.meta.vitest * import { Console, Effect } from "effect" * + * const messages: Array = [] + * const testConsole: Console.Console = Object.assign(Object.create(console), { + * dirxml: (...args: ReadonlyArray) => messages.push(...args) + * }) * const program = Effect.gen(function*() { * yield* Console.dirxml("Ada") * }) * - * Effect.runSync(program) - * // Ada + * Effect.runSync(Effect.provideService(program, Console.Console, testConsole)) + * messages // => ["Ada"] * ``` * * @category accessors @@ -293,9 +372,13 @@ export const dirxml = (...args: ReadonlyArray): Effect.Effect => * * **Example** (Writing error messages) * - * ```ts + * ```ts import.meta.vitest * import { Console, Effect } from "effect" * + * const messages: Array> = [] + * const testConsole: Console.Console = Object.assign(Object.create(console), { + * error: (...args: ReadonlyArray) => messages.push(args) + * }) * const program = Effect.gen(function*() { * yield* Console.error("Something went wrong!") * yield* Console.error("Error details:", { @@ -303,6 +386,13 @@ export const dirxml = (...args: ReadonlyArray): Effect.Effect => * message: "Internal Server Error" * }) * }) + * + * Effect.runSync(Effect.provideService(program, Console.Console, testConsole)) + * const expected = [ + * ["Something went wrong!"], + * ["Error details:", { code: 500, message: "Internal Server Error" }] + * ] + * messages // => expected * ``` * * @category accessors @@ -320,9 +410,15 @@ export const error = (...args: ReadonlyArray): Effect.Effect => * * **Example** (Grouping scoped output) * - * ```ts + * ```ts import.meta.vitest * import { Console, Effect } from "effect" * + * const operations: Array = [] + * const testConsole: Console.Console = Object.assign(Object.create(console), { + * group: (label?: string) => operations.push(`group:${label}`), + * groupEnd: () => operations.push("groupEnd"), + * log: (message: string) => operations.push(`log:${message}`) + * }) * const program = Effect.gen(function*() { * yield* Effect.scoped( * Effect.gen(function*() { @@ -333,6 +429,16 @@ export const error = (...args: ReadonlyArray): Effect.Effect => * }) * ) * }) + * + * Effect.runSync(Effect.provideService(program, Console.Console, testConsole)) + * const expected = [ + * "group:User Processing", + * "log:Loading user data...", + * "log:Validating user...", + * "log:User processed successfully", + * "groupEnd" + * ] + * operations // => expected * ``` * * @category accessors @@ -363,9 +469,13 @@ export const group = ( * * **Example** (Writing informational messages) * - * ```ts + * ```ts import.meta.vitest * import { Console, Effect } from "effect" * + * const messages: Array> = [] + * const testConsole: Console.Console = Object.assign(Object.create(console), { + * info: (...args: ReadonlyArray) => messages.push(args) + * }) * const program = Effect.gen(function*() { * yield* Console.info("Application started successfully") * yield* Console.info("Server configuration:", { @@ -373,6 +483,13 @@ export const group = ( * env: "development" * }) * }) + * + * Effect.runSync(Effect.provideService(program, Console.Console, testConsole)) + * const expected = [ + * ["Application started successfully"], + * ["Server configuration:", { port: 3000, env: "development" }] + * ] + * messages // => expected * ``` * * @category accessors @@ -390,14 +507,26 @@ export const info = (...args: ReadonlyArray): Effect.Effect => * * **Example** (Writing log messages) * - * ```ts + * ```ts import.meta.vitest * import { Console, Effect } from "effect" * + * const messages: Array> = [] + * const testConsole: Console.Console = Object.assign(Object.create(console), { + * log: (...args: ReadonlyArray) => messages.push(args) + * }) * const program = Effect.gen(function*() { * yield* Console.log("Hello, world!") * yield* Console.log("User data:", { name: "John", age: 30 }) * yield* Console.log("Processing", 42, "items") * }) + * + * Effect.runSync(Effect.provideService(program, Console.Console, testConsole)) + * const expected = [ + * ["Hello, world!"], + * ["User data:", { name: "John", age: 30 }], + * ["Processing", 42, "items"] + * ] + * messages // => expected * ``` * * @category accessors @@ -415,9 +544,16 @@ export const log = (...args: ReadonlyArray): Effect.Effect => * * **Example** (Displaying tabular data) * - * ```ts + * ```ts import.meta.vitest * import { Console, Effect } from "effect" * + * const calls: Array = [] + * const testConsole: Console.Console = Object.assign(Object.create(console), { + * table: (data: ReadonlyArray, properties?: ReadonlyArray) => { + * calls.push({ rows: data.length, properties }) + * } + * }) + * * const program = Effect.gen(function*() { * const users = [ * { name: "John", age: 30, city: "New York" }, @@ -427,6 +563,9 @@ export const log = (...args: ReadonlyArray): Effect.Effect => * yield* Console.table(users) * yield* Console.table(users, ["name", "age"]) // Only show specific columns * }) + * + * Effect.runSync(Effect.provideService(program, Console.Console, testConsole)) + * calls // => [{ rows: 3, properties: undefined }, { rows: 3, properties: ["name", "age"] }] * ``` * * @category accessors @@ -444,19 +583,28 @@ export const table = (tabularData: any, properties?: ReadonlyArray): Eff * * **Example** (Timing scoped work) * - * ```ts + * ```ts import.meta.vitest * import { Console, Effect } from "effect" * + * const operations: Array = [] + * const testConsole: Console.Console = Object.assign(Object.create(console), { + * time: (label?: string) => operations.push(`start:${label}`), + * timeEnd: (label?: string) => operations.push(`end:${label}`), + * log: (message: string) => operations.push(`log:${message}`) + * }) + * * const program = Effect.gen(function*() { * yield* Effect.scoped( * Effect.gen(function*() { * yield* Console.time("operation-timer") - * yield* Effect.sleep("1 second") * yield* Console.log("Operation completed") * // Timer ends automatically when scope closes * }) * ) * }) + * + * Effect.runSync(Effect.provideService(program, Console.Console, testConsole)) + * operations // => ["start:operation-timer", "log:Operation completed", "end:operation-timer"] * ``` * * @category accessors @@ -480,20 +628,28 @@ export const time = (label?: string | undefined): Effect.Effect = [] + * const testConsole: Console.Console = Object.assign(Object.create(console), { + * time: (label?: string) => operations.push(["start", label]), + * timeLog: (label?: string, ...args: ReadonlyArray) => operations.push(["log", label, ...args]), + * timeEnd: (label?: string) => operations.push(["end", label]) + * }) + * * const program = Effect.gen(function*() { * yield* Effect.scoped( * Effect.gen(function*() { * yield* Console.time("long-operation") - * yield* Effect.sleep("500 millis") * yield* Console.timeLog("long-operation", "Halfway done") - * yield* Effect.sleep("500 millis") * // Timer ends when scope closes * }) * ) * }) + * + * Effect.runSync(Effect.provideService(program, Console.Console, testConsole)) + * operations // => [["start", "long-operation"], ["log", "long-operation", "Halfway done"], ["end", "long-operation"]] * ``` * * @category accessors @@ -512,13 +668,21 @@ export const timeLog = (label?: string, ...args: ReadonlyArray): Effect.Eff * * **Example** (Writing stack traces) * - * ```ts + * ```ts import.meta.vitest * import { Console, Effect } from "effect" * + * const traces: Array> = [] + * const testConsole: Console.Console = Object.assign(Object.create(console), { + * trace: (...args: ReadonlyArray) => traces.push(args) + * }) + * * const program = Effect.gen(function*() { * yield* Console.trace("Debug trace point") * yield* Console.trace("Function call:", { functionName: "processData" }) * }) + * + * Effect.runSync(Effect.provideService(program, Console.Console, testConsole)) + * traces // => [["Debug trace point"], ["Function call:", { functionName: "processData" }]] * ``` * * @category accessors @@ -537,15 +701,26 @@ export const trace = (...args: ReadonlyArray): Effect.Effect => * * **Example** (Writing warning messages) * - * ```ts + * ```ts import.meta.vitest * import { Console, Effect } from "effect" * + * const messages: Array> = [] + * const testConsole: Console.Console = Object.assign(Object.create(console), { + * warn: (...args: ReadonlyArray) => messages.push(args) + * }) * const program = Effect.gen(function*() { * yield* Console.warn("This feature is deprecated") * yield* Console.warn("Performance warning:", { * slowQuery: "SELECT * FROM large_table" * }) * }) + * + * Effect.runSync(Effect.provideService(program, Console.Console, testConsole)) + * const expected = [ + * ["This feature is deprecated"], + * ["Performance warning:", { slowQuery: "SELECT * FROM large_table" }] + * ] + * messages // => expected * ``` * * @category accessors @@ -563,9 +738,15 @@ export const warn = (...args: ReadonlyArray): Effect.Effect => * * **Example** (Wrapping an effect in a group) * - * ```ts + * ```ts import.meta.vitest * import { Console, Effect } from "effect" * + * const operations: Array = [] + * const testConsole: Console.Console = Object.assign(Object.create(console), { + * group: (label?: string) => operations.push(`group:${label}`), + * groupEnd: () => operations.push("groupEnd"), + * log: (message: string) => operations.push(`log:${message}`) + * }) * const program = Effect.gen(function*() { * yield* Console.withGroup( * Effect.gen(function*() { @@ -576,6 +757,16 @@ export const warn = (...args: ReadonlyArray): Effect.Effect => * { label: "Processing Steps", collapsed: false } * ) * }) + * + * Effect.runSync(Effect.provideService(program, Console.Console, testConsole)) + * const expected = [ + * "group:Processing Steps", + * "log:Step 1: Initialize", + * "log:Step 2: Process", + * "log:Step 3: Complete", + * "groupEnd" + * ] + * operations // => expected * ``` * * @category accessors @@ -618,18 +809,27 @@ export const withGroup = dual< * * **Example** (Timing an effect) * - * ```ts + * ```ts import.meta.vitest * import { Console, Effect } from "effect" * + * const operations: Array = [] + * const testConsole: Console.Console = Object.assign(Object.create(console), { + * time: (label?: string) => operations.push(`start:${label}`), + * timeEnd: (label?: string) => operations.push(`end:${label}`), + * log: (message: string) => operations.push(`log:${message}`) + * }) + * * const program = Effect.gen(function*() { * yield* Console.withTime( * Effect.gen(function*() { - * yield* Effect.sleep("1 second") * yield* Console.log("Operation completed") * }), * "my-operation" * ) * }) + * + * Effect.runSync(Effect.provideService(program, Console.Console, testConsole)) + * operations // => ["start:my-operation", "log:Operation completed", "end:my-operation"] * ``` * * @category accessors diff --git a/repos/effect/packages/effect/src/Context.ts b/repos/effect/packages/effect/src/Context.ts index cfc8cd983a..631e2d1d7c 100644 --- a/repos/effect/packages/effect/src/Context.ts +++ b/repos/effect/packages/effect/src/Context.ts @@ -17,7 +17,6 @@ import { dual, type LazyArg } from "./Function.ts" import * as Hash from "./Hash.ts" import type { Inspectable } from "./Inspectable.ts" import { exitSucceed, PipeInspectableProto, withFiber } from "./internal/core.ts" -import { getStackTraceLimit, setStackTraceLimit } from "./internal/stackTraceLimit.ts" import * as Option from "./Option.ts" import type { Pipeable } from "./Pipeable.ts" import { hasProperty } from "./Predicate.ts" @@ -67,7 +66,6 @@ export interface Key extends Effect extends Effect extends Effect `Result: ${sql}` }) + * Context.get(context, Database).query("SELECT 1") // => "Result: SELECT 1" * ``` * - * @category models + * @category services * @since 4.0.0 */ export interface Service extends Key { @@ -118,7 +117,7 @@ export interface Service extends Key @@ -139,7 +138,7 @@ export declare namespace ServiceClass { * Runtime and type-level metadata carried by a class-style service key, * including its service type identifier, string key, and service shape. * - * @category models + * @category services * @since 4.0.0 */ export interface Shape { @@ -172,7 +171,7 @@ export declare namespace ServiceClass { * * **Example** (Creating service keys) * - * ```ts + * ```ts import.meta.vitest * import { Context } from "effect" * * // Create a simple service @@ -190,15 +189,23 @@ export declare namespace ServiceClass { * query: (sql) => `Result: ${sql}` * }) * const config = Context.make(Config, { port: 8080 }) + * Context.get(db, Database).query("SELECT 1") // => "Result: SELECT 1" + * Context.get(config, Config).port // => 8080 * ``` * * @see {@link Reference} for service keys with default values * - * @category constructors + * @category services * @since 4.0.0 */ export const Service: { - (key: string): Service + ( + key: string, + options?: { + /** @internal */ + readonly fiberCached?: boolean | undefined + } | undefined + ): Service (): < const Identifier extends string, E, @@ -207,7 +214,9 @@ export const Service: { >( id: Identifier, options?: { - readonly make: ((...args: Args) => Effect) | Effect | undefined + readonly make?: ((...args: Args) => Effect) | Effect | undefined + /** @internal */ + readonly fiberCached?: boolean | undefined } | undefined ) => & ServiceClass @@ -220,6 +229,8 @@ export const Service: { id: Identifier, options: { readonly make: Make + /** @internal */ + readonly fiberCached?: boolean | undefined } ) => & ServiceClass< @@ -231,36 +242,28 @@ export const Service: { > & { readonly make: Make } } = function() { - const prevLimit = getStackTraceLimit() - setStackTraceLimit(2) - const err = new Error() - setStackTraceLimit(prevLimit) function KeyClass() {} const self = KeyClass as any as Types.Mutable> Object.setPrototypeOf(self, ServiceProto) - // @effect-diagnostics-next-line floatingEffect:off - Object.defineProperty(self, "stack", { - get() { - return err.stack - } - }) - if (arguments.length > 0) { - self.key = arguments[0] - if (arguments[1]?.defaultValue) { - self[ReferenceTypeId] = ReferenceTypeId - self.defaultValue = arguments[1].defaultValue - } - return self - } - return function(key: string, options?: { + const init = (key: string, options?: { + readonly defaultValue?: any readonly make?: any - }) { + readonly fiberCached?: boolean + }) => { self.key = key + if (options?.defaultValue) { + self[ReferenceTypeId] = ReferenceTypeId + self.defaultValue = options.defaultValue + } if (options?.make) { ;(self as any).make = options.make } + if (options?.fiberCached) { + cacheKeys.add(key) + } return self } + return arguments.length > 0 ? init(arguments[0], arguments[1]) : init } as any const ServiceProto: any = { @@ -274,8 +277,7 @@ const ServiceProto: any = { toJSON(this: Service) { return { _id: "Service", - key: this.key, - stack: this.stack + key: this.key } }, of(this: void, self: Service): Service { @@ -295,6 +297,8 @@ const ServiceProto: any = { } } +const cacheKeys = new Set() + const ReferenceTypeId = "~effect/Context/Reference" as const /** @@ -308,21 +312,24 @@ const ReferenceTypeId = "~effect/Context/Reference" as const * * **Example** (Defining a reference with a default value) * - * ```ts + * ```ts import.meta.vitest * import { Context } from "effect" * * // Define a reference with a default value + * const messages: Array = [] * const LoggerRef: Context.Reference<{ log: (msg: string) => void }> = * Context.Reference("Logger", { - * defaultValue: () => ({ log: (msg: string) => console.log(msg) }) + * defaultValue: () => ({ log: (msg) => { messages.push(msg) } }) * }) * * // The reference can be used without explicit provision * const context = Context.empty() * const logger = Context.get(context, LoggerRef) // Uses default value + * logger.log("default logger") + * messages // => ["default logger"] * ``` * - * @category models + * @category services * @since 3.11.0 */ export interface Reference extends Service { @@ -337,7 +344,7 @@ export interface Reference extends Service { * * **Example** (Extracting service types) * - * ```ts + * ```ts import.meta.vitest * import { Context } from "effect" * * const Database = Context.Service<{ @@ -349,6 +356,8 @@ export interface Reference extends Service { * * // Extract identifier type from a key * type DatabaseId = Context.Service.Identifier + * + * Database.key // => "Database" * ``` * * @since 2.0.0 @@ -360,7 +369,7 @@ export declare namespace Service { * * **Example** (Typing any service key) * - * ```ts + * ```ts import.meta.vitest * import { Context } from "effect" * * // Any represents any possible service type @@ -368,9 +377,10 @@ export declare namespace Service { * Context.Service<{ log: (msg: string) => void }>("Logger"), * Context.Service<{ query: (sql: string) => string }>("Database") * ] + * services.map((service) => service.key) // => ["Logger", "Database"] * ``` * - * @category models + * @category utility types * @since 4.0.0 */ export type Any = Key | Key @@ -381,7 +391,7 @@ export declare namespace Service { * * **Example** (Extracting a service shape) * - * ```ts + * ```ts import.meta.vitest * import { Context } from "effect" * * const Database = Context.Service<{ query: (sql: string) => string }>( @@ -391,9 +401,10 @@ export declare namespace Service { * // Extract the service shape from the service * type DatabaseService = Context.Service.Shape * // DatabaseService is { query: (sql: string) => string } + * Database.key // => "Database" * ``` * - * @category models + * @category utility types * @since 4.0.0 */ export type Shape = T extends Key ? S : never @@ -404,7 +415,7 @@ export declare namespace Service { * * **Example** (Extracting a service identifier) * - * ```ts + * ```ts import.meta.vitest * import { Context } from "effect" * * const Database = Context.Service<{ query: (sql: string) => string }>( @@ -414,9 +425,10 @@ export declare namespace Service { * // Extract the identifier type from a key * type DatabaseId = Context.Service.Identifier * // DatabaseId is the identifier type + * Database.key // => "Database" * ``` * - * @category models + * @category utility types * @since 2.0.0 */ export type Identifier = T extends Key ? I : never @@ -435,7 +447,7 @@ const TypeId = "~effect/Context" as const * * **Example** (Creating a context with multiple services) * - * ```ts + * ```ts import.meta.vitest * import { Context } from "effect" * * // Create a context with multiple services @@ -444,10 +456,9 @@ const TypeId = "~effect/Context" as const * "Database" * ) * - * const context = Context.make(Logger, { - * log: (msg: string) => console.log(msg) - * }) + * const context = Context.make(Logger, { log: (_msg: string) => {} }) * .pipe(Context.add(Database, { query: (sql) => `Result: ${sql}` })) + * Context.get(context, Database).query("SELECT 1") // => "Result: SELECT 1" * ``` * * @category models @@ -458,7 +469,81 @@ export interface Context extends Equal.Equal, Pipeable, Inspectable readonly _Services: Types.Contravariant } readonly mapUnsafe: ReadonlyMap - mutable: boolean +} + +interface ContextImpl extends Context { + cacheRoot: ContextImpl | undefined + base: ReadonlyMap + baseHits: number + overlay: Overlay | undefined + depth: number + _flat: ReadonlyMap | undefined +} + +interface Overlay { + readonly key: string + readonly value: unknown + readonly parent: Overlay | undefined +} + +const MaxDepth = 8 +const FlattenAfterBaseHits = 8 + +const makeImpl = ( + cacheRoot: ContextImpl | undefined, + base: ReadonlyMap, + overlay: Overlay | undefined, + depth: number +): ContextImpl => { + const self: ContextImpl = Object.create(Proto) + self.cacheRoot = cacheRoot ?? self + self.base = base + self.overlay = overlay + self.depth = depth + self._flat = undefined + self.baseHits = 0 + return self +} + +const applyOverlays = (map: Map, overlay: Overlay | undefined): void => { + if (!overlay) return + applyOverlays(map, overlay.parent) + map.set(overlay.key, overlay.value) +} + +const flatten = (self: ContextImpl): ReadonlyMap => { + if (self._flat) return self._flat + if (!self.overlay) return self._flat = self.base + const map = new Map(self.base) + applyOverlays(map, self.overlay) + return self._flat = map +} + +const withFlat = (self: Context, f: (map: Map) => void): Context => { + const map = new Map(self.mapUnsafe) + f(map) + return makeUnsafe(map) +} + +// A private symbol so user code cannot forge a value that reads as absent +const notFound = Symbol() + +const lookup = (self: Context, key: string): unknown => { + const impl = self as ContextImpl + for (let overlay = impl.overlay; overlay; overlay = overlay.parent) { + if (overlay.key === key) return overlay.value + } + const value = impl.base.get(key) + // Misses must not advance the counter: reference-default lookups miss the + // base on every fiber cache refresh, which would flatten every short-lived + // request context and reintroduce the O(services) per-request cost + if (value === undefined && !impl.base.has(key)) return notFound + if (impl.overlay && ++impl.baseHits >= FlattenAfterBaseHits) { + impl.base = flatten(impl) + impl.overlay = undefined + impl.depth = 0 + } + return value } /** @@ -471,38 +556,41 @@ export interface Context extends Equal.Equal, Pipeable, Inspectable * * **Gotchas** * - * This is unsafe because later mutation of the provided map can affect the - * created `Context`. Prefer `empty`, `make`, `add`, or `merge` for normal - * Context construction. + * The provided map is retained without copying and must not be mutated after + * construction. Prefer `empty`, `make`, `add`, or `merge` for normal Context + * construction. * * **Example** (Creating a context from a map) * - * ```ts + * ```ts import.meta.vitest * import { Context } from "effect" * * // Create a context from a Map (unsafe) * const map = new Map([ - * ["Logger", { log: (msg: string) => console.log(msg) }] + * ["Logger", { log: (_msg: string) => {} }] * ]) * * const context = Context.makeUnsafe(map) + * context.mapUnsafe.size // => 1 * ``` * * @category constructors * @since 4.0.0 */ -export const makeUnsafe = (mapUnsafe: ReadonlyMap): Context => { - const self = Object.create(Proto) - self.mapUnsafe = mapUnsafe - self.mutable = false - return self -} +export const makeUnsafe = (mapUnsafe: ReadonlyMap): Context => + makeImpl(undefined, mapUnsafe, undefined, 0) -const Proto: Omit, "mapUnsafe" | "mutable"> = { +const Proto: Omit< + ContextImpl, + "cacheRoot" | "base" | "overlay" | "depth" | "_flat" | "baseHits" +> = { ...PipeInspectableProto, [TypeId]: { _Services: (_: never) => _ }, + get mapUnsafe() { + return flatten(this as any as ContextImpl) + }, toJSON(this: Context) { return { _id: "Context", @@ -510,17 +598,12 @@ const Proto: Omit, "mapUnsafe" | "mutable"> = { } }, [Equal.symbol](this: Context, that: unknown): boolean { - if ( - !isContext(that) - || this.mapUnsafe.size !== that.mapUnsafe.size - ) return false - for (const k of this.mapUnsafe.keys()) { - if ( - !that.mapUnsafe.has(k) || - !Equal.equals(this.mapUnsafe.get(k), that.mapUnsafe.get(k)) - ) { - return false - } + if (!isContext(that)) return false + const self = this.mapUnsafe + const other = that.mapUnsafe + if (self.size !== other.size) return false + for (const [key, value] of self) { + if (!other.has(key) || !Equal.equals(value, other.get(key))) return false } return true }, @@ -529,6 +612,12 @@ const Proto: Omit, "mapUnsafe" | "mutable"> = { } } +/** @internal */ +export const hasSameCache = ( + self: Context, + that: Context +): boolean => (self as ContextImpl).cacheRoot === (that as ContextImpl).cacheRoot + /** * Checks whether the provided argument is a `Context`. * @@ -549,11 +638,9 @@ const Proto: Omit, "mapUnsafe" | "mutable"> = { * * **Example** (Checking for contexts) * - * ```ts + * ```ts import.meta.vitest * import { Context } from "effect" - * import * as assert from "node:assert" - * - * assert.strictEqual(Context.isContext(Context.empty()), true) + * Context.isContext(Context.empty()) // => true * ``` * * @see {@link isKey} for checking service keys @@ -569,11 +656,9 @@ export const isContext = (u: unknown): u is Context => hasProperty(u, Typ * * **Example** (Checking for keys) * - * ```ts + * ```ts import.meta.vitest * import { Context } from "effect" - * import * as assert from "node:assert" - * - * assert.strictEqual(Context.isKey(Context.Service("Service")), true) + * Context.isKey(Context.Service("Service")) // => true * ``` * * @category guards @@ -586,33 +671,30 @@ export const isKey = (u: unknown): u is Key => hasProperty(u, ServiceT * * **Example** (Checking for references) * - * ```ts + * ```ts import.meta.vitest * import { Context } from "effect" - * import * as assert from "node:assert" * * const LoggerRef = Context.Reference("Logger", { - * defaultValue: () => ({ log: (msg: string) => console.log(msg) }) + * defaultValue: () => ({ log: (_msg: string) => {} }) * }) * - * assert.strictEqual(Context.isReference(LoggerRef), true) - * assert.strictEqual(Context.isReference(Context.Service("Key")), false) + * Context.isReference(LoggerRef) // => true + * Context.isReference(Context.Service("Key")) // => false * ``` * * @category guards * @since 3.11.0 */ -export const isReference = (u: unknown): u is Reference => hasProperty(u, ReferenceTypeId) +export const isReference = (u: Key): u is Reference => !!(u as Reference)[ReferenceTypeId] /** * Returns an empty `Context`. * * **Example** (Creating an empty context) * - * ```ts + * ```ts import.meta.vitest * import { Context } from "effect" - * import * as assert from "node:assert" - * - * assert.strictEqual(Context.isContext(Context.empty()), true) + * Context.empty().mapUnsafe.size // => 0 * ``` * * @category constructors @@ -626,15 +708,14 @@ const emptyContext = makeUnsafe(new Map()) * * **Example** (Creating a context with one service) * - * ```ts + * ```ts import.meta.vitest * import { Context } from "effect" - * import * as assert from "node:assert" * * const Port = Context.Service<{ PORT: number }>("Port") * * const context = Context.make(Port, { PORT: 8080 }) * - * assert.deepStrictEqual(Context.get(context, Port), { PORT: 8080 }) + * Context.get(context, Port).PORT // => 8080 * ``` * * @category constructors @@ -659,9 +740,8 @@ export const make = ( * * **Example** (Adding a service to a context) * - * ```ts + * ```ts import.meta.vitest * import { Context, pipe } from "effect" - * import * as assert from "node:assert" * * const Port = Context.Service<{ PORT: number }>("Port") * const Timeout = Context.Service<{ TIMEOUT: number }>("Timeout") @@ -673,13 +753,13 @@ export const make = ( * Context.add(Timeout, { TIMEOUT: 5000 }) * ) * - * assert.deepStrictEqual(Context.get(context, Port), { PORT: 8080 }) - * assert.deepStrictEqual(Context.get(context, Timeout), { TIMEOUT: 5000 }) + * const values = [Context.get(context, Port).PORT, Context.get(context, Timeout).TIMEOUT] + * values // => [8080, 5000] * ``` * * @see {@link addOrOmit} for adding or removing a service from an `Option` * - * @category adders + * @category combining * @since 2.0.0 */ export const add: { @@ -696,10 +776,24 @@ export const add: { self: Context, key: Key, service: Types.NoInfer -): Context => - withMapUnsafe(self, (map) => { +): Context => { + const impl = self as ContextImpl + const cacheRoot = cacheKeys.has(key.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.key, service) - })) + return makeImpl(cacheRoot, map, undefined, 0) + } + + return makeImpl( + cacheRoot, + impl.base, + { key: key.key, value: service, parent: impl.overlay }, + impl.depth + 1 + ) +}) /** * Adds or removes a service depending on an `Option`. @@ -715,7 +809,7 @@ export const add: { * * **Example** (Adding optional services) * - * ```ts + * ```ts import.meta.vitest * import { Context, Option } from "effect" * * const Port = Context.Service<{ PORT: number }>("Port") @@ -727,35 +821,33 @@ export const add: { * const withoutPort = withPort.pipe( * Context.addOrOmit(Port, Option.none()) * ) + * Context.getOption(withPort, Port) // => Option.some({ PORT: 8080 }) + * Context.getOption(withoutPort, Port) // => Option.none() * ``` * * @see {@link add} for always storing a service value * - * @category adders + * @category combining * @since 4.0.0 */ export const addOrOmit: { ( key: Key, service: Option.Option> - ): (self: Context) => Context + ): (self: Context) => Context> ( self: Context, key: Key, service: Option.Option> - ): Context + ): Context> } = dual(3, ( self: Context, key: Key, service: Option.Option> -): Context => - withMapUnsafe(self, (map) => { - if (service._tag === "None") { - map.delete(key.key) - } else { - map.set(key.key, service.value) - } - })) +): Context> => + service._tag === "None" + ? omit(key)(self) + : add(self, key, service.value) as any) /** * Gets the service for a key, or evaluates the fallback when a non-reference @@ -778,7 +870,7 @@ export const addOrOmit: { * * **Example** (Falling back for missing services) * - * ```ts + * ```ts import.meta.vitest * import { Context } from "effect" * * const Logger = Context.Service<{ log: (msg: string) => void }>("Logger") @@ -786,9 +878,7 @@ export const addOrOmit: { * "Database" * ) * - * const context = Context.make(Logger, { - * log: (msg: string) => console.log(msg) - * }) + * const context = Context.make(Logger, { log: (_msg: string) => {} }) * * const logger = Context.getOrElse(context, Logger, () => ({ log: () => {} })) * const database = Context.getOrElse( @@ -797,8 +887,8 @@ export const addOrOmit: { * () => ({ query: () => "fallback" }) * ) * - * console.log(logger === Context.get(context, Logger)) // true - * console.log(database.query("SELECT 1")) // "fallback" + * logger === Context.get(context, Logger) // => true + * database.query("SELECT 1") // => "fallback" * ``` * * @see {@link getOption} for returning `Option.none` when a non-reference key is missing @@ -810,9 +900,8 @@ export const getOrElse: { (key: Key, orElse: LazyArg): (self: Context) => S | B (self: Context, key: Key, orElse: LazyArg): S | B } = dual(3, (self: Context, key: Key, orElse: LazyArg): S | B => { - if (self.mapUnsafe.has(key.key)) { - return self.mapUnsafe.get(key.key)! as any - } + const value = lookup(self, key.key) + if (value !== notFound) return value as any return isReference(key) ? getDefaultValue(key) : orElse() }) @@ -840,9 +929,15 @@ export const getOrUndefined: { (self: Context, key: Key): S | undefined } = dual( 2, - (self: Context, key: Key): S | undefined => self.mapUnsafe.get(key.key) + (self: Context, key: Key): S | undefined => getOrUndefinedUnsafe(self, key.key) ) +/** @internal */ +export const getOrUndefinedUnsafe = (self: Context, key: string): A | undefined => { + const value = lookup(self, key) + return value === notFound ? undefined : value as A +} + /** * Gets the service for a key, throwing if an absent non-reference key cannot be * resolved. @@ -860,17 +955,16 @@ export const getOrUndefined: { * * **Example** (Getting services unsafely) * - * ```ts - * import { Context } from "effect" - * import * as assert from "node:assert" + * ```ts import.meta.vitest + * import { Context, Option } from "effect" * * const Port = Context.Service<{ PORT: number }>("Port") * const Timeout = Context.Service<{ TIMEOUT: number }>("Timeout") * * const context = Context.make(Port, { PORT: 8080 }) * - * assert.deepStrictEqual(Context.getUnsafe(context, Port), { PORT: 8080 }) - * assert.throws(() => Context.getUnsafe(context, Timeout)) + * Context.getUnsafe(context, Port).PORT // => 8080 + * Context.getOption(context, Timeout) // => Option.none() * ``` * * @see {@link get} for type-checked service access @@ -885,11 +979,12 @@ export const getUnsafe: { } = dual( 2, (self: Context, service: Key): S => { - if (!self.mapUnsafe.has(service.key)) { - if (ReferenceTypeId in service) return getDefaultValue(service as any) + const value = lookup(self, service.key) + if (value === notFound) { + if (isReference(service)) return getDefaultValue(service as any) throw serviceNotFoundError(service) } - return self.mapUnsafe.get(service.key)! as any + return value as any } ) @@ -903,9 +998,8 @@ export const getUnsafe: { * * **Example** (Getting a service from a context) * - * ```ts + * ```ts import.meta.vitest * import { Context, pipe } from "effect" - * import * as assert from "node:assert" * * const Port = Context.Service<{ PORT: number }>("Port") * const Timeout = Context.Service<{ TIMEOUT: number }>("Timeout") @@ -915,7 +1009,7 @@ export const getUnsafe: { * Context.add(Timeout, { TIMEOUT: 5000 }) * ) * - * assert.deepStrictEqual(Context.get(context, Timeout), { TIMEOUT: 5000 }) + * Context.get(context, Timeout).TIMEOUT // => 5000 * ``` * * @see {@link getOption} for optional service access @@ -929,54 +1023,6 @@ export const get: { (self: Context, service: Key): S } = getUnsafe -/** - * Gets the value for a `Context.Reference`, returning its cached default when - * the context does not contain an override. - * - * **When to use** - * - * Use when you need a `Context.Reference` value resolved from either a stored - * override or the reference's default value. - * - * **Details** - * - * Stored overrides take precedence. If no override is present, the reference's - * default value is computed lazily and cached on the reference itself. - * - * **Gotchas** - * - * Mutable default values can be shared across contexts unless an override is - * provided, because the default is cached on the `Context.Reference`. - * - * **Example** (Getting reference defaults unsafely) - * - * ```ts - * import { Context } from "effect" - * - * const LoggerRef = Context.Reference("Logger", { - * defaultValue: () => ({ log: (msg: string) => console.log(msg) }) - * }) - * - * const context = Context.empty() - * const logger = Context.getReferenceUnsafe(context, LoggerRef) - * - * console.log(typeof logger.log) // "function" - * ``` - * - * @see {@link getUnsafe} for unsafe access with any service key - * @see {@link get} for type-checked reference-aware access - * @see {@link getOption} for optional access to non-reference keys - * - * @category unsafe - * @since 4.0.0 - */ -export const getReferenceUnsafe = (self: Context, service: Reference): S => { - if (!self.mapUnsafe.has(service.key)) { - return getDefaultValue(service as any) - } - return self.mapUnsafe.get(service.key)! as any -} - const defaultValueCacheKey = "~effect/Context/defaultValue" as const const getDefaultValue = (ref: Reference) => { @@ -990,15 +1036,6 @@ const serviceNotFoundError = (service: Key) => { const error = new Error( `Service not found${service.key ? `: ${String(service.key)}` : ""}` ) - if (service.stack) { - const lines = service.stack.split("\n") - if (lines.length > 2) { - const afterAt = lines[2].match(/at (.*)/) - if (afterAt) { - error.message = error.message + ` (defined at ${afterAt[1]})` - } - } - } if (error.stack) { const lines = error.stack.split("\n") lines.splice(1, 3) @@ -1023,20 +1060,16 @@ const serviceNotFoundError = (service: Key) => { * * **Example** (Getting optional services) * - * ```ts + * ```ts import.meta.vitest * import { Context, Option } from "effect" - * import * as assert from "node:assert" * * const Port = Context.Service<{ PORT: number }>("Port") * const Timeout = Context.Service<{ TIMEOUT: number }>("Timeout") * * const context = Context.make(Port, { PORT: 8080 }) * - * assert.deepStrictEqual( - * Context.getOption(context, Port), - * Option.some({ PORT: 8080 }) - * ) - * assert.deepStrictEqual(Context.getOption(context, Timeout), Option.none()) + * Context.getOption(context, Port) // => Option.some({ PORT: 8080 }) + * Context.getOption(context, Timeout) // => Option.none() * ``` * * @see {@link getOrElse} for returning a fallback value directly @@ -1048,9 +1081,8 @@ export const getOption: { (service: Key): (self: Context) => Option.Option (self: Context, service: Key): Option.Option } = dual(2, (self: Context, service: Key): Option.Option => { - if (self.mapUnsafe.has(service.key)) { - return Option.some(self.mapUnsafe.get(service.key)! as any) - } + const value = lookup(self, service.key) + if (value !== notFound) return Option.some(value as any) return isReference(service) ? Option.some(getDefaultValue(service as any)) : Option.none() }) @@ -1068,9 +1100,8 @@ export const getOption: { * * **Example** (Merging two contexts) * - * ```ts + * ```ts import.meta.vitest * import { Context } from "effect" - * import * as assert from "node:assert" * * const Port = Context.Service<{ PORT: number }>("Port") * const Timeout = Context.Service<{ TIMEOUT: number }>("Timeout") @@ -1080,8 +1111,8 @@ export const getOption: { * * const context = Context.merge(firstContext, secondContext) * - * assert.deepStrictEqual(Context.get(context, Port), { PORT: 8080 }) - * assert.deepStrictEqual(Context.get(context, Timeout), { TIMEOUT: 5000 }) + * const values = [Context.get(context, Port).PORT, Context.get(context, Timeout).TIMEOUT] + * values // => [8080, 5000] * ``` * * @see {@link mergeAll} for merging more than two contexts at once @@ -1095,9 +1126,7 @@ export const merge: { } = dual(2, (self: Context, that: Context): Context => { if (self.mapUnsafe.size === 0) return that as any if (that.mapUnsafe.size === 0) return self as any - return withMapUnsafe(self, (map) => { - that.mapUnsafe.forEach((value, key) => map.set(key, value)) - }) + return withFlat(self, (map) => that.mapUnsafe.forEach((value, key) => map.set(key, value))) }) /** @@ -1114,9 +1143,8 @@ export const merge: { * * **Example** (Merging multiple contexts) * - * ```ts + * ```ts import.meta.vitest * import { Context } from "effect" - * import * as assert from "node:assert" * * const Port = Context.Service<{ PORT: number }>("Port") * const Timeout = Context.Service<{ TIMEOUT: number }>("Timeout") @@ -1132,9 +1160,7 @@ export const merge: { * thirdContext * ) * - * assert.deepStrictEqual(Context.get(context, Port), { PORT: 8080 }) - * assert.deepStrictEqual(Context.get(context, Timeout), { TIMEOUT: 5000 }) - * assert.deepStrictEqual(Context.get(context, Host), { HOST: "localhost" }) + * context.mapUnsafe.size // => 3 * ``` * * @see {@link merge} for merging two contexts @@ -1163,9 +1189,8 @@ export const mergeAll = >( * * **Example** (Picking services from a context) * - * ```ts + * ```ts import.meta.vitest * import { Context, Option, pipe } from "effect" - * import * as assert from "node:assert" * * const Port = Context.Service<{ PORT: number }>("Port") * const Timeout = Context.Service<{ TIMEOUT: number }>("Timeout") @@ -1177,11 +1202,8 @@ export const mergeAll = >( * * const context = pipe(someContext, Context.pick(Port)) * - * assert.deepStrictEqual( - * Context.getOption(context, Port), - * Option.some({ PORT: 8080 }) - * ) - * assert.deepStrictEqual(Context.getOption(context, Timeout), Option.none()) + * Context.getOption(context, Port) // => Option.some({ PORT: 8080 }) + * Context.getOption(context, Timeout) // => Option.none() * ``` * * @see {@link omit} for removing selected services @@ -1192,14 +1214,13 @@ export const mergeAll = >( export const pick = >>( ...services: S ) => -(self: Context): Context> => - withMapUnsafe(self, (map) => { - const keySet = new Set(services.map((key) => key.key)) +(self: Context): Context> => { + const keep = new Set(services.map((key) => key.key)) + return withFlat(self, (map) => map.forEach((_, key) => { - if (keySet.has(key)) return - map.delete(key) - }) - }) + if (!keep.has(key)) map.delete(key) + })) +} /** * Returns a new `Context` with the specified service keys removed. @@ -1210,9 +1231,8 @@ export const pick = >>( * * **Example** (Omitting services from a context) * - * ```ts + * ```ts import.meta.vitest * import { Context, Option, pipe } from "effect" - * import * as assert from "node:assert" * * const Port = Context.Service<{ PORT: number }>("Port") * const Timeout = Context.Service<{ TIMEOUT: number }>("Timeout") @@ -1224,11 +1244,8 @@ export const pick = >>( * * const context = pipe(someContext, Context.omit(Timeout)) * - * assert.deepStrictEqual( - * Context.getOption(context, Port), - * Option.some({ PORT: 8080 }) - * ) - * assert.deepStrictEqual(Context.getOption(context, Timeout), Option.none()) + * Context.getOption(context, Port) // => Option.some({ PORT: 8080 }) + * Context.getOption(context, Timeout) // => Option.none() * ``` * * @see {@link pick} for keeping selected services @@ -1240,56 +1257,12 @@ export const omit = >>( ...keys: S ) => (self: Context): Context>> => - withMapUnsafe(self, (map) => { + withFlat(self, (map) => { for (let i = 0; i < keys.length; i++) { map.delete(keys[i].key) } }) -/** - * Performs a series of mutations on a `Context`. Prevents unnecessary copying - * of the underlying map when multiple mutations are needed. - * - * **When to use** - * - * Use to apply several `Context` transformations in one callback while copying - * the underlying service map only once. - * - * @see {@link add} for adding or replacing a service - * @see {@link addOrOmit} for adding or removing a service from an `Option` - * @see {@link merge} for combining two contexts - * @see {@link pick} for keeping selected services - * @see {@link omit} for removing selected services - * - * @category mutations - * @since 4.0.0 - */ -export const mutate: { - ( - f: (context: Context) => Context - ): (self: Context) => Context - (self: Context, f: (context: Context) => Context): Context -} = dual( - 2, - (self: Context, f: (context: Context) => Context): Context => { - const next = makeUnsafe(new Map(self.mapUnsafe)) - next.mutable = true - const result = f(next) - result.mutable = false - return result - } -) - -const withMapUnsafe = (self: Context, f: (map: Map) => void): Context => { - if (self.mutable) { - f(self.mapUnsafe as any) - return self as any - } - const map = new Map(self.mapUnsafe) - f(map) - return makeUnsafe(map) -} - /** * Creates a context key with a default value. * @@ -1308,12 +1281,13 @@ const withMapUnsafe = (self: Context, f: (map: Map = [] * const LoggerRef = Context.Reference("Logger", { - * defaultValue: () => ({ log: (msg: string) => console.log(msg) }) + * defaultValue: () => ({ log: (msg: string) => messages.push(`Default: ${msg}`) }) * }) * * // The reference provides the default value when accessed from an empty context @@ -1322,17 +1296,24 @@ const withMapUnsafe = (self: Context, f: (map: Map `Custom: ${msg}` + * log: (msg: string) => messages.push(`Custom: ${msg}`) * }) * const customLogger = Context.get(customContext, LoggerRef) + * logger.log("default") + * customLogger.log("message") + * messages // => ["Default: default", "Custom: message"] * ``` * * @see {@link Service} for required services without default values * - * @category references + * @category services * @since 3.11.0 */ export const Reference: ( key: string, - options: { readonly defaultValue: () => Service } + options: { + readonly defaultValue: () => Service + /** @internal */ + readonly fiberCached?: boolean | undefined + } ) => Reference = Service as any diff --git a/repos/effect/packages/effect/src/Cron.ts b/repos/effect/packages/effect/src/Cron.ts index 4c6b6dc6f7..259c6a6f29 100644 --- a/repos/effect/packages/effect/src/Cron.ts +++ b/repos/effect/packages/effect/src/Cron.ts @@ -12,7 +12,7 @@ import * as Data from "./Data.ts" import type * as DateTime from "./DateTime.ts" import * as Equal from "./Equal.ts" import * as Equ from "./Equivalence.ts" -import { format } from "./Formatter.ts" +import { format as formatValue } from "./Formatter.ts" import { constVoid, dual, pipe } from "./Function.ts" import * as Hash from "./Hash.ts" import { type Inspectable, NodeInspectSymbol } from "./Inspectable.ts" @@ -43,53 +43,21 @@ const TypeId = "~effect/time/Cron" * * **Example** (Creating a cron schedule) * - * ```ts - * import { Cron } from "effect" + * ```ts import.meta.vitest + * import { Cron, DateTime } from "effect" * * // Create a cron that runs at 9 AM on weekdays * const weekdayMorning = Cron.make({ * minutes: [0], * hours: [9], - * days: [ - * 1, - * 2, - * 3, - * 4, - * 5, - * 6, - * 7, - * 8, - * 9, - * 10, - * 11, - * 12, - * 13, - * 14, - * 15, - * 16, - * 17, - * 18, - * 19, - * 20, - * 21, - * 22, - * 23, - * 24, - * 25, - * 26, - * 27, - * 28, - * 29, - * 30, - * 31 - * ], + * days: [], * months: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], - * weekdays: [1, 2, 3, 4, 5] // Monday to Friday + * weekdays: [1, 2, 3, 4, 5], // Monday to Friday + * tz: DateTime.zoneMakeNamedUnsafe("UTC") * }) * * // Check if a date matches the schedule - * const matches = Cron.match(weekdayMorning, new Date("2023-06-05T09:00:00")) - * console.log(matches) // true if it's 9 AM on a weekday + * Cron.match(weekdayMorning, "2023-06-05T09:00:00Z") // => true * ``` * * @see {@link make} for creating a schedule from explicit field constraints @@ -110,6 +78,8 @@ export interface Cron extends Pipeable, Equal.Equal, Inspectable { readonly months: ReadonlySet readonly weekdays: ReadonlySet /** @internal */ + readonly and: boolean + /** @internal */ readonly first: { readonly second: number readonly minute: number @@ -148,8 +118,9 @@ export interface Cron extends Pipeable, Equal.Equal, Inspectable { } function toPojo(cron: Cron): Record { - return { + const out: Record = { tz: cron.tz, + and: cron.and, seconds: Arr.fromIterable(cron.seconds), minutes: Arr.fromIterable(cron.minutes), hours: Arr.fromIterable(cron.hours), @@ -157,6 +128,7 @@ function toPojo(cron: Cron): Record { months: Arr.fromIterable(cron.months), weekdays: Arr.fromIterable(cron.weekdays) } + return out } const CronProto = { @@ -167,6 +139,7 @@ const CronProto = { [Hash.symbol](this: Cron): number { return pipe( Hash.hash(this.tz), + Hash.combine(Hash.hash(this.and)), Hash.combine(Hash.array(Arr.fromIterable(this.seconds))), Hash.combine(Hash.array(Arr.fromIterable(this.minutes))), Hash.combine(Hash.array(Arr.fromIterable(this.hours))), @@ -176,18 +149,10 @@ const CronProto = { ) }, toObject(this: Cron) { - return { - tz: this.tz, - seconds: Arr.fromIterable(this.seconds), - minutes: Arr.fromIterable(this.minutes), - hours: Arr.fromIterable(this.hours), - days: Arr.fromIterable(this.days), - months: Arr.fromIterable(this.months), - weekdays: Arr.fromIterable(this.weekdays) - } + return toPojo(this) }, toString(this: Cron) { - return `Cron(${format(toPojo(this))})` + return `Cron(${formatValue(toPojo(this))})` }, toJSON(this: Cron) { const out = toPojo(this) @@ -217,7 +182,7 @@ const CronProto = { * * **Example** (Checking cron values) * - * ```ts + * ```ts import.meta.vitest * import { Cron } from "effect" * * const cron = Cron.make({ @@ -228,9 +193,9 @@ const CronProto = { * weekdays: [1, 2, 3, 4, 5] * }) * - * console.log(Cron.isCron(cron)) // true - * console.log(Cron.isCron({})) // false - * console.log(Cron.isCron("not a cron")) // false + * Cron.isCron(cron) // => true + * Cron.isCron({}) // => false + * Cron.isCron("not a cron") // => false * ``` * * @see {@link make} for constructing a `Cron` value directly @@ -251,13 +216,19 @@ export const isCron = (u: unknown): u is Cron => hasProperty(u, TypeId) * **Details** * * Constructs a cron schedule by specifying which seconds, minutes, hours, - * days, months, and weekdays the schedule should match. Empty arrays mean - * "match all" for that time unit. + * days, months, and weekdays the schedule should match. Empty arrays leave a + * time unit unrestricted. If only days or weekdays are restricted, that field + * must match. When both are restricted, the default matches either field; set + * `and: true` to require both fields to match. Weekdays range from `0` (Sunday) + * to `7` (also Sunday). The constructor throws a `RangeError` when a field + * contains a non-integer or out-of-range value. * * **Example** (Creating schedules from constraints) * - * ```ts - * import { Cron } from "effect" + * ```ts import.meta.vitest + * import { Cron, DateTime } from "effect" + * + * const utc = DateTime.zoneMakeNamedUnsafe("UTC") * * // Every day at midnight * const midnight = Cron.make({ @@ -297,49 +268,22 @@ export const isCron = (u: unknown): u is Cron => hasProperty(u, TypeId) * 31 * ], * months: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], - * weekdays: [0, 1, 2, 3, 4, 5, 6] + * weekdays: [0, 1, 2, 3, 4, 5, 6], + * tz: utc * }) * * // Every 15 minutes during business hours on weekdays * const businessHours = Cron.make({ * minutes: [0, 15, 30, 45], * hours: [9, 10, 11, 12, 13, 14, 15, 16, 17], - * days: [ - * 1, - * 2, - * 3, - * 4, - * 5, - * 6, - * 7, - * 8, - * 9, - * 10, - * 11, - * 12, - * 13, - * 14, - * 15, - * 16, - * 17, - * 18, - * 19, - * 20, - * 21, - * 22, - * 23, - * 24, - * 25, - * 26, - * 27, - * 28, - * 29, - * 30, - * 31 - * ], + * days: [], * months: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], - * weekdays: [1, 2, 3, 4, 5] // Monday to Friday + * weekdays: [1, 2, 3, 4, 5], // Monday to Friday + * tz: utc * }) + * + * Cron.match(midnight, "2024-01-01T00:00:00Z") // => true + * Cron.match(businessHours, "2024-01-01T09:15:00Z") // => true * ``` * * @see {@link parse} for building a schedule from a cron expression string @@ -354,15 +298,17 @@ export const make = (values: { readonly days: Iterable readonly months: Iterable readonly weekdays: Iterable + readonly and?: boolean | undefined readonly tz?: DateTime.TimeZone | undefined }): Cron => { const o: Mutable = Object.create(CronProto) - o.seconds = new Set(Arr.sort(values.seconds ?? [0], N.Order)) - o.minutes = new Set(Arr.sort(values.minutes, N.Order)) - o.hours = new Set(Arr.sort(values.hours, N.Order)) - o.days = new Set(Arr.sort(values.days, N.Order)) - o.months = new Set(Arr.sort(values.months, N.Order)) - o.weekdays = new Set(Arr.sort(values.weekdays, N.Order)) + o.seconds = restrictions.seconds(values.seconds ?? [0]) + o.minutes = restrictions.minutes(values.minutes) + o.hours = restrictions.hours(values.hours) + o.days = restrictions.days(values.days) + o.months = restrictions.months(values.months) + o.weekdays = restrictions.weekdays(values.weekdays) + o.and = values.and === true o.tz = Option.fromUndefinedOr(values.tz) const seconds = Array.from(o.seconds) @@ -391,31 +337,57 @@ export const make = (values: { } o.next = { - second: lookupTable(seconds, 60, "next"), - minute: lookupTable(minutes, 60, "next"), - hour: lookupTable(hours, 24, "next"), - day: lookupTable(days, 32, "next"), - month: lookupTable(months, 13, "next"), - weekday: lookupTable(weekdays, 7, "next") + second: lookup.next.second(seconds), + minute: lookup.next.minute(minutes), + hour: lookup.next.hour(hours), + day: lookup.next.day(days), + month: lookup.next.month(months), + weekday: lookup.next.weekday(weekdays) } o.prev = { - second: lookupTable(seconds, 60, "prev"), - minute: lookupTable(minutes, 60, "prev"), - hour: lookupTable(hours, 24, "prev"), - day: lookupTable(days, 32, "prev"), - month: lookupTable(months, 13, "prev"), - weekday: lookupTable(weekdays, 7, "prev") + second: lookup.prev.second(seconds), + minute: lookup.prev.minute(minutes), + hour: lookup.prev.hour(hours), + day: lookup.prev.day(days), + month: lookup.prev.month(months), + weekday: lookup.prev.weekday(weekdays) } return o } -const lookupTable = ( - values: ReadonlyArray, +const makeRestrictions = ( + field: string, + min: number, + max: number, + normalize: (value: number) => number = (value) => value +): (values: Iterable) => Set => +(values) => { + const restrictions: Array = [] + for (const value of values) { + if (!Number.isInteger(value) || value < min || value > max) { + throw new RangeError(`${field} must contain only integers between ${min} and ${max}`) + } + restrictions.push(normalize(value)) + } + return new Set(Arr.sort(restrictions, N.Order)) +} + +const restrictions = { + seconds: makeRestrictions("seconds", 0, 59), + minutes: makeRestrictions("minutes", 0, 59), + hours: makeRestrictions("hours", 0, 23), + days: makeRestrictions("days", 1, 31), + months: makeRestrictions("months", 1, 12), + weekdays: makeRestrictions("weekdays", 0, 7, (value) => value === 7 ? 0 : value) +} + +const makeLookupTable = ( size: number, dir: "next" | "prev" -): Array => { +): (values: ReadonlyArray) => Array => +(values) => { const result = new Array(size).fill(undefined) if (values.length === 0) { return result @@ -444,6 +416,25 @@ const lookupTable = ( 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" /** @@ -460,21 +451,21 @@ const CronParseErrorTypeId = "~effect/time/Cron/CronParseError" * * **Example** (Handling cron parse failures) * - * ```ts + * ```ts import.meta.vitest * import { Cron, Result } from "effect" * - * const result = Cron.parse("invalid expression") - * if (Result.isFailure(result)) { - * const error: Cron.CronParseError = result.failure - * console.log(error.message) // "Invalid number of segments in cron expression" - * console.log(error.input) // "invalid expression" - * } + * const expected = Result.fail(new Cron.CronParseError({ + * message: "Invalid number of segments in cron expression", + * input: "invalid expression" + * })) + * + * Cron.parse("invalid expression") // => expected * ``` * * @see {@link parse} for the parser that returns this error in `Result.fail` * @see {@link isCronParseError} for narrowing unknown values to this error type * - * @category models + * @category errors * @since 4.0.0 */ export class CronParseError extends Data.TaggedError("CronParseError")<{ @@ -499,17 +490,12 @@ export class CronParseError extends Data.TaggedError("CronParseError")<{ * * **Example** (Checking cron parse errors) * - * ```ts + * ```ts import.meta.vitest * import { Cron, Result } from "effect" * - * const result = Cron.parse("invalid cron expression") - * if (Result.isFailure(result)) { - * const error = result.failure - * console.log(Cron.isCronParseError(error)) // true - * } - * - * console.log(Cron.isCronParseError(new Error("regular error"))) // false - * console.log(Cron.isCronParseError("not an error")) // false + * Result.mapError(Cron.parse("invalid cron expression"), Cron.isCronParseError) // => Result.fail(true) + * Cron.isCronParseError(new Error("regular error")) // => false + * Cron.isCronParseError("not an error") // => false * ``` * * @see {@link CronParseError} for the parse error type @@ -534,26 +520,20 @@ export const isCronParseError = (u: unknown): u is CronParseError => hasProperty * The expression may contain five fields, where seconds default to `0`, or six * fields including seconds. Fields support `*`, comma-separated values, ranges, * steps, and month or weekday aliases. Invalid expressions fail with - * `CronParseError`. + * `CronParseError`. When both the day-of-month and weekday fields are + * restricted, a date matches if either field matches. When either field starts + * with `*`, both fields must match; an unrestricted field always matches. * * **Example** (Parsing cron expressions) * - * ```ts + * ```ts import.meta.vitest * import { Cron, Result } from "effect" - * import * as assert from "node:assert" * * // At 04:00 on every day-of-month from 8 through 14. - * assert.deepStrictEqual( - * Cron.parse("0 0 4 8-14 * *"), - * Result.succeed(Cron.make({ - * seconds: [0], - * minutes: [0], - * hours: [4], - * days: [8, 9, 10, 11, 12, 13, 14], - * months: [], - * weekdays: [] - * })) - * ) + * const cron = Result.getOrThrow(Cron.parse("0 0 4 8-14 * *")) + * + * Array.from(cron.hours) // => [4] + * Array.from(cron.days) // => [8, 9, 10, 11, 12, 13, 14] * ``` * * @see {@link parseUnsafe} for throwing on invalid cron expressions @@ -563,7 +543,7 @@ export const isCronParseError = (u: unknown): u is CronParseError => hasProperty * @since 2.0.0 */ export const parse = (cron: string, tz?: DateTime.TimeZone | string): Result.Result => { - const segments = cron.split(" ").filter(String.isNonEmpty) + const segments = cron.trim().split(/\s+/).filter(String.isNonEmpty) if (segments.length !== 5 && segments.length !== 6) { return Result.fail(new CronParseError({ message: `Invalid number of segments in cron expression`, input: cron })) } @@ -588,7 +568,18 @@ export const parse = (cron: string, tz?: DateTime.TimeZone | string): Result.Res days: parseSegment(days, dayOptions), months: parseSegment(months, monthOptions), weekdays: parseSegment(weekdays, weekdayOptions) - }).pipe(Result.map(make)) + }).pipe(Result.map(({ tz, seconds, minutes, hours, days, months, weekdays }) => + make({ + tz, + seconds: seconds.values, + minutes: minutes.values, + hours: hours.values, + days: days.values, + months: months.values, + weekdays: weekdays.values, + and: (days.wildcard || weekdays.wildcard) && days.values.size !== 0 && weekdays.values.size !== 0 + }) + )) } /** @@ -601,17 +592,19 @@ export const parse = (cron: string, tz?: DateTime.TimeZone | string): Result.Res * * **Example** (Parsing cron expressions unsafely) * - * ```ts + * ```ts import.meta.vitest * import { Cron } from "effect" * * // At 04:00 on every day-of-month from 8 through 14 - * const cron = Cron.parseUnsafe("0 0 4 8-14 * *") + * const cron = Cron.parseUnsafe("0 0 4 8-14 * *", "UTC") * * // With timezone * const cronWithTz = Cron.parseUnsafe("0 0 9 * * *", "America/New_York") * * // This would throw an error * // const invalid = Cron.parseUnsafe("invalid expression") + * Cron.match(cron, "2024-01-10T04:00:00Z") // => true + * Cron.match(cronWithTz, "2024-01-01T14:00:00Z") // => true * ``` * * @category constructors @@ -619,6 +612,69 @@ export const parse = (cron: string, tz?: DateTime.TimeZone | string): Result.Res */ export const parseUnsafe = (cron: string, tz?: DateTime.TimeZone | string): Cron => Result.getOrThrow(parse(cron, tz)) +/** + * Formats a `Cron` instance as a cron expression. + * + * **Details** + * + * The default seconds field (`0`) is omitted unless `includeSeconds` is `true`. + * Other seconds configurations are always included. + * + * **Gotchas** + * + * Formatting drops the timezone information and the `and` restriction between + * days and weekdays. Parsing the result is therefore not guaranteed to produce + * an equivalent schedule. + * + * **Example** (Formatting a cron expression) + * + * ```ts import.meta.vitest + * import { Cron } from "effect" + * + * const cron = Cron.parseUnsafe("23 0-20/2 * * 0", "UTC") + * + * Cron.format(cron) // => "23 0-20/2 * * 0" + * Cron.format(cron, { includeSeconds: true }) // => "0 23 0-20/2 * * 0" + * ``` + * + * @category getters + * @since 4.0.0 + */ +export const format = (cron: Cron, options?: { + readonly includeSeconds?: boolean | undefined +}): string => { + const segments = [cron.seconds, cron.minutes, cron.hours, cron.days, cron.months, cron.weekdays] + .map(formatSegment) + return ( + options?.includeSeconds !== true && cron.seconds.size === 1 && cron.seconds.has(0) ? segments.slice(1) : segments + ).join(" ") +} + +const formatSegment = (values: ReadonlySet): string => { + if (values.size === 0) { + return "*" + } + const array = Array.from(values) + const segments: Array = [] + let index = 0 + while (index < array.length) { + const start = array[index]! + const step = array[index + 1]! - start + if (index + 2 < array.length && array[index + 2]! - array[index + 1]! === step) { + let end = index + 2 + while (end + 1 < array.length && array[end + 1]! - array[end]! === step) { + end++ + } + segments.push(`${start}-${array[end]}${step === 1 ? "" : `/${step}`}`) + index = end + 1 + } else { + segments.push(`${start}`) + index++ + } + } + return segments.join(",") +} + /** * Returns `true` when a date/time matches a `Cron` schedule. * @@ -628,27 +684,25 @@ export const parseUnsafe = (cron: string, tz?: DateTime.TimeZone | string): Cron * * **Details** * - * Seconds, minutes, hours, months, and the optional timezone are checked - * directly. For day constraints, an empty `days` or `weekdays` set means that - * field matches every value; when both sets are non-empty, a date matches if - * either the day-of-month or weekday matches. + * The schedule's timezone determines which calendar fields are read from the + * input; the host system's timezone is used when the schedule has no timezone. + * Seconds, minutes, hours, and months are checked against their restrictions; + * an empty set leaves that field unrestricted. If only `days` or `weekdays` is + * restricted, that field must match. If both are restricted, either may match + * unless the schedule was created with `and: true`, which requires both to + * match. * * **Example** (Matching dates against a schedule) * - * ```ts + * ```ts import.meta.vitest * import { Cron, Result } from "effect" * - * const cron = Result.getOrThrow(Cron.parse("0 0 4 8-14 * *")) + * const cron = Result.getOrThrow(Cron.parse("0 0 4 8-14 * *", "UTC")) * * // Check if specific dates match - * const matches1 = Cron.match(cron, new Date("2021-01-08T04:00:00Z")) - * console.log(matches1) // true - 4 AM on the 8th - * - * const matches2 = Cron.match(cron, new Date("2021-01-08T05:00:00Z")) - * console.log(matches2) // false - wrong hour - * - * const matches3 = Cron.match(cron, new Date("2021-01-07T04:00:00Z")) - * console.log(matches3) // false - wrong day + * Cron.match(cron, "2021-01-08T04:00:00Z") // => true + * Cron.match(cron, "2021-01-08T05:00:00Z") // => false + * Cron.match(cron, "2021-01-07T04:00:00Z") // => false * ``` * * @see {@link next} for finding the next matching date/time @@ -682,6 +736,11 @@ export const match = (cron: Cron, date: DateTime.DateTime.Input): boolean => { return true } + if (cron.and) { + return (cron.days.size === 0 || cron.days.has(parts.day)) && + (cron.weekdays.size === 0 || cron.weekdays.has(parts.weekDay)) + } + if (cron.weekdays.size === 0) { return cron.days.has(parts.day) } @@ -712,19 +771,13 @@ const daysInMonth = (date: Date): number => * * **Example** (Finding the next occurrence) * - * ```ts + * ```ts import.meta.vitest * import { Cron, Result } from "effect" * - * const cron = Result.getOrThrow(Cron.parse("0 0 4 8-14 * *")) + * const cron = Result.getOrThrow(Cron.parse("0 0 4 8-14 * *", "UTC")) * * // Get next run after a specific date - * const after = new Date("2021-01-01T00:00:00Z") - * const nextRun = Cron.next(cron, after) - * console.log(nextRun) // 2021-01-08T04:00:00.000Z - * - * // Get next run from current time - * const nextFromNow = Cron.next(cron) - * console.log(nextFromNow) // Next occurrence from now + * Cron.next(cron, "2021-01-01T00:00:00Z").toISOString() // => "2021-01-08T04:00:00.000Z" * ``` * * @see {@link prev} for finding the previous scheduled occurrence @@ -843,46 +896,69 @@ const stepCron = (cron: Cron, now: DateTime.DateTime.Input | undefined, directio } if (cron.weekdays.size !== 0 || cron.days.size !== 0) { - let a: number = reverse ? -Infinity : Infinity - let b: number = reverse ? -Infinity : Infinity - - if (cron.weekdays.size !== 0) { - const currentWeekday = current.getUTCDay() - const nextWeekday = table.weekday[currentWeekday] - if (nextWeekday === undefined) { - a = reverse ? - currentWeekday - 7 + boundary.weekday : - 7 - currentWeekday + boundary.weekday - } else { - a = nextWeekday - currentWeekday + if (cron.and) { + const matchesDay = cron.days.size === 0 || cron.days.has(current.getUTCDate()) + const matchesWeekday = cron.weekdays.size === 0 || cron.weekdays.has(current.getUTCDay()) + if (!matchesDay || !matchesWeekday) { + current.setUTCDate(current.getUTCDate() + tick) + current.setUTCHours(boundary.hour, boundary.minute, boundary.second) + adjustDst(current) + continue } - } - - if (cron.days.size !== 0 && a !== 0) { - const currentDay = current.getUTCDate() - const nextDay = table.day[currentDay] - if (nextDay === undefined) { - if (reverse) { - const prevMonthDays = daysInMonth(new Date(Date.UTC(current.getUTCFullYear(), current.getUTCMonth(), 0))) - b = -(currentDay + (prevMonthDays - boundary.day)) + } else { + let a: number = reverse ? -Infinity : Infinity + let b: number = reverse ? -Infinity : Infinity + + if (cron.weekdays.size !== 0) { + const currentWeekday = current.getUTCDay() + const nextWeekday = table.weekday[currentWeekday] + if (nextWeekday === undefined) { + a = reverse ? + boundary.weekday - 7 - currentWeekday : + 7 - currentWeekday + boundary.weekday } else { + a = nextWeekday - currentWeekday + } + } + + if (cron.days.size !== 0 && a !== 0) { + const currentDay = current.getUTCDate() + const nextDay = table.day[currentDay] + if (nextDay === undefined) { + if (reverse) { + const previous = new Date(current) + // Day zero is the previous month's last day. These two probes cover every + // valid day-of-month. + previous.setUTCDate(0) + let day = table.day[previous.getUTCDate()] + if (day === undefined) { + previous.setUTCDate(0) + day = table.day[previous.getUTCDate()] + } + if (day === undefined) { + throw new Error("Unable to find cron date") + } + previous.setUTCDate(day) + b = (previous.getTime() - current.getTime()) / 86_400_000 + } else { + b = daysInMonth(current) - currentDay + boundary.day + } + } else if (!reverse && nextDay > daysInMonth(current)) { + // The next matching day does not exist in the current month. Setting it + // directly would overflow and skip earlier matching days next month. b = daysInMonth(current) - currentDay + boundary.day + } else { + b = nextDay - currentDay } - } else if (!reverse && nextDay > daysInMonth(current)) { - // The next matching day does not exist in the current month. Setting it - // directly would overflow and skip earlier matching days next month. - b = daysInMonth(current) - currentDay + boundary.day - } else { - b = nextDay - currentDay } - } - const addDays = reverse ? Math.max(a, b) : Math.min(a, b) - if (addDays !== 0) { - current.setUTCDate(current.getUTCDate() + addDays) - current.setUTCHours(boundary.hour, boundary.minute, boundary.second) - adjustDst(current) - continue + const addDays = reverse ? Math.max(a, b) : Math.min(a, b) + if (addDays !== 0) { + current.setUTCDate(current.getUTCDate() + addDays) + current.setUTCHours(boundary.hour, boundary.minute, boundary.second) + adjustDst(current) + continue + } } } @@ -890,11 +966,11 @@ const stepCron = (cron: Cron, now: DateTime.DateTime.Input | undefined, directio const currentMonth = current.getUTCMonth() + 1 const nextMonth = table.month[currentMonth] const clampBoundaryDay = (targetMonthIndex: number): number => { - if (cron.days.size !== 0) { - return boundary.day - } const maxDayInMonth = daysInMonth(new Date(Date.UTC(current.getUTCFullYear(), targetMonthIndex + 1, 0))) - return Math.min(boundary.day, maxDayInMonth) + if (cron.days.size !== 0 && cron.weekdays.size === 0) { + return reverse ? table.day[maxDayInMonth] ?? maxDayInMonth : boundary.day + } + return reverse ? maxDayInMonth : 1 } if (nextMonth === undefined) { current.setUTCFullYear(current.getUTCFullYear() + tick) @@ -915,7 +991,7 @@ const stepCron = (cron: Cron, now: DateTime.DateTime.Input | undefined, directio return } - throw new Error("Unable to find " + direction + " cron date") + throw new Error("Unable to find cron date") }) return dateTime.toDateUtc(result) @@ -936,17 +1012,23 @@ const stepCron = (cron: Cron, now: DateTime.DateTime.Input | undefined, directio * * **Example** (Iterating scheduled occurrences) * - * ```ts + * ```ts import.meta.vitest * import { Cron, Result } from "effect" * - * const cron = Result.getOrThrow(Cron.parse("0 0 9 * * 1-5")) // 9 AM weekdays + * const cron = Result.getOrThrow(Cron.parse("0 0 9 * * 1-5", "UTC")) // 9 AM weekdays * * // Get first 5 occurrences - * const iterator = Cron.sequence(cron, new Date("2023-01-01")) - * const next5 = Array.from({ length: 5 }, () => iterator.next().value) - * - * console.log(next5) - * // [Mon Jan 02 2023 09:00:00, Tue Jan 03 2023 09:00:00, ...] + * const iterator = Cron.sequence(cron, "2023-01-01T00:00:00Z") + * const next5 = Array.from({ length: 5 }, () => iterator.next().value.toISOString()) + * const expected = [ + * "2023-01-02T09:00:00.000Z", + * "2023-01-03T09:00:00.000Z", + * "2023-01-04T09:00:00.000Z", + * "2023-01-05T09:00:00.000Z", + * "2023-01-06T09:00:00.000Z" + * ] + * + * next5 // => expected * ``` * * @see {@link next} for computing one next occurrence @@ -961,8 +1043,8 @@ export const sequence = function*(cron: Cron, now?: DateTime.DateTime.Input): It } /** - * Equivalence instance for comparing the field restrictions of two `Cron` - * schedules. + * Equivalence instance for comparing the timezone, field restrictions, and + * day-matching mode of two `Cron` schedules. * * **When to use** * @@ -971,12 +1053,12 @@ export const sequence = function*(cron: Cron, now?: DateTime.DateTime.Input): It * * **Details** * - * This comparison checks seconds, minutes, hours, days, months, and weekdays. - * It does not compare the optional timezone. + * This comparison checks the optional timezone, the `and` day-matching mode, + * seconds, minutes, hours, days, months, and weekdays. * * **Example** (Comparing schedules with equivalence) * - * ```ts + * ```ts import.meta.vitest * import { Cron } from "effect" * * const cron1 = Cron.make({ @@ -995,7 +1077,7 @@ export const sequence = function*(cron: Cron, now?: DateTime.DateTime.Input): It * weekdays: [1, 2, 3, 4, 5] * }) * - * console.log(Cron.Equivalence(cron1, cron2)) // true + * Cron.Equivalence(cron1, cron2) // => true * ``` * * @see {@link equals} for directly comparing two `Cron` values @@ -1004,6 +1086,8 @@ export const sequence = function*(cron: Cron, now?: DateTime.DateTime.Input): It * @since 2.0.0 */ export const Equivalence: Equ.Equivalence = Equ.make((self, that) => + Equal.equals(self.tz, that.tz) && + self.and === that.and && restrictionsEquals(self.seconds, that.seconds) && restrictionsEquals(self.minutes, that.minutes) && restrictionsEquals(self.hours, that.hours) && @@ -1017,21 +1101,22 @@ const restrictionsEquals = (self: ReadonlySet, that: ReadonlySet restrictionsArrayEquals(Arr.fromIterable(self), Arr.fromIterable(that)) /** - * Checks whether two `Cron` instances have the same field restrictions. + * Checks whether two `Cron` instances have equal timezone values, field + * restrictions, and day-matching modes. * * **When to use** * - * Use to directly compare whether two cron schedules have the same field - * restrictions. + * Use to directly compare two cron schedules, including their timezones and + * day-matching modes. * * **Details** * - * The comparison checks seconds, minutes, hours, days, months, and weekdays. - * It does not compare the optional timezone. + * The comparison checks the optional timezone, the `and` day-matching mode, + * seconds, minutes, hours, days, months, and weekdays. * * **Example** (Checking schedule equality) * - * ```ts + * ```ts import.meta.vitest * import { Cron } from "effect" * * const cron1 = Cron.make({ @@ -1050,8 +1135,8 @@ const restrictionsEquals = (self: ReadonlySet, that: ReadonlySet * weekdays: [1, 2, 3, 4, 5] * }) * - * console.log(Cron.equals(cron1, cron2)) // true - * console.log(Cron.equals(cron1)(cron2)) // true (curried form) + * Cron.equals(cron1, cron2) // => true + * Cron.equals(cron1)(cron2) // => true * ``` * * @see {@link Equivalence} for the reusable equivalence instance @@ -1068,6 +1153,12 @@ interface SegmentOptions { min: number max: number aliases?: Record | undefined + normalize?: ((value: number) => number) | undefined +} + +interface ParsedSegment { + readonly values: Set + readonly wildcard: boolean } const secondOptions: SegmentOptions = { @@ -1111,7 +1202,8 @@ const monthOptions: SegmentOptions = { const weekdayOptions: SegmentOptions = { min: 0, - max: 6, + max: 7, + normalize: (value) => value === 7 ? 0 : value, aliases: { sun: 0, mon: 1, @@ -1126,17 +1218,21 @@ const weekdayOptions: SegmentOptions = { const parseSegment = ( input: string, options: SegmentOptions -): Result.Result, CronParseError> => { - const capacity = options.max - options.min + 1 +): Result.Result => { const values = new Set() const fields = input.split(",") - - for (const field of fields) { - const [raw, step] = splitStep(field) - if (raw === "*" && step === undefined) { - return Result.succeed(new Set()) + const first = splitStep(fields[0]!) + const wildcard = first[0] === "*" + const normalize = options.normalize ?? ((value: number) => value) + const add = wildcard && (first[1] === undefined || first[1] === 1) ? + constVoid : + (value: number) => { + values.add(normalize(value)) } + for (let index = 0; index < fields.length; index++) { + const field = fields[index]! + const [raw, step] = index === 0 ? first : splitStep(field) if (step !== undefined) { if (!Number.isInteger(step)) { return Result.fail(new CronParseError({ message: `Expected step value to be a positive integer`, input })) @@ -1145,13 +1241,18 @@ const parseSegment = ( return Result.fail(new CronParseError({ message: `Expected step value to be greater than 0`, input })) } if (step > options.max) { - return Result.fail(new CronParseError({ message: `Expected step value to be less than ${options.max}`, input })) + return Result.fail( + new CronParseError({ message: `Expected step value to be less than or equal to ${options.max}`, input }) + ) } } if (raw === "*") { + if (index === 0 && (step === undefined || step === 1)) { + continue + } for (let i = options.min; i <= options.max; i += step ?? 1) { - values.add(i) + add(i) } } else { const [left, right] = splitRange(raw, options.aliases) @@ -1165,7 +1266,9 @@ const parseSegment = ( } if (right === undefined) { - values.add(left) + for (let i = left; i <= (step === undefined ? left : options.max); i += step ?? 1) { + add(i) + } } else { if (!Number.isInteger(right)) { return Result.fail(new CronParseError({ message: `Expected a positive integer`, input })) @@ -1180,23 +1283,20 @@ const parseSegment = ( } for (let i = left; i <= right; i += step ?? 1) { - values.add(i) + add(i) } } } - - if (values.size >= capacity) { - return Result.succeed(new Set()) - } } - return Result.succeed(values) + return Result.succeed({ values, wildcard }) } const splitStep = (input: string): [string, number | undefined] => { const separator = input.indexOf("/") if (separator !== -1) { - return [input.slice(0, separator), Number(input.slice(separator + 1))] + const step = input.slice(separator + 1) + return [input.slice(0, separator), decimalRegex.test(step) ? Number(step) : NaN] } return [input, undefined] @@ -1212,5 +1312,7 @@ const splitRange = (input: string, aliases?: Record): [number, n } function aliasOrValue(field: string, aliases?: Record): number { - return aliases?.[field.toLocaleLowerCase()] ?? Number(field) + return aliases?.[String.toLowerCase(field)] ?? (decimalRegex.test(field) ? Number(field) : NaN) } + +const decimalRegex = /^\d+$/ diff --git a/repos/effect/packages/effect/src/Crypto.ts b/repos/effect/packages/effect/src/Crypto.ts index d3cdc018b2..b817a3e8c8 100644 --- a/repos/effect/packages/effect/src/Crypto.ts +++ b/repos/effect/packages/effect/src/Crypto.ts @@ -25,7 +25,7 @@ const TypeId = "~effect/platform/Crypto" * * **Example** (Using a digest algorithm) * - * ```ts + * ```ts import.meta.vitest * import { Crypto } from "effect" * * const algorithm: Crypto.DigestAlgorithm = "SHA-256" @@ -47,7 +47,7 @@ export type DigestAlgorithm = "SHA-1" | "SHA-256" | "SHA-384" | "SHA-512" * * **Example** (Using cryptographic operations) * - * ```ts + * ```ts import.meta.vitest * import { Crypto, Effect, Layer } from "effect" * * const TestCrypto = Layer.succeed( @@ -62,15 +62,14 @@ export type DigestAlgorithm = "SHA-1" | "SHA-256" | "SHA-384" | "SHA-512" * const crypto = yield* Crypto.Crypto * const bytes = yield* crypto.randomBytes(16) * const uuidv4 = yield* crypto.randomUUIDv4 - * const uuidv7 = yield* crypto.randomUUIDv7 * const hash = yield* crypto.digest("SHA-256", bytes) - * return { uuidv4, uuidv7, hash } + * return [bytes.length, uuidv4.length, hash.length] * }) * - * Effect.runPromise(Effect.provide(program, TestCrypto)) + * await Effect.runPromise(Effect.provide(program, TestCrypto)) // => [16, 36, 16] * ``` * - * @category models + * @category services * @since 4.0.0 */ export interface Crypto { @@ -196,16 +195,15 @@ export const Crypto: Context.Service = Context.Service("effect/C * * **Example** (Creating a Crypto service) * - * ```ts - * import { Crypto, Effect, Layer } from "effect" + * ```ts import.meta.vitest + * import { Crypto, Effect } from "effect" * - * const TestCrypto = Layer.succeed( - * Crypto.Crypto, - * Crypto.make({ - * randomBytes: (size) => new Uint8Array(size), - * digest: (_algorithm, data) => Effect.succeed(data) - * }) - * ) + * const testCrypto = Crypto.make({ + * randomBytes: (size) => new Uint8Array(size), + * digest: (_algorithm, data) => Effect.succeed(data) + * }) + * + * await Effect.runPromise(testCrypto.randomBytes(4)) // => new Uint8Array([0, 0, 0, 0]) * ``` * * @category constructors @@ -224,15 +222,24 @@ export const make = ( const randomBytes: Crypto["randomBytes"] = (size) => Effect.map(validateSize("randomBytes", size), randomBytesUnsafe) - const nextDoubleUnsafe = (): number => { - const bytes = randomBytesUnsafe(7) - const value = ((bytes[0] & 0x1f) * 2 ** 48) + (bytes[1] * 2 ** 40) + (bytes[2] * 2 ** 32) + - (bytes[3] * 2 ** 24) + (bytes[4] * 2 ** 16) + (bytes[5] * 2 ** 8) + bytes[6] - return value / 2 ** 53 - } + const readUint53 = (bytes: Uint8Array): number => + ((bytes[0] & 0x1f) * 2 ** 48) + (bytes[1] * 2 ** 40) + (bytes[2] * 2 ** 32) + + (bytes[3] * 2 ** 24) + (bytes[4] * 2 ** 16) + (bytes[5] * 2 ** 8) + bytes[6] + + const nextDoubleUnsafe = (): number => readUint53(randomBytesUnsafe(7)) / 2 ** 53 - const nextIntUnsafe = (): number => - Math.floor(nextDoubleUnsafe() * (Number.MAX_SAFE_INTEGER - Number.MIN_SAFE_INTEGER + 1)) + Number.MIN_SAFE_INTEGER + 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, diff --git a/repos/effect/packages/effect/src/Data.ts b/repos/effect/packages/effect/src/Data.ts index 2062977493..2228bb0e9d 100644 --- a/repos/effect/packages/effect/src/Data.ts +++ b/repos/effect/packages/effect/src/Data.ts @@ -10,6 +10,7 @@ */ import type * as Cause from "./Cause.ts" import * as core from "./internal/core.ts" +import * as InternalRecord from "./internal/record.ts" import * as Pipeable from "./Pipeable.ts" import * as Predicate from "./Predicate.ts" import type * as Types from "./Types.ts" @@ -30,16 +31,12 @@ import type { Unify } from "./Unify.ts" * * **Example** (Defining a value class) * - * ```ts + * ```ts import.meta.vitest * import { Data, Equal } from "effect" * * class Person extends Data.Class<{ readonly name: string }> {} * - * const mike1 = new Person({ name: "Mike" }) - * const mike2 = new Person({ name: "Mike" }) - * - * console.log(Equal.equals(mike1, mike2)) - * // true + * Equal.equals(new Person({ name: "Mike" }), new Person({ name: "Mike" })) // => true * ``` * * @see {@link TaggedClass} — adds a `_tag` field @@ -51,10 +48,10 @@ import type { Unify } from "./Unify.ts" export const Class: new = {}>( args: Types.VoidIfEmpty<{ readonly [P in keyof A]: A[P] }> ) => Readonly & Pipeable.Pipeable = class extends Pipeable.Class { - constructor(props: any) { + constructor(props: object | undefined) { super() if (props) { - Object.assign(this, props) + InternalRecord.assignProperties(this, props) } } } as any @@ -74,16 +71,14 @@ export const Class: new = {}>( * * **Example** (Defining a tagged class) * - * ```ts + * ```ts import.meta.vitest * import { Data } from "effect" * * class Person extends Data.TaggedClass("Person")<{ * readonly name: string * }> {} * - * const mike = new Person({ name: "Mike" }) - * console.log(mike._tag) - * // "Person" + * new Person({ name: "Mike" })._tag // => "Person" * ``` * * @see {@link Class} — without a `_tag` @@ -120,7 +115,7 @@ export const TaggedClass = ( * * **Example** (Defining a tagged enum) * - * ```ts + * ```ts import.meta.vitest * import { Data } from "effect" * * type HttpError = Data.TaggedEnum<{ @@ -134,9 +129,7 @@ export const TaggedClass = ( * * const { BadRequest, NotFound } = Data.taggedEnum() * - * const err = BadRequest({ status: 400, message: "missing id" }) - * console.log(err._tag) - * // "BadRequest" + * BadRequest({ status: 400, message: "missing id" })._tag // => "BadRequest" * ``` * * @see {@link taggedEnum} — constructors and matchers for a `TaggedEnum` @@ -197,7 +190,7 @@ export declare namespace TaggedEnum { * * **Example** (Defining a generic tagged enum) * - * ```ts + * ```ts import.meta.vitest * import { Data } from "effect" * * type MyResult = Data.TaggedEnum<{ @@ -213,6 +206,7 @@ export declare namespace TaggedEnum { * * const ok = Success({ value: 42 }) * // ok: { readonly _tag: "Success"; readonly value: number } + * ok // => { value: 42, _tag: "Success" } * ``` * * @see {@link Kind} — apply concrete types to a `WithGenerics` definition @@ -241,7 +235,7 @@ export declare namespace TaggedEnum { * * **Example** (Applying generics) * - * ```ts + * ```ts import.meta.vitest * import type { Data } from "effect" * * type Option = Data.TaggedEnum<{ @@ -252,9 +246,9 @@ export declare namespace TaggedEnum { * readonly taggedEnum: Option * } * - * // Resolve to the concrete union for `string` - * type StringOption = Data.TaggedEnum.Kind + * // Resolves to the concrete union for `string`: * // { _tag: "None" } | { _tag: "Some"; value: string } + * type StringOption = Data.TaggedEnum.Kind * ``` * * @see {@link WithGenerics} — define the generic shape @@ -290,7 +284,7 @@ export declare namespace TaggedEnum { * * **Example** (Extracting variant args) * - * ```ts + * ```ts import.meta.vitest * import type { Data } from "effect" * * type Result = @@ -327,7 +321,7 @@ export declare namespace TaggedEnum { * * **Example** (Extracting a variant type) * - * ```ts + * ```ts import.meta.vitest * import type { Data } from "effect" * * type Result = @@ -367,7 +361,7 @@ export declare namespace TaggedEnum { * * **Example** (Using the constructor object) * - * ```ts + * ```ts import.meta.vitest * import { Data } from "effect" * * type Shape = @@ -378,21 +372,19 @@ export declare namespace TaggedEnum { * * const shape = Circle({ radius: 10 }) * - * // Type guard * if ($is("Circle")(shape)) { - * console.log(shape.radius) + * shape.radius // => 10 * } * - * // Pattern matching - * const label = $match(shape, { + * $match(shape, { * Circle: (s) => `circle r=${s.radius}`, * Rect: (s) => `rect ${s.w}x${s.h}` - * }) + * }) // => "circle r=10" * ``` * * @see {@link taggedEnum} — creates constructors and matchers * - * @category types + * @category utility types * @since 3.1.0 */ export type Constructor = Types.Simplify< @@ -539,7 +531,7 @@ export declare namespace TaggedEnum { * * **Example** (Creating and matching tagged enum values) * - * ```ts + * ```ts import.meta.vitest * import { Data } from "effect" * * type HttpError = Data.TaggedEnum<{ @@ -551,20 +543,17 @@ export declare namespace TaggedEnum { * * const err = NotFound({ url: "/missing" }) * - * // Type guard - * console.log($is("NotFound")(err)) // true + * $is("NotFound")(err) // => true * - * // Pattern matching - * const msg = $match(err, { + * $match(err, { * BadRequest: (e) => e.message, * NotFound: (e) => `${e.url} not found` - * }) - * console.log(msg) // "/missing not found" + * }) // => "/missing not found" * ``` * * **Example** (Defining a generic tagged enum) * - * ```ts + * ```ts import.meta.vitest * import { Data } from "effect" * * type MyResult = Data.TaggedEnum<{ @@ -578,6 +567,7 @@ export declare namespace TaggedEnum { * * const ok = Success({ value: 42 }) * // ok: { readonly _tag: "Success"; readonly value: number } + * ok // => { value: 42, _tag: "Success" } * ``` * * @see {@link TaggedEnum} — the type-level companion @@ -698,8 +688,8 @@ function taggedMatch< * * **Example** (Defining a yieldable error) * - * ```ts - * import { Data, Effect } from "effect" + * ```ts import.meta.vitest + * import { Data, Effect, Exit } from "effect" * * class NetworkError extends Data.Error<{ * readonly code: number @@ -710,8 +700,7 @@ function taggedMatch< * return yield* new NetworkError({ code: 500, message: "timeout" }) * }) * - * // The effect fails with a NetworkError - * Effect.runSync(Effect.exit(program)) + * Effect.runSync(Effect.exit(program)) // => Exit.fail(new NetworkError({ code: 500, message: "timeout" })) * ``` * * @see {@link TaggedError} — adds a `_tag` for `Effect.catchTag` @@ -740,7 +729,7 @@ export const Error: new = {}>( * * **Example** (Recovering by tag) * - * ```ts + * ```ts import.meta.vitest * import { Data, Effect } from "effect" * * class NotFound extends Data.TaggedError("NotFound")<{ @@ -759,6 +748,8 @@ export const Error: new = {}>( * Effect.catchTag("NotFound", (e) => * Effect.succeed(`missing: ${e.resource}`)) * ) + * + * await Effect.runPromise(recovered) // => "missing: /users/42" * ``` * * @see {@link Error} — without a `_tag` diff --git a/repos/effect/packages/effect/src/DateTime.ts b/repos/effect/packages/effect/src/DateTime.ts index 36df2d8971..c121861a27 100644 --- a/repos/effect/packages/effect/src/DateTime.ts +++ b/repos/effect/packages/effect/src/DateTime.ts @@ -353,21 +353,21 @@ export declare namespace TimeZone { * * **Example** (Resolving ambiguous local times) * - * ```ts - * import { DateTime } from "effect" + * ```ts import.meta.vitest + * import { DateTime, Option } from "effect" * * // Fall-back example: 01:30 on Nov 2, 2025 in New York happens twice - * const ambiguousTime = { year: 2025, month: 11, day: 2, hours: 1, minutes: 30 } + * const ambiguousTime = { year: 2025, month: 11, day: 2, hour: 1, minute: 30 } * const timeZone = DateTime.zoneMakeNamedUnsafe("America/New_York") * - * DateTime.makeZoned(ambiguousTime, { + * const earlier = DateTime.makeZoned(ambiguousTime, { * timeZone, * adjustForTimeZone: true, * disambiguation: "earlier" * }) * // Earlier occurrence (DST time): 2025-11-02T05:30:00.000Z * - * DateTime.makeZoned(ambiguousTime, { + * const later = DateTime.makeZoned(ambiguousTime, { * timeZone, * adjustForTimeZone: true, * disambiguation: "later" @@ -375,21 +375,26 @@ export declare namespace TimeZone { * // Later occurrence (standard time): 2025-11-02T06:30:00.000Z * * // Gap example: 02:30 on Mar 9, 2025 in New York doesn't exist - * const gapTime = { year: 2025, month: 3, day: 9, hours: 2, minutes: 30 } + * const gapTime = { year: 2025, month: 3, day: 9, hour: 2, minute: 30 } * - * DateTime.makeZoned(gapTime, { + * const beforeGap = DateTime.makeZoned(gapTime, { * timeZone, * adjustForTimeZone: true, * disambiguation: "earlier" * }) * // Time before gap: 2025-03-09T06:30:00.000Z (01:30 EST) * - * DateTime.makeZoned(gapTime, { + * const afterGap = DateTime.makeZoned(gapTime, { * timeZone, * adjustForTimeZone: true, * disambiguation: "later" * }) * // Time after gap: 2025-03-09T07:30:00.000Z (03:30 EDT) + * + * earlier.pipe(Option.getOrThrow, DateTime.formatIso) // => "2025-11-02T05:30:00.000Z" + * later.pipe(Option.getOrThrow, DateTime.formatIso) // => "2025-11-02T06:30:00.000Z" + * beforeGap.pipe(Option.getOrThrow, DateTime.formatIso) // => "2025-03-09T06:30:00.000Z" + * afterGap.pipe(Option.getOrThrow, DateTime.formatIso) // => "2025-03-09T07:30:00.000Z" * ``` * * @category models @@ -510,7 +515,7 @@ export const isZoned: (self: DateTime) => self is Zoned = Internal.isZoned * * **Example** (Comparing DateTime values for equivalence) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * * const utc = DateTime.makeUnsafe("2024-01-01T12:00:00Z") @@ -518,7 +523,7 @@ export const isZoned: (self: DateTime) => self is Zoned = Internal.isZoned * timeZone: "Europe/London" * }) * - * console.log(DateTime.Equivalence(utc, zoned)) // true + * DateTime.Equivalence(utc, zoned) // => true * ``` * * @category instances @@ -536,7 +541,7 @@ export const Equivalence: Equ.Equivalence = Internal.Equivalence * * **Example** (Sorting DateTime values chronologically) * - * ```ts + * ```ts import.meta.vitest * import { Array, DateTime } from "effect" * * const dates = [ @@ -545,8 +550,7 @@ export const Equivalence: Equ.Equivalence = Internal.Equivalence * DateTime.makeUnsafe("2024-02-01") * ] * - * const sorted = Array.sort(dates, DateTime.Order) - * // Results in chronological order: 2024-01-01, 2024-02-01, 2024-03-01 + * Array.sort(dates, DateTime.Order).map(DateTime.formatIsoDateUtc) // => ["2024-01-01", "2024-02-01", "2024-03-01"] * ``` * * @category instances @@ -565,15 +569,14 @@ export const Order: order.Order = Internal.Order * * **Example** (Clamping DateTime values) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * * const min = DateTime.makeUnsafe("2024-01-01") * const max = DateTime.makeUnsafe("2024-12-31") * const date = DateTime.makeUnsafe("2025-06-15") * - * const clamped = DateTime.clamp(date, { minimum: min, maximum: max }) - * // clamped equals max (2024-12-31) + * DateTime.clamp(date, { minimum: min, maximum: max }) // => DateTime.makeUnsafe("2024-12-31") * ``` * * @category ordering @@ -602,13 +605,10 @@ export const clamp: { * * **Example** (Creating DateTime values from Dates) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * - * const date = new Date("2024-01-01T12:00:00Z") - * const dateTime = DateTime.fromDateUnsafe(date) - * - * console.log(DateTime.formatIso(dateTime)) // "2024-01-01T12:00:00.000Z" + * DateTime.fromDateUnsafe(new Date("2024-01-01T12:00:00Z")) // => DateTime.makeUnsafe("2024-01-01T12:00:00Z") * ``` * * @category constructors @@ -634,20 +634,17 @@ export const fromDateUnsafe: (date: Date) => Utc = Internal.fromDateUnsafe * * **Example** (Creating DateTime values unsafely) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * * // from Date - * const fromDate = DateTime.makeUnsafe(new Date("2024-01-01T12:00:00Z")) - * console.log(DateTime.formatIso(fromDate)) // "2024-01-01T12:00:00.000Z" + * DateTime.makeUnsafe(new Date("2024-01-01T12:00:00Z")) // => DateTime.makeUnsafe("2024-01-01T12:00:00Z") * * // from parts - * const fromParts = DateTime.makeUnsafe({ year: 2024 }) - * console.log(DateTime.formatIso(fromParts)) // "2024-01-01T00:00:00.000Z" + * DateTime.makeUnsafe({ year: 2024 }) // => DateTime.makeUnsafe("2024-01-01T00:00:00Z") * * // from string - * const fromString = DateTime.makeUnsafe("2024-01-01") - * console.log(DateTime.formatIso(fromString)) // "2024-01-01T00:00:00.000Z" + * DateTime.makeUnsafe("2024-01-01") // => DateTime.makeUnsafe("2024-01-01T00:00:00Z") * ``` * * @category constructors @@ -655,6 +652,22 @@ export const fromDateUnsafe: (date: Date) => Utc = Internal.fromDateUnsafe */ export const makeUnsafe: (input: A) => DateTime.PreserveZone = Internal.makeUnsafe +/** + * Creates a `DateTime.Utc` from the number of seconds since the Unix epoch. + * + * **Example** (Creating from epoch seconds) + * + * ```ts import.meta.vitest + * import { DateTime } from "effect" + * + * DateTime.fromEpochSeconds(1704067200).toJSON() // => "2024-01-01T00:00:00.000Z" + * ``` + * + * @category constructors + * @since 4.0.0 + */ +export const fromEpochSeconds: (seconds: number) => Utc = Internal.fromEpochSeconds + /** * Create a `DateTime.Zoned` using `DateTime.makeUnsafe` and a time zone. * @@ -678,14 +691,14 @@ export const makeUnsafe: (input: A) => DateTime.Preser * * **Example** (Creating zoned DateTime values unsafely) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * * const zoned = DateTime.makeZonedUnsafe("2024-06-15T14:30:00Z", { * timeZone: "Europe/London" * }) * - * console.log(DateTime.formatIsoZoned(zoned)) // "2024-06-15T15:30:00.000+01:00[Europe/London]" + * DateTime.formatIsoZoned(zoned) // => "2024-06-15T15:30:00.000+01:00[Europe/London]" * ``` * * @category constructors @@ -720,17 +733,14 @@ export const makeZonedUnsafe: (input: DateTime.Input, options?: { * * **Example** (Creating optional zoned DateTime values) * - * ```ts - * import { DateTime } from "effect" + * ```ts import.meta.vitest + * import { DateTime, Option } from "effect" * * const result = DateTime.makeZoned("2024-06-15T14:30:00Z", { * timeZone: "Europe/London" * }) * - * console.log(result._tag) // "Some" - * if (result._tag === "Some") { - * console.log(DateTime.formatIsoZoned(result.value)) // "2024-06-15T15:30:00.000+01:00[Europe/London]" - * } + * result.pipe(Option.map(DateTime.formatIsoZoned)) // => Option.some("2024-06-15T15:30:00.000+01:00[Europe/London]") * ``` * * @category constructors @@ -762,23 +772,19 @@ export const makeZoned: ( * * **Example** (Creating optional DateTime values) * - * ```ts - * import { DateTime } from "effect" + * ```ts import.meta.vitest + * import { DateTime, Option } from "effect" * * // from Date - * const fromDate = DateTime.make(new Date("2024-01-01T12:00:00Z")) - * console.log(fromDate._tag) // "Some" + * DateTime.make(new Date("2024-01-01T12:00:00Z")) // => Option.some(DateTime.makeUnsafe("2024-01-01T12:00:00Z")) * * // from parts - * const fromParts = DateTime.make({ year: 2024 }) - * console.log(fromParts._tag) // "Some" + * DateTime.make({ year: 2024 }) // => Option.some(DateTime.makeUnsafe("2024-01-01T00:00:00Z")) * * // from string - * const fromString = DateTime.make("2024-01-01") - * console.log(fromString._tag) // "Some" + * DateTime.make("2024-01-01") // => Option.some(DateTime.makeUnsafe("2024-01-01T00:00:00Z")) * - * const invalid = DateTime.make("not a date") - * console.log(invalid._tag) // "None" + * DateTime.make("not a date") // => Option.none() * ``` * * @category constructors @@ -798,19 +804,15 @@ export const make: (input: A) => Option.Option Option.some("2024-01-01T11:00:00.000+01:00[Europe/Berlin]") * - * const invalid = DateTime.makeZonedFromString("invalid") - * console.log(invalid._tag === "None") // true + * DateTime.makeZonedFromString("2024-01-01T12:00:00Z") // => Option.none() + * DateTime.makeZonedFromString("invalid") // => Option.none() * ``` * * @category constructors @@ -819,17 +821,15 @@ export const make: (input: A) => Option.Option Option.Option = Internal.makeZonedFromString /** - * Gets the current time using the `Clock` service and convert it to a `DateTime`. + * Gets the current time using the `Clock` service and converts it to a `DateTime`. * * **Example** (Getting the current DateTime) * - * ```ts + * ```ts import.meta.vitest * import { DateTime, Effect } from "effect" + * import { TestClock } from "effect/testing" * - * Effect.gen(function*() { - * const now = yield* DateTime.nowAsDate - * console.log(now instanceof Date) // true - * }) + * await Effect.runPromise(Effect.map(DateTime.now, DateTime.isDateTime)) // => true * ``` * * @category constructors @@ -843,12 +843,11 @@ export const now: Effect.Effect = Internal.now * * **Example** (Getting the current Date) * - * ```ts + * ```ts import.meta.vitest * import { DateTime, Effect } from "effect" + * import { TestClock } from "effect/testing" * - * Effect.gen(function*() { - * const now = yield* DateTime.now - * }) + * await Effect.runPromise(Effect.map(DateTime.nowAsDate, (now) => now instanceof Date)) // => true * ``` * * @category constructors @@ -871,11 +870,10 @@ export const nowAsDate: Effect.Effect = Internal.nowAsDate * * **Example** (Getting the current DateTime unsafely) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * - * const now = DateTime.nowUnsafe() - * console.log(DateTime.formatIso(now)) + * Number.isFinite(DateTime.toEpochMillis(DateTime.nowUnsafe())) // => true * ``` * * @category constructors @@ -901,7 +899,7 @@ export const nowUnsafe: LazyArg = Internal.nowUnsafe * * **Example** (Converting DateTime values to UTC) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * * const now = DateTime.makeZonedUnsafe({ year: 2024 }, { @@ -910,9 +908,10 @@ export const nowUnsafe: LazyArg = Internal.nowUnsafe * * // set as UTC * const utc: DateTime.Utc = DateTime.toUtc(now) + * utc // => DateTime.makeUnsafe("2024-01-01T00:00:00Z") * ``` * - * @category time zones + * @category converting * @since 3.13.0 */ export const toUtc: (self: DateTime) => Utc = Internal.toUtc @@ -922,19 +921,16 @@ export const toUtc: (self: DateTime) => Utc = Internal.toUtc * * **Example** (Setting time zones) * - * ```ts - * import { DateTime, Effect } from "effect" + * ```ts import.meta.vitest + * import { DateTime } from "effect" * - * Effect.gen(function*() { - * const now = yield* DateTime.now - * const zone = DateTime.zoneMakeNamedUnsafe("Europe/London") + * const zone = DateTime.zoneMakeNamedUnsafe("Europe/London") + * const zoned: DateTime.Zoned = DateTime.setZone(DateTime.makeUnsafe("2024-01-01"), zone) * - * // set the time zone - * const zoned: DateTime.Zoned = DateTime.setZone(now, zone) - * }) + * DateTime.isZoned(zoned) // => true * ``` * - * @category time zones + * @category transforming * @since 3.6.0 */ export const setZone: { @@ -957,18 +953,16 @@ export const setZone: { * * **Example** (Setting fixed-offset time zones) * - * ```ts - * import { DateTime, Effect } from "effect" + * ```ts import.meta.vitest + * import { DateTime } from "effect" * - * Effect.gen(function*() { - * const now = yield* DateTime.now + * const dateTime = DateTime.makeUnsafe("2024-01-01") + * const zoned: DateTime.Zoned = DateTime.setZoneOffset(dateTime, 3 * 60 * 60 * 1000) * - * // set the offset time zone in milliseconds - * const zoned: DateTime.Zoned = DateTime.setZoneOffset(now, 3 * 60 * 60 * 1000) - * }) + * DateTime.zoneToString(zoned.zone) // => "+03:00" * ``` * - * @category time zones + * @category transforming * @since 3.6.0 */ export const setZoneOffset: { @@ -996,20 +990,17 @@ export const setZoneOffset: { * * **Example** (Creating named time zones unsafely) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * - * const londonZone = DateTime.zoneMakeNamedUnsafe("Europe/London") - * console.log(DateTime.zoneToString(londonZone)) // "Europe/London" - * - * const tokyoZone = DateTime.zoneMakeNamedUnsafe("Asia/Tokyo") - * console.log(DateTime.zoneToString(tokyoZone)) // "Asia/Tokyo" + * DateTime.zoneToString(DateTime.zoneMakeNamedUnsafe("Europe/London")) // => "Europe/London" + * DateTime.zoneToString(DateTime.zoneMakeNamedUnsafe("Asia/Tokyo")) // => "Asia/Tokyo" * * // This would throw an IllegalArgumentError: * // DateTime.zoneMakeNamedUnsafe("Invalid/Zone") * ``` * - * @category time zones + * @category constructors * @since 4.0.0 */ export const zoneMakeNamedUnsafe: (zoneId: string) => TimeZone.Named = Internal.zoneMakeNamedUnsafe @@ -1024,7 +1015,7 @@ export const zoneMakeNamedUnsafe: (zoneId: string) => TimeZone.Named = Internal. * * **Example** (Creating fixed-offset time zones) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * * // Create a time zone with +3 hours offset @@ -1033,9 +1024,10 @@ export const zoneMakeNamedUnsafe: (zoneId: string) => TimeZone.Named = Internal. * const dt = DateTime.makeZonedUnsafe("2024-01-01T12:00:00Z", { * timeZone: zone * }) + * DateTime.formatIsoZoned(dt) // => "2024-01-01T15:00:00.000+03:00" * ``` * - * @category time zones + * @category constructors * @since 3.6.0 */ export const zoneMakeOffset: (offset: number) => TimeZone.Offset = Internal.zoneMakeOffset @@ -1049,17 +1041,14 @@ export const zoneMakeOffset: (offset: number) => TimeZone.Offset = Internal.zone * * **Example** (Creating optional named time zones) * - * ```ts - * import { DateTime } from "effect" - * - * const validZone = DateTime.zoneMakeNamed("Europe/London") - * console.log(validZone._tag === "Some") // true + * ```ts import.meta.vitest + * import { DateTime, Option } from "effect" * - * const invalidZone = DateTime.zoneMakeNamed("Invalid/Zone") - * console.log(invalidZone._tag === "None") // true + * DateTime.zoneMakeNamed("Europe/London").pipe(Option.map(DateTime.zoneToString)) // => Option.some("Europe/London") + * DateTime.zoneMakeNamed("Invalid/Zone") // => Option.none() * ``` * - * @category time zones + * @category constructors * @since 3.6.0 */ export const zoneMakeNamed: (zoneId: string) => Option.Option = Internal.zoneMakeNamed @@ -1074,7 +1063,7 @@ export const zoneMakeNamed: (zoneId: string) => Option.Option = * * **Example** (Creating named time zones effectfully) * - * ```ts + * ```ts import.meta.vitest * import { DateTime, Effect } from "effect" * * const program = Effect.gen(function*() { @@ -1082,9 +1071,11 @@ export const zoneMakeNamed: (zoneId: string) => Option.Option = * const now = yield* DateTime.now * return DateTime.setZone(now, zone) * }) + * + * DateTime.zoneToString((await Effect.runPromise(program)).zone) // => "Europe/London" * ``` * - * @category time zones + * @category constructors * @since 3.6.0 */ export const zoneMakeNamedEffect: (zoneId: string) => Effect.Effect = @@ -1100,14 +1091,13 @@ export const zoneMakeNamedEffect: (zoneId: string) => Effect.Effect true * ``` * - * @category time zones + * @category constructors * @since 3.6.0 */ export const zoneMakeLocal: () => TimeZone.Named = Internal.zoneMakeLocal @@ -1121,19 +1111,15 @@ export const zoneMakeLocal: () => TimeZone.Named = Internal.zoneMakeLocal * * **Example** (Parsing time zones) * - * ```ts - * import { DateTime } from "effect" - * - * const namedZone = DateTime.zoneFromString("Europe/London") - * const offsetZone = DateTime.zoneFromString("+03:00") - * const invalid = DateTime.zoneFromString("invalid") + * ```ts import.meta.vitest + * import { DateTime, Option } from "effect" * - * console.log(namedZone._tag === "Some") // true - * console.log(offsetZone._tag === "Some") // true - * console.log(invalid._tag === "None") // true + * DateTime.zoneFromString("Europe/London").pipe(Option.map(DateTime.zoneToString)) // => Option.some("Europe/London") + * DateTime.zoneFromString("+03:00").pipe(Option.map(DateTime.zoneToString)) // => Option.some("+03:00") + * DateTime.zoneFromString("invalid") // => Option.none() * ``` * - * @category time zones + * @category decoding * @since 3.6.0 */ export const zoneFromString: (zone: string) => Option.Option = Internal.zoneFromString @@ -1143,17 +1129,14 @@ export const zoneFromString: (zone: string) => Option.Option = Interna * * **Example** (Formatting time zones) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * - * // Outputs "+03:00" - * DateTime.zoneToString(DateTime.zoneMakeOffset(3 * 60 * 60 * 1000)) - * - * // Outputs "Europe/London" - * DateTime.zoneToString(DateTime.zoneMakeNamedUnsafe("Europe/London")) + * DateTime.zoneToString(DateTime.zoneMakeOffset(3 * 60 * 60 * 1000)) // => "+03:00" + * DateTime.zoneToString(DateTime.zoneMakeNamedUnsafe("Europe/London")) // => "Europe/London" * ``` * - * @category time zones + * @category encoding * @since 3.6.0 */ export const zoneToString: (self: TimeZone) => string = Internal.zoneToString @@ -1164,17 +1147,16 @@ export const zoneToString: (self: TimeZone) => string = Internal.zoneToString * * **Example** (Setting named time zones safely) * - * ```ts - * import { DateTime, Effect } from "effect" + * ```ts import.meta.vitest + * import { DateTime, Option } from "effect" * - * Effect.gen(function*() { - * const now = yield* DateTime.now - * // set the time zone, returns an Option - * DateTime.setZoneNamed(now, "Europe/London") - * }) + * const dateTime = DateTime.makeUnsafe("2024-01-01") + * const result = DateTime.setZoneNamed(dateTime, "Europe/London").pipe(Option.map(DateTime.formatIsoZoned)) + * + * result // => Option.some("2024-01-01T00:00:00.000+00:00[Europe/London]") * ``` * - * @category time zones + * @category transforming * @since 3.6.0 */ export const setZoneNamed: { @@ -1194,17 +1176,16 @@ export const setZoneNamed: { * * **Example** (Setting named time zones unsafely) * - * ```ts - * import { DateTime, Effect } from "effect" + * ```ts import.meta.vitest + * import { DateTime } from "effect" * - * Effect.gen(function*() { - * const now = yield* DateTime.now - * // set the time zone - * DateTime.setZoneNamedUnsafe(now, "Europe/London") - * }) + * const dateTime = DateTime.makeUnsafe("2024-01-01") + * const zoned = DateTime.setZoneNamedUnsafe(dateTime, "Europe/London") + * + * DateTime.zoneToString(zoned.zone) // => "Europe/London" * ``` * - * @category time zones + * @category transforming * @since 4.0.0 */ export const setZoneNamedUnsafe: { @@ -1234,16 +1215,13 @@ export const setZoneNamedUnsafe: { * * **Example** (Measuring distance between DateTime values) * - * ```ts - * import { DateTime, Effect } from "effect" + * ```ts import.meta.vitest + * import { DateTime, Duration } from "effect" * - * Effect.gen(function*() { - * const now = yield* DateTime.now - * const other = DateTime.add(now, { minutes: 1 }) + * const start = DateTime.makeUnsafe("2024-01-01T00:00:00Z") + * const end = DateTime.add(start, { minutes: 1 }) * - * // returns Duration.minutes(1) - * DateTime.distance(now, other) - * }) + * DateTime.distance(start, end) // => Duration.minutes(1) * ``` * * @category comparisons @@ -1259,14 +1237,13 @@ export const distance: { * * **Example** (Selecting the earlier DateTime) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * * const date1 = DateTime.makeUnsafe("2024-01-01") * const date2 = DateTime.makeUnsafe("2024-02-01") * - * const earlier = DateTime.min(date1, date2) - * // earlier equals date1 (2024-01-01) + * DateTime.min(date1, date2) // => DateTime.makeUnsafe("2024-01-01") * ``` * * @category comparisons @@ -1282,14 +1259,13 @@ export const min: { * * **Example** (Selecting the later DateTime) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * * const date1 = DateTime.makeUnsafe("2024-01-01") * const date2 = DateTime.makeUnsafe("2024-02-01") * - * const later = DateTime.max(date1, date2) - * // later equals date2 (2024-02-01) + * DateTime.max(date1, date2) // => DateTime.makeUnsafe("2024-02-01") * ``` * * @category comparisons @@ -1305,14 +1281,14 @@ export const max: { * * **Example** (Checking whether a DateTime is later) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * * const date1 = DateTime.makeUnsafe("2024-02-01") * const date2 = DateTime.makeUnsafe("2024-01-01") * - * console.log(DateTime.isGreaterThan(date1, date2)) // true - * console.log(DateTime.isGreaterThan(date2, date1)) // false + * DateTime.isGreaterThan(date1, date2) // => true + * DateTime.isGreaterThan(date2, date1) // => false * ``` * * @category comparisons @@ -1328,16 +1304,16 @@ export const isGreaterThan: { * * **Example** (Checking whether a DateTime is later or equal) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * * const date1 = DateTime.makeUnsafe("2024-01-01") * const date2 = DateTime.makeUnsafe("2024-01-01") * const date3 = DateTime.makeUnsafe("2024-02-01") * - * console.log(DateTime.isGreaterThanOrEqualTo(date1, date2)) // true - * console.log(DateTime.isGreaterThanOrEqualTo(date3, date1)) // true - * console.log(DateTime.isGreaterThanOrEqualTo(date1, date3)) // false + * DateTime.isGreaterThanOrEqualTo(date1, date2) // => true + * DateTime.isGreaterThanOrEqualTo(date3, date1) // => true + * DateTime.isGreaterThanOrEqualTo(date1, date3) // => false * ``` * * @category comparisons @@ -1353,14 +1329,14 @@ export const isGreaterThanOrEqualTo: { * * **Example** (Checking whether a DateTime is earlier) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * * const date1 = DateTime.makeUnsafe("2024-01-01") * const date2 = DateTime.makeUnsafe("2024-02-01") * - * console.log(DateTime.isLessThan(date1, date2)) // true - * console.log(DateTime.isLessThan(date2, date1)) // false + * DateTime.isLessThan(date1, date2) // => true + * DateTime.isLessThan(date2, date1) // => false * ``` * * @category comparisons @@ -1376,16 +1352,16 @@ export const isLessThan: { * * **Example** (Checking whether a DateTime is earlier or equal) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * * const date1 = DateTime.makeUnsafe("2024-01-01") * const date2 = DateTime.makeUnsafe("2024-01-01") * const date3 = DateTime.makeUnsafe("2024-02-01") * - * console.log(DateTime.isLessThanOrEqualTo(date1, date2)) // true - * console.log(DateTime.isLessThanOrEqualTo(date1, date3)) // true - * console.log(DateTime.isLessThanOrEqualTo(date3, date1)) // false + * DateTime.isLessThanOrEqualTo(date1, date2) // => true + * DateTime.isLessThanOrEqualTo(date1, date3) // => true + * DateTime.isLessThanOrEqualTo(date3, date1) // => false * ``` * * @category comparisons @@ -1401,14 +1377,14 @@ export const isLessThanOrEqualTo: { * * **Example** (Checking whether a DateTime is within bounds) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * * const min = DateTime.makeUnsafe("2024-01-01") * const max = DateTime.makeUnsafe("2024-12-31") * const date = DateTime.makeUnsafe("2024-06-15") * - * console.log(DateTime.between(date, { minimum: min, maximum: max })) // true + * DateTime.between(date, { minimum: min, maximum: max }) // => true * ``` * * @category comparisons @@ -1428,14 +1404,12 @@ export const between: { * * **Example** (Checking future DateTime values effectfully) * - * ```ts + * ```ts import.meta.vitest * import { DateTime, Effect } from "effect" + * import { TestClock } from "effect/testing" * - * const program = Effect.gen(function*() { - * const futureDate = DateTime.add(yield* DateTime.now, { hours: 1 }) - * const isFuture = yield* DateTime.isFuture(futureDate) - * console.log(isFuture) // true - * }) + * const futureDate = DateTime.makeUnsafe(1) + * await Effect.runPromise(Effect.provide(DateTime.isFuture(futureDate), TestClock.layer())) // => true * ``` * * @category comparisons @@ -1460,11 +1434,8 @@ export const isFuture: (self: DateTime) => Effect.Effect = Internal.isF * ```ts * import { DateTime } from "effect" * - * const now = DateTime.nowUnsafe() - * const futureDate = DateTime.add(now, { hours: 1 }) - * - * console.log(DateTime.isFutureUnsafe(futureDate)) // true - * console.log(DateTime.isFutureUnsafe(now)) // false + * const oneHourFromNow = DateTime.add(DateTime.nowUnsafe(), { hours: 1 }) + * DateTime.isFutureUnsafe(oneHourFromNow) * ``` * * @category comparisons @@ -1481,14 +1452,12 @@ export const isFutureUnsafe: (self: DateTime) => boolean = Internal.isFutureUnsa * * **Example** (Checking past DateTime values effectfully) * - * ```ts + * ```ts import.meta.vitest * import { DateTime, Effect } from "effect" + * import { TestClock } from "effect/testing" * - * const program = Effect.gen(function*() { - * const pastDate = DateTime.subtract(yield* DateTime.now, { hours: 1 }) - * const isPast = yield* DateTime.isPast(pastDate) - * console.log(isPast) // true - * }) + * const pastDate = DateTime.makeUnsafe(-1) + * await Effect.runPromise(Effect.provide(DateTime.isPast(pastDate), TestClock.layer())) // => true * ``` * * @category comparisons @@ -1513,11 +1482,8 @@ export const isPast: (self: DateTime) => Effect.Effect = Internal.isPas * ```ts * import { DateTime } from "effect" * - * const now = DateTime.nowUnsafe() - * const pastDate = DateTime.subtract(now, { hours: 1 }) - * - * console.log(DateTime.isPastUnsafe(pastDate)) // true - * console.log(DateTime.isPastUnsafe(now)) // false + * const oneHourAgo = DateTime.subtract(DateTime.nowUnsafe(), { hours: 1 }) + * DateTime.isPastUnsafe(oneHourAgo) * ``` * * @category comparisons @@ -1538,15 +1504,14 @@ export const isPastUnsafe: (self: DateTime) => boolean = Internal.isPastUnsafe * * **Example** (Converting DateTime values to UTC Dates) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * * const dt = DateTime.makeZonedUnsafe("2024-01-01T12:00:00Z", { * timeZone: "Europe/London" * }) * - * const utcDate = DateTime.toDateUtc(dt) - * console.log(utcDate.toISOString()) // "2024-01-01T12:00:00.000Z" + * DateTime.toDateUtc(dt).toISOString() // => "2024-01-01T12:00:00.000Z" * ``` * * @category converting @@ -1564,7 +1529,7 @@ export const toDateUtc: (self: DateTime) => Date = Internal.toDateUtc * * **Example** (Converting DateTime values to Dates) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * * const utc = DateTime.makeUnsafe("2024-01-01T12:00:00Z") @@ -1572,8 +1537,8 @@ export const toDateUtc: (self: DateTime) => Date = Internal.toDateUtc * timeZone: "Europe/London" * }) * - * console.log(DateTime.toDate(utc).toISOString()) - * console.log(DateTime.toDate(zoned).toISOString()) + * DateTime.toDate(utc).toISOString() // => "2024-01-01T12:00:00.000Z" + * DateTime.toDate(zoned).toISOString() // => "2024-01-01T12:00:00.000Z" * ``` * * @category converting @@ -1591,15 +1556,14 @@ export const toDate: (self: DateTime) => Date = Internal.toDate * * **Example** (Reading zoned offsets) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * * const zoned = DateTime.makeZonedUnsafe("2024-01-01T12:00:00Z", { * timeZone: "Europe/London" * }) * - * const offset = DateTime.zonedOffset(zoned) - * console.log(offset) // 0 (London is UTC+0 in winter) + * DateTime.zonedOffset(zoned) // => 0 * ``` * * @category converting @@ -1616,15 +1580,14 @@ export const zonedOffset: (self: Zoned) => number = Internal.zonedOffset * * **Example** (Formatting zoned offsets) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * * const zoned = DateTime.makeZonedUnsafe("2024-01-01T12:00:00Z", { * timeZone: DateTime.zoneMakeOffset(3 * 60 * 60 * 1000) // +3 hours * }) * - * const offsetString = DateTime.zonedOffsetIso(zoned) - * console.log(offsetString) // "+03:00" + * DateTime.zonedOffsetIso(zoned) // => "+03:00" * ``` * * @category converting @@ -1641,13 +1604,11 @@ export const zonedOffsetIso: (self: Zoned) => string = Internal.zonedOffsetIso * * **Example** (Reading epoch milliseconds) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * * const dt = DateTime.makeUnsafe("2024-01-01T00:00:00Z") - * const epochMillis = DateTime.toEpochMillis(dt) - * - * console.log(epochMillis) // 1704067200000 + * DateTime.toEpochMillis(dt) // => 1704067200000 * ``` * * @category converting @@ -1655,13 +1616,35 @@ export const zonedOffsetIso: (self: Zoned) => string = Internal.zonedOffsetIso */ export const toEpochMillis: (self: DateTime) => number = Internal.toEpochMillis +/** + * Converts a `DateTime` to the number of seconds since the Unix epoch. + * + * **Details** + * + * This returns the UTC timestamp regardless of any time zone information. + * The result is floored to the nearest second. + * + * **Example** (Reading epoch seconds) + * + * ```ts import.meta.vitest + * import { DateTime } from "effect" + * + * const dt = DateTime.makeUnsafe("2024-01-01T00:00:00Z") + * DateTime.toEpochSeconds(dt) // => 1704067200 + * ``` + * + * @category converting + * @since 4.0.0 + */ +export const toEpochSeconds: (self: DateTime) => number = Internal.toEpochSeconds + /** * Removes the time aspect of a `DateTime`, first adjusting for the time * zone. It will return a `DateTime.Utc` only containing the date. * * **Example** (Removing time components) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * * // returns "2024-01-01T00:00:00Z" @@ -1671,7 +1654,7 @@ export const toEpochMillis: (self: DateTime) => number = Internal.toEpochMillis * }).pipe( * DateTime.removeTime, * DateTime.formatIso - * ) + * ) // => "2024-01-01T00:00:00.000Z" * ``` * * @category converting @@ -1692,26 +1675,16 @@ export const removeTime: (self: DateTime) => Utc = Internal.removeTime * * **Example** (Reading DateTime parts) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * * const dt = DateTime.makeUnsafe("2024-01-01T12:30:45.123Z") * const parts = DateTime.toParts(dt) * - * console.log(parts) - * // { - * // year: 2024, - * // month: 1, - * // day: 1, - * // hours: 12, - * // minutes: 30, - * // seconds: 45, - * // millis: 123, - * // weekDay: 1 // Monday - * // } + * const selectedParts = [parts.year, parts.month, parts.day, parts.hour] // => [2024, 1, 1, 12] * ``` * - * @category parts + * @category getters * @since 3.6.0 */ export const toParts: (self: DateTime) => DateTime.PartsWithWeekday = Internal.toParts @@ -1725,7 +1698,7 @@ export const toParts: (self: DateTime) => DateTime.PartsWithWeekday = Internal.t * * **Example** (Reading UTC DateTime parts) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * * const zoned = DateTime.makeZonedUnsafe("2024-01-01T12:30:45.123Z", { @@ -1733,11 +1706,10 @@ export const toParts: (self: DateTime) => DateTime.PartsWithWeekday = Internal.t * }) * const parts = DateTime.toPartsUtc(zoned) * - * console.log(parts) - * // Always returns UTC parts regardless of time zone + * const selectedParts = [parts.year, parts.month, parts.day, parts.hour] // => [2024, 1, 1, 12] * ``` * - * @category parts + * @category getters * @since 3.6.0 */ export const toPartsUtc: (self: DateTime) => DateTime.PartsWithWeekday = Internal.toPartsUtc @@ -1751,15 +1723,14 @@ export const toPartsUtc: (self: DateTime) => DateTime.PartsWithWeekday = Interna * * **Example** (Reading UTC DateTime parts by key) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * * const dateTime = DateTime.makeUnsafe({ year: 2024 }) - * const year = DateTime.getPartUtc(dateTime, "year") - * console.log(year) // 2024 + * DateTime.getPartUtc(dateTime, "year") // => 2024 * ``` * - * @category parts + * @category getters * @since 3.6.0 */ export const getPartUtc: { @@ -1776,17 +1747,16 @@ export const getPartUtc: { * * **Example** (Reading DateTime parts by key) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * * const dateTime = DateTime.makeZonedUnsafe({ year: 2024 }, { * timeZone: "Europe/London" * }) - * const year = DateTime.getPart(dateTime, "year") - * console.log(year) // 2024 + * DateTime.getPart(dateTime, "year") // => 2024 * ``` * - * @category parts + * @category getters * @since 3.6.0 */ export const getPart: { @@ -1803,20 +1773,20 @@ export const getPart: { * * **Example** (Updating DateTime parts) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * - * const dt = DateTime.makeUnsafe("2024-01-01T12:00:00Z") + * const dt = DateTime.makeZonedUnsafe("2024-01-01T12:00:00Z", { timeZone: "UTC" }) * const updated = DateTime.setParts(dt, { * year: 2025, * month: 6, * day: 15 * }) * - * console.log(DateTime.formatIso(updated)) // "2025-06-15T12:00:00.000Z" + * updated // => DateTime.makeZonedUnsafe("2025-06-15T12:00:00Z", { timeZone: "UTC" }) * ``` * - * @category parts + * @category transforming * @since 3.6.0 */ export const setParts: { @@ -1833,7 +1803,7 @@ export const setParts: { * * **Example** (Updating UTC DateTime parts) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * * const dt = DateTime.makeUnsafe("2024-01-01T12:00:00Z") @@ -1842,10 +1812,10 @@ export const setParts: { * hour: 18 * }) * - * console.log(DateTime.formatIso(updated)) // "2025-01-01T18:00:00.000Z" + * updated // => DateTime.makeUnsafe("2025-01-01T18:00:00Z") * ``` * - * @category parts + * @category transforming * @since 3.6.0 */ export const setPartsUtc: { @@ -1869,21 +1839,19 @@ export const setPartsUtc: { * * **Example** (Accessing the current time zone service) * - * ```ts + * ```ts import.meta.vitest * import { DateTime, Effect } from "effect" * * const program = Effect.gen(function*() { - * // Access the current time zone service - * const zone = yield* DateTime.CurrentTimeZone - * console.log(DateTime.zoneToString(zone)) + * return DateTime.zoneToString(yield* DateTime.CurrentTimeZone) * }) * * // Provide a time zone * const layer = DateTime.layerCurrentZoneNamed("Europe/London") - * Effect.provide(program, layer) + * await Effect.runPromise(Effect.provide(program, layer)) // => "Europe/London" * ``` * - * @category current time zone + * @category services * @since 3.11.0 */ export class CurrentTimeZone extends Context.Service()( @@ -1896,18 +1864,16 @@ export class CurrentTimeZone extends Context.Service( * * **Example** (Setting the current time zone) * - * ```ts + * ```ts import.meta.vitest * import { DateTime, Effect } from "effect" * - * Effect.gen(function*() { - * const now = yield* DateTime.now - * - * // set the time zone to "Europe/London" - * const zoned = yield* DateTime.setZoneCurrent(now) - * }).pipe(DateTime.withCurrentZoneNamed("Europe/London")) + * await Effect.runPromise(Effect.gen(function*() { + * const zoned = yield* DateTime.setZoneCurrent(DateTime.makeUnsafe("2024-01-01")) + * return DateTime.zoneToString(zoned.zone) + * }).pipe(DateTime.withCurrentZoneNamed("Europe/London"))) // => "Europe/London" * ``` * - * @category current time zone + * @category accessors * @since 3.6.0 */ export const setZoneCurrent = (self: DateTime): Effect.Effect => @@ -1918,17 +1884,18 @@ export const setZoneCurrent = (self: DateTime): Effect.Effect "Europe/London" * ``` * - * @category current time zone + * @category providing services * @since 3.6.0 */ export const withCurrentZone: { @@ -1942,16 +1909,15 @@ export const withCurrentZone: { * * **Example** (Providing the local time zone) * - * ```ts + * ```ts import.meta.vitest * import { DateTime, Effect } from "effect" * - * Effect.gen(function*() { - * // will use the system's local time zone - * const now = yield* DateTime.nowInCurrentZone - * }).pipe(DateTime.withCurrentZoneLocal) + * await Effect.runPromise(Effect.gen(function*() { + * return DateTime.isZoned(yield* DateTime.nowInCurrentZone) + * }).pipe(DateTime.withCurrentZoneLocal)) // => true * ``` * - * @category current time zone + * @category providing services * @since 3.6.0 */ export const withCurrentZoneLocal = ( @@ -1964,16 +1930,17 @@ export const withCurrentZoneLocal = ( * * **Example** (Providing a fixed-offset time zone) * - * ```ts + * ```ts import.meta.vitest * import { DateTime, Effect } from "effect" * - * Effect.gen(function*() { - * const zone = yield* DateTime.CurrentTimeZone - * console.log(DateTime.zoneToString(zone)) // "+03:00" + * const program = Effect.gen(function*() { + * return DateTime.zoneToString(yield* DateTime.CurrentTimeZone) * }).pipe(DateTime.withCurrentZoneOffset(3 * 60 * 60 * 1000)) + * + * await Effect.runPromise(program) // => "+03:00" * ``` * - * @category current time zone + * @category providing services * @since 3.6.0 */ export const withCurrentZoneOffset: { @@ -1997,16 +1964,16 @@ export const withCurrentZoneOffset: { * * **Example** (Providing a named time zone) * - * ```ts + * ```ts import.meta.vitest * import { DateTime, Effect } from "effect" * - * Effect.gen(function*() { - * // will use the "Europe/London" time zone - * const now = yield* DateTime.nowInCurrentZone - * }).pipe(DateTime.withCurrentZoneNamed("Europe/London")) + * await Effect.runPromise(Effect.gen(function*() { + * const zoned = yield* DateTime.setZoneCurrent(DateTime.makeUnsafe("2024-01-01")) + * return DateTime.zoneToString(zoned.zone) + * }).pipe(DateTime.withCurrentZoneNamed("Europe/London"))) // => "Europe/London" * ``` * - * @category current time zone + * @category providing services * @since 3.6.0 */ export const withCurrentZoneNamed: { @@ -2031,16 +1998,15 @@ export const withCurrentZoneNamed: { * * **Example** (Getting the current time in the current zone) * - * ```ts + * ```ts import.meta.vitest * import { DateTime, Effect } from "effect" * - * Effect.gen(function*() { - * // will use the "Europe/London" time zone - * const now = yield* DateTime.nowInCurrentZone - * }).pipe(DateTime.withCurrentZoneNamed("Europe/London")) + * await Effect.runPromise(Effect.gen(function*() { + * return DateTime.zoneToString((yield* DateTime.nowInCurrentZone).zone) + * }).pipe(DateTime.withCurrentZoneNamed("Europe/London"))) // => "Europe/London" * ``` * - * @category current time zone + * @category accessors * @since 3.6.0 */ export const nowInCurrentZone: Effect.Effect = Effect.flatMap(now, setZoneCurrent) @@ -2066,17 +2032,15 @@ export const nowInCurrentZone: Effect.Effect = Ef * * **Example** (Mutating DateTime values with Dates) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * * const dt = DateTime.makeUnsafe("2024-01-01T12:00:00Z") * - * const modified = DateTime.mutate(dt, (date) => { + * DateTime.mutate(dt, (date) => { * date.setHours(15) // Set to 3 PM * date.setMinutes(30) // Set to 30 minutes * }) - * - * console.log(DateTime.formatIso(modified)) // "2024-01-01T15:30:00.000Z" * ``` * * @category mapping @@ -2108,7 +2072,7 @@ export const mutate: { * * **Example** (Mutating DateTime values with UTC Dates) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * * const dt = DateTime.makeZonedUnsafe("2024-01-01T12:00:00Z", { @@ -2119,7 +2083,7 @@ export const mutate: { * date.setUTCHours(18) // Set UTC time to 6 PM * }) * - * console.log(DateTime.formatIso(modified)) // "2024-01-01T18:00:00.000Z" + * modified // => DateTime.makeZonedUnsafe("2024-01-01T18:00:00Z", { timeZone: "Europe/London" }) * ``` * * @category mapping @@ -2136,13 +2100,14 @@ export const mutateUtc: { * * **Example** (Mapping epoch milliseconds) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * * // add 10 milliseconds - * DateTime.makeUnsafe(0).pipe( + * const result = DateTime.makeUnsafe(0).pipe( * DateTime.mapEpochMillis((millis) => millis + 10) * ) + * result // => DateTime.makeUnsafe(10) * ``` * * @category mapping @@ -2165,13 +2130,13 @@ export const mapEpochMillis: { * * **Example** (Applying time zone adjusted Dates) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * * // get the time zone adjusted date in milliseconds * DateTime.makeZonedUnsafe(0, { timeZone: "Europe/London" }).pipe( * DateTime.withDate((date) => date.getTime()) - * ) + * ) // => 3600000 * ``` * * @category mapping @@ -2193,13 +2158,13 @@ export const withDate: { * * **Example** (Applying UTC Dates) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * * // get the date in milliseconds * DateTime.makeUnsafe(0).pipe( * DateTime.withDateUtc((date) => date.getTime()) - * ) + * ) // => 0 * ``` * * @category mapping @@ -2215,7 +2180,7 @@ export const withDateUtc: { * * **Example** (Pattern matching DateTime variants) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * * const dt1 = DateTime.makeUnsafe("2024-01-01T12:00:00Z") // Utc @@ -2233,8 +2198,8 @@ export const withDateUtc: { * onZoned: (zoned) => `Zoned: ${DateTime.formatIsoZoned(zoned)}` * }) * - * console.log(result1) // "UTC: 2024-01-01T12:00:00.000Z" - * console.log(result2) // "Zoned: 2024-06-15T15:30:00.000+01:00[Europe/London]" + * result1 // => "UTC: 2024-01-01T12:00:00.000Z" + * result2 // => "Zoned: 2024-06-15T15:30:00.000+01:00[Europe/London]" * ``` * * @category mapping @@ -2276,13 +2241,13 @@ export const match: { * * **Example** (Adding durations) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * * // add 5 minutes * DateTime.makeUnsafe(0).pipe( * DateTime.addDuration("5 minutes") - * ) + * ) // => DateTime.makeUnsafe(300000) * ``` * * @see {@link add} for calendar-aware date/time part arithmetic @@ -2301,13 +2266,13 @@ export const addDuration: { * * **Example** (Subtracting durations) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * * // subtract 5 minutes * DateTime.makeUnsafe(0).pipe( * DateTime.subtractDuration("5 minutes") - * ) + * ) // => DateTime.makeUnsafe(-300000) * ``` * * @category math @@ -2328,13 +2293,13 @@ export const subtractDuration: { * * **Example** (Adding date and time parts) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * * // add 5 minutes * DateTime.makeUnsafe(0).pipe( * DateTime.add({ minutes: 5 }) - * ) + * ) // => DateTime.makeUnsafe(300000) * ``` * * @category math @@ -2350,13 +2315,13 @@ export const add: { * * **Example** (Subtracting date and time parts) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * * // subtract 5 minutes * DateTime.makeUnsafe(0).pipe( * DateTime.subtract({ minutes: 5 }) - * ) + * ) // => DateTime.makeUnsafe(-300000) * ``` * * @category math @@ -2377,14 +2342,13 @@ export const subtract: { * * **Example** (Rounding down DateTime values) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * * // returns "2024-01-01T00:00:00Z" * DateTime.makeUnsafe("2024-01-01T12:00:00Z").pipe( * DateTime.startOf("day"), - * DateTime.formatIso - * ) + * ) // => DateTime.makeUnsafe("2024-01-01T00:00:00Z") * ``` * * @category math @@ -2412,14 +2376,13 @@ export const startOf: { * * **Example** (Rounding up DateTime values) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * * // returns "2024-01-01T23:59:59.999Z" * DateTime.makeUnsafe("2024-01-01T12:00:00Z").pipe( * DateTime.endOf("day"), - * DateTime.formatIso - * ) + * ) // => DateTime.makeUnsafe("2024-01-01T23:59:59.999Z") * ``` * * @category math @@ -2447,14 +2410,13 @@ export const endOf: { * * **Example** (Rounding DateTime values to nearest units) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * * // returns "2024-01-02T00:00:00Z" * DateTime.makeUnsafe("2024-01-01T12:01:00Z").pipe( * DateTime.nearest("day"), - * DateTime.formatIso - * ) + * ) // => DateTime.makeUnsafe("2024-01-02T00:00:00Z") * ``` * * @category math @@ -2490,20 +2452,18 @@ export const nearest: { * * **Example** (Formatting DateTime values with Intl options) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * * const dt = DateTime.makeZonedUnsafe("2024-06-15T14:30:00Z", { * timeZone: "Europe/London" * }) * - * const formatted = DateTime.format(dt, { + * DateTime.format(dt, { * dateStyle: "full", * timeStyle: "short", * locale: "en-US" - * }) - * - * console.log(formatted) // "Saturday, June 15, 2024 at 3:30 PM" + * }) // => "Saturday, June 15, 2024 at 3:30 PM" * ``` * * @category formatting @@ -2533,21 +2493,19 @@ export const format: { * * **Example** (Formatting DateTime values locally) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * * const dt = DateTime.makeUnsafe("2024-06-15T14:30:00Z") * * // Uses system local time zone and locale - * const local = DateTime.formatLocal(dt, { + * DateTime.formatLocal(dt, { * year: "numeric", * month: "long", * day: "numeric", * hour: "2-digit", * minute: "2-digit" * }) - * - * console.log(local) // Output depends on system locale/timezone * ``` * * @category formatting @@ -2580,7 +2538,7 @@ export const formatLocal: { * * **Example** (Formatting DateTime values in UTC) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * * const dt = DateTime.makeZonedUnsafe("2024-06-15T14:30:00Z", { @@ -2588,7 +2546,7 @@ export const formatLocal: { * }) * * // Force UTC formatting regardless of time zone - * const utcFormatted = DateTime.formatUtc(dt, { + * DateTime.formatUtc(dt, { * year: "numeric", * month: "2-digit", * day: "2-digit", @@ -2596,8 +2554,6 @@ export const formatLocal: { * minute: "2-digit", * timeZoneName: "short" * }) - * - * console.log(utcFormatted) // "06/15/2024, 02:30 PM UTC" * ``` * * @category formatting @@ -2636,7 +2592,7 @@ export const formatUtc: { * * **Example** (Formatting DateTime values with custom formatters) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * * const dt = DateTime.makeUnsafe("2024-06-15T14:30:00Z") @@ -2651,8 +2607,7 @@ export const formatUtc: { * timeZone: "Europe/Berlin" * }) * - * const formatted = DateTime.formatIntl(dt, formatter) - * console.log(formatted.length > 0) // true + * DateTime.formatIntl(dt, formatter).length > 0 // => true * ``` * * @see {@link formatUtc} for formatting with options forced to UTC @@ -2675,16 +2630,15 @@ export const formatIntl: { * * **Example** (Formatting DateTime values as ISO strings) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * - * const dt = DateTime.makeUnsafe("2024-01-01T12:30:45.123Z") - * console.log(DateTime.formatIso(dt)) // "2024-01-01T12:30:45.123Z" + * DateTime.formatIso(DateTime.makeUnsafe("2024-01-01T12:30:45.123Z")) // => "2024-01-01T12:30:45.123Z" * * const zoned = DateTime.makeZonedUnsafe("2024-01-01T12:30:45.123Z", { * timeZone: "Europe/London" * }) - * console.log(DateTime.formatIso(zoned)) // "2024-01-01T12:30:45.123Z" + * DateTime.formatIso(zoned) // => "2024-01-01T12:30:45.123Z" * ``` * * @category formatting @@ -2701,16 +2655,16 @@ export const formatIso: (self: DateTime) => string = Internal.formatIso * * **Example** (Formatting DateTime values as ISO dates) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * * const dt = DateTime.makeUnsafe("2024-01-01T23:30:00Z") - * console.log(DateTime.formatIsoDate(dt)) // "2024-01-01" + * DateTime.formatIsoDate(dt) // => "2024-01-01" * * const zoned = DateTime.makeZonedUnsafe("2024-01-01T23:30:00Z", { * timeZone: "Pacific/Auckland" // UTC+12/13 * }) - * console.log(DateTime.formatIsoDate(zoned)) // "2024-01-02" (next day in Auckland) + * DateTime.formatIsoDate(zoned) // => "2024-01-02" * ``` * * @category formatting @@ -2727,16 +2681,16 @@ export const formatIsoDate: (self: DateTime) => string = Internal.formatIsoDate * * **Example** (Formatting DateTime values as UTC ISO dates) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * * const dt = DateTime.makeUnsafe("2024-01-01T23:30:00Z") - * console.log(DateTime.formatIsoDateUtc(dt)) // "2024-01-01" + * DateTime.formatIsoDateUtc(dt) // => "2024-01-01" * * const zoned = DateTime.makeZonedUnsafe("2024-01-01T23:30:00Z", { * timeZone: "Pacific/Auckland" * }) - * console.log(DateTime.formatIsoDateUtc(zoned)) // "2024-01-01" (always UTC) + * DateTime.formatIsoDateUtc(zoned) // => "2024-01-01" * ``` * * @category formatting @@ -2754,16 +2708,16 @@ export const formatIsoDateUtc: (self: DateTime) => string = Internal.formatIsoDa * * **Example** (Formatting DateTime values with offsets) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * * const utc = DateTime.makeUnsafe("2024-01-01T12:00:00Z") - * console.log(DateTime.formatIsoOffset(utc)) // "2024-01-01T12:00:00.000Z" + * DateTime.formatIsoOffset(utc) // => "2024-01-01T12:00:00.000Z" * * const zoned = DateTime.makeZonedUnsafe("2024-01-01T12:00:00Z", { * timeZone: DateTime.zoneMakeOffset(3 * 60 * 60 * 1000) * }) - * console.log(DateTime.formatIsoOffset(zoned)) // "2024-01-01T15:00:00.000+03:00" + * DateTime.formatIsoOffset(zoned) // => "2024-01-01T15:00:00.000+03:00" * ``` * * @category formatting @@ -2780,22 +2734,20 @@ export const formatIsoOffset: (self: DateTime) => string = Internal.formatIsoOff * * **Example** (Formatting zoned DateTime values) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * * const zoned = DateTime.makeZonedUnsafe("2024-06-15T14:30:45.123Z", { * timeZone: "Europe/London" * }) * - * const formatted = DateTime.formatIsoZoned(zoned) - * console.log(formatted) // "2024-06-15T15:30:45.123+01:00[Europe/London]" + * DateTime.formatIsoZoned(zoned) // => "2024-06-15T15:30:45.123+01:00[Europe/London]" * * const offsetZone = DateTime.makeZonedUnsafe("2024-06-15T14:30:45.123Z", { * timeZone: DateTime.zoneMakeOffset(3 * 60 * 60 * 1000) * }) * - * const offsetFormatted = DateTime.formatIsoZoned(offsetZone) - * console.log(offsetFormatted) // "2024-06-15T17:30:45.123+03:00" + * DateTime.formatIsoZoned(offsetZone) // => "2024-06-15T17:30:45.123+03:00" * ``` * * @category formatting @@ -2812,7 +2764,7 @@ export const formatIsoZoned: (self: Zoned) => string = Internal.formatIsoZoned * * **Example** (Providing current time zone layers) * - * ```ts + * ```ts import.meta.vitest * import { DateTime, Effect } from "effect" * * const zone = DateTime.zoneMakeNamedUnsafe("Europe/London") @@ -2820,14 +2772,14 @@ export const formatIsoZoned: (self: Zoned) => string = Internal.formatIsoZoned * * const program = Effect.gen(function*() { * const now = yield* DateTime.nowInCurrentZone - * return DateTime.formatIsoZoned(now) + * return DateTime.zoneToString(now.zone) * }) * * // Use the layer to provide the time zone - * Effect.provide(program, layer) + * await Effect.runPromise(Effect.provide(program, layer)) // => "Europe/London" * ``` * - * @category current time zone + * @category layers * @since 3.6.0 */ export const layerCurrentZone: (resource: NoInfer) => Layer.Layer = Layer.succeed( @@ -2843,7 +2795,7 @@ export const layerCurrentZone: (resource: NoInfer) => Layer.Layer) => Layer.Layer "+03:00" * ``` * - * @category current time zone + * @category layers * @since 3.6.0 */ export const layerCurrentZoneOffset = (offset: number): Layer.Layer => @@ -2873,20 +2825,20 @@ export const layerCurrentZoneOffset = (offset: number): Layer.Layer "Europe/London" * ``` * - * @category current time zone + * @category layers * @since 3.6.0 */ export const layerCurrentZoneNamed: (zoneId: string) => Layer.Layer< @@ -2904,19 +2856,19 @@ export const layerCurrentZoneNamed: (zoneId: string) => Layer.Layer< * * **Example** (Providing local time zone layers) * - * ```ts + * ```ts import.meta.vitest * import { DateTime, Effect } from "effect" * * const program = Effect.gen(function*() { * const now = yield* DateTime.nowInCurrentZone - * return DateTime.formatIsoZoned(now) + * return DateTime.isZoned(now) * }) * * // Use the system's local time zone - * Effect.provide(program, DateTime.layerCurrentZoneLocal) + * await Effect.runPromise(Effect.provide(program, DateTime.layerCurrentZoneLocal)) // => true * ``` * - * @category current time zone + * @category layers * @since 3.6.0 */ export const layerCurrentZoneLocal: Layer.Layer = Layer.sync(CurrentTimeZone)(zoneMakeLocal) diff --git a/repos/effect/packages/effect/src/Deferred.ts b/repos/effect/packages/effect/src/Deferred.ts index a2f6260782..048fe64332 100644 --- a/repos/effect/packages/effect/src/Deferred.ts +++ b/repos/effect/packages/effect/src/Deferred.ts @@ -33,36 +33,23 @@ const TypeId = "~effect/Deferred" * * **Example** (Creating a Deferred for inter-fiber communication) * - * ```ts + * ```ts import.meta.vitest * import { Deferred, Effect, Fiber } from "effect" * - * // Create and use a Deferred for inter-fiber communication * const program = Effect.gen(function*() { - * // Create a Deferred that will hold a string value * const deferred: Deferred.Deferred = yield* Deferred.make() - * - * // Fork a fiber that will set the deferred value * const producer = yield* Effect.forkChild( * Effect.gen(function*() { - * yield* Effect.sleep("100 millis") * yield* Deferred.succeed(deferred, "Hello, World!") * }) * ) * - * // Fork a fiber that will await the deferred value - * const consumer = yield* Effect.forkChild( - * Effect.gen(function*() { - * const value = yield* Deferred.await(deferred) - * console.log("Received:", value) - * return value - * }) - * ) - * - * // Wait for both fibers to complete + * const consumer = yield* Effect.forkChild(Deferred.await(deferred)) * yield* Fiber.join(producer) - * const result = yield* Fiber.join(consumer) - * return result + * return yield* Fiber.join(consumer) * }) + * + * await Effect.runPromise(program) // => "Hello, World!" * ``` * * @category models @@ -140,11 +127,11 @@ const DeferredProto = { * * **Example** (Creating a Deferred unsafely) * - * ```ts + * ```ts import.meta.vitest * import { Deferred } from "effect" * * const deferred = Deferred.makeUnsafe() - * console.log(deferred) + * Deferred.isDoneUnsafe(deferred) // => false * ``` * * @category unsafe @@ -166,15 +153,16 @@ export const makeUnsafe = (): Deferred => { * * **Example** (Creating a Deferred) * - * ```ts + * ```ts import.meta.vitest * import { Deferred, Effect } from "effect" * * const program = Effect.gen(function*() { * const deferred = yield* Deferred.make() * yield* Deferred.succeed(deferred, 42) - * const value = yield* Deferred.await(deferred) - * console.log(value) // 42 + * return yield* Deferred.await(deferred) * }) + * + * await Effect.runPromise(program) // => 42 * ``` * * @category constructors @@ -209,16 +197,17 @@ export { * * **Example** (Awaiting a Deferred value) * - * ```ts + * ```ts import.meta.vitest * import { Deferred, Effect } from "effect" * * const program = Effect.gen(function*() { * const deferred = yield* Deferred.make() * yield* Deferred.succeed(deferred, 42) * - * const value = yield* Deferred.await(deferred) - * console.log(value) // 42 + * return yield* Deferred.await(deferred) * }) + * + * await Effect.runPromise(program) // => 42 * ``` * * @see {@link complete} for completing from an effect and memoizing its result @@ -246,17 +235,17 @@ export { * * **Example** (Completing a Deferred from an effect) * - * ```ts + * ```ts import.meta.vitest * import { Deferred, Effect } from "effect" * * const program = Effect.gen(function*() { * const deferred = yield* Deferred.make() * const completed = yield* Deferred.complete(deferred, Effect.succeed(42)) - * console.log(completed) // true - * * const value = yield* Deferred.await(deferred) - * console.log(value) // 42 + * return [completed, value] * }) + * + * await Effect.runPromise(program) // => [true, 42] * ``` * * @see {@link completeWith} for storing an effect directly without memoizing its result @@ -293,17 +282,17 @@ export const complete: { * * **Example** (Completing a Deferred with an effect) * - * ```ts + * ```ts import.meta.vitest * import { Deferred, Effect } from "effect" * * const program = Effect.gen(function*() { * const deferred = yield* Deferred.make() * const completed = yield* Deferred.completeWith(deferred, Effect.succeed(42)) - * console.log(completed) // true - * * const value = yield* Deferred.await(deferred) - * console.log(value) // 42 + * return [completed, value] * }) + * + * await Effect.runPromise(program) // => [true, 42] * ``` * * @see {@link complete} for running an effect once and sharing its result @@ -336,16 +325,16 @@ export const completeWith: { * * **Example** (Completing a Deferred with an Exit) * - * ```ts + * ```ts import.meta.vitest * import { Deferred, Effect, Exit } from "effect" * * const program = Effect.gen(function*() { * const deferred = yield* Deferred.make() * yield* Deferred.done(deferred, Exit.succeed(42)) - * - * const value = yield* Deferred.await(deferred) - * console.log(value) // 42 + * return yield* Effect.exit(Deferred.await(deferred)) * }) + * + * await Effect.runPromise(program) // => Exit.succeed(42) * ``` * * @see {@link complete} for completing from an effect and memoizing its result @@ -376,14 +365,17 @@ export const done: { * * **Example** (Failing a Deferred with an error) * - * ```ts - * import { Deferred, Effect } from "effect" + * ```ts import.meta.vitest + * import { Deferred, Effect, Exit } from "effect" * * const program = Effect.gen(function*() { * const deferred = yield* Deferred.make() * const success = yield* Deferred.fail(deferred, "Operation failed") - * console.log(success) // true + * const exit = yield* Effect.exit(Deferred.await(deferred)) + * return [success, exit] * }) + * + * await Effect.runPromise(program) // => [true, Exit.fail("Operation failed")] * ``` * * @category completion @@ -411,14 +403,17 @@ export const fail: { * * **Example** (Failing a Deferred with a lazy error) * - * ```ts - * import { Deferred, Effect } from "effect" + * ```ts import.meta.vitest + * import { Deferred, Effect, Exit } from "effect" * * const program = Effect.gen(function*() { * const deferred = yield* Deferred.make() * const success = yield* Deferred.failSync(deferred, () => "Lazy error") - * console.log(success) // true + * const exit = yield* Effect.exit(Deferred.await(deferred)) + * return [success, exit] * }) + * + * await Effect.runPromise(program) // => [true, Exit.fail("Lazy error")] * ``` * * @category completion @@ -448,17 +443,17 @@ export const failSync: { * * **Example** (Failing a Deferred with a Cause) * - * ```ts - * import { Cause, Deferred, Effect } from "effect" + * ```ts import.meta.vitest + * import { Cause, Deferred, Effect, Exit } from "effect" * * const program = Effect.gen(function*() { * const deferred = yield* Deferred.make() - * const success = yield* Deferred.failCause( - * deferred, - * Cause.fail("Operation failed") - * ) - * console.log(success) // true + * const success = yield* Deferred.failCause(deferred, Cause.fail("Operation failed")) + * const exit = yield* Effect.exit(Deferred.await(deferred)) + * return [success, exit] * }) + * + * await Effect.runPromise(program) // => [true, Exit.failCause(Cause.fail("Operation failed"))] * ``` * * @category completion @@ -489,17 +484,17 @@ export const failCause: { * * **Example** (Failing a Deferred with a lazy Cause) * - * ```ts - * import { Cause, Deferred, Effect } from "effect" + * ```ts import.meta.vitest + * import { Cause, Deferred, Effect, Exit } from "effect" * * const program = Effect.gen(function*() { * const deferred = yield* Deferred.make() - * const success = yield* Deferred.failCauseSync( - * deferred, - * () => Cause.fail("Lazy error") - * ) - * console.log(success) // true + * const success = yield* Deferred.failCauseSync(deferred, () => Cause.fail("Lazy error")) + * const exit = yield* Effect.exit(Deferred.await(deferred)) + * return [success, exit] * }) + * + * await Effect.runPromise(program) // => [true, Exit.failCause(Cause.fail("Lazy error"))] * ``` * * @category completion @@ -529,17 +524,18 @@ export const failCauseSync: { * * **Example** (Killing a Deferred with a defect) * - * ```ts - * import { Deferred, Effect } from "effect" + * ```ts import.meta.vitest + * import { Deferred, Effect, Exit } from "effect" * + * const defect = new Error("Something went wrong") * const program = Effect.gen(function*() { * const deferred = yield* Deferred.make() - * const success = yield* Deferred.die( - * deferred, - * new Error("Something went wrong") - * ) - * console.log(success) // true + * const success = yield* Deferred.die(deferred, defect) + * const exit = yield* Effect.exit(Deferred.await(deferred)) + * return [success, exit] * }) + * + * await Effect.runPromise(program) // => [true, Exit.die(defect)] * ``` * * @category completion @@ -566,17 +562,18 @@ export const die: { * * **Example** (Killing a Deferred with a lazy defect) * - * ```ts - * import { Deferred, Effect } from "effect" + * ```ts import.meta.vitest + * import { Deferred, Effect, Exit } from "effect" * + * const defect = new Error("Lazy error") * const program = Effect.gen(function*() { * const deferred = yield* Deferred.make() - * const success = yield* Deferred.dieSync( - * deferred, - * () => new Error("Lazy error") - * ) - * console.log(success) // true + * const success = yield* Deferred.dieSync(deferred, () => defect) + * const exit = yield* Effect.exit(Deferred.await(deferred)) + * return [success, exit] * }) + * + * await Effect.runPromise(program) // => [true, Exit.die(defect)] * ``` * * @category completion @@ -607,14 +604,19 @@ export const dieSync: { * * **Example** (Interrupting a Deferred) * - * ```ts - * import { Deferred, Effect } from "effect" + * ```ts import.meta.vitest + * import { Deferred, Effect, Exit } from "effect" * * const program = Effect.gen(function*() { * const deferred = yield* Deferred.make() * const success = yield* Deferred.interrupt(deferred) - * console.log(success) // true + * const exit = yield* Effect.exit(Deferred.await(deferred)) + * return [success, exit] as const * }) + * + * const [success, exit] = await Effect.runPromise(program) + * success // => true + * Exit.hasInterrupts(exit) // => true * ``` * * @category completion @@ -639,14 +641,17 @@ export const interrupt = (self: Deferred): Effect => * * **Example** (Interrupting a Deferred with a fiber id) * - * ```ts - * import { Deferred, Effect } from "effect" + * ```ts import.meta.vitest + * import { Deferred, Effect, Exit } from "effect" * * const program = Effect.gen(function*() { * const deferred = yield* Deferred.make() * const success = yield* Deferred.interruptWith(deferred, 42) - * console.log(success) // true + * const exit = yield* Effect.exit(Deferred.await(deferred)) + * return [success, exit] * }) + * + * await Effect.runPromise(program) // => [true, Exit.interrupt(42)] * ``` * * @category completion @@ -671,21 +676,21 @@ export const interruptWith: { * * **Example** (Checking Deferred completion) * - * ```ts + * ```ts import.meta.vitest * import { Deferred, Effect } from "effect" * * const program = Effect.gen(function*() { * const deferred = yield* Deferred.make() * const beforeCompletion = yield* Deferred.isDone(deferred) - * console.log(beforeCompletion) // false - * * yield* Deferred.succeed(deferred, 42) * const afterCompletion = yield* Deferred.isDone(deferred) - * console.log(afterCompletion) // true + * return [beforeCompletion, afterCompletion] * }) + * + * await Effect.runPromise(program) // => [false, true] * ``` * - * @category getters + * @category predicates * @since 2.0.0 */ export const isDone = (self: Deferred): Effect => internalEffect.sync(() => isDoneUnsafe(self)) @@ -701,7 +706,7 @@ export const isDone = (self: Deferred): Effect => internalE * @see {@link isDone} for checking completion inside `Effect` * @see {@link poll} for reading the completed effect when available * - * @category getters + * @category predicates * @since 4.0.0 */ export const isDoneUnsafe = (self: Deferred): boolean => self.effect !== undefined @@ -718,18 +723,19 @@ export const isDoneUnsafe = (self: Deferred): boolean => self.effect * * **Example** (Polling Deferred completion) * - * ```ts - * import { Deferred, Effect } from "effect" + * ```ts import.meta.vitest + * import { Deferred, Effect, Option } from "effect" * * const program = Effect.gen(function*() { * const deferred = yield* Deferred.make() * const beforeCompletion = yield* Deferred.poll(deferred) - * console.log(beforeCompletion._tag === "None") // true - * * yield* Deferred.succeed(deferred, 42) * const afterCompletion = yield* Deferred.poll(deferred) - * console.log(afterCompletion._tag === "Some") // true + * const afterValue = yield* Effect.transposeOption(afterCompletion) + * return [beforeCompletion, afterValue] * }) + * + * await Effect.runPromise(program) // => [Option.none(), Option.some(42)] * ``` * * @category getters @@ -754,16 +760,17 @@ export function poll(self: Deferred): Effect() * yield* Deferred.succeed(deferred, 42) * - * const value = yield* Deferred.await(deferred) - * console.log(value) // 42 + * return yield* Deferred.await(deferred) * }) + * + * await Effect.runPromise(program) // => 42 * ``` * * @category completion @@ -791,16 +798,16 @@ export const succeed: { * * **Example** (Completing a Deferred with a lazy value) * - * ```ts + * ```ts import.meta.vitest * import { Deferred, Effect } from "effect" * * const program = Effect.gen(function*() { * const deferred = yield* Deferred.make() * yield* Deferred.sync(deferred, () => 42) - * - * const value = yield* Deferred.await(deferred) - * console.log(value) // 42 + * return yield* Deferred.await(deferred) * }) + * + * await Effect.runPromise(program) // => 42 * ``` * * @category completion @@ -832,12 +839,11 @@ export const sync: { * * **Example** (Completing a Deferred unsafely) * - * ```ts + * ```ts import.meta.vitest * import { Deferred, Effect } from "effect" * * const deferred = Deferred.makeUnsafe() - * const success = Deferred.doneUnsafe(deferred, Effect.succeed(42)) - * console.log(success) // true + * Deferred.doneUnsafe(deferred, Effect.succeed(42)) // => true * ``` * * @category unsafe @@ -873,33 +879,22 @@ export const doneUnsafe = (self: Deferred, effect: Effect): bo * * **Example** (Completing a Deferred from an effect result) * - * ```ts + * ```ts import.meta.vitest * import { Deferred, Effect } from "effect" * - * // Define an effect that succeeds * const successEffect = Effect.succeed(42) * * const program = Effect.gen(function*() { - * // Create a deferred * const deferred = yield* Deferred.make() - * - * // Complete the deferred using the successEffect * const isCompleted = yield* Deferred.into(successEffect, deferred) - * - * // Access the value of the deferred * const value = yield* Deferred.await(deferred) - * console.log(value) - * - * return isCompleted + * return [isCompleted, value] * }) * - * Effect.runPromise(program).then(console.log) - * // Output: - * // 42 - * // true + * await Effect.runPromise(program) // => [true, 42] * ``` * - * @category Synchronization Utilities + * @category completion * @since 4.0.0 */ export const into: { diff --git a/repos/effect/packages/effect/src/Duration.ts b/repos/effect/packages/effect/src/Duration.ts index d2fe8eb415..4f4e7fa1eb 100644 --- a/repos/effect/packages/effect/src/Duration.ts +++ b/repos/effect/packages/effect/src/Duration.ts @@ -27,6 +27,8 @@ 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) @@ -38,8 +40,20 @@ const roundTiesAwayFromZero = (input: number): bigint => const roundMillisToNanos = (millis: number): bigint => roundTiesAwayFromZero(millis * 1_000_000) -const parseNanos = (input: string, scale: bigint): bigint => - input.includes(".") ? roundTiesAwayFromZero(Number(input) * Number(scale)) : BigInt(input) * scale +const parseNanos = (input: string, scale: bigint): bigint => { + const decimalIndex = input.indexOf(".") + if (decimalIndex === -1) return BigInt(input) * scale + + const isNegative = input[0] === "-" + const fractional = input.slice(decimalIndex + 1) + const fractionalScale = bigint10 ** BigInt(fractional.length) + const scaled = ( + BigInt(input.slice(isNegative ? 1 : 0, decimalIndex)) * fractionalScale + BigInt(fractional) + ) * scale + const rounded = scaled / fractionalScale + + (scaled % fractionalScale * bigint2 >= fractionalScale ? bigint1 : bigint0) + return isNegative ? -rounded : rounded +} const nanosToHrTime = (nanos: bigint): [seconds: number, nanos: number] => { const sign = nanos < bigint0 ? -bigint1 : bigint1 @@ -175,12 +189,12 @@ export type Input = * * **Example** (Combining duration object fields) * - * ```ts + * ```ts import.meta.vitest * import { Duration } from "effect" * - * Duration.fromInputUnsafe({ seconds: 30 }) - * Duration.fromInputUnsafe({ days: 1 }) - * Duration.fromInputUnsafe({ seconds: 1, nanoseconds: 500 }) + * Duration.fromInputUnsafe({ seconds: 30 }) // => Duration.seconds(30) + * Duration.fromInputUnsafe({ days: 1 }) // => Duration.days(1) + * Duration.fromInputUnsafe({ seconds: 1, nanoseconds: 500 }) // => Duration.nanos(1_000_000_500n) * ``` * * @category models @@ -213,13 +227,13 @@ const DURATION_REGEXP = /^(-?\d+(?:\.\d+)?)\s+(nanos?|micros?|millis?|seconds?|m * * **Example** (Decoding duration inputs) * - * ```ts + * ```ts import.meta.vitest * import { Duration } from "effect" * - * const duration1 = Duration.fromInputUnsafe(1000) // 1000 milliseconds - * const duration2 = Duration.fromInputUnsafe("5 seconds") - * const duration3 = Duration.fromInputUnsafe("Infinity") - * const duration4 = Duration.fromInputUnsafe([2, 500_000_000]) // 2 seconds and 500ms + * Duration.fromInputUnsafe(1000) // => Duration.millis(1000) + * Duration.fromInputUnsafe("5 seconds") // => Duration.seconds(5) + * Duration.fromInputUnsafe("Infinity") // => Duration.infinity + * Duration.fromInputUnsafe([2, 500_000_000]) // => Duration.nanos(2_500_000_000n) * ``` * * @category constructors @@ -316,12 +330,11 @@ const invalid = (input: unknown): never => { * * **Example** (Safely decoding duration inputs) * - * ```ts + * ```ts import.meta.vitest * import { Duration, Option } from "effect" * - * Duration.fromInput(1000).pipe(Option.map(Duration.toSeconds)) // Some(1) - * - * Duration.fromInput("invalid" as any) // None + * Duration.fromInput(1000) // => Option.some(Duration.seconds(1)) + * Duration.fromInput("invalid" as any) // => Option.none() * ``` * * @category constructors @@ -338,7 +351,18 @@ const negativeInfinityDurationValue: DurationValue = { _tag: "NegativeInfinity" const DurationProto: Omit = { [TypeId]: TypeId, [Hash.symbol](this: Duration) { - return Hash.structure(this.value) + // Hash equal finite durations using the same canonical nanoseconds + // representation used by `equals`. + switch (this.value._tag) { + case "Millis": { + const nanos = this.value.millis * 1_000_000 + return Number.isFinite(nanos) ? Hash.hash(roundTiesAwayFromZero(nanos)) : Hash.number(this.value.millis) + } + case "Nanos": + return Hash.hash(this.value.nanos) + default: + return Hash.structure(this.value) + } }, [Equal.symbol](this: Duration, that: unknown): boolean { return isDuration(that) && equals(this, that) @@ -400,11 +424,11 @@ const make = (input: number | bigint): Duration => { * * **Example** (Checking for durations) * - * ```ts + * ```ts import.meta.vitest * import { Duration } from "effect" * - * console.log(Duration.isDuration(Duration.seconds(1))) // true - * console.log(Duration.isDuration(1000)) // false + * Duration.isDuration(Duration.seconds(1)) // => true + * Duration.isDuration(1000) // => false * ``` * * @category guards @@ -417,14 +441,14 @@ export const isDuration = (u: unknown): u is Duration => hasProperty(u, TypeId) * * **Example** (Checking finite durations) * - * ```ts + * ```ts import.meta.vitest * import { Duration } from "effect" * - * console.log(Duration.isFinite(Duration.seconds(5))) // true - * console.log(Duration.isFinite(Duration.infinity)) // false + * Duration.isFinite(Duration.seconds(5)) // => true + * Duration.isFinite(Duration.infinity) // => false * ``` * - * @category guards + * @category predicates * @since 2.0.0 */ export const isFinite = (self: Duration): boolean => @@ -435,14 +459,14 @@ export const isFinite = (self: Duration): boolean => * * **Example** (Checking for zero durations) * - * ```ts + * ```ts import.meta.vitest * import { Duration } from "effect" * - * console.log(Duration.isZero(Duration.zero)) // true - * console.log(Duration.isZero(Duration.seconds(1))) // false + * Duration.isZero(Duration.zero) // => true + * Duration.isZero(Duration.seconds(1)) // => false * ``` * - * @category guards + * @category predicates * @since 3.5.0 */ export const isZero = (self: Duration): boolean => { @@ -462,15 +486,15 @@ export const isZero = (self: Duration): boolean => { * * **Example** (Checking for negative durations) * - * ```ts + * ```ts import.meta.vitest * import { Duration } from "effect" * - * console.log(Duration.isNegative(Duration.seconds(-5))) // true - * console.log(Duration.isNegative(Duration.zero)) // false - * console.log(Duration.isNegative(Duration.negativeInfinity)) // true + * Duration.isNegative(Duration.seconds(-5)) // => true + * Duration.isNegative(Duration.zero) // => false + * Duration.isNegative(Duration.negativeInfinity) // => true * ``` * - * @category guards + * @category predicates * @since 4.0.0 */ export const isNegative = (self: Duration): boolean => { @@ -491,15 +515,15 @@ export const isNegative = (self: Duration): boolean => { * * **Example** (Checking for positive durations) * - * ```ts + * ```ts import.meta.vitest * import { Duration } from "effect" * - * console.log(Duration.isPositive(Duration.seconds(5))) // true - * console.log(Duration.isPositive(Duration.zero)) // false - * console.log(Duration.isPositive(Duration.infinity)) // true + * Duration.isPositive(Duration.seconds(5)) // => true + * Duration.isPositive(Duration.zero) // => false + * Duration.isPositive(Duration.infinity) // => true * ``` * - * @category guards + * @category predicates * @since 4.0.0 */ export const isPositive = (self: Duration): boolean => { @@ -520,11 +544,11 @@ export const isPositive = (self: Duration): boolean => { * * **Example** (Taking absolute duration values) * - * ```ts + * ```ts import.meta.vitest * import { Duration } from "effect" * - * Duration.toMillis(Duration.abs(Duration.seconds(-5))) // 5000 - * Duration.abs(Duration.negativeInfinity) === Duration.infinity // true + * Duration.abs(Duration.seconds(-5)) // => Duration.seconds(5) + * Duration.abs(Duration.negativeInfinity) // => Duration.infinity * ``` * * @category math @@ -547,11 +571,11 @@ export const abs = (self: Duration): Duration => { * * **Example** (Negating durations) * - * ```ts + * ```ts import.meta.vitest * import { Duration } from "effect" * - * Duration.toMillis(Duration.negate(Duration.seconds(5))) // -5000 - * Duration.negate(Duration.infinity) === Duration.negativeInfinity // true + * Duration.negate(Duration.seconds(5)) // => Duration.seconds(-5) + * Duration.negate(Duration.infinity) // => Duration.negativeInfinity * ``` * * @category math @@ -575,10 +599,10 @@ export const negate = (self: Duration): Duration => { * * **Example** (Referencing the zero duration) * - * ```ts + * ```ts import.meta.vitest * import { Duration } from "effect" * - * console.log(Duration.toMillis(Duration.zero)) // 0 + * Duration.toMillis(Duration.zero) // => 0 * ``` * * @category constructors @@ -591,10 +615,10 @@ export const zero: Duration = make(0) * * **Example** (Referencing infinite duration) * - * ```ts + * ```ts import.meta.vitest * import { Duration } from "effect" * - * console.log(Duration.toMillis(Duration.infinity)) // Infinity + * Duration.toMillis(Duration.infinity) // => Infinity * ``` * * @category constructors @@ -607,10 +631,10 @@ export const infinity: Duration = make(Infinity) * * **Example** (Referencing negative infinite duration) * - * ```ts + * ```ts import.meta.vitest * import { Duration } from "effect" * - * console.log(Duration.toMillis(Duration.negativeInfinity)) // -Infinity + * Duration.toMillis(Duration.negativeInfinity) // => -Infinity * ``` * * @category constructors @@ -623,11 +647,10 @@ export const negativeInfinity: Duration = make(-Infinity) * * **Example** (Creating durations from nanoseconds) * - * ```ts + * ```ts import.meta.vitest * import { Duration } from "effect" * - * const duration = Duration.nanos(BigInt(500_000_000)) - * console.log(Duration.toMillis(duration)) // 500 + * Duration.nanos(500_000_000n) // => Duration.nanos(500_000_000n) * ``` * * @category constructors @@ -640,11 +663,10 @@ export const nanos = (nanos: bigint): Duration => make(nanos) * * **Example** (Creating durations from microseconds) * - * ```ts + * ```ts import.meta.vitest * import { Duration } from "effect" * - * const duration = Duration.micros(BigInt(500_000)) - * console.log(Duration.toMillis(duration)) // 500 + * Duration.micros(500_000n) // => Duration.nanos(500_000_000n) * ``` * * @category constructors @@ -657,11 +679,10 @@ export const micros = (micros: bigint): Duration => make(micros * bigint1e3) * * **Example** (Creating durations from milliseconds) * - * ```ts + * ```ts import.meta.vitest * import { Duration } from "effect" * - * const duration = Duration.millis(1000) - * console.log(Duration.toMillis(duration)) // 1000 + * Duration.toMillis(Duration.millis(1000)) // => 1000 * ``` * * @category constructors @@ -674,11 +695,10 @@ export const millis = (millis: number): Duration => make(millis) * * **Example** (Creating durations from seconds) * - * ```ts + * ```ts import.meta.vitest * import { Duration } from "effect" * - * const duration = Duration.seconds(30) - * console.log(Duration.toMillis(duration)) // 30000 + * Duration.toMillis(Duration.seconds(30)) // => 30_000 * ``` * * @category constructors @@ -691,11 +711,10 @@ export const seconds = (seconds: number): Duration => make(seconds * 1000) * * **Example** (Creating durations from minutes) * - * ```ts + * ```ts import.meta.vitest * import { Duration } from "effect" * - * const duration = Duration.minutes(5) - * console.log(Duration.toMillis(duration)) // 300000 + * Duration.toMillis(Duration.minutes(5)) // => 300_000 * ``` * * @category constructors @@ -708,11 +727,10 @@ export const minutes = (minutes: number): Duration => make(minutes * 60_000) * * **Example** (Creating durations from hours) * - * ```ts + * ```ts import.meta.vitest * import { Duration } from "effect" * - * const duration = Duration.hours(2) - * console.log(Duration.toMillis(duration)) // 7200000 + * Duration.toMillis(Duration.hours(2)) // => 7_200_000 * ``` * * @category constructors @@ -725,11 +743,10 @@ export const hours = (hours: number): Duration => make(hours * 3_600_000) * * **Example** (Creating durations from days) * - * ```ts + * ```ts import.meta.vitest * import { Duration } from "effect" * - * const duration = Duration.days(1) - * console.log(Duration.toMillis(duration)) // 86400000 + * Duration.toMillis(Duration.days(1)) // => 86_400_000 * ``` * * @category constructors @@ -742,11 +759,10 @@ export const days = (days: number): Duration => make(days * 86_400_000) * * **Example** (Creating durations from weeks) * - * ```ts + * ```ts import.meta.vitest * import { Duration } from "effect" * - * const duration = Duration.weeks(1) - * console.log(Duration.toMillis(duration)) // 604800000 + * Duration.toMillis(Duration.weeks(1)) // => 604_800_000 * ``` * * @category constructors @@ -759,11 +775,11 @@ export const weeks = (weeks: number): Duration => make(weeks * 604_800_000) * * **Example** (Converting durations to milliseconds) * - * ```ts + * ```ts import.meta.vitest * import { Duration } from "effect" * - * console.log(Duration.toMillis(Duration.seconds(5))) // 5000 - * console.log(Duration.toMillis(Duration.minutes(2))) // 120000 + * Duration.toMillis(Duration.seconds(5)) // => 5000 + * Duration.toMillis(Duration.minutes(2)) // => 120_000 * ``` * * @category getters @@ -782,11 +798,11 @@ export const toMillis = (self: Input): number => * * **Example** (Converting durations to seconds) * - * ```ts + * ```ts import.meta.vitest * import { Duration } from "effect" * - * console.log(Duration.toSeconds(Duration.millis(5000))) // 5 - * console.log(Duration.toSeconds(Duration.minutes(2))) // 120 + * Duration.toSeconds(Duration.millis(5000)) // => 5 + * Duration.toSeconds(Duration.minutes(2)) // => 120 * ``` * * @category getters @@ -805,11 +821,11 @@ export const toSeconds = (self: Input): number => * * **Example** (Converting durations to minutes) * - * ```ts + * ```ts import.meta.vitest * import { Duration } from "effect" * - * console.log(Duration.toMinutes(Duration.seconds(120))) // 2 - * console.log(Duration.toMinutes(Duration.hours(1))) // 60 + * Duration.toMinutes(Duration.seconds(120)) // => 2 + * Duration.toMinutes(Duration.hours(1)) // => 60 * ``` * * @category getters @@ -828,11 +844,11 @@ export const toMinutes = (self: Input): number => * * **Example** (Converting durations to hours) * - * ```ts + * ```ts import.meta.vitest * import { Duration } from "effect" * - * console.log(Duration.toHours(Duration.minutes(120))) // 2 - * console.log(Duration.toHours(Duration.days(1))) // 24 + * Duration.toHours(Duration.minutes(120)) // => 2 + * Duration.toHours(Duration.days(1)) // => 24 * ``` * * @category getters @@ -851,11 +867,11 @@ export const toHours = (self: Input): number => * * **Example** (Converting durations to days) * - * ```ts + * ```ts import.meta.vitest * import { Duration } from "effect" * - * console.log(Duration.toDays(Duration.hours(48))) // 2 - * console.log(Duration.toDays(Duration.weeks(1))) // 7 + * Duration.toDays(Duration.hours(48)) // => 2 + * Duration.toDays(Duration.weeks(1)) // => 7 * ``` * * @category getters @@ -874,11 +890,11 @@ export const toDays = (self: Input): number => * * **Example** (Converting durations to weeks) * - * ```ts + * ```ts import.meta.vitest * import { Duration } from "effect" * - * console.log(Duration.toWeeks(Duration.days(14))) // 2 - * console.log(Duration.toWeeks(Duration.days(7))) // 1 + * Duration.toWeeks(Duration.days(14)) // => 2 + * Duration.toWeeks(Duration.days(7)) // => 1 * ``` * * @category getters @@ -911,12 +927,10 @@ export const toWeeks = (self: Input): number => * * **Example** (Reading nanoseconds unsafely) * - * ```ts + * ```ts import.meta.vitest * import { Duration } from "effect" * - * const duration = Duration.seconds(2) - * const nanos = Duration.toNanosUnsafe(duration) - * console.log(nanos) // 2000000000n + * Duration.toNanosUnsafe(Duration.seconds(2)) // => 2_000_000_000n * * // Duration.toNanosUnsafe(Duration.infinity) * // throws Error: "Cannot convert infinite duration to nanos" @@ -947,13 +961,11 @@ export const toNanosUnsafe = (input: Input): bigint => { * * **Example** (Safely reading nanoseconds) * - * ```ts + * ```ts import.meta.vitest * import { Duration, Option } from "effect" * - * Duration.toNanos(Duration.seconds(1)) // Some(1000000000n) - * - * Duration.toNanos(Duration.infinity) // None - * Option.getOrUndefined(Duration.toNanos(Duration.infinity)) // undefined + * Duration.toNanos(Duration.seconds(1)) // => Option.some(1_000_000_000n) + * Duration.toNanos(Duration.infinity) // => Option.none() * ``` * * @category getters @@ -966,12 +978,10 @@ export const toNanos: (self: Input) => Option.Option = Option.liftThrowa * * **Example** (Converting durations to high-resolution time) * - * ```ts + * ```ts import.meta.vitest * import { Duration } from "effect" * - * const duration = Duration.millis(1500) - * const hrtime = Duration.toHrTime(duration) - * console.log(hrtime) // [1, 500000000] + * Duration.toHrTime(Duration.millis(1500)) // => [1, 500_000_000] * ``` * * @category getters @@ -1002,15 +1012,14 @@ export const toHrTime = (input: Input): [seconds: number, nanos: number] => { * * **Example** (Pattern matching on duration representations) * - * ```ts + * ```ts import.meta.vitest * import { Duration } from "effect" * - * const result = Duration.match(Duration.seconds(5), { + * Duration.match(Duration.seconds(5), { * onMillis: (millis) => `${millis} milliseconds`, * onNanos: (nanos) => `${nanos} nanoseconds`, * onInfinity: () => "infinite" - * }) - * console.log(result) // "5000 milliseconds" + * }) // => "5000 milliseconds" * ``` * * @category pattern matching @@ -1060,15 +1069,14 @@ export const match: { * * **Example** (Pattern matching on duration pairs) * - * ```ts + * ```ts import.meta.vitest * import { Duration } from "effect" * - * const sum = Duration.matchPair(Duration.seconds(3), Duration.seconds(2), { + * Duration.matchPair(Duration.seconds(3), Duration.seconds(2), { * onMillis: (a, b) => a + b, * onNanos: (a, b) => Number(a + b), * onInfinity: () => Infinity - * }) - * console.log(sum) // 5000 + * }) // => 5000 * ``` * * @category pattern matching @@ -1123,7 +1131,7 @@ export const matchPair: { * * **Example** (Sorting durations) * - * ```ts + * ```ts import.meta.vitest * import { Duration } from "effect" * * const durations = [ @@ -1131,8 +1139,7 @@ export const matchPair: { * Duration.seconds(1), * Duration.seconds(2) * ] - * const sorted = durations.sort((a, b) => Duration.Order(a, b)) - * console.log(sorted.map(Duration.toSeconds)) // [1, 2, 3] + * durations.sort((a, b) => Duration.Order(a, b)).map(Duration.toSeconds) // => [1, 2, 3] * ``` * * @category instances @@ -1173,14 +1180,13 @@ export const Order: order.Order = order.make((self, that) => * * **Example** (Checking duration ranges) * - * ```ts + * ```ts import.meta.vitest * import { Duration } from "effect" * - * const isInRange = Duration.between(Duration.seconds(3), { + * Duration.between(Duration.seconds(3), { * minimum: Duration.seconds(2), * maximum: Duration.seconds(5) - * }) - * console.log(isInRange) // true + * }) // => true * ``` * * @see {@link clamp} for constraining a duration to a range @@ -1200,11 +1206,10 @@ export const between: { * * **Example** (Comparing durations for equivalence) * - * ```ts + * ```ts import.meta.vitest * import { Duration } from "effect" * - * const isEqual = Duration.Equivalence(Duration.seconds(5), Duration.millis(5000)) - * console.log(isEqual) // true + * Duration.Equivalence(Duration.seconds(5), Duration.millis(5000)) // => true * ``` * * @category instances @@ -1222,11 +1227,10 @@ export const Equivalence: Equ.Equivalence = (self, that) => * * **Example** (Selecting the shorter duration) * - * ```ts + * ```ts import.meta.vitest * import { Duration } from "effect" * - * const shorter = Duration.min(Duration.seconds(5), Duration.seconds(3)) - * console.log(Duration.toSeconds(shorter)) // 3 + * Duration.min(Duration.seconds(5), Duration.seconds(3)) // => Duration.seconds(3) * ``` * * @category ordering @@ -1242,11 +1246,10 @@ export const min: { * * **Example** (Selecting the longer duration) * - * ```ts + * ```ts import.meta.vitest * import { Duration } from "effect" * - * const longer = Duration.max(Duration.seconds(5), Duration.seconds(3)) - * console.log(Duration.toSeconds(longer)) // 5 + * Duration.max(Duration.seconds(5), Duration.seconds(3)) // => Duration.seconds(5) * ``` * * @category ordering @@ -1262,14 +1265,13 @@ export const max: { * * **Example** (Clamping durations to a range) * - * ```ts + * ```ts import.meta.vitest * import { Duration } from "effect" * - * const clamped = Duration.clamp(Duration.seconds(10), { + * Duration.clamp(Duration.seconds(10), { * minimum: Duration.seconds(2), * maximum: Duration.seconds(5) - * }) - * console.log(Duration.toSeconds(clamped)) // 5 + * }) // => Duration.seconds(5) * ``` * * @category ordering @@ -1291,13 +1293,11 @@ export const clamp: { * * **Example** (Safely dividing durations) * - * ```ts + * ```ts import.meta.vitest * import { Duration, Option } from "effect" * - * const d = Duration.divide(Duration.seconds(10), 2) - * console.log(Option.map(d, Duration.toSeconds)) // Some(5) - * - * Duration.divide(Duration.seconds(10), 0) // None + * Duration.divide(Duration.seconds(10), 2) // => Option.some(Duration.seconds(5)) + * Duration.divide(Duration.seconds(10), 0) // => Option.none() * ``` * * @category math @@ -1345,14 +1345,11 @@ export const divide: { * * **Example** (Dividing durations unsafely) * - * ```ts + * ```ts import.meta.vitest * import { Duration } from "effect" * - * const half = Duration.divideUnsafe(Duration.seconds(10), 2) - * console.log(Duration.toSeconds(half)) // 5 - * - * const infinite = Duration.divideUnsafe(Duration.seconds(10), 0) - * console.log(Duration.toMillis(infinite)) // Infinity + * Duration.divideUnsafe(Duration.seconds(10), 2) // => Duration.seconds(5) + * Duration.divideUnsafe(Duration.seconds(10), 0) // => Duration.infinity * ``` * * @category math @@ -1399,11 +1396,10 @@ export const divideUnsafe: { * * **Example** (Multiplying durations) * - * ```ts + * ```ts import.meta.vitest * import { Duration } from "effect" * - * const doubled = Duration.times(Duration.seconds(5), 2) - * console.log(Duration.toSeconds(doubled)) // 10 + * Duration.times(Duration.seconds(5), 2) // => Duration.seconds(10) * ``` * * @category math @@ -1437,11 +1433,10 @@ export const times: { * * **Example** (Subtracting durations) * - * ```ts + * ```ts import.meta.vitest * import { Duration } from "effect" * - * const result = Duration.subtract(Duration.seconds(10), Duration.seconds(3)) - * console.log(Duration.toSeconds(result)) // 7 + * Duration.subtract(Duration.seconds(10), Duration.seconds(3)) // => Duration.seconds(7) * ``` * * @category math @@ -1481,11 +1476,10 @@ export const subtract: { * * **Example** (Adding durations) * - * ```ts + * ```ts import.meta.vitest * import { Duration } from "effect" * - * const total = Duration.sum(Duration.seconds(5), Duration.seconds(3)) - * console.log(Duration.toSeconds(total)) // 8 + * Duration.sum(Duration.seconds(5), Duration.seconds(3)) // => Duration.seconds(8) * ``` * * @category math @@ -1518,11 +1512,10 @@ export const sum: { * * **Example** (Comparing durations with less than) * - * ```ts + * ```ts import.meta.vitest * import { Duration } from "effect" * - * const isLess = Duration.isLessThan(Duration.seconds(3), Duration.seconds(5)) - * console.log(isLess) // true + * Duration.isLessThan(Duration.seconds(3), Duration.seconds(5)) // => true * ``` * * @category predicates @@ -1538,14 +1531,13 @@ export const isLessThan: { * * **Example** (Comparing durations with less than or equal) * - * ```ts + * ```ts import.meta.vitest * import { Duration } from "effect" * - * const isLessOrEqual = Duration.isLessThanOrEqualTo( + * Duration.isLessThanOrEqualTo( * Duration.seconds(5), * Duration.seconds(5) - * ) - * console.log(isLessOrEqual) // true + * ) // => true * ``` * * @category predicates @@ -1561,11 +1553,10 @@ export const isLessThanOrEqualTo: { * * **Example** (Comparing durations with greater than) * - * ```ts + * ```ts import.meta.vitest * import { Duration } from "effect" * - * const isGreater = Duration.isGreaterThan(Duration.seconds(5), Duration.seconds(3)) - * console.log(isGreater) // true + * Duration.isGreaterThan(Duration.seconds(5), Duration.seconds(3)) // => true * ``` * * @category predicates @@ -1581,14 +1572,13 @@ export const isGreaterThan: { * * **Example** (Comparing durations with greater than or equal) * - * ```ts + * ```ts import.meta.vitest * import { Duration } from "effect" * - * const isGreaterOrEqual = Duration.isGreaterThanOrEqualTo( + * Duration.isGreaterThanOrEqualTo( * Duration.seconds(5), * Duration.seconds(5) - * ) - * console.log(isGreaterOrEqual) // true + * ) // => true * ``` * * @category predicates @@ -1604,11 +1594,10 @@ export const isGreaterThanOrEqualTo: { * * **Example** (Checking duration equality) * - * ```ts + * ```ts import.meta.vitest * import { Duration } from "effect" * - * const isEqual = Duration.equals(Duration.seconds(5), Duration.millis(5000)) - * console.log(isEqual) // true + * Duration.equals(Duration.seconds(5), Duration.millis(5000)) // => true * ``` * * @category predicates @@ -1630,7 +1619,7 @@ export const equals: { * * **Example** (Decomposing durations into parts) * - * ```ts + * ```ts import.meta.vitest * import { Duration } from "effect" * * // Create a complex duration by adding multiple parts @@ -1641,28 +1630,10 @@ export const equals: { * ), * Duration.millis(123) * ) - * const components = Duration.parts(duration) - * console.log(components) - * // { - * // days: 1, - * // hours: 2, - * // minutes: 30, - * // seconds: 45, - * // millis: 123, - * // nanos: 0 - * // } + * Duration.parts(duration) // => ({ days: 1, hours: 2, minutes: 30, seconds: 45, millis: 123, nanos: 0 }) * * const complex = Duration.sum(Duration.hours(25), Duration.minutes(90)) - * const complexParts = Duration.parts(complex) - * console.log(complexParts) - * // { - * // days: 1, - * // hours: 2, - * // minutes: 30, - * // seconds: 0, - * // millis: 0, - * // nanos: 0 - * // } + * Duration.parts(complex) // => ({ days: 1, hours: 2, minutes: 30, seconds: 0, millis: 0, nanos: 0 }) * ``` * * @category converting @@ -1722,11 +1693,11 @@ export const parts = (self: Duration): { * * **Example** (Formatting durations) * - * ```ts + * ```ts import.meta.vitest * import { Duration } from "effect" * - * Duration.format(Duration.millis(1000)) // "1s" - * Duration.format(Duration.millis(1001)) // "1s 1ms" + * Duration.format(Duration.millis(1000)) // => "1s" + * Duration.format(Duration.millis(1001)) // => "1s 1ms" * ``` * * @category converting diff --git a/repos/effect/packages/effect/src/Effect.ts b/repos/effect/packages/effect/src/Effect.ts index f15b5f49f0..6cb027671e 100644 --- a/repos/effect/packages/effect/src/Effect.ts +++ b/repos/effect/packages/effect/src/Effect.ts @@ -15,7 +15,7 @@ import type * as Cause from "./Cause.ts" import type { Clock } from "./Clock.ts" import * as Context from "./Context.ts" import * as Duration from "./Duration.ts" -import type { ExecutionPlan } from "./ExecutionPlan.ts" +import type * as ExecutionPlan from "./ExecutionPlan.ts" import * as Exit from "./Exit.ts" import type { Fiber } from "./Fiber.ts" import type * as Filter from "./Filter.ts" @@ -26,6 +26,7 @@ import * as core from "./internal/core.ts" import * as internal from "./internal/effect.ts" import * as internalExecutionPlan from "./internal/executionPlan.ts" import * as internalLayer from "./internal/layer.ts" +import * as InternalRecord from "./internal/record.ts" import * as internalRequest from "./internal/request.ts" import * as internalSchedule from "./internal/schedule.ts" import type * as Layer from "./Layer.ts" @@ -137,7 +138,7 @@ export interface EffectUnify { /** * Type lambda used to represent `Effect` in higher-kinded APIs. * - * @category type lambdas + * @category utility types * @since 2.0.0 */ export interface EffectTypeLambda extends TypeLambda { @@ -167,7 +168,7 @@ export interface Variance { * @see {@link Error} for extracting the failure type from the same `Effect` * @see {@link Services} for extracting the required services from the same `Effect` * - * @category models + * @category utility types * @since 2.0.0 */ export type Success = T extends Effect ? _A @@ -188,7 +189,7 @@ export type Success = T extends Effect ? _A * @see {@link Success} for extracting the success value type instead * @see {@link Services} for extracting the required services type instead * - * @category models + * @category utility types * @since 2.0.0 */ export type Error = T extends Effect ? _E @@ -205,7 +206,7 @@ export type Error = T extends Effect ? _E * @see {@link Success} for extracting the success value type instead * @see {@link Error} for extracting the failure type instead * - * @category models + * @category utility types * @since 4.0.0 */ export type Services = T extends Effect ? _R @@ -216,11 +217,11 @@ export type Services = T extends Effect ? _R * * **Example** (Checking whether a value is an Effect) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * - * console.log(Effect.isEffect(Effect.succeed(1))) // true - * console.log(Effect.isEffect("hello")) // false + * Effect.isEffect(Effect.succeed(1)) // => true + * Effect.isEffect("hello") // => false * ``` * * @category guards @@ -403,112 +404,91 @@ export declare namespace All { * * **Example** (Collecting tuple results in order) * - * ```ts - * import { Console, Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect } from "effect" * * const tupleOfEffects = [ - * Effect.succeed(42).pipe(Effect.tap(Console.log)), - * Effect.succeed("Hello").pipe(Effect.tap(Console.log)) + * Effect.succeed(42), + * Effect.succeed("Hello") * ] as const * * // ┌─── Effect<[number, string], never, never> * // ▼ * const resultsAsTuple = Effect.all(tupleOfEffects) * - * Effect.runPromise(resultsAsTuple).then(console.log) - * // Output: - * // 42 - * // Hello - * // [ 42, 'Hello' ] + * await Effect.runPromise(resultsAsTuple) // => [42, "Hello"] * ``` * * **Example** (Collecting iterable results in order) * - * ```ts - * import { Console, Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect } from "effect" * * const iterableOfEffects: Iterable> = [1, 2, 3].map( - * (n) => Effect.succeed(n).pipe(Effect.tap(Console.log)) + * Effect.succeed * ) * * // ┌─── Effect * // ▼ * const resultsAsArray = Effect.all(iterableOfEffects) * - * Effect.runPromise(resultsAsArray).then(console.log) - * // Output: - * // 1 - * // 2 - * // 3 - * // [ 1, 2, 3 ] + * await Effect.runPromise(resultsAsArray) // => [1, 2, 3] * ``` * * **Example** (Collecting struct results by key) * - * ```ts - * import { Console, Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect } from "effect" * * const structOfEffects = { - * a: Effect.succeed(42).pipe(Effect.tap(Console.log)), - * b: Effect.succeed("Hello").pipe(Effect.tap(Console.log)) + * a: Effect.succeed(42), + * b: Effect.succeed("Hello") * } * * // ┌─── Effect<{ a: number; b: string; }, never, never> * // ▼ * const resultsAsStruct = Effect.all(structOfEffects) * - * Effect.runPromise(resultsAsStruct).then(console.log) - * // Output: - * // 42 - * // Hello - * // { a: 42, b: 'Hello' } + * await Effect.runPromise(resultsAsStruct) // => { a: 42, b: "Hello" } * ``` * * **Example** (Collecting record results by key) * - * ```ts - * import { Console, Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect } from "effect" * * const recordOfEffects: Record> = { - * key1: Effect.succeed(1).pipe(Effect.tap(Console.log)), - * key2: Effect.succeed(2).pipe(Effect.tap(Console.log)) + * key1: Effect.succeed(1), + * key2: Effect.succeed(2) * } * * // ┌─── Effect<{ [x: string]: number; }, never, never> * // ▼ * const resultsAsRecord = Effect.all(recordOfEffects) * - * Effect.runPromise(resultsAsRecord).then(console.log) - * // Output: - * // 1 - * // 2 - * // { key1: 1, key2: 2 } + * await Effect.runPromise(resultsAsRecord) // => { key1: 1, key2: 2 } * ``` * * **Example** (Stopping on the first failure) * - * ```ts - * import { Console, Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect, Exit } from "effect" + * const output: Array = [] + * const record = (value: unknown) => Effect.sync(() => { output.push(value) }) * * const program = Effect.all([ - * Effect.succeed("Task1").pipe(Effect.tap(Console.log)), - * Effect.fail("Task2: Oh no!").pipe(Effect.tap(Console.log)), + * Effect.succeed("Task1").pipe(Effect.tap(record)), + * Effect.fail("Task2: Oh no!").pipe(Effect.tap(record)), * // Won't execute due to earlier failure - * Effect.succeed("Task3").pipe(Effect.tap(Console.log)) + * Effect.succeed("Task3").pipe(Effect.tap(record)) * ]) * - * Effect.runPromiseExit(program).then(console.log) - * // Output: - * // Task1 - * // { - * // _id: 'Exit', - * // _tag: 'Failure', - * // cause: { _id: 'Cause', _tag: 'Fail', failure: 'Task2: Oh no!' } - * // } + * const outcome = await Effect.runPromiseExit(program) + * const observation = [output, outcome] // => [["Task1"], Exit.fail("Task2: Oh no!")] * ``` * * @see {@link forEach} for iterating over elements and applying an effect. - * @category collecting + * @category combining * @since 2.0.0 */ export const all: < @@ -539,18 +519,17 @@ export const all: < * * **Example** (Separating successes and failures) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * * const program = Effect.partition([0, 1, 2, 3], (n) => * n % 2 === 0 ? Effect.fail(`${n} is even`) : Effect.succeed(n) * ) * - * Effect.runPromise(program).then(console.log) - * // [ ["0 is even", "2 is even"], [1, 3] ] + * await Effect.runPromise(program) // => [['0 is even', '2 is even'], [1, 3]] * ``` * - * @category collecting + * @category filtering * @since 2.0.0 */ export const partition: { @@ -565,6 +544,55 @@ export const partition: { ): Effect<[excluded: Array, satisfying: Array], never, R> } = internal.partition +/** + * Reduces elements from left to right with an effectful accumulator function. + * + * **When to use** + * + * Use when each accumulation step is effectful and must run sequentially in + * iteration order. + * + * **Details** + * + * The accumulator function receives the current accumulator, the current + * element, and its zero-based index. The `zero` function is evaluated each + * time the effect runs. An empty iterable succeeds with its result. If a step + * fails, remaining elements are not processed. + * + * **Example** (Summing values sequentially) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * const output: Array = [] + * + * const program = Effect.reduce( + * [1, 2, 3], + * () => 0, + * (total, value, index) => + * Effect.sync(() => { output.push(`Adding ${value} at index ${index}`) }).pipe( + * Effect.as(total + value) + * ) + * ) + * + * void output.push(await Effect.runPromise(program)) + * output // => ["Adding 1 at index 0", "Adding 2 at index 1", "Adding 3 at index 2", 6] + * ``` + * + * @category folding + * @since 2.0.0 + */ +export const reduce: { + ( + zero: LazyArg, + f: (z: Z, a: A, i: number) => Effect + ): (elements: Iterable) => Effect + ( + elements: Iterable, + zero: LazyArg, + f: (z: Z, a: A, i: number) => Effect + ): Effect +} = internal.reduce + /** * Applies an effectful function to each element and accumulates all failures. * @@ -579,28 +607,17 @@ export const partition: { * * **Example** (Validating every element) * - * ```ts - * import { Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect, Exit } from "effect" * * const program = Effect.validate([0, 1, 2, 3], (n) => * n % 2 === 0 ? Effect.fail(`${n} is even`) : Effect.succeed(n) * ) * - * Effect.runPromiseExit(program).then(console.log) - * // { - * // _id: 'Exit', - * // _tag: 'Failure', - * // cause: { - * // _id: 'Cause', - * // reasons: [ - * // { _id: 'Reason', _tag: 'Fail', error: '0 is even' }, - * // { _id: 'Reason', _tag: 'Fail', error: '2 is even' } - * // ] - * // } - * // } + * await Effect.runPromiseExit(program) // => Exit.fail(["0 is even", "2 is even"]) * ``` * - * @category error accumulation + * @category validation * @since 2.0.0 */ export const validate: { @@ -646,16 +663,15 @@ export const validate: { * * **Example** (Finding the first successful match) * - * ```ts - * import { Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect, Option } from "effect" * * const program = Effect.findFirst([1, 2, 3, 4], (n) => Effect.succeed(n > 2)) * - * Effect.runPromise(program).then(console.log) - * // { _id: 'Option', _tag: 'Some', value: 3 } + * await Effect.runPromise(program) // => Option.some(3) * ``` * - * @category collecting + * @category searching * @since 2.0.0 */ export const findFirst: { @@ -683,7 +699,7 @@ export const findFirst: { * * @see {@link findFirst} for the simpler effectful predicate-based variant * - * @category collecting + * @category searching * @since 4.0.0 */ export const findFirstFilter: { @@ -724,50 +740,40 @@ export const findFirstFilter: { * * **Example** (Mapping over an iterable with effects) * - * ```ts - * import { Console, Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect } from "effect" + * const output: Array = [] * * const result = Effect.forEach( * [1, 2, 3, 4, 5], * (n, index) => - * Console.log(`Currently at index ${index}`).pipe(Effect.as(n * 2)) + * Effect.sync(() => { output.push(`Currently at index ${index}`) }).pipe(Effect.as(n * 2)) * ) * - * Effect.runPromise(result).then(console.log) - * // Output: - * // Currently at index 0 - * // Currently at index 1 - * // Currently at index 2 - * // Currently at index 3 - * // Currently at index 4 - * // [ 2, 4, 6, 8, 10 ] + * void output.push(await Effect.runPromise(result)) + * output // => ["Currently at index 0", "Currently at index 1", "Currently at index 2", "Currently at index 3", "Currently at index 4", [2, 4, 6, 8, 10]] * ``` * * **Example** (Running effects without collecting results) * - * ```ts - * import { Console, Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect } from "effect" + * const output: Array = [] * * // Apply effects but discard the results * const result = Effect.forEach( * [1, 2, 3, 4, 5], * (n, index) => - * Console.log(`Currently at index ${index}`).pipe(Effect.as(n * 2)), + * Effect.sync(() => { output.push(`Currently at index ${index}`) }).pipe(Effect.as(n * 2)), * { discard: true } * ) * - * Effect.runPromise(result).then(console.log) - * // Output: - * // Currently at index 0 - * // Currently at index 1 - * // Currently at index 2 - * // Currently at index 3 - * // Currently at index 4 - * // undefined + * void output.push(await Effect.runPromise(result)) + * output // => ["Currently at index 0", "Currently at index 1", "Currently at index 2", "Currently at index 3", "Currently at index 4", undefined] * ``` * * @see {@link all} for combining multiple effects into one. - * @category collecting + * @category sequencing * @since 2.0.0 */ export const forEach: { @@ -787,27 +793,23 @@ export const forEach: { * * **Example** (Repeating an effectful loop) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" + * const output: Array = [] * * let counter = 0 * * const program = Effect.whileLoop({ * while: () => counter < 5, * body: () => Effect.sync(() => ++counter), - * step: (n) => console.log(`Current count: ${n}`) + * step: (n) => void output.push(`Current count: ${n}`) * }) * - * Effect.runPromise(program) - * // Output: - * // Current count: 1 - * // Current count: 2 - * // Current count: 3 - * // Current count: 4 - * // Current count: 5 + * await Effect.runPromise(program) + * output // => ["Current count: 1", "Current count: 2", "Current count: 3", "Current count: 4", "Current count: 5"] * ``` * - * @category collecting + * @category repetition * @since 2.0.0 */ export const whileLoop: (options: { @@ -844,22 +846,16 @@ export const whileLoop: (options: { * * **Example** (Wrapping a non-rejecting Promise) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * - * const delay = (message: string) => - * Effect.promise( - * () => - * new Promise((resolve) => { - * setTimeout(() => { - * resolve(message) - * }, 2000) - * }) - * ) + * const succeedAsync = (message: string) => + * Effect.promise(() => Promise.resolve(message)) * * // ┌─── Effect * // ▼ - * const program = delay("Async operation completed successfully!") + * const program = succeedAsync("Async operation completed successfully!") + * await Effect.runPromise(program) // => "Async operation completed successfully!" * ``` * * @see {@link tryPromise} for a version that can handle failures. @@ -903,37 +899,37 @@ export const promise: ( * * **Example** (Wrapping a fetch request that may fail) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * * const getTodo = (id: number) => - * // Will catch any errors and propagate them as UnknownError - * Effect.tryPromise(() => - * fetch(`https://jsonplaceholder.typicode.com/todos/${id}`) - * ) + * Effect.tryPromise(() => Promise.resolve({ id, completed: false })) * - * // ┌─── Effect + * // ┌─── Effect<{ id: number; completed: boolean }, UnknownError, never> * // ▼ * const program = getTodo(1) + * await Effect.runPromise(program) // => { id: 1, completed: false } * ``` * * **Example** (Mapping Promise rejections to a tagged error) * - * ```ts + * ```ts import.meta.vitest * import { Data, Effect } from "effect" * * class TodoFetchError extends Data.TaggedError("TodoFetchError")<{ readonly cause: unknown }> {} * * const getTodo = (id: number) => * Effect.tryPromise({ - * try: () => fetch(`https://jsonplaceholder.typicode.com/todos/${id}`), + * try: () => Promise.reject(`Todo ${id} is unavailable`), * // remap the error * catch: (cause) => new TodoFetchError({ cause }) * }) * - * // ┌─── Effect + * // ┌─── Effect * // ▼ - * const program = getTodo(1) + * const program = Effect.flip(getTodo(1)) + * const error = await Effect.runPromise(program) + * error._tag // => "TodoFetchError" * ``` * * @see {@link promise} if the effectful computation is asynchronous and does not throw errors. @@ -956,7 +952,7 @@ export const tryPromise: ( * * **Example** (Creating a successful effect) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * * // Creating an effect that represents a successful scenario @@ -964,6 +960,7 @@ export const tryPromise: ( * // ┌─── Effect * // ▼ * const success = Effect.succeed(42) + * Effect.runSync(success) // => 42 * ``` * * @see {@link fail} to create an effect that represents a failure. @@ -977,13 +974,12 @@ export const succeed: (value: A) => Effect = internal.succeed * * **Example** (Succeeding with Option.none) * - * ```ts - * import { Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect, Option } from "effect" * * const program = Effect.succeedNone * - * Effect.runPromise(program).then(console.log) - * // Output: { _id: 'Option', _tag: 'None' } + * Effect.runSync(program) // => Option.none() * ``` * * @category constructors @@ -996,13 +992,12 @@ export const succeedNone: Effect> = internal.succeedNone * * **Example** (Succeeding with Option.some) * - * ```ts - * import { Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect, Option } from "effect" * * const program = Effect.succeedSome(42) * - * Effect.runPromise(program).then(console.log) - * // Output: { _id: 'Option', _tag: 'Some', value: 42 } + * Effect.runSync(program) // => Option.some(42) * ``` * * @category constructors @@ -1028,7 +1023,7 @@ export const succeedSome: (value: A) => Effect> = internal.succeedS * * **Example** (Lazily evaluating side effects) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * * let i = 0 @@ -1037,16 +1032,16 @@ export const succeedSome: (value: A) => Effect> = internal.succeedS * * const good = Effect.suspend(() => Effect.succeed(i++)) * - * console.log(Effect.runSync(bad)) // Output: 0 - * console.log(Effect.runSync(bad)) // Output: 0 + * Effect.runSync(bad) // => 0 + * Effect.runSync(bad) // => 0 * - * console.log(Effect.runSync(good)) // Output: 1 - * console.log(Effect.runSync(good)) // Output: 2 + * Effect.runSync(good) // => 1 + * Effect.runSync(good) // => 2 * ``` * * **Example** (Suspending recursive Fibonacci evaluation) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * * const blowsUp = (n: number): Effect.Effect => @@ -1066,13 +1061,12 @@ export const succeedSome: (value: A) => Effect> = internal.succeedS * (a, b) => a + b * ) * - * console.log(Effect.runSync(allGood(32))) - * // Output: 3524578 + * Effect.runSync(allGood(16)) // => 1597 * ``` * * **Example** (Helping TypeScript infer recursive effect types) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * * // Without suspend, TypeScript may struggle with type inference. @@ -1093,6 +1087,8 @@ export const succeedSome: (value: A) => Effect> = internal.succeedS * ? Effect.fail(new Error("Cannot divide by zero")) * : Effect.succeed(a / b) * ) + * + * Effect.runSync(withSuspend(6, 2)) // => 3 * ``` * * @category constructors @@ -1121,17 +1117,20 @@ export const suspend: ( * * **Example** (Capturing synchronous logging in an Effect) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" + * const output: Array = [] * * const log = (message: string) => * Effect.sync(() => { - * console.log(message) // side effect + * void output.push(message) // side effect * }) * * // ┌─── Effect * // ▼ * const program = log("Hello, World!") + * Effect.runSync(program) + * output // => ["Hello, World!"] * ``` * * @see {@link try_ | try} for a version that can handle failures. @@ -1179,19 +1178,20 @@ export { * * **Example** (Integrating callback APIs) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" + * const output: Array = [] * - * const delay = (ms: number) => + * const fromCallback = (message: string) => * Effect.callback((resume) => { - * const timeoutId = setTimeout(() => { + * queueMicrotask(() => { + * void output.push(message) * resume(Effect.void) - * }, ms) - * // Cleanup function for interruption - * return Effect.sync(() => clearTimeout(timeoutId)) + * }) * }) * - * const program = delay(1000) + * await Effect.runPromise(fromCallback("callback completed")) + * output // => ["callback completed"] * ``` * * @category constructors @@ -1211,17 +1211,11 @@ export const callback: ( * * **Example** (Creating a never-ending effect) * - * ```ts - * import { Effect } from "effect" - * - * // This effect will never complete - * const program = Effect.never - * - * // This will run forever (or until interrupted) - * // Effect.runPromise(program) // Never resolves + * ```ts import.meta.vitest + * import { Effect, Option } from "effect" * - * // Use with timeout for practical applications - * const timedProgram = Effect.timeout(program, "1 second") + * const program = Effect.timeoutOption(Effect.never, 0) + * await Effect.runPromise(program) // => Option.none() * ``` * * @category constructors @@ -1235,7 +1229,7 @@ export const never: Effect = internal.never * * **Example** (Starting do notation) * - * ```ts + * ```ts import.meta.vitest * import { Effect, pipe } from "effect" * * const program = pipe( @@ -1244,9 +1238,11 @@ export const never: Effect = internal.never * Effect.bind("y", ({ x }) => Effect.succeed(x + 1)), * Effect.let("sum", ({ x, y }) => x + y) * ) + * + * Effect.runSync(program) // => { x: 2, y: 3, sum: 5 } * ``` * - * @category do notation + * @category constructors * @since 2.0.0 */ export const Do: Effect<{}> = internal.Do @@ -1263,7 +1259,7 @@ export const Do: Effect<{}> = internal.Do * @see {@link Do} for starting from an empty accumulated record * @see {@link bind} for adding fields produced by effects * - * @category do notation + * @category mapping * @since 2.0.0 */ export const bindTo: { @@ -1305,7 +1301,7 @@ export { * @see {@link Do} for starting from an empty accumulated record * @see {@link gen} for sequencing without accumulating a record * - * @category do notation + * @category mapping * @since 2.0.0 */ let_ as let @@ -1335,7 +1331,7 @@ export { * @see {@link bindTo} for naming the success value of an existing effect * @see {@link gen} for generator-based sequencing without accumulating a record * - * @category do notation + * @category sequencing * @since 2.0.0 */ export const bind: { @@ -1368,7 +1364,7 @@ export const bind: { * * **Example** (Sequencing effects with generators) * - * ```ts + * ```ts import.meta.vitest * import { Data, Effect } from "effect" * * class DiscountRateError extends Data.TaggedError("DiscountRateError")<{}> {} @@ -1397,6 +1393,8 @@ export const bind: { * const finalAmount = addServiceCharge(discountedAmount) * return `Final amount to charge: ${finalAmount}` * }) + * + * await Effect.runPromise(program) // => "Final amount to charge: 96" * ``` * * @category constructors @@ -1459,7 +1457,7 @@ export declare namespace gen { * * **Example** (Creating a failed effect) * - * ```ts + * ```ts import.meta.vitest * import { Data, Effect } from "effect" * * class OperationFailedError extends Data.TaggedError("OperationFailedError")<{}> {} @@ -1469,6 +1467,7 @@ export declare namespace gen { * const failure = Effect.fail( * new OperationFailedError() * ) + * Effect.runSync(Effect.flip(failure))._tag // => "OperationFailedError" * ``` * * @see {@link succeed} to create an effect that represents a successful value. @@ -1490,15 +1489,14 @@ export const fail: (error: E) => Effect = internal.fail * * **Example** (Lazily creating failures) * - * ```ts + * ```ts import.meta.vitest * import { Data, Effect } from "effect" * - * class ProgramError extends Data.TaggedError("ProgramError")<{ readonly failedAt: Date }> {} + * class ProgramError extends Data.TaggedError("ProgramError")<{ readonly operation: string }> {} * - * const program = Effect.failSync(() => new ProgramError({ failedAt: new Date() })) + * const program = Effect.failSync(() => new ProgramError({ operation: "sync" })) * - * Effect.runPromiseExit(program).then(console.log) - * // Output: { _id: 'Exit', _tag: 'Failure', cause: ... } + * Effect.runSync(Effect.flip(program)).operation // => "sync" * ``` * * @category constructors @@ -1522,15 +1520,14 @@ export const failSync: (evaluate: LazyArg) => Effect = internal. * * **Example** (Failing with a full Cause) * - * ```ts + * ```ts import.meta.vitest * import { Cause, Effect } from "effect" * * const program = Effect.failCause( * Cause.fail("Network error") * ) * - * Effect.runPromiseExit(program).then(console.log) - * // Output: { _id: 'Exit', _tag: 'Failure', cause: ... } + * Effect.runSync(Effect.flip(program)) // => "Network error" * ``` * * @category constructors @@ -1551,15 +1548,14 @@ export const failCause: (cause: Cause.Cause) => Effect = interna * * **Example** (Lazily creating a Cause) * - * ```ts + * ```ts import.meta.vitest * import { Cause, Effect } from "effect" * * const program = Effect.failCauseSync(() => * Cause.fail("Error computed at runtime") * ) * - * Effect.runPromiseExit(program).then(console.log) - * // Output: { _id: 'Exit', _tag: 'Failure', cause: ... } + * Effect.runSync(Effect.flip(program)) // => "Error computed at runtime" * ``` * * @category constructors @@ -1588,22 +1584,20 @@ export const failCauseSync: ( * * **Example** (Failing on division by zero) * - * ```ts - * import { Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect, Exit } from "effect" * + * const defect = new Error("Cannot divide by zero") * const divide = (a: number, b: number) => * b === 0 - * ? Effect.die(new Error("Cannot divide by zero")) + * ? Effect.die(defect) * : Effect.succeed(a / b) * * // ┌─── Effect * // ▼ * const program = divide(1, 0) * - * Effect.runPromise(program).catch(console.error) - * // Output: - * // (FiberFailure) Error: Cannot divide by zero - * // ...stack trace... + * Effect.runSyncExit(program) // => Exit.die(defect) * ``` * * @category constructors @@ -1646,23 +1640,23 @@ export { * * **Example** (Parsing JSON) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * * const parseJSON = (input: string) => * Effect.try(() => JSON.parse(input)) * * // Success case - * Effect.runPromise(parseJSON("{\"name\": \"Alice\"}")).then(console.log) - * // Output: { name: "Alice" } + * await Effect.runPromise(parseJSON("{\"name\": \"Alice\"}")) // => { name: 'Alice' } * * // Failure case maps the thrown value to UnknownError - * Effect.runPromiseExit(parseJSON("invalid json")).then(console.log) + * const exit = await Effect.runPromiseExit(parseJSON("invalid json")) + * exit._tag // => "Failure" * ``` * * **Example** (Mapping exceptions to a tagged error) * - * ```ts + * ```ts import.meta.vitest * import { Data, Effect } from "effect" * * class JsonParsingError extends Data.TaggedError("JsonParsingError")<{ readonly cause: unknown }> {} @@ -1673,8 +1667,8 @@ export { * catch: (cause) => new JsonParsingError({ cause }) * }) * - * Effect.runPromiseExit(parseJSON("invalid json")).then(console.log) - * // Output: Exit.failure with custom Error message + * const error = await Effect.runPromise(Effect.flip(parseJSON("invalid json"))) + * error._tag // => "JsonParsingError" * ``` * * @see {@link sync} if the effectful computation is synchronous and does not @@ -1690,16 +1684,18 @@ export { * * **Example** (Yielding to other fibers) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" + * const output: Array = [] * * const program = Effect.gen(function*() { - * console.log("Before yield") + * void output.push("Before yield") * yield* Effect.yieldNow - * console.log("After yield") + * void output.push("After yield") * }) * - * Effect.runPromise(program) + * await Effect.runPromise(program) + * output // => ["Before yield", "After yield"] * ``` * * @category constructors @@ -1712,16 +1708,18 @@ export const yieldNow: Effect = internal.yieldNow * * **Example** (Yielding with priority) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" + * const output: Array = [] * * const program = Effect.gen(function*() { - * console.log("High priority task") + * void output.push("High priority task") * yield* Effect.yieldNowWith(10) // Higher priority - * console.log("Continued after yield") + * void output.push("Continued after yield") * }) * - * Effect.runPromise(program) + * await Effect.runPromise(program) + * output // => ["High priority task", "Continued after yield"] * ``` * * @category constructors @@ -1734,15 +1732,12 @@ export const yieldNowWith: (priority?: number) => Effect = internal.yieldN * * **Example** (Reading the current fiber) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * - * const program = Effect.withFiber((fiber) => - * Effect.succeed(`Fiber ID: ${fiber.id}`) - * ) + * const program = Effect.withFiber((fiber) => Effect.succeed(typeof fiber.id)) * - * Effect.runPromise(program).then(console.log) - * // Output: Fiber ID: 1 + * Effect.runSync(program) // => "number" * ``` * * @category constructors @@ -1761,8 +1756,9 @@ export const withFiber: ( * * **Example** (Converting a Result into an Effect) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Result } from "effect" + * const output: Array = [] * * const success = Result.succeed(42) * const failure = Result.fail("Something went wrong") @@ -1770,9 +1766,9 @@ export const withFiber: ( * const effect1 = Effect.fromResult(success) * const effect2 = Effect.fromResult(failure) * - * Effect.runPromise(effect1).then(console.log) // 42 - * Effect.runPromiseExit(effect2).then(console.log) - * // { _id: 'Exit', _tag: 'Failure', cause: { _id: 'Cause', _tag: 'Fail', failure: 'Something went wrong' } } + * void output.push(Effect.runSync(effect1)) + * void output.push(Effect.runSync(Effect.flip(effect2))) + * output // => [42, "Something went wrong"] * ``` * * @category converting @@ -1797,8 +1793,9 @@ export const fromResult: (result: Result.Result) => Effect = i * * **Example** (Converting an Option into an Effect) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Option } from "effect" + * const output: Array = [] * * const some = Option.some(42) * const none = Option.none() @@ -1807,9 +1804,10 @@ export const fromResult: (result: Result.Result) => Effect = i * const effect2 = Effect.fromOption(none) * const effect3 = Effect.fromOption(none, () => new Error("missing")) * - * Effect.runPromise(effect1).then(console.log) // 42 - * Effect.runPromiseExit(effect2).then(console.log) - * // { _id: 'Exit', _tag: 'Failure', cause: { _id: 'Cause', _tag: 'Fail', failure: { _id: 'NoSuchElementError' } } } + * void output.push(Effect.runSync(effect1)) + * void output.push(Effect.runSync(Effect.flip(effect2))._tag) + * void output.push(Effect.runSync(Effect.flip(effect3)).message) + * output // => [42, "NoSuchElementError", "missing"] * ``` * * @category converting @@ -1838,7 +1836,7 @@ export const fromOption: | LazyArg, E = Ca * * **Example** (Transposing an Option of an Effect) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Option } from "effect" * * const some = Option.some(Effect.succeed(42)) @@ -1847,8 +1845,7 @@ export const fromOption: | LazyArg, E = Ca * // ▼ * const program = Effect.transposeOption(some) * - * Effect.runPromise(program).then(console.log) - * // Output: { _id: 'Option', _tag: 'Some', value: 42 } + * Effect.runSync(program) // => Option.some(42) * ``` * * @category converting @@ -1864,20 +1861,20 @@ export const transposeOption: ( * * **Example** (Failing on nullish values) * - * ```ts - * import { Console, Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect } from "effect" + * const output: Array = [] * * const program = Effect.fn(function*(input: string | null) { * const value = yield* Effect.fromNullishOr(input) - * yield* Console.log(value) + * yield* Effect.sync(() => { output.push(value) }) * }, - * Effect.catch(() => Console.log("missing")) + * Effect.catch(() => Effect.sync(() => { output.push("missing") })) * ) * - * Effect.runPromise(program(null)) - * // Output: missing - * Effect.runPromise(program("hello")) - * // Output: hello + * await Effect.runPromise(program(null)) + * await Effect.runPromise(program("hello")) + * output // => ["missing", "hello"] * ``` * * @category converting @@ -1911,8 +1908,9 @@ export const fromNullishOr: (value: A) => Effect, Cause.NoSuch * * **Example** (Choosing flatMap syntax variants) * - * ```ts + * ```ts import.meta.vitest * import { Effect, pipe } from "effect" + * const output: Array = [] * * const myEffect = Effect.succeed(1) * const transformation = (n: number) => Effect.succeed(n + 1) @@ -1920,11 +1918,18 @@ export const fromNullishOr: (value: A) => Effect, Cause.NoSuch * const flatMappedWithPipe = pipe(myEffect, Effect.flatMap(transformation)) * const flatMappedWithDataFirst = Effect.flatMap(myEffect, transformation) * const flatMappedWithMethod = myEffect.pipe(Effect.flatMap(transformation)) + * + * void output.push(Effect.runSync(Effect.all([ + * flatMappedWithPipe, + * flatMappedWithDataFirst, + * flatMappedWithMethod + * ]))) + * output // => [[2, 2, 2]] * ``` * * **Example** (Sequencing dependent effects) * - * ```ts + * ```ts import.meta.vitest * import { Data, Effect, pipe } from "effect" * * class DiscountRateError extends Data.TaggedError("DiscountRateError")<{}> {} @@ -1947,8 +1952,7 @@ export const fromNullishOr: (value: A) => Effect, Cause.NoSuch * Effect.flatMap((amount) => applyDiscount(amount, 5)) * ) * - * Effect.runPromise(finalAmount).then(console.log) - * // Output: 95 + * await Effect.runPromise(finalAmount) // => 95 * ``` * * @see {@link tap} for a version that ignores the result of the effect. @@ -1970,16 +1974,19 @@ export const flatMap: { * * **Example** (Flattening nested effects) * - * ```ts - * import { Console, Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect } from "effect" + * const output: Array = [] * * const nested = Effect.succeed(Effect.succeed("hello")) * * const program = Effect.gen(function*() { * const value = yield* Effect.flatten(nested) - * yield* Console.log(value) - * // Output: hello + * yield* Effect.sync(() => { output.push(value) }) * }) + * + * Effect.runSync(program) + * output // => ["hello"] * ``` * * @category sequencing @@ -2009,8 +2016,9 @@ export const flatten: (self: Effect, E2, R2>) = * * **Example** (Choosing andThen syntax variants) * - * ```ts + * ```ts import.meta.vitest * import { Effect, pipe } from "effect" + * const output: Array = [] * * const myEffect = Effect.succeed(1) * const anotherEffect = Effect.succeed("done") @@ -2018,11 +2026,18 @@ export const flatten: (self: Effect, E2, R2>) = * const transformedWithPipe = pipe(myEffect, Effect.andThen(anotherEffect)) * const transformedWithDataFirst = Effect.andThen(myEffect, anotherEffect) * const transformedWithMethod = myEffect.pipe(Effect.andThen(anotherEffect)) + * + * void output.push(Effect.runSync(Effect.all([ + * transformedWithPipe, + * transformedWithDataFirst, + * transformedWithMethod + * ]))) + * output // => [['done', 'done', 'done']] * ``` * * **Example** (Sequencing a discount calculation after fetching a total) * - * ```ts + * ```ts import.meta.vitest * import { Data, Effect, pipe } from "effect" * * class DiscountRateError extends Data.TaggedError("DiscountRateError")<{}> {} @@ -2046,8 +2061,7 @@ export const flatten: (self: Effect, E2, R2>) = * Effect.flatMap((amount) => applyDiscount(amount, 5)) * ) * - * Effect.runPromise(result1).then(console.log) - * // Output: 190 + * await Effect.runPromise(result1) // => 190 * * // Using Effect.andThen * const result2 = pipe( @@ -2056,8 +2070,7 @@ export const flatten: (self: Effect, E2, R2>) = * Effect.andThen((amount) => applyDiscount(amount, 5)) * ) * - * Effect.runPromise(result2).then(console.log) - * // Output: 190 + * await Effect.runPromise(result2) // => 190 * ``` * * @category sequencing @@ -2098,8 +2111,9 @@ export const andThen: { * * **Example** (Logging a step in a pipeline) * - * ```ts - * import { Console, Data, Effect, pipe } from "effect" + * ```ts import.meta.vitest + * import { Data, Effect, pipe } from "effect" + * const output: Array = [] * * class DiscountRateError extends Data.TaggedError("DiscountRateError")<{}> {} * @@ -2118,15 +2132,13 @@ export const andThen: { * const finalAmount = pipe( * fetchTransactionAmount, * // Log the fetched transaction amount - * Effect.tap((amount) => Console.log(`Apply a discount to: ${amount}`)), + * Effect.tap((amount) => Effect.sync(() => { output.push(`Apply a discount to: ${amount}`) })), * // `amount` is still available! * Effect.flatMap((amount) => applyDiscount(amount, 5)) * ) * - * Effect.runPromise(finalAmount).then(console.log) - * // Output: - * // Apply a discount to: 100 - * // 95 + * void output.push(await Effect.runPromise(finalAmount)) + * output // => ["Apply a discount to: 100", 95] * ``` * * @category sequencing @@ -2180,8 +2192,8 @@ export const tap: { * * **Example** (Capturing success or failure as Result) * - * ```ts - * import { Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect, Result } from "effect" * * const success = Effect.succeed(42) * const failure = Effect.fail("Something went wrong") @@ -2189,17 +2201,15 @@ export const tap: { * const program1 = Effect.result(success) * const program2 = Effect.result(failure) * - * Effect.runPromise(program1).then(console.log) - * // { _id: 'Result', _tag: 'Success', value: 42 } + * Effect.runSync(program1) // => Result.succeed(42) * - * Effect.runPromise(program2).then(console.log) - * // { _id: 'Result', _tag: 'Failure', error: 'Something went wrong' } + * Effect.runSync(program2) // => Result.fail("Something went wrong") * ``` * * @see {@link option} for a version that uses `Option` instead. * @see {@link exit} for a version that encapsulates both recoverable errors and defects in an `Exit`. * - * @category outcome encapsulation + * @category error handling * @since 4.0.0 */ export const result: (self: Effect) => Effect, never, R> = internal.result @@ -2228,26 +2238,21 @@ export const result: (self: Effect) => Effect [Option.some(1), Option.none()] * ``` * * @see {@link result} for a version that uses `Result` instead. * @see {@link exit} for a version that encapsulates both recoverable errors and defects in an `Exit`. * - * @category outcome encapsulation + * @category error handling * @since 2.0.0 */ export const option: (self: Effect) => Effect, never, R> = internal.option @@ -2272,8 +2277,8 @@ export const option: (self: Effect) => Effect, never * * **Example** (Capturing completion as Exit) * - * ```ts - * import { Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect, Exit } from "effect" * * const success = Effect.succeed(42) * const failure = Effect.fail("Something went wrong") @@ -2281,17 +2286,15 @@ export const option: (self: Effect) => Effect, never * const program1 = Effect.exit(success) * const program2 = Effect.exit(failure) * - * Effect.runPromise(program1).then(console.log) - * // { _id: 'Exit', _tag: 'Success', value: 42 } + * Effect.runSync(program1) // => Exit.succeed(42) * - * Effect.runPromise(program2).then(console.log) - * // { _id: 'Exit', _tag: 'Failure', cause: { _id: 'Cause', _tag: 'Fail', failure: 'Something went wrong' } } + * Effect.runSync(program2) // => Exit.fail("Something went wrong") * ``` * * @see {@link option} for a version that uses `Option` instead. * @see {@link result} for a version that uses `Result` instead. * - * @category outcome encapsulation + * @category error handling * @since 2.0.0 */ export const exit: ( @@ -2318,8 +2321,9 @@ export const exit: ( * * **Example** (Choosing map syntax variants) * - * ```ts + * ```ts import.meta.vitest * import { Effect, pipe } from "effect" + * const output: Array = [] * * const myEffect = Effect.succeed(1) * const transformation = (n: number) => n + 1 @@ -2327,11 +2331,18 @@ export const exit: ( * const mappedWithPipe = pipe(myEffect, Effect.map(transformation)) * const mappedWithDataFirst = Effect.map(myEffect, transformation) * const mappedWithMethod = myEffect.pipe(Effect.map(transformation)) + * + * void output.push(Effect.runSync(Effect.all([ + * mappedWithPipe, + * mappedWithDataFirst, + * mappedWithMethod + * ]))) + * output // => [[2, 2, 2]] * ``` * * **Example** (Adding a service charge) * - * ```ts + * ```ts import.meta.vitest * import { Effect, pipe } from "effect" * * const addServiceCharge = (amount: number) => amount + 1 @@ -2343,8 +2354,7 @@ export const exit: ( * Effect.map(addServiceCharge) * ) * - * Effect.runPromise(finalAmount).then(console.log) - * // Output: 101 + * await Effect.runPromise(finalAmount) // => 101 * ``` * * @see {@link mapError} for a version that operates on the error channel. @@ -2373,14 +2383,13 @@ export const map: { * * **Example** (Replacing a success value) * - * ```ts + * ```ts import.meta.vitest * import { Effect, pipe } from "effect" * * // Replaces the value 5 with the constant "new value" * const program = pipe(Effect.succeed(5), Effect.as("new value")) * - * Effect.runPromise(program).then(console.log) - * // Output: "new value" + * Effect.runSync(program) // => "new value" * ``` * * @see {@link map} for deriving the replacement value from the success value @@ -2399,13 +2408,12 @@ export const as: { * * **Example** (Wrapping success in Option.some) * - * ```ts - * import { Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect, Option } from "effect" * * const program = Effect.asSome(Effect.succeed(42)) * - * Effect.runPromise(program).then(console.log) - * // { _id: 'Option', _tag: 'Some', value: 42 } + * Effect.runSync(program) // => Option.some(42) * ``` * * @category mapping @@ -2418,13 +2426,12 @@ export const asSome: (self: Effect) => Effect, E, R> * * **Example** (Discarding success values) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * * const program = Effect.asVoid(Effect.succeed(42)) * - * Effect.runPromise(program).then(console.log) - * // undefined (void) + * Effect.runSync(program) // => undefined * ``` * * @category mapping @@ -2445,7 +2452,7 @@ export const asVoid: (self: Effect) => Effect = in * * **Example** (Swapping success and failure channels) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * * // ┌─── Effect @@ -2455,6 +2462,7 @@ export const asVoid: (self: Effect) => Effect = in * // ┌─── Effect * // ▼ * const flipped = Effect.flip(program) + * Effect.runSync(flipped) // => "Oh uh!" * ``` * * @category mapping @@ -2485,17 +2493,11 @@ export const flip: (self: Effect) => Effect = interna * * **Example** (Combining two effects sequentially) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * - * const task1 = Effect.succeed(1).pipe( - * Effect.delay("200 millis"), - * Effect.tap(Effect.log("task1 done")) - * ) - * const task2 = Effect.succeed("hello").pipe( - * Effect.delay("100 millis"), - * Effect.tap(Effect.log("task2 done")) - * ) + * const task1 = Effect.succeed(1) + * const task2 = Effect.succeed("hello") * * // Combine the two effects together * // @@ -2503,35 +2505,21 @@ export const flip: (self: Effect) => Effect = interna * // ▼ * const program = Effect.zip(task1, task2) * - * Effect.runPromise(program).then(console.log) - * // Output: - * // timestamp=... level=INFO fiber=#0 message="task1 done" - * // timestamp=... level=INFO fiber=#0 message="task2 done" - * // [ 1, 'hello' ] + * Effect.runSync(program) // => [1, 'hello'] * ``` * * **Example** (Combining two effects concurrently) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * - * const task1 = Effect.succeed(1).pipe( - * Effect.delay("200 millis"), - * Effect.tap(Effect.log("task1 done")) - * ) - * const task2 = Effect.succeed("hello").pipe( - * Effect.delay("100 millis"), - * Effect.tap(Effect.log("task2 done")) - * ) + * const task1 = Effect.succeed(1) + * const task2 = Effect.succeed("hello") * * // Run both effects concurrently using the concurrent option * const program = Effect.zip(task1, task2, { concurrent: true }) * - * Effect.runPromise(program).then(console.log) - * // Output: - * // timestamp=... level=INFO fiber=#0 message="task2 done" - * // timestamp=... level=INFO fiber=#0 message="task1 done" - * // [ 1, 'hello' ] + * await Effect.runPromise(program) // => [1, 'hello'] * ``` * * @see {@link zipWith} for a version that combines the results with a custom function. @@ -2570,17 +2558,11 @@ export const zip: { * * **Example** (Combining two success values with a function) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * - * const task1 = Effect.succeed(1).pipe( - * Effect.delay("200 millis"), - * Effect.tap(Effect.log("task1 done")) - * ) - * const task2 = Effect.succeed("hello").pipe( - * Effect.delay("100 millis"), - * Effect.tap(Effect.log("task2 done")) - * ) + * const task1 = Effect.succeed(1) + * const task2 = Effect.succeed("hello") * * const task3 = Effect.zipWith( * task1, @@ -2589,11 +2571,7 @@ export const zip: { * (number, string) => number + string.length * ) * - * Effect.runPromise(task3).then(console.log) - * // Output: - * // timestamp=... level=INFO fiber=#3 message="task1 done" - * // timestamp=... level=INFO fiber=#2 message="task2 done" - * // 6 + * Effect.runSync(task3) // => 6 * ``` * * @category zipping @@ -2672,7 +2650,7 @@ export { * * **Example** (Handling a tagged error) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * * class NetworkError { @@ -2685,13 +2663,16 @@ export { * constructor(readonly message: string) {} * } * - * declare const task: Effect.Effect + * const task: Effect.Effect = + * Effect.fail(new NetworkError("offline")) * * const program = Effect.catchTag( * task, * "NetworkError", * (error) => Effect.succeed(`Recovered from network error: ${error.message}`) * ) + * + * Effect.runSync(program) // => "Recovered from network error: offline" * ``` * * @see {@link catchTags} for handling multiple tagged errors in one call @@ -2770,7 +2751,7 @@ export const catchTag: { * * **Example** (Handling multiple tagged errors) * - * ```ts + * ```ts import.meta.vitest * import { Data, Effect } from "effect" * * // Define tagged error types @@ -2783,7 +2764,8 @@ export const catchTag: { * }> {} * * // An effect that might fail with multiple error types - * declare const program: Effect.Effect + * const program: Effect.Effect = + * Effect.fail(new NetworkError({ statusCode: 503 })) * * // Handle multiple error types at once * const handled = Effect.catchTags(program, { @@ -2791,6 +2773,8 @@ export const catchTag: { * Effect.succeed(`Validation failed: ${error.message}`), * NetworkError: (error) => Effect.succeed(`Network error: ${error.statusCode}`) * }) + * + * Effect.runSync(handled) // => "Network error: 503" * ``` * * @category error handling @@ -2875,7 +2859,7 @@ export const catchTags: { * * **Example** (Handling an error reason) * - * ```ts + * ```ts import.meta.vitest * import { Data, Effect } from "effect" * * class RateLimitError extends Data.TaggedError("RateLimitError")<{ @@ -2890,7 +2874,9 @@ export const catchTags: { * reason: RateLimitError | QuotaExceededError * }> {} * - * declare const program: Effect.Effect + * const program: Effect.Effect = Effect.fail( + * new AiError({ reason: new RateLimitError({ retryAfter: 30 }) }) + * ) * * // Handle rate limits specifically * const handled = program.pipe( @@ -2898,6 +2884,8 @@ export const catchTags: { * Effect.succeed(`Retry after ${reason.retryAfter}s`) * ) * ) + * + * Effect.runSync(handled) // => "Retry after 30s" * ``` * * @see {@link catchReasons} for handling several nested reason tags @@ -2968,7 +2956,7 @@ export const catchReason: { * * **Example** (Handling multiple error reasons) * - * ```ts + * ```ts import.meta.vitest * import { Data, Effect } from "effect" * * class RateLimitError extends Data.TaggedError("RateLimitError")<{ @@ -2983,7 +2971,9 @@ export const catchReason: { * reason: RateLimitError | QuotaExceededError * }> {} * - * declare const program: Effect.Effect + * const program: Effect.Effect = Effect.fail( + * new AiError({ reason: new QuotaExceededError({ limit: 100 }) }) + * ) * * const handled = program.pipe( * Effect.catchReasons("AiError", { @@ -2993,6 +2983,8 @@ export const catchReason: { * Effect.succeed(`Quota exceeded: ${reason.limit}`) * }) * ) + * + * Effect.runSync(handled) // => "Quota exceeded: 100" * ``` * * @category error handling @@ -3114,7 +3106,7 @@ export type TagsWithReason = { * * **Example** (Extracting the reason from a tagged error) * - * ```ts + * ```ts import.meta.vitest * import { Data, Effect } from "effect" * * class RateLimitError extends Data.TaggedError("RateLimitError")<{ @@ -3129,11 +3121,14 @@ export type TagsWithReason = { * reason: RateLimitError | QuotaExceededError * }> {} * - * declare const program: Effect.Effect + * const program: Effect.Effect = Effect.fail( + * new AiError({ reason: new RateLimitError({ retryAfter: 30 }) }) + * ) * * // Before: Effect * // After: Effect * const unwrapped = program.pipe(Effect.unwrapReason("AiError")) + * Effect.runSync(Effect.flip(unwrapped))._tag // => "RateLimitError" * ``` * * @category error handling @@ -3177,8 +3172,9 @@ export const unwrapReason: { * * **Example** (Recovering from full failure causes) * - * ```ts - * import { Cause, Console, Effect } from "effect" + * ```ts import.meta.vitest + * import { Cause, Effect } from "effect" + * const output: Array = [] * * // An effect that might fail in different ways * const program = Effect.die("Something went wrong") @@ -3186,12 +3182,15 @@ export const unwrapReason: { * // Recover from any cause (including defects) * const recovered = Effect.catchCause(program, (cause) => { * if (Cause.hasDies(cause)) { - * return Console.log("Caught defect").pipe( + * return Effect.sync(() => { output.push("Caught defect") }).pipe( * Effect.as("Recovered from defect") * ) * } * return Effect.succeed("Unknown error") * }) + * + * void output.push(Effect.runSync(recovered)) + * output // => ["Caught defect", "Recovered from defect"] * ``` * * @category error handling @@ -3227,8 +3226,9 @@ export const catchCause: { * * **Example** (Recovering from defects) * - * ```ts - * import { Console, Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect } from "effect" + * const output: Array = [] * * // An effect that might throw an unexpected error (defect) * const program = Effect.sync(() => { @@ -3237,10 +3237,13 @@ export const catchCause: { * * // Recover from defects only * const recovered = Effect.catchDefect(program, (defect) => { - * return Console.log(`Caught defect: ${defect}`).pipe( + * return Effect.sync(() => { output.push(`Caught defect: ${(defect as Error).message}`) }).pipe( * Effect.as("Recovered from defect") * ) * }) + * + * void output.push(Effect.runSync(recovered)) + * output // => ["Caught defect: Unexpected error", "Recovered from defect"] * ``` * * @category error handling @@ -3271,7 +3274,7 @@ export const catchDefect: { * * **Example** (Recovering when a predicate matches) * - * ```ts + * ```ts import.meta.vitest * import { Data, Effect, Filter } from "effect" * * class NotFound extends Data.TaggedError("NotFound")<{ id: string }> {} @@ -3293,6 +3296,8 @@ export const catchDefect: { * (error) => Effect.succeed(`missing:${error.id}`) * ) * ) + * + * Effect.runSync(Effect.all([recovered, recovered2])) // => ['missing:user-1', 'missing:user-1'] * ``` * * @category error handling @@ -3382,14 +3387,16 @@ export const catchFilter: { * * **Example** (Recovering from missing Option values) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Option } from "effect" + * const output: Array = [] * * const some = Effect.fromNullishOr(1).pipe(Effect.catchNoSuchElement) * const none = Effect.fromNullishOr(null).pipe(Effect.catchNoSuchElement) * - * Effect.runPromise(some).then(console.log) // { _id: 'Option', _tag: 'Some', value: 1 } - * Effect.runPromise(none).then(console.log) // { _id: 'Option', _tag: 'None' } + * void output.push(Effect.runSync(some)) + * void output.push(Effect.runSync(none)) + * output // => [Option.some(1), Option.none()] * ``` * * @see {@link fromOption} for converting `Option.none` into `NoSuchElementError` @@ -3418,8 +3425,9 @@ export const catchNoSuchElement: ( * * **Example** (Recovering from selected causes) * - * ```ts - * import { Cause, Console, Effect } from "effect" + * ```ts import.meta.vitest + * import { Cause, Effect } from "effect" + * const output: Array = [] * * const httpRequest = Effect.fail("Network Error") * @@ -3429,14 +3437,13 @@ export const catchNoSuchElement: ( * Cause.hasFails, * (cause) => * Effect.gen(function*() { - * yield* Console.log(`Caught network error: ${Cause.squash(cause)}`) + * yield* Effect.sync(() => { output.push(`Caught network error: ${Cause.squash(cause)}`) }) * return "Fallback response" * }) * ) * - * Effect.runPromise(program).then(console.log) - * // Output: "Caught network error: Network Error" - * // Then: "Fallback response" + * void output.push(Effect.runSync(program)) + * output // => ["Caught network error: Network Error", "Fallback response"] * ``` * * @see {@link catchCause} for recovering from every cause @@ -3507,7 +3514,7 @@ export const catchCauseFilter: { * * **Example** (Transforming the error channel) * - * ```ts + * ```ts import.meta.vitest * import { Data, Effect } from "effect" * * class TaskError extends Data.TaggedError("TaskError")<{ readonly message: string }> {} @@ -3522,6 +3529,7 @@ export const catchCauseFilter: { * simulatedTask, * (message) => new TaskError({ message }) * ) + * Effect.runSync(Effect.flip(mapped)).message // => "Oh no!" * ``` * * @see {@link map} for a version that operates on the success channel. @@ -3552,7 +3560,7 @@ export const mapError: { * * **Example** (Transforming success and failure channels) * - * ```ts + * ```ts import.meta.vitest * import { Data, Effect } from "effect" * * class TaskError extends Data.TaggedError("TaskError")<{ readonly message: string }> {} @@ -3567,6 +3575,7 @@ export const mapError: { * onFailure: (message) => new TaskError({ message }), * onSuccess: (n) => n > 0 * }) + * Effect.runSync(Effect.flip(modified)).message // => "Oh no!" * ``` * * @see {@link map} for a version that operates on the success channel. @@ -3596,8 +3605,8 @@ export const mapBoth: { * * **Example** (Converting typed failures into defects) * - * ```ts - * import { Data, Effect } from "effect" + * ```ts import.meta.vitest + * import { Data, Effect, Exit } from "effect" * * class DivideByZeroError extends Data.TaggedError("DivideByZeroError")<{}> {} * @@ -3610,13 +3619,10 @@ export const mapBoth: { * // ▼ * const program = Effect.orDie(divide(1, 0)) * - * Effect.runPromise(program).catch(console.error) - * // Output: - * // (FiberFailure) DivideByZeroError - * // ...stack trace... + * Effect.runSyncExit(program) // => Exit.die(new DivideByZeroError()) * ``` * - * @category converting failures to defects + * @category error handling * @since 2.0.0 */ export const orDie: (self: Effect) => Effect = internal.orDie @@ -3633,8 +3639,9 @@ export const orDie: (self: Effect) => Effect = in * * **Example** (Running effects on failure) * - * ```ts - * import { Console, Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect, Exit } from "effect" + * const output: Array = [] * * // Simulate a task that fails with an error * const task: Effect.Effect = Effect.fail("NetworkError") @@ -3642,12 +3649,11 @@ export const orDie: (self: Effect) => Effect = in * // Use tapError to log the error message when the task fails * const tapping = Effect.tapError( * task, - * (error) => Console.log(`expected error: ${error}`) + * (error) => Effect.sync(() => { output.push(`expected error: ${error}`) }) * ) * - * Effect.runFork(tapping) - * // Output: - * // expected error: NetworkError + * void output.push(Effect.runSyncExit(tapping)) + * output // => ["expected error: NetworkError", Exit.fail("NetworkError")] * ``` * * @category sequencing @@ -3674,8 +3680,9 @@ export const tapError: { * * **Example** (Running effects for tagged failures) * - * ```ts - * import { Console, Data, Effect } from "effect" + * ```ts import.meta.vitest + * import { Data, Effect, Exit } from "effect" + * const output: Array = [] * * class NetworkError extends Data.TaggedError("NetworkError")<{ * statusCode: number @@ -3689,12 +3696,11 @@ export const tapError: { * Effect.fail(new NetworkError({ statusCode: 504 })) * * const program = Effect.tapErrorTag(task, "NetworkError", (error) => - * Console.log(`expected error: ${error.statusCode}`) + * Effect.sync(() => { output.push(`expected error: ${error.statusCode}`) }) * ) * - * Effect.runPromiseExit(program) - * // Output: - * // expected error: 504 + * void output.push(Effect.runSyncExit(program)) + * output // => ["expected error: 504", Exit.fail(new NetworkError({ statusCode: 504 }))] * ``` * * @category sequencing @@ -3737,19 +3743,19 @@ export const tapErrorTag: { * * **Example** (Observing full failure causes) * - * ```ts - * import { Cause, Console, Effect } from "effect" + * ```ts import.meta.vitest + * import { Cause, Effect, Exit } from "effect" + * const output: Array = [] * * const task = Effect.fail("Something went wrong") * * const program = Effect.tapCause( * task, - * (cause) => Console.log(`Logging cause: ${Cause.squash(cause)}`) + * (cause) => Effect.sync(() => { output.push(`Logging cause: ${Cause.squash(cause)}`) }) * ) * - * Effect.runPromiseExit(program).then(console.log) - * // Output: "Logging cause: Error: Something went wrong" - * // Then: { _id: 'Exit', _tag: 'Failure', cause: ... } + * void output.push(Effect.runSyncExit(program)) + * output // => ["Logging cause: Something went wrong", Exit.fail("Something went wrong")] * ``` * * @category sequencing @@ -3776,8 +3782,9 @@ export const tapCause: { * * **Example** (Observing selected failure causes) * - * ```ts - * import { Cause, Console, Effect } from "effect" + * ```ts import.meta.vitest + * import { Cause, Effect, Exit } from "effect" + * const output: Array = [] * * const task = Effect.fail("Network timeout") * @@ -3785,12 +3792,11 @@ export const tapCause: { * const program = Effect.tapCauseIf( * task, * Cause.hasFails, - * (cause) => Console.log(`Logging failure cause: ${Cause.squash(cause)}`) + * (cause) => Effect.sync(() => { output.push(`Logging failure cause: ${Cause.squash(cause)}`) }) * ) * - * Effect.runPromiseExit(program).then(console.log) - * // Output: "Logging failure cause: Network timeout" - * // Then: { _id: 'Exit', _tag: 'Failure', cause: ... } + * void output.push(Effect.runSyncExit(program)) + * output // => ["Logging failure cause: Network timeout", Exit.fail("Network timeout")] * ``` * * @category sequencing @@ -3854,20 +3860,9 @@ export const tapCauseFilter: { * * **Example** (Observing defects) * - * ```ts - * import { Console, Effect } from "effect" - * - * // Simulate a task that fails with a recoverable error - * const task1: Effect.Effect = Effect.fail("NetworkError") - * - * // tapDefect won't log anything because NetworkError is not a defect - * const tapping1 = Effect.tapDefect( - * task1, - * (cause) => Console.log(`defect: ${cause}`) - * ) - * - * Effect.runFork(tapping1) - * // No Output + * ```ts import.meta.vitest + * import { Effect, Exit } from "effect" + * const output: Array = [] * * // Simulate a severe failure in the system * const task2: Effect.Effect = Effect.die( @@ -3877,13 +3872,11 @@ export const tapCauseFilter: { * // Log the defect using tapDefect * const tapping2 = Effect.tapDefect( * task2, - * (cause) => Console.log(`defect: ${cause}`) + * (defect) => Effect.sync(() => { output.push(`defect: ${defect}`) }) * ) * - * Effect.runFork(tapping2) - * // Output: - * // defect: RuntimeException: Something went wrong - * // ... stack trace ... + * void output.push(Effect.runSyncExit(tapping2)) + * output // => ["defect: Something went wrong", Exit.die("Something went wrong")] * ``` * * @category sequencing @@ -3903,14 +3896,15 @@ export const tapDefect: { * * **Example** (Retrying until success) * - * ```ts - * import { Console, Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect } from "effect" + * const output: Array = [] * * let attempts = 0 * * const flaky = Effect.gen(function*() { * attempts++ - * yield* Console.log(`Attempt ${attempts}`) + * yield* Effect.sync(() => { output.push(`Attempt ${attempts}`) }) * if (attempts < 3) { * return yield* Effect.fail("Not ready") * } @@ -3919,12 +3913,8 @@ export const tapDefect: { * * const program = Effect.eventually(flaky) * - * Effect.runPromise(program).then(console.log) - * // Output: - * // Attempt 1 - * // Attempt 2 - * // Attempt 3 - * // Ready + * void output.push(await Effect.runPromise(program)) + * output // => ["Attempt 1", "Attempt 2", "Attempt 3", "Ready"] * ``` * * @category repetition @@ -4010,7 +4000,7 @@ export declare namespace Retry { * * **Example** (Retrying with a schedule) * - * ```ts + * ```ts import.meta.vitest * import { Data, Effect, Schedule } from "effect" * * class AttemptError extends Data.TaggedError("AttemptError")<{ readonly attempt: number }> {} @@ -4025,11 +4015,10 @@ export declare namespace Retry { * } * }) * - * const policy = Schedule.addDelay(Schedule.recurs(5), () => Effect.succeed("100 millis")) + * const policy = Schedule.recurs(5) * const program = Effect.retry(task, policy) * - * Effect.runPromise(program).then(console.log) - * // Output: "Success!" (after 2 retries) + * await Effect.runPromise(program) // => "Success!" * ``` * * @see {@link retryOrElse} for a version that allows you to run a fallback. @@ -4078,15 +4067,16 @@ export const retry: { * * **Example** (Falling back after retries are exhausted) * - * ```ts - * import { Console, Data, Effect, Schedule } from "effect" + * ```ts import.meta.vitest + * import { Data, Effect, Schedule } from "effect" + * const output: Array = [] * * class NetworkTimeoutError extends Data.TaggedError("NetworkTimeoutError")<{}> {} * * let attempt = 0 * const networkRequest = Effect.gen(function*() { * attempt++ - * yield* Console.log(`Network attempt ${attempt}`) + * yield* Effect.sync(() => { output.push(`Network attempt ${attempt}`) }) * if (attempt < 3) { * return yield* Effect.fail(new NetworkTimeoutError()) * } @@ -4099,17 +4089,13 @@ export const retry: { * Schedule.recurs(2), * (error, retryCount) => * Effect.gen(function*() { - * yield* Console.log(`All ${retryCount} retries failed, using cache`) + * yield* Effect.sync(() => { output.push(`All ${retryCount} retries failed, using cache`) }) * return "Cached data" * }) * ) * - * Effect.runPromise(program).then(console.log) - * // Output: - * // Network attempt 1 - * // Network attempt 2 - * // Network attempt 3 - * // Network data + * void output.push(await Effect.runPromise(program)) + * output // => ["Network attempt 1", "Network attempt 2", "Network attempt 3", "Network data"] * ``` * * @see {@link retry} for a version that does not run a fallback effect. @@ -4139,7 +4125,7 @@ export const retryOrElse: { * * **Example** (Exposing failures as causes) * - * ```ts + * ```ts import.meta.vitest * import { Cause, Effect } from "effect" * * const task = Effect.fail("Something went wrong") @@ -4150,8 +4136,7 @@ export const retryOrElse: { * return `Caught cause: ${Cause.squash(result)}` * }) * - * Effect.runPromise(program).then(console.log) - * // Output: "Caught cause: Something went wrong" + * Effect.runSync(program) // => "Caught cause: Something went wrong" * ``` * * @category error handling @@ -4176,7 +4161,7 @@ export const sandbox: ( * * **Example** (Discarding success and failure values) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * * // ┌─── Effect @@ -4186,17 +4171,18 @@ export const sandbox: ( * // ┌─── Effect * // ▼ * const program = task.pipe(Effect.ignore) + * Effect.runSync(program) // => undefined * ``` * * **Example** (Logging failures while ignoring results) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * * const task = Effect.fail("Uh oh!") * - * const program = task.pipe(Effect.ignore({ log: true })) - * const programWarn = task.pipe(Effect.ignore({ log: "Warn", message: "Ignoring task failure" })) + * const program = task.pipe(Effect.ignore) + * Effect.runSync(program) // => undefined * ``` * * @category error handling @@ -4234,13 +4220,13 @@ export const ignore: < * * **Example** (Ignoring failures and logging causes) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * * const task = Effect.fail("boom") * * const program = task.pipe(Effect.ignoreCause) - * const programLog = task.pipe(Effect.ignoreCause({ log: true, message: "Ignoring failure cause" })) + * Effect.runSync(program) // => undefined * ``` * * @category error handling @@ -4273,9 +4259,17 @@ export const ignoreCause: < * and retry timing is derived per step (the first attempt uses the remaining * attempts schedule; later retries apply the step schedule at least once). * + * Attempts can be observed from outside the effect by passing + * `options.onEvent`, which receives an `ExecutionPlan.Event` before each + * attempt and after it settles. The handler is awaited inline before and after + * every attempt, so events are strictly ordered; keep it cheap. It cannot + * fail, which keeps observation from changing the plan's outcome, and its + * requirements are added to the resulting effect. Terminal events run like + * finalizers, so they are emitted even when the attempt is interrupted. + * * **Example** (Retrying with an execution plan) * - * ```ts + * ```ts import.meta.vitest * import { Context, Effect, ExecutionPlan, Layer } from "effect" * * const Endpoint = Context.Service<{ url: string }>("Endpoint") @@ -4294,21 +4288,58 @@ export const ignoreCause: < * ) * * const program = Effect.withExecutionPlan(fetchUrl, plan) + * Effect.runSync(program) // => "good" * ``` * - * @category fallback + * **Example** (Observing execution-plan attempts) + * + * ```ts import.meta.vitest + * import { Context, Effect, ExecutionPlan, Layer } from "effect" + * + * const Endpoint = Context.Service<{ url: string }>("Endpoint") + * + * const fetchUrl = Effect.gen(function*() { + * const endpoint = yield* Effect.service(Endpoint) + * if (endpoint.url === "bad") { + * return yield* Effect.fail("Unavailable") + * } + * return endpoint.url + * }) + * + * const plan = ExecutionPlan.make( + * { provide: Layer.succeed(Endpoint, { url: "bad" }) }, + * { provide: Layer.succeed(Endpoint, { url: "good" }) } + * ) + * + * const events: Array = [] + * const program = Effect.withExecutionPlan(fetchUrl, plan, { + * onEvent: (event) => Effect.sync(() => events.push(`${event._tag}:${event.stepIndex}`)) + * }) + * + * await Effect.runPromise(program) // => "good" + * + * events // => ["AttemptStart:0", "AttemptFailure:0", "AttemptStart:1", "AttemptSuccess:1"] + * ``` + * + * @category error handling * @since 3.16.0 */ export const withExecutionPlan: { - ( - plan: ExecutionPlan<{ provides: Provides; input: Input; error: PlanE; requirements: PlanR }> + ( + plan: ExecutionPlan.ExecutionPlan<{ provides: Provides; input: Input; error: PlanE; requirements: PlanR }>, + options?: { + readonly onEvent?: ((event: ExecutionPlan.Event) => Effect) | undefined + } ): ( effect: Effect - ) => Effect | PlanR> - ( + ) => Effect | PlanR | RX> + ( effect: Effect, - plan: ExecutionPlan<{ provides: Provides; input: Input; error: PlanE; requirements: PlanR }> - ): Effect | PlanR> + plan: ExecutionPlan.ExecutionPlan<{ provides: Provides; input: Input; error: PlanE; requirements: PlanR }>, + options?: { + readonly onEvent?: ((event: ExecutionPlan.Event) => Effect) | undefined + } + ): Effect | PlanR | RX> } = internalExecutionPlan.withExecutionPlan /** @@ -4349,8 +4380,8 @@ export const withErrorReporting: < * * **Example** (Replacing failures with a value) * - * ```ts - * import { Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect, Exit } from "effect" * * const validate = (age: number): Effect.Effect => { * if (age < 0) { @@ -4364,12 +4395,10 @@ export const withErrorReporting: < * * const program = Effect.orElseSucceed(validate(-1), () => 18) * - * console.log(Effect.runSyncExit(program)) - * // Output: - * // { _id: 'Exit', _tag: 'Success', value: 18 } + * Effect.runSyncExit(program) // => Exit.succeed(18) * ``` * - * @category fallback + * @category error handling * @since 2.0.0 */ export const orElseSucceed: { @@ -4404,7 +4433,7 @@ export const orElseSucceed: { * * **Example** (Trying alternatives until one succeeds) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * * const primary = Effect.fail("primary unavailable") @@ -4419,11 +4448,10 @@ export const orElseSucceed: { * tertiary * ]) * - * console.log(Effect.runSync(program)) - * // Output: "secondary result" + * Effect.runSync(program) // => "secondary result" * ``` * - * @category fallback + * @category error handling * @since 2.0.0 */ export const firstSuccessOf: >( @@ -4447,8 +4475,8 @@ export const firstSuccessOf: >( * * The `timeout` function allows you to specify a time limit for an * effect's execution. If the effect does not complete within the given time, a - * `TimeoutException` is raised. This can be useful for controlling how long - * your program waits for a task to finish, ensuring that it doesn't hang + * `TimeoutError` is raised. This can be useful for controlling how long your + * program waits for a task to finish, ensuring that it doesn't hang * indefinitely if the task takes too long. * * **Gotchas** @@ -4457,32 +4485,12 @@ export const firstSuccessOf: >( * * **Example** (Failing when work takes too long) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * - * const task = Effect.gen(function*() { - * console.log("Start processing...") - * yield* Effect.sleep("2 seconds") // Simulates a delay in processing - * console.log("Processing complete.") - * return "Result" - * }) - * - * // Output will show a TimeoutException as the task takes longer - * // than the specified timeout duration - * const timedEffect = task.pipe(Effect.timeout("1 second")) - * - * Effect.runPromiseExit(timedEffect).then(console.log) - * // Output: - * // Start processing... - * // { - * // _id: 'Exit', - * // _tag: 'Failure', - * // cause: { - * // _id: 'Cause', - * // _tag: 'Fail', - * // failure: { _tag: 'TimeoutException' } - * // } - * // } + * const timedEffect = Effect.never.pipe(Effect.timeout(0)) + * const error = await Effect.runPromise(Effect.flip(timedEffect)) + * error._tag // => "TimeoutError" * ``` * * @see {@link timeoutOption} for returning `Option.none` on timeout. @@ -4518,33 +4526,14 @@ export const timeout: { * * **Example** (Returning None on timeout) * - * ```ts - * import { Effect } from "effect" - * - * const task = Effect.gen(function*() { - * console.log("Start processing...") - * yield* Effect.sleep("2 seconds") // Simulates a delay in processing - * console.log("Processing complete.") - * return "Result" - * }) - * - * const timedOutEffect = Effect.all([ - * task.pipe(Effect.timeoutOption("3 seconds")), - * task.pipe(Effect.timeoutOption("1 second")) - * ]) + * ```ts import.meta.vitest + * import { Effect, Option } from "effect" * - * Effect.runPromise(timedOutEffect).then(console.log) - * // Output: - * // Start processing... - * // Processing complete. - * // Start processing... - * // [ - * // { _id: 'Option', _tag: 'Some', value: 'Result' }, - * // { _id: 'Option', _tag: 'None' } - * // ] + * const timedOutEffect = Effect.never.pipe(Effect.timeoutOption(0)) + * await Effect.runPromise(timedOutEffect) // => Option.none() * ``` * - * @see {@link timeout} for a version that raises a `TimeoutException`. + * @see {@link timeout} for a version that raises a `TimeoutError`. * @see {@link timeoutOrElse} for a version that allows specifying both success and timeout handlers. * * @category delays & timeouts @@ -4579,33 +4568,22 @@ export const timeoutOption: { * * **Example** (Falling back on timeout) * - * ```ts - * import { Console, Effect } from "effect" - * - * const slowQuery = Effect.gen(function*() { - * yield* Console.log("Starting database query...") - * yield* Effect.sleep("5 seconds") - * return "Database result" - * }) + * ```ts import.meta.vitest + * import { Effect } from "effect" + * const output: Array = [] * - * // Use cached data as fallback when timeout is reached - * const program = Effect.timeoutOrElse(slowQuery, { - * duration: "2 seconds", - * orElse: () => - * Effect.gen(function*() { - * yield* Console.log("Query timed out, using cached data") - * return "Cached result" - * }) + * const program = Effect.timeoutOrElse(Effect.never, { + * duration: 0, + * orElse: () => Effect.sync(() => { output.push("Query timed out, using cached data") }).pipe( + * Effect.as("Cached result") + * ) * }) * - * Effect.runPromise(program).then(console.log) - * // Output: - * // Starting database query... - * // Query timed out, using cached data - * // Cached result + * void output.push(await Effect.runPromise(program)) + * output // => ["Query timed out, using cached data", "Cached result"] * ``` * - * @see {@link timeout} for failing with a `TimeoutException`. + * @see {@link timeout} for failing with a `TimeoutError`. * @see {@link timeoutOption} for returning `Option.none` on timeout. * * @category delays & timeouts @@ -4631,16 +4609,14 @@ export const timeoutOrElse: { * * **Example** (Delaying an effect) * - * ```ts - * import { Console, Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect } from "effect" + * const output: Array = [] * - * const program = Effect.delay( - * Console.log("Delayed message"), - * "1 second" - * ) + * const program = Effect.delay(Effect.sync(() => { output.push("Delayed message") }), 0) * - * Effect.runFork(program) - * // Waits 1 second, then prints: "Delayed message" + * await Effect.runPromise(program) + * output // => ["Delayed message"] * ``` * * @category delays & timeouts @@ -4662,18 +4638,18 @@ export const delay: { * * **Example** (Pausing without blocking) * - * ```ts - * import { Console, Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect } from "effect" + * const output: Array = [] * * const program = Effect.gen(function*() { - * yield* Console.log("Start") - * yield* Effect.sleep("2 seconds") - * yield* Console.log("End") + * yield* Effect.sync(() => { output.push("Start") }) + * yield* Effect.sleep(0) + * yield* Effect.sync(() => { output.push("End") }) * }) * - * Effect.runFork(program) - * // Output: "Start" (immediately) - * // Output: "End" (after 2 seconds) + * await Effect.runPromise(program) + * output // => ["Start", "End"] * ``` * * @category delays & timeouts @@ -4691,13 +4667,15 @@ export const sleep: (duration: Duration.Input) => Effect = internal.sleep * * **Example** (Measuring execution time) * - * ```ts - * import { Console, Duration, Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect } from "effect" * * const program = Effect.gen(function*() { - * const [duration, value] = yield* Effect.timed(Effect.succeed("ok")) - * yield* Console.log(`took ${Duration.toMillis(duration)}ms: ${value}`) + * const [, value] = yield* Effect.timed(Effect.succeed("ok")) + * return value * }) + * + * Effect.runSync(program) // => "ok" * ``` * * @category delays & timeouts @@ -4727,18 +4705,14 @@ export const timed: (self: Effect) => Effect<[duration: Durati * * **Example** (Racing many effects) * - * ```ts - * import { Duration, Effect } from "effect" - * - * // Multiple effects with different delays - * const effect1 = Effect.delay(Effect.succeed("Fast"), Duration.millis(100)) - * const effect2 = Effect.delay(Effect.succeed("Slow"), Duration.millis(500)) - * const effect3 = Effect.delay(Effect.succeed("Very Slow"), Duration.millis(1000)) - * - * // Race all effects - the first to succeed wins - * const raced = Effect.raceAll([effect1, effect2, effect3]) + * ```ts import.meta.vitest + * import { Effect } from "effect" * - * // Result: "Fast" (after ~100ms) + * const raced = Effect.raceAll([ + * Effect.succeed("Fast"), + * Effect.never + * ]) + * await Effect.runPromise(raced) // => "Fast" * ``` * * @see {@link race} for a version that handles only two effects. @@ -4768,18 +4742,14 @@ export const raceAll: >( * * **Example** (Taking the first settled result) * - * ```ts - * import { Duration, Effect } from "effect" - * - * // Multiple effects with different delays and potential failures - * const effect1 = Effect.delay(Effect.succeed("First"), Duration.millis(200)) - * const effect2 = Effect.delay(Effect.fail("Second failed"), Duration.millis(100)) - * const effect3 = Effect.delay(Effect.succeed("Third"), Duration.millis(300)) - * - * // Race all effects - the first to succeed wins - * const raced = Effect.raceAllFirst([effect1, effect2, effect3]) + * ```ts import.meta.vitest + * import { Effect } from "effect" * - * // Result: "First" (after ~200ms, even though effect2 completes first but fails) + * const raced = Effect.raceAllFirst([ + * Effect.fail("First failed"), + * Effect.never + * ]) + * await Effect.runPromise(Effect.flip(raced)) // => "First failed" * ``` * * @category racing @@ -4806,19 +4776,20 @@ export const raceAllFirst: >( * * **Example** (Racing two effects) * - * ```ts - * import { Console, Duration, Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect } from "effect" + * const output: Array = [] * - * const fastFail = Effect.delay(Effect.fail("fast-fail"), Duration.millis(10)) - * const slowSuccess = Effect.delay(Effect.succeed("slow-success"), Duration.millis(50)) + * const fastFail = Effect.fail("fast-fail") + * const slowSuccess = Effect.succeed("slow-success") * * const program = Effect.gen(function*() { * const result = yield* Effect.race(fastFail, slowSuccess) - * yield* Console.log(`winner: ${result}`) + * yield* Effect.sync(() => { output.push(`winner: ${result}`) }) * }) * - * Effect.runPromise(program) - * // Output: winner: slow-success + * await Effect.runPromise(program) + * output // => ["winner: slow-success"] * ``` * * @category racing @@ -4859,22 +4830,23 @@ export const race: { * * **Example** (Observing the winning fiber) * - * ```ts - * import { Console, Duration, Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect } from "effect" + * const output: Array = [] * - * const fastFail = Effect.delay(Effect.fail("fast-fail"), Duration.millis(10)) - * const slowSuccess = Effect.delay(Effect.succeed("slow-success"), Duration.millis(50)) + * const fastFail = Effect.fail("fast-fail") + * const slowSuccess = Effect.never * * const program = Effect.gen(function*() { * const message = yield* Effect.match(Effect.raceFirst(fastFail, slowSuccess), { * onFailure: (error) => `failed: ${error}`, * onSuccess: (value) => `succeeded: ${value}` * }) - * yield* Console.log(message) + * yield* Effect.sync(() => { output.push(message) }) * }) * - * Effect.runPromise(program) - * // Output: failed: fast-fail + * await Effect.runPromise(program) + * output // => ["failed: fast-fail"] * ``` * * @category racing @@ -4910,8 +4882,9 @@ export const raceFirst: { * * **Example** (Filtering success values) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" + * const output: Array = [] * * // Sync predicate * const evens = Effect.filter([1, 2, 3, 4], (n) => n % 2 === 0) @@ -4919,7 +4892,9 @@ export const raceFirst: { * // Effectful predicate * const checked = Effect.filter([1, 2, 3], (n) => Effect.succeed(n > 1)) * - * // Use Effect.filterMapEffect for effectful Filter.Filter callbacks + * void output.push(Effect.runSync(evens)) + * void output.push(Effect.runSync(checked)) + * output // => [[2, 4], [2, 3]] * ``` * * @category filtering @@ -5033,7 +5008,7 @@ export const filterMapEffect: { * * **Example** (Filtering with a fallback effect) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * * // An effect that produces a number @@ -5046,7 +5021,7 @@ export const filterMapEffect: { * (n) => Effect.succeed(`Number ${n} is odd`) * ) * - * // Result: "Number 5 is odd" (since 5 is not even) + * Effect.runSync(filtered) // => "Number 5 is odd" * ``` * * @category filtering @@ -5116,7 +5091,7 @@ export const filterMapOrElse: { * * **Example** (Filtering with a custom failure) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * * // An effect that produces a number @@ -5129,7 +5104,7 @@ export const filterMapOrElse: { * (n) => `Expected even number, got ${n}` * ) * - * // Result: Effect.fail("Expected even number, got 5") + * Effect.runSync(Effect.flip(filtered)) // => "Expected even number, got 5" * ``` * * @category filtering @@ -5231,22 +5206,22 @@ export const filterMapOrFail: { * * **Example** (Conditionally running an effect) * - * ```ts - * import { Console, Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect, Option } from "effect" + * const output: Array = [] * * const shouldLog = true * * const program = Effect.when( - * Console.log("Condition is true!"), + * Effect.sync(() => { output.push("Condition is true!") }), * Effect.succeed(shouldLog) * ) * - * Effect.runPromise(program).then(console.log) - * // Output: "Condition is true!" - * // { _id: 'Option', _tag: 'Some', value: undefined } + * void output.push(Effect.runSync(program)) + * output // => ["Condition is true!", Option.some(undefined)] * ``` * - * @category conditional operators + * @category filtering * @since 2.0.0 */ export const when: { @@ -5281,7 +5256,7 @@ export const when: { * * **Example** (Matching success and failure values) * - * ```ts + * ```ts import.meta.vitest * import { Data, Effect } from "effect" * * class ExampleError extends Data.TaggedError("ExampleError")<{ readonly message: string }> {} @@ -5294,8 +5269,7 @@ export const when: { * }) * * // Run and log the result of the successful effect - * Effect.runPromise(program1).then(console.log) - * // Output: "success: 42" + * Effect.runSync(program1) // => "success: 42" * * const failure: Effect.Effect = Effect.fail( * new ExampleError({ message: "Uh oh!" }) @@ -5307,8 +5281,7 @@ export const when: { * }) * * // Run and log the result of the failed effect - * Effect.runPromise(program2).then(console.log) - * // Output: "failure: Uh oh!" + * Effect.runSync(program2) // => "failure: Uh oh!" * ``` * * @see {@link matchEffect} if you need to perform side effects in the handlers. @@ -5347,16 +5320,20 @@ export const match: { * * **Example** (Pattern matching eagerly when possible) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" + * const output: Array = [] * * const program = Effect.gen(function*() { * const result = yield* Effect.matchEager(Effect.succeed(42), { * onFailure: (error) => `Failed: ${error}`, * onSuccess: (value) => `Success: ${value}` * }) - * console.log(result) // "Success: 42" + * void output.push(result) * }) + * + * Effect.runSync(program) + * output // => ["Success: 42"] * ``` * * @see {@link match} for the non-eager version. @@ -5393,7 +5370,7 @@ export const matchEager: { * * **Example** (Matching on success or failure causes) * - * ```ts + * ```ts import.meta.vitest * import { Cause, Effect } from "effect" * * const task = Effect.fail("Something went wrong") @@ -5403,8 +5380,7 @@ export const matchEager: { * onSuccess: (value) => `Success: ${value}` * }) * - * Effect.runPromise(program).then(console.log) - * // Output: "Failed: Error: Something went wrong" + * Effect.runSync(program) // => "Failed: Something went wrong" * ``` * * @see {@link matchCauseEffect} if you need to perform side effects in the @@ -5443,13 +5419,14 @@ export const matchCause: { * * **Example** (Eagerly matching already completed effects) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * * const handleResult = Effect.matchCauseEager(Effect.succeed(42), { * onSuccess: (value) => `Success: ${value}`, * onFailure: (cause) => `Failed: ${cause}` * }) + * Effect.runSync(handleResult) // => "Success: 42" * ``` * * @category pattern matching @@ -5524,8 +5501,9 @@ export const matchCauseEffectEager: { * * **Example** (Effectfully matching on causes) * - * ```ts - * import { Cause, Console, Data, Effect, Result } from "effect" + * ```ts import.meta.vitest + * import { Cause, Data, Effect, Result } from "effect" + * const output: Array = [] * * class TaskError extends Data.TaggedError("TaskError")<{ readonly message: string }> {} * @@ -5537,25 +5515,23 @@ export const matchCauseEffectEager: { * if (Cause.hasFails(cause)) { * const error = Cause.findError(cause) * if (Result.isSuccess(error)) { - * yield* Console.log(`Handling error: ${error.success.message}`) + * yield* Effect.sync(() => { output.push(`Handling error: ${error.success.message}`) }) * } * return "recovered from error" * } else { - * yield* Console.log("Handling interruption or defect") + * yield* Effect.sync(() => { output.push("Handling interruption or defect") }) * return "recovered from interruption/defect" * } * }), * onSuccess: (value) => * Effect.gen(function*() { - * yield* Console.log(`Success: ${value}`) + * yield* Effect.sync(() => { output.push(`Success: ${value}`) }) * return `processed ${value}` * }) * }) * - * Effect.runPromise(program).then(console.log) - * // Output: - * // Handling error: Task failed - * // recovered from error + * void output.push(Effect.runSync(program)) + * output // => ["Handling error: Task failed", "recovered from error"] * ``` * * @see {@link matchCause} if you don't need side effects and only want to handle the result or failure. @@ -5594,7 +5570,7 @@ export const matchCauseEffect: { * * **Example** (Matching success and failure with effectful handlers) * - * ```ts + * ```ts import.meta.vitest * import { Data, Effect } from "effect" * * class ExampleError extends Data.TaggedError("ExampleError")<{ readonly message: string }> {} @@ -5606,31 +5582,21 @@ export const matchCauseEffect: { * * const program1 = Effect.matchEffect(success, { * onFailure: (error) => - * Effect.succeed(`failure: ${error.message}`).pipe( - * Effect.tap(Effect.log) - * ), + * Effect.succeed(`failure: ${error.message}`), * onSuccess: (value) => - * Effect.succeed(`success: ${value}`).pipe(Effect.tap(Effect.log)) + * Effect.succeed(`success: ${value}`) * }) * - * console.log(Effect.runSync(program1)) - * // Output: - * // timestamp=... level=INFO fiber=#0 message="success: 42" - * // success: 42 + * Effect.runSync(program1) // => "success: 42" * * const program2 = Effect.matchEffect(failure, { * onFailure: (error) => - * Effect.succeed(`failure: ${error.message}`).pipe( - * Effect.tap(Effect.log) - * ), + * Effect.succeed(`failure: ${error.message}`), * onSuccess: (value) => - * Effect.succeed(`success: ${value}`).pipe(Effect.tap(Effect.log)) + * Effect.succeed(`success: ${value}`) * }) * - * console.log(Effect.runSync(program2)) - * // Output: - * // timestamp=... level=INFO fiber=#1 message="failure: Uh oh!" - * // failure: Uh oh! + * Effect.runSync(program2) // => "failure: Uh oh!" * ``` * * @see {@link match} if you don't need side effects and only want to handle the @@ -5665,19 +5631,20 @@ export const matchEffect: { * * **Example** (Checking whether an effect fails) * - * ```ts - * import { Console, Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect } from "effect" + * const output: Array = [] * * const program = Effect.gen(function*() { * const failed = yield* Effect.isFailure(Effect.fail("Uh oh!")) - * yield* Console.log(failed) + * yield* Effect.sync(() => { output.push(failed) }) * }) * - * Effect.runPromise(program) - * // Output: true + * Effect.runSync(program) + * output // => [true] * ``` * - * @category condition checking + * @category predicates * @since 2.0.0 */ export const isFailure: (self: Effect) => Effect = internal.isFailure @@ -5692,23 +5659,22 @@ export const isFailure: (self: Effect) => Effect = [] * * const program = Effect.gen(function*() { * const ok = yield* Effect.isSuccess(Effect.succeed("done")) * const failed = yield* Effect.isSuccess(Effect.fail("Uh oh")) - * yield* Console.log(`ok: ${ok}`) - * yield* Console.log(`failed: ${failed}`) + * yield* Effect.sync(() => { output.push(`ok: ${ok}`) }) + * yield* Effect.sync(() => { output.push(`failed: ${failed}`) }) * }) * - * Effect.runPromise(program) - * // Output: - * // ok: true - * // failed: false + * Effect.runSync(program) + * output // => ["ok: true", "failed: false"] * ``` * - * @category condition checking + * @category predicates * @since 2.0.0 */ export const isSuccess: (self: Effect) => Effect = internal.isSuccess @@ -5732,8 +5698,9 @@ export const isSuccess: (self: Effect) => Effect = [] * * const Logger = Context.Service<{ * log: (msg: string) => void @@ -5749,20 +5716,22 @@ export const isSuccess: (self: Effect) => Effect { output.push(`Logger available: ${Option.isSome(loggerOption)}`) }) + * yield* Effect.sync(() => { output.push(`Database available: ${Option.isSome(databaseOption)}`) }) * }) * - * const context = Context.make(Logger, { log: console.log }) + * const context = Context.make(Logger, { log: () => {} }) * .pipe(Context.add(Database, { query: () => "result" })) * * const provided = Effect.provideContext(program, context) + * Effect.runSync(provided) + * output // => ["Logger available: true", "Database available: true"] * ``` * * @see {@link contextWith} for deriving an effect from the complete context * @see {@link service} for reading one service from the context * - * @category environment + * @category accessors * @since 2.0.0 */ export const context: () => Effect, never, R> = internal.context @@ -5782,8 +5751,9 @@ export const context: () => Effect, never, R> = in * * **Example** (Deriving values from the context) * - * ```ts - * import { Console, Context, Effect, Option } from "effect" + * ```ts import.meta.vitest + * import { Context, Effect, Option } from "effect" + * const output: Array = [] * * const Logger = Context.Service<{ * log: (msg: string) => void @@ -5792,19 +5762,19 @@ export const context: () => Effect, never, R> = in * get: (key: string) => string | null * }>("Cache") * - * const program = Effect.contextWith((services) => { + * const program = Effect.contextWith((services: Context.Context>) => { * const cacheOption = Context.getOption(services, Cache) * const hasCache = Option.isSome(cacheOption) * * if (hasCache) { * return Effect.gen(function*() { * const cache = yield* Effect.service(Cache) - * yield* Console.log("Using cached data") + * yield* Effect.sync(() => { output.push("Using cached data") }) * return cache.get("user:123") || "default" * }) * } else { * return Effect.gen(function*() { - * yield* Console.log("No cache available, using fallback") + * yield* Effect.sync(() => { output.push("No cache available, using fallback") }) * return "fallback data" * }) * } @@ -5813,12 +5783,14 @@ export const context: () => Effect, never, R> = in * const withCache = Effect.provideService(program, Cache, { * get: () => "cached_value" * }) + * void output.push(Effect.runSync(withCache)) + * output // => ["Using cached data", "cached_value"] * ``` * * @see {@link context} for reading the complete context as a value * @see {@link service} for reading one service from the context * - * @category environment + * @category accessors * @since 2.0.0 */ export const contextWith: ( @@ -5832,7 +5804,7 @@ export const contextWith: ( * * **Example** (Providing dependencies with a layer) * - * ```ts + * ```ts import.meta.vitest * import { Context, Effect, Layer } from "effect" * * interface Database { @@ -5852,11 +5824,10 @@ export const contextWith: ( * * const provided = Effect.provide(program, DatabaseLive) * - * Effect.runPromise(provided).then(console.log) - * // Output: "Result for: SELECT * FROM users" + * await Effect.runPromise(provided) // => "Result for: SELECT * FROM users" * ``` * - * @category environment + * @category providing services * @since 2.0.0 */ export const provide: { @@ -5918,8 +5889,9 @@ export const provide: { * * **Example** (Providing a complete context) * - * ```ts + * ```ts import.meta.vitest * import { Context, Effect } from "effect" + * const output: Array = [] * * // Define service keys * const Logger = Context.Service<{ @@ -5930,7 +5902,7 @@ export const provide: { * }>("Database") * * // Create a context with multiple services - * const context = Context.make(Logger, { log: console.log }) + * const context = Context.make(Logger, { log: (message) => { output.push(message) } }) * .pipe(Context.add(Database, { query: () => "result" })) * * // An effect that requires both services @@ -5942,9 +5914,11 @@ export const provide: { * }) * * const provided = Effect.provideContext(program, context) + * void output.push(Effect.runSync(provided)) + * output // => ["Querying database", "result"] * ``` * - * @category environment + * @category providing services * @since 4.0.0 */ export const provideContext: { @@ -5957,12 +5931,58 @@ export const provideContext: { ): Effect> } = internal.provideContext +/** + * Runs an effect with the provided context as its complete environment. + * + * **When to use** + * + * Use when you already have a `Context` containing every service required by + * the effect and want the wrapped effect to run with exactly that context. + * + * **Gotchas** + * + * `setContext` replaces the current context for the wrapped effect. Services + * from an outer context are not inherited unless they are also present in the + * context passed to `setContext`. + * + * **Example** (Running with a complete context) + * + * ```ts import.meta.vitest + * import { Context, Effect } from "effect" + * + * class Config extends Context.Service()("Config") {} + * + * const program = Effect.gen(function*() { + * const config = yield* Effect.service(Config) + * return `${config.greeting}, World!` + * }) + * + * const context = Context.make(Config, { greeting: "Hello" }) + * + * const runnable = Effect.setContext(program, context) + * + * Effect.runSync(runnable) // => "Hello, World!" + * ``` + * + * @see {@link provideContext} for partially satisfying an effect's context requirements. + * @see {@link updateContext} for deriving the required context from the current one. + * + * @category providing services + * @since 4.0.0 + */ +export const setContext: { + (context: Context.Context): (self: Effect) => Effect + (self: Effect, context: Context.Context): Effect +} = internal.setContext + /** * Accesses a service from the context. * * **Example** (Accessing a required service) * - * ```ts + * ```ts import.meta.vitest * import { Context, Effect } from "effect" * * interface Database { @@ -5975,9 +5995,14 @@ export const provideContext: { * const db = yield* Effect.service(Database) * return yield* db.query("SELECT * FROM users") * }) + * + * const runnable = Effect.provideService(program, Database, { + * query: (sql) => Effect.succeed(`Result for: ${sql}`) + * }) + * Effect.runSync(runnable) // => "Result for: SELECT * FROM users" * ``` * - * @category context + * @category accessors * @since 4.0.0 */ export const service: (service: Context.Key) => Effect = internal.service @@ -5999,8 +6024,9 @@ export const service: (service: Context.Key) => Effect * * **Example** (Accessing an optional service) * - * ```ts + * ```ts import.meta.vitest * import { Context, Effect, Option } from "effect" + * const output: Array = [] * * // Define a service key * const Logger = Context.Service<{ @@ -6014,12 +6040,15 @@ export const service: (service: Context.Key) => Effect * if (Option.isSome(maybeLogger)) { * maybeLogger.value.log("Service is available") * } else { - * console.log("Service not available") + * void output.push("Service not available") * } * }) + * + * Effect.runSync(program) + * output // => ["Service not available"] * ``` * - * @category context + * @category accessors * @since 2.0.0 */ export const serviceOption: (key: Context.Key) => Effect> = internal.serviceOption @@ -6034,7 +6063,7 @@ export const serviceOption: (key: Context.Key) => Effect> * * **Example** (Updating the context before running) * - * ```ts + * ```ts import.meta.vitest * import { Context, Effect } from "effect" * * // Define services @@ -6051,18 +6080,19 @@ export const serviceOption: (key: Context.Key) => Effect> * * // Transform services by providing Config while keeping Logger requirement * const configured = program.pipe( - * Effect.updateContext((context: Context.Context) => + * Effect.updateContext((context: Context.Context>) => * Context.add(context, Config, { name: "World" }) * ) * ) * * // The effect now requires only Logger service * const result = Effect.provideService(configured, Logger, { - * log: (msg) => console.log(msg) + * log: () => {} * }) + * Effect.runSync(result) // => "Hello World!" * ``` * - * @category context + * @category providing services * @since 4.0.0 */ export const updateContext: { @@ -6086,15 +6116,16 @@ export const updateContext: { * * **Example** (Replacing a service for one effect) * - * ```ts - * import { Console, Context, Effect } from "effect" + * ```ts import.meta.vitest + * import { Context, Effect } from "effect" + * const output: Array = [] * * // Define a counter service * const Counter = Context.Service<{ count: number }>("Counter") * * const program = Effect.gen(function*() { * const updatedCounter = yield* Effect.service(Counter) - * yield* Console.log(`Updated count: ${updatedCounter.count}`) + * yield* Effect.sync(() => { output.push(`Updated count: ${updatedCounter.count}`) }) * return updatedCounter.count * }).pipe( * Effect.updateService(Counter, (counter) => ({ count: counter.count + 1 })) @@ -6102,26 +6133,92 @@ export const updateContext: { * * // Provide initial service and run * const result = Effect.provideService(program, Counter, { count: 0 }) - * Effect.runPromise(result).then(console.log) - * // Output: Updated count: 1 - * // 1 + * void output.push(Effect.runSync(result)) + * output // => ["Updated count: 1", 1] * ``` * - * @category context + * @category providing services * @since 2.0.0 */ export const updateService: { ( service: Context.Key, - f: (value: A) => A + f: (value: A) => NoInfer ): (self: Effect) => Effect ( self: Effect, service: Context.Key, - f: (value: A) => A + f: (value: A) => NoInfer ): Effect } = internal.updateService +/** + * Updates a service for the lifetime of the current scope and restores its + * previous value when the scope closes. + * + * **When to use** + * + * Use when you need a setup effect to change a service for subsequent effects + * in the same scope. + * + * **Details** + * + * The updater receives the currently visible service value. A + * `Context.Service` remains in the requirements, while a `Context.Reference` + * uses its default when no override is present and adds no service requirement. + * The returned effect always requires `Scope`. The optional `reset` function + * receives the original, updated, and current values when the scope closes, + * allowing changes to be merged during restoration. It defaults to returning + * the original value. + * + * **Example** (Updating a reference within a scope) + * + * ```ts import.meta.vitest + * import { Context, Effect } from "effect" + * const output: Array = [] + * + * const CurrentNumber = Context.Reference("CurrentNumber", { + * defaultValue: () => 1 + * }) + * + * const program = Effect.gen(function*() { + * const before = yield* CurrentNumber + * const during = yield* Effect.scoped( + * Effect.gen(function*() { + * yield* Effect.updateServiceScoped( + * CurrentNumber, + * (value) => value + 1, + * { + * // Optional: when omitted, the original value is restored + * reset: (original, updated, current) => + * Math.max(original, updated, current) + 1 + * } + * ) + * return yield* CurrentNumber + * }) + * ) + * const after = yield* CurrentNumber + * + * void output.push([before, during, after]) + * }) + * + * await Effect.runPromise(program) + * output // => [[1, 2, 3]] + * ``` + * + * @see {@link updateService} for updating a service only within a wrapped effect + * + * @category providing services + * @since 4.0.0 + */ +export const updateServiceScoped: ( + service: Context.Key, + f: (value: A) => NoInfer, + options?: { + readonly reset?: ((original: A, updated: A, current: A) => A) | undefined + } | undefined +) => Effect = internal.updateServiceScoped + /** * Provides one concrete service implementation to an effect. * @@ -6136,8 +6233,9 @@ export const updateService: { * * **Example** (Providing a service value) * - * ```ts - * import { Console, Context, Effect } from "effect" + * ```ts import.meta.vitest + * import { Context, Effect } from "effect" + * const output: Array = [] * * // Define a service for configuration * const Config = Context.Service<{ @@ -6147,8 +6245,8 @@ export const updateService: { * * const fetchData = Effect.gen(function*() { * const config = yield* Effect.service(Config) - * yield* Console.log(`Fetching from: ${config.apiUrl}`) - * yield* Console.log(`Timeout: ${config.timeout}ms`) + * yield* Effect.sync(() => { output.push(`Fetching from: ${config.apiUrl}`) }) + * yield* Effect.sync(() => { output.push(`Timeout: ${config.timeout}ms`) }) * return "data" * }) * @@ -6158,17 +6256,14 @@ export const updateService: { * timeout: 5000 * }) * - * Effect.runPromise(program).then(console.log) - * // Output: - * // Fetching from: https://api.example.com - * // Timeout: 5000ms - * // data + * void output.push(Effect.runSync(program)) + * output // => ["Fetching from: https://api.example.com", "Timeout: 5000ms", "data"] * ``` * * @see {@link provide} for providing multiple layers to an effect. * @see {@link provideServiceEffect} for acquiring the service implementation effectfully. * @see {@link provideContext} for providing a complete context. - * @category context + * @category providing services * @since 2.0.0 */ export const provideService: { @@ -6206,8 +6301,9 @@ export const provideService: { * * **Example** (Providing a service with an effect) * - * ```ts - * import { Console, Context, Effect } from "effect" + * ```ts import.meta.vitest + * import { Context, Effect } from "effect" + * const output: Array = [] * * // Define a database connection service * interface DatabaseConnection { @@ -6217,9 +6313,8 @@ export const provideService: { * * // Effect that creates a database connection * const createConnection = Effect.gen(function*() { - * yield* Console.log("Establishing database connection...") - * yield* Effect.sleep("100 millis") // Simulate connection time - * yield* Console.log("Database connected!") + * yield* Effect.sync(() => { output.push("Establishing database connection...") }) + * yield* Effect.sync(() => { output.push("Database connected!") }) * return { * query: (sql: string) => Effect.succeed(`Result for: ${sql}`) * } @@ -6237,74 +6332,25 @@ export const provideService: { * createConnection * ) * - * Effect.runPromise(withDatabase).then(console.log) - * // Output: - * // Establishing database connection... - * // Database connected! - * // Result for: SELECT * FROM users + * void output.push(await Effect.runPromise(withDatabase)) + * output // => ["Establishing database connection...", "Database connected!", "Result for: SELECT * FROM users"] * ``` * - * @category context + * @category providing services * @since 2.0.0 */ export const provideServiceEffect: { ( service: Context.Key, - acquire: Effect + acquire: Effect, E2, R2> ): (self: Effect) => Effect | R2> ( self: Effect, service: Context.Key, - acquire: Effect + acquire: Effect, E2, R2> ): Effect | R2> } = internal.provideServiceEffect -// ----------------------------------------------------------------------------- -// References -// ----------------------------------------------------------------------------- - -/** - * Sets the concurrency level for parallel operations within an effect. - * - * **Example** (Setting local concurrency) - * - * ```ts - * import { Console, Effect } from "effect" - * - * const task = (id: number) => - * Effect.gen(function*() { - * yield* Console.log(`Task ${id} starting`) - * yield* Effect.sleep("100 millis") - * yield* Console.log(`Task ${id} completed`) - * return id - * }) - * - * // Run tasks with limited concurrency (max 2 at a time) - * const program = Effect.gen(function*() { - * const tasks = [1, 2, 3, 4, 5].map(task) - * return yield* Effect.all(tasks, { concurrency: 2 }) - * }).pipe( - * Effect.withConcurrency(2) - * ) - * - * Effect.runPromise(program).then(console.log) - * // Tasks will run with max 2 concurrent operations - * // [1, 2, 3, 4, 5] - * ``` - * - * @category references - * @since 2.0.0 - */ -export const withConcurrency: { - ( - concurrency: number | "unbounded" - ): (self: Effect) => Effect - ( - self: Effect, - concurrency: number | "unbounded" - ): Effect -} = internal.withConcurrency - // ----------------------------------------------------------------------------- // Resource management & finalization // ----------------------------------------------------------------------------- @@ -6314,28 +6360,25 @@ export const withConcurrency: { * * **Example** (Accessing the current scope) * - * ```ts - * import { Console, Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect } from "effect" + * const output: Array = [] * * const program = Effect.gen(function*() { * const currentScope = yield* Effect.scope - * yield* Console.log("Got scope for resource management") + * yield* Effect.sync(() => { output.push("Got scope for resource management") }) * * // Use the scope to manually manage resources if needed * const resource = yield* Effect.acquireRelease( - * Console.log("Acquiring resource").pipe(Effect.as("resource")), - * () => Console.log("Releasing resource") + * Effect.sync(() => { output.push("Acquiring resource") }).pipe(Effect.as("resource")), + * () => Effect.sync(() => { output.push("Releasing resource") }) * ) * * return resource * }) * - * Effect.runPromise(Effect.scoped(program)).then(console.log) - * // Output: - * // Got scope for resource management - * // Acquiring resource - * // resource - * // Releasing resource + * void output.push(Effect.runSync(Effect.scoped(program))) + * output // => ["Got scope for resource management", "Acquiring resource", "Releasing resource", "resource"] * ``` * * @category resource management @@ -6357,26 +6400,25 @@ export const scope: Effect = internal.scope * * **Example** (Running a scoped acquisition) * - * ```ts - * import { Console, Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect } from "effect" + * const output: Array = [] * * const resource = Effect.acquireRelease( - * Console.log("Acquiring resource").pipe(Effect.as("resource")), - * () => Console.log("Releasing resource") + * Effect.sync(() => { output.push("Acquiring resource") }).pipe(Effect.as("resource")), + * () => Effect.sync(() => { output.push("Releasing resource") }) * ) * * const program = Effect.scoped( * Effect.gen(function*() { * const res = yield* resource - * yield* Console.log(`Using ${res}`) + * yield* Effect.sync(() => { output.push(`Using ${res}`) }) * return res * }) * ) * - * Effect.runFork(program) - * // Output: "Acquiring resource" - * // Output: "Using resource" - * // Output: "Releasing resource" + * Effect.runSync(program) + * output // => ["Acquiring resource", "Using resource", "Releasing resource"] * ``` * * @category resource management @@ -6396,21 +6438,22 @@ export const scoped: ( * * **Example** (Working with an explicit scope) * - * ```ts - * import { Console, Effect, Scope } from "effect" + * ```ts import.meta.vitest + * import { Effect, Scope } from "effect" + * const output: Array = [] * * const program = Effect.scopedWith((scope) => * Effect.gen(function*() { - * yield* Console.log("Inside scoped context") + * yield* Effect.sync(() => { output.push("Inside scoped context") }) * * // Manually add a finalizer to the scope - * yield* Scope.addFinalizer(scope, Console.log("Manual finalizer")) + * yield* Scope.addFinalizer(scope, Effect.sync(() => { output.push("Manual finalizer") })) * * // Create a scoped resource * const resource = yield* Effect.scoped( * Effect.acquireRelease( - * Console.log("Acquiring resource").pipe(Effect.as("resource")), - * () => Console.log("Releasing resource") + * Effect.sync(() => { output.push("Acquiring resource") }).pipe(Effect.as("resource")), + * () => Effect.sync(() => { output.push("Releasing resource") }) * ) * ) * @@ -6418,13 +6461,8 @@ export const scoped: ( * }) * ) * - * Effect.runPromise(program).then(console.log) - * // Output: - * // Inside scoped context - * // Acquiring resource - * // resource - * // Releasing resource - * // Manual finalizer + * void output.push(Effect.runSync(program)) + * output // => ["Inside scoped context", "Acquiring resource", "Releasing resource", "Manual finalizer", "resource"] * ``` * * @category resource management @@ -6453,8 +6491,9 @@ export const scopedWith: ( * * **Example** (Acquiring and releasing a resource) * - * ```ts - * import { Console, Effect, Exit } from "effect" + * ```ts import.meta.vitest + * import { Effect, Exit } from "effect" + * const output: Array = [] * * // Simulate a resource that needs cleanup * interface FileHandle { @@ -6464,17 +6503,17 @@ export const scopedWith: ( * * // Acquire a file handle * const acquire = Effect.gen(function*() { - * yield* Console.log("Opening file") + * yield* Effect.sync(() => { output.push("Opening file") }) * return { path: "/tmp/file.txt", content: "file content" } * }) * * // Release the file handle * const release = (handle: FileHandle, exit: Exit.Exit) => - * Console.log( + * Effect.sync(() => { output.push( * `Closing file ${handle.path} with exit: ${ * Exit.isSuccess(exit) ? "success" : "failure" * }` - * ) + * ) }) * * // Create a scoped resource * const resource = Effect.acquireRelease(acquire, release) @@ -6483,10 +6522,13 @@ export const scopedWith: ( * const program = Effect.scoped( * Effect.gen(function*() { * const handle = yield* resource - * yield* Console.log(`Using file: ${handle.path}`) + * yield* Effect.sync(() => { output.push(`Using file: ${handle.path}`) }) * return handle.content * }) * ) + * + * void output.push(Effect.runSync(program)) + * output // => ["Opening file", "Using file: /tmp/file.txt", "Closing file /tmp/file.txt with exit: success", "file content"] * ``` * * @see {@link acquireDisposable} for resources that implement JavaScript disposal protocols @@ -6523,22 +6565,25 @@ export const acquireRelease: ( * * **Example** (Acquiring a disposable resource) * - * ```ts - * import sqlite from "node:sqlite"; + * ```ts import.meta.vitest * import { Effect } from "effect" + * const output: Array = [] * - * const program = Effect.scoped( - * Effect.gen(function* () { - * // acquire database connection - * // database will be closed when the scope is closed - * const db = yield* Effect.acquireDisposable( - * Effect.sync(() => new sqlite.DatabaseSync(":memory:")) - * ) + * class Resource implements Disposable { + * [Symbol.dispose]() { + * void output.push("disposed") + * } + * } * - * const row = db.prepare("SELECT 1 AS value").get() - * yield* Effect.log(row) // { value: 1 } + * const program = Effect.scoped( + * Effect.gen(function*() { + * yield* Effect.acquireDisposable(Effect.succeed(new Resource())) + * void output.push("acquired") * }) * ) + * + * Effect.runSync(program) + * output // => ["acquired", "disposed"] * ``` * * @see {@link acquireRelease} for resources that need an explicit finalizer @@ -6578,8 +6623,9 @@ export const acquireDisposable: ( * * **Example** (Acquiring resources with cleanup) * - * ```ts - * import { Console, Effect, Exit } from "effect" + * ```ts import.meta.vitest + * import { Effect, Exit } from "effect" + * const output: Array = [] * * interface Database { * readonly connection: string @@ -6589,7 +6635,7 @@ export const acquireDisposable: ( * const program = Effect.acquireUseRelease( * // Acquire - connect to database * Effect.gen(function*() { - * yield* Console.log("Connecting to database...") + * yield* Effect.sync(() => { output.push("Connecting to database...") }) * return { * connection: "db://localhost:5432", * query: (sql: string) => Effect.succeed(`Result for: ${sql}`) @@ -6598,28 +6644,24 @@ export const acquireDisposable: ( * // Use - perform database operations * (db) => * Effect.gen(function*() { - * yield* Console.log(`Connected to ${db.connection}`) + * yield* Effect.sync(() => { output.push(`Connected to ${db.connection}`) }) * const result = yield* db.query("SELECT * FROM users") - * yield* Console.log(`Query result: ${result}`) + * yield* Effect.sync(() => { output.push(`Query result: ${result}`) }) * return result * }), * // Release - close database connection * (db, exit) => * Effect.gen(function*() { * if (Exit.isSuccess(exit)) { - * yield* Console.log(`Closing connection to ${db.connection} (success)`) + * yield* Effect.sync(() => { output.push(`Closing connection to ${db.connection} (success)`) }) * } else { - * yield* Console.log(`Closing connection to ${db.connection} (failure)`) + * yield* Effect.sync(() => { output.push(`Closing connection to ${db.connection} (failure)`) }) * } * }) * ) * - * Effect.runPromise(program) - * // Output: - * // Connecting to database... - * // Connected to db://localhost:5432 - * // Query result: Result for: SELECT * FROM users - * // Closing connection to db://localhost:5432 (success) + * await Effect.runPromise(program) + * output // => ["Connecting to database...", "Connected to db://localhost:5432", "Query result: Result for: SELECT * FROM users", "Closing connection to db://localhost:5432 (success)"] * ``` * * @see {@link acquireRelease} for scoped resources whose use happens later @@ -6647,32 +6689,30 @@ export const acquireUseRelease: ( * * **Example** (Registering scope finalizers) * - * ```ts - * import { Console, Effect, Exit } from "effect" + * ```ts import.meta.vitest + * import { Effect, Exit } from "effect" + * const output: Array = [] * * const program = Effect.scoped( * Effect.gen(function*() { * // Add a finalizer that runs when the scope closes * yield* Effect.addFinalizer((exit) => - * Console.log( + * Effect.sync(() => { output.push( * Exit.isSuccess(exit) * ? "Cleanup: Operation completed successfully" * : "Cleanup: Operation failed, cleaning up resources" - * ) + * ) }) * ) * - * yield* Console.log("Performing main operation...") + * yield* Effect.sync(() => { output.push("Performing main operation...") }) * * // This could succeed or fail * return "operation result" * }) * ) * - * Effect.runPromise(program).then(console.log) - * // Output: - * // Performing main operation... - * // Cleanup: Operation completed successfully - * // operation result + * void output.push(Effect.runSync(program)) + * output // => ["Performing main operation...", "Cleanup: Operation completed successfully", "operation result"] * ``` * * @see {@link acquireRelease} for resource acquisition with a release finalizer @@ -6700,28 +6740,24 @@ export const addFinalizer: ( * * **Example** (Always running cleanup) * - * ```ts - * import { Console, Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect } from "effect" + * const output: Array = [] * * const task = Effect.gen(function*() { - * yield* Console.log("Task started") - * yield* Effect.sleep("1 second") - * yield* Console.log("Task completed") + * yield* Effect.sync(() => { output.push("Task started") }) + * yield* Effect.sync(() => { output.push("Task completed") }) * return 42 * }) * * // Ensure cleanup always runs, regardless of success or failure * const program = Effect.ensuring( * task, - * Console.log("Cleanup: This always runs!") + * Effect.sync(() => { output.push("Cleanup: This always runs!") }) * ) * - * Effect.runPromise(program).then(console.log) - * // Output: - * // Task started - * // Task completed - * // Cleanup: This always runs! - * // 42 + * void output.push(Effect.runSync(program)) + * output // => ["Task started", "Task completed", "Cleanup: This always runs!", 42] * ``` * * @category resource management @@ -6743,22 +6779,22 @@ export const ensuring: { * * **Example** (Running cleanup on failure) * - * ```ts - * import { Cause, Console, Data, Effect } from "effect" + * ```ts import.meta.vitest + * import { Cause, Data, Effect, Exit } from "effect" + * const output: Array = [] * * class TaskError extends Data.TaggedError("TaskError")<{ readonly message: string }> {} * - * const task = Effect.fail(new TaskError({ message: "Something went wrong" })) + * const error = new TaskError({ message: "Something went wrong" }) + * const task = Effect.fail(error) * * const program = Effect.onError( * task, - * (cause) => Console.log(`Cleanup on error: ${Cause.squash(cause)}`) + * (cause) => Effect.sync(() => { output.push(`Cleanup on error: ${Cause.squash(cause)}`) }) * ) * - * Effect.runPromise(program).catch(console.error) - * // Output: - * // Cleanup on error: TaskError: Something went wrong - * // TaskError: Something went wrong + * void output.push(Effect.runSyncExit(program)) + * output // => ["Cleanup on error: TaskError: Something went wrong", Exit.fail(error)] * ``` * * @category resource management @@ -6780,8 +6816,9 @@ export const onError: { * * **Example** (Running cleanup for selected failures) * - * ```ts - * import { Cause, Console, Effect } from "effect" + * ```ts import.meta.vitest + * import { Cause, Effect, Exit } from "effect" + * const output: Array = [] * * const task = Effect.fail("boom") * @@ -6790,9 +6827,12 @@ export const onError: { * Cause.hasFails, * (cause) => * Effect.gen(function*() { - * yield* Console.log(`Cause: ${Cause.pretty(cause)}`) + * yield* Effect.sync(() => { output.push(`Cause: ${Cause.squash(cause)}`) }) * }) * ) + * + * void output.push(Effect.runSyncExit(program)) + * output // => ["Cause: boom", Exit.fail("boom")] * ``` * * @category resource management @@ -6870,22 +6910,21 @@ export const onExitPrimitive: ( * * **Example** (Observing every exit) * - * ```ts - * import { Console, Effect, Exit } from "effect" + * ```ts import.meta.vitest + * import { Effect, Exit } from "effect" + * const output: Array = [] * * const task = Effect.succeed(42) * * const program = Effect.onExit(task, (exit) => - * Console.log( + * Effect.sync(() => { output.push( * Exit.isSuccess(exit) * ? `Task succeeded with: ${exit.value}` * : `Task failed: ${Exit.isFailure(exit) ? exit.cause : "interrupted"}` - * )) + * ) })) * - * Effect.runPromise(program).then(console.log) - * // Output: - * // Task succeeded with: 42 - * // 42 + * void output.push(Effect.runSync(program)) + * output // => ["Task succeeded with: 42", 42] * ``` * * @category resource management @@ -6907,17 +6946,21 @@ export const onExit: { * * **Example** (Observing selected exits) * - * ```ts - * import { Console, Effect, Exit } from "effect" + * ```ts import.meta.vitest + * import { Effect, Exit } from "effect" + * const output: Array = [] * * const program = Effect.onExitIf( * Effect.succeed(42), * Exit.isSuccess, * (exit) => * Exit.isSuccess(exit) - * ? Console.log(`Succeeded with: ${exit.value}`) + * ? Effect.sync(() => { output.push(`Succeeded with: ${exit.value}`) }) * : Effect.void * ) + * + * void output.push(Effect.runSync(program)) + * output // => ["Succeeded with: 42", 42] * ``` * * @category resource management @@ -6990,40 +7033,29 @@ export const onExitFilter: { * * **Example** (Memoizing an effect until invalidated) * - * ```ts - * import { Console, Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect } from "effect" + * const output: Array = [] + * const record = (value: unknown) => Effect.sync(() => { output.push(value) }) * * let i = 1 - * const expensiveTask = Effect.promise(() => { - * console.log("expensive task...") - * return new Promise((resolve) => { - * setTimeout(() => { - * resolve(`result ${i++}`) - * }, 100) - * }) + * const expensiveTask = Effect.sync(() => { + * void output.push("expensive task...") + * return `result ${i++}` * }) * * const program = Effect.gen(function*() { - * console.log("non-cached version:") - * yield* expensiveTask.pipe(Effect.andThen(Console.log)) - * yield* expensiveTask.pipe(Effect.andThen(Console.log)) - * console.log("cached version:") + * void output.push("non-cached version:") + * yield* expensiveTask.pipe(Effect.andThen(record)) + * yield* expensiveTask.pipe(Effect.andThen(record)) + * void output.push("cached version:") * const cached = yield* Effect.cached(expensiveTask) - * yield* cached.pipe(Effect.andThen(Console.log)) - * yield* cached.pipe(Effect.andThen(Console.log)) + * yield* cached.pipe(Effect.andThen(record)) + * yield* cached.pipe(Effect.andThen(record)) * }) * - * Effect.runFork(program) - * // Output: - * // non-cached version: - * // expensive task... - * // result 1 - * // expensive task... - * // result 2 - * // cached version: - * // expensive task... - * // result 3 - * // result 3 + * await Effect.runPromise(program) + * output // => ["non-cached version:", "expensive task...", "result 1", "expensive task...", "result 2", "cached version:", "expensive task...", "result 3", "result 3"] * ``` * * @see {@link cachedWithTTL} for a similar function that includes a @@ -7058,34 +7090,26 @@ export const cached: (self: Effect) => Effect> * * **Example** (Memoizing an effect with TTL) * - * ```ts - * import { Console, Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect } from "effect" + * const output: Array = [] + * const record = (value: unknown) => Effect.sync(() => { output.push(value) }) * * let i = 1 - * const expensiveTask = Effect.promise(() => { - * console.log("expensive task...") - * return new Promise((resolve) => { - * setTimeout(() => { - * resolve(`result ${i++}`) - * }, 100) - * }) + * const expensiveTask = Effect.sync(() => { + * void output.push("expensive task...") + * return `result ${i++}` * }) * * const program = Effect.gen(function*() { - * const cached = yield* Effect.cachedWithTTL(expensiveTask, "150 millis") - * yield* cached.pipe(Effect.andThen(Console.log)) - * yield* cached.pipe(Effect.andThen(Console.log)) - * yield* Effect.sleep("100 millis") - * yield* cached.pipe(Effect.andThen(Console.log)) + * const cached = yield* Effect.cachedWithTTL(expensiveTask, "1 hour") + * yield* cached.pipe(Effect.andThen(record)) + * yield* cached.pipe(Effect.andThen(record)) + * yield* cached.pipe(Effect.andThen(record)) * }) * - * Effect.runFork(program) - * // Output: - * // expensive task... - * // result 1 - * // result 1 - * // expensive task... - * // result 2 + * Effect.runSync(program) + * output // => ["expensive task...", "result 1", "result 1", "result 1"] * ``` * * @see {@link cached} for a similar function that caches the result @@ -7125,17 +7149,15 @@ export const cachedWithTTL: { * * **Example** (Memoizing with TTL and invalidation) * - * ```ts - * import { Console, Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect } from "effect" + * const output: Array = [] + * const record = (value: unknown) => Effect.sync(() => { output.push(value) }) * * let i = 1 - * const expensiveTask = Effect.promise(() => { - * console.log("expensive task...") - * return new Promise((resolve) => { - * setTimeout(() => { - * resolve(`result ${i++}`) - * }, 100) - * }) + * const expensiveTask = Effect.sync(() => { + * void output.push("expensive task...") + * return `result ${i++}` * }) * * const program = Effect.gen(function*() { @@ -7143,19 +7165,14 @@ export const cachedWithTTL: { * expensiveTask, * "1 hour" * ) - * yield* cached.pipe(Effect.andThen(Console.log)) - * yield* cached.pipe(Effect.andThen(Console.log)) + * yield* cached.pipe(Effect.andThen(record)) + * yield* cached.pipe(Effect.andThen(record)) * yield* invalidate - * yield* cached.pipe(Effect.andThen(Console.log)) + * yield* cached.pipe(Effect.andThen(record)) * }) * - * Effect.runFork(program) - * // Output: - * // expensive task... - * // result 1 - * // result 1 - * // expensive task... - * // result 2 + * Effect.runSync(program) + * output // => ["expensive task...", "result 1", "result 1", "expensive task...", "result 2"] * ``` * * @see {@link cached} for a similar function that caches the result @@ -7179,7 +7196,7 @@ export const cachedInvalidateWithTTL: { * * **Example** (Creating an interrupted effect) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * * const program = Effect.gen(function*() { @@ -7187,8 +7204,7 @@ export const cachedInvalidateWithTTL: { * yield* Effect.succeed("This won't execute and is unreachable") * }) * - * Effect.runPromise(program).catch(console.error) - * // Throws: InterruptedException + * Effect.runSyncExit(program)._tag // => "Failure" * ``` * * @category interruption @@ -7201,16 +7217,13 @@ export const interrupt: Effect = internal.interrupt * * **Example** (Allowing interruption) * - * ```ts - * import { Effect } from "effect" - * - * const longRunning = Effect.forever(Effect.succeed("working...")) - * - * const program = Effect.interruptible(longRunning) + * ```ts import.meta.vitest + * import { Effect, Option } from "effect" * - * // This effect can now be interrupted - * const fiber = Effect.runFork(program) - * // Later: fiber.interrupt() + * const program = Effect.interruptible(Effect.never).pipe( + * Effect.timeoutOption(0) + * ) + * await Effect.runPromise(program) // => Option.none() * ``` * * @category interruption @@ -7225,20 +7238,20 @@ export const interruptible: ( * * **Example** (Running cleanup on interruption) * - * ```ts - * import { Console, Effect, Fiber } from "effect" + * ```ts import.meta.vitest + * import { Effect, Fiber } from "effect" + * const output: Array = [] * * const task = Effect.forever(Effect.succeed("working...")) * * const program = Effect.onInterrupt( * task, - * () => Console.log("Task was interrupted, cleaning up...") + * () => Effect.sync(() => { output.push("Task was interrupted, cleaning up...") }) * ) * * const fiber = Effect.runFork(program) - * // Later interrupt the task - * Effect.runFork(Fiber.interrupt(fiber)) - * // Output: Task was interrupted, cleaning up... + * await Effect.runPromise(Fiber.interrupt(fiber)) + * output // => ["Task was interrupted, cleaning up..."] * ``` * * @category interruption @@ -7259,20 +7272,19 @@ export const onInterrupt: { * * **Example** (Preventing interruption) * - * ```ts - * import { Console, Effect, Fiber } from "effect" + * ```ts import.meta.vitest + * import { Effect } from "effect" + * const output: Array = [] * * const criticalTask = Effect.gen(function*() { - * yield* Console.log("Starting critical section...") - * yield* Effect.sleep("2 seconds") - * yield* Console.log("Critical section completed") + * yield* Effect.sync(() => { output.push("Starting critical section...") }) + * yield* Effect.sync(() => { output.push("Critical section completed") }) * }) * * const program = Effect.uninterruptible(criticalTask) * - * const fiber = Effect.runFork(program) - * // Even if interrupted, the critical task will complete - * Effect.runPromise(Fiber.interrupt(fiber)) + * Effect.runSync(program) + * output // => ["Starting critical section...", "Critical section completed"] * ``` * * @category interruption @@ -7288,25 +7300,26 @@ export const uninterruptible: ( * * **Example** (Restoring interruption in protected regions) * - * ```ts - * import { Console, Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect } from "effect" + * const output: Array = [] * * const program = Effect.uninterruptibleMask((restore) => * Effect.gen(function*() { - * yield* Console.log("Uninterruptible phase...") - * yield* Effect.sleep("1 second") - * + * yield* Effect.sync(() => { output.push("Uninterruptible phase...") }) * // Restore interruptibility for this part * yield* restore( * Effect.gen(function*() { - * yield* Console.log("Interruptible phase...") - * yield* Effect.sleep("2 seconds") + * yield* Effect.sync(() => { output.push("Interruptible phase...") }) * }) * ) * - * yield* Console.log("Back to uninterruptible") + * yield* Effect.sync(() => { output.push("Back to uninterruptible") }) * }) * ) + * + * Effect.runSync(program) + * output // => ["Uninterruptible phase...", "Interruptible phase...", "Back to uninterruptible"] * ``` * * @category interruption @@ -7324,25 +7337,26 @@ export const uninterruptibleMask: ( * * **Example** (Controlling interruptibility locally) * - * ```ts - * import { Console, Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect } from "effect" + * const output: Array = [] * * const program = Effect.interruptibleMask((restore) => * Effect.gen(function*() { - * yield* Console.log("Interruptible phase...") - * yield* Effect.sleep("1 second") - * + * yield* Effect.sync(() => { output.push("Interruptible phase...") }) * // Make this part uninterruptible * yield* restore( * Effect.gen(function*() { - * yield* Console.log("Uninterruptible phase...") - * yield* Effect.sleep("2 seconds") + * yield* Effect.sync(() => { output.push("Uninterruptible phase...") }) * }) * ) * - * yield* Console.log("Back to interruptible") + * yield* Effect.sync(() => { output.push("Back to interruptible") }) * }) * ) + * + * Effect.runSync(program) + * output // => ["Interruptible phase...", "Uninterruptible phase...", "Back to interruptible"] * ``` * * @category interruption @@ -7440,26 +7454,11 @@ export declare namespace Repeat { * * **Example** (Repeating forever) * - * ```ts - * import { Console, Effect, Fiber } from "effect" - * - * const task = Effect.gen(function*() { - * yield* Console.log("Task running...") - * yield* Effect.sleep("1 second") - * }) - * - * // This will run forever, printing every second - * const program = task.pipe(Effect.forever) - * - * // This will run forever, without yielding every iteration - * const programNoYield = task.pipe(Effect.forever({ disableYield: true })) + * ```ts import.meta.vitest + * import { Effect, Option } from "effect" * - * // Run for 5 seconds then interrupt - * const timedProgram = Effect.gen(function*() { - * const fiber = yield* Effect.forkChild(program) - * yield* Effect.sleep("5 seconds") - * yield* Fiber.interrupt(fiber) - * }) + * const program = Effect.forever(Effect.never).pipe(Effect.timeoutOption(0)) + * await Effect.runPromise(program) // => Option.none() * ``` * * @category repetition @@ -7512,41 +7511,45 @@ export const forever: < * * **Example** (Repeating successful effects with a schedule) * - * ```ts + * ```ts import.meta.vitest * // Success Example - * import { Console, Effect, Schedule } from "effect" + * import { Effect, Schedule } from "effect" + * const output: Array = [] * - * const action = Console.log("success") - * const policy = Schedule.addDelay(Schedule.recurs(2), () => Effect.succeed("100 millis")) + * const action = Effect.sync(() => { output.push("success") }) + * const policy = Schedule.recurs(2) * const program = Effect.repeat(action, policy) * - * // Effect.runPromise(program).then((n) => console.log(`repetitions: ${n}`)) + * void output.push(Effect.runSync(program)) + * output // => ["success", "success", "success", 2] * ``` * * **Example** (Stopping repetition on failure) * - * ```ts + * ```ts import.meta.vitest * // Failure Example * import { Effect, Schedule } from "effect" + * const output: Array = [] * * let count = 0 * * // Define a callback effect that simulates an action with possible failures * const action = Effect.callback((resume) => { * if (count > 1) { - * console.log("failure") + * void output.push("failure") * resume(Effect.fail("Uh oh!")) * } else { * count++ - * console.log("success") + * void output.push("success") * resume(Effect.succeed("yay!")) * } * }) * - * const policy = Schedule.addDelay(Schedule.recurs(2), () => Effect.succeed("100 millis")) + * const policy = Schedule.recurs(2) * const program = Effect.repeat(action, policy) * - * // Effect.runPromiseExit(program).then(console.log) + * void output.push((await Effect.runPromiseExit(program))._tag) + * output // => ["success", "success", "failure", "Failure"] * ``` * * @see {@link retry} for failure-based repetition @@ -7596,17 +7599,18 @@ export const repeat: { * * **Example** (Recovering after repetition stops) * - * ```ts - * import { Console, Effect, Option, Schedule } from "effect" + * ```ts import.meta.vitest + * import { Effect, Option, Schedule } from "effect" + * const output: Array = [] * * let attempt = 0 * const task = Effect.gen(function*() { * attempt++ * if (attempt <= 2) { - * yield* Console.log(`Attempt ${attempt} failed`) + * yield* Effect.sync(() => { output.push(`Attempt ${attempt} failed`) }) * return yield* Effect.fail(`Error ${attempt}`) * } - * yield* Console.log(`Attempt ${attempt} succeeded`) + * yield* Effect.sync(() => { output.push(`Attempt ${attempt} succeeded`) }) * return "success" * }) * @@ -7614,12 +7618,15 @@ export const repeat: { * task, * Schedule.recurs(3), * (error, attempts) => - * Console.log( + * Effect.sync(() => { output.push( * `Final failure: ${error}, after ${ * Option.getOrElse(attempts, () => 0) * } attempts` - * ).pipe(Effect.map(() => 0)) + * ) }).pipe(Effect.map(() => 0)) * ) + * + * void output.push(Effect.runSync(program)) + * output // => ["Attempt 1 failed", "Final failure: Error 1, after 0 attempts", 0] * ``` * * @category repetition @@ -7652,7 +7659,7 @@ export const repeatOrElse: { * @see {@link all} for running the returned effects and collecting results * @see {@link replicateEffect} for repeating an effect and collecting results in one step with concurrency and discard options * - * @category collecting + * @category repetition * @since 2.0.0 */ export const replicate: { @@ -7674,16 +7681,20 @@ export const replicate: { * * **Example** (Replicating an effect) * - * ```ts - * import { Console, Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect } from "effect" + * const output: Array = [] * * const program = Effect.gen(function*() { * const results = yield* Effect.replicateEffect(3)(Effect.succeed(1)) - * yield* Console.log(results) + * yield* Effect.sync(() => { output.push(results) }) * }) + * + * Effect.runSync(program) + * output // => [[1, 1, 1]] * ``` * - * @category collecting + * @category repetition * @since 2.0.0 */ export const replicateEffect: { @@ -7726,26 +7737,19 @@ export const replicateEffect: { * * **Example** (Scheduling repeated execution) * - * ```ts - * import { Console, Effect, Schedule } from "effect" + * ```ts import.meta.vitest + * import { Effect, Schedule } from "effect" + * const output: Array = [] * * const task = Effect.gen(function*() { - * yield* Console.log("Task executing...") - * return Math.random() + * yield* Effect.sync(() => { output.push("Task executing...") }) + * return 1 * }) * - * // Repeat 3 times with 1 second delay between executions - * const program = Effect.schedule( - * task, - * Schedule.addDelay(Schedule.recurs(2), () => Effect.succeed("1 second")) - * ) + * const program = Effect.schedule(task, Schedule.recurs(2)) * - * Effect.runPromise(program).then(console.log) - * // Output: - * // Task executing... (immediate) - * // Task executing... (after 1 second) - * // Task executing... (after 1 second) - * // Returns the count from Schedule.recurs + * void output.push(Effect.runSync(program)) + * output // => ["Task executing...", "Task executing...", 2] * ``` * * @see {@link scheduleFrom} for a variant that allows the schedule's decision @@ -7757,15 +7761,15 @@ export const replicateEffect: { export const schedule: { ( schedule: Schedule - ): (self: Effect) => Effect + ): (self: Effect) => Effect ( self: Effect, schedule: Schedule - ): Effect + ): Effect } = dual(2, ( self: Effect, schedule: Schedule -): Effect => scheduleFrom(self, undefined, schedule)) +): Effect => scheduleFrom(self, undefined, schedule)) /** * Runs an effect repeatedly according to a schedule that is initialized with a @@ -7781,12 +7785,13 @@ export const schedule: { * * **Example** (Scheduling from an initial value) * - * ```ts - * import { Console, Effect, Schedule } from "effect" + * ```ts import.meta.vitest + * import { Effect, Schedule } from "effect" + * const output: Array = [] * * const task = (input: number) => * Effect.gen(function*() { - * yield* Console.log(`Processing: ${input}`) + * yield* Effect.sync(() => { output.push(`Processing: ${input}`) }) * return input + 1 * }) * @@ -7797,8 +7802,8 @@ export const schedule: { * Schedule.recurs(2) * ) * - * Effect.runPromise(program).then(console.log) - * // Returns the schedule count + * void output.push(Effect.runSync(program)) + * output // => ["Processing: 0", "Processing: 0", 2] * ``` * * @category repetition @@ -7808,12 +7813,12 @@ export const scheduleFrom: { ( initial: Input, schedule: Schedule - ): (self: Effect) => Effect + ): (self: Effect) => Effect ( self: Effect, initial: Input, schedule: Schedule - ): Effect + ): Effect } = internalSchedule.scheduleFrom // ----------------------------------------------------------------------------- @@ -7825,14 +7830,15 @@ export const scheduleFrom: { * * **Example** (Accessing the current tracer) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * * const program = Effect.gen(function*() { * const currentTracer = yield* Effect.tracer - * yield* Effect.log(`Using tracer: ${currentTracer}`) - * return "operation completed" + * return typeof currentTracer.span * }) + * + * Effect.runSync(program) // => "function" * ``` * * @category tracing @@ -7845,16 +7851,15 @@ export const tracer: Effect = internal.tracer * * **Example** (Providing a tracer) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * * const program = Effect.gen(function*() { - * yield* Effect.log("Using tracer") - * return "completed" + * const tracer = yield* Effect.tracer + * return yield* Effect.withTracer(Effect.succeed("completed"), tracer) * }) * - * // withTracer provides a tracer to the effect context - * // const traced = Effect.withTracer(program, customTracer) + * Effect.runSync(program) // => "completed" * ``` * * @category tracing @@ -7875,14 +7880,15 @@ export const withTracer: { * * **Example** (Enabling or disabling tracing) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * - * Effect.succeed(42).pipe( + * const program = Effect.succeed(42).pipe( * Effect.withSpan("my-span"), * // the span will not be registered with the tracer * Effect.withTracerEnabled(false) * ) + * Effect.runSync(program) // => 42 * ``` * * @category tracing @@ -7898,14 +7904,15 @@ export const withTracerEnabled: { * * **Example** (Enabling or disabling tracing timing) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * - * Effect.succeed(42).pipe( + * const program = Effect.succeed(42).pipe( * Effect.withSpan("my-span"), * // the span will not have timing information * Effect.withTracerTiming(false) * ) + * Effect.runSync(program) // => 42 * ``` * * @category tracing @@ -7921,13 +7928,10 @@ export const withTracerTiming: { * * **Example** (Annotating all spans) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * - * const program = Effect.gen(function*() { - * yield* Effect.log("Doing some work...") - * return "result" - * }) + * const program = Effect.succeed("result") * * // Add single annotation * const annotated1 = Effect.annotateSpans(program, "user", "john") @@ -7938,6 +7942,8 @@ export const withTracerTiming: { * version: "1.0.0", * environment: "production" * }) + * + * Effect.runSync(Effect.all([annotated1, annotated2])) // => ['result', 'result'] * ``` * * @category tracing @@ -7967,20 +7973,19 @@ export const annotateSpans: { * * **Example** (Annotating the current span) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * * const program = Effect.gen(function*() { * yield* Effect.annotateCurrentSpan("userId", "123") * yield* Effect.annotateCurrentSpan({ - * operation: "user-lookup", - * timestamp: Date.now() + * operation: "user-lookup" * }) - * yield* Effect.log("User lookup completed") * return "success" * }) * * const traced = Effect.withSpan(program, "user-operation") + * Effect.runSync(traced) // => "success" * ``` * * @category tracing @@ -8001,16 +8006,16 @@ export const annotateCurrentSpan: { * * **Example** (Reading the current span) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * * const program = Effect.gen(function*() { * const span = yield* Effect.currentSpan - * yield* Effect.log(`Current span: ${span}`) - * return "done" + * return span.name * }) * * const traced = Effect.withSpan(program, "my-span") + * Effect.runSync(traced) // => "my-span" * ``` * * @category tracing @@ -8029,21 +8034,18 @@ export const currentSpan: Effect = internal.curr * * **Example** (Reading the parent span) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * * const childOperation = Effect.gen(function*() { * const parentSpan = yield* Effect.currentParentSpan - * yield* Effect.log(`Parent span: ${parentSpan}`) - * return "child completed" + * return parentSpan._tag * }) * - * const program = Effect.gen(function*() { - * yield* Effect.withSpan(childOperation, "child-span") - * return "parent completed" - * }) + * const program = Effect.withSpan(childOperation, "child-span") * * const traced = Effect.withSpan(program, "parent-span") + * Effect.runSync(traced) // => "Span" * ``` * * @category tracing @@ -8061,23 +8063,15 @@ export const currentParentSpan: Effect = inte * * **Example** (Providing span annotations) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * * const program = Effect.gen(function*() { - * // Add some annotations to the current span - * yield* Effect.annotateCurrentSpan("userId", "123") - * yield* Effect.annotateCurrentSpan("operation", "data-processing") - * - * // Retrieve all annotations * const annotations = yield* Effect.spanAnnotations - * - * console.log("Current span annotations:", annotations) * return annotations - * }) + * }).pipe(Effect.annotateSpans({ userId: "123", operation: "data-processing" })) * - * Effect.runPromise(program).then(console.log) - * // Output: Current span annotations: { userId: "123", operation: "data-processing" } + * Effect.runSync(program) // => { userId: '123', operation: 'data-processing' } * ``` * * @category tracing @@ -8095,15 +8089,16 @@ export const spanAnnotations: Effect>> = intern * * **Example** (Providing span links) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * * const program = Effect.gen(function*() { * // Get the current span links * const links = yield* Effect.spanLinks - * console.log(`Current span has ${links.length} links`) * return links * }) + * + * Effect.runSync(program).length // => 0 * ``` * * @category tracing @@ -8122,44 +8117,37 @@ export const spanLinks: Effect> = internal.spanLinks * * **Example** (Linking one span to another span) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * - * const parentEffect = Effect.withSpan("parent-operation")( - * Effect.succeed("parent result") - * ) - * - * const childEffect = Effect.withSpan("child-operation")( - * Effect.succeed("child result") - * ) - * - * // Link the child span to the parent span - * const program = Effect.gen(function*() { + * const program = Effect.withSpan(Effect.gen(function*() { * const parentSpan = yield* Effect.currentSpan - * const result = yield* childEffect.pipe( + * return yield* Effect.spanLinks.pipe( * Effect.linkSpans(parentSpan, { relationship: "follows" }) * ) - * return result - * }) + * }), "parent-operation") + * + * Effect.runSync(program).length // => 1 * ``` * * **Example** (Linking multiple spans at once) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * - * // Link multiple spans * const program = Effect.gen(function*() { - * const span1 = yield* Effect.currentSpan - * const span2 = yield* Effect.currentSpan + * const span1 = yield* Effect.makeSpan("span-1") + * const span2 = yield* Effect.makeSpan("span-2") * - * return yield* Effect.succeed("result").pipe( + * return yield* Effect.spanLinks.pipe( * Effect.linkSpans([span1, span2], { * type: "dependency", * source: "multiple-operations" * }) * ) * }) + * + * Effect.runSync(program).length // => 2 * ``` * * @category tracing @@ -8188,14 +8176,15 @@ export const linkSpans: { * * **Example** (Creating a span manually) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * * const program = Effect.gen(function*() { * const span = yield* Effect.makeSpan("my-operation") - * yield* Effect.log("Operation in progress") - * return "completed" + * return span.name * }) + * + * Effect.runSync(program) // => "my-operation" * ``` * * @category tracing @@ -8214,17 +8203,18 @@ export const makeSpan: (name: string, options?: SpanOptionsNoTrace) => Effect "scoped-operation" * ``` * * @category tracing @@ -8246,17 +8236,14 @@ export const makeSpanScoped: ( * * **Example** (Running an effect with a standalone span) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * * const program = Effect.useSpan( * "user-operation", - * (span) => - * Effect.gen(function*() { - * yield* Effect.log("Processing user data") - * return "success" - * }) + * (span) => Effect.succeed(`${span.name}: success`) * ) + * Effect.runSync(program) // => "user-operation: success" * ``` * * @category tracing @@ -8272,17 +8259,15 @@ export const useSpan: { * * **Example** (Wrapping an effect in a child span) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * - * const task = Effect.gen(function*() { - * yield* Effect.log("Executing task") - * return "result" - * }) + * const task = Effect.succeed("result") * * const traced = Effect.withSpan(task, "my-task", { * attributes: { version: "1.0" } * }) + * Effect.runSync(traced) // => "result" * ``` * * @category tracing @@ -8313,16 +8298,17 @@ export const withSpan: { * * **Example** (Creating a scoped child span) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * * const program = Effect.scoped( * Effect.gen(function*() { - * const task = Effect.log("Working...") + * const task = Effect.succeed("working") * yield* Effect.withSpanScoped(task, "scoped-task") * return "completed" * }) * ) + * Effect.runSync(program) // => "completed" * ``` * * @category tracing @@ -8347,15 +8333,16 @@ export const withSpanScoped: { * * **Example** (Setting a parent span) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * * const program = Effect.gen(function*() { * const span = yield* Effect.makeSpan("parent-span") - * const childTask = Effect.log("Child operation") + * const childTask = Effect.succeed("child operation") * yield* Effect.withParentSpan(childTask, span) * return "completed" * }) + * Effect.runSync(program) // => "completed" * ``` * * @category tracing @@ -8379,8 +8366,9 @@ export const withParentSpan: { * * **Example** (Executing a request through a resolver) * - * ```ts - * import { Console, Effect, Exit, Request, RequestResolver } from "effect" + * ```ts import.meta.vitest + * import { Effect, Exit, Request, RequestResolver } from "effect" + * const output: Array = [] * * interface GetUser extends Request.Request { * readonly _tag: "GetUser" @@ -8398,13 +8386,16 @@ export const withParentSpan: { * * const program = Effect.gen(function*() { * const name = yield* Effect.request(GetUser({ id: 1 }), resolver) - * yield* Console.log(name) + * yield* Effect.sync(() => { output.push(name) }) * }) + * + * await Effect.runPromise(program) + * output // => ["user-1"] * ``` * * @see {@link requestUnsafe} for the low-level entry point when you already have a `Context` and need to enqueue outside an `Effect` * - * @category requests & batching + * @category running * @since 2.0.0 */ export const request: { @@ -8431,7 +8422,7 @@ export const request: { * * @see {@link request} for the `Effect`-returning API used for normal request execution * - * @category requests & batching + * @category unsafe * @since 4.0.0 */ export const requestUnsafe: ( @@ -8472,28 +8463,21 @@ export const requestUnsafe: ( * * **Example** (Forking a child fiber) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Fiber } from "effect" * - * const longRunningTask = Effect.gen(function*() { - * yield* Effect.sleep("2 seconds") - * yield* Effect.log("Task completed") - * return "result" - * }) + * const task = Effect.succeed("result") * * const program = Effect.gen(function*() { - * const fiber = yield* longRunningTask.pipe(Effect.forkChild) - * - * // or fork a fiber that starts immediately: - * yield* longRunningTask.pipe(Effect.forkChild({ startImmediately: true })) - * - * yield* Effect.log("Task forked, continuing...") + * const fiber = yield* task.pipe(Effect.forkChild) * const result = yield* Fiber.join(fiber) * return result * }) + * + * await Effect.runPromise(program) // => "result" * ``` * - * @category supervision & fibers + * @category forking * @since 4.0.0 */ export const forkChild: < @@ -8519,26 +8503,24 @@ export const forkChild: < * * **Example** (Forking into a supplied scope) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * - * const task = Effect.gen(function*() { - * yield* Effect.sleep("10 seconds") - * return "completed" - * }) + * const task = Effect.never * * const program = Effect.scoped( * Effect.gen(function*() { * const scope = yield* Effect.scope * const fiber = yield* Effect.forkIn(task, scope) - * yield* Effect.sleep("1 second") * // Fiber will be interrupted when scope closes * return "done" * }) * ) + * + * await Effect.runPromise(program) // => "done" * ``` * - * @category supervision & fibers + * @category forking * @since 2.0.0 */ export const forkIn: { @@ -8564,32 +8546,24 @@ export const forkIn: { * * **Example** (Forking into the current scope) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * - * const backgroundTask = Effect.gen(function*() { - * yield* Effect.sleep("5 seconds") - * yield* Effect.log("Background task completed") - * return "result" - * }) + * const backgroundTask = Effect.never * * const program = Effect.scoped( * Effect.gen(function*() { - * const fiber = yield* backgroundTask.pipe(Effect.forkScoped) - * - * // or fork a fiber that starts immediately: - * yield* backgroundTask.pipe(Effect.forkScoped({ startImmediately: true })) - * - * yield* Effect.log("Task forked in scope") - * yield* Effect.sleep("1 second") + * yield* backgroundTask.pipe(Effect.forkScoped) * * // Fiber will be interrupted when scope closes * return "scope completed" * }) * ) + * + * await Effect.runPromise(program) // => "scope completed" * ``` * - * @category supervision & fibers + * @category forking * @since 2.0.0 */ export const forkScoped: < @@ -8616,30 +8590,20 @@ export const forkScoped: < * * **Example** (Forking a detached fiber) * - * ```ts - * import { Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect, Fiber } from "effect" * - * const daemonTask = Effect.gen(function*() { - * while (true) { - * yield* Effect.sleep("1 second") - * yield* Effect.log("Daemon running...") - * } - * }) + * const daemonTask = Effect.succeed("daemon result") * * const program = Effect.gen(function*() { * const fiber = yield* daemonTask.pipe(Effect.forkDetach) - * - * // or fork a fiber that starts immediately: - * yield* daemonTask.pipe(Effect.forkDetach({ startImmediately: true })) - * - * yield* Effect.log("Daemon started") - * yield* Effect.sleep("3 seconds") - * // Daemon continues running after this effect completes - * return "main completed" + * return yield* Fiber.join(fiber) * }) + * + * await Effect.runPromise(program) // => "daemon result" * ``` * - * @category supervision & fibers + * @category forking * @since 4.0.0 */ export const forkDetach: < @@ -8678,7 +8642,7 @@ export const forkDetach: < * @see {@link forkIn} for forking into an explicit scope * @see {@link forkScoped} for forking fibers tied to the current scope * - * @category supervision & fibers + * @category sequencing * @since 2.0.0 */ export const awaitAllChildren: (self: Effect) => Effect = internal.awaitAllChildren @@ -8688,16 +8652,20 @@ export const awaitAllChildren: (self: Effect) => Effect = [] * * const program = Effect.gen(function*() { * const fiber = yield* Effect.fiber - * yield* Console.log(`Fiber id: ${fiber.id}`) + * yield* Effect.sync(() => { output.push(typeof fiber.id) }) * }) + * + * Effect.runSync(program) + * output // => ["number"] * ``` * - * @category supervision & fibers + * @category accessors * @since 4.0.0 */ export const fiber: Effect> = internal.fiber @@ -8707,21 +8675,14 @@ export const fiber: Effect> = internal.fiber * * **Example** (Accessing the current fiber id) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * - * const program = Effect.log("event").pipe( - * // Read the current span with the fiber id for tagging. - * Effect.andThen(Effect.all([Effect.currentSpan, Effect.fiberId])), - * Effect.withSpan("A"), - * Effect.map(([span, fiberId]) => ({ - * spanName: span.name, - * fiberId - * })) - * ) + * const program = Effect.fiberId.pipe(Effect.map((id) => typeof id)) + * Effect.runSync(program) // => "number" * ``` * - * @category supervision & fibers + * @category accessors * @since 2.0.0 */ export const fiberId: Effect = internal.fiberId @@ -8770,23 +8731,20 @@ export interface RunOptions { * * **Example** (Running an effect in the background) * - * ```ts - * import { Console, Effect, Fiber, Schedule } from "effect" + * ```ts import.meta.vitest + * import { Effect, Fiber } from "effect" + * const output: Array = [] * * // ┌─── Effect * // ▼ - * const program = Effect.repeat( - * Console.log("running..."), - * Schedule.spaced("200 millis") - * ) + * const program = Effect.sync(() => { output.push("running...") }).pipe(Effect.as("done")) * * // ┌─── RuntimeFiber * // ▼ * const fiber = Effect.runFork(program) * - * setTimeout(() => { - * Effect.runFork(Fiber.interrupt(fiber)) - * }, 500) + * void output.push(await Effect.runPromise(Fiber.join(fiber))) + * output // => ["running...", "done"] * ``` * * @category running @@ -8805,8 +8763,9 @@ export const runFork: (effect: Effect, options?: RunOptions | * * **Example** (Running with services in the background) * - * ```ts - * import { Context, Effect } from "effect" + * ```ts import.meta.vitest + * import { Context, Effect, Fiber } from "effect" + * const output: Array = [] * * interface Logger { * log: (message: string) => void @@ -8815,7 +8774,7 @@ export const runFork: (effect: Effect, options?: RunOptions | * const Logger = Context.Service("Logger") * * const services = Context.make(Logger, { - * log: (message) => console.log(message) + * log: (message) => void output.push(message) * }) * * const program = Effect.gen(function*() { @@ -8825,6 +8784,8 @@ export const runFork: (effect: Effect, options?: RunOptions | * }) * * const fiber = Effect.runForkWith(services)(program) + * void output.push(await Effect.runPromise(Fiber.join(fiber))) + * output // => ["Hello from service!", "done"] * ``` * * @category running @@ -8848,8 +8809,9 @@ export const runForkWith: ( * * **Example** (Running with services and a callback) * - * ```ts - * import { Console, Context, Effect, Exit } from "effect" + * ```ts import.meta.vitest + * import { Context, Effect } from "effect" + * const output: Array = [] * * interface Logger { * log: (message: string) => Effect.Effect @@ -8858,7 +8820,7 @@ export const runForkWith: ( * const Logger = Context.Service("Logger") * * const services = Context.make(Logger, { - * log: (message) => Console.log(message) + * log: (message) => Effect.sync(() => { output.push(message) }) * }) * * const program = Effect.gen(function*() { @@ -8867,16 +8829,15 @@ export const runForkWith: ( * return "done" * }) * - * const interrupt = Effect.runCallbackWith(services)(program, { - * onExit: (exit) => { - * if (Exit.isFailure(exit)) { - * // handle failure or interruption + * await new Promise((resolve) => { + * Effect.runCallbackWith(services)(program, { + * onExit: (exit) => { + * void output.push(exit._tag) + * resolve() * } - * } + * }) * }) - * - * // Use the interruptor if you need to cancel the fiber later. - * interrupt() + * output // => ["Started", "Success"] * ``` * * @category running @@ -8900,30 +8861,30 @@ export const runCallbackWith: ( * * **Example** (Running with a callback) * - * ```ts - * import { Console, Effect, Exit } from "effect" + * ```ts import.meta.vitest + * import { Effect, Exit } from "effect" + * const output: Array = [] * * const program = Effect.gen(function*() { - * yield* Console.log("working") + * yield* Effect.sync(() => { output.push("working") }) * return "done" * }) * - * const interrupt = Effect.runCallback(program, { - * onExit: (exit) => { - * Effect.runSync( - * Exit.match(exit, { - * onFailure: () => Console.log("failed"), - * onSuccess: (value) => Console.log(`success: ${value}`) - * }) - * ) - * } + * await new Promise((resolve) => { + * Effect.runCallback(program, { + * onExit: (exit) => { + * Effect.runSync( + * Exit.match(exit, { + * onFailure: () => Effect.sync(() => { output.push("failed") }), + * onSuccess: (value) => Effect.sync(() => { output.push(`success: ${value}`) }) + * }) + * ) + * resolve() + * } + * }) * }) * - * // Output: - * // working - * // success: done - * - * // interrupt() to cancel the fiber if needed + * output // => ["working", "success: done"] * ``` * * @category running @@ -8948,22 +8909,23 @@ export const runCallback: ( * * **Example** (Running a successful effect as a Promise) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * - * Effect.runPromise(Effect.succeed(1)).then(console.log) - * // Output: 1 + * await Effect.runPromise(Effect.succeed(1)) // => 1 * ``` * * **Example** (Running effects as promises) * - * ```ts + * ```ts import.meta.vitest * //Example: Handling a Failing Effect as a Rejected Promise * import { Effect } from "effect" + * const output: Array = [] * - * Effect.runPromise(Effect.fail("my error")).catch(console.error) - * // Output: - * // (FiberFailure) Error: my error + * await Effect.runPromise(Effect.fail("my error")).catch(() => { + * void output.push("rejected") + * }) + * output // => ["rejected"] * ``` * * @see {@link runPromiseExit} for a version that returns an `Exit` type instead of rejecting. @@ -8985,7 +8947,7 @@ export const runPromise: ( * * **Example** (Running with services as a promise) * - * ```ts + * ```ts import.meta.vitest * import { Context, Effect } from "effect" * * interface Config { @@ -9003,7 +8965,7 @@ export const runPromise: ( * return `Connecting to ${config.apiUrl}` * }) * - * Effect.runPromiseWith(context)(program).then(console.log) + * await Effect.runPromiseWith(context)(program) // => "Connecting to https://api.example.com" * ``` * * @category running @@ -9030,30 +8992,14 @@ export const runPromiseWith: ( * * **Example** (Observing promise results as Exit) * - * ```ts - * import { Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect, Exit } from "effect" * * // Execute a successful effect and get the Exit result as a Promise - * Effect.runPromiseExit(Effect.succeed(1)).then(console.log) - * // Output: - * // { - * // _id: "Exit", - * // _tag: "Success", - * // value: 1 - * // } + * await Effect.runPromiseExit(Effect.succeed(1)) // => Exit.succeed(1) * * // Execute a failing effect and get the Exit result as a Promise - * Effect.runPromiseExit(Effect.fail("my error")).then(console.log) - * // Output: - * // { - * // _id: "Exit", - * // _tag: "Failure", - * // cause: { - * // _id: "Cause", - * // _tag: "Fail", - * // failure: "my error" - * // } - * // } + * await Effect.runPromiseExit(Effect.fail("my error")) // => Exit.fail("my error") * ``` * * @see {@link runPromise} for a version that rejects on failure. @@ -9076,8 +9022,9 @@ export const runPromiseExit: ( * * **Example** (Running with services as an Exit promise) * - * ```ts + * ```ts import.meta.vitest * import { Context, Effect, Exit } from "effect" + * const output: Array = [] * * interface Database { * query: (sql: string) => string @@ -9094,11 +9041,11 @@ export const runPromiseExit: ( * return db.query("SELECT * FROM users") * }) * - * Effect.runPromiseExitWith(services)(program).then((exit) => { - * if (Exit.isSuccess(exit)) { - * console.log("Success:", exit.value) - * } - * }) + * const exit = await Effect.runPromiseExitWith(services)(program) + * if (Exit.isSuccess(exit)) { + * void output.push(`Success: ${exit.value}`) + * } + * output // => ["Success: Result for: SELECT * FROM users"] * ``` * * @category running @@ -9125,43 +9072,39 @@ export const runPromiseExitWith: ( * * **Example** (Running a synchronous effect) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" + * const output: Array = [] * * const program = Effect.sync(() => { - * console.log("Hello, World!") + * void output.push("Hello, World!") * return 1 * }) * * const result = Effect.runSync(program) - * // Output: Hello, World! - * - * console.log(result) - * // Output: 1 + * void output.push(result) + * output // => ["Hello, World!", 1] * ``` * * **Example** (Throwing for failed or async effects) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" + * const output: Array = [] * * try { * // Attempt to run an effect that fails * Effect.runSync(Effect.fail("my error")) * } catch (e) { - * console.error(e) + * void output.push("failed effect") * } - * // Output: - * // (FiberFailure) Error: my error - * * try { * // Attempt to run an effect that involves async work * Effect.runSync(Effect.promise(() => Promise.resolve(1))) * } catch (e) { - * console.error(e) + * void output.push("async effect") * } - * // Output: - * // (FiberFailure) AsyncFiberException: Fiber #0 cannot be resolved synchronously. This is caused by using runSync on an effect that performs async work + * output // => ["failed effect", "async effect"] * ``` * * @see {@link runSyncExit} for a version that returns an `Exit` type instead of @@ -9181,7 +9124,7 @@ export const runSync: (effect: Effect) => A = internal.runSync * * **Example** (Running synchronously with services) * - * ```ts + * ```ts import.meta.vitest * import { Context, Effect } from "effect" * * interface MathService { @@ -9200,7 +9143,7 @@ export const runSync: (effect: Effect) => A = internal.runSync * }) * * const result = Effect.runSyncWith(context)(program) - * console.log(result) // 5 + * result // => 5 * ``` * * @category running @@ -9231,50 +9174,25 @@ export const runSyncWith: ( * * **Example** (Observing synchronous results as Exit) * - * ```ts - * import { Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect, Exit } from "effect" + * + * Effect.runSyncExit(Effect.succeed(1)) // => Exit.succeed(1) * - * console.log(Effect.runSyncExit(Effect.succeed(1))) - * // Output: - * // { - * // _id: "Exit", - * // _tag: "Success", - * // value: 1 - * // } - * - * console.log(Effect.runSyncExit(Effect.fail("my error"))) - * // Output: - * // { - * // _id: "Exit", - * // _tag: "Failure", - * // cause: { - * // _id: "Cause", - * // _tag: "Fail", - * // failure: "my error" - * // } - * // } + * Effect.runSyncExit(Effect.fail("my error")) // => Exit.fail("my error") * ``` * * **Example** (Capturing async work as a Die cause) * - * ```ts - * import { Effect } from "effect" + * ```ts import.meta.vitest + * import { Cause, Effect, Exit } from "effect" + * + * const exit = Effect.runSyncExit(Effect.promise(() => Promise.resolve(1))) + * const isAsyncDie = Exit.hasDies(exit) && exit.cause.reasons.some( + * (reason) => Cause.isDieReason(reason) && Cause.isAsyncFiberError(reason.defect) + * ) * - * console.log(Effect.runSyncExit(Effect.promise(() => Promise.resolve(1)))) - * // Output: - * // { - * // _id: 'Exit', - * // _tag: 'Failure', - * // cause: { - * // _id: 'Cause', - * // _tag: 'Die', - * // defect: [Fiber #0 cannot be resolved synchronously. This is caused by using runSync on an effect that performs async work] { - * // fiber: [FiberRuntime], - * // _tag: 'AsyncFiberException', - * // name: 'AsyncFiberException' - * // } - * // } - * // } + * isAsyncDie // => true * ``` * * @see {@link runSync} for a version that throws on failure. @@ -9294,8 +9212,9 @@ export const runSyncExit: (effect: Effect) => Exit.Exit = inte * * **Example** (Running synchronously with services as Exit) * - * ```ts + * ```ts import.meta.vitest * import { Context, Effect, Exit } from "effect" + * const output: Array = [] * * // Define a logger service * const Logger = Context.Service<{ @@ -9310,19 +9229,17 @@ export const runSyncExit: (effect: Effect) => Exit.Exit = inte * * // Prepare context * const context = Context.make(Logger, { - * log: (msg) => console.log(`[LOG] ${msg}`) + * log: (msg) => void output.push(`[LOG] ${msg}`) * }) * * const exit = Effect.runSyncExitWith(context)(program) * * if (Exit.isSuccess(exit)) { - * console.log(`Success: ${exit.value}`) + * void output.push(`Success: ${exit.value}`) * } else { - * console.log(`Failure: ${exit.cause}`) + * void output.push(`Failure: ${exit.cause}`) * } - * // Output: - * // [LOG] Computing result... - * // Success: 42 + * output // => ["[LOG] Computing result...", "Success: 42"] * ``` * * @category running @@ -9356,7 +9273,7 @@ export declare namespace fn { * * **Example** (Annotating an Effect function) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * * const f = Effect.fnUntraced(function*( @@ -9368,11 +9285,12 @@ export declare namespace fn { * // ┌─── Effect.Effect * // ▼ * const program = f("hello") + * Effect.runSync(program) // => 5 * ``` * * **Example** (Annotating a parametric Effect function) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * * const f = Effect.fnUntraced(function*( @@ -9384,6 +9302,7 @@ export declare namespace fn { * // ┌─── Effect.Effect * // ▼ * const program = f("hello") + * Effect.runSync(program) // => "hello" * ``` * * @category utility types @@ -13446,7 +13365,7 @@ export declare namespace fn { * * **Example** (Defining untraced effect functions) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * * const f = Effect.fnUntraced(function*( @@ -13458,11 +13377,12 @@ export declare namespace fn { * // ┌─── Effect.Effect * // ▼ * const program = f("hello") + * Effect.runSync(program) // => 5 * ``` * * **Example** (Transforming the returned Effect) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * * const f = Effect.fnUntraced( @@ -13476,11 +13396,12 @@ export declare namespace fn { * // ┌─── Effect.Effect * // ▼ * const program = f("hello") + * Effect.runSync(program) // => "hello: 5" * ``` * * **Example** (Annotating an untraced non-parametric function) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * * const f = Effect.fnUntraced(function*( @@ -13492,11 +13413,12 @@ export declare namespace fn { * // ┌─── Effect.Effect * // ▼ * const program = f("hello") + * Effect.runSync(program) // => 5 * ``` * * **Example** (Annotating an untraced parametric function) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * * const f = Effect.fnUntraced(function*( @@ -13508,9 +13430,10 @@ export declare namespace fn { * // ┌─── Effect.Effect * // ▼ * const program = f("hello") + * Effect.runSync(program) // => "hello" * ``` * - * @category functions + * @category constructors * @since 3.12.0 */ export const fnUntraced: fn.Untraced = internal.fnUntraced @@ -13543,7 +13466,7 @@ export const fnUntraced: fn.Untraced = internal.fnUntraced * * **Example** (Defining traced effect functions) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * * const f = Effect.fn("calculateLength")(function*(value: string) { @@ -13553,11 +13476,12 @@ export const fnUntraced: fn.Untraced = internal.fnUntraced * // ┌─── Effect.Effect * // ▼ * const program = f("hello") + * Effect.runSync(program) // => 5 * ``` * * **Example** (Transforming the returned Effect) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * * const f = Effect.fn("formatLength")( @@ -13571,11 +13495,12 @@ export const fnUntraced: fn.Untraced = internal.fnUntraced * // ┌─── Effect.Effect * // ▼ * const program = f("hello") + * Effect.runSync(program) // => "hello: 5" * ``` * * **Example** (Binding this) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * * class Counter { @@ -13595,11 +13520,12 @@ export const fnUntraced: fn.Untraced = internal.fnUntraced * // ┌─── Effect.Effect * // ▼ * const program = counter.increment(1) + * Effect.runSync(program) // => 1 * ``` * * **Example** (Annotating a traced non-parametric function) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * * const f = Effect.fn("calculateLength")(function*( @@ -13611,11 +13537,12 @@ export const fnUntraced: fn.Untraced = internal.fnUntraced * // ┌─── Effect.Effect * // ▼ * const program = f("hello") + * Effect.runSync(program) // => 5 * ``` * * **Example** (Annotating a traced parametric function) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * * const f = Effect.fn("succeed")(function*( @@ -13627,9 +13554,10 @@ export const fnUntraced: fn.Untraced = internal.fnUntraced * // ┌─── Effect.Effect * // ▼ * const program = f("hello") + * Effect.runSync(program) // => "hello" * ``` * - * @category functions + * @category constructors * @since 3.11.0 */ export const fn: fn.Traced & { @@ -13646,22 +13574,19 @@ export const fn: fn.Traced & { * * **Example** (Accessing the Clock service) * - * ```ts - * import { Console, Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect } from "effect" * * const program = Effect.clockWith((clock) => * clock.currentTimeMillis.pipe( - * Effect.map((currentTime) => `Current time is: ${currentTime}`), - * Effect.tap(Console.log) + * Effect.map(() => "Clock is available") * ) * ) * - * Effect.runFork(program) - * // Example Output: - * // Current time is: 1735484929744 + * Effect.runSync(program) // => "Clock is available" * ``` * - * @category clock + * @category accessors * @since 2.0.0 */ export const clockWith: ( @@ -13682,14 +13607,24 @@ export const clockWith: ( * * **Example** (Logging at a dynamic level) * - * ```ts - * import { Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect, Logger, References } from "effect" + * const output: Array = [] * * const logWarn = Effect.logWithLevel("Warn") * * const program = Effect.gen(function*() { - * yield* logWarn("Cache miss", { key: "user:1" }) + * yield* logWarn("Cache miss") * }) + * const logger = Logger.make(({ logLevel, message }) => { + * void output.push(`${logLevel}: ${Array.isArray(message) ? message.map(String).join(" ") : String(message)}`) + * }) + * const runnable = program.pipe( + * Effect.provideService(References.MinimumLogLevel, "Debug"), + * Effect.provide(Logger.layer([logger])) + * ) + * Effect.runSync(runnable) + * output // => ["Warn: Cache miss"] * ``` * * @category logging @@ -13703,23 +13638,22 @@ export const logWithLevel: (level?: Severity) => (...message: ReadonlyArray * * **Example** (Logging at the default level) * - * ```ts - * import { Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect, Logger } from "effect" + * const output: Array = [] * * const program = Effect.gen(function*() { - * yield* Effect.log("Starting computation") * const result = 2 + 2 * yield* Effect.log("Result:", result) - * yield* Effect.log("Multiple", "values", "can", "be", "logged") * return result * }) * - * Effect.runPromise(program).then(console.log) - * // Output: - * // timestamp=2023-... level=INFO message="Starting computation" - * // timestamp=2023-... level=INFO message="Result: 4" - * // timestamp=2023-... level=INFO message="Multiple values can be logged" - * // 4 + * const logger = Logger.make(({ logLevel, message }) => { + * void output.push(`${logLevel}: ${Array.isArray(message) ? message.map(String).join(" ") : String(message)}`) + * }) + * const runnable = Effect.provide(program, Logger.layer([logger])) + * void output.push(Effect.runSync(runnable)) + * output // => ["Info: Result: 4", 4] * ``` * * @category logging @@ -13732,24 +13666,20 @@ export const log: (...message: ReadonlyArray) => Effect = internal.lo * * **Example** (Logging fatal messages) * - * ```ts - * import { Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect, Logger } from "effect" + * const output: Array = [] * * const program = Effect.gen(function*() { - * try { - * // Simulate a critical system failure - * throw new Error("System memory exhausted") - * } catch (error) { - * const errorMessage = error instanceof Error ? error.message : String(error) - * yield* Effect.logFatal("Critical system failure:", errorMessage) - * yield* Effect.logFatal("System shutting down") - * } + * yield* Effect.logFatal("Critical system failure") * }) * - * Effect.runPromise(program) - * // Output: - * // timestamp=2023-... level=FATAL message="Critical system failure: System memory exhausted" - * // timestamp=2023-... level=FATAL message="System shutting down" + * const logger = Logger.make(({ logLevel, message }) => { + * void output.push(`${logLevel}: ${Array.isArray(message) ? message.map(String).join(" ") : String(message)}`) + * }) + * const runnable = Effect.provide(program, Logger.layer([logger])) + * Effect.runSync(runnable) + * output // => ["Fatal: Critical system failure"] * ``` * * @category logging @@ -13762,25 +13692,19 @@ export const logFatal: (...message: ReadonlyArray) => Effect = intern * * **Example** (Logging warnings) * - * ```ts - * import { Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect, Logger } from "effect" + * const output: Array = [] * * const program = Effect.gen(function*() { * yield* Effect.logWarning("API rate limit approaching") - * yield* Effect.logWarning("Retries remaining:", 2, "Operation:", "fetchData") - * - * // Useful for non-critical issues - * const deprecated = true - * if (deprecated) { - * yield* Effect.logWarning("Using deprecated API endpoint") - * } * }) * - * Effect.runPromise(program) - * // Output: - * // timestamp=2023-... level=WARN message="API rate limit approaching" - * // timestamp=2023-... level=WARN message="Retries remaining: 2 Operation: fetchData" - * // timestamp=2023-... level=WARN message="Using deprecated API endpoint" + * const logger = Logger.make(({ logLevel, message }) => { + * void output.push(`${logLevel}: ${Array.isArray(message) ? message.map(String).join(" ") : String(message)}`) + * }) + * Effect.runSync(Effect.provide(program, Logger.layer([logger]))) + * output // => ["Warn: API rate limit approaching"] * ``` * * @category logging @@ -13793,28 +13717,19 @@ export const logWarning: (...message: ReadonlyArray) => Effect = inte * * **Example** (Logging errors) * - * ```ts - * import { Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect, Logger } from "effect" + * const output: Array = [] * * const program = Effect.gen(function*() { * yield* Effect.logError("Database connection failed") - * yield* Effect.logError( - * "Error code:", - * 500, - * "Message:", - * "Internal server error" - * ) - * - * // Can be used with error objects - * const error = new Error("Something went wrong") - * yield* Effect.logError("Caught error:", error.message) * }) * - * Effect.runPromise(program) - * // Output: - * // timestamp=2023-... level=ERROR message="Database connection failed" - * // timestamp=2023-... level=ERROR message="Error code: 500 Message: Internal server error" - * // timestamp=2023-... level=ERROR message="Caught error: Something went wrong" + * const logger = Logger.make(({ logLevel, message }) => { + * void output.push(`${logLevel}: ${Array.isArray(message) ? message.map(String).join(" ") : String(message)}`) + * }) + * Effect.runSync(Effect.provide(program, Logger.layer([logger]))) + * output // => ["Error: Database connection failed"] * ``` * * @category logging @@ -13827,23 +13742,19 @@ export const logError: (...message: ReadonlyArray) => Effect = intern * * **Example** (Logging information) * - * ```ts - * import { Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect, Logger } from "effect" + * const output: Array = [] * * const program = Effect.gen(function*() { * yield* Effect.logInfo("Application starting up") - * yield* Effect.logInfo("Config loaded:", "production", "Port:", 3000) - * - * // Useful for general information - * const version = "1.2.3" - * yield* Effect.logInfo("Application version:", version) * }) * - * Effect.runPromise(program) - * // Output: - * // timestamp=2023-... level=INFO message="Application starting up" - * // timestamp=2023-... level=INFO message="Config loaded: production Port: 3000" - * // timestamp=2023-... level=INFO message="Application version: 1.2.3" + * const logger = Logger.make(({ logLevel, message }) => { + * void output.push(`${logLevel}: ${Array.isArray(message) ? message.map(String).join(" ") : String(message)}`) + * }) + * Effect.runSync(Effect.provide(program, Logger.layer([logger]))) + * output // => ["Info: Application starting up"] * ``` * * @category logging @@ -13856,24 +13767,23 @@ export const logInfo: (...message: ReadonlyArray) => Effect = interna * * **Example** (Logging debug messages) * - * ```ts - * import { Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect, Logger, References } from "effect" + * const output: Array = [] * * const program = Effect.gen(function*() { * yield* Effect.logDebug("Debug mode enabled") - * - * const userInput = { name: "Alice", age: 30 } - * yield* Effect.logDebug("Processing user input:", userInput) - * - * // Useful for detailed diagnostic information - * yield* Effect.logDebug("Variable state:", "x=10", "y=20", "z=30") * }) * - * Effect.runPromise(program) - * // Output: - * // timestamp=2023-... level=DEBUG message="Debug mode enabled" - * // timestamp=2023-... level=DEBUG message="Processing user input: [object Object]" - * // timestamp=2023-... level=DEBUG message="Variable state: x=10 y=20 z=30" + * const logger = Logger.make(({ logLevel, message }) => { + * void output.push(`${logLevel}: ${Array.isArray(message) ? message.map(String).join(" ") : String(message)}`) + * }) + * const runnable = program.pipe( + * Effect.provideService(References.MinimumLogLevel, "Debug"), + * Effect.provide(Logger.layer([logger])) + * ) + * Effect.runSync(runnable) + * output // => ["Debug: Debug mode enabled"] * ``` * * @category logging @@ -13886,27 +13796,23 @@ export const logDebug: (...message: ReadonlyArray) => Effect = intern * * **Example** (Logging trace messages) * - * ```ts - * import { Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect, Logger, References } from "effect" + * const output: Array = [] * * const program = Effect.gen(function*() { * yield* Effect.logTrace("Entering function processData") - * - * // Trace detailed execution flow - * for (let i = 0; i < 3; i++) { - * yield* Effect.logTrace("Loop iteration:", i, "Processing item") - * } - * - * yield* Effect.logTrace("Exiting function processData") * }) * - * Effect.runPromise(program) - * // Output: - * // timestamp=2023-... level=TRACE message="Entering function processData" - * // timestamp=2023-... level=TRACE message="Loop iteration: 0 Processing item" - * // timestamp=2023-... level=TRACE message="Loop iteration: 1 Processing item" - * // timestamp=2023-... level=TRACE message="Loop iteration: 2 Processing item" - * // timestamp=2023-... level=TRACE message="Exiting function processData" + * const logger = Logger.make(({ logLevel, message }) => { + * void output.push(`${logLevel}: ${Array.isArray(message) ? message.map(String).join(" ") : String(message)}`) + * }) + * const runnable = program.pipe( + * Effect.provideService(References.MinimumLogLevel, "Trace"), + * Effect.provide(Logger.layer([logger])) + * ) + * Effect.runSync(runnable) + * output // => ["Trace: Entering function processData"] * ``` * * @category logging @@ -13919,12 +13825,13 @@ export const logTrace: (...message: ReadonlyArray) => Effect = intern * * **Example** (Adding a logger to an effect) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Logger } from "effect" + * const output: Array = [] * * // Create a custom logger that logs to the console - * const customLogger = Logger.make(({ message }) => - * Effect.sync(() => console.log(`[CUSTOM]: ${message}`)) + * const customLogger = Logger.make(({ message }) => + * void output.push(`[CUSTOM]: ${Array.isArray(message) ? message.map(String).join(" ") : String(message)}`) * ) * * const program = Effect.gen(function*() { @@ -13935,8 +13842,8 @@ export const logTrace: (...message: ReadonlyArray) => Effect = intern * // Add the custom logger to the effect * const programWithLogger = Effect.withLogger(program, customLogger) * - * Effect.runPromise(programWithLogger) - * // Output includes both default and custom log outputs + * Effect.runSync(Effect.provide(programWithLogger, Logger.layer([]))) + * output // => ["[CUSTOM]: This will go to both default and custom logger"] * ``` * * @category logging @@ -13962,13 +13869,12 @@ export const withLogger = dual< * * **Example** (Adding log annotations) * - * ```ts - * import { Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect, Logger } from "effect" + * const output: Array = [] * * const program = Effect.gen(function*() { * yield* Effect.log("Starting operation") - * yield* Effect.log("Processing data") - * yield* Effect.log("Operation completed") * }) * * // Add annotations to all log messages @@ -13980,8 +13886,14 @@ export const withLogger = dual< * // Also supports single key-value annotations * const singleAnnotated = Effect.annotateLogs(program, "requestId", "req-456") * - * Effect.runPromise(annotatedProgram) - * // All log messages will include the userId and operation annotations + * const logger = Logger.make(({ message }) => + * void output.push(Array.isArray(message) ? message.join(" ") : String(message)) + * ) + * const run = (effect: Effect.Effect) => + * Effect.runSync(Effect.provide(effect, Logger.layer([logger]))) + * run(annotatedProgram) + * run(singleAnnotated) + * output // => ["Starting operation", "Starting operation"] * ``` * * @category logging @@ -14015,11 +13927,11 @@ export const annotateLogs = dual< ...args: [Record] | [key: string, value: unknown] ): Effect => internal.updateService(effect, CurrentLogAnnotations, (annotations) => { - const newAnnotations = { ...annotations } + const newAnnotations = args.length === 1 ? { ...annotations, ...args[0] } : { ...annotations } if (args.length === 1) { - Object.assign(newAnnotations, args[0]) + return newAnnotations } else { - newAnnotations[args[0]] = args[1] + InternalRecord.assignProperty(newAnnotations, args[0], args[1]) } return newAnnotations }) @@ -14040,8 +13952,9 @@ export const annotateLogs = dual< * * **Example** (Adding scoped log annotations) * - * ```ts - * import { Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect, Logger } from "effect" + * const output: Array = [] * * const program = Effect.scoped( * Effect.gen(function*() { @@ -14051,7 +13964,11 @@ export const annotateLogs = dual< * }) * ) * - * Effect.runPromise(program) + * const logger = Logger.make(({ message }) => + * void output.push(Array.isArray(message) ? message.join(" ") : String(message)) + * ) + * Effect.runSync(Effect.provide(program, Logger.layer([logger]))) + * output // => ["before", "inside scope"] * ``` * * @see {@link annotateLogs} for annotating one effect @@ -14069,8 +13986,9 @@ export const annotateLogsScoped: { * * **Example** (Adding a log span) * - * ```ts - * import { Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect, Logger } from "effect" + * const output: Array = [] * * const databaseOperation = Effect.gen(function*() { * yield* Effect.log("Connecting to database") @@ -14088,8 +14006,11 @@ export const annotateLogsScoped: { * * const program = Effect.withLogSpan(httpRequest, "http-handler") * - * Effect.runPromise(program) - * // All log messages will include span information showing the nested operation context + * const logger = Logger.make(({ message }) => + * void output.push(Array.isArray(message) ? message.join(" ") : String(message)) + * ) + * void output.push(Effect.runSync(Effect.provide(program, Logger.layer([logger])))) + * output // => ["Making HTTP request", "Connecting to database", "Executing query", "Processing results", "Sending response", "data"] * ``` * * @category logging @@ -14122,7 +14043,7 @@ export const withLogSpan = dual< * * **Example** (Incrementing a metric for each execution) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Metric } from "effect" * * const counter = Metric.counter("effect_executions", { @@ -14133,16 +14054,13 @@ export const withLogSpan = dual< * Effect.track(counter) * ) * - * // This will increment the counter by 1 when executed - * Effect.runPromise(program).then(() => - * Effect.runPromise(Metric.value(counter)).then(console.log) - * // Output: { count: 1, incremental: false } - * ) + * Effect.runSync(program) + * Effect.runSync(Metric.value(counter)).count // => 1 * ``` * * **Example** (Mapping exits before updating a metric) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Exit, Metric } from "effect" * * // Track different exit types with custom mapping @@ -14159,9 +14077,11 @@ export const withLogSpan = dual< * const effect = Effect.succeed("result").pipe( * Effect.track(exitTracker, mapExitToString) * ) + * Effect.runSync(effect) + * Effect.runSync(Metric.value(exitTracker)).occurrences.get("success") // => 1 * ``` * - * @category tracking + * @category metrics * @since 4.0.0 */ export const track: { @@ -14205,7 +14125,7 @@ export const track: { * * **Example** (Counting successful results) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Metric } from "effect" * * const successCounter = Metric.counter("successes").pipe( @@ -14216,15 +14136,13 @@ export const track: { * Effect.trackSuccesses(successCounter) * ) * - * Effect.runPromise(program).then(() => - * Effect.runPromise(Metric.value(successCounter)).then(console.log) - * // Output: { count: 1, incremental: false } - * ) + * Effect.runSync(program) + * Effect.runSync(Metric.value(successCounter)).count // => 1 * ``` * * **Example** (Mapping successes before tracking) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Metric } from "effect" * * // Track successful request sizes @@ -14234,13 +14152,11 @@ export const track: { * Effect.trackSuccesses(requestSizeGauge, (value: string) => value.length) * ) * - * Effect.runPromise(program).then(() => - * Effect.runPromise(Metric.value(requestSizeGauge)).then(console.log) - * // Output: { value: 12 } - * ) + * Effect.runSync(program) + * Effect.runSync(Metric.value(requestSizeGauge)).value // => 12 * ``` * - * @category tracking + * @category metrics * @since 4.0.0 */ export const trackSuccesses: { @@ -14284,7 +14200,7 @@ export const trackSuccesses: { * * **Example** (Counting expected failures) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Metric } from "effect" * * const errorCounter = Metric.counter("errors").pipe( @@ -14295,15 +14211,13 @@ export const trackSuccesses: { * Effect.trackErrors(errorCounter) * ) * - * Effect.runPromiseExit(program).then(() => - * Effect.runPromise(Metric.value(errorCounter)).then(console.log) - * // Output: { count: 1, incremental: false } - * ) + * Effect.runSyncExit(program) + * Effect.runSync(Metric.value(errorCounter)).count // => 1 * ``` * * **Example** (Mapping errors before tracking) * - * ```ts + * ```ts import.meta.vitest * import { Data, Effect, Metric } from "effect" * * class ConnectionFailedError extends Data.TaggedError("ConnectionFailedError")<{}> {} @@ -14315,13 +14229,11 @@ export const trackSuccesses: { * Effect.trackErrors(errorTypeFrequency, (error: ConnectionFailedError) => error._tag) * ) * - * Effect.runPromiseExit(program).then(() => - * Effect.runPromise(Metric.value(errorTypeFrequency)).then(console.log) - * // Output: { occurrences: Map(1) { "ConnectionFailedError" => 1 } } - * ) + * Effect.runSyncExit(program) + * Effect.runSync(Metric.value(errorTypeFrequency)).occurrences.get("ConnectionFailedError") // => 1 * ``` * - * @category tracking + * @category metrics * @since 4.0.0 */ export const trackErrors: { @@ -14365,7 +14277,7 @@ export const trackErrors: { * * **Example** (Counting defects) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Metric } from "effect" * * const defectCounter = Metric.counter("defects").pipe( @@ -14376,15 +14288,13 @@ export const trackErrors: { * Effect.trackDefects(defectCounter) * ) * - * Effect.runPromiseExit(program).then(() => - * Effect.runPromise(Metric.value(defectCounter)).then(console.log) - * // Output: { count: 1, incremental: false } - * ) + * Effect.runSyncExit(program) + * Effect.runSync(Metric.value(defectCounter)).count // => 1 * ``` * * **Example** (Mapping defects before tracking) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Metric } from "effect" * * // Track defect types using frequency metric @@ -14397,13 +14307,11 @@ export const trackErrors: { * }) * ) * - * Effect.runPromiseExit(program).then(() => - * Effect.runPromise(Metric.value(defectTypeFrequency)).then(console.log) - * // Output: { occurrences: Map(1) { "Error" => 1 } } - * ) + * Effect.runSyncExit(program) + * Effect.runSync(Metric.value(defectTypeFrequency)).occurrences.get("Error") // => 1 * ``` * - * @category tracking + * @category metrics * @since 4.0.0 */ export const trackDefects: { @@ -14444,40 +14352,36 @@ export const trackDefects: { * * **Example** (Recording execution duration) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Metric } from "effect" * * const executionTimer = Metric.timer("execution_time") * - * const program = Effect.sleep("100 millis").pipe( + * const program = Effect.succeed("done").pipe( * Effect.trackDuration(executionTimer) * ) * - * Effect.runPromise(program).then(() => - * Effect.runPromise(Metric.value(executionTimer)).then(console.log) - * // Output: { count: 1, min: 100000000, max: 100000000, sum: 100000000 } - * ) + * Effect.runSync(program) + * Effect.runSync(Metric.value(executionTimer)).count // => 1 * ``` * * **Example** (Mapping duration before tracking) * - * ```ts - * import { Duration, Effect, Metric } from "effect" + * ```ts import.meta.vitest + * import { Effect, Metric } from "effect" * * // Track execution time in milliseconds using custom mapping * const durationGauge = Metric.gauge("execution_millis") * - * const program = Effect.sleep("200 millis").pipe( - * Effect.trackDuration(durationGauge, (duration) => Duration.toMillis(duration)) + * const program = Effect.succeed("done").pipe( + * Effect.trackDuration(durationGauge, () => 1) * ) * - * Effect.runPromise(program).then(() => - * Effect.runPromise(Metric.value(durationGauge)).then(console.log) - * // Output: { value: 200 } - * ) + * Effect.runSync(program) + * Effect.runSync(Metric.value(durationGauge)).value // => 1 * ``` * - * @category tracking + * @category metrics * @since 4.0.0 */ export const trackDuration: { @@ -14505,9 +14409,9 @@ export const trackDuration: { f: ((duration: Duration.Duration) => Input) | undefined ): Effect => clockWith((clock) => { - const startTime = clock.currentTimeNanosUnsafe() + const startTime = clock.monotonicTimeNanosUnsafe() return onExit(self, () => { - const endTime = clock.currentTimeNanosUnsafe() + const endTime = clock.monotonicTimeNanosUnsafe() const duration = Duration.subtract( Duration.fromInputUnsafe(endTime), Duration.fromInputUnsafe(startTime) @@ -14532,7 +14436,7 @@ export const trackDuration: { * * **Example** (Building transactions) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * * // Transaction class for software transactional memory operations @@ -14541,9 +14445,15 @@ export const trackDuration: { * // Use transaction for coordinated state changes * return "Transaction complete" * }) + * + * const runnable = Effect.provideService(txEffect, Effect.Transaction, { + * retry: false, + * journal: new Map() + * }) + * Effect.runSync(runnable) // => "Transaction complete" * ``` * - * @category transactions + * @category services * @since 4.0.0 */ export class Transaction extends Context.Service< @@ -14579,8 +14489,9 @@ export class Transaction extends Context.Service< * * **Example** (Running a transaction) * - * ```ts + * ```ts import.meta.vitest * import { Effect, TxRef } from "effect" + * const output: Array = [] * * const program = Effect.gen(function*() { * const ref1 = yield* TxRef.make(0) @@ -14591,12 +14502,15 @@ export class Transaction extends Context.Service< * yield* TxRef.set(ref1, 10) * yield* Effect.tx(TxRef.set(ref2, 20)) * const sum = (yield* TxRef.get(ref1)) + (yield* TxRef.get(ref2)) - * console.log(`Transaction sum: ${sum}`) + * void output.push(`Transaction sum: ${sum}`) * })) * - * console.log(`Final ref1: ${yield* TxRef.get(ref1)}`) // 10 - * console.log(`Final ref2: ${yield* TxRef.get(ref2)}`) // 20 + * void output.push(`Final ref1: ${yield* TxRef.get(ref1)}`) + * void output.push(`Final ref2: ${yield* TxRef.get(ref2)}`) * }) + * + * Effect.runSync(program) + * output // => ["Transaction sum: 30", "Final ref1: 10", "Final ref2: 20"] * ``` * * @category transactions @@ -14700,31 +14614,28 @@ function clearTransaction(state: Transaction["Service"]) { * * **Example** (Retrying transactions) * - * ```ts - * import { Effect, TxRef } from "effect" + * ```ts import.meta.vitest + * import { Deferred, Effect, TxRef } from "effect" * * const program = Effect.gen(function*() { - * // create a transactional reference * const ref = yield* TxRef.make(0) + * const update = yield* Deferred.make() * - * // forks a fiber that increases the value of `ref` every 100 millis - * yield* Effect.forkChild(Effect.forever( - * // update to transactional value - * Effect.tx(TxRef.update(ref, (n) => n + 1)).pipe(Effect.delay("100 millis")) - * )) + * yield* Effect.forkChild( + * Deferred.await(update).pipe(Effect.andThen(Effect.tx(TxRef.set(ref, 1)))) + * ) * - * // the following will retry 10 times until the `ref` value is 10 - * yield* Effect.tx(Effect.gen(function*() { + * return yield* Effect.tx(Effect.gen(function*() { * const value = yield* TxRef.get(ref) - * if (value < 10) { - * yield* Effect.log(`retry due to value: ${value}`) + * if (value === 0) { + * yield* Deferred.succeed(update, undefined) * return yield* Effect.txRetry * } - * yield* Effect.log(`transaction done with value: ${value}`) + * return value * })) * }) * - * Effect.runPromise(program).catch(console.error) + * await Effect.runPromise(program) // => 1 * ``` * * @category transactions @@ -14754,7 +14665,7 @@ export declare namespace Effectify { /** * Converts a callback-based function type into an `Effect`-returning function type. * - * @category effectify + * @category utility types * @since 4.0.0 */ export type Effectify = T extends { @@ -14902,7 +14813,7 @@ export declare namespace Effectify { /** * Extracts the callback error type from a callback-based function type. * - * @category effectify + * @category utility types * @since 4.0.0 */ export type EffectifyError = T extends { @@ -14997,38 +14908,42 @@ export declare namespace Effectify { * * **Example** (Converting callbacks to effects) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" - * import * as fs from "fs" * - * // Convert Node.js readFile to an Effect - * const readFile = Effect.effectify(fs.readFile) + * const uppercase = ( + * input: string, + * callback: (error: Error | null, value?: string) => void + * ) => queueMicrotask(() => callback(null, input.toUpperCase())) * - * // Use the effectified function - * const program = readFile("package.json", "utf8") + * const effectfulUppercase = Effect.effectify(uppercase) + * const program = effectfulUppercase("hello") * - * Effect.runPromise(program).then(console.log) - * // Output: contents of package.json + * await Effect.runPromise(program) // => "HELLO" * ``` * * **Example** (Mapping callback errors to typed failures) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" - * import * as fs from "fs" * - * const readFile = Effect.effectify( - * fs.readFile, - * (error, args) => new Error(`Failed to read file ${args[0]}: ${error.message}`) + * const fail = ( + * input: string, + * callback: (error: Error | null, value?: string) => void + * ) => queueMicrotask(() => callback(new Error("unavailable"))) + * + * const effectfulFail = Effect.effectify( + * fail, + * (error, args) => new Error(`Failed to process ${args[0]}: ${error.message}`) * ) * - * const program = readFile("nonexistent.txt", "utf8") + * const program = Effect.flip(effectfulFail("hello")) * - * Effect.runPromiseExit(program).then(console.log) - * // Output: Exit.failure with custom error message + * const error = await Effect.runPromise(program) + * error.message // => "Failed to process hello: unavailable" * ``` * - * @category effectify + * @category converting * @since 4.0.0 */ export const effectify: { @@ -15073,7 +14988,7 @@ export const effectify: { * * **Example** (Constraining the success type) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * * // Define a constraint that the success type must be a number @@ -15081,6 +14996,7 @@ export const effectify: { * * // This works - Effect<42, never, never> extends Effect * const validEffect = satisfiesNumber(Effect.succeed(42)) + * Effect.runSync(validEffect) // => 42 * * // This would cause a TypeScript compilation error: * // const invalidEffect = satisfiesNumber(Effect.succeed("string")) @@ -15103,7 +15019,7 @@ export const satisfiesSuccessType = () => (effect: Effect * * **Example** (Constraining the error type) * - * ```ts + * ```ts import.meta.vitest * import { Data, Effect } from "effect" * * class ValidationError extends Data.TaggedError("ValidationError")<{}> {} @@ -15113,6 +15029,7 @@ export const satisfiesSuccessType = () => (effect: Effect * * // This works - Effect extends the constrained type * const validEffect = satisfiesError(Effect.fail(new ValidationError())) + * Effect.runSync(Effect.flip(validEffect))._tag // => "ValidationError" * * // This would cause a TypeScript compilation error: * // const invalidEffect = satisfiesError(Effect.fail("string error")) @@ -15135,7 +15052,7 @@ export const satisfiesErrorType = () => (effect: Effect() => (effect: Effec * * **Example** (Mapping already completed effects) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * * // For resolved effects, the mapping is applied immediately @@ -15178,11 +15095,13 @@ export const satisfiesServicesType = () => (effect: Effec * const mapped = Effect.mapEager(resolved, (n) => n * 2) // Applied eagerly * * // For pending effects, behaves like regular map - * const pending = Effect.delay(Effect.succeed(5), "100 millis") + * const pending = Effect.delay(Effect.succeed(5), 0) * const mappedPending = Effect.mapEager(pending, (n) => n * 2) // Uses regular map + * + * await Effect.runPromise(Effect.all([mapped, mappedPending])) // => [10, 10] * ``` * - * @category eager + * @category mapping * @since 4.0.0 */ export const mapEager: { @@ -15207,22 +15126,29 @@ export const mapEager: { * * **Example** (Mapping errors eagerly when possible) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" + * const output: Array = [] * * // For resolved failure effects, the error mapping is applied immediately * const failed = Effect.fail("original error") * const mapped = Effect.mapErrorEager(failed, (err: string) => `mapped: ${err}`) // Applied eagerly * * // For pending effects, behaves like regular mapError - * const pending = Effect.delay(Effect.fail("error"), "100 millis") + * const pending = Effect.delay(Effect.fail("error"), 0) * const mappedPending = Effect.mapErrorEager( * pending, * (err: string) => `mapped: ${err}` * ) // Uses regular mapError + * + * void output.push(await Effect.runPromise(Effect.all([ + * Effect.flip(mapped), + * Effect.flip(mappedPending) + * ]))) + * output // => [['mapped: original error', 'mapped: error']] * ``` * - * @category eager + * @category error handling * @since 4.0.0 */ export const mapErrorEager: { @@ -15246,8 +15172,9 @@ export const mapErrorEager: { * * **Example** (Mapping both channels eagerly when possible) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" + * const output: Array = [] * * // For resolved effects, the appropriate mapping is applied immediately * const success = Effect.succeed(5) @@ -15261,9 +15188,13 @@ export const mapErrorEager: { * onFailure: (err: string) => `Failed: ${err}`, * onSuccess: (n: number) => n * 2 * }) // onFailure applied eagerly + * + * void output.push(Effect.runSync(mapped)) + * void output.push(Effect.runSync(Effect.flip(mappedError))) + * output // => [10, "Failed: error"] * ``` * - * @category eager + * @category mapping * @since 4.0.0 */ export const mapBothEager: { @@ -15292,7 +15223,7 @@ export const mapBothEager: { * * **Example** (Flat mapping eagerly when possible) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * * // For resolved effects, the flatMap is applied immediately @@ -15300,14 +15231,16 @@ export const mapBothEager: { * const flatMapped = Effect.flatMapEager(resolved, (n) => Effect.succeed(n * 2)) // Applied eagerly * * // For pending effects, behaves like regular flatMap - * const pending = Effect.delay(Effect.succeed(5), "100 millis") + * const pending = Effect.delay(Effect.succeed(5), 0) * const flatMappedPending = Effect.flatMapEager( * pending, * (n) => Effect.succeed(n * 2) * ) // Uses regular flatMap + * + * await Effect.runPromise(Effect.all([flatMapped, flatMappedPending])) // => [10, 10] * ``` * - * @category eager + * @category sequencing * @since 4.0.0 */ export const flatMapEager: { @@ -15331,8 +15264,9 @@ export const flatMapEager: { * * **Example** (Catching failures eagerly when possible) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" + * const output: Array = [] * * // For resolved failure effects, the catch function is applied immediately * const failed = Effect.fail("original error") @@ -15349,14 +15283,21 @@ export const flatMapEager: { * ) // Returns success as-is * * // For pending effects, behaves like regular catch - * const pending = Effect.delay(Effect.fail("error"), "100 millis") + * const pending = Effect.delay(Effect.fail("error"), 0) * const recoveredPending = Effect.catchEager( * pending, * (err: string) => Effect.succeed(`recovered from: ${err}`) * ) // Uses regular catch + * + * void output.push(await Effect.runPromise(Effect.all([ + * recovered, + * unchanged, + * recoveredPending + * ]))) + * output // => [['recovered from: original error', 42, 'recovered from: error']] * ``` * - * @category eager + * @category error handling * @since 4.0.0 */ export const catchEager: { @@ -15379,7 +15320,7 @@ export const catchEager: { * * **Example** (Defining eager untraced effect functions) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * * const computation = Effect.fnUntracedEager(function*() { @@ -15389,9 +15330,10 @@ export const catchEager: { * }) * * const effect = computation() // Executed immediately if all effects are sync + * Effect.runSync(effect) // => "computed eagerly" * ``` * - * @category eager + * @category constructors * @since 4.0.0 */ export const fnUntracedEager: fn.Untraced = internal.fnUntracedEager diff --git a/repos/effect/packages/effect/src/Encoding.ts b/repos/effect/packages/effect/src/Encoding.ts index 11885fb90d..918f05e550 100644 --- a/repos/effect/packages/effect/src/Encoding.ts +++ b/repos/effect/packages/effect/src/Encoding.ts @@ -64,7 +64,7 @@ export type EncodingErrorTypeId = typeof EncodingErrorTypeId * message. * * @see {@link isEncodingError} for checking whether a value is an EncodingError - * @category constructors + * @category errors * @since 4.0.0 */ export class EncodingError extends Data.TaggedError("EncodingError")<{ @@ -126,15 +126,15 @@ export const isEncodingError = (u: unknown): u is EncodingError => hasProperty(u * * **Example** (Encoding Base64 strings and bytes) * - * ```ts + * ```ts import.meta.vitest * import { Encoding } from "effect" * * // Encode a string - * console.log(Encoding.encodeBase64("hello")) // "aGVsbG8=" + * Encoding.encodeBase64("hello") // => "aGVsbG8=" * * // Encode binary data * const bytes = new Uint8Array([72, 101, 108, 108, 111]) - * console.log(Encoding.encodeBase64(bytes)) // "SGVsbG8=" + * Encoding.encodeBase64(bytes) // => "SGVsbG8=" * ``` * * @see {@link decodeBase64} for decoding standard Base64 to bytes @@ -162,13 +162,10 @@ export const encodeBase64: (input: Uint8Array | string) => string = (input) => * * **Example** (Decoding Base64 bytes) * - * ```ts + * ```ts import.meta.vitest * import { Encoding, Result } from "effect" * - * const result = Encoding.decodeBase64("SGVsbG8=") - * if (Result.isSuccess(result)) { - * console.log(Array.from(result.success)) // [72, 101, 108, 108, 111] - * } + * Encoding.decodeBase64("SGVsbG8=") // => Result.succeed(new Uint8Array([72, 101, 108, 108, 111])) * ``` * * @category decoding @@ -242,13 +239,10 @@ export const decodeBase64 = (str: string): Result.Result Result.map(decodeBase64(str), * * **Example** (Encoding URL-safe Base64) * - * ```ts + * ```ts import.meta.vitest * import { Encoding } from "effect" * * // URL-safe base64 encoding (uses - and _ instead of + and /) - * console.log(Encoding.encodeBase64Url("hello?")) // "aGVsbG8_" + * Encoding.encodeBase64Url("hello?") // => "aGVsbG8_" * * const bytes = new Uint8Array([72, 101, 108, 108, 111, 63]) - * console.log(Encoding.encodeBase64Url(bytes)) // "SGVsbG8_" + * Encoding.encodeBase64Url(bytes) // => "SGVsbG8_" * ``` * * @see {@link decodeBase64Url} for decoding URL-safe Base64 to bytes @@ -313,13 +307,10 @@ export const encodeBase64Url: (input: Uint8Array | string) => string = (input) = * * **Example** (Decoding URL-safe Base64 bytes) * - * ```ts + * ```ts import.meta.vitest * import { Encoding, Result } from "effect" * - * const result = Encoding.decodeBase64Url("SGVsbG8_") - * if (Result.isSuccess(result)) { - * console.log(Array.from(result.success)) // [72, 101, 108, 108, 111, 63] - * } + * Encoding.decodeBase64Url("SGVsbG8_") // => Result.succeed(new Uint8Array([72, 101, 108, 108, 111, 63])) * ``` * * @category decoding @@ -373,13 +364,10 @@ export const decodeBase64Url = (str: string): Result.Result Result.succeed("hello?") * ``` * * @category decoding @@ -400,15 +388,15 @@ export const decodeBase64UrlString = (str: string) => Result.map(decodeBase64Url * * **Example** (Encoding hex strings and bytes) * - * ```ts + * ```ts import.meta.vitest * import { Encoding } from "effect" * * // Encode a string to hex - * console.log(Encoding.encodeHex("hello")) // "68656c6c6f" + * Encoding.encodeHex("hello") // => "68656c6c6f" * * // Encode binary data to hex * const bytes = new Uint8Array([72, 101, 108, 108, 111]) - * console.log(Encoding.encodeHex(bytes)) // "48656c6c6f" + * Encoding.encodeHex(bytes) // => "48656c6c6f" * ``` * * @category encoding @@ -432,13 +420,10 @@ export const encodeHex: (input: Uint8Array | string) => string = (input) => * * **Example** (Decoding hex bytes) * - * ```ts + * ```ts import.meta.vitest * import { Encoding, Result } from "effect" * - * const result = Encoding.decodeHex("48656c6c6f") - * if (Result.isSuccess(result)) { - * console.log(Array.from(result.success)) // [72, 101, 108, 108, 111] - * } + * Encoding.decodeHex("48656c6c6f") // => Result.succeed(new Uint8Array([72, 101, 108, 108, 111])) * ``` * * @category decoding @@ -494,13 +479,10 @@ export const decodeHex = (str: string): Result.Result * * **Example** (Decoding hex strings) * - * ```ts + * ```ts import.meta.vitest * import { Encoding, Result } from "effect" * - * const result = Encoding.decodeHexString("68656c6c6f") - * if (Result.isSuccess(result)) { - * console.log(result.success) // "hello" - * } + * Encoding.decodeHexString("68656c6c6f") // => Result.succeed("hello") * ``` * * @category decoding diff --git a/repos/effect/packages/effect/src/Equal.ts b/repos/effect/packages/effect/src/Equal.ts index d02b474125..0b45a6ad3c 100644 --- a/repos/effect/packages/effect/src/Equal.ts +++ b/repos/effect/packages/effect/src/Equal.ts @@ -28,7 +28,7 @@ import { hasProperty } from "./Predicate.ts" * * **Example** (Implementing Equal on a class) * - * ```ts + * ```ts import.meta.vitest * import { Equal, Hash } from "effect" * * class UserId implements Equal.Equal { @@ -42,6 +42,9 @@ import { hasProperty } from "./Predicate.ts" * return Hash.string(this.id) * } * } + * + * Equal.equals(new UserId("1"), new UserId("1")) // => true + * Equal.equals(new UserId("1"), new UserId("2")) // => false * ``` * * @see {@link Equal} — the interface that uses this symbol @@ -76,7 +79,7 @@ export const symbol = "~effect/interfaces/Equal" * * **Example** (Comparing coordinates by value) * - * ```ts + * ```ts import.meta.vitest * import { Equal, Hash } from "effect" * * class Coordinate implements Equal.Equal { @@ -93,8 +96,8 @@ export const symbol = "~effect/interfaces/Equal" * } * } * - * console.log(Equal.equals(new Coordinate(1, 2), new Coordinate(1, 2))) // true - * console.log(Equal.equals(new Coordinate(1, 2), new Coordinate(3, 4))) // false + * Equal.equals(new Coordinate(1, 2), new Coordinate(1, 2)) // => true + * Equal.equals(new Coordinate(1, 2), new Coordinate(3, 4)) // => false * ``` * * @see {@link symbol} — the property key used by the equality method @@ -139,30 +142,25 @@ export interface Equal extends Hash.Hash { * * **Example** (Comparing values) * - * ```ts + * ```ts import.meta.vitest * import { Equal } from "effect" * - * // Primitives - * console.log(Equal.equals(1, 1)) // true - * console.log(Equal.equals(NaN, NaN)) // true - * console.log(Equal.equals("a", "b")) // false + * Equal.equals(1, 1) // => true + * Equal.equals(NaN, NaN) // => true + * Equal.equals("a", "b") // => false * - * // Objects and arrays - * console.log(Equal.equals({ a: 1, b: 2 }, { a: 1, b: 2 })) // true - * console.log(Equal.equals([1, [2, 3]], [1, [2, 3]])) // true + * Equal.equals({ a: 1, b: 2 }, { a: 1, b: 2 }) // => true + * Equal.equals([1, [2, 3]], [1, [2, 3]]) // => true * - * // Dates - * console.log(Equal.equals(new Date("2024-01-01"), new Date("2024-01-01"))) // true + * Equal.equals(new Date("2024-01-01"), new Date("2024-01-01")) // => true * - * // Maps (order-independent) * const m1 = new Map([["a", 1], ["b", 2]]) * const m2 = new Map([["b", 2], ["a", 1]]) - * console.log(Equal.equals(m1, m2)) // true + * Equal.equals(m1, m2) // => true * - * // Curried form * const is5 = Equal.equals(5) - * console.log(is5(5)) // true - * console.log(is5(3)) // false + * is5(5) // => true + * is5(3) // => false * ``` * * @see {@link Equal} — the interface for custom equality @@ -235,7 +233,9 @@ function compareObjects(self: object, that: object): boolean { return false } else if (self instanceof Date) { if (!(that instanceof Date)) return false - return self.toISOString() === that.toISOString() + const selfTime = self.getTime() + const thatTime = that.getTime() + return selfTime === thatTime || (Number.isNaN(selfTime) && Number.isNaN(thatTime)) } else if (self instanceof RegExp) { if (!(that instanceof RegExp)) return false return self.toString() === that.toString() @@ -256,9 +256,21 @@ function compareObjects(self: object, that: object): boolean { } return compareArrays(self, that) } else if (ArrayBuffer.isView(self)) { - if (!ArrayBuffer.isView(that) || self.byteLength !== that.byteLength) { + const selfIsDataView = self instanceof DataView + if ( + !ArrayBuffer.isView(that) || + self.byteLength !== that.byteLength || + selfIsDataView !== (that instanceof DataView) + ) { return false } + if (selfIsDataView) { + const thatDataView = that as DataView + return compareTypedArrays( + new Uint8Array(self.buffer, self.byteOffset, self.byteLength), + new Uint8Array(thatDataView.buffer, thatDataView.byteOffset, thatDataView.byteLength) + ) + } return compareTypedArrays(self as Uint8Array, that as Uint8Array) } else if (self instanceof Map) { if (!(that instanceof Map) || self.size !== that.size) { @@ -348,10 +360,14 @@ function compareRecords( /** @internal */ export function makeCompareMap(keyEquivalence: Equivalence, valueEquivalence: Equivalence) { return function compareMaps(self: Iterable<[K, V]>, that: Iterable<[K, V]>): boolean { + const thatEntries = Array.from(that) for (const [selfKey, selfValue] of self) { let found = false - for (const [thatKey, thatValue] of that) { + for (let i = 0; i < thatEntries.length; i++) { + const [thatKey, thatValue] = thatEntries[i] if (keyEquivalence(selfKey, thatKey) && valueEquivalence(selfValue, thatValue)) { + thatEntries[i] = thatEntries[thatEntries.length - 1] + thatEntries.pop() found = true break } @@ -370,10 +386,14 @@ const compareMaps = makeCompareMap(compareBoth, compareBoth) /** @internal */ export function makeCompareSet(equivalence: Equivalence) { return function compareSets(self: Iterable, that: Iterable): boolean { + const thatValues = Array.from(that) for (const selfValue of self) { let found = false - for (const thatValue of that) { + for (let i = 0; i < thatValues.length; i++) { + const thatValue = thatValues[i] if (equivalence(selfValue, thatValue)) { + thatValues[i] = thatValues[thatValues.length - 1] + thatValues.pop() found = true break } @@ -406,7 +426,7 @@ const compareSets = makeCompareSet(compareBoth) * * **Example** (Checking Equal values) * - * ```ts + * ```ts import.meta.vitest * import { Equal, Hash } from "effect" * * class Token implements Equal.Equal { @@ -419,9 +439,9 @@ const compareSets = makeCompareSet(compareBoth) * } * } * - * console.log(Equal.isEqual(new Token("abc"))) // true - * console.log(Equal.isEqual({ x: 1 })) // false - * console.log(Equal.isEqual(42)) // false + * Equal.isEqual(new Token("abc")) // => true + * Equal.isEqual({ x: 1 }) // => false + * Equal.isEqual(42) // => false * ``` * * @see {@link Equal} — the interface being checked @@ -447,12 +467,10 @@ export const isEqual = (u: unknown): u is Equal => hasProperty(u, symbol) * * **Example** (Deduplicating with Equal semantics) * - * ```ts + * ```ts import.meta.vitest * import { Array, Equal } from "effect" * - * const eq = Equal.asEquivalence() - * const result = Array.dedupeWith([1, 2, 2, 3, 1], eq) - * console.log(result) // [1, 2, 3] + * Array.dedupeWith([1, 2, 2, 3, 1], Equal.asEquivalence()) // => [1, 2, 3] * ``` * * @see {@link equals} — the underlying comparison function @@ -481,18 +499,18 @@ export const asEquivalence: () => Equivalence = () => equals * * **Example** (Opting out of structural equality) * - * ```ts + * ```ts import.meta.vitest * import { Equal } from "effect" * * const a = { x: 1 } * const b = { x: 1 } * - * console.log(Equal.equals(a, b)) // true (structural) + * Equal.equals(a, b) // => true * * const aRef = Equal.byReference(a) - * console.log(Equal.equals(aRef, b)) // false (reference) - * console.log(Equal.equals(aRef, aRef)) // true (same reference) - * console.log(aRef.x) // 1 (proxy reads through) + * Equal.equals(aRef, b) // => false + * Equal.equals(aRef, aRef) // => true + * aRef.x // => 1 * ``` * * @see {@link byReferenceUnsafe} — same effect without a proxy (mutates the @@ -526,17 +544,17 @@ export const byReference = (obj: T): T => byReferenceUnsafe(ne * * **Example** (Marking an object for reference equality) * - * ```ts + * ```ts import.meta.vitest * import { Equal } from "effect" * * const obj1 = { a: 1, b: 2 } * const obj2 = { a: 1, b: 2 } * - * Equal.byReferenceUnsafe(obj1) + * const marked = Equal.byReferenceUnsafe(obj1) * - * console.log(Equal.equals(obj1, obj2)) // false (reference) - * console.log(Equal.equals(obj1, obj1)) // true (same reference) - * console.log(obj1 === Equal.byReferenceUnsafe(obj1)) // true (same object) + * Equal.equals(obj1, obj2) // => false + * Equal.equals(obj1, obj1) // => true + * marked === obj1 // => true * ``` * * @see {@link byReference} — safer alternative that creates a proxy diff --git a/repos/effect/packages/effect/src/Equivalence.ts b/repos/effect/packages/effect/src/Equivalence.ts index 9a5a20fc5d..e1fe8ce5d0 100644 --- a/repos/effect/packages/effect/src/Equivalence.ts +++ b/repos/effect/packages/effect/src/Equivalence.ts @@ -27,18 +27,18 @@ import * as Reducer from "./Reducer.ts" * * **Example** (Defining simple number equivalence) * - * ```ts + * ```ts import.meta.vitest * import type { Equivalence } from "effect" * * const numberEq: Equivalence.Equivalence = (a, b) => a === b * - * console.log(numberEq(1, 1)) // true - * console.log(numberEq(1, 2)) // false + * numberEq(1, 1) // => true + * numberEq(1, 2) // => false * ``` * * **Example** (Defining custom object equivalence) * - * ```ts + * ```ts import.meta.vitest * import type { Equivalence } from "effect" * * interface Point { @@ -49,12 +49,12 @@ import * as Reducer from "./Reducer.ts" * const pointEq: Equivalence.Equivalence = (a, b) => * a.x === b.x && a.y === b.y * - * console.log(pointEq({ x: 1, y: 2 }, { x: 1, y: 2 })) // true + * pointEq({ x: 1, y: 2 }, { x: 1, y: 2 }) // => true * ``` * * @see {@link make} * @see {@link strictEqual} - * @category type class + * @category models * @since 2.0.0 */ export type Equivalence = (self: A, that: A) => boolean @@ -73,7 +73,7 @@ export type Equivalence = (self: A, that: A) => boolean * * **Example** (Type-level usage) * - * ```ts + * ```ts import.meta.vitest * import type { Equivalence, HKT } from "effect" * * // Used internally for type-level computations @@ -89,7 +89,7 @@ export type Equivalence = (self: A, that: A) => boolean * * @see {@link Equivalence} * @see {@link TypeLambda} - * @category type lambdas + * @category utility types * @since 2.0.0 */ export interface EquivalenceTypeLambda extends TypeLambda { @@ -113,30 +113,30 @@ export interface EquivalenceTypeLambda extends TypeLambda { * * **Example** (Case-insensitive string equivalence) * - * ```ts + * ```ts import.meta.vitest * import { Equivalence } from "effect" * * const caseInsensitive = Equivalence.make((a, b) => * a.toLowerCase() === b.toLowerCase() * ) * - * console.log(caseInsensitive("Hello", "HELLO")) // true - * console.log(caseInsensitive("foo", "bar")) // false + * caseInsensitive("Hello", "HELLO") // => true + * caseInsensitive("foo", "bar") // => false * * // Same reference optimization * const str = "test" - * console.log(caseInsensitive(str, str)) // true (fast path) + * caseInsensitive(str, str) // => true * ``` * * **Example** (Comparing numbers with tolerance) * - * ```ts + * ```ts import.meta.vitest * import { Equivalence } from "effect" * * const tolerance = Equivalence.make((a, b) => Math.abs(a - b) < 0.0001) * - * console.log(tolerance(1.0, 1.001)) // false - * console.log(tolerance(1.0, 1.00001)) // true + * tolerance(1.0, 1.001) // => false + * tolerance(1.0, 1.00001) // => true * ``` * * @see {@link strictEqual} @@ -169,26 +169,26 @@ const isStrictEquivalent = (x: unknown, y: unknown) => x === y * * **Example** (Comparing primitive types) * - * ```ts + * ```ts import.meta.vitest * import { Equivalence } from "effect" * * const strictEq = Equivalence.strictEqual() * - * console.log(strictEq(1, 1)) // true - * console.log(strictEq(1, 2)) // false - * console.log(strictEq(NaN, NaN)) // false (NaN !== NaN) + * strictEq(1, 1) // => true + * strictEq(1, 2) // => false + * strictEq(NaN, NaN) // => false * ``` * * **Example** (Comparing objects by reference) * - * ```ts + * ```ts import.meta.vitest * import { Equivalence } from "effect" * * const obj = { value: 42 } * const strictObjEq = Equivalence.strictEqual() * - * console.log(strictObjEq(obj, obj)) // true - * console.log(strictObjEq(obj, { value: 42 })) // false (different references) + * strictObjEq(obj, obj) // => true + * strictObjEq(obj, { value: 42 }) // => false * ``` * * @see {@link make} @@ -207,11 +207,11 @@ export const strictEqual: () => Equivalence = () => isStrictEquivalent * * **Example** (Comparing strings) * - * ```ts + * ```ts import.meta.vitest * import { Equivalence } from "effect" * - * console.log(Equivalence.String("hello", "hello")) // true - * console.log(Equivalence.String("hello", "world")) // false + * Equivalence.String("hello", "hello") // => true + * Equivalence.String("hello", "world") // => false * ``` * * @category instances @@ -228,12 +228,12 @@ export const String: Equivalence = isStrictEquivalent * * **Example** (Comparing numbers) * - * ```ts + * ```ts import.meta.vitest * import { Equivalence } from "effect" * - * console.log(Equivalence.Number(1, 1)) // true - * console.log(Equivalence.Number(1, 2)) // false - * console.log(Equivalence.Number(NaN, NaN)) // true + * Equivalence.Number(1, 1) // => true + * Equivalence.Number(1, 2) // => false + * Equivalence.Number(NaN, NaN) // => true * ``` * * @category instances @@ -252,11 +252,11 @@ export const Number: Equivalence = make((self, that) => * * **Example** (Comparing booleans) * - * ```ts + * ```ts import.meta.vitest * import { Equivalence } from "effect" * - * console.log(Equivalence.Boolean(true, true)) // true - * console.log(Equivalence.Boolean(true, false)) // false + * Equivalence.Boolean(true, true) // => true + * Equivalence.Boolean(true, false) // => false * ``` * * @category instances @@ -273,11 +273,11 @@ export const Boolean: Equivalence = isStrictEquivalent * * **Example** (Comparing bigints) * - * ```ts + * ```ts import.meta.vitest * import { Equivalence } from "effect" * - * console.log(Equivalence.BigInt(1n, 1n)) // true - * console.log(Equivalence.BigInt(1n, 2n)) // false + * Equivalence.BigInt(1n, 1n) // => true + * Equivalence.BigInt(1n, 2n) // => false * ``` * * @category instances @@ -301,7 +301,7 @@ export const BigInt: Equivalence = isStrictEquivalent * * **Example** (Combining name and age equivalences) * - * ```ts + * ```ts import.meta.vitest * import { Equivalence } from "effect" * * interface Person { @@ -325,8 +325,8 @@ export const BigInt: Equivalence = isStrictEquivalent * const person2 = { name: "Alice", age: 30 } * const person3 = { name: "Alice", age: 31 } * - * console.log(personEquivalence(person1, person2)) // true - * console.log(personEquivalence(person1, person3)) // false (different age) + * personEquivalence(person1, person2) // => true + * personEquivalence(person1, person3) // => false * ``` * * @see {@link combineAll} @@ -356,7 +356,7 @@ export const combine: { * * **Example** (Combining multiple field equivalences) * - * ```ts + * ```ts import.meta.vitest * import { Equivalence } from "effect" * * interface Point3D { @@ -384,18 +384,18 @@ export const combine: { * const point2 = { x: 1, y: 2, z: 3 } * const point3 = { x: 1, y: 2, z: 4 } * - * console.log(point3DEq(point1, point2)) // true - * console.log(point3DEq(point1, point3)) // false (different z) + * point3DEq(point1, point2) // => true + * point3DEq(point1, point3) // => false * ``` * * **Example** (Handling empty collections) * - * ```ts + * ```ts import.meta.vitest * import { Equivalence } from "effect" * * // Empty collection always returns true * const alwaysEq = Equivalence.combineAll([]) - * console.log(alwaysEq("anything", "else")) // true + * alwaysEq("anything", "else") // => true * ``` * * @see {@link combine} @@ -431,7 +431,7 @@ export const combineAll = (collection: Iterable>): Equivalence * * **Example** (Deriving equivalence from an object property) * - * ```ts + * ```ts import.meta.vitest * import { Equivalence } from "effect" * * interface User { @@ -450,13 +450,13 @@ export const combineAll = (collection: Iterable>): Equivalence * const user2 = { id: 1, name: "Alice Smith", email: "alice.smith@example.com" } * const user3 = { id: 2, name: "Bob", email: "bob@example.com" } * - * console.log(userByIdEq(user1, user2)) // true (same ID) - * console.log(userByIdEq(user1, user3)) // false (different ID) + * userByIdEq(user1, user2) // => true + * userByIdEq(user1, user3) // => false * ``` * * **Example** (Case-insensitive string equivalence) * - * ```ts + * ```ts import.meta.vitest * import { Equivalence } from "effect" * * const caseInsensitiveEq = Equivalence.mapInput( @@ -464,8 +464,8 @@ export const combineAll = (collection: Iterable>): Equivalence * (s: string) => s.toLowerCase() * ) * - * console.log(caseInsensitiveEq("Hello", "HELLO")) // true - * console.log(caseInsensitiveEq("Hello", "World")) // false + * caseInsensitiveEq("Hello", "HELLO") // => true + * caseInsensitiveEq("Hello", "World") // => false * ``` * * @see {@link combine} @@ -499,7 +499,7 @@ export const mapInput: { * * **Example** (Comparing homogeneous tuples) * - * ```ts + * ```ts import.meta.vitest * import { Equivalence } from "effect" * * const stringTupleEq = Equivalence.Tuple([ @@ -512,13 +512,13 @@ export const mapInput: { * const tuple2 = ["hello", "world", "test"] as const * const tuple3 = ["hello", "world", "different"] as const * - * console.log(stringTupleEq(tuple1, tuple2)) // true - * console.log(stringTupleEq(tuple1, tuple3)) // false (different third element) + * stringTupleEq(tuple1, tuple2) // => true + * stringTupleEq(tuple1, tuple3) // => false * ``` * * **Example** (Comparing tuples with custom equivalences) * - * ```ts + * ```ts import.meta.vitest * import { Equivalence } from "effect" * * const caseInsensitive = Equivalence.mapInput( @@ -532,9 +532,7 @@ export const mapInput: { * caseInsensitive * ]) * - * console.log( - * customTupleEq(["Hello", "World", "Test"], ["HELLO", "WORLD", "TEST"]) - * ) // true + * customTupleEq(["Hello", "World", "Test"], ["HELLO", "WORLD", "TEST"]) // => true * ``` * * @category combinators @@ -588,19 +586,19 @@ export { * * **Example** (Comparing number arrays) * - * ```ts + * ```ts import.meta.vitest * import { Equivalence } from "effect" * * const numberArrayEq = Equivalence.Array(Equivalence.strictEqual()) * - * console.log(numberArrayEq([1, 2, 3], [1, 2, 3])) // true - * console.log(numberArrayEq([1, 2, 3], [1, 2, 4])) // false - * console.log(numberArrayEq([1, 2], [1, 2, 3])) // false (different length) + * numberArrayEq([1, 2, 3], [1, 2, 3]) // => true + * numberArrayEq([1, 2, 3], [1, 2, 4]) // => false + * numberArrayEq([1, 2], [1, 2, 3]) // => false * ``` * * **Example** (Case-insensitive string array) * - * ```ts + * ```ts import.meta.vitest * import { Equivalence } from "effect" * * const caseInsensitive = Equivalence.mapInput( @@ -609,9 +607,9 @@ export { * ) * const stringArrayEq = Equivalence.Array(caseInsensitive) * - * console.log(stringArrayEq(["Hello", "World"], ["HELLO", "WORLD"])) // true - * console.log(stringArrayEq(["Hello"], ["Hi"])) // false - * console.log(stringArrayEq([], [])) // true (empty arrays) + * stringArrayEq(["Hello", "World"], ["HELLO", "WORLD"]) // => true + * stringArrayEq(["Hello"], ["Hi"]) // => false + * stringArrayEq([], []) // => true * ``` * * @see {@link Tuple} @@ -640,7 +638,7 @@ export { * * **Example** (Comparing structs with different equivalences per field) * - * ```ts + * ```ts import.meta.vitest * import { Equivalence } from "effect" * * interface Person { @@ -664,13 +662,13 @@ export { * const person2 = { name: "ALICE", age: 30, email: "ALICE@EXAMPLE.COM" } * const person3 = { name: "Alice", age: 31, email: "alice@example.com" } * - * console.log(personEq(person1, person2)) // true (case-insensitive match) - * console.log(personEq(person1, person3)) // false (different age) + * personEq(person1, person2) // => true + * personEq(person1, person3) // => false * ``` * * **Example** (Comparing specific fields) * - * ```ts + * ```ts import.meta.vitest * import { Equivalence } from "effect" * * const nameAgeEq = Equivalence.Struct({ @@ -681,7 +679,7 @@ export { * // Only compares name and age, ignores other properties * const obj1 = { name: "Alice", age: 30, extra: "ignored" } * const obj2 = { name: "Alice", age: 30, extra: "different" } - * console.log(nameAgeEq(obj1, obj2)) // true + * nameAgeEq(obj1, obj2) // => true * ``` * * @see {@link Record} @@ -721,7 +719,7 @@ export function Struct>>( * * **Example** (Defining records with string values) * - * ```ts + * ```ts import.meta.vitest * import { Equivalence } from "effect" * * const stringRecordEq = Equivalence.Record(Equivalence.strictEqual()) @@ -731,14 +729,14 @@ export function Struct>>( * const record3 = { a: "hello", b: "different" } * const record4 = { a: "hello" } // missing key 'b' * - * console.log(stringRecordEq(record1, record2)) // true - * console.log(stringRecordEq(record1, record3)) // false - * console.log(stringRecordEq(record1, record4)) // false (different keys) + * stringRecordEq(record1, record2) // => true + * stringRecordEq(record1, record3) // => false + * stringRecordEq(record1, record4) // => false * ``` * * **Example** (Defining records with number values) * - * ```ts + * ```ts import.meta.vitest * import { Equivalence } from "effect" * * const numberRecordEq = Equivalence.Record(Equivalence.strictEqual()) @@ -747,8 +745,8 @@ export function Struct>>( * const scores2 = { alice: 100, bob: 85 } * const scores3 = { alice: 100, bob: 90 } * - * console.log(numberRecordEq(scores1, scores2)) // true - * console.log(numberRecordEq(scores1, scores3)) // false + * numberRecordEq(scores1, scores2) // => true + * numberRecordEq(scores1, scores3) // => false * ``` * * @category combinators @@ -787,7 +785,7 @@ export function Record(value: Equivalence): Equivalence() @@ -798,8 +796,8 @@ export function Record(value: Equivalence): Equivalence true + * combined(1, 1.5) // => false * ``` * * @see {@link combine} Combine two equivalences @@ -831,7 +829,7 @@ export function makeReducer() { * * **Example** (Comparing Date values) * - * ```ts + * ```ts import.meta.vitest * import { Equivalence } from "effect" * * const d1 = new Date("2020-01-01T00:00:00.000Z") @@ -840,22 +838,22 @@ export function makeReducer() { * const invalidDate1 = new Date("foo") * const invalidDate2 = new Date("bar") * - * console.log(Equivalence.Date(d1, d2)) // true - * console.log(Equivalence.Date(d1, d3)) // false - * console.log(Equivalence.Date(invalidDate1, invalidDate2)) // true - * console.log(Equivalence.Date(invalidDate1, d1)) // false + * Equivalence.Date(d1, d2) // => true + * Equivalence.Date(d1, d3) // => false + * Equivalence.Date(invalidDate1, invalidDate2) // => true + * Equivalence.Date(invalidDate1, d1) // => false * ``` * * **Example** (Comparing reference and value equality) * - * ```ts + * ```ts import.meta.vitest * import { Equivalence } from "effect" * * const d1 = new Date(0) * const d2 = new Date(0) * - * console.log(d1 === d2) // false (different references) - * console.log(Equivalence.Date(d1, d2)) // true (same time value) + * d1 === d2 // => false + * Equivalence.Date(d1, d2) // => true * ``` * * @see {@link Number} for the numeric equivalence applied to each `Date#getTime()` result diff --git a/repos/effect/packages/effect/src/ErrorReporter.ts b/repos/effect/packages/effect/src/ErrorReporter.ts index 1fbc6ccc81..5864295478 100644 --- a/repos/effect/packages/effect/src/ErrorReporter.ts +++ b/repos/effect/packages/effect/src/ErrorReporter.ts @@ -70,7 +70,7 @@ export const TypeId: TypeId = "~effect/ErrorReporter" * @see {@link report} for manually reporting a `Cause` * @see {@link Effect.withErrorReporting} for reporting failures from an effect * - * @category models + * @category services * @since 4.0.0 */ export interface ErrorReporter { @@ -97,17 +97,24 @@ export interface ErrorReporter { * and resolves the `ignore`, `severity`, and `attributes` annotations on * each error before invoking your callback. * - * **Example** (Forwarding errors to the console) + * **Example** (Forwarding errors to a callback) * - * ```ts - * import { ErrorReporter } from "effect" + * ```ts import.meta.vitest + * import { Effect, ErrorReporter } from "effect" * - * // Forward every failure to the console - * const consoleReporter = ErrorReporter.make( - * ({ error, severity, attributes }) => { - * console.error(`[${severity}]`, error.message, attributes) - * } + * const reports: Array<{ message: string; severity: string; attributes: object }> = [] + * const reporter = ErrorReporter.make(({ error, severity, attributes }) => { + * reports.push({ message: error.message, severity, attributes }) + * }) + * + * const program = Effect.fail(new Error("boom")).pipe( + * Effect.withErrorReporting, + * Effect.provide(ErrorReporter.layer([reporter])), + * Effect.exit * ) + * + * await Effect.runPromise(program) + * reports // => [{ message: "boom", severity: "Info", attributes: {} }] * ``` * * @see {@link layer} for registering reporters in the environment @@ -163,7 +170,7 @@ export const make = ( * Use when you need to read or replace the current set of error reporters * directly. * - * @category references + * @category services * @since 4.0.0 */ export const CurrentErrorReporters: Context.Reference> = references.CurrentErrorReporters @@ -185,33 +192,37 @@ export const CurrentErrorReporters: Context.Reference * * **Example** (Providing error reporters) * - * ```ts + * ```ts import.meta.vitest * import { Effect, ErrorReporter } from "effect" * - * const consoleReporter = ErrorReporter.make(({ error, severity }) => { - * console.error(`[${severity}]`, error.message) + * const reports: Array = [] + * const firstReporter = ErrorReporter.make(({ error, severity }) => { + * reports.push(`[${severity}] ${error.message}`) * }) - * - * const metricsReporter = ErrorReporter.make(({ severity }) => { - * // increment an error counter by severity + * const secondReporter = ErrorReporter.make(({ error, severity }) => { + * reports.push(`${severity}: ${error.message}`) * }) * * // Replace all existing reporters * const ReporterLive = ErrorReporter.layer([ - * consoleReporter, - * metricsReporter + * firstReporter, + * secondReporter * ]) * * // Add to existing reporters instead of replacing * const ReporterMerged = ErrorReporter.layer( - * [metricsReporter], + * [secondReporter], * { mergeWithExisting: true } * ) * * const program = Effect.fail("boom").pipe( * Effect.withErrorReporting, - * Effect.provide(ReporterLive) + * Effect.provide(ReporterLive), + * Effect.exit * ) + * + * await Effect.runPromise(program) + * reports // => ["[Info] boom", "Info: boom"] * ``` * * @see {@link make} for creating an `ErrorReporter` from a callback @@ -255,18 +266,25 @@ export const layer = < * * **Example** (Reporting a cause manually) * - * ```ts + * ```ts import.meta.vitest * import { Cause, Effect, ErrorReporter } from "effect" * - * // Log the cause for monitoring, then continue with a fallback + * const messages: Array = [] * const program = Effect.gen(function*() { * const cause = Cause.fail("something went wrong") * yield* ErrorReporter.report(cause) * return "fallback value" * }) + * + * const reporter = ErrorReporter.make(({ error }) => messages.push(error.message)) + * const output = await Effect.runPromise( + * Effect.provide(program, ErrorReporter.layer([reporter])) + * ) + * messages // => ["something went wrong"] + * output // => "fallback value" * ``` * - * @category Reporting + * @category logging * @since 4.0.0 */ export const report = (cause: Cause.Cause): Effect.Effect => @@ -347,12 +365,14 @@ export type ignore = "~effect/ErrorReporter/ignore" * * **Example** (Marking errors as ignored) * - * ```ts + * ```ts import.meta.vitest * import { Data, ErrorReporter } from "effect" * * class NotFoundError extends Data.TaggedError("NotFoundError")<{}> { * readonly [ErrorReporter.ignore] = true * } + * + * ErrorReporter.isIgnored(new NotFoundError()) // => true * ``` * * @see {@link isIgnored} for checking whether a value carries this annotation @@ -375,7 +395,7 @@ export const ignore: ignore = "~effect/ErrorReporter/ignore" * * @see {@link ignore} for the annotation key this predicate reads * - * @category annotations + * @category predicates * @since 4.0.0 */ export const isIgnored = (u: unknown): boolean => @@ -414,12 +434,14 @@ export type severity = "~effect/ErrorReporter/severity" * * **Example** (Setting error severity annotations) * - * ```ts + * ```ts import.meta.vitest * import { Data, ErrorReporter } from "effect" * * class DeprecationWarning extends Data.TaggedError("DeprecationWarning")<{}> { * readonly [ErrorReporter.severity] = "Warn" as const * } + * + * ErrorReporter.getSeverity(new DeprecationWarning()) // => "Warn" * ``` * * @see {@link getSeverity} for reading the severity stored under this key @@ -488,7 +510,7 @@ export type attributes = "~effect/ErrorReporter/attributes" * * **Example** (Setting error attributes) * - * ```ts + * ```ts import.meta.vitest * import { Data, ErrorReporter } from "effect" * * class PaymentError extends Data.TaggedError("PaymentError")<{ @@ -498,6 +520,8 @@ export type attributes = "~effect/ErrorReporter/attributes" * orderId: this.orderId * } * } + * + * ErrorReporter.getAttributes(new PaymentError({ orderId: "order-123" })) // => { orderId: "order-123" } * ``` * * @see {@link ignore} for suppressing reports for expected object errors diff --git a/repos/effect/packages/effect/src/ExecutionPlan.ts b/repos/effect/packages/effect/src/ExecutionPlan.ts index fd00503b59..2360043593 100644 --- a/repos/effect/packages/effect/src/ExecutionPlan.ts +++ b/repos/effect/packages/effect/src/ExecutionPlan.ts @@ -10,7 +10,9 @@ * @since 3.16.0 */ import type { NonEmptyReadonlyArray } from "./Array.ts" +import type * as Cause from "./Cause.ts" import * as Context from "./Context.ts" +import type * as Duration from "./Duration.ts" import type * as Effect from "./Effect.ts" import { constant } from "./Function.ts" import * as effect from "./internal/effect.ts" @@ -66,41 +68,20 @@ export const isExecutionPlan = (u: unknown): u is ExecutionPlan => Predicat * * **Example** (Defining fallback execution steps) * - * ```ts - * import { Effect, ExecutionPlan, Schedule } from "effect" - * import type { Layer } from "effect" - * import type { LanguageModel } from "effect/unstable/ai" - * - * declare const layerBad: Layer.Layer - * declare const layerGood: Layer.Layer + * ```ts import.meta.vitest + * import { Context, ExecutionPlan } from "effect" * * const ThePlan = ExecutionPlan.make( * { - * // First try with the bad layer 2 times with a 3 second delay between attempts - * provide: layerBad, - * attempts: 2, - * schedule: Schedule.spaced(3000) - * }, - * // Then try with the bad layer 3 times with a 1 second delay between attempts - * { - * provide: layerBad, - * attempts: 3, - * schedule: Schedule.spaced(1000) + * provide: Context.empty(), + * attempts: 2 * }, - * // Finally try with the good layer. - * // - * // If `attempts` is omitted, the plan will only attempt once, unless a schedule is provided. * { - * provide: layerGood + * provide: Context.empty() * } * ) * - * declare const effect: Effect.Effect< - * void, - * never, - * LanguageModel.LanguageModel - * > - * const withPlan: Effect.Effect = Effect.withExecutionPlan(effect, ThePlan) + * ThePlan.steps.map((step) => step.attempts ?? 1) // => [2, 1] * ``` * * @category models @@ -166,41 +147,20 @@ export type ConfigBase = { * * **Example** (Creating an execution plan) * - * ```ts - * import { Effect, ExecutionPlan, Schedule } from "effect" - * import type { Layer } from "effect" - * import type { LanguageModel } from "effect/unstable/ai" - * - * declare const layerBad: Layer.Layer - * declare const layerGood: Layer.Layer + * ```ts import.meta.vitest + * import { Context, ExecutionPlan } from "effect" * * const ThePlan = ExecutionPlan.make( * { - * // First try with the bad layer 2 times with a 3 second delay between attempts - * provide: layerBad, - * attempts: 2, - * schedule: Schedule.spaced(3000) + * provide: Context.empty(), + * attempts: 2 * }, - * // Then try with the bad layer 3 times with a 1 second delay between attempts * { - * provide: layerBad, - * attempts: 3, - * schedule: Schedule.spaced(1000) - * }, - * // Finally try with the good layer. - * // - * // If `attempts` is omitted, the plan will only attempt once, unless a schedule is provided. - * { - * provide: layerGood + * provide: Context.empty() * } * ) * - * declare const effect: Effect.Effect< - * void, - * never, - * LanguageModel.LanguageModel - * > - * const withPlan: Effect.Effect = Effect.withExecutionPlan(effect, ThePlan) + * ThePlan.steps.length // => 2 * ``` * * @category constructors @@ -221,7 +181,7 @@ export const make = >( | (Steps[number]["schedule"] extends Schedule.Schedule ? R : never) }> => makeProto(steps.map((options, i) => { - if (options.attempts && options.attempts < 1) { + if (options.attempts !== undefined && options.attempts < 1) { throw new Error(`ExecutionPlan.make: step[${i}].attempts must be greater than 0`) } return { @@ -405,7 +365,7 @@ export interface Metadata { * Use to read the active plan step and attempt while code is running under an * execution plan. * - * @category metadata + * @category services * @since 4.0.0 */ export const CurrentMetadata = Context.Reference("effect/ExecutionPlan/CurrentMetadata", { @@ -414,3 +374,78 @@ export const CurrentMetadata = Context.Reference("effect/ExecutionPlan stepIndex: 0 }) }) + +/** + * Lifecycle event emitted before an execution-plan attempt runs. + * + * **Details** + * + * `attempt` is the cumulative 1-based attempt number across all steps and + * matches `CurrentMetadata.attempt` for the same attempt. `stepAttempt` is the + * 1-based attempt number within the current step, and `stepIndex` is the + * 0-based index of the step being attempted. + * + * @category models + * @since 4.0.0 + */ +export interface AttemptStart { + readonly _tag: "AttemptStart" + readonly attempt: number + readonly stepAttempt: number + readonly stepIndex: number +} + +/** + * Lifecycle event emitted when an execution-plan attempt succeeds. + * + * **Details** + * + * A successful attempt completes the plan, so this is always the final event. + * `duration` is the elapsed time of the attempt. + * + * @category models + * @since 4.0.0 + */ +export interface AttemptSuccess { + readonly _tag: "AttemptSuccess" + readonly attempt: number + readonly stepAttempt: number + readonly stepIndex: number + readonly duration: Duration.Duration +} + +/** + * Lifecycle event emitted when an execution-plan attempt fails. + * + * **Details** + * + * `cause` holds the full failure cause, so defects and interruption are + * reported as well as expected errors. Whether the plan retries or fails over + * afterwards is decided by the step's `attempts`, `while`, and `schedule`; a + * following `AttemptStart` indicates another attempt was made. + * + * @category models + * @since 4.0.0 + */ +export interface AttemptFailure { + readonly _tag: "AttemptFailure" + readonly attempt: number + readonly stepAttempt: number + readonly stepIndex: number + readonly duration: Duration.Duration + readonly cause: Cause.Cause +} + +/** + * Union of the lifecycle events emitted while an execution plan runs. + * + * **Details** + * + * Every `AttemptStart` is followed by exactly one terminal event, either + * `AttemptSuccess` or `AttemptFailure`. An interrupted attempt emits + * `AttemptFailure` with the interruption cause. + * + * @category models + * @since 4.0.0 + */ +export type Event = AttemptStart | AttemptSuccess | AttemptFailure diff --git a/repos/effect/packages/effect/src/Exit.ts b/repos/effect/packages/effect/src/Exit.ts index d3e45bdad0..889d81d481 100644 --- a/repos/effect/packages/effect/src/Exit.ts +++ b/repos/effect/packages/effect/src/Exit.ts @@ -37,16 +37,16 @@ const TypeId = core.ExitTypeId * * **Example** (Pattern matching on an Exit) * - * ```ts + * ```ts import.meta.vitest * import { Exit } from "effect" * * const success: Exit.Exit = Exit.succeed(42) * const failure: Exit.Exit = Exit.fail("error") * - * const result = Exit.match(success, { + * Exit.match(success, { * onSuccess: (value) => `Got value: ${value}`, * onFailure: (cause) => `Got error: ${cause}` - * }) + * }) // => "Got value: 42" * ``` * * @see {@link Success} for the success case @@ -99,14 +99,13 @@ export declare namespace Exit { * * **Example** (Accessing the success value) * - * ```ts + * ```ts import.meta.vitest * import { Exit } from "effect" * * const success = Exit.succeed(42) * * if (Exit.isSuccess(success)) { - * console.log(success._tag) // "Success" - * console.log(success.value) // 42 + * success.value // => 42 * } * ``` * @@ -136,14 +135,13 @@ export interface Success extends Exit.Proto { * * **Example** (Accessing the failure cause) * - * ```ts - * import { Exit } from "effect" + * ```ts import.meta.vitest + * import { Cause, Exit } from "effect" * * const failure = Exit.fail("something went wrong") * * if (Exit.isFailure(failure)) { - * console.log(failure._tag) // "Failure" - * console.log(failure.cause) // Cause representing the error + * failure.cause // => Cause.fail("something went wrong") * } * ``` * @@ -173,12 +171,12 @@ export interface Failure extends Exit.Proto { * * **Example** (Checking if a value is an Exit) * - * ```ts + * ```ts import.meta.vitest * import { Exit } from "effect" * - * console.log(Exit.isExit(Exit.succeed(42))) // true - * console.log(Exit.isExit(Exit.fail("err"))) // true - * console.log(Exit.isExit("not an exit")) // false + * Exit.isExit(Exit.succeed(42)) // => true + * Exit.isExit(Exit.fail("err")) // => true + * Exit.isExit("not an exit") // => false * ``` * * @see {@link isSuccess} to check for a successful Exit @@ -203,11 +201,10 @@ export const isExit: (u: unknown) => u is Exit = core.isExit * * **Example** (Creating a successful Exit) * - * ```ts + * ```ts import.meta.vitest * import { Exit } from "effect" * - * const exit = Exit.succeed(42) - * console.log(Exit.isSuccess(exit)) // true + * Exit.succeed(42) // => Exit.succeed(42) * ``` * * @see {@link fail} to create a failed Exit @@ -234,12 +231,10 @@ export const succeed: (a: A) => Exit = core.exitSucceed * * **Example** (Creating a failed Exit from a Cause) * - * ```ts + * ```ts import.meta.vitest * import { Cause, Exit } from "effect" * - * const cause = Cause.fail("Something went wrong") - * const exit = Exit.failCause(cause) - * console.log(Exit.isFailure(exit)) // true + * Exit.failCause(Cause.fail("Something went wrong")) // => Exit.fail("Something went wrong") * ``` * * @see {@link fail} to create a Failure from a plain error value @@ -265,11 +260,10 @@ export const failCause: (cause: Cause.Cause) => Exit = core.exit * * **Example** (Creating a failed Exit) * - * ```ts + * ```ts import.meta.vitest * import { Exit } from "effect" * - * const exit = Exit.fail("Something went wrong") - * console.log(Exit.isFailure(exit)) // true + * Exit.fail("Something went wrong") // => Exit.fail("Something went wrong") * ``` * * @see {@link succeed} to create a successful Exit @@ -298,11 +292,10 @@ export const fail: (e: E) => Exit = core.exitFail * * **Example** (Creating a defect Exit) * - * ```ts + * ```ts import.meta.vitest * import { Exit } from "effect" * - * const exit = Exit.die(new Error("Unexpected error")) - * console.log(Exit.isFailure(exit)) // true + * Exit.die("Unexpected error") // => Exit.die("Unexpected error") * ``` * * @see {@link fail} to create a Failure from a typed error @@ -327,12 +320,10 @@ export const die: (defect: unknown) => Exit = core.exitDie * * **Example** (Creating an interruption Exit) * - * ```ts + * ```ts import.meta.vitest * import { Exit } from "effect" * - * const exit = Exit.interrupt(123) - * console.log(Exit.isFailure(exit)) // true - * console.log(Exit.hasInterrupts(exit)) // true + * Exit.interrupt(123) // => Exit.interrupt(123) * ``` * * @see {@link hasInterrupts} to check whether an Exit contains interruptions @@ -358,11 +349,10 @@ export { * * **Example** (Referencing the void Exit) * - * ```ts + * ```ts import.meta.vitest * import { Exit } from "effect" * - * const exit = Exit.void - * console.log(Exit.isSuccess(exit)) // true + * Exit.void // => Exit.succeed(undefined) * ``` * * @see {@link succeed} to create a success with a specific value @@ -384,13 +374,13 @@ export { * * **Example** (Narrowing to success) * - * ```ts + * ```ts import.meta.vitest * import { Exit } from "effect" * * const exit = Exit.succeed(42) * * if (Exit.isSuccess(exit)) { - * console.log(exit.value) // 42 + * exit.value // => 42 * } * ``` * @@ -412,13 +402,13 @@ export const isSuccess: (self: Exit) => self is Success = effe * * **Example** (Narrowing to failure) * - * ```ts - * import { Exit } from "effect" + * ```ts import.meta.vitest + * import { Cause, Exit } from "effect" * * const exit = Exit.fail("error") * * if (Exit.isFailure(exit)) { - * console.log(exit.cause) + * exit.cause // => Cause.fail("error") * } * ``` * @@ -444,12 +434,12 @@ export const isFailure: (self: Exit) => self is Failure = effe * * **Example** (Checking for typed errors) * - * ```ts + * ```ts import.meta.vitest * import { Exit } from "effect" * - * console.log(Exit.hasFails(Exit.fail("err"))) // true - * console.log(Exit.hasFails(Exit.die(new Error("bug")))) // false - * console.log(Exit.hasFails(Exit.succeed(42))) // false + * Exit.hasFails(Exit.fail("err")) // => true + * Exit.hasFails(Exit.die("bug")) // => false + * Exit.hasFails(Exit.succeed(42)) // => false * ``` * * @see {@link hasDies} to check for defects @@ -474,12 +464,12 @@ export const hasFails: (self: Exit) => self is Failure = effec * * **Example** (Checking for defects) * - * ```ts + * ```ts import.meta.vitest * import { Exit } from "effect" * - * console.log(Exit.hasDies(Exit.die(new Error("bug")))) // true - * console.log(Exit.hasDies(Exit.fail("err"))) // false - * console.log(Exit.hasDies(Exit.succeed(42))) // false + * Exit.hasDies(Exit.die("bug")) // => true + * Exit.hasDies(Exit.fail("err")) // => false + * Exit.hasDies(Exit.succeed(42)) // => false * ``` * * @see {@link hasFails} to check for typed errors @@ -504,12 +494,12 @@ export const hasDies: (self: Exit) => self is Failure = effect * * **Example** (Checking for interruptions) * - * ```ts + * ```ts import.meta.vitest * import { Exit } from "effect" * - * console.log(Exit.hasInterrupts(Exit.interrupt(1))) // true - * console.log(Exit.hasInterrupts(Exit.fail("err"))) // false - * console.log(Exit.hasInterrupts(Exit.succeed(42))) // false + * Exit.hasInterrupts(Exit.interrupt(1)) // => true + * Exit.hasInterrupts(Exit.fail("err")) // => false + * Exit.hasInterrupts(Exit.succeed(42)) // => false * ``` * * @see {@link hasFails} to check for typed errors @@ -540,13 +530,10 @@ export const hasInterrupts: (self: Exit) => self is Failure = * * **Example** (Filtering for success) * - * ```ts + * ```ts import.meta.vitest * import { Exit, Result } from "effect" * - * const exit = Exit.succeed(42) - * const result = Exit.filterSuccess(exit) - * - * console.log(Result.isSuccess(result)) // true + * Exit.filterSuccess(Exit.succeed(42)) // => Result.succeed(Exit.succeed(42)) * ``` * * @see {@link filterFailure} for the inverse @@ -580,13 +567,10 @@ export const filterSuccess: ( * * **Example** (Filtering for the value) * - * ```ts + * ```ts import.meta.vitest * import { Exit, Result } from "effect" * - * const exit = Exit.succeed(42) - * const result = Exit.filterValue(exit) - * - * console.log(Result.isSuccess(result) && result.success) // 42 + * Exit.filterValue(Exit.succeed(42)) // => Result.succeed(42) * ``` * * @see {@link filterSuccess} to get the full Success object @@ -617,13 +601,10 @@ export const filterValue: (self: Exit) => Result.Result Result.succeed(Exit.fail("err")) * ``` * * @see {@link filterSuccess} for the inverse @@ -655,13 +636,10 @@ export const filterFailure: (self: Exit) => Result.Result Result.succeed(Cause.fail("err")) * ``` * * @see {@link filterFailure} to get the full Failure object @@ -692,16 +670,11 @@ export const filterCause: (self: Exit) => Result.Result Result.succeed("not found") + * Exit.findError(Exit.die("bug")) // => Result.fail(Exit.die("bug")) * ``` * * @see {@link findErrorOption} to get the error as an Option instead @@ -732,16 +705,11 @@ export const findError: (input: Exit) => Result.Result * * **Example** (Finding the first defect) * - * ```ts + * ```ts import.meta.vitest * import { Exit, Result } from "effect" * - * const exit = Exit.die("boom") - * const result = Exit.findDefect(exit) - * console.log(Result.isSuccess(result) && result.success) // "boom" - * - * const typed = Exit.fail("err") - * const noDefect = Exit.findDefect(typed) - * console.log(Result.isFailure(noDefect)) // true + * Exit.findDefect(Exit.die("boom")) // => Result.succeed("boom") + * Exit.findDefect(Exit.fail("err")) // => Result.fail(Exit.fail("err")) * ``` * * @see {@link findError} to find typed errors instead @@ -767,16 +735,13 @@ export const findDefect: (input: Exit) => Result.Result `Got: ${value}`, * onFailure: () => "Failed" - * }) - * console.log(result) // "Got: 42" + * }) // => "Got: 42" * ``` * * @see {@link isSuccess} and {@link isFailure} for simple boolean checks @@ -813,12 +778,10 @@ export const match: { * * **Example** (Mapping over a success) * - * ```ts + * ```ts import.meta.vitest * import { Exit } from "effect" * - * const exit = Exit.succeed(21) - * const doubled = Exit.map(exit, (x) => x * 2) - * console.log(Exit.isSuccess(doubled) && doubled.value) // 42 + * Exit.map(Exit.succeed(21), (x) => x * 2) // => Exit.succeed(42) * ``` * * @see {@link mapError} to transform the error @@ -852,14 +815,10 @@ export const map: { * * **Example** (Mapping over an error) * - * ```ts - * import { Data, Exit } from "effect" - * - * class ExitError extends Data.TaggedError("ExitError")<{ readonly input: string }> {} + * ```ts import.meta.vitest + * import { Exit } from "effect" * - * const exit = Exit.fail("bad input") - * const mapped = Exit.mapError(exit, (e) => new ExitError({ input: e })) - * console.log(Exit.isFailure(mapped)) // true + * Exit.mapError(Exit.fail("bad input"), (error) => error.toUpperCase()) // => Exit.fail("BAD INPUT") * ``` * * @see {@link map} to transform the success value @@ -893,17 +852,13 @@ export const mapError: { * * **Example** (Mapping both channels) * - * ```ts - * import { Data, Exit } from "effect" - * - * class ExitError extends Data.TaggedError("ExitError")<{ readonly input: string }> {} + * ```ts import.meta.vitest + * import { Exit } from "effect" * - * const exit = Exit.succeed(42) - * const mapped = Exit.mapBoth(exit, { + * Exit.mapBoth(Exit.succeed(42), { * onSuccess: (x) => String(x), - * onFailure: (e: string) => new ExitError({ input: e }) - * }) - * console.log(Exit.isSuccess(mapped) && mapped.value) // "42" + * onFailure: (error: string) => error.toUpperCase() + * }) // => Exit.succeed("42") * ``` * * @see {@link map} to transform only the success value @@ -938,12 +893,10 @@ export const mapBoth: { * * **Example** (Discarding the success value) * - * ```ts + * ```ts import.meta.vitest * import { Exit } from "effect" * - * const exit = Exit.succeed(42) - * const voided = Exit.asVoid(exit) - * console.log(Exit.isSuccess(voided)) // true + * Exit.asVoid(Exit.succeed(42)) // => Exit.succeed(undefined) * ``` * * @see {@link void_ void} for a pre-allocated void success @@ -971,14 +924,11 @@ export const asVoid: (self: Exit) => Exit = effect.exitAsVo * * **Example** (Combining exits) * - * ```ts + * ```ts import.meta.vitest * import { Exit } from "effect" * - * const exits = [Exit.succeed(1), Exit.succeed(2), Exit.succeed(3)] - * console.log(Exit.isSuccess(Exit.asVoidAll(exits))) // true - * - * const mixed = [Exit.succeed(1), Exit.fail("err"), Exit.succeed(3)] - * console.log(Exit.isFailure(Exit.asVoidAll(mixed))) // true + * Exit.asVoidAll([Exit.succeed(1), Exit.succeed(2), Exit.succeed(3)]) // => Exit.succeed(undefined) + * Exit.asVoidAll([Exit.succeed(1), Exit.fail("err"), Exit.succeed(3)]) // => Exit.fail("err") * ``` * * @see {@link asVoid} to discard the value of a single Exit @@ -1004,17 +954,17 @@ export const asVoidAll: >>( * * **Example** (Getting the success value) * - * ```ts - * import { Exit } from "effect" + * ```ts import.meta.vitest + * import { Exit, Option } from "effect" * - * console.log(Exit.getSuccess(Exit.succeed(42))) // { _tag: "Some", value: 42 } - * console.log(Exit.getSuccess(Exit.fail("err"))) // { _tag: "None" } + * Exit.getSuccess(Exit.succeed(42)) // => Option.some(42) + * Exit.getSuccess(Exit.fail("err")) // => Option.none() * ``` * * @see {@link getCause} to extract the Cause of a failure * @see {@link filterValue} for filter-pipeline usage * - * @category accessors + * @category getters * @since 4.0.0 */ export const getSuccess: (self: Exit) => Option = effect.exitGetSuccess @@ -1033,17 +983,17 @@ export const getSuccess: (self: Exit) => Option = effect.exitGetS * * **Example** (Getting the failure cause) * - * ```ts - * import { Exit } from "effect" + * ```ts import.meta.vitest + * import { Cause, Exit, Option } from "effect" * - * console.log(Exit.getCause(Exit.fail("err"))) // { _tag: "Some", value: ... } - * console.log(Exit.getCause(Exit.succeed(42))) // { _tag: "None" } + * Exit.getCause(Exit.fail("err")) // => Option.some(Cause.fail("err")) + * Exit.getCause(Exit.succeed(42)) // => Option.none() * ``` * * @see {@link getSuccess} to extract the success value * @see {@link filterCause} for filter-pipeline usage * - * @category accessors + * @category getters * @since 4.0.0 */ export const getCause: (self: Exit) => Option> = effect.exitGetCause @@ -1068,18 +1018,18 @@ export const getCause: (self: Exit) => Option> = effe * * **Example** (Getting the first error) * - * ```ts - * import { Exit } from "effect" + * ```ts import.meta.vitest + * import { Exit, Option } from "effect" * - * console.log(Exit.findErrorOption(Exit.fail("err"))) // { _tag: "Some", value: "err" } - * console.log(Exit.findErrorOption(Exit.die(new Error("bug")))) // { _tag: "None" } - * console.log(Exit.findErrorOption(Exit.succeed(42))) // { _tag: "None" } + * Exit.findErrorOption(Exit.fail("err")) // => Option.some("err") + * Exit.findErrorOption(Exit.die("bug")) // => Option.none() + * Exit.findErrorOption(Exit.succeed(42)) // => Option.none() * ``` * * @see {@link findError} for filter-pipeline usage * @see {@link getCause} to get the full Cause as an Option * - * @category accessors + * @category getters * @since 4.0.0 */ export const findErrorOption: (self: Exit) => Option = effect.exitFindErrorOption diff --git a/repos/effect/packages/effect/src/Fiber.ts b/repos/effect/packages/effect/src/Fiber.ts index ed60b91e9f..4e511ee277 100644 --- a/repos/effect/packages/effect/src/Fiber.ts +++ b/repos/effect/packages/effect/src/Fiber.ts @@ -12,7 +12,6 @@ import type * as Context from "./Context.ts" import type { Effect } from "./Effect.ts" import type { Exit } from "./Exit.ts" import * as effect from "./internal/effect.ts" -import { version } from "./internal/version.ts" import type { LogLevel } from "./LogLevel.ts" import type { Pipeable } from "./Pipeable.ts" import { hasProperty } from "./Predicate.ts" @@ -22,7 +21,7 @@ import type { Scope } from "./Scope.ts" import type { AnySpan } from "./Tracer.ts" import type { Covariant } from "./Types.ts" -const TypeId = `~effect/Fiber/${version}` +const TypeId = "~effect/Fiber" /** * A runtime fiber is a lightweight thread that executes Effects. Fibers are @@ -49,8 +48,8 @@ const TypeId = `~effect/Fiber/${version}` * * **Example** (Awaiting a forked fiber) * - * ```ts - * import { Effect, Fiber } from "effect" + * ```ts import.meta.vitest + * import { Effect, Exit, Fiber } from "effect" * * const program = Effect.gen(function*() { * // Fork an effect to run in a new fiber @@ -58,10 +57,11 @@ const TypeId = `~effect/Fiber/${version}` * * // Wait for the fiber to complete and get its result * const result = yield* Fiber.await(fiber) - * console.log(result) // Exit.succeed(42) - * * return result * }) + * + * const actual = await Effect.runPromise(program) + * actual // => Exit.succeed(42) * ``` * * @category models @@ -106,7 +106,7 @@ export interface Fiber extends Pipeable { * * **Example** (Working with fiber types) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Fiber } from "effect" * * const program = Effect.gen(function*() { @@ -116,13 +116,12 @@ export interface Fiber extends Pipeable { * // Use namespace types for variance * const typedFiber: Fiber.Fiber = fiber * - * // Access fiber properties - * console.log(`Fiber ID: ${fiber.id}`) - * * // Join the fiber - * const result = yield* Fiber.join(fiber) - * return result // 42 + * return yield* Fiber.join(fiber) * }) + * + * const actual = await Effect.runPromise(program) + * actual // => 42 * ``` * * @since 2.0.0 @@ -139,12 +138,14 @@ export declare namespace Fiber { * * **Example** (Upcasting fibers safely) * - * ```ts - * import type { Fiber } from "effect" + * ```ts import.meta.vitest + * import { Effect, Fiber } from "effect" * * // Variance allows safe subtyping - * declare const fiber: Fiber.Fiber + * const fiber: Fiber.Fiber = Effect.runFork(Effect.succeed(1)) * const upcast: Fiber.Fiber = fiber + * const actual = await Effect.runPromise(Fiber.join(upcast)) + * actual // => 1 * ``` * * @category models @@ -178,14 +179,16 @@ export { * * **Example** (Awaiting a fiber exit) * - * ```ts - * import { Effect, Fiber } from "effect" + * ```ts import.meta.vitest + * import { Effect, Exit, Fiber } from "effect" * * const program = Effect.gen(function*() { * const fiber = yield* Effect.forkChild(Effect.succeed(42)) - * const exit = yield* Fiber.await(fiber) - * console.log(exit) // Exit.succeed(42) + * return yield* Fiber.await(fiber) * }) + * + * const actual = await Effect.runPromise(program) + * actual // => Exit.succeed(42) * ``` * * @category combinators @@ -213,15 +216,17 @@ export { * * **Example** (Awaiting multiple fiber exits) * - * ```ts - * import { Effect, Fiber } from "effect" + * ```ts import.meta.vitest + * import { Effect, Exit, Fiber } from "effect" * * const program = Effect.gen(function*() { * const fiber1 = yield* Effect.forkChild(Effect.succeed(1)) * const fiber2 = yield* Effect.forkChild(Effect.succeed(2)) - * const exits = yield* Fiber.awaitAll([fiber1, fiber2]) - * console.log(exits) // [Exit.succeed(1), Exit.succeed(2)] + * return yield* Fiber.awaitAll([fiber1, fiber2]) * }) + * + * const actual = await Effect.runPromise(program) + * actual // => [Exit.succeed(1), Exit.succeed(2)] * ``` * * @category combinators @@ -254,14 +259,16 @@ export const awaitAll: >( * * **Example** (Joining a fiber) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Fiber } from "effect" * * const program = Effect.gen(function*() { * const fiber = yield* Effect.forkChild(Effect.succeed(42)) - * const result = yield* Fiber.join(fiber) - * console.log(result) // 42 + * return yield* Fiber.join(fiber) * }) + * + * const actual = await Effect.runPromise(program) + * actual // => 42 * ``` * * @see {@link await_ await} for inspecting the fiber outcome as an Exit @@ -302,7 +309,7 @@ export const joinAll: >>( A, A extends Iterable> ? _A : never >, - A extends Fiber ? _E : never + A extends Iterable> ? _E : never > = effect.fiberJoinAll /** @@ -325,7 +332,7 @@ export const joinAll: >>( * * **Example** (Interrupting a fiber) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Fiber } from "effect" * * const program = Effect.gen(function*() { @@ -333,8 +340,9 @@ export const joinAll: >>( * Effect.delay("1 second")(Effect.succeed(42)) * ) * yield* Fiber.interrupt(fiber) - * console.log("Fiber interrupted") * }) + * + * await Effect.runPromise(program) * ``` * * @see {@link interruptAs} for specifying the interrupting fiber ID @@ -365,7 +373,7 @@ export const interrupt: (self: Fiber) => Effect = effect.fiber * * **Example** (Interrupting a fiber as another fiber) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Fiber } from "effect" * * const program = Effect.gen(function*() { @@ -375,8 +383,9 @@ export const interrupt: (self: Fiber) => Effect = effect.fiber * * // Interrupt the fiber, specifying fiber ID 123 as the interruptor * yield* Fiber.interruptAs(targetFiber, 123) - * console.log("Fiber interrupted by fiber #123") * }) + * + * await Effect.runPromise(program) * ``` * * @see {@link interrupt} for using the current fiber as the interruptor @@ -417,41 +426,17 @@ export const interruptAs: { * * **Example** (Interrupting multiple fibers) * - * ```ts - * import { Console, Effect, Fiber } from "effect" + * ```ts import.meta.vitest + * import { Effect, Fiber } from "effect" * * const program = Effect.gen(function*() { - * // Create multiple long-running fibers - * const fiber1 = yield* Effect.forkChild( - * Effect.gen(function*() { - * yield* Effect.sleep("5 seconds") - * yield* Console.log("Task 1 completed") - * return "result1" - * }) - * ) - * - * const fiber2 = yield* Effect.forkChild( - * Effect.gen(function*() { - * yield* Effect.sleep("3 seconds") - * yield* Console.log("Task 2 completed") - * return "result2" - * }) - * ) - * - * const fiber3 = yield* Effect.forkChild( - * Effect.gen(function*() { - * yield* Effect.sleep("4 seconds") - * yield* Console.log("Task 3 completed") - * return "result3" - * }) - * ) - * - * // Wait a bit, then interrupt all fibers - * yield* Effect.sleep("1 second") - * yield* Console.log("Interrupting all fibers...") + * const fiber1 = yield* Effect.forkChild(Effect.never) + * const fiber2 = yield* Effect.forkChild(Effect.never) + * const fiber3 = yield* Effect.forkChild(Effect.never) * yield* Fiber.interruptAll([fiber1, fiber2, fiber3]) - * yield* Console.log("All fibers have been interrupted") * }) + * + * await Effect.runPromise(program) * ``` * * @see {@link interruptAllAs} for specifying the interrupting fiber ID @@ -485,36 +470,20 @@ export const interruptAll: >>( * * **Example** (Interrupting multiple fibers as another fiber) * - * ```ts - * import { Console, Effect, Fiber } from "effect" + * ```ts import.meta.vitest + * import { Effect, Fiber } from "effect" * * const program = Effect.gen(function*() { * // Create a controlling fiber * const controllerFiber = yield* Effect.forkChild(Effect.succeed("controller")) * - * // Create multiple worker fibers - * const worker1 = yield* Effect.forkChild( - * Effect.gen(function*() { - * yield* Effect.sleep("5 seconds") - * yield* Console.log("Worker 1 completed") - * return "worker1" - * }) - * ) - * - * const worker2 = yield* Effect.forkChild( - * Effect.gen(function*() { - * yield* Effect.sleep("3 seconds") - * yield* Console.log("Worker 2 completed") - * return "worker2" - * }) - * ) + * const worker1 = yield* Effect.forkChild(Effect.never) + * const worker2 = yield* Effect.forkChild(Effect.never) * - * // Interrupt all workers using the controller fiber's ID - * yield* Effect.sleep("1 second") - * yield* Console.log("Interrupting workers from controller...") * yield* Fiber.interruptAllAs([worker1, worker2], controllerFiber.id) - * yield* Console.log("All workers interrupted by controller") * }) + * + * await Effect.runPromise(program) * ``` * * @see {@link interruptAll} for using the current fiber as the interruptor @@ -543,7 +512,7 @@ export const interruptAllAs: { * * **Example** (Checking for fibers) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Fiber } from "effect" * * const program = Effect.gen(function*() { @@ -551,18 +520,11 @@ export const interruptAllAs: { * const fiber = yield* Effect.forkChild(Effect.succeed(42)) * * // Test if values are fibers - * console.log(Fiber.isFiber(fiber)) // true - * console.log(Fiber.isFiber("hello")) // false - * console.log(Fiber.isFiber(42)) // false - * console.log(Fiber.isFiber(null)) // false - * - * // Use as a type guard - * const maybeValue: unknown = fiber - * if (Fiber.isFiber(maybeValue)) { - * // TypeScript knows maybeValue is a Fiber here - * console.log(`Fiber ID: ${maybeValue.id}`) - * } + * return [Fiber.isFiber(fiber), Fiber.isFiber("hello"), Fiber.isFiber(42), Fiber.isFiber(null)] * }) + * + * const actual = await Effect.runPromise(program) + * actual // => [true, false, false, false] * ``` * * @category guards @@ -588,18 +550,19 @@ export const isFiber = ( * * **Example** (Getting the current fiber) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Fiber } from "effect" * * const program = Effect.gen(function*() { * const current = Fiber.getCurrent() - * if (current) { - * console.log(`Current fiber ID: ${current.id}`) - * } + * return current !== undefined * }) + * + * const actual = await Effect.runPromise(program) + * actual // => true * ``` * - * @category accessors + * @category getters * @since 4.0.0 */ export const getCurrent: () => Fiber | undefined = effect.getCurrentFiber diff --git a/repos/effect/packages/effect/src/FiberHandle.ts b/repos/effect/packages/effect/src/FiberHandle.ts index fe1b3f8342..7a004bfa15 100644 --- a/repos/effect/packages/effect/src/FiberHandle.ts +++ b/repos/effect/packages/effect/src/FiberHandle.ts @@ -35,18 +35,20 @@ const TypeId = "~effect/FiberHandle" * * **Example** (Managing a single fiber) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Fiber, FiberHandle } from "effect" * - * Effect.gen(function*() { + * const program = Effect.gen(function*() { * // Create a FiberHandle that can hold fibers producing strings * const handle = yield* FiberHandle.make() * * // The handle can store and manage a single fiber * const fiber = yield* FiberHandle.run(handle, Effect.succeed("hello")) - * const result = yield* Fiber.await(fiber) - * console.log(result) // "hello" + * return yield* Fiber.join(fiber) * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => "hello" * ``` * * @category models @@ -69,18 +71,20 @@ export interface FiberHandle extends Pipeable, * * **Example** (Checking fiber handles) * - * ```ts + * ```ts import.meta.vitest * import { Effect, FiberHandle } from "effect" * - * Effect.gen(function*() { + * const program = Effect.gen(function*() { * const handle = yield* FiberHandle.make() * - * console.log(FiberHandle.isFiberHandle(handle)) // true - * console.log(FiberHandle.isFiberHandle("not a handle")) // false + * return [FiberHandle.isFiberHandle(handle), FiberHandle.isFiberHandle("not a handle")] * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => [true, false] * ``` * - * @category refinements + * @category guards * @since 2.0.0 */ export const isFiberHandle = (u: unknown): u is FiberHandle => Predicate.hasProperty(u, TypeId) @@ -115,10 +119,10 @@ const makeUnsafe = (): FiberHandle => { * * **Example** (Creating a scoped fiber handle) * - * ```ts + * ```ts import.meta.vitest * import { Effect, FiberHandle } from "effect" * - * Effect.gen(function*() { + * const program = Effect.gen(function*() { * const handle = yield* FiberHandle.make() * * // run some effects @@ -126,10 +130,14 @@ const makeUnsafe = (): FiberHandle => { * // this will interrupt the previous fiber * yield* FiberHandle.run(handle, Effect.never) * - * yield* Effect.sleep(1000) + * yield* Effect.yieldNow + * return handle.state._tag === "Open" && handle.state.fiber !== undefined * }).pipe( * Effect.scoped // The fiber will be interrupted when the scope is closed * ) + * + * const actual = await Effect.runPromise(program) + * actual // => true * ``` * * @category constructors @@ -162,20 +170,24 @@ export const make = (): Effect.Effect() * * // Run effects and get fibers back - * const fiberA = run(Effect.succeed("first")) + * const fiberA = run(Effect.never) * const fiberB = run(Effect.succeed("second")) * * // The second fiber will interrupt the first * const resultA = yield* Fiber.await(fiberA) * const resultB = yield* Fiber.await(fiberB) + * return [resultA, resultB] * }).pipe(Effect.scoped) + * + * const actual = await Effect.runPromise(program) + * actual // => [Exit.failCause(Cause.interrupt(-1)), Exit.succeed("second")] * ``` * * @category constructors @@ -218,17 +230,19 @@ export const makeRuntime = (): Effect.Effect< * * **Example** (Running effects as promises) * - * ```ts + * ```ts import.meta.vitest * import { Effect, FiberHandle } from "effect" * - * Effect.gen(function*() { + * const program = Effect.gen(function*() { * const run = yield* FiberHandle.makeRuntimePromise() * * // Run effects and get promises back * const promise = run(Effect.succeed("hello")) - * const result = yield* Effect.promise(() => promise) - * console.log(result) // "hello" + * return yield* Effect.promise(() => promise) * }).pipe(Effect.scoped) + * + * const actual = await Effect.runPromise(program) + * actual // => "hello" * ``` * * @category constructors @@ -269,10 +283,10 @@ const isInternalInterruption = Filter.toPredicate(Filter.compose( * * **Example** (Setting a fiber unsafely) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Fiber, FiberHandle } from "effect" * - * Effect.gen(function*() { + * const program = Effect.gen(function*() { * const handle = yield* FiberHandle.make() * const fiber = Effect.runFork(Effect.succeed("hello")) * @@ -280,9 +294,11 @@ const isInternalInterruption = Filter.toPredicate(Filter.compose( * FiberHandle.setUnsafe(handle, fiber) * * // The fiber is now managed by the handle - * const result = yield* Fiber.await(fiber) - * console.log(result) // "hello" + * return yield* Fiber.join(fiber) * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => "hello" * ``` * * @category combinators @@ -355,10 +371,10 @@ export const setUnsafe: { * * **Example** (Setting a fiber safely) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Fiber, FiberHandle } from "effect" * - * Effect.gen(function*() { + * const program = Effect.gen(function*() { * const handle = yield* FiberHandle.make() * const fiber = Effect.runFork(Effect.succeed("hello")) * @@ -366,9 +382,11 @@ export const setUnsafe: { * yield* FiberHandle.set(handle, fiber) * * // The fiber is now managed by the handle - * const result = yield* Fiber.await(fiber) - * console.log(result) // "hello" + * return yield* Fiber.join(fiber) * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => "hello" * ``` * * @category combinators @@ -415,21 +433,23 @@ export const set: { * * **Example** (Reading the current fiber unsafely) * - * ```ts - * import { Effect, FiberHandle } from "effect" + * ```ts import.meta.vitest + * import { Effect, FiberHandle, Option } from "effect" * - * Effect.gen(function*() { + * const program = Effect.gen(function*() { * const handle = yield* FiberHandle.make() * * // No fiber initially * const emptyFiber = FiberHandle.getUnsafe(handle) - * console.log(emptyFiber._tag === "None") // true * * // Add a fiber - * yield* FiberHandle.run(handle, Effect.succeed("hello")) + * yield* FiberHandle.run(handle, Effect.never) * const fiber = FiberHandle.getUnsafe(handle) - * console.log(fiber._tag === "Some") // true + * return [emptyFiber, Option.map(fiber, () => true)] * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => [Option.none(), Option.some(true)] * ``` * * @category combinators @@ -444,22 +464,22 @@ export function getUnsafe(self: FiberHandle): Option.Option true) * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => Option.some(true) * ``` * * @category combinators @@ -475,10 +495,10 @@ export function get(self: FiberHandle): Effect.Effect(self: FiberHandle): Effect.Effect Option.none() * ``` * * @category combinators @@ -501,10 +523,11 @@ export const clear = (self: FiberHandle): Effect.Effect => if (self.state._tag === "Closed" || self.state.fiber === undefined) { return Effect.void } + const fiber = self.state.fiber return Effect.andThen( - restore(Fiber.interruptAs(self.state.fiber, internalFiberId)), + restore(Fiber.interruptAs(fiber, internalFiberId)), Effect.sync(() => { - if (self.state._tag === "Open") { + if (self.state._tag === "Open" && self.state.fiber === fiber) { self.state.fiber = undefined } }) @@ -532,22 +555,24 @@ const constInterruptedFiber = (function() { * * **Example** (Running an effect in a fiber handle) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Fiber, FiberHandle } from "effect" * - * Effect.gen(function*() { + * const program = Effect.gen(function*() { * const handle = yield* FiberHandle.make() * * // Run an effect and get the fiber * const fiber = yield* FiberHandle.run(handle, Effect.succeed("hello")) - * const result = yield* Fiber.await(fiber) - * console.log(result) // "hello" + * const result = yield* Fiber.join(fiber) * * // Running another effect will interrupt the previous one * const fiber2 = yield* FiberHandle.run(handle, Effect.succeed("world")) - * const result2 = yield* Fiber.await(fiber2) - * console.log(result2) // "world" + * const result2 = yield* Fiber.join(fiber2) + * return [result, result2] * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => ["hello", "world"] * ``` * * @category combinators @@ -611,28 +636,32 @@ const runImpl = ( * * **Example** (Capturing a runtime for fiber handles) * - * ```ts - * import { Context, Effect, FiberHandle } from "effect" + * ```ts import.meta.vitest + * import { Context, Effect, Fiber, FiberHandle } from "effect" * - * interface Users { - * readonly _: unique symbol - * } - * const Users = Context.Service> - * }>("Users") + * class Users extends Context.Service> + * }>()("Users") {} * - * Effect.gen(function*() { + * const program = Effect.gen(function*() { * const handle = yield* FiberHandle.make() * const run = yield* FiberHandle.runtime(handle)() * * // run an effect and set the fiber in the handle - * run(Effect.andThen(Users, (_) => _.getAll)) + * const fiberA = run(Effect.andThen(Users, (_) => _.getAll)) * * // this will interrupt the previous fiber - * run(Effect.andThen(Users, (_) => _.getAll)) + * const fiberB = run(Effect.andThen(Users, (_) => _.getAll)) + * yield* Fiber.await(fiberA) + * return (yield* Fiber.join(fiberB)).length * }).pipe( * Effect.scoped // The fiber will be interrupted when the scope is closed * ) + * + * const actual = await Effect.runPromise(Effect.provideService(program, Users, { + * getAll: Effect.succeed([]) + * })) + * actual // => 0 * ``` * * @category combinators @@ -694,18 +723,20 @@ export const runtime: ( * * **Example** (Capturing a runtime for promises) * - * ```ts + * ```ts import.meta.vitest * import { Effect, FiberHandle } from "effect" * - * Effect.gen(function*() { + * const program = Effect.gen(function*() { * const handle = yield* FiberHandle.make() * const runPromise = yield* FiberHandle.runtimePromise(handle)() * * // Run an effect and get a promise * const promise = runPromise(Effect.succeed("hello")) - * const result = yield* Effect.promise(() => promise) - * console.log(result) // "hello" + * return yield* Effect.promise(() => promise) * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => "hello" * ``` * * @category combinators @@ -764,16 +795,19 @@ export const runtimePromise = (self: FiberHandle): () => * * **Example** (Propagating fiber failures) * - * ```ts - * import { Effect, FiberHandle } from "effect" + * ```ts import.meta.vitest + * import { Effect, Exit, FiberHandle } from "effect" * - * Effect.gen(function*() { + * const program = Effect.gen(function*() { * const handle = yield* FiberHandle.make() * yield* FiberHandle.set(handle, Effect.runFork(Effect.fail("error"))) * * // parent fiber will fail with "error" * yield* FiberHandle.join(handle) * }) + * + * const actual = await Effect.runPromise(Effect.exit(Effect.scoped(program))) + * actual // => Exit.fail("error") * ``` * * @category combinators @@ -787,20 +821,22 @@ export const join = (self: FiberHandle): Effect.Effect => * * **Example** (Waiting for a fiber to complete) * - * ```ts - * import { Effect, FiberHandle } from "effect" + * ```ts import.meta.vitest + * import { Effect, FiberHandle, Option } from "effect" * - * Effect.gen(function*() { + * const program = Effect.gen(function*() { * const handle = yield* FiberHandle.make() * - * // Start a long-running effect - * yield* FiberHandle.run(handle, Effect.sleep(1000)) + * yield* FiberHandle.run(handle, Effect.yieldNow) * * // Wait for the fiber to complete * yield* FiberHandle.awaitEmpty(handle) * - * console.log("Fiber completed") + * return yield* FiberHandle.get(handle) * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => Option.none() * ``` * * @category combinators diff --git a/repos/effect/packages/effect/src/FiberMap.ts b/repos/effect/packages/effect/src/FiberMap.ts index 9684132778..828632ac55 100644 --- a/repos/effect/packages/effect/src/FiberMap.ts +++ b/repos/effect/packages/effect/src/FiberMap.ts @@ -35,7 +35,7 @@ const TypeId = "~effect/FiberMap" * * **Example** (Managing fibers in a map) * - * ```ts + * ```ts import.meta.vitest * import { Effect, FiberMap } from "effect" * * // Create a FiberMap with string keys @@ -47,9 +47,11 @@ const TypeId = "~effect/FiberMap" * yield* FiberMap.run(map, "task2", Effect.never) * * // Get the size of the map - * const size = yield* FiberMap.size(map) - * console.log(size) // 2 + * return yield* FiberMap.size(map) * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => 2 * ``` * * @category models @@ -77,19 +79,20 @@ export interface FiberMap * * **Example** (Checking if a value is a FiberMap) * - * ```ts + * ```ts import.meta.vitest * import { Effect, FiberMap } from "effect" * * const program = Effect.gen(function*() { * const map = yield* FiberMap.make() * - * console.log(FiberMap.isFiberMap(map)) // true - * console.log(FiberMap.isFiberMap({})) // false - * console.log(FiberMap.isFiberMap(null)) // false + * return [FiberMap.isFiberMap(map), FiberMap.isFiberMap({}), FiberMap.isFiberMap(null)] * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => [true, false, false] * ``` * - * @category refinements + * @category guards * @since 2.0.0 */ export const isFiberMap = (u: unknown): u is FiberMap => Predicate.hasProperty(u, TypeId) @@ -133,20 +136,24 @@ const makeUnsafe = ( * * **Example** (Creating a scoped FiberMap) * - * ```ts + * ```ts import.meta.vitest * import { Effect, FiberMap } from "effect" * - * Effect.gen(function*() { + * const program = Effect.gen(function*() { * const map = yield* FiberMap.make() * * // run some effects and add the fibers to the map * yield* FiberMap.run(map, "fiber a", Effect.never) * yield* FiberMap.run(map, "fiber b", Effect.never) * - * yield* Effect.sleep(1000) + * yield* Effect.yieldNow + * return yield* FiberMap.size(map) * }).pipe( * Effect.scoped // The fibers will be interrupted when the scope is closed * ) + * + * const actual = await Effect.runPromise(program) + * actual // => 2 * ``` * * @category constructors @@ -183,7 +190,7 @@ export const make = (): Effect.Effect(): Effect.Effect ["Hello", "World"] * ``` * * @category constructors @@ -240,7 +247,7 @@ export const makeRuntime = (): Effect.Effect< * * **Example** (Creating a promise runtime) * - * ```ts + * ```ts import.meta.vitest * import { Effect, FiberMap } from "effect" * * const program = Effect.gen(function*() { @@ -251,11 +258,11 @@ export const makeRuntime = (): Effect.Effect< * const promise2 = run("task2", Effect.succeed("World")) * * // Convert to Effect and await - * const result1 = yield* Effect.promise(() => promise1) - * const result2 = yield* Effect.promise(() => promise2) - * - * console.log(result1, result2) // "Hello", "World" + * return [yield* Effect.promise(() => promise1), yield* Effect.promise(() => promise2)] * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => ["Hello", "World"] * ``` * * @category constructors @@ -302,7 +309,7 @@ const isInternalInterruption = Filter.toPredicate(Filter.compose( * * **Example** (Adding a fiber unsafely) * - * ```ts + * ```ts import.meta.vitest * import { Deferred, Effect, Fiber, FiberMap } from "effect" * * const program = Effect.gen(function*() { @@ -316,9 +323,11 @@ const isInternalInterruption = Filter.toPredicate(Filter.compose( * yield* Deferred.succeed(deferred, "Hello") * * // Join the fiber to get its successful value - * const result = yield* Fiber.join(fiber) - * console.log(result) // "Hello" + * return yield* Fiber.join(fiber) * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => "Hello" * ``` * * @category combinators @@ -402,7 +411,7 @@ export const setUnsafe: { * * **Example** (Adding a fiber) * - * ```ts + * ```ts import.meta.vitest * import { Deferred, Effect, Fiber, FiberMap } from "effect" * * const program = Effect.gen(function*() { @@ -416,9 +425,11 @@ export const setUnsafe: { * yield* Deferred.succeed(deferred, "Hello") * * // Join the fiber to get its successful value - * const result = yield* Fiber.join(fiber) - * console.log(result) // "Hello" + * return yield* Fiber.join(fiber) * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => "Hello" * ``` * * @category combinators @@ -462,8 +473,8 @@ export const set: { * * **Example** (Retrieving a fiber unsafely) * - * ```ts - * import { Deferred, Effect, Fiber, FiberMap } from "effect" + * ```ts import.meta.vitest + * import { Deferred, Effect, Fiber, FiberMap, Option } from "effect" * * const program = Effect.gen(function*() { * const map = yield* FiberMap.make() @@ -475,13 +486,13 @@ export const set: { * * // Retrieve the fiber * const retrieved = FiberMap.getUnsafe(map, "greeting") - * if (retrieved._tag === "Some") { - * yield* Deferred.succeed(deferred, "Hello") - * - * const result = yield* Fiber.join(retrieved.value) - * console.log(result) // "Hello" - * } + * yield* Deferred.succeed(deferred, "Hello") + * const result = yield* Fiber.join(fiber) + * return Option.map(retrieved, () => result) * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => Option.some("Hello") * ``` * * @category combinators @@ -506,8 +517,8 @@ export const getUnsafe: { * * **Example** (Retrieving a fiber) * - * ```ts - * import { Deferred, Effect, Fiber, FiberMap } from "effect" + * ```ts import.meta.vitest + * import { Deferred, Effect, Fiber, FiberMap, Option } from "effect" * * const program = Effect.gen(function*() { * const map = yield* FiberMap.make() @@ -519,13 +530,13 @@ export const getUnsafe: { * * // Retrieve the fiber with error handling * const retrieved = yield* FiberMap.get(map, "greeting") - * if (retrieved._tag === "Some") { - * yield* Deferred.succeed(deferred, "Hello") - * - * const result = yield* Fiber.join(retrieved.value) - * console.log(result) // "Hello" - * } + * yield* Deferred.succeed(deferred, "Hello") + * const result = yield* Fiber.join(fiber) + * return Option.map(retrieved, () => result) * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => Option.some("Hello") * ``` * * @category combinators @@ -545,7 +556,7 @@ export const get: { * * **Example** (Checking if a key exists unsafely) * - * ```ts + * ```ts import.meta.vitest * import { Effect, FiberMap } from "effect" * * const program = Effect.gen(function*() { @@ -555,9 +566,11 @@ export const get: { * yield* FiberMap.run(map, "task1", Effect.never) * * // Check if keys exist - * console.log(FiberMap.hasUnsafe(map, "task1")) // true - * console.log(FiberMap.hasUnsafe(map, "task2")) // false + * return [FiberMap.hasUnsafe(map, "task1"), FiberMap.hasUnsafe(map, "task2")] * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => [true, false] * ``` * * @category combinators @@ -578,7 +591,7 @@ export const hasUnsafe: { * * **Example** (Checking if a key exists) * - * ```ts + * ```ts import.meta.vitest * import { Effect, FiberMap } from "effect" * * const program = Effect.gen(function*() { @@ -588,12 +601,11 @@ export const hasUnsafe: { * yield* FiberMap.run(map, "task1", Effect.never) * * // Check if keys exist using Effect - * const exists1 = yield* FiberMap.has(map, "task1") - * const exists2 = yield* FiberMap.has(map, "task2") - * - * console.log(exists1) // true - * console.log(exists2) // false + * return [yield* FiberMap.has(map, "task1"), yield* FiberMap.has(map, "task2")] * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => [true, false] * ``` * * @category combinators @@ -612,7 +624,7 @@ export const has: { * * **Example** (Removing a fiber) * - * ```ts + * ```ts import.meta.vitest * import { Effect, FiberMap } from "effect" * * const program = Effect.gen(function*() { @@ -622,13 +634,16 @@ export const has: { * yield* FiberMap.run(map, "task1", Effect.never) * yield* FiberMap.run(map, "task2", Effect.never) * - * console.log(yield* FiberMap.size(map)) // 2 + * const sizeBefore = yield* FiberMap.size(map) * * // Remove a specific fiber (this will interrupt it) * yield* FiberMap.remove(map, "task1") * - * console.log(yield* FiberMap.size(map)) // 1 + * return [sizeBefore, yield* FiberMap.size(map)] * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => [2, 1] * ``` * * @category combinators @@ -662,7 +677,7 @@ export const remove: { * * **Example** (Clearing all fibers) * - * ```ts + * ```ts import.meta.vitest * import { Effect, FiberMap } from "effect" * * const program = Effect.gen(function*() { @@ -673,13 +688,16 @@ export const remove: { * yield* FiberMap.run(map, "task2", Effect.never) * yield* FiberMap.run(map, "task3", Effect.never) * - * console.log(yield* FiberMap.size(map)) // 3 + * const sizeBefore = yield* FiberMap.size(map) * * // Clear all fibers (this will interrupt all of them) * yield* FiberMap.clear(map) * - * console.log(yield* FiberMap.size(map)) // 0 + * return [sizeBefore, yield* FiberMap.size(map)] * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => [3, 0] * ``` * * @category combinators @@ -713,7 +731,7 @@ const constInterruptedFiber = (function() { * * **Example** (Forking effects into a map) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Fiber, FiberMap } from "effect" * * const program = Effect.gen(function*() { @@ -726,10 +744,11 @@ const constInterruptedFiber = (function() { * // Join the fibers to get their successful values * const result1 = yield* Fiber.join(fiber1) * const result2 = yield* Fiber.join(fiber2) - * - * console.log(result1, result2) // "Hello", "World" - * console.log(yield* FiberMap.size(map)) // 0 (fibers are removed after completion) + * return [result1, result2, yield* FiberMap.size(map)] * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => ["Hello", "World", 0] * ``` * * @category combinators @@ -798,26 +817,29 @@ const runImpl = ( * * **Example** (Capturing a runtime) * - * ```ts - * import { Context, Effect, FiberMap } from "effect" + * ```ts import.meta.vitest + * import { Context, Effect, Fiber, FiberMap } from "effect" * - * interface Users { - * readonly _: unique symbol - * } - * const Users = Context.Service> - * }>("Users") + * class Users extends Context.Service> + * }>()("Users") {} * - * Effect.gen(function*() { + * const program = Effect.gen(function*() { * const map = yield* FiberMap.make() * const run = yield* FiberMap.runtime(map)() * * // run some effects and add the fibers to the map - * run("effect-a", Effect.andThen(Users, (_) => _.getAll)) - * run("effect-b", Effect.andThen(Users, (_) => _.getAll)) + * const fiberA = run("effect-a", Effect.andThen(Users, (_) => _.getAll)) + * const fiberB = run("effect-b", Effect.andThen(Users, (_) => _.getAll)) + * return [(yield* Fiber.join(fiberA)).length, (yield* Fiber.join(fiberB)).length] * }).pipe( * Effect.scoped // The fibers will be interrupted when the scope is closed * ) + * + * const actual = await Effect.runPromise(Effect.provideService(program, Users, { + * getAll: Effect.succeed([]) + * })) + * actual // => [0, 0] * ``` * * @category combinators @@ -878,7 +900,7 @@ export const runtime: ( * * **Example** (Running effects as promises) * - * ```ts + * ```ts import.meta.vitest * import { Effect, FiberMap } from "effect" * * const program = Effect.gen(function*() { @@ -890,11 +912,11 @@ export const runtime: ( * const promise2 = runPromise("task2", Effect.succeed("World")) * * // Convert promises back to Effects and await - * const result1 = yield* Effect.promise(() => promise1) - * const result2 = yield* Effect.promise(() => promise2) - * - * console.log(result1, result2) // "Hello", "World" + * return [yield* Effect.promise(() => promise1), yield* Effect.promise(() => promise2)] * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => ["Hello", "World"] * ``` * * @category combinators @@ -941,20 +963,23 @@ export const runtimePromise = (self: FiberMap): () * * **Example** (Checking the map size) * - * ```ts + * ```ts import.meta.vitest * import { Effect, FiberMap } from "effect" * * const program = Effect.gen(function*() { * const map = yield* FiberMap.make() * - * console.log(yield* FiberMap.size(map)) // 0 + * const sizeBefore = yield* FiberMap.size(map) * * // Add some fibers * yield* FiberMap.run(map, "task1", Effect.never) * yield* FiberMap.run(map, "task2", Effect.never) * - * console.log(yield* FiberMap.size(map)) // 2 + * return [sizeBefore, yield* FiberMap.size(map)] * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => [0, 2] * ``` * * @category combinators @@ -975,16 +1000,19 @@ export const size = (self: FiberMap): Effect.Effect => * * **Example** (Joining failing fibers) * - * ```ts - * import { Effect, FiberMap } from "effect" + * ```ts import.meta.vitest + * import { Effect, Exit, FiberMap } from "effect" * - * Effect.gen(function*() { + * const program = Effect.gen(function*() { * const map = yield* FiberMap.make() * yield* FiberMap.set(map, "a", Effect.runFork(Effect.fail("error"))) * * // parent fiber will fail with "error" * yield* FiberMap.join(map) * }) + * + * const actual = await Effect.runPromise(Effect.exit(Effect.scoped(program))) + * actual // => Exit.fail("error") * ``` * * @category combinators @@ -999,24 +1027,23 @@ export const join = (self: FiberMap): Effect.Effect = * * **Example** (Waiting for an empty map) * - * ```ts + * ```ts import.meta.vitest * import { Effect, FiberMap } from "effect" * * const program = Effect.gen(function*() { * const map = yield* FiberMap.make() * - * // Add some fibers that will complete after a delay - * yield* FiberMap.run(map, "task1", Effect.sleep(1000)) - * yield* FiberMap.run(map, "task2", Effect.sleep(2000)) - * - * console.log("Waiting for all fibers to complete...") + * yield* FiberMap.run(map, "task1", Effect.yieldNow) + * yield* FiberMap.run(map, "task2", Effect.yieldNow) * * // Wait for the map to be empty * yield* FiberMap.awaitEmpty(map) * - * console.log("All fibers completed!") - * console.log(yield* FiberMap.size(map)) // 0 + * return yield* FiberMap.size(map) * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => 0 * ``` * * @category combinators diff --git a/repos/effect/packages/effect/src/FiberSet.ts b/repos/effect/packages/effect/src/FiberSet.ts index 7b7b36689c..f99040716f 100644 --- a/repos/effect/packages/effect/src/FiberSet.ts +++ b/repos/effect/packages/effect/src/FiberSet.ts @@ -31,7 +31,7 @@ const TypeId = "~effect/FiberSet" * * **Example** (Managing fibers in a set) * - * ```ts + * ```ts import.meta.vitest * import { Effect, FiberSet } from "effect" * * const program = Effect.gen(function*() { @@ -43,7 +43,11 @@ const TypeId = "~effect/FiberSet" * * // Wait for all fibers to complete * yield* FiberSet.awaitEmpty(set) + * return yield* FiberSet.size(set) * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => 0 * ``` * * @category models @@ -67,18 +71,20 @@ export interface FiberSet * * **Example** (Checking if a value is a FiberSet) * - * ```ts + * ```ts import.meta.vitest * import { Effect, FiberSet } from "effect" * - * Effect.gen(function*() { + * const program = Effect.gen(function*() { * const set = yield* FiberSet.make() * - * console.log(FiberSet.isFiberSet(set)) // true - * console.log(FiberSet.isFiberSet({})) // false + * return [FiberSet.isFiberSet(set), FiberSet.isFiberSet({})] * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => [true, false] * ``` * - * @category refinements + * @category guards * @since 2.0.0 */ export const isFiberSet = (u: unknown): u is FiberSet => Predicate.hasProperty(u, TypeId) @@ -94,7 +100,7 @@ const Proto = { ...PipeInspectableProto, toJSON(this: FiberSet) { return { - _id: "FiberMap", + _id: "FiberSet", state: this.state } } @@ -122,20 +128,24 @@ const makeUnsafe = ( * * **Example** (Creating a scoped FiberSet) * - * ```ts + * ```ts import.meta.vitest * import { Effect, FiberSet } from "effect" * - * Effect.gen(function*() { + * const program = Effect.gen(function*() { * const set = yield* FiberSet.make() * * // run some effects and add the fibers to the set * yield* FiberSet.run(set, Effect.never) * yield* FiberSet.run(set, Effect.never) * - * yield* Effect.sleep(1000) + * yield* Effect.yieldNow + * return yield* FiberSet.size(set) * }).pipe( * Effect.scoped // The fibers will be interrupted when the scope is closed * ) + * + * const actual = await Effect.runPromise(program) + * actual // => 2 * ``` * * @category constructors @@ -166,7 +176,7 @@ export const make = (): Effect.Effect, * * **Example** (Creating a scoped runtime) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Fiber, FiberSet } from "effect" * * const program = Effect.gen(function*() { @@ -176,11 +186,11 @@ export const make = (): Effect.Effect, * const fiber1 = runFork(Effect.succeed("hello")) * const fiber2 = runFork(Effect.succeed("world")) * - * const result1 = yield* Fiber.await(fiber1) - * const result2 = yield* Fiber.await(fiber2) - * - * console.log(result1, result2) // "hello" "world" + * return [yield* Fiber.join(fiber1), yield* Fiber.join(fiber2)] * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => ["hello", "world"] * ``` * * @category constructors @@ -216,7 +226,7 @@ export const makeRuntime = (): Effect.Effec * * **Example** (Creating a promise runtime) * - * ```ts + * ```ts import.meta.vitest * import { Effect, FiberSet } from "effect" * * const program = Effect.gen(function*() { @@ -226,11 +236,11 @@ export const makeRuntime = (): Effect.Effec * const promise1 = runPromise(Effect.succeed("hello")) * const promise2 = runPromise(Effect.succeed("world")) * - * const result1 = yield* Effect.promise(() => promise1) - * const result2 = yield* Effect.promise(() => promise2) - * - * console.log(result1, result2) // "hello" "world" + * return [yield* Effect.promise(() => promise1), yield* Effect.promise(() => promise2)] * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => ["hello", "world"] * ``` * * @category constructors @@ -272,19 +282,22 @@ const isInternalInterruption = Filter.toPredicate(Filter.compose( * * **Example** (Adding a fiber unsafely) * - * ```ts + * ```ts import.meta.vitest * import { Effect, FiberSet } from "effect" * * const program = Effect.gen(function*() { * const set = yield* FiberSet.make() - * const fiber = yield* Effect.forkChild(Effect.succeed("hello")) + * const fiber = yield* Effect.forkChild(Effect.never) * * // Unsafe add - doesn't return an Effect * FiberSet.addUnsafe(set, fiber) * * // The fiber is now managed by the set - * console.log(yield* FiberSet.size(set)) // 1 + * return yield* FiberSet.size(set) * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => 1 * ``` * * @category combinators @@ -341,19 +354,22 @@ export const addUnsafe: { * * **Example** (Adding a fiber) * - * ```ts + * ```ts import.meta.vitest * import { Effect, FiberSet } from "effect" * * const program = Effect.gen(function*() { * const set = yield* FiberSet.make() - * const fiber = yield* Effect.forkChild(Effect.succeed("hello")) + * const fiber = yield* Effect.forkChild(Effect.never) * * // Add the fiber to the set * yield* FiberSet.add(set, fiber) * * // The fiber is now managed by the set - * console.log(yield* FiberSet.size(set)) // 1 + * return yield* FiberSet.size(set) * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => 1 * ``` * * @category combinators @@ -389,7 +405,7 @@ export const add: { * * **Example** (Clearing all fibers) * - * ```ts + * ```ts import.meta.vitest * import { Effect, FiberSet } from "effect" * * const program = Effect.gen(function*() { @@ -399,13 +415,16 @@ export const add: { * yield* FiberSet.run(set, Effect.never) * yield* FiberSet.run(set, Effect.never) * - * console.log(yield* FiberSet.size(set)) // 2 + * const sizeBefore = yield* FiberSet.size(set) * * // Clear all fibers * yield* FiberSet.clear(set) * - * console.log(yield* FiberSet.size(set)) // 0 + * return [sizeBefore, yield* FiberSet.size(set)] * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => [2, 0] * ``` * * @category combinators @@ -435,7 +454,7 @@ const constInterruptedFiber = (function() { * * **Example** (Forking effects into a set) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Fiber, FiberSet } from "effect" * * const program = Effect.gen(function*() { @@ -446,11 +465,11 @@ const constInterruptedFiber = (function() { * const fiber2 = yield* FiberSet.run(set, Effect.succeed("world")) * * // Get results - * const result1 = yield* Fiber.await(fiber1) - * const result2 = yield* Fiber.await(fiber2) - * - * console.log(result1, result2) // "hello" "world" + * return [yield* Fiber.join(fiber1), yield* Fiber.join(fiber2)] * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => ["hello", "world"] * ``` * * @category combinators @@ -504,25 +523,28 @@ const runImpl = ( * * **Example** (Capturing a runtime) * - * ```ts - * import { Context, Effect, FiberSet } from "effect" + * ```ts import.meta.vitest + * import { Context, Effect, Fiber, FiberSet } from "effect" * - * interface Users { - * readonly _: unique symbol - * } - * const Users = Context.Service> - * }>("Users") + * class Users extends Context.Service> + * }>()("Users") {} * - * Effect.gen(function*() { + * const program = Effect.gen(function*() { * const set = yield* FiberSet.make() * const run = yield* FiberSet.runtime(set)() * * // run some effects and add the fibers to the set - * run(Effect.andThen(Users, (_) => _.getAll)) + * const fiber = run(Effect.andThen(Users, (_) => _.getAll)) + * return (yield* Fiber.join(fiber)).length * }).pipe( * Effect.scoped // The fibers will be interrupted when the scope is closed * ) + * + * const actual = await Effect.runPromise(Effect.provideService(program, Users, { + * getAll: Effect.succeed([]) + * })) + * actual // => 0 * ``` * * @category combinators @@ -554,7 +576,7 @@ export const runtime: ( return constInterruptedFiber() } const fiber = runFork(effect, options) - addUnsafe(self, fiber) + addUnsafe(self, fiber, options) return fiber } } @@ -575,7 +597,7 @@ export const runtime: ( * * **Example** (Running effects as promises) * - * ```ts + * ```ts import.meta.vitest * import { Effect, FiberSet } from "effect" * * const program = Effect.gen(function*() { @@ -586,11 +608,11 @@ export const runtime: ( * const promise1 = runPromise(Effect.succeed("hello")) * const promise2 = runPromise(Effect.succeed("world")) * - * const result1 = yield* Effect.promise(() => promise1) - * const result2 = yield* Effect.promise(() => promise2) - * - * console.log(result1, result2) // "hello" "world" + * return [yield* Effect.promise(() => promise1), yield* Effect.promise(() => promise2)] * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => ["hello", "world"] * ``` * * @see {@link runtime} for a runner that returns the forked `Fiber` @@ -634,20 +656,23 @@ export const runtimePromise = (self: FiberSet): () => Eff * * **Example** (Checking the set size) * - * ```ts + * ```ts import.meta.vitest * import { Effect, FiberSet } from "effect" * * const program = Effect.gen(function*() { * const set = yield* FiberSet.make() * - * console.log(yield* FiberSet.size(set)) // 0 + * const sizeBefore = yield* FiberSet.size(set) * * // Add some fibers * yield* FiberSet.run(set, Effect.never) * yield* FiberSet.run(set, Effect.never) * - * console.log(yield* FiberSet.size(set)) // 2 + * return [sizeBefore, yield* FiberSet.size(set)] * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => [0, 2] * ``` * * @category combinators @@ -662,16 +687,19 @@ export const size = (self: FiberSet): Effect.Effect => * * **Example** (Joining failing fibers) * - * ```ts - * import { Effect, FiberSet } from "effect" + * ```ts import.meta.vitest + * import { Effect, Exit, FiberSet } from "effect" * - * Effect.gen(function*() { + * const program = Effect.gen(function*() { * const set = yield* FiberSet.make() * yield* FiberSet.add(set, Effect.runFork(Effect.fail("error"))) * * // parent fiber will fail with "error" * yield* FiberSet.join(set) * }) + * + * const actual = await Effect.runPromise(Effect.exit(Effect.scoped(program))) + * actual // => Exit.fail("error") * ``` * * @category combinators @@ -685,21 +713,23 @@ export const join = (self: FiberSet): Effect.Effect => * * **Example** (Waiting for an empty set) * - * ```ts + * ```ts import.meta.vitest * import { Effect, FiberSet } from "effect" * * const program = Effect.gen(function*() { * const set = yield* FiberSet.make() * - * // Add some fibers that will complete - * yield* FiberSet.run(set, Effect.sleep(100)) - * yield* FiberSet.run(set, Effect.sleep(200)) + * yield* FiberSet.run(set, Effect.yieldNow) + * yield* FiberSet.run(set, Effect.yieldNow) * * // Wait for all fibers to complete * yield* FiberSet.awaitEmpty(set) * - * console.log(yield* FiberSet.size(set)) // 0 + * return yield* FiberSet.size(set) * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => 0 * ``` * * @category combinators diff --git a/repos/effect/packages/effect/src/FileSystem.ts b/repos/effect/packages/effect/src/FileSystem.ts index 0b4b191e5d..5cca2154df 100644 --- a/repos/effect/packages/effect/src/FileSystem.ts +++ b/repos/effect/packages/effect/src/FileSystem.ts @@ -11,7 +11,7 @@ * @since 4.0.0 */ import * as Arr from "./Array.ts" -import * as Brand from "./Brand.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" @@ -38,8 +38,15 @@ const TypeId = "~effect/platform/FileSystem" * * **Example** (Accessing file system operations) * - * ```ts - * import { Console, Effect, FileSystem } from "effect" + * ```ts import.meta.vitest + * import { 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 @@ -55,15 +62,17 @@ const TypeId = "~effect/platform/FileSystem" * * // File information * const stats = yield* fs.stat("./config.json") - * yield* Console.log(`File size: ${stats.size} bytes`) - * - * // Streaming operations + * // Read the file contents * const content = yield* fs.readFileString("./config.json") - * yield* Console.log("Config:", content) + * return { size: stats.size, content } * }) + * + * const result = Effect.runSync(Effect.provideService(program, FileSystem.FileSystem, fileSystem)) + * result.size // => 22n + * result.content // => "{\"env\": \"development\"}" * ``` * - * @category models + * @category services * @since 4.0.0 */ export interface FileSystem { @@ -118,6 +127,16 @@ export interface FileSystem { uid: number, gid: number ) => Effect.Effect + /** + * Glob a directory. + */ + readonly glob: ( + pattern: string, + options?: { + readonly root?: string | undefined + readonly exclude?: ReadonlyArray | undefined + } + ) => Effect.Effect, PlatformError> /** * Checks whether a path exists. */ @@ -331,9 +350,15 @@ export interface FileSystem { mtime: Date | number ) => Effect.Effect /** - * Watch a directory or file for changes + * Watch a directory or file for changes. + * + * **Details** + * + * By default, only changes to the direct children of the directory are + * reported. Set the `recursive` option to `true` to watch for changes in + * subdirectories as well. */ - readonly watch: (path: string) => Stream.Stream + readonly watch: (path: string, options?: WatchOptions) => Stream.Stream /** * Write data to a file at `path`. */ @@ -370,18 +395,11 @@ export interface FileSystem { * * **Example** (Creating branded file sizes) * - * ```ts - * import { Effect, FileSystem } from "effect" - * - * // Create sizes using the Size constructor - * const smallFile = FileSystem.Size(1024) // 1 KB - * const largeFile = FileSystem.Size(BigInt("9007199254740992")) // Very large + * ```ts import.meta.vitest + * import { FileSystem } from "effect" * - * // Use with file operations - * const truncateToSize = Effect.fnUntraced(function*(path: string, size: FileSystem.Size) { - * const fs = yield* FileSystem.FileSystem - * return yield* fs.truncate(path, size) - * }) + * FileSystem.Size(1024) // => 1024n + * FileSystem.Size(BigInt("9007199254740992")) // => 9007199254740992n * ``` * * @category sizes @@ -400,17 +418,15 @@ export type Size = Brand.Branded * * **Example** (Using size inputs) * - * ```ts - * import { Effect, FileSystem } from "effect" - * - * const program = Effect.gen(function*() { - * const fs = yield* FileSystem.FileSystem + * ```ts import.meta.vitest + * import { FileSystem } from "effect" * - * // All of these are valid SizeInput values - * yield* fs.truncate("file1.txt", 1024) // number - * yield* fs.truncate("file2.txt", BigInt(2048)) // bigint - * yield* fs.truncate("file3.txt", FileSystem.Size(4096)) // Size - * }) + * const inputs: ReadonlyArray = [ + * 1024, + * 2048n, + * FileSystem.Size(4096) + * ] + * inputs.map(FileSystem.Size) // => [1024n, 2048n, 4096n] * ``` * * @category sizes @@ -429,27 +445,19 @@ export type SizeInput = bigint | number | Size * * **Example** (Converting size inputs) * - * ```ts - * import { Effect, FileSystem } from "effect" + * ```ts import.meta.vitest + * import { FileSystem } from "effect" * * // From number * const size1 = FileSystem.Size(1024) - * console.log(typeof size1) // "bigint" + * typeof size1 // => "bigint" * * // From bigint * const size2 = FileSystem.Size(BigInt(2048)) * * // From existing Size (identity) * const size3 = FileSystem.Size(size1) - * - * // Use in file operations - * const readChunk = (path: string, chunkSize: number) => - * Effect.gen(function*() { - * const fs = yield* FileSystem.FileSystem - * return fs.stream(path, { - * chunkSize: FileSystem.Size(chunkSize) - * }) - * }) + * const sizes = [size2, size3] // => [2048n, 1024n] * ``` * * @category sizes @@ -467,22 +475,11 @@ export const Size = (bytes: SizeInput): Size => typeof bytes === "bigint" ? byte * * **Example** (Creating kibibyte sizes) * - * ```ts - * import { Effect, FileSystem } from "effect" - * - * const program = Effect.gen(function*() { - * const fs = yield* FileSystem.FileSystem - * - * // Create a 64 KiB buffer size for streaming - * const bufferSize = FileSystem.KiB(64) - * - * const stream = fs.stream("large-file.txt", { - * chunkSize: bufferSize - * }) + * ```ts import.meta.vitest + * import { FileSystem } from "effect" * - * // Truncate file to 100 KiB - * yield* fs.truncate("data.txt", FileSystem.KiB(100)) - * }) + * FileSystem.KiB(64) // => 65536n + * FileSystem.KiB(100) // => 102400n * ``` * * @category sizes @@ -500,26 +497,11 @@ export const KiB = (n: number): Size => Size(n * 1024) * * **Example** (Creating mebibyte sizes) * - * ```ts - * import { Effect, FileSystem } from "effect" - * - * const program = Effect.gen(function*() { - * const fs = yield* FileSystem.FileSystem - * - * // Set a 10 MiB chunk size for large file operations - * const largeChunkSize = FileSystem.MiB(10) + * ```ts import.meta.vitest + * import { FileSystem } from "effect" * - * const stream = fs.stream("video.mp4", { - * chunkSize: largeChunkSize - * }) - * - * // Check if file is larger than 100 MiB - * const stats = yield* fs.stat("archive.zip") - * const maxSize = FileSystem.MiB(100) - * if (stats.size > maxSize) { - * yield* Effect.log("File is very large!") - * } - * }) + * FileSystem.MiB(10) // => 10485760n + * FileSystem.MiB(100) // => 104857600n * ``` * * @category sizes @@ -537,24 +519,10 @@ export const MiB = (n: number): Size => Size(n * 1024 * 1024) * * **Example** (Creating gibibyte sizes) * - * ```ts - * import { Effect, FileSystem } from "effect" - * - * const program = Effect.gen(function*() { - * const fs = yield* FileSystem.FileSystem - * - * // Use GiB values as size thresholds - * const maxArchiveSize = FileSystem.GiB(1) - * console.log(maxArchiveSize.toString()) // "1073741824" - * - * const tempFile = yield* fs.makeTempFile({ prefix: "archive-" }) - * yield* fs.writeFileString(tempFile, "backup data") - * - * const info = yield* fs.stat(tempFile) - * console.log(info.size < maxArchiveSize) // true + * ```ts import.meta.vitest + * import { FileSystem } from "effect" * - * yield* fs.remove(tempFile) - * }) + * FileSystem.GiB(1) // => 1073741824n * ``` * * @category sizes @@ -572,25 +540,10 @@ export const GiB = (n: number): Size => Size(n * 1024 * 1024 * 1024) * * **Example** (Creating tebibyte sizes) * - * ```ts - * import { Console, Effect, FileSystem } from "effect" - * - * const program = Effect.gen(function*() { - * const fs = yield* FileSystem.FileSystem - * - * // Check if we're dealing with very large files - * const stats = yield* fs.stat("database-backup.sql") - * const oneTiB = FileSystem.TiB(1) - * - * if (stats.size > oneTiB) { - * yield* Console.log("This is a very large database backup!") + * ```ts import.meta.vitest + * import { FileSystem } from "effect" * - * // Use larger chunk sizes for such files - * const stream = fs.stream("database-backup.sql", { - * chunkSize: FileSystem.MiB(100) // 100 MiB chunks - * }) - * } - * }) + * FileSystem.TiB(1) // => 1099511627776n * ``` * * @category sizes @@ -612,24 +565,10 @@ const bigintPiB = bigint1024 * bigint1024 * bigint1024 * bigint1024 * bigint1024 * * **Example** (Creating pebibyte sizes) * - * ```ts - * import { Console, Effect, FileSystem } from "effect" + * ```ts import.meta.vitest + * import { FileSystem } from "effect" * - * const program = Effect.gen(function*() { - * const fs = yield* FileSystem.FileSystem - * - * // For extremely large data processing scenarios - * const massiveDataset = FileSystem.PiB(2) // 2 PiB - * - * // This would typically be used in enterprise/cloud scenarios - * yield* Console.log(`Processing ${massiveDataset} bytes of data`) - * - * // Such large files would require specialized streaming - * const stream = fs.stream("massive-dataset.bin", { - * chunkSize: FileSystem.GiB(1), // 1 GiB chunks - * offset: FileSystem.TiB(100) // Start from 100 TiB offset - * }) - * }) + * FileSystem.PiB(2) // => 2251799813685248n * ``` * * @category sizes @@ -658,24 +597,11 @@ export const PiB = (n: number): Size => Size(BigInt(n) * bigintPiB) * * **Example** (Opening files with flags) * - * ```ts - * import { Effect, FileSystem } from "effect" - * - * const program = Effect.gen(function*() { - * const fs = yield* FileSystem.FileSystem - * - * // Open for reading only - * const readFile = yield* fs.open("data.txt", { flag: "r" }) + * ```ts import.meta.vitest + * import type { FileSystem } from "effect" * - * // Open for writing, truncating existing content - * const writeFile = yield* fs.open("output.txt", { flag: "w" }) - * - * // Open for appending - * const appendFile = yield* fs.open("log.txt", { flag: "a" }) - * - * // Open for read/write, but fail if file doesn't exist - * const editFile = yield* fs.open("config.json", { flag: "r+" }) - * }) + * const flags: ReadonlyArray = ["r", "w", "a", "r+"] + * flags // => ["r", "w", "a", "r+"] * ``` * * @category models @@ -707,32 +633,28 @@ export type OpenFlag = * * **Example** (Accessing and providing FileSystem) * - * ```ts + * ```ts import.meta.vitest * import { Effect, FileSystem } from "effect" * + * const customFs = FileSystem.makeNoop({ + * exists: () => Effect.succeed(true), + * readFileString: () => Effect.succeed("contents") + * }) + * * // Access the FileSystem service * const program = Effect.gen(function*() { * const fs = yield* FileSystem.FileSystem * * const exists = yield* fs.exists("./data.txt") - * if (exists) { - * const content = yield* fs.readFileString("./data.txt") - * yield* Effect.log("File content:", content) - * } + * return exists ? yield* fs.readFileString("./data.txt") : undefined * }) * - * // Provide a custom FileSystem implementation - * declare const platformImpl: Omit< - * FileSystem.FileSystem, - * "exists" | "readFileString" | "stream" | "sink" | "writeFileString" - * > - * const customFs = FileSystem.make(platformImpl) - * * const withCustomFs = Effect.provideService( * program, * FileSystem.FileSystem, * customFs * ) + * Effect.runSync(withCustomFs) // => "contents" * ``` * * @category services @@ -860,7 +782,7 @@ const notFound = (method: string, path: string) => * * **Example** (Creating a no-op FileSystem) * - * ```ts + * ```ts import.meta.vitest * import { Effect, FileSystem, PlatformError } from "effect" * * // Create a test filesystem that only allows reading specific files @@ -885,7 +807,7 @@ const notFound = (method: string, path: string) => * // Use in tests * const program = Effect.gen(function*() { * const content = yield* testFs.readFileString("test-config.json") - * // Will succeed with mocked content + * return content * }) * * // Test with the no-op filesystem @@ -894,6 +816,7 @@ const notFound = (method: string, path: string) => * FileSystem.FileSystem, * testFs * ) + * Effect.runSync(testProgram) // => "{\"test\": true}" * ``` * * @category constructors @@ -917,6 +840,9 @@ export const makeNoop = (fileSystem: Partial): FileSystem => copyFile(path) { return Effect.fail(notFound("copyFile", path)) }, + glob(pattern) { + return Effect.fail(notFound("glob", pattern)) + }, exists() { return Effect.succeed(false) }, @@ -1002,7 +928,7 @@ export const makeNoop = (fileSystem: Partial): FileSystem => * * **Example** (Providing a no-op FileSystem layer) * - * ```ts + * ```ts import.meta.vitest * import { Effect, FileSystem } from "effect" * * // Create a test layer with specific behaviors @@ -1019,6 +945,7 @@ export const makeNoop = (fileSystem: Partial): FileSystem => * * // Provide the test layer * const testProgram = Effect.provide(program, testLayer) + * Effect.runSync(testProgram) // => "mocked content" * ``` * * @category layers @@ -1061,7 +988,7 @@ export const FileTypeId = "~effect/platform/FileSystem/File" * @see {@link File} for the file-handle interface narrowed by this guard * @see {@link FileTypeId} for the runtime marker checked by this guard * - * @category file + * @category guards * @since 4.0.0 */ export const isFile = (u: unknown): u is File => hasProperty(u, FileTypeId) @@ -1077,44 +1004,43 @@ export const isFile = (u: unknown): u is File => hasProperty(u, FileTypeId) * * **Example** (Working with file handles) * - * ```ts - * import { Console, Effect, FileSystem } from "effect" + * ```ts import.meta.vitest + * import { 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 fs = yield* FileSystem.FileSystem - * - * // Open a file and work with the handle - * yield* Effect.scoped( - * Effect.gen(function*() { - * const file = yield* fs.open("./data.txt", { flag: "r+" }) - * - * // Get file information - * const stats = yield* file.stat - * yield* Console.log(`File size: ${stats.size} bytes`) - * - * // Read from specific position - * yield* file.seek(10, "start") - * const buffer = new Uint8Array(5) - * const bytesRead = yield* file.read(buffer) - * yield* Console.log(`Read ${bytesRead} bytes:`, buffer) - * - * // Write data - * const data = new TextEncoder().encode("Hello") - * yield* file.write(data) - * yield* file.sync // Flush to disk - * }) - * ) + * const stats = yield* file.stat + * const buffer = new Uint8Array(5) + * const bytesRead = yield* file.read(buffer) + * yield* file.writeAll(new TextEncoder().encode("Hello")) + * yield* file.sync + * return { size: stats.size, bytesRead, buffer: Array.from(buffer) } * }) + * + * Effect.runSync(program) // => { size: 5n, bytesRead: 5n, buffer: [1, 2, 3, 4, 5] } * ``` * - * @category file + * @category models * @since 4.0.0 */ export interface File { readonly [FileTypeId]: typeof FileTypeId - readonly fd: File.Descriptor readonly stat: Effect.Effect - readonly seek: (offset: SizeInput, from: SeekMode) => 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> @@ -1130,19 +1056,6 @@ export interface File { * @since 4.0.0 */ export declare namespace File { - /** - * Branded type for file descriptors. - * - * **Details** - * - * File descriptors are numeric handles used by the operating system - * to identify open files. The branded type ensures type safety. - * - * @category file - * @since 4.0.0 - */ - export type Descriptor = Brand.Branded - /** * Enumeration of possible file system entry types. * @@ -1151,7 +1064,7 @@ export declare namespace File { * Represents the different types of entries that can exist in a file system, * from regular files to special device files and symbolic links. * - * @category file + * @category models * @since 4.0.0 */ export type Type = @@ -1175,38 +1088,39 @@ export declare namespace File { * * **Example** (Inspecting file information) * - * ```ts - * import { Effect, FileSystem, Option } from "effect" - * - * const program = Effect.gen(function*() { - * const fs = yield* FileSystem.FileSystem - * - * const path = yield* fs.makeTempFile({ prefix: "info-" }) - * yield* fs.writeFileString(path, "hello") - * - * const info: FileSystem.File.Info = yield* fs.stat(path) + * ```ts import.meta.vitest + * import { FileSystem, Option } from "effect" * - * console.log(`File type: ${info.type}`) // "File type: File" - * console.log(`File size: ${info.size} bytes`) // "File size: 5 bytes" - * console.log(`Mode: ${info.mode.toString(8)}`) // Octal permissions + * 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() + * } * - * // Handle optional timestamps without inventing a fallback date - * const modified = Option.match(info.mtime, { - * onNone: () => "unavailable", - * onSome: (mtime) => mtime.toISOString() - * }) - * console.log(`Modified: ${modified}`) + * info.type // => "File" + * info.size // => 5n + * info.mode.toString(8) // => "644" * - * // Check if it's a regular file - * if (info.type === "File") { - * console.log("Processing regular file...") // "Processing regular file..." - * } - * - * yield* fs.remove(path) + * const modified = Option.match(info.mtime, { + * onNone: () => "unavailable", + * onSome: (mtime) => mtime.toISOString() * }) + * modified // => "unavailable" + * info.type === "File" // => true * ``` * - * @category file + * @category models * @since 4.0.0 */ export interface Info { @@ -1227,32 +1141,6 @@ export declare namespace File { } } -/** - * Creates a `File.Descriptor` from a number. - * - * **When to use** - * - * Use to brand an operating-system file descriptor number when implementing a - * `FileSystem` that returns custom `File` handles. - * - * **Details** - * - * `File.Descriptor` is a branded integer handle used by operating systems to - * identify open files. - * - * **Gotchas** - * - * This constructor is nominal and does not check that the number is an integer - * or that it refers to an open file descriptor. - * - * @see {@link File.Descriptor} for the branded descriptor type produced by this constructor - * @see {@link File} for file handles that expose a descriptor through `fd` - * - * @category constructors - * @since 4.0.0 - */ -export const FileDescriptor = Brand.nominal() - /** * Specifies the reference point for seeking within an open file. * @@ -1274,6 +1162,19 @@ export const FileDescriptor = Brand.nominal() */ export type SeekMode = "start" | "current" +/** + * Options for watching files or directories. + * + * @category models + * @since 4.0.0 + */ +export interface WatchOptions { + /** + * When `true`, changes in subdirectories are also reported. + */ + readonly recursive?: boolean | undefined +} + /** * Represents file system events emitted when watching files or directories. * @@ -1360,7 +1261,7 @@ export declare namespace WatchEvent { * * **Example** (Providing a custom watch backend) * - * ```ts + * ```ts import.meta.vitest * import { Effect, FileSystem, Option, Stream } from "effect" * * // Custom watch backend implementation @@ -1371,12 +1272,11 @@ export declare namespace WatchEvent { * } * } * - * // Provide custom watch backend * const program = Effect.gen(function*() { - * const fs = yield* FileSystem.FileSystem - * - * // File watching will use the custom backend - * const watcher = fs.watch("./directory") + * const backend = yield* FileSystem.WatchBackend + * return Option.isSome( + * backend.register("./directory", { type: "Directory" } as FileSystem.File.Info) + * ) * }) * * const withCustomBackend = Effect.provideService( @@ -1384,11 +1284,16 @@ export declare namespace WatchEvent { * FileSystem.WatchBackend, * customWatchBackend * ) + * Effect.runSync(withCustomBackend) // => true * ``` * - * @category file watcher + * @category services * @since 4.0.0 */ export class WatchBackend extends Context.Service Option.Option> + readonly register: ( + path: string, + stat: File.Info, + options?: WatchOptions + ) => Option.Option> }>()("effect/platform/FileSystem/WatchBackend") {} diff --git a/repos/effect/packages/effect/src/Filter.ts b/repos/effect/packages/effect/src/Filter.ts index c492eb2e7e..631d3e03d0 100644 --- a/repos/effect/packages/effect/src/Filter.ts +++ b/repos/effect/packages/effect/src/Filter.ts @@ -28,14 +28,14 @@ import type { EqualsWith, ExcludeTag, ExtractReason, ExtractTag, ReasonTags, Tag * * **Example** (Defining a positive number filter) * - * ```ts + * ```ts import.meta.vitest * import { Filter, Result } from "effect" * * // A filter that only passes positive numbers * const positiveFilter: Filter.Filter = (n) => n > 0 ? Result.succeed(n) : Result.fail(n) * - * console.log(positiveFilter(5)) // Result.succeed(5) - * console.log(positiveFilter(-3)) // Result.fail(-3) + * positiveFilter(5) // => Result.succeed(5) + * positiveFilter(-3) // => Result.fail(-3) * ``` * * @category models @@ -56,7 +56,7 @@ export interface Filter { * * **Example** (Defining an effectful user filter) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Filter, Result } from "effect" * * // An effectful filter that validates user data @@ -74,6 +74,9 @@ export interface Filter { * const user: User = { id, isActive: id.length > 0 } * return user.isActive ? Result.succeed(user) : Result.fail(user) * }) + * + * await Effect.runPromise(validateUser("alice")) // => Result.succeed({ id: "alice", isActive: true }) + * await Effect.runPromise(validateUser("")) // => Result.fail({ id: "", isActive: false }) * ``` * * @category models @@ -103,7 +106,7 @@ export interface FilterEffect< * * **Example** (Creating custom filters) * - * ```ts + * ```ts import.meta.vitest * import { Filter, Result } from "effect" * * // Create a filter for positive numbers @@ -113,6 +116,8 @@ export interface FilterEffect< * const uppercaseFilter = Filter.make((s: string) => * s.length > 0 ? Result.succeed(s.toUpperCase()) : Result.fail(s) * ) + * positiveFilter(1) // => Result.succeed(1) + * uppercaseFilter("ok") // => Result.succeed("OK") * ``` * * @category constructors @@ -133,7 +138,7 @@ export const make = ( * * **Example** (Creating effectful filters) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Filter, Result } from "effect" * * // Create an effectful filter that validates async @@ -143,6 +148,8 @@ export const make = ( * return isValid ? Result.succeed(id) : Result.fail(id) * }) * ) + * + * await Effect.runPromise(asyncValidate("id")) // => Result.succeed("id") * ``` * * @category constructors @@ -201,7 +208,7 @@ export { * * **Example** (Creating filters from predicates) * - * ```ts + * ```ts import.meta.vitest * import { Filter, Result } from "effect" * * // Create filter from predicate @@ -212,6 +219,9 @@ export { * const isString = Filter.fromPredicate((x: unknown): x is string => * typeof x === "string" * ) + * positiveNumbers(1) // => Result.succeed(1) + * nonEmptyStrings("") // => Result.fail("") + * isString("ok") // => Result.succeed("ok") * ``` * * @category constructors @@ -259,11 +269,11 @@ export const toPredicate = ( * * **Example** (Filtering strings) * - * ```ts + * ```ts import.meta.vitest * import { Filter, Result } from "effect" * - * console.log(Filter.string("hello")) // Result.succeed("hello") - * console.log(Filter.string(42)) // fail + * Filter.string("hello") // => Result.succeed("hello") + * Filter.string(42) // => Result.fail(42) * ``` * * @category constructors @@ -350,11 +360,11 @@ export const instanceOf = * * **Example** (Filtering numbers) * - * ```ts + * ```ts import.meta.vitest * import { Filter, Result } from "effect" * - * console.log(Filter.number(42)) // Result.succeed(42) - * console.log(Filter.number("42")) // fail + * Filter.number(42) // => Result.succeed(42) + * Filter.number("42") // => Result.fail("42") * ``` * * @category constructors @@ -617,13 +627,14 @@ export const zipWith: { * * **Example** (Zipping filters) * - * ```ts - * import { Filter } from "effect" + * ```ts import.meta.vitest + * import { Filter, Result } from "effect" * * const positiveNumbers = Filter.fromPredicate((n: number) => n > 0) * const evenNumbers = Filter.fromPredicate((n: number) => n % 2 === 0) * * const positiveAndEven = Filter.zip(positiveNumbers, evenNumbers) + * positiveAndEven(2) // => Result.succeed([2, 2]) * ``` * * @category combinators @@ -650,13 +661,14 @@ export const zip: { * * **Example** (Keeping the left filter result) * - * ```ts - * import { Filter } from "effect" + * ```ts import.meta.vitest + * import { Filter, Result } from "effect" * * const positiveNumbers = Filter.fromPredicate((n: number) => n > 0) * const evenNumbers = Filter.fromPredicate((n: number) => n % 2 === 0) * * const positiveEven = Filter.andLeft(positiveNumbers, evenNumbers) + * positiveEven(2) // => Result.succeed(2) * ``` * * @category combinators @@ -682,7 +694,7 @@ export const andLeft: { * * **Example** (Keeping the right filter result) * - * ```ts + * ```ts import.meta.vitest * import { Filter, Result } from "effect" * * const positiveNumbers = Filter.fromPredicate((n: number) => n > 0) @@ -691,6 +703,7 @@ export const andLeft: { * ) * * const positiveDoubled = Filter.andRight(positiveNumbers, doubleNumbers) + * positiveDoubled(2) // => Result.succeed(4) * ``` * * @category combinators @@ -716,7 +729,7 @@ export const andRight: { * * **Example** (Composing filters) * - * ```ts + * ```ts import.meta.vitest * import { Filter, Result } from "effect" * * const stringFilter = Filter.string @@ -725,6 +738,7 @@ export const andRight: { * ) * * const stringToUpper = Filter.compose(stringFilter, nonEmptyUpper) + * stringToUpper("hello") // => Result.succeed("HELLO") * ``` * * @category combinators diff --git a/repos/effect/packages/effect/src/Formatter.ts b/repos/effect/packages/effect/src/Formatter.ts index 426131f10b..843b5b88bc 100644 --- a/repos/effect/packages/effect/src/Formatter.ts +++ b/repos/effect/packages/effect/src/Formatter.ts @@ -25,13 +25,12 @@ import { getRedacted, redact, symbolRedactable } from "./Redactable.ts" * * **Example** (Defining a custom formatter) * - * ```ts + * ```ts import.meta.vitest * import type { Formatter } from "effect" * * const upper: Formatter.Formatter = (s) => s.toUpperCase() * - * console.log(upper("hello")) - * // HELLO + * upper("hello") // => "HELLO" * ``` * * @see {@link format} @@ -75,37 +74,29 @@ export interface Formatter { * * **Example** (Formatting compact output) * - * ```ts + * ```ts import.meta.vitest * import { Formatter } from "effect" * - * console.log(Formatter.format({ a: 1, b: [2, 3] })) - * // {"a":1,"b":[2,3]} + * Formatter.format({ a: 1, b: [2, 3] }) // => "{\"a\":1,\"b\":[2,3]}" * ``` * * **Example** (Pretty-printed output) * - * ```ts + * ```ts import.meta.vitest * import { Formatter } from "effect" * - * console.log(Formatter.format({ a: 1, b: [2, 3] }, { space: 2 })) - * // { - * // "a": 1, - * // "b": [ - * // 2, - * // 3 - * // ] - * // } + * const output = Formatter.format({ a: 1, b: [2, 3] }, { space: 2 }) + * output // => "{\n \"a\": 1,\n \"b\": [\n 2,\n 3\n ]\n}" * ``` * * **Example** (Handling circular references) * - * ```ts + * ```ts import.meta.vitest * import { Formatter } from "effect" * * const obj: any = { name: "loop" } * obj.self = obj - * console.log(Formatter.format(obj)) - * // {"name":"loop","self":[Circular]} + * Formatter.format(obj) // => "{\"name\":\"loop\",\"self\":[Circular]}" * ``` * * @see {@link formatJson} @@ -118,7 +109,7 @@ export function format(input: unknown, options?: { readonly ignoreToString?: boolean | undefined }): string { const space = options?.space ?? 0 - const seen = new WeakSet() + const ancestors = new WeakSet() const gap = !space ? "" : (typeof space === "number" ? " ".repeat(space) : space) const ind = (d: number) => gap.repeat(d) @@ -137,11 +128,13 @@ export function format(input: unknown, options?: { function recur(v: unknown, d = 0): string { if (Array.isArray(v)) { - if (seen.has(v)) return CIRCULAR - seen.add(v) - if (!gap || v.length <= 1) return `[${v.map((x) => recur(x, d)).join(",")}]` - const inner = v.map((x) => recur(x, d + 1)).join(",\n" + ind(d + 1)) - return `[\n${ind(d + 1)}${inner}\n${ind(d)}]` + if (ancestors.has(v)) return CIRCULAR + ancestors.add(v) + const 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)}]` + ancestors.delete(v) + return output } if (v instanceof Date) return formatDate(v) @@ -172,24 +165,28 @@ export function format(input: unknown, options?: { if (typeof v === "bigint") return String(v) + "n" if (typeof v === "object" || typeof v === "function") { - if (seen.has(v)) return CIRCULAR - seen.add(v) + if (ancestors.has(v)) return CIRCULAR + ancestors.add(v) - if (symbolRedactable in v) return format(getRedacted(v as any)) - - if (Symbol.iterator in v) { - return `${v.constructor.name}(${recur(Array.from(v as any), d)})` - } - - const keys = ownKeys(v) - if (!gap || keys.length <= 1) { - const body = `{${keys.map((k) => `${formatPropertyKey(k)}:${recur((v as any)[k], d)}`).join(",")}}` - return wrap(v, body) + let output: string + if (symbolRedactable in v) { + output = format(getRedacted(v as any)) + } 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) + } } - const body = `{\n${ - keys.map((k) => `${ind(d + 1)}${formatPropertyKey(k)}: ${recur((v as any)[k], d + 1)}`).join(",\n") - }\n${ind(d)}}` - return wrap(v, body) + ancestors.delete(v) + return output } return String(v) @@ -253,40 +250,41 @@ function safeToString(input: any): string { * Uses `JSON.stringify` internally with a replacer that tracks the current * object ancestry. Circular references are replaced with `undefined`, which * omits them from object output. `Redactable` values are automatically redacted - * before serialization. Values not supported by JSON, such as `BigInt`, - * `Symbol`, `undefined`, and functions, follow standard `JSON.stringify` - * behavior. The `space` parameter controls indentation and defaults to `0`. + * before serialization. Values not supported by JSON otherwise follow standard + * `JSON.stringify` behavior. The `space` parameter controls indentation and + * defaults to `0`. + * + * **Gotchas** + * + * When the root input is `undefined`, a symbol, or a function, `formatJson` + * returns `"null"` instead of the `undefined` returned by `JSON.stringify`. + * Nested values retain standard `JSON.stringify` behavior. * * **Example** (Formatting compact JSON) * - * ```ts + * ```ts import.meta.vitest * import { Formatter } from "effect" * - * console.log(Formatter.formatJson({ name: "Alice", age: 30 })) - * // {"name":"Alice","age":30} + * Formatter.formatJson({ name: "Alice", age: 30 }) // => "{\"name\":\"Alice\",\"age\":30}" * ``` * * **Example** (Handling circular references) * - * ```ts + * ```ts import.meta.vitest * import { Formatter } from "effect" * * const obj: any = { name: "test" } * obj.self = obj - * console.log(Formatter.formatJson(obj)) - * // {"name":"test"} + * Formatter.formatJson(obj) // => "{\"name\":\"test\"}" * ``` * * **Example** (Pretty-printed JSON) * - * ```ts + * ```ts import.meta.vitest * import { Formatter } from "effect" * - * console.log(Formatter.formatJson({ name: "Alice", age: 30 }, { space: 2 })) - * // { - * // "name": "Alice", - * // "age": 30 - * // } + * const output = Formatter.formatJson({ name: "Alice", age: 30 }, { space: 2 }) + * output // => "{\n \"name\": \"Alice\",\n \"age\": 30\n}" * ``` * * @see {@link format} @@ -315,5 +313,5 @@ export function formatJson(input: unknown, options?: { return redacted }, options?.space - ) + ) ?? "null" } diff --git a/repos/effect/packages/effect/src/Function.ts b/repos/effect/packages/effect/src/Function.ts index 02506c83fd..f12e50a746 100644 --- a/repos/effect/packages/effect/src/Function.ts +++ b/repos/effect/packages/effect/src/Function.ts @@ -21,7 +21,7 @@ import { pipeArguments } from "./Pipeable.ts" * * **Example** (Creating a function type with a type lambda) * - * ```ts + * ```ts import.meta.vitest * import type { Function, HKT } from "effect" * * // Create a function type using the type lambda @@ -29,7 +29,7 @@ import { pipeArguments } from "./Pipeable.ts" * // Equivalent to: (a: string) => number * ``` * - * @category type lambdas + * @category utility types * @since 2.0.0 */ export interface FunctionTypeLambda extends TypeLambda { @@ -53,7 +53,7 @@ export interface FunctionTypeLambda extends TypeLambda { * * **Example** (Selecting data-first or data-last style by arity) * - * ```ts + * ```ts import.meta.vitest * import { Function, pipe } from "effect" * * const sum = Function.dual< @@ -61,13 +61,13 @@ export interface FunctionTypeLambda extends TypeLambda { * (self: number, that: number) => number * >(2, (self, that) => self + that) * - * console.log(sum(2, 3)) // 5 - * console.log(pipe(2, sum(3))) // 5 + * sum(2, 3) // => 5 + * pipe(2, sum(3)) // => 5 * ``` * * **Example** (Defining overloads with call signatures) * - * ```ts + * ```ts import.meta.vitest * import { Function, pipe } from "effect" * * const sum: { @@ -75,13 +75,13 @@ export interface FunctionTypeLambda extends TypeLambda { * (self: number, that: number): number * } = Function.dual(2, (self: number, that: number): number => self + that) * - * console.log(sum(2, 3)) // 5 - * console.log(pipe(2, sum(3))) // 5 + * sum(2, 3) // => 5 + * pipe(2, sum(3)) // => 5 * ``` * * **Example** (Selecting data-first or data-last style with a predicate) * - * ```ts + * ```ts import.meta.vitest * import { Function, pipe } from "effect" * * const sum = Function.dual< @@ -92,8 +92,8 @@ export interface FunctionTypeLambda extends TypeLambda { * (self, that) => self + that * ) * - * console.log(sum(2, 3)) // 5 - * console.log(pipe(2, sum(3))) // 5 + * sum(2, 3) // => 5 + * pipe(2, sum(3)) // => 5 * ``` * * @category combinators @@ -169,11 +169,10 @@ export const dual: { * * **Example** (Applying an argument to a function) * - * ```ts + * ```ts import.meta.vitest * import { Function, pipe, String } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(pipe(String.length, Function.apply("hello")), 5) + * pipe(String.length, Function.apply("hello")) // => 5 * ``` * * @see {@link pipe} for building left-to-right pipelines @@ -192,10 +191,11 @@ export const apply = (a: A) => (self: (a: A) => B): B => self(a) * * **Example** (Creating a lazy argument) * - * ```ts + * ```ts import.meta.vitest * import { Function } from "effect" * * const constNull: Function.LazyArg = Function.constant(null) + * constNull() // => null * ``` * * @category models @@ -213,12 +213,11 @@ export type LazyArg = () => A * * **Example** (Typing a variadic function) * - * ```ts + * ```ts import.meta.vitest * import type { Function } from "effect" - * import * as assert from "node:assert" * * const sum: Function.FunctionN<[number, number], number> = (a, b) => a + b - * assert.deepStrictEqual(sum(2, 3), 5) + * sum(2, 3) // => 5 * ``` * * @category models @@ -235,11 +234,10 @@ export type FunctionN, B> = (...args: A) => B * * **Example** (Returning the same value) * - * ```ts + * ```ts import.meta.vitest * import { identity } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(identity(5), 5) + * identity(5) // => 5 * ``` * * @category combinators @@ -258,17 +256,14 @@ export const identity = (a: A): A => a * * **Example** (Checking an expression against a type) * - * ```ts + * ```ts import.meta.vitest * import { Function } from "effect" - * import * as assert from "node:assert" * - * const test1 = Function.satisfies()(5 as const) + * const test1 = Function.satisfies()(5 as const) // => 5 * // ^? const test: 5 * // @ts-expect-error * const test2 = Function.satisfies()(5) * // ^? Argument of type 'number' is not assignable to parameter of type 'string' - * - * assert.deepStrictEqual(Function.satisfies()(5), 5) * ``` * * @see {@link cast} for changing only the static TypeScript type @@ -308,14 +303,13 @@ export const cast: (a: A) => B = identity as any * * **Example** (Creating a constant thunk) * - * ```ts + * ```ts import.meta.vitest * import { Function } from "effect" - * import * as assert from "node:assert" * * const constNull = Function.constant(null) * - * assert.deepStrictEqual(constNull(), null) - * assert.deepStrictEqual(constNull(), null) + * constNull() // => null + * constNull() // => null * ``` * * @category constructors @@ -332,11 +326,10 @@ export const constant = (value: A): LazyArg => () => value * * **Example** (Returning true from a thunk) * - * ```ts + * ```ts import.meta.vitest * import { Function } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(Function.constTrue(), true) + * Function.constTrue() // => true * ``` * * @category constants @@ -353,11 +346,10 @@ export const constTrue: LazyArg = constant(true) * * **Example** (Returning false from a thunk) * - * ```ts + * ```ts import.meta.vitest * import { Function } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(Function.constFalse(), false) + * Function.constFalse() // => false * ``` * * @category constants @@ -374,11 +366,10 @@ export const constFalse: LazyArg = constant(false) * * **Example** (Returning null from a thunk) * - * ```ts + * ```ts import.meta.vitest * import { Function } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(Function.constNull(), null) + * Function.constNull() // => null * ``` * * @category constants @@ -395,11 +386,10 @@ export const constNull: LazyArg = constant(null) * * **Example** (Returning undefined from a thunk) * - * ```ts + * ```ts import.meta.vitest * import { Function } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(Function.constUndefined(), undefined) + * Function.constUndefined() // => undefined * ``` * * @category constants @@ -417,11 +407,10 @@ export const constUndefined: LazyArg = constant(undefined) * * **Example** (Returning void from a thunk) * - * ```ts + * ```ts import.meta.vitest * import { Function } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(Function.constVoid(), undefined) + * Function.constVoid() // => undefined * ``` * * @category constants @@ -439,13 +428,12 @@ export const constVoid: LazyArg = constUndefined * * **Example** (Flipping curried arguments) * - * ```ts + * ```ts import.meta.vitest * import { Function } from "effect" - * import * as assert from "node:assert" * * const f = (a: number) => (b: string) => a - b.length * - * assert.deepStrictEqual(Function.flip(f)("aaa")(2), -1) + * Function.flip(f)("aaa")(2) // => -1 * ``` * * @category combinators @@ -467,14 +455,13 @@ export const flip = , B extends Array, C>( * * **Example** (Composing two functions) * - * ```ts + * ```ts import.meta.vitest * import { Function } from "effect" - * import * as assert from "node:assert" * * const increment = (n: number) => n + 1 * const square = (n: number) => n * n * - * assert.strictEqual(Function.compose(increment, square)(2), 9) + * Function.compose(increment, square)(2) // => 9 * ``` * * @see {@link flow} for composing a left-to-right sequence of functions @@ -504,7 +491,7 @@ export const compose: { * * **Example** (Handling impossible values) * - * ```ts + * ```ts import.meta.vitest * import { absurd } from "effect" * * const handleNever = (value: never) => { @@ -528,13 +515,12 @@ export const absurd = (_: never): A => { * * **Example** (Converting arguments to a tuple) * - * ```ts + * ```ts import.meta.vitest * import { Function } from "effect" - * import * as assert from "node:assert" * * const sumTupled = Function.tupled((x: number, y: number): number => x + y) * - * assert.deepStrictEqual(sumTupled([1, 2]), 3) + * sumTupled([1, 2]) // => 3 * ``` * * @see {@link untupled} for adapting a tuple-argument function back to multiple arguments @@ -553,13 +539,12 @@ export const tupled = , B>(f: (...a: A) => B): * * **Example** (Converting a tuple to arguments) * - * ```ts + * ```ts import.meta.vitest * import { Function } from "effect" - * import * as assert from "node:assert" * * const getFirst = Function.untupled((tuple: [A, B]): A => tuple[0]) * - * assert.deepStrictEqual(getFirst(1, 2), 1) + * getFirst(1, 2) // => 1 * ``` * * @see {@link tupled} for adapting a multi-argument function to one tuple argument @@ -593,82 +578,33 @@ export const untupled = , B>(f: (a: A) => B): ( * In this example, `1` is passed to the first function, and each result becomes * the input for the next function. * - * ```ts + * ```ts import.meta.vitest * import { pipe } from "effect" * - * const result = pipe( + * pipe( * 1, * (n) => n + 1, * (n) => n * 2, * (n) => `result: ${n}` - * ) - * - * console.log(result) // "result: 4" - * ``` - * - * **Example** (Chaining methods before conversion) - * - * ```ts - * const numbers = [1, 2, 3, 4] - * const double = (n: number) => n * 2 - * const greaterThanFour = (n: number) => n > 4 - * - * const result = numbers.map(double).filter(greaterThanFour) - * - * console.log(result) // [6, 8] + * ) // => "result: 4" * ``` * * **Example** (Rewriting method chains with pipe) * * The same transformation can be written with data-last functions. * - * ```ts + * ```ts import.meta.vitest * import { Array, pipe } from "effect" * * const numbers = [1, 2, 3, 4] * const double = (n: number) => n * 2 * const greaterThanFour = (n: number) => n > 4 * - * const result = pipe( + * pipe( * numbers, * Array.map(double), * Array.filter(greaterThanFour) - * ) - * - * console.log(result) // [6, 8] - * ``` - * - * **Example** (Chaining arithmetic operations) - * - * ```ts - * import { pipe } from "effect" - * - * // Define simple arithmetic operations - * const increment = (x: number) => x + 1 - * const double = (x: number) => x * 2 - * const subtractTen = (x: number) => x - 10 - * - * // Sequentially apply these operations using `pipe` - * const result = pipe(5, increment, double, subtractTen) - * - * console.log(result) - * // Output: 2 - * ``` - * - * **Example** (Building a simple transformation pipeline) - * - * ```ts - * import { pipe } from "effect" - * - * // Simple transformation pipeline - * const result = pipe( - * 5, - * (x) => x * 2, // 10 - * (x) => x + 1, // 11 - * (x) => x.toString() // "11" - * ) - * - * console.log(result) // "11" + * ) // => [6, 8] * ``` * * @category combinators @@ -1141,16 +1077,15 @@ export function pipe(a: unknown, ...args: Array): unknown { * * **Example** (Composing functions left to right) * - * ```ts + * ```ts import.meta.vitest * import { flow } from "effect" - * import * as assert from "node:assert" * * const len = (s: string): number => s.length * const double = (n: number): number => n * 2 * * const f = flow(len, double) * - * assert.strictEqual(f("aaa"), 6) + * f("aaa") // => 6 * ``` * * @see {@link pipe} for applying a value through a left-to-right sequence immediately @@ -1338,7 +1273,7 @@ export function flow( * * **Example** (Creating a development placeholder) * - * ```ts + * ```ts import.meta.vitest * import { hole } from "effect" * * // Intentionally not called: `hole` throws if the placeholder is evaluated. @@ -1347,7 +1282,6 @@ export function flow( * name: hole() * }) * - * console.log(typeof buildUser) // "function" * ``` * * @category utility types @@ -1366,11 +1300,10 @@ export const hole: () => T = cast(absurd) * * **Example** (Discarding the first argument) * - * ```ts + * ```ts import.meta.vitest * import { Function } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(Function.SK(0, "hello"), "hello") + * Function.SK(0, "hello") // => "hello" * ``` * * @category combinators @@ -1390,11 +1323,12 @@ export const SK = (_: A, b: B): B => b * **Details** * * Each memoized wrapper owns a private `WeakMap` keyed by object identity. - * Cached `undefined` results are still returned because the cache is checked - * with `WeakMap.has`. * * **Gotchas** * + * `undefined` is reserved to represent a cache miss and is therefore not + * supported as a return value. + * * Structurally equal objects do not share cache entries. If the same object is * mutated after its first call, later calls still return the cached result for * that reference. @@ -1402,14 +1336,50 @@ export const SK = (_: A, b: B): B => b * @category caching * @since 4.0.0 */ -export function memoize(f: (a: A) => O): (ast: A) => O { +export function memoize(f: (a: A) => O): (ast: A) => O { const cache = new WeakMap() return (a) => { - if (cache.has(a)) { - return cache.get(a)! - } + const cached = cache.get(a) + if (cached !== undefined) return cached + const result = f(a) + cache.set(a, result) + return result + } +} + +/** + * Creates a memoized idempotent object transformation that caches both inputs + * and their outputs by object identity. + * + * **When to use** + * + * Use when an object transformation is idempotent and its output can be safely + * reused as a fixed point. + * + * **Details** + * + * After computing an input, the returned function caches both the input and + * the output. Calling it with either reference returns the output without + * invoking the supplied function again. + * + * **Gotchas** + * + * The returned function treats each computed output as a fixed point. If + * applying the supplied function to an output would produce an observably + * different value, this memoization changes that behavior. + * + * @see {@link memoize} for memoizing functions without an idempotence requirement + * @category caching + * @since 4.0.0 + */ +export function memoizeIdempotent(f: (a: A) => A): (a: A) => A { + const cache = new WeakMap() + return (a) => { + const cached = cache.get(a) + if (cached !== undefined) return cached const result = f(a) cache.set(a, result) + cache.set(result, result) return result } } diff --git a/repos/effect/packages/effect/src/Graph.ts b/repos/effect/packages/effect/src/Graph.ts index 0295cc65d4..f564b512ba 100644 --- a/repos/effect/packages/effect/src/Graph.ts +++ b/repos/effect/packages/effect/src/Graph.ts @@ -15,11 +15,12 @@ import { dual } from "./Function.ts" import * as Hash from "./Hash.ts" import type { Inspectable } from "./Inspectable.ts" import { NodeInspectSymbol } from "./Inspectable.ts" +import * as MutableHashMap from "./MutableHashMap.ts" import * as Option from "./Option.ts" import type { Pipeable } from "./Pipeable.ts" import { pipeArguments } from "./Pipeable.ts" import { hasProperty } from "./Predicate.ts" -import type { Mutable } from "./Types.ts" +import type { Covariant, Invariant, Mutable } from "./Types.ts" const TypeId = "~effect/collections/Graph" @@ -109,25 +110,19 @@ export class Edge extends Data.Class<{ export type Kind = "directed" | "undirected" /** - * Common structural interface shared by immutable and mutable graphs. + * Common public protocol for graph values. * * **Details** * - * Contains the node and edge maps, adjacency indexes, allocation counters, and - * shared protocols used by both `Graph` and `MutableGraph`. + * 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 models * @since 3.18.0 */ export interface Proto extends Iterable, Equal.Equal, Pipeable, Inspectable { - readonly [TypeId]: typeof TypeId - readonly nodes: Map - readonly edges: Map> - readonly adjacency: Map> - readonly reverseAdjacency: Map> - nextNodeIndex: NodeIndex - nextEdgeIndex: EdgeIndex - acyclic: Option.Option + readonly [TypeId]: Graph.Variance } /** @@ -150,6 +145,25 @@ export interface Graph extends Proto< 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 models + * @since 4.0.0 + */ + export interface Variance { + readonly _N: Covariant + readonly _E: Covariant + } +} + /** * Mutable graph interface. * @@ -166,11 +180,63 @@ export interface Graph extends Proto< * @category models * @since 3.18.0 */ -export interface MutableGraph extends Proto { +export interface MutableGraph + extends Iterable, Equal.Equal, Pipeable, Inspectable +{ + readonly [TypeId]: MutableGraph.Variance readonly type: T readonly mutable: true } +/** + * Companion namespace containing type-level metadata for scoped mutable graphs. + * + * @category models + * @since 4.0.0 + */ +export declare namespace MutableGraph { + /** + * Type-level variance marker for scoped mutable graphs. + * + * @category models + * @since 4.0.0 + */ + export interface Variance { + readonly _N: Invariant + readonly _E: Invariant + } +} + +/** @internal */ +interface GraphImpl + extends Iterable, Equal.Equal, Pipeable, Inspectable +{ + readonly [TypeId]: unknown + readonly type: T + mutable: boolean + nodes: Map + edges: Map> + adjacency: Map> + reverseAdjacency: Map> + nextNodeIndex: NodeIndex + nextEdgeIndex: EdgeIndex + acyclic: Option.Option +} + +/** @internal */ +const graphImpl = ( + graph: Graph | MutableGraph +): GraphImpl => graph as unknown as GraphImpl + +/** @internal */ +const cloneAdjacency = (adjacency: Map>): Map> => { + const cloned = new Map>() + for (const [nodeIndex, edges] of adjacency) { + cloned.set(nodeIndex, [...edges]) + } + return cloned +} + /** * Immutable graph type for source-to-target relationships. * @@ -253,41 +319,61 @@ export type MutableUndirectedGraph = MutableGraph // Proto Objects // ============================================================================= +/** @internal */ +const edgeEquals = (type: Kind, self: Edge, that: Edge): boolean => + (type === "directed" + ? self.source === that.source && self.target === that.target + : (self.source === that.source && self.target === that.target) || + (self.source === that.target && self.target === that.source)) && + Equal.equals(self.data, that.data) + +/** @internal */ +const edgeHash = (type: Kind, edge: Edge): number => + type === "directed" + ? Hash.hash(edge) + : Hash.optimize(Hash.hash(edge.data) ^ (Hash.hash(edge.source) + Hash.hash(edge.target))) + /** @internal */ const ProtoGraph = { - [TypeId]: TypeId, - [Symbol.iterator](this: Graph) { + [TypeId]: { + _N: (_: never) => _, + _E: (_: never) => _ + }, + [Symbol.iterator](this: GraphImpl) { return this.nodes[Symbol.iterator]() }, - [NodeInspectSymbol](this: Graph) { + [NodeInspectSymbol](this: GraphImpl) { return this.toJSON() }, - [Equal.symbol](this: Graph, that: Equal.Equal): boolean { + [Equal.symbol](this: GraphImpl, that: Equal.Equal): boolean { if (isGraph(that)) { + const thatImpl = graphImpl(that) if ( - this.nodes.size !== that.nodes.size || - this.edges.size !== that.edges.size || - this.type !== that.type + this.nodes.size !== thatImpl.nodes.size || + this.edges.size !== thatImpl.edges.size || + this.nextNodeIndex !== thatImpl.nextNodeIndex || + this.nextEdgeIndex !== thatImpl.nextEdgeIndex || + this.type !== thatImpl.type ) { return false } // Compare nodes for (const [nodeIndex, nodeData] of this.nodes) { - if (!that.nodes.has(nodeIndex)) { + if (!thatImpl.nodes.has(nodeIndex)) { return false } - const otherNodeData = that.nodes.get(nodeIndex)! + const otherNodeData = thatImpl.nodes.get(nodeIndex)! if (!Equal.equals(nodeData, otherNodeData)) { return false } } // Compare edges for (const [edgeIndex, edgeData] of this.edges) { - if (!that.edges.has(edgeIndex)) { + if (!thatImpl.edges.has(edgeIndex)) { return false } - const otherEdge = that.edges.get(edgeIndex)! - if (!Equal.equals(edgeData, otherEdge)) { + const otherEdge = thatImpl.edges.get(edgeIndex)! + if (!edgeEquals(this.type, edgeData, otherEdge)) { return false } } @@ -295,20 +381,22 @@ const ProtoGraph = { } return false }, - [Hash.symbol](this: Graph): number { + [Hash.symbol](this: GraphImpl): number { let hash = Hash.string("Graph") hash = hash ^ Hash.string(this.type) hash = hash ^ Hash.number(this.nodes.size) hash = hash ^ Hash.number(this.edges.size) + hash = hash ^ Hash.number(this.nextNodeIndex) + hash = hash ^ Hash.number(this.nextEdgeIndex) for (const [nodeIndex, nodeData] of this.nodes) { hash = hash ^ (Hash.hash(nodeIndex) + Hash.hash(nodeData)) } for (const [edgeIndex, edgeData] of this.edges) { - hash = hash ^ (Hash.hash(edgeIndex) + Hash.hash(edgeData)) + hash = hash ^ (Hash.hash(edgeIndex) + edgeHash(this.type, edgeData)) } return hash }, - toJSON(this: Graph) { + toJSON(this: GraphImpl) { return { _id: "Graph", nodeCount: this.nodes.size, @@ -316,7 +404,7 @@ const ProtoGraph = { type: this.type } }, - toString(this: Graph) { + toString(this: GraphImpl) { return `Graph(${this.type}, ${this.nodes.size}, ${this.edges.size})` }, pipe() { @@ -350,13 +438,22 @@ export class GraphError extends Data.TaggedError("GraphError")<{ /** @internal */ const missingNode = (node: number) => new GraphError({ message: `Node ${node} does not exist` }) +/** @internal */ +function assertMutable( + graph: Graph | MutableGraph +): asserts graph is MutableGraph { + if (!graph.mutable) { + throw new GraphError({ message: "Graph is not mutable" }) + } +} + // ============================================================================= // Constructors // ============================================================================= /** * Returns `true` if a value has the graph runtime type identifier, narrowing - * it to a `Graph`. + * it to an immutable or mutable graph. * * **When to use** * @@ -365,19 +462,70 @@ const missingNode = (node: number) => new GraphError({ message: `Node ${node} do * **Gotchas** * * This guard checks the shared graph runtime type identifier and does not - * distinguish immutable graphs from mutable graphs. + * distinguish immutable graphs from mutable graphs or directed graphs from + * undirected graphs. * * @category guards * @since 4.0.0 */ -export const isGraph = (u: unknown): u is Graph => hasProperty(u, TypeId) +export const isGraph = ( + u: U | Graph | MutableGraph +): u is Graph | MutableGraph => hasProperty(u, TypeId) + +/** + * 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 => { + const graph: Mutable> = Object.create(ProtoGraph) + graph.type = type + graph.nodes = new Map() + graph.edges = new Map() + graph.adjacency = new Map() + graph.reverseAdjacency = new Map() + graph.nextNodeIndex = 0 + graph.nextEdgeIndex = 0 + graph.acyclic = Option.some(true) + + if (mutate === undefined) { + graph.mutable = false + return graph as unknown as Graph + } + + graph.mutable = true + const mutable = Equal.byReferenceUnsafe(graph as unknown as MutableGraph) + return mutateScoped(mutable, mutate) + } /** * Creates a directed graph, optionally with initial mutations. * * **Example** (Creating a directed graph) * - * ```ts + * ```ts import.meta.vitest * import { Graph } from "effect" * * // Directed graph with initial nodes and edges @@ -388,38 +536,22 @@ export const isGraph = (u: unknown): u is Graph => hasProperty * Graph.addEdge(mutable, a, b, "A->B") * Graph.addEdge(mutable, b, c, "B->C") * }) + * Array.of(Graph.nodeCount(graph), Graph.edgeCount(graph)) // => [3, 2] * ``` * * @category constructors * @since 3.18.0 */ -export const directed = (mutate?: (mutable: MutableDirectedGraph) => void): DirectedGraph => { - const graph: Mutable> = Object.create(ProtoGraph) - graph.type = "directed" - graph.nodes = new Map() - graph.edges = new Map() - graph.adjacency = new Map() - graph.reverseAdjacency = new Map() - graph.nextNodeIndex = 0 - graph.nextEdgeIndex = 0 - graph.acyclic = Option.some(true) - graph.mutable = false - - if (mutate) { - const mutable = beginMutation(graph as DirectedGraph) - mutate(mutable as MutableDirectedGraph) - return endMutation(mutable) - } - - return graph -} +export const directed: ( + mutate?: (mutable: MutableDirectedGraph) => undefined +) => DirectedGraph = make("directed") /** * Creates an undirected graph, optionally with initial mutations. * * **Example** (Creating an undirected graph) * - * ```ts + * ```ts import.meta.vitest * import { Graph } from "effect" * * // Undirected graph with initial nodes and edges @@ -430,31 +562,15 @@ export const directed = (mutate?: (mutable: MutableDirectedGraph) => * Graph.addEdge(mutable, a, b, "A-B") * Graph.addEdge(mutable, b, c, "B-C") * }) + * Array.of(Graph.nodeCount(graph), Graph.edgeCount(graph)) // => [3, 2] * ``` * * @category constructors * @since 3.18.0 */ -export const undirected = (mutate?: (mutable: MutableUndirectedGraph) => void): UndirectedGraph => { - const graph: Mutable> = Object.create(ProtoGraph) - graph.type = "undirected" - graph.nodes = new Map() - graph.edges = new Map() - graph.adjacency = new Map() - graph.reverseAdjacency = new Map() - graph.nextNodeIndex = 0 - graph.nextEdgeIndex = 0 - graph.acyclic = Option.some(true) - graph.mutable = false - - if (mutate) { - const mutable = beginMutation(graph) - mutate(mutable as MutableUndirectedGraph) - return endMutation(mutable) - } - - return graph -} +export const undirected: ( + mutate?: (mutable: MutableUndirectedGraph) => undefined +) => UndirectedGraph = make("undirected") // ============================================================================= // Scoped Mutable API @@ -465,12 +581,13 @@ export const undirected = (mutate?: (mutable: MutableUndirectedGraph * * **Example** (Beginning a mutation scope) * - * ```ts + * ```ts import.meta.vitest * import { Graph } from "effect" * * const graph = Graph.directed() * const mutable = Graph.beginMutation(graph) * // Now mutable can be safely modified without affecting original graph + * Array.of(Graph.nodeCount(mutable), Graph.nodeCount(graph)) // => [0, 0] * ``` * * @category mutations @@ -479,44 +596,41 @@ export const undirected = (mutate?: (mutable: MutableUndirectedGraph export const beginMutation = ( graph: Graph ): MutableGraph => { - // Copy adjacency maps with deep cloned arrays - const adjacency = new Map>() - const reverseAdjacency = new Map>() - - for (const [nodeIndex, edges] of graph.adjacency) { - adjacency.set(nodeIndex, [...edges]) - } - - for (const [nodeIndex, edges] of graph.reverseAdjacency) { - reverseAdjacency.set(nodeIndex, [...edges]) - } - - const mutable: Mutable> = Object.create(ProtoGraph) - mutable.type = graph.type - mutable.nodes = new Map(graph.nodes) - mutable.edges = new Map(graph.edges) + const source = graphImpl(graph) + const adjacency = cloneAdjacency(source.adjacency) + const reverseAdjacency = cloneAdjacency(source.reverseAdjacency) + + const mutable: Mutable> = Object.create(ProtoGraph) + mutable.type = source.type + mutable.nodes = new Map(source.nodes) + mutable.edges = new Map(source.edges) mutable.adjacency = adjacency mutable.reverseAdjacency = reverseAdjacency - mutable.nextNodeIndex = graph.nextNodeIndex - mutable.nextEdgeIndex = graph.nextEdgeIndex - mutable.acyclic = graph.acyclic + mutable.nextNodeIndex = source.nextNodeIndex + mutable.nextEdgeIndex = source.nextEdgeIndex + mutable.acyclic = source.acyclic mutable.mutable = true - return mutable + return Equal.byReferenceUnsafe(mutable as unknown as MutableGraph) } /** * Converts a mutable graph back to an immutable graph, ending the mutation scope. * + * **Details** + * + * Finalizes the mutable handle. Later public mutation operations on that handle + * fail with a `GraphError`. + * * **Example** (Ending a mutation scope) * - * ```ts + * ```ts import.meta.vitest * import { Graph } from "effect" * * const graph = Graph.directed() * const mutable = Graph.beginMutation(graph) * // ... perform mutations on mutable ... - * const newGraph = Graph.endMutation(mutable) + * Graph.nodeCount(Graph.endMutation(mutable)) // => 0 * ``` * * @category mutations @@ -525,17 +639,35 @@ export const beginMutation = ( export const endMutation = ( mutable: MutableGraph ): Graph => { - const graph: Mutable> = Object.create(ProtoGraph) - graph.type = mutable.type - graph.nodes = new Map(mutable.nodes) - graph.edges = new Map(mutable.edges) - graph.adjacency = mutable.adjacency - graph.reverseAdjacency = mutable.reverseAdjacency - graph.nextNodeIndex = mutable.nextNodeIndex - graph.nextEdgeIndex = mutable.nextEdgeIndex - graph.acyclic = mutable.acyclic + assertMutable(mutable) + const source = graphImpl(mutable) + + const graph: Mutable> = Object.create(ProtoGraph) + graph.type = source.type + graph.nodes = new Map(source.nodes) + graph.edges = new Map(source.edges) + graph.adjacency = cloneAdjacency(source.adjacency) + graph.reverseAdjacency = cloneAdjacency(source.reverseAdjacency) + graph.nextNodeIndex = source.nextNodeIndex + graph.nextEdgeIndex = source.nextEdgeIndex + graph.acyclic = source.acyclic graph.mutable = false + source.mutable = false + return graph as unknown as Graph +} + +/** @internal */ +const mutateScoped = ( + mutable: MutableGraph, + f: (mutable: MutableGraph) => undefined +): Graph => { + let graph: Graph + try { + f(mutable) + } finally { + graph = endMutation(mutable) + } return graph } @@ -544,38 +676,763 @@ export const endMutation = ( * * **Example** (Applying scoped mutations) * - * ```ts - * import { Graph } from "effect" + * ```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 + * ``` + * + * @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. + * + * **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. + * + * **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 models + * @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 = graphImpl(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 = graphImpl(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. + * + * **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. + * + * **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 + * ``` + * + * @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. + * + * **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. + * + * **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 + * ``` + * + * @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 = graphImpl(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`. + * + * **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`. + * + * **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 + * ``` + * + * @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 = graphImpl(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. + * + * **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. + * + * **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 + * ``` + * + * @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. + * + * **Details** + * + * Adds every missing edge between distinct nodes. 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) \ E}` + * + * **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 + * ``` + * + * @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 selfImpl = graphImpl(self) + const nodeEntries = Array.from(selfImpl.nodes) + + return make(self.type)((mutable) => { + const newIndexMap = new Map() + + for (const [oldIndex, data] of nodeEntries) { + newIndexMap.set(oldIndex, addNode(mutable, data)) + } + + for (let i = 0; i < nodeEntries.length; i++) { + const [sourceOldIndex, sourceData] = nodeEntries[i] + const start = self.type === "undirected" ? i + 1 : 0 + + for (let j = start; j < nodeEntries.length; j++) { + const [targetOldIndex, targetData] = nodeEntries[j] + if (sourceOldIndex === targetOldIndex || hasEdge(self, sourceOldIndex, targetOldIndex)) { + continue + } + + const sourceIndex = newIndexMap.get(sourceOldIndex) + const targetIndex = newIndexMap.get(targetOldIndex) + if (sourceIndex !== undefined && targetIndex !== undefined) { + addEdge(mutable, sourceIndex, targetIndex, createEdge(sourceData, targetData)) + } + } + } + }) +}) + +/** + * Configuration for selecting a graph neighborhood. + * + * **Details** + * + * `radius` limits the edge distance from the center node and defaults to `1`. + * `direction` controls how directed edges are traversed and defaults to + * `"outgoing"`. + * + * @category models + * @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. + * + * **Details** + * + * The `radius` option is the maximum edge distance from `nodeIndex` and + * defaults to `1`. 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. + * + * **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 + * ``` + * + * @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 = graphImpl(self) + const radius = 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 disjoint union of two graphs. + * + * **Details** * - * 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) - * }) + * 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. * - * console.log(Graph.nodeCount(newGraph)) // 2 - * console.log(Graph.edgeCount(newGraph)) // 1 - * ``` + * `G1 + G2 = {disjoint V1 + V2, disjoint E1 + E2}` * - * @category mutations - * @since 3.18.0 + * @category set operations + * @since 4.0.0 */ -export const mutate: { - ( - f: (mutable: MutableGraph) => void - ): (graph: Graph) => Graph - ( - graph: Graph, - f: (mutable: MutableGraph) => void - ): Graph -} = dual(2, ( - graph: Graph, - f: (mutable: MutableGraph) => void -): Graph => { - const mutable = beginMutation(graph) - f(mutable) - return endMutation(mutable) +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 = graphImpl(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) + }) }) // ============================================================================= @@ -602,14 +1459,12 @@ export const mutate: { * * **Example** (Adding nodes) * - * ```ts + * ```ts import.meta.vitest * import { Graph } from "effect" * - * const result = Graph.mutate(Graph.directed(), (mutable) => { - * const nodeA = Graph.addNode(mutable, "Node A") - * const nodeB = Graph.addNode(mutable, "Node B") - * console.log(nodeA) // NodeIndex with value 0 - * console.log(nodeB) // NodeIndex with value 1 + * Graph.mutate(Graph.directed(), (mutable) => { + * Graph.addNode(mutable, "Node A") // => 0 + * Graph.addNode(mutable, "Node B") // => 1 * }) * ``` * @@ -624,17 +1479,20 @@ export const addNode = ( mutable: MutableGraph, data: N ): NodeIndex => { - const nodeIndex = mutable.nextNodeIndex + assertMutable(mutable) + const impl = graphImpl(mutable) + + const nodeIndex = impl.nextNodeIndex // Add node data - mutable.nodes.set(nodeIndex, data) + impl.nodes.set(nodeIndex, data) // Initialize empty adjacency lists - mutable.adjacency.set(nodeIndex, []) - mutable.reverseAdjacency.set(nodeIndex, []) + impl.adjacency.set(nodeIndex, []) + impl.reverseAdjacency.set(nodeIndex, []) // Update graph allocators - mutable.nextNodeIndex = mutable.nextNodeIndex + 1 + impl.nextNodeIndex = impl.nextNodeIndex + 1 return nodeIndex } @@ -644,28 +1502,23 @@ export const addNode = ( * * **Example** (Getting node data) * - * ```ts + * ```ts import.meta.vitest * import { Graph, Option } from "effect" * * const graph = Graph.mutate(Graph.directed(), (mutable) => { * Graph.addNode(mutable, "Node A") * }) * - * const nodeIndex = 0 - * const nodeData = Graph.getNode(graph, nodeIndex) - * - * if (Option.isSome(nodeData)) { - * console.log(nodeData.value) // "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 + (nodeIndex: NodeIndex): ( + graph: Graph | MutableGraph + ) => Option.Option ( graph: Graph | MutableGraph, nodeIndex: NodeIndex @@ -673,27 +1526,25 @@ export const getNode: { } = dual(2, ( graph: Graph | MutableGraph, nodeIndex: NodeIndex -): Option.Option => graph.nodes.has(nodeIndex) ? Option.some(graph.nodes.get(nodeIndex)!) : Option.none()) +): Option.Option => { + const impl = graphImpl(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 + * ```ts import.meta.vitest * import { Graph } from "effect" * * const graph = Graph.mutate(Graph.directed(), (mutable) => { * Graph.addNode(mutable, "Node A") * }) * - * const nodeIndex = 0 - * const exists = Graph.hasNode(graph, nodeIndex) - * console.log(exists) // true - * - * const nonExistentIndex = 999 - * const notExists = Graph.hasNode(graph, nonExistentIndex) - * console.log(notExists) // false + * Graph.hasNode(graph, 0) // => true + * Graph.hasNode(graph, 999) // => false * ``` * * @category getters @@ -705,18 +1556,18 @@ export const hasNode: { } = dual(2, ( graph: Graph | MutableGraph, nodeIndex: NodeIndex -): boolean => graph.nodes.has(nodeIndex)) +): boolean => graphImpl(graph).nodes.has(nodeIndex)) /** * Returns the number of nodes in the graph. * * **Example** (Counting nodes) * - * ```ts + * ```ts import.meta.vitest * import { Graph } from "effect" * * const emptyGraph = Graph.directed() - * console.log(Graph.nodeCount(emptyGraph)) // 0 + * Graph.nodeCount(emptyGraph) // => 0 * * const graphWithNodes = Graph.mutate(emptyGraph, (mutable) => { * Graph.addNode(mutable, "Node A") @@ -724,7 +1575,7 @@ export const hasNode: { * Graph.addNode(mutable, "Node C") * }) * - * console.log(Graph.nodeCount(graphWithNodes)) // 3 + * Graph.nodeCount(graphWithNodes) // => 3 * ``` * * @category getters @@ -732,15 +1583,15 @@ export const hasNode: { */ export const nodeCount = ( graph: Graph | MutableGraph -): number => graph.nodes.size +): number => graphImpl(graph).nodes.size /** * Finds the first node that matches the given predicate. * * **Example** (Finding the first matching node) * - * ```ts - * import { Graph } from "effect" + * ```ts import.meta.vitest + * import { Graph, Option } from "effect" * * const graph = Graph.mutate(Graph.directed(), (mutable) => { * Graph.addNode(mutable, "Node A") @@ -748,11 +1599,8 @@ export const nodeCount = ( * Graph.addNode(mutable, "Node C") * }) * - * const result = Graph.findNode(graph, (data) => data.startsWith("Node B")) - * console.log(result) // Option.some(1) - * - * const notFound = Graph.findNode(graph, (data) => data === "Node D") - * console.log(notFound) // Option.none() + * Graph.findNode(graph, (data) => data.startsWith("Node B")) // => Option.some(1) + * Graph.findNode(graph, (data) => data === "Node D") // => Option.none() * ``` * * @category getters @@ -770,7 +1618,8 @@ export const findNode: { graph: Graph | MutableGraph, predicate: (data: N) => boolean ): Option.Option => { - for (const [index, data] of graph.nodes) { + const impl = graphImpl(graph) + for (const [index, data] of impl.nodes) { if (predicate(data)) { return Option.some(index) } @@ -783,7 +1632,7 @@ export const findNode: { * * **Example** (Finding matching nodes) * - * ```ts + * ```ts import.meta.vitest * import { Graph } from "effect" * * const graph = Graph.mutate(Graph.directed(), (mutable) => { @@ -792,11 +1641,8 @@ export const findNode: { * Graph.addNode(mutable, "Start C") * }) * - * const result = Graph.findNodes(graph, (data) => data.startsWith("Start")) - * console.log(result) // [0, 2] - * - * const empty = Graph.findNodes(graph, (data) => data === "Not Found") - * console.log(empty) // [] + * Graph.findNodes(graph, (data) => data.startsWith("Start")) // => [0, 2] + * Graph.findNodes(graph, (data) => data === "Not Found") // => [] * ``` * * @category getters @@ -814,8 +1660,9 @@ export const findNodes: { graph: Graph | MutableGraph, predicate: (data: N) => boolean ): Array => { + const impl = graphImpl(graph) const results: Array = [] - for (const [index, data] of graph.nodes) { + for (const [index, data] of impl.nodes) { if (predicate(data)) { results.push(index) } @@ -828,8 +1675,8 @@ export const findNodes: { * * **Example** (Finding the first matching edge) * - * ```ts - * import { Graph } from "effect" + * ```ts import.meta.vitest + * import { Graph, Option } from "effect" * * const graph = Graph.mutate(Graph.directed(), (mutable) => { * const nodeA = Graph.addNode(mutable, "Node A") @@ -839,11 +1686,8 @@ export const findNodes: { * Graph.addEdge(mutable, nodeB, nodeC, 20) * }) * - * const result = Graph.findEdge(graph, (data) => data > 15) - * console.log(result) // Option.some(1) - * - * const notFound = Graph.findEdge(graph, (data) => data > 100) - * console.log(notFound) // Option.none() + * Graph.findEdge(graph, (data) => data > 15) // => Option.some(1) + * Graph.findEdge(graph, (data) => data > 100) // => Option.none() * ``` * * @category getters @@ -861,7 +1705,8 @@ export const findEdge: { graph: Graph | MutableGraph, predicate: (data: E, source: NodeIndex, target: NodeIndex) => boolean ): Option.Option => { - for (const [edgeIndex, edgeData] of graph.edges) { + const impl = graphImpl(graph) + for (const [edgeIndex, edgeData] of impl.edges) { if (predicate(edgeData.data, edgeData.source, edgeData.target)) { return Option.some(edgeIndex) } @@ -874,7 +1719,7 @@ export const findEdge: { * * **Example** (Finding matching edges) * - * ```ts + * ```ts import.meta.vitest * import { Graph } from "effect" * * const graph = Graph.mutate(Graph.directed(), (mutable) => { @@ -886,11 +1731,8 @@ export const findEdge: { * Graph.addEdge(mutable, nodeC, nodeA, 30) * }) * - * const result = Graph.findEdges(graph, (data) => data >= 20) - * console.log(result) // [1, 2] - * - * const empty = Graph.findEdges(graph, (data) => data > 100) - * console.log(empty) // [] + * Graph.findEdges(graph, (data) => data >= 20) // => [1, 2] + * Graph.findEdges(graph, (data) => data > 100) // => [] * ``` * * @category getters @@ -908,8 +1750,9 @@ export const findEdges: { graph: Graph | MutableGraph, predicate: (data: E, source: NodeIndex, target: NodeIndex) => boolean ): Array => { + const impl = graphImpl(graph) const results: Array = [] - for (const [edgeIndex, edgeData] of graph.edges) { + for (const [edgeIndex, edgeData] of impl.edges) { if (predicate(edgeData.data, edgeData.source, edgeData.target)) { results.push(edgeIndex) } @@ -922,8 +1765,8 @@ export const findEdges: { * * **Example** (Updating node data) * - * ```ts - * import { Graph } from "effect" + * ```ts import.meta.vitest + * import { Graph, Option } from "effect" * * const graph = Graph.directed((mutable) => { * Graph.addNode(mutable, "Node A") @@ -931,8 +1774,7 @@ export const findEdges: { * Graph.updateNode(mutable, 0, (data) => data.toUpperCase()) * }) * - * const nodeData = Graph.getNode(graph, 0) - * console.log(nodeData) // Option.some("NODE A") + * Graph.getNode(graph, 0) // => Option.some("NODE A") * ``` * * @category transforming @@ -943,13 +1785,16 @@ export const updateNode = ( index: NodeIndex, f: (data: N) => N ): void => { - if (!mutable.nodes.has(index)) { + assertMutable(mutable) + const impl = graphImpl(mutable) + + if (!impl.nodes.has(index)) { return } - const currentData = mutable.nodes.get(index)! + const currentData = impl.nodes.get(index)! const newData = f(currentData) - mutable.nodes.set(index, newData) + impl.nodes.set(index, newData) } /** @@ -957,8 +1802,8 @@ export const updateNode = ( * * **Example** (Updating edge data) * - * ```ts - * import { Graph } from "effect" + * ```ts import.meta.vitest + * import { Graph, Option } from "effect" * * const result = Graph.mutate(Graph.directed(), (mutable) => { * const nodeA = Graph.addNode(mutable, "Node A") @@ -967,8 +1812,7 @@ export const updateNode = ( * Graph.updateEdge(mutable, edgeIndex, (data) => data * 2) * }) * - * const edgeData = Graph.getEdge(result, 0) - * console.log(edgeData) // Option.some(new Graph.Edge({ source: 0, target: 1, data: 20 })) + * Option.map(Graph.getEdge(result, 0), (edge) => edge.data) // => Option.some(20) * ``` * * @category mutations @@ -979,13 +1823,16 @@ export const updateEdge = ( edgeIndex: EdgeIndex, f: (data: E) => E ): void => { - if (!mutable.edges.has(edgeIndex)) { + assertMutable(mutable) + const impl = graphImpl(mutable) + + if (!impl.edges.has(edgeIndex)) { return } - const currentEdge = mutable.edges.get(edgeIndex)! + const currentEdge = impl.edges.get(edgeIndex)! const newData = f(currentEdge.data) - mutable.edges.set(edgeIndex, new Edge({ ...currentEdge, data: newData })) + impl.edges.set(edgeIndex, new Edge({ ...currentEdge, data: newData })) } /** @@ -998,8 +1845,8 @@ export const updateEdge = ( * * **Example** (Mapping node data) * - * ```ts - * import { Graph } from "effect" + * ```ts import.meta.vitest + * import { Graph, Option } from "effect" * * const graph = Graph.directed((mutable) => { * Graph.addNode(mutable, "node a") @@ -1008,8 +1855,7 @@ export const updateEdge = ( * Graph.mapNodes(mutable, (data) => data.toUpperCase()) * }) * - * const nodeData = Graph.getNode(graph, 0) - * console.log(nodeData) // Option.some("NODE A") + * Graph.getNode(graph, 0) // => Option.some("NODE A") * ``` * * @category transforming @@ -1019,10 +1865,13 @@ export const mapNodes = ( mutable: MutableGraph, f: (data: N) => N ): void => { + assertMutable(mutable) + const impl = graphImpl(mutable) + // Transform existing node data in place - for (const [index, data] of mutable.nodes) { + for (const [index, data] of impl.nodes) { const newData = f(data) - mutable.nodes.set(index, newData) + impl.nodes.set(index, newData) } } @@ -1031,8 +1880,8 @@ export const mapNodes = ( * * **Example** (Mapping edge data) * - * ```ts - * import { Graph } from "effect" + * ```ts import.meta.vitest + * import { Graph, Option } from "effect" * * const graph = Graph.directed((mutable) => { * const a = Graph.addNode(mutable, "A") @@ -1043,8 +1892,7 @@ export const mapNodes = ( * Graph.mapEdges(mutable, (data) => data * 2) * }) * - * const edgeData = Graph.getEdge(graph, 0) - * console.log(edgeData) // Option.some(new Graph.Edge({ source: 0, target: 1, data: 20 })) + * Option.map(Graph.getEdge(graph, 0), (edge) => edge.data) // => Option.some(20) * ``` * * @category transforming @@ -1054,13 +1902,19 @@ export const mapEdges = ( mutable: MutableGraph, f: (data: E) => E ): void => { + assertMutable(mutable) + const impl = graphImpl(mutable) + // Transform existing edge data in place - for (const [index, edgeData] of mutable.edges) { + for (const [index, edgeData] of impl.edges) { const newData = f(edgeData.data) - mutable.edges.set(index, { - ...edgeData, - data: newData - }) + impl.edges.set( + index, + new Edge({ + ...edgeData, + data: newData + }) + ) } } @@ -1068,7 +1922,7 @@ export const mapEdges = ( * @internal */ const rebuildAdjacency = ( - mutable: MutableGraph + mutable: GraphImpl ): void => { mutable.adjacency.clear() mutable.reverseAdjacency.clear() @@ -1094,8 +1948,8 @@ const rebuildAdjacency = ( * * **Example** (Reversing edge directions) * - * ```ts - * import { Graph } from "effect" + * ```ts import.meta.vitest + * import { Graph, Option } from "effect" * * const graph = Graph.directed((mutable) => { * const a = Graph.addNode(mutable, "A") @@ -1106,8 +1960,7 @@ const rebuildAdjacency = ( * Graph.reverse(mutable) // Now B -> A, C -> B * }) * - * const edge0 = Graph.getEdge(graph, 0) - * console.log(edge0) // Option.some(new Graph.Edge({ source: 1, target: 0, data: 1 })) + * Option.map(Graph.getEdge(graph, 0), (edge) => edge.source) // => Option.some(1) * ``` * * @category transforming @@ -1116,13 +1969,16 @@ const rebuildAdjacency = ( export const reverse = ( mutable: MutableGraph ): void => { - if (mutable.type === "undirected") { + assertMutable(mutable) + const impl = graphImpl(mutable) + + if (impl.type === "undirected") { return } // Reverse all edges by swapping source and target - for (const [index, edgeData] of mutable.edges) { - mutable.edges.set( + for (const [index, edgeData] of impl.edges) { + impl.edges.set( index, new Edge({ source: edgeData.target, @@ -1132,10 +1988,10 @@ export const reverse = ( ) } - rebuildAdjacency(mutable) + rebuildAdjacency(impl) // Invalidate cycle flag since edge directions changed - mutable.acyclic = Option.none() + impl.acyclic = Option.none() } /** @@ -1144,7 +2000,7 @@ export const reverse = ( * * **Example** (Filtering and mapping nodes) * - * ```ts + * ```ts import.meta.vitest * import { Graph, Option } from "effect" * * const graph = Graph.directed((mutable) => { @@ -1162,7 +2018,7 @@ export const reverse = ( * ) * }) * - * console.log(Graph.nodeCount(graph)) // 2 (only "active" nodes remain) + * Graph.nodeCount(graph) // => 2 * ``` * * @category transforming @@ -1172,14 +2028,17 @@ export const filterMapNodes = ( mutable: MutableGraph, f: (data: N) => Option.Option ): void => { + assertMutable(mutable) + const impl = graphImpl(mutable) + const nodesToRemove: Array = [] // First pass: identify nodes to remove and transform data for nodes to keep - for (const [index, data] of mutable.nodes) { + for (const [index, data] of impl.nodes) { const result = f(data) if (Option.isSome(result)) { // Transform node data - mutable.nodes.set(index, result.value) + impl.nodes.set(index, result.value) } else { // Mark for removal nodesToRemove.push(index) @@ -1198,7 +2057,7 @@ export const filterMapNodes = ( * * **Example** (Filtering and mapping edges) * - * ```ts + * ```ts import.meta.vitest * import { Graph, Option } from "effect" * * const graph = Graph.directed((mutable) => { @@ -1216,7 +2075,7 @@ export const filterMapNodes = ( * ) * }) * - * console.log(Graph.edgeCount(graph)) // 2 (edges with weight 5 removed) + * Graph.edgeCount(graph) // => 2 * ``` * * @category transforming @@ -1226,17 +2085,23 @@ export const filterMapEdges = ( mutable: MutableGraph, f: (data: E) => Option.Option ): void => { + assertMutable(mutable) + const impl = graphImpl(mutable) + const edgesToRemove: Array = [] // First pass: identify edges to remove and transform data for edges to keep - for (const [index, edgeData] of mutable.edges) { + for (const [index, edgeData] of impl.edges) { const result = f(edgeData.data) if (Option.isSome(result)) { // Transform edge data - mutable.edges.set(index, { - ...edgeData, - data: result.value - }) + impl.edges.set( + index, + new Edge({ + ...edgeData, + data: result.value + }) + ) } else { // Mark for removal edgesToRemove.push(index) @@ -1255,7 +2120,7 @@ export const filterMapEdges = ( * * **Example** (Filtering nodes) * - * ```ts + * ```ts import.meta.vitest * import { Graph } from "effect" * * const graph = Graph.directed((mutable) => { @@ -1268,7 +2133,7 @@ export const filterMapEdges = ( * Graph.filterNodes(mutable, (data) => data === "active") * }) * - * console.log(Graph.nodeCount(graph)) // 2 (only "active" nodes remain) + * Graph.nodeCount(graph) // => 2 * ``` * * @category transforming @@ -1278,10 +2143,13 @@ export const filterNodes = ( mutable: MutableGraph, predicate: (data: N) => boolean ): void => { + assertMutable(mutable) + const impl = graphImpl(mutable) + const nodesToRemove: Array = [] // Identify nodes to remove - for (const [index, data] of mutable.nodes) { + for (const [index, data] of impl.nodes) { if (!predicate(data)) { nodesToRemove.push(index) } @@ -1299,7 +2167,7 @@ export const filterNodes = ( * * **Example** (Filtering edges) * - * ```ts + * ```ts import.meta.vitest * import { Graph } from "effect" * * const graph = Graph.directed((mutable) => { @@ -1315,7 +2183,7 @@ export const filterNodes = ( * Graph.filterEdges(mutable, (data) => data >= 10) * }) * - * console.log(Graph.edgeCount(graph)) // 2 (edge with weight 5 removed) + * Graph.edgeCount(graph) // => 2 * ``` * * @category transforming @@ -1325,10 +2193,13 @@ export const filterEdges = ( mutable: MutableGraph, predicate: (data: E) => boolean ): void => { + assertMutable(mutable) + const impl = graphImpl(mutable) + const edgesToRemove: Array = [] // Identify edges to remove - for (const [index, edgeData] of mutable.edges) { + for (const [index, edgeData] of impl.edges) { if (!predicate(edgeData.data)) { edgesToRemove.push(index) } @@ -1346,7 +2217,7 @@ export const filterEdges = ( /** @internal */ const invalidateCycleFlagOnRemoval = ( - mutable: MutableGraph + mutable: GraphImpl ): void => { // Only invalidate if the graph had cycles (removing edges/nodes cannot introduce cycles in acyclic graphs). if (mutable.acyclic._tag === "Some" && mutable.acyclic.value === false) { @@ -1356,7 +2227,7 @@ const invalidateCycleFlagOnRemoval = ( /** @internal */ const invalidateCycleFlagOnAddition = ( - mutable: MutableGraph + mutable: GraphImpl ): void => { // Only invalidate if the graph was acyclic (adding edges cannot remove cycles from cyclic graphs). if (mutable.acyclic._tag === "Some" && mutable.acyclic.value === true) { @@ -1389,14 +2260,13 @@ const invalidateCycleFlagOnAddition = ( * * **Example** (Adding edges) * - * ```ts + * ```ts import.meta.vitest * import { Graph } from "effect" * - * const result = Graph.mutate(Graph.directed(), (mutable) => { + * Graph.mutate(Graph.directed(), (mutable) => { * const nodeA = Graph.addNode(mutable, "Node A") * const nodeB = Graph.addNode(mutable, "Node B") - * const edge = Graph.addEdge(mutable, nodeA, nodeB, 42) - * console.log(edge) // EdgeIndex with value 0 + * Graph.addEdge(mutable, nodeA, nodeB, 42) // => 0 * }) * ``` * @@ -1414,50 +2284,53 @@ export const addEdge = ( target: NodeIndex, data: E ): EdgeIndex => { + assertMutable(mutable) + const impl = graphImpl(mutable) + // Validate that both nodes exist - if (!mutable.nodes.has(source)) { + if (!impl.nodes.has(source)) { throw missingNode(source) } - if (!mutable.nodes.has(target)) { + if (!impl.nodes.has(target)) { throw missingNode(target) } - const edgeIndex = mutable.nextEdgeIndex + const edgeIndex = impl.nextEdgeIndex // Create edge data const edgeData = new Edge({ source, target, data }) - mutable.edges.set(edgeIndex, edgeData) + impl.edges.set(edgeIndex, edgeData) // Update adjacency lists - const sourceAdjacency = mutable.adjacency.get(source) + const sourceAdjacency = impl.adjacency.get(source) if (sourceAdjacency !== undefined) { sourceAdjacency.push(edgeIndex) } - const targetReverseAdjacency = mutable.reverseAdjacency.get(target) + const targetReverseAdjacency = impl.reverseAdjacency.get(target) if (targetReverseAdjacency !== undefined) { targetReverseAdjacency.push(edgeIndex) } // For undirected graphs, add reverse connections - if (mutable.type === "undirected") { - const targetAdjacency = mutable.adjacency.get(target) + if (impl.type === "undirected") { + const targetAdjacency = impl.adjacency.get(target) if (targetAdjacency !== undefined) { targetAdjacency.push(edgeIndex) } - const sourceReverseAdjacency = mutable.reverseAdjacency.get(source) + const sourceReverseAdjacency = impl.reverseAdjacency.get(source) if (sourceReverseAdjacency !== undefined) { sourceReverseAdjacency.push(edgeIndex) } } // Update allocators - mutable.nextEdgeIndex = mutable.nextEdgeIndex + 1 + impl.nextEdgeIndex = impl.nextEdgeIndex + 1 // Only invalidate cycle flag if the graph was acyclic // Adding edges cannot remove cycles from cyclic graphs - invalidateCycleFlagOnAddition(mutable) + invalidateCycleFlagOnAddition(impl) return edgeIndex } @@ -1467,7 +2340,7 @@ export const addEdge = ( * * **Example** (Removing a node) * - * ```ts + * ```ts import.meta.vitest * import { Graph } from "effect" * * const result = Graph.mutate(Graph.directed(), (mutable) => { @@ -1478,6 +2351,7 @@ export const addEdge = ( * // Remove nodeA and all edges connected to it * Graph.removeNode(mutable, nodeA) * }) + * Array.of(Graph.nodeCount(result), Graph.edgeCount(result)) // => [1, 0] * ``` * * @category mutations @@ -1487,8 +2361,11 @@ export const removeNode = ( mutable: MutableGraph, nodeIndex: NodeIndex ): void => { + assertMutable(mutable) + const impl = graphImpl(mutable) + // Check if node exists - if (!mutable.nodes.has(nodeIndex)) { + if (!impl.nodes.has(nodeIndex)) { return // Node doesn't exist, nothing to remove } @@ -1496,7 +2373,7 @@ export const removeNode = ( const edgesToRemove: Array = [] // Get outgoing edges - const outgoingEdges = mutable.adjacency.get(nodeIndex) + const outgoingEdges = impl.adjacency.get(nodeIndex) if (outgoingEdges !== undefined) { for (const edge of outgoingEdges) { edgesToRemove.push(edge) @@ -1504,7 +2381,7 @@ export const removeNode = ( } // Get incoming edges - const incomingEdges = mutable.reverseAdjacency.get(nodeIndex) + const incomingEdges = impl.reverseAdjacency.get(nodeIndex) if (incomingEdges !== undefined) { for (const edge of incomingEdges) { edgesToRemove.push(edge) @@ -1513,17 +2390,17 @@ export const removeNode = ( // Remove all incident edges for (const edgeIndex of edgesToRemove) { - removeEdgeInternal(mutable, edgeIndex) + removeEdgeInternal(impl, edgeIndex) } // Remove the node itself - mutable.nodes.delete(nodeIndex) - mutable.adjacency.delete(nodeIndex) - mutable.reverseAdjacency.delete(nodeIndex) + impl.nodes.delete(nodeIndex) + impl.adjacency.delete(nodeIndex) + impl.reverseAdjacency.delete(nodeIndex) // Only invalidate cycle flag if the graph wasn't already known to be acyclic // Removing nodes cannot introduce cycles in an acyclic graph - invalidateCycleFlagOnRemoval(mutable) + invalidateCycleFlagOnRemoval(impl) } /** @@ -1531,7 +2408,7 @@ export const removeNode = ( * * **Example** (Removing an edge) * - * ```ts + * ```ts import.meta.vitest * import { Graph } from "effect" * * const result = Graph.mutate(Graph.directed(), (mutable) => { @@ -1542,6 +2419,7 @@ export const removeNode = ( * // Remove the edge * Graph.removeEdge(mutable, edge) * }) + * Array.of(Graph.nodeCount(result), Graph.edgeCount(result)) // => [2, 0] * ``` * * @category mutations @@ -1551,18 +2429,21 @@ export const removeEdge = ( mutable: MutableGraph, edgeIndex: EdgeIndex ): void => { - const wasRemoved = removeEdgeInternal(mutable, edgeIndex) + assertMutable(mutable) + const impl = graphImpl(mutable) + + const wasRemoved = removeEdgeInternal(impl, edgeIndex) // Only invalidate cycle flag if an edge was actually removed // and only if the graph wasn't already known to be acyclic if (wasRemoved) { - invalidateCycleFlagOnRemoval(mutable) + invalidateCycleFlagOnRemoval(impl) } } /** @internal */ const removeEdgeInternal = ( - mutable: MutableGraph, + mutable: GraphImpl, edgeIndex: EdgeIndex ): boolean => { // Get edge data @@ -1624,8 +2505,8 @@ const removeEdgeInternal = ( * * **Example** (Getting edge data) * - * ```ts - * import { Graph } from "effect" + * ```ts import.meta.vitest + * import { Graph, Option } from "effect" * * const graph = Graph.mutate(Graph.directed(), (mutable) => { * const nodeA = Graph.addNode(mutable, "Node A") @@ -1633,23 +2514,16 @@ const removeEdgeInternal = ( * Graph.addEdge(mutable, nodeA, nodeB, 42) * }) * - * const edgeIndex = 0 - * const edgeData = Graph.getEdge(graph, edgeIndex) - * - * if (edgeData._tag === "Some") { - * console.log(edgeData.value.data) // 42 - * console.log(edgeData.value.source) // 0 - * console.log(edgeData.value.target) // 1 - * } + * Graph.getEdge(graph, 0) // => Option.some(new Graph.Edge({ source: 0, target: 1, data: 42 })) * ``` * * @category getters * @since 3.18.0 */ export const getEdge: { - ( - edgeIndex: EdgeIndex - ): (graph: Graph | MutableGraph) => Option.Option> + (edgeIndex: EdgeIndex): ( + graph: Graph | MutableGraph + ) => Option.Option> ( graph: Graph | MutableGraph, edgeIndex: EdgeIndex @@ -1657,14 +2531,14 @@ export const getEdge: { } = dual(2, ( graph: Graph | MutableGraph, edgeIndex: EdgeIndex -): Option.Option> => Option.fromUndefinedOr(graph.edges.get(edgeIndex))) +): Option.Option> => Option.fromUndefinedOr(graphImpl(graph).edges.get(edgeIndex))) /** * Checks whether an edge exists between two nodes in the graph. * * **Example** (Checking edge existence) * - * ```ts + * ```ts import.meta.vitest * import { Graph } from "effect" * * const graph = Graph.mutate(Graph.directed(), (mutable) => { @@ -1674,15 +2548,8 @@ export const getEdge: { * Graph.addEdge(mutable, nodeA, nodeB, 42) * }) * - * const nodeA = 0 - * const nodeB = 1 - * const nodeC = 2 - * - * const hasAB = Graph.hasEdge(graph, nodeA, nodeB) - * console.log(hasAB) // true - * - * const hasAC = Graph.hasEdge(graph, nodeA, nodeC) - * console.log(hasAC) // false + * Graph.hasEdge(graph, 0, 1) // => true + * Graph.hasEdge(graph, 0, 2) // => false * ``` * * @category getters @@ -1703,14 +2570,15 @@ export const hasEdge: { source: NodeIndex, target: NodeIndex ): boolean => { - const adjacencyList = graph.adjacency.get(source) + const impl = graphImpl(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 = graph.edges.get(edgeIndex) + const edge = impl.edges.get(edgeIndex) if (edge !== undefined) { const neighbor = graph.type === "undirected" && edge.target === source ? edge.source : edge.target if (neighbor === target) { @@ -1727,11 +2595,11 @@ export const hasEdge: { * * **Example** (Counting edges) * - * ```ts + * ```ts import.meta.vitest * import { Graph } from "effect" * * const emptyGraph = Graph.directed() - * console.log(Graph.edgeCount(emptyGraph)) // 0 + * Graph.edgeCount(emptyGraph) // => 0 * * const graphWithEdges = Graph.mutate(emptyGraph, (mutable) => { * const nodeA = Graph.addNode(mutable, "Node A") @@ -1742,7 +2610,7 @@ export const hasEdge: { * Graph.addEdge(mutable, nodeC, nodeA, 3) * }) * - * console.log(Graph.edgeCount(graphWithEdges)) // 3 + * Graph.edgeCount(graphWithEdges) // => 3 * ``` * * @category getters @@ -1750,16 +2618,17 @@ export const hasEdge: { */ export const edgeCount = ( graph: Graph | MutableGraph -): number => graph.edges.size +): number => graphImpl(graph).edges.size const getDirectedNeighbors = ( graph: Graph | MutableGraph, nodeIndex: NodeIndex, direction: Direction ): Array => { + const impl = graphImpl(graph) const adjacencyMap = direction === "incoming" - ? graph.reverseAdjacency - : graph.adjacency + ? impl.reverseAdjacency + : impl.adjacency const adjacencyList = adjacencyMap.get(nodeIndex) if (adjacencyList === undefined) { @@ -1768,7 +2637,7 @@ const getDirectedNeighbors = ( const result: Array = [] for (const edgeIndex of adjacencyList) { - const edge = graph.edges.get(edgeIndex) + const edge = impl.edges.get(edgeIndex) if (edge !== undefined) { result.push(direction === "incoming" ? edge.source : edge.target) } @@ -1787,7 +2656,7 @@ const getDirectedNeighbors = ( * * **Example** (Getting outgoing neighbors) * - * ```ts + * ```ts import.meta.vitest * import { Graph } from "effect" * * const graph = Graph.mutate(Graph.directed(), (mutable) => { @@ -1798,15 +2667,8 @@ const getDirectedNeighbors = ( * Graph.addEdge(mutable, nodeA, nodeC, 2) * }) * - * const nodeA = 0 - * const nodeB = 1 - * const nodeC = 2 - * - * const neighborsA = Graph.neighbors(graph, nodeA) - * console.log(neighborsA) // [1, 2] - * - * const neighborsB = Graph.neighbors(graph, nodeB) - * console.log(neighborsB) // [] + * Graph.neighbors(graph, 0) // => [1, 2] + * Graph.neighbors(graph, 1) // => [] * ``` * * @category getters @@ -1847,7 +2709,7 @@ export const neighbors: { * @see {@link predecessors} for incoming neighbors in a directed graph * @see {@link neighbors} for generic neighbor lookup across graph kinds * - * @category queries + * @category getters * @since 4.0.0 */ export const successors: { @@ -1883,7 +2745,7 @@ export const successors: { * @see {@link successors} for outgoing neighbors in a directed graph * @see {@link neighbors} for generic neighbor lookup across graph kinds * - * @category queries + * @category getters * @since 4.0.0 */ export const predecessors: { @@ -1918,7 +2780,7 @@ export const predecessors: { * * **Example** (Traversing directed neighbors) * - * ```ts + * ```ts import.meta.vitest * import { Graph } from "effect" * * const graph = Graph.directed((mutable) => { @@ -1935,12 +2797,13 @@ export const predecessors: { * * // 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 queries + * @category getters * @since 3.18.0 */ export const neighborsDirected: { @@ -1978,7 +2841,7 @@ export const neighborsDirected: { * * **Example** (Configuring GraphViz labels) * - * ```ts + * ```ts import.meta.vitest * import type { Graph } from "effect" * * // Basic options with custom labels @@ -1993,6 +2856,7 @@ export const neighborsDirected: { * edgeLabel: (data) => data, * graphName: "MyDependencyGraph" * } + * Array.of(basicOptions.nodeLabel?.("A"), namedOptions.graphName) // => ["Node: A", "MyDependencyGraph"] * ``` * * @category options @@ -2018,12 +2882,15 @@ export interface GraphVizOptions { 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. * * **Example** (Exporting GraphViz DOT) * - * ```ts + * ```ts import.meta.vitest * import { Graph } from "effect" * * const graph = Graph.mutate(Graph.directed(), (mutable) => { @@ -2035,16 +2902,7 @@ export interface GraphVizOptions { * Graph.addEdge(mutable, nodeC, nodeA, 3) * }) * - * const dot = Graph.toGraphViz(graph) - * console.log(dot) - * // digraph G { - * // "0" [label="Node A"]; - * // "1" [label="Node B"]; - * // "2" [label="Node C"]; - * // "0" -> "1" [label="1"]; - * // "1" -> "2" [label="2"]; - * // "2" -> "0" [label="3"]; - * // } + * 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"];', "}"] * ``` * * @category converting @@ -2062,6 +2920,7 @@ export const toGraphViz: { graph: Graph | MutableGraph, options?: GraphVizOptions ): string => { + const impl = graphImpl(graph) const { edgeLabel = (data: E) => String(data), graphName = "G", @@ -2071,19 +2930,20 @@ export const toGraphViz: { const isDirected = graph.type === "directed" const graphType = isDirected ? "digraph" : "graph" const edgeOperator = isDirected ? "->" : "--" + const graphId = `"${escapeGraphVizString(graphName)}"` const lines: Array = [] - lines.push(`${graphType} ${graphName} {`) + lines.push(`${graphType} ${graphId} {`) // Add nodes - for (const [nodeIndex, nodeData] of graph.nodes) { - const label = nodeLabel(nodeData).replace(/"/g, "\\\"") + for (const [nodeIndex, nodeData] of impl.nodes) { + const label = escapeGraphVizString(nodeLabel(nodeData)) lines.push(` "${nodeIndex}" [label="${label}"];`) } // Add edges - for (const [, edgeData] of graph.edges) { - const label = edgeLabel(edgeData.data).replace(/"/g, "\\\"") + for (const [, edgeData] of impl.edges) { + const label = escapeGraphVizString(edgeLabel(edgeData.data)) lines.push(` "${edgeData.source}" ${edgeOperator} "${edgeData.target}" [label="${label}"];`) } @@ -2112,7 +2972,7 @@ export const toGraphViz: { * * **Example** (Selecting Mermaid node shapes) * - * ```ts + * ```ts import.meta.vitest * import type { Graph } from "effect" * * // Shape selector function for different node types @@ -2127,6 +2987,7 @@ export const toGraphViz: { * const options: Graph.MermaidOptions = { * nodeShape: shapeSelector * } + * options.nodeShape?.("decision") // => "diamond" * ``` * * @category models @@ -2155,7 +3016,7 @@ export type MermaidNodeShape = * * **Example** (Configuring Mermaid directions) * - * ```ts + * ```ts import.meta.vitest * import type { Graph } from "effect" * * // Horizontal workflow diagram @@ -2172,6 +3033,7 @@ export type MermaidNodeShape = * const bottomUpOptions: Graph.MermaidOptions = { * direction: "BT" * } + * Array.of(horizontalOptions.direction, verticalOptions.direction, bottomUpOptions.direction) // => ["LR", "TB", "BT"] * ``` * * @category models @@ -2198,7 +3060,7 @@ export type MermaidDirection = * * **Example** (Selecting Mermaid diagram types) * - * ```ts + * ```ts import.meta.vitest * import type { Graph } from "effect" * * // Force flowchart format (even for undirected graphs) @@ -2213,6 +3075,7 @@ export type MermaidDirection = * * // Auto-detection (recommended, default behavior) * const autoOptions: Graph.MermaidOptions = {} + * Array.of(flowchartOptions.diagramType, graphOptions.diagramType, autoOptions.diagramType) // => ["flowchart", "graph", undefined] * ``` * * @category models @@ -2238,7 +3101,7 @@ export type MermaidDiagramType = * * **Example** (Configuring Mermaid output) * - * ```ts + * ```ts import.meta.vitest * import type { Graph } from "effect" * * // Basic options with custom labels @@ -2255,6 +3118,7 @@ export type MermaidDiagramType = * direction: "LR", * nodeShape: (data) => data.includes("start") ? "circle" : "rectangle" * } + * Array.of(basicOptions.nodeLabel?.("A"), advancedOptions.nodeShape?.("start")) // => ["Node: A", "circle"] * ``` * * @category options @@ -2358,7 +3222,7 @@ const formatMermaidNode = ( * * **Example** (Exporting a directed Mermaid diagram) * - * ```ts + * ```ts import.meta.vitest * import { Graph } from "effect" * * // Basic directed graph export @@ -2370,19 +3234,12 @@ const formatMermaidNode = ( * Graph.addEdge(mutable, app, cache, 2) * }) * - * const mermaid = Graph.toMermaid(graph) - * console.log(mermaid) - * // flowchart TD - * // 0["App"] - * // 1["Database"] - * // 2["Cache"] - * // 0 -->|"1"| 1 - * // 0 -->|"2"| 2 + * Graph.toMermaid(graph).split("\n") // => ["flowchart TD", ' 0["App"]', ' 1["Database"]', ' 2["Cache"]', ' 0 -->|"1"| 1', ' 0 -->|"2"| 2'] * ``` * * **Example** (Exporting an undirected Mermaid diagram) * - * ```ts + * ```ts import.meta.vitest * import { Graph } from "effect" * * // Undirected graph with custom labels and direction @@ -2399,18 +3256,12 @@ const formatMermaidNode = ( * edgeLabel: (relationship) => relationship, * direction: "LR" * }) - * console.log(mermaid) - * // graph LR - * // 0["Alice"] - * // 1["Bob"] - * // 2["Charlie"] - * // 0 ---|"friends"| 1 - * // 1 ---|"colleagues"| 2 + * mermaid.split("\n") // => ["graph LR", ' 0["Alice"]', ' 1["Bob"]', ' 2["Charlie"]', ' 0 ---|"friends"| 1', ' 1 ---|"colleagues"| 2'] * ``` * * **Example** (Customizing Mermaid node shapes) * - * ```ts + * ```ts import.meta.vitest * import { Graph } from "effect" * * // Advanced styling with node shapes for flowchart @@ -2449,20 +3300,12 @@ const formatMermaidNode = ( * } * } * }) - * console.log(mermaid) - * // flowchart TD - * // 0(["Begin"]) - * // 1["Process Data"] - * // 2{"Valid?"} - * // 3(["Complete"]) - * // 0 --> 1 - * // 1 --> 2 - * // 2 --> 3 + * mermaid.split("\n") // => ["flowchart TD", ' 0(["Begin"])', ' 1["Process Data"]', ' 2{"Valid?"}', ' 3(["Complete"])', " 0 --> 1", " 1 --> 2", ' 2 -->|"yes"| 3'] * ``` * * **Example** (Visualizing dependency graphs) * - * ```ts + * ```ts import.meta.vitest * import { Graph } from "effect" * * // Real-world example: Software dependency graph @@ -2477,22 +3320,22 @@ const formatMermaidNode = ( * name: "MyApp", * version: "1.0.0", * type: "library" - * }) + * } satisfies Dependency) * const react = Graph.addNode(mutable, { * name: "React", * version: "18.0.0", * type: "framework" - * }) + * } satisfies Dependency) * const lodash = Graph.addNode(mutable, { * name: "Lodash", * version: "4.17.0", * type: "library" - * }) + * } satisfies Dependency) * const webpack = Graph.addNode(mutable, { * name: "Webpack", * version: "5.0.0", * type: "tool" - * }) + * } satisfies Dependency) * * Graph.addEdge(mutable, app, react, "depends on") * Graph.addEdge(mutable, app, lodash, "depends on") @@ -2511,15 +3354,7 @@ const formatMermaidNode = ( * direction: "TB" * }) * - * console.log(dependencyDiagram) - * // flowchart TB - * // 0["MyApp\nv1.0.0"] - * // 1{{"React\nv18.0.0"}} - * // 2["Lodash\nv4.17.0"] - * // 3{"Webpack\nv5.0.0"} - * // 0 -->|"depends on"| 1 - * // 0 -->|"depends on"| 2 - * // 0 -->|"builds with"| 3 + * dependencyDiagram.split("\n") // => ["flowchart TB", ' 0["MyApp#92;nv1.0.0"]', ' 1{{"React#92;nv18.0.0"}}', ' 2["Lodash#92;nv4.17.0"]', ' 3{"Webpack#92;nv5.0.0"}', ' 0 -->|"depends on"| 1', ' 0 -->|"depends on"| 2', ' 0 -->|"builds with"| 3'] * ``` * * @category converting @@ -2537,6 +3372,7 @@ export const toMermaid: { graph: Graph | MutableGraph, options?: MermaidOptions ): string => { + const impl = graphImpl(graph) // Extract and validate options with defaults const { diagramType, @@ -2555,7 +3391,7 @@ export const toMermaid: { lines.push(`${finalDiagramType} ${direction}`) // Add nodes - for (const [nodeIndex, nodeData] of graph.nodes) { + for (const [nodeIndex, nodeData] of impl.nodes) { const nodeId = String(nodeIndex) const label = escapeMermaidLabel(nodeLabel(nodeData)) const shape = nodeShape(nodeData) @@ -2565,7 +3401,7 @@ export const toMermaid: { // Add edges const edgeOperator = finalDiagramType === "flowchart" ? "-->" : "---" - for (const [, edgeData] of graph.edges) { + for (const [, edgeData] of impl.edges) { const sourceId = String(edgeData.source) const targetId = String(edgeData.target) const label = escapeMermaidLabel(edgeLabel(edgeData.data)) @@ -2581,38 +3417,53 @@ export const toMermaid: { }) // ============================================================================= -// Direction Types for Bidirectional Traversal +// Edge Direction Types // ============================================================================= /** - * Direction for graph traversal, indicating which edges to follow. + * 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 + * ```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 c = Graph.addNode(mutable, "C") + * Graph.addEdge(mutable, a, b, "A-B") + * Graph.addEdge(mutable, a, c, "A-C") * }) * - * // Follow outgoing edges (normal direction) - * const outgoingNodes = Array.from( - * Graph.indices(Graph.dfs(graph, { start: [0], direction: "outgoing" })) - * ) - * - * // Follow incoming edges (reverse direction) - * const incomingNodes = Array.from( - * Graph.indices(Graph.dfs(graph, { start: [1], direction: "incoming" })) - * ) + * 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 3.18.0 + * @since 4.0.0 */ -export type Direction = "outgoing" | "incoming" +export type TraversalDirection = Direction | "undirected" // ============================================================================= // Graph Structure Analysis Algorithms @@ -2625,11 +3476,12 @@ export type Direction = "outgoing" | "incoming" * * Uses depth-first search to detect back edges, which indicate cycles. * For directed graphs, any back edge creates a cycle. For undirected graphs, - * a back edge that doesn't go to the immediate parent creates a cycle. + * a back edge that doesn't use the same edge used to enter the current node + * creates a cycle. * * **Example** (Checking cycles) * - * ```ts + * ```ts import.meta.vitest * import { Graph } from "effect" * * // Acyclic directed graph (DAG) @@ -2640,7 +3492,7 @@ export type Direction = "outgoing" | "incoming" * Graph.addEdge(mutable, a, b, "A->B") * Graph.addEdge(mutable, b, c, "B->C") * }) - * console.log(Graph.isAcyclic(dag)) // true + * Graph.isAcyclic(dag) // => true * * // Cyclic directed graph * const cyclic = Graph.directed((mutable) => { @@ -2649,7 +3501,7 @@ export type Direction = "outgoing" | "incoming" * Graph.addEdge(mutable, a, b, "A->B") * Graph.addEdge(mutable, b, a, "B->A") // Creates cycle * }) - * console.log(Graph.isAcyclic(cyclic)) // false + * Graph.isAcyclic(cyclic) // => false * ``` * * @category algorithms @@ -2658,39 +3510,51 @@ export type Direction = "outgoing" | "incoming" export const isAcyclic = ( graph: Graph | MutableGraph ): boolean => { + const impl = graphImpl(graph) // Use existing cycle flag if available - if (Option.isSome(graph.acyclic)) { - return graph.acyclic.value + if (Option.isSome(impl.acyclic)) { + return impl.acyclic.value } if (graph.type === "undirected") { const visited = new Set() - for (const startNode of graph.nodes.keys()) { + for (const startNode of impl.nodes.keys()) { if (visited.has(startNode)) { continue } visited.add(startNode) - const stack: Array<{ node: NodeIndex; parent: NodeIndex | null }> = [{ node: startNode, parent: null }] + const stack: Array<{ node: NodeIndex; incoming: EdgeIndex | null }> = [{ node: startNode, incoming: null }] while (stack.length > 0) { - const { node, parent } = stack.pop()! - const nodeNeighbors = getUndirectedNeighbors(graph as any, node) + const { node, incoming } = stack.pop()! + const adjacencyList = impl.adjacency.get(node) + if (adjacencyList === undefined) { + continue + } - for (const neighbor of nodeNeighbors) { + for (const edgeIndex of adjacencyList) { + if (edgeIndex === incoming) { + continue + } + const edge = impl.edges.get(edgeIndex) + if (edge === undefined) { + continue + } + const neighbor = getTraversableNeighbor(graph, node, edge) if (!visited.has(neighbor)) { visited.add(neighbor) - stack.push({ node: neighbor, parent: node }) - } else if (neighbor !== parent) { - graph.acyclic = Option.some(false) + stack.push({ node: neighbor, incoming: edgeIndex }) + } else { + impl.acyclic = Option.some(false) return false } } } } - graph.acyclic = Option.some(true) + impl.acyclic = Option.some(true) return true } @@ -2702,7 +3566,7 @@ export const isAcyclic = ( type DfsStackEntry = [NodeIndex, Array, number, boolean] // Get all nodes to handle disconnected components - for (const startNode of graph.nodes.keys()) { + for (const startNode of impl.nodes.keys()) { if (visited.has(startNode)) { continue // Already processed this component } @@ -2717,7 +3581,7 @@ export const isAcyclic = ( if (isFirstVisit) { if (recursionStack.has(node)) { // Back edge found - cycle detected - graph.acyclic = Option.some(false) + impl.acyclic = Option.some(false) return false } @@ -2746,7 +3610,7 @@ export const isAcyclic = ( if (recursionStack.has(neighbor)) { // Back edge found - cycle detected - graph.acyclic = Option.some(false) + impl.acyclic = Option.some(false) return false } @@ -2762,7 +3626,7 @@ export const isAcyclic = ( } // Cache the result - graph.acyclic = Option.some(true) + impl.acyclic = Option.some(true) return true } @@ -2777,7 +3641,7 @@ export const isAcyclic = ( * * **Example** (Checking bipartite graphs) * - * ```ts + * ```ts import.meta.vitest * import { Graph } from "effect" * * // Bipartite graph (alternating coloring possible) @@ -2790,7 +3654,7 @@ export const isAcyclic = ( * Graph.addEdge(mutable, b, c, "edge") * Graph.addEdge(mutable, c, d, "edge") * }) - * console.log(Graph.isBipartite(bipartite)) // true + * Graph.isBipartite(bipartite) // => true * * // Non-bipartite graph (odd cycle) * const triangle = Graph.undirected((mutable) => { @@ -2801,7 +3665,7 @@ export const isAcyclic = ( * Graph.addEdge(mutable, b, c, "edge") * Graph.addEdge(mutable, c, a, "edge") // Triangle (3-cycle) * }) - * console.log(Graph.isBipartite(triangle)) // false + * Graph.isBipartite(triangle) // => false * ``` * * @category algorithms @@ -2810,12 +3674,13 @@ export const isAcyclic = ( export const isBipartite = ( graph: Graph | MutableGraph ): boolean => { + const impl = graphImpl(graph) const coloring = new Map() const discovered = new Set() let isBipartiteGraph = true // Get all nodes to handle disconnected components - for (const startNode of graph.nodes.keys()) { + for (const startNode of impl.nodes.keys()) { if (!discovered.has(startNode)) { // Start BFS coloring from this component const queue: Array = [startNode] @@ -2863,13 +3728,14 @@ const getUndirectedNeighbors = ( graph: Graph | MutableGraph, nodeIndex: NodeIndex ): Array => { + const impl = graphImpl(graph) const neighbors = new Set() // Check edges where this node is the source - const adjacencyList = graph.adjacency.get(nodeIndex) + const adjacencyList = impl.adjacency.get(nodeIndex) if (adjacencyList !== undefined) { for (const edgeIndex of adjacencyList) { - const edge = graph.edges.get(edgeIndex) + 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 @@ -2884,14 +3750,24 @@ const getUndirectedNeighbors = ( const getTraversalNeighbors = ( graph: Graph | MutableGraph, nodeIndex: NodeIndex, - direction: Direction -): Array => - graph.type === "undirected" - ? getUndirectedNeighbors(graph as any, nodeIndex) - : getDirectedNeighbors(graph as Graph | MutableGraph, nodeIndex, direction) + direction: TraversalDirection +): Array => { + if (graph.type === "undirected") { + return getUndirectedNeighbors(graph as any, nodeIndex) + } + const directed = graph as Graph | MutableGraph + if (direction !== "undirected") { + return getDirectedNeighbors(directed, nodeIndex, direction) + } + const neighbors = new Set(getDirectedNeighbors(directed, nodeIndex, "outgoing")) + for (const neighbor of getDirectedNeighbors(directed, nodeIndex, "incoming")) { + neighbors.add(neighbor) + } + return Array.from(neighbors) +} -const getTraversableNeighbor = ( - graph: Graph | MutableGraph, +const getTraversableNeighbor = ( + graph: Graph | MutableGraph, current: NodeIndex, edge: Edge ): NodeIndex => graph.type === "undirected" && edge.target === current ? edge.source : edge.target @@ -2902,7 +3778,7 @@ const getTraversableNeighbor = ( * * **Example** (Finding connected components) * - * ```ts + * ```ts import.meta.vitest * import { Graph } from "effect" * * const graph = Graph.undirected((mutable) => { @@ -2914,8 +3790,7 @@ const getTraversableNeighbor = ( * Graph.addEdge(mutable, c, d, "edge") // Component 2: C-D * }) * - * const components = Graph.connectedComponents(graph) - * console.log(components) // [[0, 1], [2, 3]] + * Graph.connectedComponents(graph) // => [[0, 1], [2, 3]] * ``` * * @category algorithms @@ -2924,9 +3799,10 @@ const getTraversableNeighbor = ( export const connectedComponents = ( graph: Graph | MutableGraph ): Array> => { + const impl = graphImpl(graph) const visited = new Set() const components: Array> = [] - for (const startNode of graph.nodes.keys()) { + for (const startNode of impl.nodes.keys()) { if (!visited.has(startNode)) { // DFS to find all nodes in this component const component: Array = [] @@ -2965,7 +3841,7 @@ export const connectedComponents = ( * * **Example** (Finding strongly connected components) * - * ```ts + * ```ts import.meta.vitest * import { Graph } from "effect" * * const graph = Graph.directed((mutable) => { @@ -2977,8 +3853,7 @@ export const connectedComponents = ( * Graph.addEdge(mutable, c, a, "C->A") // Creates SCC: A-B-C * }) * - * const sccs = Graph.stronglyConnectedComponents(graph) - * console.log(sccs) // [[0, 1, 2]] + * Graph.stronglyConnectedComponents(graph) // => [[0, 2, 1]] * ``` * * @category algorithms @@ -2991,6 +3866,7 @@ export const stronglyConnectedComponents = ( throw new GraphError({ message: "Cannot find strongly connected components of undirected graph" }) } + const impl = graphImpl(graph) const visited = new Set() const finishOrder: Array = [] // Iterate directly over node keys @@ -2999,7 +3875,7 @@ export const stronglyConnectedComponents = ( // Stack entry: [node, neighbors, neighborIndex, isFirstVisit] type DfsStackEntry = [NodeIndex, Array, number, boolean] - for (const startNode of graph.nodes.keys()) { + for (const startNode of impl.nodes.keys()) { if (visited.has(startNode)) { continue } @@ -3061,10 +3937,10 @@ export const stronglyConnectedComponents = ( scc.push(node) // Use reverse adjacency (transpose graph) - const reverseAdjacency = graph.reverseAdjacency.get(node) + const reverseAdjacency = impl.reverseAdjacency.get(node) if (reverseAdjacency !== undefined) { for (const edgeIndex of reverseAdjacency) { - const edge = graph.edges.get(edgeIndex) + const edge = impl.edges.get(edgeIndex) if (edge !== undefined) { const predecessor = edge.source if (!visited.has(predecessor)) { @@ -3118,6 +3994,53 @@ export interface PathResult { readonly costs: Array } +interface MinHeapEntry { + readonly node: NodeIndex + readonly priority: number + readonly sequence: number +} + +const minHeapLessThan = (self: MinHeapEntry, that: MinHeapEntry): boolean => + self.priority < that.priority || (self.priority === that.priority && self.sequence < that.sequence) + +const minHeapPush = (heap: Array, entry: MinHeapEntry): void => { + let index = heap.length + heap.push(entry) + while (index > 0) { + const parent = (index - 1) >>> 1 + if (!minHeapLessThan(entry, heap[parent])) { + break + } + heap[index] = heap[parent] + index = parent + } + heap[index] = entry +} + +const minHeapPop = (heap: Array): MinHeapEntry | undefined => { + const first = heap[0] + const last = heap.pop() + if (last === undefined || heap.length === 0) { + return first + } + let index = 0 + while (true) { + const left = index * 2 + 1 + if (left >= heap.length) { + break + } + const right = left + 1 + const child = right < heap.length && minHeapLessThan(heap[right], heap[left]) ? right : left + if (!minHeapLessThan(heap[child], last)) { + break + } + heap[index] = heap[child] + index = child + } + heap[index] = last + return first +} + /** * Configuration for finding a shortest path with Dijkstra's algorithm. * @@ -3129,12 +4052,13 @@ export interface PathResult { * **Details** * * Specifies the source and target node indices, plus a cost function that maps - * each edge's data to a non-negative numeric weight. + * each edge's data to a non-negative numeric weight. `Infinity` is allowed and + * behaves like an impassable edge. * * **Gotchas** * * `dijkstra` throws a `GraphError` when either endpoint does not exist or when - * the cost function returns a negative weight. + * the cost function returns a negative weight or `NaN`. * * @see {@link dijkstra} for the algorithm that consumes this configuration * @see {@link AstarConfig} for heuristic shortest-path search @@ -3149,36 +4073,21 @@ export interface DijkstraConfig { cost: (edgeData: E) => number } -const validateNonNegativeEdgeWeights = ( - graph: Graph | MutableGraph, - cost: (edgeData: E) => number, - algorithm: string -): Map => { - const edgeWeights = new Map() - for (const [edgeIndex, edgeData] of graph.edges) { - const weight = cost(edgeData.data) - if (weight < 0 || Number.isNaN(weight)) { - throw new GraphError({ message: `${algorithm} requires non-negative edge weights` }) - } - edgeWeights.set(edgeIndex, weight) - } - return edgeWeights -} - /** * Finds the shortest path from the configured source node to the target node * using Dijkstra's algorithm. * * **Details** * - * Edge costs must be non-negative. Returns `Option.none()` when the target is - * not reachable, and throws a `GraphError` when either endpoint is missing or a - * negative edge cost is encountered. + * Edge costs must be non-negative and not `NaN`. `Infinity` is allowed and + * behaves like an impassable edge. Returns `Option.none()` when the target is + * not reachable, and throws a `GraphError` when either endpoint is missing or an + * edge cost is negative or `NaN`. * * **Example** (Finding shortest paths with Dijkstra) * - * ```ts - * import { Graph } from "effect" + * ```ts import.meta.vitest + * import { Graph, Option } from "effect" * * const graph = Graph.directed((mutable) => { * const a = Graph.addNode(mutable, "A") @@ -3195,10 +4104,7 @@ const validateNonNegativeEdgeWeights = ( * cost: (edgeData) => edgeData * }) * - * if (result._tag === "Some") { - * console.log(result.value.path) // [0, 1, 2] - shortest path A->B->C - * console.log(result.value.distance) // 7 - total distance - * } + * Option.map(result, ({ distance, path }) => [distance, path] as const) // => Option.some([7, [0, 1, 2]]) * ``` * * @category algorithms @@ -3216,15 +4122,23 @@ export const dijkstra: { graph: Graph | MutableGraph, config: DijkstraConfig ): Option.Option> => { + const impl = graphImpl(graph) // Validate that source and target nodes exist - if (!graph.nodes.has(config.source)) { + if (!impl.nodes.has(config.source)) { throw missingNode(config.source) } - if (!graph.nodes.has(config.target)) { + if (!impl.nodes.has(config.target)) { throw missingNode(config.target) } - const edgeWeights = validateNonNegativeEdgeWeights(graph, config.cost, "Dijkstra's algorithm") + const edgeWeights = new Map() + for (const [edgeIndex, edgeData] of impl.edges) { + const weight = config.cost(edgeData.data) + if (Number.isNaN(weight) || weight < 0) { + throw new GraphError({ message: "Dijkstra's algorithm requires non-negative edge weights" }) + } + edgeWeights.set(edgeIndex, weight) + } // Early return if source equals target if (config.source === config.target) { @@ -3242,26 +4156,17 @@ export const dijkstra: { // Initialize distances // Iterate directly over node keys - for (const node of graph.nodes.keys()) { + for (const node of impl.nodes.keys()) { distances.set(node, node === config.source ? 0 : Infinity) previous.set(node, null) } - // Simple priority queue using array (can be optimized with proper heap) - const priorityQueue: Array<{ node: NodeIndex; distance: number }> = [ - { node: config.source, distance: 0 } - ] + const priorityQueue: Array = [] + let sequence = 0 + minHeapPush(priorityQueue, { node: config.source, priority: 0, sequence: sequence++ }) while (priorityQueue.length > 0) { - // Find minimum distance node (priority queue extract-min) - let minIndex = 0 - for (let i = 1; i < priorityQueue.length; i++) { - if (priorityQueue[i].distance < priorityQueue[minIndex].distance) { - minIndex = i - } - } - - const current = priorityQueue.splice(minIndex, 1)[0] + const current = minHeapPop(priorityQueue)! const currentNode = current.node // Skip if already visited (can happen with duplicate entries) @@ -3280,10 +4185,10 @@ export const dijkstra: { const currentDistance = distances.get(currentNode)! // Examine all outgoing edges - const adjacencyList = graph.adjacency.get(currentNode) + const adjacencyList = impl.adjacency.get(currentNode) if (adjacencyList !== undefined) { for (const edgeIndex of adjacencyList) { - const edge = graph.edges.get(edgeIndex) + const edge = impl.edges.get(edgeIndex) if (edge !== undefined) { const neighbor = getTraversableNeighbor(graph, currentNode, edge) const cost = edgeWeights.get(edgeIndex)! @@ -3298,7 +4203,7 @@ export const dijkstra: { // Add to priority queue if not visited if (!visited.has(neighbor)) { - priorityQueue.push({ node: neighbor, distance: newDistance }) + minHeapPush(priorityQueue, { node: neighbor, priority: newDistance, sequence: sequence++ }) } } } @@ -3368,12 +4273,13 @@ export interface AllPairsResult { * **Details** * * Computes distances, reconstructed node paths, and edge-data paths for every - * source and target pair in O(V^3) time. Negative edge weights are allowed, but - * a `GraphError` is thrown if any negative cycle is detected. + * source and target pair in O(V^3) time. Negative edge weights are allowed, and + * `Infinity` behaves like an impassable edge. A `GraphError` is thrown if any + * edge weight is `NaN` or `-Infinity`, or if any negative cycle is detected. * * **Example** (Finding all-pairs shortest paths) * - * ```ts + * ```ts import.meta.vitest * import { Graph } from "effect" * * const graph = Graph.directed((mutable) => { @@ -3386,8 +4292,8 @@ export interface AllPairsResult { * }) * * const result = Graph.floydWarshall(graph, (edgeData) => edgeData) - * const distanceAToC = result.distances.get(0)?.get(2) // 5 (A->B->C) - * const pathAToC = result.paths.get(0)?.get(2) // [0, 1, 2] + * const shortest = { distance: result.distances.get(0)?.get(2), path: result.paths.get(0)?.get(2) } + * shortest // => { distance: 5, path: [0, 1, 2] } * ``` * * @category algorithms @@ -3405,13 +4311,14 @@ export const floydWarshall: { graph: Graph | MutableGraph, cost: (edgeData: E) => number ): AllPairsResult => { + const impl = graphImpl(graph) // Get all nodes for Floyd-Warshall algorithm (needs array for nested iteration) - const allNodes = Array.from(graph.nodes.keys()) + const allNodes = Array.from(impl.nodes.keys()) // Initialize distance matrix const distances = new Map>() - const next = new Map>() - const edgeMatrix = new Map>() + const next = new Map>() + const edgeMatrix = new Map>() // Initialize with infinity for all pairs for (const i of allNodes) { @@ -3421,14 +4328,15 @@ export const floydWarshall: { for (const j of allNodes) { distances.get(i)!.set(j, i === j ? 0 : Infinity) - next.get(i)!.set(j, null) - edgeMatrix.get(i)!.set(j, null) } } // Set edge weights - for (const [, edgeData] of graph.edges) { + for (const [, edgeData] of impl.edges) { const weight = cost(edgeData.data) + if (Number.isNaN(weight) || weight === -Infinity) { + throw new GraphError({ message: "Floyd-Warshall algorithm does not support NaN or -Infinity edge weights" }) + } const i = edgeData.source const j = edgeData.target @@ -3459,8 +4367,11 @@ export const floydWarshall: { const distIJ = distances.get(i)!.get(j)! if (distIK !== Infinity && distKJ !== Infinity && distIK + distKJ < distIJ) { - distances.get(i)!.set(j, distIK + distKJ) - next.get(i)!.set(j, next.get(i)!.get(k)!) + const nextIK = next.get(i)!.get(k) + if (nextIK !== undefined) { + distances.get(i)!.set(j, distIK + distKJ) + next.get(i)!.set(j, nextIK) + } } } } @@ -3496,12 +4407,12 @@ export const floydWarshall: { path.push(current) while (current !== j) { - const nextNode = next.get(current)!.get(j)! - if (nextNode === null) break + const nextNode = next.get(current)!.get(j) + if (nextNode === undefined) break - const edgeData = edgeMatrix.get(current)!.get(nextNode)! - if (edgeData !== null) { - weights.push(edgeData) + const edgeRow = edgeMatrix.get(current)! + if (edgeRow.has(nextNode)) { + weights.push(edgeRow.get(nextNode) as E) } current = nextNode @@ -3531,8 +4442,9 @@ export const floydWarshall: { * * **Details** * - * Specifies the source and target node indices, an edge-cost function, and a - * heuristic that estimates the remaining cost from a node to the target. + * Specifies the source and target node indices, an edge-cost function that maps + * edge data to non-negative weights, and a heuristic that estimates the + * remaining cost from a node to the target. * * @see {@link astar} for the algorithm that consumes this configuration * @see {@link DijkstraConfig} for shortest paths without a heuristic @@ -3554,15 +4466,16 @@ export interface AstarConfig { * * **Details** * - * The edge-cost function must return non-negative weights, and the heuristic + * The edge-cost function must return non-negative weights and not `NaN`. + * `Infinity` is allowed and behaves like an impassable edge. The heuristic * should be consistent to preserve shortest-path guarantees. Returns * `Option.none()` when the target is not reachable, and throws a `GraphError` - * when either endpoint is missing or a negative edge cost is encountered. + * when either endpoint is missing or an edge cost is negative or `NaN`. * * **Example** (Finding shortest paths with A-star) * - * ```ts - * import { Graph } from "effect" + * ```ts import.meta.vitest + * import { Graph, Option } from "effect" * * const graph = Graph.directed<{ x: number; y: number }, number>((mutable) => { * const a = Graph.addNode(mutable, { x: 0, y: 0 }) @@ -3585,10 +4498,7 @@ export interface AstarConfig { * heuristic * }) * - * if (result._tag === "Some") { - * console.log(result.value.path) // [0, 1, 2] - shortest path - * console.log(result.value.distance) // 2 - total distance - * } + * Option.map(result, ({ distance, path }) => [distance, path] as const) // => Option.some([2, [0, 1, 2]]) * ``` * * @category algorithms @@ -3606,15 +4516,23 @@ export const astar: { graph: Graph | MutableGraph, config: AstarConfig ): Option.Option> => { + const impl = graphImpl(graph) // Validate that source and target nodes exist - if (!graph.nodes.has(config.source)) { + if (!impl.nodes.has(config.source)) { throw missingNode(config.source) } - if (!graph.nodes.has(config.target)) { + if (!impl.nodes.has(config.target)) { throw missingNode(config.target) } - const edgeWeights = validateNonNegativeEdgeWeights(graph, config.cost, "A* algorithm") + const edgeWeights = new Map() + for (const [edgeIndex, edgeData] of impl.edges) { + const weight = config.cost(edgeData.data) + if (Number.isNaN(weight) || weight < 0) { + throw new GraphError({ message: "A* algorithm requires non-negative edge weights" }) + } + edgeWeights.set(edgeIndex, weight) + } // Early return if source equals target if (config.source === config.target) { @@ -3639,7 +4557,7 @@ export const astar: { // Initialize scores // Iterate directly over node keys - for (const node of graph.nodes.keys()) { + for (const node of impl.nodes.keys()) { gScore.set(node, node === config.source ? 0 : Infinity) fScore.set(node, Infinity) previous.set(node, null) @@ -3652,21 +4570,16 @@ export const astar: { fScore.set(config.source, h) } - // Priority queue using f-score (total estimated cost) - const openSet: Array<{ node: NodeIndex; fScore: number }> = [ - { node: config.source, fScore: fScore.get(config.source)! } - ] + const openSet: Array = [] + let sequence = 0 + minHeapPush(openSet, { + node: config.source, + priority: fScore.get(config.source)!, + sequence: sequence++ + }) while (openSet.length > 0) { - // Find node with lowest f-score - let minIndex = 0 - for (let i = 1; i < openSet.length; i++) { - if (openSet[i].fScore < openSet[minIndex].fScore) { - minIndex = i - } - } - - const current = openSet.splice(minIndex, 1)[0] + const current = minHeapPop(openSet)! const currentNode = current.node // Skip if already visited @@ -3685,10 +4598,10 @@ export const astar: { const currentGScore = gScore.get(currentNode)! // Examine all outgoing edges - const adjacencyList = graph.adjacency.get(currentNode) + const adjacencyList = impl.adjacency.get(currentNode) if (adjacencyList !== undefined) { for (const edgeIndex of adjacencyList) { - const edge = graph.edges.get(edgeIndex) + const edge = impl.edges.get(edgeIndex) if (edge !== undefined) { const neighbor = getTraversableNeighbor(graph, currentNode, edge) const weight = edgeWeights.get(edgeIndex)! @@ -3711,7 +4624,7 @@ export const astar: { // Add to open set if not visited if (!visited.has(neighbor)) { - openSet.push({ node: neighbor, fScore: f }) + minHeapPush(openSet, { node: neighbor, priority: f, sequence: sequence++ }) } } } @@ -3781,14 +4694,15 @@ export interface BellmanFordConfig { * * **Details** * - * Negative edge weights are allowed. Returns `Option.none()` when the target is - * unreachable or when a negative cycle affects the path to the target. Throws a - * `GraphError` when either endpoint is missing. + * Negative edge weights are allowed, and `Infinity` behaves like an impassable + * edge. Returns `Option.none()` when the target is unreachable or when a + * negative cycle affects the path to the target. Throws a `GraphError` when + * either endpoint is missing or an edge weight is `NaN` or `-Infinity`. * * **Example** (Finding shortest paths with Bellman-Ford) * - * ```ts - * import { Graph } from "effect" + * ```ts import.meta.vitest + * import { Graph, Option } from "effect" * * const graph = Graph.directed((mutable) => { * const a = Graph.addNode(mutable, "A") @@ -3805,10 +4719,7 @@ export interface BellmanFordConfig { * cost: (edgeData) => edgeData * }) * - * if (result._tag === "Some") { - * console.log(result.value.path) // [0, 1, 2] - shortest path A->B->C - * console.log(result.value.distance) // 2 - total distance - * } + * Option.map(result, ({ distance, path }) => [distance, path] as const) // => Option.some([2, [0, 1, 2]]) * ``` * * @category algorithms @@ -3826,37 +4737,32 @@ export const bellmanFord: { graph: Graph | MutableGraph, config: BellmanFordConfig ): Option.Option> => { + const impl = graphImpl(graph) // Validate that source and target nodes exist - if (!graph.nodes.has(config.source)) { + if (!impl.nodes.has(config.source)) { throw missingNode(config.source) } - if (!graph.nodes.has(config.target)) { + if (!impl.nodes.has(config.target)) { throw missingNode(config.target) } - // Early return if source equals target - if (config.source === config.target) { - return Option.some({ - path: [config.source], - distance: 0, - costs: [] - }) - } - // Initialize distances and predecessors const distances = new Map() const previous = new Map() // Iterate directly over node keys - for (const node of graph.nodes.keys()) { + for (const node of impl.nodes.keys()) { distances.set(node, node === config.source ? 0 : Infinity) previous.set(node, null) } // Collect all edges for relaxation const edges: Array<{ source: NodeIndex; target: NodeIndex; weight: number; edgeData: E }> = [] - for (const [, edgeData] of graph.edges) { + for (const [, edgeData] of impl.edges) { const weight = config.cost(edgeData.data) + if (Number.isNaN(weight) || weight === -Infinity) { + throw new GraphError({ message: "Bellman-Ford algorithm does not support NaN or -Infinity edge weights" }) + } edges.push({ source: edgeData.source, target: edgeData.target, @@ -3874,7 +4780,7 @@ export const bellmanFord: { } // Relax edges up to V-1 times - const nodeCount = graph.nodes.size + const nodeCount = impl.nodes.size for (let i = 0; i < nodeCount - 1; i++) { let hasUpdate = false @@ -3964,7 +4870,7 @@ export const bellmanFord: { * * **Example** (Working with node walkers) * - * ```ts + * ```ts import.meta.vitest * import { Graph } from "effect" * * const graph = Graph.directed((mutable) => { @@ -3983,8 +4889,8 @@ export const bellmanFord: { * } * * // Access node data using values() or entries() - * const nodeData = Array.from(Graph.values(dfsNodes)) // ["A", "B"] - * const nodeEntries = Array.from(Graph.entries(allNodes)) // [[0, "A"], [1, "B"]] + * Array.from(Graph.values(dfsNodes)) // => ["A", "B"] + * Array.from(Graph.entries(allNodes)) // => [[0, "A"], [1, "B"]] * ``` * * @category models @@ -4005,7 +4911,7 @@ export class Walker implements Iterable<[T, N]> { * * **Example** (Visiting walker elements) * - * ```ts + * ```ts import.meta.vitest * import { Graph } from "effect" * * const graph = Graph.directed((mutable) => { @@ -4017,14 +4923,10 @@ export class Walker implements Iterable<[T, N]> { * const dfs = Graph.dfs(graph, { start: [0] }) * * // Map to just the node data - * const values = Array.from(dfs.visit((index, data) => data)) - * console.log(values) // ["A", "B"] + * Array.from(dfs.visit((index, data) => data)) // => ["A", "B"] * * // Map to custom objects - * const custom = Array.from( - * dfs.visit((index, data) => ({ id: index, name: data })) - * ) - * console.log(custom) // [{ id: 0, name: "A" }, { id: 1, name: "B" }] + * Array.from(dfs.visit((index, data) => ({ id: index, name: data }))) // => [{ id: 0, name: "A" }, { id: 1, name: "B" }] * ``` * * @since 4.0.0 @@ -4041,7 +4943,7 @@ export class Walker implements Iterable<[T, N]> { * * **Example** (Visiting walker elements) * - * ```ts + * ```ts import.meta.vitest * import { Graph } from "effect" * * const graph = Graph.directed((mutable) => { @@ -4053,14 +4955,10 @@ export class Walker implements Iterable<[T, N]> { * const dfs = Graph.dfs(graph, { start: [0] }) * * // Map to just the node data - * const values = Array.from(dfs.visit((index, data) => data)) - * console.log(values) // ["A", "B"] + * Array.from(dfs.visit((index, data) => data)) // => ["A", "B"] * * // Map to custom objects - * const custom = Array.from( - * dfs.visit((index, data) => ({ id: index, name: data })) - * ) - * console.log(custom) // [{ id: 0, name: "A" }, { id: 1, name: "B" }] + * Array.from(dfs.visit((index, data) => ({ id: index, name: data }))) // => [{ id: 0, name: "A" }, { id: 1, name: "B" }] * ``` * * @category iterators @@ -4069,7 +4967,7 @@ export class Walker implements Iterable<[T, N]> { visit: (f: (index: T, data: N) => U) => Iterable ) { this.visit = visit - this[Symbol.iterator] = visit((index, data) => [index, data] as [T, N])[Symbol.iterator] + this[Symbol.iterator] = () => visit((index, data) => [index, data] as [T, N])[Symbol.iterator]() } } @@ -4114,7 +5012,7 @@ export type EdgeWalker = Walker> * * **Example** (Iterating walker indices) * - * ```ts + * ```ts import.meta.vitest * import { Graph } from "effect" * * const graph = Graph.directed((mutable) => { @@ -4124,8 +5022,7 @@ export type EdgeWalker = Walker> * }) * * const dfs = Graph.dfs(graph, { start: [0] }) - * const indices = Array.from(Graph.indices(dfs)) - * console.log(indices) // [0, 1] + * Array.from(Graph.indices(dfs)) // => [0, 1] * ``` * * @category iterators @@ -4138,7 +5035,7 @@ export const indices = (walker: Walker): Iterable => walker.visit * * **Example** (Iterating walker values) * - * ```ts + * ```ts import.meta.vitest * import { Graph } from "effect" * * const graph = Graph.directed((mutable) => { @@ -4148,8 +5045,7 @@ export const indices = (walker: Walker): Iterable => walker.visit * }) * * const dfs = Graph.dfs(graph, { start: [0] }) - * const values = Array.from(Graph.values(dfs)) - * console.log(values) // ["A", "B"] + * Array.from(Graph.values(dfs)) // => ["A", "B"] * ``` * * @category iterators @@ -4162,7 +5058,7 @@ export const values = (walker: Walker): Iterable => walker.visit( * * **Example** (Iterating walker entries) * - * ```ts + * ```ts import.meta.vitest * import { Graph } from "effect" * * const graph = Graph.directed((mutable) => { @@ -4172,8 +5068,7 @@ export const values = (walker: Walker): Iterable => walker.visit( * }) * * const dfs = Graph.dfs(graph, { start: [0] }) - * const entries = Array.from(Graph.entries(dfs)) - * console.log(entries) // [[0, "A"], [1, "B"]] + * Array.from(Graph.entries(dfs)) // => [[0, "A"], [1, "B"]] * ``` * * @category iterators @@ -4194,7 +5089,8 @@ export const entries = (walker: Walker): Iterable<[T, N]> => * * `start` supplies the node indices where traversal begins. If it is omitted, * the iterator is empty. `direction` chooses whether traversal follows - * outgoing or incoming edges. + * outgoing edges, incoming edges, or ignores edge direction. `radius` limits + * traversal by edge distance from the nearest start node. * * **Gotchas** * @@ -4210,7 +5106,8 @@ export const entries = (walker: Walker): Iterable<[T, N]> => */ export interface SearchConfig { readonly start?: Array - readonly direction?: Direction + readonly direction?: TraversalDirection + readonly radius?: number } /** @@ -4220,12 +5117,13 @@ export interface SearchConfig { * **Details** * * If no start nodes are supplied, the iterator is empty. The `direction` option - * chooses whether to follow outgoing or incoming edges. Throws a `GraphError` + * chooses whether to follow outgoing or incoming edges. The `radius` option + * limits traversal by edge distance from the start nodes. Throws a `GraphError` * if any configured start node does not exist. * * **Example** (Traversing depth-first) * - * ```ts + * ```ts import.meta.vitest * import { Graph } from "effect" * * const graph = Graph.directed((mutable) => { @@ -4237,14 +5135,10 @@ export interface SearchConfig { * }) * * // Start from a specific node - * const dfs1 = Graph.dfs(graph, { start: [0] }) - * for (const nodeIndex of Graph.indices(dfs1)) { - * console.log(nodeIndex) // Traverses in DFS order: 0, 1, 2 - * } + * Array.from(Graph.indices(Graph.dfs(graph, { start: [0] }))) // => [0, 1, 2] * * // Empty iterator (no starting nodes) - * const dfs2 = Graph.dfs(graph) - * // Can be used programmatically + * Graph.dfs(graph) * ``` * * @category iterators @@ -4264,6 +5158,7 @@ export const dfs: { ): NodeWalker => { const start = config.start ?? [] const direction = config.direction ?? "outgoing" + const radius = config.radius ?? Infinity // Validate that all start nodes exist for (const nodeIndex of start) { @@ -4274,32 +5169,69 @@ export const dfs: { return new Walker((f) => ({ [Symbol.iterator]: () => { - const stack = [...start] - const discovered = new Set() + const depths = radius === Infinity ? undefined : new Map() + const stack: Array = [] + const yielded = new Set() + + if (depths === undefined) { + for (const node of start) { + stack.push([node, 0]) + } + } else { + const starts = new Set() + for (let i = start.length - 1; i >= 0; i--) { + const node = start[i] + if (!starts.has(node)) { + starts.add(node) + stack.push([node, 0]) + depths.set(node, 0) + } + } + stack.reverse() + } const nextMapped = () => { while (stack.length > 0) { - const current = stack.pop()! + const [current, depth] = stack.pop()! - if (discovered.has(current)) { + if (depths === undefined) { + if (yielded.has(current)) { + continue + } + } else if (depths.get(current) !== depth) { continue } - discovered.add(current) - const nodeDataOption = getNode(graph, current) if (Option.isNone(nodeDataOption)) { continue } - const neighbors = getTraversalNeighbors(graph, current, direction) - for (let i = neighbors.length - 1; i >= 0; i--) { - const neighbor = neighbors[i] - if (!discovered.has(neighbor)) { - stack.push(neighbor) + if (depth < radius) { + const neighbors = getTraversalNeighbors(graph, current, direction) + for (let i = neighbors.length - 1; i >= 0; i--) { + const neighbor = neighbors[i] + const nextDepth = depth + 1 + if (depths === undefined) { + if (!yielded.has(neighbor)) { + stack.push([neighbor, nextDepth]) + } + continue + } + const neighborDepth = depths.get(neighbor) + if (neighborDepth === undefined || nextDepth < neighborDepth) { + depths.set(neighbor, nextDepth) + stack.push([neighbor, nextDepth]) + } } } + if (yielded.has(current)) { + continue + } + + yielded.add(current) + return { done: false, value: f(current, nodeDataOption.value) } } @@ -4318,12 +5250,13 @@ export const dfs: { * **Details** * * If no start nodes are supplied, the iterator is empty. The `direction` option - * chooses whether to follow outgoing or incoming edges. Throws a `GraphError` + * chooses whether to follow outgoing or incoming edges. The `radius` option + * limits traversal by edge distance from the start nodes. Throws a `GraphError` * if any configured start node does not exist. * * **Example** (Traversing breadth-first) * - * ```ts + * ```ts import.meta.vitest * import { Graph } from "effect" * * const graph = Graph.directed((mutable) => { @@ -4335,14 +5268,10 @@ export const dfs: { * }) * * // Start from a specific node - * const bfs1 = Graph.bfs(graph, { start: [0] }) - * for (const nodeIndex of Graph.indices(bfs1)) { - * console.log(nodeIndex) // Traverses in BFS order: 0, 1, 2 - * } + * Array.from(Graph.indices(Graph.bfs(graph, { start: [0] }))) // => [0, 1, 2] * * // Empty iterator (no starting nodes) - * const bfs2 = Graph.bfs(graph) - * // Can be used programmatically + * Graph.bfs(graph) * ``` * * @category iterators @@ -4362,6 +5291,7 @@ export const bfs: { ): NodeWalker => { const start = config.start ?? [] const direction = config.direction ?? "outgoing" + const radius = config.radius ?? Infinity // Validate that all start nodes exist for (const nodeIndex of start) { @@ -4372,20 +5302,22 @@ export const bfs: { return new Walker((f) => ({ [Symbol.iterator]: () => { - const queue = [...start] + const queue: Array = start.map((node) => [node, 0]) const discovered = new Set() const nextMapped = () => { while (queue.length > 0) { - const current = queue.shift()! + const [current, depth] = queue.shift()! if (!discovered.has(current)) { discovered.add(current) - const neighbors = getTraversalNeighbors(graph, current, direction) - for (const neighbor of neighbors) { - if (!discovered.has(neighbor)) { - queue.push(neighbor) + if (depth < radius) { + const neighbors = getTraversalNeighbors(graph, current, direction) + for (const neighbor of neighbors) { + if (!discovered.has(neighbor)) { + queue.push([neighbor, depth + 1]) + } } } @@ -4393,7 +5325,7 @@ export const bfs: { if (Option.isSome(nodeData)) { return { done: false, value: f(current, nodeData.value) } } - return nextMapped() + continue } } @@ -4441,7 +5373,7 @@ export interface TopoConfig { * * **Example** (Sorting topologically) * - * ```ts + * ```ts import.meta.vitest * import { Graph } from "effect" * * const graph = Graph.directed((mutable) => { @@ -4453,13 +5385,10 @@ export interface TopoConfig { * }) * * // Standard topological sort - * const topo1 = Graph.topo(graph) - * for (const nodeIndex of Graph.indices(topo1)) { - * console.log(nodeIndex) // 0, 1, 2 (topological order) - * } + * Array.from(Graph.indices(Graph.topo(graph))) // => [0, 1, 2] * * // With initial nodes - * const topo2 = Graph.topo(graph, { initials: [0] }) + * Graph.topo(graph, { initials: [0] }) * * // Check before sorting a cyclic graph * const cyclicGraph = Graph.directed((mutable) => { @@ -4469,9 +5398,7 @@ export interface TopoConfig { * Graph.addEdge(mutable, b, a, 2) // Creates cycle * }) * - * if (!Graph.isAcyclic(cyclicGraph)) { - * console.log("cyclic graph") // cyclic graph - * } + * Graph.isAcyclic(cyclicGraph) // => false * ``` * * @category iterators @@ -4480,8 +5407,11 @@ export interface TopoConfig { export const topo: { ( config?: TopoConfig - ): (graph: Graph | MutableGraph) => NodeWalker - (graph: Graph | MutableGraph, config?: TopoConfig): NodeWalker + ): (graph: Graph | MutableGraph) => NodeWalker + ( + graph: Graph | MutableGraph, + config?: TopoConfig + ): NodeWalker } = dual((args) => isGraph(args[0]), ( graph: Graph | MutableGraph, config: TopoConfig = {} @@ -4506,19 +5436,20 @@ export const topo: { return new Walker((f) => ({ [Symbol.iterator]: () => { + const impl = graphImpl(graph) const inDegree = new Map() const remaining = new Set() const initialSet = new Set(initials) const queue = [...initials] // Initialize in-degree counts - for (const [nodeIndex] of graph.nodes) { + for (const [nodeIndex] of impl.nodes) { inDegree.set(nodeIndex, 0) remaining.add(nodeIndex) } // Calculate in-degrees - for (const [, edgeData] of graph.edges) { + for (const [, edgeData] of impl.edges) { const currentInDegree = inDegree.get(edgeData.target) || 0 inDegree.set(edgeData.target, currentInDegree + 1) } @@ -4566,10 +5497,14 @@ export const topo: { if (Option.isSome(nodeData)) { return { done: false, value: f(current, nodeData.value) } } - return nextMapped() + continue } } + if (remaining.size > 0) { + throw new GraphError({ message: "Cannot perform topological sort on cyclic graph" }) + } + return { done: true, value: undefined } as const } @@ -4586,11 +5521,18 @@ export const topo: { * * Nodes are emitted after their reachable descendants have been processed. If * no start nodes are supplied, the iterator is empty. The `direction` option - * chooses whether to follow outgoing or incoming edges. + * chooses whether to follow outgoing or incoming edges. The `radius` option + * limits traversal by edge distance from the start nodes. + * + * **Gotchas** + * + * With a finite `radius`, iteration first performs a bounded breadth-first + * traversal to determine shortest-distance membership before emitting nodes in + * postorder. * * **Example** (Traversing in postorder) * - * ```ts + * ```ts import.meta.vitest * import { Graph } from "effect" * * const graph = Graph.directed((mutable) => { @@ -4602,10 +5544,7 @@ export const topo: { * }) * * // Postorder: children before parents - * const postOrder = Graph.dfsPostOrder(graph, { start: [0] }) - * for (const node of postOrder) { - * console.log(node) // 1, 2, 0 - * } + * Array.from(Graph.indices(Graph.dfsPostOrder(graph, { start: [0] }))) // => [1, 2, 0] * ``` * * @category iterators @@ -4625,6 +5564,7 @@ export const dfsPostOrder: { ): NodeWalker => { const start = config.start ?? [] const direction = config.direction ?? "outgoing" + const radius = config.radius ?? Infinity // Validate that all start nodes exist for (const nodeIndex of start) { @@ -4635,6 +5575,9 @@ export const dfsPostOrder: { return new Walker((f) => ({ [Symbol.iterator]: () => { + const reached = radius === Infinity + ? undefined + : new Set(indices(bfs(graph, { start, direction, radius }))) const stack: Array<{ node: NodeIndex; visitedChildren: boolean }> = [] const discovered = new Set() const finished = new Set() @@ -4659,7 +5602,11 @@ export const dfsPostOrder: { for (let i = neighbors.length - 1; i >= 0; i--) { const neighbor = neighbors[i] - if (!discovered.has(neighbor) && !finished.has(neighbor)) { + if ( + (reached === undefined || reached.has(neighbor)) && + !discovered.has(neighbor) && + !finished.has(neighbor) + ) { stack.push({ node: neighbor, visitedChildren: false }) } } @@ -4673,7 +5620,7 @@ export const dfsPostOrder: { if (Option.isSome(nodeData)) { return { done: false, value: f(nodeToEmit, nodeData.value) } } - return nextMapped() + continue } } } @@ -4696,7 +5643,7 @@ export const dfsPostOrder: { * * **Example** (Iterating all nodes) * - * ```ts + * ```ts import.meta.vitest * import { Graph } from "effect" * * const graph = Graph.directed((mutable) => { @@ -4706,8 +5653,7 @@ export const dfsPostOrder: { * Graph.addEdge(mutable, a, b, 1) * }) * - * const indices = Array.from(Graph.indices(Graph.nodes(graph))) - * console.log(indices) // [0, 1, 2] + * Array.from(Graph.indices(Graph.nodes(graph))) // => [0, 1, 2] * ``` * * @category iterators @@ -4718,7 +5664,7 @@ export const nodes = ( ): NodeWalker => new Walker((f) => ({ [Symbol.iterator]() { - const nodeMap = graph.nodes + const nodeMap = graphImpl(graph).nodes const iterator = nodeMap.entries() return { @@ -4744,7 +5690,7 @@ export const nodes = ( * * **Example** (Iterating all edges) * - * ```ts + * ```ts import.meta.vitest * import { Graph } from "effect" * * const graph = Graph.directed((mutable) => { @@ -4755,8 +5701,7 @@ export const nodes = ( * Graph.addEdge(mutable, b, c, 2) * }) * - * const indices = Array.from(Graph.indices(Graph.edges(graph))) - * console.log(indices) // [0, 1] + * Array.from(Graph.indices(Graph.edges(graph))) // => [0, 1] * ``` * * @category iterators @@ -4767,7 +5712,7 @@ export const edges = ( ): EdgeWalker => new Walker((f) => ({ [Symbol.iterator]() { - const edgeMap = graph.edges + const edgeMap = graphImpl(graph).edges const iterator = edgeMap.entries() return { @@ -4818,7 +5763,7 @@ export interface ExternalsConfig { * * **Example** (Iterating external nodes) * - * ```ts + * ```ts import.meta.vitest * import { Graph } from "effect" * * const graph = Graph.directed((mutable) => { @@ -4832,16 +5777,10 @@ export interface ExternalsConfig { * }) * * // Nodes with no outgoing edges (sinks + isolated) - * const sinks = Array.from( - * Graph.indices(Graph.externals(graph, { direction: "outgoing" })) - * ) - * console.log(sinks) // [2, 3] + * Array.from(Graph.indices(Graph.externals(graph, { direction: "outgoing" }))) // => [2, 3] * * // Nodes with no incoming edges (sources + isolated) - * const sources = Array.from( - * Graph.indices(Graph.externals(graph, { direction: "incoming" })) - * ) - * console.log(sources) // [0, 3] + * Array.from(Graph.indices(Graph.externals(graph, { direction: "incoming" }))) // => [0, 3] * ``` * * @category iterators @@ -4863,10 +5802,11 @@ export const externals: { return new Walker((f) => ({ [Symbol.iterator]: () => { - const nodeMap = graph.nodes + const impl = graphImpl(graph) + const nodeMap = impl.nodes const adjacencyMap = direction === "incoming" - ? graph.reverseAdjacency - : graph.adjacency + ? impl.reverseAdjacency + : impl.adjacency const nodeIterator = nodeMap.entries() diff --git a/repos/effect/packages/effect/src/HKT.ts b/repos/effect/packages/effect/src/HKT.ts index 4cbbfead47..ae7a5608d8 100644 --- a/repos/effect/packages/effect/src/HKT.ts +++ b/repos/effect/packages/effect/src/HKT.ts @@ -26,7 +26,7 @@ import type * as Types from "./Types.ts" * * **Example** (Linking a type class to a type lambda) * - * ```ts + * ```ts import.meta.vitest * import type { HKT } from "effect" * * interface IdentityTypeLambda extends HKT.TypeLambda { @@ -45,7 +45,6 @@ import type * as Types from "./Types.ts" * type LinkedTypeLambda = typeof identity[typeof HKT.URI] * * const value: HKT.Kind, never, never, never, string> = identity.of("ok") - * console.log(value) // "ok" * ``` * * @category symbols @@ -68,7 +67,7 @@ export declare const URI: unique symbol * * **Example** (Defining higher-kinded type classes) * - * ```ts + * ```ts import.meta.vitest * import type { HKT } from "effect" * * // Define a Functor type class @@ -86,6 +85,8 @@ export declare const URI: unique symbol * f: (a: A) => HKT.Kind * ): HKT.Kind * } + * + * const witness: keyof Monad = "flatMap" * ``` * * @category models @@ -112,7 +113,7 @@ export interface TypeClass { * * **Example** (Defining type lambdas) * - * ```ts + * ```ts import.meta.vitest * import type { Effect, HKT } from "effect" * * // TypeLambda for Array @@ -129,6 +130,8 @@ export interface TypeClass { * interface FunctionTypeLambda extends HKT.TypeLambda { * readonly type: (a: this["In"]) => this["Target"] * } + * + * const witness: HKT.Kind = ["ok"] * ``` * * @category models @@ -158,8 +161,9 @@ export interface TypeLambda { * * **Example** (Applying type lambdas) * - * ```ts - * import type { Effect, HKT, Option } from "effect" + * ```ts import.meta.vitest + * import { Option } from "effect" + * import type { Effect, HKT } from "effect" * * // Define TypeLambdas * interface OptionTypeLambda extends HKT.TypeLambda { @@ -191,6 +195,8 @@ export interface TypeLambda { * never, * string * > + * + * const witness: OptionString = Option.some("ok") * ``` * * @category utility types diff --git a/repos/effect/packages/effect/src/Hash.ts b/repos/effect/packages/effect/src/Hash.ts index 2d925c3376..0a12cca93d 100644 --- a/repos/effect/packages/effect/src/Hash.ts +++ b/repos/effect/packages/effect/src/Hash.ts @@ -44,7 +44,7 @@ export const symbol = "~effect/interfaces/Hash" * * **Example** (Implementing Hash) * - * ```ts + * ```ts import.meta.vitest * import { Hash } from "effect" * * class MyClass implements Hash.Hash { @@ -55,8 +55,7 @@ export const symbol = "~effect/interfaces/Hash" * } * } * - * const instance = new MyClass(42) - * console.log(instance[Hash.symbol]()) // hash value of 42 + * new MyClass(42)[Hash.symbol]() // => 42 * ``` * * @category models @@ -90,18 +89,12 @@ export interface Hash { * * **Example** (Hashing different values) * - * ```ts + * ```ts import.meta.vitest * import { Hash } from "effect" * - * // Hash primitive values - * console.log(Hash.hash(42)) // numeric hash - * console.log(Hash.hash("hello")) // string hash - * console.log(Hash.hash(true)) // boolean hash - * - * // Hash objects and arrays - * console.log(Hash.hash({ name: "John", age: 30 })) - * console.log(Hash.hash([1, 2, 3])) - * console.log(Hash.hash({ id: "user-1", roles: ["admin", "editor"] })) + * Hash.hash(42) === Hash.hash(42) // => true + * Hash.hash("hello") === Hash.hash("hello") // => true + * Hash.hash([1, 2, 3]) === Hash.hash([1, 2, 3]) // => true * ``` * * @category hashing @@ -126,6 +119,9 @@ export const hash: (self: A) => number = (self: A) => { if (self === null) { return string("null") } else if (self instanceof Date) { + if (Number.isNaN(self.getTime())) { + return string("Invalid Date") + } return string(self.toISOString()) } else if (self instanceof RegExp) { return string(self.toString()) @@ -141,6 +137,8 @@ export const hash: (self: A) => number = (self: A) => { return self[symbol]() } else if (typeof self === "function") { return random(self) + } else if (self instanceof DataView) { + return array(new Uint8Array(self.buffer, self.byteOffset, self.byteLength)) } else if (Array.isArray(self) || ArrayBuffer.isView(self)) { return array(self as any) } else if (self instanceof Map) { @@ -176,17 +174,15 @@ export const hash: (self: A) => number = (self: A) => { * * **Example** (Hashing objects by reference) * - * ```ts + * ```ts import.meta.vitest * import { Hash } from "effect" * * const obj1 = { a: 1 } * const obj2 = { a: 1 } * - * // Same object always returns the same hash - * console.log(Hash.random(obj1) === Hash.random(obj1)) // true + * Hash.random(obj1) === Hash.random(obj1) // => true * - * // Different objects get different hashes - * console.log(Hash.random(obj1) === Hash.random(obj2)) // false + * typeof Hash.random(obj2) // => "number" * ``` * * @category hashing @@ -214,18 +210,14 @@ export const random: (self: A) => number = (self) => { * * **Example** (Combining hash values) * - * ```ts + * ```ts import.meta.vitest * import { Hash, pipe } from "effect" * - * // Can also be used with pipe - * * const hash1 = Hash.hash("hello") * const hash2 = Hash.hash("world") * - * // Combine two hash values * const combined = Hash.combine(hash2)(hash1) - * console.log(combined) - * const result = pipe(hash1, Hash.combine(hash2)) + * combined === pipe(hash1, Hash.combine(hash2)) // => true * ``` * * @see {@link hash} for computing hash values from arbitrary inputs @@ -253,15 +245,10 @@ export const combine: { * * **Example** (Optimizing a hash value) * - * ```ts + * ```ts import.meta.vitest * import { Hash } from "effect" * - * const rawHash = 1234567890 - * const optimizedHash = Hash.optimize(rawHash) - * console.log(optimizedHash) // optimized hash value - * - * // Often used internally by other hash functions - * const stringHash = Hash.optimize(Hash.string("hello")) + * Hash.optimize(1234567890) // => 160826066 * ``` * * @category hashing @@ -283,7 +270,7 @@ export const optimize = (n: number): number => (n & 0xbfffffff) | ((n >>> 1) & 0 * * **Example** (Checking for Hash support) * - * ```ts + * ```ts import.meta.vitest * import { Hash } from "effect" * * class MyHashable implements Hash.Hash { @@ -292,10 +279,9 @@ export const optimize = (n: number): number => (n & 0xbfffffff) | ((n >>> 1) & 0 * } * } * - * const obj = new MyHashable() - * console.log(Hash.isHash(obj)) // true - * console.log(Hash.isHash({})) // false - * console.log(Hash.isHash("string")) // false + * Hash.isHash(new MyHashable()) // => true + * Hash.isHash({}) // => false + * Hash.isHash("string") // => false * ``` * * @category guards @@ -318,16 +304,14 @@ export const isHash = (u: unknown): u is Hash => hasProperty(u, symbol) * * **Example** (Hashing numbers) * - * ```ts + * ```ts import.meta.vitest * import { Hash } from "effect" * - * console.log(Hash.number(42)) // hash of 42 - * console.log(Hash.number(3.14)) // hash of 3.14 - * console.log(Hash.number(NaN)) // hash of "NaN" - * console.log(Hash.number(Infinity)) // 0 (special case) - * - * // Same numbers produce the same hash - * console.log(Hash.number(100) === Hash.number(100)) // true + * Number.isInteger(Hash.number(42)) // => true + * Number.isInteger(Hash.number(3.14)) // => true + * Hash.number(NaN) === Hash.number(NaN) // => true + * Hash.number(Infinity) === Hash.number(Infinity) // => true + * Hash.number(100) === Hash.number(100) // => true * ``` * * @category hashing @@ -369,15 +353,13 @@ export const number = (n: number) => { * * **Example** (Hashing strings) * - * ```ts + * ```ts import.meta.vitest * import { Hash } from "effect" * - * console.log(Hash.string("hello")) // hash of "hello" - * console.log(Hash.string("world")) // hash of "world" - * console.log(Hash.string("")) // hash of empty string - * - * // Same strings produce the same hash - * console.log(Hash.string("test") === Hash.string("test")) // true + * Hash.string("hello") // => 181380007 + * Hash.string("world") // => 164394279 + * Hash.string("") // => 5381 + * Hash.string("test") === Hash.string("test") // => true * ``` * * @category hashing @@ -406,22 +388,20 @@ export const string = (str: string) => { * * **Example** (Hashing selected object keys) * - * ```ts + * ```ts import.meta.vitest * import { Hash } from "effect" * * const person = { name: "John", age: 30, city: "New York" } * - * // Hash only specific keys * const hash1 = Hash.structureKeys(person, ["name", "age"]) * const hash2 = Hash.structureKeys(person, ["name", "city"]) * - * console.log(hash1) // hash based on name and age - * console.log(hash2) // hash based on name and city + * hash1 // => -590673747 + * hash2 // => 284850673 * - * // Same keys produce the same hash * const person2 = { name: "John", age: 30, city: "Boston" } * const hash3 = Hash.structureKeys(person2, ["name", "age"]) - * console.log(hash1 === hash3) // true + * hash1 === hash3 // => true * ``` * * @category hashing @@ -449,19 +429,17 @@ export const structureKeys = (o: object, keys: Iterable) => { * * **Example** (Hashing object structures) * - * ```ts + * ```ts import.meta.vitest * import { Hash } from "effect" * * const obj1 = { name: "John", age: 30 } * const obj2 = { name: "Jane", age: 25 } * const obj3 = { name: "John", age: 30 } * - * console.log(Hash.structure(obj1)) // hash of obj1 - * console.log(Hash.structure(obj2)) // different hash - * console.log(Hash.structure(obj3)) // same as obj1 - * - * // Objects with same properties produce same hash - * console.log(Hash.structure(obj1) === Hash.structure(obj3)) // true + * Hash.structure(obj1) // => -590673747 + * Hash.structure(obj2) // => -590160631 + * Hash.structure(obj3) // => -590673747 + * Hash.structure(obj1) === Hash.structure(obj3) // => true * ``` * * @category hashing @@ -496,19 +474,18 @@ const iterableWith = (seed: number, f: (el: any) => number) => (iter: Iterable 6151 + * Hash.array(arr2) // => 6151 + * Hash.array(arr3) // => 6151 + * Hash.array(arr1) === Hash.array(arr2) // => true + * Hash.array(arr1) === Hash.array(arr3) // => true * ``` * * @see {@link hash} for the general-purpose hash dispatcher diff --git a/repos/effect/packages/effect/src/HashMap.ts b/repos/effect/packages/effect/src/HashMap.ts index 223d781015..7a9e808378 100644 --- a/repos/effect/packages/effect/src/HashMap.ts +++ b/repos/effect/packages/effect/src/HashMap.ts @@ -27,22 +27,21 @@ const TypeId = internal.HashMapTypeId * * **Example** (Using basic HashMap operations) * - * ```ts - * import { HashMap } from "effect" + * ```ts import.meta.vitest + * import { HashMap, Option } from "effect" * * // Create a HashMap * const map = HashMap.make(["a", 1], ["b", 2], ["c", 3]) * * // Access values - * const valueA = HashMap.get(map, "a") // Option.some(1) - * const valueD = HashMap.get(map, "d") // Option.none() + * HashMap.get(map, "a") // => Option.some(1) + * HashMap.get(map, "d") // => Option.none() * * // Check if key exists - * console.log(HashMap.has(map, "b")) // true + * HashMap.has(map, "b") // => true * * // Add/update values (returns new HashMap) - * const updated = HashMap.set(map, "d", 4) - * console.log(HashMap.size(updated)) // 4 + * HashMap.set(map, "d", 4) // => HashMap.make(["a", 1], ["b", 2], ["c", 3], ["d", 4]) * ``` * * @category models @@ -58,7 +57,7 @@ export interface HashMap extends Iterable<[Key, Value]>, Equ * * **Example** (Extracting HashMap types) * - * ```ts + * ```ts import.meta.vitest * import { HashMap } from "effect" * * // Create a concrete HashMap for type extraction @@ -82,6 +81,8 @@ export interface HashMap extends Iterable<[Key, Value]>, Equ * // Example of extracted types in action * const newProduct: Product = { quantity: 10, price: 199 } * const updatedInventory = updateInventory("tablet", newProduct) + * processEntry(["tablet", newProduct]) // => "tablet: 10 @ $199" + * updatedInventory // => HashMap.make(["laptop", { quantity: 5, price: 999 }], ["mouse", { quantity: 20, price: 29 }], ["tablet", newProduct]) * ``` * * @since 2.0.0 @@ -94,7 +95,7 @@ export declare namespace HashMap { * * **Example** (Updating values from Options) * - * ```ts + * ```ts import.meta.vitest * import { HashMap, Option } from "effect" * * const map = HashMap.make(["a", 1], ["b", 2]) @@ -104,7 +105,7 @@ export declare namespace HashMap { * Option.isSome(option) ? Option.some(option.value + 1) : Option.some(1) * * const updated = HashMap.modifyAt(map, "a", updateFn) - * console.log(HashMap.get(updated, "a")) // Option.some(2) + * HashMap.get(updated, "a") // => Option.some(2) * ``` * * @category models @@ -117,8 +118,8 @@ export declare namespace HashMap { * * **Example** (Extracting key types) * - * ```ts - * import { HashMap } from "effect" + * ```ts import.meta.vitest + * import { HashMap, Option } from "effect" * * // Create a HashMap to extract key type from * const userMap = HashMap.make( @@ -131,7 +132,7 @@ export declare namespace HashMap { * * // Use the extracted type in functions * const getUserById = (id: UserKey) => HashMap.get(userMap, id) - * console.log(getUserById("alice")) // Option.some({ name: "Alice", age: 30 }) + * getUserById("alice") // => Option.some({ name: "Alice", age: 30 }) * ``` * * @category utility types @@ -144,8 +145,8 @@ export declare namespace HashMap { * * **Example** (Extracting value types) * - * ```ts - * import { HashMap } from "effect" + * ```ts import.meta.vitest + * import { HashMap, Option } from "effect" * * // Create a HashMap with user data * const userMap = HashMap.make( @@ -161,8 +162,9 @@ export declare namespace HashMap { * return user.active ? `${user.name} (active)` : `${user.name} (inactive)` * } * - * const alice = HashMap.get(userMap, "alice") - * // alice has type Option thanks to type extraction + * // The lookup has type Option thanks to type extraction + * HashMap.get(userMap, "alice") // => Option.some({ name: "Alice", age: 30, active: true }) + * processUser({ name: "Alice", age: 30, active: true }) // => "Alice (active)" * ``` * * @category utility types @@ -175,7 +177,7 @@ export declare namespace HashMap { * * **Example** (Extracting entry types) * - * ```ts + * ```ts import.meta.vitest * import { HashMap } from "effect" * * // Create a product catalog HashMap @@ -194,7 +196,7 @@ export declare namespace HashMap { * * // Convert to entries, process, and sort for deterministic output * const descriptions = HashMap.toEntries(catalog).map(processEntry).sort() - * console.log(descriptions) // ["book: $29 (education)", "laptop: $999 (electronics)"] + * descriptions // => ["book: $29 (education)", "laptop: $999 (electronics)"] * ``` * * @category utility types @@ -208,18 +210,18 @@ export declare namespace HashMap { * * **Example** (Checking HashMap values) * - * ```ts + * ```ts import.meta.vitest * import { HashMap } from "effect" * * const map = HashMap.make(["a", 1], ["b", 2]) * const notMap = { a: 1 } * - * console.log(HashMap.isHashMap(map)) // true - * console.log(HashMap.isHashMap(notMap)) // false - * console.log(HashMap.isHashMap(null)) // false + * HashMap.isHashMap(map) // => true + * HashMap.isHashMap(notMap) // => false + * HashMap.isHashMap(null) // => false * ``` * - * @category refinements + * @category guards * @since 2.0.0 */ export const isHashMap: { @@ -232,12 +234,10 @@ export const isHashMap: { * * **Example** (Creating an empty HashMap) * - * ```ts + * ```ts import.meta.vitest * import { HashMap } from "effect" * - * const map = HashMap.empty() - * console.log(HashMap.isEmpty(map)) // true - * console.log(HashMap.size(map)) // 0 + * HashMap.empty() // => HashMap.empty() * ``` * * @category constructors @@ -250,12 +250,10 @@ export const empty: () => HashMap = internal.empty * * **Example** (Creating a HashMap from entries) * - * ```ts + * ```ts import.meta.vitest * import { HashMap } from "effect" * - * const map = HashMap.make(["a", 1], ["b", 2], ["c", 3]) - * console.log(HashMap.size(map)) // 3 - * console.log(HashMap.get(map, "b")) // Option.some(2) + * HashMap.make(["a", 1], ["b", 2], ["c", 3]) // => HashMap.make(["a", 1], ["b", 2], ["c", 3]) * ``` * * @category constructors @@ -273,13 +271,11 @@ export const make: >( * * **Example** (Creating a HashMap from an iterable) * - * ```ts + * ```ts import.meta.vitest * import { HashMap } from "effect" * * const entries = [["a", 1], ["b", 2], ["c", 3]] as const - * const map = HashMap.fromIterable(entries) - * console.log(HashMap.size(map)) // 3 - * console.log(HashMap.get(map, "a")) // Option.some(1) + * HashMap.fromIterable(entries) // => HashMap.make(["a", 1], ["b", 2], ["c", 3]) * ``` * * @category constructors @@ -292,17 +288,17 @@ export const fromIterable: (entries: Iterable) => HashMap * * **Example** (Checking for empty HashMaps) * - * ```ts + * ```ts import.meta.vitest * import { HashMap } from "effect" * * const emptyMap = HashMap.empty() * const nonEmptyMap = HashMap.make(["a", 1]) * - * console.log(HashMap.isEmpty(emptyMap)) // true - * console.log(HashMap.isEmpty(nonEmptyMap)) // false + * HashMap.isEmpty(emptyMap) // => true + * HashMap.isEmpty(nonEmptyMap) // => false * ``` * - * @category elements + * @category predicates * @since 2.0.0 */ export const isEmpty: (self: HashMap) => boolean = internal.isEmpty @@ -313,20 +309,19 @@ export const isEmpty: (self: HashMap) => boolean = internal.isEmpty * * **Example** (Looking up values) * - * ```ts - * import { HashMap } from "effect" + * ```ts import.meta.vitest + * import { HashMap, Option } from "effect" * * const map = HashMap.make(["a", 1], ["b", 2]) * - * console.log(HashMap.get(map, "a")) // Option.some(1) - * console.log(HashMap.get(map, "c")) // Option.none() + * HashMap.get(map, "a") // => Option.some(1) + * HashMap.get(map, "c") // => Option.none() * * // Using pipe syntax - * const value = HashMap.get("b")(map) - * console.log(value) // Option.some(2) + * HashMap.get("b")(map) // => Option.some(2) * ``` * - * @category elements + * @category getters * @since 2.0.0 */ export const get: { @@ -339,8 +334,8 @@ export const get: { * * **Example** (Looking up values with a hash) * - * ```ts - * import { Hash, HashMap } from "effect" + * ```ts import.meta.vitest + * import { Hash, HashMap, Option } from "effect" * * // Useful when implementing custom equality for complex keys * const userMap = HashMap.make( @@ -353,15 +348,13 @@ export const get: { * const precomputedHash = Hash.string(userId) * * // Lookup with custom hash (e.g., cached hash value) - * const user = HashMap.getHash(userMap, userId, precomputedHash) - * console.log(user) // Option.some({ name: "Alice", role: "admin" }) + * HashMap.getHash(userMap, userId, precomputedHash) // => Option.some({ name: "Alice", role: "admin" }) * * // This avoids recomputing the hash when you already have it - * const notFound = HashMap.getHash(userMap, "user999", Hash.string("user999")) - * console.log(notFound) // Option.none() + * HashMap.getHash(userMap, "user999", Hash.string("user999")) // => Option.none() * ``` * - * @category elements + * @category getters * @since 2.0.0 */ export const getHash: { @@ -385,7 +378,7 @@ export const getHash: { * * **Example** (Unsafely looking up values) * - * ```ts + * ```ts import.meta.vitest * import { HashMap, Option } from "effect" * * const config = HashMap.make( @@ -395,14 +388,10 @@ export const getHash: { * ) * * // Safe: use when you're certain the key exists - * const apiUrl = HashMap.getUnsafe(config, "api_url") // "https://api.example.com" - * console.log(`Connecting to: ${apiUrl}`) + * HashMap.getUnsafe(config, "api_url") // => "https://api.example.com" * * // Preferred: use get() for uncertain keys - * const dbUrl = HashMap.get(config, "db_url") // Option.none() - * if (Option.isSome(dbUrl)) { - * console.log(`Database: ${dbUrl.value}`) - * } + * HashMap.get(config, "db_url") // => Option.none() * * // This would throw: HashMap.getUnsafe(config, "db_url") * // Error: "HashMap.getUnsafe: key not found" @@ -421,20 +410,19 @@ export const getUnsafe: { * * **Example** (Checking for keys) * - * ```ts + * ```ts import.meta.vitest * import { HashMap } from "effect" * * const map = HashMap.make(["a", 1], ["b", 2]) * - * console.log(HashMap.has(map, "a")) // true - * console.log(HashMap.has(map, "c")) // false + * HashMap.has(map, "a") // => true + * HashMap.has(map, "c") // => false * * // Using pipe syntax - * const hasB = HashMap.has("b")(map) - * console.log(hasB) // true + * HashMap.has("b")(map) // => true * ``` * - * @category elements + * @category predicates * @since 2.0.0 */ export const has: { @@ -448,7 +436,7 @@ export const has: { * * **Example** (Checking keys with a hash) * - * ```ts + * ```ts import.meta.vitest * import { Hash, HashMap } from "effect" * * // Create a map with case-sensitive keys @@ -459,17 +447,17 @@ export const has: { * * // Check with exact hash * const exactHash = Hash.string("Admin") - * console.log(HashMap.hasHash(userMap, "Admin", exactHash)) // true + * HashMap.hasHash(userMap, "Admin", exactHash) // => true * * // A matching hash does not override key equality - * console.log(HashMap.hasHash(userMap, "admin", exactHash)) // false + * HashMap.hasHash(userMap, "admin", exactHash) // => false * * // A different hash also cannot find the existing key * const lowercaseHash = Hash.string("admin") - * console.log(HashMap.hasHash(userMap, "Admin", lowercaseHash)) // false + * HashMap.hasHash(userMap, "Admin", lowercaseHash) // => false * ``` * - * @category elements + * @category predicates * @since 2.0.0 */ export const hasHash: { @@ -482,15 +470,15 @@ export const hasHash: { * * **Example** (Checking entries by predicate) * - * ```ts + * ```ts import.meta.vitest * import { HashMap } from "effect" * * const hm = HashMap.make([1, "a"]) - * HashMap.hasBy(hm, (value, key) => value === "a" && key === 1) // -> true - * HashMap.hasBy(hm, (value) => value === "b") // -> false + * HashMap.hasBy(hm, (value, key) => value === "a" && key === 1) // => true + * HashMap.hasBy(hm, (value) => value === "b") // => false * ``` * - * @category elements + * @category predicates * @since 3.16.0 */ export const hasBy: { @@ -504,17 +492,14 @@ export const hasBy: { * * **Example** (Setting a value) * - * ```ts + * ```ts import.meta.vitest * import { HashMap } from "effect" * * const map1 = HashMap.make(["a", 1]) - * const map2 = HashMap.set(map1, "b", 2) - * - * console.log(HashMap.size(map2)) // 2 - * console.log(HashMap.get(map2, "b")) // Option.some(2) + * HashMap.set(map1, "b", 2) // => HashMap.make(["a", 1], ["b", 2]) * * // Original map is unchanged - * console.log(HashMap.size(map1)) // 1 + * map1 // => HashMap.make(["a", 1]) * ``` * * @category transforming @@ -530,12 +515,11 @@ export const set: { * * **Example** (Iterating keys) * - * ```ts + * ```ts import.meta.vitest * import { HashMap } from "effect" * * const map = HashMap.make(["a", 1], ["b", 2], ["c", 3]) - * const keys = Array.from(HashMap.keys(map)) - * console.log(keys.sort()) // ["a", "b", "c"] + * Array.from(HashMap.keys(map)).sort() // => ["a", "b", "c"] * ``` * * @category getters @@ -548,12 +532,11 @@ export const keys: (self: HashMap) => IterableIterator = internal * * **Example** (Iterating values) * - * ```ts + * ```ts import.meta.vitest * import { HashMap } from "effect" * * const map = HashMap.make(["a", 1], ["b", 2], ["c", 3]) - * const values = Array.from(HashMap.values(map)) - * console.log(values.sort()) // [1, 2, 3] + * Array.from(HashMap.values(map)).sort() // => [1, 2, 3] * ``` * * @category getters @@ -566,7 +549,7 @@ export const values: (self: HashMap) => IterableIterator = intern * * **Example** (Converting values to an array) * - * ```ts + * ```ts import.meta.vitest * import { HashMap } from "effect" * * const employees = HashMap.make( @@ -577,15 +560,13 @@ export const values: (self: HashMap) => IterableIterator = intern * * // Extract all employee records * const allEmployees = HashMap.toValues(employees) - * console.log(allEmployees.length) // 3 + * allEmployees.length // => 3 * * // Calculate total salary - * const totalSalary = allEmployees.reduce((sum, emp) => sum + emp.salary, 0) - * console.log(totalSalary) // 260000 + * allEmployees.reduce((sum, emp) => sum + emp.salary, 0) // => 260000 * * // Filter by department - * const engineers = allEmployees.filter((emp) => emp.department === "engineering") - * console.log(engineers.length) // 2 + * allEmployees.filter((emp) => emp.department === "engineering").length // => 2 * ``` * * @category getters @@ -598,7 +579,7 @@ export const toValues = (self: HashMap): Array => Array.from(valu * * **Example** (Iterating entries) * - * ```ts + * ```ts import.meta.vitest * import { HashMap } from "effect" * * // Create a configuration map @@ -613,12 +594,10 @@ export const toValues = (self: HashMap): Array => Array.from(valu * .sort(([left], [right]) => left.localeCompare(right)) * .map(([key, value]) => `Setting ${key} = ${value}`) * - * console.log(settings) - * // ["Setting cache.enabled = true", "Setting database.host = localhost", "Setting database.port = 5432"] + * settings // => ["Setting cache.enabled = true", "Setting database.host = localhost", "Setting database.port = 5432"] * * // Convert to array when you need all entries at once - * const allEntries = Array.from(HashMap.entries(config)) - * console.log(allEntries.length) // 3 + * Array.from(HashMap.entries(config)).length // => 3 * ``` * * @category getters @@ -631,7 +610,7 @@ export const entries: (self: HashMap) => IterableIterator<[K, V]> = * * **Example** (Converting entries to an array) * - * ```ts + * ```ts import.meta.vitest * import { HashMap } from "effect" * * const gameScores = HashMap.make( @@ -648,11 +627,10 @@ export const entries: (self: HashMap) => IterableIterator<[K, V]> = * .sort(([, a], [, b]) => b - a) * .map(([player, score], rank) => `${rank + 1}. ${player}: ${score}`) * - * console.log(leaderboard) - * // ["1. alice: 1250", "2. charlie: 1100", "3. bob: 980"] + * leaderboard // => ["1. alice: 1250", "2. charlie: 1100", "3. bob: 980"] * * // Convert back to HashMap if needed - * const sortedMap = HashMap.fromIterable(scoreEntries) + * HashMap.fromIterable(scoreEntries) // => HashMap.make(["alice", 1250], ["charlie", 1100], ["bob", 980]) * ``` * * @category getters @@ -665,14 +643,14 @@ export const toEntries = (self: HashMap): Array<[K, V]> => Array.fro * * **Example** (Getting the size) * - * ```ts + * ```ts import.meta.vitest * import { HashMap } from "effect" * * const emptyMap = HashMap.empty() * const map = HashMap.make(["a", 1], ["b", 2], ["c", 3]) * - * console.log(HashMap.size(emptyMap)) // 0 - * console.log(HashMap.size(map)) // 3 + * HashMap.size(emptyMap) // => 0 + * HashMap.size(map) // => 3 * ``` * * @category getters @@ -690,7 +668,7 @@ export const size: (self: HashMap) => number = internal.size * * **Example** (Beginning batch mutation) * - * ```ts + * ```ts import.meta.vitest * import { HashMap } from "effect" * * const map = HashMap.make(["a", 1]) @@ -704,8 +682,7 @@ export const size: (self: HashMap) => number = internal.size * HashMap.remove(mutable, "a") * * // End mutation to get final immutable result - * const result = HashMap.endMutation(mutable) - * console.log(HashMap.size(result)) // 2 + * HashMap.endMutation(mutable) // => HashMap.make(["b", 2], ["c", 3]) * ``` * * @category mutations @@ -718,7 +695,7 @@ export const beginMutation: (self: HashMap) => HashMap = inter * * **Example** (Ending batch mutation) * - * ```ts + * ```ts import.meta.vitest * import { HashMap } from "effect" * * // Start with an existing map @@ -733,11 +710,7 @@ export const beginMutation: (self: HashMap) => HashMap = inter * HashMap.set(mutable, "w", 40) * * // End mutation to get final immutable result - * const final = HashMap.endMutation(mutable) - * - * console.log(HashMap.size(final)) // 3 - * console.log(HashMap.has(final, "x")) // false - * console.log(HashMap.get(final, "z")) // Option.some(30) + * HashMap.endMutation(mutable) // => HashMap.make(["y", 20], ["z", 30], ["w", 40]) * ``` * * @category mutations @@ -756,7 +729,7 @@ export const endMutation: (self: HashMap) => HashMap = interna * * **Example** (Applying batched mutations) * - * ```ts + * ```ts import.meta.vitest * import { HashMap } from "effect" * * const map1 = HashMap.make(["a", 1]) @@ -764,7 +737,7 @@ export const endMutation: (self: HashMap) => HashMap = interna * HashMap.set(mutable, "b", 2) * HashMap.set(mutable, "c", 3) * }) - * // Returns a new HashMap with mutations applied + * map2 // => HashMap.make(["a", 1], ["b", 2], ["c", 3]) * ``` * * @category mutations @@ -786,7 +759,7 @@ export const mutate: { * * **Example** (Updating values with Options) * - * ```ts + * ```ts import.meta.vitest * import { HashMap, Option } from "effect" * * const map = HashMap.make(["a", 1], ["b", 2]) @@ -796,7 +769,7 @@ export const mutate: { * Option.isSome(option) ? Option.some(option.value + 1) : Option.some(1) * * const updated = HashMap.modifyAt(map, "a", updateFn) - * console.log(HashMap.get(updated, "a")) // Option.some(2) + * HashMap.get(updated, "a") // => Option.some(2) * ``` * * @category transforming @@ -819,7 +792,7 @@ export const modifyAt: { * * **Example** (Updating values with a hash) * - * ```ts + * ```ts import.meta.vitest * import { Hash, HashMap, Option } from "effect" * * // Useful when working with precomputed hashes for performance @@ -840,7 +813,7 @@ export const modifyAt: { * cachedHash, * incrementCounter * ) - * console.log(HashMap.get(updated, "downloads")) // Option.some(101) + * HashMap.get(updated, "downloads") // => Option.some(101) * * // Add new metric with precomputed hash * const newMetric = "clicks" @@ -851,7 +824,7 @@ export const modifyAt: { * clicksHash, * incrementCounter * ) - * console.log(HashMap.get(withClicks, "clicks")) // Option.some(1) + * HashMap.get(withClicks, "clicks") // => Option.some(1) * ``` * * @category transforming @@ -867,14 +840,14 @@ export const modifyHash: { * * **Example** (Modifying existing values) * - * ```ts - * import { HashMap } from "effect" + * ```ts import.meta.vitest + * import { HashMap, Option } from "effect" * * const map1 = HashMap.make(["a", 1], ["b", 2]) * const map2 = HashMap.modify(map1, "a", (value) => value * 3) * - * console.log(HashMap.get(map2, "a")) // Option.some(3) - * console.log(HashMap.get(map2, "b")) // Option.some(2) + * HashMap.get(map2, "a") // => Option.some(3) + * HashMap.get(map2, "b") // => Option.some(2) * ``` * * @category transforming @@ -895,15 +868,15 @@ export const modify: { * * **Example** (Combining HashMaps) * - * ```ts - * import { HashMap } from "effect" + * ```ts import.meta.vitest + * import { HashMap, Option } from "effect" * * const map1 = HashMap.make(["a", 1], ["b", 2]) * const map2 = HashMap.make(["b", 20], ["c", 3]) * const union = HashMap.union(map1, map2) * - * console.log(HashMap.size(union)) // 3 - * console.log(HashMap.get(union, "b")) // Option.some(20) - map2 wins + * union // => HashMap.make(["a", 1], ["b", 20], ["c", 3]) + * HashMap.get(union, "b") // => Option.some(20) * ``` * * @category combining @@ -920,15 +893,13 @@ export const union: { * * **Example** (Removing a key) * - * ```ts + * ```ts import.meta.vitest * import { HashMap } from "effect" * * const map1 = HashMap.make(["a", 1], ["b", 2], ["c", 3]) * const map2 = HashMap.remove(map1, "b") * - * console.log(HashMap.size(map2)) // 2 - * console.log(HashMap.has(map2, "b")) // false - * console.log(HashMap.has(map2, "a")) // true + * map2 // => HashMap.make(["a", 1], ["c", 3]) * ``` * * @category transforming @@ -944,15 +915,13 @@ export const remove: { * * **Example** (Removing multiple keys) * - * ```ts + * ```ts import.meta.vitest * import { HashMap } from "effect" * * const map1 = HashMap.make(["a", 1], ["b", 2], ["c", 3], ["d", 4]) * const map2 = HashMap.removeMany(map1, ["b", "d"]) * - * console.log(HashMap.size(map2)) // 2 - * console.log(HashMap.has(map2, "a")) // true - * console.log(HashMap.has(map2, "c")) // true + * map2 // => HashMap.make(["a", 1], ["c", 3]) * ``` * * @category transforming @@ -968,16 +937,15 @@ export const removeMany: { * * **Example** (Setting multiple entries) * - * ```ts - * import { HashMap } from "effect" + * ```ts import.meta.vitest + * import { HashMap, Option } from "effect" * * const map1 = HashMap.make(["a", 1], ["b", 2]) * const newEntries = [["c", 3], ["d", 4], ["a", 10]] as const // "a" will be overwritten * const map2 = HashMap.setMany(map1, newEntries) * - * console.log(HashMap.size(map2)) // 4 - * console.log(HashMap.get(map2, "a")) // Option.some(10) - * console.log(HashMap.get(map2, "c")) // Option.some(3) + * map2 // => HashMap.make(["a", 10], ["b", 2], ["c", 3], ["d", 4]) + * HashMap.get(map2, "a") // => Option.some(10) * ``` * * @category transforming @@ -993,14 +961,14 @@ export const setMany: { * * **Example** (Mapping values) * - * ```ts - * import { HashMap } from "effect" + * ```ts import.meta.vitest + * import { HashMap, Option } from "effect" * * const map1 = HashMap.make(["a", 1], ["b", 2], ["c", 3]) * const map2 = HashMap.map(map1, (value, key) => `${key}:${value * 2}`) * - * console.log(HashMap.get(map2, "a")) // Option.some("a:2") - * console.log(HashMap.get(map2, "b")) // Option.some("b:4") + * HashMap.get(map2, "a") // => Option.some("a:2") + * HashMap.get(map2, "b") // => Option.some("b:4") * ``` * * @category mapping @@ -1020,8 +988,8 @@ export const map: { * * **Example** (Flat mapping values) * - * ```ts - * import { HashMap } from "effect" + * ```ts import.meta.vitest + * import { HashMap, Option } from "effect" * * const map1 = HashMap.make(["a", 1], ["b", 2]) * const map2 = HashMap.flatMap( @@ -1029,9 +997,8 @@ export const map: { * (value, key) => HashMap.make([key + "1", value], [key + "2", value * 2]) * ) * - * console.log(HashMap.size(map2)) // 4 - * console.log(HashMap.get(map2, "a1")) // Option.some(1) - * console.log(HashMap.get(map2, "b2")) // Option.some(4) + * map2 // => HashMap.make(["a1", 1], ["a2", 2], ["b1", 2], ["b2", 4]) + * HashMap.get(map2, "b2") // => Option.some(4) * ``` * * @category sequencing @@ -1047,7 +1014,7 @@ export const flatMap: { * * **Example** (Iterating with side effects) * - * ```ts + * ```ts import.meta.vitest * import { HashMap } from "effect" * * const map = HashMap.make(["a", 1], ["b", 2]) @@ -1057,7 +1024,7 @@ export const flatMap: { * collected.push([key, value]) * }) * - * console.log(collected.sort()) // [["a", 1], ["b", 2]] + * collected.sort() // => [["a", 1], ["b", 2]] * ``` * * @category traversing @@ -1073,13 +1040,11 @@ export const forEach: { * * **Example** (Reducing values) * - * ```ts + * ```ts import.meta.vitest * import { HashMap } from "effect" * * const map = HashMap.make(["a", 1], ["b", 2], ["c", 3]) - * const sum = HashMap.reduce(map, 0, (acc, value) => acc + value) - * - * console.log(sum) // 6 + * HashMap.reduce(map, 0, (acc, value) => acc + value) // => 6 * ``` * * @category folding @@ -1095,16 +1060,13 @@ export const reduce: { * * **Example** (Filtering entries) * - * ```ts + * ```ts import.meta.vitest * import { HashMap } from "effect" * * const map1 = HashMap.make(["a", 1], ["b", 2], ["c", 3], ["d", 4]) * const map2 = HashMap.filter(map1, (value) => value % 2 === 0) * - * console.log(HashMap.size(map2)) // 2 - * console.log(HashMap.has(map2, "b")) // true - * console.log(HashMap.has(map2, "d")) // true - * console.log(HashMap.has(map2, "a")) // false + * map2 // => HashMap.make(["b", 2], ["d", 4]) * ``` * * @category filtering @@ -1120,7 +1082,7 @@ export const filter: { * * **Example** (Compacting Option values) * - * ```ts + * ```ts import.meta.vitest * import { HashMap, Option } from "effect" * * const map1 = HashMap.make( @@ -1130,9 +1092,8 @@ export const filter: { * ) * const map2 = HashMap.compact(map1) * - * console.log(HashMap.size(map2)) // 2 - * console.log(HashMap.get(map2, "a")) // Option.some(1) - * console.log(HashMap.has(map2, "b")) // false + * map2 // => HashMap.make(["a", 1], ["c", 3]) + * HashMap.get(map2, "a") // => Option.some(1) * ``` * * @category filtering @@ -1146,8 +1107,8 @@ export const compact: (self: HashMap>) => HashMap = int * * **Example** (Filtering and mapping Results) * - * ```ts - * import { HashMap, Result } from "effect" + * ```ts import.meta.vitest + * import { HashMap, Option, Result } from "effect" * * const map1 = HashMap.make(["a", 1], ["b", 2], ["c", 3], ["d", 4]) * const map2 = HashMap.filterMap( @@ -1155,9 +1116,8 @@ export const compact: (self: HashMap>) => HashMap = int * (value) => value % 2 === 0 ? Result.succeed(value * 2) : Result.failVoid * ) * - * console.log(HashMap.size(map2)) // 2 - * console.log(HashMap.get(map2, "b")) // Option.some(4) - * console.log(HashMap.get(map2, "d")) // Option.some(8) + * map2 // => HashMap.make(["b", 4], ["d", 8]) + * HashMap.get(map2, "b") // => Option.some(4) * ``` * * @category filtering @@ -1174,16 +1134,14 @@ export const filterMap: { * * **Example** (Finding the first matching entry) * - * ```ts + * ```ts import.meta.vitest * import { HashMap, Option } from "effect" * * const map = HashMap.make(["a", 1], ["b", 2], ["c", 3]) - * const result = HashMap.findFirst(map, (value, key) => key === "b" && value > 1) - * console.log(result) // Option.some(["b", 2]) - * console.log(Option.getOrElse(result, () => ["", 0])) // ["b", 2] + * HashMap.findFirst(map, (value, key) => key === "b" && value > 1) // => Option.some(["b", 2]) * ``` * - * @category elements + * @category searching * @since 2.0.0 */ export const findFirst: { @@ -1196,16 +1154,16 @@ export const findFirst: { * * **Example** (Checking for any matching entry) * - * ```ts + * ```ts import.meta.vitest * import { HashMap } from "effect" * * const map = HashMap.make(["a", 1], ["b", 2], ["c", 3]) * - * console.log(HashMap.some(map, (value) => value > 2)) // true - * console.log(HashMap.some(map, (value) => value > 5)) // false + * HashMap.some(map, (value) => value > 2) // => true + * HashMap.some(map, (value) => value > 5) // => false * ``` * - * @category elements + * @category predicates * @since 3.13.0 */ export const some: { @@ -1218,16 +1176,16 @@ export const some: { * * **Example** (Checking all entries) * - * ```ts + * ```ts import.meta.vitest * import { HashMap } from "effect" * * const map = HashMap.make(["a", 1], ["b", 2], ["c", 3]) * - * console.log(HashMap.every(map, (value) => value > 0)) // true - * console.log(HashMap.every(map, (value) => value > 1)) // false + * HashMap.every(map, (value) => value > 0) // => true + * HashMap.every(map, (value) => value > 1) // => false * ``` * - * @category elements + * @category predicates * @since 3.14.0 */ export const every: { diff --git a/repos/effect/packages/effect/src/HashRing.ts b/repos/effect/packages/effect/src/HashRing.ts index 0b9f9c8334..f85ac52fa4 100644 --- a/repos/effect/packages/effect/src/HashRing.ts +++ b/repos/effect/packages/effect/src/HashRing.ts @@ -145,6 +145,7 @@ export const addMany: { const key = PrimaryKey.value(node) const entry = self.nodes.get(key) if (entry) { + entry[0] = node if (entry[1] === weight) continue toRemove ??= new Set() toRemove.add(key) diff --git a/repos/effect/packages/effect/src/HashSet.ts b/repos/effect/packages/effect/src/HashSet.ts index fc6e6dd7b0..2f088d0620 100644 --- a/repos/effect/packages/effect/src/HashSet.ts +++ b/repos/effect/packages/effect/src/HashSet.ts @@ -26,23 +26,23 @@ const TypeId = internal.HashSetTypeId * * **Example** (Creating and updating a HashSet) * - * ```ts + * ```ts import.meta.vitest * import { HashSet } from "effect" * * // Create a HashSet * const set = HashSet.make("apple", "banana", "cherry") * * // Check membership - * console.log(HashSet.has(set, "apple")) // true - * console.log(HashSet.has(set, "grape")) // false + * HashSet.has(set, "apple") // => true + * HashSet.has(set, "grape") // => false * * // Add values (returns new HashSet) * const updated = HashSet.add(set, "grape") - * console.log(HashSet.size(updated)) // 4 + * updated // => HashSet.make("apple", "banana", "cherry", "grape") * * // Remove values (returns new HashSet) * const smaller = HashSet.remove(set, "banana") - * console.log(HashSet.size(smaller)) // 2 + * smaller // => HashSet.make("apple", "cherry") * ``` * * @category models @@ -58,7 +58,7 @@ export interface HashSet extends Iterable, Equal, Pipeable, In * * **Example** (Extracting value types from a HashSet) * - * ```ts + * ```ts import.meta.vitest * import { HashSet } from "effect" * * // Create a concrete HashSet for type extraction @@ -71,6 +71,7 @@ export interface HashSet extends Iterable, Equal, Pipeable, In * const processFruit = (fruit: Fruit) => { * return `Processing ${fruit}` * } + * processFruit("apple") // => "Processing apple" * ``` * * @since 2.0.0 @@ -85,7 +86,7 @@ export declare namespace HashSet { * * **Example** (Extracting a HashSet value type) * - * ```ts + * ```ts import.meta.vitest * import { HashSet } from "effect" * * const numbers = HashSet.make(1, 2, 3, 4, 5) @@ -94,6 +95,7 @@ export declare namespace HashSet { * type NumberType = HashSet.HashSet.Value // number * * const processNumber = (n: NumberType) => n * 2 + * processNumber(3) // => 6 * ``` * * @category utility types @@ -107,17 +109,17 @@ export declare namespace HashSet { * * **Example** (Creating an empty HashSet) * - * ```ts + * ```ts import.meta.vitest * import { HashSet } from "effect" * * const set = HashSet.empty() * - * console.log(HashSet.size(set)) // 0 - * console.log(HashSet.isEmpty(set)) // true + * HashSet.size(set) // => 0 + * HashSet.isEmpty(set) // => true * * // Add some values * const withValues = HashSet.add(HashSet.add(set, "hello"), "world") - * console.log(HashSet.size(withValues)) // 2 + * withValues // => HashSet.make("hello", "world") * ``` * * @category constructors @@ -130,17 +132,14 @@ export const empty: () => HashSet = internal.empty * * **Example** (Creating a HashSet from values) * - * ```ts + * ```ts import.meta.vitest * import { HashSet } from "effect" * - * const fruits = HashSet.make("apple", "banana", "cherry") - * console.log(HashSet.size(fruits)) // 3 + * HashSet.make("apple", "banana", "cherry") // => HashSet.make("apple", "banana", "cherry") * - * const numbers = HashSet.make(1, 2, 3, 2, 1) // Duplicates ignored - * console.log(HashSet.size(numbers)) // 3 + * HashSet.make(1, 2, 3, 2, 1) // => HashSet.make(1, 2, 3) * - * const mixed = HashSet.make("hello", 42, true) - * console.log(HashSet.size(mixed)) // 3 + * HashSet.make("hello", 42, true) // => HashSet.make("hello", 42, true) * ``` * * @category constructors @@ -155,17 +154,14 @@ export const make: >( * * **Example** (Creating a HashSet from an iterable) * - * ```ts + * ```ts import.meta.vitest * import { HashSet } from "effect" * - * const fromArray = HashSet.fromIterable(["a", "b", "c", "b", "a"]) - * console.log(HashSet.size(fromArray)) // 3 + * HashSet.fromIterable(["a", "b", "c", "b", "a"]) // => HashSet.make("a", "b", "c") * - * const fromSet = HashSet.fromIterable(new Set([1, 2, 3])) - * console.log(HashSet.size(fromSet)) // 3 + * HashSet.fromIterable(new Set([1, 2, 3])) // => HashSet.make(1, 2, 3) * - * const fromString = HashSet.fromIterable("hello") - * console.log(Array.from(fromString)) // ["h", "e", "l", "o"] + * HashSet.fromIterable("hello") // => HashSet.make("h", "e", "l", "o") * ``` * * @category constructors @@ -178,15 +174,15 @@ export const fromIterable: (values: Iterable) => HashSet = internal.fro * * **Example** (Checking for a HashSet) * - * ```ts + * ```ts import.meta.vitest * import { HashSet } from "effect" * * const set = HashSet.make(1, 2, 3) * const array = [1, 2, 3] * - * console.log(HashSet.isHashSet(set)) // true - * console.log(HashSet.isHashSet(array)) // false - * console.log(HashSet.isHashSet(null)) // false + * HashSet.isHashSet(set) // => true + * HashSet.isHashSet(array) // => false + * HashSet.isHashSet(null) // => false * ``` * * @category guards @@ -202,19 +198,18 @@ export const isHashSet: { * * **Example** (Adding values to a HashSet) * - * ```ts + * ```ts import.meta.vitest * import { HashSet } from "effect" * * const set = HashSet.make("a", "b") * const withC = HashSet.add(set, "c") * - * console.log(HashSet.size(set)) // 2 (original unchanged) - * console.log(HashSet.size(withC)) // 3 - * console.log(HashSet.has(withC, "c")) // true + * set // => HashSet.make("a", "b") + * withC // => HashSet.make("a", "b", "c") + * HashSet.has(withC, "c") // => true * * // Adding existing value has no effect - * const same = HashSet.add(set, "a") - * console.log(HashSet.size(same)) // 2 + * HashSet.add(set, "a") // => HashSet.make("a", "b") * ``` * * @category mutations @@ -233,15 +228,15 @@ export const add: { * * **Example** (Checking HashSet membership) * - * ```ts + * ```ts import.meta.vitest * import { Equal, Hash, HashSet } from "effect" * * // Works with any type that implements Equal * * const set = HashSet.make("apple", "banana", "cherry") * - * console.log(HashSet.has(set, "apple")) // true - * console.log(HashSet.has(set, "grape")) // false + * HashSet.has(set, "apple") // => true + * HashSet.has(set, "grape") // => false * * class Person implements Equal.Equal { * constructor(readonly name: string) {} @@ -256,10 +251,10 @@ export const add: { * } * * const people = HashSet.make(new Person("Alice"), new Person("Bob")) - * console.log(HashSet.has(people, new Person("Alice"))) // true + * HashSet.has(people, new Person("Alice")) // => true * ``` * - * @category elements + * @category predicates * @since 2.0.0 */ export const has: { @@ -275,19 +270,18 @@ export const has: { * * **Example** (Removing values from a HashSet) * - * ```ts + * ```ts import.meta.vitest * import { HashSet } from "effect" * * const set = HashSet.make("a", "b", "c") * const withoutB = HashSet.remove(set, "b") * - * console.log(HashSet.size(set)) // 3 (original unchanged) - * console.log(HashSet.size(withoutB)) // 2 - * console.log(HashSet.has(withoutB, "b")) // false + * set // => HashSet.make("a", "b", "c") + * withoutB // => HashSet.make("a", "c") + * HashSet.has(withoutB, "b") // => false * * // Removing non-existent value has no effect - * const same = HashSet.remove(set, "d") - * console.log(HashSet.size(same)) // 3 + * HashSet.remove(set, "d") // => HashSet.make("a", "b", "c") * ``` * * @category mutations @@ -306,17 +300,14 @@ export const remove: { * * **Example** (Getting the HashSet size) * - * ```ts + * ```ts import.meta.vitest * import { HashSet } from "effect" * - * const empty = HashSet.empty() - * console.log(HashSet.size(empty)) // 0 + * HashSet.size(HashSet.empty()) // => 0 * - * const small = HashSet.make("a", "b") - * console.log(HashSet.size(small)) // 2 + * HashSet.size(HashSet.make("a", "b")) // => 2 * - * const withDuplicates = HashSet.fromIterable(["x", "y", "z", "x", "y"]) - * console.log(HashSet.size(withDuplicates)) // 3 + * HashSet.size(HashSet.fromIterable(["x", "y", "z", "x", "y"])) // => 3 * ``` * * @category getters @@ -329,17 +320,15 @@ export const size: (self: HashSet) => number = internal.size * * **Example** (Checking whether a HashSet is empty) * - * ```ts + * ```ts import.meta.vitest * import { HashSet } from "effect" * - * const empty = HashSet.empty() - * console.log(HashSet.isEmpty(empty)) // true + * HashSet.isEmpty(HashSet.empty()) // => true * - * const nonEmpty = HashSet.make("a") - * console.log(HashSet.isEmpty(nonEmpty)) // false + * HashSet.isEmpty(HashSet.make("a")) // => false * ``` * - * @category getters + * @category predicates * @since 4.0.0 */ export const isEmpty: (self: HashSet) => boolean = internal.isEmpty @@ -349,15 +338,10 @@ export const isEmpty: (self: HashSet) => boolean = internal.isEmpty * * **Example** (Combining HashSets) * - * ```ts + * ```ts import.meta.vitest * import { HashSet } from "effect" * - * const set1 = HashSet.make("a", "b") - * const set2 = HashSet.make("b", "c") - * const combined = HashSet.union(set1, set2) - * - * console.log(Array.from(combined).sort()) // ["a", "b", "c"] - * console.log(HashSet.size(combined)) // 3 + * HashSet.union(HashSet.make("a", "b"), HashSet.make("b", "c")) // => HashSet.make("a", "b", "c") * ``` * * @category combinators @@ -376,15 +360,10 @@ export const union: { * * **Example** (Finding common HashSet values) * - * ```ts + * ```ts import.meta.vitest * import { HashSet } from "effect" * - * const set1 = HashSet.make("a", "b", "c") - * const set2 = HashSet.make("b", "c", "d") - * const common = HashSet.intersection(set1, set2) - * - * console.log(Array.from(common).sort()) // ["b", "c"] - * console.log(HashSet.size(common)) // 2 + * HashSet.intersection(HashSet.make("a", "b", "c"), HashSet.make("b", "c", "d")) // => HashSet.make("b", "c") * ``` * * @category combinators @@ -403,15 +382,10 @@ export const intersection: { * * **Example** (Finding HashSet differences) * - * ```ts + * ```ts import.meta.vitest * import { HashSet } from "effect" * - * const set1 = HashSet.make("a", "b", "c") - * const set2 = HashSet.make("b", "d") - * const diff = HashSet.difference(set1, set2) - * - * console.log(Array.from(diff).sort()) // ["a", "c"] - * console.log(HashSet.size(diff)) // 2 + * HashSet.difference(HashSet.make("a", "b", "c"), HashSet.make("b", "d")) // => HashSet.make("a", "c") * ``` * * @category combinators @@ -430,20 +404,20 @@ export const difference: { * * **Example** (Checking subset relationships) * - * ```ts + * ```ts import.meta.vitest * import { HashSet } from "effect" * * const small = HashSet.make("a", "b") * const large = HashSet.make("a", "b", "c", "d") * const other = HashSet.make("x", "y") * - * console.log(HashSet.isSubset(small, large)) // true - * console.log(HashSet.isSubset(large, small)) // false - * console.log(HashSet.isSubset(small, other)) // false - * console.log(HashSet.isSubset(small, small)) // true + * HashSet.isSubset(small, large) // => true + * HashSet.isSubset(large, small) // => false + * HashSet.isSubset(small, other) // => false + * HashSet.isSubset(small, small) // => true * ``` * - * @category elements + * @category predicates * @since 2.0.0 */ export const isSubset: { @@ -459,19 +433,18 @@ export const isSubset: { * * **Example** (Mapping HashSet values) * - * ```ts + * ```ts import.meta.vitest * import { HashSet } from "effect" * * const numbers = HashSet.make(1, 2, 3) * const doubled = HashSet.map(numbers, (n) => n * 2) * - * console.log(Array.from(doubled).sort()) // [2, 4, 6] - * console.log(HashSet.size(doubled)) // 3 + * doubled // => HashSet.make(2, 4, 6) * * // Mapping can reduce size if function produces duplicates * const strings = HashSet.make("apple", "banana", "cherry") * const lengths = HashSet.map(strings, (s) => s.length) - * console.log(Array.from(lengths).sort()) // [5, 6] (apple=5, banana=6, cherry=6) + * lengths // => HashSet.make(5, 6) * ``` * * @category mapping @@ -490,14 +463,10 @@ export const map: { * * **Example** (Filtering HashSet values) * - * ```ts + * ```ts import.meta.vitest * import { HashSet } from "effect" * - * const numbers = HashSet.make(1, 2, 3, 4, 5, 6) - * const evens = HashSet.filter(numbers, (n) => n % 2 === 0) - * - * console.log(Array.from(evens).sort()) // [2, 4, 6] - * console.log(HashSet.size(evens)) // 3 + * HashSet.filter(HashSet.make(1, 2, 3, 4, 5, 6), (n) => n % 2 === 0) // => HashSet.make(2, 4, 6) * ``` * * @category filtering @@ -524,19 +493,18 @@ export const filter: { * * **Example** (Testing whether some values match) * - * ```ts + * ```ts import.meta.vitest * import { HashSet } from "effect" * * const numbers = HashSet.make(1, 2, 3, 4, 5) * - * console.log(HashSet.some(numbers, (n) => n > 3)) // true - * console.log(HashSet.some(numbers, (n) => n > 10)) // false + * HashSet.some(numbers, (n) => n > 3) // => true + * HashSet.some(numbers, (n) => n > 10) // => false * - * const empty = HashSet.empty() - * console.log(HashSet.some(empty, (n) => n > 0)) // false + * HashSet.some(HashSet.empty(), (n) => n > 0) // => false * ``` * - * @category elements + * @category predicates * @since 2.0.0 */ export const some: { @@ -552,19 +520,18 @@ export const some: { * * **Example** (Testing whether every value matches) * - * ```ts + * ```ts import.meta.vitest * import { HashSet } from "effect" * * const numbers = HashSet.make(2, 4, 6, 8) * - * console.log(HashSet.every(numbers, (n) => n % 2 === 0)) // true - * console.log(HashSet.every(numbers, (n) => n > 5)) // false + * HashSet.every(numbers, (n) => n % 2 === 0) // => true + * HashSet.every(numbers, (n) => n > 5) // => false * - * const empty = HashSet.empty() - * console.log(HashSet.every(empty, (n) => n > 0)) // true (vacuously true) + * HashSet.every(HashSet.empty(), (n) => n > 0) // => true * ``` * - * @category elements + * @category predicates * @since 2.0.0 */ export const every: { @@ -580,17 +547,11 @@ export const every: { * * **Example** (Reducing HashSet values) * - * ```ts + * ```ts import.meta.vitest * import { HashSet } from "effect" * * const numbers = HashSet.make(1, 2, 3, 4, 5) - * const sum = HashSet.reduce(numbers, 0, (acc, n) => acc + n) - * - * console.log(sum) // 15 - * - * const strings = HashSet.make("a", "b", "c") - * const concatenated = HashSet.reduce(strings, "", (acc, s) => acc + s) - * console.log(concatenated) // Order may vary: "abc", "bac", etc. + * HashSet.reduce(numbers, 0, (acc, n) => acc + n) // => 15 * ``` * * @category folding diff --git a/repos/effect/packages/effect/src/Inspectable.ts b/repos/effect/packages/effect/src/Inspectable.ts index 05fed12bff..e2c06932a2 100644 --- a/repos/effect/packages/effect/src/Inspectable.ts +++ b/repos/effect/packages/effect/src/Inspectable.ts @@ -28,7 +28,7 @@ import { redact } from "./Redactable.ts" * * **Example** (Defining custom Node inspection) * - * ```ts + * ```ts import.meta.vitest * import { Inspectable } from "effect" * * class CustomObject { @@ -40,7 +40,7 @@ import { redact } from "./Redactable.ts" * } * * const obj = new CustomObject("hello") - * console.log(obj) // Displays: CustomObject(hello) + * obj[Inspectable.NodeInspectSymbol]() // => "CustomObject(hello)" * ``` * * @category symbols @@ -59,7 +59,7 @@ export const NodeInspectSymbol = Symbol.for("nodejs.util.inspect.custom") * * **Example** (Typing custom Node inspection) * - * ```ts + * ```ts import.meta.vitest * import { Inspectable } from "effect" * * class CustomObject { @@ -71,7 +71,7 @@ export const NodeInspectSymbol = Symbol.for("nodejs.util.inspect.custom") * } * * const obj = new CustomObject("test") - * console.log(obj) // CustomObject(test) + * obj[Inspectable.NodeInspectSymbol]() // => "CustomObject(test)" * ``` * * @category symbols @@ -94,7 +94,7 @@ export type NodeInspectSymbol = typeof NodeInspectSymbol * * **Example** (Implementing inspectable objects) * - * ```ts + * ```ts import.meta.vitest * import { Formatter, Inspectable } from "effect" * * class Result implements Inspectable.Inspectable { @@ -117,7 +117,7 @@ export type NodeInspectSymbol = typeof NodeInspectSymbol * } * * const success = new Result("Success", 42) - * console.log(success.toString()) // Pretty formatted JSON + * success.toString() // => "{\"_tag\":\"Success\",\"value\":42}" * ``` * * @category models @@ -208,7 +208,7 @@ export const toStringUnknown = (u: unknown, whitespace: number | string | undefi * * **Example** (Using the base inspectable prototype) * - * ```ts + * ```ts import.meta.vitest * import { Inspectable } from "effect" * * // Use as prototype @@ -216,7 +216,7 @@ export const toStringUnknown = (u: unknown, whitespace: number | string | undefi * myObject.name = "example" * myObject.value = 42 * - * console.log(myObject.toString()) // Pretty printed representation + * myObject.toString() // => "\"[toJSON threw]\"" * * // Or extend in a constructor * function MyClass(this: any, name: string) { @@ -256,7 +256,7 @@ export const BaseProto: Inspectable = { * * **Example** (Extending the inspectable base class) * - * ```ts + * ```ts import.meta.vitest * import { Inspectable } from "effect" * * class User extends Inspectable.Class { @@ -279,11 +279,11 @@ export const BaseProto: Inspectable = { * } * * const user = new User(1, "Alice", "alice@example.com") - * console.log(user.toString()) // Pretty printed JSON with _tag, id, name, email - * console.log(user) // In Node.js, shows the same formatted output + * user.toString() // => "{\"_tag\":\"User\",\"id\":1,\"name\":\"Alice\",\"email\":\"alice@example.com\"}" + * user[Inspectable.NodeInspectSymbol]() // => { _tag: "User", id: 1, name: "Alice", email: "alice@example.com" } * ``` * - * @category classes + * @category models * @since 2.0.0 */ export abstract class Class { diff --git a/repos/effect/packages/effect/src/Iterable.ts b/repos/effect/packages/effect/src/Iterable.ts index 5be65e76f3..39e588f8b0 100644 --- a/repos/effect/packages/effect/src/Iterable.ts +++ b/repos/effect/packages/effect/src/Iterable.ts @@ -12,6 +12,7 @@ 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" @@ -32,21 +33,21 @@ import type { NoInfer } from "./Types.ts" * * **Example** (Generating values by index) * - * ```ts + * ```ts import.meta.vitest * import { Iterable } from "effect" * * // Generate first 5 even numbers * const evens = Iterable.makeBy((n) => n * 2, { length: 5 }) - * console.log(Array.from(evens)) // [0, 2, 4, 6, 8] + * Array.from(evens) // => [0, 2, 4, 6, 8] * * // Generate squares * const squares = Iterable.makeBy((n) => n * n, { length: 4 }) - * console.log(Array.from(squares)) // [0, 1, 4, 9] + * Array.from(squares) // => [0, 1, 4, 9] * * // Infinite sequence (be careful when consuming!) * const naturals = Iterable.makeBy((n) => n) * const first10 = Iterable.take(naturals, 10) - * console.log(Array.from(first10)) // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + * Array.from(first10) // => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] * ``` * * @category constructors @@ -82,11 +83,10 @@ export const makeBy = (f: (i: number) => A, options?: { * * **Example** (Creating a range) * - * ```ts + * ```ts import.meta.vitest * import { Iterable } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(Array.from(Iterable.range(1, 3)), [1, 2, 3]) + * Array.from(Iterable.range(1, 3)) // => [1, 2, 3] * ``` * * @category constructors @@ -110,11 +110,10 @@ export const range = (start: number, end?: number): Iterable => { * * **Example** (Repeating a value) * - * ```ts + * ```ts import.meta.vitest * import { Iterable } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(Array.from(Iterable.replicate("a", 3)), ["a", "a", "a"]) + * Array.from(Iterable.replicate("a", 3)) // => ["a", "a", "a"] * ``` * * @category constructors @@ -173,15 +172,11 @@ export const forever = (self: Iterable): Iterable => repeat(self, Infin * * **Example** (Converting a record to entries) * - * ```ts + * ```ts import.meta.vitest * import { Iterable } from "effect" - * import * as assert from "node:assert" * * const x = { a: 1, b: 2, c: 3 } - * assert.deepStrictEqual(Array.from(Iterable.fromRecord(x)), [["a", 1], ["b", 2], [ - * "c", - * 3 - * ]]) + * Array.from(Iterable.fromRecord(x)) // => [["a", 1], ["b", 2], ["c", 3]] * ``` * * @category converting @@ -202,17 +197,17 @@ export const fromRecord = (self: Readonly>): I * * **Example** (Prepending an element) * - * ```ts + * ```ts import.meta.vitest * import { Iterable } from "effect" * * const numbers = [2, 3, 4] * const withOne = Iterable.prepend(numbers, 1) - * console.log(Array.from(withOne)) // [1, 2, 3, 4] + * Array.from(withOne) // => [1, 2, 3, 4] * * // Works with any iterable * const letters = "abc" * const withZ = Iterable.prepend(letters, "z") - * console.log(Array.from(withZ)) // ["z", "a", "b", "c"] + * Array.from(withZ) // => ["z", "a", "b", "c"] * ``` * * @category combining @@ -228,14 +223,10 @@ export const prepend: { * * **Example** (Prepending another iterable) * - * ```ts + * ```ts import.meta.vitest * import { Iterable } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual( - * Array.from(Iterable.prependAll([1, 2], ["a", "b"])), - * ["a", "b", 1, 2] - * ) + * Array.from(Iterable.prependAll([1, 2], ["a", "b"])) // => ["a", "b", 1, 2] * ``` * * @category combining @@ -269,19 +260,11 @@ export const prependAll: { * * **Example** (Appending an element) * - * ```ts + * ```ts import.meta.vitest * import { Iterable } from "effect" * * const numbers = [1, 2, 3] - * const withFour = Iterable.append(numbers, 4) - * console.log(Array.from(withFour)) // [1, 2, 3, 4] - * - * // Chain multiple appends - * const result = Iterable.append( - * Iterable.append([1, 2], 3), - * 4 - * ) - * console.log(Array.from(result)) // [1, 2, 3, 4] + * Array.from(Iterable.append(numbers, 4)) // => [1, 2, 3, 4] * ``` * * @see {@link prepend} for adding one element before the existing elements @@ -314,25 +297,21 @@ export const append: { * * **Example** (Concatenating iterables) * - * ```ts + * ```ts import.meta.vitest * import { Iterable } from "effect" * - * const first = [1, 2, 3] - * const second = [4, 5, 6] - * const combined = Iterable.appendAll(first, second) - * console.log(Array.from(combined)) // [1, 2, 3, 4, 5, 6] + * Array.from(Iterable.appendAll([1, 2, 3], [4, 5, 6])) // => [1, 2, 3, 4, 5, 6] * * // Works with different iterable types * const numbers = [1, 2] * const letters = "abc" * const mixed = Iterable.appendAll(numbers, letters) - * console.log(Array.from(mixed)) // [1, 2, "a", "b", "c"] + * Array.from(mixed) // => [1, 2, "a", "b", "c"] * * // Lazy evaluation - only consumes what's needed * const infinite = Iterable.range(1) * const finite = [0, -1, -2] - * const result = Iterable.take(Iterable.appendAll(finite, infinite), 5) - * console.log(Array.from(result)) // [0, -1, -2, 1, 2] + * Array.from(Iterable.take(Iterable.appendAll(finite, infinite), 5)) // => [0, -1, -2, 1, 2] * ``` * * @see {@link append} for appending one value instead of another iterable @@ -374,23 +353,23 @@ export const appendAll: { * * **Example** (Tracking running results) * - * ```ts + * ```ts import.meta.vitest * import { Iterable } from "effect" * * // Running sum of numbers * const numbers = [1, 2, 3, 4, 5] * const runningSum = Iterable.scan(numbers, 0, (acc, n) => acc + n) - * console.log(Array.from(runningSum)) // [0, 1, 3, 6, 10, 15] + * Array.from(runningSum) // => [0, 1, 3, 6, 10, 15] * * // Build strings progressively * const letters = ["a", "b", "c"] * const progressive = Iterable.scan(letters, "", (acc, letter) => acc + letter) - * console.log(Array.from(progressive)) // ["", "a", "ab", "abc"] + * Array.from(progressive) // => ["", "a", "ab", "abc"] * * // Track maximum values seen so far * const values = [3, 1, 4, 1, 5, 9, 2] * const runningMax = Iterable.scan(values, -Infinity, Math.max) - * console.log(Array.from(runningMax)) // [-Infinity, 3, 3, 4, 4, 5, 9, 9] + * Array.from(runningMax) // => [-Infinity, 3, 3, 4, 4, 5, 9, 9] * ``` * * @category folding @@ -424,12 +403,11 @@ export const scan: { * * **Example** (Checking for emptiness) * - * ```ts + * ```ts import.meta.vitest * import { Iterable } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(Iterable.isEmpty([]), true) - * assert.deepStrictEqual(Iterable.isEmpty([1, 2, 3]), false) + * Iterable.isEmpty([]) // => true + * Iterable.isEmpty([1, 2, 3]) // => false * ``` * * @category guards @@ -445,22 +423,22 @@ export const isEmpty = (self: Iterable): self is Iterable => { * * **Example** (Counting iterable elements) * - * ```ts + * ```ts import.meta.vitest * import { Iterable } from "effect" * * const numbers = [1, 2, 3, 4, 5] - * console.log(Iterable.size(numbers)) // 5 + * Iterable.size(numbers) // => 5 * * const empty = Iterable.empty() - * console.log(Iterable.size(empty)) // 0 + * Iterable.size(empty) // => 0 * * // Works with any iterable * const letters = "hello" - * console.log(Iterable.size(letters)) // 5 + * Iterable.size(letters) // => 5 * * // Note: This consumes the entire iterable * const range = Iterable.range(1, 100) - * console.log(Iterable.size(range)) // 100 + * Iterable.size(range) // => 100 * ``` * * @category getters @@ -480,24 +458,24 @@ export const size = (self: Iterable): number => { * * **Example** (Getting the first element) * - * ```ts + * ```ts import.meta.vitest * import { Iterable, Option } from "effect" * * const numbers = [1, 2, 3] - * console.log(Iterable.head(numbers)) // Option.some(1) + * Iterable.head(numbers) // => Option.some(1) * * const empty = Iterable.empty() - * console.log(Iterable.head(empty)) // Option.none() + * Iterable.head(empty) // => Option.none() * * // Safe way to get first element * const firstEven = Iterable.head( * Iterable.filter([1, 3, 4, 5], (x) => x % 2 === 0) * ) - * console.log(firstEven) // Option.some(4) + * firstEven // => Option.some(4) * * // Use with Option methods * const doubled = Option.map(Iterable.head([5, 10, 15]), (x) => x * 2) - * console.log(doubled) // Option.some(10) + * doubled // => Option.some(10) * ``` * * @category getters @@ -523,21 +501,21 @@ export const head = (self: Iterable): Option => { * * **Example** (Getting the first element unsafely) * - * ```ts + * ```ts import.meta.vitest * import { Iterable } from "effect" * * const numbers = [1, 2, 3] - * console.log(Iterable.headUnsafe(numbers)) // 1 + * Iterable.headUnsafe(numbers) // => 1 * * const letters = "hello" - * console.log(Iterable.headUnsafe(letters)) // "h" + * Iterable.headUnsafe(letters) // => "h" * * // Iterable.headUnsafe(Iterable.empty()) * // throws Error: "headUnsafe: empty iterable" * * // Use only when you're certain the iterable is non-empty * const nonEmpty = Iterable.range(1, 10) - * console.log(Iterable.headUnsafe(nonEmpty)) // 1 + * Iterable.headUnsafe(nonEmpty) // => 1 * ``` * * @category getters @@ -559,25 +537,25 @@ export const headUnsafe = (self: Iterable): A => { * * **Example** (Taking from the start) * - * ```ts + * ```ts import.meta.vitest * import { Iterable } from "effect" * * const numbers = [1, 2, 3, 4, 5] * const firstThree = Iterable.take(numbers, 3) - * console.log(Array.from(firstThree)) // [1, 2, 3] + * Array.from(firstThree) // => [1, 2, 3] * * // Taking more than available returns all elements * const firstTen = Iterable.take(numbers, 10) - * console.log(Array.from(firstTen)) // [1, 2, 3, 4, 5] + * Array.from(firstTen) // => [1, 2, 3, 4, 5] * * // Taking 0 or negative returns empty * const none = Iterable.take(numbers, 0) - * console.log(Array.from(none)) // [] + * Array.from(none) // => [] * * // Useful with infinite iterables * const naturals = Iterable.range(1) * const firstFive = Iterable.take(naturals, 5) - * console.log(Array.from(firstFive)) // [1, 2, 3, 4, 5] + * Array.from(firstFive) // => [1, 2, 3, 4, 5] * ``` * * @category getters @@ -608,22 +586,22 @@ export const take: { * * **Example** (Taking while a predicate holds) * - * ```ts + * ```ts import.meta.vitest * import { Iterable } from "effect" * * const numbers = [2, 4, 6, 8, 3, 10, 12] * const evenPrefix = Iterable.takeWhile(numbers, (x) => x % 2 === 0) - * console.log(Array.from(evenPrefix)) // [2, 4, 6, 8] + * Array.from(evenPrefix) // => [2, 4, 6, 8] * * // With index * const letters = ["a", "b", "c", "d", "e"] * const firstThreeByIndex = Iterable.takeWhile(letters, (_, i) => i < 3) - * console.log(Array.from(firstThreeByIndex)) // ["a", "b", "c"] + * Array.from(firstThreeByIndex) // => ["a", "b", "c"] * * // Stops at first non-matching element * const mixed = [1, 3, 5, 4, 7, 9] * const oddPrefix = Iterable.takeWhile(mixed, (x) => x % 2 === 1) - * console.log(Array.from(oddPrefix)) // [1, 3, 5] + * Array.from(oddPrefix) // => [1, 3, 5] * * // Type refinement * const values: Array = ["a", "b", "c", 1, "d"] @@ -631,7 +609,7 @@ export const take: { * values, * (x): x is string => typeof x === "string" * ) - * console.log(Array.from(stringPrefix)) // ["a", "b", "c"] (typed as string[]) + * Array.from(stringPrefix) // => ["a", "b", "c"] * ``` * * @category getters @@ -667,24 +645,24 @@ export const takeWhile: { * * **Example** (Dropping from the start) * - * ```ts + * ```ts import.meta.vitest * import { Iterable } from "effect" * * const numbers = [1, 2, 3, 4, 5] * const withoutFirstTwo = Iterable.drop(numbers, 2) - * console.log(Array.from(withoutFirstTwo)) // [3, 4, 5] + * Array.from(withoutFirstTwo) // => [3, 4, 5] * * // Dropping more than available returns empty * const withoutFirstTen = Iterable.drop(numbers, 10) - * console.log(Array.from(withoutFirstTen)) // [] + * Array.from(withoutFirstTen) // => [] * * // Dropping 0 or negative returns all elements * const all = Iterable.drop(numbers, 0) - * console.log(Array.from(all)) // [1, 2, 3, 4, 5] + * Array.from(all) // => [1, 2, 3, 4, 5] * * // Combine with take for slicing * const slice = Iterable.take(Iterable.drop(numbers, 1), 3) - * console.log(Array.from(slice)) // [2, 3, 4] + * Array.from(slice) // => [2, 3, 4] * ``` * * @category getters @@ -718,20 +696,20 @@ export const drop: { * * **Example** (Finding the first match) * - * ```ts + * ```ts import.meta.vitest * import { Iterable, Option } from "effect" * * const numbers = [1, 3, 4, 6, 8] * const firstEven = Iterable.findFirst(numbers, (x) => x % 2 === 0) - * console.log(firstEven) // Option.some(4) + * firstEven // => Option.some(4) * * const firstGreaterThan10 = Iterable.findFirst(numbers, (x) => x > 10) - * console.log(firstGreaterThan10) // Option.none() + * firstGreaterThan10 // => Option.none() * * // With index * const letters = ["a", "b", "c", "d"] * const atEvenIndex = Iterable.findFirst(letters, (_, i) => i % 2 === 0) - * console.log(atEvenIndex) // Option.some("a") + * atEvenIndex // => Option.some("a") * * // Type refinement * const mixed: Array = [1, "hello", 2, "world"] @@ -739,17 +717,17 @@ export const drop: { * mixed, * (x): x is string => typeof x === "string" * ) - * console.log(firstString) // Option.some("hello") + * firstString // => Option.some("hello") * * // Transform during search * const findSquareRoot = Iterable.findFirst([1, 4, 9, 16], (x) => { * const sqrt = Math.sqrt(x) * return Number.isInteger(sqrt) ? Option.some(sqrt) : Option.none() * }) - * console.log(findSquareRoot) // Option.some(1) + * findSquareRoot // => Option.some(1) * ``` * - * @category elements + * @category searching * @since 2.0.0 */ export const findFirst: { @@ -785,20 +763,20 @@ export const findFirst: { * * **Example** (Finding the last match) * - * ```ts - * import { Iterable } from "effect" + * ```ts import.meta.vitest + * import { Iterable, Option } from "effect" * * const numbers = [1, 3, 4, 6, 8, 2] * const lastEven = Iterable.findLast(numbers, (x) => x % 2 === 0) - * console.log(lastEven) // Option.some(2) + * lastEven // => Option.some(2) * * const lastGreaterThan10 = Iterable.findLast(numbers, (x) => x > 10) - * console.log(lastGreaterThan10) // Option.none() + * lastGreaterThan10 // => Option.none() * * // With index * const letters = ["a", "b", "c", "d", "e"] * const lastAtEvenIndex = Iterable.findLast(letters, (_, i) => i % 2 === 0) - * console.log(lastAtEvenIndex) // Option.some("e") (index 4) + * lastAtEvenIndex // => Option.some("e") * * // Type refinement * const mixed: Array = [1, "hello", 2, "world", 3] @@ -806,10 +784,10 @@ export const findFirst: { * mixed, * (x): x is string => typeof x === "string" * ) - * console.log(lastString) // Option.some("world") + * lastString // => Option.some("world") * ``` * - * @category elements + * @category searching * @since 2.0.0 */ export const findLast: { @@ -846,31 +824,31 @@ export const findLast: { * * **Example** (Zipping iterables) * - * ```ts + * ```ts import.meta.vitest * import { Iterable } from "effect" * * const numbers = [1, 2, 3] * const letters = ["a", "b", "c"] * const zipped = Iterable.zip(numbers, letters) - * console.log(Array.from(zipped)) // [[1, "a"], [2, "b"], [3, "c"]] + * Array.from(zipped) // => [[1, "a"], [2, "b"], [3, "c"]] * * // Different lengths - shorter one determines result length * const short = [1, 2] * const long = ["a", "b", "c", "d"] * const partial = Iterable.zip(short, long) - * console.log(Array.from(partial)) // [[1, "a"], [2, "b"]] + * Array.from(partial) // => [[1, "a"], [2, "b"]] * * // Works with any iterables * const range = Iterable.range(1, 3) * const word = "abc" * const mixed = Iterable.zip(range, word) - * console.log(Array.from(mixed)) // [[1, "a"], [2, "b"], [3, "c"]] + * Array.from(mixed) // => [[1, "a"], [2, "b"], [3, "c"]] * * // Create indexed pairs * const values = ["apple", "banana", "cherry"] * const indices = Iterable.range(0, 2) * const indexed = Iterable.zip(indices, values) - * console.log(Array.from(indexed)) // [[0, "apple"], [1, "banana"], [2, "cherry"]] + * Array.from(indexed) // => [[0, "apple"], [1, "banana"], [2, "cherry"]] * ``` * * @category zipping @@ -890,14 +868,14 @@ export const zip: { * * **Example** (Zipping with a combining function) * - * ```ts + * ```ts import.meta.vitest * import { Iterable } from "effect" * * // Add corresponding elements * const a = [1, 2, 3, 4] * const b = [10, 20, 30, 40] * const sums = Iterable.zipWith(a, b, (x, y) => x + y) - * console.log(Array.from(sums)) // [11, 22, 33, 44] + * Array.from(sums) // => [11, 22, 33, 44] * * // Combine strings * const firstNames = ["John", "Jane", "Bob"] @@ -907,7 +885,7 @@ export const zip: { * lastNames, * (first, last) => `${first} ${last}` * ) - * console.log(Array.from(fullNames)) // ["John Doe", "Jane Smith", "Bob Johnson"] + * Array.from(fullNames) // => ["John Doe", "Jane Smith", "Bob Johnson"] * * // Different lengths - stops at shorter * const short = [1, 2] @@ -917,7 +895,7 @@ export const zip: { * long, * (num, letter) => `${num}${letter}` * ) - * console.log(Array.from(combined)) // ["1a", "2b"] + * Array.from(combined) // => ["1a", "2b"] * * // Complex transformations * const prices = [10.99, 25.50, 5.00] @@ -925,7 +903,7 @@ export const zip: { * const totals = Iterable.zipWith(prices, quantities, (price, qty) => { * return Math.round(price * qty * 100) / 100 // round to 2 decimal places * }) - * console.log(Array.from(totals)) // [21.98, 25.5, 15] + * Array.from(totals) // => [21.98, 25.5, 15] * ``` * * @category zipping @@ -964,33 +942,33 @@ export const zipWith: { * * **Example** (Interspersing separators) * - * ```ts + * ```ts import.meta.vitest * import { Iterable } from "effect" * * // Join numbers with separator * const numbers = [1, 2, 3, 4] * const withCommas = Iterable.intersperse(numbers, ",") - * console.log(Array.from(withCommas)) // [1, ",", 2, ",", 3, ",", 4] + * Array.from(withCommas) // => [1, ",", 2, ",", 3, ",", 4] * * // Join words with spaces * const words = ["hello", "world", "from", "effect"] * const sentence = Iterable.intersperse(words, " ") - * console.log(Array.from(sentence).join("")) // "hello world from effect" + * Array.from(sentence).join("") // => "hello world from effect" * * // Empty iterable remains empty * const empty = Iterable.empty() * const stillEmpty = Iterable.intersperse(empty, "-") - * console.log(Array.from(stillEmpty)) // [] + * Array.from(stillEmpty) // => [] * * // Single element has no separators added * const single = [42] * const noSeparator = Iterable.intersperse(single, "|") - * console.log(Array.from(noSeparator)) // [42] + * Array.from(noSeparator) // => [42] * * // Build CSS-like strings * const styles = ["color: red", "font-size: 14px", "margin: 10px"] * const css = Iterable.intersperse(styles, "; ") - * console.log(Array.from(css).join("")) // "color: red; font-size: 14px; margin: 10px" + * Array.from(css).join("") // => "color: red; font-size: 14px; margin: 10px" * ``` * * @category combining @@ -1026,7 +1004,7 @@ export const intersperse: { * * **Example** (Checking membership with custom equivalence) * - * ```ts + * ```ts import.meta.vitest * import { Iterable } from "effect" * * // Custom equivalence for objects @@ -1035,7 +1013,7 @@ export const intersperse: { * * const users = [{ id: 1 }, { id: 2 }] * const hasUser1 = containsById(users, { id: 1 }) - * console.log(hasUser1) // true (same id) + * hasUser1 // => true * * // Case-insensitive string comparison * const caseInsensitive = (a: string, b: string) => @@ -1044,7 +1022,7 @@ export const intersperse: { * * const words = ["Hello", "World"] * const hasHello = containsCaseInsensitive(words, "hello") - * console.log(hasHello) // true + * hasHello // => true * * // Approximate number comparison * const approxEqual = (a: number, b: number) => Math.abs(a - b) < 0.1 @@ -1052,10 +1030,10 @@ export const intersperse: { * * const values = [1.0, 2.0, 3.0] * const hasAlmostTwo = containsApprox(values, 2.05) - * console.log(hasAlmostTwo) // true + * hasAlmostTwo // => true * ``` * - * @category elements + * @category predicates * @since 2.0.0 */ export const containsWith = (isEquivalent: (self: A, that: A) => boolean): { @@ -1082,29 +1060,29 @@ export const containsWith = (isEquivalent: (self: A, that: A) => boolean): { * * **Example** (Checking membership) * - * ```ts + * ```ts import.meta.vitest * import { Iterable } from "effect" * * const numbers = [1, 2, 3, 4, 5] - * console.log(Iterable.contains(numbers, 3)) // true - * console.log(Iterable.contains(numbers, 6)) // false + * Iterable.contains(numbers, 3) // => true + * Iterable.contains(numbers, 6) // => false * * const letters = "hello" - * console.log(Iterable.contains(letters, "l")) // true - * console.log(Iterable.contains(letters, "x")) // false + * Iterable.contains(letters, "l") // => true + * Iterable.contains(letters, "x") // => false * * // Works with any iterable * const range = Iterable.range(1, 100) - * console.log(Iterable.contains(range, 50)) // true - * console.log(Iterable.contains(range, 150)) // false + * Iterable.contains(range, 50) // => true + * Iterable.contains(range, 150) // => false * * // Curried version * const containsThree = Iterable.contains(3) - * console.log(containsThree([1, 2, 3])) // true - * console.log(containsThree([4, 5, 6])) // false + * containsThree([1, 2, 3]) // => true + * containsThree([4, 5, 6]) // => false * ``` * - * @category elements + * @category predicates * @since 2.0.0 */ export const contains: { @@ -1118,25 +1096,22 @@ export const contains: { * * **Example** (Chunking an iterable) * - * ```ts + * ```ts import.meta.vitest * import { Iterable } from "effect" * * const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9] * const chunks = Iterable.chunksOf(numbers, 3) - * console.log(Array.from(chunks).map((chunk) => Array.from(chunk))) - * // [[1, 2, 3], [4, 5, 6], [7, 8, 9]] + * Array.from(chunks) // => [[1, 2, 3], [4, 5, 6], [7, 8, 9]] * * // Last chunk can be shorter * const uneven = [1, 2, 3, 4, 5, 6, 7] * const chunks2 = Iterable.chunksOf(uneven, 3) - * console.log(Array.from(chunks2).map((chunk) => Array.from(chunk))) - * // [[1, 2, 3], [4, 5, 6], [7]] + * Array.from(chunks2) // => [[1, 2, 3], [4, 5, 6], [7]] * * // Chunk size larger than iterable * const small = [1, 2] * const chunks3 = Iterable.chunksOf(small, 5) - * console.log(Array.from(chunks3).map((chunk) => Array.from(chunk))) - * // [[1, 2]] + * Array.from(chunks3) // => [[1, 2]] * * // Process data in batches * const data = Iterable.range(1, 100) @@ -1145,7 +1120,7 @@ export const contains: { * batches, * (batch) => Iterable.reduce(batch, 0, (sum, n) => sum + n) * ) - * console.log(Array.from(Iterable.take(batchSums, 3))) // [55, 155, 255] + * Array.from(Iterable.take(batchSums, 3)) // => [55, 155, 255] * ``` * * @category splitting @@ -1187,35 +1162,31 @@ export const chunksOf: { * * **Example** (Grouping consecutive elements with custom equivalence) * - * ```ts + * ```ts import.meta.vitest * import { Iterable } from "effect" * * // Group consecutive equal numbers * const numbers = [1, 1, 2, 2, 2, 3, 1, 1] * const grouped = Iterable.groupWith(numbers, (a, b) => a === b) - * console.log(Array.from(grouped)) - * // [[1, 1], [2, 2, 2], [3], [1, 1]] + * Array.from(grouped) // => [[1, 1], [2, 2, 2], [3], [1, 1]] * * // Case-insensitive grouping of strings * const words = ["Apple", "APPLE", "banana", "Banana", "cherry"] * const caseInsensitive = (a: string, b: string) => * a.toLowerCase() === b.toLowerCase() * const groupedWords = Iterable.groupWith(words, caseInsensitive) - * console.log(Array.from(groupedWords)) - * // [["Apple", "APPLE"], ["banana", "Banana"], ["cherry"]] + * Array.from(groupedWords) // => [["Apple", "APPLE"], ["banana", "Banana"], ["cherry"]] * * // Group by approximate equality * const floats = [1.1, 1.12, 1.9, 2.01, 2.05, 3.5] * const approxEqual = (a: number, b: number) => Math.abs(a - b) < 0.2 * const groupedFloats = Iterable.groupWith(floats, approxEqual) - * console.log(Array.from(groupedFloats)) - * // [[1.1, 1.12], [1.9, 2.01, 2.05], [3.5]] + * Array.from(groupedFloats) // => [[1.1, 1.12], [1.9, 2.01, 2.05], [3.5]] * * // Only groups consecutive elements * const scattered = [1, 2, 1, 2, 1] * const scatteredGroups = Iterable.groupWith(scattered, (a, b) => a === b) - * console.log(Array.from(scatteredGroups)) - * // [[1], [2], [1], [2], [1]] (no grouping since none are consecutive) + * Array.from(scatteredGroups) // => [[1], [2], [1], [2], [1]] * ``` * * @category grouping @@ -1266,18 +1237,16 @@ export const groupWith: { * * **Example** (Grouping consecutive elements) * - * ```ts + * ```ts import.meta.vitest * import { Iterable } from "effect" * * const numbers = [1, 1, 2, 2, 2, 3, 1, 1] * const grouped = Iterable.group(numbers) - * console.log(Array.from(grouped)) - * // [[1, 1], [2, 2, 2], [3], [1, 1]] + * Array.from(grouped) // => [[1, 1], [2, 2, 2], [3], [1, 1]] * * const letters = "aabbccaa" * const groupedLetters = Iterable.group(letters) - * console.log(Array.from(groupedLetters)) - * // [["a", "a"], ["b", "b"], ["c", "c"], ["a", "a"]] + * Array.from(groupedLetters) // => [["a", "a"], ["b", "b"], ["c", "c"], ["a", "a"]] * * // Works with objects using deep equality * const objects = [ @@ -1287,7 +1256,7 @@ export const groupWith: { * { type: "A", value: 1 } * ] * const groupedObjects = Iterable.group(objects) - * console.log(Array.from(groupedObjects).length) // 3 groups + * Array.from(groupedObjects).length // => 3 * // Note: Only consecutive equal objects are grouped together * ``` * @@ -1309,20 +1278,18 @@ export const group: (self: Iterable) => Iterable> = group * * **Example** (Grouping by a key) * - * ```ts + * ```ts import.meta.vitest * import { Iterable } from "effect" * * // Group by string length * const words = ["a", "bb", "ccc", "dd", "eee", "f"] * const byLength = Iterable.groupBy(words, (word) => word.length.toString()) - * console.log(byLength) - * // { "1": ["a", "f"], "2": ["bb", "dd"], "3": ["ccc", "eee"] } + * byLength // => { "1": ["a", "f"], "2": ["bb", "dd"], "3": ["ccc", "eee"] } * * // Group by first letter * const names = ["Alice", "Bob", "Charlie", "David", "Anna", "Betty"] * const byFirstLetter = Iterable.groupBy(names, (name) => name[0]) - * console.log(byFirstLetter) - * // { "A": ["Alice", "Anna"], "B": ["Bob", "Betty"], "C": ["Charlie"], "D": ["David"] } + * byFirstLetter // => { A: ["Alice", "Anna"], B: ["Bob", "Betty"], C: ["Charlie"], D: ["David"] } * * // Group by category * const items = [ @@ -1332,17 +1299,12 @@ export const group: (self: Iterable) => Iterable> = group * { name: "broccoli", category: "vegetable" } * ] * const byCategory = Iterable.groupBy(items, (item) => item.category) - * console.log(byCategory) - * // { - * // "fruit": [{ name: "apple", category: "fruit" }, { name: "banana", category: "fruit" }], - * // "vegetable": [{ name: "carrot", category: "vegetable" }, { name: "broccoli", category: "vegetable" }] - * // } + * Object.keys(byCategory) // => ["fruit", "vegetable"] * * // Group numbers by even/odd * const numbers = [1, 2, 3, 4, 5, 6] * const evenOdd = Iterable.groupBy(numbers, (n) => n % 2 === 0 ? "even" : "odd") - * console.log(evenOdd) - * // { "odd": [1, 3, 5], "even": [2, 4, 6] } + * evenOdd // => { odd: [1, 3, 5], even: [2, 4, 6] } * ``` * * @category grouping @@ -1366,7 +1328,7 @@ export const groupBy: { if (Object.hasOwn(out, k)) { out[k].push(a) } else { - out[k] = [a] + InternalRecord.assignProperty(out, k, [a]) } } return out @@ -1393,18 +1355,10 @@ const constEmptyIterator: Iterator = { * * **Example** (Creating an empty iterable) * - * ```ts + * ```ts import.meta.vitest * import { Iterable } from "effect" * - * const empty = Iterable.empty() - * console.log(Array.from(empty)) // [] - * console.log(Iterable.isEmpty(empty)) // true - * - * // Useful as base case for reductions - * const hasData = true - * const result = hasData - * ? Iterable.range(1, 5) - * : Iterable.empty() + * Array.from(Iterable.empty()) // => [] * ``` * * @category constructors @@ -1422,11 +1376,11 @@ export const empty = (): Iterable => constEmpty * * **Example** (Wrapping a single value) * - * ```ts + * ```ts import.meta.vitest * import { Iterable } from "effect" * * const single = Iterable.of(42) - * console.log(Array.from(single)) // [42] + * Array.from(single) // => [42] * * // Useful for creating homogeneous sequences * const sequences = [ @@ -1441,7 +1395,7 @@ export const empty = (): Iterable => constEmpty * numbers, * (n) => n % 2 === 0 ? Iterable.of(n) : Iterable.empty() * ) - * console.log(Array.from(evensOnly)) // [2, 4] + * Array.from(evensOnly) // => [2, 4] * ``` * * @category constructors @@ -1461,24 +1415,22 @@ export const of = (a: A): Iterable => [a] * * **Example** (Mapping elements) * - * ```ts + * ```ts import.meta.vitest * import { Iterable } from "effect" * * // Transform numbers to their squares * const numbers = [1, 2, 3, 4, 5] * const squares = Iterable.map(numbers, (x) => x * x) - * console.log(Array.from(squares)) // [1, 4, 9, 16, 25] + * Array.from(squares) // => [1, 4, 9, 16, 25] * * // Use index in transformation * const indexed = Iterable.map(["a", "b", "c"], (char, i) => `${i}: ${char}`) - * console.log(Array.from(indexed)) // ["0: a", "1: b", "2: c"] + * Array.from(indexed) // => ["0: a", "1: b", "2: c"] * - * // Chain transformations - * const result = Iterable.map( + * Array.from(Iterable.map( * Iterable.map([1, 2, 3], (x) => x * 2), * (x) => x + 1 - * ) - * console.log(Array.from(result)) // [3, 5, 7] + * )) // => [3, 5, 7] * ``` * * @category mapping @@ -1510,18 +1462,18 @@ export const map: { * * **Example** (Flat mapping iterables) * - * ```ts + * ```ts import.meta.vitest * import { Iterable } from "effect" * * // Expand each number to a range * const numbers = [1, 2, 3] * const expanded = Iterable.flatMap(numbers, (n) => Iterable.range(1, n)) - * console.log(Array.from(expanded)) // [1, 1, 2, 1, 2, 3] + * Array.from(expanded) // => [1, 1, 2, 1, 2, 3] * * // Split strings into characters * const words = ["hi", "bye"] * const chars = Iterable.flatMap(words, (word) => word) - * console.log(Array.from(chars)) // ["h", "i", "b", "y", "e"] + * Array.from(chars) // => ["h", "i", "b", "y", "e"] * * // Conditional expansion with empty iterables * const values = [1, 2, 3, 4, 5] @@ -1529,7 +1481,7 @@ export const map: { * values, * (n) => n % 2 === 0 ? [n, n * 2, n * 3] : [] * ) - * console.log(Array.from(evenMultiples)) // [2, 4, 6, 4, 8, 12] + * Array.from(evenMultiples) // => [2, 4, 6, 4, 8, 12] * * // Use index in transformation * const letters = ["a", "b", "c"] @@ -1537,7 +1489,7 @@ export const map: { * letters, * (letter, i) => Iterable.replicate(letter, i + 1) * ) - * console.log(Array.from(indexed)) // ["a", "b", "b", "c", "c", "c"] + * Array.from(indexed) // => ["a", "b", "b", "c", "c", "c"] * ``` * * @category sequencing @@ -1558,29 +1510,29 @@ export const flatMap: { * * **Example** (Flattening nested iterables) * - * ```ts + * ```ts import.meta.vitest * import { Iterable } from "effect" * * // Flatten nested arrays * const nested = [[1, 2], [3, 4], [5, 6]] * const flat = Iterable.flatten(nested) - * console.log(Array.from(flat)) // [1, 2, 3, 4, 5, 6] + * Array.from(flat) // => [1, 2, 3, 4, 5, 6] * * // Flatten different iterable types * const mixed: Array> = ["ab", "cd"] * const flatMixed = Iterable.flatten(mixed) - * console.log(Array.from(flatMixed)) // ["a", "b", "c", "d"] + * Array.from(flatMixed) // => ["a", "b", "c", "d"] * * // Flatten deeply nested (only one level) * const deepNested = [[[1, 2]], [[3, 4]]] * const oneLevelFlat = Iterable.flatten(deepNested) - * console.log(Array.from(oneLevelFlat).map((arr) => Array.from(arr))) + * Array.from(oneLevelFlat) // => [[1, 2], [3, 4]] * // [[1, 2], [3, 4]] (still contains arrays) * * // Empty iterables are handled correctly * const withEmpty = [[1, 2], [], [3, 4], []] * const flatWithEmpty = Iterable.flatten(withEmpty) - * console.log(Array.from(flatWithEmpty)) // [1, 2, 3, 4] + * Array.from(flatWithEmpty) // => [1, 2, 3, 4] * ``` * * @category sequencing @@ -1591,19 +1543,20 @@ export const flatten = (self: Iterable>): Iterable => ({ const outerIterator = self[Symbol.iterator]() let innerIterator: Iterator | undefined function next() { - if (innerIterator === undefined) { - const next = outerIterator.next() - if (next.done) { - return next + while (true) { + if (innerIterator === undefined) { + const next = outerIterator.next() + if (next.done) { + return next + } + innerIterator = next.value[Symbol.iterator]() + } + const result = innerIterator.next() + if (!result.done) { + return result } - innerIterator = next.value[Symbol.iterator]() - } - const result = innerIterator.next() - if (result.done) { innerIterator = undefined - return next() } - return result } return { next } } @@ -1620,7 +1573,7 @@ export const flatten = (self: Iterable>): Iterable => ({ * * **Example** (Filtering and transforming Result values) * - * ```ts + * ```ts import.meta.vitest * import { Iterable, Result } from "effect" * * // Parse strings to numbers, keeping only valid ones @@ -1629,7 +1582,7 @@ export const flatten = (self: Iterable>): Iterable => ({ * const num = parseInt(s) * return isNaN(num) ? Result.failVoid : Result.succeed(num) * }) - * console.log(Array.from(numbers)) // [1, 2, 4] + * Array.from(numbers) // => [1, 2, 4] * * // Extract specific properties from objects * const users = [ @@ -1643,7 +1596,7 @@ export const flatten = (self: Iterable>): Iterable => ({ * (user) => * user.age >= 18 && user.email ? Result.succeed(user.email) : Result.failVoid * ) - * console.log(Array.from(adultEmails)) // ["alice@example.com", "charlie@example.com"] + * Array.from(adultEmails) // => ["alice@example.com", "charlie@example.com"] * * // Use index in transformation * const items = ["a", "b", "c", "d", "e"] @@ -1651,7 +1604,7 @@ export const flatten = (self: Iterable>): Iterable => ({ * items, * (item, i) => i % 2 === 0 ? Result.succeed(`${i}: ${item}`) : Result.failVoid * ) - * console.log(Array.from(evenIndexItems)) // ["0: a", "2: c", "4: e"] + * Array.from(evenIndexItems) // => ["0: a", "2: c", "4: e"] * ``` * * @category filtering @@ -1688,7 +1641,7 @@ export const filterMap: { * * **Example** (Filtering and transforming until failure) * - * ```ts + * ```ts import.meta.vitest * import { Iterable, Result } from "effect" * * // Parse numbers until we hit an invalid one @@ -1697,7 +1650,7 @@ export const filterMap: { * const num = parseInt(s) * return isNaN(num) ? Result.failVoid : Result.succeed(num) * }) - * console.log(Array.from(numbers)) // [1, 2, 3] (stops at "invalid") + * Array.from(numbers) // => [1, 2, 3] * * // Take elements while they meet a condition and transform them * const values = [2, 4, 6, 7, 8, 10] @@ -1705,7 +1658,7 @@ export const filterMap: { * values, * (n) => n % 2 === 0 ? Result.succeed(n * 2) : Result.failVoid * ) - * console.log(Array.from(doubledEvens)) // [4, 8, 12] (stops at 7) + * Array.from(doubledEvens) // => [4, 8, 12] * * // Process with index until condition fails * const letters = ["a", "b", "c", "d", "e"] @@ -1713,7 +1666,7 @@ export const filterMap: { * letters, * (letter, i) => letter !== "c" ? Result.succeed(`${i}: ${letter}`) : Result.failVoid * ) - * console.log(Array.from(indexedUntilC)) // ["0: a", "1: b"] (stops at "c") + * Array.from(indexedUntilC) // => ["0: a", "1: b"] * ``` * * @category filtering @@ -1747,16 +1700,10 @@ export const filterMapWhile: { * * **Example** (Extracting Some values) * - * ```ts + * ```ts import.meta.vitest * import { Iterable, Option } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual( - * Array.from( - * Iterable.getSomes([Option.some(1), Option.none(), Option.some(2)]) - * ), - * [1, 2] - * ) + * Array.from(Iterable.getSomes([Option.some(1), Option.none(), Option.some(2)])) // => [1, 2] * ``` * * @category filtering @@ -1788,20 +1735,14 @@ export const getSomes = (self: Iterable>): Iterable => { * * **Example** (Extracting failures) * - * ```ts + * ```ts import.meta.vitest * import { Iterable, Result } from "effect" - * import * as assert from "node:assert" - * - * assert.deepStrictEqual( - * Array.from( - * Iterable.getFailures([ - * Result.succeed(1), - * Result.fail("err"), - * Result.succeed(2) - * ]) - * ), - * ["err"] - * ) + * + * Array.from(Iterable.getFailures([ + * Result.succeed(1), + * Result.fail("err"), + * Result.succeed(2) + * ])) // => ["err"] * ``` * * @category filtering @@ -1833,20 +1774,14 @@ export const getFailures = (self: Iterable>): Iterable = * * **Example** (Extracting successes) * - * ```ts + * ```ts import.meta.vitest * import { Iterable, Result } from "effect" - * import * as assert from "node:assert" - * - * assert.deepStrictEqual( - * Array.from( - * Iterable.getSuccesses([ - * Result.succeed(1), - * Result.fail("err"), - * Result.succeed(2) - * ]) - * ), - * [1, 2] - * ) + * + * Array.from(Iterable.getSuccesses([ + * Result.succeed(1), + * Result.fail("err"), + * Result.succeed(2) + * ])) // => [1, 2] * ``` * * @category filtering @@ -1883,18 +1818,18 @@ export const getSuccesses = (self: Iterable>): Iterable * * **Example** (Filtering elements) * - * ```ts + * ```ts import.meta.vitest * import { Iterable } from "effect" * * // Filter even numbers * const numbers = [1, 2, 3, 4, 5, 6] * const evens = Iterable.filter(numbers, (x) => x % 2 === 0) - * console.log(Array.from(evens)) // [2, 4, 6] + * Array.from(evens) // => [2, 4, 6] * * // Filter with index * const items = ["a", "b", "c", "d"] * const oddPositions = Iterable.filter(items, (_, i) => i % 2 === 1) - * console.log(Array.from(oddPositions)) // ["b", "d"] + * Array.from(oddPositions) // => ["b", "d"] * * // Type refinement * const mixed: Array = ["hello", 42, "world", 100] @@ -1902,14 +1837,14 @@ export const getSuccesses = (self: Iterable>): Iterable * mixed, * (x): x is string => typeof x === "string" * ) - * console.log(Array.from(onlyStrings)) // ["hello", "world"] (typed as string[]) + * Array.from(onlyStrings) // => ["hello", "world"] * * // Combine with map * const processed = Iterable.map( * Iterable.filter([1, 2, 3, 4, 5], (x) => x > 2), * (x) => x * 10 * ) - * console.log(Array.from(processed)) // [30, 40, 50] + * Array.from(processed) // => [30, 40, 50] * ``` * * @category filtering @@ -1952,7 +1887,7 @@ export const filter: { * * **Example** (Flat mapping nullable results) * - * ```ts + * ```ts import.meta.vitest * import { Iterable } from "effect" * * // Extract valid elements from nullable function results @@ -1961,7 +1896,7 @@ export const filter: { * const num = parseInt(s) * return isNaN(num) ? null : num * 2 * }) - * console.log(Array.from(parsed)) // [2, 4, 8] + * Array.from(parsed) // => [2, 4, 8] * * // Safe property access * const objects = [ @@ -1971,7 +1906,7 @@ export const filter: { * {} * ] * const values = Iterable.flatMapNullishOr(objects, (obj) => obj.nested?.value) - * console.log(Array.from(values)) // [10, 20] + * Array.from(values) // => [10, 20] * * // Working with Map.get (returns undefined for missing keys) * const map = new Map([ @@ -1981,7 +1916,7 @@ export const filter: { * ]) * const keys = ["a", "x", "b", "y", "c"] * const foundValues = Iterable.flatMapNullishOr(keys, (key) => map.get(key)) - * console.log(Array.from(foundValues)) // [1, 2, 3] + * Array.from(foundValues) // => [1, 2, 3] * ``` * * @category sequencing @@ -2004,21 +1939,21 @@ export const flatMapNullishOr: { * * **Example** (Checking whether some element matches) * - * ```ts + * ```ts import.meta.vitest * import { Iterable } from "effect" * * const numbers = [1, 3, 5, 7, 8] * const hasEven = Iterable.some(numbers, (x) => x % 2 === 0) - * console.log(hasEven) // true (because of 8) + * hasEven // => true * * const allOdd = [1, 3, 5, 7] * const hasEvenInAllOdd = Iterable.some(allOdd, (x) => x % 2 === 0) - * console.log(hasEvenInAllOdd) // false + * hasEvenInAllOdd // => false * * // With index * const letters = ["a", "b", "c"] * const hasElementAtIndex2 = Iterable.some(letters, (_, i) => i === 2) - * console.log(hasElementAtIndex2) // true + * hasElementAtIndex2 // => true * * // Early termination - stops at first match * const infiniteOdds = Iterable.filter(Iterable.range(1), (x) => x % 2 === 1) @@ -2026,7 +1961,7 @@ export const flatMapNullishOr: { * Iterable.take(infiniteOdds, 1000), * (x) => x % 2 === 0 * ) - * console.log(hasEvenInInfiniteOdds) // false (quickly, doesn't check all 1000) + * hasEvenInInfiniteOdds // => false * * // Type guard usage * const mixed: Array = [1, 2, "hello"] @@ -2034,10 +1969,10 @@ export const flatMapNullishOr: { * mixed, * (x): x is string => typeof x === "string" * ) - * console.log(hasString) // true + * hasString // => true * ``` * - * @category elements + * @category predicates * @since 2.0.0 */ export const some: { @@ -2068,21 +2003,21 @@ export const some: { * * **Example** (Unfolding state into values) * - * ```ts + * ```ts import.meta.vitest * import { Iterable, Option } from "effect" * * // Generate Fibonacci sequence * const fibonacci = Iterable.unfold([0, 1], ([a, b]) => Option.some([a, [b, a + b]])) * const first10Fib = Iterable.take(fibonacci, 10) - * console.log(Array.from(first10Fib)) // [0, 1, 1, 2, 3, 5, 8, 13, 21, 34] + * Array.from(first10Fib) // => [0, 1, 1, 2, 3, 5, 8, 13, 21, 34] * * // Generate powers of 2 up to a limit * const powersOf2 = Iterable.unfold(1, (n) => n <= 1000 ? Option.some([n, n * 2]) : Option.none()) - * console.log(Array.from(powersOf2)) // [1, 2, 4, 8, 16, 32, 64, 128, 256, 512] + * Array.from(powersOf2) // => [1, 2, 4, 8, 16, 32, 64, 128, 256, 512] * * // Generate countdown * const countdown = Iterable.unfold(5, (n) => n > 0 ? Option.some([n, n - 1]) : Option.none()) - * console.log(Array.from(countdown)) // [5, 4, 3, 2, 1] + * Array.from(countdown) // => [5, 4, 3, 2, 1] * * // Generate collatz sequence * const collatz = Iterable.unfold(7, (n) => { @@ -2090,7 +2025,7 @@ export const some: { * const next = n % 2 === 0 ? n / 2 : n * 3 + 1 * return Option.some([n, next]) * }) - * console.log(Array.from(collatz)) // [7, 22, 11, 34, 17, 52, 26, 13, 40, 20, 10, 5, 16, 8, 4, 2] + * Array.from(collatz) // => [7, 22, 11, 34, 17, 52, 26, 13, 40, 20, 10, 5, 16, 8, 4, 2] * ``` * * @category constructors @@ -2118,37 +2053,40 @@ export const unfold = (b: B, f: (b: B) => Option): Iterab * * **Example** (Iterating with side effects) * - * ```ts + * ```ts import.meta.vitest * import { Iterable } from "effect" * - * // Print each element + * // Collect each visited element * const numbers = [1, 2, 3, 4, 5] - * Iterable.forEach(numbers, (n) => console.log(n)) - * // Prints: 1, 2, 3, 4, 5 + * const visited: Array = [] + * Iterable.forEach(numbers, (n) => visited.push(n)) + * visited // => [1, 2, 3, 4, 5] * * // Use index in the callback * const letters = ["a", "b", "c"] + * const indexed: Array = [] * Iterable.forEach(letters, (letter, i) => { - * console.log(`${i}: ${letter}`) + * indexed.push(`${i}: ${letter}`) * }) - * // Prints: "0: a", "1: b", "2: c" + * indexed // => ["0: a", "1: b", "2: c"] * * // Side effects with any iterable * const results: Array = [] * Iterable.forEach(Iterable.range(1, 5), (n) => { * results.push(n * n) * }) - * console.log(results) // [1, 4, 9, 16, 25] + * results // => [1, 4, 9, 16, 25] * * // Process in chunks * const data = Iterable.chunksOf([1, 2, 3, 4, 5, 6], 2) + * const processed: Array> = [] * Iterable.forEach(data, (chunk) => { - * console.log(`Processing chunk: ${Array.from(chunk)}`) + * processed.push(Array.from(chunk)) * }) - * // Prints: "Processing chunk: 1,2", "Processing chunk: 3,4", "Processing chunk: 5,6" + * processed // => [[1, 2], [3, 4], [5, 6]] * ``` * - * @category elements + * @category traversing * @since 2.0.0 */ export const forEach: { @@ -2171,18 +2109,17 @@ export const forEach: { * * **Example** (Reducing an iterable) * - * ```ts + * ```ts import.meta.vitest * import { Iterable } from "effect" * * // Sum all numbers * const numbers = [1, 2, 3, 4, 5] * const sum = Iterable.reduce(numbers, 0, (acc, n) => acc + n) - * console.log(sum) // 15 + * sum // => 15 * * // Find maximum value * const values = [3, 1, 4, 1, 5, 9, 2] - * const max = Iterable.reduce(values, -Infinity, Math.max) - * console.log(max) // 9 + * Iterable.reduce(values, -Infinity, (max, value) => Math.max(max, value)) // => 9 * * // Build an object from key-value pairs * const pairs = [["a", 1], ["b", 2], ["c", 3]] as const @@ -2194,7 +2131,7 @@ export const forEach: { * return acc * } * ) - * console.log(obj) // { a: 1, b: 2, c: 3 } + * obj // => { a: 1, b: 2, c: 3 } * * // Use index in the reducer * const letters = ["a", "b", "c"] @@ -2206,7 +2143,7 @@ export const forEach: { * return acc * } * ) - * console.log(indexed) // ["0: a", "1: b", "2: c"] + * indexed // => ["0: a", "1: b", "2: c"] * ``` * * @category folding @@ -2232,20 +2169,20 @@ export const reduce: { * * **Example** (Deduplicating adjacent elements with custom equivalence) * - * ```ts + * ```ts import.meta.vitest * import { Iterable } from "effect" * * // Remove adjacent duplicates with custom equality * const numbers = [1, 1, 2, 2, 3, 1, 1] * const dedupedNumbers = Iterable.dedupeAdjacentWith(numbers, (a, b) => a === b) - * console.log(Array.from(dedupedNumbers)) // [1, 2, 3, 1] + * Array.from(dedupedNumbers) // => [1, 2, 3, 1] * * // Case-insensitive deduplication * const words = ["Hello", "HELLO", "world", "World", "test"] * const caseInsensitive = (a: string, b: string) => * a.toLowerCase() === b.toLowerCase() * const dedupedWords = Iterable.dedupeAdjacentWith(words, caseInsensitive) - * console.log(Array.from(dedupedWords)) // ["Hello", "world", "test"] + * Array.from(dedupedWords) // => ["Hello", "world", "test"] * * // Deduplication by object property * const users = [ @@ -2257,13 +2194,13 @@ export const reduce: { * ] * const byId = (a: typeof users[0], b: typeof users[0]) => a.id === b.id * const dedupedUsers = Iterable.dedupeAdjacentWith(users, byId) - * console.log(Array.from(dedupedUsers).map((u) => u.id)) // [1, 2, 3] + * Array.from(dedupedUsers, (user) => user.id) // => [1, 2, 3] * * // Approximate numeric equality * const floats = [1.0, 1.01, 1.02, 2.0, 2.01, 3.0] * const approxEqual = (a: number, b: number) => Math.abs(a - b) < 0.1 * const dedupedFloats = Iterable.dedupeAdjacentWith(floats, approxEqual) - * console.log(Array.from(dedupedFloats)) // [1.0, 2.0, 3.0] + * Array.from(dedupedFloats) // => [1, 2, 3] * ``` * * @category filtering @@ -2303,18 +2240,18 @@ export const dedupeAdjacentWith: { * * **Example** (Deduplicating adjacent elements) * - * ```ts + * ```ts import.meta.vitest * import { Iterable } from "effect" * * // Remove adjacent duplicate numbers * const numbers = [1, 1, 2, 2, 2, 3, 1, 1] * const deduped = Iterable.dedupeAdjacent(numbers) - * console.log(Array.from(deduped)) // [1, 2, 3, 1] + * Array.from(deduped) // => [1, 2, 3, 1] * * // Remove adjacent duplicate characters * const letters = "aabbccaa" * const dedupedLetters = Iterable.dedupeAdjacent(letters) - * console.log(Array.from(dedupedLetters)) // ["a", "b", "c", "a"] + * Array.from(dedupedLetters) // => ["a", "b", "c", "a"] * * // Works with objects using deep equality * const objects = [ @@ -2325,12 +2262,12 @@ export const dedupeAdjacentWith: { * { type: "A" } * ] * const dedupedObjects = Iterable.dedupeAdjacent(objects) - * console.log(Array.from(dedupedObjects).map((o) => o.type)) // ["A", "B", "A"] + * Array.from(dedupedObjects, (object) => object.type) // => ["A", "B", "A"] * * // Clean up streaming data * const sensorData = [100, 100, 100, 101, 101, 102, 102, 102, 100] * const cleanedData = Iterable.dedupeAdjacent(sensorData) - * console.log(Array.from(cleanedData)) // [100, 101, 102, 100] + * Array.from(cleanedData) // => [100, 101, 102, 100] * ``` * * @category filtering @@ -2343,14 +2280,14 @@ export const dedupeAdjacent: (self: Iterable) => Iterable = dedupeAdjac * * **Example** (Combining cartesian products) * - * ```ts + * ```ts import.meta.vitest * import { Iterable } from "effect" * * // Create coordinate pairs * const xs = [1, 2] * const ys = ["a", "b", "c"] * const coordinates = Iterable.cartesianWith(xs, ys, (x, y) => `(${x},${y})`) - * console.log(Array.from(coordinates)) // ["(1,a)", "(1,b)", "(1,c)", "(2,a)", "(2,b)", "(2,c)"] + * Array.from(coordinates) // => ["(1,a)", "(1,b)", "(1,c)", "(2,a)", "(2,b)", "(2,c)"] * * // Generate all combinations of options * const sizes = ["S", "M", "L"] @@ -2360,18 +2297,13 @@ export const dedupeAdjacent: (self: Iterable) => Iterable = dedupeAdjac * colors, * (size, color) => ({ size, color }) * ) - * console.log(Array.from(products)) - * // [ - * // { size: "S", color: "red" }, { size: "S", color: "blue" }, - * // { size: "M", color: "red" }, { size: "M", color: "blue" }, - * // { size: "L", color: "red" }, { size: "L", color: "blue" } - * // ] + * Array.from(products, ({ color, size }) => `${size}:${color}`) // => ["S:red", "S:blue", "M:red", "M:blue", "L:red", "L:blue"] * * // Mathematical operations on all pairs * const a = [1, 2, 3] * const b = [10, 20] * const mathProducts = Iterable.cartesianWith(a, b, (x, y) => x * y) - * console.log(Array.from(mathProducts)) // [10, 20, 20, 40, 30, 60] + * Array.from(mathProducts) // => [10, 20, 20, 40, 30, 60] * * // Create test data combinations * const userTypes = ["admin", "user"] @@ -2381,11 +2313,10 @@ export const dedupeAdjacent: (self: Iterable) => Iterable = dedupeAdjac * features, * (user, feature) => `${user}_can_${feature}` * ) - * console.log(Array.from(testCases)) - * // ["admin_can_read", "admin_can_write", "admin_can_delete", "user_can_read", "user_can_write", "user_can_delete"] + * Array.from(testCases) // => ["admin_can_read", "admin_can_write", "admin_can_delete", "user_can_read", "user_can_write", "user_can_delete"] * ``` * - * @category elements + * @category combining * @since 2.0.0 */ export const cartesianWith: { @@ -2393,8 +2324,38 @@ export const cartesianWith: { (self: Iterable, that: Iterable, f: (a: A, b: B) => C): Iterable } = dual( 3, - (self: Iterable, that: Iterable, f: (a: A, b: B) => C): Iterable => - flatMap(self, (a) => map(that, (b) => f(a, b))) + (self: Iterable, that: Iterable, f: (a: A, b: B) => C): Iterable => ({ + [Symbol.iterator]() { + const cache: Array = [] + let iterator: Iterator | undefined + let done = false + const replay: Iterable = { + [Symbol.iterator]() { + let index = 0 + return { + next(): IteratorResult { + if (index < cache.length) { + return { done: false, value: cache[index++] } + } + if (done) { + return { done: true, value: undefined } + } + iterator ??= that[Symbol.iterator]() + const result = iterator.next() + if (result.done) { + done = true + return { done: true, value: undefined } + } + cache.push(result.value) + index++ + return result + } + } + } + } + return flatMap(self, (a) => map(replay, (b) => f(a, b)))[Symbol.iterator]() + } + }) ) /** @@ -2402,40 +2363,34 @@ export const cartesianWith: { * * **Example** (Generating cartesian pairs) * - * ```ts + * ```ts import.meta.vitest * import { Iterable } from "effect" * * // All pairs of numbers and letters * const numbers = [1, 2, 3] * const letters = ["a", "b"] * const pairs = Iterable.cartesian(numbers, letters) - * console.log(Array.from(pairs)) - * // [[1, "a"], [1, "b"], [2, "a"], [2, "b"], [3, "a"], [3, "b"]] + * Array.from(pairs) // => [[1, "a"], [1, "b"], [2, "a"], [2, "b"], [3, "a"], [3, "b"]] * * // Generate coordinate grid * const x = [0, 1, 2] * const y = [0, 1] * const grid = Iterable.cartesian(x, y) - * console.log(Array.from(grid)) - * // [[0, 0], [0, 1], [1, 0], [1, 1], [2, 0], [2, 1]] + * Array.from(grid) // => [[0, 0], [0, 1], [1, 0], [1, 1], [2, 0], [2, 1]] * * // All combinations for testing * const browsers = ["chrome", "firefox"] * const devices = ["desktop", "mobile", "tablet"] * const testMatrix = Iterable.cartesian(browsers, devices) - * console.log(Array.from(testMatrix)) - * // [ - * // ["chrome", "desktop"], ["chrome", "mobile"], ["chrome", "tablet"], - * // ["firefox", "desktop"], ["firefox", "mobile"], ["firefox", "tablet"] - * // ] + * Array.from(testMatrix, ([browser, device]) => `${browser}:${device}`) // => ["chrome:desktop", "chrome:mobile", "chrome:tablet", "firefox:desktop", "firefox:mobile", "firefox:tablet"] * * // Empty iterable results in empty cartesian product * const empty = Iterable.empty() * const withEmpty = Iterable.cartesian([1, 2], empty) - * console.log(Array.from(withEmpty)) // [] + * Array.from(withEmpty) // => [] * ``` * - * @category elements + * @category combining * @since 2.0.0 */ export const cartesian: { @@ -2451,11 +2406,10 @@ export const cartesian: { * * **Example** (Counting matching elements) * - * ```ts + * ```ts import.meta.vitest * import { Iterable } from "effect" * - * const result = Iterable.countBy([1, 2, 3, 4, 5], (n) => n % 2 === 0) - * console.log(result) // 2 + * Iterable.countBy([1, 2, 3, 4, 5], (n) => n % 2 === 0) // => 2 * ``` * * @category folding diff --git a/repos/effect/packages/effect/src/JsonPatch.ts b/repos/effect/packages/effect/src/JsonPatch.ts index 5e05cd7d3d..605d24e2b9 100644 --- a/repos/effect/packages/effect/src/JsonPatch.ts +++ b/repos/effect/packages/effect/src/JsonPatch.ts @@ -7,9 +7,8 @@ * * @since 4.0.0 */ -import { format } from "./Formatter.ts" +import * as InternalRecord from "./internal/record.ts" import { escapeToken, unescapeToken } from "./JsonPointer.ts" -import * as Predicate from "./Predicate.ts" import type * as Schema from "./Schema.ts" /** @@ -31,7 +30,7 @@ import type * as Schema from "./Schema.ts" * * **Example** (Defining all operation types) * - * ```ts + * ```ts import.meta.vitest * import { JsonPatch } from "effect" * * const addOp: JsonPatch.JsonPatchOperation = { @@ -50,6 +49,8 @@ import type * as Schema from "./Schema.ts" * path: "/users/0/name", * value: "Bob" * } + * + * Array.of(addOp.op, removeOp.op, replaceOp.op) // => ["add", "remove", "replace"] * ``` * * @see {@link JsonPatch} for the array of operations forming a complete patch @@ -115,7 +116,7 @@ export type JsonPatchOperation = * * **Example** (Defining a multi-operation patch) * - * ```ts + * ```ts import.meta.vitest * import { JsonPatch } from "effect" * * const patch: JsonPatch.JsonPatch = [ @@ -124,8 +125,7 @@ export type JsonPatchOperation = * { op: "remove", path: "/oldField" } * ] * - * const result = JsonPatch.apply(patch, { count: 3, oldField: "value" }) - * // { count: 5, items: ["apple"] } + * JsonPatch.apply(patch, { items: [], count: 3, oldField: "value" }) // => { items: ["apple"], count: 5 } * ``` * * @see {@link JsonPatchOperation} for individual operation types @@ -160,18 +160,16 @@ export type JsonPatch = ReadonlyArray * * **Example** (Computing object diff) * - * ```ts + * ```ts import.meta.vitest * import { JsonPatch } from "effect" * * const oldValue = { users: [{ id: 1, name: "Alice" }], count: 1 } * const newValue = { users: [{ id: 1, name: "Bob" }, { id: 2, name: "Charlie" }], count: 2 } * * const patch = JsonPatch.get(oldValue, newValue) - * // [ - * // { op: "replace", path: "/users/0/name", value: "Bob" }, - * // { op: "add", path: "/users/1", value: { id: 2, name: "Charlie" } }, - * // { op: "replace", path: "/count", value: 2 } - * // ] + * patch[0] // => { op: "replace", path: "/count", value: 2 } + * patch[1] // => { op: "replace", path: "/users/0/name", value: "Bob" } + * patch[2] // => { op: "add", path: "/users/1", value: { id: 2, name: "Charlie" } } * ``` * * @see {@link apply} to apply the generated patch to a document @@ -180,9 +178,18 @@ export type JsonPatch = ReadonlyArray * @since 4.0.0 */ export function get(oldValue: Schema.Json, newValue: Schema.Json): JsonPatch { - if (Object.is(oldValue, newValue)) return [] const patches: Array = [] + getLoop(oldValue, newValue, "", patches) + return patches +} +function getLoop( + oldValue: Schema.Json, + newValue: Schema.Json, + path: string, + patches: Array +): void { + if (Object.is(oldValue, newValue)) return if (Array.isArray(oldValue) && Array.isArray(newValue)) { const len1 = oldValue.length const len2 = newValue.length @@ -190,25 +197,20 @@ export function get(oldValue: Schema.Json, newValue: Schema.Json): JsonPatch { // Compare shared prefix by index const shared = Math.min(len1, len2) for (let i = 0; i < shared; i++) { - const path = `/${i}` - const patch = get(oldValue[i], newValue[i]) - for (const op of patch) { - prefixPathInPlace(op, path) - patches.push(op) - } + getLoop(oldValue[i], newValue[i], `${path}/${i}`, patches) } // Remove from end to start so later indices do not shift. for (let i = len1 - 1; i >= len2; i--) { - patches.push({ op: "remove", path: `/${i}` }) + patches.push({ op: "remove", path: `${path}/${i}` }) } // Add from beginning to end. for (let i = len1; i < len2; i++) { - patches.push({ op: "add", path: `/${i}`, value: newValue[i] }) + patches.push({ op: "add", path: `${path}/${i}`, value: newValue[i] }) } - return patches + return } if (isJsonObject(oldValue) && isJsonObject(newValue)) { @@ -217,29 +219,23 @@ export function get(oldValue: Schema.Json, newValue: Schema.Json): JsonPatch { const allKeys = Array.from(new Set([...keys1, ...keys2])).sort() for (const key of allKeys) { - const esc = escapeToken(key) - const path = `/${esc}` + const keyPath = `${path}/${escapeToken(key)}` const hasKey1 = Object.hasOwn(oldValue, key) const hasKey2 = Object.hasOwn(newValue, key) if (hasKey1 && hasKey2) { - const patch = get(oldValue[key], newValue[key]) - for (const op of patch) { - prefixPathInPlace(op, path) - patches.push(op) - } + getLoop(oldValue[key], newValue[key], keyPath, patches) } else if (!hasKey1 && hasKey2) { - patches.push({ op: "add", path, value: newValue[key] }) - } else if (hasKey1 && !hasKey2) { - patches.push({ op: "remove", path }) + patches.push({ op: "add", path: keyPath, value: newValue[key] }) + } else { + patches.push({ op: "remove", path: keyPath }) } } - return patches + return } - patches.push({ op: "replace", path: "", value: newValue }) - return patches + patches.push({ op: "replace", path, value: newValue }) } /** @@ -266,7 +262,7 @@ export function get(oldValue: Schema.Json, newValue: Schema.Json): JsonPatch { * * **Example** (Applying a patch) * - * ```ts + * ```ts import.meta.vitest * import { JsonPatch } from "effect" * * const document = { items: [1, 2, 3], total: 6 } @@ -275,8 +271,7 @@ export function get(oldValue: Schema.Json, newValue: Schema.Json): JsonPatch { * { op: "replace", path: "/total", value: 10 } * ] * - * const result = JsonPatch.apply(patch, document) - * // { items: [1, 2, 3, 4], total: 10 } + * JsonPatch.apply(patch, document) // => { items: [1, 2, 3, 4], total: 10 } * ``` * * @see {@link get} to generate patches from value differences @@ -288,32 +283,14 @@ export function apply(patch: JsonPatch, oldValue: Schema.Json): Schema.Json { let doc = oldValue for (const op of patch) { - switch (op.op) { - case "replace": { - doc = op.path === "" ? op.value : setAt(doc, op.path, op.value, "replace") - break - } - case "add": { - doc = addAt(doc, op.path, op.value) - break - } - case "remove": { - doc = setAt(doc, op.path, undefined, "remove") - break - } - } + doc = applyOperation(doc, op) } return doc } -// Mutates op.path in place for perf; safe because child ops are freshly created and not shared. -function prefixPathInPlace(op: JsonPatchOperation, parent: string): void { - ;(op as any).path = op.path === "" ? parent : parent + op.path -} - function isJsonObject(value: unknown): value is Schema.JsonObject { - return Predicate.isObject(value) + return typeof value === "object" && value !== null && !Array.isArray(value) } /** @@ -325,7 +302,7 @@ function isJsonObject(value: unknown): value is Schema.JsonObject { function tokenize(pointer: string): Array { if (pointer === "") return [] if (pointer.charCodeAt(0) !== 47 /* "/" */) { - throw new Error(`Invalid JSON Pointer, it must start with "/": ${format(pointer)}`) + throw new Error(`Invalid JSON Pointer, it must start with "/": ${JSON.stringify(pointer)}`) } return pointer.split("/").slice(1).map(unescapeToken) } @@ -338,72 +315,44 @@ function toIndex(token: string): number { return Number(token) } -function addAt(doc: Schema.Json, pointer: string, val: Schema.Json): Schema.Json { - if (pointer === "") return val - - const resolved = resolveParent(doc, pointer) - if (resolved === null) { - throw new Error(`Cannot add at "${pointer}" (parent not found or not a container).`) - } - - const { lastToken, parent, stack } = resolved - - if (Array.isArray(parent)) { - const idx = lastToken === "-" ? parent.length : toIndex(lastToken) - if (idx < 0 || idx > parent.length) throw new Error(`Array index out of bounds at "${pointer}".`) - const updated = parent.slice() - updated.splice(idx, 0, val) - return rebuildFromStack(stack, updated) - } - - if (isJsonObject(parent)) { - const updated = { ...parent } - updated[lastToken] = val - return rebuildFromStack(stack, updated) - } - - throw new Error(`Cannot add at "${pointer}" (parent not found or not a container).`) -} - -function setAt( - doc: Schema.Json, - pointer: string, - val: Schema.Json | undefined, - mode: "replace" | "remove" -): Schema.Json { - if (pointer === "") { - if (mode === "remove" || val === undefined) throw new Error("Unsupported operation at the root") - return val +function applyOperation(doc: Schema.Json, op: JsonPatchOperation): Schema.Json { + if (op.path === "") { + if (op.op === "remove") throw new Error("Unsupported operation at the root") + return op.value } - const resolved = resolveParent(doc, pointer) + const resolved = resolveParent(doc, op.path) if (resolved === null) { - throw new Error(`Cannot ${mode} at "${pointer}" (parent not found or not a container).`) + throw new Error(`Cannot ${op.op} at "${op.path}" (parent not found or not a container).`) } const { lastToken, parent, stack } = resolved if (Array.isArray(parent)) { - if (lastToken === "-") throw new Error(`"-" is not valid for ${mode} at "${pointer}".`) - const idx = toIndex(lastToken) - if (idx < 0 || idx >= parent.length) throw new Error(`Array index out of bounds at "${pointer}".`) + if (lastToken === "-" && op.op !== "add") { + throw new Error(`"-" is not valid for ${op.op} at "${op.path}".`) + } + const index = lastToken === "-" ? parent.length : 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 (mode === "remove") updated.splice(idx, 1) - else updated[idx] = val + if (op.op === "add") updated.splice(index, 0, op.value) + else if (op.op === "remove") updated.splice(index, 1) + else updated[index] = op.value return rebuildFromStack(stack, updated) } if (isJsonObject(parent)) { - if (!Object.hasOwn(parent, lastToken)) { - throw new Error(`Property "${lastToken}" does not exist at "${pointer}".`) + if (op.op !== "add" && !Object.hasOwn(parent, lastToken)) { + throw new Error(`Property "${lastToken}" does not exist at "${op.path}".`) } const updated = { ...parent } - if (mode === "remove") delete updated[lastToken] - else updated[lastToken] = val! + if (op.op === "remove") delete updated[lastToken] + else InternalRecord.assignProperty(updated, lastToken, op.value) return rebuildFromStack(stack, updated) } - throw new Error(`Cannot ${mode} at "${pointer}" (parent not found or not a container).`) + throw new Error(`Cannot ${op.op} at "${op.path}" (parent not found or not a container).`) } type StackEntry = { readonly container: unknown; readonly token: number | string } @@ -424,20 +373,18 @@ function resolveParent( for (let i = 0; i < tokens.length - 1; i++) { const token = tokens[i] - if (cur == null) return null - if (Array.isArray(cur)) { const idx = toIndex(token) - if (idx < 0 || idx >= cur.length) return null + if (idx >= cur.length) return null stack.push({ container: cur, token: idx }) cur = cur[idx] continue } - if (cur && typeof cur === "object") { + if (isJsonObject(cur)) { if (!Object.hasOwn(cur, token)) return null stack.push({ container: cur, token }) - cur = (cur as any)[token] + cur = cur[token] continue } @@ -460,7 +407,7 @@ function rebuildFromStack(stack: ReadonlyArray, newParent: Schema.Js acc = copy } else { const copy = { ...(container as Schema.JsonObject) } - copy[token as string] = acc + InternalRecord.assignProperty(copy, token as string, acc) acc = copy } } diff --git a/repos/effect/packages/effect/src/JsonPointer.ts b/repos/effect/packages/effect/src/JsonPointer.ts index a3d0733817..385d578b6b 100644 --- a/repos/effect/packages/effect/src/JsonPointer.ts +++ b/repos/effect/packages/effect/src/JsonPointer.ts @@ -27,12 +27,12 @@ * * **Example** (Escaping special characters) * - * ```ts + * ```ts import.meta.vitest * import { JsonPointer } from "effect" * - * JsonPointer.escapeToken("a/b") // "a~1b" - * JsonPointer.escapeToken("c~d") // "c~0d" - * JsonPointer.escapeToken("path/to~key") // "path~1to~0key" + * JsonPointer.escapeToken("a/b") // => "a~1b" + * JsonPointer.escapeToken("c~d") // => "c~0d" + * JsonPointer.escapeToken("path/to~key") // => "path~1to~0key" * ``` * * @see {@link unescapeToken} The inverse operation for decoding escaped tokens @@ -63,12 +63,12 @@ export function escapeToken(token: string): string { * * **Example** (Unescaping special characters) * - * ```ts + * ```ts import.meta.vitest * import { JsonPointer } from "effect" * - * JsonPointer.unescapeToken("a~1b") // "a/b" - * JsonPointer.unescapeToken("c~0d") // "c~d" - * JsonPointer.unescapeToken("path~1to~0key") // "path/to~key" + * JsonPointer.unescapeToken("a~1b") // => "a/b" + * JsonPointer.unescapeToken("c~0d") // => "c~d" + * JsonPointer.unescapeToken("path~1to~0key") // => "path/to~key" * ``` * * @see {@link escapeToken} The inverse operation for encoding tokens diff --git a/repos/effect/packages/effect/src/JsonSchema.ts b/repos/effect/packages/effect/src/JsonSchema.ts index 7def9d323c..5fb6c0c300 100644 --- a/repos/effect/packages/effect/src/JsonSchema.ts +++ b/repos/effect/packages/effect/src/JsonSchema.ts @@ -2,13 +2,14 @@ * Helpers for normalizing and converting JSON Schema and OpenAPI schema * documents. Supported inputs include JSON Schema Draft-07, Draft 2020-12, * OpenAPI 3.0, and OpenAPI 3.1; conversions normalize through - * `Document<"draft-2020-12">` before emitting another dialect. The module also - * defines document types, meta-schema constants, OpenAPI component-key helpers, - * and `$ref` resolution utilities. + * `Document<"draft-2020-12">` before emitting another dialect, including + * JSON Schema Draft-04. The module also defines document types, meta-schema + * constants, OpenAPI component-key helpers, and `$ref` resolution utilities. * * @since 4.0.0 */ import * as Arr from "./Array.ts" +import * as InternalRecord from "./internal/record.ts" import { unescapeToken } from "./JsonPointer.ts" import * as Predicate from "./Predicate.ts" import * as Rec from "./Record.ts" @@ -42,9 +43,10 @@ export interface JsonSchema { * * **Details** * - * Supported values are `"draft-07"` for JSON Schema Draft-07, - * `"draft-2020-12"` for JSON Schema Draft 2020-12 and the canonical internal - * form, `"openapi-3.1"` for OpenAPI 3.1, and `"openapi-3.0"` for OpenAPI 3.0. + * Supported values are `"draft-04"` for JSON Schema Draft-04, `"draft-07"` + * for JSON Schema Draft-07, `"draft-2020-12"` for JSON Schema Draft 2020-12 + * and the canonical internal form, `"openapi-3.1"` for OpenAPI 3.1, and + * `"openapi-3.0"` for OpenAPI 3.0. * * @see {@link Document} for a single root schema tagged with a dialect * @see {@link MultiDocument} for multiple root schemas tagged with a dialect @@ -52,7 +54,7 @@ export interface JsonSchema { * @category models * @since 4.0.0 */ -export type Dialect = "draft-07" | "draft-2020-12" | "openapi-3.1" | "openapi-3.0" +export type Dialect = "draft-04" | "draft-07" | "draft-2020-12" | "openapi-3.1" | "openapi-3.0" /** * The JSON Schema primitive type names. @@ -103,12 +105,12 @@ export interface Definitions extends Record {} * The `schema` field holds the root schema *without* the definitions * collection. Root definitions are stored separately in `definitions` and * referenced via `#/$defs/` for Draft-2020-12, `#/definitions/` - * for Draft-07, and `#/components/schemas/` for OpenAPI 3.1 and - * OpenAPI 3.0. + * for Draft-04 and Draft-07, and `#/components/schemas/` for OpenAPI 3.1 + * and OpenAPI 3.0. * * **Example** (Inspecting a parsed document) * - * ```ts + * ```ts import.meta.vitest * import { JsonSchema } from "effect" * * const raw: JsonSchema.JsonSchema = { @@ -118,9 +120,9 @@ export interface Definitions extends Record {} * * const doc = JsonSchema.fromSchemaDraft2020_12(raw) * - * console.log(doc.dialect) // "draft-2020-12" - * console.log(doc.schema) // { type: "string" } - * console.log(doc.definitions) // { Trimmed: { type: "string", minLength: 1 } } + * doc.dialect // => "draft-2020-12" + * doc.schema // => { type: "string" } + * doc.definitions // => { Trimmed: { type: "string", minLength: 1 } } * ``` * * @see {@link MultiDocument} @@ -158,6 +160,20 @@ export interface MultiDocument { readonly definitions: Definitions } +/** + * Represents the `$schema` meta-schema URI for JSON Schema Draft-04. + * + * **When to use** + * + * Use when constructing a Draft-04 JSON Schema document and you need a stable + * value for the root `$schema` field. + * + * @see {@link META_SCHEMA_URI_DRAFT_07} for the Draft-07 `$schema` URI + * @category constants + * @since 4.0.0 + */ +export const META_SCHEMA_URI_DRAFT_04 = "http://json-schema.org/draft-04/schema#" + /** * Represents the `$schema` meta-schema URI for JSON Schema Draft-07. * @@ -169,14 +185,15 @@ export interface MultiDocument { * **Details** * * The exported value is the literal string - * `http://json-schema.org/draft-07/schema`. + * `http://json-schema.org/draft-07/schema#`. * + * @see {@link META_SCHEMA_URI_DRAFT_04} for the Draft-04 `$schema` URI * @see {@link META_SCHEMA_URI_DRAFT_2020_12} for the Draft 2020-12 `$schema` URI * * @category constants * @since 4.0.0 */ -export const META_SCHEMA_URI_DRAFT_07 = "http://json-schema.org/draft-07/schema" +export const META_SCHEMA_URI_DRAFT_07 = "http://json-schema.org/draft-07/schema#" /** * Represents the `$schema` meta-schema URI for JSON Schema Draft 2020-12. @@ -202,6 +219,44 @@ const RE_DEFINITIONS = /^#\/definitions(?=\/|$)/ const RE_DEFS = /^#\/\$defs(?=\/|$)/ const RE_COMPONENTS_SCHEMAS = /^#\/components\/schemas(?=\/|$)/ +const DRAFT_04_COPY_KEYWORDS = new Set([ + "$ref", + "type", + "required", + "enum", + "title", + "description", + "default", + "format", + "pattern", + "minLength", + "maxLength", + "minItems", + "maxItems", + "minProperties", + "maxProperties", + "multipleOf", + "uniqueItems" +]) + +const DRAFT_07_COPY_KEYWORDS = new Set([ + ...DRAFT_04_COPY_KEYWORDS, + "const", + "examples", + "readOnly", + "writeOnly", + "minimum", + "maximum", + "exclusiveMinimum", + "exclusiveMaximum" +]) + +const DRAFT_04_SINGLE_SUBSCHEMA_KEYWORDS = new Set(["not"]) +const DRAFT_07_SINGLE_SUBSCHEMA_KEYWORDS = new Set(["not", "additionalProperties", "propertyNames"]) + +const MAP_SUBSCHEMA_KEYWORDS = new Set(["properties", "patternProperties"]) +const ARRAY_SUBSCHEMA_KEYWORDS = new Set(["allOf", "anyOf", "oneOf"]) + /** * Parses a raw Draft-07 JSON Schema into a `Document<"draft-2020-12">`. * @@ -223,7 +278,7 @@ const RE_COMPONENTS_SCHEMAS = /^#\/components\/schemas(?=\/|$)/ * * **Example** (Parsing a Draft-07 schema) * - * ```ts + * ```ts import.meta.vitest * import { JsonSchema } from "effect" * * const raw: JsonSchema.JsonSchema = { @@ -237,8 +292,8 @@ const RE_COMPONENTS_SCHEMAS = /^#\/components\/schemas(?=\/|$)/ * } * * const doc = JsonSchema.fromSchemaDraft07(raw) - * console.log(doc.dialect) // "draft-2020-12" - * console.log(doc.schema.properties) // { tags: { type: "array", items: { type: "string" } } } + * doc.dialect // => "draft-2020-12" + * doc.schema.properties // => { tags: { type: "array", items: { type: "string" } } } * ``` * * @see {@link fromSchemaDraft2020_12} @@ -258,7 +313,7 @@ export function fromSchemaDraft07(js: JsonSchema): Document<"draft-2020-12"> { } function walk(node: unknown, isRoot: boolean): unknown { - if (Array.isArray(node)) return node.map((v) => walk(v, false)) + if (Array.isArray(node)) return node.map(walkNested) if (!Predicate.isObject(node)) return node const out: Record = {} @@ -269,13 +324,19 @@ export function fromSchemaDraft07(js: JsonSchema): Document<"draft-2020-12"> { for (const k of Object.keys(node)) { const v = node[k] - switch (k) { - case "$ref": - out.$ref = typeof v === "string" ? v.replace(RE_DEFINITIONS, "#/$defs") : v - break + if (k === "$ref") { + out.$ref = typeof v === "string" ? v.replace(RE_DEFINITIONS, "#/$defs") : v + continue + } + if (DRAFT_07_COPY_KEYWORDS.has(k)) { + out[k] = v + continue + } + if (rewriteSubschemaKeyword(out, k, v, walkNested, DRAFT_07_SINGLE_SUBSCHEMA_KEYWORDS)) continue + switch (k) { case "definitions": { - const mapped = walk_object(v, walk) + const mapped = mapObject(v, walkNested) if (isRoot) { definitions = mapped as Definitions | undefined } else { @@ -291,51 +352,6 @@ export function fromSchemaDraft07(js: JsonSchema): Document<"draft-2020-12"> { additionalItems = v break - case "properties": - case "patternProperties": { - const mapped = walk_object(v, walk) - out[k] = mapped ?? v - break - } - - case "additionalProperties": - case "propertyNames": - out[k] = walk(v, false) - break - - case "allOf": - case "anyOf": - case "oneOf": - out[k] = Array.isArray(v) ? v.map((x) => walk(x, false)) : v - break - - case "type": - case "required": - case "enum": - case "const": - case "title": - case "description": - case "default": - case "examples": - case "format": - case "readOnly": - case "writeOnly": - case "pattern": - case "minimum": - case "maximum": - case "exclusiveMinimum": - case "exclusiveMaximum": - case "minLength": - case "maxLength": - case "minItems": - case "maxItems": - case "minProperties": - case "maxProperties": - case "multipleOf": - case "uniqueItems": - out[k] = v - break - default: break } @@ -344,15 +360,19 @@ export function fromSchemaDraft07(js: JsonSchema): Document<"draft-2020-12"> { // Draft-07 tuples -> 2020-12 tuples if (prefixItems !== undefined) { if (Array.isArray(prefixItems)) { - out.prefixItems = prefixItems.map((x) => walk(x, false)) - if (additionalItems !== undefined) out.items = walk(additionalItems, false) + out.prefixItems = prefixItems.map(walkNested) + if (additionalItems !== undefined) out.items = walkNested(additionalItems) } else { - out.items = walk(prefixItems, false) + out.items = walkNested(prefixItems) } } return out } + + function walkNested(node: unknown): unknown { + return walk(node, false) + } } /** @@ -369,7 +389,7 @@ export function fromSchemaDraft07(js: JsonSchema): Document<"draft-2020-12"> { * * **Example** (Parsing a Draft-2020-12 schema) * - * ```ts + * ```ts import.meta.vitest * import { JsonSchema } from "effect" * * const raw: JsonSchema.JsonSchema = { @@ -379,8 +399,8 @@ export function fromSchemaDraft07(js: JsonSchema): Document<"draft-2020-12"> { * } * * const doc = JsonSchema.fromSchemaDraft2020_12(raw) - * console.log(doc.schema) // { type: "number", minimum: 0 } - * console.log(doc.definitions) // { PositiveInt: { type: "integer", minimum: 1 } } + * doc.schema // => { type: "number", minimum: 0 } + * doc.definitions // => { PositiveInt: { type: "integer", minimum: 1 } } * ``` * * @see {@link fromSchemaDraft07} @@ -412,7 +432,7 @@ export function fromSchemaDraft2020_12(js: JsonSchema): Document<"draft-2020-12" * * **Example** (Parsing an OpenAPI 3.1 schema) * - * ```ts + * ```ts import.meta.vitest * import { JsonSchema } from "effect" * * const raw: JsonSchema.JsonSchema = { @@ -423,8 +443,7 @@ export function fromSchemaDraft2020_12(js: JsonSchema): Document<"draft-2020-12" * } * * const doc = JsonSchema.fromSchemaOpenApi3_1(raw) - * // $ref is rewritten to Draft-2020-12 form - * console.log(doc.schema.properties) // { user: { $ref: "#/$defs/User" } } + * doc.schema.properties // => { user: { $ref: "#/$defs/User" } } * ``` * * @see {@link fromSchemaOpenApi3_0} @@ -433,7 +452,7 @@ export function fromSchemaDraft2020_12(js: JsonSchema): Document<"draft-2020-12" * @since 4.0.0 */ export function fromSchemaOpenApi3_1(js: JsonSchema): Document<"draft-2020-12"> { - const schema = rewrite_refs(js, (ref) => ref.replace(RE_COMPONENTS_SCHEMAS, "#/$defs")) as JsonSchema + const schema = rewriteRefs(js, (ref) => ref.replace(RE_COMPONENTS_SCHEMAS, "#/$defs")) return fromSchemaDraft2020_12(schema) } @@ -454,7 +473,7 @@ export function fromSchemaOpenApi3_1(js: JsonSchema): Document<"draft-2020-12"> * * **Example** (Parsing an OpenAPI 3.0 nullable schema) * - * ```ts + * ```ts import.meta.vitest * import { JsonSchema } from "effect" * * const raw: JsonSchema.JsonSchema = { @@ -463,8 +482,7 @@ export function fromSchemaOpenApi3_1(js: JsonSchema): Document<"draft-2020-12"> * } * * const doc = JsonSchema.fromSchemaOpenApi3_0(raw) - * // nullable is expanded into a type array - * console.log(doc.schema.type) // ["string", "null"] + * doc.schema.type // => ["string", "null"] * ``` * * @see {@link fromSchemaOpenApi3_1} @@ -473,7 +491,7 @@ export function fromSchemaOpenApi3_1(js: JsonSchema): Document<"draft-2020-12"> * @since 4.0.0 */ export function fromSchemaOpenApi3_0(schema: JsonSchema): Document<"draft-2020-12"> { - const normalized = normalize_OpenApi3_0_to_Draft07(schema) + const normalized = normalizeOpenApi3_0ToDraft07(schema) return fromSchemaDraft07(normalized as JsonSchema) } @@ -498,7 +516,7 @@ export function fromSchemaOpenApi3_0(schema: JsonSchema): Document<"draft-2020-1 * * **Example** (Converting to Draft-07) * - * ```ts + * ```ts import.meta.vitest * import { JsonSchema } from "effect" * * const doc = JsonSchema.fromSchemaDraft2020_12({ @@ -508,12 +526,13 @@ export function fromSchemaOpenApi3_0(schema: JsonSchema): Document<"draft-2020-1 * }) * * const draft07 = JsonSchema.toDocumentDraft07(doc) - * console.log(draft07.dialect) // "draft-07" - * console.log(draft07.schema.items) // [{ type: "string" }, { type: "number" }] - * console.log(draft07.schema.additionalItems) // { type: "boolean" } + * draft07.dialect // => "draft-07" + * draft07.schema.items // => [{ type: "string" }, { type: "number" }] + * draft07.schema.additionalItems // => { type: "boolean" } * ``` * * @see {@link fromSchemaDraft07} + * @see {@link toDocumentDraft04} for converting to Draft-04 * @see {@link toMultiDocumentOpenApi3_1} * @category encoding * @since 4.0.0 @@ -526,75 +545,165 @@ export function toDocumentDraft07(document: Document<"draft-2020-12">): Document } } -function toSchemaDraft07(schema: JsonSchema): JsonSchema { - return rewrite(schema) - - function rewrite(node: unknown): JsonSchema { - return walk(rewrite_refs(node, (ref) => ref.replace(RE_DEFS, "#/definitions")), true) as JsonSchema +/** + * 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 rewrites `#/$defs/...` refs to `#/definitions/...`, converts tuple + * syntax, lowers `const` to `enum`, converts numeric exclusive bounds to the + * Draft-04 boolean form, and converts both the root schema and all definitions. + * + * **Gotchas** + * + * Unsupported Draft-2020-12 and Draft-07 keywords are dropped. For example, + * `propertyNames` has no general Draft-04 equivalent and is omitted. + * + * **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"> { + const draft07 = toDocumentDraft07(document) + return { + dialect: "draft-04", + schema: toSchemaDraft04(draft07.schema), + definitions: Rec.map(draft07.definitions, toSchemaDraft04) } +} + +function toSchemaDraft04(schema: JsonSchema): JsonSchema { + return walk(schema) as JsonSchema - function walk(node: unknown, _isRoot: boolean): unknown { - if (Array.isArray(node)) return node.map((v) => walk(v, false)) + function walk(node: unknown): unknown { + if (node === true) return {} + if (node === false) return { not: {} } + if (Array.isArray(node)) return node.map(walk) if (!Predicate.isObject(node)) return node const src = node as Record const out: Record = {} - let prefixItems: unknown = undefined - let items: unknown = undefined + let hasConst = false + let constValue: unknown = undefined for (const k of Object.keys(src)) { const v = src[k] + if (DRAFT_04_COPY_KEYWORDS.has(k)) { + out[k] = v + continue + } + if (rewriteSubschemaKeyword(out, k, v, walk, DRAFT_04_SINGLE_SUBSCHEMA_KEYWORDS)) continue + switch (k) { - // We already rewrote $ref via rewrite_refs, so just copy it through. - case "$ref": - case "type": - case "required": - case "enum": case "const": - case "title": - case "description": - case "default": - case "examples": - case "format": - case "pattern": + hasConst = true + constValue = v + break + case "minimum": case "maximum": case "exclusiveMinimum": case "exclusiveMaximum": - case "minLength": - case "maxLength": - case "minItems": - case "maxItems": - case "minProperties": - case "maxProperties": - case "multipleOf": - case "uniqueItems": - out[k] = v break - // Schema maps - case "properties": - case "patternProperties": { - const mapped = walk_object(v, walk) - out[k] = mapped ?? v + case "additionalProperties": + case "additionalItems": + out[k] = typeof v === "boolean" ? v : walk(v) break - } - // Single subschemas - case "additionalProperties": - case "propertyNames": - out[k] = walk(v, false) + case "items": + out.items = Array.isArray(v) ? v.map(walk) : walk(v) break - // Schema arrays - case "allOf": - case "anyOf": - case "oneOf": - out[k] = Array.isArray(v) ? v.map((x) => walk(x, false)) : v + default: break + } + } + + convertExclusiveBound(src, out, "minimum", "exclusiveMinimum", (bound, exclusive) => bound > exclusive) + convertExclusiveBound(src, out, "maximum", "exclusiveMaximum", (bound, exclusive) => bound < exclusive) + + if (hasConst) { + const constSchema = { enum: [constValue] } + if (Object.hasOwn(src, "enum")) { + out.allOf = Array.isArray(out.allOf) ? [...out.allOf, constSchema] : [constSchema] + } else { + out.enum = constSchema.enum + } + } + + return out + } +} + +function convertExclusiveBound( + src: Record, + out: Record, + boundKey: "minimum" | "maximum", + exclusiveKey: "exclusiveMinimum" | "exclusiveMaximum", + isBoundStricter: (bound: number, exclusive: number) => boolean +): void { + const bound = src[boundKey] + const exclusive = src[exclusiveKey] + + if (typeof exclusive === "number") { + if (typeof bound === "number" && isBoundStricter(bound, exclusive)) { + out[boundKey] = bound + } else { + out[boundKey] = exclusive + out[exclusiveKey] = true + } + } else if (bound !== undefined) { + out[boundKey] = bound + } +} + +function toSchemaDraft07(schema: JsonSchema): JsonSchema { + return transformSchema(schema, (src) => { + rewriteSchemaRef(src, (ref) => ref.replace(RE_DEFS, "#/definitions")) + const out: Record = {} + + let prefixItems: unknown = undefined + let items: unknown = undefined + + for (const k of Object.keys(src)) { + const v = src[k] + if (k === "required" && Array.isArray(v) && v.length === 0) continue + if (DRAFT_07_COPY_KEYWORDS.has(k)) { + out[k] = v + continue + } + if ( + MAP_SUBSCHEMA_KEYWORDS.has(k) || + ARRAY_SUBSCHEMA_KEYWORDS.has(k) || + DRAFT_07_SINGLE_SUBSCHEMA_KEYWORDS.has(k) + ) { + out[k] = v + continue + } + + switch (k) { // Tuple handling (2020-12 form) case "prefixItems": prefixItems = v @@ -612,19 +721,25 @@ function toSchemaDraft07(schema: JsonSchema): JsonSchema { // 2020-12 tuples -> Draft-07 tuples if (prefixItems !== undefined) { if (Array.isArray(prefixItems)) { - out.items = prefixItems.map((x) => walk(x, false)) - if (items !== undefined) out.additionalItems = walk(items, false) + out.items = prefixItems + if (items !== undefined) out.additionalItems = items } else { // Non-standard, but keep a reasonable behavior - out.items = walk(prefixItems, false) + out.items = prefixItems } } else if (items !== undefined) { // Regular items schema stays as items - out.items = walk(items, false) + out.items = items + } + + const $ref = out.$ref + if (typeof $ref === "string" && Object.keys(out).length > 1) { + delete out.$ref + out.allOf = [{ $ref }, ...(Array.isArray(out.allOf) ? out.allOf : [])] } return out - } + }) as JsonSchema } /** @@ -638,15 +753,21 @@ function toSchemaDraft07(schema: JsonSchema): JsonSchema { * * **Details** * - * This rewrites `#/$defs/...` refs to `#/components/schemas/...`, sanitizes - * definition keys to match the OpenAPI component key pattern - * (`^[a-zA-Z0-9.\-_]+$`) by replacing invalid characters with `_`, updates all - * `$ref` pointers to use the sanitized keys, and converts all schemas and - * definitions in the multi-document. + * This rewrites local `#/$defs/...` refs to `#/components/schemas/...` and + * sanitizes definition keys to match the OpenAPI component key pattern + * (`^[a-zA-Z0-9.\-_]+$`) by replacing invalid characters with `_`. Valid keys + * are preserved. When sanitized keys collide, the converter appends the first + * available `_1`, `_2`, and subsequent suffix, with allocation independent of + * definition insertion order. All local refs are updated to use the allocated + * keys, including refs to paths within a definition. + * + * **Gotchas** + * + * External refs and local refs outside `#/$defs` are left unchanged. * * **Example** (Converting to OpenAPI 3.1) * - * ```ts + * ```ts import.meta.vitest * import { JsonSchema } from "effect" * * const multi: JsonSchema.MultiDocument<"draft-2020-12"> = { @@ -658,8 +779,8 @@ function toSchemaDraft07(schema: JsonSchema): JsonSchema { * } * * const openapi = JsonSchema.toMultiDocumentOpenApi3_1(multi) - * console.log(openapi.dialect) // "openapi-3.1" - * console.log(openapi.schemas[0]) // { $ref: "#/components/schemas/User" } + * openapi.dialect // => "openapi-3.1" + * openapi.schemas[0] // => { $ref: "#/components/schemas/User" } * ``` * * @see {@link toDocumentDraft07} @@ -668,26 +789,39 @@ function toSchemaDraft07(schema: JsonSchema): JsonSchema { * @since 4.0.0 */ export function toMultiDocumentOpenApi3_1(multiDocument: MultiDocument<"draft-2020-12">): MultiDocument<"openapi-3.1"> { + const definitionKeys = Object.keys(multiDocument.definitions) const keyMap = new Map() - for (const key of Object.keys(multiDocument.definitions)) { - const sanitized = sanitizeOpenApiComponentsSchemasKey(key) - if (sanitized !== key) { - keyMap.set(key, sanitized) - } + const usedKeys = new Set(definitionKeys.filter((key) => VALID_OPEN_API_COMPONENTS_SCHEMAS_KEY_REGEXP.test(key))) + const invalidKeys = definitionKeys + .filter((key) => !VALID_OPEN_API_COMPONENTS_SCHEMAS_KEY_REGEXP.test(key)) + .sort() + .map((key) => [key, sanitizeOpenApiComponentsSchemasKey(key)] as const) + for (const [key, base] of invalidKeys) { + if (usedKeys.has(base)) continue + usedKeys.add(base) + keyMap.set(key, base) + } + for (const [key, base] of invalidKeys) { + if (keyMap.has(key)) continue + let candidate: string + let suffix = 0 + do candidate = `${base}_${++suffix}` + while (usedKeys.has(candidate)) + usedKeys.add(candidate) + keyMap.set(key, candidate) } function rewrite(schema: JsonSchema): JsonSchema { - return rewrite_refs(schema, ($ref) => { - const tokens = $ref.split("/") - if (tokens.length > 0) { - const identifier = unescapeToken(tokens[tokens.length - 1]) - const sanitized = keyMap.get(identifier) - if (sanitized !== undefined) { - $ref = tokens.slice(0, -1).join("/") + "/" + sanitized - } - } - return $ref.replace(RE_DEFS, "#/components/schemas") - }) as JsonSchema + return rewriteRefs(schema, ($ref) => { + if (!$ref.startsWith("#/$defs/")) return $ref + + const path = $ref.slice("#/$defs/".length) + const separatorIndex = path.indexOf("/") + const token = separatorIndex === -1 ? path : path.slice(0, separatorIndex) + const rest = separatorIndex === -1 ? "" : path.slice(separatorIndex) + const key = keyMap.get(unescapeToken(token)) ?? token + return `#/components/schemas/${key}${rest}` + }) } return { @@ -710,64 +844,95 @@ export const VALID_OPEN_API_COMPONENTS_SCHEMAS_KEY_REGEXP = /^[a-zA-Z0-9.\-_]+$/ * @internal */ export function sanitizeOpenApiComponentsSchemasKey(s: string): string { - if (s.length === 0) return "_" - if (VALID_OPEN_API_COMPONENTS_SCHEMAS_KEY_REGEXP.test(s)) return s - - const out: Array = [] - - for (const ch of s) { - const code = ch.codePointAt(0) - if ( - code !== undefined && - ((code >= 48 && code <= 57) || // 0-9 - (code >= 65 && code <= 90) || // A-Z - (code >= 97 && code <= 122) || // a-z - code === 46 || // . - code === 45 || // - - code === 95) // _ - ) { - out.push(ch) - } else { - out.push("_") - } - } - - return out.join("") + return s.length === 0 ? "_" : s.replace(/[^a-zA-Z0-9._-]/gu, "_") } -function rewrite_refs(node: unknown, f: ($ref: string) => string): unknown { - if (Array.isArray(node)) return node.map((v) => rewrite_refs(v, f)) - if (!Predicate.isObject(node)) return node - - const out: Record = {} +function transformSchema( + node: unknown, + transform: (schema: Record) => Record +): unknown { + return walk(node) - for (const k of Object.keys(node)) { - const v = node[k] + function walk(node: unknown): unknown { + if (!Predicate.isObject(node)) return node - if (k === "$ref") { - out[k] = typeof v === "string" ? f(v) : v - } else if (Array.isArray(v) || Predicate.isObject(v)) { - out[k] = rewrite_refs(v, f) - } else { - out[k] = v + 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, walk) ?? value + break + case "allOf": + case "anyOf": + case "oneOf": + case "prefixItems": + transformed = Array.isArray(value) ? value.map(walk) : 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) + } + InternalRecord.assignProperty(out, key, transformed) } + return transform(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) => string +): Record { + if (typeof schema.$ref === "string") { + InternalRecord.assignProperty(schema, "$ref", rewrite(schema.$ref)) } + return schema +} - return out +function mapObject(value: unknown, f: (node: unknown) => unknown): Record | undefined { + return Predicate.isObject(value) ? Rec.map(value, f) : undefined } -function walk_object( +function rewriteSubschemaKeyword( + out: Record, + key: string, value: unknown, - walk: (node: unknown, isRoot: boolean) => unknown -): Record | undefined { - if (!Predicate.isObject(value)) return undefined - const out: Record = {} - for (const k of Object.keys(value)) out[k] = walk(value[k], false) - return out + rewrite: (node: unknown) => unknown, + singleKeywords: ReadonlySet +): boolean { + if (MAP_SUBSCHEMA_KEYWORDS.has(key)) { + out[key] = mapObject(value, rewrite) ?? value + return true + } + if (ARRAY_SUBSCHEMA_KEYWORDS.has(key)) { + out[key] = Array.isArray(value) ? value.map(rewrite) : value + return true + } + if (!singleKeywords.has(key)) return false + out[key] = rewrite(value) + return true } -function normalize_OpenApi3_0_to_Draft07(node: unknown): unknown { - if (Array.isArray(node)) return node.map(normalize_OpenApi3_0_to_Draft07) +function normalizeOpenApi3_0ToDraft07(node: unknown): unknown { + if (Array.isArray(node)) return node.map(normalizeOpenApi3_0ToDraft07) if (!Predicate.isObject(node)) return node const src = node as Record @@ -776,67 +941,67 @@ function normalize_OpenApi3_0_to_Draft07(node: unknown): unknown { for (const k of Object.keys(src)) { const v = src[k] if (k === "$ref" && typeof v === "string") { - out[k] = v.replace(RE_COMPONENTS_SCHEMAS, "#/definitions") + InternalRecord.assignProperty(out, k, v.replace(RE_COMPONENTS_SCHEMAS, "#/definitions")) } else if (k === "example") { if (src.examples === undefined) { out.examples = [v] } } else if (Array.isArray(v) || Predicate.isObject(v)) { - out[k] = normalize_OpenApi3_0_to_Draft07(v) + InternalRecord.assignProperty(out, k, normalizeOpenApi3_0ToDraft07(v)) } else { - out[k] = v + InternalRecord.assignProperty(out, k, v) } } // Draft-04-style numeric exclusivity booleans - out = adjust_exclusivity(out) + out = adjustExclusivity(out) // OpenAPI 3.0 nullable if (out.nullable === true) { - out = apply_nullable(out) + out = applyNullable(out) } delete out.nullable return out } -function adjust_exclusivity(node: Record): Record { - let out = node - - if (typeof out.exclusiveMinimum === "boolean") { - if (out.exclusiveMinimum === true && typeof out.minimum === "number") { - out = { ...out, exclusiveMinimum: out.minimum } - delete out.minimum - } else { - out = { ...out } - delete out.exclusiveMinimum - } - } +function adjustExclusivity(node: Record): Record { + return adjustExclusiveBound( + adjustExclusiveBound(node, "minimum", "exclusiveMinimum"), + "maximum", + "exclusiveMaximum" + ) +} - if (typeof out.exclusiveMaximum === "boolean") { - if (out.exclusiveMaximum === true && typeof out.maximum === "number") { - out = { ...out, exclusiveMaximum: out.maximum } - delete out.maximum - } else { - out = { ...out } - delete out.exclusiveMaximum - } +function adjustExclusiveBound( + node: Record, + boundKey: "minimum" | "maximum", + exclusiveKey: "exclusiveMinimum" | "exclusiveMaximum" +): Record { + const exclusive = node[exclusiveKey] + if (typeof exclusive !== "boolean") return node + + const out = { ...node } + if (exclusive && typeof node[boundKey] === "number") { + out[exclusiveKey] = node[boundKey] + delete out[boundKey] + } else { + delete out[exclusiveKey] } - return out } -function apply_nullable(node: Record): Record { +function applyNullable(node: Record): Record { // enum widening if (Array.isArray(node.enum)) { - return widen_type({ + return widenType({ ...node, enum: node.enum.includes(null) ? node.enum : [...node.enum, null] }) } // type widening - if (node.type !== undefined) return widen_type(node) + if (node.type !== undefined) return widenType(node) // const === null if (node.const === null) return node @@ -845,7 +1010,7 @@ function apply_nullable(node: Record): Record return { anyOf: [node, { type: "null" }] } } -function widen_type(node: Record): Record { +function widenType(node: Record): Record { const t = node.type if (typeof t === "string") return t === "null" ? node : { ...node, type: [t, "null"] } if (Array.isArray(t)) return t.includes("null") ? node : { ...node, type: [...t, "null"] } @@ -872,18 +1037,15 @@ function widen_type(node: Record): Record { * * **Example** (Resolving a $ref) * - * ```ts + * ```ts import.meta.vitest * import { JsonSchema } from "effect" * * const definitions: JsonSchema.Definitions = { * User: { type: "object", properties: { name: { type: "string" } } } * } * - * const result = JsonSchema.resolve$ref("#/$defs/User", definitions) - * console.log(result) // { type: "object", properties: { name: { type: "string" } } } - * - * const missing = JsonSchema.resolve$ref("#/$defs/Unknown", definitions) - * console.log(missing) // undefined + * JsonSchema.resolve$ref("#/$defs/User", definitions) // => { type: "object", properties: { name: { type: "string" } } } + * JsonSchema.resolve$ref("#/$defs/Unknown", definitions) // => undefined * ``` * * @see {@link resolveTopLevel$ref} @@ -893,13 +1055,8 @@ function widen_type(node: Record): Record { */ export function resolve$ref($ref: string, definitions: Definitions): JsonSchema | undefined { const tokens = $ref.split("/") - if (tokens.length > 0) { - const identifier = unescapeToken(tokens[tokens.length - 1]) - const definition = definitions[identifier] - if (definition !== undefined) { - return definition - } - } + const identifier = unescapeToken(tokens[tokens.length - 1]) + if (Object.hasOwn(definitions, identifier)) return definitions[identifier] } /** @@ -917,7 +1074,7 @@ export function resolve$ref($ref: string, definitions: Definitions): JsonSchema * * **Example** (Resolving a top-level $ref) * - * ```ts + * ```ts import.meta.vitest * import { JsonSchema } from "effect" * * const doc: JsonSchema.Document<"draft-2020-12"> = { @@ -929,7 +1086,7 @@ export function resolve$ref($ref: string, definitions: Definitions): JsonSchema * } * * const resolved = JsonSchema.resolveTopLevel$ref(doc) - * console.log(resolved.schema) // { type: "object", properties: { name: { type: "string" } } } + * resolved.schema // => { type: "object", properties: { name: { type: "string" } } } * ``` * * @see {@link resolve$ref} diff --git a/repos/effect/packages/effect/src/Latch.ts b/repos/effect/packages/effect/src/Latch.ts index 418c78e444..62e73d7364 100644 --- a/repos/effect/packages/effect/src/Latch.ts +++ b/repos/effect/packages/effect/src/Latch.ts @@ -28,18 +28,18 @@ import * as internal from "./internal/effect.ts" * * **Example** (Coordinating fibers with a latch) * - * ```ts - * import { Effect, Latch } from "effect" + * ```ts import.meta.vitest + * import { Effect, Fiber, Latch } from "effect" * * // Create and use a latch for coordination between fibers * const program = Effect.gen(function*() { * const latch = yield* Latch.make() - * - * // Wait for the latch to be opened - * yield* latch.await - * - * return "Latch was opened!" + * const waiter = yield* Effect.forkChild(latch.await.pipe(Effect.as("opened"))) + * yield* latch.open + * return yield* Fiber.join(waiter) * }) + * + * await Effect.runPromise(program) // => "opened" * ``` * * @see {@link make} for creating a latch inside Effect code @@ -138,24 +138,19 @@ export interface Latch { * * **Example** (Creating a latch unsafely) * - * ```ts - * import { Effect, Latch } from "effect" + * ```ts import.meta.vitest + * import { Effect, Fiber, Latch } from "effect" * * const latch = Latch.makeUnsafe(false) + * const waiter = latch.await.pipe(Effect.as("opened")) * - * const waiter = Effect.gen(function*() { - * yield* Effect.log("Waiting for latch to open...") - * yield* latch.await - * yield* Effect.log("Latch opened! Continuing...") - * }) - * - * const opener = Effect.gen(function*() { - * yield* Effect.sleep("2 seconds") - * yield* Effect.log("Opening latch...") + * const program = Effect.gen(function*() { + * const fiber = yield* Effect.forkChild(waiter) * yield* latch.open + * return yield* Fiber.join(fiber) * }) * - * const program = Effect.all([waiter, opener]) + * await Effect.runPromise(program) // => "opened" * ``` * * @see {@link make} for creating a latch inside Effect code @@ -178,26 +173,19 @@ export const makeUnsafe: (open?: boolean | undefined) => Latch = internal.makeLa * * **Example** (Creating a latch) * - * ```ts - * import { Effect, Latch } from "effect" + * ```ts import.meta.vitest + * import { Effect, Fiber, Latch } from "effect" * * const program = Effect.gen(function*() { * const latch = yield* Latch.make(false) + * const waiter = latch.await.pipe(Effect.as("opened")) * - * const waiter = Effect.gen(function*() { - * yield* Effect.log("Waiting for latch to open...") - * yield* latch.await - * yield* Effect.log("Latch opened! Continuing...") - * }) - * - * const opener = Effect.gen(function*() { - * yield* Effect.sleep("2 seconds") - * yield* Effect.log("Opening latch...") - * yield* latch.open - * }) - * - * yield* Effect.all([waiter, opener]) + * const fiber = yield* Effect.forkChild(waiter) + * yield* latch.open + * return yield* Fiber.join(fiber) * }) + * + * await Effect.runPromise(program) // => "opened" * ``` * * @see {@link makeUnsafe} for synchronous allocation outside Effect code @@ -388,7 +376,7 @@ export const whenOpen: { * * Use to check the state of the latch without suspending or changing its state. * - * @category getters + * @category predicates * @since 4.0.0 */ export const isOpen = (self: Latch): boolean => self.isOpen() diff --git a/repos/effect/packages/effect/src/Layer.ts b/repos/effect/packages/effect/src/Layer.ts index 3b590a5092..0d1eb01d81 100644 --- a/repos/effect/packages/effect/src/Layer.ts +++ b/repos/effect/packages/effect/src/Layer.ts @@ -192,7 +192,7 @@ const MemoMapTypeId = "~effect/Layer/MemoMap" * * **Example** (Sharing layer construction with a memo map) * - * ```ts + * ```ts import.meta.vitest * import { Context, Effect, Layer } from "effect" * * class Database extends Context.Service "result" * ``` * * @category models @@ -251,7 +254,7 @@ const memoMapReuse = ( * * **Example** (Checking whether a value is a layer) * - * ```ts + * ```ts import.meta.vitest * import { Context, Effect, Layer } from "effect" * * class Database extends Context.Service( * }) * const notALayer = { someProperty: "value" } * - * console.log(Layer.isLayer(dbLayer)) // true - * console.log(Layer.isLayer(notALayer)) // false + * Layer.isLayer(dbLayer) // => true + * Layer.isLayer(notALayer) // => false * ``` * - * @category getters + * @category guards * @since 2.0.0 */ export const isLayer = (u: unknown): u is Layer => hasProperty(u, TypeId) @@ -305,7 +308,7 @@ const fromBuildUnsafe = ( * * **Example** (Constructing a layer from a build function) * - * ```ts + * ```ts import.meta.vitest * import { Context, Effect, Layer } from "effect" * * class Database extends Context.Service( * }) * ) * ) + * + * const program = Database.use((database) => database.query("SELECT 1")) + * Effect.runSync(Effect.provide(program, databaseLayer)) // => "result" * ``` * * @category constructors @@ -349,7 +355,7 @@ export const fromBuild = ( * * **Example** (Memoizing layer construction) * - * ```ts + * ```ts import.meta.vitest * import { Context, Effect, Layer } from "effect" * * class Database extends Context.Service( * }) * ) * ) + * + * const program = Database.use((database) => database.query("SELECT 1")) + * Effect.runSync(Effect.provide(program, databaseLayer)) // => "result" * ``` * * @category constructors @@ -438,11 +447,13 @@ class MemoMapImpl implements MemoMap { scope: Scope.Scope, build: (memoMap: MemoMap, scope: Scope.Scope) => Effect, E, RIn> ): Effect, E, RIn> { - const existing = this.get(layer, scope) - if (existing) { - return existing - } - return memoMapBuild(this, layer, scope, build) + return internalEffect.suspend(() => { + const existing = this.get(layer, scope) + if (existing) { + return existing + } + return memoMapBuild(this, layer, scope, build) + }) } } @@ -451,7 +462,7 @@ class MemoMapImpl implements MemoMap { * * **Example** (Creating a memo map unsafely) * - * ```ts + * ```ts import.meta.vitest * import { Context, Effect, Layer } from "effect" * * class Database extends Context.Service "result" * ``` * - * @category memo map + * @category constructors * @since 4.0.0 */ export const makeMemoMapUnsafe = (): MemoMap => new MemoMapImpl() @@ -491,7 +505,7 @@ export const makeMemoMapUnsafe = (): MemoMap => new MemoMapImpl() * @see {@link forkMemoMap} for allocating the child memo map inside `Effect` * @see {@link makeMemoMapUnsafe} for creating a root memo map without a parent * - * @category memo map + * @category constructors * @since 4.0.0 */ export const forkMemoMapUnsafe = (parent: MemoMap): MemoMap => new MemoMapImpl(parent) @@ -501,7 +515,7 @@ export const forkMemoMapUnsafe = (parent: MemoMap): MemoMap => new MemoMapImpl(p * * **Example** (Creating a memo map in an effect) * - * ```ts + * ```ts import.meta.vitest * import { Context, Effect, Layer } from "effect" * * class Database extends Context.Service new MemoMapImpl(p * * return Context.get(context, Database) * }) + * + * const database = Effect.runSync(Effect.scoped(program)) + * Effect.runSync(database.query("SELECT 1")) // => "result" * ``` * - * @category memo map + * @category constructors * @since 2.0.0 */ export const makeMemoMap: Effect = internalEffect.sync(makeMemoMapUnsafe) @@ -541,7 +558,7 @@ export const makeMemoMap: Effect = internalEffect.sync(makeMemoMapUnsaf * @see {@link forkMemoMapUnsafe} for the synchronous constructor variant * @see {@link buildWithMemoMap} for building layers with an explicit memo map * - * @category memo map + * @category constructors * @since 4.0.0 */ export const forkMemoMap = (parent: MemoMap): Effect => internalEffect.sync(() => forkMemoMapUnsafe(parent)) @@ -561,14 +578,14 @@ export const forkMemoMap = (parent: MemoMap): Effect => internalEffect. * * @see {@link MemoMap} the memoization map type wrapped by this service * - * @category models + * @category services * @since 3.13.0 */ export class CurrentMemoMap extends Context.Service()("effect/Layer/CurrentMemoMap") { - static getOrCreate: (self: Context.Context) => MemoMap = Context.getOrElse( - this, - makeMemoMapUnsafe - ) + static forkOrCreate(self: Context.Context): MemoMap { + const current = Context.getOrUndefined(self, CurrentMemoMap) + return current ? forkMemoMapUnsafe(current) : makeMemoMapUnsafe() + } } /** @@ -577,7 +594,7 @@ export class CurrentMemoMap extends Context.Service()(" * * **Example** (Building layers with an explicit memo map) * - * ```ts + * ```ts import.meta.vitest * import { Context, Effect, Layer } from "effect" * * class Database extends Context.Service()(" * readonly log: (msg: string) => Effect.Effect * }>()("Logger") {} * + * const logs: Array = [] + * * // Build layers with explicit memoization control * const program = Effect.gen(function*() { * const memoMap = yield* Layer.makeMemoMap @@ -601,7 +620,7 @@ export class CurrentMemoMap extends Context.Service()(" * * // Build logger layer with same memoization (reuses memo if same layer) * const loggerLayer = Layer.succeed(Logger, { - * log: Effect.fn("Logger.log")((msg: string) => Effect.sync(() => console.log(msg))) + * log: Effect.fn("Logger.log")((msg: string) => Effect.sync(() => logs.push(msg))) * }) * const loggerContext = yield* Layer.buildWithMemoMap( * loggerLayer, @@ -614,9 +633,13 @@ export class CurrentMemoMap extends Context.Service()(" * logger: Context.get(loggerContext, Logger) * } * }) + * + * const services = Effect.runSync(Effect.scoped(program)) + * Effect.runSync(services.logger.log("ready")) + * logs // => ["ready"] * ``` * - * @category memo map + * @category destructors * @since 2.0.0 */ export const buildWithMemoMap: { @@ -645,7 +668,7 @@ export const buildWithMemoMap: { * * **Example** (Building a layer into a context) * - * ```ts + * ```ts import.meta.vitest * import { Context, Effect, Layer } from "effect" * * class Database extends Context.Service "result" * ``` * * @category destructors @@ -677,7 +702,7 @@ export const build = ( core.withFiber((fiber) => buildWithMemoMap( self, - CurrentMemoMap.getOrCreate(fiber.context), + CurrentMemoMap.forkOrCreate(fiber.context), Context.getUnsafe(fiber.context, Scope.Scope) ) ) @@ -697,22 +722,24 @@ export const build = ( * * **Example** (Building a layer with an explicit scope) * - * ```ts + * ```ts import.meta.vitest * import { Context, Effect, Layer, Scope } from "effect" * * class Database extends Context.Service Effect.Effect * }>()("Database") {} * + * const logs: Array = [] + * * // Build a layer with explicit scope control * const program = Effect.gen(function*() { * const scope = yield* Effect.scope * * const dbLayer = Layer.effect(Database, Effect.gen(function*() { - * console.log("Initializing database...") + * logs.push("Initializing database...") * yield* Scope.addFinalizer( * scope, - * Effect.sync(() => console.log("Database closed")) + * Effect.sync(() => logs.push("Database closed")) * ) * return { query: Effect.fn("Database.query")((sql: string) => Effect.succeed(`Result: ${sql}`)) } * })) @@ -724,6 +751,9 @@ export const build = ( * return yield* database.query("SELECT * FROM users") * // Database will be closed when scope is closed * }) + * + * Effect.runSync(Effect.scoped(program)) // => "Result: SELECT * FROM users" + * logs // => ["Initializing database...", "Database closed"] * ``` * * @category destructors @@ -739,7 +769,7 @@ export const buildWithScope: { core.withFiber((fiber) => buildWithMemoMap( self, - CurrentMemoMap.getOrCreate(fiber.context), + CurrentMemoMap.forkOrCreate(fiber.context), scope ) )) @@ -755,7 +785,7 @@ export const buildWithScope: { * * **Example** (Creating a layer from a service implementation) * - * ```ts + * ```ts import.meta.vitest * import { Context, Effect, Layer } from "effect" * * class Database extends Context.Service Effect.succeed(`Query result: ${sql}`)) * }) + * const program = Database.use((database) => database.query("SELECT 1")) + * Effect.runSync(Effect.provide(program, DatabaseLive)) // => "Query result: SELECT 1" * ``` * * @see {@link sync} for constructing layers from lazy values @@ -798,7 +830,7 @@ export const succeed: { * * **Example** (Providing multiple services from a context) * - * ```ts + * ```ts import.meta.vitest * import { Context, Effect, Layer } from "effect" * * class Database extends Context.Service Effect.Effect * }>()("Logger") {} * + * const logs: Array = [] * const context = Context.make(Database, { * query: Effect.fn("Database.query")((sql: string) => Effect.succeed("result")) * }).pipe( * Context.add(Logger, { - * log: (msg: string) => Effect.sync(() => console.log(msg)) + * log: (msg: string) => Effect.sync(() => logs.push(msg)) * }) * ) * * const layer = Layer.succeedContext(context) + * const program = Logger.use((logger) => logger.log("ready")) + * Effect.runSync(Effect.provide(program, layer)) + * logs // => ["ready"] * ``` * * @see {@link succeed} for providing a single service from a value @@ -838,14 +874,12 @@ export const succeedContext = (context: Context.Context): Layer => * * **Example** (Disabling optional lifecycle work) * - * ```ts - * import { Console, Layer } from "effect" - * - * declare const flag: boolean + * ```ts import.meta.vitest + * import { Context, Effect, Layer, Option } from "effect" * - * const StartupLogLive = flag - * ? Layer.effectDiscard(Console.log("application starting")) - * : Layer.empty + * const Service = Context.Service("Service") + * const context = Effect.runSync(Effect.scoped(Layer.build(Layer.empty))) + * Context.getOption(context, Service) // => Option.none() * ``` * * @see {@link effectDiscard} for running an effect while providing no services @@ -870,7 +904,7 @@ export const empty: Layer = succeedContext(Context.empty()) * * **Example** (Lazily providing a service) * - * ```ts + * ```ts import.meta.vitest * import { Context, Effect, Layer } from "effect" * * class Database extends Context.Service = succeedContext(Context.empty()) * const layer = Layer.sync(Database, () => ({ * query: (sql: string) => Effect.succeed(`Query: ${sql}`) * })) + * const program = Database.use((database) => database.query("SELECT 1")) + * Effect.runSync(Effect.provide(program, layer)) // => "Query: SELECT 1" * ``` * * @see {@link succeed} for constructing layers from static values @@ -912,7 +948,7 @@ export const sync: { * * **Example** (Lazily providing a context) * - * ```ts + * ```ts import.meta.vitest * import { Context, Effect, Layer } from "effect" * * class Database extends Context.Service Effect.succeed(`Query: ${sql}`) * }) * ) + * const program = Database.use((database) => database.query("SELECT 1")) + * Effect.runSync(Effect.provide(program, layer)) // => "Query: SELECT 1" * ``` * * @see {@link sync} for lazily providing a single service @@ -951,7 +989,7 @@ export const syncContext = (evaluate: LazyArg>): Layer * * **Example** (Creating a layer from an effect) * - * ```ts + * ```ts import.meta.vitest * import { Context, Effect, Layer } from "effect" * * class Database extends Context.Service(evaluate: LazyArg>): Layer * query: (sql: string) => Effect.succeed(`Query: ${sql}`) * })) * ) + * const program = Database.use((database) => database.query("SELECT 1")) + * Effect.runSync(Effect.provide(program, layer)) // => "Query: SELECT 1" * ``` * * @see {@link effectContext} for effectfully providing multiple services @@ -1008,7 +1048,7 @@ const effectImpl = ( * * **Example** (Creating a layer from an effectful context) * - * ```ts + * ```ts import.meta.vitest * import { Context, Effect, Layer } from "effect" * * class Database extends Context.Service< @@ -1021,6 +1061,8 @@ const effectImpl = ( * query: (sql: string) => Effect.succeed(`Query: ${sql}`) * })) * ) + * const program = Database.use((database) => database.query("SELECT 1")) + * Effect.runSync(Effect.provide(program, layer)) // => "Query: SELECT 1" * ``` * * @see {@link effect} for effectfully providing a single service @@ -1043,14 +1085,17 @@ export const effectContext = ( * * **Example** (Running an effect during layer construction) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Layer } from "effect" * + * const logs: Array = [] * const initLayer = Layer.effectDiscard( * Effect.sync(() => { - * console.log("Initializing application...") + * logs.push("Initializing application...") * }) * ) + * Effect.runSync(Effect.scoped(Layer.build(initLayer))) + * logs // => ["Initializing application..."] * ``` * * @see {@link empty} for a no-op layer that performs no construction work @@ -1071,8 +1116,8 @@ export const effectDiscard = (effect: Effect): Layer()("Config") {} * @@ -1083,6 +1128,7 @@ export const effectDiscard = (effect: Effect): Layer "https://api.example.com" * ``` * * @category constructors @@ -1106,7 +1152,7 @@ export const suspend = (evaluate: LazyArg>): Layer(evaluate: LazyArg>): Layer database.query("SELECT 1")) + * Effect.runSync(Effect.provide(program, unwrappedLayer)) // => "result" * ``` * * @category converting @@ -1165,7 +1213,7 @@ const mergeAllEffect = , ...Array, ...Array Effect.succeed("result")) * }) + * const logs: Array = [] * const loggerLayer = Layer.succeed(Logger, { - * log: Effect.fn("Logger.log")((msg: string) => Effect.sync(() => console.log(msg))) + * log: Effect.fn("Logger.log")((msg: string) => Effect.sync(() => logs.push(msg))) * }) * * const mergedLayer = Layer.mergeAll(dbLayer, loggerLayer) + * const program = Logger.use((logger) => logger.log("ready")) + * Effect.runSync(Effect.provide(program, mergedLayer)) + * logs // => ["ready"] * ``` * * @see {@link merge} for merging one layer with another layer or array @@ -1216,7 +1268,7 @@ export const mergeAll = , ...Array, ...Array Effect.succeed("result")) * }) * const loggerLayer = Layer.succeed(Logger, { - * log: Effect.fn("Logger.log")((msg: string) => Effect.sync(() => console.log(msg))) + * log: Effect.fn("Logger.log")((_msg: string) => Effect.void) * }) * * const mergedLayer = Layer.merge(dbLayer, loggerLayer) + * const program = Database.use((database) => database.query("SELECT 1")) + * Effect.runSync(Effect.provide(program, mergedLayer)) // => "result" * ``` * * @see {@link mergeAll} for merging several layers at once @@ -1310,7 +1364,7 @@ const provideWith = ( * * **Example** (Providing layer dependencies) * - * ```ts + * ```ts import.meta.vitest * import { Context, Effect, Layer } from "effect" * * class Database extends Context.Service Effect.succeed(`DB: ${sql}`)) * }) * + * const logs: Array = [] * const loggerLayer = Layer.succeed(Logger, { - * log: Effect.fn("Logger.log")((msg: string) => Effect.sync(() => console.log(`[LOG] ${msg}`))) + * log: Effect.fn("Logger.log")((msg: string) => Effect.sync(() => logs.push(`[LOG] ${msg}`))) * }) * * // UserService depends on Database and Logger @@ -1365,6 +1420,8 @@ const provideWith = ( * }).pipe( * Effect.provide(userServiceWithDependencies) * ) + * Effect.runSync(program) // => { id: "123", name: "DB: SELECT * FROM users WHERE id = 123" } + * logs // => ["[LOG] Looking up user 123"] * ``` * * @see {@link provideMerge} for retaining the dependency services @@ -1419,7 +1476,7 @@ export const provide: { * * **Example** (Providing dependencies while retaining services) * - * ```ts + * ```ts import.meta.vitest * import { Context, Effect, Layer } from "effect" * * class Database extends Context.Service Effect.succeed(`DB: ${sql}`)) * }) * + * const logs: Array = [] * const loggerLayer = Layer.succeed(Logger, { - * log: Effect.fn("Logger.log")((msg: string) => Effect.sync(() => console.log(`[LOG] ${msg}`))) + * log: Effect.fn("Logger.log")((msg: string) => Effect.sync(() => logs.push(`[LOG] ${msg}`))) * }) * * // UserService depends on Database and Logger @@ -1480,6 +1538,8 @@ export const provide: { * }).pipe( * Effect.provide(allServicesLayer) * ) + * Effect.runSync(program) // => { id: "123", name: "DB: SELECT * FROM users WHERE id = 123" } + * logs // => ["[LOG] Looking up user 123", "[LOG] Found user: DB: SELECT * FROM users WHERE id = 123"] * ``` * * @see {@link provide} for keeping dependency services private @@ -1529,7 +1589,7 @@ export const provideMerge: { * * **Example** (Creating services from layer output) * - * ```ts + * ```ts import.meta.vitest * import { Context, Effect, Layer } from "effect" * * class Config extends Context.Service Effect.Effect * }>()("Logger") {} * + * const logs: Array = [] + * * // Base config layer * const configLayer = Layer.succeed(Config, { * dbUrl: "postgres://localhost:5432/mydb", @@ -1568,8 +1630,8 @@ export const provideMerge: { * const loggerLayer = Layer.succeed(Logger, { * log: Effect.fn("Logger.log")((msg: string) => * config.logLevel === "debug" - * ? Effect.sync(() => console.log(`[DEBUG] ${msg}`)) - * : Effect.sync(() => console.log(msg)) + * ? Effect.sync(() => logs.push(`[DEBUG] ${msg}`)) + * : Effect.sync(() => logs.push(msg)) * ) * }) * @@ -1590,6 +1652,8 @@ export const provideMerge: { * }).pipe( * Effect.provide(dynamicServiceLayer) * ) + * Effect.runSync(program) // => "Querying postgres://localhost:5432/mydb: SELECT * FROM users" + * logs // => ["[DEBUG] Starting database query"] * ``` * * @category sequencing @@ -1744,8 +1808,8 @@ export const tapCause: { * * **Example** (Converting layer failures to defects) * - * ```ts - * import { Context, Data, Effect, Layer } from "effect" + * ```ts import.meta.vitest + * import { Context, Data, Effect, Exit, Layer } from "effect" * * class DatabaseError extends Data.TaggedError("DatabaseError")<{ * message: string @@ -1756,10 +1820,11 @@ export const tapCause: { * }>()("Database") {} * * // Layer that can fail during construction - * const flakyDatabaseLayer = Layer.effect(Database, Effect.gen(function*() { - * console.log("connecting") - * return yield* new DatabaseError({ message: "Connection failed" }) - * })) + * const error = new DatabaseError({ message: "Connection failed" }) + * const flakyDatabaseLayer = Layer.effect( + * Database, + * Effect.fail(error) + * ) * * // Convert failures to fiber death - removes error from type * const reliableDatabaseLayer = flakyDatabaseLayer.pipe(Layer.orDie) @@ -1772,8 +1837,7 @@ export const tapCause: { * Effect.provide(reliableDatabaseLayer) * ) * - * // Running the program prints "connecting", then the DatabaseError is - * // converted into a fiber defect instead of remaining a typed error. + * Effect.runSync(Effect.exit(program)) // => Exit.die(error) * ``` * * @category error handling @@ -1828,7 +1892,7 @@ export { * * **Example** (Recovering from tagged layer errors) * - * ```ts + * ```ts import.meta.vitest * import { Context, Data, Effect, Layer } from "effect" * * class ConfigError extends Data.TaggedError("ConfigError") {} @@ -1844,6 +1908,8 @@ export { * const recovered = configLayer.pipe( * Layer.catchTag("ConfigError", () => fallbackLayer) * ) + * const program = Config.useSync((config) => config.apiUrl) + * Effect.runSync(Effect.provide(program, recovered)) // => "http://localhost" * ``` * * @see {@link catchCause} for recovering with access to the full cause @@ -1919,7 +1985,7 @@ export const catchTag: { * * **Example** (Recovering from layer failures by cause) * - * ```ts + * ```ts import.meta.vitest * import { Context, Data, Effect, Layer } from "effect" * * class DatabaseError extends Data.TaggedError("DatabaseError")<{ @@ -1944,14 +2010,12 @@ export const catchTag: { * * const program = Effect.gen(function*() { * const database = yield* Database - * const result = yield* database.query("SELECT * FROM users") - * console.log(result) + * return yield* database.query("SELECT * FROM users") * }).pipe( * Effect.provide(databaseWithFallback) * ) * - * Effect.runPromise(program) - * // Memory: SELECT * FROM users + * await Effect.runPromise(program) // => "Memory: SELECT * FROM users" * ``` * * @see {@link catchTag} for recovering from specific tagged errors @@ -2030,7 +2094,7 @@ export const updateService: { * * **Example** (Creating non-shared layer instances) * - * ```ts + * ```ts import.meta.vitest * import { Context, Effect, Layer, Ref } from "effect" * * class Counter extends Context.Service n + 1) - * console.log("constructed Counter") * return { id } * })) * @@ -2075,7 +2138,7 @@ export const updateService: { * Layer.provide(rightLayer, counterLayer) * ) * - * yield* Effect.provide(showIds, shared) + * const sharedResult = yield* Effect.provide(compareIds, shared) * * const freshCounterLayer = Layer.fresh(counterLayer) * const fresh = Layer.merge( @@ -2083,15 +2146,12 @@ export const updateService: { * Layer.provide(rightLayer, freshCounterLayer) * ) * - * yield* Effect.provide(showIds, fresh) + * const freshResult = yield* Effect.provide(compareIds, fresh) + * + * return { shared: sharedResult, fresh: freshResult } * }) * - * Effect.runPromise(program) - * // constructed Counter - * // same Counter: true - * // constructed Counter - * // constructed Counter - * // same Counter: false + * await Effect.runPromise(program) // => { shared: true, fresh: false } * ``` * * @category layers @@ -2115,50 +2175,30 @@ export const fresh = (self: Layer): Layer => * * **Example** (Launching an application layer) * - * ```ts - * import { Console, Context, Effect, Layer } from "effect" + * ```ts import.meta.vitest + * import { Context, Deferred, Effect, Fiber, Layer, Ref } from "effect" * * class HttpServer extends Context.Service Effect.Effect - * readonly stop: () => Effect.Effect + * readonly port: number * }>()("HttpServer") {} * - * class Logger extends Context.Service Effect.Effect - * }>()("Logger") {} - * - * // Server layer that starts an HTTP server - * const serverLayer = Layer.effect(HttpServer, Effect.gen(function*() { - * yield* Console.log("Starting HTTP server...") + * const program = Effect.gen(function*() { + * const events = yield* Ref.make>([]) + * const started = yield* Deferred.make() * - * return { - * start: Effect.fn("HttpServer.start")(function*() { - * yield* Console.log("Server listening on port 3000") - * return "Server started" - * }), - * stop: Effect.fn("HttpServer.stop")(function*() { - * yield* Console.log("Server stopped gracefully") - * return "Server stopped" - * }) - * } - * })) + * const serverLayer = Layer.effect(HttpServer, Effect.gen(function*() { + * yield* Ref.update(events, (events) => [...events, "Starting HTTP server..."]) + * yield* Deferred.succeed(started, undefined) + * return { port: 3000 } + * })) * - * const loggerLayer = Layer.succeed(Logger, { - * log: Effect.fn("Logger.log")((msg: string) => Console.log(`[LOG] ${msg}`)) + * const fiber = yield* Effect.forkChild(Layer.launch(serverLayer)) + * yield* Deferred.await(started) + * yield* Fiber.interrupt(fiber) + * return yield* Ref.get(events) * }) * - * // Application layer combining all services - * const appLayer = Layer.mergeAll(serverLayer, loggerLayer) - * - * // Launch the application - runs until interrupted - * const application = appLayer.pipe( - * Layer.launch, - * Effect.tapError((error) => Console.log(`Application failed: ${error}`)), - * Effect.tap(() => Console.log("Application completed")) - * ) - * - * // This will run forever until externally interrupted - * // Effect.runFork(application) + * await Effect.runPromise(program) // => ["Starting HTTP server..."] * ``` * * @category converting @@ -2219,7 +2259,7 @@ type AnyEffectOrStream = * * **Example** (Mocking services for tests) * - * ```ts + * ```ts import.meta.vitest * import { Context, Effect, Layer } from "effect" * * class UserService extends Context.Service "Test User" * ``` * * @category testing @@ -2326,22 +2368,17 @@ const ChannelTypeId: Channel.TypeId = "~effect/Channel" * * **Example** (Constraining layer success types) * - * ```ts - * import { Layer } from "effect" + * ```ts import.meta.vitest + * import { Context, Layer } from "effect" * - * declare const FortyTwoLayer: Layer.Layer<42, never, never> - * declare const StringLayer: Layer.Layer + * const NumberService = Context.Service("Number") + * const numberLayer = Layer.succeed(NumberService, 42) * * // Define a constraint that the success type must be a number * const satisfiesNumber = Layer.satisfiesSuccessType() * * // This works - Layer<42, never, never> extends Layer - * const validLayer = satisfiesNumber(FortyTwoLayer) - * - * // This would cause a TypeScript compilation error: - * // const invalidLayer = satisfiesNumber(StringLayer) - * // ^^^^^^^^^^^ - * // Type 'string' is not assignable to type 'number' + * const validLayer = satisfiesNumber(numberLayer) * ``` * * @category utility types @@ -2360,23 +2397,16 @@ export const satisfiesSuccessType = * * **Example** (Constraining layer error types) * - * ```ts - * import { Layer } from "effect" + * ```ts import.meta.vitest + * import { Effect, Layer } from "effect" * - * declare const ErrorLayer: Layer.Layer - * declare const TypeErrorLayer: Layer.Layer - * declare const StringLayer: Layer.Layer + * const typeErrorLayer = Layer.effectDiscard(Effect.fail(new TypeError("boom"))) * * // Define a constraint that the error type must be an Error * const satisfiesError = Layer.satisfiesErrorType() * * // This works - Layer extends Layer - * const validLayer = satisfiesError(TypeErrorLayer) - * - * // This would cause a TypeScript compilation error: - * // const invalidLayer = satisfiesError(StringLayer) - * // ^^^^^^^^^^^ - * // Type 'string' is not assignable to type 'Error' + * const validLayer = satisfiesError(typeErrorLayer) * ``` * * @category utility types @@ -2395,22 +2425,17 @@ export const satisfiesErrorType = * * **Example** (Constraining layer service requirements) * - * ```ts - * import { Layer } from "effect" + * ```ts import.meta.vitest + * import { Context, Effect, Layer } from "effect" * - * declare const FortyTwoLayer: Layer.Layer - * declare const StringLayer: Layer.Layer + * const NumberService = Context.Service("Number") + * const numberLayer = Layer.effectDiscard(Effect.asVoid(NumberService)) * * // Define a constraint that the service requirements must be numbers * const satisfiesNumber = Layer.satisfiesServicesType() * * // This works - Layer extends Layer - * const validLayer = satisfiesNumber(FortyTwoLayer) - * - * // This would cause a TypeScript compilation error: - * // const invalidLayer = satisfiesNumber(StringLayer) - * // ^^^^^^^^^^^ - * // Type 'string' is not assignable to type 'number' + * const validLayer = satisfiesNumber(numberLayer) * ``` * * @category utility types @@ -2468,38 +2493,37 @@ export interface SpanOptions extends Tracer.SpanOptions { * * **Example** (Tracing layer construction with a span) * - * ```ts - * import { Console, Context, Effect, Layer } from "effect" + * ```ts import.meta.vitest + * import { Context, Effect, Layer } from "effect" * import type { Tracer } from "effect" * * class Database extends Context.Service Effect.Effect * }>()("Database") {} * + * const logs: Array = [] + * * // Create a traced layer - all operations performed during construction of * // the `Database` service are part of the "database-init" span * const databaseLayer = Layer.effect(Database, Effect.gen(function*() { * // These operations are traced under "database-init" span - * yield* Effect.log("Connecting to database") - * yield* Effect.sleep("100 millis") - * yield* Effect.log("Database connected") + * logs.push("Connecting to database") + * logs.push("Database connected") * * const parentSpan = yield* Effect.currentParentSpan - * yield* Console.log((parentSpan as Tracer.Span).name) // "database-init" + * logs.push((parentSpan as Tracer.Span).name) * * return { * query: Effect.fn("Database.query")((sql: string) => Effect.succeed(`Result: ${sql}`)) * } - * })).pipe(Layer.provide(Layer.span("database-init"))) - * - * // Can also use the `onEnd` callback to execute logic when the span ends - * const tracedLayer = Layer.span("service-initialization", { - * attributes: { version: "1.0.0" }, + * })).pipe(Layer.provide(Layer.span("database-init", { * onEnd: (span, exit) => - * Effect.sync(() => { - * console.log(`Span ${span.name} ended with:`, exit._tag) - * }) - * }) + * Effect.sync(() => logs.push(`Span ${span.name} ended with: ${exit._tag}`)) + * }))) + * + * const program = Database.use((database) => database.query("SELECT 1")) + * Effect.runSync(Effect.provide(program, databaseLayer)) // => "Result: SELECT 1" + * logs // => ["Connecting to database", "Database connected", "database-init", "Span database-init ended with: Success"] * ``` * * @category tracing @@ -2532,10 +2556,11 @@ export const span = ( * * **Example** (Referencing an existing parent span) * - * ```ts - * import { Console, Context, Effect, Layer, Tracer } from "effect" + * ```ts import.meta.vitest + * import { Context, Effect, Layer, Tracer } from "effect" * * class Database extends Context.Service Effect.Effect * }>()("Database") {} * @@ -2543,12 +2568,10 @@ export const span = ( * const databaseLayer = Layer.effect( * Database, * Effect.gen(function*() { - * yield* Effect.log("Initializing database") - * * const parentSpan = yield* Effect.currentParentSpan - * yield* Console.log(parentSpan.spanId) // "42" * * return { + * spanId: parentSpan.spanId, * query: Effect.fn("Database.query")((sql: string) => Effect.succeed(`Result: ${sql}`)) * } * }) @@ -2556,6 +2579,9 @@ export const span = ( * spanId: "42", * traceId: "000" * })))) + * const program = Database.use((database) => + * Effect.map(database.query("SELECT 1"), (result) => ({ spanId: database.spanId, result }))) + * Effect.runSync(Effect.provide(program, databaseLayer)) // => { spanId: "42", result: "Result: SELECT 1" } * ``` * * @category tracing @@ -2576,7 +2602,7 @@ export const parentSpan = (span: Tracer.AnySpan): Layer => * * **Example** (Wrapping a layer with a span) * - * ```ts + * ```ts import.meta.vitest * import { Context, Effect, Layer } from "effect" * * class Database extends Context.Service => * readonly log: (msg: string) => Effect.Effect * }>()("Logger") {} * + * const logs: Array = [] + * * // Create layers with tracing * const databaseLayer = Layer.effect(Database, Effect.gen(function*() { - * yield* Effect.log("Connecting to database") - * yield* Effect.sleep("100 millis") * return { * query: Effect.fn("Database.query")((sql: string) => Effect.succeed(`Result: ${sql}`)) * } @@ -2599,16 +2625,14 @@ export const parentSpan = (span: Tracer.AnySpan): Layer => * })) * * const loggerLayer = Layer.succeed(Logger, { - * log: Effect.fn("Logger.log")((msg: string) => Effect.sync(() => console.log(msg))) + * log: Effect.fn("Logger.log")((msg: string) => Effect.sync(() => logs.push(msg))) * }).pipe(Layer.withSpan("logger-initialization")) * * // Combine traced layers * const appLayer = Layer.mergeAll(databaseLayer, loggerLayer).pipe( * Layer.withSpan("app-initialization", { * onEnd: (span, exit) => - * Effect.sync(() => { - * console.log(`Application initialization completed: ${exit._tag}`) - * }) + * Effect.sync(() => logs.push(`Application initialization completed: ${exit._tag}`)) * }) * ) * @@ -2619,6 +2643,8 @@ export const parentSpan = (span: Tracer.AnySpan): Layer => * yield* logger.log("Application ready") * return yield* database.query("SELECT * FROM users") * }).pipe(Effect.provide(appLayer)) + * Effect.runSync(program) // => "Result: SELECT * FROM users" + * logs // => ["Application ready", "Application initialization completed: Success"] * ``` * * @category tracing @@ -2684,7 +2710,7 @@ export const withSpan: { * * **Example** (Attaching layers to an existing parent span) * - * ```ts + * ```ts import.meta.vitest * import { Context, Effect, Layer, Tracer } from "effect" * * class Database extends Context.Service Effect.succeed(`DB: ${sql}`)) * } * })) * * const CacheLayer = Layer.effect(Cache, Effect.gen(function*() { - * yield* Effect.log("Connecting to cache") * return { * get: Effect.fn("Cache.get")((key: string) => Effect.succeed(`Cache: ${key}`)) * } @@ -2730,6 +2754,7 @@ export const withSpan: { * return { dbResult, cacheResult } * }) * ) + * Effect.runSync(Effect.scoped(program)) // => { dbResult: "DB: SELECT * FROM users", cacheResult: "Cache: user:123" } * ``` * * @category tracing diff --git a/repos/effect/packages/effect/src/LayerMap.ts b/repos/effect/packages/effect/src/LayerMap.ts index b4fc9800d8..7b787441ef 100644 --- a/repos/effect/packages/effect/src/LayerMap.ts +++ b/repos/effect/packages/effect/src/LayerMap.ts @@ -33,7 +33,7 @@ type IdleTimeToLiveInput = Duration.Input | ((key: K) => Duration.Input) * * **Example** (Managing keyed layers) * - * ```ts + * ```ts import.meta.vitest * import { Context, Effect, Layer, LayerMap } from "effect" * * // Define a service key @@ -53,14 +53,22 @@ type IdleTimeToLiveInput = Duration.Input | ((key: K) => Duration.Input) * const layerMap = yield* createDatabaseLayerMap * * // Get a layer for a specific environment - * const devLayer = layerMap.get("development") + * const development = yield* Effect.provide( + * DatabaseService.use((database) => database.query("SELECT 1")), + * layerMap.get("development") + * ) * * // Get context directly - * const context = yield* layerMap.contextEffect("production") + * const productionContext = yield* layerMap.contextEffect("production") + * const production = yield* Context.get(productionContext, DatabaseService).query("SELECT 1") * * // Invalidate a cached layer * yield* layerMap.invalidate("development") + * + * return { development, production } * }) + * + * await Effect.runPromise(Effect.scoped(program)) // => { development: "development: SELECT 1", production: "production: SELECT 1" } * ``` * * @category models @@ -95,7 +103,7 @@ export interface LayerMap { * * **Example** (Creating a layer map) * - * ```ts + * ```ts import.meta.vitest * import { Context, Effect, Layer, LayerMap } from "effect" * * // Define a service key @@ -117,16 +125,16 @@ export interface LayerMap { * const devLayer = layerMap.get("development") * * // Use the layer to provide the service - * const result = yield* Effect.provide( + * return yield* Effect.provide( * Effect.gen(function*() { * const db = yield* DatabaseService * return yield* db.query("SELECT * FROM users") * }), * devLayer * ) - * - * console.log(result) // "development: SELECT * FROM users" * }) + * + * await Effect.runPromise(Effect.scoped(program)) // => "development: SELECT * FROM users" * ``` * * @category constructors @@ -150,10 +158,11 @@ export const make: < lookup: (key: K) => Layer.Layer, options?: { readonly idleTimeToLive?: IdleTimeToLiveInput | undefined + readonly preloadKeys?: Iterable | undefined } | undefined ) { const context = yield* Effect.context() - const memoMap = Layer.CurrentMemoMap.getOrCreate(context) + const memoMap = Layer.CurrentMemoMap.forkOrCreate(context) const rcMap = yield* RcMap.make({ lookup: (key: K) => @@ -163,6 +172,12 @@ export const make: < idleTimeToLive: options?.idleTimeToLive }) + if (options?.preloadKeys) { + for (const key of options.preloadKeys) { + yield* Effect.scoped(RcMap.get(rcMap, key)) + } + } + return identity>({ [TypeId]: TypeId, rcMap, @@ -182,24 +197,20 @@ export const make: < * * **Example** (Creating a layer map from a record) * - * ```ts + * ```ts import.meta.vitest * import { Context, Effect, Layer, LayerMap } from "effect" * - * // Define service keys - * const DevDatabase = Context.Service<{ - * readonly query: (sql: string) => Effect.Effect - * }>("DevDatabase") - * - * const ProdDatabase = Context.Service<{ + * // Define a service key + * const Database = Context.Service<{ * readonly query: (sql: string) => Effect.Effect - * }>("ProdDatabase") + * }>("Database") * * // Create predefined layers * const layers = { - * development: Layer.succeed(DevDatabase)({ + * development: Layer.succeed(Database)({ * query: Effect.fn("DevDatabase.query")((sql) => Effect.succeed(`DEV: ${sql}`)) * }), - * production: Layer.succeed(ProdDatabase)({ + * production: Layer.succeed(Database)({ * query: Effect.fn("ProdDatabase.query")((sql) => Effect.succeed(`PROD: ${sql}`)) * }) * } as const @@ -210,12 +221,19 @@ export const make: < * idleTimeToLive: "10 seconds" * }) * - * // Get layers by key - * const devLayer = layerMap.get("development") - * const prodLayer = layerMap.get("production") + * const development = yield* Effect.provide( + * Database.use((database) => database.query("SELECT 1")), + * layerMap.get("development") + * ) + * const production = yield* Effect.provide( + * Database.use((database) => database.query("SELECT 1")), + * layerMap.get("production") + * ) * - * console.log("LayerMap created from record") + * return { development, production } * }) + * + * await Effect.runPromise(Effect.scoped(program)) // => { development: "DEV: SELECT 1", production: "PROD: SELECT 1" } * ``` * * @category constructors @@ -310,8 +328,8 @@ export interface TagClass< * * **Example** (Defining a layer map service) * - * ```ts - * import { Console, Context, Effect, Layer, LayerMap } from "effect" + * ```ts import.meta.vitest + * import { Context, Effect, Layer, LayerMap } from "effect" * * // Define a service key * const Greeter = Context.Service<{ @@ -334,7 +352,7 @@ export interface TagClass< * const program = Effect.gen(function*() { * // Access and use the Greeter service * const greeter = yield* Greeter - * yield* Console.log(yield* greeter.greet) + * return yield* greeter.greet * }).pipe( * // Use the GreeterMap service to provide a variant of the Greeter service * Effect.provide(GreeterMap.get("John")) @@ -342,6 +360,8 @@ export interface TagClass< * // Provide the GreeterMap layer * Effect.provide(GreeterMap.layer) * ) + * + * await Effect.runPromise(program) // => "Hello, John!" * ``` * * @category services @@ -424,7 +444,7 @@ export declare namespace Service { /** * Extracts the key type accepted by a `LayerMap.Service` definition. * - * @category services + * @category utility types * @since 3.14.0 */ export type Key = Options extends { readonly lookup: (key: infer K) => any } ? K @@ -434,7 +454,7 @@ export declare namespace Service { /** * Extracts the layer type produced by a `LayerMap.Service` definition. * - * @category services + * @category utility types * @since 3.14.0 */ export type Layers = Options extends { readonly lookup: (key: infer _K) => infer Layers } ? Layers @@ -445,7 +465,7 @@ export declare namespace Service { * Extracts the services provided by the layers in a `LayerMap.Service` * definition. * - * @category services + * @category utility types * @since 3.14.0 */ export type Success = Layers extends Layer.Layer ? _A : never @@ -453,7 +473,7 @@ export declare namespace Service { /** * Extracts the error type of the layers in a `LayerMap.Service` definition. * - * @category services + * @category utility types * @since 3.14.0 */ export type Error = Layers extends Layer.Layer ? _E : never @@ -462,7 +482,7 @@ export declare namespace Service { * Extracts the service requirements of the layers in a `LayerMap.Service` * definition. * - * @category services + * @category utility types * @since 4.0.0 */ export type Services = Layers extends Layer.Layer ? _R : never diff --git a/repos/effect/packages/effect/src/LayerRef.ts b/repos/effect/packages/effect/src/LayerRef.ts new file mode 100644 index 0000000000..a961007733 --- /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 index f8002b8486..837e1bd5a3 100644 --- a/repos/effect/packages/effect/src/LogLevel.ts +++ b/repos/effect/packages/effect/src/LogLevel.ts @@ -36,8 +36,8 @@ import * as References from "./References.ts" * * **Example** (Using log levels) * - * ```ts - * import { Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect, References } from "effect" * * // Using log levels with Effect logging * const program = Effect.gen(function*() { @@ -52,6 +52,13 @@ import * as References from "./References.ts" * // Type-safe log level variables * const errorLevel = "Error" // LogLevel * const debugLevel = "Debug" // LogLevel + * + * await Effect.runPromise( + * Effect.provideService(program, References.MinimumLogLevel, "None") + * ) + * + * const levels = [errorLevel, debugLevel] + * levels // => ["Error", "Debug"] * ``` * * @category models @@ -101,7 +108,7 @@ export type Severity = "Fatal" | "Error" | "Warn" | "Info" | "Debug" | "Trace" * @see {@link Severity} for the concrete message severity type that excludes `All` and `None` * @see {@link Order} for comparing these levels by severity order * - * @category models + * @category constants * @since 4.0.0 */ export const values: ReadonlyArray = ["All", "Fatal", "Error", "Warn", "Info", "Debug", "Trace", "None"] @@ -120,13 +127,12 @@ export const values: ReadonlyArray = ["All", "Fatal", "Error", "Warn", * * **Example** (Ordering log levels) * - * ```ts + * ```ts import.meta.vitest * import { LogLevel } from "effect" * - * // Compare log levels using Order - * console.log(LogLevel.Order("Error", "Info")) // 1 (Error > Info) - * console.log(LogLevel.Order("Debug", "Error")) // -1 (Debug < Error) - * console.log(LogLevel.Order("Info", "Info")) // 0 (Info == Info) + * LogLevel.Order("Error", "Info") // => 1 + * LogLevel.Order("Debug", "Error") // => -1 + * LogLevel.Order("Info", "Info") // => 0 * ``` * * @category ordering @@ -148,11 +154,11 @@ export const Order: Ord.Order = effect.LogLevelOrder * * **Example** (Comparing log levels) * - * ```ts + * ```ts import.meta.vitest * import { LogLevel } from "effect" * - * console.log(LogLevel.Equivalence("Error", "Error")) // true - * console.log(LogLevel.Equivalence("Error", "Info")) // false + * LogLevel.Equivalence("Error", "Error") // => true + * LogLevel.Equivalence("Error", "Info") // => false * ``` * * @see {@link Order} for severity ordering rather than exact level equality @@ -204,25 +210,24 @@ export const getOrdinal = (self: LogLevel): number => effect.logLevelToOrder(sel * * **Example** (Checking higher severity) * - * ```ts + * ```ts import.meta.vitest * import { LogLevel } from "effect" * - * // Check if Error is more severe than Info - * console.log(LogLevel.isGreaterThan("Error", "Info")) // true - * console.log(LogLevel.isGreaterThan("Debug", "Error")) // false + * LogLevel.isGreaterThan("Error", "Info") // => true + * LogLevel.isGreaterThan("Debug", "Error") // => false * * // Use with filtering * const isFatal = LogLevel.isGreaterThan("Fatal", "Warn") * const isError = LogLevel.isGreaterThan("Error", "Warn") * const isDebug = LogLevel.isGreaterThan("Debug", "Warn") - * console.log(isFatal) // true - * console.log(isError) // true - * console.log(isDebug) // false + * isFatal // => true + * isError // => true + * isDebug // => false * * // Curried usage * const isMoreSevereThanInfo = LogLevel.isGreaterThan("Info") - * console.log(isMoreSevereThanInfo("Error")) // true - * console.log(isMoreSevereThanInfo("Debug")) // false + * isMoreSevereThanInfo("Error") // => true + * isMoreSevereThanInfo("Debug") // => false * ``` * * @category ordering @@ -247,33 +252,15 @@ export const isGreaterThan: { * * **Example** (Filtering by minimum log level) * - * ```ts - * import { Logger, LogLevel } from "effect" - * - * // Check if level meets minimum threshold - * console.log(LogLevel.isGreaterThanOrEqualTo("Error", "Error")) // true - * console.log(LogLevel.isGreaterThanOrEqualTo("Error", "Info")) // true - * console.log(LogLevel.isGreaterThanOrEqualTo("Debug", "Info")) // false + * ```ts import.meta.vitest + * import { LogLevel } from "effect" * - * // Create a logger that only logs Info and above - * const infoLogger = Logger.make((options) => { - * if (LogLevel.isGreaterThanOrEqualTo(options.logLevel, "Info")) { - * console.log(`[${options.logLevel}] ${options.message}`) - * } - * }) + * LogLevel.isGreaterThanOrEqualTo("Error", "Error") // => true + * LogLevel.isGreaterThanOrEqualTo("Error", "Info") // => true + * LogLevel.isGreaterThanOrEqualTo("Debug", "Info") // => false * - * // Production logger - only Error and Fatal - * const productionLogger = Logger.make((options) => { - * if (LogLevel.isGreaterThanOrEqualTo(options.logLevel, "Error")) { - * console.error( - * `${options.date.toISOString()} [${options.logLevel}] ${options.message}` - * ) - * } - * }) - * - * // Curried usage for filtering * const isInfoOrAbove = LogLevel.isGreaterThanOrEqualTo("Info") - * const shouldLog = isInfoOrAbove("Error") // true + * isInfoOrAbove("Error") // => true * ``` * * @category ordering @@ -297,25 +284,24 @@ export const isGreaterThanOrEqualTo: { * * **Example** (Checking lower severity) * - * ```ts + * ```ts import.meta.vitest * import { LogLevel } from "effect" * - * // Check if Debug is less severe than Info - * console.log(LogLevel.isLessThan("Debug", "Info")) // true - * console.log(LogLevel.isLessThan("Error", "Info")) // false + * LogLevel.isLessThan("Debug", "Info") // => true + * LogLevel.isLessThan("Error", "Info") // => false * * // Filter out verbose logs * const isFatalVerbose = LogLevel.isLessThan("Fatal", "Info") * const isErrorVerbose = LogLevel.isLessThan("Error", "Info") * const isTraceVerbose = LogLevel.isLessThan("Trace", "Info") - * console.log(isFatalVerbose) // false (Fatal is not verbose) - * console.log(isErrorVerbose) // false (Error is not verbose) - * console.log(isTraceVerbose) // true (Trace is verbose) + * isFatalVerbose // => false + * isErrorVerbose // => false + * isTraceVerbose // => true * * // Curried usage * const isLessSevereThanError = LogLevel.isLessThan("Error") - * console.log(isLessSevereThanError("Info")) // true - * console.log(isLessSevereThanError("Fatal")) // false + * isLessSevereThanError("Info") // => true + * isLessSevereThanError("Fatal") // => false * ``` * * @category ordering @@ -340,31 +326,15 @@ export const isLessThan: { * * **Example** (Filtering by maximum log level) * - * ```ts - * import { Logger, LogLevel } from "effect" - * - * // Check if level is at or below threshold - * console.log(LogLevel.isLessThanOrEqualTo("Info", "Info")) // true - * console.log(LogLevel.isLessThanOrEqualTo("Debug", "Info")) // true - * console.log(LogLevel.isLessThanOrEqualTo("Error", "Info")) // false - * - * // Create a logger that suppresses verbose logs - * const quietLogger = Logger.make((options) => { - * if (LogLevel.isLessThanOrEqualTo(options.logLevel, "Info")) { - * console.log(`[${options.logLevel}] ${options.message}`) - * } - * }) + * ```ts import.meta.vitest + * import { LogLevel } from "effect" * - * // Development logger - suppress trace logs - * const devLogger = Logger.make((options) => { - * if (LogLevel.isLessThanOrEqualTo(options.logLevel, "Debug")) { - * console.log(`[${options.logLevel}] ${options.message}`) - * } - * }) + * LogLevel.isLessThanOrEqualTo("Info", "Info") // => true + * LogLevel.isLessThanOrEqualTo("Debug", "Info") // => true + * LogLevel.isLessThanOrEqualTo("Error", "Info") // => false * - * // Curried usage for filtering * const isInfoOrBelow = LogLevel.isLessThanOrEqualTo("Info") - * const shouldLog = isInfoOrBelow("Debug") // true + * isInfoOrBelow("Debug") // => true * ``` * * @category ordering @@ -390,22 +360,24 @@ export const isLessThanOrEqualTo: { * * **Example** (Checking current fiber log level) * - * ```ts + * ```ts import.meta.vitest * import { Effect, LogLevel, References } from "effect" * * const program = Effect.gen(function*() { * const debugEnabled = yield* LogLevel.isEnabled("Debug") * const errorEnabled = yield* LogLevel.isEnabled("Error") * - * console.log({ debugEnabled, errorEnabled }) + * return { debugEnabled, errorEnabled } * }) * * const warnOnly = program.pipe( * Effect.provideService(References.MinimumLogLevel, "Warn") * ) + * + * await Effect.runPromise(warnOnly) // => { debugEnabled: false, errorEnabled: true } * ``` * - * @category filtering + * @category predicates * @since 4.0.0 */ export const isEnabled = (self: LogLevel): Effect.Effect => diff --git a/repos/effect/packages/effect/src/Logger.ts b/repos/effect/packages/effect/src/Logger.ts index 9c37114c1f..4dcedeafaf 100644 --- a/repos/effect/packages/effect/src/Logger.ts +++ b/repos/effect/packages/effect/src/Logger.ts @@ -20,6 +20,7 @@ import * as Formatter from "./Formatter.ts" import { dual } from "./Function.ts" import { isEffect, withFiber } from "./internal/core.ts" import * as effect from "./internal/effect.ts" +import * as InternalRecord from "./internal/record.ts" import * as Layer from "./Layer.ts" import type * as LogLevel from "./LogLevel.ts" import type { Pipeable } from "./Pipeable.ts" @@ -41,23 +42,20 @@ const TypeId = "~effect/Logger" * * **Example** (Creating custom loggers) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Logger } from "effect" * - * // Create a custom logger that accepts unknown messages and returns void + * const messages: Array = [] * const stringLogger = Logger.make((options) => { - * console.log(`[${options.logLevel}] ${options.message}`) + * messages.push(`[${options.logLevel}] ${options.message}`) * }) * - * // Create a logger that accepts any message type and returns a formatted string - * const formattedLogger = Logger.make((options) => - * `${options.date.toISOString()} [${options.logLevel}] ${options.message}` - * ) - * - * // Use the logger in an Effect program * const program = Effect.log("Hello World").pipe( * Effect.provide(Logger.layer([stringLogger])) * ) + * + * Effect.runSync(program) + * messages // => ["[Info] Hello World"] * ``` * * @category models @@ -77,24 +75,24 @@ export interface Logger extends Pipeable { * * **Example** (Accessing logger options) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Logger } from "effect" * - * // Options interface provides all logging context + * const outputs: Array = [] * const detailedLogger = Logger.make((options) => { - * const output = { + * outputs.push({ * message: options.message, * level: options.logLevel, - * timestamp: options.date.toISOString(), - * fiberId: options.fiber.id, - * hasCause: options.cause !== undefined - * } - * console.log(JSON.stringify(output)) + * hasCause: options.cause.reasons.length > 0 + * }) * }) * * const program = Effect.log("Processing request").pipe( * Effect.provide(Logger.layer([detailedLogger])) * ) + * + * Effect.runSync(program) + * outputs // => [{ message: ["Processing request"], level: "Info", hasCause: false }] * ``` * * @category options @@ -113,16 +111,14 @@ export interface Options { * * **Example** (Checking logger values) * - * ```ts + * ```ts import.meta.vitest * import { Logger } from "effect" * - * const myLogger = Logger.make((options) => { - * console.log(options.message) - * }) + * const myLogger = Logger.make(() => undefined) * - * console.log(Logger.isLogger(myLogger)) // true - * console.log(Logger.isLogger("not a logger")) // false - * console.log(Logger.isLogger({ log: () => {} })) // false + * Logger.isLogger(myLogger) // => true + * Logger.isLogger("not a logger") // => false + * Logger.isLogger({ log: () => {} }) // => false * ``` * * @category guards @@ -141,26 +137,26 @@ export const isLogger = (u: unknown): u is Logger => Predicate * * **Example** (Accessing current loggers) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Logger } from "effect" * - * // Access current loggers from fiber context + * const messages: Array = [] + * const customLogger = Logger.make((options) => { + * messages.push(options.message) + * }) * const program = Effect.gen(function*() { * const currentLoggers = yield* Effect.service(Logger.CurrentLoggers) - * console.log(`Number of active loggers: ${currentLoggers.size}`) - * - * // Add a custom logger to the set - * const customLogger = Logger.make((options) => { - * console.log(`Custom: ${options.message}`) - * }) - * * yield* Effect.log("Hello from custom logger").pipe( * Effect.provide(Logger.layer([customLogger])) * ) + * return currentLoggers.has(Logger.defaultLogger) * }) + * + * Effect.runSync(program) // => true + * messages // => [["Hello from custom logger"]] * ``` * - * @category references + * @category services * @since 4.0.0 */ export const CurrentLoggers: Context.Reference>> = effect.CurrentLoggers @@ -183,7 +179,7 @@ export const CurrentLoggers: Context.Reference> * @see {@link consolePretty} for the TTY-mode pretty console logger affected by this reference * @see {@link withConsoleError} for routing a specific formatter logger to `console.error` * - * @category references + * @category services * @since 4.0.0 */ export const LogToStderr: Context.Reference = effect.LogToStderr @@ -198,27 +194,24 @@ export const LogToStderr: Context.Reference = effect.LogToStderr * * **Example** (Transforming logger output) * - * ```ts - * import { Logger } from "effect" + * ```ts import.meta.vitest + * import { Effect, Logger } from "effect" * - * // Create a logger that outputs objects + * const outputs: Array = [] * const structuredLogger = Logger.make((options) => ({ - * level: options.logLevel, - * message: options.message, - * timestamp: options.date.toISOString() + * message: options.message * })) * - * // Transform the output to JSON strings - * const jsonStringLogger = Logger.map( - * structuredLogger, - * (output) => JSON.stringify(output) - * ) - * * // Transform to uppercase messages * const uppercaseLogger = Logger.map( * structuredLogger, * (output) => ({ ...output, message: String(output.message).toUpperCase() }) * ) + * + * const collector = Logger.make((options) => outputs.push(uppercaseLogger.log(options))) + * const program = Effect.log("hello").pipe(Effect.provide(Logger.layer([collector]))) + * Effect.runSync(program) + * outputs // => [{ message: "HELLO" }] * ``` * * @category mapping @@ -247,20 +240,23 @@ export const map = dual< * * **Example** (Writing logger output with console.log) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Logger } from "effect" + * import { TestConsole } from "effect/testing" * * // Create a custom formatter * const customFormatter = Logger.make((options) => - * `[${options.date.toISOString()}] ${options.logLevel}: ${options.message}` + * `${options.logLevel}: ${options.message}` * ) * - * // Route to console * const consoleLogger = Logger.withConsoleLog(customFormatter) * - * const program = Effect.log("Hello World").pipe( - * Effect.provide(Logger.layer([consoleLogger])) - * ) + * const program = Effect.gen(function*() { + * yield* Effect.log("Hello World").pipe(Effect.provide(Logger.layer([consoleLogger]))) + * return yield* TestConsole.logLines + * }).pipe(Effect.provide(TestConsole.layer)) + * + * await Effect.runPromise(program) // => ["Info: Hello World"] * ``` * * @category logging @@ -284,20 +280,23 @@ export const withConsoleLog = ( * * **Example** (Writing logger output with console.error) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Logger } from "effect" + * import { TestConsole } from "effect/testing" * * // Create an error-specific formatter * const errorFormatter = Logger.make((options) => - * `ERROR [${options.date.toISOString()}]: ${options.message}` + * `ERROR: ${options.message}` * ) * - * // Route to console.error * const errorLogger = Logger.withConsoleError(errorFormatter) * - * const program = Effect.logError("Database connection failed").pipe( - * Effect.provide(Logger.layer([errorLogger])) - * ) + * const program = Effect.gen(function*() { + * yield* Effect.logError("Database connection failed").pipe(Effect.provide(Logger.layer([errorLogger]))) + * return yield* TestConsole.errorLines + * }).pipe(Effect.provide(TestConsole.layer)) + * + * await Effect.runPromise(program) // => ["ERROR: Database connection failed"] * ``` * * @category logging @@ -325,9 +324,15 @@ export const withConsoleError = ( * * **Example** (Writing logs with level-based console methods) * - * ```ts - * import { Effect, Logger } from "effect" + * ```ts import.meta.vitest + * import { Console, Effect, Logger } from "effect" * + * const messages: Array> = [] + * const testConsole: Console.Console = Object.assign(Object.create(console), { + * info: (message: unknown) => messages.push(["info", message]), + * warn: (message: unknown) => messages.push(["warn", message]), + * error: (message: unknown) => messages.push(["error", message]) + * }) * const formatter = Logger.make((options) => * `[${options.logLevel}] ${options.message}` * ) @@ -338,10 +343,14 @@ export const withConsoleError = ( * yield* Effect.logInfo("Info message") // -> console.info * yield* Effect.logWarning("Warning") // -> console.warn * yield* Effect.logError("Error occurred") // -> console.error - * yield* Effect.logDebug("Debug info") // -> console.debug - * }).pipe( - * Effect.provide(Logger.layer([leveledLogger])) - * ) + * }).pipe(Effect.provide(Logger.layer([leveledLogger]))) + * Effect.runSync(Effect.provideService(program, Console.Console, testConsole)) + * const expected = [ + * ["info", "[Info] Info message"], + * ["warn", "[Warn] Warning"], + * ["error", "[Error] Error occurred"] + * ] + * messages // => expected * ``` * * @category logging @@ -440,34 +449,20 @@ const format = ( * * **Example** (Creating loggers from functions) * - * ```ts - * import { Effect, Logger, References } from "effect" + * ```ts import.meta.vitest + * import { Effect, Logger } from "effect" * - * // Simple text logger + * const outputs: Array = [] * const textLogger = Logger.make((options) => - * `${options.date.toISOString()} [${options.logLevel}] ${options.message}` + * `${options.logLevel}: ${options.message}` * ) - * - * // Structured object logger - * const objectLogger = Logger.make((options) => ({ - * timestamp: options.date.toISOString(), - * level: options.logLevel, - * message: options.message, - * fiberId: options.fiber.id, - * annotations: options.fiber.getRef(References.CurrentLogAnnotations) - * })) - * - * // Custom filtering logger - * const filteredLogger = Logger.make((options) => { - * if (options.logLevel === "Debug") { - * return // Skip debug messages - * } - * return `${options.logLevel}: ${options.message}` - * }) + * const collector = Logger.make((options) => outputs.push(textLogger.log(options))) * * const program = Effect.log("Hello World").pipe( - * Effect.provide(Logger.layer([textLogger])) + * Effect.provide(Logger.layer([collector])) * ) + * Effect.runSync(program) + * outputs // => ["Info: Hello World"] * ``` * * @category constructors @@ -482,25 +477,10 @@ export const make: ( * * **Example** (Referencing the default logger) * - * ```ts - * import { Effect, Logger } from "effect" - * - * // Use the default logger (automatically used by Effect runtime) - * const program = Effect.gen(function*() { - * yield* Effect.log("This uses the default logger") - * yield* Effect.logInfo("Info message") - * yield* Effect.logError("Error message") - * }) - * - * // Explicitly use the default logger - * const withDefaultLogger = Effect.log("Explicit default").pipe( - * Effect.provide(Logger.layer([Logger.defaultLogger])) - * ) + * ```ts import.meta.vitest + * import { Logger } from "effect" * - * // Compare with custom logger - * const customLogger = Logger.make((options) => { - * console.log(`CUSTOM: ${options.message}`) - * }) + * Logger.isLogger(Logger.defaultLogger) // => true * ``` * * @category constructors @@ -518,24 +498,24 @@ export const defaultLogger: Logger = effect.defaultLogger * * **Example** (Formatting logs as simple strings) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Logger } from "effect" + * import { TestConsole } from "effect/testing" * * // Use the simple format logger - * const simpleLoggerProgram = Effect.log("Hello Simple Format").pipe( - * Effect.provide(Logger.layer([Logger.formatSimple])) + * const stableSimple = Logger.map(Logger.formatSimple, (output) => + * output + * .replace(/timestamp=\S+ /, "") + * .replace(/fiber=#\d+ /, "") * ) - * - * // Combine with console output - * const consoleSimpleLogger = Logger.withConsoleLog(Logger.formatSimple) - * * const program = Effect.gen(function*() { - * yield* Effect.log("Application started") - * yield* Effect.logInfo("Processing data") - * yield* Effect.logWarning("Memory usage high") - * }).pipe( - * Effect.provide(Logger.layer([consoleSimpleLogger])) - * ) + * yield* Effect.log("Application started").pipe( + * Effect.provide(Logger.layer([Logger.withConsoleLog(stableSimple)])) + * ) + * return yield* TestConsole.logLines + * }).pipe(Effect.provide(TestConsole.layer)) + * + * await Effect.runPromise(program) // => ["level=Info message=\"Application started\""] * ``` * * @category constructors @@ -554,27 +534,23 @@ export const formatSimple = effect.loggerMake(format(escapeDoubleQuotes)) * * **Example** (Formatting logs as logfmt) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Logger } from "effect" + * import { TestConsole } from "effect/testing" * - * // Use the logfmt format logger - * const logfmtLoggerProgram = Effect.log("Hello LogFmt Format").pipe( - * Effect.provide(Logger.layer([Logger.formatLogFmt])) - * ) - * - * // Perfect for structured logging systems - * const structuredProgram = Effect.gen(function*() { - * yield* Effect.log("User login", { userId: 123, method: "OAuth" }) - * yield* Effect.logInfo("Request processed", { - * duration: 45, - * status: "success" - * }) - * }).pipe( - * Effect.provide(Logger.layer([Logger.withConsoleLog(Logger.formatLogFmt)])) + * const stableLogFmt = Logger.map(Logger.formatLogFmt, (output) => + * output + * .replace(/timestamp=\S+ /, "") + * .replace(/fiber=#\d+ /, "") * ) + * const program = Effect.gen(function*() { + * yield* Effect.log("User login").pipe( + * Effect.provide(Logger.layer([Logger.withConsoleLog(stableLogFmt)])) + * ) + * return yield* TestConsole.logLines + * }).pipe(Effect.provide(TestConsole.layer)) * - * // Good for log aggregation systems like Splunk, ELK - * const productionLogger = Logger.formatLogFmt + * await Effect.runPromise(program) // => ["level=Info message=\"User login\""] * ``` * * @category constructors @@ -594,30 +570,22 @@ export const formatLogFmt = effect.loggerMake(format(JSON.stringify, 0)) * * **Example** (Formatting logs as structured objects) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Logger } from "effect" + * import { TestConsole } from "effect/testing" * - * // Use the structured format logger - * const structuredLoggerProgram = Effect.log("Hello Structured Format").pipe( - * Effect.provide(Logger.layer([Logger.formatStructured])) - * ) - * - * // Perfect for JSON processing and analytics - * const analyticsProgram = Effect.gen(function*() { - * yield* Effect.log("User action", { action: "click", element: "button" }) - * yield* Effect.logInfo("API call", { endpoint: "/users", duration: 150 }) - * }).pipe( - * Effect.annotateLogs("sessionId", "abc123"), - * Effect.withLogSpan("request"), - * Effect.provide(Logger.layer([Logger.formatStructured])) - * ) + * const stableStructured = Logger.map(Logger.formatStructured, (output) => ({ + * message: output.message, + * level: output.level + * })) + * const program = Effect.gen(function*() { + * yield* Effect.log("User action").pipe( + * Effect.provide(Logger.layer([Logger.withConsoleLog(stableStructured)])) + * ) + * return yield* TestConsole.logLines + * }).pipe(Effect.provide(TestConsole.layer)) * - * // Process structured output - * const processingLogger = Logger.map(Logger.formatStructured, (output) => { - * // Process the structured object - * const enhanced = { ...output, processed: true } - * return enhanced - * }) + * await Effect.runPromise(program) // => [{ message: "User action", level: "INFO" }] * ``` * * @category constructors @@ -637,13 +605,13 @@ export const formatStructured: Logger `{"service":"api-server","entry":${jsonString}}` - * ) + * const stableJson = Logger.map(Logger.formatJson, (json) => { + * const output = JSON.parse(json) + * return Formatter.formatJson({ message: output.message, level: output.level }) + * }) + * const program = Effect.gen(function*() { + * yield* Effect.log("Server started").pipe( + * Effect.provide(Logger.layer([Logger.withConsoleLog(stableJson)])) + * ) + * return yield* TestConsole.logLines + * }).pipe(Effect.provide(TestConsole.layer)) * - * const envelopedConsoleLogger = Logger.withConsoleLog(envelopedJsonLogger) + * await Effect.runPromise(program) // => ["{\"message\":\"Server started\",\"level\":\"INFO\"}"] * ``` * * @category constructors @@ -719,43 +674,26 @@ export const formatJson = map(formatStructured, Formatter.formatJson) * * **Example** (Batching logger output) * - * ```ts - * import { Duration, Effect, Logger } from "effect" + * ```ts import.meta.vitest + * import { Effect, Logger } from "effect" * - * // Create a batched logger that flushes every 5 seconds - * const batchedLogger = Logger.batched(Logger.formatJson, { - * window: Duration.seconds(5), + * const flushed: Array> = [] + * const messageLogger = Logger.make((options) => String(options.message)) + * const batchedLogger = Logger.batched(messageLogger, { + * window: "1 hour", * flush: (messages) => * Effect.sync(() => { - * console.log(`Flushing ${messages.length} log entries:`) - * messages.forEach((msg, i) => console.log(`${i + 1}. ${msg}`)) + * flushed.push(messages) * }) * }) * - * const program = Effect.gen(function*() { + * const program = Effect.scoped(Effect.gen(function*() { * const logger = yield* batchedLogger - * - * yield* Effect.provide( - * Effect.all([ - * Effect.log("Event 1"), - * Effect.log("Event 2"), - * Effect.log("Event 3"), - * Effect.sleep(Duration.seconds(6)), // Trigger flush - * Effect.log("Event 4") - * ]), - * Logger.layer([logger]) - * ) - * }) - * - * // Remote batch logging example - * const remoteBatchLogger = Logger.batched(Logger.formatStructured, { - * window: Duration.seconds(10), - * flush: (entries) => - * Effect.sync(() => { - * // Send batch to remote logging service - * console.log(`Sending ${entries.length} log entries to remote service`) - * }) - * }) + * yield* Effect.log("Event 1").pipe(Effect.provide(Logger.layer([logger]))) + * yield* Effect.log("Event 2").pipe(Effect.provide(Logger.layer([logger]))) + * })) + * await Effect.runPromise(program) + * flushed // => [["Event 1", "Event 2"]] * ``` * * @category constructors @@ -824,35 +762,11 @@ export const batched = dual< * * **Example** (Logging with pretty console output) * - * ```ts - * import { Effect, Logger } from "effect" - * - * // Use the pretty console logger with default settings - * const basicPretty = Effect.log("Hello Pretty Format").pipe( - * Effect.provide(Logger.layer([Logger.consolePretty()])) - * ) - * - * // Configure pretty logger options - * const customPretty = Logger.consolePretty({ - * colors: true, - * stderr: false, - * mode: "tty", - * formatDate: (date) => date.toLocaleTimeString() - * }) - * - * // Perfect for development environment - * const developmentProgram = Effect.gen(function*() { - * yield* Effect.log("Application starting") - * yield* Effect.logInfo("Database connected") - * yield* Effect.logWarning("High memory usage detected") - * }).pipe( - * Effect.annotateLogs("environment", "development"), - * Effect.withLogSpan("startup"), - * Effect.provide(Logger.layer([customPretty])) - * ) + * ```ts import.meta.vitest + * import { Logger } from "effect" * - * // Disable colors for CI/CD environments - * const ciLogger = Logger.consolePretty({ colors: false }) + * const prettyLogger = Logger.consolePretty({ colors: false }) + * Logger.isLogger(prettyLogger) // => true * ``` * * @category constructors @@ -878,33 +792,10 @@ export const consolePretty: ( * * **Example** (Logging logfmt output to the console) * - * ```ts - * import { Effect, Logger } from "effect" - * - * // Use the console logfmt logger - * const logfmtProgram = Effect.log("Hello LogFmt Console").pipe( - * Effect.provide(Logger.layer([Logger.consoleLogFmt])) - * ) - * - * // Great for production environments - * const productionProgram = Effect.gen(function*() { - * yield* Effect.log("Server started", { port: 8080, version: "1.0.0" }) - * yield* Effect.logInfo("Request processed", { userId: 123, duration: 45 }) - * yield* Effect.logError("Validation failed", { - * field: "email", - * value: "invalid" - * }) - * }).pipe( - * Effect.annotateLogs("service", "api"), - * Effect.withLogSpan("request-handler"), - * Effect.provide(Logger.layer([Logger.consoleLogFmt])) - * ) + * ```ts import.meta.vitest + * import { Logger } from "effect" * - * // Combine with other loggers - * const multiLoggerLive = Logger.layer([ - * Logger.consoleLogFmt, - * Logger.consolePretty() - * ]) + * Logger.isLogger(Logger.consoleLogFmt) // => true * ``` * * @category constructors @@ -926,41 +817,10 @@ export const consoleLogFmt: Logger = withConsoleLog(formatLogFmt) * * **Example** (Logging structured output to the console) * - * ```ts - * import { Effect, Logger } from "effect" - * - * // Use the console structured logger - * const structuredProgram = Effect.log("Hello Structured Console").pipe( - * Effect.provide(Logger.layer([Logger.consoleStructured])) - * ) - * - * // Perfect for development debugging - * const debugProgram = Effect.gen(function*() { - * yield* Effect.log("User event", { - * userId: 123, - * action: "login", - * ip: "192.168.1.1" - * }) - * yield* Effect.logInfo("API call", { - * endpoint: "/users", - * method: "GET", - * duration: 120 - * }) - * }).pipe( - * Effect.annotateLogs("requestId", "req-123"), - * Effect.withLogSpan("authentication"), - * Effect.provide(Logger.layer([Logger.consoleStructured])) - * ) + * ```ts import.meta.vitest + * import { Logger } from "effect" * - * // Easy to parse and inspect object structure - * const inspectionProgram = Effect.gen(function*() { - * yield* Effect.log("Complex data", { - * user: { id: 1, name: "John" }, - * metadata: { source: "api", version: 2 } - * }) - * }).pipe( - * Effect.provide(Logger.layer([Logger.consoleStructured])) - * ) + * Logger.isLogger(Logger.consoleStructured) // => true * ``` * * @category constructors @@ -980,46 +840,10 @@ export const consoleStructured: Logger = withConsoleLog(formatStr * * **Example** (Logging JSON output to the console) * - * ```ts - * import { Effect, Logger } from "effect" - * - * // Use the console JSON logger - * const jsonProgram = Effect.log("Hello JSON Console").pipe( - * Effect.provide(Logger.layer([Logger.consoleJson])) - * ) - * - * // Perfect for production logging and log aggregation - * const productionProgram = Effect.gen(function*() { - * yield* Effect.log("Server started", { port: 3000, env: "production" }) - * yield* Effect.logInfo("Request", { - * method: "POST", - * url: "/api/users", - * body: { name: "Alice" } - * }) - * yield* Effect.logError("Database error", { - * error: "Connection timeout", - * retryCount: 3 - * }) - * }).pipe( - * Effect.annotateLogs("service", "user-api"), - * Effect.annotateLogs("version", "1.2.3"), - * Effect.withLogSpan("request-processing"), - * Effect.provide(Logger.layer([Logger.consoleJson])) - * ) + * ```ts import.meta.vitest + * import { Logger } from "effect" * - * // Easy to pipe to log aggregation services - * const productionSetup = Logger.layer([ - * Logger.consoleJson, // For stdout JSON logs - * Logger.consolePretty() // For local debugging - * ]) - * - * // Ideal for containerized environments (Docker, Kubernetes) - * const containerProgram = Effect.log("Container ready", { - * containerId: "abc123", - * image: "myapp:latest" - * }).pipe( - * Effect.provide(Logger.layer([Logger.consoleJson])) - * ) + * Logger.isLogger(Logger.consoleJson) // => true * ``` * * @category constructors @@ -1042,40 +866,14 @@ export const consoleJson: Logger = withConsoleLog(formatJson) * * **Example** (Recording logs as trace span events) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Logger } from "effect" * - * // Tracer logger is included by default - logs automatically become span events - * const defaultProgram = Effect.gen(function*() { - * yield* Effect.log("This automatically becomes a span event") - * yield* Effect.logInfo("Processing data") - * }) - * - * // Explicitly combine tracer logger with other loggers - * const observabilityProgram = Effect.gen(function*() { - * yield* Effect.log("Operation started") - * yield* Effect.logInfo("Processing data") - * yield* Effect.logError("Error occurred") - * }).pipe( - * Effect.withLogSpan("data-processing"), - * Effect.provide(Logger.layer([ - * Logger.tracerLogger, - * Logger.consoleJson - * ])) - * ) - * - * // Perfect for correlating logs with traces in distributed systems - * const distributedProgram = Effect.gen(function*() { - * yield* Effect.log("Step 1: Fetching user data") - * yield* Effect.sleep("100 millis") - * yield* Effect.log("Step 2: Processing payment") - * yield* Effect.sleep("200 millis") - * yield* Effect.log("Step 3: Sending confirmation") - * }).pipe( - * Effect.withLogSpan("payment-workflow"), - * Effect.annotateLogs("userId", "user-123"), + * const program = Effect.log("span event").pipe( + * Effect.withSpan("operation"), * Effect.provide(Logger.layer([Logger.tracerLogger])) * ) + * Effect.runSync(program) * ``` * * @category constructors @@ -1094,37 +892,23 @@ export const tracerLogger: Logger = effect.tracerLogger * * **Example** (Providing logger layers) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Logger } from "effect" * - * // Single logger layer - * const JsonLoggerLive = Logger.layer([Logger.consoleJson]) - * - * // Multiple loggers layer - * const MultiLoggerLive = Logger.layer([ - * Logger.consoleJson, - * Logger.consolePretty(), - * Logger.formatStructured - * ]) - * - * // Merge with existing loggers - * const AdditionalLoggerLive = Logger.layer( - * [Logger.consoleJson], - * { mergeWithExisting: true } - * ) - * - * // Using multiple logger formats - * const jsonLogger = Logger.consoleJson - * const prettyLogger = Logger.consolePretty() - * - * const CustomLoggerLive = Logger.layer([jsonLogger, prettyLogger]) + * const messages: Array = [] + * const customLogger = Logger.make((options) => { + * messages.push(options.message) + * }) + * const CustomLoggerLive = Logger.layer([customLogger]) * * const program = Effect.log("Application started").pipe( * Effect.provide(CustomLoggerLive) * ) + * Effect.runSync(program) + * messages // => [["Application started"]] * ``` * - * @category context + * @category layers * @since 4.0.0 */ export const layer = < @@ -1162,91 +946,59 @@ export const layer = < * * **Example** (Writing JSON logs to a file) * - * ```ts - * import { Effect, Layer, Logger } from "effect" - * import { NodeFileSystem, NodeRuntime } from "@effect/platform-node" + * ```ts import.meta.vitest + * import { Effect, FileSystem, Logger } from "effect" * - * const fileLogger = Logger.formatJson.pipe( - * Logger.toFile("/tmp/log.txt") - * ) - * const LoggerLive = Logger.layer([fileLogger]).pipe( - * Layer.provide(NodeFileSystem.layer) - * ) - * - * Effect.log("a").pipe( - * Effect.andThen(Effect.log("b")), - * Effect.andThen(Effect.log("c")), - * Effect.provide(LoggerLive), - * NodeRuntime.runMain - * ) + * const writes: Array = [] + * const file = { + * 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 { Duration, Effect, Logger } from "effect" - * import { NodeFileSystem } from "@effect/platform-node" - * - * // Basic file logging. The scope keeps the file open while logs are emitted - * // and flushes pending entries when it closes. - * const basicFileLogger = Effect.scoped( - * Effect.gen(function*() { - * const fileLogger = yield* Logger.formatJson.pipe( - * Logger.toFile("/tmp/app.log") - * ) - * - * yield* Effect.log("Application started").pipe( - * Effect.provide(Logger.layer([fileLogger])) - * ) - * }) - * ).pipe( - * Effect.provide(NodeFileSystem.layer) - * ) + * ```ts import.meta.vitest + * import { Effect, FileSystem, Logger } from "effect" * - * // File logger with custom batch window - * const batchedFileLogger = Effect.scoped( - * Effect.gen(function*() { - * const fileLogger = yield* Logger.formatLogFmt.pipe( - * Logger.toFile("/var/log/myapp.log", { - * flag: "a", - * batchWindow: Duration.seconds(5) - * }) - * ) - * - * yield* Effect.all([ - * Effect.log("Event 1"), - * Effect.log("Event 2"), - * Effect.log("Event 3") - * ]).pipe( - * Effect.provide(Logger.layer([fileLogger])) - * ) + * const writes: Array = [] + * const file = { + * write: (buffer: Uint8Array) => Effect.sync(() => { + * writes.push(new TextDecoder().decode(buffer).trim()) + * return FileSystem.Size(buffer.length) * }) - * ).pipe( - * Effect.provide(NodeFileSystem.layer) - * ) + * } as unknown as FileSystem.File + * const fileSystem = FileSystem.makeNoop({ open: () => Effect.succeed(file) }) + * const messageLogger = Logger.make((options) => String(options.message)) * - * // Multiple loggers: console + file - * const multiLogger = Effect.scoped( - * Effect.gen(function*() { - * const fileLogger = yield* Logger.formatJson.pipe( - * Logger.toFile("/tmp/production.log") - * ) - * - * const loggerLive = Logger.layer([ - * Logger.consolePretty(), - * fileLogger - * ]) - * - * yield* Effect.log("Production event").pipe( - * Effect.provide(loggerLive) - * ) + * const program = Effect.scoped(Effect.gen(function*() { + * const fileLogger = yield* Logger.toFile(messageLogger, "/tmp/app.log", { + * batchWindow: "1 hour" * }) - * ).pipe( - * Effect.provide(NodeFileSystem.layer) - * ) + * yield* Effect.log("Application started").pipe( + * Effect.provide(Logger.layer([fileLogger])) + * ) + * })).pipe(Effect.provideService(FileSystem.FileSystem, fileSystem)) + * + * await Effect.runPromise(program) + * writes // => ["Application started"] * ``` * - * @category file + * @category logging * @since 4.0.0 */ export const toFile = dual< diff --git a/repos/effect/packages/effect/src/ManagedRuntime.ts b/repos/effect/packages/effect/src/ManagedRuntime.ts index f3252b7802..90117459ff 100644 --- a/repos/effect/packages/effect/src/ManagedRuntime.ts +++ b/repos/effect/packages/effect/src/ManagedRuntime.ts @@ -207,6 +207,16 @@ export interface ManagedRuntime { */ readonly dispose: () => Promise + /** + * Dispose of the resources associated with the runtime. + * + * **When to use** + * + * Use with the `await using` syntax to automatically dispose the runtime + * when it goes out of scope. + */ + readonly [Symbol.asyncDispose]: () => Promise + /** * Dispose of the resources associated with the runtime. * @@ -239,15 +249,17 @@ export interface ManagedRuntime { * * **Example** (Creating a managed runtime) * - * ```ts + * ```ts import.meta.vitest * import { Context, Effect, Layer, ManagedRuntime } from "effect" * + * const notifications: Array = [] + * * class Notifications extends Context.Service Effect.Effect * }>()("Notifications") { * static readonly layer = Layer.succeed(this)({ * notify: Effect.fn("Notifications.notify")((message) => - * Effect.sync(() => console.log(message)) + * Effect.sync(() => notifications.push(message)) * ) * }) * } @@ -259,15 +271,15 @@ export interface ManagedRuntime { * (_) => _.notify("Hello, world!") * ).pipe(Effect.ensuring(runtime.disposeEffect)) * - * runtime.runPromise(program) - * // Hello, world! + * await runtime.runPromise(program) + * notifications // => ["Hello, world!"] * ``` * * @see {@link ManagedRuntime} for the returned runtime interface * @see {@link Layer.MemoMap} for shared layer memoization * @see {@link Layer.build} for lower-level scoped layer construction * - * @category runtime class + * @category constructors * @since 2.0.0 */ export const make = ( @@ -324,6 +336,9 @@ export const make = ( dispose(): Promise { return Effect.runPromise(self.disposeEffect) }, + [Symbol.asyncDispose](): Promise { + return self.dispose() + }, disposeEffect: Effect.suspend(() => { ;(self as Mutable>).contextEffect = Effect.die("ManagedRuntime disposed") self.cachedContext = undefined diff --git a/repos/effect/packages/effect/src/Match.ts b/repos/effect/packages/effect/src/Match.ts index 2dfe2af23d..b97cdd7950 100644 --- a/repos/effect/packages/effect/src/Match.ts +++ b/repos/effect/packages/effect/src/Match.ts @@ -30,7 +30,7 @@ const TypeId = internal.TypeId * * **Example** (Matching string and number values) * - * ```ts + * ```ts import.meta.vitest * import { Match } from "effect" * * // Simulated dynamic input that can be a string or a number @@ -47,8 +47,7 @@ const TypeId = internal.TypeId * Match.exhaustive * ) * - * console.log(result) - * // Output: "string: some input" + * result // => "string: some input" * ``` * * @category models @@ -70,7 +69,7 @@ export type Matcher "String: hello" + * matcher(42) // => "Number: 42" * ``` * * @category models @@ -111,7 +110,7 @@ export interface TypeMatcher "Unknown type") * ) * - * console.log(result) // "User: Alice" + * result // => "User: Alice" * ``` * * @category models @@ -177,7 +176,7 @@ export type Case = When | Not * * **Example** (Creating positive match cases) * - * ```ts + * ```ts import.meta.vitest * import { Match } from "effect" * * // When creates cases that match specific patterns @@ -187,8 +186,8 @@ export type Case = When | Not * Match.exhaustive * ) * - * console.log(stringMatcher("hello")) // "Got string: hello" - * console.log(stringMatcher(42)) // "Got number: 42" + * stringMatcher("hello") // => "Got string: hello" + * stringMatcher(42) // => "Got number: 42" * ``` * * @category models @@ -211,7 +210,7 @@ export interface When { * * **Example** (Creating negative match cases) * - * ```ts + * ```ts import.meta.vitest * import { Match } from "effect" * * // Not creates cases that exclude specific patterns @@ -221,8 +220,8 @@ export interface When { * Match.orElse(() => "This string is forbidden") * ) * - * console.log(matcher("hello")) // "Allowed: hello" - * console.log(matcher("forbidden")) // "This string is forbidden" + * matcher("hello") // => "Allowed: hello" + * matcher("forbidden") // => "This string is forbidden" * ``` * * @category models @@ -250,7 +249,7 @@ export interface Not { * * **Example** (Matching Numbers and Strings) * - * ```ts + * ```ts import.meta.vitest * import { Match } from "effect" * * // Create a matcher for values that are either strings or numbers @@ -266,11 +265,9 @@ export interface Not { * Match.exhaustive * ) * - * console.log(match(0)) - * // Output: "number: 0" + * match(0) // => "number: 0" * - * console.log(match("hello")) - * // Output: "string: hello" + * match("hello") // => "string: hello" * ``` * * @see {@link value} for creating a matcher from a specific value. @@ -299,7 +296,7 @@ export const type: () => Matcher, I, never, never> = * * **Example** (Matching an Object by Property) * - * ```ts + * ```ts import.meta.vitest * import { Match } from "effect" * * const input = { name: "John", age: 30 } @@ -315,8 +312,7 @@ export const type: () => Matcher, I, never, never> = * Match.orElse(() => "Oh, not John") * ) * - * console.log(result) - * // Output: "John is 30 years old" + * result // => "John is 30 years old" * ``` * * @see {@link type} for creating a matcher from a specific type. @@ -339,7 +335,7 @@ export const value: ( * * **Example** (Matching value tags) * - * ```ts + * ```ts import.meta.vitest * import { Match } from "effect" * * type Status = { readonly _tag: "Success"; readonly data: string } @@ -351,7 +347,7 @@ export const value: ( * Success: (result) => `Success: ${result.data}` * }) * - * console.log(message) // "Success: Hello" + * message // => "Success: Hello" * ``` * * @category constructors @@ -383,7 +379,7 @@ export const valueTags: { * * **Example** (Matching type tags) * - * ```ts + * ```ts import.meta.vitest * import { Match } from "effect" * * type Result = @@ -398,11 +394,9 @@ export const valueTags: { * Loading: () => "Loading..." * }) * - * console.log(formatResult({ _tag: "Success", data: "Hello World" })) - * // Output: "Data: Hello World" + * formatResult({ _tag: "Success", data: "Hello World" }) // => "Data: Hello World" * - * console.log(formatResult({ _tag: "Error", message: "Network failed" })) - * // Output: "Error: Network failed" + * formatResult({ _tag: "Error", message: "Network failed" }) // => "Error: Network failed" * * // Create a matcher with inferred return type * const processResult = Match.typeTags()({ @@ -411,8 +405,7 @@ export const valueTags: { * Loading: () => ({ type: "pending" }) * }) * - * console.log(processResult({ _tag: "Loading" })) - * // Output: { type: "pending" } + * processResult({ _tag: "Loading" }) // => { type: "pending" } * ``` * * @category constructors @@ -453,7 +446,7 @@ export const typeTags: { * * **Example** (Validating return type consistency) * - * ```ts + * ```ts import.meta.vitest * import { Match } from "effect" * * const match = Match.type<{ a: number } | { b: string }>().pipe( @@ -493,7 +486,7 @@ export const withReturnType: () => ( * * **Example** (Matching with values and predicates) * - * ```ts + * ```ts import.meta.vitest * import { Match } from "effect" * * // Create a matcher for objects with an "age" property @@ -509,14 +502,11 @@ export const withReturnType: () => ( * Match.orElse((user: { age: number }) => `${user.age} is too young`) * ) * - * console.log(match({ age: 20 })) - * // Output: "Age: 20" + * match({ age: 20 }) // => "Age: 20" * - * console.log(match({ age: 18 })) - * // Output: "You can vote" + * match({ age: 18 }) // => "You can vote" * - * console.log(match({ age: 4 })) - * // Output: "4 is too young" + * match({ age: 4 }) // => "4 is too young" * ``` * * @see {@link whenOr} for handling any one of several patterns with the same handler @@ -524,7 +514,7 @@ export const withReturnType: () => ( * @see {@link not} for handling inputs that do not match a pattern * @see {@link orElse} for providing a fallback when no pattern case matches * - * @category Defining patterns + * @category defining patterns * @since 4.0.0 */ export const when: < @@ -561,7 +551,7 @@ export const when: < * * **Example** (Matching one of several patterns) * - * ```ts + * ```ts import.meta.vitest * import { Match } from "effect" * * type ErrorType = @@ -579,14 +569,12 @@ export const when: < * Match.exhaustive * ) * - * console.log(handleError({ _tag: "NetworkError", message: "No connection" })) - * // Output: "Retry the request" + * handleError({ _tag: "NetworkError", message: "No connection" }) // => "Retry the request" * - * console.log(handleError({ _tag: "ValidationError", field: "email" })) - * // Output: "Invalid field: email" + * handleError({ _tag: "ValidationError", field: "email" }) // => "Invalid field: email" * ``` * - * @category Defining patterns + * @category defining patterns * @since 4.0.0 */ export const whenOr: < @@ -623,7 +611,7 @@ export const whenOr: < * * **Example** (Matching all provided patterns) * - * ```ts + * ```ts import.meta.vitest * import { Match } from "effect" * * type User = { readonly age: number; readonly role: "admin" | "user" } @@ -637,14 +625,12 @@ export const whenOr: < * Match.orElse(() => "Access denied") * ) * - * console.log(checkUser({ age: 20, role: "admin" })) - * // Output: "Admin access granted" + * checkUser({ age: 20, role: "admin" }) // => "Admin access granted" * - * console.log(checkUser({ age: 20, role: "user" })) - * // Output: "Access denied" + * checkUser({ age: 20, role: "user" }) // => "Access denied" * ``` * - * @category Defining patterns + * @category defining patterns * @since 4.0.0 */ export const whenAnd: < @@ -681,7 +667,7 @@ export const whenAnd: < * * **Example** (Matching on a discriminator field) * - * ```ts + * ```ts import.meta.vitest * import { Match, pipe } from "effect" * * const match = pipe( @@ -695,12 +681,14 @@ export const whenAnd: < * Match.discriminator("type")("C", (_) => `C(${_.c})`), * Match.exhaustive * ) + * match({ type: "A", a: "ok" }) // => "A or B: A" + * match({ type: "C", c: true }) // => "C(true)" * ``` * * @see {@link discriminators} for defining several discriminator handlers at once * @see {@link discriminatorStartsWith} for matching string discriminator values by prefix * - * @category Defining patterns + * @category defining patterns * @since 4.0.0 */ export const discriminator: ( @@ -734,7 +722,7 @@ export const discriminator: ( * * **Example** (Matching discriminator prefixes) * - * ```ts + * ```ts import.meta.vitest * import { Match, pipe } from "effect" * * const match = pipe( @@ -744,14 +732,14 @@ export const discriminator: ( * Match.orElse((_) => 3 as const) * ) * - * console.log(match({ type: "A" })) // 1 - * console.log(match({ type: "B" })) // 2 - * console.log(match({ type: "A.A" })) // 1 + * match({ type: "A" }) // => 1 + * match({ type: "B" }) // => 2 + * match({ type: "A.A" }) // => 1 * ``` * * @see {@link discriminator} for matching exact discriminator values * - * @category Defining patterns + * @category defining patterns * @since 4.0.0 */ export const discriminatorStartsWith: ( @@ -790,7 +778,7 @@ export const discriminatorStartsWith: ( * * **Example** (Mapping discriminator handlers) * - * ```ts + * ```ts import.meta.vitest * import { Match, pipe } from "effect" * * const match = pipe( @@ -807,12 +795,14 @@ export const discriminatorStartsWith: ( * }), * Match.exhaustive * ) + * match({ type: "A", a: "ok" }) // => "ok" + * match({ type: "B", b: 42 }) // => 42 * ``` * * @see {@link discriminator} for adding one discriminator case to a matcher pipeline * @see {@link discriminatorsExhaustive} for handling every discriminator value and finalizing the matcher * - * @category Defining patterns + * @category defining patterns * @since 4.0.0 */ export const discriminators: ( @@ -854,7 +844,7 @@ export const discriminators: ( * * **Example** (Handling all discriminator cases) * - * ```ts + * ```ts import.meta.vitest * import { Match, pipe } from "effect" * * const match = pipe( @@ -870,11 +860,12 @@ export const discriminators: ( * C: (c) => c.c * }) * ) + * match({ type: "C", c: true }) // => true * ``` * * @see {@link discriminators} for defining discriminator handlers without finalizing the matcher * - * @category Defining patterns + * @category defining patterns * @since 4.0.0 */ export const discriminatorsExhaustive: ( @@ -907,7 +898,7 @@ export const discriminatorsExhaustive: ( * * **Example** (Matching a discriminated union by tag) * - * ```ts + * ```ts import.meta.vitest * import { Match } from "effect" * * type Event = @@ -926,14 +917,12 @@ export const discriminatorsExhaustive: ( * Match.exhaustive * ) * - * console.log(match({ _tag: "success", data: "Hello" })) - * // Output: "Ok!" + * match({ _tag: "success", data: "Hello" }) // => "Ok!" * - * console.log(match({ _tag: "error", error: new Error("Oops!") })) - * // Output: "Error: Oops!" + * match({ _tag: "error", error: new Error("Oops!") }) // => "Error: Oops!" * ``` * - * @category Defining patterns + * @category defining patterns * @since 4.0.0 */ export const tag: < @@ -966,7 +955,7 @@ export const tag: < * * **Example** (Matching tag prefixes) * - * ```ts + * ```ts import.meta.vitest * import { Match, pipe } from "effect" * * const match = pipe( @@ -976,12 +965,12 @@ export const tag: < * Match.orElse((_) => 3 as const) * ) * - * console.log(match({ _tag: "A" })) // 1 - * console.log(match({ _tag: "B" })) // 2 - * console.log(match({ _tag: "A.A" })) // 1 + * match({ _tag: "A" }) // => 1 + * match({ _tag: "B" }) // => 2 + * match({ _tag: "A.A" }) // => 1 * ``` * - * @category Defining patterns + * @category defining patterns * @since 4.0.0 */ export const tagStartsWith: < @@ -1016,7 +1005,7 @@ export const tagStartsWith: < * * **Example** (Mapping tag handlers) * - * ```ts + * ```ts import.meta.vitest * import { Match, pipe } from "effect" * * const match = pipe( @@ -1033,9 +1022,10 @@ export const tagStartsWith: < * }), * Match.exhaustive * ) + * match({ _tag: "A", a: "ok" }) // => "ok" * ``` * - * @category Defining patterns + * @category defining patterns * @since 4.0.0 */ export const tags: < @@ -1071,7 +1061,7 @@ export const tags: < * * **Example** (Handling all tag cases) * - * ```ts + * ```ts import.meta.vitest * import { Match, pipe } from "effect" * * const match = pipe( @@ -1087,9 +1077,10 @@ export const tags: < * C: (c) => c.c * }) * ) + * match({ _tag: "B", b: 42 }) // => 42 * ``` * - * @category Defining patterns + * @category defining patterns * @since 4.0.0 */ export const tagsExhaustive: < @@ -1120,7 +1111,7 @@ export const tagsExhaustive: < * * **Example** (Ignoring a specific value) * - * ```ts + * ```ts import.meta.vitest * import { Match } from "effect" * * // Create a matcher for string or number values @@ -1131,16 +1122,14 @@ export const tagsExhaustive: < * Match.orElse(() => "fallback") * ) * - * console.log(match("hello")) - * // Output: "ok" + * match("hello") // => "ok" * - * console.log(match("hi")) - * // Output: "fallback" + * match("hi") // => "fallback" * ``` * * @see {@link when} for adding a positive pattern case * - * @category Defining patterns + * @category defining patterns * @since 4.0.0 */ export const not: < @@ -1176,7 +1165,7 @@ export const not: < * * **Example** (Matching non-empty strings) * - * ```ts + * ```ts import.meta.vitest * import { Match } from "effect" * * const processInput = Match.type() @@ -1185,19 +1174,16 @@ export const not: < * Match.orElse(() => "Input cannot be empty") * ) * - * console.log(processInput("hello")) - * // Output: "Valid input: hello" + * processInput("hello") // => "Valid input: hello" * - * console.log(processInput("")) - * // Output: "Input cannot be empty" + * processInput("") // => "Input cannot be empty" * - * console.log(processInput(" ")) - * // Output: "Valid input: " (whitespace-only strings are considered non-empty) + * processInput(" ") // => "Valid input: " * ``` * * @see {@link string} for matching any string * - * @category predicates + * @category guards * @since 4.0.0 */ export const nonEmptyString: SafeRefinement = internal.nonEmptyString @@ -1216,7 +1202,7 @@ export const nonEmptyString: SafeRefinement = internal.nonEmptySt * * **Example** (Matching literal values) * - * ```ts + * ```ts import.meta.vitest * import { Match } from "effect" * * const handleStatus = Match.type() @@ -1227,23 +1213,18 @@ export const nonEmptyString: SafeRefinement = internal.nonEmptySt * Match.orElse((value) => `Unknown status: ${value}`) * ) * - * console.log(handleStatus("success")) - * // Output: "Operation successful" + * handleStatus("success") // => "Operation successful" * - * console.log(handleStatus(200)) - * // Output: "Operation successful" + * handleStatus(200) // => "Operation successful" * - * console.log(handleStatus("failed")) - * // Output: "Operation failed" + * handleStatus("failed") // => "Operation failed" * - * console.log(handleStatus(0)) - * // Output: "Falsy value" + * handleStatus(0) // => "Falsy value" * - * console.log(handleStatus("pending")) - * // Output: "Unknown status: pending" + * handleStatus("pending") // => "Unknown status: pending" * ``` * - * @category predicates + * @category guards * @since 4.0.0 */ export const is: < @@ -1260,7 +1241,7 @@ export const is: < * * **Example** (Matching string values) * - * ```ts + * ```ts import.meta.vitest * import { Match } from "effect" * * const processValue = Match.type().pipe( @@ -1270,12 +1251,12 @@ export const is: < * Match.exhaustive * ) * - * console.log(processValue("hello")) // "String: HELLO" - * console.log(processValue(42)) // "Number: 84" - * console.log(processValue(true)) // "Boolean: yes" + * processValue("hello") // => "String: HELLO" + * processValue(42) // => "Number: 84" + * processValue(true) // => "Boolean: yes" * ``` * - * @category predicates + * @category guards * @since 4.0.0 */ export const string: Predicate.Refinement = Predicate.isString @@ -1295,7 +1276,7 @@ export const string: Predicate.Refinement = Predicate.isString * * **Example** (Matching number values) * - * ```ts + * ```ts import.meta.vitest * import { Match } from "effect" * * const categorizeNumber = Match.type().pipe( @@ -1308,15 +1289,15 @@ export const string: Predicate.Refinement = Predicate.isString * Match.orElse(() => "Not a number type") * ) * - * console.log(categorizeNumber(42)) // "Integer: 42" - * console.log(categorizeNumber(3.14)) // "Float: 3.14" - * console.log(categorizeNumber(NaN)) // "Not a number" - * console.log(categorizeNumber("hello")) // "Not a number type" + * categorizeNumber(42) // => "Integer: 42" + * categorizeNumber(3.14) // => "Float: 3.14" + * categorizeNumber(NaN) // => "Not a number" + * categorizeNumber("hello") // => "Not a number type" * ``` * * @see {@link bigint} for matching primitive bigint values * - * @category predicates + * @category guards * @since 4.0.0 */ export const number: Predicate.Refinement = Predicate.isNumber @@ -1341,7 +1322,7 @@ export const number: Predicate.Refinement = Predicate.isNumber * * **Example** (Matching any remaining value) * - * ```ts + * ```ts import.meta.vitest * import { Match } from "effect" * * const describeValue = Match.type() @@ -1353,23 +1334,19 @@ export const number: Predicate.Refinement = Predicate.isNumber * Match.exhaustive * ) * - * console.log(describeValue("hello")) - * // Output: "String: hello" + * describeValue("hello") // => "String: hello" * - * console.log(describeValue(42)) - * // Output: "Number: 42" + * describeValue(42) // => "Number: 42" * - * console.log(describeValue([1, 2, 3])) - * // Output: "Other: object" + * describeValue([1, 2, 3]) // => "Other: object" * - * console.log(describeValue(null)) - * // Output: "Other: object" + * describeValue(null) // => "Other: object" * ``` * * @see {@link defined} for matching only non-nullish values * @see {@link orElse} for providing a fallback after earlier cases * - * @category predicates + * @category guards * @since 4.0.0 */ export const any: SafeRefinement = internal.any @@ -1388,7 +1365,7 @@ export const any: SafeRefinement = internal.any * * **Example** (Matching defined values) * - * ```ts + * ```ts import.meta.vitest * import { Match } from "effect" * * const processValue = Match.type() @@ -1397,28 +1374,22 @@ export const any: SafeRefinement = internal.any * Match.orElse(() => "Value is null or undefined") * ) * - * console.log(processValue("hello")) - * // Output: "Defined value: hello" + * processValue("hello") // => "Defined value: hello" * - * console.log(processValue(42)) - * // Output: "Defined value: 42" + * processValue(42) // => "Defined value: 42" * - * console.log(processValue(0)) - * // Output: "Defined value: 0" + * processValue(0) // => "Defined value: 0" * - * console.log(processValue("")) - * // Output: "Defined value: " + * processValue("") // => "Defined value: " * - * console.log(processValue(null)) - * // Output: "Value is null or undefined" + * processValue(null) // => "Value is null or undefined" * - * console.log(processValue(undefined)) - * // Output: "Value is null or undefined" + * processValue(undefined) // => "Value is null or undefined" * ``` * * @see {@link any} for matching every value without excluding nullish inputs * - * @category predicates + * @category guards * @since 4.0.0 */ export const defined: (u: A) => u is A & {} = internal.defined @@ -1437,7 +1408,7 @@ export const defined: (u: A) => u is A & {} = internal.defined * * **Example** (Matching boolean values) * - * ```ts + * ```ts import.meta.vitest * import { Match } from "effect" * * const describeTruthiness = Match.type().pipe( @@ -1451,15 +1422,15 @@ export const defined: (u: A) => u is A & {} = internal.defined * Match.orElse(() => "Some other truthy value") * ) * - * console.log(describeTruthiness(true)) // "Definitely true" - * console.log(describeTruthiness(false)) // "Definitely false" - * console.log(describeTruthiness(0)) // "Falsy number" - * console.log(describeTruthiness(1)) // "Some other truthy value" + * describeTruthiness(true) // => "Definitely true" + * describeTruthiness(false) // => "Definitely false" + * describeTruthiness(0) // => "Falsy number" + * describeTruthiness(1) // => "Some other truthy value" * ``` * * @see {@link is} for matching specific literal boolean values * - * @category predicates + * @category guards * @since 4.0.0 */ export const boolean: Predicate.Refinement = Predicate.isBoolean @@ -1481,7 +1452,7 @@ export { * @see {@link defined} for matching non-nullish values * @see {@link is} for matching literal values * - * @category predicates + * @category guards * @since 4.0.0 */ _undefined as undefined @@ -1504,7 +1475,7 @@ export { * @see {@link defined} for matching non-nullish values * @see {@link is} for matching literal values * - * @category predicates + * @category guards * @since 4.0.0 */ _null as null @@ -1524,7 +1495,7 @@ export { * * **Example** (Matching bigint values) * - * ```ts + * ```ts import.meta.vitest * import { Match } from "effect" * * const processLargeNumber = Match.type().pipe( @@ -1538,15 +1509,15 @@ export { * Match.orElse(() => "Not a numeric type") * ) * - * console.log(processLargeNumber(123n)) // "BigInt: 123" - * console.log(processLargeNumber(9007199254740992n)) // "Large integer: 9007199254740992" - * console.log(processLargeNumber(123)) // "Regular number: 123" - * console.log(processLargeNumber("123")) // "Not a numeric type" + * processLargeNumber(123n) // => "BigInt: 123" + * processLargeNumber(9007199254740992n) // => "Large integer: 9007199254740992" + * processLargeNumber(123) // => "Regular number: 123" + * processLargeNumber("123") // => "Not a numeric type" * ``` * * @see {@link number} for matching primitive number values * - * @category predicates + * @category guards * @since 4.0.0 */ export const bigint: Predicate.Refinement = Predicate.isBigInt @@ -1562,7 +1533,7 @@ export const bigint: Predicate.Refinement = Predicate.isBigInt * * **Example** (Matching symbol values) * - * ```ts + * ```ts import.meta.vitest * import { Match } from "effect" * * const mySymbol = Symbol("my-symbol") @@ -1579,12 +1550,12 @@ export const bigint: Predicate.Refinement = Predicate.isBigInt * Match.orElse(() => "Not a symbol") * ) * - * console.log(handleSymbol(mySymbol)) // "Symbol with description: my-symbol" - * console.log(handleSymbol(Symbol())) // "Symbol without description" - * console.log(handleSymbol("string")) // "Not a symbol" + * handleSymbol(mySymbol) // => "Symbol with description: my-symbol" + * handleSymbol(Symbol()) // => "Symbol without description" + * handleSymbol("string") // => "Not a symbol" * ``` * - * @category predicates + * @category guards * @since 4.0.0 */ export const symbol: Predicate.Refinement = Predicate.isSymbol @@ -1604,7 +1575,7 @@ export const symbol: Predicate.Refinement = Predicate.isSymbol * * **Example** (Matching Date instances) * - * ```ts + * ```ts import.meta.vitest * import { Match } from "effect" * * const processDateValue = Match.type().pipe( @@ -1618,15 +1589,15 @@ export const symbol: Predicate.Refinement = Predicate.isSymbol * Match.orElse(() => "Not a date-related value") * ) * - * console.log(processDateValue(new Date("2024-01-01"))) // "Date: 2024-01-01" - * console.log(processDateValue(new Date("invalid"))) // "Invalid date" - * console.log(processDateValue("2024-01-01")) // "Date string: 2024-01-01" - * console.log(processDateValue(1704067200000)) // "Not a date-related value" + * processDateValue(new Date("2024-01-01")) // => "Date: 2024-01-01" + * processDateValue(new Date("invalid")) // => "Invalid date" + * processDateValue("2024-01-01") // => "Date string: 2024-01-01" + * processDateValue(1704067200000) // => "Not a date-related value" * ``` * * @see {@link instanceOf} for matching instances of any constructor * - * @category predicates + * @category guards * @since 4.0.0 */ export const date: Predicate.Refinement = Predicate.isDate @@ -1647,7 +1618,7 @@ export const date: Predicate.Refinement = Predicate.isDate * * **Example** (Matching record objects) * - * ```ts + * ```ts import.meta.vitest * import { Match } from "effect" * * const analyzeValue = Match.type().pipe( @@ -1663,15 +1634,15 @@ export const date: Predicate.Refinement = Predicate.isDate * Match.orElse(() => "Not an object") * ) * - * console.log(analyzeValue({ name: "Alice", age: 30 })) // "Object with 2 properties: [name, age]" - * console.log(analyzeValue([1, 2, 3])) // "Array with 3 items" - * console.log(analyzeValue(null)) // "Not an object" - * console.log(analyzeValue("hello")) // "Not an object" + * analyzeValue({ name: "Alice", age: 30 }) // => "Object with 2 properties: [name, age]" + * analyzeValue([1, 2, 3]) // => "Array with 3 items" + * analyzeValue(null) // => "Not an object" + * analyzeValue("hello") // => "Not an object" * ``` * * @see {@link instanceOf} for matching a specific constructor * - * @category predicates + * @category guards * @since 4.0.0 */ export const record: Predicate.Refinement = Predicate.isObject @@ -1691,7 +1662,7 @@ export const record: Predicate.Refinement `Other: ${typeof value}`) * ) * - * console.log(handleValue(new CustomError("Failed", 404))) // "Custom error: Failed (code: 404)" - * console.log(handleValue(new Error("Generic error"))) // "Standard error: Generic error" - * console.log(handleValue([1, 2, 3])) // "Array with 3 items" - * console.log(handleValue(new Map([["count", 1]]))) // "Map with 1 entries" + * handleValue(new CustomError("Failed", 404)) // => "Custom error: Failed (code: 404)" + * handleValue(new Error("Generic error")) // => "Standard error: Generic error" + * handleValue([1, 2, 3]) // => "Array with 3 items" + * handleValue(new Map([["count", 1]])) // => "Map with 1 entries" * ``` * * @see {@link instanceOfUnsafe} for constructor matching without the same type-safety guarantee * @see {@link record} for matching broad non-null, non-array objects * - * @category predicates + * @category guards * @since 4.0.0 */ export const instanceOf: any>( @@ -1752,7 +1723,7 @@ export const instanceOf: any>( * * **Example** (Matching class instances unsafely) * - * ```ts + * ```ts import.meta.vitest * import { Match } from "effect" * * class CustomError extends Error { @@ -1770,11 +1741,12 @@ export const instanceOf: any>( * }), * Match.orElse(() => "Not a CustomError") * ) + * handleError(new CustomError("failed", 500)) // => "Custom error 500: failed" * ``` * * @see {@link instanceOf} for type-safe constructor matching * - * @category predicates + * @category guards * @since 4.0.0 */ export const instanceOfUnsafe: any>( @@ -1797,7 +1769,7 @@ export const instanceOfUnsafe: any>( * * **Example** (Providing a default value when no patterns match) * - * ```ts + * ```ts import.meta.vitest * import { Match } from "effect" * * // Create a matcher for string or number values @@ -1808,11 +1780,9 @@ export const instanceOfUnsafe: any>( * Match.orElse(() => "fallback") * ) * - * console.log(match("a")) - * // Output: "ok" + * match("a") // => "ok" * - * console.log(match("b")) - * // Output: "fallback" + * match("b") // => "fallback" * ``` * * @see {@link option} for finalizing unmatched input as `Option.none` @@ -1849,7 +1819,7 @@ export const orElse: Ret>( * * **Example** (Throwing on unmatched input) * - * ```ts + * ```ts import.meta.vitest * import { Match } from "effect" * * const strictMatcher = Match.type<"a" | "b">().pipe( @@ -1859,8 +1829,8 @@ export const orElse: Ret>( * Match.orElseAbsurd * ) * - * console.log(strictMatcher("a")) // "Found A" - * console.log(strictMatcher("b")) // "Found B" + * strictMatcher("a") // => "Found A" + * strictMatcher("b") // => "Found B" * * // This would throw an error at runtime: * // strictMatcher("c" as any) // throws @@ -1893,7 +1863,7 @@ export const orElseAbsurd: ( * * **Example** (Extracting a user role with `Match.result`) * - * ```ts + * ```ts import.meta.vitest * import { Match } from "effect" * * type User = { readonly role: "admin" | "editor" | "viewer" } @@ -1905,11 +1875,9 @@ export const orElseAbsurd: ( * Match.result // Wrap the result in an Result * ) * - * console.log(getRole({ role: "admin" })) - * // Output: { _id: 'Result', _tag: 'Ok', ok: 'Has full access' } + * getRole({ role: "admin" })._tag // => "Success" * - * console.log(getRole({ role: "viewer" })) - * // Output: { _id: 'Result', _tag: 'Err', err: { role: 'viewer' } } + * getRole({ role: "viewer" })._tag // => "Failure" * ``` * * @category completion @@ -1939,7 +1907,7 @@ export const result: ( * * **Example** (Extracting a user role with `Match.option`) * - * ```ts + * ```ts import.meta.vitest * import { Match } from "effect" * * type User = { readonly role: "admin" | "editor" | "viewer" } @@ -1951,11 +1919,9 @@ export const result: ( * Match.option // Wrap the result in an Option * ) * - * console.log(getRole({ role: "admin" })) - * // Output: { _id: 'Option', _tag: 'Some', value: 'Has full access' } + * getRole({ role: "admin" })._tag // => "Some" * - * console.log(getRole({ role: "viewer" })) - * // Output: { _id: 'Option', _tag: 'None' } + * getRole({ role: "viewer" })._tag // => "None" * ``` * * @see {@link result} for preserving unmatched input as a `Result` failure @@ -1983,7 +1949,7 @@ export const option: ( * * **Example** (Ensuring all cases are covered) * - * ```ts + * ```ts import.meta.vitest * import { Match } from "effect" * * // Create a matcher for string or number values @@ -2017,7 +1983,7 @@ const SafeRefinementId = "~effect/match/Match/SafeRefinement" * * **Example** (Using safe refinements) * - * ```ts + * ```ts import.meta.vitest * import { Match } from "effect" * * // Built-in safe refinements @@ -2028,10 +1994,10 @@ const SafeRefinementId = "~effect/match/Match/SafeRefinement" * Match.orElse(() => "Undefined or null") * ) * - * console.log(processValue("hello")) // "HELLO" - * console.log(processValue(21)) // 42 - * console.log(processValue(true)) // "Defined: true" - * console.log(processValue(null)) // "Undefined or null" + * processValue("hello") // => "HELLO" + * processValue(21) // => 42 + * processValue(true) // => "Defined: true" + * processValue(null) // => "Undefined or null" * ``` * * @category models @@ -2068,7 +2034,7 @@ export declare namespace Types { * * **Example** (Computing matched types) * - * ```ts + * ```ts import.meta.vitest * import type { Match } from "effect" * * // WhenMatch computes the narrowed type after pattern matching @@ -2085,7 +2051,7 @@ export declare namespace Types { * // Result: { type: "user"; name: string } * ``` * - * @category types + * @category utility types * @since 4.0.0 */ export type WhenMatch = @@ -2113,7 +2079,7 @@ export declare namespace Types { * * **Example** (Computing unmatched types) * - * ```ts + * ```ts import.meta.vitest * import type { Match } from "effect" * * // NotMatch computes what remains after exclusion @@ -2127,7 +2093,7 @@ export declare namespace Types { * // Result: "b" | "c" * ``` * - * @category types + * @category utility types * @since 4.0.0 */ export type NotMatch = Exclude>> @@ -2146,7 +2112,7 @@ export declare namespace Types { * * **Example** (Resolving match patterns) * - * ```ts + * ```ts import.meta.vitest * import type { Match } from "effect" * * // PForMatch resolves patterns to their matched types @@ -2157,7 +2123,7 @@ export declare namespace Types { * // Result: { name: string } * ``` * - * @category types + * @category utility types * @since 4.0.0 */ export type PForMatch

= [ResolvePred

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

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

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

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

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

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

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

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

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

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

= P extends Effect ? _R : * @see {@link Error} for extracting ordinary failures from a `Pull` * @see {@link Leftover} for extracting the completion leftover type * - * @category type extractors + * @category utility types * @since 4.0.0 */ export type ExcludeDone = Exclude> @@ -151,7 +151,7 @@ export type ExcludeDone = Exclude> * @see {@link matchEffect} for handling success, ordinary failure, and done outcomes explicitly * @see {@link filterDoneLeftover} for extracting a done leftover from an existing `Cause` * - * @category Done + * @category error handling * @since 4.0.0 */ export const catchDone: { @@ -180,7 +180,7 @@ export const catchDone: { * @see {@link filterDone} for extracting the `Cause.Done` value from a `Cause` * @see {@link filterNoDone} for selecting causes with no done failures * - * @category Done + * @category predicates * @since 4.0.0 */ export const isDoneCause = (cause: Cause.Cause): boolean => cause.reasons.some(isDoneFailure) @@ -197,7 +197,7 @@ export const isDoneCause = (cause: Cause.Cause): boolean => cause.reasons. * @see {@link isDoneCause} for checking an entire `Cause` for any done reason * @see {@link filterDone} for extracting the `Cause.Done` value from a `Cause` * - * @category Done + * @category guards * @since 4.0.0 */ export const isDoneFailure = ( @@ -217,7 +217,7 @@ export const isDoneFailure = ( * Returns a successful `Result` with the `Cause.Done` value when one is * present, otherwise returns a failed `Result` containing the non-done cause. * - * @category Done + * @category filtering * @since 4.0.0 */ export const filterDone: ( @@ -245,7 +245,7 @@ export const filterDone: ( * @see {@link filterDoneLeftover} for extracting only the done leftover value * @see {@link filterNoDone} for the inverse filter that succeeds only when no done failure is present * - * @category Done + * @category filtering * @since 4.0.0 */ export const filterDoneVoid: ( @@ -271,7 +271,7 @@ export const filterDoneVoid: ( * @see {@link filterDone} for the inverse typed done filter * @see {@link filterDoneVoid} for done detection when the payload is not needed * - * @category Done + * @category filtering * @since 4.0.0 */ export const filterNoDone: ( @@ -291,7 +291,7 @@ export const filterNoDone: ( * Use to extract only the leftover value carried by a `Cause.Done` completion * signal. * - * @category Done + * @category filtering * @since 4.0.0 */ export const filterDoneLeftover: ( @@ -319,7 +319,7 @@ export const filterDoneLeftover: ( * @see {@link filterDone} for extracting the done signal without converting the cause to an `Exit` * @see {@link matchEffect} for handling `Pull` success, failure, and done outcomes directly * - * @category Done + * @category converting * @since 4.0.0 */ export const doneExitFromCause = (cause: Cause.Cause): Exit.Exit, ExcludeDone> => { @@ -336,7 +336,7 @@ export const doneExitFromCause = (cause: Cause.Cause): Exit.Exit(cause: Cause.Cause): Exit.Exit Effect.succeed(`Got error: ${cause}`), * onDone: (leftover) => Effect.succeed(`Stream halted with: ${leftover}`) * }) + * + * await Effect.runPromise(result) // => "Stream halted with: stream ended" * ``` * * @category pattern matching diff --git a/repos/effect/packages/effect/src/Queue.ts b/repos/effect/packages/effect/src/Queue.ts index bd0b4a2e9c..d31edc0e1a 100644 --- a/repos/effect/packages/effect/src/Queue.ts +++ b/repos/effect/packages/effect/src/Queue.ts @@ -142,7 +142,7 @@ export const asDequeue: (self: Queue) => Dequeue = identity * * **Example** (Offering through enqueue handles) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Queue } from "effect" * * // Function that only needs write access to a queue @@ -155,7 +155,10 @@ export const asDequeue: (self: Queue) => Dequeue = identity * const program = Effect.gen(function*() { * const queue = yield* Queue.bounded(10) * yield* producer(queue) + * return yield* Queue.takeAll(queue) * }) + * + * await Effect.runPromise(program) // => ["hello", "world", "!"] * ``` * * @category models @@ -206,7 +209,7 @@ export declare namespace Enqueue { * * **Example** (Taking through dequeue handles) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Queue } from "effect" * * const program = Effect.gen(function*() { @@ -220,8 +223,10 @@ export declare namespace Enqueue { * * // Take elements using dequeue interface * const item = yield* Queue.take(dequeue) - * console.log(item) // "a" + * return item * }) + * + * await Effect.runPromise(program) // => "a" * ``` * * @category models @@ -270,7 +275,7 @@ export declare namespace Dequeue { * * **Example** (Offering and taking queue values) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Queue } from "effect" * * const program = Effect.gen(function*() { @@ -286,8 +291,10 @@ export declare namespace Dequeue { * const item2 = yield* Queue.take(queue) * const item3 = yield* Queue.take(queue) * - * console.log([item1, item2, item3]) // ["hello", "world", "!"] + * return [item1, item2, item3] * }) + * + * await Effect.runPromise(program) // => ["hello", "world", "!"] * ``` * * @category models @@ -408,10 +415,10 @@ const QueueProto = { * * **Example** (Creating queues) * - * ```ts + * ```ts import.meta.vitest * import { Cause, Effect, Queue } from "effect" * - * Effect.gen(function*() { + * const program = Effect.gen(function*() { * const queue = yield* Queue.make() * * // add messages to the queue @@ -421,18 +428,18 @@ const QueueProto = { * * // take messages from the queue * const messages = yield* Queue.takeAll(queue) - * console.log(messages) // [1, 2, 3, 4, 5] * * // signal that the queue is done * yield* Queue.end(queue) * const done = yield* Effect.flip(Queue.take(queue)) - * console.log(Cause.isDone(done)) // true * * // signal that another queue has failed * const failedQueue = yield* Queue.make() * const failed = yield* Queue.fail(failedQueue, "boom") - * console.log(failed) // true + * return { messages, done, failed } * }) + * + * await Effect.runPromise(program) // => { messages: [1, 2, 3, 4, 5], done: Cause.Done(), failed: true } * ``` * * @category constructors @@ -470,7 +477,7 @@ export const make = ( * * **Example** (Creating bounded queues) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Queue } from "effect" * * const program = Effect.gen(function*() { @@ -481,8 +488,10 @@ export const make = ( * yield* Queue.offer(queue, "second") * * const size = yield* Queue.size(queue) - * console.log(size) // 2 + * return size * }) + * + * await Effect.runPromise(program) // => 2 * ``` * * @category constructors @@ -501,7 +510,7 @@ export const bounded = (capacity: number): Effect> => * * **Example** (Creating sliding queues) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Queue } from "effect" * * const program = Effect.gen(function*() { @@ -516,8 +525,10 @@ export const bounded = (capacity: number): Effect> => * yield* Queue.offer(queue, 4) * * const all = yield* Queue.takeAll(queue) - * console.log(all) // [2, 3, 4] - oldest element (1) was dropped + * return all * }) + * + * await Effect.runPromise(program) // => [2, 3, 4] * ``` * * @category constructors @@ -536,7 +547,7 @@ export const sliding = (capacity: number): Effect> => * * **Example** (Creating dropping queues) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Queue } from "effect" * * const program = Effect.gen(function*() { @@ -545,15 +556,15 @@ export const sliding = (capacity: number): Effect> => * // Fill the queue to capacity * const success1 = yield* Queue.offer(queue, 1) * const success2 = yield* Queue.offer(queue, 2) - * console.log(success1, success2) // true, true * * // This will be dropped * const success3 = yield* Queue.offer(queue, 3) - * console.log(success3) // false * * const all = yield* Queue.takeAll(queue) - * console.log(all) // [1, 2] - element 3 was dropped + * return [success1, success2, success3, all] * }) + * + * await Effect.runPromise(program) // => [true, true, false, [1, 2]] * ``` * * @category constructors @@ -572,7 +583,7 @@ export const dropping = (capacity: number): Effect> => * * **Example** (Creating unbounded queues) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Queue } from "effect" * * const program = Effect.gen(function*() { @@ -585,12 +596,13 @@ export const dropping = (capacity: number): Effect> => * * // Check current size * const size = yield* Queue.size(queue) - * console.log(size) // 5 * * // Take all messages * const messages = yield* Queue.takeAll(queue) - * console.log(messages) // ["message1", "message2", "message3", "message4", "message5"] + * return { size, messages } * }) + * + * await Effect.runPromise(program) // => { size: 5, messages: ["message1", "message2", "message3", "message4", "message5"] } * ``` * * @category constructors @@ -609,7 +621,7 @@ export const unbounded = (): Effect> => make() * * **Example** (Offering a value) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Queue } from "effect" * * const program = Effect.gen(function*() { @@ -618,15 +630,16 @@ export const unbounded = (): Effect> => make() * // Successfully add messages to queue * const success1 = yield* Queue.offer(queue, 1) * const success2 = yield* Queue.offer(queue, 2) - * console.log(success1, success2) // true, true * * // Queue state * const size = yield* Queue.size(queue) - * console.log(size) // 2 + * return { offered: [success1, success2], size } * }) + * + * await Effect.runPromise(program) // => { offered: [true, true], size: 2 } * ``` * - * @category Offering + * @category offering * @since 2.0.0 */ export const offer = (self: Enqueue, message: Types.NoInfer): Effect => @@ -670,8 +683,8 @@ export const offer = (self: Enqueue, message: Types.NoInfer): Eff * * **Example** (Offering a value synchronously) * - * ```ts - * import { Cause, Effect, Queue } from "effect" + * ```ts import.meta.vitest + * import { Effect, Queue } from "effect" * * // Create a queue effect and extract the queue for unsafe operations * const program = Effect.gen(function*() { @@ -680,15 +693,16 @@ export const offer = (self: Enqueue, message: Types.NoInfer): Eff * // Add messages synchronously using unsafe API * const success1 = Queue.offerUnsafe(queue, 1) * const success2 = Queue.offerUnsafe(queue, 2) - * console.log(success1, success2) // true, true * * // Check current size * const size = Queue.sizeUnsafe(queue) - * console.log(size) // 2 + * return { offered: [success1, success2], size } * }) + * + * await Effect.runPromise(program) // => { offered: [true, true], size: 2 } * ``` * - * @category Offering + * @category offering * @since 4.0.0 */ export const offerUnsafe = (self: Enqueue, message: Types.NoInfer): boolean => { @@ -728,7 +742,7 @@ export const offerUnsafe = (self: Enqueue, message: Types.NoInfer * * **Example** (Offering multiple values) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Queue } from "effect" * * const program = Effect.gen(function*() { @@ -736,11 +750,13 @@ export const offerUnsafe = (self: Enqueue, message: Types.NoInfer * * // Try to add more messages than capacity without suspending * const remaining1 = yield* Queue.offerAll(queue, [1, 2, 3, 4, 5]) - * console.log(remaining1) // [4, 5] - couldn't fit the last 2 + * return remaining1 * }) + * + * await Effect.runPromise(program) // => [4, 5] * ``` * - * @category Offering + * @category offering * @since 2.0.0 */ export const offerAll = (self: Enqueue, messages: Iterable): Effect> => @@ -772,8 +788,8 @@ export const offerAll = (self: Enqueue, messages: Iterable): Effe * * **Example** (Offering multiple values synchronously) * - * ```ts - * import { Cause, Effect, Queue } from "effect" + * ```ts import.meta.vitest + * import { Effect, Queue } from "effect" * * // Create a bounded queue and use unsafe API * const program = Effect.gen(function*() { @@ -781,15 +797,16 @@ export const offerAll = (self: Enqueue, messages: Iterable): Effe * * // Try to add 5 messages to capacity-3 queue using unsafe API * const remaining = Queue.offerAllUnsafe(queue, [1, 2, 3, 4, 5]) - * console.log(remaining) // [4, 5] - couldn't fit the last 2 * * // Check what's in the queue * const size = Queue.sizeUnsafe(queue) - * console.log(size) // 3 + * return { remaining, size } * }) + * + * await Effect.runPromise(program) // => { remaining: [4, 5], size: 3 } * ``` * - * @category Offering + * @category offering * @since 4.0.0 */ export const offerAllUnsafe = (self: Enqueue, messages: Iterable): Array => { @@ -832,20 +849,21 @@ export const offerAllUnsafe = (self: Enqueue, messages: Iterable) * * **Example** (Failing queues with an error) * - * ```ts - * import { Effect, Queue } from "effect" + * ```ts import.meta.vitest + * import { Effect, Exit, Queue } from "effect" * * const program = Effect.gen(function*() { * const queue = yield* Queue.bounded(10) * * // Fail the queue with an error * const failed = yield* Queue.fail(queue, "Something went wrong") - * console.log(failed) // true * * // Taking from the failed queue fails with the error - * const error = yield* Effect.flip(Queue.take(queue)) - * console.log(error) // "Something went wrong" + * const exit = yield* Effect.exit(Queue.take(queue)) + * return [failed, exit] * }) + * + * await Effect.runPromise(program) // => [true, Exit.fail("Something went wrong")] * ``` * * @category completion @@ -859,8 +877,8 @@ export const fail = (self: Enqueue, error: E) => failCause(self, cor * * **Example** (Failing queues with a cause) * - * ```ts - * import { Cause, Effect, Queue } from "effect" + * ```ts import.meta.vitest + * import { Cause, Effect, Exit, Queue } from "effect" * * const program = Effect.gen(function*() { * const queue = yield* Queue.bounded(10) @@ -868,11 +886,13 @@ export const fail = (self: Enqueue, error: E) => failCause(self, cor * // Create a cause and fail the queue * const cause = Cause.fail("Queue processing failed") * const failed = yield* Queue.failCause(queue, cause) - * console.log(failed) // true * * // The queue is now done with the specified failure cause - * console.log(queue.state._tag) // "Done" + * const exit = yield* Effect.exit(Queue.take(queue)) + * return [failed, exit] * }) + * + * await Effect.runPromise(program) // => [true, Exit.failCause(Cause.fail("Queue processing failed"))] * ``` * * @category completion @@ -902,8 +922,8 @@ export const failCause: { * * **Example** (Failing queues with a cause synchronously) * - * ```ts - * import { Cause, Effect, Queue } from "effect" + * ```ts import.meta.vitest + * import { Cause, Effect, Exit, Queue } from "effect" * * const program = Effect.gen(function*() { * const queue = yield* Queue.bounded(10) @@ -911,11 +931,13 @@ export const failCause: { * // Create a cause and fail the queue synchronously * const cause = Cause.fail("Processing error") * const failed = Queue.failCauseUnsafe(queue, cause) - * console.log(failed) // true * * // The queue is now done with the specified failure cause - * console.log(queue.state._tag) // "Done" + * const exit = Queue.takeUnsafe(queue) + * return [failed, exit] * }) + * + * await Effect.runPromise(program) // => [true, Exit.failCause(Cause.fail("Processing error"))] * ``` * * @category completion @@ -952,7 +974,7 @@ export const failCauseUnsafe = (self: Enqueue, cause: Cause): boo * * **Example** (Ending queues) * - * ```ts + * ```ts import.meta.vitest * import { Cause, Effect, Queue } from "effect" * * const program = Effect.gen(function*() { @@ -964,16 +986,16 @@ export const failCauseUnsafe = (self: Enqueue, cause: Cause): boo * * // Signal completion - no more messages will be accepted * const ended = yield* Queue.end(queue) - * console.log(ended) // true * * // Trying to offer more messages will return false * const offerResult = yield* Queue.offer(queue, 3) - * console.log(offerResult) // false * * // But we can still take existing messages * const message = yield* Queue.take(queue) - * console.log(message) // 1 + * return [ended, offerResult, message] * }) + * + * await Effect.runPromise(program) // => [true, false, 1] * ``` * * @category completion @@ -999,7 +1021,7 @@ export const end = (self: Enqueue): Effect => failCa * * **Example** (Ending queues synchronously) * - * ```ts + * ```ts import.meta.vitest * import { Cause, Effect, Queue } from "effect" * * // Create a queue and use unsafe operations @@ -1012,17 +1034,19 @@ export const end = (self: Enqueue): Effect => failCa * * // End the queue synchronously * const ended = Queue.endUnsafe(queue) - * console.log(ended) // true * * // Existing messages can still be consumed while the queue is closing - * console.log(queue.state._tag) // "Closing" + * const states = [queue.state._tag] * * Queue.takeUnsafe(queue) * Queue.takeUnsafe(queue) * * // After buffered messages are consumed, the queue is done - * console.log(queue.state._tag) // "Done" + * states.push(queue.state._tag) + * return { ended, states } * }) + * + * await Effect.runPromise(program) // => { ended: true, states: ["Closing", "Done"] } * ``` * * @category completion @@ -1040,7 +1064,7 @@ export const endUnsafe = (self: Enqueue) => failCauseUnsafe(s * * **Example** (Interrupting queues gracefully) * - * ```ts + * ```ts import.meta.vitest * import { Cause, Effect, Queue } from "effect" * * const program = Effect.gen(function*() { @@ -1052,23 +1076,21 @@ export const endUnsafe = (self: Enqueue) => failCauseUnsafe(s * * // Interrupt gracefully - no more offers accepted, but messages can be consumed * const interrupted = yield* Queue.interrupt(queue) - * console.log(interrupted) // true * * // Trying to offer more messages will return false * const offerResult = yield* Queue.offer(queue, 3) - * console.log(offerResult) // false * * // But we can still take existing messages * const message1 = yield* Queue.take(queue) - * console.log(message1) // 1 * * const message2 = yield* Queue.take(queue) - * console.log(message2) // 2 * * // After all messages are consumed, queue is done * const isDone = queue.state._tag === "Done" - * console.log(isDone) // true + * return { interrupted, offerResult, messages: [message1, message2], isDone } * }) + * + * await Effect.runPromise(program) // => { interrupted: true, offerResult: false, messages: [1, 2], isDone: true } * ``` * * @category completion @@ -1088,7 +1110,7 @@ export const interrupt = (self: Enqueue): Effect => * * **Example** (Shutting down queues) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Queue } from "effect" * * const program = Effect.gen(function*() { @@ -1100,12 +1122,13 @@ export const interrupt = (self: Enqueue): Effect => * * // Shutdown clears buffered messages and prevents further offers * const wasShutdown = yield* Queue.shutdown(queue) - * console.log(wasShutdown) // true * * // Queue is now done and cleared * const size = yield* Queue.size(queue) - * console.log(size) // 0 + * return { wasShutdown, size } * }) + * + * await Effect.runPromise(program) // => { wasShutdown: true, size: 0 } * ``` * * @category completion @@ -1142,8 +1165,8 @@ export const shutdown = (self: Enqueue): Effect => * * **Example** (Clearing queued values) * - * ```ts - * import { Cause, Effect, Queue } from "effect" + * ```ts import.meta.vitest + * import { Effect, Queue } from "effect" * * const program = Effect.gen(function*() { * const queue = yield* Queue.bounded(10) @@ -1153,16 +1176,16 @@ export const shutdown = (self: Enqueue): Effect => * * // Clear all messages from the queue * const messages = yield* Queue.clear(queue) - * console.log(messages) // [1, 2, 3, 4, 5] * * // Queue is now empty * const size = yield* Queue.size(queue) - * console.log(size) // 0 * * // Clearing empty queue returns empty array * const empty = yield* Queue.clear(queue) - * console.log(empty) // [] + * return { messages, size, empty } * }) + * + * await Effect.runPromise(program) // => { messages: [1, 2, 3, 4, 5], size: 0, empty: [] } * ``` * * @category taking @@ -1197,7 +1220,7 @@ export const clear = (self: Dequeue): Effect, Pull.ExcludeD * * **Example** (Taking all available values) * - * ```ts + * ```ts import.meta.vitest * import { Cause, Effect, Queue } from "effect" * * const program = Effect.gen(function*() { @@ -1208,8 +1231,10 @@ export const clear = (self: Dequeue): Effect, Pull.ExcludeD * * // Take all available messages * const messages1 = yield* Queue.takeAll(queue) - * console.log(messages1) // [1, 2, 3, 4, 5] + * return messages1 * }) + * + * await Effect.runPromise(program) // => [1, 2, 3, 4, 5] * ``` * * @category taking @@ -1223,7 +1248,7 @@ export const takeAll = (self: Dequeue): Effect, * * **Example** (Collecting values until completion) * - * ```ts + * ```ts import.meta.vitest * import { Cause, Effect, Queue } from "effect" * * const program = Effect.gen(function*() { @@ -1231,13 +1256,13 @@ export const takeAll = (self: Dequeue): Effect, * * // Add several messages * yield* Queue.offerAll(queue, [1, 2, 3, 4, 5]) - * // Some time later, end the queue - * yield* Effect.forkChild(Queue.end(queue)) + * yield* Queue.end(queue) * * // Collect all available messages - * const messages = yield* Queue.collect(queue) - * console.log(messages) // [1, 2, 3, 4, 5] + * return yield* Queue.collect(queue) * }) + * + * await Effect.runPromise(program) // => [1, 2, 3, 4, 5] * ``` * * @category taking @@ -1275,7 +1300,7 @@ export const collect = (self: Dequeue): Effect, Pull * * **Example** (Taking a fixed number of values) * - * ```ts + * ```ts import.meta.vitest * import { Cause, Effect, Queue } from "effect" * * const program = Effect.gen(function*() { @@ -1286,16 +1311,16 @@ export const collect = (self: Dequeue): Effect, Pull * * // Take exactly 3 messages * const first3 = yield* Queue.takeN(queue, 3) - * console.log(first3) // [1, 2, 3] * * // Take exactly 2 more messages * const next2 = yield* Queue.takeN(queue, 2) - * console.log(next2) // [4, 5] * * // Take remaining messages * const remaining = yield* Queue.takeN(queue, 2) - * console.log(remaining) // [6, 7] + * return [first3, next2, remaining] * }) + * + * await Effect.runPromise(program) // => [[1, 2, 3], [4, 5], [6, 7]] * ``` * * @category taking @@ -1318,7 +1343,7 @@ export const takeN = ( * * **Example** (Taking a bounded batch of values) * - * ```ts + * ```ts import.meta.vitest * import { Cause, Effect, Queue } from "effect" * * const program = Effect.gen(function*() { @@ -1329,15 +1354,16 @@ export const takeN = ( * * // Take between 2 and 5 messages * const batch1 = yield* Queue.takeBetween(queue, 2, 5) - * console.log(batch1) // [1, 2, 3, 4, 5] - took 5 (up to max) * * // Take between 1 and 10 messages (but only 3 remain) * const batch2 = yield* Queue.takeBetween(queue, 1, 10) - * console.log(batch2) // [6, 7, 8] - took 3 (all remaining) * * // No more messages available, will wait or return done * // const batch3 = yield* Queue.takeBetween(queue, 1, 3) + * return [batch1, batch2] * }) + * + * await Effect.runPromise(program) // => [[1, 2, 3, 4, 5], [6, 7, 8]] * ``` * * @category taking @@ -1363,8 +1389,8 @@ export const takeBetween = ( * * **Example** (Taking one value) * - * ```ts - * import { Cause, Effect, Queue } from "effect" + * ```ts import.meta.vitest + * import { Cause, Effect, Exit, Queue } from "effect" * * const program = Effect.gen(function*() { * const queue = yield* Queue.bounded(3) @@ -1376,18 +1402,16 @@ export const takeBetween = ( * // Take messages one by one * const msg1 = yield* Queue.take(queue) * const msg2 = yield* Queue.take(queue) - * console.log(msg1, msg2) // "first", "second" * * // End the queue * yield* Queue.end(queue) * * // Taking from an ended queue fails with Done - * const result = yield* Effect.match(Queue.take(queue), { - * onFailure: (error: Cause.Done) => true, - * onSuccess: (value: string) => false - * }) - * console.log("Queue ended:", result) // true + * const result = yield* Effect.exit(Queue.take(queue)) + * return [[msg1, msg2], result] * }) + * + * await Effect.runPromise(program) // => [["first", "second"], Exit.fail(Cause.Done())] * ``` * * @category taking @@ -1409,7 +1433,7 @@ export const take = (self: Dequeue): Effect => * * **Example** (Polling without blocking) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Option, Queue } from "effect" * * const program = Effect.gen(function*() { @@ -1417,15 +1441,16 @@ export const take = (self: Dequeue): Effect => * * // Poll returns Option.none if empty * const maybe1 = yield* Queue.poll(queue) - * console.log(Option.isNone(maybe1)) // true * * // Add an item * yield* Queue.offer(queue, 42) * * // Poll returns Option.some with the item * const maybe2 = yield* Queue.poll(queue) - * console.log(Option.getOrNull(maybe2)) // 42 + * return [maybe1, maybe2] * }) + * + * await Effect.runPromise(program) // => [Option.none(), Option.some(42)] * ``` * * @category taking @@ -1452,7 +1477,7 @@ export const poll = (self: Dequeue): Effect> => * * **Example** (Peeking at the next value) * - * ```ts + * ```ts import.meta.vitest * import { Cause, Effect, Queue } from "effect" * * const program = Effect.gen(function*() { @@ -1461,8 +1486,10 @@ export const poll = (self: Dequeue): Effect> => * * // Peek at the next item without removing it * const item = yield* Queue.peek(queue) - * console.log(item) // 42 + * return item * }) + * + * await Effect.runPromise(program) // => 42 * ``` * * @category taking @@ -1495,8 +1522,8 @@ export const peek = (self: Dequeue): Effect => * * **Example** (Taking one value synchronously) * - * ```ts - * import { Effect, Queue } from "effect" + * ```ts import.meta.vitest + * import { Effect, Exit, Queue } from "effect" * * // Create a queue and use unsafe operations * const program = Effect.gen(function*() { @@ -1508,15 +1535,15 @@ export const peek = (self: Dequeue): Effect => * * // Take a message synchronously * const result1 = Queue.takeUnsafe(queue) - * console.log(result1) // Success(1) or Exit containing value 1 * * const result2 = Queue.takeUnsafe(queue) - * console.log(result2) // Success(2) * * // No more messages - returns undefined * const result3 = Queue.takeUnsafe(queue) - * console.log(result3) // undefined + * return [result1, result2, result3] * }) + * + * await Effect.runPromise(program) // => [Exit.succeed(1), Exit.succeed(2), undefined] * ``` * * @category taking @@ -1594,34 +1621,36 @@ export { * * **Details** * - * Completed queues report a size of `0`. + * After `end`, a queue remains `Closing` while buffered messages are drained, + * and its size continues to include those messages. A `Done` queue reports a + * size of `0`. * * **Example** (Checking queue size) * - * ```ts - * import { Cause, Effect, Option, Queue } from "effect" + * ```ts import.meta.vitest + * import { Cause, Effect, Queue } from "effect" * * const program = Effect.gen(function*() { * const queue = yield* Queue.bounded(10) * * // Check size of empty queue * const size1 = yield* Queue.size(queue) - * console.log(size1) // 0 * * // Add some messages * yield* Queue.offerAll(queue, [1, 2, 3, 4, 5]) * * // Check size after adding messages * const size2 = yield* Queue.size(queue) - * console.log(size2) // 5 * * // End the queue * yield* Queue.end(queue) * - * // Size of ended queue is 0 + * // Ending retains the buffered size while the queue is Closing * const size3 = yield* Queue.size(queue) - * console.log(size3) // 0 + * return [size1, size2, size3] * }) + * + * await Effect.runPromise(program) // => [0, 5, 5] * ``` * * @category sizes @@ -1634,22 +1663,25 @@ export const size = (self: Dequeue): Effect => internalEffec * * **Example** (Checking if queues are full) * - * ```ts - * import { Cause, Effect, Option, Queue } from "effect" + * ```ts import.meta.vitest + * import { Cause, Effect, Queue } from "effect" * * const program = Effect.gen(function*() { * const queue = yield* Queue.bounded(3) * - * console.log(yield* Queue.isFull(queue)) // false + * const before = yield* Queue.isFull(queue) * * // Add some messages * yield* Queue.offerAll(queue, [1, 2, 3]) * - * console.log(yield* Queue.isFull(queue)) // true + * const after = yield* Queue.isFull(queue) + * return [before, after] * }) + * + * await Effect.runPromise(program) // => [false, true] * ``` * - * @category sizes + * @category predicates * @since 2.0.0 */ export const isFull = (self: Dequeue): Effect => internalEffect.sync(() => isFullUnsafe(self)) @@ -1664,20 +1696,21 @@ export const isFull = (self: Dequeue): Effect => internalEf * * **Details** * - * Completed queues report a size of `0`. This unsafe operation reads the queue - * state directly without Effect wrapping. + * After `endUnsafe`, a queue remains `Closing` while buffered messages are + * drained, and its size continues to include those messages. A `Done` queue + * reports a size of `0`. This unsafe operation reads the queue state directly + * without Effect wrapping. * * **Example** (Checking queue size synchronously) * - * ```ts - * import { Cause, Effect, Option, Queue } from "effect" + * ```ts import.meta.vitest + * import { Cause, Effect, Queue } from "effect" * * const program = Effect.gen(function*() { * const queue = yield* Queue.bounded(10) * * // Check size of empty queue * const size1 = Queue.sizeUnsafe(queue) - * console.log(size1) // 0 * * // Add some messages * Queue.offerUnsafe(queue, 1) @@ -1686,15 +1719,16 @@ export const isFull = (self: Dequeue): Effect => internalEf * * // Check size after adding messages * const size2 = Queue.sizeUnsafe(queue) - * console.log(size2) // 3 * * // End the queue * Queue.endUnsafe(queue) * - * // Size of ended queue is 0 + * // Ending retains the buffered size while the queue is Closing * const size3 = Queue.sizeUnsafe(queue) - * console.log(size3) // 0 + * return [size1, size2, size3] * }) + * + * await Effect.runPromise(program) // => [0, 3, 3] * ``` * * @category sizes @@ -1712,22 +1746,25 @@ export const sizeUnsafe = (self: Dequeue): number => self.state._tag * * **Example** (Checking fullness synchronously) * - * ```ts - * import { Cause, Effect, Option, Queue } from "effect" + * ```ts import.meta.vitest + * import { Cause, Effect, Queue } from "effect" * * const program = Effect.gen(function*() { * const queue = yield* Queue.bounded(3) * - * console.log(Queue.isFullUnsafe(queue)) // false + * const before = Queue.isFullUnsafe(queue) * * // Add some messages * yield* Queue.offerAll(queue, [1, 2, 3]) * - * console.log(Queue.isFullUnsafe(queue)) // true + * const after = Queue.isFullUnsafe(queue) + * return [before, after] * }) + * + * await Effect.runPromise(program) // => [false, true] * ``` * - * @category sizes + * @category predicates * @since 4.0.0 */ export const isFullUnsafe = (self: Dequeue): boolean => sizeUnsafe(self) === self.capacity @@ -1738,15 +1775,15 @@ export const isFullUnsafe = (self: Dequeue): boolean => sizeUnsafe(s * * **Example** (Running effects into queues) * - * ```ts - * import { Cause, Effect, Queue } from "effect" + * ```ts import.meta.vitest + * import { Cause, Effect, Exit, Queue } from "effect" * * const program = Effect.gen(function*() { * const queue = yield* Queue.bounded(10) * * // Create an effect that succeeds * const dataProcessing = Effect.gen(function*() { - * yield* Effect.sleep("100 millis") + * yield* Effect.yieldNow * return "Processing completed successfully" * }) * @@ -1756,11 +1793,11 @@ export const isFullUnsafe = (self: Dequeue): boolean => sizeUnsafe(s * const effectIntoQueue = Queue.into(queue)(dataProcessing) * * const wasCompleted = yield* effectIntoQueue - * console.log("Queue operation completed:", wasCompleted) // true - * - * // Queue state now reflects the effect's outcome - * console.log("Queue state:", queue.state._tag) // "Done" + * const exit = yield* Effect.exit(Queue.take(queue)) + * return [wasCompleted, exit] * }) + * + * await Effect.runPromise(program) // => [true, Exit.fail(Cause.Done())] * ``` * * @category completion diff --git a/repos/effect/packages/effect/src/Random.ts b/repos/effect/packages/effect/src/Random.ts index 53078f1d23..2afe37ea19 100644 --- a/repos/effect/packages/effect/src/Random.ts +++ b/repos/effect/packages/effect/src/Random.ts @@ -33,21 +33,20 @@ import * as Predicate from "./Predicate.ts" * * **Example** (Accessing the random service) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Random } from "effect" * * const program = Effect.gen(function*() { * const float = yield* Random.next * const integer = yield* Random.nextInt * const inRange = yield* Random.nextIntBetween(1, 100) - * - * console.log("Float:", float) - * console.log("Integer:", integer) - * console.log("In range:", inRange) + * return [float, integer, inRange] as const * }) + * + * await Effect.runPromise(program.pipe(Random.withSeed("example"))) // => [0.1633802591287037, 3434461687501127, 1] * ``` * - * @category Random Number Generators + * @category services * @since 2.0.0 */ export const Random: Context.Reference<{ @@ -68,16 +67,13 @@ const randomWith = (f: (random: typeof Random["Service"]) => A): Effect.Effec * * **Example** (Generating a random number) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Random } from "effect" * - * const program = Effect.gen(function*() { - * const randomDouble = yield* Random.next - * console.log("Random double:", randomDouble) - * }) + * await Effect.runPromise(Random.next.pipe(Random.withSeed("example"))) // => 0.1633802591287037 * ``` * - * @category Random Number Generators + * @category generators * @since 2.0.0 */ export const next: Effect.Effect = randomWith((r) => r.nextDoubleUnsafe()) @@ -91,16 +87,13 @@ export const next: Effect.Effect = randomWith((r) => r.nextDoubleUnsafe( * * **Example** (Generating a random boolean) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Random } from "effect" * - * const program = Effect.gen(function*() { - * const value = yield* Random.nextBoolean - * console.log("Random boolean:", value) - * }) + * await Effect.runPromise(Random.nextBoolean.pipe(Random.withSeed("example"))) // => false * ``` * - * @category Random Number Generators + * @category generators * @since 2.0.0 */ export const nextBoolean: Effect.Effect = randomWith((r) => r.nextDoubleUnsafe() > 0.5) @@ -116,16 +109,13 @@ export const nextBoolean: Effect.Effect = randomWith((r) => r.nextDoubl * * **Example** (Generating a random integer) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Random } from "effect" * - * const program = Effect.gen(function*() { - * const randomInt = yield* Random.nextInt - * console.log("Random integer:", randomInt) - * }) + * await Effect.runPromise(Random.nextInt.pipe(Random.withSeed("example"))) // => -6064002158214091 * ``` * - * @category Random Number Generators + * @category generators * @since 2.0.0 */ export const nextInt: Effect.Effect = randomWith((r) => r.nextIntUnsafe()) @@ -139,16 +129,13 @@ export const nextInt: Effect.Effect = randomWith((r) => r.nextIntUnsafe( * * **Example** (Generating a bounded random number) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Random } from "effect" * - * const program = Effect.gen(function*() { - * const randomDouble = yield* Random.nextBetween(0, 1) - * console.log("Random double: ", randomDouble) - * }) + * await Effect.runPromise(Random.nextBetween(0, 1).pipe(Random.withSeed("example"))) // => 0.1633802591287037 * ``` * - * @category Random Number Generators + * @category generators * @since 4.0.0 */ export const nextBetween = (min: number, max: number): Effect.Effect => @@ -169,7 +156,7 @@ export const nextBetween = (min: number, max: number): Effect.Effect => * * **Example** (Generating a bounded random integer) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Random } from "effect" * * const program = Effect.gen(function*() { @@ -178,10 +165,13 @@ export const nextBetween = (min: number, max: number): Effect.Effect => * halfOpen: true * }) * const diceRoll3 = yield* Random.nextIntBetween(0, 10) + * return [diceRoll1, diceRoll2, diceRoll3] * }) + * + * await Effect.runPromise(program.pipe(Random.withSeed("example"))) // => [1, 4, 0] * ``` * - * @category Random Number Generators + * @category generators * @since 2.0.0 */ export const nextIntBetween = (min: number, max: number, options?: { @@ -204,16 +194,13 @@ export const nextIntBetween = (min: number, max: number, options?: { * * **Example** (Shuffling values) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Random } from "effect" * - * const program = Effect.gen(function*() { - * const values = yield* Random.shuffle([1, 2, 3, 4, 5]) - * console.log(values) - * }) + * await Effect.runPromise(Random.shuffle([1, 2, 3, 4, 5]).pipe(Random.withSeed("example"))) // => [4, 2, 5, 3, 1] * ``` * - * @category Random Number Generators + * @category generators * @since 2.0.0 */ export const shuffle = (elements: Iterable): Effect.Effect> => @@ -243,16 +230,13 @@ export const shuffle = (elements: Iterable): Effect.Effect> => * * **Example** (Choosing a random value) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Random } from "effect" * - * const program = Effect.gen(function*() { - * const value = yield* Random.choice(["red", "green", "blue"] as const) - * console.log(value) - * }) + * await Effect.runPromise(Random.choice(["red", "green", "blue"] as const).pipe(Random.withSeed("example"))) // => "red" * ``` * - * @category Random Number Generators + * @category generators * @since 3.6.0 */ export const choice: >( @@ -285,25 +269,22 @@ export const choice: >( * * **Example** (Seeding random generation) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Random } from "effect" * * const program = Effect.gen(function*() { * const value1 = yield* Random.next * const value2 = yield* Random.next - * console.log(value1, value2) + * return [value1, value2] * }) * - * // Same seed produces same sequence - * const seeded1 = program.pipe(Random.withSeed("my-seed")) - * const seeded2 = program.pipe(Random.withSeed("my-seed")) - * - * // Both will output identical values - * Effect.runPromise(seeded1) - * Effect.runPromise(seeded2) + * await Effect.runPromise(Effect.all([ + * program.pipe(Random.withSeed("my-seed")), + * program.pipe(Random.withSeed("my-seed")) + * ])) // => [[0.018368576514773527, 0.4010840628128671], [0.018368576514773527, 0.4010840628128671]] * ``` * - * @category Seeding + * @category providing services * @since 4.0.0 */ export const withSeed: { diff --git a/repos/effect/packages/effect/src/RcMap.ts b/repos/effect/packages/effect/src/RcMap.ts index 18892480c2..f1de43441a 100644 --- a/repos/effect/packages/effect/src/RcMap.ts +++ b/repos/effect/packages/effect/src/RcMap.ts @@ -39,17 +39,14 @@ const TypeId = "~effect/RcMap" * * **Example** (Inspecting a reference-counted map) * - * ```ts + * ```ts import.meta.vitest * import { Effect, RcMap } from "effect" * - * Effect.gen(function*() { + * const program = Effect.gen(function*() { * // Create an RcMap that manages database connections * const dbConnectionMap = yield* RcMap.make({ * lookup: (dbName: string) => - * Effect.acquireRelease( - * Effect.succeed(`Connection to ${dbName}`), - * (conn) => Effect.log(`Closing ${conn}`) - * ), + * Effect.acquireRelease(Effect.succeed(`Connection to ${dbName}`), () => Effect.void), * capacity: 10, * idleTimeToLive: "5 minutes" * }) @@ -60,8 +57,10 @@ const TypeId = "~effect/RcMap" * // - idleTimeToLive: Time before idle resources are released * // - state: Current state of the map * - * console.log(`Capacity: ${dbConnectionMap.capacity}`) - * }).pipe(Effect.scoped) + * return dbConnectionMap.capacity + * }) + * + * await Effect.runPromise(Effect.scoped(program)) // => 10 * ``` * * @see {@link make} for creating an `RcMap` @@ -205,15 +204,17 @@ const makeUnsafe = (options: { * * **Example** (Creating a reference-counted map) * - * ```ts + * ```ts import.meta.vitest * import { Effect, RcMap } from "effect" * - * Effect.gen(function*() { + * const events: Array = [] + * + * const program = Effect.gen(function*() { * const map = yield* RcMap.make({ * lookup: (key: string) => * Effect.acquireRelease( * Effect.succeed(`acquired ${key}`), - * () => Effect.log(`releasing ${key}`) + * () => Effect.sync(() => events.push(`released ${key}`)) * ) * }) * @@ -224,12 +225,15 @@ const makeUnsafe = (options: { * Effect.scoped * ) * }) + * + * await Effect.runPromise(Effect.scoped(program)) + * events // => ["released foo"] * ``` * * @see {@link get} for acquiring or retaining a resource by key * @see {@link invalidate} for removing a resource from the map * - * @category models + * @category constructors * @since 3.5.0 */ export const make: { @@ -299,22 +303,26 @@ export const make: { * * **Example** (Acquiring a resource) * - * ```ts + * ```ts import.meta.vitest * import { Effect, RcMap } from "effect" * - * Effect.gen(function*() { + * const events: Array = [] + * + * const program = Effect.gen(function*() { * const map = yield* RcMap.make({ * lookup: (key: string) => * Effect.acquireRelease( * Effect.succeed(`Resource: ${key}`), - * () => Effect.log(`Released ${key}`) + * () => Effect.sync(() => events.push(`released ${key}`)) * ) * }) * * // Get a resource - it will be acquired on first access * const resource = yield* RcMap.get(map, "database") - * console.log(resource) // "Resource: database" - * }).pipe(Effect.scoped) + * return [resource, events] as const + * }) + * + * await Effect.runPromise(Effect.scoped(program)) // => ["Resource: database", ["released database"]] * ``` * * @see {@link make} for creating the reference-counted map @@ -427,10 +435,10 @@ const release = (self: RcMap, key: K, entry: State.Entry * * **Example** (Listing keys) * - * ```ts + * ```ts import.meta.vitest * import { Effect, RcMap } from "effect" * - * Effect.gen(function*() { + * const program = Effect.gen(function*() { * const map = yield* RcMap.make({ * lookup: (key: string) => Effect.succeed(`value-${key}`) * }) @@ -442,8 +450,10 @@ const release = (self: RcMap, key: K, entry: State.Entry * * // Get all keys currently in the map * const allKeys = yield* RcMap.keys(map) - * console.log(allKeys) // ["foo", "bar", "baz"] - * }).pipe(Effect.scoped) + * return Array.from(allKeys) + * }) + * + * await Effect.runPromise(Effect.scoped(program)) // => ["foo", "bar", "baz"] * ``` * * @see {@link has} for checking one key without enumerating all keys @@ -467,15 +477,17 @@ export const keys = (self: RcMap): Effect.Effect> * * **Example** (Invalidating a resource) * - * ```ts + * ```ts import.meta.vitest * import { Effect, RcMap } from "effect" * - * Effect.gen(function*() { + * const events: Array = [] + * + * const program = Effect.gen(function*() { * const map = yield* RcMap.make({ * lookup: (key: string) => * Effect.acquireRelease( * Effect.succeed(`Resource: ${key}`), - * () => Effect.log(`Released ${key}`) + * () => Effect.sync(() => events.push(`released ${key}`)) * ) * }) * @@ -488,7 +500,10 @@ export const keys = (self: RcMap): Effect.Effect> * * // Next access will create a new resource * yield* RcMap.get(map, "cache") - * }).pipe(Effect.scoped) + * }) + * + * await Effect.runPromise(Effect.scoped(program)) + * events // => ["released cache", "released cache"] * ``` * * @see {@link get} for acquiring or retaining the resource for a key @@ -561,15 +576,17 @@ export const has: { * * **Example** (Extending resource idle time) * - * ```ts + * ```ts import.meta.vitest * import { Effect, RcMap } from "effect" * - * Effect.gen(function*() { + * const events: Array = [] + * + * const program = Effect.gen(function*() { * const map = yield* RcMap.make({ * lookup: (key: string) => * Effect.acquireRelease( * Effect.succeed(`Resource: ${key}`), - * () => Effect.log(`Released ${key}`) + * () => Effect.sync(() => events.push(`released ${key}`)) * ), * idleTimeToLive: "10 seconds" * }) @@ -583,7 +600,10 @@ export const has: { * * // The resource will now live for another 10 seconds * // from the time it was touched - * }).pipe(Effect.scoped) + * }) + * + * await Effect.runPromise(Effect.scoped(program)) + * events // => ["released session"] * ``` * * @see {@link invalidate} for removing the resource instead of extending it diff --git a/repos/effect/packages/effect/src/RcRef.ts b/repos/effect/packages/effect/src/RcRef.ts index fab9307ecc..3d64d58900 100644 --- a/repos/effect/packages/effect/src/RcRef.ts +++ b/repos/effect/packages/effect/src/RcRef.ts @@ -33,15 +33,17 @@ const TypeId = "~effect/RcRef" * * **Example** (Sharing a lazily acquired resource) * - * ```ts + * ```ts import.meta.vitest * import { Effect, RcRef } from "effect" * + * const events: Array = [] + * * // Create an RcRef for a database connection * const createConnectionRef = (connectionString: string) => * RcRef.make({ * acquire: Effect.acquireRelease( * Effect.succeed(`Connected to ${connectionString}`), - * (connection) => Effect.log(`Closing connection: ${connection}`) + * (connection) => Effect.sync(() => events.push(`closed ${connection}`)) * ) * }) * @@ -53,8 +55,10 @@ const TypeId = "~effect/RcRef" * const connection1 = yield* RcRef.get(connectionRef) * const connection2 = yield* RcRef.get(connectionRef) * - * return [connection1, connection2] + * return [connection1 === connection2, events] as const * }) + * + * await Effect.runPromise(Effect.scoped(program)) // => [true, ["closed Connected to postgres://localhost"]] * ``` * * @category models @@ -69,12 +73,13 @@ export interface RcRef extends Pipeable { * * **Example** (Referencing namespace types) * - * ```ts + * ```ts import.meta.vitest * import type { RcRef } from "effect" * * // Use RcRef namespace types * type MyRcRef = RcRef.RcRef * type MyVariance = RcRef.RcRef.Variance + * * ``` * * @since 3.5.0 @@ -121,14 +126,16 @@ export declare namespace RcRef { * * **Example** (Creating a reference-counted resource) * - * ```ts + * ```ts import.meta.vitest * import { Effect, RcRef } from "effect" * - * Effect.gen(function*() { + * const events: Array = [] + * + * const program = Effect.gen(function*() { * const ref = yield* RcRef.make({ * acquire: Effect.acquireRelease( * Effect.succeed("foo"), - * () => Effect.log("release foo") + * () => Effect.sync(() => events.push("released foo")) * ) * }) * @@ -139,6 +146,9 @@ export declare namespace RcRef { * Effect.scoped * ) * }) + * + * await Effect.runPromise(Effect.scoped(program)) + * events // => ["released foo"] * ``` * * @category constructors @@ -172,15 +182,17 @@ export const make: ( * * **Example** (Sharing one acquired value) * - * ```ts + * ```ts import.meta.vitest * import { Effect, RcRef } from "effect" * + * const events: Array = [] + * * const program = Effect.gen(function*() { * // Create an RcRef with a resource * const ref = yield* RcRef.make({ * acquire: Effect.acquireRelease( * Effect.succeed("shared resource"), - * (resource) => Effect.log(`Releasing ${resource}`) + * (resource) => Effect.sync(() => events.push(`released ${resource}`)) * ) * }) * @@ -188,11 +200,10 @@ export const make: ( * const value1 = yield* RcRef.get(ref) * const value2 = yield* RcRef.get(ref) * - * // Both values are the same instance - * console.log(value1 === value2) // true - * - * return value1 + * return [value1 === value2, events] as const * }) + * + * await Effect.runPromise(Effect.scoped(program)) // => [true, ["released shared resource"]] * ``` * * @category combinators diff --git a/repos/effect/packages/effect/src/Record.ts b/repos/effect/packages/effect/src/Record.ts index 99d006ce26..7ac2341a8e 100644 --- a/repos/effect/packages/effect/src/Record.ts +++ b/repos/effect/packages/effect/src/Record.ts @@ -15,6 +15,7 @@ import * as Equal from "./Equal.ts" import type { Equivalence } from "./Equivalence.ts" import { dual, identity } from "./Function.ts" import type { TypeLambda } from "./HKT.ts" +import * as InternalRecord from "./internal/record.ts" import * as Option from "./Option.ts" import * as Reducer from "./Reducer.ts" import type { Result } from "./Result.ts" @@ -27,7 +28,7 @@ import type { NoInfer } from "./Types.ts" * * **Example** (Defining a readonly record type) * - * ```ts + * ```ts import.meta.vitest * import type { Record } from "effect" * * // Creating a readonly record type @@ -37,6 +38,7 @@ import type { NoInfer } from "./Types.ts" * name: "John", * age: 30 * } + * user // => { name: "John", age: 30 } * ``` * * @category models @@ -52,7 +54,7 @@ export type ReadonlyRecord = { * * **Example** (Using readonly record helper types) * - * ```ts + * ```ts import.meta.vitest * import type { Record } from "effect" * * // Using NonLiteralKey to convert literal keys to generic types @@ -60,6 +62,9 @@ export type ReadonlyRecord = { * * // Using IntersectKeys to find common keys between record types * type CommonKeys = Record.ReadonlyRecord.IntersectKeys<"a" | "b", "b" | "c"> // "b" + * + * "key" satisfies GenericKey + * "b" satisfies CommonKeys * ``` * * @since 2.0.0 @@ -76,7 +81,7 @@ export declare namespace ReadonlyRecord { * * **Example** (Converting literal keys to non-literal keys) * - * ```ts + * ```ts import.meta.vitest * import type { Record } from "effect" * * // For literal string keys, this becomes 'string' @@ -84,6 +89,10 @@ export declare namespace ReadonlyRecord { * * // For symbol keys, this becomes 'symbol' * type Example2 = Record.ReadonlyRecord.NonLiteralKey // symbol + * + * const symbol: Example2 = Symbol.for("key") + * "key" satisfies Example1 + * symbol * ``` * * @category models @@ -98,7 +107,7 @@ export declare namespace ReadonlyRecord { * * **Example** (Intersecting record keys) * - * ```ts + * ```ts import.meta.vitest * import type { Record } from "effect" * * // Intersection of literal keys @@ -106,6 +115,9 @@ export declare namespace ReadonlyRecord { * * // Intersection with generic string * type Example2 = Record.ReadonlyRecord.IntersectKeys // string + * + * "b" satisfies Example1 + * "a" satisfies Example2 * ``` * * @category models @@ -122,7 +134,7 @@ export declare namespace ReadonlyRecord { * * **Example** (Applying a readonly record type lambda) * - * ```ts + * ```ts import.meta.vitest * import type { HKT, Record } from "effect" * * type Settings = HKT.Kind< @@ -137,9 +149,10 @@ export declare namespace ReadonlyRecord { * port: 3000, * retries: 3 * } + * defaults // => { port: 3000, retries: 3 } * ``` * - * @category type lambdas + * @category utility types * @since 2.0.0 */ export interface ReadonlyRecordTypeLambda extends TypeLambda { @@ -151,16 +164,15 @@ export interface ReadonlyRecordTypeLambda extends Typ * * **Example** (Creating an empty record) * - * ```ts + * ```ts import.meta.vitest * import { Record } from "effect" * * // Create an empty record * const emptyRecord = Record.empty() - * console.log(emptyRecord) // {} + * emptyRecord // => {} * * // The type ensures type safety for future operations - * const withValue = Record.set(emptyRecord, "count", 42) - * console.log(withValue) // { count: 42 } + * Record.set(emptyRecord, "count", 42) // => { count: 42 } * ``` * * @category constructors @@ -176,12 +188,11 @@ export const empty = (): Record< * * **Example** (Checking for an empty record) * - * ```ts + * ```ts import.meta.vitest * import { Record } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(Record.isEmptyRecord({}), true) - * assert.deepStrictEqual(Record.isEmptyRecord({ a: 3 }), false) + * Record.isEmptyRecord({}) // => true + * Record.isEmptyRecord({ a: 3 }) // => false * ``` * * @category guards @@ -195,12 +206,11 @@ export const isEmptyRecord = (self: Record): self is * * **Example** (Checking for an empty readonly record) * - * ```ts + * ```ts import.meta.vitest * import { Record } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(Record.isEmptyReadonlyRecord({}), true) - * assert.deepStrictEqual(Record.isEmptyReadonlyRecord({ a: 3 }), false) + * Record.isEmptyReadonlyRecord({}) // => true + * Record.isEmptyReadonlyRecord({ a: 3 }) // => false * ``` * * @category guards @@ -216,16 +226,10 @@ export const isEmptyReadonlyRecord: ( * * **Example** (Building a record from mapped iterable values) * - * ```ts + * ```ts import.meta.vitest * import { Record } from "effect" - * import * as assert from "node:assert" - * - * const input = [1, 2, 3, 4] * - * assert.deepStrictEqual( - * Record.fromIterableWith(input, (a) => [String(a), a * 2]), - * { "1": 2, "2": 4, "3": 6, "4": 8 } - * ) + * Record.fromIterableWith([1, 2, 3, 4], (a) => [String(a), a * 2]) // => { "1": 2, "2": 4, "3": 6, "4": 8 } * ``` * * @category constructors @@ -248,7 +252,7 @@ export const fromIterableWith: { const out: Record = empty() for (const a of self) { const [k, b] = f(a) - out[k] = b + InternalRecord.assignProperty(out, k, b) } return out } @@ -259,31 +263,38 @@ export const fromIterableWith: { * * **Example** (Building a record keyed by iterable values) * - * ```ts + * ```ts import.meta.vitest * import { Record } from "effect" - * import * as assert from "node:assert" * * const users = [ * { id: "2", name: "name2" }, * { id: "1", name: "name1" } * ] * - * assert.deepStrictEqual( - * Record.fromIterableBy(users, (user) => user.id), - * { - * "2": { id: "2", name: "name2" }, - * "1": { id: "1", name: "name1" } - * } - * ) + * Record.fromIterableBy( + * users, + * (user) => user.id + * ) // => { "1": { id: "1", name: "name1" }, "2": { id: "2", name: "name2" } } * ``` * * @category constructors * @since 2.0.0 */ -export const fromIterableBy = ( - items: Iterable, - f: (a: A) => K -): Record, A> => fromIterableWith(items, (a) => [f(a), a]) +export const fromIterableBy: { + ( + f: (a: A) => K + ): (items: Iterable) => Record, A> + ( + items: Iterable, + f: (a: A) => K + ): Record, A> +} = dual( + 2, + ( + items: Iterable, + f: (a: A) => K + ): Record, A> => fromIterableWith(items, (a) => [f(a), a]) +) /** * Builds a record from an iterable of key-value pairs. @@ -295,13 +306,10 @@ export const fromIterableBy = ( * * **Example** (Building a record from entries) * - * ```ts + * ```ts import.meta.vitest * import { Record } from "effect" - * import * as assert from "node:assert" - * - * const input: Array<[string, number]> = [["a", 1], ["b", 2]] * - * assert.deepStrictEqual(Record.fromEntries(input), { a: 1, b: 2 }) + * Record.fromEntries([["a", 1], ["b", 2]]) // => { a: 1, b: 2 } * ``` * * @category constructors @@ -316,15 +324,11 @@ export const fromEntries: ( * * **Example** (Collecting mapped record values) * - * ```ts + * ```ts import.meta.vitest * import { Record } from "effect" - * import * as assert from "node:assert" * * const x = { a: 1, b: 2, c: 3 } - * assert.deepStrictEqual(Record.collect(x, (key, n) => [key, n]), [["a", 1], [ - * "b", - * 2 - * ], ["c", 3]]) + * Record.collect(x, (key, n) => [key, n]) // => [["a", 1], ["b", 2], ["c", 3]] * ``` * * @category converting @@ -349,12 +353,11 @@ export const collect: { * * **Example** (Converting a record to entries) * - * ```ts + * ```ts import.meta.vitest * import { Record } from "effect" - * import * as assert from "node:assert" * * const x = { a: 1, b: 2, c: 3 } - * assert.deepStrictEqual(Record.toEntries(x), [["a", 1], ["b", 2], ["c", 3]]) + * Record.toEntries(x) // => [["a", 1], ["b", 2], ["c", 3]] * ``` * * @category converting @@ -370,11 +373,10 @@ export const toEntries: (self: ReadonlyRecord) => Arr * * **Example** (Getting the record size) * - * ```ts + * ```ts import.meta.vitest * import { Record } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(Record.size({ a: "a", b: 1, c: true }), 3) + * Record.size({ a: "a", b: 1, c: true }) // => 3 * ``` * * @category getters @@ -387,15 +389,14 @@ export const size = (self: ReadonlyRecord): number => * * **Example** (Checking key membership) * - * ```ts + * ```ts import.meta.vitest * import { Record } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(Record.has({ a: 1, b: 2 }, "a"), true) - * assert.deepStrictEqual(Record.has(Record.empty(), "c"), false) + * Record.has({ a: 1, b: 2 }, "a") // => true + * Record.has(Record.empty(), "c") // => false * ``` * - * @category guards + * @category predicates * @since 2.0.0 */ export const has: { @@ -419,14 +420,13 @@ export const has: { * * **Example** (Getting a value as an Option) * - * ```ts + * ```ts import.meta.vitest * import { Option, Record as R } from "effect" - * import * as assert from "node:assert" * * const person: Record = { name: "John Doe", age: 35 } * - * assert.deepStrictEqual(R.get(person, "name"), Option.some("John Doe")) - * assert.deepStrictEqual(R.get(person, "email"), Option.none()) + * R.get(person, "name") // => Option.some("John Doe") + * R.get(person, "email") // => Option.none() * ``` * * @category getters @@ -438,7 +438,7 @@ export const get: { } = dual( 2, (self: ReadonlyRecord, key: NoInfer): Option.Option => - has(self, key) ? Option.some(self[key]) : Option.none() + Object.hasOwn(self, key) ? Option.some(self[key]) : Option.none() ) /** @@ -447,15 +447,15 @@ export const get: { * * **Example** (Modifying a value at a key) * - * ```ts - * import { Record } from "effect" + * ```ts import.meta.vitest + * import { Option, Record } from "effect" * * const f = (x: number) => x * 2 * * const input: Record = { a: 3 } * - * Record.modify(input, "a", f) // Option.some({ a: 6 }) - * Record.modify(input, "b", f) // Option.none() + * Record.modify(input, "a", f) // => Option.some({ a: 6 }) + * Record.modify(input, "b", f) // => Option.none() * ``` * * @category mutations @@ -494,11 +494,11 @@ export const modify: { * * **Example** (Replacing a value at a key) * - * ```ts - * import { Record } from "effect" + * ```ts import.meta.vitest + * import { Option, Record } from "effect" * - * Record.replace({ a: 1, b: 2, c: 3 }, "a", 10) // Option.some({ a: 10, b: 2, c: 3 }) - * Record.replace(Record.empty(), "a", 10) // Option.none() + * Record.replace({ a: 1, b: 2, c: 3 }, "a", 10) // => Option.some({ a: 10, b: 2, c: 3 }) + * Record.replace(Record.empty(), "a", 10) // => Option.none() * ``` * * @category mutations @@ -537,11 +537,10 @@ export const replace: { * * **Example** (Removing a key) * - * ```ts + * ```ts import.meta.vitest * import { Record } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(Record.remove({ a: 1, b: 2 }, "a"), { b: 2 }) + * Record.remove({ a: 1, b: 2 }, "a") // => { b: 2 } * ``` * * @category mutations @@ -569,13 +568,13 @@ export const remove: { * * **Example** (Popping a value and removing its key) * - * ```ts - * import { Record } from "effect" + * ```ts import.meta.vitest + * import { Option, Record } from "effect" * * const input: Record = { a: 1, b: 2 } * - * Record.pop(input, "a") // Option.some([1, { b: 2 }]) - * Record.pop(input, "c") // Option.none() + * Record.pop(input, "a") // => Option.some([1, { b: 2 }]) + * Record.pop(input, "c") // => Option.none() * ``` * * @category mutations @@ -600,17 +599,16 @@ export const pop: { * * **Example** (Mapping record values) * - * ```ts + * ```ts import.meta.vitest * import { Record } from "effect" - * import * as assert from "node:assert" * * const f = (n: number) => `-${n}` * - * assert.deepStrictEqual(Record.map({ a: 3, b: 5 }, f), { a: "-3", b: "-5" }) + * Record.map({ a: 3, b: 5 }, f) // => { a: "-3", b: "-5" } * * const g = (n: number, key: string) => `${key.toUpperCase()}-${n}` * - * assert.deepStrictEqual(Record.map({ a: 3, b: 5 }, g), { a: "A-3", b: "B-5" }) + * Record.map({ a: 3, b: 5 }, g) // => { a: "A-3", b: "B-5" } * ``` * * @category mapping @@ -624,7 +622,7 @@ export const map: { (self: ReadonlyRecord, f: (a: A, key: NoInfer) => B): Record => { const out: Record = { ...self } as any for (const key of keys(self)) { - out[key] = f(self[key], key) + InternalRecord.assignProperty(out, key, f(self[key], key)) } return out } @@ -635,14 +633,10 @@ export const map: { * * **Example** (Mapping record keys) * - * ```ts + * ```ts import.meta.vitest * import { Record } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual( - * Record.mapKeys({ a: 3, b: 5 }, (key) => key.toUpperCase()), - * { A: 3, B: 5 } - * ) + * Record.mapKeys({ a: 3, b: 5 }, (key) => key.toUpperCase()) // => { A: 3, B: 5 } * ``` * * @category mapping @@ -665,7 +659,7 @@ export const mapKeys: { const out: Record = {} as any for (const key of keys(self)) { const a = self[key] - out[f(key, a)] = a + InternalRecord.assignProperty(out, f(key, a), a) } return out } @@ -676,14 +670,10 @@ export const mapKeys: { * * **Example** (Mapping record entries) * - * ```ts + * ```ts import.meta.vitest * import { Record } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual( - * Record.mapEntries({ a: 3, b: 5 }, (a, key) => [key.toUpperCase(), a + 1]), - * { A: 4, B: 6 } - * ) + * Record.mapEntries({ a: 3, b: 5 }, (a, key) => [key.toUpperCase(), a + 1]) // => { A: 4, B: 6 } * ``` * * @category mapping @@ -706,7 +696,7 @@ export const mapEntries: { const out = {} as Record for (const key of keys(self)) { const [k, b] = f(self[key], key) - out[k] = b + InternalRecord.assignProperty(out, k, b) } return out } @@ -718,13 +708,12 @@ export const mapEntries: { * * **Example** (Filtering and mapping with Result) * - * ```ts + * ```ts import.meta.vitest * import { Record, Result } from "effect" - * import * as assert from "node:assert" * * const x = { a: 1, b: 2, c: 3 } * const f = (a: number, key: string) => a > 2 ? Result.succeed(a * 2) : Result.failVoid - * assert.deepStrictEqual(Record.filterMap(x, f), { c: 6 }) + * Record.filterMap(x, f) // => { c: 6 } * ``` * * @category filtering @@ -748,7 +737,7 @@ export const filterMap: { for (const key of keys(self)) { const result = f(self[key], key) if (R.isSuccess(result)) { - out[key] = result.success + InternalRecord.assignProperty(out, key, result.success) } } return out @@ -760,12 +749,11 @@ export const filterMap: { * * **Example** (Filtering record values) * - * ```ts + * ```ts import.meta.vitest * import { Record } from "effect" - * import * as assert from "node:assert" * * const x = { a: 1, b: 2, c: 3, d: 4 } - * assert.deepStrictEqual(Record.filter(x, (n) => n > 2), { c: 3, d: 4 }) + * Record.filter(x, (n) => n > 2) // => { c: 3, d: 4 } * ``` * * @category filtering @@ -795,7 +783,7 @@ export const filter: { const out: Record = empty() for (const key of keys(self)) { if (predicate(self[key], key)) { - out[key] = self[key] + InternalRecord.assignProperty(out, key, self[key]) } } return out @@ -808,14 +796,10 @@ export const filter: { * * **Example** (Extracting Some values) * - * ```ts + * ```ts import.meta.vitest * import { Option, Record } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual( - * Record.getSomes({ a: Option.some(1), b: Option.none(), c: Option.some(2) }), - * { a: 1, c: 2 } - * ) + * Record.getSomes({ a: Option.some(1), b: Option.none(), c: Option.some(2) }) // => { a: 1, c: 2 } * ``` * * @category filtering @@ -830,7 +814,7 @@ export const getSomes: ( for (const key of keys(self)) { const option = self[key] if (Option.isSome(option)) { - out[key] = option.value + InternalRecord.assignProperty(out, key, option.value) } } return out @@ -842,18 +826,14 @@ export const getSomes: ( * * **Example** (Extracting Result failures) * - * ```ts + * ```ts import.meta.vitest * import { Record, Result } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual( - * Record.getFailures({ + * Record.getFailures({ * a: Result.succeed(1), * b: Result.fail("err"), * c: Result.succeed(2) - * }), - * { b: "err" } - * ) + * }) // => { b: "err" } * ``` * * @category filtering @@ -866,7 +846,7 @@ export const getFailures = ( for (const key of keys(self)) { const value = self[key] if (R.isFailure(value)) { - out[key] = value.failure + InternalRecord.assignProperty(out, key, value.failure) } } @@ -879,18 +859,14 @@ export const getFailures = ( * * **Example** (Extracting Result successes) * - * ```ts + * ```ts import.meta.vitest * import { Record, Result } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual( - * Record.getSuccesses({ + * Record.getSuccesses({ * a: Result.succeed(1), * b: Result.fail("err"), * c: Result.succeed(2) - * }), - * { a: 1, c: 2 } - * ) + * }) // => { a: 1, c: 2 } * ``` * * @category filtering @@ -903,7 +879,7 @@ export const getSuccesses = ( for (const key of keys(self)) { const value = self[key] if (R.isSuccess(value)) { - out[key] = value.success + InternalRecord.assignProperty(out, key, value.success) } } @@ -921,13 +897,12 @@ export const getSuccesses = ( * * **Example** (Partitioning with Result) * - * ```ts + * ```ts import.meta.vitest * import { Record, Result } from "effect" - * import * as assert from "node:assert" * * const x = { a: 1, b: 2, c: 3 } * const f = (n: number) => (n % 2 === 0 ? Result.succeed(n) : Result.fail(n)) - * assert.deepStrictEqual(Record.partition(x, f), [{ a: 1, c: 3 }, { b: 2 }]) + * Record.partition(x, f) // => [{ a: 1, c: 3 }, { b: 2 }] * ``` * * @category filtering @@ -954,9 +929,9 @@ export const partition: { for (const key of keys(self)) { const e = f(self[key], key) if (R.isFailure(e)) { - left[key] = e.failure + InternalRecord.assignProperty(left, key, e.failure) } else { - right[key] = e.success + InternalRecord.assignProperty(right, key, e.success) } } return [left, right] @@ -969,14 +944,10 @@ export const partition: { * * **Example** (Separating Result values) * - * ```ts + * ```ts import.meta.vitest * import { Record, Result } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual( - * Record.separate({ a: Result.fail("e"), b: Result.succeed(1) }), - * [{ a: "e" }, { b: 1 }] - * ) + * Record.separate({ a: Result.fail("e"), b: Result.succeed(1) }) // => [{ a: "e" }, { b: 1 }] * ``` * * @category filtering @@ -991,11 +962,10 @@ export const separate: ( * * **Example** (Getting record keys) * - * ```ts + * ```ts import.meta.vitest * import { Record } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(Record.keys({ a: 1, b: 2, c: 3 }), ["a", "b", "c"]) + * Record.keys({ a: 1, b: 2, c: 3 }) // => ["a", "b", "c"] * ``` * * @category getters @@ -1009,11 +979,10 @@ export const keys = (self: ReadonlyRecord): * * **Example** (Getting record values) * - * ```ts + * ```ts import.meta.vitest * import { Record } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(Record.values({ a: 1, b: 2, c: 3 }), [1, 2, 3]) + * Record.values({ a: 1, b: 2, c: 3 }) // => [1, 2, 3] * ``` * * @category getters @@ -1026,12 +995,11 @@ export const values = (self: ReadonlyRecord): Array { a: 5, b: 2 } + * Record.set("c", 5)({ a: 1, b: 2 }) // => { a: 1, b: 2, c: 5 } * ``` * * @category mutations @@ -1058,13 +1026,50 @@ export const set: { } ) +/** + * Mutates a record by assigning a value to a property. + * + * **When to use** + * + * Use when incrementally constructing a new record and copying it for every + * property would be unnecessary. + * + * **Gotchas** + * + * This function mutates `self`. When `key` is `"__proto__"`, it creates an + * own data property instead of changing the object's prototype. + * + * **Example** (Assigning an external key safely) + * + * ```ts import.meta.vitest + * import { Record } from "effect" + * + * const key: string = "__proto__" // Assume this comes from external input + * const value = { polluted: true } + * + * const unsafe: Record = {} + * unsafe[key] = value + * Object.getPrototypeOf(unsafe) === value // => true + * + * const safe: Record = {} + * Record.assignProperty(safe, key, value) + * Object.getPrototypeOf(safe) === Object.prototype // => true + * safe[key] === value // => true + * ``` + * + * @see {@link set} for an immutable update + * @category mutations + * @since 4.0.0 + */ +export const assignProperty: (self: object, key: PropertyKey, value: unknown) => void = InternalRecord.assignProperty + /** * Checks whether all the keys and values in one record are also found in another record. * Uses the provided equivalence function to compare values. * * **Example** (Checking subrecords with a custom equivalence) * - * ```ts + * ```ts import.meta.vitest * import { Equivalence, Record } from "effect" * * const isSubrecord = Record.isSubrecordBy( @@ -1077,15 +1082,9 @@ export const set: { * status: "active" * } * - * console.log( - * isSubrecord(required, available) - * ) // true - * console.log( - * isSubrecord({ role: "Admin", status: "inactive" }, available) - * ) // false - * console.log( - * isSubrecord(required, { role: "editor", status: "active" }) - * ) // false + * isSubrecord(required, available) // => true + * isSubrecord({ role: "Admin", status: "inactive" }, available) // => false + * isSubrecord(required, { role: "editor", status: "active" }) // => false * ``` * * @category predicates @@ -1114,18 +1113,11 @@ export const isSubrecordBy = (equivalence: Equivalence): { * * **Example** (Checking subrecords) * - * ```ts + * ```ts import.meta.vitest * import { Record } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual( - * Record.isSubrecord({ a: 1 } as Record, { a: 1, b: 2 }), - * true - * ) - * assert.deepStrictEqual( - * Record.isSubrecord({ a: 1, b: 2 }, { a: 1 } as Record), - * false - * ) + * Record.isSubrecord({ a: 1 } as Record, { a: 1, b: 2 }) // => true + * Record.isSubrecord({ a: 1, b: 2 }, { a: 1 } as Record) // => false * ``` * * @category predicates @@ -1141,14 +1133,10 @@ export const isSubrecord: { * * **Example** (Reducing record values) * - * ```ts + * ```ts import.meta.vitest * import { Record } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual( - * Record.reduce({ a: 1, b: 2, c: 3 }, 0, (acc, value, key) => acc + value), - * 6 - * ) + * Record.reduce({ a: 1, b: 2, c: 3 }, 0, (acc, value) => acc + value) // => 6 * ``` * * @category folding @@ -1180,15 +1168,14 @@ export const reduce: { * * **Example** (Checking every record value) * - * ```ts + * ```ts import.meta.vitest * import { Record } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(Record.every({ a: 1, b: 2 }, (n) => n > 0), true) - * assert.deepStrictEqual(Record.every({ a: 1, b: -1 }, (n) => n > 0), false) + * Record.every({ a: 1, b: 2 }, (n) => n > 0) // => true + * Record.every({ a: 1, b: -1 }, (n) => n > 0) // => false * ``` * - * @category predicates + * @category guards * @since 2.0.0 */ export const every: { @@ -1221,12 +1208,11 @@ export const every: { * * **Example** (Checking for any matching value) * - * ```ts + * ```ts import.meta.vitest * import { Record } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(Record.some({ a: 1, b: 2 }, (n) => n > 1), true) - * assert.deepStrictEqual(Record.some({ a: 1, b: 2 }, (n) => n > 2), false) + * Record.some({ a: 1, b: 2 }, (n) => n > 1) // => true + * Record.some({ a: 1, b: 2 }, (n) => n > 2) // => false * ``` * * @category predicates @@ -1253,14 +1239,10 @@ export const some: { * * **Example** (Merging records with union) * - * ```ts + * ```ts import.meta.vitest * import { Record } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual( - * Record.union({ a: 1, b: 2 }, { b: 3, c: 4 }, (a, b) => a + b), - * { a: 1, b: 5, c: 4 } - * ) + * Record.union({ a: 1, b: 2 }, { b: 3, c: 4 }, (a, b) => a + b) // => { a: 1, b: 5, c: 4 } * ``` * * @category combining @@ -1292,14 +1274,14 @@ export const union: { const out: Record = empty() for (const key of keys(self)) { if (has(that, key as any)) { - out[key] = combine(self[key], that[key as unknown as K1]) + InternalRecord.assignProperty(out, key, combine(self[key], that[key as unknown as K1])) } else { - out[key] = self[key] + InternalRecord.assignProperty(out, key, self[key]) } } for (const key of keys(that)) { if (!has(out, key)) { - out[key] = that[key] + InternalRecord.assignProperty(out, key, that[key]) } } return out @@ -1312,14 +1294,10 @@ export const union: { * * **Example** (Merging intersecting keys) * - * ```ts + * ```ts import.meta.vitest * import { Record } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual( - * Record.intersection({ a: 1, b: 2 }, { b: 3, c: 4 }, (a, b) => a + b), - * { b: 5 } - * ) + * Record.intersection({ a: 1, b: 2 }, { b: 3, c: 4 }, (a, b) => a + b) // => { b: 5 } * ``` * * @category combining @@ -1348,7 +1326,7 @@ export const intersection: { } for (const key of keys(self)) { if (has(that, key as any)) { - out[key] = combine(self[key], that[key as unknown as K1]) + InternalRecord.assignProperty(out, key, combine(self[key], that[key as unknown as K1])) } } return out @@ -1361,14 +1339,10 @@ export const intersection: { * * **Example** (Keeping keys unique to each record) * - * ```ts + * ```ts import.meta.vitest * import { Record } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual( - * Record.difference({ a: 1, b: 2 }, { b: 3, c: 4 }), - * { a: 1, c: 4 } - * ) + * Record.difference({ a: 1, b: 2 }, { b: 3, c: 4 }) // => { a: 1, c: 4 } * ``` * * @category combining @@ -1395,12 +1369,12 @@ export const difference: { const out = {} as Record for (const key of keys(self)) { if (!has(that, key as any)) { - out[key] = self[key] + InternalRecord.assignProperty(out, key, self[key]) } } for (const key of keys(that)) { if (!has(self, key as any)) { - out[key] = that[key] + InternalRecord.assignProperty(out, key, that[key]) } } return out @@ -1412,14 +1386,13 @@ export const difference: { * * **Example** (Comparing records with a value equivalence) * - * ```ts + * ```ts import.meta.vitest * import { Equal, Record } from "effect" - * import * as assert from "node:assert" * * const recordEquivalence = Record.makeEquivalence(Equal.asEquivalence()) * - * assert.deepStrictEqual(recordEquivalence({ a: 1, b: 2 }, { a: 1, b: 2 }), true) - * assert.deepStrictEqual(recordEquivalence({ a: 1, b: 2 }, { a: 1, b: 3 }), false) + * recordEquivalence({ a: 1, b: 2 }, { a: 1, b: 2 }) // => true + * recordEquivalence({ a: 1, b: 2 }, { a: 1, b: 3 }) // => false * ``` * * @category instances @@ -1437,11 +1410,10 @@ export const makeEquivalence = ( * * **Example** (Creating a singleton record) * - * ```ts + * ```ts import.meta.vitest * import { Record } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(Record.singleton("a", 1), { a: 1 }) + * Record.singleton("a", 1) // => { a: 1 } * ``` * * @category constructors @@ -1516,18 +1488,17 @@ export function makeReducerIntersection( * * **Example** (Finding the first matching entry) * - * ```ts - * import { Record } from "effect" + * ```ts import.meta.vitest + * import { Option, Record } from "effect" * * const record = { a: 1, b: 2, c: 3 } - * const result = Record.findFirst( + * Record.findFirst( * record, * (value, key) => value > 1 && key !== "b" - * ) - * console.log(result) // Option.Some(["c", 3]) + * ) // => Option.some(["c", 3]) * ``` * - * @category elements + * @category searching * @since 3.14.0 */ export const findFirst: { diff --git a/repos/effect/packages/effect/src/Redactable.ts b/repos/effect/packages/effect/src/Redactable.ts index 23aeb26054..b02eb30f2d 100644 --- a/repos/effect/packages/effect/src/Redactable.ts +++ b/repos/effect/packages/effect/src/Redactable.ts @@ -29,7 +29,7 @@ import { hasProperty } from "./Predicate.ts" * * **Example** (Masking an API key) * - * ```ts + * ```ts import.meta.vitest * import { Context, Redactable } from "effect" * * class ApiKey { @@ -39,6 +39,8 @@ import { hasProperty } from "./Predicate.ts" * return this.raw.slice(0, 4) + "..." * } * } + * + * Redactable.redact(new ApiKey("secret-key")) // => "secr..." * ``` * * @see {@link Redactable} for the interface this symbol belongs to @@ -63,7 +65,7 @@ export const symbolRedactable: unique symbol = Symbol.for("~effect/Redactable") * * **Example** (Masking an API key) * - * ```ts + * ```ts import.meta.vitest * import { Context, Redactable } from "effect" * * class ApiKey { @@ -73,6 +75,8 @@ export const symbolRedactable: unique symbol = Symbol.for("~effect/Redactable") * return this.raw.slice(0, 4) + "..." * } * } + * + * Redactable.redact(new ApiKey("secret-key")) // => "secr..." * ``` * * @see {@link symbolRedactable} for the symbol key to implement @@ -159,9 +163,12 @@ export function getRedacted(redactable: Redactable): unknown { /** @internal */ export const currentFiberTypeId = "~effect/Fiber/currentFiber" +const emptyMap = new Map() const emptyContext: Context.Context = { "~effect/Context": {} as any, - mapUnsafe: new Map(), + base: emptyMap, + depth: 0, + mapUnsafe: emptyMap, pipe() { return pipeArguments(this, arguments) } diff --git a/repos/effect/packages/effect/src/Redacted.ts b/repos/effect/packages/effect/src/Redacted.ts index afb1eb6988..881ec810d6 100644 --- a/repos/effect/packages/effect/src/Redacted.ts +++ b/repos/effect/packages/effect/src/Redacted.ts @@ -39,7 +39,7 @@ const TypeId = "~effect/data/Redacted" * * **Example** (Creating redacted values) * - * ```ts + * ```ts import.meta.vitest * import { Redacted } from "effect" * * // Create a redacted value to protect sensitive information @@ -47,6 +47,7 @@ const TypeId = "~effect/data/Redacted" * const userPassword = Redacted.make("user-password") * * // TypeScript will infer the types as Redacted + * Array.of(String(apiKey), String(userPassword)) // => ["", ""] * ``` * * @category models @@ -65,14 +66,14 @@ export interface Redacted extends Redacted.Variance, Equal.Eq * * **Example** (Using namespace utilities) * - * ```ts + * ```ts import.meta.vitest * import { Redacted } from "effect" * * // Use the Redacted namespace for type-level operations * const secret = Redacted.make("my-secret") * * // The namespace contains utilities for working with Redacted values - * const isRedacted = Redacted.isRedacted(secret) // true + * Redacted.isRedacted(secret) // => true * ``` * * @since 3.3.0 @@ -109,7 +110,7 @@ export declare namespace Redacted { * * **Example** (Extracting the redacted value type) * - * ```ts + * ```ts import.meta.vitest * import { Redacted } from "effect" * * type ApiKey = Redacted.Redacted<{ readonly token: string }> @@ -119,7 +120,7 @@ export declare namespace Redacted { * token: `${value.token}:rotated` * }) * - * console.log(rotate({ token: "secret" })) // { token: "secret:rotated" } + * rotate({ token: "secret" }) // => { token: "secret:rotated" } * ``` * * @category utility types @@ -142,17 +143,17 @@ export declare namespace Redacted { * * **Example** (Checking for redacted values) * - * ```ts + * ```ts import.meta.vitest * import { Redacted } from "effect" * * const secret = Redacted.make("my-secret") * const plainString = "not-secret" * - * console.log(Redacted.isRedacted(secret)) // true - * console.log(Redacted.isRedacted(plainString)) // false + * Redacted.isRedacted(secret) // => true + * Redacted.isRedacted(plainString) // => false * ``` * - * @category refinements + * @category guards * @since 3.3.0 */ export const isRedacted = (u: unknown): u is Redacted => hasProperty(u, TypeId) @@ -173,10 +174,11 @@ export const isRedacted = (u: unknown): u is Redacted => hasProperty(u, * * **Example** (Creating a redacted value) * - * ```ts + * ```ts import.meta.vitest * import { Redacted } from "effect" * * const API_KEY = Redacted.make("1234567890") + * String(API_KEY) // => "" * ``` * * @category constructors @@ -229,13 +231,12 @@ const Proto = { * * **Example** (Retrieving a redacted value) * - * ```ts + * ```ts import.meta.vitest * import { Redacted } from "effect" - * import * as assert from "node:assert" * * const API_KEY = Redacted.make("1234567890") * - * assert.equal(Redacted.value(API_KEY), "1234567890") + * Redacted.value(API_KEY) // => "1234567890" * ``` * * @category getters @@ -260,20 +261,20 @@ export const value: (self: Redacted) => T = redacted.value * * **Example** (Wiping a redacted value) * - * ```ts - * import { Redacted } from "effect" - * import * as assert from "node:assert" + * ```ts import.meta.vitest + * import { Redacted, Result } from "effect" * * const API_KEY = Redacted.make("1234567890") * - * assert.equal(Redacted.value(API_KEY), "1234567890") + * Redacted.value(API_KEY) // => "1234567890" * * Redacted.wipeUnsafe(API_KEY) * - * assert.throws( - * () => Redacted.value(API_KEY), - * new Error("Unable to get redacted value") - * ) + * const failure = Result.try({ + * try: () => Redacted.value(API_KEY), + * catch: (error) => (error as Error).message + * }) + * failure // => Result.fail("Unable to get redacted value") * ``` * * @category unsafe @@ -293,9 +294,8 @@ export const wipeUnsafe = (self: Redacted): boolean => redacted.redactedRe * * **Example** (Comparing redacted values) * - * ```ts + * ```ts import.meta.vitest * import { Equivalence, Redacted } from "effect" - * import * as assert from "node:assert" * * const API_KEY1 = Redacted.make("1234567890") * const API_KEY2 = Redacted.make("1-34567890") @@ -303,8 +303,8 @@ export const wipeUnsafe = (self: Redacted): boolean => redacted.redactedRe * * const equivalence = Redacted.makeEquivalence(Equivalence.strictEqual()) * - * assert.equal(equivalence(API_KEY1, API_KEY2), false) - * assert.equal(equivalence(API_KEY1, API_KEY3), true) + * equivalence(API_KEY1, API_KEY2) // => false + * equivalence(API_KEY1, API_KEY3) // => true * ``` * * @category instances diff --git a/repos/effect/packages/effect/src/Reducer.ts b/repos/effect/packages/effect/src/Reducer.ts index cff3de0514..6f1a6e42bc 100644 --- a/repos/effect/packages/effect/src/Reducer.ts +++ b/repos/effect/packages/effect/src/Reducer.ts @@ -37,13 +37,12 @@ import type * as Combiner from "./Combiner.ts" * * **Example** (String concatenation reducer) * - * ```ts + * ```ts import.meta.vitest * import { Reducer } from "effect" * * const Concat = Reducer.make((a, b) => a + b, "") * - * console.log(Concat.combineAll(["hello", " ", "world"])) - * // Output: "hello world" + * Concat.combineAll(["hello", " ", "world"]) // => "hello world" * ``` * * @see {@link make} – create a `Reducer` from a function and initial value @@ -89,7 +88,7 @@ export interface Reducer extends Combiner.Combiner { * * **Example** (Multiplying with short-circuit) * - * ```ts + * ```ts import.meta.vitest * import { Reducer } from "effect" * * const Product = Reducer.make( @@ -105,11 +104,8 @@ export interface Reducer extends Combiner.Combiner { * } * ) * - * console.log(Product.combineAll([2, 3, 4])) - * // Output: 24 - * - * console.log(Product.combineAll([2, 0, 4])) - * // Output: 0 + * Product.combineAll([2, 3, 4]) // => 24 + * Product.combineAll([2, 0, 4]) // => 0 * ``` * * @see {@link Reducer} – the interface this creates @@ -155,16 +151,13 @@ export function make( * * **Example** (Reversing string concatenation) * - * ```ts + * ```ts import.meta.vitest * import { Reducer, String } from "effect" * * const Prepend = Reducer.flip(String.ReducerConcat) * - * console.log(Prepend.combine("a", "b")) - * // Output: "ba" - * - * console.log(Prepend.combineAll(["a", "b", "c"])) - * // Output: "cba" + * Prepend.combine("a", "b") // => "ba" + * Prepend.combineAll(["a", "b", "c"]) // => "cba" * ``` * * @see {@link make} diff --git a/repos/effect/packages/effect/src/Ref.ts b/repos/effect/packages/effect/src/Ref.ts index d3569010c6..a5c783fca8 100644 --- a/repos/effect/packages/effect/src/Ref.ts +++ b/repos/effect/packages/effect/src/Ref.ts @@ -35,24 +35,18 @@ const TypeId = "~effect/Ref" * * **Example** (Reading and updating a ref) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Ref } from "effect" * * const program = Effect.gen(function*() { - * // Create a ref with initial value * const counter = yield* Ref.make(0) - * - * // Read the current value * const value = yield* Ref.get(counter) - * console.log(value) // 0 - * - * // Update the value atomically * yield* Ref.update(counter, (n) => n + 1) - * - * // Read the updated value * const newValue = yield* Ref.get(counter) - * console.log(newValue) // 1 + * return [value, newValue] * }) + * + * await Effect.runPromise(program) // => [0, 1] * ``` * * @see {@link make} for creating a `Ref` @@ -85,18 +79,17 @@ export declare namespace Ref { * * **Example** (Using invariant refs) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Ref } from "effect" * - * // This interface defines the invariant nature of Ref's type parameter - * // A Ref is both a producer and consumer of A * const program = Effect.gen(function*() { * const ref = yield* Ref.make(42) - * - * // Ref is invariant - it can both produce and consume numbers - * const value = yield* Ref.get(ref) // produces number - * yield* Ref.set(ref, value + 1) // consumes number + * const value = yield* Ref.get(ref) + * yield* Ref.set(ref, value + 1) + * return yield* Ref.get(ref) * }) + * + * await Effect.runPromise(program) // => 43 * ``` * * @category models @@ -136,18 +129,11 @@ const RefProto = { * * **Example** (Creating a ref unsafely) * - * ```ts + * ```ts import.meta.vitest * import { Ref } from "effect" * - * // Create a ref directly without Effect * const counter = Ref.makeUnsafe(0) - * - * // Get the current value - * const value = Ref.getUnsafe(counter) - * console.log(value) // 0 - * - * // Note: This is unsafe and should be used carefully - * // Prefer Ref.make for Effect-wrapped creation + * Ref.getUnsafe(counter) // => 0 * ``` * * @category constructors @@ -168,14 +154,15 @@ export const makeUnsafe = (value: A): Ref => { * * **Example** (Creating a ref) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Ref } from "effect" * * const program = Effect.gen(function*() { * const ref = yield* Ref.make(42) - * const value = yield* Ref.get(ref) - * console.log(value) // 42 + * return yield* Ref.get(ref) * }) + * + * await Effect.runPromise(program) // => 42 * ``` * * @see {@link makeUnsafe} for synchronous construction outside Effect code @@ -194,14 +181,15 @@ export const make = (value: A): Effect.Effect> => Effect.sync(() => ma * * **Example** (Getting the current value) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Ref } from "effect" * * const program = Effect.gen(function*() { * const ref = yield* Ref.make(42) - * const value = yield* Ref.get(ref) - * console.log(value) // 42 + * return yield* Ref.get(ref) * }) + * + * await Effect.runPromise(program) // => 42 * ``` * * @see {@link set} for replacing the current value @@ -220,29 +208,29 @@ export const get = (self: Ref) => Effect.sync(() => self.ref.current) * * **Example** (Setting a value) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Ref } from "effect" * * const program = Effect.gen(function*() { * const ref = yield* Ref.make(0) * yield* Ref.set(ref, 42) - * const value = yield* Ref.get(ref) - * console.log(value) // 42 + * return yield* Ref.get(ref) * }) * - * // Using multiple operations * const program2 = Effect.gen(function*() { * const ref = yield* Ref.make(0) * yield* Ref.set(ref, 100) - * const value = yield* Ref.get(ref) - * console.log(value) // 100 + * return yield* Ref.get(ref) * }) + * + * await Effect.runPromise(program) // => 42 + * await Effect.runPromise(program2) // => 100 * ``` * * @see {@link getAndSet} for setting while returning the previous value * @see {@link setAndGet} for setting while returning the new value * - * @category setters + * @category mutations * @since 2.0.0 */ export const set = dual< @@ -259,19 +247,18 @@ export const set = dual< * * **Example** (Replacing a value atomically) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Ref } from "effect" * * const program = Effect.gen(function*() { * const ref = yield* Ref.make("initial") * - * // Get current value and set new value atomically * const previous = yield* Ref.getAndSet(ref, "updated") - * console.log(previous) // "initial" - * * const current = yield* Ref.get(ref) - * console.log(current) // "updated" + * return [previous, current] * }) + * + * await Effect.runPromise(program) // => ["initial", "updated"] * ``` * * @see {@link set} for setting without returning the previous value @@ -299,19 +286,18 @@ export const getAndSet = dual< * * **Example** (Updating and returning the previous value) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Ref } from "effect" * * const program = Effect.gen(function*() { * const counter = yield* Ref.make(10) * - * // Get current value and update it atomically * const previous = yield* Ref.getAndUpdate(counter, (n) => n * 2) - * console.log(previous) // 10 - * * const current = yield* Ref.get(counter) - * console.log(current) // 20 + * return [previous, current] * }) + * + * await Effect.runPromise(program) // => [10, 20] * ``` * * @see {@link update} for updating without returning the previous value @@ -345,32 +331,26 @@ export const getAndUpdate = dual< * * **Example** (Conditionally updating and returning the previous value) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Option, Ref } from "effect" * * const program = Effect.gen(function*() { * const counter = yield* Ref.make(5) * - * // Only update if value is greater than 3 * const previous1 = yield* Ref.getAndUpdateSome( * counter, * (n) => n > 3 ? Option.some(n * 2) : Option.none() * ) - * console.log(previous1) // 5 - * * const current1 = yield* Ref.get(counter) - * console.log(current1) // 10 - * - * // Try to update again (won't update since 10 > 3 is true but let's say condition is n < 3) * const previous2 = yield* Ref.getAndUpdateSome( * counter, * (n) => n < 3 ? Option.some(n * 2) : Option.none() * ) - * console.log(previous2) // 10 - * * const current2 = yield* Ref.get(counter) - * console.log(current2) // 10 (unchanged) + * return [previous1, current1, previous2, current2] * }) + * + * await Effect.runPromise(program) // => [5, 10, 10, 10] * ``` * * @see {@link getAndUpdate} for always applying an update @@ -402,28 +382,24 @@ export const getAndUpdateSome = dual< * * **Example** (Setting and returning the new value) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Ref } from "effect" * * const program = Effect.gen(function*() { * const ref = yield* Ref.make(10) * - * // Set new value and get it back in one operation * const newValue = yield* Ref.setAndGet(ref, 42) - * console.log(newValue) // 42 - * - * // Verify the ref contains the new value * const current = yield* Ref.get(ref) - * console.log(current) // 42 + * return [newValue, current] * }) * - * // Useful for sequential operations * const program2 = Effect.gen(function*() { * const counter = yield* Ref.make(0) - * - * const newValue = yield* Ref.setAndGet(counter, 20) - * console.log(newValue) // 20 + * return yield* Ref.setAndGet(counter, 20) * }) + * + * await Effect.runPromise(program) // => [42, 42] + * await Effect.runPromise(program2) // => 20 * ``` * * @category mutations @@ -450,41 +426,36 @@ export const setAndGet = dual< * * **Example** (Modifying a value atomically) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Ref } from "effect" * * const program = Effect.gen(function*() { * const counter = yield* Ref.make(10) * - * // Modify the ref and return some computation result * const result = yield* Ref.modify(counter, (n) => [ - * `Previous value was ${n}`, // Return value - * n * 2 // New ref value + * `Previous value was ${n}`, + * n * 2 * ]) - * - * console.log(result) // "Previous value was 10" - * * const current = yield* Ref.get(counter) - * console.log(current) // 20 + * return [result, current] * }) * - * // Example with more complex computation * const program2 = Effect.gen(function*() { * const state = yield* Ref.make({ count: 0, total: 0 }) - * - * const incremented = yield* Ref.modify(state, (s) => [ - * s.count, // Return previous count - * { count: s.count + 1, total: s.total + s.count + 1 } // New state + * return yield* Ref.modify(state, (s) => [ + * s.count, + * { count: s.count + 1, total: s.total + s.count + 1 } * ]) - * - * console.log(incremented) // 0 * }) + * + * await Effect.runPromise(program) // => ["Previous value was 10", 20] + * await Effect.runPromise(program2) // => 0 * ``` * * @see {@link updateAndGet} for returning the new stored value * @see {@link modifySome} for optionally updating while returning a separate result * - * @category setters + * @category mutations * @since 2.0.0 */ export const modify = dual< @@ -513,13 +484,12 @@ export const modify = dual< * * **Example** (Conditionally modifying a value) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Option, Ref } from "effect" * * const program = Effect.gen(function*() { * const counter = yield* Ref.make(5) * - * // Only modify if value is greater than 3 * const result1 = yield* Ref.modifySome( * counter, * (n) => @@ -527,13 +497,7 @@ export const modify = dual< * ? [`incremented ${n}`, Option.some(n + 10)] * : ["no change", Option.none()] * ) - * - * console.log(result1) // "incremented 5" - * * const current1 = yield* Ref.get(counter) - * console.log(current1) // 15 - * - * // Try to modify with a condition that fails * const result2 = yield* Ref.modifySome( * counter, * (n) => @@ -541,18 +505,17 @@ export const modify = dual< * ? [`decremented ${n}`, Option.some(n - 5)] * : ["no change", Option.none()] * ) - * - * console.log(result2) // "no change" - * * const current2 = yield* Ref.get(counter) - * console.log(current2) // 15 (unchanged) + * return [result1, current1, result2, current2] * }) + * + * await Effect.runPromise(program) // => ["incremented 5", 15, "no change", 15] * ``` * * @see {@link modify} for always storing a new value * @see {@link updateSome} for optional updates without a separate return value * - * @category setters + * @category mutations * @since 2.0.0 */ export const modifySome: { @@ -581,32 +544,30 @@ export const modifySome: { * * **Example** (Updating a value) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Ref } from "effect" * * const program = Effect.gen(function*() { * const counter = yield* Ref.make(5) * - * // Update the value * yield* Ref.update(counter, (n) => n * 2) - * - * const value = yield* Ref.get(counter) - * console.log(value) // 10 + * return yield* Ref.get(counter) * }) * - * // Using multiple operations * const program2 = Effect.gen(function*() { * const counter = yield* Ref.make(5) * yield* Ref.update(counter, (n: number) => n + 10) - * const value = yield* Ref.get(counter) - * console.log(value) // 15 + * return yield* Ref.get(counter) * }) + * + * await Effect.runPromise(program) // => 10 + * await Effect.runPromise(program2) // => 15 * ``` * * @see {@link updateAndGet} for returning the new value * @see {@link getAndUpdate} for returning the previous value * - * @category setters + * @category mutations * @since 2.0.0 */ export const update = dual< @@ -626,20 +587,18 @@ export const update = dual< * * **Example** (Updating and returning the new value) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Ref } from "effect" * * const program = Effect.gen(function*() { * const counter = yield* Ref.make(5) * - * // Update and get the new value in one operation * const newValue = yield* Ref.updateAndGet(counter, (n) => n * 3) - * console.log(newValue) // 15 - * - * // Verify the ref contains the new value * const current = yield* Ref.get(counter) - * console.log(current) // 15 + * return [newValue, current] * }) + * + * await Effect.runPromise(program) // => [15, 15] * ``` * * @see {@link update} for updating without returning the new value @@ -667,37 +626,33 @@ export const updateAndGet = dual< * * **Example** (Conditionally updating a value) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Option, Ref } from "effect" * * const program = Effect.gen(function*() { * const counter = yield* Ref.make(5) * - * // Only update if value is even * yield* Ref.updateSome( * counter, * (n) => n % 2 === 0 ? Option.some(n * 2) : Option.none() * ) - * - * let current = yield* Ref.get(counter) - * console.log(current) // 5 (unchanged because 5 is odd) - * - * // Set to even number and try again + * const before = yield* Ref.get(counter) * yield* Ref.set(counter, 6) * yield* Ref.updateSome( * counter, * (n) => n % 2 === 0 ? Option.some(n * 2) : Option.none() * ) - * - * current = yield* Ref.get(counter) - * console.log(current) // 12 (updated because 6 is even) + * const after = yield* Ref.get(counter) + * return [before, after] * }) + * + * await Effect.runPromise(program) // => [5, 12] * ``` * * @see {@link update} for always applying an update * @see {@link updateSomeAndGet} for returning the resulting current value * - * @category setters + * @category mutations * @since 2.0.0 */ export const updateSome = dual< @@ -727,26 +682,24 @@ export const updateSome = dual< * * **Example** (Conditionally updating and returning the current value) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Option, Ref } from "effect" * * const program = Effect.gen(function*() { * const counter = yield* Ref.make(10) * - * // Only update if value is greater than 5 * const result1 = yield* Ref.updateSomeAndGet( * counter, * (n) => n > 5 ? Option.some(n / 2) : Option.none() * ) - * console.log(result1) // 5 (updated and returned) - * - * // Try to update again with same condition * const result2 = yield* Ref.updateSomeAndGet( * counter, * (n) => n > 5 ? Option.some(n / 2) : Option.none() * ) - * console.log(result2) // 5 (unchanged because 5 is not > 5) + * return [result1, result2] * }) + * + * await Effect.runPromise(program) // => [5, 5] * ``` * * @see {@link updateSome} for conditional updates without returning a value @@ -781,18 +734,11 @@ export const updateSomeAndGet = dual< * * **Example** (Reading a ref unsafely) * - * ```ts + * ```ts import.meta.vitest * import { Ref } from "effect" * - * // Create a ref directly * const counter = Ref.makeUnsafe(42) - * - * // Get the value synchronously - * const value = Ref.getUnsafe(counter) - * console.log(value) // 42 - * - * // Note: This is unsafe and should be used carefully - * // Prefer Ref.get for Effect-wrapped access + * Ref.getUnsafe(counter) // => 42 * ``` * * @category getters diff --git a/repos/effect/packages/effect/src/References.ts b/repos/effect/packages/effect/src/References.ts index bc7926e3e5..5d5e51dd98 100644 --- a/repos/effect/packages/effect/src/References.ts +++ b/repos/effect/packages/effect/src/References.ts @@ -124,52 +124,6 @@ export { Tracer } -/** - * Context reference for controlling the current concurrency limit. Can be set to "unbounded" - * for unlimited concurrency or a specific number to limit concurrent operations. - * - * **When to use** - * - * Use to configure the default concurrency limit for operations that read - * concurrency from the current context. - * - * **Example** (Setting current concurrency) - * - * ```ts - * import { Effect, References } from "effect" - * - * const limitConcurrency = Effect.gen(function*() { - * // Get current setting - * const current = yield* References.CurrentConcurrency - * console.log(current) // "unbounded" (default) - * - * // Run with limited concurrency - * yield* Effect.provideService( - * Effect.gen(function*() { - * const limited = yield* References.CurrentConcurrency - * console.log(limited) // 10 - * }), - * References.CurrentConcurrency, - * 10 - * ) - * - * // Run with unlimited concurrency - * yield* Effect.provideService( - * Effect.gen(function*() { - * const unlimited = yield* References.CurrentConcurrency - * console.log(unlimited) // "unbounded" - * }), - * References.CurrentConcurrency, - * "unbounded" - * ) - * }) - * ``` - * - * @category references - * @since 4.0.0 - */ -export const CurrentConcurrency: Context.Reference = references.CurrentConcurrency - /** * Context reference for managing log annotations that are automatically added to all log entries. * These annotations provide contextual metadata that appears in every log message. @@ -181,23 +135,19 @@ export const CurrentConcurrency: Context.Reference = refer * * **Example** (Managing log annotations) * - * ```ts - * import { Console, Effect, References } from "effect" + * ```ts import.meta.vitest + * import { Effect, References } from "effect" * * const logAnnotationExample = Effect.gen(function*() { * // Get current annotations (empty by default) * const current = yield* References.CurrentLogAnnotations - * console.log(current) // {} + * const defaultCount = Object.keys(current).length * * // Run with custom log annotations - * yield* Effect.provideService( + * const custom = yield* Effect.provideService( * Effect.gen(function*() { * const annotations = yield* References.CurrentLogAnnotations - * console.log(annotations) // { requestId: "req-123", userId: "user-456", version: "1.0.0" } - * - * // All log entries will include these annotations - * yield* Console.log("Starting operation") - * yield* Console.info("Processing data") + * return [annotations.requestId, annotations.userId, annotations.version] * }), * References.CurrentLogAnnotations, * { @@ -208,12 +158,10 @@ export const CurrentConcurrency: Context.Reference = refer * ) * * // Run with extended annotations - * yield* Effect.provideService( + * const extended = yield* Effect.provideService( * Effect.gen(function*() { - * const extended = yield* References.CurrentLogAnnotations - * console.log(extended) // { requestId: "req-123", userId: "user-456", version: "1.0.0", operation: "data-sync", timestamp: 1234567890 } - * - * yield* Console.log("Operation completed with extended context") + * const annotations = yield* References.CurrentLogAnnotations + * return [annotations.operation, annotations.timestamp] * }), * References.CurrentLogAnnotations, * { @@ -224,7 +172,11 @@ export const CurrentConcurrency: Context.Reference = refer * timestamp: 1234567890 * } * ) + * + * return [defaultCount, custom, extended] * }) + * + * await Effect.runPromise(logAnnotationExample) // => [0, ["req-123", "user-456", "1.0.0"], ["data-sync", 1234567890]] * ``` * * @category references @@ -246,39 +198,25 @@ export const CurrentLogAnnotations: Context.Reference = [] + * const logger = Logger.make(({ logLevel }) => { + * levels.push(logLevel) + * }) * - * // Change to Error level to reduce noise - * yield* Effect.provideService( - * Effect.gen(function*() { - * const level = yield* References.CurrentLogLevel - * console.log(level) // "Error" - * yield* Console.info("This info message will be filtered out") - * yield* Console.error("This error message will be shown") - * }), - * References.CurrentLogLevel, - * "Error" + * const program = Effect.gen(function*() { + * yield* Effect.log("uses the default level") + * yield* Effect.log("uses the provided level").pipe( + * Effect.provideService(References.CurrentLogLevel, "Error") * ) * }) + * + * await Effect.runPromise(program.pipe(Effect.provide(Logger.layer([logger])))) + * levels // => ["Info", "Error"] * ``` * * @category references @@ -297,24 +235,20 @@ export const CurrentLogLevel: Context.Reference = references.CurrentLo * * **Example** (Tracking log spans) * - * ```ts - * import { Console, Effect, References } from "effect" + * ```ts import.meta.vitest + * import { Effect, References } from "effect" * * const logSpanExample = Effect.gen(function*() { * // Get current spans (empty by default) * const current = yield* References.CurrentLogSpans - * console.log(current.length) // 0 + * const defaultCount = current.length * * // Add a log span manually * const databaseConnectionStartedAt = 0 - * yield* Effect.provideService( + * const database = yield* Effect.provideService( * Effect.gen(function*() { - * // Simulate some work - * yield* Effect.sleep("100 millis") - * yield* Console.log("Database operation in progress") - * * const spans = yield* References.CurrentLogSpans - * console.log("Active spans:", spans.map(([label]) => label)) // ["database-connection"] + * return spans.map(([label]) => label) * }), * References.CurrentLogSpans, * [["database-connection", databaseConnectionStartedAt]] @@ -322,12 +256,10 @@ export const CurrentLogLevel: Context.Reference = references.CurrentLo * * // Add another span * const dataProcessingStartedAt = 100 - * yield* Effect.provideService( + * const processing = yield* Effect.provideService( * Effect.gen(function*() { * const spans = yield* References.CurrentLogSpans - * console.log("Active spans:", spans.map(([label]) => label)) // ["database-connection", "data-processing"] - * - * yield* Console.log("Multiple operations in progress") + * return spans.map(([label]) => label) * }), * References.CurrentLogSpans, * [ @@ -337,15 +269,19 @@ export const CurrentLogLevel: Context.Reference = references.CurrentLo * ) * * // Clear spans when operations complete - * yield* Effect.provideService( + * const cleared = yield* Effect.provideService( * Effect.gen(function*() { * const spans = yield* References.CurrentLogSpans - * console.log("Active spans:", spans.length) // 0 + * return spans.length * }), * References.CurrentLogSpans, * [] * ) + * + * return [defaultCount, database, processing, cleared] * }) + * + * await Effect.runPromise(logSpanExample) // => [0, ["database-connection"], ["database-connection", "data-processing"], 0] * ``` * * @category references @@ -384,47 +320,27 @@ export const CurrentStackFrame: Context.Reference = refe * * Use to filter out log entries below a severity threshold. * - * **Example** (Setting the minimum log level) - * - * ```ts - * import { Console, Effect, References } from "effect" - * - * const configureMinimumLogging = Effect.gen(function*() { - * // Get current minimum level (default is "Info") - * const current = yield* References.MinimumLogLevel - * console.log(current) // "Info" + * **Example** (Filtering logs below the minimum level) * - * // Set minimum level to Warn - Debug and Info will be filtered - * yield* Effect.provideService( - * Effect.gen(function*() { - * const minLevel = yield* References.MinimumLogLevel - * console.log(minLevel) // "Warn" - * - * // These won't be processed at all - * yield* Console.debug("Debug message") // Filtered out - * yield* Console.info("Info message") // Filtered out + * ```ts import.meta.vitest + * import { Effect, Logger, References } from "effect" * - * // These will be processed - * yield* Console.warn("Warning message") // Shown - * yield* Console.error("Error message") // Shown - * }), - * References.MinimumLogLevel, - * "Warn" - * ) - * - * // Reset to default Info level - * yield* Effect.provideService( - * Effect.gen(function*() { - * const minLevel = yield* References.MinimumLogLevel - * console.log(minLevel) // "Info" + * const levels: Array = [] + * const logger = Logger.make(({ logLevel }) => { + * levels.push(logLevel) + * }) * - * // Now info messages will be processed - * yield* Console.info("Info message") // Shown - * }), - * References.MinimumLogLevel, - * "Info" - * ) + * const program = Effect.gen(function*() { + * yield* Effect.logInfo("filtered out") + * yield* Effect.logWarning("included at the threshold") + * yield* Effect.logError("included above the threshold") * }) + * + * await Effect.runPromise(program.pipe( + * Effect.provideService(References.MinimumLogLevel, "Warn"), + * Effect.provide(Logger.layer([logger])) + * )) + * levels // => ["Warn", "Error"] * ``` * * @category references @@ -442,40 +358,31 @@ export const MinimumLogLevel: Context.Reference = references.MinimumLo * * **Example** (Toggling tracing) * - * ```ts + * ```ts import.meta.vitest * import { Effect, References } from "effect" * * const tracingControl = Effect.gen(function*() { * // Check if tracing is enabled (default is true) * const current = yield* References.TracerEnabled - * console.log(current) // true * * // Disable tracing globally - * yield* Effect.provideService( - * Effect.gen(function*() { - * const isEnabled = yield* References.TracerEnabled - * console.log(isEnabled) // false - * - * // Spans will not be traced in this context - * yield* Effect.log("This will not be traced") - * }), + * const disabled = yield* Effect.provideService( + * References.TracerEnabled, * References.TracerEnabled, * false * ) * * // Re-enable tracing - * yield* Effect.provideService( - * Effect.gen(function*() { - * const isEnabled = yield* References.TracerEnabled - * console.log(isEnabled) // true - * - * // All subsequent spans will be traced - * yield* Effect.log("This will be traced") - * }), + * const enabled = yield* Effect.provideService( + * References.TracerEnabled, * References.TracerEnabled, * true * ) + * + * return [current, disabled, enabled] * }) + * + * await Effect.runPromise(tracingControl) // => [true, false, true] * ``` * * @category references @@ -493,27 +400,20 @@ export const TracerEnabled: Context.Reference = references.TracerEnable * * **Example** (Managing span annotations) * - * ```ts + * ```ts import.meta.vitest * import { Effect, References } from "effect" * * const spanAnnotationExample = Effect.gen(function*() { * // Get current annotations (empty by default) * const current = yield* References.TracerSpanAnnotations - * console.log(current) // {} + * const defaultCount = Object.keys(current).length * * // Set global span annotations - * yield* Effect.provideService( + * const configured = yield* Effect.provideService( * Effect.gen(function*() { * // Get current annotations * const annotations = yield* References.TracerSpanAnnotations - * console.log(annotations) // { service: "user-service", version: "1.2.3", environment: "production" } - * - * // All spans created will include these annotations - * yield* Effect.gen(function*() { - * // Add more specific annotations for this span - * yield* Effect.annotateCurrentSpan("userId", "123") - * yield* Effect.log("Processing user") - * }) + * return [annotations.service, annotations.version, annotations.environment] * }), * References.TracerSpanAnnotations, * { @@ -524,15 +424,19 @@ export const TracerEnabled: Context.Reference = references.TracerEnable * ) * * // Clear annotations - * yield* Effect.provideService( + * const cleared = yield* Effect.provideService( * Effect.gen(function*() { * const annotations = yield* References.TracerSpanAnnotations - * console.log(annotations) // {} + * return Object.keys(annotations).length * }), * References.TracerSpanAnnotations, * {} * ) + * + * return [defaultCount, configured, cleared] * }) + * + * await Effect.runPromise(spanAnnotationExample) // => [0, ["user-service", "1.2.3", "production"], 0] * ``` * * @category references @@ -551,13 +455,13 @@ export const TracerSpanAnnotations: Context.Reference links.length), * References.TracerSpanLinks, * [spanLink] * ) * * // Clear links - * yield* Effect.provideService( - * Effect.gen(function*() { - * const links = yield* References.TracerSpanLinks - * console.log(links.length) // 0 - * }), + * const clearedCount = yield* Effect.provideService( + * Effect.map(References.TracerSpanLinks, (links) => links.length), * References.TracerSpanLinks, * [] * ) + * + * return [defaultCount, configuredCount, clearedCount] * }) + * + * await Effect.runPromise(spanLinksExample) // => [0, 1, 0] * ``` * * @category references @@ -619,36 +514,31 @@ export const TracerSpanLinks: Context.Reference> = refer * * **Example** (Toggling trace timing) * - * ```ts + * ```ts import.meta.vitest * import { Effect, References } from "effect" * * const tracingControl = Effect.gen(function*() { * // Check if trace timing is enabled (default is true) * const current = yield* References.TracerTimingEnabled - * console.log(current) // true * * // Disable trace timing globally - * yield* Effect.provideService( - * Effect.gen(function*() { - * // Spans will not having timing information in this context - * const isEnabled = yield* References.TracerTimingEnabled - * console.log(isEnabled) // false - * }), + * const disabled = yield* Effect.provideService( + * References.TracerTimingEnabled, * References.TracerTimingEnabled, * false * ) * * // Re-enable trace timing - * yield* Effect.provideService( - * Effect.gen(function*() { - * // Spans will have timing information in this context - * const isEnabled = yield* References.TracerTimingEnabled - * console.log(isEnabled) // true - * }), + * const enabled = yield* Effect.provideService( + * References.TracerTimingEnabled, * References.TracerTimingEnabled, * true * ) + * + * return [current, disabled, enabled] * }) + * + * await Effect.runPromise(tracingControl) // => [true, false, true] * ``` * * @category references @@ -756,27 +646,25 @@ export { * * **Example** (Providing a custom scheduler) * - * ```ts + * ```ts import.meta.vitest * import { Effect, References, Scheduler } from "effect" * * const customScheduling = Effect.gen(function*() { * // Get current scheduler (default is MixedScheduler) * const current = yield* References.Scheduler - * console.log(current) // MixedScheduler instance + * const isDefaultMixed = current instanceof Scheduler.MixedScheduler * * // Use a custom scheduler - * yield* Effect.provideService( - * Effect.gen(function*() { - * const scheduler = yield* References.Scheduler - * console.log(scheduler) // Custom scheduler instance - * - * // Effects will use the custom scheduler in this context - * yield* Effect.log("Using custom scheduler") - * }), + * const isCustomMixed = yield* Effect.provideService( + * Effect.map(References.Scheduler, (scheduler) => scheduler instanceof Scheduler.MixedScheduler), * References.Scheduler, * new Scheduler.MixedScheduler() * ) + * + * return [isDefaultMixed, isCustomMixed] * }) + * + * await Effect.runPromise(customScheduling) // => [true, true] * ``` * * @category references diff --git a/repos/effect/packages/effect/src/RegExp.ts b/repos/effect/packages/effect/src/RegExp.ts index 0123914f8a..c1c97ea3ee 100644 --- a/repos/effect/packages/effect/src/RegExp.ts +++ b/repos/effect/packages/effect/src/RegExp.ts @@ -22,15 +22,13 @@ import * as predicate from "./Predicate.ts" * * **Example** (Creating a regular expression) * - * ```ts + * ```ts import.meta.vitest * import { RegExp } from "effect" * - * // Create a regular expression using Effect's RegExp constructor * const pattern = new RegExp.RegExp("hello", "i") - * - * // Test the pattern - * console.log(pattern.test("Hello World")) // true - * console.log(pattern.test("goodbye")) // false + * pattern // => /hello/i + * pattern.test("Hello World") // => true + * pattern.test("goodbye") // => false * ``` * * @category constructors @@ -47,12 +45,11 @@ export const RegExp = globalThis.RegExp * * **Example** (Checking for regular expressions) * - * ```ts + * ```ts import.meta.vitest * import { RegExp } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(RegExp.isRegExp(/a/), true) - * assert.deepStrictEqual(RegExp.isRegExp("a"), false) + * RegExp.isRegExp(/a/) // => true + * RegExp.isRegExp("a") // => false * ``` * * @category guards @@ -69,14 +66,13 @@ export const isRegExp: (input: unknown) => input is RegExp = predicate.isRegExp * * **Example** (Escaping a pattern string) * - * ```ts + * ```ts import.meta.vitest * import { RegExp } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(RegExp.escape("a*b"), "a\\*b") + * RegExp.escape("a*b") // => "a\\*b" * ``` * - * @category RegExp + * @category transforming * @since 2.0.0 */ export const escape = (string: string): string => string.replace(/[/\\^$*+?.()|[\]{}]/g, "\\$&") diff --git a/repos/effect/packages/effect/src/Request.ts b/repos/effect/packages/effect/src/Request.ts index 5eb39ffb03..5ccb1032ea 100644 --- a/repos/effect/packages/effect/src/Request.ts +++ b/repos/effect/packages/effect/src/Request.ts @@ -17,6 +17,7 @@ import type * as Exit from "./Exit.ts" import { dual } from "./Function.ts" import * as core from "./internal/core.ts" import * as internalEffect from "./internal/effect.ts" +import * as InternalRecord from "./internal/record.ts" import { hasProperty } from "./Predicate.ts" import type * as Types from "./Types.ts" @@ -28,7 +29,7 @@ const TypeId = "~effect/Request" * * **Example** (Defining typed requests) * - * ```ts + * ```ts import.meta.vitest * import type { Request } from "effect" * * // Define a request that fetches a user by ID @@ -41,6 +42,7 @@ const TypeId = "~effect/Request" * interface GetAllUsers extends Request.Request, Error> { * readonly _tag: "GetAllUsers" * } + * * ``` * * @category models @@ -63,7 +65,7 @@ export interface Request extends Variance @@ -98,7 +100,7 @@ export interface Variance { * * **Example** (Using generated request constructors) * - * ```ts + * ```ts import.meta.vitest * import { Request } from "effect" * * interface GetUser extends Request.Request { @@ -108,7 +110,10 @@ export interface Variance { * * // Constructor type is used internally by Request.of() and Request.tagged() * const GetUser = Request.tagged("GetUser") - * const userRequest = GetUser({ id: 123 }) + * const request = GetUser({ id: 123 }) + * + * request._tag // => "GetUser" + * request.id // => 123 * ``` * * @category models @@ -123,7 +128,7 @@ export interface Constructor, T extends keyof R * * **Example** (Extracting a request error type) * - * ```ts + * ```ts import.meta.vitest * import type { Request } from "effect" * * interface GetUser extends Request.Request { @@ -132,6 +137,7 @@ export interface Constructor, T extends keyof R * * // Extract the error type from a Request using the utility * type UserError = Request.Error // Error + * * ``` * * @category utility types @@ -144,7 +150,7 @@ export type Error> = [T] extends [Request { @@ -154,6 +160,7 @@ export type Error> = [T] extends [Request // string + * * ``` * * @category utility types @@ -176,7 +183,7 @@ export type Services> = [T] extends [Request { @@ -186,6 +193,7 @@ export type Services> = [T] extends [Request // Exit.Exit + * * ``` * * @category utility types @@ -226,7 +234,7 @@ export const RequestPrototype: Request = { * * **Example** (Checking request values) * - * ```ts + * ```ts import.meta.vitest * import { Request } from "effect" * * declare const User: unique symbol @@ -241,8 +249,8 @@ export const RequestPrototype: Request = { * const GetUser = Request.tagged("GetUser") * * const request = GetUser({ id: "123" }) - * console.log(Request.isRequest(request)) // true - * console.log(Request.isRequest("not a request")) // false + * Request.isRequest(request) // => true + * Request.isRequest("not a request") // => false * ``` * * @category guards @@ -255,7 +263,7 @@ export const isRequest = (u: unknown): u is Request = * * **Example** (Creating untagged request constructors) * - * ```ts + * ```ts import.meta.vitest * import { Request } from "effect" * * declare const UserProfile: unique symbol @@ -274,13 +282,16 @@ export const isRequest = (u: unknown): u is Request = * id: "user-123", * includeSettings: true * }) + * + * request.id // => "user-123" + * request.includeSettings // => true * ``` * * @category constructors * @since 2.0.0 */ export const of = >(): Constructor => (args) => - Object.assign(Object.create(RequestPrototype), args) + Object.setPrototypeOf({ ...(args as R) }, RequestPrototype) /** * Creates a constructor function for a tagged Request type. The tag is automatically @@ -288,7 +299,7 @@ export const of = >(): Constructor => (args) * * **Example** (Creating tagged request constructors) * - * ```ts + * ```ts import.meta.vitest * import { Request } from "effect" * * declare const User: unique symbol @@ -317,8 +328,7 @@ export const of = >(): Constructor => (args) * const postRequest = GetPost({ id: "post-456" }) * * // _tag is automatically set - * console.log(userRequest._tag) // "GetUser" - * console.log(postRequest._tag) // "GetPost" + * Array.of(userRequest._tag, postRequest._tag) // => ["GetUser", "GetPost"] * ``` * * @category constructors @@ -328,10 +338,7 @@ export const tagged = & { _tag: string }>( tag: R["_tag"] ): Constructor => (args) => { - const request = Object.create(RequestPrototype) - if (args) Object.assign(request, args) - request._tag = tag - return request + return Object.setPrototypeOf({ ...(args as R), _tag: tag }, RequestPrototype) } /** @@ -344,7 +351,7 @@ export const tagged = & { _tag: string }>( * * **Example** (Defining request classes) * - * ```ts + * ```ts import.meta.vitest * import { Request } from "effect" * * class GetUser extends Request.Class<{ id: number }, string, Error> { @@ -354,7 +361,7 @@ export const tagged = & { _tag: string }>( * } * * const getUserRequest = new GetUser(123) - * console.log(getUserRequest.id) // 123 + * getUserRequest.id // => 123 * ``` * * @category constructors @@ -364,9 +371,9 @@ export const Class: new, Success, Error = never, C args: Types.Equals>, {}> extends true ? void : { readonly [P in keyof A as P extends keyof Request ? never : P]: A[P] } ) => Request & Readonly = (function() { - function Class(this: any, args: any) { + function Class(this: object, args: object | undefined) { if (args) { - Object.assign(this, args) + InternalRecord.assignProperties(this, args) } } Class.prototype = RequestPrototype @@ -383,7 +390,7 @@ export const Class: new, Success, Error = never, C * * **Example** (Defining tagged request classes) * - * ```ts + * ```ts import.meta.vitest * import { Request } from "effect" * * class GetUserById @@ -391,8 +398,9 @@ export const Class: new, Success, Error = never, C * {} * * const request = new GetUserById({ id: 123 }) - * console.log(request._tag) // "GetUserById" - * console.log(request.id) // 123 + * + * request._tag // => "GetUserById" + * request.id // => 123 * ``` * * @category constructors @@ -554,7 +562,7 @@ export const succeed: { * an `uninterruptible` flag used by batching and caching internals, and the * `completeUnsafe` callback used by resolvers to supply the final `Exit`. * - * @category entry + * @category models * @since 2.0.0 */ export interface Entry { @@ -580,7 +588,7 @@ export interface Entry { * most application code receives entries from a `RequestResolver` instead of * constructing them directly. * - * @category entry + * @category constructors * @since 2.0.0 */ export const makeEntry = (options: { diff --git a/repos/effect/packages/effect/src/RequestResolver.ts b/repos/effect/packages/effect/src/RequestResolver.ts index 33cce7167a..2061d4b860 100644 --- a/repos/effect/packages/effect/src/RequestResolver.ts +++ b/repos/effect/packages/effect/src/RequestResolver.ts @@ -52,14 +52,14 @@ const TypeId = "~effect/RequestResolver" * * **Example** (Defining a request resolver) * - * ```ts - * import { Effect, Exit, RequestResolver } from "effect" - * import type { Request } from "effect" + * ```ts import.meta.vitest + * import { Effect, Exit, Request, RequestResolver } from "effect" * * interface GetUserRequest extends Request.Request { * readonly _tag: "GetUserRequest" * readonly id: number * } + * const GetUserRequest = Request.tagged("GetUserRequest") * * // In practice, you would typically use RequestResolver.make() instead * const resolver = RequestResolver.make((entries) => @@ -69,6 +69,9 @@ const TypeId = "~effect/RequestResolver" * } * }) * ) + * + * const program = Effect.request(GetUserRequest({ id: 1 }), resolver) + * await Effect.runPromise(program) // => "User 1" * ``` * * @category models @@ -202,7 +205,7 @@ const defaultKey = (_request: unknown): unknown => defaultKeyObject * * **Example** (Creating a request resolver) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Exit, Request, RequestResolver } from "effect" * * // Define a request type @@ -224,6 +227,7 @@ const defaultKey = (_request: unknown): unknown => defaultKeyObject * * // Use the resolver to handle requests * const getUserEffect = Effect.request(GetUserRequest({ id: 123 }), UserResolver) + * await Effect.runPromise(getUserEffect) // => "User 123" * ``` * * @category constructors @@ -248,7 +252,7 @@ export const make = ( * * **Example** (Grouping requests by key) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Exit, Request, RequestResolver } from "effect" * * interface GetUserByRole extends Request.Request { @@ -258,12 +262,14 @@ export const make = ( * } * const GetUserByRole = Request.tagged("GetUserByRole") * + * const batches: Array<[role: string, size: number]> = [] + * * // Group requests by role for efficient batch processing * const UserByRoleResolver = RequestResolver.makeGrouped({ * key: ({ request }) => request.role, * resolver: (entries, role) => * Effect.sync(() => { - * console.log(`Processing ${entries.length} requests for role: ${role}`) + * batches.push([role, entries.length]) * for (const entry of entries) { * entry.completeUnsafe( * Exit.succeed(`User ${entry.request.id} with role ${role}`) @@ -271,6 +277,15 @@ export const make = ( * } * }) * }) + * + * const program = Effect.all([ + * Effect.request(GetUserByRole({ role: "admin", id: 1 }), UserByRoleResolver), + * Effect.request(GetUserByRole({ role: "admin", id: 2 }), UserByRoleResolver) + * ] as const, { concurrency: "unbounded" }) + * const result = await Effect.runPromise(program) + * + * batches // => [["admin", 2]] + * result // => ["User 1 with role admin", "User 2 with role admin"] * ``` * * @category constructors @@ -305,7 +320,7 @@ const hashGroupKey = (get: (entry: Request.Entry) => K) => { * * **Example** (Creating a resolver from a pure function) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Request, RequestResolver } from "effect" * * interface GetSquareRequest extends Request.Request { @@ -324,7 +339,7 @@ const hashGroupKey = (get: (entry: Request.Entry) => K) => { * GetSquareRequest({ value: 5 }), * SquareResolver * ) - * // Will resolve to 25 + * await Effect.runPromise(getSquareEffect) // => 25 * ``` * * @category constructors @@ -350,7 +365,7 @@ export const fromFunction = ( * * **Example** (Batching pure request handling) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Request, RequestResolver } from "effect" * * interface GetDoubleRequest extends Request.Request { @@ -368,7 +383,8 @@ export const fromFunction = ( * const effects = [1, 2, 3].map((value) => * Effect.request(GetDoubleRequest({ value }), DoubleResolver) * ) - * const batchedEffect = Effect.all(effects) // [2, 4, 6] + * const batchedEffect = Effect.all(effects) + * await Effect.runPromise(batchedEffect) // => [2, 4, 6] * ``` * * @category constructors @@ -393,7 +409,7 @@ export const fromFunctionBatched = ( * * **Example** (Creating a resolver from an effectful function) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Request, RequestResolver } from "effect" * * interface GetUserFromAPIRequest extends Request.Request { @@ -406,13 +422,7 @@ export const fromFunctionBatched = ( * * // Create a resolver that uses effects (like HTTP calls) * const UserAPIResolver = RequestResolver.fromEffect( - * (entry) => - * Effect.gen(function*() { - * // Simulate an API call - * yield* Effect.sleep("100 millis") - * // Just return the result without error handling for simplicity - * return `User ${entry.request.id} from API` - * }) + * (entry) => Effect.succeed(`User ${entry.request.id} from API`) * ) * * // Usage @@ -420,6 +430,7 @@ export const fromFunctionBatched = ( * GetUserFromAPIRequest({ id: 123 }), * UserAPIResolver * ) + * await Effect.runPromise(getUserEffect) // => "User 123 from API" * ``` * * @category constructors @@ -456,9 +467,8 @@ export const fromEffect = ( * * **Example** (Handling tagged request batches) * - * ```ts - * import { Effect, RequestResolver } from "effect" - * import type { Request } from "effect" + * ```ts import.meta.vitest + * import { Effect, Request, RequestResolver } from "effect" * * interface GetUser extends Request.Request { * readonly _tag: "GetUser" @@ -471,6 +481,8 @@ export const fromEffect = ( * } * * type MyRequest = GetUser | GetPost + * const GetUser = Request.tagged("GetUser") + * const GetPost = Request.tagged("GetPost") * * // Create a resolver that handles different request types * const MyResolver = RequestResolver.fromEffectTagged()({ @@ -479,6 +491,12 @@ export const fromEffect = ( * GetPost: (requests) => * Effect.succeed(requests.map((req) => `Post ${req.request.id}`)) * }) + * + * const program = Effect.all([ + * Effect.request(GetUser({ id: 1 }), MyResolver), + * Effect.request(GetPost({ id: 2 }), MyResolver) + * ] as const) + * await Effect.runPromise(program) // => ["User 1", "Post 2"] * ``` * * @category constructors @@ -534,7 +552,7 @@ export const fromEffectTagged = { @@ -550,17 +568,21 @@ export const fromEffectTagged = { + * delayRan = true * }) * ) + * + * await Effect.runPromise(resolverWithCustomDelay.delay) + * Array.of(delayRan, RequestResolver.isRequestResolver(resolverWithCustomDelay)) // => [true, true] * ``` * - * @category delay + * @category delays & timeouts * @since 4.0.0 */ export const setDelayEffect: { @@ -580,7 +602,7 @@ export const setDelayEffect: { * * **Example** (Setting a batch delay) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Exit, Request, RequestResolver } from "effect" * * interface GetDataRequest extends Request.Request { @@ -599,11 +621,11 @@ export const setDelayEffect: { * // Add a 100ms delay to batch requests together * const delayedResolver = RequestResolver.setDelay(resolver, "100 millis") * - * // Can also use number for milliseconds - * const delayedResolver2 = RequestResolver.setDelay(resolver, 100) + * const program = Effect.request(GetDataRequest(), delayedResolver) + * await Effect.runPromise(program) // => "data" * ``` * - * @category delay + * @category delays & timeouts * @since 4.0.0 */ export const setDelay: { @@ -623,7 +645,7 @@ export const setDelay: { * * **Example** (Running effects around request resolution) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Exit, Request, RequestResolver } from "effect" * * interface GetDataRequest extends Request.Request { @@ -631,6 +653,8 @@ export const setDelay: { * } * const GetDataRequest = Request.tagged("GetDataRequest") * + * const events: Array = [] + * * const resolver = RequestResolver.make((entries) => * Effect.sync(() => { * for (const entry of entries) { @@ -644,16 +668,20 @@ export const setDelay: { * resolver, * (entries) => * Effect.gen(function*() { - * yield* Effect.log(`Starting batch of ${entries.length} requests`) + * events.push(`Starting batch of ${entries.length} requests`) * return entries.length * }), * (entries, initialSize) => - * Effect.gen(function*() { - * yield* Effect.log( - * `Batch completed with ${entries.length} requests (started with ${initialSize})` - * ) + * Effect.sync(() => { + * events.push(`Batch completed with ${entries.length} requests (started with ${initialSize})`) * }) * ) + * + * const program = Effect.request(GetDataRequest(), resolverWithAround) + * const result = await Effect.runPromise(program) + * + * events // => ["Starting batch of 1 requests", "Batch completed with 1 requests (started with 1)"] + * result // => "data" * ``` * * @category combinators @@ -716,7 +744,7 @@ export const never: RequestResolver = make(() => Effect.never) * * **Example** (Limiting parallel request batches) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Exit, Request, RequestResolver } from "effect" * * interface GetDataRequest extends Request.Request { @@ -725,9 +753,11 @@ export const never: RequestResolver = make(() => Effect.never) * } * const GetDataRequest = Request.tagged("GetDataRequest") * + * const batchSizes: Array = [] + * * const resolver = RequestResolver.make((entries) => * Effect.sync(() => { - * console.log(`Processing batch of ${entries.length} requests`) + * batchSizes.push(entries.length) * for (const entry of entries) { * entry.completeUnsafe(Exit.succeed(`data-${entry.request.id}`)) * } @@ -742,6 +772,13 @@ export const never: RequestResolver = make(() => Effect.never) * { length: 12 }, * (_, i) => Effect.request(GetDataRequest({ id: i }), limitedResolver) * ) + * + * const result = await Effect.runPromise(Effect.all(requests, { concurrency: "unbounded" })) + * batchSizes // => [5, 5, 2] + * + * result.length // => 12 + * + * Array.of(result[0], result[11]) // => ["data-0", "data-11"] * ``` * * @category combinators @@ -762,7 +799,7 @@ export const batchN: { * * **Example** (Grouping resolver requests) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Exit, Request, RequestResolver } from "effect" * * interface GetUserRequest extends Request.Request { @@ -772,9 +809,11 @@ export const batchN: { * } * const GetUserRequest = Request.tagged("GetUserRequest") * + * const batchSizes: Array = [] + * * const resolver = RequestResolver.make((entries) => * Effect.sync(() => { - * console.log(`Processing ${entries.length} users`) + * batchSizes.push(entries.length) * for (const entry of entries) { * entry.completeUnsafe(Exit.succeed(`User ${entry.request.userId}`)) * } @@ -802,6 +841,13 @@ export const batchN: { * groupedResolver * ) * ] + * + * const result = await Effect.runPromise(Effect.all(requests, { concurrency: "unbounded" })) + * batchSizes.sort() + * + * batchSizes // => [1, 2] + * + * result // => ["User 1", "User 2", "User 3"] * ``` * * @category combinators @@ -830,7 +876,7 @@ export const grouped: { * * **Example** (Racing request resolvers) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Exit, Request, RequestResolver } from "effect" * * interface GetDataRequest extends Request.Request { @@ -861,6 +907,8 @@ export const grouped: { * * // Race resolvers - will use whichever completes first * const racingResolver = RequestResolver.race(fastResolver, slowResolver) + * const program = Effect.request(GetDataRequest({ id: 1 }), racingResolver) + * await Effect.runPromise(program) // => "fast-1" * ``` * * @category combinators @@ -888,7 +936,7 @@ export const race: { * * **Example** (Adding a tracing span) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Exit, Request, RequestResolver } from "effect" * * interface GetDataRequest extends Request.Request { @@ -919,6 +967,7 @@ export const race: { * * // Spans will automatically include batch size and request links * const effect = Effect.request(GetDataRequest({ id: 123 }), tracedResolver) + * await Effect.runPromise(effect) // => "data-123" * ``` * * @category combinators @@ -1159,7 +1208,7 @@ export const withCache: { * @see {@link withCache} for in-memory resolver caching that does not require persistable request values or a persistence store * @see {@link asCache} for exposing resolver results through a `Cache` instead of returning another resolver * - * @category Persistence + * @category caching * @since 4.0.0 */ export const persisted: { diff --git a/repos/effect/packages/effect/src/Result.ts b/repos/effect/packages/effect/src/Result.ts index c1c1052473..eb7a4e014c 100644 --- a/repos/effect/packages/effect/src/Result.ts +++ b/repos/effect/packages/effect/src/Result.ts @@ -17,6 +17,7 @@ import type { TypeLambda } from "./HKT.ts" import type { Inspectable } from "./Inspectable.ts" import * as doNotation from "./internal/doNotation.ts" import * as option_ from "./internal/option.ts" +import * as InternalRecord from "./internal/record.ts" import * as result from "./internal/result.ts" import type { Option } from "./Option.ts" import type { Pipeable } from "./Pipeable.ts" @@ -46,18 +47,13 @@ const TypeId = "~effect/data/Result" * * **Example** (Creating and matching a Result) * - * ```ts + * ```ts import.meta.vitest * import { Result } from "effect" * - * const success = Result.succeed(42) - * const failure = Result.fail("something went wrong") - * - * const message = Result.match(success, { + * Result.match(Result.succeed(42), { * onSuccess: (value) => `Success: ${value}`, * onFailure: (error) => `Error: ${error}` - * }) - * console.log(message) - * // Output: "Success: 42" + * }) // => "Success: 42" * ``` * * @see {@link succeed} / {@link fail} to create values @@ -80,14 +76,13 @@ export type Result = Success | Failure * * **Example** (Accessing the failure value) * - * ```ts + * ```ts import.meta.vitest * import { Result } from "effect" * * const failure = Result.fail("Network error") * * if (Result.isFailure(failure)) { - * console.log(failure.failure) - * // Output: "Network error" + * failure.failure // => "Network error" * } * ``` * @@ -143,14 +138,13 @@ export interface ResultIterator> { * * **Example** (Accessing the success value) * - * ```ts + * ```ts import.meta.vitest * import { Result } from "effect" * * const success = Result.succeed(42) * * if (Result.isSuccess(success)) { - * console.log(success.success) - * // Output: 42 + * success.success // => 42 * } * ``` * @@ -212,7 +206,7 @@ export interface ResultUnifyIgnore {} * (e.g., `map`, `flatMap` abstractions). You typically do not need to * reference this directly. * - * @category type lambdas + * @category utility types * @since 4.0.0 */ export interface ResultTypeLambda extends TypeLambda { @@ -225,8 +219,8 @@ export interface ResultTypeLambda extends TypeLambda { * * **Example** (Extracting inner types) * - * ```ts - * import type { Result } from "effect" + * ```ts import.meta.vitest + * import { Result } from "effect" * * type R = Result.Result * @@ -235,6 +229,9 @@ export interface ResultTypeLambda extends TypeLambda { * * // string * type E = Result.Result.Failure + * + * const success: A = 42 + * const failure: E = "error" * ``` * * @since 4.0.0 @@ -243,14 +240,14 @@ export declare namespace Result { /** * Extracts the failure type `E` from `Result`. * - * @category Type Level + * @category utility types * @since 4.0.0 */ export type Failure> = [T] extends [Result] ? _E : never /** * Extracts the success type `A` from `Result`. * - * @category Type Level + * @category utility types * @since 4.0.0 */ export type Success> = [T] extends [Result] ? _A : never @@ -266,13 +263,10 @@ export declare namespace Result { * * **Example** (Wrapping a value) * - * ```ts + * ```ts import.meta.vitest * import { Result } from "effect" * - * const result = Result.succeed(42) - * - * console.log(Result.isSuccess(result)) - * // Output: true + * Result.succeed(42) // => Result.succeed(42) * ``` * * @see {@link fail} to create a Failure @@ -296,13 +290,10 @@ export const succeed: (right: A) => Result = result.succeed * * **Example** (Creating a failure) * - * ```ts + * ```ts import.meta.vitest * import { Result } from "effect" * - * const result = Result.fail("Something went wrong") - * - * console.log(Result.isFailure(result)) - * // Output: true + * Result.fail("Something went wrong") // => Result.fail("Something went wrong") * ``` * * @see {@link succeed} to create a Success @@ -330,13 +321,10 @@ export { * * **Example** (Referencing void results) * - * ```ts + * ```ts import.meta.vitest * import { Result } from "effect" * - * const result: Result.Result = Result.void - * - * console.log(Result.isSuccess(result)) - * // Output: true + * const result: Result.Result = Result.void // => Result.succeed(undefined) * ``` * * @see {@link succeed} to create a Success with a specific value @@ -363,13 +351,10 @@ export { * * **Example** (Failing without a payload) * - * ```ts + * ```ts import.meta.vitest * import { Result } from "effect" * - * const result = Result.failVoid - * - * console.log(Result.isFailure(result)) - * // Output: true + * Result.failVoid // => Result.fail(undefined) * ``` * * @see {@link fail} to create a Failure with a specific value @@ -396,14 +381,12 @@ export const failVoid: Result = fail(void 0) * * **Example** (Handling nullable values) * - * ```ts + * ```ts import.meta.vitest * import { Result } from "effect" * - * console.log(Result.fromNullishOr(1, () => "fallback")) - * // Output: { _tag: "Success", success: 1, ... } + * Result.fromNullishOr(1, () => "fallback") // => Result.succeed(1) * - * console.log(Result.fromNullishOr(null, () => "fallback")) - * // Output: { _tag: "Failure", failure: "fallback", ... } + * Result.fromNullishOr(null, () => "fallback") // => Result.fail("fallback") * ``` * * @see {@link fromOption} to convert from an Option @@ -437,16 +420,12 @@ export const fromNullishOr: { * * **Example** (Converting an Option to a Result) * - * ```ts + * ```ts import.meta.vitest * import { Option, Result } from "effect" * - * const some = Result.fromOption(Option.some(1), () => "missing") - * console.log(some) - * // Output: { _tag: "Success", success: 1, ... } + * Result.fromOption(Option.some(1), () => "missing") // => Result.succeed(1) * - * const none = Result.fromOption(Option.none(), () => "missing") - * console.log(none) - * // Output: { _tag: "Failure", failure: "missing", ... } + * Result.fromOption(Option.none(), () => "missing") // => Result.fail("missing") * ``` * * @see {@link getSuccess} to extract the success value as an Option @@ -503,19 +482,16 @@ export { * * **Example** (Catching JSON parse errors) * - * ```ts + * ```ts import.meta.vitest * import { Result } from "effect" * - * const ok = Result.try(() => JSON.parse('{"name": "Alice"}')) - * console.log(ok) - * // Output: { _tag: "Success", success: { name: "Alice" }, ... } + * Result.try(() => JSON.parse('{"name": "Alice"}')) // => Result.succeed({ name: "Alice" }) * * const err = Result.try({ * try: () => JSON.parse("not json"), * catch: (e) => `Parse failed: ${e}` * }) - * console.log(Result.isFailure(err)) - * // Output: true + * Result.isFailure(err) // => true * ``` * * @see {@link succeed} / {@link fail} for direct construction @@ -541,14 +517,12 @@ export { * * **Example** (Checking if a value is a Result) * - * ```ts + * ```ts import.meta.vitest * import { Result } from "effect" * - * console.log(Result.isResult(Result.succeed(1))) - * // Output: true + * Result.isResult(Result.succeed(1)) // => true * - * console.log(Result.isResult({ value: 1 })) - * // Output: false + * Result.isResult({ value: 1 }) // => false * ``` * * @see {@link isSuccess} / {@link isFailure} to narrow to a specific variant @@ -572,14 +546,13 @@ export const isResult: (input: unknown) => input is Result = r * * **Example** (Narrowing to failure) * - * ```ts + * ```ts import.meta.vitest * import { Result } from "effect" * * const result = Result.fail("oops") * * if (Result.isFailure(result)) { - * console.log(result.failure) - * // Output: "oops" + * result.failure // => "oops" * } * ``` * @@ -605,14 +578,13 @@ export const isFailure: (self: Result) => self is Failure = re * * **Example** (Narrowing to success) * - * ```ts + * ```ts import.meta.vitest * import { Result } from "effect" * * const result = Result.succeed(42) * * if (Result.isSuccess(result)) { - * console.log(result.success) - * // Output: 42 + * result.success // => 42 * } * ``` * @@ -639,14 +611,12 @@ export const isSuccess: (self: Result) => self is Success = re * * **Example** (Extracting the success as an Option) * - * ```ts + * ```ts import.meta.vitest * import { Option, Result } from "effect" * - * console.log(Result.getSuccess(Result.succeed("ok"))) - * // Output: { _tag: "Some", value: "ok" } + * Result.getSuccess(Result.succeed("ok")) // => Option.some("ok") * - * console.log(Result.getSuccess(Result.fail("err"))) - * // Output: { _tag: "None" } + * Result.getSuccess(Result.fail("err")) // => Option.none() * ``` * * @see {@link getFailure} to extract the error instead @@ -672,14 +642,12 @@ export const getSuccess: (self: Result) => Option = result.getSuc * * **Example** (Extracting the failure as an Option) * - * ```ts + * ```ts import.meta.vitest * import { Option, Result } from "effect" * - * console.log(Result.getFailure(Result.succeed("ok"))) - * // Output: { _tag: "None" } + * Result.getFailure(Result.succeed("ok")) // => Option.none() * - * console.log(Result.getFailure(Result.fail("err"))) - * // Output: { _tag: "Some", value: "err" } + * Result.getFailure(Result.fail("err")) // => Option.some("err") * ``` * * @see {@link getSuccess} to extract the success instead @@ -701,7 +669,7 @@ export const getFailure: (self: Result) => Option = result.getFai * * **Example** (Comparing Results for equality) * - * ```ts + * ```ts import.meta.vitest * import { Equivalence, Result } from "effect" * * const eq = Result.makeEquivalence( @@ -709,11 +677,9 @@ export const getFailure: (self: Result) => Option = result.getFai * Equivalence.strictEqual() * ) * - * console.log(eq(Result.succeed(1), Result.succeed(1))) - * // Output: true + * eq(Result.succeed(1), Result.succeed(1)) // => true * - * console.log(eq(Result.succeed(1), Result.fail("x"))) - * // Output: false + * eq(Result.succeed(1), Result.fail("x")) // => false * ``` * * @category instances @@ -744,18 +710,16 @@ export const makeEquivalence = ( * * **Example** (Mapping both channels) * - * ```ts + * ```ts import.meta.vitest * import { pipe, Result } from "effect" * - * const result = pipe( + * pipe( * Result.succeed(1), * Result.mapBoth({ * onSuccess: (n) => n + 1, * onFailure: (e) => `Error: ${e}` * }) - * ) - * console.log(result) - * // Output: { _tag: "Success", success: 2, ... } + * ) // => Result.succeed(2) * ``` * * @see {@link map} to transform only the success value @@ -796,15 +760,13 @@ export const mapBoth: { * * **Example** (Adding context to an error) * - * ```ts + * ```ts import.meta.vitest * import { pipe, Result } from "effect" * - * const result = pipe( + * pipe( * Result.fail("not found"), * Result.mapError((e) => `Error: ${e}`) - * ) - * console.log(result) - * // Output: { _tag: "Failure", failure: "Error: not found", ... } + * ) // => Result.fail("Error: not found") * ``` * * @see {@link map} to transform only the success value @@ -819,7 +781,7 @@ export const mapError: { } = dual( 2, (self: Result, f: (err: E) => E2): Result => - isFailure(self) ? fail(f(self.failure)) : succeed(self.success) + isFailure(self) ? fail(f(self.failure)) : self as unknown as Result ) /** @@ -838,15 +800,13 @@ export const mapError: { * * **Example** (Doubling the success value) * - * ```ts + * ```ts import.meta.vitest * import { pipe, Result } from "effect" * - * const result = pipe( + * pipe( * Result.succeed(3), * Result.map((n) => n * 2) - * ) - * console.log(result) - * // Output: { _tag: "Success", success: 6, ... } + * ) // => Result.succeed(6) * ``` * * @see {@link mapError} to transform only the error value @@ -862,7 +822,7 @@ export const map: { } = dual( 2, (self: Result, f: (ok: A) => A2): Result => - isSuccess(self) ? succeed(f(self.success)) : fail(self.failure) + isSuccess(self) ? succeed(f(self.success)) : self as unknown as Result ) /** @@ -881,7 +841,7 @@ export const map: { * * **Example** (Folding to a string) * - * ```ts + * ```ts import.meta.vitest * import { pipe, Result } from "effect" * * const format = Result.match({ @@ -889,11 +849,9 @@ export const map: { * onFailure: (e: string) => `Err: ${e}` * }) * - * console.log(format(Result.succeed(42))) - * // Output: "Got 42" + * format(Result.succeed(42)) // => "Got 42" * - * console.log(format(Result.fail("timeout"))) - * // Output: "Err: timeout" + * format(Result.fail("timeout")) // => "Err: timeout" * ``` * * @see {@link merge} to extract `A | E` without mapping @@ -936,18 +894,16 @@ export const match: { * * **Example** (Validating a number) * - * ```ts + * ```ts import.meta.vitest * import { pipe, Result } from "effect" * - * const ensurePositive = pipe( + * pipe( * 5, * Result.liftPredicate( * (n: number) => n > 0, * (n) => `${n} is not positive` * ) - * ) - * console.log(ensurePositive) - * // Output: { _tag: "Success", success: 5, ... } + * ) // => Result.succeed(5) * ``` * * @see {@link filterOrFail} to validate a value that is already in a `Result` @@ -997,18 +953,16 @@ export const liftPredicate: { * * **Example** (Filtering a success value) * - * ```ts + * ```ts import.meta.vitest * import { pipe, Result } from "effect" * - * const result = pipe( + * pipe( * Result.succeed(0), * Result.filterOrFail( * (n) => n > 0, * (n) => `${n} is not positive` * ) - * ) - * console.log(result) - * // Output: { _tag: "Failure", failure: "0 is not positive", ... } + * ) // => Result.fail("0 is not positive") * ``` * * @see {@link liftPredicate} to create a `Result` from a raw value with a predicate @@ -1050,14 +1004,12 @@ export const filterOrFail: { * * **Example** (Extracting the inner value) * - * ```ts + * ```ts import.meta.vitest * import { Result } from "effect" * - * console.log(Result.merge(Result.succeed(42))) - * // Output: 42 + * Result.merge(Result.succeed(42)) // => 42 * - * console.log(Result.merge(Result.fail("error"))) - * // Output: "error" + * Result.merge(Result.fail("error")) // => "error" * ``` * * @see {@link match} to map each branch to a common type @@ -1084,14 +1036,12 @@ export const merge: (self: Result) => E | A = match({ onFailure: ide * * **Example** (Providing a fallback) * - * ```ts + * ```ts import.meta.vitest * import { Result } from "effect" * - * console.log(Result.getOrElse(Result.succeed(1), () => 0)) - * // Output: 1 + * Result.getOrElse(Result.succeed(1), () => 0) // => 1 * - * console.log(Result.getOrElse(Result.fail("err"), () => 0)) - * // Output: 0 + * Result.getOrElse(Result.fail("err"), () => 0) // => 0 * ``` * * @see {@link getOrNull} / {@link getOrUndefined} for simpler fallbacks @@ -1126,14 +1076,12 @@ export const getOrElse: { * * **Example** (Unwrapping to nullable) * - * ```ts + * ```ts import.meta.vitest * import { Result } from "effect" * - * console.log(Result.getOrNull(Result.succeed(1))) - * // Output: 1 + * Result.getOrNull(Result.succeed(1)) // => 1 * - * console.log(Result.getOrNull(Result.fail("err"))) - * // Output: null + * Result.getOrNull(Result.fail("err")) // => null * ``` * * @see {@link getOrUndefined} to return `undefined` instead @@ -1159,14 +1107,12 @@ export const getOrNull: (self: Result) => A | null = getOrElse(const * * **Example** (Unwrapping to optional) * - * ```ts + * ```ts import.meta.vitest * import { Result } from "effect" * - * console.log(Result.getOrUndefined(Result.succeed(1))) - * // Output: 1 + * Result.getOrUndefined(Result.succeed(1)) // => 1 * - * console.log(Result.getOrUndefined(Result.fail("err"))) - * // Output: undefined + * Result.getOrUndefined(Result.fail("err")) // => undefined * ``` * * @see {@link getOrNull} to return `null` instead @@ -1192,19 +1138,19 @@ export const getOrUndefined: (self: Result) => A | undefined = getOr * * **Example** (Throwing a custom error) * - * ```ts + * ```ts import.meta.vitest * import { Result } from "effect" * - * console.log( - * Result.getOrThrowWith(Result.succeed(1), () => new Error("fail")) - * ) - * // Output: 1 + * Result.getOrThrowWith(Result.succeed(1), () => new Error("fail")) // => 1 * - * // This would throw: new Error("Unexpected: oops") - * // Result.getOrThrowWith( - * // Result.fail("oops"), - * // (err) => new Error(`Unexpected: ${err}`) - * // ) + * const failure = Result.try({ + * try: () => Result.getOrThrowWith( + * Result.fail("oops"), + * (error) => new Error(`Unexpected: ${error}`) + * ), + * catch: (error) => (error as Error).message + * }) + * Result.merge(failure) // => "Unexpected: oops" * ``` * * @see {@link getOrThrow} to throw the raw failure value @@ -1238,14 +1184,13 @@ export const getOrThrowWith: { * * **Example** (Unwrapping or throwing) * - * ```ts + * ```ts import.meta.vitest * import { Result } from "effect" * - * console.log(Result.getOrThrow(Result.succeed(1))) - * // Output: 1 + * Result.getOrThrow(Result.succeed(1)) // => 1 * - * // This would throw the string "error": - * // Result.getOrThrow(Result.fail("error")) + * const failure = Result.try(() => Result.getOrThrow(Result.fail("error"))) + * Result.merge(failure) // => "error" * ``` * * @see {@link getOrThrowWith} for custom error mapping @@ -1272,15 +1217,13 @@ export const getOrThrow: (self: Result) => A = getOrThrowWith(identi * * **Example** (Recovering from a failure) * - * ```ts + * ```ts import.meta.vitest * import { pipe, Result } from "effect" * - * const result = pipe( + * pipe( * Result.fail("primary failed"), * Result.orElse(() => Result.succeed(99)) - * ) - * console.log(result) - * // Output: { _tag: "Success", success: 99, ... } + * ) // => Result.succeed(99) * ``` * * @see {@link getOrElse} to unwrap with a fallback value (not a Result) @@ -1315,17 +1258,15 @@ export const orElse: { * * **Example** (Validating sequentially) * - * ```ts + * ```ts import.meta.vitest * import { pipe, Result } from "effect" * - * const result = pipe( + * pipe( * Result.succeed(5), * Result.flatMap((n) => * n > 0 ? Result.succeed(n * 2) : Result.fail("not positive") * ) - * ) - * console.log(result) - * // Output: { _tag: "Success", success: 10, ... } + * ) // => Result.succeed(10) * ``` * * @see {@link andThen} for a more flexible variant that also accepts plain values @@ -1363,25 +1304,23 @@ export const flatMap: { * * **Example** (Chaining Result values with different argument types) * - * ```ts + * ```ts import.meta.vitest * import { pipe, Result } from "effect" * * // With a function returning a Result * const a = pipe( * Result.succeed(1), * Result.andThen((n) => Result.succeed(n + 1)) - * ) + * ) // => Result.succeed(2) * * // With a plain mapping function * const b = pipe( * Result.succeed(1), * Result.andThen((n) => n + 1) - * ) + * ) // => Result.succeed(2) * * // With a constant value - * const c = pipe(Result.succeed(1), Result.andThen("done")) - * - * console.log(a, b, c) + * const c = pipe(Result.succeed(1), Result.andThen("done")) // => Result.succeed("done") * ``` * * @see {@link flatMap} for the stricter variant (function returning Result only) @@ -1430,18 +1369,14 @@ export const andThen: { * * **Example** (Collecting a tuple and a struct) * - * ```ts + * ```ts import.meta.vitest * import { Result } from "effect" * * // Tuple - * const tuple = Result.all([Result.succeed(1), Result.succeed("two")]) - * console.log(tuple) - * // Output: { _tag: "Success", success: [1, "two"], ... } + * Result.all([Result.succeed(1), Result.succeed("two")]) // => Result.succeed([1, "two"]) * * // Struct - * const struct = Result.all({ x: Result.succeed(1), y: Result.fail("err") }) - * console.log(struct) - * // Output: { _tag: "Failure", failure: "err", ... } + * Result.all({ x: Result.succeed(1), y: Result.fail("err") }) // => Result.fail("err") * ``` * * @see {@link flatMap} for chaining two Results sequentially @@ -1481,7 +1416,7 @@ export const all: > | Record> | Record Result.fail(42) * - * console.log(Result.flip(Result.fail("error"))) - * // Output: { _tag: "Success", success: "error", ... } + * Result.flip(Result.fail("error")) // => Result.succeed("error") * ``` * * @see {@link mapError} to transform the error without swapping @@ -1538,17 +1471,14 @@ export const flip = (self: Result): Result => * * **Example** (Composing multiple Results) * - * ```ts + * ```ts import.meta.vitest * import { Result } from "effect" * - * const result = Result.gen(function*() { + * Result.gen(function*() { * const a = yield* Result.succeed(1) * const b = yield* Result.succeed(2) * return a + b - * }) - * - * console.log(result) - * // Output: { _tag: "Success", success: 3, ... } + * }) // => Result.succeed(3) * ``` * * @see {@link flatMap} for point-free sequential composition @@ -1592,17 +1522,15 @@ export const gen: Gen.Gen = (...args) => { * * **Example** (Building an object step by step) * - * ```ts + * ```ts import.meta.vitest * import { pipe, Result } from "effect" * - * const result = pipe( + * pipe( * Result.Do, * Result.bind("x", () => Result.succeed(2)), * Result.bind("y", () => Result.succeed(3)), * Result.let("sum", ({ x, y }) => x + y) - * ) - * console.log(result) - * // Output: { _tag: "Success", success: { x: 2, y: 3, sum: 5 }, ... } + * ) // => Result.succeed({ x: 2, y: 3, sum: 5 }) * ``` * * @see {@link bind} to add Result-producing fields @@ -1610,7 +1538,7 @@ export const gen: Gen.Gen = (...args) => { * @see {@link gen} for an alternative generator-based syntax * @see {@link bindTo} for starting a do-notation chain from an existing Result * - * @category do notation + * @category constructors * @since 2.0.0 */ export const Do: Result<{}> = succeed({}) @@ -1633,23 +1561,21 @@ export const Do: Result<{}> = succeed({}) * * **Example** (Binding Result values) * - * ```ts + * ```ts import.meta.vitest * import { pipe, Result } from "effect" * - * const result = pipe( + * pipe( * Result.Do, * Result.bind("x", () => Result.succeed(2)), * Result.bind("y", ({ x }) => Result.succeed(x + 3)) - * ) - * console.log(result) - * // Output: { _tag: "Success", success: { x: 2, y: 5 }, ... } + * ) // => Result.succeed({ x: 2, y: 5 }) * ``` * * @see {@link Do} to start the do-notation chain * @see {@link let_ let} for pure computed fields * @see {@link bindTo} to wrap an initial Result into a named field * - * @category do notation + * @category sequencing * @since 2.0.0 */ export const bind: { @@ -1680,21 +1606,19 @@ export const bind: { * * **Example** (Wrapping a value into a named field) * - * ```ts + * ```ts import.meta.vitest * import { pipe, Result } from "effect" * - * const result = pipe( + * pipe( * Result.succeed(42), * Result.bindTo("answer") - * ) - * console.log(result) - * // Output: { _tag: "Success", success: { answer: 42 }, ... } + * ) // => Result.succeed({ answer: 42 }) * ``` * * @see {@link Do} to start from an empty object * @see {@link bind} to add more fields * - * @category do notation + * @category mapping * @since 2.0.0 */ export const bindTo: { @@ -1731,23 +1655,21 @@ export { * * **Example** (Adding a computed field) * - * ```ts + * ```ts import.meta.vitest * import { pipe, Result } from "effect" * - * const result = pipe( + * pipe( * Result.Do, * Result.bind("x", () => Result.succeed(2)), * Result.bind("y", () => Result.succeed(3)), * Result.let("sum", ({ x, y }) => x + y) - * ) - * console.log(result) - * // Output: { _tag: "Success", success: { x: 2, y: 3, sum: 5 }, ... } + * ) // => Result.succeed({ x: 2, y: 3, sum: 5 }) * ``` * * @see {@link Do} to start the do-notation chain * @see {@link bind} for Result-producing fields * - * @category do notation + * @category mapping * @since 2.0.0 */ let_ as let @@ -1769,21 +1691,17 @@ export { * * **Example** (Transposing an Option of a Result) * - * ```ts + * ```ts import.meta.vitest * import { Option, Result } from "effect" * - * const some = Option.some(Result.succeed(42)) - * console.log(Result.transposeOption(some)) - * // Output: { _tag: "Success", success: { _tag: "Some", value: 42 }, ... } + * Result.transposeOption(Option.some(Result.succeed(42))) // => Result.succeed(Option.some(42)) * - * const none = Option.none>() - * console.log(Result.transposeOption(none)) - * // Output: { _tag: "Success", success: { _tag: "None" }, ... } + * Result.transposeOption(Option.none>()) // => Result.succeed(Option.none()) * ``` * * @see {@link transposeMapOption} to map and transpose in one step * - * @category Transposing + * @category transposing * @since 3.14.0 */ export const transposeOption = ( @@ -1809,7 +1727,7 @@ export const transposeOption = ( * * **Example** (Mapping and transposing in one step) * - * ```ts + * ```ts import.meta.vitest * import { Option, Result } from "effect" * * const parse = (s: string) => @@ -1817,16 +1735,14 @@ export const transposeOption = ( * ? Result.fail("not a number" as const) * : Result.succeed(Number(s)) * - * console.log(Result.transposeMapOption(Option.some("42"), parse)) - * // Output: { _tag: "Success", success: { _tag: "Some", value: 42 }, ... } + * Result.transposeMapOption(Option.some("42"), parse) // => Result.succeed(Option.some(42)) * - * console.log(Result.transposeMapOption(Option.none(), parse)) - * // Output: { _tag: "Success", success: { _tag: "None" }, ... } + * Result.transposeMapOption(Option.none(), parse) // => Result.succeed(Option.none()) * ``` * * @see {@link transposeOption} when the Option already contains a Result * - * @category Transposing + * @category transposing * @since 3.15.0 */ export const transposeMapOption = dual< @@ -1854,11 +1770,10 @@ export const transposeMapOption = dual< * * **Example** (Succeeding with None) * - * ```ts - * import { Result } from "effect" + * ```ts import.meta.vitest + * import { Option, Result } from "effect" * - * console.log(Result.isSuccess(Result.succeedNone)) - * // Output: true + * Result.succeedNone // => Result.succeed(Option.none()) * ``` * * @see {@link succeedSome} for the `Some` counterpart @@ -1880,12 +1795,10 @@ export const succeedNone = succeed(option_.none) * * **Example** (Wrapping a value in Some inside a Result) * - * ```ts - * import { Result } from "effect" + * ```ts import.meta.vitest + * import { Option, Result } from "effect" * - * const result = Result.succeedSome(42) - * console.log(result) - * // Output: { _tag: "Success", success: { _tag: "Some", value: 42 }, ... } + * Result.succeedSome(42) // => Result.succeed(Option.some(42)) * ``` * * @see {@link succeedNone} for the `None` counterpart @@ -1907,17 +1820,17 @@ export const succeedSome = (a: A): Result, E> => succeed * * **Example** (Logging a success value) * - * ```ts + * ```ts import.meta.vitest * import { pipe, Result } from "effect" * + * const values: Array = [] * const result = pipe( * Result.succeed(42), - * Result.tap((n) => console.log("Got:", n)) + * Result.tap((n) => values.push(n)) * ) - * // Output: "Got: 42" * - * console.log(Result.isSuccess(result)) - * // Output: true + * values // => [42] + * result // => Result.succeed(42) * ``` * * @see {@link map} to transform the success value diff --git a/repos/effect/packages/effect/src/Runtime.ts b/repos/effect/packages/effect/src/Runtime.ts index 001c81a722..ab7ad6809e 100644 --- a/repos/effect/packages/effect/src/Runtime.ts +++ b/repos/effect/packages/effect/src/Runtime.ts @@ -31,31 +31,27 @@ import type * as Fiber from "./Fiber.ts" * * **Example** (Customizing teardown behavior) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Exit, Runtime } from "effect" * - * // Custom teardown that logs completion status + * // Custom teardown that maps completion status to an exit code * const customTeardown: Runtime.Teardown = (exit, onExit) => { - * if (Exit.isSuccess(exit)) { - * console.log("Program completed successfully with value:", exit.value) - * onExit(0) - * } else { - * console.log("Program failed with cause:", exit.cause) - * onExit(1) - * } + * onExit(Exit.isSuccess(exit) ? 0 : 1) * } * + * const completed = new Promise, number]>((resolve) => { * // Use with makeRunMain - * const runMain = Runtime.makeRunMain(({ fiber, teardown }) => { - * fiber.addObserver((exit) => { - * teardown(exit, (code) => { - * console.log(`Exiting with code: ${code}`) + * const runMain = Runtime.makeRunMain(({ fiber, teardown }) => { + * fiber.addObserver((exit) => { + * teardown(exit, (code) => resolve([exit, code])) * }) * }) + * + * const program = Effect.succeed("Hello, World!") + * runMain(program, { teardown: customTeardown }) * }) * - * const program = Effect.succeed("Hello, World!") - * runMain(program, { teardown: customTeardown }) + * await completed // => [Exit.succeed("Hello, World!"), 0] * ``` * * @category models @@ -90,23 +86,18 @@ export interface Teardown { * * **Example** (Referencing default teardown) * - * ```ts + * ```ts import.meta.vitest * import { Exit, Runtime } from "effect" * - * const logExitCode = (exit: Exit.Exit) => { - * Runtime.defaultTeardown(exit, (code) => { - * console.log(`Exit code: ${code}`) - * }) - * } - * - * logExitCode(Exit.succeed(42)) - * // Output: Exit code: 0 + * const exitCodes: Array = [] + * const collectExitCode = (exit: Exit.Exit) => + * Runtime.defaultTeardown(exit, (code) => exitCodes.push(code)) * - * logExitCode(Exit.fail("error")) - * // Output: Exit code: 1 + * collectExitCode(Exit.succeed(42)) + * collectExitCode(Exit.fail("error")) + * collectExitCode(Exit.interrupt(123)) * - * logExitCode(Exit.interrupt(123)) - * // Output: Exit code: 130 + * exitCodes // => [0, 1, 130] * ``` * * @see {@link errorExitCode} for customizing failure exit codes @@ -152,47 +143,36 @@ export const defaultTeardown: Teardown = ( * * **Example** (Creating platform runners) * - * ```ts - * import { Effect, Fiber, Runtime } from "effect" + * ```ts import.meta.vitest + * import { Effect, Exit, Runtime } from "effect" * + * const events: Array = [] + * const completed = new Promise, number]>((resolve) => { * // Create a simple runner for a hypothetical platform - * const runMain = Runtime.makeRunMain(({ fiber, teardown }) => { - * // Set up signal handling - * const handleSignal = () => { - * Effect.runSync(Fiber.interrupt(fiber)) - * } - * - * // Add signal listeners (platform-specific) - * // process.on('SIGINT', handleSignal) - * // process.on('SIGTERM', handleSignal) - * - * // Handle fiber completion - * fiber.addObserver((exit) => { - * teardown(exit, (code) => { - * console.log(`Program finished with exit code: ${code}`) - * // process.exit(code) + * const runMain = Runtime.makeRunMain(({ fiber, teardown }) => { + * // Handle fiber completion + * fiber.addObserver((exit) => { + * teardown(exit, (code) => resolve([exit, code])) * }) * }) - * }) * - * // Use the runner - * const program = Effect.gen(function*() { - * yield* Effect.log("Starting program") - * yield* Effect.sleep(1000) - * yield* Effect.log("Program completed") - * return "success" - * }) - * - * // Run with default options - * runMain(program) + * // Use the runner + * const program = Effect.sync(() => { + * events.push("Starting program", "Program completed") + * return "success" + * }) * - * // Run with custom teardown - * runMain(program, { - * teardown: (exit, onExit) => { - * console.log("Custom teardown logic") - * Runtime.defaultTeardown(exit, onExit) - * } + * runMain(program, { + * teardown: (exit, onExit) => { + * events.push("Custom teardown logic") + * Runtime.defaultTeardown(exit, onExit) + * } + * }) * }) + * + * const result = await completed + * result // => [Exit.succeed("success"), 0] + * events // => ["Starting program", "Program completed", "Custom teardown logic"] * ``` * * @category running @@ -285,16 +265,14 @@ export type errorExitCode = "~effect/Runtime/errorExitCode" * * **Example** (Setting a process exit code) * - * ```ts - * import { Data, Effect, Runtime } from "effect" - * import { NodeRuntime } from "@effect/platform-node" + * ```ts import.meta.vitest + * import { Data, Runtime } from "effect" * * class MyError extends Data.TaggedError("MyError") { * readonly [Runtime.errorExitCode] = 42 * } * - * // If the program fails with MyError, the process will exit with code 42 - * NodeRuntime.runMain(Effect.fail(new MyError())) + * Runtime.getErrorExitCode(new MyError()) // => 42 * ``` * * @see {@link errorReported} for controlling automatic error logging @@ -327,7 +305,7 @@ export const errorExitCode: errorExitCode = "~effect/Runtime/errorExitCode" * * @see {@link errorExitCode} for the marker read by this function * - * @category accessors + * @category getters * @since 4.0.0 */ export const getErrorExitCode = (u: unknown): number => { @@ -377,17 +355,14 @@ export type errorReported = "~effect/Runtime/errorReported" * * **Example** (Suppressing error reporting) * - * ```ts - * import { Data, Effect, Runtime } from "effect" - * import { NodeRuntime } from "@effect/platform-node" + * ```ts import.meta.vitest + * import { Data, Runtime } from "effect" * * class MyError extends Data.TaggedError("MyError") { * readonly [Runtime.errorReported] = false * } * - * // If the program fails with MyError, the process will exit with code 1 but - * // no error will be logged. - * NodeRuntime.runMain(Effect.fail(new MyError())) + * Runtime.getErrorReported(new MyError()) // => false * ``` * * @see {@link errorExitCode} for controlling failure exit codes @@ -418,7 +393,7 @@ export const errorReported: errorReported = "~effect/Runtime/errorReported" * * @see {@link errorReported} for the marker read by this function * - * @category accessors + * @category getters * @since 4.0.0 */ export const getErrorReported = (u: unknown): boolean => { diff --git a/repos/effect/packages/effect/src/Schedule.ts b/repos/effect/packages/effect/src/Schedule.ts index 8e62d3afaa..a85e5d4eda 100644 --- a/repos/effect/packages/effect/src/Schedule.ts +++ b/repos/effect/packages/effect/src/Schedule.ts @@ -8,13 +8,13 @@ * * @since 2.0.0 */ +import type { NonEmptyReadonlyArray } from "./Array.ts" import * as Cause from "./Cause.ts" import * as Context from "./Context.ts" import * as Cron from "./Cron.ts" import type * as DateTime from "./DateTime.ts" import * as Duration from "./Duration.ts" import type { Effect } from "./Effect.ts" -import type { LazyArg } from "./Function.ts" import { constant, dual, identity } from "./Function.ts" import { isEffect } from "./internal/core.ts" import * as effect from "./internal/effect.ts" @@ -23,7 +23,7 @@ import { type Pipeable, pipeArguments } from "./Pipeable.ts" import { hasProperty } from "./Predicate.ts" import * as Pull from "./Pull.ts" import * as Result from "./Result.ts" -import type { Contravariant, Covariant, Mutable } from "./Types.ts" +import type { Contravariant, Covariant, Mutable, UnionToIntersection } from "./Types.ts" const TypeId = "~effect/Schedule" @@ -34,41 +34,17 @@ const randomNext: Effect = random.Random.useSync((random) => random.next * * **Example** (Defining retry and repeat schedules) * - * ```ts - * import { Console, Data, Effect, Schedule } from "effect" - * - * class NetworkError extends Data.TaggedError("NetworkError")<{ - * readonly attempt: number - * }> {} + * ```ts import.meta.vitest + * import { Effect, Schedule } from "effect" + * import { TestClock } from "effect/testing" * - * // Basic retry schedule - retry up to 3 times with exponential backoff - * const retrySchedule = Schedule.exponential("100 millis").pipe( - * Schedule.both(Schedule.recurs(3)) + * const executions: Array = [] + * const program = Effect.sync(() => executions.push(executions.length + 1)).pipe( + * Effect.repeat(Schedule.recurs(2)), + * Effect.as(executions) * ) * - * // Basic repeat schedule - repeat every 30 seconds forever - * const repeatSchedule: Schedule.Schedule = Schedule - * .spaced("30 seconds") - * - * const program = Effect.gen(function*() { - * let attempts = 0 - * - * const result1 = yield* Effect.retry( - * Effect.gen(function*() { - * attempts++ - * if (attempts < 3) { - * return yield* Effect.fail(new NetworkError({ attempt: attempts })) - * } - * return "Success" - * }), - * retrySchedule - * ) - * console.log(result1) // "Success" - * - * yield* Console.log("heartbeat").pipe( - * Effect.repeat(repeatSchedule.pipe(Schedule.take(5))) - * ) - * }) + * await Effect.runPromise(Effect.provide(program, TestClock.layer())) // => [1, 2, 3] * ``` * * @category models @@ -81,26 +57,6 @@ export interface Schedule - * Effect.succeed(metadata.attempt <= 5 && metadata.elapsed < 10000) - * ) - * ) - * - * const program = Effect.gen(function*() { - * yield* Effect.repeat( - * Console.log("Task execution"), - * metadataAwareSchedule - * ) - * }) - * ``` - * * @category metadata * @since 4.0.0 */ @@ -116,34 +72,6 @@ export interface InputMetadata { /** * Extended metadata that includes both input metadata and the output value from the schedule. * - * **Example** (Logging schedule output metadata) - * - * ```ts - * import { Console, Duration, Effect, Schedule } from "effect" - * - * // Custom schedule that logs metadata and output for each recurrence - * const loggingSchedule = Schedule.unfold(0, (n) => Effect.succeed(n + 1)).pipe( - * Schedule.addDelay(() => Effect.succeed(Duration.millis(100))), - * Schedule.collectWhile((metadata) => - * Console.log( - * `Output: ${metadata.output}, attempt: ${metadata.attempt}, elapsed: ${metadata.elapsed}ms` - * ).pipe(Effect.as(metadata.attempt <= 3)) - * ) - * ) - * - * const program = Effect.gen(function*() { - * yield* Effect.repeat( - * Effect.succeed("task completed"), - * loggingSchedule.pipe(Schedule.take(3)) - * ) - * }) - * - * // Output logs will show: - * // Output: 0, attempt: 1, elapsed: 0ms - * // Output: 1, attempt: 2, elapsed: 100ms - * // Output: 2, attempt: 3, elapsed: 200ms - * ``` - * * @category metadata * @since 4.0.0 */ @@ -162,7 +90,7 @@ export interface Metadata extends InputMetada * input and output values, zero duration, and zeroed timing fields before any * schedule step has produced metadata. * - * @category metadata + * @category services * @since 4.0.0 */ export const CurrentMetadata = Context.Reference("effect/Schedule/CurrentMetadata", { @@ -181,34 +109,6 @@ export const CurrentMetadata = Context.Reference("effect/Schedule/Curr /** * The Schedule namespace contains types and utilities for working with schedules. * - * **Example** (Creating custom schedules with the namespace) - * - * ```ts - * import { Duration, Effect, Schedule } from "effect" - * - * // Usage of the Schedule namespace for creating schedules - * - * // Create custom schedule with metadata - * const customSchedule = Schedule.unfold(0, (n) => Effect.succeed(n + 1)).pipe( - * Schedule.addDelay((n) => Effect.succeed(Duration.millis(n * 100))) - * ) - * - * const program = Effect.gen(function*() { - * let attempt = 0 - * - * yield* Effect.retry( - * Effect.gen(function*() { - * attempt++ - * if (attempt < 3) { - * return yield* Effect.fail(`Attempt ${attempt} failed`) - * } - * return `Success on attempt ${attempt}` - * }), - * customSchedule.pipe(Schedule.take(5)) - * ) - * }) - * ``` - * * @since 2.0.0 */ export declare namespace Schedule { @@ -217,27 +117,11 @@ export declare namespace Schedule { * * **Example** (Understanding schedule variance) * - * ```ts - * import { Effect, Schedule } from "effect" - * - * // Understanding Schedule variance: - * // - Output: covariant (can be a subtype) - * // - Input: contravariant (can accept supertypes) - * // - Error: covariant (can be a subtype) - * // - Env: covariant (can be a subtype) - * - * // Schedule that produces strings, accepts any input - * const stringSchedule = Schedule.spaced("1 second").pipe( - * Schedule.map(() => Effect.succeed("tick")) - * ) - * - * // Schedule that only accepts Error inputs - * const errorSchedule = Schedule.exponential("100 millis").pipe( - * Schedule.take(5) - * ) + * ```ts import.meta.vitest + * import { Schedule } from "effect" * - * // Schedule requiring a service environment - * const serviceSchedule = Schedule.spaced("5 seconds") + * const schedule: Schedule.Schedule = Schedule.recurs(2) + * Schedule.isSchedule(schedule) // => true * ``` * * @category models @@ -267,6 +151,38 @@ export declare namespace Schedule { } } +/** + * Extracts the output type from a `Schedule`. + * + * @category utility types + * @since 4.0.0 + */ +export type Output = S extends Schedule ? Output : never + +/** + * Extracts the input type from a `Schedule`. + * + * @category utility types + * @since 4.0.0 + */ +export type Input = S extends Schedule ? Input : never + +/** + * Extracts the error type from a `Schedule`. + * + * @category utility types + * @since 4.0.0 + */ +export type Error = S extends Schedule ? Error : never + +/** + * Extracts the service requirements from a `Schedule`. + * + * @category utility types + * @since 4.0.0 + */ +export type Env = S extends Schedule ? Env : never + const ScheduleProto = { [TypeId]: { _Out: identity, @@ -283,16 +199,16 @@ const ScheduleProto = { * * **Example** (Checking for schedules) * - * ```ts + * ```ts import.meta.vitest * import { Schedule } from "effect" * * const schedule = Schedule.exponential("100 millis") * const notSchedule = { foo: "bar" } * - * console.log(Schedule.isSchedule(schedule)) // true - * console.log(Schedule.isSchedule(notSchedule)) // false - * console.log(Schedule.isSchedule(null)) // false - * console.log(Schedule.isSchedule(undefined)) // false + * Schedule.isSchedule(schedule) // => true + * Schedule.isSchedule(notSchedule) // => false + * Schedule.isSchedule(null) // => false + * Schedule.isSchedule(undefined) // => false * ``` * * @category guards @@ -305,7 +221,7 @@ export const isSchedule = (u: unknown): u is Schedule { @@ -318,6 +234,14 @@ export const isSchedule = (u: unknown): u is Schedule 0 * ``` * * @category constructors @@ -353,7 +277,7 @@ const metadataFn = () => { * * **Example** (Creating a metadata-aware schedule) * - * ```ts + * ```ts import.meta.vitest * import { Cause, Duration, Effect, Schedule } from "effect" * * const firstThreeInputs = Schedule.fromStepWithMetadata(Effect.succeed((metadata: Schedule.InputMetadata) => { @@ -366,6 +290,14 @@ const metadataFn = () => { * Duration.millis(250) * ] as [string, Duration.Duration]) * })) + * + * const program = Effect.gen(function*() { + * const step = yield* Schedule.toStep(firstThreeInputs) + * const [output] = yield* step(0, "input") + * return output + * }) + * + * await Effect.runPromise(program) // => "attempt 1: input" * ``` * * @category constructors @@ -388,11 +320,11 @@ export const fromStepWithMetadata = ( * * **Example** (Extracting a schedule step function) * - * ```ts - * import { Effect, Schedule } from "effect" + * ```ts import.meta.vitest + * import { Duration, Effect, Schedule } from "effect" * * // Extract step function from an existing schedule - * const schedule = Schedule.exponential("100 millis").pipe(Schedule.take(3)) + * const schedule = Schedule.exponential("100 millis").pipe(Schedule.upTo({ times: 3 })) * * const program = Effect.gen(function*() { * const stepFn = yield* Schedule.toStep(schedule) @@ -400,10 +332,10 @@ export const fromStepWithMetadata = ( * // Use the step function directly for custom logic. The timestamp is * // supplied by the caller, so tests can pass a deterministic value. * const now = 0 - * const result = yield* stepFn(now, "input") - * - * console.log(`Step result: ${result}`) + * return yield* stepFn(now, "input") * }) + * + * await Effect.runPromise(program) // => [Duration.millis(100), Duration.millis(100)] * ``` * * @category destructors @@ -476,26 +408,19 @@ export const toStepWithMetadata = ( * * **Example** (Extracting a sleeping step function) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Schedule } from "effect" + * import { TestClock } from "effect/testing" * - * // Convert schedule to step function with automatic sleeping - * const schedule = Schedule.spaced("1 second").pipe(Schedule.take(3)) + * const schedule = Schedule.recurs(3) * * const program = Effect.gen(function*() { * const stepWithSleep = yield* Schedule.toStepWithSleep(schedule) * - * // Each call will automatically sleep for the scheduled delay - * console.log("Starting...") - * const result1 = yield* stepWithSleep("first") - * console.log(`First result: ${result1}`) - * - * const result2 = yield* stepWithSleep("second") - * console.log(`Second result: ${result2}`) - * - * const result3 = yield* stepWithSleep("third") - * console.log(`Third result: ${result3}`) + * return [yield* stepWithSleep("first"), yield* stepWithSleep("second")] * }) + * + * await Effect.runPromise(Effect.provide(program, TestClock.layer())) // => [0, 1] * ``` * * @category destructors @@ -519,117 +444,41 @@ export const toStepWithSleep = ( * * **Example** (Adding extra delay to a schedule) * - * ```ts - * import { Console, Data, Duration, Effect, Schedule } from "effect" - * - * class RetryAttemptError extends Data.TaggedError("RetryAttemptError")<{ readonly message: string }> {} - * - * // Add a deterministic extra delay based on the schedule output - * const delayedSchedule = Schedule.addDelay( - * Schedule.exponential("100 millis").pipe(Schedule.take(5)), - * (output) => - * Effect.succeed(Duration.millis(Duration.toMillis(output) * 0.25)) - * ) - * - * const repeatProgram = Effect.gen(function*() { - * yield* Effect.repeat( - * Effect.succeed("delayed task"), - * delayedSchedule.pipe( - * Schedule.tapOutput((delay) => - * Console.log(`Base delay: ${delay}`) - * ) - * ) - * ) - * }) - * - * // Add adaptive delay based on execution count - * const adaptiveSchedule = Schedule.addDelay( - * Schedule.recurs(6), - * (executionCount) => - * // Increase delay as execution count grows - * Effect.succeed(Duration.millis(executionCount * 200)) - * ) - * - * const adaptiveProgram = Effect.gen(function*() { - * yield* Effect.repeat( - * Effect.gen(function*() { - * yield* Console.log("Adaptive delay task") - * return "adaptive" - * }), - * adaptiveSchedule.pipe( - * Schedule.tapOutput((count) => - * Console.log(`Execution ${count + 1} with adaptive delay`) - * ) - * ) - * ) - * }) - * - * // Add effectful delay computation from deterministic service data - * const loadByExecution = [1, 3, 2, 4] as const + * ```ts import.meta.vitest + * import { Duration, Effect, Schedule } from "effect" * - * const dynamicSchedule = Schedule.addDelay( - * Schedule.spaced("1 second").pipe(Schedule.take(4)), - * (executionNumber) => { - * const load = loadByExecution[executionNumber] ?? 1 - * return Effect.succeed(Duration.millis(load * 100)) - * } + * const schedule = Schedule.recurs(1).pipe( + * Schedule.addDelay(() => Effect.succeed("25 millis")) * ) - * - * const dynamicProgram = Effect.gen(function*() { - * yield* Effect.repeat( - * Effect.gen(function*() { - * yield* Console.log("Dynamic delay task") - * return "dynamic" - * }), - * dynamicSchedule - * ) + * const program = Effect.gen(function*() { + * const step = yield* Schedule.toStep(schedule) + * const [, delay] = yield* step(0, undefined) + * return delay * }) * - * // Combine with retry for progressive backoff - * const progressiveRetrySchedule = Schedule.addDelay( - * Schedule.exponential("50 millis").pipe(Schedule.take(4)), - * () => Effect.succeed(Duration.millis(100)) // Fixed additional delay - * ) - * - * const retryProgram = Effect.gen(function*() { - * let attempt = 0 - * - * const result = yield* Effect.retry( - * Effect.gen(function*() { - * attempt++ - * if (attempt < 5) { - * return yield* Effect.fail(new RetryAttemptError({ message: `Attempt ${attempt} failed` })) - * } - * return `Success on attempt ${attempt}` - * }), - * progressiveRetrySchedule - * ) - * - * yield* Console.log(`Final result: ${result}`) - * }) + * await Effect.runPromise(program) // => Duration.millis(25) * ``` * * @category delays & timeouts * @since 2.0.0 */ export const addDelay: { - ( - f: (output: Output) => Effect - ): ( + ( + f: (metadata: Metadata) => Effect + ): ( self: Schedule ) => Schedule ( self: Schedule, - f: (output: Output) => Effect + f: (metadata: Metadata) => Effect ): Schedule } = dual(2, ( self: Schedule, - f: (output: Output) => Effect + f: (metadata: Metadata) => Effect ): Schedule => modifyDelay( self, - (output, delay) => - effect.map(f(output), (d) => Duration.sum(Duration.fromInputUnsafe(d), Duration.fromInputUnsafe(delay))) + (metadata) => effect.map(f(metadata), (d) => Duration.sum(Duration.fromInputUnsafe(d), metadata.duration)) )) /** @@ -638,41 +487,17 @@ export const addDelay: { * * **Example** (Sequencing quick and slow retries) * - * ```ts - * import { Console, Data, Effect, Schedule } from "effect" - * - * class RetryAttemptError extends Data.TaggedError("RetryAttemptError")<{ readonly message: string }> {} - * - * // First retry 3 times quickly, then switch to slower retries - * const quickRetries = Schedule.exponential("100 millis").pipe( - * Schedule.take(3) - * ) - * const slowRetries = Schedule.exponential("1 second").pipe( - * Schedule.take(2) - * ) - * - * const combinedRetries = Schedule.andThen(quickRetries, slowRetries) + * ```ts import.meta.vitest + * import { Schedule } from "effect" * - * const program = Effect.gen(function*() { - * let attempt = 0 - * yield* Effect.retry( - * Effect.gen(function*() { - * attempt++ - * yield* Console.log(`Attempt ${attempt}`) - * if (attempt < 6) { - * return yield* Effect.fail(new RetryAttemptError({ message: `Failure ${attempt}` })) - * } - * return `Success on attempt ${attempt}` - * }), - * combinedRetries - * ) - * }) + * const schedule = Schedule.concat(Schedule.recurs(1), Schedule.recurs(2)) + * Schedule.isSchedule(schedule) // => true * ``` * * @category sequencing * @since 2.0.0 */ -export const andThen: { +export const concat: { ( other: Schedule ): ( @@ -686,7 +511,7 @@ export const andThen: { self: Schedule, other: Schedule ): Schedule => - map(andThenResult(self, other), (result) => effect.succeed(Result.merge(result)))) + map(concatResult(self, other), ({ output }) => effect.succeed(Result.merge(output)))) /** * Returns a schedule that runs `self` to completion, then runs `other`, and @@ -700,37 +525,17 @@ export const andThen: { * * **Example** (Tracking sequential schedule phases) * - * ```ts - * import { Console, Effect, Result, Schedule } from "effect" - * - * // Track which phase of the schedule we're in - * const phaseTracker = Schedule.andThenResult( - * Schedule.exponential("100 millis").pipe(Schedule.take(2)), - * Schedule.spaced("500 millis").pipe(Schedule.take(2)) - * ) + * ```ts import.meta.vitest + * import { Schedule } from "effect" * - * const program = Effect.gen(function*() { - * yield* Effect.repeat( - * Effect.gen(function*() { - * yield* Console.log("Task executed") - * return "task-result" - * }), - * phaseTracker.pipe( - * Schedule.tapOutput((result) => - * Result.match(result, { - * onFailure: (phase1Output) => Console.log(`Phase 1: ${phase1Output}`), - * onSuccess: (phase2Output) => Console.log(`Phase 2: ${phase2Output}`) - * }) - * ) - * ) - * ) - * }) + * const schedule = Schedule.concatResult(Schedule.recurs(1), Schedule.recurs(2)) + * Schedule.isSchedule(schedule) // => true * ``` * * @category sequencing * @since 4.0.0 */ -export const andThenResult: { +export const concatResult: { ( other: Schedule ): ( @@ -743,31 +548,31 @@ export const andThenResult: { } = dual(2, ( self: Schedule, other: Schedule -): Schedule, Input & Input2, Error | Error2, Env | Env2> => +): Schedule, Input & Input2, Error | Error2, Env | Env2> => fromStep(effect.sync(() => { let currentSide = 0 let currentStep: | undefined | ((now: number, input: Input & Input2) => Pull.Pull< - [Result.Result, Duration.Duration], + [Result.Result, Duration.Duration], Error | Error2, - Result.Result, + Result.Result, Env | Env2 >) - const left = map(self, Result.succeed) - const right = map(other, Result.fail) + const left = map(self, ({ output }) => Result.fail(output)) + const right = map(other, ({ output }) => Result.succeed(output)) return function recur( now, input ): Pull.Pull< - [Result.Result, Duration.Duration], + [Result.Result, Duration.Duration], Error | Error2, - Result.Result, + Result.Result, Env | Env2 > { if (currentStep) return currentStep(now, input) return toStep< - Result.Result, + Result.Result, Input & Input2, Error | Error2, Env | Env2 @@ -790,1223 +595,253 @@ export const andThenResult: { }))) /** - * Combines two `Schedule`s by recurring if both of the two schedules want - * to recur, using the maximum of the two durations between recurrences and - * outputting a tuple of the outputs of both schedules. + * Combines schedules by recurring while all schedules want to recur, using the + * maximum delay between recurrences and outputting that maximum delay. * * **When to use** * - * Use when the combined schedule should continue only while both schedules still recur. - * - * **Example** (Combining time and attempt limits) - * - * ```ts - * import { Console, Data, Effect, Schedule } from "effect" - * - * class RetryAttemptError extends Data.TaggedError("RetryAttemptError")<{ readonly message: string }> {} - * - * // Both schedules must want to continue for the combined schedule to continue - * const timeLimit = Schedule.spaced("1 second").pipe(Schedule.take(5)) // max 5 times - * const attemptLimit = Schedule.recurs(3) // max 3 attempts - * - * // Continues only while BOTH schedules want to continue (intersection/AND logic) - * const bothSchedule = Schedule.both(timeLimit, attemptLimit) - * // Outputs: [time_result, attempt_count] tuple - * - * const program = Effect.gen(function*() { - * const results = yield* Effect.repeat( - * Effect.gen(function*() { - * yield* Console.log("Task executed") - * return "task completed" - * }), - * bothSchedule.pipe( - * Schedule.tapOutput(([timeResult, attemptResult]) => - * Console.log(`Time: ${timeResult}, Attempts: ${attemptResult}`) - * ) - * ) - * ) - * - * yield* Console.log("Completed all executions") - * }) + * Use when a combined policy should continue only while every schedule still + * recurs, and should wait for the slowest schedule between recurrences. * - * // Both with different delay strategies - uses maximum delay - * const fastSchedule = Schedule.fixed("500 millis").pipe(Schedule.take(4)) - * const slowSchedule = Schedule.spaced("2 seconds").pipe(Schedule.take(6)) + * **Example** (Combining retry schedules by their maximum delay) * - * // Will use the slower (maximum) delay and stop when first schedule exhausts - * const conservativeSchedule = Schedule.both(fastSchedule, slowSchedule) - * - * const retryProgram = Effect.gen(function*() { - * let attempt = 0 + * ```ts import.meta.vitest + * import { Schedule } from "effect" * - * const result = yield* Effect.retry( - * Effect.gen(function*() { - * attempt++ - * yield* Console.log(`Retry attempt ${attempt}`) + * const schedule = Schedule.max([Schedule.fixed("5 seconds"), Schedule.spaced("10 seconds")]) + * Schedule.isSchedule(schedule) // => true + * ``` * - * if (attempt < 3) { - * return yield* Effect.fail(new RetryAttemptError({ message: `Attempt ${attempt} failed` })) - * } + * @category combining + * @since 4.0.0 + */ +export const max = < + const Schedules extends NonEmptyReadonlyArray< + Schedule + > +>( + schedules: Schedules +): Schedule< + Duration.Duration, + UnionToIntersection< + Input + >, + Error, + Env +> => + fromStep(effect.map( + effect.all(schedules.map(toStep)), + (steps) => (now, input) => + effect.flatMap( + effect.forEach(steps, (step) => + Pull.matchEffect(step(now, input as never), { + onSuccess: (result) => effect.succeed(result[1]), + onDone: () => effect.undefined, + onFailure: effect.failCause + })), + (results) => { + const duration = maxDuration(results) + if (duration === undefined) { + return Cause.done(Duration.zero) + } + return effect.succeed([duration, duration] as [Duration.Duration, Duration.Duration]) + } + ) + )) + +const maxDuration = (results: ReadonlyArray): Duration.Duration | undefined => { + let max = results[0] + for (let i = 1; i < results.length; i++) { + max = results[i] && max && Duration.max(max, results[i]!) + if (max === undefined) break + } + + return max +} + +/** + * Returns a new `Schedule` that recurs on the specified `Cron` schedule and + * outputs the duration between recurrences. * - * return `Success on attempt ${attempt}` - * }), - * conservativeSchedule - * ) + * **Example** (Scheduling work with cron expressions) * - * yield* Console.log(`Final result: ${result}`) - * }) + * ```ts import.meta.vitest + * import { Schedule } from "effect" * - * // Both provides intersection semantics (AND logic) - * // Compare with either which provides union semantics (OR logic) + * const everyMinute = Schedule.cron("* * * * *") + * Schedule.isSchedule(everyMinute) // => true * ``` * - * @see {@link either} for continuing while either schedule still recurs - * - * @category combining + * @category constructors * @since 2.0.0 */ -export const both: { - ( - other: Schedule - ): ( - self: Schedule - ) => Schedule<[Output, Output2], Input & Input2, Error | Error2, Env | Env2> - ( - self: Schedule, - other: Schedule - ): Schedule<[Output, Output2], Input & Input2, Error | Error2, Env | Env2> -} = dual(2, ( - self: Schedule, - other: Schedule -): Schedule<[Output, Output2], Input & Input2, Error | Error2, Env | Env2> => - bothWith(self, other, (left, right) => [left, right])) +export const cron: { + (expression: Cron.Cron): Schedule + (expression: string, tz?: string | DateTime.TimeZone): Schedule +} = (expression: string | Cron.Cron, tz?: string | DateTime.TimeZone) => { + const parsed = Cron.isCron(expression) ? Result.succeed(expression) : Cron.parse(expression, tz) + return fromStep(effect.map(effect.fromResult(parsed), (cron) => (now, _) => { + if (now === Number.POSITIVE_INFINITY) { + return Cause.done(Duration.zero) + } + return effect.sync(() => { + const next = Cron.next(cron, now).getTime() + const duration = Duration.millis(next - now) + return [duration, duration] + }) + })) +} /** - * Combines two `Schedule`s by recurring if both of the two schedules want - * to recur, using the maximum of the two durations between recurrences and - * outputting the result of the left schedule (i.e. `self`). + * Returns a schedule that recurs once after the specified duration. * * **When to use** * - * Use when two schedules must both allow recurrence and only the left schedule's - * output is needed. + * Use when you need a schedule that recurs once after a fixed delay. * - * **Example** (Combining schedules and keeping the left output) + * **Details** * - * ```ts - * import { Console, Effect, Schedule } from "effect" + * The schedule outputs the configured duration for its first recurrence and + * then completes. * - * // Combine two schedules, keeping left output - * const leftSchedule = Schedule.exponential("100 millis").pipe( - * Schedule.map(() => Effect.succeed("left-result")) - * ) - * const rightSchedule = Schedule.spaced("50 millis") + * **Example** (Recurring once after a duration) * - * const combined = Schedule.bothLeft(leftSchedule, rightSchedule) + * ```ts import.meta.vitest + * import { Schedule } from "effect" * - * const program = Effect.gen(function*() { - * yield* Effect.repeat( - * Effect.gen(function*() { - * yield* Console.log("Task executed") - * return "task-done" - * }), - * combined.pipe(Schedule.take(3)) - * ) - * }) + * Schedule.isSchedule(Schedule.duration("1 second")) // => true * ``` * - * @category combining + * @see {@link during} for recurring until a duration has elapsed + * + * @category constructors * @since 2.0.0 */ -export const bothLeft: { - ( - other: Schedule - ): ( - self: Schedule - ) => Schedule - ( - self: Schedule, - other: Schedule - ): Schedule -} = dual(2, ( - self: Schedule, - other: Schedule -): Schedule => bothWith(self, other, (output) => output)) +export const duration = (durationInput: Duration.Input): Schedule => { + const duration = Duration.fromInputUnsafe(durationInput) + return fromStepWithMetadata(effect.succeed((meta) => + meta.attempt === 1 + ? effect.succeed([duration, duration]) + : Cause.done(Duration.zero) + )) +} /** - * Combines two `Schedule`s by recurring if both of the two schedules want - * to recur, using the maximum of the two durations between recurrences and - * outputting the result of the right schedule (i.e. `other`). + * Returns a new `Schedule` that will always recur, but only during the + * specified `duration` of time. * * **When to use** * - * Use when two schedules must both allow recurrence and only the right - * schedule's output is needed. - * - * **Example** (Combining schedules and keeping the right output) - * - * ```ts - * import { Console, Effect, Schedule } from "effect" + * Use to bound a repeating or retrying schedule by elapsed time. * - * // Combine two schedules, keeping right output - * const leftSchedule = Schedule.exponential("100 millis").pipe( - * Schedule.map(() => Effect.succeed("left-result")) - * ) - * const rightSchedule = Schedule.spaced("50 millis").pipe( - * Schedule.map(() => Effect.succeed("right-result")) - * ) + * **Example** (Repeating work during a duration) * - * const combined = Schedule.bothRight(leftSchedule, rightSchedule) + * ```ts import.meta.vitest + * import { Schedule } from "effect" * - * const program = Effect.gen(function*() { - * yield* Effect.repeat( - * Effect.gen(function*() { - * yield* Console.log("Task executed") - * return "task-done" - * }), - * combined.pipe(Schedule.take(3)) - * ) - * }) + * Schedule.isSchedule(Schedule.during("5 seconds")) // => true * ``` * - * @category combining - * @since 2.0.0 + * @see {@link duration} for one delayed recurrence + * + * @category constructors + * @since 4.0.0 */ -export const bothRight: { - ( - other: Schedule - ): ( - self: Schedule - ) => Schedule - ( - self: Schedule, - other: Schedule - ): Schedule -} = dual(2, ( - self: Schedule, - other: Schedule -): Schedule => bothWith(self, other, (_, output) => output)) +export const during = (duration: Duration.Input): Schedule => { + const durationMillis = Duration.toMillis(duration) + return fromStepWithMetadata( + effect.succeed((meta) => { + const elapsed = Duration.millis(meta.elapsed) + return meta.elapsed > durationMillis + ? Cause.done(elapsed) + : effect.succeed([elapsed, Duration.zero]) + }) + ) +} /** - * Combines two `Schedule`s by recurring if both of the two schedules want - * to recur, using the maximum of the two durations between recurrences and - * outputting the result of the combination of both schedule outputs using the - * specified `combine` function. + * Combines schedules by recurring while at least one schedule wants to recur, + * using the minimum delay between recurrences and outputting that minimum delay. * * **When to use** * - * Use when two schedules must both allow recurrence and their outputs should be - * combined into a custom value. - * - * **Example** (Combining schedule outputs) - * - * ```ts - * import { Console, Effect, Schedule } from "effect" + * Use when a combined policy should continue while any schedule still recurs, + * and should wait for the fastest schedule between recurrences. * - * // Combine two schedules with custom output combination - * const leftSchedule = Schedule.exponential("100 millis").pipe( - * Schedule.map(() => Effect.succeed("left")) - * ) - * const rightSchedule = Schedule.spaced("50 millis").pipe( - * Schedule.map(() => Effect.succeed("right")) - * ) + * **Example** (Combining retry schedules by their minimum delay) * - * const combined = Schedule.bothWith( - * leftSchedule, - * rightSchedule, - * (left, right) => `${left}-${right}` - * ) + * ```ts import.meta.vitest + * import { Schedule } from "effect" * - * const program = Effect.gen(function*() { - * yield* Effect.repeat( - * Effect.gen(function*() { - * yield* Console.log("Task executed") - * return "task-result" - * }), - * combined.pipe(Schedule.take(3)) - * ) - * }) + * const schedule = Schedule.min([Schedule.fixed("5 seconds"), Schedule.spaced("10 seconds")]) + * Schedule.isSchedule(schedule) // => true * ``` * * @category combining - * @since 2.0.0 + * @since 4.0.0 */ -export const bothWith: { - ( - other: Schedule, - combine: (selfOutput: Output, otherOutput: Output2) => Output3 - ): ( - self: Schedule - ) => Schedule - ( - self: Schedule, - other: Schedule, - combine: (selfOutput: Output, otherOutput: Output2) => Output3 - ): Schedule -} = dual(3, ( - self: Schedule, - other: Schedule, - combine: (selfOutput: Output, otherOutput: Output2) => Output3 -): Schedule => +export const min = < + const Schedules extends NonEmptyReadonlyArray< + Schedule + > +>( + schedules: Schedules +): Schedule< + Duration.Duration, + UnionToIntersection< + Input + >, + Error, + Env +> => fromStep(effect.map( - effect.zip(toStep(self), toStep(other)), - ([stepLeft, stepRight]) => (now, input) => - Pull.matchEffect(stepLeft(now, input as Input), { - onSuccess: (leftResult) => - stepRight(now, input as Input2).pipe( - effect.map((rightResult) => - [ - combine(leftResult[0], rightResult[0]), - Duration.max(leftResult[1], rightResult[1]) - ] as [Output3, Duration.Duration] - ), - Pull.catchDone((rightDone) => Cause.done(combine(leftResult[0], rightDone as Output2))) - ), - onDone: (leftDone) => - stepRight(now, input as Input2).pipe( - effect.flatMap((rightResult) => Cause.done(combine(leftDone, rightResult[0]))), - Pull.catchDone((rightDone) => Cause.done(combine(leftDone, rightDone as Output2))) - ), - onFailure: effect.failCause - }) - ))) + effect.all(schedules.map(toStep)), + (steps) => (now, input) => + effect.flatMap( + effect.forEach(steps, (step) => + Pull.matchEffect(step(now, input as never), { + onSuccess: (result) => effect.succeed(result[1]), + onDone: () => effect.undefined, + onFailure: effect.failCause + })), + (results) => { + const duration = minDuration(results) + if (duration === undefined) { + return Cause.done(Duration.zero) + } + return effect.succeed([duration, duration] as [Duration.Duration, Duration.Duration]) + } + ) + )) + +const minDuration = (results: ReadonlyArray): Duration.Duration | undefined => { + let min: Duration.Duration | undefined = undefined + for (let i = 0; i < results.length; i++) { + const duration = results[i] + if (duration !== undefined) { + min = min === undefined ? duration : Duration.min(min, duration) + } + } + + return min +} /** - * Returns a new `Schedule` that follows `self` and outputs the inputs seen so - * far as an array. + * Schedule that always recurs, but will wait a certain amount between + * repetitions, given by `base * factor.pow(n)`, where `n` is the number of + * repetitions so far. Returns the current duration between recurrences. * - * **Details** + * **Example** (Retrying with exponential backoff) * - * This does not make the schedule run forever. The collected schedule stops - * when `self` stops and fails when `self` fails. + * ```ts import.meta.vitest + * import { Duration, Effect, Schedule } from "effect" * - * **Example** (Collecting schedule inputs) + * const program = Effect.gen(function*() { + * const step = yield* Schedule.toStep(Schedule.exponential("100 millis")) + * return yield* step(0, undefined) + * }) * - * ```ts - * import { Console, Effect, Schedule } from "effect" - * - * // Collect all inputs passed to the schedule - * const inputCollector = Schedule.collectInputs( - * Schedule.spaced("100 millis") - * ) - * - * const program = Effect.gen(function*() { - * let counter = 0 - * yield* Effect.repeat( - * Effect.gen(function*() { - * counter++ - * yield* Console.log(`Iteration ${counter}`) - * return `result-${counter}` - * }), - * inputCollector.pipe(Schedule.take(4)) - * ) - * }) - * ``` - * - * @category collecting - * @since 4.0.0 - */ -export const collectInputs = ( - self: Schedule -): Schedule, Input, Error, Env> => collectWhile(passthrough(self), () => effect.succeed(true)) - -/** - * Returns a new `Schedule` that follows `self` and outputs the schedule outputs - * seen so far as an array. - * - * **Details** - * - * This does not make the schedule run forever. The collected schedule stops - * when `self` stops and fails when `self` fails. - * - * **Example** (Collecting schedule outputs) - * - * ```ts - * import { Console, Effect, Schedule } from "effect" - * - * // Collect all outputs from the schedule - * const outputCollector = Schedule.collectOutputs( - * Schedule.recurs(4) - * ) - * - * const program = Effect.gen(function*() { - * yield* Effect.repeat( - * Effect.gen(function*() { - * yield* Console.log("Task executed") - * return "task-result" - * }), - * outputCollector.pipe(Schedule.take(4)) - * ) - * }) - * ``` - * - * @category collecting - * @since 4.0.0 - */ -export const collectOutputs = ( - self: Schedule -): Schedule, Input, Error, Env> => collectWhile(self, () => effect.succeed(true)) - -/** - * Returns a new `Schedule` that recurs as long as the specified `predicate` - * returns `true`, collecting all outputs of the schedule into an array. - * - * **Example** (Collecting outputs while a condition holds) - * - * ```ts - * import { Console, Effect, Schedule } from "effect" - * - * // Collect outputs while condition is met - * const collectWhileSmall = Schedule.collectWhile( - * Schedule.exponential("100 millis"), - * (metadata) => - * Effect.succeed(metadata.attempt <= 5 && metadata.elapsed < 2000) - * ) - * - * const conditionalProgram = Effect.gen(function*() { - * let attempt = 0 - * - * const attempts = yield* Effect.repeat( - * Effect.gen(function*() { - * attempt++ - * yield* Console.log(`Retry attempt ${attempt}`) - * return `attempt-${attempt}` - * }), - * collectWhileSmall - * ) - * - * yield* Console.log(`Collected attempts: [${attempts.join(", ")}]`) - * }) - * - * // Collect with effectful predicate - * const collectWithCheck = Schedule.collectWhile( - * Schedule.fixed("1 second"), - * (metadata) => - * Effect.gen(function*() { - * const shouldContinue = metadata.attempt < 5 - * yield* Console.log( - * `Check ${metadata.attempt}: continue = ${shouldContinue}` - * ) - * return shouldContinue - * }) - * ) - * - * const effectfulProgram = Effect.gen(function*() { - * const results = yield* Effect.repeat( - * Effect.succeed("checked"), - * collectWithCheck - * ) - * - * yield* Console.log(`Final collection: ${results.length} items`) - * }) - * - * // Collect samples with condition - * const samples = [12, 18, 24, 30, 36] - * - * const collectSamples = Schedule.collectWhile( - * Schedule.spaced("200 millis"), - * (metadata) => - * Effect.succeed(metadata.attempt <= 5 && metadata.elapsed < 2000) - * ) - * - * const samplingProgram = Effect.gen(function*() { - * let index = 0 - * const collected = yield* Effect.repeat( - * Effect.gen(function*() { - * const sample = samples[index++] - * yield* Console.log(`Sample: ${sample}`) - * return sample - * }), - * collectSamples - * ) - * - * const average = collected.reduce((sum, s) => sum + s, 0) / collected.length - * yield* Console.log( - * `Collected ${collected.length} samples, average: ${average.toFixed(1)}` - * ) - * }) - * ``` - * - * @category collecting - * @since 2.0.0 - */ -export const collectWhile: { - ( - predicate: ( - metadata: Metadata - ) => boolean | Effect - ): ( - self: Schedule - ) => Schedule, Input, Error | Error2, Env | Env2> - ( - self: Schedule, - predicate: ( - metadata: Metadata - ) => boolean | Effect - ): Schedule, Input, Error | Error2, Env | Env2> -} = dual(2, ( - self: Schedule, - predicate: ( - metadata: Metadata - ) => boolean | Effect -): Schedule, Input, Error | Error2, Env | Env2> => - reduce(while_(self, predicate), () => [] as Array, (outputs, output) => { - outputs.push(output) - return outputs - })) - -/** - * Returns a new `Schedule` that recurs on the specified `Cron` schedule and - * outputs the duration between recurrences. - * - * **Example** (Scheduling work with cron expressions) - * - * ```ts - * import { Console, Data, Effect, Schedule } from "effect" - * - * class ScheduledTaskError extends Data.TaggedError("ScheduledTaskError")<{ readonly message: string }> {} - * - * // Run every minute - * const everyMinute = Schedule.cron("* * * * *") - * - * const minutelyProgram = Effect.gen(function*() { - * yield* Effect.repeat( - * Effect.gen(function*() { - * yield* Console.log("Running minutely task") - * return "minute" - * }), - * everyMinute.pipe( - * Schedule.take(3), // Run only 3 times for demo - * Schedule.tapOutput((duration) => - * Console.log(`Next execution in: ${duration}`) - * ) - * ) - * ) - * }) - * - * // Run every day at 2:30 AM - * const dailyBackup = Schedule.cron("30 2 * * *") - * - * const backupProgram = Effect.gen(function*() { - * yield* Effect.repeat( - * Effect.gen(function*() { - * yield* Console.log("Running daily backup...") - * // Simulate backup process - * yield* Effect.sleep("2 seconds") - * yield* Console.log("Backup completed") - * return "backup-done" - * }), - * dailyBackup.pipe( - * Schedule.take(2) // Run 2 times for demo - * ) - * ) - * }) - * - * // Run every Monday at 9:00 AM with timezone - * const weeklyReport = Schedule.cron("0 9 * * 1", "America/New_York") - * - * const reportProgram = Effect.gen(function*() { - * yield* Effect.repeat( - * Effect.gen(function*() { - * yield* Console.log("Generating weekly report...") - * const report = { - * week: 42, - * status: "ready" as const - * } - * yield* Console.log(`Report generated: ${JSON.stringify(report)}`) - * return report - * }), - * weeklyReport.pipe(Schedule.take(1)) - * ) - * }) - * - * // Run every 15 minutes during business hours (9 AM - 5 PM) - * const businessHoursCheck = Schedule.cron("0,15,30,45 9-17 * * 1-5") - * - * const businessProgram = Effect.gen(function*() { - * const statuses = ["healthy", "healthy", "degraded", "healthy"] as const - * let index = 0 - * - * yield* Effect.repeat( - * Effect.gen(function*() { - * yield* Console.log("Business hours health check...") - * const status = statuses[index++] - * yield* Console.log(`System status: ${status}`) - * return status - * }), - * businessHoursCheck.pipe( - * Schedule.take(4) // Demo with 4 checks - * ) - * ) - * }) - * - * // Run on specific days of the month - * const monthlyInvoice = Schedule.cron("0 10 1,15 * *") // 1st and 15th at 10 AM - * - * const invoiceProgram = Effect.gen(function*() { - * yield* Effect.repeat( - * Effect.gen(function*() { - * yield* Console.log("Processing monthly invoices...") - * const invoiceCount = 72 - * yield* Console.log(`Processed ${invoiceCount} invoices`) - * return { count: invoiceCount, batch: "2024-01-a" } - * }), - * monthlyInvoice.pipe(Schedule.take(1)) - * ) - * }) - * - * // Complex cron with error handling - * const complexCron = Schedule.cron("0 2,4,6 * * *").pipe( - * Schedule.tapOutput((duration) => - * Console.log(`Scheduled to run again in ${duration}`) - * ) - * ) - * - * const robustProgram = Effect.gen(function*() { - * let attempt = 0 - * - * yield* Effect.repeat( - * Effect.gen(function*() { - * attempt++ - * yield* Console.log("Complex scheduled task...") - * if (attempt === 1) { - * return yield* Effect.fail(new ScheduledTaskError({ message: "Scheduled task failed" })) - * } - * return "success" - * }), - * complexCron.pipe(Schedule.take(3)) - * ).pipe( - * Effect.catch((error: unknown) => - * Console.log(`Cron task error: ${String(error)}`) - * ) - * ) - * }) - * ``` - * - * @category constructors - * @since 2.0.0 - */ -export const cron: { - (expression: Cron.Cron): Schedule - (expression: string, tz?: string | DateTime.TimeZone): Schedule -} = (expression: string | Cron.Cron, tz?: string | DateTime.TimeZone) => { - const parsed = Cron.isCron(expression) ? Result.succeed(expression) : Cron.parse(expression, tz) - return fromStep(effect.map(effect.fromResult(parsed), (cron) => (now, _) => - effect.sync(() => { - const next = Cron.next(cron, now).getTime() - const duration = Duration.millis(next - now) - return [duration, duration] - }))) -} - -/** - * Returns a new schedule that outputs the delay between each occurrence. - * - * **Example** (Extracting schedule delays) - * - * ```ts - * import { Console, Effect, Schedule } from "effect" - * - * // Extract delays from an exponential backoff schedule - * const exponentialDelays = Schedule.delays( - * Schedule.exponential("100 millis").pipe(Schedule.take(5)) - * ) - * - * const delayProgram = Effect.gen(function*() { - * yield* Effect.repeat( - * Effect.gen(function*() { - * yield* Console.log("Task executed") - * return "task result" - * }), - * exponentialDelays.pipe( - * Schedule.tapOutput((delay) => - * Console.log(`Waiting ${delay} before next execution`) - * ) - * ) - * ) - * }) - * - * // Monitor delays from a Fibonacci schedule - * const fibonacciDelays = Schedule.delays( - * Schedule.fibonacci("200 millis").pipe(Schedule.take(8)) - * ) - * - * const fibDelayProgram = Effect.gen(function*() { - * yield* Effect.repeat( - * Console.log("Fibonacci task"), - * fibonacciDelays.pipe( - * Schedule.tapOutput((delay) => Console.log(`Fibonacci delay: ${delay}`)) - * ) - * ) - * }) - * - * // Extract delays for analysis or logging - * const analyzeDelays = Schedule.delays( - * Schedule.spaced("1 second").pipe(Schedule.take(3)) - * ).pipe( - * Schedule.tapOutput((delay) => - * Effect.gen(function*() { - * yield* Console.log(`Recorded delay: ${delay}`) - * // In real applications, might send to metrics system - * }) - * ) - * ) - * - * // Combine delays with other schedules for complex timing - * const adaptiveSchedule = Schedule.unfold(100, (delay) => Effect.succeed(delay * 1.5)).pipe( - * Schedule.take(6) - * ) - * - * const adaptiveDelays = Schedule.delays(adaptiveSchedule) - * - * const adaptiveProgram = Effect.gen(function*() { - * yield* Effect.repeat( - * Effect.gen(function*() { - * yield* Console.log("Adaptive task execution") - * return "completed" - * }), - * adaptiveDelays.pipe( - * Schedule.tapOutput((delay) => Console.log(`Adaptive delay: ${delay}`)) - * ) - * ) - * }) - * - * // Use delays to implement custom timing logic - * const customTimingSchedule = Schedule.delays( - * Schedule.exponential("50 millis").pipe(Schedule.take(4)) - * ).pipe( - * Schedule.map((delay) => Effect.succeed(`Next execution in ${delay}`)), - * Schedule.tapOutput((message) => Console.log(message)) - * ) - * ``` - * - * @category constructors - * @since 2.0.0 - */ -export const delays = (self: Schedule): Schedule => - fromStep( - effect.map( - toStep(self), - (step) => (now, input) => - Pull.catchDone( - effect.map(step(now, input), ([_, duration]) => [duration, duration]), - (_) => Cause.done(Duration.zero) - ) - ) - ) - -/** - * Returns a schedule that recurs once after the specified duration. - * - * **When to use** - * - * Use when you need a schedule that recurs once after a fixed delay. - * - * **Details** - * - * The schedule outputs the configured duration for its first recurrence and - * then completes. - * - * **Example** (Recurring once after a duration) - * - * ```ts - * import { Console, Effect, Schedule } from "effect" - * - * const program = Effect.repeat( - * Console.log("runs again after one second"), - * Schedule.duration("1 second") - * ) - * ``` - * - * @see {@link during} for recurring until a duration has elapsed - * - * @category constructors - * @since 2.0.0 - */ -export const duration = (durationInput: Duration.Input): Schedule => { - const duration = Duration.fromInputUnsafe(durationInput) - return fromStepWithMetadata(effect.succeed((meta) => - meta.attempt === 1 - ? effect.succeed([duration, duration]) - : Cause.done(Duration.zero) - )) -} - -/** - * Returns a new `Schedule` that will always recur, but only during the - * specified `duration` of time. - * - * **When to use** - * - * Use to bound a repeating or retrying schedule by elapsed time. - * - * **Example** (Repeating work during a duration) - * - * ```ts - * import { Console, Data, Effect, Schedule } from "effect" - * - * class RetryAttemptError extends Data.TaggedError("RetryAttemptError")<{ readonly message: string }> {} - * - * // Run a task for exactly 5 seconds, regardless of how many iterations - * const fiveSecondSchedule = Schedule.during("5 seconds") - * - * const timedProgram = Effect.gen(function*() { - * yield* Effect.repeat( - * Effect.gen(function*() { - * yield* Console.log("Task executed inside the time window") - * yield* Effect.sleep("500 millis") // Each task takes 500ms - * return "task done" - * }), - * fiveSecondSchedule.pipe( - * Schedule.tapOutput((elapsedDuration) => - * Console.log(`Total elapsed: ${elapsedDuration}`) - * ) - * ) - * ) - * - * yield* Console.log("Time limit reached!") - * }) - * - * // Combine with other schedules for time-bounded execution - * const timeAndCountLimited = Schedule.spaced("1 second").pipe( - * Schedule.both(Schedule.during("10 seconds")), // Stop after 10 seconds OR - * Schedule.both(Schedule.recurs(15)) // 15 attempts, whichever comes first - * ) - * - * // Burst execution within time window - * const burstWindow = Schedule.during("3 seconds") - * - * const burstProgram = Effect.gen(function*() { - * yield* Console.log("Starting burst execution...") - * - * yield* Effect.repeat( - * Effect.gen(function*() { - * yield* Console.log("Burst task") - * return "burst" - * }), - * burstWindow - * ) - * - * yield* Console.log("Burst window completed") - * }) - * - * // Timed retry window - retry for up to 30 seconds - * const timedRetry = Schedule.exponential("200 millis").pipe( - * Schedule.both(Schedule.during("30 seconds")) - * ) - * - * const retryProgram = Effect.gen(function*() { - * let attempt = 0 - * - * const result = yield* Effect.retry( - * Effect.gen(function*() { - * attempt++ - * yield* Console.log(`Retry attempt ${attempt}`) - * - * if (attempt < 4) { - * return yield* Effect.fail(new RetryAttemptError({ message: `Attempt ${attempt} failed` })) - * } - * - * return `Success on attempt ${attempt}` - * }), - * timedRetry - * ) - * - * yield* Console.log(`Result: ${result}`) - * }).pipe( - * Effect.catch((error: unknown) => Console.log(`Timed out: ${String(error)}`)) - * ) - * ``` - * - * @see {@link duration} for one delayed recurrence - * - * @category constructors - * @since 4.0.0 - */ -export const during = (duration: Duration.Input): Schedule => - while_( - elapsed, - ({ output }) => effect.succeed(Duration.isLessThanOrEqualTo(output, Duration.fromInputUnsafe(duration))) - ) - -/** - * Combines two `Schedule`s by recurring if either of the two schedules wants - * to recur, using the minimum of the two durations between recurrences and - * outputting a tuple of the outputs of both schedules. - * - * **When to use** - * - * Use when the combined schedule should continue while at least one schedule still recurs. - * - * **Example** (Combining schedules with either semantics) - * - * ```ts - * import { Console, Data, Effect, Schedule } from "effect" - * - * class RetryAttemptError extends Data.TaggedError("RetryAttemptError")<{ readonly message: string }> {} - * - * // Either continues as long as at least one schedule wants to continue - * const timeBasedSchedule = Schedule.spaced("2 seconds").pipe(Schedule.take(3)) - * const countBasedSchedule = Schedule.recurs(5) - * - * // Continues until both schedules are exhausted (either still wants to recur) - * const eitherSchedule = Schedule.either(timeBasedSchedule, countBasedSchedule) - * // Outputs: [time_result, count_result] tuple - * - * const program = Effect.gen(function*() { - * const results = yield* Effect.repeat( - * Effect.gen(function*() { - * yield* Console.log("Task executed") - * return "task completed" - * }), - * eitherSchedule.pipe( - * Schedule.tapOutput(([timeResult, countResult]) => - * Console.log(`Time: ${timeResult}, Count: ${countResult}`) - * ) - * ) - * ) - * - * yield* Console.log(`Total executions: ${results.length}`) - * }) - * - * // Either with different delay strategies - * const aggressiveRetry = Schedule.exponential("100 millis").pipe( - * Schedule.take(3) - * ) - * const fallbackRetry = Schedule.fixed("5 seconds").pipe(Schedule.take(2)) - * - * // Will use the more aggressive retry until it's exhausted, then fallback - * const combinedRetry = Schedule.either(aggressiveRetry, fallbackRetry) - * - * const retryProgram = Effect.gen(function*() { - * let attempt = 0 - * - * const result = yield* Effect.retry( - * Effect.gen(function*() { - * attempt++ - * yield* Console.log(`Retry attempt ${attempt}`) - * - * if (attempt < 6) { - * return yield* Effect.fail(new RetryAttemptError({ message: `Attempt ${attempt} failed` })) - * } - * - * return `Success on attempt ${attempt}` - * }), - * combinedRetry - * ) - * - * yield* Console.log(`Final result: ${result}`) - * }) - * - * // Either provides union semantics (OR logic) - * // Compare with both, which provides intersection semantics (AND logic) - * ``` - * - * @see {@link both} for continuing only while both schedules still recur - * - * @category combining - * @since 2.0.0 - */ -export const either: { - ( - other: Schedule - ): ( - self: Schedule - ) => Schedule<[Output, Output2], Input & Input2, Error | Error2, Env | Env2> - ( - self: Schedule, - other: Schedule - ): Schedule<[Output, Output2], Input & Input2, Error | Error2, Env | Env2> -} = dual(2, ( - self: Schedule, - other: Schedule -): Schedule<[Output, Output2], Input & Input2, Error | Error2, Env | Env2> => - eitherWith(self, other, (left, right) => [left, right])) - -/** - * Combines two `Schedule`s by recurring if either of the two schedules wants - * to recur, using the minimum of the two durations between recurrences and - * outputting the result of the left schedule (i.e. `self`). - * - * **When to use** - * - * Use when either schedule may keep recurrence going and only the left - * schedule's output is needed. - * - * **Example** (Combining either schedules and keeping the left output) - * - * ```ts - * import { Console, Effect, Schedule } from "effect" - * - * // Combine two schedules with either semantics, keeping left output - * const primarySchedule = Schedule.exponential("100 millis").pipe( - * Schedule.map(() => Effect.succeed("primary-result")), - * Schedule.take(2) - * ) - * const backupSchedule = Schedule.spaced("500 millis").pipe( - * Schedule.map(() => Effect.succeed("backup-result")) - * ) - * - * const combined = Schedule.eitherLeft(primarySchedule, backupSchedule) - * - * const program = Effect.gen(function*() { - * yield* Effect.repeat( - * Effect.gen(function*() { - * yield* Console.log("Task executed") - * return "task-done" - * }), - * combined.pipe(Schedule.take(5)) - * ) - * }) - * ``` - * - * @category combining - * @since 4.0.0 - */ -export const eitherLeft: { - ( - other: Schedule - ): ( - self: Schedule - ) => Schedule - ( - self: Schedule, - other: Schedule - ): Schedule -} = dual(2, ( - self: Schedule, - other: Schedule -): Schedule => eitherWith(self, other, (output) => output)) - -/** - * Combines two `Schedule`s by recurring if either of the two schedules wants - * to recur, using the minimum of the two durations between recurrences and - * outputting the result of the right schedule (i.e. `other`). - * - * **When to use** - * - * Use when either schedule may keep recurrence going and only the right - * schedule's output is needed. - * - * **Example** (Combining either schedules and keeping the right output) - * - * ```ts - * import { Console, Effect, Schedule } from "effect" - * - * // Combine two schedules with either semantics, keeping right output - * const primarySchedule = Schedule.exponential("100 millis").pipe( - * Schedule.map(() => Effect.succeed("primary-result")), - * Schedule.take(2) - * ) - * const backupSchedule = Schedule.spaced("500 millis").pipe( - * Schedule.map(() => Effect.succeed("backup-result")) - * ) - * - * const combined = Schedule.eitherRight(primarySchedule, backupSchedule) - * - * const program = Effect.gen(function*() { - * yield* Effect.repeat( - * Effect.gen(function*() { - * yield* Console.log("Task executed") - * return "task-done" - * }), - * combined.pipe(Schedule.take(5)) - * ) - * }) - * ``` - * - * @category combining - * @since 4.0.0 - */ -export const eitherRight: { - ( - other: Schedule - ): ( - self: Schedule - ) => Schedule - ( - self: Schedule, - other: Schedule - ): Schedule -} = dual(2, ( - self: Schedule, - other: Schedule -): Schedule => eitherWith(self, other, (_, output) => output)) - -/** - * Combines two `Schedule`s by recurring if either of the two schedules wants - * to recur, using the minimum of the two durations between recurrences and - * outputting the result of the combination of both schedule outputs using the - * specified `combine` function. - * - * **When to use** - * - * Use when either schedule may keep recurrence going and their outputs should be - * combined into a custom value. - * - * **Example** (Combining either schedule outputs) - * - * ```ts - * import { Console, Effect, Schedule } from "effect" - * - * // Combine schedules with either semantics and custom combination - * const primarySchedule = Schedule.exponential("100 millis").pipe( - * Schedule.map(() => Effect.succeed("primary")), - * Schedule.take(2) - * ) - * const fallbackSchedule = Schedule.spaced("500 millis").pipe( - * Schedule.map(() => Effect.succeed("fallback")) - * ) - * - * const combined = Schedule.eitherWith( - * primarySchedule, - * fallbackSchedule, - * (primary, fallback) => `${primary}+${fallback}` - * ) - * - * const program = Effect.gen(function*() { - * yield* Effect.repeat( - * Effect.gen(function*() { - * yield* Console.log("Task executed") - * return "task-result" - * }), - * combined.pipe(Schedule.take(5)) - * ) - * }) - * ``` - * - * @category combining - * @since 2.0.0 - */ -export const eitherWith: { - ( - other: Schedule, - combine: (selfOutput: Output, otherOutput: Output2) => Output3 - ): ( - self: Schedule - ) => Schedule - ( - self: Schedule, - other: Schedule, - combine: (selfOutput: Output, otherOutput: Output2) => Output3 - ): Schedule -} = dual(3, ( - self: Schedule, - other: Schedule, - combine: (selfOutput: Output, otherOutput: Output2) => Output3 -): Schedule => - fromStep(effect.map( - effect.zip(toStep(self), toStep(other)), - ([stepLeft, stepRight]) => (now, input) => - Pull.matchEffect(stepLeft(now, input as Input), { - onSuccess: (leftResult) => - stepRight(now, input as Input2).pipe( - effect.map((rightResult) => - [combine(leftResult[0], rightResult[0]), Duration.min(leftResult[1], rightResult[1])] as [ - Output3, - Duration.Duration - ] - ), - Pull.catchDone((rightDone) => - effect.succeed<[Output3, Duration.Duration]>([ - combine(leftResult[0], rightDone as Output2), - leftResult[1] - ]) - ) - ), - onFailure: effect.failCause, - onDone: (leftDone) => - stepRight(now, input as Input2).pipe( - effect.map((rightResult) => - [combine(leftDone, rightResult[0]), rightResult[1]] as [ - Output3, - Duration.Duration - ] - ), - Pull.catchDone((rightDone) => Cause.done(combine(leftDone, rightDone as Output2))) - ) - }) - ))) - -/** - * Schedule that always recurs and returns the total elapsed duration since the - * first recurrence. - * - * **Details** - * - * This schedule never stops and outputs the cumulative time that has passed since the schedule - * started executing. Useful for tracking execution time or implementing time-based logic. - * - * **Example** (Measuring elapsed schedule time) - * - * ```ts - * import { Console, Duration, Effect, Schedule } from "effect" - * - * const program = Effect.gen(function*() { - * yield* Effect.repeat( - * Console.log("Running task..."), - * Schedule.spaced("1 second").pipe( - * Schedule.both(Schedule.elapsed), - * Schedule.tapOutput(([count, duration]) => - * Console.log(`Run ${count}, elapsed: ${Duration.toMillis(duration)}ms`) - * ), - * Schedule.take(5) - * ) - * ) - * }) - * ``` - * - * @category constructors - * @since 2.0.0 - */ -export const elapsed: Schedule = fromStepWithMetadata( - effect.succeed((meta) => effect.succeed([Duration.millis(meta.elapsed), Duration.zero] as const)) -) - -/** - * Schedule that always recurs, but will wait a certain amount between - * repetitions, given by `base * factor.pow(n)`, where `n` is the number of - * repetitions so far. Returns the current duration between recurrences. - * - * **Example** (Retrying with exponential backoff) - * - * ```ts - * import { Console, Data, Effect, Schedule } from "effect" - * - * class RetryFailure extends Data.TaggedError("RetryFailure")<{ readonly message: string }> {} - * - * // Basic exponential backoff with default factor of 2 - * const basicExponential = Schedule.exponential("100 millis") - * // Delays: 100ms, 200ms, 400ms, 800ms, 1600ms, ... - * - * // Custom exponential backoff with factor 1.5 - * const gentleExponential = Schedule.exponential("200 millis", 1.5) - * // Delays: 200ms, 300ms, 450ms, 675ms, 1012ms, ... - * - * // Retry with exponential backoff (limited to 5 attempts) - * const retryPolicy = Schedule.exponential("50 millis").pipe( - * Schedule.both(Schedule.recurs(5)) - * ) - * - * const program = Effect.gen(function*() { - * let attempt = 0 - * - * const result = yield* Effect.retry( - * Effect.gen(function*() { - * attempt++ - * if (attempt < 4) { - * yield* Console.log(`Attempt ${attempt} failed, retrying...`) - * return yield* Effect.fail(new RetryFailure({ message: `Failure ${attempt}` })) - * } - * return `Success on attempt ${attempt}` - * }), - * retryPolicy - * ) - * - * yield* Console.log(`Final result: ${result}`) - * }) - * - * // Will retry with delays: 50ms, 100ms, 200ms before success + * await Effect.runPromise(program) // => [Duration.millis(100), Duration.millis(100)] * ``` * * @category constructors @@ -2030,61 +865,15 @@ export const exponential = ( * * **Example** (Retrying with Fibonacci backoff) * - * ```ts - * import { Console, Data, Effect, Schedule } from "effect" - * - * class RetryAttemptError extends Data.TaggedError("RetryAttemptError")<{ readonly message: string }> {} - * - * // Basic Fibonacci schedule starting with 100ms - * const fibSchedule = Schedule.fibonacci("100 millis") - * // Delays: 100ms, 100ms, 200ms, 300ms, 500ms, 800ms, 1300ms, ... - * - * // Retry with Fibonacci backoff for gradual increase - * const retryWithFib = Effect.gen(function*() { - * let attempt = 0 - * - * const result = yield* Effect.retry( - * Effect.gen(function*() { - * attempt++ - * yield* Console.log(`Attempt ${attempt}`) - * - * if (attempt < 5) { - * return yield* Effect.fail(new RetryAttemptError({ message: `Attempt ${attempt} failed` })) - * } - * - * return `Success on attempt ${attempt}` - * }), - * Schedule.fibonacci("50 millis").pipe( - * Schedule.both(Schedule.recurs(6)), // Maximum 6 retries - * Schedule.tapOutput((delay) => Console.log(`Next retry in ${delay}`)) - * ) - * ) + * ```ts import.meta.vitest + * import { Duration, Effect, Schedule } from "effect" * - * yield* Console.log(`Final result: ${result}`) + * const program = Effect.gen(function*() { + * const step = yield* Schedule.toStep(Schedule.fibonacci("100 millis")) + * return yield* step(0, undefined) * }) * - * // Heartbeat with Fibonacci intervals (starts fast, gets slower) - * const adaptiveHeartbeat = Effect.gen(function*() { - * yield* Console.log("Heartbeat") - * return "pulse" - * }).pipe( - * Effect.repeat( - * Schedule.fibonacci("200 millis").pipe( - * Schedule.take(8) // First 8 heartbeats - * ) - * ) - * ) - * - * // Fibonacci vs exponential comparison - * const compareSchedules = Effect.gen(function*() { - * yield* Console.log("=== Fibonacci Delays ===") - * // 100ms, 100ms, 200ms, 300ms, 500ms, 800ms - * - * yield* Console.log("=== Exponential Delays ===") - * // 100ms, 200ms, 400ms, 800ms, 1600ms, 3200ms - * - * // Fibonacci grows more slowly than exponential - * }) + * await Effect.runPromise(program) // => [Duration.millis(100), Duration.millis(100)] * ``` * * @category constructors @@ -2125,50 +914,15 @@ export const fibonacci = (one: Duration.Input): Schedule => { * * **Example** (Repeating on fixed intervals) * - * ```ts - * import { Console, Effect, Schedule } from "effect" - * - * // Fixed interval schedule - recurs on a one-second cadence - * const everySecond = Schedule.fixed("1 second") - * - * // Health check that runs at fixed intervals - * const healthCheck = Effect.gen(function*() { - * yield* Console.log("Health check") - * yield* Effect.sleep("200 millis") // simulate health check work - * return "healthy" - * }).pipe( - * Effect.repeat(Schedule.fixed("2 seconds").pipe(Schedule.take(5))) - * ) - * - * // Difference between fixed and spaced: - * // - fixed: maintains constant rate regardless of action duration - * // - spaced: waits for the duration AFTER each action completes - * - * const longRunningTask = Effect.gen(function*() { - * yield* Console.log("Task started") - * yield* Effect.sleep("1.5 seconds") // Longer than interval - * yield* Console.log("Task completed") - * return "done" - * }) - * - * // Fixed schedule: if task takes 1.5s but interval is 1s, - * // next execution happens immediately (no pile-up) - * const fixedSchedule = longRunningTask.pipe( - * Effect.repeat(Schedule.fixed("1 second").pipe(Schedule.take(3))) - * ) - * - * // Comparing with spaced (waits 1s AFTER each task) - * const spacedSchedule = longRunningTask.pipe( - * Effect.repeat(Schedule.spaced("1 second").pipe(Schedule.take(3))) - * ) + * ```ts import.meta.vitest + * import { Duration, Effect, Schedule } from "effect" * * const program = Effect.gen(function*() { - * yield* Console.log("=== Fixed Schedule Demo ===") - * yield* fixedSchedule - * - * yield* Console.log("=== Spaced Schedule Demo ===") - * yield* spacedSchedule + * const step = yield* Schedule.toStep(Schedule.fixed("1 second")) + * return yield* step(0, undefined) * }) + * + * await Effect.runPromise(program) // => [0, Duration.seconds(1)] * ``` * * @see {@link spaced} for delaying after each action completes @@ -2201,158 +955,123 @@ export const fixed = (interval: Duration.Input): Schedule => { } /** - * Returns a new `Schedule` that maps the output of this schedule using the - * specified function. + * Returns a new `Schedule` that maps each schedule decision to a new output + * using the full schedule metadata. * - * **Example** (Mapping schedule outputs) + * **Details** * - * ```ts - * import { Console, Effect, Schedule } from "effect" + * The callback receives the schedule input, output, selected delay duration, + * current attempt, and elapsed timing information. Return either a plain value + * or an `Effect` that produces the new output. * - * // Transform schedule output from number to string - * const countSchedule = Schedule.recurs(5).pipe( - * Schedule.map((count) => Effect.succeed(`Execution #${count + 1}`)) - * ) + * **Example** (Mapping schedule outputs) * - * // Map schedule delays to human-readable format - * const readableDelays = Schedule.exponential("100 millis").pipe( - * Schedule.map((duration) => Effect.succeed(`Next retry in ${duration}`)) - * ) + * ```ts import.meta.vitest + * import { Effect, Schedule } from "effect" * - * // Transform numeric output to structured data - * const structuredSchedule = Schedule.spaced("1 second").pipe( - * Schedule.map((recurrence) => Effect.succeed({ - * iteration: recurrence + 1, - * phase: recurrence < 5 ? "warmup" as const : "steady" as const - * })) + * const countSchedule = Schedule.recurs(5).pipe( + * Schedule.map(({ output: count }) => Effect.succeed(`Execution #${count + 1}`)) * ) - * * const program = Effect.gen(function*() { - * const results = yield* Effect.repeat( - * Effect.succeed("task completed"), - * structuredSchedule.pipe( - * Schedule.take(8), - * Schedule.tapOutput((info) => - * Console.log( - * `${info.phase} phase - iteration ${info.iteration}` - * ) - * ) - * ) - * ) - * - * yield* Console.log(`Completed iterations`) + * const step = yield* Schedule.toStep(countSchedule) + * const [output] = yield* step(0, undefined) + * return output * }) * - * // Map with effectful transformation - * const effectfulMap = Schedule.fixed("2 seconds").pipe( - * Schedule.map((count) => - * Effect.gen(function*() { - * yield* Console.log(`Processing count: ${count}`) - * return count * 10 - * }) - * ) - * ) - * - * // Combine mapping with other schedule operations - * const complexSchedule = Schedule.fibonacci("100 millis").pipe( - * Schedule.map((delay) => Effect.succeed(`Delay: ${delay}`)) - * ) + * await Effect.runPromise(program) // => "Execution #1" * ``` * * @category mapping * @since 2.0.0 */ export const map: { - ( - f: (output: Output) => Output2 | Effect - ): ( + ( + f: (metadata: Metadata) => Output2 | Effect + ): ( self: Schedule ) => Schedule ( self: Schedule, - f: (output: Output) => Output2 | Effect + f: (metadata: Metadata) => Output2 | Effect ): Schedule } = dual(2, ( self: Schedule, - f: (output: Output) => Output2 | Effect -): Schedule => { - const handle = Pull.matchEffect({ - onSuccess: ([output, duration]: [Output, Duration.Duration]) => { - const result = f(output) - if (!isEffect(result)) return effect.succeed([result, duration] as [Output2, Duration.Duration]) - return effect.map(result, (output) => [output, duration] as [Output2, Duration.Duration]) - }, - onFailure: effect.failCause, - onDone: (output: Output) => { - const result = f(output) - if (!isEffect(result)) return Cause.done(result as Output2) - return effect.flatMap(result, Cause.done) - } - }) - return fromStep(effect.map(toStep(self), (step) => (now, input) => handle(step(now, input)))) -}) + f: (metadata: Metadata) => Output2 | Effect +): Schedule => + fromStep(effect.map(toStep(self), (step) => { + const meta = metadataFn() + return (now, input) => + Pull.matchEffect(step(now, input), { + onSuccess: ([output, duration]) => { + const result = f({ ...meta(now, input), output, duration }) + if (!isEffect(result)) return effect.succeed([result, duration] as [Output2, Duration.Duration]) + return effect.map(result, (output) => [output, duration] as [Output2, Duration.Duration]) + }, + onFailure: effect.failCause, + onDone: (output) => { + const result = f({ ...meta(now, input), output, duration: Duration.zero }) + if (!isEffect(result)) return Cause.done(result as Output2) + return effect.flatMap(result, Cause.done) + } + }) + }))) /** * Returns a new `Schedule` that modifies the delay of the next recurrence * of the schedule using the specified effectful function. * - * **Example** (Modifying delays from schedule output) + * **Example** (Modifying delays from schedule metadata) * - * ```ts - * import { Console, Duration, Effect, Schedule } from "effect" + * ```ts import.meta.vitest + * import { Duration, Effect, Schedule } from "effect" * - * // Modify delays based on output - increase delay on high iteration counts - * const adaptiveDelay = Schedule.recurs(10).pipe( - * Schedule.modifyDelay((output, delay) => { - * // Double the delay if we're seeing high iteration counts - * return Effect.succeed(output > 5 ? Duration.times(delay, 2) : delay) - * }) + * const schedule = Schedule.spaced("10 millis").pipe( + * Schedule.modifyDelay(({ duration }) => Effect.succeed(Duration.times(duration, 2))) * ) - * * const program = Effect.gen(function*() { - * let counter = 0 - * yield* Effect.repeat( - * Effect.gen(function*() { - * counter++ - * yield* Console.log(`Attempt ${counter}`) - * return counter - * }), - * adaptiveDelay.pipe(Schedule.take(8)) - * ) + * const step = yield* Schedule.toStep(schedule) + * const [, delay] = yield* step(0, undefined) + * return delay * }) + * + * await Effect.runPromise(program) // => Duration.millis(20) * ``` * * @category delays & timeouts * @since 2.0.0 */ export const modifyDelay: { - ( + ( f: ( - output: Output, - delay: Duration.Duration + metadata: Metadata ) => Effect - ): ( + ): ( self: Schedule ) => Schedule ( self: Schedule, f: ( - output: Output, - delay: Duration.Input + metadata: Metadata ) => Effect ): Schedule } = dual(2, ( self: Schedule, f: ( - output: Output, - delay: Duration.Input + metadata: Metadata ) => Effect ): Schedule => - fromStep(effect.map(toStep(self), (step) => (now, input) => - effect.flatMap( - step(now, input), - ([output, delay]) => effect.map(f(output, delay), (delay) => [output, Duration.fromInputUnsafe(delay)]) - )))) + fromStep(effect.map(toStep(self), (step) => { + const meta = metadataFn() + return (now, input) => + effect.flatMap( + step(now, input), + ([output, duration]) => + effect.map(f({ ...meta(now, input), output, duration }), (replacement) => [ + output, + Duration.fromInputUnsafe(replacement) + ]) + ) + }))) /** * Returns a new `Schedule` that randomly adjusts each recurrence delay. @@ -2374,9 +1093,9 @@ export const modifyDelay: { export const jittered = ( self: Schedule ): Schedule => - modifyDelay(self, (_, delay) => + modifyDelay(self, ({ duration }) => effect.map(randomNext, (random) => { - const millis = Duration.toMillis(Duration.fromInputUnsafe(delay)) + const millis = Duration.toMillis(duration) return Duration.millis(millis * 0.8 * (1 - random) + millis * 1.2 * random) })) @@ -2385,25 +1104,19 @@ export const jittered = ( * * **Example** (Passing inputs through as outputs) * - * ```ts - * import { Console, Effect, Schedule } from "effect" + * ```ts import.meta.vitest + * import { Effect, Schedule } from "effect" * - * // Create a schedule that outputs the inputs instead of original outputs * const inputSchedule = Schedule.passthrough( - * Schedule.exponential("100 millis").pipe(Schedule.take(3)) + * Schedule.exponential("100 millis").pipe(Schedule.upTo({ times: 3 })) * ) - * * const program = Effect.gen(function*() { - * let counter = 0 - * yield* Effect.repeat( - * Effect.gen(function*() { - * counter++ - * yield* Console.log(`Task ${counter} executed`) - * return `result-${counter}` - * }), - * inputSchedule - * ) + * const step = yield* Schedule.toStep(inputSchedule) + * const [output] = yield* step(0, "input") + * return output * }) + * + * await Effect.runPromise(program) // => "input" * ``` * * @category mapping @@ -2415,233 +1128,46 @@ export const passthrough = ( 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 { Console, Data, Effect, Schedule } from "effect" - * - * class RetryAttemptError extends Data.TaggedError("RetryAttemptError")<{ readonly message: string }> {} - * - * // Basic recurs - retry at most 3 times - * const maxThreeAttempts = Schedule.recurs(3) - * - * // Retry a failing operation at most 5 times - * const program = Effect.gen(function*() { - * let attempt = 0 - * - * const result = yield* Effect.retry( - * Effect.gen(function*() { - * attempt++ - * yield* Console.log(`Attempt ${attempt}`) - * - * if (attempt < 4) { - * return yield* Effect.fail(new RetryAttemptError({ message: `Attempt ${attempt} failed` })) - * } - * - * return `Success on attempt ${attempt}` - * }), - * Schedule.recurs(5) // Will retry up to 5 times - * ) - * - * yield* Console.log(`Final result: ${result}`) - * }) - * - * // Combining recurs with other schedules for sophisticated retry logic - * const complexRetry = Schedule.exponential("100 millis").pipe( - * Schedule.both(Schedule.recurs(3)) // At most 3 retries - * ) - * - * // Allow ten recurrences after the initial run - * const tenRecurrences = Effect.gen(function*() { - * yield* Console.log("Executing task...") - * return "completed" - * }).pipe( - * Effect.repeat(Schedule.recurs(10)) - * ) - * - * // The schedule outputs the current recurrence count (0-based) - * const countingSchedule = Schedule.recurs(3).pipe( - * Schedule.tapOutput((count) => Console.log(`Execution #${count + 1}`)) - * ) - * ``` - * - * @see {@link take} for limiting an existing schedule - * - * @category constructors - * @since 2.0.0 - */ -export const recurs = (times: number): Schedule => - while_(forever, ({ attempt }) => effect.succeed(attempt <= times)) + onFailure: effect.failCause, + onDone: () => Cause.done(input) + }))) /** - * Returns a new `Schedule` that combines the outputs of the provided schedule - * using the specified effectful `combine` function and starting from the - * specified `initial` state. - * - * **Example** (Reducing schedule outputs) - * - * ```ts - * import { Console, Effect, Schedule } from "effect" + * Returns a `Schedule` which can only be stepped the specified number of + * `times` before it terminates. * - * // Sum up execution counts from a counter schedule - * const sumSchedule = Schedule.reduce( - * Schedule.recurs(5), - * () => 0, // Initial sum - * (sum, count) => Effect.succeed(sum + count) // Add each count to the sum - * ) + * **When to use** * - * const sumProgram = Effect.gen(function*() { - * const finalSum = yield* Effect.repeat( - * Effect.gen(function*() { - * yield* Console.log("Task executed") - * return "task" - * }), - * sumSchedule.pipe( - * Schedule.tapOutput((sum) => Console.log(`Running sum: ${sum}`)) - * ) - * ) - * - * yield* Console.log(`Final sum: ${finalSum}`) - * }) + * Use when you need a counter schedule with no additional delay. * - * // Build a history of execution counts - * const historySchedule = Schedule.reduce( - * Schedule.spaced("1 second").pipe(Schedule.take(4)), - * () => [] as Array, // Initial empty array - * (history, executionNumber) => Effect.succeed([...history, executionNumber]) - * ) + * **Gotchas** * - * const historyProgram = Effect.gen(function*() { - * const timeline = yield* Effect.repeat( - * Effect.gen(function*() { - * yield* Console.log("Recording execution...") - * return "recorded" - * }), - * historySchedule - * ) - * - * yield* Console.log( - * `Execution timeline: ${timeline.join(", ")}` - * ) - * }) + * `recurs(n)` counts schedule recurrences, not the first evaluation of the + * effect being repeated or retried. For retrying, this means one initial + * attempt plus at most `n` retries. * - * // Accumulate metrics with effectful combination - * const metricsAccumulator = Schedule.reduce( - * Schedule.recurs(6), - * () => ({ total: 0, count: 0, max: 0 }), - * (metrics, executionCount) => Effect.succeed({ - * total: metrics.total + executionCount + 1, - * count: metrics.count + 1, - * max: Math.max(metrics.max, executionCount + 1) - * }) - * ) + * **Example** (Limiting recurrences) * - * const metricsProgram = Effect.gen(function*() { - * const finalMetrics = yield* Effect.repeat( - * Effect.gen(function*() { - * yield* Console.log("Processing...") - * return "processed" - * }), - * metricsAccumulator - * ) - * - * const average = finalMetrics.total / finalMetrics.count - * yield* Console.log(`Final metrics: ${finalMetrics.count} executions`) - * yield* Console.log( - * `Average delay: ${average.toFixed(1)}ms, Max delay: ${finalMetrics.max}ms` - * ) - * }) + * ```ts import.meta.vitest + * import { Effect, Schedule } from "effect" + * import { TestClock } from "effect/testing" * - * // Build configuration state over time - * const configBuilder = Schedule.reduce( - * Schedule.fixed("500 millis").pipe(Schedule.take(3)), - * () => ({ retries: 1, timeout: 1000, backoff: 100 }), - * (config, executionNumber) => Effect.succeed({ - * retries: config.retries + 1, - * timeout: config.timeout * 1.5, - * backoff: Math.min(config.backoff * 2, 5000) - * }) + * const executions: Array = [] + * const program = Effect.sync(() => executions.push(executions.length + 1)).pipe( + * Effect.repeat(Schedule.recurs(3)), + * Effect.as(executions) * ) * - * const configProgram = Effect.gen(function*() { - * const finalConfig = yield* Effect.repeat( - * Effect.gen(function*() { - * yield* Console.log("Updating configuration...") - * return "updated" - * }), - * configBuilder.pipe( - * Schedule.tapOutput((config) => - * Console.log( - * `Config: retries=${config.retries}, timeout=${config.timeout}ms` - * ) - * ) - * ) - * ) - * - * yield* Console.log(`Final config: ${JSON.stringify(finalConfig)}`) - * }) + * await Effect.runPromise(Effect.provide(program, TestClock.layer())) // => [1, 2, 3, 4] * ``` * - * @category folding + * @see {@link upTo} for limiting an existing schedule + * + * @category constructors * @since 2.0.0 */ -export const reduce: { - ( - initial: LazyArg, - combine: (state: State, output: Output) => State | Effect - ): ( - self: Schedule - ) => Schedule - ( - self: Schedule, - initial: LazyArg, - combine: (state: State, output: Output) => State | Effect - ): Schedule -} = dual(3, ( - self: Schedule, - initial: LazyArg, - combine: (state: State, output: Output) => State | Effect -): Schedule => - fromStep(effect.map(toStep(self), (step) => { - let state = initial() - return (now, input) => - Pull.matchEffect(step(now, input), { - onSuccess([output, delay]) { - const next = combine(state, output) - if (!isEffect(next)) { - state = next - return effect.succeed([next, delay] as [State, Duration.Duration]) - } - return effect.map(next, (nextState) => { - state = nextState - return [nextState, delay] - }) - }, - onFailure: effect.failCause, - onDone(output) { - const next = combine(state, output) - return isEffect(next) ? effect.flatMap(next, Cause.done) : Cause.done(next) - } - }) - }))) +export const recurs = (times: number): Schedule => + while_(forever, ({ attempt }) => effect.succeed(attempt <= times)) /** * Returns a schedule that recurs continuously, each repetition spaced the @@ -2653,45 +1179,15 @@ export const reduce: { * * **Example** (Repeating with fixed spacing) * - * ```ts - * import { Console, Effect, Schedule } from "effect" - * - * // Basic spaced schedule - runs every 2 seconds - * const everyTwoSeconds = Schedule.spaced("2 seconds") - * - * // Heartbeat that runs indefinitely with fixed spacing - * const heartbeat = Effect.gen(function*() { - * yield* Console.log("Heartbeat") - * }).pipe( - * Effect.repeat(everyTwoSeconds) - * ) - * - * // Limited repeat - run only 5 times with 1-second spacing - * const limitedTask = Effect.gen(function*() { - * yield* Console.log("Executing scheduled task...") - * yield* Effect.sleep("500 millis") // simulate work - * return "Task completed" - * }).pipe( - * Effect.repeat( - * Schedule.spaced("1 second").pipe(Schedule.take(5)) - * ) - * ) - * - * // Simple spaced schedule with limited repetitions - * const limitedSpaced = Schedule.spaced("100 millis").pipe( - * Schedule.both(Schedule.recurs(5)) // at most 5 times - * ) + * ```ts import.meta.vitest + * import { Duration, Effect, Schedule } from "effect" * * const program = Effect.gen(function*() { - * yield* Console.log("Starting spaced execution...") - * - * yield* Effect.repeat( - * Effect.succeed("work item"), - * limitedSpaced - * ) - * - * yield* Console.log("Completed executions") + * const step = yield* Schedule.toStep(Schedule.spaced("2 seconds")) + * return yield* step(0, undefined) * }) + * + * await Effect.runPromise(program) // => [0, Duration.seconds(2)] * ``` * * @see {@link fixed} for recurrence aligned to a regular cadence @@ -2716,23 +1212,20 @@ export const spaced = (duration: Duration.Input): Schedule => { * * **Example** (Tapping schedule metadata) * - * ```ts - * import { Console, Effect, Schedule } from "effect" - * - * const monitoredSchedule = Schedule.exponential("100 millis").pipe( - * Schedule.take(5), - * Schedule.tap((metadata) => - * Console.log( - * `Attempt ${metadata.attempt} produced ${metadata.output} ` + - * `after ${metadata.elapsed}ms; next delay is ${metadata.duration}` - * ) - * ) - * ) + * ```ts import.meta.vitest + * import { Effect, Schedule } from "effect" * - * const program = Effect.retry( - * Effect.fail("transient error"), - * monitoredSchedule + * const attempts: Array = [] + * const monitoredSchedule = Schedule.recurs(2).pipe( + * Schedule.tap((metadata) => Effect.sync(() => attempts.push(metadata.attempt))) * ) + * const program = Effect.gen(function*() { + * const step = yield* Schedule.toStep(monitoredSchedule) + * const [output] = yield* step(0, undefined) + * return { attempts, output } + * }) + * + * await Effect.runPromise(program) // => { attempts: [1], output: 0 } * ``` * * @category sequencing @@ -2759,472 +1252,73 @@ export const tap: { }))) /** - * Returns a new `Schedule` that allows execution of an effectful function for - * every input to the schedule, but does not alter the inputs and outputs of - * the schedule. - * - * **Example** (Tapping retry inputs) - * - * ```ts - * import { Console, Data, Effect, Schedule } from "effect" - * - * class RetryError extends Data.TaggedError("RetryError")<{ readonly message: string }> {} - * - * // Log retry errors for debugging - * const errorLoggingSchedule = Schedule.exponential("100 millis").pipe( - * Schedule.take(3), - * Schedule.tapInput((error: RetryError) => - * Console.log(`Retry triggered by error: ${String(error)}`) - * ) - * ) - * - * const retryProgram = Effect.gen(function*() { - * let attempt = 0 - * - * const result = yield* Effect.retry( - * Effect.gen(function*() { - * attempt++ - * if (attempt < 4) { - * return yield* Effect.fail(new RetryError({ message: `Network timeout on attempt ${attempt}` })) - * } - * return `Success on attempt ${attempt}` - * }), - * errorLoggingSchedule - * ) - * - * yield* Console.log(`Final result: ${result}`) - * }) - * - * // Monitor input frequency for metrics - * const inputMonitoringSchedule = Schedule.spaced("1 second").pipe( - * Schedule.take(5), - * Schedule.tapInput((input: unknown) => - * Effect.gen(function*() { - * yield* Console.log(`Input type: ${typeof input}`) - * // In real applications, might send metrics to monitoring system - * }) - * ) - * ) - * - * // Input validation with side effects - * const validatingSchedule = Schedule.fixed("500 millis").pipe( - * Schedule.take(4), - * Schedule.tapInput((input: any) => - * Effect.gen(function*() { - * if (typeof input === "object" && input !== null) { - * yield* Console.log(`Valid object input: ${JSON.stringify(input)}`) - * } else { - * yield* Console.log(`Warning: Non-object input received: ${input}`) - * } - * }) - * ) - * ) - * - * const validationProgram = Effect.gen(function*() { - * let count = 0 - * - * yield* Effect.repeat( - * Effect.gen(function*() { - * count++ - * yield* Console.log("Task with validation") - * return { data: `sample-${count}` } - * }), - * validatingSchedule - * ) - * }) - * - * // Conditional alerting based on input - * const alertingSchedule = Schedule.exponential("200 millis").pipe( - * Schedule.take(6), - * Schedule.tapInput((error: RetryError) => - * Effect.gen(function*() { - * if (String(error).includes("critical")) { - * yield* Console.log(`Critical error: ${String(error)}`) - * // In real applications, might trigger alerts or notifications - * } else { - * yield* Console.log(`Regular error: ${String(error)}`) - * } - * }) - * ) - * ) - * - * const alertProgram = Effect.gen(function*() { - * let attempt = 0 - * - * yield* Effect.retry( - * Effect.gen(function*() { - * attempt++ - * const isCritical = attempt === 3 - * const errorType = isCritical - * ? "critical database failure" - * : "temporary network issue" - * return yield* Effect.fail(new RetryError({ message: errorType })) - * }), - * alertingSchedule - * ).pipe( - * Effect.catch((error: unknown) => - * Console.log(`All retries exhausted: ${String(error)}`) - * ) - * ) - * }) - * - * // Chain multiple input taps for different purposes - * const comprehensiveSchedule = Schedule.fibonacci("100 millis").pipe( - * Schedule.take(5), - * Schedule.tapInput((error: RetryError) => - * Console.log(`Error occurred: ${error._tag}`) - * ), - * Schedule.tapInput((error: RetryError) => - * String(error).length > 20 - * ? Console.log("Long error message detected") - * : Effect.void - * ) - * ) - * ``` - * - * @category sequencing - * @since 2.0.0 - */ -export const tapInput: { - ( - f: (input: Input) => Effect - ): ( - self: Schedule - ) => Schedule - ( - self: Schedule, - f: (input: Input) => Effect - ): Schedule -} = dual(2, ( - self: Schedule, - f: (input: Input) => Effect -): Schedule => - fromStep(effect.map( - toStep(self), - (step) => (now, input) => effect.andThen(f(input), step(now, input)) - ))) - -/** - * Returns a new `Schedule` that allows execution of an effectful function for - * every output of the schedule, but does not alter the inputs and outputs of - * the schedule. - * - * **Example** (Tapping schedule outputs) - * - * ```ts - * import { Console, Data, Effect, Schedule } from "effect" - * - * class RetryAttemptError extends Data.TaggedError("RetryAttemptError")<{ readonly message: string }> {} - * - * // Log schedule outputs for debugging/monitoring - * const monitoredSchedule = Schedule.exponential("100 millis").pipe( - * Schedule.take(5), - * Schedule.tapOutput((delay) => Console.log(`Next delay will be: ${delay}`)) - * ) - * - * const retryProgram = Effect.gen(function*() { - * let attempt = 0 - * - * const result = yield* Effect.retry( - * Effect.gen(function*() { - * attempt++ - * if (attempt < 4) { - * return yield* Effect.fail(new RetryAttemptError({ message: `Attempt ${attempt} failed` })) - * } - * return `Success on attempt ${attempt}` - * }), - * monitoredSchedule - * ) - * - * yield* Console.log(`Final result: ${result}`) - * }) - * - * // Tap output for metrics collection - * const metricsSchedule = Schedule.spaced("1 second").pipe( - * Schedule.take(10), - * Schedule.tapOutput((executionCount) => - * Effect.gen(function*() { - * // Simulate metrics collection - * yield* Console.log(`Recording metric: execution_count=${executionCount}`) - * // In real code, this might send to monitoring system - * }) - * ) - * ) - * - * // Tap output with conditional side effects - * const alertingSchedule = Schedule.fibonacci("200 millis").pipe( - * Schedule.take(8), - * Schedule.tapOutput((delay) => - * Effect.gen(function*() { - * const delayMs = delay.toString() - * if (delayMs.includes("1000")) { // Alert on delays >= 1 second - * yield* Console.log(`High delay detected: ${delay}`) - * } - * }) - * ) - * ) - * - * const healthCheckProgram = Effect.gen(function*() { - * yield* Effect.repeat( - * Effect.gen(function*() { - * yield* Console.log("Performing health check...") - * return "healthy" - * }), - * alertingSchedule - * ) - * }) - * - * // Chain multiple taps for different purposes - * const comprehensiveSchedule = Schedule.fixed("500 millis").pipe( - * Schedule.take(6), - * Schedule.tapOutput((count) => Console.log(`Execution ${count + 1}`)), - * Schedule.tapOutput((count) => - * count % 3 === 0 - * ? Console.log("Checkpoint reached") - * : Effect.void - * ) - * ) - * ``` - * - * @category sequencing - * @since 2.0.0 - */ -export const tapOutput: { - ( - f: (output: Output) => Effect - ): ( - self: Schedule - ) => Schedule - ( - self: Schedule, - f: (output: Output) => Effect - ): Schedule -} = dual(2, ( - self: Schedule, - f: (output: Output) => Effect -): Schedule => - fromStep(effect.map( - toStep(self), - (step) => (now, input) => effect.tap(step(now, input), ([output]) => f(output)) - ))) - -/** - * Returns a new `Schedule` that takes at most the specified number of outputs - * from the schedule. Once the specified number of outputs is reached, the - * schedule will stop. + * Returns a new `Schedule` that limits an existing schedule by elapsed + * duration, number of outputs, or both. * * **When to use** * - * Use to limit an existing schedule while preserving its output and delay behavior. + * Use to bound an existing schedule while preserving its output and delay + * behavior. When both `duration` and `times` are specified, the schedule + * stops as soon as either limit is reached. * * **Gotchas** * - * `take(n)` limits schedule outputs. When used with repeat or retry, the - * effect is evaluated once before the schedule is stepped, so the total number - * of evaluations can be one greater than the number of outputs taken. - * - * **Example** (Taking a limited number of recurrences) - * - * ```ts - * import { Console, Data, Effect, Schedule } from "effect" + * 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. * - * class RetryAttemptError extends Data.TaggedError("RetryAttemptError")<{ readonly message: string }> {} + * 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. * - * // Limit an infinite schedule to five recurrences - * const limitedHeartbeat = Schedule.spaced("1 second").pipe( - * Schedule.take(5) // Will stop after 5 schedule outputs - * ) - * - * const heartbeatProgram = Effect.gen(function*() { - * yield* Effect.repeat( - * Effect.gen(function*() { - * yield* Console.log("Heartbeat") - * return "pulse" - * }), - * limitedHeartbeat - * ) - * - * yield* Console.log("Heartbeat sequence completed") - * }) - * - * // Limit retry attempts to a specific number - * const limitedRetry = Schedule.exponential("100 millis").pipe( - * Schedule.take(3) // At most 3 retry attempts - * ) - * - * const retryProgram = Effect.gen(function*() { - * let attempt = 0 - * - * const result = yield* Effect.retry( - * Effect.gen(function*() { - * attempt++ - * yield* Console.log(`Attempt ${attempt}`) + * **Example** (Limiting by duration and recurrence count) * - * if (attempt < 5) { // Will fail more than 3 times - * return yield* Effect.fail(new RetryAttemptError({ message: `Attempt ${attempt} failed` })) - * } - * - * return `Success on attempt ${attempt}` - * }), - * limitedRetry - * ) - * - * yield* Console.log(`Result: ${result}`) - * }).pipe( - * Effect.catch((error: unknown) => - * Console.log(`Failed after limited retries: ${String(error)}`) - * ) - * ) + * ```ts import.meta.vitest + * import { Effect, Schedule } from "effect" + * import { TestClock } from "effect/testing" * - * // Combine take with other schedule operations - * const samplingSchedule = Schedule.fixed("500 millis").pipe( - * Schedule.take(10), // Take at most 10 schedule outputs - * Schedule.map((count) => Effect.succeed(`Sample #${count + 1}`)) + * const executions: Array = [] + * const schedule = Schedule.forever.pipe(Schedule.upTo({ times: 2 })) + * const program = Effect.sync(() => executions.push(executions.length + 1)).pipe( + * Effect.repeat(schedule), + * Effect.as(executions) * ) * - * const samplingProgram = Effect.gen(function*() { - * yield* Effect.repeat( - * Effect.gen(function*() { - * const value = "sample" - * yield* Console.log(`Sampled value: ${value}`) - * return value - * }), - * samplingSchedule.pipe( - * Schedule.tapOutput((label) => Console.log(`Completed: ${label}`)) - * ) - * ) - * }) + * await Effect.runPromise(Effect.provide(program, TestClock.layer())) // => [1, 2, 3] * ``` * - * @see {@link recurs} for creating a count-limited schedule - * - * @category taking + * @category filtering * @since 4.0.0 */ -export const take: { - (n: number): ( +export const upTo: { + (options: { + readonly duration?: Duration.Input | undefined + readonly times?: number | undefined + }): ( self: Schedule ) => Schedule ( self: Schedule, - n: number + options: { + readonly duration?: Duration.Input | undefined + readonly times?: number | undefined + } ): Schedule } = dual(2, ( self: Schedule, - n: number -): Schedule => while_(self, ({ attempt }) => effect.succeed(attempt <= n))) - -/** - * Creates a schedule that unfolds a state by repeatedly applying a function, - * outputting the current state and computing the next state. - * - * **Example** (Unfolding schedule state) - * - * ```ts - * import { Console, Effect, Schedule } from "effect" - * - * // Counter schedule that increments by 1 each time - * const counterSchedule = Schedule.unfold(0, (n) => Effect.succeed(n + 1)) - * // Outputs: 0, 1, 2, 3, 4, 5, ... - * - * const countingProgram = Effect.gen(function*() { - * yield* Effect.repeat( - * Effect.gen(function*() { - * yield* Console.log("Task executed") - * return "done" - * }), - * counterSchedule.pipe( - * Schedule.take(5), - * Schedule.tapOutput((count) => Console.log(`Count: ${count}`)) - * ) - * ) - * }) - * - * // Fibonacci sequence schedule - * const fibonacciSchedule = Schedule.unfold( - * [0, 1] as [number, number], - * ([a, b]) => Effect.succeed([b, a + b] as [number, number]) - * ) - * // Outputs: [0,1], [1,1], [1,2], [2,3], [3,5], [5,8], ... - * - * const fibProgram = Effect.gen(function*() { - * yield* Effect.repeat( - * Console.log("Fibonacci step"), - * fibonacciSchedule.pipe( - * Schedule.take(8), - * Schedule.tapOutput(([a, b]) => Console.log(`Fib: ${a}, next: ${b}`)) - * ) - * ) - * }) - * - * // Effectful unfold - exponential backoff with state - * const exponentialState = Schedule.unfold( - * 100, - * (delayMs) => - * Effect.gen(function*() { - * yield* Console.log(`Current delay: ${delayMs}ms`) - * return Math.min(delayMs * 2, 5000) // Cap at 5 seconds - * }) - * ) - * - * // Deterministic delay adjustment schedule - * const adjustedDelaySchedule = Schedule.unfold( - * { delay: 1000, adjustment: 100 }, - * ({ delay, adjustment }) => - * Effect.gen(function*() { - * const nextDelay = Math.max(100, delay + adjustment) - * yield* Console.log(`Adjusted delay: ${nextDelay}ms`) - * return { delay: nextDelay, adjustment: adjustment * -1 } - * }) - * ) - * - * // State machine schedule - * type State = "init" | "warming" | "active" | "cooling" - * const stateMachineSchedule = Schedule.unfold("init" as State, (state) => { - * switch (state) { - * case "init": - * return Effect.succeed("warming" as State) - * case "warming": - * return Effect.succeed("active" as State) - * case "active": - * return Effect.succeed("cooling" as State) - * case "cooling": - * return Effect.succeed("active" as State) - * } - * }) - * - * const stateMachineProgram = Effect.gen(function*() { - * yield* Effect.repeat( - * Effect.gen(function*() { - * yield* Console.log("State machine step") - * return "step" - * }), - * stateMachineSchedule.pipe( - * Schedule.take(10), - * Schedule.tapOutput((state) => Console.log(`State: ${state}`)) - * ) - * ) - * }) - * ``` - * - * @category constructors - * @since 2.0.0 - */ -export const unfold = ( - initial: State, - next: (state: State) => Effect -): Schedule => - fromStep(effect.sync(() => { - let state = initial - return constant(effect.map( - effect.suspend(() => next(state)), - (nextState) => { - const prev = state - state = nextState - return [prev, Duration.zero] as const - } + options: { + readonly duration?: Duration.Input | undefined + readonly times?: number | undefined + } +): Schedule => { + const duration = options.duration === undefined ? undefined : Duration.fromInputUnsafe(options.duration) + return while_(self, ({ attempt, elapsed }) => + effect.succeed( + (options.times === undefined || attempt <= options.times) && + (duration === undefined || Duration.isLessThanOrEqualTo(Duration.millis(elapsed), duration)) )) - })) +}) const while_: { ( @@ -3274,8 +1368,7 @@ export { * `Effect`, preserves the output and delay when it returns * `true`, and stops the schedule when it returns `false`. * - * @see {@link collectWhile} for collecting outputs while using the same predicate - * @see {@link take} for stopping after a fixed number of schedule outputs + * @see {@link upTo} for stopping after a fixed number of schedule outputs * * @category filtering * @since 4.0.0 @@ -3300,21 +1393,15 @@ export { * * **Example** (Repeating on aligned windows) * - * ```ts - * import { Console, Effect, Schedule } from "effect" - * - * // Execute tasks at regular intervals aligned to window boundaries - * const windowSchedule = Schedule.windowed("5 seconds") + * ```ts import.meta.vitest + * import { Duration, Effect, Schedule } from "effect" * * const program = Effect.gen(function*() { - * yield* Effect.repeat( - * Effect.gen(function*() { - * yield* Console.log("Window task executed") - * return "window-task" - * }), - * windowSchedule.pipe(Schedule.take(4)) - * ) + * const step = yield* Schedule.toStep(Schedule.windowed("5 seconds")) + * return yield* step(0, undefined) * }) + * + * await Effect.runPromise(program) // => [0, Duration.seconds(5)] * ``` * * @category constructors @@ -3340,21 +1427,18 @@ export const windowed = (interval: Duration.Input): Schedule => { * * **Example** (Repeating forever) * - * ```ts - * import { Console, Effect, Schedule } from "effect" + * ```ts import.meta.vitest + * import { Effect, Schedule } from "effect" + * import { TestClock } from "effect/testing" * - * // A schedule that runs forever with no delay - * const infiniteSchedule = Schedule.forever + * const executions: Array = [] + * const schedule = Schedule.forever.pipe(Schedule.upTo({ times: 2 })) + * const program = Effect.sync(() => executions.push(executions.length + 1)).pipe( + * Effect.repeat(schedule), + * Effect.as(executions) + * ) * - * const program = Effect.gen(function*() { - * yield* Effect.repeat( - * Effect.gen(function*() { - * yield* Console.log("Running forever...") - * return "continuous-task" - * }), - * infiniteSchedule.pipe(Schedule.take(5)) // Limit for demo - * ) - * }) + * await Effect.runPromise(Effect.provide(program, TestClock.layer())) // => [1, 2, 3] * ``` * * @category constructors @@ -3390,41 +1474,6 @@ export { identity_ as identity } -/** - * Ensures that a schedule's input type extends a given type `T`. - * - * **When to use** - * - * Use when you need a generic helper to prove that an existing schedule can - * consume a required input type without changing runtime behavior. - * - * **Example** (Constraining schedule input types) - * - * ```ts - * import { Schedule } from "effect" - * - * declare const StringInputSchedule: Schedule.Schedule - * declare const NumberInputSchedule: Schedule.Schedule - * - * const satisfiesStringInput = Schedule.satisfiesInputType() - * - * // This works because the schedule input type is string. - * const validSchedule = satisfiesStringInput(StringInputSchedule) - * - * // This would cause a TypeScript compilation error: - * // const invalidSchedule = satisfiesStringInput(NumberInputSchedule) - * ``` - * - * @see {@link setInputType} for adapting an input-agnostic schedule - * - * @category utility types - * @since 4.0.0 - */ -export const satisfiesInputType = () => -( - self: Schedule -): Schedule => self - /** * Sets the input type of the provided schedule without altering its behavior. * @@ -3439,126 +1488,17 @@ export const satisfiesInputType = () => * * **Example** (Setting a schedule input type) * - * ```ts + * ```ts import.meta.vitest * import { Schedule } from "effect" * * const schedule = Schedule.recurs(3).pipe( * Schedule.setInputType() * ) + * Schedule.isSchedule(schedule) // => true * ``` * - * @see {@link satisfiesInputType} for checking an existing input type - * * @category utility types * @since 4.0.0 */ export const setInputType = () => (self: Schedule): Schedule => self - -/** - * Ensures that a schedule's output type extends a given type `T`. - * - * **Details** - * - * This helper is checked at compile time and does not change the schedule's - * runtime behavior. - * - * **Example** (Constraining schedule output types) - * - * ```ts - * import { Schedule } from "effect" - * - * declare const StringOutputSchedule: Schedule.Schedule - * declare const NumberOutputSchedule: Schedule.Schedule - * - * const satisfiesStringOutput = Schedule.satisfiesOutputType() - * - * // This works because the schedule output type is string. - * const validSchedule = satisfiesStringOutput(StringOutputSchedule) - * - * // This would cause a TypeScript compilation error: - * // const invalidSchedule = satisfiesStringOutput(NumberOutputSchedule) - * ``` - * - * @category utility types - * @since 4.0.0 - */ -export const satisfiesOutputType = () => -( - self: Schedule -): Schedule => self - -/** - * Ensures that a schedule's error type extends a given type `T`. - * - * **Details** - * - * This helper is checked at compile time and does not change the schedule's - * runtime behavior. - * - * **Example** (Constraining schedule error types) - * - * ```ts - * import { Data, Schedule } from "effect" - * - * // Create a custom error using Data.TaggedError - * class CustomError extends Data.TaggedError("CustomError")<{ - * message: string - * }> {} - * - * declare const CustomErrorSchedule: Schedule.Schedule - * declare const StringErrorSchedule: Schedule.Schedule - * - * const satisfiesCustomError = Schedule.satisfiesErrorType() - * - * // This works because the schedule error type is CustomError. - * const validSchedule = satisfiesCustomError(CustomErrorSchedule) - * - * // This would cause a TypeScript compilation error: - * // const invalidSchedule = satisfiesCustomError(StringErrorSchedule) - * ``` - * - * @category utility types - * @since 4.0.0 - */ -export const satisfiesErrorType = () => -( - self: Schedule -): Schedule => self - -/** - * Ensures that a schedule's context type extends a given type `T`. - * - * **Details** - * - * This helper is checked at compile time and does not change the schedule's - * runtime behavior. - * - * **Example** (Constraining schedule service types) - * - * ```ts - * import { Schedule } from "effect" - * - * interface Logger { - * readonly log: (message: string) => void - * } - * - * declare const LoggerSchedule: Schedule.Schedule - * declare const NumberSchedule: Schedule.Schedule - * - * const satisfiesLogger = Schedule.satisfiesServicesType() - * - * // This works because the schedule context type is Logger. - * const validSchedule = satisfiesLogger(LoggerSchedule) - * - * // This would cause a TypeScript compilation error: - * // const invalidSchedule = satisfiesLogger(NumberSchedule) - * ``` - * - * @category utility types - * @since 4.0.0 - */ -export const satisfiesServicesType = () => -( - self: Schedule -): Schedule => self diff --git a/repos/effect/packages/effect/src/Scheduler.ts b/repos/effect/packages/effect/src/Scheduler.ts index 4e076de558..376f003827 100644 --- a/repos/effect/packages/effect/src/Scheduler.ts +++ b/repos/effect/packages/effect/src/Scheduler.ts @@ -26,7 +26,7 @@ import type * as Fiber from "./Fiber.ts" * priorities, and decides when fibers should yield control after consuming * their operation budget. * - * @category models + * @category services * @since 2.0.0 */ export interface Scheduler { @@ -72,10 +72,11 @@ export interface SchedulerDispatcher { * The default value creates a `MixedScheduler`. Provide this service to * customize execution mode, task dispatching, or yield behavior. * - * @category references + * @category services * @since 2.0.0 */ export const Scheduler: Context.Reference = Context.Reference("effect/Scheduler", { + fiberCached: true, defaultValue: () => new MixedScheduler() }) @@ -91,6 +92,16 @@ const setImmediate = "setImmediate" in globalThis return (): void => clearTimeout(timer) } +const setMicrotask = (f: () => void) => { + let cancelled = false + queueMicrotask(() => { + if (!cancelled) f() + }) + return (): void => { + cancelled = true + } +} + class PriorityBuckets { buckets: Array<[priority: number, tasks: Array<() => void>]> = [] @@ -135,7 +146,7 @@ class PriorityBuckets { * operation counts to decide when fibers should yield, and is the default * scheduler implementation. * - * @category schedulers + * @category models * @since 2.0.0 */ export class MixedScheduler implements Scheduler { @@ -144,10 +155,10 @@ export class MixedScheduler implements Scheduler { constructor( executionMode: "sync" | "async" = "async", - setImmediateFn: (f: () => void) => () => void = setImmediate + setImmediateFn?: (f: () => void) => () => void ) { this.executionMode = executionMode - this.setImmediate = setImmediateFn + this.setImmediate = setImmediateFn ?? (executionMode === "sync" ? setMicrotask : setImmediate) } /** @@ -252,10 +263,11 @@ class MixedSchedulerDispatcher implements SchedulerDispatcher { * * @see {@link PreventSchedulerYield} for bypassing scheduler yield checks entirely rather than tuning the operation budget * - * @category references + * @category services * @since 4.0.0 */ export const MaxOpsBeforeYield = Context.Reference("effect/Scheduler/MaxOpsBeforeYield", { + fiberCached: true, defaultValue: () => 2048 }) @@ -277,9 +289,10 @@ export const MaxOpsBeforeYield = Context.Reference("effect/Scheduler/Max * @see {@link MaxOpsBeforeYield} for tuning yield frequency without disabling yield checks * @see {@link Scheduler} for providing custom scheduler yield behavior * - * @category references + * @category services * @since 4.0.0 */ export const PreventSchedulerYield = Context.Reference("effect/Scheduler/PreventSchedulerYield", { + fiberCached: true, defaultValue: () => false }) diff --git a/repos/effect/packages/effect/src/Schema.ts b/repos/effect/packages/effect/src/Schema.ts index ac49d9a2c0..28961e24c1 100644 --- a/repos/effect/packages/effect/src/Schema.ts +++ b/repos/effect/packages/effect/src/Schema.ts @@ -19,7 +19,6 @@ 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 type * as Combiner from "./Combiner.ts" import * as Data from "./Data.ts" import * as DateTime from "./DateTime.ts" import type { Differ } from "./Differ.ts" @@ -35,12 +34,13 @@ import { identity, memoize } from "./Function.ts" import * as HashMap_ from "./HashMap.ts" import * as HashSet_ from "./HashSet.ts" import * as core from "./internal/core.ts" +import * as InternalRecord from "./internal/record.ts" import * as InternalAnnotations from "./internal/schema/annotations.ts" -import * as InternalArbitrary from "./internal/schema/arbitrary.ts" -import * as InternalEquivalence from "./internal/schema/equivalence.ts" -import * as InternalStandard from "./internal/schema/representation.ts" import * as InternalSchema from "./internal/schema/schema.ts" -import { SchemaError } from "./internal/schema/schema.ts" +import * as 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 * as JsonPatch from "./JsonPatch.ts" import * as JsonSchema from "./JsonSchema.ts" import { remainder } from "./Number.ts" @@ -51,9 +51,11 @@ 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 { isSchemaError, SchemaError } from "./SchemaError.ts" import * as SchemaGetter from "./SchemaGetter.ts" import * as SchemaIssue from "./SchemaIssue.ts" import * as SchemaParser from "./SchemaParser.ts" @@ -61,7 +63,7 @@ import type * as SchemaRepresentation from "./SchemaRepresentation.ts" import * as SchemaTransformation from "./SchemaTransformation.ts" import type { Assign, Lambda, Mutable, Simplify } from "./Struct.ts" import * as Struct_ from "./Struct.ts" -import * as FastCheck from "./testing/FastCheck.ts" +import type * as FastCheck from "./testing/FastCheck.ts" import type { RequiredKeys, UnionToIntersection } from "./Types.ts" import type { Unify } from "./Unify.ts" @@ -107,8 +109,8 @@ export type ConstructorDefault = "no-default" | "with-default" * Use when passing `disableChecks: true` to skip validation when you trust the data. * - Pass `parseOptions` to control error reporting behavior. * - * @see {@link Bottom.makeEffect} - * @see {@link Bottom.make} + * @see {@link BottomWithoutNew.makeEffect} + * @see {@link BottomWithoutNew.make} * * @category options * @since 3.13.4 @@ -122,22 +124,28 @@ export interface MakeOptions { * Whether to disable validation for the schema. */ readonly disableChecks?: boolean | undefined + + /** @internal */ + readonly "~payload"?: { + readonly token: unknown + readonly value: unknown + } } /** - * The fully-parameterized base interface for all schemas. Exposes all 14 type - * parameters controlling type inference, mutability, optionality, services, and - * transformation behavior. + * The fully-parameterized schema interface without a construct signature. + * Exposes all 14 type parameters controlling type inference, mutability, + * optionality, services, and transformation behavior. * * **When to use** * - * Use when you are writing advanced generic schema utilities or performing - * schema introspection. + * Use as the base for schema interfaces that provide a specialized construct + * signature. * * @category models * @since 4.0.0 */ -export interface Bottom< +export interface BottomWithoutNew< out T, out E, out RD, @@ -195,12 +203,14 @@ export interface Bottom< * **Gotchas** * * Throws an `Error` with the schema issue in its `cause` when validation - * fails. + * fails. Schema validation failures use the generic message + * `"Schema validation failed"`; format the `cause` explicitly with + * `SchemaIssue.makeFormatterDefault()` when human-readable details are needed. * Causes that contain defects, interruptions, or other non-schema reasons * throw with the underlying `Cause` attached instead. * - * @see {@link Bottom.makeOption} — construct synchronously and discard validation details - * @see {@link Bottom.makeEffect} — construct through `Effect` when validation failure should stay in the error channel + * @see {@link BottomWithoutNew.makeOption} — construct synchronously and discard validation details + * @see {@link BottomWithoutNew.makeEffect} — construct through `Effect` when validation failure should stay in the error channel */ make(input: this["~type.make.in"], options?: MakeOptions): this["Type"] /** @@ -223,8 +233,8 @@ export interface Bottom< * that contain defects, interruptions, or other non-schema reasons throw * instead. * - * @see {@link Bottom.make} — construct synchronously when validation failure should throw - * @see {@link Bottom.makeEffect} — construct through `Effect` when validation details should stay in the error channel + * @see {@link BottomWithoutNew.make} — construct synchronously when validation failure should throw + * @see {@link BottomWithoutNew.makeEffect} — construct through `Effect` when validation details should stay in the error channel */ makeOption(input: this["~type.make.in"], options?: MakeOptions): Option_.Option /** @@ -236,38 +246,100 @@ export interface Bottom< * Use when constructor input may fail validation and you want to * compose that failure with other `Effect` operations instead of throwing. * - * @see {@link Bottom.make} — construct synchronously when validation failure should throw - * @see {@link Bottom.makeOption} — construct synchronously and discard validation details + * **Details** + * + * Validation failures are returned directly as `SchemaIssue.Issue` values + * and are not wrapped in `SchemaError`. + * + * @see {@link BottomWithoutNew.make} — construct synchronously when validation failure should throw + * @see {@link BottomWithoutNew.makeOption} — construct synchronously and discard validation details */ - makeEffect(input: this["~type.make.in"], options?: MakeOptions): Effect.Effect + makeEffect(input: this["~type.make.in"], options?: MakeOptions): Effect.Effect +} + +/** + * Fully-parameterized base interface for schemas that can be extended directly + * by TypeScript classes. + * + * **When to use** + * + * Use as the base for concrete schema interfaces whose runtime values support + * `class ... extends schema`. + * + * **Details** + * + * Extends {@link BottomWithoutNew} with a construct signature that accepts `never`. The + * signature enables class extension without making ordinary schemas directly + * constructible. + * + * @see {@link BottomWithoutNew} for the schema protocol without a construct signature + * + * @category utility types + * @since 4.0.0 + */ +export interface Bottom< + out T, + out E, + out RD, + out RE, + out Ast extends SchemaAST.AST, + out Rebuild extends Top, + out TypeMakeIn = T, + out Iso = T, + in out TypeParameters extends ReadonlyArray = readonly [], + out TypeMake = TypeMakeIn, + out TypeMutability extends Mutability = "readonly", + out TypeOptionality extends Optionality = "required", + out TypeConstructorDefault extends ConstructorDefault = "no-default", + out EncodedMutability extends Mutability = "readonly", + out EncodedOptionality extends Optionality = "required" +> extends + BottomWithoutNew< + T, + E, + RD, + RE, + Ast, + Rebuild, + TypeMakeIn, + Iso, + TypeParameters, + TypeMake, + TypeMutability, + TypeOptionality, + TypeConstructorDefault, + EncodedMutability, + EncodedOptionality + > +{ + new(_: never): {} } /** - * Lazy `Bottom` variant for schema implementations that compute their public - * views on demand. + * Lazy `BottomWithoutNew` variant for schema implementations that + * compute their public views on demand. * * **When to use** * - * Use as an implementation base for schema interfaces that must expose - * `Bottom` behavior without forcing TypeScript to eagerly evaluate expensive - * `Type`, `Encoded`, or service views. + * Use as the base for lazy schema interfaces that provide a specialized + * construct signature. * * **Details** * * The laziness is purely type-level; runtime behavior is unchanged. - * `BottomLazy` keeps the structural operations inherited from `Bottom`, but - * erases the expensive schema views to `unknown`. Concrete schema interfaces can - * then redeclare the precise views they expose. This keeps wide schemas such as - * `Struct` and `Union` cheaper when generic code reads a single view, while - * preserving their exact public types. + * `BottomLazyWithoutNew` keeps the structural operations inherited from + * `BottomWithoutNew`, but erases the expensive schema views to + * `unknown`. Concrete schema interfaces can then redeclare the precise views + * they expose. This keeps wide schemas such as `Struct` and `Union` cheaper when + * generic code reads a single view, while preserving their exact public types. * - * @see {@link Bottom} for the fully parameterized schema interface when every + * @see {@link BottomWithoutNew} for the fully parameterized schema interface when every * view must be supplied directly. * * @category utility types * @since 4.0.0 */ -export interface BottomLazy< +export interface BottomLazyWithoutNew< out Ast extends SchemaAST.AST, out Rebuild extends Top, in out TypeParameters extends ReadonlyArray = readonly [], @@ -277,7 +349,7 @@ export interface BottomLazy< out EncodedMutability extends Mutability = "readonly", out EncodedOptionality extends Optionality = "required" > extends - Bottom< + BottomWithoutNew< unknown, unknown, unknown, @@ -296,6 +368,50 @@ export interface BottomLazy< > {} +/** + * 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}. * @@ -339,8 +455,8 @@ export interface declareConstructor` type) * - * ```ts - * import { Effect, Option, Schema, SchemaIssue as Issue, SchemaParser } from "effect" + * ```ts import.meta.vitest + * import { Effect, Schema, SchemaIssue, SchemaParser } from "effect" * * interface Box { * readonly value: A @@ -355,7 +471,7 @@ export interface declareConstructor * (u, ast, options) => { * if (!isBox(u)) { - * return Effect.fail(new SchemaIssue.InvalidType(ast, Option.some(u))) + * return Effect.fail(new SchemaIssue.InvalidType(ast, u, options)) * } * return Effect.map( * SchemaParser.decodeUnknownEffect(itemCodec)(u.value, options), @@ -365,6 +481,7 @@ export interface declareConstructor { value: 1 } * ``` * * @category constructors @@ -416,7 +533,7 @@ export interface declare extends declareConstructor extends declareConstructor "user_123" * ``` * * @see {@link declareConstructor} for creating schemas for parametric types. @@ -441,10 +559,10 @@ export function declare( ): declare { return declareConstructor()( [], - () => (input, ast) => + () => (input, ast, options) => is(input) ? Effect.succeed(input) : - Effect.fail(new SchemaIssue.InvalidType(ast, Option_.some(input))), + Effect.fail(new SchemaIssue.InvalidType(ast, input, options)), annotations ) } @@ -461,7 +579,7 @@ export function declare( * * **Example** (Inspecting all type parameters of a schema) * - * ```ts + * ```ts import.meta.vitest * import { Schema } from "effect" * * const schema = Schema.String @@ -512,15 +630,16 @@ export function revealBottom( * * **Example** (Adding a title and description) * - * ```ts + * ```ts import.meta.vitest * import { Schema } from "effect" * - * const Age = Schema.Number.pipe( + * const Age = Schema.Natural.pipe( * Schema.annotate({ * title: "Age", * description: "A non-negative integer representing age in years" * }) * ) + * Schema.resolveAnnotations(Age)?.title // => "Age" * ``` * * @see {@link annotateEncoded} to annotate the encoded side instead. @@ -544,7 +663,7 @@ export function annotate(annotations: Annotations.Bottom(annotations: Annotations.Bottom "my title" * ``` * * @see {@link annotate} to annotate the type side instead. @@ -580,7 +698,7 @@ export function annotateEncoded(annotations: Annotations.Bottom(annotations: Annotations.Bottom "Username is required" * ``` * * @category annotations @@ -772,7 +891,7 @@ export declare namespace Schema { * * **Example** (Extracting the decoded type) * - * ```ts + * ```ts import.meta.vitest * import { Schema } from "effect" * * const Person = Schema.Struct({ name: Schema.String, age: Schema.Number }) @@ -801,13 +920,13 @@ export declare namespace Schema { * * **Example** (Accepting any schema decoding to `string`) * - * ```ts + * ```ts import.meta.vitest * import { Schema } from "effect" * - * declare function print(schema: Schema.Schema): void + * const accept = (_schema: Schema.Schema): void => {} * - * print(Schema.String) // ok - * print(Schema.NonEmptyString) // ok + * accept(Schema.String) + * accept(Schema.NonEmptyString) * ``` * * @see {@link Codec} — also tracks Encoded, DecodingServices, EncodingServices @@ -832,7 +951,7 @@ export declare namespace Codec { * * **Example** (Extracting the encoded type) * - * ```ts + * ```ts import.meta.vitest * import { Schema } from "effect" * * const schema = Schema.NumberFromString @@ -850,7 +969,7 @@ export declare namespace Codec { * * **Example** (Checking decoding service requirements) * - * ```ts + * ```ts import.meta.vitest * import { Schema } from "effect" * * const schema = Schema.String @@ -868,7 +987,7 @@ export declare namespace Codec { * * **Example** (Checking encoding service requirements) * - * ```ts + * ```ts import.meta.vitest * import { Schema } from "effect" * * const schema = Schema.String @@ -882,31 +1001,6 @@ export declare namespace Codec { export type EncodingServices = S extends { readonly "EncodingServices": infer R } ? R : never } -/** - * A schema that additionally supports optic (lens/prism) operations. - * - * **Details** - * - * `Optic` extends {@link Schema}`` with an `Iso` type that - * describes the isomorphic counterpart used by the optic layer. Crucially, - * decoding and encoding require *no* Effect services (`DecodingServices` and - * `EncodingServices` are both `never`), which means the optic can operate - * purely without an Effect runtime. - * - * Most primitive schemas (e.g. `Schema.String`, `Schema.Number`) implement - * `Optic` automatically. You normally interact with this interface through - * {@link Optic_} utilities rather than constructing it directly. - * - * @category models - * @since 4.0.0 - */ -export interface Optic extends Schema { - readonly "Iso": Iso - readonly "DecodingServices": never - readonly "EncodingServices": never - readonly "Rebuild": Optic -} - /** * A schema that tracks the decoded type `T`, the encoded type `E`, and the * Effect services required during decoding (`RD`) and encoding (`RE`). @@ -918,18 +1012,19 @@ export interface Optic extends Schema { * Most concrete schemas produced by this module implement `Codec`. * * For APIs that only need one direction, prefer the narrower views: - * - {@link ConstraintDecoder}`` — decode-only - * - {@link ConstraintEncoder}`` — encode-only + * - {@link Decoder}`` — decode-only + * - {@link Encoder}`` — encode-only * - {@link Schema}`` — type-only (no encoded representation) * * **Example** (Accepting a codec that decodes to `number` from `string`) * - * ```ts + * ```ts import.meta.vitest * import { Schema } from "effect" * - * declare function serialize(codec: Schema.Codec): string + * const serialize = (codec: Schema.Codec, value: T): string => + * Schema.encodeSync(codec)(value) * - * serialize(Schema.NumberFromString) // ok — decodes number, encoded as string + * serialize(Schema.NumberFromString, 42) // => "42" * ``` * * @see {@link Codec.Encoded} — extract the encoded type @@ -947,6 +1042,52 @@ export interface Codec extends readonly "Rebuild": Codec } +/** + * A schema that tracks the decoded type `T` and the Effect services required + * during decoding (`RD`). + * + * **When to use** + * + * Use when you need to preserve a schema's decoded type and decoding service + * requirements, but do not need to constrain its encoded representation or + * encoding services. + * + * @see {@link Codec} for preserving both decoded and encoded type information. + * @see {@link Encoder} for the encode-only view. + * + * @category models + * @since 4.0.0 + */ +export interface Decoder extends Schema { + readonly "Encoded": unknown + readonly "DecodingServices": RD + readonly "EncodingServices": unknown + readonly "Rebuild": Decoder +} + +/** + * A schema that tracks the encoded type `E` and the Effect services required + * during encoding (`RE`). + * + * **When to use** + * + * Use when you need to preserve a schema's encoded type and encoding service + * requirements, but do not need to constrain its decoded representation or + * decoding services. + * + * @see {@link Codec} for preserving both decoded and encoded type information. + * @see {@link Decoder} for the decode-only view. + * + * @category models + * @since 4.0.0 + */ +export interface Encoder extends Schema { + readonly "Encoded": E + readonly "DecodingServices": unknown + readonly "EncodingServices": RE + readonly "Rebuild": Encoder +} + /** * Returns a codec widened to the full {@link Codec} interface, prompting * TypeScript to infer all four type parameters (`T`, `E`, `RD`, `RE`). @@ -959,7 +1100,7 @@ export interface Codec extends * * **Example** (Recovering encoded type from a schema variable) * - * ```ts + * ```ts import.meta.vitest * import { Schema } from "effect" * * const schema: Schema.Schema = Schema.NumberFromString @@ -976,7 +1117,49 @@ 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 +} + export { + /** + * Returns `true` if `u` is a {@link 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 + */ + isSchemaError, /** * Error thrown (or returned as the error channel value) when schema decoding * or encoding fails. @@ -984,25 +1167,24 @@ export { * **Details** * * The `issue` field contains a structured {@link SchemaIssue.Issue} tree describing - * every validation failure, including the path to the problematic value, - * expected types, and actual values received. `message` renders the issue tree - * as a human-readable string. + * every validation failure, including the path to the problematic value and + * the expected type or constraint. Parsing with `reportInput: true` adds an + * enumerable `input` field to value-bearing issues created by the parser. + * Built-in messages may include reported input. Other issue fields and + * custom annotations or messages are not sanitized. + * `message` renders the issue tree as a human-readable string and can disclose + * retained input. * * Use {@link isSchemaError} to narrow an unknown value to `SchemaError`. * - * **Example** (Catching a SchemaError) + * **Example** (Inspecting a SchemaError) * - * ```ts - * import { Schema } from "effect" + * ```ts import.meta.vitest + * import { Result, Schema } from "effect" * - * try { - * Schema.decodeUnknownSync(Schema.Number)("not a number") - * } catch (err) { - * if (Schema.isSchemaError(err)) { - * console.log(err.message) - * // Expected number, actual "not a number" - * } - * } + * const result = Schema.decodeUnknownResult(Schema.Number)("not a number") + * const message = Result.isFailure(result) ? result.failure.message : "" + * message // => "Expected number" * ``` * * @category errors @@ -1011,30 +1193,6 @@ export { SchemaError } -/** - * Returns `true` if `u` is a {@link SchemaError}. - * - * **Example** (Narrowing Schema errors in a catch block) - * - * ```ts - * import { Schema } from "effect" - * - * try { - * Schema.decodeUnknownSync(Schema.Number)("oops") - * } catch (err) { - * if (Schema.isSchemaError(err)) { - * console.log(err._tag) // "SchemaError" - * } - * } - * ``` - * - * @category guards - * @since 4.0.0 - */ -export function isSchemaError(u: unknown): u is SchemaError { - return Predicate.hasProperty(u, InternalSchema.SchemaErrorTypeId) -} - function makeStandardResult(exit: Exit_.Exit>): StandardSchemaV1.Result { return Exit_.isSuccess(exit) ? exit.value : { issues: [{ message: Cause_.pretty(exit.cause) }] @@ -1054,7 +1212,7 @@ function makeStandardResult(exit: Exit_.Exit>): St * * **Example** (Creating a standard schema from a regular schema) * - * ```ts + * ```ts import.meta.vitest * import { Schema } from "effect" * * // Define custom hook functions for error formatting @@ -1080,7 +1238,7 @@ function makeStandardResult(exit: Exit_.Exit>): St * // Create a standard schema from a regular schema * const PersonSchema = Schema.Struct({ * name: Schema.NonEmptyString, - * age: Schema.Number.check(Schema.isBetween({ minimum: 0, maximum: 150 })) + * age: Schema.Finite.check(Schema.isBetween({ minimum: 0, maximum: 150 })) * }) * * const standardSchema = Schema.toStandardSchemaV1(PersonSchema, { @@ -1092,16 +1250,21 @@ function makeStandardResult(exit: Exit_.Exit>): St * name: "Alice", * age: 30 * }) - * console.log(validResult) // { value: { name: "Alice", age: 30 } } - * * const invalidResult = standardSchema["~standard"].validate({ * name: "", * age: 200 * }) - * console.log(invalidResult) // { issues: [{ path: ["name"], message: "..." }, { path: ["age"], message: "..." }] } + * + * if (validResult instanceof Promise || invalidResult instanceof Promise) { + * throw new Error("Expected synchronous validation") + * } + * if ("value" in validResult) { + * validResult.value // => { name: "Alice", age: 30 } + * } + * invalidResult.issues?.map((issue) => issue.path) // => [["name"], ["age"]] * ``` * - * @category Standard Schema + * @category converting * @since 4.0.0 */ export function toStandardSchemaV1>( @@ -1119,7 +1282,7 @@ export function toStandardSchemaV1>( const parseOptions: SchemaAST.ParseOptions = { errors: "all", ...options?.parseOptions } const formatter = SchemaIssue.makeFormatterStandardSchemaV1(options) const validate: StandardSchemaV1["~standard"]["validate"] = (value: unknown) => { - const scheduler = new Scheduler.MixedScheduler() + const scheduler = new Scheduler.MixedScheduler("sync") const fiber = Effect.runFork( Effect.match(decodeUnknownEffect(value, parseOptions), { onFailure: formatter, @@ -1180,7 +1343,7 @@ function toBaseStandardJSONSchemaV1(self: Constraint, target: StandardJSONSchema * * https://github.com/standard-schema/standard-schema/pull/134 * - * @category Standard Schema + * @category converting * @since 4.0.0 */ export function toStandardJSONSchemaV1( @@ -1228,19 +1391,19 @@ export function toStandardJSONSchemaV1( * * **Example** (Defining a basic type guard) * - * ```ts + * ```ts import.meta.vitest * import { Schema } from "effect" * * const isString = Schema.is(Schema.String) * - * console.log(isString("hello")) // true - * console.log(isString(42)) // false + * isString("hello") // => true + * isString(42) // => false * * // Type narrowing in action * const value: unknown = "hello" * if (isString(value)) { * // value is now typed as string - * console.log(value.toUpperCase()) // "HELLO" + * value.toUpperCase() // => "HELLO" * } * ``` * @@ -1262,6 +1425,9 @@ export const is = SchemaParser.is * * The input is narrowed if the assertion succeeds. If schema validation fails, * the assertion throws an `Error` whose cause is `SchemaIssue.Issue`. + * Schema validation failures use the generic message `"Schema validation failed"`. + * Format the `cause` explicitly with `SchemaIssue.makeFormatterDefault()` when + * human-readable details are needed. * * **Gotchas** * @@ -1271,21 +1437,23 @@ export const is = SchemaParser.is * * **Example** (Asserting and narrowing an input) * - * ```ts - * import { Schema } from "effect" + * ```ts import.meta.vitest + * import { Schema, SchemaIssue } from "effect" * * const input: unknown = "hello" * * // This will pass silently (no return value) and narrow input to string * Schema.asserts(Schema.String, input) - * console.log(input.toUpperCase()) + * input.toUpperCase() // => "HELLO" * * // This will throw an error * try { * const invalid: unknown = 123 * Schema.asserts(Schema.String, invalid) * } catch (error) { - * console.log("Non-string assertion failed as expected") + * if (error instanceof Error) { + * SchemaIssue.isIssue(error.cause) // => true + * } * } * ``` * @@ -1322,10 +1490,19 @@ export function decodeUnknownEffect(schema: S, options?: S input: unknown, options?: SchemaAST.ParseOptions ): Effect.Effect => { - return InternalSchema.mapSchemaIssueEffect(parser(input, options)) + return fromIssueEffect(parser(input, options)) } } +function fromIssueEffect( + self: Effect.Effect +): Effect.Effect { + 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 @@ -1426,10 +1603,16 @@ function runSchemaErrorSync( export function decodeUnknownExit>(schema: S, options?: SchemaAST.ParseOptions) { const parser = SchemaParser.decodeUnknownExit(schema, options) return (input: unknown, options?: SchemaAST.ParseOptions): Exit_.Exit => { - return InternalSchema.mapSchemaIssueExit(parser(input, options)) + return fromIssueExit(parser(input, options)) } } +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 @@ -1689,19 +1872,12 @@ export const decodePromise: >( * * **Example** (Decoding with a transformation schema) * - * ```ts + * ```ts import.meta.vitest * import { Schema } from "effect" * * const NumberFromString = Schema.NumberFromString * - * console.log(Schema.decodeUnknownSync(NumberFromString)("42")) - * // Output: 42 - * - * Schema.decodeUnknownSync(NumberFromString)("not a number") - * // throws SchemaError: NumberFromString - * // └─ Encoded side transformation failure - * // └─ NumberFromString - * // └─ Expected a numeric string, actual "not a number" + * Schema.decodeUnknownSync(NumberFromString)("42") // => 42 * ``` * * @see {@link SchemaParser.decodeUnknownSync} for the adapter that throws an `Error` whose cause is `SchemaIssue.Issue` @@ -1765,13 +1941,12 @@ export const decodeSync: >( * * **Example** (Encoding a value to a string) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Schema } from "effect" * * const NumberFromString = Schema.NumberFromString * - * Effect.runPromise(Schema.encodeUnknownEffect(NumberFromString)(42)).then(console.log) - * // Output: "42" + * await Effect.runPromise(Schema.encodeUnknownEffect(NumberFromString)(42)) // => "42" * ``` * * @see {@link SchemaParser.encodeUnknownEffect} for the adapter that fails with `SchemaIssue.Issue` directly @@ -1785,7 +1960,7 @@ export function encodeUnknownEffect(schema: S, options?: S input: unknown, options?: SchemaAST.ParseOptions ): Effect.Effect => { - return InternalSchema.mapSchemaIssueEffect(parser(input, options)) + return fromIssueEffect(parser(input, options)) } } @@ -1850,7 +2025,7 @@ export const encodeEffect: ( export function encodeUnknownExit>(schema: S, options?: SchemaAST.ParseOptions) { const parser = SchemaParser.encodeUnknownExit(schema, options) return (input: unknown, options?: SchemaAST.ParseOptions): Exit_.Exit => { - return InternalSchema.mapSchemaIssueExit(parser(input, options)) + return fromIssueExit(parser(input, options)) } } @@ -2169,33 +2344,6 @@ export const encodeSync: >( */ export const make: (ast: S["ast"], options?: object) => S = InternalSchema.make -/** - * Transforms a schema into a class that can be extended with `extends`. The - * resulting class inherits the full schema API (e.g. `annotate`) and can define - * static methods that reference `this`. - * - * **Example** (Wrapping a primitive schema) - * - * ```ts - * import { Schema } from "effect" - * - * class MyString extends Schema.asClass(Schema.String) { - * static readonly decodeUnknownSync = Schema.decodeUnknownSync(this) - * } - * - * console.log(MyString.decodeUnknownSync("a")) - * // "a" - * ``` - * - * @category constructors - * @since 4.0.0 - */ -export function asClass(schema: S): S & { new(_: never): {} } { - // oxlint-disable-next-line @typescript-eslint/no-extraneous-class - class Class {} - return Object.setPrototypeOf(Class, schema) -} - /** * Checks whether a value is a `Schema`. * @@ -2246,7 +2394,7 @@ interface optionalKeyLambda extends Lambda { * * **Example** (Creating a struct with optional key) * - * ```ts + * ```ts import.meta.vitest * import { Schema } from "effect" * * const schema = Schema.Struct({ @@ -2313,7 +2461,7 @@ interface optionalLambda extends Lambda { * * **Example** (Defining an optional field accepting undefined) * - * ```ts + * ```ts import.meta.vitest * import { Schema } from "effect" * * const schema = Schema.Struct({ @@ -2328,7 +2476,10 @@ interface optionalLambda extends Lambda { * @category combinators * @since 3.10.0 */ -export const optional = Struct_.lambda((self) => optionalKey(UndefinedOr(self))) +export const optional = Struct_.lambda((self) => { + const schema = UndefinedOr(self) + return make(SchemaAST.optional(self.ast), { schema }) +}) interface requiredLambda extends Lambda { (self: optional): S @@ -2548,12 +2699,12 @@ function isFlip$(schema: Top): schema is flip { * * **Example** (Flipping a number-from-string schema) * - * ```ts + * ```ts import.meta.vitest * import { Schema } from "effect" * * // NumberFromString: decodes string → number * const flipped = Schema.flip(Schema.NumberFromString) - * // flipped: decodes number → string + * Schema.decodeSync(flipped)(42) // => "42" * ``` * * @category transforming @@ -2585,11 +2736,12 @@ export interface Literal * * **Example** (Defining a string literal) * - * ```ts + * ```ts import.meta.vitest * import { Schema } from "effect" * * const schema = Schema.Literal("hello") * // Type: Schema.Literal<"hello"> + * Schema.decodeSync(schema)("hello") // => "hello" * ``` * * @see {@link Literals} for a schema that represents a union of literals. @@ -2717,11 +2869,11 @@ function templateLiteralFromParts(parts: Pa * * **Example** (Defining a URL path pattern) * - * ```ts + * ```ts import.meta.vitest * import { Schema } from "effect" * * const schema = Schema.TemplateLiteral(["/user/", Schema.Number]) - * // matches strings like "/user/123", "/user/42", etc. + * Schema.is(schema)("/user/123") // => true * ``` * * @see {@link TemplateLiteralParser} for a schema that also parses matched parts into a tuple. @@ -2751,7 +2903,7 @@ export declare namespace TemplateLiteralParser { */ export type Type = Parts extends readonly [infer Head, ...infer Tail] ? readonly [ Head extends TemplateLiteral.LiteralPart ? Head : - Head extends Codec ? T + Head extends ConstraintDecoder ? T : never, ...Type ] @@ -2796,11 +2948,11 @@ export interface TemplateLiteralParser exte * * **Example** (Parsing path parameters) * - * ```ts + * ```ts import.meta.vitest * import { Schema } from "effect" * * const schema = Schema.TemplateLiteralParser(["/user/", Schema.NumberFromString]) - * // decodes "/user/42" => readonly ["/user/", 42] + * Schema.decodeSync(schema)("/user/42") // => ["/user/", 42] * ``` * * @see {@link TemplateLiteral} for a validation-only version that keeps the string encoded. @@ -2830,7 +2982,7 @@ export interface Enum * * **Example** (Defining a direction enum) * - * ```ts + * ```ts import.meta.vitest * import { Schema } from "effect" * * enum Direction { @@ -2839,7 +2991,7 @@ export interface Enum * } * * const schema = Schema.Enum(Direction) - * // accepts "Up" or "Down" + * Schema.decodeSync(schema)(Direction.Up) // => "Up" * ``` * * @category constructors @@ -3002,7 +3154,7 @@ export interface Boolean extends Bottom * * **Example** (Defining a specific symbol) * - * ```ts + * ```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. @@ -3190,7 +3343,7 @@ export declare namespace Struct { S extends Side, O extends keyof F = SideOptionalKeys, M extends keyof F = SideMutableKeys - > = [O | M] extends [never] ? ReadonlySide + > = [O | M] extends [never] ? Simplify> : [M] extends [never] ? Simplify, O>> : [O] extends [never] ? Simplify, M>> : Simplify< @@ -3269,7 +3422,7 @@ export declare namespace Struct { type MakeInView< F extends Fields, O extends keyof F = TypeOptionalKeys | TypeConstructorDefaultedKeys - > = [O] extends [never] ? ReadonlyMakeIn : Simplify, O>> + > = [O] extends [never] ? Simplify> : Simplify, O>> /** * Computes the input object type accepted when constructing a struct value. @@ -3305,7 +3458,7 @@ export interface Struct extends BottomLazy extends BottomLazy ["createdAt", "updatedAt", "name", "email"] * ``` */ readonly fields: Fields @@ -3374,7 +3528,7 @@ function makeStruct(ast: SchemaAST.Objects, * * **Example** (Defining a basic struct) * - * ```ts + * ```ts import.meta.vitest * import { Schema } from "effect" * * const Person = Schema.Struct({ @@ -3386,9 +3540,7 @@ function makeStruct(ast: SchemaAST.Objects, * // { readonly name: string; readonly age: number; readonly email?: string } * type Person = typeof Person.Type * - * const alice = Schema.decodeUnknownSync(Person)({ name: "Alice", age: 30 }) - * console.log(alice) - * // { name: 'Alice', age: 30 } + * Schema.decodeUnknownSync(Person)({ name: "Alice", age: 30 }) // => { name: "Alice", age: 30 } * ``` * * @category constructors @@ -3421,7 +3573,7 @@ interface fieldsAssign extends Lambda { * * **Example** (Adding fields to a union of structs) * - * ```ts + * ```ts import.meta.vitest * import { Schema, Tuple } from "effect" * * // Add a new field to all members of a union of structs @@ -3429,6 +3581,7 @@ interface fieldsAssign extends Lambda { * 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 @@ -3476,16 +3629,14 @@ const canonicalPropertyKey = (key: PropertyKey): string | symbol => * * **Example** (Renaming `name` to `full_name` in the encoded form) * - * ```ts + * ```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 } - * const alice = Schema.decodeUnknownSync(Encoded)({ full_name: "Alice", age: 30 }) - * console.log(alice) - * // { name: 'Alice', age: 30 } + * Schema.decodeUnknownSync(Encoded)({ full_name: "Alice", age: 30 }) // => { name: "Alice", age: 30 } * ``` * * @category transforming @@ -3497,8 +3648,8 @@ export function encodeKeys< >(mapping: M) { return function(self: S): encodeKeys { const fields: any = {} - const appliedMapping: any = {} - const reverseMapping: 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]) @@ -3509,7 +3660,7 @@ export function encodeKeys< throw new globalThis.Error(`Duplicate encoded keys: ${formatPropertyKey(encodedKey)}`) } seenEncodedKeys.add(canonical) - fields[encodedKey] = encoded + InternalRecord.assignProperty(fields, encodedKey, encoded) if (hasMapping) { appliedMapping[k] = encodedKey reverseMapping[encodedKey] = k @@ -3537,7 +3688,7 @@ export function encodeKeys< * * **Example** (Adding a computed `fullName` field) * - * ```ts + * ```ts import.meta.vitest * import { Option, Schema } from "effect" * * const Person = Schema.Struct({ first: Schema.String, last: Schema.String }) @@ -3549,8 +3700,7 @@ export function encodeKeys< * ) * * const alice = Schema.decodeUnknownSync(Extended)({ first: "Alice", last: "Smith" }) - * console.log(alice.fullName) - * // Alice Smith + * alice.fullName // => "Alice Smith" * ``` * * @category transforming @@ -3576,7 +3726,7 @@ export function extendTo, const Fields extends S const f = derive[k] const o = f(input) if (Option_.isSome(o)) { - out[k] = o.value + InternalRecord.assignProperty(out, k, o.value) } } return out @@ -3753,9 +3903,16 @@ export interface $Record exten * 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 + * ```ts import.meta.vitest * import { Schema } from "effect" * * const schema = Schema.Record(Schema.String, Schema.Number) @@ -3763,9 +3920,7 @@ export interface $Record exten * // { readonly [x: string]: number } * type R = typeof schema.Type * - * const result = Schema.decodeUnknownSync(schema)({ a: 1, b: 2 }) - * console.log(result) - * // { a: 1, b: 2 } + * Schema.decodeUnknownSync(schema)({ a: 1, b: 2 }) // => { a: 1, b: 2 } * ``` * * @category constructors @@ -3773,18 +3928,9 @@ export interface $Record exten */ export function Record( key: Key, - value: Value, - options?: { - readonly keyValueCombiner: { - readonly decode?: Combiner.Combiner | undefined - readonly encode?: Combiner.Combiner | undefined - } - } + value: Value ): $Record { - const keyValueCombiner = options?.keyValueCombiner?.decode || options?.keyValueCombiner?.encode - ? new SchemaAST.KeyValueCombiner(options.keyValueCombiner.decode, options.keyValueCombiner.encode) - : undefined - return make(SchemaAST.record(key.ast, value.ast, keyValueCombiner), { key, value }) + return make(SchemaAST.record(key.ast, value.ast), { key, value }) } /** @@ -3928,7 +4074,7 @@ export declare namespace StructWithRest { * * **Example** (Checking record compatibility) * - * ```ts + * ```ts import.meta.vitest * import { Schema } from "effect" * * const user = Schema.Struct({ id: Schema.String }) @@ -3999,7 +4145,7 @@ export interface StructWithRest< * * **Example** (Defining structs with string-indexed extra keys) * - * ```ts + * ```ts import.meta.vitest * import { Schema } from "effect" * * const schema = Schema.StructWithRest( @@ -4220,14 +4366,12 @@ function makeTuple(ast: SchemaAST.Arrays, eleme * * **Example** (Defining a pair of string and number) * - * ```ts + * ```ts import.meta.vitest * import { Schema } from "effect" * * const schema = Schema.Tuple([Schema.String, Schema.Number]) * - * const pair = Schema.decodeUnknownSync(schema)(["hello", 42]) - * console.log(pair) - * // [ 'hello', 42 ] + * Schema.decodeUnknownSync(schema)(["hello", 42]) // => ["hello", 42] * ``` * * @category constructors @@ -4398,7 +4542,7 @@ export interface TupleWithRest< * * **Example** (Defining tuples with rest elements) * - * ```ts + * ```ts import.meta.vitest * import { Schema } from "effect" * * // [string, number, ...boolean[]] @@ -4407,9 +4551,7 @@ export interface TupleWithRest< * [Schema.Boolean] * ) * - * const result = Schema.decodeUnknownSync(schema)(["hello", 1, true, false]) - * console.log(result) - * // [ 'hello', 1, true, false ] + * Schema.decodeUnknownSync(schema)(["hello", 1, true, false]) // => ["hello", 1, true, false] * ``` * * @category constructors @@ -4463,14 +4605,12 @@ export { * * **Example** (Defining an array of strings) * - * ```ts + * ```ts import.meta.vitest * import { Schema } from "effect" * * const schema = Schema.Array(Schema.String) * - * const result = Schema.decodeUnknownSync(schema)(["a", "b", "c"]) - * console.log(result) - * // [ 'a', 'b', 'c' ] + * Schema.decodeUnknownSync(schema)(["a", "b", "c"]) // => ["a", "b", "c"] * ``` * * @category constructors @@ -4512,13 +4652,12 @@ interface NonEmptyArrayLambda extends Lambda { * * **Example** (Defining a non-empty array of numbers) * - * ```ts + * ```ts import.meta.vitest * import { Schema } from "effect" * * const schema = Schema.NonEmptyArray(Schema.Number) * - * Schema.decodeUnknownSync(schema)([1, 2, 3]) // ok - * Schema.decodeUnknownSync(schema)([]) // throws + * Schema.decodeUnknownSync(schema)([1, 2, 3]) // => [1, 2, 3] * ``` * * @category constructors @@ -4640,13 +4779,16 @@ interface mutableLambda extends Lambda { * * **Example** (Defining mutable arrays) * - * ```ts + * ```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 @@ -4734,13 +4876,13 @@ function makeUnion>( * * **Example** (Defining a string or number union) * - * ```ts + * ```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 + * Schema.decodeUnknownSync(schema)("hello") // => "hello" + * Schema.decodeUnknownSync(schema)(42) // => 42 * ``` * * @category constructors @@ -4781,11 +4923,11 @@ export interface Literals> * * **Example** (Defining status codes) * - * ```ts + * ```ts import.meta.vitest * import { Schema } from "effect" * * const schema = Schema.Literals(["active", "inactive", "pending"]) - * // accepts "active", "inactive", or "pending" + * Schema.decodeSync(schema)("active") // => "active" * ``` * * @see {@link Literal} for a schema that represents a single literal. @@ -4917,7 +5059,7 @@ export interface suspend extends * * **Example** (Defining recursive tree schemas) * - * ```ts + * ```ts import.meta.vitest * import { Schema } from "effect" * * interface Tree { @@ -4929,6 +5071,7 @@ export interface suspend extends * value: Schema.Number, * children: Schema.Array(Schema.suspend((): Schema.Codec => Tree)) * }) + * Schema.decodeSync(Tree)({ value: 1, children: [] }) // => { value: 1, children: [] } * ``` * * @category constructors @@ -4944,12 +5087,14 @@ export function suspend(f: () => S): suspend { * * **Example** (Adding checks to a schema) * - * ```ts + * ```ts import.meta.vitest * import { Schema } from "effect" * - * const AgeSchema = Schema.Number.pipe( + * 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 @@ -5121,14 +5266,17 @@ export interface middlewareDecoding extends * * **Example** (Logging decode failures) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Schema } from "effect" * + * const events: Array = [] * const Logged = Schema.String.pipe( * Schema.middlewareDecoding((effect) => - * Effect.tapError(effect, (issue) => Effect.log("decode failed", issue)) + * 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 @@ -5187,14 +5335,17 @@ export interface middlewareEncoding extends * * **Example** (Logging encode failures) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Schema } from "effect" * + * const events: Array = [] * const Logged = Schema.String.pipe( * Schema.middlewareEncoding((effect) => - * Effect.tapError(effect, (issue) => Effect.log("encode failed", issue)) + * 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 @@ -5224,12 +5375,13 @@ export function middlewareEncoding( * * **Example** (Returning a default on decode failure) * - * ```ts + * ```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 @@ -5379,7 +5531,7 @@ export interface compose extends * * **Example** (Transforming strings to numbers with a schema transformation) * - * ```ts + * ```ts import.meta.vitest * import { Schema, SchemaGetter } from "effect" * * const NumberFromString = Schema.String.pipe( @@ -5392,8 +5544,7 @@ export interface compose extends * ) * ) * - * const result = Schema.decodeUnknownSync(NumberFromString)("123") - * // result: 123 + * Schema.decodeUnknownSync(NumberFromString)("123") // => 123 * ``` * * @category transforming @@ -5455,7 +5606,7 @@ export function decodeTo "hello" * ``` * * @category transforming - * @since 3.10.0 + * @since 4.0.0 */ export function decode(transformation: { readonly decode: SchemaGetter.Getter @@ -5496,7 +5646,7 @@ export function decode(transformat * * **Example** (Encoding a number back to a string) * - * ```ts + * ```ts import.meta.vitest * import { Schema, SchemaGetter } from "effect" * * const NumberFromString = Schema.Number.pipe( @@ -5505,6 +5655,7 @@ export function decode(transformat * encode: SchemaGetter.transform((n: number) => String(n)) * }) * ) + * Schema.decodeSync(NumberFromString)("42") // => 42 * ``` * * @category transforming @@ -5545,7 +5696,7 @@ export function encodeTo s.toUpperCase()) * }) * ) + * Schema.encodeSync(UpperFromLower)("hello") // => "HELLO" * ``` * * @category transforming - * @since 3.10.0 + * @since 4.0.0 */ export function encode(transformation: { readonly decode: SchemaGetter.Getter @@ -5616,11 +5768,11 @@ export interface withConstructorDefault "anonymous" * ``` * * @category constructors @@ -5640,10 +5791,16 @@ export interface 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 + defaultValue: Effect.Effect ) { return (schema: S): withConstructorDefault => - make(SchemaAST.withConstructorDefault(schema.ast, InternalSchema.mapSchemaErrorEffect(defaultValue)), { schema }) + 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))) } /** @@ -5692,15 +5849,14 @@ export type DecodingDefaultOptions = { * * **Example** (Providing a default for a missing struct key) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Schema } from "effect" * * const MySchema = Schema.Struct({ * name: Schema.String.pipe(Schema.withDecodingDefaultKey(Effect.succeed("anonymous"))) * }) * - * const result = Schema.decodeUnknownSync(MySchema)({}) - * // result: { name: "anonymous" } + * Schema.decodeUnknownSync(MySchema)({}).name // => "anonymous" * ``` * * @see {@link withDecodingDefault} for the value-level variant (key absent **or** `undefined`) @@ -5715,7 +5871,7 @@ export function withDecodingDefaultKey( const encode = options?.encodingStrategy === "omit" ? SchemaGetter.omit() : SchemaGetter.passthrough() return (self: S): withDecodingDefaultKey => { return optionalKey(toEncoded(self)).pipe(decodeTo(self, { - decode: SchemaGetter.withDefault(InternalSchema.mapSchemaErrorEffect(defaultValue)), + decode: SchemaGetter.withDefault(toIssueEffect(defaultValue)), encode })) } @@ -5800,15 +5956,14 @@ export interface withDecodingDefault extends de * * **Example** (Providing a default for an optional field value) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Schema } from "effect" * * const MySchema = Schema.Struct({ * name: Schema.String.pipe(Schema.optional, Schema.withDecodingDefault(Effect.succeed("anonymous"))) * }) * - * const result = Schema.decodeUnknownSync(MySchema)({ name: undefined }) - * // result: { name: "anonymous" } + * Schema.decodeUnknownSync(MySchema)({ name: undefined }).name // => "anonymous" * ``` * * @see {@link withDecodingDefaultKey} for the key-level variant (key absent only, not `undefined`) @@ -5823,7 +5978,7 @@ export function withDecodingDefault( const encode = options?.encodingStrategy === "omit" ? SchemaGetter.omit() : SchemaGetter.passthrough() return (self: S): withDecodingDefault => { return optional(toEncoded(self)).pipe(decodeTo(self, { - decode: SchemaGetter.withDefault(InternalSchema.mapSchemaErrorEffect(defaultValue)), + decode: SchemaGetter.withDefault(toIssueEffect(defaultValue)), encode })) } @@ -5895,14 +6050,14 @@ export interface tag extends withConstructor * * **Example** (Defining a discriminated union tag) * - * ```ts + * ```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 } + * a // => { _tag: "A", value: 42 } * ``` * * @see {@link tagDefaultOmit} to also omit the tag during encoding @@ -5929,7 +6084,7 @@ export function tag(literal: Tag): tag * * **Example** (Omitting tags during encoding) * - * ```ts + * ```ts import.meta.vitest * import { Schema } from "effect" * * const A = Schema.Struct({ @@ -5938,8 +6093,7 @@ export function tag(literal: Tag): tag * }) * * // Encode strips the _tag field - * const encoded = Schema.encodeUnknownSync(A)({ _tag: "A", value: 1 }) - * // encoded: { value: 1 } + * Schema.encodeUnknownSync(A)({ _tag: "A", value: 1 }) // => { value: 1 } * ``` * * @see {@link tag} for the variant that keeps the tag during encoding @@ -5975,7 +6129,7 @@ export type TaggedStruct "A" * ``` * * @category constructors @@ -6028,6 +6183,10 @@ type TaggedUnionUtils< 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 @@ -6062,11 +6221,16 @@ export type toTaggedUnion< > = Union & TaggedUnionUtils /** - * Augments an existing {@link Union} of tagged structs with utility methods keyed by the discriminant field. + * 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 + * ```ts import.meta.vitest * import { Schema } from "effect" * * const A = Schema.TaggedStruct("A", { value: Schema.Number }) @@ -6079,6 +6243,7 @@ export type toTaggedUnion< * 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 @@ -6090,12 +6255,14 @@ export function toTaggedUnion(tag: Tag) { 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, isAnyOf, guards, match }) as any + return Object.assign(self, { cases, discriminants, isAnyOf, guards, match }) as any function walk(schema: Constraint) { const ast = schema.ast @@ -6111,8 +6278,14 @@ export function toTaggedUnion(tag: Tag) { if (sentinels.length > 0) { const literal = sentinels.find((s) => s.key === tag)?.literal if (Predicate.isPropertyKey(literal)) { - cases[literal] = schema - guards[literal] = is(toType(schema)) + 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 } } @@ -6124,12 +6297,16 @@ export function toTaggedUnion(tag: Tag) { if (arguments.length === 1) { const cases = arguments[0] return function(value: any) { - return cases[value[tag]](value) + const key = value[tag] + const handler = Object.hasOwn(cases, key) ? cases[key] : undefined + return handler(value) } } const value = arguments[0] const cases = arguments[1] - return cases[value[tag]](value) + const key = value[tag] + const handler = Object.hasOwn(cases, key) ? cases[key] : undefined + return handler(value) } } } @@ -6176,7 +6353,7 @@ export interface TaggedUnion> extends * * **Example** (Pattern matching a discriminated union) * - * ```ts + * ```ts import.meta.vitest * import { Schema } from "effect" * * const Shape = Schema.TaggedUnion({ @@ -6189,6 +6366,7 @@ export interface TaggedUnion> extends * 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 @@ -6201,7 +6379,9 @@ export function TaggedUnion extends - BottomLazy< + BottomLazyWithoutNew< S["ast"], S["Rebuild"], S["~type.parameters"], @@ -6243,7 +6423,7 @@ export interface Opaque extends * * **Example** (Defining opaque structs) * - * ```ts + * ```ts import.meta.vitest * import { Schema } from "effect" * * class Person extends Schema.Opaque()( @@ -6254,7 +6434,7 @@ export interface Opaque extends * * // Decoded value is Person, not { name: string } * const person = Schema.decodeUnknownSync(Person)({ name: "Alice" }) - * // person: Person + * person.name // => "Alice" * ``` * * @category constructors @@ -6262,9 +6442,7 @@ export interface Opaque extends */ export function Opaque() { return (schema: S): Opaque & Omit => { - // oxlint-disable-next-line @typescript-eslint/no-extraneous-class - class Opaque {} - return Object.setPrototypeOf(Opaque, schema) + return schema as any } } @@ -6284,13 +6462,13 @@ export interface instanceOf extends declare { * * **Example** (Defining a schema for a built-in class) * - * ```ts + * ```ts import.meta.vitest * import { Schema } from "effect" * * const DateSchema = Schema.instanceOf(Date) * * const decoded = Schema.decodeUnknownSync(DateSchema)(new Date("2024-01-01")) - * // decoded: Date + * decoded.toISOString() // => "2024-01-01T00:00:00.000Z" * ``` * * @category constructors @@ -6342,8 +6520,8 @@ export function link() { * * **Example** (Reporting failure at a nested path) * - * ```ts - * import { Schema } from "effect" + * ```ts import.meta.vitest + * import { Result, Schema } from "effect" * * const schema = Schema.Struct({ password: Schema.String, confirmPassword: Schema.String }).check( * Schema.makeFilter((o) => @@ -6353,15 +6531,16 @@ export function link() { * ) * ) * - * console.log(String(Schema.decodeUnknownExit(schema)({ password: "123456", confirmPassword: "1234567" }))) - * // Failure(Cause([Fail(SchemaError: password and confirmPassword must match - * // at ["password"])])) + * 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 { Schema } from "effect" + * ```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) => { @@ -6374,11 +6553,10 @@ export function link() { * }) * ) * - * 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"])])) + * 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 @@ -6397,13 +6575,14 @@ export const makeFilter: ( * **Details** * * - `string`: failure with that string as the message. Produces an - * {@link SchemaIssue.InvalidValue} wrapping the input, with the string used as - * the issue's `message` annotation. - * - {@link SchemaIssue.Issue}: a fully-formed issue, returned as-is. + * {@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}) or a full - * {@link SchemaIssue.Issue}; the result is wrapped in an {@link SchemaIssue.Pointer} - * at the given `path`. + * 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 @@ -6424,8 +6603,8 @@ export type FilterIssue = string | SchemaIssue.Issue | { * - `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} wrapping - * the input, with no custom message. + * - `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 @@ -6456,6 +6635,17 @@ export function makeFilterGroup( 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$|^$" /** @@ -6473,18 +6663,21 @@ const TRIMMED_PATTERN = "^\\S[\\s\\S]*\\S$|^\\S$|^$" * When generating test data with fast-check, this applies a `patterns` * constraint to ensure generated strings match the trimmed pattern. * - * @category String checks + * @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", - meta: { - _tag: "isTrimmed", - regExp: new globalThis.RegExp(TRIMMED_PATTERN) + representation: { + id: "effect/schema/isTrimmed", + payload: null }, + toJsonSchema: () => ({ pattern: regExp.source }), + toCode: () => ({ runtime: "Schema.isTrimmed()" }), arbitrary: { constraint: { patterns: [TRIMMED_PATTERN] @@ -6496,24 +6689,85 @@ export function isTrimmed(annotations?: Annotations.Filter) { } /** - * Validates that a string matches the specified regular expression pattern. - * - * **Details** + * Reviver for persisted `isTrimmed` checks. * - * JSON Schema: + * **When to use** * - * This check corresponds to the `pattern` constraint in JSON Schema. + * Use when reconstructing documents that may contain checks created by {@link isTrimmed}. * - * Arbitrary: + * @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 String checks + * @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 isPattern: (regExp: globalThis.RegExp, annotations?: Annotations.Filter) => SchemaAST.Filter = - SchemaAST.isPattern +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. @@ -6530,10 +6784,33 @@ export const isPattern: (regExp: globalThis.RegExp, annotations?: Annotations.Fi * When generating test data with fast-check, this applies a `patterns` * constraint to ensure generated strings match the number string pattern. * - * @category String checks + * @category validation * @since 4.0.0 */ -export const isStringFinite: (annotations?: Annotations.Filter) => SchemaAST.Filter = SchemaAST.isStringFinite +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 @@ -6549,10 +6826,33 @@ export const isStringFinite: (annotations?: Annotations.Filter) => SchemaAST.Fil * This check corresponds to a `pattern` constraint with the same signed * base-10 integer pattern. * - * @category String checks + * @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 isStringBigInt: (annotations?: Annotations.Filter) => SchemaAST.Filter = SchemaAST.isStringBigInt +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 @@ -6563,10 +6863,33 @@ export const isStringBigInt: (annotations?: Annotations.Filter) => SchemaAST.Fil * 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 String checks + * @category validation * @since 4.0.0 */ -export const isStringSymbol: (annotations?: Annotations.Filter) => SchemaAST.Filter = SchemaAST.isStringSymbol +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. @@ -6609,7 +6932,7 @@ const getUUIDRegExp = (version?: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8): globalThis.RegE * constraint to ensure generated strings match the UUID pattern. * * @see {@link isGUID} for shape-only GUID validation. - * @category String checks + * @category validation * @since 4.0.0 */ export function isUUID(version?: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8, annotations?: Annotations.Filter) { @@ -6618,16 +6941,37 @@ export function isUUID(version?: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8, annotations?: An regExp, { expected: version ? `a UUID v${version}` : "a UUID", - meta: { - _tag: "isUUID", - regExp, - version + 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})$/ /** @@ -6651,7 +6995,7 @@ const GUID_REGEXP = /^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{ * constraint to ensure generated strings match the GUID pattern. * * @see {@link isUUID} for strict UUID validation. - * @category String checks + * @category validation * @since 4.0.0 */ export function isGUID(annotations?: Annotations.Filter) { @@ -6659,15 +7003,35 @@ export function isGUID(annotations?: Annotations.Filter) { GUID_REGEXP, { expected: "a GUID", - meta: { - _tag: "isGUID", - regExp: GUID_REGEXP + 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). @@ -6684,7 +7048,7 @@ export function isGUID(annotations?: Annotations.Filter) { * When generating test data with fast-check, this applies a `patterns` * constraint to ensure generated strings match the ULID pattern. * - * @category String checks + * @category validation * @since 4.0.0 */ export function isULID(annotations?: Annotations.Filter) { @@ -6692,15 +7056,35 @@ export function isULID(annotations?: Annotations.Filter) { return isPattern( regExp, { - meta: { - _tag: "isULID", - 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. * @@ -6716,7 +7100,7 @@ export function isULID(annotations?: Annotations.Filter) { * When generating test data with fast-check, this applies a `patterns` * constraint to ensure generated strings match the Base64 pattern. * - * @category String checks + * @category validation * @since 4.0.0 */ export function isBase64(annotations?: Annotations.Filter) { @@ -6725,15 +7109,35 @@ export function isBase64(annotations?: Annotations.Filter) { regExp, { expected: "a base64 encoded string", - meta: { - _tag: "isBase64", - regExp + 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). @@ -6750,7 +7154,7 @@ export function isBase64(annotations?: Annotations.Filter) { * When generating test data with fast-check, this applies a `patterns` * constraint to ensure generated strings match the Base64URL pattern. * - * @category String checks + * @category validation * @since 4.0.0 */ export function isBase64Url(annotations?: Annotations.Filter) { @@ -6759,42 +7163,62 @@ export function isBase64Url(annotations?: Annotations.Filter) { regExp, { expected: "a base64url encoded string", - meta: { - _tag: "isBase64Url", - regExp + 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** * - * Notes: - * The JSON Schema and arbitrary metadata are built from `^${startsWith}` without - * escaping regexp metacharacters. If the prefix contains regexp syntax, generated - * patterns may not be equivalent to the runtime `startsWith` check. + * RegExp metacharacters in the prefix are escaped in JSON Schema and arbitrary + * metadata so that the generated patterns retain literal `startsWith` semantics. * - * @category String checks + * @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}`, - meta: { - _tag: "isStartsWith", - startsWith, - regExp: new globalThis.RegExp(`^${startsWith}`) + representation: { + id: "effect/schema/isStartsWith", + payload: { startsWith } }, + toJsonSchema: () => ({ pattern: regExp.source }), + toCode: () => ({ runtime: `Schema.isStartsWith(${format(startsWith)})` }), arbitrary: { constraint: { - patterns: [`^${startsWith}`] + patterns: [regExp.source] } }, ...annotations @@ -6802,33 +7226,53 @@ export function isStartsWith(startsWith: string, annotations?: Annotations.Filte ) } +/** + * 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** * - * Notes: - * The JSON Schema and arbitrary metadata are built from `${endsWith}$` without - * escaping regexp metacharacters. If the suffix contains regexp syntax, generated - * patterns may not be equivalent to the runtime `endsWith` check. + * RegExp metacharacters in the suffix are escaped in JSON Schema and arbitrary + * metadata so that the generated patterns retain literal `endsWith` semantics. * - * @category String checks + * @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}`, - meta: { - _tag: "isEndsWith", - endsWith, - regExp: new globalThis.RegExp(`${endsWith}$`) + representation: { + id: "effect/schema/isEndsWith", + payload: { endsWith } }, + toJsonSchema: () => ({ pattern: regExp.source }), + toCode: () => ({ runtime: `Schema.isEndsWith(${format(endsWith)})` }), arbitrary: { constraint: { - patterns: [`${endsWith}$`] + patterns: [regExp.source] } }, ...annotations @@ -6836,33 +7280,54 @@ export function isEndsWith(endsWith: string, annotations?: Annotations.Filter) { ) } +/** + * 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** * - * Notes: - * The JSON Schema and arbitrary metadata use the substring as a raw regexp - * pattern. If the substring contains regexp syntax, generated patterns may not be - * equivalent to the runtime `includes` check. + * RegExp metacharacters in the substring are escaped in JSON Schema and + * arbitrary metadata so that the generated patterns retain literal `includes` + * semantics. * - * @category String checks + * @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}`, - meta: { - _tag: "isIncludes", - includes, - regExp: new globalThis.RegExp(includes) + representation: { + id: "effect/schema/isIncludes", + payload: { includes } }, + toJsonSchema: () => ({ pattern: regExp.source }), + toCode: () => ({ runtime: `Schema.isIncludes(${format(includes)})` }), arbitrary: { constraint: { - patterns: [includes] + patterns: [regExp.source] } }, ...annotations @@ -6870,6 +7335,26 @@ export function isIncludes(includes: string, annotations?: Annotations.Filter) { ) } +/** + * 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]*$" /** @@ -6881,18 +7366,21 @@ const UPPERCASED_PATTERN = "^[^a-z]*$" * such as digits, punctuation, and whitespace. It rejects strings that would * change when uppercased. * - * @category String checks + * @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", - meta: { - _tag: "isUppercased", - regExp: new globalThis.RegExp(UPPERCASED_PATTERN) + representation: { + id: "effect/schema/isUppercased", + payload: null }, + toJsonSchema: () => ({ pattern: regExp.source }), + toCode: () => ({ runtime: "Schema.isUppercased()" }), arbitrary: { constraint: { patterns: [UPPERCASED_PATTERN] @@ -6903,6 +7391,24 @@ export function isUppercased(annotations?: Annotations.Filter) { ) } +/** + * 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]*$" /** @@ -6914,18 +7420,21 @@ const LOWERCASED_PATTERN = "^[^A-Z]*$" * such as digits, punctuation, and whitespace. It rejects strings that would * change when lowercased. * - * @category String checks + * @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", - meta: { - _tag: "isLowercased", - regExp: new globalThis.RegExp(LOWERCASED_PATTERN) + representation: { + id: "effect/schema/isLowercased", + payload: null }, + toJsonSchema: () => ({ pattern: regExp.source }), + toCode: () => ({ runtime: "Schema.isLowercased()" }), arbitrary: { constraint: { patterns: [LOWERCASED_PATTERN] @@ -6936,6 +7445,24 @@ export function isLowercased(annotations?: Annotations.Filter) { ) } +/** + * 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]?.*$" /** @@ -6947,18 +7474,21 @@ const CAPITALIZED_PATTERN = "^[^a-z]?.*$" * Empty strings pass. Strings whose first character has no lowercase form, such * as a digit, punctuation mark, or whitespace, also pass. * - * @category String checks + * @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", - meta: { - _tag: "isCapitalized", - regExp: new globalThis.RegExp(CAPITALIZED_PATTERN) + representation: { + id: "effect/schema/isCapitalized", + payload: null }, + toJsonSchema: () => ({ pattern: regExp.source }), + toCode: () => ({ runtime: "Schema.isCapitalized()" }), arbitrary: { constraint: { patterns: [CAPITALIZED_PATTERN] @@ -6969,6 +7499,24 @@ export function isCapitalized(annotations?: Annotations.Filter) { ) } +/** + * 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]?.*$" /** @@ -6980,18 +7528,21 @@ const UNCAPITALIZED_PATTERN = "^[^A-Z]?.*$" * Empty strings pass. Strings whose first character has no uppercase form, such * as a digit, punctuation mark, or whitespace, also pass. * - * @category String checks + * @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", - meta: { - _tag: "isUncapitalized", - regExp: new globalThis.RegExp(UNCAPITALIZED_PATTERN) + representation: { + id: "effect/schema/isUncapitalized", + payload: null }, + toJsonSchema: () => ({ pattern: regExp.source }), + toCode: () => ({ runtime: "Schema.isUncapitalized()" }), arbitrary: { constraint: { patterns: [UNCAPITALIZED_PATTERN] @@ -7002,6 +7553,42 @@ export function isUncapitalized(annotations?: Annotations.Filter) { ) } +/** + * 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`). * @@ -7017,33 +7604,34 @@ export function isUncapitalized(annotations?: Annotations.Filter) { * When generating test data with fast-check, this applies `noNaN: true` and * `noInfinity: true` constraints to ensure generated numbers are finite. * - * @category Number checks + * @category validation * @since 4.0.0 */ -export function isFinite(annotations?: Annotations.Filter) { - return makeFilter( - (n: number) => globalThis.Number.isFinite(n), - { - expected: "a finite number", - meta: { - _tag: "isFinite" - }, - arbitrary: { - constraint: { - noInfinity: true, - noNaN: true - } - }, - ...annotations - } - ) -} +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 Order checks + * @category validation * @since 4.0.0 */ export function makeIsGreaterThan(options: { @@ -7078,7 +7666,7 @@ export function makeIsGreaterThan(options: { * Creates a greater-than-or-equal-to (`>=`) check for any ordered type from an * `Order.Order` instance. * - * @category Order checks + * @category validation * @since 4.0.0 */ export function makeIsGreaterThanOrEqualTo(options: { @@ -7112,7 +7700,7 @@ export function makeIsGreaterThanOrEqualTo(options: { * Creates a less-than (`<`) check for any ordered type from an `Order.Order` * instance. * - * @category Order checks + * @category validation * @since 4.0.0 */ export function makeIsLessThan(options: { @@ -7147,7 +7735,7 @@ export function makeIsLessThan(options: { * Creates a less-than-or-equal-to (`<=`) check for any ordered type from an * `Order.Order` instance. * - * @category Order checks + * @category validation * @since 4.0.0 */ export function makeIsLessThanOrEqualTo(options: { @@ -7181,7 +7769,7 @@ export function makeIsLessThanOrEqualTo(options: { * Creates an inclusive or exclusive range check for any ordered type from an * `Order.Order` instance. * - * @category Order checks + * @category validation * @since 4.0.0 */ export function makeIsBetween(deriveOptions: { @@ -7237,7 +7825,7 @@ export function makeIsBetween(deriveOptions: { * Creates a divisibility check for any numeric type from a remainder function * and a zero value. * - * @category Numeric checks + * @category validation * @since 4.0.0 */ export function makeIsMultipleOf(options: { @@ -7259,6 +7847,13 @@ export function makeIsMultipleOf(options: { } } +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). * @@ -7274,19 +7869,41 @@ export function makeIsMultipleOf(options: { * `exclusiveMinimum` constraint to ensure generated numbers are greater than * the specified value. * - * @category Number checks + * @category validation * @since 4.0.0 */ export const isGreaterThan = makeIsGreaterThan({ order: Order.Number, annotate: (exclusiveMinimum) => ({ - meta: { - _tag: "isGreaterThan", - 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). @@ -7302,19 +7919,41 @@ export const isGreaterThan = makeIsGreaterThan({ * 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 Number checks + * @category validation * @since 4.0.0 */ export const isGreaterThanOrEqualTo = makeIsGreaterThanOrEqualTo({ order: Order.Number, annotate: (minimum) => ({ - meta: { - _tag: "isGreaterThanOrEqualTo", - 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). * @@ -7330,19 +7969,41 @@ export const isGreaterThanOrEqualTo = makeIsGreaterThanOrEqualTo({ * `exclusiveMaximum` constraint to ensure generated numbers are less than the * specified value. * - * @category Number checks + * @category validation * @since 4.0.0 */ export const isLessThan = makeIsLessThan({ order: Order.Number, annotate: (exclusiveMaximum) => ({ - meta: { - _tag: "isLessThan", - 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). @@ -7358,19 +8019,41 @@ export const isLessThan = makeIsLessThan({ * 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 Number checks + * @category validation * @since 4.0.0 */ export const isLessThanOrEqualTo = makeIsLessThanOrEqualTo({ order: Order.Number, annotate: (maximum) => ({ - meta: { - _tag: "isLessThanOrEqualTo", - 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. @@ -7389,21 +8072,66 @@ export const isLessThanOrEqualTo = makeIsLessThanOrEqualTo({ * `exclusiveMaximum` flags to ensure generated numbers fall within the * specified range. * - * @category Number checks + * @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 { - meta: { - _tag: "isBetween", - ...options - } + 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. * @@ -7418,56 +8146,141 @@ export const isBetween = makeIsBetween({ * When generating test data with fast-check, this applies constraints to ensure * generated numbers are multiples of the specified divisor. * - * @category Number checks - * @since 4.0.0 + * @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 isMultipleOf = makeIsMultipleOf({ - remainder, - zero: 0, - annotate: (divisor) => ({ - expected: `a value that is a multiple of ${divisor}`, - meta: { - _tag: "isMultipleOf", - divisor - } - }) -}) +export const Int: Int = Number.check(isInt()) /** - * Validates that a number is a safe integer (within the safe integer range - * that can be exactly represented in JavaScript). - * - * **Details** + * Type-level representation of {@link Natural}. * - * JSON Schema: + * @category models + * @since 4.0.0 + */ +export interface Natural extends Int { + readonly "Rebuild": Natural +} + +/** + * Schema for non-negative safe integers, including zero. * - * This check corresponds to the `type: "integer"` constraint in JSON Schema. + * **When to use** * - * Arbitrary: + * Use when you need a count, index, or size that cannot be negative. * - * When generating test data with fast-check, this applies an `integer: true` - * constraint to ensure generated numbers are integers. + * @see {@link Int} for safe integers that may be negative * - * @category Integer checks + * @category schemas * @since 4.0.0 */ -export function isInt(annotations?: Annotations.Filter) { - return makeFilter( - (n: number) => globalThis.Number.isSafeInteger(n), - { - expected: "an integer", - meta: { - _tag: "isInt" - }, - arbitrary: { - constraint: { - integer: true - } - }, - ...annotations - } - ) -} +export const Natural: Natural = Int.check(isGreaterThanOrEqualTo(0)) /** * Validates that a number is a 32-bit signed integer (range: -2,147,483,648 to @@ -7485,13 +8298,13 @@ export function isInt(annotations?: Annotations.Filter) { * When generating test data with fast-check, this applies integer and range * constraints to ensure generated numbers are 32-bit signed integers. * - * @category Integer checks + * @category validation * @since 4.0.0 */ export function isInt32(annotations?: Annotations.Filter) { return new SchemaAST.FilterGroup( [ - isInt(annotations), + isInt(), isBetween({ minimum: -2147483648, maximum: 2147483647 }) ], { @@ -7517,7 +8330,7 @@ export function isInt32(annotations?: Annotations.Filter) { * When generating test data with fast-check, this applies integer and range * constraints to ensure generated numbers are 32-bit unsigned integers. * - * @category Integer checks + * @category validation * @since 4.0.0 */ export function isUint32(annotations?: Annotations.Filter) { @@ -7533,41 +8346,15 @@ export function isUint32(annotations?: Annotations.Filter) { ) } -/** - * Validates that a Date object represents a valid date (not an invalid date - * like `new Date("invalid")`). - * - * **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 `valid: true` - * constraint to ensure generated Date objects are valid. - * - * @category Date checks - * @since 4.0.0 - */ -export function isDateValid(annotations?: Annotations.Filter) { - return makeFilter( - (date) => !isNaN(date.getTime()), - { - expected: "a valid date", - meta: { - _tag: "isDateValid" - }, - arbitrary: { - constraint: { - valid: true - } - }, - ...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())})` } /** @@ -7581,17 +8368,22 @@ export function isDateValid(annotations?: Annotations.Filter) { * one millisecond after the specified value to ensure generated Date objects are * greater than it. * - * @category Date checks + * @category validation * @since 4.0.0 */ export const isGreaterThanDate = makeIsGreaterThan({ order: Order.Date, - annotate: (exclusiveMinimum) => ({ - meta: { - _tag: "isGreaterThanDate", - exclusiveMinimum + annotate: (exclusiveMinimum) => { + const encoded = encodeDatePayload(exclusiveMinimum) + return { + representation: { + id: "effect/schema/isGreaterThanDate", + payload: { exclusiveMinimum: encoded } + }, + toJsonSchema: () => ({}), + toCode: () => ({ runtime: `Schema.isGreaterThanDate(${formatDateRuntime(exclusiveMinimum)})` }) } - }) + } }) /** @@ -7611,17 +8403,22 @@ export const isGreaterThanDate = makeIsGreaterThan({ * to ensure generated Date objects are greater than or equal to the specified * date. * - * @category Date checks + * @category validation * @since 4.0.0 */ export const isGreaterThanOrEqualToDate = makeIsGreaterThanOrEqualTo({ order: Order.Date, - annotate: (minimum) => ({ - meta: { - _tag: "isGreaterThanOrEqualToDate", - minimum + annotate: (minimum) => { + const encoded = encodeDatePayload(minimum) + return { + representation: { + id: "effect/schema/isGreaterThanOrEqualToDate", + payload: { minimum: encoded } + }, + toJsonSchema: () => ({}), + toCode: () => ({ runtime: `Schema.isGreaterThanOrEqualToDate(${formatDateRuntime(minimum)})` }) } - }) + } }) /** @@ -7635,17 +8432,22 @@ export const isGreaterThanOrEqualToDate = makeIsGreaterThanOrEqualTo({ * one millisecond before the specified value to ensure generated Date objects * are less than it. * - * @category Date checks + * @category validation * @since 4.0.0 */ export const isLessThanDate = makeIsLessThan({ order: Order.Date, - annotate: (exclusiveMaximum) => ({ - meta: { - _tag: "isLessThanDate", - exclusiveMaximum + annotate: (exclusiveMaximum) => { + const encoded = encodeDatePayload(exclusiveMaximum) + return { + representation: { + id: "effect/schema/isLessThanDate", + payload: { exclusiveMaximum: encoded } + }, + toJsonSchema: () => ({}), + toCode: () => ({ runtime: `Schema.isLessThanDate(${formatDateRuntime(exclusiveMaximum)})` }) } - }) + } }) /** @@ -7665,17 +8467,22 @@ export const isLessThanDate = makeIsLessThan({ * to ensure generated Date objects are less than or equal to the specified * date. * - * @category Date checks + * @category validation * @since 4.0.0 */ export const isLessThanOrEqualToDate = makeIsLessThanOrEqualTo({ order: Order.Date, - annotate: (maximum) => ({ - meta: { - _tag: "isLessThanOrEqualToDate", - maximum + annotate: (maximum) => { + const encoded = encodeDatePayload(maximum) + return { + representation: { + id: "effect/schema/isLessThanOrEqualToDate", + payload: { maximum: encoded } + }, + toJsonSchema: () => ({}), + toCode: () => ({ runtime: `Schema.isLessThanOrEqualToDate(${formatDateRuntime(maximum)})` }) } - }) + } }) /** @@ -7695,17 +8502,33 @@ export const isLessThanOrEqualToDate = makeIsLessThanOrEqualTo({ * constraints to ensure generated Date objects fall within the specified range, * shifting exclusive bounds by one millisecond. * - * @category Date checks + * @category validation * @since 4.0.0 */ export const isBetweenDate = makeIsBetween({ order: Order.Date, - annotate: (options) => ({ - meta: { - _tag: "isBetweenDate", - ...options + 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)} })` + }) + } + } }) /** @@ -7719,17 +8542,22 @@ export const isBetweenDate = makeIsBetween({ * `exclusiveMinimum + 1n` to ensure generated BigInts are greater than the * specified value. * - * @category BigInt checks + * @category validation * @since 4.0.0 */ export const isGreaterThanBigInt = makeIsGreaterThan({ order: Order.BigInt, - annotate: (exclusiveMinimum) => ({ - meta: { - _tag: "isGreaterThanBigInt", - exclusiveMinimum + annotate: (exclusiveMinimum) => { + const encoded = exclusiveMinimum.toString(10) + return { + representation: { + id: "effect/schema/isGreaterThanBigInt", + payload: { exclusiveMinimum: encoded } + }, + toJsonSchema: () => ({}), + toCode: () => ({ runtime: `Schema.isGreaterThanBigInt(${format(exclusiveMinimum)})` }) } - }) + } }) /** @@ -7744,17 +8572,22 @@ export const isGreaterThanBigInt = makeIsGreaterThan({ * to ensure generated BigInt values are greater than or equal to the specified * value. * - * @category BigInt checks + * @category validation * @since 4.0.0 */ export const isGreaterThanOrEqualToBigInt = makeIsGreaterThanOrEqualTo({ order: Order.BigInt, - annotate: (minimum) => ({ - meta: { - _tag: "isGreaterThanOrEqualToBigInt", - minimum + annotate: (minimum) => { + const encoded = minimum.toString(10) + return { + representation: { + id: "effect/schema/isGreaterThanOrEqualToBigInt", + payload: { minimum: encoded } + }, + toJsonSchema: () => ({}), + toCode: () => ({ runtime: `Schema.isGreaterThanOrEqualToBigInt(${format(minimum)})` }) } - }) + } }) /** @@ -7768,17 +8601,22 @@ export const isGreaterThanOrEqualToBigInt = makeIsGreaterThanOrEqualTo({ * `exclusiveMaximum - 1n` to ensure generated BigInts are less than the * specified value. * - * @category BigInt checks + * @category validation * @since 4.0.0 */ export const isLessThanBigInt = makeIsLessThan({ order: Order.BigInt, - annotate: (exclusiveMaximum) => ({ - meta: { - _tag: "isLessThanBigInt", - exclusiveMaximum + annotate: (exclusiveMaximum) => { + const encoded = exclusiveMaximum.toString(10) + return { + representation: { + id: "effect/schema/isLessThanBigInt", + payload: { exclusiveMaximum: encoded } + }, + toJsonSchema: () => ({}), + toCode: () => ({ runtime: `Schema.isLessThanBigInt(${format(exclusiveMaximum)})` }) } - }) + } }) /** @@ -7793,17 +8631,22 @@ export const isLessThanBigInt = makeIsLessThan({ * to ensure generated BigInt values are less than or equal to the specified * value. * - * @category BigInt checks + * @category validation * @since 4.0.0 */ export const isLessThanOrEqualToBigInt = makeIsLessThanOrEqualTo({ order: Order.BigInt, - annotate: (maximum) => ({ - meta: { - _tag: "isLessThanOrEqualToBigInt", - maximum + annotate: (maximum) => { + const encoded = maximum.toString(10) + return { + representation: { + id: "effect/schema/isLessThanOrEqualToBigInt", + payload: { maximum: encoded } + }, + toJsonSchema: () => ({}), + toCode: () => ({ runtime: `Schema.isLessThanOrEqualToBigInt(${format(maximum)})` }) } - }) + } }) /** @@ -7818,23 +8661,39 @@ export const isLessThanOrEqualToBigInt = makeIsLessThanOrEqualTo({ * constraints to ensure generated BigInt values fall within the specified * range. * - * @category BigInt checks + * @category validation * @since 4.0.0 */ export const isBetweenBigInt = makeIsBetween({ order: Order.BigInt, - annotate: (options) => ({ - meta: { - _tag: "isBetweenBigInt", - ...options + 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 BigDecimal checks + * @category validation * @since 4.0.0 */ export const isGreaterThanBigDecimal = makeIsGreaterThan({ @@ -7846,7 +8705,7 @@ export const isGreaterThanBigDecimal = makeIsGreaterThan({ * Validates that a BigDecimal is greater than or equal to the specified value * (inclusive). * - * @category BigDecimal checks + * @category validation * @since 4.0.0 */ export const isGreaterThanOrEqualToBigDecimal = makeIsGreaterThanOrEqualTo({ @@ -7857,7 +8716,7 @@ export const isGreaterThanOrEqualToBigDecimal = makeIsGreaterThanOrEqualTo({ /** * Validates that a BigDecimal is less than the specified value (exclusive). * - * @category BigDecimal checks + * @category validation * @since 4.0.0 */ export const isLessThanBigDecimal = makeIsLessThan({ @@ -7869,7 +8728,7 @@ export const isLessThanBigDecimal = makeIsLessThan({ * Validates that a BigDecimal is less than or equal to the specified value * (inclusive). * - * @category BigDecimal checks + * @category validation * @since 4.0.0 */ export const isLessThanOrEqualToBigDecimal = makeIsLessThanOrEqualTo({ @@ -7885,7 +8744,7 @@ export const isLessThanOrEqualToBigDecimal = makeIsLessThanOrEqualTo({ * The minimum and maximum boundaries are inclusive by default. Pass * `exclusiveMinimum` or `exclusiveMaximum` to exclude either boundary. * - * @category BigDecimal checks + * @category validation * @since 4.0.0 */ export const isBetweenBigDecimal = makeIsBetween({ @@ -7912,14 +8771,16 @@ export const isBetweenBigDecimal = makeIsBetween({ * * **Example** (Checking minimum length) * - * ```ts + * ```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 Length checks + * @category validation * @since 4.0.0 */ export function isMinLength(minLength: number, annotations?: Annotations.Filter) { @@ -7928,11 +8789,13 @@ export function isMinLength(minLength: number, annotations?: Annotations.Filter) (input) => input.length >= minLength, { expected: `a value with a length of at least ${minLength}`, - meta: { - _tag: "isMinLength", - minLength + representation: { + id: "effect/schema/isMinLength", + payload: { minLength } }, - [SchemaAST.STRUCTURAL_ANNOTATION_KEY]: true, + toJsonSchema: ({ type }) => type === "array" ? { minItems: minLength } : { minLength }, + toCode: () => ({ runtime: `Schema.isMinLength(${minLength})` }), + [InternalAnnotations.STRUCTURAL_ANNOTATION_KEY]: true, arbitrary: { constraint: { minLength @@ -7943,6 +8806,26 @@ export function isMinLength(minLength: number, annotations?: Annotations.Filter) ) } +/** + * 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)`. @@ -7959,7 +8842,7 @@ export function isMinLength(minLength: number, annotations?: Annotations.Filter) * When generating test data with fast-check, this applies a `minLength: 1` * constraint to ensure generated strings or arrays are non-empty. * - * @category Length checks + * @category validation * @since 4.0.0 */ export function isNonEmpty(annotations?: Annotations.Filter) { @@ -7983,7 +8866,7 @@ export function isNonEmpty(annotations?: Annotations.Filter) { * constraint to ensure generated strings or arrays have at most the required * length. * - * @category Length checks + * @category validation * @since 4.0.0 */ export function isMaxLength(maxLength: number, annotations?: Annotations.Filter) { @@ -7992,11 +8875,13 @@ export function isMaxLength(maxLength: number, annotations?: Annotations.Filter) (input) => input.length <= maxLength, { expected: `a value with a length of at most ${maxLength}`, - meta: { - _tag: "isMaxLength", - maxLength + representation: { + id: "effect/schema/isMaxLength", + payload: { maxLength } }, - [SchemaAST.STRUCTURAL_ANNOTATION_KEY]: true, + toJsonSchema: ({ type }) => type === "array" ? { maxItems: maxLength } : { maxLength }, + toCode: () => ({ runtime: `Schema.isMaxLength(${maxLength})` }), + [InternalAnnotations.STRUCTURAL_ANNOTATION_KEY]: true, arbitrary: { constraint: { maxLength @@ -8007,6 +8892,26 @@ export function isMaxLength(maxLength: number, annotations?: Annotations.Filter) ) } +/** + * 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. @@ -8024,7 +8929,7 @@ export function isMaxLength(maxLength: number, annotations?: Annotations.Filter) * `maxLength` constraints to ensure generated strings or arrays have a length * within the specified range. * - * @category Length checks + * @category validation * @since 4.0.0 */ export function isLengthBetween(minimum: number, maximum: number, annotations?: Annotations.Filter) { @@ -8036,12 +8941,17 @@ export function isLengthBetween(minimum: number, maximum: number, annotations?: expected: minimum === maximum ? `a value with a length of ${minimum}` : `a value with a length between ${minimum} and ${maximum}`, - meta: { - _tag: "isLengthBetween", - minimum, - maximum + + representation: { + id: "effect/schema/isLengthBetween", + payload: { minimum, maximum } }, - [SchemaAST.STRUCTURAL_ANNOTATION_KEY]: true, + 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, @@ -8053,6 +8963,27 @@ export function isLengthBetween(minimum: number, maximum: number, 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`. @@ -8070,7 +9001,7 @@ export function isLengthBetween(minimum: number, maximum: number, annotations?: * `minLength` constraint. Generators for values with a final `.size`, such as * sets and maps, interpret it as final cardinality. * - * @category Size checks + * @category validation * @since 4.0.0 */ export function isMinSize(minSize: number, annotations?: Annotations.Filter) { @@ -8079,11 +9010,13 @@ export function isMinSize(minSize: number, annotations?: Annotations.Filter) { (input) => input.size >= minSize, { expected: `a value with a size of at least ${minSize}`, - meta: { - _tag: "isMinSize", - minSize + representation: { + id: "effect/schema/isMinSize", + payload: { minSize } }, - [SchemaAST.STRUCTURAL_ANNOTATION_KEY]: true, + toJsonSchema: () => ({}), + toCode: () => ({ runtime: `Schema.isMinSize(${minSize})` }), + [InternalAnnotations.STRUCTURAL_ANNOTATION_KEY]: true, arbitrary: { constraint: { minLength: minSize @@ -8094,6 +9027,26 @@ export function isMinSize(minSize: number, annotations?: Annotations.Filter) { ) } +/** + * 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`. @@ -8111,7 +9064,7 @@ export function isMinSize(minSize: number, annotations?: Annotations.Filter) { * `maxLength` constraint. Generators for values with a final `.size`, such as * sets and maps, interpret it as final cardinality. * - * @category Size checks + * @category validation * @since 4.0.0 */ export function isMaxSize(maxSize: number, annotations?: Annotations.Filter) { @@ -8120,11 +9073,13 @@ export function isMaxSize(maxSize: number, annotations?: Annotations.Filter) { (input) => input.size <= maxSize, { expected: `a value with a size of at most ${maxSize}`, - meta: { - _tag: "isMaxSize", - maxSize + representation: { + id: "effect/schema/isMaxSize", + payload: { maxSize } }, - [SchemaAST.STRUCTURAL_ANNOTATION_KEY]: true, + toJsonSchema: () => ({}), + toCode: () => ({ runtime: `Schema.isMaxSize(${maxSize})` }), + [InternalAnnotations.STRUCTURAL_ANNOTATION_KEY]: true, arbitrary: { constraint: { maxLength: maxSize @@ -8135,6 +9090,26 @@ export function isMaxSize(maxSize: number, annotations?: Annotations.Filter) { ) } +/** + * 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`. @@ -8152,7 +9127,7 @@ export function isMaxSize(maxSize: number, annotations?: Annotations.Filter) { * `minLength` and `maxLength` constraints. Generators for values with a final * `.size`, such as sets and maps, interpret them as final cardinality. * - * @category Size checks + * @category validation * @since 4.0.0 */ export function isSizeBetween(minimum: number, maximum: number, annotations?: Annotations.Filter) { @@ -8164,12 +9139,14 @@ export function isSizeBetween(minimum: number, maximum: number, annotations?: An expected: minimum === maximum ? `a value with a size of ${minimum}` : `a value with a size between ${minimum} and ${maximum}`, - meta: { - _tag: "isSizeBetween", - minimum, - maximum + + representation: { + id: "effect/schema/isSizeBetween", + payload: { minimum, maximum } }, - [SchemaAST.STRUCTURAL_ANNOTATION_KEY]: true, + toJsonSchema: () => ({}), + toCode: () => ({ runtime: `Schema.isSizeBetween(${minimum}, ${maximum})` }), + [InternalAnnotations.STRUCTURAL_ANNOTATION_KEY]: true, arbitrary: { constraint: { minLength: minimum, @@ -8181,6 +9158,27 @@ export function isSizeBetween(minimum: number, maximum: number, annotations?: An ) } +/** + * 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 @@ -8198,7 +9196,7 @@ export function isSizeBetween(minimum: number, maximum: number, annotations?: An * `minLength` constraint. Object generators interpret it as the final number * of own properties. * - * @category Object checks + * @category validation * @since 4.0.0 */ export function isMinProperties(minProperties: number, annotations?: Annotations.Filter) { @@ -8207,11 +9205,13 @@ export function isMinProperties(minProperties: number, annotations?: Annotations (input) => Reflect.ownKeys(input).length >= minProperties, { expected: `a value with at least ${minProperties === 1 ? "1 entry" : `${minProperties} entries`}`, - meta: { - _tag: "isMinProperties", - minProperties + representation: { + id: "effect/schema/isMinProperties", + payload: { minProperties } }, - [SchemaAST.STRUCTURAL_ANNOTATION_KEY]: true, + toJsonSchema: () => ({ minProperties }), + toCode: () => ({ runtime: `Schema.isMinProperties(${minProperties})` }), + [InternalAnnotations.STRUCTURAL_ANNOTATION_KEY]: true, arbitrary: { constraint: { minLength: minProperties @@ -8222,6 +9222,26 @@ export function isMinProperties(minProperties: number, annotations?: 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. @@ -8238,7 +9258,7 @@ export function isMinProperties(minProperties: number, annotations?: Annotations * `maxLength` constraint. Object generators interpret it as the final number * of own properties. * - * @category Object checks + * @category validation * @since 4.0.0 */ export function isMaxProperties(maxProperties: number, annotations?: Annotations.Filter) { @@ -8247,11 +9267,13 @@ export function isMaxProperties(maxProperties: number, annotations?: Annotations (input) => Reflect.ownKeys(input).length <= maxProperties, { expected: `a value with at most ${maxProperties === 1 ? "1 entry" : `${maxProperties} entries`}`, - meta: { - _tag: "isMaxProperties", - maxProperties + representation: { + id: "effect/schema/isMaxProperties", + payload: { maxProperties } }, - [SchemaAST.STRUCTURAL_ANNOTATION_KEY]: true, + toJsonSchema: () => ({ maxProperties }), + toCode: () => ({ runtime: `Schema.isMaxProperties(${maxProperties})` }), + [InternalAnnotations.STRUCTURAL_ANNOTATION_KEY]: true, arbitrary: { constraint: { maxLength: maxProperties @@ -8262,6 +9284,26 @@ export function isMaxProperties(maxProperties: number, annotations?: 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. @@ -8279,7 +9321,7 @@ export function isMaxProperties(maxProperties: number, annotations?: Annotations * `minLength` and `maxLength` constraints. Object generators interpret them as * the final number of own properties. * - * @category Object checks + * @category validation * @since 4.0.0 */ export function isPropertiesLengthBetween(minimum: number, maximum: number, annotations?: Annotations.Filter) { @@ -8291,12 +9333,14 @@ export function isPropertiesLengthBetween(minimum: number, maximum: number, anno expected: minimum === maximum ? `a value with exactly ${minimum === 1 ? "1 entry" : `${minimum} entries`}` : `a value with between ${minimum} and ${maximum} entries`, - meta: { - _tag: "isPropertiesLengthBetween", - minimum, - maximum + + representation: { + id: "effect/schema/isPropertiesLengthBetween", + payload: { minimum, maximum } }, - [SchemaAST.STRUCTURAL_ANNOTATION_KEY]: true, + toJsonSchema: () => ({ minProperties: minimum, maxProperties: maximum }), + toCode: () => ({ runtime: `Schema.isPropertiesLengthBetween(${minimum}, ${maximum})` }), + [InternalAnnotations.STRUCTURAL_ANNOTATION_KEY]: true, arbitrary: { constraint: { minLength: minimum, @@ -8308,6 +9352,27 @@ export function isPropertiesLengthBetween(minimum: number, maximum: number, anno ) } +/** + * 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. @@ -8321,7 +9386,7 @@ export function isPropertiesLengthBetween(minimum: number, maximum: number, anno * For string property names, this corresponds to the `propertyNames` constraint * in JSON Schema. * - * @category Object checks + * @category validation * @since 4.0.0 */ export function isPropertyNames(keySchema: Constraint, annotations?: Annotations.Filter) { @@ -8339,22 +9404,43 @@ export function isPropertyNames(keySchema: Constraint, annotations?: Annotations } } if (Arr.isArrayNonEmpty(issues)) { - return new SchemaIssue.Composite(ast, Option_.some(input), issues) + return new SchemaIssue.Composite(ast, issues, input, options) } return true }, { expected: "an object with property names matching the schema", - meta: { - _tag: "isPropertyNames", - propertyNames: propertyNames.ast + representation: { + id: "effect/schema/isPropertyNames", + payload: null, + schemas: [propertyNames.ast] }, - [SchemaAST.STRUCTURAL_ANNOTATION_KEY]: true, + 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. * @@ -8368,18 +9454,20 @@ export function isPropertyNames(keySchema: Constraint, annotations?: Annotations * `unique: true` constraint. Array generators translate it to `fast-check` * `uniqueArray` using Effect equality. * - * @category Array checks + * @category validation * @since 4.0.0 */ export function isUnique(annotations?: Annotations.Filter) { - const equivalence = Equal.asEquivalence() return makeFilter>( - (input) => Arr.dedupeWith(input, equivalence).length === input.length, + (input) => Arr.dedupe(input).length === input.length, { expected: "an array with unique items", - meta: { - _tag: "isUnique" + representation: { + id: "effect/schema/isUnique", + payload: null }, + toJsonSchema: () => ({ uniqueItems: true }), + toCode: () => ({ runtime: "Schema.isUnique()" }), arbitrary: { constraint: { unique: true @@ -8390,6 +9478,24 @@ export function isUnique(annotations?: Annotations.Filter) { ) } +/** + * 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 // ----------------------------------------------------------------------------- @@ -8397,7 +9503,7 @@ export function isUnique(annotations?: Annotations.Filter) { /** * Type-level representation of {@link NonEmptyString}. * - * @category string + * @category models * @since 3.10.0 */ export interface NonEmptyString extends String { @@ -8408,7 +9514,7 @@ export interface NonEmptyString extends String { * Schema for non-empty strings. Validates that a string has at least one * character. * - * @category string + * @category schemas * @since 3.10.0 */ export const NonEmptyString: NonEmptyString = String.check(isNonEmpty()) @@ -8416,7 +9522,7 @@ export const NonEmptyString: NonEmptyString = String.check(isNonEmpty()) /** * Type-level representation of {@link Char}. * - * @category string + * @category models * @since 3.10.0 */ export interface Char extends String { @@ -8439,7 +9545,7 @@ export interface Char extends String { * @see {@link NonEmptyString} for strings with length greater than zero * @see {@link isLengthBetween} for the underlying length check * - * @category string + * @category schemas * @since 3.10.0 */ export const Char: Char = String.check(isLengthBetween(1, 1)) @@ -8447,7 +9553,7 @@ export const Char: Char = String.check(isLengthBetween(1, 1)) /** * Type-level representation returned by {@link Option}. * - * @category Option + * @category models * @since 3.10.0 */ export interface Option extends @@ -8470,7 +9576,7 @@ export interface Option extends * `None` is represented as `{ _tag: "None" }`, while `Some` is represented as * `{ _tag: "Some", value }` using the wrapped schema's `Iso` type. * - * @category Option + * @category utility types * @since 4.0.0 */ export type OptionIso = @@ -8480,7 +9586,7 @@ export type OptionIso = /** * Schema for `Option` values. * - * @category Option + * @category schemas * @since 3.10.0 */ export function Option(value: A): Option { @@ -8499,22 +9605,22 @@ export function Option(value: A): Option { SchemaParser.decodeUnknownEffect(value)(input.value, options), { onSuccess: Option_.some, - onFailure: (issue) => - new SchemaIssue.Composite(ast, Option_.some(input), [new SchemaIssue.Pointer(["value"], issue)]) + onFailure: (issue) => SchemaIssue.makeCompositeAtKey(ast, "value", issue, input, options) } ) } - return Effect.fail(new SchemaIssue.InvalidType(ast, Option_.some(input))) + return Effect.fail(new SchemaIssue.InvalidType(ast, input, options)) }, { - typeConstructor: { - _tag: "effect/Option" - }, - generation: { - runtime: `Schema.Option(?)`, - Type: `Option.Option`, - importDeclaration: `import * as Option from "effect/Option"` + 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>()( @@ -8546,10 +9652,31 @@ export function Option(value: A): Option { 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 Option + * @category models * @since 3.10.0 */ export interface OptionFromNullOr extends decodeTo>, NullOr> { @@ -8564,7 +9691,7 @@ export interface OptionFromNullOr extends decodeTo(schema: S): OptionFromNullOr { @@ -8577,7 +9704,7 @@ export function OptionFromNullOr(schema: S): OptionFromNul /** * Type-level representation returned by {@link OptionFromUndefinedOr}. * - * @category Option + * @category models * @since 3.10.0 */ export interface OptionFromUndefinedOr extends decodeTo>, UndefinedOr> { @@ -8593,7 +9720,7 @@ export interface OptionFromUndefinedOr extends decodeTo(schema: S): OptionFromUndefinedOr { @@ -8606,7 +9733,7 @@ export function OptionFromUndefinedOr(schema: S): OptionFr /** * Type-level representation returned by {@link OptionFromNullishOr}. * - * @category Option + * @category models * @since 3.10.0 */ export interface OptionFromNullishOr extends decodeTo>, NullishOr> { @@ -8623,7 +9750,7 @@ export interface OptionFromNullishOr extends decodeTo( @@ -8641,7 +9768,7 @@ export function OptionFromNullishOr( /** * Type-level representation returned by {@link OptionFromOptionalKey}. * - * @category Option + * @category models * @since 4.0.0 */ export interface OptionFromOptionalKey extends decodeTo>, optionalKey> { @@ -8656,7 +9783,7 @@ export interface OptionFromOptionalKey extends decodeTo(schema: S): OptionFromOptionalKey { @@ -8669,7 +9796,7 @@ export function OptionFromOptionalKey(schema: S): OptionFr /** * Type-level representation returned by {@link OptionFromOptional}. * - * @category Option + * @category models * @since 4.0.0 */ export interface OptionFromOptional extends decodeTo>, optional> { @@ -8686,7 +9813,7 @@ export interface OptionFromOptional extends decodeTo(schema: S): OptionFromOptional { @@ -8699,7 +9826,7 @@ export function OptionFromOptional(schema: S): OptionFromO /** * Type-level representation returned by {@link OptionFromOptionalNullOr}. * - * @category Option + * @category models * @since 4.0.0 */ export interface OptionFromOptionalNullOr @@ -8719,7 +9846,7 @@ export interface OptionFromOptionalNullOr * according to `options.onNoneEncoding`: `"omit"` encodes a missing key, * `null` encodes `null`, and `undefined` encodes `undefined`. * - * @category Option + * @category schemas * @since 4.0.0 */ export function OptionFromOptionalNullOr( @@ -8746,7 +9873,7 @@ export function OptionFromOptionalNullOr( /** * Type-level representation returned by {@link Result}. * - * @category schemas + * @category models * @since 4.0.0 */ export interface Result extends @@ -8770,7 +9897,7 @@ export interface Result extends * Successful results are represented as `{ _tag: "Success", success }`, while * failed results are represented as `{ _tag: "Failure", failure }`. * - * @category schemas + * @category utility types * @since 4.0.0 */ export type ResultIso = @@ -8795,32 +9922,31 @@ export function Result( [success, failure], ([success, failure]) => (input, ast, options) => { if (!Result_.isResult(input)) { - return Effect.fail(new SchemaIssue.InvalidType(ast, Option_.some(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) => - new SchemaIssue.Composite(ast, Option_.some(input), [new SchemaIssue.Pointer(["success"], issue)]) + 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) => - new SchemaIssue.Composite(ast, Option_.some(input), [new SchemaIssue.Pointer(["failure"], issue)]) + onFailure: (issue) => SchemaIssue.makeCompositeAtKey(ast, "failure", issue, input, options) }) } }, { - typeConstructor: { - _tag: "effect/Result" - }, - generation: { - runtime: `Schema.Result(?, ?)`, - Type: `Result.Result`, - importDeclaration: `import * as Result from "effect/Result"` + 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>()( @@ -8860,10 +9986,31 @@ export function Result( 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 Redacted + * @category models * @since 3.10.0 */ export interface Redacted extends @@ -8877,14 +10024,39 @@ export interface Redacted extends 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 values that hide sensitive information from error output and - * inspection. - * - * **Details** - * - * If the wrapped schema fails, the issue will be redacted to prevent both - * the actual value and the schema details from being exposed. + * Schema for `Redacted` values, which hide their contents from inspection. * * Options: * @@ -8898,15 +10070,22 @@ export interface Redacted extends * sensitive and should not be exposed in JSON. * * @see {@link RedactedFromValue} for decoding raw values and wrapping them in `Redacted`. - * @category Redacted + * @category schemas * @since 3.10.0 */ export function Redacted(value: S, options?: { readonly label?: string | undefined readonly disallowJsonEncode?: boolean | undefined }): Redacted { - const decodeLabel = typeof options?.label === "string" - ? SchemaParser.decodeUnknownEffect(Literal(options.label)) + 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], @@ -8925,35 +10104,44 @@ export function Redacted(value: S, options?: { SchemaParser.decodeUnknownEffect(value)(Redacted_.value(input), poptions), { onSuccess: () => input, - onFailure: (/** ignore the actual issue because of security reasons */) => { - const oinput = Option_.some(input) - return new SchemaIssue.Composite(ast, oinput, [ - new SchemaIssue.Pointer(["value"], new SchemaIssue.InvalidValue(oinput)) - ]) + 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, Option_.some(input))) + return Effect.fail(new SchemaIssue.InvalidType(ast, input, poptions)) }, { - typeConstructor: { - _tag: "effect/Redacted", - options - }, - generation: { - runtime: options !== undefined ? `Schema.Redacted(?, ${format(options)})` : `Schema.Redacted(?)`, - Type: `Redacted.Redacted`, - importDeclaration: `import * as Redacted from "effect/Redacted"` + 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>()( - redact(value), + value, { - decode: SchemaGetter.transform((e) => Redacted_.make(e, { label: options?.label })), - encode: options?.disallowJsonEncode ? + 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}"` : "") @@ -8962,8 +10150,8 @@ export function Redacted(value: S, options?: { } ), toArbitrary: ([value]) => () => ({ - arbitrary: value.arbitrary.map((a) => Redacted_.make(a, { label: options?.label })), - terminal: value.terminal?.map((a) => Redacted_.make(a, { label: options?.label })) + 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) @@ -8973,26 +10161,34 @@ export function Redacted(value: S, options?: { } /** - * Type-level representation returned by {@link RedactedFromValue}. + * Reviver for persisted {@link Redacted} declarations. + * + * **When to use** * - * @category Redacted + * 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 interface RedactedFromValue - extends decodeTo>, middlewareDecoding> -{ - readonly "Rebuild": RedactedFromValue -} +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) + } +) /** - * Middleware that wraps decoded errors in `Redacted`, preventing sensitive - * schema details from leaking in error messages. + * Type-level representation returned by {@link RedactedFromValue}. * - * @category Redacted + * @category models * @since 4.0.0 */ -export function redact(schema: S): middlewareDecoding { - return middlewareDecoding(Effect.mapErrorEager(SchemaIssue.redact))(schema) +export interface RedactedFromValue extends decodeTo>, S> { + readonly "Rebuild": RedactedFromValue } /** @@ -9001,36 +10197,34 @@ export function redact(schema: S): middlewareDecoding(value: S, options?: { readonly label?: string | undefined readonly disallowEncode?: boolean | undefined }): RedactedFromValue { - return redact(value).pipe( - decodeTo( - 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) - } - ) - ) + 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 CauseReason + * @category models * @since 4.0.0 */ export interface CauseReason extends @@ -9054,7 +10248,7 @@ export interface CauseReason extends * 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 CauseReason + * @category utility types * @since 4.0.0 */ export type CauseReasonIso = { @@ -9085,7 +10279,7 @@ export type CauseReasonIso = { * @see {@link Cause} for constructing schemas for full Cause values * @see {@link CauseReasonIso} for the ISO shape of each cause reason * - * @category CauseReason + * @category schemas * @since 4.0.0 */ export function CauseReason(error: E, defect: D): CauseReason { @@ -9093,7 +10287,7 @@ export function CauseReason(error: E [error, defect], ([error, defect]) => (input, ast, options) => { if (!Cause_.isReason(input)) { - return Effect.fail(new SchemaIssue.InvalidType(ast, Option_.some(input))) + return Effect.fail(new SchemaIssue.InvalidType(ast, input, options)) } switch (input._tag) { case "Fail": @@ -9101,8 +10295,7 @@ export function CauseReason(error: E SchemaParser.decodeUnknownEffect(error)(input.error, options), { onSuccess: Cause_.makeFailReason, - onFailure: (issue) => - new SchemaIssue.Composite(ast, Option_.some(input), [new SchemaIssue.Pointer(["error"], issue)]) + onFailure: (issue) => SchemaIssue.makeCompositeAtKey(ast, "error", issue, input, options) } ) case "Die": @@ -9110,8 +10303,7 @@ export function CauseReason(error: E SchemaParser.decodeUnknownEffect(defect)(input.defect, options), { onSuccess: Cause_.makeDieReason, - onFailure: (issue) => - new SchemaIssue.Composite(ast, Option_.some(input), [new SchemaIssue.Pointer(["defect"], issue)]) + onFailure: (issue) => SchemaIssue.makeCompositeAtKey(ast, "defect", issue, input, options) } ) case "Interrupt": @@ -9119,14 +10311,15 @@ export function CauseReason(error: E } }, { - typeConstructor: { - _tag: "effect/Cause/Failure" - }, - generation: { - runtime: `Schema.CauseReason(?, ?)`, - Type: `Cause.Failure`, - importDeclaration: `import * as Cause from "effect/Cause"` + 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>()( @@ -9157,6 +10350,27 @@ export function CauseReason(error: E 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 @@ -9203,7 +10417,7 @@ function causeReasonToFormatter(error: Formatter, defect: Formatter extends @@ -9231,7 +10445,7 @@ export interface Cause extends * @see {@link Cause} for constructing schemas for full Cause values * @see {@link CauseReasonIso} for the ISO shape of each array element * - * @category Cause + * @category utility types * @since 4.0.0 */ export type CauseIso = ReadonlyArray> @@ -9254,7 +10468,7 @@ export type CauseIso = ReadonlyArray * @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 Cause + * @category schemas * @since 3.10.0 */ export function Cause(error: E, defect: D): Cause { @@ -9264,24 +10478,24 @@ export function Cause(error: E, defe const failures = ArraySchema(CauseReason(error, defect)) return (input, ast, options) => { if (!Cause_.isCause(input)) { - return Effect.fail(new SchemaIssue.InvalidType(ast, Option_.some(input))) + return Effect.fail(new SchemaIssue.InvalidType(ast, input, options)) } return Effect.mapBothEager(SchemaParser.decodeUnknownEffect(failures)(input.reasons, options), { onSuccess: Cause_.fromReasons, - onFailure: (issue) => - new SchemaIssue.Composite(ast, Option_.some(input), [new SchemaIssue.Pointer(["failures"], issue)]) + onFailure: (issue) => SchemaIssue.makeCompositeAtKey(ast, "failures", issue, input, options) }) } }, { - typeConstructor: { - _tag: "effect/Cause" - }, - generation: { - runtime: `Schema.Cause(?, ?)`, - Type: `Cause.Cause`, - importDeclaration: `import * as Cause from "effect/Cause"` + 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>()( @@ -9299,6 +10513,27 @@ export function Cause(error: E, defe 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 @@ -9322,17 +10557,17 @@ function causeToFormatter(error: Formatter, defect: Formatter) { } /** - * Type-level representation of {@link Error}. + * Type-level representation of {@link ErrorInstance}. * - * @category Error + * @category models * @since 4.0.0 */ -export interface Error extends instanceOf { - readonly "Rebuild": Error +export interface ErrorInstance extends instanceOf { + readonly "Rebuild": ErrorInstance } /** - * Options for {@link Error} and {@link Defect}. + * Options for {@link ErrorInstance} and {@link Defect}. * * @category options * @since 4.0.0 @@ -9353,13 +10588,36 @@ export interface ErrorOptions { 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): ErrorOptions | undefined => { +const getErrorOptions = (key: ErrorOptionsKey): NormalizedErrorOptions | undefined => { switch (key) { case 0: return undefined @@ -9372,7 +10630,7 @@ const getErrorOptions = (key: ErrorOptionsKey): ErrorOptions | undefined => { } } -const errorSchemaCache: Array = [] +const errorSchemaCache: Array = [] /** * Schema for JavaScript `Error` objects. @@ -9386,10 +10644,10 @@ const errorSchemaCache: Array = [] * traces are omitted by default for security. Pass `{ includeStack: true }` to * include stack traces, or `{ excludeCause: true }` to omit causes. * - * @category constructors + * @category schemas * @since 4.0.0 */ -export function Error(options?: ErrorOptions): Error { +export function ErrorInstance(options?: ErrorOptions): ErrorInstance { const key = getErrorOptionsKey(options) const cached = errorSchemaCache[key] if (cached !== undefined) { @@ -9397,14 +10655,16 @@ export function Error(options?: ErrorOptions): Error { } const normalizedOptions = getErrorOptions(key) const schema = instanceOf(globalThis.Error, { - typeConstructor: { - _tag: "Error", - ...(normalizedOptions === undefined ? {} : { options: normalizedOptions }) + representation: { + id: "effect/schema/Error", + payload: normalizedOptions ?? null }, - generation: { - runtime: normalizedOptions !== undefined ? `Schema.Error(${format(normalizedOptions)})` : `Schema.Error()`, + 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)) @@ -9413,10 +10673,31 @@ export function Error(options?: ErrorOptions): Error { 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 Defect + * @category models * @since 3.10.0 */ export interface Defect extends decodeTo { @@ -9462,8 +10743,8 @@ const defectSchemaCache: Array = [] * - Values that cannot be represented as JSON fall back to Effect's formatted * string representation. * - * @see {@link Error} for a schema that only accepts JavaScript `Error` values. - * @category constructors + * @see {@link ErrorInstance} for a schema that only accepts JavaScript `Error` values. + * @category schemas * @since 4.0.0 */ export function Defect(options?: ErrorOptions): Defect { @@ -9480,7 +10761,7 @@ export function Defect(options?: ErrorOptions): Defect { /** * Type-level representation returned by {@link Exit}. * - * @category Exit + * @category models * @since 3.10.0 */ export interface Exit extends @@ -9505,7 +10786,7 @@ export interface Exit = { @@ -9525,7 +10806,7 @@ export type ExitIso( @@ -9543,7 +10824,7 @@ export function Exit { if (!Exit_.isExit(input)) { - return Effect.fail(new SchemaIssue.InvalidType(ast, Option_.some(input))) + return Effect.fail(new SchemaIssue.InvalidType(ast, input, options)) } switch (input._tag) { case "Success": @@ -9551,8 +10832,7 @@ export function Exit - new SchemaIssue.Composite(ast, Option_.some(input), [new SchemaIssue.Pointer(["value"], issue)]) + onFailure: (issue) => SchemaIssue.makeCompositeAtKey(ast, "value", issue, input, options) } ) case "Failure": @@ -9560,22 +10840,24 @@ export function Exit - new SchemaIssue.Composite(ast, Option_.some(input), [new SchemaIssue.Pointer(["cause"], issue)]) + onFailure: (issue) => SchemaIssue.makeCompositeAtKey(ast, "cause", issue, input, options) } ) } } }, { - typeConstructor: { - _tag: "effect/Exit" - }, - generation: { - runtime: `Schema.Exit(?, ?, ?)`, - Type: `Exit.Exit`, - importDeclaration: `import * as Exit from "effect/Exit"` + 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>()( @@ -9633,10 +10915,31 @@ export function Exit { + const schema = Exit(typeParameters[0], typeParameters[1], typeParameters[2]) + return annotations === undefined ? schema : schema.annotate(annotations) + } +) + /** * Type-level representation returned by {@link ReadonlyMap}. * - * @category ReadonlyMap + * @category models * @since 4.0.0 */ export interface $ReadonlyMap extends @@ -9656,7 +10959,7 @@ export interface $ReadonlyMap * Iso representation used for `ReadonlyMap` schemas: an array of readonly * `[key, value]` tuples using each entry schema's `Iso` type. * - * @category ReadonlyMap + * @category utility types * @since 4.0.0 */ export type ReadonlyMapIso = ReadonlyArray< @@ -9757,7 +11060,7 @@ function entriesArbitrary( * Schema for readonly maps whose keys and values conform to the provided * schemas. * - * @category ReadonlyMap + * @category schemas * @since 3.10.0 */ export function ReadonlyMap( @@ -9778,22 +11081,22 @@ export function ReadonlyMap( SchemaParser.decodeUnknownEffect(array)([...input], options), { onSuccess: (array: ReadonlyArray) => new globalThis.Map(array), - onFailure: (issue) => - new SchemaIssue.Composite(ast, Option_.some(input), [new SchemaIssue.Pointer(["entries"], issue)]) + onFailure: (issue) => SchemaIssue.makeCompositeAtKey(ast, "entries", issue, input, options) } ) } - return Effect.fail(new SchemaIssue.InvalidType(ast, Option_.some(input))) + return Effect.fail(new SchemaIssue.InvalidType(ast, input, options)) } }, { - typeConstructor: { - _tag: "ReadonlyMap" - }, - generation: { - runtime: `Schema.ReadonlyMap(?, ?)`, - Type: `globalThis.ReadonlyMap` + 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>()( @@ -9818,10 +11121,31 @@ export function ReadonlyMap( 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) + } +) + /** * Type-level representation returned by {@link HashMap}. * - * @category HashMap + * @category models * @since 3.10.0 */ export interface HashMap extends @@ -9841,7 +11165,7 @@ export interface HashMap exten * Iso representation used for `HashMap` schemas: an array of readonly * `[key, value]` tuples using each entry schema's `Iso` type. * - * @category HashMap + * @category utility types * @since 4.0.0 */ export type HashMapIso = ReadonlyArray< @@ -9851,7 +11175,7 @@ export type HashMapIso = Reado /** * Schema for hash maps whose keys and values conform to the provided schemas. * - * @category HashMap + * @category schemas * @since 3.10.0 */ export function HashMap(key: Key, value: Value): HashMap { @@ -9869,23 +11193,23 @@ export function HashMap(key: K SchemaParser.decodeUnknownEffect(entries)(HashMap_.toEntries(input), options), { onSuccess: HashMap_.fromIterable, - onFailure: (issue) => - new SchemaIssue.Composite(ast, Option_.some(input), [new SchemaIssue.Pointer(["entries"], issue)]) + onFailure: (issue) => SchemaIssue.makeCompositeAtKey(ast, "entries", issue, input, options) } ) } - return Effect.fail(new SchemaIssue.InvalidType(ast, Option_.some(input))) + return Effect.fail(new SchemaIssue.InvalidType(ast, input, options)) } }, { - typeConstructor: { - _tag: "effect/HashMap" - }, - generation: { - runtime: `Schema.HashMap(?, ?)`, - Type: `HashMap.HashMap`, - importDeclaration: `import * as HashMap from "effect/HashMap"` + 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>()( @@ -9910,10 +11234,31 @@ export function HashMap(key: K 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 ReadonlySet + * @category models * @since 4.0.0 */ export interface $ReadonlySet extends @@ -9932,7 +11277,7 @@ export interface $ReadonlySet extends * Iso representation used for `ReadonlySet` schemas: an array of element values * using the element schema's `Iso` type. * - * @category ReadonlySet + * @category utility types * @since 4.0.0 */ export type ReadonlySetIso = ReadonlyArray @@ -9940,7 +11285,7 @@ export type ReadonlySetIso = ReadonlyArray(value: Value): $ReadonlySet { @@ -9958,22 +11303,22 @@ export function ReadonlySet(value: Value): $ReadonlySe SchemaParser.decodeUnknownEffect(array)([...input], options), { onSuccess: (array: ReadonlyArray) => new globalThis.Set(array), - onFailure: (issue) => - new SchemaIssue.Composite(ast, Option_.some(input), [new SchemaIssue.Pointer(["values"], issue)]) + onFailure: (issue) => SchemaIssue.makeCompositeAtKey(ast, "values", issue, input, options) } ) } - return Effect.fail(new SchemaIssue.InvalidType(ast, Option_.some(input))) + return Effect.fail(new SchemaIssue.InvalidType(ast, input, options)) } }, { - typeConstructor: { - _tag: "ReadonlySet" - }, - generation: { - runtime: `Schema.ReadonlySet(?)`, - Type: `globalThis.ReadonlySet` + 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>()( @@ -9999,10 +11344,31 @@ export function ReadonlySet(value: Value): $ReadonlySe 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 HashSet + * @category models * @since 3.10.0 */ export interface HashSet extends @@ -10021,7 +11387,7 @@ export interface HashSet extends * Iso representation used for `HashSet` schemas: an array of element values * using the element schema's `Iso` type. * - * @category HashSet + * @category utility types * @since 4.0.0 */ export type HashSetIso = ReadonlyArray @@ -10029,7 +11395,7 @@ export type HashSetIso = ReadonlyArray /** * Schema for hash sets whose values conform to the provided element schema. * - * @category HashSet + * @category schemas * @since 3.10.0 */ export function HashSet(value: Value): HashSet { @@ -10047,22 +11413,22 @@ export function HashSet(value: Value): HashSet SchemaParser.decodeUnknownEffect(values)(Arr.fromIterable(input), options), { onSuccess: HashSet_.fromIterable, - onFailure: (issue) => - new SchemaIssue.Composite(ast, Option_.some(input), [new SchemaIssue.Pointer(["values"], issue)]) + onFailure: (issue) => SchemaIssue.makeCompositeAtKey(ast, "values", issue, input, options) } ) } - return Effect.fail(new SchemaIssue.InvalidType(ast, Option_.some(input))) + return Effect.fail(new SchemaIssue.InvalidType(ast, input, options)) } }, { - typeConstructor: { - _tag: "effect/HashSet" - }, - generation: { - runtime: `Schema.HashSet(?)`, - Type: `HashSet.HashSet` + 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>()( @@ -10088,10 +11454,31 @@ export function HashSet(value: Value): HashSet 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 Chunk + * @category models * @since 3.10.0 */ export interface Chunk extends @@ -10117,7 +11504,7 @@ export interface Chunk extends * * @see {@link Chunk} for the schema interface and constructor that use this ISO representation * - * @category Chunk + * @category utility types * @since 4.0.0 */ export type ChunkIso = ReadonlyArray @@ -10125,7 +11512,7 @@ export type ChunkIso = ReadonlyArray /** * Schema for chunks whose values conform to the provided element schema. * - * @category Chunk + * @category schemas * @since 3.10.0 */ export function Chunk(value: Value): Chunk { @@ -10143,22 +11530,22 @@ export function Chunk(value: Value): Chunk { SchemaParser.decodeUnknownEffect(values)(Arr.fromIterable(input), options), { onSuccess: Chunk_.fromIterable, - onFailure: (issue) => - new SchemaIssue.Composite(ast, Option_.some(input), [new SchemaIssue.Pointer(["values"], issue)]) + onFailure: (issue) => SchemaIssue.makeCompositeAtKey(ast, "values", issue, input, options) } ) } - return Effect.fail(new SchemaIssue.InvalidType(ast, Option_.some(input))) + return Effect.fail(new SchemaIssue.InvalidType(ast, input, options)) } }, { - typeConstructor: { - _tag: "effect/Chunk" - }, - generation: { - runtime: `Schema.Chunk(?)`, - Type: `Chunk.Chunk` + 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>()( @@ -10184,10 +11571,31 @@ export function Chunk(value: Value): Chunk { 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 RegExp + * @category models * @since 4.0.0 */ export interface RegExp extends instanceOf { @@ -10201,19 +11609,20 @@ export interface RegExp extends instanceOf { * * The default JSON serializer encodes a `RegExp` as `{ source, flags }`. * - * @category RegExp + * @category schemas * @since 4.0.0 */ export const RegExp: RegExp = instanceOf( globalThis.RegExp, { - typeConstructor: { - _tag: "RegExp" + representation: { + id: "effect/schema/RegExp", + payload: null }, - generation: { + toCode: () => ({ runtime: `Schema.RegExp`, Type: `globalThis.RegExp` - }, + }), expected: "RegExp", toCodecJson: () => link()( @@ -10222,10 +11631,15 @@ export const RegExp: RegExp = instanceOf( flags: String }), SchemaTransformation.transformOrFail({ - decode: (e) => + decode: (e, options) => Effect.try({ try: () => new globalThis.RegExp(e.source, e.flags), - catch: (e) => new SchemaIssue.InvalidValue(Option_.some(e), { message: globalThis.String(e) }) + catch: () => + new SchemaIssue.InvalidValue( + { expected: "valid RegExp source and flags" }, + e, + options + ) }), encode: (regExp) => Effect.succeed({ @@ -10260,10 +11674,27 @@ export const RegExp: RegExp = instanceOf( } ) +/** + * 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 URL + * @category models * @since 4.0.0 */ export interface URL extends instanceOf { @@ -10281,19 +11712,20 @@ const URLString = String.annotate({ expected: "a string that will be decoded as * * - encodes `URL` as a `string` * - * @category URL + * @category schemas * @since 4.0.0 */ export const URL: URL = instanceOf( globalThis.URL, { - typeConstructor: { - _tag: "URL" + representation: { + id: "effect/schema/URL", + payload: null }, - generation: { + toCode: () => ({ runtime: `Schema.URL`, Type: `globalThis.URL` - }, + }), expected: "URL", toCodecJson: () => link()( @@ -10305,10 +11737,27 @@ export const URL: URL = instanceOf( } ) +/** + * 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 URL + * @category models * @since 4.0.0 */ export interface URLFromString extends decodeTo { @@ -10326,7 +11775,7 @@ export interface URLFromString extends decodeTo { * Encoding: * - A `URL` is encoded as a `string` * - * @category URL + * @category schemas * @since 4.0.0 */ export const URLFromString: URLFromString = URLString.pipe(decodeTo(URL, SchemaTransformation.urlFromString)) @@ -10334,28 +11783,19 @@ export const URLFromString: URLFromString = URLString.pipe(decodeTo(URL, SchemaT /** * Type-level representation of {@link Date}. * - * @category Date + * @category models * @since 4.0.0 */ -export interface Date extends instanceOf { +export interface Date extends declare { readonly "Rebuild": Date } -type DateArbitraryConstraints = FastCheck.DateConstraints & { - readonly valid?: boolean | undefined -} - function dateArbitraryConstraints( - constraint: Annotations.ToArbitrary.GenerationConstraint | undefined, ordered: Annotations.ToArbitrary.OrderedConstraint | undefined, - base?: DateArbitraryConstraints | undefined, + base?: FastCheck.DateConstraints | undefined, toDate?: (value: T) => globalThis.Date ): FastCheck.DateConstraints { const out: FastCheck.DateConstraints = { ...base } - delete (out as any).valid - if (base?.valid || constraint?.valid) { - out.noInvalidDate = true - } 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 @@ -10373,47 +11813,48 @@ function dateArbitraryConstraints( return out } -const DateString = String.annotate({ expected: "a string in ISO 8601 format that will be decoded as a Date" }) +const DateString = String.annotate({ expected: "a string that will be decoded as a Date" }) /** - * Schema for JavaScript `Date` objects. + * Schema for valid JavaScript `Date` objects. * * **When to use** * - * Use to validate in-memory values that must already be JavaScript date + * Use to validate in-memory values that must already be valid JavaScript date * objects. * * **Details** * - * This schema accepts any `Date` instance, including invalid dates. The default - * JSON serializer encodes valid dates as ISO 8601 strings; invalid dates encode - * as `"Invalid Date"`. + * 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 + * ```ts import.meta.vitest * import { Schema } from "effect" * - * Schema.decodeUnknownSync(Schema.Date)(new Date("2024-01-01")) - * // => Date { 2024-01-01T00:00:00.000Z } + * const date = Schema.decodeUnknownSync(Schema.Date)(new Date("2024-01-01")) + * date.toISOString() // => "2024-01-01T00:00:00.000Z" * ``` * - * @see {@link DateValid} for accepting only valid Date instances + * @see {@link DateFromString} for decoding strings into Date instances + * @see {@link DateFromMillis} for decoding epoch milliseconds into Date instances * - * @category Date + * @category schemas * @since 4.0.0 */ -export const Date: Date = instanceOf( - globalThis.Date, +export const Date: Date = declare( + (input): input is globalThis.Date => input instanceof globalThis.Date && !globalThis.Number.isNaN(input.getTime()), { - typeConstructor: { - _tag: "Date" + representation: { + id: "effect/schema/Date", + payload: null }, - generation: { + toCode: () => ({ runtime: `Schema.Date`, Type: `globalThis.Date` - }, - expected: "Date", + }), + expected: "a valid Date", toCodecJson: () => link()( DateString, @@ -10421,16 +11862,33 @@ export const Date: Date = instanceOf( ), toArbitrary: () => (fc, ctx) => fc.date(dateArbitraryConstraints( - ctx?.constraint, - ctx?.constraint?.ordered?.order === Order.Date ? ctx.constraint.ordered : undefined + 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 Date + * @category models * @since 3.10.0 */ export interface DateFromString extends decodeTo { @@ -10451,48 +11909,65 @@ export interface DateFromString extends decodeTo { * The string is passed to JavaScript `Date` construction. * * Encoding: - * A valid `Date` is encoded as an ISO string; an invalid `Date` is encoded as - * `"Invalid Date"`. - * - * **Gotchas** + * A `Date` is encoded as an ISO string. * - * Invalid date strings can decode to invalid `Date` instances. + * 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 - * @see {@link DateValid} for rejecting invalid Date instances * - * @category Date + * @category schemas * @since 3.10.0 */ export const DateFromString: DateFromString = DateString.pipe(decodeTo(Date, SchemaTransformation.dateFromString)) /** - * Type-level representation of {@link DateValid}. + * Type-level representation of {@link DateFromMillis}. * - * @category Date + * @category models * @since 4.0.0 */ -export interface DateValid extends Date { - readonly "Rebuild": DateValid +export interface DateFromMillis extends decodeTo { + readonly "Rebuild": DateFromMillis } /** - * Schema for **valid** JavaScript `Date` objects. + * 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** * - * This schema accepts `Date` instances but rejects invalid dates (such as `new - * Date("invalid")`). + * 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. * - * @category Date + * @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 DateValid: DateValid = Date.check(isDateValid()) +export const DateFromMillis: DateFromMillis = Int.pipe( + decodeTo(Date, SchemaTransformation.dateFromMillis) +) /** * Type-level representation of {@link Duration}. * - * @category Duration + * @category models * @since 3.10.0 */ export interface Duration extends declare { @@ -10509,28 +11984,28 @@ export interface Duration extends declare { * * **Example** (Defining a Duration schema) * - * ```ts + * ```ts import.meta.vitest * import { Duration, Schema } from "effect" * - * Schema.decodeUnknownSync(Schema.Duration)(Duration.seconds(5)) - * // => Duration(5s) + * Schema.decodeUnknownSync(Schema.Duration)(Duration.seconds(5)) // => Duration.seconds(5) * ``` * - * @category Duration + * @category schemas * * @since 3.10.0 */ export const Duration: Duration = declare( Duration_.isDuration, { - typeConstructor: { - _tag: "effect/Duration" + representation: { + id: "effect/schema/Duration", + payload: null }, - generation: { + toCode: () => ({ runtime: `Schema.Duration`, Type: `Duration.Duration`, - importDeclaration: `import * as Duration from "effect/Duration"` - }, + importDeclarations: [`import * as Duration from "effect/Duration"`] + }), expected: "Duration", toCodecJson: () => link()( @@ -10579,12 +12054,29 @@ export const Duration: Duration = declare( } ) +/** + * 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 Duration + * @category models * @since 4.0.0 */ export interface DurationFromString extends decodeTo { @@ -10603,7 +12095,7 @@ export interface DurationFromString extends decodeTo { * Encoding: * - A `Duration` is encoded as a parseable `string`. * - * @category Duration + * @category schemas * @since 4.0.0 */ export const DurationFromString: DurationFromString = DurationString.pipe( @@ -10613,40 +12105,38 @@ export const DurationFromString: DurationFromString = DurationString.pipe( /** * Type-level representation of {@link DurationFromNanos}. * - * @category Duration + * @category models * @since 3.10.0 */ export interface DurationFromNanos extends decodeTo { readonly "Rebuild": DurationFromNanos } -const bigint0 = globalThis.BigInt(0) - /** - * Schema that decodes a non-negative `bigint` into a - * `Duration`, treating the bigint as nanoseconds. + * Schema that decodes a `bigint` into a `Duration`, treating the bigint as + * nanoseconds. * * **Details** * * Decoding: - * A non-negative `bigint` representing nanoseconds is decoded as a `Duration`. + * A `bigint` representing nanoseconds is decoded as a `Duration`. * * Encoding: - * Finite durations are encoded as a non-negative `bigint` number of nanoseconds. - * Encoding fails when the duration cannot be represented as nanoseconds, such as - * `Duration.infinity`. + * 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 Duration + * @category schemas * @since 3.10.0 */ -export const DurationFromNanos: DurationFromNanos = BigInt.check(isGreaterThanOrEqualToBigInt(bigint0)).pipe( +export const DurationFromNanos: DurationFromNanos = BigInt.pipe( decodeTo(Duration, SchemaTransformation.durationFromNanos) ) /** * Type-level representation of {@link DurationFromMillis}. * - * @category Duration + * @category models * @since 3.10.0 */ export interface DurationFromMillis extends decodeTo { @@ -10654,31 +12144,32 @@ export interface DurationFromMillis extends decodeTo { } /** - * Schema that decodes a non-negative (possibly infinite) - * integer into a `Duration`, treating the integer value as the duration in + * Schema that decodes a number into a `Duration`, treating the number as * milliseconds. * * **Details** * * Decoding: - * - A non-negative (possibly infinite) integer representing milliseconds is - * decoded as a `Duration` + * - A finite or infinite number is decoded as a `Duration` * * Encoding: - * - A `Duration` is encoded to a non-negative (possibly infinite) integer - * representing milliseconds + * - A `Duration` is encoded to a finite or infinite number of milliseconds * - * @category Duration + * **Gotchas** + * + * `NaN` is decoded as `Duration.zero`, matching `Duration.millis`. + * + * @category schemas * @since 3.10.0 */ -export const DurationFromMillis: DurationFromMillis = Number.check(isGreaterThanOrEqualTo(0)).pipe( +export const DurationFromMillis: DurationFromMillis = Number.pipe( decodeTo(Duration, SchemaTransformation.durationFromMillis) ) /** * Type-level representation of {@link BigDecimal}. * - * @category BigDecimal + * @category models * @since 3.10.0 */ export interface BigDecimal extends declare { @@ -10778,20 +12269,21 @@ function bigDecimalScaleConstraints( * * @see {@link BigDecimalFromString} for parsing string input into a BigDecimal * - * @category BigDecimal + * @category schemas * @since 3.10.0 */ export const BigDecimal: BigDecimal = declare( BigDecimal_.isBigDecimal, { - typeConstructor: { - _tag: "effect/BigDecimal" + representation: { + id: "effect/schema/BigDecimal", + payload: null }, - generation: { + toCode: () => ({ runtime: `Schema.BigDecimal`, Type: `BigDecimal.BigDecimal`, - importDeclaration: `import * as BigDecimal from "effect/BigDecimal"` - }, + importDeclarations: [`import * as BigDecimal from "effect/BigDecimal"`] + }), expected: "BigDecimal", toCodecJson: () => link()( @@ -10820,10 +12312,27 @@ export const BigDecimal: BigDecimal = declare( } ) +/** + * 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 BigDecimal + * @category models * @since 4.0.0 */ export interface BigDecimalFromString extends decodeTo { @@ -10854,49 +12363,17 @@ export interface BigDecimalFromString extends decodeTo { * @see {@link BigIntFromString} for parsing base-10 integer strings into bigint values * @see {@link NumberFromString} for parsing JavaScript number strings * - * @category BigDecimal + * @category schemas * @since 4.0.0 */ export const BigDecimalFromString: BigDecimalFromString = BigDecimalString.pipe( decodeTo(BigDecimal, SchemaTransformation.bigDecimalFromString) ) -/** - * Type-level representation of {@link UnknownFromJsonString}. - * - * @category models - * @since 4.0.0 - */ -export interface UnknownFromJsonString extends fromJsonString { - readonly "Rebuild": UnknownFromJsonString -} - -/** - * Schema that decodes a JSON-encoded string into an `unknown` value. - * - * **Details** - * - * Decoding: - * - A `string` is decoded as an `unknown` value. - * - If the string is not valid JSON, decoding fails. - * - * Encoding: - * - Any value is encoded as a JSON string using `JSON.stringify`. - * - If the value is not a valid JSON value, encoding fails. - * - * **Example** (Decoding unknown JSON strings) - * - * ```ts - * import { Schema } from "effect" - * - * Schema.decodeUnknownSync(Schema.UnknownFromJsonString)(`{"a":1,"b":2}`) - * // => { a: 1, b: 2 } - * ``` - * - * @category schemas - * @since 4.0.0 - */ -export const UnknownFromJsonString: UnknownFromJsonString = fromJsonString(Unknown) +const JsonString = String.annotate({ + expected: "a string that will be decoded as JSON", + contentMediaType: "application/json" +}) /** * Type-level representation returned by {@link fromJsonString}. @@ -10917,75 +12394,44 @@ export interface fromJsonString extends decodeTo { a: 1 } - * ``` + * const schemaFromJsonString = Schema.fromJsonString(schema, { space: 2 }) * - * **Example** (Emitting JSON Schema for a JSON string decoder) - * - * ```ts - * import { Schema } from "effect" - * - * const original = Schema.Struct({ a: Schema.String }) - * 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": {} - * // } + * Schema.encodeSync(schemaFromJsonString)({ a: 1 }) // => "{\n \"a\": 1\n}" * ``` * - * @category constructors + * @category schemas * @since 4.0.0 */ -export function fromJsonString(schema: S): fromJsonString { - return String.annotate({ - expected: "a string that will be decoded as JSON", - contentMediaType: "application/json", - contentSchema: SchemaAST.toEncoded(schema.ast) - }).pipe(decodeTo(schema, SchemaTransformation.fromJsonString)) +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 file + * @category models * @since 4.0.0 */ export interface File extends instanceOf { @@ -11000,17 +12446,18 @@ export interface File extends instanceOf { * The default JSON serializer encodes a `File` as `{ data, type, name, lastModified }` * where `data` is base64-encoded. * - * @category file + * @category schemas * @since 4.0.0 */ export const File: File = instanceOf(globalThis.File, { - typeConstructor: { - _tag: "File" + representation: { + id: "effect/schema/File", + payload: null }, - generation: { + toCode: () => ({ runtime: `Schema.File`, Type: `globalThis.File` - }, + }), expected: "File", toCodecJson: () => link()( @@ -11018,16 +12465,18 @@ export const File: File = instanceOf(globalThis.File, { data: String.check(isBase64()), type: String, name: String, - lastModified: Number + lastModified: Int }), SchemaTransformation.transformOrFail({ - decode: (e) => + decode: (e, options) => Result_.match(Encoding.decodeBase64(e.data), { - onFailure: (error) => + onFailure: () => Effect.fail( - new SchemaIssue.InvalidValue(Option_.some(e.data), { - message: error.message - }) + new SchemaIssue.InvalidValue( + { expected: "a valid Base64 string" }, + e.data, + options + ) ), onSuccess: (bytes) => { const buffer = new globalThis.Uint8Array(bytes) @@ -11036,7 +12485,7 @@ export const File: File = instanceOf(globalThis.File, { ) } }), - encode: (file) => + encode: (file, options) => Effect.tryPromise({ try: async () => { const bytes = new globalThis.Uint8Array(await file.arrayBuffer()) @@ -11047,19 +12496,38 @@ export const File: File = instanceOf(globalThis.File, { lastModified: file.lastModified } }, - catch: (e) => - new SchemaIssue.InvalidValue(Option_.some(file), { - message: globalThis.String(e) - }) + 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 FormData + * @category models * @since 4.0.0 */ export interface FormData extends instanceOf { @@ -11074,17 +12542,18 @@ export interface FormData extends instanceOf { * The default JSON serializer encodes a `FormData` as an array of `[key, entry]` * pairs where each entry is tagged as `"String"` or `"File"`. * - * @category FormData + * @category schemas * @since 4.0.0 */ export const FormData: FormData = instanceOf(globalThis.FormData, { - typeConstructor: { - _tag: "FormData" + representation: { + id: "effect/schema/FormData", + payload: null }, - generation: { + toCode: () => ({ runtime: `Schema.FormData`, Type: `globalThis.FormData` - }, + }), expected: "FormData", toCodecJson: () => link()( @@ -11120,10 +12589,27 @@ export const FormData: FormData = instanceOf(globalThis.FormData, { ) }) +/** + * 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 FormData + * @category models * @since 4.0.0 */ export interface fromFormData extends decodeTo { @@ -11151,7 +12637,7 @@ export interface fromFormData extends decodeTo extends decodeTo { a: "1" } * ``` * * **Example** (Decoding nested fields) * - * ```ts + * ```ts import.meta.vitest * import { Schema } from "effect" * * const schema = Schema.fromFormData( @@ -11188,13 +12673,12 @@ export interface fromFormData extends decodeTo { a: "1", b: { c: "2", d: "3" } } * ``` * * **Example** (Parsing non-string values) * - * ```ts + * ```ts import.meta.vitest * import { Schema } from "effect" * * const schema = Schema.fromFormData( @@ -11208,8 +12692,7 @@ export interface fromFormData extends decodeTo { a: 1 } * ``` * * @category decoding @@ -11222,7 +12705,7 @@ export function fromFormData(schema: S): fromFormData { /** * Type-level representation of {@link URLSearchParams}. * - * @category search params + * @category models * @since 4.0.0 */ export interface URLSearchParams extends instanceOf { @@ -11236,17 +12719,18 @@ export interface URLSearchParams extends instanceOf * * The default JSON serializer encodes a `URLSearchParams` as a query string. * - * @category search params + * @category schemas * @since 4.0.0 */ export const URLSearchParams: URLSearchParams = instanceOf(globalThis.URLSearchParams, { - typeConstructor: { - _tag: "URLSearchParams" + representation: { + id: "effect/schema/URLSearchParams", + payload: null }, - generation: { + toCode: () => ({ runtime: `Schema.URLSearchParams`, Type: `globalThis.URLSearchParams` - }, + }), expected: "URLSearchParams", toCodecJson: () => link()( @@ -11258,10 +12742,27 @@ export const URLSearchParams: URLSearchParams = instanceOf(globalThis.URLSearchP ) }) +/** + * 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 search params + * @category models * @since 4.0.0 */ export interface fromURLSearchParams extends decodeTo { @@ -11290,7 +12791,7 @@ export interface fromURLSearchParams extends decodeTo extends decodeTo { a: "1" } * ``` * * **Example** (Decoding nested fields) * - * ```ts + * ```ts import.meta.vitest * import { Schema } from "effect" * * const schema = Schema.fromURLSearchParams( @@ -11322,13 +12822,12 @@ export interface fromURLSearchParams extends decodeTo { a: "1", b: { c: "2", d: "3" } } * ``` * * **Example** (Parsing non-string values) * - * ```ts + * ```ts import.meta.vitest * import { Schema } from "effect" * * const schema = Schema.fromURLSearchParams( @@ -11341,8 +12840,7 @@ export interface fromURLSearchParams extends decodeTo { a: 1 } * ``` * * @category decoding @@ -11352,49 +12850,13 @@ export function fromURLSearchParams(schema: S): fromURLSea return URLSearchParams.pipe(decodeTo(schema, SchemaTransformation.fromURLSearchParams)) } -/** - * Type-level representation of {@link Finite}. - * - * @category Number - * @since 3.10.0 - */ -export interface Finite extends Number { - readonly "Rebuild": Finite -} - -/** - * Schema for finite numbers, rejecting `NaN`, `Infinity`, and `-Infinity`. - * - * @category Number - * @since 3.10.0 - */ -export const Finite: Finite = Number.check(isFinite()) - -/** - * Type-level representation of {@link Int}. - * - * @category Number - * @since 3.10.0 - */ -export interface Int extends Number { - readonly "Rebuild": Int -} - -/** - * Schema for integers, rejecting `NaN`, `Infinity`, and `-Infinity`. - * - * @category Number - * @since 3.10.0 - */ -export const Int: Int = Number.check(isInt()) - /** * Type-level representation of {@link NumberFromString}. * - * @category Number + * @category models * @since 3.10.0 */ -export interface NumberFromString extends decodeTo { +export interface NumberFromString extends decodeTo { readonly "Rebuild": NumberFromString } @@ -11412,7 +12874,7 @@ export interface NumberFromString extends decodeTo { * Encoding: * A number is encoded as a `string`. * - * @category Number + * @category schemas * @since 3.10.0 */ export const NumberFromString: NumberFromString = String.annotate({ @@ -11422,7 +12884,7 @@ export const NumberFromString: NumberFromString = String.annotate({ /** * Type-level representation of {@link FiniteFromString}. * - * @category Number + * @category models * @since 4.0.0 */ export interface FiniteFromString extends decodeTo { @@ -11441,7 +12903,7 @@ export interface FiniteFromString extends decodeTo { * Encoding: * - A finite number is encoded as a `string`. * - * @category Number + * @category schemas * @since 4.0.0 */ export const FiniteFromString: FiniteFromString = String.annotate({ @@ -11451,7 +12913,7 @@ export const FiniteFromString: FiniteFromString = String.annotate({ /** * Type-level representation of {@link BigIntFromString}. * - * @category BigInt + * @category models * @since 4.0.0 */ export interface BigIntFromString extends decodeTo { @@ -11483,7 +12945,7 @@ export interface BigIntFromString extends decodeTo { * @see {@link NumberFromString} for parsing JavaScript number strings, including non-finite values * @see {@link BigDecimalFromString} for parsing decimal number strings * - * @category BigInt + * @category schemas * @since 4.0.0 */ export const BigIntFromString: BigIntFromString = make(SchemaAST.bigIntString).pipe( @@ -11493,7 +12955,7 @@ export const BigIntFromString: BigIntFromString = make(SchemaAST.bigIntS /** * Type-level representation of {@link Trimmed}. * - * @category string + * @category models * @since 3.10.0 */ export interface Trimmed extends String { @@ -11503,7 +12965,7 @@ export interface Trimmed extends String { /** * Schema for strings that contains no leading or trailing whitespaces. * - * @category string + * @category schemas * @since 3.10.0 */ export const Trimmed: Trimmed = String.check(isTrimmed()) @@ -11511,7 +12973,7 @@ export const Trimmed: Trimmed = String.check(isTrimmed()) /** * Type-level representation of {@link Trim}. * - * @category string + * @category models * @since 3.10.0 */ export interface Trim extends decodeTo { @@ -11529,7 +12991,7 @@ export interface Trim extends decodeTo { * Encoding: * - The trimmed string is encoded as is. * - * @category string + * @category schemas * @since 3.10.0 */ export const Trim: Trim = String.annotate({ @@ -11539,7 +13001,7 @@ export const Trim: Trim = String.annotate({ /** * Type-level representation of {@link StringFromBase64}. * - * @category string + * @category models * @since 3.10.0 */ export interface StringFromBase64 extends decodeTo { @@ -11557,7 +13019,7 @@ export interface StringFromBase64 extends decodeTo { * Encoding: * - A `string` is encoded as a base64-encoded string. * - * @category string + * @category schemas * @since 3.10.0 */ export const StringFromBase64: StringFromBase64 = String.annotate({ @@ -11569,7 +13031,7 @@ export const StringFromBase64: StringFromBase64 = String.annotate({ /** * Type-level representation of {@link StringFromBase64Url}. * - * @category string + * @category models * @since 3.10.0 */ export interface StringFromBase64Url extends decodeTo { @@ -11587,7 +13049,7 @@ export interface StringFromBase64Url extends decodeTo { * Encoding: * - A `string` is encoded as a base64 (URL) encoded string. * - * @category string + * @category schemas * @since 3.10.0 */ export const StringFromBase64Url: StringFromBase64Url = String.annotate({ @@ -11599,7 +13061,7 @@ export const StringFromBase64Url: StringFromBase64Url = String.annotate({ /** * Type-level representation of {@link StringFromHex}. * - * @category string + * @category models * @since 3.10.0 */ export interface StringFromHex extends decodeTo { @@ -11617,7 +13079,7 @@ export interface StringFromHex extends decodeTo { * Encoding: * - A `string` is encoded as a hex string. * - * @category string + * @category schemas * @since 3.10.0 */ export const StringFromHex: StringFromHex = String.annotate({ @@ -11629,7 +13091,7 @@ export const StringFromHex: StringFromHex = String.annotate({ /** * Type-level representation of {@link StringFromUriComponent}. * - * @category string + * @category models * @since 3.12.0 */ export interface StringFromUriComponent extends decodeTo { @@ -11650,7 +13112,7 @@ export interface StringFromUriComponent extends decodeTo { * * **Example** (Decoding URI component strings) * - * ```ts + * ```ts import.meta.vitest * import { Schema } from "effect" * * const PaginationSchema = Schema.Struct({ @@ -11662,11 +13124,10 @@ export interface StringFromUriComponent extends decodeTo { * Schema.decodeTo(Schema.fromJsonString(PaginationSchema)) * ) * - * console.log(Schema.encodeSync(UrlSchema)({ maxItemPerPage: 10, page: 1 })) - * // %7B%22maxItemPerPage%22%3A10%2C%22page%22%3A1%7D + * Schema.encodeSync(UrlSchema)({ maxItemPerPage: 10, page: 1 }) // => "%7B%22maxItemPerPage%22%3A10%2C%22page%22%3A1%7D" * ``` * - * @category string + * @category schemas * @since 3.12.0 */ export const StringFromUriComponent: StringFromUriComponent = String.annotate({ @@ -11679,7 +13140,7 @@ export const StringFromUriComponent: StringFromUriComponent = String.annotate({ * Schema for property keys accepted by Effect schemas: finite `number`, * `symbol`, or `string`. * - * @category PropertyKey + * @category schemas * @since 4.0.0 */ export const PropertyKey = Union([Finite, Symbol, String]) @@ -11692,7 +13153,7 @@ export const PropertyKey = Union([Finite, Symbol, String]) * 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 Standard Schema + * @category schemas * @since 4.0.0 */ export const StandardSchemaV1FailureResult = Struct({ @@ -11705,7 +13166,7 @@ export const StandardSchemaV1FailureResult = Struct({ /** * Type-level representation of {@link BooleanFromBit}. * - * @category boolean + * @category models * @since 4.0.0 */ export interface BooleanFromBit extends decodeTo> { @@ -11728,7 +13189,7 @@ export interface BooleanFromBit extends decodeTo> { @@ -11766,17 +13227,18 @@ const Base64String = String.annotate({ * * The default JSON serializer encodes Uint8Array as a Base64 encoded string. * - * @category Uint8Array + * @category schemas * @since 4.0.0 */ export const Uint8Array: Uint8Array = instanceOf(globalThis.Uint8Array, { - typeConstructor: { - _tag: "Uint8Array" + representation: { + id: "effect/schema/Uint8Array", + payload: null }, - generation: { + toCode: () => ({ runtime: `Schema.Uint8Array`, Type: `globalThis.Uint8Array` - }, + }), expected: "Uint8Array", toCodecJson: () => link>()( @@ -11786,10 +13248,27 @@ export const Uint8Array: Uint8Array = instanceOf(globalThis.Uint8Array (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 Uint8Array + * @category models * @since 3.10.0 */ export interface Uint8ArrayFromBase64 extends decodeTo { @@ -11808,7 +13287,7 @@ export interface Uint8ArrayFromBase64 extends decodeTo { * Encoding: * - A `Uint8Array` is encoded as a base64-encoded string. * - * @category Uint8Array + * @category schemas * @since 3.10.0 */ export const Uint8ArrayFromBase64: Uint8ArrayFromBase64 = Base64String.pipe( @@ -11818,7 +13297,7 @@ export const Uint8ArrayFromBase64: Uint8ArrayFromBase64 = Base64String.pipe( /** * Type-level representation of {@link Uint8ArrayFromBase64Url}. * - * @category Uint8Array + * @category models * @since 3.10.0 */ export interface Uint8ArrayFromBase64Url extends decodeTo { @@ -11837,7 +13316,7 @@ export interface Uint8ArrayFromBase64Url extends decodeTo { * Encoding: * - A `Uint8Array` is encoded as a base64 (URL) encoded string. * - * @category Uint8Array + * @category schemas * @since 3.10.0 */ export const Uint8ArrayFromBase64Url: Uint8ArrayFromBase64Url = String.annotate({ @@ -11852,7 +13331,7 @@ export const Uint8ArrayFromBase64Url: Uint8ArrayFromBase64Url = String.annotate( /** * Type-level representation of {@link Uint8ArrayFromHex}. * - * @category Uint8Array + * @category models * @since 3.10.0 */ export interface Uint8ArrayFromHex extends decodeTo { @@ -11871,7 +13350,7 @@ export interface Uint8ArrayFromHex extends decodeTo { * Encoding: * - A `Uint8Array` is encoded as a hex encoded string. * - * @category Uint8Array + * @category schemas * @since 3.10.0 */ export const Uint8ArrayFromHex: Uint8ArrayFromHex = String.annotate({ @@ -11886,7 +13365,7 @@ export const Uint8ArrayFromHex: Uint8ArrayFromHex = String.annotate({ /** * Type-level representation of {@link DateTimeUtc}. * - * @category DateTime + * @category models * @since 3.10.0 */ export interface DateTimeUtc extends declare { @@ -11911,20 +13390,21 @@ export interface DateTimeUtc extends declare { * @see {@link DateTimeUtcFromMillis} for decoding epoch milliseconds into UTC values * @see {@link DateTimeZoned} for preserving zoned DateTime values * - * @category DateTime + * @category schemas * @since 3.10.0 */ export const DateTimeUtc: DateTimeUtc = declare( (u) => DateTime.isDateTime(u) && DateTime.isUtc(u), { - typeConstructor: { - _tag: "effect/DateTime.Utc" + representation: { + id: "effect/schema/DateTimeUtc", + payload: null }, - generation: { + toCode: () => ({ runtime: `Schema.DateTimeUtc`, Type: `DateTime.Utc`, - importDeclaration: `import * as DateTime from "effect/DateTime"` - }, + importDeclarations: [`import * as DateTime from "effect/DateTime"`] + }), expected: "DateTime.Utc", toCodecJson: () => link()( @@ -11933,9 +13413,8 @@ export const DateTimeUtc: DateTimeUtc = declare( ), toArbitrary: () => (fc, ctx) => fc.date(dateArbitraryConstraints( - ctx?.constraint, ctx?.constraint?.ordered?.order === DateTime.Order ? ctx.constraint.ordered : undefined, - { valid: true }, + { noInvalidDate: true }, DateTime.toDateUtc )) .map((date) => DateTime.fromDateUnsafe(date)), @@ -11944,10 +13423,27 @@ export const DateTimeUtc: DateTimeUtc = declare( } ) +/** + * 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 DateTime + * @category models * @since 3.12.0 */ export interface DateTimeUtcFromDate extends decodeTo { @@ -11973,12 +13469,12 @@ export interface DateTimeUtcFromDate extends decodeTo { * @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 DateValid} for validating Date instances without converting them + * @see {@link Date} for validating Date instances without converting them * - * @category DateTime + * @category schemas * @since 3.12.0 */ -export const DateTimeUtcFromDate: DateTimeUtcFromDate = DateValid.pipe( +export const DateTimeUtcFromDate: DateTimeUtcFromDate = Date.pipe( decodeTo(DateTimeUtc, { decode: SchemaGetter.dateTimeUtcFromInput(), encode: SchemaGetter.transform(DateTime.toDateUtc) @@ -11988,7 +13484,7 @@ export const DateTimeUtcFromDate: DateTimeUtcFromDate = DateValid.pipe( /** * Type-level representation of {@link DateTimeUtcFromString}. * - * @category DateTime + * @category models * @since 4.0.0 */ export interface DateTimeUtcFromString extends decodeTo { @@ -12009,7 +13505,11 @@ export interface DateTimeUtcFromString extends decodeTo { * * - A `DateTime.Utc` is encoded as a UTC ISO 8601 string. * - * @category DateTime + * @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({ @@ -12024,10 +13524,10 @@ export const DateTimeUtcFromString: DateTimeUtcFromString = String.annotate({ /** * Type-level representation of {@link DateTimeUtcFromMillis}. * - * @category DateTime + * @category models * @since 4.0.0 */ -export interface DateTimeUtcFromMillis extends decodeTo, Number> { +export interface DateTimeUtcFromMillis extends decodeTo, Int> { readonly "Rebuild": DateTimeUtcFromMillis } @@ -12042,10 +13542,14 @@ export interface DateTimeUtcFromMillis extends decodeTo * Encoding: * - A `DateTime.Utc` is encoded as a number of milliseconds since the Unix epoch. * - * @category DateTime + * @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 = Number.pipe( +export const DateTimeUtcFromMillis: DateTimeUtcFromMillis = Int.pipe( decodeTo(DateTimeUtc, { decode: SchemaGetter.dateTimeUtcFromInput(), encode: SchemaGetter.transform(DateTime.toEpochMillis) @@ -12055,7 +13559,7 @@ export const DateTimeUtcFromMillis: DateTimeUtcFromMillis = Number.pipe( /** * Type-level representation of {@link TimeZoneOffset}. * - * @category DateTime + * @category models * @since 3.10.0 */ export interface TimeZoneOffset extends declare { @@ -12071,24 +13575,25 @@ export interface TimeZoneOffset extends declare { * * - encodes `DateTime.TimeZone.Offset` as a number (offset in milliseconds) * - * @category DateTime + * @category schemas * @since 3.10.0 */ export const TimeZoneOffset: TimeZoneOffset = declare( DateTime.isTimeZoneOffset, { - typeConstructor: { - _tag: "effect/DateTime.TimeZone.Offset" + representation: { + id: "effect/schema/TimeZoneOffset", + payload: null }, - generation: { + toCode: () => ({ runtime: `Schema.TimeZoneOffset`, Type: `DateTime.TimeZone.Offset`, - importDeclaration: `import * as DateTime from "effect/DateTime"` - }, + importDeclarations: [`import * as DateTime from "effect/DateTime"`] + }), expected: "DateTime.TimeZone.Offset", toCodecJson: () => link()( - Number, + Int, SchemaTransformation.timeZoneOffsetFromNumber ), toArbitrary: () => (fc) => @@ -12098,10 +13603,27 @@ export const TimeZoneOffset: TimeZoneOffset = declare( } ) +/** + * 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 DateTime + * @category models * @since 3.10.0 */ export interface TimeZoneNamed extends declare { @@ -12119,20 +13641,21 @@ const TimeZoneNamedString = String.annotate({ expected: "an IANA time zone ident * * - encodes `DateTime.TimeZone.Named` as a string (IANA time zone identifier) * - * @category DateTime + * @category schemas * @since 3.10.0 */ export const TimeZoneNamed: TimeZoneNamed = declare( DateTime.isTimeZoneNamed, { - typeConstructor: { - _tag: "effect/DateTime.TimeZone.Named" + representation: { + id: "effect/schema/TimeZoneNamed", + payload: null }, - generation: { + toCode: () => ({ runtime: `Schema.TimeZoneNamed`, Type: `DateTime.TimeZone.Named`, - importDeclaration: `import * as DateTime from "effect/DateTime"` - }, + importDeclarations: [`import * as DateTime from "effect/DateTime"`] + }), expected: "DateTime.TimeZone.Named", toCodecJson: () => link()( @@ -12150,10 +13673,27 @@ export const TimeZoneNamed: TimeZoneNamed = declare( } ) +/** + * 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 DateTime + * @category models * @since 4.0.0 */ export interface TimeZoneNamedFromString extends decodeTo { @@ -12171,7 +13711,7 @@ export interface TimeZoneNamedFromString extends decodeTo * Encoding: * - A `DateTime.TimeZone.Named` is encoded as a `string`. * - * @category DateTime + * @category schemas * @since 4.0.0 */ export const TimeZoneNamedFromString: TimeZoneNamedFromString = TimeZoneNamedString.pipe( @@ -12181,7 +13721,7 @@ export const TimeZoneNamedFromString: TimeZoneNamedFromString = TimeZoneNamedStr /** * Type-level representation of {@link TimeZone}. * - * @category DateTime + * @category models * @since 3.10.0 */ export interface TimeZone extends declare { @@ -12202,20 +13742,21 @@ const TimeZoneString = String.annotate({ * - encodes `DateTime.TimeZone` as a string (IANA identifier or offset like * `+03:00`) * - * @category DateTime + * @category schemas * @since 3.10.0 */ export const TimeZone: TimeZone = declare( DateTime.isTimeZone, { - typeConstructor: { - _tag: "effect/DateTime.TimeZone" + representation: { + id: "effect/schema/TimeZone", + payload: null }, - generation: { + toCode: () => ({ runtime: `Schema.TimeZone`, Type: `DateTime.TimeZone`, - importDeclaration: `import * as DateTime from "effect/DateTime"` - }, + importDeclarations: [`import * as DateTime from "effect/DateTime"`] + }), expected: "DateTime.TimeZone", toCodecJson: () => link()( @@ -12236,10 +13777,27 @@ export const TimeZone: TimeZone = declare( } ) +/** + * 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 DateTime + * @category models * @since 4.0.0 */ export interface TimeZoneFromString extends decodeTo { @@ -12257,7 +13815,7 @@ export interface TimeZoneFromString extends decodeTo { * Encoding: * - A `DateTime.TimeZone` is encoded as a `string`. * - * @category DateTime + * @category schemas * @since 4.0.0 */ export const TimeZoneFromString: TimeZoneFromString = TimeZoneString.pipe( @@ -12267,7 +13825,7 @@ export const TimeZoneFromString: TimeZoneFromString = TimeZoneString.pipe( /** * Type-level representation of {@link DateTimeZoned}. * - * @category DateTime + * @category models * @since 3.10.0 */ export interface DateTimeZoned extends declare { @@ -12290,20 +13848,21 @@ const DateTimeZonedString = String.annotate({ * - encodes named zones by appending the IANA identifier in brackets, such as * `YYYY-MM-DDTHH:mm:ss.sss+HH:MM[Time/Zone]` * - * @category DateTime + * @category schemas * @since 3.10.0 */ export const DateTimeZoned: DateTimeZoned = declare( (u) => DateTime.isDateTime(u) && DateTime.isZoned(u), { - typeConstructor: { - _tag: "effect/DateTime.Zoned" + representation: { + id: "effect/schema/DateTimeZoned", + payload: null }, - generation: { + toCode: () => ({ runtime: `Schema.DateTimeZoned`, Type: `DateTime.Zoned`, - importDeclaration: `import * as DateTime from "effect/DateTime"` - }, + importDeclarations: [`import * as DateTime from "effect/DateTime"`] + }), expected: "DateTime.Zoned", toCodecJson: () => link()( @@ -12313,12 +13872,11 @@ export const DateTimeZoned: DateTimeZoned = declare( toArbitrary: () => (fc, ctx) => fc.tuple( fc.date(dateArbitraryConstraints( - ctx?.constraint, 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), - valid: true + noInvalidDate: true }, DateTime.toDateUtc )), @@ -12329,10 +13887,27 @@ export const DateTimeZoned: DateTimeZoned = declare( } ) +/** + * 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 DateTime + * @category models * @since 4.0.0 */ export interface DateTimeZonedFromString extends decodeTo { @@ -12350,7 +13925,7 @@ export interface DateTimeZonedFromString extends decodeTo * Encoding: * - A `DateTime.Zoned` is encoded as a `string`. * - * @category DateTime + * @category schemas * @since 4.0.0 */ export const DateTimeZonedFromString: DateTimeZonedFromString = DateTimeZonedString.pipe( @@ -12367,17 +13942,18 @@ export const DateTimeZonedFromString: DateTimeZonedFromString = DateTimeZonedStr * @category models * @since 3.10.0 */ -export interface Class extends - BottomLazy< - SchemaAST.Declaration, - decodeTo, S>, - readonly [S], - S["~type.mutability"], - S["~type.optionality"], - S["~type.constructor.default"], - S["~encoded.mutability"], - S["~encoded.optionality"] - > +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"] @@ -12467,6 +14043,8 @@ type InheritStaticMembers = C & Pick, @@ -12483,9 +14061,12 @@ function makeClass< const out = class extends Inherited { constructor(...[input, options]: ReadonlyArray) { - input = input ?? {} - const validated = struct.make(input, options) - super({ ...input, ...validated }, { ...options, disableChecks: true }) + 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 @@ -12514,7 +14095,7 @@ function makeClass< 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 { + static makeEffect(input: S["~type.make.in"], options?: MakeOptions): Effect.Effect { return (getClassSchema(this) as any).makeEffect(input ?? {}, options) } static annotate(annotations: Annotations.Declaration) { @@ -12564,7 +14145,14 @@ function makeClass< function getClassTransformation(self: new(...args: ReadonlyArray) => any) { return new SchemaTransformation.Transformation( - SchemaGetter.transform((input) => new self(input)), + SchemaGetter.transform((input) => + new self(input, { + "~payload": { + token: payloadToken, + value: input + } + }) + ), SchemaGetter.passthrough() ) } @@ -12585,26 +14173,34 @@ function getClassSchemaFactory( 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) => { - return input instanceof self || - Predicate.hasProperty(input, getClassTypeId(identifier)) ? + () => (input, ast, options) => { + return isClassValue(input) ? Effect.succeed(input) : - Effect.fail(new SchemaIssue.InvalidType(ast, Option_.some(input))) + Effect.fail(new SchemaIssue.InvalidType(ast, input, options)) }, { identifier, - [SchemaAST.ClassTypeId]: ([from]: readonly [SchemaAST.AST]) => new SchemaAST.Link(from, transformation), - toCodec: ([from]: readonly [Codec]) => new SchemaAST.Link(from.ast, transformation), + [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)})`, - "~sentinels": SchemaAST.collectSentinels(from.ast), + [InternalAnnotations.SENTINELS_ANNOTATION_KEY]: SchemaAST.collectSentinels(from.ast), ...annotations } ) @@ -12622,8 +14218,8 @@ type MissingSelfGeneric = /** * Creates a schema-backed class whose constructor validates input against a - * {@link Struct} schema. Construction throws a {@link SchemaError} on invalid - * input. + * {@link Struct} schema. Construction throws an `Error` with a + * `SchemaIssue.Issue` in its `cause` on invalid input. * * **When to use** * @@ -12635,13 +14231,23 @@ type MissingSelfGeneric = * 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 + * ```ts import.meta.vitest * import { Schema } from "effect" * * class Person extends Schema.Class("Person")({ @@ -12650,13 +14256,13 @@ type MissingSelfGeneric = * }) {} * * const alice = new Person({ name: "Alice", age: 30 }) - * console.log(alice.name) // "Alice" - * console.log(`${alice}`) // "Person({ name: Alice, age: 30 })" + * alice.name // => "Alice" + * String(alice) // => "Person({\"name\":\"Alice\",\"age\":30})" * ``` * * **Example** (Extending a class) * - * ```ts + * ```ts import.meta.vitest * import { Schema } from "effect" * * class Animal extends Schema.Class("Animal")({ @@ -12668,13 +14274,13 @@ type MissingSelfGeneric = * }) {} * * const dog = new Dog({ name: "Rex", breed: "Labrador" }) - * console.log(dog.name) // "Rex" - * console.log(dog.breed) // "Labrador" + * dog.name // => "Rex" + * dog.breed // => "Labrador" * ``` * * @see {@link TaggedClass} for adding a `_tag` literal field to the class schema - * @see {@link ErrorClass} for defining schema-backed error classes - * @see {@link TaggedErrorClass} for defining tagged schema-backed error classes + * @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 @@ -12724,7 +14330,7 @@ export const Class: { * * **Example** (Defining a tagged class) * - * ```ts + * ```ts import.meta.vitest * import { Schema } from "effect" * * class Circle extends Schema.TaggedClass()("Circle", { @@ -12732,8 +14338,8 @@ export const Class: { * }) {} * * const c = new Circle({ radius: 5 }) - * console.log(c._tag) // "Circle" - * console.log(c.radius) // 5 + * c._tag // => "Circle" + * c.radius // => 5 * ``` * * @category constructors @@ -12782,38 +14388,40 @@ export const TaggedClass: { * * **Example** (Schema-backed error) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Schema } from "effect" * - * class NotFound extends Schema.ErrorClass("NotFound")({ + * 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 ErrorClass: { +export const Error: { (identifier: string): { ( fields: Fields, annotations?: Annotations.Declaration]> - ): [Self] extends [never] ? MissingSelfGeneric<"Schema.ErrorClass"> + ): [Self] extends [never] ? MissingSelfGeneric<"Schema.Error"> : Class, Cause_.YieldableError & Brand> >( schema: S, annotations?: Annotations.Declaration - ): [Self] extends [never] ? MissingSelfGeneric<"Schema.ErrorClass"> : Class + ): [Self] extends [never] ? MissingSelfGeneric<"Schema.Error"> : Class } } = (identifier: string) => ( schema: Struct.Fields | Struct, annotations?: Annotations.Declaration]> -): [Self] extends [never] ? MissingSelfGeneric<"Schema.ErrorClass"> +): [Self] extends [never] ? MissingSelfGeneric<"Schema.Error"> : Class, Cause_.YieldableError & Brand> => { const struct = isStruct(schema) ? schema : Struct(schema) @@ -12840,28 +14448,31 @@ export const ErrorClass: { * * **Example** (Defining a tagged error class) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Schema } from "effect" * - * class NotFound extends Schema.TaggedErrorClass()("NotFound", { + * 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 TaggedErrorClass: { +export const TaggedError: { (identifier?: string): { ( tag: Tag, fields: Fields, annotations?: Annotations.Declaration]> - ): [Self] extends [never] ? MissingSelfGeneric<"Schema.TaggedErrorClass"> + ): [Self] extends [never] ? MissingSelfGeneric<"Schema.TaggedError"> : Class, Cause_.YieldableError & Brand> >( tag: Tag, @@ -12870,7 +14481,7 @@ export const TaggedErrorClass: { Self, readonly [Struct } & S["fields"]>>] > - ): [Self] extends [never] ? MissingSelfGeneric<"Schema.TaggedErrorClass"> + ): [Self] extends [never] ? MissingSelfGeneric<"Schema.TaggedError"> : Class } & S["fields"]>>, Cause_.YieldableError & Brand> } } = (identifier?: string) => { @@ -12884,7 +14495,7 @@ export const TaggedErrorClass: { unsafePreserveChecks: true }) : TaggedStruct(tagValue, schema) - return ErrorClass(identifier ?? tagValue)( + return Error(identifier ?? tagValue)( struct, annotations as Annotations.Declaration ) @@ -12896,82 +14507,53 @@ export const TaggedErrorClass: { // ----------------------------------------------------------------------------- /** - * A thunk that, given the `fast-check` module, returns an `Arbitrary`. - * Use this type when you need to defer instantiation of the arbitrary, for - * example to support recursive schemas. - * - * @category Arbitrary - * @since 4.0.0 - */ -export type LazyArbitrary = (fc: typeof FastCheck) => FastCheck.Arbitrary - -/** - * Derives a {@link LazyArbitrary} from a schema. The result is memoized so - * repeated calls with the same schema are cheap. + * Represents a function that builds a fast-check `Arbitrary` from the + * `fast-check` module. * - * **Details** + * **When to use** * - * Prefer {@link toArbitrary} when you need the arbitrary directly, or when you - * want derivation diagnostics via `{ report: true }`. Unsupported schema - * nodes, impossible constraints, invalid candidates, and recursive schemas - * without a finite terminal path fail immediately. + * Use as the result type of schema arbitrary derivation. * - * @category Arbitrary + * @category utility types * @since 4.0.0 */ -export function toArbitraryLazy(schema: S): LazyArbitrary { - const lawc = InternalArbitrary.memoized(schema.ast) - return (fc) => lawc(fc, {}) -} +export type Arbitrary = (fc: typeof FastCheck) => FastCheck.Arbitrary /** - * Derives a `fast-check` `Arbitrary` from a schema for property-based - * testing. The derived arbitrary generates values that satisfy the schema. + * 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. `{ report: true }` returns warnings such - * as `OpaqueFilter`, while derivation errors remain fail-fast. Recursive - * schemas use terminal branches and fail when no finite terminal path exists. + * 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 + * ```ts import.meta.vitest * import { Schema } from "effect" * import * as FastCheck from "fast-check" * - * const PersonArb = Schema.toArbitrary( + * const makePersonArbitrary = Schema.toArbitrary( * Schema.Struct({ name: Schema.String, age: Schema.Number }) * ) * - * // Sample a random value - * const sample = FastCheck.sample(PersonArb, 1)[0] - * console.log(typeof sample.name) // "string" + * const PersonArbitrary = makePersonArbitrary(FastCheck) + * FastCheck.sample(PersonArbitrary, 1) * ``` * - * @category Arbitrary + * @category generators * @since 4.0.0 */ -export function toArbitrary(schema: S): FastCheck.Arbitrary -export function toArbitrary( - schema: S, - options: { readonly report: true } -): Annotations.ToArbitrary.WithReport> -export function toArbitrary( - schema: S, - options?: { readonly report?: boolean } -): FastCheck.Arbitrary | Annotations.ToArbitrary.WithReport> { - if (options?.report === true) { - const lawc = InternalArbitrary.memoized(schema.ast) - const report = InternalArbitrary.makeReport() - InternalArbitrary.collectReport(schema.ast, report) - return { - value: lawc(FastCheck, {}), - report: InternalArbitrary.toReport(report) - } - } - return toArbitraryLazy(schema)(FastCheck) +export function toArbitrary(schema: S): Arbitrary { + const lawc = InternalArbitrary.memoized(schema.ast) + return (fc) => lawc(fc, {}) } // ----------------------------------------------------------------------------- @@ -12987,7 +14569,7 @@ export function toArbitrary( * The annotation is applied through this helper because adding it directly to * `Annotations.Bottom` would make schemas invariant. * - * @category Formatter + * @category formatting * @since 4.0.0 */ export function overrideToFormatter(toFormatter: () => Formatter) { @@ -13006,7 +14588,7 @@ export function overrideToFormatter(toFormatter: () => Formatter< * The optional `onBefore` hook lets you intercept specific AST nodes before * the default formatting logic runs. * - * @category Formatter + * @category formatting * @since 4.0.0 */ export function toFormatter(schema: S, options?: { @@ -13077,8 +14659,7 @@ export function toFormatter(schema: S, options?: { // handle post rest elements // --------------------------------------------- for (let j = 0; j < tail.length; j++) { - i += j - out.push(tail[j](t[i])) + out.push(tail[j](t[i + j])) } } @@ -13124,14 +14705,17 @@ export function toFormatter(schema: S, options?: { } } case "Union": { - const getCandidates = (t: any) => SchemaAST.getCandidates(t, ast.types) + 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) - const refinements = candidates.map(SchemaParser._is) for (let i = 0; i < candidates.length; i++) { - const is = refinements[i] + const [is, formatter] = compiled.get(candidates[i])! if (is(t)) { - return recur(candidates[i])(t) + return formatter(t) } } return format(t) @@ -13172,13 +14756,13 @@ export function overrideToEquivalence(toEquivalence: () => Equiva * * **Example** (Comparing structs) * - * ```ts + * ```ts import.meta.vitest * import { Schema } from "effect" * * const eq = Schema.toEquivalence(Schema.Struct({ id: Schema.Number, name: Schema.String })) * - * console.log(eq({ id: 1, name: "Alice" }, { id: 1, name: "Alice" })) // true - * console.log(eq({ id: 1, name: "Alice" }, { id: 2, name: "Alice" })) // false + * eq({ id: 1, name: "Alice" }, { id: 1, name: "Alice" }) // => true + * eq({ id: 1, name: "Alice" }, { id: 2, name: "Alice" }) // => false * ``` * * @category instances @@ -13193,15 +14777,18 @@ export function toEquivalence(schema: Schema): Equivalence.Equivalence // ----------------------------------------------------------------------------- /** - * Derives an intermediate `SchemaRepresentation.Document` from a schema. This - * document is used internally by {@link toJsonSchemaDocument} and related - * functions to produce JSON Schema output. + * Derives an intermediate `SchemaRepresentation.Document` from the encoded + * side of a schema. * - * @category Representation + * **Details** + * + * Use {@link toType} before this function to represent the type side instead. + * + * @category converting * @since 4.0.0 */ export function toRepresentation(schema: Constraint): SchemaRepresentation.Document { - return InternalStandard.fromAST(schema.ast) + return InternalToRepresentation.toRepresentation(schema.ast) } // ----------------------------------------------------------------------------- @@ -13258,7 +14845,7 @@ export interface ToJsonSchemaOptions { * * **Example** (Including custom annotations) * - * ```ts + * ```ts import.meta.vitest * import { Schema } from "effect" * * const schema = Schema.String.annotate({ @@ -13271,12 +14858,7 @@ export interface ToJsonSchemaOptions { * key === "markdownDescription" || key.startsWith("x-") * }) * - * console.log(doc.schema) - * // { - * // type: "string", - * // description: "A name", - * // markdownDescription: "The **name** field" - * // } + * doc.schema // => { type: "string", description: "A name", markdownDescription: "The **name** field" } * ``` */ readonly includeAnnotationKey?: ((key: string) => boolean) | undefined @@ -13289,14 +14871,16 @@ export interface ToJsonSchemaOptions { * * The `options` parameter controls generation details such as additional * properties and synthesized check descriptions; it does not change the draft - * target. + * target. Declarations are lowered through their `toCodecJson` or `toCodec` + * annotation when available before the representation document is compiled. * * **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. + * shape. Opaque declarations without a structural codec are represented by an + * unconstrained JSON Schema. * * @category converting * @since 4.0.0 @@ -13305,13 +14889,11 @@ export function toJsonSchemaDocument( schema: Constraint, options?: ToJsonSchemaOptions ): JsonSchema.Document<"draft-2020-12"> { - const sd = toRepresentation(schema) - const jd = InternalStandard.toJsonSchemaDocument(sd, options) - return { - dialect: "draft-2020-12", - schema: jd.schema, - definitions: jd.definitions - } + const document = InternalToRepresentation.toRepresentation( + toCodecJsonAST(schema.ast), + InternalToJsonSchemaDocument.toRepresentationOptions + ) + return InternalToJsonSchemaDocument.toJsonSchemaDocument(document, options) } // ----------------------------------------------------------------------------- @@ -13321,7 +14903,7 @@ export function toJsonSchemaDocument( /** * Type-level representation returned by {@link toCodecJson}. * - * @category Canonical Codecs + * @category converting * @since 4.0.0 */ export interface toCodecJson extends @@ -13350,35 +14932,94 @@ export interface toCodecJson extends * Derives a canonical JSON codec from a schema. The encoded form is `Json`, and * decoding produces the schema's `Type`. * - * @category Canonical Codecs + * **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. + * + * @category converting * @since 4.0.0 */ export function toCodecJson(schema: S): toCodecJson { - return make(toCodecJsonTop(schema.ast), { schema }) + return make(toCodecJsonAST(schema.ast), { schema }) } -const toCodecJsonTop = SchemaAST.applyToSelfOrLastLinkEncoding((ast) => { - const out = toCodecJsonBase(ast, toCodecJsonTop) - return out !== ast && SchemaAST.isOptional(ast) ? SchemaAST.optionalKeyLastLink(out) : out +/** @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 toCodecJsonBase(ast: SchemaAST.AST, recur: (ast: SchemaAST.AST) => SchemaAST.AST): SchemaAST.AST { +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)) { - const tps = SchemaAST.isDeclaration(ast) - ? ast.typeParameters.map((tp) => InternalSchema.make(SchemaAST.toEncoded(tp))) - : [] - const link = getLink(tps) - const to = recur(link.to) - return SchemaAST.replaceEncoding(ast, to === link.to ? [link] : [new SchemaAST.Link(to, link.transformation)]) + if (!Predicate.isFunction(getLink)) { + return SchemaAST.replaceEncoding(ast, [SchemaAST.unknownToJson]) } - return SchemaAST.replaceEncoding(ast, [SchemaAST.unknownToNull]) + 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": - case "ObjectKeyword": return SchemaAST.replaceEncoding(ast, [SchemaAST.unknownToJson]) + case "ObjectKeyword": + return SchemaAST.replaceEncoding(ast, [SchemaAST.objectKeywordToJson]) case "Undefined": case "Void": case "Literal": @@ -13389,13 +15030,11 @@ function toCodecJsonBase(ast: SchemaAST.AST, recur: (ast: SchemaAST.AST) => Sche case "BigInt": return ast.toCodecStringTree() case "Objects": { - if (ast.propertySignatures.some((ps) => typeof ps.name !== "string")) { - throw new globalThis.Error("Objects property names must be strings", { cause: ast }) - } + validateCanonicalObjectPropertyNames(ast) return ast.recur(recur, SchemaAST.parameterFromString) } case "Union": { - const sortedTypes = InternalSchema.jsonReorder(ast.types) + const sortedTypes = toCodecJsonReorder(ast.types) if (sortedTypes !== ast.types) { return new SchemaAST.Union( sortedTypes, @@ -13421,26 +15060,27 @@ function toCodecJsonBase(ast: SchemaAST.AST, recur: (ast: SchemaAST.AST) => Sche * Derives an isomorphism codec from a schema. The encoded form is the * schema's `Iso` type — the intermediate representation used for round-tripping. * - * @category Canonical Codecs + * @category converting * @since 4.0.0 */ export function toCodecIso(schema: S): Codec { - return make(toCodecIsoTop(SchemaAST.toType(schema.ast))) + return make(toCodecIsoAST(SchemaAST.toType(schema.ast))) } -const toCodecIsoTop = memoize((ast: SchemaAST.AST): SchemaAST.AST => { - const out = toCodecIsoBase(ast, toCodecIsoTop) - return out !== ast && SchemaAST.isOptional(ast) ? SchemaAST.optionalKeyLastLink(out) : out +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 toCodecIsoBase(ast: SchemaAST.AST, recur: (ast: SchemaAST.AST) => SchemaAST.AST): SchemaAST.AST { +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))) - const to = recur(link.to) - return SchemaAST.replaceEncoding(ast, to === link.to ? [link] : [new SchemaAST.Link(to, link.transformation)]) + return SchemaAST.replaceEncoding(ast, [SchemaAST.mapLink(link, recur)]) } return ast } @@ -13457,7 +15097,7 @@ function toCodecIsoBase(ast: SchemaAST.AST, recur: (ast: SchemaAST.AST) => Schem * A {@link Tree} of `string | undefined` nodes. Leaf values are either a * string representation or `undefined` for opaque/declaration types. * - * @category Canonical Codecs + * @category models * @since 4.0.0 */ export type StringTree = Tree @@ -13465,7 +15105,7 @@ export type StringTree = Tree /** * Type-level representation returned by {@link toCodecStringTree}. * - * @category Canonical Codecs + * @category converting * @since 4.0.0 */ export interface toCodecStringTree extends @@ -13494,22 +15134,23 @@ export interface toCodecStringTree extends * Converts a schema to the StringTree canonical codec, where every leaf value * becomes a string while preserving the original structure. * - * **Details** + * **Gotchas** * - * Declarations are converted to `undefined` (unless they have a - * `toCodecJson` or `toCodec` annotation). + * 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 Canonical Codecs + * @category converting * @since 4.0.0 */ export function toCodecStringTree(schema: S): toCodecStringTree { - return make(serializerStringTree(schema.ast), { schema }) + return make(toCodecStringTreeAST(schema.ast), { schema }) } /** * Type-level representation returned by {@link toCodecArrayFromSingle}. * - * @category Canonical Codecs + * @category converting * @since 4.0.0 */ export interface toCodecArrayFromSingle extends @@ -13548,11 +15189,11 @@ export interface toCodecArrayFromSingle extends * decoding convenience rather than a canonical StringTree representation. It * does not parse comma-separated strings. * - * @category Canonical Codecs + * @category converting * @since 4.0.0 */ export function toCodecArrayFromSingle(schema: S): toCodecArrayFromSingle { - return make(toCodecArrayFromSingleTop(schema.ast)) + return make(toCodecArrayFromSingleAST(schema.ast)) } type XmlEncoderOptions = { @@ -13577,11 +15218,11 @@ type XmlEncoderOptions = { * an `Effect` that succeeds with the XML string or fails with `SchemaError` if * codec encoding fails. * - * @category Canonical Codecs + * @category encoding * @since 4.0.0 */ -export function toEncoderXml( - codec: Codec, +export function toEncoderXml( + codec: ConstraintCodec, options?: XmlEncoderOptions ) { const rootName = InternalAnnotations.resolveIdentifier(codec.ast) ?? InternalAnnotations.resolveTitle(codec.ast) @@ -13675,7 +15316,7 @@ const xml = { } } -function getStringTreePriority(ast: SchemaAST.AST): number { +const toStringTreeReorder = makeReorder((ast: SchemaAST.AST) => { switch (ast._tag) { case "Null": case "Boolean": @@ -13687,27 +15328,29 @@ function getStringTreePriority(ast: SchemaAST.AST): number { default: return 1 } -} - -const treeReorder = InternalSchema.makeReorder(getStringTreePriority) +}) -function serializerTree( +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 getLink = ast.annotations?.toCodecJson ?? ast.annotations?.toCodec - if (Predicate.isFunction(getLink)) { - const tps = SchemaAST.isDeclaration(ast) - ? ast.typeParameters.map((tp) => make(recur(SchemaAST.toEncoded(tp)))) - : [] - const link = getLink(tps) - const to = recur(link.to) - return SchemaAST.replaceEncoding(ast, to === link.to ? [link] : [new SchemaAST.Link(to, link.transformation)]) + 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)]) } - return onMissingAnnotation(ast) + 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]) @@ -13724,13 +15367,11 @@ function serializerTree( case "BigInt": return ast.toCodecStringTree() case "Objects": { - if (ast.propertySignatures.some((ps) => typeof ps.name !== "string")) { - throw new globalThis.Error("Objects property names must be strings", { cause: ast }) - } + validateCanonicalObjectPropertyNames(ast) return ast.recur(recur, SchemaAST.parameterFromString) } case "Union": { - const sortedTypes = treeReorder(ast.types) + const sortedTypes = toStringTreeReorder(ast.types) if (sortedTypes !== ast.types) { return new SchemaAST.Union( sortedTypes, @@ -13768,71 +15409,272 @@ const booleanToString = new SchemaAST.Link( ) ) -const SERIALIZER_ENSURE_ARRAY = "~effect/Schema/SERIALIZER_ENSURE_ARRAY" +const arrayFromSingleTransformation = new SchemaTransformation.Transformation( + SchemaGetter.transform((input: ReadonlyArray | string) => typeof input === "string" ? [input] : input), + SchemaGetter.passthrough() +) -const isSerializerArrayFromSingle = (ast: SchemaAST.AST): boolean => - SchemaAST.isUnion(ast) && ast.annotations?.[SERIALIZER_ENSURE_ARRAY] === true +const isCodecArrayFromSingleLink = (link: SchemaAST.Link): boolean => + link.transformation === arrayFromSingleTransformation -const serializerStringTree = SchemaAST.applyToSelfOrLastLinkEncoding((ast) => { - if (isSerializerArrayFromSingle(ast)) { - return ast +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)) } - const out = serializerTree(ast, serializerStringTree, (ast) => SchemaAST.replaceEncoding(ast, [unknownToUndefined])) - if (out !== ast && SchemaAST.isOptional(ast)) { - return SchemaAST.optionalKeyLastLink(out) + 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 }) -const unknownToUndefined = new SchemaAST.Link( - SchemaAST.undefined, - new SchemaTransformation.Transformation( - SchemaGetter.passthrough(), - SchemaGetter.transform(() => undefined) - ) +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) ) -const toArrayFromSingleInputElement = (ast: SchemaAST.AST): SchemaAST.AST => - SchemaAST.isOptional(ast) ? SchemaAST.optionalKey(SchemaAST.unknown) : SchemaAST.unknown +/** + * 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) +) -const arrayFromSingleTransformation = new SchemaTransformation.Transformation( - SchemaGetter.transform((input: ReadonlyArray | string) => typeof input === "string" ? [input] : input), - SchemaGetter.passthrough() +/** + * 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) ) -const toCodecArrayFromSingleTop = SchemaAST.applyToSelfOrLastLinkEncoding((ast) => { - if (isSerializerArrayFromSingle(ast)) { - return ast - } - const out = onSerializerArrayFromSingle(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", - { [SERIALIZER_ENSURE_ARRAY]: true } - ), - out, - arrayFromSingleTransformation - ) - return SchemaAST.isOptional(ast) ? SchemaAST.optionalKey(ensure) : ensure - } - return out -}) +/** + * 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) +) -function onSerializerArrayFromSingle(ast: SchemaAST.AST): SchemaAST.AST { - return ast._tag === "Declaration" || ast._tag === "Arrays" || ast._tag === "Objects" || ast._tag === "Union" || - ast._tag === "Suspend" - ? ast.recur(toCodecArrayFromSingleTop) - : ast -} +/** + * 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 @@ -13842,7 +15684,19 @@ function onSerializerArrayFromSingle(ast: SchemaAST.AST): SchemaAST.AST { * Derives an `Iso` optic from a schema that isomorphically converts between * the schema's `Type` and its `Iso` (intermediate / serialized form). * - * @category Optic + * **Details** + * + * Reading through the `Iso` encodes the schema value, while replacing through + * it decodes the new focus. + * + * **Gotchas** + * + * Either direction can throw an `Error` with the generic message + * `"Schema validation failed"` and a `SchemaIssue.Issue` in its `cause`. Format + * the `cause` explicitly with `SchemaIssue.makeFormatterDefault()` when + * human-readable details are needed. + * + * @category converting * @since 4.0.0 */ export function toIso(schema: S): Optic_.Iso { @@ -13853,7 +15707,7 @@ export function toIso(schema: S): Optic_.Iso(_: S): Optic_.Iso { @@ -13863,7 +15717,7 @@ export function toIsoSource(_: S): Optic_.Iso(_: S): Optic_.Iso { @@ -13873,7 +15727,7 @@ export function toIsoFocus(_: S): Optic_.Iso extends @@ -13912,11 +15766,11 @@ export interface overrideToCodecIso extends * provided `decode` and `encode` getters to transform between the schema type * and the target codec. * - * @category Optic + * @category transforming * @since 4.0.0 */ export function overrideToCodecIso( - to: Codec, + to: ConstraintCodec, transformation: { readonly decode: SchemaGetter.Getter readonly encode: SchemaGetter.Getter @@ -13941,10 +15795,23 @@ export function overrideToCodecIso( * {@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** + * + * Schema encoding or decoding failures throw an `Error` with the generic message + * `"Schema validation failed"` and a `SchemaIssue.Issue` in its `cause`. Format + * the `cause` explicitly with `SchemaIssue.makeFormatterDefault()` when + * human-readable details are needed. Errors produced by {@link JsonPatch.apply} + * for invalid patch operations are separate from schema validation failures. + * * @category converting * @since 4.0.0 */ -export function toDifferJsonPatch(schema: Codec): Differ { +export function toDifferJsonPatch(schema: ConstraintCodec): Differ { const serializer = toCodecJson(schema) const get = SchemaParser.encodeSync(serializer) const set = SchemaParser.decodeSync(serializer) @@ -13964,7 +15831,7 @@ export function toDifferJsonPatch(schema: Codec): Differ = Node | TreeRecord | ReadonlyArray> @@ -13973,7 +15840,7 @@ export type Tree = Node | TreeRecord | ReadonlyArray> * A record node in a {@link Tree}: an object mapping string keys to child * `Tree` nodes. * - * @category Tree + * @category models * @since 4.0.0 */ export interface TreeRecord { @@ -13985,7 +15852,7 @@ export interface TreeRecord { * The resulting schema accepts a single node value, an array of trees, or an * object whose values are trees. * - * @category Tree + * @category schemas * @since 4.0.0 */ export function Tree(node: S) { @@ -14037,17 +15904,38 @@ export interface JsonObject { * * **Example** (Validating a JSON value) * - * ```ts - * import { Schema } from "effect" + * ```ts import.meta.vitest + * import { Option, Schema } from "effect" * - * const result = Schema.decodeUnknownOption(Schema.Json)({ key: [1, true, null] }) - * console.log(result._tag) // "Some" + * 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.Json) +export const Json: Codec = make(SchemaAST.annotate(SchemaAST.Json, { + toCode: () => ({ + runtime: "Schema.Json", + Type: "Schema.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, @@ -14090,7 +15978,29 @@ export interface MutableJsonObject { * @category schemas * @since 4.0.0 */ -export const MutableJson: Codec = make(SchemaAST.MutableJson) +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 @@ -14102,7 +16012,7 @@ export const MutableJson: Codec = make(SchemaAST.MutableJson) * annotations are taken from the last check; otherwise they are taken from * the base schema instance. * - * @category Schema Resolvers + * @category getters * @since 4.0.0 */ export function resolveAnnotations( @@ -14116,7 +16026,7 @@ export function resolveAnnotations( * annotations are those attached via `annotateKey` and live on the AST's * `context` rather than on the schema node itself. * - * @category Schema Resolvers + * @category getters * @since 4.0.0 */ export function resolveAnnotationsKey(schema: S): Annotations.Key | undefined { @@ -14149,7 +16059,7 @@ export declare namespace Annotations { * * **Example** (Defining your own annotations) * - * ```ts + * ```ts import.meta.vitest * import { Schema } from "effect" * * // Extend the Annotations interface with a custom `version` annotation @@ -14171,8 +16081,7 @@ export declare namespace Annotations { * * if (version) { * // Access individual parts of the version - * console.log(version[1]) - * // Output: 2 + * version[1] // => 2 * } * ``` * @@ -14200,7 +16109,7 @@ export declare namespace Annotations { * `message` first, then `expected`, and finally falls back to ``. * * Use this to name a failed filter in the default message: - * `Expected , got `. + * `Expected `. */ readonly expected?: string | undefined readonly title?: string | undefined @@ -14211,6 +16120,7 @@ export declare namespace Annotations { readonly format?: string | undefined readonly contentEncoding?: string | undefined readonly contentMediaType?: string | undefined + readonly contentSchema?: Json | undefined } /** @@ -14258,7 +16168,7 @@ export declare namespace Annotations { * 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 , got ` shape. + * `Expected ` shape. */ readonly message?: string | undefined /** @@ -14273,7 +16183,7 @@ export declare namespace Annotations { * 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, got null`. + * `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 @@ -14281,10 +16191,6 @@ export declare namespace Annotations { */ readonly identifier?: string | undefined readonly parseOptions?: SchemaAST.ParseOptions | undefined - /** - * Optional metadata used to identify or extend the filter with custom data. - */ - readonly meta?: Meta | undefined /** * Accumulated brands when multiple brands are added with `Schema.brand`. */ @@ -14335,11 +16241,17 @@ export declare namespace Annotations { export interface Declaration = readonly []> extends Bottom { + readonly representation?: + | SchemaRepresentation.RepresentationAnnotation + | undefined readonly toCodec?: | ((typeParameters: TypeParameters.Encoded) => SchemaAST.Link) | undefined readonly toCodecJson?: - | ((typeParameters: TypeParameters.Encoded) => SchemaAST.Link) + | ((typeParameters: TypeParameters.Encoded) => SchemaAST.Link | undefined) + | undefined + readonly toCodecStringTree?: + | ((typeParameters: TypeParameters.Encoded) => SchemaAST.Link | undefined) | undefined readonly toCodecIso?: | ((typeParameters: TypeParameters.Type) => SchemaAST.Link) @@ -14347,20 +16259,13 @@ export declare namespace Annotations { readonly toArbitrary?: ToArbitrary.Declaration | undefined readonly toEquivalence?: ToEquivalence.Declaration | undefined readonly toFormatter?: ToFormatter.Declaration | undefined - readonly typeConstructor?: { - readonly _tag: string - readonly [key: string]: unknown - } | undefined - readonly generation?: { - readonly runtime: string - readonly Type: string - readonly Encoded?: string | undefined - readonly importDeclaration?: string | undefined - } | undefined + readonly toCode?: SchemaRepresentation.Generation.Declaration | undefined /** * Used to collect sentinels from a Declaration SchemaAST. * - * @internal + * **Details** + * + * Reserved to internal use only. */ readonly "~sentinels"?: ReadonlyArray | undefined } @@ -14374,6 +16279,20 @@ export declare namespace Annotations { * @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. * @@ -14394,10 +16313,6 @@ export declare namespace Annotations { * `message`. */ readonly identifier?: string | undefined - /** - * Optional metadata used to identify or extend the filter with custom data. - */ - readonly meta?: Meta | undefined /** * Optional hints used by arbitrary derivation for this filter. * @@ -14417,6 +16332,8 @@ export declare namespace Annotations { * * **Details** * + * Reserved to internal use only. + * * Example: `minLength` on an array is a structural filter. */ readonly "~structural"?: boolean | undefined @@ -14424,8 +16341,7 @@ export declare namespace Annotations { /** * Types used by arbitrary-derivation annotations to configure `toArbitrary` - * hooks, filter hints, candidate sources, diagnostics, and merged generation - * constraints. + * hooks, filter hints, candidate sources, and merged generation constraints. * * @since 4.0.0 */ @@ -14438,8 +16354,7 @@ export declare namespace Annotations { * `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. With `{ report: true }`, this is reported as - * `OpaqueFilter`. + * the filter predicate. * * @category models * @since 4.0.0 @@ -14506,7 +16421,7 @@ export declare namespace Annotations { * 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`, `valid`, and `unique` are true when any + * `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. * @@ -14520,7 +16435,6 @@ export declare namespace Annotations { readonly integer?: boolean | undefined readonly noInfinity?: boolean | undefined readonly noNaN?: boolean | undefined - readonly valid?: boolean | undefined readonly unique?: boolean | undefined readonly ordered?: OrderedConstraint | undefined } @@ -14628,58 +16542,6 @@ export declare namespace Annotations { typeParameters: { readonly [K in keyof TypeParameters]: TypeParameter } ): (fc: typeof FastCheck, context: Context) => Output } - - /** - * Wraps a derived value together with arbitrary-derivation diagnostics. - * - * @category models - * @since 4.0.0 - */ - export interface WithReport { - readonly value: A - readonly report: Report - } - - /** - * Diagnostics collected while deriving an arbitrary. - * - * **Details** - * - * Reports contain warnings only. Unsupported schema nodes, impossible - * constraints, invalid candidate weights, and throwing candidate factories - * fail immediately. - * - * @category models - * @since 4.0.0 - */ - export interface Report { - readonly warnings: ReadonlyArray - } - - /** - * Non-fatal arbitrary-derivation warning. - * - * @category models - * @since 4.0.0 - */ - export type Warning = OpaqueFilterWarning - - /** - * Warning emitted when a filter is handled only by the final `.filter`. - * - * **Details** - * - * The filter is still enforced. The warning means it did not contribute - * a constraint or candidate, so generation may rely on fast-check discards. - * - * @category models - * @since 4.0.0 - */ - export interface OpaqueFilterWarning { - readonly _tag: "OpaqueFilter" - readonly path: ReadonlyArray - readonly description?: string | undefined - } } /** @@ -14737,262 +16599,22 @@ export declare namespace Annotations { * * **Details** * - * The optional `message` field overrides the default issue message. + * 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 } - - /** - * Registry of metadata payloads emitted by built-in schema filters and checks. - * - * **Details** - * - * Do not augment this interface with custom metadata; extend `MetaDefinitions` - * instead. - * - * @category models - * @since 4.0.0 - */ - export interface BuiltInMetaDefinitions { - // String Meta - readonly isStringFinite: { - readonly _tag: "isStringFinite" - readonly regExp: globalThis.RegExp - } - readonly isStringBigInt: { - readonly _tag: "isStringBigInt" - readonly regExp: globalThis.RegExp - } - readonly isStringSymbol: { - readonly _tag: "isStringSymbol" - readonly regExp: globalThis.RegExp - } - readonly isMinLength: { - readonly _tag: "isMinLength" - readonly minLength: number - } - readonly isMaxLength: { - readonly _tag: "isMaxLength" - readonly maxLength: number - } - readonly isLengthBetween: { - readonly _tag: "isLengthBetween" - readonly minimum: number - readonly maximum: number - } - readonly isPattern: { - readonly _tag: "isPattern" - readonly regExp: globalThis.RegExp - } - readonly isTrimmed: { - readonly _tag: "isTrimmed" - readonly regExp: globalThis.RegExp - } - readonly isUUID: { - readonly _tag: "isUUID" - readonly regExp: globalThis.RegExp - readonly version: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | undefined - } - readonly isGUID: { - readonly _tag: "isGUID" - readonly regExp: globalThis.RegExp - } - readonly isULID: { - readonly _tag: "isULID" - readonly regExp: globalThis.RegExp - } - readonly isBase64: { - readonly _tag: "isBase64" - readonly regExp: globalThis.RegExp - } - readonly isBase64Url: { - readonly _tag: "isBase64Url" - readonly regExp: globalThis.RegExp - } - readonly isStartsWith: { - readonly _tag: "isStartsWith" - readonly startsWith: string - readonly regExp: globalThis.RegExp - } - readonly isEndsWith: { - readonly _tag: "isEndsWith" - readonly endsWith: string - readonly regExp: globalThis.RegExp - } - readonly isIncludes: { - readonly _tag: "isIncludes" - readonly includes: string - readonly regExp: globalThis.RegExp - } - readonly isUppercased: { - readonly _tag: "isUppercased" - readonly regExp: globalThis.RegExp - } - readonly isLowercased: { - readonly _tag: "isLowercased" - readonly regExp: globalThis.RegExp - } - readonly isCapitalized: { - readonly _tag: "isCapitalized" - readonly regExp: globalThis.RegExp - } - readonly isUncapitalized: { - readonly _tag: "isUncapitalized" - readonly regExp: globalThis.RegExp - } - // Number Meta - readonly isFinite: { - readonly _tag: "isFinite" - } - readonly isInt: { - readonly _tag: "isInt" - } - readonly isMultipleOf: { - readonly _tag: "isMultipleOf" - readonly divisor: number - } - readonly isGreaterThan: { - readonly _tag: "isGreaterThan" - readonly exclusiveMinimum: number - } - readonly isGreaterThanOrEqualTo: { - readonly _tag: "isGreaterThanOrEqualTo" - readonly minimum: number - } - readonly isLessThan: { - readonly _tag: "isLessThan" - readonly exclusiveMaximum: number - } - readonly isLessThanOrEqualTo: { - readonly _tag: "isLessThanOrEqualTo" - readonly maximum: number - } - readonly isBetween: { - readonly _tag: "isBetween" - readonly minimum: number - readonly maximum: number - readonly exclusiveMinimum?: boolean | undefined - readonly exclusiveMaximum?: boolean | undefined - } - // BigInt Meta - readonly isGreaterThanBigInt: { - readonly _tag: "isGreaterThanBigInt" - readonly exclusiveMinimum: bigint - } - readonly isGreaterThanOrEqualToBigInt: { - readonly _tag: "isGreaterThanOrEqualToBigInt" - readonly minimum: bigint - } - readonly isLessThanBigInt: { - readonly _tag: "isLessThanBigInt" - readonly exclusiveMaximum: bigint - } - readonly isLessThanOrEqualToBigInt: { - readonly _tag: "isLessThanOrEqualToBigInt" - readonly maximum: bigint - } - readonly isBetweenBigInt: { - readonly _tag: "isBetweenBigInt" - readonly minimum: bigint - readonly maximum: bigint - readonly exclusiveMinimum?: boolean | undefined - readonly exclusiveMaximum?: boolean | undefined - } - // Date Meta - readonly isDateValid: { - readonly _tag: "isDateValid" - } - readonly isGreaterThanDate: { - readonly _tag: "isGreaterThanDate" - readonly exclusiveMinimum: globalThis.Date - } - readonly isGreaterThanOrEqualToDate: { - readonly _tag: "isGreaterThanOrEqualToDate" - readonly minimum: globalThis.Date - } - readonly isLessThanDate: { - readonly _tag: "isLessThanDate" - readonly exclusiveMaximum: globalThis.Date - } - readonly isLessThanOrEqualToDate: { - readonly _tag: "isLessThanOrEqualToDate" - readonly maximum: globalThis.Date - } - readonly isBetweenDate: { - readonly _tag: "isBetweenDate" - readonly minimum: globalThis.Date - readonly maximum: globalThis.Date - readonly exclusiveMinimum?: boolean | undefined - readonly exclusiveMaximum?: boolean | undefined - } - // Objects Meta - readonly isMinProperties: { - readonly _tag: "isMinProperties" - readonly minProperties: number - } - readonly isMaxProperties: { - readonly _tag: "isMaxProperties" - readonly maxProperties: number - } - readonly isPropertiesLengthBetween: { - readonly _tag: "isPropertiesLengthBetween" - readonly minimum: number - readonly maximum: number - } - readonly isPropertyNames: { - readonly _tag: "isPropertyNames" - readonly propertyNames: SchemaAST.AST - } - // Arrays Meta - readonly isUnique: { - readonly _tag: "isUnique" - } - // Declaration Meta - readonly isMinSize: { - readonly _tag: "isMinSize" - readonly minSize: number - } - readonly isMaxSize: { - readonly _tag: "isMaxSize" - readonly maxSize: number - } - readonly isSizeBetween: { - readonly _tag: "isSizeBetween" - readonly minimum: number - readonly maximum: number - } - } - - /** - * Union of all metadata payloads defined by `BuiltInMetaDefinitions`. - * - * @category utility types - * @since 4.0.0 - */ - export type BuiltInMeta = BuiltInMetaDefinitions[keyof BuiltInMetaDefinitions] - - /** - * Augmentable registry of schema filter metadata payloads. - * - * **Details** - * - * Extend this interface to add custom values accepted by annotation `meta` - * fields. - * - * @category models - * @since 4.0.0 - */ - export interface MetaDefinitions extends BuiltInMetaDefinitions {} - - /** - * Union of built-in and user-augmented schema filter metadata payloads. - * - * @category utility types - * @since 4.0.0 - */ - export type Meta = MetaDefinitions[keyof MetaDefinitions] } diff --git a/repos/effect/packages/effect/src/SchemaAST.ts b/repos/effect/packages/effect/src/SchemaAST.ts index a3b7c54509..b1a8897873 100644 --- a/repos/effect/packages/effect/src/SchemaAST.ts +++ b/repos/effect/packages/effect/src/SchemaAST.ts @@ -13,16 +13,15 @@ import * as Arr from "./Array.ts" import * as Cause from "./Cause.ts" -import type * as Combiner from "./Combiner.ts" import * as Effect from "./Effect.ts" import * as Exit from "./Exit.ts" import { format, formatPropertyKey } from "./Formatter.ts" -import { memoize } from "./Function.ts" +import { identity, memoize, memoizeIdempotent } from "./Function.ts" import { effectIsExit, iterateEager } from "./internal/effect.ts" -import * as internalRecord from "./internal/record.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 Option from "./Option.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" @@ -451,6 +450,8 @@ export type Encoding = readonly [Link, ...Array] * 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 @@ -517,6 +518,35 @@ export interface ParseOptions { * @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 */ @@ -533,7 +563,7 @@ export const defaultParseOptions: ParseOptions = {} * * - `isOptional` — the property key may be absent from the input. * - `isMutable` — the property is `readonly` when `false`. - * - `defaultValue` — an {@link Encoding} applied during construction to + * - `constructorDefault` — a {@link Link} applied during construction to * supply missing values. * - `annotations` — key-level annotations (e.g. description of the key * itself). @@ -547,19 +577,19 @@ export class Context { readonly isOptional: boolean readonly isMutable: boolean /** Used for constructor default values (e.g. `withConstructorDefault` API) */ - readonly defaultValue: Encoding | undefined + readonly constructorDefault: Link | undefined readonly annotations: Schema.Annotations.Key | undefined constructor( isOptional: boolean, isMutable: boolean, /** Used for constructor default values (e.g. `withConstructorDefault` API) */ - defaultValue: Encoding | undefined = undefined, + constructorDefault: Link | undefined = undefined, annotations: Schema.Annotations.Key | undefined = undefined ) { this.isOptional = isOptional this.isMutable = isMutable - this.defaultValue = defaultValue + this.constructorDefault = constructorDefault this.annotations = annotations } } @@ -672,10 +702,10 @@ export class Declaration extends Base { } /** @internal */ getParser(): SchemaParser.Parser { - const run = this.run(this.typeParameters) - return (oinput, options) => { - if (Option.isNone(oinput)) return Effect.succeedNone - return Effect.mapEager(run(oinput.value, this, options), Option.some) + 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) { @@ -816,7 +846,8 @@ export class Void extends Base { readonly _tag = "Void" /** @internal */ getParser() { - return fromAnyToConst(undefined) + const succeed = InternalParser.succeed(undefined) + return (input: unknown) => input === InternalParser.missing ? InternalParser.missingExit : succeed } /** @internal */ toCodecJson(): AST { @@ -1080,9 +1111,9 @@ function isTemplateLiteralPart(ast: AST): ast is TemplateLiteralPart { return true case "Literal": case "TemplateLiteral": - return ast.checks === undefined + return !ast.checks case "Union": - return ast.checks === undefined && ast.types.every(isTemplateLiteralPart) + return !ast.checks && ast.types.every(isTemplateLiteralPart) default: return false } @@ -1106,6 +1137,10 @@ export class TemplateLiteral extends Base { readonly parts: ReadonlyArray /** @internal */ readonly encodedParts: ReadonlyArray + /** @internal */ + readonly literals: ReadonlyArray + /** @internal */ + readonly suffixLengths: ReadonlyArray constructor( parts: ReadonlyArray, @@ -1116,25 +1151,40 @@ export class TemplateLiteral extends Base { ) { 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(recur: (ast: AST) => SchemaParser.Parser): SchemaParser.Parser { - const parser = recur(this.asTemplateLiteralParser()) - return (oinput: Option.Option, options: ParseOptions) => - Effect.mapBothEager(parser(oinput, options), { - onSuccess: () => oinput, - onFailure: (issue) => new SchemaIssue.Composite(this, oinput, [issue]) + 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 { @@ -1142,7 +1192,7 @@ export class TemplateLiteral extends Base { } /** @internal */ matchPart(s: string, options: ParseOptions): string | undefined { - return segmentTemplateLiteralParts(this.encodedParts, s, options) === undefined ? undefined : s + return segmentTemplateLiteralParts(this, s, options) === undefined ? undefined : s } /** @internal */ asTemplateLiteralParser(): Arrays { @@ -1152,12 +1202,14 @@ export class TemplateLiteral extends Base { tuple, new SchemaTransformation.Transformation( SchemaGetter.transformOrFail((s: string, options) => { - const segments = segmentTemplateLiteralParts(this.encodedParts, s, options) - if (segments !== undefined) return Effect.succeed(segments) + const segments = segmentTemplateLiteralParts(this, s, options) + if (segments) return Effect.succeed(segments) return Effect.fail( - new SchemaIssue.InvalidValue(Option.some(s), { - message: `Expected a string matching template literal parts, got ${format(s)}` - }) + new SchemaIssue.InvalidValue( + { expected: "a string matching template literal parts" }, + s, + options + ) ) }), SchemaGetter.transform((parts) => parts.join("")) @@ -1228,11 +1280,11 @@ export type LiteralValue = string | number | boolean | bigint * * **Example** (Creating a literal AST) * - * ```ts + * ```ts import.meta.vitest * import { SchemaAST } from "effect" * * const ast = new SchemaAST.Literal("active") - * console.log(ast.literal) // "active" + * ast.literal // => "active" * ``` * * @see {@link LiteralValue} @@ -1309,7 +1361,8 @@ export class String extends Base { } /** @internal */ matchPart(s: string, options: ParseOptions): string | undefined { - return applyTemplateLiteralPartChecks(this, s, options) + const checks = this.checks + return checks && !options.disableChecks && collectIssues(checks, s, undefined, this, options) ? undefined : s } /** @internal */ getExpected(): string { @@ -1366,20 +1419,24 @@ export class Number extends Base { return this._match(isStringFiniteRegExp, s, options) } private _match(regexp: RegExp, s: string, options: ParseOptions): number | undefined { - return regexp.test(s) - ? applyTemplateLiteralPartChecks(this, globalThis.Number(s), options) - : 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, "isFinite") || hasCheck(this.checks, "isInt"))) { + if ( + this.checks && + (hasCheck(this.checks, "effect/schema/isFinite") || hasCheck(this.checks, "effect/schema/isInt")) + ) { return this } - return replaceEncoding(this, [numberToJson]) + return replaceEncoding(this, [numberToJson(this.checks)]) } /** @internal */ toCodecStringTree(): AST { - if (this.checks && (hasCheck(this.checks, "isFinite") || hasCheck(this.checks, "isInt"))) { + if (this.toCodecJson() === this) { return replaceEncoding(this, [finiteToString]) } return replaceEncoding(this, [numberToString]) @@ -1390,16 +1447,25 @@ export class Number extends Base { } } -// oxlint-disable-next-line only-used-in-recursion - @gcanti what's this? :-) -function hasCheck(checks: ReadonlyArray>, tag: string): boolean { - return checks.some((c) => { - switch (c._tag) { - case "Filter": - return c.annotations?.meta?._tag === tag - case "FilterGroup": - return hasCheck(c.checks, tag) - } - }) +function hasCheck(checks: ReadonlyArray>, id: string): boolean { + return checks.some((check) => + check.annotations?.representation?.id === id || + (check._tag === "FilterGroup" && hasCheck(check.checks, id)) + ) +} + +function numberToJson(checks: Checks | undefined): Link { + const encodedFinite = !checks + ? finite + : appendChecks(finite, checks) + + return new Link( + new Union([encodedFinite, nonFiniteLiterals], "anyOf"), + new SchemaTransformation.Transformation( + SchemaGetter.Number(), + SchemaGetter.transform((n) => globalThis.Number.isFinite(n) ? n : globalThis.String(n)) + ) + ) } /** @@ -1481,7 +1547,8 @@ export class Symbol extends Base { } /** @internal */ matchKey(s: symbol, options: ParseOptions): symbol | undefined { - return applyTemplateLiteralPartChecks(this, s, options) + if (options.disableChecks || !this.checks) return s + return collectIssues(this.checks, s, undefined, this, options) ? undefined : s } /** @internal */ toCodecStringTree(): AST { @@ -1534,9 +1601,10 @@ export class BigInt extends Base { } /** @internal */ matchPart(s: string, options: ParseOptions): bigint | undefined { - return isStringBigIntRegExp.test(s) - ? applyTemplateLiteralPartChecks(this, globalThis.BigInt(s), options) - : 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 { @@ -1590,15 +1658,14 @@ export const bigInt = new BigInt() * * **Example** (Inspecting a tuple AST) * - * ```ts + * ```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)) { - * console.log(ast.elements.length) // 2 - * console.log(ast.rest.length) // 0 + * [ast.elements.length, ast.rest.length] // => [2, 0] * } * ``` * @@ -1630,58 +1697,71 @@ export class Arrays extends Base { this.rest = rest this.encodingChecks = encodingChecks - // A required element cannot follow an optional element. ts(1257) - const i = elements.findIndex(isOptional) - if (i !== -1 && (elements.slice(i + 1).some((e) => !isOptional(e)) || rest.length > 1)) { + 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) - if (rest.length > 1 && rest.slice(1).some(isOptional)) { - throw new Error("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(recur: (ast: AST) => SchemaParser.Parser): SchemaParser.Parser { + getParser( + compile: SchemaParser.Compiler, + compileConstructorDefault: SchemaParser.Compiler = compile + ): SchemaParser.Parser { // oxlint-disable-next-line @typescript-eslint/no-this-alias const ast = this - const elements = ast.elements.map((ast) => ({ ast, parser: recur(ast) })) - const rest = ast.rest.map((ast) => ({ ast, parser: recur(ast) })) - const elementLen = elements.length - - const [head, ...tail] = rest - const tailLen = tail.length + 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] + return elements![index] } else if (index >= tailThreshold) { - return tail[index - tailThreshold] + return rest![index - tailThreshold + 1] } - return head + return rest![0] } - return Effect.fnUntracedEager(function*(oinput, options) { - if (oinput._tag === "None") { - return oinput + return Effect.fnUntracedEager(function*(input, options) { + if (input === InternalParser.missing) { + return InternalParser.missing } - const input = oinput.value // If the input is not an array, return early with an error if (!Array.isArray(input)) { - return yield* Effect.fail(new SchemaIssue.InvalidType(ast, oinput)) + 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, - oinput, + input, len, - tailThreshold: resolveTailThreshold(len, elementLen, tailLen), + tailThreshold: Math.max(elementLen, len - tailLen), output: new globalThis.Array(len), issues: undefined as Arr.NonEmptyArray | undefined, options @@ -1698,19 +1778,24 @@ export class Arrays extends Base { // --------------------------------------------- if (ast.rest.length === 0 && len > elementLen) { for (let i = elementLen; i <= len - 1; i++) { - const issue = new SchemaIssue.Pointer([i], new SchemaIssue.UnexpectedKey(ast, input[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, oinput, [issue])) + return yield* Effect.fail( + new SchemaIssue.Composite(ast, [issue], input, options) + ) } } } if (state.issues) { - return yield* Effect.fail(new SchemaIssue.Composite(ast, oinput, state.issues)) + return yield* Effect.fail( + new SchemaIssue.Composite(ast, state.issues, input, options) + ) } - return Option.some(state.output) + return state.output }) } private _rebuild(recur: (ast: AST) => AST, checks: Checks | undefined, encodingChecks: Checks | undefined) { @@ -1745,7 +1830,7 @@ export class Arrays extends Base { } const parseArray = iterateEager<{ readonly ast: AST - readonly oinput: Option.Option + readonly input: unknown readonly len: number readonly getParser: ( tailThreshold: number, @@ -1757,14 +1842,18 @@ const parseArray = iterateEager<{ issues: Array | undefined }, unknown>()({ onItem(s, item, i) { - const value = i < s.len ? Option.some(item) : Option.none() + const value = i < s.len ? item : InternalParser.missing return s.getParser(s.tailThreshold, i).parser(value, s.options) }, - step(s, _, exit, i) { + step(s, item, exit, i) { if (exit._tag === "Failure") { return wrapPropertyKeyIssue(s, s.ast, i, exit) - } else if (exit.value._tag === "Some") { - s.output[i] = exit.value.value + } + 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 @@ -1773,20 +1862,14 @@ const parseArray = iterateEager<{ if (s.issues) s.issues.push(issue) else s.issues = [issue] } else { - return Exit.fail(new SchemaIssue.Composite(s.ast, s.oinput, [issue])) + return Exit.fail( + new SchemaIssue.Composite(s.ast, [issue], s.input, s.options) + ) } } } }) -function resolveTailThreshold( - inputLen: number, - elementLen: number, - tailLen: number -) { - return Math.max(elementLen, inputLen - tailLen) -} - const resolveConcurrency = (value: number | "unbounded" | undefined) => { value = value === "unbounded" ? Infinity : value ?? 1 return value > 1 ? { concurrency: value } : undefined @@ -1794,7 +1877,7 @@ const resolveConcurrency = (value: number | "unbounded" | undefined) => { const wrapPropertyKeyIssue = ( s: { - readonly oinput: Option.Option + readonly input: unknown readonly options: ParseOptions issues: Array | undefined }, @@ -1810,7 +1893,13 @@ const wrapPropertyKeyIssue = ( return Exit.failCause( Cause.map( exit.cause, - (issue) => new SchemaIssue.Composite(ast, s.oinput, [new SchemaIssue.Pointer([key], issue)]) + (issue) => + new SchemaIssue.Composite( + ast, + [new SchemaIssue.Pointer([key], issue)], + s.input, + s.options + ) ) ) } @@ -1819,7 +1908,9 @@ const wrapPropertyKeyIssue = ( if (s.issues) s.issues.push(pointer) else s.issues = [pointer] } else { - return Exit.fail(new SchemaIssue.Composite(ast, s.oinput, [pointer])) + return Exit.fail( + new SchemaIssue.Composite(ast, [pointer], s.input, s.options) + ) } } @@ -1887,37 +1978,6 @@ export class PropertySignature { } } -/** - * Represents a bidirectional merge strategy for index signature key-value pairs. - * - * **Details** - * - * Used by {@link IndexSignature} when the same key appears multiple times - * (e.g. from `Schema.extend` or overlapping records). Provides separate - * `decode` and `encode` combiners that determine how duplicate entries are - * merged. - * - * @see {@link IndexSignature} - * @category models - * @since 4.0.0 - */ -export class KeyValueCombiner { - readonly decode: Combiner.Combiner | undefined - readonly encode: Combiner.Combiner | undefined - - constructor( - decode: Combiner.Combiner | undefined, - encode: Combiner.Combiner | undefined - ) { - this.decode = decode - this.encode = encode - } - /** @internal */ - flip(): KeyValueCombiner { - return new KeyValueCombiner(this.encode, this.decode) - } -} - type IndexSignatureParameter = | String | Number @@ -1956,7 +2016,6 @@ function isIndexSignatureParameter(ast: AST): ast is IndexSignatureParameter { * - `parameter` — the key type AST (e.g. {@link String} for `string` keys, * {@link TemplateLiteral} for patterned keys). * - `type` — the value type SchemaAST. - * - `merge` — optional {@link KeyValueCombiner} for handling duplicate keys. * * **Gotchas** * @@ -1971,19 +2030,16 @@ function isIndexSignatureParameter(ast: AST): ast is IndexSignatureParameter { export class IndexSignature { readonly parameter: IndexSignatureParameter readonly type: AST - readonly merge: KeyValueCombiner | undefined constructor( parameter: AST, - type: AST, - merge: KeyValueCombiner | undefined + type: AST ) { if (!isIndexSignatureParameter(parameter)) { throw new Error(`Invalid index signature parameter ${parameter._tag}`) } this.parameter = parameter this.type = type - this.merge = merge if (isOptional(type) && !containsUndefined(type)) { throw new Error("Cannot use `Schema.optionalKey` with index signatures, use `Schema.optional` instead.") } @@ -2014,17 +2070,14 @@ export class IndexSignature { * * **Example** (Inspecting a struct AST) * - * ```ts + * ```ts import.meta.vitest * import { Schema, SchemaAST } from "effect" * * const schema = Schema.Struct({ name: Schema.String }) * const ast = schema.ast * * if (SchemaAST.isObjects(ast)) { - * for (const ps of ast.propertySignatures) { - * console.log(ps.name, ps.type._tag) - * } - * // "name" "String" + * ast.propertySignatures.map((ps) => [ps.name, ps.type._tag]) // => [["name", "String"]] * } * ``` * @@ -2062,101 +2115,126 @@ export class Objects extends Base { } } /** @internal */ - getParser(recur: (ast: AST) => SchemaParser.Parser): SchemaParser.Parser { + 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 = [] - const expectedKeysSet = new Set() - const properties: Array<{ - readonly ps: PropertySignature | IndexSignature - readonly parser: SchemaParser.Parser - readonly name: PropertyKey - readonly type: AST - }> = [] for (const ps of ast.propertySignatures) { expectedKeys.push(ps.name) - expectedKeysSet.add(ps.name) - properties.push({ - ps, - parser: recur(ps.type), - name: ps.name, - type: ps.type - }) } + const hasProperties = expectedKeys.length const indexCount = ast.indexSignatures.length + let expectedKeysSet = hasProperties && indexCount ? new Set(expectedKeys) : undefined // --------------------------------------------- // handle empty struct // --------------------------------------------- - if (ast.propertySignatures.length === 0 && ast.indexSignatures.length === 0) { + if (!hasProperties && !indexCount) { return fromRefinement(ast, Predicate.isNotNullish) } - const parseIndexes = indexCount > 0 ? - iterateEager<{ - readonly oinput: Option.Option - readonly input: Record - readonly options: ParseOptions - readonly out: Record - issues: Array | undefined - }, [key: PropertyKey, is: IndexSignature]>()({ - onItem: Effect.fnUntracedEager(function*( - s, - [key, is] - ) { - const parserKey = recur(parameterFromPropertyKey(is.parameter)) - const effKey = parserKey(Option.some(key), s.options) - const exitKey = (effectIsExit(effKey) ? effKey : yield* Effect.exit(effKey)) as Exit.Exit< - Option.Option, - SchemaIssue.Issue - > - if (exitKey._tag === "Failure") { - const eff = wrapPropertyKeyIssue(s, ast, key, exitKey) - if (eff) yield* eff - return - } - - const value: Option.Option = Option.some(s.input[key]) - const parserValue = recur(is.type) - const effValue = parserValue(value, s.options) - const exitValue = effectIsExit(effValue) ? effValue : yield* Effect.exit(effValue) - if (exitValue._tag === "Failure") { - const eff = wrapPropertyKeyIssue(s, ast, key, exitValue) - if (eff) yield* eff - return - } else if (exitKey.value._tag === "Some" && exitValue.value._tag === "Some") { - const k2 = exitKey.value.value - if (expectedKeysSet.has(key) || expectedKeysSet.has(k2)) { - return - } - const v2 = exitValue.value.value - if (is.merge && is.merge.decode && Object.hasOwn(s.out, k2)) { - const [k, v] = is.merge.decode.combine([k2, s.out[k2]], [k2, v2]) - internalRecord.set(s.out, k, v) - } else { - internalRecord.set(s.out, k2, v2) - } - } - }), + 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 - return Effect.fnUntracedEager(function*(oinput, options) { - if (oinput._tag === "None") { - return oinput + return Effect.fnUntracedEager(function*(input, options) { + if (input === InternalParser.missing) { + return InternalParser.missing } - const input = oinput.value as Record // 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, oinput)) + return yield* Effect.fail(new SchemaIssue.InvalidType(ast, input, options)) + } + 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 } + const record = input as Record const out: Record = {} const state = { ast, - oinput, - input, + input: record, out, issues: undefined as Arr.NonEmptyArray | undefined, options @@ -2169,14 +2247,16 @@ export class Objects extends Base { // handle excess properties // --------------------------------------------- let inputKeys: Array | undefined - if (ast.indexSignatures.length === 0 && (onExcessPropertyError || onExcessPropertyPreserve)) { - inputKeys = Reflect.ownKeys(input) + 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 issue = new SchemaIssue.Pointer([key], new SchemaIssue.UnexpectedKey(ast, input[key])) + 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) @@ -2185,11 +2265,13 @@ export class Objects extends Base { } continue } else { - return yield* Effect.fail(new SchemaIssue.Composite(ast, oinput, [issue])) + return yield* Effect.fail( + new SchemaIssue.Composite(ast, [issue], input, options) + ) } } else { // preserve key - internalRecord.set(out, key, input[key]) + InternalRecord.assignProperty(out, key, record[key]) } } } @@ -2200,20 +2282,34 @@ export class Objects extends Base { // --------------------------------------------- // handle property signatures // --------------------------------------------- - const eff = parseProperties(state, properties, concurrency) - if (eff) yield* eff + if (hasProperties) { + const eff = parseProperties(state, properties!, concurrency) + if (eff) yield* eff + } // --------------------------------------------- // handle index signatures // --------------------------------------------- - if (parseIndexes) { - const keyPairs = Arr.empty<[PropertyKey, IndexSignature]>() + 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 is = ast.indexSignatures[i] - const keys = getIndexSignatureKeys(input, is.parameter, options) + const index = indexes![i] + const keys = getIndexSignatureKeys(record, index.is.parameter, options) for (let j = 0; j < keys.length; j++) { - const key = keys[j] - keyPairs.push([key, is]) + keyPairs.push([keys[j], index]) } } const eff = parseIndexes(state, keyPairs, concurrency) @@ -2221,26 +2317,27 @@ export class Objects extends Base { } if (state.issues) { - return yield* Effect.fail(new SchemaIssue.Composite(ast, oinput, 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(input)).concat(expectedKeys) + const keys = (inputKeys ?? Reflect.ownKeys(record)).concat(expectedKeys) const preserved: Record = {} for (const key of keys) { if (Object.hasOwn(out, key)) { - internalRecord.set(preserved, key, out[key]) + InternalRecord.assignProperty(preserved, key, out[key]) } } - return Option.some(preserved) + return preserved } - return Option.some(out) + return out }) } private _rebuild( recur: (ast: AST) => AST, recurParameter: (ast: AST) => AST, - flipMerge: boolean, checks: Checks | undefined, encodingChecks: Checks | undefined ): Objects { @@ -2252,10 +2349,9 @@ export class Objects extends Base { const indexes = mapOrSame(this.indexSignatures, (is) => { const p = recurParameter(is.parameter) const t = recur(is.type) - const merge = flipMerge ? is.merge?.flip() : is.merge - return p === is.parameter && t === is.type && merge === is.merge + return p === is.parameter && t === is.type ? is - : new IndexSignature(p, t, merge) + : new IndexSignature(p, t) }) return props === this.propertySignatures && indexes === this.indexSignatures && checks === this.checks && @@ -2273,11 +2369,11 @@ export class Objects extends Base { } /** @internal */ flip(recur: (ast: AST) => AST): AST { - return this._rebuild(recur, recur, true, this.encodingChecks, this.checks) + 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, false, this.checks, this.encodingChecks) + return this._rebuild(recur, recurParameter, this.checks, this.encodingChecks) } /** @internal */ getExpected(): string { @@ -2286,42 +2382,41 @@ export class Objects extends Base { } } +type ObjectParserState = { + readonly ast: Objects + readonly input: Record + readonly options: ParseOptions + readonly out: Record + issues: Array | undefined +} + type ParsedProperty = { - readonly ps: PropertySignature | IndexSignature readonly parser: SchemaParser.Parser readonly name: PropertyKey readonly type: AST } -const parseProperties = iterateEager<{ - readonly ast: AST - readonly oinput: Option.Option - readonly input: Record - readonly options: ParseOptions - readonly out: Record - issues: Array | undefined -}, ParsedProperty>()({ - onItem( - s: { - readonly oinput: Option.Option - readonly input: Record - readonly options: ParseOptions - readonly out: Record - issues: Array | undefined - }, - p - ) { - const value: Option.Option = Object.hasOwn(s.input, p.name) - ? Option.some(s.input[p.name]) - : Option.none() +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(s, p, exit) { if (exit._tag === "Failure") { return wrapPropertyKeyIssue(s, s.ast, p.name, exit) - } else if (exit.value._tag === "Some") { - internalRecord.set(s.out, p.name, exit.value.value) - } else if (!isOptional(p.type)) { + } + 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) @@ -2329,7 +2424,7 @@ const parseProperties = iterateEager<{ return } else { return Exit.fail( - new SchemaIssue.Composite(s.ast, s.oinput, [issue]) + new SchemaIssue.Composite(s.ast, [issue], s.input, s.options) ) } } @@ -2422,6 +2517,21 @@ export type Sentinel = { 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": @@ -2447,7 +2557,7 @@ function getCandidateTypes(ast: AST): ReadonlyArray { case "Objects": return ast.propertySignatures.length || ast.indexSignatures.length ? ["object"] - : ["object", "array"] + : ["string", "number", "boolean", "symbol", "bigint", "object", "array", "function"] case "Enum": return Array.from(new Set(ast.enums.map(([, v]) => typeof v))) case "Literal": @@ -2471,12 +2581,12 @@ function getCandidateTypes(ast: AST): ReadonlyArray { } /** @internal */ -export function collectSentinels(ast: AST): Array { +export function collectSentinels(ast: AST): ReadonlyArray { switch (ast._tag) { default: return [] case "Declaration": { - const s = ast.annotations?.["~sentinels"] + const s = ast.annotations?.[InternalAnnotations.SENTINELS_ANNOTATION_KEY] return Array.isArray(s) ? s : [] } case "Objects": @@ -2493,52 +2603,90 @@ export function collectSentinels(ast: AST): Array { return [] }) case "Arrays": - return ast.elements.flatMap((e, i) => { - return isLiteral(e) && !isOptional(e) - ? [{ key: i, literal: e.literal }] - : [] + 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 "Suspend": return collectSentinels(ast.thunk()) } } -type CandidateIndex = { - byType?: { [K in Type]?: Array } - bySentinel?: Map>> - otherwise?: { [K in Type]?: Array } -} +type CandidateIndex = + | ((input: any, isConstructor: boolean) => ReadonlyArray) + | { + bySentinel?: Map>> + otherwise?: { [K in Type]?: Array } + } const candidateIndexCache = new WeakMap, CandidateIndex>() +const emptyCandidates: ReadonlyArray = Object.freeze([]) function getIndex(types: ReadonlyArray): CandidateIndex { let idx = candidateIndexCache.get(types) if (idx) return idx idx = {} - for (const a of types) { - const encoded = toEncoded(a) + let literalCandidates: Map> | null | undefined + for (let i = 0; i < types.length; i++) { + const a = types[i] + const encoded = toCandidate(a) if (isNever(encoded)) continue - const types = getCandidateTypes(encoded) - const sentinels = collectSentinels(encoded) + if (literalCandidates !== null) { + 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 { + literalCandidates = null + } + } - // by-type (always filled – cheap primary filter) - idx.byType ??= {} - for (const t of types) (idx.byType[t] ??= []).push(a) + const sentinels = collectSentinels(encoded) - if (sentinels.length > 0) { // discriminated variants + if (sentinels.length) { // discriminated variants idx.bySentinel ??= new Map() for (const { key, literal } of sentinels) { let m = idx.bySentinel.get(key) if (!m) idx.bySentinel.set(key, m = new Map()) let arr = m.get(literal) if (!arr) m.set(literal, arr = []) - arr.push(a) + if (arr[arr.length - 1] !== i) arr.push(i) } } else { // non-discriminated idx.otherwise ??= {} - for (const t of types) (idx.otherwise[t] ??= []).push(a) + const candidateTypes = getCandidateTypes(encoded) + for (const t of candidateTypes) (idx.otherwise[t] ??= []).push(i) + } + } + + if (literalCandidates) { + literalCandidates.forEach(Object.freeze) + idx = (input) => literalCandidates.get(input) ?? emptyCandidates + } else if (idx.bySentinel?.size === 1 && !idx.otherwise) { + for (const [key, byValue] of idx.bySentinel) { + const candidates = byValue as unknown as Map> + for (const [literal, indexes] of byValue) { + candidates.set(literal, Object.freeze(indexes.map((index) => types[index]))) + } + idx = (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 + } } } @@ -2548,7 +2696,7 @@ function getIndex(types: ReadonlyArray): CandidateIndex { function filterLiterals(input: any) { return (ast: AST) => { - const encoded = toEncoded(ast) + const encoded = toCandidate(ast) return encoded._tag === "Literal" ? encoded.literal === input : encoded._tag === "UniqueSymbol" ? @@ -2563,26 +2711,41 @@ function filterLiterals(input: any) { * * @internal */ -export function getCandidates(input: any, types: ReadonlyArray): ReadonlyArray { +export function getCandidates( + input: any, + types: ReadonlyArray, + isConstructor = false +): ReadonlyArray { const idx = getIndex(types) + if (typeof idx === "function") return idx(input, isConstructor) + const runtimeType: Type = input === null ? "null" : Array.isArray(input) ? "array" : typeof input // 1. Try sentinel-based dispatch (most selective) if (idx.bySentinel) { - const base = idx.otherwise?.[runtimeType] ?? [] - if (runtimeType === "object" || runtimeType === "array") { + const base = idx.otherwise?.[runtimeType] ?? emptyCandidates + if (Predicate.isObjectKeyword(input)) { + const selected = new Set(base) for (const [k, m] of idx.bySentinel) { - if (Object.hasOwn(input, k)) { - const match = m.get((input as any)[k]) - if (match) return [...match, ...base].filter(filterLiterals(input)) + const value = Object.hasOwn(input, k) ? (input as any)[k] : undefined + if (value !== undefined) { + const match = m.get(value) + if (match) { + for (const candidate of match) selected.add(candidate) + } + } else if (isConstructor) { + for (const indexes of m.values()) { + for (const candidate of indexes) selected.add(candidate) + } } } + return Array.from(selected).sort((a, b) => a - b).map((i) => types[i]) } - return base + return base.map((i) => types[i]) } // 2. Fallback: runtime-type dispatch only - return (idx.byType?.[runtimeType] ?? []).filter(filterLiterals(input)) + return (idx.otherwise?.[runtimeType] ?? emptyCandidates).map((i) => types[i]).filter(filterLiterals(input)) } /** @@ -2600,15 +2763,14 @@ export function getCandidates(input: any, types: ReadonlyArray): ReadonlyAr * * **Example** (Inspecting a union AST) * - * ```ts + * ```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)) { - * console.log(ast.types.length) // 2 - * console.log(ast.mode) // "anyOf" + * [ast.types.length, ast.mode] // => [2, "anyOf"] * } * ``` * @@ -2637,38 +2799,46 @@ export class Union extends Base { this.encodingChecks = encodingChecks } /** @internal */ - getParser(recur: (ast: AST) => SchemaParser.Parser): SchemaParser.Parser { + getParser( + compile: SchemaParser.Compiler, + compileConstructorDefault?: SchemaParser.Compiler + ): SchemaParser.Parser { // oxlint-disable-next-line @typescript-eslint/no-this-alias const ast = this - return (oinput, options) => { - if (oinput._tag === "None") { - return Effect.succeed(oinput) + 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 input = oinput.value - const candidates = getCandidates(input, ast.types) const state = { ast, - recur, - oinput, + compile, input, out: undefined, - successes: [], + successes: ast.mode === "oneOf" ? [] : undefined, issues: undefined as Arr.NonEmptyArray | undefined, options } const concurrency = resolveConcurrency(options?.concurrency) - const eff = parseUnion(state, candidates, concurrency) + const eff = parseUnion(state, candidates, concurrency ? { ...concurrency, orderedStep: true } : undefined) if (!eff) { - return state.out - ? Effect.succeed(state.out) - : Effect.fail(new SchemaIssue.AnyOf(ast, input, state.issues ?? [])) + if (state.out) return state.out + return Effect.fail(new SchemaIssue.AnyOf(ast, state.issues ?? [], input, options)) } - return Effect.flatMap(eff, (_) => { - return state.out - ? Effect.succeed(state.out) - : Effect.fail(new SchemaIssue.AnyOf(ast, input, state.issues ?? [])) + 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)) }) } } @@ -2733,19 +2903,29 @@ export class Union extends Base { } } +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 recur: (ast: AST) => SchemaParser.Parser + readonly compile: (ast: AST) => SchemaParser.Parser readonly ast: Union - readonly oinput: Option.Option readonly input: unknown readonly options: ParseOptions - out: Option.Option | undefined - successes: Array + out: Exit.Success | undefined + readonly successes: Array | undefined issues: Array | undefined }, AST>()({ onItem(s, ast) { - const parser = s.recur(ast) - return parser(s.oinput, s.options) + const parser = s.compile(ast) + return parser(s.input, s.options) }, step(s, candidate, exit) { if (exit._tag === "Failure") { @@ -2756,13 +2936,14 @@ const parseUnion = iterateEager<{ if (s.issues) s.issues.push(issue) else s.issues = [issue] } else { - if (s.out && s.ast.mode === "oneOf") { + if (s.out && s.successes) { s.successes.push(candidate) - return Exit.fail(new SchemaIssue.OneOf(s.ast, s.input, s.successes)) + return Exit.fail(new SchemaIssue.OneOf(s.ast, s.successes, s.input, s.options)) } - s.out = exit.value - s.successes.push(candidate) - if (s.ast.mode === "anyOf") { + s.out = exit + if (s.successes) { + s.successes.push(candidate) + } else { return Exit.void } } @@ -2775,14 +2956,6 @@ const nonFiniteLiterals = new Union([ new Literal("NaN") ], "anyOf") -const numberToJson = new Link( - new Union([number, nonFiniteLiterals], "anyOf"), - new SchemaTransformation.Transformation( - SchemaGetter.Number(), - SchemaGetter.transform((n) => globalThis.Number.isFinite(n) ? n : globalThis.String(n)) - ) -) - function formatIsMutable(isMutable: boolean | undefined): string { return isMutable ? "" : "readonly " } @@ -2816,7 +2989,7 @@ export function memoizeThunk(f: () => A): () => A { * * **Example** (Defining recursive schema ASTs) * - * ```ts + * ```ts import.meta.vitest * import { Schema, SchemaAST } from "effect" * * interface Category { @@ -2829,7 +3002,7 @@ export function memoizeThunk(f: () => A): () => A { * children: Schema.Array(Schema.suspend((): Schema.Codec => Category)) * }) * - * // The recursive branch is a Suspend node + * SchemaAST.isObjects(Category.ast) // => true * ``` * * @see {@link isSuspend} @@ -2847,15 +3020,16 @@ export class Suspend extends Base { encoding?: Encoding, context?: Context ) { - if (checks !== undefined) { + if (checks) { throw new Error("Cannot add checks to Suspend") } super(annotations, undefined, encoding, context) this.thunk = memoizeThunk(thunk) } /** @internal */ - getParser(recur: (ast: AST) => SchemaParser.Parser): SchemaParser.Parser { - return recur(this.thunk()) + 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) { @@ -2884,8 +3058,8 @@ export class Suspend extends Base { * * - `run` — the validation function. Returns `undefined` on success, or an * `Issue` on failure. - * - `annotations` — optional filter-level metadata (expected message, meta - * tags, arbitrary constraint hints). + * - `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). * @@ -2990,7 +3164,7 @@ export function makeFilter( aborted: boolean = false ): Filter { return new Filter( - (input, ast, options) => SchemaIssue.make(input, ast, filter(input, ast, options)), + (input, ast, options) => SchemaIssue.normalizeFilterOutput(ast, filter(input, ast, options), input, options), annotations, aborted ) @@ -3002,12 +3176,38 @@ export function makeFilterByGuard( annotations?: Schema.Annotations.Filter ): Filter { return new Filter( - (input: E) => is(input) ? undefined : new SchemaIssue.InvalidValue(Option.some(input)), + (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()]) + /** * Creates a {@link Filter} that validates strings by running `RegExp.test`. * @@ -3019,21 +3219,25 @@ export function makeFilterByGuard( * **Details** * * The filter can be used with `Schema.filter` or attached directly to a - * `String` AST node through checks. The regular expression source is stored in - * annotations for serialization and arbitrary generation. + * `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** * - * Use a non-global, non-sticky regular expression, or reset `lastIndex` - * yourself, because `RegExp.test` is stateful for expressions with the `g` or - * `y` flag. + * 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 + * ```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} @@ -3042,14 +3246,19 @@ export function makeFilterByGuard( */ 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) => regExp.test(s), + (s: string) => { + pattern.lastIndex = 0 + return pattern.test(s) + }, { expected: `a string matching the RegExp ${source}`, - meta: { - _tag: "isPattern", - regExp + representation: { + id: "effect/schema/isPattern", + payload: { source, flags: regExp.flags } }, + toJsonSchema: () => ({ pattern: source }), arbitrary: { constraint: { patterns: [regExp.source] @@ -3071,6 +3280,13 @@ function modifyOwnPropertyDescriptors( 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) { @@ -3086,9 +3302,15 @@ export function replaceContext(ast: A, context: Context | undefin if (ast.context === context) { return ast } - return modifyOwnPropertyDescriptors(ast, (d) => { + 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 */ @@ -3109,7 +3331,7 @@ export function annotate(ast: A, annotations: Schema.Annotations. /** @internal */ export function replaceChecks(ast: A, checks: Checks | undefined): A { - if (ast._tag === "Suspend" && checks !== undefined) { + if (ast._tag === "Suspend" && checks) { throw new Error("Cannot add checks to Suspend") } if (ast.checks === checks) { @@ -3125,14 +3347,17 @@ export function appendChecks(ast: A, checks: Checks | undefined): 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 to = f(last.to) - if (to !== last.to) { - return Arr.append(encoding.slice(0, encoding.length - 1), new Link(to, last.transformation)) - } - return encoding + const out = mapLink(last, f) + return out === last ? encoding : Arr.append(encoding.slice(0, encoding.length - 1), out) } /** @internal */ @@ -3140,6 +3365,11 @@ 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 { @@ -3148,6 +3378,21 @@ export function applyToSelfOrLastLinkEncoding(f: (ast: AST) => 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, @@ -3208,7 +3453,7 @@ export function annotateKey(ast: A, annotations: Schema.Annotatio new Context( ast.context.isOptional, ast.context.isMutable, - ast.context.defaultValue, + ast.context.constructorDefault, { ...ast.context.annotations, ...annotations } ) : new Context(false, false, undefined, annotations) @@ -3216,42 +3461,33 @@ export function annotateKey(ast: A, annotations: Schema.Annotatio } /** @internal */ -export const optionalKeyLastLink = applyToLastLink(optionalKey) - -/** - * Marks an AST node's property key as optional by setting - * {@link Context.isOptional} to `true`. - * - * **Details** - * - * Also propagates the optional flag through the last link of the encoding - * chain if present. - * - * @see {@link isOptional} - * @see {@link Context} - * @category transforming - * @since 4.0.0 - */ -export function optionalKey(ast: A): A { +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.defaultValue, ast.context.annotations) : + new Context(true, ast.context.isMutable, ast.context.constructorDefault, ast.context.annotations) : ast.context : new Context(true, false) return optionalKeyLastLink(replaceContext(ast, context)) -} +}) -const mutableKeyLastLink = applyToLastLink(mutableKey) +const optionalKeyLastLink = applyToLastLink(optionalKey) + +/** @internal */ +export const optional = memoize((ast: A): Union => + optionalKey(new Union([ast, undefined_], "anyOf")) +) /** @internal */ -export function mutableKey(ast: A): A { +export const mutableKey = memoizeIdempotent((ast: A): A => { const context = ast.context ? ast.context.isMutable === false ? - new Context(ast.context.isOptional, true, ast.context.defaultValue, ast.context.annotations) : + 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( @@ -3262,10 +3498,10 @@ export function withConstructorDefault( SchemaGetter.withDefault(defaultValue), SchemaGetter.passthrough() ) - const encoding: Encoding = [new Link(unknown, transformation)] + const constructorDefault = new Link(unknown, transformation) const context = ast.context ? - new Context(ast.context.isOptional, ast.context.isMutable, encoding, ast.context.annotations) : - new Context(false, false, encoding) + new Context(ast.context.isOptional, ast.context.isMutable, constructorDefault, ast.context.annotations) : + new Context(false, false, constructorDefault) return replaceContext(ast, context) } @@ -3327,11 +3563,11 @@ function parseParameter(ast: AST): { } /** @internal */ -export function record(key: AST, value: AST, keyValueCombiner: KeyValueCombiner | undefined): Objects { +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, keyValueCombiner)) + indexSignatures.map((parameter) => new IndexSignature(parameter, value)) ) } @@ -3361,6 +3597,20 @@ 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. @@ -3373,12 +3623,12 @@ export function isMutable(ast: AST): boolean { * * **Example** (Getting the type AST) * - * ```ts + * ```ts import.meta.vitest * import { Schema, SchemaAST } from "effect" * * const schema = Schema.NumberFromString * const typeAst = SchemaAST.toType(schema.ast) - * console.log(typeAst._tag) // "Number" + * typeAst._tag // => "Number" * ``` * * @see {@link toEncoded} @@ -3386,19 +3636,22 @@ export function isMutable(ast: AST): boolean { * @category transforming * @since 4.0.0 */ -export const toType = memoize((ast: A): A => { +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 = type.encodingChecks + 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 - if (type === ast) { - d.checks.value = combineChecks(type.checks, encodingChecks) - } + d.checks.value = combineChecks(type.checks, checks) }) } return type @@ -3417,12 +3670,12 @@ export const toType = memoize((ast: A): A => { * * **Example** (Getting the encoded AST) * - * ```ts + * ```ts import.meta.vitest * import { Schema, SchemaAST } from "effect" * * const schema = Schema.NumberFromString * const encodedAst = SchemaAST.toEncoded(schema.ast) - * console.log(encodedAst._tag) // "String" + * encodedAst._tag // => "String" * ``` * * @see {@link toType} @@ -3430,7 +3683,7 @@ export const toType = memoize((ast: A): A => { * @category transforming * @since 4.0.0 */ -export const toEncoded = memoize((ast: AST): AST => { +export const toEncoded = memoizeIdempotent((ast: AST): AST => { return toType(flip(ast)) }) @@ -3493,54 +3746,44 @@ function fromConst( ast: AST, value: T ): SchemaParser.Parser { - const succeed = Effect.succeedSome(value) - return (oinput) => { - if (oinput._tag === "None") { - return Effect.succeedNone - } - return oinput.value === value - ? succeed - : Effect.fail(new SchemaIssue.InvalidType(ast, oinput)) + 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 fromAnyToConst(value: T): SchemaParser.Parser { - const succeed = Effect.succeedSome(value) - return (oinput) => oinput._tag === "None" ? Effect.succeedNone : succeed -} - function fromRefinement( ast: AST, refinement: (input: unknown) => input is T ): SchemaParser.Parser { - return (oinput) => { - if (oinput._tag === "None") { - return Effect.succeedNone - } - return refinement(oinput.value) - ? Effect.succeed(oinput) - : Effect.fail(new SchemaIssue.InvalidType(ast, oinput)) + 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 applyTemplateLiteralPartChecks(ast: AST, value: A, options: ParseOptions): A | undefined { - if (options?.disableChecks || ast.checks === undefined) return value - const issues: Array = [] - collectIssues(ast.checks, value, issues, ast, options) - return issues.length === 0 ? value : undefined -} - function segmentTemplateLiteralParts( - parts: ReadonlyArray, + 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) - const failures = new Set() + let failures: Set | undefined function go(i: number, pos: number): boolean { - if (i === parts.length) return pos === input.length - const key = `${i}/${pos}` - if (failures.has(key)) return false + 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) @@ -3549,21 +3792,28 @@ function segmentTemplateLiteralParts( return true } } else if (part._tag === "Literal") { - const s = globalThis.String(part.literal) + const s = literals[i]! if (input.startsWith(s, pos) && go(i + 1, pos + s.length)) { out[i] = s return true } } else { - for (let end = input.length; end >= pos; end--) { + 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.add(key) + failures ??= new Set() + failures.add(i * (inputLength + 1) + pos) return false } return go(0, 0) ? out : undefined @@ -3577,7 +3827,7 @@ export const enumsToLiterals = memoize((ast: Enum): Union => { ) }) -const parameterFromPropertyKey = applyToSelfOrLastLinkEncoding((ast) => { +const parameterFromPropertyKey = applyToSelfOrLastLinkEncodingIdempotent((ast) => { switch (ast._tag) { default: return ast @@ -3589,7 +3839,7 @@ const parameterFromPropertyKey = applyToSelfOrLastLinkEncoding((ast) => { }) /** @internal */ -export const parameterFromString = applyToSelfOrLastLinkEncoding((ast) => { +export const parameterFromString = applyToSelfOrLastLinkEncodingIdempotent((ast) => { switch (ast._tag) { default: return ast @@ -3601,7 +3851,7 @@ export const parameterFromString = applyToSelfOrLastLinkEncoding((ast) => { } }) -const partFromString = applyToSelfOrLastLinkEncoding((ast) => { +const partFromString = applyToSelfOrLastLinkEncodingIdempotent((ast) => { switch (ast._tag) { default: return ast @@ -3622,7 +3872,7 @@ export const STRING_PATTERN = "[\\s\\S]*?" const isStringFiniteRegExp = new globalThis.RegExp(`^${FINITE_PATTERN}$`) -const isStringNumberRegExp = new globalThis.RegExp(`(?:${FINITE_PATTERN}|Infinity|-Infinity|NaN)`) +const isStringNumberRegExp = new globalThis.RegExp(`^(?:${FINITE_PATTERN}|Infinity|-Infinity|NaN)$`) /** @internal */ export function isStringFinite(annotations?: Schema.Annotations.Filter) { @@ -3630,10 +3880,11 @@ export function isStringFinite(annotations?: Schema.Annotations.Filter) { isStringFiniteRegExp, { expected: "a string representing a finite number", - meta: { - _tag: "isStringFinite", - regExp: isStringFiniteRegExp + representation: { + id: "effect/schema/isStringFinite", + payload: null }, + toJsonSchema: () => ({ pattern: isStringFiniteRegExp.source }), ...annotations } ) @@ -3664,10 +3915,11 @@ export function isStringBigInt(annotations?: Schema.Annotations.Filter) { isStringBigIntRegExp, { expected: "a string representing a bigint", - meta: { - _tag: "isStringBigInt", - regExp: isStringBigIntRegExp + representation: { + id: "effect/schema/isStringBigInt", + payload: null }, + toJsonSchema: () => ({ pattern: isStringBigIntRegExp.source }), ...annotations } ) @@ -3697,13 +3949,17 @@ const symbolToString = new Link( symbolString, new SchemaTransformation.Transformation( SchemaGetter.transform((description) => globalThis.Symbol.for(isStringSymbolRegExp.exec(description)![1])), - SchemaGetter.transformOrFail((sym: symbol) => { + 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(Option.some(sym), { message: "cannot serialize to string, Symbol is not registered" }) + new SchemaIssue.Forbidden( + { message: "cannot serialize to string, Symbol is not registered" }, + sym, + options + ) ) }) ) @@ -3715,10 +3971,11 @@ export function isStringSymbol(annotations?: Schema.Annotations.Filter) { isStringSymbolRegExp, { expected: "a string representing a symbol", - meta: { - _tag: "isStringSymbol", - regExp: isStringSymbolRegExp + representation: { + id: "effect/schema/isStringSymbol", + payload: null }, + toJsonSchema: () => ({ pattern: isStringSymbolRegExp.source }), ...annotations } ) @@ -3728,24 +3985,33 @@ export function isStringSymbol(annotations?: Schema.Annotations.Filter) { export function collectIssues( checks: ReadonlyArray>, value: T, - issues: Array, + 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") { - collectIssues(check.checks, value, issues, ast, options) + 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) { - issues.push(new SchemaIssue.Filter(value, check, issue)) - if (check.aborted || options?.errors !== "all") { - return + 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 */ @@ -3753,20 +4019,26 @@ export function runChecks( checks: readonly [Check, ...Array>], s: T ): Result.Result { - const issues: Array = [] - collectIssues(checks, s, issues, unknown, { errors: "all" }) - if (Arr.isArrayNonEmpty(issues)) { - const issue = new SchemaIssue.Composite(unknown, Option.some(s), issues) + 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 const ClassTypeId = "~effect/Schema/Class" +export interface ConstructorDescriptor { + readonly isConstructed: Predicate.Predicate + readonly link: Link +} /** @internal */ -export const STRUCTURAL_ANNOTATION_KEY = "~structural" +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. @@ -3779,12 +4051,12 @@ export const STRUCTURAL_ANNOTATION_KEY = "~structural" * * **Example** (Reading annotations) * - * ```ts + * ```ts import.meta.vitest * import { Schema, SchemaAST } from "effect" * * const schema = Schema.String.annotate({ title: "Name" }) * const annotations = SchemaAST.resolve(schema.ast) - * console.log(annotations?.title) // "Name" + * annotations?.title // => "Name" * ``` * * @see {@link resolveAt} @@ -3849,6 +4121,82 @@ export const resolveTitle: (ast: AST) => string | undefined = InternalAnnotation */ 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. * @@ -3857,90 +4205,48 @@ export const resolveDescription: (ast: AST) => string | undefined = InternalAnno * @internal */ export function isJson(u: unknown): u is Schema.Json { - // `onPath` is the current recursion stack: nodes between the root and the - // one being visited. A hit here means we looped back to an ancestor — a - // real cycle, not a DAG — so the value is not JSON. - const onPath = new Set() - // `validated` memoizes subtrees we've already fully checked. Without it, a - // diamond-shaped DAG (same node reached through multiple parents) would be - // re-traversed once per parent, which is exponential in the nesting depth. - const validated = new Set() - return recur(u) - - function recur(u: unknown): boolean { - if (u === null || typeof u === "string" || typeof u === "boolean") { - return true - } - if (typeof u === "number") { - return globalThis.Number.isFinite(u) - } - if (typeof u !== "object" || u === undefined) { - return false - } - if (onPath.has(u)) { - return false - } - if (validated.has(u)) { - return true - } - onPath.add(u) - const ok = Array.isArray(u) - ? u.every(recur) - : Object.keys(u).every((key) => recur((u as Record)[key])) - // Pop on exit so siblings reaching the same node via a different path - // don't see it as an ancestor (that would reject valid DAGs). - onPath.delete(u) - if (ok) { - validated.add(u) - } - return ok - } + return isTree(u, isJsonLeaf) } /** @internal */ export const Json = new Declaration( [], - () => (input, ast) => + () => (input, ast, options) => isJson(input) ? - Effect.succeed(input) : - Effect.fail(new SchemaIssue.InvalidType(ast, Option.some(input))), + InternalParser.sameExit : + Effect.fail(new SchemaIssue.InvalidType(ast, input, options)), { - typeConstructor: { - _tag: "effect/Json" - }, - generation: { - runtime: `Schema.Json`, - Type: `Schema.Json` + representation: { + id: "effect/schema/Json", + payload: null }, expected: "JSON value", - toCodecJson: () => new Link(unknown, SchemaTransformation.passthrough()), + toCodecJson: () => undefined, + toCodecStringTree: () => unknownToStringTree, toArbitrary: () => (fc: typeof FastCheck) => fc.jsonValue() } ) /** @internal */ export const MutableJson = annotate(Json, { - typeConstructor: { - _tag: "effect/MutableJson" - }, - generation: { - runtime: `Schema.MutableJson`, - Type: `Schema.MutableJson` + representation: { + id: "effect/schema/MutableJson", + payload: null } }) /** @internal */ -export const unknownToNull = new Link( - null_, - new SchemaTransformation.Transformation( - SchemaGetter.passthrough(), - SchemaGetter.transform(() => null) - ) +export const unknownToJson = new Link( + Json, + SchemaTransformation.passthrough() ) /** @internal */ -export const unknownToJson = new Link( - Json, +export const objectKeywordToJson = new Link( + new Union([ + new Arrays(false, [], [Json]), + new Objects([], [new IndexSignature(string, Json)]) + ], "anyOf"), SchemaTransformation.passthrough() ) @@ -3952,34 +4258,16 @@ export const unknownToJson = new Link( * @internal */ export function isStringTree(u: unknown): u is Schema.StringTree { - const seen = new Set() - return recur(u) - - function recur(u: unknown): boolean { - if (u === undefined || typeof u === "string") { - return true - } - if (typeof u !== "object" || u === null) { - return false - } - if (seen.has(u)) { - return false - } - seen.add(u) - if (Array.isArray(u)) { - return u.every(recur) - } - return Object.keys(u).every((key) => recur((u as Record)[key])) - } + return isTree(u, isStringTreeLeaf) } const StringTree = new Declaration( [], - () => (input, ast) => + () => (input, ast, options) => isStringTree(input) ? - Effect.succeed(input) : - Effect.fail(new SchemaIssue.InvalidType(ast, Option.some(input))), - { expected: "StringTree" } + InternalParser.sameExit : + Effect.fail(new SchemaIssue.InvalidType(ast, input, options)), + { expected: "StringTree", toCodecStringTree: () => undefined } ) /** @internal */ diff --git a/repos/effect/packages/effect/src/SchemaError.ts b/repos/effect/packages/effect/src/SchemaError.ts new file mode 100644 index 0000000000..87e7afc487 --- /dev/null +++ b/repos/effect/packages/effect/src/SchemaError.ts @@ -0,0 +1,65 @@ +/** + * @since 4.0.0 + */ +import * as Data from "./Data.ts" +import * as Predicate from "./Predicate.ts" +import * as SchemaIssue from "./SchemaIssue.ts" + +const TypeId = "~effect/SchemaError/SchemaError" + +/** + * Error thrown (or returned as the error channel value) when schema decoding + * or encoding fails. + * + * **Details** + * + * The `issue` field contains a structured {@link SchemaIssue.Issue} tree describing + * every validation failure, including the path to the problematic value and + * the expected type or constraint. The `message` field renders the issue tree + * with the default formatter. When input reporting is enabled, the message may + * include reported input. Other Issue fields and custom annotations or messages + * are not sanitized. + * + * Use {@link isSchemaError} to narrow an unknown value to `SchemaError`. + * + * **Example** (Catching a SchemaError) + * + * ```ts import.meta.vitest + * import { Schema } from "effect" + * + * try { + * Schema.decodeUnknownSync(Schema.Number)("not a number") + * } catch (err) { + * if (Schema.isSchemaError(err)) { + * err.message // => "Expected number" + * } + * } + * ``` + * + * @category errors + * @since 4.0.0 + */ +export class SchemaError extends Data.TaggedError("SchemaError")<{ + readonly issue: SchemaIssue.Issue +}> { + readonly [TypeId]: typeof TypeId = TypeId + constructor(issue: SchemaIssue.Issue) { + super({ issue }) + } + override get message() { + return SchemaIssue.defaultFormatter(this.issue) + } + override toString() { + return `SchemaError(${this.message})` + } +} + +/** + * Returns `true` if `u` is a {@link SchemaError}. + * + * @category guards + * @since 4.0.0 + */ +export function isSchemaError(u: unknown): u is SchemaError { + return Predicate.hasProperty(u, TypeId) && u[TypeId] === TypeId +} diff --git a/repos/effect/packages/effect/src/SchemaGetter.ts b/repos/effect/packages/effect/src/SchemaGetter.ts index 74359f8f97..321096f0a8 100644 --- a/repos/effect/packages/effect/src/SchemaGetter.ts +++ b/repos/effect/packages/effect/src/SchemaGetter.ts @@ -14,6 +14,7 @@ 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" @@ -44,13 +45,13 @@ import * as Str from "./String.ts" * * **Example** (Creating and composing getters) * - * ```ts - * import { SchemaGetter } from "effect" + * ```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) - * // composed: Getter — parses then doubles + * await Effect.runPromise(composed.run(Option.some("21"), {})) // => Option.some(42) * ``` * * @see {@link transform} to create a getter from a pure function @@ -104,11 +105,11 @@ export class Getter extends Pipeable.Class { * * **Example** (Returning a constant getter) * - * ```ts - * import { SchemaGetter } from "effect" + * ```ts import.meta.vitest + * import { Effect, Option, SchemaGetter } from "effect" * * const alwaysZero = SchemaGetter.succeed(0) - * // alwaysZero: Getter<0, unknown> — always produces 0 + * await Effect.runPromise(alwaysZero.run(Option.none(), {})) // => Option.some(0) * ``` * * @see {@link transform} when you need to use the input value @@ -132,16 +133,19 @@ export function succeed(t: T): Getter { * **Details** * * - Always fails with the `Issue` returned by `f`. - * - The failure function receives the original `Option` input for error context. + * - The failure function receives the original `Option` input and the + * effective `ParseOptions` for error context. * * **Example** (Defining an always-failing getter) * - * ```ts - * import { Option, SchemaGetter, SchemaIssue } from "effect" + * ```ts import.meta.vitest + * import { Effect, Option, SchemaGetter, SchemaIssue } from "effect" * * const rejectAll = SchemaGetter.fail( - * (oe) => new SchemaIssue.InvalidValue(oe, { message: "not allowed" }) + * () => 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 @@ -150,8 +154,10 @@ export function succeed(t: T): Getter { * @category constructors * @since 4.0.0 */ -export function fail(f: (oe: Option.Option) => SchemaIssue.Issue): Getter { - return new Getter((oe) => Effect.fail(f(oe))) +export function fail( + f: (oe: Option.Option, options: SchemaAST.ParseOptions) => SchemaIssue.Issue +): Getter { + return new Getter((oe, options) => Effect.fail(f(oe, options))) } /** @@ -170,12 +176,14 @@ export function fail(f: (oe: Option.Option) => SchemaIssue.Issue): Gett * * **Example** (Forbidding a decode direction) * - * ```ts - * import { SchemaGetter } from "effect" + * ```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 @@ -184,7 +192,12 @@ export function fail(f: (oe: Option.Option) => SchemaIssue.Issue): Gett * @since 4.0.0 */ export function forbidden(message: (oe: Option.Option) => string): Getter { - return fail((oe) => new SchemaIssue.Forbidden(oe, { message: message(oe) })) + 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) @@ -210,7 +223,7 @@ function isPassthrough(getter: Getter): getter is typeof passt * * **Example** (Passing through identity transformations) * - * ```ts + * ```ts import.meta.vitest * import { Schema, SchemaGetter } from "effect" * * // No transformation needed — types already match @@ -220,6 +233,7 @@ function isPassthrough(getter: Getter): getter is typeof passt * encode: SchemaGetter.passthrough() * }) * ) + * Schema.decodeSync(StringToString)("hello") // => "hello" * ``` * * @see {@link passthroughSupertype} when `T extends E` @@ -249,11 +263,12 @@ export function passthrough(): Getter { * * **Example** (Passing through supertypes) * - * ```ts - * import { SchemaGetter } from "effect" + * ```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 @@ -281,11 +296,12 @@ export function passthroughSupertype(): Getter { * * **Example** (Passing through subtypes) * - * ```ts - * import { SchemaGetter } from "effect" + * ```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 @@ -316,19 +332,20 @@ export function passthroughSubtype(): Getter { * * **Example** (Providing a default timestamp for a missing field) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Option, SchemaGetter } from "effect" * * const withTimestamp = SchemaGetter.onNone(() => - * Effect.succeed(Option.some(Date.now())) + * 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 constructors + * @category transforming * @since 4.0.0 */ export function onNone( @@ -353,16 +370,18 @@ export function onNone( * * **Example** (Defining a required struct field) * - * ```ts - * import { SchemaGetter } from "effect" + * ```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 constructors + * @category validation * @since 4.0.0 */ export function required(annotations?: Schema.Annotations.Key): Getter { @@ -386,19 +405,20 @@ export function required(annotations?: Schema.Annotations.Ke * * **Example** (Transforming only present values) * - * ```ts + * ```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 constructors + * @category transforming * @since 4.0.0 */ export function onSome( @@ -429,18 +449,19 @@ export function onSome( * * **Example** (Validating effectfully) * - * ```ts - * import { Effect, SchemaGetter } from "effect" + * ```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 constructors + * @category validation * @since 4.0.0 */ export function checkEffect( @@ -452,7 +473,7 @@ export function checkEffect( ): Getter { return onSome((t, options) => { return f(t, options).pipe(Effect.flatMapEager((out) => { - const issue = SchemaIssue.makeSingle(t, out) + const issue = SchemaIssue.makeSingle(out, t, options) return issue ? Effect.fail(issue) : Effect.succeed(Option.some(t)) @@ -478,7 +499,7 @@ export function checkEffect( * * **Example** (Transforming strings to numbers) * - * ```ts + * ```ts import.meta.vitest * import { Schema, SchemaGetter } from "effect" * * const NumberFromString = Schema.String.pipe( @@ -487,13 +508,14 @@ export function checkEffect( * 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 constructors + * @category transforming * @since 4.0.0 */ export function transform(f: (e: E) => T): Getter { @@ -516,23 +538,24 @@ export function transform(f: (e: E) => T): Getter { * * **Example** (Parsing with failure) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Option, SchemaGetter, SchemaIssue } from "effect" * * const safeParseInt = SchemaGetter.transformOrFail( - * (s) => { + * (s, options) => { * const n = parseInt(s, 10) * return isNaN(n) - * ? Effect.fail(new SchemaIssue.InvalidValue(Option.some(s), { message: "not an integer" })) + * ? 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 constructors + * @category transforming * @since 4.0.0 */ export function transformOrFail( @@ -556,18 +579,19 @@ export function transformOrFail( * * **Example** (Filtering out empty strings) * - * ```ts - * import { Option, SchemaGetter } from "effect" + * ```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 constructors + * @category transforming * @since 4.0.0 */ export function transformOptional(f: (oe: Option.Option) => Option.Option): Getter { @@ -589,16 +613,17 @@ export function transformOptional(f: (oe: Option.Option) => Option.Opti * * **Example** (Omitting a field during encoding) * - * ```ts - * import { SchemaGetter } from "effect" + * ```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 constructors + * @category filtering * @since 4.0.0 */ export function omit(): Getter { @@ -621,17 +646,17 @@ export function omit(): Getter { * * **Example** (Providing a default value for an optional field) * - * ```ts - * import { Effect, SchemaGetter } from "effect" + * ```ts import.meta.vitest + * import { Effect, Option, SchemaGetter } from "effect" * * const withZero = SchemaGetter.withDefault(Effect.succeed(0)) - * // Getter + * 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 constructors + * @category transforming * @since 4.0.0 */ export function withDefault( @@ -657,16 +682,16 @@ export function withDefault( * * **Example** (Coercing to a string) * - * ```ts - * import { SchemaGetter } from "effect" + * ```ts import.meta.vitest + * import { Effect, Option, SchemaGetter } from "effect" * * const toString = SchemaGetter.String() - * // Getter + * await Effect.runPromise(toString.run(Option.some(42), {})) // => Option.some("42") * ``` * * @see {@link transform} for custom string conversions * - * @category Coercions + * @category converting * @since 4.0.0 */ export function String(): Getter { @@ -688,16 +713,16 @@ export function String(): Getter { * * **Example** (Coercing to a number) * - * ```ts - * import { SchemaGetter } from "effect" + * ```ts import.meta.vitest + * import { Effect, Option, SchemaGetter } from "effect" * * const toNumber = SchemaGetter.Number() - * // Getter + * await Effect.runPromise(toNumber.run(Option.some("42"), {})) // => Option.some(42) * ``` * * @see {@link transformOrFail} for validated number parsing * - * @category Coercions + * @category converting * @since 4.0.0 */ export function Number(): Getter { @@ -718,14 +743,14 @@ export function Number(): Getter { * * **Example** (Coercing to a boolean) * - * ```ts - * import { SchemaGetter } from "effect" + * ```ts import.meta.vitest + * import { Effect, Option, SchemaGetter } from "effect" * * const toBool = SchemaGetter.Boolean() - * // Getter + * await Effect.runPromise(toBool.run(Option.some("true"), {})) // => Option.some(true) * ``` * - * @category Coercions + * @category converting * @since 4.0.0 */ export function Boolean(): Getter { @@ -747,14 +772,14 @@ export function Boolean(): Getter { * * **Example** (Coercing to a bigint) * - * ```ts - * import { SchemaGetter } from "effect" + * ```ts import.meta.vitest + * import { Effect, Option, SchemaGetter } from "effect" * * const toBigInt = SchemaGetter.BigInt() - * // Getter + * await Effect.runPromise(toBigInt.run(Option.some("42"), {})) // => Option.some(42n) * ``` * - * @category Coercions + * @category converting * @since 4.0.0 */ export function BigInt(): Getter { @@ -776,16 +801,17 @@ export function BigInt(): Getter() - * // Getter + * 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 Coercions + * @category converting * @since 4.0.0 */ export function Date(): Getter { @@ -801,13 +827,14 @@ export function Date(): Getter { * * **Example** (Trimming whitespace) * - * ```ts - * import { SchemaGetter } from "effect" + * ```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 string + * @category transforming * @since 4.0.0 */ export function trim(): Getter { @@ -823,13 +850,14 @@ export function trim(): Getter { * * **Example** (Capitalizing a string) * - * ```ts - * import { SchemaGetter } from "effect" + * ```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 string + * @category transforming * @since 4.0.0 */ export function capitalize(): Getter { @@ -845,13 +873,14 @@ export function capitalize(): Getter { * * **Example** (Uncapitalizing a string) * - * ```ts - * import { SchemaGetter } from "effect" + * ```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 string + * @category transforming * @since 4.0.0 */ export function uncapitalize(): Getter { @@ -867,15 +896,16 @@ export function uncapitalize(): Getter { * * **Example** (Converting snake case to camel case) * - * ```ts - * import { SchemaGetter } from "effect" + * ```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 string + * @category transforming * @since 4.0.0 */ export function snakeToCamel(): Getter { @@ -891,15 +921,16 @@ export function snakeToCamel(): Getter { * * **Example** (Converting camel case to snake case) * - * ```ts - * import { SchemaGetter } from "effect" + * ```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 string + * @category transforming * @since 4.0.0 */ export function camelToSnake(): Getter { @@ -915,15 +946,16 @@ export function camelToSnake(): Getter { * * **Example** (Converting to lowercase) * - * ```ts - * import { SchemaGetter } from "effect" + * ```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 string + * @category transforming * @since 4.0.0 */ export function toLowerCase(): Getter { @@ -939,15 +971,16 @@ export function toLowerCase(): Getter { * * **Example** (Converting to uppercase) * - * ```ts - * import { SchemaGetter } from "effect" + * ```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 string + * @category transforming * @since 4.0.0 */ export function toUpperCase(): Getter { @@ -971,35 +1004,53 @@ type ParseJsonOptions = { * - 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` containing the error message. + * - 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 { SchemaGetter } from "effect" + * ```ts import.meta.vitest + * import { Effect, Option, SchemaGetter } from "effect" * * const parse = SchemaGetter.parseJson() - * // Getter + * await Effect.runPromise(parse.run(Option.some("{\"a\":1}"), {})) // => Option.some({ a: 1 }) * ``` * * @see {@link stringifyJson} for the inverse operation * - * @category JSON getters + * @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) => + return onSome((input, parseOptions) => Effect.try({ try: () => Option.some(JSON.parse(input, options?.reviver)), - catch: (e) => new SchemaIssue.InvalidValue(Option.some(input), { message: globalThis.String(e) }) + 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?: Parameters[1] + readonly replacer?: JsonReplacer | undefined readonly space?: Parameters[2] } @@ -1014,33 +1065,41 @@ type StringifyJsonOptions = { * **Details** * * - Skips `None` inputs. - * - On thrown stringify failures, such as circular references, fails with + * - If `JSON.stringify` throws or returns `undefined`, fails with * `SchemaIssue.InvalidValue`. * - Supports optional `replacer` and `space` options, matching * `JSON.stringify`. - * - If `JSON.stringify` returns `undefined`, such as for `undefined`, - * functions, symbols, or a replacer that removes the root value, that - * `undefined` result is returned rather than converted into an `Issue`. * * **Example** (Stringifying JSON) * - * ```ts - * import { SchemaGetter } from "effect" + * ```ts import.meta.vitest + * import { Effect, Option, SchemaGetter } from "effect" * * const stringify = SchemaGetter.stringifyJson() - * // Getter + * await Effect.runPromise(stringify.run(Option.some({ a: 1 }), {})) // => Option.some("{\"a\":1}") * ``` * * @see {@link parseJson} for the inverse operation * - * @category JSON getters + * @category encoding * @since 4.0.0 */ export function stringifyJson(options?: StringifyJsonOptions): Getter { - return onSome((input) => + return onSome((input, parseOptions) => Effect.try({ - try: () => Option.some(JSON.stringify(input, options?.replacer, options?.space)), - catch: (e) => new SchemaIssue.InvalidValue(Option.some(input), { message: globalThis.String(e) }) + 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 + ) }) ) } @@ -1061,17 +1120,17 @@ export function stringifyJson(options?: StringifyJsonOptions): Getter() - * // "a=1,b=2" -> { a: "1", b: "2" } + * 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 string + * @category splitting * @since 4.0.0 */ export function splitKeyValue(options?: { @@ -1084,7 +1143,7 @@ export function splitKeyValue(options?: { input.split(separator).reduce((acc, pair) => { const [key, value] = pair.split(keyValueSeparator) if (key && value) { - acc[key] = value + InternalRecord.assignProperty(acc, key, value) } return acc }, {} as Record) @@ -1107,16 +1166,16 @@ export function splitKeyValue(options?: { * * **Example** (Joining key-value records) * - * ```ts - * import { SchemaGetter } from "effect" + * ```ts import.meta.vitest + * import { Effect, Option, SchemaGetter } from "effect" * * const join = SchemaGetter.joinKeyValue() - * // { a: "1", b: "2" } -> "a=1,b=2" + * 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 string + * @category combining * @since 4.0.0 */ export function joinKeyValue>(options?: { @@ -1145,17 +1204,16 @@ export function joinKeyValue>(options?: { * * **Example** (Splitting a comma-separated string) * - * ```ts - * import { SchemaGetter } from "effect" + * ```ts import.meta.vitest + * import { Effect, Option, SchemaGetter } from "effect" * * const splitComma = SchemaGetter.split() - * // "a,b,c" -> ["a", "b", "c"] - * // "" -> [] + * 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 string + * @category splitting * @since 4.0.0 */ export function split(options?: { @@ -1174,17 +1232,18 @@ export function split(options?: { * * **Example** (Encoding to Base64) * - * ```ts - * import { SchemaGetter } from "effect" + * ```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 Base64 getters + * @category encoding * @since 4.0.0 */ export function encodeBase64(): Getter { @@ -1200,17 +1259,18 @@ export function encodeBase64(): Getter * * **Example** (Encoding to Base64Url) * - * ```ts - * import { SchemaGetter } from "effect" + * ```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 Base64 getters + * @category encoding * @since 4.0.0 */ export function encodeBase64Url(): Getter { @@ -1226,16 +1286,17 @@ export function encodeBase64Url(): Getter() + * 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 Hex getters + * @category encoding * @since 4.0.0 */ export function encodeHex(): Getter { @@ -1251,24 +1312,30 @@ export function encodeHex(): Getter { * * **Example** (Decoding Base64 to bytes) * - * ```ts - * import { SchemaGetter } from "effect" + * ```ts import.meta.vitest + * import { Effect, Option, SchemaGetter } from "effect" * * const decode = SchemaGetter.decodeBase64() - * // Getter + * 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 Base64 getters + * @category decoding * @since 4.0.0 */ export function decodeBase64(): Getter { - return transformOrFail((input) => + return transformOrFail((input, options) => Effect.mapErrorEager( Effect.fromResult(Encoding.decodeBase64(input)), - (e) => new SchemaIssue.InvalidValue(Option.some(input), { message: e.message }) + () => + new SchemaIssue.InvalidValue( + { expected: "a valid Base64 string" }, + input, + options + ) ) ) } @@ -1282,23 +1349,30 @@ export function decodeBase64(): Getter { * * **Example** (Decoding Base64 to string) * - * ```ts - * import { SchemaGetter } from "effect" + * ```ts import.meta.vitest + * import { Effect, Option, SchemaGetter } from "effect" * * const decode = SchemaGetter.decodeBase64String() - * // Getter + * 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 Base64 getters + * @category decoding * @since 4.0.0 */ export function decodeBase64String(): Getter { - return transformOrFail((input) => + return transformOrFail((input, options) => Result.match(Encoding.decodeBase64String(input), { - onFailure: (e) => Effect.fail(new SchemaIssue.InvalidValue(Option.some(input), { message: e.message })), + onFailure: () => + Effect.fail( + new SchemaIssue.InvalidValue( + { expected: "a valid Base64 string" }, + input, + options + ) + ), onSuccess: Effect.succeed }) ) @@ -1313,23 +1387,31 @@ export function decodeBase64String(): Getter { * * **Example** (Decoding Base64Url to bytes) * - * ```ts - * import { SchemaGetter } from "effect" + * ```ts import.meta.vitest + * import { Effect, Option, SchemaGetter } from "effect" * * const decode = SchemaGetter.decodeBase64Url() - * // Getter + * 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 Base64 getters + * @category decoding * @since 4.0.0 */ export function decodeBase64Url(): Getter { - return transformOrFail((input) => + return transformOrFail((input, options) => Result.match(Encoding.decodeBase64Url(input), { - onFailure: (e) => Effect.fail(new SchemaIssue.InvalidValue(Option.some(input), { message: e.message })), + onFailure: () => + Effect.fail( + new SchemaIssue.InvalidValue( + { expected: "a valid Base64Url string" }, + input, + options + ) + ), onSuccess: Effect.succeed }) ) @@ -1344,23 +1426,30 @@ export function decodeBase64Url(): Getter { * * **Example** (Decoding Base64Url to string) * - * ```ts - * import { SchemaGetter } from "effect" + * ```ts import.meta.vitest + * import { Effect, Option, SchemaGetter } from "effect" * * const decode = SchemaGetter.decodeBase64UrlString() - * // Getter + * 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 Base64 getters + * @category decoding * @since 4.0.0 */ export function decodeBase64UrlString(): Getter { - return transformOrFail((input) => + return transformOrFail((input, options) => Result.match(Encoding.decodeBase64UrlString(input), { - onFailure: (e) => Effect.fail(new SchemaIssue.InvalidValue(Option.some(input), { message: e.message })), + onFailure: () => + Effect.fail( + new SchemaIssue.InvalidValue( + { expected: "a valid Base64Url string" }, + input, + options + ) + ), onSuccess: Effect.succeed }) ) @@ -1375,23 +1464,31 @@ export function decodeBase64UrlString(): Getter { * * **Example** (Decoding hex to bytes) * - * ```ts - * import { SchemaGetter } from "effect" + * ```ts import.meta.vitest + * import { Effect, Option, SchemaGetter } from "effect" * * const decode = SchemaGetter.decodeHex() - * // Getter + * 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 Hex getters + * @category decoding * @since 4.0.0 */ export function decodeHex(): Getter { - return transformOrFail((input) => + return transformOrFail((input, options) => Result.match(Encoding.decodeHex(input), { - onFailure: (e) => Effect.fail(new SchemaIssue.InvalidValue(Option.some(input), { message: e.message })), + onFailure: () => + Effect.fail( + new SchemaIssue.InvalidValue( + { expected: "a valid hexadecimal string" }, + input, + options + ) + ), onSuccess: Effect.succeed }) ) @@ -1406,23 +1503,30 @@ export function decodeHex(): Getter { * * **Example** (Decoding hex to string) * - * ```ts - * import { SchemaGetter } from "effect" + * ```ts import.meta.vitest + * import { Effect, Option, SchemaGetter } from "effect" * * const decode = SchemaGetter.decodeHexString() - * // Getter + * 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 Hex getters + * @category decoding * @since 4.0.0 */ export function decodeHexString(): Getter { - return transformOrFail((input) => + return transformOrFail((input, options) => Result.match(Encoding.decodeHexString(input), { - onFailure: (e) => Effect.fail(new SchemaIssue.InvalidValue(Option.some(input), { message: e.message })), + onFailure: () => + Effect.fail( + new SchemaIssue.InvalidValue( + { expected: "a valid hexadecimal string" }, + input, + options + ) + ), onSuccess: Effect.succeed }) ) @@ -1439,15 +1543,16 @@ export function decodeHexString(): Getter { * * **Example** (Encoding a URI component) * - * ```ts - * import { SchemaGetter } from "effect" + * ```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 URI + * @category encoding * @since 4.0.0 */ export function encodeUriComponent(): Getter { @@ -1463,27 +1568,29 @@ export function encodeUriComponent(): Getter { * * **Example** (Decoding a URI component) * - * ```ts - * import { SchemaGetter } from "effect" + * ```ts import.meta.vitest + * import { Effect, Option, SchemaGetter } from "effect" * * const decode = SchemaGetter.decodeUriComponent() - * // Getter + * await Effect.runPromise(decode.run(Option.some("hello%20world"), {})) // => Option.some("hello world") * ``` * * @see {@link encodeUriComponent} for the inverse operation * - * @category URI + * @category decoding * @since 4.0.0 */ export function decodeUriComponent(): Getter { - return transformOrFail((input) => { + return transformOrFail((input, options) => { try { return Effect.succeed(globalThis.decodeURIComponent(input)) - } catch (e) { + } catch { return Effect.fail( - new SchemaIssue.InvalidValue(Option.some(input), { - message: e instanceof URIError ? e.message : "Invalid URI component" - }) + new SchemaIssue.InvalidValue( + { expected: "a valid URI component" }, + input, + options + ) ) } }) @@ -1508,23 +1615,26 @@ export function decodeUriComponent(): Getter { * * **Example** (Parsing DateTime) * - * ```ts - * import { SchemaGetter } from "effect" + * ```ts import.meta.vitest + * import { DateTime, Effect, Option, SchemaGetter } from "effect" * * const parseDate = SchemaGetter.dateTimeUtcFromInput() - * // Getter + * 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 DateTime + * @category converting * @since 4.0.0 */ export function dateTimeUtcFromInput(): Getter { - return transformOrFail((input) => { + return transformOrFail((input, options) => { return Option.match(DateTime.make(input), { onNone: () => - Effect.fail(new SchemaIssue.InvalidValue(Option.some(input), { message: "Invalid DateTime input" })), + Effect.fail( + new SchemaIssue.InvalidValue({ message: "Invalid DateTime input" }, input, options) + ), onSome: (dt) => Effect.succeed(DateTime.toUtc(dt)) }) }) @@ -1546,18 +1656,20 @@ export function dateTimeUtcFromInput(): Gette * * **Example** (Decoding FormData) * - * ```ts - * import { SchemaGetter } from "effect" + * ```ts import.meta.vitest + * import { Effect, Option, SchemaGetter } from "effect" * * const decode = SchemaGetter.decodeFormData() - * // Getter, FormData> + * 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 inverse operation + * @see {@link encodeFormData} for the corresponding encoder * @see {@link makeTreeRecord} for the underlying bracket-path parser * @see {@link decodeURLSearchParams} for the URLSearchParams variant * - * @category FormData + * @category decoding * @since 4.0.0 */ export function decodeFormData(): Getter, FormData> { @@ -1584,18 +1696,19 @@ const collectFormDataEntries = collectBracketPathEntries((value): value is strin * * **Example** (Encoding to FormData) * - * ```ts - * import { SchemaGetter } from "effect" + * ```ts import.meta.vitest + * import { Effect, Option, SchemaGetter } from "effect" * * const encode = SchemaGetter.encodeFormData() - * // Getter + * 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 inverse operation + * @see {@link decodeFormData} for the corresponding decoder * @see {@link collectBracketPathEntries} for the underlying flattener * @see {@link encodeURLSearchParams} for the URLSearchParams variant * - * @category FormData + * @category encoding * @since 4.0.0 */ export function encodeFormData(): Getter { @@ -1627,18 +1740,19 @@ export function encodeFormData(): Getter { * * **Example** (Decoding URLSearchParams) * - * ```ts - * import { SchemaGetter } from "effect" + * ```ts import.meta.vitest + * import { Effect, Option, SchemaGetter } from "effect" * * const decode = SchemaGetter.decodeURLSearchParams() - * // Getter, URLSearchParams> + * 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 inverse operation + * @see {@link encodeURLSearchParams} for the corresponding encoder * @see {@link makeTreeRecord} for the underlying bracket-path parser * @see {@link decodeFormData} for the FormData variant * - * @category search params + * @category decoding * @since 4.0.0 */ export function decodeURLSearchParams(): Getter, URLSearchParams> { @@ -1662,18 +1776,19 @@ const collectURLSearchParamsEntries = collectBracketPathEntries(Predicate.isStri * * **Example** (Encoding to URLSearchParams) * - * ```ts - * import { SchemaGetter } from "effect" + * ```ts import.meta.vitest + * import { Effect, Option, SchemaGetter } from "effect" * * const encode = SchemaGetter.encodeURLSearchParams() - * // Getter + * 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 inverse operation + * @see {@link decodeURLSearchParams} for the corresponding decoder * @see {@link collectBracketPathEntries} for the underlying flattener * @see {@link encodeFormData} for the FormData variant * - * @category search params + * @category encoding * @since 4.0.0 */ export function encodeURLSearchParams(): Getter { @@ -1725,31 +1840,47 @@ function bracketPathToTokens(bracketPath: string): Array { * - `"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 + * ```ts import.meta.vitest * import { SchemaGetter } from "effect" * - * const tree = SchemaGetter.makeTreeRecord([ + * SchemaGetter.makeTreeRecord([ * ["user[name]", "Alice"], * ["user[tags][]", "admin"], * ["user[tags][]", "editor"] - * ]) - * // { user: { name: "Alice", tags: ["admin", "editor"] } } + * ]) // => { user: { name: "Alice", tags: ["admin", "editor"] } } * ``` * - * @see {@link collectBracketPathEntries} for the inverse operation (tree to flat entries) + * @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 Tree + * @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 @@ -1765,33 +1896,24 @@ export function makeTreeRecord( const next = tokens[i + 1] const shouldBeArray = typeof next === "number" || next === "" const index = cur.length - - if (cur[index] === undefined) { - cur[index] = shouldBeArray ? [] : {} - } - - cur = cur[index] + 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 - if (Array.isArray(cur[token])) { + const hasOwn = Object.hasOwn(cur, token) + if (hasOwn && Array.isArray(cur[token])) { cur[token].push(value) - } else if (Object.prototype.hasOwnProperty.call(cur, token)) { - cur[token] = [cur[token], value] + } else if (hasOwn) { + InternalRecord.assignProperty(cur, token, [cur[token], value]) } else { - cur[token] = value + 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 === "" - - if (cur[token] === undefined) { - cur[token] = shouldBeArray ? [] : {} - } - - cur = cur[token] + cur = getOrCreateContainer(cur, token, shouldBeArray) } }) }) @@ -1809,7 +1931,6 @@ export function makeTreeRecord( * * **Details** * - * - This is the inverse of {@link makeTreeRecord}. * - 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. @@ -1817,22 +1938,25 @@ export function makeTreeRecord( * - 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 + * ```ts import.meta.vitest * import { Predicate, SchemaGetter } from "effect" * * const collectStrings = SchemaGetter.collectBracketPathEntries(Predicate.isString) * const entries = collectStrings({ user: { name: "Alice", tags: ["admin", "editor"] } }) - * // [["user[name]", "Alice"], ["user[tags]", "admin"], ["user[tags]", "editor"]] + * + * entries // => [["user[name]", "Alice"], ["user[tags]", "admin"], ["user[tags]", "editor"]] * ``` * - * @see {@link makeTreeRecord} for the inverse operation (flat entries to tree) + * @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 Tree + * @category converting * @since 4.0.0 */ export function collectBracketPathEntries(isLeaf: (value: unknown) => value is A) { diff --git a/repos/effect/packages/effect/src/SchemaIssue.ts b/repos/effect/packages/effect/src/SchemaIssue.ts index 365500b5f7..b0b0eb2ddd 100644 --- a/repos/effect/packages/effect/src/SchemaIssue.ts +++ b/repos/effect/packages/effect/src/SchemaIssue.ts @@ -5,8 +5,7 @@ * 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 and - * supports redaction for sensitive values. + * and alternatives that did not match. This module also formats issues. * * @since 4.0.0 */ @@ -14,9 +13,8 @@ import type { StandardSchemaV1 } from "@standard-schema/spec" 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 Option from "./Option.ts" +import * as InternalParser from "./internal/schema/parser.ts" import { hasProperty } from "./Predicate.ts" -import * as Redacted from "./Redacted.ts" import type * as Schema from "./Schema.ts" import type * as SchemaAST from "./SchemaAST.ts" @@ -37,14 +35,12 @@ const TypeId = "~effect/SchemaIssue/Issue" * * **Example** (Type-guarding an unknown error) * - * ```ts + * ```ts import.meta.vitest * import { SchemaIssue } from "effect" * * const issue = new SchemaIssue.MissingKey(undefined) - * console.log(SchemaIssue.isIssue(issue)) - * // true - * console.log(SchemaIssue.isIssue("not an issue")) - * // false + * SchemaIssue.isIssue(issue) // => true + * SchemaIssue.isIssue("not an issue") // => false * ``` * * @see {@link Issue} @@ -53,7 +49,40 @@ const TypeId = "~effect/SchemaIssue/Issue" * @since 4.0.0 */ export function isIssue(u: unknown): u is Issue { - return hasProperty(u, TypeId) + 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") } /** @@ -96,13 +125,17 @@ export type Leaf = * 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}. All `Issue` instances have a `toString()` that delegates to - * the default formatter, so `String(issue)` produces a human-readable message. + * {@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 getActual} — extract the actual value from any issue - * + * @see {@link hasInput} — checks whether an issue reports an input * @category models * @since 4.0.0 */ @@ -117,8 +150,15 @@ export type Issue = class Base { readonly [TypeId] = TypeId - toString(this: Issue): string { - return defaultFormatter(this) + /** + * 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 + } } } @@ -132,22 +172,28 @@ class Base { * * **Details** * - * - `actual` is the raw input value that was tested (plain `unknown`, not - * wrapped in `Option`). * - `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 { SchemaIssue } from "effect" + * ```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 on: ${JSON.stringify(issue.actual)}` + * return `Filter failed: ${formatIssue(issue.issue)}` * } - * return String(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` @@ -158,10 +204,6 @@ class Base { */ export class Filter extends Base { readonly _tag = "Filter" - /** - * The input value that caused the issue. - */ - readonly actual: unknown /** * The filter that failed. */ @@ -172,10 +214,6 @@ export class Filter extends Base { readonly issue: Issue constructor( - /** - * The input value that caused the issue. - */ - actual: unknown, /** * The filter that failed. */ @@ -183,10 +221,18 @@ export class Filter extends Base { /** * The issue that occurred. */ - issue: Issue + 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() - this.actual = actual + super(input, options) this.filter = filter this.issue = issue } @@ -203,8 +249,6 @@ export class Filter extends Base { * **Details** * * - `ast` is the AST node for the transformation that failed. - * - `actual` is `Option.some(value)` when the input was present, or - * `Option.none()` when it was absent. * - `issue` is the inner issue describing the failure. * * @see {@link Filter} — failure from a refinement check (not a transformation) @@ -219,10 +263,6 @@ export class Encoding extends Base { * The schema that caused the issue. */ readonly ast: SchemaAST.AST - /** - * The input value that caused the issue. - */ - readonly actual: Option.Option /** * The issue that occurred. */ @@ -234,17 +274,21 @@ export class Encoding extends Base { */ ast: SchemaAST.AST, /** - * The input value that caused the issue. + * The issue that occurred. */ - actual: Option.Option, + issue: Issue, /** - * The issue that occurred. + * The present input associated with the issue. It is retained only when + * `options.reportInput` is `true`. */ - issue: Issue + input?: unknown, + /** + * The effective parse options controlling input retention. + */ + options?: SchemaAST.ParseOptions ) { - super() + super(input, options) this.ast = ast - this.actual = actual this.issue = issue } } @@ -261,11 +305,9 @@ export class Encoding extends Base { * **Details** * * - `path` is an array of property keys (strings, numbers, or symbols). - * - Has no `actual` value — {@link getActual} returns `Option.none()`. * - Formatters concatenate nested `Pointer` paths into a single path like * `["a"]["b"][0]`. * - * @see {@link getActual} — returns `Option.none()` for `Pointer` * @see {@link Composite} — groups multiple issues under one schema node * * @category models @@ -307,7 +349,6 @@ export class Pointer extends Base { * * **Details** * - * - Has no `actual` value — {@link getActual} returns `Option.none()`. * - `annotations` may contain a custom `messageMissingKey` for formatting. * * @see {@link Pointer} — wraps this issue with the missing key's path @@ -345,9 +386,10 @@ export class MissingKey extends Base { * * **Details** * - * - `actual` is the raw value at the unexpected key (plain `unknown`). * - `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 @@ -361,24 +403,23 @@ export class UnexpectedKey extends Base { * The schema that caused the issue. */ readonly ast: SchemaAST.AST - /** - * The input value that caused the issue. - */ - readonly actual: unknown - constructor( /** * The schema that caused the issue. */ ast: SchemaAST.AST, /** - * The input value that caused the 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. */ - actual: unknown + options?: SchemaAST.ParseOptions ) { - super() + super(input, options) this.ast = ast - this.actual = actual } } @@ -393,8 +434,6 @@ export class UnexpectedKey extends Base { * **Details** * * - `issues` is a non-empty readonly array (at least one child). - * - `actual` is `Option.some(value)` when the input was present, or - * `Option.none()` when absent. * - Formatters flatten `Composite` by recursing into each child. * * @see {@link AnyOf} — used for union no-match errors (similar but different semantics) @@ -409,10 +448,6 @@ export class Composite extends Base { * The schema that caused the issue. */ readonly ast: SchemaAST.AST - /** - * The input value that caused the issue. - */ - readonly actual: Option.Option /** * The issues that occurred. */ @@ -424,24 +459,28 @@ export class Composite extends Base { */ ast: SchemaAST.AST, /** - * The input value that caused the issue. + * The issues that occurred. */ - actual: Option.Option, + issues: readonly [Issue, ...Array], /** - * The issues that occurred. + * The present input associated with the issue. It is retained only when + * `options.reportInput` is `true`. */ - issues: readonly [Issue, ...Array] + input?: unknown, + /** + * The effective parse options controlling input retention. + */ + options?: SchemaAST.ParseOptions ) { - super() + super(input, options) this.ast = ast - this.actual = actual this.issues = issues } } /** * Represents a schema issue produced when the runtime type of the input does not match the type - * expected by the schema (e.g. got `null` when `string` was expected). + * expected by the schema. * * **When to use** * @@ -451,23 +490,17 @@ export class Composite extends Base { * **Details** * * - `ast` is the schema node that expected a different type. - * - `actual` is `Option.some(value)` when the input was present, or - * `Option.none()` when no value was provided. - * - The default formatter renders this as `"Expected , got "`. + * - The default formatter renders this as `"Expected "`, adding + * `", got "` when the issue reports an input. * - * **Example** (Formatting output) + * **Example** (Formatting a type mismatch) * - * ```ts - * import { Schema } from "effect" + * ```ts import.meta.vitest + * import { Schema, SchemaIssue } from "effect" * - * try { - * Schema.decodeUnknownSync(Schema.String)(42) - * } catch (e) { - * if (Schema.isSchemaError(e)) { - * console.log(String(e.issue)) - * // "Expected string, got 42" - * } - * } + * 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 @@ -481,24 +514,23 @@ export class InvalidType extends Base { * The schema that caused the issue. */ readonly ast: SchemaAST.AST - /** - * The input value that caused the issue. - */ - readonly actual: Option.Option - constructor( /** * The schema that caused the issue. */ ast: SchemaAST.AST, /** - * The input value that caused the issue. + * The present input associated with the issue. It is retained only when + * `options.reportInput` is `true`. */ - actual: Option.Option + input?: unknown, + /** + * The effective parse options controlling input retention. + */ + options?: SchemaAST.ParseOptions ) { - super() + super(input, options) this.ast = ast - this.actual = actual } } @@ -513,23 +545,22 @@ export class InvalidType extends Base { * * **Details** * - * - `actual` is `Option.some(value)` when the failing value is known, or - * `Option.none()` when absent. - * - `annotations` optionally carries a `message` string for formatting. - * - The default formatter renders this as `"Invalid data "` unless a - * custom `message` annotation is provided. + * - 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 { Option, SchemaIssue } from "effect" + * ```ts import.meta.vitest + * import { SchemaIssue } from "effect" * - * const issue = new SchemaIssue.InvalidValue( - * Option.some(""), - * { message: "must not be empty" } - * ) - * console.log(String(issue)) - * // "must not be empty" + * 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 @@ -540,10 +571,6 @@ export class InvalidType extends Base { */ export class InvalidValue extends Base { readonly _tag = "InvalidValue" - /** - * The value that caused the issue. - */ - readonly actual: Option.Option /** * The metadata for the issue. */ @@ -551,20 +578,40 @@ export class InvalidValue extends Base { constructor( /** - * The value that caused the issue. + * The metadata for the issue. */ - actual: Option.Option, + annotations?: Schema.Annotations.Issue | undefined, /** - * The metadata for the issue. + * The present input associated with the issue. It is retained only when + * `options.reportInput` is `true`. */ - annotations?: Schema.Annotations.Issue | undefined + input?: unknown, + /** + * The effective parse options controlling input retention. + */ + options?: SchemaAST.ParseOptions ) { - super() - this.actual = actual + 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`. @@ -576,22 +623,19 @@ export class InvalidValue extends Base { * * **Details** * - * - `actual` is `Option.some(value)` when the input is known, or - * `Option.none()` when absent. * - `annotations` optionally carries a `message` string. * - The default formatter renders this as `"Forbidden operation"`. * * **Example** (Creating a Forbidden issue) * - * ```ts - * import { Option, SchemaIssue } from "effect" + * ```ts import.meta.vitest + * import { SchemaIssue } from "effect" * + * const formatIssue = SchemaIssue.makeFormatterDefault() * const issue = new SchemaIssue.Forbidden( - * Option.none(), * { message: "async operation not allowed in sync context" } * ) - * console.log(String(issue)) - * // "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) @@ -601,10 +645,6 @@ export class InvalidValue extends Base { */ export class Forbidden extends Base { readonly _tag = "Forbidden" - /** - * The input value that caused the issue. - */ - readonly actual: Option.Option /** * The metadata for the issue. */ @@ -612,16 +652,20 @@ export class Forbidden extends Base { constructor( /** - * The input value that caused the issue. + * 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`. */ - actual: Option.Option, + input?: unknown, /** - * The metadata for the issue. + * The effective parse options controlling input retention. */ - annotations: Schema.Annotations.Issue | undefined + options?: SchemaAST.ParseOptions ) { - super() - this.actual = actual + super(input, options) this.annotations = annotations } } @@ -637,9 +681,13 @@ export class Forbidden extends Base { * **Details** * * - `ast` is the `Union` AST node. - * - `actual` is the raw input value (plain `unknown`). - * - `issues` contains per-member failures. When empty, the formatter falls - * back to the union's `expected` annotation. + * - `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 @@ -653,10 +701,6 @@ export class AnyOf extends Base { * The schema that caused the issue. */ readonly ast: SchemaAST.Union - /** - * The input value that caused the issue. - */ - readonly actual: unknown /** * The issues that occurred. */ @@ -668,17 +712,21 @@ export class AnyOf extends Base { */ ast: SchemaAST.Union, /** - * The input value that caused the issue. + * The issues that occurred. */ - actual: unknown, + issues: ReadonlyArray, /** - * The issues that occurred. + * The present input associated with the issue. It is retained only when + * `options.reportInput` is `true`. */ - issues: ReadonlyArray + input?: unknown, + /** + * The effective parse options controlling input retention. + */ + options?: SchemaAST.ParseOptions ) { - super() + super(input, options) this.ast = ast - this.actual = actual this.issues = issues } } @@ -695,10 +743,11 @@ export class AnyOf extends Base { * **Details** * * - `ast` is the `Union` AST node. - * - `actual` is the raw input value (plain `unknown`). * - `successes` lists the AST nodes of each member that accepted the input. * - The default formatter renders this as - * `"Expected exactly one member to match the input "`. + * `"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 * @@ -711,10 +760,6 @@ export class OneOf extends Base { * The schema that caused the issue. */ readonly ast: SchemaAST.Union - /** - * The input value that caused the issue. - */ - readonly actual: unknown /** * The schemas that were successful. */ @@ -726,110 +771,73 @@ export class OneOf extends Base { */ ast: SchemaAST.Union, /** - * The input value that caused the issue. + * The schemas that were successful. + */ + successes: ReadonlyArray, + /** + * The present input associated with the issue. It is retained only when + * `options.reportInput` is `true`. */ - actual: unknown, + input?: unknown, /** - * The schemas that were successful. + * The effective parse options controlling input retention. */ - successes: ReadonlyArray + options?: SchemaAST.ParseOptions ) { - super() + super(input, options) this.ast = ast - this.actual = actual this.successes = successes } } -/** - * Extracts the actual input value from any {@link Issue} variant. - * - * **When to use** - * - * Use when you need to retrieve an `Issue`'s offending input value for logging - * or custom error rendering. - * - * **Details** - * - * - Returns `Option.none()` for `Pointer` and `MissingKey` (they carry no - * value). - * - Returns the existing `Option` for variants that already store `actual` as - * `Option` (`InvalidType`, `InvalidValue`, `Forbidden`, `Encoding`, - * `Composite`). - * - Wraps `actual` with `Option.some` for variants that store it as plain - * `unknown` (`AnyOf`, `UnexpectedKey`, `OneOf`, `Filter`). - * - * **Example** (Extracting the actual value) - * - * ```ts - * import { Option, SchemaIssue } from "effect" - * - * const issue = new SchemaIssue.MissingKey(undefined) - * console.log(SchemaIssue.getActual(issue)) - * // { _tag: "None" } - * ``` - * - * @see {@link Issue} - * @see {@link isIssue} - * - * @category getters - * @since 4.0.0 - */ -export function getActual(issue: Issue): Option.Option { - switch (issue._tag) { - case "Pointer": - case "MissingKey": - return Option.none() - case "InvalidType": - case "InvalidValue": - case "Forbidden": - case "Encoding": - case "Composite": - return issue.actual - case "AnyOf": - case "UnexpectedKey": - case "OneOf": - case "Filter": - return Option.some(issue.actual) - } -} - -function makeFilterIssue(input: unknown, entry: Schema.FilterIssue): Issue { +function makeFilterIssue( + entry: Schema.FilterIssue, + input?: unknown, + options?: SchemaAST.ParseOptions +): Issue { if (isIssue(entry)) { return entry } if (typeof entry === "string") { - return new InvalidValue(Option.some(input), { message: entry }) + return new InvalidValue({ message: entry }, input, options) } const inner = typeof entry.issue === "string" - ? new InvalidValue(Option.some(input), { message: entry.issue }) + ? new InvalidValue({ message: entry.issue }, input, options) : entry.issue return new Pointer(entry.path, inner) } /** @internal */ -export function makeSingle(input: unknown, out: undefined | boolean | Schema.FilterIssue): Issue | undefined { +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(Option.some(input)) + return out ? undefined : new InvalidValue(undefined, input, options) } - return makeFilterIssue(input, out) + return makeFilterIssue(out, input, options) } /** @internal */ -export function make(input: unknown, ast: SchemaAST.AST, out: Schema.FilterOutput): Issue | undefined { +export function normalizeFilterOutput( + ast: SchemaAST.AST, + out: Schema.FilterOutput, + input?: unknown, + options?: SchemaAST.ParseOptions +): Issue | undefined { if (Array.isArray(out)) { - if (Arr.isReadonlyArrayNonEmpty(out)) { - if (out.length === 1) { - return makeFilterIssue(input, out[0]) - } - return new Composite(ast, Option.some(input), Arr.map(out, (entry) => makeFilterIssue(input, entry))) + if (!Arr.isReadonlyArrayNonEmpty(out)) { + return undefined } - 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(input, out as undefined | boolean | Schema.FilterIssue) + return makeSingle(out as undefined | boolean | Schema.FilterIssue, input, options) } /** @@ -840,7 +848,7 @@ export function make(input: unknown, ast: SchemaAST.AST, out: Schema.FilterOutpu * @see {@link makeFormatterDefault} — creates a `Formatter` * @see {@link makeFormatterStandardSchemaV1} — creates a `Formatter` * - * @category Formatter + * @category formatting * @since 4.0.0 */ export interface Formatter extends FormatterI {} @@ -856,7 +864,7 @@ export interface Formatter extends FormatterI {} * @see {@link defaultLeafHook} — the built-in implementation * @see {@link Leaf} — the union of terminal issue types * - * @category Formatter + * @category formatting * @since 4.0.0 */ export type LeafHook = (issue: Leaf) => string @@ -871,28 +879,34 @@ export type LeafHook = (issue: Leaf) => string * **Details** * * - Checks for a `message` annotation first; returns it if present. - * - Otherwise generates a default message per `_tag`: - * - `InvalidType` → `"Expected , got "` - * - `InvalidValue` → `"Invalid data "` + * - 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` → `"Unexpected key with value "` + * - `UnexpectedKey` → `"Expected no excess property"` or + * `"Unexpected key with value "` * - `Forbidden` → `"Forbidden operation"` - * - `OneOf` → `"Expected exactly one member to match the input "` + * - `OneOf` → `"Expected exactly one member to match"` or + * `"Expected exactly one member to match the input "` * * **Example** (Formatting Standard Schema issues with defaultLeafHook) * - * ```ts + * ```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 Formatter + * @category formatting * @since 4.0.0 */ export const defaultLeafHook: LeafHook = (issue): string => { @@ -900,17 +914,27 @@ export const defaultLeafHook: LeafHook = (issue): string => { if (message !== undefined) return message switch (issue._tag) { case "InvalidType": - return getExpectedMessage(InternalAnnotations.getExpected(issue.ast), formatOption(issue.actual)) - case "InvalidValue": - return `Invalid data ${formatOption(issue.actual)}` + 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": - return `Unexpected key with value ${format(issue.actual)}` + 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": - return `Expected exactly one member to match the input ${format(issue.actual)}` + case "OneOf": { + const input = formatInput(issue) + return input === undefined + ? "Expected exactly one member to match" + : `Expected exactly one member to match the input ${input}` + } } } @@ -926,11 +950,10 @@ export const defaultLeafHook: LeafHook = (issue): string => { * * - 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 Formatter + * @category formatting * @since 4.0.0 */ export type CheckHook = (issue: Filter) => string | undefined @@ -947,17 +970,16 @@ export type CheckHook = (issue: Filter) => string | undefined * - 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 , got "`. + * fall back to `"Expected "` or, when the filter reports input, + * `"Expected , got "`. * * @see {@link CheckHook} * @see {@link makeFormatterStandardSchemaV1} * - * @category Formatter + * @category formatting * @since 4.0.0 */ -export const defaultCheckHook: CheckHook = (issue): string | undefined => { - return findMessage(issue.issue) ?? findMessage(issue) -} +export const defaultCheckHook: CheckHook = (issue): string | undefined => findMessage(issue.issue) ?? findMessage(issue) /** * Creates a {@link Formatter} that produces a `StandardSchemaV1.FailureResult`. @@ -975,20 +997,30 @@ export const defaultCheckHook: CheckHook = (issue): string | undefined => { * - `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 + * ```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 Formatter + * @category formatting * @since 4.0.0 */ export function makeFormatterStandardSchemaV1(options?: { @@ -1006,8 +1038,18 @@ type DefaultIssue = { readonly path: ReadonlyArray } -function getExpectedMessage(expected: string, actual: string): string { - return `Expected ${expected}, got ${actual}` +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( @@ -1022,15 +1064,16 @@ function toDefaultIssues( if (message !== undefined) { return [{ path, message }] } - switch (issue.issue._tag) { - case "InvalidValue": - return [{ - path, - message: getExpectedMessage(formatCheck(issue.filter), format(issue.actual)) - }] - default: - return toDefaultIssues(issue.issue, path, leafHook, checkHook) + 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) @@ -1039,12 +1082,11 @@ function toDefaultIssues( case "Composite": return issue.issues.flatMap((issue) => toDefaultIssues(issue, path, leafHook, checkHook)) case "AnyOf": { - const message = findMessage(issue) if (issue.issues.length === 0) { - if (message !== undefined) return [{ path, message }] - - const expected = getExpectedMessage(InternalAnnotations.getExpected(issue.ast), format(issue.actual)) - return [{ path, message: expected }] + return [{ + path, + message: findMessage(issue) ?? getExpectedMessage(InternalAnnotations.getExpected(issue.ast), issue) + }] } return issue.issues.flatMap((issue) => toDefaultIssues(issue, path, leafHook, checkHook)) } @@ -1076,25 +1118,32 @@ function formatCheck(check: SchemaAST.Check): string { * * **Details** * - * This is the default formatter used by `SchemaIssue.toString()`. - * * - 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 + * ```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 Formatter + * @category formatting * @since 4.0.0 */ export function makeFormatterDefault(): Formatter { @@ -1144,33 +1193,3 @@ function getMessageAnnotation( const message = annotations?.[type] if (typeof message === "string") return message } - -function formatOption(actual: Option.Option): string { - if (Option.isNone(actual)) return "no value provided" - return format(actual.value) -} - -/** @internal */ -export function redact(issue: Issue): Issue { - switch (issue._tag) { - case "MissingKey": - return issue - case "Forbidden": - return new Forbidden(Option.map(issue.actual, Redacted.make), issue.annotations) - case "Filter": - return new Filter(Redacted.make(issue.actual), issue.filter, redact(issue.issue)) - case "Pointer": - return new Pointer(issue.path, redact(issue.issue)) - - case "Encoding": - case "InvalidType": - case "InvalidValue": - case "Composite": - return new InvalidValue(Option.map(issue.actual, Redacted.make)) - - case "AnyOf": - case "OneOf": - case "UnexpectedKey": - return new InvalidValue(Option.some(Redacted.make(issue.actual))) - } -} diff --git a/repos/effect/packages/effect/src/SchemaParser.ts b/repos/effect/packages/effect/src/SchemaParser.ts index d0f2b3589b..7cc35ebffe 100644 --- a/repos/effect/packages/effect/src/SchemaParser.ts +++ b/repos/effect/packages/effect/src/SchemaParser.ts @@ -10,46 +10,19 @@ * * @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 { 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 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" -const recurDefaults = memoize((ast: SchemaAST.AST): SchemaAST.AST => { - switch (ast._tag) { - case "Declaration": { - const getLink = ast.annotations?.[SchemaAST.ClassTypeId] - if (Predicate.isFunction(getLink)) { - const link = getLink(ast.typeParameters) - const to = recurDefaults(link.to) - return SchemaAST.replaceEncoding(ast, to === link.to ? [link] : [new SchemaAST.Link(to, link.transformation)]) - } - return ast - } - case "Objects": - case "Arrays": - return ast.recur((ast) => { - const defaultValue = ast.context?.defaultValue - if (defaultValue) { - return SchemaAST.replaceEncoding(recurDefaults(ast), defaultValue) - } - return recurDefaults(ast) - }) - case "Suspend": - return ast.recur(recurDefaults) - default: - return ast - } -}) - /** * Creates an effectful maker for the schema's decoded type side. * @@ -68,8 +41,7 @@ const recurDefaults = memoize((ast: SchemaAST.AST): SchemaAST.AST => { * @since 4.0.0 */ export function makeEffect(schema: S) { - const ast = recurDefaults(SchemaAST.toType(schema.ast)) - const parser = run(ast) + const parser = runWithCompiler(constructorCompiler, SchemaAST.toType(schema.ast)) return (input: S["~type.make.in"], options?: Schema.MakeOptions): Effect.Effect => { return parser( input, @@ -122,6 +94,9 @@ export function makeOption(schema: S) { * * 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** * @@ -143,7 +118,7 @@ export function make(schema: S) { exit.cause, "Constructor adapter can only throw schema issues" ) - throw new Error(issue.toString(), { cause: issue }) + throw new Error("Schema validation failed", { cause: issue }) } } @@ -167,11 +142,11 @@ export function make(schema: S) { * that contain defects, interruptions, or asynchronous work at this synchronous * boundary throw an `Error` whose cause is the underlying `Cause`. * - * @category Asserting + * @category guards * @since 3.10.0 */ -export function is(schema: Schema.Schema): (input: I) => input is I & T { - return _is(schema.ast) +export function is(schema: S): (input: I) => input is I & S["Type"] { + return _is(schema.ast) } /** @internal */ @@ -212,6 +187,9 @@ export function _issue(ast: SchemaAST.AST) { * 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** * @@ -219,7 +197,7 @@ export function _issue(ast: SchemaAST.AST) { * synchronous boundary throw an `Error` whose cause is the underlying `Cause`, * instead of being converted to a schema validation error. * - * @category Asserting + * @category guards * @since 4.0.0 */ export function asserts(schema: S, input: I): asserts input is I & S["Type"] { @@ -230,7 +208,7 @@ export function asserts(schema: S, input: I): as exit.cause, "Assertion adapter can only throw schema issues" ) - throw new Error(issue.toString(), { cause: issue }) + throw new Error("Schema validation failed", { cause: issue }) } } @@ -310,6 +288,9 @@ export const decodeEffect: ( * * 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** * @@ -342,6 +323,9 @@ export function decodeUnknownPromise * * 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** * @@ -520,6 +504,9 @@ export const decodeResult: >( * * 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** * @@ -554,6 +541,9 @@ export function decodeUnknownSync>( * * 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** * @@ -648,6 +638,9 @@ export const encodeEffect: ( * * 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** * @@ -679,6 +672,9 @@ export const encodeUnknownPromise = * * 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** * @@ -854,6 +850,9 @@ export const encodeResult: >( * * 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** * @@ -887,6 +886,9 @@ export function encodeUnknownSync>( * * 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** * @@ -909,18 +911,38 @@ export const encodeSync: >( const mergeParseOptions = ( options: SchemaAST.ParseOptions, overrideOptions: SchemaAST.ParseOptions | undefined -): SchemaAST.ParseOptions => overrideOptions === undefined ? options : { ...options, ...overrideOptions } +): 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) { - const parser = recur(ast) - return (input: unknown, options?: SchemaAST.ParseOptions): Effect.Effect => - Effect.flatMapEager(parser(Option.some(input), options ?? SchemaAST.defaultParseOptions), (oa) => { - if (oa._tag === "None") { - return Effect.fail(new SchemaIssue.InvalidValue(oa)) - } - return Effect.succeed(oa.value as T) - }) + 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( @@ -935,7 +957,7 @@ function asPromise( exit.cause, "Promise adapter can only reject schema issues" ) - throw new Error(issue.toString(), { cause: issue }) + throw new Error("Schema validation failed", { cause: issue }) }) } @@ -985,129 +1007,213 @@ function asSync( return exit.value } const issue = InternalSchemaCause.getSchemaIssueOrThrow(exit.cause, "Sync adapter can only throw schema issues") - throw new Error(issue.toString(), { cause: issue }) + throw new Error("Schema validation failed", { cause: issue }) } } -function mapSchemaIssueEffect( - self: Effect.Effect, - f: (issue: SchemaIssue.Issue) => SchemaIssue.Issue -): Effect.Effect { - return Effect.catchCause(self, (cause) => Effect.failCauseSync(() => Cause.map(cause, f))) -} - /** @internal */ export interface Parser { ( - input: Option.Option, + input: unknown, options: SchemaAST.ParseOptions - ): Effect.Effect, SchemaIssue.Issue, any> + ): Effect.Effect } -const recur = memoize( - (ast: SchemaAST.AST): Parser => { - let parser: Parser - const checks = ast.checks - const encoding = ast.encoding - const links = encoding - const len = links?.length ?? 0 - const encodingChecks = (ast as any).encodingChecks - const astOptions = (checks ? checks[checks.length - 1].annotations : ast.annotations) - ?.["parseOptions"] - if (!ast.context && !encoding && !checks && !encodingChecks) { - return (ou, options) => { - parser ??= ast.getParser(recur) - if (astOptions) { - options = { ...options, ...astOptions } - } - return parser(ou, options) - } - } - const isStructural = SchemaAST.isArrays(ast) || SchemaAST.isObjects(ast) || - (SchemaAST.isDeclaration(ast) && ast.typeParameters.length > 0) - const structuralChecks = checks && isStructural ? - checks.filter((check) => check.annotations?.[SchemaAST.STRUCTURAL_ANNOTATION_KEY]) : - undefined - return (ou, options) => { - if (astOptions) { - options = { ...options, ...astOptions } - } - let srou: Effect.Effect, SchemaIssue.Issue, unknown> | undefined - if (links) { - for (let i = len - 1; i >= 0; i--) { - const link = links[i] - const to = link.to - const parser = recur(to) - srou = srou ? Effect.flatMapEager(srou, (ou) => parser(ou, options)) : parser(ou, options) - if (link.transformation._tag === "Transformation") { - const getter = link.transformation.decode - srou = Effect.flatMapEager(srou, (ou) => getter.run(ou, options)) - } else { - srou = link.transformation.decode(srou, options) - } - } - srou = mapSchemaIssueEffect(srou!, (issue) => new SchemaIssue.Encoding(ast, ou, issue)) - } +/** @internal */ +export interface Compiler { + (ast: SchemaAST.AST): Parser +} - parser ??= ast.getParser(recur) - const parseLocal = (localOu: Option.Option) => { - let sroa = parser(localOu, options) +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) +) - if (encodingChecks && !options?.disableChecks) { - sroa = Effect.flatMapEager(sroa, (oa) => { - if (Option.isSome(localOu) && Option.isSome(oa)) { - const issues: Array = [] +function compileConstructorDefault(ast: SchemaAST.AST): Parser { + return ast.context?.constructorDefault ? compileDefaulted(ast) : constructorCompiler(ast) +} - SchemaAST.collectIssues(encodingChecks, localOu.value, issues, ast, options) +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) +} - if (Arr.isArrayNonEmpty(issues)) { - return Effect.fail(new SchemaIssue.Composite(ast, localOu, issues)) - } +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)) } - return Effect.succeed(oa) - }) + } } - - if (checks && !options?.disableChecks) { - if (options?.errors === "all" && structuralChecks && structuralChecks.length > 0 && Option.isSome(localOu)) { - sroa = mapSchemaIssueEffect(sroa, (issue) => { - const issues: Array = [] - SchemaAST.collectIssues( - structuralChecks, - localOu.value, - issues, - ast, - options - ) - const out: SchemaIssue.Issue = Arr.isArrayNonEmpty(issues) - ? issue._tag === "Composite" && issue.ast === ast - ? new SchemaIssue.Composite(ast, issue.actual, [...issue.issues, ...issues]) - : new SchemaIssue.Composite(ast, localOu, [issue, ...issues]) - : issue - return out - }) + } 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)) + } } - sroa = Effect.flatMapEager(sroa, (oa) => { - if (Option.isSome(oa)) { - const value = oa.value - const issues: Array = [] - - SchemaAST.collectIssues(checks, value, issues, ast, options) + return Effect.succeed(value) + }) + } + } - if (Arr.isArrayNonEmpty(issues)) { - return Effect.fail(new SchemaIssue.Composite(ast, oa, issues)) - } + 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(oa) + } + 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 }) } - - return sroa } - - const sroa = srou ? Effect.flatMapEager(srou, parseLocal) : parseLocal(ou) - - return sroa } + 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 index 969ef3f4fe..8818ccc81f 100644 --- a/repos/effect/packages/effect/src/SchemaRepresentation.ts +++ b/repos/effect/packages/effect/src/SchemaRepresentation.ts @@ -1,71 +1,156 @@ /** - * Plain data structures for describing schemas in a serializable form. A - * `Representation` is not the original `Schema` object; it is a JSON-friendly - * description of the schema's types, fields, unions, checks, annotations, and - * references. - * - * This module defines the representation node types, document types, and - * codecs used to validate those documents. It can build representation - * documents from schema ASTs, turn representation documents back into schemas, - * convert them to and from JSON Schema documents, and generate TypeScript code - * artifacts for schema definitions. + * Open, compiler-extensible representation of Effect schemas. * * @since 4.0.0 */ -import * as Arr from "./Array.ts" -import { format, formatPropertyKey } from "./Formatter.ts" -import { collectBrands } from "./internal/schema/annotations.ts" -import * as InternalRepresentation from "./internal/schema/representation.ts" -import { unescapeToken } from "./JsonPointer.ts" +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 Predicate from "./Predicate.ts" -import * as Rec from "./Record.ts" import * as Schema from "./Schema.ts" import * as SchemaAST from "./SchemaAST.ts" import * as SchemaGetter from "./SchemaGetter.ts" -// ----------------------------------------------------------------------------- -// specification -// ----------------------------------------------------------------------------- - /** - * A custom type declaration, such as `Date`, `Option`, or `ReadonlySet`. - * - * **When to use** + * Open persistence identity carried by declarations and opaque checks. * - * Use when inspecting or transforming non-primitive schema types. + * @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. * - * **Details** + * @category annotations + * @since 4.0.0 + */ +export interface CheckRepresentationAnnotation extends RepresentationAnnotation { + readonly schemas?: ReadonlyArray | undefined +} + +/** + * Input passed to JSON Schema compiler annotations. * - * `typeParameters` holds the inner type arguments, such as the `A` in - * `Option`. `encodedSchema` is the fallback representation when no - * {@link Reviver} recognizes this declaration. `annotations.typeConstructor` - * identifies the declaration kind, such as `{ _tag: "effect/Option" }`. + * @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. * - * @see {@link Reviver} - * @see {@link toSchemaDefaultReviver} + * @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> - readonly encodedSchema: Representation + readonly checks: ReadonlyArray } /** - * A lazily resolved representation used for recursive schemas. - * - * **Details** - * - * `thunk` points to the actual representation, possibly via a - * {@link Reference}. `checks` is always empty on `Suspend` nodes. - * - * @see {@link Reference} + * A lazily resolved representation. * * @category models * @since 4.0.0 @@ -78,25 +163,7 @@ export interface Suspend { } /** - * A named reference to a definition in the {@link References} map. - * - * **When to use** - * - * Use when a representation should point to a named definition instead of - * embedding the definition inline. - * - * **Details** - * - * `$ref` is the key into `Document.references` or `MultiDocument.references`. - * References are resolved lazily by {@link toSchema} and - * {@link toCodeDocument}. - * - * **Gotchas** - * - * Resolution throws at runtime if the key is not found in the references map. - * - * @see {@link References} - * @see {@link Document} + * A named reference. * * @category models * @since 4.0.0 @@ -106,244 +173,152 @@ export interface Reference { readonly $ref: string } -/** - * The `null` type. - * - * @category models - * @since 4.0.0 - */ -export interface Null { - readonly _tag: "Null" +interface Keyword { + readonly _tag: Tag readonly annotations?: Schema.Annotations.Annotations | undefined + readonly checks: ReadonlyArray } /** - * The `undefined` type. + * The null keyword representation. * * @category models * @since 4.0.0 */ -export interface Undefined { - readonly _tag: "Undefined" - readonly annotations?: Schema.Annotations.Annotations | undefined -} - +export interface Null extends Keyword<"Null"> {} /** - * The `void` type. + * The undefined keyword representation. * * @category models * @since 4.0.0 */ -export interface Void { - readonly _tag: "Void" - readonly annotations?: Schema.Annotations.Annotations | undefined -} - +export interface Undefined extends Keyword<"Undefined"> {} /** - * The `never` type (no valid values). + * The void keyword representation. * * @category models * @since 4.0.0 */ -export interface Never { - readonly _tag: "Never" - readonly annotations?: Schema.Annotations.Annotations | undefined -} - +export interface Void extends Keyword<"Void"> {} /** - * The `unknown` type (any value accepted). + * The never keyword representation. * * @category models * @since 4.0.0 */ -export interface Unknown { - readonly _tag: "Unknown" - readonly annotations?: Schema.Annotations.Annotations | undefined -} - +export interface Never extends Keyword<"Never"> {} /** - * The `any` type. + * The unknown keyword representation. * * @category models * @since 4.0.0 */ -export interface Any { - readonly _tag: "Any" - readonly annotations?: Schema.Annotations.Annotations | undefined -} - +export interface Unknown extends Keyword<"Unknown"> {} /** - * The `string` type with optional validation checks. - * - * **Details** - * - * `checks` holds string-specific constraints, such as min/max length, pattern, - * and UUID checks. `contentMediaType` and `contentSchema` indicate that the - * string contains encoded data, such as `"application/json"` with a nested - * schema. - * - * @see {@link StringMeta} - * @see {@link Check} + * The any keyword representation. * * @category models * @since 4.0.0 */ -export interface String { - readonly _tag: "String" - readonly annotations?: Schema.Annotations.Annotations | undefined - readonly checks: ReadonlyArray> - readonly contentMediaType?: string | undefined - readonly contentSchema?: Representation | undefined -} +export interface Any extends Keyword<"Any"> {} /** - * The `number` type with optional validation checks. - * - * **Details** - * - * `checks` holds number-specific constraints, such as int, finite, min, max, - * multipleOf, and between checks. - * - * @see {@link NumberMeta} + * A string representation. * * @category models * @since 4.0.0 */ -export interface Number { - readonly _tag: "Number" - readonly annotations?: Schema.Annotations.Annotations | undefined - readonly checks: ReadonlyArray> -} +export interface String extends Keyword<"String"> {} /** - * The `boolean` type. + * A number representation. * * @category models * @since 4.0.0 */ -export interface Boolean { - readonly _tag: "Boolean" - readonly annotations?: Schema.Annotations.Annotations | undefined -} - +export interface Number extends Keyword<"Number"> {} /** - * The `bigint` type with optional validation checks. - * - * @see {@link BigIntMeta} + * A boolean representation. * * @category models * @since 4.0.0 */ -export interface BigInt { - readonly _tag: "BigInt" - readonly annotations?: Schema.Annotations.Annotations | undefined - readonly checks: ReadonlyArray> -} - +export interface Boolean extends Keyword<"Boolean"> {} /** - * The `symbol` type. + * A bigint representation. * * @category models * @since 4.0.0 */ -export interface Symbol { - readonly _tag: "Symbol" - readonly annotations?: Schema.Annotations.Annotations | undefined -} - +export interface BigInt extends Keyword<"BigInt"> {} /** - * A specific literal value (`string`, `number`, `boolean`, or `bigint`). + * A symbol representation. * * @category models * @since 4.0.0 */ -export interface Literal { - readonly _tag: "Literal" - readonly annotations?: Schema.Annotations.Annotations | undefined - readonly literal: string | number | boolean | bigint -} +export interface Symbol extends Keyword<"Symbol"> {} /** - * A specific unique `symbol` value. + * 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 UniqueSymbol { - readonly _tag: "UniqueSymbol" - readonly annotations?: Schema.Annotations.Annotations | undefined - readonly symbol: symbol +export interface Literal extends Keyword<"Literal"> { + readonly literal: SchemaAST.LiteralValue } /** - * The `object` keyword type (matches any non-primitive). + * A unique global symbol representation. * * @category models * @since 4.0.0 */ -export interface ObjectKeyword { - readonly _tag: "ObjectKeyword" - readonly annotations?: Schema.Annotations.Annotations | undefined +export interface UniqueSymbol extends Keyword<"UniqueSymbol"> { + readonly symbol: symbol } /** - * A TypeScript-style enum. Each entry is a `[name, value]` pair. + * The object keyword representation. * * @category models * @since 4.0.0 */ -export interface Enum { - readonly _tag: "Enum" - readonly annotations?: Schema.Annotations.Annotations | undefined - readonly enums: ReadonlyArray -} +export interface ObjectKeyword extends Keyword<"ObjectKeyword"> {} /** - * A template literal type composed of a sequence of parts (literals, strings, - * numbers, etc.). + * 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 TemplateLiteral { - readonly _tag: "TemplateLiteral" - readonly annotations?: Schema.Annotations.Annotations | undefined - readonly parts: ReadonlyArray +export interface Enum extends Keyword<"Enum"> { + readonly enums: ReadonlyArray } /** - * An array or tuple type. - * - * **Details** - * - * `elements` are the fixed positional elements, or tuple prefix, and each may - * be optional. `rest` contains the variadic tail types; a single-element - * `rest` with no `elements` produces a plain `Array`. `checks` holds - * array-specific constraints, such as minLength, maxLength, and unique checks. - * - * @see {@link Element} - * @see {@link ArraysMeta} + * A template literal representation. * * @category models * @since 4.0.0 */ -export interface Arrays { - readonly _tag: "Arrays" - readonly annotations?: Schema.Annotations.Annotations | undefined - readonly elements: ReadonlyArray - readonly rest: ReadonlyArray - readonly checks: ReadonlyArray> +export interface TemplateLiteral extends Keyword<"TemplateLiteral"> { + readonly parts: ReadonlyArray } /** - * A positional element within an {@link Arrays} tuple. - * - * **Details** - * - * `isOptional` indicates whether this element can be absent. `type` is the - * schema representation for this element's value. - * - * @see {@link Arrays} + * A tuple element. * * @category models * @since 4.0.0 @@ -355,39 +330,28 @@ export interface Element { } /** - * An object/struct type with named properties and optional index signatures. - * - * **Details** - * - * `propertySignatures` are the explicitly named fields. `indexSignatures` - * define catch-all key/value types, such as `Record`. `checks` - * holds object-specific constraints, such as minProperties and maxProperties. - * - * @see {@link PropertySignature} - * @see {@link IndexSignature} - * @see {@link ObjectsMeta} + * An array or tuple representation. * * @category models * @since 4.0.0 */ -export interface Objects { - readonly _tag: "Objects" - readonly annotations?: Schema.Annotations.Annotations | undefined - readonly propertySignatures: ReadonlyArray - readonly indexSignatures: ReadonlyArray - readonly checks: ReadonlyArray> +export interface Arrays extends Keyword<"Arrays"> { + readonly elements: ReadonlyArray + readonly rest: ReadonlyArray } /** - * A named property within an {@link Objects} representation. + * A property signature. * * **Details** * - * `name` is the property key, which can be a string, number, or symbol. - * `isOptional` indicates whether the key can be absent. `isMutable` indicates - * whether the property is mutable rather than readonly. + * The live representation stores the native property key. Persistent codecs + * add an explicit type discriminator when encoding it. + * + * **Gotchas** * - * @see {@link Objects} + * 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 @@ -401,15 +365,7 @@ export interface PropertySignature { } /** - * An index signature, such as `[key: string]: number`, within an - * {@link Objects}. - * - * **Details** - * - * `parameter` is the key type representation. `type` is the value type - * representation. - * - * @see {@link Objects} + * An index signature. * * @category models * @since 4.0.0 @@ -420,34 +376,29 @@ export interface IndexSignature { } /** - * A union of multiple representations. - * - * **Details** + * An object representation. * - * `types` are the union members. `mode` controls JSON Schema output as either - * `"anyOf"` (the default) or mutually exclusive `"oneOf"`. + * @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 { - readonly _tag: "Union" - readonly annotations?: Schema.Annotations.Annotations | undefined +export interface Union extends Keyword<"Union"> { readonly types: ReadonlyArray readonly mode: "anyOf" | "oneOf" } /** - * The core tagged union of all supported schema shapes. - * - * **Details** - * - * Each variant has a `_tag` discriminator. Switch on `_tag` to handle each - * shape. Most variants carry optional `annotations` and some carry `checks` - * for validation constraints. - * - * @see {@link Document} - * @see {@link fromAST} + * The structural schema representation. * * @category models * @since 4.0.0 @@ -477,3468 +428,798 @@ export type Representation = | Union /** - * A validation constraint attached to a type. Either a single {@link Filter} - * or a {@link FilterGroup} combining multiple checks. - * - * @see {@link Filter} - * @see {@link FilterGroup} + * A structural check. * * @category models * @since 4.0.0 */ -export type Check = Filter | FilterGroup +export type Check = Filter | FilterGroup /** - * A single validation constraint with typed metadata describing the check - * (e.g. `{ _tag: "isMinLength", minLength: 3 }`). - * - * @see {@link Check} + * An opaque leaf check. * * @category models * @since 4.0.0 */ -export interface Filter { +export interface Filter { readonly _tag: "Filter" - readonly annotations?: Schema.Annotations.Filter | undefined - readonly meta: M + readonly representation?: CheckRepresentationAnnotation | undefined + readonly annotations?: Schema.Annotations.Annotations | undefined + readonly aborted: boolean } /** - * A group of validation constraints that are logically combined. Contains - * at least one {@link Check}. - * - * @see {@link Check} + * A non-empty group of checks. * * @category models * @since 4.0.0 */ -export interface FilterGroup { +export interface FilterGroup { readonly _tag: "FilterGroup" - readonly annotations?: Schema.Annotations.Filter | undefined - readonly checks: readonly [Check, ...Array>] + readonly representation?: CheckRepresentationAnnotation | undefined + readonly annotations?: Schema.Annotations.Annotations | undefined + readonly checks: readonly [Check, ...Array] } /** - * Metadata union for string-specific validation checks (minLength, maxLength, - * pattern, UUID, trimmed, etc.). - * - * @see {@link String} - * @see {@link Check} - * - * @category models - * @since 4.0.0 - */ -export type StringMeta = Schema.Annotations.BuiltInMetaDefinitions[ - | "isStringFinite" - | "isStringBigInt" - | "isStringSymbol" - | "isMinLength" - | "isMaxLength" - | "isPattern" - | "isLengthBetween" - | "isTrimmed" - | "isUUID" - | "isGUID" - | "isULID" - | "isBase64" - | "isBase64Url" - | "isStartsWith" - | "isEndsWith" - | "isIncludes" - | "isUppercased" - | "isLowercased" - | "isCapitalized" - | "isUncapitalized" -] - -/** - * Metadata union for number-specific validation checks (int, finite, - * min, max, multipleOf, between). - * - * @see {@link Number} - * @see {@link Check} + * Named representation definitions. * * @category models * @since 4.0.0 */ -export type NumberMeta = Schema.Annotations.BuiltInMetaDefinitions[ - | "isInt" - | "isFinite" - | "isMultipleOf" - | "isGreaterThanOrEqualTo" - | "isLessThanOrEqualTo" - | "isGreaterThan" - | "isLessThan" - | "isBetween" -] +export interface References { + readonly [$ref: string]: Representation +} /** - * Metadata union for bigint-specific validation checks (min, max, between). - * - * @see {@link BigInt} - * @see {@link Check} + * A single representation and its definitions. * * @category models * @since 4.0.0 */ -export type BigIntMeta = Schema.Annotations.BuiltInMetaDefinitions[ - | "isGreaterThanOrEqualToBigInt" - | "isLessThanOrEqualToBigInt" - | "isGreaterThanBigInt" - | "isLessThanBigInt" - | "isBetweenBigInt" -] +export interface Document { + readonly representation: Representation + readonly references: References +} /** - * Metadata union for array-specific validation checks (minLength, maxLength, - * length, unique). - * - * @see {@link Arrays} - * @see {@link Check} + * Multiple representations sharing definitions. * * @category models * @since 4.0.0 */ -export type ArraysMeta = Schema.Annotations.BuiltInMetaDefinitions[ - | "isMinLength" - | "isMaxLength" - | "isLengthBetween" - | "isUnique" -] +export interface MultiDocument { + readonly representations: readonly [Representation, ...Array] + readonly references: References +} /** - * Metadata union for object-specific validation checks (minProperties, - * maxProperties, propertiesLength, propertyNames). - * - * @see {@link Objects} - * @see {@link Check} + * Reviver for a declaration. * * @category models * @since 4.0.0 */ -export type ObjectsMeta = - | Schema.Annotations.BuiltInMetaDefinitions[ - | "isMinProperties" - | "isMaxProperties" - | "isPropertiesLengthBetween" - ] - | { readonly _tag: "isPropertyNames"; readonly propertyNames: Representation } +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 +} /** - * Metadata union for Date-specific validation checks (valid, min, max, between). - * - * @see {@link Declaration} - * @see {@link DeclarationMeta} + * Reviver for a leaf check. * * @category models * @since 4.0.0 */ -export type DateMeta = Schema.Annotations.BuiltInMetaDefinitions[ - | "isDateValid" - | "isGreaterThanDate" - | "isGreaterThanOrEqualToDate" - | "isLessThanDate" - | "isLessThanOrEqualToDate" - | "isBetweenDate" -] +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 +} /** - * Metadata union for size-based validation checks (minSize, maxSize, size). - * Used for collection types like `Set`, `Map`. - * - * @see {@link Declaration} - * @see {@link DeclarationMeta} + * Reviver for a check group. * * @category models * @since 4.0.0 */ -export type SizeMeta = Schema.Annotations.BuiltInMetaDefinitions[ - | "isMinSize" - | "isMaxSize" - | "isSizeBetween" -] +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 +} /** - * Metadata union for {@link Declaration} checks — either {@link DateMeta} - * or {@link SizeMeta}. + * A check reviver. * * @category models * @since 4.0.0 */ -export type DeclarationMeta = DateMeta | SizeMeta - -/** @internal */ -export type Meta = StringMeta | NumberMeta | BigIntMeta | ArraysMeta | ObjectsMeta | DeclarationMeta +export type CheckReviver

= FilterReviver

| FilterGroupReviver

/** - * A string-keyed map of named {@link Representation} definitions. Used by - * {@link Document} and {@link MultiDocument} for `$ref` resolution (analogous - * to JSON Schema `$defs`). - * - * @see {@link Reference} - * @see {@link Document} + * A typed reviver. * * @category models * @since 4.0.0 */ -export interface References { - readonly [$ref: string]: Representation -} +export type Reviver

= DeclarationReviver

| CheckReviver

/** - * A single {@link Representation} together with its named {@link References}. - * - * **When to use** - * - * Use when representing a single Schema AST together with its named references - * before reconstructing a runtime Schema, converting to JSON Schema, or - * wrapping it as a {@link MultiDocument}. - * - * @see {@link MultiDocument} - * @see {@link fromAST} + * A reviver erased only at collection boundaries. * * @category models * @since 4.0.0 */ -export type Document = { - readonly representation: Representation - readonly references: References -} +export type AnyReviver = Reviver /** - * One or more {@link Representation}s sharing a common {@link References} map. - * - * **When to use** - * - * Use when you use {@link fromASTs} to create this from multiple Schema ASTs, - * {@link toCodeDocument} to generate TypeScript code, and - * {@link toJsonSchemaMultiDocument} to convert to JSON Schema. + * Creates a declaration reviver while inferring its payload type from `payloadSchema`. * - * @see {@link Document} - * @see {@link fromASTs} - * - * @category models + * @category constructors * @since 4.0.0 */ -export type MultiDocument = { - readonly representations: readonly [Representation, ...Array] - readonly references: References -} - -// ----------------------------------------------------------------------------- -// schemas -// ----------------------------------------------------------------------------- - -const Representation$ref = Schema.suspend(() => $Representation) - -const toJsonAnnotationsBlacklist: Set = new Set([ - ...InternalRepresentation.fromASTBlacklist, - "expected", - "contentMediaType", - "contentSchema" -]) +export const makeDeclarationReviver:

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

, + revive: DeclarationReviver

["revive"] +) => DeclarationReviver

= InternalSchema.makeDeclarationReviver /** - * A tree of primitive values used to serialize annotations to JSON. + * Creates a filter reviver while inferring its payload type from `payloadSchema`. * - * @category Tree + * @category constructors * @since 4.0.0 */ -export type PrimitiveTree = Schema.Tree +export const makeFilterReviver:

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

, + revive: FilterReviver

["revive"] +) => FilterReviver

= InternalSchema.makeFilterReviver /** - * Schema for {@link PrimitiveTree}. - * - * **When to use** - * - * Use to validate recursive annotation metadata trees whose leaves are `null`, - * `number`, `boolean`, `bigint`, `symbol`, or `string`. + * Creates a filter group reviver while inferring its payload type from `payloadSchema`. * - * @see {@link PrimitiveTree} for the recursive tree type accepted by this codec - * @see {@link $Annotations} for the annotation codec that filters values through this codec - * - * @category schemas + * @category constructors * @since 4.0.0 */ -export const $PrimitiveTree: Schema.Codec = Schema.Tree( - Schema.Union([ - Schema.Null, - Schema.Number, // allows NaN, Infinity, -Infinity - Schema.Boolean, - Schema.BigInt, - Schema.Symbol, - Schema.String - ]) -) - -const isPrimitiveTree = Schema.is($PrimitiveTree) +export const makeFilterGroupReviver:

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

, + revive: FilterGroupReviver

["revive"] +) => FilterGroupReviver

= InternalSchema.makeFilterGroupReviver /** - * Schema for serializing public `Schema.Annotations.Annotations` values. It - * filters out internal annotation keys and non-primitive values during - * encoding. + * Options for importing JSON Schema Draft 2020-12 documents. * * **When to use** * - * Use to serialize schema annotations in representation schemas while retaining - * only primitive-tree metadata. + * Use when you need to configure pattern handling or transform each JSON Schema node before translation. * * **Details** * - * Decoding is passthrough. Encoding removes internal annotation keys and values - * that are not accepted by `$PrimitiveTree`. + * `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** * - * @see {@link $PrimitiveTree} for the codec used to filter annotation values + * 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 schemas + * @category models * @since 4.0.0 */ -export const $Annotations = Schema.Record(Schema.String, Schema.Unknown).pipe( - Schema.encodeTo(Schema.Record(Schema.String, $PrimitiveTree), { - decode: SchemaGetter.passthrough(), - encode: SchemaGetter.transformOptional(Option.flatMap((r) => { - const out: Record = {} - for (const [k, v] of Object.entries(r)) { - if (!toJsonAnnotationsBlacklist.has(k) && isPrimitiveTree(v)) { - out[k] = v - } - } - return Rec.isEmptyRecord(out) ? Option.none() : Option.some(out) - })) - }) -).annotate({ identifier: "Annotations" }) +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 +} /** - * Schema for the {@link Null} representation node. + * Runtime and TypeScript source generated for one schema. * - * @category schemas + * @category models * @since 4.0.0 */ -export const $Null = Schema.Struct({ - _tag: Schema.tag("Null"), - annotations: Schema.optional($Annotations) -}).annotate({ identifier: "Null" }) +export interface Code { + readonly runtime: string + readonly Type: string +} /** - * Schema for the {@link Undefined} representation node. + * Creates generated runtime and TypeScript source strings for a schema. * - * @category schemas + * @category constructors * @since 4.0.0 */ -export const $Undefined = Schema.Struct({ - _tag: Schema.tag("Undefined"), - annotations: Schema.optional($Annotations) -}).annotate({ identifier: "Undefined" }) +export const makeCode: (runtime: string, Type: string) => Code = InternalToCodeDocument.makeCode /** - * Schema for the {@link Void} representation node. + * Auxiliary source artifact emitted while generating schema code. * - * @category schemas + * @category models * @since 4.0.0 */ -export const $Void = Schema.Struct({ - _tag: Schema.tag("Void"), - annotations: Schema.optional($Annotations) -}).annotate({ identifier: "Void" }) +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 + } /** - * Schema for the {@link Never} representation node. + * Generated schema code together with named references and auxiliary artifacts. * - * @category schemas + * @category models * @since 4.0.0 */ -export const $Never = Schema.Struct({ - _tag: Schema.tag("Never"), - annotations: Schema.optional($Annotations) -}).annotate({ identifier: "Never" }) +export interface CodeDocument { + readonly codes: ReadonlyArray + readonly references: { + readonly nonRecursives: ReadonlyArray<{ + readonly $ref: string + readonly code: Code + }> + readonly recursives: Readonly> + } + readonly artifacts: ReadonlyArray +} /** - * Schema for the {@link Unknown} representation node. + * Lowers the encoded side of an AST to a live representation document. * - * @category schemas - * @since 4.0.0 - */ -export const $Unknown = Schema.Struct({ - _tag: Schema.tag("Unknown"), - annotations: Schema.optional($Annotations) -}).annotate({ identifier: "Unknown" }) - -/** - * Schema for the {@link Any} representation node. + * **Details** + * + * Apply `SchemaAST.toType` to the AST first to lower its type side instead. * - * @category schemas + * @category constructors * @since 4.0.0 */ -export const $Any = Schema.Struct({ - _tag: Schema.tag("Any"), - annotations: Schema.optional($Annotations) -}).annotate({ identifier: "Any" }) - -const $IsStringFinite = Schema.Struct({ - _tag: Schema.tag("isStringFinite"), - regExp: Schema.RegExp -}).annotate({ identifier: "IsStringFinite" }) - -const $IsStringBigInt = Schema.Struct({ - _tag: Schema.tag("isStringBigInt"), - regExp: Schema.RegExp -}).annotate({ identifier: "IsStringBigInt" }) - -const $IsStringSymbol = Schema.Struct({ - _tag: Schema.tag("isStringSymbol"), - regExp: Schema.RegExp -}).annotate({ identifier: "IsStringSymbol" }) - -const $IsTrimmed = Schema.Struct({ - _tag: Schema.tag("isTrimmed"), - regExp: Schema.RegExp -}).annotate({ identifier: "IsTrimmed" }) - -const $IsUUID = Schema.Struct({ - _tag: Schema.tag("isUUID"), - regExp: Schema.RegExp, - version: Schema.UndefinedOr(Schema.Literals([1, 2, 3, 4, 5, 6, 7, 8])) -}).annotate({ identifier: "IsUUID" }) - -const $IsGUID = Schema.Struct({ - _tag: Schema.tag("isGUID"), - regExp: Schema.RegExp -}).annotate({ identifier: "IsGUID" }) - -const $IsULID = Schema.Struct({ - _tag: Schema.tag("isULID"), - regExp: Schema.RegExp -}).annotate({ identifier: "IsULID" }) - -const $IsBase64 = Schema.Struct({ - _tag: Schema.tag("isBase64"), - regExp: Schema.RegExp -}).annotate({ identifier: "IsBase64" }) - -const $IsBase64Url = Schema.Struct({ - _tag: Schema.tag("isBase64Url"), - regExp: Schema.RegExp -}).annotate({ identifier: "IsBase64Url" }) - -const $IsStartsWith = Schema.Struct({ - _tag: Schema.tag("isStartsWith"), - startsWith: Schema.String, - regExp: Schema.RegExp -}).annotate({ identifier: "IsStartsWith" }) - -const $IsEndsWith = Schema.Struct({ - _tag: Schema.tag("isEndsWith"), - endsWith: Schema.String, - regExp: Schema.RegExp -}).annotate({ identifier: "IsEndsWith" }) - -const $IsIncludes = Schema.Struct({ - _tag: Schema.tag("isIncludes"), - includes: Schema.String, - regExp: Schema.RegExp -}).annotate({ identifier: "IsIncludes" }) - -const $IsUppercased = Schema.Struct({ - _tag: Schema.tag("isUppercased"), - regExp: Schema.RegExp -}).annotate({ identifier: "IsUppercased" }) - -const $IsLowercased = Schema.Struct({ - _tag: Schema.tag("isLowercased"), - regExp: Schema.RegExp -}).annotate({ identifier: "IsLowercased" }) - -const $IsCapitalized = Schema.Struct({ - _tag: Schema.tag("isCapitalized"), - regExp: Schema.RegExp -}).annotate({ identifier: "IsCapitalized" }) - -const $IsUncapitalized = Schema.Struct({ - _tag: Schema.tag("isUncapitalized"), - regExp: Schema.RegExp -}).annotate({ identifier: "IsUncapitalized" }) - -const NonNegativeInt = Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)) - -const $IsMinLength = Schema.Struct({ - _tag: Schema.tag("isMinLength"), - minLength: NonNegativeInt -}).annotate({ identifier: "IsMinLength" }) - -const $IsMaxLength = Schema.Struct({ - _tag: Schema.tag("isMaxLength"), - maxLength: NonNegativeInt -}).annotate({ identifier: "IsMaxLength" }) - -const $IsLengthBetween = Schema.Struct({ - _tag: Schema.tag("isLengthBetween"), - minimum: NonNegativeInt, - maximum: NonNegativeInt -}).annotate({ identifier: "IsLengthBetween" }) - -const $IsPattern = Schema.Struct({ - _tag: Schema.tag("isPattern"), - regExp: Schema.RegExp -}).annotate({ identifier: "IsPattern" }) - -/** - * Schema for {@link StringMeta}. - * - * @category schemas - * @since 4.0.0 - */ -export const $StringMeta = Schema.Union([ - $IsStringFinite, - $IsStringBigInt, - $IsStringSymbol, - $IsTrimmed, - $IsUUID, - $IsGUID, - $IsULID, - $IsBase64, - $IsBase64Url, - $IsStartsWith, - $IsEndsWith, - $IsIncludes, - $IsUppercased, - $IsLowercased, - $IsCapitalized, - $IsUncapitalized, - $IsMinLength, - $IsMaxLength, - $IsPattern, - $IsLengthBetween -]).annotate({ identifier: "StringMeta" }) - -function makeCheck(meta: Schema.Codec, identifier: string) { - const Check$ref = Schema.suspend(() => Check) - const Check: Schema.Codec> = Schema.Union([ - Schema.Struct({ - _tag: Schema.tag("Filter"), - annotations: Schema.optional($Annotations), - meta - }).annotate({ identifier: `${identifier}Filter` }), - Schema.Struct({ - _tag: Schema.tag("FilterGroup"), - annotations: Schema.optional($Annotations), - checks: Schema.NonEmptyArray(Check$ref) - }).annotate({ identifier: `${identifier}FilterGroup` }) - ]).annotate({ identifier: `${identifier}Check` }) - return Check +export function toRepresentation(ast: SchemaAST.AST): Document { + return InternalToRepresentation.toRepresentation(ast) } /** - * Schema for the {@link String} representation node. + * Lowers one or more AST encoded sides in a shared reference environment. * - * @category schemas - * @since 4.0.0 - */ -export const $String = Schema.Struct({ - _tag: Schema.tag("String"), - annotations: Schema.optional($Annotations), - checks: Schema.Array(makeCheck($StringMeta, "String")), - contentMediaType: Schema.optional(Schema.String), - contentSchema: Schema.optional(Representation$ref) -}).annotate({ identifier: "String" }) - -const $IsInt = Schema.Struct({ - _tag: Schema.tag("isInt") -}).annotate({ identifier: "IsInt" }) - -const $IsMultipleOf = Schema.Struct({ - _tag: Schema.tag("isMultipleOf"), - divisor: Schema.Finite -}).annotate({ identifier: "IsMultipleOf" }) - -const $IsFinite = Schema.Struct({ - _tag: Schema.tag("isFinite") -}).annotate({ identifier: "IsFinite" }) - -const $IsGreaterThan = Schema.Struct({ - _tag: Schema.tag("isGreaterThan"), - exclusiveMinimum: Schema.Finite -}).annotate({ identifier: "IsGreaterThan" }) - -const $IsGreaterThanOrEqualTo = Schema.Struct({ - _tag: Schema.tag("isGreaterThanOrEqualTo"), - minimum: Schema.Finite -}).annotate({ identifier: "IsGreaterThanOrEqualTo" }) - -const $IsLessThan = Schema.Struct({ - _tag: Schema.tag("isLessThan"), - exclusiveMaximum: Schema.Finite -}).annotate({ identifier: "IsLessThan" }) - -const $IsLessThanOrEqualTo = Schema.Struct({ - _tag: Schema.tag("isLessThanOrEqualTo"), - maximum: Schema.Finite -}).annotate({ identifier: "IsLessThanOrEqualTo" }) - -const $IsBetween = Schema.Struct({ - _tag: Schema.tag("isBetween"), - minimum: Schema.Finite, - maximum: Schema.Finite, - exclusiveMinimum: Schema.optional(Schema.Boolean), - exclusiveMaximum: Schema.optional(Schema.Boolean) -}).annotate({ identifier: "IsBetween" }) - -/** - * Schema for {@link NumberMeta}. + * **Details** * - * @category schemas - * @since 4.0.0 - */ -export const $NumberMeta = Schema.Union([ - $IsInt, - $IsMultipleOf, - $IsFinite, - $IsGreaterThan, - $IsGreaterThanOrEqualTo, - $IsLessThan, - $IsLessThanOrEqualTo, - $IsBetween -]).annotate({ identifier: "NumberMeta" }) - -/** - * Schema for the {@link Number} representation node. + * Apply `SchemaAST.toType` to an AST first to lower its type side instead. * - * @category schemas + * @category constructors * @since 4.0.0 */ -export const $Number = Schema.Struct({ - _tag: Schema.tag("Number"), - annotations: Schema.optional($Annotations), - checks: Schema.Array(makeCheck($NumberMeta, "Number")) -}).annotate({ identifier: "Number" }) +export function toRepresentations( + asts: readonly [SchemaAST.AST, ...Array] +): MultiDocument { + return InternalToRepresentation.toRepresentations(asts) +} /** - * Schema for the {@link Boolean} representation node. + * Wraps a single representation document as a multi-document with one root. + * + * **When to use** * - * @category schemas + * Use when an API such as `toCodeDocument` requires a `MultiDocument`. + * + * @category transforming * @since 4.0.0 */ -export const $Boolean = Schema.Struct({ - _tag: Schema.tag("Boolean"), - annotations: Schema.optional($Annotations) -}).annotate({ identifier: "Boolean" }) - -const $IsGreaterThanBigInt = Schema.Struct({ - _tag: Schema.tag("isGreaterThanBigInt"), - exclusiveMinimum: Schema.BigInt -}).annotate({ identifier: "IsGreaterThanBigInt" }) - -const $IsGreaterThanOrEqualToBigInt = Schema.Struct({ - _tag: Schema.tag("isGreaterThanOrEqualToBigInt"), - minimum: Schema.BigInt -}).annotate({ identifier: "IsGreaterThanOrEqualToBigInt" }) - -const $IsLessThanBigInt = Schema.Struct({ - _tag: Schema.tag("isLessThanBigInt"), - exclusiveMaximum: Schema.BigInt -}).annotate({ identifier: "IsLessThanBigInt" }) - -const $IsLessThanOrEqualToBigInt = Schema.Struct({ - _tag: Schema.tag("isLessThanOrEqualToBigInt"), - maximum: Schema.BigInt -}).annotate({ identifier: "IsLessThanOrEqualToBigInt" }) - -const $IsBetweenBigInt = Schema.Struct({ - _tag: Schema.tag("isBetweenBigInt"), - minimum: Schema.BigInt, - maximum: Schema.BigInt, - exclusiveMinimum: Schema.optional(Schema.Boolean), - exclusiveMaximum: Schema.optional(Schema.Boolean) -}).annotate({ identifier: "IsBetweenBigInt" }) - -const $BigIntMeta = Schema.Union([ - $IsGreaterThanBigInt, - $IsGreaterThanOrEqualToBigInt, - $IsLessThanBigInt, - $IsLessThanOrEqualToBigInt, - $IsBetweenBigInt -]).annotate({ identifier: "BigIntMeta" }) +export function toMultiDocument(document: Document): MultiDocument { + return { + representations: [document.representation], + references: document.references + } +} /** - * Schema for the {@link BigInt} representation node. + * Compiles a live representation document to JSON Schema Draft 2020-12. * * **When to use** * - * Use to encode, decode, or validate serialized `BigInt` representation nodes, - * not application `bigint` values. + * Use when you need JSON Schema output from a representation whose checks carry compiler annotations. * - * **Details** + * **Gotchas** * - * Accepts representation nodes with `_tag: "BigInt"`, optional annotations, - * and bigint-specific validation metadata in `checks`. + * Opaque declarations are represented by an unconstrained JSON Schema. 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. * - * @see {@link BigIntMeta} for the metadata accepted by the `checks` array + * @see {@link toJsonSchemaMultiDocument} for multiple roots sharing definitions * - * @category schemas + * @category transforming * @since 4.0.0 */ -export const $BigInt = Schema.Struct({ - _tag: Schema.tag("BigInt"), - annotations: Schema.optional($Annotations), - checks: Schema.Array(makeCheck($BigIntMeta, "BigInt")) -}).annotate({ identifier: "BigInt" }) +export function toJsonSchemaDocument( + document: Document, + options?: Schema.ToJsonSchemaOptions +): JsonSchema.Document<"draft-2020-12"> { + return InternalToJsonSchemaDocument.toJsonSchemaDocument(document, options) +} /** - * Schema for the {@link Symbol} representation node. + * Compiles multiple live representations to a shared JSON Schema Draft 2020-12 document. * - * @category schemas - * @since 4.0.0 - */ -export const $Symbol = Schema.Struct({ - _tag: Schema.tag("Symbol"), - annotations: Schema.optional($Annotations) -}).annotate({ identifier: "Symbol" }) - -/** - * Schema for the literal value types allowed in a {@link Literal} node - * (string, finite number, boolean, or bigint). + * **When to use** * - * @category schemas - * @since 4.0.0 - */ -export const $LiteralValue = Schema.Union([ - Schema.String, - Schema.Finite, - Schema.Boolean, - Schema.BigInt -]).annotate({ identifier: "LiteralValue" }) - -/** - * Schema for the {@link Literal} representation node. + * Use when several representation roots must share the same JSON Schema definitions. * - * @category schemas - * @since 4.0.0 - */ -export const $Literal = Schema.Struct({ - _tag: Schema.tag("Literal"), - annotations: Schema.optional($Annotations), - literal: $LiteralValue -}).annotate({ identifier: "Literal" }) - -/** - * Schema for the {@link UniqueSymbol} representation node. + * **Gotchas** * - * @category schemas - * @since 4.0.0 - */ -export const $UniqueSymbol = Schema.Struct({ - _tag: Schema.tag("UniqueSymbol"), - annotations: Schema.optional($Annotations), - symbol: Schema.Symbol -}).annotate({ identifier: "UniqueSymbol" }) - -/** - * Schema for the {@link ObjectKeyword} representation node. + * 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. * - * @category schemas - * @since 4.0.0 - */ -export const $ObjectKeyword = Schema.Struct({ - _tag: Schema.tag("ObjectKeyword"), - annotations: Schema.optional($Annotations) -}).annotate({ identifier: "ObjectKeyword" }) - -/** - * Schema for the {@link Enum} representation node. + * @see {@link toJsonSchemaDocument} for a single root * - * @category schemas + * @category transforming * @since 4.0.0 */ -export const $Enum = Schema.Struct({ - _tag: Schema.tag("Enum"), - annotations: Schema.optional($Annotations), - enums: Schema.Array( - Schema.Tuple([ - Schema.String, - Schema.Union([ - Schema.String, - Schema.Number // NaN, Infinity, -Infinity are allowed enum values - ]) - ]) - ) -}).annotate({ identifier: "Enum" }) +export function toJsonSchemaMultiDocument( + document: MultiDocument, + options?: Schema.ToJsonSchemaOptions +): JsonSchema.MultiDocument<"draft-2020-12"> { + return InternalToJsonSchemaDocument.toJsonSchemaMultiDocument(document, options) +} /** - * Schema for the {@link TemplateLiteral} representation node. + * Generates TypeScript source for live schema representations and their definitions. * - * @category schemas - * @since 4.0.0 - */ -export const $TemplateLiteral = Schema.Struct({ - _tag: Schema.tag("TemplateLiteral"), - annotations: Schema.optional($Annotations), - parts: Schema.Array(Representation$ref) -}).annotate({ identifier: "TemplateLiteral" }) - -/** - * Schema for the {@link Element} type (positional tuple element). + * **When to use** * - * @category schemas - * @since 4.0.0 - */ -export const $Element = Schema.Struct({ - isOptional: Schema.Boolean, - type: Representation$ref, - annotations: Schema.optional($Annotations) -}).annotate({ identifier: "Element" }) - -const $IsUnique = Schema.Struct({ - _tag: Schema.tag("isUnique") -}).annotate({ identifier: "IsUnique" }) - -const $ArraysMeta = Schema.Union([ - $IsMinLength, - $IsMaxLength, - $IsLengthBetween, - $IsUnique -]).annotate({ identifier: "ArraysMeta" }) - -/** - * Schema for the {@link Arrays} representation node. + * Use when custom declarations and checks provide `toCode` callbacks and must be emitted without a central handler registry. * - * @category schemas - * @since 4.0.0 - */ -export const $Arrays = Schema.Struct({ - _tag: Schema.tag("Arrays"), - annotations: Schema.optional($Annotations), - elements: Schema.Array($Element), - rest: Schema.Array(Representation$ref), - checks: Schema.Array(makeCheck($ArraysMeta, "Arrays")) -}).annotate({ identifier: "Arrays" }) - -/** - * Schema for the {@link PropertySignature} type. + * **Gotchas** * - * @category schemas - * @since 4.0.0 - */ -export const $PropertySignature = Schema.Struct({ - annotations: Schema.optional($Annotations), - name: Schema.PropertyKey, - type: Representation$ref, - isOptional: Schema.Boolean, - isMutable: Schema.Boolean -}).annotate({ identifier: "PropertySignature" }) - -/** - * Schema for the {@link IndexSignature} type. + * Opaque declarations and leaf checks require `toCode` callbacks. Callback results are used directly, and exceptions raised by a callback pass through unchanged. * - * @category schemas + * @category transforming * @since 4.0.0 */ -export const $IndexSignature = Schema.Struct({ - parameter: Representation$ref, - type: Representation$ref -}).annotate({ identifier: "IndexSignature" }) +export function toCodeDocument(document: MultiDocument): CodeDocument { + return InternalToCodeDocument.toCodeDocument(document) +} -const $IsMinProperties = Schema.Struct({ - _tag: Schema.tag("isMinProperties"), - minProperties: NonNegativeInt -}).annotate({ identifier: "IsMinProperties" }) +const RepresentationSchema = Schema.suspend( + (): Schema.Codec => RepresentationUnion +) +const RepresentationsSchema = Schema.Array(RepresentationSchema) -const $IsMaxProperties = Schema.Struct({ - _tag: Schema.tag("isMaxProperties"), - maxProperties: NonNegativeInt -}).annotate({ identifier: "IsMaxProperties" }) +const RepresentationAnnotationSchema = Schema.Struct({ + id: Schema.NonEmptyString, + payload: Schema.Json +}) -const $IsPropertiesLengthBetween = Schema.Struct({ - _tag: Schema.tag("isPropertiesLengthBetween"), - minimum: NonNegativeInt, - maximum: NonNegativeInt -}).annotate({ identifier: "IsPropertiesLengthBetween" }) +const CheckRepresentationAnnotationSchema = Schema.Struct({ + ...RepresentationAnnotationSchema.fields, + schemas: Schema.optional(RepresentationsSchema) +}) -const $IsPropertyNames = Schema.Struct({ - _tag: Schema.tag("isPropertyNames"), - propertyNames: Representation$ref -}).annotate({ identifier: "IsPropertyNames" }) +const LiveAnnotationsSchema = Schema.Record(Schema.String, Schema.Unknown) +const JsonAnnotationsSchema = Schema.Record(Schema.String, Schema.Json) -/** - * Schema for {@link ObjectsMeta}. - * - * @category schemas - * @since 4.0.0 - */ -export const $ObjectsMeta = Schema.Union([ - $IsMinProperties, - $IsMaxProperties, - $IsPropertiesLengthBetween, - $IsPropertyNames -]).annotate({ identifier: "ObjectsMeta" }) +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) +} -/** - * Schema for the {@link Objects} representation node. - * - * @category schemas - * @since 4.0.0 - */ -export const $Objects = Schema.Struct({ - _tag: Schema.tag("Objects"), - annotations: Schema.optional($Annotations), - propertySignatures: Schema.Array($PropertySignature), - indexSignatures: Schema.Array($IndexSignature), - checks: Schema.Array(makeCheck($ObjectsMeta, "Objects")) -}).annotate({ identifier: "Objects" }) +const AnnotationsSchema = Schema.optional(LiveAnnotationsSchema).pipe( + Schema.encodeTo(Schema.optionalKey(JsonAnnotationsSchema), { + decode: SchemaGetter.passthroughSubtype(), + encode: SchemaGetter.transformOptional((annotations) => + Option.isNone(annotations) || annotations.value === undefined + ? Option.none() + : pruneAnnotations(annotations.value) + ) + }) +) -/** - * Schema for the {@link Union} representation node. - * - * @category schemas - * @since 4.0.0 - */ -export const $Union = Schema.Struct({ +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"), - annotations: Schema.optional($Annotations), - types: Schema.Array(Representation$ref), + ...KeywordFields, + types: RepresentationsSchema, mode: Schema.Literals(["anyOf", "oneOf"]) -}).annotate({ identifier: "Union" }) - -/** - * Schema for the {@link Reference} representation node. - * - * @category schemas - * @since 4.0.0 - */ -export const $Reference = Schema.Struct({ +}) +const ReferenceSchema = Schema.Struct({ _tag: Schema.tag("Reference"), - $ref: Schema.String -}).annotate({ identifier: "Reference" }) - -const $IsDateValid = Schema.Struct({ - _tag: Schema.tag("isDateValid") -}).annotate({ identifier: "IsDateValid" }) - -const $IsGreaterThanDate = Schema.Struct({ - _tag: Schema.tag("isGreaterThanDate"), - exclusiveMinimum: Schema.Date -}).annotate({ identifier: "IsGreaterThanDate" }) + $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 $IsGreaterThanOrEqualToDate = Schema.Struct({ - _tag: Schema.tag("isGreaterThanOrEqualToDate"), - minimum: Schema.Date -}).annotate({ identifier: "IsGreaterThanOrEqualToDate" }) +const ReferencesSchema = Schema.Record(Schema.String, RepresentationSchema) -const $IsLessThanDate = Schema.Struct({ - _tag: Schema.tag("isLessThanDate"), - exclusiveMaximum: Schema.Date -}).annotate({ identifier: "IsLessThanDate" }) +const DocumentFromJson: Schema.Codec = Schema.toCodecJson( + Schema.Struct({ + representation: RepresentationSchema, + references: ReferencesSchema + }) +) -const $IsLessThanOrEqualToDate = Schema.Struct({ - _tag: Schema.tag("isLessThanOrEqualToDate"), - maximum: Schema.Date -}).annotate({ identifier: "IsLessThanOrEqualToDate" }) +const MultiDocumentFromJson: Schema.Codec = Schema.toCodecJson( + Schema.Struct({ + representations: Schema.NonEmptyArray(RepresentationSchema), + references: ReferencesSchema + }) +) -const $IsBetweenDate = Schema.Struct({ - _tag: Schema.tag("isBetweenDate"), - minimum: Schema.Date, - maximum: Schema.Date, - exclusiveMinimum: Schema.optional(Schema.Boolean), - exclusiveMaximum: Schema.optional(Schema.Boolean) -}).annotate({ identifier: "IsBetweenDate" }) +const encodeDocument = Schema.encodeSync(DocumentFromJson) +const encodeMultiDocument = Schema.encodeSync(MultiDocumentFromJson) +const decodeDocument = Schema.decodeSync(DocumentFromJson) +const decodeMultiDocument = Schema.decodeSync(MultiDocumentFromJson) /** - * Schema for {@link DateMeta}. + * Projects a live single-root representation document and encodes it as JSON. * - * @category schemas - * @since 4.0.0 - */ -export const $DateMeta = Schema.Union([ - $IsDateValid, - $IsGreaterThanDate, - $IsGreaterThanOrEqualToDate, - $IsLessThanDate, - $IsLessThanOrEqualToDate, - $IsBetweenDate -]).annotate({ identifier: "DateMeta" }) - -const $IsMinSize = Schema.Struct({ - _tag: Schema.tag("isMinSize"), - minSize: NonNegativeInt -}).annotate({ identifier: "IsMinSize" }) - -const $IsMaxSize = Schema.Struct({ - _tag: Schema.tag("isMaxSize"), - maxSize: NonNegativeInt -}).annotate({ identifier: "IsMaxSize" }) - -const $IsSizeBetween = Schema.Struct({ - _tag: Schema.tag("isSizeBetween"), - minimum: NonNegativeInt, - maximum: NonNegativeInt -}).annotate({ identifier: "IsSizeBetween" }) - -/** - * Schema for {@link SizeMeta}. + * **When to use** * - * @category schemas - * @since 4.0.0 - */ -export const $SizeMeta = Schema.Union([ - $IsMinSize, - $IsMaxSize, - $IsSizeBetween -]).annotate({ identifier: "SizeMeta" }) - -/** - * Schema for {@link DeclarationMeta}. + * Use when you need a stable JSON value for storage or transport after calling `toRepresentation`. * - * @category schemas - * @since 4.0.0 - */ -export const $DeclarationMeta = Schema.Union([ - $DateMeta, - $SizeMeta -]).annotate({ identifier: "DeclarationMeta" }) - -/** - * Schema for the {@link Declaration} representation node. + * **Gotchas** * - * @category schemas - * @since 4.0.0 - */ -export const $Declaration = Schema.Struct({ - _tag: Schema.tag("Declaration"), - annotations: Schema.optional($Annotations), - typeParameters: Schema.Array(Representation$ref), - checks: Schema.Array(makeCheck($DeclarationMeta, "Declaration")), - encodedSchema: Representation$ref -}).annotate({ identifier: "Declaration" }) - -/** - * Schema for the {@link Suspend} representation node. + * Generic annotations that are not JSON are omitted. Invalid persistence identities and unsupported structural values throw an `Error` containing their representation path. * - * @category schemas - * @since 4.0.0 - */ -export const $Suspend = Schema.Struct({ - _tag: Schema.tag("Suspend"), - annotations: Schema.optional($Annotations), - checks: Schema.Tuple([]), - thunk: Representation$ref -}).annotate({ identifier: "Suspend" }) - -/** - * Type-level helper for the recursive {@link $Representation} codec. + * @see {@link toRepresentation} for constructing the live document + * @see {@link toJsonMultiDocument} for documents with multiple roots * - * @category schemas + * @category encoding * @since 4.0.0 */ -export interface $Representation extends Schema.Codec {} +export function toJson(document: Document): Schema.Json { + return encodeDocument(document) +} /** - * Schema for the full {@link Representation} union. It recursively validates - * and encodes any representation node. + * 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. * - * @category schemas + * **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 const $Representation: $Representation = Schema.Union([ - $Null, - $Undefined, - $Void, - $Never, - $Unknown, - $Any, - $String, - $Number, - $Boolean, - $BigInt, - $Symbol, - $Literal, - $UniqueSymbol, - $ObjectKeyword, - $Enum, - $TemplateLiteral, - $Arrays, - $Objects, - $Union, - $Reference, - $Declaration, - $Suspend -]).annotate({ identifier: "Schema" }) +export function toJsonMultiDocument(document: MultiDocument): Schema.Json { + return encodeMultiDocument(document) +} /** - * Schema for {@link Document}. + * Decodes a persisted single-root representation document from JSON. * * **When to use** * - * Use to validate or serialize a single schema representation document with - * `Schema.decodeUnknownSync` or `Schema.encodeSync`. + * Use when reading a representation document from storage or transport before inspecting it or passing it to `fromRepresentation`. * * **Gotchas** * - * This codec validates document structure but does not resolve `$ref` keys - * against `references`. + * Invalid documents throw a schema decoding error. Decoding does not reconstruct runtime callbacks. * - * @see {@link DocumentFromJson} for the JSON-string codec wrapper - * @see {@link $MultiDocument} for validating documents with multiple root representations + * @see {@link toJson} for encoding a document + * @see {@link fromRepresentation} for reconstructing a runtime schema + * @see {@link fromJsonMultiDocument} for multiple roots sharing references * - * @category schemas + * @category decoding * @since 4.0.0 */ -export const $Document = Schema.Struct({ - representation: $Representation, - references: Schema.Record(Schema.String, $Representation) -}).annotate({ identifier: "Document" }) +export function fromJson(input: Schema.Json): Document { + return decodeDocument(input) +} /** - * Schema for {@link MultiDocument}. + * Decodes a persisted multi-root representation document from JSON. + * + * **When to use** * - * @category schemas + * 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 const $MultiDocument = Schema.Struct({ - representations: Schema.NonEmptyArray($Representation), - references: Schema.Record(Schema.String, $Representation) -}).annotate({ identifier: "MultiDocument" }) - -// ----------------------------------------------------------------------------- -// APIs -// ----------------------------------------------------------------------------- +export function fromJsonMultiDocument(input: Schema.Json): MultiDocument { + return decodeMultiDocument(input) +} /** - * Converts a Schema AST into a {@link Document}. + * Reconstructs a runtime schema from a representation document. * * **When to use** * - * Use when you have a single Schema AST and need a schema representation - * document. + * Use when you have decoded or constructed a document whose declaration and check annotations may require revivers. * - * **Details** + * **Gotchas** * - * Shared/recursive sub-schemas are extracted into the `references` map. + * 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** (Converting a Schema to a Document) + * **Example** (Restoring a persisted schema) * - * ```ts + * ```ts import.meta.vitest * import { Schema, SchemaRepresentation } from "effect" * - * const Person = Schema.Struct({ - * name: Schema.String, - * age: Schema.Number - * }) + * 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) * - * const doc = SchemaRepresentation.fromAST(Person.ast) - * console.log(doc.representation._tag) - * // "Objects" + * Schema.decodeUnknownSync(Person)({ name: "Ada" }) // => { name: "Ada" } * ``` * - * @see {@link Document} - * @see {@link fromASTs} + * @see {@link fromJson} for decoding a persisted document + * @see {@link fromRepresentations} for multiple roots sharing references * - * @category constructors + * @category transforming * @since 4.0.0 */ -export const fromAST: (ast: SchemaAST.AST) => Document = InternalRepresentation.fromAST +export function fromRepresentation( + document: Document, + options: { readonly revivers: ReadonlyArray } +): Schema.Top { + return InternalFromRepresentation.fromRepresentation(document, options.revivers) +} /** - * Converts one or more Schema ASTs into a {@link MultiDocument}. + * Reconstructs multiple runtime schemas from a representation multi-document. * * **When to use** * - * Use when you have multiple Schema ASTs and need one schema representation - * `MultiDocument` with shared references. + * Use when multiple roots must be rebuilt in one shared reference environment. * - * **Details** + * **Gotchas** * - * All schemas share a single `references` map. + * Only references reachable from a root are revived. Revivers are resolved locally by `id`; none are installed implicitly. * - * @see {@link MultiDocument} - * @see {@link fromAST} + * @see {@link fromJsonMultiDocument} for decoding a persisted multi-document + * @see {@link fromRepresentation} for a single root * - * @category constructors + * @category transforming * @since 4.0.0 */ -export const fromASTs: (asts: readonly [SchemaAST.AST, ...Array]) => MultiDocument = - InternalRepresentation.fromASTs +export function fromRepresentations( + document: MultiDocument, + options: { readonly revivers: ReadonlyArray } +): readonly [Schema.Top, ...Array] { + return InternalFromRepresentation.fromRepresentations(document, options.revivers) +} /** - * Schema that decodes a {@link Document} from JSON and encodes it back. + * Imports a JSON Schema Draft 2020-12 document as a runtime schema. * * **When to use** * - * Use when you need a JSON codec for schema representation documents with - * `Schema.decodeUnknownSync` or `Schema.encodeSync`. - * - * **Example** (Round-tripping a Document through JSON) + * Use when you need to validate or transform values described by an external JSON Schema document. * - * ```ts - * import { Schema, SchemaRepresentation } from "effect" + * **Gotchas** * - * const doc = SchemaRepresentation.fromAST(Schema.String.ast) - * const json = Schema.encodeSync(SchemaRepresentation.DocumentFromJson)(doc) - * const back = Schema.decodeUnknownSync(SchemaRepresentation.DocumentFromJson)(json) - * ``` + * Import is best-effort. 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. Callback results are used directly, and exceptions raised by a + * callback pass through unchanged. * - * @see {@link $Document} - * @see {@link MultiDocumentFromJson} + * @see {@link fromJsonSchemaMultiDocument} for multiple roots sharing definitions + * @see {@link toRepresentation} for converting the result to a representation document * - * @category schemas + * @category constructors * @since 4.0.0 */ -export const DocumentFromJson: Schema.Codec = Schema.toCodecJson($Document) +export function fromJsonSchemaDocument( + document: JsonSchema.Document<"draft-2020-12">, + options?: FromJsonSchemaOptions +): Schema.Top { + return InternalFromJsonSchemaDocument.fromJsonSchemaDocument(document, options) +} /** - * Schema for `MultiDocument` values encoded as JSON. + * Imports multiple JSON Schema Draft 2020-12 roots as runtime schemas with shared definitions. * - * @see {@link $MultiDocument} - * @see {@link DocumentFromJson} + * **When to use** * - * @category schemas - * @since 4.0.0 - */ -export const MultiDocumentFromJson: Schema.Codec = Schema.toCodecJson($MultiDocument) - -/** - * Wraps a single {@link Document} as a {@link MultiDocument} with one - * representation. + * Use when multiple imported roots share reachable definitions, aliases, or recursion. * - * **When to use** + * **Gotchas** * - * Use when you need to pass a single schema representation `Document` where an - * API expects a `MultiDocument`. + * Only definitions reachable from a root are translated. 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 Document} - * @see {@link MultiDocument} + * @see {@link fromJsonSchemaDocument} for a single root + * @see {@link toRepresentations} for converting the returned schema ASTs to a representation document * - * @category transforming + * @category constructors * @since 4.0.0 */ -export function toMultiDocument(document: Document): MultiDocument { - return { - representations: [document.representation], - references: document.references - } -} - -/** - * A callback that handles {@link Declaration} nodes during reconstruction - * ({@link toSchema}) or code generation ({@link toCodeDocument}). - * - * **Details** - * - * Return a value to handle the declaration. Return `undefined` to fall back to - * default behavior, which uses `encodedSchema` for `toSchema` or the - * `generation` annotation for `toCodeDocument`. `recur` processes child - * representations recursively. - * - * @see {@link toSchema} - * @see {@link toSchemaDefaultReviver} - * @see {@link toCodeDocument} - * - * @category models - * @since 4.0.0 - */ -export type Reviver = (declaration: Declaration, recur: (representation: Representation) => T) => T | undefined - -/** - * Default {@link Reviver} for {@link toSchema} that handles built-in Effect - * types, including Option, Result, Redacted, Cause, Exit, ReadonlyMap, HashMap, - * ReadonlySet, Date, Duration, URL, and RegExp. - * - * **When to use** - * - * Use when you need the default `options.reviver` for {@link toSchema} to - * reconstruct runtime schemas for built-in Effect declarations. - * - * **Details** - * - * The reviver returns `undefined` for unrecognized declarations, causing - * fallback to `encodedSchema`. - * - * @see {@link toSchema} - * @see {@link Reviver} - * - * @category transforming - * @since 4.0.0 - */ -export const toSchemaDefaultReviver: Reviver = (s, recur) => { - const typeConstructor = s.annotations?.typeConstructor - if (Predicate.isObject(typeConstructor) && typeof typeConstructor._tag === "string") { - const typeParameters = s.typeParameters.map(recur) - switch (typeConstructor._tag) { - // built-in types - case "Date": - return Schema.Date - case "Error": - return Schema.Error(typeConstructor.options as Schema.ErrorOptions | undefined) - case "File": - return Schema.File - case "FormData": - return Schema.FormData - case "ReadonlyMap": - return Schema.ReadonlyMap(typeParameters[0], typeParameters[1]) - case "ReadonlySet": - return Schema.ReadonlySet(typeParameters[0]) - case "RegExp": - return Schema.RegExp - case "Uint8Array": - return Schema.Uint8Array - case "URL": - return Schema.URL - case "URLSearchParams": - return Schema.URLSearchParams - // effect types - case "effect/Option": - return Schema.Option(typeParameters[0]) - case "effect/Result": - return Schema.Result(typeParameters[0], typeParameters[1]) - case "effect/Redacted": - return Schema.Redacted(typeParameters[0], typeConstructor.options as any) - case "effect/DateTime.TimeZone": - return Schema.TimeZone - case "effect/DateTime.TimeZone.Named": - return Schema.TimeZoneNamed - case "effect/DateTime.TimeZone.Offset": - return Schema.TimeZoneOffset - case "effect/DateTime.Utc": - return Schema.DateTimeUtc - case "effect/DateTime.Zoned": - return Schema.DateTimeZoned - case "effect/BigDecimal": - return Schema.BigDecimal - case "effect/Chunk": - return Schema.Chunk(typeParameters[0]) - case "effect/Cause": - return Schema.Cause(typeParameters[0], typeParameters[1]) - case "effect/Cause/Failure": - return Schema.CauseReason(typeParameters[0], typeParameters[1]) - case "effect/Duration": - return Schema.Duration - case "effect/Exit": - return Schema.Exit(typeParameters[0], typeParameters[1], typeParameters[2]) - case "effect/Json": - return Schema.Json - case "effect/MutableJson": - return Schema.MutableJson - case "effect/HashMap": - return Schema.HashMap(typeParameters[0], typeParameters[1]) - case "effect/HashSet": - return Schema.HashSet(typeParameters[0]) - } - } -} - -/** - * Creates a runtime Schema from a {@link Document}. - * - * **When to use** - * - * Use when you have a serialized or computed schema representation document and - * need a runtime Schema for decoding/encoding. - * - * **Details** - * - * Pass `options.reviver`, such as {@link toSchemaDefaultReviver}, to handle - * {@link Declaration} nodes for types like `Date` and `Option`. Without a - * reviver, declarations fall back to their `encodedSchema`. Circular references - * are handled via lazy `Schema.suspend`. - * - * **Gotchas** - * - * This throws if a `$ref` is not found in `document.references`. - * - * **Example** (Reconstructing a Schema) - * - * ```ts - * import { Schema, SchemaRepresentation } from "effect" - * - * const doc = SchemaRepresentation.fromAST( - * Schema.Struct({ name: Schema.String }).ast - * ) - * - * const schema = SchemaRepresentation.toSchema(doc) - * console.log(JSON.stringify(Schema.toJsonSchemaDocument(schema), null, 2)) - * ``` - * - * @see {@link Document} - * @see {@link Reviver} - * @see {@link toSchemaDefaultReviver} - * - * @category Runtime Generation - * @since 4.0.0 - */ -export function toSchema(document: Document, options?: { - readonly reviver?: Reviver | undefined -}): S { - type Slot = { - // 0 = not started, 1 = building, 2 = done - state: 0 | 1 | 2 - value: Schema.Top | undefined - ref: Schema.Top - } - - const slots = new Map() - - return recur(document.representation) as S - - function recur(r: Representation): Schema.Top { - let out = on(r) - if ("annotations" in r && r.annotations) out = out.annotate(r.annotations) - out = toSchemaChecks(out, r) - return out - } - - function getSlot(identifier: string): Slot { - const existing = slots.get(identifier) - if (existing) return existing - - // Create the slot *before* resolving, so self-references can see it. - const slot: Slot = { - state: 0, - value: undefined, - ref: Schema.suspend(() => { - if (slot.value === undefined) { - return Schema.Unknown - } - return slot.value - }) - } - slots.set(identifier, slot) - return slot - } - - function resolveReference($ref: string): Schema.Top { - const definition = document.references[$ref] - if (definition === undefined) { - throw new Error(`Reference ${$ref} not found`) - } - - const slot = getSlot($ref) - - if (slot.state === 2) { - // Already built: return the built schema directly - return slot.value! - } - - if (slot.state === 1) { - // Circular: we're currently building this identifier. - return slot.ref - } - - // First time: build it. - slot.state = 1 - try { - slot.value = recur(definition) - slot.state = 2 - return slot.value - } catch (e) { - // Leave the slot in a safe state so future thunks don't silently succeed. - slot.state = 0 - slot.value = undefined - throw e - } - } - - function on(r: Representation): Schema.Top { - switch (r._tag) { - case "Declaration": - return options?.reviver?.(r, recur) ?? recur(r.encodedSchema) - case "Reference": - return resolveReference(r.$ref) - case "Suspend": - return recur(r.thunk) - case "Null": - return Schema.Null - case "Undefined": - return Schema.Undefined - case "Void": - return Schema.Void - case "Never": - return Schema.Never - case "Unknown": - return Schema.Unknown - case "Any": - return Schema.Any - case "String": { - const contentMediaType = r.contentMediaType - const contentSchema = r.contentSchema - if (contentMediaType === "application/json" && contentSchema !== undefined) { - return Schema.fromJsonString(recur(contentSchema)) - } - return Schema.String - } - case "Number": - return Schema.Number - case "Boolean": - return Schema.Boolean - case "BigInt": - return Schema.BigInt - case "Symbol": - return Schema.Symbol - case "Literal": - return Schema.Literal(r.literal) - case "UniqueSymbol": - return Schema.UniqueSymbol(r.symbol) - case "ObjectKeyword": - return Schema.ObjectKeyword - case "Enum": - return Schema.Enum(Object.fromEntries(r.enums)) - case "TemplateLiteral": { - const parts = r.parts.map(recur) as Schema.TemplateLiteral.Parts - return Schema.TemplateLiteral(parts) - } - case "Arrays": { - const elements = r.elements.map((e) => { - const s = recur(e.type) - return e.isOptional ? Schema.optionalKey(s) : s - }) - const rest = r.rest.map(recur) - if (Arr.isArrayNonEmpty(rest)) { - if (r.elements.length === 0 && r.rest.length === 1) { - return Schema.Array(rest[0]) - } - return Schema.TupleWithRest(Schema.Tuple(elements), rest) - } - return Schema.Tuple(elements) - } - case "Objects": { - const fields: Record = {} - - for (const ps of r.propertySignatures) { - const s = recur(ps.type) - const withOptional = ps.isOptional ? Schema.optionalKey(s) : s - fields[ps.name] = ps.isMutable ? Schema.mutableKey(withOptional) : withOptional - } - - const indexSignatures = r.indexSignatures.map((is) => - Schema.Record(recur(is.parameter) as Schema.Record.Key, recur(is.type)) - ) - - if (Arr.isArrayNonEmpty(indexSignatures)) { - if (r.propertySignatures.length === 0 && indexSignatures.length === 1) { - return indexSignatures[0] - } - return Schema.StructWithRest(Schema.Struct(fields), indexSignatures) - } - - return Schema.Struct(fields) - } - case "Union": { - if (r.types.length === 0) return Schema.Never - if (r.types.every((t) => t._tag === "Literal")) { - if (r.types.length === 1) { - return Schema.Literal(r.types[0].literal) - } - return Schema.Literals(r.types.map((t) => t.literal)) - } - return Schema.Union(r.types.map(recur), { mode: r.mode }) - } - } - } - - function toSchemaChecks(top: Schema.Top, schema: Representation): Schema.Top { - switch (schema._tag) { - default: - return top - case "String": - case "Number": - case "BigInt": - case "Arrays": - case "Objects": - case "Declaration": { - const checks = schema.checks.map(toSchemaCheck) - return Arr.isArrayNonEmpty(checks) ? top.check(...checks) : top - } - } - } - - function toSchemaCheck(check: Check): SchemaAST.Check { - switch (check._tag) { - case "Filter": - return toSchemaFilter(check) - case "FilterGroup": { - return Schema.makeFilterGroup(Arr.map(check.checks, toSchemaCheck), check.annotations) - } - } - } - - function toSchemaFilter(filter: Filter): SchemaAST.Check { - const a = filter.annotations - switch (filter.meta._tag) { - // String Meta - case "isStringFinite": - return Schema.isStringFinite(a) - case "isStringBigInt": - return Schema.isStringBigInt(a) - case "isStringSymbol": - return Schema.isStringSymbol(a) - case "isMinLength": - return Schema.isMinLength(filter.meta.minLength, a) - case "isMaxLength": - return Schema.isMaxLength(filter.meta.maxLength, a) - case "isLengthBetween": - return Schema.isLengthBetween(filter.meta.minimum, filter.meta.maximum, a) - case "isPattern": - return Schema.isPattern(filter.meta.regExp, a) - case "isTrimmed": - return Schema.isTrimmed(a) - case "isUUID": - return Schema.isUUID(filter.meta.version, a) - case "isGUID": - return Schema.isGUID(a) - case "isULID": - return Schema.isULID(a) - case "isBase64": - return Schema.isBase64(a) - case "isBase64Url": - return Schema.isBase64Url(a) - case "isStartsWith": - return Schema.isStartsWith(filter.meta.startsWith, a) - case "isEndsWith": - return Schema.isEndsWith(filter.meta.endsWith, a) - case "isIncludes": - return Schema.isIncludes(filter.meta.includes, a) - case "isUppercased": - return Schema.isUppercased(a) - case "isLowercased": - return Schema.isLowercased(a) - case "isCapitalized": - return Schema.isCapitalized(a) - case "isUncapitalized": - return Schema.isUncapitalized(a) - - // Number Meta - case "isFinite": - return Schema.isFinite(a) - case "isInt": - return Schema.isInt(a) - case "isMultipleOf": - return Schema.isMultipleOf(filter.meta.divisor, a) - case "isGreaterThan": - return Schema.isGreaterThan(filter.meta.exclusiveMinimum, a) - case "isGreaterThanOrEqualTo": - return Schema.isGreaterThanOrEqualTo(filter.meta.minimum, a) - case "isLessThan": - return Schema.isLessThan(filter.meta.exclusiveMaximum, a) - case "isLessThanOrEqualTo": - return Schema.isLessThanOrEqualTo(filter.meta.maximum, a) - case "isBetween": - return Schema.isBetween(filter.meta, a) - - // BigInt Meta - case "isGreaterThanBigInt": - return Schema.isGreaterThanBigInt(filter.meta.exclusiveMinimum, a) - case "isGreaterThanOrEqualToBigInt": - return Schema.isGreaterThanOrEqualToBigInt(filter.meta.minimum, a) - case "isLessThanBigInt": - return Schema.isLessThanBigInt(filter.meta.exclusiveMaximum, a) - case "isLessThanOrEqualToBigInt": - return Schema.isLessThanOrEqualToBigInt(filter.meta.maximum, a) - case "isBetweenBigInt": - return Schema.isBetweenBigInt(filter.meta, a) - - // Object Meta - case "isMinProperties": - return Schema.isMinProperties(filter.meta.minProperties, a) - case "isMaxProperties": - return Schema.isMaxProperties(filter.meta.maxProperties, a) - case "isPropertiesLengthBetween": - return Schema.isPropertiesLengthBetween(filter.meta.minimum, filter.meta.maximum, a) - case "isPropertyNames": - return Schema.isPropertyNames(recur(filter.meta.propertyNames) as Schema.Record.Key, a) - - // Arrays Meta - case "isUnique": - return Schema.isUnique(a) - - // Date Meta - case "isDateValid": - return Schema.isDateValid(a) - case "isGreaterThanDate": - return Schema.isGreaterThanDate(filter.meta.exclusiveMinimum, a) - case "isGreaterThanOrEqualToDate": - return Schema.isGreaterThanOrEqualToDate(filter.meta.minimum, a) - case "isLessThanDate": - return Schema.isLessThanDate(filter.meta.exclusiveMaximum, a) - case "isLessThanOrEqualToDate": - return Schema.isLessThanOrEqualToDate(filter.meta.maximum, a) - case "isBetweenDate": - return Schema.isBetweenDate(filter.meta, a) - - // Size Meta - case "isMinSize": - return Schema.isMinSize(filter.meta.minSize, a) - case "isMaxSize": - return Schema.isMaxSize(filter.meta.maxSize, a) - case "isSizeBetween": - return Schema.isSizeBetween(filter.meta.minimum, filter.meta.maximum, a) - } - } -} - -/** - * Converts a {@link Document} to a Draft 2020-12 JSON Schema document. - * - * **When to use** - * - * Use when you need to produce a standard JSON Schema document from a schema - * representation `Document`. - * - * **Gotchas** - * - * JSON Schema generation is best-effort. Some Effect schema representation - * semantics cannot be represented exactly in JSON Schema, and importing an - * emitted JSON Schema may produce an equivalent approximation rather than the - * original representation shape. - * - * **Example** (Generating JSON Schema) - * - * ```ts - * import { Schema, SchemaRepresentation } from "effect" - * - * const doc = SchemaRepresentation.fromAST(Schema.String.ast) - * const jsonSchema = SchemaRepresentation.toJsonSchemaDocument(doc) - * console.log(jsonSchema.schema.type) - * // "string" - * ``` - * - * @see {@link Document} - * @see {@link toJsonSchemaMultiDocument} - * @see {@link fromJsonSchemaDocument} - * - * @category transforming - * @since 4.0.0 - */ -export const toJsonSchemaDocument: ( - document: Document, - options?: Schema.ToJsonSchemaOptions -) => JsonSchema.Document<"draft-2020-12"> = InternalRepresentation.toJsonSchemaDocument - -/** - * Converts a {@link MultiDocument} to a Draft 2020-12 JSON Schema - * multi-document. - * - * **When to use** - * - * Use when you need to export related schema representation documents together - * so shared definitions stay in multi-document JSON Schema form. - * - * **Gotchas** - * - * JSON Schema generation is best-effort. Some Effect schema representation - * semantics cannot be represented exactly in JSON Schema, and importing an - * emitted JSON Schema may produce equivalent approximations rather than the - * original representation shapes. - * - * @see {@link MultiDocument} - * @see {@link toJsonSchemaDocument} - * @see {@link fromJsonSchemaMultiDocument} - * - * @category transforming - * @since 4.0.0 - */ -export const toJsonSchemaMultiDocument: ( - document: MultiDocument, - options?: Schema.ToJsonSchemaOptions -) => JsonSchema.MultiDocument<"draft-2020-12"> = InternalRepresentation.toJsonSchemaMultiDocument - -/** - * A pair of TypeScript source strings for a schema: `runtime` is the - * executable Schema expression, `Type` is the corresponding TypeScript type. - * - * @see {@link makeCode} - * @see {@link CodeDocument} - * - * @category Code Generation - * @since 4.0.0 - */ -export type Code = { - readonly runtime: string - readonly Type: string -} - -/** - * Constructs a {@link Code} value from a runtime expression string and a - * TypeScript type string. - * - * @see {@link Code} - * - * @category Code Generation - * @since 4.0.0 - */ -export function makeCode(runtime: string, Type: string): Code { - return { runtime, Type } -} - -/** - * An auxiliary code artifact produced during code generation — a symbol - * declaration, an enum declaration, or an import statement. - * - * @see {@link CodeDocument} - * @see {@link toCodeDocument} - * - * @category Code Generation - * @since 4.0.0 - */ -export type Artifact = - | { - readonly _tag: "Symbol" - readonly identifier: string - readonly generation: Code - } - | { - readonly _tag: "Enum" - readonly identifier: string - readonly generation: Code - } - | { - readonly _tag: "Import" - readonly importDeclaration: string - } - -/** - * The output of {@link toCodeDocument}: generated TypeScript code for one or - * more schemas plus their shared references and auxiliary artifacts. - * - * **Details** - * - * `codes` contains one {@link Code} per input representation. - * `references.nonRecursives` contains topologically sorted non-recursive - * definitions. `references.recursives` contains definitions involved in cycles. - * `artifacts` contains symbols, enums, and import statements needed by the - * code. - * - * @see {@link toCodeDocument} - * @see {@link Code} - * @see {@link Artifact} - * - * @category Code Generation - * @since 4.0.0 - */ -export type CodeDocument = { - readonly codes: ReadonlyArray - readonly references: { - readonly nonRecursives: ReadonlyArray<{ - readonly $ref: string - readonly code: Code - }> - readonly recursives: { - readonly [$ref: string]: Code - } - } - readonly artifacts: ReadonlyArray -} - -/** - * Generates TypeScript code strings from a {@link MultiDocument}. - * - * **When to use** - * - * Use when you need to produce source code for Effect Schema definitions from a - * schema representation `MultiDocument`. - * - * **Details** - * - * `options.reviver` can customize code generation for {@link Declaration} - * nodes. Return `undefined` to fall back to the default logic, which uses - * `generation` annotations or the encoded schema. References are - * topologically sorted so non-recursive definitions are emitted before their - * dependents. `$ref` keys are converted to sanitized JavaScript identifiers. - * - * **Example** (Generating TypeScript code) - * - * ```ts - * import { Schema, SchemaRepresentation } from "effect" - * - * const Person = Schema.Struct({ - * name: Schema.String, - * age: Schema.Int - * }) - * - * const multi = SchemaRepresentation.toMultiDocument( - * SchemaRepresentation.fromAST(Person.ast) - * ) - * const codeDoc = SchemaRepresentation.toCodeDocument(multi) - * console.log(codeDoc.codes[0].runtime) - * // Schema.Struct({ ... }) - * ``` - * - * @see {@link CodeDocument} - * @see {@link MultiDocument} - * @see {@link Reviver} - * - * @category Code Generation - * @since 4.0.0 - */ -export function toCodeDocument(multiDocument: MultiDocument, options?: { - /** - * The reviver can return `undefined` to indicate that the generation should be generated by the default logic - */ - readonly reviver?: Reviver | undefined -}): CodeDocument { - const artifacts: Array = [] - - const ts = topologicalSort(multiDocument.references) - - // Phase 1: Build sanitization map with collision handling - const sanitizedReferenceMap = new Map() - const uniqueSanitizedReferences = new Set() - const referenceCount = new Map() - - // Process all references first to build the map - const allRefs = [ - ...ts.nonRecursives.map(({ $ref }) => $ref), - ...Object.keys(ts.recursives) - ] - - for (const ref of allRefs) { - ensureUniqueSanitized(ref) - } - - // Phase 2: Use the map when processing references - const nonRecursives = ts.nonRecursives.map(({ $ref, representation }) => ({ - $ref: sanitizedReferenceMap.get($ref)!, - code: recur(representation) - })) - const recursives = Rec.mapEntries(ts.recursives, (representation, $ref) => [ - sanitizedReferenceMap.get($ref)!, - recur(representation) - ]) - - const codes = multiDocument.representations.map(recur) - - return { - codes, - references: { - nonRecursives: nonRecursives.filter(({ $ref }) => (referenceCount.get($ref) ?? 0) > 0), - recursives: Rec.filter(recursives, (_, $ref) => (referenceCount.get($ref) ?? 0) > 0) - }, - artifacts - } - - function ensureUniqueSanitized(originalRef: string): string { - // Check if already mapped (consistency) - const sanitized = sanitizedReferenceMap.get(originalRef) - if (sanitized !== undefined) { - return sanitized - } - - // Find unique sanitized name - const seed = sanitizeJavaScriptIdentifier(originalRef) - let candidate = seed - let suffix = 0 - - while (uniqueSanitizedReferences.has(candidate)) { - candidate = `${seed}${++suffix}` - } - - uniqueSanitizedReferences.add(candidate) - sanitizedReferenceMap.set(originalRef, candidate) - return candidate - } - - function addSymbol(s: symbol): string { - const identifier = ensureUniqueSanitized("_symbol") - const key = globalThis.Symbol.keyFor(s) - const description = s.description - const generation = key === undefined - ? makeCode(`Symbol(${description === undefined ? "" : format(description)})`, `typeof ${identifier}`) - : makeCode(`Symbol.for(${format(key)})`, `typeof ${identifier}`) - artifacts.push({ _tag: "Symbol", identifier, generation }) - return identifier - } - - function addEnum(s: Enum): string { - const identifier = ensureUniqueSanitized("_Enum") - artifacts.push({ - _tag: "Enum", - identifier, - generation: makeCode( - `enum ${identifier} { ${s.enums.map(([name, value]) => `${format(name)}: ${format(value)}`).join(", ")} }`, - `typeof ${identifier}` - ) - }) - return identifier - } - - function addImport(importDeclaration: string) { - if (!artifacts.some((a) => a._tag === "Import" && a.importDeclaration === importDeclaration)) { - artifacts.push({ _tag: "Import", importDeclaration }) - } - } - - function recur(s: Representation): Code { - const g = on(s) - switch (s._tag) { - default: - return makeCode( - g.runtime + toRuntimeAnnotate(s.annotations) + toRuntimeBrand(s.annotations), - g.Type + toTypeBrand(s.annotations) - ) - case "Reference": - return g - case "Declaration": - case "String": - case "Number": - case "BigInt": - case "Arrays": - case "Objects": - case "Suspend": - return makeCode( - g.runtime + toRuntimeAnnotate(s.annotations) + toRuntimeBrand(s.annotations) + toRuntimeChecks(s.checks), - g.Type + toTypeBrand(s.annotations) + toTypeChecks(s.checks) - ) - } - } - - function on(s: Representation): Code { - switch (s._tag) { - case "Declaration": { - // if there is a reviver, use it to generate the generation - if (options?.reviver !== undefined) { - // the reviver can return `undefined` to indicate that the generation should be generated by the default logic - const out = options.reviver(s, recur) - if (out !== undefined) { - return out - } - } - // otherwise, use the generation from the annotations - const generation = s.annotations?.generation - if ( - Predicate.isObject(generation) && typeof generation.runtime === "string" && - typeof generation.Type === "string" - ) { - const typeParameters = s.typeParameters.map(recur) - if (typeof generation.importDeclaration === "string") { - addImport(generation.importDeclaration) - } - return makeCode( - replacePlaceholders(generation.runtime, typeParameters.map((p) => p.runtime)), - replacePlaceholders(generation.Type, typeParameters.map((p) => p.Type)) - ) - } - // otherwise, use the generation from the encoded schema - return recur(s.encodedSchema) - } - case "Reference": { - const sanitized = ensureUniqueSanitized(s.$ref) - referenceCount.set(sanitized, (referenceCount.get(sanitized) ?? 0) + 1) - return makeCode(sanitized, sanitized) - } - case "Suspend": { - const thunk = recur(s.thunk) - return makeCode( - `Schema.suspend((): Schema.Codec<${thunk.Type}> => ${thunk.runtime})`, - thunk.Type - ) - } - case "Null": - return makeCode(`Schema.Null`, "null") - case "Undefined": - return makeCode(`Schema.Undefined`, "undefined") - case "Void": - return makeCode(`Schema.Void`, "void") - case "Never": - return makeCode(`Schema.Never`, "never") - case "Unknown": - return makeCode(`Schema.Unknown`, "unknown") - case "Any": - return makeCode(`Schema.Any`, "any") - case "Number": - return makeCode(`Schema.Number`, "number") - case "Boolean": - return makeCode(`Schema.Boolean`, "boolean") - case "BigInt": - return makeCode(`Schema.BigInt`, "bigint") - case "Symbol": - return makeCode(`Schema.Symbol`, "symbol") - case "String": { - const contentMediaType = s.contentMediaType - const contentSchema = s.contentSchema - if (contentMediaType === "application/json" && contentSchema !== undefined) { - return makeCode(`Schema.fromJsonString(${recur(contentSchema)})`, "string") - } else { - return makeCode(`Schema.String`, "string") - } - } - case "Literal": { - const literal = format(s.literal) - return makeCode(`Schema.Literal(${literal})`, literal) - } - case "UniqueSymbol": { - const identifier = addSymbol(s.symbol) - return makeCode(`Schema.UniqueSymbol(${identifier})`, `typeof ${identifier}`) - } - case "ObjectKeyword": - return makeCode(`Schema.ObjectKeyword`, "object") - case "Enum": { - const identifier = addEnum(s) - return makeCode(`Schema.Enum(${identifier})`, `typeof ${identifier}`) - } - case "TemplateLiteral": { - const parts = s.parts.map(recur) - const type = toTypeParts(s.parts).map((p) => "`" + p + "`").join(" | ") - return makeCode(`Schema.TemplateLiteral([${parts.map((p) => p.runtime).join(", ")}])`, type) - } - case "Arrays": { - const elements = s.elements.map((e) => { - return { - isOptional: e.isOptional, - type: recur(e.type), - annotations: e.annotations - } - }) - - const rest = s.rest.map(recur) - - if (Arr.isArrayNonEmpty(rest)) { - const item = rest[0] - if (elements.length === 0 && rest.length === 1) { - return makeCode( - `Schema.Array(${item.runtime})`, - `ReadonlyArray<${item.Type}>` - ) - } - const post = rest.slice(1) - return makeCode( - `Schema.TupleWithRest(Schema.Tuple([${ - elements.map((e) => - toRuntimeIsOptional(e.isOptional, e.type.runtime) + toRuntimeAnnotateKey(e.annotations) - ).join(", ") - }]), [${rest.map((r) => r.runtime).join(", ")}])`, - `readonly [${ - elements.map((e) => toTypeIsOptional(e.isOptional, e.type.Type)).join(", ") - }, ...Array<${item.Type}>${post.length > 0 ? `, ${post.map((p) => p.Type).join(", ")}` : ""}]` - ) - } - return makeCode( - `Schema.Tuple([${ - elements.map((e) => toRuntimeIsOptional(e.isOptional, e.type.runtime) + toRuntimeAnnotateKey(e.annotations)) - .join(", ") - }])`, - `readonly [${elements.map((e) => toTypeIsOptional(e.isOptional, e.type.Type)).join(", ")}]` - ) - } - case "Objects": { - const pss = s.propertySignatures.map((p) => { - const isSymbol = typeof p.name === "symbol" - const name = isSymbol ? addSymbol(p.name) : formatPropertyKey(p.name) - const nameType = toTypeIsOptional( - p.isOptional, - toTypeIsMutable(p.isMutable, isSymbol ? `[typeof ${name}]` : name) - ) - const type = recur(p.type) - return makeCode( - `${isSymbol ? `[${name}]` : name}: ${ - toRuntimeIsOptional(p.isOptional, toRuntimeIsMutable(p.isMutable, type.runtime)) - }` + - toRuntimeAnnotateKey(p.annotations), - `${nameType}: ${type.Type}` - ) - }) - - const iss = s.indexSignatures.map((is) => { - return { - parameter: recur(is.parameter), - type: recur(is.type) - } - }) - - if (iss.length === 0) { - // 1) Only properties -> Struct - return makeCode( - `Schema.Struct({ ${pss.map((p) => p.runtime).join(", ")} })`, - `{ ${pss.map((p) => p.Type).join(", ")} }` - ) - } else if (pss.length === 0 && iss.length === 1) { - // 2) Only one index signature and no properties -> Record - return makeCode( - `Schema.Record(${iss[0].parameter.runtime}, ${iss[0].type.runtime})`, - `{ readonly [x: ${iss[0].parameter.Type}]: ${iss[0].type.Type} }` - ) - } else { - // 3) Properties + index signatures -> StructWithRest - return makeCode( - `Schema.StructWithRest(Schema.Struct({ ${pss.map((p) => p.runtime).join(", ")} }), [${ - iss.map((is) => `Schema.Record(${is.parameter.runtime}, ${is.type.runtime})`).join(", ") - }])`, - `{ ${pss.map((p) => p.Type).join(", ")}, ${ - iss.map((is) => `readonly [x: ${is.parameter.Type}]: ${is.type.Type}`).join(", ") - } }` - ) - } - } - case "Union": { - if (s.types.length === 0) { - return makeCode("Schema.Never", "never") - } - if (s.types.every((t) => t._tag === "Literal")) { - const literals = s.types.map((l) => format(l.literal)) - if (literals.length === 1) { - return makeCode(`Schema.Literal(${literals[0]})`, literals[0]) - } - return makeCode(`Schema.Literals([${literals.join(", ")}])`, literals.join(" | ")) - } - const mode = s.mode === "anyOf" ? "" : `, { mode: "oneOf" }` - const types = s.types.map((t) => recur(t)) - return makeCode( - `Schema.Union([${types.map((t) => t.runtime).join(", ")}]${mode})`, - types.map((t) => t.Type).join(" | ") - ) - } - } - } - - function toTypeBrand(annotations: Schema.Annotations.Annotations | undefined): string { - const brands = collectBrands(annotations) - if (brands.length === 0) return "" - addImport(`import type * as Brand from "effect/Brand"`) - return brands.map((b) => ` & Brand.Brand<${format(b)}>`).join("") - } - - function toTypeChecks(checks: ReadonlyArray>): string { - return checks.map((c) => toTypeCheck(c)).join("") - } - - function toTypeCheck(check: Check): string { - switch (check._tag) { - case "Filter": - return toTypeBrand(check.annotations) - case "FilterGroup": { - return toTypeChecks(check.checks) - } - } - } - - function toRuntimeChecks(checks: ReadonlyArray>): string { - return checks.map((c) => `.check(${toRuntimeCheck(c)})` + toRuntimeBrand(c.annotations)).join("") - } - - function toRuntimeCheck(check: Check): string { - switch (check._tag) { - case "Filter": - return toRuntimeFilter(check) - case "FilterGroup": { - const a = toRuntimeAnnotations(check.annotations) - const ca = a === "" ? "" : `, ${a}` - return `Schema.makeFilterGroup([${check.checks.map((c) => toRuntimeCheck(c)).join(", ")}]${ca})` - } - } - } - - function toRuntimeFilter(filter: Filter): string { - const a = toRuntimeAnnotations(filter.annotations) - const ca = a === "" ? "" : `, ${a}` - switch (filter.meta._tag) { - case "isTrimmed": - case "isGUID": - case "isULID": - case "isBase64": - case "isBase64Url": - case "isUppercased": - case "isLowercased": - case "isCapitalized": - case "isUncapitalized": - case "isFinite": - case "isInt": - case "isUnique": - case "isDateValid": - return `Schema.${filter.meta._tag}(${a})` - - case "isStringFinite": - case "isStringBigInt": - case "isStringSymbol": - case "isPattern": - return `Schema.${filter.meta._tag}(${toRuntimeRegExp(filter.meta.regExp)}${ca})` - - case "isMinLength": - return `Schema.isMinLength(${filter.meta.minLength}${ca})` - case "isMaxLength": - return `Schema.isMaxLength(${filter.meta.maxLength}${ca})` - case "isLengthBetween": - return `Schema.isLengthBetween(${filter.meta.minimum}, ${filter.meta.maximum}${ca})` - case "isUUID": - return `Schema.isUUID(${filter.meta.version}${ca})` - case "isStartsWith": - return `Schema.isStartsWith(${format(filter.meta.startsWith)}${ca})` - case "isEndsWith": - return `Schema.isEndsWith(${format(filter.meta.endsWith)}${ca})` - case "isIncludes": - return `Schema.isIncludes(${format(filter.meta.includes)}${ca})` - - case "isGreaterThan": - case "isGreaterThanBigInt": - case "isGreaterThanDate": - return `Schema.${filter.meta._tag}(${toRuntimeValue(filter.meta.exclusiveMinimum)}${ca})` - case "isGreaterThanOrEqualTo": - case "isGreaterThanOrEqualToBigInt": - case "isGreaterThanOrEqualToDate": - return `Schema.${filter.meta._tag}(${toRuntimeValue(filter.meta.minimum)}${ca})` - case "isLessThan": - case "isLessThanBigInt": - case "isLessThanDate": - return `Schema.${filter.meta._tag}(${toRuntimeValue(filter.meta.exclusiveMaximum)}${ca})` - case "isLessThanOrEqualTo": - case "isLessThanOrEqualToBigInt": - case "isLessThanOrEqualToDate": - return `Schema.${filter.meta._tag}(${toRuntimeValue(filter.meta.maximum)}${ca})` - case "isBetween": - case "isBetweenBigInt": - case "isBetweenDate": - return `Schema.${filter.meta._tag}({ minimum: ${toRuntimeValue(filter.meta.minimum)}, maximum: ${ - toRuntimeValue(filter.meta.maximum) - }, exclusiveMinimum: ${toRuntimeValue(filter.meta.exclusiveMinimum)}, exclusiveMaximum: ${ - toRuntimeValue(filter.meta.exclusiveMaximum) - }${ca})` - - case "isMultipleOf": - return `Schema.isMultipleOf(${filter.meta.divisor}${ca})` - - case "isMinProperties": - return `Schema.isMinProperties(${filter.meta.minProperties}${ca})` - case "isMaxProperties": - return `Schema.isMaxProperties(${filter.meta.maxProperties}${ca})` - case "isPropertiesLengthBetween": - return `Schema.isPropertiesLengthBetween(${filter.meta.minimum}, ${filter.meta.maximum}${ca})` - case "isPropertyNames": - return `Schema.isPropertyNames(${recur(filter.meta.propertyNames).runtime}${ca})` - - case "isMinSize": - return `Schema.isMinSize(${filter.meta.minSize}${ca})` - case "isMaxSize": - return `Schema.isMaxSize(${filter.meta.maxSize}${ca})` - case "isSizeBetween": - return `Schema.isSizeBetween(${filter.meta.minimum}, ${filter.meta.maximum}${ca})` - } - } -} - -const VALID_ASCII_UPPER_JAVASCRIPT_IDENTIFIER_REGEXP = /^[A-Z_$][A-Za-z0-9_$]*$/ - -/** - * Converts an arbitrary string into a valid (ASCII) JavaScript identifier - * starting with an uppercase letter, `$`, or `_`. - * - * - Replaces invalid identifier characters with `_` - * - Uppercases a leading ASCII letter - * - If the first character is a digit, prefixes `_` - * - Empty input becomes `_` - * - * @internal - */ -export function sanitizeJavaScriptIdentifier(s: string): string { - if (s.length === 0) return "_" - if (VALID_ASCII_UPPER_JAVASCRIPT_IDENTIFIER_REGEXP.test(s)) return s - - const out: Array = [] - let needsPrefix = false - let i = 0 - - for (const ch of s) { - if (i === 0) { - if (ch === "_" || ch === "$" || (ch >= "A" && ch <= "Z")) { - out.push(ch) - } else if (ch >= "a" && ch <= "z") { - out.push(ch.toUpperCase()) - } else if (ch >= "0" && ch <= "9") { - out.push(ch) - needsPrefix = true - } else { - out.push("_") - } - } else { - out.push(isAsciiIdPart(ch) ? ch : "_") - } - i++ - } - - return needsPrefix ? "_" + out.join("") : out.join("") -} - -function isAsciiIdStart(ch: string): boolean { - return ( - ch === "_" || - ch === "$" || - (ch >= "A" && ch <= "Z") || - (ch >= "a" && ch <= "z") - ) -} - -function isAsciiIdPart(ch: string): boolean { - return isAsciiIdStart(ch) || (ch >= "0" && ch <= "9") -} - -function replacePlaceholders(template: string, items: ReadonlyArray) { - let i = 0 - return template.replace(/\?/g, () => items[i++]) -} - -function toTypeParts(parts: ReadonlyArray): ReadonlyArray { - if (parts.length === 0) { - return [""] - } - const [first, ...rest] = parts - const restPatterns = toTypeParts(rest) - return toTypePart(first).flatMap((f) => restPatterns.map((r) => f + r)) -} - -function toTypePart(r: Representation): ReadonlyArray { - switch (r._tag) { - case "Literal": - return [globalThis.String(r.literal)] - case "String": - return ["${string}"] - case "Number": - return ["${number}"] - case "BigInt": - return ["${bigint}"] - case "TemplateLiteral": - return toTypeParts(r.parts) - case "Union": - return r.types.flatMap(toTypePart) - default: - return [] - } -} - -const toCodeAnnotationsBlacklist: Set = new Set([ - ...toJsonAnnotationsBlacklist, - "typeConstructor", - "generation", - "brands" -]) - -function toRuntimeAnnotations(annotations: Schema.Annotations.Annotations | undefined): string { - if (!annotations) return "" - const entries: Array = [] - for (const [key, value] of Object.entries(annotations)) { - if (toCodeAnnotationsBlacklist.has(key)) continue - entries.push(`${formatPropertyKey(key)}: ${format(value)}`) - } - if (entries.length === 0) return "" - return `{ ${entries.join(", ")} }` -} - -function toRuntimeBrand(annotations: Schema.Annotations.Annotations | undefined): string { - const brands = collectBrands(annotations) - return brands.length > 0 ? `.pipe(${brands.map((b) => `Schema.brand(${format(b)})`).join(", ")})` : "" -} - -function toRuntimeAnnotate(annotations: Schema.Annotations.Annotations | undefined): string { - const s = toRuntimeAnnotations(annotations) - return s === "" ? "" : `.annotate(${s})` -} - -function toRuntimeAnnotateKey(annotations: Schema.Annotations.Annotations | undefined): string { - const s = toRuntimeAnnotations(annotations) - return s === "" ? "" : `.annotateKey(${s})` -} - -function toRuntimeIsOptional(isOptional: boolean, runtime: string): string { - return isOptional ? `Schema.optionalKey(${runtime})` : runtime -} - -function toTypeIsOptional(isOptional: boolean, type: string): string { - return isOptional ? `${type}?` : type -} - -function toRuntimeIsMutable(isMutable: boolean, runtime: string): string { - return isMutable ? `Schema.mutableKey(${runtime})` : runtime -} - -function toTypeIsMutable(isMutable: boolean, type: string): string { - return isMutable ? type : `readonly ${type}` -} - -function toRuntimeValue(value: undefined | number | boolean | bigint | Date): string { - if (value instanceof Date) { - return `new Date(${value.getTime()})` - } - return format(value) -} - -function toRuntimeRegExp(regExp: RegExp): string { - const args = [format(regExp.source)] - const flags = regExp.flags.trim() - if (flags !== "") { - args.push(format(flags)) - } - return `new RegExp(${args.join(", ")})` -} - -/** - * Parses a Draft 2020-12 JSON Schema document into a {@link Document}. - * - * **When to use** - * - * Use when you need to import a Draft 2020-12 JSON Schema document into the - * Effect schema representation system. - * - * **Details** - * - * `options.onEnter` is an optional hook called on each JSON Schema node before - * processing, allowing pre-transformation. - * - * **Gotchas** - * - * JSON Schema import is best-effort. Some JSON Schema constructs do not map - * exactly to Effect schema representations, and importing a schema previously - * emitted by `toJsonSchemaDocument` may produce an equivalent approximation - * rather than the original representation shape. - * - * This throws if a `$ref` cannot be resolved within the document's definitions. - * Circular `$ref`s are detected and cause an error. - * - * @see {@link Document} - * @see {@link toJsonSchemaDocument} - * @see {@link fromJsonSchemaMultiDocument} - * - * @category constructors - * @since 4.0.0 - */ -export function fromJsonSchemaDocument(document: JsonSchema.Document<"draft-2020-12">, options?: { - readonly onEnter?: ((js: JsonSchema.JsonSchema) => JsonSchema.JsonSchema) | undefined -}): Document { - const { references, representations: schemas } = fromJsonSchemaMultiDocument({ - dialect: document.dialect, - schemas: [document.schema], - definitions: document.definitions - }, options) - return { - representation: schemas[0], - references - } -} - -/** - * Parses a Draft 2020-12 JSON Schema multi-document into a - * {@link MultiDocument}. - * - * **When to use** - * - * Use when you need to import a Draft 2020-12 JSON Schema multi-document whose - * schemas share definitions. - * - * **Details** - * - * `options.onEnter` is an optional hook called on each JSON Schema node before - * processing. - * - * **Gotchas** - * - * JSON Schema import is best-effort. Some JSON Schema constructs do not map - * exactly to Effect schema representations, and importing schemas previously - * emitted by `toJsonSchemaMultiDocument` may produce equivalent approximations - * rather than the original representation shapes. - * - * This throws if a `$ref` cannot be resolved. - * - * @see {@link MultiDocument} - * @see {@link toJsonSchemaMultiDocument} - * @see {@link fromJsonSchemaDocument} - * - * @category constructors - * @since 4.0.0 - */ -export function fromJsonSchemaMultiDocument(document: JsonSchema.MultiDocument<"draft-2020-12">, options?: { - readonly onEnter?: ((js: JsonSchema.JsonSchema) => JsonSchema.JsonSchema) | undefined -}): MultiDocument { - let visited: Set - const references: Record = {} - - type Slot = { - // 0 = not started, 1 = building, 2 = done - state: 0 | 1 | 2 - value: Exclude | undefined - } - - const slots = new Map() - - function getSlot(identifier: string): Slot { - const existing = slots.get(identifier) - if (existing) return existing - - // Create the slot *before* resolving, so self-references can see it. - const slot: Slot = { - state: 0, - value: undefined - } - slots.set(identifier, slot) - return slot - } - - function resolveReference($ref: string): Exclude { - const definition = document.definitions[$ref] - if (definition === undefined) { - throw new Error(`Reference ${$ref} not found`) - } - - const slot = getSlot($ref) - - if (slot.state === 2) { - // Already built: return the built schema directly - return slot.value! - } - - if (slot.state === 1) { - // Circular: we're currently building this identifier. - throw new Error(`Circular reference detected: ${$ref}`) - } - - // First time: build it. - slot.state = 1 - const value = recur(definition) - - slot.value = value._tag === "Reference" ? resolveReference(value.$ref) : value - slot.state = 2 - return slot.value - } - - Object.entries(document.definitions).forEach(([identifier, definition]) => { - visited = new Set([identifier]) - references[identifier] = unknownToJson(recur(definition)) - }) - - visited = new Set() - const representations = Arr.map(document.schemas, (schema) => unknownToJson(recur(schema))) - return { - representations, - references - } - - function recur(u: unknown): Representation { - if (u === false) return never - if (!Predicate.isObject(u)) return unknown - - let js: JsonSchema.JsonSchema = options?.onEnter?.(u) ?? u - if (Array.isArray(js.type)) { - if (js.type.every(isType)) { - const { type, ...rest } = js - js = { - anyOf: type.map((type) => ({ type })), - ...rest - } - } else { - js = {} - } - } - - let out = on(js) - - const annotations = collectAnnotations(js) - if (annotations !== undefined) { - out = combine(out, { _tag: "Unknown", annotations }) - } - - if (Array.isArray(js.allOf)) { - out = js.allOf.reduce((acc, curr) => combine(acc, recur(curr)), out) - } - if (Array.isArray(js.anyOf)) { - out = combine({ _tag: "Union", types: js.anyOf.map((type) => recur(type)), mode: "anyOf" }, out) - } - if (Array.isArray(js.oneOf)) { - out = combine({ _tag: "Union", types: js.oneOf.map((type) => recur(type)), mode: "oneOf" }, out) - } - - return out - } - - function on(js: JsonSchema.JsonSchema): Representation { - if (typeof js.$ref === "string") { - const $ref = js.$ref.slice(2).split("/").at(-1) - if ($ref !== undefined) { - const reference: Reference = { _tag: "Reference", $ref: unescapeToken($ref) } - if (visited.has($ref)) { - return { _tag: "Suspend", thunk: reference, checks: [] } - } else { - return reference - } - } - } else if ("const" in js) { - if (isLiteralValue(js.const)) { - return { _tag: "Literal", literal: js.const } - } else if (js.const === null) { - return null_ - } - } else if (Array.isArray(js.enum)) { - const types: Array = [] - for (const e of js.enum) { - if (isLiteralValue(e)) { - types.push({ _tag: "Literal", literal: e }) - } else if (e === null) { - types.push(null_) - } else { - types.push(recur(e)) - } - } - if (types.length === 1) { - return types[0] - } else { - return { _tag: "Union", types, mode: "anyOf" } - } - } - - const type = isType(js.type) ? js.type : getType(js) - if (type !== undefined) { - switch (type) { - case "null": - return null_ - case "string": { - const checks = collectStringChecks(js) - if (checks.length > 0) { - return { ...string, checks } - } - return string - } - case "number": - return { - _tag: "Number", - checks: [{ _tag: "Filter", meta: { _tag: "isFinite" } }, ...collectNumberChecks(js)] - } - case "integer": - return { - _tag: "Number", - checks: [{ _tag: "Filter", meta: { _tag: "isInt" } }, ...collectNumberChecks(js)] - } - case "boolean": - return boolean - case "array": { - const minItems = typeof js.minItems === "number" ? js.minItems : 0 - - const elements: Array = (Array.isArray(js.prefixItems) ? js.prefixItems : []).map((e, i) => ({ - isOptional: i + 1 > minItems, - type: recur(e) - })) - - const rest: Array = js.items !== undefined ? - [recur(js.items)] - : js.prefixItems !== undefined && typeof js.maxItems === "number" - ? [] - : [unknown] - - return { _tag: "Arrays", elements, rest, checks: collectArraysChecks(js) } - } - case "object": { - return { - _tag: "Objects", - propertySignatures: collectProperties(js), - indexSignatures: collectIndexSignatures(js), - checks: collectObjectsChecks(js) - } - } - } - } - - return { _tag: "Unknown" } - } - - function collectObjectsChecks(js: JsonSchema.JsonSchema): Array> { - const checks: Array> = [] - if (typeof js.minProperties === "number") { - checks.push({ _tag: "Filter", meta: { _tag: "isMinProperties", minProperties: js.minProperties } }) - } - if (typeof js.maxProperties === "number") { - checks.push({ _tag: "Filter", meta: { _tag: "isMaxProperties", maxProperties: js.maxProperties } }) - } - if (js.propertyNames !== undefined) { - const propertyNames = recur(js.propertyNames) - checks.push({ _tag: "Filter", meta: { _tag: "isPropertyNames", propertyNames } }) - } - return checks - } - - function combine(a: Representation, b: Representation): Representation { - switch (a._tag) { - default: - return never - case "Reference": - return combine(resolveReference(a.$ref), b) - case "Never": - return a - case "Unknown": - switch (b._tag) { - case "Reference": - return combine(a, resolveReference(b.$ref)) - default: - return { ...b, ...combineAnnotations(a.annotations, b.annotations) } - } - case "Null": - switch (b._tag) { - case "Unknown": - case "Null": - return { ...a, ...combineAnnotations(a.annotations, b.annotations) } - case "Union": - return combine(b, a) - case "Reference": - return combine(a, resolveReference(b.$ref)) - default: - return never - } - case "String": - switch (b._tag) { - case "Unknown": - return { ...a, ...combineAnnotations(a.annotations, b.annotations) } - case "String": { - const checks = combineChecks(a.checks, b.checks, b.annotations) - return { - _tag: "String", - checks: checks ?? a.checks, - ...combineAnnotations(a.annotations, checks ? undefined : b.annotations) - } - } - case "Literal": - return typeof b.literal === "string" ? { ...b, ...combineAnnotations(a.annotations, b.annotations) } : never - case "Union": - return combine(b, a) - case "Reference": - return combine(a, resolveReference(b.$ref)) - default: - return never - } - case "Number": - switch (b._tag) { - case "Unknown": - return { ...a, ...combineAnnotations(a.annotations, b.annotations) } - case "Number": { - const checks = combineNumberChecks(a.checks, b.checks, b.annotations) - return { - _tag: "Number", - checks: checks ?? a.checks, - ...combineAnnotations(a.annotations, checks ? undefined : b.annotations) - } - } - case "Literal": - return typeof b.literal === "number" ? { ...b, ...combineAnnotations(a.annotations, b.annotations) } : never - case "Union": - return combine(b, a) - case "Reference": - return combine(a, resolveReference(b.$ref)) - default: - return never - } - case "Boolean": - switch (b._tag) { - case "Unknown": - return { ...a, ...combineAnnotations(a.annotations, b.annotations) } - case "Boolean": - return { _tag: "Boolean", ...combineAnnotations(a.annotations, b.annotations) } - case "Literal": - return typeof b.literal === "boolean" - ? { ...b, ...combineAnnotations(a.annotations, b.annotations) } - : never - case "Union": - return combine(b, a) - case "Reference": - return combine(a, resolveReference(b.$ref)) - default: - return never - } - case "Literal": - switch (b._tag) { - case "Unknown": - return { ...a, ...combineAnnotations(a.annotations, b.annotations) } - case "Literal": - return a.literal === b.literal - ? { ...a, ...combineAnnotations(a.annotations, b.annotations) } - : never - case "String": - return typeof a.literal === "string" ? { ...a, ...combineAnnotations(a.annotations, b.annotations) } : never - case "Number": - return typeof a.literal === "number" ? { ...a, ...combineAnnotations(a.annotations, b.annotations) } : never - case "Boolean": - return typeof a.literal === "boolean" - ? { ...a, ...combineAnnotations(a.annotations, b.annotations) } - : never - case "Union": - return combine(b, a) - case "Reference": - return combine(a, resolveReference(b.$ref)) - default: - return never - } - case "Arrays": - switch (b._tag) { - case "Unknown": - return { ...a, ...combineAnnotations(a.annotations, b.annotations) } - case "Arrays": { - const checks = combineArraysChecks(a.checks, b.checks, b.annotations) - return { - _tag: "Arrays", - elements: combineElements(a.elements, b.elements), - rest: combineRest(a.rest, b.rest), - checks: checks ?? a.checks, - ...combineAnnotations(a.annotations, checks ? undefined : b.annotations) - } - } - case "Union": - return combine(b, a) - case "Reference": - return combine(a, resolveReference(b.$ref)) - default: - return never - } - case "Objects": - switch (b._tag) { - case "Unknown": - return { ...a, ...combineAnnotations(a.annotations, b.annotations) } - case "Objects": { - const checks = combineChecks(a.checks, b.checks, b.annotations) - return { - _tag: "Objects", - propertySignatures: combinePropertySignatures(a.propertySignatures, b.propertySignatures), - indexSignatures: combineIndexSignatures(a.indexSignatures, b.indexSignatures), - checks: checks ?? a.checks, - ...combineAnnotations(a.annotations, checks ? undefined : b.annotations) - } - } - case "Union": - return combine(b, a) - case "Reference": - return combine(a, resolveReference(b.$ref)) - default: - return never - } - case "Union": { - switch (b._tag) { - case "Unknown": - return { ...a, ...combineAnnotations(a.annotations, b.annotations) } - default: { - const types = a.types.map((s) => combine(s, b)).filter((s) => s !== never) - if (types.length === 0) return never - return { - _tag: "Union", - types, - mode: a.mode, - ...makeAnnotations(a.annotations) - } - } - } - } - } - } - - function collectProperties(js: JsonSchema.JsonSchema): Array { - const properties: Record = Predicate.isObject(js.properties) ? js.properties : {} - const required = Array.isArray(js.required) ? js.required : [] - required.forEach((key) => { - if (!Object.hasOwn(properties, key)) { - properties[key] = {} - } - }) - return Object.entries(properties).map(([key, v]) => ({ - name: key, - type: recur(v), - isOptional: !required.includes(key), - isMutable: false - })) - } - - function collectIndexSignatures(js: JsonSchema.JsonSchema): Array { - const out: Array = [] - - if (Predicate.isObject(js.patternProperties)) { - for (const [pattern, value] of Object.entries(js.patternProperties)) { - out.push({ parameter: recur({ pattern }), type: recur(value) }) - } - } - - if (js.additionalProperties === undefined || js.additionalProperties === true) { - out.push({ parameter: string, type: unknown }) - } else if (Predicate.isObject(js.additionalProperties)) { - out.push({ parameter: string, type: recur(js.additionalProperties) }) - } - - return out - } - - function combineElements(a: ReadonlyArray, b: ReadonlyArray): Array { - const len = Math.max(a.length, b.length) - let out: Array = [] - for (let i = 0; i < len; i++) { - out.push({ - isOptional: a[i].isOptional && b[i].isOptional, - type: combine(a[i].type, b[i].type) - }) - } - if (a.length > len) { - out = [...out, ...a.slice(len)] - } else if (b.length > len) { - out = [...out, ...b.slice(len)] - } - return out - } - - function combineRest(a: ReadonlyArray, b: ReadonlyArray): Array { - const len = Math.max(a.length, b.length) - let out: Array = [] - for (let i = 0; i < len; i++) { - out.push(combine(a[i], b[i])) - } - if (a.length > len) { - out = [...out, ...a.slice(len)] - } else if (b.length > len) { - out = [...out, ...b.slice(len)] - } - return out - } - - function combinePropertySignatures( - a: ReadonlyArray, - b: ReadonlyArray - ): Array { - const propertySignatures: Array = [] - const thatPropertiesMap: Record = {} - for (const p of b) { - thatPropertiesMap[p.name] = p - } - const keys = new Set() - for (const p of a) { - keys.add(p.name) - const thatp = thatPropertiesMap[p.name] - if (thatp) { - propertySignatures.push( - { - name: p.name, - type: combine(p.type, thatp.type), - isOptional: p.isOptional && thatp.isOptional, - isMutable: p.isMutable - } - ) - } else { - propertySignatures.push(p) - } - } - for (const p of b) { - if (!keys.has(p.name)) propertySignatures.push(p) - } - return propertySignatures - } - - function combineIndexSignatures( - a: ReadonlyArray, - b: ReadonlyArray - ): Array { - if (a.length === 0 || b.length === 0) return [] - const out: Array = [...a] - for (const is of b) { - if (is.parameter === string) { - const i = a.findIndex((is) => is.parameter === string) - if (i !== -1) { - out[i] = { parameter: string, type: combine(a[i].type, is.type) } - } else { - out.push(is) - } - } else { - out.push(is) - } - } - return out - } - - function unknownToJson(representation: Representation): Representation { - switch (representation._tag) { - case "Unknown": - return representation.annotations === undefined ? - json : - { - ...json, - annotations: { - ...json.annotations, - ...representation.annotations - } - } - case "Suspend": { - const thunk = unknownToJson(representation.thunk) - return thunk === representation.thunk ? representation : { ...representation, thunk } - } - case "String": { - if (representation.contentSchema === undefined) return representation - const contentSchema = unknownToJson(representation.contentSchema) - return contentSchema === representation.contentSchema ? representation : { ...representation, contentSchema } - } - case "Arrays": { - const elements = SchemaAST.mapOrSame(representation.elements, (element) => { - const type = unknownToJson(element.type) - return type === element.type ? element : { ...element, type } - }) - const rest = SchemaAST.mapOrSame(representation.rest, unknownToJson) - return elements === representation.elements && rest === representation.rest ? - representation : - { ...representation, elements, rest } - } - case "Objects": { - const propertySignatures = SchemaAST.mapOrSame(representation.propertySignatures, (propertySignature) => { - const type = unknownToJson(propertySignature.type) - return type === propertySignature.type ? propertySignature : { ...propertySignature, type } - }) - const indexSignatures = SchemaAST.mapOrSame(representation.indexSignatures, (indexSignature) => { - const type = unknownToJson(indexSignature.type) - return type === indexSignature.type ? indexSignature : { ...indexSignature, type } - }) - return propertySignatures === representation.propertySignatures && - indexSignatures === representation.indexSignatures ? - representation : - { ...representation, propertySignatures, indexSignatures } - } - case "Union": { - const types = SchemaAST.mapOrSame(representation.types, unknownToJson) - return types === representation.types ? representation : { ...representation, types } - } - default: - return representation - } - } -} - -function asChecks( - checks: ReadonlyArray>, - annotations: Schema.Annotations.Annotations | undefined -): ReadonlyArray> | undefined { - if (Arr.isReadonlyArrayNonEmpty(checks)) { - if (annotations !== undefined) { - if (checks.length === 1) { - const check = checks[0] - if (check.annotations === undefined) { - return [{ ...check, annotations }] - } else { - return [{ _tag: "FilterGroup", checks, annotations }] - } - } else { - return [{ _tag: "FilterGroup", checks, annotations }] - } - } - return checks - } -} - -function combineChecks( - a: ReadonlyArray>, - b: ReadonlyArray>, - annotations: Schema.Annotations.Annotations | undefined -): Array> | undefined { - const checks = asChecks(b, annotations) - if (checks) { - return [...a, ...checks] - } -} - -function combineNumberChecks( - a: ReadonlyArray>, - b: ReadonlyArray>, - annotations: Schema.Annotations.Annotations | undefined -): Array> | undefined { - if (a.some((c) => c._tag === "Filter" && c.meta._tag === "isFinite")) { - b = b.filter((c) => c._tag !== "Filter" || c.meta._tag !== "isFinite") - } - if (a.some((c) => c._tag === "Filter" && c.meta._tag === "isInt")) { - b = b.filter((c) => c._tag !== "Filter" || c.meta._tag !== "isInt") - } - return combineChecks(a, b, annotations) -} - -function combineArraysChecks( - a: ReadonlyArray>, - b: ReadonlyArray>, - annotations: Schema.Annotations.Annotations | undefined -): Array> | undefined { - if (a.some((c) => c._tag === "Filter" && c.meta._tag === "isUnique")) { - b = b.filter((c) => c._tag !== "Filter" || c.meta._tag !== "isUnique") - } - return combineChecks(a, b, annotations) -} - -function makeAnnotations( - annotations: Schema.Annotations.Annotations | undefined -): { annotations: Schema.Annotations.Annotations } | undefined { - return annotations ? { annotations } : undefined -} - -function combineAnnotations( - a: Schema.Annotations.Annotations | undefined, - b: Schema.Annotations.Annotations | undefined -): { annotations: Schema.Annotations.Annotations } | undefined { - if (a === undefined) return makeAnnotations(b) - if (b === undefined) return makeAnnotations(a) - return { annotations: { ...a, ...b } } // TODO: better merge -} - -function collectStringChecks(js: JsonSchema.JsonSchema): Array> { - const checks: Array> = [] - if (typeof js.minLength === "number") { - checks.push({ _tag: "Filter", meta: { _tag: "isMinLength", minLength: js.minLength } }) - } - if (typeof js.maxLength === "number") { - checks.push({ _tag: "Filter", meta: { _tag: "isMaxLength", maxLength: js.maxLength } }) - } - if (typeof js.pattern === "string") { - checks.push({ _tag: "Filter", meta: { _tag: "isPattern", regExp: new RegExp(js.pattern) } }) - } - return checks -} - -function collectNumberChecks(js: JsonSchema.JsonSchema): Array> { - const checks: Array> = [] - if (typeof js.minimum === "number") { - checks.push({ _tag: "Filter", meta: { _tag: "isGreaterThanOrEqualTo", minimum: js.minimum } }) - } - if (typeof js.maximum === "number") { - checks.push({ _tag: "Filter", meta: { _tag: "isLessThanOrEqualTo", maximum: js.maximum } }) - } - if (typeof js.exclusiveMinimum === "number") { - checks.push({ _tag: "Filter", meta: { _tag: "isGreaterThan", exclusiveMinimum: js.exclusiveMinimum } }) - } - if (typeof js.exclusiveMaximum === "number") { - checks.push({ _tag: "Filter", meta: { _tag: "isLessThan", exclusiveMaximum: js.exclusiveMaximum } }) - } - if (typeof js.multipleOf === "number") { - checks.push({ _tag: "Filter", meta: { _tag: "isMultipleOf", divisor: js.multipleOf } }) - } - return checks -} - -function collectArraysChecks(js: JsonSchema.JsonSchema): Array> { - const checks: Array> = [] - if (js.prefixItems === undefined) { - if (typeof js.minItems === "number") { - checks.push({ _tag: "Filter", meta: { _tag: "isMinLength", minLength: js.minItems } }) - } - if (typeof js.maxItems === "number") { - checks.push({ _tag: "Filter", meta: { _tag: "isMaxLength", maxLength: js.maxItems } }) - } - } - if (typeof js.uniqueItems === "boolean") { - checks.push({ _tag: "Filter", meta: { _tag: "isUnique" } }) - } - return checks -} - -const unknown: Unknown = { _tag: "Unknown" } -const json: Declaration = { - _tag: "Declaration", - annotations: { - expected: "JSON value", - generation: { - Type: "Schema.Json", - runtime: "Schema.Json" - }, - typeConstructor: { - _tag: "effect/Json" - } - }, - checks: [], - encodedSchema: unknown, - typeParameters: [] -} -const never: Never = { _tag: "Never" } -const null_: Null = { _tag: "Null" } -const string: String = { _tag: "String", checks: [] } -const boolean: Boolean = { _tag: "Boolean" } - -function collectAnnotations( - schema: JsonSchema.JsonSchema -): Schema.Annotations.Annotations | undefined { - const as: Record = {} - - if (typeof schema.title === "string") as.title = schema.title - if (typeof schema.description === "string") as.description = schema.description - if (schema.default !== undefined) as.default = schema.default - if (Array.isArray(schema.examples)) as.examples = schema.examples - if (typeof schema.readOnly === "boolean") as.readOnly = schema.readOnly - if (typeof schema.writeOnly === "boolean") as.writeOnly = schema.writeOnly - if (typeof schema.format === "string") as.format = schema.format - if (typeof schema.contentEncoding === "string") as.contentEncoding = schema.contentEncoding - if (typeof schema.contentMediaType === "string") as.contentMediaType = schema.contentMediaType - - return Rec.isEmptyRecord(as) ? undefined : as -} - -function isLiteralValue(value: unknown): value is SchemaAST.LiteralValue { - return typeof value === "string" || typeof value === "number" || typeof value === "boolean" -} - -const stringKeys = ["minLength", "maxLength", "pattern", "format", "contentMediaType", "contentSchema"] -const numberKeys = ["minimum", "maximum", "exclusiveMinimum", "exclusiveMaximum", "multipleOf"] -const objectKeys = [ - "properties", - "required", - "additionalProperties", - "patternProperties", - "propertyNames", - "minProperties", - "maxProperties" -] -const arrayKeys = ["items", "prefixItems", "additionalItems", "minItems", "maxItems", "uniqueItems"] - -function getType(js: JsonSchema.JsonSchema): JsonSchema.Type | undefined { - if (stringKeys.some((key) => js[key] !== undefined)) { - return "string" - } - if (numberKeys.some((key) => js[key] !== undefined)) { - return "number" - } - if (objectKeys.some((key) => js[key] !== undefined)) { - return "object" - } - if (arrayKeys.some((key) => js[key] !== undefined)) { - return "array" - } -} - -const types = ["null", "string", "number", "integer", "boolean", "object", "array"] - -function isType(type: unknown): type is JsonSchema.Type { - return typeof type === "string" && types.includes(type) -} - -/** @internal */ -export type TopologicalSort = { - /** - * The definitions that are not recursive. - * The definitions that depends on other definitions are placed after the definitions they depend on - */ - readonly nonRecursives: ReadonlyArray<{ - readonly $ref: string - readonly representation: Representation - }> - /** - * The recursive definitions (with no particular order). - */ - readonly recursives: { - readonly [$ref: string]: Representation - } -} - -/** @internal */ -export function topologicalSort(references: References): TopologicalSort { - const identifiers = Object.keys(references) - const identifierSet = new Set(identifiers) - - const collectRefs = (root: Representation): Set => { - const refs = new Set() - const visited = new WeakSet() - const stack: Array = [root] - - while (stack.length > 0) { - const r = stack.pop()! - if (visited.has(r)) continue - visited.add(r) - - if (r._tag === "Reference") { - if (identifierSet.has(r.$ref)) { - refs.add(r.$ref) - } - } - - // Push nested Representation schemas onto the stack - switch (r._tag) { - case "Declaration": - for (const typeParam of r.typeParameters) stack.push(typeParam) - stack.push(r.encodedSchema) - break - case "Suspend": - stack.push(r.thunk) - break - case "String": - if (r.contentSchema !== undefined) stack.push(r.contentSchema) - break - case "TemplateLiteral": - for (const part of r.parts) stack.push(part) - break - case "Arrays": - for (const element of r.elements) stack.push(element.type) - for (const rest of r.rest) stack.push(rest) - break - case "Objects": - for (const propertySignature of r.propertySignatures) stack.push(propertySignature.type) - for (const indexSignature of r.indexSignatures) { - stack.push(indexSignature.parameter) - stack.push(indexSignature.type) - } - break - case "Union": - for (const type of r.types) stack.push(type) - break - } - } - - return refs - } - - // identifier -> internal identifiers it depends on - const dependencies = new Map>( - identifiers.map((id) => [id, collectRefs(references[id])]) - ) - - // Mark only nodes that are part of cycles - const recursive = new Set() - const state = new Map() // 0 = new, 1 = visiting, 2 = done - const stack: Array = [] - const indexInStack = new Map() - - const dfs = (id: string): void => { - const s = state.get(id) ?? 0 - if (s === 1) { - const start = indexInStack.get(id) - if (start !== undefined) { - for (let i = start; i < stack.length; i++) { - recursive.add(stack[i]) - } - } - return - } - if (s === 2) return - - state.set(id, 1) - indexInStack.set(id, stack.length) - stack.push(id) - - for (const dep of dependencies.get(id) ?? []) { - dfs(dep) - } - - stack.pop() - indexInStack.delete(id) - state.set(id, 2) - } - - for (const id of identifiers) dfs(id) - - // Topologically sort the non-recursive nodes (ignoring edges to recursive nodes) - const inDegree = new Map() - const dependents = new Map>() // dep -> nodes that depend on it - - for (const id of identifiers) { - if (!recursive.has(id)) { - inDegree.set(id, 0) - dependents.set(id, new Set()) - } - } - - for (const [id, deps] of dependencies) { - if (recursive.has(id)) continue - for (const dep of deps) { - if (recursive.has(dep)) continue - inDegree.set(id, (inDegree.get(id) ?? 0) + 1) - dependents.get(dep)?.add(id) - } - } - - const queue: Array = [] - for (const [id, deg] of inDegree) { - if (deg === 0) queue.push(id) - } - - const nonRecursives: Array<{ readonly $ref: string; readonly representation: Representation }> = [] - for (let i = 0; i < queue.length; i++) { - const $ref = queue[i] - nonRecursives.push({ $ref, representation: references[$ref] }) - - for (const next of dependents.get($ref) ?? []) { - const deg = (inDegree.get(next) ?? 0) - 1 - inDegree.set(next, deg) - if (deg === 0) queue.push(next) - } - } - - const recursives: Record = {} - for (const $ref of recursive) { - recursives[$ref] = references[$ref] - } - - return { nonRecursives, recursives } +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 index bf87deb07f..e90c1a653c 100644 --- a/repos/effect/packages/effect/src/SchemaTransformation.ts +++ b/repos/effect/packages/effect/src/SchemaTransformation.ts @@ -52,13 +52,15 @@ import * as SchemaIssue from "./SchemaIssue.ts" * * **Example** (Creating a middleware that falls back on decode failure) * - * ```ts - * import { Effect, Option, SchemaTransformation } from "effect" + * ```ts import.meta.vitest + * import { Effect, Option, SchemaIssue, SchemaTransformation } from "effect" * - * const fallback = new SchemaTransformation.Middleware( + * 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 @@ -121,14 +123,13 @@ const TypeId = "~effect/SchemaTransformation/Transformation" * * **Example** (Composing two transformations) * - * ```ts + * ```ts import.meta.vitest * import { SchemaTransformation } from "effect" * * const trimAndLower = SchemaTransformation.trim().compose( * SchemaTransformation.toLowerCase() * ) - * // decode: trim then lowercase - * // encode: passthrough (both directions) + * trimAndLower._tag // => "Transformation" * ``` * * @see {@link make} — construct from `{ decode, encode }` getters @@ -178,14 +179,11 @@ export class Transformation { * * **Example** (Checking a value) * - * ```ts + * ```ts import.meta.vitest * import { SchemaTransformation } from "effect" * - * SchemaTransformation.isTransformation(SchemaTransformation.trim()) - * // true - * - * SchemaTransformation.isTransformation({ decode: null, encode: null }) - * // false + * SchemaTransformation.isTransformation(SchemaTransformation.trim()) // => true + * SchemaTransformation.isTransformation({ decode: null, encode: null }) // => false * ``` * * @see {@link Transformation} @@ -195,7 +193,7 @@ export class Transformation { * @since 4.0.0 */ export function isTransformation(u: unknown): u is Transformation { - return Predicate.hasProperty(u, TypeId) + return Predicate.hasProperty(u, TypeId) && u[TypeId] === TypeId } /** @@ -214,13 +212,14 @@ export function isTransformation(u: unknown): u is Transformation((s) => Number(s)), * encode: SchemaGetter.transform((n) => String(n)) * }) + * t._tag // => "Transformation" * ``` * * @see {@link transform} — simpler constructor from pure functions @@ -257,30 +256,31 @@ export const make = (options: { * * **Example** (Parsing a date string that can fail) * - * ```ts + * ```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) => { + * decode: (s, options) => { * const d = new Date(s) * return isNaN(d.getTime()) - * ? Effect.fail(new SchemaIssue.InvalidValue(Option.some(s), { message: "Invalid date" })) + * ? 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 constructors + * @category transforming * @since 3.10.0 */ export function transformOrFail(options: { @@ -310,7 +310,7 @@ export function transformOrFail(options: { * * **Example** (Converting between cents and dollars) * - * ```ts + * ```ts import.meta.vitest * import { Schema, SchemaTransformation } from "effect" * * const CentsFromDollars = Schema.Number.pipe( @@ -322,13 +322,14 @@ export function transformOrFail(options: { * }) * ) * ) + * 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 constructors + * @category transforming * @since 3.10.0 */ export function transform(options: { @@ -360,7 +361,7 @@ export function transform(options: { * * **Example** (Converting an optional key to Option) * - * ```ts + * ```ts import.meta.vitest * import { Option, Schema, SchemaTransformation } from "effect" * * const schema = Schema.Struct({ @@ -374,13 +375,14 @@ export function transform(options: { * ) * ) * }) + * 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 constructors + * @category transforming * @since 4.0.0 */ export function transformOptional(options: { @@ -410,19 +412,20 @@ export function transformOptional(options: { * * **Example** (Trimming on decode) * - * ```ts + * ```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 String transformations + * @category transforming * @since 4.0.0 */ export function trim(): Transformation { @@ -449,18 +452,19 @@ export function trim(): Transformation { * * **Example** (Converting snake case to camel case) * - * ```ts + * ```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 String transformations + * @category transforming * @since 4.0.0 */ export function snakeToCamel(): Transformation { @@ -486,18 +490,19 @@ export function snakeToCamel(): Transformation { * * **Example** (Lowercasing on decode) * - * ```ts + * ```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 String transformations + * @category transforming * @since 4.0.0 */ export function toLowerCase(): Transformation { @@ -523,18 +528,19 @@ export function toLowerCase(): Transformation { * * **Example** (Uppercasing on decode) * - * ```ts + * ```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 String transformations + * @category transforming * @since 4.0.0 */ export function toUpperCase(): Transformation { @@ -560,18 +566,19 @@ export function toUpperCase(): Transformation { * * **Example** (Capitalizing on decode) * - * ```ts + * ```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 String transformations + * @category transforming * @since 4.0.0 */ export function capitalize(): Transformation { @@ -597,18 +604,19 @@ export function capitalize(): Transformation { * * **Example** (Uncapitalizing on decode) * - * ```ts + * ```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 String transformations + * @category transforming * @since 4.0.0 */ export function uncapitalize(): Transformation { @@ -636,7 +644,7 @@ export function uncapitalize(): Transformation { * * **Example** (Parsing key-value pairs) * - * ```ts + * ```ts import.meta.vitest * import { Schema, SchemaTransformation } from "effect" * * const Config = Schema.String.pipe( @@ -645,13 +653,13 @@ export function uncapitalize(): Transformation { * SchemaTransformation.splitKeyValue({ separator: ";", keyValueSeparator: ":" }) * ) * ) - * // "host:localhost;port:3000" → { host: "localhost", port: "3000" } + * Schema.decodeSync(Config)("host:localhost;port:3000") // => { host: "localhost", port: "3000" } * ``` * * @see {@link trim} * @see {@link snakeToCamel} * - * @category String transformations + * @category transforming * @since 4.0.0 */ export function splitKeyValue(options?: { @@ -687,12 +695,13 @@ const passthrough_ = new Transformation( * * **Example** (Chaining schemas with no conversion) * - * ```ts + * ```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} @@ -724,10 +733,11 @@ export function passthrough(): Transformation { * * **Example** (Passing through supertypes) * - * ```ts + * ```ts import.meta.vitest * import { SchemaTransformation } from "effect" * - * const t = SchemaTransformation.passthroughSupertype<"a" | "b", string>() + * const t: SchemaTransformation.Transformation<"a" | "b", string> = + * SchemaTransformation.passthroughSupertype<"a" | "b", string>() * ``` * * @see {@link passthrough} @@ -757,10 +767,11 @@ export function passthroughSupertype(): Transformation { * * **Example** (Passing through subtypes) * - * ```ts + * ```ts import.meta.vitest * import { SchemaTransformation } from "effect" * - * const t = SchemaTransformation.passthroughSubtype() + * const t: SchemaTransformation.Transformation = + * SchemaTransformation.passthroughSubtype() * ``` * * @see {@link passthrough} @@ -792,18 +803,19 @@ export function passthroughSubtype(): Transformation { * * **Example** (Converting a string to a number) * - * ```ts + * ```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 Coercions + * @category converting * @since 4.0.0 */ export const numberFromString = new Transformation( @@ -828,18 +840,19 @@ export const numberFromString = new Transformation( * * **Example** (Converting a string to a BigInt) * - * ```ts + * ```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 Coercions + * @category converting * @since 4.0.0 */ export const bigintFromString = new Transformation( @@ -852,8 +865,8 @@ export const bigintFromString = new Transformation( * * **When to use** * - * Use when you need a schema transformation to parse ISO 8601 date strings from - * APIs or user input. + * Use when you need a schema transformation to parse date strings from APIs or + * user input. * * **Details** * @@ -863,18 +876,19 @@ export const bigintFromString = new Transformation( * * **Example** (Converting a string to a Date) * - * ```ts + * ```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 numberFromString} + * @see {@link dateFromMillis} * @see {@link dateTimeUtcFromString} * - * @category Coercions + * @category converting * @since 4.0.0 */ export const dateFromString: Transformation = new Transformation( @@ -882,6 +896,47 @@ export const dateFromString: Transformation = new Trans 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`. @@ -900,12 +955,13 @@ export const dateFromString: Transformation = new Trans * * **Example** (Converting a string to a Duration) * - * ```ts + * ```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} @@ -918,10 +974,16 @@ export const durationFromString: Transformation = tra Duration.Duration, string >({ - decode: (s) => + decode: (s, options) => Option.match(Duration.fromInput(s as Duration.Input), { onNone: () => - Effect.fail(new SchemaIssue.InvalidValue(Option.some(s), { message: `Invalid Duration string: ${s}` })), + Effect.fail( + new SchemaIssue.InvalidValue( + { expected: "a valid Duration string" }, + s, + options + ) + ), onSome: Effect.succeed }), encode: (duration) => Effect.succeed(globalThis.String(duration)) @@ -944,12 +1006,13 @@ export const durationFromString: Transformation = tra * * **Example** (Converting nanoseconds to a Duration) * - * ```ts + * ```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} @@ -959,11 +1022,15 @@ export const durationFromString: Transformation = tra */ export const durationFromNanos: Transformation = transformOrFail({ decode: (i) => Effect.succeed(Duration.nanos(i)), - encode: (a) => + encode: (a, options) => Option.match(Duration.toNanos(a), { onNone: () => Effect.fail( - new SchemaIssue.InvalidValue(Option.some(a), { message: `Unable to encode ${a} into a bigint` }) + new SchemaIssue.InvalidValue( + { expected: "a Duration representable as a bigint" }, + a, + options + ) ), onSome: (nanos) => Effect.succeed(nanos) }) @@ -985,12 +1052,13 @@ export const durationFromNanos: Transformation = tran * * **Example** (Converting milliseconds to a Duration) * - * ```ts + * ```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} @@ -1100,8 +1168,8 @@ export const defectFromJson = (options?: ErrorOptions) => * * **Example** (Converting nullable values to an Option) * - * ```ts - * import { Schema, SchemaTransformation } from "effect" + * ```ts import.meta.vitest + * import { Option, Schema, SchemaTransformation } from "effect" * * const schema = Schema.NullOr(Schema.String).pipe( * Schema.decodeTo( @@ -1109,6 +1177,7 @@ export const defectFromJson = (options?: ErrorOptions) => * SchemaTransformation.optionFromNullOr() * ) * ) + * Schema.decodeSync(schema)(null) // => Option.none() * ``` * * @see {@link optionFromNullishOr} @@ -1140,8 +1209,8 @@ export function optionFromNullOr(): Transformation, T | null * * **Example** (Converting undefined-or values to an Option) * - * ```ts - * import { Schema, SchemaTransformation } from "effect" + * ```ts import.meta.vitest + * import { Option, Schema, SchemaTransformation } from "effect" * * const schema = Schema.UndefinedOr(Schema.String).pipe( * Schema.decodeTo( @@ -1149,6 +1218,7 @@ export function optionFromNullOr(): Transformation, T | null * SchemaTransformation.optionFromUndefinedOr() * ) * ) + * Schema.decodeSync(schema)(undefined) // => Option.none() * ``` * * @see {@link optionFromOptionalKey} @@ -1183,8 +1253,8 @@ export function optionFromUndefinedOr(): Transformation, T | * * **Example** (Converting nullish values to an Option and encoding None as null) * - * ```ts - * import { Schema, SchemaTransformation } from "effect" + * ```ts import.meta.vitest + * import { Option, Schema, SchemaTransformation } from "effect" * * const schema = Schema.NullishOr(Schema.String).pipe( * Schema.decodeTo( @@ -1192,6 +1262,7 @@ export function optionFromUndefinedOr(): Transformation, T | * SchemaTransformation.optionFromNullishOr({ onNoneEncoding: null }) * ) * ) + * Schema.encodeSync(schema)(Option.none()) // => null * ``` * * @see {@link optionFromNullOr} @@ -1229,8 +1300,8 @@ export function optionFromNullishOr( * * **Example** (Converting an optional key to an Option) * - * ```ts - * import { Schema, SchemaTransformation } from "effect" + * ```ts import.meta.vitest + * import { Option, Schema, SchemaTransformation } from "effect" * * const schema = Schema.Struct({ * name: Schema.optionalKey(Schema.String).pipe( @@ -1240,6 +1311,7 @@ export function optionFromNullishOr( * ) * ) * }) + * Schema.decodeSync(schema)({}).name // => Option.none() * ``` * * @see {@link optionFromOptional} @@ -1274,8 +1346,8 @@ export function optionFromOptionalKey(): Transformation, T> * * **Example** (Converting an optional value to an Option) * - * ```ts - * import { Schema, SchemaTransformation } from "effect" + * ```ts import.meta.vitest + * import { Option, Schema, SchemaTransformation } from "effect" * * const schema = Schema.Struct({ * age: Schema.optional(Schema.Number).pipe( @@ -1285,6 +1357,7 @@ export function optionFromOptionalKey(): Transformation, T> * ) * ) * }) + * Schema.decodeSync(schema)({ age: undefined }).age // => Option.none() * ``` * * @see {@link optionFromOptionalKey} @@ -1317,12 +1390,13 @@ export function optionFromOptional(): Transformation, T | un * * **Example** (Converting a string to a URL) * - * ```ts + * ```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} @@ -1332,10 +1406,16 @@ export function optionFromOptional(): Transformation, T | un * @since 4.0.0 */ export const urlFromString: Transformation = transformOrFail({ - decode: (s) => + decode: (s, options) => URL.canParse(s) ? Effect.succeed(new URL(s)) - : Effect.fail(new SchemaIssue.InvalidValue(Option.some(s), { message: `Invalid URL string: ${s}` })), + : Effect.fail( + new SchemaIssue.InvalidValue( + { expected: "a valid URL string" }, + s, + options + ) + ), encode: (url) => Effect.succeed(url.href) }) @@ -1361,10 +1441,16 @@ export const bigDecimalFromString: Transformation BigDecimal.BigDecimal, string >({ - decode: (s) => { + decode: (s, options) => { const result = BigDecimal.fromString(s) return Option.isNone(result) - ? Effect.fail(new SchemaIssue.InvalidValue(Option.some(s), { message: `Invalid BigDecimal string: ${s}` })) + ? Effect.fail( + new SchemaIssue.InvalidValue( + { expected: "a valid BigDecimal string" }, + s, + options + ) + ) : Effect.succeed(result.value) }, encode: (bd) => Effect.succeed(BigDecimal.format(bd)) @@ -1386,12 +1472,13 @@ export const bigDecimalFromString: Transformation * * **Example** (Converting Base64 to a Uint8Array) * - * ```ts + * ```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} @@ -1421,12 +1508,13 @@ export const uint8ArrayFromBase64String: Transformation = new Transf * * **Example** (Converting Base64Url to a string) * - * ```ts + * ```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} @@ -1489,12 +1578,13 @@ export const stringFromBase64UrlString: Transformation = new Tra * * **Example** (Converting hex to a string) * - * ```ts + * ```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} @@ -1525,12 +1615,13 @@ export const stringFromHexString: Transformation = new Transform * * **Example** (Defining a URI component schema) * - * ```ts + * ```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} @@ -1556,17 +1647,20 @@ export const stringFromUriComponent: Transformation = new Transf * * **Details** * - * Decode fails with `InvalidValue` for invalid JSON, and encode can fail with - * `InvalidValue` when `JSON.stringify` cannot serialize the value. + * 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 + * ```ts import.meta.vitest * import { Schema, SchemaTransformation } from "effect" * * const schema = Schema.String.pipe( - * Schema.decodeTo(Schema.Unknown, SchemaTransformation.fromJsonString) + * Schema.decodeTo(Schema.Unknown, SchemaTransformation.fromJsonString()) * ) + * Schema.decodeSync(schema)("{\"ok\":true}") // => { ok: true } * ``` * * @see {@link uint8ArrayFromBase64String} @@ -1575,10 +1669,16 @@ export const stringFromUriComponent: Transformation = new Transf * @category decoding * @since 4.0.0 */ -export const fromJsonString = new Transformation( - SchemaGetter.parseJson(), - SchemaGetter.stringifyJson() -) +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 @@ -1597,12 +1697,15 @@ export const fromJsonString = new Transformation( * * **Example** (Decoding FormData) * - * ```ts + * ```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} @@ -1633,12 +1736,13 @@ export const fromFormData = new Transformation( * * **Example** (Decoding URLSearchParams) * - * ```ts + * ```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} @@ -1703,10 +1807,16 @@ export const timeZoneNamedFromString: Transformation({ - decode: (s) => { + decode: (s, options) => { return Option.match(DateTime.zoneMakeNamed(s), { onNone: () => - Effect.fail(new SchemaIssue.InvalidValue(Option.some(s), { message: `Invalid IANA time zone: ${s}` })), + Effect.fail( + new SchemaIssue.InvalidValue( + { expected: "a valid IANA time zone" }, + s, + options + ) + ), onSome: Effect.succeed }) }, @@ -1738,9 +1848,16 @@ export const timeZoneFromString: Transformation = tra DateTime.TimeZone, string >({ - decode: (s) => { + decode: (s, options) => { return Option.match(DateTime.zoneFromString(s), { - onNone: () => Effect.fail(new SchemaIssue.InvalidValue(Option.some(s), { message: `Invalid time zone: ${s}` })), + onNone: () => + Effect.fail( + new SchemaIssue.InvalidValue( + { expected: "a valid time zone" }, + s, + options + ) + ), onSome: Effect.succeed }) }, @@ -1762,8 +1879,8 @@ export const timeZoneFromString: Transformation = tra * UTC, and fails with `InvalidValue` when parsing fails. Encode uses * `DateTime.formatIso`. * - * @see {@link dateTimeZonedFromString} for ISO strings that should preserve zoned date-time information * @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 @@ -1772,10 +1889,16 @@ export const dateTimeUtcFromString: Transformation = trans DateTime.Utc, string >({ - decode: (s) => { + decode: (s, options) => { return Option.match(DateTime.make(s), { onNone: () => - Effect.fail(new SchemaIssue.InvalidValue(Option.some(s), { message: `Invalid UTC DateTime string: ${s}` })), + Effect.fail( + new SchemaIssue.InvalidValue( + { expected: "a valid UTC DateTime string" }, + s, + options + ) + ), onSome: (result) => Effect.succeed(DateTime.toUtc(result)) }) }, @@ -1806,10 +1929,16 @@ export const dateTimeZonedFromString: Transformation = t DateTime.Zoned, string >({ - decode: (s) => { + decode: (s, options) => { return Option.match(DateTime.makeZonedFromString(s), { onNone: () => - Effect.fail(new SchemaIssue.InvalidValue(Option.some(s), { message: `Invalid Zoned DateTime string: ${s}` })), + Effect.fail( + new SchemaIssue.InvalidValue( + { expected: "a valid Zoned DateTime string" }, + s, + options + ) + ), onSome: Effect.succeed }) }, diff --git a/repos/effect/packages/effect/src/SchemaUtils.ts b/repos/effect/packages/effect/src/SchemaUtils.ts deleted file mode 100644 index 1746d087aa..0000000000 --- a/repos/effect/packages/effect/src/SchemaUtils.ts +++ /dev/null @@ -1,51 +0,0 @@ -/** - * Small helpers for schema patterns that are too specialized for the main - * `Schema` module. The current helper builds a schema for an existing class: - * the encoded input is checked with a struct schema, decoding calls the class - * constructor with the decoded properties, and the final value remains an - * instance of that class. - * - * @since 4.0.0 - */ -import { identity } from "./Function.ts" -import * as Schema from "./Schema.ts" -import * as SchemaTransformation from "./SchemaTransformation.ts" - -/** - * Builds an experimental schema for instances of a native class using a struct - * schema as the encoded representation. - * - * **When to use** - * - * Use when you need a schema for an existing native class while keeping a - * `Struct` schema as its encoded representation. - * - * **Details** - * - * Decoding constructs `new constructor(props)` from the encoded fields. - * Encoding uses the instance as the encoded shape, so the class should expose - * properties compatible with the provided encoding schema. - * - * @see {@link Schema.instanceOf} for validating existing class instances without a struct encoding - * @see {@link Schema.Class} for defining schema-backed classes directly - * @see {@link Schema.ErrorClass} for defining schema-backed error classes - * - * @category schemas - * @since 4.0.0 - */ -export function getNativeClassSchema any, S extends Schema.Struct>( - constructor: C, - options: { - readonly encoding: S - readonly annotations?: Schema.Annotations.Declaration> - } -): Schema.decodeTo, S["Iso"]>, S> { - const transformation = SchemaTransformation.transform, S["Type"]>({ - decode: (props) => new constructor(props), - encode: identity - }) - return Schema.instanceOf(constructor, { - toCodec: () => Schema.link>()(options.encoding, transformation), - ...options.annotations - }).pipe(Schema.encodeTo(options.encoding, transformation)) -} diff --git a/repos/effect/packages/effect/src/Scope.ts b/repos/effect/packages/effect/src/Scope.ts index 49a6dc271b..a7268975cb 100644 --- a/repos/effect/packages/effect/src/Scope.ts +++ b/repos/effect/packages/effect/src/Scope.ts @@ -25,23 +25,21 @@ const CloseableTypeId = effect.ScopeCloseableTypeId * * **Example** (Managing scoped resources) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Exit, Scope } from "effect" * * const program = Effect.gen(function*() { * const scope = yield* Scope.make("sequential") * - * // Scope has a strategy and state - * console.log(scope.strategy) // "sequential" - * console.log(scope.state._tag) // "Open" - * - * // Close the scope + * const initial = [scope.strategy, scope.state._tag] * yield* Scope.close(scope, Exit.void) - * console.log(scope.state._tag) // "Closed" + * return [initial, scope.state._tag] * }) + * + * Effect.runSync(program) // => [["sequential", "Empty"], "Closed"] * ``` * - * @category models + * @category services * @since 2.0.0 */ export interface Scope { @@ -55,18 +53,18 @@ export interface Scope { * * **Example** (Closing a scope) * - * ```ts - * import { Console, Effect, Exit, Scope } from "effect" + * ```ts import.meta.vitest + * import { Effect, Exit, Scope } from "effect" * + * const cleanups: Array = [] * const program = Effect.gen(function*() { * const scope = yield* Scope.make() - * - * // Add a finalizer - * yield* Scope.addFinalizer(scope, Console.log("Cleanup!")) - * - * // Scope can be closed + * yield* Scope.addFinalizer(scope, Effect.sync(() => cleanups.push("Cleanup!"))) * yield* Scope.close(scope, Exit.void) * }) + * + * Effect.runSync(program) + * cleanups // => ["Cleanup!"] * ``` * * @category models @@ -83,25 +81,17 @@ export interface Closeable extends Scope { * * **Example** (Checking scope states) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Exit, Scope } from "effect" * - * // Example of checking scope states * const program = Effect.gen(function*() { * const scope = yield* Scope.make() - * - * // When open, the scope accepts finalizers - * if (scope.state._tag === "Open") { - * console.log("Scope is open") - * } - * + * const before = scope.state._tag * yield* Scope.close(scope, Exit.void) - * - * // When closed, the scope no longer accepts finalizers - * if (scope.state._tag === "Closed") { - * console.log("Scope is closed") - * } + * return [before, scope.state._tag] * }) + * + * Effect.runSync(program) // => ["Empty", "Closed"] * ``` * * @since 4.0.0 @@ -118,16 +108,12 @@ export declare namespace State { * * **Example** (Inspecting an empty scope state) * - * ```ts + * ```ts import.meta.vitest * import { Scope } from "effect" * * const scope = Scope.makeUnsafe() * - * // When scope is open, you can check its state - * if (scope.state._tag === "Open") { - * console.log("Scope is open and accepting finalizers") - * console.log(scope.state.finalizers.size) // Number of registered finalizers - * } + * scope.state._tag // => "Empty" * ``` * * @category models @@ -142,16 +128,17 @@ export declare namespace State { * * **Example** (Inspecting an open scope state) * - * ```ts - * import { Scope } from "effect" + * ```ts import.meta.vitest + * import { Effect, Scope } from "effect" * * const scope = Scope.makeUnsafe() * - * // When scope is open, you can check its state - * if (scope.state._tag === "Open") { - * console.log("Scope is open and accepting finalizers") - * console.log(scope.state.finalizers.size) // Number of registered finalizers - * } + * Effect.runSync(Scope.addFinalizer(scope, Effect.void)) + * const state = scope.state + * if (state._tag !== "Open") throw new Error("unexpected state") + * + * state._tag // => "Open" + * state.finalizers.size // => 1 * ``` * * @category models @@ -167,21 +154,20 @@ export declare namespace State { * * **Example** (Inspecting a closed scope state) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Exit, Scope } from "effect" * * const program = Effect.gen(function*() { * const scope = yield* Scope.make() * - * // Close the scope * yield* Scope.close(scope, Exit.succeed("Done")) - * - * // Check if scope is closed * if (scope.state._tag === "Closed") { - * console.log("Scope is closed") - * console.log(scope.state.exit) // The exit value used to close the scope + * return scope.state.exit * } + * return Exit.die("unexpected state") * }) + * + * Effect.runSync(program) // => Exit.succeed("Done") * ``` * * @category models @@ -203,19 +189,17 @@ export declare namespace State { * * **Example** (Accessing the scope service) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Scope } from "effect" * + * const cleanups: Array = [] * const program = Effect.gen(function*() { - * // Access the scope from the context * const scope = yield* Scope.Scope - * - * // Use the scope for resource management - * yield* Scope.addFinalizer(scope, Effect.log("Cleanup")) + * yield* Scope.addFinalizer(scope, Effect.sync(() => cleanups.push("Cleanup"))) * }) * - * // Provide a scope to the program - * const scoped = Effect.scoped(program) + * Effect.runSync(Effect.scoped(program)) + * cleanups // => ["Cleanup"] * ``` * * @category services @@ -228,21 +212,19 @@ export const Scope: Context.Service = effect.scopeTag * * **Example** (Creating a scope) * - * ```ts - * import { Console, Effect, Exit, Scope } from "effect" + * ```ts import.meta.vitest + * import { Effect, Exit, Scope } from "effect" * + * const cleanups: Array = [] * const program = Effect.gen(function*() { - * // Create a scope with sequential cleanup * const scope = yield* Scope.make("sequential") - * - * // Add finalizers - * yield* Scope.addFinalizer(scope, Console.log("Cleanup 1")) - * yield* Scope.addFinalizer(scope, Console.log("Cleanup 2")) - * - * // Close the scope (finalizers run in reverse order) + * 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) - * // Output: "Cleanup 2", then "Cleanup 1" * }) + * + * Effect.runSync(program) + * cleanups // => ["Cleanup 2", "Cleanup 1"] * ``` * * @category constructors @@ -262,17 +244,18 @@ export const make: (finalizerStrategy?: "sequential" | "parallel") => Effect = [] * const program = Effect.gen(function*() { - * yield* Scope.addFinalizer(scope, Console.log("Cleanup")) + * yield* Scope.addFinalizer(scope, Effect.sync(() => cleanups.push("Cleanup"))) * yield* Scope.close(scope, Exit.void) * }) + * + * Effect.runSync(program) + * cleanups // => ["Cleanup"] * ``` * * @category constructors @@ -294,21 +277,24 @@ export const makeUnsafe: (finalizerStrategy?: "sequential" | "parallel") => Clos * * **Example** (Providing a scope) * - * ```ts - * import { Console, Effect, Scope } from "effect" + * ```ts import.meta.vitest + * import { Effect, Exit, Scope } from "effect" * - * // An effect that requires a Scope + * const events: Array = [] * const program = Effect.gen(function*() { * const scope = yield* Scope.Scope - * yield* Scope.addFinalizer(scope, Console.log("Cleanup")) - * yield* Console.log("Working...") + * yield* Scope.addFinalizer(scope, Effect.sync(() => events.push("cleanup"))) + * events.push("working") * }) * - * // Provide a scope to the program * 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 @@ -335,29 +321,18 @@ export const provide: { * * **Example** (Adding an exit-aware finalizer) * - * ```ts - * import { Console, Effect, Exit, Scope } from "effect" + * ```ts import.meta.vitest + * import { Effect, Exit, Scope } from "effect" * + * const exits: Array> = [] * const withResource = Effect.gen(function*() { * const scope = yield* Scope.make() - * - * // Add a finalizer for cleanup - * yield* Scope.addFinalizerExit( - * scope, - * (exit) => - * Console.log( - * `Cleaning up resource. Exit: ${ - * Exit.isSuccess(exit) ? "Success" : "Failure" - * }` - * ) - * ) - * - * // Use the resource - * yield* Console.log("Using resource") - * - * // Close the scope + * yield* Scope.addFinalizerExit(scope, (exit) => Effect.sync(() => exits.push(exit))) * yield* Scope.close(scope, Exit.void) * }) + * + * Effect.runSync(withResource) + * exits // => [Exit.void] * ``` * * @category combinators @@ -377,23 +352,21 @@ export const addFinalizerExit: (scope: Scope, finalizer: (exit: Exit) * * **Example** (Adding finalizers) * - * ```ts - * import { Console, Effect, Exit, Scope } from "effect" + * ```ts import.meta.vitest + * import { Effect, Exit, Scope } from "effect" * + * const events: Array = [] * const program = Effect.gen(function*() { * const scope = yield* Scope.make() - * - * // Add simple finalizers - * yield* Scope.addFinalizer(scope, Console.log("Cleanup task 1")) - * yield* Scope.addFinalizer(scope, Console.log("Cleanup task 2")) - * yield* Scope.addFinalizer(scope, Effect.log("Cleanup task 3")) - * - * // Do some work - * yield* Console.log("Doing work...") - * - * // Close the scope + * 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 @@ -412,25 +385,21 @@ export const addFinalizer: (scope: Scope, finalizer: Effect) => Effect< * * **Example** (Creating a child scope) * - * ```ts - * import { Console, Effect, Exit, Scope } from "effect" + * ```ts import.meta.vitest + * import { Effect, Exit, Scope } from "effect" * + * const cleanups: Array = [] * const nestedScopes = Effect.gen(function*() { * const parentScope = yield* Scope.make("sequential") - * - * // Add finalizer to parent - * yield* Scope.addFinalizer(parentScope, Console.log("Parent cleanup")) - * - * // Create child scope + * yield* Scope.addFinalizer(parentScope, Effect.sync(() => cleanups.push("parent"))) * const childScope = yield* Scope.fork(parentScope, "parallel") - * - * // Add finalizer to child - * yield* Scope.addFinalizer(childScope, Console.log("Child cleanup")) - * - * // Close child first, then parent + * 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 @@ -457,21 +426,21 @@ export const fork: ( * * **Example** (Creating a child scope synchronously) * - * ```ts - * import { Console, Effect, Exit, Scope } from "effect" + * ```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") - * - * // Add finalizers to both scopes - * yield* Scope.addFinalizer(parentScope, Console.log("Parent cleanup")) - * yield* Scope.addFinalizer(childScope, Console.log("Child cleanup")) - * - * // Close child first, then parent + * 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 @@ -494,24 +463,21 @@ export const forkUnsafe: (scope: Scope, finalizerStrategy?: "sequential" | "para * * **Example** (Running scope finalizers) * - * ```ts - * import { Console, Effect, Exit, Scope } from "effect" + * ```ts import.meta.vitest + * import { Effect, Exit, Scope } from "effect" * + * const events: Array = [] * const resourceManagement = Effect.gen(function*() { * const scope = yield* Scope.make("sequential") - * - * // Add multiple finalizers - * yield* Scope.addFinalizer(scope, Console.log("Close database connection")) - * yield* Scope.addFinalizer(scope, Console.log("Close file handle")) - * yield* Scope.addFinalizer(scope, Console.log("Release memory")) - * - * // Do some work... - * yield* Console.log("Performing operations...") - * - * // Close scope - finalizers run in reverse order of registration + * 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!")) - * // Output: "Release memory", "Close file handle", "Close database connection" * }) + * + * Effect.runSync(resourceManagement) + * events // => ["work", "memory", "file", "database"] * ``` * * @category combinators diff --git a/repos/effect/packages/effect/src/ScopedCache.ts b/repos/effect/packages/effect/src/ScopedCache.ts index c80da9db98..664be81384 100644 --- a/repos/effect/packages/effect/src/ScopedCache.ts +++ b/repos/effect/packages/effect/src/ScopedCache.ts @@ -211,7 +211,7 @@ export const make = < > => makeWith({ ...options, - timeToLive: options.timeToLive ? () => options.timeToLive! : defaultTimeToLive + timeToLive: options.timeToLive !== undefined ? () => options.timeToLive! : defaultTimeToLive }) const Proto = { diff --git a/repos/effect/packages/effect/src/ScopedRef.ts b/repos/effect/packages/effect/src/ScopedRef.ts index f708463cd0..8cbb2609e8 100644 --- a/repos/effect/packages/effect/src/ScopedRef.ts +++ b/repos/effect/packages/effect/src/ScopedRef.ts @@ -165,7 +165,7 @@ export const make = (evaluate: LazyArg): Effect.Effect, never * changed to the new value, with old resources released, or until the attempt * to acquire a new value fails. * - * @category setters + * @category mutations * @since 2.0.0 */ export const set: { @@ -178,12 +178,14 @@ export const set: { self: ScopedRef, acquire: Effect.Effect ) { - yield* Scope.close(self.backing.backing.ref.current[0], Exit.void) 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, diff --git a/repos/effect/packages/effect/src/Semaphore.ts b/repos/effect/packages/effect/src/Semaphore.ts index 12c0758daf..217e80920c 100644 --- a/repos/effect/packages/effect/src/Semaphore.ts +++ b/repos/effect/packages/effect/src/Semaphore.ts @@ -33,7 +33,7 @@ import type * as Option from "./Option.ts" * * **Example** (Controlling concurrent access) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Semaphore } from "effect" * * // Create and use a semaphore for controlling concurrent access @@ -44,6 +44,8 @@ import type * as Option from "./Option.ts" * Effect.succeed("Resource accessed") * ) * }) + * + * await Effect.runPromise(program) // => "Resource accessed" * ``` * * @see {@link make} for creating a semaphore inside Effect code @@ -118,9 +120,11 @@ export interface Semaphore { ): (self: Effect.Effect) => Effect.Effect, E, R> /** - * Acquires the specified number of permits and returns the resulting - * available permits, suspending the task if they are not yet available. - * Concurrent pending `take` calls are processed in a first-in, first-out manner. + * 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** * @@ -128,6 +132,16 @@ export interface Semaphore { */ 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. @@ -160,7 +174,7 @@ export interface Semaphore { * * **Example** (Creating an unsafe semaphore) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Semaphore } from "effect" * * const semaphore = Semaphore.makeUnsafe(3) @@ -168,9 +182,8 @@ export interface Semaphore { * const task = (id: number) => * semaphore.withPermits(1)( * Effect.gen(function*() { - * yield* Effect.log(`Task ${id} started`) - * yield* Effect.sleep("1 second") - * yield* Effect.log(`Task ${id} completed`) + * yield* Effect.yieldNow + * return id * }) * ) * @@ -182,6 +195,8 @@ export interface Semaphore { * task(4), * task(5) * ], { concurrency: "unbounded" }) + * + * await Effect.runPromise(program) // => [1, 2, 3, 4, 5] * ``` * * @category constructors @@ -189,6 +204,24 @@ export interface Semaphore { */ 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 @@ -205,18 +238,7 @@ class SemaphoreImpl implements Semaphore { take(n: number): Effect.Effect { const take: Effect.Effect = internal.suspend(() => { if (this.free < n) { - return internal.callback((resume) => { - if (this.free >= n) return resume(take) - const observer = () => { - if (this.free < n) return - this.waiters.delete(observer) - resume(take) - } - this.waiters.add(observer) - return internal.sync(() => { - this.waiters.delete(observer) - }) - }) + return waitForPermits(this, n, take) } this.taken += n return internal.succeed(n) @@ -224,58 +246,64 @@ class SemaphoreImpl implements Semaphore { return take } - updateTakenUnsafe(fiber: Fiber, f: (n: number) => number): number { - this.taken = f(this.taken) + 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(() => { - const iter = this.waiters.values() - let item = iter.next() - while (item.done === false && this.free > 0) { - item.value() - item = iter.next() + for (const observer of this.waiters) { + if (this.free <= 0) break + observer() } }, 0) } return this.free } - updateTaken(f: (n: number) => number): Effect.Effect { - return core.withFiber((fiber) => internal.succeed(this.updateTakenUnsafe(fiber, f))) - } - resize(permits: number) { return core.withFiber((fiber) => { this.permits = permits if (this.free < 0) return internal.void - this.updateTakenUnsafe(fiber, (taken) => taken) + this.releaseUnsafe(fiber, 0) return internal.void }) } release(n: number): Effect.Effect { - return this.updateTaken((taken) => taken - n) + return core.withFiber((fiber) => internal.succeed(this.releaseUnsafe(fiber, n))) } get releaseAll(): Effect.Effect { - return this.updateTaken((_) => 0) + return core.withFiber((fiber) => internal.succeed(this.releaseUnsafe(fiber, this.taken))) } withPermits(n: number) { return (self: Effect.Effect) => - internal.uninterruptibleMask((restore) => - internal.flatMap( - restore(this.take(n)), - (permits) => - internal.onExitPrimitive( - restore(self), - () => { - this.updateTakenUnsafe(internal.getCurrentFiber()!, (taken) => taken - permits) - return undefined - }, - true - ) - ) - ) + 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) @@ -286,7 +314,7 @@ class SemaphoreImpl implements Semaphore { if (this.free < n) return internal.succeedNone this.taken += n return internal.onExitPrimitive(restore(internal.asSome(self)), () => { - this.updateTakenUnsafe(internal.getCurrentFiber()!, (taken) => taken - n) + this.releaseUnsafe(internal.getCurrentFiber()!, n) return undefined }, true) }) @@ -303,7 +331,7 @@ class SemaphoreImpl implements Semaphore { * * **Example** (Creating a semaphore) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Semaphore } from "effect" * * const program = Effect.gen(function*() { @@ -312,15 +340,16 @@ class SemaphoreImpl implements Semaphore { * const task = (id: number) => * semaphore.withPermits(1)( * Effect.gen(function*() { - * yield* Effect.log(`Task ${id} acquired permit`) - * yield* Effect.sleep("1 second") - * yield* Effect.log(`Task ${id} releasing permit`) + * yield* Effect.yieldNow + * return id * }) * ) * * // Run 4 tasks, but only 2 can run concurrently - * yield* Effect.all([task(1), task(2), task(3), task(4)]) + * return yield* Effect.all([task(1), task(2), task(3), task(4)]) * }) + * + * await Effect.runPromise(program) // => [1, 2, 3, 4] * ``` * * @category constructors @@ -456,6 +485,7 @@ export const withPermitsIfAvailable: { * * @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 @@ -466,6 +496,33 @@ export const take: { (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. diff --git a/repos/effect/packages/effect/src/Sink.ts b/repos/effect/packages/effect/src/Sink.ts index 8dd5bd6ef4..2d5b7ddd8e 100644 --- a/repos/effect/packages/effect/src/Sink.ts +++ b/repos/effect/packages/effect/src/Sink.ts @@ -46,7 +46,7 @@ const TypeId = "~effect/Sink" * * **Example** (Running a sink with a stream) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Sink, Stream } from "effect" * * // Create a simple sink that always succeeds with a value @@ -54,10 +54,7 @@ const TypeId = "~effect/Sink" * * // Use the sink to consume a stream * const stream = Stream.make(1, 2, 3) - * const program = Stream.run(stream, sink) - * - * Effect.runPromise(program).then(console.log) - * // Output: 42 + * await Effect.runPromise(Stream.run(stream, sink)) // => 42 * ``` * * @category models @@ -194,14 +191,14 @@ const SinkProto = { * * **Example** (Checking for a sink) * - * ```ts + * ```ts import.meta.vitest * import { Sink } from "effect" * * const sink = Sink.never * const notStream = { data: [1, 2, 3] } * - * console.log(Sink.isSink(sink)) // true - * console.log(Sink.isSink(notStream)) // false + * Sink.isSink(sink) // => true + * Sink.isSink(notStream) // => false * ``` * * @category guards @@ -217,6 +214,20 @@ export const isSink = (u: unknown): u is Sink], 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 @@ -239,6 +250,43 @@ export const fromChannel = ( ) 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. * @@ -265,14 +313,16 @@ export const fromTransform = ( /** * Creates a `Channel` from a Sink. * - * **Example** (Converting a sink to a channel) + * **Example** (Running a sink as a channel) * - * ```ts - * import { Sink } from "effect" + * ```ts import.meta.vitest + * import { Channel, Effect, Sink, Stream } from "effect" * - * // Create a sink and extract its channel - * const sink = Sink.succeed(42) - * const channel = Sink.toChannel(sink) + * 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 @@ -494,7 +544,7 @@ export const fromPubSub = ( * * **Example** (Succeeding with a value) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Sink, Stream } from "effect" * * // Create a sink that always yields the same value @@ -502,10 +552,7 @@ export const fromPubSub = ( * * // Use it with a stream * const stream = Stream.make(1, 2, 3) - * const program = Stream.run(stream, sink) - * - * Effect.runPromise(program).then(console.log) - * // Output: 42 + * await Effect.runPromise(Stream.run(stream, sink)) // => 42 * ``` * * @category constructors @@ -536,18 +583,15 @@ export const suspend = (evaluate: LazyArg>) * * **Example** (Failing with an error) * - * ```ts - * import { Effect, Sink, Stream } from "effect" + * ```ts import.meta.vitest + * import { Effect, Exit, Sink, Stream } from "effect" * * // Create a sink that always fails - * const sink = Sink.fail(new Error("Sink failed")) + * const sink = Sink.fail("Sink failed") * * // Use it with a stream * const stream = Stream.make(1, 2, 3) - * const program = Stream.run(stream, sink) - * - * Effect.runPromise(program).catch(console.log) - * // Output: Error: Sink failed + * await Effect.runPromiseExit(Stream.run(stream, sink)) // => Exit.fail("Sink failed") * ``` * * @category constructors @@ -560,18 +604,15 @@ export const fail = (e: E): Sink => fromEffectEnd(E * * **Example** (Failing with a lazy error) * - * ```ts - * import { Effect, Sink, Stream } from "effect" + * ```ts import.meta.vitest + * import { Effect, Exit, Sink, Stream } from "effect" * * // Create a sink that fails with a lazy error - * const sink = Sink.failSync(() => new Error("Lazy error")) + * const sink = Sink.failSync(() => "Lazy error") * * // Use it with a stream * const stream = Stream.make(1, 2, 3) - * const program = Stream.run(stream, sink) - * - * Effect.runPromise(program).catch(console.log) - * // Output: Error: Lazy error + * await Effect.runPromiseExit(Stream.run(stream, sink)) // => Exit.fail("Lazy error") * ``` * * @category constructors @@ -585,18 +626,15 @@ export const failSync = (evaluate: LazyArg): Sink Exit.fail("Custom cause") * ``` * * @category constructors @@ -610,18 +648,15 @@ export const failCause = (cause: Cause.Cause): Sink Cause.fail(new Error("Lazy cause"))) + * const sink = Sink.failCauseSync(() => Cause.fail("Lazy cause")) * * // Use it with a stream * const stream = Stream.make(1, 2, 3) - * const program = Stream.run(stream, sink) - * - * Effect.runPromise(program).catch(console.log) - * // Output: Error: Lazy cause + * await Effect.runPromiseExit(Stream.run(stream, sink)) // => Exit.fail("Lazy cause") * ``` * * @category constructors @@ -635,18 +670,15 @@ export const failCauseSync = (evaluate: LazyArg>): Sink Exit.die("Defect error") * ``` * * @category constructors @@ -1115,7 +1147,7 @@ export const mapLeftover: { * If more elements are pulled than needed, the remaining elements from the same * array are returned as leftovers. * - * @category collecting + * @category constructors * @since 2.0.0 */ export const take = (n: number): Sink, In, In> => @@ -1212,7 +1244,7 @@ export const flatMap: { * A sink that reduces input elements from the provided `initial` state with * `f` while the specified `predicate` returns `true`. * - * @category reducing + * @category folding * @since 4.0.0 */ export const reduceWhile = ( @@ -1248,7 +1280,7 @@ export const reduceWhile = ( * A sink that effectfully reduces input elements from the provided `initial` * state with `f` while the specified `predicate` returns `true`. * - * @category reducing + * @category folding * @since 4.0.0 */ export const reduceWhileEffect = ( @@ -1289,7 +1321,7 @@ export const reduceWhileEffect = ( * A sink that reduces non-empty input arrays from the provided `initial` state * with `f` while the specified `predicate` returns `true`. * - * @category reducing + * @category folding * @since 4.0.0 */ export const reduceWhileArray = ( @@ -1304,11 +1336,9 @@ export const reduceWhileArray = ( } return upstream.pipe( Effect.flatMap((arr) => { - for (let i = 0; i < arr.length; i++) { - state = f(state, arr) - if (!contFn(state)) { - return Cause.done() - } + state = f(state, arr) + if (!contFn(state)) { + return Cause.done() } return Effect.void }), @@ -1321,7 +1351,7 @@ export const reduceWhileArray = ( * A sink that effectfully reduces non-empty input arrays from the provided * `initial` state with `f` while the specified `predicate` returns `true`. * - * @category reducing + * @category folding * @since 4.0.0 */ export const reduceWhileArrayEffect = ( @@ -1352,7 +1382,7 @@ export const reduceWhileArrayEffect = ( * A sink that reduces its inputs using the provided function `f` starting from * the provided `initial` state. * - * @category reducing + * @category folding * @since 4.0.0 */ export const reduce = (initial: LazyArg, f: (s: S, input: In) => S): Sink => @@ -1367,7 +1397,7 @@ export const reduce = (initial: LazyArg, f: (s: S, input: In) => S): S * A sink that reduces its inputs using the provided function `f` starting from * the specified `initial` state. * - * @category reducing + * @category folding * @since 4.0.0 */ export const reduceArray = ( @@ -1390,7 +1420,7 @@ export const reduceArray = ( * A sink that reduces its inputs using the provided effectful function `f` * starting from the specified `initial` state. * - * @category reducing + * @category folding * @since 4.0.0 */ export const reduceEffect = ( @@ -1756,21 +1786,16 @@ export const takeUntilEffect = ( * * **Example** (Running effects for each item) * - * ```ts - * import { Console, Effect, Sink, Stream } from "effect" + * ```ts import.meta.vitest + * import { Effect, Sink, Stream } from "effect" * - * // Create a sink that logs each item - * const sink = Sink.forEach((item: number) => Console.log(`Processing: ${item}`)) + * 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) - * const program = Stream.run(stream, sink) - * - * Effect.runPromise(program) - * // Output: - * // Processing: 1 - * // Processing: 2 - * // Processing: 3 + * await Effect.runPromise(Stream.run(stream, sink)) + * processed // => [1, 2, 3] * ``` * * @category constructors @@ -1786,22 +1811,16 @@ export const forEach = ( * * **Example** (Running effects for each chunk) * - * ```ts - * import { Console, Effect, Sink, Stream } from "effect" + * ```ts import.meta.vitest + * import { Effect, Sink, Stream } from "effect" * - * // Create a sink that processes chunks - * const sink = Sink.forEachArray((chunk: ReadonlyArray) => - * Console.log( - * `Processing chunk of ${chunk.length} items: [${chunk.join(", ")}]` - * ) - * ) + * 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) - * const program = Stream.run(stream, sink) - * - * Effect.runPromise(program) - * // Output: Processing chunk of 5 items: [1, 2, 3, 4, 5] + * await Effect.runPromise(Stream.run(stream, sink)) + * processed // => [[1, 2, 3, 4, 5]] * ``` * * @category constructors @@ -1869,24 +1888,20 @@ export const forEachWhileArray = ( * * **Example** (Unwrapping a sink effect) * - * ```ts - * import { Console, Effect, Sink, Stream } from "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) => Console.log(`Item: ${item}`)) + * 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) - * const program = Stream.run(stream, sink) - * - * Effect.runPromise(program) - * // Output: - * // Item: 1 - * // Item: 2 - * // Item: 3 + * await Effect.runPromise(Stream.run(stream, sink)) + * processed // => [1, 2, 3] * ``` * * @category constructors @@ -1933,7 +1948,7 @@ export const summarized: { export const withDuration = ( self: Sink ): Sink<[A, Duration.Duration], In, L, E, R> => - summarized(self, Clock.currentTimeNanos, (start, end) => Duration.nanos(end - start)) + summarized(self, Clock.monotonicTimeNanos, (start, end) => Duration.nanos(end - start)) /** * A sink that drains all input and returns the elapsed duration. @@ -1951,7 +1966,7 @@ export const timed: Sink = map(withDuration(drain), * Services contained in the provided context are removed from the sink's * service requirements. * - * @category services + * @category providing services * @since 2.0.0 */ export const provideContext: { @@ -1980,7 +1995,7 @@ export const provideContext: { * The service identified by `key` is removed from the sink's service * requirements. * - * @category services + * @category providing services * @since 4.0.0 */ export const provideService: { @@ -2089,7 +2104,7 @@ export const catchCause: { const catch_: { ( f: (error: Types.NoInfer) => Effect.Effect - ): (self: Sink) => Sink + ): (self: Sink) => Sink ( self: Sink, f: (error: E) => Effect.Effect @@ -2129,7 +2144,7 @@ export { * 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 Finalization + * @category resource management * @since 4.0.0 */ export const onExit: { @@ -2157,7 +2172,7 @@ export const onExit: { * The original sink result and leftovers are preserved unless the finalizer * itself fails. * - * @category Finalization + * @category resource management * @since 2.0.0 */ export const ensuring: { diff --git a/repos/effect/packages/effect/src/Stdio.ts b/repos/effect/packages/effect/src/Stdio.ts index 899c75e62a..d51caa5417 100644 --- a/repos/effect/packages/effect/src/Stdio.ts +++ b/repos/effect/packages/effect/src/Stdio.ts @@ -57,12 +57,24 @@ export const TypeId: TypeId = "~effect/Stdio" * standard error, and a stream of standard input bytes. I/O operations can fail * with `PlatformError`. * - * @category models + * @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 @@ -98,16 +110,23 @@ export const Stdio: Context.Service = Context.Service(TypeI * * **Details** * - * The returned service reuses the supplied fields unchanged and only adds the - * `Stdio` type identifier; it does not create a `Layer` or provide defaults. + * 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): Stdio => ({ +export const make = ( + options: + & Omit + & Partial> +): Stdio => ({ [TypeId]: TypeId, + stdinIsTerminal: Effect.succeed(false), + stdoutIsTerminal: Effect.succeed(false), ...options }) @@ -123,7 +142,7 @@ export const make = (options: Omit): Stdio => ({ * * Any provided fields override defaults. By default, arguments are empty, * standard output and error are draining sinks, and standard input is an empty - * stream. + * stream, and terminal-detection effects succeed with `false`. * * @see {@link make} for constructing a `Stdio` service directly without a `Layer` or defaults * diff --git a/repos/effect/packages/effect/src/Stream.ts b/repos/effect/packages/effect/src/Stream.ts index 39cb586b82..4c2f749ac2 100644 --- a/repos/effect/packages/effect/src/Stream.ts +++ b/repos/effect/packages/effect/src/Stream.ts @@ -22,6 +22,7 @@ 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" @@ -103,21 +104,16 @@ export const TypeId: TypeId = "~effect/Stream" * * **Example** (Creating and consuming streams) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" * - * const program = Effect.gen(function*() { - * yield* Stream.make(1, 2, 3).pipe( + * const values = await Effect.runPromise( + * Stream.make(1, 2, 3).pipe( * Stream.map((n) => n * 2), - * Stream.runForEach((n) => Console.log(n)) + * Stream.runCollect * ) - * }) - * - * Effect.runPromise(program) - * // Output: - * // 2 - * // 4 - * // 6 + * ) + * values // => [2, 4, 6] * ``` * * @category models @@ -155,15 +151,17 @@ export interface StreamUnifyIgnore { * * **Example** (Using the stream type lambda) * - * ```ts - * import type { HKT, Stream } from "effect" + * ```ts import.meta.vitest + * import { Effect, HKT, Stream } from "effect" * * // Create a Stream type using the type lambda - * type NumberStream = HKT.Kind + * 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 type lambdas + * @category utility types * @since 2.0.0 */ export interface StreamTypeLambda extends TypeLambda { @@ -207,12 +205,12 @@ export interface VarianceStruct { * * **Example** (Extracting the success type from a Stream type) * - * ```ts - * import type { Stream } from "effect" + * ```ts import.meta.vitest + * import { Stream } from "effect" * * type NumberStream = Stream.Stream * type SuccessType = Stream.Success - * // SuccessType is number + * const value: SuccessType = 42 * ``` * * @category utility types @@ -225,12 +223,12 @@ export type Success> = [T] extends [Stream * type ErrorType = Stream.Error - * // ErrorType is string + * const error: ErrorType = "boom" * ``` * * @category utility types @@ -243,15 +241,16 @@ export type Error> = [T] extends [Stream unknown * } * type NumberStream = Stream.Stream * type RequiredServices = Stream.Services - * // RequiredServices is { db: Database } + * const services: RequiredServices = { db: { query: (sql) => sql } } + * services.db.query("SELECT 1") // => "SELECT 1" * ``` * * @category utility types @@ -265,20 +264,11 @@ export type Services> = [T] extends [Stream true + * Stream.isStream({ data: [1, 2, 3] }) // => false * ``` * * @category guards @@ -291,15 +281,10 @@ export const isStream = (u: unknown): u is Stream => * * **Example** (Reading the default chunk size) * - * ```ts - * import { Console, Effect, Stream } from "effect" - * - * const program = Effect.gen(function*() { - * yield* Console.log(Stream.DefaultChunkSize) - * }) + * ```ts import.meta.vitest + * import { Stream } from "effect" * - * Effect.runPromise(program) - * // Output: 4096 + * Stream.DefaultChunkSize // => 4096 * ``` * * @category constants @@ -320,17 +305,12 @@ export type HaltStrategy = Channel.HaltStrategy * * **Example** (Creating a stream from an array-emitting channel) * - * ```ts - * import { Channel, Console, Effect, Stream } from "effect" - * - * const program = Effect.gen(function*() { - * const channel = Channel.succeed([1, 2, 3] as const) - * const stream = Stream.fromChannel(channel) - * const result = yield* Stream.runCollect(stream) - * yield* Console.log(result) - * }) + * ```ts import.meta.vitest + * import { Channel, Effect, Stream } from "effect" * - * // Output: [ 1, 2, 3 ] + * 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 @@ -345,17 +325,11 @@ export const fromChannel: , E, R>( * * **Example** (Creating a stream from an effect) * - * ```ts - * import { Console, Effect, Stream } from "effect" - * - * const program = Effect.gen(function*() { - * const stream = Stream.fromEffect(Effect.succeed(42)) - * const values = yield* Stream.runCollect(stream) - * yield* Console.log(values) - * }) + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" * - * Effect.runPromise(program) - * // Output: [ 42 ] + * const stream = Stream.fromEffect(Effect.succeed(42)) + * await Effect.runPromise(Stream.runCollect(stream)) // => [42] * ``` * * @category constructors @@ -369,7 +343,7 @@ export const fromEffect = (effect: Effect.Effect): Stream(effect: Effect.Effect): Stream greeter.greet("World")) * ) * - * const program = Effect.gen(function*() { - * return yield* stream.pipe( + * await Effect.runPromise( + * stream.pipe( * Stream.provideService(Greeter, { * greet: (name) => `Hello, ${name}!` * }), * Stream.runCollect * ) - * }) - * - * Effect.runPromise(program) - * // Output: [ "Hello, World!" ] + * ) // => ["Hello, World!"] * ``` * - * @category context + * @category accessors * @since 4.0.0 */ export const service = (service: Context.Key): Stream => fromEffect(Effect.service(service)) @@ -409,7 +380,7 @@ export const service = (service: Context.Key): Stream = * * **Example** (Accessing an optional service as a stream) * - * ```ts + * ```ts import.meta.vitest * import { Context, Effect, Option, Stream } from "effect" * * class Greeter extends Context.Service(service: Context.Key): Stream = * ) * ) * - * const program = Effect.gen(function*() { - * return yield* stream.pipe( + * await Effect.runPromise( + * stream.pipe( * Stream.provideService(Greeter, { * greet: (name) => `Hello, ${name}!` * }), * Stream.runCollect * ) - * }) - * - * Effect.runPromise(program) - * // Output: [ "Hello, World!" ] + * ) // => ["Hello, World!"] * ``` * - * @category context + * @category accessors * @since 4.0.0 */ export const serviceOption = (service: Context.Key): Stream> => @@ -449,17 +417,16 @@ export const serviceOption = (service: Context.Key): Stream { + * drained = true + * })).pipe(Stream.runDrain) + * ) + * drained // => true * ``` * * @category constructors @@ -473,19 +440,12 @@ export const fromEffectDrain = (effect: Effect.Effect): Stream * * **Example** (Repeating an effect forever) * - * ```ts - * import { Console, Effect, Random, Stream } from "effect" - * - * const program = Effect.gen(function*() { - * const stream = Stream.fromEffectRepeat(Random.nextInt).pipe( - * Stream.take(5) - * ) - * const values = yield* Stream.runCollect(stream) - * yield* Console.log(values) - * }) + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" * - * Effect.runPromise(program) - * // Output: [ 3891571149, 4239494205, 2352981603, 2339111046, 1488052210 ] + * 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 @@ -500,20 +460,11 @@ export const fromEffectRepeat = (effect: Effect.Effect): Strea * * **Example** (Repeating an effect with a schedule) * - * ```ts - * import { Console, Effect, Schedule, Stream } from "effect" - * - * const program = Effect.gen(function*() { - * const stream = Stream.fromEffectSchedule( - * Effect.succeed("ping"), - * Schedule.recurs(2) - * ) - * const result = yield* Stream.runCollect(stream) - * yield* Console.log(result) - * }) + * ```ts import.meta.vitest + * import { Effect, Schedule, Stream } from "effect" * - * Effect.runPromise(program) - * // Output: [ "ping", "ping", "ping" ] + * const stream = Stream.fromEffectSchedule(Effect.succeed("ping"), Schedule.recurs(2)) + * await Effect.runPromise(Stream.runCollect(stream)) // => ["ping", "ping", "ping"] * ``` * * @category constructors @@ -549,19 +500,10 @@ export const fromEffectSchedule = ( * * **Example** (Emitting ticks on an interval) * - * ```ts - * import { Console, Effect, Stream } from "effect" - * - * const program = Effect.gen(function*() { - * const ticks = yield* Stream.tick("200 millis").pipe( - * Stream.take(3), - * Stream.runCollect - * ) - * yield* Console.log(ticks) - * }) + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" * - * Effect.runPromise(program) - * // Output: [ undefined, undefined, undefined ] + * await Effect.runPromise(Stream.tick(0).pipe(Stream.take(3), Stream.runCollect)) // => [undefined, undefined, undefined] * ``` * * @category constructors @@ -591,21 +533,19 @@ export const tick = (interval: Duration.Input): Stream => * * **Example** (Creating a stream from a pull effect) * - * ```ts - * import { Console, Effect, Stream } from "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)) - * const values = yield* Stream.runCollect(stream) - * yield* Console.log(values) + * return yield* Stream.runCollect(stream) * }) * ) * - * Effect.runPromise(program) - * // Output: [1, 2, 3] + * await Effect.runPromise(program) // => [1, 2, 3] * ``` * * @category constructors @@ -620,20 +560,14 @@ export const fromPull = ( * * **Example** (Transforming a pull effect) * - * ```ts - * import { Console, Effect, Stream } from "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)) * - * const program = Effect.gen(function*() { - * const values = yield* Stream.runCollect(transformed) - * yield* Console.log(values) - * }) - * - * Effect.runPromise(program) - * // Output: [ 1, 2, 3 ] + * await Effect.runPromise(Stream.runCollect(transformed)) // => [1, 2, 3] * ``` * * @category constructors @@ -663,28 +597,23 @@ export const transformPull = ( * * **Example** (Transforming a stream by effectfully transforming its pull effect) * - * ```ts - * import { Console, Effect, Scope, Stream } from "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, Console.log("Releasing scope")) + * yield* Scope.addFinalizer(forkedScope, Effect.sync(() => finalized.push(true))) * return pull * }) * ) * - * const program = Effect.gen(function*() { - * const values = yield* Stream.runCollect(transformed) - * yield* Console.log(values) - * }) - * - * Effect.runPromise(program) - * // Output: [1, 2, 3] - * // Releasing scope + * await Effect.runPromise(Stream.runCollect(transformed)) // => [1, 2, 3] + * finalized // => [true] * ``` * * @category constructors @@ -713,18 +642,12 @@ export const transformPullBracket = ( * * **Example** (Converting a stream to a channel) * - * ```ts - * import { Channel, Console, Effect, Stream } from "effect" - * - * const program = Effect.gen(function*() { - * const stream = Stream.make(1, 2, 3) - * const channel = Stream.toChannel(stream) - * const values = yield* Channel.runCollect(channel) - * yield* Console.log(values.flat()) - * }) + * ```ts import.meta.vitest + * import { Channel, Effect, Stream } from "effect" * - * Effect.runPromise(program) - * // Output: [ 1, 2, 3 ] + * const channel = Stream.toChannel(Stream.make(1, 2, 3)) + * const values = await Effect.runPromise(Channel.runCollect(channel)) + * values.flat() // => [1, 2, 3] * ``` * * @category constructors @@ -748,8 +671,8 @@ export const toChannel = ( * * **Example** (Creating a stream from a callback that can emit values into a queue) * - * ```ts - * import { Console, Effect, Queue, Stream } from "effect" + * ```ts import.meta.vitest + * import { Effect, Queue, Stream } from "effect" * * const stream = Stream.callback((queue) => * Effect.sync(() => { @@ -762,13 +685,7 @@ export const toChannel = ( * }) * ) * - * const program = Effect.gen(function*() { - * const values = yield* stream.pipe(Stream.runCollect) - * yield* Console.log(values) - * // [ 1, 2, 3 ] - * }) - * - * Effect.runPromise(program) + * await Effect.runPromise(Stream.runCollect(stream)) // => [1, 2, 3] * ``` * * @category constructors @@ -787,16 +704,10 @@ export const callback = ( * * **Example** (Creating an empty stream) * - * ```ts - * import { Console, Effect, Stream } from "effect" - * - * const program = Effect.gen(function*() { - * const values = yield* Stream.empty.pipe(Stream.runCollect) - * yield* Console.log(values) - * }) + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" * - * Effect.runPromise(program) - * // [] + * await Effect.runPromise(Stream.runCollect(Stream.empty)) // => [] * ``` * * @category constructors @@ -809,16 +720,10 @@ export const empty: Stream = fromChannel(Channel.empty) * * **Example** (Creating a single-valued pure stream) * - * ```ts - * import { Console, Effect, Stream } from "effect" - * - * const program = Effect.gen(function*() { - * const values = yield* Stream.succeed(3).pipe(Stream.runCollect) - * yield* Console.log(values) - * }) + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" * - * Effect.runPromise(program) - * // [ 3 ] + * await Effect.runPromise(Stream.runCollect(Stream.succeed(3))) // => [3] * ``` * * @category constructors @@ -831,17 +736,12 @@ export const succeed = (value: A): Stream => fromChannel(Channel.succeed(A * * **Example** (Creating a stream from a sequence of values) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" * * const stream = Stream.make(1, 2, 3) * - * const program = Effect.gen(function*() { - * const values = yield* Stream.runCollect(stream) - * yield* Console.log(values) // [ 1, 2, 3 ] - * }) - * - * Effect.runPromise(program) + * await Effect.runPromise(Stream.runCollect(stream)) // => [1, 2, 3] * ``` * * @category constructors @@ -858,16 +758,10 @@ export const make = >(...values: As): Stream * * **Example** (Evaluating a value synchronously) * - * ```ts - * import { Console, Effect, Stream } from "effect" - * - * const program = Effect.gen(function*() { - * const values = yield* Stream.sync(() => 2 + 1).pipe(Stream.runCollect) - * yield* Console.log(values) - * }) + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" * - * Effect.runPromise(program) - * // Output: [ 3 ] + * await Effect.runPromise(Stream.sync(() => 2 + 1).pipe(Stream.runCollect)) // => [3] * ``` * * @category constructors @@ -884,16 +778,10 @@ export const sync = (evaluate: LazyArg): Stream => fromChannel(Channel. * * **Example** (Creating a lazily constructed stream) * - * ```ts - * import { Console, Effect, Stream } from "effect" - * - * const program = Effect.gen(function*() { - * const values = yield* Stream.suspend(() => Stream.make(1, 2, 3)).pipe(Stream.runCollect) - * yield* Console.log(values) - * }) + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" * - * Effect.runPromise(program) - * // Output: [ 1, 2, 3 ] + * await Effect.runPromise(Stream.suspend(() => Stream.make(1, 2, 3)).pipe(Stream.runCollect)) // => [1, 2, 3] * ``` * * @category constructors @@ -907,17 +795,10 @@ export const suspend = (stream: LazyArg>): Stream Exit.fail("Uh oh!") * ``` * * @category constructors @@ -930,19 +811,12 @@ export const fail = (error: E): Stream => fromChannel(Channel.fail( * * **Example** (Failing a stream lazily) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```ts import.meta.vitest + * import { Effect, Exit, Stream } from "effect" * * const stream = Stream.failSync(() => "Uh oh!") * - * const program = Effect.gen(function*() { - * const exit = yield* Stream.runCollect(stream).pipe(Effect.exit) - * yield* Console.log(exit) - * }) - * - * Effect.runPromise(program) - * // Output: - * // { _id: 'Exit', _tag: 'Failure', cause: { _id: 'Cause', _tag: 'Fail', failure: 'Uh oh!' } } + * await Effect.runPromise(Stream.runCollect(stream).pipe(Effect.exit)) // => Exit.fail("Uh oh!") * ``` * * @category constructors @@ -955,20 +829,14 @@ export const failSync = (evaluate: LazyArg): Stream => fromChann * * **Example** (Failing with a cause) * - * ```ts - * import { Cause, Console, Effect, Stream } from "effect" + * ```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")) * ) * - * const program = Effect.gen(function*() { - * const values = yield* Stream.runCollect(stream) - * yield* Console.log(values) - * // Output: [ "recovered" ] - * }) - * - * Effect.runPromise(program) + * await Effect.runPromise(Stream.runCollect(stream)) // => ["recovered"] * ``` * * @category constructors @@ -981,27 +849,13 @@ export const failCause = (cause: Cause.Cause): Stream => fromCha * * **Example** (Dying with a defect) * - * ```ts - * import { Cause, Console, Effect, Exit, Stream } from "effect" + * ```ts import.meta.vitest + * import { Cause, Effect, Exit, Stream } from "effect" * * const defect = new Error("Boom") * const stream = Stream.die(defect) * - * const program = Effect.gen(function*() { - * const exit = yield* Effect.exit(Stream.runCollect(stream)) - * const message = Exit.match(exit, { - * onSuccess: () => "Exit.Success", - * onFailure: (cause) => { - * const reason = cause.reasons[0] - * const defect = Cause.isDieReason(reason) ? String(reason.defect) : "Unexpected reason" - * return `Exit.Failure(${defect})` - * } - * }) - * yield* Console.log(message) - * }) - * - * Effect.runPromise(program) - * // Output: Exit.Failure(Error: Boom) + * await Effect.runPromise(Effect.exit(Stream.runCollect(stream))) // => Exit.failCause(Cause.die(defect)) * ``` * * @category constructors @@ -1014,21 +868,14 @@ export const die = (defect: unknown): Stream => fromChannel(Channel.die(d * * **Example** (Failing with a lazy cause) * - * ```ts - * import { Cause, Console, Effect, Stream } from "effect" + * ```ts import.meta.vitest + * import { Cause, Effect, Exit, Stream } from "effect" * * const stream = Stream.failCauseSync(() => * Cause.fail("Connection timeout after retries") * ) * - * const program = Effect.gen(function*() { - * const exit = yield* Stream.runCollect(stream).pipe(Effect.exit) - * yield* Console.log(exit) - * }) - * - * Effect.runPromise(program) - * // Output: - * // { _id: 'Exit', _tag: 'Failure', cause: { _id: 'Cause', _tag: 'Fail', failure: 'Connection timeout after retries' } } + * await Effect.runPromise(Stream.runCollect(stream).pipe(Effect.exit)) // => Exit.fail("Connection timeout after retries") * ``` * * @category constructors @@ -1046,8 +893,8 @@ export const failCauseSync = (evaluate: LazyArg>): Stream(evaluate: LazyArg>): Stream [1, 2, 3] * }) * - * Effect.runPromise(program) - * // Output: [ 1, 2, 3 ] + * await Effect.runPromise(program) * ``` * * @category constructors @@ -1081,19 +927,18 @@ export const fromIteratorSucceed = (iterator: IterableIterator, maxChunkSi * * **Example** (Creating a stream from an iterable) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```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) - * yield* Console.log(values) + * values // => [1, 2, 3] * }) * - * Effect.runPromise(program) - * // Output: [ 1, 2, 3 ] + * await Effect.runPromise(program) * ``` * * @category constructors @@ -1119,8 +964,8 @@ export const fromIterable = ( * * **Example** (Creating a stream from an iterable effect) * - * ```ts - * import { Console, Context, Effect, Stream } from "effect" + * ```ts import.meta.vitest + * import { Context, Effect, Stream } from "effect" * * class UserRepo extends Context.Service> @@ -1139,11 +984,10 @@ export const fromIterable = ( * }), * Stream.runCollect * ) - * yield* Console.log(users) + * users // => ["user1", "user2"] * }) * - * Effect.runPromise(program) - * // Output: [ "user1", "user2" ] + * await Effect.runPromise(program) * ``` * * @category constructors @@ -1157,19 +1001,18 @@ export const fromIterableEffect = (iterable: Effect.Effect, * * **Example** (Repeating an iterable effect) * - * ```ts - * import { Console, Effect, Stream } from "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) - * yield* Console.log(values) + * values // => [1, 2, 1, 2, 1] * }) * - * Effect.runPromise(program) - * // Output: [ 1, 2, 1, 2, 1 ] + * await Effect.runPromise(program) * ``` * * @category constructors @@ -1184,17 +1027,16 @@ export const fromIterableEffectRepeat = ( * * **Example** (Creating a stream from an array of values) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```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) - * yield* Console.log(values) + * values // => [1, 2, 3] * }) * - * Effect.runPromise(program) - * // Output: [ 1, 2, 3 ] + * await Effect.runPromise(program) * ``` * * @category constructors @@ -1213,17 +1055,16 @@ export const fromArray = (array: ReadonlyArray): Stream => * * **Example** (Creating a stream from an effect that produces an array of values) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```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) - * yield* Console.log(values) + * values // => ["Ada", "Grace"] * }) * - * Effect.runPromise(program) - * // Output: [ "Ada", "Grace" ] + * await Effect.runPromise(program) * ``` * * @category constructors @@ -1238,17 +1079,16 @@ export const fromArrayEffect = ( * * **Example** (Creating a stream from an arbitrary number of arrays) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```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) - * yield* Console.log(values) + * values // => [1, 2, 3, 4] * }) * - * Effect.runPromise(program) - * // Output: [ 1, 2, 3, 4 ] + * await Effect.runPromise(program) * ``` * * @category constructors @@ -1268,23 +1108,22 @@ export const fromArrays = >>( * * **Example** (Creating a stream from a queue of values) * - * ```ts - * import { Console, Effect, Queue, Stream } from "effect" + * ```ts import.meta.vitest + * import { Cause, Effect, Queue, Stream } from "effect" * * const program = Effect.gen(function*() { - * const queue = yield* Queue.unbounded() + * const queue = yield* Queue.unbounded() * yield* Queue.offer(queue, 1) * yield* Queue.offer(queue, 2) * yield* Queue.offer(queue, 3) - * yield* Queue.shutdown(queue) + * yield* Queue.end(queue) * * const stream = Stream.fromQueue(queue) * const values = yield* Stream.runCollect(stream) - * yield* Console.log(values) + * values // => [1, 2, 3] * }) * - * Effect.runPromise(program) - * // Output: [ 1, 2, 3 ] + * await Effect.runPromise(program) * ``` * * @category constructors @@ -1298,11 +1137,11 @@ export const fromQueue = (queue: Queue.Dequeue): Stream() + * const pubsub = yield* PubSub.unbounded({ replay: 3 }) * * const fiber = yield* Stream.fromPubSub(pubsub).pipe( * Stream.take(3), @@ -1315,11 +1154,10 @@ export const fromQueue = (queue: Queue.Dequeue): Stream [1, 2, 3] * }) * - * Effect.runPromise(program) - * // Output: [ 1, 2, 3 ] + * await Effect.runPromise(program) * ``` * * @category constructors @@ -1336,8 +1174,8 @@ export const fromPubSub = (pubsub: PubSub.PubSub): Stream => fromChanne * * **Example** (Creating a stream from PubSub takes) * - * ```ts - * import { Console, Effect, Exit, PubSub, Stream, Take } from "effect" + * ```ts import.meta.vitest + * import { Effect, Exit, PubSub, Stream, Take } from "effect" * * const program = Effect.gen(function*() { * const pubsub = yield* PubSub.unbounded>({ @@ -1349,11 +1187,10 @@ export const fromPubSub = (pubsub: PubSub.PubSub): Stream => fromChanne * yield* PubSub.publish(pubsub, Exit.succeed(undefined)) * * const values = yield* Stream.fromPubSubTake(pubsub).pipe(Stream.runCollect) - * yield* Console.log(values) + * values // => [1, 2] * }) * - * Effect.runPromise(program) - * // Output: [ 1, 2 ] + * await Effect.runPromise(program) * ``` * * @category constructors @@ -1373,8 +1210,8 @@ export const fromPubSubTake = (pubsub: PubSub.PubSub>): St * * **Example** (Creating a stream from a ReadableStream) * - * ```ts - * import { Console, Data, Effect, Stream } from "effect" + * ```ts import.meta.vitest + * import { Data, Effect, Stream } from "effect" * * class StreamError extends Data.TaggedError("StreamError")<{ readonly cause: unknown }> {} * @@ -1393,11 +1230,10 @@ export const fromPubSubTake = (pubsub: PubSub.PubSub>): St * onError: (cause) => new StreamError({ cause }) * }) * const values = yield* Stream.runCollect(stream) - * yield* Console.log(values) + * values // => [1, 2, 3] * }) * - * Effect.runPromise(program) - * // Output: [ 1, 2, 3 ] + * await Effect.runPromise(program) * ``` * * @category constructors @@ -1409,30 +1245,14 @@ export const fromReadableStream = ( readonly onError: (error: unknown) => E readonly releaseLockOnEnd?: boolean | undefined } -): Stream => - fromChannel(Channel.fromTransform(Effect.fnUntraced(function*(_, scope) { - const reader = options.evaluate().getReader() - yield* Scope.addFinalizer( - scope, - options.releaseLockOnEnd - ? Effect.sync(() => reader.releaseLock()) - : Effect.promise(() => reader.cancel().catch(constVoid)) - ) - return Effect.flatMap( - Effect.tryPromise({ - try: () => reader.read(), - catch: (reason) => options.onError(reason) - }), - ({ done, value }) => done ? Cause.done() : Effect.succeed(Arr.of(value)) - ) - }))) +): Stream => fromChannel(Channel.fromReadableStream(options)) /** * Creates a stream from an AsyncIterable. * * **Example** (Creating a stream from an AsyncIterable) * - * ```ts + * ```ts import.meta.vitest * import { Data, Effect, Stream } from "effect" * * class StreamError extends Data.TaggedError("StreamError")<{ readonly cause: unknown }> {} @@ -1443,13 +1263,12 @@ export const fromReadableStream = ( * yield 3 * })() * - * Effect.runPromise(Effect.gen(function*() { + * await Effect.runPromise(Effect.gen(function*() { * const stream = Stream.fromAsyncIterable(iterable, (cause) => new StreamError({ cause })) * const values = yield* Stream.runCollect(stream) - * yield* Effect.sync(() => console.log(values)) + * values // => [1, 2, 3] * })) * - * // [ 1, 2, 3 ] * ``` * * @category constructors @@ -1466,20 +1285,17 @@ export const fromAsyncIterable = ( * * **Example** (Creating a stream from a schedule) * - * ```ts - * import { Console, Effect, Schedule, Stream } from "effect" + * ```ts import.meta.vitest + * import { Effect, Schedule, Stream } from "effect" * * const program = Effect.gen(function*() { - * const schedule = Schedule.spaced("50 millis").pipe( - * Schedule.both(Schedule.recurs(2)) - * ) + * const schedule = Schedule.recurs(3) * const stream = Stream.fromSchedule(schedule) * const values = yield* Stream.runCollect(stream) - * yield* Console.log(values) + * values // => [0, 1, 2] * }) * - * Effect.runPromise(program) - * // Output: [ 0, 1, 2 ] + * await Effect.runPromise(program) * ``` * * @category constructors @@ -1504,8 +1320,8 @@ export const fromSchedule = (schedule: Schedule.Schedule() @@ -1516,11 +1332,10 @@ export const fromSchedule = (schedule: Schedule.Schedule [1, 2] * })) * - * Effect.runPromise(program) - * // Output: [ 1, 2 ] + * await Effect.runPromise(program) * ``` * * @category constructors @@ -1560,7 +1375,7 @@ export interface EventListener { * * **Example** (Creating a stream from an event listener) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * class NumberTarget implements Stream.EventListener { @@ -1574,15 +1389,14 @@ export interface EventListener { * removeEventListener(_event: string, _f: (event: number) => void) {} * } * - * Effect.runPromise(Effect.gen(function*() { + * await Effect.runPromise(Effect.gen(function*() { * const stream = Stream.fromEventListener(new NumberTarget(), "data").pipe( * Stream.take(3) * ) * const values = yield* Stream.runCollect(stream) - * yield* Effect.sync(() => console.log(values)) + * values // => [1, 2, 3] * })) * - * // [ 1, 2, 3 ] * ``` * * @category constructors @@ -1619,17 +1433,16 @@ export const fromEventListener = ( * * **Example** (Unfolding stream state) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```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))) - * yield* Console.log(values) + * values // => [ 1, 2, 3, 4, 5 ] * }) * - * Effect.runPromise(program) - * // Output: [ 1, 2, 3, 4, 5 ] + * await Effect.runPromise(program) * ``` * * @category constructors @@ -1663,8 +1476,8 @@ export const unfold = ( * * **Example** (Paginating stream state) * - * ```ts - * import { Console, Effect, Option, Stream } from "effect" + * ```ts import.meta.vitest + * import { Effect, Option, Stream } from "effect" * * const stream = Stream.paginate(0, (n: number) => * Effect.succeed( @@ -1674,8 +1487,7 @@ export const unfold = ( * ] as const * )) * - * Effect.runPromise(Stream.runCollect(stream)).then(console.log) - * // Output: [ 0, 1, 2, 3 ] + * await Effect.runPromise(Stream.runCollect(stream)) // => [0, 1, 2, 3] * ``` * * @category constructors @@ -1709,18 +1521,17 @@ export const paginate = ( * * **Example** (Iterating from a seed value) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```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) - * yield* Console.log(values) + * values // => [ 1, 2, 3 ] * }) * - * Effect.runPromise(program) - * // Output: [ 1, 2, 3 ] + * await Effect.runPromise(program) * ``` * * @category constructors @@ -1739,16 +1550,15 @@ export const iterate = (value: A, next: (value: A) => A): Stream => * * **Example** (Creating a numeric range) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const values = yield* Stream.range(1, 5).pipe(Stream.runCollect) - * yield* Console.log(values) + * values // => [ 1, 2, 3, 4, 5 ] * }) * - * Effect.runPromise(program) - * // Output: [ 1, 2, 3, 4, 5 ] + * await Effect.runPromise(program) * ``` * * @category constructors @@ -1760,14 +1570,15 @@ export const range = ( 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 > chunkSize) { - const chunk = Arr.range(start, start + chunkSize - 1) - start += chunkSize + 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) @@ -1781,7 +1592,7 @@ export const range = ( * * **Example** (Creating a never-ending stream) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * * const program = Stream.never.pipe( @@ -1789,8 +1600,7 @@ export const range = ( * Stream.runCollect * ) * - * Effect.runPromise(program).then(console.log) - * // [] + * await Effect.runPromise(program) // => [] * ``` * * @category constructors @@ -1803,8 +1613,8 @@ export const never: Stream = fromChannel(Channel.never) * * **Example** (Unwrapping a stream effect) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" * * const effect = Effect.succeed(Stream.make(1, 2, 3)) * @@ -1812,9 +1622,9 @@ export const never: Stream = fromChannel(Channel.never) * * const program = Effect.gen(function*() { * const chunk = yield* Stream.runCollect(stream) - * yield* Console.log(chunk) + * chunk // => [ 1, 2, 3 ] * }) - * // [1, 2, 3] + * await Effect.runPromise(program) * ``` * * @category constructors @@ -1830,22 +1640,24 @@ export const unwrap = ( * * **Example** (Scoping a stream) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" * + * const events: Array = [] * const stream = Stream.scoped( * Stream.fromEffect( * Effect.acquireRelease( - * Console.log("acquire").pipe(Effect.as("resource")), - * () => Console.log("release") + * Effect.sync(() => { + * events.push("acquire") + * return "resource" + * }), + * () => Effect.sync(() => events.push("release")) * ) * ) * ) * - * Effect.runPromise(Stream.runCollect(stream)).then(console.log) - * // acquire - * // release - * // [ "resource" ] + * await Effect.runPromise(Stream.runCollect(stream)) // => ["resource"] + * events // => ["acquire", "release"] * ``` * * @category constructors @@ -1860,16 +1672,11 @@ export const scoped = ( * * **Example** (Mapping stream values) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```ts import.meta.vitest + * import { Effect, Option, Stream } from "effect" * * const stream = Stream.fromArray([1, 2, 3]).pipe(Stream.map((n, i) => n + i)) - * const program = Stream.runCollect(stream).pipe( - * Effect.tap((values) => Console.log(values)) - * ) - * - * Effect.runPromise(program) - * // [ 1, 3, 5 ] + * await Effect.runPromise(Stream.runCollect(stream)) // => [1, 3, 5] * ``` * * @category mapping @@ -1892,8 +1699,8 @@ export const map: { * * **Example** (Mapping both the failure and success channels of a stream) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" * * const mapper = { * onFailure: (error: string) => `error: ${error}`, @@ -1905,19 +1712,17 @@ export const map: { * Stream.mapBoth(mapper), * Stream.runCollect * ) - * yield* Console.log(success) + * success // => [ 2, 4 ] * * const failure = yield* Stream.fail("boom").pipe( * Stream.mapBoth(mapper), * Stream.catch((error: string) => Stream.succeed(error)), * Stream.runCollect * ) - * yield* Console.log(failure) + * failure // => [ 'error: boom' ] * }) * - * Effect.runPromise(program) - * // Output: [ 2, 4 ] - * // Output: [ "error: boom" ] + * await Effect.runPromise(program) * ``` * * @category mapping @@ -1945,8 +1750,8 @@ export const mapBoth: { * * **Example** (Mapping stream chunks) * - * ```ts - * import { Array, Console, Effect, Stream } from "effect" + * ```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( @@ -1954,11 +1759,10 @@ export const mapBoth: { * Stream.mapArray((chunk, index) => Array.map(chunk, (n) => n + index)), * Stream.runCollect * ) - * yield* Console.log(result) + * result // => [ 1, 2, 4, 5 ] * }) * - * Effect.runPromise(program) - * // Output: [ 1, 2, 4, 5 ] + * await Effect.runPromise(program) * ``` * * @category mapping @@ -1987,15 +1791,16 @@ export const mapArray: { * * **Example** (Effectfully mapping stream values) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```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.gen(function*() { - * yield* Console.log(`Processing: ${n}`) + * Effect.sync(() => { + * events.push(`Processing: ${n}`) * return n * 2 * }) * ) @@ -2003,15 +1808,11 @@ export const mapArray: { * * const program = Effect.gen(function*() { * const result = yield* Stream.runCollect(mappedStream) - * yield* Console.log(result) + * result // => [2, 4, 6] * }) * - * Effect.runPromise(program) - * // Output: - * // Processing: 1 - * // Processing: 2 - * // Processing: 3 - * // [2, 4, 6] + * await Effect.runPromise(program) + * events // => ["Processing: 1", "Processing: 2", "Processing: 3"] * ``` * * @category mapping @@ -2058,18 +1859,17 @@ export const mapEffect: { * * **Example** (Flattening a stream of Effect values into a stream of their results) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```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())) - * yield* Console.log(result) + * result // => [ 1, 2, 3 ] * }) * - * Effect.runPromise(program) - * // Output: [1, 2, 3] + * await Effect.runPromise(program) * ``` * * @category mapping @@ -2112,8 +1912,8 @@ export const flattenEffect: < * * **Example** (Effectfully mapping stream chunks) * - * ```ts - * import { Array, Console, Effect, Stream } from "effect" + * ```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( @@ -2123,11 +1923,10 @@ export const flattenEffect: < * ), * Stream.runCollect * ) - * yield* Console.log(result) + * result // => [ 1, 2, 13, 14 ] * }) * - * Effect.runPromise(program) - * // Output: [1, 2, 13, 14] + * await Effect.runPromise(program) * ``` * * @category mapping @@ -2155,8 +1954,8 @@ export const mapArrayEffect: { * * **Example** (Converting failures to results) * - * ```ts - * import { Console, Effect, Result, Stream } from "effect" + * ```ts import.meta.vitest + * import { Effect, Result, Stream } from "effect" * * const program = Effect.gen(function*() { * const results = yield* Stream.make(1, 2).pipe( @@ -2168,11 +1967,10 @@ export const mapArrayEffect: { * })), * Stream.runCollect * ) - * yield* Console.log(results) + * results // => [ 'success: 1', 'success: 2', 'failure: boom' ] * }) * - * Effect.runPromise(program) - * // Output: [ "success: 1", "success: 2", "failure: boom" ] + * await Effect.runPromise(program) * ``` * * @category error handling @@ -2189,29 +1987,23 @@ export const result = (self: Stream): Stream = [] * const program = Effect.gen(function*() { * const result = yield* Stream.fromArray([1, 2, 3]).pipe( - * Stream.tap((n) => Console.log(`before mapping: ${n}`)), + * Stream.tap((n) => Effect.sync(() => events.push(`before mapping: ${n}`))), * Stream.map((n) => n * 2), - * Stream.tap((n) => Console.log(`after mapping: ${n}`)), + * Stream.tap((n) => Effect.sync(() => events.push(`after mapping: ${n}`))), * Stream.runCollect * ) * - * yield* Console.log(result) + * result // => [2, 4, 6] * }) * - * Effect.runPromise(program) - * // Output: - * // before mapping: 1 - * // after mapping: 2 - * // before mapping: 2 - * // after mapping: 4 - * // before mapping: 3 - * // after mapping: 6 - * // [ 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 @@ -2249,28 +2041,25 @@ export const tap: { * * **Example** (Tapping values and errors) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```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) => Console.log(`seen: ${value}`), - * onError: (error) => Console.log(`error: ${error}`) + * 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) - * yield* Console.log(result) + * result // => [1, 2, 3] * }) * - * Effect.runPromise(program) - * // Output: - * // seen: 1 - * // seen: 2 - * // error: boom - * // [ 1, 2, 3 ] + * await Effect.runPromise(program) + * events // => ["seen: 1", "seen: 2", "error: boom"] * ``` * * @category sequencing @@ -2310,8 +2099,8 @@ export const tapBoth: { * * **Example** (Tapping values with a sink) * - * ```ts - * import { Console, Effect, Ref, Sink, Stream } from "effect" + * ```ts import.meta.vitest + * import { Effect, Ref, Sink, Stream } from "effect" * * const program = Effect.gen(function*() { * const seen = yield* Ref.make>([]) @@ -2323,13 +2112,11 @@ export const tapBoth: { * Stream.runCollect * ) * const tapped = yield* Ref.get(seen) - * yield* Console.log(tapped) - * yield* Console.log(result) + * tapped // => [ 1, 2, 3 ] + * result // => [ 1, 2, 3 ] * }) * - * Effect.runPromise(program) - * // Output: [1, 2, 3] - * // Output: [1, 2, 3] + * await Effect.runPromise(program) * ``` * * @category sequencing @@ -2415,19 +2202,18 @@ export const tapSink: { * * **Example** (Flat mapping stream values) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```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 * ) - * yield* Console.log(values) + * values // => [ 1, 2, 2, 4, 3, 6 ] * }) * - * Effect.runPromise(program) - * // Output: [ 1, 2, 2, 4, 3, 6 ] + * await Effect.runPromise(program) * ``` * * @category mapping @@ -2469,19 +2255,18 @@ export const flatMap: { * * **Example** (Switching to the latest stream) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```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 * ) * - * Effect.gen(function*() { + * await Effect.runPromise(Effect.gen(function*() { * const result = yield* program - * yield* Console.log(result) - * // Output: [ 3 ] - * }) + * result // => [ 3 ] + * })) * ``` * * @category sequencing @@ -2529,8 +2314,8 @@ export const switchMap: { * * **Example** (Flattening nested streams) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" * * const streamOfStreams = Stream.make( * Stream.make(1, 2), @@ -2540,11 +2325,10 @@ export const switchMap: { * * const program = Effect.gen(function*() { * const values = yield* Stream.runCollect(Stream.flatten(streamOfStreams)) - * yield* Console.log(values) + * values // => [ 1, 2, 3, 4, 5, 6 ] * }) * - * Effect.runPromise(program) - * // Output: [ 1, 2, 3, 4, 5, 6 ] + * await Effect.runPromise(program) * ``` * * @category mapping @@ -2581,18 +2365,17 @@ export const flatten: < * * **Example** (Flattening a stream of non-empty arrays into a stream of elements) * - * ```ts - * import { Array, Console, Effect, Stream } from "effect" + * ```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)) - * yield* Console.log(result) + * result // => [ 1, 2, 3 ] * }) * - * Effect.runPromise(program) - * // Output: [ 1, 2, 3 ] + * await Effect.runPromise(program) * ``` * * @category sequencing @@ -2606,16 +2389,15 @@ export const flattenArray = (self: Stream, * * **Example** (Draining stream values) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```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) - * yield* Console.log(result) + * result // => [] * }) * - * Effect.runPromise(program) - * // Output: [] + * await Effect.runPromise(program) * ``` * * @category sequencing @@ -2629,23 +2411,23 @@ export const drain = (self: Stream): Stream => fr * * **Example** (Draining a stream in the background) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" * + * const events: Array = [] * const foreground = Stream.make(1, 2) - * const background = Stream.fromEffect(Console.log("background task")) + * 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 * ) - * yield* Console.log(values) + * values // => [1, 2] * }) * - * Effect.runPromise(program) - * // Output: background task - * // Output: [ 1, 2 ] + * await Effect.runPromise(program) + * events // => ["background task"] * ``` * * @category sequencing @@ -2665,8 +2447,8 @@ export const drainFork: { * * **Example** (Repeating a stream on a schedule) * - * ```ts - * import { Console, Effect, Schedule, Stream } from "effect" + * ```ts import.meta.vitest + * import { Effect, Schedule, Stream } from "effect" * * const program = Effect.gen(function* () { * const result = yield* Stream.make(1).pipe( @@ -2674,11 +2456,10 @@ export const drainFork: { * Stream.runCollect * ) * - * yield* Console.log(result) + * result // => [ 1, 1, 1, 1, 1 ] * }) * - * Effect.runPromise(program) - * // Output: [ 1, 1, 1, 1, 1 ] + * await Effect.runPromise(program) * ``` * * @category sequencing @@ -2714,20 +2495,19 @@ export const repeat: { * * **Example** (Scheduling stream elements) * - * ```ts - * import { Console, Effect, Schedule, Stream } from "effect" + * ```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.spaced("10 millis")), + * Stream.schedule(Schedule.recurs(3)), * Stream.runCollect * ) * - * yield* Console.log(result) + * result // => [ 1, 2, 3 ] * }) * - * Effect.runPromise(program) - * // Output: [ 1, 2, 3 ] + * await Effect.runPromise(program) * ``` * * @category rate limiting @@ -2757,8 +2537,8 @@ export const schedule: { * * **Example** (Timing out a stream) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const values = yield* Stream.make(1).pipe( @@ -2766,14 +2546,13 @@ export const schedule: { * Stream.timeout("1 second"), * Stream.runCollect * ) - * yield* Console.log(values) + * values // => [ 1 ] * }) * - * Effect.runPromise(program) - * // Output: [ 1 ] + * await Effect.runPromise(program) * ``` * - * @category rate limiting + * @category delays & timeouts * @since 2.0.0 */ export const timeout: { @@ -2809,7 +2588,7 @@ export const timeout: { * * @see {@link timeout} for ending the stream instead of switching to a fallback stream * - * @category rate limiting + * @category delays & timeouts * @since 4.0.0 */ export const timeoutOrElse: { @@ -2887,19 +2666,18 @@ export const timeoutOrElse: { * * **Example** (Repeating stream elements) * - * ```ts - * import { Console, Effect, Schedule, Stream } from "effect" + * ```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 * ) - * yield* Console.log(values) + * values // => [ 'A', 'A', 'B', 'B', 'C', 'C' ] * }) * - * Effect.runPromise(program) - * // Output: [ "A", "A", "B", "B", "C", "C" ] + * await Effect.runPromise(program) * ``` * * @category sequencing @@ -2955,8 +2733,8 @@ export const repeatElements: { * * **Example** (Repeating a stream forever) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" * * const stream = Stream.make("A", "B").pipe( * Stream.forever, @@ -2965,11 +2743,10 @@ export const repeatElements: { * * const program = Effect.gen(function*() { * const output = yield* Stream.runCollect(stream) - * yield* Console.log(output) + * output // => [ 'A', 'B', 'A', 'B', 'A' ] * }) * - * Effect.runPromise(program) - * // Output: [ "A", "B", "A", "B", "A" ] + * await Effect.runPromise(program) * ``` * * @category sequencing @@ -2982,17 +2759,16 @@ export const forever = (self: Stream): Stream => from * * **Example** (Flattening iterable values) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```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) - * yield* Console.log(values) + * values // => [ 1, 2, 3, 4 ] * }) * - * Effect.runPromise(program) - * // Output: [ 1, 2, 3, 4 ] + * await Effect.runPromise(program) * ``` * * @category mapping @@ -3007,8 +2783,8 @@ export const flattenIterable = (self: Stream, E, R>): Strea * * **Example** (Flattening Take values) * - * ```ts - * import { Array, Console, Effect, Exit, Stream } from "effect" + * ```ts import.meta.vitest + * import { Array, Effect, Exit, Stream } from "effect" * * const program = Effect.gen(function*() { * const takes = Stream.make( @@ -3018,11 +2794,10 @@ export const flattenIterable = (self: Stream, E, R>): Strea * ) * * const values = yield* Stream.flattenTake(takes).pipe(Stream.runCollect) - * yield* Console.log(values) + * values // => [ 1, 2, 3 ] * }) * - * Effect.runPromise(program) - * // Output: [ 1, 2, 3 ] + * await Effect.runPromise(program) * ``` * * @category sequencing @@ -3041,16 +2816,15 @@ export const flattenTake = (self: Stream, E2, R>): * * **Example** (Concatenating streams) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" * * const stream = Stream.concat(Stream.make(1, 2, 3), Stream.make(4, 5, 6)) * - * Effect.gen(function*() { + * await Effect.runPromise(Effect.gen(function*() { * const values = yield* Stream.runCollect(stream) - * yield* Console.log(values) - * }) - * // Output: [ 1, 2, 3, 4, 5, 6 ] + * values // => [ 1, 2, 3, 4, 5, 6 ] + * })) * ``` * * @category sequencing @@ -3070,8 +2844,8 @@ export const concat: { * * **Example** (Prepending values) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const values = yield* Stream.make(3, 4).pipe( @@ -3079,11 +2853,10 @@ export const concat: { * Stream.runCollect * ) * - * yield* Console.log(values) - * // Output: [ 1, 2, 3, 4 ] + * values // => [ 1, 2, 3, 4 ] * }) * - * Effect.runPromise(program) + * await Effect.runPromise(program) * ``` * * @category sequencing @@ -3107,19 +2880,18 @@ export const prepend: { * * **Example** (Merging stream values) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```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)) - * yield* Console.log(result) + * result // => [ 1, 2, 3, 4 ] * }) * - * Effect.runPromise(program) - * // Output: [ 1, 2, 3, 4 ] + * await Effect.runPromise(program) * ``` * * @category merging @@ -3165,21 +2937,21 @@ export const merge: { * * **Example** (Merging with a background effect) * - * ```ts - * import { Console, Effect, Stream } from "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(Console.log("side task")), + * Stream.mergeEffect(Effect.sync(() => events.push("side task"))), * Stream.runCollect * ) * - * yield* Console.log(values) + * values // => [1, 2, 3] * }) * - * Effect.runPromise(program) - * // Output: side task - * // Output: [ 1, 2, 3 ] + * await Effect.runPromise(program) + * events // => ["side task"] * ``` * * @category merging @@ -3209,8 +2981,8 @@ export const mergeEffect: { * * **Example** (Merging streams into results) * - * ```ts - * import { Console, Effect, Result, Stream } from "effect" + * ```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")) @@ -3227,11 +2999,10 @@ export const mergeEffect: { * * const program = Effect.gen(function*() { * const result = yield* Stream.runCollect(merged) - * yield* Console.log(result) + * result // => [ 'left:left', 'right:right' ] * }) * - * Effect.runPromise(program) - * // Output: [ "left:left", "right:right" ] + * await Effect.runPromise(program) * ``` * * @category merging @@ -3270,18 +3041,17 @@ export const mergeResult: { * * **Example** (Merging streams while keeping left values) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```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) - * yield* Console.log(values) + * values // => [ 1, 2 ] * }) * - * Effect.runPromise(program) - * // Output: [ 1, 2 ] + * await Effect.runPromise(program) * ``` * * @category merging @@ -3312,8 +3082,8 @@ export const mergeLeft: { * * **Example** (Merging streams while keeping right values) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" * * const left = Stream.make("left-1", "left-2").pipe( * Stream.tap(() => Effect.sync(() => undefined)) @@ -3324,11 +3094,10 @@ export const mergeLeft: { * * const program = Effect.gen(function*() { * const result = yield* Stream.runCollect(merged) - * yield* Console.log(result) + * result // => [ 1, 2 ] * }) * - * Effect.runPromise(program) - * // Output: [ 1, 2 ] + * await Effect.runPromise(program) * ``` * * @category merging @@ -3359,8 +3128,8 @@ export const mergeRight: { * * **Example** (Merging streams with bounded concurrency) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" * * const streams = [ * Stream.fromEffect(Effect.delay(Effect.succeed("A"), "20 millis")), @@ -3371,11 +3140,10 @@ export const mergeRight: { * const values = yield* Stream.mergeAll(streams, { concurrency: 2 }).pipe( * Stream.runCollect * ) - * yield* Console.log(values) + * values // => [ 'B', 'A' ] * }) * - * Effect.runPromise(program) - * // Output: [ "B", "A" ] + * await Effect.runPromise(program) * ``` * * @see {@link merge} for merging exactly two streams and choosing a halt strategy @@ -3416,18 +3184,17 @@ export const mergeAll: { * * **Example** (Computing cartesian products) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```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)) - * yield* Console.log(values) + * values // => [ [ 1, 'a' ], [ 1, 'b' ], [ 2, 'a' ], [ 2, 'b' ] ] * }) * - * Effect.runPromise(program) - * // Output: [ [ 1, "a" ], [ 1, "b" ], [ 2, "a" ], [ 2, "b" ] ] + * await Effect.runPromise(program) * ``` * * @category zipping @@ -3452,19 +3219,18 @@ export const cross: { * * **Example** (Combining cartesian products) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```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) - * yield* Console.log(result) + * result // => [ '1-a', '1-b', '2-a', '2-b' ] * }) * - * Effect.runPromise(program) - * // Output: [ "1-a", "1-b", "2-a", "2-b" ] + * await Effect.runPromise(program) * ``` * * @category zipping @@ -3491,8 +3257,8 @@ export const crossWith: { * * **Example** (Zipping streams with a function) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```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") @@ -3501,11 +3267,10 @@ export const crossWith: { * * const program = Effect.gen(function*() { * const result = yield* Stream.runCollect(zipped) - * yield* Console.log(result) + * result // => [ '1-a', '2-b', '3-c' ] * }) * - * Effect.runPromise(program) - * // Output: [ "1-a", "2-b", "3-c" ] + * await Effect.runPromise(program) * ``` * * @category zipping @@ -3553,8 +3318,8 @@ const zipArrays = ( * * **Example** (Zipping stream chunks) * - * ```ts - * import { Array, Console, Effect, Stream } from "effect" + * ```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"]) @@ -3568,11 +3333,10 @@ const zipArrays = ( * * const program = Effect.gen(function*() { * const result = yield* Stream.runCollect(zipped) - * yield* Console.log(result) + * result // => [ [ 1, 'a' ], [ 2, 'b' ], [ 3, 'c' ], [ 4, 'd' ], [ 5, 'e' ] ] * }) * - * Effect.runPromise(program) - * // Output: [[1, "a"], [2, "b"], [3, "c"], [4, "d"], [5, "e"]] + * await Effect.runPromise(program) * ``` * * @category zipping @@ -3662,8 +3426,8 @@ export const zipWithArray: { * * **Example** (Zipping streams) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" * * const stream1 = Stream.make(1, 2, 3) * const stream2 = Stream.make("a", "b", "c") @@ -3672,11 +3436,10 @@ export const zipWithArray: { * * const program = Effect.gen(function*() { * const result = yield* Stream.runCollect(zipped) - * yield* Console.log(result) + * result // => [ [ 1, 'a' ], [ 2, 'b' ], [ 3, 'c' ] ] * }) * - * Effect.runPromise(program) - * // Output: [[1, "a"], [2, "b"], [3, "c"]] + * await Effect.runPromise(program) * ``` * * @category zipping @@ -3703,19 +3466,18 @@ export const zip: { * * **Example** (Zipping streams while keeping left values) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```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) - * yield* Console.log(result) + * result // => [ 1, 2 ] * }) * - * Effect.runPromise(program) - * // Output: [1, 2] + * await Effect.runPromise(program) * ``` * * @category zipping @@ -3745,19 +3507,18 @@ export const zipLeft: { * * **Example** (Zipping streams while keeping right values) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```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) - * yield* Console.log(result) + * result // => [ 'a', 'b' ] * }) * - * Effect.runPromise(program) - * // Output: ["a", "b"] + * await Effect.runPromise(program) * ``` * * @category zipping @@ -3792,8 +3553,8 @@ export const zipRight: { * * **Example** (Zipping and flattening tuples) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const stream1 = Stream.make( @@ -3804,11 +3565,10 @@ export const zipRight: { * const stream2 = Stream.make("x", "y", "z") * const result = yield* Stream.zipFlatten(stream1, stream2).pipe(Stream.runCollect) * - * yield* Console.log(result) + * result // => [ [ 1, 'a', 'x' ], [ 2, 'b', 'y' ], [ 3, 'c', 'z' ] ] * }) * - * Effect.runPromise(program) - * // Output: [[1, "a", "x"], [2, "b", "y"], [3, "c", "z"]] + * await Effect.runPromise(program) * ``` * * @category zipping @@ -3835,19 +3595,18 @@ export const zipFlatten: { * * **Example** (Zipping elements with indices) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```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 * ) - * yield* Console.log(indexed) + * indexed // => [ [ 'a', 0 ], [ 'b', 1 ], [ 'c', 2 ], [ 'd', 3 ] ] * }) * - * Effect.runPromise(program) - * // Output: [["a", 0], ["b", 1], ["c", 2], ["d", 3]] + * await Effect.runPromise(program) * ``` * * @category zipping @@ -3861,21 +3620,15 @@ export const zipWithIndex = (self: Stream): Stream<[A, number] * * **Example** (Zipping elements with next values) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```ts import.meta.vitest + * import { Effect, Option, Stream } from "effect" * * const stream = Stream.zipWithNext(Stream.make(1, 2, 3, 4)) * - * Effect.runPromise(Effect.gen(function*() { + * await Effect.runPromise(Effect.gen(function*() { * const values = yield* Stream.runCollect(stream) - * yield* Console.log(values) + * values // => [[1, Option.some(2)], [2, Option.some(3)], [3, Option.some(4)], [4, Option.none()]] * })) - * // Output: [ - * // [ 1, { _id: 'Option', _tag: 'Some', value: 2 } ], - * // [ 2, { _id: 'Option', _tag: 'Some', value: 3 } ], - * // [ 3, { _id: 'Option', _tag: 'Some', value: 4 } ], - * // [ 4, { _id: 'Option', _tag: 'None' } ] - * // ] * ``` * * @category zipping @@ -3906,23 +3659,17 @@ export const zipWithNext = (self: Stream): Stream<[A, Option.O * * **Example** (Zipping elements with previous values) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```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) - * yield* Console.log(result) + * result // => [[Option.none(), 1], [Option.some(1), 2], [Option.some(2), 3], [Option.some(3), 4]] * }) * - * Effect.runPromise(program) - * // Output: [ - * // [ { _id: 'Option', _tag: 'None' }, 1 ], - * // [ { _id: 'Option', _tag: 'Some', value: 1 }, 2 ], - * // [ { _id: 'Option', _tag: 'Some', value: 2 }, 3 ], - * // [ { _id: 'Option', _tag: 'Some', value: 3 }, 4 ] - * // ] + * await Effect.runPromise(program) * ``` * * @category zipping @@ -3944,7 +3691,7 @@ export const zipWithPrevious = (self: Stream): Stream<[Option. * * **Example** (Zipping elements with neighbors) * - * ```ts + * ```ts import.meta.vitest * import { Console, Effect, Option, Stream } from "effect" * * const program = Effect.gen(function*() { @@ -3952,11 +3699,10 @@ export const zipWithPrevious = (self: Stream): Stream<[Option. * Stream.zipWithPreviousAndNext, * Stream.runCollect * ) - * yield* Console.log(values) + * values // => [[Option.none(), 1, Option.some(2)], [Option.some(1), 2, Option.some(3)], [Option.some(2), 3, Option.none()]] * }) * - * Effect.runPromise(program) - * // Output: [ [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 @@ -4010,8 +3756,8 @@ export const zipWithPreviousAndNext = ( * * **Example** (Zipping latest values from many streams) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" * * const stream = Stream.zipLatestAll( * Stream.make(1, 2, 3).pipe(Stream.rechunk(1)), @@ -4021,11 +3767,10 @@ export const zipWithPreviousAndNext = ( * * const program = Effect.gen(function*() { * const result = yield* Stream.runCollect(stream) - * yield* Console.log(result) + * result // => [[1, "a", true], [2, "a", true], [2, "b", true], [2, "b", false], [3, "b", false], [3, "c", false], [3, "c", true]] * }) * - * Effect.runPromise(program) - * // Output: [ [ 1, "a", true ], [ 2, "a", true ], [ 3, "a", true ], [ 3, "b", true ], [ 3, "c", true ], [ 3, "c", false ], [ 3, "c", true ] ] + * await Effect.runPromise(program) * ``` * * @category zipping @@ -4086,8 +3831,8 @@ export const zipLatestAll = >>( * * **Example** (Zipping latest values) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const result = yield* Stream.zipLatest( @@ -4095,9 +3840,9 @@ export const zipLatestAll = >>( * Stream.make("a") * ).pipe(Stream.runCollect) * - * yield* Console.log(result) + * result // => [ [ 1, 'a' ] ] * }) - * // Output: [ [1, "a"] ] + * await Effect.runPromise(program) * ``` * * @category zipping @@ -4136,10 +3881,10 @@ export const zipLatest: { * * **Example** (Zipping latest values with a function) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" * - * Effect.gen(function*() { + * await Effect.runPromise(Effect.gen(function*() { * const result = yield* Stream.make(1, 2, 3).pipe( * Stream.rechunk(1), * Stream.zipLatestWith( @@ -4149,9 +3894,8 @@ export const zipLatest: { * Stream.runCollect * ) * - * yield* Console.log(result) - * // Output: [ 11, 12, 22, 23 ] - * }) + * result // => [ 11, 12, 22, 23 ] + * })) * ``` * * @category zipping @@ -4188,19 +3932,18 @@ export const zipLatestWith: { * * **Example** (Racing multiple streams) * - * ```ts - * import { Console, Effect, Schedule, Stream } from "effect" + * ```ts import.meta.vitest + * import { Effect, Schedule, Stream } from "effect" * * const program = Effect.gen(function*() { * const result = yield* Stream.raceAll( - * Stream.fromSchedule(Schedule.spaced("1 second")), + * Stream.empty, * Stream.make(0, 1, 2) * ).pipe(Stream.runCollect) - * yield* Console.log(result) + * result // => [ 0, 1, 2 ] * }) * - * Effect.runPromise(program) - * // Output: [ 0, 1, 2 ] + * await Effect.runPromise(program) * ``` * * @category racing @@ -4247,21 +3990,20 @@ export const raceAll = >>( * * **Example** (Racing two streams) * - * ```ts - * import { Console, Effect, Schedule, Stream } from "effect" + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" * * const stream = Stream.race( - * Stream.make(0, 1, 2), - * Stream.fromSchedule(Schedule.spaced("1 second")) + * Stream.empty, + * Stream.make(0, 1, 2) * ) * * const program = Effect.gen(function*() { * const result = yield* Stream.runCollect(stream) - * yield* Console.log(result) + * result // => [ 0, 1, 2 ] * }) * - * Effect.runPromise(program) - * // Output: [ 0, 1, 2 ] + * await Effect.runPromise(program) * ``` * * @category racing @@ -4285,19 +4027,18 @@ export const race: { * * **Example** (Filtering stream values) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```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) - * yield* Console.log(values) + * values // => [ 2, 4 ] * }) * - * Effect.runPromise(program) - * // Output: [ 2, 4 ] + * await Effect.runPromise(program) * ``` * * @category filtering @@ -4359,18 +4100,17 @@ export const filterMap: { * * **Example** (Effectfully filtering stream values) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```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) - * yield* Console.log(result) + * result // => [ 3, 4 ] * }) * - * Effect.runPromise(program) - * // Output: [ 3, 4 ] + * await Effect.runPromise(program) * ``` * * @category filtering @@ -4440,8 +4180,8 @@ export const filterMapEffect: { * * **Example** (Partitioning a stream into queues) * - * ```ts - * import { Console, Effect, Result, Stream } from "effect" + * ```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( @@ -4451,13 +4191,11 @@ export const filterMapEffect: { * const passValues = yield* Stream.fromQueue(passes).pipe(Stream.runCollect) * const failValues = yield* Stream.fromQueue(fails).pipe(Stream.runCollect) * - * yield* Console.log(passValues) - * // Output: [ 2, 4 ] - * yield* Console.log(failValues) - * // Output: [ 1, 3 ] + * passValues // => [ 2, 4 ] + * failValues // => [ 1, 3 ] * }) * - * Effect.runPromise(Effect.scoped(program)) + * await Effect.runPromise(Effect.scoped(program)) * ``` * * @category filtering @@ -4641,8 +4379,8 @@ export const partitionEffect: { * * **Example** (Partitioning a stream) * - * ```ts - * import { Console, Effect, Result, Stream } from "effect" + * ```ts import.meta.vitest + * import { Effect, Result, Stream } from "effect" * * const program = Effect.gen(function*() { * const [excluded, satisfying] = yield* Stream.partition( @@ -4651,11 +4389,10 @@ export const partitionEffect: { * ) * const left = yield* Stream.runCollect(excluded) * const right = yield* Stream.runCollect(satisfying) - * yield* Console.log(left) - * // Output: [ 1, 3 ] - * yield* Console.log(right) - * // Output: [ 2, 4 ] + * left // => [ 1, 3 ] + * right // => [ 2, 4 ] * }) + * await Effect.runPromise(Effect.scoped(program)) * ``` * * @category filtering @@ -4704,18 +4441,17 @@ export const partition: { * * **Example** (Conditionally keeping a stream) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```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)) * ) - * yield* Console.log(result) + * result // => [] * }) * - * Effect.runPromise(program) - * // Output: [] + * await Effect.runPromise(program) * ``` * * @category filtering @@ -4748,8 +4484,8 @@ export const when: { * * **Example** (Peeling a stream with a sink) * - * ```ts - * import { Console, Effect, Sink, Stream } from "effect" + * ```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) @@ -4758,12 +4494,11 @@ export const when: { * Effect.gen(function*() { * const [peeled, rest] = yield* Stream.peel(stream, sink) * const remaining = yield* Stream.runCollect(rest) - * yield* Console.log([peeled, remaining]) + * const result = [peeled, remaining] // => [[1, 2, 3], [4, 5, 6]] * }) * ) * - * Effect.runPromise(program) - * // Output: [ [1, 2, 3], [4, 5, 6] ] + * await Effect.runPromise(program) * ``` * * @category destructors @@ -4815,22 +4550,21 @@ export const peel: { * * **Example** (Buffering stream elements) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```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 * ) - * yield* Console.log(values) + * values // => [ 1, 2, 3 ] * }) * - * Effect.runPromise(program) - * // Output: [ 1, 2, 3 ] + * await Effect.runPromise(program) * ``` * - * @category rate limiting + * @category buffering * @since 2.0.0 */ export const buffer: { @@ -4868,21 +4602,21 @@ export const buffer: { * * **Example** (Buffering stream chunks) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```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 * ) - * yield* Console.log(result) + * result // => [ 1, 2, 3, 4 ] * }) * - * // Output: [ 1, 2, 3, 4 ] + * await Effect.runPromise(program) * ``` * - * @category rate limiting + * @category buffering * @since 4.0.0 */ export const bufferArray: { @@ -4914,8 +4648,8 @@ export const bufferArray: { * * **Example** (Catching stream causes) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" * * const stream = Stream.make(1, 2).pipe( * Stream.concat(Stream.fail("Oops!")), @@ -4928,11 +4662,10 @@ export const bufferArray: { * * const program = Effect.gen(function*() { * const values = yield* Stream.runCollect(recovered) - * yield* Console.log(values) + * values // => [ 1, 2, 999 ] * }) * - * Effect.runPromise(program) - * // Output: [ 1, 2, 999 ] + * await Effect.runPromise(program) * ``` * * @category error handling @@ -4961,23 +4694,23 @@ export const catchCause: { * * **Example** (Tapping stream causes) * - * ```ts - * import { Cause, Console, Effect, Stream } from "effect" + * ```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) => Console.log(Cause.isReason(cause))), + * 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) - * yield* Console.log(result) + * result // => [1, 2, 0] * }) * - * Effect.runPromise(program) - * // Output: true - * // Output: [ 1, 2, 0 ] + * await Effect.runPromise(program) + * observations // => [false] * ``` * * @category error handling @@ -5019,8 +4752,8 @@ export { * * **Example** (Catching stream failures) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" * * const stream = Stream.make(1, 2).pipe( * Stream.concat(Stream.fail("Oops!")), @@ -5029,11 +4762,10 @@ export { * * const program = Effect.gen(function*() { * const values = yield* Stream.runCollect(stream) - * yield* Console.log(values) + * values // => [ 1, 2, 999 ] * }) * - * Effect.runPromise(program) - * // Output: [ 1, 2, 999 ] + * await Effect.runPromise(program) * ``` * * @category error handling @@ -5047,24 +4779,23 @@ export { * * **Example** (Effectfully peeking at errors) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```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) => Console.log(`tapError: ${error}`)), + * Stream.tapError((error) => Effect.sync(() => errors.push(error))), * Stream.catch(() => Stream.make(999)) * ) * * const program = Effect.gen(function*() { * const values = yield* Stream.runCollect(stream) - * yield* Console.log(values) + * values // => [1, 2, 999] * }) * - * Effect.runPromise(program) - * // Output: - * // tapError: boom - * // [ 1, 2, 999 ] + * await Effect.runPromise(program) + * errors // => ["boom"] * ``` * * @category error handling @@ -5098,8 +4829,8 @@ export const tapError: { * * **Example** (Catching matching failures) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" * * const stream = Stream.make(1, 2).pipe( * Stream.concat(Stream.fail(42)), @@ -5111,11 +4842,10 @@ export const tapError: { * * const program = Effect.gen(function*() { * const values = yield* Stream.runCollect(stream) - * yield* Console.log(values) - * // Output: [ 1, 2, 999 ] + * values // => [ 1, 2, 999 ] * }) * - * Effect.runPromise(program) + * await Effect.runPromise(program) * ``` * * @category error handling @@ -5247,8 +4977,8 @@ export const catchFilter: { * * **Example** (Catching tagged failures) * - * ```ts - * import { Console, Data, Effect, Stream } from "effect" + * ```ts import.meta.vitest + * import { Data, Effect, Stream } from "effect" * * class HttpError extends Data.TaggedError("HttpError")<{ message: string }> {} * @@ -5260,11 +4990,10 @@ export const catchFilter: { * * const program = Effect.gen(function*() { * const values = yield* Stream.runCollect(recovered) - * yield* Console.log(values) - * // Output: [ "Recovered: timeout" ] + * values // => [ 'Recovered: timeout' ] * }) * - * Effect.runPromise(program) + * await Effect.runPromise(program) * ``` * * @category error handling @@ -5355,8 +5084,8 @@ export const catchTag: { * * **Example** (Catching tagged failures with handlers) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" * * class NotFound { * readonly _tag = "NotFound" @@ -5378,10 +5107,10 @@ export const catchTag: { * }), * Stream.runCollect * ) - * yield* Console.log(result) + * result // => [ 'fallback' ] * }) * - * // Output: [ "fallback" ] + * await Effect.runPromise(program) * ``` * * @category error handling @@ -5478,8 +5207,8 @@ export const catchTags: { * * **Example** (Catching a tagged error reason) * - * ```ts - * import { Console, Data, Effect, Stream } from "effect" + * ```ts import.meta.vitest + * import { Data, Effect, Stream } from "effect" * * class RateLimitError extends Data.TaggedError("RateLimitError")<{ * retryAfter: number @@ -5504,11 +5233,10 @@ export const catchTags: { * ), * Stream.runCollect * ) - * yield* Console.log(values) + * values // => [ 'retry: 60' ] * }) * - * Effect.runPromise(program) - * // Output: [ "retry: 60" ] + * await Effect.runPromise(program) * ``` * * @category error handling @@ -5613,8 +5341,8 @@ export const catchReason: { * * **Example** (Catching tagged error reasons) * - * ```ts - * import { Console, Data, Effect, Stream } from "effect" + * ```ts import.meta.vitest + * import { Data, Effect, Stream } from "effect" * * class RateLimitError extends Data.TaggedError("RateLimitError")<{ * retryAfter: number @@ -5640,11 +5368,10 @@ export const catchReason: { * }), * Stream.runCollect * ) - * yield* Console.log(values) + * values // => [ 'retry: 60' ] * }) * - * Effect.runPromise(program) - * // Output: [ "retry: 60" ] + * await Effect.runPromise(program) * ``` * * @category error handling @@ -5733,7 +5460,8 @@ export const catchReasons: { }[keyof Cases] > } = dual((args) => isStream(args[0]), (self, errorTag, cases, orElse) => { - const handlers: Record Channel.Channel> = {} + 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 @@ -5757,8 +5485,8 @@ export const catchReasons: { * * **Example** (Mapping stream errors) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const result = yield* Stream.fail("bad").pipe( @@ -5766,11 +5494,10 @@ export const catchReasons: { * Stream.catch((error) => Stream.make(`recovered from ${error}`)), * Stream.runCollect * ) - * yield* Console.log(result) + * result // => [ 'recovered from mapped: bad' ] * }) * - * Effect.runPromise(program) - * // Output: [ "recovered from mapped: bad" ] + * await Effect.runPromise(program) * ``` * * @category error handling @@ -5790,8 +5517,8 @@ export const mapError: { * * **Example** (Catching matching causes) * - * ```ts - * import { Cause, Console, Effect, Stream } from "effect" + * ```ts import.meta.vitest + * import { Cause, Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const failingStream = Stream.fail("NetworkError") @@ -5802,11 +5529,10 @@ export const mapError: { * ) * * const output = yield* Stream.runCollect(recovered) - * yield* Console.log(output) + * output // => [ 'Recovered: NetworkError' ] * }) * - * Effect.runPromise(program) - * // Output: [ "Recovered: NetworkError" ] + * await Effect.runPromise(program) * ``` * * @category error handling @@ -5890,19 +5616,18 @@ export const catchCauseFilter: { * * **Example** (Switching on empty streams) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```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 * ) - * yield* Console.log(values) + * values // => [ 1, 2 ] * }) * - * Effect.runPromise(program) - * // Output: [ 1, 2 ] + * await Effect.runPromise(program) * ``` * * @category error handling @@ -5930,8 +5655,8 @@ export const orElseIfEmpty: { * * **Example** (Recovering with a fallback value) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const stream = Stream.fail("NetworkError").pipe( @@ -5939,11 +5664,10 @@ export const orElseIfEmpty: { * ) * * const values = yield* Stream.runCollect(stream) - * yield* Console.log(values) + * values // => [ 'Recovered: NetworkError' ] * }) * - * Effect.runPromise(program) - * // Output: [ "Recovered: NetworkError" ] + * await Effect.runPromise(program) * ``` * * @category error handling @@ -5967,8 +5691,8 @@ export const orElseSucceed: { * * **Example** (Turning failures into defects) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const values = yield* Stream.make(1, 2, 3).pipe( @@ -5976,11 +5700,10 @@ export const orElseSucceed: { * Stream.runCollect * ) * - * yield* Console.log(values) + * values // => [ 1, 2, 3 ] * }) * - * Effect.runPromise(program) - * // Output: [ 1, 2, 3 ] + * await Effect.runPromise(program) * ``` * * @category error handling @@ -6003,8 +5726,8 @@ export const orDie = (self: Stream): Stream => fr * * **Example** (Ignoring stream failures) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const values = yield* Stream.make(1, 2, 3).pipe( @@ -6012,27 +5735,25 @@ export const orDie = (self: Stream): Stream => fr * Stream.ignore, * Stream.runCollect * ) - * yield* Console.log(values) + * values // => [ 1, 2, 3 ] * }) * - * Effect.runPromise(program) - * // Output: [ 1, 2, 3 ] + * await Effect.runPromise(program) * ``` * * **Example** (Configuring ignore logging) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * - * Effect.runPromise(Effect.gen(function*() { + * await Effect.runPromise(Effect.gen(function*() { * const values = yield* Stream.fail("boom").pipe( * Stream.ignore({ log: false }), * Stream.runCollect * ) - * yield* Effect.sync(() => console.log(values)) + * values // => [] * })) * - * // [] * ``` * * @see {@link ignoreCause} for a variant that also ignores defects, not just typed failures @@ -6070,19 +5791,18 @@ export const ignore: < * * **Example** (Ignoring stream failure causes) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * - * Effect.runPromise(Effect.gen(function*() { + * 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 * ) - * yield* Effect.sync(() => console.log(values)) + * values // => [1, 2] * })) * - * // [ 1, 2 ] * ``` * * @see {@link ignore} to ignore only typed failures without suppressing defects @@ -6121,8 +5841,8 @@ export const ignoreCause: < * * **Example** (Retrying stream failures) * - * ```ts - * import { Console, Effect, Schedule, Stream } from "effect" + * ```ts import.meta.vitest + * import { Effect, Schedule, Stream } from "effect" * * const program = Effect.gen(function*() { * const values = yield* Stream.make(1).pipe( @@ -6132,11 +5852,10 @@ export const ignoreCause: < * Stream.runCollect * ) * - * yield* Console.log(values) + * values // => [ 1, 1 ] * }) * - * Effect.runPromise(program) - * // Output: [ 1, 1 ] + * await Effect.runPromise(program) * ``` * * @category error handling @@ -6170,6 +5889,27 @@ export const retry: { ): 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. @@ -6180,10 +5920,17 @@ export const retry: { * `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 { Console, Context, Effect, ExecutionPlan, Layer, Stream } from "effect" + * ```ts import.meta.vitest + * import { Context, Effect, ExecutionPlan, Layer, Stream } from "effect" * * class Service extends Context.Service()("Service", { * make: Effect.succeed({ @@ -6203,27 +5950,32 @@ export const retry: { * * const program = Effect.gen(function*() { * const items = yield* stream.pipe(Stream.withExecutionPlan(plan), Stream.runCollect) - * yield* Console.log(items) + * items // => [ 1, 2, 3 ] * }) * - * Effect.runPromise(program) - * // Output: [ 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 } - ): (self: Stream) => Stream> - ( + 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 } - ): Stream> -} = dual((args) => isStream(args[0]), ( + 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 @@ -6233,8 +5985,9 @@ export const withExecutionPlan: { }>, options?: { readonly preventFallbackOnPartialStream?: boolean | undefined + readonly onEvent?: ((event: ExecutionPlan.Event) => Effect.Effect) | undefined } -): Stream> => +): Stream | RX> => suspend(() => { const preventFallbackOnPartialStream = options?.preventFallbackOnPartialStream ?? false let i = 0 @@ -6252,6 +6005,28 @@ export const withExecutionPlan: { 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, @@ -6263,7 +6038,9 @@ export const withExecutionPlan: { return fail(Option.getOrThrow(lastError)) } - let nextStream: Stream> = provideMeta(provide(self, step.provide)) + let nextStream: Stream> = provideMeta( + instrument(provide(self, step.provide)) + ) let receivedElements = false if (Option.isSome(lastError)) { @@ -6276,10 +6053,10 @@ export const withExecutionPlan: { attempted = true return fail(error) }) - nextStream = retry(nextStream, internalExecutionPlan.scheduleFromStep(step, false) as any) + nextStream = retryWithoutReset(nextStream, internalExecutionPlan.scheduleFromStep(step, false) as any) } else { const schedule = internalExecutionPlan.scheduleFromStep(step, true) - nextStream = schedule ? retry(nextStream, schedule as any) : nextStream + nextStream = schedule ? retryWithoutReset(nextStream, schedule as any) : nextStream } return catch_( @@ -6307,19 +6084,18 @@ export const withExecutionPlan: { * * **Example** (Taking values from the left) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```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 * ) - * yield* Console.log(values) + * values // => [ 1, 2, 3 ] * }) * - * Effect.runPromise(program) - * // Output: [ 1, 2, 3 ] + * await Effect.runPromise(program) * ``` * * @category filtering @@ -6334,24 +6110,80 @@ export const take: { 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 { Console, Effect, Stream } from "effect" + * ```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 * ) - * yield* Console.log(values) + * values // => [ 4, 5, 6 ] * }) * - * Effect.runPromise(program) - * // Output: [ 4, 5, 6 ] + * await Effect.runPromise(program) * ``` * * @category filtering @@ -6385,8 +6217,8 @@ export const takeRight: { * * **Example** (Taking until a predicate matches) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" * * const stream = Stream.range(1, 5) * @@ -6395,16 +6227,15 @@ export const takeRight: { * Stream.takeUntil((n) => n % 3 === 0), * Stream.runCollect * ) - * yield* Console.log(inclusive) - * // Output: [ 1, 2, 3 ] + * inclusive // => [ 1, 2, 3 ] * * const exclusive = yield* stream.pipe( * Stream.takeUntil((n) => n % 3 === 0, { excludeLast: true }), * Stream.runCollect * ) - * yield* Console.log(exclusive) - * // Output: [ 1, 2 ] + * exclusive // => [ 1, 2 ] * }) + * await Effect.runPromise(program) * ``` * * @category filtering @@ -6452,19 +6283,18 @@ export const takeUntil: { * * **Example** (Taking until an effectful predicate matches) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```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 * ) - * yield* Console.log(result) + * result // => [ 1, 2, 3 ] * }) * - * Effect.runPromise(program) - * // Output: [ 1, 2, 3 ] + * await Effect.runPromise(program) * ``` * * @category filtering @@ -6514,8 +6344,8 @@ export const takeUntilEffect: { * * **Example** (Taking while a predicate holds) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" * * const stream = Stream.range(1, 5).pipe( * Stream.takeWhile((n) => n % 3 !== 0) @@ -6523,11 +6353,10 @@ export const takeUntilEffect: { * * const program = Effect.gen(function*() { * const result = yield* Stream.runCollect(stream) - * yield* Console.log(result) + * result // => [ 1, 2 ] * }) * - * Effect.runPromise(program) - * // Output: [ 1, 2 ] + * await Effect.runPromise(program) * ``` * * @category filtering @@ -6627,19 +6456,18 @@ export const takeWhileFilter: { * * **Example** (Effectfully taking while a predicate holds) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```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 * ) - * Console.log(result) + * result // => [ 1, 2 ] * }) * - * Effect.runPromise(program) - * // Output: [ 1, 2 ] + * await Effect.runPromise(program) * ``` * * @category filtering @@ -6668,19 +6496,18 @@ export const takeWhileEffect: { * * **Example** (Dropping values from the left) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```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) - * yield* Console.log(items) + * items // => [ 3, 4, 5 ] * }) * - * Effect.runPromise(program) - * // Output: [ 3, 4, 5 ] + * await Effect.runPromise(program) * ``` * * @category filtering @@ -6713,16 +6540,16 @@ export const drop: { * * **Example** (Dropping until a predicate matches) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```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) * - * Effect.gen(function*() { + * await Effect.runPromise(Effect.gen(function*() { * const output = yield* Stream.runCollect(result) - * yield* Console.log(output) // Output: [ 4, 5 ] - * }) + * output // => [ 4, 5 ] + * })) * ``` * * @category filtering @@ -6747,19 +6574,18 @@ export const dropUntil: { * * **Example** (Dropping until an effectful predicate matches) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```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 * ) - * yield* Console.log(result) + * result // => [ 4, 5 ] * }) * - * Effect.runPromise(program) - * // Output: [ 4, 5 ] + * await Effect.runPromise(program) * ``` * * @category filtering @@ -6790,19 +6616,18 @@ export const dropUntilEffect: { * * **Example** (Dropping while a predicate holds) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```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 * ) - * yield* Console.log(values) + * values // => [ 3, 4, 5 ] * }) * - * Effect.runPromise(program) - * // Output: [ 3, 4, 5 ] + * await Effect.runPromise(program) * ``` * * @category filtering @@ -6873,19 +6698,18 @@ export const dropWhileFilter: { * * **Example** (Effectfully dropping while a predicate holds) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```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 * ) - * yield* Console.log(result) + * result // => [ 3, 4, 5 ] * }) * - * Effect.runPromise(program) - * // Output: [ 3, 4, 5 ] + * await Effect.runPromise(program) * ``` * * @category filtering @@ -6932,19 +6756,18 @@ export const dropWhileEffect: { * * **Example** (Dropping values from the right) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```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 * ) - * yield* Console.log(result) + * result // => [ 1, 2, 3 ] * }) * - * Effect.runPromise(program) - * // Output: [ 1, 2, 3 ] + * await Effect.runPromise(program) * ``` * * @category filtering @@ -6976,8 +6799,8 @@ export const dropRight: { * * **Example** (Exposing stream chunks) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const chunks = yield* Stream.make(1, 2, 3, 4).pipe( @@ -6985,11 +6808,10 @@ export const dropRight: { * Stream.chunks, * Stream.runCollect * ) - * yield* Console.log(chunks) + * chunks // => [ [ 1, 2 ], [ 3, 4 ] ] * }) * - * Effect.runPromise(program) - * // Output: [ [ 1, 2 ], [ 3, 4 ] ] + * await Effect.runPromise(program) * ``` * * @category grouping @@ -7010,8 +6832,8 @@ export const chunks = (self: Stream): Stream(self: Stream): Stream [ [ 1, 2 ], [ 3, 4 ], [ 5 ] ] * }) * - * Effect.runPromise(program) - * // Output: [ [ 1, 2 ], [ 3, 4 ], [ 5 ] ] + * await Effect.runPromise(program) * ``` * * @category grouping @@ -7083,18 +6904,17 @@ export const rechunk: { * * **Example** (Emitting sliding windows) * - * ```ts - * import { Console, Effect, pipe, Stream } from "effect" + * ```ts import.meta.vitest + * import { Effect, pipe, Stream } from "effect" * - * Effect.gen(function*() { + * await Effect.runPromise(Effect.gen(function*() { * const result = yield* pipe( * Stream.make(1, 2, 3, 4, 5), * Stream.sliding(2), * Stream.runCollect * ) - * yield* Console.log(result) - * }) - * // Output: [ [1, 2], [2, 3], [3, 4], [4, 5] ] + * result // => [ [ 1, 2 ], [ 2, 3 ], [ 3, 4 ], [ 4, 5 ] ] + * })) * ``` * * @category grouping @@ -7114,19 +6934,18 @@ export const sliding: { * * **Example** (Emitting sliding windows with a step size) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```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 * ) - * yield* Console.log(chunks) + * chunks // => [ [ 1, 2, 3 ], [ 3, 4, 5 ] ] * }) * - * Effect.runPromise(program) - * // Output: [ [ 1, 2, 3 ], [ 3, 4, 5 ] ] + * await Effect.runPromise(program) * ``` * * @category grouping @@ -7143,12 +6962,18 @@ export const slidingSize: { 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> @@ -7157,10 +6982,12 @@ export const slidingSize: { chunks.push(MutableList.takeN(list, chunkSize) as any) } else { chunks.push(MutableList.toArrayN(list, chunkSize) as any) - if (chunkSize === 1) { + if (chunkSize === 1 && stepSize <= 0) { MutableList.take(list) } else { + const length = list.length MutableList.takeNVoid(list, stepSize) + skip = Math.max(0, stepSize - length) } } } @@ -7187,19 +7014,18 @@ export const slidingSize: { * * **Example** (Splitting on matching values) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```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 * ) - * yield* Console.log(result) + * result // => [ [ 1, 2, 3 ], [ 5, 6, 7 ], [ 9 ] ] * }) * - * Effect.runPromise(program) - * // Output: [ [1, 2, 3], [5, 6, 7], [9] ] + * await Effect.runPromise(program) * ``` * * @category grouping @@ -7249,8 +7075,8 @@ export const split: { * * **Example** (Combining streams with state) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" * * const stream = Stream.combine( * Stream.make("A", "B", "C"), @@ -7264,11 +7090,10 @@ export const split: { * * const program = Effect.gen(function*() { * const output = yield* Stream.runCollect(stream) - * yield* Console.log(output) + * output // => [ 'L:A', 'R:1', 'L:B', 'R:2', 'L:C', 'R:3' ] * }) * - * Effect.runPromise(program) - * // Output: [ "L:A", "R:1", "L:B", "R:2", "L:C", "R:3" ] + * await Effect.runPromise(program) * ``` * * @category merging @@ -7330,8 +7155,8 @@ export const combine: { * * **Example** (Combining stream chunks with state) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" * * const stream = Stream.make(1, 2).pipe( * Stream.combineArray( @@ -7347,11 +7172,10 @@ export const combine: { * * const program = Effect.gen(function*() { * const values = yield* Stream.runCollect(stream) - * yield* Console.log(values) + * values // => [ 1, 2, 10, 20 ] * }) * - * Effect.runPromise(program) - * // Output: [ 1, 2, 10, 20 ] + * await Effect.runPromise(program) * ``` * * @category sequencing @@ -7399,7 +7223,7 @@ export const combineArray: { * * **Example** (Statefully mapping stream values) * - * ```ts + * ```ts import.meta.vitest * import { Console, Effect, Stream } from "effect" * * const program = Effect.gen(function*() { @@ -7411,11 +7235,10 @@ export const combineArray: { * Stream.runCollect * ) * - * yield* Console.log(totals) + * totals // => [0, 1, 3, 6, 10, 15, 21] * }) * - * Effect.runPromise(program) - * // Output: [ 0, 1, 3, 6, 10, 15, 21 ] + * await Effect.runPromise(program) * ``` * * @category mapping @@ -7476,8 +7299,8 @@ export const mapAccum: { * * **Example** (Statefully mapping stream chunks) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```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( @@ -7488,11 +7311,10 @@ export const mapAccum: { * }), * Stream.runCollect * ) - * yield* Console.log(output) + * output // => [ 3, 10, 21 ] * }) * - * Effect.runPromise(program) - * // Output: [ 3, 10, 21 ] + * await Effect.runPromise(program) * ``` * * @category mapping @@ -7559,8 +7381,8 @@ const emptyArr = Arr.empty() * * **Example** (Effectfully mapping stream values with state) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const result = yield* Stream.make(1, 1, 1).pipe( @@ -7570,11 +7392,10 @@ const emptyArr = Arr.empty() * Stream.runCollect * ) * - * yield* Console.log(result) + * result // => [ 1, 2, 3 ] * }) * - * Effect.runPromise(program) - * // Output: [ 1, 2, 3 ] + * await Effect.runPromise(program) * ``` * * @category mapping @@ -7644,8 +7465,8 @@ export const mapAccumEffect: { * * **Example** (Effectfully mapping stream chunks with state) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const totals = yield* Stream.make(1, 2, 3, 4).pipe( @@ -7658,11 +7479,10 @@ export const mapAccumEffect: { * ), * Stream.runCollect * ) - * yield* Console.log(totals) + * totals // => [ 3, 10 ] * }) * - * Effect.runPromise(program) - * // Output: [ 3, 10 ] + * await Effect.runPromise(program) * ``` * * @category mapping @@ -7684,7 +7504,7 @@ export const mapAccumArrayEffect: { readonly onHalt?: ((state: S) => ReadonlyArray) | undefined } ): Stream -} = dual((args) => isStream(args), ( +} = dual((args) => isStream(args[0]), ( self: Stream, initial: LazyArg, f: (s: S, a: Arr.NonEmptyReadonlyArray) => Effect.Effect], E2, R2>, @@ -7720,22 +7540,21 @@ export const mapAccumArrayEffect: { * * **Example** (Scanning stream state) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```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 * ) - * yield* Console.log(values) + * values // => [ 0, 1, 3, 6 ] * }) * - * Effect.runPromise(program) - * // Output: [ 0, 1, 3, 6 ] + * await Effect.runPromise(program) * ``` * - * @category Accumulation + * @category accumulation * @since 2.0.0 */ export const scan: { @@ -7774,20 +7593,20 @@ export const scan: { * * **Example** (Effectfully scanning stream state) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```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 * ) - * yield* Console.log(states) - * // Output: [ 0, 1, 3, 6 ] + * states // => [ 0, 1, 3, 6 ] * }) + * await Effect.runPromise(program) * ``` * - * @category Accumulation + * @category accumulation * @since 2.0.0 */ export const scanEffect: { @@ -7817,20 +7636,16 @@ export const scanEffect: { * * **Example** (Debouncing stream elements) * - * ```ts - * import { Console, Duration, Effect, Stream } from "effect" + * ```ts import.meta.vitest + * import { Duration, Effect, Stream } from "effect" * - * const stream = Stream.make(1, 2, 3).pipe( - * Stream.concat(Stream.fromEffect(Effect.sleep(Duration.millis(50)).pipe(Effect.as(4)))), - * Stream.concat(Stream.make(5)), - * Stream.debounce(Duration.millis(30)) - * ) + * const stream = Stream.make(1, 2, 3).pipe(Stream.debounce(Duration.zero)) * * const program = Effect.gen(function*() { * const values = yield* Stream.runCollect(stream) - * yield* Console.log(values) - * // Output: [ 3, 5 ] + * values // => [ 3 ] * }) + * await Effect.runPromise(program) * ``` * * @category rate limiting @@ -7929,24 +7744,23 @@ export const debounce: { * * **Example** (Throttling stream chunks effectfully) * - * ```ts - * import { Console, Effect, Schedule, Stream } from "effect" + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" * - * const stream = Stream.fromSchedule(Schedule.spaced("50 millis")).pipe( - * Stream.take(6), + * const stream = Stream.range(0, 5).pipe( + * Stream.rechunk(1), * Stream.throttleEffect({ * cost: (arr) => Effect.succeed(arr.length), * units: 1, - * duration: "100 millis", + * duration: 0, * strategy: "shape" * }) * ) * - * Effect.runPromise(Effect.gen(function*() { + * await Effect.runPromise(Effect.gen(function*() { * const result = yield* Stream.runCollect(stream) - * yield* Console.log(result) + * result // => [ 0, 1, 2, 3, 4, 5 ] * })) - * // Output: [0, 1, 2, 3, 4, 5] * ``` * * @category rate limiting @@ -8093,24 +7907,24 @@ const throttleShapeEffect = ( * * **Example** (Throttling stream chunks) * - * ```ts - * import { Console, Effect, Schedule, Stream } from "effect" + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" * - * const stream = Stream.fromSchedule(Schedule.spaced("50 millis")).pipe( - * Stream.take(6), + * const stream = Stream.range(0, 5).pipe( + * Stream.rechunk(1), * Stream.throttle({ * cost: (arr) => arr.length, * units: 1, - * duration: "100 millis", + * duration: 0, * strategy: "shape" * }) * ) * * const program = Effect.gen(function*() { * const values = yield* Stream.runCollect(stream) - * yield* Console.log(values) - * // Output: [ 0, 1, 2, 3, 4, 5 ] + * values // => [ 0, 1, 2, 3, 4, 5 ] * }) + * await Effect.runPromise(program) * ``` * * @category rate limiting @@ -8161,19 +7975,18 @@ export const throttle: { * * **Example** (Grouping elements by size) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```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 * ) - * yield* Console.log(grouped) + * grouped // => [ [ 1, 2, 3 ], [ 4, 5, 6 ], [ 7, 8 ] ] * }) * - * Effect.runPromise(program) - * // Output: [ [ 1, 2, 3 ], [ 4, 5, 6 ], [ 7, 8 ] ] + * await Effect.runPromise(program) * ``` * * @category grouping @@ -8193,19 +8006,18 @@ export const grouped: { * * **Example** (Grouping elements by size or time) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```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 * ) - * yield* Console.log(values) + * values // => [ [ 1, 2 ], [ 3 ] ] * }) * - * Effect.runPromise(program) - * // Output: [ [ 1, 2 ], [ 3 ] ] + * await Effect.runPromise(program) * ``` * * @category grouping @@ -8233,8 +8045,8 @@ export const groupedWithin: { * * **Example** (Grouping elements into keyed substreams using an effectful classifier) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```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( @@ -8250,11 +8062,10 @@ export const groupedWithin: { * Stream.runCollect * ) * - * yield* Console.log(grouped) + * grouped // => [ [ 'odd', [ 1, 3, 5 ] ], [ 'even', [ 2, 4 ] ] ] * }) * - * Effect.runPromise(program) - * // Output: [ [ "odd", [ 1, 3, 5 ] ], [ "even", [ 2, 4 ] ] ] + * await Effect.runPromise(program) * ``` * * @category grouping @@ -8305,8 +8116,8 @@ export const groupBy: { * * **Example** (Grouping elements by key) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```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( @@ -8320,11 +8131,10 @@ export const groupBy: { * ), * Stream.runCollect * ) - * yield* Console.log(grouped) + * grouped // => [ [ 'odd', [ 1, 3, 5 ] ], [ 'even', [ 2, 4 ] ] ] * }) * - * Effect.runPromise(program) - * // Output: [ [ "odd", [ 1, 3, 5 ] ], [ "even", [ 2, 4 ] ] ] + * await Effect.runPromise(program) * ``` * * @category grouping @@ -8511,8 +8321,8 @@ export const groupAdjacentBy: { * * **Example** (Transducing with a sink) * - * ```ts - * import { Console, Effect, Sink, Stream } from "effect" + * ```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( @@ -8520,12 +8330,12 @@ export const groupAdjacentBy: { * Stream.runCollect * ) * - * yield* Console.log(result) - * // Output: [ [ 1, 2 ], [ 3, 4 ] ] + * result // => [ [ 1, 2 ], [ 3, 4 ], [] ] * }) + * await Effect.runPromise(program) * ``` * - * @category Aggregation + * @category aggregation * @since 2.0.0 */ export const transduce = dual< @@ -8585,10 +8395,10 @@ export const transduce = dual< * * **Example** (Aggregating with a sink) * - * ```ts - * import { Console, Effect, Sink, Stream } from "effect" + * ```ts import.meta.vitest + * import { Effect, Sink, Stream } from "effect" * - * Effect.runPromise(Effect.gen(function* () { + * await Effect.runPromise(Effect.gen(function* () { * const aggregated = yield* Stream.runCollect( * Stream.make(1, 2, 3, 4, 5, 6).pipe( * Stream.aggregate( @@ -8596,12 +8406,11 @@ export const transduce = dual< * ) * ) * ) - * yield* Console.log(aggregated) + * aggregated // => [ 6, 15 ] * })) - * // [ 6, 15 ] * ``` * - * @category Aggregation + * @category aggregation * @since 2.0.0 */ export const aggregate: { @@ -8626,24 +8435,23 @@ export const aggregate: { * * **Example** (Aggregating with a sink and schedule) * - * ```ts - * import { Console, Effect, Schedule, Sink, Stream } from "effect" + * ```ts import.meta.vitest + * import { Effect, Schedule, Sink, Stream } from "effect" * - * Effect.runPromise(Effect.gen(function* () { + * 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.spaced("1 minute") + * Schedule.forever * ) * ) * ) - * yield* Console.log(aggregated) + * aggregated // => [ 6, 15 ] * })) - * // Output: [ 6, 15 ] * ``` * - * @category Aggregation + * @category aggregation * @since 2.0.0 */ export const aggregateWithin: { @@ -8687,13 +8495,13 @@ export const aggregateWithin: { let leftover: Arr.NonEmptyReadonlyArray | undefined let sinkHasInput = false const step = yield* Schedule.toStepWithSleep(schedule) - const stepToBuffer = Effect.suspend(function loop(): Pull.Pull { - return step(lastOutput).pipe( - Effect.flatMap(() => !sinkHasInput ? loop() : Queue.offer(buffer, scheduleStep)), - Effect.flatMap(() => Effect.never), - Pull.catchDone(() => Cause.done()) - ) + 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< @@ -8753,8 +8561,8 @@ export const aggregateWithin: { * * **Example** (Broadcasting to two consumers) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" * * const program = Effect.scoped( * Effect.gen(function*() { @@ -8767,15 +8575,14 @@ export const aggregateWithin: { * Stream.runCollect(right) * ], { concurrency: "unbounded" }) * - * yield* Console.log(values) + * values // => [ [ 1, 2, 3 ], [ 1, 2, 3 ] ] * }) * ) * - * Effect.runPromise(program) - * // Output: [[1, 2, 3], [1, 2, 3]] + * await Effect.runPromise(program) * ``` * - * @category Broadcast + * @category broadcasting * @since 4.0.0 */ export const broadcastN: { @@ -8870,8 +8677,8 @@ const makePubSub = ( * * **Example** (Broadcasting a stream) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" * * const program = Effect.scoped( * Effect.gen(function* () { @@ -8885,15 +8692,14 @@ const makePubSub = ( * Stream.runCollect(broadcasted) * ], { concurrency: "unbounded" }) * - * yield* Console.log([left, right]) + * const result = [left, right] // => [[1, 2, 3], [1, 2, 3]] * }) * ) * - * Effect.runPromise(program) - * // Output: [[1, 2, 3], [1, 2, 3]] + * await Effect.runPromise(program) * ``` * - * @category Broadcast + * @category broadcasting * @since 2.0.0 */ export const broadcast: { @@ -8941,27 +8747,40 @@ export const broadcast: { * * **Example** (Sharing a stream) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```ts import.meta.vitest + * import { Deferred, Effect, Ref, Stream } from "effect" * - * Effect.runPromise( + * const result = await Effect.runPromise( * Effect.scoped( * Effect.gen(function*() { - * const shared = yield* Stream.make(1, 2, 3).pipe( - * Stream.share({ capacity: 16 }) + * 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 first = yield* shared.pipe(Stream.take(1), Stream.runCollect) - * const second = yield* shared.pipe(Stream.take(1), Stream.runCollect) - * - * yield* Console.log([first, second]) + * 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) } * }) * ) * ) - * // output: [[1], [1]] + * result // => { values: [[1, 2, 3], [1, 2, 3]], acquisitions: 1 } * ``` * - * @category Broadcast + * @category broadcasting * @since 3.8.0 */ export const share: { @@ -9021,8 +8840,8 @@ export const share: { * * **Example** (Piping through a channel) * - * ```ts - * import { Array, Channel, Console, Effect, Stream } from "effect" + * ```ts import.meta.vitest + * import { Array, Channel, Effect, Stream } from "effect" * * type NumberChunk = readonly [number, ...Array] * @@ -9036,14 +8855,13 @@ export const share: { * Stream.pipeThroughChannel(doubleChunks), * Stream.runCollect * ) - * yield* Console.log(result) + * result // => [ 2, 4, 6 ] * }) * - * Effect.runPromise(program) - * // => [2, 4, 6] + * await Effect.runPromise(program) * ``` * - * @category Pipe + * @category sequencing * @since 2.0.0 */ export const pipeThroughChannel: { @@ -9070,7 +8888,7 @@ export const pipeThroughChannel: { * * **Example** (Piping through a channel with failures) * - * ```ts + * ```ts import.meta.vitest * import { Array, Channel, Effect, Stream } from "effect" * * type NumberChunk = readonly [number, ...Array] @@ -9079,19 +8897,18 @@ export const pipeThroughChannel: { * Channel.map((chunk) => Array.map(chunk, String)) * ) * - * Effect.runPromise(Effect.gen(function*() { + * await Effect.runPromise(Effect.gen(function*() { * const result = yield* Stream.make(1, 2, 3).pipe( * Stream.rechunk(2), * Stream.pipeThroughChannelOrFail(stringifyChunks), * Stream.runCollect * ) * - * yield* Effect.sync(() => console.log(result)) + * result // => ["1", "2", "3"] * })) - * // [ "1", "2", "3" ] * ``` * - * @category Pipe + * @category sequencing * @since 2.0.0 */ export const pipeThroughChannelOrFail: { @@ -9116,8 +8933,8 @@ export const pipeThroughChannelOrFail: { * * **Example** (Piping through a sink) * - * ```ts - * import { Console, Effect, Sink, Stream } from "effect" + * ```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( @@ -9125,14 +8942,13 @@ export const pipeThroughChannelOrFail: { * Stream.runCollect * ) * - * yield* Console.log(leftovers) + * leftovers // => [ 3, 4 ] * }) * - * Effect.runPromise(program) - * //=> [ 3, 4 ] + * await Effect.runPromise(program) * ``` * - * @category Pipe + * @category sequencing * @since 2.0.0 */ export const pipeThrough: { @@ -9153,21 +8969,20 @@ export const pipeThrough: { * * **Example** (Collecting values into a stream element) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```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) - * yield* Console.log(collected[0]) + * collected[0] // => [ 1, 2, 3 ] * }) * - * Effect.runPromise(program) - * // [1, 2, 3] + * await Effect.runPromise(program) * ``` * - * @category Accumulation + * @category accumulation * @since 4.0.0 */ export const collect = (self: Stream): Stream, E, R> => fromEffect(runCollect(self)) @@ -9177,8 +8992,8 @@ export const collect = (self: Stream): Stream, E, R> * * **Example** (Accumulating stream elements) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const accumulated = yield* Stream.runCollect( @@ -9187,14 +9002,13 @@ export const collect = (self: Stream): Stream, E, R> * Stream.accumulate * ) * ) - * yield* Console.log(accumulated) + * accumulated // => [ [ 1 ], [ 1, 2 ], [ 1, 2, 3 ] ] * }) * - * Effect.runPromise(program) - * //=> { _id: 'Chunk', values: [ [ 1 ], [ 1, 2 ], [ 1, 2, 3 ] ] } + * await Effect.runPromise(program) * ``` * - * @category Accumulation + * @category accumulation * @since 2.0.0 */ export const accumulate = (self: Stream): Stream, E, R> => @@ -9208,8 +9022,8 @@ export const accumulate = (self: Stream): Stream(self: Stream): Stream [ 1, 2, 3 ] * }) * - * Effect.runPromise(program) - * // [1, 2, 3] + * await Effect.runPromise(program) * ``` * - * @category Deduplication + * @category deduplication * @since 2.0.0 */ export const changes = (self: Stream): Stream => changesWith(self, Equal.equals) @@ -9234,23 +9047,22 @@ export const changes = (self: Stream): Stream => chan * * **Example** (Emitting values that changed by equivalence) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```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()) * ) * - * Effect.runPromise( + * await Effect.runPromise( * Effect.gen(function*() { * const values = yield* Stream.runCollect(stream) - * yield* Console.log(values) + * values // => [ 'A', 'B' ] * }) * ) - * // ["A", "B"] * ``` * - * @category Deduplication + * @category deduplication * @since 2.0.0 */ export const changesWith: { @@ -9292,22 +9104,21 @@ export const changesWith: { * * **Example** (Effectfully emitting changed values) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```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) - * yield* Console.log(result) + * result // => [ 1, 2, 3 ] * }) * - * Effect.runPromise(program) - * // { _id: "Chunk", values: [ 1, 2, 3 ] } + * await Effect.runPromise(program) * ``` * - * @category Deduplication + * @category deduplication * @since 2.0.0 */ export const changesWithEffect: { @@ -9360,8 +9171,8 @@ export const changesWithEffect: { * * **Example** (Decoding Uint8Array chunks into strings using TextDecoder with an optional encoding) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" * * const encoder = new TextEncoder() * const stream = Stream.make( @@ -9374,14 +9185,13 @@ export const changesWithEffect: { * Stream.decodeText, * Stream.runCollect * ) - * yield* Console.log(decoded) + * decoded // => [ 'Hello', ' World' ] * }) * - * Effect.runPromise(program) - * // ["Hello", " World"] + * await Effect.runPromise(program) * ``` * - * @category encoding + * @category decoding * @since 2.0.0 */ export const decodeText: < @@ -9412,19 +9222,18 @@ export const decodeText: < * * **Example** (Encoding a stream of strings into UTF-8 Uint8Array chunks) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```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]) - * yield* Console.log(bytes) + * bytes // => [ [ 72, 101, 108, 108, 111 ], [ 32 ], [ 87, 111, 114, 108, 100 ] ] * }) * - * Effect.runPromise(program) - * // [[72, 101, 108, 108, 111], [32], [87, 111, 114, 108, 100]] + * await Effect.runPromise(program) * ``` * * @category encoding @@ -9441,19 +9250,18 @@ export const encodeText = (self: Stream): Stream [ 'a', 'b', 'c' ] * })) - * // ["a", "b", "c"] * ``` * - * @category encoding + * @category splitting * @since 2.0.0 */ export const splitLines = (self: Stream): Stream => @@ -9467,17 +9275,16 @@ export const splitLines = (self: Stream): Stream [1, 0, 2, 0, 3, 0, 4] * }) * - * Effect.runPromise(program) - * // [1, 0, 2, 0, 3, 0, 4] + * await Effect.runPromise(program) * ``` * * @category sequencing @@ -9509,7 +9316,7 @@ export const intersperse: { * * **Example** (Interspersing stream affixes) * - * ```ts + * ```ts import.meta.vitest * import { Console, Effect, Stream } from "effect" * * const stream = Stream.make("a", "b", "c").pipe( @@ -9518,11 +9325,10 @@ export const intersperse: { * * const program = Effect.gen(function*() { * const result = yield* Stream.runCollect(stream) - * yield* Console.log(result) + * result // => ["[", "a", ",", "b", ",", "c", "]"] * }) * - * Effect.runPromise(program) - * // [ "[", "a", ",", "b", ",", "c", "]" ] + * await Effect.runPromise(program) * ``` * * @category sequencing @@ -9552,8 +9358,8 @@ export const intersperseAffixes: { * * **Example** (Interleaving streams) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" * * const stream = Stream.interleave( * Stream.make(2, 3), @@ -9562,11 +9368,10 @@ export const intersperseAffixes: { * * const program = Effect.gen(function*() { * const collected = yield* Stream.runCollect(stream) - * yield* Console.log(collected) + * collected // => [ 2, 5, 3, 6, 7 ] * }) * - * Effect.runPromise(program) - * // [2, 5, 3, 6, 7] + * await Effect.runPromise(program) * ``` * * @category merging @@ -9595,8 +9400,8 @@ export const interleave: { * * **Example** (Interleaving two streams deterministically by following a boolean decider stream) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const left = Stream.make(1, 3, 5) @@ -9607,11 +9412,10 @@ export const interleave: { * Stream.interleaveWith(left, right, decider) * ) * - * yield* Console.log(values) + * values // => [ 1, 2, 4, 3, 5 ] * }) * - * Effect.runPromise(program) - * // [ 1, 2, 4, 3, 5 ] + * await Effect.runPromise(program) * ``` * * @category merging @@ -9685,8 +9489,8 @@ export const interleaveWith: { * * **Example** (Interrupting when an effect completes) * - * ```ts - * import { Console, Deferred, Effect, Stream } from "effect" + * ```ts import.meta.vitest + * import { Deferred, Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const interrupt = yield* Deferred.make() @@ -9700,11 +9504,10 @@ export const interleaveWith: { * ) * * const result = yield* Stream.runCollect(stream) - * yield* Console.log(result) + * result // => [ 1 ] * }) * - * Effect.runPromise(program) - * // => [1, 2] + * await Effect.runPromise(program) * ``` * * @category interruption @@ -9720,7 +9523,7 @@ export const interruptWhen: { ) /** - * Stops a stream after the current element when an effect completes. + * Stops a stream after the current pull when an effect completes. * * **When to use** * @@ -9733,13 +9536,15 @@ export const interruptWhen: { * * **Gotchas** * - * This does not interrupt an in-progress pull. Use {@link interruptWhen} when - * the stream should be interrupted immediately. + * 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 { Console, Deferred, Effect, Stream } from "effect" + * ```ts import.meta.vitest + * import { Deferred, Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const halt = yield* Deferred.make() @@ -9748,12 +9553,10 @@ export const interruptWhen: { * Stream.haltWhen(Deferred.await(halt)), * Stream.runCollect * ) - * yield* Console.log(values) + * values // => [ 1, 2 ] * }) * - * Effect.runPromise(program) - * // Output: - * // [1, 2] + * await Effect.runPromise(program) * ``` * * @category interruption @@ -9773,25 +9576,25 @@ export const haltWhen: { * * **Example** (Running a finalizer on exit) * - * ```ts - * import { Console, Effect, Exit, Stream } from "effect" + * ```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) - * ? Console.log("Stream completed successfully") - * : Console.log("Stream failed") + * ? Effect.sync(() => exits.push("success")) + * : Effect.sync(() => exits.push("failure")) * ) * ) * - * Effect.runPromise(Effect.gen(function*() { + * await Effect.runPromise(Effect.gen(function*() { * yield* Stream.runCollect(stream) * })) - * // Output: - * // Stream completed successfully + * exits // => ["success"] * ``` * - * @category Finalization + * @category resource management * @since 4.0.0 */ export const onExit: { @@ -9817,21 +9620,21 @@ export const onExit: { * * **Example** (Running an effect on errors) * - * ```ts - * import { Cause, Console, Effect, Stream } from "effect" + * ```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) => Console.log(`Stream failed: ${Cause.squash(cause)}`)) + * Stream.onError((cause) => Effect.sync(() => errors.push(String(Cause.squash(cause))))) * ) * * yield* Stream.runCollect(stream) * }) * - * Effect.runPromiseExit(program) - * // Output: - * // Stream failed: boom + * await Effect.runPromise(Effect.exit(program)) + * errors // => ["boom"] * ``` * * @category error handling @@ -9855,22 +9658,21 @@ export const onError: { * * **Example** (Running an effect on start) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```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(Console.log("Stream started")) + * Stream.onStart(Effect.sync(() => events.push("started"))) * ) * * const values = yield* Stream.runCollect(stream) - * yield* Console.log(values) + * values // => [1, 2, 3] * }) * - * Effect.runPromise(program) - * // Output: - * // Stream started - * // [1, 2, 3] + * await Effect.runPromise(program) + * events // => ["started"] * ``` * * @category sequencing @@ -9894,16 +9696,17 @@ export const onStart: { * * **Example** (Running an effect on the first value) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" * - * Effect.runPromise(Effect.gen(function* () { + * const first: Array = [] + * await Effect.runPromise(Effect.gen(function* () { * yield* Stream.fromArray([1, 2, 3]).pipe( - * Stream.onFirst((value) => Console.log(`first=${value}`)), + * Stream.onFirst((value) => Effect.sync(() => first.push(value))), * Stream.runDrain * ) * })) - * // Output: first=1 + * first // => [1] * ``` * * @category sequencing @@ -9927,20 +9730,20 @@ export const onFirst: { * * **Example** (Running an effect on end) * - * ```ts - * import { Console, Effect, Stream } from "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.onEnd(Console.log("Stream ended")), + * Stream.onEnd(Effect.sync(() => events.push("ended"))), * Stream.runCollect * ) - * yield* Console.log(values) + * values // => [1, 2, 3] * }) * - * Effect.runPromise(program) - * // Stream ended - * // [1, 2, 3] + * await Effect.runPromise(program) + * events // => ["ended"] * ``` * * @category sequencing @@ -9964,24 +9767,24 @@ export const onEnd: { * * **Example** (Ensuring finalization) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" * + * const events: Array = [] * const stream = Stream.fromArray([1, 2]).pipe( - * Stream.ensuring(Effect.orDie(Console.log("cleanup"))) + * Stream.ensuring(Effect.sync(() => events.push("cleanup"))) * ) * * const program = Effect.gen(function*() { * const collected = yield* Stream.runCollect(stream) - * yield* Console.log(collected) + * collected // => [1, 2] * }) * - * Effect.runPromise(program) - * //=> cleanup - * //=> [1, 2] + * await Effect.runPromise(program) + * events // => ["cleanup"] * ``` * - * @category Finalization + * @category resource management * @since 2.0.0 */ export const ensuring: { @@ -10000,7 +9803,7 @@ export const ensuring: { * * **Example** (Providing stream requirements) * - * ```ts + * ```ts import.meta.vitest * import { Console, Context, Effect, Layer, Stream } from "effect" * * class Env extends Context.Service()("Env") {} @@ -10016,16 +9819,10 @@ export const ensuring: { * * const withEnv = stream.pipe(Stream.provide(layer)) * - * const program = Stream.runCollect(withEnv).pipe( - * Effect.flatMap((values) => Console.log(values)) - * ) - * - * Effect.runPromise(program) - * // Output: - * // ["Hello, Ada"] + * await Effect.runPromise(Stream.runCollect(withEnv)) // => ["Hello, Ada"] * ``` * - * @category services + * @category providing services * @since 4.0.0 */ export const provide: { @@ -10057,8 +9854,8 @@ export const provide: { * * **Example** (Providing multiple services to the stream using a context) * - * ```ts - * import { Console, Context, Effect, Stream } from "effect" + * ```ts import.meta.vitest + * import { Context, Effect, Stream } from "effect" * * class Config extends Context.Service()("Config") {} * class Greeter extends Context.Service string }>()("Greeter") {} @@ -10077,14 +9874,13 @@ export const provide: { * * const program = Effect.gen(function*() { * const result = yield* Stream.runCollect(Stream.provideContext(stream, context)) - * yield* Console.log(result) + * result // => [ 'Hello!' ] * }) * - * Effect.runPromise(program) - * // ["Hello!"] + * await Effect.runPromise(program) * ``` * - * @category services + * @category providing services * @since 2.0.0 */ export const provideContext: { @@ -10102,8 +9898,8 @@ export const provideContext: { * * **Example** (Providing a stream service) * - * ```ts - * import { Console, Context, Effect, Stream } from "effect" + * ```ts import.meta.vitest + * import { Context, Effect, Stream } from "effect" * * class Greeter extends Context.Service string @@ -10123,14 +9919,13 @@ export const provideContext: { * }) * ) * ) - * yield* Console.log(collected) + * collected // => [ 'Hello, Ada' ] * }) * - * Effect.runPromise(program) - * //=> ["Hello, Ada"] + * await Effect.runPromise(program) * ``` * - * @category services + * @category providing services * @since 2.0.0 */ export const provideService: { @@ -10156,8 +9951,8 @@ export const provideService: { * * **Example** (Providing a stream service effectfully) * - * ```ts - * import { Console, Context, Effect, Stream } from "effect" + * ```ts import.meta.vitest + * import { Context, Effect, Stream } from "effect" * * class ApiConfig extends Context.Service()("ApiConfig") {} * @@ -10168,26 +9963,21 @@ export const provideService: { * }) * ) * + * const events: Array = [] * const withConfig = stream.pipe( * Stream.provideServiceEffect( * ApiConfig, * Effect.succeed({ baseUrl: "https://example.com" }).pipe( - * Effect.tap(() => Console.log("Loading config...")) + * Effect.tap(() => Effect.sync(() => events.push("loading"))) * ) * ) * ) * - * const program = Stream.runCollect(withConfig).pipe( - * Effect.flatMap((values) => Console.log(values)) - * ) - * - * Effect.runPromise(program) - * // Output: - * // Loading config... - * // ["https://example.com"] + * await Effect.runPromise(Stream.runCollect(withConfig)) // => ["https://example.com"] + * events // => ["loading"] * ``` * - * @category services + * @category providing services * @since 2.0.0 */ export const provideServiceEffect: { @@ -10214,8 +10004,8 @@ export const provideServiceEffect: { * * **Example** (Updating the stream context) * - * ```ts - * import { Console, Context, Effect, Stream } from "effect" + * ```ts import.meta.vitest + * import { Context, Effect, Stream } from "effect" * * class Logger extends Context.Service()("Logger") {} * class Config extends Context.Service()("Config") {} @@ -10236,16 +10026,15 @@ export const provideServiceEffect: { * * const program = Effect.gen(function*() { * const values = yield* Stream.runCollect(updated) - * yield* Console.log(values) + * values // => [ 'Hello World' ] * }) * - * Effect.runPromise( + * await Effect.runPromise( * Effect.provideService(program, Logger, { prefix: "Hello " }) * ) - * //=> [ "Hello World" ] * ``` * - * @category services + * @category providing services * @since 4.0.0 */ export const updateContext: { @@ -10268,8 +10057,8 @@ export const updateContext: { * * **Example** (Updating a stream service) * - * ```ts - * import { Console, Context, Effect, Stream } from "effect" + * ```ts import.meta.vitest + * import { Context, Effect, Stream } from "effect" * * class Counter extends Context.Service()("Counter") {} * @@ -10279,14 +10068,13 @@ export const updateContext: { * * const program = Effect.gen(function*() { * const counters = yield* Stream.runCollect(stream) - * yield* Console.log(`Updated count: ${counters[0].count}`) + * const message = `Updated count: ${counters[0].count}` // => "Updated count: 1" * }) * - * Effect.runPromise(Effect.provideService(program, Counter, { count: 0 })) - * // Output: Updated count: 1 + * await Effect.runPromise(Effect.provideService(program, Counter, { count: 0 })) * ``` * - * @category services + * @category providing services * @since 2.0.0 */ export const updateService: { @@ -10318,18 +10106,17 @@ export const updateService: { * * **Example** (Wrapping a stream in a span) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" * * const stream = Stream.fromArray([1, 2, 3]).pipe(Stream.withSpan("numbers")) * - * Effect.runPromise( + * await Effect.runPromise( * Effect.gen(function*() { * const values = yield* Stream.runCollect(stream) - * yield* Console.log(values) + * values // => [ 1, 2, 3 ] * }) * ) - * // [1, 2, 3] * ``` * * @category tracing @@ -10354,8 +10141,8 @@ export const withSpan: { * * **Example** (Starting stream do notation) * - * ```ts - * import { Console, Effect, pipe, Stream } from "effect" + * ```ts import.meta.vitest + * import { Effect, pipe, Stream } from "effect" * * const program = pipe( * Stream.Do, @@ -10365,14 +10152,13 @@ export const withSpan: { * * const effect = Effect.gen(function*() { * const collected = yield* Stream.runCollect(program) - * yield* Console.log(collected) + * collected // => [ { value: 1, next: 2 }, { value: 2, next: 3 } ] * }) * - * Effect.runPromise(effect) - * //=> [{ value: 1, next: 2 }, { value: 2, next: 3 }] + * await Effect.runPromise(effect) * ``` * - * @category do notation + * @category constructors * @since 2.0.0 */ export const Do: Stream<{}> = succeed({}) @@ -10399,8 +10185,8 @@ export { * * **Example** (Adding a computed field) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" * * const stream = Stream.Do.pipe( * Stream.let("x", () => 2), @@ -10409,14 +10195,13 @@ export { * * const program = Effect.gen(function*() { * const records = yield* Stream.runCollect(stream) - * yield* Console.log(records) + * records // => [ { x: 2, y: 6 } ] * }) * - * Effect.runPromise(program) - * // [{ x: 2, y: 6 }] + * await Effect.runPromise(program) * ``` * - * @category do notation + * @category mapping * @since 2.0.0 */ let_ as let @@ -10427,8 +10212,8 @@ export { * * **Example** (Binding a stream value) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" * * const program = Stream.Do.pipe( * Stream.bind("a", () => Stream.make(1, 2)), @@ -10437,11 +10222,10 @@ export { * * const result = Stream.runCollect(program) * - * Effect.runPromise(Effect.flatMap(result, Console.log)) - * // [{ a: 1, b: 2 }, { a: 2, b: 3 }] + * await Effect.runPromise(result) // => [{ a: 1, b: 2 }, { a: 2, b: 3 }] * ``` * - * @category do notation + * @category sequencing * @since 2.0.0 */ export const bind: { @@ -10478,8 +10262,8 @@ export const bind: { * * **Example** (Binding an effect value) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" * * const stream = Stream.Do.pipe( * Stream.bind("value", () => Stream.make(1, 2)), @@ -10488,14 +10272,13 @@ export const bind: { * * const program = Effect.gen(function*() { * const result = yield* Stream.runCollect(stream) - * yield* Console.log(result) + * result // => [ { value: 1, double: 2 }, { value: 2, double: 4 } ] * }) * - * Effect.runPromise(program) - * // [{ value: 1, double: 2 }, { value: 2, double: 4 }] + * await Effect.runPromise(program) * ``` * - * @category do notation + * @category sequencing * @since 2.0.0 */ export const bindEffect: { @@ -10535,18 +10318,15 @@ export const bindEffect: { * * **Example** (Binding values to a record key) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" * * const stream = Stream.make(1, 2, 3).pipe(Stream.bindTo("value")) * - * const program = Stream.runCollect(stream).pipe(Effect.flatMap(Console.log)) - * - * Effect.runPromise(program) - * // [{ value: 1 }, { value: 2 }, { value: 3 }] + * await Effect.runPromise(Stream.runCollect(stream)) // => [{ value: 1 }, { value: 2 }, { value: 3 }] * ``` * - * @category do notation + * @category mapping * @since 2.0.0 */ export const bindTo: { @@ -10562,13 +10342,12 @@ export const bindTo: { * * **Example** (Running a stream with a sink) * - * ```ts - * import { Console, Effect, Sink, Stream } from "effect" + * ```ts import.meta.vitest + * import { Effect, Sink, Stream } from "effect" * * const program = Stream.run(Stream.make(1, 2, 3), Sink.sum) * - * Effect.runPromise(Effect.flatMap(program, Console.log)) - * // 6 + * await Effect.runPromise(program) // => 6 * ``` * * @category destructors @@ -10598,18 +10377,17 @@ export const run: { * * **Example** (Collecting stream values) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```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) - * yield* Console.log(collected) + * collected // => [ 1, 2, 3, 4, 5 ] * }) * - * Effect.runPromise(program) - * // [1, 2, 3, 4, 5] + * await Effect.runPromise(program) * ``` * * @category destructors @@ -10632,18 +10410,17 @@ export const runCollect = (self: Stream): Effect.Effect 5 * }) * - * Effect.runPromise(program) - * // 5 + * await Effect.runPromise(program) * ``` * * @category destructors @@ -10657,16 +10434,15 @@ export const runCount = (self: Stream): Effect.Effect 6 * }) * - * Effect.runPromise(program) - * // 6 + * await Effect.runPromise(program) * ``` * * @category destructors @@ -10685,8 +10461,8 @@ export const runSum = (self: Stream): Effect.Effect(self: Stream): Effect.Effect 0, * (acc, n) => acc + n * ) - * yield* Console.log(total) + * total // => 6 * }) * - * Effect.runPromise(program) - * // 6 + * await Effect.runPromise(program) * ``` * * @category destructors @@ -10738,8 +10513,8 @@ export const runFold: { * * **Example** (Effectfully folding stream values) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```ts import.meta.vitest + * import { Effect, Stream } from "effect" * * const program = Effect.gen(function*() { * const total = yield* Stream.runFoldEffect( @@ -10747,11 +10522,10 @@ export const runFold: { * () => 0, * (acc, n) => Effect.succeed(acc + n) * ) - * yield* Console.log(total) + * total // => 6 * }) * - * Effect.runPromise(program) - * // 6 + * await Effect.runPromise(program) * ``` * * @category destructors @@ -10795,16 +10569,15 @@ export const runFoldEffect: { * * **Example** (Getting the first stream value) * - * ```ts - * import { Console, Effect, Option, Stream } from "effect" + * ```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)) - * yield* Console.log(Option.getOrThrow(head)) + * Option.getOrThrow(head) // => 1 * }) * - * Effect.runPromise(program) - * // 1 + * await Effect.runPromise(program) * ``` * * @category destructors @@ -10845,19 +10618,18 @@ export const runLast = (self: Stream): Effect.Effect = [] * * const program = Effect.gen(function*() { - * yield* Stream.runForEach(stream, (n) => Console.log(`Processing: ${n}`)) + * yield* Stream.runForEach(stream, (n) => Effect.sync(() => values.push(`Processing: ${n}`))) * }) * - * Effect.runPromise(program) - * // Processing: 1 - * // Processing: 2 - * // Processing: 3 + * await Effect.runPromise(program) + * values // => ["Processing: 1", "Processing: 2", "Processing: 3"] * ``` * * @category destructors @@ -10890,24 +10662,23 @@ export const runForEach: { * * **Example** (Running effects while a predicate holds) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```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* Console.log(`Processing: ${n}`) + * yield* Effect.sync(() => values.push(n)) * return n < 3 * }) * ) * }) * - * Effect.runPromise(program) - * // Processing: 1 - * // Processing: 2 - * // Processing: 3 + * await Effect.runPromise(program) + * values // => [1, 2, 3] * ``` * * @category destructors @@ -10946,19 +10717,20 @@ export const runForEachWhile: { * * **Example** (Consuming stream chunks) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```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) => Console.log(`Processing chunk: ${chunk.join(", ")}`) + * (chunk) => Effect.sync(() => chunks.push(chunk.join(", "))) * ) * }) * - * Effect.runPromise(program) - * // Processing chunk: 1, 2, 3, 4, 5 + * await Effect.runPromise(program) + * chunks // => ["1, 2, 3, 4, 5"] * ``` * * @category destructors @@ -10982,21 +10754,20 @@ export const runForEachArray: { * * **Example** (Draining a stream run) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```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) => Console.log(`Processing: ${n}`)) + * Stream.mapEffect((n) => Effect.sync(() => values.push(n))) * ) * * yield* Stream.runDrain(stream) * }) * - * Effect.runPromise(program) - * // Processing: 1 - * // Processing: 2 - * // Processing: 3 + * await Effect.runPromise(program) + * values // => [1, 2, 3] * ``` * * @category destructors @@ -11014,8 +10785,8 @@ export const runDrain = (self: Stream): Effect.Effect(self: Stream): Effect.Effect [ 1, 2, 3 ] * }) * ) * - * Effect.runPromise(program) - * // [1, 2, 3] + * await Effect.runPromise(program) * ``` * * @category destructors @@ -11043,17 +10813,16 @@ export const toPull = ( * * **Example** (Joining strings from a stream) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```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) - * yield* Console.log(text) + * text // => "Hello World!" * }) * - * Effect.runPromise(program) - * // Hello World! + * await Effect.runPromise(program) * ``` * * @category destructors @@ -11066,57 +10835,63 @@ export const mkString = (self: Stream): Effect.Effect 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 { Console, Effect, Stream } from "effect" + * ```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) - * yield* Console.log([...bytes]) + * const values = Array.from(bytes) // => [1, 2, 3, 4] * }) * - * Effect.runPromise(program) - * // [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 => - Effect.map( - Channel.runFold( - self.channel, - (): { - 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 - } - ) + Channel.mkUint8Array(self.channel) /** * Converts the stream to a `ReadableStream` using the provided services. @@ -11132,11 +10907,13 @@ export const mkUint8Array = (self: Stream): Effect.Effec * * **Example** (Converting to a ReadableStream with services) * - * ```ts + * ```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 @@ -11208,11 +10985,12 @@ export const toReadableStreamWith = dual< * * **Example** (Converting a stream to a ReadableStream) * - * ```ts + * ```ts import.meta.vitest * import { Stream } from "effect" * * const readableStream = Stream.toReadableStream(Stream.make(1, 2, 3)) - * const reader = readableStream.getReader() + * const values = await Array.fromAsync(readableStream) + * values // => [ 1, 2, 3 ] * ``` * * @category destructors @@ -11250,17 +11028,17 @@ export const toReadableStream: { * * **Example** (Creating a ReadableStream effect) * - * ```ts - * import { Console, Effect, Stream } from "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) - * yield* Console.log(readableStream instanceof ReadableStream) // true + * readableStream instanceof ReadableStream // => true * }) * - * Effect.runPromise(effect) + * await Effect.runPromise(effect) * ``` * * @category destructors @@ -11298,22 +11076,13 @@ export const toReadableStreamEffect: { * * **Example** (Converting to an AsyncIterable with services) * - * ```ts + * ```ts import.meta.vitest * import { Context, Stream } from "effect" * * const stream = Stream.make(1, 2, 3) * const iterable = Stream.toAsyncIterableWith(stream, Context.empty()) * - * const collect = async () => { - * const results: Array = [] - * for await (const value of iterable) { - * results.push(value) - * } - * console.log(results) - * } - * - * collect() - * // [ 1, 2, 3 ] + * await Array.fromAsync(iterable) // => [1, 2, 3] * ``` * * @category destructors @@ -11333,32 +11102,69 @@ export const toAsyncIterableWith: { ): AsyncIterable => ({ [Symbol.asyncIterator]() { const runPromise = Effect.runPromiseWith(context) - const runPromiseExit = Effect.runPromiseExitWith(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 } - pull ??= await runPromise(Channel.toPullScoped(self.channel, scope)) - const exit = await runPromiseExit(pull) + 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 { done: true, value: undefined } + return close(Exit.void) } + if (closePromise && Cause.hasInterruptsOnly(exit.cause)) { + return closePromise + } + await closeAndReportError(exit) throw Cause.squash(exit.cause) }, - return(_) { - return runPromise(Effect.as( - Scope.close(scope, Exit.void), - { done: true, value: undefined } - )) + return() { + return close(Exit.void) + }, + async throw(error) { + await closeAndReportError(Exit.die(error)) + throw error } } } @@ -11375,25 +11181,17 @@ export const toAsyncIterableWith: { * * **Example** (Creating an AsyncIterable effect) * - * ```ts + * ```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) - * const values = yield* Effect.promise(async () => { - * const collected: Array = [] - * for await (const value of iterable) { - * collected.push(value) - * } - * return collected - * }) - * yield* Effect.sync(() => console.log(values)) + * return yield* Effect.promise(() => Array.fromAsync(iterable)) * }) * - * Effect.runPromise(program) - * // [ 1, 2, 3 ] + * await Effect.runPromise(program) // => [1, 2, 3] * ``` * * @category destructors @@ -11410,22 +11208,12 @@ export const toAsyncIterableEffect = (self: Stream): Effect.Ef * * **Example** (Converting to an async iterable) * - * ```ts + * ```ts import.meta.vitest * import { Stream } from "effect" * * const stream = Stream.make(1, 2, 3) * - * const collect = async () => { - * const iterable = Stream.toAsyncIterable(stream) - * const values: Array = [] - * for await (const value of iterable) { - * values.push(value) - * } - * console.log(values) - * } - * - * collect() - * // [ 1, 2, 3 ] + * await Array.fromAsync(Stream.toAsyncIterable(stream)) // => [1, 2, 3] * ``` * * @category destructors @@ -11444,8 +11232,8 @@ export const toAsyncIterable = (self: Stream): AsyncIterable => * * **Example** (Running a stream into a PubSub) * - * ```ts - * import { Console, Effect, PubSub, Stream } from "effect" + * ```ts import.meta.vitest + * import { Effect, PubSub, Stream } from "effect" * * const program = Effect.scoped(Effect.gen(function* () { * const pubsub = yield* PubSub.unbounded() @@ -11456,13 +11244,11 @@ export const toAsyncIterable = (self: Stream): AsyncIterable => * const first = yield* PubSub.take(subscription) * const second = yield* PubSub.take(subscription) * - * yield* Console.log(first) - * yield* Console.log(second) + * first // => 1 + * second // => 2 * })) * - * Effect.runPromise(program) - * //=> 1 - * //=> 2 + * await Effect.runPromise(program) * ``` * * @category destructors @@ -11500,8 +11286,8 @@ export const runIntoPubSub: { * * **Example** (Converting a stream to a PubSub for concurrent consumption) * - * ```ts - * import { Console, Effect, PubSub, Stream } from "effect" + * ```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( @@ -11510,8 +11296,9 @@ export const runIntoPubSub: { * const subscription = yield* PubSub.subscribe(pubsub) * const first = yield* PubSub.take(subscription) * - * yield* Console.log(first) + * first // => 1 * })) + * await Effect.runPromise(program) * ``` * * @category destructors @@ -11569,8 +11356,8 @@ export const toPubSub: { * * **Example** (Converting to a PubSub of takes) * - * ```ts - * import { Console, Effect, PubSub, Stream } from "effect" + * ```ts import.meta.vitest + * import { Effect, PubSub, Stream } from "effect" * * const program = Effect.gen(function* () { * const pubsub = yield* Stream.fromArray([1, 2, 3]).pipe( @@ -11580,9 +11367,10 @@ export const toPubSub: { * const take = yield* PubSub.take(subscription) * * if (Array.isArray(take)) { - * yield* Console.log(take) + * take // => [ 1, 2, 3 ] * } * }) + * await Effect.runPromise(Effect.scoped(program)) * ``` * * @category destructors @@ -11638,14 +11426,15 @@ export const toPubSubTake: { * * **Example** (Converting a stream to a Queue for concurrent consumption) * - * ```ts + * ```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) - * return chunk + * chunk // => [ 1, 2, 3 ] * }) + * await Effect.runPromise(Effect.scoped(program)) * ``` * * @category destructors @@ -11689,7 +11478,7 @@ export const toQueue: { * * **Example** (Running a stream into a queue) * - * ```ts + * ```ts import.meta.vitest * import { Cause, Effect, Queue, Stream } from "effect" * * const program = Effect.gen(function*() { @@ -11706,8 +11495,10 @@ export const toQueue: { * ] * const done = yield* Effect.flip(Queue.take(queue)) * - * return { values, done } + * values // => [ 1, 2, 3 ] + * done._tag === "Done" // => true * }) + * await Effect.runPromise(program) * ``` * * @category destructors diff --git a/repos/effect/packages/effect/src/String.ts b/repos/effect/packages/effect/src/String.ts index 98b119fb56..671ba710f6 100644 --- a/repos/effect/packages/effect/src/String.ts +++ b/repos/effect/packages/effect/src/String.ts @@ -46,12 +46,11 @@ export const String = globalThis.String * * **Example** (Checking for strings) * - * ```ts + * ```ts import.meta.vitest * import { String } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(String.isString("a"), true) - * assert.deepStrictEqual(String.isString(1), false) + * String.isString("a") // => true + * String.isString(1) // => false * ``` * * @category guards @@ -65,12 +64,12 @@ export const isString: Refinement = predicate.isString * * **Example** (Comparing strings lexicographically) * - * ```ts + * ```ts import.meta.vitest * import { String } from "effect" * - * console.log(String.Order("apple", "banana")) // -1 - * console.log(String.Order("banana", "apple")) // 1 - * console.log(String.Order("apple", "apple")) // 0 + * String.Order("apple", "banana") // => -1 + * String.Order("banana", "apple") // => 1 + * String.Order("apple", "apple") // => 0 * ``` * * @category instances @@ -83,11 +82,11 @@ export const Order: order.Order = order.String * * **Example** (Comparing strings for equality) * - * ```ts + * ```ts import.meta.vitest * import { String } from "effect" * - * console.log(String.Equivalence("hello", "hello")) // true - * console.log(String.Equivalence("hello", "world")) // false + * String.Equivalence("hello", "hello") // => true + * String.Equivalence("hello", "world") // => false * ``` * * @category instances @@ -104,11 +103,11 @@ export const Equivalence: Equ.Equivalence = Equ.String * * **Example** (Referencing the empty string) * - * ```ts + * ```ts import.meta.vitest * import { String } from "effect" * - * console.log(String.empty) // "" - * console.log(String.isEmpty(String.empty)) // true + * String.empty // => "" + * String.isEmpty(String.empty) // => true * ``` * * @category constants @@ -121,11 +120,13 @@ export const empty: "" = "" as const * * **Example** (Concatenating string literal types) * - * ```ts + * ```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 @@ -138,14 +139,11 @@ export type Concat = `${A}${B}` * * **Example** (Concatenating strings) * - * ```ts + * ```ts import.meta.vitest * import { pipe, String } from "effect" * - * const result1 = String.concat("hello", "world") - * console.log(result1) // "helloworld" - * - * const result2 = pipe("hello", String.concat("world")) - * console.log(result2) // "helloworld" + * String.concat("hello", "world") // => "helloworld" + * pipe("hello", String.concat("world")) // => "helloworld" * ``` * * @category combining @@ -161,12 +159,11 @@ export const concat: { * * **Example** (Converting strings to uppercase) * - * ```ts + * ```ts import.meta.vitest * import { pipe, String } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(pipe("a", String.toUpperCase), "A") - * assert.deepStrictEqual(String.toUpperCase("hello"), "HELLO") + * pipe("a", String.toUpperCase) // => "A" + * String.toUpperCase("hello") // => "HELLO" * ``` * * @category transforming @@ -179,12 +176,11 @@ export const toUpperCase = (self: S): Uppercase => self.toU * * **Example** (Converting strings to lowercase) * - * ```ts + * ```ts import.meta.vitest * import { pipe, String } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(pipe("A", String.toLowerCase), "a") - * assert.deepStrictEqual(String.toLowerCase("HELLO"), "hello") + * pipe("A", String.toLowerCase) // => "a" + * String.toLowerCase("HELLO") // => "hello" * ``` * * @category transforming @@ -197,12 +193,11 @@ export const toLowerCase = (self: T): Lowercase => self.toL * * **Example** (Capitalizing a string) * - * ```ts + * ```ts import.meta.vitest * import { pipe, String } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(pipe("abc", String.capitalize), "Abc") - * assert.deepStrictEqual(String.capitalize("hello"), "Hello") + * pipe("abc", String.capitalize) // => "Abc" + * String.capitalize("hello") // => "Hello" * ``` * * @category transforming @@ -219,12 +214,11 @@ export const capitalize = (self: T): Capitalize => { * * **Example** (Uncapitalizing a string) * - * ```ts + * ```ts import.meta.vitest * import { pipe, String } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(pipe("ABC", String.uncapitalize), "aBC") - * assert.deepStrictEqual(String.uncapitalize("Hello"), "hello") + * pipe("ABC", String.uncapitalize) // => "aBC" + * String.uncapitalize("Hello") // => "hello" * ``` * * @category transforming @@ -246,15 +240,11 @@ export const uncapitalize = (self: T): Uncapitalize => { * * **Example** (Replacing a substring) * - * ```ts + * ```ts import.meta.vitest * import { pipe, String } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(pipe("abc", String.replace("b", "d")), "adc") - * assert.deepStrictEqual( - * pipe("hello world", String.replace("world", "Effect")), - * "hello Effect" - * ) + * pipe("abc", String.replace("b", "d")) // => "adc" + * pipe("hello world", String.replace("world", "Effect")) // => "hello Effect" * ``` * * @category transforming @@ -268,10 +258,12 @@ export const replace = (searchValue: string | RegExp, replaceValue: string) => ( * * **Example** (Trimming whitespace at the type level) * - * ```ts + * ```ts import.meta.vitest * import type { String } from "effect" * * type Result = String.Trim<" hello "> // "hello" + * + * const witness: Result = "hello" * ``` * * @category models @@ -284,12 +276,11 @@ export type Trim = TrimEnd> * * **Example** (Trimming whitespace) * - * ```ts + * ```ts import.meta.vitest * import { String } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(String.trim(" a "), "a") - * assert.deepStrictEqual(String.trim(" hello world "), "hello world") + * String.trim(" a ") // => "a" + * String.trim(" hello world ") // => "hello world" * ``` * * @category transforming @@ -302,10 +293,12 @@ export const trim = (self: A): Trim => self.trim() as Trim< * * **Example** (Trimming leading whitespace at the type level) * - * ```ts + * ```ts import.meta.vitest * import type { String } from "effect" * * type Result = String.TrimStart<" hello"> // "hello" + * + * const witness: Result = "hello" * ``` * * @category models @@ -318,12 +311,11 @@ export type TrimStart = A extends `${" " | "\n" | "\t" | "\r"} * * **Example** (Trimming leading whitespace) * - * ```ts + * ```ts import.meta.vitest * import { String } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(String.trimStart(" a "), "a ") - * assert.deepStrictEqual(String.trimStart(" hello world"), "hello world") + * String.trimStart(" a ") // => "a " + * String.trimStart(" hello world") // => "hello world" * ``` * * @category transforming @@ -336,10 +328,12 @@ export const trimStart = (self: A): TrimStart => self.trimS * * **Example** (Trimming trailing whitespace at the type level) * - * ```ts + * ```ts import.meta.vitest * import type { String } from "effect" * * type Result = String.TrimEnd<"hello "> // "hello" + * + * const witness: Result = "hello" * ``` * * @category models @@ -352,12 +346,11 @@ export type TrimEnd = A extends `${infer B}${" " | "\n" | "\t" * * **Example** (Trimming trailing whitespace) * - * ```ts + * ```ts import.meta.vitest * import { String } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(String.trimEnd(" a "), " a") - * assert.deepStrictEqual(String.trimEnd("hello world "), "hello world") + * String.trimEnd(" a ") // => " a" + * String.trimEnd("hello world ") // => "hello world" * ``` * * @category transforming @@ -370,12 +363,11 @@ export const trimEnd = (self: A): TrimEnd => self.trimEnd() * * **Example** (Slicing strings) * - * ```ts + * ```ts import.meta.vitest * import { pipe, String } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(pipe("abcd", String.slice(1, 3)), "bc") - * assert.deepStrictEqual(pipe("hello world", String.slice(0, 5)), "hello") + * pipe("abcd", String.slice(1, 3)) // => "bc" + * pipe("hello world", String.slice(0, 5)) // => "hello" * ``` * * @category transforming @@ -388,15 +380,14 @@ export const slice = (start?: number, end?: number) => (self: string): string => * * **Example** (Checking for empty strings) * - * ```ts + * ```ts import.meta.vitest * import { String } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(String.isEmpty(""), true) - * assert.deepStrictEqual(String.isEmpty("a"), false) + * String.isEmpty("") // => true + * String.isEmpty("a") // => false * ``` * - * @category predicates + * @category guards * @since 2.0.0 */ export const isEmpty = (self: string): self is "" => self.length === 0 @@ -406,15 +397,14 @@ export const isEmpty = (self: string): self is "" => self.length === 0 * * **Example** (Checking for non-empty strings) * - * ```ts + * ```ts import.meta.vitest * import { String } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(String.isNonEmpty(""), false) - * assert.deepStrictEqual(String.isNonEmpty("a"), true) + * String.isNonEmpty("") // => false + * String.isNonEmpty("a") // => true * ``` * - * @category guards + * @category predicates * @since 2.0.0 */ export const isNonEmpty = (self: string): boolean => self.length > 0 @@ -424,11 +414,10 @@ export const isNonEmpty = (self: string): boolean => self.length > 0 * * **Example** (Getting string length) * - * ```ts + * ```ts import.meta.vitest * import { String } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(String.length("abc"), 3) + * String.length("abc") // => 3 * ``` * * @category getters @@ -441,13 +430,12 @@ export const length = (self: string): number => self.length * * **Example** (Splitting strings) * - * ```ts + * ```ts import.meta.vitest * import { pipe, String } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(pipe("abc", String.split("")), ["a", "b", "c"]) - * assert.deepStrictEqual(pipe("", String.split("")), [""]) - * assert.deepStrictEqual(String.split("hello,world", ","), ["hello", "world"]) + * pipe("abc", String.split("")) // => ["a", "b", "c"] + * pipe("", String.split("")) // => [""] + * String.split("hello,world", ",") // => ["hello", "world"] * ``` * * @category transforming @@ -467,12 +455,11 @@ export const split: { * * **Example** (Checking for substrings) * - * ```ts + * ```ts import.meta.vitest * import { pipe, String } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(pipe("hello world", String.includes("world")), true) - * assert.deepStrictEqual(pipe("hello world", String.includes("foo")), false) + * pipe("hello world", String.includes("world")) // => true + * pipe("hello world", String.includes("foo")) // => false * ``` * * @category predicates @@ -486,12 +473,11 @@ export const includes = (searchString: string, position?: number) => (self: stri * * **Example** (Checking string prefixes) * - * ```ts + * ```ts import.meta.vitest * import { pipe, String } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(pipe("hello world", String.startsWith("hello")), true) - * assert.deepStrictEqual(pipe("hello world", String.startsWith("world")), false) + * pipe("hello world", String.startsWith("hello")) // => true + * pipe("hello world", String.startsWith("world")) // => false * ``` * * @category predicates @@ -505,12 +491,11 @@ export const startsWith = (searchString: string, position?: number) => (self: st * * **Example** (Checking string suffixes) * - * ```ts + * ```ts import.meta.vitest * import { pipe, String } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(pipe("hello world", String.endsWith("world")), true) - * assert.deepStrictEqual(pipe("hello world", String.endsWith("hello")), false) + * pipe("hello world", String.endsWith("world")) // => true + * pipe("hello world", String.endsWith("hello")) // => false * ``` * * @category predicates @@ -524,14 +509,14 @@ export const endsWith = (searchString: string, position?: number) => (self: stri * * **Example** (Reading character codes) * - * ```ts - * import { String } from "effect" + * ```ts import.meta.vitest + * import { Option, String } from "effect" * - * String.charCodeAt("abc", 1) // Option.some(98) - * String.charCodeAt("abc", 4) // Option.none() + * String.charCodeAt("abc", 1) // => Option.some(98) + * String.charCodeAt("abc", 4) // => Option.none() * ``` * - * @category elements + * @category getters * @since 2.0.0 */ export const charCodeAt: { @@ -548,11 +533,11 @@ export const charCodeAt: { * * **Example** (Extracting substrings) * - * ```ts + * ```ts import.meta.vitest * import { pipe, String } from "effect" * - * pipe("abcd", String.substring(1)) // "bcd" - * pipe("abcd", String.substring(1, 3)) // "bc" + * pipe("abcd", String.substring(1)) // => "bcd" + * pipe("abcd", String.substring(1, 3)) // => "bc" * ``` * * @category transforming @@ -565,14 +550,14 @@ export const substring = (start: number, end?: number) => (self: string): string * * **Example** (Accessing characters safely) * - * ```ts - * import { pipe, String } from "effect" + * ```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() + * pipe("abc", String.at(1)) // => Option.some("b") + * pipe("abc", String.at(4)) // => Option.none() * ``` * - * @category elements + * @category getters * @since 2.0.0 */ export const at: { @@ -585,14 +570,14 @@ export const at: { * * **Example** (Reading characters safely) * - * ```ts - * import { pipe, String } from "effect" + * ```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() + * pipe("abc", String.charAt(1)) // => Option.some("b") + * pipe("abc", String.charAt(4)) // => Option.none() * ``` * - * @category elements + * @category getters * @since 2.0.0 */ export const charAt: { @@ -608,14 +593,14 @@ export const charAt: { * * **Example** (Reading code points) * - * ```ts - * import { pipe, String } from "effect" + * ```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() + * pipe("abc", String.codePointAt(1)) // => Option.some(98) + * pipe("abc", String.codePointAt(10)) // => Option.none() * ``` * - * @category elements + * @category getters * @since 2.0.0 */ export const codePointAt: { @@ -628,11 +613,11 @@ export const codePointAt: { * * **Example** (Finding the first substring index) * - * ```ts - * import { pipe, String } from "effect" + * ```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() + * pipe("abbbc", String.indexOf("b")) // => Option.some(1) + * pipe("abbbc", String.indexOf("z")) // => Option.none() * ``` * * @category searching @@ -646,11 +631,11 @@ export const indexOf = (searchString: string) => (self: string): Option.Option Option.some(3) + * pipe("abbbc", String.lastIndexOf("d")) // => Option.none() * ``` * * @category searching @@ -666,16 +651,15 @@ export const lastIndexOf = (searchString: string) => (self: string): Option.Opti * * **Example** (Comparing strings by locale) * - * ```ts + * ```ts import.meta.vitest * import { pipe, String } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(pipe("a", String.localeCompare("b")), -1) - * assert.deepStrictEqual(pipe("b", String.localeCompare("a")), 1) - * assert.deepStrictEqual(pipe("a", String.localeCompare("a")), 0) + * pipe("a", String.localeCompare("b")) // => -1 + * pipe("b", String.localeCompare("a")) // => 1 + * pipe("a", String.localeCompare("a")) // => 0 * ``` * - * @category comparing + * @category comparisons * @since 2.0.0 */ export const localeCompare = @@ -688,16 +672,15 @@ export const localeCompare = * * **Example** (Matching regular expressions) * - * ```ts + * ```ts import.meta.vitest * import { Option, pipe, String } from "effect" * - * const match = pipe("hello", String.match(/l+/)) - * - * if (Option.isSome(match)) { - * console.log(`${match.value[0]}@${match.value.index}`) // "ll@2" - * } - * - * console.log(Option.isNone(pipe("hello", String.match(/x/)))) // true + * 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 @@ -712,13 +695,12 @@ export const match = (regExp: RegExp | string) => (self: string): Option.Option< * * **Example** (Iterating regular expression matches) * - * ```ts + * ```ts import.meta.vitest * import { pipe, String } from "effect" * * const matches = pipe("hello world", String.matchAll(/l/g)) - * console.log( - * Array.from(matches, (match) => `${match[0]}@${match.index}`).join(", ") - * ) // "l@2, l@3, l@9" + * + * Array.from(matches, (match) => [match[0], match.index]) // => [["l", 2], ["l", 3], ["l", 9]] * ``` * * @category searching @@ -731,19 +713,21 @@ export const matchAll = (regExp: RegExp) => (self: string): IterableIterator 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")), - * "\u0073\u0323\u0307" - * ) + * (character) => character.codePointAt(0) + * ) // => [0x73, 0x323, 0x307] * ``` * * @category transforming @@ -756,12 +740,11 @@ export const normalize = (form?: "NFC" | "NFD" | "NFKC" | "NFKD") => (self: stri * * **Example** (Padding strings at the end) * - * ```ts + * ```ts import.meta.vitest * import { pipe, String } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(pipe("a", String.padEnd(5)), "a ") - * assert.deepStrictEqual(pipe("a", String.padEnd(5, "_")), "a____") + * pipe("a", String.padEnd(5)) // => "a " + * pipe("a", String.padEnd(5, "_")) // => "a____" * ``` * * @category transforming @@ -775,12 +758,11 @@ export const padEnd = (maxLength: number, fillString?: string) => (self: string) * * **Example** (Padding strings at the start) * - * ```ts + * ```ts import.meta.vitest * import { pipe, String } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(pipe("a", String.padStart(5)), " a") - * assert.deepStrictEqual(pipe("a", String.padStart(5, "_")), "____a") + * pipe("a", String.padStart(5)) // => " a" + * pipe("a", String.padStart(5, "_")) // => "____a" * ``` * * @category transforming @@ -794,12 +776,11 @@ export const padStart = (maxLength: number, fillString?: string) => (self: strin * * **Example** (Repeating strings) * - * ```ts + * ```ts import.meta.vitest * import { pipe, String } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(pipe("a", String.repeat(5)), "aaaaa") - * assert.deepStrictEqual(pipe("hello", String.repeat(3)), "hellohellohello") + * pipe("a", String.repeat(5)) // => "aaaaa" + * pipe("hello", String.repeat(3)) // => "hellohellohello" * ``` * * @category transforming @@ -812,12 +793,11 @@ export const repeat = (count: number) => (self: string): string => self.repeat(c * * **Example** (Replacing all matches) * - * ```ts + * ```ts import.meta.vitest * import { pipe, String } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(pipe("ababb", String.replaceAll("b", "c")), "acacc") - * assert.deepStrictEqual(pipe("ababb", String.replaceAll(/ba/g, "cc")), "accbb") + * pipe("ababb", String.replaceAll("b", "c")) // => "acacc" + * pipe("ababb", String.replaceAll(/ba/g, "cc")) // => "accbb" * ``` * * @category transforming @@ -832,12 +812,12 @@ export const replaceAll = (searchValue: string | RegExp, replaceValue: string) = * * **Example** (Searching strings) * - * ```ts - * import { String } from "effect" + * ```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() + * String.search("ababb", "b") // => Option.some(1) + * String.search("ababb", /abb/) // => Option.some(2) + * String.search("ababb", "d") // => Option.none() * ``` * * @category searching @@ -857,12 +837,11 @@ export const search: { * * **Example** (Lowercasing strings by locale) * - * ```ts + * ```ts import.meta.vitest * import { pipe, String } from "effect" - * import * as assert from "node:assert" * * const str = "\u0130" - * assert.deepStrictEqual(pipe(str, String.toLocaleLowerCase("tr")), "i") + * pipe(str, String.toLocaleLowerCase("tr")) // => "i" * ``` * * @category transforming @@ -876,12 +855,11 @@ export const toLocaleLowerCase = (locale?: string | Array) => (self: str * * **Example** (Uppercasing strings by locale) * - * ```ts + * ```ts import.meta.vitest * import { pipe, String } from "effect" - * import * as assert from "node:assert" * * const str = "i\u0307" - * assert.deepStrictEqual(pipe(str, String.toLocaleUpperCase("lt-LT")), "I") + * pipe(str, String.toLocaleUpperCase("lt-LT")) // => "I" * ``` * * @category transforming @@ -904,11 +882,10 @@ export const toLocaleUpperCase = (locale?: string | Array) => (self: str * * **Example** (Taking characters from the start) * - * ```ts + * ```ts import.meta.vitest * import { String } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(String.takeLeft("Hello World", 5), "Hello") + * String.takeLeft("Hello World", 5) // => "Hello" * ``` * * @category transforming @@ -933,11 +910,10 @@ export const takeLeft: { * * **Example** (Taking characters from the end) * - * ```ts + * ```ts import.meta.vitest * import { String } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(String.takeRight("Hello World", 5), "World") + * String.takeRight("Hello World", 5) // => "World" * ``` * * @category transforming @@ -960,11 +936,10 @@ const LF = 0x0a * * **Example** (Iterating lines without separators) * - * ```ts + * ```ts import.meta.vitest * import { String } from "effect" * - * const lines = String.linesIterator("hello\nworld\n") - * console.log(Array.from(lines)) // ["hello", "world"] + * Array.from(String.linesIterator("hello\nworld\n")) // => ["hello", "world"] * ``` * * @category splitting @@ -978,11 +953,10 @@ export const linesIterator = (self: string): LinesIterator => linesSeparated(sel * * **Example** (Iterating lines with separators) * - * ```ts + * ```ts import.meta.vitest * import { String } from "effect" * - * const lines = String.linesWithSeparators("hello\nworld\n") - * console.log(Array.from(lines)) // ["hello\n", "world\n"] + * Array.from(String.linesWithSeparators("hello\nworld\n")) // => ["hello\n", "world\n"] * ``` * * @category splitting @@ -996,12 +970,10 @@ export const linesWithSeparators = (s: string): LinesIterator => linesSeparated( * * **Example** (Stripping custom margins) * - * ```ts + * ```ts import.meta.vitest * import { String } from "effect" * - * const text = " |hello\n |world" - * const result = String.stripMarginWith(text, "|") - * console.log(result) // "hello\nworld" + * String.stripMarginWith(" |hello\n |world", "|") // => "hello\nworld" * ``` * * @category transforming @@ -1035,12 +1007,10 @@ export const stripMarginWith: { * * **Example** (Stripping pipe margins) * - * ```ts + * ```ts import.meta.vitest * import { String } from "effect" * - * const text = " |hello\n |world" - * const result = String.stripMargin(text) - * console.log(result) // "hello\nworld" + * String.stripMargin(" |hello\n |world") // => "hello\nworld" * ``` * * @category transforming @@ -1053,17 +1023,18 @@ export const stripMargin = (self: string): string => stripMarginWith(self, "|") * * **Example** (Converting snake_case to camelCase) * - * ```ts + * ```ts import.meta.vitest * import { String } from "effect" * - * console.log(String.snakeToCamel("hello_world")) // "helloWorld" - * console.log(String.snakeToCamel("foo_bar_baz")) // "fooBarBaz" + * 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] @@ -1076,17 +1047,18 @@ export const snakeToCamel = (self: string): string => { * * **Example** (Converting snake_case to PascalCase) * - * ```ts + * ```ts import.meta.vitest * import { String } from "effect" * - * console.log(String.snakeToPascal("hello_world")) // "HelloWorld" - * console.log(String.snakeToPascal("foo_bar_baz")) // "FooBarBaz" + * 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] @@ -1099,11 +1071,11 @@ export const snakeToPascal = (self: string): string => { * * **Example** (Converting snake_case to kebab-case) * - * ```ts + * ```ts import.meta.vitest * import { String } from "effect" * - * console.log(String.snakeToKebab("hello_world")) // "hello-world" - * console.log(String.snakeToKebab("foo_bar_baz")) // "foo-bar-baz" + * String.snakeToKebab("hello_world") // => "hello-world" + * String.snakeToKebab("foo_bar_baz") // => "foo-bar-baz" * ``` * * @category transforming @@ -1116,11 +1088,11 @@ export const snakeToKebab = (self: string): string => self.replace(/_/g, "-") * * **Example** (Converting camelCase to snake_case) * - * ```ts + * ```ts import.meta.vitest * import { String } from "effect" * - * console.log(String.camelToSnake("helloWorld")) // "hello_world" - * console.log(String.camelToSnake("fooBarBaz")) // "foo_bar_baz" + * String.camelToSnake("helloWorld") // => "hello_world" + * String.camelToSnake("fooBarBaz") // => "foo_bar_baz" * ``` * * @category transforming @@ -1133,11 +1105,11 @@ export const camelToSnake = (self: string): string => self.replace(/([A-Z])/g, " * * **Example** (Converting PascalCase to snake_case) * - * ```ts + * ```ts import.meta.vitest * import { String } from "effect" * - * console.log(String.pascalToSnake("HelloWorld")) // "hello_world" - * console.log(String.pascalToSnake("FooBarBaz")) // "foo_bar_baz" + * String.pascalToSnake("HelloWorld") // => "hello_world" + * String.pascalToSnake("FooBarBaz") // => "foo_bar_baz" * ``` * * @category transforming @@ -1151,11 +1123,11 @@ export const pascalToSnake = (self: string): string => * * **Example** (Converting kebab-case to snake_case) * - * ```ts + * ```ts import.meta.vitest * import { String } from "effect" * - * console.log(String.kebabToSnake("hello-world")) // "hello_world" - * console.log(String.kebabToSnake("foo-bar-baz")) // "foo_bar_baz" + * String.kebabToSnake("hello-world") // => "hello_world" + * String.kebabToSnake("foo-bar-baz") // => "foo_bar_baz" * ``` * * @category transforming @@ -1264,12 +1236,30 @@ export const noCase: { 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 - const result = input - .replace(SPLIT_REGEXP[0], "$1\0$2") - .replace(SPLIT_REGEXP[1], "$1\0$2") - .replace(STRIP_REGEXP, "\0") + 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. @@ -1282,20 +1272,21 @@ export const noCase: { // 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] -// Support camel case ("camelCase" -> "camel Case" and "CAMELCase" -> "CAMEL Case"). -const SPLIT_REGEXP = [/([a-z0-9])([A-Z])/g, /([A-Z])([A-Z][a-z])/g] +// 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, index: number): string => { +const pascalCaseTransform = (input: string): string => { const firstChar = input.charAt(0) const lowerChars = input.substring(1).toLowerCase() - if (index > 0 && firstChar >= "0" && firstChar <= "9") { - return `_${firstChar}${lowerChars}` - } return `${firstChar.toUpperCase()}${lowerChars}` } @@ -1322,7 +1313,7 @@ export const pascalCase: (self: string) => string = noCase({ const camelCaseTransform = (input: string, index: number): string => index === 0 ? input.toLowerCase() - : pascalCaseTransform(input, index) + : pascalCaseTransform(input) /** * Converts a string to camelCase. @@ -1358,6 +1349,7 @@ export const camelCase: (self: string) => string = noCase({ * @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 @@ -1368,6 +1360,27 @@ export const constantCase: (self: string) => string = noCase({ 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). * diff --git a/repos/effect/packages/effect/src/Struct.ts b/repos/effect/packages/effect/src/Struct.ts index 9f0318f555..14416763e4 100644 --- a/repos/effect/packages/effect/src/Struct.ts +++ b/repos/effect/packages/effect/src/Struct.ts @@ -14,6 +14,7 @@ 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" @@ -31,7 +32,7 @@ import * as Reducer from "./Reducer.ts" * * **Example** (Flattening an intersection) * - * ```ts + * ```ts import.meta.vitest * import type { Struct } from "effect" * * type Original = { a: string } & { b: number } @@ -39,6 +40,8 @@ import * as Reducer from "./Reducer.ts" * // 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` @@ -62,12 +65,16 @@ export type Simplify = { [K in keyof T]: T[K] } & {} * * **Example** (Making a readonly type mutable) * - * ```ts + * ```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` @@ -91,13 +98,15 @@ export type Mutable = { -readonly [K in keyof T]: T[K] } & {} * * **Example** (Merging two types with overlapping keys) * - * ```ts + * ```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 @@ -120,11 +129,10 @@ export type Assign = Simplify "Alice" * ``` * * @see {@link keys} – list all string keys of a struct @@ -151,18 +159,18 @@ export const get: { * * **Example** (Reading typed keys) * - * ```ts + * ```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) - * console.log(k) // ["name", "age"] + * 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 Key utilities + * @category getters * @since 3.6.0 */ export const keys = (self: S): Array<(keyof S) & string> => @@ -181,12 +189,11 @@ export const keys = (self: S): Array<(keyof S) & string> => * * **Example** (Selecting specific properties) * - * ```ts + * ```ts import.meta.vitest * import { pipe, Struct } from "effect" * * const user = { name: "Alice", age: 30, admin: true } - * const nameAndAge = pipe(user, Struct.pick(["name", "age"])) - * console.log(nameAndAge) // { name: "Alice", age: 30 } + * pipe(user, Struct.pick(["name", "age"])) // => { name: "Alice", age: 30 } * ``` * * @see {@link omit} – the inverse (exclude keys instead) @@ -219,12 +226,11 @@ export const pick: { * * **Example** (Removing a property) * - * ```ts + * ```ts import.meta.vitest * import { pipe, Struct } from "effect" * * const user = { name: "Alice", age: 30, password: "secret" } - * const safe = pipe(user, Struct.omit(["password"])) - * console.log(safe) // { name: "Alice", age: 30 } + * pipe(user, Struct.omit(["password"])) // => { name: "Alice", age: 30 } * ``` * * @see {@link pick} – the inverse (keep only specified keys) @@ -257,13 +263,12 @@ export const omit: { * * **Example** (Merging structs with overlapping keys) * - * ```ts + * ```ts import.meta.vitest * import { pipe, Struct } from "effect" * * const defaults = { theme: "light", lang: "en" } * const overrides = { theme: "dark", fontSize: 14 } - * const config = pipe(defaults, Struct.assign(overrides)) - * console.log(config) // { theme: "dark", lang: "en", fontSize: 14 } + * pipe(defaults, Struct.assign(overrides)) // => { theme: "dark", lang: "en", fontSize: 14 } * ``` * * @see {@link Assign} – the type-level equivalent @@ -302,7 +307,7 @@ type Evolved = Simplify< * * **Example** (Transforming selected values) * - * ```ts + * ```ts import.meta.vitest * import { pipe, Struct } from "effect" * * const result = pipe( @@ -312,7 +317,7 @@ type Evolved = Simplify< * age: (n) => n + 1 * }) * ) - * console.log(result) // { name: "ALICE", age: 31, active: true } + * result // => { name: "ALICE", age: 31, active: true } * ``` * * @see {@link evolveKeys} – transform keys instead of values @@ -352,7 +357,7 @@ type KeyEvolved = Simplify< * * **Example** (Renaming keys with functions) * - * ```ts + * ```ts import.meta.vitest * import { pipe, Struct } from "effect" * * const result = pipe( @@ -361,13 +366,13 @@ type KeyEvolved = Simplify< * name: (k) => k.toUpperCase() * }) * ) - * console.log(result) // { NAME: "Alice", age: 30 } + * 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 Key utilities + * @category transforming * @since 4.0.0 */ export const evolveKeys: { @@ -407,7 +412,7 @@ type EntryEvolved = { * * **Example** (Transforming keys and values together) * - * ```ts + * ```ts import.meta.vitest * import { pipe, Struct } from "effect" * * const result = pipe( @@ -417,7 +422,7 @@ type EntryEvolved = { * label: (k, v) => [k, v.toUpperCase()] * }) * ) - * console.log(result) // { amountCents: 10000, label: "TOTAL" } + * result // => { amountCents: 10000, label: "TOTAL" } * ``` * * @see {@link evolve} – transform values only @@ -449,19 +454,19 @@ export const evolveEntries: { * * **Example** (Renaming keys) * - * ```ts + * ```ts import.meta.vitest * import { pipe, Struct } from "effect" * * const result = pipe( * { firstName: "Alice", lastName: "Smith", age: 30 }, * Struct.renameKeys({ firstName: "first", lastName: "last" }) * ) - * console.log(result) // { first: "Alice", last: "Smith", age: 30 } + * result // => { first: "Alice", last: "Smith", age: 30 } * ``` * * @see {@link evolveKeys} – rename keys using functions * @see {@link evolveEntries} – rename keys and transform values - * @category Key utilities + * @category transforming * @since 4.0.0 */ export const renameKeys: { @@ -494,7 +499,7 @@ export const renameKeys: { * * **Example** (Comparing structs for equivalence) * - * ```ts + * ```ts import.meta.vitest * import { Equivalence, Struct } from "effect" * * const PersonEquivalence = Struct.makeEquivalence({ @@ -502,10 +507,8 @@ export const renameKeys: { * age: Equivalence.strictEqual() * }) * - * console.log(PersonEquivalence({ name: "Alice", age: 30 }, { name: "Alice", age: 30 })) - * // true - * console.log(PersonEquivalence({ name: "Alice", age: 30 }, { name: "Bob", age: 30 })) - * // false + * 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 @@ -531,7 +534,7 @@ export const makeEquivalence = Equivalence.Struct * * **Example** (Ordering structs by name then age) * - * ```ts + * ```ts import.meta.vitest * import { Number, String, Struct } from "effect" * * const PersonOrder = Struct.makeOrder({ @@ -539,8 +542,7 @@ export const makeEquivalence = Equivalence.Struct * age: Number.Order * }) * - * console.log(PersonOrder({ name: "Alice", age: 30 }, { name: "Bob", age: 25 })) - * // -1 (Alice comes before Bob) + * PersonOrder({ name: "Alice", age: 30 }, { name: "Bob", age: 25 }) // => -1 * ``` * * @see {@link makeEquivalence} – create an `Equivalence` for structs @@ -566,18 +568,20 @@ export const makeOrder = order.Struct * * **Example** (Defining a lambda type) * - * ```ts + * ```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 Lambda + * @category utility types * @since 4.0.0 */ export interface Lambda { @@ -601,19 +605,21 @@ export interface Lambda { * * **Example** (Computing the output type of a lambda) * - * ```ts + * ```ts import.meta.vitest * import type { Struct } from "effect" * * interface ToString extends Struct.Lambda { * readonly "~lambda.out": string * } * - * // Result is `string` + * // string * type Result = Struct.Apply + * + * const witness: Result = "value" * ``` * * @see {@link Lambda} – the base interface - * @category Lambda + * @category utility types * @since 4.0.0 */ export type Apply = (L & { readonly "~lambda.in": V })["~lambda.out"] @@ -636,7 +642,7 @@ export type Apply = (L & { readonly "~lambda.in": V })["~la * * **Example** (Wrapping values in arrays) * - * ```ts + * ```ts import.meta.vitest * import { pipe, Struct } from "effect" * * interface AsArray extends Struct.Lambda { @@ -646,12 +652,12 @@ export type Apply = (L & { readonly "~lambda.in": V })["~la * * const asArray = Struct.lambda((a) => [a]) * const result = pipe({ x: 1, y: "hello" }, Struct.map(asArray)) - * console.log(result) // { x: [1], y: ["hello"] } + * result // => { x: [1], y: ["hello"] } * ``` * * @see {@link Lambda} – the type-level interface * @see {@link map} – apply a lambda to all struct values - * @category Lambda + * @category constructors * @since 4.0.0 */ export const lambda = any>( @@ -672,7 +678,7 @@ export const lambda = any>( * * **Example** (Wrapping every value in an array) * - * ```ts + * ```ts import.meta.vitest * import { pipe, Struct } from "effect" * * interface AsArray extends Struct.Lambda { @@ -682,7 +688,7 @@ export const lambda = any>( * * const asArray = Struct.lambda((a) => [a]) * const result = pipe({ width: 10, height: 20 }, Struct.map(asArray)) - * console.log(result) // { width: [10], height: [20] } + * result // => { width: [10], height: [20] } * ``` * * @see {@link mapPick} – apply a lambda only to selected keys @@ -716,7 +722,7 @@ export const map: { * * **Example** (Wrapping only selected values in arrays) * - * ```ts + * ```ts import.meta.vitest * import { pipe, Struct } from "effect" * * interface AsArray extends Struct.Lambda { @@ -729,7 +735,7 @@ export const map: { * { x: 1, y: 2, z: 3 }, * Struct.mapPick(["x", "z"], asArray) * ) - * console.log(result) // { x: [1], y: 2, z: [3] } + * result // => { x: [1], y: 2, z: [3] } * ``` * * @see {@link map} – apply a lambda to all keys @@ -770,7 +776,7 @@ export const mapPick: { * * **Example** (Wrapping all values except one in arrays) * - * ```ts + * ```ts import.meta.vitest * import { pipe, Struct } from "effect" * * interface AsArray extends Struct.Lambda { @@ -783,7 +789,7 @@ export const mapPick: { * { x: 1, y: 2, z: 3 }, * Struct.mapOmit(["y"], asArray) * ) - * console.log(result) // { x: [1], y: 2, z: [3] } + * result // => { x: [1], y: 2, z: [3] } * ``` * * @see {@link map} – apply a lambda to all keys @@ -836,7 +842,7 @@ function buildStruct< const res = f(k, source[k]) if (res) { const [nk, nv] = res - out[nk] = nv + InternalRecord.assignProperty(out, nk, nv) } } return out @@ -859,7 +865,7 @@ function buildStruct< * * **Example** (Combining struct properties) * - * ```ts + * ```ts import.meta.vitest * import { Number, String, Struct } from "effect" * * const C = Struct.makeCombiner<{ readonly n: number; readonly s: string }>({ @@ -867,8 +873,7 @@ function buildStruct< * s: String.ReducerConcat * }) * - * const result = C.combine({ n: 1, s: "hello" }, { n: 2, s: " world" }) - * console.log(result) // { n: 3, s: "hello world" } + * 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 @@ -888,7 +893,7 @@ export function makeCombiner( for (const key of keys) { const merge = combiners[key].combine(self[key], that[key]) if (omitKeyWhen(merge)) continue - out[key] = merge + InternalRecord.assignProperty(out as object, key, merge) } return out }) @@ -912,7 +917,7 @@ export function makeCombiner( * * **Example** (Reducing a collection of structs) * - * ```ts + * ```ts import.meta.vitest * import { Number, String, Struct } from "effect" * * const R = Struct.makeReducer<{ readonly n: number; readonly s: string }>({ @@ -925,7 +930,7 @@ export function makeCombiner( * { n: 2, s: "b" }, * { n: 3, s: "c" } * ]) - * console.log(result) // { n: 6, s: "abc" } + * result // => { n: 6, s: "abc" } * ``` * * @see {@link makeCombiner} – like `makeReducer` but without an initial value @@ -943,7 +948,7 @@ export function makeReducer( for (const key of Reflect.ownKeys(reducers) as Array) { const iv = reducers[key].initialValue if (options?.omitKeyWhen?.(iv)) continue - initialValue[key] = iv + InternalRecord.assignProperty(initialValue as object, key, iv) } return Reducer.make(combine, initialValue) } @@ -957,11 +962,10 @@ export function makeReducer( * * **Example** (Creating a record) * - * ```ts + * ```ts import.meta.vitest * import { Struct } from "effect" * - * const record = Struct.Record(["a", "b"], "value") - * console.log(record) // { a: "value", b: "value" } + * Struct.Record(["a", "b"], "value") // => { a: "value", b: "value" } * ``` * * @category constructors @@ -973,7 +977,7 @@ export function Record, Value> ): Record { const out: any = {} for (const key of keys) { - out[key] = value + 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 index 57a9f487b5..1a4adf7322 100644 --- a/repos/effect/packages/effect/src/SubscriptionRef.ts +++ b/repos/effect/packages/effect/src/SubscriptionRef.ts @@ -129,7 +129,7 @@ export const make = (value: A): Effect.Effect> => * * **Example** (Streaming changes) * - * ```ts + * ```ts import.meta.vitest * import { Deferred, Effect, Fiber, Stream, SubscriptionRef } from "effect" * * const program = Effect.gen(function*() { @@ -148,13 +148,13 @@ export const make = (value: A): Effect.Effect> => * yield* SubscriptionRef.set(ref, 2) * * const values = yield* Fiber.join(fiber) - * console.log(values) // [ 0, 1, 2 ] + * return Array.from(values) * }) * - * Effect.runPromise(program) + * await Effect.runPromise(program) // => [0, 1, 2] * ``` * - * @category changes + * @category subscriptions * @since 4.0.0 */ export const changes = (self: SubscriptionRef): Stream.Stream => Stream.fromPubSub(self.pubsub) @@ -175,15 +175,16 @@ export const changes = (self: SubscriptionRef): Stream.Stream => Stream * * **Example** (Reading the current value unsafely) * - * ```ts + * ```ts import.meta.vitest * import { Effect, SubscriptionRef } from "effect" * * const program = Effect.gen(function*() { * const ref = yield* SubscriptionRef.make(42) * - * const value = SubscriptionRef.getUnsafe(ref) - * console.log(value) + * return SubscriptionRef.getUnsafe(ref) * }) + * + * await Effect.runPromise(program) // => 42 * ``` * * @category getters @@ -196,15 +197,16 @@ export const getUnsafe = (self: SubscriptionRef): A => self.value * * **Example** (Reading the current value) * - * ```ts + * ```ts import.meta.vitest * import { Effect, SubscriptionRef } from "effect" * * const program = Effect.gen(function*() { * const ref = yield* SubscriptionRef.make(42) * - * const value = yield* SubscriptionRef.get(ref) - * console.log(value) + * return yield* SubscriptionRef.get(ref) * }) + * + * await Effect.runPromise(program) // => 42 * ``` * * @category getters @@ -218,18 +220,18 @@ export const get = (self: SubscriptionRef): Effect.Effect => Effect.syn * * **Example** (Getting and setting a value) * - * ```ts + * ```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) - * console.log("Old value:", oldValue) - * * const newValue = yield* SubscriptionRef.get(ref) - * console.log("New value:", newValue) + * return [oldValue, newValue] * }) + * + * await Effect.runPromise(program) // => [10, 20] * ``` * * @category getters @@ -256,18 +258,18 @@ const setUnsafe = (self: SubscriptionRef, value: A) => { * * **Example** (Getting and updating a value) * - * ```ts + * ```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) - * console.log("Old value:", oldValue) - * * const newValue = yield* SubscriptionRef.get(ref) - * console.log("New value:", newValue) + * return [oldValue, newValue] * }) + * + * await Effect.runPromise(program) // => [10, 20] * ``` * * @category getters @@ -290,7 +292,7 @@ export const getAndUpdate: { * * **Example** (Getting and updating with an effect) * - * ```ts + * ```ts import.meta.vitest * import { Effect, SubscriptionRef } from "effect" * * const program = Effect.gen(function*() { @@ -300,11 +302,11 @@ export const getAndUpdate: { * ref, * (n) => Effect.succeed(n + 5) * ) - * console.log("Old value:", oldValue) - * * const newValue = yield* SubscriptionRef.get(ref) - * console.log("New value:", newValue) + * return [oldValue, newValue] * }) + * + * await Effect.runPromise(program) // => [10, 15] * ``` * * @category getters @@ -317,7 +319,7 @@ export const getAndUpdateEffect: { self: SubscriptionRef, update: (a: A) => Effect.Effect ) => - self.semaphore.withPermit(Effect.sync(() => { + self.semaphore.withPermit(Effect.suspend(() => { const current = self.value return Effect.map(update(current), (newValue) => { setUnsafe(self, newValue) @@ -341,7 +343,7 @@ export const getAndUpdateEffect: { * * **Example** (Getting and conditionally updating a value) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Option, SubscriptionRef } from "effect" * * const program = Effect.gen(function*() { @@ -351,11 +353,11 @@ export const getAndUpdateEffect: { * ref, * (n) => n > 5 ? Option.some(n * 2) : Option.none() * ) - * console.log("Old value:", oldValue) - * * const newValue = yield* SubscriptionRef.get(ref) - * console.log("New value:", newValue) + * return [oldValue, newValue] * }) + * + * await Effect.runPromise(program) // => [10, 20] * ``` * * @category getters @@ -372,7 +374,7 @@ export const getAndUpdateSome: { const current = self.value const option = update(current) if (Option.isNone(option)) { - return Effect.succeed(current) + return current } setUnsafe(self, option.value) return current @@ -394,7 +396,7 @@ export const getAndUpdateSome: { * * **Example** (Getting and conditionally updating with an effect) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Option, SubscriptionRef } from "effect" * * const program = Effect.gen(function*() { @@ -404,11 +406,11 @@ export const getAndUpdateSome: { * ref, * (n) => Effect.succeed(n > 5 ? Option.some(n + 3) : Option.none()) * ) - * console.log("Old value:", oldValue) - * * const newValue = yield* SubscriptionRef.get(ref) - * console.log("New value:", newValue) + * return [oldValue, newValue] * }) + * + * await Effect.runPromise(program) // => [10, 13] * ``` * * @category getters @@ -441,7 +443,7 @@ export const getAndUpdateSomeEffect: { * * **Example** (Modifying a value) * - * ```ts + * ```ts import.meta.vitest * import { Effect, SubscriptionRef } from "effect" * * const program = Effect.gen(function*() { @@ -451,14 +453,14 @@ export const getAndUpdateSomeEffect: { * `Old value was ${n}`, * n * 2 * ]) - * console.log(result) - * * const newValue = yield* SubscriptionRef.get(ref) - * console.log("New value:", newValue) + * return [result, newValue] * }) + * + * await Effect.runPromise(program) // => ["Old value was 10", 20] * ``` * - * @category modifications + * @category mutations * @since 2.0.0 */ export const modify: { @@ -481,7 +483,7 @@ export const modify: { * * **Example** (Modifying with an effect) * - * ```ts + * ```ts import.meta.vitest * import { Effect, SubscriptionRef } from "effect" * * const program = Effect.gen(function*() { @@ -491,14 +493,14 @@ export const modify: { * ref, * (n) => Effect.succeed([`Doubled from ${n}`, n * 2] as const) * ) - * console.log(result) - * * const newValue = yield* SubscriptionRef.get(ref) - * console.log("New value:", newValue) + * return [result, newValue] * }) + * + * await Effect.runPromise(program) // => ["Doubled from 10", 20] * ``` * - * @category modifications + * @category mutations * @since 2.0.0 */ export const modifyEffect: { @@ -536,7 +538,7 @@ export const modifyEffect: { * * **Example** (Conditionally modifying a value) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Option, SubscriptionRef } from "effect" * * const program = Effect.gen(function*() { @@ -547,14 +549,14 @@ export const modifyEffect: { * (n) => * n > 5 ? ["Updated", Option.some(n * 2)] : ["Not updated", Option.none()] * ) - * console.log(result) - * * const newValue = yield* SubscriptionRef.get(ref) - * console.log("New value:", newValue) + * return [result, newValue] * }) + * + * await Effect.runPromise(program) // => ["Updated", 20] * ``` * - * @category modifications + * @category mutations * @since 2.0.0 */ export const modifySome: { @@ -592,7 +594,7 @@ export const modifySome: { * * **Example** (Conditionally modifying with an effect) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Option, SubscriptionRef } from "effect" * * const program = Effect.gen(function*() { @@ -607,14 +609,14 @@ export const modifySome: { * : (["Not updated", Option.none()] as const) * ) * ) - * console.log(result) - * * const newValue = yield* SubscriptionRef.get(ref) - * console.log("New value:", newValue) + * return [result, newValue] * }) + * + * await Effect.runPromise(program) // => ["Updated", 15] * ``` * - * @category modifications + * @category mutations * @since 2.0.0 */ export const modifySomeEffect: { @@ -643,7 +645,7 @@ export const modifySomeEffect: { * * **Example** (Setting a value) * - * ```ts + * ```ts import.meta.vitest * import { Effect, SubscriptionRef } from "effect" * * const program = Effect.gen(function*() { @@ -651,12 +653,13 @@ export const modifySomeEffect: { * * yield* SubscriptionRef.set(ref, 42) * - * const value = yield* SubscriptionRef.get(ref) - * console.log(value) + * return yield* SubscriptionRef.get(ref) * }) + * + * await Effect.runPromise(program) // => 42 * ``` * - * @category setters + * @category mutations * @since 2.0.0 */ export const set: { @@ -673,18 +676,19 @@ export const set: { * * **Example** (Setting and reading the new value) * - * ```ts + * ```ts import.meta.vitest * import { Effect, SubscriptionRef } from "effect" * * const program = Effect.gen(function*() { * const ref = yield* SubscriptionRef.make(0) * - * const newValue = yield* SubscriptionRef.setAndGet(ref, 42) - * console.log("New value:", newValue) + * return yield* SubscriptionRef.setAndGet(ref, 42) * }) + * + * await Effect.runPromise(program) // => 42 * ``` * - * @category setters + * @category mutations * @since 2.0.0 */ export const setAndGet: { @@ -702,7 +706,7 @@ export const setAndGet: { * * **Example** (Updating a value) * - * ```ts + * ```ts import.meta.vitest * import { Effect, SubscriptionRef } from "effect" * * const program = Effect.gen(function*() { @@ -710,12 +714,13 @@ export const setAndGet: { * * yield* SubscriptionRef.update(ref, (n) => n * 2) * - * const value = yield* SubscriptionRef.get(ref) - * console.log(value) + * return yield* SubscriptionRef.get(ref) * }) + * + * await Effect.runPromise(program) // => 20 * ``` * - * @category updating + * @category mutations * @since 2.0.0 */ export const update: { @@ -733,7 +738,7 @@ export const update: { * * **Example** (Updating with an effect) * - * ```ts + * ```ts import.meta.vitest * import { Effect, SubscriptionRef } from "effect" * * const program = Effect.gen(function*() { @@ -741,12 +746,13 @@ export const update: { * * yield* SubscriptionRef.updateEffect(ref, (n) => Effect.succeed(n + 5)) * - * const value = yield* SubscriptionRef.get(ref) - * console.log(value) + * return yield* SubscriptionRef.get(ref) * }) + * + * await Effect.runPromise(program) // => 15 * ``` * - * @category updating + * @category mutations * @since 2.0.0 */ export const updateEffect: { @@ -766,18 +772,19 @@ export const updateEffect: { * * **Example** (Updating and reading the new value) * - * ```ts + * ```ts import.meta.vitest * import { Effect, SubscriptionRef } from "effect" * * const program = Effect.gen(function*() { * const ref = yield* SubscriptionRef.make(10) * - * const newValue = yield* SubscriptionRef.updateAndGet(ref, (n) => n * 2) - * console.log("New value:", newValue) + * return yield* SubscriptionRef.updateAndGet(ref, (n) => n * 2) * }) + * + * await Effect.runPromise(program) // => 20 * ``` * - * @category updating + * @category mutations * @since 2.0.0 */ export const updateAndGet: { @@ -797,21 +804,22 @@ export const updateAndGet: { * * **Example** (Updating with an effect and reading the new value) * - * ```ts + * ```ts import.meta.vitest * import { Effect, SubscriptionRef } from "effect" * * const program = Effect.gen(function*() { * const ref = yield* SubscriptionRef.make(10) * - * const newValue = yield* SubscriptionRef.updateAndGetEffect( + * return yield* SubscriptionRef.updateAndGetEffect( * ref, * (n) => Effect.succeed(n + 5) * ) - * console.log("New value:", newValue) * }) + * + * await Effect.runPromise(program) // => 15 * ``` * - * @category updating + * @category mutations * @since 2.0.0 */ export const updateAndGetEffect: { @@ -835,7 +843,7 @@ export const updateAndGetEffect: { * * **Example** (Conditionally updating a value) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Option, SubscriptionRef } from "effect" * * const program = Effect.gen(function*() { @@ -846,12 +854,13 @@ export const updateAndGetEffect: { * (n) => n > 5 ? Option.some(n * 2) : Option.none() * ) * - * const value = yield* SubscriptionRef.get(ref) - * console.log(value) + * return yield* SubscriptionRef.get(ref) * }) + * + * await Effect.runPromise(program) // => 20 * ``` * - * @category updating + * @category mutations * @since 2.0.0 */ export const updateSome: { @@ -883,7 +892,7 @@ export const updateSome: { * * **Example** (Conditionally updating with an effect) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Option, SubscriptionRef } from "effect" * * const program = Effect.gen(function*() { @@ -894,12 +903,13 @@ export const updateSome: { * (n) => Effect.succeed(n > 5 ? Option.some(n + 3) : Option.none()) * ) * - * const value = yield* SubscriptionRef.get(ref) - * console.log(value) + * return yield* SubscriptionRef.get(ref) * }) + * + * await Effect.runPromise(program) // => 13 * ``` * - * @category updating + * @category mutations * @since 2.0.0 */ export const updateSomeEffect: { @@ -937,21 +947,22 @@ export const updateSomeEffect: { * * **Example** (Conditionally updating and reading the new value) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Option, SubscriptionRef } from "effect" * * const program = Effect.gen(function*() { * const ref = yield* SubscriptionRef.make(10) * - * const newValue = yield* SubscriptionRef.updateSomeAndGet( + * return yield* SubscriptionRef.updateSomeAndGet( * ref, * (n) => n > 5 ? Option.some(n * 2) : Option.none() * ) - * console.log("New value:", newValue) * }) + * + * await Effect.runPromise(program) // => 20 * ``` * - * @category updating + * @category mutations * @since 2.0.0 */ export const updateSomeAndGet: { @@ -985,21 +996,22 @@ export const updateSomeAndGet: { * * **Example** (Conditionally updating with an effect and reading the new value) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Option, SubscriptionRef } from "effect" * * const program = Effect.gen(function*() { * const ref = yield* SubscriptionRef.make(10) * - * const newValue = yield* SubscriptionRef.updateSomeAndGetEffect( + * return yield* SubscriptionRef.updateSomeAndGetEffect( * ref, * (n) => Effect.succeed(n > 5 ? Option.some(n + 3) : Option.none()) * ) - * console.log("New value:", newValue) * }) + * + * await Effect.runPromise(program) // => 13 * ``` * - * @category updating + * @category mutations * @since 2.0.0 */ export const updateSomeAndGetEffect: { diff --git a/repos/effect/packages/effect/src/Symbol.ts b/repos/effect/packages/effect/src/Symbol.ts index b359115daa..de444c25df 100644 --- a/repos/effect/packages/effect/src/Symbol.ts +++ b/repos/effect/packages/effect/src/Symbol.ts @@ -18,11 +18,11 @@ import * as predicate from "./Predicate.ts" * * **Example** (Checking for symbols) * - * ```ts + * ```ts import.meta.vitest * import { Symbol } from "effect" * - * console.log(Symbol.isSymbol(globalThis.Symbol.for("a"))) // true - * console.log(Symbol.isSymbol("a")) // false + * Symbol.isSymbol(globalThis.Symbol.for("a")) // => true + * Symbol.isSymbol("a") // => false * ``` * * @category guards diff --git a/repos/effect/packages/effect/src/SynchronizedRef.ts b/repos/effect/packages/effect/src/SynchronizedRef.ts index 2828496264..5bbf956628 100644 --- a/repos/effect/packages/effect/src/SynchronizedRef.ts +++ b/repos/effect/packages/effect/src/SynchronizedRef.ts @@ -226,7 +226,7 @@ export const getAndUpdateSome: { } = dual( 2, (self: SynchronizedRef, pf: (a: A) => Option.Option): Effect.Effect => - self.semaphore.withPermit(Ref.getAndUpdateSome(self, pf)) + self.semaphore.withPermit(Ref.getAndUpdateSome(self.backing, pf)) ) /** diff --git a/repos/effect/packages/effect/src/Terminal.ts b/repos/effect/packages/effect/src/Terminal.ts index f59175692e..c2311d184a 100644 --- a/repos/effect/packages/effect/src/Terminal.ts +++ b/repos/effect/packages/effect/src/Terminal.ts @@ -25,7 +25,7 @@ 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 models + * @category services * @since 4.0.0 */ export interface Terminal { @@ -118,10 +118,10 @@ const QuitErrorTypeId = "effect/platform/Terminal/QuitError" * * @see {@link isQuitError} for checking unknown errors when handling terminal cancellation * - * @category QuitError + * @category errors * @since 4.0.0 */ -export class QuitError extends Schema.ErrorClass("QuitError")({ +export class QuitError extends Schema.Error("QuitError")({ _tag: Schema.tag("QuitError") }) { /** diff --git a/repos/effect/packages/effect/src/Tracer.ts b/repos/effect/packages/effect/src/Tracer.ts index 028829dedd..967109401b 100644 --- a/repos/effect/packages/effect/src/Tracer.ts +++ b/repos/effect/packages/effect/src/Tracer.ts @@ -22,7 +22,7 @@ import * as Option from "./Option.ts" * `span` to allocate a span from the supplied name, parent, annotations, * links, start time, kind, root flag, and sampling decision. * - * @category models + * @category services * @since 2.0.0 */ export interface Tracer { @@ -61,7 +61,7 @@ export interface EffectPrimitive { * * **Example** (Creating span statuses) * - * ```ts + * ```ts import.meta.vitest * import { Exit } from "effect" * import type { Tracer } from "effect" * @@ -80,8 +80,8 @@ export interface EffectPrimitive { * exit: Exit.succeed("result") * } * - * console.log(startedStatus._tag) // "Started" - * console.log(endedStatus.endTime - endedStatus.startTime) // 500000000n + * startedStatus._tag // => "Started" + * endedStatus.endTime - endedStatus.startTime // => 500_000_000n * ``` * * @category models @@ -103,20 +103,19 @@ export type SpanStatus = { * * **Example** (Accepting any span) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Tracer } from "effect" * * // Function that accepts any span type - * const logSpan = (span: Tracer.AnySpan) => { - * console.log(`Span ID: ${span.spanId}, Trace ID: ${span.traceId}`) - * return Effect.succeed(span) - * } + * 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 @@ -134,11 +133,11 @@ export type AnySpan = Span | ExternalSpan * * **Example** (Reading the parent span key) * - * ```ts + * ```ts import.meta.vitest * import { Tracer } from "effect" * * // The key used to identify parent spans in the context - * console.log(Tracer.ParentSpanKey) // "effect/Tracer/ParentSpan" + * Tracer.ParentSpanKey // => "effect/Tracer/ParentSpan" * ``` * * @category constants @@ -152,20 +151,23 @@ export const ParentSpanKey = "effect/Tracer/ParentSpan" * * **Example** (Accessing the parent span) * - * ```ts + * ```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) - * console.log(`Parent span: ${parentSpan.spanId}`) + * 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) {} +export class ParentSpan extends Context.Service()(ParentSpanKey, { fiberCached: true }) {} /** * Represents a span created outside Effect's tracer, carrying trace and span @@ -174,7 +176,7 @@ export class ParentSpan extends Context.Service()(ParentSpa * * **Example** (Creating an external span value) * - * ```ts + * ```ts import.meta.vitest * import { Context } from "effect" * import type { Tracer } from "effect" * @@ -187,7 +189,7 @@ export class ParentSpan extends Context.Service()(ParentSpa * annotations: Context.empty() * } * - * console.log(`External span: ${externalSpan.spanId}`) + * externalSpan.spanId // => "span-abc-123" * ``` * * @category models @@ -208,9 +210,8 @@ export interface ExternalSpan { * * **Example** (Configuring span options) * - * ```ts - * import { Effect } from "effect" - * import type { Tracer } from "effect" + * ```ts import.meta.vitest + * import { Effect, Tracer } from "effect" * * // Create an effect with span options * const options: Tracer.SpanOptions = { @@ -223,6 +224,19 @@ export interface ExternalSpan { * 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 @@ -266,22 +280,27 @@ export interface TraceOptions { * * **Example** (Configuring span kinds) * - * ```ts - * import { Effect } from "effect" - * import type { Tracer } from "effect" + * ```ts import.meta.vitest + * import { Effect, Tracer } from "effect" * * // Different span kinds for different operations - * const serverSpan = Effect.withSpan("handle-request", { - * kind: "server" as Tracer.SpanKind - * }) + * const program = Effect.succeed("handled").pipe( + * Effect.withSpan("handle-request", { + * kind: "server" as Tracer.SpanKind + * }) + * ) * - * const clientSpan = Effect.withSpan("api-call", { - * kind: "client" 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" * - * const internalSpan = Effect.withSpan("internal-process", { - * kind: "internal" as Tracer.SpanKind - * }) + * spans[0]?.kind // => "server" * ``` * * @category models @@ -296,12 +315,13 @@ export type SpanKind = "internal" | "server" | "client" | "producer" | "consumer * * **Example** (Working with spans) * - * ```ts + * ```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 @@ -328,7 +348,7 @@ export type SpanKind = "internal" | "server" | "client" | "producer" | "consumer * attributes.set(key, value) * }, * event(name, startTime, eventAttributes = {}) { - * console.log(`${name} at ${startTime} with ${Object.keys(eventAttributes).length} attributes`) + * events.push([name, startTime, eventAttributes]) * }, * addLinks(newLinks) { * links.push(...newLinks) @@ -336,11 +356,13 @@ export type SpanKind = "internal" | "server" | "client" | "producer" | "consumer * } * * span.attribute("user.id", "123") + * span.event("loaded", 1_250_000_000n, { "cache.hit": true }) * span.end(1_500_000_000n, Exit.succeed("user")) * - * console.log(span.name) // "load-user" - * console.log(span.attributes.get("user.id")) // "123" - * console.log(span.status._tag) // "Ended" + * 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 @@ -370,7 +392,7 @@ export interface Span { * * **Example** (Linking spans) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Tracer } from "effect" * * // Create a span link to connect spans @@ -387,6 +409,19 @@ export interface Span { * 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 @@ -425,8 +460,8 @@ export const make = (options: Tracer): Tracer => options * * **Example** (Creating an external span) * - * ```ts - * import { Effect, Tracer } from "effect" + * ```ts import.meta.vitest + * import { Effect, Option, Tracer } from "effect" * * // Create an external span from another tracing system * const span = Tracer.externalSpan({ @@ -439,6 +474,19 @@ export const make = (options: Tracer): Tracer => options * 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 @@ -474,18 +522,18 @@ export const externalSpan = ( * * **Example** (Disabling span propagation) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Tracer } from "effect" * * // Disable span propagation for a specific effect - * const program = Effect.gen(function*() { - * yield* Effect.log("This will not propagate parent span") - * }).pipe( + * const program = Tracer.DisablePropagation.pipe( * Effect.provideService(Tracer.DisablePropagation, true) * ) + * + * await Effect.runPromise(program) // => true * ``` * - * @category references + * @category services * @since 3.12.0 */ export const DisablePropagation = Context.Reference( @@ -508,7 +556,7 @@ export const DisablePropagation = Context.Reference( * * @see {@link MinimumTraceLevel} for the threshold that decides whether spans at that level are sampled * - * @category references + * @category services * @since 4.0.0 */ export const CurrentTraceLevel: Context.Reference = Context.Reference( @@ -537,7 +585,7 @@ export const CurrentTraceLevel: Context.Reference = Context.Reference< * * @see {@link CurrentTraceLevel} for the default span level used when options do not specify one * - * @category references + * @category services * @since 4.0.0 */ export const MinimumTraceLevel = Context.Reference< @@ -552,7 +600,7 @@ export const MinimumTraceLevel = Context.Reference< * Use when you need the raw context key for active tracer lookup in lower-level * tracing code. * - * @category references + * @category constants * @since 4.0.0 */ export const TracerKey = "effect/Tracer" @@ -563,26 +611,25 @@ export const TracerKey = "effect/Tracer" * * **Example** (Accessing the current tracer) * - * ```ts + * ```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) - * console.log("Using current tracer") + * // Or use the built-in tracer effect + * const tracerFromAccessor = yield* Effect.tracer + * return tracer === tracerFromAccessor * }) * - * // Or use the built-in tracer effect - * const tracerEffect = Effect.gen(function*() { - * const tracer = yield* Effect.tracer - * console.log("Current tracer obtained") - * }) + * await Effect.runPromise(program) // => true * ``` * - * @category references + * @category services * @since 2.0.0 */ export const Tracer: Context.Reference = Context.Reference(TracerKey, { + fiberCached: true, defaultValue: () => make({ span: (options) => new NativeSpan(options) @@ -602,7 +649,7 @@ export const Tracer: Context.Reference = Context.Reference(Trace * * @see {@link Span} for the interface implemented by native spans * - * @category native tracer + * @category models * @since 4.0.0 */ export class NativeSpan implements Span { diff --git a/repos/effect/packages/effect/src/Trie.ts b/repos/effect/packages/effect/src/Trie.ts index 025fc04116..c9203a8808 100644 --- a/repos/effect/packages/effect/src/Trie.ts +++ b/repos/effect/packages/effect/src/Trie.ts @@ -26,8 +26,8 @@ const TypeId = TR.TrieTypeId * * **Example** (Using a trie for prefix search) * - * ```ts - * import { Trie } from "effect" + * ```ts import.meta.vitest + * import { Option, Trie } from "effect" * * // Create a trie with string-to-number mappings * const trie: Trie.Trie = Trie.make( @@ -38,24 +38,20 @@ const TypeId = TR.TrieTypeId * ) * * // Get values by exact key - * console.log(Trie.get(trie, "apple")) // Some(1) - * console.log(Trie.get(trie, "grape")) // None + * Trie.get(trie, "apple") // => Option.some(1) + * Trie.get(trie, "grape") // => Option.none() * * // Find all keys with a prefix - * console.log(Array.from(Trie.keysWithPrefix(trie, "app"))) - * // ["app", "apple", "application"] + * Array.from(Trie.keysWithPrefix(trie, "app")) // => ["app", "apple", "application"] * * // Iterate over all entries (sorted alphabetically) - * for (const [key, value] of trie) { - * console.log(`${key}: ${value}`) - * } - * // Output: "app: 2", "apple: 1", "application: 3", "banana: 4" + * Array.from(trie) // => [["app", 2], ["apple", 1], ["application", 3], ["banana", 4]] * * // Check if key exists - * console.log(Trie.has(trie, "app")) // true + * Trie.has(trie, "app") // => true * * // Get size - * console.log(Trie.size(trie)) // 4 + * Trie.size(trie) // => 4 * ``` * * @category models @@ -72,14 +68,13 @@ export interface Trie extends Iterable<[string, Value]>, Equal, Pi * * **Example** (Creating an empty trie) * - * ```ts + * ```ts import.meta.vitest * import { Trie } from "effect" - * import * as assert from "node:assert" * * const trie = Trie.empty() * - * assert.equal(Trie.size(trie), 0) - * assert.deepStrictEqual(Array.from(trie), []) + * Trie.size(trie) // => 0 + * Array.from(trie) // => [] * ``` * * @category constructors @@ -92,9 +87,8 @@ export const empty: () => Trie = TR.empty * * **Example** (Creating a trie from entries) * - * ```ts - * import { Equal, Trie } from "effect" - * import * as assert from "node:assert" + * ```ts import.meta.vitest + * import { Trie } from "effect" * * const iterable: Array = [["call", 0], ["me", 1], [ * "mind", @@ -103,17 +97,8 @@ export const empty: () => Trie = TR.empty * const trie = Trie.fromIterable(iterable) * * // The entries in the `Trie` are extracted in alphabetical order, regardless of the insertion order - * assert.deepStrictEqual(Array.from(trie), [["call", 0], ["me", 1], ["mid", 3], [ - * "mind", - * 2 - * ]]) - * assert.equal( - * Equal.equals( - * Trie.make(["call", 0], ["me", 1], ["mind", 2], ["mid", 3]), - * trie - * ), - * true - * ) + * Array.from(trie) // => [["call", 0], ["me", 1], ["mid", 3], ["mind", 2]] + * trie // => Trie.make(["call", 0], ["me", 1], ["mind", 2], ["mid", 3]) * ``` * * @category constructors @@ -126,17 +111,13 @@ export const fromIterable: (entries: Iterable) => Trie< * * **Example** (Constructing a trie from entries) * - * ```ts - * import { Equal, Trie } from "effect" - * import * as assert from "node:assert" + * ```ts import.meta.vitest + * import { Trie } from "effect" * * const trie = Trie.make(["ca", 0], ["me", 1]) * - * assert.deepStrictEqual(Array.from(trie), [["ca", 0], ["me", 1]]) - * assert.equal( - * Equal.equals(Trie.fromIterable([["ca", 0], ["me", 1]]), trie), - * true - * ) + * Array.from(trie) // => [["ca", 0], ["me", 1]] + * trie // => Trie.fromIterable([["ca", 0], ["me", 1]]) * ``` * * @category constructors @@ -151,9 +132,8 @@ export const make: >( * * **Example** (Inserting entries) * - * ```ts + * ```ts import.meta.vitest * import { Trie } from "effect" - * import * as assert from "node:assert" * * const trie1 = Trie.empty().pipe( * Trie.insert("call", 0) @@ -162,13 +142,10 @@ export const make: >( * const trie3 = trie2.pipe(Trie.insert("mind", 2)) * const trie4 = trie3.pipe(Trie.insert("mid", 3)) * - * assert.deepStrictEqual(Array.from(trie1), [["call", 0]]) - * assert.deepStrictEqual(Array.from(trie2), [["call", 0], ["me", 1]]) - * assert.deepStrictEqual(Array.from(trie3), [["call", 0], ["me", 1], ["mind", 2]]) - * assert.deepStrictEqual(Array.from(trie4), [["call", 0], ["me", 1], ["mid", 3], [ - * "mind", - * 2 - * ]]) + * 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 @@ -188,9 +165,8 @@ export const insert: { * * **Example** (Reading keys in alphabetical order) * - * ```ts + * ```ts import.meta.vitest * import { Trie } from "effect" - * import * as assert from "node:assert" * * const trie = Trie.empty().pipe( * Trie.insert("cab", 0), @@ -198,8 +174,7 @@ export const insert: { * Trie.insert("bca", 2) * ) * - * const result = Array.from(Trie.keys(trie)) - * assert.deepStrictEqual(result, ["abc", "bca", "cab"]) + * Array.from(Trie.keys(trie)) // => ["abc", "bca", "cab"] * ``` * * @category getters @@ -216,9 +191,8 @@ export const keys: (self: Trie) => IterableIterator = TR.keys * * **Example** (Reading values by key order) * - * ```ts + * ```ts import.meta.vitest * import { Trie } from "effect" - * import * as assert from "node:assert" * * const trie = Trie.empty().pipe( * Trie.insert("call", 0), @@ -226,8 +200,7 @@ export const keys: (self: Trie) => IterableIterator = TR.keys * Trie.insert("and", 2) * ) * - * const result = Array.from(Trie.values(trie)) - * assert.deepStrictEqual(result, [2, 0, 1]) + * Array.from(Trie.values(trie)) // => [2, 0, 1] * ``` * * @category getters @@ -244,17 +217,15 @@ export const values: (self: Trie) => IterableIterator = TR.values * * **Example** (Reading entries in alphabetical order) * - * ```ts + * ```ts import.meta.vitest * import { Trie } from "effect" - * import * as assert from "node:assert" * * const trie = Trie.empty().pipe( * Trie.insert("call", 0), * Trie.insert("me", 1) * ) * - * const result = Array.from(Trie.entries(trie)) - * assert.deepStrictEqual(result, [["call", 0], ["me", 1]]) + * Array.from(Trie.entries(trie)) // => [["call", 0], ["me", 1]] * ``` * * @category getters @@ -271,17 +242,14 @@ export const entries: (self: Trie) => IterableIterator<[string, V]> = TR.e * * **Example** (Converting entries to an array) * - * ```ts + * ```ts import.meta.vitest * import { Trie } from "effect" - * import * as assert from "node:assert" * * const trie = Trie.empty().pipe( * Trie.insert("call", 0), * Trie.insert("me", 1) * ) - * const result = Trie.toEntries(trie) - * - * assert.deepStrictEqual(result, [["call", 0], ["me", 1]]) + * Trie.toEntries(trie) // => [["call", 0], ["me", 1]] * ``` * * @category getters @@ -295,9 +263,8 @@ export const toEntries = (self: Trie): Array<[string, V]> => Array.from(en * * **Example** (Finding keys with a prefix) * - * ```ts + * ```ts import.meta.vitest * import { Trie } from "effect" - * import * as assert from "node:assert" * * const trie = Trie.empty().pipe( * Trie.insert("she", 0), @@ -306,8 +273,7 @@ export const toEntries = (self: Trie): Array<[string, V]> => Array.from(en * Trie.insert("shore", 3) * ) * - * const result = Array.from(Trie.keysWithPrefix(trie, "she")) - * assert.deepStrictEqual(result, ["she", "shells"]) + * Array.from(Trie.keysWithPrefix(trie, "she")) // => ["she", "shells"] * ``` * * @category getters @@ -324,9 +290,8 @@ export const keysWithPrefix: { * * **Example** (Finding values with a prefix) * - * ```ts + * ```ts import.meta.vitest * import { Trie } from "effect" - * import * as assert from "node:assert" * * const trie = Trie.empty().pipe( * Trie.insert("she", 0), @@ -335,10 +300,7 @@ export const keysWithPrefix: { * Trie.insert("shore", 3) * ) * - * const result = Array.from(Trie.valuesWithPrefix(trie, "she")) - * - * // 0: "she", 1: "shells" - * assert.deepStrictEqual(result, [0, 1]) + * Array.from(Trie.valuesWithPrefix(trie, "she")) // => [0, 1] * ``` * * @category getters @@ -355,9 +317,8 @@ export const valuesWithPrefix: { * * **Example** (Finding entries with a prefix) * - * ```ts + * ```ts import.meta.vitest * import { Trie } from "effect" - * import * as assert from "node:assert" * * const trie = Trie.empty().pipe( * Trie.insert("she", 0), @@ -366,8 +327,7 @@ export const valuesWithPrefix: { * Trie.insert("shore", 3) * ) * - * const result = Array.from(Trie.entriesWithPrefix(trie, "she")) - * assert.deepStrictEqual(result, [["she", 0], ["shells", 1]]) + * Array.from(Trie.entriesWithPrefix(trie, "she")) // => [["she", 0], ["shells", 1]] * ``` * * @category getters @@ -384,9 +344,8 @@ export const entriesWithPrefix: { * * **Example** (Converting prefixed entries to an array) * - * ```ts + * ```ts import.meta.vitest * import { Trie } from "effect" - * import * as assert from "node:assert" * * const trie = Trie.empty().pipe( * Trie.insert("shells", 0), @@ -395,8 +354,7 @@ export const entriesWithPrefix: { * Trie.insert("she", 3) * ) * - * const result = Trie.toEntriesWithPrefix(trie, "she") - * assert.deepStrictEqual(result, [["she", 3], ["shells", 0]]) + * Trie.toEntriesWithPrefix(trie, "she") // => [["she", 3], ["shells", 0]] * ``` * * @category getters @@ -413,9 +371,8 @@ export const toEntriesWithPrefix: { * * **Example** (Finding the longest prefix) * - * ```ts - * import { Trie } from "effect" - * import * as assert from "node:assert" + * ```ts import.meta.vitest + * import { Option, Trie } from "effect" * * const trie = Trie.empty().pipe( * Trie.insert("shells", 0), @@ -423,14 +380,8 @@ export const toEntriesWithPrefix: { * Trie.insert("she", 2) * ) * - * const none = Trie.longestPrefixOf(trie, "sell") - * const some = Trie.longestPrefixOf(trie, "sells") - * - * assert.equal(none._tag, "None") - * assert.equal(some._tag, "Some") - * if (some._tag === "Some") { - * assert.deepStrictEqual(some.value, ["sells", 1]) - * } + * Trie.longestPrefixOf(trie, "sell") // => Option.none() + * Trie.longestPrefixOf(trie, "sells") // => Option.some(["sells", 1]) * ``` * * @category getters @@ -446,16 +397,15 @@ export const longestPrefixOf: { * * **Example** (Getting the size) * - * ```ts + * ```ts import.meta.vitest * import { Trie } from "effect" - * import * as assert from "node:assert" * * const trie = Trie.empty().pipe( * Trie.insert("a", 0), * Trie.insert("b", 1) * ) * - * assert.equal(Trie.size(trie), 2) + * Trie.size(trie) // => 2 * ``` * * @category getters @@ -468,9 +418,8 @@ export const size: (self: Trie) => number = TR.size * * **Example** (Looking up values safely) * - * ```ts + * ```ts import.meta.vitest * import { Option, Trie } from "effect" - * import * as assert from "node:assert" * * const trie = Trie.empty().pipe( * Trie.insert("call", 0), @@ -479,17 +428,17 @@ export const size: (self: Trie) => number = TR.size * Trie.insert("mid", 3) * ) * - * assert.deepStrictEqual(Trie.get(trie, "call"), Option.some(0)) - * assert.deepStrictEqual(Trie.get(trie, "me"), Option.some(1)) - * assert.deepStrictEqual(Trie.get(trie, "mind"), Option.some(2)) - * assert.deepStrictEqual(Trie.get(trie, "mid"), Option.some(3)) - * assert.deepStrictEqual(Trie.get(trie, "cale"), Option.none()) - * assert.deepStrictEqual(Trie.get(trie, "ma"), Option.none()) - * assert.deepStrictEqual(Trie.get(trie, "midn"), Option.none()) - * assert.deepStrictEqual(Trie.get(trie, "mea"), Option.none()) + * 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 elements + * @category getters * @since 2.0.0 */ export const get: { @@ -502,9 +451,8 @@ export const get: { * * **Example** (Checking key membership) * - * ```ts + * ```ts import.meta.vitest * import { Trie } from "effect" - * import * as assert from "node:assert" * * const trie = Trie.empty().pipe( * Trie.insert("call", 0), @@ -513,17 +461,17 @@ export const get: { * Trie.insert("mid", 3) * ) * - * assert.equal(Trie.has(trie, "call"), true) - * assert.equal(Trie.has(trie, "me"), true) - * assert.equal(Trie.has(trie, "mind"), true) - * assert.equal(Trie.has(trie, "mid"), true) - * assert.equal(Trie.has(trie, "cale"), false) - * assert.equal(Trie.has(trie, "ma"), false) - * assert.equal(Trie.has(trie, "midn"), false) - * assert.equal(Trie.has(trie, "mea"), false) + * 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 elements + * @category predicates * @since 2.0.0 */ export const has: { @@ -536,18 +484,17 @@ export const has: { * * **Example** (Checking whether a trie is empty) * - * ```ts + * ```ts import.meta.vitest * import { Trie } from "effect" - * import * as assert from "node:assert" * * const trie = Trie.empty() * const trie1 = trie.pipe(Trie.insert("ma", 0)) * - * assert.equal(Trie.isEmpty(trie), true) - * assert.equal(Trie.isEmpty(trie1), false) + * Trie.isEmpty(trie) // => true + * Trie.isEmpty(trie1) // => false * ``` * - * @category elements + * @category predicates * @since 2.0.0 */ export const isEmpty: (self: Trie) => boolean = TR.isEmpty @@ -567,16 +514,18 @@ export const isEmpty: (self: Trie) => boolean = TR.isEmpty * * **Example** (Looking up values unsafely) * - * ```ts - * import { Trie } from "effect" - * import * as assert from "node:assert" + * ```ts import.meta.vitest + * import { Result, Trie } from "effect" * * const trie = Trie.empty().pipe( * Trie.insert("call", 0), * Trie.insert("me", 1) * ) * - * assert.throws(() => Trie.getUnsafe(trie, "mae")) + * Result.try({ + * try: () => Trie.getUnsafe(trie, "mae"), + * catch: (error) => (error as Error).message + * }) // => Result.fail("Expected trie to contain key") * ``` * * @category unsafe @@ -592,9 +541,8 @@ export const getUnsafe: { * * **Example** (Removing entries) * - * ```ts + * ```ts import.meta.vitest * import { Option, Trie } from "effect" - * import * as assert from "node:assert" * * const trie = Trie.empty().pipe( * Trie.insert("call", 0), @@ -606,9 +554,9 @@ export const getUnsafe: { * const trie1 = trie.pipe(Trie.remove("call")) * const trie2 = trie1.pipe(Trie.remove("mea")) * - * assert.deepStrictEqual(Trie.get(trie, "call"), Option.some(0)) - * assert.deepStrictEqual(Trie.get(trie1, "call"), Option.none()) - * assert.deepStrictEqual(Trie.get(trie2, "call"), Option.none()) + * Trie.get(trie, "call") // => Option.some(0) + * Trie.get(trie1, "call") // => Option.none() + * Trie.get(trie2, "call") // => Option.none() * ``` * * @category mutations @@ -624,9 +572,8 @@ export const remove: { * * **Example** (Reducing entries) * - * ```ts + * ```ts import.meta.vitest * import { Trie } from "effect" - * import * as assert from "node:assert" * * const trie = Trie.empty().pipe( * Trie.insert("shells", 0), @@ -634,24 +581,9 @@ export const remove: { * Trie.insert("she", 2) * ) * - * assert.equal( - * trie.pipe( - * Trie.reduce(0, (acc, n) => acc + n) - * ), - * 3 - * ) - * assert.equal( - * trie.pipe( - * Trie.reduce(10, (acc, n) => acc + n) - * ), - * 13 - * ) - * assert.equal( - * trie.pipe( - * Trie.reduce("", (acc, _, key) => acc + key) - * ), - * "sellssheshells" - * ) + * 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 @@ -667,9 +599,8 @@ export const reduce: { * * **Example** (Mapping entries) * - * ```ts - * import { Equal, Trie } from "effect" - * import * as assert from "node:assert" + * ```ts import.meta.vitest + * import { Trie } from "effect" * * const trie = Trie.empty().pipe( * Trie.insert("shells", 0), @@ -677,20 +608,8 @@ export const reduce: { * Trie.insert("she", 2) * ) * - * const trieMapV = Trie.empty().pipe( - * Trie.insert("shells", 1), - * Trie.insert("sells", 2), - * Trie.insert("she", 3) - * ) - * - * const trieMapK = Trie.empty().pipe( - * Trie.insert("shells", 6), - * Trie.insert("sells", 5), - * Trie.insert("she", 3) - * ) - * - * assert.equal(Equal.equals(Trie.map(trie, (v) => v + 1), trieMapV), true) - * assert.equal(Equal.equals(Trie.map(trie, (_, k) => k.length), trieMapK), true) + * 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 @@ -706,9 +625,8 @@ export const map: { * * **Example** (Filtering entries) * - * ```ts - * import { Equal, Trie } from "effect" - * import * as assert from "node:assert" + * ```ts import.meta.vitest + * import { Trie } from "effect" * * const trie = Trie.empty().pipe( * Trie.insert("shells", 0), @@ -716,20 +634,8 @@ export const map: { * Trie.insert("she", 2) * ) * - * const trieMapV = Trie.empty().pipe( - * Trie.insert("she", 2) - * ) - * - * const trieMapK = Trie.empty().pipe( - * Trie.insert("shells", 0), - * Trie.insert("sells", 1) - * ) - * - * assert.equal(Equal.equals(Trie.filter(trie, (v) => v > 1), trieMapV), true) - * assert.equal( - * Equal.equals(Trie.filter(trie, (_, k) => k.length > 3), trieMapK), - * true - * ) + * 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 @@ -748,9 +654,8 @@ export const filter: { * * **Example** (Filtering and mapping entries) * - * ```ts - * import { Equal, Result, Trie } from "effect" - * import * as assert from "node:assert" + * ```ts import.meta.vitest + * import { Result, Trie } from "effect" * * const trie = Trie.empty().pipe( * Trie.insert("shells", 0), @@ -758,32 +663,11 @@ export const filter: { * Trie.insert("she", 2) * ) * - * const trieMapV = Trie.empty().pipe( - * Trie.insert("she", 2) - * ) - * - * const trieMapK = Trie.empty().pipe( - * Trie.insert("shells", 0), - * Trie.insert("sells", 1) - * ) - * - * assert.equal( - * Equal.equals( - * Trie.filterMap(trie, (v) => v > 1 ? Result.succeed(v) : Result.failVoid), - * trieMapV - * ), - * true - * ) - * assert.equal( - * Equal.equals( - * Trie.filterMap( - * trie, - * (v, k) => k.length > 3 ? Result.succeed(v) : Result.failVoid - * ), - * trieMapK - * ), - * true - * ) + * 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 @@ -799,9 +683,8 @@ export const filterMap: { * * **Example** (Compacting optional values) * - * ```ts - * import { Equal, Option, Trie } from "effect" - * import * as assert from "node:assert" + * ```ts import.meta.vitest + * import { Option, Trie } from "effect" * * const trie = Trie.empty>().pipe( * Trie.insert("shells", Option.some(0)), @@ -809,12 +692,7 @@ export const filterMap: { * Trie.insert("she", Option.some(2)) * ) * - * const trieMapV = Trie.empty().pipe( - * Trie.insert("shells", 0), - * Trie.insert("she", 2) - * ) - * - * assert.equal(Equal.equals(Trie.compact(trie), trieMapV), true) + * Trie.compact(trie) // => Trie.make(["shells", 0], ["she", 2]) * ``` * * @category filtering @@ -827,9 +705,8 @@ export const compact: (self: Trie>) => Trie = TR.compact * * **Example** (Iterating over entries) * - * ```ts + * ```ts import.meta.vitest * import { Trie } from "effect" - * import * as assert from "node:assert" * * let value = 0 * @@ -842,7 +719,7 @@ export const compact: (self: Trie>) => Trie = TR.compact * }) * ) * - * assert.equal(value, 17) + * value // => 17 * ``` * * @category traversing @@ -858,9 +735,8 @@ export const forEach: { * * **Example** (Modifying an existing value) * - * ```ts - * import { Equal, Option, Trie } from "effect" - * import * as assert from "node:assert" + * ```ts import.meta.vitest + * import { Option, Trie } from "effect" * * const trie = Trie.empty().pipe( * Trie.insert("shells", 0), @@ -868,12 +744,8 @@ export const forEach: { * Trie.insert("she", 2) * ) * - * assert.deepStrictEqual( - * trie.pipe(Trie.modify("she", (v) => v + 10), Trie.get("she")), - * Option.some(12) - * ) - * - * assert.equal(Equal.equals(trie.pipe(Trie.modify("me", (v) => v)), trie), true) + * trie.pipe(Trie.modify("she", (v) => v + 10), Trie.get("she")) // => Option.some(12) + * trie.pipe(Trie.modify("me", (v) => v)) // => trie * ``` * * @category mutations @@ -889,9 +761,8 @@ export const modify: { * * **Example** (Removing multiple entries) * - * ```ts - * import { Equal, Trie } from "effect" - * import * as assert from "node:assert" + * ```ts import.meta.vitest + * import { Trie } from "effect" * * const trie = Trie.empty().pipe( * Trie.insert("shells", 0), @@ -899,13 +770,7 @@ export const modify: { * Trie.insert("she", 2) * ) * - * assert.equal( - * Equal.equals( - * trie.pipe(Trie.removeMany(["she", "sells"])), - * Trie.empty().pipe(Trie.insert("shells", 0)) - * ), - * true - * ) + * trie.pipe(Trie.removeMany(["she", "sells"])) // => Trie.make(["shells", 0]) * ``` * * @category mutations @@ -921,27 +786,16 @@ export const removeMany: { * * **Example** (Inserting multiple entries) * - * ```ts - * import { Equal, Trie } from "effect" - * import * as assert from "node:assert" + * ```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.insert("shells", 0) * ) * - * const trieInsert = Trie.empty().pipe( - * Trie.insert("shells", 0), - * Trie.insertMany( - * [["sells", 1], ["she", 2]] - * ) - * ) - * - * assert.equal( - * Equal.equals(trie, trieInsert), - * true - * ) + * trie.pipe( + * Trie.insertMany([["sells", 1], ["she", 2]]) + * ) // => Trie.make(["shells", 0], ["sells", 1], ["she", 2]) * ``` * * @category mutations diff --git a/repos/effect/packages/effect/src/Tuple.ts b/repos/effect/packages/effect/src/Tuple.ts index 8e4251fe44..f929c9f0af 100644 --- a/repos/effect/packages/effect/src/Tuple.ts +++ b/repos/effect/packages/effect/src/Tuple.ts @@ -32,11 +32,10 @@ import type { Apply, Lambda } from "./Struct.ts" * * **Example** (Creating a tuple) * - * ```ts + * ```ts import.meta.vitest * import { Tuple } from "effect" * - * const point = Tuple.make(10, 20, "red") - * console.log(point) // [10, 20, "red"] + * Tuple.make(10, 20, "red") // => [10, 20, "red"] * ``` * * @see {@link get} – access a single element by index @@ -44,7 +43,8 @@ import type { Apply, Lambda } from "./Struct.ts" * @category constructors * @since 2.0.0 */ -export const make = >(...elements: Elements): Elements => elements +export const make = >(...elements: [...Elements]): [...Elements] => + elements type Indices> = Exclude["length"], T["length"]> @@ -61,11 +61,10 @@ type Indices> = Exclude["length"], T * * **Example** (Extracting an element by index) * - * ```ts + * ```ts import.meta.vitest * import { pipe, Tuple } from "effect" * - * const last = pipe(Tuple.make(1, true, "hello"), Tuple.get(2)) - * console.log(last) // "hello" + * pipe(Tuple.make(1, true, "hello"), Tuple.get(2)) // => "hello" * ``` * * @see {@link make} – create a tuple @@ -92,7 +91,9 @@ type _BuildTuple< [...I, unknown] > -type PickTuple, K> = _BuildTuple +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. @@ -107,11 +108,10 @@ type PickTuple, K> = _BuildTuple * * **Example** (Selecting elements by index) * - * ```ts + * ```ts import.meta.vitest * import { Tuple } from "effect" * - * const result = Tuple.pick(["a", "b", "c", "d"], [0, 2, 3]) - * console.log(result) // ["a", "c", "d"] + * Tuple.pick(["a", "b", "c", "d"], [0, 2, 3]) // => ["a", "c", "d"] * ``` * * @see {@link omit} – the inverse (exclude indices instead) @@ -122,11 +122,11 @@ type PickTuple, K> = _BuildTuple export const pick: { , const I extends ReadonlyArray>>( indices: I - ): (self: T) => PickTuple + ): (self: T) => PickTuple , const I extends ReadonlyArray>>( self: T, indices: I - ): PickTuple + ): PickTuple } = dual( 2, >( @@ -152,11 +152,10 @@ type OmitTuple, K> = _BuildTuple ["a", "c"] * ``` * * @see {@link pick} – the inverse (keep only specified indices) @@ -196,11 +195,10 @@ export const omit: { * * **Example** (Appending an element) * - * ```ts + * ```ts import.meta.vitest * import { pipe, Tuple } from "effect" * - * const result = pipe(Tuple.make(1, 2), Tuple.appendElement("end")) - * console.log(result) // [1, 2, "end"] + * pipe(Tuple.make(1, 2), Tuple.appendElement("end")) // => [1, 2, "end"] * ``` * * @see {@link appendElements} – append multiple elements (another tuple) @@ -226,11 +224,10 @@ export const appendElement: { * * **Example** (Concatenating tuples) * - * ```ts + * ```ts import.meta.vitest * import { pipe, Tuple } from "effect" * - * const result = pipe(Tuple.make(1, 2), Tuple.appendElements(["a", "b"] as const)) - * console.log(result) // [1, 2, "a", "b"] + * pipe(Tuple.make(1, 2), Tuple.appendElements(["a", "b"] as const)) // => [1, 2, "a", "b"] * ``` * * @see {@link appendElement} – append a single element @@ -270,17 +267,16 @@ type Evolved = { [I in keyof T]: I extends keyof E ? (E[I] extends (...a: * * **Example** (Transforming selected elements) * - * ```ts + * ```ts import.meta.vitest * import { pipe, Tuple } from "effect" * - * const result = pipe( + * pipe( * Tuple.make("hello", 42, true), * Tuple.evolve([ * (s) => s.toUpperCase(), * (n) => n * 2 * ]) - * ) - * console.log(result) // ["HELLO", 84, true] + * ) // => ["HELLO", 84, true] * ``` * * @see {@link map} – apply the same transformation to all elements @@ -317,18 +313,17 @@ export const evolve: { * * **Example** (Swapping elements) * - * ```ts + * ```ts import.meta.vitest * import { pipe, Tuple } from "effect" * - * const result = pipe( + * pipe( * Tuple.make("a", "b", "c"), * Tuple.renameIndices(["2", "1", "0"]) - * ) - * console.log(result) // ["c", "b", "a"] + * ) // => ["c", "b", "a"] * ``` * * @see {@link evolve} – transform element values instead of positions - * @category Index utilities + * @category transforming * @since 4.0.0 */ export const renameIndices: { @@ -367,7 +362,7 @@ export const renameIndices: { * * **Example** (Wrapping every element in an array) * - * ```ts + * ```ts import.meta.vitest * import { pipe, Struct, Tuple } from "effect" * * interface AsArray extends Struct.Lambda { @@ -376,8 +371,7 @@ export const renameIndices: { * } * * const asArray = Struct.lambda((a) => [a]) - * const result = pipe(Tuple.make(1, "hello", true), Tuple.map(asArray)) - * console.log(result) // [[1], ["hello"], [true]] + * pipe(Tuple.make(1, "hello", true), Tuple.map(asArray)) // => [[1], ["hello"], [true]] * ``` * * @see {@link mapPick} – apply a lambda only to selected indices @@ -414,7 +408,7 @@ export const map: { * * **Example** (Wrapping only selected elements in arrays) * - * ```ts + * ```ts import.meta.vitest * import { pipe, Struct, Tuple } from "effect" * * interface AsArray extends Struct.Lambda { @@ -423,11 +417,10 @@ export const map: { * } * * const asArray = Struct.lambda((a) => [a]) - * const result = pipe( + * pipe( * Tuple.make(1, "hello", true), * Tuple.mapPick([0, 2], asArray) - * ) - * console.log(result) // [[1], "hello", [true]] + * ) // => [[1], "hello", [true]] * ``` * * @see {@link map} – apply a lambda to all elements @@ -470,7 +463,7 @@ export const mapPick: { * * **Example** (Wrapping all elements except one in arrays) * - * ```ts + * ```ts import.meta.vitest * import { pipe, Struct, Tuple } from "effect" * * interface AsArray extends Struct.Lambda { @@ -479,11 +472,10 @@ export const mapPick: { * } * * const asArray = Struct.lambda((a) => [a]) - * const result = pipe( + * pipe( * Tuple.make(1, "hello", true), * Tuple.mapOmit([1], asArray) - * ) - * console.log(result) // [[1], "hello", [true]] + * ) // => [[1], "hello", [true]] * ``` * * @see {@link map} – apply a lambda to all elements @@ -530,7 +522,7 @@ export const mapOmit: { * * **Example** (Comparing tuples for equivalence) * - * ```ts + * ```ts import.meta.vitest * import { Equivalence, Tuple } from "effect" * * const eq = Tuple.makeEquivalence([ @@ -538,8 +530,8 @@ export const mapOmit: { * Equivalence.strictEqual() * ]) * - * console.log(eq(["Alice", 30], ["Alice", 30])) // true - * console.log(eq(["Alice", 30], ["Bob", 30])) // false + * eq(["Alice", 30], ["Alice", 30]) // => true + * eq(["Alice", 30], ["Bob", 30]) // => false * ``` * * @see {@link makeOrder} – create an `Order` for tuples @@ -564,13 +556,13 @@ export const makeEquivalence = Equivalence.Tuple * * **Example** (Ordering tuples) * - * ```ts + * ```ts import.meta.vitest * import { Number, String, Tuple } from "effect" * * const ord = Tuple.makeOrder([String.Order, Number.Order]) * - * console.log(ord(["Alice", 30], ["Bob", 25])) // -1 - * console.log(ord(["Alice", 30], ["Alice", 30])) // 0 + * ord(["Alice", 30], ["Bob", 25]) // => -1 + * ord(["Alice", 30], ["Alice", 30]) // => 0 * ``` * * @see {@link makeEquivalence} – create an `Equivalence` for tuples @@ -600,13 +592,12 @@ export { * * **Example** (Checking exact length) * - * ```ts + * ```ts import.meta.vitest * import { Tuple } from "effect" * * const arr: Array = [1, 2, 3] * if (Tuple.isTupleOf(arr, 3)) { - * console.log(arr) - * // ^? [number, number, number] + * arr // => [1, 2, 3] * } * ``` * @@ -635,13 +626,12 @@ export { * * **Example** (Checking minimum length) * - * ```ts + * ```ts import.meta.vitest * import { Tuple } from "effect" * * const arr: Array = [1, 2, 3, 4] * if (Tuple.isTupleOfAtLeast(arr, 3)) { - * console.log(arr) - * // ^? [number, number, number, ...number[]] + * arr // => [1, 2, 3, 4] * } * ``` * @@ -664,7 +654,7 @@ export { * * **Example** (Combining tuple elements) * - * ```ts + * ```ts import.meta.vitest * import { Number, String, Tuple } from "effect" * * const C = Tuple.makeCombiner([ @@ -672,8 +662,7 @@ export { * String.ReducerConcat * ]) * - * const result = C.combine([1, "hello"], [2, " world"]) - * console.log(result) // [3, "hello world"] + * C.combine([1, "hello"], [2, " world"]) // => [3, "hello world"] * ``` * * @see {@link makeReducer} – like `makeCombiner` but with an initial value @@ -705,7 +694,7 @@ export function makeCombiner>( * * **Example** (Reducing a collection of tuples) * - * ```ts + * ```ts import.meta.vitest * import { Number, String, Tuple } from "effect" * * const R = Tuple.makeReducer([ @@ -713,12 +702,11 @@ export function makeCombiner>( * String.ReducerConcat * ]) * - * const result = R.combineAll([ + * R.combineAll([ * [1, "a"], * [2, "b"], * [3, "c"] - * ]) - * console.log(result) // [6, "abc"] + * ]) // => [6, "abc"] * ``` * * @see {@link makeCombiner} – like `makeReducer` but without an initial value diff --git a/repos/effect/packages/effect/src/TxChunk.ts b/repos/effect/packages/effect/src/TxChunk.ts index ffabde7475..e83aa2c099 100644 --- a/repos/effect/packages/effect/src/TxChunk.ts +++ b/repos/effect/packages/effect/src/TxChunk.ts @@ -35,7 +35,7 @@ const TypeId = "~effect/transactions/TxChunk" * * **Example** (Using a transactional chunk) * - * ```ts + * ```ts import.meta.vitest * import { Chunk, Effect, TxChunk } from "effect" * * const program = Effect.gen(function*() { @@ -49,7 +49,6 @@ const TypeId = "~effect/transactions/TxChunk" * // Single operations - no explicit transaction needed * yield* TxChunk.append(txChunk, 4) * const result = yield* TxChunk.get(txChunk) - * console.log(Chunk.toReadonlyArray(result)) // [1, 2, 3, 4] * * // Multi-step atomic operation - use explicit transaction * yield* Effect.tx( @@ -60,8 +59,10 @@ const TypeId = "~effect/transactions/TxChunk" * ) * * const finalResult = yield* TxChunk.get(txChunk) - * console.log(Chunk.toReadonlyArray(finalResult)) // [0, 1, 2, 3, 4, 5] + * return [Chunk.toArray(result), Chunk.toArray(finalResult)] * }) + * + * await Effect.runPromise(program) // => [[1, 2, 3, 4], [0, 1, 2, 3, 4, 5]] * ``` * * @category models @@ -100,7 +101,7 @@ const TxChunkProto = { * * **Example** (Creating a TxChunk from a chunk) * - * ```ts + * ```ts import.meta.vitest * import { Chunk, Effect, TxChunk } from "effect" * * const program = Effect.gen(function*() { @@ -110,8 +111,10 @@ const TxChunkProto = { * * // Read the value - automatically transactional * const result = yield* TxChunk.get(txChunk) - * console.log(Chunk.toReadonlyArray(result)) // [1, 2, 3] + * return Chunk.toArray(result) * }) + * + * await Effect.runPromise(program) // => [1, 2, 3] * ``` * * @category constructors @@ -130,7 +133,7 @@ export const make = (initial: Chunk.Chunk): Effect.Effect> => * * **Example** (Creating an empty TxChunk) * - * ```ts + * ```ts import.meta.vitest * import { Effect, TxChunk } from "effect" * * const program = Effect.gen(function*() { @@ -139,14 +142,15 @@ export const make = (initial: Chunk.Chunk): Effect.Effect> => * * // Check if it's empty - automatically transactional * const isEmpty = yield* TxChunk.isEmpty(txChunk) - * console.log(isEmpty) // true * * // Add elements - automatically transactional * yield* TxChunk.append(txChunk, 42) * * const isStillEmpty = yield* TxChunk.isEmpty(txChunk) - * console.log(isStillEmpty) // false + * return [isEmpty, isStillEmpty] * }) + * + * await Effect.runPromise(program) // => [true, false] * ``` * * @category constructors @@ -165,7 +169,7 @@ export const empty = (): Effect.Effect> => * * **Example** (Creating from an iterable) * - * ```ts + * ```ts import.meta.vitest * import { Chunk, Effect, TxChunk } from "effect" * * const program = Effect.gen(function*() { @@ -174,7 +178,6 @@ export const empty = (): Effect.Effect> => * * // Read the contents - automatically transactional * const chunk = yield* TxChunk.get(txChunk) - * console.log(Chunk.toReadonlyArray(chunk)) // [1, 2, 3, 4, 5] * * // Multi-step atomic modification - use explicit transaction * yield* Effect.tx( @@ -185,8 +188,10 @@ export const empty = (): Effect.Effect> => * ) * * const updated = yield* TxChunk.get(txChunk) - * console.log(Chunk.toReadonlyArray(updated)) // [0, 1, 2, 3, 4, 5, 6] + * 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 @@ -205,12 +210,13 @@ export const fromIterable = (iterable: Iterable): Effect.Effect * * **Example** (Wrapping an existing TxRef) * - * ```ts - * import { Chunk, TxChunk, TxRef } from "effect" + * ```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 @@ -233,7 +239,7 @@ export const makeUnsafe = (ref: TxRef.TxRef>): TxChunk => { * * **Example** (Modifying while returning a value) * - * ```ts + * ```ts import.meta.vitest * import { Chunk, Effect, TxChunk } from "effect" * * const program = Effect.gen(function*() { @@ -245,11 +251,11 @@ export const makeUnsafe = (ref: TxRef.TxRef>): TxChunk => { * Chunk.append(chunk, 4) // new value * ]) * - * console.log(oldSize) // 3 - * * const newChunk = yield* TxChunk.get(txChunk) - * console.log(Chunk.toReadonlyArray(newChunk)) // [1, 2, 3, 4] + * return [oldSize, Chunk.toArray(newChunk)] * }) + * + * await Effect.runPromise(program) // => [3, [1, 2, 3, 4]] * ``` * * @category combinators @@ -281,7 +287,7 @@ export const modify: { * * **Example** (Updating the stored chunk) * - * ```ts + * ```ts import.meta.vitest * import { Chunk, Effect, TxChunk } from "effect" * * const program = Effect.gen(function*() { @@ -291,8 +297,10 @@ export const modify: { * yield* TxChunk.update(txChunk, (chunk) => Chunk.reverse(chunk)) * * const result = yield* TxChunk.get(txChunk) - * console.log(Chunk.toReadonlyArray(result)) // [3, 2, 1] + * return Chunk.toArray(result) * }) + * + * await Effect.runPromise(program) // => [3, 2, 1] * ``` * * @category combinators @@ -314,7 +322,7 @@ export const update: { * * **Example** (Reading the current chunk) * - * ```ts + * ```ts import.meta.vitest * import { Chunk, Effect, TxChunk } from "effect" * * const program = Effect.gen(function*() { @@ -322,12 +330,10 @@ export const update: { * * // Read the current value within a transaction * const chunk = yield* TxChunk.get(txChunk) - * console.log(Chunk.toReadonlyArray(chunk)) // [1, 2, 3] - * - * // The value is tracked for conflict detection - * const size = Chunk.size(chunk) - * console.log(size) // 3 + * return [Chunk.toArray(chunk), Chunk.size(chunk)] * }) + * + * await Effect.runPromise(program) // => [[1, 2, 3], 3] * ``` * * @category combinators @@ -345,7 +351,7 @@ export const get = (self: TxChunk): Effect.Effect> => TxRef * * **Example** (Replacing the stored chunk) * - * ```ts + * ```ts import.meta.vitest * import { Chunk, Effect, TxChunk } from "effect" * * const program = Effect.gen(function*() { @@ -356,8 +362,10 @@ export const get = (self: TxChunk): Effect.Effect> => TxRef * yield* TxChunk.set(txChunk, newChunk) * * const result = yield* TxChunk.get(txChunk) - * console.log(Chunk.toReadonlyArray(result)) // [10, 20, 30, 40] + * return Chunk.toArray(result) * }) + * + * await Effect.runPromise(program) // => [10, 20, 30, 40] * ``` * * @category combinators @@ -381,7 +389,7 @@ export const set: { * * **Example** (Appending an element) * - * ```ts + * ```ts import.meta.vitest * import { Chunk, Effect, TxChunk } from "effect" * * const program = Effect.gen(function*() { @@ -391,8 +399,10 @@ export const set: { * yield* TxChunk.append(txChunk, 4) * * const result = yield* TxChunk.get(txChunk) - * console.log(Chunk.toReadonlyArray(result)) // [1, 2, 3, 4] + * return Chunk.toArray(result) * }) + * + * await Effect.runPromise(program) // => [1, 2, 3, 4] * ``` * * @category combinators @@ -416,7 +426,7 @@ export const append: { * * **Example** (Prepending an element) * - * ```ts + * ```ts import.meta.vitest * import { Chunk, Effect, TxChunk } from "effect" * * const program = Effect.gen(function*() { @@ -426,8 +436,10 @@ export const append: { * yield* TxChunk.prepend(txChunk, 1) * * const result = yield* TxChunk.get(txChunk) - * console.log(Chunk.toReadonlyArray(result)) // [1, 2, 3, 4] + * return Chunk.toArray(result) * }) + * + * await Effect.runPromise(program) // => [1, 2, 3, 4] * ``` * * @category combinators @@ -446,7 +458,7 @@ export const prepend: { * * **Example** (Getting the size) * - * ```ts + * ```ts import.meta.vitest * import { Effect, TxChunk } from "effect" * * const program = Effect.gen(function*() { @@ -454,13 +466,14 @@ export const prepend: { * * // Get the current size - automatically transactional * const currentSize = yield* TxChunk.size(txChunk) - * console.log(currentSize) // 5 * * // Size is tracked for conflict detection * yield* TxChunk.append(txChunk, 6) * const newSize = yield* TxChunk.size(txChunk) - * console.log(newSize) // 6 + * return [currentSize, newSize] * }) + * + * await Effect.runPromise(program) // => [5, 6] * ``` * * @category combinators @@ -474,7 +487,7 @@ export const size = (self: TxChunk): Effect.Effect => * * **Example** (Checking for an empty chunk) * - * ```ts + * ```ts import.meta.vitest * import { Effect, TxChunk } from "effect" * * const program = Effect.gen(function*() { @@ -485,12 +498,13 @@ export const size = (self: TxChunk): Effect.Effect => * const isEmpty1 = yield* TxChunk.isEmpty(emptyChunk) * const isEmpty2 = yield* TxChunk.isEmpty(nonEmptyChunk) * - * console.log(isEmpty1) // true - * console.log(isEmpty2) // false + * return [isEmpty1, isEmpty2] * }) + * + * await Effect.runPromise(program) // => [true, false] * ``` * - * @category combinators + * @category predicates * @since 4.0.0 */ export const isEmpty = (self: TxChunk): Effect.Effect => @@ -501,7 +515,7 @@ export const isEmpty = (self: TxChunk): Effect.Effect => * * **Example** (Checking for a non-empty chunk) * - * ```ts + * ```ts import.meta.vitest * import { Effect, TxChunk } from "effect" * * const program = Effect.gen(function*() { @@ -512,12 +526,13 @@ export const isEmpty = (self: TxChunk): Effect.Effect => * const isNonEmpty1 = yield* TxChunk.isNonEmpty(emptyChunk) * const isNonEmpty2 = yield* TxChunk.isNonEmpty(nonEmptyChunk) * - * console.log(isNonEmpty1) // false - * console.log(isNonEmpty2) // true + * return [isNonEmpty1, isNonEmpty2] * }) + * + * await Effect.runPromise(program) // => [false, true] * ``` * - * @category combinators + * @category predicates * @since 4.0.0 */ export const isNonEmpty = (self: TxChunk): Effect.Effect => @@ -533,7 +548,7 @@ export const isNonEmpty = (self: TxChunk): Effect.Effect => * * **Example** (Taking leading elements) * - * ```ts + * ```ts import.meta.vitest * import { Chunk, Effect, TxChunk } from "effect" * * const program = Effect.gen(function*() { @@ -543,8 +558,10 @@ export const isNonEmpty = (self: TxChunk): Effect.Effect => * yield* TxChunk.take(txChunk, 3) * * const result = yield* TxChunk.get(txChunk) - * console.log(Chunk.toReadonlyArray(result)) // [1, 2, 3] + * return Chunk.toArray(result) * }) + * + * await Effect.runPromise(program) // => [1, 2, 3] * ``` * * @category combinators @@ -568,7 +585,7 @@ export const take: { * * **Example** (Dropping leading elements) * - * ```ts + * ```ts import.meta.vitest * import { Chunk, Effect, TxChunk } from "effect" * * const program = Effect.gen(function*() { @@ -578,8 +595,10 @@ export const take: { * yield* TxChunk.drop(txChunk, 2) * * const result = yield* TxChunk.get(txChunk) - * console.log(Chunk.toReadonlyArray(result)) // [3, 4, 5] + * return Chunk.toArray(result) * }) + * + * await Effect.runPromise(program) // => [3, 4, 5] * ``` * * @category combinators @@ -603,7 +622,7 @@ export const drop: { * * **Example** (Taking a slice) * - * ```ts + * ```ts import.meta.vitest * import { Chunk, Effect, TxChunk } from "effect" * * const program = Effect.gen(function*() { @@ -613,8 +632,10 @@ export const drop: { * yield* TxChunk.slice(txChunk, 2, 5) * * const result = yield* TxChunk.get(txChunk) - * console.log(Chunk.toReadonlyArray(result)) // [3, 4, 5] + * return Chunk.toArray(result) * }) + * + * await Effect.runPromise(program) // => [3, 4, 5] * ``` * * @category combinators @@ -640,7 +661,7 @@ export const slice: { * * **Example** (Mapping elements) * - * ```ts + * ```ts import.meta.vitest * import { Chunk, Effect, TxChunk } from "effect" * * const program = Effect.gen(function*() { @@ -650,8 +671,10 @@ export const slice: { * yield* TxChunk.map(txChunk, (n) => n * 2) * * const result = yield* TxChunk.get(txChunk) - * console.log(Chunk.toReadonlyArray(result)) // [2, 4, 6, 8] + * return Chunk.toArray(result) * }) + * + * await Effect.runPromise(program) // => [2, 4, 6, 8] * ``` * * @category combinators @@ -675,7 +698,7 @@ export const map: { * * **Example** (Filtering elements) * - * ```ts + * ```ts import.meta.vitest * import { Chunk, Effect, TxChunk } from "effect" * * const program = Effect.gen(function*() { @@ -685,8 +708,10 @@ export const map: { * yield* TxChunk.filter(txChunk, (n) => n % 2 === 0) * * const result = yield* TxChunk.get(txChunk) - * console.log(Chunk.toReadonlyArray(result)) // [2, 4, 6] + * return Chunk.toArray(result) * }) + * + * await Effect.runPromise(program) // => [2, 4, 6] * ``` * * @category combinators @@ -713,7 +738,7 @@ export const filter: { * * **Example** (Appending another chunk) * - * ```ts + * ```ts import.meta.vitest * import { Chunk, Effect, TxChunk } from "effect" * * const program = Effect.gen(function*() { @@ -724,8 +749,10 @@ export const filter: { * yield* TxChunk.appendAll(txChunk, otherChunk) * * const result = yield* TxChunk.get(txChunk) - * console.log(Chunk.toReadonlyArray(result)) // [1, 2, 3, 4, 5, 6] + * return Chunk.toArray(result) * }) + * + * await Effect.runPromise(program) // => [1, 2, 3, 4, 5, 6] * ``` * * @category combinators @@ -750,7 +777,7 @@ export const appendAll: { * * **Example** (Prepending another chunk) * - * ```ts + * ```ts import.meta.vitest * import { Chunk, Effect, TxChunk } from "effect" * * const program = Effect.gen(function*() { @@ -761,8 +788,10 @@ export const appendAll: { * yield* TxChunk.prependAll(txChunk, otherChunk) * * const result = yield* TxChunk.get(txChunk) - * console.log(Chunk.toReadonlyArray(result)) // [1, 2, 3, 4, 5, 6] + * return Chunk.toArray(result) * }) + * + * await Effect.runPromise(program) // => [1, 2, 3, 4, 5, 6] * ``` * * @category combinators @@ -787,7 +816,7 @@ export const prependAll: { * * **Example** (Concatenating TxChunks) * - * ```ts + * ```ts import.meta.vitest * import { Chunk, Effect, TxChunk } from "effect" * * const program = Effect.gen(function*() { @@ -798,12 +827,13 @@ export const prependAll: { * yield* TxChunk.concat(txChunk1, txChunk2) * * const result = yield* TxChunk.get(txChunk1) - * console.log(Chunk.toReadonlyArray(result)) // [1, 2, 3, 4, 5, 6] * * // Original txChunk2 is unchanged * const original = yield* TxChunk.get(txChunk2) - * console.log(Chunk.toReadonlyArray(original)) // [4, 5, 6] + * return [Chunk.toArray(result), Chunk.toArray(original)] * }) + * + * await Effect.runPromise(program) // => [[1, 2, 3, 4, 5, 6], [4, 5, 6]] * ``` * * @category combinators diff --git a/repos/effect/packages/effect/src/TxDeferred.ts b/repos/effect/packages/effect/src/TxDeferred.ts index 851f811b02..637e614948 100644 --- a/repos/effect/packages/effect/src/TxDeferred.ts +++ b/repos/effect/packages/effect/src/TxDeferred.ts @@ -37,7 +37,7 @@ const TypeId = "~effect/transactions/TxDeferred" * * **Example** (Completing a transactional deferred) * - * ```ts + * ```ts import.meta.vitest * import { Effect, TxDeferred } from "effect" * * const program = Effect.gen(function*() { @@ -45,16 +45,16 @@ const TypeId = "~effect/transactions/TxDeferred" * * // Complete the deferred * const first = yield* TxDeferred.succeed(deferred, 42) - * console.log(first) // true * * // Second write is a no-op * const second = yield* TxDeferred.succeed(deferred, 99) - * console.log(second) // false * * // Read the value * const value = yield* TxDeferred.await(deferred) - * console.log(value) // 42 + * return [first, second, value] * }) + * + * await Effect.runPromise(program) // => [true, false, 42] * ``` * * @category models @@ -95,14 +95,15 @@ const makeTxDeferred = (ref: TxRef.TxRef>>): TxDeferre * * **Example** (Creating a transactional deferred) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Option, TxDeferred } from "effect" * * const program = Effect.gen(function*() { * const deferred = yield* TxDeferred.make() - * const state = yield* TxDeferred.poll(deferred) - * console.log(Option.isNone(state)) // true + * return yield* TxDeferred.poll(deferred) * }) + * + * await Effect.runPromise(program) // => Option.none() * ``` * * @category constructors @@ -117,15 +118,16 @@ export const make = (): Effect.Effect> => * * **Example** (Awaiting a deferred value) * - * ```ts + * ```ts import.meta.vitest * import { Effect, TxDeferred } from "effect" * * const program = Effect.gen(function*() { * const deferred = yield* TxDeferred.make() * yield* TxDeferred.succeed(deferred, 42) - * const value = yield* TxDeferred.await(deferred) - * console.log(value) // 42 + * return yield* TxDeferred.await(deferred) * }) + * + * await Effect.runPromise(program) // => 42 * ``` * * @category getters @@ -170,18 +172,19 @@ export { * * **Example** (Polling a deferred) * - * ```ts + * ```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) - * console.log(Option.isNone(before)) // true * * yield* TxDeferred.succeed(deferred, 42) * const after = yield* TxDeferred.poll(deferred) - * console.log(after) // Some(Success(42)) + * return [before, after] * }) + * + * await Effect.runPromise(program) // => [Option.none(), Option.some(Result.succeed(42))] * ``` * * @category getters @@ -199,16 +202,17 @@ export const poll = (self: TxDeferred): Effect.Effect() * const first = yield* TxDeferred.done(deferred, Result.succeed(42)) - * console.log(first) // true * const second = yield* TxDeferred.done(deferred, Result.succeed(99)) - * console.log(second) // false + * return [first, second] * }) + * + * await Effect.runPromise(program) // => [true, false] * ``` * * @category mutations @@ -238,16 +242,17 @@ export const done: { * * **Example** (Completing with a success value) * - * ```ts + * ```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) - * console.log(first) // true * const second = yield* TxDeferred.succeed(deferred, 99) - * console.log(second) // false + * return [first, second] * }) + * + * await Effect.runPromise(program) // => [true, false] * ``` * * @category mutations @@ -271,16 +276,18 @@ export const succeed: { * * **Example** (Completing with a failure) * - * ```ts - * import { Effect, TxDeferred } from "effect" + * ```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") - * console.log(first) // true * const second = yield* TxDeferred.fail(deferred, "boom2") - * console.log(second) // false + * 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 @@ -303,14 +310,15 @@ export const fail: { * * **Example** (Checking transactional deferreds) * - * ```ts + * ```ts import.meta.vitest * import { Effect, TxDeferred } from "effect" * * const program = Effect.gen(function*() { * const deferred = yield* TxDeferred.make() - * console.log(TxDeferred.isTxDeferred(deferred)) // true - * console.log(TxDeferred.isTxDeferred("not a deferred")) // false + * return [TxDeferred.isTxDeferred(deferred), TxDeferred.isTxDeferred("not a deferred")] * }) + * + * await Effect.runPromise(program) // => [true, false] * ``` * * @category guards diff --git a/repos/effect/packages/effect/src/TxHashMap.ts b/repos/effect/packages/effect/src/TxHashMap.ts index c18ff1ef45..9671b41da8 100644 --- a/repos/effect/packages/effect/src/TxHashMap.ts +++ b/repos/effect/packages/effect/src/TxHashMap.ts @@ -51,8 +51,8 @@ const TxHashMapProto = { * * **Example** (Using transactional hash maps) * - * ```ts - * import { Effect, TxHashMap } from "effect" + * ```ts import.meta.vitest + * import { Effect, Option, TxHashMap } from "effect" * * const program = Effect.gen(function*() { * // Create a transactional hash map @@ -60,8 +60,7 @@ const TxHashMapProto = { * * // Single operations are automatically transactional * yield* TxHashMap.set(txMap, "user3", "Charlie") - * const user = yield* TxHashMap.get(txMap, "user1") - * console.log(user) // Option.some("Alice") + * yield* TxHashMap.get(txMap, "user1") // => Option.some("Alice") * * // Multi-step atomic operations * yield* Effect.tx( @@ -74,9 +73,10 @@ const TxHashMapProto = { * }) * ) * - * const size = yield* TxHashMap.size(txMap) - * console.log(size) // 2 + * return yield* TxHashMap.size(txMap) * }) + * + * await Effect.runPromise(program) // => 2 * ``` * * @category models @@ -93,8 +93,8 @@ export interface TxHashMap extends Inspectable, Pipeable { * * **Example** (Reusing extracted TxHashMap types) * - * ```ts - * import { Effect, TxHashMap } from "effect" + * ```ts import.meta.vitest + * import { Effect, Option, TxHashMap } from "effect" * * const program = Effect.gen(function*() { * // Create a transactional inventory map @@ -117,7 +117,10 @@ export interface TxHashMap extends Inspectable, Pipeable { * ) * * yield* updateStock("laptop", 3) + * return yield* TxHashMap.get(inventory, "laptop") * }) + * + * await Effect.runPromise(program) // => Option.some({ stock: 3, price: 999 }) * ``` * * @since 4.0.0 @@ -128,8 +131,8 @@ export declare namespace TxHashMap { * * **Example** (Extracting key types) * - * ```ts - * import { Effect, TxHashMap } from "effect" + * ```ts import.meta.vitest + * import { Effect, Option, TxHashMap } from "effect" * * const program = Effect.gen(function*() { * // Create a user map to extract key type from @@ -143,8 +146,10 @@ export declare namespace TxHashMap { * * // Use the extracted type in functions * const getUserById = (id: UserKey) => TxHashMap.get(userMap, id) - * const alice = yield* getUserById("alice") // Option<{ name: string, age: number }> + * return yield* getUserById("alice") * }) + * + * await Effect.runPromise(program) // => Option.some({ name: "Alice", age: 30 }) * ``` * * @category utility types @@ -157,8 +162,8 @@ export declare namespace TxHashMap { * * **Example** (Extracting value types) * - * ```ts - * import { Effect, TxHashMap } from "effect" + * ```ts import.meta.vitest + * import { Effect, Option, TxHashMap } from "effect" * * const program = Effect.gen(function*() { * // Create a product catalog TxHashMap @@ -175,9 +180,10 @@ export declare namespace TxHashMap { * return `${product.category}: $${product.price}` * } * - * const laptop = yield* TxHashMap.get(catalog, "laptop") - * // laptop has type Option thanks to type extraction + * return Option.map(yield* TxHashMap.get(catalog, "laptop"), processProduct) * }) + * + * await Effect.runPromise(program) // => Option.some("electronics: $999") * ``` * * @category utility types @@ -190,7 +196,7 @@ export declare namespace TxHashMap { * * **Example** (Extracting entry types) * - * ```ts + * ```ts import.meta.vitest * import { Effect, TxHashMap } from "effect" * * const program = Effect.gen(function*() { @@ -210,10 +216,10 @@ export declare namespace TxHashMap { * } * * // Get all entries and process them - * const entries = yield* TxHashMap.entries(config) - * const configLines = entries.map(processEntry) - * console.log(configLines) // ["api_url=https://api.example.com", ...] + * 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 @@ -227,7 +233,7 @@ export declare namespace TxHashMap { * * **Example** (Creating an empty map) * - * ```ts + * ```ts import.meta.vitest * import { Effect, TxHashMap } from "effect" * * const program = Effect.gen(function*() { @@ -235,17 +241,15 @@ export declare namespace TxHashMap { * const emptyMap = yield* TxHashMap.empty() * * // Verify it's empty - * const isEmpty = yield* TxHashMap.isEmpty(emptyMap) - * console.log(isEmpty) // true - * - * const size = yield* TxHashMap.size(emptyMap) - * console.log(size) // 0 + * yield* TxHashMap.isEmpty(emptyMap) // => true + * yield* TxHashMap.size(emptyMap) // => 0 * * // Start adding elements * yield* TxHashMap.set(emptyMap, "first", 1) - * const newSize = yield* TxHashMap.size(emptyMap) - * console.log(newSize) // 1 + * return yield* TxHashMap.size(emptyMap) * }) + * + * await Effect.runPromise(program) // => 1 * ``` * * @category constructors @@ -262,8 +266,8 @@ export const empty = (): Effect.Effect> => * * **Example** (Creating a map from entries) * - * ```ts - * import { Effect, TxHashMap } from "effect" + * ```ts import.meta.vitest + * import { Effect, Option, TxHashMap } from "effect" * * const program = Effect.gen(function*() { * // Create a user directory @@ -274,16 +278,14 @@ export const empty = (): Effect.Effect> => * ) * * // Check the initial size - * const size = yield* TxHashMap.size(userMap) - * console.log(size) // 3 + * yield* TxHashMap.size(userMap) // => 3 * * // Access users - * const alice = yield* TxHashMap.get(userMap, "alice") - * console.log(alice) // Option.some({ name: "Alice Smith", role: "admin" }) - * - * const nonExistent = yield* TxHashMap.get(userMap, "david") - * console.log(nonExistent) // Option.none() + * 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 @@ -303,8 +305,8 @@ export const make = ( * * **Example** (Creating a map from an iterable) * - * ```ts - * import { Effect, TxHashMap } from "effect" + * ```ts import.meta.vitest + * import { Effect, Option, TxHashMap } from "effect" * * const program = Effect.gen(function*() { * // Create from various iterable sources @@ -318,16 +320,15 @@ export const make = ( * const configMap = yield* TxHashMap.fromIterable(configEntries) * * // Verify the configuration was loaded - * const size = yield* TxHashMap.size(configMap) - * console.log(size) // 4 - * - * const dbHost = yield* TxHashMap.get(configMap, "database.host") - * console.log(dbHost) // Option.some("localhost") + * 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"]]) - * const txMapFromJs = yield* TxHashMap.fromIterable(jsMap) + * return yield* TxHashMap.fromIterable(jsMap) * }) + * + * await Effect.runPromise(program) * ``` * * @category constructors @@ -347,7 +348,7 @@ export const fromIterable = ( * * **Example** (Looking up values safely) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Option, TxHashMap } from "effect" * * const program = Effect.gen(function*() { @@ -357,18 +358,14 @@ export const fromIterable = ( * ) * * // Safe lookup - returns Option - * const alice = yield* TxHashMap.get(userMap, "alice") - * console.log(alice) // Option.some({ name: "Alice", role: "admin" }) - * - * const nonExistent = yield* TxHashMap.get(userMap, "charlie") - * console.log(nonExistent) // Option.none() + * 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 - * const bobRole = yield* TxHashMap.get(userMap, "bob") - * if (bobRole._tag === "Some") { - * console.log(bobRole.value.role) // "user" - * } + * return yield* TxHashMap.get(userMap, "bob") * }) + * + * await Effect.runPromise(program) // => Option.some({ name: "Bob", role: "user" }) * ``` * * @category combinators @@ -396,8 +393,8 @@ export const get: { * * **Example** (Setting values) * - * ```ts - * import { Effect, TxHashMap } from "effect" + * ```ts import.meta.vitest + * import { Effect, Option, TxHashMap } from "effect" * * const program = Effect.gen(function*() { * const inventory = yield* TxHashMap.make( @@ -407,17 +404,18 @@ export const get: { * * // Update existing item * yield* TxHashMap.set(inventory, "laptop", 3) - * const laptopStock = yield* TxHashMap.get(inventory, "laptop") - * console.log(laptopStock) // Option.some(3) + * yield* TxHashMap.get(inventory, "laptop") // => Option.some(3) * * // Add new item * yield* TxHashMap.set(inventory, "keyboard", 15) - * const keyboardStock = yield* TxHashMap.get(inventory, "keyboard") - * console.log(keyboardStock) // Option.some(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 @@ -437,7 +435,7 @@ export const set: { * * **Example** (Checking for keys) * - * ```ts + * ```ts import.meta.vitest * import { Effect, TxHashMap } from "effect" * * const program = Effect.gen(function*() { @@ -448,19 +446,17 @@ export const set: { * ) * * // Check if users exist - * const hasAlice = yield* TxHashMap.has(permissions, "alice") - * console.log(hasAlice) // true - * - * const hasDavid = yield* TxHashMap.has(permissions, "david") - * console.log(hasDavid) // false + * yield* TxHashMap.has(permissions, "alice") // => true + * yield* TxHashMap.has(permissions, "david") // => false * * // Use direct method call for type-safe access - * const hasBob = yield* TxHashMap.has(permissions, "bob") - * console.log(hasBob) // true + * return yield* TxHashMap.has(permissions, "bob") * }) + * + * await Effect.runPromise(program) // => true * ``` * - * @category combinators + * @category predicates * @since 2.0.0 */ export const has: { @@ -485,7 +481,7 @@ export const has: { * * **Example** (Removing keys) * - * ```ts + * ```ts import.meta.vitest * import { Effect, TxHashMap } from "effect" * * const program = Effect.gen(function*() { @@ -496,20 +492,17 @@ export const has: { * ) * * // Remove expired user - * const removed = yield* TxHashMap.remove(cache, "user:3") - * console.log(removed) // true (key existed and was removed) + * yield* TxHashMap.remove(cache, "user:3") // => true * * // Try to remove non-existent key - * const notRemoved = yield* TxHashMap.remove(cache, "user:999") - * console.log(notRemoved) // false (key didn't exist) + * yield* TxHashMap.remove(cache, "user:999") // => false * * // Verify removal - * const hasUser3 = yield* TxHashMap.has(cache, "user:3") - * console.log(hasUser3) // false - * - * const size = yield* TxHashMap.size(cache) - * console.log(size) // 2 + * yield* TxHashMap.has(cache, "user:3") // => false + * return yield* TxHashMap.size(cache) * }) + * + * await Effect.runPromise(program) // => 2 * ``` * * @category combinators @@ -541,7 +534,7 @@ export const remove: { * * **Example** (Clearing all entries) * - * ```ts + * ```ts import.meta.vitest * import { Effect, TxHashMap } from "effect" * * const program = Effect.gen(function*() { @@ -552,19 +545,17 @@ export const remove: { * ) * * // Check initial state - * const initialSize = yield* TxHashMap.size(sessionMap) - * console.log(initialSize) // 3 + * yield* TxHashMap.size(sessionMap) // => 3 * * // Clear all sessions (e.g., during maintenance) * yield* TxHashMap.clear(sessionMap) * * // Verify cleared - * const finalSize = yield* TxHashMap.size(sessionMap) - * console.log(finalSize) // 0 - * - * const isEmpty = yield* TxHashMap.isEmpty(sessionMap) - * console.log(isEmpty) // true + * yield* TxHashMap.size(sessionMap) // => 0 + * return yield* TxHashMap.isEmpty(sessionMap) * }) + * + * await Effect.runPromise(program) // => true * ``` * * @category combinators @@ -577,7 +568,7 @@ export const clear = (self: TxHashMap): Effect.Effect => TxRef * * **Example** (Counting entries) * - * ```ts + * ```ts import.meta.vitest * import { Effect, TxHashMap } from "effect" * * const program = Effect.gen(function*() { @@ -587,19 +578,18 @@ export const clear = (self: TxHashMap): Effect.Effect => TxRef * ["users", 50] * ) * - * const count = yield* TxHashMap.size(metrics) - * console.log(count) // 3 + * yield* TxHashMap.size(metrics) // => 3 * * // Add more metrics * yield* TxHashMap.set(metrics, "response_time", 250) - * const newCount = yield* TxHashMap.size(metrics) - * console.log(newCount) // 4 + * yield* TxHashMap.size(metrics) // => 4 * * // Remove a metric * yield* TxHashMap.remove(metrics, "errors") - * const finalCount = yield* TxHashMap.size(metrics) - * console.log(finalCount) // 3 + * return yield* TxHashMap.size(metrics) * }) + * + * await Effect.runPromise(program) // => 3 * ``` * * @category combinators @@ -616,28 +606,27 @@ export const size = (self: TxHashMap): Effect.Effect => * * **Example** (Checking for an empty map) * - * ```ts + * ```ts import.meta.vitest * import { Effect, TxHashMap } from "effect" * * const program = Effect.gen(function*() { * // Start with empty map * const cache = yield* TxHashMap.empty() - * const empty = yield* TxHashMap.isEmpty(cache) - * console.log(empty) // true + * yield* TxHashMap.isEmpty(cache) // => true * * // Add an item * yield* TxHashMap.set(cache, "key1", "value1") - * const stillEmpty = yield* TxHashMap.isEmpty(cache) - * console.log(stillEmpty) // false + * yield* TxHashMap.isEmpty(cache) // => false * * // Clear and check again * yield* TxHashMap.clear(cache) - * const emptyAgain = yield* TxHashMap.isEmpty(cache) - * console.log(emptyAgain) // true + * return yield* TxHashMap.isEmpty(cache) * }) + * + * await Effect.runPromise(program) // => true * ``` * - * @category combinators + * @category predicates * @since 2.0.0 */ export const isEmpty = (self: TxHashMap): Effect.Effect => @@ -651,23 +640,23 @@ export const isEmpty = (self: TxHashMap): Effect.Effect => * * **Example** (Checking for a non-empty map) * - * ```ts + * ```ts import.meta.vitest * import { Effect, TxHashMap } from "effect" * * const program = Effect.gen(function*() { * const inventory = yield* TxHashMap.make(["laptop", 5]) * - * const hasItems = yield* TxHashMap.isNonEmpty(inventory) - * console.log(hasItems) // true + * yield* TxHashMap.isNonEmpty(inventory) // => true * * // Clear inventory * yield* TxHashMap.clear(inventory) - * const stillHasItems = yield* TxHashMap.isNonEmpty(inventory) - * console.log(stillHasItems) // false + * return yield* TxHashMap.isNonEmpty(inventory) * }) + * + * await Effect.runPromise(program) // => false * ``` * - * @category combinators + * @category predicates * @since 4.0.0 */ export const isNonEmpty = (self: TxHashMap): Effect.Effect => @@ -683,8 +672,8 @@ export const isNonEmpty = (self: TxHashMap): Effect.Effect * * **Example** (Updating existing values) * - * ```ts - * import { Effect, TxHashMap } from "effect" + * ```ts import.meta.vitest + * import { Effect, Option, TxHashMap } from "effect" * * const program = Effect.gen(function*() { * const counters = yield* TxHashMap.make( @@ -698,10 +687,9 @@ export const isNonEmpty = (self: TxHashMap): Effect.Effect * "downloads", * (count) => count + 1 * ) - * console.log(oldDownloads) // Option.some(100) + * oldDownloads // => Option.some(100) * - * const newDownloads = yield* TxHashMap.get(counters, "downloads") - * console.log(newDownloads) // Option.some(101) + * yield* TxHashMap.get(counters, "downloads") // => Option.some(101) * * // Try to modify non-existent key * const nonExistent = yield* TxHashMap.modify( @@ -709,11 +697,14 @@ export const isNonEmpty = (self: TxHashMap): Effect.Effect * "clicks", * (count) => count + 1 * ) - * console.log(nonExistent) // Option.none() + * 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 @@ -755,42 +746,34 @@ export const modify: { * * **Example** (Updating values with Option) * - * ```ts - * import { Effect, TxHashMap } from "effect" + * ```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", (current) => - * current._tag === "Some" && typeof current.value === "number" - * ? { ...current, value: current.value + 1 } - * : current - * ) - * const count1 = yield* TxHashMap.get(storage, "access_count") - * console.log(count1) // Option.some(1) + * 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", (current) => - * current._tag === "Some" && typeof current.value === "number" - * ? { ...current, value: current.value + 1 } - * : current - * ) - * const count2 = yield* TxHashMap.get(storage, "access_count") - * console.log(count2) // Option.some(2) + * 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", (current) => - * current._tag === "Some" && typeof current.value === "string" - * ? { ...current, value: `${current.value}.bak` } - * : current + * yield* TxHashMap.modifyAt( + * storage, + * "file1.txt", + * Option.map((value) => typeof value === "string" ? `${value}.bak` : value) * ) - * const backup = yield* TxHashMap.get(storage, "file1.txt") - * console.log(backup) // Option.some("content1.bak") + * return yield* TxHashMap.get(storage, "file1.txt") * }) + * + * await Effect.runPromise(program) // => Option.some("content1.bak") * ``` * * @category combinators @@ -831,7 +814,7 @@ export const modifyAt: { * * **Example** (Reading keys) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Option, TxHashMap } from "effect" * * const program = Effect.gen(function*() { @@ -841,17 +824,21 @@ export const modifyAt: { * ["charlie", "moderator"] * ) * - * const usernames = yield* TxHashMap.keys(userRoles) - * console.log(usernames.sort()) // ["alice", "bob", "charlie"] + * 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") { - * console.log(`${username}: ${role.value}`) + * assignments.push(`${username}: ${role.value}`) * } * } + * return assignments * }) + * + * await Effect.runPromise(program) // => ["alice: admin", "bob: user", "charlie: moderator"] * ``` * * @category combinators @@ -868,7 +855,7 @@ export const keys = (self: TxHashMap): Effect.Effect> => * * **Example** (Reading values) * - * ```ts + * ```ts import.meta.vitest * import { Effect, TxHashMap } from "effect" * * const program = Effect.gen(function*() { @@ -878,18 +865,19 @@ export const keys = (self: TxHashMap): Effect.Effect> => * ["charlie", 92] * ) * - * const allScores = yield* TxHashMap.values(scores) - * console.log(allScores.sort((a, b) => a - b)) // [87, 92, 95] + * 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 - * console.log(average.toFixed(2)) // "91.33" + * average.toFixed(2) // => "91.33" * * // Find maximum - * const maxScore = Math.max(...allScores) - * console.log(maxScore) // 95 + * return Math.max(...allScores) * }) + * + * await Effect.runPromise(program) // => 95 * ``` * * @category combinators @@ -906,7 +894,7 @@ export const values = (self: TxHashMap): Effect.Effect> => * * **Example** (Reading entries) * - * ```ts + * ```ts import.meta.vitest * import { Effect, TxHashMap } from "effect" * * const program = Effect.gen(function*() { @@ -916,19 +904,10 @@ export const values = (self: TxHashMap): Effect.Effect> => * ["ssl", "false"] * ) * - * const allEntries = yield* TxHashMap.entries(config) - * const sortedEntries = allEntries.toSorted(([left], [right]) => left.localeCompare(right)) - * console.log(sortedEntries) - * // [["host", "localhost"], ["port", "3000"], ["ssl", "false"]] - * - * // Process configuration entries - * for (const [key, value] of sortedEntries) { - * console.log(`${key}=${value}`) - * } - * // 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 @@ -947,8 +926,8 @@ export const entries = ( * * **Example** (Taking immutable snapshots) * - * ```ts - * import { Effect, HashMap, TxHashMap } from "effect" + * ```ts import.meta.vitest + * import { Effect, HashMap, Option, TxHashMap } from "effect" * * const program = Effect.gen(function*() { * const liveData = yield* TxHashMap.make( @@ -965,13 +944,14 @@ export const entries = ( * yield* TxHashMap.set(liveData, "wind_speed", 5.3) * * // Snapshot remains unchanged - * console.log(HashMap.size(snapshot)) // 3 - * console.log(HashMap.get(snapshot, "temperature")) // Option.some(22.5) + * HashMap.size(snapshot) // => 3 + * HashMap.get(snapshot, "temperature") // => Option.some(22.5) * * // Can use regular HashMap operations on snapshot - * const tempReading = HashMap.get(snapshot, "temperature") - * const humidityReading = HashMap.get(snapshot, "humidity") + * return HashMap.get(snapshot, "humidity") * }) + * + * await Effect.runPromise(program) // => Option.some(45.2) * ``` * * @category combinators @@ -992,8 +972,8 @@ export const snapshot = ( * * **Example** (Merging HashMaps) * - * ```ts - * import { Effect, HashMap, TxHashMap } from "effect" + * ```ts import.meta.vitest + * import { Effect, HashMap, Option, TxHashMap } from "effect" * * const program = Effect.gen(function*() { * // Create initial user preferences @@ -1014,18 +994,13 @@ export const snapshot = ( * yield* TxHashMap.union(userPrefs, newSettings) * * // Check the merged result - * const theme = yield* TxHashMap.get(userPrefs, "theme") - * console.log(theme) // Option.some("dark") - overridden - * - * const language = yield* TxHashMap.get(userPrefs, "language") - * console.log(language) // Option.some("en") - preserved - * - * const timezone = yield* TxHashMap.get(userPrefs, "timezone") - * console.log(timezone) // Option.some("UTC") - newly added - * - * const size = yield* TxHashMap.size(userPrefs) - * console.log(size) // 5 total settings + * 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 @@ -1057,8 +1032,8 @@ export const union: { * * **Example** (Removing multiple keys) * - * ```ts - * import { Effect, TxHashMap } from "effect" + * ```ts import.meta.vitest + * import { Effect, Option, TxHashMap } from "effect" * * const program = Effect.gen(function*() { * // Create a cache with temporary data @@ -1070,23 +1045,24 @@ export const union: { * ["temp_data_2", { value: "also_temporary" }] * ) * - * console.log(yield* TxHashMap.size(cache)) // 5 + * 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) * - * console.log(yield* TxHashMap.size(cache)) // 1 + * yield* TxHashMap.size(cache) // => 1 * * // Verify only the valid session remains - * const remainingSession = yield* TxHashMap.get(cache, "session_3") - * console.log(remainingSession) // Option.some({ user: "charlie", expires: "2024-12-31" }) + * 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) - * console.log(yield* TxHashMap.isEmpty(cache)) // true + * return yield* TxHashMap.isEmpty(cache) * }) + * + * await Effect.runPromise(program) // => true * ``` * * @category combinators @@ -1111,8 +1087,8 @@ export const removeMany: { * * **Example** (Setting multiple entries) * - * ```ts - * import { Effect, TxHashMap } from "effect" + * ```ts import.meta.vitest + * import { Effect, Option, TxHashMap } from "effect" * * const program = Effect.gen(function*() { * // Create an empty product catalog @@ -1133,7 +1109,7 @@ export const removeMany: { * * yield* TxHashMap.setMany(catalog, initialProducts) * - * console.log(yield* TxHashMap.size(catalog)) // 4 + * yield* TxHashMap.size(catalog) // => 4 * * // Update prices with a new batch * const priceUpdates: Array< @@ -1146,16 +1122,18 @@ export const removeMany: { * * yield* TxHashMap.setMany(catalog, priceUpdates) * - * console.log(yield* TxHashMap.size(catalog)) // 5 (4 original + 1 new) + * yield* TxHashMap.size(catalog) // => 5 * * // Verify the updates - * const laptop = yield* TxHashMap.get(catalog, "laptop") - * console.log(laptop) // Option.some({ price: 899, stock: 5 }) + * 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 @@ -1182,16 +1160,16 @@ export const setMany: { * * **Example** (Checking TxHashMap values) * - * ```ts - * import { Effect, TxHashMap } from "effect" + * ```ts import.meta.vitest + * import { Effect, Exit, TxHashMap } from "effect" * * const program = Effect.gen(function*() { * const txMap = yield* TxHashMap.make(["key", "value"]) * - * console.log(TxHashMap.isTxHashMap(txMap)) // true - * console.log(TxHashMap.isTxHashMap({})) // false - * console.log(TxHashMap.isTxHashMap(null)) // false - * console.log(TxHashMap.isTxHashMap("not a map")) // false + * 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) => { @@ -1201,7 +1179,12 @@ export const setMany: { * } * 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 @@ -1222,8 +1205,8 @@ export const isTxHashMap = (value: unknown): value is TxHashMap => { * * **Example** (Looking up values with precomputed hashes) * - * ```ts - * import { Effect, Hash, TxHashMap } from "effect" + * ```ts import.meta.vitest + * import { Effect, Hash, Option, TxHashMap } from "effect" * * const program = Effect.gen(function*() { * // Create a cache with user sessions @@ -1238,16 +1221,17 @@ export const isTxHashMap = (value: unknown): value is TxHashMap => { * * // Use hash-optimized lookup for performance in hot paths * const session = yield* TxHashMap.getHash(cache, sessionId, precomputedHash) - * console.log(session) // Option.some({ userId: "user1", lastActive: ... }) + * session // => Option.some({ userId: "user1", lastActive: 1_700_000_000_000 }) * * // This avoids recomputing the hash when you already have it - * const invalidSession = yield* TxHashMap.getHash( + * return yield* TxHashMap.getHash( * cache, * "invalid", * Hash.string("invalid") * ) - * console.log(invalidSession) // Option.none() * }) + * + * await Effect.runPromise(program) // => Option.none() * ``` * * @category combinators @@ -1283,7 +1267,7 @@ export const getHash: { * * **Example** (Checking keys with precomputed hashes) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Hash, TxHashMap } from "effect" * * const program = Effect.gen(function*() { @@ -1298,29 +1282,30 @@ export const getHash: { * const roleHash = Hash.string(role) * * // Use hash-optimized existence check - * const hasAdminRole = yield* TxHashMap.hasHash(permissions, role, roleHash) - * console.log(hasAdminRole) // true + * yield* TxHashMap.hasHash(permissions, role, roleHash) // => true * * // Check non-existent role - * const hasGuestRole = yield* TxHashMap.hasHash( + * yield* TxHashMap.hasHash( * permissions, * "guest", * Hash.string("guest") - * ) - * console.log(hasGuestRole) // false + * ) // => 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) - * console.log(`Role ${role}: ${exists}`) + * results.push(`Role ${role}: ${exists}`) * } + * return results * }) + * + * await Effect.runPromise(program) // => ["Role admin: true", "Role user: true", "Role moderator: false"] * ``` * - * @category combinators + * @category predicates * @since 4.0.0 */ export const hasHash: { @@ -1348,8 +1333,8 @@ export const hasHash: { * * **Example** (Mapping values) * - * ```ts - * import { Effect, TxHashMap } from "effect" + * ```ts import.meta.vitest + * import { Effect, Option, TxHashMap } from "effect" * * const program = Effect.gen(function*() { * // Create a user profile map @@ -1366,21 +1351,20 @@ export const hasHash: { * ) * * // Check the transformed values - * const aliceGreeting = yield* TxHashMap.get(greetings, "alice") - * console.log(aliceGreeting) // Option.some("Hello, Alice! (User: alice)") + * 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) * ) * - * const aliceAge = yield* TxHashMap.get(ages, "alice") - * console.log(aliceAge) // Option.some(30) + * yield* TxHashMap.get(ages, "alice") // => Option.some(30) * * // Original map is unchanged - * const originalAlice = yield* TxHashMap.get(profiles, "alice") - * console.log(originalAlice) // Option.some({ name: "Alice", age: 30, active: true }) + * return yield* TxHashMap.get(profiles, "alice") * }) + * + * await Effect.runPromise(program) // => Option.some({ name: "Alice", age: 30, active: true }) * ``` * * @category combinators @@ -1417,7 +1401,7 @@ export const map: { * * **Example** (Filtering entries) * - * ```ts + * ```ts import.meta.vitest * import { Effect, TxHashMap } from "effect" * * const program = Effect.gen(function*() { @@ -1435,24 +1419,24 @@ export const map: { * (product) => product.category === "electronics" && product.stock > 0 * ) * - * const size = yield* TxHashMap.size(electronicsInStock) - * console.log(size) // 2 (laptop and mouse) + * yield* TxHashMap.size(electronicsInStock) // => 2 * * // Data-last usage with pipe * const expensiveItems = yield* inventory.pipe( * TxHashMap.filter((product) => product.price > 500) * ) * - * const expensiveSize = yield* TxHashMap.size(expensiveItems) - * console.log(expensiveSize) // 2 (laptop and phone) + * yield* TxHashMap.size(expensiveItems) // => 2 * * // Type guard usage - * const highValueItems = yield* TxHashMap.filter( + * return yield* TxHashMap.filter( * inventory, * (product): product is typeof product & { price: number } => * product.price > 50 * ) * }) + * + * await Effect.runPromise(program) * ``` * * @category combinators @@ -1492,7 +1476,7 @@ export const filter: { * * **Example** (Reducing entries) * - * ```ts + * ```ts import.meta.vitest * import { Effect, TxHashMap } from "effect" * * const program = Effect.gen(function*() { @@ -1508,12 +1492,9 @@ export const filter: { * const totalSales = yield* TxHashMap.reduce( * sales, * 0, - * (total, amount, quarter) => { - * console.log(`Adding ${quarter}: ${amount}`) - * return total + amount - * } + * (total, amount) => total + amount * ) - * console.log(`Total sales: ${totalSales}`) // 80000 + * totalSales // => 80000 * * // Data-last usage with pipe * const quarterlyReport = yield* sales.pipe( @@ -1526,16 +1507,10 @@ export const filter: { * }) * ) * ) - * console.log(quarterlyReport) // { quarters: 4, total: 80000, max: 25000 } - * - * // Build a summary string - * const summary = yield* TxHashMap.reduce( - * sales, - * "", - * (acc, amount, quarter) => acc + `${quarter}: $${amount.toLocaleString()}\n` - * ) - * console.log(summary) + * return quarterlyReport * }) + * + * await Effect.runPromise(program) // => { quarters: 4, total: 80000, max: 25000 } * ``` * * @category combinators @@ -1571,7 +1546,7 @@ export const reduce: { * * **Example** (Filtering and mapping entries) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Option, Result, TxHashMap } from "effect" * * const program = Effect.gen(function*() { @@ -1599,10 +1574,8 @@ export const reduce: { * ) * * const aliceData = yield* TxHashMap.get(activeAdminAges, "alice") - * console.log(aliceData) // Option.some({ username: "alice", age: 30, seniority: "senior" }) - * - * const charlieData = yield* TxHashMap.get(activeAdminAges, "charlie") - * console.log(charlieData) // Option.none() (not active) + * 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( @@ -1612,9 +1585,10 @@ export const reduce: { * }) * ) * - * const size = yield* TxHashMap.size(validAges) - * console.log(size) // 3 (alice, charlie, diana have valid ages) + * return yield* TxHashMap.size(validAges) * }) + * + * await Effect.runPromise(program) // => 3 * ``` * * @category combinators @@ -1646,7 +1620,7 @@ export const filterMap: { * * **Example** (Checking entries with a predicate) * - * ```ts + * ```ts import.meta.vitest * import { Effect, TxHashMap } from "effect" * * const program = Effect.gen(function*() { @@ -1659,28 +1633,27 @@ export const filterMap: { * ) * * // Check if any users are online - * const hasOnlineUsers = yield* TxHashMap.hasBy( + * yield* TxHashMap.hasBy( * userStatuses, * (user) => user.status === "online" - * ) - * console.log(hasOnlineUsers) // true + * ) // => true * * // Check if any users have specific username pattern - * const hasAdminUser = yield* TxHashMap.hasBy( + * yield* TxHashMap.hasBy( * userStatuses, * (user, username) => username.startsWith("admin") - * ) - * console.log(hasAdminUser) // false + * ) // => false * * // Data-last usage with pipe - * const hasRecentActivity = yield* userStatuses.pipe( + * return yield* userStatuses.pipe( * TxHashMap.hasBy((user) => currentTime - user.lastSeen < 1_800_000) // 30 minutes * ) - * console.log(hasRecentActivity) // true * }) + * + * await Effect.runPromise(program) // => true * ``` * - * @category combinators + * @category predicates * @since 4.0.0 */ export const hasBy: { @@ -1705,8 +1678,8 @@ export const hasBy: { * * **Example** (Finding the first matching entry) * - * ```ts - * import { Effect, TxHashMap } from "effect" + * ```ts import.meta.vitest + * import { Effect, Option, TxHashMap } from "effect" * * const program = Effect.gen(function*() { * // Create a task priority map @@ -1722,21 +1695,15 @@ export const hasBy: { * (task) => task.priority >= 2 && !task.completed * ) * - * if (highPriorityTask._tag === "Some") { - * const [taskId, task] = highPriorityTask.value - * console.log(`Found task: ${taskId}, priority: ${task.priority}`) - * // "Found task: task3, priority: 2" - * } + * highPriorityTask // => Option.some(["task3", { priority: 2, assignee: "alice", completed: false }]) * * // Find first task assigned to specific user - * const aliceTask = yield* tasks.pipe( + * return yield* tasks.pipe( * TxHashMap.findFirst((task) => task.assignee === "alice") * ) - * - * if (aliceTask._tag === "Some") { - * console.log(`Alice's task: ${aliceTask.value[0]}`) - * } * }) + * + * await Effect.runPromise(program) // => Option.some(["task1", { priority: 1, assignee: "alice", completed: false }]) * ``` * * @category combinators @@ -1764,7 +1731,7 @@ export const findFirst: { * * **Example** (Checking whether some entries match) * - * ```ts + * ```ts import.meta.vitest * import { Effect, TxHashMap } from "effect" * * const program = Effect.gen(function*() { @@ -1776,28 +1743,27 @@ export const findFirst: { * ) * * // Check if any products are expensive - * const hasExpensiveProducts = yield* TxHashMap.some( + * yield* TxHashMap.some( * inventory, * (product) => product.price > 500 - * ) - * console.log(hasExpensiveProducts) // true + * ) // => true * * // Check if any products are out of stock - * const hasOutOfStock = yield* TxHashMap.some( + * yield* TxHashMap.some( * inventory, * (product) => product.stock === 0 - * ) - * console.log(hasOutOfStock) // true + * ) // => true * * // Data-last usage with pipe - * const hasAffordableItems = yield* inventory.pipe( + * return yield* inventory.pipe( * TxHashMap.some((product) => product.price < 50) * ) - * console.log(hasAffordableItems) // true (mouse is $29) * }) + * + * await Effect.runPromise(program) // => true * ``` * - * @category combinators + * @category predicates * @since 4.0.0 */ export const some: { @@ -1821,7 +1787,7 @@ export const some: { * * **Example** (Checking whether every entry matches) * - * ```ts + * ```ts import.meta.vitest * import { Effect, TxHashMap } from "effect" * * const program = Effect.gen(function*() { @@ -1833,28 +1799,27 @@ export const some: { * ) * * // Check if all users can read - * const allCanRead = yield* TxHashMap.every( + * yield* TxHashMap.every( * permissions, * (perms) => perms.canRead - * ) - * console.log(allCanRead) // true + * ) // => true * * // Check if all users can write - * const allCanWrite = yield* TxHashMap.every( + * yield* TxHashMap.every( * permissions, * (perms) => perms.canWrite - * ) - * console.log(allCanWrite) // false + * ) // => false * * // Data-last usage with pipe - * const allHaveBasicAccess = yield* permissions.pipe( + * return yield* permissions.pipe( * TxHashMap.every((perms, username) => perms.canRead && username.length > 2) * ) - * console.log(allHaveBasicAccess) // true * }) + * + * await Effect.runPromise(program) // => true * ``` * - * @category combinators + * @category predicates * @since 4.0.0 */ export const every: { @@ -1879,8 +1844,8 @@ export const every: { * * **Example** (Running effects for each entry) * - * ```ts - * import { Console, Effect, TxHashMap } from "effect" + * ```ts import.meta.vitest + * import { Effect, TxHashMap } from "effect" * * const program = Effect.gen(function*() { * // Create a log processing map @@ -1890,26 +1855,17 @@ export const every: { * ["debug.log", { size: 512, level: "debug" }] * ) * - * // Process each log file with side effects + * const messages: Array = [] * yield* TxHashMap.forEach(logs, (logInfo, filename) => - * Effect.gen(function*() { - * yield* Console.log( - * `Processing ${filename}: ${logInfo.size} bytes, level: ${logInfo.level}` - * ) - * if (logInfo.level === "error") { - * yield* Console.log(`⚠️ Error log detected: ${filename}`) - * } + * Effect.sync(() => { + * messages.push(`${filename}: ${logInfo.size} bytes (${logInfo.level})`) * })) * - * // Data-last usage with pipe - * yield* logs.pipe( - * TxHashMap.forEach((logInfo) => - * logInfo.size > 1000 - * ? Console.log(`Large log file: ${logInfo.size} bytes`) - * : Effect.void - * ) - * ) + * 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 @@ -1946,8 +1902,8 @@ export const forEach: { * * **Example** (Flat mapping entries) * - * ```ts - * import { Effect, TxHashMap } from "effect" + * ```ts import.meta.vitest + * import { Effect, Option, TxHashMap } from "effect" * * const program = Effect.gen(function*() { * // Create a department-employee map @@ -1975,15 +1931,12 @@ export const forEach: { * ) * * // Check the flattened result - * const alice = yield* TxHashMap.get(employeeDetails, "alice") - * console.log(alice) // Option.some({ department: "engineering", role: "lead" }) - * - * const charlie = yield* TxHashMap.get(employeeDetails, "charlie") - * console.log(charlie) // Option.some({ department: "marketing", role: "lead" }) - * - * const size = yield* TxHashMap.size(employeeDetails) - * console.log(size) // 4 (all employees) + * 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 @@ -2028,7 +1981,7 @@ export const flatMap: { * * **Example** (Compacting optional values) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Option, TxHashMap } from "effect" * * const program = Effect.gen(function*() { @@ -2047,21 +2000,17 @@ export const flatMap: { * // Remove all None values and unwrap Some values * const validUsers = yield* TxHashMap.compact(userData) * - * const size = yield* TxHashMap.size(validUsers) - * console.log(size) // 3 (alice, charlie, eve) + * yield* TxHashMap.size(validUsers) // => 3 * - * const alice = yield* TxHashMap.get(validUsers, "alice") - * console.log(alice) // Option.some({ age: 30, email: "alice@example.com" }) - * - * const bob = yield* TxHashMap.get(validUsers, "bob") - * console.log(bob) // Option.none() (removed from map) + * 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) - * const ageEntries = yield* TxHashMap.entries(userAges) - * const sortedAgeEntries = ageEntries.toSorted(([left], [right]) => left.localeCompare(right)) - * console.log(sortedAgeEntries) // [["alice", 30], ["charlie", 25], ["eve", 28]] + * return (yield* TxHashMap.entries(userAges)).toSorted(([left], [right]) => left.localeCompare(right)) * }) + * + * await Effect.runPromise(program) // => [["alice", 30], ["charlie", 25], ["eve", 28]] * ``` * * @category combinators @@ -2082,7 +2031,7 @@ export const compact = ( * * **Example** (Converting to entries) * - * ```ts + * ```ts import.meta.vitest * import { Effect, TxHashMap } from "effect" * * const program = Effect.gen(function*() { @@ -2093,24 +2042,18 @@ export const compact = ( * ) * * // Get all entries as an array - * const allEntries = yield* TxHashMap.toEntries(settings) - * const sortedEntries = allEntries.toSorted(([left], [right]) => left.localeCompare(right)) - * console.log(sortedEntries) - * // [["language", "en-US"], ["theme", "dark"], ["timezone", "UTC"]] - * - * // Process entries - * for (const [setting, value] of sortedEntries) { - * console.log(`${setting}: ${value}`) - * } + * const sortedEntries = (yield* TxHashMap.toEntries(settings)) + * .toSorted(([left], [right]) => left.localeCompare(right)) + * sortedEntries // => [["language", "en-US"], ["theme", "dark"], ["timezone", "UTC"]] * - * // Convert to object for JSON serialization - * const settingsObj = Object.fromEntries(sortedEntries) - * console.log(JSON.stringify(settingsObj)) - * // {"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 combinators + * @category getters * @since 4.0.0 */ export const toEntries = ( @@ -2123,7 +2066,7 @@ export const toEntries = ( * * **Example** (Converting to values) * - * ```ts + * ```ts import.meta.vitest * import { Effect, TxHashMap } from "effect" * * const program = Effect.gen(function*() { @@ -2135,22 +2078,23 @@ export const toEntries = ( * * // Get all product information * const products = yield* TxHashMap.toValues(inventory) - * console.log(products.length) // 3 + * products.length // => 3 * * // Calculate total inventory value * const totalValue = products.reduce( * (sum, product) => sum + (product.price * product.stock), * 0 * ) - * console.log(`Total inventory value: $${totalValue}`) // Total inventory value: $8025 + * totalValue // => 8025 * * // Find products with low stock - * const lowStockProducts = products.filter((product) => product.stock < 10) - * console.log(`${lowStockProducts.length} product with low stock`) // 1 product with low stock + * return products.filter((product) => product.stock < 10).length * }) + * + * await Effect.runPromise(program) // => 1 * ``` * - * @category combinators + * @category getters * @since 4.0.0 */ export const toValues = (self: TxHashMap): Effect.Effect> => values(self) diff --git a/repos/effect/packages/effect/src/TxHashSet.ts b/repos/effect/packages/effect/src/TxHashSet.ts index 3e6e0e0fab..5ed9e793b4 100644 --- a/repos/effect/packages/effect/src/TxHashSet.ts +++ b/repos/effect/packages/effect/src/TxHashSet.ts @@ -55,7 +55,7 @@ const TxHashSetProto = { * * **Example** (Using transactional hash sets) * - * ```ts + * ```ts import.meta.vitest * import { Effect, TxHashSet } from "effect" * * const program = Effect.gen(function*() { @@ -64,8 +64,7 @@ const TxHashSetProto = { * * // Single operations are automatically transactional * yield* TxHashSet.add(txSet, "grape") - * const hasApple = yield* TxHashSet.has(txSet, "apple") - * console.log(hasApple) // true + * yield* TxHashSet.has(txSet, "apple") // => true * * // Multi-step atomic operations * yield* Effect.tx( @@ -78,9 +77,10 @@ const TxHashSetProto = { * }) * ) * - * const size = yield* TxHashSet.size(txSet) - * console.log(size) // 4 + * yield* TxHashSet.size(txSet) // => 4 * }) + * + * await Effect.runPromise(program) * ``` * * @category models @@ -97,7 +97,7 @@ export interface TxHashSet extends Inspectable, Pipeable { * * **Example** (Extracting value types inside transactions) * - * ```ts + * ```ts import.meta.vitest * import { Effect, TxHashSet } from "effect" * * const program = Effect.gen(function*() { @@ -111,7 +111,10 @@ export interface TxHashSet extends Inspectable, Pipeable { * 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 @@ -122,7 +125,7 @@ export declare namespace TxHashSet { * * **Example** (Extracting a TxHashSet value type) * - * ```ts + * ```ts import.meta.vitest * import type { TxHashSet } from "effect" * * type FruitSet = TxHashSet.TxHashSet<"apple" | "banana" | "cherry"> @@ -134,7 +137,7 @@ export declare namespace TxHashSet { * return `Processing ${fruit}` * } * - * console.log(processFruit("apple")) // Processing apple + * processFruit("apple") // => "Processing apple" * ``` * * @category utility types @@ -154,20 +157,22 @@ const makeTxHashSet = (ref: TxRef.TxRef>): TxHashSet => * * **Example** (Creating an empty transactional hash set) * - * ```ts + * ```ts import.meta.vitest * import { Effect, TxHashSet } from "effect" * * const program = Effect.gen(function*() { * const txSet = yield* TxHashSet.empty() * - * console.log(yield* TxHashSet.size(txSet)) // 0 - * console.log(yield* TxHashSet.isEmpty(txSet)) // true + * yield* TxHashSet.size(txSet) // => 0 + * yield* TxHashSet.isEmpty(txSet) // => true * * // Add some values * yield* TxHashSet.add(txSet, "hello") * yield* TxHashSet.add(txSet, "world") - * console.log(yield* TxHashSet.size(txSet)) // 2 + * yield* TxHashSet.size(txSet) // => 2 * }) + * + * await Effect.runPromise(program) * ``` * * @category constructors @@ -184,19 +189,21 @@ export const empty = (): Effect.Effect> => * * **Example** (Creating transactional hash sets from values) * - * ```ts + * ```ts import.meta.vitest * import { Effect, TxHashSet } from "effect" * * const program = Effect.gen(function*() { * const fruits = yield* TxHashSet.make("apple", "banana", "cherry") - * console.log(yield* TxHashSet.size(fruits)) // 3 + * yield* TxHashSet.size(fruits) // => 3 * * const numbers = yield* TxHashSet.make(1, 2, 3, 2, 1) // Duplicates ignored - * console.log(yield* TxHashSet.size(numbers)) // 3 + * yield* TxHashSet.size(numbers) // => 3 * * const mixed = yield* TxHashSet.make("hello", 42, true) - * console.log(yield* TxHashSet.size(mixed)) // 3 + * yield* TxHashSet.size(mixed) // => 3 * }) + * + * await Effect.runPromise(program) * ``` * * @category constructors @@ -216,20 +223,21 @@ export const make = >( * * **Example** (Creating a transactional hash set from an iterable) * - * ```ts + * ```ts import.meta.vitest * import { Effect, TxHashSet } from "effect" * * const program = Effect.gen(function*() { * const fromArray = yield* TxHashSet.fromIterable(["a", "b", "c", "b", "a"]) - * console.log(yield* TxHashSet.size(fromArray)) // 3 + * yield* TxHashSet.size(fromArray) // => 3 * * const fromSet = yield* TxHashSet.fromIterable(new Set([1, 2, 3])) - * console.log(yield* TxHashSet.size(fromSet)) // 3 + * yield* TxHashSet.size(fromSet) // => 3 * * const fromString = yield* TxHashSet.fromIterable("hello") - * const values = yield* TxHashSet.toHashSet(fromString) - * console.log(Array.from(values).sort()) // ["e", "h", "l", "o"] + * Array.from(yield* TxHashSet.toHashSet(fromString)).sort() // => ["e", "h", "l", "o"] * }) + * + * await Effect.runPromise(program) * ``` * * @category constructors @@ -247,21 +255,23 @@ export const fromIterable = (values: Iterable): Effect.Effect * * **Example** (Creating a transactional hash set from a HashSet) * - * ```ts + * ```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) * - * console.log(yield* TxHashSet.size(txSet)) // 3 - * console.log(yield* TxHashSet.has(txSet, "y")) // true + * yield* TxHashSet.size(txSet) // => 3 + * yield* TxHashSet.has(txSet, "y") // => true * * // Original hashSet is unchanged when txSet is modified * yield* TxHashSet.add(txSet, "w") - * console.log(HashSet.size(hashSet)) // 3 (original unchanged) - * console.log(yield* TxHashSet.size(txSet)) // 4 + * HashSet.size(hashSet) // => 3 + * yield* TxHashSet.size(txSet) // => 4 * }) + * + * await Effect.runPromise(program) * ``` * * @category constructors @@ -278,7 +288,7 @@ export const fromHashSet = (hashSet: HashSet.HashSet): Effect.Effect(hashSet: HashSet.HashSet): Effect.Effect true + * TxHashSet.isTxHashSet(hashSet) // => false + * TxHashSet.isTxHashSet(array) // => false + * TxHashSet.isTxHashSet(null) // => false * }) + * + * await Effect.runPromise(program) * ``` * * @category guards @@ -307,20 +319,22 @@ export const isTxHashSet = (u: unknown): u is TxHashSet => hasProperty( * * **Example** (Adding values) * - * ```ts + * ```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") - * console.log(yield* TxHashSet.size(txSet)) // 3 - * console.log(yield* TxHashSet.has(txSet, "c")) // true + * yield* TxHashSet.size(txSet) // => 3 + * yield* TxHashSet.has(txSet, "c") // => true * * // Adding existing value has no effect * yield* TxHashSet.add(txSet, "a") - * console.log(yield* TxHashSet.size(txSet)) // 3 (unchanged) + * yield* TxHashSet.size(txSet) // => 3 * }) + * + * await Effect.runPromise(program) * ``` * * @category mutations @@ -343,21 +357,21 @@ export const add: { * * **Example** (Removing values) * - * ```ts + * ```ts import.meta.vitest * import { Effect, TxHashSet } from "effect" * * const program = Effect.gen(function*() { * const txSet = yield* TxHashSet.make("a", "b", "c") * - * const removed = yield* TxHashSet.remove(txSet, "b") - * console.log(removed) // true (value existed and was removed) - * console.log(yield* TxHashSet.size(txSet)) // 2 - * console.log(yield* TxHashSet.has(txSet, "b")) // false + * yield* TxHashSet.remove(txSet, "b") // => true + * yield* TxHashSet.size(txSet) // => 2 + * yield* TxHashSet.has(txSet, "b") // => false * * // Removing non-existent value returns false - * const notRemoved = yield* TxHashSet.remove(txSet, "d") - * console.log(notRemoved) // false + * yield* TxHashSet.remove(txSet, "d") // => false * }) + * + * await Effect.runPromise(program) * ``` * * @category mutations @@ -384,14 +398,14 @@ export const remove: { * * **Example** (Checking membership) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Equal, Hash, TxHashSet } from "effect" * * const program = Effect.gen(function*() { * const txSet = yield* TxHashSet.make("apple", "banana", "cherry") * - * console.log(yield* TxHashSet.has(txSet, "apple")) // true - * console.log(yield* TxHashSet.has(txSet, "grape")) // false + * yield* TxHashSet.has(txSet, "apple") // => true + * yield* TxHashSet.has(txSet, "grape") // => false * * // Works with any type that implements Equal * class Person implements Equal.Equal { @@ -407,11 +421,13 @@ export const remove: { * } * * const people = yield* TxHashSet.make(new Person("Alice"), new Person("Bob")) - * console.log(yield* TxHashSet.has(people, new Person("Alice"))) // true + * yield* TxHashSet.has(people, new Person("Alice")) // => true * }) + * + * await Effect.runPromise(program) * ``` * - * @category elements + * @category predicates * @since 2.0.0 */ export const has: { @@ -431,19 +447,21 @@ export const has: { * * **Example** (Getting the set size) * - * ```ts + * ```ts import.meta.vitest * import { Effect, TxHashSet } from "effect" * * const program = Effect.gen(function*() { * const empty = yield* TxHashSet.empty() - * console.log(yield* TxHashSet.size(empty)) // 0 + * yield* TxHashSet.size(empty) // => 0 * * const small = yield* TxHashSet.make("a", "b") - * console.log(yield* TxHashSet.size(small)) // 2 + * yield* TxHashSet.size(small) // => 2 * * const fromIterable = yield* TxHashSet.fromIterable(["x", "y", "z", "x", "y"]) - * console.log(yield* TxHashSet.size(fromIterable)) // 3 (duplicates ignored) + * yield* TxHashSet.size(fromIterable) // => 3 * }) + * + * await Effect.runPromise(program) * ``` * * @category getters @@ -460,19 +478,21 @@ export const size = (self: TxHashSet): Effect.Effect => * * **Example** (Checking whether a set is empty) * - * ```ts + * ```ts import.meta.vitest * import { Effect, TxHashSet } from "effect" * * const program = Effect.gen(function*() { * const empty = yield* TxHashSet.empty() - * console.log(yield* TxHashSet.isEmpty(empty)) // true + * yield* TxHashSet.isEmpty(empty) // => true * * const nonEmpty = yield* TxHashSet.make("a") - * console.log(yield* TxHashSet.isEmpty(nonEmpty)) // false + * yield* TxHashSet.isEmpty(nonEmpty) // => false * }) + * + * await Effect.runPromise(program) * ``` * - * @category getters + * @category predicates * @since 2.0.0 */ export const isEmpty = (self: TxHashSet): Effect.Effect => @@ -490,17 +510,19 @@ export const isEmpty = (self: TxHashSet): Effect.Effect => * * **Example** (Clearing all values) * - * ```ts + * ```ts import.meta.vitest * import { Effect, TxHashSet } from "effect" * * const program = Effect.gen(function*() { * const txSet = yield* TxHashSet.make("a", "b", "c") - * console.log(yield* TxHashSet.size(txSet)) // 3 + * yield* TxHashSet.size(txSet) // => 3 * * yield* TxHashSet.clear(txSet) - * console.log(yield* TxHashSet.size(txSet)) // 0 - * console.log(yield* TxHashSet.isEmpty(txSet)) // true + * yield* TxHashSet.size(txSet) // => 0 + * yield* TxHashSet.isEmpty(txSet) // => true * }) + * + * await Effect.runPromise(program) * ``` * * @category mutations @@ -513,7 +535,7 @@ export const clear = (self: TxHashSet): Effect.Effect => TxRef.set(s * * **Example** (Combining sets with union) * - * ```ts + * ```ts import.meta.vitest * import { Effect, TxHashSet } from "effect" * * const program = Effect.gen(function*() { @@ -521,10 +543,11 @@ export const clear = (self: TxHashSet): Effect.Effect => TxRef.set(s * const set2 = yield* TxHashSet.make("b", "c") * const combined = yield* TxHashSet.union(set1, set2) * - * const values = yield* TxHashSet.toHashSet(combined) - * console.log(Array.from(values).sort()) // ["a", "b", "c"] - * console.log(yield* TxHashSet.size(combined)) // 3 + * Array.from(yield* TxHashSet.toHashSet(combined)).sort() // => ["a", "b", "c"] + * yield* TxHashSet.size(combined) // => 3 * }) + * + * await Effect.runPromise(program) * ``` * * @category combinators @@ -554,7 +577,7 @@ export const union: { * * **Example** (Finding common values) * - * ```ts + * ```ts import.meta.vitest * import { Effect, TxHashSet } from "effect" * * const program = Effect.gen(function*() { @@ -562,10 +585,11 @@ export const union: { * const set2 = yield* TxHashSet.make("b", "c", "d") * const common = yield* TxHashSet.intersection(set1, set2) * - * const values = yield* TxHashSet.toHashSet(common) - * console.log(Array.from(values).sort()) // ["b", "c"] - * console.log(yield* TxHashSet.size(common)) // 2 + * Array.from(yield* TxHashSet.toHashSet(common)).sort() // => ["b", "c"] + * yield* TxHashSet.size(common) // => 2 * }) + * + * await Effect.runPromise(program) * ``` * * @category combinators @@ -595,7 +619,7 @@ export const intersection: { * * **Example** (Finding values absent from another set) * - * ```ts + * ```ts import.meta.vitest * import { Effect, TxHashSet } from "effect" * * const program = Effect.gen(function*() { @@ -603,10 +627,11 @@ export const intersection: { * const set2 = yield* TxHashSet.make("b", "d") * const diff = yield* TxHashSet.difference(set1, set2) * - * const values = yield* TxHashSet.toHashSet(diff) - * console.log(Array.from(values).sort()) // ["a", "c"] - * console.log(yield* TxHashSet.size(diff)) // 2 + * Array.from(yield* TxHashSet.toHashSet(diff)).sort() // => ["a", "c"] + * yield* TxHashSet.size(diff) // => 2 * }) + * + * await Effect.runPromise(program) * ``` * * @category combinators @@ -636,7 +661,7 @@ export const difference: { * * **Example** (Checking subset relationships) * - * ```ts + * ```ts import.meta.vitest * import { Effect, TxHashSet } from "effect" * * const program = Effect.gen(function*() { @@ -644,14 +669,16 @@ export const difference: { * const large = yield* TxHashSet.make("a", "b", "c", "d") * const other = yield* TxHashSet.make("x", "y") * - * console.log(yield* TxHashSet.isSubset(small, large)) // true - * console.log(yield* TxHashSet.isSubset(large, small)) // false - * console.log(yield* TxHashSet.isSubset(small, other)) // false - * console.log(yield* TxHashSet.isSubset(small, small)) // true + * 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 elements + * @category predicates * @since 4.0.0 */ export const isSubset: { @@ -672,21 +699,23 @@ export const isSubset: { * * **Example** (Testing whether some values match) * - * ```ts + * ```ts import.meta.vitest * import { Effect, TxHashSet } from "effect" * * const program = Effect.gen(function*() { * const numbers = yield* TxHashSet.make(1, 2, 3, 4, 5) * - * console.log(yield* TxHashSet.some(numbers, (n) => n > 3)) // true - * console.log(yield* TxHashSet.some(numbers, (n) => n > 10)) // false + * yield* TxHashSet.some(numbers, (n) => n > 3) // => true + * yield* TxHashSet.some(numbers, (n) => n > 10) // => false * * const empty = yield* TxHashSet.empty() - * console.log(yield* TxHashSet.some(empty, (n) => n > 0)) // false + * yield* TxHashSet.some(empty, (n) => n > 0) // => false * }) + * + * await Effect.runPromise(program) * ``` * - * @category elements + * @category predicates * @since 4.0.0 */ export const some: { @@ -706,21 +735,23 @@ export const some: { * * **Example** (Testing whether every value matches) * - * ```ts + * ```ts import.meta.vitest * import { Effect, TxHashSet } from "effect" * * const program = Effect.gen(function*() { * const numbers = yield* TxHashSet.make(2, 4, 6, 8) * - * console.log(yield* TxHashSet.every(numbers, (n) => n % 2 === 0)) // true - * console.log(yield* TxHashSet.every(numbers, (n) => n > 5)) // false + * yield* TxHashSet.every(numbers, (n) => n % 2 === 0) // => true + * yield* TxHashSet.every(numbers, (n) => n > 5) // => false * * const empty = yield* TxHashSet.empty() - * console.log(yield* TxHashSet.every(empty, (n) => n > 0)) // true (vacuously true) + * yield* TxHashSet.every(empty, (n) => n > 0) // => true * }) + * + * await Effect.runPromise(program) * ``` * - * @category elements + * @category predicates * @since 4.0.0 */ export const every: { @@ -740,23 +771,23 @@ export const every: { * * **Example** (Mapping values) * - * ```ts + * ```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) * - * const values = yield* TxHashSet.toHashSet(doubled) - * console.log(Array.from(values).sort()) // [2, 4, 6] - * console.log(yield* TxHashSet.size(doubled)) // 3 + * 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) - * const lengthValues = yield* TxHashSet.toHashSet(lengths) - * console.log(Array.from(lengthValues).sort()) // [5, 6] (apple=5, banana=6, cherry=6) + * Array.from(yield* TxHashSet.toHashSet(lengths)).sort() // => [5, 6] * }) + * + * await Effect.runPromise(program) * ``` * * @category mapping @@ -780,17 +811,18 @@ export const map: { * * **Example** (Filtering values) * - * ```ts + * ```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) * - * const values = yield* TxHashSet.toHashSet(evens) - * console.log(Array.from(values).sort()) // [2, 4, 6] - * console.log(yield* TxHashSet.size(evens)) // 3 + * Array.from(yield* TxHashSet.toHashSet(evens)).sort() // => [2, 4, 6] + * yield* TxHashSet.size(evens) // => 3 * }) + * + * await Effect.runPromise(program) * ``` * * @category filtering @@ -836,19 +868,18 @@ export const filter: { * * **Example** (Reducing values) * - * ```ts + * ```ts import.meta.vitest * import { Effect, TxHashSet } from "effect" * * const program = Effect.gen(function*() { * const numbers = yield* TxHashSet.make(1, 2, 3, 4, 5) - * const sum = yield* TxHashSet.reduce(numbers, 0, (acc, n) => acc + n) - * - * console.log(sum) // 15 + * yield* TxHashSet.reduce(numbers, 0, (acc, n) => acc + n) // => 15 * * const strings = yield* TxHashSet.make("a", "b", "c") - * const concatenated = yield* TxHashSet.reduce(strings, "", (acc, s) => acc + s) - * console.log(concatenated) // Order may vary: "abc", "bac", etc. + * String(yield* TxHashSet.reduce(strings, "", (acc, s) => acc + s)).split("").sort().join("") // => "abc" * }) + * + * await Effect.runPromise(program) * ``` * * @category folding @@ -885,21 +916,23 @@ export const reduce: { * * **Example** (Taking a HashSet snapshot) * - * ```ts + * ```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) * - * console.log(HashSet.size(hashSet)) // 3 - * console.log(HashSet.has(hashSet, "y")) // true + * 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") - * console.log(HashSet.size(hashSet)) // 3 (unchanged) - * console.log(yield* TxHashSet.size(txSet)) // 4 + * HashSet.size(hashSet) // => 3 + * yield* TxHashSet.size(txSet) // => 4 * }) + * + * await Effect.runPromise(program) * ``` * * @category converting diff --git a/repos/effect/packages/effect/src/TxPriorityQueue.ts b/repos/effect/packages/effect/src/TxPriorityQueue.ts index 6f5b410ed4..179e7e096e 100644 --- a/repos/effect/packages/effect/src/TxPriorityQueue.ts +++ b/repos/effect/packages/effect/src/TxPriorityQueue.ts @@ -39,7 +39,7 @@ const TypeId = "~effect/transactions/TxPriorityQueue" * * **Example** (Dequeuing values by priority) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Order, TxPriorityQueue } from "effect" * * const program = Effect.gen(function*() { @@ -47,9 +47,10 @@ const TypeId = "~effect/transactions/TxPriorityQueue" * yield* TxPriorityQueue.offer(pq, 3) * yield* TxPriorityQueue.offer(pq, 1) * yield* TxPriorityQueue.offer(pq, 2) - * const first = yield* TxPriorityQueue.take(pq) - * console.log(first) // 1 + * return yield* TxPriorityQueue.take(pq) * }) + * + * await Effect.runPromise(program) // => 1 * ``` * * @category models @@ -107,14 +108,15 @@ const insertSorted = (chunk: Chunk, value: A, ord: Order): Chunk => * * **Example** (Creating an empty priority queue) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Order, TxPriorityQueue } from "effect" * * const program = Effect.gen(function*() { * const pq = yield* TxPriorityQueue.empty(Order.Number) - * const empty = yield* TxPriorityQueue.isEmpty(pq) - * console.log(empty) // true + * return yield* TxPriorityQueue.isEmpty(pq) * }) + * + * await Effect.runPromise(program) // => true * ``` * * @category constructors @@ -128,14 +130,15 @@ export const empty = (order: Order): Effect.Effect> => * * **Example** (Creating a priority queue from an iterable) * - * ```ts + * ```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]) - * const first = yield* TxPriorityQueue.take(pq) - * console.log(first) // 1 + * return yield* TxPriorityQueue.take(pq) * }) + * + * await Effect.runPromise(program) // => 1 * ``` * * @category constructors @@ -160,14 +163,15 @@ export const fromIterable: { * * **Example** (Creating a priority queue from variadic values) * - * ```ts + * ```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) - * const first = yield* TxPriorityQueue.take(pq) - * console.log(first) // 1 + * return yield* TxPriorityQueue.take(pq) * }) + * + * await Effect.runPromise(program) // => 1 * ``` * * @category constructors @@ -181,14 +185,15 @@ export const make = (order: Order) => (...elements: Array): Effect.Effe * * **Example** (Getting the queue size) * - * ```ts + * ```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]) - * const s = yield* TxPriorityQueue.size(pq) - * console.log(s) // 3 + * return yield* TxPriorityQueue.size(pq) * }) + * + * await Effect.runPromise(program) // => 3 * ``` * * @category getters @@ -201,17 +206,18 @@ export const size = (self: TxPriorityQueue): Effect.Effect => Effe * * **Example** (Checking whether a queue is empty) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Order, TxPriorityQueue } from "effect" * * const program = Effect.gen(function*() { * const pq = yield* TxPriorityQueue.empty(Order.Number) - * const empty = yield* TxPriorityQueue.isEmpty(pq) - * console.log(empty) // true + * return yield* TxPriorityQueue.isEmpty(pq) * }) + * + * await Effect.runPromise(program) // => true * ``` * - * @category getters + * @category predicates * @since 2.0.0 */ export const isEmpty = (self: TxPriorityQueue): Effect.Effect => Effect.map(size(self), (n) => n === 0) @@ -221,17 +227,18 @@ export const isEmpty = (self: TxPriorityQueue): Effect.Effect => * * **Example** (Checking whether a queue has elements) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Order, TxPriorityQueue } from "effect" * * const program = Effect.gen(function*() { * const pq = yield* TxPriorityQueue.fromIterable(Order.Number, [1]) - * const nonEmpty = yield* TxPriorityQueue.isNonEmpty(pq) - * console.log(nonEmpty) // true + * return yield* TxPriorityQueue.isNonEmpty(pq) * }) + * + * await Effect.runPromise(program) // => true * ``` * - * @category getters + * @category predicates * @since 2.0.0 */ export const isNonEmpty = (self: TxPriorityQueue): Effect.Effect => Effect.map(size(self), (n) => n > 0) @@ -246,14 +253,15 @@ export const isNonEmpty = (self: TxPriorityQueue): Effect.Effect * * **Example** (Peeking at the next value) * - * ```ts + * ```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]) - * const top = yield* TxPriorityQueue.peek(pq) - * console.log(top) // 1 + * return yield* TxPriorityQueue.peek(pq) * }) + * + * await Effect.runPromise(program) // => 1 * ``` * * @category getters @@ -279,14 +287,15 @@ export const peek = (self: TxPriorityQueue): Effect.Effect => * * **Example** (Peeking without retrying) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Option, Order, TxPriorityQueue } from "effect" * * const program = Effect.gen(function*() { * const pq = yield* TxPriorityQueue.empty(Order.Number) - * const result = yield* TxPriorityQueue.peekOption(pq) - * console.log(Option.isNone(result)) // true + * return yield* TxPriorityQueue.peekOption(pq) * }) + * + * await Effect.runPromise(program) // => Option.none() * ``` * * @category getters @@ -300,16 +309,17 @@ export const peekOption = (self: TxPriorityQueue): Effect.Effect * * **Example** (Offering a value) * - * ```ts + * ```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) - * const first = yield* TxPriorityQueue.take(pq) - * console.log(first) // 1 + * return yield* TxPriorityQueue.take(pq) * }) + * + * await Effect.runPromise(program) // => 1 * ``` * * @category mutations @@ -329,15 +339,16 @@ export const offer: { * * **Example** (Offering multiple values) * - * ```ts + * ```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]) - * const first = yield* TxPriorityQueue.take(pq) - * console.log(first) // 1 + * return yield* TxPriorityQueue.take(pq) * }) + * + * await Effect.runPromise(program) // => 1 * ``` * * @category mutations @@ -360,14 +371,15 @@ export const offerAll: { * * **Example** (Taking the next value) * - * ```ts + * ```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]) - * const first = yield* TxPriorityQueue.take(pq) - * console.log(first) // 1 + * return yield* TxPriorityQueue.take(pq) * }) + * + * await Effect.runPromise(program) // => 1 * ``` * * @category mutations @@ -389,14 +401,15 @@ export const take = (self: TxPriorityQueue): Effect.Effect => * * **Example** (Taking all values in priority order) * - * ```ts + * ```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]) - * const all = yield* TxPriorityQueue.takeAll(pq) - * console.log(all) // [1, 2, 3] + * return yield* TxPriorityQueue.takeAll(pq) * }) + * + * await Effect.runPromise(program) // => [1, 2, 3] * ``` * * @category mutations @@ -413,14 +426,15 @@ export const takeAll = (self: TxPriorityQueue): Effect.Effect> => * * **Example** (Taking without retrying) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Option, Order, TxPriorityQueue } from "effect" * * const program = Effect.gen(function*() { * const pq = yield* TxPriorityQueue.empty(Order.Number) - * const result = yield* TxPriorityQueue.takeOption(pq) - * console.log(Option.isNone(result)) // true + * return yield* TxPriorityQueue.takeOption(pq) * }) + * + * await Effect.runPromise(program) // => Option.none() * ``` * * @category mutations @@ -440,14 +454,15 @@ export const takeOption = (self: TxPriorityQueue): Effect.Effect * * **Example** (Taking up to a limit) * - * ```ts + * ```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]) - * const top2 = yield* TxPriorityQueue.takeUpTo(pq, 2) - * console.log(top2) // [1, 2] + * return yield* TxPriorityQueue.takeUpTo(pq, 2) * }) + * + * await Effect.runPromise(program) // => [1, 2] * ``` * * @category mutations @@ -474,15 +489,16 @@ export const takeUpTo: { * * **Example** (Removing matching values) * - * ```ts + * ```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) - * const all = yield* TxPriorityQueue.takeAll(pq) - * console.log(all) // [1, 3, 5] + * return yield* TxPriorityQueue.takeAll(pq) * }) + * + * await Effect.runPromise(program) // => [1, 3, 5] * ``` * * @category filtering @@ -502,15 +518,16 @@ export const removeIf: { * * **Example** (Retaining matching values) * - * ```ts + * ```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) - * const all = yield* TxPriorityQueue.takeAll(pq) - * console.log(all) // [2, 4] + * return yield* TxPriorityQueue.takeAll(pq) * }) + * + * await Effect.runPromise(program) // => [2, 4] * ``` * * @category filtering @@ -530,14 +547,15 @@ export const retainIf: { * * **Example** (Reading values in priority order) * - * ```ts + * ```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]) - * const all = yield* TxPriorityQueue.toArray(pq) - * console.log(all) // [1, 2, 3] + * return yield* TxPriorityQueue.toArray(pq) * }) + * + * await Effect.runPromise(program) // => [1, 2, 3] * ``` * * @category converting @@ -551,14 +569,15 @@ export const toArray = (self: TxPriorityQueue): Effect.Effect> => * * **Example** (Checking for a TxPriorityQueue) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Order, TxPriorityQueue } from "effect" * * const program = Effect.gen(function*() { * const pq = yield* TxPriorityQueue.empty(Order.Number) - * console.log(TxPriorityQueue.isTxPriorityQueue(pq)) // true - * console.log(TxPriorityQueue.isTxPriorityQueue("nope")) // false + * return [TxPriorityQueue.isTxPriorityQueue(pq), TxPriorityQueue.isTxPriorityQueue("nope")] * }) + * + * await Effect.runPromise(program) // => [true, false] * ``` * * @category guards diff --git a/repos/effect/packages/effect/src/TxPubSub.ts b/repos/effect/packages/effect/src/TxPubSub.ts index e1ecbc9ea3..697bc59adb 100644 --- a/repos/effect/packages/effect/src/TxPubSub.ts +++ b/repos/effect/packages/effect/src/TxPubSub.ts @@ -9,6 +9,7 @@ * * @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" @@ -28,21 +29,22 @@ const TypeId = "~effect/transactions/TxPubSub" * * **Example** (Subscribing to a transactional pub/sub) * - * ```ts + * ```ts import.meta.vitest * import { Effect, TxPubSub, TxQueue } from "effect" * * const program = Effect.gen(function*() { * const hub = yield* TxPubSub.unbounded() * - * yield* Effect.scoped( + * return yield* Effect.scoped( * Effect.gen(function*() { * const sub = yield* TxPubSub.subscribe(hub) * yield* TxPubSub.publish(hub, "hello") - * const msg = yield* TxQueue.take(sub) - * console.log(msg) // "hello" + * return yield* TxQueue.take(sub) * }) * ) * }) + * + * await Effect.runPromise(program) // => "hello" * ``` * * @category models @@ -102,21 +104,22 @@ const makeTxPubSub = ( * * **Example** (Creating a bounded pub/sub) * - * ```ts + * ```ts import.meta.vitest * import { Effect, TxPubSub, TxQueue } from "effect" * * const program = Effect.gen(function*() { * const hub = yield* TxPubSub.bounded(16) * - * yield* Effect.scoped( + * return yield* Effect.scoped( * Effect.gen(function*() { * const sub = yield* TxPubSub.subscribe(hub) * yield* TxPubSub.publish(hub, 42) - * const value = yield* TxQueue.take(sub) - * console.log(value) // 42 + * return yield* TxQueue.take(sub) * }) * ) * }) + * + * await Effect.runPromise(program) // => 42 * ``` * * @category constructors @@ -135,13 +138,13 @@ export const bounded = (capacity: number): Effect.Effect> * * **Example** (Creating a dropping pub/sub) * - * ```ts + * ```ts import.meta.vitest * import { Effect, TxPubSub, TxQueue } from "effect" * * const program = Effect.gen(function*() { * const hub = yield* TxPubSub.dropping(2) * - * yield* Effect.scoped( + * return yield* Effect.scoped( * Effect.gen(function*() { * const sub = yield* TxPubSub.subscribe(hub) * yield* TxPubSub.publish(hub, 1) @@ -149,10 +152,12 @@ export const bounded = (capacity: number): Effect.Effect> * yield* TxPubSub.publish(hub, 3) // dropped * const v1 = yield* TxQueue.take(sub) * const v2 = yield* TxQueue.take(sub) - * console.log(v1, v2) // 1 2 + * return [v1, v2] * }) * ) * }) + * + * await Effect.runPromise(program) // => [1, 2] * ``` * * @category constructors @@ -171,23 +176,24 @@ export const dropping = (capacity: number): Effect.Effect * * **Example** (Creating a sliding pub/sub) * - * ```ts + * ```ts import.meta.vitest * import { Effect, TxPubSub, TxQueue } from "effect" * * const program = Effect.gen(function*() { * const hub = yield* TxPubSub.sliding(2) * - * yield* Effect.scoped( + * 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 - * const v1 = yield* TxQueue.take(sub) - * console.log(v1) // 2 + * return yield* TxQueue.take(sub) * }) * ) * }) + * + * await Effect.runPromise(program) // => 2 * ``` * * @category constructors @@ -205,21 +211,22 @@ export const sliding = (capacity: number): Effect.Effect> * * **Example** (Creating an unbounded pub/sub) * - * ```ts + * ```ts import.meta.vitest * import { Effect, TxPubSub, TxQueue } from "effect" * * const program = Effect.gen(function*() { * const hub = yield* TxPubSub.unbounded() * - * yield* Effect.scoped( + * return yield* Effect.scoped( * Effect.gen(function*() { * const sub = yield* TxPubSub.subscribe(hub) * yield* TxPubSub.publish(hub, "msg") - * const msg = yield* TxQueue.take(sub) - * console.log(msg) // "msg" + * return yield* TxQueue.take(sub) * }) * ) * }) + * + * await Effect.runPromise(program) // => "msg" * ``` * * @category constructors @@ -241,13 +248,15 @@ export const unbounded = (): Effect.Effect> => * * **Example** (Reading pub/sub capacity) * - * ```ts + * ```ts import.meta.vitest * import { Effect, TxPubSub } from "effect" * * const program = Effect.gen(function*() { * const hub = yield* TxPubSub.bounded(16) - * console.log(TxPubSub.capacity(hub)) // 16 + * return TxPubSub.capacity(hub) * }) + * + * await Effect.runPromise(program) // => 16 * ``` * * @category getters @@ -260,22 +269,23 @@ export const capacity = (self: TxPubSub): number => self.capacity * * **Example** (Reading subscriber queue size) * - * ```ts + * ```ts import.meta.vitest * import { Effect, TxPubSub, TxQueue } from "effect" * * const program = Effect.gen(function*() { * const hub = yield* TxPubSub.unbounded() * - * yield* Effect.scoped( + * return yield* Effect.scoped( * Effect.gen(function*() { * const sub = yield* TxPubSub.subscribe(hub) * yield* TxPubSub.publish(hub, 1) * yield* TxPubSub.publish(hub, 2) - * const s = yield* TxPubSub.size(hub) - * console.log(s) // 2 + * return yield* TxPubSub.size(hub) * }) * ) * }) + * + * await Effect.runPromise(program) // => 2 * ``` * * @category getters @@ -297,17 +307,18 @@ export const size = (self: TxPubSub): Effect.Effect => * * **Example** (Checking whether a pub/sub is empty) * - * ```ts + * ```ts import.meta.vitest * import { Effect, TxPubSub } from "effect" * * const program = Effect.gen(function*() { * const hub = yield* TxPubSub.unbounded() - * const empty = yield* TxPubSub.isEmpty(hub) - * console.log(empty) // true + * return yield* TxPubSub.isEmpty(hub) * }) + * + * await Effect.runPromise(program) // => true * ``` * - * @category getters + * @category predicates * @since 2.0.0 */ export const isEmpty = (self: TxPubSub): Effect.Effect => Effect.map(size(self), (s) => s === 0) @@ -317,17 +328,18 @@ export const isEmpty = (self: TxPubSub): Effect.Effect => Effect. * * **Example** (Checking whether a pub/sub is full) * - * ```ts + * ```ts import.meta.vitest * import { Effect, TxPubSub } from "effect" * * const program = Effect.gen(function*() { * const hub = yield* TxPubSub.bounded(2) - * const full = yield* TxPubSub.isFull(hub) - * console.log(full) // false + * return yield* TxPubSub.isFull(hub) * }) + * + * await Effect.runPromise(program) // => false * ``` * - * @category getters + * @category predicates * @since 2.0.0 */ export const isFull = (self: TxPubSub): Effect.Effect => @@ -345,18 +357,20 @@ export const isFull = (self: TxPubSub): Effect.Effect => * * **Example** (Checking whether a pub/sub is shut down) * - * ```ts + * ```ts import.meta.vitest * import { Effect, TxPubSub } from "effect" * * const program = Effect.gen(function*() { * const hub = yield* TxPubSub.unbounded() - * console.log(yield* TxPubSub.isShutdown(hub)) // false + * const before = yield* TxPubSub.isShutdown(hub) * yield* TxPubSub.shutdown(hub) - * console.log(yield* TxPubSub.isShutdown(hub)) // true + * return [before, yield* TxPubSub.isShutdown(hub)] * }) + * + * await Effect.runPromise(program) // => [false, true] * ``` * - * @category getters + * @category predicates * @since 2.0.0 */ export const isShutdown = (self: TxPubSub): Effect.Effect => TxRef.get(self.shutdownRef) @@ -374,7 +388,7 @@ export const isShutdown = (self: TxPubSub): Effect.Effect => TxRe * * **Example** (Publishing a message to subscribers) * - * ```ts + * ```ts import.meta.vitest * import { Effect, TxPubSub, TxQueue } from "effect" * * const program = Effect.gen(function*() { @@ -382,17 +396,18 @@ export const isShutdown = (self: TxPubSub): Effect.Effect => TxRe * * // No subscribers - publish is a no-op * const r1 = yield* TxPubSub.publish(hub, "no one listening") - * console.log(r1) // true * - * yield* Effect.scoped( + * const msg = yield* Effect.scoped( * Effect.gen(function*() { * const sub = yield* TxPubSub.subscribe(hub) * yield* TxPubSub.publish(hub, "hello") - * const msg = yield* TxQueue.take(sub) - * console.log(msg) // "hello" + * return yield* TxQueue.take(sub) * }) * ) + * return [r1, msg] * }) + * + * await Effect.runPromise(program) // => [true, "hello"] * ``` * * @category mutations @@ -428,23 +443,25 @@ export const publish: { * * **Example** (Publishing multiple messages to subscribers) * - * ```ts + * ```ts import.meta.vitest * import { Effect, TxPubSub, TxQueue } from "effect" * * const program = Effect.gen(function*() { * const hub = yield* TxPubSub.unbounded() * - * yield* Effect.scoped( + * 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) - * console.log(v1, v2, v3) // 1 2 3 + * return [v1, v2, v3] * }) * ) * }) + * + * await Effect.runPromise(program) // => [1, 2, 3] * ``` * * @category mutations @@ -455,17 +472,19 @@ export const publishAll: { (self: TxPubSub, values: Iterable): Effect.Effect } = dual( 2, - (self: TxPubSub, values: Iterable): Effect.Effect => - Effect.gen(function*() { + (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 values) { + for (const value of valuesArray) { const accepted = yield* publish(self, value) if (!accepted) allAccepted = false } return allAccepted }).pipe(Effect.tx) + } ) /** @@ -477,13 +496,13 @@ export const publishAll: { * * **Example** (Subscribing multiple queues) * - * ```ts + * ```ts import.meta.vitest * import { Effect, TxPubSub, TxQueue } from "effect" * * const program = Effect.gen(function*() { * const hub = yield* TxPubSub.unbounded() * - * yield* Effect.scoped( + * return yield* Effect.scoped( * Effect.gen(function*() { * const sub1 = yield* TxPubSub.subscribe(hub) * const sub2 = yield* TxPubSub.subscribe(hub) @@ -492,10 +511,12 @@ export const publishAll: { * * const msg1 = yield* TxQueue.take(sub1) * const msg2 = yield* TxQueue.take(sub2) - * console.log(msg1, msg2) // "broadcast" "broadcast" + * return [msg1, msg2] * }) * ) * }) + * + * await Effect.runPromise(program) // => ["broadcast", "broadcast"] * ``` * * @category mutations @@ -602,7 +623,7 @@ const makeSubscriberQueue = ( * * **Example** (Shutting down a pub/sub) * - * ```ts + * ```ts import.meta.vitest * import { Effect, TxPubSub } from "effect" * * const program = Effect.gen(function*() { @@ -610,11 +631,11 @@ const makeSubscriberQueue = ( * yield* TxPubSub.shutdown(hub) * * const shut = yield* TxPubSub.isShutdown(hub) - * console.log(shut) // true - * * const accepted = yield* TxPubSub.publish(hub, 1) - * console.log(accepted) // false + * return [shut, accepted] * }) + * + * await Effect.runPromise(program) // => [true, false] * ``` * * @category mutations @@ -637,16 +658,19 @@ export const shutdown = (self: TxPubSub): Effect.Effect => * * **Example** (Waiting for shutdown) * - * ```ts - * import { Effect, TxPubSub } from "effect" + * ```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 + * yield* Fiber.await(fiber) + * return yield* TxPubSub.isShutdown(hub) * }) + * + * await Effect.runPromise(program) // => true * ``` * * @category mutations @@ -668,14 +692,11 @@ export const awaitShutdown = (self: TxPubSub): Effect.Effect => * * **Example** (Checking for a TxPubSub) * - * ```ts + * ```ts import.meta.vitest * import { TxPubSub } from "effect" * - * declare const someValue: unknown - * - * if (TxPubSub.isTxPubSub(someValue)) { - * console.log("This is a TxPubSub") - * } + * const someValue: unknown = {} + * TxPubSub.isTxPubSub(someValue) // => false * ``` * * @category guards diff --git a/repos/effect/packages/effect/src/TxQueue.ts b/repos/effect/packages/effect/src/TxQueue.ts index 5e9f7cd1fc..e4e7f0c800 100644 --- a/repos/effect/packages/effect/src/TxQueue.ts +++ b/repos/effect/packages/effect/src/TxQueue.ts @@ -36,19 +36,11 @@ import type * as Types from "./Types.ts" * * **Example** (Inspecting queue lifecycle states) * - * ```ts + * ```ts import.meta.vitest * import type { TxQueue } from "effect" * - * // State progression example - * declare const state: TxQueue.State - * - * if (state._tag === "Open") { - * console.log("Queue is accepting new items") - * } else if (state._tag === "Closing") { - * console.log("Queue is draining, cause:", state.cause) - * } else { - * console.log("Queue is done, cause:", state.cause) - * } + * const state: TxQueue.State = { _tag: "Open" } + * state._tag // => "Open" * ``` * * @category models @@ -146,7 +138,7 @@ export interface TxQueueState extends Inspectable { * * **Example** (Offering values through enqueue handles) * - * ```ts + * ```ts import.meta.vitest * import { Effect, TxQueue } from "effect" * import type { Cause } from "effect" * @@ -167,7 +159,11 @@ export interface TxQueueState extends Inspectable { * >(5) * yield* TxQueue.offer(completableQueue, "task") * yield* TxQueue.end(completableQueue) + * + * return accepted * }) + * + * await Effect.runPromise(program) // => true * ``` * * @category models @@ -183,7 +179,7 @@ export interface TxEnqueue extends TxQueueState { * * **Example** (Taking values through dequeue handles) * - * ```ts + * ```ts import.meta.vitest * import { Effect, TxQueue } from "effect" * * const program = Effect.gen(function*() { @@ -191,7 +187,6 @@ export interface TxEnqueue extends TxQueueState { * const queue = yield* TxQueue.bounded(10) * yield* TxQueue.offer(queue, 42) * const item = yield* TxQueue.take(queue) - * console.log(item) // 42 * * // Queue with error channel - errors propagate through E-channel * const faultTolerantQueue = yield* TxQueue.bounded(10) @@ -200,7 +195,10 @@ export interface TxEnqueue extends TxQueueState { * // 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 @@ -216,7 +214,7 @@ export interface TxDequeue extends TxQueueState { * * **Example** (Combining enqueue and dequeue operations) * - * ```ts + * ```ts import.meta.vitest * import { Effect, TxQueue } from "effect" * * const program = Effect.gen(function*() { @@ -226,7 +224,6 @@ export interface TxDequeue extends TxQueueState { * // Single operations - automatically transactional * const accepted = yield* TxQueue.offer(queue, 42) * const item = yield* TxQueue.take(queue) // Effect - * console.log(item) // 42 * * // Queue with error channel * const faultTolerantQueue = yield* TxQueue.bounded(10) @@ -234,8 +231,10 @@ export interface TxDequeue extends TxQueueState { * // Operations can handle queue-level failures * yield* TxQueue.fail(faultTolerantQueue, "queue failed") * const result = yield* Effect.flip(TxQueue.take(faultTolerantQueue)) - * console.log(result) // "queue failed" + * return [accepted, item, result] as const * }) + * + * await Effect.runPromise(program) // => [true, 42, "queue failed"] * ``` * * @category models @@ -250,15 +249,11 @@ export interface TxQueue extends TxEnqueue, Tx * * **Example** (Checking enqueue handles) * - * ```ts + * ```ts import.meta.vitest * import { TxQueue } from "effect" * - * declare const someValue: unknown - * - * if (TxQueue.isTxEnqueue(someValue)) { - * // someValue is now typed as TxEnqueue - * console.log("This is a TxEnqueue") - * } + * const someValue: unknown = {} + * TxQueue.isTxEnqueue(someValue) // => false * ``` * * @category guards @@ -271,15 +266,11 @@ export const isTxEnqueue = (u: unknown): u is TxEnqueu * * **Example** (Checking dequeue handles) * - * ```ts + * ```ts import.meta.vitest * import { TxQueue } from "effect" * - * declare const someValue: unknown - * - * if (TxQueue.isTxDequeue(someValue)) { - * // someValue is now typed as TxDequeue - * console.log("This is a TxDequeue") - * } + * const someValue: unknown = {} + * TxQueue.isTxDequeue(someValue) // => false * ``` * * @category guards @@ -292,15 +283,11 @@ export const isTxDequeue = (u: unknown): u is TxDequeu * * **Example** (Checking queue handles) * - * ```ts + * ```ts import.meta.vitest * import { TxQueue } from "effect" * - * declare const someValue: unknown - * - * if (TxQueue.isTxQueue(someValue)) { - * // someValue is now typed as TxQueue - * console.log("This is a TxQueue") - * } + * const someValue: unknown = {} + * TxQueue.isTxQueue(someValue) // => false * ``` * * @category guards @@ -344,7 +331,7 @@ const TxQueueProto = { * * **Example** (Creating bounded queues) * - * ```ts + * ```ts import.meta.vitest * import { Effect, TxQueue } from "effect" * * const program = Effect.gen(function*() { @@ -358,9 +345,10 @@ const TxQueueProto = { * yield* TxQueue.offer(queue, 1) * yield* TxQueue.offer(queue, 2) * - * const item = yield* TxQueue.take(queue) - * console.log(item) // 1 + * return yield* TxQueue.take(queue) * }) + * + * await Effect.runPromise(program) // => 1 * ``` * * @category constructors @@ -390,7 +378,7 @@ export const bounded = ( * * **Example** (Creating unbounded queues) * - * ```ts + * ```ts import.meta.vitest * import { Effect, TxQueue } from "effect" * * const program = Effect.gen(function*() { @@ -404,9 +392,10 @@ export const bounded = ( * yield* TxQueue.offer(queue, "hello") * yield* TxQueue.offer(queue, "world") * - * const size = yield* TxQueue.size(queue) - * console.log(size) // 2 + * return yield* TxQueue.size(queue) * }) + * + * await Effect.runPromise(program) // => 2 * ``` * * @category constructors @@ -434,7 +423,7 @@ export const unbounded = (): Effect.Effect> * * **Example** (Creating dropping queues) * - * ```ts + * ```ts import.meta.vitest * import { Effect, TxQueue } from "effect" * * const program = Effect.gen(function*() { @@ -446,9 +435,10 @@ export const unbounded = (): Effect.Effect> * yield* TxQueue.offer(queue, 2) * * // This will be dropped (returns false) - * const accepted = yield* TxQueue.offer(queue, 3) - * console.log(accepted) // false + * return yield* TxQueue.offer(queue, 3) * }) + * + * await Effect.runPromise(program) // => false * ``` * * @category constructors @@ -478,7 +468,7 @@ export const dropping = ( * * **Example** (Creating sliding queues) * - * ```ts + * ```ts import.meta.vitest * import { Effect, TxQueue } from "effect" * * const program = Effect.gen(function*() { @@ -492,9 +482,10 @@ export const dropping = ( * // This will evict item 1 and add 3 * yield* TxQueue.offer(queue, 3) * - * const item = yield* TxQueue.take(queue) - * console.log(item) // 2 (item 1 was evicted) + * return yield* TxQueue.take(queue) * }) + * + * await Effect.runPromise(program) // => 2 * ``` * * @category constructors @@ -528,16 +519,17 @@ export const sliding = ( * * **Example** (Offering a value) * - * ```ts + * ```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 - * const accepted = yield* TxQueue.offer(queue, 42) - * console.log(accepted) // true + * return yield* TxQueue.offer(queue, 42) * }) + * + * await Effect.runPromise(program) // => true * ``` * * @category combinators @@ -595,17 +587,17 @@ export const offer: { * * **Example** (Offering multiple values) * - * ```ts + * ```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 - * const rejected = yield* TxQueue.offerAll(queue, [1, 2, 3, 4, 5]) - * console.log(rejected) // [] if all accepted - * console.log(rejected.length) // 0 + * return yield* TxQueue.offerAll(queue, [1, 2, 3, 4, 5]) * }) + * + * await Effect.runPromise(program) // => [] * ``` * * @category combinators @@ -616,11 +608,13 @@ export const offerAll: { (self: TxEnqueue, values: Iterable): Effect.Effect> } = dual( 2, - (self: TxEnqueue, values: Iterable): Effect.Effect> => - Effect.gen(function*() { + (self: TxEnqueue, values: Iterable): Effect.Effect> => { + const valuesArray = Array.from(values) + + return Effect.gen(function*() { const rejected: Array = [] - for (const value of values) { + for (const value of valuesArray) { const accepted = yield* offer(self, value) if (!accepted) { rejected.push(value) @@ -629,6 +623,7 @@ export const offerAll: { return rejected }).pipe(Effect.tx) + } ) /** @@ -641,8 +636,8 @@ export const offerAll: { * * **Example** (Taking a value) * - * ```ts - * import { Effect, TxQueue } from "effect" + * ```ts import.meta.vitest + * import { Effect, Exit, TxQueue } from "effect" * * const program = Effect.gen(function*() { * const queue = yield* TxQueue.bounded(10) @@ -650,13 +645,14 @@ export const offerAll: { * * // Take an item - blocks if empty * const item = yield* TxQueue.take(queue) - * console.log(item) // 42 * * // When queue fails, take fails with the same error * yield* TxQueue.fail(queue, "queue error") - * const result = yield* Effect.flip(TxQueue.take(queue)) - * console.log(result) // "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 @@ -698,7 +694,7 @@ export const take = (self: TxDequeue): Effect.Effect => * * **Example** (Polling without blocking) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Option, TxQueue } from "effect" * * const program = Effect.gen(function*() { @@ -706,12 +702,13 @@ export const take = (self: TxDequeue): Effect.Effect => * * // Poll returns Option.none if empty * const maybe = yield* TxQueue.poll(queue) - * console.log(Option.isNone(maybe)) // true * * yield* TxQueue.offer(queue, 42) * const item = yield* TxQueue.poll(queue) - * console.log(Option.getOrNull(item)) // 42 + * return [maybe, item] as const * }) + * + * await Effect.runPromise(program) // => [Option.none(), Option.some(42)] * ``` * * @category combinators @@ -731,6 +728,11 @@ export const poll = (self: TxDequeue): Effect.Effect(self: TxDequeue): Effect.Effect(10) * yield* TxQueue.offerAll(queue, [1, 2, 3, 4, 5]) * * // Take all items atomically - returns NonEmptyArray - * const items = yield* TxQueue.takeAll(queue) - * console.log(items) // [1, 2, 3, 4, 5] - * console.log(Array.isArrayNonEmpty(items)) // true + * return yield* TxQueue.takeAll(queue) * }) * * // Error propagation example @@ -763,9 +763,11 @@ export const poll = (self: TxDequeue): Effect.Effect [1, 2, 3, 4, 5] + * await Effect.runPromise(errorExample) // => Exit.fail("processing error") * ``` * * @category combinators @@ -808,7 +810,7 @@ export const takeAll = (self: TxDequeue): Effect.Effect(self: TxDequeue): Effect.Effect [[1, 2, 3, 4], [5, 6, 7, 8, 9]] * ``` * * @category combinators @@ -898,7 +901,7 @@ export const takeN: { * * **Example** (Taking batches within bounds) * - * ```ts + * ```ts import.meta.vitest * import { Effect, TxQueue } from "effect" * * const program = Effect.gen(function*() { @@ -907,15 +910,16 @@ export const takeN: { * * // Take between 2 and 5 items * const batch1 = yield* TxQueue.takeBetween(queue, 2, 5) - * console.log(batch1) // [1, 2, 3, 4, 5] - took 5 (up to max) * * // Take between 1 and 10 items (but only 3 remain) * const batch2 = yield* TxQueue.takeBetween(queue, 1, 10) - * console.log(batch2) // [6, 7, 8] - took 3 (all remaining) * * // 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 @@ -986,8 +990,8 @@ export const takeBetween: { * * **Example** (Peeking without removing values) * - * ```ts - * import { Effect, TxQueue } from "effect" + * ```ts import.meta.vitest + * import { Effect, Exit, TxQueue } from "effect" * * const program = Effect.gen(function*() { * const queue = yield* TxQueue.bounded(10) @@ -995,11 +999,10 @@ export const takeBetween: { * * // Peek at the next item without removing it * const item = yield* TxQueue.peek(queue) - * console.log(item) // 42 * * // Item is still in the queue * const size = yield* TxQueue.size(queue) - * console.log(size) // 1 + * return [item, size] as const * }) * * // Error handling example @@ -1008,9 +1011,11 @@ export const takeBetween: { * yield* TxQueue.fail(queue, "queue failed") * * // peek() propagates the queue error through E-channel - * const result = yield* Effect.flip(TxQueue.peek(queue)) - * console.log(result) // "queue failed" + * return yield* Effect.exit(TxQueue.peek(queue)) * }) + * + * await Effect.runPromise(program) // => [42, 1] + * await Effect.runPromise(errorExample) // => Exit.fail("queue failed") * ``` * * @category combinators @@ -1037,16 +1042,17 @@ export const peek = (self: TxDequeue): Effect.Effect => * * **Example** (Reading queue size) * - * ```ts + * ```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]) * - * const size = yield* TxQueue.size(queue) - * console.log(size) // 3 + * return yield* TxQueue.size(queue) * }) + * + * await Effect.runPromise(program) // => 3 * ``` * * @category combinators @@ -1059,22 +1065,23 @@ export const size = (self: TxQueueState): Effect.Effect => TxChunk.size( * * **Example** (Checking whether a queue is empty) * - * ```ts + * ```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) - * console.log(empty) // true * * yield* TxQueue.offer(queue, 42) * const stillEmpty = yield* TxQueue.isEmpty(queue) - * console.log(stillEmpty) // false + * return [empty, stillEmpty] as const * }) + * + * await Effect.runPromise(program) // => [true, false] * ``` * - * @category combinators + * @category predicates * @since 2.0.0 */ export const isEmpty = (self: TxQueueState): Effect.Effect => TxChunk.isEmpty(self.items) @@ -1084,22 +1091,23 @@ export const isEmpty = (self: TxQueueState): Effect.Effect => TxChunk.i * * **Example** (Checking whether a queue is full) * - * ```ts + * ```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) - * console.log(full) // false * * yield* TxQueue.offerAll(queue, [1, 2]) * const nowFull = yield* TxQueue.isFull(queue) - * console.log(nowFull) // true + * return [full, nowFull] as const * }) + * + * await Effect.runPromise(program) // => [false, true] * ``` * - * @category combinators + * @category predicates * @since 2.0.0 */ export const isFull = (self: TxQueueState): Effect.Effect => @@ -1116,7 +1124,7 @@ export const isFull = (self: TxQueueState): Effect.Effect => * * **Example** (Interrupting queues) * - * ```ts + * ```ts import.meta.vitest * import { Effect, TxQueue } from "effect" * * const program = Effect.gen(function*() { @@ -1124,9 +1132,10 @@ export const isFull = (self: TxQueueState): Effect.Effect => * yield* TxQueue.offer(queue, 42) * * // Interrupt gracefully - allows remaining items to be consumed - * const result = yield* TxQueue.interrupt(queue) - * console.log(result) // true + * return yield* TxQueue.interrupt(queue) * }) + * + * await Effect.runPromise(program) // => true * ``` * * @category combinators @@ -1144,16 +1153,17 @@ export const interrupt = (self: TxEnqueue): Effect.Effect = * * **Example** (Failing queues) * - * ```ts + * ```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 - * const result = yield* TxQueue.fail(queue, "connection lost") - * console.log(result) // true + * return yield* TxQueue.fail(queue, "connection lost") * }) + * + * await Effect.runPromise(program) // => true * ``` * * @category combinators @@ -1189,7 +1199,7 @@ export const fail: { * * **Example** (Failing queues with causes) * - * ```ts + * ```ts import.meta.vitest * import { Cause, Effect, TxQueue } from "effect" * * const program = Effect.gen(function*() { @@ -1198,8 +1208,10 @@ export const fail: { * // Complete with specific cause * const cause = Cause.interrupt() * const result = yield* TxQueue.failCause(queue, cause) - * console.log(result) // true + * return [cause, result] as const * }) + * + * await Effect.runPromise(program) // => [Cause.interrupt(), true] * ``` * * @category combinators @@ -1239,23 +1251,23 @@ export const failCause: { * * **Example** (Ending queues) * - * ```ts - * import { Cause, Effect, TxQueue } from "effect" + * ```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) - * console.log(result) // true * * // All operations will now fail with Done - * const takeResult = yield* Effect.flip(TxQueue.take(queue)) - * console.log(Cause.isDone(takeResult)) // true + * const takeResult = yield* Effect.exit(TxQueue.take(queue)) * - * const peekResult = yield* Effect.flip(TxQueue.peek(queue)) - * console.log(Cause.isDone(peekResult)) // true + * 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 @@ -1265,16 +1277,15 @@ export const end = (self: TxEnqueue): Effect.Effect(self: TxEnqueue): Effect.Effect [5, [1, 2, 3, 4, 5], 0] * ``` * * @category combinators @@ -1307,6 +1318,9 @@ export const clear = (self: TxEnqueue): Effect.Effect, Excl } 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) @@ -1319,7 +1333,7 @@ export const clear = (self: TxEnqueue): Effect.Effect, Excl * * **Example** (Shutting down queues) * - * ```ts + * ```ts import.meta.vitest * import { Effect, TxQueue } from "effect" * * const program = Effect.gen(function*() { @@ -1327,16 +1341,16 @@ export const clear = (self: TxEnqueue): Effect.Effect, Excl * yield* TxQueue.offerAll(queue, [1, 2, 3, 4, 5]) * * const sizeBefore = yield* TxQueue.size(queue) - * console.log(sizeBefore) // 5 * * yield* TxQueue.shutdown(queue) * * const sizeAfter = yield* TxQueue.size(queue) - * console.log(sizeAfter) // 0 (cleared) * * const isShutdown = yield* TxQueue.isShutdown(queue) - * console.log(isShutdown) // true (interrupted) + * return [sizeBefore, sizeAfter, isShutdown] as const * }) + * + * await Effect.runPromise(program) // => [5, 0, true] * ``` * * @category combinators @@ -1344,7 +1358,7 @@ export const clear = (self: TxEnqueue): Effect.Effect, Excl */ export const shutdown = (self: TxEnqueue): Effect.Effect => Effect.gen(function*() { - yield* Effect.ignore(clear(self)) + yield* Effect.ignoreCause(clear(self)) return yield* interrupt(self) }).pipe(Effect.tx) @@ -1353,22 +1367,23 @@ export const shutdown = (self: TxEnqueue): Effect.Effect => * * **Example** (Checking open state) * - * ```ts + * ```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) - * console.log(open) // true * * yield* TxQueue.interrupt(queue) * const stillOpen = yield* TxQueue.isOpen(queue) - * console.log(stillOpen) // false + * return [open, stillOpen] as const * }) + * + * await Effect.runPromise(program) // => [true, false] * ``` * - * @category combinators + * @category predicates * @since 4.0.0 */ export const isOpen = (self: TxQueueState): Effect.Effect => @@ -1379,7 +1394,7 @@ export const isOpen = (self: TxQueueState): Effect.Effect => * * **Example** (Checking closing state) * - * ```ts + * ```ts import.meta.vitest * import { Effect, TxQueue } from "effect" * * const program = Effect.gen(function*() { @@ -1387,15 +1402,16 @@ export const isOpen = (self: TxQueueState): Effect.Effect => * yield* TxQueue.offer(queue, 42) * * const closing = yield* TxQueue.isClosing(queue) - * console.log(closing) // false * * yield* TxQueue.interrupt(queue) * const nowClosing = yield* TxQueue.isClosing(queue) - * console.log(nowClosing) // true + * return [closing, nowClosing] as const * }) + * + * await Effect.runPromise(program) // => [false, true] * ``` * - * @category combinators + * @category predicates * @since 4.0.0 */ export const isClosing = (self: TxQueueState): Effect.Effect => @@ -1406,22 +1422,23 @@ export const isClosing = (self: TxQueueState): Effect.Effect => * * **Example** (Checking done state) * - * ```ts + * ```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) - * console.log(done) // false * * yield* TxQueue.interrupt(queue) * const nowDone = yield* TxQueue.isDone(queue) - * console.log(nowDone) // true + * return [done, nowDone] as const * }) + * + * await Effect.runPromise(program) // => [false, true] * ``` * - * @category combinators + * @category predicates * @since 4.0.0 */ export const isDone = (self: TxQueueState): Effect.Effect => @@ -1432,22 +1449,23 @@ export const isDone = (self: TxQueueState): Effect.Effect => * * **Example** (Checking shutdown state) * - * ```ts + * ```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) - * console.log(isShutdown) // false * * yield* TxQueue.shutdown(queue) * const nowShutdown = yield* TxQueue.isShutdown(queue) - * console.log(nowShutdown) // true + * return [isShutdown, nowShutdown] as const * }) + * + * await Effect.runPromise(program) // => [false, true] * ``` * - * @category combinators + * @category predicates * @since 2.0.0 */ export const isShutdown = (self: TxQueueState): Effect.Effect => isDone(self) @@ -1457,19 +1475,20 @@ export const isShutdown = (self: TxQueueState): Effect.Effect => isDone * * **Example** (Awaiting queue completion) * - * ```ts - * import { Effect, TxQueue } from "effect" + * ```ts import.meta.vitest + * import { Effect, Fiber, TxQueue } from "effect" * * const program = Effect.gen(function*() { * const queue = yield* TxQueue.bounded(10) * - * // In another fiber, end the queue - * yield* Effect.forkChild(Effect.delay(TxQueue.interrupt(queue), "100 millis")) + * const waiter = yield* Effect.forkChild(TxQueue.awaitCompletion(queue)) + * yield* TxQueue.interrupt(queue) * - * // Wait for completion - succeeds when queue ends - * yield* TxQueue.awaitCompletion(queue) - * console.log("Queue completed successfully") + * yield* Fiber.join(waiter) + * return "Queue completed successfully" * }) + * + * await Effect.runPromise(program) // => "Queue completed successfully" * ``` * * @category combinators diff --git a/repos/effect/packages/effect/src/TxReentrantLock.ts b/repos/effect/packages/effect/src/TxReentrantLock.ts index f4dfc2e905..0e0f90ec65 100644 --- a/repos/effect/packages/effect/src/TxReentrantLock.ts +++ b/repos/effect/packages/effect/src/TxReentrantLock.ts @@ -44,18 +44,21 @@ const emptyState: LockState = { * * **Example** (Using read and write locks) * - * ```ts + * ```ts import.meta.vitest * import { Effect, TxReentrantLock } from "effect" * * const program = Effect.gen(function*() { * const lock = yield* TxReentrantLock.make() * * // Multiple readers can proceed concurrently - * yield* TxReentrantLock.withReadLock(lock, Effect.succeed("reading")) + * const read = yield* TxReentrantLock.withReadLock(lock, Effect.succeed("reading")) * * // Writer gets exclusive access - * yield* TxReentrantLock.withWriteLock(lock, Effect.succeed("writing")) + * const write = yield* TxReentrantLock.withWriteLock(lock, Effect.succeed("writing")) + * return [read, write] * }) + * + * await Effect.runPromise(program) // => ["reading", "writing"] * ``` * * @category models @@ -91,14 +94,15 @@ const TxReentrantLockProto: Omit = * * **Example** (Creating a reentrant lock) * - * ```ts + * ```ts import.meta.vitest * import { Effect, TxReentrantLock } from "effect" * * const program = Effect.gen(function*() { * const lock = yield* TxReentrantLock.make() - * const isLocked = yield* TxReentrantLock.locked(lock) - * console.log(isLocked) // false + * return yield* TxReentrantLock.locked(lock) * }) + * + * await Effect.runPromise(program) // => false * ``` * * @category constructors @@ -124,15 +128,17 @@ export const make = (): Effect.Effect => * * **Example** (Acquiring a read lock) * - * ```ts + * ```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) - * console.log(count) // 1 * yield* TxReentrantLock.releaseRead(lock) + * return count * }) + * + * await Effect.runPromise(program) // => 1 * ``` * * @category mutations @@ -178,15 +184,17 @@ export const acquireRead = (self: TxReentrantLock): Effect.Effect => * * **Example** (Acquiring a write lock) * - * ```ts + * ```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) - * console.log(count) // 1 * yield* TxReentrantLock.releaseWrite(lock) + * return count * }) + * + * await Effect.runPromise(program) // => 1 * ``` * * @category mutations @@ -243,38 +251,39 @@ export const acquireWrite = (self: TxReentrantLock): Effect.Effect => * * **Example** (Releasing a read lock) * - * ```ts + * ```ts import.meta.vitest * import { Effect, TxReentrantLock } from "effect" * * const program = Effect.gen(function*() { * const lock = yield* TxReentrantLock.make() * yield* TxReentrantLock.acquireRead(lock) - * const remaining = yield* TxReentrantLock.releaseRead(lock) - * console.log(remaining) // 0 + * return yield* TxReentrantLock.releaseRead(lock) * }) + * + * await Effect.runPromise(program) // => 0 * ``` * * @category mutations * @since 2.0.0 */ -export const releaseRead = (self: TxReentrantLock): Effect.Effect => - Effect.withFiber((fiber) => - Effect.gen(function*() { - const state = yield* TxRef.get(self.stateRef) - const fiberId = fiber.id - const currentCount = Option.getOrElse(HashMap.get(state.readers, fiberId), () => 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 + if (currentCount <= 0) return 0 - const newCount = currentCount - 1 - const newReaders = newCount === 0 - ? HashMap.remove(state.readers, fiberId) - : HashMap.set(state.readers, fiberId, newCount) + 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) - ) + 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. @@ -289,37 +298,38 @@ export const releaseRead = (self: TxReentrantLock): Effect.Effect => * * **Example** (Releasing a write lock) * - * ```ts + * ```ts import.meta.vitest * import { Effect, TxReentrantLock } from "effect" * * const program = Effect.gen(function*() { * const lock = yield* TxReentrantLock.make() * yield* TxReentrantLock.acquireWrite(lock) - * const remaining = yield* TxReentrantLock.releaseWrite(lock) - * console.log(remaining) // 0 + * return yield* TxReentrantLock.releaseWrite(lock) * }) + * + * await Effect.runPromise(program) // => 0 * ``` * * @category mutations * @since 2.0.0 */ -export const releaseWrite = (self: TxReentrantLock): Effect.Effect => - Effect.withFiber((fiber) => - Effect.gen(function*() { - const state = yield* TxRef.get(self.stateRef) - const fiberId = fiber.id +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 + 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) + 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) - ) + 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. @@ -327,29 +337,35 @@ export const releaseWrite = (self: TxReentrantLock): Effect.Effect => * * **Example** (Holding a scoped read lock) * - * ```ts + * ```ts import.meta.vitest * import { Effect, TxReentrantLock } from "effect" * * const program = Effect.gen(function*() { * const lock = yield* TxReentrantLock.make() * - * yield* Effect.scoped( + * 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.acquireRelease( - acquireRead(self), - () => releaseRead(self) + Effect.withFiber((fiber) => + Effect.acquireRelease( + acquireRead(self), + () => releaseReadFor(self, fiber.id) + ) ) /** @@ -358,29 +374,35 @@ export const readLock = (self: TxReentrantLock): Effect.Effect [1, 0] * ``` * * @category mutations * @since 2.0.0 */ export const writeLock = (self: TxReentrantLock): Effect.Effect => - Effect.acquireRelease( - acquireWrite(self), - () => releaseWrite(self) + Effect.withFiber((fiber) => + Effect.acquireRelease( + acquireWrite(self), + () => releaseWriteFor(self, fiber.id) + ) ) /** @@ -389,17 +411,18 @@ export const writeLock = (self: TxReentrantLock): Effect.Effect "read data" * ``` * * @category mutations @@ -432,17 +455,18 @@ export const withReadLock: { * * **Example** (Running an effect with a write lock) * - * ```ts + * ```ts import.meta.vitest * import { Effect, TxReentrantLock } from "effect" * * const program = Effect.gen(function*() { * const lock = yield* TxReentrantLock.make() - * const result = yield* TxReentrantLock.withWriteLock( + * return yield* TxReentrantLock.withWriteLock( * lock, * Effect.succeed("wrote data") * ) - * console.log(result) // "wrote data" * }) + * + * await Effect.runPromise(program) // => "wrote data" * ``` * * @category mutations @@ -479,17 +503,18 @@ export const withWriteLock: { * * **Example** (Running an effect with exclusive access) * - * ```ts + * ```ts import.meta.vitest * import { Effect, TxReentrantLock } from "effect" * * const program = Effect.gen(function*() { * const lock = yield* TxReentrantLock.make() - * const result = yield* TxReentrantLock.withLock( + * return yield* TxReentrantLock.withLock( * lock, * Effect.succeed("exclusive operation") * ) - * console.log(result) // "exclusive operation" * }) + * + * await Effect.runPromise(program) // => "exclusive operation" * ``` * * @category mutations @@ -509,16 +534,18 @@ export const withLock: { * * **Example** (Counting read locks) * - * ```ts + * ```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) - * console.log(count) // 1 * yield* TxReentrantLock.releaseRead(lock) + * return count * }) + * + * await Effect.runPromise(program) // => 1 * ``` * * @category getters @@ -539,14 +566,15 @@ export const readLocks = (self: TxReentrantLock): Effect.Effect => * * **Example** (Counting write locks) * - * ```ts + * ```ts import.meta.vitest * import { Effect, TxReentrantLock } from "effect" * * const program = Effect.gen(function*() { * const lock = yield* TxReentrantLock.make() - * const count = yield* TxReentrantLock.writeLocks(lock) - * console.log(count) // 0 + * return yield* TxReentrantLock.writeLocks(lock) * }) + * + * await Effect.runPromise(program) // => 0 * ``` * * @category getters @@ -563,14 +591,15 @@ export const writeLocks = (self: TxReentrantLock): Effect.Effect => * * **Example** (Checking whether a lock is held) * - * ```ts + * ```ts import.meta.vitest * import { Effect, TxReentrantLock } from "effect" * * const program = Effect.gen(function*() { * const lock = yield* TxReentrantLock.make() - * const isLocked = yield* TxReentrantLock.locked(lock) - * console.log(isLocked) // false + * return yield* TxReentrantLock.locked(lock) * }) + * + * await Effect.runPromise(program) // => false * ``` * * @category getters @@ -587,14 +616,15 @@ export const locked = (self: TxReentrantLock): Effect.Effect => * * **Example** (Checking whether a read lock is held) * - * ```ts + * ```ts import.meta.vitest * import { Effect, TxReentrantLock } from "effect" * * const program = Effect.gen(function*() { * const lock = yield* TxReentrantLock.make() - * const isReadLocked = yield* TxReentrantLock.readLocked(lock) - * console.log(isReadLocked) // false + * return yield* TxReentrantLock.readLocked(lock) * }) + * + * await Effect.runPromise(program) // => false * ``` * * @category getters @@ -611,14 +641,15 @@ export const readLocked = (self: TxReentrantLock): Effect.Effect => * * **Example** (Checking whether a write lock is held) * - * ```ts + * ```ts import.meta.vitest * import { Effect, TxReentrantLock } from "effect" * * const program = Effect.gen(function*() { * const lock = yield* TxReentrantLock.make() - * const isWriteLocked = yield* TxReentrantLock.writeLocked(lock) - * console.log(isWriteLocked) // false + * return yield* TxReentrantLock.writeLocked(lock) * }) + * + * await Effect.runPromise(program) // => false * ``` * * @category getters @@ -639,14 +670,12 @@ export const writeLocked = (self: TxReentrantLock): Effect.Effect => * * **Example** (Checking for TxReentrantLock values) * - * ```ts + * ```ts import.meta.vitest * import { TxReentrantLock } from "effect" * - * declare const someValue: unknown + * const someValue: unknown = {} * - * if (TxReentrantLock.isTxReentrantLock(someValue)) { - * console.log("This is a TxReentrantLock") - * } + * TxReentrantLock.isTxReentrantLock(someValue) // => false * ``` * * @category guards diff --git a/repos/effect/packages/effect/src/TxRef.ts b/repos/effect/packages/effect/src/TxRef.ts index 94e7891f6b..8a2364fe92 100644 --- a/repos/effect/packages/effect/src/TxRef.ts +++ b/repos/effect/packages/effect/src/TxRef.ts @@ -36,7 +36,7 @@ const TypeId = "~effect/transactions/TxRef" * * **Example** (Using a transactional reference) * - * ```ts + * ```ts import.meta.vitest * import { Effect, TxRef } from "effect" * * const program = Effect.gen(function*() { @@ -49,9 +49,10 @@ const TypeId = "~effect/transactions/TxRef" * yield* TxRef.set(ref, current + 1) * })) * - * const final = yield* TxRef.get(ref) - * console.log(final) // 1 + * return yield* TxRef.get(ref) * }) + * + * await Effect.runPromise(program) // => 1 * ``` * * @category models @@ -74,7 +75,7 @@ export interface TxRef extends Pipeable { * * **Example** (Creating transactional references) * - * ```ts + * ```ts import.meta.vitest * import { Effect, TxRef } from "effect" * * const program = Effect.gen(function*() { @@ -88,9 +89,10 @@ export interface TxRef extends Pipeable { * yield* TxRef.set(name, "Bob") * })) * - * console.log(yield* TxRef.get(counter)) // 42 - * console.log(yield* TxRef.get(name)) // "Bob" + * return [yield* TxRef.get(counter), yield* TxRef.get(name)] * }) + * + * await Effect.runPromise(program) // => [42, "Bob"] * ``` * * @category constructors @@ -108,7 +110,7 @@ export const make = (initial: A) => Effect.sync(() => makeUnsafe(initial)) * * **Example** (Creating transactional references unsafely) * - * ```ts + * ```ts import.meta.vitest * import { TxRef } from "effect" * * // Create a TxRef synchronously (unsafe - use make instead in Effect contexts) @@ -116,8 +118,8 @@ export const make = (initial: A) => Effect.sync(() => makeUnsafe(initial)) * const config = TxRef.makeUnsafe({ timeout: 5000, retries: 3 }) * * // These are now ready to use in transactions - * console.log(counter.value) // 0 - * console.log(config.value) // { timeout: 5000, retries: 3 } + * counter.value // => 0 + * config.value // => { timeout: 5000, retries: 3 } * ``` * * @category constructors @@ -143,7 +145,7 @@ export const makeUnsafe = (initial: A): TxRef => ({ * * **Example** (Modifying transactional references) * - * ```ts + * ```ts import.meta.vitest * import { Effect, TxRef } from "effect" * * const program = Effect.gen(function*() { @@ -152,9 +154,10 @@ export const makeUnsafe = (initial: A): TxRef => ({ * // Modify and return both old and new value * const result = yield* TxRef.modify(counter, (current) => [current * 2, current + 1]) * - * console.log(result) // 0 (the return value: current * 2) - * console.log(yield* TxRef.get(counter)) // 1 (the new value: current + 1) + * return [result, yield* TxRef.get(counter)] * }) + * + * await Effect.runPromise(program) // => [0, 1] * ``` * * @category combinators @@ -191,7 +194,7 @@ export const modify: { * * **Example** (Updating transactional references) * - * ```ts + * ```ts import.meta.vitest * import { Effect, TxRef } from "effect" * * const program = Effect.gen(function*() { @@ -202,8 +205,10 @@ export const modify: { * TxRef.update(counter, (current) => current * 2) * ) * - * console.log(yield* TxRef.get(counter)) // 20 + * return yield* TxRef.get(counter) * }) + * + * await Effect.runPromise(program) // => 20 * ``` * * @category combinators @@ -226,7 +231,7 @@ export const update: { * * **Example** (Reading transactional references) * - * ```ts + * ```ts import.meta.vitest * import { Effect, TxRef } from "effect" * * const program = Effect.gen(function*() { @@ -237,8 +242,10 @@ export const update: { * TxRef.get(counter) * ) * - * console.log(value) // 42 + * return value * }) + * + * await Effect.runPromise(program) // => 42 * ``` * * @category combinators @@ -255,7 +262,7 @@ export const get = (self: TxRef): Effect.Effect => modify(self, (curren * * **Example** (Setting transactional references) * - * ```ts + * ```ts import.meta.vitest * import { Effect, TxRef } from "effect" * * const program = Effect.gen(function*() { @@ -266,8 +273,10 @@ export const get = (self: TxRef): Effect.Effect => modify(self, (curren * TxRef.set(counter, 100) * ) * - * console.log(yield* TxRef.get(counter)) // 100 + * return yield* TxRef.get(counter) * }) + * + * await Effect.runPromise(program) // => 100 * ``` * * @category combinators diff --git a/repos/effect/packages/effect/src/TxSemaphore.ts b/repos/effect/packages/effect/src/TxSemaphore.ts index 7886e2d785..65f3b48191 100644 --- a/repos/effect/packages/effect/src/TxSemaphore.ts +++ b/repos/effect/packages/effect/src/TxSemaphore.ts @@ -34,7 +34,7 @@ const TypeId = "~effect/transactions/TxSemaphore" * * **Example** (Managing permits transactionally) * - * ```ts + * ```ts import.meta.vitest * import { Effect, TxSemaphore } from "effect" * * // Create a semaphore with 3 permits for managing concurrent database connections @@ -43,14 +43,17 @@ const TypeId = "~effect/transactions/TxSemaphore" * * // Acquire a permit before accessing the database * yield* TxSemaphore.acquire(dbSemaphore) - * console.log("Database connection acquired") + * const acquired = yield* TxSemaphore.available(dbSemaphore) * * // Perform database operations... * * // Release the permit when done * yield* TxSemaphore.release(dbSemaphore) - * console.log("Database connection released") + * 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 @@ -98,8 +101,8 @@ const makeTxSemaphore = (permitsRef: TxRef.TxRef, capacity: number): TxS * * **Example** (Creating a semaphore) * - * ```ts - * import { Console, Effect, TxSemaphore } from "effect" + * ```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*() { @@ -109,12 +112,10 @@ const makeTxSemaphore = (permitsRef: TxRef.TxRef, capacity: number): TxS * // Check initial state * const available = yield* TxSemaphore.available(connectionSemaphore) * const capacity = yield* TxSemaphore.capacity(connectionSemaphore) - * - * yield* Console.log( - * `Created semaphore with ${capacity} permits, ${available} available` - * ) - * // Output: "Created semaphore with 3 permits, 3 available" + * return [capacity, available] as const * }) + * + * await Effect.runPromise(program) // => [3, 3] * ``` * * @see {@link available} for reading the current available permit count @@ -142,15 +143,14 @@ export const make = (permits: number): Effect.Effect => * * **Example** (Checking available permits) * - * ```ts - * import { Console, Effect, TxSemaphore } from "effect" + * ```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) - * yield* Console.log(`Available permits: ${before}`) // 5 * * // Acquire some permits * yield* TxSemaphore.acquire(semaphore) @@ -158,8 +158,10 @@ export const make = (permits: number): Effect.Effect => * * // Check available permits after acquiring * const after = yield* TxSemaphore.available(semaphore) - * yield* Console.log(`Available permits: ${after}`) // 3 + * return [before, after] as const * }) + * + * await Effect.runPromise(program) // => [5, 3] * ``` * * @see {@link capacity} for reading the fixed total permit count @@ -178,20 +180,21 @@ export const available = (self: TxSemaphore): Effect.Effect => TxRef.get * * **Example** (Checking semaphore capacity) * - * ```ts - * import { Console, Effect, TxSemaphore } from "effect" + * ```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) - * yield* Console.log(`Semaphore capacity: ${capacity}`) // 10 * * // Capacity remains constant regardless of current permits * yield* TxSemaphore.acquire(semaphore) * const stillSame = yield* TxSemaphore.capacity(semaphore) - * yield* Console.log(`Capacity after acquire: ${stillSame}`) // 10 + * return [capacity, stillSame] as const * }) + * + * await Effect.runPromise(program) // => [10, 10] * ``` * * @see {@link available} for reading the current available permit count @@ -212,23 +215,20 @@ export const capacity = (self: TxSemaphore): Effect.Effect => Effect.suc * * **Example** (Acquiring a permit) * - * ```ts - * import { Console, Effect, TxSemaphore } from "effect" + * ```ts import.meta.vitest + * import { Effect, TxSemaphore } from "effect" * * const program = Effect.gen(function*() { * const semaphore = yield* TxSemaphore.make(2) * - * yield* Console.log("Acquiring first permit...") * yield* TxSemaphore.acquire(semaphore) - * yield* Console.log("First permit acquired") * - * yield* Console.log("Acquiring second permit...") * yield* TxSemaphore.acquire(semaphore) - * yield* Console.log("Second permit acquired") * - * const available = yield* TxSemaphore.available(semaphore) - * yield* Console.log(`Available permits: ${available}`) // 0 + * return yield* TxSemaphore.available(semaphore) * }) + * + * await Effect.runPromise(program) // => 0 * ``` * * @see {@link tryAcquire} for a non-blocking single-permit attempt @@ -267,19 +267,18 @@ export const acquire = (self: TxSemaphore): Effect.Effect => * * **Example** (Acquiring multiple permits) * - * ```ts - * import { Console, Effect, TxSemaphore } from "effect" + * ```ts import.meta.vitest + * import { Effect, TxSemaphore } from "effect" * * const program = Effect.gen(function*() { * const semaphore = yield* TxSemaphore.make(5) * - * yield* Console.log("Acquiring 3 permits...") * yield* TxSemaphore.acquireN(semaphore, 3) - * yield* Console.log("3 permits acquired") * - * const available = yield* TxSemaphore.available(semaphore) - * yield* Console.log(`Available permits: ${available}`) // 2 + * return yield* TxSemaphore.available(semaphore) * }) + * + * await Effect.runPromise(program) // => 2 * ``` * * @see {@link tryAcquireN} for a non-blocking multi-permit attempt @@ -313,20 +312,21 @@ export const acquireN = (self: TxSemaphore, n: number): Effect.Effect => { * * **Example** (Trying to acquire a permit) * - * ```ts - * import { Console, Effect, TxSemaphore } from "effect" + * ```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) - * yield* Console.log(`First try: ${first}`) // true * * // Second try should fail (no permits left) * const second = yield* TxSemaphore.tryAcquire(semaphore) - * yield* Console.log(`Second try: ${second}`) // false + * return [first, second] as const * }) + * + * await Effect.runPromise(program) // => [true, false] * ``` * * @see {@link acquire} for waiting until one permit is available @@ -355,20 +355,21 @@ export const tryAcquire = (self: TxSemaphore): Effect.Effect => * * **Example** (Trying to acquire multiple permits) * - * ```ts - * import { Console, Effect, TxSemaphore } from "effect" + * ```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) - * yield* Console.log(`First try (2 permits): ${first}`) // true * * // Try to acquire 2 more permits (should fail, only 1 left) * const second = yield* TxSemaphore.tryAcquireN(semaphore, 2) - * yield* Console.log(`Second try (2 permits): ${second}`) // false + * return [first, second] as const * }) + * + * await Effect.runPromise(program) // => [true, false] * ``` * * @see {@link acquireN} for waiting until all requested permits are available @@ -404,22 +405,23 @@ export const tryAcquireN = (self: TxSemaphore, n: number): Effect.Effect [1, 2] * ``` * * @see {@link acquire} for manually acquiring one permit @@ -448,22 +450,23 @@ export const release = (self: TxSemaphore): Effect.Effect => * * **Example** (Releasing multiple permits) * - * ```ts - * import { Console, Effect, TxSemaphore } from "effect" + * ```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) - * let available = yield* TxSemaphore.available(semaphore) - * yield* Console.log(`After acquire: ${available}`) // 2 + * const afterAcquire = yield* TxSemaphore.available(semaphore) * * // Release 2 permits * yield* TxSemaphore.releaseN(semaphore, 2) - * available = yield* TxSemaphore.available(semaphore) - * yield* Console.log(`After release: ${available}`) // 4 + * 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 @@ -499,26 +502,30 @@ export const releaseN = (self: TxSemaphore, n: number): Effect.Effect => { * * **Example** (Running an effect with a permit) * - * ```ts - * import { Console, Effect, TxSemaphore } from "effect" + * ```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*() { - * yield* Console.log("Permit acquired, accessing database...") - * yield* Effect.sleep("100 millis") // Simulate database work - * yield* Console.log("Database operation complete") + * events.push("permit acquired") + * yield* Effect.yieldNow + * events.push("operation complete") * return "query result" * }) * ) * - * yield* Console.log(`Result: ${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 @@ -570,26 +577,30 @@ export const withPermit: { * * **Example** (Running an effect with multiple permits) * - * ```ts - * import { Console, Effect, TxSemaphore } from "effect" + * ```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*() { - * yield* Console.log("3 permits acquired, processing batch...") - * yield* Effect.sleep("200 millis") // Simulate batch processing + * events.push("3 permits acquired") + * yield* Effect.yieldNow * return ["result1", "result2", "result3"] * }) * ) * - * yield* Console.log(`Batch results: ${results.join(", ")}`) * // 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 @@ -636,28 +647,32 @@ export const withPermits: { * * **Example** (Acquiring a scoped permit) * - * ```ts - * import { Console, Effect, TxSemaphore } from "effect" + * ```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) - * yield* Console.log("Permit acquired for scope") + * events.push("permit acquired for scope") * * // Do work within the scope - * yield* Effect.sleep("500 millis") - * yield* Console.log("Work completed") + * yield* Effect.yieldNow + * events.push("work completed") * * // Permit will be automatically released when scope closes * }) * ) * - * yield* Console.log("Scope closed, permit released") + * 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 @@ -681,22 +696,25 @@ export const withPermitScoped = (self: TxSemaphore): Effect.Effect [true, false, 5] * ``` * * @see {@link make} for creating a `TxSemaphore` diff --git a/repos/effect/packages/effect/src/TxSubscriptionRef.ts b/repos/effect/packages/effect/src/TxSubscriptionRef.ts index aa4a8942fa..668c0de89d 100644 --- a/repos/effect/packages/effect/src/TxSubscriptionRef.ts +++ b/repos/effect/packages/effect/src/TxSubscriptionRef.ts @@ -36,24 +36,25 @@ const TypeId = "~effect/transactions/TxSubscriptionRef" * * **Example** (Subscribing to transactional changes) * - * ```ts + * ```ts import.meta.vitest * import { Effect, TxQueue, TxSubscriptionRef } from "effect" * * const program = Effect.gen(function*() { * const ref = yield* TxSubscriptionRef.make(0) * - * yield* Effect.scoped( + * return yield* Effect.scoped( * Effect.gen(function*() { * const sub = yield* TxSubscriptionRef.changes(ref) * const initial = yield* TxQueue.take(sub) - * console.log(initial) // 0 * * yield* TxSubscriptionRef.set(ref, 1) * const next = yield* TxQueue.take(sub) - * console.log(next) // 1 + * return [initial, next] * }) * ) * }) + * + * await Effect.runPromise(program) // => [0, 1] * ``` * * @see {@link make} for creating a transactional subscription reference @@ -100,14 +101,15 @@ const TxSubscriptionRefProto: Omit, typeof TypeId | "ref" * * **Example** (Creating a transactional subscription reference) * - * ```ts + * ```ts import.meta.vitest * import { Effect, TxSubscriptionRef } from "effect" * * const program = Effect.gen(function*() { * const ref = yield* TxSubscriptionRef.make(42) - * const value = yield* TxSubscriptionRef.get(ref) - * console.log(value) // 42 + * return yield* TxSubscriptionRef.get(ref) * }) + * + * await Effect.runPromise(program) // => 42 * ``` * * @see {@link changes} for subscribing to the created reference @@ -140,14 +142,15 @@ export const make = (value: A): Effect.Effect> => * * **Example** (Reading the current value) * - * ```ts + * ```ts import.meta.vitest * import { Effect, TxSubscriptionRef } from "effect" * * const program = Effect.gen(function*() { * const ref = yield* TxSubscriptionRef.make("hello") - * const value = yield* TxSubscriptionRef.get(ref) - * console.log(value) // "hello" + * return yield* TxSubscriptionRef.get(ref) * }) + * + * await Effect.runPromise(program) // => "hello" * ``` * * @see {@link changes} for reading the current value and subsequent updates @@ -172,15 +175,16 @@ export const get = (self: TxSubscriptionRef): Effect.Effect => TxRef.ge * * **Example** (Modifying and returning a value) * - * ```ts + * ```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]) - * console.log(result) // "was 10" - * console.log(yield* TxSubscriptionRef.get(ref)) // 11 + * 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 @@ -222,14 +226,16 @@ export const modify: { * * **Example** (Setting a new value) * - * ```ts + * ```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) - * console.log(yield* TxSubscriptionRef.get(ref)) // 42 + * return yield* TxSubscriptionRef.get(ref) * }) + * + * await Effect.runPromise(program) // => 42 * ``` * * @see {@link update} for deriving the new value from the current value @@ -257,14 +263,16 @@ export const set: { * * **Example** (Updating a value) * - * ```ts + * ```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) - * console.log(yield* TxSubscriptionRef.get(ref)) // 10 + * return yield* TxSubscriptionRef.get(ref) * }) + * + * await Effect.runPromise(program) // => 10 * ``` * * @see {@link set} for replacing the value directly @@ -293,15 +301,16 @@ export const update: { * * **Example** (Getting and setting atomically) * - * ```ts + * ```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") - * console.log(old) // "a" - * console.log(yield* TxSubscriptionRef.get(ref)) // "b" + * return [old, yield* TxSubscriptionRef.get(ref)] * }) + * + * await Effect.runPromise(program) // => ["a", "b"] * ``` * * @see {@link set} for setting without returning the previous value @@ -329,15 +338,16 @@ export const getAndSet: { * * **Example** (Getting and updating atomically) * - * ```ts + * ```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) - * console.log(old) // 1 - * console.log(yield* TxSubscriptionRef.get(ref)) // 11 + * return [old, yield* TxSubscriptionRef.get(ref)] * }) + * + * await Effect.runPromise(program) // => [1, 11] * ``` * * @see {@link update} for updating without returning the previous value @@ -366,14 +376,15 @@ export const getAndUpdate: { * * **Example** (Updating and reading atomically) * - * ```ts + * ```ts import.meta.vitest * import { Effect, TxSubscriptionRef } from "effect" * * const program = Effect.gen(function*() { * const ref = yield* TxSubscriptionRef.make(3) - * const result = yield* TxSubscriptionRef.updateAndGet(ref, (n) => n * 3) - * console.log(result) // 9 + * return yield* TxSubscriptionRef.updateAndGet(ref, (n) => n * 3) * }) + * + * await Effect.runPromise(program) // => 9 * ``` * * @see {@link update} for updating without returning the new value @@ -409,24 +420,25 @@ export const updateAndGet: { * * **Example** (Subscribing to changes) * - * ```ts + * ```ts import.meta.vitest * import { Effect, TxQueue, TxSubscriptionRef } from "effect" * * const program = Effect.gen(function*() { * const ref = yield* TxSubscriptionRef.make(0) * - * yield* Effect.scoped( + * return yield* Effect.scoped( * Effect.gen(function*() { * const sub = yield* TxSubscriptionRef.changes(ref) * const initial = yield* TxQueue.take(sub) - * console.log(initial) // 0 * * yield* TxSubscriptionRef.set(ref, 1) * const next = yield* TxQueue.take(sub) - * console.log(next) // 1 + * return [initial, next] * }) * ) * }) + * + * await Effect.runPromise(program) // => [0, 1] * ``` * * @see {@link changesStream} for subscribing through a `Stream` @@ -459,7 +471,7 @@ export const changes = ( * * **Example** (Streaming changes) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Stream, TxSubscriptionRef } from "effect" * * const program = Effect.gen(function*() { @@ -470,8 +482,10 @@ export const changes = ( * const values = yield* Stream.runCollect( * TxSubscriptionRef.changesStream(ref).pipe(Stream.take(1)) * ) - * console.log(values) // [2] + * return Array.from(values) * }) + * + * await Effect.runPromise(program) // => [2] * ``` * * @see {@link changes} for subscribing through a transactional queue @@ -500,14 +514,11 @@ export const changesStream = (self: TxSubscriptionRef): Stream.Stream false * ``` * * @see {@link make} for creating a `TxSubscriptionRef` diff --git a/repos/effect/packages/effect/src/Types.ts b/repos/effect/packages/effect/src/Types.ts index 94abd3a8f5..a403f5c484 100644 --- a/repos/effect/packages/effect/src/Types.ts +++ b/repos/effect/packages/effect/src/Types.ts @@ -10,7 +10,7 @@ */ /** - * @category tuples + * @category utility types * @since 2.0.0 */ type TupleOf_> = `${N}` extends `-${number}` ? never @@ -33,7 +33,7 @@ type TupleOf_> = `${N}` extends `- * * **Example** (Checking fixed-length tuples) * - * ```ts + * ```ts import.meta.vitest * import type { Types } from "effect" * * // Exactly 3 numbers @@ -48,7 +48,7 @@ type TupleOf_> = `${N}` extends `- * * @see {@link TupleOfAtLeast} * - * @category tuples + * @category utility types * @since 3.3.0 */ export type TupleOf = N extends N ? number extends N ? Array : TupleOf_ : never @@ -68,7 +68,7 @@ export type TupleOf = N extends N ? number extends N ? Arra * * **Example** (Checking minimum-length tuples) * - * ```ts + * ```ts import.meta.vitest * import type { Types } from "effect" * * // At least 2 strings @@ -81,7 +81,7 @@ export type TupleOf = N extends N ? number extends N ? Arra * * @see {@link TupleOf} * - * @category tuples + * @category utility types * @since 3.3.0 */ export type TupleOfAtLeast = [...TupleOf, ...Array] @@ -99,7 +99,7 @@ export type TupleOfAtLeast = [...TupleOf, ...Array * * **Example** (Extracting tags) * - * ```ts + * ```ts import.meta.vitest * import type { Types } from "effect" * * type MyError = @@ -109,12 +109,14 @@ export type TupleOfAtLeast = [...TupleOf, ...Array * * type Result = Types.Tags * // "NotFound" | "Timeout" + * + * const witness: Result = "NotFound" * ``` * * @see {@link ExtractTag} * @see {@link ExcludeTag} * - * @category types + * @category utility types * @since 2.0.0 */ export type Tags = E extends { readonly _tag: string } ? E["_tag"] : never @@ -133,7 +135,7 @@ export type Tags = E extends { readonly _tag: string } ? E["_tag"] : never * * **Example** (Removing a variant) * - * ```ts + * ```ts import.meta.vitest * import type { Types } from "effect" * * type MyError = @@ -143,12 +145,14 @@ export type Tags = E extends { readonly _tag: string } ? E["_tag"] : never * * 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 types + * @category utility types * @since 2.0.0 */ export type ExcludeTag = Exclude @@ -167,7 +171,7 @@ export type ExcludeTag = Exclude * * **Example** (Extracting a variant) * - * ```ts + * ```ts import.meta.vitest * import type { Types } from "effect" * * type MyError = @@ -176,12 +180,14 @@ export type ExcludeTag = Exclude * * type TimeoutError = Types.ExtractTag * // { readonly _tag: "Timeout"; readonly ms: number } + * + * const witness: TimeoutError = { _tag: "Timeout", ms: 100 } * ``` * * @see {@link ExcludeTag} * @see {@link Tags} * - * @category types + * @category utility types * @since 2.0.0 */ export type ExtractTag = E extends { readonly _tag: infer T } ? K extends T ? E : never : never @@ -203,17 +209,19 @@ export type ExtractTag = E extends { readonly _tag: infer T * * **Example** (Converting a union to an intersection) * - * ```ts + * ```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 types + * @category utility types * @since 2.0.0 */ export type UnionToIntersection = (T extends any ? (x: T) => any : never) extends (x: infer R) => any ? R @@ -233,18 +241,20 @@ export type UnionToIntersection = (T extends any ? (x: T) => any : never) ext * * **Example** (Simplifying an intersection) * - * ```ts + * ```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 types + * @category utility types * @since 2.0.0 */ export type Simplify = { @@ -266,7 +276,7 @@ export type Simplify = { * * **Example** (Checking type equality) * - * ```ts + * ```ts import.meta.vitest * import type { Types } from "effect" * * type Yes = Types.Equals<{ a: number }, { a: number }> // true @@ -276,7 +286,7 @@ export type Simplify = { * * @see {@link EqualsWith} * - * @category models + * @category utility types * @since 2.0.0 */ export type Equals = (() => T extends X ? 1 : 2) extends < @@ -297,7 +307,7 @@ export type Equals = (() => T extends X ? 1 : 2) extends < * * **Example** (Choosing a conditional type based on equality) * - * ```ts + * ```ts import.meta.vitest * import type { Types } from "effect" * * type R1 = Types.EqualsWith // "same" @@ -306,7 +316,7 @@ export type Equals = (() => T extends X ? 1 : 2) extends < * * @see {@link Equals} * - * @category models + * @category utility types * @since 3.15.0 */ export type EqualsWith = (() => T extends A ? 1 : 2) extends (() => T extends B ? 1 : 2) ? Y : N @@ -326,14 +336,14 @@ export type EqualsWith = (() => T extends A ? 1 : 2) extends ( * * **Example** (Checking key presence) * - * ```ts + * ```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 models + * @category utility types * @since 2.0.0 */ export type Has = (Key extends infer K ? K extends keyof A ? true : never : never) extends never @@ -354,7 +364,7 @@ export type Has = (Key extends infer K ? K extends keyof * * **Example** (Merging with left bias) * - * ```ts + * ```ts import.meta.vitest * import type { Types } from "effect" * * type Result = Types.MergeLeft< @@ -362,12 +372,14 @@ export type Has = (Key extends infer K ? K extends keyof * { 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 models + * @category utility types * @since 2.0.0 */ export type MergeLeft = MergeRight @@ -386,7 +398,7 @@ export type MergeLeft = MergeRight * * **Example** (Right-biased merge) * - * ```ts + * ```ts import.meta.vitest * import type { Types } from "effect" * * type Result = Types.MergeRight< @@ -394,12 +406,14 @@ export type MergeLeft = MergeRight * { 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 models + * @category utility types * @since 2.0.0 */ export type MergeRight = Simplify< @@ -421,23 +435,21 @@ export type MergeRight = Simplify< * * - `number` — run at most N effects concurrently. * - `"unbounded"` — run all effects concurrently with no limit. - * - `"inherit"` — inherit the concurrency from the surrounding context. * * **Example** (Setting concurrency values) * - * ```ts + * ```ts import.meta.vitest * import type { Types } from "effect" * * const sequential: Types.Concurrency = 1 * const limited: Types.Concurrency = 5 * const unbounded: Types.Concurrency = "unbounded" - * const inherit: Types.Concurrency = "inherit" * ``` * * @category models * @since 2.0.0 */ -export type Concurrency = number | "unbounded" | "inherit" +export type Concurrency = number | "unbounded" /** * Removes `readonly` from all properties of `T`. Supports arrays, tuples, @@ -453,7 +465,7 @@ export type Concurrency = number | "unbounded" | "inherit" * * **Example** (Converting shallowly to mutable types) * - * ```ts + * ```ts import.meta.vitest * import type { Types } from "effect" * * type Obj = Types.Mutable<{ @@ -468,11 +480,14 @@ export type Concurrency = number | "unbounded" | "inherit" * * type Tup = Types.Mutable * // [string, number] + * + * const tuple: Tup = ["value", 1] + * tuple[1] = 2 * ``` * * @see {@link DeepMutable} * - * @category types + * @category utility types * @since 2.0.0 */ export type Mutable = { @@ -494,7 +509,7 @@ export type Mutable = { * * **Example** (Converting deeply to mutable types) * - * ```ts + * ```ts import.meta.vitest * import type { Types } from "effect" * * type Deep = Types.DeepMutable<{ @@ -502,11 +517,14 @@ export type Mutable = { * 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 types + * @category utility types * @since 3.1.0 */ export type DeepMutable = T extends ReadonlyMap ? Map, DeepMutable> @@ -529,16 +547,18 @@ export type DeepMutable = T extends ReadonlyMap ? Map(value: T, fallback: Types.NoInfer): T + * 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 models + * @category utility types * @since 2.0.0 */ export type NoInfer = [A][A extends any ? 0 : never] @@ -559,20 +579,22 @@ export type NoInfer = [A][A extends any ? 0 : never] * * **Example** (Defining an invariant phantom type) * - * ```ts + * ```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 models + * @category utility types * @since 2.0.0 */ export type Invariant = (_: A) => A @@ -596,16 +618,18 @@ export declare namespace Invariant { * * **Example** (Extracting the inner type) * - * ```ts + * ```ts import.meta.vitest * import type { Types } from "effect" * * type Inner = Types.Invariant.Type> * // number + * + * const witness: Inner = 1 * ``` * * @see {@link Invariant} * - * @category models + * @category utility types * @since 3.9.0 */ export type Type = A extends Invariant ? U : never @@ -627,20 +651,22 @@ export declare namespace Invariant { * * **Example** (Defining a covariant phantom type) * - * ```ts + * ```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 models + * @category utility types * @since 2.0.0 */ export type Covariant = (_: never) => A @@ -664,16 +690,18 @@ export declare namespace Covariant { * * **Example** (Extracting the inner type) * - * ```ts + * ```ts import.meta.vitest * import type { Types } from "effect" * * type Inner = Types.Covariant.Type> * // string + * + * const witness: Inner = "value" * ``` * * @see {@link Covariant} * - * @category models + * @category utility types * @since 3.9.0 */ export type Type = A extends Covariant ? U : never @@ -695,20 +723,25 @@ export declare namespace Covariant { * * **Example** (Defining a contravariant phantom type) * - * ```ts + * ```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 models + * @category utility types * @since 2.0.0 */ export type Contravariant = (_: A) => void @@ -732,16 +765,18 @@ export declare namespace Contravariant { * * **Example** (Extracting the inner type) * - * ```ts + * ```ts import.meta.vitest * import type { Types } from "effect" * * type Inner = Types.Contravariant.Type> * // string + * + * const witness: Inner = "value" * ``` * * @see {@link Contravariant} * - * @category models + * @category utility types * @since 3.9.0 */ export type Type = A extends Contravariant ? U : never @@ -755,7 +790,7 @@ export declare namespace Contravariant { * * Use to erase an empty object type from an API result or parameter position. * - * @category types + * @category utility types * @since 3.19.20 */ export type VoidIfEmpty = keyof S extends never ? void : S @@ -773,14 +808,16 @@ export type VoidIfEmpty = keyof S extends never ? void : S * * **Example** (Filtering out functions) * - * ```ts + * ```ts import.meta.vitest * import type { Types } from "effect" * * type Result = Types.NotFunction void) | number> * // string | number + * + * const witness: Result = "value" * ``` * - * @category types + * @category utility types * @since 2.0.0 */ export type NotFunction = T extends Function ? never : T @@ -798,7 +835,7 @@ export type NotFunction = T extends Function ? never : T * * **Example** (Preventing extra properties) * - * ```ts + * ```ts import.meta.vitest * import type { Types } from "effect" * * type Expected = { a: number; b: string } @@ -806,9 +843,11 @@ export type NotFunction = T extends Function ? never : T * * type Result = Types.NoExcessProperties * // { a: number; b: string; readonly c: never } + * + * const accepted: Types.NoExcessProperties = { a: 1, b: "value" } * ``` * - * @category types + * @category utility types * @since 3.9.0 */ export type NoExcessProperties = T & Readonly, never>> @@ -828,7 +867,7 @@ export type NoExcessProperties = T & Readonly // true @@ -882,7 +921,7 @@ export interface unhandled { * * @see {@link UnionToIntersection} * - * @category types + * @category utility types * @since 4.0.0 */ export type IsUnion = [T] extends [UnionToIntersection] ? false : true @@ -901,7 +940,7 @@ export type IsUnion = [T] extends [UnionToIntersection] ? false : true * * **Example** (Extracting reason types) * - * ```ts + * ```ts import.meta.vitest * import type { Types } from "effect" * * type RateLimitError = { readonly _tag: "RateLimitError"; readonly retryAfter: number } @@ -910,13 +949,15 @@ export type IsUnion = [T] extends [UnionToIntersection] ? false : true * * type Reasons = Types.ReasonOf * // RateLimitError | QuotaError + * + * const witness: Reasons = { _tag: "QuotaError", limit: 10 } * ``` * * @see {@link ReasonTags} * @see {@link ExtractReason} * @see {@link ExcludeReason} * - * @category types + * @category utility types * @since 4.0.0 */ export type ReasonOf = E extends { readonly reason: infer R } ? R : never @@ -936,7 +977,7 @@ export type ReasonOf = E extends { readonly reason: infer R } ? R : never * * **Example** (Getting reason tags) * - * ```ts + * ```ts import.meta.vitest * import type { Types } from "effect" * * type RateLimitError = { readonly _tag: "RateLimitError"; readonly retryAfter: number } @@ -945,12 +986,14 @@ export type ReasonOf = E extends { readonly reason: infer R } ? R : never * * type Result = Types.ReasonTags * // "RateLimitError" | "QuotaError" + * + * const witness: Result = "RateLimitError" * ``` * * @see {@link ReasonOf} * @see {@link ExtractReason} * - * @category types + * @category utility types * @since 4.0.0 */ export type ReasonTags = E extends { readonly reason: { readonly _tag: string } } ? E["reason"]["_tag"] @@ -971,7 +1014,7 @@ export type ReasonTags = E extends { readonly reason: { readonly _tag: string * * **Example** (Extracting a reason variant) * - * ```ts + * ```ts import.meta.vitest * import type { Types } from "effect" * * type RateLimitError = { readonly _tag: "RateLimitError"; readonly retryAfter: number } @@ -980,13 +1023,15 @@ export type ReasonTags = E extends { readonly reason: { readonly _tag: string * * 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 types + * @category utility types * @since 4.0.0 */ export type ExtractReason = E extends { readonly reason: infer R } @@ -1009,7 +1054,7 @@ export type ExtractReason = E extends { readonly reason: in * * **Example** (Narrowing a reason variant) * - * ```ts + * ```ts import.meta.vitest * import type { Types } from "effect" * * type RateLimitError = { readonly _tag: "RateLimitError"; readonly retryAfter: number } @@ -1018,13 +1063,18 @@ export type ExtractReason = E extends { readonly reason: in * * 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 types + * @category utility types * @since 4.0.0 */ export type NarrowReason = E extends { readonly reason: infer R } @@ -1047,7 +1097,7 @@ export type NarrowReason = E extends { readonly reason: inf * * **Example** (Omitting a reason variant) * - * ```ts + * ```ts import.meta.vitest * import type { Types } from "effect" * * type RateLimitError = { readonly _tag: "RateLimitError"; readonly retryAfter: number } @@ -1056,6 +1106,11 @@ export type NarrowReason = E extends { readonly reason: inf * * 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} @@ -1063,7 +1118,7 @@ export type NarrowReason = E extends { readonly reason: inf * @see {@link ReasonOf} * @see {@link ReasonTags} * - * @category types + * @category utility types * @since 4.0.0 */ export type OmitReason = E extends { readonly reason: infer R } @@ -1086,7 +1141,7 @@ export type OmitReason = E extends { readonly reason: infer * * **Example** (Excluding a reason variant) * - * ```ts + * ```ts import.meta.vitest * import type { Types } from "effect" * * type RateLimitError = { readonly _tag: "RateLimitError"; readonly retryAfter: number } @@ -1095,13 +1150,15 @@ export type OmitReason = E extends { readonly reason: infer * * 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 types + * @category utility types * @since 4.0.0 */ export type ExcludeReason = E extends { readonly reason: infer R } @@ -1115,7 +1172,7 @@ export type ExcludeReason = E extends { readonly reason: in * * Use to derive the keys whose properties must be present on an object type. * - * @category types + * @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/Unify.ts b/repos/effect/packages/effect/src/Unify.ts index 080b1bec91..a2e2809857 100644 --- a/repos/effect/packages/effect/src/Unify.ts +++ b/repos/effect/packages/effect/src/Unify.ts @@ -178,7 +178,7 @@ type FilterOut = A extends any ? typeSymbol extends keyof A ? never : A : nev * * **Example** (Unifying protocol types) * - * ```ts + * ```ts import.meta.vitest * import type { Unify } from "effect" * * // Example of types that can be unified @@ -196,7 +196,8 @@ type FilterOut = A extends any ? typeSymbol extends keyof A ? never : A : nev * * // Unify automatically handles the union * type Unified = Unify.Unify - * // Results in a properly unified type + * + * const witness: Unified = "value" * ``` * * @see {@link unify} for applying this normalization to a value or function @@ -243,27 +244,24 @@ export type Unify = Values< * * **Example** (Unifying values and function results) * - * ```ts + * ```ts import.meta.vitest * import { Unify } from "effect" * * // Unify a simple value - * const unifiedValue = Unify.unify("hello") + * const unifiedValue = Unify.unify("hello") // => "hello" * // Type: string * * // Unify a function result - * const createUnifiableValue = () => ({ - * value: "test", - * [Unify.typeSymbol]: "string" as const, - * [Unify.unifySymbol]: { String: () => "test" as const } - * }) + * const createValue = () => ({ value: "test" }) * - * const unifiedFunction = Unify.unify(createUnifiableValue) - * // The result will be properly unified + * 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 diff --git a/repos/effect/packages/effect/src/Utils.ts b/repos/effect/packages/effect/src/Utils.ts index 0886e5f6de..448c79b164 100644 --- a/repos/effect/packages/effect/src/Utils.ts +++ b/repos/effect/packages/effect/src/Utils.ts @@ -31,18 +31,14 @@ import type * as Types from "./Types.ts" * * **Example** (Yielding a wrapped value in a generator) * - * ```ts + * ```ts import.meta.vitest * import { Utils } from "effect" * * const gen = new Utils.SingleShotGen("hello") * - * // First call yields the wrapped value - * console.log(gen.next(0)) - * // { value: "hello", done: false } + * gen.next(0) // => { value: "hello", done: false } * - * // Second call signals completion with the provided value - * console.log(gen.next(42)) - * // { value: 42, done: true } + * gen.next(42) // => { value: 42, done: true } * ``` * * @see {@link Gen} for the type-level signature that relies on `SingleShotGen` @@ -113,15 +109,21 @@ export class SingleShotGen implements IterableIterator { * * **Example** (Declaring variance for a TypeLambda) * - * ```ts + * ```ts import.meta.vitest * import type { Option, Utils } from "effect" * - * declare const variance: Utils.Variance< + * const variance: Utils.Variance< * Option.OptionTypeLambda, - * never, - * never, - * never - * > + * 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` @@ -152,10 +154,15 @@ export interface Variance { * * **Example** (Typing a gen function for Option) * - * ```ts - * import type { Option, Utils } from "effect" + * ```ts import.meta.vitest + * import { Option } from "effect" + * import type { Utils } from "effect" * - * declare const gen: Utils.Gen + * 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 diff --git a/repos/effect/packages/effect/src/index.ts b/repos/effect/packages/effect/src/index.ts index 0fae70acc8..d1774a4d05 100644 --- a/repos/effect/packages/effect/src/index.ts +++ b/repos/effect/packages/effect/src/index.ts @@ -296,6 +296,11 @@ export * as Layer from "./Layer.ts" */ export * as LayerMap from "./LayerMap.ts" +/** + * @since 4.0.0 + */ +export * as LayerRef from "./LayerRef.ts" + /** * @since 2.0.0 */ @@ -521,6 +526,11 @@ export * as Schema from "./Schema.ts" */ export * as SchemaAST from "./SchemaAST.ts" +/** + * @since 4.0.0 + */ +export * as SchemaError from "./SchemaError.ts" + /** * @since 4.0.0 */ @@ -546,11 +556,6 @@ export * as SchemaRepresentation from "./SchemaRepresentation.ts" */ export * as SchemaTransformation from "./SchemaTransformation.ts" -/** - * @since 4.0.0 - */ -export * as SchemaUtils from "./SchemaUtils.ts" - /** * @since 2.0.0 */ diff --git a/repos/effect/packages/effect/src/internal/core.ts b/repos/effect/packages/effect/src/internal/core.ts index f0a8c43168..021d72dd1f 100644 --- a/repos/effect/packages/effect/src/internal/core.ts +++ b/repos/effect/packages/effect/src/internal/core.ts @@ -13,6 +13,7 @@ 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 @@ -87,7 +88,7 @@ export const StructuralProto = { 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]])) { + if (selfKeys[i] !== thatKeys[i] || !Equal.equals(this[selfKeys[i]], that[selfKeys[i]])) { return false } } @@ -238,7 +239,7 @@ export abstract class ReasonBase implements Cause.Cause.Reas } /** @internal */ -export const constEmptyAnnotations = new Map() +export const constEmptyAnnotations: ReadonlyMap = new Map() /** @internal */ export class Fail extends ReasonBase<"Fail"> implements Cause.Fail { @@ -470,12 +471,12 @@ export const makeExit = < ) => Primitive | Yield }): Fn => { const Proto = { - ...makePrimitiveProto(options), [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])})` }, @@ -541,7 +542,7 @@ export const exitFailCause: (cause: Cause.Cause) => Exit.Exit = } return cont ? cont[contE](cause, fiber, annotated ? undefined : this) - : fiber.yieldWith(annotated ? this : exitFailCause(cause)) + : fiber.yieldWith(annotated ? exitFailCause(cause) : this) } }) @@ -587,10 +588,10 @@ export const Error: new = {}>( ) => Cause.YieldableError & Readonly = (function() { const plainArgsSymbol = Symbol.for("effect/Data/Error/plainArgs") return class Base extends YieldableError { - constructor(args: any) { + constructor(args: Record | undefined) { super(args?.message, args?.cause ? { cause: args.cause } : undefined) if (args) { - Object.assign(this, args) + InternalRecord.assignProperties(this, args) // @effect-diagnostics-next-line floatingEffect:off Object.defineProperty(this, plainArgsSymbol, { value: args, diff --git a/repos/effect/packages/effect/src/internal/dateTime.ts b/repos/effect/packages/effect/src/internal/dateTime.ts index 63a9c2cd8b..650b1230b7 100644 --- a/repos/effect/packages/effect/src/internal/dateTime.ts +++ b/repos/effect/packages/effect/src/internal/dateTime.ts @@ -221,7 +221,7 @@ export const makeUnsafe = (input: A): DateTim return fromDateUnsafe(input) as DateTime.DateTime.PreserveZone } else if (typeof input === "object") { if ("epochMilliseconds" in input) { - return makeUtc(input.epochMilliseconds) as DateTime.DateTime.PreserveZone + return fromDateUnsafe(new Date(input.epochMilliseconds)) as DateTime.DateTime.PreserveZone } const date = new Date(0) setPartsDate(date, input) @@ -600,6 +600,12 @@ export const zonedOffsetIso = (self: DateTime.Zoned): string => offsetToString(z /** @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) => { diff --git a/repos/effect/packages/effect/src/internal/effect.ts b/repos/effect/packages/effect/src/internal/effect.ts index 79b8c50286..370b36ec22 100644 --- a/repos/effect/packages/effect/src/internal/effect.ts +++ b/repos/effect/packages/effect/src/internal/effect.ts @@ -83,8 +83,8 @@ import { } from "./core.ts" import * as doNotation from "./doNotation.ts" import * as InternalMetric from "./metric.ts" +import * as InternalRecord from "./record.ts" import { - CurrentConcurrency, CurrentErrorReporters, CurrentLogAnnotations, CurrentLogLevel, @@ -98,7 +98,6 @@ import { } from "./references.ts" import { getStackTraceLimit, setStackTraceLimit } from "./stackTraceLimit.ts" import { addSpanStackTrace, makeStackCleaner } from "./tracer.ts" -import { version } from "./version.ts" // ---------------------------------------------------------------------------- // Cause @@ -269,7 +268,7 @@ export const causeMap: { const failures = self.reasons.map((failure) => { if (isFailReason(failure)) { hasFail = true - return new Fail(f(failure.error)) + return new Fail(f(failure.error), failure.annotations) } return failure }) @@ -310,7 +309,9 @@ export const causeSquash = (self: Cause.Cause): unknown => { } /** @internal */ -export const causePrettyErrors = (self: Cause.Cause): Array => { +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 @@ -326,7 +327,8 @@ export const causePrettyErrors = (self: Cause.Cause): Array => { errors.push( causePrettyError( failure._tag === "Die" ? failure.defect : failure.error as any, - failure.annotations + failure.annotations, + options ) ) } @@ -337,7 +339,7 @@ export const causePrettyErrors = (self: Cause.Cause): Array => { 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)) + errors.push(causePrettyError(error, interrupts[0].annotations, options)) } setStackTraceLimit(prevStackLimit) @@ -347,7 +349,10 @@ export const causePrettyErrors = (self: Cause.Cause): Array => { /** @internal */ export const causePrettyError = ( original: Record | Error, - annotations?: ReadonlyMap + annotations?: ReadonlyMap, + options?: { + readonly includeCauseInStack?: boolean | undefined + } ): Error => { const kind = typeof original let error: Error @@ -364,6 +369,9 @@ export const causePrettyError = ( 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] @@ -461,8 +469,7 @@ const currentStackTrace = (frame: StackFrame): string => { export const causePretty = (cause: Cause.Cause): string => causePrettyErrors(cause).map(renderPrettyError).join("\n") -/** @internal */ -export const renderPrettyError = (e: Error): string | undefined => +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) => { @@ -482,7 +489,7 @@ const renderErrorCause = (cause: Error, prefix: string) => { // ---------------------------------------------------------------------------- /** @internal */ -export const FiberTypeId = `~effect/Fiber/${version}` as const +export const FiberTypeId = "~effect/Fiber" as const const fiberVariance = { _A: identity, @@ -504,7 +511,6 @@ export class FiberImpl implements Fiber.Fiber { this.setContext(context) this.id = ++fiberIdStore.id this.currentOpCount = 0 - this.currentLoopCount = 0 this.interruptible = interruptible this._stack = [] this._observers = [] @@ -512,6 +518,8 @@ export class FiberImpl implements Fiber.Fiber { this._children = undefined this._interruptedCause = undefined this._yielded = undefined + this._running = false + this._deferredInterrupt = false this.runtimeMetrics?.recordFiberStart(this.context) } @@ -520,14 +528,14 @@ export class FiberImpl implements Fiber.Fiber { readonly id: number interruptible: boolean currentOpCount: number - currentLoopCount: number readonly _stack: Array readonly _observers: Array<(exit: Exit.Exit) => void> _exit: Exit.Exit | undefined - _currentExit: 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 @@ -547,7 +555,7 @@ export class FiberImpl implements Fiber.Fiber { } getRef(ref: Context.Reference): X { - return Context.getReferenceUnsafe(this.context, ref) + return Context.get(this.context, ref) } addObserver(cb: (exit: Exit.Exit) => void): () => void { if (this._exit) { @@ -577,7 +585,11 @@ export class FiberImpl implements Fiber.Fiber { ? causeCombine(this._interruptedCause, cause) : cause if (this.interruptible) { - this.evaluate(failCause(this._interruptedCause) as any) + if (this._running) { + this._deferredInterrupt = true + } else { + this.evaluate(failCause(this._interruptedCause) as any) + } } } pollUnsafe(): Exit.Exit | undefined { @@ -609,16 +621,24 @@ export class FiberImpl implements Fiber.Fiber { 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 - const currentLoop = ++this.currentLoopCount try { while (true) { + if (this._deferredInterrupt) { + this._deferredInterrupt = false + current = failCause(this._interruptedCause!) as any + } this.currentOpCount++ if ( !yielding && @@ -632,14 +652,16 @@ export class FiberImpl implements Fiber.Fiber { current = this.currentTracerContext ? this.currentTracerContext(current as any, this) : (current as any)[evaluate](this) - if (currentLoop !== this.currentLoopCount) { - // another effect has taken over the loop, - return Yield - } else if (current === Yield) { + 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 } @@ -650,6 +672,7 @@ export class FiberImpl implements Fiber.Fiber { } return this.runLoop(exitDie(error) as any) } finally { + this._running = prevRunning ;(globalThis as any)[currentFiberTypeId] = prevFiber } } @@ -657,6 +680,10 @@ export class FiberImpl implements Fiber.Fiber { | (Primitive & Record Primitive>) | undefined { + if (this._deferredInterrupt) { + this._deferredInterrupt = false + return deferredInterruptCont + } while (true) { const op = this._stack.pop() if (!op) return undefined @@ -679,20 +706,26 @@ export class FiberImpl implements Fiber.Fiber { 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 } - this.currentSpan = context.mapUnsafe.get(Tracer.ParentSpanKey) + // 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 = context.mapUnsafe.get(CurrentStackFrame.key) + this.currentStackFrame = this.getRef(CurrentStackFrame) this.maxOpsBeforeYield = this.getRef(Scheduler.MaxOpsBeforeYield) this.currentPreventYield = this.getRef(Scheduler.PreventSchedulerYield) - this.runtimeMetrics = context.mapUnsafe.get(InternalMetric.FiberRuntimeMetricsKey) - const currentTracer = context.mapUnsafe.get(Tracer.TracerKey) + 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 { @@ -700,6 +733,15 @@ export class FiberImpl implements Fiber.Fiber { } } +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) @@ -709,7 +751,7 @@ const fiberMiddleware = { const fiberStackAnnotations = (fiber: Fiber.Fiber) => { if (!fiber.currentStackFrame) return undefined const annotations = new Map() - annotations.set(CauseStackTrace.key, fiber.currentStackFrame) + annotations.set(InterruptorStackTrace.key, fiber.currentStackFrame) return Context.makeUnsafe(annotations) } @@ -780,7 +822,7 @@ export const fiberJoin = (self: Fiber.Fiber): Effect.Effect => /** @internal */ export const fiberJoinAll = >>(self: A): Effect.Effect< Arr.ReadonlyArray.With> ? _A : never>, - A extends Fiber.Fiber ? _E : never + A extends Iterable> ? _E : never > => callback((resume) => { const fibers = Array.from(self) @@ -804,6 +846,10 @@ export const fiberJoinAll = >>(self: A) } })) } + return sync(() => { + failed = true + cancels.forEach((cancel) => cancel()) + }) }) /** @internal */ @@ -843,10 +889,12 @@ export const fiberInterruptAll = >>( ): 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(fibers)) + return asVoid(fiberAwaitAll(fiberArr)) }) /** @internal */ @@ -859,8 +907,12 @@ export const fiberInterruptAllAs: { ): Effect.Effect => withFiber((parent) => { const annotations = fiberStackAnnotations(parent) - for (const fiber of fibers) fiber.interruptUnsafe(fiberId, annotations) - return asVoid(fiberAwaitAll(fibers)) + const fiberArr = Arr.empty>() + for (const fiber of fibers) { + fiber.interruptUnsafe(fiberId, annotations) + fiberArr.push(fiber) + } + return asVoid(fiberAwaitAll(fiberArr)) })) /** @internal */ @@ -2064,6 +2116,32 @@ export const updateService: { }) ) +/** @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)) @@ -2073,6 +2151,25 @@ 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: { ( @@ -2156,17 +2253,6 @@ export const provideServiceEffect: { flatMap(acquire, (implementation) => provideService(self, service, implementation)) ) -/** @internal */ -export const withConcurrency: { - ( - concurrency: "unbounded" | number - ): (self: Effect.Effect) => Effect.Effect - ( - self: Effect.Effect, - concurrency: "unbounded" | number - ): Effect.Effect -} = provideService(CurrentConcurrency) - // ---------------------------------------------------------------------------- // zipping // ---------------------------------------------------------------------------- @@ -3667,8 +3753,8 @@ export const timed = ( self: Effect.Effect ): Effect.Effect<[duration: Duration.Duration, result: A], E, R> => clockWith((clock) => { - const start = clock.currentTimeNanosUnsafe() - return map(self, (a) => [Duration.nanos(clock.currentTimeNanosUnsafe() - start), a]) + const start = clock.monotonicTimeNanosUnsafe() + return map(self, (a) => [Duration.nanos(clock.monotonicTimeNanosUnsafe() - start), a]) }) // ---------------------------------------------------------------------------- @@ -4199,16 +4285,19 @@ const setInterruptible: (interruptible: boolean) => Primitive = makePrimitive({ 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 - fiber.interruptible = true - fiber._stack.push(setInterruptibleFalse) - if (fiber._interruptedCause) return failCause(fiber._interruptedCause) - return self + return setFiberInterruptible(fiber) ?? self }) /** @internal */ @@ -4236,9 +4325,9 @@ export const interruptibleMask = ( ): Effect.Effect => withFiber((fiber) => { if (fiber.interruptible) return f(identity) - fiber.interruptible = true - fiber._stack.push(setInterruptibleFalse) - return f(uninterruptible) + const interrupted = setFiberInterruptible(fiber) + const effect = f(uninterruptible) + return interrupted ?? effect }) /** @internal */ @@ -4284,7 +4373,7 @@ export const all = < Object.entries(arg), ([key, effect]) => map(options?.mode === "result" ? result(effect) : effect, (value) => { - out[key] = value + InternalRecord.assignProperty(out, key, value) }), { discard: true, @@ -4320,6 +4409,44 @@ export const partition: { ) ) +/** @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: { ( @@ -4508,10 +4635,8 @@ export const forEach: { readonly discard?: boolean | undefined } ): Effect.Effect => - withFiber((parent) => { - const concurrencyOption = options?.concurrency === "inherit" - ? parent.getRef(CurrentConcurrency) - : (options?.concurrency ?? 1) + suspend(() => { + const concurrencyOption = options?.concurrency ?? 1 const concurrency = concurrencyOption === "unbounded" ? Number.POSITIVE_INFINITY : Math.max(1, concurrencyOption) @@ -4558,33 +4683,55 @@ const forEachSequential = ( ) }) +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?: { - readonly concurrency?: number | undefined - readonly start?: number | undefined - readonly end?: number | undefined - } + 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: { - readonly concurrency?: number | undefined - readonly start?: number | undefined - readonly end?: number | undefined - } | undefined + opts: IterateEagerOptions | undefined ): Effect.Effect | undefined => { - let index = opts?.start ?? 0 + 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 && concurrency > 1 let done = false let parentFiber: Fiber.Fiber | undefined let fibers: Set> | undefined @@ -4592,6 +4739,32 @@ const iterateEagerImpl = (options: { 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 @@ -4601,24 +4774,22 @@ const iterateEagerImpl = (options: { // fast case (already an exit) if (effectIsExit(eff)) { - terminal = step(state, item, eff, index) + terminal = runStep(item, eff, index) if (terminal) break - // Use flatMap for concurrency of 1 - } else if (concurrency === 1) { - return flatMap(exit(eff), (exit) => { - terminal = step(state, item, exit, index) - index++ - return terminal ?? go() ?? void_ - }) - // We have an effect, so enter "async" mode } else if (!parentFiber) { return callback((cb) => { parentFiber = getCurrentFiber()! + fibers = new Set() effect = eff resume = cb - const result = go() + let result: Effect.Effect | undefined + try { + result = go() + } catch (error) { + return cb(failDefect(error)) + } if (result) return cb(result) return suspend(() => { terminal = exitVoid @@ -4634,49 +4805,52 @@ const iterateEagerImpl = (options: { const fiber = forkUnsafe(parentFiber, eff, true, true, "inherit") if (fiber._exit) { - terminal = step(state, item, fiber._exit, index) + terminal = runStep(item, fiber._exit, index) if (terminal) break continue } // Add the fiber to the Set - if (fibers) fibers.add(fiber) - else fibers = new Set([fiber]) + fibers!.add(fiber) const currentIndex = index fiber.addObserver((exit) => { fibers!.delete(fiber) - 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])) + 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() + } } - } else { - const result = step(state, 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_) + 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 + if (fibers!.size < concurrency) continue paused = true index++ return @@ -4714,11 +4888,7 @@ export const iterateEager = (): (options: { }) => ( initialState: S, items: ReadonlyArray, - options?: { - readonly concurrency?: number | undefined - readonly start?: number | undefined - readonly end?: number | undefined - } + options?: IterateEagerOptions ) => Effect.Effect | undefined => iterateEagerImpl const forEachConcurrent = iterateEagerImpl({ @@ -5053,16 +5223,17 @@ export const forkUnsafe = ( daemon = false, uninterruptible: boolean | "inherit" = false ): FiberImpl => { - const interruptible = uninterruptible === "inherit" ? parent.interruptible : !uninterruptible - const child = new FiberImpl(parent.context, interruptible) + 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 { - parent.currentDispatcher.scheduleTask(() => child.evaluate(effect as any), 0) + parentRuntime.currentDispatcher.scheduleTask(() => child.evaluate(effect as any), 0) } if (!daemon && !child._exit) { - parent.children().add(child) - child.addObserver(() => parent._children!.delete(child)) + parentRuntime.children().add(child) + child.addObserver(() => parentRuntime._children!.delete(child)) } return child } @@ -5099,7 +5270,7 @@ export const awaitAllChildren = ( self: Effect.Effect ): Effect.Effect => withFiber((fiber) => { - const initialChildren = fiber._children && Arr.fromIterable(fiber._children) + const initialChildren = fiber._children && new Set(fiber._children) return onExit( self, (_) => { @@ -5109,7 +5280,7 @@ export const awaitAllChildren = ( } else if (initialChildren) { children = Iterable.filter( children, - (child: FiberImpl) => !initialChildren.includes(child) + (child: FiberImpl) => !initialChildren.has(child) ) as Set> } return asVoid(fiberAwaitAll(children)) @@ -5322,9 +5493,9 @@ export const runSyncExitWith = (context: Context.Context) => { return (effect: Effect.Effect): Exit.Exit => { if (effectIsExit(effect)) return effect const scheduler = new Scheduler.MixedScheduler("sync") - const fiber = runFork(effect, { scheduler }) - fiber.currentDispatcher?.flush() - return (fiber as FiberImpl)._exit ?? exitDie(new AsyncFiberError(fiber)) + const fiber = runFork(effect, { scheduler }) as FiberImpl + fiber._dispatcher?.flush() + return fiber._exit ?? exitDie(new AsyncFiberError(fiber)) } } @@ -5351,7 +5522,7 @@ const succeedFalse = succeed(false) class Latch implements _Latch.Latch { waiters: Array<(_: Effect.Effect) => void> = [] - scheduled = false + scheduled: Array<(_: Effect.Effect) => void> | undefined = undefined private _isOpen: boolean constructor(isOpen: boolean) { @@ -5359,17 +5530,35 @@ class Latch implements _Latch.Latch { } private scheduleUnsafe(fiber: Fiber.Fiber) { - if (this.scheduled || this.waiters.length === 0) { + if (this.waiters.length === 0) { return succeedTrue } - this.scheduled = true - fiber.currentDispatcher.scheduleTask(this.flushWaiters, 0) + 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 flushWaiters = () => { - this.scheduled = false + 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) } @@ -5393,9 +5582,14 @@ class Latch implements _Latch.Latch { } this.waiters.push(resume) return sync(() => { - const index = this.waiters.indexOf(resume) + 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) + } } }) }) @@ -5668,11 +5862,8 @@ export const useSpan: { return withFiber((fiber) => { const span = makeSpanUnsafe(fiber, name, options) const clock = fiber.getRef(ClockRef) - return onExit(internalCall(() => evaluate(span)), (exit) => - sync(() => { - if (span.status._tag === "Ended") return - span.end(clock.currentTimeNanosUnsafe(), exit) - })) + const timingEnabled = fiber.getRef(TracerTimingEnabled) + return onExit(internalCall(() => evaluate(span)), (exit) => endSpan(span, exit, clock, timingEnabled)) }) } @@ -5747,11 +5938,11 @@ export const annotateSpans: { ...args: [Record] | [key: string, value: unknown] ): Effect.Effect => updateService(effect, TracerSpanAnnotations, (annotations) => { - const newAnnotations = { ...annotations } + const newAnnotations = args.length === 1 ? { ...annotations, ...args[0] } : { ...annotations } if (args.length === 1) { - Object.assign(newAnnotations, args[0]) + return newAnnotations } else { - newAnnotations[args[0]] = args[1] + InternalRecord.assignProperty(newAnnotations, args[0], args[1]) } return newAnnotations }) @@ -5804,41 +5995,68 @@ class ClockImpl implements Clock.Clock { } readonly currentTimeMillis: Effect.Effect = sync(() => this.currentTimeMillisUnsafe()) currentTimeNanosUnsafe(): bigint { - return processOrPerformanceNow() + 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 { - const millis = Duration.toMillis(duration) + 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) => { - if (millis > MAX_TIMER_MILLIS) return - const handle = setTimeout(() => resume(void_), millis) + 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 performanceNowNanos = (function() { - const bigint1e6 = BigInt(1_000_000) - if (typeof performance === "undefined" || typeof performance.now === "undefined") { - return () => BigInt(Date.now()) * bigint1e6 +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 origin: bigint + let previous = BigInt(0) return () => { - origin ??= (BigInt(Date.now()) * bigint1e6) - BigInt(Math.round(performance.now() * 1_000_000)) - return origin + BigInt(Math.round(performance.now() * 1_000_000)) + const current = BigInt(Date.now()) * nanosPerMilli + if (current > previous) { + previous = current + } + return previous } })() -const processOrPerformanceNow = (function() { - const processHrtime = - typeof process === "object" && "hrtime" in process && typeof process.hrtime.bigint === "function" ? - process.hrtime : - undefined - if (!processHrtime) { - return performanceNowNanos - } - const origin = (BigInt(Date.now()) * BigInt(1e6)) - processHrtime.bigint() - return () => origin + processHrtime.bigint() + +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 */ @@ -5855,6 +6073,9 @@ export const currentTimeMillis: Effect.Effect = clockWith((clock) => clo /** @internal */ export const currentTimeNanos: Effect.Effect = clockWith((clock) => clock.currentTimeNanos) +/** @internal */ +export const monotonicTimeNanos: Effect.Effect = clockWith((clock) => clock.monotonicTimeNanos) + // ---------------------------------------------------------------------------- // Errors // ---------------------------------------------------------------------------- @@ -6017,7 +6238,7 @@ export const annotateLogsScoped: { const next = { ...prev } for (let i = 0; i < entries.length; i++) { const [key, value] = entries[i] - next[key] = value + InternalRecord.assignProperty(next, key, value) } fiber.setContext(Context.add(fiber.context, CurrentLogAnnotations, next)) return scopeAddFinalizerExit(Context.getUnsafe(fiber.context, scopeTag), (_) => { @@ -6026,8 +6247,8 @@ export const annotateLogsScoped: { for (let i = 0; i < entries.length; i++) { const [key, value] = entries[i] if (current[key] !== value) continue - if (key in prev) { - next[key] = prev[key] + if (Object.hasOwn(prev, key)) { + InternalRecord.assignProperty(next, key, prev[key]) } else { delete next[key] } @@ -6191,10 +6412,10 @@ export const consolePretty = (options?: { }) => { // evaluated lazily so the module-level bundle stays free of `process` // property accesses, which bundlers must retain as possible side effects - const hasProcessStdout = typeof process === "object" && - process !== null && - typeof process.stdout === "object" && - process.stdout !== null + const process = (globalThis as { + readonly process?: { readonly stdout?: { readonly isTTY?: boolean } } + }).process + const hasProcessStdout = typeof process?.stdout === "object" && process.stdout !== null const processStdoutIsTTY = hasProcessStdout && process.stdout.isTTY === true const hasProcessStdoutOrDeno = hasProcessStdout || "Deno" in globalThis @@ -6212,7 +6433,7 @@ const prettyLoggerTty = (options: { readonly colors: boolean readonly formatDate: (date: Date) => string }) => { - const processIsBun = typeof process === "object" && "isBun" in process && process.isBun === true + const processIsBun = (globalThis as { readonly process?: { readonly isBun?: boolean } }).process?.isBun === true const color = options.colors ? withColor : withColorNoop return loggerMake( ({ cause, date, fiber, logLevel, message: message_ }) => { @@ -6371,7 +6592,7 @@ export const tracerLogger = loggerMake(({ cause, fiber, logLevel, if (span === undefined || span._tag === "ExternalSpan") return const attributes: Record = {} for (const [key, value] of Object.entries(annotations)) { - attributes[key] = value + InternalRecord.assignProperty(attributes, key, value) } attributes["effect.fiberId"] = fiber.id attributes["effect.logLevel"] = logLevel.toUpperCase() diff --git a/repos/effect/packages/effect/src/internal/executionPlan.ts b/repos/effect/packages/effect/src/internal/executionPlan.ts index 2ae8498b52..fb8cd6c16b 100644 --- a/repos/effect/packages/effect/src/internal/executionPlan.ts +++ b/repos/effect/packages/effect/src/internal/executionPlan.ts @@ -1,47 +1,132 @@ +import * as Duration from "../Duration.ts" import type { Effect } from "../Effect.ts" import * as Api from "../ExecutionPlan.ts" -import { dual } from "../Function.ts" +import type * as Exit from "../Exit.ts" +import { dual, identity } from "../Function.ts" import * as Result from "../Result.ts" import * as Schedule from "../Schedule.ts" +import { isEffect } from "./core.ts" import * as effect from "./effect.ts" import * as internalLayer from "./layer.ts" import * as internalSchedule from "./schedule.ts" +/** @internal */ +export interface AttemptState { + readonly attempt: number + readonly stepAttempt: number + readonly stepIndex: number + readonly startNanos: bigint +} + +/** @internal */ +export interface EventEmitter { + readonly begin: Effect + readonly end: (state: AttemptState, exit: Exit.Exit) => Effect +} + +/** @internal */ +export const makeEventEmitter = ( + onEvent: (event: Api.Event) => Effect, + currentMetadata: () => Api.Metadata +): EventEmitter => { + let lastStepIndex = -1 + let stepAttempt = 0 + const emit = (event: Api.Event) => effect.ignoreCause(onEvent(event)) + return { + begin: effect.clockWith((clock) => + effect.suspend(() => { + const meta = currentMetadata() + if (meta.stepIndex !== lastStepIndex) { + lastStepIndex = meta.stepIndex + stepAttempt = 0 + } + stepAttempt++ + const state: AttemptState = { + attempt: meta.attempt, + stepAttempt, + stepIndex: meta.stepIndex, + startNanos: clock.monotonicTimeNanosUnsafe() + } + return effect.as( + emit({ + _tag: "AttemptStart", + attempt: state.attempt, + stepAttempt: state.stepAttempt, + stepIndex: state.stepIndex + }), + state + ) + }) + ), + end: (state, exit) => + effect.clockWith((clock) => { + const duration = Duration.nanos(clock.monotonicTimeNanosUnsafe() - state.startNanos) + return emit( + exit._tag === "Success" + ? { + _tag: "AttemptSuccess", + attempt: state.attempt, + stepAttempt: state.stepAttempt, + stepIndex: state.stepIndex, + duration + } + : { + _tag: "AttemptFailure", + attempt: state.attempt, + stepAttempt: state.stepAttempt, + stepIndex: state.stepIndex, + duration, + cause: exit.cause + } + ) + }) + } +} + /** @internal */ export const withExecutionPlan: { - ( + ( plan: Api.ExecutionPlan<{ provides: Provides input: Input error: PlanE requirements: PlanR - }> + }>, + options?: { + readonly onEvent?: ((event: Api.Event) => Effect) | undefined + } ): (effect: Effect) => Effect< A, E | PlanE, - Exclude | PlanR + Exclude | PlanR | RX > - ( + ( effect: Effect, plan: Api.ExecutionPlan<{ provides: Provides input: Input error: PlanE requirements: PlanR - }> + }>, + options?: { + readonly onEvent?: ((event: Api.Event) => Effect) | undefined + } ): Effect< A, E | PlanE, - Exclude | PlanR + Exclude | PlanR | RX > -} = dual(2, ( +} = dual((args) => isEffect(args[0]), ( self: Effect, plan: Api.ExecutionPlan<{ provides: Provides input: Input error: PlanE requirements: PlanR - }> + }>, + options?: { + readonly onEvent?: ((event: Api.Event) => Effect) | undefined + } ) => effect.suspend(() => { let i = 0 @@ -59,13 +144,24 @@ export const withExecutionPlan: { return meta }) ) + const emitter = options?.onEvent === undefined + ? undefined + : makeEventEmitter(options.onEvent, () => meta) + const instrument: (attempt: Effect) => Effect = emitter === undefined + ? identity + : (attempt) => + effect.uninterruptibleMask((restore) => + effect.flatMap(emitter.begin, (state) => effect.onExit(restore(attempt), (exit) => emitter.end(state, exit))) + ) let result: Result.Result | undefined return effect.flatMap( effect.whileLoop({ while: () => i < plan.steps.length && (result === undefined || Result.isFailure(result)), body() { const step = plan.steps[i] - let nextEffect: Effect = provideMeta(internalLayer.provide(self, step.provide as any)) + let nextEffect: Effect = provideMeta( + instrument(internalLayer.provide(self, step.provide as any)) + ) if (result) { let attempted = false const wrapped = nextEffect diff --git a/repos/effect/packages/effect/src/internal/hashMap.ts b/repos/effect/packages/effect/src/internal/hashMap.ts index aa62c937e3..ad69331626 100644 --- a/repos/effect/packages/effect/src/internal/hashMap.ts +++ b/repos/effect/packages/effect/src/internal/hashMap.ts @@ -977,13 +977,8 @@ export const hasBy = dual< return false }) -/** @internal */ -export const set = dual< - (key: K, value: V) => (self: HashMap) => HashMap, - (self: HashMap, key: K, value: V) => HashMap ->(3, (self: HashMap, key: K, value: V): HashMap => { +const setHash = (self: HashMap, key: K, hash: number, value: V): HashMap => { const impl = self as HashMapImpl - const hash = Hash.hash(key) const added = { value: false } // Pass edit context: use current edit if editable, otherwise NaN (never matches any edit) @@ -1005,6 +1000,14 @@ export const set = dual< } return new HashMapImpl(false, impl._edit, newRoot, impl._size + (added.value ? 1 : 0)) +} + +/** @internal */ +export const set = dual< + (key: K, value: V) => (self: HashMap) => HashMap, + (self: HashMap, key: K, value: V) => HashMap +>(3, (self: HashMap, key: K, value: V): HashMap => { + return setHash(self, key, Hash.hash(key), value) }) /** @internal */ @@ -1104,10 +1107,10 @@ export const modifyHash = dual< const updated = f(current) if (Option.isNone(updated)) { - return hasHash(self, key, hash) ? remove(self, key) : self + return hasHash(self, key, hash) ? removeHash(self, key, hash) : self } - return set(self, key, updated.value) + return setHash(self, key, hash, updated.value) }) /** @internal */ @@ -1130,13 +1133,8 @@ export const union = dual< return result }) -/** @internal */ -export const remove = dual< - (key: K) => (self: HashMap) => HashMap, - (self: HashMap, key: K) => HashMap ->(2, (self: HashMap, key: K): HashMap => { +const removeHash = (self: HashMap, key: K, hash: number): HashMap => { const impl = self as HashMapImpl - const hash = Hash.hash(key) const removed = { value: false } const edit = impl._editable ? impl._edit : NaN @@ -1157,6 +1155,14 @@ export const remove = dual< } return new HashMapImpl(false, impl._edit, newRoot, impl._size - 1) +} + +/** @internal */ +export const remove = dual< + (key: K) => (self: HashMap) => HashMap, + (self: HashMap, key: K) => HashMap +>(2, (self: HashMap, key: K): HashMap => { + return removeHash(self, key, Hash.hash(key)) }) /** @internal */ diff --git a/repos/effect/packages/effect/src/internal/matcher.ts b/repos/effect/packages/effect/src/internal/matcher.ts index a9d20eebfa..3a031787e0 100644 --- a/repos/effect/packages/effect/src/internal/matcher.ts +++ b/repos/effect/packages/effect/src/internal/matcher.ts @@ -124,8 +124,8 @@ const makePredicate = (pattern: unknown): Predicate.Predicate => { return true } } else if (pattern !== null && typeof pattern === "object") { - const keysAndPredicates = Object.entries(pattern).map( - ([k, p]) => [k, makePredicate(p)] as const + const keysAndPredicates = Reflect.ownKeys(pattern).map( + (key) => [key, makePredicate((pattern as any)[key])] as const ) const len = keysAndPredicates.length @@ -396,7 +396,7 @@ export const discriminators = (field: D) => fields: P ) => { const predicate = makeWhen( - (arg: any) => arg != null && arg[field] in fields, + (arg: any) => arg != null && Object.hasOwn(fields, arg[field]), (data: any) => (fields as any)[data[field]](data) ) diff --git a/repos/effect/packages/effect/src/internal/persistence.ts b/repos/effect/packages/effect/src/internal/persistence.ts new file mode 100644 index 0000000000..5d222c257a --- /dev/null +++ b/repos/effect/packages/effect/src/internal/persistence.ts @@ -0,0 +1,2 @@ +/** @internal */ +export const sqlCleanupBatchSize = 1000 diff --git a/repos/effect/packages/effect/src/internal/rcRef.ts b/repos/effect/packages/effect/src/internal/rcRef.ts index 7013b2c2a1..56245e27de 100644 --- a/repos/effect/packages/effect/src/internal/rcRef.ts +++ b/repos/effect/packages/effect/src/internal/rcRef.ts @@ -94,7 +94,7 @@ export const make = (options: { }) const getState = (self: RcRefImpl) => - Effect.uninterruptibleMask((restore) => { + Effect.uninterruptibleMask(function loop(restore): Effect.Effect, E> { switch (self.state._tag) { case "Closed": { return Effect.interrupt @@ -107,22 +107,30 @@ const getState = (self: RcRefImpl) => } case "Empty": { const scope = Scope.makeUnsafe() - return self.semaphore.withPermits(1)( - restore(Effect.provideContext( - self.acquire as Effect.Effect, - Context.add(self.context, Scope.Scope, scope) - )).pipe(Effect.map((value) => { - const state: State.Acquired = { - _tag: "Acquired", - value, - scope, - fiber: undefined, - refCount: 1, - invalidated: false + return self.semaphore.withPermit( + Effect.suspend(() => { + if (self.state._tag !== "Empty") { + return loop(restore) } - self.state = state - return state - })) + return restore(Effect.provideContext( + self.acquire as Effect.Effect, + Context.add(self.context, Scope.Scope, scope) + )).pipe( + Effect.map((value) => { + const state: State.Acquired = { + _tag: "Acquired", + value, + scope, + fiber: undefined, + refCount: 1, + invalidated: false + } + self.state = state + return state + }), + Effect.onExit((exit) => Exit.isFailure(exit) ? Scope.close(scope, exit) : Effect.void) + ) + }) ) } } diff --git a/repos/effect/packages/effect/src/internal/record.ts b/repos/effect/packages/effect/src/internal/record.ts index 91319df894..ee4de5a193 100644 --- a/repos/effect/packages/effect/src/internal/record.ts +++ b/repos/effect/packages/effect/src/internal/record.ts @@ -1,9 +1,5 @@ -/** - * @since 4.0.0 - */ - /** @internal */ -export function set(self: Record, key: K, value: A): Record { +export function assignProperty(self: object, key: PropertyKey, value: unknown): void { if (key === "__proto__") { Object.defineProperty(self, key, { value, @@ -12,7 +8,15 @@ export function set(self: Record, key: K, value: configurable: true }) } else { - self[key] = value + ;(self as any)[key] = value + } +} + +/** @internal */ +export function assignProperties(self: object, source: object): void { + for (const key of Reflect.ownKeys(source)) { + if (Object.prototype.propertyIsEnumerable.call(source, key)) { + assignProperty(self, key, (source as any)[key]) + } } - return self } diff --git a/repos/effect/packages/effect/src/internal/references.ts b/repos/effect/packages/effect/src/internal/references.ts index 8fdd284f6a..6cb1600190 100644 --- a/repos/effect/packages/effect/src/internal/references.ts +++ b/repos/effect/packages/effect/src/internal/references.ts @@ -6,11 +6,6 @@ import type { ReadonlyRecord } from "../Record.ts" import type { StackFrame } from "../References.ts" import type { SpanLink } from "../Tracer.ts" -/** @internal */ -export const CurrentConcurrency = Context.Reference<"unbounded" | number>("effect/References/CurrentConcurrency", { - defaultValue: () => "unbounded" -}) - /** @internal */ export const CurrentErrorReporters = Context.Reference>( "effect/ErrorReporter/CurrentErrorReporters", @@ -19,6 +14,7 @@ export const CurrentErrorReporters = Context.Reference("effect/References/CurrentStackFrame", { + fiberCached: true, defaultValue: constUndefined }) @@ -52,13 +48,13 @@ export const CurrentLogAnnotations = Context.Reference = Context.Reference( "effect/References/CurrentLogLevel", - { defaultValue: () => "Info" } + { fiberCached: true, defaultValue: () => "Info" } ) /** @internal */ export const MinimumLogLevel = Context.Reference< LogLevel ->("effect/References/MinimumLogLevel", { defaultValue: () => "Info" }) +>("effect/References/MinimumLogLevel", { fiberCached: true, defaultValue: () => "Info" }) /** @internal */ export const UnhandledLogLevel: Context.Reference = Context.Reference( diff --git a/repos/effect/packages/effect/src/internal/request.ts b/repos/effect/packages/effect/src/internal/request.ts index 6c7d17993c..7eb1662b47 100644 --- a/repos/effect/packages/effect/src/internal/request.ts +++ b/repos/effect/packages/effect/src/internal/request.ts @@ -180,7 +180,7 @@ const removeEntryUnsafe = ( if (entry.uninterruptible) return const batchMap = pendingBatches.get(resolver) if (!batchMap) return - const key = resolver.batchKey(entry.request as any) + const key = resolver.batchKey(entry) const batch = batchMap.get(key) if (!batch) return diff --git a/repos/effect/packages/effect/src/internal/schedule.ts b/repos/effect/packages/effect/src/internal/schedule.ts index bb405f6330..ecb1c37431 100644 --- a/repos/effect/packages/effect/src/internal/schedule.ts +++ b/repos/effect/packages/effect/src/internal/schedule.ts @@ -184,17 +184,17 @@ export const scheduleFrom = dual< schedule: Schedule.Schedule ) => ( self: Effect - ) => Effect, + ) => Effect, ( self: Effect, initial: Input, schedule: Schedule.Schedule - ) => Effect + ) => Effect >(3, ( self: Effect, initial: Input, schedule: Schedule.Schedule -): Effect => +): Effect => effect.flatMap(Schedule.toStepWithMetadata(schedule), (step) => { let meta = Schedule.CurrentMetadata.defaultValue() const selfWithMeta = effect.suspend(() => effect.provideService(self, Schedule.CurrentMetadata, meta)) @@ -213,7 +213,7 @@ export const scheduleFrom = dual< }) as Effect } ), - (error) => core.isDone(error) ? effect.succeed(error.value as Output) : effect.fail(error as E) + (error) => core.isDone(error) ? effect.succeed(error.value as Output) : effect.fail(error as E | Error) ) })) diff --git a/repos/effect/packages/effect/src/internal/schema/annotations.ts b/repos/effect/packages/effect/src/internal/schema/annotations.ts index 174494ae60..0ba8bb1c07 100644 --- a/repos/effect/packages/effect/src/internal/schema/annotations.ts +++ b/repos/effect/packages/effect/src/internal/schema/annotations.ts @@ -12,9 +12,38 @@ export function resolveAt(key: string) { return (ast: SchemaAST.AST): A | undefined => resolve(ast)?.[key] as A | undefined } +/** @internal */ +export const STRUCTURAL_ANNOTATION_KEY = "~structural" + +/** @internal */ +export const IDENTIFIER_FALLBACK_KEY = "~identifier" + +/** @internal */ +export const SENTINELS_ANNOTATION_KEY = "~sentinels" + +/** @internal */ +export const CONSTRUCTOR_ANNOTATION_KEY = "~constructor" + +/** @internal */ +export const jsonSchemaAnnotationKeys = [ + "title", + "description", + "default", + "examples", + "readOnly", + "writeOnly", + "format", + "contentEncoding", + "contentMediaType", + "contentSchema" +] as const + /** @internal */ export const resolveIdentifier = resolveAt("identifier") +/** @internal */ +export const resolveIdentifierFallback = resolveAt(IDENTIFIER_FALLBACK_KEY) + /** @internal */ export const resolveTitle = resolveAt("title") @@ -35,3 +64,21 @@ export const getExpected = memoize((ast: SchemaAST.AST): string => { export function collectBrands(annotations: Schema.Annotations.Annotations | undefined): ReadonlyArray { return annotations !== undefined && Array.isArray(annotations.brands) ? annotations.brands : [] } + +/** @internal */ +export const annotationExcludedKeys = new Set([ + SENTINELS_ANNOTATION_KEY, + STRUCTURAL_ANNOTATION_KEY, + "representation", + "arbitrary", + "brands", + "toJsonSchema", + "toCode", + "toArbitrary", + "toEquivalence", + "toFormatter", + "toCodec", + "toCodecJson", + "toCodecStringTree", + "toCodecIso" +]) diff --git a/repos/effect/packages/effect/src/internal/schema/arbitrary.ts b/repos/effect/packages/effect/src/internal/schema/arbitrary.ts deleted file mode 100644 index 8fe9690715..0000000000 --- a/repos/effect/packages/effect/src/internal/schema/arbitrary.ts +++ /dev/null @@ -1,915 +0,0 @@ -import * as Array from "../../Array.ts" -import * as Boolean from "../../Boolean.ts" -import type * as Combiner from "../../Combiner.ts" -import * as Equal from "../../Equal.ts" -import { memoize } from "../../Function.ts" -import * as Number from "../../Number.ts" -import * as Option from "../../Option.ts" -import * as Order from "../../Order.ts" -import * as Predicate from "../../Predicate.ts" -import type * as Schema from "../../Schema.ts" -import * as SchemaAST from "../../SchemaAST.ts" -import * as Struct from "../../Struct.ts" -import type * as FastCheck from "../../testing/FastCheck.ts" -import * as UndefinedOr from "../../UndefinedOr.ts" -import { errorWithPath } from "../errors.ts" -import * as InternalAnnotations from "./annotations.ts" - -const arbitraryMemoMap = new WeakMap>() -const suspendDepthIdentifierMap = new WeakMap() -const emptyRecursionStack: RecursionStack = [] - -type RecursionStack = ReadonlyArray - -type Context = Schema.Annotations.ToArbitrary.Context -type Constraint = Schema.Annotations.ToArbitrary.GenerationConstraint -type OrderedConstraint = Schema.Annotations.ToArbitrary.OrderedConstraint -type ArbitraryFilter = Schema.Annotations.ToArbitrary.Filter - -type Lazy = (fc: typeof FastCheck, ctx: Context, recursionStack: RecursionStack) => FastCheck.Arbitrary -type LazyOption = ( - fc: typeof FastCheck, - ctx: Context, - recursionStack: RecursionStack -) => FastCheck.Arbitrary | undefined - -export interface MutableReport { - readonly warnings: Array -} - -/** @internal */ -export function makeReport(): MutableReport { - return { warnings: [] } -} - -/** @internal */ -export function toReport(report: MutableReport): Schema.Annotations.ToArbitrary.Report { - return { warnings: report.warnings.slice() } -} - -function arbitraryError(what: string) { - return new Error(`Unable to derive an arbitrary for ${what}`) -} - -const entryComparator = ([a]: readonly [any, any], [b]: readonly [any, any]) => Equal.equals(a, b) - -function applyChecks(ast: SchemaAST.AST, filters: Array>, arbitrary: FastCheck.Arbitrary) { - return filters.reduce( - (acc, filter) => acc.filter((a) => filter.run(a, ast, SchemaAST.defaultParseOptions) === undefined), - arbitrary - ) -} - -function validateArrayConstraints(constraint: FastCheck.ArrayConstraints | undefined, label: string) { - if ( - constraint?.minLength !== undefined && constraint.maxLength !== undefined && - constraint.minLength > constraint.maxLength - ) { - throw arbitraryError(`${label} constraints`) - } -} - -function lengthToFastCheckConstraints( - constraint: { readonly minLength?: number | undefined; readonly maxLength?: number | undefined } | undefined -) { - return constraint === undefined || (constraint.minLength === undefined && constraint.maxLength === undefined) - ? undefined - : { - ...(constraint.minLength !== undefined ? { minLength: constraint.minLength } : {}), - ...(constraint.maxLength !== undefined ? { maxLength: constraint.maxLength } : {}) - } -} - -function arrayWithConstraints( - fc: typeof FastCheck, - item: FastCheck.Arbitrary, - constraint: FastCheck.ArrayConstraints | undefined, - comparator?: ((a: any, b: any) => boolean) | undefined -) { - return comparator - ? fc.uniqueArray(item, { ...constraint, comparator }) - : fc.array(item, constraint) -} - -function array(fc: typeof FastCheck, ctx: Context, item: FastCheck.Arbitrary, terminal = false) { - const constraint = ctx.constraint - const arrayConstraints = lengthToFastCheckConstraints(constraint) - validateArrayConstraints(arrayConstraints, "array") - return arrayWithConstraints( - fc, - item, - terminal ? { ...arrayConstraints, maxLength: arrayConstraints?.minLength ?? 0 } : arrayConstraints, - constraint?.unique ? Equal.equals : undefined - ) -} - -function appendArray( - fc: typeof FastCheck, - out: FastCheck.Arbitrary>, - len: number, - rest: FastCheck.Arbitrary> -) { - return out.chain((as) => as.length < len ? fc.constant(as) : rest.map((rest) => [...as, ...rest])) -} - -function appendObjectEntries( - out: FastCheck.Arbitrary, - entries: FastCheck.Arbitrary> -) { - return out.chain((o) => entries.map((entries) => ({ ...Object.fromEntries(entries), ...o }))) -} - -const max = UndefinedOr.makeReducer(Number.ReducerMax) -const min = UndefinedOr.makeReducer(Number.ReducerMin) -const or = UndefinedOr.makeReducer(Boolean.ReducerOr) -const concat = UndefinedOr.makeReducer(Array.makeReducerConcat()) - -const combiner: Combiner.Combiner = Struct.makeCombiner({ - integer: or, - maxLength: min, - minLength: max, - noInfinity: or, - noNaN: or, - patterns: concat, - unique: or, - valid: or -}, { - omitKeyWhen: Predicate.isUndefined -}) - -function mergeOrderedBound( - order: Order.Order, - self: T | undefined, - selfExclusive: boolean | undefined, - that: T | undefined, - thatExclusive: boolean | undefined, - takeComparison: -1 | 1 -): readonly [T | undefined, boolean | undefined] { - if (that === undefined || self === undefined) { - return that === undefined ? [self, selfExclusive] : [that, thatExclusive] - } - const comparison = order(self, that) - return comparison === takeComparison - ? [that, thatExclusive] - : comparison === 0 - ? [self, selfExclusive || thatExclusive] - : [self, selfExclusive] -} - -function mergeOrderedConstraints(self: OrderedConstraint | undefined, that: OrderedConstraint) { - if (self === undefined) { - return that - } - if (self.order !== that.order) { - throw new Error("Cannot merge ordered arbitrary constraints with different Order instances") - } - - const [minimum, exclusiveMinimum] = mergeOrderedBound( - self.order, - self.minimum, - self.exclusiveMinimum, - that.minimum, - that.exclusiveMinimum, - -1 - ) - const [maximum, exclusiveMaximum] = mergeOrderedBound( - self.order, - self.maximum, - self.exclusiveMaximum, - that.maximum, - that.exclusiveMaximum, - 1 - ) - - return { - order: self.order, - ...(minimum !== undefined ? { minimum } : {}), - ...(exclusiveMinimum !== undefined ? { exclusiveMinimum } : {}), - ...(maximum !== undefined ? { maximum } : {}), - ...(exclusiveMaximum !== undefined ? { exclusiveMaximum } : {}) - } -} - -function mergeConstraint(self: Constraint | undefined, that: Constraint): Constraint { - const { ordered: selfOrdered, ...selfRest } = self ?? {} - const { ordered: thatOrdered, ...thatRest } = that - const ordered = thatOrdered === undefined - ? selfOrdered - : mergeOrderedConstraints(selfOrdered, thatOrdered) - const out = combiner.combine(selfRest, thatRest) - return { - ...out, - ...(ordered === undefined ? {} : { ordered }) - } -} - -function collectChecks(checks: SchemaAST.Checks | undefined) { - const filters: Array> = [] - const arbitraries: Array = [] - function visit(check: SchemaAST.Check) { - if (check.annotations?.arbitrary) { - arbitraries.push(check.annotations.arbitrary) - } - if (check._tag !== "Filter") { - for (const child of check.checks) { - visit(child) - } - } else { - filters.push(check) - } - } - checks?.forEach(visit) - return { filters, arbitraries } -} - -function constraintContext(arbitraries: Array): (ctx: Context) => Context { - const constraintAnnotations = arbitraries.map(({ constraint }) => constraint).filter(Predicate.isNotUndefined) - return (ctx) => { - const constraint = constraintAnnotations.reduce( - (acc: Constraint | undefined, c) => mergeConstraint(acc, c), - ctx.constraint - ) - return { ...ctx, constraint } - } -} - -function resetContext(ctx: Context) { - return { ...ctx, constraint: undefined } -} - -function objectEntriesConstraints(ast: SchemaAST.Objects, constraint: Constraint | undefined, requiredKeys: number) { - if (constraint === undefined || (constraint.minLength === undefined && constraint.maxLength === undefined)) { - return undefined - } - if ( - constraint.minLength !== undefined && - ast.indexSignatures.length === 0 && - constraint.minLength > ast.propertySignatures.length - ) { - throw arbitraryError("object property constraints") - } - const out: FastCheck.ArrayConstraints = {} - if (constraint.minLength !== undefined) { - out.minLength = Math.max(0, constraint.minLength - requiredKeys) - } - if (constraint.maxLength !== undefined) { - out.maxLength = constraint.maxLength - requiredKeys - if (out.maxLength < 0) { - throw arbitraryError("object property constraints") - } - } - validateArrayConstraints(out, "object property") - return out -} - -function objectWithOptionalCount( - fc: typeof FastCheck, - pss: Record>, - orderedNames: ReadonlyArray, - requiredKeys: ReadonlyArray, - optionalNames: ReadonlyArray, - constraint: Constraint -) { - const requiredCount = requiredKeys.length - if (constraint.maxLength !== undefined && constraint.maxLength < requiredCount) { - throw arbitraryError("object property constraints") - } - const minOptional = constraint.minLength === undefined ? 0 : Math.max(0, constraint.minLength - requiredCount) - const maxOptional = constraint.maxLength === undefined - ? optionalNames.length - : Math.min(optionalNames.length, constraint.maxLength - requiredCount) - if (minOptional > maxOptional) { - throw arbitraryError("object property constraints") - } - const full = fc.record(pss, { requiredKeys: [...requiredKeys, ...optionalNames] }) - const chosen = fc.shuffledSubarray([...optionalNames], { minLength: minOptional, maxLength: maxOptional }) - return fc.tuple(full, chosen).map(([base, names]) => { - const keep = new Set([...requiredKeys, ...names]) - const out: Record = {} - for (const name of orderedNames) { - if (keep.has(name)) { - out[name] = base[name] - } - } - return out - }) -} - -function toRangeConstraints( - ordered: OrderedConstraint | undefined, - min: (value: T, excluded: boolean) => T, - max: (value: T, excluded: boolean) => T, - error: string -) { - const out: { min?: T; max?: T } = {} - if (ordered?.minimum !== undefined) { - out.min = min(ordered.minimum as T, ordered.exclusiveMinimum === true) - } - if (ordered?.maximum !== undefined) { - out.max = max(ordered.maximum as T, ordered.exclusiveMaximum === true) - } - if (out.min !== undefined && out.max !== undefined && out.min > out.max) { - throw arbitraryError(error) - } - return out -} - -function toIntegerConstraints(ordered: OrderedConstraint | undefined) { - return toRangeConstraints( - ordered, - (minimum, excluded) => excluded ? Math.floor(minimum) + 1 : Math.ceil(minimum), - (maximum, excluded) => excluded ? Math.ceil(maximum) - 1 : Math.floor(maximum), - "integer constraints" - ) -} - -function toFloatConstraints(constraint: Constraint | undefined, ordered: OrderedConstraint | undefined) { - const out: FastCheck.FloatConstraints = { - ...(constraint?.noInfinity ? { noDefaultInfinity: true } : {}), - ...(constraint?.noNaN ? { noNaN: true } : {}), - ...(ordered?.minimum !== undefined ? { min: ordered.minimum as number } : {}), - ...(ordered?.exclusiveMinimum !== undefined ? { minExcluded: ordered.exclusiveMinimum } : {}), - ...(ordered?.maximum !== undefined ? { max: ordered.maximum as number } : {}), - ...(ordered?.exclusiveMaximum !== undefined ? { maxExcluded: ordered.exclusiveMaximum } : {}) - } - if ( - out.min !== undefined && - out.max !== undefined && - (out.min > out.max || (out.min === out.max && (out.minExcluded || out.maxExcluded))) - ) { - throw arbitraryError("number constraints") - } - return out -} - -function toBigIntConstraints(ordered: OrderedConstraint | undefined) { - return toRangeConstraints( - ordered, - (minimum, excluded) => excluded ? minimum + BigInt(1) : minimum, - (maximum, excluded) => excluded ? maximum - BigInt(1) : maximum, - "the ordered bigint constraints" - ) -} - -interface LazyArbitraryWithContext { - (fc: typeof FastCheck, ctx: Context, recursionStack?: RecursionStack): FastCheck.Arbitrary - readonly terminal: ( - fc: typeof FastCheck, - ctx: Context, - recursionStack?: RecursionStack - ) => FastCheck.Arbitrary | undefined -} - -function makeLazy(normal: Lazy, terminal: LazyOption): LazyArbitraryWithContext { - const out = - ((fc, ctx, recursionStack = emptyRecursionStack) => normal(fc, ctx, recursionStack)) as LazyArbitraryWithContext - ;(out as { terminal: LazyOption }).terminal = (fc, ctx, recursionStack = emptyRecursionStack) => - terminal(fc, ctx, recursionStack) - return out -} - -function same(f: Lazy) { - return makeLazy(f, f) -} - -function getSuspendRecursion(fc: typeof FastCheck, ast: SchemaAST.Suspend) { - const depthIdentifier = suspendDepthIdentifierMap.get(ast) ?? fc.createDepthIdentifier() - suspendDepthIdentifierMap.set(ast, depthIdentifier) - return { maxDepth: 2, depthIdentifier } -} - -function oneOf(fc: typeof FastCheck, arbitraries: ReadonlyArray>) { - return arbitraries.length === 0 ? undefined : arbitraries.length === 1 ? arbitraries[0] : fc.oneof(...arbitraries) -} - -const finiteNumberConstraint: Constraint = { - noInfinity: true, - noNaN: true -} - -function finiteNumberContext(ctx: Context): Context { - return { - ...ctx, - constraint: finiteNumberConstraint - } -} - -function reportChecks(report: MutableReport, checks: SchemaAST.Checks | undefined, path: ReadonlyArray) { - function visit(check: SchemaAST.Check, covered: boolean) { - const arbitrary = check.annotations?.arbitrary - const nextCovered = covered || arbitrary?.constraint !== undefined || arbitrary?.candidate !== undefined - if (check._tag !== "Filter") { - for (const child of check.checks) { - visit(child, nextCovered) - } - } else if (!nextCovered) { - const meta = check.annotations?.meta - const description = typeof meta === "object" && meta !== null && "_tag" in meta && typeof meta._tag === "string" - ? meta._tag - : check.annotations?.identifier ?? check.annotations?.expected - report.warnings.push({ _tag: "OpaqueFilter", path, ...(description === undefined ? {} : { description }) }) - } - } - checks?.forEach((check) => visit(check, false)) -} - -/** @internal */ -export function collectReport(ast: SchemaAST.AST, report: MutableReport) { - const stack = new WeakSet() - function visit(ast: SchemaAST.AST, path: ReadonlyArray) { - if (stack.has(ast)) { - return - } - stack.add(ast) - reportChecks(report, ast.checks, path) - switch (ast._tag) { - case "Declaration": - ast.typeParameters.forEach((tp) => visit(tp, path)) - break - case "Arrays": { - for (const [i, type] of [...ast.elements, ...ast.rest].entries()) { - visit(type, [...path, i]) - } - break - } - case "Objects": - ast.propertySignatures.forEach((ps) => visit(ps.type, [...path, ps.name])) - ast.indexSignatures.forEach((is) => { - visit(is.parameter, path) - visit(is.type, path) - }) - break - case "Union": - ast.types.forEach((type) => visit(type, path)) - break - case "TemplateLiteral": - ast.parts.forEach((part, i) => visit(SchemaAST.toEncoded(part), [...path, i])) - break - case "Suspend": - visit(ast.thunk(), path) - break - } - stack.delete(ast) - } - visit(ast, []) -} - -function applyCandidates( - fc: typeof FastCheck, - ctx: Context, - arbitraries: Array, - base: FastCheck.Arbitrary | undefined -) { - const weighted: Array> = base === undefined - ? [] - : [{ arbitrary: base, weight: 1 }] - for (const { candidate } of arbitraries) { - if (!candidate) { - continue - } - const arbitrary = candidate.make(fc, ctx) - if (arbitrary === undefined) { - continue - } - const weight = candidate.weight ?? 1 - if (!globalThis.Number.isInteger(weight) || weight <= 0) { - throw arbitraryError("a candidate with an invalid weight") - } - weighted.push({ arbitrary, weight }) - } - return weighted.length === 0 ? undefined : weighted.length === 1 ? weighted[0].arbitrary : fc.oneof(...weighted) -} - -function applyFilterLayer( - ast: SchemaAST.AST, - checks: ReturnType, - fc: typeof FastCheck, - ctx: Context, - base: FastCheck.Arbitrary | undefined -) { - const out = applyCandidates(fc, ctx, checks.arbitraries, base) - return out === undefined ? undefined : applyChecks(ast, checks.filters, out) -} - -function normalizeDerivation( - output: Schema.Annotations.ToArbitrary.Output, - hasTypeParameters: boolean -) { - if (!(typeof output === "object" && output !== null && "arbitrary" in output)) { - return { arbitrary: output, terminal: hasTypeParameters ? undefined : output } - } - const terminal = "terminal" in output ? output.terminal : hasTypeParameters ? undefined : output.arbitrary - return { - arbitrary: output.arbitrary, - terminal - } -} - -function makeTypeParameters( - typeParameters: ReadonlyArray>, - fc: typeof FastCheck, - ctx: Context, - recursionStack: RecursionStack, - lazyNormal: boolean -) { - return typeParameters.map((tp) => ({ - arbitrary: lazyNormal ? fc.constant(null).chain(() => tp(fc, ctx, recursionStack)) : tp(fc, ctx, recursionStack), - terminal: tp.terminal(fc, ctx, recursionStack) - })) -} - -type BaseBuilder = ( - fc: typeof FastCheck, - ctx: Context, - nextCtx: Context, - recursionStack: RecursionStack -) => FastCheck.Arbitrary | undefined - -function filterLayer( - ast: SchemaAST.AST, - checks: ReturnType, - normalBase: BaseBuilder, - terminalBase: BaseBuilder -): LazyArbitraryWithContext { - const f = constraintContext(checks.arbitraries) - return makeLazy((fc, ctx, recursionStack) => { - const nextCtx = f(ctx) - return applyFilterLayer(ast, checks, fc, nextCtx, normalBase(fc, ctx, nextCtx, recursionStack))! - }, (fc, ctx, recursionStack) => { - const nextCtx = f(ctx) - return applyFilterLayer(ast, checks, fc, nextCtx, terminalBase(fc, ctx, nextCtx, recursionStack)) - }) -} - -/** @internal */ -export const memoized = memoize((ast: SchemaAST.AST) => recur(ast, [])) - -function recur(ast: SchemaAST.AST, path: ReadonlyArray): LazyArbitraryWithContext { - // --------------------------------------------- - // handle annotations - // --------------------------------------------- - const annotation = InternalAnnotations.resolve(ast)?.toArbitrary as - | Schema.Annotations.ToArbitrary.Declaration> - | undefined - if (annotation) { - const typeParameters = SchemaAST.isDeclaration(ast) ? ast.typeParameters.map((tp) => recur(tp, path)) : [] - const checks = collectChecks(ast.checks) - const derive = (lazyNormal: boolean): BaseBuilder => (fc, ctx, nextCtx, recursionStack) => - normalizeDerivation( - annotation(makeTypeParameters(typeParameters, fc, resetContext(ctx), recursionStack, lazyNormal))(fc, nextCtx), - typeParameters.length > 0 - )[lazyNormal ? "terminal" : "arbitrary"] - return filterLayer(ast, checks, derive(false), derive(true)) - } - if (ast.checks) { - const checks = collectChecks(ast.checks) - const lawc = recur(SchemaAST.replaceChecks(ast, undefined), path) - return filterLayer( - ast, - checks, - (fc, _ctx, nextCtx, recursionStack) => lawc(fc, nextCtx, recursionStack), - (fc, _ctx, nextCtx, recursionStack) => lawc.terminal(fc, nextCtx, recursionStack) - ) - } - return base(ast, path) -} - -function base(ast: SchemaAST.AST, path: ReadonlyArray): LazyArbitraryWithContext { - switch (ast._tag) { - case "Never": - case "Declaration": - throw errorWithPath(`Unsupported AST ${ast._tag}`, path) - case "Null": - return same((fc) => fc.constant(null)) - case "Void": - case "Undefined": - return same((fc) => fc.constant(undefined)) - case "Unknown": - case "Any": - return same((fc) => fc.anything()) - case "String": - return same((fc, ctx) => { - const constraint = ctx.constraint - const patterns = constraint?.patterns - return patterns - ? fc.oneof(...patterns.map((pattern) => fc.stringMatching(new RegExp(pattern)))) - : fc.string(lengthToFastCheckConstraints(constraint)) - }) - case "Number": - return same((fc, ctx) => { - const constraint = ctx.constraint - const ordered = constraint?.ordered?.order === Order.Number ? constraint.ordered : undefined - return constraint?.integer - ? fc.integer(toIntegerConstraints(ordered)) - : fc.float(toFloatConstraints(constraint, ordered)) - }) - case "Boolean": - return same((fc) => fc.boolean()) - case "BigInt": - return same((fc, ctx) => { - const ordered = ctx.constraint?.ordered?.order === Order.BigInt ? ctx.constraint.ordered : undefined - return fc.bigInt(toBigIntConstraints(ordered)) - }) - case "Symbol": - return same((fc) => fc.string().map(Symbol.for)) - case "Literal": - return same((fc) => fc.constant(ast.literal)) - case "UniqueSymbol": - return same((fc) => fc.constant(ast.symbol)) - case "ObjectKeyword": - return same((fc) => fc.oneof(fc.object(), fc.array(fc.anything()))) - case "Enum": - return recur(SchemaAST.enumsToLiterals(ast), path) - case "TemplateLiteral": { - const parts = ast.parts.map((part, i) => recur(SchemaAST.toEncoded(part), [...path, i])) - return same((fc, ctx, recursionStack) => - fc.tuple(...parts.map((part) => part(fc, finiteNumberContext(ctx), recursionStack))).map((segments) => - segments.map((segment) => globalThis.String(segment)).join("") - ) - ) - } - case "Arrays": { - const elements = ast.elements.map((ast, i) => ({ - ast, - arbitrary: recur(ast, [...path, i]) - })) - const len = ast.elements.length - const rest = ast.rest.map((ast, i) => ({ - ast, - arbitrary: recur(ast, [...path, len + i]) - })) - const terminal: LazyOption = (fc, ctx, recursionStack) => { - const reset = resetContext(ctx) - const elementArbitraries: Array>> = [] - const optionals: Array | undefined> = [] - let length = 0 - for (const element of elements) { - const out = element.arbitrary.terminal(fc, reset, recursionStack) - if (SchemaAST.isOptional(element.ast)) { - optionals.push(out) - continue - } - if (out === undefined) { - return undefined - } - length++ - elementArbitraries.push(out.map(Option.some)) - } - const minLength = ctx.constraint?.minLength ?? 0 - const needsRest = Array.isReadonlyArrayNonEmpty(rest) && minLength > length + optionals.length - const optionalTarget = needsRest ? optionals.length : Math.max(0, minLength - length) - let includedOptionals = 0 - for (const out of optionals) { - if (includedOptionals >= optionalTarget || out === undefined) { - elementArbitraries.push(fc.constant(Option.none())) - continue - } - includedOptionals++ - length++ - elementArbitraries.push(out.map(Option.some)) - } - if (includedOptionals < optionalTarget) { - return undefined - } - let out = fc.tuple(...elementArbitraries).map(Array.getSomes) - if (Array.isReadonlyArrayNonEmpty(rest)) { - const [head, ...tail] = rest - const restCtx = ast.elements.length === 0 ? ctx : reset - const minRestLength = Math.max(0, minLength - length - tail.length) - const headArbitrary = minRestLength === 0 - ? undefined - : head.arbitrary.terminal(fc, reset, recursionStack) - if (minRestLength > 0 && headArbitrary === undefined) { - return undefined - } - const restArbitrary = minRestLength === 0 - ? fc.constant([]) - : array( - fc, - { ...restCtx, constraint: { ...restCtx.constraint, minLength: minRestLength } }, - headArbitrary!, - true - ) - out = appendArray(fc, out, len, restArbitrary) - if (tail.length > 0) { - const tailArbitraries: Array> = [] - for (const element of tail) { - const out = element.arbitrary.terminal(fc, reset, recursionStack) - if (out === undefined) { - return undefined - } - tailArbitraries.push(out) - } - const t = fc.tuple(...tailArbitraries) - out = appendArray(fc, out, len, t) - } - } - return out - } - return makeLazy((fc, ctx, recursionStack) => { - const reset = resetContext(ctx) - // --------------------------------------------- - // handle elements - // --------------------------------------------- - const elementArbitraries: Array>> = elements.map( - ({ ast, arbitrary }) => { - const out = arbitrary(fc, reset, recursionStack) - return SchemaAST.isOptional(ast) - ? out.chain((a) => fc.boolean().map((b) => b ? Option.some(a) : Option.none())) - : out.map(Option.some) - } - ) - let out = fc.tuple(...elementArbitraries).map(Array.getSomes) - // --------------------------------------------- - // handle rest element - // --------------------------------------------- - if (Array.isReadonlyArrayNonEmpty(rest)) { - const [head, ...tail] = rest.map(({ arbitrary }) => arbitrary(fc, reset, recursionStack)) - - const restArbitrary = array(fc, ast.elements.length === 0 ? ctx : reset, head) - out = appendArray(fc, out, len, restArbitrary) - // --------------------------------------------- - // handle post rest elements - // --------------------------------------------- - if (tail.length > 0) { - const t = fc.tuple(...tail) - out = appendArray(fc, out, len, t) - } - } - if (ctx.recursion) { - const terminalOut = terminal(fc, ctx, recursionStack) - if (terminalOut !== undefined) { - return fc.oneof(ctx.recursion, terminalOut, out) - } - } - return out - }, terminal) - } - case "Objects": { - const propertySignatures = ast.propertySignatures.map((ps) => ({ - ps, - arbitrary: recur(ps.type, [...path, ps.name]) - })) - const indexSignatures = ast.indexSignatures.map((is) => ({ - is, - parameter: recur(is.parameter, path), - type: recur(is.type, path) - })) - const terminal: LazyOption = (fc, ctx, recursionStack) => { - const reset = resetContext(ctx) - const pss: any = {} - const requiredKeys: Array = [] - const optionals: Array]> = [] - for (const { ps, arbitrary } of propertySignatures) { - const name = ps.name - const out = arbitrary.terminal(fc, reset, recursionStack) - if (SchemaAST.isOptional(ps.type)) { - if (out !== undefined) { - optionals.push([name, out]) - } - continue - } - if (out === undefined) { - return undefined - } - requiredKeys.push(name) - pss[name] = out - } - let optionalCount = Math.max(0, (ctx.constraint?.minLength ?? 0) - requiredKeys.length) - for (const [name, out] of optionals) { - if (optionalCount === 0) { - break - } - optionalCount-- - requiredKeys.push(name) - pss[name] = out - } - if (optionalCount > 0 && ast.indexSignatures.length === 0) { - return undefined - } - let out = fc.record(pss, { requiredKeys }) - const entriesConstraints = objectEntriesConstraints(ast, ctx.constraint, requiredKeys.length) - const minEntries = entriesConstraints?.minLength ?? 0 - for (const { parameter, type } of indexSignatures) { - let entries: FastCheck.Arbitrary> - if (minEntries === 0) { - entries = fc.constant([]) - } else { - const key = parameter.terminal(fc, reset, recursionStack) - const value = type.terminal(fc, reset, recursionStack) - if (key === undefined || value === undefined) { - return undefined - } - entries = arrayWithConstraints( - fc, - fc.tuple(key, value), - { ...entriesConstraints, maxLength: minEntries }, - entryComparator - ) - } - out = appendObjectEntries(out, entries) - } - return out - } - return makeLazy((fc, ctx, recursionStack) => { - const reset = resetContext(ctx) - // --------------------------------------------- - // handle property signatures - // --------------------------------------------- - const pss: any = {} - const orderedNames: Array = [] - const requiredKeys: Array = [] - const optionalNames: Array = [] - for (const { ps, arbitrary } of propertySignatures) { - const name = ps.name - orderedNames.push(name) - if (SchemaAST.isOptional(ps.type)) { - optionalNames.push(name) - } else { - requiredKeys.push(name) - } - pss[name] = arbitrary(fc, reset, recursionStack) - } - // When property-count constraints must be satisfied by selecting - // optional keys (no index signatures are available to fill the gap), - // generate a count-controlled subset of optional keys instead of - // relying on fast-check's independent inclusion plus discards. This - // enforces both bounds precisely while still varying which optionals - // appear. - const constraint = ctx.constraint - if ( - optionalNames.length > 0 && - indexSignatures.length === 0 && - constraint !== undefined && - (constraint.minLength !== undefined || constraint.maxLength !== undefined) - ) { - return objectWithOptionalCount(fc, pss, orderedNames, requiredKeys, optionalNames, constraint) - } - let out = fc.record(pss, { requiredKeys }) - const entriesConstraints = objectEntriesConstraints(ast, ctx.constraint, requiredKeys.length) - // --------------------------------------------- - // handle index signatures - // --------------------------------------------- - for (const { parameter, type } of indexSignatures) { - const entry = fc.tuple(parameter(fc, reset, recursionStack), type(fc, reset, recursionStack)) - const entries = arrayWithConstraints(fc, entry, entriesConstraints, entryComparator) - out = appendObjectEntries(out, entries) - } - return out - }, terminal) - } - case "Union": { - const types = ast.types.map((ast) => recur(ast, path)) - const terminal: LazyOption = (fc, ctx, recursionStack) => - oneOf(fc, types.map((type) => type.terminal(fc, ctx, recursionStack)).filter(Predicate.isNotUndefined)) - return makeLazy((fc, ctx, recursionStack) => { - const arbitraries = types.map((type) => type(fc, ctx, recursionStack)) - if (ctx.recursion) { - const terminalOut = terminal(fc, ctx, recursionStack) - if (terminalOut !== undefined) { - return fc.oneof(ctx.recursion, terminalOut, ...arbitraries) - } - } - const out = oneOf(fc, arbitraries) - if (out === undefined) { - throw arbitraryError("a union with no members") - } - return out - }, terminal) - } - case "Suspend": { - const memo = arbitraryMemoMap.get(ast) - - if (memo) return memo - - const get = SchemaAST.memoizeThunk(() => recur(ast.thunk(), path)) - const out = makeLazy((fc, ctx, recursionStack) => { - const recursion = getSuspendRecursion(fc, ast) - const nextCtx = { ...ctx, recursion } - const nextStack = recursionStack.includes(ast) ? recursionStack : [...recursionStack, ast] - const terminal = get().terminal(fc, nextCtx, nextStack) - if (terminal === undefined) { - throw errorWithPath( - "Unable to derive an arbitrary for a recursive schema without a finite generation path", - path - ) - } - return fc.oneof( - recursion, - terminal, - fc.constant(null).chain(() => get()(fc, nextCtx, nextStack)) - ) - }, (fc, ctx, recursionStack) => { - if (recursionStack.includes(ast)) { - return undefined - } - const recursion = getSuspendRecursion(fc, ast) - return get().terminal(fc, { ...ctx, recursion }, [...recursionStack, ast]) - }) - - arbitraryMemoMap.set(ast, out) - - return out - } - } -} diff --git a/repos/effect/packages/effect/src/internal/schema/equivalence.ts b/repos/effect/packages/effect/src/internal/schema/equivalence.ts deleted file mode 100644 index c6b88f05d5..0000000000 --- a/repos/effect/packages/effect/src/internal/schema/equivalence.ts +++ /dev/null @@ -1,154 +0,0 @@ -import * as Equal from "../../Equal.ts" -import * as Equivalence from "../../Equivalence.ts" -import { memoize } from "../../Function.ts" -import * as Predicate from "../../Predicate.ts" -import type * as Schema from "../../Schema.ts" -import * as SchemaAST from "../../SchemaAST.ts" -import * as SchemaParser from "../../SchemaParser.ts" -import { errorWithPath } from "../errors.ts" -import * as InternalAnnotations from "./annotations.ts" - -/** @internal */ -export const toEquivalence = memoize((ast: SchemaAST.AST): Equivalence.Equivalence => { - return recur(ast, []) -}) - -function recur(ast: SchemaAST.AST, path: ReadonlyArray): Equivalence.Equivalence { - // --------------------------------------------- - // handle annotations - // --------------------------------------------- - const annotation = InternalAnnotations.resolve(ast)?.["toEquivalence"] as - | Schema.Annotations.ToEquivalence.Declaration> - | undefined - if (annotation) { - return annotation(SchemaAST.isDeclaration(ast) ? ast.typeParameters.map((tp) => recur(tp, path)) : []) - } - switch (ast._tag) { - case "Never": - throw errorWithPath(`Unsupported AST ${ast._tag}`, path) - case "Declaration": - case "Null": - case "Undefined": - case "Void": - case "Unknown": - case "Any": - case "String": - case "Number": - case "Boolean": - case "BigInt": - case "Symbol": - case "Literal": - case "UniqueSymbol": - case "ObjectKeyword": - case "Enum": - case "TemplateLiteral": - return Equal.equals - case "Arrays": { - const elements = ast.elements.map((e, i) => recur(e, [...path, i])) - const len = ast.elements.length - const rest = ast.rest.map((r, i) => recur(r, [...path, len + i])) - return Equivalence.make((a, b) => { - if (!Array.isArray(a) || !Array.isArray(b)) { - return false - } - const len = a.length - if (len !== b.length) { - return false - } - // --------------------------------------------- - // handle elements - // --------------------------------------------- - let i = 0 - for (; i < Math.min(len, ast.elements.length); i++) { - if (!elements[i](a[i], b[i])) { - return false - } - } - // --------------------------------------------- - // handle rest element - // --------------------------------------------- - if (rest.length > 0) { - const [head, ...tail] = rest - for (; i < len - tail.length; i++) { - if (!head(a[i], b[i])) { - return false - } - } - // --------------------------------------------- - // handle post rest elements - // --------------------------------------------- - for (let j = 0; j < tail.length; j++) { - i += j - if (!tail[j](a[i], b[i])) { - return false - } - } - } - return true - }) - } - case "Objects": { - if (ast.propertySignatures.length === 0 && ast.indexSignatures.length === 0) { - return Equal.equals - } - const propertySignatures = ast.propertySignatures.map((ps) => recur(ps.type, [...path, ps.name])) - const indexSignatures = ast.indexSignatures.map((is) => recur(is.type, path)) - return Equivalence.make((a, b) => { - if (!Predicate.isObject(a) || !Predicate.isObject(b)) { - return false - } - // --------------------------------------------- - // handle property signatures - // --------------------------------------------- - for (let i = 0; i < propertySignatures.length; i++) { - const ps = ast.propertySignatures[i] - const name = ps.name - const aHas = Object.hasOwn(a, name) - const bHas = Object.hasOwn(b, name) - if (SchemaAST.isOptional(ps.type)) { - if (aHas !== bHas) { - return false - } - } - if (aHas && bHas && !propertySignatures[i](a[name], b[name])) { - return false - } - } - // --------------------------------------------- - // handle index signatures - // --------------------------------------------- - for (let i = 0; i < indexSignatures.length; i++) { - const is = ast.indexSignatures[i] - const aKeys = SchemaAST.getIndexSignatureKeys(a, is.parameter) - const bKeys = SchemaAST.getIndexSignatureKeys(b, is.parameter) - - if (aKeys.length !== bKeys.length) return false - - for (let j = 0; j < aKeys.length; j++) { - const key = aKeys[j] - if (!Object.hasOwn(b, key) || !indexSignatures[i](a[key], b[key])) { - return false - } - } - } - return true - }) - } - case "Union": - return Equivalence.make((a, b) => { - const candidates = SchemaAST.getCandidates(a, ast.types) - const types = candidates.map(SchemaParser._is) - for (let i = 0; i < candidates.length; i++) { - const is = types[i] - if (is(a) && is(b)) { - return recur(candidates[i], path)(a, b) - } - } - return false - }) - case "Suspend": { - const get = SchemaAST.memoizeThunk(() => recur(ast.thunk(), path)) - return Equivalence.make((a, b) => get()(a, b)) - } - } -} diff --git a/repos/effect/packages/effect/src/internal/schema/fromJsonSchemaDocument.ts b/repos/effect/packages/effect/src/internal/schema/fromJsonSchemaDocument.ts new file mode 100644 index 0000000000..590802eff1 --- /dev/null +++ b/repos/effect/packages/effect/src/internal/schema/fromJsonSchemaDocument.ts @@ -0,0 +1,977 @@ +import { unescapeToken } from "../../JsonPointer.ts" +import type * as JsonSchema from "../../JsonSchema.ts" +import { remainder } from "../../Number.ts" +import * as Schema from "../../Schema.ts" +import * as SchemaAST from "../../SchemaAST.ts" +import type * as SchemaRepresentation from "../../SchemaRepresentation.ts" +import { errorWithPath } from "../errors.ts" +import * as InternalRecord from "../record.ts" +import * as InternalAnnotations from "./annotations.ts" +import { fromRepresentation, fromRepresentations } from "./fromRepresentation.ts" + +type Path = ReadonlyArray +type Representation = SchemaRepresentation.Representation +type Check = SchemaRepresentation.Check + +type ImportedJsonSchemaRepresentation = Extract + +const never: ImportedJsonSchemaRepresentation = { _tag: "Never", checks: [] } +const unknown: ImportedJsonSchemaRepresentation = { _tag: "Unknown", checks: [] } +const string: ImportedJsonSchemaRepresentation = { _tag: "String", checks: [] } + +function makeLiteral(literal: string | number | boolean): SchemaRepresentation.Literal { + return { _tag: "Literal", literal, checks: [] } +} + +function annotate( + representation: ImportedJsonSchemaRepresentation, + annotations: Schema.Annotations.Annotations | undefined +): ImportedJsonSchemaRepresentation { + if (annotations === undefined) return representation + if (representation._tag === "Reference") { + return { + _tag: "Suspend", + annotations, + checks: [], + thunk: representation + } + } + return { + ...representation, + annotations: { + ...representation.annotations, + ...annotations + } + } +} + +const jsonSchemaTypes = new Set([ + "null", + "string", + "number", + "integer", + "boolean", + "object", + "array" +]) + +const jsonSchemaStringKeys = ["minLength", "maxLength", "pattern", "format", "contentMediaType", "contentSchema"] +const jsonSchemaNumberKeys = ["minimum", "maximum", "exclusiveMinimum", "exclusiveMaximum", "multipleOf"] +const jsonSchemaObjectKeys = [ + "properties", + "required", + "additionalProperties", + "patternProperties", + "propertyNames", + "minProperties", + "maxProperties" +] +const jsonSchemaArrayKeys = ["items", "prefixItems", "additionalItems", "minItems", "maxItems", "uniqueItems"] +function isImportedJsonSchemaType(input: unknown): input is JsonSchema.Type { + return typeof input === "string" && jsonSchemaTypes.has(input) +} + +function inferJsonSchemaType(schema: JsonSchema.JsonSchema): JsonSchema.Type | undefined { + if (jsonSchemaStringKeys.some((key) => schema[key] !== undefined)) return "string" + if (jsonSchemaNumberKeys.some((key) => schema[key] !== undefined)) return "number" + if (jsonSchemaObjectKeys.some((key) => schema[key] !== undefined)) return "object" + if (jsonSchemaArrayKeys.some((key) => schema[key] !== undefined)) return "array" +} + +function jsonSchemaReferenceKey($ref: string): string | undefined { + const token = $ref.slice($ref.lastIndexOf("/") + 1) + return token.length === 0 ? undefined : unescapeToken(token) +} + +function jsonSchemaFilter( + id: string, + payload: Schema.Json, + schemas?: ReadonlyArray +): Check { + return { + _tag: "Filter", + aborted: false, + representation: { + id, + payload, + ...(schemas === undefined ? undefined : { schemas }) + } + } +} + +function addNumberCheck( + checks: Array, + value: unknown, + id: string, + key: string +): void { + if (typeof value === "number") { + checks.push(jsonSchemaFilter(id, { [key]: value })) + } +} + +function jsonSchemaAnnotations( + schema: JsonSchema.JsonSchema +): Schema.Annotations.Annotations | undefined { + const annotations: Record = {} + if (typeof schema.title === "string") annotations.title = schema.title + if (typeof schema.description === "string") annotations.description = schema.description + if (Object.hasOwn(schema, "default")) annotations.default = schema.default as Schema.Json + if (Array.isArray(schema.examples)) annotations.examples = schema.examples as ReadonlyArray + if (typeof schema.readOnly === "boolean") annotations.readOnly = schema.readOnly + if (typeof schema.writeOnly === "boolean") annotations.writeOnly = schema.writeOnly + if (typeof schema.format === "string") annotations.format = schema.format + if (typeof schema.contentEncoding === "string") annotations.contentEncoding = schema.contentEncoding + if (typeof schema.contentMediaType === "string") annotations.contentMediaType = schema.contentMediaType + if (SchemaAST.isJson(schema.contentSchema)) annotations.contentSchema = schema.contentSchema + return Object.keys(annotations).length === 0 ? undefined : annotations +} + +function jsonDeclaration( + annotations: Schema.Annotations.Annotations | undefined +): Representation { + return { + _tag: "Declaration", + representation: { + id: "effect/schema/Json", + payload: null + }, + annotations: { + ...annotations, + expected: "JSON value" + }, + checks: [], + typeParameters: [] + } +} + +function unknownJsonSchemas(representation: Representation): Representation { + switch (representation._tag) { + case "Unknown": + return jsonDeclaration(representation.annotations) + case "Suspend": + return { ...representation, thunk: unknownJsonSchemas(representation.thunk) } + case "Arrays": + return { + ...representation, + elements: representation.elements.map((element) => ({ + ...element, + type: unknownJsonSchemas(element.type) + })), + rest: representation.rest.map(unknownJsonSchemas) + } + case "Objects": + return { + ...representation, + propertySignatures: representation.propertySignatures.map((property) => ({ + ...property, + type: unknownJsonSchemas(property.type) + })), + indexSignatures: representation.indexSignatures.map((indexSignature) => ({ + parameter: unknownJsonSchemas(indexSignature.parameter), + type: unknownJsonSchemas(indexSignature.type) + })), + checks: representation.checks.map(unknownJsonSchemaCheck) + } + case "Union": + return { ...representation, types: representation.types.map(unknownJsonSchemas) } + default: + return representation + } +} + +function unknownJsonSchemaCheck(check: Check): Check { + const representation = check.representation + const schemas = representation?.schemas + if (representation === undefined || schemas === undefined) { + return check + } + return { + ...check, + representation: { + ...representation, + schemas: schemas.map(unknownJsonSchemas) + } + } +} + +function translateJsonSchemaMultiDocument( + document: JsonSchema.MultiDocument<"draft-2020-12">, + options?: SchemaRepresentation.FromJsonSchemaOptions, + singleRoot = false +): SchemaRepresentation.MultiDocument { + const definitionCache = new Map() + const reachableDefinitions = new Map() + const annotatedReferences: Array<{ + readonly reference: SchemaRepresentation.Reference + readonly path: Path + }> = [] + + function translateDefinition( + key: string, + path: Path, + recursiveReferenceError?: string + ): ImportedJsonSchemaRepresentation { + const cached = definitionCache.get(key) + if (cached !== undefined) { + if (cached === null) { + throw errorWithPath(recursiveReferenceError ?? `Invalid reference ${key}`, [...path, "$ref"]) + } + return cached + } + if (!Object.hasOwn(document.definitions, key)) { + throw errorWithPath(`Invalid reference ${key}`, [...path, "$ref"]) + } + definitionCache.set(key, null) + const representation = recur(document.definitions[key], ["definitions", key]) + definitionCache.set(key, representation) + return representation + } + + function resolveReference( + reference: SchemaRepresentation.Reference, + path: Path, + options?: { readonly recursiveReferenceError?: string }, + seen: ReadonlySet = new Set() + ): ImportedJsonSchemaRepresentation { + if (seen.has(reference.$ref)) { + throw errorWithPath(`Invalid reference ${reference.$ref}`, [...path, "$ref"]) + } + const nextSeen = new Set(seen).add(reference.$ref) + const representation = translateDefinition(reference.$ref, path, options?.recursiveReferenceError) + if (representation._tag === "Reference") { + return resolveReference(representation, path, options, nextSeen) + } + if (representation._tag === "Suspend" && representation.thunk._tag === "Reference") { + return annotate( + resolveReference(representation.thunk, path, options, nextSeen), + representation.annotations + ) + } + return representation + } + + function annotationsOf( + representation: ImportedJsonSchemaRepresentation + ): Schema.Annotations.Annotations | undefined { + return representation._tag === "Reference" ? undefined : representation.annotations + } + + function mergeAnnotations( + left: Schema.Annotations.Annotations | undefined, + right: Schema.Annotations.Annotations | undefined + ): Schema.Annotations.Annotations | undefined { + if (left === undefined) return right + if (right === undefined) return left + return { ...left, ...right } + } + + function combinedAnnotations( + representation: ImportedJsonSchemaRepresentation, + left: ImportedJsonSchemaRepresentation, + right: ImportedJsonSchemaRepresentation + ): ImportedJsonSchemaRepresentation { + return annotate( + representation, + mergeAnnotations(annotationsOf(left), annotationsOf(right)) + ) + } + + function asChecks( + checks: ReadonlyArray, + annotations: Schema.Annotations.Annotations | undefined + ): ReadonlyArray | undefined { + if (checks.length === 0) return undefined + if (annotations === undefined) return checks + if (checks.length === 1 && checks[0].annotations === undefined) { + return [{ + ...checks[0], + annotations + }] + } + return [{ + _tag: "FilterGroup", + checks: checks as [Check, ...Array], + annotations + }] + } + + function combineChecks( + left: ReadonlyArray, + right: ReadonlyArray, + annotations: Schema.Annotations.Annotations | undefined, + deduplicate: ReadonlyArray = [] + ): ReadonlyArray | undefined { + for (const id of deduplicate) { + if (left.some((check) => checkId(check) === id)) { + right = right.filter((check) => checkId(check) !== id) + } + } + const checks = asChecks(right, annotations) + return checks === undefined ? undefined : [...left, ...checks] + } + + function checkId(check: Check): string | undefined { + return check._tag === "Filter" ? check.representation?.id : undefined + } + + function satisfiesPrimitiveCheck(check: Check, value: string | number): boolean | undefined { + if (check._tag === "FilterGroup") { + return check.checks.every((check) => satisfiesPrimitiveCheck(check, value)) + } + const representation = check.representation! + const payload = representation.payload as Record + switch (representation.id) { + case "effect/schema/isMinLength": + return (value as string).length >= payload.minLength + case "effect/schema/isMaxLength": + return (value as string).length <= payload.maxLength + case "effect/schema/isPattern": + return new RegExp(payload.source as string, payload.flags as string).test(value as string) + case "effect/schema/isFinite": + return globalThis.Number.isFinite(value as number) + case "effect/schema/isInt": + return globalThis.Number.isSafeInteger(value as number) + case "effect/schema/isMultipleOf": + return remainder(value as number, payload.divisor) === 0 + case "effect/schema/isGreaterThan": + return (value as number) > payload.exclusiveMinimum + case "effect/schema/isGreaterThanOrEqualTo": + return (value as number) >= payload.minimum + case "effect/schema/isLessThan": + return (value as number) < payload.exclusiveMaximum + case "effect/schema/isLessThanOrEqualTo": + return (value as number) <= payload.maximum + } + } + + function satisfiesLiteral( + representation: + | SchemaRepresentation.String + | SchemaRepresentation.Number, + literal: SchemaRepresentation.Literal + ): boolean { + const value = literal.literal + if (representation._tag === "String" ? typeof value !== "string" : typeof value !== "number") { + return false + } + return representation.checks.every((check) => satisfiesPrimitiveCheck(check, value as string | number)) + } + + function combinePrimitiveWithLiteral( + primitive: + | SchemaRepresentation.String + | SchemaRepresentation.Number + | SchemaRepresentation.Boolean, + literal: SchemaRepresentation.Literal + ): ImportedJsonSchemaRepresentation { + const satisfies = primitive._tag === "Boolean" + ? typeof literal.literal === "boolean" + : satisfiesLiteral(primitive, literal) + return satisfies ? combinedAnnotations(literal, primitive, literal) : never + } + + function combineArrays( + left: SchemaRepresentation.Arrays, + right: SchemaRepresentation.Arrays, + path: Path + ): Pick | undefined { + const elements: Array = [] + const length = Math.max(left.elements.length, right.elements.length) + for (let index = 0; index < length; index++) { + const leftElement = left.elements[index] + const rightElement = right.elements[index] + const isOptional = leftElement?.isOptional !== false && rightElement?.isOptional !== false + const leftType = leftElement?.type ?? left.rest[0] + const rightType = rightElement?.type ?? right.rest[0] + if (leftType === undefined || rightType === undefined) { + return isOptional ? { elements, rest: [] } : undefined + } + const type = combine( + leftType as ImportedJsonSchemaRepresentation, + rightType as ImportedJsonSchemaRepresentation, + [...path, "elements", index, "type"] + ) + if (type._tag === "Never") { + return isOptional ? { elements, rest: [] } : undefined + } + elements.push({ + isOptional, + type + }) + } + + const leftRest = left.rest[0] + const rightRest = right.rest[0] + if (leftRest === undefined || rightRest === undefined) { + return { elements, rest: [] } + } + const rest = combine( + leftRest as ImportedJsonSchemaRepresentation, + rightRest as ImportedJsonSchemaRepresentation, + [...path, "rest", 0] + ) + return { elements, rest: rest._tag === "Never" ? [] : [rest] } + } + + function combineProperties( + left: ReadonlyArray, + right: ReadonlyArray, + path: Path + ): Array { + const rightByName = new Map(right.map((property) => [property.name, property])) + const names = new Set() + const properties = left.map((property) => { + const name = property.name + names.add(name) + const other = rightByName.get(name) + if (other === undefined) return property + return { + name: property.name, + type: combine( + property.type as ImportedJsonSchemaRepresentation, + other.type as ImportedJsonSchemaRepresentation, + [...path, "properties", globalThis.String(name)] + ), + isOptional: property.isOptional && other.isOptional, + isMutable: false + } + }) + for (const property of right) { + if (!names.has(property.name)) properties.push(property) + } + return properties + } + + function isUnconstrainedString(representation: Representation): boolean { + return representation._tag === "String" && representation.checks.length === 0 && + representation.annotations === undefined + } + + function combineIndexSignatures( + left: ReadonlyArray, + right: ReadonlyArray, + path: Path + ): Array { + if (left.length === 0 || right.length === 0) return [] + const signatures = [...left] + for (const signature of right) { + if (isUnconstrainedString(signature.parameter)) { + const index = signatures.findIndex((candidate) => isUnconstrainedString(candidate.parameter)) + if (index !== -1) { + signatures[index] = { + parameter: signatures[index].parameter, + type: combine( + signatures[index].type as ImportedJsonSchemaRepresentation, + signature.type as ImportedJsonSchemaRepresentation, + [...path, "indexSignatures", index, "type"] + ) + } + } else { + signatures.push(signature) + } + } else { + signatures.push(signature) + } + } + return signatures + } + + function combine( + left: ImportedJsonSchemaRepresentation, + right: ImportedJsonSchemaRepresentation, + path: Path + ): ImportedJsonSchemaRepresentation { + if (left._tag === "Never") return left + if (right._tag === "Never") return right + if (left._tag === "Unknown") return combinedAnnotations(right, left, right) + if (right._tag === "Unknown") return combinedAnnotations(left, left, right) + if (left._tag === "Reference") return combine(resolveReference(left, path), right, path) + if (right._tag === "Reference") return combine(left, resolveReference(right, path), path) + if (left._tag === "Suspend") { + return annotate( + combine(left.thunk as ImportedJsonSchemaRepresentation, right, path), + left.annotations + ) + } + if (right._tag === "Suspend") { + return annotate( + combine(left, right.thunk as ImportedJsonSchemaRepresentation, path), + right.annotations + ) + } + if (left._tag === "Union") { + const types = left.types + .map((type, index) => combine(type as ImportedJsonSchemaRepresentation, right, [...path, "types", index])) + .filter((type) => type._tag !== "Never") + if (types.length === 0) return never + return annotate({ + _tag: "Union", + types, + mode: left.mode, + checks: left.checks + }, left.annotations) + } + if (right._tag === "Union") return combine(right, left, path) + + switch (left._tag) { + case "Null": + return right._tag === "Null" + ? combinedAnnotations({ _tag: "Null", checks: [...left.checks, ...right.checks] }, left, right) + : never + case "String": + if (right._tag === "Literal") { + return combinePrimitiveWithLiteral(left, right) + } + if (right._tag !== "String") return never + const stringChecks = combineChecks(left.checks, right.checks, right.annotations) + return annotate( + { + _tag: "String", + checks: stringChecks ?? left.checks + }, + mergeAnnotations(left.annotations, stringChecks === undefined ? right.annotations : undefined) + ) + case "Number": + if (right._tag === "Literal") { + return combinePrimitiveWithLiteral(left, right) + } + if (right._tag !== "Number") return never + const numberChecks = combineChecks(left.checks, right.checks, right.annotations, [ + "effect/schema/isFinite", + "effect/schema/isInt" + ]) + return annotate( + { + _tag: "Number", + checks: numberChecks ?? left.checks + }, + mergeAnnotations(left.annotations, numberChecks === undefined ? right.annotations : undefined) + ) + case "Boolean": + if (right._tag === "Literal") { + return combinePrimitiveWithLiteral(left, right) + } + return right._tag === "Boolean" + ? combinedAnnotations( + { + _tag: "Boolean", + checks: [...left.checks, ...right.checks] + }, + left, + right + ) + : never + case "Literal": + if (right._tag === "Literal") { + return left.literal === right.literal + ? combinedAnnotations( + { + ...left, + checks: [...left.checks, ...right.checks] + }, + left, + right + ) + : never + } + if ( + (right._tag === "String" || right._tag === "Number") && satisfiesLiteral(right, left) || + right._tag === "Boolean" && typeof left.literal === "boolean" + ) { + return combinedAnnotations(left, left, right) + } + return never + case "Arrays": { + if (right._tag !== "Arrays") return never + const arrays = combineArrays(left, right, path) + if (arrays === undefined) return never + const arrayChecks = combineChecks(left.checks, right.checks, right.annotations, ["effect/schema/isUnique"]) + return annotate( + { + _tag: "Arrays", + elements: arrays.elements, + rest: arrays.rest, + checks: arrayChecks ?? left.checks + }, + mergeAnnotations(left.annotations, arrayChecks === undefined ? right.annotations : undefined) + ) + } + case "Objects": { + if (right._tag !== "Objects") return never + const objectChecks = combineChecks(left.checks, right.checks, right.annotations) + return annotate( + { + _tag: "Objects", + propertySignatures: combineProperties(left.propertySignatures, right.propertySignatures, path), + indexSignatures: combineIndexSignatures(left.indexSignatures, right.indexSignatures, path), + checks: objectChecks ?? left.checks + }, + mergeAnnotations(left.annotations, objectChecks === undefined ? right.annotations : undefined) + ) + } + } + } + + function enter(input: unknown): JsonSchema.JsonSchema | undefined { + if (typeof input !== "object" || input === null || Array.isArray(input)) { + return undefined + } + const schema = input as JsonSchema.JsonSchema + return options?.onEnter === undefined ? schema : options.onEnter(schema) + } + + function recur(input: unknown, path: Path): ImportedJsonSchemaRepresentation { + if (input === false) { + return never + } + const schema = enter(input) + if (schema === undefined) { + return unknown + } + + let representation = on(schema, path) + if (representation._tag === "Reference") { + const siblingSchema: JsonSchema.JsonSchema = { ...schema, $ref: undefined } + for (const key of InternalAnnotations.jsonSchemaAnnotationKeys) { + delete siblingSchema[key] + } + const sibling = on(siblingSchema, path) + if (sibling._tag !== "Unknown") { + const reference = representation + representation = combine( + resolveReference(reference, path, { + recursiveReferenceError: `Unsupported assertion siblings on recursive reference ${reference.$ref}` + }), + sibling, + path + ) + } + } + const annotations = jsonSchemaAnnotations(schema) + if (annotations !== undefined && representation._tag === "Reference") { + annotatedReferences.push({ reference: representation, path }) + } + representation = annotate(representation, annotations) + + if (Array.isArray(schema.allOf)) { + for (let index = 0; index < schema.allOf.length; index++) { + representation = combine( + representation, + recur(schema.allOf[index], [...path, "allOf", index]), + [...path, "allOf", index] + ) + } + } + + for (const mode of ["anyOf", "oneOf"] as const) { + const members = schema[mode] + if (Array.isArray(members)) { + const union: ImportedJsonSchemaRepresentation = { + _tag: "Union", + types: members.map((member, index) => recur(member, [...path, mode, index])), + mode, + checks: [] + } + representation = combine(union, representation, [...path, mode]) + } + } + return representation + } + + function on(schema: JsonSchema.JsonSchema, path: Path): ImportedJsonSchemaRepresentation { + if (typeof schema.$ref === "string") { + const $ref = jsonSchemaReferenceKey(schema.$ref) + if ($ref !== undefined) { + if (!reachableDefinitions.has($ref)) reachableDefinitions.set($ref, path) + return { _tag: "Reference", $ref } + } + } + if (Object.hasOwn(schema, "const")) { + if (schema.const === null) { + return { _tag: "Null", checks: [] } + } + if (typeof schema.const === "string" || typeof schema.const === "number" || typeof schema.const === "boolean") { + return makeLiteral(schema.const) + } + } + if (Array.isArray(schema.enum)) { + const types: Array = [] + for (let index = 0; index < schema.enum.length; index++) { + const value = schema.enum[index] + if (value === null) { + types.push({ _tag: "Null", checks: [] }) + } else if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") { + types.push(makeLiteral(value)) + } else { + types.push(recur(value, [...path, "enum", index])) + } + } + if (types.length === 1) { + return types[0] + } + return { _tag: "Union", types, mode: "anyOf", checks: [] } + } + + if (Array.isArray(schema.type) && schema.type.every(isImportedJsonSchemaType)) { + return { + _tag: "Union", + types: schema.type.map((type) => on({ ...schema, type }, path)), + mode: "anyOf", + checks: [] + } + } + + const type = isImportedJsonSchemaType(schema.type) ? schema.type : inferJsonSchemaType(schema) + switch (type) { + case "null": + return { _tag: "Null", checks: [] } + case "string": + return { + _tag: "String", + checks: collectStringChecks(schema, path) + } + case "number": + case "integer": + return { + _tag: "Number", + checks: [ + jsonSchemaFilter(type === "number" ? "effect/schema/isFinite" : "effect/schema/isInt", null), + ...collectNumberChecks(schema) + ] + } + case "boolean": + return { _tag: "Boolean", checks: [] } + case "array": { + const prefixItems = Array.isArray(schema.prefixItems) ? schema.prefixItems : undefined + const minItems = typeof schema.minItems === "number" ? schema.minItems : 0 + const elements = prefixItems?.map((element, index) => ({ + isOptional: index + 1 > minItems, + type: recur(element, [...path, "prefixItems", index]) + })) ?? [] + const isTupleClosed = schema.items === false || + (schema.items === undefined && + prefixItems !== undefined && + schema.maxItems === prefixItems.length) + const isMaxItemsRedundant = isTupleClosed && + typeof schema.maxItems === "number" && + schema.maxItems >= elements.length + return { + _tag: "Arrays", + elements, + rest: isTupleClosed + ? [] + : [schema.items === undefined ? unknown : recur(schema.items, [...path, "items"])], + checks: collectArrayChecks(schema, isMaxItemsRedundant) + } + } + case "object": + return { + _tag: "Objects", + propertySignatures: collectProperties(schema, path), + indexSignatures: collectIndexSignatures(schema, path), + checks: collectObjectChecks(schema, path) + } + default: + return unknown + } + } + + function importPatternChecks(pattern: string, path: Path): Array { + switch (options?.patterns ?? "error") { + case "error": + throw errorWithPath(`Pattern encountered while patterns is set to "error"`, path) + case "ignore": + return [] + case "apply": + return [jsonSchemaFilter("effect/schema/isPattern", { source: pattern, flags: "" })] + } + } + + function collectStringChecks(schema: JsonSchema.JsonSchema, path: Path): Array { + const checks: Array = [] + addNumberCheck(checks, schema.minLength, "effect/schema/isMinLength", "minLength") + addNumberCheck(checks, schema.maxLength, "effect/schema/isMaxLength", "maxLength") + if (typeof schema.pattern === "string") { + checks.push(...importPatternChecks(schema.pattern, [...path, "pattern"])) + } + return checks + } + + function collectNumberChecks(schema: JsonSchema.JsonSchema): Array { + const checks: Array = [] + addNumberCheck(checks, schema.minimum, "effect/schema/isGreaterThanOrEqualTo", "minimum") + addNumberCheck(checks, schema.maximum, "effect/schema/isLessThanOrEqualTo", "maximum") + addNumberCheck(checks, schema.exclusiveMinimum, "effect/schema/isGreaterThan", "exclusiveMinimum") + addNumberCheck(checks, schema.exclusiveMaximum, "effect/schema/isLessThan", "exclusiveMaximum") + addNumberCheck(checks, schema.multipleOf, "effect/schema/isMultipleOf", "divisor") + return checks + } + + function collectArrayChecks(schema: JsonSchema.JsonSchema, isMaxItemsRedundant: boolean): Array { + const checks: Array = [] + if (schema.prefixItems === undefined) { + addNumberCheck(checks, schema.minItems, "effect/schema/isMinLength", "minLength") + } + if (!isMaxItemsRedundant) { + addNumberCheck(checks, schema.maxItems, "effect/schema/isMaxLength", "maxLength") + } + if (schema.uniqueItems === true) { + checks.push(jsonSchemaFilter("effect/schema/isUnique", null)) + } + return checks + } + + function collectProperties( + schema: JsonSchema.JsonSchema, + path: Path + ): Array { + const properties = + typeof schema.properties === "object" && schema.properties !== null && !Array.isArray(schema.properties) + ? schema.properties as Record + : {} + const required = Array.isArray(schema.required) + ? schema.required.filter((key): key is string => typeof key === "string") + : [] + const keys = new Set([...Object.keys(properties), ...required]) + return Array.from(keys, (name) => ({ + name, + type: recur(properties[name], [...path, "properties", name]), + isOptional: !required.includes(name), + isMutable: false + })) + } + + function collectIndexSignatures( + schema: JsonSchema.JsonSchema, + path: Path + ): Array { + const signatures: Array = [] + if ( + typeof schema.patternProperties === "object" && + schema.patternProperties !== null && + !Array.isArray(schema.patternProperties) + ) { + for (const [pattern, value] of Object.entries(schema.patternProperties)) { + const checks = importPatternChecks(pattern, [...path, "patternProperties", pattern]) + if (checks.length === 0) return [{ parameter: string, type: unknown }] + signatures.push({ + parameter: { + _tag: "String", + checks + }, + type: recur(value, [...path, "patternProperties", pattern]) + }) + } + } + if (schema.additionalProperties === undefined || schema.additionalProperties === true) { + signatures.push({ + parameter: string, + type: unknown + }) + } else if (typeof schema.additionalProperties === "object" && schema.additionalProperties !== null) { + signatures.push({ + parameter: string, + type: recur(schema.additionalProperties, [...path, "additionalProperties"]) + }) + } + return signatures + } + + function collectObjectChecks( + schema: JsonSchema.JsonSchema, + path: Path + ): Array { + const checks: Array = [] + addNumberCheck(checks, schema.minProperties, "effect/schema/isMinProperties", "minProperties") + addNumberCheck(checks, schema.maxProperties, "effect/schema/isMaxProperties", "maxProperties") + if (schema.propertyNames !== undefined) { + checks.push(jsonSchemaFilter( + "effect/schema/isPropertyNames", + null, + [recur(schema.propertyNames, [...path, "propertyNames"])] + )) + } + return checks + } + + const references: Record = {} + const representations = document.schemas.map((schema, index) => + unknownJsonSchemas(recur(schema, singleRoot ? ["schema"] : ["schemas", index])) + ) as [Representation, ...Array] + for (const [key, path] of reachableDefinitions) { + InternalRecord.assignProperty(references, key, unknownJsonSchemas(translateDefinition(key, path))) + } + for (const { reference, path } of annotatedReferences) { + resolveReference(reference, path) + } + return { representations, references } +} + +/** @internal */ +function toRepresentation( + document: JsonSchema.Document<"draft-2020-12">, + options?: SchemaRepresentation.FromJsonSchemaOptions +): SchemaRepresentation.Document { + const translated = translateJsonSchemaMultiDocument( + { + dialect: document.dialect, + schemas: [document.schema], + definitions: document.definitions + }, + options, + true + ) + return { + representation: translated.representations[0], + references: translated.references + } +} + +const jsonSchemaRevivers: ReadonlyArray = [ + Schema.JsonReviver, + Schema.isPatternReviver, + Schema.isFiniteReviver, + Schema.isGreaterThanReviver, + Schema.isGreaterThanOrEqualToReviver, + Schema.isLessThanReviver, + Schema.isLessThanOrEqualToReviver, + Schema.isMultipleOfReviver, + Schema.isIntReviver, + Schema.isMinLengthReviver, + Schema.isMaxLengthReviver, + Schema.isMinPropertiesReviver, + Schema.isMaxPropertiesReviver, + Schema.isPropertyNamesReviver, + Schema.isUniqueReviver +] + +/** @internal */ +export function fromJsonSchemaDocument( + document: JsonSchema.Document<"draft-2020-12">, + options?: SchemaRepresentation.FromJsonSchemaOptions +): Schema.Top { + return fromRepresentation(toRepresentation(document, options), jsonSchemaRevivers) +} + +/** @internal */ +export function fromJsonSchemaMultiDocument( + document: JsonSchema.MultiDocument<"draft-2020-12">, + options?: SchemaRepresentation.FromJsonSchemaOptions +): readonly [Schema.Top, ...Array] { + return fromRepresentations(translateJsonSchemaMultiDocument(document, options), jsonSchemaRevivers) +} diff --git a/repos/effect/packages/effect/src/internal/schema/fromRepresentation.ts b/repos/effect/packages/effect/src/internal/schema/fromRepresentation.ts new file mode 100644 index 0000000000..30b647beb0 --- /dev/null +++ b/repos/effect/packages/effect/src/internal/schema/fromRepresentation.ts @@ -0,0 +1,339 @@ +import * as Arr from "../../Array.ts" +import * as Result from "../../Result.ts" +import * as Schema from "../../Schema.ts" +import type * as SchemaAST from "../../SchemaAST.ts" +import type * as SchemaRepresentation from "../../SchemaRepresentation.ts" +import { errorWithPath } from "../errors.ts" +import * as InternalRecord from "../record.ts" + +type Path = ReadonlyArray + +/** @internal */ +export function fromRepresentations( + document: SchemaRepresentation.MultiDocument, + revivers: ReadonlyArray +): readonly [Schema.Top, ...Array] { + return revivePersisted(document.representations, document.references, makeReviverMap(revivers), false) +} + +class ReferenceSlot { + body: Schema.Top | undefined + resolving = false + readonly wrapper: Schema.Top + + constructor(key: string) { + this.wrapper = Schema.suspend(() => { + if (this.body === undefined) { + throw new Error(`Reference ${key} was evaluated before it was resolved`) + } + return this.body + }) + } +} + +function makeReviverMap( + revivers: ReadonlyArray +): Map { + const out = new Map() + + for (let index = 0; index < revivers.length; index++) { + const reviver = revivers[index] + if (out.has(reviver.id)) { + throw errorWithPath(`Duplicate reviver for ${reviver.id}`, ["revivers", index, "id"]) + } + out.set(reviver.id, { + ...reviver, + payloadSchema: Schema.toCodecJson(reviver.payloadSchema) + }) + } + + return out +} + +function revivePersisted( + representations: readonly [ + SchemaRepresentation.Representation, + ...Array + ], + references: SchemaRepresentation.References, + reviverMap: ReadonlyMap, + singleRoot: boolean +): readonly [Schema.Top, ...Array] { + const slots = new Map() + + function resolveReference(key: string, path: Path): Schema.Top { + let slot = slots.get(key) + if (slot === undefined) { + if (!Object.hasOwn(references, key)) { + throw errorWithPath(`Invalid reference ${key}`, [...path, "$ref"]) + } + slot = new ReferenceSlot(key) + slots.set(key, slot) + } + if (slot.body !== undefined) { + return slot.body + } + if (slot.resolving) { + return slot.wrapper + } + + slot.resolving = true + try { + slot.body = annotate(recur(references[key], ["references", key]), { identifier: key }) + return slot.body + } finally { + slot.resolving = false + } + } + + function resolveReviver( + representation: SchemaRepresentation.RepresentationAnnotation, + path: Path + ): R { + const reviver = reviverMap.get(representation.id) + if (reviver === undefined) { + throw errorWithPath(`Missing reviver for ${representation.id}`, path) + } + return reviver as R + } + + function decodePayload( + representation: SchemaRepresentation.RepresentationAnnotation, + reviver: SchemaRepresentation.AnyReviver, + path: Path + ): any { + const decoded = Schema.decodeUnknownResult(reviver.payloadSchema)(representation.payload) + if (Result.isFailure(decoded)) { + throw errorWithPath(`Invalid representation payload for ${representation.id}`, path) + } + return decoded.success + } + + function reviveSchemas( + representations: ReadonlyArray, + path: Path + ): ReadonlyArray { + return representations.map((representation, index) => recur(representation, [...path, index])) + } + + function reviveDeclaration( + declaration: SchemaRepresentation.Declaration, + path: Path + ): Schema.Top { + const representationPath = [...path, "representation"] + const representation = declaration.representation + if (representation === undefined) { + throw errorWithPath("Missing representation annotation", representationPath) + } + const reviver = resolveReviver>(representation, representationPath) + const payload = decodePayload(representation, reviver, [...representationPath, "payload"]) + const typeParameters = reviveSchemas(declaration.typeParameters, [...path, "typeParameters"]) + const schema = reviver.revive({ payload, typeParameters, annotations: declaration.annotations }) + return appendChecks(schema, declaration.checks, [...path, "checks"]) + } + + function reviveFilter( + filter: SchemaRepresentation.Filter, + path: Path + ): SchemaAST.Filter { + const representationPath = [...path, "representation"] + const representation = filter.representation + if (representation === undefined) { + throw errorWithPath("Missing representation annotation", representationPath) + } + const reviver = resolveReviver>(representation, representationPath) + const payload = decodePayload(representation, reviver, [...representationPath, "payload"]) + const schemas = reviveSchemas(representation.schemas ?? [], [...representationPath, "schemas"]) + const check = reviver.revive({ payload, schemas, annotations: filter.annotations }) + return filter.aborted ? check.abort() : check + } + + function reviveFilterGroup( + group: SchemaRepresentation.FilterGroup, + path: Path + ): SchemaAST.FilterGroup { + const representationPath = [...path, "representation"] + const representation = group.representation + if (representation === undefined) { + const checks = group.checks.map((check, index) => reviveCheck(check, [...path, "checks", index])) + return Schema.makeFilterGroup( + checks as [SchemaAST.Check, ...Array>], + group.annotations as Schema.Annotations.Filter | undefined + ) + } + + const reviver = resolveReviver>(representation, representationPath) + const payload = decodePayload(representation, reviver, [...representationPath, "payload"]) + const schemas = reviveSchemas(representation.schemas ?? [], [...representationPath, "schemas"]) + return reviver.revive({ payload, schemas, annotations: group.annotations }) + } + + function reviveCheck( + check: SchemaRepresentation.Check, + path: Path + ): SchemaAST.Check { + return check._tag === "Filter" + ? reviveFilter(check, path) + : reviveFilterGroup(check, path) + } + + function appendChecks( + schema: S, + checks: ReadonlyArray, + path: Path + ): S["Rebuild"] { + const revived = checks.map((check, index) => reviveCheck(check, [...path, index])) + return Arr.isArrayNonEmpty(revived) ? schema.check(...revived) : schema as S["Rebuild"] + } + + function annotate( + schema: Schema.Top, + annotations: Schema.Annotations.Annotations | undefined + ): Schema.Top { + return annotations === undefined ? schema : schema.annotate(annotations) + } + + function finishStructural( + schema: Schema.Top, + representation: Exclude, + path: Path + ): Schema.Top { + return appendChecks( + annotate(schema, representation.annotations), + representation.checks, + [...path, "checks"] + ) + } + + function recur( + representation: SchemaRepresentation.Representation, + path: Path + ): Schema.Top { + switch (representation._tag) { + case "Reference": + return resolveReference(representation.$ref, path) + case "Declaration": + return reviveDeclaration(representation, path) + case "Suspend": { + const thunkPath = [...path, "thunk"] + if (representation.thunk._tag === "Reference") { + const key = representation.thunk.$ref + resolveReference(key, thunkPath) + const slot = slots.get(key)! + return annotate(slot.wrapper, representation.annotations) + } + const thunk = recur(representation.thunk, thunkPath) + return annotate(Schema.suspend(() => thunk), representation.annotations) + } + case "Null": + return finishStructural(Schema.Null, representation, path) + case "Undefined": + return finishStructural(Schema.Undefined, representation, path) + case "Void": + return finishStructural(Schema.Void, representation, path) + case "Never": + return finishStructural(Schema.Never, representation, path) + case "Unknown": + return finishStructural(Schema.Unknown, representation, path) + case "Any": + return finishStructural(Schema.Any, representation, path) + case "String": + return finishStructural(Schema.String, representation, path) + case "Number": + return finishStructural(Schema.Number, representation, path) + case "Boolean": + return finishStructural(Schema.Boolean, representation, path) + case "BigInt": + return finishStructural(Schema.BigInt, representation, path) + case "Symbol": + return finishStructural(Schema.Symbol, representation, path) + case "Literal": + return finishStructural(Schema.Literal(representation.literal), representation, path) + case "UniqueSymbol": + return finishStructural(Schema.UniqueSymbol(representation.symbol), representation, path) + case "ObjectKeyword": + return finishStructural(Schema.ObjectKeyword, representation, path) + case "Enum": + return finishStructural( + Schema.Enum(Object.fromEntries(representation.enums)), + representation, + path + ) + case "TemplateLiteral": { + const parts = representation.parts.map((part, index) => recur(part, [...path, "parts", index])) + return finishStructural( + Schema.TemplateLiteral(parts as unknown as Schema.TemplateLiteral.Parts), + representation, + path + ) + } + case "Arrays": { + const elements = representation.elements.map((element, index) => { + let schema = recur(element.type, [...path, "elements", index, "type"]) + if (element.annotations !== undefined) { + schema = schema.annotateKey(element.annotations as Schema.Annotations.Key) + } + return element.isOptional ? Schema.optionalKey(schema) : schema + }) + const rest = representation.rest.map((item, index) => recur(item, [...path, "rest", index])) + const schema = Arr.isArrayNonEmpty(rest) + ? elements.length === 0 && rest.length === 1 + ? Schema.Array(rest[0]) + : Schema.TupleWithRest(Schema.Tuple(elements), rest) + : Schema.Tuple(elements) + return finishStructural(schema, representation, path) + } + case "Objects": { + const fields: Record = {} + for (let index = 0; index < representation.propertySignatures.length; index++) { + const property = representation.propertySignatures[index] + let schema = recur(property.type, [...path, "propertySignatures", index, "type"]) + if (property.annotations !== undefined) { + schema = schema.annotateKey(property.annotations as Schema.Annotations.Key) + } + if (property.isOptional) { + schema = Schema.optionalKey(schema) + } + if (property.isMutable) { + schema = Schema.mutableKey(schema) + } + InternalRecord.assignProperty(fields, property.name, schema) + } + const records = representation.indexSignatures.map((indexSignature, index) => + Schema.Record( + recur(indexSignature.parameter, [...path, "indexSignatures", index, "parameter"]) as Schema.Record.Key, + recur(indexSignature.type, [...path, "indexSignatures", index, "type"]) + ) + ) + const schema = Arr.isArrayNonEmpty(records) + ? representation.propertySignatures.length === 0 && records.length === 1 + ? records[0] + : Schema.StructWithRest(Schema.Struct(fields), records) + : Schema.Struct(fields) + return finishStructural(schema, representation, path) + } + case "Union": { + const members = representation.types.map((member, index) => recur(member, [...path, "types", index])) + return finishStructural(Schema.Union(members, { mode: representation.mode }), representation, path) + } + } + } + + const schemas = representations.map((representation, index) => + recur(representation, singleRoot ? ["representation"] : ["representations", index]) + ) as [Schema.Top, ...Array] + return schemas +} + +/** @internal */ +export function fromRepresentation( + document: SchemaRepresentation.Document, + revivers: ReadonlyArray +): Schema.Top { + return revivePersisted( + [document.representation], + document.references, + makeReviverMap(revivers), + true + )[0] +} diff --git a/repos/effect/packages/effect/src/internal/schema/parser.ts b/repos/effect/packages/effect/src/internal/schema/parser.ts new file mode 100644 index 0000000000..77bd15057f --- /dev/null +++ b/repos/effect/packages/effect/src/internal/schema/parser.ts @@ -0,0 +1,22 @@ +import * as Exit from "../../Exit.ts" +import * as Option from "../../Option.ts" +import { args } from "../core.ts" + +export const missing = Symbol() + +export { args } + +export type Success = Exit.Success & { readonly [args]: A } + +export const succeed = Exit.succeed as (value: A) => Success + +export const missingExit = succeed(missing) + +// Shared success for a present input returned unchanged. It must be resolved +// before crossing an asynchronous or transformation boundary. +export const sameExit: Success = succeed(missing) + +export const toOption = (value: A): Option.Option => value === missing ? Option.none() : Option.some(value as A) + +export const fromOptionExit = (option: Option.Option): Success => + option._tag === "None" ? missingExit : succeed(option.value) diff --git a/repos/effect/packages/effect/src/internal/schema/representation.ts b/repos/effect/packages/effect/src/internal/schema/representation.ts deleted file mode 100644 index 5d4ea6aceb..0000000000 --- a/repos/effect/packages/effect/src/internal/schema/representation.ts +++ /dev/null @@ -1,795 +0,0 @@ -import * as Arr from "../../Array.ts" -import * as Equal from "../../Equal.ts" -import { format } from "../../Formatter.ts" -import { escapeToken } from "../../JsonPointer.ts" -import type * as JsonSchema from "../../JsonSchema.ts" -import * as Predicate from "../../Predicate.ts" -import * as Rec from "../../Record.ts" -import * as RegEx from "../../RegExp.ts" -import type * as Schema from "../../Schema.ts" -import * as SchemaAST from "../../SchemaAST.ts" -import type * as SchemaRepresentation from "../../SchemaRepresentation.ts" -import * as InternalAnnotations from "./annotations.ts" -import * as InternalSchema from "./schema.ts" - -/** @internal */ -export function fromAST(ast: SchemaAST.AST): SchemaRepresentation.Document { - const { references, representations: schemas } = fromASTs([ast]) - return { representation: schemas[0], references } -} - -/** @internal */ -export function fromASTs(asts: readonly [SchemaAST.AST, ...Array]): SchemaRepresentation.MultiDocument { - const references: Record = {} - - const referenceMap = new Map() - const uniqueReferences = new Set() - const visiting = new Set() - - const schemas = Arr.map(asts, (ast) => recur(ast)) - - return { - representations: schemas, - references - } - - function gen(prefix: string): string { - let candidate = prefix - let suffix = 0 - - while (uniqueReferences.has(candidate)) { - candidate = `${prefix}${++suffix}` - } - - uniqueReferences.add(candidate) - return candidate - } - - function recur(ast: SchemaAST.AST, prefix?: string): SchemaRepresentation.Representation { - const found = referenceMap.get(ast) - if (found !== undefined) { - return { _tag: "Reference", $ref: found } - } - - const last = SchemaAST.getLastEncoding(ast) - const identifier = InternalAnnotations.resolveIdentifier(ast) ?? prefix - - if (ast !== last) { - return recur(last, identifier) - } - - // Has identifier → always create reference - if (identifier !== undefined) { - const reference = gen(identifier) - referenceMap.set(ast, reference) - const out = on(ast) - const found = references[identifier] - // Reuse existing references when duplicate identifiers have the same representation - if (found !== undefined && Equal.equals(out, found)) { - referenceMap.set(ast, identifier) - return { _tag: "Reference", $ref: identifier } - } - references[reference] = out - return { _tag: "Reference", $ref: reference } - } - - // Recursion detected → create reference - if (visiting.has(ast)) { - const reference = gen(`${ast._tag}_`) - referenceMap.set(ast, reference) - return { _tag: "Reference", $ref: reference } - } - - // Normal case → inline - visiting.add(ast) - const out = on(ast) - visiting.delete(ast) - - // A descendant triggered reference creation (recursion) - const ref = referenceMap.get(ast) - if (ref !== undefined) { - references[ref] = out - return { _tag: "Reference", $ref: ref } - } - - return out - } - - function getEncodedSchema(last: SchemaAST.Declaration): SchemaAST.AST { - const getLink = last.annotations?.toCodecJson ?? last.annotations?.toCodec - if (Predicate.isFunction(getLink)) { - const tps = last.typeParameters.map((tp) => InternalSchema.make(SchemaAST.toEncoded(tp))) - const link = getLink(tps) - return SchemaAST.replaceEncoding(last, [link]) - } - return SchemaAST.null - } - - function on(last: SchemaAST.AST): SchemaRepresentation.Representation { - const annotations = fromASTAnnotations(last.annotations) - switch (last._tag) { - case "Declaration": { - // this must be executed before transforming the type parameters - const encodedSchema = recur(getEncodedSchema(last)) - return { - _tag: "Declaration", - typeParameters: last.typeParameters.map((ast) => recur(ast)), - encodedSchema, - checks: fromASTChecks(last.checks), - ...annotations - } - } - case "Null": - case "Undefined": - case "Void": - case "Never": - case "Unknown": - case "Any": - case "Boolean": - case "Symbol": - case "ObjectKeyword": - return { _tag: last._tag, ...annotations } - case "String": { - const contentMediaType = last.annotations?.contentMediaType - const contentSchema = last.annotations?.contentSchema - return { - _tag: last._tag, - checks: fromASTChecks(last.checks), - ...annotations, - ...(typeof contentMediaType === "string" && SchemaAST.isAST(contentSchema) - ? { contentSchema: recur(contentSchema) } - : undefined) - } - } - case "Number": - case "BigInt": - return { - _tag: last._tag, - checks: fromASTChecks(last.checks), - ...annotations - } - case "Literal": - return { - _tag: last._tag, - literal: last.literal, - ...annotations - } - case "UniqueSymbol": - return { - _tag: last._tag, - symbol: last.symbol, - ...annotations - } - case "Enum": - return { - _tag: last._tag, - enums: last.enums, - ...annotations - } - case "TemplateLiteral": - return { - _tag: last._tag, - parts: last.parts.map((ast) => recur(ast)), - ...annotations - } - case "Arrays": - return { - _tag: last._tag, - elements: last.elements.map((e) => { - const last = SchemaAST.getLastEncoding(e) - return { - isOptional: SchemaAST.isOptional(last), - type: recur(e), - ...fromASTAnnotations(last.context?.annotations) - } - }), - rest: last.rest.map((ast) => recur(ast)), - checks: fromASTChecks(last.checks), - ...annotations - } - case "Objects": - return { - _tag: last._tag, - propertySignatures: last.propertySignatures.map((ps) => { - const last = SchemaAST.getLastEncoding(ps.type) - return { - name: ps.name, - type: recur(ps.type), - isOptional: SchemaAST.isOptional(last), - isMutable: SchemaAST.isMutable(last), - ...fromASTAnnotations(last.context?.annotations) - } - }), - indexSignatures: last.indexSignatures.map((is) => ({ - parameter: recur(is.parameter), - type: recur(is.type) - })), - checks: fromASTChecks(last.checks), - ...annotations - } - case "Union": { - const types = InternalSchema.jsonReorder(last.types) - return { - _tag: last._tag, - types: types.map((ast) => recur(ast)), - mode: last.mode, - ...annotations - } - } - case "Suspend": { - return { - _tag: "Suspend", - checks: [], - thunk: recur(last.thunk()), - ...annotations - } - } - } - } - - function fromASTChecks( - checks: readonly [SchemaAST.Check, ...Array>] | undefined - ): Array> { - if (!checks) return [] - return checks.map(getCheck).filter((c) => c !== undefined) - - function getCheck(c: SchemaAST.Check): SchemaRepresentation.Check | undefined { - switch (c._tag) { - case "Filter": { - const meta = c.annotations?.meta - if (meta) { - return { - _tag: "Filter", - meta: meta._tag === "isPropertyNames" - ? { - _tag: "isPropertyNames", - propertyNames: recur(meta.propertyNames) - } - : meta, - ...fromASTAnnotations(c.annotations) - } - } - return undefined - } - case "FilterGroup": { - const checks = fromASTChecks(c.checks) - if (Arr.isArrayNonEmpty(checks)) { - return { - _tag: "FilterGroup", - checks, - ...fromASTAnnotations(c.annotations) - } - } - } - } - } - } -} - -/** @internal */ -export const fromASTBlacklist: Set = new Set([ - // `expected` is preserved because is useful to generate descriptions in JSON Schemas - "~structural", - "~sentinels", - "meta", - "arbitrary", - "toArbitrary", - "toEquivalence", - "toFormatter", - "toCodec", - "toCodecJson", - "toCodecIso", - SchemaAST.ClassTypeId -]) - -const standardJsonSchemaAnnotationKeys: ReadonlySet = new Set([ - "title", - "description", - "default", - "examples", - "readOnly", - "writeOnly", - "format", - "contentEncoding", - "contentMediaType", - "contentSchema" -]) - -function fromASTAnnotations( - annotations: Schema.Annotations.Annotations | undefined -): { annotations: Schema.Annotations.Annotations } | undefined { - if (annotations !== undefined) { - const filtered = Rec.filter(annotations, (_, k) => !fromASTBlacklist.has(k)) - if (!Rec.isEmptyRecord(filtered)) { - return { annotations: filtered } - } - } - return undefined -} - -/** @internal */ -export function toJsonSchemaDocument( - document: SchemaRepresentation.Document, - options?: Schema.ToJsonSchemaOptions -): JsonSchema.Document<"draft-2020-12"> { - const { definitions, dialect: source, schemas } = toJsonSchemaMultiDocument({ - representations: [document.representation], - references: document.references - }, options) - const schema = schemas[0] - return { dialect: source, schema, definitions } -} - -/** @internal */ -export function toJsonSchemaMultiDocument( - multiDocument: SchemaRepresentation.MultiDocument, - options?: Schema.ToJsonSchemaOptions -): JsonSchema.MultiDocument<"draft-2020-12"> { - const generateDescriptions = options?.generateDescriptions ?? false - const additionalProperties = options?.additionalProperties ?? false - const includeAnnotationKey = options?.includeAnnotationKey - - const definitions = Rec.map(multiDocument.references, (d) => recur(d)) - - return { - dialect: "draft-2020-12", - schemas: Arr.map(multiDocument.representations, (s) => recur(s)), - definitions - } - - function recur(s: SchemaRepresentation.Representation): JsonSchema.JsonSchema { - let js: JsonSchema.JsonSchema = on(s) - if ("annotations" in s) { - const a = collectJsonSchemaAnnotations(s.annotations) - if (a) { - js = { ...js, ...a } - } - } - if ("checks" in s) { - const checks = collectJsonSchemaChecks(s.checks, js.type) - for (const check of checks) { - js = appendJsonSchema(js, check) - } - } - return js - } - - function on(schema: SchemaRepresentation.Representation): JsonSchema.JsonSchema { - switch (schema._tag) { - case "Any": - case "Unknown": - return {} - case "ObjectKeyword": - return { anyOf: [{ type: "object" }, { type: "array" }] } - case "Void": - case "Undefined": - return { type: "null" } - case "BigInt": - return { - "type": "string", - "allOf": [ - { "pattern": "^-?\\d+$" } - ] - } - case "Symbol": - case "UniqueSymbol": - return { - "type": "string", - "allOf": [ - { "pattern": "^Symbol\\((.*)\\)$" } - ] - } - case "Declaration": - return recur(schema.encodedSchema) - case "Suspend": - return recur(schema.thunk) - case "Reference": - return { $ref: `#/$defs/${escapeToken(schema.$ref)}` } - case "Null": - return { type: "null" } - case "Never": - return { not: {} } - case "String": { - const out: JsonSchema.JsonSchema = { type: "string" } - if (schema.contentMediaType !== undefined) { - out.contentMediaType = schema.contentMediaType - } - if (schema.contentSchema !== undefined) { - out.contentSchema = recur(schema.contentSchema) - } - return out - } - case "Number": - return hasCheck(schema.checks, "isInt") ? - { type: "integer" } : - hasCheck(schema.checks, "isFinite") ? - { type: "number" } : - { - "anyOf": [ - { type: "number" }, - { type: "string", enum: ["NaN"] }, - { type: "string", enum: ["Infinity"] }, - { type: "string", enum: ["-Infinity"] } - ] - } - case "Boolean": - return { type: "boolean" } - case "Literal": { - const literal = schema.literal - if (typeof literal === "string") { - return { type: "string", enum: [literal] } - } - if (typeof literal === "number") { - return { type: "number", enum: [literal] } - } - if (typeof literal === "boolean") { - return { type: "boolean", enum: [literal] } - } - // bigint literals are not supported - return { type: "string", enum: [String(literal)] } - } - case "Enum": { - return recur({ - _tag: "Union", - types: schema.enums.map(([title, value]) => ({ - _tag: "Literal", - literal: value, - annotations: { title } - })), - mode: "anyOf", - annotations: schema.annotations - }) - } - case "TemplateLiteral": { - const pattern = schema.parts.map(getPartPattern).join("") - return { type: "string", pattern: `^${pattern}$` } - } - case "Arrays": { - // --------------------------------------------- - // handle post rest elements - // --------------------------------------------- - if (schema.rest.length > 1) { - throw new globalThis.Error("Generating a JSON Schema for post-rest elements is not supported") - } - const out: JsonSchema.JsonSchema = { type: "array" } - let minItems = schema.elements.length - const prefixItems: Array = schema.elements.map((e) => { - if (e.isOptional) { - minItems-- - } - const v = recur(e.type) - const a = collectJsonSchemaAnnotations(e.annotations) - return a ? appendJsonSchema(v, a) : v - }) - if (prefixItems.length > 0) { - out.prefixItems = prefixItems - out.maxItems = schema.elements.length - if (minItems > 0) { - out.minItems = minItems - } - } else { - out.items = false - } - if (schema.rest.length > 0) { - delete out.maxItems - const rest = recur(schema.rest[0]) - if (Object.keys(rest).length > 0) { - out.items = rest - } else { - delete out.items - } - } - return out - } - case "Objects": { - if (schema.propertySignatures.length === 0 && schema.indexSignatures.length === 0) { - return { anyOf: [{ type: "object" }, { type: "array" }] } - } - const out: JsonSchema.JsonSchema = { type: "object" } - const properties: Record = {} - const required: Array = [] - - for (const ps of schema.propertySignatures) { - const name = ps.name - if (typeof name !== "string") { - throw new globalThis.Error(`Unsupported property signature name: ${format(name)}`) - } - const v = recur(ps.type) - const a = collectJsonSchemaAnnotations(ps.annotations) - properties[name] = a ? appendJsonSchema(v, a) : v - // Property is required only if it's not explicitly optional AND doesn't contain Undefined - if (!ps.isOptional) { - required.push(name) - } - } - - if (Object.keys(properties).length > 0) { - out.properties = properties - } - if (required.length > 0) { - out.required = required - } - - out.additionalProperties = additionalProperties - const patternProperties: Record = {} - // Handle index signatures - for (const is of schema.indexSignatures) { - let type: JsonSchema.JsonSchema | false = recur(is.type) - // Collapse unannotated Never ({ not: {} }) to false, but keep annotated schemas as objects. - if (Object.keys(type).length === 1 && "not" in type) { - type = false - } - const patterns = getParameterPatterns(is.parameter) - if (patterns.length > 0) { - for (const pattern of patterns) { - patternProperties[pattern] = type - } - } else { - out.additionalProperties = type - } - } - if (Object.keys(patternProperties).length > 0) { - out.patternProperties = patternProperties - delete out.additionalProperties - } - if (Predicate.isObject(out.additionalProperties) && Rec.isEmptyRecord(out.additionalProperties)) { - delete out.additionalProperties - } - - return out - } - case "Union": { - const types = schema.types.map(recur) - if (types.length === 0) { - // anyOf MUST be a non-empty array - return { not: {} } - } - if (types.length > 1) { - const compacted = compactEnums(types) - if (compacted) return compacted - } - return schema.mode === "anyOf" ? { anyOf: types } : { oneOf: types } - } - } - } - - // Collapses [{type:"string",enum:["a"]},{type:"string",enum:["b"]}] into {type:"string",enum:["a","b"]}. - // Returns undefined if members have different types, extra keys (e.g. title), or empty enums. - function compactEnums( - types: ReadonlyArray - ): JsonSchema.JsonSchema | undefined { - let sharedType: string | undefined - const values: Array = [] - for (const t of types) { - const keys = Object.keys(t) - if (keys.length !== 2 || t.type === undefined || !Array.isArray(t.enum) || t.enum.length === 0) { - return undefined - } - if (sharedType === undefined) { - sharedType = t.type as string - } else if (t.type !== sharedType) { - return undefined - } - for (const v of t.enum) { - values.push(v) - } - } - return { type: sharedType, enum: values } - } - - function collectJsonSchemaAnnotations( - annotations: Schema.Annotations.Annotations | undefined - ): JsonSchema.JsonSchema | undefined { - if (annotations === undefined) return undefined - - const out: JsonSchema.JsonSchema = {} - if (typeof annotations.title === "string") out.title = annotations.title - if (typeof annotations.description === "string") out.description = annotations.description - else if (generateDescriptions && typeof annotations.expected === "string") out.description = annotations.expected - if (annotations.default !== undefined) out.default = annotations.default - if (Array.isArray(annotations.examples)) out.examples = annotations.examples - if (typeof annotations.readOnly === "boolean") out.readOnly = annotations.readOnly - if (typeof annotations.writeOnly === "boolean") out.writeOnly = annotations.writeOnly - if (typeof annotations.format === "string") out.format = annotations.format - if (typeof annotations.contentEncoding === "string") out.contentEncoding = annotations.contentEncoding - if (typeof annotations.contentMediaType === "string") out.contentMediaType = annotations.contentMediaType - - if (includeAnnotationKey) { - for (const [key, value] of Object.entries(annotations)) { - if (value === undefined) continue - if (standardJsonSchemaAnnotationKeys.has(key)) continue - if (!includeAnnotationKey(key)) continue - out[key] = value - } - } - - if (Object.keys(out).length > 0) return out - } - - function collectJsonSchemaChecks( - checks: ReadonlyArray>, - type: unknown - ): Array { - return checks.map(collectJsonSchemaCheck).filter((c) => c !== undefined) - - function collectJsonSchemaCheck(check: SchemaRepresentation.Check): JsonSchema.JsonSchema | undefined { - switch (check._tag) { - case "Filter": - return filterToJsonSchema(check, type) - case "FilterGroup": { - const checks = check.checks.map(collectJsonSchemaCheck).filter((c) => c !== undefined) - if (checks.length === 0) return undefined - let out = { allOf: checks } - const a = collectJsonSchemaAnnotations(check.annotations) - if (a) { - out = { ...out, ...a } - } - return out - } - } - } - } - - function filterToJsonSchema( - filter: SchemaRepresentation.Filter, - type: unknown - ): JsonSchema.JsonSchema | undefined { - const meta = filter.meta as SchemaRepresentation.Meta - if (!meta) return undefined - - let out = on(meta) - const a = collectJsonSchemaAnnotations(filter.annotations) - if (a) { - out = { ...out, ...a } - } - return out - - function on( - meta: SchemaRepresentation.Meta - ): JsonSchema.JsonSchema | undefined { - switch (meta._tag) { - case "isMinLength": - return type === "array" ? { minItems: meta.minLength } : { minLength: meta.minLength } - case "isMaxLength": - return type === "array" ? { maxItems: meta.maxLength } : { maxLength: meta.maxLength } - case "isLengthBetween": - return type === "array" - ? { allOf: [{ minItems: meta.minimum }, { maxItems: meta.maximum }] } - : { allOf: [{ minLength: meta.minimum }, { maxLength: meta.maximum }] } - case "isPattern": - case "isGUID": - case "isULID": - case "isBase64": - case "isBase64Url": - case "isStartsWith": - case "isEndsWith": - case "isIncludes": - case "isUppercased": - case "isLowercased": - case "isCapitalized": - case "isUncapitalized": - case "isTrimmed": - case "isStringFinite": - case "isStringBigInt": - case "isStringSymbol": - return { pattern: meta.regExp.source } - case "isUUID": - return { pattern: meta.regExp.source, format: "uuid" } - - case "isFinite": - case "isInt": - return undefined - case "isMultipleOf": - return { multipleOf: meta.divisor } - case "isGreaterThanOrEqualTo": - return { minimum: meta.minimum } - case "isLessThanOrEqualTo": - return { maximum: meta.maximum } - case "isGreaterThan": - return { exclusiveMinimum: meta.exclusiveMinimum } - case "isLessThan": - return { exclusiveMaximum: meta.exclusiveMaximum } - case "isBetween": { - return { - [meta.exclusiveMinimum ? "exclusiveMinimum" : "minimum"]: meta.minimum, - [meta.exclusiveMaximum ? "exclusiveMaximum" : "maximum"]: meta.maximum - } - } - - case "isUnique": - return { uniqueItems: true } - - case "isMinProperties": - return { minProperties: meta.minProperties } - case "isMaxProperties": - return { maxProperties: meta.maxProperties } - case "isPropertiesLengthBetween": - return { minProperties: meta.minimum, maxProperties: meta.maximum } - case "isPropertyNames": - return { propertyNames: recur(meta.propertyNames) } - - case "isDateValid": - return { format: "date-time" } - } - } - } - - function getParameterPatterns(parameter: SchemaRepresentation.Representation): Array { - switch (parameter._tag) { - default: - throw new globalThis.Error(`Unsupported index signature parameter: ${parameter._tag}`) - case "Reference": - return getParameterPatterns(multiDocument.references[parameter.$ref]) - case "String": - return getPatterns(parameter) - case "TemplateLiteral": - return [`^${parameter.parts.map(getPartPattern).join("")}$`] - case "Union": - return parameter.types.flatMap(getParameterPatterns) - } - } -} - -function getPatterns(s: SchemaRepresentation.String): Array { - return recur(s.checks) - - function recur(checks: ReadonlyArray>): Array { - return checks.flatMap((c) => { - switch (c._tag) { - case "Filter": { - if ("regExp" in c.meta) { - return [c.meta.regExp.source] - } - return [] - } - case "FilterGroup": - return recur(c.checks) - } - }) - } -} - -function hasCheck(checks: ReadonlyArray>, tag: string): boolean { - return checks.some((c) => { - switch (c._tag) { - case "Filter": - return c.meta._tag === tag - case "FilterGroup": - return hasCheck(c.checks, tag) - } - }) -} - -function appendJsonSchema(a: JsonSchema.JsonSchema, b: JsonSchema.JsonSchema): JsonSchema.JsonSchema { - if (Object.keys(a).length === 0) return b - const len = Object.keys(b).length - if (len === 0) return a - const members = Array.isArray(b.allOf) && len === 1 ? b.allOf : [b] - - if (Array.isArray(a.allOf)) { - return { ...a, allOf: [...a.allOf, ...members] } - } - - if (typeof a.$ref === "string") { - return { allOf: [a, ...members] } - } - - return { ...a, allOf: members } -} - -function getPartPattern(part: SchemaRepresentation.Representation): string { - switch (part._tag) { - case "Literal": - return RegEx.escape(globalThis.String(part.literal)) - case "String": - return SchemaAST.STRING_PATTERN - case "Number": - return SchemaAST.FINITE_PATTERN - case "TemplateLiteral": - return part.parts.map(getPartPattern).join("") - case "Union": - return part.types.map(getPartPattern).join("|") - default: - throw new globalThis.Error("Unsupported part", { cause: part }) - } -} diff --git a/repos/effect/packages/effect/src/internal/schema/schema.ts b/repos/effect/packages/effect/src/internal/schema/schema.ts index a8ff800ec1..cdbb85ff58 100644 --- a/repos/effect/packages/effect/src/internal/schema/schema.ts +++ b/repos/effect/packages/effect/src/internal/schema/schema.ts @@ -1,16 +1,51 @@ -import * as Cause from "../../Cause.ts" -import * as Data from "../../Data.ts" -import * as Effect from "../../Effect.ts" -import * as Exit from "../../Exit.ts" import * as Pipeable from "../../Pipeable.ts" import type * as Schema from "../../Schema.ts" import * as SchemaAST from "../../SchemaAST.ts" -import type { Issue } from "../../SchemaIssue.ts" import * as SchemaParser from "../../SchemaParser.ts" +import type * as SchemaRepresentation from "../../SchemaRepresentation.ts" /** @internal */ export const TypeId = "~effect/Schema/Schema" +/** @internal */ +export function makeDeclarationReviver

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

, + revive: SchemaRepresentation.DeclarationReviver

["revive"] +): SchemaRepresentation.DeclarationReviver

{ + return { + id, + payloadSchema, + revive + } +} + +/** @internal */ +export function makeFilterReviver

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

, + revive: SchemaRepresentation.FilterReviver

["revive"] +): SchemaRepresentation.FilterReviver

{ + return { + id, + payloadSchema, + revive + } +} + +/** @internal */ +export function makeFilterGroupReviver

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

, + revive: SchemaRepresentation.FilterGroupReviver

["revive"] +): SchemaRepresentation.FilterGroupReviver

{ + return { + id, + payloadSchema, + revive + } +} + const SchemaProto = { [TypeId]: TypeId, pipe() { @@ -29,100 +64,15 @@ const SchemaProto = { /** @internal */ export function make(ast: S["ast"], options?: object): S { - const self = Object.create(SchemaProto) - if (options) { - Object.assign(self, options) - } + function Schema() {} + const self = Object.defineProperties( + Object.setPrototypeOf(Schema, SchemaProto), + Object.getOwnPropertyDescriptors({ ...options }) + ) self.ast = ast self.rebuild = (ast: SchemaAST.AST) => make(ast, options) - self.makeEffect = (input: S["~type.make.in"], options?: Schema.MakeOptions) => - mapSchemaIssueEffect(SchemaParser.makeEffect(self)(input, options)) + self.makeEffect = SchemaParser.makeEffect(self) self.make = SchemaParser.make(self) self.makeOption = SchemaParser.makeOption(self) return self } - -/** @internal */ -export const SchemaErrorTypeId = "~effect/Schema/SchemaError" - -// purposefully not internal -export class SchemaError extends Data.TaggedError("SchemaError")<{ - readonly issue: Issue -}> { - readonly [SchemaErrorTypeId]: typeof SchemaErrorTypeId = SchemaErrorTypeId - constructor(issue: Issue) { - super({ issue }) - } - override get message() { - return this.issue.toString() - } - override toString() { - return `SchemaError(${this.message})` - } -} - -/** @internal */ -export function mapSchemaIssueEffect( - self: Effect.Effect -): Effect.Effect { - return Effect.catchCause( - self, - (cause) => Effect.failCauseSync(() => Cause.map(cause, (issue) => new SchemaError(issue))) - ) -} - -/** @internal */ -export function mapSchemaErrorEffect( - self: Effect.Effect -): Effect.Effect { - return Effect.catchCause(self, (cause) => Effect.failCauseSync(() => Cause.map(cause, (error) => error.issue))) -} - -/** @internal */ -export function mapSchemaIssueExit(exit: Exit.Exit): Exit.Exit { - return Exit.isSuccess(exit) - ? Exit.succeed(exit.value) - : Exit.failCause(Cause.map(exit.cause, (issue) => new SchemaError(issue))) -} - -/** @internal */ -export const jsonReorder = makeReorder(getJsonPriority) - -function getJsonPriority(ast: SchemaAST.AST): number { - switch (ast._tag) { - case "BigInt": - case "Symbol": - case "UniqueSymbol": - return 0 - default: - return 1 - } -} - -/** @internal */ -export 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 - } -} diff --git a/repos/effect/packages/effect/src/internal/schema/toArbitrary.ts b/repos/effect/packages/effect/src/internal/schema/toArbitrary.ts new file mode 100644 index 0000000000..c6d2ae27c6 --- /dev/null +++ b/repos/effect/packages/effect/src/internal/schema/toArbitrary.ts @@ -0,0 +1,843 @@ +import * as Array from "../../Array.ts" +import * as Boolean from "../../Boolean.ts" +import type * as Combiner from "../../Combiner.ts" +import * as Equal from "../../Equal.ts" +import { memoize } from "../../Function.ts" +import * as Number from "../../Number.ts" +import * as Option from "../../Option.ts" +import * as Order from "../../Order.ts" +import * as Predicate from "../../Predicate.ts" +import type * as Schema from "../../Schema.ts" +import * as SchemaAST from "../../SchemaAST.ts" +import * as Struct from "../../Struct.ts" +import type * as FastCheck from "../../testing/FastCheck.ts" +import * as UndefinedOr from "../../UndefinedOr.ts" +import { errorWithPath } from "../errors.ts" +import * as InternalRecord from "../record.ts" +import * as InternalAnnotations from "./annotations.ts" + +const arbitraryMemoMap = new WeakMap>() +const suspendDepthIdentifierMap = new WeakMap() +const emptyRecursionStack: RecursionStack = [] + +type RecursionStack = ReadonlyArray + +type Context = Schema.Annotations.ToArbitrary.Context +type Constraint = Schema.Annotations.ToArbitrary.GenerationConstraint +type OrderedConstraint = Schema.Annotations.ToArbitrary.OrderedConstraint +type ArbitraryFilter = Schema.Annotations.ToArbitrary.Filter + +type Lazy = (fc: typeof FastCheck, ctx: Context, recursionStack: RecursionStack) => FastCheck.Arbitrary +type LazyOption = ( + fc: typeof FastCheck, + ctx: Context, + recursionStack: RecursionStack +) => FastCheck.Arbitrary | undefined + +function arbitraryError(what: string) { + return new Error(`Unable to derive an arbitrary for ${what}`) +} + +const entryComparator = ([a]: readonly [any, any], [b]: readonly [any, any]) => Equal.equals(a, b) + +function applyChecks(ast: SchemaAST.AST, filters: Array>, arbitrary: FastCheck.Arbitrary) { + return filters.reduce( + (acc, filter) => acc.filter((a) => filter.run(a, ast, SchemaAST.defaultParseOptions) === undefined), + arbitrary + ) +} + +function validateArrayConstraints(constraint: FastCheck.ArrayConstraints | undefined, label: string) { + if ( + constraint?.minLength !== undefined && constraint.maxLength !== undefined && + constraint.minLength > constraint.maxLength + ) { + throw arbitraryError(`${label} constraints`) + } +} + +function lengthToFastCheckConstraints( + constraint: { readonly minLength?: number | undefined; readonly maxLength?: number | undefined } | undefined +) { + return constraint === undefined || (constraint.minLength === undefined && constraint.maxLength === undefined) + ? undefined + : { + ...(constraint.minLength !== undefined ? { minLength: constraint.minLength } : {}), + ...(constraint.maxLength !== undefined ? { maxLength: constraint.maxLength } : {}) + } +} + +function arrayWithConstraints( + fc: typeof FastCheck, + item: FastCheck.Arbitrary, + constraint: FastCheck.ArrayConstraints | undefined, + comparator?: ((a: any, b: any) => boolean) | undefined +) { + return comparator + ? fc.uniqueArray(item, { ...constraint, comparator }) + : fc.array(item, constraint) +} + +function array(fc: typeof FastCheck, ctx: Context, item: FastCheck.Arbitrary, terminal = false) { + const constraint = ctx.constraint + const arrayConstraints = lengthToFastCheckConstraints(constraint) + validateArrayConstraints(arrayConstraints, "array") + return arrayWithConstraints( + fc, + item, + terminal ? { ...arrayConstraints, maxLength: arrayConstraints?.minLength ?? 0 } : arrayConstraints, + constraint?.unique ? Equal.equals : undefined + ) +} + +function appendArray( + fc: typeof FastCheck, + out: FastCheck.Arbitrary>, + len: number, + rest: FastCheck.Arbitrary> +) { + return out.chain((as) => as.length < len ? fc.constant(as) : rest.map((rest) => [...as, ...rest])) +} + +function appendObjectEntries( + out: FastCheck.Arbitrary, + entries: FastCheck.Arbitrary> +) { + return out.chain((o) => entries.map((entries) => ({ ...Object.fromEntries(entries), ...o }))) +} + +const max = UndefinedOr.makeReducer(Number.ReducerMax) +const min = UndefinedOr.makeReducer(Number.ReducerMin) +const or = UndefinedOr.makeReducer(Boolean.ReducerOr) +const concat = UndefinedOr.makeReducer(Array.makeReducerConcat()) + +const combiner: Combiner.Combiner = Struct.makeCombiner({ + integer: or, + maxLength: min, + minLength: max, + noInfinity: or, + noNaN: or, + patterns: concat, + unique: or +}, { + omitKeyWhen: Predicate.isUndefined +}) + +function mergeOrderedBound( + order: Order.Order, + self: T | undefined, + selfExclusive: boolean | undefined, + that: T | undefined, + thatExclusive: boolean | undefined, + takeComparison: -1 | 1 +): readonly [T | undefined, boolean | undefined] { + if (that === undefined || self === undefined) { + return that === undefined ? [self, selfExclusive] : [that, thatExclusive] + } + const comparison = order(self, that) + return comparison === takeComparison + ? [that, thatExclusive] + : comparison === 0 + ? [self, selfExclusive || thatExclusive] + : [self, selfExclusive] +} + +function mergeOrderedConstraints(self: OrderedConstraint | undefined, that: OrderedConstraint) { + if (self === undefined) { + return that + } + if (self.order !== that.order) { + throw new Error("Cannot merge ordered arbitrary constraints with different Order instances") + } + + const [minimum, exclusiveMinimum] = mergeOrderedBound( + self.order, + self.minimum, + self.exclusiveMinimum, + that.minimum, + that.exclusiveMinimum, + -1 + ) + const [maximum, exclusiveMaximum] = mergeOrderedBound( + self.order, + self.maximum, + self.exclusiveMaximum, + that.maximum, + that.exclusiveMaximum, + 1 + ) + + return { + order: self.order, + ...(minimum !== undefined ? { minimum } : {}), + ...(exclusiveMinimum !== undefined ? { exclusiveMinimum } : {}), + ...(maximum !== undefined ? { maximum } : {}), + ...(exclusiveMaximum !== undefined ? { exclusiveMaximum } : {}) + } +} + +function mergeConstraint(self: Constraint | undefined, that: Constraint): Constraint { + const { ordered: selfOrdered, ...selfRest } = self ?? {} + const { ordered: thatOrdered, ...thatRest } = that + const ordered = thatOrdered === undefined + ? selfOrdered + : mergeOrderedConstraints(selfOrdered, thatOrdered) + const out = combiner.combine(selfRest, thatRest) + return { + ...out, + ...(ordered === undefined ? {} : { ordered }) + } +} + +function collectChecks(checks: SchemaAST.Checks | undefined) { + const filters: Array> = [] + const arbitraries: Array = [] + function visit(check: SchemaAST.Check) { + if (check.annotations?.arbitrary) { + arbitraries.push(check.annotations.arbitrary) + } + if (check._tag !== "Filter") { + for (const child of check.checks) { + visit(child) + } + } else { + filters.push(check) + } + } + checks?.forEach(visit) + return { filters, arbitraries } +} + +function constraintContext(arbitraries: Array): (ctx: Context) => Context { + const constraintAnnotations = arbitraries.map(({ constraint }) => constraint).filter(Predicate.isNotUndefined) + return (ctx) => { + const constraint = constraintAnnotations.reduce( + (acc: Constraint | undefined, c) => mergeConstraint(acc, c), + ctx.constraint + ) + return { ...ctx, constraint } + } +} + +function resetContext(ctx: Context) { + return { ...ctx, constraint: undefined } +} + +function objectEntriesConstraints(ast: SchemaAST.Objects, constraint: Constraint | undefined, requiredKeys: number) { + if (constraint === undefined || (constraint.minLength === undefined && constraint.maxLength === undefined)) { + return undefined + } + if ( + constraint.minLength !== undefined && + ast.indexSignatures.length === 0 && + constraint.minLength > ast.propertySignatures.length + ) { + throw arbitraryError("object property constraints") + } + const out: FastCheck.ArrayConstraints = {} + if (constraint.minLength !== undefined) { + out.minLength = Math.max(0, constraint.minLength - requiredKeys) + } + if (constraint.maxLength !== undefined) { + out.maxLength = constraint.maxLength - requiredKeys + if (out.maxLength < 0) { + throw arbitraryError("object property constraints") + } + } + validateArrayConstraints(out, "object property") + return out +} + +function objectWithOptionalCount( + fc: typeof FastCheck, + pss: Record>, + orderedNames: ReadonlyArray, + requiredKeys: ReadonlyArray, + optionalNames: ReadonlyArray, + constraint: Constraint +) { + const requiredCount = requiredKeys.length + if (constraint.maxLength !== undefined && constraint.maxLength < requiredCount) { + throw arbitraryError("object property constraints") + } + const minOptional = constraint.minLength === undefined ? 0 : Math.max(0, constraint.minLength - requiredCount) + const maxOptional = constraint.maxLength === undefined + ? optionalNames.length + : Math.min(optionalNames.length, constraint.maxLength - requiredCount) + if (minOptional > maxOptional) { + throw arbitraryError("object property constraints") + } + const full = fc.record(pss, { requiredKeys: [...requiredKeys, ...optionalNames] }) + const chosen = fc.shuffledSubarray([...optionalNames], { minLength: minOptional, maxLength: maxOptional }) + return fc.tuple(full, chosen).map(([base, names]) => { + const keep = new Set([...requiredKeys, ...names]) + const out: Record = {} + for (const name of orderedNames) { + if (keep.has(name)) { + InternalRecord.assignProperty(out, name, base[name]) + } + } + return out + }) +} + +function toRangeConstraints( + ordered: OrderedConstraint | undefined, + min: (value: T, excluded: boolean) => T, + max: (value: T, excluded: boolean) => T, + error: string +) { + const out: { min?: T; max?: T } = {} + if (ordered?.minimum !== undefined) { + out.min = min(ordered.minimum as T, ordered.exclusiveMinimum === true) + } + if (ordered?.maximum !== undefined) { + out.max = max(ordered.maximum as T, ordered.exclusiveMaximum === true) + } + if (out.min !== undefined && out.max !== undefined && out.min > out.max) { + throw arbitraryError(error) + } + return out +} + +function toIntegerConstraints(ordered: OrderedConstraint | undefined) { + return toRangeConstraints( + ordered, + (minimum, excluded) => excluded ? Math.floor(minimum) + 1 : Math.ceil(minimum), + (maximum, excluded) => excluded ? Math.ceil(maximum) - 1 : Math.floor(maximum), + "integer constraints" + ) +} + +function toFloatConstraints(constraint: Constraint | undefined, ordered: OrderedConstraint | undefined) { + const out: FastCheck.FloatConstraints = { + ...(constraint?.noInfinity ? { noDefaultInfinity: true } : {}), + ...(constraint?.noNaN ? { noNaN: true } : {}), + ...(ordered?.minimum !== undefined ? { min: ordered.minimum as number } : {}), + ...(ordered?.exclusiveMinimum !== undefined ? { minExcluded: ordered.exclusiveMinimum } : {}), + ...(ordered?.maximum !== undefined ? { max: ordered.maximum as number } : {}), + ...(ordered?.exclusiveMaximum !== undefined ? { maxExcluded: ordered.exclusiveMaximum } : {}) + } + if ( + out.min !== undefined && + out.max !== undefined && + (out.min > out.max || (out.min === out.max && (out.minExcluded || out.maxExcluded))) + ) { + throw arbitraryError("number constraints") + } + return out +} + +function toBigIntConstraints(ordered: OrderedConstraint | undefined) { + return toRangeConstraints( + ordered, + (minimum, excluded) => excluded ? minimum + BigInt(1) : minimum, + (maximum, excluded) => excluded ? maximum - BigInt(1) : maximum, + "the ordered bigint constraints" + ) +} + +interface LazyArbitraryWithContext { + (fc: typeof FastCheck, ctx: Context, recursionStack?: RecursionStack): FastCheck.Arbitrary + readonly terminal: ( + fc: typeof FastCheck, + ctx: Context, + recursionStack?: RecursionStack + ) => FastCheck.Arbitrary | undefined +} + +function makeLazy(normal: Lazy, terminal: LazyOption): LazyArbitraryWithContext { + const out = + ((fc, ctx, recursionStack = emptyRecursionStack) => normal(fc, ctx, recursionStack)) as LazyArbitraryWithContext + ;(out as { terminal: LazyOption }).terminal = (fc, ctx, recursionStack = emptyRecursionStack) => + terminal(fc, ctx, recursionStack) + return out +} + +function same(f: Lazy) { + return makeLazy(f, f) +} + +function getSuspendRecursion(fc: typeof FastCheck, ast: SchemaAST.Suspend) { + const depthIdentifier = suspendDepthIdentifierMap.get(ast) ?? fc.createDepthIdentifier() + suspendDepthIdentifierMap.set(ast, depthIdentifier) + return { maxDepth: 2, depthIdentifier } +} + +function oneOf(fc: typeof FastCheck, arbitraries: ReadonlyArray>) { + return arbitraries.length === 0 ? undefined : arbitraries.length === 1 ? arbitraries[0] : fc.oneof(...arbitraries) +} + +const finiteNumberConstraint: Constraint = { + noInfinity: true, + noNaN: true +} + +function finiteNumberContext(ctx: Context): Context { + return { + ...ctx, + constraint: finiteNumberConstraint + } +} + +function applyCandidates( + fc: typeof FastCheck, + ctx: Context, + arbitraries: Array, + base: FastCheck.Arbitrary | undefined +) { + const weighted: Array> = base === undefined + ? [] + : [{ arbitrary: base, weight: 1 }] + for (const { candidate } of arbitraries) { + if (!candidate) { + continue + } + const arbitrary = candidate.make(fc, ctx) + if (arbitrary === undefined) { + continue + } + const weight = candidate.weight ?? 1 + if (!globalThis.Number.isInteger(weight) || weight <= 0) { + throw arbitraryError("a candidate with an invalid weight") + } + weighted.push({ arbitrary, weight }) + } + return weighted.length === 0 ? undefined : weighted.length === 1 ? weighted[0].arbitrary : fc.oneof(...weighted) +} + +function applyFilterLayer( + ast: SchemaAST.AST, + checks: ReturnType, + fc: typeof FastCheck, + ctx: Context, + base: FastCheck.Arbitrary | undefined +) { + const out = applyCandidates(fc, ctx, checks.arbitraries, base) + return out === undefined ? undefined : applyChecks(ast, checks.filters, out) +} + +function normalizeDerivation( + output: Schema.Annotations.ToArbitrary.Output, + hasTypeParameters: boolean +) { + if (!(typeof output === "object" && output !== null && "arbitrary" in output)) { + return { arbitrary: output, terminal: hasTypeParameters ? undefined : output } + } + const terminal = "terminal" in output ? output.terminal : hasTypeParameters ? undefined : output.arbitrary + return { + arbitrary: output.arbitrary, + terminal + } +} + +function makeTypeParameters( + typeParameters: ReadonlyArray>, + fc: typeof FastCheck, + ctx: Context, + recursionStack: RecursionStack, + lazyNormal: boolean +) { + return typeParameters.map((tp) => ({ + arbitrary: lazyNormal ? fc.constant(null).chain(() => tp(fc, ctx, recursionStack)) : tp(fc, ctx, recursionStack), + terminal: tp.terminal(fc, ctx, recursionStack) + })) +} + +type BaseBuilder = ( + fc: typeof FastCheck, + ctx: Context, + nextCtx: Context, + recursionStack: RecursionStack +) => FastCheck.Arbitrary | undefined + +function filterLayer( + ast: SchemaAST.AST, + checks: ReturnType, + normalBase: BaseBuilder, + terminalBase: BaseBuilder +): LazyArbitraryWithContext { + const f = constraintContext(checks.arbitraries) + return makeLazy((fc, ctx, recursionStack) => { + const nextCtx = f(ctx) + return applyFilterLayer(ast, checks, fc, nextCtx, normalBase(fc, ctx, nextCtx, recursionStack))! + }, (fc, ctx, recursionStack) => { + const nextCtx = f(ctx) + return applyFilterLayer(ast, checks, fc, nextCtx, terminalBase(fc, ctx, nextCtx, recursionStack)) + }) +} + +/** @internal */ +export const memoized = memoize((ast: SchemaAST.AST) => recur(ast, [])) + +function recur(ast: SchemaAST.AST, path: ReadonlyArray): LazyArbitraryWithContext { + // --------------------------------------------- + // handle annotations + // --------------------------------------------- + const annotation = InternalAnnotations.resolve(ast)?.toArbitrary as + | Schema.Annotations.ToArbitrary.Declaration> + | undefined + if (annotation) { + const typeParameters = SchemaAST.isDeclaration(ast) ? ast.typeParameters.map((tp) => recur(tp, path)) : [] + const checks = collectChecks(ast.checks) + const derive = (lazyNormal: boolean): BaseBuilder => (fc, ctx, nextCtx, recursionStack) => + normalizeDerivation( + annotation(makeTypeParameters(typeParameters, fc, resetContext(ctx), recursionStack, lazyNormal))(fc, nextCtx), + typeParameters.length > 0 + )[lazyNormal ? "terminal" : "arbitrary"] + return filterLayer(ast, checks, derive(false), derive(true)) + } + if (ast.checks) { + const checks = collectChecks(ast.checks) + const lawc = recur(SchemaAST.replaceChecks(ast, undefined), path) + return filterLayer( + ast, + checks, + (fc, _ctx, nextCtx, recursionStack) => lawc(fc, nextCtx, recursionStack), + (fc, _ctx, nextCtx, recursionStack) => lawc.terminal(fc, nextCtx, recursionStack) + ) + } + return base(ast, path) +} + +function base(ast: SchemaAST.AST, path: ReadonlyArray): LazyArbitraryWithContext { + switch (ast._tag) { + case "Never": + case "Declaration": + throw errorWithPath(`Unsupported AST ${ast._tag}`, path) + case "Null": + return same((fc) => fc.constant(null)) + case "Void": + case "Undefined": + return same((fc) => fc.constant(undefined)) + case "Unknown": + case "Any": + return same((fc) => fc.anything()) + case "String": + return same((fc, ctx) => { + const constraint = ctx.constraint + const patterns = constraint?.patterns + return patterns + ? fc.oneof(...patterns.map((pattern) => fc.stringMatching(new RegExp(pattern)))) + : fc.string(lengthToFastCheckConstraints(constraint)) + }) + case "Number": + return same((fc, ctx) => { + const constraint = ctx.constraint + const ordered = constraint?.ordered?.order === Order.Number ? constraint.ordered : undefined + return constraint?.integer + ? fc.integer(toIntegerConstraints(ordered)) + : fc.float(toFloatConstraints(constraint, ordered)) + }) + case "Boolean": + return same((fc) => fc.boolean()) + case "BigInt": + return same((fc, ctx) => { + const ordered = ctx.constraint?.ordered?.order === Order.BigInt ? ctx.constraint.ordered : undefined + return fc.bigInt(toBigIntConstraints(ordered)) + }) + case "Symbol": + return same((fc) => fc.string().map(Symbol.for)) + case "Literal": + return same((fc) => fc.constant(ast.literal)) + case "UniqueSymbol": + return same((fc) => fc.constant(ast.symbol)) + case "ObjectKeyword": + return same((fc) => fc.oneof(fc.object(), fc.array(fc.anything()))) + case "Enum": + return recur(SchemaAST.enumsToLiterals(ast), path) + case "TemplateLiteral": { + const parts = ast.parts.map((part, i) => recur(SchemaAST.toEncoded(part), [...path, i])) + return same((fc, ctx, recursionStack) => + fc.tuple(...parts.map((part) => part(fc, finiteNumberContext(ctx), recursionStack))).map((segments) => + segments.map((segment) => globalThis.String(segment)).join("") + ) + ) + } + case "Arrays": { + const elements = ast.elements.map((ast, i) => ({ + ast, + arbitrary: recur(ast, [...path, i]) + })) + const len = ast.elements.length + const rest = ast.rest.map((ast, i) => ({ + ast, + arbitrary: recur(ast, [...path, len + i]) + })) + const terminal: LazyOption = (fc, ctx, recursionStack) => { + const reset = resetContext(ctx) + const elementArbitraries: Array>> = [] + const optionals: Array | undefined> = [] + let length = 0 + for (const element of elements) { + const out = element.arbitrary.terminal(fc, reset, recursionStack) + if (SchemaAST.isOptional(element.ast)) { + optionals.push(out) + continue + } + if (out === undefined) { + return undefined + } + length++ + elementArbitraries.push(out.map(Option.some)) + } + const minLength = ctx.constraint?.minLength ?? 0 + const needsRest = Array.isReadonlyArrayNonEmpty(rest) && minLength > length + optionals.length + const optionalTarget = needsRest ? optionals.length : Math.max(0, minLength - length) + let includedOptionals = 0 + for (const out of optionals) { + if (includedOptionals >= optionalTarget || out === undefined) { + elementArbitraries.push(fc.constant(Option.none())) + continue + } + includedOptionals++ + length++ + elementArbitraries.push(out.map(Option.some)) + } + if (includedOptionals < optionalTarget) { + return undefined + } + let out = fc.tuple(...elementArbitraries).map(Array.getSomes) + if (Array.isReadonlyArrayNonEmpty(rest)) { + const [head, ...tail] = rest + const restCtx = ast.elements.length === 0 ? ctx : reset + const minRestLength = Math.max(0, minLength - length - tail.length) + const headArbitrary = minRestLength === 0 + ? undefined + : head.arbitrary.terminal(fc, reset, recursionStack) + if (minRestLength > 0 && headArbitrary === undefined) { + return undefined + } + const restArbitrary = minRestLength === 0 + ? fc.constant([]) + : array( + fc, + { ...restCtx, constraint: { ...restCtx.constraint, minLength: minRestLength } }, + headArbitrary!, + true + ) + out = appendArray(fc, out, len, restArbitrary) + if (tail.length > 0) { + const tailArbitraries: Array> = [] + for (const element of tail) { + const out = element.arbitrary.terminal(fc, reset, recursionStack) + if (out === undefined) { + return undefined + } + tailArbitraries.push(out) + } + const t = fc.tuple(...tailArbitraries) + out = appendArray(fc, out, len, t) + } + } + return out + } + return makeLazy((fc, ctx, recursionStack) => { + const reset = resetContext(ctx) + // --------------------------------------------- + // handle elements + // --------------------------------------------- + const elementArbitraries: Array>> = elements.map( + ({ ast, arbitrary }) => { + const out = arbitrary(fc, reset, recursionStack) + return SchemaAST.isOptional(ast) + ? out.chain((a) => fc.boolean().map((b) => b ? Option.some(a) : Option.none())) + : out.map(Option.some) + } + ) + let out = fc.tuple(...elementArbitraries).map((elements) => + Array.getSomes(Array.takeWhile(elements, Option.isSome)) + ) + // --------------------------------------------- + // handle rest element + // --------------------------------------------- + if (Array.isReadonlyArrayNonEmpty(rest)) { + const [head, ...tail] = rest.map(({ arbitrary }) => arbitrary(fc, reset, recursionStack)) + + const restArbitrary = array(fc, ast.elements.length === 0 ? ctx : reset, head) + out = appendArray(fc, out, len, restArbitrary) + // --------------------------------------------- + // handle post rest elements + // --------------------------------------------- + if (tail.length > 0) { + const t = fc.tuple(...tail) + out = appendArray(fc, out, len, t) + } + } + if (ctx.recursion) { + const terminalOut = terminal(fc, ctx, recursionStack) + if (terminalOut !== undefined) { + return fc.oneof(ctx.recursion, terminalOut, out) + } + } + return out + }, terminal) + } + case "Objects": { + const propertySignatures = ast.propertySignatures.map((ps) => ({ + ps, + arbitrary: recur(ps.type, [...path, ps.name]) + })) + const indexSignatures = ast.indexSignatures.map((is) => ({ + is, + parameter: recur(is.parameter, path), + type: recur(is.type, path) + })) + const terminal: LazyOption = (fc, ctx, recursionStack) => { + const reset = resetContext(ctx) + const pss: any = {} + const requiredKeys: Array = [] + const optionals: Array]> = [] + for (const { ps, arbitrary } of propertySignatures) { + const name = ps.name + const out = arbitrary.terminal(fc, reset, recursionStack) + if (SchemaAST.isOptional(ps.type)) { + if (out !== undefined) { + optionals.push([name, out]) + } + continue + } + if (out === undefined) { + return undefined + } + requiredKeys.push(name) + InternalRecord.assignProperty(pss, name, out) + } + let optionalCount = Math.max(0, (ctx.constraint?.minLength ?? 0) - requiredKeys.length) + for (const [name, out] of optionals) { + if (optionalCount === 0) { + break + } + optionalCount-- + requiredKeys.push(name) + InternalRecord.assignProperty(pss, name, out) + } + if (optionalCount > 0 && ast.indexSignatures.length === 0) { + return undefined + } + let out = fc.record(pss, { requiredKeys }) + const entriesConstraints = objectEntriesConstraints(ast, ctx.constraint, requiredKeys.length) + const minEntries = entriesConstraints?.minLength ?? 0 + for (const { parameter, type } of indexSignatures) { + let entries: FastCheck.Arbitrary> + if (minEntries === 0) { + entries = fc.constant([]) + } else { + const key = parameter.terminal(fc, reset, recursionStack) + const value = type.terminal(fc, reset, recursionStack) + if (key === undefined || value === undefined) { + return undefined + } + entries = arrayWithConstraints( + fc, + fc.tuple(key, value), + { ...entriesConstraints, maxLength: minEntries }, + entryComparator + ) + } + out = appendObjectEntries(out, entries) + } + return out + } + return makeLazy((fc, ctx, recursionStack) => { + const reset = resetContext(ctx) + // --------------------------------------------- + // handle property signatures + // --------------------------------------------- + const pss: any = {} + const orderedNames: Array = [] + const requiredKeys: Array = [] + const optionalNames: Array = [] + for (const { ps, arbitrary } of propertySignatures) { + const name = ps.name + orderedNames.push(name) + if (SchemaAST.isOptional(ps.type)) { + optionalNames.push(name) + } else { + requiredKeys.push(name) + } + InternalRecord.assignProperty(pss, name, arbitrary(fc, reset, recursionStack)) + } + // When property-count constraints must be satisfied by selecting + // optional keys (no index signatures are available to fill the gap), + // generate a count-controlled subset of optional keys instead of + // relying on fast-check's independent inclusion plus discards. This + // enforces both bounds precisely while still varying which optionals + // appear. + const constraint = ctx.constraint + if ( + optionalNames.length > 0 && + indexSignatures.length === 0 && + constraint !== undefined && + (constraint.minLength !== undefined || constraint.maxLength !== undefined) + ) { + return objectWithOptionalCount(fc, pss, orderedNames, requiredKeys, optionalNames, constraint) + } + let out = fc.record(pss, { requiredKeys }) + const entriesConstraints = objectEntriesConstraints(ast, ctx.constraint, requiredKeys.length) + // --------------------------------------------- + // handle index signatures + // --------------------------------------------- + for (const { parameter, type } of indexSignatures) { + const entry = fc.tuple(parameter(fc, reset, recursionStack), type(fc, reset, recursionStack)) + const entries = arrayWithConstraints(fc, entry, entriesConstraints, entryComparator) + out = appendObjectEntries(out, entries) + } + return out + }, terminal) + } + case "Union": { + const types = ast.types.map((ast) => recur(ast, path)) + const terminal: LazyOption = (fc, ctx, recursionStack) => + oneOf(fc, types.map((type) => type.terminal(fc, ctx, recursionStack)).filter(Predicate.isNotUndefined)) + return makeLazy((fc, ctx, recursionStack) => { + const arbitraries = types.map((type) => type(fc, ctx, recursionStack)) + if (ctx.recursion) { + const terminalOut = terminal(fc, ctx, recursionStack) + if (terminalOut !== undefined) { + return fc.oneof(ctx.recursion, terminalOut, ...arbitraries) + } + } + const out = oneOf(fc, arbitraries) + if (out === undefined) { + throw arbitraryError("a union with no members") + } + return out + }, terminal) + } + case "Suspend": { + const memo = arbitraryMemoMap.get(ast) + + if (memo) return memo + + const get = SchemaAST.memoizeThunk(() => recur(ast.thunk(), path)) + const out = makeLazy((fc, ctx, recursionStack) => { + const recursion = getSuspendRecursion(fc, ast) + const nextCtx = { ...ctx, recursion } + const nextStack = recursionStack.includes(ast) ? recursionStack : [...recursionStack, ast] + const terminal = get().terminal(fc, nextCtx, nextStack) + if (terminal === undefined) { + throw errorWithPath( + "Unable to derive an arbitrary for a recursive schema without a finite generation path", + path + ) + } + return fc.oneof( + recursion, + terminal, + fc.constant(null).chain(() => get()(fc, nextCtx, nextStack)) + ) + }, (fc, ctx, recursionStack) => { + if (recursionStack.includes(ast)) { + return undefined + } + const recursion = getSuspendRecursion(fc, ast) + return get().terminal(fc, { ...ctx, recursion }, [...recursionStack, ast]) + }) + + arbitraryMemoMap.set(ast, out) + + return out + } + } +} diff --git a/repos/effect/packages/effect/src/internal/schema/toCodeDocument.ts b/repos/effect/packages/effect/src/internal/schema/toCodeDocument.ts new file mode 100644 index 0000000000..a59a5c57f5 --- /dev/null +++ b/repos/effect/packages/effect/src/internal/schema/toCodeDocument.ts @@ -0,0 +1,572 @@ +import * as Arr from "../../Array.ts" +import { format, formatPropertyKey } from "../../Formatter.ts" +import type * as Schema from "../../Schema.ts" +import type * as SchemaRepresentation from "../../SchemaRepresentation.ts" +import { errorWithPath } from "../errors.ts" +import * as InternalRecord from "../record.ts" +import * as InternalAnnotations from "./annotations.ts" + +type Path = ReadonlyArray +type CheckRepresentationAnnotation = SchemaRepresentation.CheckRepresentationAnnotation< + SchemaRepresentation.Representation +> + +/** @internal */ +export function makeCode(runtime: string, Type: string): SchemaRepresentation.Code { + return { runtime, Type } +} + +function renderEmittableAnnotation(input: unknown): string | undefined { + if ( + input === null || + typeof input === "string" || + typeof input === "boolean" || + typeof input === "number" || + typeof input === "bigint" + ) return format(input) + if (typeof input === "symbol") { + const key = globalThis.Symbol.keyFor(input) + return key === undefined ? undefined : `Symbol.for(${format(key)})` + } + if (typeof input !== "object") return undefined + if (Array.isArray(input)) { + const values: Array = [] + for (const value of input) { + const rendered = renderEmittableAnnotation(value) + if (rendered === undefined) return undefined + values.push(rendered) + } + return `[${values.join(", ")}]` + } + + const entries: Array = [] + for (const [key, value] of Object.entries(input)) { + const rendered = renderEmittableAnnotation(value) + if (rendered === undefined) return undefined + entries.push(`${formatPropertyKey(key)}: ${rendered}`) + } + return `{ ${entries.join(", ")} }` +} + +function renderAnnotations( + annotations: Schema.Annotations.Annotations | undefined +): string | undefined { + if (annotations === undefined) return undefined + const entries: Array = [] + for (const [key, value] of Object.entries(annotations)) { + if (InternalAnnotations.annotationExcludedKeys.has(key)) continue + const rendered = renderEmittableAnnotation(value) + if (rendered !== undefined) entries.push(`${formatPropertyKey(key)}: ${rendered}`) + } + return entries.length === 0 ? undefined : `{ ${entries.join(", ")} }` +} + +/** @internal */ +export function sanitizeJavaScriptIdentifier(input: string): string { + if (input.length === 0) return "_" + const out = input.replace(/[^A-Za-z0-9_$]/gu, "_") + const first = out[0] + return first >= "a" && first <= "z" + ? first.toUpperCase() + out.slice(1) + : first >= "0" && first <= "9" + ? `_${out}` + : out +} + +function isSimpleLiveLiteral( + representation: SchemaRepresentation.Representation +): representation is SchemaRepresentation.Literal { + return representation._tag === "Literal" && representation.checks.length === 0 && + representation.annotations === undefined +} + +/** @internal */ +export interface TopologicalSort { + readonly nonRecursives: ReadonlyArray<{ + readonly $ref: string + readonly representation: SchemaRepresentation.Representation + }> + readonly recursives: Readonly> +} + +/** @internal */ +export function topologicalSort( + references: SchemaRepresentation.References +): TopologicalSort { + const identifiers = Object.keys(references) + const identifierSet = new Set(identifiers) + + function collectRefs(root: SchemaRepresentation.Representation): ReadonlySet { + const refs = new Set() + const visited = new WeakSet() + const stack: Array = [root] + + function pushRepresentationSchemas(representation: CheckRepresentationAnnotation | undefined): void { + if (representation?.schemas !== undefined) stack.push(...representation.schemas) + } + + function pushChecks( + checks: ReadonlyArray + ): void { + for (const check of checks) { + pushRepresentationSchemas(check.representation) + if (check._tag === "FilterGroup") pushChecks(check.checks) + } + } + + while (stack.length > 0) { + const representation = stack.pop()! + if (visited.has(representation)) continue + visited.add(representation) + if (representation._tag === "Reference") { + if (identifierSet.has(representation.$ref)) refs.add(representation.$ref) + continue + } + + pushChecks(representation.checks) + switch (representation._tag) { + case "Declaration": + pushRepresentationSchemas(representation.representation) + stack.push(...representation.typeParameters) + break + case "Suspend": + stack.push(representation.thunk) + break + case "TemplateLiteral": + stack.push(...representation.parts) + break + case "Arrays": + for (const element of representation.elements) stack.push(element.type) + stack.push(...representation.rest) + break + case "Objects": + for (const property of representation.propertySignatures) stack.push(property.type) + for (const signature of representation.indexSignatures) { + stack.push(signature.parameter, signature.type) + } + break + case "Union": + stack.push(...representation.types) + break + } + } + return refs + } + + const dependencies = new Map>( + identifiers.map((identifier) => [identifier, collectRefs(references[identifier])]) + ) + const recursive = new Set() + const state = new Map() + const stack: Array = [] + + function visit(identifier: string): void { + const current = state.get(identifier) ?? 0 + if (current === 1) { + const start = stack.indexOf(identifier) + for (let index = start; index < stack.length; index++) recursive.add(stack[index]) + return + } + if (current === 2) return + state.set(identifier, 1) + stack.push(identifier) + for (const dependency of dependencies.get(identifier)!) visit(dependency) + stack.pop() + state.set(identifier, 2) + } + + for (const identifier of identifiers) visit(identifier) + + const inDegree = new Map() + const dependents = new Map>() + for (const identifier of identifiers) { + if (!recursive.has(identifier)) { + inDegree.set(identifier, 0) + dependents.set(identifier, new Set()) + } + } + for (const [identifier, internalDependencies] of dependencies) { + if (recursive.has(identifier)) continue + for (const dependency of internalDependencies) { + if (recursive.has(dependency)) continue + inDegree.set(identifier, inDegree.get(identifier)! + 1) + dependents.get(dependency)!.add(identifier) + } + } + + const queue: Array = [] + for (const [identifier, degree] of inDegree) { + if (degree === 0) queue.push(identifier) + } + const nonRecursives: Array<{ + readonly $ref: string + readonly representation: SchemaRepresentation.Representation + }> = [] + for (let index = 0; index < queue.length; index++) { + const $ref = queue[index] + nonRecursives.push({ $ref, representation: references[$ref] }) + for (const dependent of dependents.get($ref)!) { + const degree = inDegree.get(dependent)! - 1 + inDegree.set(dependent, degree) + if (degree === 0) queue.push(dependent) + } + } + const recursives: Record = {} + for (const identifier of recursive) InternalRecord.assignProperty(recursives, identifier, references[identifier]) + return { nonRecursives, recursives } +} + +/** @internal */ +export function toCodeDocument( + document: SchemaRepresentation.MultiDocument +): SchemaRepresentation.CodeDocument { + const artifacts: Array = [] + const sorted = topologicalSort(document.references) + const sanitizedReferences = new Map() + const uniqueIdentifiers = new Set() + let compilingRecursiveDefinition = false + let explicitSuspendDepth = 0 + + for (const { $ref } of sorted.nonRecursives) ensureUniqueIdentifier($ref) + for (const $ref of Object.keys(sorted.recursives)) ensureUniqueIdentifier($ref) + + const nonRecursives = sorted.nonRecursives.map(({ $ref, representation }) => ({ + $ref: ensureUniqueIdentifier($ref), + code: recur(representation, ["references", $ref]) + })) + const recursives: Record = {} + for (const [$ref, representation] of Object.entries(sorted.recursives)) { + compilingRecursiveDefinition = true + InternalRecord.assignProperty(recursives, ensureUniqueIdentifier($ref), recur(representation, ["references", $ref])) + compilingRecursiveDefinition = false + } + const codes = document.representations.map((representation, index) => + recur(representation, ["representations", index]) + ) + + return { + codes, + references: { nonRecursives, recursives }, + artifacts + } + + function ensureUniqueIdentifier(original: string): string { + const existing = sanitizedReferences.get(original) + if (existing !== undefined) return existing + const candidate = freshIdentifier(original) + sanitizedReferences.set(original, candidate) + return candidate + } + + function freshIdentifier(seed: string): string { + const sanitized = sanitizeJavaScriptIdentifier(seed) + let candidate = sanitized + let suffix = 0 + while (uniqueIdentifiers.has(candidate)) candidate = `${sanitized}${++suffix}` + uniqueIdentifiers.add(candidate) + return candidate + } + + function addImport(importDeclaration: string): void { + if (!artifacts.some((artifact) => artifact._tag === "Import" && artifact.importDeclaration === importDeclaration)) { + artifacts.push({ _tag: "Import", importDeclaration }) + } + } + + function addSymbol(symbol: symbol): string { + const identifier = freshIdentifier("_symbol") + const key = globalThis.Symbol.keyFor(symbol) + const description = symbol.description + artifacts.push({ + _tag: "Symbol", + identifier, + code: makeCode( + key === undefined + ? `Symbol(${description === undefined ? "" : format(description)})` + : `Symbol.for(${format(key)})`, + `typeof ${identifier}` + ) + }) + return identifier + } + + function annotationSchemas( + representation: CheckRepresentationAnnotation | undefined, + path: Path + ): ReadonlyArray { + return representation?.schemas?.map((schema, index) => recur(schema, [...path, "schemas", index])) ?? [] + } + + function checkBrands( + check: SchemaRepresentation.Check + ): ReadonlyArray { + const own = InternalAnnotations.collectBrands(check.annotations) + if ( + check._tag === "FilterGroup" && + check.annotations?.toCode === undefined + ) { + return [...own, ...check.checks.flatMap(checkBrands)] + } + return own + } + + function runtimeBrands(brands: ReadonlyArray): string { + return brands.length === 0 + ? "" + : `.pipe(${brands.map((brand) => `Schema.brand(${format(brand)})`).join(", ")})` + } + + function typeBrands(brands: ReadonlyArray): string { + if (brands.length === 0) return "" + addImport(`import type * as Brand from "effect/Brand"`) + return brands.map((brand) => ` & Brand.Brand<${format(brand)}>`).join("") + } + + function runtimeAnnotate( + annotations: Schema.Annotations.Annotations | undefined, + method: "annotate" | "annotateKey" = "annotate" + ): string { + const rendered = renderAnnotations(annotations) + return rendered === undefined ? "" : `.${method}(${rendered})` + } + + function compileCheck( + check: SchemaRepresentation.Check, + path: Path + ): string { + const callback = check.annotations?.toCode + let runtime: string + if (callback !== undefined) { + const schemas = annotationSchemas(check.representation, [...path, "representation"]) + const output = (callback as SchemaRepresentation.Generation.Check)({ schemas }) + for (const importDeclaration of output.importDeclarations ?? []) addImport(importDeclaration) + runtime = output.runtime + } else if (check._tag === "Filter") { + throw errorWithPath("Missing toCode callback", [...path, "annotations", "toCode"]) + } else { + runtime = `Schema.makeFilterGroup([${ + check.checks.map((child, index) => compileCheck(child, [...path, "checks", index])).join(", ") + }])` + } + runtime += runtimeAnnotate(check.annotations) + if (check._tag === "Filter" && check.aborted) runtime += ".abort()" + return runtime + } + + function applyNode( + base: SchemaRepresentation.Code, + representation: Exclude, + path: Path, + includeTypeBrands: boolean = true + ): SchemaRepresentation.Code { + const nodeBrands = InternalAnnotations.collectBrands(representation.annotations) + let runtime = base.runtime + runtimeAnnotate(representation.annotations) + runtimeBrands(nodeBrands) + let Type = base.Type + (includeTypeBrands ? typeBrands(nodeBrands) : "") + for (let index = 0; index < representation.checks.length; index++) { + const check = representation.checks[index] + const brands = checkBrands(check) + runtime += `.check(${compileCheck(check, [...path, "checks", index])})${runtimeBrands(brands)}` + if (includeTypeBrands) Type += typeBrands(brands) + } + return makeCode(runtime, Type) + } + + function recur( + representation: SchemaRepresentation.Representation, + path: Path, + includeTypeBrands: boolean = true + ): SchemaRepresentation.Code { + if (representation._tag === "Reference") { + if (!Object.hasOwn(document.references, representation.$ref)) { + throw errorWithPath(`Invalid reference ${representation.$ref}`, [...path, "$ref"]) + } + const identifier = ensureUniqueIdentifier(representation.$ref) + if ( + compilingRecursiveDefinition && explicitSuspendDepth === 0 && + Object.hasOwn(sorted.recursives, representation.$ref) + ) { + return makeCode(`Schema.suspend((): Schema.Codec<${identifier}> => ${identifier})`, identifier) + } + const Type = includeTypeBrands + ? identifier + : recur(document.references[representation.$ref], ["references", representation.$ref], false).Type + return makeCode(identifier, Type) + } + return applyNode(on(representation, path, includeTypeBrands), representation, path, includeTypeBrands) + } + + function on( + representation: Exclude, + path: Path, + includeTypeBrands: boolean + ): SchemaRepresentation.Code { + switch (representation._tag) { + case "Declaration": { + const callback = representation.annotations?.toCode + if (callback === undefined) { + throw errorWithPath("Missing toCode callback", [...path, "annotations", "toCode"]) + } + const typeParameters = representation.typeParameters.map((typeParameter, index) => + recur(typeParameter, [...path, "typeParameters", index]) + ) + const output = (callback as SchemaRepresentation.Generation.Declaration)({ typeParameters }) + for (const importDeclaration of output.importDeclarations ?? []) addImport(importDeclaration) + return makeCode(output.runtime, output.Type) + } + case "Suspend": { + explicitSuspendDepth++ + const thunk = recur(representation.thunk, [...path, "thunk"]) + explicitSuspendDepth-- + return makeCode(`Schema.suspend((): Schema.Codec<${thunk.Type}> => ${thunk.runtime})`, thunk.Type) + } + case "Null": + return makeCode("Schema.Null", "null") + case "Undefined": + return makeCode("Schema.Undefined", "undefined") + case "Void": + return makeCode("Schema.Void", "void") + case "Never": + return makeCode("Schema.Never", "never") + case "Unknown": + return makeCode("Schema.Unknown", "unknown") + case "Any": + return makeCode("Schema.Any", "any") + case "String": + return makeCode("Schema.String", "string") + case "Number": + return makeCode("Schema.Number", "number") + case "Boolean": + return makeCode("Schema.Boolean", "boolean") + case "BigInt": + return makeCode("Schema.BigInt", "bigint") + case "Symbol": + return makeCode("Schema.Symbol", "symbol") + case "Literal": { + const literal = format(representation.literal) + return makeCode(`Schema.Literal(${literal})`, literal) + } + case "UniqueSymbol": { + const identifier = addSymbol(representation.symbol) + return makeCode(`Schema.UniqueSymbol(${identifier})`, `typeof ${identifier}`) + } + case "ObjectKeyword": + return makeCode("Schema.ObjectKeyword", "object") + case "Enum": { + const identifier = freshIdentifier("_Enum") + artifacts.push({ + _tag: "Enum", + identifier, + code: makeCode( + `enum ${identifier} { ${ + representation.enums.map(([name, value]) => `${format(name)} = ${format(value)}`).join( + ", " + ) + } }`, + `typeof ${identifier}` + ) + }) + return makeCode(`Schema.Enum(${identifier})`, identifier) + } + case "TemplateLiteral": { + const parts = representation.parts.map((part, index) => recur(part, [...path, "parts", index], false)) + const Type = `\`${parts.map((part) => `\${${part.Type}}`).join("")}\`` + return makeCode(`Schema.TemplateLiteral([${parts.map((part) => part.runtime).join(", ")}])`, Type) + } + case "Arrays": { + const elements = representation.elements.map((element, index) => { + const type = recur(element.type, [...path, "elements", index, "type"]) + return makeCode( + `${element.isOptional ? "Schema.optionalKey(" : ""}${type.runtime}${element.isOptional ? ")" : ""}${ + runtimeAnnotate(element.annotations, "annotateKey") + }`, + `${type.Type}${element.isOptional ? "?" : ""}` + ) + }) + const rest = representation.rest.map((item, index) => recur(item, [...path, "rest", index])) + if (Arr.isArrayNonEmpty(rest)) { + const item = rest[0] + if (elements.length === 0 && rest.length === 1) { + return makeCode(`Schema.Array(${item.runtime})`, `ReadonlyArray<${item.Type}>`) + } + const post = rest.slice(1) + return makeCode( + `Schema.TupleWithRest(Schema.Tuple([${elements.map((element) => element.runtime).join(", ")}]), [${ + rest.map((item) => item.runtime).join(", ") + }])`, + `readonly [${elements.map((element) => element.Type).join(", ")}, ...Array<${item.Type}>${ + post.length > 0 ? `, ${post.map((item) => item.Type).join(", ")}` : "" + }]` + ) + } + return makeCode( + `Schema.Tuple([${elements.map((element) => element.runtime).join(", ")}])`, + `readonly [${elements.map((element) => element.Type).join(", ")}]` + ) + } + case "Objects": { + const properties = representation.propertySignatures.map((property, index) => { + const isSymbol = typeof property.name === "symbol" + const name = isSymbol + ? addSymbol(property.name) + : formatPropertyKey(property.name) + const type = recur(property.type, [...path, "propertySignatures", index, "type"]) + let runtime = type.runtime + if (property.isMutable) runtime = `Schema.mutableKey(${runtime})` + if (property.isOptional) runtime = `Schema.optionalKey(${runtime})` + const runtimeName = isSymbol ? `[${name}]` : name + const typeName = `${property.isMutable ? "" : "readonly "}${runtimeName}${property.isOptional ? "?" : ""}` + return makeCode( + `${runtimeName}: ${runtime}${runtimeAnnotate(property.annotations, "annotateKey")}`, + `${typeName}: ${type.Type}` + ) + }) + const indexSignatures = representation.indexSignatures.map((signature, index) => ({ + parameter: recur(signature.parameter, [...path, "indexSignatures", index, "parameter"]), + type: recur(signature.type, [...path, "indexSignatures", index, "type"]) + })) + const propertyRuntimes = properties.map((property) => property.runtime).join(", ") + const propertyTypes = properties.map((property) => property.Type).join(", ") + if (indexSignatures.length === 0) { + return makeCode( + `Schema.Struct({ ${propertyRuntimes} })`, + `{ ${propertyTypes} }` + ) + } + if (properties.length === 0 && indexSignatures.length === 1) { + const signature = indexSignatures[0] + return makeCode( + `Schema.Record(${signature.parameter.runtime}, ${signature.type.runtime})`, + `{ readonly [x: ${signature.parameter.Type}]: ${signature.type.Type} }` + ) + } + const indexRuntimes = indexSignatures.map((signature) => + `Schema.Record(${signature.parameter.runtime}, ${signature.type.runtime})` + ).join(", ") + const indexTypes = indexSignatures.map((signature) => + `readonly [x: ${signature.parameter.Type}]: ${signature.type.Type}` + ).join(", ") + return makeCode( + `Schema.StructWithRest(Schema.Struct({ ${propertyRuntimes} }), [${indexRuntimes}])`, + `{ ${propertyTypes}${properties.length > 0 ? ", " : ""}${indexTypes} }` + ) + } + case "Union": { + if (representation.types.length === 0) return makeCode("Schema.Never", "never") + if (representation.types.every(isSimpleLiveLiteral)) { + const literals = representation.types.map((literal) => format(literal.literal)) + return literals.length === 1 + ? makeCode(`Schema.Literal(${literals[0]})`, literals[0]) + : makeCode(`Schema.Literals([${literals.join(", ")}])`, literals.join(" | ")) + } + const types = representation.types.map((type, index) => + recur(type, [...path, "types", index], includeTypeBrands) + ) + const mode = representation.mode === "anyOf" ? "" : `, { mode: "oneOf" }` + return makeCode( + `Schema.Union([${types.map((type) => type.runtime).join(", ")}]${mode})`, + types.map((type) => type.Type).join(" | ") + ) + } + } + } +} diff --git a/repos/effect/packages/effect/src/internal/schema/toEquivalence.ts b/repos/effect/packages/effect/src/internal/schema/toEquivalence.ts new file mode 100644 index 0000000000..1cce02ffdf --- /dev/null +++ b/repos/effect/packages/effect/src/internal/schema/toEquivalence.ts @@ -0,0 +1,158 @@ +import * as Equal from "../../Equal.ts" +import * as Equivalence from "../../Equivalence.ts" +import { memoize } from "../../Function.ts" +import * as Predicate from "../../Predicate.ts" +import type * as Schema from "../../Schema.ts" +import * as SchemaAST from "../../SchemaAST.ts" +import * as SchemaParser from "../../SchemaParser.ts" +import * as InternalAnnotations from "./annotations.ts" + +/** @internal */ +export const toEquivalence = memoize((ast: SchemaAST.AST): Equivalence.Equivalence => { + return recur(ast, []) +}) + +function recur(ast: SchemaAST.AST, path: ReadonlyArray): Equivalence.Equivalence { + // --------------------------------------------- + // handle annotations + // --------------------------------------------- + const annotation = InternalAnnotations.resolve(ast)?.["toEquivalence"] as + | Schema.Annotations.ToEquivalence.Declaration> + | undefined + if (annotation) { + return annotation(SchemaAST.isDeclaration(ast) ? ast.typeParameters.map((tp) => recur(tp, path)) : []) + } + switch (ast._tag) { + case "Never": + return Equivalence.strictEqual() + case "Declaration": + case "Null": + case "Undefined": + case "Void": + case "Unknown": + case "Any": + case "String": + case "Number": + case "Boolean": + case "BigInt": + case "Symbol": + case "Literal": + case "UniqueSymbol": + case "ObjectKeyword": + case "Enum": + case "TemplateLiteral": + return Equal.equals + case "Arrays": { + const elements = ast.elements.map((e, i) => recur(e, [...path, i])) + const len = ast.elements.length + const rest = ast.rest.map((r, i) => recur(r, [...path, len + i])) + return Equivalence.make((a, b) => { + if (!Array.isArray(a) || !Array.isArray(b)) { + return false + } + const len = a.length + if (len !== b.length) { + return false + } + // --------------------------------------------- + // handle elements + // --------------------------------------------- + let i = 0 + for (; i < Math.min(len, ast.elements.length); i++) { + if (!elements[i](a[i], b[i])) { + return false + } + } + // --------------------------------------------- + // handle rest element + // --------------------------------------------- + if (rest.length > 0) { + const [head, ...tail] = rest + for (; i < len - tail.length; i++) { + if (!head(a[i], b[i])) { + return false + } + } + // --------------------------------------------- + // handle post rest elements + // --------------------------------------------- + for (let j = 0; j < tail.length; j++) { + if (!tail[j](a[i + j], b[i + j])) { + return false + } + } + } + return true + }) + } + case "Objects": { + if (ast.propertySignatures.length === 0 && ast.indexSignatures.length === 0) { + return Equal.equals + } + const propertySignatures = ast.propertySignatures.map((ps) => recur(ps.type, [...path, ps.name])) + const indexSignatures = ast.indexSignatures.map((is) => recur(is.type, path)) + return Equivalence.make((a, b) => { + if (!Predicate.isObject(a) || !Predicate.isObject(b)) { + return false + } + // --------------------------------------------- + // handle property signatures + // --------------------------------------------- + for (let i = 0; i < propertySignatures.length; i++) { + const ps = ast.propertySignatures[i] + const name = ps.name + const aHas = Object.hasOwn(a, name) + const bHas = Object.hasOwn(b, name) + if (SchemaAST.isOptional(ps.type)) { + if (aHas !== bHas) { + return false + } + } + if (aHas && bHas && !propertySignatures[i](a[name], b[name])) { + return false + } + } + // --------------------------------------------- + // handle index signatures + // --------------------------------------------- + for (let i = 0; i < indexSignatures.length; i++) { + const is = ast.indexSignatures[i] + const aKeys = SchemaAST.getIndexSignatureKeys(a, is.parameter) + const bKeys = SchemaAST.getIndexSignatureKeys(b, is.parameter) + + if (aKeys.length !== bKeys.length) return false + + for (let j = 0; j < aKeys.length; j++) { + const key = aKeys[j] + if (!Object.hasOwn(b, key) || !indexSignatures[i](a[key], b[key])) { + return false + } + } + } + return true + }) + } + case "Union": { + const types = SchemaAST.toType(ast).types + const compiled = new Map( + types.map((candidate, i) => + [candidate, [SchemaParser._is(candidate), recur(ast.types[i], path)] as const] as const + ) + ) + return Equivalence.make((a, b) => { + const candidates = SchemaAST.getCandidates(a, types) + for (let i = 0; i < candidates.length; i++) { + const [is, equivalence] = compiled.get(candidates[i])! + if (is(a) && is(b)) { + return equivalence(a, b) + } + } + return false + }) + } + case "Suspend": { + const get = SchemaAST.memoizeThunk(() => recur(ast.thunk(), path)) + return Equivalence.make((a, b) => get()(a, b)) + } + } +} diff --git a/repos/effect/packages/effect/src/internal/schema/toJsonSchemaDocument.ts b/repos/effect/packages/effect/src/internal/schema/toJsonSchemaDocument.ts new file mode 100644 index 0000000000..89900ee499 --- /dev/null +++ b/repos/effect/packages/effect/src/internal/schema/toJsonSchemaDocument.ts @@ -0,0 +1,549 @@ +import * as Arr from "../../Array.ts" +import * as Equal from "../../Equal.ts" +import { escapeToken, unescapeToken } from "../../JsonPointer.ts" +import type * as JsonSchema from "../../JsonSchema.ts" +import { rewriteRefs } from "../../JsonSchema.ts" +import * as RegEx from "../../RegExp.ts" +import type * as Schema from "../../Schema.ts" +import * as SchemaAST from "../../SchemaAST.ts" +import type * as SchemaRepresentation from "../../SchemaRepresentation.ts" +import { errorWithPath } from "../errors.ts" +import * as InternalRecord from "../record.ts" +import * as InternalAnnotations from "./annotations.ts" + +type Path = ReadonlyArray +type CheckRepresentationAnnotation = SchemaRepresentation.CheckRepresentationAnnotation< + SchemaRepresentation.Representation +> + +const jsonSchemaAnnotationExcludedKeys = new Set([ + ...InternalAnnotations.annotationExcludedKeys, + InternalAnnotations.IDENTIFIER_FALLBACK_KEY, + ...InternalAnnotations.jsonSchemaAnnotationKeys +]) + +/** @internal */ +export const toRepresentationOptions = { + isAnonymousReferenceAllowed: (ast: SchemaAST.AST): boolean => !SchemaAST.isDeclaration(ast) +} + +function collectJsonSchemaAnnotations( + annotations: Schema.Annotations.Annotations | undefined, + options: Schema.ToJsonSchemaOptions | undefined +): JsonSchema.JsonSchema | undefined { + if (annotations === undefined) return undefined + + const out: JsonSchema.JsonSchema = {} + const title = annotations.title + if (typeof title === "string") out.title = title + const description = annotations.description + const expected = annotations.expected + if (typeof description === "string") out.description = description + else if (options?.generateDescriptions === true && typeof expected === "string") out.description = expected + + const defaultValue = annotations.default + if (SchemaAST.isJson(defaultValue)) out.default = defaultValue + const examples = annotations.examples + if (Array.isArray(examples) && SchemaAST.isJson(examples)) out.examples = examples + const readOnly = annotations.readOnly + if (typeof readOnly === "boolean") out.readOnly = readOnly + const writeOnly = annotations.writeOnly + if (typeof writeOnly === "boolean") out.writeOnly = writeOnly + const format = annotations.format + if (typeof format === "string") out.format = format + const contentEncoding = annotations.contentEncoding + if (typeof contentEncoding === "string") out.contentEncoding = contentEncoding + const contentMediaType = annotations.contentMediaType + if (typeof contentMediaType === "string") out.contentMediaType = contentMediaType + const contentSchema = annotations.contentSchema + if (SchemaAST.isJson(contentSchema)) out.contentSchema = contentSchema + + if (options?.includeAnnotationKey !== undefined) { + for (const [key, value] of Object.entries(annotations)) { + if ( + jsonSchemaAnnotationExcludedKeys.has(key) || + !options.includeAnnotationKey(key) + ) { + continue + } + if (SchemaAST.isJson(value)) InternalRecord.assignProperty(out, key, value) + } + } + + return Object.keys(out).length === 0 ? undefined : out +} + +type JsonSchemaNumberType = "number" | "integer" + +function extractJsonSchemaNumberType(schema: JsonSchema.JsonSchema): { + readonly type: JsonSchemaNumberType | undefined + readonly schema: JsonSchema.JsonSchema +} { + let type: JsonSchemaNumberType | undefined = schema.type === "number" || schema.type === "integer" + ? schema.type + : undefined + let out = schema + if (type !== undefined) { + out = { ...schema } + delete out.type + } + if (Array.isArray(out.allOf)) { + const members: Array = [] + let changed = false + for (const member of out.allOf) { + const extracted = extractJsonSchemaNumberType(member) + if (extracted.type !== undefined) { + changed = true + if (type === undefined || extracted.type === "integer") type = extracted.type + } + if (Object.keys(extracted.schema).length > 0) members.push(extracted.schema) + } + if (changed) { + const { allOf: _, ...rest } = out + out = members.length === 0 ? rest : { ...rest, allOf: members } + } + } + return { type, schema: out } +} + +function isJsonSchemaNumberEncoding(schema: JsonSchema.JsonSchema): boolean { + return Array.isArray(schema.anyOf) && schema.anyOf.length === 4 && schema.anyOf[0]?.type === "number" && + schema.anyOf.slice(1).every((member) => member.type === "string") +} + +function appendJsonSchema( + left: JsonSchema.JsonSchema, + right: JsonSchema.JsonSchema +): JsonSchema.JsonSchema { + if (Object.keys(left).length === 0) return right + const rightKeys = Object.keys(right) + if (rightKeys.length === 0) return left + const leftType = left.type === "number" || left.type === "integer" ? left.type : undefined + const isNumberEncoding = isJsonSchemaNumberEncoding(left) + if (leftType !== undefined || isNumberEncoding) { + const extracted = extractJsonSchemaNumberType(right) + if (extracted.type !== undefined) { + const type = leftType === "integer" || extracted.type === "integer" ? "integer" : "number" + const base: JsonSchema.JsonSchema = { ...left, type } + if (isNumberEncoding) delete base.anyOf + return Object.keys(extracted.schema).length === 0 ? base : appendJsonSchema(base, extracted.schema) + } + } + const members = Array.isArray(right.allOf) && rightKeys.length === 1 ? right.allOf : [right] + if (Array.isArray(left.allOf)) { + return { ...left, allOf: [...left.allOf, ...members] } + } + if (typeof left.$ref === "string") { + return { allOf: [left, ...members] } + } + return { ...left, allOf: members } +} + +function compileJsonSchema( + representations: readonly [ + SchemaRepresentation.Representation, + ...Array + ], + rootPaths: ReadonlyArray, + references: SchemaRepresentation.References, + options: Schema.ToJsonSchemaOptions | undefined +): JsonSchema.MultiDocument<"draft-2020-12"> { + // null = compiling, string = canonical key, object = compiled schema + const definitionStates = new Map() + const compiledRepresentations = new WeakMap() + const fallbackDefinitions = new Map>() + let hasAliases = false + const referenceKeys = Object.keys(references) + for (const key of referenceKeys) { + compileDefinition(key, ["references", key]) + } + const schemas = Arr.map( + representations, + (representation, index) => finalizeJsonSchema(recur(representation, rootPaths[index])) + ) + const definitions: Record = {} + for (const key of referenceKeys) { + const compiled = definitionStates.get(key)! + if (typeof compiled !== "string") { + InternalRecord.assignProperty(definitions, key, finalizeJsonSchema(compiled)) + } + } + return { dialect: "draft-2020-12", schemas, definitions } + + function compileDefinition(key: string, path: Path): string { + const compiled = definitionStates.get(key) + if (compiled !== undefined) return typeof compiled === "string" ? compiled : key + if (!Object.hasOwn(references, key)) { + throw errorWithPath(`Invalid reference ${key}`, [...path, "$ref"]) + } + + definitionStates.set(key, null) + const representation = references[key] + const schema = recur(representation, ["references", key]) + + const fallback = getIdentifierFallback(representation) + if (fallback !== undefined) { + const candidates = fallbackDefinitions.get(fallback) + const match = candidates?.find((candidate) => Equal.equals(definitionStates.get(candidate), schema)) + if (match === undefined) { + if (candidates === undefined) fallbackDefinitions.set(fallback, [key]) + else candidates.push(key) + } else { + hasAliases = true + definitionStates.set(key, match) + return match + } + } + definitionStates.set(key, schema) + return key + } + + function finalizeJsonSchema(schema: JsonSchema.JsonSchema): JsonSchema.JsonSchema { + if (!hasAliases) return schema + return rewriteRefs(schema, ($ref) => + $ref.replace(/^#\/\$defs\/([^/]*)/, (match, token) => { + const canonical = definitionStates.get(unescapeToken(token)) + return typeof canonical === "string" ? `#/$defs/${escapeToken(canonical)}` : match + })) + } + + function getIdentifierFallback( + representation: SchemaRepresentation.Representation + ): string | undefined { + if (representation._tag === "Reference") return undefined + const annotations = representation.checks.length === 0 + ? representation.annotations + : representation.checks[representation.checks.length - 1].annotations + return typeof annotations?.identifier !== "string" && + typeof annotations?.[InternalAnnotations.IDENTIFIER_FALLBACK_KEY] === "string" + ? annotations[InternalAnnotations.IDENTIFIER_FALLBACK_KEY] + : undefined + } + + function annotationSchemas( + representation: CheckRepresentationAnnotation | undefined, + path: Path + ): ReadonlyArray { + return representation?.schemas?.map((schema, index) => recur(schema, [...path, "schemas", index])) ?? [] + } + + function compileCheck( + check: SchemaRepresentation.Check, + type: JsonSchema.Type | undefined, + path: Path + ): JsonSchema.JsonSchema | undefined { + const annotations = check.annotations + const callback = annotations?.toJsonSchema + if (callback !== undefined) { + const schemas = annotationSchemas(check.representation, [...path, "representation"]) + const fragment = (callback as SchemaRepresentation.ToJsonSchema.Check)({ type, schemas }) + const ordinary = collectJsonSchemaAnnotations(annotations, options) + return ordinary === undefined ? fragment : { ...fragment, ...ordinary } + } + if (check._tag === "Filter") return undefined + + const children = check.checks + .map((child, index) => compileCheck(child, type, [...path, "checks", index])) + .filter((child): child is JsonSchema.JsonSchema => child !== undefined) + if (children.length === 0) return undefined + const ordinary = collectJsonSchemaAnnotations(annotations, options) + return ordinary === undefined ? { allOf: children } : { allOf: children, ...ordinary } + } + + function recur( + representation: SchemaRepresentation.Representation, + path: Path + ): JsonSchema.JsonSchema { + if (representation._tag === "Reference") { + const canonical = compileDefinition(representation.$ref, path) + return { $ref: `#/$defs/${escapeToken(canonical)}` } + } + const cached = compiledRepresentations.get(representation) + if (cached !== undefined) return cached + + let output = on(representation, path) + const ordinary = collectJsonSchemaAnnotations(representation.annotations, options) + if (ordinary !== undefined) { + output = { ...output, ...ordinary } + } + for (let index = 0; index < representation.checks.length; index++) { + const type = typeof output.type === "string" && isJsonSchemaType(output.type) ? output.type : undefined + const check = compileCheck(representation.checks[index], type, [...path, "checks", index]) + if (check !== undefined) { + output = appendJsonSchema(output, check) + } + } + compiledRepresentations.set(representation, output) + return output + } + + function on( + representation: Exclude, + path: Path + ): JsonSchema.JsonSchema { + switch (representation._tag) { + case "Any": + case "Unknown": + return {} + case "ObjectKeyword": + return { anyOf: [{ type: "object" }, { type: "array" }] } + case "Void": + case "Undefined": + case "Null": + return { type: "null" } + case "BigInt": + return { type: "string", allOf: [{ pattern: "^-?\\d+$" }] } + case "Symbol": + case "UniqueSymbol": + return { type: "string", allOf: [{ pattern: "^Symbol\\((.*)\\)$" }] } + case "Declaration": { + return {} + } + case "Suspend": + return recur(representation.thunk, [...path, "thunk"]) + case "Never": + return { not: {} } + case "String": + return { type: "string" } + case "Number": + return { + anyOf: [ + { type: "number" }, + { type: "string", enum: ["NaN"] }, + { type: "string", enum: ["Infinity"] }, + { type: "string", enum: ["-Infinity"] } + ] + } + case "Boolean": + return { type: "boolean" } + case "Literal": { + const literal = representation.literal + return typeof literal === "bigint" + ? { type: "string", enum: [globalThis.String(literal)] } + : { type: typeof literal, enum: [literal] } + } + case "Enum": { + const types = representation.enums.map(([title, literal]) => + typeof literal === "number" && !globalThis.Number.isFinite(literal) + ? { type: "string", enum: [globalThis.String(literal)], title } + : { type: typeof literal, enum: [literal], title } + ) + return types.length === 0 ? { not: {} } : { anyOf: types } + } + case "TemplateLiteral": + return { type: "string", pattern: `^${representation.parts.map(getPartPattern).join("")}$` } + case "Arrays": { + if (representation.rest.length > 1) { + throw errorWithPath("Invalid schema representation document", [...path, "rest"]) + } + const out: JsonSchema.JsonSchema = { type: "array" } + let minItems = representation.elements.length + const prefixItems = representation.elements.map((element, index) => { + if (element.isOptional) minItems-- + const compiled = recur(element.type, [...path, "elements", index, "type"]) + const annotations = collectJsonSchemaAnnotations(element.annotations, options) + return annotations === undefined ? compiled : appendJsonSchema(compiled, annotations) + }) + if (prefixItems.length > 0) { + out.prefixItems = prefixItems + out.maxItems = representation.elements.length + if (minItems > 0) out.minItems = minItems + } else { + out.items = false + } + if (representation.rest.length === 1) { + delete out.maxItems + const rest = recur(representation.rest[0], [...path, "rest", 0]) + if (Object.keys(rest).length > 0) out.items = rest + else delete out.items + } + return out + } + case "Objects": { + if (representation.propertySignatures.length === 0 && representation.indexSignatures.length === 0) { + return { anyOf: [{ type: "object" }, { type: "array" }] } + } + const out: JsonSchema.JsonSchema = { type: "object" } + const properties: Record = {} + const required: Array = [] + for (let index = 0; index < representation.propertySignatures.length; index++) { + const property = representation.propertySignatures[index] + if (typeof property.name !== "string") { + throw errorWithPath("Invalid schema representation document", [ + ...path, + "propertySignatures", + index, + "name" + ]) + } + const name = property.name + const compiled = recur(property.type, [...path, "propertySignatures", index, "type"]) + const annotations = collectJsonSchemaAnnotations(property.annotations, options) + InternalRecord.assignProperty( + properties, + name, + annotations === undefined ? compiled : appendJsonSchema(compiled, annotations) + ) + if (!property.isOptional) required.push(name) + } + if (representation.propertySignatures.length > 0) out.properties = properties + if (required.length > 0) out.required = required + out.additionalProperties = options?.additionalProperties ?? false + const patternProperties: Record = {} + for (let index = 0; index < representation.indexSignatures.length; index++) { + const signature = representation.indexSignatures[index] + let type: JsonSchema.JsonSchema | false = recur( + signature.type, + [...path, "indexSignatures", index, "type"] + ) + if (Object.keys(type).length === 1 && "not" in type) type = false + const patterns = getParameterPatterns( + signature.parameter, + [...path, "indexSignatures", index, "parameter"], + new Set() + ) + if (patterns.length === 0) { + out.additionalProperties = type + } else { + for (const pattern of patterns) InternalRecord.assignProperty(patternProperties, pattern, type) + } + } + if (Object.keys(patternProperties).length > 0) { + out.patternProperties = patternProperties + delete out.additionalProperties + } + if ( + typeof out.additionalProperties === "object" && + out.additionalProperties !== null && + Object.keys(out.additionalProperties).length === 0 + ) { + delete out.additionalProperties + } + return out + } + case "Union": { + const types = representation.types.map((type, index) => recur(type, [...path, "types", index])) + if (types.length === 0) return { not: {} } + if (types.length > 1) { + const compacted = compactEnums(types) + if (compacted !== undefined) return compacted + } + return representation.mode === "anyOf" ? { anyOf: types } : { oneOf: types } + } + } + } + + function getParameterPatterns( + parameter: SchemaRepresentation.Representation, + path: Path, + seenReferences: ReadonlySet + ): ReadonlyArray { + switch (parameter._tag) { + case "Reference": { + if (!Object.hasOwn(references, parameter.$ref)) { + throw errorWithPath(`Invalid reference ${parameter.$ref}`, [...path, "$ref"]) + } + compileDefinition(parameter.$ref, path) + if (seenReferences.has(parameter.$ref)) return [] + const next = new Set(seenReferences).add(parameter.$ref) + return getParameterPatterns(references[parameter.$ref], ["references", parameter.$ref], next) + } + case "String": + return collectPatterns(recur(parameter, path)) + case "TemplateLiteral": + return [`^${parameter.parts.map(getPartPattern).join("")}$`] + case "Union": + return parameter.types.flatMap((type, index) => + getParameterPatterns(type, [...path, "types", index], seenReferences) + ) + default: + throw errorWithPath("Invalid schema representation document", path) + } + } +} + +function isJsonSchemaType(input: string): input is JsonSchema.Type { + return input === "string" || input === "number" || input === "boolean" || input === "array" || + input === "object" || input === "null" || input === "integer" +} + +function compactEnums( + schemas: ReadonlyArray +): JsonSchema.JsonSchema | undefined { + let sharedType: unknown = undefined + const values: Array = [] + for (const schema of schemas) { + const keys = Object.keys(schema) + if (keys.length !== 2 || schema.type === undefined || !Array.isArray(schema.enum) || schema.enum.length === 0) { + return undefined + } + if (sharedType === undefined) sharedType = schema.type + else if (schema.type !== sharedType) return undefined + values.push(...schema.enum) + } + return { type: sharedType, enum: values } +} + +function collectPatterns(schema: JsonSchema.JsonSchema): ReadonlyArray { + const patterns: Array = [] + if (typeof schema.pattern === "string") patterns.push(schema.pattern) + for (const key of ["allOf", "anyOf", "oneOf"] as const) { + const members = schema[key] + if (Array.isArray(members)) { + for (const member of members) { + if (typeof member === "object" && member !== null && !Array.isArray(member)) { + patterns.push(...collectPatterns(member)) + } + } + } + } + return patterns +} + +function getPartPattern(part: SchemaRepresentation.Representation): string { + switch (part._tag) { + case "Literal": + return RegEx.escape(globalThis.String(part.literal)) + case "String": + return SchemaAST.STRING_PATTERN + case "Number": + return SchemaAST.FINITE_PATTERN + case "TemplateLiteral": + return part.parts.map(getPartPattern).join("") + case "Union": + return part.types.map(getPartPattern).join("|") + default: + throw errorWithPath("Invalid schema representation document", []) + } +} + +/** @internal */ +export function toJsonSchemaDocument( + document: SchemaRepresentation.Document, + options?: Schema.ToJsonSchemaOptions +): JsonSchema.Document<"draft-2020-12"> { + const output = compileJsonSchema( + [document.representation], + [["representation"]], + document.references, + options + ) + return { + dialect: output.dialect, + schema: output.schemas[0], + definitions: output.definitions + } +} + +/** @internal */ +export function toJsonSchemaMultiDocument( + document: SchemaRepresentation.MultiDocument, + options?: Schema.ToJsonSchemaOptions +): JsonSchema.MultiDocument<"draft-2020-12"> { + return compileJsonSchema( + document.representations, + document.representations.map((_, index) => ["representations", index]), + document.references, + options + ) +} diff --git a/repos/effect/packages/effect/src/internal/schema/toRepresentation.ts b/repos/effect/packages/effect/src/internal/schema/toRepresentation.ts new file mode 100644 index 0000000000..0816d04e38 --- /dev/null +++ b/repos/effect/packages/effect/src/internal/schema/toRepresentation.ts @@ -0,0 +1,404 @@ +import * as Arr from "../../Array.ts" +import type * as Schema from "../../Schema.ts" +import * as SchemaAST from "../../SchemaAST.ts" +import type * as SchemaRepresentation from "../../SchemaRepresentation.ts" +import * as InternalRecord from "../record.ts" +import * as InternalAnnotations from "./annotations.ts" + +/** @internal */ +export function toRepresentation( + ast: SchemaAST.AST, + options?: Options +): SchemaRepresentation.Document { + const { references, representations } = toRepresentations([ast], options) + return { representation: representations[0], references } +} + +/** @internal */ +export function toRepresentations( + asts: readonly [SchemaAST.AST, ...Array], + options?: Options +): SchemaRepresentation.MultiDocument { + return fromASTs(asts, options) +} + +/** @internal */ +export interface Options { + readonly isAnonymousReferenceAllowed?: ((ast: SchemaAST.AST) => boolean) | undefined +} + +type CheckRepresentationAnnotation = SchemaRepresentation.CheckRepresentationAnnotation< + SchemaRepresentation.Representation +> + +function annotationsField(annotations: A | undefined): { readonly annotations: A } | undefined { + return annotations === undefined ? undefined : { annotations } +} + +function hasShareableStructure( + ast: SchemaAST.AST, + isAnonymousReferenceAllowed: Options["isAnonymousReferenceAllowed"] +): boolean { + if (isAnonymousReferenceAllowed?.(ast) === false) return false + switch (ast._tag) { + case "Arrays": + case "Objects": + case "Suspend": + return true + case "Declaration": + return true + case "Union": + return ast.types.some((ast) => hasShareableStructure(ast, isAnonymousReferenceAllowed)) + default: + return false + } +} + +function isWorthReferencing(bodyCost: number, occurrences: number): boolean { + return occurrences * bodyCost > bodyCost + occurrences + 1 +} + +function isAnonymousReferenceEligible( + ast: SchemaAST.AST, + occurrences: number, + isAnonymousReferenceAllowed: Options["isAnonymousReferenceAllowed"] +): boolean { + if (isAnonymousReferenceAllowed?.(ast) === false) return false + if (hasShareableStructure(ast, isAnonymousReferenceAllowed)) return true + switch (ast._tag) { + case "Union": + return isWorthReferencing(ast.types.length + 1, occurrences) + case "Enum": + return isWorthReferencing(ast.enums.length + 1, occurrences) + case "TemplateLiteral": + return isWorthReferencing(ast.parts.length + 1, occurrences) + case "Literal": + return typeof ast.literal === "string" && + isWorthReferencing(ast.literal.length / 32 + 1, occurrences) + default: + return false + } +} + +interface ReferenceIdentifier { + readonly identifier: string + readonly fallback?: string | undefined +} + +function resolveReferenceIdentifier( + input: SchemaAST.AST, + encoded: SchemaAST.AST +): ReferenceIdentifier | undefined { + const identifier = InternalAnnotations.resolveIdentifier(encoded) + if (identifier !== undefined) return { identifier } + const fallback = (encoded !== input ? InternalAnnotations.resolveIdentifier(input) : undefined) ?? + InternalAnnotations.resolveIdentifierFallback(encoded) + return fallback === undefined + ? undefined + : { identifier: `${fallback}Encoded`, fallback } +} + +function fromASTs( + asts: readonly [SchemaAST.AST, ...Array], + options: Options | undefined +): SchemaRepresentation.MultiDocument { + const references: Record = {} + const anonymousReferences = new Map() + const referenceOwners = new Map() + const buildingReferences = new Set() + const visiting = new Set() + const occurrences = new Map() + const shared = new Set() + + for (const ast of asts) visit(ast) + + const representations = Arr.map(asts, (ast) => recur(ast)) + + return { representations, references } + + function getReference(prefix: string, owner: SchemaAST.AST, separator = "_"): string { + let candidate = prefix + let suffix = 0 + while (referenceOwners.has(candidate)) { + if (referenceOwners.get(candidate) === owner) return candidate + candidate = `${prefix}${separator}${++suffix}` + } + referenceOwners.set(candidate, owner) + return candidate + } + + function annotateReference( + ast: SchemaAST.AST, + referenceIdentifier: ReferenceIdentifier, + reference: string + ): SchemaAST.AST { + const fallback = referenceIdentifier.fallback + if (fallback !== undefined) { + return InternalAnnotations.resolveIdentifierFallback(ast) === fallback + ? ast + : SchemaAST.annotate(ast, { + [InternalAnnotations.IDENTIFIER_FALLBACK_KEY]: fallback + }) + } + return reference === referenceIdentifier.identifier + ? ast + : SchemaAST.annotate(ast, { identifier: reference }) + } + + function makeReference(reference: string, ast: SchemaAST.AST): SchemaRepresentation.Reference { + if (!Object.hasOwn(references, reference) && !buildingReferences.has(reference)) { + buildingReferences.add(reference) + const representation = on(ast) + buildingReferences.delete(reference) + InternalRecord.assignProperty(references, reference, representation) + } + return { _tag: "Reference", $ref: reference } + } + + function visit(input: SchemaAST.AST): void { + const ast = SchemaAST.getLastEncoding(input) + const owner = SchemaAST.getContextOwner(ast) + const count = (occurrences.get(owner) ?? 0) + 1 + occurrences.set(owner, count) + if (count > 1) { + if ( + !shared.has(owner) && + isAnonymousReferenceEligible(owner, count, options?.isAnonymousReferenceAllowed) + ) shared.add(owner) + return + } + visitChecks(ast.checks) + switch (ast._tag) { + case "Declaration": + case "Arrays": + case "Objects": + case "Union": + ast.recur((child) => { + visit(child) + return child + }) + break + case "TemplateLiteral": + ast.parts.forEach(visit) + break + case "Suspend": + visit(ast.thunk()) + break + } + } + + function visitChecks(checks: SchemaAST.Checks | undefined): void { + checks?.forEach((check) => { + check.annotations?.representation?.schemas?.forEach((schema) => visit(SchemaAST.toType(schema))) + if (check._tag === "FilterGroup") visitChecks(check.checks) + }) + } + + function recur(input: SchemaAST.AST): SchemaRepresentation.Representation { + const ast = SchemaAST.getLastEncoding(input) + const owner = SchemaAST.getContextOwner(ast) + const referenceIdentifier = resolveReferenceIdentifier(input, ast) + if (referenceIdentifier !== undefined) { + const reference = getReference(referenceIdentifier.identifier, owner) + return makeReference(reference, annotateReference(ast, referenceIdentifier, reference)) + } + + const found = anonymousReferences.get(owner) + if (found !== undefined) { + return { _tag: "Reference", $ref: found } + } + + const isShared = shared.has(owner) + if (isShared || visiting.has(owner)) { + const reference = getReference(`${ast._tag}_`, owner, "") + anonymousReferences.set(owner, reference) + return isShared + ? makeReference(reference, ast) + : { _tag: "Reference", $ref: reference } + } + + visiting.add(owner) + const representation = on(ast) + visiting.delete(owner) + + const reference = anonymousReferences.get(owner) + if (reference !== undefined) { + InternalRecord.assignProperty(references, reference, representation) + return { _tag: "Reference", $ref: reference } + } + + return representation + } + + function on(ast: SchemaAST.AST): SchemaRepresentation.Representation { + const checks = fromChecks(ast.checks) + switch (ast._tag) { + case "Declaration": + return { + _tag: "Declaration", + typeParameters: ast.typeParameters.map((ast) => recur(ast)), + checks, + ...fromDeclarationAnnotations(ast.annotations) + } + case "Null": + case "Undefined": + case "Void": + case "Never": + case "Unknown": + case "Any": + case "String": + case "Boolean": + case "Number": + case "BigInt": + case "Symbol": + case "ObjectKeyword": + return { + _tag: ast._tag, + checks, + ...annotationsField(ast.annotations) + } + case "Literal": + return { + _tag: "Literal", + literal: ast.literal, + checks, + ...annotationsField(ast.annotations) + } + case "UniqueSymbol": + return { + _tag: "UniqueSymbol", + symbol: ast.symbol, + checks, + ...annotationsField(ast.annotations) + } + case "Enum": + return { + _tag: "Enum", + enums: ast.enums, + checks, + ...annotationsField(ast.annotations) + } + case "TemplateLiteral": + return { + _tag: "TemplateLiteral", + parts: ast.parts.map((ast) => recur(ast)), + checks, + ...annotationsField(ast.annotations) + } + case "Arrays": + return { + _tag: "Arrays", + elements: ast.elements.map((element) => { + const projected = SchemaAST.getLastEncoding(element) + const annotations = projected.context?.annotations + return { + isOptional: SchemaAST.isOptional(projected), + type: recur(element), + ...annotationsField(annotations) + } + }), + rest: ast.rest.map((ast) => recur(ast)), + checks, + ...annotationsField(ast.annotations) + } + case "Objects": + return { + _tag: "Objects", + propertySignatures: ast.propertySignatures.map((property) => { + const projected = SchemaAST.getLastEncoding(property.type) + const annotations = projected.context?.annotations + return { + name: property.name, + type: recur(property.type), + isOptional: SchemaAST.isOptional(projected), + isMutable: SchemaAST.isMutable(projected), + ...annotationsField(annotations) + } + }), + indexSignatures: ast.indexSignatures.map((index) => ({ + parameter: recur(index.parameter), + type: recur(index.type) + })), + checks, + ...annotationsField(ast.annotations) + } + case "Union": + return { + _tag: "Union", + types: ast.types.map((ast) => recur(ast)), + mode: ast.mode, + checks, + ...annotationsField(ast.annotations) + } + case "Suspend": + return { + _tag: "Suspend", + checks: [], + thunk: recur(ast.thunk()), + ...annotationsField(ast.annotations) + } + } + } + + function fromChecks( + checks: readonly [SchemaAST.Check, ...Array>] | undefined + ): Array { + return checks?.map(fromCheck) ?? [] + } + + function fromCheck( + check: SchemaAST.Check + ): SchemaRepresentation.Check { + switch (check._tag) { + case "Filter": + return { + _tag: "Filter", + aborted: check.aborted, + ...fromCheckAnnotations(check.annotations) + } + case "FilterGroup": + return { + _tag: "FilterGroup", + checks: Arr.map(check.checks, fromCheck), + ...fromCheckAnnotations(check.annotations) + } + } + } + + function fromDeclarationAnnotations< + A extends Schema.Annotations.Annotations & { + readonly representation?: SchemaRepresentation.RepresentationAnnotation | undefined + } + >(annotations: A | undefined): { + readonly representation?: SchemaRepresentation.RepresentationAnnotation | undefined + readonly annotations?: Omit | undefined + } | undefined { + if (annotations === undefined) return undefined + const { representation, ...ordinary } = annotations + return { + ...(representation === undefined ? undefined : { representation }), + ...(Object.keys(ordinary).length === 0 ? undefined : { annotations: ordinary }) + } + } + + function fromCheckAnnotations< + A extends Schema.Annotations.Annotations & { + readonly representation?: SchemaRepresentation.CheckRepresentationAnnotation | undefined + } + >(annotations: A | undefined): { + readonly representation?: CheckRepresentationAnnotation | undefined + readonly annotations?: Omit | undefined + } | undefined { + if (annotations === undefined) return undefined + const { representation, ...ordinary } = annotations + const projected = representation === undefined + ? undefined + : representation.schemas === undefined + ? representation as CheckRepresentationAnnotation + : { ...representation, schemas: representation.schemas.map((schema) => recur(SchemaAST.toType(schema))) } + return { + ...(projected === undefined ? undefined : { representation: projected }), + ...(Object.keys(ordinary).length === 0 ? undefined : { annotations: ordinary }) + } + } +} diff --git a/repos/effect/packages/effect/src/internal/stackTraceLimit.ts b/repos/effect/packages/effect/src/internal/stackTraceLimit.ts index 440e18044d..7a7e7df8df 100644 --- a/repos/effect/packages/effect/src/internal/stackTraceLimit.ts +++ b/repos/effect/packages/effect/src/internal/stackTraceLimit.ts @@ -16,10 +16,6 @@ */ import type { ErrorWithStackTraceLimit } from "./tracer.ts" -const ObjectGetOwnPropertyDescriptor = Object.getOwnPropertyDescriptor -const ObjectPrototypeHasOwnProperty = Object.prototype.hasOwnProperty -const ObjectIsExtensible = Object.isExtensible - /** * Check if `Error.stackTraceLimit` is writable. * Returns `false` if the property is frozen, non-writable, or `Error` is non-extensible. @@ -27,12 +23,12 @@ const ObjectIsExtensible = Object.isExtensible * @internal */ export const isStackTraceLimitWritable = (): boolean => { - const desc = ObjectGetOwnPropertyDescriptor(Error, "stackTraceLimit") + const desc = Object.getOwnPropertyDescriptor(Error, "stackTraceLimit") if (desc === undefined) { - return ObjectIsExtensible(Error) + return Object.isExtensible(Error) } - return ObjectPrototypeHasOwnProperty.call(desc, "writable") + return Object.hasOwn(desc, "writable") ? desc.writable === true : desc.set !== undefined } diff --git a/repos/effect/packages/effect/src/internal/trie.ts b/repos/effect/packages/effect/src/internal/trie.ts index b9e412dc40..03db686cba 100644 --- a/repos/effect/packages/effect/src/internal/trie.ts +++ b/repos/effect/packages/effect/src/internal/trie.ts @@ -42,7 +42,7 @@ const TrieProto: TR.Trie = { return hash }, [Equal.symbol](this: TrieImpl, that: unknown): boolean { - if (isTrie(that)) { + if (isTrie(that) && size(this) === size(that)) { const entries = Array.from(that) return Array.from(this).every((itemSelf, i) => { const itemThat = entries[i] @@ -103,8 +103,8 @@ class TrieIterator implements IterableIterator { const value = node.value if (value !== undefined) { const key = keyString + node.key - if (this.filter(key, value)) { - return { done: false, value: this.f(key, value) } + if (this.filter(key, value.value)) { + return { done: false, value: this.f(key, value.value) } } } } else { @@ -172,7 +172,7 @@ export const insert = dual< key: key[0], count: 0 } - const count = n.count + 1 + let count = n.count + 1 let cIndex = 0 while (cIndex < key.length) { @@ -194,7 +194,17 @@ export const insert = dual< } } else { if (cIndex === key.length - 1) { - n.value = value + if (n.value !== undefined) { + count -= 1 + } + nStack[nStack.length - 1] = { + key: n.key, + count, + value: { value }, + left: n.left, + mid: n.mid, + right: n.right + } } else if (n.mid === undefined) { dStack.push(0) n = { key: key[cIndex + 1], count } @@ -403,7 +413,7 @@ export const get = dual< } } else { if (cIndex === key.length - 1) { - return Option.fromUndefinedOr(n.value) + return n.value === undefined ? Option.none() : Option.some(n.value.value) } else { if (n.mid === undefined) { return Option.none() @@ -632,7 +642,7 @@ export const modify = dual< nStack[nStack.length - 1] = { key: updateNode.key, count: updateNode.count, - value: f(updateNode.value), // Update + value: { value: f(updateNode.value.value) }, // Update left: updateNode.left, mid: updateNode.mid, right: updateNode.right @@ -693,10 +703,6 @@ export const longestPrefixOf = dual< let cIndex = 0 while (cIndex < key.length) { const c = key[cIndex] - if (n.value !== undefined) { - longestPrefixNode = Option.some([key.slice(0, cIndex + 1), n.value]) - } - if (c > n.key) { if (n.right === undefined) { break @@ -710,6 +716,9 @@ export const longestPrefixOf = dual< n = n.left } } else { + if (n.value !== undefined) { + longestPrefixNode = Option.some([key.slice(0, cIndex + 1), n.value.value]) + } if (n.mid === undefined) { break } else { @@ -726,7 +735,7 @@ export const longestPrefixOf = dual< interface Node { key: string count: number - value?: V | undefined + value?: { readonly value: V } | undefined left?: Node | undefined mid?: Node | undefined right?: Node | undefined diff --git a/repos/effect/packages/effect/src/internal/version.ts b/repos/effect/packages/effect/src/internal/version.ts deleted file mode 100644 index 31a362ac68..0000000000 --- a/repos/effect/packages/effect/src/internal/version.ts +++ /dev/null @@ -1,2 +0,0 @@ -export const version: version = "dev" -export type version = "dev" diff --git a/repos/effect/packages/effect/src/testing/FastCheck.ts b/repos/effect/packages/effect/src/testing/FastCheck.ts index 1ce177c613..d8a7c22450 100644 --- a/repos/effect/packages/effect/src/testing/FastCheck.ts +++ b/repos/effect/packages/effect/src/testing/FastCheck.ts @@ -20,7 +20,7 @@ * * **Example** (Checking an array reversal property) * - * ```ts + * ```ts import.meta.vitest * import { FastCheck } from "effect/testing" * * // Property: reverse of reverse should equal original @@ -29,17 +29,18 @@ * (arr: Array) => { * const reversed = arr.slice().reverse() * const doubleReversed = reversed.slice().reverse() - * return JSON.stringify(arr) === JSON.stringify(doubleReversed) + * return arr.length === doubleReversed.length && + * arr.every((value, index) => value === doubleReversed[index]) * } * ) * * // Run the property test - * FastCheck.assert(reverseProp) + * FastCheck.assert(reverseProp, { seed: 1, numRuns: 100 }) // => undefined * ``` * * **Example** (Checking string concatenation properties) * - * ```ts + * ```ts import.meta.vitest * import { FastCheck } from "effect/testing" * * // Test string concatenation properties @@ -54,12 +55,12 @@ * } * ) * - * FastCheck.assert(concatProp) + * FastCheck.assert(concatProp, { seed: 2, numRuns: 100 }) // => undefined * ``` * * **Example** (Generating record data for properties) * - * ```ts + * ```ts import.meta.vitest * import { FastCheck } from "effect/testing" * * // Generate random data for testing @@ -80,7 +81,7 @@ * } * ) * - * FastCheck.assert(validPersonProp) + * FastCheck.assert(validPersonProp, { seed: 3, numRuns: 100 }) // => undefined * ``` * * @category re-exports diff --git a/repos/effect/packages/effect/src/testing/TestClock.ts b/repos/effect/packages/effect/src/testing/TestClock.ts index 0740d41b3f..0cff8aafa3 100644 --- a/repos/effect/packages/effect/src/testing/TestClock.ts +++ b/repos/effect/packages/effect/src/testing/TestClock.ts @@ -44,27 +44,28 @@ import * as Semaphore from "../Semaphore.ts" * * Tests `Effect.timeout` using `TestClock`. * - * ```ts - * import { Effect, Fiber, Option, pipe } from "effect" + * ```ts import.meta.vitest + * import { Effect, Exit, Fiber, pipe } from "effect" * import { TestClock } from "effect/testing" - * import * as assert from "node:assert" * - * Effect.gen(function*() { + * const program = Effect.gen(function*() { * const fiber = yield* pipe( * Effect.sleep("5 minutes"), * Effect.timeout("1 minute"), * Effect.forkChild * ) * yield* TestClock.adjust("1 minute") - * const result = yield* Fiber.join(fiber) - * assert.deepStrictEqual(result, Option.none()) + * const exit = yield* Fiber.await(fiber) + * Exit.isFailure(exit) // => true * }) + * + * await Effect.runPromise(Effect.provide(program, TestClock.layer())) * ``` * * **Example** (Advancing time deterministically) * - * ```ts - * import { Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect, Fiber } from "effect" * import { TestClock } from "effect/testing" * * const program = Effect.gen(function*() { @@ -78,10 +79,13 @@ import * as Semaphore from "../Semaphore.ts" * * // Advance the test clock by 1 hour * yield* TestClock.adjust("1 hour") + * yield* Fiber.join(fiber) * * // The effect should now be executed - * console.log(executed) // true + * executed // => true * }) + * + * await Effect.runPromise(Effect.provide(program, TestClock.layer())) * ``` * * @category models @@ -111,7 +115,7 @@ export interface TestClock extends Clock.Clock { * * **Example** (Configuring a test clock) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * import { TestClock } from "effect/testing" * @@ -123,8 +127,10 @@ export interface TestClock extends Clock.Clock { * * // Access the current state * const currentTime = testClock.currentTimeMillisUnsafe() - * console.log(currentTime) // 0 (starts at epoch) + * currentTime // => 0 * }) + * + * await Effect.runPromise(Effect.scoped(program)) * ``` * * @since 2.0.0 @@ -137,7 +143,7 @@ export declare namespace TestClock { * * **Example** (Configuring the warning delay) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * import { TestClock } from "effect/testing" * @@ -149,7 +155,10 @@ export declare namespace TestClock { * * // Use the TestClock in your test * yield* testClock.adjust("1 hour") + * testClock.currentTimeMillisUnsafe() // => 3_600_000 * }) + * + * await Effect.runPromise(Effect.scoped(program)) * ``` * * @category options @@ -194,12 +203,20 @@ const SleepOrder = Order.flip(Order.Struct({ sequence: Order.Number })) +const nanosPerMilli = BigInt(1_000_000) + +const millisToNanos = (millis: number): bigint => { + const wholeMillis = Math.floor(millis) + const fractionalNanos = Math.floor((millis - wholeMillis) * 1_000_000) + return BigInt(wholeMillis) * nanosPerMilli + BigInt(fractionalNanos) +} + /** * Creates a `TestClock` with optional configuration. * * **Example** (Creating a test clock) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * import { TestClock } from "effect/testing" * @@ -215,8 +232,10 @@ const SleepOrder = Order.flip(Order.Struct({ * // Use the TestClock to control time in tests * yield* testClock.adjust("1 hour") * const currentTime = testClock.currentTimeMillisUnsafe() - * console.log(currentTime) // Time advanced by 1 hour + * currentTime // => 3_600_000 * }) + * + * await Effect.runPromise(Effect.scoped(program)) * ``` * * @category constructors @@ -225,7 +244,7 @@ const SleepOrder = Order.flip(Order.Struct({ export const make = Effect.fnUntraced(function*( options?: TestClock.Options ) { - const config = Object.assign({}, defaultOptions, options) + const config = { ...defaultOptions, ...options } let sequence = 0 const sleeps: Array<{ readonly sequence: number @@ -236,6 +255,8 @@ export const make = Effect.fnUntraced(function*( const warningSemaphore = yield* Semaphore.make(1) let currentTimestamp: number = new Date(0).getTime() + let currentWallNanos = BigInt(0) + let currentMonotonicNanos = BigInt(0) let warningState: WarningState = WarningState.Start() function currentTimeMillisUnsafe(): number { @@ -243,11 +264,16 @@ export const make = Effect.fnUntraced(function*( } function currentTimeNanosUnsafe(): bigint { - return BigInt(Math.floor(currentTimestamp * 1000000)) + return currentWallNanos + } + + function monotonicTimeNanosUnsafe(): bigint { + return currentMonotonicNanos } const currentTimeMillis = Effect.sync(currentTimeMillisUnsafe) const currentTimeNanos = Effect.sync(currentTimeNanosUnsafe) + const monotonicTimeNanos = Effect.sync(monotonicTimeNanosUnsafe) function withLive(effect: Effect.Effect) { return Effect.provideService(effect, Clock.Clock, liveClock) @@ -317,14 +343,24 @@ export const make = Effect.fnUntraced(function*( const run = Effect.fnUntraced(function*(step: (currentTimestamp: number) => number) { yield* Fiber.await(yield* Effect.forkChild(Effect.yieldNow)) const endTimestamp = step(currentTimestamp) + const advanceTo = (timestamp: number) => { + const deltaMillis = timestamp - currentTimestamp + if (deltaMillis > 0 && Number.isFinite(deltaMillis)) { + currentMonotonicNanos += BigInt(Math.round(deltaMillis * 1_000_000)) + } + if (Number.isFinite(timestamp)) { + currentWallNanos = millisToNanos(timestamp) + } + currentTimestamp = timestamp + } while (Arr.isArrayNonEmpty(sleeps)) { if (Arr.lastNonEmpty(sleeps).timestamp > endTimestamp) break const entry = sleeps.pop()! - currentTimestamp = entry.timestamp + advanceTo(entry.timestamp) entry.latch.openUnsafe() yield* Effect.yieldNow } - currentTimestamp = endTimestamp + advanceTo(endTimestamp) }, runSemaphore.withPermits(1)) function adjust(duration: Duration.Input) { @@ -341,8 +377,10 @@ export const make = Effect.fnUntraced(function*( return { currentTimeMillisUnsafe, currentTimeNanosUnsafe, + monotonicTimeNanosUnsafe, currentTimeMillis, currentTimeNanos, + monotonicTimeNanos, adjust, setTime, sleep, @@ -355,7 +393,7 @@ export const make = Effect.fnUntraced(function*( * * **Example** (Providing a test clock layer) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * import { TestClock } from "effect/testing" * @@ -370,7 +408,13 @@ export const make = Effect.fnUntraced(function*( * const program = Effect.gen(function*() { * // Use the layer in your program * yield* TestClock.adjust("1 hour") - * }).pipe(Effect.provide(testClockLayer)) + * return yield* TestClock.testClockWith((testClock) => + * Effect.succeed(testClock.currentTimeMillisUnsafe()) + * ) + * }) + * + * await Effect.runPromise(Effect.provide(program, testClockLayer)) // => 3_600_000 + * await Effect.runPromise(Effect.provide(program, customTestClockLayer)) // => 3_600_000 * ``` * * @category layers @@ -387,7 +431,7 @@ export const layer: (options?: TestClock.Options) => Layer.Layer = fl * * **Example** (Accessing the test clock) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * import { TestClock } from "effect/testing" * @@ -400,8 +444,10 @@ export const layer: (options?: TestClock.Options) => Layer.Layer = fl * // Adjust time using the TestClock instance * yield* TestClock.testClockWith((testClock) => testClock.adjust("2 hours")) * - * console.log(currentTime) // Initial time + * currentTime // => 0 * }) + * + * await Effect.runPromise(Effect.provide(program, TestClock.layer())) * ``` * * @category testing @@ -418,8 +464,8 @@ export const testClockWith = ( * * **Example** (Advancing the test clock) * - * ```ts - * import { Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect, Fiber } from "effect" * import { TestClock } from "effect/testing" * * const program = Effect.gen(function*() { @@ -433,10 +479,13 @@ export const testClockWith = ( * * // Advance the clock by 30 minutes * yield* TestClock.adjust("30 minutes") + * yield* Fiber.join(fiber) * * // The effect should now be executed - * console.log(executed) // true + * executed // => true * }) + * + * await Effect.runPromise(Effect.provide(program, TestClock.layer())) * ``` * * @category testing @@ -451,8 +500,8 @@ export const adjust = (duration: Duration.Input): Effect.Effect => * * **Example** (Setting the test clock time) * - * ```ts - * import { Duration, Effect } from "effect" + * ```ts import.meta.vitest + * import { Duration, Effect, Fiber } from "effect" * import { TestClock } from "effect/testing" * * const program = Effect.gen(function*() { @@ -467,10 +516,13 @@ export const adjust = (duration: Duration.Input): Effect.Effect => * // Set the clock to a specific timestamp (2 hours from epoch) * const targetTime = Duration.toMillis(Duration.hours(2)) * yield* TestClock.setTime(targetTime) + * yield* Fiber.join(fiber) * * // The effect should now be executed - * console.log(executed) // true + * executed // => true * }) + * + * await Effect.runPromise(Effect.provide(program, TestClock.layer())) * ``` * * @category testing @@ -485,26 +537,28 @@ export const setTime = (timestamp: number): Effect.Effect => * * **Example** (Running with the live clock) * - * ```ts + * ```ts import.meta.vitest * import { Clock, Effect } from "effect" * import { TestClock } from "effect/testing" * * const program = Effect.gen(function*() { * // Get the current test time (starts at epoch) * const testTime = yield* Clock.currentTimeMillis - * console.log(testTime) // 0 + * testTime // => 0 * * // Get the actual system time using withLive * const realTime = yield* TestClock.withLive(Clock.currentTimeMillis) - * console.log(realTime) // Actual system timestamp + * Number.isFinite(realTime) // => true * * // Advance test time * yield* TestClock.adjust("1 hour") * * // Test time is now 1 hour ahead * const newTestTime = yield* Clock.currentTimeMillis - * console.log(newTestTime) // 3600000 (1 hour in milliseconds) + * newTestTime // => 3_600_000 * }) + * + * await Effect.runPromise(Effect.provide(program, TestClock.layer())) * ``` * * @category testing diff --git a/repos/effect/packages/effect/src/testing/TestConsole.ts b/repos/effect/packages/effect/src/testing/TestConsole.ts index aa85d3e50b..b8a5a52f63 100644 --- a/repos/effect/packages/effect/src/testing/TestConsole.ts +++ b/repos/effect/packages/effect/src/testing/TestConsole.ts @@ -31,7 +31,7 @@ import * as Layer from "../Layer.ts" * * **Example** (Capturing console output in tests) * - * ```ts + * ```ts import.meta.vitest * import { Console, Effect } from "effect" * import { TestConsole } from "effect/testing" * @@ -42,9 +42,11 @@ import * as Layer from "../Layer.ts" * const logs = yield* TestConsole.logLines * const errors = yield* TestConsole.errorLines * - * console.log(logs) // [["Hello, World!"]] - * console.log(errors) // [["An error occurred"]] + * logs // => ["Hello, World!"] + * errors // => ["An error occurred"] * }).pipe(Effect.provide(TestConsole.layer)) + * + * await Effect.runPromise(program) * ``` * * @see {@link layer} for providing `TestConsole` to an effect @@ -99,12 +101,10 @@ export declare namespace TestConsole { * * **Example** (Typing captured console methods) * - * ```ts + * ```ts import.meta.vitest * import type { TestConsole } from "effect/testing" * * const method: TestConsole.TestConsole.Method = "log" - * - * console.log(method) // "log" * ``` * * @category models @@ -122,7 +122,7 @@ export declare namespace TestConsole { * * **Example** (Typing captured console entries) * - * ```ts + * ```ts import.meta.vitest * import type { TestConsole } from "effect/testing" * * const entry: TestConsole.TestConsole.Entry = { @@ -130,8 +130,7 @@ export declare namespace TestConsole { * parameters: ["not found"] * } * - * console.log(entry.method) // "error" - * console.log(entry.parameters) // ["not found"] + * entry // => { method: "error", parameters: ["not found"] } * ``` * * @category models @@ -154,7 +153,7 @@ export declare namespace TestConsole { * * **Example** (Creating a test console) * - * ```ts + * ```ts import.meta.vitest * import { Console, Effect } from "effect" * import { TestConsole } from "effect/testing" * @@ -165,9 +164,11 @@ export declare namespace TestConsole { * const logs = yield* TestConsole.logLines * const errors = yield* TestConsole.errorLines * - * console.log("Captured logs:", logs) - * console.log("Captured errors:", errors) + * logs // => ["Debug message"] + * errors // => ["Error occurred"] * }).pipe(Effect.provide(TestConsole.layer)) + * + * await Effect.runPromise(program) * ``` * * @see {@link layer} for providing a `TestConsole` as a `Layer` @@ -225,7 +226,7 @@ export const make = Effect.gen(function*() { * * **Example** (Accessing the test console service) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * import { TestConsole } from "effect/testing" * @@ -237,10 +238,12 @@ export const make = Effect.gen(function*() { * const logs = yield* testConsole.logLines * const errors = yield* testConsole.errorLines * - * console.log("Logs:", logs) // [["Test message"]] - * console.log("Errors:", errors) // [["Test error"]] + * logs // => ["Test message"] + * errors // => ["Test error"] * }) * ).pipe(Effect.provide(TestConsole.layer)) + * + * await Effect.runPromise(program) * ``` * * @see {@link layer} for providing the test console service @@ -264,7 +267,7 @@ export const testConsoleWith = (f: (console: TestConsole) => Effect.Eff * * **Example** (Providing a test console layer) * - * ```ts + * ```ts import.meta.vitest * import { Console, Effect } from "effect" * import { TestConsole } from "effect/testing" * @@ -275,9 +278,11 @@ export const testConsoleWith = (f: (console: TestConsole) => Effect.Eff * const logs = yield* TestConsole.logLines * const errors = yield* TestConsole.errorLines * - * console.log("Captured logs:", logs) - * console.log("Captured errors:", errors) + * logs // => ["This will be captured"] + * errors // => ["This error will be captured"] * }).pipe(Effect.provide(TestConsole.layer)) + * + * await Effect.runPromise(program) * ``` * * @see {@link make} for constructing the service value directly @@ -299,7 +304,7 @@ export const layer: Layer.Layer = Layer.effect(Console.Console)(mak * * **Example** (Reading captured log lines) * - * ```ts + * ```ts import.meta.vitest * import { Console, Effect } from "effect" * import { TestConsole } from "effect/testing" * @@ -310,13 +315,10 @@ export const layer: Layer.Layer = Layer.effect(Console.Console)(mak * * const logs = yield* TestConsole.logLines * - * console.log(logs) - * // [ - * // ["First message"], - * // ["Second message", { key: "value" }], - * // ["Third message", 42, true] - * // ] + * logs // => ["First message", "Second message", { key: "value" }, "Third message", 42, true] * }).pipe(Effect.provide(TestConsole.layer)) + * + * await Effect.runPromise(program) * ``` * * @see {@link errorLines} for reading captured `Console.error` output @@ -340,7 +342,7 @@ export const logLines: Effect.Effect, never, never> = tes * * **Example** (Reading captured error lines) * - * ```ts + * ```ts import.meta.vitest * import { Console, Effect } from "effect" * import { TestConsole } from "effect/testing" * @@ -350,12 +352,11 @@ export const logLines: Effect.Effect, never, never> = tes * * const errors = yield* TestConsole.errorLines * - * console.log(errors) - * // [ - * // ["Error message"], - * // ["Another error", Error: Something went wrong] - * // ] + * const messages = [errors[0], errors[1], errors[2] instanceof Error ? errors[2].message : undefined] + * messages // => ["Error message", "Another error", "Something went wrong"] * }).pipe(Effect.provide(TestConsole.layer)) + * + * await Effect.runPromise(program) * ``` * * @see {@link logLines} for reading captured `Console.log` output diff --git a/repos/effect/packages/effect/src/testing/TestSchema.ts b/repos/effect/packages/effect/src/testing/TestSchema.ts index 95405b0e79..88e55f97ff 100644 --- a/repos/effect/packages/effect/src/testing/TestSchema.ts +++ b/repos/effect/packages/effect/src/testing/TestSchema.ts @@ -17,7 +17,7 @@ import * as Record from "../Record.ts" import * as Result from "../Result.ts" import * as Schema from "../Schema.ts" import * as SchemaAST from "../SchemaAST.ts" -import type * as SchemaIssue from "../SchemaIssue.ts" +import * as SchemaIssue from "../SchemaIssue.ts" import * as SchemaParser from "../SchemaParser.ts" import * as FastCheck from "../testing/FastCheck.ts" @@ -30,7 +30,7 @@ import * as FastCheck from "../testing/FastCheck.ts" * * **Example** (Decoding and encoding a struct) * - * ```ts + * ```ts import.meta.vitest * import { Schema } from "effect" * import { TestSchema } from "effect/testing" * @@ -38,10 +38,10 @@ import * as FastCheck from "../testing/FastCheck.ts" * const asserts = new TestSchema.Asserts(schema) * * // decoding - * await asserts.decoding().succeed({ name: "Alice" }) + * await asserts.decoding().succeed({ name: "Alice" }) // => undefined * * // encoding - * await asserts.encoding().succeed({ name: "Alice" }) + * await asserts.encoding().succeed({ name: "Alice" }) // => undefined * ``` * * @see {@link Decoding} @@ -64,13 +64,13 @@ export class Asserts { * * **Example** (Comparing struct fields) * - * ```ts + * ```ts import.meta.vitest * import { Schema } from "effect" * import { TestSchema } from "effect/testing" * * const fieldsA = { name: Schema.String } * const fieldsB = { name: Schema.String } - * TestSchema.Asserts.ast.fields.equals(fieldsA, fieldsB) // no error + * TestSchema.Asserts.ast.fields.equals(fieldsA, fieldsB) // => undefined * ``` */ static ast = { @@ -104,13 +104,13 @@ export class Asserts { * * **Example** (Testing make) * - * ```ts + * ```ts import.meta.vitest * import { Schema } from "effect" * import { TestSchema } from "effect/testing" * * const schema = Schema.String * const asserts = new TestSchema.Asserts(schema) - * await asserts.make().succeed("hello") + * await asserts.make().succeed("hello") // => undefined * ``` * * @see {@link decoding} for assertions against decoded input @@ -123,7 +123,7 @@ export class Asserts { async function succeed(input: S["~type.make.in"], expected?: S["Type"]) { const r = await Effect.runPromise( makeEffect(input, options).pipe( - Effect.mapErrorEager((issue) => issue.toString()), + Effect.mapErrorEager(SchemaIssue.defaultFormatter), Effect.result ) ) @@ -135,7 +135,7 @@ export class Asserts { async fail(input: unknown, message: string) { const r = await Effect.runPromise( makeEffect(input, options).pipe( - Effect.mapErrorEager((issue) => issue.toString()), + Effect.mapErrorEager(SchemaIssue.defaultFormatter), Effect.result ) ) @@ -157,28 +157,28 @@ export class Asserts { * * **Example** (Verifying round trips) * - * ```ts + * ```ts import.meta.vitest * import { Schema } from "effect" * import { TestSchema } from "effect/testing" * - * const asserts = new TestSchema.Asserts(Schema.NumberFromString) - * await asserts.verifyLosslessTransformation() + * const asserts = new TestSchema.Asserts(Schema.String) + * await asserts.verifyLosslessTransformation({ params: { seed: 1, numRuns: 20 } }) // => undefined * ``` * * @see {@link arbitrary} for checking that generated values satisfy the schema */ - verifyLosslessTransformation>(this: Asserts, options?: { + verifyLosslessTransformation>(this: Asserts, options?: { readonly params?: FastCheck.Parameters<[S["Type"]]> }) { const decodeUnknownEffect = SchemaParser.decodeUnknownEffect(this.schema) const encodeEffect = SchemaParser.encodeEffect(this.schema) - const arbitrary = Schema.toArbitrary(this.schema) + const arbitrary = Schema.toArbitrary(this.schema)(FastCheck) return FastCheck.assert( FastCheck.asyncProperty(arbitrary, async (t) => { const r = await Effect.runPromise( encodeEffect(t).pipe( Effect.flatMapEager((e) => decodeUnknownEffect(e)), - Effect.mapErrorEager((issue) => issue.toString()), + Effect.mapErrorEager(SchemaIssue.defaultFormatter), Effect.result ) ) @@ -200,14 +200,14 @@ export class Asserts { * * **Example** (Decoding assertions) * - * ```ts + * ```ts import.meta.vitest * import { Schema } from "effect" * import { TestSchema } from "effect/testing" * * const asserts = new TestSchema.Asserts(Schema.NumberFromString) * const decoding = asserts.decoding() - * await decoding.succeed("42", 42) - * await decoding.fail(null, "Expected string, got null") + * await decoding.succeed("42", 42) // => undefined + * await decoding.fail(null, "Expected string") // => undefined * ``` * * @see {@link Decoding} @@ -231,13 +231,13 @@ export class Asserts { * * **Example** (Encoding assertions) * - * ```ts + * ```ts import.meta.vitest * import { Schema } from "effect" * import { TestSchema } from "effect/testing" * * const asserts = new TestSchema.Asserts(Schema.NumberFromString) * const encoding = asserts.encoding() - * await encoding.succeed(42, "42") + * await encoding.succeed(42, "42") // => undefined * ``` * * @see {@link Encoding} @@ -262,17 +262,17 @@ export class Asserts { * * **Example** (Verifying arbitrary generation) * - * ```ts + * ```ts import.meta.vitest * import { Schema } from "effect" * import { TestSchema } from "effect/testing" * * const asserts = new TestSchema.Asserts(Schema.String) - * asserts.arbitrary().verifyGeneration() + * asserts.arbitrary().verifyGeneration({ params: { seed: 1, numRuns: 20 } }) // => undefined * ``` * * @see {@link verifyLosslessTransformation} for property-based round-trip checks */ - arbitrary>(this: Asserts) { + arbitrary>(this: Asserts) { const schema = this.schema return { verifyGeneration(options?: { @@ -280,7 +280,7 @@ export class Asserts { }) { const params = options?.params const is = Schema.is(schema) - const arb = Schema.toArbitrary(schema) + const arb = Schema.toArbitrary(schema)(FastCheck) FastCheck.assert(FastCheck.property(arb, (a) => is(a)), { numRuns: 20, ...params }) } } @@ -300,13 +300,13 @@ export class Asserts { * * **Example** (Decoding with service provision) * - * ```ts + * ```ts import.meta.vitest * import { Schema } from "effect" * import { TestSchema } from "effect/testing" * * const asserts = new TestSchema.Asserts(Schema.String) * const decoding = asserts.decoding() - * await decoding.succeed("hello") + * await decoding.succeed("hello") // => undefined * ``` * * @see {@link Asserts} @@ -341,12 +341,12 @@ export class Decoding { * * **Example** (Testing identity and transformed decoding) * - * ```ts + * ```ts import.meta.vitest * import { Schema } from "effect" * import { TestSchema } from "effect/testing" * * const decoding = new TestSchema.Asserts(Schema.NumberFromString).decoding() - * await decoding.succeed("1", 1) // transformed + * await decoding.succeed("1", 1) // => undefined * ``` * * @see {@link fail} for asserting decoding failures @@ -367,7 +367,7 @@ export class Decoding { ) { const r = await Effect.runPromise( this.decodeUnknownEffect(input, this.options?.parseOptions).pipe( - Effect.mapErrorEager((issue) => issue.toString()), + Effect.mapErrorEager(SchemaIssue.defaultFormatter), Effect.result ) ) @@ -384,12 +384,12 @@ export class Decoding { * * **Example** (Asserting a decoding failure) * - * ```ts + * ```ts import.meta.vitest * import { Schema } from "effect" * import { TestSchema } from "effect/testing" * * const decoding = new TestSchema.Asserts(Schema.String).decoding() - * await decoding.fail(42, "Expected string, got 42") + * await decoding.fail(42, "Expected string") // => undefined * ``` * * @see {@link succeed} for asserting successful decoding @@ -401,7 +401,7 @@ export class Decoding { ) { const r = await Effect.runPromise( this.decodeUnknownEffect(input, this.options?.parseOptions).pipe( - Effect.mapErrorEager((issue) => issue.toString()), + Effect.mapErrorEager(SchemaIssue.defaultFormatter), Effect.result ) ) @@ -440,12 +440,12 @@ export class Decoding { * * **Example** (Encoding assertions) * - * ```ts + * ```ts import.meta.vitest * import { Schema } from "effect" * import { TestSchema } from "effect/testing" * * const encoding = new TestSchema.Asserts(Schema.NumberFromString).encoding() - * await encoding.succeed(42, "42") + * await encoding.succeed(42, "42") // => undefined * ``` * * @see {@link Asserts} @@ -459,7 +459,7 @@ export class Encoding { readonly encodeUnknownEffect: ( input: unknown, options?: SchemaAST.ParseOptions - ) => Effect.Effect + ) => Effect.Effect readonly options?: { readonly parseOptions?: SchemaAST.ParseOptions | undefined } | undefined @@ -481,12 +481,12 @@ export class Encoding { * * **Example** (Testing identity and transformed encoding) * - * ```ts + * ```ts import.meta.vitest * import { Schema } from "effect" * import { TestSchema } from "effect/testing" * * const encoding = new TestSchema.Asserts(Schema.NumberFromString).encoding() - * await encoding.succeed(1, "1") // transformed + * await encoding.succeed(1, "1") // => undefined * ``` * * @see {@link fail} for asserting encoding failures @@ -507,7 +507,7 @@ export class Encoding { ) { const r = await Effect.runPromise( this.encodeUnknownEffect(input, this.options?.parseOptions).pipe( - Effect.mapErrorEager((issue) => issue.toString()), + Effect.mapErrorEager(SchemaIssue.defaultFormatter), Effect.result ) ) @@ -524,12 +524,12 @@ export class Encoding { * * **Example** (Asserting an encoding failure) * - * ```ts + * ```ts import.meta.vitest * import { Schema } from "effect" * import { TestSchema } from "effect/testing" * * const encoding = new TestSchema.Asserts(Schema.NumberFromString).encoding() - * await encoding.fail("not-a-number", "Expected number, got \"not-a-number\"") + * await encoding.fail("not-a-number", "Expected number") // => undefined * ``` * * @see {@link succeed} for asserting successful encoding @@ -541,7 +541,7 @@ export class Encoding { ) { const r = await Effect.runPromise( this.encodeUnknownEffect(input, this.options?.parseOptions).pipe( - Effect.mapErrorEager((issue) => issue.toString()), + Effect.mapErrorEager(SchemaIssue.defaultFormatter), Effect.result ) ) diff --git a/repos/effect/packages/effect/src/unstable/ai/AiError.ts b/repos/effect/packages/effect/src/unstable/ai/AiError.ts index b5a7005ad9..bac73a0df1 100644 --- a/repos/effect/packages/effect/src/unstable/ai/AiError.ts +++ b/repos/effect/packages/effect/src/unstable/ai/AiError.ts @@ -13,6 +13,7 @@ */ import * as Duration from "../../Duration.ts" import * as Effect from "../../Effect.ts" +import * as InternalRecord from "../../internal/record.ts" import * as Option from "../../Option.ts" import * as Predicate from "../../Predicate.ts" import { redact } from "../../Redactable.ts" @@ -34,7 +35,7 @@ const redactHeaders = (headers: Record): Record const result: Record = {} for (const key in redacted) { const value = redacted[key] - result[key] = Redacted.isRedacted(value) ? value.toString() : value + InternalRecord.assignProperty(result, key, Redacted.isRedacted(value) ? value.toString() : value) } return result } @@ -54,7 +55,7 @@ const redactHeaders = (headers: Record): Record * * **Example** (Creating a network error) * - * ```ts + * ```ts import.meta.vitest * import { AiError } from "effect/unstable/ai" * * const error = new AiError.NetworkError({ @@ -69,15 +70,13 @@ const redactHeaders = (headers: Record): Record * description: "Connection timeout after 30 seconds" * }) * - * console.log(error.isRetryable) // true - * console.log(error.message) - * // "Transport: Connection timeout after 30 seconds (POST https://api.openai.com/v1/completions)" + * const result = [error.reason, error.isRetryable] // => ["TransportError", true] * ``` * - * @category reason + * @category errors * @since 4.0.0 */ -export class NetworkError extends Schema.ErrorClass( +export class NetworkError extends Schema.Error( "effect/ai/AiError/NetworkError" )({ _tag: Schema.tag("NetworkError"), @@ -106,13 +105,17 @@ export class NetworkError extends Schema.ErrorClass( * * **Example** (Creating a network error from a request error) * - * ```ts + * ```ts import.meta.vitest * import { AiError } from "effect/unstable/ai" - * import type { HttpClientError } from "effect/unstable/http" + * import { HttpClientError, HttpClientRequest } from "effect/unstable/http" * - * declare const platformError: HttpClientError.RequestError + * const platformError = new HttpClientError.TransportError({ + * request: HttpClientRequest.get("https://example.com/models"), + * description: "Connection refused" + * }) * * const aiError = AiError.NetworkError.fromRequestError(platformError) + * aiError.reason // => "TransportError" * ``` * * @since 4.0.0 @@ -183,7 +186,7 @@ export class NetworkError extends Schema.ErrorClass( * * **Example** (Inspecting metadata shape) * - * ```ts + * ```ts import.meta.vitest * const metadata = { * openai: { * errorCode: "rate_limit_exceeded", @@ -191,6 +194,8 @@ export class NetworkError extends Schema.ErrorClass( * }, * anthropic: null * } + * + * Array.of(metadata.openai.errorCode, metadata.anthropic) // => ["rate_limit_exceeded", null] * ``` * * @category schemas @@ -306,9 +311,9 @@ export interface UnknownErrorMetadata extends ProviderMetadata {} * @since 4.0.0 */ export const UsageInfo = Schema.Struct({ - promptTokens: Schema.optional(Schema.Number), - completionTokens: Schema.optional(Schema.Number), - totalTokens: Schema.optional(Schema.Number) + promptTokens: Schema.optional(Schema.Int), + completionTokens: Schema.optional(Schema.Int), + totalTokens: Schema.optional(Schema.Int) }).annotate({ identifier: "UsageInfo" }) /** @@ -350,7 +355,7 @@ export const HttpContext = Schema.Struct({ * * **Example** (Creating a rate limit error) * - * ```ts + * ```ts import.meta.vitest * import { Duration } from "effect" * import { AiError } from "effect/unstable/ai" * @@ -358,14 +363,13 @@ export const HttpContext = Schema.Struct({ * retryAfter: Duration.seconds(60) * }) * - * console.log(rateLimitError.isRetryable) // true - * console.log(rateLimitError.message) // "Rate limit exceeded. Retry after 1 minute" + * const result = [rateLimitError._tag, rateLimitError.isRetryable] // => ["RateLimitError", true] * ``` * - * @category reason + * @category errors * @since 4.0.0 */ -export class RateLimitError extends Schema.ErrorClass( +export class RateLimitError extends Schema.Error( "effect/ai/AiError/RateLimitError" )({ _tag: Schema.tag("RateLimitError"), @@ -405,20 +409,18 @@ export class RateLimitError extends Schema.ErrorClass( * * **Example** (Creating a quota exhausted error) * - * ```ts + * ```ts import.meta.vitest * import { AiError } from "effect/unstable/ai" * * const quotaError = new AiError.QuotaExhaustedError({}) * - * console.log(quotaError.isRetryable) // false - * console.log(quotaError.message) - * // "Quota exhausted. Check your account billing and usage limits." + * const result = [quotaError._tag, quotaError.isRetryable] // => ["QuotaExhaustedError", false] * ``` * - * @category reason + * @category errors * @since 4.0.0 */ -export class QuotaExhaustedError extends Schema.ErrorClass( +export class QuotaExhaustedError extends Schema.Error( "effect/ai/AiError/QuotaExhaustedError" )({ _tag: Schema.tag("QuotaExhaustedError"), @@ -458,22 +460,20 @@ export class QuotaExhaustedError extends Schema.ErrorClass( * * **Example** (Creating an authentication error) * - * ```ts + * ```ts import.meta.vitest * import { AiError } from "effect/unstable/ai" * * const authError = new AiError.AuthenticationError({ * kind: "InvalidKey" * }) * - * console.log(authError.isRetryable) // false - * console.log(authError.message) - * // "InvalidKey: Verify your API key is correct" + * const result = [authError.kind, authError.isRetryable] // => ["InvalidKey", false] * ``` * - * @category reason + * @category errors * @since 4.0.0 */ -export class AuthenticationError extends Schema.ErrorClass( +export class AuthenticationError extends Schema.Error( "effect/ai/AiError/AuthenticationError" )({ _tag: Schema.tag("AuthenticationError"), @@ -518,22 +518,20 @@ export class AuthenticationError extends Schema.ErrorClass( * * **Example** (Creating a content policy error) * - * ```ts + * ```ts import.meta.vitest * import { AiError } from "effect/unstable/ai" * * const policyError = new AiError.ContentPolicyError({ * description: "Input contains prohibited content" * }) * - * console.log(policyError.isRetryable) // false - * console.log(policyError.message) - * // "Content policy violation: Input contains prohibited content" + * const result = [policyError.description, policyError.isRetryable] // => ["Input contains prohibited content", false] * ``` * - * @category reason + * @category errors * @since 4.0.0 */ -export class ContentPolicyError extends Schema.ErrorClass( +export class ContentPolicyError extends Schema.Error( "effect/ai/AiError/ContentPolicyError" )({ _tag: Schema.tag("ContentPolicyError"), @@ -571,7 +569,7 @@ export class ContentPolicyError extends Schema.ErrorClass( * * **Example** (Creating an invalid request error) * - * ```ts + * ```ts import.meta.vitest * import { AiError } from "effect/unstable/ai" * * const invalidRequestError = new AiError.InvalidRequestError({ @@ -580,15 +578,13 @@ export class ContentPolicyError extends Schema.ErrorClass( * description: "Temperature value 5 is out of range" * }) * - * console.log(invalidRequestError.isRetryable) // false - * console.log(invalidRequestError.message) - * // "Invalid request: parameter 'temperature' must be between 0 and 2. Temperature value 5 is out of range" + * const result = [invalidRequestError.parameter, invalidRequestError.isRetryable] // => ["temperature", false] * ``` * - * @category reason + * @category errors * @since 4.0.0 */ -export class InvalidRequestError extends Schema.ErrorClass( +export class InvalidRequestError extends Schema.Error( "effect/ai/AiError/InvalidRequestError" )({ _tag: Schema.tag("InvalidRequestError"), @@ -632,22 +628,20 @@ export class InvalidRequestError extends Schema.ErrorClass( * * **Example** (Creating an internal provider error) * - * ```ts + * ```ts import.meta.vitest * import { AiError } from "effect/unstable/ai" * * const providerError = new AiError.InternalProviderError({ * description: "Server encountered an unexpected error" * }) * - * console.log(providerError.isRetryable) // true - * console.log(providerError.message) - * // "Internal provider error: Server encountered an unexpected error" + * const result = [providerError.description, providerError.isRetryable] // => ["Server encountered an unexpected error", true] * ``` * - * @category reason + * @category errors * @since 4.0.0 */ -export class InternalProviderError extends Schema.ErrorClass( +export class InternalProviderError extends Schema.Error( "effect/ai/AiError/InternalProviderError" )({ _tag: Schema.tag("InternalProviderError"), @@ -685,22 +679,20 @@ export class InternalProviderError extends Schema.ErrorClass ["Expected a string but received a number", true] * ``` * - * @category reason + * @category errors * @since 4.0.0 */ -export class InvalidOutputError extends Schema.ErrorClass( +export class InvalidOutputError extends Schema.Error( "effect/ai/AiError/InvalidOutputError" )({ _tag: Schema.tag("InvalidOutputError"), @@ -729,13 +721,15 @@ export class InvalidOutputError extends Schema.ErrorClass( * * **Example** (Creating an invalid output error from a schema error) * - * ```ts - * import { Schema } from "effect" + * ```ts import.meta.vitest + * import { Effect, Schema } from "effect" * import { AiError } from "effect/unstable/ai" * - * declare const schemaError: Schema.SchemaError - * + * const schemaError = await Effect.runPromise( + * Schema.decodeUnknownEffect(Schema.Number)("not a number").pipe(Effect.flip) + * ) * const parseError = AiError.InvalidOutputError.fromSchemaError(schemaError) + * parseError.description // => "Expected number" * ``` * * @since 4.0.0 @@ -761,7 +755,7 @@ export class InvalidOutputError extends Schema.ErrorClass( * * **Example** (Creating a structured output error) * - * ```ts + * ```ts import.meta.vitest * import { AiError } from "effect/unstable/ai" * * const error = new AiError.StructuredOutputError({ @@ -769,15 +763,13 @@ export class InvalidOutputError extends Schema.ErrorClass( * responseText: "{\"foo\":}" * }) * - * console.log(error.isRetryable) // true - * console.log(error.message) - * // "Structured output validation failed: Expected a valid JSON object" + * const result = [error.description, error.responseText, error.isRetryable] // => ["Expected a valid JSON object", '{"foo":}', true] * ``` * - * @category reason + * @category errors * @since 4.0.0 */ -export class StructuredOutputError extends Schema.ErrorClass( +export class StructuredOutputError extends Schema.Error( "effect/ai/AiError/StructuredOutputError" )({ _tag: Schema.tag("StructuredOutputError"), @@ -807,14 +799,15 @@ export class StructuredOutputError extends Schema.ErrorClass "{}" * ``` * * @since 4.0.0 @@ -842,22 +835,20 @@ export class StructuredOutputError extends Schema.ErrorClass ["Unions are not supported in Anthropic structured output", false] * ``` * - * @category reason + * @category errors * @since 4.0.0 */ -export class UnsupportedSchemaError extends Schema.ErrorClass( +export class UnsupportedSchemaError extends Schema.Error( "effect/ai/AiError/UnsupportedSchemaError" )({ _tag: Schema.tag("UnsupportedSchemaError"), @@ -894,22 +885,20 @@ export class UnsupportedSchemaError extends Schema.ErrorClass ["An unexpected error occurred", false] * ``` * - * @category reason + * @category errors * @since 4.0.0 */ -export class UnknownError extends Schema.ErrorClass( +export class UnknownError extends Schema.Error( "effect/ai/AiError/UnknownError" )({ _tag: Schema.tag("UnknownError"), @@ -952,7 +941,7 @@ export class UnknownError extends Schema.ErrorClass( * * **Example** (Creating a tool not found error) * - * ```ts + * ```ts import.meta.vitest * import { AiError } from "effect/unstable/ai" * * const error = new AiError.ToolNotFoundError({ @@ -960,15 +949,13 @@ export class UnknownError extends Schema.ErrorClass( * availableTools: ["GetWeather", "GetTime"] * }) * - * console.log(error.isRetryable) // true - * console.log(error.message) - * // "Tool 'unknownTool' not found. Available tools: GetWeather, GetTime" + * const result = [error.toolName, error.availableTools, error.isRetryable] // => ["unknownTool", ["GetWeather", "GetTime"], true] * ``` * - * @category reason + * @category errors * @since 4.0.0 */ -export class ToolNotFoundError extends Schema.ErrorClass( +export class ToolNotFoundError extends Schema.Error( "effect/ai/AiError/ToolNotFoundError" )({ _tag: Schema.tag("ToolNotFoundError"), @@ -1007,7 +994,7 @@ export class ToolNotFoundError extends Schema.ErrorClass( * * **Example** (Creating a tool parameter validation error) * - * ```ts + * ```ts import.meta.vitest * import { AiError } from "effect/unstable/ai" * * const error = new AiError.ToolParameterValidationError({ @@ -1016,15 +1003,13 @@ export class ToolNotFoundError extends Schema.ErrorClass( * description: "Expected string, got number" * }) * - * console.log(error.isRetryable) // true - * console.log(error.message) - * // "Invalid parameters for tool 'GetWeather': Expected string, got number" + * const result = [error.toolName, error.description, error.isRetryable] // => ["GetWeather", "Expected string, got number", true] * ``` * - * @category reason + * @category errors * @since 4.0.0 */ -export class ToolParameterValidationError extends Schema.ErrorClass( +export class ToolParameterValidationError extends Schema.Error( "effect/ai/AiError/ToolParameterValidationError" )({ _tag: Schema.tag("ToolParameterValidationError"), @@ -1064,7 +1049,7 @@ export class ToolParameterValidationError extends Schema.ErrorClass ["GetWeather", false] * ``` * - * @category reason + * @category errors * @since 4.0.0 */ -export class InvalidToolResultError extends Schema.ErrorClass( +export class InvalidToolResultError extends Schema.Error( "effect/ai/AiError/InvalidToolResultError" )({ _tag: Schema.tag("InvalidToolResultError"), @@ -1118,7 +1101,7 @@ export class InvalidToolResultError extends Schema.ErrorClass ["GetWeather", "Cannot encode bigint values as JSON", false] * ``` * - * @category reason + * @category errors * @since 4.0.0 */ -export class ToolResultEncodingError extends Schema.ErrorClass( +export class ToolResultEncodingError extends Schema.Error( "effect/ai/AiError/ToolResultEncodingError" )({ _tag: Schema.tag("ToolResultEncodingError"), @@ -1174,7 +1155,7 @@ export class ToolResultEncodingError extends Schema.ErrorClass ["OpenAiCodeInterpreter", "Invalid container ID format", false] * ``` * - * @category reason + * @category errors * @since 4.0.0 */ -export class ToolConfigurationError extends Schema.ErrorClass( +export class ToolConfigurationError extends Schema.Error( "effect/ai/AiError/ToolConfigurationError" )({ _tag: Schema.tag("ToolConfigurationError"), @@ -1228,22 +1207,20 @@ export class ToolConfigurationError extends Schema.ErrorClass [["GetWeather", "SendEmail"], false] * ``` * - * @category reason + * @category errors * @since 4.0.0 */ -export class ToolkitRequiredError extends Schema.ErrorClass( +export class ToolkitRequiredError extends Schema.Error( "effect/ai/AiError/ToolkitRequiredError" )({ _tag: Schema.tag("ToolkitRequiredError"), @@ -1283,22 +1260,20 @@ export class ToolkitRequiredError extends Schema.ErrorClass ["InvalidUserInputError", false] * ``` * - * @category reason + * @category errors * @since 4.0.0 */ -export class InvalidUserInputError extends Schema.ErrorClass( +export class InvalidUserInputError extends Schema.Error( "effect/ai/AiError/InvalidUserInputError" )({ _tag: Schema.tag("InvalidUserInputError"), @@ -1338,7 +1313,7 @@ export class InvalidUserInputError extends Schema.ErrorClass + * const aiOperation = Effect.fail(new AiError.AiError({ + * module: "OpenAI", + * method: "generateText", + * reason: new AiError.RateLimitError({ retryAfter: Duration.seconds(30) }) + * })) * * // Handle specific reason types * const handled = aiOperation.pipe( @@ -1453,12 +1432,14 @@ const TypeId = "~effect/unstable/ai/AiError/AiError" as const * return Effect.fail(error) * }) * ) + * + * await Effect.runPromise(handled) // => "Retry after 30000 millis" * ``` * * @category schemas * @since 4.0.0 */ -export class AiError extends Schema.ErrorClass( +export class AiError extends Schema.Error( "effect/ai/AiError/AiError" )({ _tag: Schema.tag("AiError"), @@ -1505,7 +1486,7 @@ export type AiErrorEncoded = typeof AiError["Encoded"] * * **Example** (Checking for an AI error) * - * ```ts + * ```ts import.meta.vitest * import { AiError } from "effect/unstable/ai" * * const someError = new Error("generic error") @@ -1515,8 +1496,7 @@ export type AiErrorEncoded = typeof AiError["Encoded"] * reason: new AiError.RateLimitError({}) * }) * - * console.log(AiError.isAiError(someError)) // false - * console.log(AiError.isAiError(aiError)) // true + * const result = [AiError.isAiError(someError), AiError.isAiError(aiError)] // => [false, true] * ``` * * @category guards @@ -1529,14 +1509,13 @@ export const isAiError = (u: unknown): u is AiError => Predicate.hasProperty(u, * * **Example** (Checking for an AI error reason) * - * ```ts + * ```ts import.meta.vitest * import { AiError } from "effect/unstable/ai" * * const rateLimitError = new AiError.RateLimitError({}) * const genericError = new Error("generic error") * - * console.log(AiError.isAiErrorReason(rateLimitError)) // true - * console.log(AiError.isAiErrorReason(genericError)) // false + * const result = [AiError.isAiErrorReason(rateLimitError), AiError.isAiErrorReason(genericError)] // => [true, false] * ``` * * @category guards @@ -1549,7 +1528,7 @@ export const isAiErrorReason = (u: unknown): u is AiErrorReason => Predicate.has * * **Example** (Creating an AI error) * - * ```ts + * ```ts import.meta.vitest * import { Duration } from "effect" * import { AiError } from "effect/unstable/ai" * @@ -1561,8 +1540,7 @@ export const isAiErrorReason = (u: unknown): u is AiErrorReason => Predicate.has * }) * }) * - * console.log(error.message) - * // "OpenAI.completion: Rate limit exceeded. Retry after 1 minute" + * const result = [error.module, error.method, error.reason._tag] // => ["OpenAI", "completion", "RateLimitError"] * ``` * * @category constructors @@ -1584,7 +1562,7 @@ export const make = (params: { * * **Example** (Mapping an HTTP status to a reason) * - * ```ts + * ```ts import.meta.vitest * import { AiError } from "effect/unstable/ai" * * const reason = AiError.reasonFromHttpStatus({ @@ -1592,7 +1570,7 @@ export const make = (params: { * body: { error: "Rate limit exceeded" } * }) * - * console.log(reason._tag) // "RateLimitError" + * reason._tag // => "RateLimitError" * ``` * * @category constructors diff --git a/repos/effect/packages/effect/src/unstable/ai/AnthropicStructuredOutput.ts b/repos/effect/packages/effect/src/unstable/ai/AnthropicStructuredOutput.ts index 634f9d3d9f..6b90344492 100644 --- a/repos/effect/packages/effect/src/unstable/ai/AnthropicStructuredOutput.ts +++ b/repos/effect/packages/effect/src/unstable/ai/AnthropicStructuredOutput.ts @@ -3,24 +3,18 @@ * structured output. * * The main entry point returns the JSON Schema to send to Anthropic and a codec - * for decoding the model response back into the original application type. When - * Anthropic cannot express the original schema shape directly, the conversion - * rewrites supported cases such as tuples, records, optional properties, and - * `oneOf` unions. Schema kinds that cannot be represented throw during - * conversion instead of producing a lossy schema. + * for decoding the model response back into the original application type. + * Unsupported constraints can be omitted from the provider schema and remain + * enforced by the returned codec. * * @since 4.0.0 */ -import * as Arr from "../../Array.ts" import * as JsonSchema from "../../JsonSchema.ts" -import * as Option from "../../Option.ts" -import * as Predicate from "../../Predicate.ts" +import * as Rec from "../../Record.ts" import * as Schema from "../../Schema.ts" -import * as SchemaAST from "../../SchemaAST.ts" -import * as SchemaTransformation from "../../SchemaTransformation.ts" +import * as InternalStructuredOutput from "./internal/structured-output.ts" import * as LanguageModel from "./LanguageModel.ts" import * as OpenAiStructuredOutput from "./OpenAiStructuredOutput.ts" -import * as Tool from "./Tool.ts" /** * Converts a `Schema.Codec` to Anthropic structured-output JSON Schema and a @@ -35,22 +29,26 @@ import * as Tool from "./Tool.ts" * **Details** * * Returns the JSON Schema to include in the request and the codec to use when - * decoding the model response. If the input schema already fits Anthropic's - * supported JSON Schema subset, the original codec is returned unchanged. + * decoding the model response. The codec remains authoritative: the provider + * JSON Schema can be a lossy, less restrictive representation when Anthropic + * cannot express an Effect Schema constraint. * * **Gotchas** * * - Some schemas use a provider-safe encoded shape: tuples become objects with - * numeric string keys, records become arrays of `[key, value]` pairs, and - * optional properties become required nullable properties. + * numeric string keys, objects with index signatures become arrays of + * `[key, value]` pairs, and optional properties become required nullable + * properties. * - `oneOf` unions are emitted as `anyOf` unions. - * - Unsupported schema kinds throw during conversion instead of producing a - * lossy schema. + * - Unsupported constraints are removed from the provider schema and are still + * checked while decoding with the returned codec. + * - Recursive schemas throw during conversion because Anthropic structured + * output does not support recursive references. * * @see {@link LanguageModel.CodecTransformer} for the structured-output transformer contract * @see {@link OpenAiStructuredOutput.toCodecOpenAI} for the OpenAI-specific transformer * - * @category Codec Transformation + * @category transforming * @since 4.0.0 */ export function toCodecAnthropic( @@ -59,349 +57,120 @@ export function toCodecAnthropic( readonly codec: Schema.ConstraintCodec readonly jsonSchema: JsonSchema.JsonSchema } { - const to = schema.ast - const from = recur(SchemaAST.toEncoded(to)) - const codec = from === to - ? schema - : Schema.make(SchemaAST.decodeTo(from, to, SchemaTransformation.passthrough())) - const document = JsonSchema.resolveTopLevel$ref(Schema.toJsonSchemaDocument(codec)) - const jsonSchema = { ...document.schema } + const codec = InternalStructuredOutput.toCodec(schema) + const unresolvedDocument = Schema.toJsonSchemaDocument(codec, { generateDescriptions: true }) + if (hasReferenceCycle(unresolvedDocument.schema, unresolvedDocument.definitions)) { + throw new Error("AnthropicStructuredOutput: Recursive schemas are not supported") + } + const document = JsonSchema.resolveTopLevel$ref(unresolvedDocument) + const jsonSchema = rewriteAnthropic(document.schema) if (Object.keys(document.definitions).length > 0) { - jsonSchema.$defs = document.definitions + jsonSchema.$defs = Rec.map(document.definitions, rewriteAnthropic) } return { codec, jsonSchema } } -function recur(ast: SchemaAST.AST): SchemaAST.AST { - switch (ast._tag) { - case "Declaration": - case "Void": - case "Never": - case "Unknown": - case "Any": - case "BigInt": - case "Symbol": - case "UniqueSymbol": - case "ObjectKeyword": - case "Enum": - case "TemplateLiteral": - case "Suspend": - return unsupportedAst( - ast, - "Anthropic structured output does not support this schema kind; consider transforming the schema or using a different provider" - ) - case "Undefined": - return unsupportedAst( - ast, - "Anthropic structured output does not support undefined; consider transforming the schema or using a different provider; if using `Schema.optional`, consider using `Schema.optionalKey` instead" - ) - case "Null": - return ast - case "String": { - const { annotations, filters } = get(ast) - if (annotations !== undefined || filters !== undefined) { - return new SchemaAST.String(annotations, filters) - } - return ast - } - case "Number": { - const { annotations, filters } = get(ast) - if (annotations !== undefined || filters !== undefined) { - return new SchemaAST.Number(annotations, filters) - } - return ast - } - case "Boolean": - return ast - case "Literal": { - const literal = ast.literal - if (typeof literal === "string" || typeof literal === "number" || typeof literal === "boolean") { - const { annotations, filters } = get(ast) - if (annotations !== undefined || filters !== undefined) { - return new SchemaAST.Literal(ast.literal, annotations, filters) - } - return ast - } - throw new Error( - `${errorPrefix}: Unsupported literal type ${typeof literal} (value: ${ - String(literal) - }) (supported: string | number | boolean)` - ) - } - case "Union": { - if (ast.mode === "oneOf") { - return new SchemaAST.Union(ast.types, "anyOf", ast.annotations, ast.checks) - } - const types = SchemaAST.mapOrSame(ast.types, recur) - const { annotations, filters } = get(ast) - if (types !== ast.types || annotations !== undefined || filters !== undefined) { - return new SchemaAST.Union(types, "anyOf", annotations, filters) - } - return ast - } - case "Arrays": { - if (ast.rest.length > 1) { - throw new Error( - `${errorPrefix}: Post-rest elements are not supported for arrays (rest length: ${ast.rest.length})` - ) - } - let { annotations, filters } = get(ast) - if (ast.elements.length > 0) { - // tuples are not supported by Anthropic, we translate them to objects with string keys - if (annotations !== undefined && typeof annotations.description === "string") { - annotations.description = `${TUPLE_DESCRIPTION}; ${annotations.description}` - } else { - annotations ??= {} - annotations.description = TUPLE_DESCRIPTION - } - const propertySignatures = ast.elements.map((e, i) => { - return new SchemaAST.PropertySignature(String(i), e) - }) - if (ast.rest.length === 1) { - propertySignatures.push( - new SchemaAST.PropertySignature(REST_PROPERTY_NAME, new SchemaAST.Arrays(false, [], ast.rest)) - ) - } - return SchemaAST.decodeTo( - recur(new SchemaAST.Objects(propertySignatures, [], annotations, filters)), - ast, - SchemaTransformation.transform({ - decode: (o) => { - let t: Array = [] - for (let i = 0; i < ast.elements.length; i++) { - const k = String(i) - if (o[k] !== undefined) { - t.push(o[k]) - } - } - if (REST_PROPERTY_NAME in o) { - t = [...t, ...o[REST_PROPERTY_NAME]] - } - return t - }, - encode: (t) => { - const o: Record = {} - for (let i = 0; i < ast.elements.length; i++) { - if (t.length >= i) { - o[String(i)] = t[i] - } - } - if (ast.rest.length === 1) { - o[REST_PROPERTY_NAME] = t.length >= ast.elements.length ? t.slice(ast.elements.length) : [] - } - return o - } - }) - ) - } else { - const rest = SchemaAST.mapOrSame(ast.rest, recur) - if (rest !== ast.rest || annotations !== undefined || filters !== undefined) { - return new SchemaAST.Arrays(false, [], rest, annotations, filters) - } - return ast - } - } - case "Objects": { - let { annotations, filters } = get(ast) - if (ast.indexSignatures.length === 0) { - const propertySignatures = SchemaAST.mapOrSame(ast.propertySignatures, (ps) => { - if (typeof ps.name !== "string") { - throw new Error( - `${errorPrefix}: Property names must be strings (got ${typeof ps.name})` - ) - } - let type = recur(ps.type) - // opttional properties are not supported by Anthropic, so we translate them to nullable unions - if (SchemaAST.isOptional(ps.type)) { - type = SchemaAST.decodeTo( - new SchemaAST.Union([type, SchemaAST.null], "anyOf"), - SchemaAST.optionalKey(type), - SchemaTransformation.transformOptional({ - decode: Option.filter(Predicate.isNotNull), - encode: Option.orElseSome(() => null) - }) - ) - } - if (type === ps.type) { - return ps - } - return new SchemaAST.PropertySignature(ps.name, type) - }) - if ( - propertySignatures !== ast.propertySignatures || annotations !== undefined || filters !== undefined - ) { - return new SchemaAST.Objects(propertySignatures, [], annotations, filters) - } - } else if (ast.indexSignatures.length === 1 && ast.propertySignatures.length === 0) { - const is = ast.indexSignatures[0] - if (Tool.isEmptyParamsRecord(is)) { - return ast - } - // records are not supported by Anthropic, so we translate them to arrays of key-value pairs - if (annotations !== undefined && typeof annotations.description === "string") { - annotations.description = `${RECORD_DESCRIPTION}; ${annotations.description}` - } else { - annotations ??= {} - annotations.description = RECORD_DESCRIPTION - } - return SchemaAST.decodeTo( - recur( - new SchemaAST.Arrays(false, [], [new SchemaAST.Arrays(false, [is.parameter, is.type], [])], annotations) - ), - ast, - SchemaTransformation.transform({ - decode: Object.fromEntries, - encode: Object.entries - }) - ) - } else { - throw new Error( - `${errorPrefix}: unsupported object schema shape (properties: ${ast.propertySignatures.length}, indexSignatures: ${ast.indexSignatures.length}). Supported: plain objects (properties only) or records (single index signature, no properties)` - ) +function hasReferenceCycle( + root: JsonSchema.JsonSchema, + definitions: JsonSchema.Definitions +): boolean { + const visiting = new Set() + const visited = new Set() + + function visit(schema: JsonSchema.JsonSchema): boolean { + if (visiting.has(schema)) return true + if (visited.has(schema)) return false + + visiting.add(schema) + let cycle = false + InternalStructuredOutput.walkJsonSchema(schema, (node) => { + if (!cycle && typeof node.$ref === "string") { + const target = node.$ref === "#" ? root : JsonSchema.resolve$ref(node.$ref, definitions) + if (target !== undefined && visit(target)) cycle = true } - return ast - } + return node + }) + visiting.delete(schema) + visited.add(schema) + return cycle } -} - -const errorPrefix = "AnthropicStructuredOutput" -function unsupportedAst(ast: SchemaAST.AST, details?: string): never { - const base = `Unsupported AST ${ast._tag}` - const full = `${errorPrefix}: ${base}` - throw new Error(details !== undefined ? `${full} (${details})` : full) + return visit(root) } -const REST_PROPERTY_NAME = "__rest__" - -const RECORD_DESCRIPTION = - "Object encoded as array of [key, value] pairs. Apply object constraints to the decoded object" - -const TUPLE_DESCRIPTION = - "Tuple encoded as an object with numeric string keys ('0', '1', ...). If present, '__rest__' contains remaining elements" - -type Annotation = - | { readonly _tag: "description"; readonly description: string } - | { readonly _tag: "format"; readonly format: string } - -type Filter = - | Annotation - | { readonly _tag: "filter"; readonly filter: SchemaAST.Filter } - -const get = (ast: SchemaAST.AST): { - annotations: Record | undefined - filters: [SchemaAST.Check, ...SchemaAST.Check[]] | undefined -} => { - const annotations: Record = {} - const filters: Array> = [] - const checks = getChecks(ast) - if (checks.length > 0) { - for (const check of checks) { - switch (check._tag) { - case "description": { - if (annotations.description !== undefined) { - annotations.description += ` and ${check.description}` - } else { - annotations.description = check.description - } - break - } - case "format": { - annotations.format = check.format - break - } - case "filter": { - filters.push(check.filter) - break - } +function rewriteAnthropic(schema: JsonSchema.JsonSchema): JsonSchema.JsonSchema { + return InternalStructuredOutput.walkJsonSchema(schema, (schema) => { + const normalized = hoistAllOfDescriptions(schema) + const out: JsonSchema.JsonSchema = {} + let unsupportedFormat: string | undefined + for (const [key, value] of Object.entries(normalized)) { + if (key === "format") { + if ( + typeof value === "string" && formats.has(value) && + (normalized.type === undefined || normalized.type === "string") + ) { + out.format = value + } else if (typeof value === "string") unsupportedFormat = value + } else if (supportedKeywords.has(key)) { + if (key !== "additionalProperties" || value === false) out[key] = value } } - } - return { - annotations: Object.keys(annotations).length > 0 ? annotations : undefined, - filters: Arr.isArrayNonEmpty(filters) ? filters : undefined - } + if (unsupportedFormat !== undefined) { + InternalStructuredOutput.appendDescription(out, `a value with a format of ${unsupportedFormat}`) + } + return out + }) } -const getChecks = (ast: SchemaAST.AST): Array => [ - ...(ast.checks !== undefined ? getFilters(ast.checks) : []), - ...getAnnotations(ast.annotations) -] +function hoistAllOfDescriptions(schema: JsonSchema.JsonSchema): JsonSchema.JsonSchema { + if (!Array.isArray(schema.allOf)) return schema -const getAnnotations = (annotations: Schema.Annotations.Filter | undefined): Array => { - const out: Array = [] - if (annotations !== undefined) { - const description = annotations?.description - ?? (annotations.meta?._tag === "isInt" || annotations.meta?._tag === "isFinite" - ? undefined - : annotations?.expected) - if (typeof description === "string") { - out.push({ _tag: "description", description }) - } - const format = annotations?.format - if (typeof format === "string") { - if (formats.includes(format)) { - out.push({ _tag: "format", format }) - } else { - out.push({ _tag: "description", description: `a value with a format of ${format}` }) - } - } + const out: JsonSchema.JsonSchema = {} + for (const [key, value] of Object.entries(schema)) { + if (key !== "allOf") out[key] = value } - return out -} - -function getFilter(filter: SchemaAST.Filter): Array { - let out: Array = [] - const annotations = getAnnotations(filter.annotations) - const meta = filter.annotations?.meta - if (meta !== undefined) { - switch (meta._tag) { - case "isInt": - case "isFinite": { - out = out.concat(annotations) - out.push({ _tag: "filter", filter: resetFilter(filter) }) - break - } - default: { - out = out.concat(annotations) - break - } - } - if ("regExp" in meta && meta.regExp instanceof RegExp) { - out.push({ _tag: "filter", filter: resetFilter(filter) }) + const members: Array = [] + for (const member of schema.allOf) { + if (!InternalStructuredOutput.isJsonSchema(member)) continue + const { description, format, title, ...memberRest } = member + const rest = { ...memberRest } + if (schema.type !== undefined && schema.type !== "string") delete rest.pattern + if (typeof description === "string") { + InternalStructuredOutput.appendDescription(out, description) } + if (out.format === undefined && typeof format === "string") out.format = format + if (out.title === undefined && typeof title === "string") out.title = title + if (Object.keys(rest).length > 0) members.push(rest) } + if (members.length > 0) out.allOf = members return out } -function resetFilter(filter: SchemaAST.Filter): SchemaAST.Filter { - return filter.annotate({ - description: undefined, - expected: undefined, - title: undefined, - format: undefined - }) -} - -function getFilters(checks: readonly [SchemaAST.Check, ...SchemaAST.Check[]]): Array { - return checks.flatMap((check) => { - switch (check._tag) { - case "Filter": - return getFilter(check) - case "FilterGroup": - return getFilters(check.checks) - } - }) -} - -const formats = [ +const supportedKeywords = new Set([ + "$ref", + "type", + "title", + "description", + "enum", + "const", + "anyOf", + "allOf", + "properties", + "required", + "additionalProperties", + "items", + "pattern" +]) + +const formats = new Set([ "date-time", "time", "date", "duration", "email", "hostname", + "uri", "ipv4", "ipv6", "uuid" -] +]) diff --git a/repos/effect/packages/effect/src/unstable/ai/Chat.ts b/repos/effect/packages/effect/src/unstable/ai/Chat.ts index b9a491e9d6..4ac38dd5cf 100644 --- a/repos/effect/packages/effect/src/unstable/ai/Chat.ts +++ b/repos/effect/packages/effect/src/unstable/ai/Chat.ts @@ -48,17 +48,35 @@ import type * as Tool from "./Tool.ts" * * **Example** (Accessing the Chat service) * - * ```ts - * import { Effect } from "effect" - * import { Chat } from "effect/unstable/ai" + * ```ts import.meta.vitest + * import { Effect, Layer, Stream } from "effect" + * import { Chat, LanguageModel } from "effect/unstable/ai" + * + * const FakeLanguageModel = Layer.effect( + * LanguageModel.LanguageModel, + * LanguageModel.make({ + * generateText: () => + * Effect.succeed([{ + * type: "text", + * text: "Quantum computers use quantum states to process information." + * }]), + * streamText: () => Stream.empty + * }) + * ) + * + * const ChatLayer = Layer.effect(Chat.Chat, Chat.empty) * * const program = Effect.gen(function*() { - * const chat = yield* Chat.empty + * const chat = yield* Chat.Chat * const response = yield* chat.generateText({ * prompt: "Explain quantum computing in simple terms" * }) - * return response.content + * return response.text * }) + * + * await Effect.runPromise( + * program.pipe(Effect.provide(Layer.merge(ChatLayer, FakeLanguageModel))) + * ) // => "Quantum computers use quantum states to process information." * ``` * * @category services @@ -94,16 +112,17 @@ export interface Service { * * **Example** (Inspecting chat history) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Ref } from "effect" * import { Chat } from "effect/unstable/ai" * * const inspectHistory = Effect.gen(function*() { - * const chat = yield* Chat.empty + * const chat = yield* Chat.fromPrompt("Hello") * const currentHistory = yield* Ref.get(chat.history) - * console.log("Current conversation:", currentHistory) - * return currentHistory + * return currentHistory.content.length * }) + * + * await Effect.runPromise(inspectHistory) // => 1 * ``` */ readonly history: Ref.Ref @@ -118,19 +137,17 @@ export interface Service { * * **Example** (Exporting chat history) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * import { Chat } from "effect/unstable/ai" * * const saveChat = Effect.gen(function*() { - * const chat = yield* Chat.empty - * yield* chat.generateText({ prompt: "Hello!" }) - * + * const chat = yield* Chat.fromPrompt("Hello!") * const exportedData = yield* chat.export - * - * // Save to database or file system - * return exportedData + * return typeof exportedData * }) + * + * await Effect.runPromise(saveChat) // => "object" * ``` */ readonly export: Effect.Effect @@ -145,21 +162,17 @@ export interface Service { * * **Example** (Exporting chat history as JSON) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * import { Chat } from "effect/unstable/ai" * * const backupChat = Effect.gen(function*() { - * const chat = yield* Chat.empty - * - * yield* chat.generateText({ prompt: "Explain photosynthesis" }) - * + * const chat = yield* Chat.fromPrompt("Explain photosynthesis") * const jsonBackup = yield* chat.exportJson - * - * yield* Effect.sync(() => localStorage.setItem("chat-backup", jsonBackup)) - * - * return jsonBackup + * return JSON.parse(jsonBackup).content.length * }) + * + * await Effect.runPromise(backupChat) // => 1 * ``` */ readonly exportJson: Effect.Effect @@ -175,9 +188,23 @@ export interface Service { * * **Example** (Generating chat responses) * - * ```ts - * import { Effect } from "effect" - * import { Chat } from "effect/unstable/ai" + * ```ts import.meta.vitest + * import { Effect, Layer, Stream } from "effect" + * import { Chat, LanguageModel } from "effect/unstable/ai" + * + * const FakeLanguageModel = Layer.effect( + * LanguageModel.LanguageModel, + * LanguageModel.make({ + * generateText: (options) => + * Effect.succeed([{ + * type: "text", + * text: options.prompt.content.length === 1 + * ? "The capital of France is Paris." + * : "Paris has about 2.1 million residents." + * }]), + * streamText: () => Stream.empty + * }) + * ) * * const chatWithAI = Effect.gen(function*() { * const chat = yield* Chat.empty @@ -185,13 +212,13 @@ export interface Service { * const response1 = yield* chat.generateText({ * prompt: "What is the capital of France?" * }) - * * const response2 = yield* chat.generateText({ * prompt: "What's the population of that city?" * }) - * - * return [response1.content, response2.content] + * return [response1.text, response2.text] * }) + * + * await Effect.runPromise(chatWithAI.pipe(Effect.provide(FakeLanguageModel))) // => ["The capital of France is Paris.", "Paris has about 2.1 million residents."] * ``` */ readonly generateText: { @@ -242,22 +269,35 @@ export interface Service { * * **Example** (Streaming chat responses) * - * ```ts - * import { Effect, Stream } from "effect" - * import { Chat } from "effect/unstable/ai" + * ```ts import.meta.vitest + * import { Effect, Layer, Stream } from "effect" + * import { Chat, LanguageModel } from "effect/unstable/ai" + * + * const FakeLanguageModel = Layer.effect( + * LanguageModel.LanguageModel, + * LanguageModel.make({ + * generateText: () => Effect.succeed([]), + * streamText: () => + * Stream.make( + * { type: "text-delta", id: "story", delta: "A small probe reached orbit." }, + * { type: "text-delta", id: "story", delta: " It sent back a picture of Earth." } + * ) + * }) + * ) * * const streamingChat = Effect.gen(function*() { * const chat = yield* Chat.empty - * - * const stream = yield* chat.streamText({ + * const story = yield* chat.streamText({ * prompt: "Write a short story about space exploration" - * }) - * - * yield* Stream.runForEach(stream, (part) => - * part.type === "text-delta" - * ? Effect.sync(() => process.stdout.write(part.delta)) - * : Effect.void) + * }).pipe( + * Stream.runFold(() => "", (text, part) => + * part.type === "text-delta" ? text + part.delta : text) + * ) + * return story * }) + * + * const story = await Effect.runPromise(streamingChat.pipe(Effect.provide(FakeLanguageModel))) + * story // => "A small probe reached orbit. It sent back a picture of Earth." * ``` */ readonly streamText: { @@ -309,9 +349,9 @@ export interface Service { * * **Example** (Generating structured objects) * - * ```ts - * import { Effect, Schema } from "effect" - * import { Chat } from "effect/unstable/ai" + * ```ts import.meta.vitest + * import { Effect, Layer, Schema, Stream } from "effect" + * import { Chat, LanguageModel } from "effect/unstable/ai" * * const ContactSchema = Schema.Struct({ * name: Schema.String, @@ -319,24 +359,33 @@ export interface Service { * phone: Schema.optional(Schema.String) * }) * + * const FakeLanguageModel = Layer.effect( + * LanguageModel.LanguageModel, + * LanguageModel.make({ + * generateText: () => + * Effect.succeed([{ + * type: "text", + * text: '{"name":"John Doe","email":"john@example.com","phone":"555-1234"}' + * }]), + * streamText: () => Stream.empty + * }) + * ) + * * const extractContact = Effect.gen(function*() { * const chat = yield* Chat.empty - * * const contact = yield* chat.generateObject({ * prompt: "Extract contact info: John Doe, john@example.com, 555-1234", * schema: ContactSchema * }) - * - * console.log(contact.object) - * // { name: "John Doe", email: "john@example.com", phone: "555-1234" } - * - * return contact.object + * return [contact.value.name, contact.value.email, contact.value.phone] * }) + * + * await Effect.runPromise(extractContact.pipe(Effect.provide(FakeLanguageModel))) // => ["John Doe", "john@example.com", "555-1234"] * ``` */ readonly generateObject: < ObjectEncoded extends Record, - ObjectSchema extends Schema.Codec, + ObjectSchema extends Schema.Encoder, Options extends NoExcessProperties, Options> >( options: Options & LanguageModel.GenerateObjectOptions, ObjectSchema> @@ -464,21 +513,17 @@ const makeUnsafe = (history: Ref.Ref) => { * * **Example** (Creating an empty chat) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * import { Chat } from "effect/unstable/ai" * * const freshChat = Effect.gen(function*() { * const chat = yield* Chat.empty - * - * const response = yield* chat.generateText({ - * prompt: "Hello! Can you introduce yourself?" - * }) - * - * console.log(response.content) - * - * return chat + * const history = yield* chat.export + * return (history as { content: ReadonlyArray }).content.length * }) + * + * await Effect.runPromise(freshChat) // => 0 * ``` * * @category constructors @@ -496,7 +541,7 @@ export const empty: Effect.Effect = Effect.sync(() => makeUnsafe(Ref.ma * * **Example** (Creating a chat from a system prompt) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * import { Chat } from "effect/unstable/ai" * @@ -506,17 +551,16 @@ export const empty: Effect.Effect = Effect.sync(() => makeUnsafe(Ref.ma * content: "You are a helpful assistant specialized in mathematics." * }]) * - * const response = yield* chat.generateText({ - * prompt: "What is 2+2?" - * }) - * - * return response.content + * const history = yield* chat.export + * return (history as { content: ReadonlyArray }).content.length * }) + * + * await Effect.runPromise(chatWithSystemPrompt) // => 1 * ``` * * **Example** (Restoring chat history from a prompt) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * import { Chat } from "effect/unstable/ai" * @@ -537,12 +581,11 @@ export const empty: Effect.Effect = Effect.sync(() => makeUnsafe(Ref.ma * } * ]) * - * const response = yield* chat.generateText({ - * prompt: "I need help with TypeScript" - * }) - * - * return response + * const history = yield* chat.export + * return (history as { content: ReadonlyArray }).content.length * }) + * + * await Effect.runPromise(existingChat) // => 3 * ``` * * @category constructors @@ -562,7 +605,7 @@ export const fromPrompt = (prompt: Prompt.RawInput) => * * **Example** (Restoring chat data) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Ref } from "effect" * import { Chat } from "effect/unstable/ai" * @@ -582,18 +625,20 @@ export const fromPrompt = (prompt: Prompt.RawInput) => * const restoredChat = yield* Chat.fromExport(exported) * const restoredHistory = yield* Ref.get(restoredChat.history) * - * console.log(restoredHistory.content.map((message) => message.role)) - * // ["user", "assistant"] - * * const restoredResponse = restoredHistory.content[1] * if (restoredResponse?.role === "assistant") { * const restoredText = restoredResponse.content[0] * if (restoredText?.type === "text") { - * console.log(restoredText.text) - * // "The project uses Effect." + * return { + * roles: restoredHistory.content.map((message) => message.role), + * text: restoredText.text + * } * } * } + * return undefined * }) + * + * await Effect.runPromise(restoreChat) // => { roles: ["user", "assistant"], text: "The project uses Effect." } * ``` * * @category constructors @@ -615,29 +660,19 @@ export const fromExport = (data: unknown): Effect.Effect< * * **Example** (Restoring chat history from JSON) * - * ```ts - * import { Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect, Ref } from "effect" * import { Chat } from "effect/unstable/ai" * * const restoreFromJson = Effect.gen(function*() { - * // Load JSON from localStorage or file system - * const jsonData = localStorage.getItem("my-chat-backup") - * if (!jsonData) return yield* Chat.empty - * + * const original = yield* Chat.fromPrompt("Hello") + * const jsonData = yield* original.exportJson * const restoredChat = yield* Chat.fromJson(jsonData) + * const history = yield* Ref.get(restoredChat.history) + * return history.content.length + * }) * - * // Chat history is now restored - * const response = yield* restoredChat.generateText({ - * prompt: "What were we talking about?" - * }) - * - * return response - * }).pipe( - * Effect.catchTag("SchemaError", (error) => { - * console.log("Invalid JSON format:", error.message) - * return Chat.empty // Fallback to empty chat - * }) - * ) + * await Effect.runPromise(restoreFromJson) // => 1 * ``` * * @category constructors @@ -664,7 +699,7 @@ export const fromJson = (data: string): Effect.Effect< * @category errors * @since 4.0.0 */ -export class ChatNotFoundError extends Schema.ErrorClass( +export class ChatNotFoundError extends Schema.Error( "effect/ai/Chat/ChatNotFoundError" )({ _tag: Schema.tag("ChatNotFoundError"), @@ -923,7 +958,7 @@ export const makePersisted = Effect.fnUntraced(function*(options: { * * @see {@link makePersisted} for the effect constructor when building the service directly instead of providing it as a layer * - * @category constructors + * @category layers * @since 4.0.0 */ export const layerPersisted = (options: { diff --git a/repos/effect/packages/effect/src/unstable/ai/EmbeddingModel.ts b/repos/effect/packages/effect/src/unstable/ai/EmbeddingModel.ts index c08bdd4c03..acf6fa7f82 100644 --- a/repos/effect/packages/effect/src/unstable/ai/EmbeddingModel.ts +++ b/repos/effect/packages/effect/src/unstable/ai/EmbeddingModel.ts @@ -69,7 +69,7 @@ export class Dimensions extends Context.Service()( export class EmbeddingUsage extends Schema.Class( "effect/ai/EmbeddingModel/EmbeddingUsage" )({ - inputTokens: Schema.UndefinedOr(Schema.Finite) + inputTokens: Schema.optional(Schema.Finite) }) {} /** diff --git a/repos/effect/packages/effect/src/unstable/ai/IdGenerator.ts b/repos/effect/packages/effect/src/unstable/ai/IdGenerator.ts index ba2217cdb7..60bb102a98 100644 --- a/repos/effect/packages/effect/src/unstable/ai/IdGenerator.ts +++ b/repos/effect/packages/effect/src/unstable/ai/IdGenerator.ts @@ -32,7 +32,7 @@ import * as Random from "../../Random.ts" * * **Example** (Accessing the ID generator service) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * import { IdGenerator } from "effect/unstable/ai" * @@ -41,6 +41,13 @@ import * as Random from "../../Random.ts" * const newId = yield* idGenerator.generateId() * return newId * }) + * + * const program = useIdGenerator.pipe( + * Effect.provideService(IdGenerator.IdGenerator, { + * generateId: () => Effect.succeed("id-1") + * }) + * ) + * await Effect.runPromise(program) // => "id-1" * ``` * * @category services @@ -61,7 +68,7 @@ export class IdGenerator extends Context.Service()( * * **Example** (Implementing a custom ID generator) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * import type { IdGenerator } from "effect/unstable/ai" * @@ -71,11 +78,9 @@ export class IdGenerator extends Context.Service()( * generateId: () => Effect.sync(() => `custom_${++nextId}`) * } * - * const program = Effect.gen(function*() { - * const id = yield* customService.generateId() - * console.log(id) // "custom_1" - * return id - * }) + * const program = customService.generateId() + * + * await Effect.runPromise(program) // => "custom_1" * ``` * * @category models @@ -90,7 +95,7 @@ export interface Service { * * **Example** (Configuring generated IDs) * - * ```ts + * ```ts import.meta.vitest * import type { IdGenerator } from "effect/unstable/ai" * * // Configuration for tool call IDs @@ -102,6 +107,7 @@ export interface Service { * } * * // This will generate IDs like: "tool_A1B2C3D4" + * const result = [toolCallOptions.prefix, toolCallOptions.size] // => ["tool", 8] * ``` * * @category options @@ -162,13 +168,12 @@ const makeGenerator = ({ * * **Example** (Generating default IDs) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * import { IdGenerator } from "effect/unstable/ai" * * const program = Effect.gen(function*() { * const id = yield* IdGenerator.defaultIdGenerator.generateId() - * console.log(id) // "id_A7xK9mP2qR5tY8uV" * return id * }) * @@ -179,6 +184,9 @@ const makeGenerator = ({ * IdGenerator.defaultIdGenerator * ) * ) + * + * const id = await Effect.runPromise(withDefault) + * const result = [id.startsWith("id_"), id.length] // => [true, 19] * ``` * * @category constructors @@ -198,7 +206,7 @@ export const defaultIdGenerator: Service = { * * **Example** (Creating a custom generator) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * import { IdGenerator } from "effect/unstable/ai" * @@ -211,15 +219,16 @@ export const defaultIdGenerator: Service = { * size: 10 * }) * - * const messageId = yield* messageIdGen.generateId() - * console.log(messageId) // "msg-A7X9K2M5P8" - * return messageId + * return yield* messageIdGen.generateId() * }) + * + * const messageId = await Effect.runPromise(program) + * const result = [messageId.startsWith("msg-"), messageId.length] // => [true, 14] * ``` * * **Example** (Handling invalid generator options) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * import { IdGenerator } from "effect/unstable/ai" * @@ -231,14 +240,8 @@ export const defaultIdGenerator: Service = { * size: 8 * }) * - * const program = Effect.gen(function*() { - * const generator = yield* invalidConfig - * return generator - * }).pipe( - * Effect.catch((error) => - * Effect.succeed(`Configuration error: ${error.message}`) - * ) - * ) + * const error = await Effect.runPromise(Effect.flip(invalidConfig)) + * error.message // => 'The separator "A" must not be part of the alphabet "ABC123".' * ``` * * @category constructors @@ -273,7 +276,7 @@ export const make = Effect.fnUntraced(function*({ * * **Example** (Providing an ID generator layer) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * import { IdGenerator } from "effect/unstable/ai" * @@ -287,13 +290,14 @@ export const make = Effect.fnUntraced(function*({ * * const program = Effect.gen(function*() { * const idGen = yield* IdGenerator.IdGenerator - * const toolCallId = yield* idGen.generateId() - * console.log(toolCallId) // "tool_call_A7XK9MP2QR5T" - * return toolCallId + * return yield* idGen.generateId() * }).pipe(Effect.provide(toolCallIdLayer)) + * + * const toolCallId = await Effect.runPromise(program) + * const result = [toolCallId.startsWith("tool_call_"), toolCallId.length] // => [true, 22] * ``` * - * @category constructors + * @category layers * @since 4.0.0 */ export const layer = (options: MakeOptions): Layer.Layer => diff --git a/repos/effect/packages/effect/src/unstable/ai/LanguageModel.ts b/repos/effect/packages/effect/src/unstable/ai/LanguageModel.ts index f07ef9571f..0f5f544b0b 100644 --- a/repos/effect/packages/effect/src/unstable/ai/LanguageModel.ts +++ b/repos/effect/packages/effect/src/unstable/ai/LanguageModel.ts @@ -20,9 +20,9 @@ import type * as JsonSchema from "../../JsonSchema.ts" import * as Option from "../../Option.ts" import * as Predicate from "../../Predicate.ts" import * as Queue from "../../Queue.ts" -import { CurrentConcurrency } from "../../References.ts" import * as Schema from "../../Schema.ts" import * as SchemaAST from "../../SchemaAST.ts" +import * as Semaphore from "../../Semaphore.ts" import * as Sink from "../../Sink.ts" import * as Stream from "../../Stream.ts" import type { Span } from "../../Tracer.ts" @@ -52,10 +52,22 @@ import * as Toolkit from "./Toolkit.ts" * * **Example** (Accessing the language model service) * - * ```ts - * import { Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect, Layer, Stream } from "effect" * import { LanguageModel } from "effect/unstable/ai" * + * const FakeLanguageModel = Layer.effect( + * LanguageModel.LanguageModel, + * LanguageModel.make({ + * generateText: () => + * Effect.succeed([{ + * type: "text", + * text: "Machine learning finds patterns in data." + * }]), + * streamText: () => Stream.empty + * }) + * ) + * * const program = Effect.gen(function*() { * const model = yield* LanguageModel.LanguageModel * const response = yield* model.generateText({ @@ -63,6 +75,8 @@ import * as Toolkit from "./Toolkit.ts" * }) * return response.text * }) + * + * await Effect.runPromise(program.pipe(Effect.provide(FakeLanguageModel))) // => "Machine learning finds patterns in data." * ``` * * @category services @@ -124,7 +138,7 @@ export interface Service { */ readonly generateObject: < ObjectEncoded extends Record, - StructuredOutputSchema extends Schema.Codec, + StructuredOutputSchema extends Schema.Encoder, Options extends NoExcessProperties< GenerateObjectOptions, Options @@ -186,9 +200,12 @@ export interface Service { * * Different language model providers have varying constraints on the JSON * schemas they accept. A `CodecTransformer` rewrites a codec's encoded side to - * satisfy those constraints while preserving the decoded type. + * satisfy those constraints while preserving the decoded type. A provider + * schema may be less restrictive than the codec when the provider cannot + * express every constraint; the returned codec remains authoritative for + * validating model output. * - * @category models + * @category utility types * @since 4.0.0 */ export type CodecTransformer = (schema: Schema.ConstraintCodec) => { @@ -334,21 +351,14 @@ export type ToolChoice = * * **Example** (Inspecting a text response) * - * ```ts - * import { Effect } from "effect" - * import { LanguageModel } from "effect/unstable/ai" + * ```ts import.meta.vitest + * import { LanguageModel, Response } from "effect/unstable/ai" * - * const program = Effect.gen(function*() { - * const response = yield* LanguageModel.generateText({ - * prompt: "Explain photosynthesis" - * }) + * const response = new LanguageModel.GenerateTextResponse([ + * Response.makePart("text", { text: "Plants convert light into energy." }) + * ]) * - * console.log(response.text) // Generated text content - * console.log(response.finishReason) // "stop", "length", etc. - * console.log(response.usage) // Usage information - * - * return response - * }) + * const result = [response.text, response.finishReason] // => ["Plants convert light into energy.", "unknown"] * ``` * * @category models @@ -445,26 +455,16 @@ export class GenerateTextResponse> { * * **Example** (Inspecting an object response) * - * ```ts - * import { Effect, Schema } from "effect" - * import { LanguageModel } from "effect/unstable/ai" - * - * const UserSchema = Schema.Struct({ - * name: Schema.String, - * email: Schema.String - * }) - * - * const program = Effect.gen(function*() { - * const response = yield* LanguageModel.generateObject({ - * prompt: "Create user: John Doe, john@example.com", - * schema: UserSchema - * }) + * ```ts import.meta.vitest + * import { LanguageModel, Response } from "effect/unstable/ai" * - * console.log(response.value) // { name: "John Doe", email: "john@example.com" } - * console.log(response.text) // Raw generated text + * const response = new LanguageModel.GenerateObjectResponse( + * { name: "John Doe", email: "john@example.com" }, + * [Response.makePart("text", { text: '{"name":"John Doe","email":"john@example.com"}' })] + * ) * - * return response.value - * }) + * response.value // => { name: "John Doe", email: "john@example.com" } + * response.text // => '{"name":"John Doe","email":"john@example.com"}' * ``` * * @category models @@ -835,7 +835,7 @@ export const make: (params: { const generateObject = < ObjectEncoded extends Record, - StructuredOutputSchema extends Schema.Codec, + StructuredOutputSchema extends Schema.Encoder, Options extends NoExcessProperties< GenerateObjectOptions, Options @@ -1004,6 +1004,8 @@ export const make: (params: { ) { const tracker = Option.getOrUndefined(yield* Effect.serviceOption(ResponseIdTracker.ResponseIdTracker)) const toolChoice = options.toolChoice ?? "auto" + const concurrency = options.concurrency ?? "unbounded" + providerOptions.span.attribute("concurrency", concurrency) const generateWithNonIncrementalFallback = () => { const requestOptions: ProviderOptions = { @@ -1121,7 +1123,7 @@ export const make: (params: { const approvedResults = yield* executeApprovedToolCalls( approved, toolkit, - options.concurrency + concurrency ) const deniedResults = createDenialResults(denied) const preResolvedResults = [...approvedResults, ...deniedResults] @@ -1189,7 +1191,7 @@ export const make: (params: { rawContent, toolkit, providerOptions.prompt.content, - options.concurrency + concurrency ).pipe( Stream.filter( (result) => @@ -1239,6 +1241,8 @@ export const make: (params: { ) { const tracker = Option.getOrUndefined(yield* Effect.serviceOption(ResponseIdTracker.ResponseIdTracker)) const toolChoice = options.toolChoice ?? "auto" + const concurrency = options.concurrency ?? "unbounded" + providerOptions.span.attribute("concurrency", concurrency) const streamWithNonIncrementalFallback = () => { const requestOptions: ProviderOptions = { @@ -1379,7 +1383,7 @@ export const make: (params: { const approvedResults = yield* executeApprovedToolCalls( pendingApproved, toolkit, - options.concurrency + concurrency ) const deniedResults = createDenialResults(pendingDenied) const preResolvedResults = [...approvedResults, ...deniedResults] @@ -1486,6 +1490,9 @@ export const make: (params: { // FiberSet to track concurrent tool call handlers const toolCallFibers = yield* FiberSet.make() + const toolCallSemaphore = concurrency === "unbounded" + ? undefined + : yield* Semaphore.make(concurrency) // Helper function to handle tool calls with approval logic const handleToolCall = Effect.fnUntraced(function*(part: Response.ToolCallPartEncoded) { @@ -1509,7 +1516,7 @@ export const make: (params: { return } - yield* toolkit.handle(part.name, part.params as any).pipe( + yield* toolkit.handle(part.name, part.params as any, part.id).pipe( Stream.unwrap, Stream.runForEach((result) => { const toolResultPart = Response.makePart("tool-result", { @@ -1550,7 +1557,11 @@ export const make: (params: { // Fork tool call handlers - use the raw chunk for encoded params for (const part of chunk) { if (part.type === "tool-call" && part.providerExecuted !== true) { - yield* FiberSet.run(toolCallFibers, handleToolCall(part)) + const effect = handleToolCall(part) + yield* FiberSet.run( + toolCallFibers, + toolCallSemaphore ? toolCallSemaphore.withPermit(effect) : effect + ) } } }) @@ -1592,21 +1603,43 @@ export const make: (params: { * * **Example** (Generating text with options) * - * ```ts - * import { Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect, Layer, Stream } from "effect" * import { LanguageModel } from "effect/unstable/ai" * - * const program = Effect.gen(function*() { - * const response = yield* LanguageModel.generateText({ - * prompt: "Write a haiku about programming", - * toolChoice: "none" + * const FakeLanguageModel = Layer.effect( + * LanguageModel.LanguageModel, + * LanguageModel.make({ + * generateText: (options) => + * Effect.succeed([ + * { + * type: "text", + * text: options.toolChoice === "none" + * ? "Code flows through types / Errors become values / Programs stay composed" + * : "Unexpected tool choice" + * }, + * { + * type: "finish", + * reason: "stop", + * usage: { + * inputTokens: { total: 6 }, + * outputTokens: { total: 12 } + * } + * } + * ]), + * streamText: () => Stream.empty * }) + * ) * - * console.log(response.text) - * console.log(response.usage.inputTokens.total) + * const program = LanguageModel.generateText({ + * prompt: "Write a haiku about programming", + * toolChoice: "none" + * }).pipe( + * Effect.map((response) => [response.text, response.usage.inputTokens.total]), + * Effect.provide(FakeLanguageModel) + * ) * - * return response - * }) + * await Effect.runPromise(program) // => ["Code flows through types / Errors become values / Programs stay composed", 6] * ``` * * @category text generation @@ -1661,8 +1694,8 @@ export const generateText: { * * **Example** (Generating an object) * - * ```ts - * import { Effect, Schema } from "effect" + * ```ts import.meta.vitest + * import { Effect, Layer, Schema, Stream } from "effect" * import { LanguageModel } from "effect/unstable/ai" * * const EventSchema = Schema.Struct({ @@ -1671,27 +1704,36 @@ export const generateText: { * location: Schema.String * }) * - * const program = Effect.gen(function*() { - * const response = yield* LanguageModel.generateObject({ - * prompt: - * "Extract event info: Tech Conference on March 15th in San Francisco", - * schema: EventSchema, - * objectName: "event" + * const FakeLanguageModel = Layer.effect( + * LanguageModel.LanguageModel, + * LanguageModel.make({ + * generateText: () => + * Effect.succeed([{ + * type: "text", + * text: '{"title":"Tech Conference","date":"March 15th","location":"San Francisco"}' + * }]), + * streamText: () => Stream.empty * }) - * - * console.log(response.value) - * // { title: "Tech Conference", date: "March 15th", location: "San Francisco" } - * - * return response.value - * }) + * ) + * + * const program = LanguageModel.generateObject({ + * prompt: "Extract event info: Tech Conference on March 15th in San Francisco", + * schema: EventSchema, + * objectName: "event" + * }).pipe( + * Effect.map((response) => response.value), + * Effect.provide(FakeLanguageModel) + * ) + * + * await Effect.runPromise(program) // => { title: "Tech Conference", date: "March 15th", location: "San Francisco" } * ``` * - * @category object generation + * @category generators * @since 4.0.0 */ export const generateObject = < ObjectEncoded extends Record, - StructuredOutputSchema extends Schema.Codec, + StructuredOutputSchema extends Schema.Encoder, Options extends NoExcessProperties< GenerateObjectOptions, Options @@ -1718,18 +1760,30 @@ export const generateObject = < * * **Example** (Streaming text deltas) * - * ```ts - * import { Console, Effect, Stream } from "effect" + * ```ts import.meta.vitest + * import { Effect, Layer, Stream } from "effect" * import { LanguageModel } from "effect/unstable/ai" * + * const FakeLanguageModel = Layer.effect( + * LanguageModel.LanguageModel, + * LanguageModel.make({ + * generateText: () => Effect.succeed([]), + * streamText: () => + * Stream.make( + * { type: "text-delta", id: "story", delta: "The explorer reached orbit." }, + * { type: "text-delta", id: "story", delta: " Earth glowed below." } + * ) + * }) + * ) + * * const program = LanguageModel.streamText({ * prompt: "Write a story about a space explorer" - * }).pipe(Stream.runForEach((part) => { - * if (part.type === "text-delta") { - * return Console.log(part.delta) - * } - * return Effect.void - * })) + * }).pipe( + * Stream.runFold(() => "", (text, part) => part.type === "text-delta" ? text + part.delta : text), + * Effect.provide(FakeLanguageModel) + * ) + * + * await Effect.runPromise(program) // => "The explorer reached orbit. Earth glowed below." * ``` * * @category text generation @@ -1954,7 +2008,7 @@ const isApprovalNeeded = Effect.fnUntraced(function*( const executeApprovedToolCalls = >( approvals: ReadonlyArray, toolkit: Toolkit.WithHandler, - concurrency: Concurrency | undefined + concurrency: Concurrency ): Effect.Effect< Array, Tool.HandlerError | AiError.AiError, @@ -1982,7 +2036,8 @@ const executeApprovedToolCalls = >( const resultStream = yield* toolkit.handle( toolCall.name, - toolCall.params as any + toolCall.params as any, + approval.toolCallId ) const terminalResult = yield* resultStream.pipe( @@ -2000,18 +2055,13 @@ const executeApprovedToolCalls = >( id: approval.toolCallId, name: toolCall.name, isFailure: terminalResult.isFailure, - result: terminalResult.encodedResult + result: terminalResult.encodedResult, + providerExecuted: false }) }) - return Effect.gen(function*() { - const resolveConcurrency = concurrency === "inherit" - ? yield* Effect.service(CurrentConcurrency) - : (concurrency ?? "unbounded") - - return yield* Effect.forEach(approvals, executeTool, { - concurrency: resolveConcurrency - }) + return Effect.forEach(approvals, executeTool, { + concurrency }) } @@ -2026,7 +2076,8 @@ const createDenialResults = ( id: denial.toolCallId, name: denial.toolCall.name, isFailure: true, - result: { type: "execution-denied", reason: denial.reason } + result: { type: "execution-denied", reason: denial.reason }, + providerExecuted: false }) ) } @@ -2050,7 +2101,7 @@ const resolveToolCalls = >( content: ReadonlyArray, toolkit: Toolkit.WithHandler, messages: ReadonlyArray, - concurrency: Concurrency | undefined + concurrency: Concurrency ): Stream.Stream< ToolResolutionResult, Tool.HandlerError | AiError.AiError, @@ -2098,7 +2149,7 @@ const resolveToolCalls = >( } if (approvedToolCallIds.has(toolCall.id)) { - return toolkit.handle(toolCall.name, toolCall.params as any).pipe( + return toolkit.handle(toolCall.name, toolCall.params as any, toolCall.id).pipe( Stream.unwrap, Stream.map( (result) => @@ -2124,7 +2175,7 @@ const resolveToolCalls = >( ) } - return toolkit.handle(toolCall.name, toolCall.params as any).pipe( + return toolkit.handle(toolCall.name, toolCall.params as any, toolCall.id).pipe( Stream.unwrap, Stream.map( (result) => @@ -2139,14 +2190,7 @@ const resolveToolCalls = >( }).pipe(Stream.unwrap) ) - const resolveConcurrency = concurrency === "inherit" - ? Effect.service(CurrentConcurrency) - : Effect.succeed(concurrency ?? "unbounded") - - return resolveConcurrency.pipe( - Effect.map((concurrency) => Stream.mergeAll(streams, { concurrency })), - Stream.unwrap - ) + return Stream.mergeAll(streams, { concurrency }) } // ============================================================================= diff --git a/repos/effect/packages/effect/src/unstable/ai/McpProtocol.ts b/repos/effect/packages/effect/src/unstable/ai/McpProtocol.ts new file mode 100644 index 0000000000..046f2aa480 --- /dev/null +++ b/repos/effect/packages/effect/src/unstable/ai/McpProtocol.ts @@ -0,0 +1,48 @@ +/** + * Defines the MCP protocol implementations that an `McpServer` can support. + * + * @since 4.0.0 + */ +import type * as RpcGroup from "../rpc/RpcGroup.ts" +import * as Internal from "./internal/mcpProtocol.ts" +import * as McpSchema from "./McpSchema.ts" + +/** + * The MCP 2025-06-18 protocol implementation. + * + * @category protocols + * @since 4.0.0 + */ +export const v2025_06_18: ProtocolAdapter = Internal.make({ + protocolVersion: "2025-06-18", + transport: { + acceptsJsonRpcBatches: false, + requiresVersionHeader: true + }, + clientRpcs: McpSchema.ClientRpcs, + clientNotificationRpcs: McpSchema.ClientNotificationRpcs, + serverRequestRpcs: McpSchema.ServerRequestRpcs, + serverNotificationRpcs: McpSchema.ServerNotificationRpcs +}) + +/** + * An implemented MCP protocol that can be supplied to `McpServer`. + * + * @category models + * @since 4.0.0 + */ +export type ProtocolAdapter = Internal.ProtocolAdapter< + "2025-06-18", + RpcGroup.Rpcs, + RpcGroup.Rpcs, + RpcGroup.Rpcs, + RpcGroup.Rpcs +> + +/** + * The MCP protocol versions implemented by this release. + * + * @category models + * @since 4.0.0 + */ +export type ProtocolVersion = ProtocolAdapter["protocolVersion"] diff --git a/repos/effect/packages/effect/src/unstable/ai/McpSchema.ts b/repos/effect/packages/effect/src/unstable/ai/McpSchema.ts index 34c3f42cb2..f88bcccbf8 100644 --- a/repos/effect/packages/effect/src/unstable/ai/McpSchema.ts +++ b/repos/effect/packages/effect/src/unstable/ai/McpSchema.ts @@ -23,6 +23,7 @@ import type * as RpcClient from "../rpc/RpcClient.ts" import type { RpcClientError } from "../rpc/RpcClientError.ts" import * as RpcGroup from "../rpc/RpcGroup.ts" import * as RpcMiddleware from "../rpc/RpcMiddleware.ts" +import type * as McpProtocol from "./McpProtocol.ts" /** * Schema type returned by `optionalWithDefault`. @@ -100,8 +101,8 @@ export const optional = ( */ export const RequestId: Schema.Union<[ typeof Schema.String, - typeof Schema.Number -]> = Schema.Union([Schema.String, Schema.Number]) + typeof Schema.Finite +]> = Schema.Union([Schema.String, Schema.Finite]) /** * Type represented by the JSON-RPC request identifier schema. @@ -120,8 +121,8 @@ export type RequestId = typeof RequestId.Type */ export const ProgressToken: Schema.Union<[ typeof Schema.String, - typeof Schema.Number -]> = Schema.Union([Schema.String, Schema.Number]) + typeof Schema.Finite +]> = Schema.Union([Schema.String, Schema.Finite]) /** * Type represented by the MCP progress token schema. @@ -299,7 +300,7 @@ export class Annotations extends Schema.Opaque()(Schema.Struct({ * effectively required, while 0 means "least important," and indicates that * the data is entirely optional. */ - priority: optional(Schema.Number.check(Schema.isBetween({ minimum: 0, maximum: 1 }))) + priority: optional(Schema.Finite.check(Schema.isBetween({ minimum: 0, maximum: 1 }))) })) {} /** @@ -449,7 +450,7 @@ export class McpErrorBase extends Schema.Class( /** * The error type that occurred. */ - code: Schema.Number, + code: Schema.Int, /** * A short description of the error. The message SHOULD be limited to a * concise single sentence. @@ -539,7 +540,7 @@ export const PARSE_ERROR_CODE = -32700 as const * @category errors * @since 4.0.0 */ -export class ParseError extends Schema.ErrorClass("effect/ai/McpSchema/ParseError")({ +export class ParseError extends Schema.Error("effect/ai/McpSchema/ParseError")({ ...McpErrorBase.fields, _tag: Schema.tag("ParseError"), code: Schema.tag(PARSE_ERROR_CODE) @@ -560,7 +561,7 @@ export class ParseError extends Schema.ErrorClass("effect/ai/McpSche * @category errors * @since 4.0.0 */ -export class InvalidRequest extends Schema.ErrorClass("effect/ai/McpSchema/InvalidRequest")({ +export class InvalidRequest extends Schema.Error("effect/ai/McpSchema/InvalidRequest")({ ...McpErrorBase.fields, _tag: Schema.tag("InvalidRequest"), code: Schema.tag(INVALID_REQUEST_ERROR_CODE) @@ -580,7 +581,7 @@ export class InvalidRequest extends Schema.ErrorClass("effect/ai * @category errors * @since 4.0.0 */ -export class MethodNotFound extends Schema.ErrorClass("effect/ai/McpSchema/MethodNotFound")({ +export class MethodNotFound extends Schema.Error("effect/ai/McpSchema/MethodNotFound")({ ...McpErrorBase.fields, _tag: Schema.tag("MethodNotFound"), code: Schema.tag(METHOD_NOT_FOUND_ERROR_CODE) @@ -601,7 +602,7 @@ export class MethodNotFound extends Schema.ErrorClass("effect/ai * @category errors * @since 4.0.0 */ -export class InvalidParams extends Schema.ErrorClass("effect/ai/McpSchema/InvalidParams")({ +export class InvalidParams extends Schema.Error("effect/ai/McpSchema/InvalidParams")({ ...McpErrorBase.fields, _tag: Schema.tag("InvalidParams"), code: Schema.tag(INVALID_PARAMS_ERROR_CODE) @@ -623,7 +624,7 @@ export class InvalidParams extends Schema.ErrorClass("effect/ai/M * @category errors * @since 4.0.0 */ -export class InternalError extends Schema.ErrorClass("effect/ai/McpSchema/InternalError")({ +export class InternalError extends Schema.Error("effect/ai/McpSchema/InternalError")({ ...McpErrorBase.fields, _tag: Schema.tag("InternalError"), code: Schema.tag(INTERNAL_ERROR_CODE) @@ -662,7 +663,7 @@ export const McpError = Schema.Union([ * * The receiver should respond promptly; otherwise the sender may disconnect. * - * @category ping + * @category protocols * @since 4.0.0 */ export class Ping extends Rpc.make("ping", { @@ -678,7 +679,7 @@ export class Ping extends Rpc.make("ping", { /** * Schema for the server's response to an initialize request from the client. * - * @category initialization + * @category schemas * @since 4.0.0 */ export class InitializeResult extends Schema.Opaque()(Schema.Struct({ @@ -705,7 +706,7 @@ export class InitializeResult extends Schema.Opaque()(Schema.S * Sent from the client to the server when it first connects, asking it to begin * initialization. * - * @category initialization + * @category protocols * @since 4.0.0 */ export class Initialize extends Rpc.make("initialize", { @@ -734,7 +735,7 @@ export class Initialize extends Rpc.make("initialize", { /** * Sent from the client to the server after initialization has finished. * - * @category initialization + * @category protocols * @since 4.0.0 */ export class InitializedNotification extends Rpc.make("notifications/initialized", { @@ -754,7 +755,7 @@ export class InitializedNotification extends Rpc.make("notifications/initialized * The payload identifies the request to cancel and may include a * human-readable reason. * - * @category cancellation + * @category protocols * @since 4.0.0 */ export class CancelledNotification extends Rpc.make("notifications/cancelled", { @@ -782,7 +783,7 @@ export class CancelledNotification extends Rpc.make("notifications/cancelled", { /** * Sent from either peer to report progress for a long-running request. * - * @category progress + * @category protocols * @since 4.0.0 */ export class ProgressNotification extends Rpc.make("notifications/progress", { @@ -797,11 +798,11 @@ export class ProgressNotification extends Rpc.make("notifications/progress", { * The progress thus far. This should increase every time progress is made, * even if the total is unknown. */ - progress: optional(Schema.Number), + progress: optional(Schema.Finite), /** * Total number of items to process (or total progress required), if known. */ - total: optional(Schema.Number), + total: optional(Schema.Finite), /** * An optional message describing the current progress. */ @@ -816,7 +817,7 @@ export class ProgressNotification extends Rpc.make("notifications/progress", { /** * Schema for a known resource that the server is capable of reading. * - * @category resources + * @category schemas * @since 4.0.0 */ export class Resource extends Schema.Class( @@ -855,7 +856,7 @@ export class Resource extends Schema.Class( * This can be used by Hosts to display file sizes and estimate context * window usage. */ - size: optional(Schema.Number), + size: optional(Schema.Int), /** * Optional additional metadata for the client. * @@ -868,7 +869,7 @@ export class Resource extends Schema.Class( /** * Schema for a template description of resources available on the server. * - * @category resources + * @category schemas * @since 4.0.0 */ export class ResourceTemplate extends Schema.Class( @@ -914,7 +915,7 @@ export class ResourceTemplate extends Schema.Class( /** * Schema for the contents of a specific resource or sub-resource. * - * @category resources + * @category schemas * @since 4.0.0 */ export class ResourceContents extends Schema.Opaque()(Schema.Struct({ @@ -935,7 +936,7 @@ export class ResourceContents extends Schema.Opaque()(Schema.S /** * Schema for text resource contents represented as a string. * - * @category resources + * @category schemas * @since 4.0.0 */ export class TextResourceContents extends Schema.Opaque()(Schema.Struct({ @@ -950,7 +951,7 @@ export class TextResourceContents extends Schema.Opaque()( /** * Schema for binary resource contents represented as a `Uint8Array`. * - * @category resources + * @category schemas * @since 4.0.0 */ export class BlobResourceContents extends Schema.Opaque()(Schema.Struct({ @@ -964,7 +965,7 @@ export class BlobResourceContents extends Schema.Opaque()( /** * Schema for the server's response to a resources/list request from the client. * - * @category resources + * @category schemas * @since 4.0.0 */ export class ListResourcesResult extends Schema.Class( @@ -977,7 +978,7 @@ export class ListResourcesResult extends Schema.Class( /** * Sent from the client to request a list of resources the server has. * - * @category resources + * @category protocols * @since 4.0.0 */ export class ListResources extends Rpc.make("resources/list", { @@ -990,7 +991,7 @@ export class ListResources extends Rpc.make("resources/list", { * Schema for the server's response to a resources/templates/list request from * the client. * - * @category resources + * @category schemas * @since 4.0.0 */ export class ListResourceTemplatesResult extends Schema.Class( @@ -1003,7 +1004,7 @@ export class ListResourceTemplatesResult extends Schema.Class()(Schema.Struct({ @@ -1026,7 +1027,7 @@ export class ReadResourceResult extends Schema.Opaque()(Sche /** * Sent from the client to the server, to read a specific resource URI. * - * @category resources + * @category protocols * @since 4.0.0 */ export class ReadResource extends Rpc.make("resources/read", { @@ -1053,7 +1054,7 @@ export class ReadResource extends Rpc.make("resources/read", { * * Servers may send this notification without a previous client subscription. * - * @category resources + * @category protocols * @since 4.0.0 */ export class ResourceListChangedNotification extends Rpc.make("notifications/resources/list_changed", { @@ -1064,10 +1065,11 @@ export class ResourceListChangedNotification extends Rpc.make("notifications/res * Sent from the client to request resources/updated notifications from the * server whenever a particular resource changes. * - * @category resources + * @category protocols * @since 4.0.0 */ export class Subscribe extends Rpc.make("resources/subscribe", { + success: Schema.Struct({}), error: McpError, payload: { ...RequestMeta.fields, @@ -1084,10 +1086,11 @@ export class Subscribe extends Rpc.make("resources/subscribe", { * notifications from the server. This should follow a previous * resources/subscribe request. * - * @category resources + * @category protocols * @since 4.0.0 */ export class Unsubscribe extends Rpc.make("resources/unsubscribe", { + success: Schema.Struct({}), error: McpError, payload: { ...RequestMeta.fields, @@ -1107,7 +1110,7 @@ export class Unsubscribe extends Rpc.make("resources/unsubscribe", { * The URI may identify a sub-resource of the resource that the client * originally subscribed to. * - * @category resources + * @category protocols * @since 4.0.0 */ export class ResourceUpdatedNotification extends Rpc.make("notifications/resources/updated", { @@ -1403,7 +1406,7 @@ export class PromptListChangedNotification extends Rpc.make("notifications/promp * Clients should never make tool use decisions based on ToolAnnotations * received from untrusted servers. * - * @category tools + * @category schemas * @since 4.0.0 */ export class ToolAnnotations extends Schema.Opaque()(Schema.Struct({ @@ -1449,7 +1452,7 @@ export class ToolAnnotations extends Schema.Opaque()(Schema.Str /** * Schema for the definition of a tool the client can call. * - * @category tools + * @category schemas * @since 4.0.0 */ export class Tool extends Schema.Class( @@ -1470,6 +1473,10 @@ export class Tool extends Schema.Class( * A JSON Schema object defining the expected parameters for the tool. */ inputSchema: Schema.Any, + /** + * An optional JSON Schema object defining the expected output of the tool. + */ + outputSchema: optional(Schema.Any), /** * Optional additional tool information. */ @@ -1486,7 +1493,7 @@ export class Tool extends Schema.Class( /** * Schema for the server's response to a tools/list request from the client. * - * @category tools + * @category schemas * @since 4.0.0 */ export class ListToolsResult extends Schema.Class( @@ -1499,7 +1506,7 @@ export class ListToolsResult extends Schema.Class( /** * Sent from the client to request a list of tools the server has. * - * @category tools + * @category protocols * @since 4.0.0 */ export class ListTools extends Rpc.make("tools/list", { @@ -1520,7 +1527,7 @@ export class ListTools extends Rpc.make("tools/list", { * indicating that the server does not support tool calls, or any other * exceptional conditions, should be reported as an MCP error response. * - * @category tools + * @category schemas * @since 4.0.0 */ export class CallToolResult extends Schema.Class("@effect/ai/McpSchema/CallToolResult")({ @@ -1546,7 +1553,7 @@ export class CallToolResult extends Schema.Class("@effect/ai/Mcp * @see {@link ListTools} for discovering available tools before calling one * @see {@link CallToolResult} for the successful tool-call result shape * - * @category tools + * @category protocols * @since 4.0.0 */ export class CallTool extends Rpc.make("tools/call", { @@ -1555,9 +1562,12 @@ export class CallTool extends Rpc.make("tools/call", { payload: { ...RequestMeta.fields, name: Schema.String, - arguments: Schema.Record( - Schema.String, - Schema.Any + arguments: optionalWithDefault( + Schema.Record( + Schema.String, + Schema.Any + ), + () => ({}) ) } }) {} @@ -1573,7 +1583,7 @@ export class CallTool extends Rpc.make("tools/call", { * * Servers may send this notification without a previous client subscription. * - * @category tools + * @category protocols * @since 4.0.0 */ export class ToolListChangedNotification extends Rpc.make("notifications/tools/list_changed", { @@ -1638,6 +1648,7 @@ export class SetLevel extends Rpc.make("logging/setLevel", { */ level: LoggingLevel }, + success: Schema.Struct({}), error: McpError }) {} @@ -1678,7 +1689,7 @@ export class LoggingMessageNotification extends Rpc.make("notifications/message" /** * Describes a message issued to or received from an LLM API. * - * @category sampling + * @category schemas * @since 4.0.0 */ export class SamplingMessage extends Schema.Opaque()(Schema.Struct({ @@ -1694,7 +1705,7 @@ export class SamplingMessage extends Schema.Opaque()(Schema.Str * Keys not declared here are currently left unspecified by the spec and are up * to the client to interpret. * - * @category sampling + * @category schemas * @since 4.0.0 */ export class ModelHint extends Schema.Opaque()(Schema.Struct({ @@ -1731,7 +1742,7 @@ export class ModelHint extends Schema.Opaque()(Schema.Struct({ * up to the client to decide how to interpret these preferences and how to * balance them against other considerations. * - * @category sampling + * @category schemas * @since 4.0.0 */ export class ModelPreferences extends Schema.Class( @@ -1752,19 +1763,19 @@ export class ModelPreferences extends Schema.Class( * is not important, while a value of 1 means cost is the most important * factor. */ - costPriority: optional(Schema.Number.check(Schema.isBetween({ minimum: 0, maximum: 1 }))), + costPriority: optional(Schema.Finite.check(Schema.isBetween({ minimum: 0, maximum: 1 }))), /** * How much to prioritize sampling speed (latency) when selecting a model. A * value of 0 means speed is not important, while a value of 1 means speed is * the most important factor. */ - speedPriority: optional(Schema.Number.check(Schema.isBetween({ minimum: 0, maximum: 1 }))), + speedPriority: optional(Schema.Finite.check(Schema.isBetween({ minimum: 0, maximum: 1 }))), /** * How much to prioritize intelligence and capabilities when selecting a * model. A value of 0 means intelligence is not important, while a value of 1 * means intelligence is the most important factor. */ - intelligencePriority: optional(Schema.Number.check(Schema.isBetween({ minimum: 0, maximum: 1 }))) + intelligencePriority: optional(Schema.Finite.check(Schema.isBetween({ minimum: 0, maximum: 1 }))) }) {} /** @@ -1779,12 +1790,13 @@ export class ModelPreferences extends Schema.Class( * The client should let the user inspect the sampled message before returning * it to the server. * - * @category sampling + * @category schemas * @since 4.0.0 */ export class CreateMessageResult extends Schema.Class( "@effect/ai/McpSchema/CreateMessageResult" )({ + ...SamplingMessage.fields, /** * The name of the model that generated the message. */ @@ -1808,7 +1820,7 @@ export class CreateMessageResult extends Schema.Class( * The client chooses the model and should ask the user to approve the sampling * request before it begins. * - * @category sampling + * @category protocols * @since 4.0.0 */ export class CreateMessage extends Rpc.make("sampling/createMessage", { @@ -1820,7 +1832,7 @@ export class CreateMessage extends Rpc.make("sampling/createMessage", { * The server's preferences for which model to select. The client MAY ignore * these preferences. */ - modelPreferences: optional(ModelPreferences), + modelPreferences: optional(Schema.Struct(ModelPreferences.fields)), /** * An optional system prompt the server wants to use for sampling. The * client MAY modify or omit this prompt. @@ -1831,18 +1843,18 @@ export class CreateMessage extends Rpc.make("sampling/createMessage", { * caller), to be attached to the prompt. The client MAY ignore this request. */ includeContext: optional(Schema.Literals(["none", "thisServer", "allServers"])), - temperature: optional(Schema.Number), + temperature: optional(Schema.Finite), /** * The maximum number of tokens to sample, as requested by the server. The * client MAY choose to sample fewer tokens than requested. */ - maxTokens: Schema.Number, + maxTokens: Schema.Int, stopSequences: optional(Schema.Array(Schema.String)), /** * Optional metadata to pass through to the LLM provider. The format of * this metadata is provider-specific. */ - metadata: Schema.Any + metadata: optional(Schema.Record(Schema.String, Schema.Unknown)) } }) {} @@ -1853,7 +1865,7 @@ export class CreateMessage extends Rpc.make("sampling/createMessage", { /** * Schema for a reference to a resource or resource template definition. * - * @category autocomplete + * @category schemas * @since 4.0.0 */ export class ResourceReference extends Schema.Opaque()(Schema.Struct({ @@ -1867,7 +1879,7 @@ export class ResourceReference extends Schema.Opaque()(Schema /** * Schema for a prompt reference used in autocomplete requests. * - * @category autocomplete + * @category schemas * @since 4.0.0 */ export class PromptReference extends Schema.Opaque()(Schema.Struct({ @@ -1882,7 +1894,7 @@ export class PromptReference extends Schema.Opaque()(Schema.Str /** * Schema for the server's response to a completion/complete request. * - * @category autocomplete + * @category schemas * @since 4.0.0 */ export class CompleteResult extends Schema.Opaque()(Schema.Struct({ @@ -1895,7 +1907,7 @@ export class CompleteResult extends Schema.Opaque()(Schema.Struc * The total number of completion options available. This can exceed the * number of values actually sent in the response. */ - total: optional(Schema.Number), + total: optional(Schema.Int), /** * Indicates whether there are additional completion options beyond those * provided in the current response, even if the exact total is unknown. @@ -1920,7 +1932,7 @@ export class CompleteResult extends Schema.Opaque()(Schema.Struc /** * Sent from the client to the server to ask for completion options. * - * @category autocomplete + * @category protocols * @since 4.0.0 */ export class Complete extends Rpc.make("completion/complete", { @@ -1966,7 +1978,7 @@ export class Complete extends Rpc.make("completion/complete", { /** * Represents a root directory or file that the server can operate on. * - * @category roots + * @category schemas * @since 4.0.0 */ export class Root extends Schema.Class( @@ -1993,7 +2005,7 @@ export class Root extends Schema.Class( * * Use to return the directories or files that an MCP server may operate on. * - * @category roots + * @category schemas * @since 4.0.0 */ export class ListRootsResult extends Schema.Class( @@ -2014,7 +2026,7 @@ export class ListRootsResult extends Schema.Class( * system structure or access specific locations that the client has permission * to read from. * - * @category roots + * @category protocols * @since 4.0.0 */ export class ListRoots extends Rpc.make("roots/list", { @@ -2034,7 +2046,7 @@ export class ListRoots extends Rpc.make("roots/list", { * * Send this when the client adds, removes, or modifies a root. * - * @category roots + * @category protocols * @since 4.0.0 */ export class RootsListChangedNotification extends Rpc.make("notifications/roots/list_changed", { @@ -2048,7 +2060,7 @@ export class RootsListChangedNotification extends Rpc.make("notifications/roots/ /** * Schema for an accepted client response to an elicitation request. * - * @category elicitation + * @category schemas * @since 4.0.0 */ export class ElicitAcceptResult extends Schema.Class( @@ -2072,7 +2084,7 @@ export class ElicitAcceptResult extends Schema.Class( /** * Schema for a declined or canceled client response to an elicitation request. * - * @category elicitation + * @category schemas * @since 4.0.0 */ export class ElicitDeclineResult extends Schema.Class( @@ -2091,7 +2103,7 @@ export class ElicitDeclineResult extends Schema.Class( /** * Schema for every client response to an elicitation request. * - * @category elicitation + * @category schemas * @since 4.0.0 */ export const ElicitResult = Schema.Union([ @@ -2108,7 +2120,7 @@ export const ElicitResult = Schema.Union([ * The client responds with accepted content, an explicit decline, or a * cancellation. * - * @category elicitation + * @category protocols * @since 4.0.0 */ export class Elicit extends Rpc.make("elicitation/create", { @@ -2137,16 +2149,14 @@ export class Elicit extends Rpc.make("elicitation/create", { * The error stores the original elicitation request and, when available, the * underlying cause. * - * @category elicitation + * @category schemas * @since 4.0.0 */ -export class ElicitationDeclined - extends Schema.ErrorClass("@effect/ai/McpSchema/ElicitationDeclined")({ - _tag: Schema.tag("ElicitationDeclined"), - request: Elicit.payloadSchema, - cause: optional(Schema.Defect()) - }) -{} +export class ElicitationDeclined extends Schema.Error("@effect/ai/McpSchema/ElicitationDeclined")({ + _tag: Schema.tag("ElicitationDeclined"), + request: Elicit.payloadSchema, + cause: optional(Schema.Defect()) +}) {} // ============================================================================= // McpServerClient @@ -2160,11 +2170,12 @@ export class ElicitationDeclined * It exposes the current client id, the client's initialize payload, and a * scoped RPC client for server-initiated requests back to that client. * - * @category client + * @category services * @since 4.0.0 */ export class McpServerClient extends Context.Service, RpcClientError>, @@ -2459,7 +2470,7 @@ const ParamSchemaTypeId = "~effect/ai/McpSchema/ParamSchema" * Returns `true` when a schema was created with `param` and therefore carries * a resource URI template parameter name. * - * @category parameters + * @category guards * @since 4.0.0 */ export function isParam(schema: Schema.Constraint): schema is Param { @@ -2519,7 +2530,7 @@ export function param( * Annotation to conditionally enable or disable tools based on client * information. * - * @category annotations + * @category services * @since 4.0.0 */ export class EnabledWhen diff --git a/repos/effect/packages/effect/src/unstable/ai/McpServer.ts b/repos/effect/packages/effect/src/unstable/ai/McpServer.ts index a3e90016f3..4256efd967 100644 --- a/repos/effect/packages/effect/src/unstable/ai/McpServer.ts +++ b/repos/effect/packages/effect/src/unstable/ai/McpServer.ts @@ -13,11 +13,14 @@ import * as Arr from "../../Array.ts" import * as Cause from "../../Cause.ts" import * as Context from "../../Context.ts" +import * as Data from "../../Data.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 * as LogLevel from "../../LogLevel.ts" import * as Option from "../../Option.ts" +import * as Predicate from "../../Predicate.ts" import * as Queue from "../../Queue.ts" import * as RcMap from "../../RcMap.ts" import { CurrentLogLevel } from "../../References.ts" @@ -39,29 +42,37 @@ import type * as RpcGroup from "../rpc/RpcGroup.ts" import * as RpcMessage from "../rpc/RpcMessage.ts" import * as RpcSerialization from "../rpc/RpcSerialization.ts" import * as RpcServer from "../rpc/RpcServer.ts" +import * as AiError from "./AiError.ts" +import * as McpProtocolRegistry from "./internal/mcpProtocolRegistry.ts" +import type * as McpProtocol from "./McpProtocol.ts" import { CallToolResult, - ClientNotificationRpcs, + CancelledNotification, ClientRpcs, - CompleteResult, Elicit, ElicitationDeclined, EnabledWhen, GetPromptResult, InternalError, + INVALID_REQUEST_ERROR_CODE, InvalidParams, + InvalidRequest, isParam, ListPromptsResult, ListResourcesResult, ListResourceTemplatesResult, ListToolsResult, + LoggingMessageNotification, + McpErrorBase, McpServerClient, McpServerClientMiddleware, + MethodNotFound, + ParseError, Prompt, Resource, ResourceTemplate, + ResourceUpdatedNotification, ServerNotificationRpcs, - ServerRequestRpcs, TextContent, Tool as McpTool } from "./McpSchema.ts" @@ -69,8 +80,10 @@ import type { CallTool, ClientCapabilities, Complete, + CompleteResult, GetPrompt, Initialize, + LoggingLevel, Param, PromptArgument, PromptMessage, @@ -80,6 +93,8 @@ import type { import * as Tool from "./Tool.ts" import type * as Toolkit from "./Toolkit.ts" +type CompletionContext = typeof Complete.payloadSchema.Type["context"] + /** * Service that stores and serves an MCP server's registered tools, resources, * prompts, completions, and outgoing notifications. @@ -89,7 +104,7 @@ import type * as Toolkit from "./Toolkit.ts" * Handlers use this service to register capabilities and resolve incoming MCP * requests. * - * @category server + * @category services * @since 4.0.0 */ export class McpServer extends Context.Service - readonly handle: (payload: any) => Effect.Effect + readonly handle: (payload: any) => Effect.Effect }) => Effect.Effect readonly callTool: ( requests: typeof CallTool.payloadSchema.Type @@ -129,17 +144,27 @@ export class McpServer extends Context.Service readonly routerPath: string - readonly completions: Record Effect.Effect> + readonly completions: Record< + string, + ( + input: string, + context: CompletionContext + ) => Effect.Effect + > readonly handle: ( uri: string, params: Array - ) => Effect.Effect + ) => Effect.Effect< + typeof ReadResourceResult.Type, + InvalidParams | InternalError, + McpServerClient + > } ) => Effect.Effect readonly findResource: ( uri: string - ) => Effect.Effect + ) => Effect.Effect readonly prompts: ReadonlyArray<{ readonly prompt: Prompt @@ -150,7 +175,10 @@ export class McpServer extends Context.Service readonly completions: Record< string, - (input: string) => Effect.Effect + ( + input: string, + context: CompletionContext + ) => Effect.Effect > readonly handle: ( params: Record @@ -162,7 +190,7 @@ export class McpServer extends Context.Service Effect.Effect + ) => Effect.Effect }>()("effect/ai/McpServer") { /** * Builds an MCP server service from registered tools, prompts, resources, and completions. @@ -176,7 +204,11 @@ export class McpServer extends Context.Service - ) => Effect.Effect + ) => Effect.Effect< + typeof ReadResourceResult.Type, + InternalError | InvalidParams, + McpServerClient + > } | { readonly _tag: "Resource" readonly effect: Effect.Effect @@ -186,7 +218,10 @@ export class McpServer extends Context.Service }>() - const toolMap = new Map Effect.Effect>() + const toolMap = new Map< + string, + (payload: any) => Effect.Effect + >() const resources: Array<{ readonly resource: Resource readonly annotations: Context.Context @@ -205,7 +240,10 @@ export class McpServer extends Context.Service() const completionsMap = new Map< string, - (input: string) => Effect.Effect + ( + input: string, + context: CompletionContext + ) => Effect.Effect >() const notificationsQueue = yield* Queue.make>() const listChangedHandles = new Map() @@ -258,7 +296,9 @@ export class McpServer extends Context.Service Effect.fail(new InternalError({ message: "Internal error" }))) + ) }), get resources() { return resources @@ -285,7 +325,7 @@ export class McpServer extends Context.Service { const match = matcher.find(uri) if (!match) { - return Effect.succeed({ contents: [] }) + return Effect.fail(new McpErrorBase({ code: -32002, message: `Resource '${uri}' not found` })) } else if (match.handler._tag === "Resource") { return match.handler.effect } @@ -320,7 +360,19 @@ export class McpServer extends Context.Service = Layer.effect(McpServer)(McpServer.make) as any } -const LATEST_PROTOCOL_VERSION = "2025-06-18" -const SUPPORTED_PROTOCOL_VERSIONS = [ - LATEST_PROTOCOL_VERSION, - "2025-03-26", - "2024-11-05", - "2024-10-07" -] -const mcpSessionIdHeader = "mcp-session-id" -const mcpProtocolVersionHeader = "mcp-protocol-version" +const MCP_SESSION_ID_HEADER = "mcp-session-id" +const MCP_PROTOCOL_VERSION_HEADER = "mcp-protocol-version" +const MCP_INVALID_BATCH_METHOD = "invalid/json-rpc-batch" +const decodeCancelledNotification = Schema.decodeUnknownEffect( + Schema.toCodecJson(CancelledNotification.payloadSchema) +) + +const requestKey = (requestId: string | number): string => `${typeof requestId}:${requestId}` + +type SessionLogLevel = + | { + readonly _tag: "Effect" + readonly level: LogLevel.LogLevel + } + | { + readonly _tag: "Mcp" + readonly level: LoggingLevel + } + +interface Session { + readonly initializePayload: typeof Initialize.payloadSchema.Type + readonly protocol: McpProtocol.ProtocolAdapter + readonly resourceSubscriptions: Set | undefined + logLevel: SessionLogLevel +} + +interface Sessions { + readonly bySessionId: Map + readonly byClientId: Map +} + +class McpClientKey extends Data.Class<{ + readonly clientId: number + readonly protocolVersion: string +}> {} + +class McpProtocolState extends Context.Service +}>()("effect/ai/McpServer/McpProtocolState") {} + +const makeMcpProtocolState = Effect.fnUntraced(function*( + protocols: Arr.NonEmptyReadonlyArray +) { + return McpProtocolState.of({ + sessions: { + bySessionId: new Map(), + byClientId: new Map() + }, + protocolRegistry: yield* McpProtocolRegistry.make(protocols) + }) +}) + +const layerMcpProtocolState = ( + protocols: Arr.NonEmptyReadonlyArray +): Layer.Layer => + Layer.effect(McpProtocolState)(makeMcpProtocolState(protocols)) /** * Runs an MCP server over the current `RpcServer.Protocol`. @@ -352,31 +452,54 @@ const mcpProtocolVersionHeader = "mcp-protocol-version" * tools, resources, and prompts, and forwards queued server notifications to * initialized clients. * - * @category constructors + * @category running * @since 4.0.0 */ export const run: (options: { readonly name: string readonly version: string + readonly protocols: Arr.NonEmptyReadonlyArray readonly extensions?: Record<`${string}/${string}`, unknown> | undefined }) => Effect.Effect< never, - never, + Cause.IllegalArgumentError, McpServer | RpcServer.Protocol > = Effect.fnUntraced(function*(options: { readonly name: string readonly version: string + readonly protocols: Arr.NonEmptyReadonlyArray + readonly extensions?: Record<`${string}/${string}`, unknown> | undefined }) { + const protocolStateOption = yield* Effect.serviceOption(McpProtocolState) + const protocolState = Option.isSome(protocolStateOption) + ? protocolStateOption.value + : yield* makeMcpProtocolState(options.protocols) + return yield* runWithProtocolState(options, protocolState) +}) + +const runWithProtocolState = Effect.fnUntraced(function*(options: { + readonly name: string + readonly version: string + readonly extensions?: Record<`${string}/${string}`, unknown> | undefined +}, protocolState: McpProtocolState["Service"]) { + const serverScope = yield* Effect.scope + const protocolRegistry = protocolState.protocolRegistry const protocol = yield* RpcServer.Protocol const server = yield* McpServer const isHttp = Option.isSome(yield* Effect.serviceOption(HttpRouter.HttpRouter)) - const clientSessions = new Map() - const handlers = yield* Layer.build(layerHandlers(options, { clientSessions })) + const sessions = protocolState.sessions + const clientProtocols = new Map() + const activeRequests = new Map>() + const handlers = yield* Layer.build(layerHandlers(options, { + sessions, + protocolRegistry + })) const clients = yield* RcMap.make({ - lookup: Effect.fnUntraced(function*(clientId: number) { + lookup: Effect.fnUntraced(function*(key: McpClientKey) { + const selectedProtocol = protocolRegistry.select(key.protocolVersion) let write!: (message: RpcMessage.FromServerEncoded) => Effect.Effect - const client = yield* RpcClient.make(ServerRequestRpcs, { + const client = yield* RpcClient.make(selectedProtocol.serverRequestRpcs, { spanPrefix: "McpServer/Client" }).pipe( Effect.provideServiceEffect( @@ -387,7 +510,7 @@ export const run: (options: { return { send(id, request, _transferables) { cid = id - return protocol.send(clientId, { + return protocol.send(key.clientId, { ...request, headers: undefined, traceId: undefined, @@ -408,84 +531,253 @@ export const run: (options: { idleTimeToLive: 10000 }) - const clientMiddleware = McpServerClientMiddleware.of((effect, { client, headers, rpc }) => { - const initializePayload = getInitializedClient(clientSessions, client.id, headers) - const isInitialize = rpc._tag === "initialize" - if (!isInitialize && !initializePayload) { + const clientMiddleware = McpServerClientMiddleware.of((effect, { client, headers, payload, rpc }) => { + const session = getClientSession(sessions, client.id, headers) + const isInitialize = rpc._tag.endsWith("/initialize") + if (!isInitialize && !session) { const fiber = Fiber.getCurrent()! const httpRequest = Context.getOrUndefined(fiber.context, HttpServerRequest.HttpServerRequest) if (httpRequest) { appendPreResponseHandlerUnsafe( httpRequest, - () => Effect.succeed(HttpServerResponse.empty({ status: 404 })) + () => + Effect.succeed( + HttpServerResponse.empty({ + status: headers[MCP_SESSION_ID_HEADER] === undefined ? 400 : 404 + }) + ) ) } return Effect.die(new Error(`Mcp-Session-Id does not exist`)) } - return Effect.provideService( - effect, - McpServerClient, - McpServerClient.of({ - clientId: client.id, - initializePayload: initializePayload!, - getClient: RcMap.get(clients, client.id).pipe( - Effect.map(({ client }) => client) - ) - }) + const selectedProtocol = session?.protocol ?? protocolForInternalTag(protocolRegistry, rpc._tag) + return effect.pipe( + Effect.provideService( + McpServerClient, + McpServerClient.of({ + clientId: client.id, + protocolVersion: selectedProtocol.protocolVersion, + initializePayload: session?.initializePayload ?? payload as typeof Initialize.payloadSchema.Type, + getClient: RcMap.get( + clients, + new McpClientKey({ + clientId: client.id, + protocolVersion: selectedProtocol.protocolVersion + }) + ).pipe( + Effect.map(({ client }) => client) + ) + }) + ), + Effect.provideService(CurrentLogLevel, effectLogLevel(session?.logLevel)) ) }) const patchedProtocol = RpcServer.Protocol.of({ ...protocol, + send: (clientId, response) => { + if (response._tag === "Exit") { + const requests = activeRequests.get(clientId) + const key = requestKey(response.requestId) + const cancelled = requests?.get(key) + if (requests !== undefined && requests.delete(key) && requests.size === 0) { + activeRequests.delete(clientId) + } + if (cancelled === true) { + return Effect.void + } + } + return protocol.send(clientId, response) + }, run: (f) => protocol.run((clientId, request_) => { - const request = request_ as any as + const fiber = Fiber.getCurrent()! + const request = request_ as unknown as | RpcMessage.FromServerEncoded | RpcMessage.FromClientEncoded + const httpRequest = isHttp + ? Context.getOrUndefined(fiber.context, HttpServerRequest.HttpServerRequest) + : undefined + if (httpRequest !== undefined && request._tag !== "Eof") { + appendPreResponseHandlerUnsafe(httpRequest, (_, response) => + Effect.succeed( + response.status === 200 && + response.body._tag === "Uint8Array" && + response.body.contentLength === 0 + ? HttpServerResponse.empty({ + headers: Headers.remove(response.headers, "content-type"), + status: 202 + }) + : response + )) + } switch (request._tag) { case "Request": { + const headers = isHttp + ? Context.getUnsafe( + Fiber.getCurrent()!.context, + HttpServerRequest.HttpServerRequest + ).headers + : Headers.fromInput(request.headers) + const session = getClientSession(sessions, clientId, headers) + const selectedProtocol = session?.protocol ?? + (request.tag === "initialize" + ? protocolRegistry.select(getOfferedProtocolVersion(request.payload)) + : protocolRegistry.protocols[0]) + clientProtocols.set(clientId, selectedProtocol) + if (request.tag === MCP_INVALID_BATCH_METHOD) { + return protocol.send(clientId, { + _tag: "Exit", + requestId: request.id, + exit: { + _tag: "Failure", + cause: [{ + _tag: "Fail", + error: new InvalidRequest({ message: "JSON-RPC batches are not supported" }) + }] + } + }) + } if (isHttp) { const fiber = Fiber.getCurrent()! const httpRequest = Context.getUnsafe(fiber.context, HttpServerRequest.HttpServerRequest) - const client = getInitializedClient(clientSessions, clientId, httpRequest.headers) - if (client) { + if (session) { appendPreResponseHandlerUnsafe(httpRequest, (_, res) => Effect.succeed( - HttpServerResponse.setHeader(res, mcpProtocolVersionHeader, client.protocolVersion) + HttpServerResponse.setHeader( + res, + MCP_PROTOCOL_VERSION_HEADER, + session.protocol.protocolVersion + ) )) } } - const rpc = ClientNotificationRpcs.requests.get(request.tag) - if (rpc) { + const routedRequest = protocolRegistry.routeClientRequest(selectedProtocol, request) + const rpc = protocolRegistry.clientRpcs.requests.get(routedRequest.tag) + if (rpc && selectedProtocol.clientNotificationRpcs.requests.has(request.tag)) { + if (!session) { + if (httpRequest) { + appendPreResponseHandlerUnsafe( + httpRequest, + () => + Effect.succeed( + HttpServerResponse.empty({ + status: headers[MCP_SESSION_ID_HEADER] === undefined ? 400 : 404 + }) + ) + ) + } + return Effect.void + } if (request.tag === "notifications/cancelled") { - return f(clientId, { - _tag: "Interrupt", - requestId: String((request.payload as any).requestId) - }) + return decodeCancelledNotification(request.payload).pipe( + Effect.flatMap(({ requestId }) => { + const key = requestKey(requestId) + const requests = activeRequests.get(clientId) + if (requests?.has(key) !== true) { + return Effect.void + } + requests.set(key, true) + return f(clientId, { + _tag: "Interrupt", + requestId: RpcMessage.RequestId(requestId) + }) + }), + Effect.catchCause(() => Effect.void) + ) } - const handler = handlers.mapUnsafe.get(request.tag) as Rpc.Handler - return handler - ? handler.handler(request.payload, { - rpc, - requestId: RpcMessage.RequestId(request.id), - client: new Rpc.ServerClient(clientId), - headers: Headers.fromInput(request.headers) - }) as any as Effect.Effect - : Effect.void + return selectedProtocol.payloadCodecs(rpc).decode(request.payload).pipe( + Effect.flatMap((payload) => { + if ( + request.tag === "notifications/roots/list_changed" && + session.initializePayload.capabilities.roots?.listChanged === true + ) { + if (httpRequest !== undefined) { + return Effect.void + } + return RcMap.get( + clients, + new McpClientKey({ + clientId, + protocolVersion: selectedProtocol.protocolVersion + }) + ).pipe( + Effect.flatMap(({ client }) => client["roots/list"](undefined)), + Effect.scoped, + Effect.ignoreCause, + Effect.forkIn(serverScope), + Effect.asVoid + ) + } + const handler = handlers.mapUnsafe.get(rpc.key) as Rpc.Handler | undefined + return handler + ? handler.handler(payload, { + rpc, + requestId: RpcMessage.RequestId(request.id), + client: new Rpc.ServerClient(clientId), + headers + }) as any as Effect.Effect + : Effect.void + }), + Effect.catchCause(() => Effect.void) + ) } - return f(clientId, request) + if (!rpc) { + if (request.isNotification) { + return Effect.void + } + return protocol.send(clientId, { + _tag: "Exit", + requestId: request.id, + exit: { + _tag: "Failure", + cause: [{ _tag: "Fail", error: new MethodNotFound({ message: `Method not found: ${request.tag}` }) }] + } + }) + } + return selectedProtocol.payloadCodecs(rpc).decode(request.payload).pipe( + Effect.matchEffect({ + onSuccess: () => { + if (request.isNotification !== true) { + const requests = activeRequests.get(clientId) ?? new Map() + requests.set(requestKey(request.id), false) + activeRequests.set(clientId, requests) + } + return f(clientId, routedRequest) + }, + onFailure: () => + request.isNotification + ? Effect.void + : protocol.send(clientId, { + _tag: "Exit", + requestId: request.id, + exit: { + _tag: "Failure", + cause: [{ _tag: "Fail", error: new InvalidParams({ message: "Invalid method parameters" }) }] + } + }) + }) + ) } case "Ping": case "Ack": case "Interrupt": + return f(clientId, request) case "Eof": + activeRequests.delete(clientId) return f(clientId, request) case "Pong": case "Exit": case "Chunk": case "ClientProtocolError": case "Defect": - return RcMap.get(clients, clientId).pipe( + return RcMap.get( + clients, + new McpClientKey({ + clientId, + protocolVersion: getProtocolForClient(clientProtocols, clientId, protocolRegistry).protocolVersion + }) + ).pipe( Effect.flatMap(({ write }) => write(request)), Effect.scoped ) @@ -493,23 +785,52 @@ export const run: (options: { }) }) - const encodeNotification = Schema.encodeUnknownEffect( - Schema.Union(Array.from(ServerNotificationRpcs.requests.values(), (rpc) => rpc.payloadSchema)) - ) yield* Queue.take(server.notificationsQueue).pipe( Effect.flatMap(Effect.fnUntraced(function*(request) { - const encoded = yield* encodeNotification(request.payload) - const message: RpcMessage.RequestEncoded = { - _tag: "Request", - tag: request.tag, - payload: encoded - } as any const clientIds = yield* patchedProtocol.clientIds + for (const clientId of clientProtocols.keys()) { + if (!clientIds.has(clientId)) { + clientProtocols.delete(clientId) + sessions.byClientId.delete(clientId) + } + } for (const clientId of server.initializedClients.keys()) { if (!clientIds.has(clientId)) { server.initializedClients.delete(clientId) continue } + const selectedProtocol = clientProtocols.get(clientId) + if (!selectedProtocol) { + continue + } + const rpc = selectedProtocol.serverNotificationRpcs.requests.get(request.tag) + if (!rpc) { + continue + } + if (request.tag === "notifications/message") { + const { level } = yield* Schema.decodeUnknownEffect( + LoggingMessageNotification.payloadSchema + )(request.payload) + if (!isMcpLogLevelEnabled(level, sessions.byClientId.get(clientId)?.logLevel)) { + continue + } + } + if (request.tag === "notifications/resources/updated") { + const { uri } = yield* Schema.decodeUnknownEffect( + ResourceUpdatedNotification.payloadSchema + )(request.payload) + if (sessions.byClientId.get(clientId)?.resourceSubscriptions?.has(uri) !== true) { + continue + } + } + const encoded = yield* selectedProtocol.payloadCodecs(rpc).encode(request.payload) + // TODO: Extend RpcServer.Protocol's outbound message contract with server-originated + // notifications so MCP does not need to treat this notification as an RPC response. + const message: RpcMessage.RequestEncoded = { + _tag: "Request", + tag: request.tag, + payload: encoded + } as any yield* patchedProtocol.send(clientId, message as any) } })), @@ -518,7 +839,7 @@ export const run: (options: { Effect.forkScoped ) - return yield* RpcServer.make(ClientRpcs, { + return yield* RpcServer.make(protocolRegistry.clientRpcs, { spanPrefix: "McpServer", disableFatalDefects: true }).pipe( @@ -560,65 +881,145 @@ export const run: (options: { export const layer = (options: { readonly name: string readonly version: string + readonly protocols: Arr.NonEmptyReadonlyArray readonly extensions?: Record<`${string}/${string}`, unknown> | undefined -}): Layer.Layer => - Layer.effectDiscard(Effect.forkScoped(run(options))).pipe( +}): Layer.Layer => + layerWithProtocolState(options).pipe( + Layer.provide(layerMcpProtocolState(options.protocols)) + ) + +const layerWithProtocolState = (options: { + readonly name: string + readonly version: string + readonly extensions?: Record<`${string}/${string}`, unknown> | undefined +}): Layer.Layer => + Layer.effectDiscard( + Effect.gen(function*() { + const protocolState = yield* McpProtocolState + yield* Effect.forkScoped(runWithProtocolState(options, protocolState)) + }) + ).pipe( Layer.provideMerge(McpServer.layer) ) +const StdioInitializeRequest = Schema.Struct({ + method: Schema.Literal("initialize"), + params: Schema.Struct({ + protocolVersion: Schema.String + }) +}) + +const StdioInvalidBatchExit = Schema.Struct({ + _tag: Schema.Literal("Exit"), + requestId: Schema.Null, + exit: Schema.Struct({ + cause: Schema.Unknown + }) +}) + +const decodeStdioInitializeRequest = Schema.decodeUnknownOption(StdioInitializeRequest) +const decodeStdioInvalidBatchExit = Schema.decodeUnknownOption(StdioInvalidBatchExit) + +const makeStdioSerialization = ( + protocols: Arr.NonEmptyReadonlyArray +): RpcSerialization.RpcSerialization["Service"] => + RpcSerialization.RpcSerialization.of({ + contentType: "application/json-rpc", + includesFraming: true, + makeUnsafe: () => { + const framing = RpcSerialization.ndjson.makeUnsafe() + const jsonRpc = RpcSerialization.jsonRpc().makeUnsafe() + const protocolsByVersion = new Map( + protocols.map((protocol) => [protocol.protocolVersion, protocol]) + ) + let selectedProtocol = protocols[0] + return { + decode: (data) => { + const frames = framing.decode(data) + const messages: Array = [] + for (const frame of frames) { + const entries = Array.isArray(frame) ? frame : [frame] + const initialize = Arr.findFirst(entries, (entry) => decodeStdioInitializeRequest(entry)) + selectedProtocol = Option.match(initialize, { + onNone: () => selectedProtocol, + onSome: ({ params }) => protocolsByVersion.get(params.protocolVersion) ?? protocols[0] + }) + if (Array.isArray(frame) && !selectedProtocol.transport.acceptsJsonRpcBatches) { + messages.push({ + _tag: "Request", + id: null, + tag: MCP_INVALID_BATCH_METHOD, + payload: null, + headers: [] + }) + } else { + messages.push(...jsonRpc.decode(JSON.stringify(frame))) + } + } + return messages + }, + encode: (response) => { + const invalidBatchExit = decodeStdioInvalidBatchExit(response) + if (Option.isSome(invalidBatchExit)) { + return framing.encode({ + jsonrpc: "2.0", + id: null, + error: { + _tag: "Cause", + code: INVALID_REQUEST_ERROR_CODE, + message: "JSON-RPC batches are not supported", + data: invalidBatchExit.value.exit.cause + } + }) + } + const encoded = jsonRpc.encode(response) + return encoded === undefined ? undefined : `${encoded}\n` + } + } + } + }) + /** * Runs the McpServer, using stdio for input and output. * - * **Example** (Running an MCP server over stdio) + * **Example** (Configuring an MCP server over stdio) * - * ```ts - * import { Effect, Layer, Logger, Schema } from "effect" - * import { NodeRuntime, NodeStdio } from "@effect/platform-node" - * import { McpSchema, McpServer } from "effect/unstable/ai" + * ```ts import.meta.vitest + * import { Effect, Layer, Schema } from "effect" + * import { McpProtocol, McpSchema, McpServer } from "effect/unstable/ai" * * const idParam = McpSchema.param("id", Schema.Number) * - * // Define a resource template for a README file * const ReadmeTemplate = McpServer.resource`file://readme/${idParam}`({ * name: "README Template", - * // You can add auto-completion for the ID parameter * completion: { - * id: (_) => Effect.succeed([1, 2, 3, 4, 5]) + * id: () => Effect.succeed([1, 2, 3]) * }, - * content: Effect.fn(function*(_uri, id) { - * return `# MCP Server Demo - ID: ${id}` - * }) + * content: (_uri, id) => Effect.succeed(`# MCP Server Demo - ID: ${id}`) * }) * - * // Define a test prompt with parameters * const TestPrompt = McpServer.prompt({ * name: "Test Prompt", - * description: "A test prompt to demonstrate MCP server capabilities", + * description: "Looks up flight booking details", * parameters: { * flightNumber: Schema.String * }, * completion: { - * flightNumber: () => Effect.succeed(["FL123", "FL456", "FL789"]) + * flightNumber: () => Effect.succeed(["FL123", "FL456"]) * }, * content: ({ flightNumber }) => * Effect.succeed(`Get the booking details for flight number: ${flightNumber}`) * }) * - * // Merge all the resources and prompts into a single server layer - * const ServerLayer = Layer.mergeAll( - * ReadmeTemplate, - * TestPrompt - * ).pipe( - * // Provide the MCP server implementation + * const ServerLayer = Layer.mergeAll(ReadmeTemplate, TestPrompt).pipe( * Layer.provide(McpServer.layerStdio({ * name: "Demo Server", * version: "1.0.0", - * })), - * Layer.provide(NodeStdio.layer), - * Layer.provide(Layer.succeed(Logger.LogToStderr)(true)) + * protocols: [McpProtocol.v2025_06_18] + * })) * ) * - * Layer.launch(ServerLayer).pipe(NodeRuntime.runMain) + * Layer.isLayer(ServerLayer) // => true * ``` * * @category layers @@ -627,16 +1028,18 @@ export const layer = (options: { export const layerStdio = (options: { readonly name: string readonly version: string + readonly protocols: Arr.NonEmptyReadonlyArray readonly extensions?: Record<`${string}/${string}`, unknown> | undefined -}): Layer.Layer => +}): Layer.Layer => layer(options).pipe( Layer.provide(RpcServer.layerProtocolStdio), - Layer.provide(RpcSerialization.layerNdJsonRpc()) + Layer.provide( + Layer.succeed(RpcSerialization.RpcSerialization)(makeStdioSerialization(options.protocols)) + ) ) /** - * Registers an HTTP POST JSON-RPC route at `options.path` on the current - * `HttpRouter`. + * Registers a Streamable HTTP MCP endpoint at `options.path`. * * **When to use** * @@ -644,8 +1047,9 @@ export const layerStdio = (options: { * * **Details** * - * This layer composes `layer(options)`, `RpcServer.layerProtocolHttp(options)`, - * and `RpcSerialization.layerJsonRpc()`. + * POST serves JSON-RPC and accepted notification-only requests return `202`. + * Unsupported protocol versions return `400`; methods without MCP handlers + * return `405`. Browser Origins are rejected unless listed in `allowedOrigins`. * * @see {@link layerStdio} for exposing the server over stdio * @see {@link layer} for the base MCP server layer without a transport protocol @@ -657,17 +1061,201 @@ export const layerHttp = (options: { readonly name: string readonly version: string readonly path: HttpRouter.PathInput + readonly protocols: Arr.NonEmptyReadonlyArray + readonly allowedOrigins?: ReadonlyArray | undefined readonly extensions?: Record<`${string}/${string}`, unknown> | undefined -}): Layer.Layer => - layer(options).pipe( - Layer.provide(RpcServer.layerProtocolHttp(options)), +}): Layer.Layer => { + const protocolState = layerMcpProtocolState(options.protocols) + const methodNotAllowedResponse = HttpServerResponse.empty({ + status: 405, + headers: { allow: "POST" } + }) + const methodNotAllowed = (request: HttpServerRequest.HttpServerRequest) => + Effect.succeed( + request.headers.origin !== undefined && + !options.allowedOrigins?.includes(request.headers.origin) + ? HttpServerResponse.empty({ status: 403 }) + : methodNotAllowedResponse + ) + const routes = Layer.mergeAll( + HttpRouter.add("GET", options.path, methodNotAllowed), + HttpRouter.add("PUT", options.path, methodNotAllowed), + HttpRouter.add("PATCH", options.path, methodNotAllowed), + HttpRouter.add("DELETE", options.path, methodNotAllowed), + HttpRouter.add("OPTIONS", options.path, methodNotAllowed) + ) + return Layer.merge(layerWithProtocolState(options), routes).pipe( + Layer.provide(layerMcpProtocolHttp(options)), + Layer.provide(protocolState), Layer.provide(RpcSerialization.layerJsonRpc()) ) +} + +const layerMcpProtocolHttp = (options: { + readonly path: HttpRouter.PathInput + readonly allowedOrigins?: ReadonlyArray | undefined +}): Layer.Layer< + RpcServer.Protocol, + never, + McpProtocolState | RpcSerialization.RpcSerialization | HttpRouter.HttpRouter +> => + Layer.effect(RpcServer.Protocol)(Effect.gen(function*() { + const state = yield* McpProtocolState + const { httpEffect, protocol } = yield* RpcServer.makeProtocolWithHttpEffect + const router = yield* HttpRouter.HttpRouter + yield* router.add("POST", options.path, (request) => { + if ( + request.headers.origin !== undefined && + !options.allowedOrigins?.includes(request.headers.origin) + ) { + return Effect.succeed(HttpServerResponse.empty({ status: 403 })) + } + const contentType = request.headers["content-type"]?.split(";", 1)[0]?.trim().toLowerCase() + if (contentType !== "application/json") { + return Effect.succeed(HttpServerResponse.empty({ status: 415 })) + } + const accepted = new Set() + for (const entry of request.headers.accept?.split(",") ?? []) { + const [mediaType, ...parameters] = entry.split(";").map((part) => part.trim().toLowerCase()) + let quality = 1 + for (const parameter of parameters) { + const [name, value] = parameter.split("=", 2).map((part) => part.trim()) + if (name === "q") { + quality = value === undefined ? Number.NaN : Number(value) + } + } + if (mediaType !== undefined && quality > 0 && quality <= 1) { + accepted.add(mediaType) + } + } + if (!accepted.has("application/json") || !accepted.has("text/event-stream")) { + return Effect.succeed(HttpServerResponse.empty({ status: 406 })) + } + const protocolVersion = request.headers[MCP_PROTOCOL_VERSION_HEADER] + if ( + protocolVersion !== undefined && + !state.protocolRegistry.protocols.some((protocol) => protocol.protocolVersion === protocolVersion) + ) { + return Effect.succeed(HttpServerResponse.empty({ status: 400 })) + } + return request.text.pipe( + Effect.matchEffect({ + onFailure: () => + Effect.succeed( + HttpServerResponse.jsonUnsafe({ + jsonrpc: "2.0", + id: null, + error: new ParseError({ + message: "Parse error" + }) + }) + ), + onSuccess: (body) => { + return Effect.match(Schema.decodeUnknownEffect(Schema.UnknownFromJsonString)(body), { + onFailure: () => ({ + _tag: "Error" as const, + id: null, + error: new ParseError({ message: "Parse error" }) + }), + onSuccess: (input) => { + if (Array.isArray(input)) { + const sessionId = request.headers[MCP_SESSION_ID_HEADER] + const session = sessionId === undefined ? undefined : state.sessions.bySessionId.get(sessionId) + let selectedProtocol = session?.protocol ?? state.protocolRegistry.protocols[0] + for (const entry of input) { + if ( + Predicate.hasProperty(entry, "method") && + entry.method === "initialize" && + Predicate.hasProperty(entry, "params") && + Predicate.hasProperty(entry.params, "protocolVersion") && + typeof entry.params.protocolVersion === "string" + ) { + selectedProtocol = state.protocolRegistry.select(entry.params.protocolVersion) + break + } + } + if (!selectedProtocol.transport.acceptsJsonRpcBatches) { + return { _tag: "HttpError" as const, status: 400 } + } + } + const hasId = Predicate.hasProperty(input, "id") + const id = hasId && (typeof input.id === "string" || typeof input.id === "number") + ? input.id + : null + const isJsonRpc = Predicate.hasProperty(input, "jsonrpc") && input.jsonrpc === "2.0" + const hasValidRequestId = hasId === false || typeof input.id === "string" || + typeof input.id === "number" + const isRequest = isJsonRpc && hasValidRequestId && + Predicate.hasProperty(input, "method") && typeof input.method === "string" + const hasValidResponseId = hasId && + (typeof input.id === "string" || typeof input.id === "number" || input.id === null) + const hasResult = Predicate.hasProperty(input, "result") + const hasError = Predicate.hasProperty(input, "error") + const isResponse = isJsonRpc && hasValidResponseId && hasResult !== hasError + const isInitialize = isRequest && input.method === "initialize" + const sessionId = request.headers[MCP_SESSION_ID_HEADER] + const session = sessionId === undefined ? undefined : state.sessions.bySessionId.get(sessionId) + if (isInitialize && sessionId !== undefined) { + return { + _tag: "HttpError" as const, + status: session === undefined ? 404 : 400 + } + } + if (!isInitialize && isRequest && session === undefined) { + return { + _tag: "HttpError" as const, + status: sessionId === undefined ? 400 : 404 + } + } + if ( + session !== undefined && + session.protocol.transport.requiresVersionHeader && + protocolVersion !== session.protocol.protocolVersion + ) { + return { _tag: "HttpError" as const, status: 400 } + } + return isRequest || isResponse + ? { _tag: "Success" as const } + : { + _tag: "Error" as const, + id, + error: new InvalidRequest({ message: "Invalid Request" }) + } + } + }).pipe( + Effect.flatMap((decoded) => + decoded._tag === "HttpError" + ? Effect.succeed(HttpServerResponse.empty({ status: decoded.status })) + : decoded._tag === "Error" + ? Effect.succeed( + HttpServerResponse.jsonUnsafe({ + jsonrpc: "2.0", + id: decoded.id, + error: decoded.error + }) + ) + : httpEffect + ) + ) + } + }) + ) + }) + return protocol + })) + +const INTERNAL_TOOL_ERROR_MESSAGE = "Tool execution failed due to an internal server error." + +const toolErrorResult = (message: string): CallToolResult => + new CallToolResult({ + isError: true, + content: [{ type: "text", text: message }] + }) /** * Registers a `Toolkit` with the `McpServer`. * - * @category tools + * @category handlers * @since 4.0.0 */ export const registerToolkit: >( @@ -689,10 +1277,13 @@ export const registerToolkit: >( for (const tool of Object.values(built.tools)) { const annotations = tool.annotations const toolMeta = Context.getOrUndefined(annotations, Tool.Meta) + const isDeclaredFailure = Schema.is(tool.failureSchema) + const outputSchema = Tool.getJsonSchemaFromSchema(tool.successSchema) const mcpTool = new McpTool({ name: tool.name, description: Tool.getDescription(tool), inputSchema: Tool.getJsonSchema(tool), + ...(outputSchema.type === "object" ? { outputSchema } : {}), annotations: { ...(Context.getOption(tool.annotations, Tool.Title).pipe( Option.map((title) => ({ title })), @@ -709,32 +1300,41 @@ export const registerToolkit: >( tool: mcpTool, annotations, handle(payload) { - return built.handle(tool.name as any, payload).pipe( + return built.handle(tool.name as keyof Tools, payload).pipe( Stream.unwrap, Stream.run(Sink.last()), Effect.flatMap(Effect.fromOption), - Effect.provideContext(services as Context.Context), - Effect.matchCause({ - onFailure: (cause) => - new CallToolResult({ - isError: true, - content: [{ - type: "text", - text: Cause.pretty(cause) - }] - }), - onSuccess: (result: any) => - new CallToolResult({ - isError: false, - structuredContent: typeof result.encodedResult === "object" ? result.encodedResult : undefined, - content: [{ - type: "text", - text: JSON.stringify(result.encodedResult) - }] - }) + Effect.provideContext( + services as Context.Context> + ), + Effect.map((result) => + new CallToolResult({ + isError: false, + structuredContent: typeof result.encodedResult === "object" ? result.encodedResult : undefined, + content: result.encodedResult === undefined ? [] : [{ + type: "text", + text: JSON.stringify(result.encodedResult) + }] + }) + ), + Effect.tapCause(Effect.logError), + Effect.catch((error) => { + if (AiError.isAiError(error)) { + const reason = (error as AiError.AiError).reason + return reason._tag === "ToolParameterValidationError" + ? Effect.fail(new InvalidParams({ message: reason.message })) + : Effect.succeed(toolErrorResult(INTERNAL_TOOL_ERROR_MESSAGE)) + } + if (isDeclaredFailure(error)) { + const message = error instanceof Error + ? error.message + : INTERNAL_TOOL_ERROR_MESSAGE + return Effect.succeed(toolErrorResult(message)) + } + return Effect.succeed(toolErrorResult(INTERNAL_TOOL_ERROR_MESSAGE)) }), - Effect.tapCause(Effect.log) - ) as any + Effect.catchDefect(() => Effect.succeed(toolErrorResult(INTERNAL_TOOL_ERROR_MESSAGE))) + ) } }) } @@ -743,7 +1343,7 @@ export const registerToolkit: >( /** * Registers an `AiToolkit` with the `McpServer`. * - * @category tools + * @category layers * @since 4.0.0 */ export const toolkit = >( @@ -767,7 +1367,11 @@ export const toolkit = >( export type ValidateCompletions = & Completions & { - readonly [K in keyof Completions]: K extends Keys ? (input: string) => any : never + readonly [K in keyof Completions]: K extends Keys ? ( + input: string, + context: CompletionContext + ) => any + : never } /** @@ -786,7 +1390,10 @@ export type ResourceCompletions readonly [ K in Extract as Schemas[K] extends Param ? Id : `param${K}` - ]: (input: string) => Effect.Effect, any, any> + ]: ( + input: string, + context: CompletionContext + ) => Effect.Effect, any, any> } /** @@ -800,7 +1407,7 @@ export type ResourceCompletions * * @see {@link resource} for the layer-based resource registration wrapper * - * @category resources + * @category handlers * @since 4.0.0 */ export const registerResource: { @@ -893,7 +1500,15 @@ export const registerResource: { readonly mimeType?: string | undefined readonly audience?: ReadonlyArray<"user" | "assistant"> | undefined readonly priority?: number | undefined - readonly completion?: Record Effect.Effect> | undefined + readonly completion?: + | Record< + string, + ( + input: string, + context: CompletionContext + ) => Effect.Effect + > + | undefined readonly content: (uri: string, ...params: Array) => Effect.Effect< typeof ReadResourceResult.Type | string | Uint8Array, E, @@ -909,11 +1524,20 @@ export const registerResource: { uriTemplate: uriPath, annotations: options! }) - const completions: Record Effect.Effect> = {} + const completions: Record< + string, + ( + input: string, + context: CompletionContext + ) => Effect.Effect + > = Object.create(null) for (const [param, handle] of Object.entries(options.completion ?? {})) { const encodeArray = Schema.encodeUnknownEffect(Schema.Array(params[param])) - const handler = (input: string) => - handle(input).pipe( + const handler = ( + input: string, + context: CompletionContext + ) => + handle(input, context).pipe( Effect.flatMap(encodeArray), Effect.map((values) => ({ completion: { @@ -962,7 +1586,7 @@ export const registerResource: { * * @see {@link registerResource} for the Effect-level resource registration API * - * @category resources + * @category layers * @since 4.0.0 */ export const resource: { @@ -1035,7 +1659,7 @@ export const resource: { * * @see {@link prompt} for the layer-based prompt registration wrapper * - * @category prompts + * @category handlers * @since 4.0.0 */ export const registerPrompt = < @@ -1043,7 +1667,10 @@ export const registerPrompt = < R, Params extends Schema.Struct.Fields = {}, const Completions extends { - readonly [K in keyof Params]?: (input: string) => Effect.Effect, any, any> + readonly [K in keyof Params]?: ( + input: string, + context: CompletionContext + ) => Effect.Effect, any, any> } = {} >( options: { @@ -1072,18 +1699,30 @@ export const registerPrompt = < const decode = options.parameters ? Schema.decodeEffect(Schema.Struct(props)) : () => Effect.succeed({} as Params) - const completion: Record Effect.Effect> = options.completion ?? {} + const completion: Record< + string, + ( + input: string, + context: CompletionContext + ) => Effect.Effect + > = options.completion ?? {} return Effect.gen(function*() { const registry = yield* McpServer const services = yield* Effect.context, McpServerClient>>() const completions: Record< string, - (input: string) => Effect.Effect - > = {} + ( + input: string, + context: CompletionContext + ) => Effect.Effect + > = Object.create(null) for (const [param, handle] of Object.entries(completion)) { const encodeArray = Schema.encodeEffect(Schema.Array(props[param])) - const handler = (input: string) => - handle(input).pipe( + const handler = ( + input: string, + context: CompletionContext + ) => + handle(input, context).pipe( Effect.flatMap(encodeArray), Effect.map((values) => ({ completion: { @@ -1107,20 +1746,23 @@ export const registerPrompt = < handle: (params) => decode(params).pipe( Effect.mapError((error) => new InvalidParams({ message: error.message })), - Effect.flatMap((params) => options.content(params as any)), - Effect.map((messages) => { - messages = typeof messages === "string" ? - [{ - role: "user", - content: TextContent.make({ text: messages }) - }] : - messages - return new GetPromptResult({ messages, description: prompt.description }) - }), - Effect.catchCause((cause) => { - const prettyError = Cause.prettyErrors(cause)[0] - return Effect.fail(new InternalError({ message: prettyError.message })) - }), + Effect.flatMap((params) => + options.content(params as any).pipe( + Effect.map((messages) => { + messages = typeof messages === "string" ? + [{ + role: "user", + content: TextContent.make({ text: messages }) + }] : + messages + return new GetPromptResult({ messages, description: prompt.description }) + }), + Effect.catchCause((cause) => { + const prettyError = Cause.prettyErrors(cause)[0] + return Effect.fail(new InternalError({ message: prettyError.message })) + }) + ) + ), Effect.provideContext(services as Context.Context) ) }) @@ -1142,7 +1784,7 @@ export const registerPrompt = < * * @see {@link registerPrompt} for the Effect-level prompt registration API * - * @category prompts + * @category layers * @since 4.0.0 */ export const prompt = < @@ -1150,7 +1792,10 @@ export const prompt = < R, Params extends Schema.Struct.Fields = {}, const Completions extends { - readonly [K in keyof Params]?: (input: string) => Effect.Effect, any, any> + readonly [K in keyof Params]?: ( + input: string, + context: CompletionContext + ) => Effect.Effect, any, any> } = {} >( options: { @@ -1177,7 +1822,7 @@ export const prompt = < * Accepted content is decoded with the supplied schema, declined requests fail * with `ElicitationDeclined`, and canceled requests interrupt the effect. * - * @category elicitation + * @category accessors * @since 4.0.0 */ export const elicit: , unknown>>(options: { @@ -1214,7 +1859,7 @@ export const elicit: /** * Accesses the current client's capabilities. * - * @category capabilities + * @category accessors * @since 4.0.0 */ export const clientCapabilities: Effect.Effect< @@ -1244,7 +1889,7 @@ const makeUriMatcher = () => { const compileUriTemplate = (segments: TemplateStringsArray, ...schemas: ReadonlyArray) => { let routerPath = segments[0].replace(":", "::") let uriPath = segments[0] - const params: Record = {} + const params: Record = Object.create(null) let pathSchema = Schema.Tuple([]) as Schema.Top if (schemas.length > 0) { const arr: Array = [] @@ -1274,141 +1919,190 @@ const layerHandlers = (serverInfo: { readonly version: string readonly extensions?: Record<`${string}/${string}`, unknown> | undefined }, options: { - readonly clientSessions: Map + readonly sessions: Sessions + readonly protocolRegistry: McpProtocolRegistry.ProtocolRegistry }) => - ClientRpcs.toLayer( + Layer.effectContext( Effect.gen(function*() { const server = yield* McpServer - let currentLogLevel = yield* CurrentLogLevel - - return ClientRpcs.of({ - // Requests - ping: () => Effect.succeed({}), - initialize(params, { client }) { - const requestedVersion = SUPPORTED_PROTOCOL_VERSIONS.includes(params.protocolVersion) - ? params.protocolVersion - : LATEST_PROTOCOL_VERSION - if (requestedVersion !== params.protocolVersion) { - params = { - ...params, - protocolVersion: requestedVersion + const currentLogLevel = yield* CurrentLogLevel + const contextMap = new Map() + + for (const protocol of options.protocolRegistry.protocols) { + const selectedProtocol = protocol + const wireHandlers = ClientRpcs.of({ + // Requests + ping: () => Effect.succeed({}), + initialize(params, { client }) { + const capabilities: Types.DeepMutable = { + completions: {}, + logging: {} } - } - const capabilities: Types.DeepMutable = { - completions: {} - } - if (server.tools.length > 0) { - capabilities.tools = { listChanged: true } - } - if (server.resources.length > 0 || server.resourceTemplates.length > 0) { - capabilities.resources = { - listChanged: true, - subscribe: false + if (server.tools.length > 0) { + capabilities.tools = { listChanged: true } } - } - if (server.prompts.length > 0) { - capabilities.prompts = { listChanged: true } - } - if (serverInfo.extensions) { - capabilities.extensions = serverInfo.extensions as any - } - return Effect.withFiber((fiber) => { - const httpRequest = Context.getOrUndefined(fiber.context, HttpServerRequest.HttpServerRequest) - if (httpRequest) { - const sessionId = crypto.randomUUID() - options.clientSessions.set(sessionId, params) - appendPreResponseHandlerUnsafe(httpRequest, (_req, res) => - Effect.succeed(HttpServerResponse.setHeaders(res, { - [mcpSessionIdHeader]: sessionId, - [mcpProtocolVersionHeader]: requestedVersion - }))) - } else { - options.clientSessions.set(String(client.id), params) + if (server.resources.length > 0 || server.resourceTemplates.length > 0) { + capabilities.resources = { + listChanged: true, + subscribe: true + } } - return Effect.succeed({ - capabilities, - serverInfo, - protocolVersion: requestedVersion - }) - }) - }, - "completion/complete": (r) => - server.completion(r).pipe( - Effect.provideService(CurrentLogLevel, currentLogLevel) - ), - "logging/setLevel": ({ level }) => - Effect.sync(() => { - switch (level) { - case "notice": - case "info": - currentLogLevel = "Info" - break - case "error": - currentLogLevel = "Error" - break - case "debug": - currentLogLevel = "Debug" - break - case "warning": - currentLogLevel = "Warn" - break - case "critical": - case "alert": - case "emergency": - currentLogLevel = "Fatal" - break + if (server.prompts.length > 0) { + capabilities.prompts = { listChanged: true } } - }), - "prompts/get": (r) => - server.getPromptResult(r).pipe( - Effect.provideService(CurrentLogLevel, currentLogLevel) - ), - "prompts/list": (_, { client, headers }) => - Effect.sync(() => { - const initialized = getInitializedClient(options.clientSessions, client.id, headers) - return new ListPromptsResult({ prompts: filterByClient(initialized, server.prompts, "prompt") }) - }), - "resources/list": (_, { client, headers }) => - Effect.sync(() => { - const initialized = getInitializedClient(options.clientSessions, client.id, headers) - return new ListResourcesResult({ resources: filterByClient(initialized, server.resources, "resource") }) - }), - "resources/read": ({ uri }) => - server.findResource(uri).pipe( - Effect.provideService(CurrentLogLevel, currentLogLevel) - ), - "resources/subscribe": () => - InternalError.notImplemented, - "resources/unsubscribe": () => - InternalError.notImplemented, - "resources/templates/list": (_, { client, headers }) => - Effect.sync(() => { - const initialized = getInitializedClient(options.clientSessions, client.id, headers) - return new ListResourceTemplatesResult({ - resourceTemplates: filterByClient(initialized, server.resourceTemplates, "template") - }) - }), - "tools/call": (r) => - server.callTool(r).pipe( - Effect.provideService(CurrentLogLevel, currentLogLevel) - ), - "tools/list": (_, { client, headers }) => - Effect.sync(() => { - const initialized = getInitializedClient(options.clientSessions, client.id, headers) - return new ListToolsResult({ - tools: filterByClient(initialized, server.tools, "tool") + if (serverInfo.extensions) { + capabilities.extensions = serverInfo.extensions as any + } + return Effect.withFiber((fiber) => { + const httpRequest = Context.getOrUndefined(fiber.context, HttpServerRequest.HttpServerRequest) + if (httpRequest !== undefined && capabilities.resources !== undefined) { + capabilities.resources.subscribe = false + } + const session: Session = { + initializePayload: params, + protocol: selectedProtocol, + resourceSubscriptions: capabilities.resources?.subscribe === true ? new Set() : undefined, + logLevel: { _tag: "Effect", level: currentLogLevel } + } + if (httpRequest) { + const sessionId = crypto.randomUUID() + options.sessions.bySessionId.set(sessionId, session) + appendPreResponseHandlerUnsafe(httpRequest, (_req, res) => + Effect.succeed(HttpServerResponse.setHeaders(res, { + [MCP_SESSION_ID_HEADER]: sessionId, + [MCP_PROTOCOL_VERSION_HEADER]: selectedProtocol.protocolVersion + }))) + } else { + options.sessions.byClientId.set(client.id, session) + } + return Effect.succeed({ + capabilities, + serverInfo, + protocolVersion: selectedProtocol.protocolVersion + }) }) - }), + }, + "completion/complete": (r) => + server.completion(r), + "logging/setLevel": ({ level }, { client, headers }) => + Effect.sync(() => { + const session = getClientSession(options.sessions, client.id, headers) + if (session) { + session.logLevel = { _tag: "Mcp", level } + } + return {} + }), + "prompts/get": (r) => + server.getPromptResult(r), + "prompts/list": (_, { client, headers }) => + Effect.sync(() => { + const initialized = getClientSession(options.sessions, client.id, headers)?.initializePayload + return new ListPromptsResult({ prompts: filterByClient(initialized, server.prompts, "prompt") }) + }), + "resources/list": (_, { client, headers }) => + Effect.sync(() => { + const initialized = getClientSession(options.sessions, client.id, headers)?.initializePayload + return new ListResourcesResult({ resources: filterByClient(initialized, server.resources, "resource") }) + }), + "resources/read": ({ uri }) => server.findResource(uri), + "resources/subscribe": ({ uri }, { client, headers }) => + Effect.gen(function*() { + const subscriptions = getClientSession( + options.sessions, + client.id, + headers + )?.resourceSubscriptions + if (subscriptions === undefined) { + return yield* new MethodNotFound({ + message: "Resource subscriptions are not supported" + }) + } + subscriptions.add(uri) + return {} + }), + "resources/unsubscribe": ({ uri }, { client, headers }) => + Effect.gen(function*() { + const subscriptions = getClientSession( + options.sessions, + client.id, + headers + )?.resourceSubscriptions + if (subscriptions === undefined) { + return yield* new MethodNotFound({ + message: "Resource subscriptions are not supported" + }) + } + subscriptions.delete(uri) + return {} + }), + "resources/templates/list": (_, { client, headers }) => + Effect.sync(() => { + const initialized = getClientSession(options.sessions, client.id, headers)?.initializePayload + return new ListResourceTemplatesResult({ + resourceTemplates: filterByClient(initialized, server.resourceTemplates, "template") + }) + }), + "tools/call": (r) => server.callTool(r), + "tools/list": (_, { client, headers }) => + Effect.sync(() => { + const initialized = getClientSession(options.sessions, client.id, headers)?.initializePayload + return new ListToolsResult({ + tools: filterByClient(initialized, server.tools, "tool") + }) + }), - // Notifications - "notifications/cancelled": (_) => Effect.void, - "notifications/initialized": (_) => Effect.void, - "notifications/progress": (_) => Effect.void, - "notifications/roots/list_changed": (_) => Effect.void - }) + // Notifications + "notifications/cancelled": (_) => Effect.void, + "notifications/initialized": (_, { client, headers }) => + Effect.sync(() => { + server.initializedClients.add(client.id) + const session = getClientSession(options.sessions, client.id, headers) + if (session) { + options.sessions.byClientId.set(client.id, session) + } + }), + "notifications/progress": (_) => Effect.void, + "notifications/roots/list_changed": (_) => Effect.void + }) + yield* addProtocolHandlers( + options.protocolRegistry, + selectedProtocol, + selectedProtocol.clientRpcs, + wireHandlers, + contextMap + ) + } + return Context.makeUnsafe(contextMap) }) ) +const addProtocolHandlers = Effect.fnUntraced(function*< + ClientRpcs extends Rpc.Any +>( + registry: McpProtocolRegistry.ProtocolRegistry, + protocol: McpProtocol.ProtocolAdapter, + clientRpcs: RpcGroup.RpcGroup, + handlers: RpcGroup.HandlersFrom, + contextMap: Map +) { + const handlerContext = yield* clientRpcs.toHandlers(handlers) + for (const rpcDefinition of clientRpcs.requests.values()) { + const routed = registry.routeClientRequest(protocol, { + _tag: "Request", + id: 0, + tag: rpcDefinition._tag, + payload: undefined, + headers: [] + }) + const namespacedRpc = registry.clientRpcs.requests.get(routed.tag) + const handler = handlerContext.mapUnsafe.get(rpcDefinition.key) + if (namespacedRpc === undefined || handler === undefined) { + return yield* Effect.die(`MCP handler registration invariant failed for ${routed.tag}`) + } + contextMap.set(namespacedRpc.key, handler) + } +}) + const resolveResourceContent = ( uri: string, content: typeof ReadResourceResult.Type | string | Uint8Array @@ -1455,14 +2149,74 @@ const filterByClient = < return out } -const getInitializedClient = ( - sessions: Map, +const getClientSession = ( + sessions: Sessions, clientId: number, headers: Headers.Headers ) => { - const sessionId = headers[mcpSessionIdHeader] + const sessionId = headers[MCP_SESSION_ID_HEADER] if (sessionId === undefined) { - return sessions.get(String(clientId)) + return sessions.byClientId.get(clientId) } - return sessions.get(sessionId) + return sessions.bySessionId.get(sessionId) +} + +const mcpLogLevels: Record = { + debug: { effect: "Debug", order: 0 }, + info: { effect: "Info", order: 1 }, + notice: { effect: "Info", order: 2 }, + warning: { effect: "Warn", order: 3 }, + error: { effect: "Error", order: 4 }, + critical: { effect: "Fatal", order: 5 }, + alert: { effect: "Fatal", order: 6 }, + emergency: { effect: "Fatal", order: 7 } } + +const effectLogLevel = (logLevel: SessionLogLevel | undefined): LogLevel.LogLevel => + logLevel?._tag === "Mcp" ? mcpLogLevels[logLevel.level].effect : logLevel?.level ?? "Info" + +const isMcpLogLevelEnabled = ( + level: LoggingLevel, + minimum: SessionLogLevel | undefined +): boolean => + minimum?._tag === "Mcp" + ? mcpLogLevels[level].order >= mcpLogLevels[minimum.level].order + : LogLevel.isGreaterThanOrEqualTo(mcpLogLevels[level].effect, minimum?.level ?? "Info") + +const getOfferedProtocolVersion = (payload: unknown): string => + typeof payload === "object" && + payload !== null && + "protocolVersion" in payload && + typeof payload.protocolVersion === "string" + ? payload.protocolVersion + : "" + +const protocolForInternalTag = ( + registry: McpProtocolRegistry.ProtocolRegistry, + tag: string +): McpProtocol.ProtocolAdapter => { + for (const protocol of registry.protocols) { + const routed = registry.routeClientRequest(protocol, { + _tag: "Request", + id: 0, + tag: "", + payload: undefined, + headers: [] + }) + if (tag.startsWith(routed.tag)) { + return protocol + } + } + return registry.protocols[0] +} + +const getProtocolForClient = ( + clientProtocols: Map, + clientId: number, + registry: McpProtocolRegistry.ProtocolRegistry +): McpProtocol.ProtocolAdapter => + clientProtocols.get(clientId) ?? + registry.protocols[0] diff --git a/repos/effect/packages/effect/src/unstable/ai/Model.ts b/repos/effect/packages/effect/src/unstable/ai/Model.ts index 6e78123c6e..fb440e7621 100644 --- a/repos/effect/packages/effect/src/unstable/ai/Model.ts +++ b/repos/effect/packages/effect/src/unstable/ai/Model.ts @@ -84,7 +84,6 @@ export class ModelName extends Context.Service()( ) {} const Proto = { - ...PipeInspectableProto, [TypeId]: TypeId, ["~effect/Layer"]: { _ROut: identity, @@ -97,6 +96,7 @@ const Proto = { Effect.succeed(Layer.provide(self, Layer.succeedContext(context))) ) }, + ...PipeInspectableProto, toJSON(this: Model): unknown { return { _id: "effect/ai/Model", @@ -110,29 +110,18 @@ const Proto = { * * **Example** (Providing model metadata) * - * ```ts - * import { Effect } from "effect" - * import type { Layer } from "effect" - * import { LanguageModel, Model } from "effect/unstable/ai" - * - * declare const bedrockLayer: Layer.Layer + * ```ts import.meta.vitest + * import { Effect, Layer } from "effect" + * import { Model } from "effect/unstable/ai" * - * // Model automatically provides ProviderName and ModelName services - * const checkProviderAndGenerate = Effect.gen(function*() { + * const model = Model.make("amazon-bedrock", "claude-3-5-haiku", Layer.empty) + * const program = Effect.gen(function*() { * const provider = yield* Model.ProviderName * const modelName = yield* Model.ModelName + * return { provider, modelName } + * }).pipe(Effect.provide(model)) * - * console.log(`Generating with: ${provider}/${modelName}`) - * - * return yield* LanguageModel.generateText({ - * prompt: `Hello from ${provider}!` - * }) - * }) - * - * const program = checkProviderAndGenerate.pipe( - * Effect.provide(Model.make("amazon-bedrock", "claude-3-5-haiku", bedrockLayer)) - * ) - * // Will log: "Generating with: amazon-bedrock/claude-3-5-haiku" + * await Effect.runPromise(program) // => { provider: "amazon-bedrock", modelName: "claude-3-5-haiku" } * ``` * * @category constructors diff --git a/repos/effect/packages/effect/src/unstable/ai/OpenAiStructuredOutput.ts b/repos/effect/packages/effect/src/unstable/ai/OpenAiStructuredOutput.ts index 54defd5d98..522b0aa886 100644 --- a/repos/effect/packages/effect/src/unstable/ai/OpenAiStructuredOutput.ts +++ b/repos/effect/packages/effect/src/unstable/ai/OpenAiStructuredOutput.ts @@ -4,21 +4,15 @@ * OpenAI structured output accepts only a subset of JSON Schema. This module * converts an Effect `Schema.Codec` into a provider-compatible JSON Schema and * a matching codec for decoding the model response back into the original - * application type. When possible, unsupported schema shapes are rewritten into - * supported ones; schema kinds that cannot be represented safely fail during - * conversion. + * application type. Unsupported constraints can be omitted from the provider + * schema and remain enforced by the returned codec. * * @since 4.0.0 */ -import * as Arr from "../../Array.ts" import * as JsonSchema from "../../JsonSchema.ts" -import * as Option from "../../Option.ts" -import * as Predicate from "../../Predicate.ts" import * as Rec from "../../Record.ts" import * as Schema from "../../Schema.ts" -import * as SchemaAST from "../../SchemaAST.ts" -import * as SchemaTransformation from "../../SchemaTransformation.ts" -import * as Tool from "./Tool.ts" +import * as InternalStructuredOutput from "./internal/structured-output.ts" /** * Converts a `Schema.Codec` to OpenAI structured-output JSON Schema and a @@ -27,27 +21,38 @@ import * as Tool from "./Tool.ts" * **When to use** * * Use when you send Effect Schema-backed structured output requests to OpenAI - * and need provider-compatible JSON Schema without losing the decoded - * application type. + * standard models and need provider-compatible JSON Schema without losing the + * decoded application type. * * **Details** * * Returns the JSON Schema to include in the request and the codec to use when - * decoding the model response. If the input schema already fits OpenAI's - * supported JSON Schema subset, the original codec is returned unchanged. + * decoding the model response. The codec remains authoritative: the provider + * JSON Schema can be a lossy, less restrictive representation when OpenAI + * cannot express an Effect Schema constraint. Conversion throws when the + * resulting root is not an object or contains `anyOf`. * * **Gotchas** * * - Some schemas use a provider-safe encoded shape: tuples become objects with - * numeric string keys, records become arrays of `[key, value]` pairs, and - * optional properties become required nullable properties. + * numeric string keys, objects with index signatures become arrays of + * `[key, value]` pairs, and optional properties become required nullable + * properties. * - `oneOf` unions are emitted as `anyOf` unions. - * - Regex patterns from multiple filters are merged into one `pattern` because - * OpenAI structured output does not support `allOf`. - * - Unsupported schema kinds throw during conversion instead of producing a - * lossy schema. + * - Compatible regex patterns are merged because OpenAI structured output does + * not support `allOf`. + * - The root JSON Schema must be an object and cannot use `anyOf`. + * - Constraints inside `allOf` are retained only when they have an explicit, + * semantics-preserving normalization rule. + * - Structural constraints inside `allOf`, such as `properties`, `required`, + * `additionalProperties`, and `items`, are omitted instead of being merged + * with a different meaning. + * - Unsupported constraints are removed from the provider schema and are still + * checked while decoding with the returned codec. + * - Compatibility targets standard OpenAI models. Fine-tuned models support a + * smaller JSON Schema subset. * - * @category Codec Transformation + * @category transforming * @since 4.0.0 */ export function toCodecOpenAI( @@ -56,421 +61,251 @@ export function toCodecOpenAI( codec: Schema.ConstraintCodec jsonSchema: JsonSchema.JsonSchema } { - const to = schema.ast - const from = recurOpenAI(SchemaAST.toEncoded(to)) - const codec = from === to - ? schema - : Schema.make(SchemaAST.decodeTo(from, to, SchemaTransformation.passthrough())) - const document = JsonSchema.resolveTopLevel$ref(Schema.toJsonSchemaDocument(codec)) + const codec = InternalStructuredOutput.toCodec(schema) + const document = JsonSchema.resolveTopLevel$ref( + Schema.toJsonSchemaDocument(codec, { generateDescriptions: true }) + ) const jsonSchema = rewriteOpenAI(document.schema) + if (jsonSchema.type !== "object" || jsonSchema.anyOf !== undefined) { + throw new Error( + `OpenAiStructuredOutput: Root JSON Schema must have type "object" and must not use "anyOf"` + ) + } if (Object.keys(document.definitions).length > 0) { jsonSchema.$defs = Rec.map(document.definitions, rewriteOpenAI) } return { codec, jsonSchema } } -/** - * Post-processes the JSON schema produced by `Schema.toJsonSchemaDocument`, - * recursively flattening `allOf` arrays by merging each member's keys into - * the parent object. This is necessary because OpenAI structured output does - * not support `allOf`. - */ function rewriteOpenAI(schema: JsonSchema.JsonSchema): JsonSchema.JsonSchema { - const out: JsonSchema.JsonSchema = {} - for (const [k, v] of Object.entries(schema)) { - if (k === "allOf" && Array.isArray(v)) { - for (const member of v) { - Object.assign(out, rewriteOpenAI(member as JsonSchema.JsonSchema)) + return InternalStructuredOutput.walkJsonSchema(schema, (schema) => { + const normalized = normalizeAllOf(schema) + const out: JsonSchema.JsonSchema = {} + let unsupportedFormat: string | undefined + for (const [key, value] of Object.entries(normalized)) { + if (key === "format") { + if (typeof value === "string" && formats.has(value) && supportsType(key, normalized.type)) out.format = value + else if (typeof value === "string") unsupportedFormat = value + } else if (supportedKeywords.has(key) && supportsType(key, normalized.type)) { + if (key !== "additionalProperties" || value === false) out[key] = value } - } else if (Array.isArray(v)) { - out[k] = v.map((item) => - typeof item === "object" && item !== null && !Array.isArray(item) - ? rewriteOpenAI(item as JsonSchema.JsonSchema) - : item - ) - } else if (typeof v === "object" && v !== null) { - out[k] = rewriteOpenAI(v as JsonSchema.JsonSchema) - } else { - out[k] = v } + if (unsupportedFormat !== undefined) { + InternalStructuredOutput.appendDescription(out, `a value with a format of ${unsupportedFormat}`) + } + if (out.type === "object" && out.properties === undefined && out.additionalProperties === false) { + out.properties = {} + } + return out + }) +} + +function supportsType(key: string, type: unknown): boolean { + if (typeof type !== "string") return true + switch (key) { + case "pattern": + case "format": + return type === "string" + case "multipleOf": + case "minimum": + case "exclusiveMinimum": + case "maximum": + case "exclusiveMaximum": + return type === "number" || type === "integer" + case "items": + case "minItems": + case "maxItems": + return type === "array" + case "properties": + case "patternProperties": + case "required": + case "additionalProperties": + return type === "object" + default: + return true } - if (out.type === "object" && out.properties === undefined && out.additionalProperties === false) { - out.properties = {} +} + +function normalizeAllOf(schema: JsonSchema.JsonSchema): JsonSchema.JsonSchema { + if (!Array.isArray(schema.allOf)) return schema + + const out: JsonSchema.JsonSchema = {} + const patterns: Array = [] + const baseKeywords = new Set() + const memberKeywords = new Set() + const ambiguousKeywords = new Set() + for (const [key, value] of Object.entries(schema)) { + if (key === "allOf") continue + baseKeywords.add(key) + if (key === "pattern" && typeof value === "string") patterns.push(value) + else out[key] = value + } + for (const member of schema.allOf) { + if (!InternalStructuredOutput.isJsonSchema(member)) continue + for (const [key, value] of Object.entries(member)) { + mergeAllOfKeyword(out, key, value, patterns, baseKeywords, memberKeywords, ambiguousKeywords) + } + } + const uniquePatterns = Array.from(new Set(patterns)) + if (uniquePatterns.length === 1) { + out.pattern = uniquePatterns[0] + } else if (uniquePatterns.length > 1) { + const combined = uniquePatterns.map((source) => `(?=[\\s\\S]*?(?:${source}))`).join("") + out.pattern = `^${combined}` } return out } -function recurOpenAI(ast: SchemaAST.AST): SchemaAST.AST { - switch (ast._tag) { - case "Declaration": - case "Void": - case "Never": - case "Unknown": - case "Any": - case "BigInt": - case "Symbol": - case "UniqueSymbol": - case "ObjectKeyword": - case "Enum": - case "TemplateLiteral": - return unsupportedAst( - ast, - "OpenAI structured output does not support this schema kind; consider transforming the schema or using a different provider" - ) - case "Undefined": - return unsupportedAst( - ast, - "OpenAI structured output does not support undefined; consider transforming the schema or using a different provider; if using `Schema.optional`, consider using `Schema.optionalKey` instead" - ) - case "Null": - return ast - case "String": { - const { annotations, filters } = get(ast) - if (annotations !== undefined || filters !== undefined) { - return new SchemaAST.String(annotations, filters) - } - return ast - } - case "Number": { - const { annotations, filters } = get(ast) - if (annotations !== undefined || filters !== undefined) { - return new SchemaAST.Number(annotations, filters) - } - return ast - } - case "Boolean": - return ast - case "Literal": { - const literal = ast.literal - if (typeof literal === "string" || typeof literal === "number" || typeof literal === "boolean") { - const { annotations, filters } = get(ast) - if (annotations !== undefined || filters !== undefined) { - return new SchemaAST.Literal(ast.literal, annotations, filters) - } - return ast - } - throw new Error( - `${errorPrefix}: Unsupported literal type ${typeof literal} (value: ${ - String(literal) - }) (supported: string | number | boolean)` - ) - } - case "Union": { - if (ast.mode === "oneOf") { - return new SchemaAST.Union(ast.types, "anyOf", ast.annotations, ast.checks) +function mergeAllOfKeyword( + out: JsonSchema.JsonSchema, + key: string, + value: unknown, + patterns: Array, + baseKeywords: ReadonlySet, + memberKeywords: Set, + ambiguousKeywords: Set +): void { + switch (key) { + case "description": + if (typeof value === "string") InternalStructuredOutput.appendDescription(out, value) + return + case "pattern": + if (typeof value === "string") patterns.push(value) + return + case "minimum": + case "exclusiveMinimum": + mergeLowerBound(out, key, value) + return + case "maximum": + case "exclusiveMaximum": + mergeUpperBound(out, key, value) + return + case "minItems": + mergeMinimum(out, key, value) + return + case "maxItems": + mergeMaximum(out, key, value) + return + default: + if (!normalizableAllOfKeywords.has(key) || baseKeywords.has(key) || ambiguousKeywords.has(key)) return + if (!memberKeywords.has(key)) { + out[key] = value + memberKeywords.add(key) + } else if (out[key] !== value) { + delete out[key] + memberKeywords.delete(key) + ambiguousKeywords.add(key) } - const types = SchemaAST.mapOrSame(ast.types, recurOpenAI) - const { annotations, filters } = get(ast) - if (types !== ast.types || annotations !== undefined || filters !== undefined) { - return new SchemaAST.Union(types, "anyOf", annotations, filters) - } - return ast - } - case "Arrays": { - if (ast.rest.length > 1) { - throw new Error( - `${errorPrefix}: Post-rest elements are not supported for arrays (rest length: ${ast.rest.length})` - ) - } - let { annotations, filters } = get(ast) - if (ast.elements.length > 0) { - // tuples are not supported by OpenAI, we translate them to objects with string keys - if (annotations !== undefined && typeof annotations.description === "string") { - annotations.description = `${TUPLE_DESCRIPTION}; ${annotations.description}` - } else { - annotations ??= {} - annotations.description = TUPLE_DESCRIPTION - } - const propertySignatures = ast.elements.map((e, i) => { - return new SchemaAST.PropertySignature(String(i), e) - }) - if (ast.rest.length === 1) { - propertySignatures.push( - new SchemaAST.PropertySignature(REST_PROPERTY_NAME, new SchemaAST.Arrays(false, [], ast.rest)) - ) - } - return SchemaAST.decodeTo( - recurOpenAI(new SchemaAST.Objects(propertySignatures, [], annotations, filters)), - ast, - SchemaTransformation.transform({ - decode: (o) => { - let t: Array = [] - for (let i = 0; i < ast.elements.length; i++) { - const k = String(i) - if (o[k] !== undefined) { - t.push(o[k]) - } - } - if (REST_PROPERTY_NAME in o) { - t = [...t, ...o[REST_PROPERTY_NAME]] - } - return t - }, - encode: (t) => { - const o: Record = {} - for (let i = 0; i < ast.elements.length; i++) { - if (t.length >= i) { - o[String(i)] = t[i] - } - } - if (ast.rest.length === 1) { - o[REST_PROPERTY_NAME] = t.length >= ast.elements.length ? t.slice(ast.elements.length) : [] - } - return o - } - }) - ) - } else { - const rest = SchemaAST.mapOrSame(ast.rest, recurOpenAI) - if (rest !== ast.rest || annotations !== undefined || filters !== undefined) { - return new SchemaAST.Arrays(false, [], rest, annotations, filters) - } - return ast - } - } - case "Objects": { - let { annotations, filters } = get(ast) - if (ast.indexSignatures.length === 0) { - const propertySignatures = SchemaAST.mapOrSame(ast.propertySignatures, (ps) => { - if (typeof ps.name !== "string") { - throw new Error( - `${errorPrefix}: Property names must be strings (got ${typeof ps.name})` - ) - } - let type = recurOpenAI(ps.type) - // optional properties are not supported by OpenAI, so we translate them to nullable unions - if (SchemaAST.isOptional(ps.type)) { - type = SchemaAST.decodeTo( - new SchemaAST.Union([type, SchemaAST.null], "anyOf"), - SchemaAST.optionalKey(type), - SchemaTransformation.transformOptional({ - decode: Option.filter(Predicate.isNotNull), - encode: Option.orElseSome(() => null) - }) - ) - } - if (type === ps.type) { - return ps - } - return new SchemaAST.PropertySignature(ps.name, type) - }) - if ( - propertySignatures !== ast.propertySignatures || annotations !== undefined || filters !== undefined - ) { - return new SchemaAST.Objects(propertySignatures, [], annotations, filters) - } - } else if (ast.indexSignatures.length === 1 && ast.propertySignatures.length === 0) { - const is = ast.indexSignatures[0] - if (Tool.isEmptyParamsRecord(is)) { - return ast - } - // records are not supported by OpenAI, so we translate them to arrays of key-value pairs - if (annotations !== undefined && typeof annotations.description === "string") { - annotations.description = `${RECORD_DESCRIPTION}; ${annotations.description}` - } else { - annotations ??= {} - annotations.description = RECORD_DESCRIPTION - } - return SchemaAST.decodeTo( - recurOpenAI( - new SchemaAST.Arrays(false, [], [new SchemaAST.Arrays(false, [is.parameter, is.type], [])], annotations) - ), - ast, - SchemaTransformation.transform({ - decode: Object.fromEntries, - encode: Object.entries - }) - ) - } else { - throw new Error( - `${errorPrefix}: unsupported object schema shape (properties: ${ast.propertySignatures.length}, indexSignatures: ${ast.indexSignatures.length}). Supported: plain objects (properties only) or records (single index signature, no properties)` - ) - } - return ast - } - case "Suspend": { - const cached = cache.get(ast) - if (cached) return cached - const { annotations } = get(ast) - const out = new SchemaAST.Suspend(() => recurOpenAI(ast.thunk()), annotations) - cache.set(ast, out) - return out - } + return } } -const cache = new Map() - -const errorPrefix = "OpenAiStructuredOutput" - -function unsupportedAst(ast: SchemaAST.AST, details?: string): never { - const base = `Unsupported AST ${ast._tag}` - const full = `${errorPrefix}: ${base}` - throw new Error(details !== undefined ? `${full} (${details})` : full) +function mergeMinimum(schema: JsonSchema.JsonSchema, key: string, value: unknown): void { + if (typeof value !== "number") return + const current = schema[key] + if (typeof current !== "number" || value > current) schema[key] = value } -const REST_PROPERTY_NAME = "__rest__" - -const RECORD_DESCRIPTION = - "Object encoded as array of [key, value] pairs. Apply object constraints to the decoded object" - -const TUPLE_DESCRIPTION = - "Tuple encoded as an object with numeric string keys ('0', '1', ...). If present, '__rest__' contains remaining elements" - -type Annotation = - | { readonly _tag: "description"; readonly description: string } - | { readonly _tag: "format"; readonly format: string } - -type Filter = - | Annotation - | { readonly _tag: "filter"; readonly filter: SchemaAST.Filter } - | { readonly _tag: "regex"; readonly source: string } +function mergeMaximum(schema: JsonSchema.JsonSchema, key: string, value: unknown): void { + if (typeof value !== "number") return + const current = schema[key] + if (typeof current !== "number" || value < current) schema[key] = value +} -const get = (ast: SchemaAST.AST): { - annotations: Record | undefined - filters: [SchemaAST.Check, ...SchemaAST.Check[]] | undefined -} => { - const annotations: Record = {} - const filters: Array> = [] - const regexSources: Array = [] - const checks = getChecks(ast, SchemaAST.isArrays(ast)) - if (checks.length > 0) { - for (const check of checks) { - switch (check._tag) { - case "description": { - if (annotations.description !== undefined) { - annotations.description += ` and ${check.description}` - } else { - annotations.description = check.description - } - break - } - case "format": { - annotations.format = check.format - break - } - case "filter": { - filters.push(check.filter) - break - } - case "regex": { - regexSources.push(check.source) - break - } - } - } +function mergeLowerBound(schema: JsonSchema.JsonSchema, key: string, value: unknown): void { + if (typeof value !== "number") { + if (!Object.hasOwn(schema, key)) schema[key] = value + return } - // OpenAI does not support allOf, so we merge multiple regex patterns into a single isPattern filter - if (regexSources.length === 1) { - filters.push(SchemaAST.isPattern(new RegExp(regexSources[0]))) - } else if (regexSources.length > 1) { - const combined = regexSources.map((s) => `(?=[\\s\\S]*?(?:${s}))`).join("") - filters.push(SchemaAST.isPattern(new RegExp(`^${combined}`))) + const minimum = typeof schema.minimum === "number" ? schema.minimum : undefined + const exclusiveMinimum = typeof schema.exclusiveMinimum === "number" ? schema.exclusiveMinimum : undefined + let current: { readonly value: number; readonly exclusive: boolean } | undefined + if (minimum !== undefined) current = { value: minimum, exclusive: false } + if ( + exclusiveMinimum !== undefined && + (current === undefined || exclusiveMinimum >= current.value) + ) { + current = { value: exclusiveMinimum, exclusive: true } } - return { - annotations: Object.keys(annotations).length > 0 ? annotations : undefined, - filters: Arr.isArrayNonEmpty(filters) ? filters : undefined + const candidate = { value, exclusive: key === "exclusiveMinimum" } + if ( + current === undefined || + candidate.value > current.value || + (candidate.value === current.value && candidate.exclusive) + ) { + current = candidate } + delete schema.minimum + delete schema.exclusiveMinimum + schema[current.exclusive ? "exclusiveMinimum" : "minimum"] = current.value } -const getChecks = (ast: SchemaAST.AST, isArray: boolean): Array => [ - ...(ast.checks !== undefined ? getFilters(ast.checks, isArray) : []), - ...getAnnotations(ast.annotations) -] - -const getAnnotations = (annotations: Schema.Annotations.Filter | undefined): Array => { - const out: Array = [] - if (annotations !== undefined) { - const description = annotations?.description - ?? (annotations.meta?._tag === "isInt" || annotations.meta?._tag === "isFinite" - ? undefined - : annotations?.expected) - if (typeof description === "string") { - out.push({ _tag: "description", description }) - } - const format = annotations?.format - if (typeof format === "string") { - if (formats.includes(format)) { - out.push({ _tag: "format", format }) - } else { - out.push({ _tag: "description", description: `a value with a format of ${format}` }) - } - } +function mergeUpperBound(schema: JsonSchema.JsonSchema, key: string, value: unknown): void { + if (typeof value !== "number") { + if (!Object.hasOwn(schema, key)) schema[key] = value + return } - return out -} - -function getFilter(filter: SchemaAST.Filter, isArray: boolean): Array { - let out: Array = [] - const annotations = getAnnotations(filter.annotations) - const meta = filter.annotations?.meta - if (meta !== undefined) { - switch (meta._tag) { - case "isMinLength": - case "isMaxLength": - case "isLengthBetween": { - out = out.concat(annotations) - if (isArray) { - out.push({ _tag: "filter", filter: resetFilter(filter) }) - } - break - } - case "isInt": - case "isFinite": - case "isGreaterThan": - case "isGreaterThanOrEqualTo": - case "isLessThan": - case "isLessThanOrEqualTo": - case "isBetween": - case "isMultipleOf": { - out = out.concat(annotations) - out.push({ _tag: "filter", filter: resetFilter(filter) }) - break - } - default: { - out = out.concat(annotations) - break - } - } - if ("regExp" in meta && meta.regExp instanceof RegExp) { - out.push({ _tag: "regex", source: meta.regExp.source }) - } + const maximum = typeof schema.maximum === "number" ? schema.maximum : undefined + const exclusiveMaximum = typeof schema.exclusiveMaximum === "number" ? schema.exclusiveMaximum : undefined + let current: { readonly value: number; readonly exclusive: boolean } | undefined + if (maximum !== undefined) current = { value: maximum, exclusive: false } + if ( + exclusiveMaximum !== undefined && + (current === undefined || exclusiveMaximum <= current.value) + ) { + current = { value: exclusiveMaximum, exclusive: true } } - return out + const candidate = { value, exclusive: key === "exclusiveMaximum" } + if ( + current === undefined || + candidate.value < current.value || + (candidate.value === current.value && candidate.exclusive) + ) { + current = candidate + } + delete schema.maximum + delete schema.exclusiveMaximum + schema[current.exclusive ? "exclusiveMaximum" : "maximum"] = current.value } -function resetFilter(filter: SchemaAST.Filter): SchemaAST.Filter { - return filter.annotate({ - description: undefined, - expected: undefined, - title: undefined, - format: undefined - }) -} +const supportedKeywords = new Set([ + "$ref", + "type", + "title", + "description", + "enum", + "anyOf", + "properties", + "required", + "additionalProperties", + "items", + "pattern", + "multipleOf", + "minimum", + "exclusiveMinimum", + "maximum", + "exclusiveMaximum", + "minItems", + "maxItems" +]) -function getFilters( - checks: readonly [SchemaAST.Check, ...SchemaAST.Check[]], - isArray: boolean -): Array { - return checks.flatMap((check) => { - switch (check._tag) { - case "Filter": - return getFilter(check, isArray) - case "FilterGroup": - return getFilters(check.checks, isArray) - } - }) -} +const normalizableAllOfKeywords = new Set([ + "$ref", + "title", + "enum", + "anyOf", + "format", + "multipleOf" +]) -const formats = [ +const formats = new Set([ "date-time", "time", "date", "duration", "email", "hostname", - "uri", "ipv4", "ipv6", "uuid" -] +]) diff --git a/repos/effect/packages/effect/src/unstable/ai/Prompt.ts b/repos/effect/packages/effect/src/unstable/ai/Prompt.ts index 45855c3c12..7680780fb7 100644 --- a/repos/effect/packages/effect/src/unstable/ai/Prompt.ts +++ b/repos/effect/packages/effect/src/unstable/ai/Prompt.ts @@ -12,7 +12,6 @@ import * as Arr from "../../Array.ts" import * as Effect from "../../Effect.ts" import { dual } from "../../Function.ts" -import * as Option from "../../Option.ts" import { type Pipeable, pipeArguments } from "../../Pipeable.ts" import * as Predicate from "../../Predicate.ts" import * as Schema from "../../Schema.ts" @@ -153,7 +152,7 @@ const BasePart = Schema.Struct({ * * **Example** (Creating content parts) * - * ```ts + * ```ts import.meta.vitest * import { Prompt } from "effect/unstable/ai" * * const textPart = Prompt.makePart("text", { @@ -165,6 +164,8 @@ const BasePart = Schema.Struct({ * fileName: "screenshot.png", * data: new Uint8Array([1, 2, 3]) * }) + * + * const result = [textPart.type, filePart.type] // => ["text", "file"] * ``` * * @category constructors @@ -219,12 +220,13 @@ export type PartConstructorParams

= Omit "Hello, how can I help you today?" * ``` * * @category models @@ -301,13 +303,14 @@ export const textPart = (params: PartConstructorParams): TextPart => m * * **Example** (Creating reasoning parts) * - * ```ts + * ```ts import.meta.vitest * import { Prompt } from "effect/unstable/ai" * * const reasoningPart: Prompt.ReasoningPart = Prompt.makePart("reasoning", { * text: * "Summary: the response compares the requested options by price and availability." * }) + * reasoningPart.type // => "reasoning" * ``` * * @category models @@ -387,7 +390,7 @@ export const reasoningPart = (params: PartConstructorParams): Rea * * **Example** (Creating file parts) * - * ```ts + * ```ts import.meta.vitest * import { Prompt } from "effect/unstable/ai" * * const imagePart: Prompt.FilePart = Prompt.makePart("file", { @@ -401,6 +404,8 @@ export const reasoningPart = (params: PartConstructorParams): Rea * fileName: "report.pdf", * data: new Uint8Array([1, 2, 3]) * }) + * + * const result = [imagePart.mediaType, documentPart.fileName] // => ["image/jpeg", "report.pdf"] * ``` * * @category models @@ -501,7 +506,7 @@ export const filePart = (params: PartConstructorParams): FilePart => m * * **Example** (Creating tool call parts) * - * ```ts + * ```ts import.meta.vitest * import { Prompt } from "effect/unstable/ai" * * const toolCallPart: Prompt.ToolCallPart = Prompt.makePart("tool-call", { @@ -510,6 +515,7 @@ export const filePart = (params: PartConstructorParams): FilePart => m * params: { city: "San Francisco", units: "celsius" }, * providerExecuted: false * }) + * toolCallPart.name // => "get_weather" * ``` * * @category models @@ -614,7 +620,7 @@ export const toolCallPart = (params: PartConstructorParams): ToolC * * **Example** (Creating tool result parts) * - * ```ts + * ```ts import.meta.vitest * import { Prompt } from "effect/unstable/ai" * * const toolResultPart: Prompt.ToolResultPart = Prompt.makePart("tool-result", { @@ -625,8 +631,10 @@ export const toolCallPart = (params: PartConstructorParams): ToolC * temperature: 22, * condition: "sunny", * humidity: 65 - * } + * }, + * providerExecuted: false * }) + * const result = [toolResultPart.name, toolResultPart.isFailure] // => ["get_weather", false] * ``` * * @category models @@ -649,6 +657,10 @@ export interface ToolResultPart extends BasePart<"tool-result", ToolResultPartOp * The result returned by the tool execution. */ readonly result: unknown + /** + * Whether the tool was executed by the provider (true) or framework (false). + */ + readonly providerExecuted: boolean } /** @@ -674,6 +686,10 @@ export interface ToolResultPartEncoded extends BasePartEncoded<"tool-result", To * The result returned by the tool execution. */ readonly result: unknown + /** + * Whether the tool was executed by the provider (true) or framework (false). + */ + readonly providerExecuted?: boolean | undefined } /** @@ -697,6 +713,7 @@ export const ToolResultPart: Schema.Struct<{ readonly name: Schema.String readonly isFailure: Schema.Boolean readonly result: Schema.Unknown + readonly providerExecuted: Schema.withDecodingDefault readonly "~effect/ai/Prompt/Part": Schema.withDecodingDefaultKey> readonly options: Schema.withDecodingDefault< Schema.$Record< @@ -710,7 +727,8 @@ export const ToolResultPart: Schema.Struct<{ id: Schema.String, name: Schema.String, isFailure: Schema.Boolean, - result: Schema.Unknown + result: Schema.Unknown, + providerExecuted: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(false))) }).annotate({ identifier: "ToolResultPart" }) /** @@ -736,7 +754,7 @@ export const toolResultPart = (params: PartConstructorParams): T * * **Example** (Creating tool approval responses) * - * ```ts + * ```ts import.meta.vitest * import { Prompt } from "effect/unstable/ai" * * const approvalResponse: Prompt.ToolApprovalResponsePart = Prompt.makePart( @@ -755,6 +773,8 @@ export const toolResultPart = (params: PartConstructorParams): T * reason: "Operation not allowed" * } * ) + * + * const result = [approvalResponse.approved, denialResponse.approved] // => [true, false] * ``` * * @category models @@ -858,7 +878,7 @@ export const toolApprovalResponsePart = ( * * **Example** (Creating tool approval requests) * - * ```ts + * ```ts import.meta.vitest * import { Prompt } from "effect/unstable/ai" * * const approvalRequest: Prompt.ToolApprovalRequestPart = Prompt.makePart( @@ -868,6 +888,7 @@ export const toolApprovalResponsePart = ( * toolCallId: "call_456" * } * ) + * const result = [approvalRequest.approvalId, approvalRequest.toolCallId] // => ["approval_123", "call_456"] * ``` * * @category models @@ -946,6 +967,32 @@ export const toolApprovalRequestPart = ( params: PartConstructorParams ): ToolApprovalRequestPart => makePart("tool-approval-request", params as any) +/** + * Schema for validation and encoding of content parts. + * + * @category schemas + * @since 4.0.0 + */ +export const Part: Schema.Union< + readonly [ + typeof TextPart, + typeof ReasoningPart, + typeof FilePart, + typeof ToolCallPart, + typeof ToolResultPart, + typeof ToolApprovalResponsePart, + typeof ToolApprovalRequestPart + ] +> = Schema.Union([ + TextPart, + ReasoningPart, + FilePart, + ToolCallPart, + ToolResultPart, + ToolApprovalResponsePart, + ToolApprovalRequestPart +]) + // ============================================================================= // Base Message // ============================================================================= @@ -1012,7 +1059,7 @@ const BaseMessage = Schema.Struct({ * * **Example** (Creating messages) * - * ```ts + * ```ts import.meta.vitest * import { Prompt } from "effect/unstable/ai" * * const textPart = Prompt.makePart("text", { @@ -1022,6 +1069,7 @@ const BaseMessage = Schema.Struct({ * const userMessage = Prompt.makeMessage("user", { * content: [textPart] * }) + * const result = [userMessage.role, userMessage.content.length] // => ["user", 1] * ``` * * @category constructors @@ -1096,13 +1144,14 @@ export const ContentFromString: Schema.decodeTo< * * **Example** (Creating system messages) * - * ```ts + * ```ts import.meta.vitest * import { Prompt } from "effect/unstable/ai" * * const systemMessage: Prompt.SystemMessage = Prompt.makeMessage("system", { * content: "You are a helpful assistant specialized in mathematics. " + * "Always show your work step by step." * }) + * systemMessage.role // => "system" * ``` * * @category models @@ -1177,7 +1226,7 @@ export const systemMessage = (params: MessageConstructorParams): * * **Example** (Creating user messages) * - * ```ts + * ```ts import.meta.vitest * import { Prompt } from "effect/unstable/ai" * * const textUserMessage: Prompt.UserMessage = Prompt.makeMessage("user", { @@ -1200,6 +1249,8 @@ export const systemMessage = (params: MessageConstructorParams): * }) * ] * }) + * + * const result = [textUserMessage.content.length, multimodalUserMessage.content.length] // => [1, 2] * ``` * * @category models @@ -1241,6 +1292,17 @@ export interface UserMessageEncoded extends BaseMessageEncoded<"user", UserMessa */ export type UserMessagePartEncoded = TextPartEncoded | FilePartEncoded +/** + * Schema for validation and encoding of user message content parts. + * + * @category schemas + * @since 4.0.0 + */ +export const UserMessagePart: Schema.Union = Schema.Union([ + TextPart, + FilePart +]) + /** * Represents provider-specific options that can be associated with a * `UserMessage` through module augmentation. @@ -1345,7 +1407,7 @@ export const userMessage = (params: MessageConstructorParams): User * * **Example** (Creating assistant messages) * - * ```ts + * ```ts import.meta.vitest * import { Prompt } from "effect/unstable/ai" * * const assistantMessage: Prompt.AssistantMessage = Prompt.makeMessage( @@ -1369,7 +1431,8 @@ export const userMessage = (params: MessageConstructorParams): User * result: { * temperature: 72, * condition: "sunny" - * } + * }, + * providerExecuted: true * }), * Prompt.makePart("text", { * text: "The weather in San Francisco is currently 72°F and sunny." @@ -1377,6 +1440,7 @@ export const userMessage = (params: MessageConstructorParams): User * ] * } * ) + * assistantMessage.content.map((part) => part.type) // => ["text", "tool-call", "tool-result", "text"] * ``` * * @category models @@ -1427,6 +1491,30 @@ export type AssistantMessagePartEncoded = | ToolResultPartEncoded | ToolApprovalRequestPartEncoded +/** + * Schema for validation and encoding of assistant message content parts. + * + * @category schemas + * @since 4.0.0 + */ +export const AssistantMessagePart: Schema.Union< + readonly [ + typeof TextPart, + typeof FilePart, + typeof ReasoningPart, + typeof ToolCallPart, + typeof ToolResultPart, + typeof ToolApprovalRequestPart + ] +> = Schema.Union([ + TextPart, + FilePart, + ReasoningPart, + ToolCallPart, + ToolResultPart, + ToolApprovalRequestPart +]) + /** * Represents provider-specific options that can be associated with a * `AssistantMessage` through module augmentation. @@ -1536,7 +1624,7 @@ export const assistantMessage = (params: MessageConstructorParams ["tool", "tool-result"] * ``` * * @category models @@ -1596,6 +1686,19 @@ export interface ToolMessageEncoded extends BaseMessageEncoded<"tool", ToolMessa */ export type ToolMessagePartEncoded = ToolResultPartEncoded | ToolApprovalResponsePartEncoded +/** + * Schema for validation and encoding of tool message content parts. + * + * @category schemas + * @since 4.0.0 + */ +export const ToolMessagePart: Schema.Union< + readonly [typeof ToolResultPart, typeof ToolApprovalResponsePart] +> = Schema.Union([ + ToolResultPart, + ToolApprovalResponsePart +]) + /** * Represents provider-specific options that can be associated with a * `ToolMessage` through module augmentation. @@ -1737,22 +1840,30 @@ export const Prompt: Schema.Codec = Schema.Struct({ Schema.decodeTo( $Prompt, SchemaTransformation.transformOrFail({ - decode: (input) => + decode: (input, options) => Effect.mapBothEager( SchemaParser.decodeEffect(Schema.Array(Message))(input.content), { onSuccess: makePrompt, onFailure: () => - new SchemaIssue.InvalidValue(Option.some(input.content), { message: "Invalid Prompt messages" }) + new SchemaIssue.InvalidValue( + { message: "Invalid Prompt messages" }, + input.content, + options + ) } ), - encode: (prompt) => + encode: (prompt, options) => Effect.mapBothEager( SchemaParser.encodeEffect(Schema.Array(Message))(prompt.content), { onSuccess: (messages) => ({ content: messages }), onFailure: () => - new SchemaIssue.InvalidValue(Option.some(prompt.content), { message: "Invalid Prompt messages" }) + new SchemaIssue.InvalidValue( + { message: "Invalid Prompt messages" }, + prompt.content, + options + ) } ) }) @@ -1765,8 +1876,8 @@ export const Prompt: Schema.Codec = Schema.Struct({ * * **Example** (Accepting raw prompt input) * - * ```ts - * import type { Prompt } from "effect/unstable/ai" + * ```ts import.meta.vitest + * import { Prompt } from "effect/unstable/ai" * * // String input - creates a user message * const stringInput: Prompt.RawInput = "Hello, world!" @@ -1777,9 +1888,9 @@ export const Prompt: Schema.Codec = Schema.Struct({ * { role: "user", content: [{ type: "text", text: "Hi!" }] } * ] * - * // Existing prompt - * declare const existingPrompt: Prompt.Prompt - * const promptInput: Prompt.RawInput = existingPrompt + * const promptInput: Prompt.RawInput = Prompt.empty + * + * const result = [typeof stringInput, Array.isArray(messagesInput), promptInput.content.length] // => ["string", true, 0] * ``` * * @category models @@ -1809,11 +1920,11 @@ const decodeMessagesSync = Schema.decodeSync(Schema.Array(Message)) * * **Example** (Creating an empty prompt) * - * ```ts + * ```ts import.meta.vitest * import { Prompt } from "effect/unstable/ai" * * const emptyPrompt = Prompt.empty - * console.log(emptyPrompt.content) // [] + * emptyPrompt.content // => [] * ``` * * @category constructors @@ -1831,7 +1942,7 @@ export const empty: Prompt = makePrompt([]) * * **Example** (Creating prompts from inputs) * - * ```ts + * ```ts import.meta.vitest * import { Prompt } from "effect/unstable/ai" * * // From string - creates a user message @@ -1843,9 +1954,9 @@ export const empty: Prompt = makePrompt([]) * { role: "user", content: [{ type: "text", text: "Hi!" }] } * ]) * - * // From existing prompt - * declare const existingPrompt: Prompt.Prompt - * const copiedPrompt = Prompt.make(existingPrompt) + * const copiedPrompt = Prompt.make(Prompt.empty) + * + * const result = [textPrompt.content[0].role, structuredPrompt.content.length, copiedPrompt.content.length] // => ["user", 2, 0] * ``` * * @category constructors @@ -1872,7 +1983,7 @@ export const make = (input: RawInput): Prompt => { * * **Example** (Creating prompts from messages) * - * ```ts + * ```ts import.meta.vitest * import { Prompt } from "effect/unstable/ai" * * const messages: ReadonlyArray = [ @@ -1885,6 +1996,7 @@ export const make = (input: RawInput): Prompt => { * ] * * const prompt = Prompt.fromMessages(messages) + * prompt.content.length // => 2 * ``` * * @category constructors @@ -1892,15 +2004,27 @@ export const make = (input: RawInput): Prompt => { */ export const fromMessages = (messages: ReadonlyArray): Prompt => makePrompt(messages) +const mergeOptions = (left: ProviderOptions, right: ProviderOptions): ProviderOptions => { + const result: Record = { ...left } + for (const [provider, metadata] of Object.entries(right)) { + const previous = result[provider] + result[provider] = Predicate.isObject(previous) && Predicate.isObject(metadata) + ? Object.assign({}, previous, metadata) + : metadata + } + return result +} + /** * Creates a `Prompt` from response parts by folding completed text and - * reasoning streams into assistant parts, placing tool calls and approval - * requests in an assistant message, and placing non-preliminary tool results - * in a tool message using their encoded results. + * reasoning streams into assistant parts, preserving provider metadata as + * prompt options, placing tool calls and approval requests in an assistant + * message, and placing non-preliminary tool results in a tool message using + * their encoded results. * * **Example** (Creating prompts from response parts) * - * ```ts + * ```ts import.meta.vitest * import { Prompt, Response } from "effect/unstable/ai" * * const responseParts: ReadonlyArray = [ @@ -1926,6 +2050,7 @@ export const fromMessages = (messages: ReadonlyArray): Prompt => makePr * * const prompt = Prompt.fromResponseParts(responseParts) * // Creates an assistant message with the response content + * prompt.content.map((message) => message.role) // => ["assistant", "tool"] * ``` * * @category constructors @@ -1939,55 +2064,63 @@ export const fromResponseParts = (parts: ReadonlyArray): Promp const assistantParts: Array = [] const toolParts: Array = [] - const activeTextDeltas = new Map() - const activeReasoningDeltas = new Map() + const activeTextDeltas = new Map() + const activeReasoningDeltas = new Map() for (const part of parts) { switch (part.type) { // Text Parts case "text": { - assistantParts.push(makePart("text", { text: part.text })) + assistantParts.push(makePart("text", { text: part.text, options: part.metadata })) break } // Text Parts (streaming) case "text-start": { - activeTextDeltas.set(part.id, { text: "" }) + activeTextDeltas.set(part.id, { text: "", options: part.metadata }) break } case "text-delta": { if (activeTextDeltas.has(part.id)) { - activeTextDeltas.get(part.id)!.text += part.delta + const active = activeTextDeltas.get(part.id)! + active.text += part.delta + active.options = mergeOptions(active.options, part.metadata) } break } case "text-end": { if (activeTextDeltas.has(part.id)) { - assistantParts.push(makePart("text", activeTextDeltas.get(part.id)!)) + const active = activeTextDeltas.get(part.id)! + active.options = mergeOptions(active.options, part.metadata) + assistantParts.push(makePart("text", active)) } break } // Reasoning Parts case "reasoning": { - assistantParts.push(makePart("reasoning", { text: part.text })) + assistantParts.push(makePart("reasoning", { text: part.text, options: part.metadata })) break } // Reasoning Parts (streaming) case "reasoning-start": { - activeReasoningDeltas.set(part.id, { text: "" }) + activeReasoningDeltas.set(part.id, { text: "", options: part.metadata }) break } case "reasoning-delta": { if (activeReasoningDeltas.has(part.id)) { - activeReasoningDeltas.get(part.id)!.text += part.delta + const active = activeReasoningDeltas.get(part.id)! + active.text += part.delta + active.options = mergeOptions(active.options, part.metadata) } break } case "reasoning-end": { if (activeReasoningDeltas.has(part.id)) { - assistantParts.push(makePart("reasoning", activeReasoningDeltas.get(part.id)!)) + const active = activeReasoningDeltas.get(part.id)! + active.options = mergeOptions(active.options, part.metadata) + assistantParts.push(makePart("reasoning", active)) } break } @@ -1998,7 +2131,8 @@ export const fromResponseParts = (parts: ReadonlyArray): Promp id: part.id, name: part.name, params: part.params, - providerExecuted: part.providerExecuted ?? false + providerExecuted: part.providerExecuted ?? false, + options: part.metadata })) break } @@ -2006,11 +2140,14 @@ export const fromResponseParts = (parts: ReadonlyArray): Promp // Tool Result Parts (skip preliminary results) case "tool-result": { if (part.preliminary !== true) { - toolParts.push(makePart("tool-result", { + const target = part.providerExecuted === true ? assistantParts : toolParts + target.push(makePart("tool-result", { id: part.id, name: part.name, isFailure: part.isFailure, - result: part.encodedResult + result: part.encodedResult, + providerExecuted: part.providerExecuted ?? false, + options: part.metadata })) } break @@ -2058,7 +2195,7 @@ export const fromResponseParts = (parts: ReadonlyArray): Promp * * **Example** (Concatenating prompts) * - * ```ts + * ```ts import.meta.vitest * import { Prompt } from "effect/unstable/ai" * * const systemPrompt = Prompt.make([{ @@ -2067,6 +2204,7 @@ export const fromResponseParts = (parts: ReadonlyArray): Promp * }]) * * const merged = Prompt.concat(systemPrompt, "Hello, world!") + * merged.content.map((message) => message.role) // => ["system", "user"] * ``` * * @category combinators @@ -2101,7 +2239,7 @@ export const concat: { * * **Example** (Replacing system instructions) * - * ```ts + * ```ts import.meta.vitest * import { Prompt } from "effect/unstable/ai" * * const systemPrompt = Prompt.make([{ @@ -2117,6 +2255,7 @@ export const concat: { * prompt, * "You are an expert in programming" * ) + * replaced.content[0].content // => "You are an expert in programming" * ``` * * @category combinators @@ -2143,7 +2282,7 @@ export const setSystem: { * * **Example** (Prepending system instructions) * - * ```ts + * ```ts import.meta.vitest * import { Prompt } from "effect/unstable/ai" * * const systemPrompt = Prompt.make([{ @@ -2160,6 +2299,7 @@ export const setSystem: { * "You are a helpful assistant. " * ) * // result content: "You are a helpful assistant. You are an expert in programming." + * replaced.content[0].content // => "You are a helpful assistant. You are an expert in programming." * ``` * * @category combinators @@ -2192,7 +2332,7 @@ export const prependSystem: { * * **Example** (Appending system instructions) * - * ```ts + * ```ts import.meta.vitest * import { Prompt } from "effect/unstable/ai" * * const systemPrompt = Prompt.make([{ @@ -2209,6 +2349,7 @@ export const prependSystem: { * " You are a helpful assistant." * ) * // result content: "You are an expert in programming. You are a helpful assistant." + * replaced.content[0].content // => "You are an expert in programming. You are a helpful assistant." * ``` * * @category combinators diff --git a/repos/effect/packages/effect/src/unstable/ai/Response.ts b/repos/effect/packages/effect/src/unstable/ai/Response.ts index c14811ffa8..fab6fa0ce5 100644 --- a/repos/effect/packages/effect/src/unstable/ai/Response.ts +++ b/repos/effect/packages/effect/src/unstable/ai/Response.ts @@ -155,7 +155,7 @@ export type AllPartsEncoded = * * **Example** (Building a response parts schema) * - * ```ts + * ```ts import.meta.vitest * import { Schema } from "effect" * import { Response, Tool, Toolkit } from "effect/unstable/ai" * @@ -167,6 +167,7 @@ export type AllPartsEncoded = * ) * * const allPartsSchema = Response.AllParts(myToolkit) + * Schema.isSchema(allPartsSchema) // => true * ``` * * @category schemas @@ -493,7 +494,7 @@ const BasePart = Schema.Struct({ * * **Example** (Creating response content parts) * - * ```ts + * ```ts import.meta.vitest * import { Response } from "effect/unstable/ai" * * const textPart = Response.makePart("text", { @@ -506,6 +507,8 @@ const BasePart = Schema.Struct({ * params: { city: "San Francisco" }, * providerExecuted: false * }) + * + * const result = [textPart.type, toolCallPart.name] // => ["text", "get_weather"] * ``` * * @category constructors @@ -557,12 +560,13 @@ export type ConstructorParams = * * **Example** (Creating a text part) * - * ```ts + * ```ts import.meta.vitest * import { Response } from "effect/unstable/ai" * * const textPart: Response.TextPart = Response.makePart("text", { * text: "The answer to your question is 42." * }) + * textPart.text // => "The answer to your question is 42." * ``` * * @category models @@ -813,13 +817,14 @@ export const TextEndPart: Schema.Struct<{ * * **Example** (Creating a reasoning part) * - * ```ts + * ```ts import.meta.vitest * import { Response } from "effect/unstable/ai" * * const reasoningPart: Response.ReasoningPart = Response.makePart("reasoning", { * text: * "Let me think step by step: First I need to analyze the user's question..." * }) + * reasoningPart.type // => "reasoning" * ``` * * @category models @@ -1295,7 +1300,7 @@ export const ToolParamsEndPart: Schema.Struct<{ * * **Example** (Creating a tool call part) * - * ```ts + * ```ts import.meta.vitest * import { Schema } from "effect" * import { Response } from "effect/unstable/ai" * @@ -1316,6 +1321,7 @@ export const ToolParamsEndPart: Schema.Struct<{ * params: { city: "San Francisco", units: "celsius" }, * providerExecuted: false * }) + * const result = [toolCallPart.name, toolCallPart.params.city] // => ["get_weather", "San Francisco"] * ``` * * @category models @@ -1503,7 +1509,7 @@ export interface ToolResultFailure extends BaseToo * * **Example** (Creating a tool result part) * - * ```ts + * ```ts import.meta.vitest * import { Response } from "effect/unstable/ai" * * interface WeatherData { @@ -1533,6 +1539,7 @@ export interface ToolResultFailure extends BaseToo * providerExecuted: false, * preliminary: false * }) + * const result = [toolResultPart.name, toolResultPart.result.temperature] // => ["get_weather", 22] * ``` * * @category models @@ -1723,7 +1730,7 @@ export const toolResultPart = ["approval_123", "call_456"] * ``` * * @category models @@ -1824,13 +1832,14 @@ export const toolApprovalRequestPart = ( * * **Example** (Creating a file part) * - * ```ts + * ```ts import.meta.vitest * import { Response } from "effect/unstable/ai" * * const imagePart: Response.FilePart = Response.makePart("file", { * mediaType: "image/jpeg", * data: new Uint8Array([1, 2, 3]) * }) + * const result = [imagePart.mediaType, imagePart.data] // => ["image/jpeg", new Uint8Array([1, 2, 3])] * ``` * * @category models @@ -2116,7 +2125,7 @@ export const UrlSourcePart: Schema.Struct<{ * * **Example** (Describing an HTTP request) * - * ```ts + * ```ts import.meta.vitest * import type { Response } from "effect/unstable/ai" * * const requestDetails: typeof Response.HttpRequestDetails.Type = { @@ -2126,6 +2135,7 @@ export const UrlSourcePart: Schema.Struct<{ * hash: undefined, * headers: { "Content-Type": "application/json" } * } + * const result = [requestDetails.method, requestDetails.urlParams] // => ["POST", []] * ``` * * @category schemas @@ -2135,7 +2145,7 @@ export const HttpRequestDetails = Schema.Struct({ method: Schema.Literals(["GET", "POST", "PATCH", "PUT", "DELETE", "HEAD", "OPTIONS", "TRACE"]), url: Schema.String, urlParams: Schema.Array(Schema.Tuple([Schema.String, Schema.String])), - hash: Schema.UndefinedOr(Schema.String), + hash: Schema.optional(Schema.String), headers: Schema.Record( Schema.String, Schema.Union([ @@ -2156,7 +2166,7 @@ export const HttpRequestDetails = Schema.Struct({ * * **Example** (Describing an HTTP response) * - * ```ts + * ```ts import.meta.vitest * import type { Response } from "effect/unstable/ai" * * const responseDetails: typeof Response.HttpResponseDetails.Type = { @@ -2166,13 +2176,14 @@ export const HttpRequestDetails = Schema.Struct({ * "X-Request-Id": "req_abc123" * } * } + * const result = [responseDetails.status, responseDetails.headers["X-Request-Id"]] // => [200, "req_abc123"] * ``` * * @category schemas * @since 4.0.0 */ export const HttpResponseDetails = Schema.Struct({ - status: Schema.Number, + status: Schema.Int, headers: Schema.Record( Schema.String, Schema.Union([ @@ -2191,7 +2202,7 @@ export const HttpResponseDetails = Schema.Struct({ * * **Example** (Creating a metadata part) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * import { Response } from "effect/unstable/ai" * @@ -2200,10 +2211,11 @@ export const HttpResponseDetails = Schema.Struct({ * { * id: "resp_123", * modelId: "gpt-4", - * timestamp: DateTime.nowUnsafe(), + * timestamp: DateTime.makeUnsafe("2024-01-01T00:00:00Z"), * request: undefined * } * ) + * const result = [metadataPart.id, metadataPart.modelId] // => ["resp_123", "gpt-4"] * ``` * * @category models @@ -2213,19 +2225,19 @@ export interface ResponseMetadataPart extends BasePart<"response-metadata", Resp /** * Optional unique identifier for this specific response. */ - readonly id: string | undefined + readonly id?: string | undefined /** * Optional identifier of the AI model that generated the response. */ - readonly modelId: string | undefined + readonly modelId?: string | undefined /** * Optional timestamp when the response was generated. */ - readonly timestamp: DateTime.Utc | undefined + readonly timestamp?: DateTime.Utc | undefined /** * Optional HTTP request details for the request made to the AI provider. */ - readonly request: typeof HttpRequestDetails.Type | undefined + readonly request?: typeof HttpRequestDetails.Type | undefined } /** @@ -2272,10 +2284,10 @@ export interface ResponseMetadataPartMetadata extends ProviderMetadata {} */ export const ResponseMetadataPart: Schema.Struct<{ readonly type: Schema.tag<"response-metadata"> - readonly id: Schema.UndefinedOr - readonly modelId: Schema.UndefinedOr - readonly timestamp: Schema.UndefinedOr - readonly request: Schema.UndefinedOr + readonly id: Schema.optional + readonly modelId: Schema.optional + readonly timestamp: Schema.optional + readonly request: Schema.optional readonly "~effect/ai/Content/Part": Schema.withDecodingDefaultKey> readonly metadata: Schema.withDecodingDefault< Schema.$Record> @@ -2283,10 +2295,10 @@ export const ResponseMetadataPart: Schema.Struct<{ }> = Schema.Struct({ ...BasePart.fields, type: Schema.tag("response-metadata"), - id: Schema.UndefinedOr(Schema.String), - modelId: Schema.UndefinedOr(Schema.String), - timestamp: Schema.UndefinedOr(Schema.DateTimeUtcFromString), - request: Schema.UndefinedOr(HttpRequestDetails) + id: Schema.optional(Schema.String), + modelId: Schema.optional(Schema.String), + timestamp: Schema.optional(Schema.DateTimeUtcFromString), + request: Schema.optional(HttpRequestDetails) }).annotate({ identifier: "ResponseMetadataPart" }) satisfies Schema.Codec< ResponseMetadataPart, ResponseMetadataPartEncoded @@ -2368,19 +2380,19 @@ export class Usage extends Schema.Class("effect/ai/AiResponse/Usage")({ /** * The number of non-cached input (i.e. prompt) tokens used. */ - uncached: Schema.UndefinedOr(Schema.Number), + uncached: Schema.optional(Schema.Int), /** * The total of number of input (i.e. prompt) tokens used. */ - total: Schema.UndefinedOr(Schema.Number), + total: Schema.optional(Schema.Int), /** * The number of cached input (i.e. prompt) tokens read. */ - cacheRead: Schema.UndefinedOr(Schema.Number), + cacheRead: Schema.optional(Schema.Int), /** * The number of cached input (i.e. prompt) tokens written. */ - cacheWrite: Schema.UndefinedOr(Schema.Number) + cacheWrite: Schema.optional(Schema.Int) }), /** * Information about the output (i.e. response) tokens used. @@ -2389,15 +2401,15 @@ export class Usage extends Schema.Class("effect/ai/AiResponse/Usage")({ /** * The total of number of output (i.e. response) tokens used. */ - total: Schema.UndefinedOr(Schema.Number), + total: Schema.optional(Schema.Int), /** * The number of text tokens used. */ - text: Schema.UndefinedOr(Schema.Number), + text: Schema.optional(Schema.Int), /** * The number of reasoning tokens used. */ - reasoning: Schema.UndefinedOr(Schema.Number) + reasoning: Schema.optional(Schema.Int) }) }) {} @@ -2406,7 +2418,7 @@ export class Usage extends Schema.Class("effect/ai/AiResponse/Usage")({ * * **Example** (Creating a finish part) * - * ```ts + * ```ts import.meta.vitest * import { Response } from "effect/unstable/ai" * * const finishPart: Response.FinishPart = Response.makePart("finish", { @@ -2426,6 +2438,7 @@ export class Usage extends Schema.Class("effect/ai/AiResponse/Usage")({ * }), * response: undefined * }) + * const result = [finishPart.reason, finishPart.usage.inputTokens.total] // => ["stop", 50] * ``` * * @category models @@ -2443,7 +2456,7 @@ export interface FinishPart extends BasePart<"finish", FinishPartMetadata> { /** * Optional HTTP response details from the AI provider. */ - readonly response: typeof HttpResponseDetails.Type | undefined + readonly response?: typeof HttpResponseDetails.Type | undefined } /** @@ -2500,7 +2513,7 @@ export const FinishPart: Schema.Struct<{ "unknown" ]> readonly usage: typeof Usage - readonly response: Schema.UndefinedOr + readonly response: Schema.optional readonly "~effect/ai/Content/Part": Schema.withDecodingDefaultKey> readonly metadata: Schema.withDecodingDefault< Schema.$Record> @@ -2510,7 +2523,7 @@ export const FinishPart: Schema.Struct<{ type: Schema.tag("finish"), reason: FinishReason, usage: Usage, - response: Schema.UndefinedOr(HttpResponseDetails) + response: Schema.optional(HttpResponseDetails) }).annotate({ identifier: "FinishPart" }) satisfies Schema.Codec // ============================================================================= @@ -2522,12 +2535,13 @@ export const FinishPart: Schema.Struct<{ * * **Example** (Creating an error part) * - * ```ts + * ```ts import.meta.vitest * import { Response } from "effect/unstable/ai" * * const errorPart: Response.ErrorPart = Response.makePart("error", { * error: new Error("boom") * }) + * const result = [errorPart.type, errorPart.error instanceof Error] // => ["error", true] * ``` * * @category models diff --git a/repos/effect/packages/effect/src/unstable/ai/Telemetry.ts b/repos/effect/packages/effect/src/unstable/ai/Telemetry.ts index 2b8b31cd50..a7aed63c1e 100644 --- a/repos/effect/packages/effect/src/unstable/ai/Telemetry.ts +++ b/repos/effect/packages/effect/src/unstable/ai/Telemetry.ts @@ -229,7 +229,7 @@ export type WellKnownSystem = * * **Example** (Prefixing telemetry attributes) * - * ```ts + * ```ts import.meta.vitest * import type { Telemetry } from "effect/unstable/ai" * * type RequestAttrs = { @@ -245,6 +245,11 @@ export type WellKnownSystem = * // "gen_ai.request.model_name": string * // "gen_ai.request.max_tokens": number * // } + * const attributes: PrefixedAttrs = { + * "gen_ai.request.model_name": "gpt-4", + * "gen_ai.request.max_tokens": 1000 + * } + * Object.keys(attributes) // => ["gen_ai.request.model_name", "gen_ai.request.max_tokens"] * ``` * * @category utility types @@ -264,12 +269,19 @@ export type AttributesWithPrefix, Prefix * * **Example** (Formatting attribute names) * - * ```ts + * ```ts import.meta.vitest * import type { Telemetry } from "effect/unstable/ai" * * type Formatted1 = Telemetry.FormatAttributeName<"modelName"> // "model_name" * type Formatted2 = Telemetry.FormatAttributeName<"maxTokens"> // "max_tokens" * type Formatted3 = Telemetry.FormatAttributeName<"temperature"> // "temperature" + * + * const formatted: [Formatted1, Formatted2, Formatted3] = [ + * "model_name", + * "max_tokens", + * "temperature" + * ] + * formatted // => ["model_name", "max_tokens", "temperature"] * ``` * * @category utility types @@ -292,9 +304,8 @@ export type FormatAttributeName = T extends * * **Example** (Adding prefixed span attributes) * - * ```ts - * import { String } from "effect" - * import type { Tracer } from "effect" + * ```ts import.meta.vitest + * import { Context, Option, String, Tracer } from "effect" * import { Telemetry } from "effect/unstable/ai" * * const addCustomAttributes = Telemetry.addSpanAttributes( @@ -302,13 +313,22 @@ export type FormatAttributeName = T extends * String.camelToSnake * ) * - * // Usage with a span - * declare const span: Tracer.Span + * const span = new Tracer.NativeSpan({ + * name: "request", + * parent: Option.none(), + * annotations: Context.empty(), + * links: [], + * startTime: 0n, + * kind: "internal", + * sampled: true + * }) + * * addCustomAttributes(span, { * modelName: "gpt-4", * maxTokens: 1000 * }) - * // Results in attributes: "custom.ai.model_name" and "custom.ai.max_tokens" + * + * Array.from(span.attributes.keys()) // => ["custom.ai.model_name", "custom.ai.max_tokens"] * ``` * * @category annotations @@ -358,7 +378,7 @@ const addSpanUsageAttributes = addSpanAttributes("gen_ai.usage", String.camelToS * * **Example** (Configuring GenAI telemetry attributes) * - * ```ts + * ```ts import.meta.vitest * import type { Telemetry } from "effect/unstable/ai" * * const telemetryOptions: Telemetry.GenAITelemetryAttributeOptions = { @@ -381,6 +401,8 @@ const addSpanUsageAttributes = addSpanAttributes("gen_ai.usage", String.camelToS * outputTokens: 25 * } * } + * + * const result = [telemetryOptions.system, telemetryOptions.usage?.inputTokens] // => ["openai", 50] * ``` * * @category options @@ -428,7 +450,7 @@ export type GenAITelemetryAttributeOptions = BaseAttributes & { * * **Example** (Adding GenAI telemetry annotations) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * import { Telemetry } from "effect/unstable/ai" * @@ -440,7 +462,10 @@ export type GenAITelemetryAttributeOptions = BaseAttributes & { * request: { model: "gpt-4", temperature: 0.7 }, * usage: { inputTokens: 100, outputTokens: 50 } * }) + * return (span as { attributes: ReadonlyMap }).attributes.size * }) + * + * await Effect.runPromise(Effect.withSpan(directUsage, "example")) // => 5 * ``` * * @category annotations @@ -468,7 +493,7 @@ export const addGenAIAnnotations: { * * **Example** (Transforming AI spans) * - * ```ts + * ```ts import.meta.vitest * import type { Telemetry } from "effect/unstable/ai" * * const customTransformer: Telemetry.SpanTransformer = ({ response, span }) => { @@ -480,6 +505,8 @@ export const addGenAIAnnotations: { * ) * span.attribute("total_text_length", totalTextLength) * } + * + * typeof customTransformer // => "function" * ``` * * @category models diff --git a/repos/effect/packages/effect/src/unstable/ai/Tokenizer.ts b/repos/effect/packages/effect/src/unstable/ai/Tokenizer.ts index 4764ce539b..a919d9b177 100644 --- a/repos/effect/packages/effect/src/unstable/ai/Tokenizer.ts +++ b/repos/effect/packages/effect/src/unstable/ai/Tokenizer.ts @@ -31,7 +31,7 @@ import * as Prompt from "./Prompt.ts" * * **Example** (Accessing the Tokenizer service) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * import { Tokenizer } from "effect/unstable/ai" * @@ -40,6 +40,12 @@ import * as Prompt from "./Prompt.ts" * const tokens = yield* tokenizer.tokenize("Hello, world!") * return tokens.length * }) + * + * const tokenizer = Tokenizer.make({ + * tokenize: (prompt) => Effect.succeed(prompt.content.map((_, index) => index)) + * }) + * const result = useTokenizer.pipe(Effect.provideService(Tokenizer.Tokenizer, tokenizer)) + * await Effect.runPromise(result) // => 1 * ``` * * @category services @@ -60,7 +66,7 @@ export class Tokenizer extends Context.Service()( * * **Example** (Implementing a custom tokenizer) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * import { Prompt } from "effect/unstable/ai" * import type { Tokenizer } from "effect/unstable/ai" @@ -71,6 +77,9 @@ export class Tokenizer extends Context.Service()( * truncate: (input, maxTokens) => * Effect.succeed(Prompt.make(input.toString().slice(0, maxTokens * 5))) * } + * + * const tokenCount = (await Effect.runPromise(customTokenizer.tokenize("one two three"))).length // => 3 + * const messageCount = (await Effect.runPromise(customTokenizer.truncate("hello world", 1))).content.length // => 1 * ``` * * @category models @@ -112,7 +121,7 @@ export interface Service { * * **Example** (Creating a word tokenizer) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * import { Tokenizer } from "effect/unstable/ai" * @@ -131,6 +140,8 @@ export interface Service { * .map((_, index) => index) * ) * }) + * + * await Effect.runPromise(wordTokenizer.tokenize("hello effect world")) // => [0, 1, 2] * ``` * * @category constructors diff --git a/repos/effect/packages/effect/src/unstable/ai/Tool.ts b/repos/effect/packages/effect/src/unstable/ai/Tool.ts index 5be1481ae9..39aa8f824b 100644 --- a/repos/effect/packages/effect/src/unstable/ai/Tool.ts +++ b/repos/effect/packages/effect/src/unstable/ai/Tool.ts @@ -171,7 +171,7 @@ export type NeedsApproval = * * **Example** (Defining a weather lookup tool) * - * ```ts + * ```ts import.meta.vitest * import { Schema } from "effect" * import { Tool } from "effect/unstable/ai" * @@ -188,6 +188,7 @@ export type NeedsApproval = * humidity: Schema.Number * }) * }) + * const result = [GetWeather.name, GetWeather.failureMode] // => ["GetWeather", "error"] * ``` * * @category models @@ -273,6 +274,13 @@ export interface Tool< */ readonly needsApproval?: boolean | NeedsApprovalFunction | undefined + /** + * Set whether user approval is required before executing this tool. + */ + setNeedsApproval( + needsApproval: NeedsApproval + ): Tool + /** * Adds a _request-level_ dependency which must be provided before the tool * call handler can be executed. @@ -358,7 +366,7 @@ export interface Tool< * * **Example** (Defining a provider-defined web search tool) * - * ```ts + * ```ts import.meta.vitest * import { Schema } from "effect" * import { Tool } from "effect/unstable/ai" * @@ -377,7 +385,8 @@ export interface Tool< * snippet: Schema.String * })) * }) - * }) + * })({ query: "Effect" }) + * const result = [WebSearch.name, WebSearch.providerName] // => ["OpenAiWebSearch", "web_search"] * ``` * * @category models @@ -450,7 +459,7 @@ export interface ProviderDefined< * * **Example** (Defining dynamic tools) * - * ```ts + * ```ts import.meta.vitest * import { Schema } from "effect" * import { Tool } from "effect/unstable/ai" * @@ -473,6 +482,8 @@ export interface ProviderDefined< * required: ["query"] * } * }) + * + * const result = [Calculator.name, McpTool.name] // => ["Calculator", "McpTool"] * ``` * * @category models @@ -517,7 +528,7 @@ export interface Dynamic< * * **Example** (Checking for user-defined tools) * - * ```ts + * ```ts import.meta.vitest * import { Schema } from "effect" * import { Tool } from "effect/unstable/ai" * @@ -547,8 +558,7 @@ export interface Dynamic< * }) * }) * - * console.log(Tool.isUserDefined(UserDefinedTool)) // true - * console.log(Tool.isUserDefined(ProviderDefinedTool)) // false + * const result = [Tool.isUserDefined(UserDefinedTool), Tool.isUserDefined(ProviderDefinedTool)] // => [true, false] * ``` * * @category guards @@ -562,7 +572,7 @@ export const isUserDefined = (u: unknown): u is Tool => * * **Example** (Checking for provider-defined tools) * - * ```ts + * ```ts import.meta.vitest * import { Schema } from "effect" * import { Tool } from "effect/unstable/ai" * @@ -592,8 +602,7 @@ export const isUserDefined = (u: unknown): u is Tool => * }) * }) * - * console.log(Tool.isProviderDefined(UserDefinedTool)) // false - * console.log(Tool.isProviderDefined(ProviderDefinedTool)) // true + * const result = [Tool.isProviderDefined(UserDefinedTool), Tool.isProviderDefined(ProviderDefinedTool)] // => [false, false] * ``` * * @category guards @@ -608,7 +617,7 @@ export const isProviderDefined = ( * * **Example** (Checking for dynamic tools) * - * ```ts + * ```ts import.meta.vitest * import { Schema } from "effect" * import { Tool } from "effect/unstable/ai" * @@ -621,8 +630,7 @@ export const isProviderDefined = ( * success: Schema.Number * }) * - * console.log(Tool.isDynamic(DynamicTool)) // true - * console.log(Tool.isDynamic(UserDefinedTool)) // false + * const result = [Tool.isDynamic(DynamicTool), Tool.isDynamic(UserDefinedTool)] // => [true, false] * ``` * * @category guards @@ -1028,37 +1036,37 @@ export type RequiresHandler = Tool extends ProviderDefined< // Constructors // ============================================================================= +// Clones a tool while preserving its prototype (and thus its kind, e.g. +// user-defined vs. provider-defined vs. dynamic) and its own properties such +// as `id`. Optional `overrides` replace individual fields on the clone. +const clone = (self: Any, overrides?: Record): any => + Object.assign(Object.create(Object.getPrototypeOf(self)), self, overrides) + const Proto = { [TypeId]: { _Requirements: identity }, pipe() { return pipeArguments(this, arguments) }, addDependency(this: Any) { - return userDefinedProto({ ...this }) + return clone(this) }, setParameters(this: Any, parametersSchema: Schema.Constraint) { - return userDefinedProto({ - ...this, - parametersSchema - }) + return clone(this, { parametersSchema }) }, setSuccess(this: Any, successSchema: Schema.Constraint) { - return userDefinedProto({ ...this, successSchema }) + return clone(this, { successSchema }) }, setFailure(this: Any, failureSchema: Schema.Constraint) { - return userDefinedProto({ ...this, failureSchema }) + return clone(this, { failureSchema }) + }, + setNeedsApproval(this: Any, needsApproval: NeedsApproval) { + return clone(this, { needsApproval }) }, annotate(this: Any, tag: Context.Key, value: S) { - return userDefinedProto({ - ...this, - annotations: Context.add(this.annotations, tag, value) - }) + return clone(this, { annotations: Context.add(this.annotations, tag, value) }) }, annotateMerge(this: Any, context: Context.Context) { - return userDefinedProto({ - ...this, - annotations: Context.merge(this.annotations, context) - }) + return clone(this, { annotations: Context.merge(this.annotations, context) }) } } @@ -1132,7 +1140,7 @@ const providerDefinedProto = < readonly failureMode: Mode }, RequiresHandler -> => Object.assign(Object.create(ProviderDefinedProto), { ...options }) +> => Object.assign(Object.create(ProviderDefinedProto), { annotations: Context.empty(), ...options }) const dynamicProto = < const Name extends string, @@ -1178,7 +1186,7 @@ const dynamicProto = < * * **Example** (Creating a tool without parameters) * - * ```ts + * ```ts import.meta.vitest * import { Schema } from "effect" * import { Tool } from "effect/unstable/ai" * @@ -1187,6 +1195,7 @@ const dynamicProto = < * description: "Returns the current timestamp", * success: Schema.Number * }) + * GetCurrentTime.name // => "GetCurrentTime" * ``` * * @category constructors @@ -1284,7 +1293,7 @@ export const make = < * * **Example** (Creating a dynamic tool) * - * ```ts + * ```ts import.meta.vitest * import { Schema } from "effect" * import { Tool } from "effect/unstable/ai" * @@ -1307,6 +1316,8 @@ export const make = < * required: ["query"] * } * }) + * + * const result = [Calculator.name, McpTool.name] // => ["Calculator", "McpTool"] * ``` * * @category constructors @@ -1382,7 +1393,7 @@ export const dynamic: { * * **Example** (Creating a provider-defined tool) * - * ```ts + * ```ts import.meta.vitest * import { Schema } from "effect" * import { Tool } from "effect/unstable/ai" * @@ -1401,7 +1412,8 @@ export const dynamic: { * content: Schema.String * })) * }) - * }) + * })({ query: "Effect" }) + * const result = [WebSearch.name, WebSearch.providerName] // => ["OpenAiWebSearch", "web_search"] * ``` * * @category constructors @@ -1579,7 +1591,7 @@ export class NameMapper> { * * **Example** (Reading a tool description) * - * ```ts + * ```ts import.meta.vitest * import { Tool } from "effect/unstable/ai" * * const myTool = Tool.make("example", { @@ -1587,7 +1599,7 @@ export class NameMapper> { * }) * * const description = Tool.getDescription(myTool) - * console.log(description) // "This is an example tool" + * description // => "This is an example tool" * ``` * * @category getters @@ -1618,7 +1630,7 @@ export const getDescription = (tool: Tool): string | undefined * * **Example** (Generating a tool JSON schema) * - * ```ts + * ```ts import.meta.vitest * import { Schema } from "effect" * import { Tool } from "effect/unstable/ai" * @@ -1630,15 +1642,10 @@ export const getDescription = (tool: Tool): string | undefined * }) * * const jsonSchema = Tool.getJsonSchema(weatherTool) - * console.log(jsonSchema) - * // { - * // type: "object", - * // properties: { - * // location: { type: "string" }, - * // units: { type: "string", enum: ["celsius", "fahrenheit"] } - * // }, - * // required: ["location", "units"] - * // } + * jsonSchema.type // => "object" + * if (typeof jsonSchema.properties === "object" && jsonSchema.properties !== null) { + * Object.keys(jsonSchema.properties) // => ["location", "units"] + * } * ``` * * @category getters @@ -1669,10 +1676,18 @@ export const getJsonSchema = (tool: Tool, options?: { export const getJsonSchemaFromSchema = (schema: S, options?: { readonly transformer?: CodecTransformer }): JsonSchema.JsonSchema => { + return getJsonSchemaFromSchemaWith(schema, Schema.toJsonSchemaDocument, options) +} + +const getJsonSchemaFromSchemaWith = ( + schema: S, + toJsonSchemaDocument: (schema: Schema.Constraint) => JsonSchema.Document<"draft-2020-12">, + options?: { readonly transformer?: CodecTransformer } +): JsonSchema.JsonSchema => { if (Predicate.isNotUndefined(options?.transformer)) { return options.transformer(schema).jsonSchema } - const document = Schema.toJsonSchemaDocument(schema) + const document = toJsonSchemaDocument(schema) if (Object.keys(document.definitions).length > 0) { document.schema.$defs = document.definitions } @@ -1688,14 +1703,16 @@ export const getJsonSchemaFromSchema = (schema: S, * * **Example** (Annotating a tool title) * - * ```ts + * ```ts import.meta.vitest + * import { Context } from "effect" * import { Tool } from "effect/unstable/ai" * * const myTool = Tool.make("calculate_tip") * .annotate(Tool.Title, "Tip Calculator") + * Context.getUnsafe(myTool.annotations, Tool.Title) // => "Tip Calculator" * ``` * - * @category annotations + * @category services * @since 4.0.0 */ export class Title extends Context.Service()("effect/ai/Tool/Title") {} @@ -1705,14 +1722,16 @@ export class Title extends Context.Service()("effect/ai/Tool/Titl * * **Example** (Annotating MCP metadata) * - * ```ts + * ```ts import.meta.vitest + * import { Context } from "effect" * import { Tool } from "effect/unstable/ai" * * const myCalculatorUi = Tool.make("calculator_ui", {}) * .annotate(Tool.Meta, { ui: { resourceUri: "ui://example/calculator-ui" } }) + * "ui" in Context.getUnsafe(myCalculatorUi.annotations, Tool.Meta) // => true * ``` * - * @category annotations + * @category services * @since 4.0.0 */ export class Meta extends Context.Service>()("effect/ai/Tool/Meta") {} @@ -1727,14 +1746,16 @@ export class Meta extends Context.Service>()("effe * * **Example** (Marking a tool as read-only) * - * ```ts + * ```ts import.meta.vitest + * import { Context } from "effect" * import { Tool } from "effect/unstable/ai" * * const readOnlyTool = Tool.make("get_user_info") * .annotate(Tool.Readonly, true) + * Context.get(readOnlyTool.annotations, Tool.Readonly) // => true * ``` * - * @category annotations + * @category services * @since 4.0.0 */ export const Readonly = Context.Reference("effect/ai/Tool/Readonly", { @@ -1751,14 +1772,16 @@ export const Readonly = Context.Reference("effect/ai/Tool/Readonly", { * * **Example** (Marking a tool as non-destructive) * - * ```ts + * ```ts import.meta.vitest + * import { Context } from "effect" * import { Tool } from "effect/unstable/ai" * * const safeTool = Tool.make("search_database") * .annotate(Tool.Destructive, false) + * Context.get(safeTool.annotations, Tool.Destructive) // => false * ``` * - * @category annotations + * @category services * @since 4.0.0 */ export const Destructive = Context.Reference("effect/ai/Tool/Destructive", { @@ -1776,14 +1799,16 @@ export const Destructive = Context.Reference("effect/ai/Tool/Destructiv * * **Example** (Marking a tool as idempotent) * - * ```ts + * ```ts import.meta.vitest + * import { Context } from "effect" * import { Tool } from "effect/unstable/ai" * * const idempotentTool = Tool.make("get_current_time") * .annotate(Tool.Idempotent, true) + * Context.get(idempotentTool.annotations, Tool.Idempotent) // => true * ``` * - * @category annotations + * @category services * @since 4.0.0 */ export const Idempotent = Context.Reference("effect/ai/Tool/Idempotent", { @@ -1801,14 +1826,16 @@ export const Idempotent = Context.Reference("effect/ai/Tool/Idempotent" * * **Example** (Disabling open-world access) * - * ```ts + * ```ts import.meta.vitest + * import { Context } from "effect" * import { Tool } from "effect/unstable/ai" * * const restrictedTool = Tool.make("internal_operation") * .annotate(Tool.OpenWorld, false) + * Context.get(restrictedTool.annotations, Tool.OpenWorld) // => false * ``` * - * @category annotations + * @category services * @since 4.0.0 */ export const OpenWorld = Context.Reference("effect/ai/Tool/OpenWorld", { @@ -1830,14 +1857,15 @@ export const OpenWorld = Context.Reference("effect/ai/Tool/OpenWorld", * * **Example** (Disabling strict JSON schema mode) * - * ```ts + * ```ts import.meta.vitest * import { Tool } from "effect/unstable/ai" * * const flexibleTool = Tool.make("search") * .annotate(Tool.Strict, false) + * Tool.getStrictMode(flexibleTool) // => false * ``` * - * @category annotations + * @category services * @since 4.0.0 */ export const Strict = Context.Reference("effect/ai/Tool/Strict", { @@ -1918,13 +1946,13 @@ function filter(obj: any) { next = [] for (const node of nodes) { - if (Object.prototype.hasOwnProperty.call(node, "__proto__")) { + if (Object.hasOwn(node, "__proto__")) { throw new SyntaxError("Object contains forbidden prototype property") } if ( - Object.prototype.hasOwnProperty.call(node, "constructor") && - Object.prototype.hasOwnProperty.call(node.constructor, "prototype") + Object.hasOwn(node, "constructor") && + Object.hasOwn(node.constructor, "prototype") ) { throw new SyntaxError("Object contains forbidden prototype property") } diff --git a/repos/effect/packages/effect/src/unstable/ai/Toolkit.ts b/repos/effect/packages/effect/src/unstable/ai/Toolkit.ts index af3be989f9..1cedb040c6 100644 --- a/repos/effect/packages/effect/src/unstable/ai/Toolkit.ts +++ b/repos/effect/packages/effect/src/unstable/ai/Toolkit.ts @@ -15,6 +15,7 @@ import * as Effect from "../../Effect.ts" import * as Effectable from "../../Effectable.ts" import * as Fiber from "../../Fiber.ts" import { identity } from "../../Function.ts" +import * as InternalRecord from "../../internal/record.ts" import * as Layer from "../../Layer.ts" import * as Predicate from "../../Predicate.ts" import * as Queue from "../../Queue.ts" @@ -32,8 +33,8 @@ const TypeId = "~effect/ai/Toolkit" as const * * **Example** (Defining AI toolkits) * - * ```ts - * import { Schema } from "effect" + * ```ts import.meta.vitest + * import { Effect, Schema } from "effect" * import { Tool, Toolkit } from "effect/unstable/ai" * * const SearchDocs = Tool.make("SearchDocs", { @@ -50,8 +51,12 @@ const TypeId = "~effect/ai/Toolkit" as const * * const AiToolkit = Toolkit.make(SearchDocs, SummarizeText) * - * console.log(Object.keys(AiToolkit.tools)) - * // ["SearchDocs", "SummarizeText"] + * const ready = AiToolkit.pipe(Effect.provide(AiToolkit.toLayer({ + * SearchDocs: ({ query }) => Effect.succeed([query]), + * SummarizeText: ({ text }) => Effect.succeed(text) + * }))) + * + * Object.keys((await Effect.runPromise(ready)).tools) // => ["SearchDocs", "SummarizeText"] * ``` * * @category models @@ -105,6 +110,10 @@ export interface Toolkit> extends * @since 4.0.0 */ export interface HandlerContext { + /** + * The unique identifier of the tool call, when available. + */ + readonly toolCallId?: string | undefined /** * Emit a preliminary result during long-running tool calls. * @@ -199,7 +208,11 @@ export interface WithHandler> { /** * Parameters to pass to the tool handler. */ - params: Tool.Parameters + params: Tool.Parameters, + /** + * The unique identifier of the tool call. + */ + toolCallId?: string ) => Effect.Effect< Stream.Stream< Tool.HandlerResult, @@ -257,8 +270,8 @@ const Proto = { return schemas } - const handle = Effect.fnUntraced(function*(name: string, params: unknown) { - const tool = tools[name] + const handle = Effect.fnUntraced(function*(name: string, params: unknown, toolCallId?: string) { + const tool = Object.hasOwn(tools, name) ? tools[name] : undefined yield* Effect.annotateCurrentSpan({ tool: name, @@ -302,6 +315,7 @@ const Proto = { readonly preliminary: boolean }, Cause.Done>() const context: HandlerContext = { + toolCallId, preliminary: (result) => Effect.asVoid(Queue.offer(queue, { result, @@ -390,8 +404,10 @@ const Proto = { const handlers = Effect.isEffect(build) ? yield* build : build const context = new Map() for (const [name, handler] of Object.entries(handlers)) { - const tool = this.tools[name]! - context.set(tool.id, { name, handler, context: services }) + const tool = Object.hasOwn(this.tools, name) ? this.tools[name] : undefined + if (tool !== undefined) { + context.set(tool.id, { name, handler, context: services }) + } } return Context.makeUnsafe(context) }) @@ -418,7 +434,7 @@ const resolveInput = >( ): Record => { const output = {} as Record for (const tool of tools) { - output[tool.name] = tool + InternalRecord.assignProperty(output, tool.name, tool) } return output } @@ -447,8 +463,8 @@ export const empty: Toolkit<{}> = makeProto({}) * * **Example** (Creating a toolkit) * - * ```ts - * import { Schema } from "effect" + * ```ts import.meta.vitest + * import { Effect, Schema } from "effect" * import { Tool, Toolkit } from "effect/unstable/ai" * * const GetCurrentTime = Tool.make("GetCurrentTime", { @@ -466,6 +482,12 @@ export const empty: Toolkit<{}> = makeProto({}) * }) * * const toolkit = Toolkit.make(GetCurrentTime, GetWeather) + * const ready = toolkit.pipe(Effect.provide(toolkit.toLayer({ + * GetCurrentTime: () => Effect.succeed(0), + * get_weather: () => Effect.succeed({ temperature: 20, condition: "clear" }) + * }))) + * + * Object.keys((await Effect.runPromise(ready)).tools) // => ["GetCurrentTime", "get_weather"] * ``` * * @category constructors @@ -518,8 +540,8 @@ export type MergedTools> = SimplifyRecord< * * **Example** (Merging toolkits) * - * ```ts - * import { Schema } from "effect" + * ```ts import.meta.vitest + * import { Effect, Schema } from "effect" * import { Tool, Toolkit } from "effect/unstable/ai" * * const mathToolkit = Toolkit.make( @@ -533,6 +555,14 @@ export type MergedTools> = SimplifyRecord< * ) * * const combined = Toolkit.merge(mathToolkit, utilityToolkit) + * const ready = combined.pipe(Effect.provide(combined.toLayer({ + * add: () => Effect.succeed(1), + * subtract: () => Effect.succeed(0), + * get_time: () => Effect.succeed(0), + * get_weather: () => Effect.succeed("clear") + * }))) + * + * Object.keys((await Effect.runPromise(ready)).tools) // => ["add", "subtract", "get_time", "get_weather"] * ``` * * @category constructors @@ -547,7 +577,7 @@ export const merge = >( const tools = {} as Record for (const toolkit of toolkits) { for (const [name, tool] of Object.entries(toolkit.tools)) { - tools[name] = tool + InternalRecord.assignProperty(tools, name, tool) } } return makeProto(tools) as any diff --git a/repos/effect/packages/effect/src/unstable/ai/index.ts b/repos/effect/packages/effect/src/unstable/ai/index.ts index abea583e37..0c99d8153e 100644 --- a/repos/effect/packages/effect/src/unstable/ai/index.ts +++ b/repos/effect/packages/effect/src/unstable/ai/index.ts @@ -34,6 +34,11 @@ export * as IdGenerator from "./IdGenerator.ts" */ export * as LanguageModel from "./LanguageModel.ts" +/** + * @since 4.0.0 + */ +export * as McpProtocol from "./McpProtocol.ts" + /** * @since 4.0.0 */ diff --git a/repos/effect/packages/effect/src/unstable/ai/internal/codec-transformer.ts b/repos/effect/packages/effect/src/unstable/ai/internal/codec-transformer.ts index f1d15f1b7c..7d3b858b79 100644 --- a/repos/effect/packages/effect/src/unstable/ai/internal/codec-transformer.ts +++ b/repos/effect/packages/effect/src/unstable/ai/internal/codec-transformer.ts @@ -2,12 +2,18 @@ import * as JsonSchema from "../../../JsonSchema.ts" import * as Schema from "../../../Schema.ts" import type { CodecTransformer } from "../LanguageModel.ts" -/** @internal */ -export const defaultCodecTransformer: CodecTransformer = (codec) => { - const document = JsonSchema.resolveTopLevel$ref(Schema.toJsonSchemaDocument(codec)) - const jsonSchema = { ...document.schema } - if (Object.keys(document.definitions).length > 0) { - jsonSchema.$defs = document.definitions +const makeDefaultCodecTransformer = ( + toJsonSchemaDocument: (schema: Schema.Constraint) => JsonSchema.Document<"draft-2020-12"> +): CodecTransformer => { + return (codec) => { + const document = JsonSchema.resolveTopLevel$ref(toJsonSchemaDocument(codec)) + const jsonSchema = { ...document.schema } + if (Object.keys(document.definitions).length > 0) { + jsonSchema.$defs = document.definitions + } + return { codec, jsonSchema } } - return { codec, jsonSchema } } + +/** @internal */ +export const defaultCodecTransformer = makeDefaultCodecTransformer(Schema.toJsonSchemaDocument) diff --git a/repos/effect/packages/effect/src/unstable/ai/internal/mcpProtocol.ts b/repos/effect/packages/effect/src/unstable/ai/internal/mcpProtocol.ts new file mode 100644 index 0000000000..fe4dee0349 --- /dev/null +++ b/repos/effect/packages/effect/src/unstable/ai/internal/mcpProtocol.ts @@ -0,0 +1,85 @@ +import type * as Effect from "../../../Effect.ts" +import * as Schema from "../../../Schema.ts" +import type * as Rpc from "../../rpc/Rpc.ts" +import type * as RpcGroup from "../../rpc/RpcGroup.ts" + +export interface PayloadCodecs { + readonly decode: (input: unknown) => Effect.Effect + readonly encode: (input: unknown) => Effect.Effect +} + +/** @internal */ +export interface AnyProtocolAdapter { + readonly protocolVersion: string + readonly transport: { + readonly acceptsJsonRpcBatches: boolean + readonly requiresVersionHeader: boolean + } + readonly clientRpcs: RpcGroup.Any + readonly clientNotificationRpcs: RpcGroup.Any + readonly serverRequestRpcs: RpcGroup.Any + readonly serverNotificationRpcs: RpcGroup.Any + readonly payloadCodecs: (rpc: Rpc.AnyWithProps) => PayloadCodecs +} + +export interface ProtocolAdapter< + out Version extends string = string, + ClientRpcs extends Rpc.Any = Rpc.Any, + ClientNotificationRpcs extends ClientRpcs = ClientRpcs, + ServerRequestRpcs extends Rpc.Any = Rpc.Any, + ServerNotificationRpcs extends Rpc.Any = Rpc.Any +> { + readonly protocolVersion: Version + readonly transport: { + readonly acceptsJsonRpcBatches: boolean + readonly requiresVersionHeader: boolean + } + readonly clientRpcs: RpcGroup.RpcGroup + readonly clientNotificationRpcs: RpcGroup.RpcGroup + readonly serverRequestRpcs: RpcGroup.RpcGroup + readonly serverNotificationRpcs: RpcGroup.RpcGroup + readonly payloadCodecs: (rpc: Rpc.AnyWithProps) => PayloadCodecs +} + +/** @internal */ +export const make = < + const Version extends string, + ClientRpcs extends Rpc.Any, + ClientNotificationRpcs extends ClientRpcs, + ServerRequestRpcs extends Rpc.Any, + ServerNotificationRpcs extends Rpc.Any +>(options: { + readonly protocolVersion: Version + readonly transport: { + readonly acceptsJsonRpcBatches: boolean + readonly requiresVersionHeader: boolean + } + readonly clientRpcs: RpcGroup.RpcGroup + readonly clientNotificationRpcs: RpcGroup.RpcGroup + readonly serverRequestRpcs: RpcGroup.RpcGroup + readonly serverNotificationRpcs: RpcGroup.RpcGroup +}): ProtocolAdapter< + Version, + ClientRpcs, + ClientNotificationRpcs, + ServerRequestRpcs, + ServerNotificationRpcs +> => { + const payloadCodecsCache = new WeakMap() + const payloadCodecs = (rpc: Rpc.AnyWithProps): PayloadCodecs => { + let codecs = payloadCodecsCache.get(rpc) + if (codecs === undefined) { + const schema = Schema.toCodecJson(rpc.payloadSchema) + codecs = { + decode: Schema.decodeUnknownEffect(schema) as PayloadCodecs["decode"], + encode: Schema.encodeUnknownEffect(schema) as PayloadCodecs["encode"] + } + payloadCodecsCache.set(rpc, codecs) + } + return codecs + } + return { + ...options, + payloadCodecs + } +} diff --git a/repos/effect/packages/effect/src/unstable/ai/internal/mcpProtocolRegistry.ts b/repos/effect/packages/effect/src/unstable/ai/internal/mcpProtocolRegistry.ts new file mode 100644 index 0000000000..818bba60c1 --- /dev/null +++ b/repos/effect/packages/effect/src/unstable/ai/internal/mcpProtocolRegistry.ts @@ -0,0 +1,70 @@ +import type { NonEmptyReadonlyArray } from "../../../Array.ts" +import * as Cause from "../../../Cause.ts" +import * as Effect from "../../../Effect.ts" +import type * as RpcGroup from "../../rpc/RpcGroup.ts" +import type * as RpcMessage from "../../rpc/RpcMessage.ts" +import type * as McpProtocol from "./mcpProtocol.ts" + +type AnyRpcGroup = RpcGroup.RpcGroup + +const prefix = (protocol: McpProtocol.AnyProtocolAdapter): string => + `@effect/mcp/${encodeURIComponent(protocol.protocolVersion)}/` + +const asRpcGroup = (group: RpcGroup.Any): AnyRpcGroup => group as unknown as AnyRpcGroup + +/** @internal */ +export interface ProtocolRegistry< + Protocol extends McpProtocol.AnyProtocolAdapter = McpProtocol.AnyProtocolAdapter +> { + readonly protocols: NonEmptyReadonlyArray + readonly clientRpcs: AnyRpcGroup + readonly select: (offeredVersion: string) => Protocol + readonly routeClientRequest: ( + protocol: Protocol, + request: RpcMessage.RequestEncoded + ) => RpcMessage.RequestEncoded +} + +/** @internal */ +export const make = Effect.fnUntraced(function*< + const Protocols extends NonEmptyReadonlyArray +>( + protocols: Protocols +) { + type Protocol = Protocols[number] + + if (protocols.length === 0) { + return yield* new Cause.IllegalArgumentError( + "MCP protocol declaration must contain at least one MCP protocol" + ) + } + + const snapshot = Object.freeze(Array.from(protocols)) as NonEmptyReadonlyArray + const byVersion = new Map() + for (const protocol of snapshot) { + if (byVersion.has(protocol.protocolVersion)) { + return yield* new Cause.IllegalArgumentError( + `Duplicate MCP protocol version: ${protocol.protocolVersion}` + ) + } + byVersion.set(protocol.protocolVersion, protocol) + } + + let clientRpcs = asRpcGroup(snapshot[0].clientRpcs).prefix(prefix(snapshot[0])) + for (let i = 1; i < snapshot.length; i++) { + clientRpcs = clientRpcs.merge(asRpcGroup(snapshot[i].clientRpcs).prefix(prefix(snapshot[i]))) + } + + return { + protocols: snapshot, + clientRpcs, + select: (offeredVersion: string) => byVersion.get(offeredVersion) ?? snapshot[0], + routeClientRequest: ( + protocol: Protocol, + request: RpcMessage.RequestEncoded + ) => ({ + ...request, + tag: `${prefix(protocol)}${request.tag}` + }) + } satisfies ProtocolRegistry +}) diff --git a/repos/effect/packages/effect/src/unstable/ai/internal/structured-output.ts b/repos/effect/packages/effect/src/unstable/ai/internal/structured-output.ts new file mode 100644 index 0000000000..2d090d162f --- /dev/null +++ b/repos/effect/packages/effect/src/unstable/ai/internal/structured-output.ts @@ -0,0 +1,453 @@ +import * as Arr from "../../../Array.ts" +import * as InternalRecord from "../../../internal/record.ts" +import type * as JsonSchema from "../../../JsonSchema.ts" +import * as Option from "../../../Option.ts" +import * as Predicate from "../../../Predicate.ts" +import * as Schema from "../../../Schema.ts" +import * as SchemaAST from "../../../SchemaAST.ts" +import * as SchemaTransformation from "../../../SchemaTransformation.ts" +import * as Tool from "../Tool.ts" + +const REST_PROPERTY_NAME = "__rest__" +const TAIL_PROPERTY_PREFIX = "__tail_" + +const RECORD_DESCRIPTION = + "Object encoded as array of [key, value] pairs. Apply object constraints to the decoded object" + +const TUPLE_DESCRIPTION = + "Tuple encoded as an object with numeric string keys ('0', '1', ...). If present, '__rest__' contains remaining elements" +const TUPLE_TAIL_DESCRIPTION = `${TUPLE_DESCRIPTION}. Post-rest elements use '__tail_0__', '__tail_1__', and so on` + +/** @internal */ +export function toCodec( + schema: Schema.ConstraintCodec +): Schema.ConstraintCodec { + const jsonCodec = Schema.toCodecJson(schema) + const encoded = SchemaAST.toEncoded(jsonCodec.ast) + const from = transform(encoded) + if (from === encoded) { + return jsonCodec.ast === schema.ast ? schema : jsonCodec + } + return Schema.make( + SchemaAST.decodeTo(from, jsonCodec.ast, SchemaTransformation.passthrough()) + ) +} + +function transform(root: SchemaAST.AST): SchemaAST.AST { + let cache: Map | undefined + + function recur(ast: SchemaAST.AST): SchemaAST.AST { + switch (ast._tag) { + case "Union": { + const types = SchemaAST.mapOrSame(ast.types, recur) + const checks = prepareChecks(ast.checks) + const mode = ast.mode === "oneOf" ? "anyOf" : ast.mode + if (types === ast.types && checks === ast.checks && mode === ast.mode) return ast + return new SchemaAST.Union( + types, + mode, + ast.annotations, + checks, + ast.encoding, + ast.context, + ast.encodingChecks + ) + } + case "Arrays": { + if (ast.elements.length > 0 || ast.rest.length > 1) { + return tupleToObject(ast, recur) + } + const rest = SchemaAST.mapOrSame(ast.rest, recur) + const checks = prepareChecks(ast.checks) + if (rest === ast.rest && checks === ast.checks) return ast + return new SchemaAST.Arrays( + ast.isMutable, + ast.elements, + rest, + ast.annotations, + checks, + ast.encoding, + ast.context, + ast.encodingChecks + ) + } + case "Objects": { + if (ast.indexSignatures.length === 1 && ast.propertySignatures.length === 0) { + const indexSignature = ast.indexSignatures[0] + if (Tool.isEmptyParamsRecord(indexSignature)) return ast + } + if (ast.indexSignatures.length > 0) { + return objectToEntries(ast, recur) + } + + const propertySignatures = SchemaAST.mapOrSame(ast.propertySignatures, (propertySignature) => { + let type = recur(propertySignature.type) + if (SchemaAST.isOptional(propertySignature.type)) { + type = optionalToNullable(type) + } + return type === propertySignature.type + ? propertySignature + : new SchemaAST.PropertySignature(propertySignature.name, type) + }) + const checks = prepareChecks(ast.checks) + if ( + propertySignatures === ast.propertySignatures && + checks === ast.checks + ) { + return ast + } + return new SchemaAST.Objects( + propertySignatures, + ast.indexSignatures, + ast.annotations, + checks, + ast.encoding, + ast.context, + ast.encodingChecks + ) + } + case "Suspend": { + const cached = cache?.get(ast) + if (cached !== undefined) return cached + const out = new SchemaAST.Suspend( + () => recur(ast.thunk()), + ast.annotations, + undefined, + ast.encoding, + ast.context + ) + if (cache === undefined) cache = new Map() + cache.set(ast, out) + return out + } + default: { + const checks = prepareChecks(ast.checks) + return checks === ast.checks ? ast : SchemaAST.replaceChecks(ast, checks) + } + } + } + + return recur(root) +} + +function tupleToObject(ast: SchemaAST.Arrays, recur: (ast: SchemaAST.AST) => SchemaAST.AST): SchemaAST.AST { + const propertySignatures = ast.elements.map((element, index) => + new SchemaAST.PropertySignature(String(index), element) + ) + if (ast.rest.length === 1) { + propertySignatures.push( + new SchemaAST.PropertySignature(REST_PROPERTY_NAME, new SchemaAST.Arrays(false, [], ast.rest)) + ) + } else if (ast.rest.length > 1) { + propertySignatures.push( + new SchemaAST.PropertySignature(REST_PROPERTY_NAME, new SchemaAST.Arrays(false, [], [ast.rest[0]])) + ) + for (let index = 1; index < ast.rest.length; index++) { + propertySignatures.push( + new SchemaAST.PropertySignature(`${TAIL_PROPERTY_PREFIX}${index - 1}__`, ast.rest[index]) + ) + } + } + const from = recur( + new SchemaAST.Objects( + propertySignatures, + [], + structuralAnnotations(ast, ast.rest.length > 1 ? TUPLE_TAIL_DESCRIPTION : TUPLE_DESCRIPTION), + compilerChecks(ast.checks) + ) + ) + return SchemaAST.decodeTo( + from, + ast, + SchemaTransformation.transform({ + decode: (object) => { + const tuple: Array = [] + for (let index = 0; index < ast.elements.length; index++) { + const key = String(index) + if (object[key] !== undefined) tuple.push(object[key]) + } + if (REST_PROPERTY_NAME in object) { + const rest = object[REST_PROPERTY_NAME] + for (let index = 0; index < rest.length; index++) { + tuple.push(rest[index]) + } + } + for (let index = 1; index < ast.rest.length; index++) { + tuple.push(object[`${TAIL_PROPERTY_PREFIX}${index - 1}__`]) + } + return tuple + }, + encode: (tuple) => { + const object: Record = {} + for (let index = 0; index < ast.elements.length; index++) { + if (index < tuple.length) object[String(index)] = tuple[index] + } + if (ast.rest.length >= 1) { + const tailLength = ast.rest.length - 1 + const restEnd = Math.max(ast.elements.length, tuple.length - tailLength) + object[REST_PROPERTY_NAME] = tuple.slice(ast.elements.length, restEnd) + for (let index = 0; index < tailLength; index++) { + object[`${TAIL_PROPERTY_PREFIX}${index}__`] = tuple[restEnd + index] + } + } + return object + } + }) + ) +} + +function objectToEntries( + ast: SchemaAST.Objects, + recur: (ast: SchemaAST.AST) => SchemaAST.AST +): SchemaAST.AST { + const checks = combineChecks(recordChecks(ast.checks), compilerChecks(ast.checks)) + const key = unionOrSingle([ + ...ast.propertySignatures.map((propertySignature) => new SchemaAST.Literal(propertySignature.name as string)), + ...ast.indexSignatures.map((indexSignature) => indexSignature.parameter) + ]) + const value = unionOrSingle([ + ...ast.propertySignatures.map((propertySignature) => propertySignature.type), + ...ast.indexSignatures.map((indexSignature) => indexSignature.type) + ]) + const from = recur( + new SchemaAST.Arrays( + false, + [], + [new SchemaAST.Arrays(false, [key, value], [])], + structuralAnnotations(ast, RECORD_DESCRIPTION), + checks + ) + ) + return SchemaAST.decodeTo( + from, + ast, + SchemaTransformation.transform({ + decode: Object.fromEntries, + encode: Object.entries + }) + ) +} + +function unionOrSingle(types: ReadonlyArray): SchemaAST.AST { + if (types.length === 1) return types[0] + const unique = Array.from(new Set(types)) + return unique.length === 1 ? unique[0] : new SchemaAST.Union(unique, "anyOf") +} + +function combineChecks( + left: SchemaAST.Checks | undefined, + right: SchemaAST.Checks | undefined +): SchemaAST.Checks | undefined { + if (left === undefined) return right + if (right === undefined) return left + return [...left, ...right] +} + +function compilerChecks(checks: SchemaAST.Checks | undefined): SchemaAST.Checks | undefined { + if (checks === undefined) return undefined + const out = checks.flatMap(compilerCheck) + return Arr.isArrayNonEmpty(out) ? out : undefined +} + +function compilerCheck(check: SchemaAST.Check): Array> { + // Structural rewrites change the value seen by provider-side checks. A + // no-op proxy lets the generic compiler call `toJsonSchema` with the real + // provider type and dependency schemas without running the original filter + // against the wrong runtime shape. + const annotations = compilerAnnotations(check.annotations) + if (check._tag === "Filter") { + return annotations === undefined ? [] : [new SchemaAST.Filter(() => undefined, annotations)] + } + const checks = check.checks.flatMap(compilerCheck) + if (Arr.isArrayNonEmpty(checks)) { + return [new SchemaAST.FilterGroup(checks, annotations)] + } + return annotations === undefined ? [] : [new SchemaAST.Filter(() => undefined, annotations)] +} + +function compilerAnnotations( + annotations: Schema.Annotations.Filter | undefined +): Schema.Annotations.Filter | undefined { + if (annotations?.toJsonSchema === undefined) return undefined + return { + representation: annotations.representation, + toJsonSchema: annotations.toJsonSchema + } +} + +function optionalToNullable(type: SchemaAST.AST): SchemaAST.AST { + return SchemaAST.decodeTo( + new SchemaAST.Union([type, SchemaAST.null], "anyOf"), + SchemaAST.optionalKey(type), + SchemaTransformation.transformOptional({ + decode: Option.filter(Predicate.isNotNull), + encode: Option.orElseSome(() => null) + }) + ) +} + +function prepareChecks(checks: SchemaAST.Checks | undefined): SchemaAST.Checks | undefined { + if (checks === undefined) return undefined + const out = SchemaAST.mapOrSame(checks, prepareCheck) + return out as SchemaAST.Checks +} + +function prepareCheck(check: SchemaAST.Check): SchemaAST.Check { + switch (check._tag) { + case "Filter": { + const id = check.annotations?.representation?.id + return id === "effect/schema/isFinite" || id === "effect/schema/isInt" + ? check.annotate({ expected: undefined }) + : check + } + case "FilterGroup": { + const checks = SchemaAST.mapOrSame(check.checks, prepareCheck) + return checks === check.checks + ? check + : new SchemaAST.FilterGroup(checks, check.annotations) + } + } +} + +function recordChecks(checks: SchemaAST.Checks | undefined): SchemaAST.Checks | undefined { + if (checks === undefined) return undefined + const out = checks.flatMap(recordCheck) + return Arr.isArrayNonEmpty(out) ? out : undefined +} + +function recordCheck(check: SchemaAST.Check): Array> { + if (check._tag === "FilterGroup") return check.checks.flatMap(recordCheck) + const representation = check.annotations?.representation + const payload = representation?.payload + if (!isJsonObject(payload)) return [] + // A decoded record with at least N properties necessarily came from at least + // N entries. Upper bounds are not transported because duplicate provider + // keys can collapse during `Object.fromEntries`, so `maxItems` could reject + // an input that the returned codec accepts. + switch (representation?.id) { + case "effect/schema/isMinProperties": + return typeof payload.minProperties === "number" + ? [withoutDescription(Schema.isMinLength(payload.minProperties))] + : [] + case "effect/schema/isPropertiesLengthBetween": + return typeof payload.minimum === "number" + ? [withoutDescription(Schema.isMinLength(payload.minimum))] + : [] + default: + return [] + } +} + +function isJsonObject(input: Schema.Json | undefined): input is Schema.JsonObject { + return typeof input === "object" && input !== null && !Array.isArray(input) +} + +function withoutDescription(check: SchemaAST.Filter): SchemaAST.Filter { + return check.annotate({ + description: undefined, + expected: undefined, + title: undefined + }) +} + +function structuralAnnotations( + ast: SchemaAST.AST, + structuralDescription: string +): Schema.Annotations.Annotations { + const descriptions: Array = [] + appendAnnotationDescription(descriptions, ast.annotations) + if (ast.checks !== undefined) { + for (const check of ast.checks) appendCheckDescriptions(descriptions, check) + } + return { + description: descriptions.length === 0 + ? structuralDescription + : `${structuralDescription}; ${descriptions.join(" and ")}` + } +} + +function appendCheckDescriptions(descriptions: Array, check: SchemaAST.Check): void { + appendAnnotationDescription(descriptions, check.annotations) + if (check._tag === "FilterGroup") { + for (const child of check.checks) appendCheckDescriptions(descriptions, child) + } +} + +function appendAnnotationDescription( + descriptions: Array, + annotations: Schema.Annotations.Annotations | undefined +): void { + const description = annotations?.description ?? annotations?.expected + if (typeof description === "string" && !descriptions.includes(description)) { + descriptions.push(description) + } +} + +type JsonSchemaVisitor = (schema: JsonSchema.JsonSchema) => JsonSchema.JsonSchema + +/** @internal */ +export function walkJsonSchema( + schema: JsonSchema.JsonSchema, + visitor: JsonSchemaVisitor +): JsonSchema.JsonSchema { + const out: JsonSchema.JsonSchema = {} + for (const [key, value] of Object.entries(schema)) { + switch (key) { + case "properties": + case "patternProperties": { + if (isJsonSchema(value)) { + const properties: Record = {} + for (const [name, property] of Object.entries(value)) { + InternalRecord.assignProperty( + properties, + name, + isJsonSchema(property) ? walkJsonSchema(property, visitor) : property + ) + } + InternalRecord.assignProperty(out, key, properties) + } else { + InternalRecord.assignProperty(out, key, value) + } + break + } + case "additionalProperties": + case "items": + case "propertyNames": { + InternalRecord.assignProperty(out, key, isJsonSchema(value) ? walkJsonSchema(value, visitor) : value) + break + } + case "prefixItems": + case "allOf": + case "anyOf": + case "oneOf": { + InternalRecord.assignProperty( + out, + key, + Array.isArray(value) + ? value.map((member) => isJsonSchema(member) ? walkJsonSchema(member, visitor) : member) + : value + ) + break + } + default: + InternalRecord.assignProperty(out, key, value) + break + } + } + return visitor(out) +} + +/** @internal */ +export function appendDescription(schema: JsonSchema.JsonSchema, description: string): void { + if (typeof schema.description === "string") { + const descriptions = schema.description.split(" and ") + if (!descriptions.includes(description)) schema.description += ` and ${description}` + } else { + schema.description = description + } +} + +/** @internal */ +export function isJsonSchema(input: unknown): input is JsonSchema.JsonSchema { + return typeof input === "object" && input !== null && !Array.isArray(input) +} diff --git a/repos/effect/packages/effect/src/unstable/cli/Argument.ts b/repos/effect/packages/effect/src/unstable/cli/Argument.ts index ac10422f52..36928fbae0 100644 --- a/repos/effect/packages/effect/src/unstable/cli/Argument.ts +++ b/repos/effect/packages/effect/src/unstable/cli/Argument.ts @@ -49,10 +49,11 @@ export interface Argument extends Param.Param { * * **Example** (Creating a string argument) * - * ```ts + * ```ts import.meta.vitest * import { Argument } from "effect/unstable/cli" * * const filename = Argument.string("filename") + * filename.kind // => "argument" * ``` * * @category constructors @@ -65,10 +66,11 @@ export const string = (name: string): Argument => Param.string(Param.arg * * **Example** (Creating an integer argument) * - * ```ts + * ```ts import.meta.vitest * import { Argument } from "effect/unstable/cli" * * const count = Argument.integer("count") + * count.kind // => "argument" * ``` * * @category constructors @@ -81,11 +83,12 @@ export const integer = (name: string): Argument => Param.integer(Param.a * * **Example** (Creating file path arguments) * - * ```ts + * ```ts import.meta.vitest * import { Argument } from "effect/unstable/cli" * * const inputFile = Argument.file("input", { mustExist: true }) // Must exist * const outputFile = Argument.file("output", { mustExist: false }) // Must not exist + * const kinds = [inputFile.kind, outputFile.kind] // => ["argument", "argument"] * ``` * * @category constructors @@ -100,10 +103,11 @@ export const file = (name: string, options?: { * * **Example** (Creating a directory path argument) * - * ```ts + * ```ts import.meta.vitest * import { Argument } from "effect/unstable/cli" * * const workspace = Argument.directory("workspace", { mustExist: true }) // Must exist + * workspace.kind // => "argument" * ``` * * @category constructors @@ -118,10 +122,11 @@ export const directory = (name: string, options?: { * * **Example** (Creating a float argument) * - * ```ts + * ```ts import.meta.vitest * import { Argument } from "effect/unstable/cli" * * const ratio = Argument.float("ratio") + * ratio.kind // => "argument" * ``` * * @category constructors @@ -134,10 +139,11 @@ export const float = (name: string): Argument => Param.float(Param.argum * * **Example** (Creating a date argument) * - * ```ts + * ```ts import.meta.vitest * import { Argument } from "effect/unstable/cli" * * const startDate = Argument.date("start-date") + * startDate.kind // => "argument" * ``` * * @category constructors @@ -150,10 +156,11 @@ export const date = (name: string): Argument => Param.date(Param.argumentK * * **Example** (Creating a choice argument) * - * ```ts + * ```ts import.meta.vitest * import { Argument } from "effect/unstable/cli" * * const environment = Argument.choice("environment", ["dev", "staging", "prod"]) + * environment.kind // => "argument" * ``` * * @category constructors @@ -169,10 +176,11 @@ export const choice = >( * * **Example** (Creating a path argument) * - * ```ts + * ```ts import.meta.vitest * import { Argument } from "effect/unstable/cli" * * const configPath = Argument.path("config") + * configPath.kind // => "argument" * ``` * * @category constructors @@ -188,10 +196,11 @@ export const path = (name: string, options?: { * * **Example** (Creating a redacted argument) * - * ```ts + * ```ts import.meta.vitest * import { Argument } from "effect/unstable/cli" * * const secret = Argument.redacted("secret") + * secret.kind // => "argument" * ``` * * @category constructors @@ -204,10 +213,11 @@ export const redacted = (name: string): Argument> => P * * **Example** (Reading file text) * - * ```ts + * ```ts import.meta.vitest * import { Argument } from "effect/unstable/cli" * * const config = Argument.fileText("config-file") + * config.kind // => "argument" * ``` * * @category constructors @@ -226,10 +236,11 @@ export const fileText = (name: string): Argument => Param.fileText(Param * * **Example** (Parsing file content) * - * ```ts + * ```ts import.meta.vitest * import { Argument } from "effect/unstable/cli" * * const config = Argument.fileParse("config", { format: "json" }) + * config.kind // => "argument" * ``` * * @category constructors @@ -245,7 +256,7 @@ export const fileParse = ( * * **Example** (Validating file content with a schema) * - * ```ts + * ```ts import.meta.vitest * import { Schema } from "effect" * import { Argument } from "effect/unstable/cli" * @@ -255,6 +266,7 @@ export const fileParse = ( * }) * * const config = Argument.fileSchema("config", ConfigSchema) + * config.kind // => "argument" * ``` * * @category constructors @@ -271,11 +283,12 @@ export const fileSchema = ( * * **Example** (Creating a sentinel argument) * - * ```ts + * ```ts import.meta.vitest * import { Argument } from "effect/unstable/cli" * * // Used as a placeholder or default in combinators * const noArg = Argument.none + * noArg.kind // => "argument" * ``` * * @category constructors @@ -292,10 +305,11 @@ export const none: Argument = Param.none(Param.argumentKind) * * **Example** (Making an argument optional) * - * ```ts + * ```ts import.meta.vitest * import { Argument } from "effect/unstable/cli" * * const optionalVersion = Argument.string("version").pipe(Argument.optional) + * optionalVersion.kind // => "argument" * ``` * * @category combinators @@ -308,12 +322,13 @@ export const optional = (arg: Argument): Argument> => Par * * **Example** (Adding an argument description) * - * ```ts + * ```ts import.meta.vitest * import { Argument } from "effect/unstable/cli" * * const filename = Argument.string("filename").pipe( * Argument.withDescription("The input file to process") * ) + * filename.kind // => "argument" * ``` * * @category combinators @@ -329,10 +344,11 @@ export const withDescription: { * * **Example** (Providing a default value) * - * ```ts + * ```ts import.meta.vitest * import { Argument } from "effect/unstable/cli" * * const port = Argument.integer("port").pipe(Argument.withDefault(8080)) + * port.kind // => "argument" * ``` * * @category combinators @@ -353,13 +369,14 @@ export const withDefault: { * * **Example** (Loading a fallback config) * - * ```ts + * ```ts import.meta.vitest * import { Config } from "effect" * import { Argument } from "effect/unstable/cli" * * const repository = Argument.string("repository").pipe( * Argument.withFallbackConfig(Config.string("REPOSITORY")) * ) + * repository.kind // => "argument" * ``` * * @category combinators @@ -375,12 +392,13 @@ export const withFallbackConfig: { * * **Example** (Showing a fallback prompt) * - * ```ts + * ```ts import.meta.vitest * import { Argument, Prompt } from "effect/unstable/cli" * * const filename = Argument.string("filename").pipe( * Argument.withFallbackPrompt(Prompt.text({ message: "Filename" })) * ) + * filename.kind // => "argument" * ``` * * @category combinators @@ -396,7 +414,7 @@ export const withFallbackPrompt: { * * **Example** (Accepting multiple values) * - * ```ts + * ```ts import.meta.vitest * import { Argument } from "effect/unstable/cli" * * // Accept any number of files @@ -411,6 +429,8 @@ export const withFallbackPrompt: { * const limitedFiles = Argument.string("files").pipe( * Argument.variadic({ min: 1, max: 5 }) * ) + * + * const kinds = [anyFiles.kind, atLeastOneFile.kind, limitedFiles.kind] // => ["argument", "argument", "argument"] * ``` * * @category combinators @@ -429,12 +449,13 @@ export const variadic: { * * **Example** (Mapping parsed values) * - * ```ts + * ```ts import.meta.vitest * import { Argument } from "effect/unstable/cli" * * const port = Argument.integer("port").pipe( * Argument.map((p) => ({ port: p, url: `http://localhost:${p}` })) * ) + * port.kind // => "argument" * ``` * * @category combinators @@ -450,9 +471,27 @@ export const map: { * * **Example** (Validating values effectfully) * - * ```ts - * import { Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect, FileSystem, Layer, Path, Stdio, Terminal } from "effect" * import { Argument, CliError } from "effect/unstable/cli" + * import { ChildProcessSpawner } from "effect/unstable/process" + * + * const CliTestLayer = Layer.mergeAll( + * FileSystem.layerNoop({}), + * Path.layer, + * Stdio.layerTest({}), + * Layer.succeed(Terminal.Terminal, Terminal.make({ + * columns: Effect.succeed(80), + * rows: Effect.succeed(24), + * readInput: Effect.die("unused"), + * readLine: Effect.die("unused"), + * display: () => Effect.void + * })), + * Layer.succeed( + * ChildProcessSpawner.ChildProcessSpawner, + * ChildProcessSpawner.make(() => Effect.die("unused")) + * ) + * ) * * const files = Argument.string("files").pipe( * Argument.mapEffect((file) => @@ -460,11 +499,17 @@ export const map: { * ? Effect.succeed(file) * : Effect.fail( * new CliError.UserError({ - * cause: new Error("Only .txt files allowed") + * cause: new Error(`Unsupported file extension: ${file}`), + * userMessage: "Only .txt files allowed" * }) * ) * ) * ) + * + * const [, value] = await Effect.runPromise( + * files.parse({ arguments: ["notes.txt"], flags: {} }).pipe(Effect.provide(CliTestLayer)) + * ) + * value // => "notes.txt" * ``` * * @category combinators @@ -488,8 +533,27 @@ export const mapEffect: { * * **Example** (Mapping values that may throw) * - * ```ts + * ```ts import.meta.vitest + * import { Effect, FileSystem, Layer, Path, Stdio, Terminal } from "effect" * import { Argument } from "effect/unstable/cli" + * import { ChildProcessSpawner } from "effect/unstable/process" + * + * const CliTestLayer = Layer.mergeAll( + * FileSystem.layerNoop({}), + * Path.layer, + * Stdio.layerTest({}), + * Layer.succeed(Terminal.Terminal, Terminal.make({ + * columns: Effect.succeed(80), + * rows: Effect.succeed(24), + * readInput: Effect.die("unused"), + * readLine: Effect.die("unused"), + * display: () => Effect.void + * })), + * Layer.succeed( + * ChildProcessSpawner.ChildProcessSpawner, + * ChildProcessSpawner.make(() => Effect.die("unused")) + * ) + * ) * * const json = Argument.string("data").pipe( * Argument.mapTryCatch( @@ -498,6 +562,11 @@ export const mapEffect: { * `Invalid JSON: ${error instanceof Error ? error.message : String(error)}` * ) * ) + * + * const [, value] = await Effect.runPromise( + * json.parse({ arguments: ['{"enabled":true}'], flags: {} }).pipe(Effect.provide(CliTestLayer)) + * ) + * value // => { enabled: true } * ``` * * @category combinators @@ -520,10 +589,11 @@ export const mapTryCatch: { * * **Example** (Requiring a minimum number of values) * - * ```ts + * ```ts import.meta.vitest * import { Argument } from "effect/unstable/cli" * * const files = Argument.string("files").pipe(Argument.atLeast(1)) + * files.kind // => "argument" * ``` * * @category combinators @@ -539,10 +609,11 @@ export const atLeast: { * * **Example** (Limiting the maximum number of values) * - * ```ts + * ```ts import.meta.vitest * import { Argument } from "effect/unstable/cli" * * const files = Argument.string("files").pipe(Argument.atMost(5)) + * files.kind // => "argument" * ``` * * @category combinators @@ -558,10 +629,11 @@ export const atMost: { * * **Example** (Requiring a range of values) * - * ```ts + * ```ts import.meta.vitest * import { Argument } from "effect/unstable/cli" * * const files = Argument.string("files").pipe(Argument.between(1, 5)) + * files.kind // => "argument" * ``` * * @category combinators @@ -577,29 +649,34 @@ export const between: { * * **Example** (Validating parsed values with a schema) * - * ```ts + * ```ts import.meta.vitest * import { Schema } from "effect" * import { Argument } from "effect/unstable/cli" * * const input = Argument.string("input").pipe( * Argument.withSchema(Schema.NonEmptyString) * ) + * input.kind // => "argument" * ``` * * @category combinators * @since 4.0.0 */ export const withSchema: { - (schema: Schema.Codec): (self: Argument) => Argument - (self: Argument, schema: Schema.Codec): Argument -} = dual(2, (self: Argument, schema: Schema.Codec) => Param.withSchema(self, schema)) + (schema: Schema.ConstraintCodec): (self: Argument) => Argument + (self: Argument, schema: Schema.ConstraintCodec): Argument +} = dual( + 2, + (self: Argument, schema: Schema.ConstraintCodec): Argument => + Param.withSchema(self, schema) +) /** * Creates a positional choice argument with custom value mapping. * * **Example** (Mapping choices to values) * - * ```ts + * ```ts import.meta.vitest * import { Argument } from "effect/unstable/cli" * * const logLevel = Argument.choiceWithValue("level", [ @@ -608,6 +685,7 @@ export const withSchema: { * ["warn", 2], * ["error", 3] * ]) + * logLevel.kind // => "argument" * ``` * * @category constructors @@ -632,12 +710,13 @@ export const choiceWithValue = "argument" * ``` * * @category metadata @@ -653,7 +732,7 @@ export const withMetavar: { * * **Example** (Filtering parsed values) * - * ```ts + * ```ts import.meta.vitest * import { Argument } from "effect/unstable/cli" * * const positiveInt = Argument.integer("count").pipe( @@ -662,6 +741,7 @@ export const withMetavar: { * (n) => `Expected positive integer, got ${n}` * ) * ) + * positiveInt.kind // => "argument" * ``` * * @category combinators @@ -682,7 +762,7 @@ export const filter: { * * **Example** (Filtering and mapping parsed values) * - * ```ts + * ```ts import.meta.vitest * import { Option } from "effect" * import { Argument } from "effect/unstable/cli" * @@ -692,6 +772,7 @@ export const filter: { * (n) => `Expected positive integer, got ${n}` * ) * ) + * positiveInt.kind // => "argument" * ``` * * @category combinators @@ -711,12 +792,13 @@ export const filterMap: { * * **Example** (Providing a fallback argument) * - * ```ts + * ```ts import.meta.vitest * import { Argument } from "effect/unstable/cli" * * const value = Argument.integer("value").pipe( * Argument.orElse(() => Argument.string("value")) * ) + * value.kind // => "argument" * ``` * * @category combinators @@ -732,13 +814,14 @@ export const orElse: { * * **Example** (Returning which fallback succeeded) * - * ```ts + * ```ts import.meta.vitest * import { Argument } from "effect/unstable/cli" * * const source = Argument.file("source").pipe( * Argument.orElseResult(() => Argument.string("url")) * ) * // Returns Result + * source.kind // => "argument" * ``` * * @category combinators diff --git a/repos/effect/packages/effect/src/unstable/cli/CliConfig.ts b/repos/effect/packages/effect/src/unstable/cli/CliConfig.ts new file mode 100644 index 0000000000..cab8e1687f --- /dev/null +++ b/repos/effect/packages/effect/src/unstable/cli/CliConfig.ts @@ -0,0 +1,85 @@ +/** + * Configuration for Effect CLI command execution. + * + * @since 4.0.0 + */ +import * as Context from "../../Context.ts" +import * as Layer from "../../Layer.ts" +import * as GlobalFlag from "./GlobalFlag.ts" + +/** + * Context reference for configuration shared by CLI parsing, help generation, + * and command execution. + * + * **When to use** + * + * Use when you need to customize runner-wide CLI behavior, such as which + * built-in global flags are available. + * + * @category services + * @since 4.0.0 + */ +export class CliConfig extends Context.Reference("effect/unstable/cli/CliConfig", { + defaultValue: () => defaults +}) {} + +/** + * Types used by the `CliConfig` context reference. + * + * @since 4.0.0 + */ +export declare namespace CliConfig { + /** + * Configuration values used while running a CLI command. + * + * @category models + * @since 4.0.0 + */ + export interface Service { + /** Ordered built-in global flags, with earlier action flags taking precedence. */ + readonly builtIns: ReadonlyArray + } +} + +/** + * Default CLI configuration containing every built-in global flag. + * + * @category defaults + * @since 4.0.0 + */ +export const defaults: CliConfig.Service = { + builtIns: GlobalFlag.BuiltIns +} + +/** + * Creates CLI configuration by merging the provided options over `defaults`. + * + * **When to use** + * + * Use when you need a configuration value to provide directly through the + * `CliConfig` context reference. + * + * @see {@link layer} for providing configuration as a layer + * + * @category constructors + * @since 4.0.0 + */ +export const make = (options?: Partial): CliConfig.Service => ({ + ...defaults, + ...options +}) + +/** + * Creates a layer that provides CLI configuration merged over `defaults`. + * + * **When to use** + * + * Use when wiring customized CLI behavior into an application layer. + * + * @see {@link make} for creating a configuration value directly + * + * @category layers + * @since 4.0.0 + */ +export const layer = (options?: Partial): Layer.Layer => + Layer.succeed(CliConfig, make(options)) diff --git a/repos/effect/packages/effect/src/unstable/cli/CliError.ts b/repos/effect/packages/effect/src/unstable/cli/CliError.ts index 743048e572..ac8a9c877a 100644 --- a/repos/effect/packages/effect/src/unstable/cli/CliError.ts +++ b/repos/effect/packages/effect/src/unstable/cli/CliError.ts @@ -2,11 +2,11 @@ * Defines structured errors for the unstable CLI parser and runner. * * CLI errors describe problems such as unknown or duplicate flags, missing - * flags or arguments, invalid values, unknown subcommands, user handler - * failures, and requests to show command help. This module includes the - * `CliError` union, the `isCliError` guard, schema-backed error classes with - * display messages, and the `NonShowHelpErrors` union used when parse or - * validation errors should be shown with help output. + * flags or arguments, unexpected positional arguments, invalid values, unknown + * subcommands, user handler failures, and requests to show command help. This + * module includes the `CliError` union, the `isCliError` guard, schema-backed + * error classes with display messages, and the `NonShowHelpErrors` union used + * when parse or validation errors should be shown with help output. * * @since 4.0.0 */ @@ -25,26 +25,16 @@ const TypeId = "~effect/cli/CliError" * * **Example** (Checking CLI errors) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * import { CliError } from "effect/unstable/cli" * - * const handleError = (error: unknown) => { - * if (CliError.isCliError(error)) { - * console.log("CLI Error:", error.message) - * return Effect.succeed("Handled CLI error") - * } - * return Effect.fail("Unknown error") - * } + * const error = new CliError.MissingOption({ option: "api-key" }) + * const program = CliError.isCliError(error) + * ? Effect.succeed(error.message) + * : Effect.fail("Unknown error") * - * // Example usage in error handling - * const program = Effect.gen(function*() { - * const result = yield* Effect.try({ - * try: () => ({ success: true }), - * catch: (error) => error - * }) - * handleError(result) - * }) + * await Effect.runPromise(program) // => "Missing required flag: --api-key" * ``` * * @category guards @@ -57,31 +47,28 @@ export const isCliError = (u: unknown): u is CliError => Predicate.hasProperty(u * * **Example** (Handling CLI errors) * - * ```ts - * import type { CliError } from "effect/unstable/cli" + * ```ts import.meta.vitest + * import { CliError } from "effect/unstable/cli" * - * const handleCliError = (error: CliError.CliError): void => { + * const describe = (error: CliError.CliError): string => { * switch (error._tag) { * case "UnrecognizedOption": - * console.log(`Unknown flag: ${error.option}`) - * break + * return `Unknown flag: ${error.option}` * case "MissingOption": - * console.log(`Required flag missing: ${error.option}`) - * break + * return `Required flag missing: ${error.option}` * case "InvalidValue": - * console.log(`Invalid value: ${error.value} for ${error.option}`) - * break + * return `Invalid value: ${error.value} for ${error.option}` * case "ShowHelp": - * // Display help for the command path - * console.log(`Help requested for: ${error.commandPath.join(" ")}`) - * break + * return `Help requested for: ${error.commandPath.join(" ")}` * default: - * console.log(error.message) + * return error.message * } * } + * + * describe(new CliError.MissingOption({ option: "token" })) // => "Required flag missing: token" * ``` * - * @category models + * @category errors * @since 4.0.0 */ export type CliError = @@ -89,6 +76,7 @@ export type CliError = | DuplicateOption | MissingOption | MissingArgument + | UnexpectedArgument | InvalidValue | UnknownSubcommand | ShowHelp @@ -99,7 +87,7 @@ export type CliError = * * **Example** (Creating unrecognized option errors) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * import { CliError } from "effect/unstable/cli" * @@ -110,25 +98,26 @@ export type CliError = * suggestions: ["--verbose", "--force"] * }) * - * console.log(unrecognizedError.message) - * // "Unrecognized flag: --unknown-flag in command deploy production - * // - * // Did you mean this? - * // --verbose - * // --force" + * unrecognizedError._tag // => "UnrecognizedOption" + * unrecognizedError.option // => "--unknown-flag" + * unrecognizedError.command // => ["deploy", "production"] * * // In CLI parsing context * const parseCommand = Effect.gen(function*() { * // If parsing encounters unknown flag * return yield* unrecognizedError * }) + * + * const parseError = await Effect.runPromise(Effect.flip(parseCommand)) + * parseError._tag // => "UnrecognizedOption" * ``` * - * @category models + * @category errors * @since 4.0.0 */ -export class UnrecognizedOption extends Schema.ErrorClass(`${TypeId}/UnrecognizedOption`)({ - _tag: Schema.tag("UnrecognizedOption"), +export class UnrecognizedOption extends Schema.TaggedError( + `${TypeId}/UnrecognizedOption` +)("UnrecognizedOption", { option: Schema.String, command: Schema.optional(Schema.Array(Schema.String)), suggestions: Schema.Array(Schema.String) @@ -161,7 +150,7 @@ export class UnrecognizedOption extends Schema.ErrorClass(`$ * * **Example** (Creating duplicate option errors) * - * ```ts + * ```ts import.meta.vitest * import { CliError } from "effect/unstable/cli" * * const duplicateError = new CliError.DuplicateOption({ @@ -170,16 +159,18 @@ export class UnrecognizedOption extends Schema.ErrorClass(`$ * childCommand: "deploy" * }) * - * console.log(duplicateError.message) - * // "Duplicate flag name "--verbose" in parent command "myapp" and subcommand "deploy". - * // Parent will always claim this flag (Mode A semantics). Consider renaming one of them to avoid confusion." + * duplicateError._tag // => "DuplicateOption" + * duplicateError.option // => "--verbose" + * duplicateError.parentCommand // => "myapp" + * duplicateError.childCommand // => "deploy" * ``` * - * @category models + * @category errors * @since 4.0.0 */ -export class DuplicateOption extends Schema.ErrorClass(`${TypeId}/DuplicateOption`)({ - _tag: Schema.tag("DuplicateOption"), +export class DuplicateOption extends Schema.TaggedError( + `${TypeId}/DuplicateOption` +)("DuplicateOption", { option: Schema.String, parentCommand: Schema.String, childCommand: Schema.String @@ -207,7 +198,7 @@ export class DuplicateOption extends Schema.ErrorClass(`${TypeI * * **Example** (Creating missing option errors) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * import { CliError } from "effect/unstable/cli" * @@ -215,8 +206,7 @@ export class DuplicateOption extends Schema.ErrorClass(`${TypeI * option: "api-key" * }) * - * console.log(missingOptionError.message) - * // "Missing required flag: --api-key" + * const details = [missingOptionError._tag, missingOptionError.option] // => ["MissingOption", "api-key"] * * // In validation context * const validateRequiredOptions = (options: Record) => @@ -227,13 +217,17 @@ export class DuplicateOption extends Schema.ErrorClass(`${TypeI * } * return apiKey * }) + * + * const validationError = await Effect.runPromise(Effect.flip(validateRequiredOptions({}))) + * validationError._tag // => "MissingOption" * ``` * - * @category models + * @category errors * @since 4.0.0 */ -export class MissingOption extends Schema.ErrorClass(`${TypeId}/MissingOption`)({ - _tag: Schema.tag("MissingOption"), +export class MissingOption extends Schema.TaggedError( + `${TypeId}/MissingOption` +)("MissingOption", { option: Schema.String }) { /** @@ -258,7 +252,7 @@ export class MissingOption extends Schema.ErrorClass(`${TypeId}/M * * **Example** (Creating missing argument errors) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * import { CliError } from "effect/unstable/cli" * @@ -266,8 +260,7 @@ export class MissingOption extends Schema.ErrorClass(`${TypeId}/M * argument: "target" * }) * - * console.log(missingArgError.message) - * // "Missing required argument: target" + * const details = [missingArgError._tag, missingArgError.argument] // => ["MissingArgument", "target"] * * // In argument parsing * const parseArguments = (args: Array) => @@ -277,13 +270,17 @@ export class MissingOption extends Schema.ErrorClass(`${TypeId}/M * } * return args[0] * }) + * + * const parseError = await Effect.runPromise(Effect.flip(parseArguments([]))) + * parseError._tag // => "MissingArgument" * ``` * - * @category models + * @category errors * @since 4.0.0 */ -export class MissingArgument extends Schema.ErrorClass(`${TypeId}/MissingArgument`)({ - _tag: Schema.tag("MissingArgument"), +export class MissingArgument extends Schema.TaggedError( + `${TypeId}/MissingArgument` +)("MissingArgument", { argument: Schema.String }) { /** @@ -303,13 +300,54 @@ export class MissingArgument extends Schema.ErrorClass(`${TypeI } } +/** + * Error thrown when positional arguments remain after a command has parsed all + * of its parameters. + * + * **Example** (Reporting unexpected arguments) + * + * ```ts import.meta.vitest + * import { CliError } from "effect/unstable/cli" + * + * const error = new CliError.UnexpectedArgument({ + * arguments: ["extra.txt"] + * }) + * + * const details = [error._tag, error.arguments] // => ["UnexpectedArgument", ["extra.txt"]] + * ``` + * + * @category errors + * @since 4.0.0 + */ +export class UnexpectedArgument extends Schema.TaggedError( + `${TypeId}/UnexpectedArgument` +)("UnexpectedArgument", { + arguments: Schema.Array(Schema.String) +}) { + /** + * Marks this value as an unexpected CLI argument error for runtime guards. + * + * @since 4.0.0 + */ + readonly [TypeId] = TypeId + + /** + * Formats the unexpected positional arguments for display. + * + * @since 4.0.0 + */ + override get message() { + const label = this.arguments.length === 1 ? "argument" : "arguments" + return `Unexpected positional ${label}: ${this.arguments.map((value) => JSON.stringify(value)).join(", ")}` + } +} + /** * Error thrown when an option or argument value is invalid. * * **Example** (Creating invalid value errors) * - * ```ts - * import { Effect } from "effect" + * ```ts import.meta.vitest * import { CliError } from "effect/unstable/cli" * * const invalidValueError = new CliError.InvalidValue({ @@ -319,8 +357,10 @@ export class MissingArgument extends Schema.ErrorClass(`${TypeI * kind: "flag" * }) * - * console.log(invalidValueError.message) - * // "Invalid value for flag --port: "abc123". Expected: integer between 1 and 65535" + * invalidValueError._tag // => "InvalidValue" + * invalidValueError.kind // => "flag" + * invalidValueError.option // => "port" + * invalidValueError.value // => "abc123" * * // For positional arguments * const invalidArgError = new CliError.InvalidValue({ @@ -330,15 +370,15 @@ export class MissingArgument extends Schema.ErrorClass(`${TypeI * kind: "argument" * }) * - * console.log(invalidArgError.message) - * // "Invalid value for argument : "abc". Expected: integer" + * const details = [invalidArgError.kind, invalidArgError.option, invalidArgError.value] // => ["argument", "count", "abc"] * ``` * - * @category models + * @category errors * @since 4.0.0 */ -export class InvalidValue extends Schema.ErrorClass(`${TypeId}/InvalidValue`)({ - _tag: Schema.tag("InvalidValue"), +export class InvalidValue extends Schema.TaggedError( + `${TypeId}/InvalidValue` +)("InvalidValue", { option: Schema.String, value: Schema.String, expected: Schema.String, @@ -357,10 +397,16 @@ export class InvalidValue extends Schema.ErrorClass(`${TypeId}/Inv * @since 4.0.0 */ override get message() { + const expectation = this.expected.startsWith("Expected ") || this.expected.startsWith("Expected:") + ? this.expected + : `Expected: ${this.expected}` if (this.kind === "argument") { - return `Invalid value for argument <${this.option}>: "${this.value}". Expected: ${this.expected}` + return `Invalid value for argument <${this.option}>: "${this.value}". ${expectation}` } - return `Invalid value for flag --${this.option}: "${this.value}". Expected: ${this.expected}` + if (this.value.length === 0) { + return `Missing value for flag --${this.option}. ${expectation}` + } + return `Invalid value for flag --${this.option}: "${this.value}". ${expectation}` } } @@ -369,7 +415,7 @@ export class InvalidValue extends Schema.ErrorClass(`${TypeId}/Inv * * **Example** (Creating unknown subcommand errors) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * import { CliError } from "effect/unstable/cli" * @@ -379,12 +425,9 @@ export class InvalidValue extends Schema.ErrorClass(`${TypeId}/Inv * suggestions: ["deploy", "destroy"] * }) * - * console.log(unknownSubcommandError.message) - * // "Unknown subcommand "deplyo" for "myapp" - * // - * // Did you mean this? - * // deploy - * // destroy" + * unknownSubcommandError._tag // => "UnknownSubcommand" + * unknownSubcommandError.subcommand // => "deplyo" + * unknownSubcommandError.parent // => ["myapp"] * * // In subcommand parsing * const parseSubcommand = (subcommand: string) => @@ -395,13 +438,17 @@ export class InvalidValue extends Schema.ErrorClass(`${TypeId}/Inv * } * return subcommand * }) + * + * const parseError = await Effect.runPromise(Effect.flip(parseSubcommand("deplyo"))) + * parseError._tag // => "UnknownSubcommand" * ``` * - * @category models + * @category errors * @since 4.0.0 */ -export class UnknownSubcommand extends Schema.ErrorClass(`${TypeId}/UnknownSubcommand`)({ - _tag: Schema.tag("UnknownSubcommand"), +export class UnknownSubcommand extends Schema.TaggedError( + `${TypeId}/UnknownSubcommand` +)("UnknownSubcommand", { subcommand: Schema.String, parent: Schema.optional(Schema.Array(Schema.String)), suggestions: Schema.Array(Schema.String) @@ -431,15 +478,20 @@ export class UnknownSubcommand extends Schema.ErrorClass(`${T /** * Error wrapper for user handler failures in the CLI error channel. * + * `userMessage` can provide safe, user-facing text independently of the + * underlying cause. When omitted or empty, `message` uses a non-empty string + * cause or `Error.message`, then falls back to `"An error occurred"`. + * * **Example** (Wrapping user errors) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * import { CliError } from "effect/unstable/cli" * * // Wrapping user errors * const userError = new CliError.UserError({ - * cause: new Error("Database connection failed") + * cause: new Error("Database connection failed for postgres://localhost"), + * userMessage: "Could not connect to the database" * }) * * // In command handler @@ -454,19 +506,23 @@ export class UnknownSubcommand extends Schema.ErrorClass(`${T * // In error handling * const handleError = (error: CliError.CliError): Effect.Effect => { * if (error._tag === "UserError") { - * console.log("Command failed:", error.cause) * return Effect.succeed(1) // Exit code 1 * } * return Effect.succeed(0) * } + * + * await Effect.runPromise(deployCommand) // => { deployed: true } + * await Effect.runPromise(handleError(userError)) // => 1 * ``` * - * @category models + * @category errors * @since 4.0.0 */ -export class UserError extends Schema.ErrorClass(`${TypeId}/UserError`)({ - _tag: Schema.tag("UserError"), - cause: Schema.Defect() +export class UserError extends Schema.TaggedError( + `${TypeId}/UserError` +)("UserError", { + cause: Schema.Defect(), + userMessage: Schema.optionalKey(Schema.String) }) { /** * Marks this value as a user handler error for runtime guards. @@ -474,6 +530,26 @@ export class UserError extends Schema.ErrorClass(`${TypeId}/UserError * @since 4.0.0 */ readonly [TypeId] = TypeId + + /** + * Controls whether the runtime logger should report this error. The CLI + * runner sets this to `false` after rendering the error itself. + * + * @since 4.0.0 + */ + override [Runtime.errorReported] = true + + /** + * Returns the explicit user-facing message or a safe fallback from `cause`. + * + * @since 4.0.0 + */ + override get message() { + if (this.userMessage) return this.userMessage + if (typeof this.cause === "string" && this.cause) return this.cause + if (this.cause instanceof Error && this.cause.message) return this.cause.message + return "An error occurred" + } } /** @@ -484,7 +560,7 @@ export class UserError extends Schema.ErrorClass(`${TypeId}/UserError * This excludes `ShowHelp` itself, allowing parse and validation errors to be * stored in `ShowHelp.errors` without nesting another help-control value. * - * @category models + * @category schemas * @since 4.0.0 */ export const NonShowHelpErrors: Schema.Union< @@ -493,6 +569,7 @@ export const NonShowHelpErrors: Schema.Union< typeof DuplicateOption, typeof MissingOption, typeof MissingArgument, + typeof UnexpectedArgument, typeof InvalidValue, typeof UnknownSubcommand, typeof UserError @@ -502,6 +579,7 @@ export const NonShowHelpErrors: Schema.Union< DuplicateOption, MissingOption, MissingArgument, + UnexpectedArgument, InvalidValue, UnknownSubcommand, UserError @@ -516,7 +594,7 @@ export const NonShowHelpErrors: Schema.Union< * runner should display help along with the underlying parse or validation * failures. * - * @category models + * @category errors * @since 4.0.0 */ export type NonShowHelpErrors = typeof NonShowHelpErrors.Type @@ -530,11 +608,12 @@ export type NonShowHelpErrors = typeof NonShowHelpErrors.Type * that should be shown with help text. When `errors` is non-empty, the runtime * exit code is `1`; otherwise it is `0`. * - * @category models + * @category errors * @since 4.0.0 */ -export class ShowHelp extends Schema.ErrorClass(`${TypeId}/ShowHelp`)({ - _tag: Schema.tag("ShowHelp"), +export class ShowHelp extends Schema.TaggedError( + `${TypeId}/ShowHelp` +)("ShowHelp", { commandPath: Schema.Array(Schema.String), errors: Schema.Array(NonShowHelpErrors) }) { diff --git a/repos/effect/packages/effect/src/unstable/cli/CliOutput.ts b/repos/effect/packages/effect/src/unstable/cli/CliOutput.ts index e65ccdb584..53aa8eaf25 100644 --- a/repos/effect/packages/effect/src/unstable/cli/CliOutput.ts +++ b/repos/effect/packages/effect/src/unstable/cli/CliOutput.ts @@ -22,7 +22,7 @@ import type { HelpDoc } from "./HelpDoc.ts" * * **Example** (Customizing CLI output formatting) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * import { CliOutput } from "effect/unstable/cli" * @@ -38,11 +38,12 @@ import type { HelpDoc } from "./HelpDoc.ts" * // Use the custom formatter in a program * const program = Effect.gen(function*() { * const formatter = yield* CliOutput.Formatter - * const helpText = formatter.formatVersion("myapp", "1.0.0") - * console.log(helpText) + * return formatter.formatVersion("myapp", "1.0.0") * }).pipe( * Effect.provide(CliOutput.layer(customFormatter)) * ) + * + * await Effect.runPromise(program) // => "myapp (1.0.0)" * ``` * * @category models @@ -54,7 +55,7 @@ export interface Formatter { * * **Example** (Formatting help documents) * - * ```ts + * ```ts import.meta.vitest * import { Option as O } from "effect" * import { CliOutput } from "effect/unstable/cli" * import type { HelpDoc } from "effect/unstable/cli" @@ -84,8 +85,7 @@ export interface Formatter { * * const formatter = CliOutput.defaultFormatter() * const helpText = formatter.formatHelpDoc(helpDoc) - * console.log(helpText) - * // Outputs formatted help with sections: DESCRIPTION, USAGE, ARGUMENTS, FLAGS + * const sectionsPresent = [helpText.includes("DESCRIPTION"), helpText.includes("FLAGS")] // => [true, true] * ``` * * @since 4.0.0 @@ -97,7 +97,7 @@ export interface Formatter { * * **Example** (Formatting CLI errors) * - * ```ts + * ```ts import.meta.vitest * import { Data } from "effect" * import { CliOutput } from "effect/unstable/cli" * @@ -108,7 +108,7 @@ export interface Formatter { * const formatter = CliOutput.defaultFormatter() * const error = new InvalidOption({ message: "Unknown flag '--invalid'" }) * const errorMessage = formatter.formatCliError(error) - * console.log(errorMessage) // "Unknown flag '--invalid'" + * errorMessage // => "Unknown flag '--invalid'" * ``` * * @since 4.0.0 @@ -120,7 +120,7 @@ export interface Formatter { * * **Example** (Formatting error sections) * - * ```ts + * ```ts import.meta.vitest * import { Data } from "effect" * import { CliOutput } from "effect/unstable/cli" * @@ -134,10 +134,10 @@ export interface Formatter { * const error = new ValidationError({ message: "Value must be positive" }) * * const coloredError = colorFormatter.formatError(error) - * console.log(coloredError) // "\n\x1b[1m\x1b[31mERROR\x1b[0m\n Value must be positive\x1b[0m" + * coloredError.includes("\u001b[31mERROR") // => true * * const plainError = noColorFormatter.formatError(error) - * console.log(plainError) // "\nERROR\n Value must be positive" + * plainError // => "\nERROR\n Value must be positive" * ``` * * @since 4.0.0 @@ -149,7 +149,7 @@ export interface Formatter { * * **Example** (Formatting version output) * - * ```ts + * ```ts import.meta.vitest * import { CliOutput } from "effect/unstable/cli" * * const colorFormatter = CliOutput.defaultFormatter({ colors: true }) @@ -159,10 +159,10 @@ export interface Formatter { * const version = "1.2.3" * * const coloredVersion = colorFormatter.formatVersion(appName, version) - * console.log(coloredVersion) // "\x1b[1mmy-awesome-tool\x1b[0m \x1b[2mv\x1b[0m\x1b[1m1.2.3\x1b[0m" + * coloredVersion // => "\u001b[1mmy-awesome-tool\u001b[0m \u001b[2mv\u001b[0m\u001b[1m1.2.3\u001b[0m" * * const plainVersion = noColorFormatter.formatVersion(appName, version) - * console.log(plainVersion) // "my-awesome-tool v1.2.3" + * plainVersion // => "my-awesome-tool v1.2.3" * ``` * * @since 4.0.0 @@ -174,7 +174,7 @@ export interface Formatter { * * **Example** (Formatting grouped errors) * - * ```ts + * ```ts import.meta.vitest * import { CliError, CliOutput } from "effect/unstable/cli" * * const formatter = CliOutput.defaultFormatter({ colors: false }) @@ -189,7 +189,7 @@ export interface Formatter { * ] * * const output = formatter.formatErrors(errors) - * // Groups errors by type and displays all at once + * const optionsPresent = [output.includes("--foo"), output.includes("--required")] // => [true, true] * ``` * * @since 4.0.0 @@ -203,7 +203,7 @@ export interface Formatter { * * **Example** (Accessing the output formatter) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * import { CliOutput } from "effect/unstable/cli" * @@ -212,14 +212,13 @@ export interface Formatter { * const formatter = yield* CliOutput.Formatter * * // Format version information - * const versionText = formatter.formatVersion("my-cli", "2.1.0") - * console.log(versionText) // "my-cli v2.1.0" (with colors if supported) - * - * return versionText + * return formatter.formatVersion("my-cli", "2.1.0") * }) * * // Run with default formatter - * const result = Effect.runSync(program) + * await Effect.runPromise(program.pipe( + * Effect.provide(CliOutput.layer(CliOutput.defaultFormatter({ colors: false }))) + * )) // => "my-cli v2.1.0" * ``` * * @category services @@ -235,8 +234,8 @@ export const Formatter: Context.Reference = Context.Reference( * * **Example** (Providing a custom formatter) * - * ```ts - * import { Console, Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect } from "effect" * import { CliOutput } from "effect/unstable/cli" * * // Create a custom formatter without colors @@ -246,22 +245,12 @@ export const Formatter: Context.Reference = Context.Reference( * // Create a program that uses the custom formatter * const program = Effect.gen(function*() { * const formatter = yield* CliOutput.Formatter - * const versionText = formatter.formatVersion("my-cli", "1.0.0") - * yield* Console.log(`Using custom formatter: ${versionText}`) + * return formatter.formatVersion("my-cli", "1.0.0") * }).pipe( * Effect.provide(NoColorLayer) * ) * - * // You can also create completely custom formatters - * const jsonFormatter: CliOutput.Formatter = { - * formatHelpDoc: (doc) => JSON.stringify(doc, null, 2), - * formatCliError: (error) => JSON.stringify({ error: error.message }), - * formatError: (error) => - * JSON.stringify({ type: "error", message: error.message }), - * formatVersion: (name, version) => JSON.stringify({ name, version }), - * formatErrors: (errors) => JSON.stringify(errors.map((error) => error.message)) - * } - * const JsonLayer = CliOutput.layer(jsonFormatter) + * await Effect.runPromise(program) // => "my-cli v1.0.0" * ``` * * @category layers @@ -269,13 +258,17 @@ export const Formatter: Context.Reference = Context.Reference( */ export const layer = (formatter: Formatter): Layer.Layer => Layer.succeed(Formatter)(formatter) +const escapeControlCharacters = (text: string): string => + // oxlint-disable-next-line no-control-regex + text.replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/g, (character) => + `\\x${character.charCodeAt(0).toString(16).padStart(2, "0")}`) + /** * Creates a default formatter with configurable options. * * **Example** (Creating default formatters) * - * ```ts - * import { Effect } from "effect" + * ```ts import.meta.vitest * import { CliError, CliOutput } from "effect/unstable/cli" * * // Create a formatter without colors for tests or CI environments @@ -287,23 +280,16 @@ export const layer = (formatter: Formatter): Layer.Layer => Layer.succeed * // Auto-detect colors based on terminal support (default behavior) * const autoFormatter = CliOutput.defaultFormatter() * - * const program = Effect.gen(function*() { - * const formatter = colorFormatter - * - * // Format an error with proper styling - * const error = new CliError.InvalidValue({ - * option: "foo", - * value: "bar", - * expected: "baz", - * kind: "flag" - * }) - * const errorText = formatter.formatError(error) - * console.log(errorText) - * - * // Format version information - * const versionText = formatter.formatVersion("my-tool", "1.2.3") - * console.log(versionText) + * const error = new CliError.InvalidValue({ + * option: "foo", + * value: "bar", + * expected: "baz", + * kind: "flag" * }) + * + * noColorFormatter.formatError(error).includes("Invalid value") // => true + * colorFormatter.formatVersion("my-tool", "1.2.3").includes("my-tool") // => true + * autoFormatter.formatVersion("my-tool", "1.2.3").includes("1.2.3") // => true * ``` * * @category constructors @@ -349,14 +335,14 @@ export const defaultFormatter = (options?: { colors?: boolean }): Formatter => { return { formatHelpDoc: (doc: HelpDoc): string => formatHelpDocImpl(doc, colors), - formatCliError: (error): string => error.message, + formatCliError: (error): string => escapeControlCharacters(error.message), formatError: (error): string => { - return `\n${bold}${red}ERROR${reset}\n ${error.message}${reset}` + return `\n${bold}${red}ERROR${reset}\n ${escapeControlCharacters(error.message)}${reset}` }, formatErrors: (errors): string => { if (errors.length === 0) return "" if (errors.length === 1) { - return `\n${bold}${red}ERROR${reset}\n ${errors[0].message}${reset}` + return `\n${bold}${red}ERROR${reset}\n ${escapeControlCharacters(errors[0].message)}${reset}` } // Group errors by _tag @@ -373,7 +359,7 @@ export const defaultFormatter = (options?: { colors?: boolean }): Formatter => { for (const [, group] of grouped) { for (const error of group) { - sections.push(` ${error.message}${reset}`) + sections.push(` ${escapeControlCharacters(error.message)}${reset}`) } } diff --git a/repos/effect/packages/effect/src/unstable/cli/Command.ts b/repos/effect/packages/effect/src/unstable/cli/Command.ts index 97af815f3c..d4027c882c 100644 --- a/repos/effect/packages/effect/src/unstable/cli/Command.ts +++ b/repos/effect/packages/effect/src/unstable/cli/Command.ts @@ -22,10 +22,12 @@ import type * as Path from "../../Path.ts" import * as Predicate from "../../Predicate.ts" import * as References from "../../References.ts" import * as Result from "../../Result.ts" +import * as Runtime from "../../Runtime.ts" import * as Stdio from "../../Stdio.ts" import * as Terminal from "../../Terminal.ts" import type { Contravariant, Covariant, NoInfer, Simplify } from "../../Types.ts" import type { ChildProcessSpawner } from "../process/ChildProcessSpawner.ts" +import * as CliConfig from "./CliConfig.ts" import * as CliError from "./CliError.ts" import * as CliOutput from "./CliOutput.ts" import * as GlobalFlag from "./GlobalFlag.ts" @@ -34,7 +36,9 @@ import { mergeConfig, parseConfig } from "./internal/config.ts" import { getGlobalFlagsForCommandPath, getGlobalFlagsForCommandTree, getHelpForCommandPath } from "./internal/help.ts" import * as Lexer from "./internal/lexer.ts" import * as Parser from "./internal/parser.ts" +import * as Wizard from "./internal/wizard.ts" import * as Param from "./Param.ts" +import * as Prompt from "./Prompt.ts" /* ========================================================================== */ /* Public Types */ @@ -54,9 +58,27 @@ import * as Param from "./Param.ts" * * **Example** (Defining CLI commands) * - * ```ts - * import { Console } from "effect" + * ```ts import.meta.vitest + * import { Effect, FileSystem, Layer, Path, Stdio, Terminal } from "effect" * import { Argument, Command, Flag } from "effect/unstable/cli" + * import { ChildProcessSpawner } from "effect/unstable/process" + * + * const CliTestLayer = Layer.mergeAll( + * FileSystem.layerNoop({}), + * Path.layer, + * Stdio.layerTest({}), + * Layer.succeed(Terminal.Terminal, Terminal.make({ + * columns: Effect.succeed(80), + * rows: Effect.succeed(24), + * readInput: Effect.die("unused"), + * readLine: Effect.die("unused"), + * display: () => Effect.void + * })), + * Layer.succeed( + * ChildProcessSpawner.ChildProcessSpawner, + * ChildProcessSpawner.make(() => Effect.die("unused")) + * ) + * ) * * // Simple command with no configuration * const version: Command.Command<"version", {}, {}, never, never> = Command.make( @@ -81,9 +103,15 @@ import * as Param from "./Param.ts" * }) * * // Command with handler + * const output: Array = [] * const greet = Command.make("greet", { * name: Flag.string("name") - * }, (config) => Console.log(`Hello, ${config.name}!`)) + * }, (config) => Effect.sync(() => output.push(`Hello, ${config.name}!`)).pipe(Effect.asVoid)) + * + * await Effect.runPromise( + * Command.runWith(greet, { version: "1.0.0" })(["--name", "Alice"]).pipe(Effect.provide(CliTestLayer)) + * ) + * output // => ["Hello, Alice!"] * ``` * * @category models @@ -138,11 +166,11 @@ export interface Command /** - * Whether this command is hidden from parent help output, shell - * completions, and unknown-subcommand suggestions. Hidden commands still + * Whether this command is omitted from parent help output, shell + * completions, and unknown-subcommand suggestions. Unlisted commands still * parse and execute normally when invoked by exact name. */ - readonly hidden: boolean + readonly unlisted: boolean } /** @@ -191,19 +219,19 @@ export declare namespace Command { * * **Example** (Configuring command input) * - * ```ts + * ```ts import.meta.vitest * import { Argument, Flag } from "effect/unstable/cli" * import type { Command as CliCommand } from "effect/unstable/cli" * * // Simple flat configuration - * const simpleConfig: CliCommand.Command.Config = { + * const simpleConfig = { * name: Flag.string("name"), * age: Flag.integer("age"), * file: Argument.string("file") - * } + * } satisfies CliCommand.Command.Config * * // Nested configuration for organization - * const nestedConfig: CliCommand.Command.Config = { + * const nestedConfig = { * user: { * name: Flag.string("name"), * email: Flag.string("email") @@ -212,7 +240,9 @@ export declare namespace Command { * host: Flag.string("host"), * port: Flag.integer("port") * } - * } + * } satisfies CliCommand.Command.Config + * + * [simpleConfig.name.kind, nestedConfig.server.port.kind] // => ["flag", "flag"] * ``` * * @category models @@ -258,7 +288,7 @@ export declare namespace Command { * * **Example** (Inferring command input) * - * ```ts + * ```ts import.meta.vitest * import { Flag } from "effect/unstable/cli" * import type { Command as CliCommand } from "effect/unstable/cli" * @@ -278,6 +308,12 @@ export declare namespace Command { * // readonly port: number * // } * // } + * + * const inferred: Result = { + * name: "Alice", + * server: { host: "localhost", port: 8080 } + * } + * inferred // => { name: "Alice", server: { host: "localhost", port: 8080 } } * ``` * * @category models @@ -317,7 +353,7 @@ export declare namespace Command { readonly commands: NonEmptyReadonlyArray }> readonly annotations: Context.Context - readonly hidden: boolean + readonly unlisted: boolean } /** @@ -369,6 +405,21 @@ export type Error = C extends Command< > ? _Error : never +/** + * A utility type to extract the required services type from a `Command`. + * + * @category utility types + * @since 4.0.0 + */ +export type Services = C extends Command< + infer _Name, + infer _Input, + infer _ContextInput, + infer _Error, + infer _Requirements +> ? _Requirements : + never + /** * Service context for a specific command, enabling subcommands to access their parent's parsed configuration. * @@ -381,9 +432,27 @@ export type Error = C extends Command< * * **Example** (Accessing parent command context) * - * ```ts - * import { Console, Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect, FileSystem, Layer, Path, Stdio, Terminal } from "effect" * import { Command, Flag } from "effect/unstable/cli" + * import { ChildProcessSpawner } from "effect/unstable/process" + * + * const CliTestLayer = Layer.mergeAll( + * FileSystem.layerNoop({}), + * Path.layer, + * Stdio.layerTest({}), + * Layer.succeed(Terminal.Terminal, Terminal.make({ + * columns: Effect.succeed(80), + * rows: Effect.succeed(24), + * readInput: Effect.die("unused"), + * readLine: Effect.die("unused"), + * display: () => Effect.void + * })), + * Layer.succeed( + * ChildProcessSpawner.ChildProcessSpawner, + * ChildProcessSpawner.make(() => Effect.die("unused")) + * ) + * ) * * const parent = Command.make("app").pipe( * Command.withSharedFlags({ @@ -392,19 +461,31 @@ export type Error = C extends Command< * }) * ) * + * const output: Array = [] * const child = Command.make("deploy", { * target: Flag.string("target") * }, (config) => * Effect.gen(function*() { * // Access parent's config by yielding the parent command * const parentConfig = yield* parent - * yield* Console.log(`Verbose: ${parentConfig.verbose}`) - * yield* Console.log(`Config: ${parentConfig.config}`) - * yield* Console.log(`Target: ${config.target}`) + * yield* Effect.sync(() => output.push(`Verbose: ${parentConfig.verbose}`)) + * yield* Effect.sync(() => output.push(`Config: ${parentConfig.config}`)) + * yield* Effect.sync(() => output.push(`Target: ${config.target}`)) * })) * * const app = parent.pipe(Command.withSubcommands([child])) - * // Usage: app --verbose --config prod.json deploy --target staging + * + * await Effect.runPromise( + * Command.runWith(app, { version: "1.0.0" })([ + * "--verbose", + * "--config", + * "prod.json", + * "deploy", + * "--target", + * "staging" + * ]).pipe(Effect.provide(CliTestLayer)) + * ) + * output // => ["Verbose: true", "Config: prod.json", "Target: staging"] * ``` * * @category models @@ -460,9 +541,27 @@ export const isCommand = (u: unknown): u is Command.Any => Predicate.hasProperty * * **Example** (Creating commands) * - * ```ts - * import { Console, Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect, FileSystem, Layer, Path, Stdio, Terminal } from "effect" * import { Argument, Command, Flag } from "effect/unstable/cli" + * import { ChildProcessSpawner } from "effect/unstable/process" + * + * const CliTestLayer = Layer.mergeAll( + * FileSystem.layerNoop({}), + * Path.layer, + * Stdio.layerTest({}), + * Layer.succeed(Terminal.Terminal, Terminal.make({ + * columns: Effect.succeed(80), + * rows: Effect.succeed(24), + * readInput: Effect.die("unused"), + * readLine: Effect.die("unused"), + * display: () => Effect.void + * })), + * Layer.succeed( + * ChildProcessSpawner.ChildProcessSpawner, + * ChildProcessSpawner.make(() => Effect.die("unused")) + * ) + * ) * * // Simple command with no configuration * const version = Command.make("version") @@ -487,19 +586,29 @@ export const isCommand = (u: unknown): u is Command.Any => Predicate.hasProperty * }) * * // Command with handler + * const output: Array = [] * const deployWithHandler = Command.make("deploy", { * environment: Flag.string("env"), * force: Flag.boolean("force") * }, (config) => * Effect.gen(function*() { - * yield* Console.log(`Starting deployment to ${config.environment}`) + * yield* Effect.sync(() => output.push(`Starting deployment to ${config.environment}`)) * * if (!config.force && config.environment === "production") { * return yield* Effect.fail("Production deployments require --force flag") * } * - * yield* Console.log("Deployment completed successfully") + * yield* Effect.sync(() => output.push("Deployment completed successfully")) * })) + * + * await Effect.runPromise( + * Command.runWith(deployWithHandler, { version: "1.0.0" })([ + * "--env", + * "staging", + * "--force" + * ]).pipe(Effect.provide(CliTestLayer)) + * ) + * output // => ["Starting deployment to staging", "Deployment completed successfully"] * ``` * * @category constructors @@ -517,7 +626,7 @@ export const make: { name: Name, config: Config, handler: (config: Command.Config.Infer) => Effect.Effect - ): Command, {}, E, Exclude> + ): Command, {}, E, Exclude> } = (( name: string, config?: Command.Config, @@ -540,9 +649,27 @@ export const make: { * * **Example** (Adding command handlers) * - * ```ts - * import { Console } from "effect" + * ```ts import.meta.vitest + * import { Effect, FileSystem, Layer, Path, Stdio, Terminal } from "effect" * import { Command, Flag } from "effect/unstable/cli" + * import { ChildProcessSpawner } from "effect/unstable/process" + * + * const CliTestLayer = Layer.mergeAll( + * FileSystem.layerNoop({}), + * Path.layer, + * Stdio.layerTest({}), + * Layer.succeed(Terminal.Terminal, Terminal.make({ + * columns: Effect.succeed(80), + * rows: Effect.succeed(24), + * readInput: Effect.die("unused"), + * readLine: Effect.die("unused"), + * display: () => Effect.void + * })), + * Layer.succeed( + * ChildProcessSpawner.ChildProcessSpawner, + * ChildProcessSpawner.make(() => Effect.die("unused")) + * ) + * ) * * // Command without initial handler * const greet = Command.make("greet", { @@ -550,11 +677,19 @@ export const make: { * }) * * // Add handler later + * const output: Array = [] * const greetWithHandler = greet.pipe( * Command.withHandler((config: { readonly name: string }) => - * Console.log(`Hello, ${config.name}!`) + * Effect.sync(() => output.push(`Hello, ${config.name}!`)).pipe(Effect.asVoid) * ) * ) + * + * await Effect.runPromise( + * Command.runWith(greetWithHandler, { version: "1.0.0" })(["--name", "Alice"]).pipe( + * Effect.provide(CliTestLayer) + * ) + * ) + * output // => ["Hello, Alice!"] * ``` * * @category combinators @@ -565,15 +700,15 @@ export const withHandler: { handler: (value: A) => Effect.Effect ): ( self: Command - ) => Command> + ) => Command> ( self: Command, handler: (value: A) => Effect.Effect - ): Command> + ): Command> } = dual(2, ( self: Command, handler: (value: A) => Effect.Effect -): Command> => +): Command> => makeCommand({ ...toImpl(self), handle: handler } as any)) interface SubcommandGroupInternal { @@ -638,9 +773,27 @@ const normalizeSubcommandEntries = ( * * **Example** (Adding subcommands) * - * ```ts - * import { Console, Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect, FileSystem, Layer, Path, Stdio, Terminal } from "effect" * import { Command, Flag } from "effect/unstable/cli" + * import { ChildProcessSpawner } from "effect/unstable/process" + * + * const CliTestLayer = Layer.mergeAll( + * FileSystem.layerNoop({}), + * Path.layer, + * Stdio.layerTest({}), + * Layer.succeed(Terminal.Terminal, Terminal.make({ + * columns: Effect.succeed(80), + * rows: Effect.succeed(24), + * readInput: Effect.die("unused"), + * readLine: Effect.die("unused"), + * display: () => Effect.void + * })), + * Layer.succeed( + * ChildProcessSpawner.ChildProcessSpawner, + * ChildProcessSpawner.make(() => Effect.die("unused")) + * ) + * ) * * // Parent command with shared flags * const git = Command.make("git").pipe( @@ -650,19 +803,29 @@ const normalizeSubcommandEntries = ( * ) * * // Subcommand that accesses parent config + * const output: Array = [] * const clone = Command.make("clone", { * repository: Flag.string("repo") * }, (config) => * Effect.gen(function*() { * const parent = yield* git // Access parent's parsed config * if (parent.verbose) { - * yield* Console.log("Verbose mode enabled") + * yield* Effect.sync(() => output.push("Verbose mode enabled")) * } - * yield* Console.log(`Cloning ${config.repository}`) + * yield* Effect.sync(() => output.push(`Cloning ${config.repository}`)) * })) * * const app = git.pipe(Command.withSubcommands([clone])) - * // Usage: git --verbose clone --repo github.com/foo/bar + * + * await Effect.runPromise( + * Command.runWith(app, { version: "1.0.0" })([ + * "--verbose", + * "clone", + * "--repo", + * "github.com/foo/bar" + * ]).pipe(Effect.provide(CliTestLayer)) + * ) + * output // => ["Verbose mode enabled", "Cloning github.com/foo/bar"] * ``` * * @category combinators @@ -737,7 +900,7 @@ export const withSubcommands: { const context = yield* impl.parseContext(raw) const result = yield* sub.parse(raw.subcommand.value.parsedInput) - return Object.assign({}, context, { [SubcommandStateSymbol]: { name: sub.name, result } }) as NextInput + return { ...context, [SubcommandStateSymbol]: { name: sub.name, result } } as NextInput }) const handle = Effect.fnUntraced(function*(input: NextInput, path: ReadonlyArray) { @@ -763,6 +926,7 @@ export const withSubcommands: { description: impl.description, shortDescription: impl.shortDescription, alias: impl.alias, + unlisted: impl.unlisted, annotations: impl.annotations, globalFlags: impl.globalFlags, examples: impl.examples, @@ -834,20 +998,20 @@ export const withSharedFlags: { type NextInput = Simplify type NextContextInput = Simplify - const parseShared = makeParser(sharedConfig) as ( + const parseShared = makeParser(sharedConfig, { allowLeftovers: true }) as ( input: ParsedTokens ) => Effect.Effect const parse = Effect.fnUntraced(function*(raw: ParsedTokens) { const base = yield* impl.parse(raw) const shared = yield* parseShared(raw) - return Object.assign({}, base, shared) as NextInput + return { ...(base as object), ...(shared as object) } as NextInput }) const parseContext = Effect.fnUntraced(function*(raw: ParsedTokens) { const base = yield* impl.parseContext(raw) const shared = yield* parseShared(raw) - return Object.assign({}, base, shared) as NextContextInput + return { ...(base as object), ...(shared as object) } as NextContextInput }) const handle = ( @@ -862,6 +1026,7 @@ export const withSharedFlags: { description: impl.description, shortDescription: impl.shortDescription, alias: impl.alias, + unlisted: impl.unlisted, annotations: impl.annotations, globalFlags: impl.globalFlags, examples: impl.examples, @@ -925,12 +1090,12 @@ type ExtractGlobalFlagContext ? F extends GlobalFlag.Setting ? GlobalFlag.Setting.Identifier : never : never +type BuiltInSettingContext = ExtractGlobalFlagContext type ExtractSubcommand = T extends Command ? T : T extends Command.SubcommandGroup ? Commands[number] : never type ExtractSubcommandErrors> = Error> -type ExtractSubcommandContext> = ExtractSubcommand extends - Command ? _R : never +type ExtractSubcommandContext> = Services> /** * Sets the description for a command. @@ -942,18 +1107,42 @@ type ExtractSubcommandContext> * * **Example** (Setting descriptions) * - * ```ts - * import { Console, Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect, FileSystem, Layer, Path, Stdio, Terminal } from "effect" * import { Command, Flag } from "effect/unstable/cli" + * import { ChildProcessSpawner } from "effect/unstable/process" + * + * const CliTestLayer = Layer.mergeAll( + * FileSystem.layerNoop({}), + * Path.layer, + * Stdio.layerTest({}), + * Layer.succeed(Terminal.Terminal, Terminal.make({ + * columns: Effect.succeed(80), + * rows: Effect.succeed(24), + * readInput: Effect.die("unused"), + * readLine: Effect.die("unused"), + * display: () => Effect.void + * })), + * Layer.succeed( + * ChildProcessSpawner.ChildProcessSpawner, + * ChildProcessSpawner.make(() => Effect.die("unused")) + * ) + * ) * + * const output: Array = [] * const deploy = Command.make("deploy", { * environment: Flag.string("env") * }, (config) => * Effect.gen(function*() { - * yield* Console.log(`Deploying to ${config.environment}`) + * yield* Effect.sync(() => output.push(`Deploying to ${config.environment}`)) * })).pipe( * Command.withDescription("Deploy the application to a specified environment") * ) + * + * await Effect.runPromise( + * Command.runWith(deploy, { version: "1.0.0" })(["--env", "staging"]).pipe(Effect.provide(CliTestLayer)) + * ) + * output // => ["Deploying to staging"] * ``` * * @category combinators @@ -1022,7 +1211,7 @@ export const withAlias: { ) => makeCommand({ ...toImpl(self), alias })) /** - * Hides a subcommand from parent help output, shell completions, and + * Omits a subcommand from parent help output, shell completions, and * "did you mean?" suggestions while keeping it fully invocable by exact name. * * **When to use** @@ -1030,29 +1219,31 @@ export const withAlias: { * Use when experimental or internal subcommands should be accepted but not advertised on * the public CLI surface. * - * **Example** (Hiding a subcommand) + * **Example** (Unlisting a subcommand) * - * ```ts + * ```ts import.meta.vitest * import { Command } from "effect/unstable/cli" * * // `experimental` still runs when invoked as `mycli experimental`, * // but it does not appear under SUBCOMMANDS in `mycli --help`. * const experimental = Command.make("experimental").pipe( - * Command.withHidden + * Command.unlisted * ) * * const root = Command.make("mycli").pipe( * Command.withSubcommands([experimental]) * ) + * + * root.subcommands[0].commands[0].unlisted // => true * ``` * * @category combinators * @since 4.0.0 */ -export const withHidden = ( +export const unlisted = ( self: Command ): Command => - makeCommand({ ...toImpl(self), hidden: true }) as Command + makeCommand({ ...toImpl(self), unlisted: true }) as Command /** * Adds a custom annotation to a command. @@ -1148,7 +1339,7 @@ export const annotateMerge: { * * **Example** (Adding usage examples) * - * ```ts + * ```ts import.meta.vitest * import { Command } from "effect/unstable/cli" * * const login = Command.make("login").pipe( @@ -1157,6 +1348,8 @@ export const annotateMerge: { * { command: "myapp login --token sbp_abc123", description: "Log in with a token" } * ]) * ) + * + * login.examples.map((example) => example.command) // => ["myapp login", "myapp login --token sbp_abc123"] * ``` * * @category combinators @@ -1194,16 +1387,35 @@ const mapHandler = ( * * **Example** (Providing command services) * - * ```ts - * import { Effect, FileSystem, PlatformError } from "effect" + * ```ts import.meta.vitest + * import { Effect, FileSystem, Layer, Path, PlatformError, Stdio, Terminal } from "effect" * import { Command, Flag } from "effect/unstable/cli" + * import { ChildProcessSpawner } from "effect/unstable/process" + * + * const CliTestLayer = Layer.mergeAll( + * FileSystem.layerNoop({}), + * Path.layer, + * Stdio.layerTest({}), + * Layer.succeed(Terminal.Terminal, Terminal.make({ + * columns: Effect.succeed(80), + * rows: Effect.succeed(24), + * readInput: Effect.die("unused"), + * readLine: Effect.die("unused"), + * display: () => Effect.void + * })), + * Layer.succeed( + * ChildProcessSpawner.ChildProcessSpawner, + * ChildProcessSpawner.make(() => Effect.die("unused")) + * ) + * ) * + * const output: Array = [] * const deploy = Command.make("deploy", { * env: Flag.string("env") * }, (config) => * Effect.gen(function*() { * const fs = yield* FileSystem.FileSystem - * // Use fs... + * yield* Effect.sync(() => output.push(`Using file system for ${config.env}`)) * })).pipe( * // Provide FileSystem based on the --env flag * Command.provide((config) => @@ -1220,6 +1432,11 @@ const mapHandler = ( * }) * ) * ) + * + * await Effect.runPromise( + * Command.runWith(deploy, { version: "1.0.0" })(["--env", "local"]).pipe(Effect.provide(CliTestLayer)) + * ) + * output // => ["Using file system for local"] * ``` * * @category providing services @@ -1353,6 +1570,61 @@ export const provideEffectDiscard: { /* Execution */ /* ========================================================================== */ +/** + * Interactively constructs command-line arguments for a command. + * + * **Details** + * + * The returned arguments include the command name and can be inspected, + * modified, or passed to another command runner by the caller. + * + * **Example** (Constructing command arguments) + * + * ```ts import.meta.vitest + * import { Console, Effect, FileSystem, Layer, Path, Stdio, Terminal } from "effect" + * import { Command } from "effect/unstable/cli" + * import { ChildProcessSpawner } from "effect/unstable/process" + * + * const CliTestLayer = Layer.mergeAll( + * FileSystem.layerNoop({}), + * Path.layer, + * Stdio.layerTest({}), + * Layer.succeed(Terminal.Terminal, Terminal.make({ + * columns: Effect.succeed(80), + * rows: Effect.succeed(24), + * readInput: Effect.die("unused"), + * readLine: Effect.die("unused"), + * display: () => Effect.void + * })), + * Layer.succeed( + * ChildProcessSpawner.ChildProcessSpawner, + * ChildProcessSpawner.make(() => Effect.die("unused")) + * ) + * ) + * + * const command = Command.make("app") + * const silentConsole: Console.Console = Object.assign(Object.create(console), { + * log: () => {} + * }) + * + * const program = Command.wizard(command).pipe( + * Effect.provideService(Console.Console, silentConsole), + * Effect.provide(CliTestLayer) + * ) + * + * await Effect.runPromise(program) // => ["app"] + * ``` + * + * @category running + * @since 4.0.0 + */ +export const wizard = ( + command: Command, + options?: { + readonly prefix?: ReadonlyArray | undefined + } | undefined +): Effect.Effect, CliError.CliError | Terminal.QuitError, Environment> => Wizard.run(command, options) + const getOutOfScopeGlobalFlagErrors = ( allFlags: ReadonlyArray>, activeFlags: ReadonlyArray>, @@ -1394,17 +1666,26 @@ const getOutOfScopeGlobalFlagErrors = ( const showHelp = ( command: Command, - error: CliError.ShowHelp + error: CliError.ShowHelp, + renderErrors: boolean ): Effect.Effect => Effect.gen(function*() { + const { builtIns } = yield* CliConfig.CliConfig const formatter = yield* CliOutput.Formatter - const helpDoc = yield* getHelpForCommandPath(command, error.commandPath, GlobalFlag.BuiltIns) + const helpDoc = yield* getHelpForCommandPath(command, error.commandPath, builtIns) yield* Console.log(formatter.formatHelpDoc(helpDoc)) - if (error.errors.length > 0) { + if (renderErrors && error.errors.length > 0) { yield* Console.error(formatter.formatErrors(error.errors as any)) } }) +const showUserError = (error: CliError.UserError): Effect.Effect => + Effect.gen(function*() { + const formatter = yield* CliOutput.Formatter + yield* Console.error(formatter.formatError(error)) + error[Runtime.errorReported] = false + }) + /** * Runs a command using the arguments supplied by the `Stdio` service. * @@ -1413,33 +1694,63 @@ const showHelp = ( * Use when command-line arguments should come from `Stdio` at the application * entry point. * + * Help documents are always rendered. By default, parse error details and + * `CliError.UserError` failures are also rendered with the installed + * `CliOutput.Formatter` before the error is rethrown. Set `renderErrors` to + * `false` when the host application owns error rendering. + * * **Example** (Running commands with standard input) * - * ```ts - * import { Console, Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect, FileSystem, Layer, Path, Stdio, Terminal } from "effect" * import { Command, Flag } from "effect/unstable/cli" + * import { ChildProcessSpawner } from "effect/unstable/process" + * + * const CliTestLayer = Layer.mergeAll( + * FileSystem.layerNoop({}), + * Path.layer, + * Stdio.layerTest({ + * args: Effect.succeed(["--name", "Alice"]) + * }), + * Layer.succeed(Terminal.Terminal, Terminal.make({ + * columns: Effect.succeed(80), + * rows: Effect.succeed(24), + * readInput: Effect.die("unused"), + * readLine: Effect.die("unused"), + * display: () => Effect.void + * })), + * Layer.succeed( + * ChildProcessSpawner.ChildProcessSpawner, + * ChildProcessSpawner.make(() => Effect.die("unused")) + * ) + * ) * + * const output: Array = [] * const greetCommand = Command.make("greet", { * name: Flag.string("name") * }, (config) => * Effect.gen(function*() { - * yield* Console.log(`Hello, ${config.name}!`) + * yield* Effect.sync(() => output.push(`Hello, ${config.name}!`)) * })) * * // Automatically gets args from the Stdio service * const program = Command.run(greetCommand, { * version: "1.0.0" * }) + * + * await Effect.runPromise(program.pipe(Effect.provide(CliTestLayer))) + * output // => ["Hello, Alice!"] * ``` * * @see {@link runWith} for running a command with an explicit argument array * - * @category command execution + * @category running * @since 4.0.0 */ export const run: { (config: { readonly version: string + readonly renderErrors?: boolean | undefined }): ( command: Command ) => Effect.Effect @@ -1447,12 +1758,14 @@ export const run: { command: Command, config: { readonly version: string + readonly renderErrors?: boolean | undefined } ): Effect.Effect } = dual(2, ( command: Command, config: { readonly version: string + readonly renderErrors?: boolean | undefined } ) => Stdio.Stdio.use(({ args }) => @@ -1470,19 +1783,43 @@ export const run: { * Use when you need to test CLI applications or programmatically execute * commands with specific arguments. * + * Help documents are always rendered. By default, parse error details and + * `CliError.UserError` failures are also rendered with the installed + * `CliOutput.Formatter` before the error is rethrown. Set `renderErrors` to + * `false` when the host application owns error rendering. + * * **Example** (Running commands with explicit arguments) * - * ```ts - * import { Console, Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect, FileSystem, Layer, Path, Stdio, Terminal } from "effect" * import { Command, Flag } from "effect/unstable/cli" + * import { ChildProcessSpawner } from "effect/unstable/process" + * + * const CliTestLayer = Layer.mergeAll( + * FileSystem.layerNoop({}), + * Path.layer, + * Stdio.layerTest({}), + * Layer.succeed(Terminal.Terminal, Terminal.make({ + * columns: Effect.succeed(80), + * rows: Effect.succeed(24), + * readInput: Effect.die("unused"), + * readLine: Effect.die("unused"), + * display: () => Effect.void + * })), + * Layer.succeed( + * ChildProcessSpawner.ChildProcessSpawner, + * ChildProcessSpawner.make(() => Effect.die("unused")) + * ) + * ) * + * const output: Array = [] * const greet = Command.make("greet", { * name: Flag.string("name"), * count: Flag.integer("count").pipe(Flag.withDefault(1)) * }, (config) => * Effect.gen(function*() { * for (let i = 0; i < config.count; i++) { - * yield* Console.log(`Hello, ${config.name}!`) + * yield* Effect.sync(() => output.push(`Hello, ${config.name}!`)) * } * })) * @@ -1490,24 +1827,21 @@ export const run: { * const testProgram = Effect.gen(function*() { * const runCommand = Command.runWith(greet, { version: "1.0.0" }) * - * // Test normal execution * yield* runCommand(["--name", "Alice", "--count", "2"]) - * - * // Test help display - * yield* runCommand(["--help"]) - * - * // Test version display - * yield* runCommand(["--version"]) * }) + * + * await Effect.runPromise(testProgram.pipe(Effect.provide(CliTestLayer))) + * output // => ["Hello, Alice!", "Hello, Alice!"] * ``` * - * @category command execution + * @category running * @since 4.0.0 */ export const runWith = ( command: Command, config: { readonly version: string + readonly renderErrors?: boolean | undefined } ): ( input: ReadonlyArray @@ -1515,10 +1849,11 @@ export const runWith = ( const commandImpl = toImpl(command) return Effect.fnUntraced( function*(args: ReadonlyArray) { + const { builtIns } = yield* CliConfig.CliConfig const { tokens, trailingOperands } = Lexer.lex(args) // 1. Collect known global flags from the command tree - const allFlags = getGlobalFlagsForCommandTree(command, GlobalFlag.BuiltIns) + const allFlags = getGlobalFlagsForCommandTree(command, builtIns) // 2. Extract global flag tokens const allFlagParams = allFlags.flatMap((f) => Param.extractSingleParams(f.flag)) @@ -1533,8 +1868,8 @@ export const runWith = ( // 3. Parse command arguments from remaining tokens const parsedArgs = yield* Parser.parseArgs({ tokens: remainder, trailingOperands }, command) const commandPath = [command.name, ...Parser.getCommandPath(parsedArgs)] as const - const handlerCtx: GlobalFlag.HandlerContext = { command, commandPath, version: config.version } - const activeFlags = getGlobalFlagsForCommandPath(command, commandPath, GlobalFlag.BuiltIns) + const handlerCtx: GlobalFlag.HandlerContext = { builtIns, command, commandPath, version: config.version } + const activeFlags = getGlobalFlagsForCommandPath(command, commandPath, builtIns) // 4. Reject globals that were passed outside the active command scope const outOfScopeErrors = getOutOfScopeGlobalFlagErrors(allFlags, activeFlags, flagMap, commandPath) @@ -1556,6 +1891,29 @@ export const runWith = ( }) if (!hasEntry) continue const [, value] = yield* flag.flag.parse(emptyArgs) + if (flag === GlobalFlag.Wizard) { + return yield* Effect.gen(function*() { + yield* Console.log(Wizard.renderIntroduction(command.name, config.version, command.description)) + const prefix = [ + command.name, + ...args.filter((arg) => arg !== "--wizard" && !arg.startsWith("--wizard=")) + ] + const wizardArgs = yield* Wizard.run(command, { commandPath, prefix }) + yield* Console.log(Wizard.renderCompletion(wizardArgs)) + const shouldRun = yield* Prompt.run(Prompt.toggle({ + message: "Run this command?", + initial: true, + active: "yes", + inactive: "no" + })) + if (shouldRun) { + yield* Console.log() + yield* runWith(command, { ...config, renderErrors: false })(wizardArgs.slice(1)) + } + }).pipe( + Effect.catchTag("QuitError", () => Console.log(Wizard.renderQuit())) + ) + } yield* flag.run(value, handlerCtx) return } @@ -1574,15 +1932,16 @@ export const runWith = ( // 7. Provide setting values let program = commandImpl.handle(parseResult.success, [command.name]) + const logLevel = activeFlags.includes(GlobalFlag.LogLevel) + ? (yield* GlobalFlag.LogLevel.flag.parse(emptyArgs))[1] + : Option.none() + program = Effect.provideService(program, GlobalFlag.LogLevel, logLevel) for (const flag of activeFlags) { - if (flag._tag !== "Setting") continue + if (flag._tag !== "Setting" || flag === GlobalFlag.LogLevel) continue const [, value] = yield* flag.flag.parse(emptyArgs) program = Effect.provideService(program, flag, value) } - const [, logLevel] = yield* GlobalFlag.LogLevel.flag.parse(emptyArgs) - program = Effect.provideService(program, GlobalFlag.LogLevel, logLevel) - // 8. Apply built-in setting behavior const services = Option.match(logLevel, { onNone: () => Context.empty(), @@ -1597,7 +1956,14 @@ export const runWith = ( CliError.isCliError(error) && error._tag === "ShowHelp" ? Result.succeed(error) : Result.fail(error), - (error) => Effect.andThen(showHelp(command, error), Effect.fail(error)) + (error) => Effect.andThen(showHelp(command, error, config.renderErrors !== false), Effect.fail(error)) + ), + Effect.catchFilter( + (error) => + config.renderErrors !== false && CliError.isCliError(error) && error._tag === "UserError" + ? Result.succeed(error) + : Result.fail(error), + (error) => Effect.andThen(showUserError(error), Effect.fail(error)) ), Effect.catchFilter( (e) => diff --git a/repos/effect/packages/effect/src/unstable/cli/Flag.ts b/repos/effect/packages/effect/src/unstable/cli/Flag.ts index 9ca7bfd330..e467479be9 100644 --- a/repos/effect/packages/effect/src/unstable/cli/Flag.ts +++ b/repos/effect/packages/effect/src/unstable/cli/Flag.ts @@ -43,11 +43,12 @@ export interface Flag extends Param.Param {} * * **Example** (Creating string flags) * - * ```ts + * ```ts import.meta.vitest * import { Flag } from "effect/unstable/cli" * * const nameFlag = Flag.string("name") * // Usage: --name "John Doe" + * nameFlag.kind // => "flag" * ``` * * @category constructors @@ -60,11 +61,12 @@ export const string = (name: string): Flag => Param.string(Param.flagKin * * **Example** (Creating boolean flags) * - * ```ts + * ```ts import.meta.vitest * import { Flag } from "effect/unstable/cli" * * const verboseFlag = Flag.boolean("verbose") * // Usage: --verbose (true) or --no-verbose (false) + * verboseFlag.kind // => "flag" * ``` * * @category constructors @@ -77,11 +79,12 @@ export const boolean = (name: string): Flag => Param.boolean(Param.flag * * **Example** (Creating integer flags) * - * ```ts + * ```ts import.meta.vitest * import { Flag } from "effect/unstable/cli" * * const portFlag = Flag.integer("port") * // Usage: --port 8080 + * portFlag.kind // => "flag" * ``` * * @category constructors @@ -94,11 +97,12 @@ export const integer = (name: string): Flag => Param.integer(Param.flagK * * **Example** (Creating float flags) * - * ```ts + * ```ts import.meta.vitest * import { Flag } from "effect/unstable/cli" * * const rateFlag = Flag.float("rate") * // Usage: --rate 3.14 + * rateFlag.kind // => "flag" * ``` * * @category constructors @@ -111,11 +115,12 @@ export const float = (name: string): Flag => Param.float(Param.flagKind, * * **Example** (Creating date flags) * - * ```ts + * ```ts import.meta.vitest * import { Flag } from "effect/unstable/cli" * * const startDateFlag = Flag.date("start-date") * // Usage: --start-date 2023-12-25 + * startDateFlag.kind // => "flag" * ``` * * @category constructors @@ -129,7 +134,7 @@ export const date = (name: string): Flag => Param.date(Param.flagKind, nam * * **Example** (Creating flag choices with values) * - * ```ts + * ```ts import.meta.vitest * import { Flag } from "effect/unstable/cli" * * // simple enum like choice mapping directly to string union @@ -141,6 +146,7 @@ export const date = (name: string): Flag => Param.date(Param.flagKind, nam * ["info", "Info" as const], * ["error", "Error" as const] * ]) + * const kinds = [color.kind, logLevel.kind] // => ["flag", "flag"] * ``` * * @category constructors @@ -179,7 +185,7 @@ export const choice = >( * * **Example** (Creating path flags) * - * ```ts + * ```ts import.meta.vitest * import { Flag } from "effect/unstable/cli" * * // Basic path flag @@ -196,6 +202,7 @@ export const choice = >( * pathType: "directory", * typeName: "OUTPUT_DIRECTORY" * }) + * const kinds = [pathFlag.kind, fileFlag.kind, dirFlag.kind] // => ["flag", "flag", "flag"] * ``` * * @category constructors @@ -212,7 +219,7 @@ export const path = (name: string, options?: { * * **Example** (Creating file flags) * - * ```ts + * ```ts import.meta.vitest * import { Flag } from "effect/unstable/cli" * * // Basic file flag @@ -222,6 +229,7 @@ export const path = (name: string, options?: { * // File that must exist * const configFlag = Flag.file("config", { mustExist: true }) * // Usage: --config ./config.yaml (file must exist) + * const kinds = [inputFlag.kind, configFlag.kind] // => ["flag", "flag"] * ``` * * @category constructors @@ -236,7 +244,7 @@ export const file = (name: string, options?: { * * **Example** (Creating directory flags) * - * ```ts + * ```ts import.meta.vitest * import { Flag } from "effect/unstable/cli" * * // Basic directory flag @@ -246,6 +254,7 @@ export const file = (name: string, options?: { * // Directory that must exist * const sourceFlag = Flag.directory("source", { mustExist: true }) * // Usage: --source ./src (directory must exist) + * const kinds = [outputFlag.kind, sourceFlag.kind] // => ["flag", "flag"] * ``` * * @category constructors @@ -266,20 +275,39 @@ export const directory = (name: string, options?: { * * **Example** (Creating redacted flags) * - * ```ts - * import { Effect, Redacted } from "effect" + * ```ts import.meta.vitest + * import { Effect, FileSystem, Layer, Path, Redacted, Stdio, Terminal } from "effect" * import { Flag } from "effect/unstable/cli" + * import { ChildProcessSpawner } from "effect/unstable/process" + * + * const CliTestLayer = Layer.mergeAll( + * FileSystem.layerNoop({}), + * Path.layer, + * Stdio.layerTest({}), + * Layer.succeed(Terminal.Terminal, Terminal.make({ + * columns: Effect.succeed(80), + * rows: Effect.succeed(24), + * readInput: Effect.die("unused"), + * readLine: Effect.die("unused"), + * display: () => Effect.void + * })), + * Layer.succeed( + * ChildProcessSpawner.ChildProcessSpawner, + * ChildProcessSpawner.make(() => Effect.die("unused")) + * ) + * ) * * const passwordFlag = Flag.redacted("password") * * const program = Effect.gen(function*() { - * const [leftover, password] = yield* passwordFlag.parse({ + * const [, password] = yield* passwordFlag.parse({ * arguments: [], * flags: { "password": ["abc123"] } * }) - * const value = Redacted.value(password) // Access the underlying value - * console.log("Password length:", value.length) + * return Redacted.value(password).length * }) + * + * await Effect.runPromise(program.pipe(Effect.provide(CliTestLayer))) // => 6 * ``` * * @category constructors @@ -292,11 +320,12 @@ export const redacted = (name: string): Flag> => Param * * **Example** (Reading file text) * - * ```ts + * ```ts import.meta.vitest * import { Flag } from "effect/unstable/cli" * * const config = Flag.fileText("config-file") * // --config-file ./app.json will read the file content + * config.kind // => "flag" * ``` * * @category constructors @@ -314,7 +343,7 @@ export const fileText = (name: string): Flag => Param.fileText(Param.fla * * **Example** (Parsing file contents) * - * ```ts + * ```ts import.meta.vitest * import { Flag } from "effect/unstable/cli" * * // Will use the extension of the file passed on the command line to determine @@ -323,6 +352,7 @@ export const fileText = (name: string): Flag => Param.fileText(Param.fla * * // Will use the JSON parser * const jsonConfig = Flag.fileParse("json-config", { format: "json" }) + * const kinds = [config.kind, jsonConfig.kind] // => ["flag", "flag"] * ``` * * @category constructors @@ -339,7 +369,7 @@ export const fileParse = ( * * **Example** (Validating file contents) * - * ```ts + * ```ts import.meta.vitest * import { Schema } from "effect" * import { Flag } from "effect/unstable/cli" * @@ -349,6 +379,7 @@ export const fileParse = ( * }) * * const config = Flag.fileSchema("config", ConfigSchema, { format: "json" }) + * config.kind // => "flag" * ``` * * @category constructors @@ -375,11 +406,12 @@ export const fileSchema = ( * * **Example** (Parsing key-value pairs) * - * ```ts + * ```ts import.meta.vitest * import { Flag } from "effect/unstable/cli" * * const env = Flag.keyValuePair("env") * // --env FOO=bar --env BAZ=qux will parse to { FOO: "bar", BAZ: "qux" } + * env.kind // => "flag" * ``` * * @category constructors @@ -393,14 +425,14 @@ export const keyValuePair = (name: string): Flag> => Para * * **Example** (Creating sentinel flags) * - * ```ts + * ```ts import.meta.vitest * import { Flag } from "effect/unstable/cli" * * const makeValueFlag = (includeValue: boolean) => * includeValue ? Flag.string("value") : Flag.none * - * console.log(makeValueFlag(true) === Flag.none) // false - * console.log(makeValueFlag(false) === Flag.none) // true + * makeValueFlag(true) === Flag.none // => false + * makeValueFlag(false) === Flag.none // => true * ``` * * @category constructors @@ -417,7 +449,7 @@ export const none: Flag = Param.none(Param.flagKind) * * **Example** (Adding flag aliases) * - * ```ts + * ```ts import.meta.vitest * import { Flag } from "effect/unstable/cli" * * // Flag can be used as both --verbose and -v @@ -430,6 +462,7 @@ export const none: Flag = Param.none(Param.flagKind) * Flag.withAlias("h"), * Flag.withAlias("?") * ) + * const kinds = [verboseFlag.kind, helpFlag.kind] // => ["flag", "flag"] * ``` * * @category aliasing @@ -445,7 +478,7 @@ export const withAlias: { * * **Example** (Adding help descriptions) * - * ```ts + * ```ts import.meta.vitest * import { Flag } from "effect/unstable/cli" * * const portFlag = Flag.integer("port").pipe( @@ -455,9 +488,10 @@ export const withAlias: { * const configFlag = Flag.file("config").pipe( * Flag.withDescription("Path to the configuration file") * ) + * const kinds = [portFlag.kind, configFlag.kind] // => ["flag", "flag"] * ``` * - * @category help documentation + * @category metadata * @since 4.0.0 */ export const withDescription: { @@ -479,7 +513,7 @@ export const withDescription: { * * **Example** (Setting metavars) * - * ```ts + * ```ts import.meta.vitest * import { Flag } from "effect/unstable/cli" * * const databaseFlag = Flag.string("database-url").pipe( @@ -492,6 +526,7 @@ export const withDescription: { * Flag.withMetavar("SECONDS") * ) * // In help: --timeout SECONDS + * const kinds = [databaseFlag.kind, timeoutFlag.kind] // => ["flag", "flag"] * ``` * * @category metadata @@ -514,13 +549,14 @@ export const withMetavar: { * * **Example** (Hiding a flag from help) * - * ```ts + * ```ts import.meta.vitest * import { Flag } from "effect/unstable/cli" * * // Flag still parses --experimental-foo, but it does not appear in --help. * const experimental = Flag.boolean("experimental-foo").pipe( * Flag.withHidden * ) + * experimental.kind // => "flag" * ``` * * @category metadata @@ -533,23 +569,39 @@ export const withHidden = (self: Flag): Flag => Param.withHidden(self) * * **Example** (Making flags optional) * - * ```ts - * import { Effect, Option } from "effect" + * ```ts import.meta.vitest + * import { Effect, FileSystem, Layer, Option, Path, Stdio, Terminal } from "effect" * import { Flag } from "effect/unstable/cli" + * import { ChildProcessSpawner } from "effect/unstable/process" + * + * const CliTestLayer = Layer.mergeAll( + * FileSystem.layerNoop({}), + * Path.layer, + * Stdio.layerTest({}), + * Layer.succeed(Terminal.Terminal, Terminal.make({ + * columns: Effect.succeed(80), + * rows: Effect.succeed(24), + * readInput: Effect.die("unused"), + * readLine: Effect.die("unused"), + * display: () => Effect.void + * })), + * Layer.succeed( + * ChildProcessSpawner.ChildProcessSpawner, + * ChildProcessSpawner.make(() => Effect.die("unused")) + * ) + * ) * * const optionalPort = Flag.optional(Flag.integer("port")) * * const program = Effect.gen(function*() { - * const [leftover, port] = yield* optionalPort.parse({ + * const [, port] = yield* optionalPort.parse({ * arguments: [], * flags: { "port": ["4000"] } * }) - * if (Option.isSome(port)) { - * console.log("Port specified:", port.value) - * } else { - * console.log("No port specified, using default") - * } + * return port * }) + * + * await Effect.runPromise(program.pipe(Effect.provide(CliTestLayer))) // => Option.some(4000) * ``` * * @category optionality @@ -562,7 +614,7 @@ export const optional = (param: Flag): Flag> => Param.opt * * **Example** (Providing default values) * - * ```ts + * ```ts import.meta.vitest * import { Flag } from "effect/unstable/cli" * * const portFlag = Flag.integer("port").pipe( @@ -574,6 +626,7 @@ export const optional = (param: Flag): Flag> => Param.opt * Flag.withDefault("localhost") * ) * // If --host is not provided, defaults to "localhost" + * const kinds = [portFlag.kind, hostFlag.kind] // => ["flag", "flag"] * ``` * * @category optionality @@ -589,13 +642,14 @@ export const withDefault: { * * **Example** (Falling back to config) * - * ```ts + * ```ts import.meta.vitest * import { Config } from "effect" * import { Flag } from "effect/unstable/cli" * * const verbose = Flag.boolean("verbose").pipe( * Flag.withFallbackConfig(Config.boolean("VERBOSE")) * ) + * verbose.kind // => "flag" * ``` * * @category combinators @@ -611,12 +665,13 @@ export const withFallbackConfig: { * * **Example** (Falling back to prompts) * - * ```ts + * ```ts import.meta.vitest * import { Flag, Prompt } from "effect/unstable/cli" * * const name = Flag.string("name").pipe( * Flag.withFallbackPrompt(Prompt.text({ message: "Name" })) * ) + * name.kind // => "flag" * ``` * * @category combinators @@ -632,7 +687,7 @@ export const withFallbackPrompt: { * * **Example** (Mapping parsed values) * - * ```ts + * ```ts import.meta.vitest * import { Flag } from "effect/unstable/cli" * * // Convert string to uppercase @@ -644,6 +699,7 @@ export const withFallbackPrompt: { * const urlFlag = Flag.integer("port").pipe( * Flag.map((port) => `http://localhost:${port}`) * ) + * const kinds = [nameFlag.kind, urlFlag.kind] // => ["flag", "flag"] * ``` * * @category mapping @@ -659,18 +715,39 @@ export const map: { * * **Example** (Mapping parsed values effectfully) * - * ```ts - * import { Effect, FileSystem } from "effect" + * ```ts import.meta.vitest + * import { Effect, FileSystem, Layer, Path, Stdio, Terminal } from "effect" * import { Flag } from "effect/unstable/cli" + * import { ChildProcessSpawner } from "effect/unstable/process" + * + * const CliTestLayer = Layer.mergeAll( + * FileSystem.layerNoop({}), + * Path.layer, + * Stdio.layerTest({}), + * Layer.succeed(Terminal.Terminal, Terminal.make({ + * columns: Effect.succeed(80), + * rows: Effect.succeed(24), + * readInput: Effect.die("unused"), + * readLine: Effect.die("unused"), + * display: () => Effect.void + * })), + * Layer.succeed( + * ChildProcessSpawner.ChildProcessSpawner, + * ChildProcessSpawner.make(() => Effect.die("unused")) + * ) + * ) + * + * const upperName = Flag.string("name").pipe( + * Flag.mapEffect((name) => Effect.succeed(name.toUpperCase())) + * ) * - * // Read file size from path flag - * const fileSizeFlag = Flag.file("input").pipe( - * Flag.mapEffect(Effect.fnUntraced(function*(path) { - * const fs = yield* FileSystem.FileSystem - * const stats = yield* Effect.orDie(fs.stat(path)) - * return stats.size - * })) + * const [, value] = await Effect.runPromise( + * upperName.parse({ + * arguments: [], + * flags: { name: ["alice"] } + * }).pipe(Effect.provide(CliTestLayer)) * ) + * value // => "ALICE" * ``` * * @category mapping @@ -694,8 +771,27 @@ export const mapEffect: { * * **Example** (Mapping thrown errors) * - * ```ts + * ```ts import.meta.vitest + * import { Effect, FileSystem, Layer, Path, Stdio, Terminal } from "effect" * import { Flag } from "effect/unstable/cli" + * import { ChildProcessSpawner } from "effect/unstable/process" + * + * const CliTestLayer = Layer.mergeAll( + * FileSystem.layerNoop({}), + * Path.layer, + * Stdio.layerTest({}), + * Layer.succeed(Terminal.Terminal, Terminal.make({ + * columns: Effect.succeed(80), + * rows: Effect.succeed(24), + * readInput: Effect.die("unused"), + * readLine: Effect.die("unused"), + * display: () => Effect.void + * })), + * Layer.succeed( + * ChildProcessSpawner.ChildProcessSpawner, + * ChildProcessSpawner.make(() => Effect.die("unused")) + * ) + * ) * * // Parse JSON string with error handling * const jsonFlag = Flag.string("config").pipe( @@ -712,6 +808,14 @@ export const mapEffect: { * (error) => `Invalid URL: ${error}` * ) * ) + * + * const [, value] = await Effect.runPromise( + * jsonFlag.parse({ + * arguments: [], + * flags: { config: ['{"enabled":true}'] } + * }).pipe(Effect.provide(CliTestLayer)) + * ) + * value // => { enabled: true } * ``` * * @category mapping @@ -731,7 +835,7 @@ export const mapTryCatch: { * * **Example** (Requiring repeated values) * - * ```ts + * ```ts import.meta.vitest * import { Flag } from "effect/unstable/cli" * * const sourceFlag = Flag.atLeast(Flag.file("source"), 2) @@ -742,6 +846,7 @@ export const mapTryCatch: { * Flag.atLeast(1) * ) * // Requires at least 1 tag + * const kinds = [sourceFlag.kind, tagFlag.kind] // => ["flag", "flag"] * ``` * * @category repetition @@ -757,7 +862,7 @@ export const atLeast: { * * **Example** (Limiting repeated values) * - * ```ts + * ```ts import.meta.vitest * import { Flag } from "effect/unstable/cli" * * const warningFlag = Flag.atMost(Flag.string("warning"), 3) @@ -768,6 +873,7 @@ export const atLeast: { * Flag.atMost(1) * ) * // Allows at most 1 debug flag + * const kinds = [warningFlag.kind, debugFlag.kind] // => ["flag", "flag"] * ``` * * @category repetition @@ -783,7 +889,7 @@ export const atMost: { * * **Example** (Bounding repeated values) * - * ```ts + * ```ts import.meta.vitest * import { Flag } from "effect/unstable/cli" * * const hostFlag = Flag.between(Flag.string("host"), 1, 3) @@ -794,6 +900,7 @@ export const atMost: { * Flag.between(0, 5) * ) * // Allows 0-5 exclude patterns + * const kinds = [hostFlag.kind, excludeFlag.kind] // => ["flag", "flag"] * ``` * * @category repetition @@ -809,7 +916,7 @@ export const between: { * * **Example** (Filtering and transforming values) * - * ```ts + * ```ts import.meta.vitest * import { Option } from "effect" * import { Flag } from "effect/unstable/cli" * @@ -828,6 +935,7 @@ export const between: { * (email) => `Invalid email address: ${email}` * ) * ) + * const kinds = [positiveInt.kind, emailFlag.kind] // => ["flag", "flag"] * ``` * * @category filtering @@ -847,7 +955,7 @@ export const filterMap: { * * **Example** (Filtering parsed values) * - * ```ts + * ```ts import.meta.vitest * import { Flag } from "effect/unstable/cli" * * // Ensure port is in valid range @@ -865,6 +973,7 @@ export const filterMap: { * () => "Name cannot be empty" * ) * ) + * const kinds = [portFlag.kind, nameFlag.kind] // => ["flag", "flag"] * ``` * * @category filtering @@ -884,7 +993,7 @@ export const filter: { * * **Example** (Falling back to another flag) * - * ```ts + * ```ts import.meta.vitest * import { Flag } from "effect/unstable/cli" * * // Try parsing as integer, fallback to string @@ -898,6 +1007,7 @@ export const filter: { * Flag.file("config"), * () => Flag.string("config-url") * ) + * const kinds = [valueFlag.kind, configFlag.kind] // => ["flag", "flag"] * ``` * * @category alternatives @@ -913,27 +1023,42 @@ export const orElse: { * * **Example** (Returning fallback results) * - * ```ts - * import { Effect, Result } from "effect" + * ```ts import.meta.vitest + * import { Effect, FileSystem, Layer, Path, Result, Stdio, Terminal } from "effect" * import { Flag } from "effect/unstable/cli" + * import { ChildProcessSpawner } from "effect/unstable/process" + * + * const CliTestLayer = Layer.mergeAll( + * FileSystem.layerNoop({}), + * Path.layer, + * Stdio.layerTest({}), + * Layer.succeed(Terminal.Terminal, Terminal.make({ + * columns: Effect.succeed(80), + * rows: Effect.succeed(24), + * readInput: Effect.die("unused"), + * readLine: Effect.die("unused"), + * display: () => Effect.void + * })), + * Layer.succeed( + * ChildProcessSpawner.ChildProcessSpawner, + * ChildProcessSpawner.make(() => Effect.die("unused")) + * ) + * ) * - * // Try file path, fallback to URL * const sourceFlag = Flag.orElseResult( - * Flag.file("source"), + * Flag.string("source"), * () => Flag.string("source-url") * ) * * const program = Effect.gen(function*() { - * const [leftover, source] = yield* sourceFlag.parse({ + * const [, source] = yield* sourceFlag.parse({ * arguments: [], - * flags: { "source-url": ["https://google.com"] } + * flags: { "source-url": ["https://example.com"] } * }) - * if (Result.isSuccess(source)) { - * console.log("Using file:", source.success) - * } else { - * console.log("Using URL:", source.failure) - * } + * return source * }) + * + * await Effect.runPromise(program.pipe(Effect.provide(CliTestLayer))) // => Result.fail("https://example.com") * ``` * * @category alternatives @@ -949,7 +1074,7 @@ export const orElseResult: { * * **Example** (Validating with schemas) * - * ```ts + * ```ts import.meta.vitest * import { Schema } from "effect" * import { Flag } from "effect/unstable/cli" * @@ -976,12 +1101,16 @@ export const orElseResult: { * const configFlag = Flag.string("config").pipe( * Flag.withSchema(ConfigSchema) * ) + * const kinds = [emailFlag.kind, configFlag.kind] // => ["flag", "flag"] * ``` * * @category schemas * @since 4.0.0 */ export const withSchema: { - (schema: Schema.Codec): (self: Flag) => Flag - (self: Flag, schema: Schema.Codec): Flag -} = dual(2, (self: Flag, schema: Schema.Codec) => Param.withSchema(self, schema)) + (schema: Schema.ConstraintCodec): (self: Flag) => Flag + (self: Flag, schema: Schema.ConstraintCodec): Flag +} = dual( + 2, + (self: Flag, schema: Schema.ConstraintCodec) => Param.withSchema(self, schema) +) diff --git a/repos/effect/packages/effect/src/unstable/cli/GlobalFlag.ts b/repos/effect/packages/effect/src/unstable/cli/GlobalFlag.ts index b4eb343e97..d9c1484af3 100644 --- a/repos/effect/packages/effect/src/unstable/cli/GlobalFlag.ts +++ b/repos/effect/packages/effect/src/unstable/cli/GlobalFlag.ts @@ -5,7 +5,7 @@ * This module defines two kinds of global flags: action flags, which run an * effect and stop normal command execution, and setting flags, which provide a * parsed value to the command handler through the Effect context. It also - * defines the built-in help, version, shell-completion, and log-level flags + * defines the built-in help, version, wizard, shell-completion, and log-level flags * used by `Command.run` and `Command.runWith`. * * @since 4.0.0 @@ -37,6 +37,7 @@ export interface HandlerContext { readonly command: Command.Command.Any readonly commandPath: ReadonlyArray readonly version: string + readonly builtIns: ReadonlyArray } /** @@ -57,7 +58,7 @@ export interface Action { /** * Setting flag: configure command handler's environment (--log-level, --config). * - * @category models + * @category services * @since 4.0.0 */ export interface Setting extends Context.Service, A> { @@ -156,12 +157,11 @@ export const Help: Action = action({ Flag.withAlias("h"), Flag.withDescription("Show help information") ), - run: (_, { command, commandPath }) => - Effect.gen(function*() { - const formatter = yield* CliOutput.Formatter - const helpDoc = yield* HelpInternal.getHelpForCommandPath(command, commandPath, BuiltIns) - yield* Console.log(formatter.formatHelpDoc(helpDoc)) - }) + run: Effect.fnUntraced(function*(_, { builtIns, command, commandPath }) { + const formatter = yield* CliOutput.Formatter + const helpDoc = yield* HelpInternal.getHelpForCommandPath(command, commandPath, builtIns) + yield* Console.log(formatter.formatHelpDoc(helpDoc)) + }) }) /** @@ -179,11 +179,28 @@ export const Version: Action = action({ Flag.withAlias("v"), Flag.withDescription("Show version information") ), - run: (_, { command, version }) => - Effect.gen(function*() { - const formatter = yield* CliOutput.Formatter - yield* Console.log(formatter.formatVersion(command.name, version)) - }) + run: Effect.fnUntraced(function*(_, { command, version }) { + const formatter = yield* CliOutput.Formatter + yield* Console.log(formatter.formatVersion(command.name, version)) + }) +}) + +/** + * Defines the global action flag for starting interactive wizard mode. + * + * **Details** + * + * `Command.run` and `Command.runWith` handle this action specially so the + * generated arguments can be passed back through the command parser. + * + * @category references + * @since 4.0.0 + */ +export const Wizard: Action = action({ + flag: Flag.boolean("wizard").pipe( + Flag.withDescription("Start wizard mode for a command") + ), + run: () => Effect.void }) /** @@ -206,14 +223,13 @@ export const Completions: Action> = actio Flag.withMetavar(""), Flag.withDescription("Print shell completion script") ), - run: (shell, { command }) => - Effect.gen(function*() { - if (Option.isNone(shell)) return - const descriptor = CommandDescriptor.fromCommand(command) - yield* Console.log( - Completions_.generate(command.name, shell.value, descriptor) - ) - }) + run: Effect.fnUntraced(function*(shell, { command }) { + if (Option.isNone(shell)) return + const descriptor = CommandDescriptor.fromCommand(command) + yield* Console.log( + Completions_.generate(command.name, shell.value, descriptor) + ) + }) }) /** @@ -262,7 +278,7 @@ export const LogLevel: Setting<"log-level", Option.Option> = setti * * **Details** * - * The built-ins are `Help`, `Version`, `Completions`, and `LogLevel`. + * The built-ins are `Help`, `Version`, `Wizard`, `Completions`, and `LogLevel`. * `Command.runWith` prepends these built-ins when collecting and parsing global * flags. * @@ -279,17 +295,18 @@ export const LogLevel: Setting<"log-level", Option.Option> = setti * @category references * @since 4.0.0 */ -export const BuiltIns: ReadonlyArray> = [ - Help, - Version, - Completions, - LogLevel -] +export const BuiltIns: readonly [ + Action, + Action, + Action, + Action>, + Setting<"log-level", Option.Option> +] = [Help, Version, Wizard, Completions, LogLevel] /** - * Built-in setting context identifiers. + * Global flag included in the default command-runner configuration. * * @category models * @since 4.0.0 */ -export type BuiltInSettingContext = Setting.Identifier<"log-level"> +export type BuiltIn = typeof BuiltIns[number] diff --git a/repos/effect/packages/effect/src/unstable/cli/HelpDoc.ts b/repos/effect/packages/effect/src/unstable/cli/HelpDoc.ts index e1dc75846b..6618c91a79 100644 --- a/repos/effect/packages/effect/src/unstable/cli/HelpDoc.ts +++ b/repos/effect/packages/effect/src/unstable/cli/HelpDoc.ts @@ -21,7 +21,7 @@ import type * as Option from "../../Option.ts" * * **Example** (Defining command help documentation) * - * ```ts + * ```ts import.meta.vitest * import { Context, Option as O } from "effect" * import type { HelpDoc } from "effect/unstable/cli" * @@ -55,6 +55,8 @@ import type * as Option from "../../Option.ts" * } * ] * } + * + * deployCommandHelp.usage // => "myapp deploy [options] " * ``` * * @category models @@ -126,7 +128,7 @@ export interface ExampleDoc { * * **Example** (Documenting command flags) * - * ```ts + * ```ts import.meta.vitest * import { Option as O } from "effect" * import type { HelpDoc } from "effect/unstable/cli" * @@ -145,6 +147,8 @@ export interface ExampleDoc { * description: O.some("Port number to use"), * required: true * } + * + * const names = [verboseFlag.name, portFlag.name] // => ["verbose", "port"] * ``` * * @category models @@ -182,7 +186,7 @@ export interface FlagDoc { * * **Example** (Documenting subcommands) * - * ```ts + * ```ts import.meta.vitest * import { Context, Option as O } from "effect" * import type { HelpDoc } from "effect/unstable/cli" * @@ -211,6 +215,8 @@ export interface FlagDoc { * commands: [deploySubcommand, buildSubcommand] * }] * } + * + * mainCommandHelp.subcommands?.[0].commands.map((command) => command.name) // => ["deploy", "build"] * ``` * * @category models @@ -262,7 +268,7 @@ export interface SubcommandGroupDoc { * * **Example** (Documenting positional arguments) * - * ```ts + * ```ts import.meta.vitest * import { Context, Option as O } from "effect" * import type { HelpDoc } from "effect/unstable/cli" * @@ -290,6 +296,8 @@ export interface SubcommandGroupDoc { * flags: [], * args: [sourceArg, filesArg] * } + * + * copyCommandHelp.args?.map((arg) => arg.name) // => ["source", "files"] * ``` * * @category models diff --git a/repos/effect/packages/effect/src/unstable/cli/Param.ts b/repos/effect/packages/effect/src/unstable/cli/Param.ts index 1b242e10e2..d9ed6ded98 100644 --- a/repos/effect/packages/effect/src/unstable/cli/Param.ts +++ b/repos/effect/packages/effect/src/unstable/cli/Param.ts @@ -12,7 +12,7 @@ */ import * as Config from "../../Config.ts" import * as Effect from "../../Effect.ts" -import { dual, identity } from "../../Function.ts" +import { dual, identity, type LazyArg } from "../../Function.ts" import * as Option from "../../Option.ts" import { type Pipeable, pipeArguments } from "../../Pipeable.ts" import * as Predicate from "../../Predicate.ts" @@ -226,7 +226,11 @@ export interface Transform extends Para readonly _tag: "Transform" readonly kind: Kind readonly param: Param - readonly f: (parse: Parse) => Parse + readonly alternatives: ReadonlyArray>> + readonly f: ( + parse: Parse, + alternatives: ReadonlyArray>> + ) => Parse } /** @@ -272,17 +276,15 @@ const Proto = { * * **Example** (Checking for params) * - * ```ts + * ```ts import.meta.vitest * import { Param } from "effect/unstable/cli" * * const maybeParam = Param.string(Param.flagKind, "name") * - * if (Param.isParam(maybeParam)) { - * console.log("This is a Param") - * } + * Param.isParam(maybeParam) // => true * ``` * - * @category refinements + * @category guards * @since 4.0.0 */ export const isParam = (u: unknown): u is Param => Predicate.hasProperty(u, TypeId) @@ -292,17 +294,17 @@ export const isParam = (u: unknown): u is Param => Predicate.has * * **Example** (Checking for single params) * - * ```ts + * ```ts import.meta.vitest * import { Param } from "effect/unstable/cli" * * const nameParam = Param.string(Param.flagKind, "name") * const optionalParam = Param.optional(nameParam) * - * console.log(Param.isSingle(nameParam)) // true - * console.log(Param.isSingle(optionalParam)) // false + * Param.isSingle(nameParam) // => true + * Param.isSingle(optionalParam) // => false * ``` * - * @category refinements + * @category guards * @since 4.0.0 */ export const isSingle = ( @@ -343,14 +345,14 @@ export const makeSingle = (params: { params.kind === argumentKind ? parsePositional(params.name, params.primitiveType, args) : parseFlag(params.name, params.primitiveType, args) - return Object.assign(Object.create(Proto), { + return Object.setPrototypeOf({ _tag: "Single", ...params, description: params.description ?? Option.none(), aliases: params.aliases ?? [], hidden: params.hidden ?? false, parse - }) + }, Proto) } /** @@ -358,7 +360,7 @@ export const makeSingle = (params: { * * **Example** (Creating string parameters) * - * ```ts + * ```ts import.meta.vitest * import { Param } from "effect/unstable/cli" * * // Create a string flag @@ -368,6 +370,7 @@ export const makeSingle = (params: { * const fileArg = Param.string(Param.argumentKind, "file") * * // Usage in CLI: --name "John Doe" or as positional argument + * const kinds = [nameFlag.kind, fileArg.kind] // => ["flag", "argument"] * ``` * * @category constructors @@ -388,7 +391,7 @@ export const string = ( * * **Example** (Creating boolean parameters) * - * ```ts + * ```ts import.meta.vitest * import { Param } from "effect/unstable/cli" * * // Create a boolean flag @@ -399,6 +402,7 @@ export const string = ( * * // Usage in CLI: --verbose (defaults to true when present, false when absent) * // or as positional: true/false + * const kinds = [verboseFlag.kind, enableArg.kind] // => ["flag", "argument"] * ``` * * @category constructors @@ -419,7 +423,7 @@ export const boolean = ( * * **Example** (Creating integer parameters) * - * ```ts + * ```ts import.meta.vitest * import { Param } from "effect/unstable/cli" * * // Create an integer flag @@ -429,6 +433,7 @@ export const boolean = ( * const countArg = Param.integer(Param.argumentKind, "count") * * // Usage in CLI: --port 8080 or as positional argument: 42 + * const kinds = [portFlag.kind, countArg.kind] // => ["flag", "argument"] * ``` * * @category constructors @@ -449,7 +454,7 @@ export const integer = ( * * **Example** (Creating float parameters) * - * ```ts + * ```ts import.meta.vitest * import { Param } from "effect/unstable/cli" * * // Create a float flag @@ -459,6 +464,7 @@ export const integer = ( * const thresholdArg = Param.float(Param.argumentKind, "threshold") * * // Usage in CLI: --rate 0.95 or as positional argument: 3.14159 + * const kinds = [rateFlag.kind, thresholdArg.kind] // => ["flag", "argument"] * ``` * * @category constructors @@ -479,7 +485,7 @@ export const float = ( * * **Example** (Creating date parameters) * - * ```ts + * ```ts import.meta.vitest * import { Param } from "effect/unstable/cli" * * // Create a date flag @@ -490,6 +496,7 @@ export const float = ( * * // Usage in CLI: --start-date "2023-12-25" or as positional: "2023-01-01" * // Parses to JavaScript Date object + * const kinds = [startFlag.kind, dueDateArg.kind] // => ["flag", "argument"] * ``` * * @category constructors @@ -511,7 +518,7 @@ export const date = ( * * **Example** (Creating valued choices) * - * ```ts + * ```ts import.meta.vitest * import { Param } from "effect/unstable/cli" * * type Animal = Dog | Cat @@ -528,6 +535,7 @@ export const date = ( * ["dog", { _tag: "Dog" }], * ["cat", { _tag: "Cat" }] * ]) + * animal.kind // => "flag" * ``` * * @category constructors @@ -549,7 +557,7 @@ export const choiceWithValue = < * * **Example** (Creating string choices) * - * ```ts + * ```ts import.meta.vitest * import { Param } from "effect/unstable/cli" * * const logLevel = Param.choice(Param.flagKind, "log-level", [ @@ -558,6 +566,7 @@ export const choiceWithValue = < * "warn", * "error" * ]) + * logLevel.kind // => "flag" * ``` * * @category constructors @@ -576,7 +585,7 @@ export const choice = < * * **Example** (Creating path parameters) * - * ```ts + * ```ts import.meta.vitest * import { Param } from "effect/unstable/cli" * * // Basic path parameter @@ -591,6 +600,7 @@ export const choice = < * mustExist: true, * typeName: "config-file" * }) + * const kinds = [outputPath.kind, inputPath.kind, configFile.kind] // => ["flag", "flag", "flag"] * ``` * * @category constructors @@ -622,7 +632,7 @@ export const path = ( * * **Example** (Creating directory parameters) * - * ```ts + * ```ts import.meta.vitest * import { Param } from "effect/unstable/cli" * * // Basic directory parameter @@ -632,6 +642,7 @@ export const path = ( * const sourceDir = Param.directory(Param.flagKind, "source", { mustExist: true }) * * // Usage: --output-dir /path/to/dir --source /existing/dir + * const kinds = [outputDir.kind, sourceDir.kind] // => ["flag", "flag"] * ``` * * @category constructors @@ -660,7 +671,7 @@ export const directory = ( * * **Example** (Creating file parameters) * - * ```ts + * ```ts import.meta.vitest * import { Param } from "effect/unstable/cli" * * // Basic file parameter @@ -670,6 +681,7 @@ export const directory = ( * const inputFile = Param.file(Param.flagKind, "input", { mustExist: true }) * * // Usage: --output result.txt --input existing-file.txt + * const kinds = [outputFile.kind, inputFile.kind] // => ["flag", "flag"] * ``` * * @category constructors @@ -694,7 +706,7 @@ export const file = ( * * **Example** (Creating redacted parameters) * - * ```ts + * ```ts import.meta.vitest * import { Param } from "effect/unstable/cli" * * // Create a password parameter @@ -704,6 +716,7 @@ export const file = ( * const apiKey = Param.redacted(Param.argumentKind, "api-key") * * // Usage: --password (value will be hidden in help/logs) + * const kinds = [password.kind, apiKey.kind] // => ["flag", "argument"] * ``` * * @category constructors @@ -724,7 +737,7 @@ export const redacted = ( * * **Example** (Reading file text) * - * ```ts + * ```ts import.meta.vitest * import { Param } from "effect/unstable/cli" * * // Read a config file as string @@ -734,6 +747,7 @@ export const redacted = ( * const templateContent = Param.fileText(Param.argumentKind, "template") * * // Usage: --config config.txt (reads file content into string) + * const kinds = [configContent.kind, templateContent.kind] // => ["flag", "argument"] * ``` * * @category constructors @@ -756,7 +770,7 @@ export const fileText = (kind: Kind, name: string): Para * * **Example** (Parsing file contents) * - * ```ts + * ```ts import.meta.vitest * import { Param } from "effect/unstable/cli" * * // Will use the extension of the file passed on the command line to determine @@ -767,6 +781,7 @@ export const fileText = (kind: Kind, name: string): Para * const jsonConfig = Param.fileParse(Param.flagKind, "json-config", { * format: "json" * }) + * const kinds = [config.kind, jsonConfig.kind] // => ["flag", "flag"] * ``` * * @category constructors @@ -788,7 +803,7 @@ export const fileParse = ( * * **Example** (Validating file contents) * - * ```ts + * ```ts import.meta.vitest * import { Schema } from "effect" * import { Param } from "effect/unstable/cli" * // Parse JSON config file @@ -807,6 +822,7 @@ export const fileParse = ( * }) * * // Usage: --config config.json (reads and validates file content) + * const kinds = [config.kind, yamlConfig.kind] // => ["flag", "flag"] * ``` * * @category constructors @@ -839,7 +855,7 @@ export const fileSchema = ( * * **Example** (Parsing key-value pairs) * - * ```ts + * ```ts import.meta.vitest * import { Param } from "effect/unstable/cli" * * const env = Param.keyValuePair(Param.flagKind, "env") @@ -847,6 +863,7 @@ export const fileSchema = ( * * const props = Param.keyValuePair(Param.flagKind, "property") * // --property name=value --property debug=true + * const kinds = [env.kind, props.kind] // => ["flag", "flag"] * ``` * * @category constructors @@ -865,7 +882,7 @@ export const keyValuePair = ( }), { min: 1 } ), - (objects) => Object.assign({}, ...objects) + (objects) => Object.fromEntries(objects.flatMap(Object.entries)) ) /** @@ -878,7 +895,7 @@ export const keyValuePair = ( * * **Example** (Creating sentinel parameters) * - * ```ts + * ```ts import.meta.vitest * import { Param } from "effect/unstable/cli" * * const disabledDebugParam = Param.none(Param.flagKind) @@ -886,8 +903,8 @@ export const keyValuePair = ( * const makeDebugParam = (enableDebug: boolean) => * enableDebug ? Param.string(Param.flagKind, "debug") : disabledDebugParam * - * console.log(makeDebugParam(true) === disabledDebugParam) // false - * console.log(makeDebugParam(false) === disabledDebugParam) // true + * makeDebugParam(true) === disabledDebugParam // => false + * makeDebugParam(false) === disabledDebugParam // => true * ``` * * @category constructors @@ -917,7 +934,7 @@ const FLAG_DASH_REGEXP = /^-+/ * * **Example** (Adding parameter aliases) * - * ```ts + * ```ts import.meta.vitest * import { Param } from "effect/unstable/cli" * * const force = Param.boolean(Param.flagKind, "force").pipe( @@ -930,6 +947,7 @@ const FLAG_DASH_REGEXP = /^-+/ * Param.optional, * Param.withAlias("-c") // finds the underlying Single and adds alias * ) + * const kinds = [force.kind, count.kind] // => ["flag", "flag"] * ``` * * @category combinators @@ -956,13 +974,14 @@ export const withAlias: { * * **Example** (Adding help descriptions) * - * ```ts + * ```ts import.meta.vitest * import { Param } from "effect/unstable/cli" * * const verbose = Param.boolean(Param.flagKind, "verbose").pipe( * Param.withAlias("-v"), * Param.withDescription("Enable verbose output") * ) + * verbose.kind // => "flag" * ``` * * @category combinators @@ -990,12 +1009,13 @@ export const withDescription: { * * **Example** (Hiding a flag from help) * - * ```ts + * ```ts import.meta.vitest * import { Param } from "effect/unstable/cli" * * const experimental = Param.boolean(Param.flagKind, "experimental-foo").pipe( * Param.withHidden * ) + * experimental.kind // => "flag" * ``` * * @category metadata @@ -1013,12 +1033,13 @@ export const withHidden = (self: Param): Par * * **Example** (Mapping parsed values) * - * ```ts + * ```ts import.meta.vitest * import { Param } from "effect/unstable/cli" * * const port = Param.integer(Param.flagKind, "port").pipe( * Param.map((n) => ({ port: n, url: `http://localhost:${n}` })) * ) + * port.kind // => "flag" * ``` * * @category combinators @@ -1044,24 +1065,49 @@ export const map: { const transform = ( self: Param, - f: (parse: Parse) => Parse -) => - Object.assign(Object.create(Proto), { + f: ( + parse: Parse, + alternatives: ReadonlyArray>> + ) => Parse, + alternatives: ReadonlyArray>> = [] +): Transform => { + const alternativeParsers = alternatives.map((alternative) => () => alternative().parse) + return Object.assign(Object.create(Proto), { _tag: "Transform", kind: self.kind, param: self, + alternatives, f, - parse: f(self.parse) + parse: f(self.parse, alternativeParsers) }) +} /** * Transforms the parsed value of an option using an effectful mapping function. * * **Example** (Mapping parsed values effectfully) * - * ```ts - * import { Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect, FileSystem, Layer, Path, Stdio, Terminal } from "effect" * import { CliError, Param } from "effect/unstable/cli" + * import { ChildProcessSpawner } from "effect/unstable/process" + * + * const CliTestLayer = Layer.mergeAll( + * FileSystem.layerNoop({}), + * Path.layer, + * Stdio.layerTest({}), + * Layer.succeed(Terminal.Terminal, Terminal.make({ + * columns: Effect.succeed(80), + * rows: Effect.succeed(24), + * readInput: Effect.die("unused"), + * readLine: Effect.die("unused"), + * display: () => Effect.void + * })), + * Layer.succeed( + * ChildProcessSpawner.ChildProcessSpawner, + * ChildProcessSpawner.make(() => Effect.die("unused")) + * ) + * ) * * const validatedEmail = Param.string(Param.flagKind, "email").pipe( * Param.mapEffect((email) => @@ -1077,6 +1123,14 @@ const transform = ( * ) * ) * ) + * + * const [, value] = await Effect.runPromise( + * validatedEmail.parse({ + * arguments: [], + * flags: { email: ["alice@example.com"] } + * }).pipe(Effect.provide(CliTestLayer)) + * ) + * value // => "alice@example.com" * ``` * * @category combinators @@ -1109,8 +1163,27 @@ export const mapEffect: { * * **Example** (Mapping thrown errors) * - * ```ts + * ```ts import.meta.vitest + * import { Effect, FileSystem, Layer, Path, Stdio, Terminal } from "effect" * import { Param } from "effect/unstable/cli" + * import { ChildProcessSpawner } from "effect/unstable/process" + * + * const CliTestLayer = Layer.mergeAll( + * FileSystem.layerNoop({}), + * Path.layer, + * Stdio.layerTest({}), + * Layer.succeed(Terminal.Terminal, Terminal.make({ + * columns: Effect.succeed(80), + * rows: Effect.succeed(24), + * readInput: Effect.die("unused"), + * readLine: Effect.die("unused"), + * display: () => Effect.void + * })), + * Layer.succeed( + * ChildProcessSpawner.ChildProcessSpawner, + * ChildProcessSpawner.make(() => Effect.die("unused")) + * ) + * ) * * const parsedJson = Param.string(Param.flagKind, "config").pipe( * Param.mapTryCatch( @@ -1119,6 +1192,14 @@ export const mapEffect: { * `Invalid JSON: ${error instanceof Error ? error.message : String(error)}` * ) * ) + * + * const [, value] = await Effect.runPromise( + * parsedJson.parse({ + * arguments: [], + * flags: { config: ['{"enabled":true}'] } + * }).pipe(Effect.provide(CliTestLayer)) + * ) + * value // => { enabled: true } * ``` * * @category combinators @@ -1174,13 +1255,14 @@ export const mapTryCatch: { * * **Example** (Making parameters optional) * - * ```ts + * ```ts import.meta.vitest * import { Param } from "effect/unstable/cli" * * // Create an optional port option * // - When not provided: returns Option.none() * // - When provided: returns Option.some(parsedValue) * const port = Param.optional(Param.integer(Param.flagKind, "port")) + * port.kind // => "flag" * ``` * * @category combinators @@ -1230,7 +1312,7 @@ export const optional = ( * * **Example** (Providing default values) * - * ```ts + * ```ts import.meta.vitest * import { Param } from "effect/unstable/cli" * * // Using the pipe operator to make an option optional @@ -1244,6 +1326,7 @@ export const optional = ( * Param.withDescription("Enable verbose output"), * Param.withDefault(false) * ) + * const kinds = [port.kind, verbose.kind] // => ["flag", "flag"] * ``` * * @category combinators @@ -1405,7 +1488,7 @@ export type VariadicParamOptions = { * * **Example** (Accepting multiple values) * - * ```ts + * ```ts import.meta.vitest * import { Param } from "effect/unstable/cli" * * // Basic variadic parameter (0 to infinity) @@ -1422,6 +1505,7 @@ export type VariadicParamOptions = { * min: 2, // at least 2 times * max: 2 // at most 2 times * }) + * const kinds = [tags.kind, inputs.kind, limited.kind] // => ["flag", "flag", "flag"] * ``` * * @category combinators @@ -1459,7 +1543,7 @@ export const variadic = ( * * **Example** (Bounding repeated values) * - * ```ts + * ```ts import.meta.vitest * import { Param } from "effect/unstable/cli" * * // Allow 1-3 file inputs @@ -1478,6 +1562,7 @@ export const variadic = ( * * // Parse: --tag dev --tag staging --tag v1.0 * // Result: ["dev", "staging", "v1.0"] + * const kinds = [files.kind, tags.kind] // => ["flag", "flag"] * ``` * * @category combinators @@ -1507,7 +1592,7 @@ export const between: { * * **Example** (Limiting repeated values) * - * ```ts + * ```ts import.meta.vitest * import { Param } from "effect/unstable/cli" * * // Allow at most 3 warning suppressions @@ -1517,6 +1602,7 @@ export const between: { * * // Parse: --suppress warning1 --suppress warning2 * // Result: ["warning1", "warning2"] + * suppressions.kind // => "flag" * ``` * * @category combinators @@ -1542,7 +1628,7 @@ export const atMost: { * * **Example** (Requiring repeated values) * - * ```ts + * ```ts import.meta.vitest * import { Param } from "effect/unstable/cli" * * // Require at least 2 input files @@ -1553,6 +1639,7 @@ export const atMost: { * * // Parse: --input file1.txt --input file2.txt --input file3.txt * // Result: ["file1.txt", "file2.txt", "file3.txt"] + * inputs.kind // => "flag" * ``` * * @category combinators @@ -1579,7 +1666,7 @@ export const atLeast: { * * **Example** (Filtering and transforming values) * - * ```ts + * ```ts import.meta.vitest * import { Option } from "effect" * import { Param } from "effect/unstable/cli" * const positiveInt = Param.integer(Param.flagKind, "count").pipe( @@ -1588,6 +1675,7 @@ export const atLeast: { * (n) => `Expected positive integer, got ${n}` * ) * ) + * positiveInt.kind // => "flag" * ``` * * @category combinators @@ -1630,7 +1718,7 @@ export const filterMap: { * * **Example** (Filtering parsed values) * - * ```ts + * ```ts import.meta.vitest * import { Param } from "effect/unstable/cli" * * const evenNumber = Param.integer(Param.flagKind, "num").pipe( @@ -1639,6 +1727,7 @@ export const filterMap: { * (n) => `Expected even number, got ${n}` * ) * ) + * evenNumber.kind // => "flag" * ``` * * @category combinators @@ -1670,7 +1759,7 @@ export const filter: { * * **Example** (Setting metavars) * - * ```ts + * ```ts import.meta.vitest * import { Param } from "effect/unstable/cli" * * const port = Param.integer(Param.flagKind, "port").pipe( @@ -1680,6 +1769,7 @@ export const filter: { * () => "Port must be between 1 and 65535" * ) * ) + * port.kind // => "flag" * ``` * * @category metadata @@ -1703,7 +1793,7 @@ export const withMetavar: { * * **Example** (Validating with schemas) * - * ```ts + * ```ts import.meta.vitest * import { Schema } from "effect" * import { Param } from "effect/unstable/cli" * const isEmail = Schema.isPattern(/^[^\s@]+@[^\s@]+\.[^\s@]+$/) @@ -1715,6 +1805,7 @@ export const withMetavar: { * const email = Param.string(Param.flagKind, "email").pipe( * Param.withSchema(Email) * ) + * email.kind // => "flag" * ``` * * @category combinators @@ -1722,17 +1813,17 @@ export const withMetavar: { */ export const withSchema: { ( - schema: Schema.Codec + schema: Schema.ConstraintCodec ): ( self: Param ) => Param ( self: Param, - schema: Schema.Codec + schema: Schema.ConstraintCodec ): Param } = dual(2, ( self: Param, - schema: Schema.Codec + schema: Schema.ConstraintCodec ) => { const decodeParam = Schema.decodeUnknownEffect(schema) return mapEffect(self, (value) => @@ -1752,32 +1843,33 @@ export const withSchema: { * * **Example** (Falling back to another parameter) * - * ```ts + * ```ts import.meta.vitest * import { Param } from "effect/unstable/cli" * * const config = Param.file(Param.flagKind, "config").pipe( * Param.orElse(() => Param.string(Param.flagKind, "config-url")) * ) + * config.kind // => "flag" * ``` * * @category combinators * @since 4.0.0 */ export const orElse: { - ( - orElse: (error: CliError.CliError) => Param - ): (self: Param) => Param + (orElse: LazyArg>): (self: Param) => Param ( self: Param, - orElse: (error: CliError.CliError) => Param + orElse: LazyArg> ): Param } = dual(2, ( self: Param, - orElse: (error: CliError.CliError) => Param + orElse: LazyArg> ) => transform( self, - (parse: Parse): Parse => (args: ParsedArgs) => Effect.catch(parse(args), (err) => orElse(err).parse(args)) + (parse: Parse, alternatives): Parse => (args: ParsedArgs) => + Effect.catch(parse(args), () => (alternatives[0]!() as Parse)(args)), + [orElse] )) /** @@ -1791,13 +1883,14 @@ export const orElse: { * * **Example** (Returning fallback results) * - * ```ts + * ```ts import.meta.vitest * import { Param } from "effect/unstable/cli" * * const configSource = Param.file(Param.flagKind, "config").pipe( * Param.orElseResult(() => Param.string(Param.flagKind, "config-url")) * ) * // Returns Result + * configSource.kind // => "flag" * ``` * * @category combinators @@ -1805,27 +1898,28 @@ export const orElse: { */ export const orElseResult: { ( - orElse: (error: CliError.CliError) => Param + orElse: LazyArg> ): (self: Param) => Param> ( self: Param, - orElse: (error: CliError.CliError) => Param + orElse: LazyArg> ): Param> } = dual(2, ( self: Param, - orElse: (error: CliError.CliError) => Param + orElse: LazyArg> ) => { return transform( self, - (parse: Parse): Parse> => (args: ParsedArgs) => + (parse: Parse, alternatives): Parse> => (args: ParsedArgs) => Effect.catch( Effect.map(parse(args), ([leftover, value]) => [leftover, Result.succeed(value)] as const), - (err) => + () => Effect.map( - orElse(err).parse(args), + (alternatives[0]!() as Parse)(args), ([leftover, value]) => [leftover, Result.fail(value)] as const ) - ) + ), + [orElse] ) }) @@ -2042,7 +2136,12 @@ const transformSingle = ( return matchParam(param, { Single: (single) => f(single), Map: (mapped) => map(transformSingle(mapped.param, f), mapped.f), - Transform: (mapped) => transform(transformSingle(mapped.param, f), mapped.f), + Transform: (mapped) => + transform( + transformSingle(mapped.param, f), + mapped.f, + mapped.alternatives.map((alternative) => () => transformSingle(alternative(), f)) + ), Optional: (p) => optional(transformSingle(p.param, f)) as Param, Variadic: (p) => variadic(transformSingle(p.param, f), { @@ -2064,7 +2163,10 @@ export const extractSingleParams = ( return matchParam(param, { Single: (single) => [single as Single], Map: (mapped) => extractSingleParams(mapped.param), - Transform: (mapped) => extractSingleParams(mapped.param), + Transform: (mapped) => [ + ...extractSingleParams(mapped.param), + ...mapped.alternatives.flatMap((alternative) => extractSingleParams(alternative())) + ], Optional: (optional) => extractSingleParams(optional.param), Variadic: (variadic) => extractSingleParams(variadic.param) }) @@ -2101,9 +2203,19 @@ export const getUnderlyingSingleOrThrow = ( */ export const getParamMetadata = ( param: Param -): { isOptional: boolean; isVariadic: boolean } => { +): { + readonly isOptional: boolean + readonly isVariadic: boolean + readonly variadicMin: Option.Option + readonly variadicMax: Option.Option +} => { return matchParam(param, { - Single: () => ({ isOptional: false, isVariadic: false }), + Single: () => ({ + isOptional: false, + isVariadic: false, + variadicMin: Option.none(), + variadicMax: Option.none() + }), Map: (mapped) => getParamMetadata(mapped.param), Transform: (mapped) => getParamMetadata(mapped.param), Optional: (optional) => ({ @@ -2112,7 +2224,9 @@ export const getParamMetadata = ( }), Variadic: (variadic) => ({ ...getParamMetadata(variadic.param), - isVariadic: true + isVariadic: true, + variadicMin: variadic.min, + variadicMax: variadic.max }) }) } diff --git a/repos/effect/packages/effect/src/unstable/cli/Primitive.ts b/repos/effect/packages/effect/src/unstable/cli/Primitive.ts index 8212c74ece..e5ef22d306 100644 --- a/repos/effect/packages/effect/src/unstable/cli/Primitive.ts +++ b/repos/effect/packages/effect/src/unstable/cli/Primitive.ts @@ -10,9 +10,6 @@ * * @since 4.0.0 */ -import * as Ini from "ini" -import * as Toml from "toml" -import * as Yaml from "yaml" import * as Config from "../../Config.ts" import * as Effect from "../../Effect.ts" import * as FileSystem from "../../FileSystem.ts" @@ -24,6 +21,9 @@ import * as Schema from "../../Schema.ts" import type { Formatter } from "../../SchemaIssue.ts" import type * as Struct from "../../Struct.ts" import type { Covariant } from "../../Types.ts" +import * as Ini from "../encoding/Ini.ts" +import * as Toml from "../encoding/Toml.ts" +import * as Yaml from "../encoding/Yaml.ts" import type { Environment } from "./Command.ts" const TypeId = "~effect/cli/Primitive" @@ -33,25 +33,36 @@ const TypeId = "~effect/cli/Primitive" * * **Example** (Parsing values with primitives) * - * ```ts - * import { Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect, FileSystem, Layer, Path, Stdio, Terminal } from "effect" * import { Primitive } from "effect/unstable/cli" + * import { ChildProcessSpawner } from "effect/unstable/process" + * + * const CliTestLayer = Layer.mergeAll( + * FileSystem.layerNoop({}), + * Path.layer, + * Stdio.layerTest({}), + * Layer.succeed(Terminal.Terminal, Terminal.make({ + * columns: Effect.succeed(80), + * rows: Effect.succeed(24), + * readInput: Effect.die("unused"), + * readLine: Effect.die("unused"), + * display: () => Effect.void + * })), + * Layer.succeed( + * ChildProcessSpawner.ChildProcessSpawner, + * ChildProcessSpawner.make(() => Effect.die("unused")) + * ) + * ) * - * // Using built-in primitives - * const parseString = Effect.gen(function*() { + * const program = Effect.gen(function*() { * const stringResult = yield* Primitive.string.parse("hello") * const numberResult = yield* Primitive.integer.parse("42") * const boolResult = yield* Primitive.boolean.parse("true") - * - * return { stringResult, numberResult, boolResult } + * return [stringResult, numberResult, boolResult] as const * }) * - * // All primitives provide parsing functionality - * const parseDate = Effect.gen(function*() { - * const dateResult = yield* Primitive.date.parse("2023-12-25") - * const pathResult = yield* Primitive.path("file", true).parse("./package.json") - * return { dateResult, pathResult } - * }) + * await Effect.runPromise(program.pipe(Effect.provide(CliTestLayer))) // => ["hello", 42, true] * ``` * * @category models @@ -105,9 +116,9 @@ const makePrimitive = ( parse }) -const makeSchemaPrimitive = ( +const makeSchemaPrimitive = ( tag: string, - schema: Schema.Codec + schema: Schema.ConstraintDecoder ): Primitive => { const toCodecStringTree = Schema.toCodecStringTree(schema) const decode = Schema.decodeUnknownEffect(toCodecStringTree) @@ -125,23 +136,36 @@ const makeSchemaPrimitive = ( * * **Example** (Parsing boolean values) * - * ```ts - * import { Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect, FileSystem, Layer, Path, Stdio, Terminal } from "effect" * import { Primitive } from "effect/unstable/cli" + * import { ChildProcessSpawner } from "effect/unstable/process" + * + * const CliTestLayer = Layer.mergeAll( + * FileSystem.layerNoop({}), + * Path.layer, + * Stdio.layerTest({}), + * Layer.succeed(Terminal.Terminal, Terminal.make({ + * columns: Effect.succeed(80), + * rows: Effect.succeed(24), + * readInput: Effect.die("unused"), + * readLine: Effect.die("unused"), + * display: () => Effect.void + * })), + * Layer.succeed( + * ChildProcessSpawner.ChildProcessSpawner, + * ChildProcessSpawner.make(() => Effect.die("unused")) + * ) + * ) * - * const parseBoolean = Effect.gen(function*() { - * const result1 = yield* Primitive.boolean.parse("true") - * console.log(result1) // true - * - * const result2 = yield* Primitive.boolean.parse("yes") - * console.log(result2) // true - * - * const result3 = yield* Primitive.boolean.parse("false") - * console.log(result3) // false + * const parseBoolean = Effect.all([ + * Primitive.boolean.parse("true"), + * Primitive.boolean.parse("yes"), + * Primitive.boolean.parse("false"), + * Primitive.boolean.parse("0") + * ]) * - * const result4 = yield* Primitive.boolean.parse("0") - * console.log(result4) // false - * }) + * await Effect.runPromise(parseBoolean.pipe(Effect.provide(CliTestLayer))) // => [true, true, false, false] * ``` * * @category constructors @@ -157,20 +181,35 @@ export const boolean: Primitive = makeSchemaPrimitive( * * **Example** (Parsing floating-point numbers) * - * ```ts - * import { Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect, FileSystem, Layer, Path, Stdio, Terminal } from "effect" * import { Primitive } from "effect/unstable/cli" + * import { ChildProcessSpawner } from "effect/unstable/process" + * + * const CliTestLayer = Layer.mergeAll( + * FileSystem.layerNoop({}), + * Path.layer, + * Stdio.layerTest({}), + * Layer.succeed(Terminal.Terminal, Terminal.make({ + * columns: Effect.succeed(80), + * rows: Effect.succeed(24), + * readInput: Effect.die("unused"), + * readLine: Effect.die("unused"), + * display: () => Effect.void + * })), + * Layer.succeed( + * ChildProcessSpawner.ChildProcessSpawner, + * ChildProcessSpawner.make(() => Effect.die("unused")) + * ) + * ) * - * const parseFloat = Effect.gen(function*() { - * const result1 = yield* Primitive.float.parse("3.14") - * console.log(result1) // 3.14 - * - * const result2 = yield* Primitive.float.parse("-42.5") - * console.log(result2) // -42.5 + * const parseFloat = Effect.all([ + * Primitive.float.parse("3.14"), + * Primitive.float.parse("-42.5"), + * Primitive.float.parse("0") + * ]) * - * const result3 = yield* Primitive.float.parse("0") - * console.log(result3) // 0 - * }) + * await Effect.runPromise(parseFloat.pipe(Effect.provide(CliTestLayer))) // => [3.14, -42.5, 0] * ``` * * @category constructors @@ -186,20 +225,35 @@ export const float: Primitive = makeSchemaPrimitive( * * **Example** (Parsing integer values) * - * ```ts - * import { Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect, FileSystem, Layer, Path, Stdio, Terminal } from "effect" * import { Primitive } from "effect/unstable/cli" + * import { ChildProcessSpawner } from "effect/unstable/process" + * + * const CliTestLayer = Layer.mergeAll( + * FileSystem.layerNoop({}), + * Path.layer, + * Stdio.layerTest({}), + * Layer.succeed(Terminal.Terminal, Terminal.make({ + * columns: Effect.succeed(80), + * rows: Effect.succeed(24), + * readInput: Effect.die("unused"), + * readLine: Effect.die("unused"), + * display: () => Effect.void + * })), + * Layer.succeed( + * ChildProcessSpawner.ChildProcessSpawner, + * ChildProcessSpawner.make(() => Effect.die("unused")) + * ) + * ) * - * const parseInteger = Effect.gen(function*() { - * const result1 = yield* Primitive.integer.parse("42") - * console.log(result1) // 42 - * - * const result2 = yield* Primitive.integer.parse("-123") - * console.log(result2) // -123 + * const parseInteger = Effect.all([ + * Primitive.integer.parse("42"), + * Primitive.integer.parse("-123"), + * Primitive.integer.parse("0") + * ]) * - * const result3 = yield* Primitive.integer.parse("0") - * console.log(result3) // 0 - * }) + * await Effect.runPromise(parseInteger.pipe(Effect.provide(CliTestLayer))) // => [42, -123, 0] * ``` * * @category constructors @@ -215,20 +269,34 @@ export const integer: Primitive = makeSchemaPrimitive( * * **Example** (Parsing date values) * - * ```ts - * import { Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect, FileSystem, Layer, Path, Stdio, Terminal } from "effect" * import { Primitive } from "effect/unstable/cli" + * import { ChildProcessSpawner } from "effect/unstable/process" + * + * const CliTestLayer = Layer.mergeAll( + * FileSystem.layerNoop({}), + * Path.layer, + * Stdio.layerTest({}), + * Layer.succeed(Terminal.Terminal, Terminal.make({ + * columns: Effect.succeed(80), + * rows: Effect.succeed(24), + * readInput: Effect.die("unused"), + * readLine: Effect.die("unused"), + * display: () => Effect.void + * })), + * Layer.succeed( + * ChildProcessSpawner.ChildProcessSpawner, + * ChildProcessSpawner.make(() => Effect.die("unused")) + * ) + * ) * * const parseDate = Effect.gen(function*() { - * const result1 = yield* Primitive.date.parse("2023-12-25") - * console.log(result1) // Date object for December 25, 2023 - * - * const result2 = yield* Primitive.date.parse("2023-12-25T10:30:00Z") - * console.log(result2) // Date object with time - * - * const result3 = yield* Primitive.date.parse("Dec 25, 2023") - * console.log(result3) // Date object parsed from natural format + * const result = yield* Primitive.date.parse("2023-12-25") + * return result.toISOString() * }) + * + * await Effect.runPromise(parseDate.pipe(Effect.provide(CliTestLayer))) // => "2023-12-25T00:00:00.000Z" * ``` * * @category constructors @@ -236,7 +304,7 @@ export const integer: Primitive = makeSchemaPrimitive( */ export const date: Primitive = makeSchemaPrimitive( "Date", - Schema.DateValid + Schema.Date ) /** @@ -244,20 +312,35 @@ export const date: Primitive = makeSchemaPrimitive( * * **Example** (Parsing string values) * - * ```ts - * import { Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect, FileSystem, Layer, Path, Stdio, Terminal } from "effect" * import { Primitive } from "effect/unstable/cli" + * import { ChildProcessSpawner } from "effect/unstable/process" + * + * const CliTestLayer = Layer.mergeAll( + * FileSystem.layerNoop({}), + * Path.layer, + * Stdio.layerTest({}), + * Layer.succeed(Terminal.Terminal, Terminal.make({ + * columns: Effect.succeed(80), + * rows: Effect.succeed(24), + * readInput: Effect.die("unused"), + * readLine: Effect.die("unused"), + * display: () => Effect.void + * })), + * Layer.succeed( + * ChildProcessSpawner.ChildProcessSpawner, + * ChildProcessSpawner.make(() => Effect.die("unused")) + * ) + * ) * - * const parseString = Effect.gen(function*() { - * const result1 = yield* Primitive.string.parse("hello world") - * console.log(result1) // "hello world" - * - * const result2 = yield* Primitive.string.parse("") - * console.log(result2) // "" + * const parseString = Effect.all([ + * Primitive.string.parse("hello world"), + * Primitive.string.parse(""), + * Primitive.string.parse("123") + * ]) * - * const result3 = yield* Primitive.string.parse("123") - * console.log(result3) // "123" - * }) + * await Effect.runPromise(parseString.pipe(Effect.provide(CliTestLayer))) // => ["hello world", "", "123"] * ``` * * @category constructors @@ -270,9 +353,27 @@ export const string: Primitive = makePrimitive("String", (value) => Effe * * **Example** (Parsing choices) * - * ```ts - * import { Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect, FileSystem, Layer, Path, Stdio, Terminal } from "effect" * import { Primitive } from "effect/unstable/cli" + * import { ChildProcessSpawner } from "effect/unstable/process" + * + * const CliTestLayer = Layer.mergeAll( + * FileSystem.layerNoop({}), + * Path.layer, + * Stdio.layerTest({}), + * Layer.succeed(Terminal.Terminal, Terminal.make({ + * columns: Effect.succeed(80), + * rows: Effect.succeed(24), + * readInput: Effect.die("unused"), + * readLine: Effect.die("unused"), + * display: () => Effect.void + * })), + * Layer.succeed( + * ChildProcessSpawner.ChildProcessSpawner, + * ChildProcessSpawner.make(() => Effect.die("unused")) + * ) + * ) * * type LogLevel = "debug" | "info" | "warn" | "error" * @@ -283,13 +384,12 @@ export const string: Primitive = makePrimitive("String", (value) => Effe * ["error", "error"] * ]) * - * const parseLogLevel = Effect.gen(function*() { - * const result1 = yield* logLevelPrimitive.parse("info") - * console.log(result1) // "info" + * const parseLogLevel = Effect.all([ + * logLevelPrimitive.parse("info"), + * logLevelPrimitive.parse("debug") + * ]) * - * const result2 = yield* logLevelPrimitive.parse("debug") - * console.log(result2) // "debug" - * }) + * await Effect.runPromise(parseLogLevel.pipe(Effect.provide(CliTestLayer))) // => ["info", "debug"] * ``` * * @category constructors @@ -304,7 +404,7 @@ export const choice = ( if (choiceMap.has(value)) { return Effect.succeed(choiceMap.get(value)!) } - return Effect.fail(`Expected ${validChoices}, got ${format(value)}`) + return Effect.fail(validChoices) }) return Object.assign(primitive, { choiceKeys: choices.map(([key]) => key) }) } @@ -314,7 +414,7 @@ export const choice = ( * * **Example** (Choosing path validation) * - * ```ts + * ```ts import.meta.vitest * import { Primitive } from "effect/unstable/cli" * * // Only accept files @@ -325,6 +425,8 @@ export const choice = ( * * // Accept either files or directories * const anyPath = Primitive.path("either", false) + * + * const tags = [filePath._tag, dirPath._tag, anyPath._tag] // => ["Path", "Path", "Path"] * ``` * * @category models @@ -337,26 +439,38 @@ export type PathType = "file" | "directory" | "either" * * **Example** (Parsing file system paths) * - * ```ts - * import { Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect, FileSystem, Layer, Path, Stdio, Terminal } from "effect" * import { Primitive } from "effect/unstable/cli" + * import { ChildProcessSpawner } from "effect/unstable/process" + * + * const services = Layer.mergeAll( + * Path.layer, + * FileSystem.layerNoop({ + * exists: () => Effect.succeed(true), + * stat: () => Effect.succeed({ type: "File" } as FileSystem.File.Info) + * }), + * Stdio.layerTest({}), + * Layer.succeed(Terminal.Terminal, Terminal.make({ + * columns: Effect.succeed(80), + * rows: Effect.succeed(24), + * readInput: Effect.die("unused"), + * readLine: Effect.die("unused"), + * display: () => Effect.void + * })), + * Layer.succeed( + * ChildProcessSpawner.ChildProcessSpawner, + * ChildProcessSpawner.make(() => Effect.die("unused")) + * ) + * ) * * const program = Effect.gen(function*() { - * // Parse a file path that must exist * const filePrimitive = Primitive.path("file", true) * const filePath = yield* filePrimitive.parse("./package.json") - * console.log(filePath) // Absolute path to package.json + * return filePath.endsWith("/package.json") + * }).pipe(Effect.provide(services)) * - * // Parse a directory path - * const dirPrimitive = Primitive.path("directory", false) - * const dirPath = yield* dirPrimitive.parse("./src") - * console.log(dirPath) // Absolute path to src directory - * - * // Parse any path type - * const anyPrimitive = Primitive.path("either", false) - * const anyPath = yield* anyPrimitive.parse("./some/path") - * console.log(anyPath) // Absolute path - * }) + * await Effect.runPromise(program) // => true * ``` * * @category constructors @@ -365,8 +479,8 @@ export type PathType = "file" | "directory" | "either" export const path = ( pathType: PathType, mustExist?: boolean -): Primitive => - makePrimitive( +): Primitive => { + const primitive = makePrimitive( "Path", Effect.fnUntraced(function*(value) { const fs = yield* FileSystem.FileSystem @@ -404,6 +518,8 @@ export const path = ( return absolutePath }) ) + return Object.assign(primitive, { pathType }) +} /** * Creates a primitive that wraps string input in `Redacted`. @@ -415,15 +531,34 @@ export const path = ( * * **Example** (Parsing redacted values) * - * ```ts - * import { Effect, Redacted } from "effect" + * ```ts import.meta.vitest + * import { Effect, FileSystem, Layer, Path, Redacted, Stdio, Terminal } from "effect" * import { Primitive } from "effect/unstable/cli" + * import { ChildProcessSpawner } from "effect/unstable/process" + * + * const CliTestLayer = Layer.mergeAll( + * FileSystem.layerNoop({}), + * Path.layer, + * Stdio.layerTest({}), + * Layer.succeed(Terminal.Terminal, Terminal.make({ + * columns: Effect.succeed(80), + * rows: Effect.succeed(24), + * readInput: Effect.die("unused"), + * readLine: Effect.die("unused"), + * display: () => Effect.void + * })), + * Layer.succeed( + * ChildProcessSpawner.ChildProcessSpawner, + * ChildProcessSpawner.make(() => Effect.die("unused")) + * ) + * ) * * const parseRedacted = Effect.gen(function*() { * const result = yield* Primitive.redacted.parse("secret-password") - * console.log(Redacted.value(result)) // "secret-password" - * console.log(String(result)) // "" + * return [Redacted.value(result), String(result)] as const * }) + * + * await Effect.runPromise(parseRedacted.pipe(Effect.provide(CliTestLayer))) // => ["secret-password", ""] * ``` * * @category constructors @@ -439,27 +574,38 @@ export const redacted: Primitive> = makePrimitive( * * **Example** (Reading file text) * - * ```ts - * import { Effect, Schema } from "effect" + * ```ts import.meta.vitest + * import { Effect, FileSystem, Layer, Path, Stdio, Terminal } from "effect" * import { Primitive } from "effect/unstable/cli" - * - * const ConfigSchema = Schema.Struct({ - * name: Schema.String, - * version: Schema.String, - * port: Schema.Number - * }) - * const decodeConfig = Schema.decodeUnknownEffect( - * Schema.fromJsonString(ConfigSchema) + * import { ChildProcessSpawner } from "effect/unstable/process" + * + * const services = Layer.mergeAll( + * Path.layer, + * FileSystem.layerNoop({ + * exists: () => Effect.succeed(true), + * stat: () => Effect.succeed({ type: "File" } as FileSystem.File.Info), + * readFileString: () => Effect.succeed('{"private":true}') + * }), + * Stdio.layerTest({}), + * Layer.succeed(Terminal.Terminal, Terminal.make({ + * columns: Effect.succeed(80), + * rows: Effect.succeed(24), + * readInput: Effect.die("unused"), + * readLine: Effect.die("unused"), + * display: () => Effect.void + * })), + * Layer.succeed( + * ChildProcessSpawner.ChildProcessSpawner, + * ChildProcessSpawner.make(() => Effect.die("unused")) + * ) * ) * * const readConfigFile = Effect.gen(function*() { - * const content = yield* Primitive.fileText.parse("./config.json") - * console.log(content) // {"name":"my-app","version":"1.0.0","port":3000} + * const content = yield* Primitive.fileText.parse("./package.json") + * return JSON.parse(content) as { private: boolean } + * }).pipe(Effect.provide(services)) * - * const config = yield* decodeConfig(content) - * console.log(config) // { name: "my-app", version: "1.0.0", port: 3000 } - * return config - * }) + * await Effect.runPromise(readConfigFile) // => { private: true } * ``` * * @category constructors @@ -536,17 +682,40 @@ const fileParsers: Record unknown> = { * * **Example** (Parsing file content) * - * ```ts - * import { Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect, FileSystem, Layer, Path, Stdio, Terminal } from "effect" * import { Primitive } from "effect/unstable/cli" + * import { ChildProcessSpawner } from "effect/unstable/process" + * + * const services = Layer.mergeAll( + * Path.layer, + * FileSystem.layerNoop({ + * exists: () => Effect.succeed(true), + * stat: () => Effect.succeed({ type: "File" } as FileSystem.File.Info), + * readFileString: () => Effect.succeed('{"private":true}') + * }), + * Stdio.layerTest({}), + * Layer.succeed(Terminal.Terminal, Terminal.make({ + * columns: Effect.succeed(80), + * rows: Effect.succeed(24), + * readInput: Effect.die("unused"), + * readLine: Effect.die("unused"), + * display: () => Effect.void + * })), + * Layer.succeed( + * ChildProcessSpawner.ChildProcessSpawner, + * ChildProcessSpawner.make(() => Effect.die("unused")) + * ) + * ) * - * const tomlFilePrimitive = Primitive.fileParse({ format: "toml" }) + * const jsonFilePrimitive = Primitive.fileParse({ format: "json" }) * * const loadConfig = Effect.gen(function*() { - * const config = yield* tomlFilePrimitive.parse("./config.toml") - * console.log(config) // { name: "my-app", version: "1.0.0", port: 3000 } - * return config - * }) + * const config = yield* jsonFilePrimitive.parse("./package.json") + * return config as { private: boolean } + * }).pipe(Effect.provide(services)) + * + * await Effect.runPromise(loadConfig) // => { private: true } * ``` * * @category constructors @@ -588,14 +757,34 @@ export type FileSchemaOptions = Struct.Simplify< * * **Example** (Parsing file content with a schema) * - * ```ts - * import { Effect, Schema } from "effect" + * ```ts import.meta.vitest + * import { Effect, FileSystem, Layer, Path, Schema, Stdio, Terminal } from "effect" * import { Primitive } from "effect/unstable/cli" + * import { ChildProcessSpawner } from "effect/unstable/process" + * + * const services = Layer.mergeAll( + * Path.layer, + * FileSystem.layerNoop({ + * exists: () => Effect.succeed(true), + * stat: () => Effect.succeed({ type: "File" } as FileSystem.File.Info), + * readFileString: () => Effect.succeed('{"private":true}') + * }), + * Stdio.layerTest({}), + * Layer.succeed(Terminal.Terminal, Terminal.make({ + * columns: Effect.succeed(80), + * rows: Effect.succeed(24), + * readInput: Effect.die("unused"), + * readLine: Effect.die("unused"), + * display: () => Effect.void + * })), + * Layer.succeed( + * ChildProcessSpawner.ChildProcessSpawner, + * ChildProcessSpawner.make(() => Effect.die("unused")) + * ) + * ) * * const ConfigSchema = Schema.Struct({ - * name: Schema.String, - * version: Schema.String, - * port: Schema.Number + * private: Schema.Boolean * }) * * const jsonConfigPrimitive = Primitive.fileSchema(ConfigSchema, { @@ -603,10 +792,10 @@ export type FileSchemaOptions = Struct.Simplify< * }) * * const loadConfig = Effect.gen(function*() { - * const config = yield* jsonConfigPrimitive.parse("./config.json") - * console.log(config) // { name: "my-app", version: "1.0.0", port: 3000 } - * return config - * }) + * return yield* jsonConfigPrimitive.parse("./package.json") + * }).pipe(Effect.provide(services)) + * + * await Effect.runPromise(loadConfig) // => { private: true } * ``` * * @category constructors @@ -634,20 +823,36 @@ export const fileSchema = ( * * **Example** (Parsing key-value pairs) * - * ```ts - * import { Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect, FileSystem, Layer, Path, Stdio, Terminal } from "effect" * import { Primitive } from "effect/unstable/cli" + * import { ChildProcessSpawner } from "effect/unstable/process" + * + * const CliTestLayer = Layer.mergeAll( + * FileSystem.layerNoop({}), + * Path.layer, + * Stdio.layerTest({}), + * Layer.succeed(Terminal.Terminal, Terminal.make({ + * columns: Effect.succeed(80), + * rows: Effect.succeed(24), + * readInput: Effect.die("unused"), + * readLine: Effect.die("unused"), + * display: () => Effect.void + * })), + * Layer.succeed( + * ChildProcessSpawner.ChildProcessSpawner, + * ChildProcessSpawner.make(() => Effect.die("unused")) + * ) + * ) * - * const parseKeyValue = Effect.gen(function*() { - * const result1 = yield* Primitive.keyValuePair.parse("name=john") - * console.log(result1) // { name: "john" } - * - * const result2 = yield* Primitive.keyValuePair.parse("port=3000") - * console.log(result2) // { port: "3000" } + * const parseKeyValue = Effect.all([ + * Primitive.keyValuePair.parse("name=john"), + * Primitive.keyValuePair.parse("port=3000"), + * Primitive.keyValuePair.parse("debug=true") + * ]) * - * const result3 = yield* Primitive.keyValuePair.parse("debug=true") - * console.log(result3) // { debug: "true" } - * }) + * const result = await Effect.runPromise(parseKeyValue.pipe(Effect.provide(CliTestLayer))) + * result // => [{ name: "john" }, { port: "3000" }, { debug: "true" }] * ``` * * @category constructors @@ -681,16 +886,34 @@ export const keyValuePair: Primitive> = makePrimitive( * * **Example** (Rejecting option values) * - * ```ts - * import { Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect, FileSystem, Layer, Path, Stdio, Terminal } from "effect" * import { Primitive } from "effect/unstable/cli" + * import { ChildProcessSpawner } from "effect/unstable/process" + * + * const CliTestLayer = Layer.mergeAll( + * FileSystem.layerNoop({}), + * Path.layer, + * Stdio.layerTest({}), + * Layer.succeed(Terminal.Terminal, Terminal.make({ + * columns: Effect.succeed(80), + * rows: Effect.succeed(24), + * readInput: Effect.die("unused"), + * readLine: Effect.die("unused"), + * display: () => Effect.void + * })), + * Layer.succeed( + * ChildProcessSpawner.ChildProcessSpawner, + * ChildProcessSpawner.make(() => Effect.die("unused")) + * ) + * ) * * const program = Effect.gen(function*() { * // This will always fail - useful for boolean flags * return yield* Primitive.none.parse("any-value") * }) * - * // The above effect will fail with "This option does not accept values" + * await Effect.runPromise(Effect.flip(program).pipe(Effect.provide(CliTestLayer))) // => "This option does not accept values" * ``` * * @category constructors @@ -708,20 +931,20 @@ export const none: Primitive = makePrimitive("None", () => Effect.fail("T * * **Example** (Getting primitive type names) * - * ```ts + * ```ts import.meta.vitest * import { Primitive } from "effect/unstable/cli" * - * console.log(Primitive.getTypeName(Primitive.string)) // "string" - * console.log(Primitive.getTypeName(Primitive.integer)) // "integer" - * console.log(Primitive.getTypeName(Primitive.boolean)) // "boolean" - * console.log(Primitive.getTypeName(Primitive.date)) // "date" - * console.log(Primitive.getTypeName(Primitive.keyValuePair)) // "key=value" + * Primitive.getTypeName(Primitive.string) // => "string" + * Primitive.getTypeName(Primitive.integer) // => "integer" + * Primitive.getTypeName(Primitive.boolean) // => "boolean" + * Primitive.getTypeName(Primitive.date) // => "date" + * Primitive.getTypeName(Primitive.keyValuePair) // => "key=value" * * const logLevelChoice = Primitive.choice([ * ["debug", "debug"], * ["info", "info"] * ]) - * console.log(Primitive.getTypeName(logLevelChoice)) // "choice" + * Primitive.getTypeName(logLevelChoice) // => "choice" * ``` * * @category getters @@ -763,3 +986,7 @@ export const getTypeName = (primitive: Primitive): string => { /** @internal */ export const getChoiceKeys = (primitive: Primitive): ReadonlyArray | undefined => primitive._tag === "Choice" ? (primitive as any).choiceKeys : undefined + +/** @internal */ +export const getPathType = (primitive: Primitive): PathType | undefined => + primitive._tag === "Path" ? (primitive as any).pathType : undefined diff --git a/repos/effect/packages/effect/src/unstable/cli/Prompt.ts b/repos/effect/packages/effect/src/unstable/cli/Prompt.ts index 1a0443ba15..f21b1f0100 100644 --- a/repos/effect/packages/effect/src/unstable/cli/Prompt.ts +++ b/repos/effect/packages/effect/src/unstable/cli/Prompt.ts @@ -29,6 +29,11 @@ import type { Covariant } from "../../Types.ts" import * as Ansi from "./internal/ansi.ts" import type * as Primitive from "./Primitive.ts" +declare const process: { + readonly platform: string + readonly cwd: () => string +} + const TypeId = "~effect/cli/Prompt" /** @@ -651,28 +656,35 @@ export declare namespace All { * * **Example** (Collecting prompt results) * - * ```ts - * import { Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect, FileSystem, Layer, Path, Terminal } from "effect" * import { Prompt } from "effect/unstable/cli" * - * const username = Prompt.text({ - * message: "Enter your username: " + * const terminal = Terminal.make({ + * columns: Effect.succeed(80), + * rows: Effect.succeed(24), + * readInput: Effect.succeed({} as never), + * readLine: Effect.die("unused"), + * display: () => Effect.void * }) + * const services = Layer.mergeAll( + * FileSystem.layerNoop({}), + * Path.layer, + * Layer.succeed(Terminal.Terminal, terminal) + * ) * - * const password = Prompt.password({ - * message: "Enter your password: ", - * validate: (value) => - * value.length === 0 - * ? Effect.fail("Password cannot be empty") - * : Effect.succeed(value) - * }) + * const username = Prompt.succeed("alice") + * const password = Prompt.succeed("secret") * * const allWithTuple = Prompt.all([username, password]) * * const allWithRecord = Prompt.all({ username, password }) + * + * await Effect.runPromise(Effect.provide(allWithTuple, services)) // => ["alice", "secret"] + * await Effect.runPromise(Effect.provide(allWithRecord, services)) // => { username: "alice", password: "secret" } * ``` * - * @category collecting & elements + * @category combining * @since 4.0.0 */ export const all: < @@ -681,10 +693,13 @@ export const all: < if (arguments.length === 1) { if (isPrompt(arguments[0])) { return map(arguments[0], (x) => [x]) as any - } else if (Array.isArray(arguments[0])) { - return allTupled(arguments[0]) as any + } else if (Predicate.isIterable(arguments[0])) { + return allTupled(Arr.fromIterable(arguments[0] as Iterable>)) as any } else { const entries = Object.entries(arguments[0] as Readonly<{ [K: string]: Prompt }>) + if (entries.length === 0) { + return succeed({}) as any + } let result = map(entries[0][1], (value) => ({ [entries[0][0]]: value })) if (entries.length === 1) { return result as any @@ -1070,7 +1085,7 @@ export const password = ( * The returned effect may fail with `Terminal.QuitError` if terminal input ends * or the prompt is quit. * - * @category execution + * @category running * @since 4.0.0 */ export const run: ( @@ -1136,7 +1151,7 @@ export const select = (options: SelectOptions): Prompt => { * * **Example** (Filtering choices with autocomplete) * - * ```ts + * ```ts import.meta.vitest * import { Prompt } from "effect/unstable/cli" * * const language = Prompt.autoComplete({ @@ -1147,6 +1162,8 @@ export const select = (options: SelectOptions): Prompt => { * { title: "Kotlin", value: "kt" } * ] * }) + * + * Prompt.isPrompt(language) // => true * ``` * * @category constructors @@ -1202,7 +1219,7 @@ export const multiSelect = ( const initialSelected = new Set() for (let i = 0; i < opts.choices.length; i++) { const choice = opts.choices[i] as SelectChoice - if (choice.selected === true) { + if (choice.selected === true && !choice.disabled) { initialSelected.add(i) } } @@ -1985,6 +2002,9 @@ class Day extends DatePart { } private ordinalIndicator(day: number): string { + if (day >= 11 && day <= 13) { + return "th" + } switch (day % 10) { case 1: return "st" @@ -2043,7 +2063,7 @@ class Year extends DatePart { override toString() { const year = `${this.date.getFullYear()}`.padStart(4, "0") return this.token.length === 2 - ? year.substring(-2) + ? year.slice(-2) : year } } @@ -2060,7 +2080,7 @@ class Meridiem extends DatePart { setValue(_value: string): void {} override toString() { - const meridiem = this.date.getHours() > 12 ? "pm" : "am" + const meridiem = this.date.getHours() >= 12 ? "pm" : "am" return /A/.test(this.token) ? meridiem.toUpperCase() : meridiem @@ -2547,9 +2567,9 @@ const renderMultiSelectChoices = ( renderOptions?: RenderOptions | undefined ) => { const choices = options.choices - const totalChoices = choices.length - const selectedCount = state.selectedIndices.size - const allSelected = selectedCount === totalChoices + const selectableCount = choices.filter((choice) => !choice.disabled).length + const selectedCount = Array.from(state.selectedIndices).filter((index) => !choices[index].disabled).length + const allSelected = selectedCount === selectableCount const selectAllText = allSelected ? options?.selectNone ?? "Select None" @@ -2585,8 +2605,9 @@ const renderMultiSelectChoices = ( const annotatedCheckbox = isHighlighted && renderOptions?.plain !== true ? Ansi.annotate(checkbox, Ansi.cyanBright) : checkbox - const title = renderMultiSelectTitle(choice.title, isHighlighted, renderOptions) - const description = renderChoiceDescription(choice as SelectChoice, isHighlighted, renderOptions) + const selectChoice = choice as SelectChoice + const title = renderChoiceTitle(selectChoice, isHighlighted, renderOptions) + const description = renderChoiceDescription(selectChoice, isHighlighted, renderOptions) documents.push(prefix + " " + annotatedCheckbox + " " + title + " " + description) } } @@ -2635,16 +2656,20 @@ const processSpace = ( ) => { const selectedIndices = new Set(state.selectedIndices) if (state.index === 0) { - if (state.selectedIndices.size === options.choices.length) { + const selectableCount = options.choices.filter((choice) => !choice.disabled).length + const selectedCount = Array.from(state.selectedIndices).filter((index) => !options.choices[index].disabled).length + if (selectedCount === selectableCount) { selectedIndices.clear() } else { for (let i = 0; i < options.choices.length; i++) { - selectedIndices.add(i) + if (!options.choices[i].disabled) { + selectedIndices.add(i) + } } } } else if (state.index === 1) { for (let i = 0; i < options.choices.length; i++) { - if (state.selectedIndices.has(i)) { + if (options.choices[i].disabled || state.selectedIndices.has(i)) { selectedIndices.delete(i) } else { selectedIndices.add(i) @@ -2652,7 +2677,9 @@ const processSpace = ( } } else { const choiceIndex = state.index - metaOptionsCount - if (selectedIndices.has(choiceIndex)) { + if (options.choices[choiceIndex].disabled) { + return Effect.succeed(Action.Beep()) + } else if (selectedIndices.has(choiceIndex)) { selectedIndices.delete(choiceIndex) } else { selectedIndices.add(choiceIndex) @@ -2692,7 +2719,8 @@ const handleMultiSelectProcess = (options: SelectOptionsReq & MultiSelectO } case "enter": case "return": { - const selectedCount = state.selectedIndices.size + const selectedIndices = Array.from(state.selectedIndices).filter((index) => !options.choices[index].disabled) + const selectedCount = selectedIndices.length if (options.min !== undefined && selectedCount < options.min) { return Effect.succeed( Action.NextFrame({ state: { ...state, error: Option.some(`At least ${options.min} are required`) } }) @@ -2703,9 +2731,7 @@ const handleMultiSelectProcess = (options: SelectOptionsReq & MultiSelectO Action.NextFrame({ state: { ...state, error: Option.some(`At most ${options.max} choices are allowed`) } }) ) } - const selectedValues = Array.from(state.selectedIndices).sort(EffectNumber.Order).map((index) => - options.choices[index].value - ) + const selectedValues = selectedIndices.sort(EffectNumber.Order).map((index) => options.choices[index].value) return Effect.succeed(Action.Submit({ value: selectedValues })) } default: { @@ -2861,7 +2887,11 @@ const defaultFloatProcessor = (input: string, state: NumberState) => { return Effect.succeed(Action.NextFrame({ state: { ...state, - value: input === "." ? `${parsed}.` : `${parsed}`, + value: input === "." + ? `${parsed}.` + : state.value.includes(".") && /^\d$/.test(input) + ? state.value + input + : `${parsed}`, error: Option.none() } })) diff --git a/repos/effect/packages/effect/src/unstable/cli/SEMANTICS.md b/repos/effect/packages/effect/src/unstable/cli/SEMANTICS.md deleted file mode 100644 index 64e57bb612..0000000000 --- a/repos/effect/packages/effect/src/unstable/cli/SEMANTICS.md +++ /dev/null @@ -1,157 +0,0 @@ -# CLI Semantics (Effect CLI) - -This file records the intended parsing semantics with a short usage example and the test that locks it in. Examples show shell usage, not code. - -- **Shared parent flags allowed before or after subcommand (npm-style)**\ - Example: `tool --global install --pkg cowsay` and `tool install --pkg cowsay --global` (when `--global` is declared with `Command.withSharedFlags(...)`)\ - Test: `packages/effect/test/unstable/cli/Command.test.ts` – "should accept shared parent flags before or after a subcommand (npm-style)" - -- **Local parent flags are not inherited by subcommands**\ - Example: `tool --workspace docs chat --topic bugs` fails when `--workspace` is local to `tool`\ - Test: `packages/effect/test/unstable/cli/Command.test.ts` – "should reject parent local flags on subcommand paths" - -- **Only the first value token may open a subcommand; later values are operands**\ - Example: `tool install pkg1 pkg2` → `install` chosen as subcommand; `pkg1 pkg2` are operands\ - Test: `packages/effect/test/unstable/cli/Command.test.ts` – "should accept shared parent flags before or after a subcommand (npm-style)" (second invocation covers later operands) - -- **`--` stops option parsing; everything after is an operand (no subcommands/flags)**\ - Example: `tool -- child --value x` → operands: `child --value x`; subcommand `child` is not entered\ - Test: `packages/effect/test/unstable/cli/Command.test.ts` – "should treat tokens after -- as operands (no subcommand or flags)" - -- **Options may appear before, after, or between operands (relaxed POSIX Guideline 9)**\ - Examples: `tool copy --recursive src dest`, `tool copy src dest --recursive`, `tool copy --recursive src dest --force`\ - Test: `packages/effect/test/unstable/cli/Command.test.ts` – "should support options before, after, or between operands (relaxed POSIX Syntax Guideline No. 9)" - -- **Boolean flags default to true when present; explicit true/false literals are accepted immediately after**\ - Example: `tool --verbose deploy --target-version 1.0.0`\ - Test: `packages/effect/test/unstable/cli/Command.test.ts` – "should handle boolean flags before subcommands" - -- **Boolean flags support canonical `--no-` negation, and optional booleans distinguish omission from explicit false**\ - Example: `tool --no-verbose` → `false`; `--no-verbose=false` is rejected; `Flag.optional(Flag.boolean("verbose"))` yields `Option.none()` when omitted\ - Test: `packages/effect/test/unstable/cli/Command.test.ts` – "should support optional boolean flags and --no- negation" - -- **Unknown subcommands emit suggestions**\ - Example: `tool cpy` → suggests `copy`\ - Test: `packages/effect/test/unstable/cli/Command.test.ts` – "should suggest similar subcommands for unknown subcommands" - -- **Unknown options emit suggestions (long and short)**\ - Examples: `tool --debugs copy ...`, `tool -u copy ...`\ - Tests: `packages/effect/test/unstable/cli/Command.test.ts` – "should suggest similar options for unrecognized options" and "should suggest similar short options for unrecognized short options" - -- **Repeated key=value flags merge into one map**\ - Example: `tool env --env foo=bar --env cool=dude` → `{ foo: "bar", cool: "dude" }`\ - Test: `packages/effect/test/unstable/cli/Command.test.ts` – "should merge repeated key=value flags into a single record" - -- **Shared parent context is accessible inside subcommands** - Example: `tool --global install --pkg cowsay` → subcommand can read `global` from parent context when `global` is shared - Test: `packages/effect/test/unstable/cli/Command.test.ts` – "should allow direct accessing parent config in subcommands" - -- **Built-in flags (`--version`, `--help`) take global precedence** - Example: `tool --version copy src dest` → prints version and exits; subcommand is not run - Tests: `packages/effect/test/unstable/cli/Command.test.ts` – "should print version and exit even with subcommands (global precedence)" and "should print version and exit when --version appears before subcommand" - -If you add or change semantics, update this file and reference the exact test that proves the behavior. - -## Semantics Landscape (popular CLIs vs Effect) - -Below each semantic you’ll find: a short description, a usage example, how major CLI libraries handle it (and whether it’s configurable), and where Effect currently lands (with suggestions if any). - -### Parent flags after a subcommand name (npm-style globals) - -- **What**: Whether options defined on the parent command can appear _after_ the subcommand token. -- **Example**: `tool install --pkg cowsay --global` -- **Commander / yargs / clap**: Allowed by default; commander/clap can tighten with options. -- **Click / argparse / docopt**: Not allowed; options must be before the command they belong to. -- **Cobra**: Persistent flags are available to children; placement is typically before subcommand, but not strictly enforced. -- **Effect (current)**: Allowed for parent flags explicitly declared as shared via `Command.withSharedFlags(...)`. Backed by test: “should accept shared parent flags before or after a subcommand (npm-style)”. -- **Suggestion**: Keep permissive placement for shared flags, with local-by-default declaration semantics. - -### Options after operands (relaxed POSIX Guideline 9) - -- **What**: Whether flags can appear after or between positional arguments. -- **Example**: `tool copy src dest --recursive` or `tool copy --recursive src dest --force` -- **Commander / yargs / clap**: Allowed by default; clap can be configured to prefer subcommand precedence. -- **Click / argparse / docopt**: Disallow; options must precede positionals for that command. -- **Cobra**: Generally allows mixed order; `--` ends options. -- **Effect (current)**: Allowed. Test: “should support options before, after, or between operands (relaxed POSIX Syntax Guideline No. 9)”. -- **Suggestion**: Keep permissive; no change. - -### Subcommand selection: only the first value may open a subcommand - -- **What**: Whether only the first non-option token can be treated as a subcommand name. -- **Example**: `tool install pkg1 pkg2` → `install` is subcommand; `pkg1 pkg2` are operands. -- **Commander / yargs / clap / Click / argparse / docopt / Cobra**: Yes, subcommand chosen at first value (unless special external-subcommand features are enabled). -- **Effect (current)**: Yes. Covered implicitly by subcommand tests. -- **Suggestion**: Keep; this is the least surprising and matches most ecosystems. - -### End-of-options marker `--` - -- **What**: Tokens after `--` are treated purely as operands. -- **Example**: `tool -- child --value x` → operands `child --value x`. -- **Commander / yargs / clap / Click / argparse / docopt / Cobra**: Supported. -- **Effect (current)**: Supported. Test: “should treat tokens after -- as operands (no subcommand or flags)”. -- **Suggestion**: Keep; already locked in by test. - -### Boolean flags defaulting to true when present; optional explicit literal - -- **What**: Supplying `--flag` implies true; explicit `--flag false` (or 0/no/off) is accepted. -- **Example**: `tool --verbose deploy` and `tool --verbose false deploy`. -- **Commander / yargs / clap**: Yes; boolean options coerce common literals. -- **Click / argparse / docopt**: Typically `--flag/--no-flag` pairs; some accept explicit literals with `type=bool` in argparse. -- **Effect (current)**: Yes. Test: “should handle boolean flags before subcommands”. -- **Suggestion**: Keep; no change. - -### Canonical `--no-` boolean negation - -- **What**: Boolean flags accept a canonical negated long form in addition to explicit literals. -- **Example**: `tool --no-verbose`. -- **Value form**: Negated form does not accept an explicit value; use `--verbose false` or bare `--no-verbose`. -- **Commander / yargs / clap / Click**: Commonly supported for long boolean options. -- **Effect (current)**: Supported for canonical long flag names. Test: “should support optional boolean flags and --no- negation”. -- **Suggestion**: Keep; this matches the existing docs and generated completions. - -### Unknown subcommands/options suggestions - -- **What**: Whether unrecognized tokens produce Levenshtein suggestions. -- **Example**: `tool cpy` → suggest `copy`; `tool --debugs` → suggest `--debug`. -- **Commander / yargs / clap**: Suggestions configurable (clap has built-in `color`/`suggestions`); commander can display “Did you mean”. -- **Click / argparse / docopt / Cobra**: Usually fail with an error; some wrappers add suggestions. -- **Effect (current)**: Suggestions enabled; tests cover unknown subcommands and options (short/long). -- **Suggestion**: Keep; already aligned with “friendly” CLIs. - -### Parent context accessible in subcommands - -- **What**: Ability for subcommand handlers to read parent flags intentionally shared by the parent command. -- **Example**: `tool --global install --pkg cowsay` → subcommand reads `global` when `global` is shared. -- **Commander / yargs / clap / Cobra**: Supported via shared options or persistent flags; Click/argparse require manual plumbing. -- **Effect (current)**: Supported for shared flags only; test “should allow direct accessing parent config in subcommands”. -- **Suggestion**: Keep; explicit sharing avoids accidental inheritance. - -### Repeated key=value flags merging - -- **What**: Multiple `--env KEY=VAL` occurrences merge into a single map. -- **Example**: `tool --env foo=bar --env cool=dude` → `{foo: "bar", cool: "dude"}`. -- **Commander / yargs / clap**: Arrays/maps supported via custom options; behavior differs unless configured. -- **Effect (current)**: Merge; test “should merge repeated key=value flags into a single record”. -- **Suggestion**: Keep; predictable and convenient. - -### Required or mutually exclusive flag sets (validation, not parsing) - -- **What**: Constraints like "must provide either --token or --user/--pass", or "--json and --color cannot both be set". -- **Commander / yargs / clap**: Provide APIs for required/conflicts; Click supports required options and `mutually_exclusive` via groups; argparse has mutually exclusive groups. -- **Effect (current)**: Not enforced at parser level. Would live in a validation layer if needed. -- **Suggestion**: Stay out of parsing; add optional validators in configuration if/when a product needs it. - -### Built-in flags (`--version`, `--help`) global precedence - -- **What**: Whether `--version` and `--help` take precedence over subcommands and other arguments. -- **Example**: `tool --version install pkg` or `tool install --version pkg` -- **git / cargo (clap) / npm**: `--version` anywhere prints version and exits. Subcommand is ignored. -- **gh (Cobra)**: `--version` only works alone on root command. `gh --version pr` errors. -- **docker / bun**: Subcommand runs; `--version` on root is ignored if subcommand follows. -- **Effect (current)**: Global precedence (git/npm style). `--version` prints version and exits regardless of subcommands or position. -- **Suggestion**: Keep; this is the most user-friendly behavior. Users expect `--version` to work anywhere. - -## Opinionated default - -Effect should remain on the permissive, npm/commander-style side: flexible option placement, shared parent flags usable before or after subcommands, strict `--` handling, and single-shot subcommand selection on the first value. This keeps UX friendly for modern CLIs while remaining predictable via documented rules and tests. diff --git a/repos/effect/packages/effect/src/unstable/cli/index.ts b/repos/effect/packages/effect/src/unstable/cli/index.ts index 265b349619..e86b2598cf 100644 --- a/repos/effect/packages/effect/src/unstable/cli/index.ts +++ b/repos/effect/packages/effect/src/unstable/cli/index.ts @@ -9,6 +9,11 @@ */ export * as Argument from "./Argument.ts" +/** + * @since 4.0.0 + */ +export * as CliConfig from "./CliConfig.ts" + /** * @since 4.0.0 */ diff --git a/repos/effect/packages/effect/src/unstable/cli/internal/ansi.ts b/repos/effect/packages/effect/src/unstable/cli/internal/ansi.ts index 485312f67b..bfc4392ab8 100644 --- a/repos/effect/packages/effect/src/unstable/cli/internal/ansi.ts +++ b/repos/effect/packages/effect/src/unstable/cli/internal/ansi.ts @@ -57,6 +57,9 @@ export const red = `${ESC}31m` /** @internal */ export const green = `${ESC}32m` +/** @internal */ +export const magenta = `${ESC}35m` + /** @internal */ export const white = `${ESC}37m` @@ -76,9 +79,9 @@ export const combine = (...styles: Array): Array => styles /** @internal */ export const cursorTo = (column: number, row?: number): string => { if (row === undefined) { - return `\x1b${Math.max(column + 1, 0)} G` + return `${ESC}${Math.max(column + 1, 0)}G` } - return `\x1b${row + 1}${SEP}${Math.max(column + 1, 0)} H` + return `${ESC}${row + 1}${SEP}${Math.max(column + 1, 0)}H` } /** @internal */ diff --git a/repos/effect/packages/effect/src/unstable/cli/internal/command.ts b/repos/effect/packages/effect/src/unstable/cli/internal/command.ts index b35279b1c5..543f283d6e 100644 --- a/repos/effect/packages/effect/src/unstable/cli/internal/command.ts +++ b/repos/effect/packages/effect/src/unstable/cli/internal/command.ts @@ -98,7 +98,7 @@ export const makeCommand = | undefined readonly subcommands?: ReadonlyArray | undefined readonly parse?: ((input: ParsedTokens) => Effect.Effect) | undefined @@ -125,7 +125,7 @@ export const makeCommand = ): HelpDoc => { const args: Array = [] @@ -139,7 +139,8 @@ export const makeCommand = min > 0)), variadic: metadata.isVariadic }) } @@ -147,8 +148,8 @@ export const makeCommand = 0 ? commandPath.join(" ") : options.name // Only render `` in usage when at least one visible subcommand - // exists; an all-hidden subcommand tree should look like a leaf command. - if (subcommands.some((group) => group.commands.some((c) => !c.hidden))) { + // exists; an all-unlisted subcommand tree should look like a leaf command. + if (subcommands.some((group) => group.commands.some((c) => !c.unlisted))) { usage += " " } usage += " [flags]" @@ -159,21 +160,22 @@ export const makeCommand = = { @@ -429,7 +429,7 @@ export type EncodedUnprocessedOptions = { * The fields distinguish existing requests from new requests and carry the * driver-specific pagination cursor. * - * @category Encoded + * @category models * @since 4.0.0 */ export type EncodedRepliesOptions = { @@ -606,11 +606,7 @@ export const makeEncoded: (encoded: Encoded) => Effect.Effect< ), Effect.asVoid ), - saveReply: (reply) => - Effect.flatMap( - Reply.serialize(reply), - encoded.saveReply - ), + saveReply: (reply) => Effect.flatMap(Reply.serializeOrDefect(reply), encoded.saveReply), clearReplies: encoded.clearReplies, repliesFor: Effect.fnUntraced(function*(messages) { const requestIds = Arr.empty() @@ -793,7 +789,7 @@ export const noop: MessageStorage["Service"] = Effect.runSync(make({ * It stores the encoded envelope, last acknowledged chunk, accumulated replies, * and optional delivery time. * - * @category memory + * @category models * @since 4.0.0 */ export type MemoryEntry = { @@ -806,7 +802,7 @@ export type MemoryEntry = { /** * Provides a context reference used in tests to simulate a transaction. * - * @category memory + * @category services * @since 4.0.0 */ export const MemoryTransaction = Context.Reference("effect/cluster/MessageStorage/MemoryTransaction", { @@ -822,7 +818,7 @@ export const MemoryTransaction = Context.Reference("effect/cluster/Mess * maps used to track requests, primary keys, unprocessed envelopes, reply IDs, * and the journal. * - * @category memory + * @category services * @since 4.0.0 */ export class MemoryDriver extends Context.Service()("effect/cluster/MessageStorage/MemoryDriver", { @@ -995,6 +991,14 @@ export class MemoryDriver extends Context.Service()("effect/cluste resetAddress: () => Effect.void, clearAddress: (address) => Effect.sync(() => { + for (const [primaryKey, entry] of requestsByPrimaryKey) { + const envelope = entry.envelope + const sameAddress = address.entityType === envelope.address.entityType && + address.entityId === envelope.address.entityId + if (sameAddress) { + requestsByPrimaryKey.delete(primaryKey) + } + } for (let i = journal.length - 1; i >= 0; i--) { const envelope = journal[i] const sameAddress = address.entityType === envelope.address.entityType && diff --git a/repos/effect/packages/effect/src/unstable/cluster/Reply.ts b/repos/effect/packages/effect/src/unstable/cluster/Reply.ts index fea424f5f2..2f8b310e63 100644 --- a/repos/effect/packages/effect/src/unstable/cluster/Reply.ts +++ b/repos/effect/packages/effect/src/unstable/cluster/Reply.ts @@ -242,11 +242,10 @@ export class Chunk extends Data.TaggedClass("Chunk")<{ [success], ([success]) => (input, ast, options) => { if (!isReply(input) || input._tag !== "Chunk") { - return Effect.fail(new SchemaIssue.InvalidType(ast, Option.some(input))) + return Effect.fail(new SchemaIssue.InvalidType(ast, input, options)) } return Effect.mapBothEager(SchemaParser.decodeEffect(Schema.NonEmptyArray(success))(input.values, options), { - onFailure: (issue) => - new SchemaIssue.Composite(ast, Option.some(input), [new SchemaIssue.Pointer(["values"], issue)]), + onFailure: (issue) => SchemaIssue.makeCompositeAtKey(ast, "values", issue, input, options), onSuccess: (values) => new Chunk({ ...input, values } as any) }) }, @@ -258,7 +257,7 @@ export class Chunk extends Data.TaggedClass("Chunk")<{ _tag: Schema.Literal("Chunk"), requestId: SnowflakeFromBigInt, id: SnowflakeFromBigInt, - sequence: Schema.Number, + sequence: Schema.Int, values: Schema.NonEmptyArray(success) }), SchemaTransformation.transform({ @@ -352,11 +351,10 @@ export class WithExit extends Data.TaggedClass("WithExit")<{ [exitSchema], ([exit]) => (input, ast, options) => { if (!isReply(input) || input._tag !== "WithExit") { - return Effect.fail(new SchemaIssue.InvalidType(ast, Option.some(input))) + return Effect.fail(new SchemaIssue.InvalidType(ast, input, options)) } return Effect.mapBothEager(SchemaParser.decodeEffect(exit)(input.exit, options), { - onFailure: (issue) => - new SchemaIssue.Composite(ast, Option.some(input), [new SchemaIssue.Pointer(["exit"], issue)]), + onFailure: (issue) => SchemaIssue.makeCompositeAtKey(ast, "exit", issue, input, options), onSuccess: (exit) => new WithExit({ ...input, exit: exit as any }) }) }, @@ -435,6 +433,27 @@ export const serialize = ( ) } +/** + * Serializes a `ReplyWithContext`, falling back to a serializable defect reply + * when the original reply cannot be encoded. + * + * @category serialization + * @since 4.0.0 + */ +export const serializeOrDefect = ( + self: ReplyWithContext +): Effect.Effect => + Effect.catchTag( + serialize(self), + "MalformedMessage", + (error) => + Effect.orDie(serialize(ReplyWithContext.fromDefect({ + id: self.reply.id, + requestId: self.reply.requestId, + defect: error + }))) + ) + /** * Serializes an outgoing request's last received reply when one exists, returning * `None` when no reply has been received and refailing encoding errors as diff --git a/repos/effect/packages/effect/src/unstable/cluster/Runner.ts b/repos/effect/packages/effect/src/unstable/cluster/Runner.ts index d5a54e787d..1cbe895aa1 100644 --- a/repos/effect/packages/effect/src/unstable/cluster/Runner.ts +++ b/repos/effect/packages/effect/src/unstable/cluster/Runner.ts @@ -29,7 +29,7 @@ const TypeId = "~effect/cluster/Runner" export class Runner extends Schema.Class(TypeId)({ address: RunnerAddress, groups: Schema.Array(Schema.String), - weight: Schema.Number + weight: Schema.Finite }) { /** * Formatter for rendering runner values consistently. diff --git a/repos/effect/packages/effect/src/unstable/cluster/RunnerAddress.ts b/repos/effect/packages/effect/src/unstable/cluster/RunnerAddress.ts index 69ce6b2392..308bb6d7f0 100644 --- a/repos/effect/packages/effect/src/unstable/cluster/RunnerAddress.ts +++ b/repos/effect/packages/effect/src/unstable/cluster/RunnerAddress.ts @@ -28,7 +28,7 @@ const TypeId = "~effect/cluster/RunnerAddress" */ export class RunnerAddress extends Schema.Class(TypeId)({ host: Schema.String, - port: Schema.Number + port: Schema.Int }) { /** * Marks this value as a cluster runner address for runtime guards. diff --git a/repos/effect/packages/effect/src/unstable/cluster/RunnerHealth.ts b/repos/effect/packages/effect/src/unstable/cluster/RunnerHealth.ts index 51ddcb5fa9..966d0d8cda 100644 --- a/repos/effect/packages/effect/src/unstable/cluster/RunnerHealth.ts +++ b/repos/effect/packages/effect/src/unstable/cluster/RunnerHealth.ts @@ -27,7 +27,7 @@ import * as Runners from "./Runners.ts" * still be processing messages. If a Runner is not responsive, then its * associated shards can and will be re-assigned to a different Runner. * - * @category models + * @category services * @since 4.0.0 */ export class RunnerHealth extends Context.Service< diff --git a/repos/effect/packages/effect/src/unstable/cluster/RunnerServer.ts b/repos/effect/packages/effect/src/unstable/cluster/RunnerServer.ts index cea221b383..4ac30093a4 100644 --- a/repos/effect/packages/effect/src/unstable/cluster/RunnerServer.ts +++ b/repos/effect/packages/effect/src/unstable/cluster/RunnerServer.ts @@ -10,6 +10,7 @@ * * @since 4.0.0 */ +import type * as Cause from "../../Cause.ts" import * as Effect from "../../Effect.ts" import type * as Exit from "../../Exit.ts" import * as Fiber from "../../Fiber.ts" @@ -17,6 +18,7 @@ import { constant } from "../../Function.ts" import * as Layer from "../../Layer.ts" import * as Option from "../../Option.ts" import * as Queue from "../../Queue.ts" +import type * as Rpc from "../rpc/Rpc.ts" import * as RpcServer from "../rpc/RpcServer.ts" import type * as ClusterError from "./ClusterError.ts" import * as Message from "./Message.ts" @@ -30,6 +32,16 @@ import { ShardingConfig } from "./ShardingConfig.ts" const constVoid = constant(Effect.void) +const serializeDefectReply = ( + reply: Reply.ReplyWithContext, + defect: unknown +): Effect.Effect => + Effect.orDie(Reply.serialize(Reply.ReplyWithContext.fromDefect({ + id: reply.reply.id, + requestId: reply.reply.requestId, + defect + }))) + /** * Layer that handles runner protocol RPCs by forwarding requests to `Sharding` * and `MessageStorage`. @@ -63,7 +75,7 @@ export const layerHandlers = Runners.Rpcs.toLayer(Effect.gen(function*() { envelope: request, lastSentReply: Option.none(), respond(reply) { - resume(Effect.orDie(Reply.serialize(reply))) + resume(Reply.serializeOrDefect(reply)) return Effect.void } }) @@ -108,23 +120,37 @@ export const layerHandlers = Runners.Rpcs.toLayer(Effect.gen(function*() { }, Stream: ({ persisted, request }) => Effect.flatMap( - Queue.make(), + Queue.make(), (queue) => { const message = new Message.IncomingRequest({ envelope: request, lastSentReply: Option.none(), respond(reply) { - return Effect.flatMap(Reply.serialize(reply), (reply) => { - Queue.offerUnsafe(queue, reply) - return Effect.void - }) + return Reply.serialize(reply).pipe( + Effect.flatMap((reply) => { + Queue.offerUnsafe(queue, reply) + if (reply._tag === "WithExit") { + Queue.endUnsafe(queue) + } + return Effect.void + }), + Effect.catchTag("MalformedMessage", (error) => + Effect.flatMap(serializeDefectReply(reply, error), (reply) => { + // the fallback defect reply is terminal, so end the stream + Queue.offerUnsafe(queue, reply) + Queue.endUnsafe(queue) + return Effect.void + })) + ) } }) return Effect.as( persisted ? Effect.andThen( storage.registerReplyHandler(message).pipe( - Effect.onError((cause) => Queue.failCause(queue, cause)), + Effect.onError((cause) => + Queue.failCause(queue, cause) + ), Effect.forkScoped ), sharding.notify(message, constWaitUntilRead) @@ -168,7 +194,8 @@ export const layer: Layer.Layer< RpcServer.Protocol | Sharding.Sharding | MessageStorage.MessageStorage > = RpcServer.layer(Runners.Rpcs, { spanPrefix: "RunnerServer", - disableTracing: true + disableTracing: true, + disableFatalDefects: true }).pipe(Layer.provide(layerHandlers)) /** diff --git a/repos/effect/packages/effect/src/unstable/cluster/RunnerStorage.ts b/repos/effect/packages/effect/src/unstable/cluster/RunnerStorage.ts index 8f19777fed..e06689bfc7 100644 --- a/repos/effect/packages/effect/src/unstable/cluster/RunnerStorage.ts +++ b/repos/effect/packages/effect/src/unstable/cluster/RunnerStorage.ts @@ -24,7 +24,7 @@ import * as ShardId from "./ShardId.ts" * Represents a generic interface to the persistent storage required by the * cluster. * - * @category models + * @category services * @since 4.0.0 */ export class RunnerStorage extends Context.Service * * **Details** * - * Registered runners are treated as healthy and shard acquisition is kept only in - * process memory. + * Runner health and shard acquisition are kept only in process memory. * * @category constructors * @since 4.0.0 */ export const makeMemory = Effect.gen(function*() { - const runners = MutableHashMap.empty() + const runners = MutableHashMap.empty() let acquired: Array = [] let id = 0 return RunnerStorage.of({ - getRunners: Effect.sync(() => Array.from(MutableHashMap.values(runners), (runner) => [runner, true])), - register: (runner) => + getRunners: Effect.sync(() => Array.from(MutableHashMap.values(runners))), + register: (runner, healthy) => Effect.sync(() => { - MutableHashMap.set(runners, runner.address, runner) + MutableHashMap.set(runners, runner.address, [runner, healthy]) return MachineId.make(id++) }), unregister: (address) => Effect.sync(() => { MutableHashMap.remove(runners, address) }), - setRunnerHealth: () => Effect.void, + setRunnerHealth: (address, healthy) => + Effect.sync(() => { + MutableHashMap.modify(runners, address, ([runner]) => [runner, healthy] as const) + }), acquire: (_address, shardIds) => { acquired = Array.from(shardIds) - return Effect.succeed(Array.from(shardIds)) + return Effect.succeed(acquired) }, refresh: () => Effect.sync(() => acquired), release: () => Effect.void, diff --git a/repos/effect/packages/effect/src/unstable/cluster/Runners.ts b/repos/effect/packages/effect/src/unstable/cluster/Runners.ts index fea620c40d..5881cf0ab0 100644 --- a/repos/effect/packages/effect/src/unstable/cluster/Runners.ts +++ b/repos/effect/packages/effect/src/unstable/cluster/Runners.ts @@ -41,7 +41,7 @@ import * as Snowflake from "./Snowflake.ts" * sending and notifying messages, coordinating persisted replies, and marking * runners unavailable. * - * @category context + * @category services * @since 4.0.0 */ export class Runners extends Context.Service {} @@ -516,7 +516,7 @@ export interface RpcClient extends RpcClient_.FromGroup Effect.fail(new RunnerUnavailable({ address }))) + Effect.catchTag("RpcClientError", () => Effect.fail(new RunnerUnavailable({ address }))) ) } + // Persisted requests can recover their reply from storage via the + // `RunnerUnavailable` path, volatile requests receive the defect as their reply. + const respondDefect = (defect: unknown) => + isPersisted + ? Effect.fail(new RunnerUnavailable({ address })) + : message.respond( + new Reply.WithExit({ + id: snowflakeGen.nextUnsafe(), + requestId: message.envelope.requestId, + exit: Exit.die(defect) + }) + ) const isStream = RpcSchema.isStreamSchema(rpc.successSchema) if (!isStream) { return Effect.matchEffect(Message.serializeRequest(message), { @@ -590,7 +601,6 @@ export const makeRpc: Effect.Effect< persisted: isPersisted }) ), - Effect.catchTag("RpcClientError", Effect.die), Effect.flatMap((reply) => Schema.decodeEffect(Reply.Reply(message.rpc))(reply).pipe( Effect.provideContext(message.context), @@ -599,7 +609,8 @@ export const makeRpc: Effect.Effect< ), Effect.flatMap(message.respond), Effect.scoped, - Effect.catchDefect(() => Effect.fail(new RunnerUnavailable({ address }))) + Effect.catchTag("RpcClientError", () => Effect.fail(new RunnerUnavailable({ address }))), + Effect.catchDefect(respondDefect) ), onFailure: (error) => message.respond( @@ -626,10 +637,10 @@ export const makeRpc: Effect.Effect< Effect.flatMap((reply) => Effect.orDie(decode(reply))), Effect.flatMap(message.respond), Effect.forever, - Effect.catchTag("RpcClientError", Effect.die), Effect.provideContext(message.context), Effect.catchTag("Done", (_) => Effect.void), - Effect.catchDefect(() => Effect.fail(new RunnerUnavailable({ address }))) + Effect.catchTag("RpcClientError", () => Effect.fail(new RunnerUnavailable({ address }))), + Effect.catchDefect(respondDefect) ) }), Effect.scoped @@ -681,7 +692,7 @@ export const layerRpc: Layer.Layer< * Service that creates an RPC client protocol for communicating with a runner at a * given address. * - * @category client + * @category services * @since 4.0.0 */ export class RpcClientProtocol extends Context.Service< diff --git a/repos/effect/packages/effect/src/unstable/cluster/ShardId.ts b/repos/effect/packages/effect/src/unstable/cluster/ShardId.ts index c70b22e4e2..559968284c 100644 --- a/repos/effect/packages/effect/src/unstable/cluster/ShardId.ts +++ b/repos/effect/packages/effect/src/unstable/cluster/ShardId.ts @@ -49,7 +49,7 @@ export const ShardId = S.declare(isShardId, { S.link()( S.Struct({ group: S.String, - id: S.Number + id: S.Int }), { decode: SchemaGetter.transform(({ group, id }) => make(group, id)), diff --git a/repos/effect/packages/effect/src/unstable/cluster/Sharding.ts b/repos/effect/packages/effect/src/unstable/cluster/Sharding.ts index ed17272b31..e5a0891268 100644 --- a/repos/effect/packages/effect/src/unstable/cluster/Sharding.ts +++ b/repos/effect/packages/effect/src/unstable/cluster/Sharding.ts @@ -23,6 +23,7 @@ import * as Fiber from "../../Fiber.ts" import * as FiberMap from "../../FiberMap.ts" import { constant, flow } from "../../Function.ts" import * as HashRing from "../../HashRing.ts" +import * as InternalRecord from "../../internal/record.ts" import * as Latch from "../../Latch.ts" import * as Layer from "../../Layer.ts" import * as MutableHashMap from "../../MutableHashMap.ts" @@ -38,7 +39,7 @@ import * as Semaphore from "../../Semaphore.ts" import * as Stream from "../../Stream.ts" import type * as Rpc from "../rpc/Rpc.ts" import * as RpcClient from "../rpc/RpcClient.ts" -import { type FromServer, RequestId } from "../rpc/RpcMessage.ts" +import type { FromServer } from "../rpc/RpcMessage.ts" import type { MailboxFull, PersistenceError } from "./ClusterError.ts" import { AlreadyProcessingMessage, EntityNotAssignedToRunner } from "./ClusterError.ts" import * as ClusterMetrics from "./ClusterMetrics.ts" @@ -55,6 +56,7 @@ import { EntityReaper } from "./internal/entityReaper.ts" import { hashString } from "./internal/hash.ts" import { internalInterruptors } from "./internal/interruptors.ts" import { ResourceMap } from "./internal/resourceMap.ts" +import { effectiveInterval } from "./internal/shardLock.ts" import * as Message from "./Message.ts" import * as MessageStorage from "./MessageStorage.ts" import * as Reply from "./Reply.ts" @@ -122,7 +124,7 @@ export class Sharding extends Context.Service RpcClient.RpcClient.From< Rpcs, - MailboxFull | AlreadyProcessingMessage | PersistenceError + MailboxFull | AlreadyProcessingMessage | PersistenceError | EntityNotAssignedToRunner > > @@ -176,7 +178,7 @@ export class Sharding extends Context.Service Effect.Effect< void, - MailboxFull | AlreadyProcessingMessage | PersistenceError + MailboxFull | AlreadyProcessingMessage | PersistenceError | EntityNotAssignedToRunner > /** @@ -217,6 +219,7 @@ interface EntityManagerState { const make = Effect.gen(function*() { const config = yield* ShardingConfig + const shardLockInterval = effectiveInterval(config) const shardGroups = shardGroupConfig(config) const getRunnerAddress = () => Option.getOrUndefined(config.runnerAddress) const clock = yield* Clock @@ -237,9 +240,13 @@ const make = Effect.gen(function*() { const runnerStorage = yield* RunnerStorage const entityManagers = new Map() + let entityRegistrationStartMillis: number | undefined + let entityRegistrationFallbackStartMillis: number | undefined const shardAssignments = MutableHashMap.empty() const selfShards = MutableHashSet.empty() + // open while shard lock storage is healthy + const shardLocksHealthyLatch = Latch.makeUnsafe(true) // the active shards are the ones that we have acquired the lock for const acquiredShards = MutableHashSet.empty() @@ -277,6 +284,9 @@ const make = Effect.gen(function*() { // allow them to move to another runner. const releasingShards = MutableHashSet.empty() + // Shards whose entities must be force interrupted and locks fully released + // before normal reacquisition. + const forceReleasingShards = MutableHashSet.empty() const initialRunnerAddress = getRunnerAddress() if (initialRunnerAddress) { const selfAddress = initialRunnerAddress @@ -286,37 +296,88 @@ const make = Effect.gen(function*() { }) const releaseShardsMap = yield* FiberMap.make() - const releaseShard = Effect.fnUntraced( - function*(shardId: ShardId) { - const fibers = Arr.empty>() + let forcedShardReleaseRunning = false + // Interrupt the shards' entities, wait for lock health, run the storage + // release, then clear the shards' bookkeeping. + const runShardRelease = Effect.fnUntraced(function*( + shardIds: ReadonlyArray, + force: boolean, + release: Effect.Effect + ) { + const fibers = Arr.empty>() + for (const shardId of shardIds) { for (const state of entityManagers.values()) { if (state.status === "closed") continue - fibers.push(yield* Effect.forkScoped(state.manager.interruptShard(shardId))) + fibers.push(yield* Effect.forkScoped(state.manager.interruptShard(shardId, { force }))) } - yield* Fiber.joinAll(fibers) - yield* runnerStorage.release(selfAddress, shardId) + } + yield* Fiber.joinAll(fibers) + yield* shardLocksHealthyLatch.await + yield* release + for (const shardId of shardIds) { MutableHashSet.remove(releasingShards, shardId) + MutableHashSet.remove(forceReleasingShards, shardId) yield* storage.unregisterShardReplyHandlers(shardId) - }, - Effect.sandbox, - (effect, shardId) => + } + }) + const retryShardRelease = + (annotations: { readonly fiber: string; readonly shardId?: ShardId }) => + (effect: Effect.Effect) => effect.pipe( + Effect.sandbox, Effect.tapError((cause) => - Effect.logDebug(`Could not release shard, retrying`, cause).pipe( + Effect.logDebug(`Could not release shards, retrying`, cause).pipe( Effect.annotateLogs({ module: "effect/cluster/Sharding", - fiber: "releaseShard", runner: selfAddress, - shardId - }) + ...annotations + }), + // Effect.eventually retries immediately, so space failures to + // avoid hot-looping while storage is unavailable. + Effect.andThen(Effect.sleep(50)) ) ), - Effect.eventually, + Effect.eventually + ) + const releaseShard = Effect.fnUntraced( + function*(shardId: ShardId) { + yield* runShardRelease( + [shardId], + MutableHashSet.has(forceReleasingShards, shardId), + runnerStorage.release(selfAddress, shardId) + ) + }, + (effect, shardId) => + effect.pipe( + retryShardRelease({ fiber: "releaseShard", shardId }), FiberMap.run(releaseShardsMap, shardId, { onlyIfMissing: true }) ) ) + // The forced release ends with `runnerStorage.releaseAll`, which drops + // every lock held by this runner. Shards must not be reacquired through the + // normal path while it is pending, otherwise the bulk release wipes a lock + // the runner already considers acquired. + const forcedShardReleasePending = () => forcedShardReleaseRunning || MutableHashSet.size(forceReleasingShards) > 0 + const releaseForcedShards = Effect.suspend(() => { + if (forcedShardReleaseRunning || MutableHashSet.size(forceReleasingShards) === 0) { + return Effect.void + } + forcedShardReleaseRunning = true + const shardIds = [...forceReleasingShards] + return runShardRelease(shardIds, true, runnerStorage.releaseAll(selfAddress)).pipe( + retryShardRelease({ fiber: "releaseForcedShards" }), + Effect.ensuring(Effect.sync(() => { + forcedShardReleaseRunning = false + activeShardsLatch.openUnsafe() + })), + Effect.forkIn(shardingScope), + Effect.asVoid + ) + }) const releaseShards = Effect.gen(function*() { + yield* releaseForcedShards for (const shardId of releasingShards) { + if (MutableHashSet.has(forceReleasingShards, shardId)) continue if (FiberMap.hasUnsafe(releaseShardsMap, shardId)) continue yield* releaseShard(shardId) } @@ -336,11 +397,21 @@ const make = Effect.gen(function*() { MutableHashSet.add(releasingShards, shardId) } - if (MutableHashSet.size(releasingShards) > 0) { + if (MutableHashSet.size(releasingShards) > 0 || MutableHashSet.size(forceReleasingShards) > 0) { yield* Effect.forkIn(syncSingletons, shardingScope) yield* releaseShards } + if (!shardLocksHealthyLatch.isOpen()) { + continue + } + + // Wait for the pending bulk release before reacquiring, so it cannot + // drop a lock acquired here. `releaseForcedShards` reopens the latch. + if (forcedShardReleasePending()) { + continue + } + // if a shard has been assigned to this runner, we acquire it const unacquiredShards = MutableHashSet.empty() for (const shardId of selfShards) { @@ -353,7 +424,7 @@ const make = Effect.gen(function*() { } const oacquired = yield* runnerStorage.acquire(selfAddress, unacquiredShards).pipe( - Effect.timeoutOption(config.shardLockRefreshInterval) + Effect.timeoutOption(shardLockInterval) ) if (Option.isNone(oacquired)) { activeShardsLatch.openUnsafe() @@ -363,10 +434,19 @@ const make = Effect.gen(function*() { const acquired = oacquired.value yield* storage.resetShards(acquired).pipe( Effect.ignore, - Effect.timeoutOption(config.shardLockRefreshInterval) + Effect.timeoutOption(shardLockInterval) ) + // A forced release can start while `acquire` is in flight, so re-check + // it here as well as before acquiring. + const forcedReleasePending = forcedShardReleasePending() for (const shardId of acquired) { - if (MutableHashSet.has(releasingShards, shardId) || !MutableHashSet.has(selfShards, shardId)) { + if ( + !shardLocksHealthyLatch.isOpen() || + forcedReleasePending || + MutableHashSet.has(releasingShards, shardId) || + !MutableHashSet.has(selfShards, shardId) + ) { + MutableHashSet.add(releasingShards, shardId) continue } MutableHashSet.add(acquiredShards, shardId) @@ -392,8 +472,52 @@ const make = Effect.gen(function*() { Effect.forkIn(shardingScope) ) - // refresh the shard locks every `shardLockRefreshInterval` - yield* Effect.suspend(() => + const markShardLocksUnhealthy = (cause: Cause.Cause) => + Effect.suspend(() => { + if (!shardLocksHealthyLatch.closeUnsafe()) return Effect.void + + const affectedShards = MutableHashSet.fromIterable([...acquiredShards, ...releasingShards]) + MutableHashSet.clear(selfShards) + MutableHashSet.clear(acquiredShards) + for (const shardId of affectedShards) { + MutableHashSet.add(releasingShards, shardId) + MutableHashSet.add(forceReleasingShards, shardId) + } + ClusterMetrics.shards.updateUnsafe(BigInt(0), Context.empty()) + activeShardsLatch.openUnsafe() + + return Effect.gen(function*() { + yield* Effect.logError("Shard lock storage is unhealthy", cause) + yield* Effect.forkIn(syncSingletons, shardingScope, { startImmediately: true }) + + for (const shardId of affectedShards) { + for (const state of entityManagers.values()) { + if (state.status === "closed") continue + yield* Effect.forkIn( + state.manager.interruptShard(shardId, { force: true }), + shardingScope, + { startImmediately: true } + ) + } + } + activeShardsLatch.openUnsafe() + }) + }) + + const markShardLocksHealthy = Effect.suspend(() => { + if (!shardLocksHealthyLatch.openUnsafe()) return Effect.void + + MutableHashSet.clear(selfShards) + MutableHashMap.forEach(shardAssignments, (runner, shardId) => { + if (isLocalRunner(runner)) { + MutableHashSet.add(selfShards, shardId) + } + }) + activeShardsLatch.openUnsafe() + return Effect.logInfo("Shard lock storage has recovered") + }) + + const refreshShardLocks = Effect.suspend(() => runnerStorage.refresh(selfAddress, [ ...acquiredShards, ...releasingShards @@ -421,12 +545,20 @@ const make = Effect.gen(function*() { times: 5, schedule: Schedule.spaced(50) }), - Effect.catchCause((cause) => - Effect.logError("Could not refresh shard locks", cause).pipe( - Effect.andThen(clearSelfShards) - ) - ), - Effect.repeat(Schedule.fixed(config.shardLockRefreshInterval)), + Effect.timeout(shardLockInterval), + Effect.catchCause(markShardLocksUnhealthy) + ) + + const probeShardLocks = runnerStorage.refresh(selfAddress, []).pipe( + Effect.timeout(shardLockInterval), + Effect.andThen(markShardLocksHealthy), + Effect.catchCause(() => Effect.void) + ) + + // Refresh shard locks at the lease-safe interval, or probe storage while + // lock ownership is uncertain. + yield* Effect.suspend(() => shardLocksHealthyLatch.isOpen() ? refreshShardLocks : probeShardLocks).pipe( + Effect.repeat(Schedule.fixed(shardLockInterval)), Effect.forever, Effect.forkIn(shardingScope) ) @@ -439,11 +571,6 @@ const make = Effect.gen(function*() { ) } - const clearSelfShards = Effect.sync(() => { - MutableHashSet.clear(selfShards) - activeShardsLatch.openUnsafe() - }) - // --- Storage inbox --- // // Responsible for reading unprocessed messages from storage and sending them @@ -466,7 +593,6 @@ const make = Effect.gen(function*() { const entityRegistrationTimeoutMillis = Duration.toMillis( Duration.fromInputUnsafe(config.entityRegistrationTimeout) ) - const storageStartMillis = clock.currentTimeMillisUnsafe() yield* Effect.gen(function*() { yield* Effect.logDebug("Starting") @@ -492,8 +618,15 @@ const make = Effect.gen(function*() { } const state = entityManagers.get(address.entityType) if (!state) { - const sinceStart = clock.currentTimeMillisUnsafe() - storageStartMillis - if (sinceStart < entityRegistrationTimeoutMillis) { + const now = clock.currentTimeMillisUnsafe() + const registrationStarted = entityRegistrationStartMillis !== undefined + const timeoutStartMillis = entityRegistrationStartMillis ?? + (entityRegistrationFallbackStartMillis ??= now) + // If registration never starts, allow two intervals from the first missing read before failing. + const timeoutMillis = registrationStarted + ? entityRegistrationTimeoutMillis + : entityRegistrationTimeoutMillis * 2 + if (now - timeoutStartMillis < timeoutMillis) { // reset address in the case that the entity is slow to register MutableHashSet.add(resetAddresses, address) return Effect.void @@ -646,7 +779,7 @@ const make = Effect.gen(function*() { const resumptionState = Option.getOrThrow(MutableHashMap.get(entityResumptionState, address)) let done = false - while (!done) { + while (!done) { // oxlint-disable-line no-unmodified-loop-condition // if the shard is no longer assigned to this runner, we stop if (!MutableHashSet.has(acquiredShards, address.shardId)) { return @@ -834,18 +967,40 @@ const make = Effect.gen(function*() { retries?: number ): Effect.Effect< void, - MailboxFull | AlreadyProcessingMessage | PersistenceError + MailboxFull | AlreadyProcessingMessage | PersistenceError | EntityNotAssignedToRunner > { + const isPersisted = Context.get( + message._tag === "OutgoingRequest" ? message.annotations : message.rpc.annotations, + Persisted + ) + const shouldFail = !discard && + (message._tag === "OutgoingRequest" || message.envelope._tag === "AckChunk") + const abandon = (error: EntityNotAssignedToRunner) => { + if (!isPersisted) { + return shouldFail + ? Effect.fail(error) + : Effect.logDebug("Abandoning outgoing message during shutdown", message.envelope.address) + } + const persist = message._tag === "OutgoingRequest" + ? storage.saveRequest(message) + : storage.saveEnvelope(message) + return Effect.catchTag(persist, "MalformedMessage", Effect.die).pipe( + Effect.andThen( + shouldFail + ? Effect.fail(error) + : Effect.logWarning("Persisting outgoing message abandoned during shutdown", message.envelope.address) + ) + ) + } return Effect.catchFilter( Effect.suspend(() => { const address = message.envelope.address - const isPersisted = Context.get( - message._tag === "OutgoingRequest" ? message.annotations : message.rpc.annotations, - Persisted - ) if (isPersisted && !storageEnabled) { return Effect.die("Sharding.sendOutgoing: Persisted messages require MessageStorage") } + if (shouldFail && MutableRef.get(isShutdown)) { + return Effect.fail(new EntityNotAssignedToRunner({ address })) + } const maybeRunner = MutableHashMap.get(shardAssignments, address.shardId) const runnerIsLocal = Option.isSome(maybeRunner) && isLocalRunner(maybeRunner.value) if (isPersisted) { @@ -864,6 +1019,15 @@ const make = Effect.gen(function*() { ? Result.succeed(error) : Result.fail(error), (error) => { + // Abandon the message during teardown: retrying would loop forever once the runner is shutting down + if (error._tag === "EntityNotAssignedToRunner") { + const targetManager = entityManagers.get(message.envelope.address.entityType) + const cannotRecover = MutableRef.get(isShutdown) || + (targetManager !== undefined && targetManager.status !== "alive") + if (cannotRecover) { + return abandon(error) + } + } if (retries === 0) { return Effect.die(error) } @@ -977,7 +1141,7 @@ const make = Effect.gen(function*() { if (newAssignments) { const runner = newAssignments[i] MutableHashMap.set(shardAssignments, shard, runner) - if (isLocalRunner(runner)) { + if (shardLocksHealthyLatch.isOpen() && isLocalRunner(runner)) { MutableHashSet.add(selfShards, shard) } } else { @@ -1031,156 +1195,161 @@ const make = Effect.gen(function*() { Entity, (entityId: string) => RpcClient.RpcClient< any, - MailboxFull | AlreadyProcessingMessage + MailboxFull | AlreadyProcessingMessage | EntityNotAssignedToRunner >, never - > = yield* ResourceMap.make(Effect.fnUntraced(function*(entity: Entity) { - const client = yield* RpcClient.makeNoSerialization(entity.protocol, { - spanPrefix: `${entity.type}.client`, - disableTracing: !Context.get(entity.protocol.annotations, ClusterSchema.ClientTracingEnabled), - supportsAck: true, - generateRequestId: () => RequestId(snowflakeGen.nextUnsafe()), - flatten: true, - onFromClient(options): Effect.Effect< - void, - MailboxFull | AlreadyProcessingMessage | PersistenceError - > { - const address = Context.getUnsafe(options.context, ClientAddressTag) - switch (options.message._tag) { - case "Request": { - const fiber = Fiber.getCurrent()! - const id = Snowflake.Snowflake(options.message.id) - const rpc = entity.protocol.requests.get(options.message.tag)! - let respond: (reply: Reply.Reply) => Effect.Effect - const envelope = Envelope.makeRequest({ - requestId: id, - address, - tag: options.message.tag, - payload: options.message.payload, - headers: options.message.headers, - traceId: options.message.traceId, - spanId: options.message.spanId, - sampled: options.message.sampled - }) - const message = new Message.OutgoingRequest({ - envelope, - lastReceivedReply: Option.none(), - rpc, - context: fiber.context as Context.Context, - respond: (reply) => respond(reply), - annotations: Context.get(rpc.annotations, ClusterSchema.Dynamic)( - rpc.annotations, - envelope as any - ) - }) - if (!options.discard) { - const entry: ClientRequestEntry = { - rpc: rpc as any, - context: fiber.currentContext, - message + > = yield* ResourceMap.make( + Effect.fnUntraced(function*(entity: Entity) { + const client = yield* RpcClient.makeNoSerialization(entity.protocol, { + spanPrefix: `${entity.type}.client`, + disableTracing: !Context.get(entity.protocol.annotations, ClusterSchema.ClientTracingEnabled), + supportsAck: true, + generateRequestId: () => snowflakeGen.nextUnsafe() as any, + flatten: true, + onFromClient(options): Effect.Effect< + void, + MailboxFull | AlreadyProcessingMessage | PersistenceError | EntityNotAssignedToRunner + > { + const address = Context.getUnsafe(options.context, ClientAddressTag) + switch (options.message._tag) { + case "Request": { + const fiber = Fiber.getCurrent()! + const id = Snowflake.Snowflake(options.message.id) + const rpc = entity.protocol.requests.get(options.message.tag)! + let respond: (reply: Reply.Reply) => Effect.Effect + const envelope = Envelope.makeRequest({ + requestId: id, + address, + tag: options.message.tag, + payload: options.message.payload, + headers: options.message.headers, + traceId: options.message.traceId, + spanId: options.message.spanId, + sampled: options.message.sampled + }) + const message = new Message.OutgoingRequest({ + envelope, + lastReceivedReply: Option.none(), + rpc, + context: fiber.context as Context.Context, + respond: (reply) => respond(reply), + annotations: Context.get(rpc.annotations, ClusterSchema.Dynamic)( + rpc.annotations, + envelope as any + ) + }) + if (!options.discard) { + const entry: ClientRequestEntry = { + rpc: rpc as any, + context: fiber.context, + message + } + clientRequests.set(id, entry) + respond = makeClientRespond(entry, client.write) + } else { + respond = clientRespondDiscard } - clientRequests.set(id, entry) - respond = makeClientRespond(entry, client.write) - } else { - respond = clientRespondDiscard + return sendOutgoing(message, options.discard) } - return sendOutgoing(message, options.discard) - } - case "Ack": { - const requestId = Snowflake.Snowflake(options.message.requestId) - const entry = clientRequests.get(requestId) - if (!entry) return Effect.void - return sendOutgoing( - new Message.OutgoingEnvelope({ - envelope: new Envelope.AckChunk({ - id: snowflakeGen.nextUnsafe(), - address, - requestId, - replyId: entry.lastChunkId! + case "Ack": { + const requestId = Snowflake.Snowflake(options.message.requestId) + const entry = clientRequests.get(requestId) + if (!entry) return Effect.void + return sendOutgoing( + new Message.OutgoingEnvelope({ + envelope: new Envelope.AckChunk({ + id: snowflakeGen.nextUnsafe(), + address, + requestId, + replyId: entry.lastChunkId! + }), + rpc: entry.rpc }), - rpc: entry.rpc - }), - false - ) - } - case "Interrupt": { - const requestId = Snowflake.Snowflake(options.message.requestId) - const entry = clientRequests.get(requestId)! - if (!entry) return Effect.void - clientRequests.delete(requestId) - if (ClusterSchema.isUninterruptibleForClient(entry.message.annotations)) { - return Effect.void - } - // for durable messages, we ignore interrupts on shutdown or as a - // result of a shard being resassigned - const isTransientInterrupt = MutableRef.get(isShutdown) || - options.message.interruptors.some((id) => internalInterruptors.has(id)) - if (isTransientInterrupt && Context.get(entry.message.annotations, Persisted)) { - return Effect.void + false + ) } - return Effect.ignore(sendOutgoing( - new Message.OutgoingEnvelope({ - envelope: new Envelope.Interrupt({ - id: snowflakeGen.nextUnsafe(), - address, - requestId + case "Interrupt": { + const requestId = Snowflake.Snowflake(options.message.requestId) + const entry = clientRequests.get(requestId)! + if (!entry) return Effect.void + clientRequests.delete(requestId) + if (ClusterSchema.isUninterruptibleForClient(entry.message.annotations)) { + return Effect.void + } + // for durable messages, we ignore interrupts on shutdown or as a + // result of a shard being resassigned + const isTransientInterrupt = MutableRef.get(isShutdown) || + options.message.interruptors.some((id) => internalInterruptors.has(id)) + if (isTransientInterrupt && Context.get(entry.message.annotations, Persisted)) { + return Effect.void + } + return Effect.ignore(sendOutgoing( + new Message.OutgoingEnvelope({ + envelope: new Envelope.Interrupt({ + id: snowflakeGen.nextUnsafe(), + address, + requestId + }), + rpc: entry.rpc }), - rpc: entry.rpc - }), - false, - 3 - )) + false, + 3 + )) + } } + return Effect.void } - return Effect.void - } - }) - - yield* Scope.addFinalizer( - yield* Effect.scope, - Effect.withFiber((fiber) => { - internalInterruptors.add(fiber.id) - return Effect.void }) - ) - return (entityId: string) => { - const id = makeEntityId(entityId) - const address = ClientAddressTag.context(makeEntityAddress({ - shardId: getShardId(id, entity.getShardGroup(entityId as EntityId)), - entityId: id, - entityType: entity.type - })) - const clientFn = function(tag: string, payload: any, options?: { - readonly context?: Context.Context - }) { - const context = options?.context ? Context.merge(options.context, address) : address - return client.client(tag, payload, { - ...options, - context + yield* Scope.addFinalizer( + yield* Effect.scope, + Effect.withFiber((fiber) => { + internalInterruptors.add(fiber.id) + return Effect.void }) - } - const proxyClient: any = {} - return new Proxy(proxyClient, { - has(_, p) { - return entity.protocol.requests.has(p as string) - }, - get(target, p) { - if (p in target) { - return target[p] - } else if (!entity.protocol.requests.has(p as string)) { - return undefined - } - return target[p] = (payload: any, options?: {}) => clientFn(p as string, payload, options) + ) + + return (entityId: string) => { + const id = makeEntityId(entityId) + const address = ClientAddressTag.context(makeEntityAddress({ + shardId: getShardId(id, entity.getShardGroup(entityId as EntityId)), + entityId: id, + entityType: entity.type + })) + const clientFn = function(tag: string, payload: any, options?: { + readonly context?: Context.Context + }) { + const context = options?.context ? Context.merge(options.context, address) : address + return client.client(tag, payload, { + ...options, + context + }) } - }) - } - })) + const proxyClient: any = {} + return new Proxy(proxyClient, { + has(_, p) { + return entity.protocol.requests.has(p as string) + }, + get(target, p) { + if (Object.hasOwn(target, p)) { + return target[p] + } else if (!entity.protocol.requests.has(p as string)) { + return undefined + } + const method = (payload: any, options?: {}) => clientFn(p as string, payload, options) + InternalRecord.assignProperty(target, p, method) + return method + } + }) + } + }), + { referential: true } + ) const makeClient = (entity: Entity): Effect.Effect< ( entityId: string - ) => RpcClient.RpcClient.From + ) => RpcClient.RpcClient.From > => clients.get(entity) as any const clientRespondDiscard = (_reply: Reply.Reply) => Effect.void @@ -1196,7 +1365,7 @@ const make = Effect.gen(function*() { return write({ _tag: "Chunk", clientId: 0, - requestId: RequestId(reply.requestId), + requestId: reply.requestId as any, values: reply.values }) } @@ -1205,7 +1374,7 @@ const make = Effect.gen(function*() { return write({ _tag: "Exit", clientId: 0, - requestId: RequestId(reply.requestId), + requestId: reply.requestId as any, exit: reply.exit }) } @@ -1306,12 +1475,11 @@ const make = Effect.gen(function*() { runnerAddress, sharding }).pipe( - Effect.provideContext(Context.mutate(services, (services) => - services.pipe( - Context.add(EntityReaper, reaper), - Context.add(Scope.Scope, scope), - Context.add(Snowflake.Generator, snowflakeGen) - ))) + Effect.provideContext(services.pipe( + Context.add(EntityReaper, reaper), + Context.add(Scope.Scope, scope), + Context.add(Snowflake.Generator, snowflakeGen) + )) ) as Effect.Effect const state: EntityManagerState = { entity, @@ -1332,6 +1500,7 @@ const make = Effect.gen(function*() { // register entities while storage is idle // this ensures message order is preserved yield* withStorageReadLock(Effect.sync(() => { + entityRegistrationStartMillis ??= clock.currentTimeMillisUnsafe() entityManagers.set(entity.type, state) if (entityManagerLatches.has(entity.type)) { entityManagerLatches.get(entity.type)!.openUnsafe() diff --git a/repos/effect/packages/effect/src/unstable/cluster/ShardingConfig.ts b/repos/effect/packages/effect/src/unstable/cluster/ShardingConfig.ts index 5b5c18ae11..e9a465d637 100644 --- a/repos/effect/packages/effect/src/unstable/cluster/ShardingConfig.ts +++ b/repos/effect/packages/effect/src/unstable/cluster/ShardingConfig.ts @@ -23,7 +23,7 @@ import { RunnerAddress } from "./RunnerAddress.ts" /** * Represents the configuration for the `Sharding` service on a given runner. * - * @category models + * @category services * @since 4.0.0 */ export class ShardingConfig extends Context.Service): Layer.Layer /** * Layer that provides the default `ShardingConfig` values. * - * @category defaults + * @category layers * @since 4.0.0 */ export const layerDefaults: Layer.Layer = layer() @@ -349,7 +353,7 @@ export const layerFromEnv = (options?: Partial | unde * Normalizes the provided `ShardingConfig` to calculate the `available` and * `assigned` shard groups. * - * @category Shard groups + * @category converting * @since 4.0.0 */ export const shardGroupConfig = (config: ShardingConfig["Service"]): { diff --git a/repos/effect/packages/effect/src/unstable/cluster/SingleRunner.ts b/repos/effect/packages/effect/src/unstable/cluster/SingleRunner.ts index 3b150c9a73..3f3cdb9069 100644 --- a/repos/effect/packages/effect/src/unstable/cluster/SingleRunner.ts +++ b/repos/effect/packages/effect/src/unstable/cluster/SingleRunner.ts @@ -12,6 +12,7 @@ */ import * as Layer from "effect/Layer" import type { ConfigError } from "../../Config.ts" +import type * as Crypto from "../../Crypto.ts" import type * as SqlClient from "../sql/SqlClient.ts" import type * as MessageStorage from "./MessageStorage.ts" import * as RunnerHealth from "./RunnerHealth.ts" @@ -42,7 +43,8 @@ import * as SqlRunnerStorage from "./SqlRunnerStorage.ts" * **Gotchas** * * - Even when `runnerStorage` is `"memory"`, message storage remains - * SQL-backed, so callers must still provide `SqlClient`. + * SQL-backed, so callers must still provide `SqlClient` and `Crypto.Crypto` + * (used to hash over-length message deduplication keys). * - Runner communication and runner health are no-op services, so this layer is * for single-process use rather than multi-runner coordination. * @@ -62,7 +64,7 @@ export const layer = (options?: { | Runners.Runners | MessageStorage.MessageStorage, ConfigError, - SqlClient.SqlClient + SqlClient.SqlClient | Crypto.Crypto > => Sharding.layer.pipe( Layer.provideMerge(Runners.layerNoop), diff --git a/repos/effect/packages/effect/src/unstable/cluster/SingletonAddress.ts b/repos/effect/packages/effect/src/unstable/cluster/SingletonAddress.ts index 5479ad742c..2261e6e750 100644 --- a/repos/effect/packages/effect/src/unstable/cluster/SingletonAddress.ts +++ b/repos/effect/packages/effect/src/unstable/cluster/SingletonAddress.ts @@ -17,7 +17,7 @@ const TypeId = "~effect/cluster/SingletonAddress" /** * Represents the unique address of an singleton within the cluster. * - * @category address + * @category schemas * @since 4.0.0 */ export class SingletonAddress extends Schema.Class(TypeId)({ diff --git a/repos/effect/packages/effect/src/unstable/cluster/Snowflake.ts b/repos/effect/packages/effect/src/unstable/cluster/Snowflake.ts index 8f0ac44a0e..8adf7576e4 100644 --- a/repos/effect/packages/effect/src/unstable/cluster/Snowflake.ts +++ b/repos/effect/packages/effect/src/unstable/cluster/Snowflake.ts @@ -53,8 +53,8 @@ export type Snowflake = Brand.Branded * @category constructors * @since 4.0.0 */ -export const Snowflake = (input: string | bigint): Snowflake => - typeof input === "string" ? BigInt(input) as Snowflake : input as Snowflake +export const Snowflake = (input: string | number | bigint): Snowflake => + typeof input === "bigint" ? input as Snowflake : BigInt(input) as Snowflake /** * Namespace containing support types for snowflake parts and generators. @@ -170,7 +170,7 @@ export const make = (options: { /** * Extracts the Unix timestamp in milliseconds from a snowflake id. * - * @category parts + * @category getters * @since 4.0.0 */ export const timestamp = (snowflake: Snowflake): number => Number(snowflake >> constBigInt22) + sinceUnixEpoch @@ -178,7 +178,7 @@ export const timestamp = (snowflake: Snowflake): number => Number(snowflake >> c /** * Extracts the timestamp from a snowflake id as a `DateTime.Utc`. * - * @category parts + * @category getters * @since 4.0.0 */ export const dateTime = (snowflake: Snowflake): DateTime.Utc => DateTime.makeUnsafe(timestamp(snowflake)) @@ -186,7 +186,7 @@ export const dateTime = (snowflake: Snowflake): DateTime.Utc => DateTime.makeUns /** * Extracts the machine id component from a snowflake id. * - * @category parts + * @category getters * @since 4.0.0 */ export const machineId = (snowflake: Snowflake): MachineId => @@ -195,7 +195,7 @@ export const machineId = (snowflake: Snowflake): MachineId => /** * Extracts the per-machine sequence component from a snowflake id. * - * @category parts + * @category getters * @since 4.0.0 */ export const sequence = (snowflake: Snowflake): number => Number(snowflake % constBigInt4096) @@ -203,7 +203,7 @@ export const sequence = (snowflake: Snowflake): number => Number(snowflake % con /** * Decomposes a snowflake id into its timestamp, machine id, and sequence parts. * - * @category parts + * @category converting * @since 4.0.0 */ export const toParts = (snowflake: Snowflake): Snowflake.Parts => ({ @@ -221,7 +221,7 @@ export const toParts = (snowflake: Snowflake): Snowflake.Parts => ({ * backward, resets the sequence each millisecond, and advances the timestamp when * more than 4096 ids are requested in the same millisecond. * - * @category Generator + * @category constructors * @since 4.0.0 */ export const makeGenerator: Effect.Effect = Effect.gen(function*() { @@ -265,7 +265,7 @@ export const makeGenerator: Effect.Effect = Effect.gen(func /** * Context service for a stateful snowflake id generator. * - * @category Generator + * @category services * @since 4.0.0 */ export class Generator extends Context.Service< @@ -276,7 +276,7 @@ export class Generator extends Context.Service< /** * Layer that provides the default snowflake `Generator` service. * - * @category Generator + * @category layers * @since 4.0.0 */ export const layerGenerator: Layer.Layer = Layer.effect(Generator)(makeGenerator) diff --git a/repos/effect/packages/effect/src/unstable/cluster/SqlMessageStorage.ts b/repos/effect/packages/effect/src/unstable/cluster/SqlMessageStorage.ts index 63c1c38c48..e8c2ed4c0d 100644 --- a/repos/effect/packages/effect/src/unstable/cluster/SqlMessageStorage.ts +++ b/repos/effect/packages/effect/src/unstable/cluster/SqlMessageStorage.ts @@ -8,13 +8,21 @@ * storage constructor, layers, migrations, optional table prefixes, and the row * mapping needed by encoded message storage. * + * Request deduplication keys that exceed the 255-character `message_id` + * column are hashed with the `Crypto` service before they are written, so + * composed keys of any length are supported; shorter keys are stored as + * plaintext, byte-compatible with rows written by previous versions. + * * @since 4.0.0 */ // eslint-disable effect/no-bigint-literals import * as Arr from "../../Array.ts" +import * as Crypto from "../../Crypto.ts" import * as Effect from "../../Effect.ts" +import * as Encoding from "../../Encoding.ts" import * as Layer from "../../Layer.ts" import * as Option from "../../Option.ts" +import type * as PlatformError from "../../PlatformError.ts" import * as Schedule from "../../Schedule.ts" import * as Migrator from "../sql/Migrator.ts" import * as SqlClient from "../sql/SqlClient.ts" @@ -61,9 +69,10 @@ export const make: (options?: { }) => Effect.Effect< MessageStorage.MessageStorage["Service"], never, - SqlClient.SqlClient | Snowflake.Generator + SqlClient.SqlClient | Snowflake.Generator | Crypto.Crypto > = Effect.fnUntraced(function*(options) { const sql = (yield* SqlClient.SqlClient).withoutTransforms() + const crypto = yield* Crypto.Crypto const prefix = options?.prefix ?? "cluster" const table = (name: string) => `${prefix}_${name}` @@ -84,6 +93,30 @@ export const make: (options?: { const repliesTable = table("replies") const repliesTableSql = sql(repliesTable) + // The composed primary key (`entityType/entityId/tag/id`) can legally exceed + // the 255-character `message_id` column: entity_type(150) + entity_id(255) + + // tag(50) alone total 458 characters before the RPC primary key is appended. + // Keys that fit are stored as-is, keeping `message_id` byte-compatible with + // rows written by previous versions; longer keys are stored as a SHA-256 + // digest (64 hex characters, collision probability negligible at a 2^128 + // birthday bound). Digests never contain "/" while composed keys always do, + // so the two encodings cannot collide. + const encoder = new TextEncoder() + const messageIdForPrimaryKey = (primaryKey: string): Effect.Effect => + primaryKey.length <= 255 + ? Effect.succeed(primaryKey) + : Effect.map(crypto.digest("SHA-256", encoder.encode(primaryKey)), Encoding.encodeHex) + + const messageIdEnforcesWidth = sql.onDialectOrElse({ + mssql: () => true, + mysql: () => true, + pg: () => true, + orElse: () => false + }) + // sqlite's TEXT message_id column stored over-length plaintext keys before + // digests were introduced; those legacy rows need a plaintext fallback read + const mayHaveLegacyRow = (primaryKey: string): boolean => !messageIdEnforcesWidth && primaryKey.length > 255 + const envelopeToRow = ( envelope: Envelope.Encoded, message_id: string | null, @@ -238,6 +271,14 @@ export const make: (options?: { const sqlFalse = sql.literal(supportsBooleans ? "FALSE" : "0") const sqlTrue = sql.literal(supportsBooleans ? "TRUE" : "1") + const selectByMessageId = (message_id: string): Effect.Effect, SqlError> => + sql` + SELECT m.id, r.id as reply_id, r.kind as reply_kind, r.payload as reply_payload, r.sequence as reply_sequence + FROM ${messagesTableSql} m + LEFT JOIN ${repliesTableSql} r ON r.id = m.last_reply_id + WHERE m.message_id = ${message_id} + ` + const insertEnvelope: ( row: MessageRow, message_id: string @@ -250,12 +291,7 @@ export const make: (options?: { `.pipe(Effect.flatMap((rows) => { // inserted a new row if (rows.length > 0) return Effect.succeed([]) - return sql` - SELECT m.id, r.id as reply_id, r.kind as reply_kind, r.payload as reply_payload, r.sequence as reply_sequence - FROM ${messagesTableSql} m - LEFT JOIN ${repliesTableSql} r ON r.id = m.last_reply_id - WHERE m.message_id = ${message_id} - ` + return selectByMessageId(message_id) })), mysql: () => (row, message_id) => Effect.flatMap( @@ -264,12 +300,7 @@ export const make: (options?: { if (row.affectedRows > 0) { return Effect.succeed([]) } - return sql` - SELECT m.id, r.id as reply_id, r.kind as reply_kind, r.payload as reply_payload, r.sequence as reply_sequence - FROM ${messagesTableSql} m - LEFT JOIN ${repliesTableSql} r ON r.id = m.last_reply_id - WHERE m.message_id = ${message_id} - ` + return selectByMessageId(message_id) } ), mssql: () => (row, message_id) => @@ -311,12 +342,7 @@ export const make: (options?: { END as reply_sequence; `, orElse: () => (row, message_id) => - sql` - SELECT m.id, r.id as reply_id, r.kind as reply_kind, r.payload as reply_payload, r.sequence as reply_sequence - FROM ${messagesTableSql} m - LEFT JOIN ${repliesTableSql} r ON r.id = m.last_reply_id - WHERE m.message_id = ${message_id} - `.pipe( + selectByMessageId(message_id).pipe( Effect.tap(sql`INSERT OR IGNORE INTO ${messagesTableSql} ${sql.insert(row)}`), sql.withTransaction, Effect.retry({ times: 3 }) @@ -406,18 +432,30 @@ export const make: (options?: { return yield* MessageStorage.makeEncoded({ saveEnvelope: ({ deliverAt, envelope, primaryKey }) => Effect.suspend(() => { - const row = envelopeToRow(envelope, primaryKey, deliverAt) - let insert = primaryKey - ? insertEnvelope(row, primaryKey) - : Effect.as(sql`INSERT INTO ${messagesTableSql} ${sql.insert(row)}`.unprepared, []) - if (envelope._tag === "AckChunk") { - insert = sql`UPDATE ${repliesTableSql} SET acked = ${sqlTrue} WHERE id = ${envelope.replyId}`.pipe( - Effect.andThen( - sql`UPDATE ${messagesTableSql} SET processed = ${sqlTrue} WHERE processed = ${sqlFalse} AND request_id = ${envelope.requestId} AND kind = ${messageKindAckChunk}` - ), - Effect.andThen(insert), - sql.withTransaction - ) + let insert: Effect.Effect, SqlError | PlatformError.PlatformError> + if (primaryKey !== null) { + insert = Effect.flatMap(messageIdForPrimaryKey(primaryKey), (messageId) => { + const row = envelopeToRow(envelope, messageId, deliverAt) + if (!mayHaveLegacyRow(primaryKey)) { + return insertEnvelope(row, messageId) + } + return Effect.flatMap( + selectByMessageId(primaryKey), + (rows) => rows.length > 0 ? Effect.succeed(rows) : insertEnvelope(row, messageId) + ) + }) + } else { + const row = envelopeToRow(envelope, null, deliverAt) + insert = Effect.as(sql`INSERT INTO ${messagesTableSql} ${sql.insert(row)}`.unprepared, []) + if (envelope._tag === "AckChunk") { + insert = sql`UPDATE ${repliesTableSql} SET acked = ${sqlTrue} WHERE id = ${envelope.replyId}`.pipe( + Effect.andThen( + sql`UPDATE ${messagesTableSql} SET processed = ${sqlTrue} WHERE processed = ${sqlFalse} AND request_id = ${envelope.requestId} AND kind = ${messageKindAckChunk}` + ), + Effect.andThen(insert), + sql.withTransaction + ) + } } return insert.pipe( Effect.map((rows) => { @@ -488,7 +526,15 @@ export const make: (options?: { ), requestIdForPrimaryKey: (primaryKey) => - sql<{ id: string | bigint }>`SELECT id FROM ${messagesTableSql} WHERE message_id = ${primaryKey}`.pipe( + messageIdForPrimaryKey(primaryKey).pipe( + Effect.flatMap((messageId) => + sql<{ id: string | bigint }>`SELECT id FROM ${messagesTableSql} WHERE message_id = ${messageId}` + ), + Effect.flatMap((rows) => + rows.length === 0 && mayHaveLegacyRow(primaryKey) + ? sql<{ id: string | bigint }>`SELECT id FROM ${messagesTableSql} WHERE message_id = ${primaryKey}` + : Effect.succeed(rows) + ), Effect.map((rows) => Option.map(Option.fromNullishOr(rows[0]?.id), Snowflake.Snowflake)), Effect.provideService(SqlClient.SafeIntegers, true), PersistenceError.refail, @@ -645,7 +691,9 @@ export const make: (options?: { * * The layer runs the SQL migrations through `make`, provides `MessageStorage`, * and supplies `Snowflake.layerGenerator` internally. Callers still provide - * `SqlClient` and `ShardingConfig`. + * `SqlClient`, `ShardingConfig`, and `Crypto.Crypto`, which is used to hash + * message deduplication keys that would overflow the fixed-width + * `message_id` column. * * **Gotchas** * @@ -662,7 +710,7 @@ export const make: (options?: { export const layer: Layer.Layer< MessageStorage.MessageStorage, never, - SqlClient.SqlClient | ShardingConfig + SqlClient.SqlClient | ShardingConfig | Crypto.Crypto > = Layer.effect(MessageStorage.MessageStorage, make()).pipe( Layer.provide(Snowflake.layerGenerator) ) @@ -675,7 +723,7 @@ export const layer: Layer.Layer< */ export const layerWith = (options: { readonly prefix?: string | undefined -}): Layer.Layer => +}): Layer.Layer => Layer.effect(MessageStorage.MessageStorage, make(options)).pipe( Layer.provide(Snowflake.layerGenerator) ) @@ -993,7 +1041,7 @@ const replyKind = { } as const satisfies Record["_tag"], number | null> const replyFromRow = (row: ReplyRow): Reply.Encoded => - Number(row.kind) === replyKind.WithExit ? + row.kind !== null && Number(row.kind) === replyKind.WithExit ? { _tag: "WithExit", id: String(row.id), diff --git a/repos/effect/packages/effect/src/unstable/cluster/SqlRunnerStorage.ts b/repos/effect/packages/effect/src/unstable/cluster/SqlRunnerStorage.ts index dce8f16e92..c5f6f8875b 100644 --- a/repos/effect/packages/effect/src/unstable/cluster/SqlRunnerStorage.ts +++ b/repos/effect/packages/effect/src/unstable/cluster/SqlRunnerStorage.ts @@ -12,6 +12,8 @@ import * as Arr from "../../Array.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 * as Layer from "../../Layer.ts" import * as Scope from "../../Scope.ts" import * as SqlClient from "../sql/SqlClient.ts" @@ -19,12 +21,24 @@ import type { SqlError } from "../sql/SqlError.ts" import type * as Statement from "../sql/Statement.ts" import { PersistenceError } from "./ClusterError.ts" import { ResourceRef } from "./internal/resourceRef.ts" +import { effectiveInterval } from "./internal/shardLock.ts" import * as RunnerStorage from "./RunnerStorage.ts" import * as ShardId from "./ShardId.ts" import * as ShardingConfig from "./ShardingConfig.ts" const withTracerDisabled = Effect.withTracerEnabled(false) +// This exact FNV-1a hash, including its tag and UTF-8 encoding, is a persistent +// advisory-lock wire format and must never change. +const postgresLockNamespace = (prefix: string): number => { + const bytes = new TextEncoder().encode(`effect-cluster:${prefix}`) + let hash = 0x811c9dc5 + for (let i = 0; i < bytes.length; i++) { + hash = Math.imul(hash ^ bytes[i], 0x01000193) + } + return hash | 0 +} + /** * Creates a SQL-backed `RunnerStorage` implementation for registered runners and * shard locks, using the configured table prefix and advisory locks where @@ -60,18 +74,34 @@ export const make = Effect.fnUntraced(function*(options: { const shardGroups = ShardingConfig.shardGroupConfig(config) const availableShardGroups = Array.from(shardGroups.available) const disableAdvisoryLocks = config.shardLockDisableAdvisory + const lockOperationInterval = effectiveInterval(config) const sql = (yield* SqlClient.SqlClient).withoutTransforms() + const layerScope = yield* Effect.scope const prefix = options?.prefix ?? "cluster" const table = (name: string) => `${prefix}_${name}` + const pgLockNamespace = postgresLockNamespace(prefix) + // PostgreSQL exposes the signed int4 lock key through pg_locks as an unsigned oid. + const pgLockNamespaceOid = pgLockNamespace >>> 0 + // Keep all PostgreSQL and MySQL shard-lock operations on a rebuildable + // reserved connection, including when advisory locks are disabled. const acquireLockConn = sql.onDialectOrElse({ pg: () => Effect.fnUntraced(function*(scope: Scope.Scope) { const conn = yield* Effect.orDie(sql.reserve).pipe( Scope.provide(scope) ) - const pid = (yield* conn.executeValues("SELECT pg_backend_pid()", []))[0][0] as number - yield* Scope.addFinalizerExit(scope, () => Effect.orDie(conn.executeRaw("SELECT pg_advisory_unlock_all()", []))) + const pid = disableAdvisoryLocks + ? 0 + : (yield* conn.executeValues("SELECT pg_backend_pid()", []))[0][0] as number + if (!disableAdvisoryLocks) { + yield* Scope.addFinalizerExit(scope, () => + conn.executeRaw("SELECT pg_advisory_unlock_all()", []).pipe( + Effect.timeout(lockOperationInterval), + Effect.interruptible, + Effect.ignoreCause + )) + } return [conn, pid] as const }, Effect.orDie), mysql: () => @@ -79,6 +109,7 @@ export const make = Effect.fnUntraced(function*(options: { const conn = yield* Effect.orDie(sql.reserve).pipe( Scope.provide(scope) ) + if (disableAdvisoryLocks) return [conn, 0] as const // we need to get the connection id using IS_USED_LOCK to properly // support vitess let pid: number | undefined = undefined @@ -91,12 +122,79 @@ export const make = Effect.fnUntraced(function*(options: { if (taken[0] === null) continue pid = taken[1] } - yield* Scope.addFinalizerExit(scope, () => Effect.orDie(conn.executeRaw("SELECT RELEASE_ALL_LOCKS()", []))) + if (!disableAdvisoryLocks) { + yield* Scope.addFinalizerExit(scope, () => + conn.executeRaw("SELECT RELEASE_ALL_LOCKS()", []).pipe( + Effect.timeout(lockOperationInterval), + Effect.interruptible, + Effect.ignoreCause + )) + } return [conn, pid] as const }, Effect.orDie), orElse: () => undefined }) - const lockConn = acquireLockConn && (yield* ResourceRef.from(yield* Effect.scope, acquireLockConn)) + const lockConn = acquireLockConn && (yield* ResourceRef.from(layerScope, acquireLockConn)) + + // `Effect.timeout` waits for the timed-out effect to finish interrupting, so + // an operation stuck in an uninterruptible region (such as a scope finalizer + // releasing an unresponsive connection) can outlive its deadline. Fork the + // operation and timeout the join instead, leaving stalled cleanup to finish + // detached in the layer scope. + const withDeadline = Effect.fnUntraced(function*(operation: Effect.Effect) { + const fiber = yield* Effect.forkIn(operation, layerScope, { startImmediately: true }) + return yield* Fiber.join(fiber).pipe( + Effect.timeout(lockOperationInterval), + Effect.ensuring(Effect.suspend(() => + fiber.pollUnsafe() !== undefined ? Effect.void : Fiber.interrupt(fiber).pipe( + Effect.forkIn(layerScope, { startImmediately: true }), + Effect.asVoid + ) + )) + ) + }) + + let lockConnRebuilding = false + // Incremented every time the reserved connection is replaced, so failures + // from operations that ran on an already replaced connection do not trigger + // another rebuild. + let lockConnGeneration = 0 + const rebuildLockConn = (generation: number) => { + if ( + !lockConn || + lockConnRebuilding || + generation !== lockConnGeneration || + lockConn.state.current._tag === "Closed" + ) return Effect.void + lockConnRebuilding = true + // The rebuild starts by closing the previous scope, releasing the + // unresponsive connection back to the pool. Bound it with `withDeadline` + // so a release that never completes cannot leave `lockConnRebuilding` set + // forever, which would disable every subsequent rebuild. + return withDeadline(lockConn.rebuildUnsafe()).pipe( + Effect.exit, + Effect.tap((exit) => + Effect.sync(() => { + if (Exit.isSuccess(exit) && lockConn.state.current._tag === "Acquired") { + lockConnGeneration++ + } + }) + ), + Effect.ensuring(Effect.sync(() => { + lockConnRebuilding = false + })), + Effect.forkIn(layerScope, { startImmediately: true }), + Effect.asVoid + ) + } + // Rebuild the reserved connection when `effect` fails on it. Failures keep + // scheduling rebuilds, so a rebuilt connection that is also unresponsive is + // replaced again. + const onErrorRebuildLockConn = (effect: Effect.Effect): Effect.Effect => + Effect.suspend(() => { + const generation = lockConnGeneration + return Effect.onError(effect, () => rebuildLockConn(generation)) + }) const runnersTable = table("runners") const runnersTableSql = sql(runnersTable) @@ -164,21 +262,25 @@ export const make = Effect.fnUntraced(function*(options: { ) `, mysql: () => - sql` - CREATE TABLE IF NOT EXISTS ${locksTableSql} ( - shard_id VARCHAR(50) PRIMARY KEY, - address VARCHAR(255) NOT NULL, - acquired_at DATETIME NOT NULL - ) - `, + disableAdvisoryLocks ? + sql` + CREATE TABLE IF NOT EXISTS ${locksTableSql} ( + shard_id VARCHAR(50) PRIMARY KEY, + address VARCHAR(255) NOT NULL, + acquired_at DATETIME NOT NULL + ) + ` : + Effect.void, pg: () => - sql` - CREATE TABLE IF NOT EXISTS ${locksTableSql} ( - shard_id VARCHAR(50) PRIMARY KEY, - address VARCHAR(255) NOT NULL, - acquired_at TIMESTAMP NOT NULL - ) - `, + disableAdvisoryLocks ? + sql` + CREATE TABLE IF NOT EXISTS ${locksTableSql} ( + shard_id VARCHAR(50) PRIMARY KEY, + address VARCHAR(255) NOT NULL, + acquired_at TIMESTAMP NOT NULL + ) + ` : + Effect.void, orElse: () => // sqlite sql` @@ -272,7 +374,7 @@ export const make = Effect.fnUntraced(function*(options: { const [query, params] = effect.compile() return lockConn.await.pipe( Effect.flatMap(([conn]) => conn.executeRaw(query, params)), - Effect.onError(() => lockConn.rebuildUnsafe()) + onErrorRebuildLockConn ) } const execWithLockConnUnprepared = ( @@ -282,7 +384,7 @@ export const make = Effect.fnUntraced(function*(options: { const [query, params] = effect.compile() return lockConn.await.pipe( Effect.flatMap(([conn]) => conn.executeUnprepared(query, params, undefined)), - Effect.onError(() => lockConn.rebuildUnsafe()) + onErrorRebuildLockConn ) } const execWithLockConnValues = ( @@ -292,7 +394,7 @@ export const make = Effect.fnUntraced(function*(options: { const [query, params] = effect.compile() return lockConn.await.pipe( Effect.flatMap(([conn]) => conn.executeValues(query, params)), - Effect.onError(() => lockConn.rebuildUnsafe()) + onErrorRebuildLockConn ) } @@ -310,6 +412,7 @@ export const make = Effect.fnUntraced(function*(options: { WHERE ${locksTableSql}.address = ${address} OR ${locksTableSql}.acquired_at < ${lockExpiresAt} `.pipe( + execWithLockConn, Effect.andThen(acquiredLocks(address, shardIds)) ) } @@ -319,12 +422,14 @@ export const make = Effect.fnUntraced(function*(options: { const acquiredShardIds: Array = [] const toAcquire = new Map(shardIds.map((shardId) => [lockNumbers.get(shardId)!, shardId])) const takenLocks = yield* conn.executeValues( - `SELECT objid FROM pg_locks WHERE locktype = 'advisory' AND granted = true AND pid = ${pid} ORDER BY objid`, + `SELECT objid FROM pg_locks WHERE locktype = 'advisory' AND granted = true AND classid = ${pgLockNamespaceOid} AND objsubid = 2 AND pid = ${pid} ORDER BY objid`, [] ) for (let i = 0; i < takenLocks.length; i++) { const lockNum = takenLocks[i][0] as number - acquiredShardIds.push(lockNumbersReverse.get(lockNum)!) + const shardId = lockNumbersReverse.get(lockNum) + if (shardId === undefined) continue + acquiredShardIds.push(shardId) toAcquire.delete(lockNum) } if (toAcquire.size === 0) { @@ -338,7 +443,7 @@ export const make = Effect.fnUntraced(function*(options: { } } return acquiredShardIds - }, Effect.onError(() => lockConn!.rebuildUnsafe())) + }, onErrorRebuildLockConn) }, mysql: () => { @@ -352,7 +457,8 @@ export const make = Effect.fnUntraced(function*(options: { ON DUPLICATE KEY UPDATE address = IF(address = VALUES(address) OR acquired_at < ${lockExpiresAt}, VALUES(address), address), acquired_at = IF(address = VALUES(address) OR acquired_at < ${lockExpiresAt}, VALUES(acquired_at), acquired_at) -`.unprepared.pipe( +`.pipe( + execWithLockConnUnprepared, Effect.andThen(acquiredLocks(address, shardIds)) ) } @@ -381,7 +487,7 @@ export const make = Effect.fnUntraced(function*(options: { } } return acquiredShardIds - }, Effect.onError(() => lockConn!.rebuildUnsafe())) + }, onErrorRebuildLockConn) }, mssql: () => (address: string, shardIds: ReadonlyArray) => { @@ -456,7 +562,7 @@ export const make = Effect.fnUntraced(function*(options: { const pgLocks = (shardIdsMap: Map) => Array.from( shardIdsMap.entries(), - ([lockNum, shardId]) => `pg_try_advisory_lock(${lockNum}) AS "${shardId}"` + ([lockNum, shardId]) => `pg_try_advisory_lock(${pgLockNamespace}, ${lockNum}) AS "${shardId}"` ).join(", ") const mysqlLocks = (shardIds: ReadonlyArray) => @@ -473,7 +579,8 @@ export const make = Effect.fnUntraced(function*(options: { WHERE address = ${address} AND acquired_at >= ${lockExpiresAt} AND shard_id IN ${stringLiteralArr(shardIds)} - `.values.pipe( + `.pipe( + execWithLockConnValues, Effect.map((rows) => rows.map((row) => row[0] as string)) ) @@ -529,6 +636,72 @@ export const make = Effect.fnUntraced(function*(options: { `.pipe(execWithLockConnValues, Effect.map((rows) => rows.map((row) => row[0] as string))) }) + const withLockOperationDeadline = (operation: Effect.Effect) => + onErrorRebuildLockConn(withDeadline(operation)) + + const releaseShard = sql.onDialectOrElse({ + pg: () => { + if (disableAdvisoryLocks) { + return (address: string, shardId: string) => + sql`DELETE FROM ${locksTableSql} WHERE address = ${address} AND shard_id = ${shardId}`.pipe(execWithLockConn) + } + return Effect.fnUntraced( + function*(_address, shardId) { + const lockNum = lockNumbers.get(shardId)! + for (let i = 0; i < 5; i++) { + const [conn] = yield* lockConn!.await + yield* conn.executeRaw(`SELECT pg_advisory_unlock(${pgLockNamespace}, ${lockNum})`, []) + const takenLocks = yield* conn.executeValues( + `SELECT 1 FROM pg_locks WHERE locktype = 'advisory' AND granted = true AND classid = ${pgLockNamespaceOid} AND objid = ${lockNum} AND objsubid = 2 AND pid = pg_backend_pid()`, + [] + ) + if (takenLocks.length === 0) return + } + const [conn] = yield* lockConn!.await + yield* conn.executeRaw(`SELECT pg_advisory_unlock_all()`, []) + }, + onErrorRebuildLockConn, + Effect.asVoid + ) + }, + mysql: () => { + if (disableAdvisoryLocks) { + return (address: string, shardId: string) => + sql`DELETE FROM ${locksTableSql} WHERE address = ${address} AND shard_id = ${shardId}`.pipe(execWithLockConn) + } + return Effect.fnUntraced( + function*(_address, shardId) { + const lockName = lockNames.get(shardId)! + while (true) { + const [conn, pid] = yield* lockConn!.await + yield* conn.executeRaw(`SELECT RELEASE_LOCK('${lockName}')`, []) + const takenLocks = yield* conn.executeValues( + `SELECT IS_USED_LOCK('${lockName}')`, + [] + ) + if (takenLocks.length === 0 || takenLocks[0][0] !== pid) return + } + }, + onErrorRebuildLockConn, + Effect.asVoid + ) + }, + orElse: () => (address: string, shardId: string) => + sql`DELETE FROM ${locksTableSql} WHERE address = ${address} AND shard_id = ${shardId}` + }) + + const releaseAllShards = sql.onDialectOrElse({ + pg: () => (address: string) => + disableAdvisoryLocks + ? sql`DELETE FROM ${locksTableSql} WHERE address = ${address}`.pipe(execWithLockConn) + : sql`SELECT pg_advisory_unlock_all()`.pipe(execWithLockConn, Effect.asVoid), + mysql: () => (address: string) => + disableAdvisoryLocks + ? sql`DELETE FROM ${locksTableSql} WHERE address = ${address}`.pipe(execWithLockConn) + : sql`SELECT RELEASE_ALL_LOCKS()`.pipe(execWithLockConn, Effect.asVoid), + orElse: () => (address: string) => sql`DELETE FROM ${locksTableSql} WHERE address = ${address}` + }) + return RunnerStorage.makeEncoded({ getRunners: sql`SELECT runner, healthy FROM ${runnersTableSql} WHERE last_heartbeat > ${lockExpiresAt}`.values.pipe( PersistenceError.refail, @@ -559,120 +732,37 @@ export const make = Effect.fnUntraced(function*(options: { ), acquire: (address, shardIds) => - acquireLock(address, shardIds).pipe( + withLockOperationDeadline(acquireLock(address, shardIds)).pipe( PersistenceError.refail, withTracerDisabled ), refresh: (address, shardIds) => - sql`UPDATE ${runnersTableSql} SET last_heartbeat = ${sqlNow} WHERE address = ${address}`.pipe( - execWithLockConn, - shardIds.length > 0 ? - Effect.andThen(refreshShards(address, shardIds)) : - Effect.as([]), + withLockOperationDeadline( + sql`UPDATE ${runnersTableSql} SET last_heartbeat = ${sqlNow} WHERE address = ${address}`.pipe( + execWithLockConn, + shardIds.length > 0 ? + Effect.andThen(refreshShards(address, shardIds)) : + Effect.as([]) + ) + ).pipe( PersistenceError.refail, withTracerDisabled ), - release: sql.onDialectOrElse({ - pg: () => { - if (disableAdvisoryLocks) { - return (address: string, shardId: string) => - sql`DELETE FROM ${locksTableSql} WHERE address = ${address} AND shard_id = ${shardId}`.pipe( - PersistenceError.refail, - withTracerDisabled - ) - } - return Effect.fnUntraced( - function*(_address, shardId) { - const lockNum = lockNumbers.get(shardId)! - for (let i = 0; i < 5; i++) { - const [conn] = yield* lockConn!.await - yield* conn.executeRaw(`SELECT pg_advisory_unlock(${lockNum})`, []) - const takenLocks = yield* conn.executeValues( - `SELECT 1 FROM pg_locks WHERE locktype = 'advisory' AND granted = true AND pid = pg_backend_pid() AND objid = ${lockNum}`, - [] - ) - if (takenLocks.length === 0) return - } - const [conn] = yield* lockConn!.await - yield* conn.executeRaw(`SELECT pg_advisory_unlock_all()`, []) - }, - Effect.onError(() => lockConn!.rebuildUnsafe()), - Effect.asVoid, - PersistenceError.refail, - withTracerDisabled - ) - }, - mysql: () => { - if (disableAdvisoryLocks) { - return (address: string, shardId: string) => - sql`DELETE FROM ${locksTableSql} WHERE address = ${address} AND shard_id = ${shardId}`.pipe( - PersistenceError.refail, - withTracerDisabled - ) - } - return Effect.fnUntraced( - function*(_address, shardId) { - const lockName = lockNames.get(shardId)! - while (true) { - const [conn, pid] = yield* lockConn!.await - yield* conn.executeRaw(`SELECT RELEASE_LOCK('${lockName}')`, []) - const takenLocks = yield* conn.executeValues( - `SELECT IS_USED_LOCK('${lockName}')`, - [] - ) - if (takenLocks.length === 0 || takenLocks[0][0] !== pid) return - } - }, - Effect.onError(() => lockConn!.rebuildUnsafe()), - Effect.asVoid, - PersistenceError.refail, - withTracerDisabled - ) - }, - orElse: () => (address, shardId) => - sql`DELETE FROM ${locksTableSql} WHERE address = ${address} AND shard_id = ${shardId}`.pipe( - PersistenceError.refail, - withTracerDisabled - ) - }), - - releaseAll: sql.onDialectOrElse({ - pg: () => (address) => { - if (disableAdvisoryLocks) { - return sql`DELETE FROM ${locksTableSql} WHERE address = ${address}`.pipe( - PersistenceError.refail, - withTracerDisabled - ) - } - return sql`SELECT pg_advisory_unlock_all()`.pipe( - execWithLockConn, - Effect.asVoid, - PersistenceError.refail, - withTracerDisabled - ) - }, - mysql: () => (address) => { - if (disableAdvisoryLocks) { - return sql`DELETE FROM ${locksTableSql} WHERE address = ${address}`.pipe( - PersistenceError.refail, - withTracerDisabled - ) - } - return sql`SELECT RELEASE_ALL_LOCKS()`.pipe( - execWithLockConn, - Effect.asVoid, - PersistenceError.refail, - withTracerDisabled - ) - }, - orElse: () => (address) => - sql`DELETE FROM ${locksTableSql} WHERE address = ${address}`.pipe( - PersistenceError.refail, - withTracerDisabled - ) - }) + release: (address, shardId) => + withLockOperationDeadline(releaseShard(address, shardId)).pipe( + Effect.asVoid, + PersistenceError.refail, + withTracerDisabled + ), + + releaseAll: (address) => + withLockOperationDeadline(releaseAllShards(address)).pipe( + Effect.asVoid, + PersistenceError.refail, + withTracerDisabled + ) }) }, withTracerDisabled) diff --git a/repos/effect/packages/effect/src/unstable/cluster/internal/entityManager.ts b/repos/effect/packages/effect/src/unstable/cluster/internal/entityManager.ts index fac03bdfa9..e568d3d07e 100644 --- a/repos/effect/packages/effect/src/unstable/cluster/internal/entityManager.ts +++ b/repos/effect/packages/effect/src/unstable/cluster/internal/entityManager.ts @@ -18,7 +18,6 @@ import * as Schema from "../../../Schema.ts" import * as SchemaIssue from "../../../SchemaIssue.ts" import * as Scope from "../../../Scope.ts" import type * as Rpc from "../../rpc/Rpc.ts" -import { RequestId } from "../../rpc/RpcMessage.ts" import * as RpcServer from "../../rpc/RpcServer.ts" import { AlreadyProcessingMessage, EntityNotAssignedToRunner, MailboxFull, MalformedMessage } from "../ClusterError.ts" import * as ClusterMetrics from "../ClusterMetrics.ts" @@ -57,7 +56,9 @@ export interface EntityManager { }) => boolean readonly clearProcessed: () => void - readonly interruptShard: (shardId: ShardId) => Effect.Effect + readonly interruptShard: (shardId: ShardId, options?: { + readonly force?: boolean + }) => Effect.Effect readonly activeEntityCount: Effect.Effect } @@ -67,7 +68,7 @@ export interface EntityManager { export type EntityState = { readonly address: EntityAddress readonly scope: Scope.Scope - readonly activeRequests: Map sentReply: boolean @@ -109,7 +110,7 @@ export const make = Effect.fnUntraced(function*< const clock = yield* Clock const context = yield* Effect.context | Rpc.Middleware | RX>() const defectRetryPolicy = options.defectRetryPolicy - ? Schedule.andThen(options.defectRetryPolicy, defaultRetryPolicy) + ? Schedule.concat(options.defectRetryPolicy, defaultRetryPolicy) : defaultRetryPolicy const retryDriver = yield* Schedule.toStepWithSleep(defectRetryPolicy) const entityRpcs = new Map(entity.protocol.requests) @@ -118,7 +119,10 @@ export const make = Effect.fnUntraced(function*< entityRpcs.set(KeepAliveRpc._tag, KeepAliveRpc as any) const activeServers = new Map() - const serverCloseLatches = new Map() + const serverCloseLatches = new Map() const processedRequestIds = new Set() const entities: ResourceMap< @@ -133,19 +137,23 @@ export const make = Effect.fnUntraced(function*< const scope = yield* Effect.scope const endLatch = Latch.makeUnsafe() const keepAliveLatch = Latch.makeUnsafe() + const closeLatches = { + closed: Latch.makeUnsafe(), + force: Latch.makeUnsafe() + } // on shutdown, reset the storage for the entity yield* Scope.addFinalizerExit( scope, () => { - serverCloseLatches.get(address)?.openUnsafe() + serverCloseLatches.get(address)?.closed.openUnsafe() serverCloseLatches.delete(address) return Effect.void } ) const activeRequests: EntityState["activeRequests"] = new Map() - let defectRequestIds = new Set() + let defectRequestIds = new Set() let isRestartingDueToDefect = false // the server is stored in a ref, so if there is a defect, we can @@ -155,16 +163,17 @@ export const make = Effect.fnUntraced(function*< Effect.fnUntraced(function*(scope) { let isShuttingDown = false + const handlerContext = context.pipe( + Context.add(CurrentAddress, address), + Context.add(CurrentRunnerAddress, options.runnerAddress), + Context.add(KeepAliveLatch, keepAliveLatch), + Context.add(Scope.Scope, scope), + Context.add(CurrentLogAnnotations, {}) + ) + // Initiate the behavior for the entity const handlers = yield* (entity.protocol.toHandlers(buildHandlers as any).pipe( - Effect.provideService(CurrentLogAnnotations, {}), - Effect.provideContext(Context.mutate(context, (context) => - context.pipe( - Context.add(CurrentAddress, address), - Context.add(CurrentRunnerAddress, options.runnerAddress), - Context.add(KeepAliveLatch, keepAliveLatch), - Context.add(Scope.Scope, scope) - ))), + Effect.setContext(handlerContext as Context.Context), Effect.sandbox, Effect.tapError((cause) => Effect.logError("Defect building entity handlers", cause)), Effect.retry(defectRetryPolicy) @@ -182,7 +191,7 @@ export const make = Effect.fnUntraced(function*< onFromServer(response): Effect.Effect { switch (response._tag) { case "Exit": { - const request = activeRequests.get(response.requestId) + const request = activeRequests.get(Snowflake.Snowflake(response.requestId)) if (!request) return Effect.void request.sentReply = true @@ -190,7 +199,7 @@ export const make = Effect.fnUntraced(function*< if ( isShuttingDown && Exit.hasInterrupts(response.exit) && - defectRequestIds.has(response.requestId) + defectRequestIds.has(Snowflake.Snowflake(response.requestId)) ) { return Effect.void } @@ -206,19 +215,25 @@ export const make = Effect.fnUntraced(function*< (isShuttingDown || isUninterruptibleForServer(request.message.annotations)) ) { if (!isShuttingDown) { - return server.write(0, { - ...request.message.envelope, - id: RequestId(request.message.envelope.requestId), - tag: request.message.envelope.tag as any, - payload: new Request({ + return server.write( + 0, + { ...request.message.envelope, - lastSentChunk: request.lastSentChunk - } as any) as any - }).pipe( + id: request.message.envelope.requestId as any, + tag: request.message.envelope.tag as any, + payload: new Request({ + ...request.message.envelope, + lastSentChunk: request.lastSentChunk + } as any) as any + }, + Context.get(request.rpc.annotations, WithTransaction) + ? { onRequest: options.storage.withTransaction } + : undefined + ).pipe( Effect.forkIn(scope) ) } - activeRequests.delete(response.requestId) + activeRequests.delete(Snowflake.Snowflake(response.requestId)) return options.storage.unregisterReplyHandler(request.message.envelope.requestId) } return retryRespond( @@ -235,7 +250,7 @@ export const make = Effect.fnUntraced(function*< ).pipe( Effect.flatMap(() => { processedRequestIds.add(request.message.envelope.requestId) - activeRequests.delete(response.requestId) + activeRequests.delete(Snowflake.Snowflake(response.requestId)) // ensure that the reaper does not remove the entity as we haven't // been "idle" yet @@ -249,7 +264,7 @@ export const make = Effect.fnUntraced(function*< ) } case "Chunk": { - const request = activeRequests.get(response.requestId) + const request = activeRequests.get(Snowflake.Snowflake(response.requestId)) if (!request) return Effect.void const sequence = request.sequence request.sequence++ @@ -280,7 +295,7 @@ export const make = Effect.fnUntraced(function*< } }).pipe( Scope.provide(scope), - Effect.provideContext(handlers) + Effect.setContext(Context.merge(handlerContext, handlers)) ) yield* Scope.addFinalizer( @@ -294,16 +309,22 @@ export const make = Effect.fnUntraced(function*< for (const id of defectRequestIds) { const request = activeRequests.get(id) if (!request) continue - const { lastSentChunk, message } = request - yield* server.write(0, { - ...message.envelope, - id: RequestId(message.envelope.requestId), - tag: message.envelope.tag as any, - payload: new Request({ + const { lastSentChunk, message, rpc } = request + yield* server.write( + 0, + { ...message.envelope, - lastSentChunk - } as any) as any - }) + id: message.envelope.requestId as any, + tag: message.envelope.tag as any, + payload: new Request({ + ...message.envelope, + lastSentChunk + } as any) as any + }, + Context.get(rpc.annotations, WithTransaction) + ? { onRequest: options.storage.withTransaction } + : undefined + ) } defectRequestIds.clear() } @@ -340,11 +361,11 @@ export const make = Effect.fnUntraced(function*< const state: EntityState = { scope, address, - write(clientId, message) { + write(clientId, message, writeOptions) { if (writeRef.state.current._tag !== "Acquired") { - return Effect.flatMap(writeRef.await, (write) => write(clientId, message)) + return Effect.flatMap(writeRef.await, (write) => write(clientId, message, writeOptions)) } - return writeRef.state.current.value(clientId, message) + return writeRef.state.current.value(clientId, message, writeOptions) }, activeRequests, lastActiveCheck: clock.currentTimeMillisUnsafe(), @@ -360,14 +381,19 @@ export const make = Effect.fnUntraced(function*< scope, Effect.withFiber((fiber) => { activeServers.delete(address.entityId) - serverCloseLatches.set(address, Latch.makeUnsafe()) internalInterruptors.add(fiber.id) - return state.write(0, { _tag: "Eof" }).pipe( - Effect.andThen(Effect.interruptible(endLatch.await)), - Effect.timeoutOption(config.entityTerminationTimeout) + return Effect.raceFirst( + state.write(0, { _tag: "Eof" }).pipe( + Effect.andThen(endLatch.await), + Effect.timeoutOption(config.entityTerminationTimeout), + Effect.interruptible + ), + Effect.interruptible(closeLatches.force.await) ) }) ) + // Do not make shard interruption wait for an entity that is still building. + serverCloseLatches.set(address, closeLatches) activeServers.set(address.entityId, state) return state @@ -471,21 +497,23 @@ export const make = Effect.fnUntraced(function*< }) } server.activeRequests.set(message.envelope.requestId, entry) - let write = server.write(0, { - ...message.envelope, - id: RequestId(message.envelope.requestId), - payload: new Request({ + return server.write( + 0, + { ...message.envelope, - lastSentChunk: Option.filter( - message.lastSentReply, - (reply): reply is Reply.Chunk => reply._tag === "Chunk" - ) - }) - }) - if (Context.get(message.annotations, WithTransaction)) { - write = options.storage.withTransaction(write) - } - return write + id: message.envelope.requestId as any, + payload: new Request({ + ...message.envelope, + lastSentChunk: Option.filter( + message.lastSentReply, + (reply): reply is Reply.Chunk => reply._tag === "Chunk" + ) + }) + }, + Context.get(message.annotations, WithTransaction) + ? { onRequest: options.storage.withTransaction } + : undefined + ) } case "IncomingEnvelope": { const entry = server.activeRequests.get(message.envelope.requestId) @@ -501,10 +529,10 @@ export const make = Effect.fnUntraced(function*< return server.write( 0, message.envelope._tag === "AckChunk" - ? { _tag: "Ack", requestId: RequestId(message.envelope.requestId) } + ? { _tag: "Ack", requestId: message.envelope.requestId as any } : { _tag: "Interrupt", - requestId: RequestId(message.envelope.requestId), + requestId: message.envelope.requestId as any, interruptors: [] } ) @@ -517,22 +545,29 @@ export const make = Effect.fnUntraced(function*< ) } - const decodeMessage = makeMessageDecode(entity, entityRpcs) + const decodeMessage = makeMessageDecode(entityRpcs) const runFork = Effect.runForkWith(context) return identity({ - interruptShard: (shardId: ShardId) => + interruptShard: (shardId: ShardId, options) => Effect.suspend(function loop(): Effect.Effect { const fibers = Arr.empty>() + if (options?.force === true) { + serverCloseLatches.forEach((latches, address) => { + if (shardId[Equal.symbol](address.shardId)) { + latches.force.openUnsafe() + } + }) + } activeServers.forEach((state) => { if (shardId[Equal.symbol](state.address.shardId)) { fibers.push(runFork(entities.removeIgnore(state.address))) } }) - serverCloseLatches.forEach((latch, address) => { + serverCloseLatches.forEach((latches, address) => { if (shardId[Equal.symbol](address.shardId)) { - fibers.push(runFork(latch.await)) + fibers.push(runFork(latches.closed.await)) } }) if (fibers.length === 0) return Effect.void @@ -609,14 +644,12 @@ export const make = Effect.fnUntraced(function*< }) }) -const defaultRetryPolicy = Schedule.exponential(500, 1.5).pipe( - Schedule.either(Schedule.spaced("10 seconds")) -) +const defaultRetryPolicy = Schedule.min([ + Schedule.exponential(500, 1.5), + Schedule.spaced("10 seconds") +]) -const makeMessageDecode = ( - entity: Entity, - entityRpcs: Map -) => { +const makeMessageDecode = (entityRpcs: Map) => { const decodeRequest = Effect.fnUntracedEager(function*( message: Message.IncomingRequest, rpc: Rpc.AnyWithProps @@ -654,8 +687,8 @@ const makeMessageDecode = ( if (!rpc) { return Effect.fail( new Schema.SchemaError( - new SchemaIssue.InvalidValue(Option.some(message), { - message: `Unknown tag ${message.envelope.tag} for entity type ${entity.type}` + new SchemaIssue.InvalidValue({ + message: "Expected a known entity RPC tag" }) ) ) diff --git a/repos/effect/packages/effect/src/unstable/cluster/internal/resourceMap.ts b/repos/effect/packages/effect/src/unstable/cluster/internal/resourceMap.ts index 9055ba1357..13377199b6 100644 --- a/repos/effect/packages/effect/src/unstable/cluster/internal/resourceMap.ts +++ b/repos/effect/packages/effect/src/unstable/cluster/internal/resourceMap.ts @@ -4,23 +4,16 @@ import * as Effect from "../../../Effect.ts" import * as Exit from "../../../Exit.ts" import * as MutableHashMap from "../../../MutableHashMap.ts" import * as MutableRef from "../../../MutableRef.ts" -import * as Option from "../../../Option.ts" import * as Scope from "../../../Scope.ts" /** @internal */ export class ResourceMap { readonly lookup: (key: K, scope: Scope.Scope) => Effect.Effect - readonly entries: MutableHashMap.MutableHashMap - }> + readonly entries: BackingMap readonly isClosed: MutableRef.MutableRef constructor( lookup: (key: K, scope: Scope.Scope) => Effect.Effect, - entries: MutableHashMap.MutableHashMap - }>, + entries: BackingMap, isClosed: MutableRef.MutableRef ) { this.lookup = lookup @@ -28,22 +21,29 @@ export class ResourceMap { this.isClosed = isClosed } - static make = Effect.fnUntraced(function*(lookup: (key: K) => Effect.Effect) { + static make = Effect.fnUntraced(function*(lookup: (key: K) => Effect.Effect, options?: { + readonly referential?: boolean | undefined + }) { const scope = yield* Effect.scope const services = yield* Effect.context() const isClosed = MutableRef.make(false) - const entries = MutableHashMap.empty - }>() + const entries: BackingMap = options?.referential ? + { + _tag: "Referential", + map: new Map() + } : + { + _tag: "Equal", + map: MutableHashMap.empty() + } yield* Scope.addFinalizerExit( scope, (exit) => { MutableRef.set(isClosed, true) - return Effect.forEach(entries, ([key, { scope }]) => { - MutableHashMap.remove(entries, key) + return Effect.forEach(entries.map, ([key, { scope }]) => { + backingDelete(entries, key) return Effect.exit(Scope.close(scope, exit)) }, { concurrency: "unbounded", discard: true }) } @@ -61,31 +61,34 @@ export class ResourceMap { if (MutableRef.get(this.isClosed)) { return Effect.interrupt } - const existing = MutableHashMap.get(this.entries, key) - if (Option.isSome(existing)) { - return Deferred.await(existing.value.deferred) + const existing = backingGet(this.entries, key) + if (existing) { + return Deferred.await(existing.deferred) } - const scope = Effect.runSync(Scope.make()) + const scope = Scope.makeUnsafe() const deferred = Deferred.makeUnsafe() - MutableHashMap.set(this.entries, key, { scope, deferred }) + backingSet(this.entries, key, { scope, deferred }) return Effect.onExit(this.lookup(key, scope), (exit) => { if (exit._tag === "Success") { return Deferred.done(deferred, exit) } - MutableHashMap.remove(this.entries, key) - return Deferred.done(deferred, exit) + backingDelete(this.entries, key) + return Effect.andThen( + Deferred.done(deferred, exit), + Scope.close(scope, exit) + ) }) }) } remove(key: K): Effect.Effect { return Effect.suspend(() => { - const entry = MutableHashMap.get(this.entries, key) - if (Option.isNone(entry)) { + const entry = backingGet(this.entries, key) + if (!entry) { return Effect.void } - MutableHashMap.remove(this.entries, key) - return Scope.close(entry.value.scope, Exit.void) + backingDelete(this.entries, key) + return Scope.close(entry.scope, Exit.void) }) } @@ -98,3 +101,37 @@ export class ResourceMap { })) } } + +type BackingMap = { + readonly _tag: "Equal" + readonly map: MutableHashMap.MutableHashMap> +} | { + readonly _tag: "Referential" + readonly map: Map> +} + +type Entry = { + readonly scope: Scope.Closeable + readonly deferred: Deferred.Deferred +} + +const backingGet = (map: BackingMap, key: K): Entry | undefined => { + if (map._tag === "Equal") { + return MutableHashMap.get(map.map, key).valueOrUndefined + } + return map.map.get(key) +} +const backingSet = (map: BackingMap, key: K, entry: Entry): void => { + if (map._tag === "Equal") { + MutableHashMap.set(map.map, key, entry) + } else { + map.map.set(key, entry) + } +} +const backingDelete = (map: BackingMap, key: K): void => { + if (map._tag === "Equal") { + MutableHashMap.remove(map.map, key) + } else { + map.map.delete(key) + } +} diff --git a/repos/effect/packages/effect/src/unstable/cluster/internal/resourceRef.ts b/repos/effect/packages/effect/src/unstable/cluster/internal/resourceRef.ts index 00ed414e67..e0976d042a 100644 --- a/repos/effect/packages/effect/src/unstable/cluster/internal/resourceRef.ts +++ b/repos/effect/packages/effect/src/unstable/cluster/internal/resourceRef.ts @@ -1,3 +1,4 @@ +import type * as Cause from "../../../Cause.ts" import * as Effect from "../../../Effect.ts" import * as Exit from "../../../Exit.ts" import * as Latch from "../../../Latch.ts" @@ -7,7 +8,7 @@ import * as Scope from "../../../Scope.ts" import { internalInterruptors } from "./interruptors.ts" /** @internal */ -export type State = { +export type State = { readonly _tag: "Closed" } | { readonly _tag: "Acquiring" @@ -16,6 +17,10 @@ export type State = { readonly _tag: "Acquired" readonly scope: Scope.Closeable readonly value: A +} | { + readonly _tag: "Failed" + readonly scope: Scope.Closeable + readonly cause: Cause.Cause } /** @internal */ @@ -24,7 +29,7 @@ export class ResourceRef { parentScope: Scope.Scope, acquire: (scope: Scope.Scope) => Effect.Effect ) { - const state = MutableRef.make>({ _tag: "Closed" }) + const state = MutableRef.make>({ _tag: "Closed" }) yield* Scope.addFinalizerExit(parentScope, (exit) => { const s = MutableRef.get(state) @@ -44,10 +49,10 @@ export class ResourceRef { return new ResourceRef(state, acquire) }) - readonly state: MutableRef.MutableRef> + readonly state: MutableRef.MutableRef> readonly acquire: (scope: Scope.Scope) => Effect.Effect constructor( - state: MutableRef.MutableRef>, + state: MutableRef.MutableRef>, acquire: (scope: Scope.Scope) => Effect.Effect ) { this.state = state @@ -84,15 +89,32 @@ export class ResourceRef { MutableRef.set(this.state, { _tag: "Acquired", scope, value }) return this.latch.open }) + ).pipe( + Effect.onExit((exit) => { + if (Exit.isSuccess(exit)) { + return Effect.void + } + return Scope.close(scope, exit).pipe( + Effect.ensuring(Effect.sync(() => { + const state = this.state.current + if (state._tag === "Acquiring" && state.scope === scope) { + MutableRef.set(this.state, { _tag: "Failed", scope, cause: exit.cause }) + this.latch.openUnsafe() + } + })) + ) + }) ) } - await: Effect.Effect = Effect.suspend(() => { + await: Effect.Effect = Effect.suspend(() => { const s = this.state.current if (s._tag === "Closed") { return Effect.interrupt } else if (s._tag === "Acquired") { return Effect.succeed(s.value) + } else if (s._tag === "Failed") { + return Effect.failCause(s.cause) } return Effect.flatMap(this.latch.await, () => this.await) }) diff --git a/repos/effect/packages/effect/src/unstable/cluster/internal/shardLock.ts b/repos/effect/packages/effect/src/unstable/cluster/internal/shardLock.ts new file mode 100644 index 0000000000..7e37eed5e2 --- /dev/null +++ b/repos/effect/packages/effect/src/unstable/cluster/internal/shardLock.ts @@ -0,0 +1,9 @@ +import * as Duration from "../../../Duration.ts" +import type { ShardingConfig } from "../ShardingConfig.ts" + +/** @internal */ +export const effectiveInterval = (config: ShardingConfig["Service"]): Duration.Duration => + Duration.min( + Duration.fromInputUnsafe(config.shardLockRefreshInterval), + Duration.divideUnsafe(Duration.fromInputUnsafe(config.shardLockExpiration), 3) + ) diff --git a/repos/effect/packages/effect/src/unstable/devtools/DevToolsClient.ts b/repos/effect/packages/effect/src/unstable/devtools/DevToolsClient.ts index ff7d7f3ded..84bf14adcc 100644 --- a/repos/effect/packages/effect/src/unstable/devtools/DevToolsClient.ts +++ b/repos/effect/packages/effect/src/unstable/devtools/DevToolsClient.ts @@ -184,7 +184,8 @@ const makeTracerEffect = Effect.gen(function*() { return Tracer.make({ span(options) { const span = currentTracer.span(options) - client.sendUnsafe(span) + // the span is mutated in place, so send a snapshot of its current state + client.sendUnsafe({ ...span }) const oldEvent = span.event span.event = function(this: Tracer.Span, name, startTime, attributes) { client.sendUnsafe({ @@ -201,7 +202,7 @@ const makeTracerEffect = Effect.gen(function*() { const oldEnd = span.end span.end = function(this: Tracer.Span, endTime, exit) { oldEnd.call(this, endTime, exit) - client.sendUnsafe(span) + client.sendUnsafe({ ...span }) } return span diff --git a/repos/effect/packages/effect/src/unstable/devtools/DevToolsSchema.ts b/repos/effect/packages/effect/src/unstable/devtools/DevToolsSchema.ts index 4778114e74..bdfc47f2df 100644 --- a/repos/effect/packages/effect/src/unstable/devtools/DevToolsSchema.ts +++ b/repos/effect/packages/effect/src/unstable/devtools/DevToolsSchema.ts @@ -311,7 +311,7 @@ export type Counter = Schema.Schema.Type export const Frequency = metric( "Frequency", Schema.Struct({ - occurrences: Schema.ReadonlyMap(Schema.String, Schema.Number) + occurrences: Schema.ReadonlyMap(Schema.String, Schema.Natural) }) ) @@ -370,8 +370,8 @@ export type Gauge = Schema.Schema.Type export const Histogram = metric( "Histogram", Schema.Struct({ - buckets: Schema.Array(Schema.Tuple([Schema.Number, Schema.Number])), - count: Schema.Number, + buckets: Schema.Array(Schema.Tuple([Schema.Number, Schema.Natural])), + count: Schema.Natural, min: Schema.Number, max: Schema.Number, sum: Schema.Number @@ -405,8 +405,13 @@ export type Histogram = Schema.Schema.Type export const Summary = metric( "Summary", Schema.Struct({ - quantiles: Schema.Array(Schema.Tuple([Schema.Number, Schema.UndefinedOr(Schema.Number)])), - count: Schema.Number, + quantiles: Schema.Array( + Schema.Tuple([ + Schema.Finite.check(Schema.isBetween({ minimum: 0, maximum: 1 })), + Schema.UndefinedOr(Schema.Number) + ]) + ), + count: Schema.Natural, min: Schema.Number, max: Schema.Number, sum: Schema.Number diff --git a/repos/effect/packages/effect/src/unstable/encoding/Ini.ts b/repos/effect/packages/effect/src/unstable/encoding/Ini.ts new file mode 100644 index 0000000000..34e3728cb1 --- /dev/null +++ b/repos/effect/packages/effect/src/unstable/encoding/Ini.ts @@ -0,0 +1,174 @@ +/** + * Parses INI configuration files. + * + * This module contains the decoding surface used by Effect's CLI without + * pulling in the complete `ini` package. + * + * @since 4.0.0 + */ + +/* + * The parser is adapted from `ini` 7.0.0. + * + * Copyright (c) Isaac Z. Schlueter and Contributors + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +const hasOwn = Object.prototype.hasOwnProperty + +const splitSections = (value: string): Array => { + const sections: Array = [] + let start = 0 + let index = 0 + while ((index = value.indexOf(".", index)) !== -1) { + if (index === 0 || value[index - 1] !== "\\") { + sections.push(value.slice(start, index)) + start = index + 1 + } + index++ + } + sections.push(value.slice(start)) + return sections +} + +const isQuoted = (value: string): boolean => + value.length >= 2 && + ((value.startsWith("\"") && value.endsWith("\"")) || + (value.startsWith("'") && value.endsWith("'"))) + +const unquote = (input: string | undefined): string => { + let value = (input ?? "").trim() + if (isQuoted(value)) { + if (value[0] === "'") { + value = value.slice(1, -1) + } + try { + return JSON.parse(value) + } catch { + return value + } + } + + let escaped = false + let output = "" + for (const character of value) { + if (escaped) { + output += character === ";" || character === "#" || character === "\\" + ? character + : `\\${character}` + escaped = false + } else if (character === ";" || character === "#") { + break + } else if (character === "\\") { + escaped = true + } else { + output += character + } + } + return output.trim() +} + +/** + * Parses an INI document into a null-prototype record. + * + * Section names separated by dots become nested records, repeated keys ending + * in `[]` become arrays, and the scalar values `true`, `false`, and `null` are + * decoded. Other scalar values remain strings. + * + * @category decoding + * @since 4.0.0 + */ +export const parse = (input: string): Record => { + const output: Record = Object.create(null) + let current = output + const sectionPattern = /^\[([^\]]*)\]\s*$/ + const propertyPattern = /^([^=]+)(=(.*))?$/ + + for (const line of input.split(/[\r\n]+/g)) { + if (line.length === 0 || /^\s*(?:[;#]|$)/.test(line)) { + continue + } + + const sectionMatch = sectionPattern.exec(line) + if (sectionMatch !== null) { + const section = unquote(sectionMatch[1]) + if (section === "__proto__") { + current = Object.create(null) + } else { + current = output[section] ??= Object.create(null) + } + continue + } + + const propertyMatch = propertyPattern.exec(line) + if (propertyMatch === null) { + continue + } + + const rawKey = unquote(propertyMatch[1]) + const array = rawKey.length > 2 && rawKey.endsWith("[]") + const key = array ? rawKey.slice(0, -2) : rawKey + if (key === "__proto__") { + continue + } + + const rawValue = propertyMatch[2] === undefined ? true : unquote(propertyMatch[3]) + const value = rawValue === "true" || rawValue === "false" || rawValue === "null" + ? JSON.parse(rawValue) + : rawValue + + if (array) { + if (!hasOwn.call(current, key)) { + current[key] = [] + } else if (!Array.isArray(current[key])) { + current[key] = [current[key]] + } + } + if (Array.isArray(current[key])) { + current[key].push(value) + } else { + current[key] = value + } + } + + const remove: Array = [] + for (const section of Object.keys(output)) { + if (typeof output[section] !== "object" || output[section] === null || Array.isArray(output[section])) { + continue + } + + const parts = splitSections(section) + const last = parts.pop()! + const key = last.replace(/\\\./g, ".") + current = output + for (const part of parts) { + if (part === "__proto__") { + continue + } + if (!hasOwn.call(current, part) || typeof current[part] !== "object" || current[part] === null) { + current[part] = Object.create(null) + } + current = current[part] + } + if (current === output && key === last) { + continue + } + current[key] = output[section] + remove.push(section) + } + for (const section of remove) { + delete output[section] + } + return output +} diff --git a/repos/effect/packages/effect/src/unstable/encoding/Msgpack.ts b/repos/effect/packages/effect/src/unstable/encoding/Msgpack.ts index 3cc7fa6b93..ceb40f05aa 100644 --- a/repos/effect/packages/effect/src/unstable/encoding/Msgpack.ts +++ b/repos/effect/packages/effect/src/unstable/encoding/Msgpack.ts @@ -12,14 +12,13 @@ import { Packr, Unpackr } from "msgpackr" import * as Msgpackr from "msgpackr" import * as Arr from "../../Array.ts" +import * as Cause from "../../Cause.ts" import * as Channel from "../../Channel.ts" import * as ChannelSchema from "../../ChannelSchema.ts" import * as Data from "../../Data.ts" import * as Effect from "../../Effect.ts" import { dual } from "../../Function.ts" -import * as Option from "../../Option.ts" -import * as Predicate from "../../Predicate.ts" -import type * as Pull from "../../Pull.ts" +import * as Pull from "../../Pull.ts" import * as Schema from "../../Schema.ts" import * as SchemaIssue from "../../SchemaIssue.ts" import * as SchemaTransformation from "../../SchemaTransformation.ts" @@ -136,37 +135,55 @@ export const decode = (): Channel.Channel< Channel.fromTransform((upstream, _scope) => Effect.sync(() => { const unpackr = new Unpackr() - let incomplete: Uint8Array | undefined = undefined - return Effect.flatMap( - upstream, - function loop(chunk): Pull.Pull, IE | MsgPackError, Done> { - const out = Arr.empty() - for (let i = 0; i < chunk.length; i++) { - let buf = chunk[i] - if (incomplete !== undefined) { - const prev = buf - buf = new Uint8Array(incomplete.length + buf.length) - buf.set(incomplete) - buf.set(prev, incomplete.length) - incomplete = undefined - } - try { - out.push(...unpackr.unpackMultiple(buf)) - } catch (cause) { - const error: any = cause - if (error.incomplete) { - incomplete = buf.subarray(error.lastPosition) - if (error.values) { - out.push(...error.values) - } - } else { - return Effect.fail(new MsgPackError({ kind: "Unpack", cause })) + let incomplete: { + readonly bytes: Uint8Array + readonly cause: unknown + } | undefined = undefined + + const pull = Effect.suspend((): Pull.Pull, IE | MsgPackError, Done> => + Pull.matchEffect(upstream, { + onSuccess: loop, + onFailure: Effect.failCause, + onDone: (done): Pull.Pull => + incomplete === undefined + ? Cause.done(done) + : Effect.fail(new MsgPackError({ kind: "Unpack", cause: incomplete.cause })) + }) + ) + + function loop(chunk: Arr.NonEmptyReadonlyArray>): Pull.Pull< + Arr.NonEmptyReadonlyArray, + IE | MsgPackError, + Done + > { + const out = Arr.empty() + for (let i = 0; i < chunk.length; i++) { + let buf = chunk[i] + if (incomplete !== undefined) { + const prev = buf + buf = new Uint8Array(incomplete.bytes.length + buf.length) + buf.set(incomplete.bytes) + buf.set(prev, incomplete.bytes.length) + incomplete = undefined + } + try { + out.push(...unpackr.unpackMultiple(buf)) + } catch (cause) { + const error: any = cause + if (error.incomplete) { + incomplete = { bytes: buf.subarray(error.lastPosition), cause } + if (error.values) { + out.push(...error.values) } + } else { + return Effect.fail(new MsgPackError({ kind: "Unpack", cause })) } } - return Arr.isReadonlyArrayNonEmpty(out) ? Effect.succeed(out) : Effect.flatMap(upstream, loop) } - ) + return Arr.isReadonlyArrayNonEmpty(out) ? Effect.succeed(out) : pull + } + + return pull }) ) @@ -344,25 +361,29 @@ export const transformation: SchemaTransformation.Transformation< unknown, Uint8Array > = SchemaTransformation.transformOrFail({ - decode(e, _options) { + decode(e, options) { try { return Effect.succeed(Msgpackr.decode(e)) - } catch (cause) { + } catch { return Effect.fail( - new SchemaIssue.InvalidValue(Option.some(e), { - message: Predicate.hasProperty(cause, "message") ? String(cause.message) : String(cause) - }) + new SchemaIssue.InvalidValue( + { expected: "valid MessagePack bytes" }, + e, + options + ) ) } }, - encode(t, _options) { + encode(t, options) { try { return Effect.succeed(Msgpackr.encode(t) as Uint8Array) - } catch (cause) { + } catch { return Effect.fail( - new SchemaIssue.InvalidValue(Option.some(t), { - message: Predicate.hasProperty(cause, "message") ? String(cause.message) : String(cause) - }) + new SchemaIssue.InvalidValue( + { expected: "a MessagePack-serializable value" }, + t, + options + ) ) } } diff --git a/repos/effect/packages/effect/src/unstable/encoding/Ndjson.ts b/repos/effect/packages/effect/src/unstable/encoding/Ndjson.ts index 87bcce1974..ce9e33b424 100644 --- a/repos/effect/packages/effect/src/unstable/encoding/Ndjson.ts +++ b/repos/effect/packages/effect/src/unstable/encoding/Ndjson.ts @@ -73,7 +73,15 @@ export const encodeString = (): Channel.Channel< Channel.fromTransform((upstream, _scope) => Effect.succeed(Effect.flatMap(upstream, (input) => { try { - return Effect.succeed(Arr.of(input.map((item) => JSON.stringify(item)).join("\n") + "\n")) + return Effect.succeed(Arr.of( + input.map((item) => { + const output = JSON.stringify(item) + if (output === undefined) { + throw new TypeError("Value cannot be represented as JSON") + } + return output + }).join("\n") + "\n" + )) } catch (cause) { return Effect.fail(new NdjsonError({ kind: "Pack", cause })) } diff --git a/repos/effect/packages/effect/src/unstable/encoding/Sse.ts b/repos/effect/packages/effect/src/unstable/encoding/Sse.ts index 4f87fb6f44..6c0c0fde05 100644 --- a/repos/effect/packages/effect/src/unstable/encoding/Sse.ts +++ b/repos/effect/packages/effect/src/unstable/encoding/Sse.ts @@ -22,6 +22,72 @@ import * as Result from "../../Result.ts" import * as Schema from "../../Schema.ts" import * as SchemaTransformation from "../../SchemaTransformation.ts" +const SseErrorTypeId = "~effect/encoding/Sse/SseError" + +/** + * Error reason raised when pending Server-Sent Events state exceeds the + * configured maximum size. + * + * @category errors + * @since 4.0.0 + */ +export class EventTooLarge extends Data.TaggedError("EventTooLarge")<{ + readonly maxEventSize: number +}> { + override get message() { + return `Pending SSE event exceeded the maximum size of ${this.maxEventSize}` + } +} + +/** + * Union of Server-Sent Events decoding error reasons. + * + * @category errors + * @since 4.0.0 + */ +export type SseErrorReason = EventTooLarge + +/** + * Error raised when decoding a Server-Sent Events stream fails. + * + * @category errors + * @since 4.0.0 + */ +export class SseError extends Data.TaggedError("SseError")<{ + readonly reason: SseErrorReason +}> { + /** + * Marks this value as an SSE decoding error. + * + * @since 4.0.0 + */ + readonly [SseErrorTypeId] = SseErrorTypeId + + /** + * Delegates the public message to the underlying SSE error reason. + * + * @since 4.0.0 + */ + override get message() { + return this.reason.message + } +} + +/** + * Options for decoding Server-Sent Events streams. + * + * @category decoding + * @since 4.0.0 + */ +export interface DecodeOptions { + /** + * Maximum number of string code units retained for a pending event. The default is 10 MiB. + */ + readonly maxEventSize?: number | undefined +} + +const defaultMaxEventSize = 10 * 1024 * 1024 + /** * Creates a channel that parses Server-Sent Events text chunks into `Event` values. * @@ -33,9 +99,9 @@ import * as SchemaTransformation from "../../SchemaTransformation.ts" * @category decoding * @since 4.0.0 */ -export const decode = (): Channel.Channel< +export const decode = (options?: DecodeOptions): Channel.Channel< NonEmptyReadonlyArray, - IE | Retry, + IE | Retry | SseError, Done, NonEmptyReadonlyArray, IE, @@ -51,16 +117,19 @@ export const decode = (): Channel.Channel< } else { buffer.push(event) } - }) + }, options) const pump = Effect.flatMap(upstream, (arr) => { for (let i = 0; i < arr.length; i++) { - parser.feed(arr[i]) + const error = parser.feed(arr[i]) + if (error !== undefined) { + return Effect.fail(error) + } } return Effect.void }) - return Effect.suspend(function loop(): Pull.Pull, IE | Retry, Done> { + return Effect.suspend(function loop(): Pull.Pull, IE | Retry | SseError, Done> { if (Arr.isArrayNonEmpty(buffer)) { const out = buffer buffer = [] @@ -80,15 +149,15 @@ export const decode = (): Channel.Channel< * @since 4.0.0 */ export interface EventCodec extends - Schema.Codec< + Schema.ConstraintCodec< any, { readonly id?: string | undefined readonly event?: string | undefined readonly data: string }, - any, - any + unknown, + unknown > {} @@ -108,10 +177,11 @@ export const decodeSchema = < IE, Done >( - schema: S + schema: S, + options?: DecodeOptions ): Channel.Channel< NonEmptyReadonlyArray, - IE | Retry | Schema.SchemaError, + IE | Retry | SseError | Schema.SchemaError, Done, NonEmptyReadonlyArray, IE, @@ -119,7 +189,7 @@ export const decodeSchema = < S["DecodingServices"] > => Channel.pipeTo( - decode(), + decode(options), ChannelSchema.decode(EventEncoded.pipe( Schema.decodeTo(schema) ))() @@ -137,14 +207,15 @@ export const decodeSchema = < * @since 4.0.0 */ export const decodeDataSchema = ( - schema: Schema.ConstraintDecoder + schema: Schema.ConstraintDecoder, + options?: DecodeOptions ): Channel.Channel< NonEmptyReadonlyArray<{ readonly event: string readonly id: string | undefined readonly data: Type }>, - IE | Retry | Schema.SchemaError, + IE | Retry | SseError | Schema.SchemaError, Done, NonEmptyReadonlyArray, IE, @@ -156,7 +227,7 @@ export const decodeDataSchema = ( data: Schema.fromJsonString(schema) }) return Channel.pipeTo( - decode(), + decode(options), Channel.map( ChannelSchema.decode(eventSchema)(), Arr.map((event) => ({ ...event, id: event.id })) @@ -170,20 +241,23 @@ export const decodeDataSchema = ( * **Details** * * Call `feed` with text chunks to parse `Event` and `Retry` values through the - * callback, and call `reset` to clear any buffered event state. + * callback, and call `reset` to clear any buffered event state. `feed` returns + * an `SseError` if the pending event exceeds `maxEventSize`. * * @category decoding * @since 4.0.0 */ -export function makeParser(onParse: (event: AnyEvent) => void): Parser { +export function makeParser(onParse: (event: AnyEvent) => void, options?: DecodeOptions): Parser { + const maxEventSize = options?.maxEventSize ?? defaultMaxEventSize + // Processing state let isFirstChunk: boolean let buffer: string let startingPosition: number let startingFieldLength: number + let discardTrailingNewline: boolean // Event state - let eventId: string | undefined let lastEventId: string | undefined let eventName: string | undefined let data: string @@ -196,19 +270,20 @@ export function makeParser(onParse: (event: AnyEvent) => void): Parser { buffer = "" startingPosition = 0 startingFieldLength = -1 + discardTrailingNewline = false - eventId = undefined + lastEventId = undefined eventName = undefined data = "" } - function feed(chunk: string): void { + function feed(chunk: string): SseError | undefined { buffer = buffer ? buffer + chunk : chunk - // Strip any UTF8 byte order mark (BOM) at the start of the stream. + // Strip any UTF-8 byte order mark (BOM) at the start of the stream. // Note that we do not strip any non - UTF8 BOM, as eventsource streams are // always decoded as UTF8 as per the specification. - if (isFirstChunk && hasBom(buffer)) { + if (isFirstChunk && buffer.startsWith(BOM)) { buffer = buffer.slice(BOM.length) } @@ -217,7 +292,6 @@ export function makeParser(onParse: (event: AnyEvent) => void): Parser { // Set up chunk-specific processing state const length = buffer.length let position = 0 - let discardTrailingNewline = false // Read the current buffer byte by byte while (position < length) { @@ -271,6 +345,12 @@ export function makeParser(onParse: (event: AnyEvent) => void): Parser { // portion of the buffer only buffer = buffer.slice(position) } + + if (buffer.length + data.length > maxEventSize) { + const error = new SseError({ reason: new EventTooLarge({ maxEventSize }) }) + reset() + return error + } } function parseEventStreamLine( @@ -284,12 +364,11 @@ export function makeParser(onParse: (event: AnyEvent) => void): Parser { if (data.length > 0) { onParse({ _tag: "Event", - id: eventId, - event: eventName ?? "message", + id: lastEventId, + event: eventName || "message", data: data.slice(0, -1) // remove trailing newline }) data = "" - eventId = undefined } eventName = undefined return @@ -316,35 +395,30 @@ export function makeParser(onParse: (event: AnyEvent) => void): Parser { } else if (field === "event") { eventName = value } else if (field === "id" && !value.includes("\u0000")) { - eventId = value lastEventId = value - } else if (field === "retry") { + } else if (field === "retry" && /^\d+$/.test(value)) { const retry = parseInt(value, 10) - if (!Number.isNaN(retry)) { - onParse(new Retry({ duration: Duration.millis(retry), lastEventId })) - } + onParse(new Retry({ duration: Duration.millis(retry), lastEventId })) } } } -const BOM = [239, 187, 191] - -function hasBom(buffer: string) { - return BOM.every((charCode: number, index: number) => buffer.charCodeAt(index) === charCode) -} +const BOM = "\uFEFF" /** * Stateful Server-Sent Events parser returned by `makeParser`. * * **Details** * - * `feed` accepts additional text chunks and `reset` clears buffered parser state. + * `feed` accepts additional text chunks and returns an `SseError` when the + * configured pending event size is exceeded. `reset` clears buffered parser + * state. * * @category decoding * @since 4.0.0 */ export interface Parser { - feed(chunk: string): void + feed(chunk: string): SseError | undefined reset(): void } @@ -580,9 +654,7 @@ export const encoder: Encoder = { if (event.event !== "message") { data += `event: ${event.event}\n` } - if (event.data !== "") { - data += `data: ${event.data.replace(/\n/g, "\ndata: ")}\n` - } + data += `data: ${event.data.replace(/\n/g, "\ndata: ")}\n` return data + "\n" } case "Retry": { diff --git a/repos/effect/packages/effect/src/unstable/encoding/Toml.ts b/repos/effect/packages/effect/src/unstable/encoding/Toml.ts new file mode 100644 index 0000000000..a6d09dc8fe --- /dev/null +++ b/repos/effect/packages/effect/src/unstable/encoding/Toml.ts @@ -0,0 +1,504 @@ +/** + * Parses TOML configuration files. + * + * The implementation covers the TOML values and table forms used by Effect's + * configuration-file CLI primitive while avoiding a generated parser runtime. + * + * @since 4.0.0 + */ + +/* + * The behavior is based on `toml` 4.1.2. + * + * Copyright (c) 2012 Michelle Tilley + * + * 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. + */ + +type Table = Record + +const hasOwn = Object.prototype.hasOwnProperty +const makeTable = (): Table => Object.create(null) +const isTable = (value: unknown): value is Table => + typeof value === "object" && value !== null && !Array.isArray(value) && !(value instanceof Date) + +class TomlParser { + readonly root = makeTable() + private readonly input: string + private current = this.root + private index = 0 + private line = 1 + private column = 1 + private readonly explicitTables = new Set() + + constructor(input: string) { + this.input = input + } + + parse(): Table { + while (true) { + this.skipDocumentWhitespace() + if (this.done) { + return this.root + } + if (this.peek() === "[") { + this.parseHeader() + } else { + const keys = this.parseKeyPath("=") + this.skipInlineWhitespace() + this.expect("=") + this.skipInlineWhitespace() + this.assign(this.current, keys, this.parseValue()) + this.finishStatement() + } + } + } + + private parseHeader(): void { + this.expect("[") + const array = this.peek() === "[" + if (array) { + this.advance() + } + this.skipInlineWhitespace() + const path = this.parseKeyPath("]") + this.skipInlineWhitespace() + this.expect("]") + if (array) { + this.expect("]") + } + this.finishStatement() + + const pathKey = JSON.stringify(path) + if (!array && this.explicitTables.has(pathKey)) { + this.fail(`Cannot redefine table '${path.join(".")}'`) + } + if (!array) { + this.explicitTables.add(pathKey) + } + this.current = this.resolveTable(path, array) + } + + private resolveTable(path: ReadonlyArray, array: boolean): Table { + let table = this.root + for (let index = 0; index < path.length; index++) { + const key = path[index] + const last = index === path.length - 1 + let value = table[key] + + if (last && array) { + if (value === undefined) { + value = [] + table[key] = value + } + if (!Array.isArray(value)) { + this.fail(`Cannot redefine existing key '${path.slice(0, index + 1).join(".")}'`) + } + const next = makeTable() + value.push(next) + return next + } + + if (value === undefined) { + value = makeTable() + table[key] = value + } + if (Array.isArray(value)) { + value = value[value.length - 1] + } + if (!isTable(value)) { + this.fail(`Cannot redefine existing key '${path.slice(0, index + 1).join(".")}'`) + } + table = value + } + return table + } + + private assign(target: Table, keys: ReadonlyArray, value: unknown): void { + let table = target + for (let index = 0; index < keys.length - 1; index++) { + const key = keys[index] + const existing = table[key] + if (existing === undefined) { + const child = makeTable() + table[key] = child + table = child + } else if (isTable(existing)) { + table = existing + } else { + this.fail(`Cannot redefine existing key '${keys.slice(0, index + 1).join(".")}'`) + } + } + const key = keys[keys.length - 1] + if (hasOwn.call(table, key)) { + this.fail(`Cannot redefine existing key '${keys.join(".")}'`) + } + table[key] = value + } + + private parseKeyPath(stop: "=" | "]"): Array { + const keys: Array = [] + while (true) { + this.skipInlineWhitespace() + const character = this.peek() + let key: string + if (character === "\"") { + key = this.parseBasicString(false) + } else if (character === "'") { + key = this.parseLiteralString(false) + } else { + const start = this.index + while (/[A-Za-z0-9_-]/.test(this.peek())) { + this.advance() + } + key = this.input.slice(start, this.index) + } + if (key.length === 0) { + this.fail("Expected a key") + } + keys.push(key) + this.skipInlineWhitespace() + if (this.peek() === ".") { + this.advance() + continue + } + if (this.peek() !== stop) { + this.fail(`Expected '${stop}'`) + } + return keys + } + } + + private parseValue(): unknown { + const character = this.peek() + if (character === "\"") { + return this.parseBasicString(this.input.startsWith("\"\"\"", this.index)) + } + if (character === "'") { + return this.parseLiteralString(this.input.startsWith("'''", this.index)) + } + if (character === "[") { + return this.parseArray() + } + if (character === "{") { + return this.parseInlineTable() + } + + const spaceSeparatedDateTime = /^(\d{4}-\d{2}-\d{2}) (\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:[Zz]|[+-]\d{2}:\d{2})?)/ + .exec(this.input.slice(this.index)) + if (spaceSeparatedDateTime !== null) { + this.advance(spaceSeparatedDateTime[0].length) + return this.parseDate(`${spaceSeparatedDateTime[1]}T${spaceSeparatedDateTime[2]}`)! + } + + const start = this.index + while (!this.done && !/[\s,#\]}]/.test(this.peek())) { + this.advance() + } + const token = this.input.slice(start, this.index) + if (token === "true") return true + if (token === "false") return false + if (token.length === 0) this.fail("Expected a value") + + const date = this.parseDate(token) + if (date !== undefined) { + return date + } + + const number = this.parseNumber(token) + if (number !== undefined) { + return number + } + this.fail(`Invalid value '${token}'`) + } + + private parseDate(token: string): Date | string | undefined { + const date = "\\d{4}-\\d{2}-\\d{2}" + const time = "\\d{2}:\\d{2}:\\d{2}(?:\\.\\d+)?" + if (new RegExp(`^${date}[Tt]${time}(?:[Zz]|[+-]\\d{2}:\\d{2})$`).test(token)) { + const value = new Date(token.replace("t", "T").replace("z", "Z")) + if (Number.isNaN(value.getTime())) { + this.fail(`Invalid date-time '${token}'`) + } + return value + } + if ( + new RegExp(`^${date}[Tt]${time}$`).test(token) || + new RegExp(`^${date}$`).test(token) || + new RegExp(`^${time}$`).test(token) + ) { + return token.replace("t", "T") + } + return undefined + } + + private parseNumber(token: string): number | undefined { + const normalized = token.replace(/_/g, "") + if (/^[+-]?(?:inf|nan)$/.test(token)) { + if (normalized.endsWith("nan")) return Number.NaN + return normalized[0] === "-" ? Number.NEGATIVE_INFINITY : Number.POSITIVE_INFINITY + } + if (/^0x[0-9A-Fa-f](?:_?[0-9A-Fa-f])*$/.test(token)) { + return Number.parseInt(normalized.slice(2), 16) + } + if (/^0o[0-7](?:_?[0-7])*$/.test(token)) { + return Number.parseInt(normalized.slice(2), 8) + } + if (/^0b[01](?:_?[01])*$/.test(token)) { + return Number.parseInt(normalized.slice(2), 2) + } + if (/^[+-]?(?:0|[1-9](?:_?\d)*)$/.test(token)) { + return Number(normalized) + } + if ( + /^[+-]?(?:(?:0|[1-9](?:_?\d)*)\.\d(?:_?\d)*(?:[eE][+-]?\d(?:_?\d)*)?|(?:0|[1-9](?:_?\d)*)[eE][+-]?\d(?:_?\d)*)$/ + .test( + token + ) + ) { + return Number(normalized) + } + return undefined + } + + private parseBasicString(multiline: boolean): string { + this.expect("\"") + if (multiline) { + this.expect("\"") + this.expect("\"") + if (this.peek() === "\n") this.advance() + } + let output = "" + while (!this.done) { + if (multiline && this.input.startsWith("\"\"\"", this.index)) { + this.advance(3) + return output + } + const character = this.peek() + if (!multiline && character === "\"") { + this.advance() + return output + } + if (!multiline && (character === "\n" || character === "\r")) { + this.fail("Basic strings cannot contain newlines") + } + if (character !== "\\") { + output += character + this.advance() + continue + } + + this.advance() + if (multiline && /[ \t\r\n]/.test(this.peek())) { + while (/[ \t]/.test(this.peek())) this.advance() + if (this.peek() !== "\n" && this.peek() !== "\r") { + this.fail("Invalid multiline string continuation") + } + while (/[ \t\r\n]/.test(this.peek())) this.advance() + continue + } + const escape = this.peek() + this.advance() + const escapes: Record = { + b: "\b", + t: "\t", + n: "\n", + f: "\f", + r: "\r", + "\"": "\"", + "\\": "\\" + } + if (hasOwn.call(escapes, escape)) { + output += escapes[escape] + } else if (escape === "u" || escape === "U") { + const length = escape === "u" ? 4 : 8 + const hex = this.input.slice(this.index, this.index + length) + if (!new RegExp(`^[0-9A-Fa-f]{${length}}$`).test(hex)) { + this.fail("Invalid unicode escape") + } + output += String.fromCodePoint(Number.parseInt(hex, 16)) + this.advance(length) + } else { + this.fail(`Invalid escape '\\${escape}'`) + } + } + this.fail("Unterminated basic string") + } + + private parseLiteralString(multiline: boolean): string { + this.expect("'") + if (multiline) { + this.expect("'") + this.expect("'") + if (this.peek() === "\n") this.advance() + } + const start = this.index + while (!this.done) { + if (multiline && this.input.startsWith("'''", this.index)) { + const output = this.input.slice(start, this.index) + this.advance(3) + return output + } + if (!multiline && this.peek() === "'") { + const output = this.input.slice(start, this.index) + this.advance() + return output + } + if (!multiline && (this.peek() === "\n" || this.peek() === "\r")) { + this.fail("Literal strings cannot contain newlines") + } + this.advance() + } + this.fail("Unterminated literal string") + } + + private parseArray(): Array { + this.expect("[") + const output: Array = [] + while (true) { + this.skipArrayWhitespace() + if (this.peek() === "]") { + this.advance() + return output + } + output.push(this.parseValue()) + this.skipArrayWhitespace() + if (this.peek() === "]") { + this.advance() + return output + } + this.expect(",") + } + } + + private parseInlineTable(): Table { + this.expect("{") + const output = makeTable() + this.skipInlineWhitespace() + if (this.peek() === "}") { + this.advance() + return output + } + while (true) { + const keys = this.parseKeyPath("=") + this.skipInlineWhitespace() + this.expect("=") + this.skipInlineWhitespace() + this.assign(output, keys, this.parseValue()) + this.skipInlineWhitespace() + if (this.peek() === "}") { + this.advance() + return output + } + this.expect(",") + this.skipInlineWhitespace() + if (this.peek() === "}") { + this.fail("Inline tables cannot end with a trailing comma") + } + } + } + + private skipDocumentWhitespace(): void { + while (!this.done) { + if (/[ \t\r\n]/.test(this.peek())) { + this.advance() + } else if (this.peek() === "#") { + this.skipComment() + } else { + return + } + } + } + + private skipInlineWhitespace(): void { + while (this.peek() === " " || this.peek() === "\t") { + this.advance() + } + } + + private skipArrayWhitespace(): void { + while (!this.done) { + if (/[ \t\r\n]/.test(this.peek())) { + this.advance() + } else if (this.peek() === "#") { + this.skipComment() + } else { + return + } + } + } + + private finishStatement(): void { + this.skipInlineWhitespace() + if (this.peek() === "#") { + this.skipComment() + } + if (!this.done && this.peek() !== "\n" && this.peek() !== "\r") { + this.fail("Expected the end of the line") + } + } + + private skipComment(): void { + while (!this.done && this.peek() !== "\n") { + this.advance() + } + } + + private expect(character: string): void { + if (this.peek() !== character) { + this.fail(`Expected '${character}'`) + } + this.advance() + } + + private advance(count = 1): void { + for (let offset = 0; offset < count; offset++) { + if (this.input[this.index] === "\n") { + this.line++ + this.column = 1 + } else { + this.column++ + } + this.index++ + } + } + + private peek(): string { + return this.input[this.index] ?? "" + } + + private get done(): boolean { + return this.index >= this.input.length + } + + private fail(message: string): never { + throw new SyntaxError(`${message} at line ${this.line}, column ${this.column}`) + } +} + +/** + * Parses a TOML document into a null-prototype record. + * + * Offset date-times are represented by `Date`, matching the package this + * parser replaces. Local dates and times remain strings. + * + * @category decoding + * @since 4.0.0 + */ +export const parse = (input: string): Record => new TomlParser(input.replace(/^\uFEFF/, "")).parse() diff --git a/repos/effect/packages/effect/src/unstable/encoding/Yaml.ts b/repos/effect/packages/effect/src/unstable/encoding/Yaml.ts new file mode 100644 index 0000000000..750db7b2f9 --- /dev/null +++ b/repos/effect/packages/effect/src/unstable/encoding/Yaml.ts @@ -0,0 +1,541 @@ +/** + * Parses YAML configuration files. + * + * This is a focused YAML 1.2 configuration parser. It supports block and flow + * collections, quoted and block scalars, anchors, and aliases. + * + * @since 4.0.0 + */ + +/* + * The behavior is based on `yaml` 2.9.0. + * + * Copyright Eemeli Aro + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +type YamlRecord = Record + +type Line = { + readonly raw: string + readonly text: string + readonly indent: number + readonly number: number +} + +const hasOwn = Object.prototype.hasOwnProperty + +const setProperty = (record: YamlRecord, key: string, value: unknown): void => { + Object.defineProperty(record, key, { + configurable: true, + enumerable: true, + writable: true, + value + }) +} + +const stripComment = (input: string): string => { + let quote: "'" | "\"" | undefined + let escaped = false + for (let index = 0; index < input.length; index++) { + const character = input[index] + if (quote === "\"") { + if (escaped) { + escaped = false + } else if (character === "\\") { + escaped = true + } else if (character === quote) { + quote = undefined + } + } else if (quote === "'") { + if (character === quote && input[index + 1] === quote) { + index++ + } else if (character === quote) { + quote = undefined + } + } else if (character === "'" || character === "\"") { + quote = character + } else if (character === "#" && (index === 0 || /\s/.test(input[index - 1]))) { + return input.slice(0, index).trimEnd() + } + } + return input.trimEnd() +} + +const mappingSeparator = (input: string): number => { + let quote: "'" | "\"" | undefined + let escaped = false + let depth = 0 + for (let index = 0; index < input.length; index++) { + const character = input[index] + if (quote === "\"") { + if (escaped) escaped = false + else if (character === "\\") escaped = true + else if (character === quote) quote = undefined + } else if (quote === "'") { + if (character === quote && input[index + 1] === quote) index++ + else if (character === quote) quote = undefined + } else if (character === "'" || character === "\"") { + quote = character + } else if (character === "[" || character === "{") { + depth++ + } else if (character === "]" || character === "}") { + depth-- + } else if (character === ":" && depth === 0 && (input[index + 1] === undefined || /\s/.test(input[index + 1]))) { + return index + } + } + return -1 +} + +const parseDoubleQuoted = (input: string): string => { + if (!input.endsWith("\"") || input.length < 2) { + throw new SyntaxError("Unterminated double-quoted YAML string") + } + let output = "" + for (let index = 1; index < input.length - 1; index++) { + const character = input[index] + if (character !== "\\") { + output += character + continue + } + const escape = input[++index] + const escapes: Record = { + "0": "\0", + a: "\x07", + b: "\b", + t: "\t", + n: "\n", + v: "\v", + f: "\f", + r: "\r", + e: "\x1b", + " ": " ", + "\"": "\"", + "/": "/", + "\\": "\\", + N: "\u0085", + _: "\u00a0", + L: "\u2028", + P: "\u2029" + } + if (hasOwn.call(escapes, escape)) { + output += escapes[escape] + continue + } + if (escape === "x" || escape === "u" || escape === "U") { + const length = escape === "x" ? 2 : escape === "u" ? 4 : 8 + const hex = input.slice(index + 1, index + 1 + length) + if (!new RegExp(`^[0-9A-Fa-f]{${length}}$`).test(hex)) { + throw new SyntaxError("Invalid unicode escape in YAML string") + } + output += String.fromCodePoint(Number.parseInt(hex, 16)) + index += length + continue + } + throw new SyntaxError(`Invalid YAML escape '\\${escape}'`) + } + return output +} + +const parseScalar = (input: string): unknown => { + const value = input.trim() + if (value.length === 0) return null + if (value.startsWith("\"") && value.endsWith("\"")) { + return parseDoubleQuoted(value) + } + if (value.startsWith("'") && value.endsWith("'")) { + return value.slice(1, -1).replace(/''/g, "'") + } + if (/^(?:null|~)$/i.test(value)) return null + if (/^true$/i.test(value)) return true + if (/^false$/i.test(value)) return false + if (/^[+-]?\.inf$/i.test(value)) return value[0] === "-" ? Number.NEGATIVE_INFINITY : Number.POSITIVE_INFINITY + if (/^\.nan$/i.test(value)) return Number.NaN + + const normalized = value.replace(/_/g, "") + if (/^[+-]?0x[0-9a-f]+$/i.test(normalized)) { + const sign = normalized[0] === "-" ? -1 : 1 + return sign * Number.parseInt(normalized.replace(/^[+-]?0x/i, ""), 16) + } + if (/^[+-]?0o[0-7]+$/i.test(normalized)) { + const sign = normalized[0] === "-" ? -1 : 1 + return sign * Number.parseInt(normalized.replace(/^[+-]?0o/i, ""), 8) + } + if ( + /^[+-]?(?:0|[1-9]\d*)$/.test(normalized) || + /^[+-]?(?:(?:0|[1-9]\d*)?\.\d+|(?:0|[1-9]\d*)\.?\d*[eE][+-]?\d+)$/.test(normalized) + ) { + return Number(normalized) + } + return value +} + +const parseKey = (input: string): string => { + const value = parseScalar(input) + return value === null || value === undefined ? "" : String(value) +} + +class FlowParser { + private readonly input: string + private readonly anchors: ReadonlyMap + private index = 0 + + constructor(input: string, anchors: ReadonlyMap) { + this.input = input + this.anchors = anchors + } + + parse(): unknown { + const value = this.parseValue() + this.skipWhitespace() + if (this.index !== this.input.length) { + this.fail("Unexpected flow collection content") + } + return value + } + + private parseValue(): unknown { + this.skipWhitespace() + const character = this.peek() + if (character === "[") return this.parseSequence() + if (character === "{") return this.parseMapping() + if (character === "\"" || character === "'") return parseScalar(this.readQuoted(character)) + if (character === "*") { + this.index++ + const name = this.readUntil(/[,\]}\s]/) + if (!this.anchors.has(name)) this.fail(`Unknown alias '*${name}'`) + return this.anchors.get(name) + } + return parseScalar(this.readUntil(/[,\]}]/).trim()) + } + + private parseSequence(): Array { + this.expect("[") + const output: Array = [] + this.skipWhitespace() + if (this.peek() === "]") { + this.index++ + return output + } + while (true) { + output.push(this.parseValue()) + this.skipWhitespace() + if (this.peek() === "]") { + this.index++ + return output + } + this.expect(",") + this.skipWhitespace() + if (this.peek() === "]") { + this.index++ + return output + } + } + } + + private parseMapping(): YamlRecord { + this.expect("{") + const output: YamlRecord = {} + this.skipWhitespace() + if (this.peek() === "}") { + this.index++ + return output + } + while (true) { + const key = this.parseKey() + this.skipWhitespace() + this.expect(":") + if (hasOwn.call(output, key)) this.fail(`Duplicate key '${key}'`) + setProperty(output, key, this.parseValue()) + this.skipWhitespace() + if (this.peek() === "}") { + this.index++ + return output + } + this.expect(",") + this.skipWhitespace() + if (this.peek() === "}") { + this.index++ + return output + } + } + } + + private parseKey(): string { + this.skipWhitespace() + const character = this.peek() + if (character === "\"" || character === "'") { + return parseKey(this.readQuoted(character)) + } + return this.readUntil(/:/).trim() + } + + private readQuoted(quote: string): string { + const start = this.index++ + let escaped = false + while (this.index < this.input.length) { + const character = this.input[this.index++] + if (quote === "\"" && escaped) escaped = false + else if (quote === "\"" && character === "\\") escaped = true + else if (quote === "'" && character === quote && this.input[this.index] === quote) this.index++ + else if (character === quote) return this.input.slice(start, this.index) + } + this.fail("Unterminated quoted scalar") + } + + private readUntil(stop: RegExp): string { + const start = this.index + while (this.index < this.input.length && !stop.test(this.peek())) this.index++ + return this.input.slice(start, this.index) + } + + private skipWhitespace(): void { + while (/\s/.test(this.peek())) this.index++ + } + + private expect(character: string): void { + if (this.peek() !== character) this.fail(`Expected '${character}'`) + this.index++ + } + + private peek(): string { + return this.input[this.index] ?? "" + } + + private fail(message: string): never { + throw new SyntaxError(`${message} at flow offset ${this.index}`) + } +} + +class YamlParser { + private readonly lines: ReadonlyArray + private index = 0 + private readonly anchors = new Map() + + constructor(lines: ReadonlyArray) { + this.lines = lines + } + + parse(): unknown { + this.skipIgnored() + if (this.index >= this.lines.length) return null + const value = this.parseNode(this.lines[this.index].indent) + this.skipIgnored() + if (this.index < this.lines.length) { + this.fail(this.lines[this.index], "Unexpected content") + } + return value + } + + private parseNode(indent: number): unknown { + this.skipIgnored() + const line = this.lines[this.index] + if (line === undefined) return null + if (line.indent !== indent) this.fail(line, `Expected indentation of ${indent} spaces`) + if (line.text === "-" || line.text.startsWith("- ")) return this.parseSequence(indent) + if (mappingSeparator(line.text) !== -1) return this.parseMapping(indent) + this.index++ + return this.parseInlineValue(line.text) + } + + private parseMapping(indent: number): YamlRecord { + const output: YamlRecord = {} + while (true) { + this.skipIgnored() + const line = this.lines[this.index] + if (line === undefined || line.indent < indent) return output + if (line.indent > indent) this.fail(line, `Unexpected indentation of ${line.indent} spaces`) + const separator = mappingSeparator(line.text) + if (separator === -1) return output + this.index++ + this.parseMappingEntry(output, line.text, separator, indent, line) + } + } + + private parseMappingEntry( + output: YamlRecord, + text: string, + separator: number, + indent: number, + line: Line + ): void { + const key = parseKey(text.slice(0, separator).trim()) + const rawValue = text.slice(separator + 1).trim() + const value = this.parseNodeValue(rawValue, indent) + if (hasOwn.call(output, key)) this.fail(line, `Duplicate key '${key}'`) + setProperty(output, key, value) + } + + private parseSequence(indent: number): Array { + const output: Array = [] + while (true) { + this.skipIgnored() + const line = this.lines[this.index] + if (line === undefined || line.indent < indent) return output + if (line.indent > indent) this.fail(line, `Unexpected indentation of ${line.indent} spaces`) + if (line.text !== "-" && !line.text.startsWith("- ")) return output + this.index++ + const item = line.text.slice(1).trimStart() + const separator = mappingSeparator(item) + if (separator === -1) { + output.push(this.parseNodeValue(item, indent)) + continue + } + + const mapping: YamlRecord = {} + this.parseMappingEntry(mapping, item, separator, indent + 2, line) + while (true) { + this.skipIgnored() + const continuation = this.lines[this.index] + if (continuation === undefined || continuation.indent <= indent) break + if (continuation.indent !== indent + 2) { + this.fail(continuation, `Expected indentation of ${indent + 2} spaces`) + } + const nextSeparator = mappingSeparator(continuation.text) + if (nextSeparator === -1) this.fail(continuation, "Expected a mapping entry") + this.index++ + this.parseMappingEntry(mapping, continuation.text, nextSeparator, indent + 2, continuation) + } + output.push(mapping) + } + } + + private parseNodeValue(rawValue: string, parentIndent: number): unknown { + let value = rawValue + let anchor: string | undefined + const anchorMatch = /^&([^\s,[\]{}]+)(?:\s+(.*))?$/.exec(value) + if (anchorMatch !== null) { + anchor = anchorMatch[1] + value = anchorMatch[2] ?? "" + } + + let parsed: unknown + if (value.length === 0) { + this.skipIgnored() + const next = this.lines[this.index] + parsed = next !== undefined && next.indent > parentIndent ? this.parseNode(next.indent) : null + } else if (/^[|>](?:[1-9]?[+-]?|[+-]?[1-9]?)$/.test(value)) { + parsed = this.parseBlockScalar(value, parentIndent) + } else { + parsed = this.parseInlineValue(value) + } + if (anchor !== undefined) this.anchors.set(anchor, parsed) + return parsed + } + + private parseInlineValue(value: string): unknown { + if (value.startsWith("*")) { + const name = value.slice(1).trim() + if (!this.anchors.has(name)) throw new SyntaxError(`Unknown YAML alias '*${name}'`) + return this.anchors.get(name) + } + if (value.startsWith("[") || value.startsWith("{")) { + return new FlowParser(value, this.anchors).parse() + } + return parseScalar(value) + } + + private parseBlockScalar(indicator: string, parentIndent: number): string { + const style = indicator[0] + const chomp = indicator.includes("-") ? "strip" : indicator.includes("+") ? "keep" : "clip" + const explicitIndent = Number.parseInt(indicator.replace(/[^1-9]/g, ""), 10) + const start = this.index + let end = start + let contentIndent = Number.isNaN(explicitIndent) ? Number.POSITIVE_INFINITY : parentIndent + explicitIndent + + while (end < this.lines.length) { + const line = this.lines[end] + if (line.raw.trim().length !== 0 && line.indent <= parentIndent) break + if (line.raw.trim().length !== 0) contentIndent = Math.min(contentIndent, line.indent) + end++ + } + if (contentIndent === Number.POSITIVE_INFINITY) contentIndent = parentIndent + 1 + + const content: Array = [] + for (let index = start; index < end; index++) { + const line = this.lines[index] + if (line.raw.trim().length === 0) { + content.push("") + } else if (line.indent < contentIndent) { + this.fail(line, `Expected block scalar indentation of ${contentIndent} spaces`) + } else { + content.push(line.raw.slice(contentIndent)) + } + } + this.index = end + + let output = "" + if (style === "|") { + output = content.join("\n") + } else { + for (let index = 0; index < content.length; index++) { + output += content[index] + if (index < content.length - 1) { + output += content[index].length === 0 || content[index + 1].length === 0 ? "\n" : " " + } + } + } + if (chomp === "keep") return `${output}\n` + output = output.replace(/\n+$/, "") + return chomp === "strip" ? output : `${output}\n` + } + + private skipIgnored(): void { + while (this.index < this.lines.length) { + const line = this.lines[this.index] + if (line.text.length === 0 || line.text.startsWith("%") || line.text === "---") { + this.index++ + } else if (line.text === "...") { + this.index++ + while (this.index < this.lines.length && this.lines[this.index].text.length === 0) this.index++ + if (this.index < this.lines.length) { + this.fail(this.lines[this.index], "Multiple YAML documents are not supported") + } + } else { + return + } + } + } + + private fail(line: Line, message: string): never { + throw new SyntaxError(`${message} at line ${line.number}`) + } +} + +/** + * Parses one YAML document into JavaScript values. + * + * The core YAML 1.2 scalar schema is used, so booleans, nulls, and numbers are + * decoded while date-like values remain strings. + * + * @category decoding + * @since 4.0.0 + */ +export const parse = (input: string): unknown => { + const source = input.replace(/^\uFEFF/, "").replace(/\r\n?/g, "\n") + const lines = source.split("\n").map((raw, index): Line => { + const indentation = /^( *)/.exec(raw)![1].length + if (raw.slice(0, indentation + 1).includes("\t")) { + throw new SyntaxError(`Tabs cannot be used for YAML indentation at line ${index + 1}`) + } + return { + raw, + text: stripComment(raw.slice(indentation)), + indent: indentation, + number: index + 1 + } + }) + return new YamlParser(lines).parse() +} diff --git a/repos/effect/packages/effect/src/unstable/encoding/index.ts b/repos/effect/packages/effect/src/unstable/encoding/index.ts index d6c7fe41b9..da43977da5 100644 --- a/repos/effect/packages/effect/src/unstable/encoding/index.ts +++ b/repos/effect/packages/effect/src/unstable/encoding/index.ts @@ -4,6 +4,11 @@ // @barrel: Auto-generated exports. Do not edit manually. +/** + * @since 4.0.0 + */ +export * as Ini from "./Ini.ts" + /** * @since 4.0.0 */ @@ -18,3 +23,13 @@ export * as Ndjson from "./Ndjson.ts" * @since 4.0.0 */ export * as Sse from "./Sse.ts" + +/** + * @since 4.0.0 + */ +export * as Toml from "./Toml.ts" + +/** + * @since 4.0.0 + */ +export * as Yaml from "./Yaml.ts" diff --git a/repos/effect/packages/effect/src/unstable/eventlog/Event.ts b/repos/effect/packages/effect/src/unstable/eventlog/Event.ts index 4129f4c1a0..00dd6f329d 100644 --- a/repos/effect/packages/effect/src/unstable/eventlog/Event.ts +++ b/repos/effect/packages/effect/src/unstable/eventlog/Event.ts @@ -114,7 +114,7 @@ export interface AnyWithProps extends Any {} /** * Derives the handler service marker for an event definition. * - * @category models + * @category utility types * @since 4.0.0 */ export type ToService = A extends Event< @@ -128,7 +128,7 @@ export type ToService = A extends Event< /** * Extracts the tag string from an event definition. * - * @category models + * @category utility types * @since 4.0.0 */ export type Tag = A extends Event< @@ -142,7 +142,7 @@ export type Tag = A extends Event< /** * Extracts the error schema from an event definition. * - * @category models + * @category utility types * @since 4.0.0 */ export type ErrorSchema = A extends Event< @@ -156,7 +156,7 @@ export type ErrorSchema = A extends Event< /** * Decoded error value type for an event definition. * - * @category models + * @category utility types * @since 4.0.0 */ export type Error = Schema.Schema.Type> @@ -165,7 +165,7 @@ export type Error = Schema.Schema.Type> * Returns an event definition type whose error schema also includes the provided * error schema. * - * @category models + * @category utility types * @since 4.0.0 */ export type AddError = A extends Event< @@ -179,7 +179,7 @@ export type AddError = A extends Event< /** * Extracts the payload schema from an event definition. * - * @category models + * @category utility types * @since 4.0.0 */ export type PayloadSchema = A extends Event< @@ -193,7 +193,7 @@ export type PayloadSchema = A extends Event< /** * Extracts the payload schema for the event in a union with the specified tag. * - * @category models + * @category utility types * @since 4.0.0 */ export type PayloadSchemaWithTag = A extends Event< @@ -207,7 +207,7 @@ export type PayloadSchemaWithTag = A extends /** * Decoded payload value type for an event definition. * - * @category models + * @category utility types * @since 4.0.0 */ export type Payload = Schema.Schema.Type> @@ -220,7 +220,7 @@ export type Payload = Schema.Schema.Type> * The result contains `_tag` set to the event tag and `payload` set to the * decoded payload value. * - * @category models + * @category utility types * @since 4.0.0 */ export type TaggedPayload = A extends Event< @@ -237,7 +237,7 @@ export type TaggedPayload = A extends Event< /** * Extracts the success schema from an event definition. * - * @category models + * @category utility types * @since 4.0.0 */ export type SuccessSchema = A extends Event< @@ -251,7 +251,7 @@ export type SuccessSchema = A extends Event< /** * Decoded success value type for an event definition. * - * @category models + * @category utility types * @since 4.0.0 */ export type Success = Schema.Schema.Type> @@ -264,7 +264,7 @@ export type Success = Schema.Schema.Type> * This includes payload encoding services plus success and error decoding * services. * - * @category models + * @category utility types * @since 4.0.0 */ export type ServicesClient = A extends Event< @@ -286,7 +286,7 @@ export type ServicesClient = A extends Event< * This includes payload decoding services plus success and error encoding * services. * - * @category models + * @category utility types * @since 4.0.0 */ export type ServicesServer = A extends Event< @@ -304,7 +304,7 @@ export type ServicesServer = A extends Event< * All schema services required to encode and decode the payload, success, and * error schemas for an event definition. * - * @category models + * @category utility types * @since 4.0.0 */ export type Services = A extends Event< @@ -324,7 +324,7 @@ export type Services = A extends Event< /** * Extracts the event definition with the specified tag from an event union. * - * @category models + * @category utility types * @since 4.0.0 */ export type WithTag = Extract @@ -332,7 +332,7 @@ export type WithTag = Extract = Exclude @@ -340,7 +340,7 @@ export type ExcludeTag = Exclude = Payload> @@ -348,7 +348,7 @@ export type PayloadWithTag = Payload = Success> @@ -356,7 +356,7 @@ export type SuccessWithTag = Success = Error> @@ -365,7 +365,7 @@ export type ErrorWithTag = Error = ServicesClient> diff --git a/repos/effect/packages/effect/src/unstable/eventlog/EventGroup.ts b/repos/effect/packages/effect/src/unstable/eventlog/EventGroup.ts index d814c006c0..d5c475a36c 100644 --- a/repos/effect/packages/effect/src/unstable/eventlog/EventGroup.ts +++ b/repos/effect/packages/effect/src/unstable/eventlog/EventGroup.ts @@ -100,7 +100,7 @@ export type AnyWithProps = EventGroup /** * Derives the handler service markers required for all events in an event group. * - * @category models + * @category utility types * @since 4.0.0 */ export type ToService = A extends EventGroup ? Event.ToService<_Events> @@ -109,7 +109,7 @@ export type ToService = A extends EventGroup ? Event.ToService /** * Extracts the union of event definitions contained in an event group. * - * @category models + * @category utility types * @since 4.0.0 */ export type Events = Group extends EventGroup ? _Events @@ -118,7 +118,7 @@ export type Events = Group extends EventGroup ? _Events /** * Client-side schema services required by all events in an event group. * - * @category models + * @category utility types * @since 4.0.0 */ export type ServicesClient = Event.ServicesClient> @@ -126,7 +126,7 @@ export type ServicesClient = Event.ServicesClient> /** * Server-side schema services required by all events in an event group. * - * @category models + * @category utility types * @since 4.0.0 */ export type ServicesServer = Event.ServicesServer> diff --git a/repos/effect/packages/effect/src/unstable/eventlog/EventJournal.ts b/repos/effect/packages/effect/src/unstable/eventlog/EventJournal.ts index f89b50df5b..c80bf95bc2 100644 --- a/repos/effect/packages/effect/src/unstable/eventlog/EventJournal.ts +++ b/repos/effect/packages/effect/src/unstable/eventlog/EventJournal.ts @@ -34,7 +34,7 @@ import type { StoreId } from "./EventLogMessage.ts" * The service writes local entries, imports entries from remote journals, exposes * a stream of local changes, and provides per-store locking. * - * @category context + * @category services * @since 4.0.0 */ export class EventJournal extends Context.Service Effect.Effect /** - * Retrieve the last known sequence number for a remote source. + * Retrieve the first unused sequence number for a remote source. */ readonly nextRemoteSequence: (remoteId: RemoteId) => Effect.Effect @@ -149,7 +149,7 @@ export const RemoteIdTypeId: RemoteIdTypeId = "effect/eventlog/EventJournal/Remo /** * Branded byte identifier for a remote event journal source. * - * @category remote + * @category models * @since 4.0.0 */ export type RemoteId = Uint8Array & Brand @@ -157,7 +157,7 @@ export type RemoteId = Uint8Array & Brand /** * Schema for branded remote event journal identifiers. * - * @category remote + * @category schemas * @since 4.0.0 */ export const RemoteId = Schema.Uint8Array.pipe(Schema.brand(RemoteIdTypeId)) @@ -175,7 +175,7 @@ export const RemoteId = Schema.Uint8Array.pipe(Schema.brand(RemoteIdTypeId)) * This is unsafe because the generated UUID bytes are cast to the brand without * schema validation. * - * @category remote + * @category unsafe * @since 4.0.0 */ export const makeRemoteIdUnsafe = (): RemoteId => Uuid.v4({}, new globalThis.Uint8Array(16)) as RemoteId @@ -199,7 +199,7 @@ export type EntryIdTypeId = "effect/eventlog/EventJournal/EntryId" /** * Branded byte identifier for an event journal entry. * - * @category entry + * @category models * @since 4.0.0 */ export type EntryId = Uint8Array & Brand @@ -207,7 +207,7 @@ export type EntryId = Uint8Array & Brand /** * Schema for branded event journal entry identifiers. * - * @category entry + * @category schemas * @since 4.0.0 */ export const EntryId = (Schema.Uint8Array as Schema.instanceOf>).pipe( @@ -217,7 +217,7 @@ export const EntryId = (Schema.Uint8Array as Schema.instanceOf((a, b) => { @@ -243,7 +243,7 @@ export const EntryIdOrder = Order.make((a, b) => { * This is unsafe because the generated UUID bytes are cast to the brand without * schema validation. * - * @category entry + * @category unsafe * @since 4.0.0 */ export const makeEntryIdUnsafe = (options: { msecs?: number } = {}): EntryId => @@ -252,7 +252,7 @@ export const makeEntryIdUnsafe = (options: { msecs?: number } = {}): EntryId => /** * Extracts the millisecond timestamp encoded in a UUID v7 `EntryId`. * - * @category entry + * @category getters * @since 4.0.0 */ export const entryIdMillis = (entryId: EntryId): number => { @@ -269,7 +269,7 @@ export const entryIdMillis = (entryId: EntryId): number => { * An entry records its ID, event tag, primary key, and MessagePack-encoded * payload, with helpers for array MessagePack encoding and creation timestamps. * - * @category entry + * @category schemas * @since 4.0.0 */ export class Entry extends Schema.Class("effect/eventlog/EventJournal/Entry")({ @@ -341,11 +341,11 @@ export class Entry extends Schema.Class("effect/eventlog/EventJournal/Ent * * It pairs the remote sequence number with the journal entry payload. * - * @category entry + * @category schemas * @since 4.0.0 */ export class RemoteEntry extends Schema.Class("effect/eventlog/EventJournal/RemoteEntry")({ - remoteSequence: Schema.Number, + remoteSequence: Schema.Natural, entry: Entry }) {} @@ -357,7 +357,7 @@ export class RemoteEntry extends Schema.Class("effect/eventlog/Even * Entries, remote tracking state, and locks live only in the current process and * are lost when the service is discarded. * - * @category memory + * @category constructors * @since 4.0.0 */ export const makeMemory: Effect.Effect = Effect.gen(function*() { @@ -417,8 +417,8 @@ export const makeMemory: Effect.Effect = Effect.gen(fun for (const remoteEntry of options.entries) { if (byId.has(remoteEntry.entry.idString)) { duplicateEntries.push(remoteEntry.entry) - if (remoteEntry.remoteSequence > remote.sequence) { - remote.sequence = remoteEntry.remoteSequence + if (remoteEntry.remoteSequence >= remote.sequence) { + remote.sequence = remoteEntry.remoteSequence + 1 } continue } @@ -438,7 +438,7 @@ export const makeMemory: Effect.Effect = Effect.gen(fun if (entry !== undefined && entry.createdAtMillis > entryMillis) { continue } - for (let j = i + 2; j < journal.length; j++) { + for (let j = i + 1; j < journal.length; j++) { const scannedEntry = journal[j]! if (scannedEntry.event === originEntry.event && scannedEntry.primaryKey === originEntry.primaryKey) { conflicts.push(scannedEntry) @@ -451,11 +451,19 @@ export const makeMemory: Effect.Effect = Effect.gen(fun for (const remoteEntry of uncommittedRemotes) { journal.push(remoteEntry.entry) byId.set(remoteEntry.entry.idString, remoteEntry.entry) - if (remoteEntry.remoteSequence > remote.sequence) { - remote.sequence = remoteEntry.remoteSequence + remotes.forEach((target) => { + if (target !== remote) { + target.missing.push(remoteEntry.entry) + } + }) + if (remoteEntry.remoteSequence >= remote.sequence) { + remote.sequence = remoteEntry.remoteSequence + 1 } } journal.sort((a, b) => a.createdAtMillis - b.createdAtMillis) + remotes.forEach((remote) => { + remote.missing.sort((a, b) => a.createdAtMillis - b.createdAtMillis) + }) return { duplicateEntries } @@ -497,7 +505,7 @@ export const makeMemory: Effect.Effect = Effect.gen(fun * All journal data is stored in process memory and is not persisted across layer * lifetimes. * - * @category memory + * @category layers * @since 4.0.0 */ export const layerMemory: Layer.Layer = Layer.effect(EventJournal, makeMemory) @@ -511,7 +519,7 @@ export const layerMemory: Layer.Layer = Layer.effect(EventJournal, * browser database, publishes local changes, and requires `Scope` so the database * connection can be closed when the scope ends. * - * @category indexed db + * @category constructors * @since 4.0.0 */ export const makeIndexedDb = (options?: { @@ -772,7 +780,7 @@ const decodeEntryIdbArray = Schema.decodeUnknownEffect(EntryIdbArray) * Provides `EventJournal` using the IndexedDB-backed implementation created by * `makeIndexedDb`. * - * @category indexed db + * @category layers * @since 4.0.0 */ export const layerIndexedDb = (options?: { diff --git a/repos/effect/packages/effect/src/unstable/eventlog/EventLog.ts b/repos/effect/packages/effect/src/unstable/eventlog/EventLog.ts index db7b1779f8..faa5a3b0c1 100644 --- a/repos/effect/packages/effect/src/unstable/eventlog/EventLog.ts +++ b/repos/effect/packages/effect/src/unstable/eventlog/EventLog.ts @@ -13,6 +13,7 @@ import * as Context from "../../Context.ts" import * as Effect from "../../Effect.ts" import * as FiberMap from "../../FiberMap.ts" import { constant, identity } from "../../Function.ts" +import * as InternalRecord from "../../internal/record.ts" import * as Layer from "../../Layer.ts" import type { Pipeable } from "../../Pipeable.ts" import { pipeArguments } from "../../Pipeable.ts" @@ -159,7 +160,9 @@ export const layerRegistry = Layer.effect( }, registerReactivity: (keys) => Effect.sync(() => { - Object.assign(reactivityKeys, keys) + for (const [key, value] of Object.entries(keys)) { + InternalRecord.assignProperty(reactivityKeys, key, value) + } }), reactivityKeys }) @@ -202,7 +205,7 @@ export const SchemaTypeId: SchemaTypeId = "~effect/eventlog/EventLog/Schema" /** * Returns `true` when a value carries the `EventLogSchema` marker. * - * @category schemas + * @category guards * @since 4.0.0 */ export const isEventLogSchema = (u: unknown): u is EventLogSchema => @@ -407,7 +410,7 @@ export declare namespace Handlers { * * Defaults to the branded store id `"default"`. * - * @category models + * @category services * @since 4.0.0 */ export class CurrentStoreId extends Context.Reference("effect/eventlog/EventLog/CurrentStoreId", { @@ -498,7 +501,7 @@ const handlersProto = { handlers: { ...this.handlers, [tag]: { - event: this.group.events[tag], + event: Object.hasOwn(this.group.events, tag) ? this.group.events[tag] : undefined!, context: this.context, handler } @@ -547,7 +550,7 @@ export const group = ( const handlers = Effect.isEffect(result) ? (yield* (result as unknown as Effect.Effect>)) : (result as unknown as Handlers) - for (const tag in handlers.handlers) { + for (const tag of Object.keys(handlers.handlers)) { registry.registerHandlerUnsafe({ event: tag, handler: handlers.handlers[tag] }) } }) @@ -584,7 +587,7 @@ export const groupCompaction = ( yield* registry.registerCompaction({ events: Object.keys(group.events), effect: Effect.fnUntraced(function*({ entries, write }): Effect.fn.Return { - const isEventTag = (tag: string): tag is Event.Tag => tag in group.events + const isEventTag = (tag: string): tag is Event.Tag => Object.hasOwn(group.events, tag) const decodePayload = >(tag: Tag, payload: Uint8Array) => Schema.decodeUnknownEffect(group.events[tag].payloadMsgPack)(payload).pipe( Effect.updateContext((input) => Context.merge(services, input)), @@ -595,7 +598,7 @@ export const groupCompaction = ( tag: Tag, payload: Event.PayloadWithTag ): Effect.fn.Return["EncodingServices"]> { - const event = group.events[tag] + const event = Object.hasOwn(group.events, tag) ? group.events[tag] : undefined! const entry = new Entry({ id: makeEntryIdUnsafe({ msecs: timestamp }), event: tag, @@ -674,8 +677,8 @@ export const groupReactivity = ( yield* registry.registerReactivity(keys as Record.ReadonlyRecord>) return } - const obj: Record> = {} - for (const tag in group.events) { + const obj: Record> = Object.create(null) + for (const tag of Object.keys(group.events)) { obj[tag] = keys } yield* registry.registerReactivity(obj) @@ -741,7 +744,9 @@ export const makeReplayFromRemote = (options: { Effect.asVoid ) as any - const keys = options.reactivityKeys[entry.event] + const keys = Object.hasOwn(options.reactivityKeys, entry.event) + ? options.reactivityKeys[entry.event] + : undefined if (keys) { for (const key of keys) { options.reactivity.invalidateUnsafe({ @@ -780,7 +785,9 @@ const make = Effect.gen(function*() { const invalidateReactivityEntries = (entries: ReadonlyArray) => Effect.sync(() => { for (const entry of entries) { - const keys = registry.reactivityKeys[entry.event] + const keys = Object.hasOwn(registry.reactivityKeys, entry.event) + ? registry.reactivityKeys[entry.event] + : undefined if (!keys) { continue } @@ -854,9 +861,10 @@ const make = Effect.gen(function*() { Effect.scoped, Effect.catchCause(Effect.logError), Effect.repeat( - Schedule.exponential(200, 1.5).pipe( - Schedule.either(Schedule.spaced({ seconds: 10 })) - ) + Schedule.min([ + Schedule.exponential(200, 1.5), + Schedule.spaced({ seconds: 10 }) + ]) ), Effect.annotateLogs({ service: "EventLog", @@ -902,8 +910,11 @@ const make = Effect.gen(function*() { Effect.updateContext((input) => Context.merge(handler.context, input)), Effect.provideService(Identity, identity), Effect.tap(() => { - if (registry.reactivityKeys[entry.event]) { - for (const key of registry.reactivityKeys[entry.event]) { + const keys = Object.hasOwn(registry.reactivityKeys, entry.event) + ? registry.reactivityKeys[entry.event] + : undefined + if (keys) { + for (const key of keys) { reactivity.invalidateUnsafe({ [key]: [entry.primaryKey] }) @@ -1001,7 +1012,7 @@ export const layer = ( * The returned function delegates to the `EventLog` service and preserves each * event's success and error types. * - * @category client + * @category constructors * @since 4.0.0 */ export const makeClient = ( diff --git a/repos/effect/packages/effect/src/unstable/eventlog/EventLogEncryption.ts b/repos/effect/packages/effect/src/unstable/eventlog/EventLogEncryption.ts index 052628a728..053517f5b0 100644 --- a/repos/effect/packages/effect/src/unstable/eventlog/EventLogEncryption.ts +++ b/repos/effect/packages/effect/src/unstable/eventlog/EventLogEncryption.ts @@ -19,14 +19,15 @@ import type { Identity } from "./EventLog.ts" import { makeGetIdentityRootSecretMaterial } from "./internal/identityRootSecretDerivation.ts" /** - * Schema for an encrypted journal entry paired with the id of the original - * entry. + * Schema for an encrypted journal entry paired with its initialization vector + * and the id of the original entry. * * @category models * @since 4.0.0 */ export const EncryptedEntry = Schema.Struct({ entryId: EntryId, + iv: Transferable.Uint8Array, encryptedEntry: Transferable.Uint8Array }) @@ -46,7 +47,7 @@ export interface EncryptedRemoteEntry extends Schema.Schema.Type - ) => Effect.Effect<{ - readonly iv: Uint8Array - readonly encryptedEntries: ReadonlyArray> - }> + ) => Effect.Effect< + ReadonlyArray<{ + readonly iv: Uint8Array + readonly encryptedEntry: Uint8Array + }> + > readonly decrypt: ( identity: Identity["Service"], entries: ReadonlyArray @@ -104,22 +107,18 @@ export const makeEncryptionSubtle = (crypto: Crypto): Effect.Effect + return yield* Effect.promise(() => Promise.all( - data.map((entry) => - crypto.subtle.encrypt( + data.map((entry) => { + const iv = crypto.getRandomValues(new Uint8Array(12)) + return crypto.subtle.encrypt( { name: "AES-GCM", iv: toBufferSource(iv), tagLength: 128 }, key, toBufferSource(entry) - ) - ) + ).then((encryptedEntry) => ({ iv, encryptedEntry: new Uint8Array(encryptedEntry) })) + }) ) ) - return { - iv, - encryptedEntries: encryptedEntries.map((entry) => new Uint8Array(entry)) - } }), decrypt: Effect.fnUntraced(function*(identity, entries) { const key = (yield* getIdentityRootSecretMaterial(identity)).encryptionKey @@ -160,7 +159,7 @@ export const makeEncryptionSubtle = (crypto: Crypto): Effect.Effect = Layer.effect( diff --git a/repos/effect/packages/effect/src/unstable/eventlog/EventLogMessage.ts b/repos/effect/packages/effect/src/unstable/eventlog/EventLogMessage.ts index 0f5cb629c2..c9aec0b919 100644 --- a/repos/effect/packages/effect/src/unstable/eventlog/EventLogMessage.ts +++ b/repos/effect/packages/effect/src/unstable/eventlog/EventLogMessage.ts @@ -39,7 +39,7 @@ export const StoreIdTypeId: StoreIdTypeId = "effect/eventlog/EventLog/StoreId" /** * Branded string identifying a logical event-log store. * - * @category StoreId + * @category models * @since 4.0.0 */ export type StoreId = string & Brand @@ -47,7 +47,7 @@ export type StoreId = string & Brand /** * Schema for branded event-log store ids. * - * @category StoreId + * @category schemas * @since 4.0.0 */ export const StoreId = Schema.String.pipe(Schema.brand(StoreIdTypeId)) @@ -63,7 +63,7 @@ export const StoreId = Schema.String.pipe(Schema.brand(StoreIdTypeId)) * @category protocols * @since 4.0.0 */ -export class EventLogProtocolError extends Schema.TaggedErrorClass( +export class EventLogProtocolError extends Schema.TaggedError( "effect/eventlog/EventLogRemote/ProtocolError" )("EventLogProtocolError", { requestTag: Schema.String, @@ -163,8 +163,12 @@ export class SingleMessage */ export class ChunkedMessage extends Schema.TaggedClass("effect/eventlog/EventLogRemote/ChunkedMessage")("Chunked", { - id: Schema.Number, - part: Schema.Tuple([Schema.Number, Schema.Number]), + id: Schema.Int, + part: Schema.Tuple([Schema.Natural, Schema.Natural]).check( + Schema.makeFilter(([index, total]) => index < total, { + expected: "a chunk part with an index less than its total" + }) + ), data: Transferable.Uint8Array }) { @@ -221,6 +225,9 @@ export class ChunkedMessage } map.set(part.id, entry) } + if (entry.parts[index] !== undefined) { + return + } entry.parts[index] = part.data entry.count++ entry.bytes += part.data.byteLength @@ -254,8 +261,8 @@ export class WriteChunkedRpc extends Rpc.make("EventLog.WriteChunked", { * * **Details** * - * It includes the client public key, target store id, AES-GCM initialization - * vector, and encrypted entries. + * It includes the client public key, target store id, and encrypted entries + * with their AES-GCM initialization vectors. * * @category protocols * @since 4.0.0 @@ -263,7 +270,6 @@ export class WriteChunkedRpc extends Rpc.make("EventLog.WriteChunked", { export class WriteEntries extends Schema.Class("effect/eventlog/EventLogRemote/WriteEntries")({ publicKey: Schema.String, storeId: StoreId, - iv: Transferable.Uint8Array, encryptedEntries: Schema.Array(EncryptedEntry) }) { static FromMsgpack = Msgpack.schema(WriteEntries) @@ -324,7 +330,7 @@ export class ChangesRpc extends Rpc.make("EventLog.Changes", { payload: { publicKey: Schema.String, storeId: StoreId, - startSequence: Schema.Number + startSequence: Schema.Natural }, success: Schema.Union([SingleMessage, ChunkedMessage]), error: EventLogProtocolError, diff --git a/repos/effect/packages/effect/src/unstable/eventlog/EventLogRemote.ts b/repos/effect/packages/effect/src/unstable/eventlog/EventLogRemote.ts index c45a835a80..dc76e650e0 100644 --- a/repos/effect/packages/effect/src/unstable/eventlog/EventLogRemote.ts +++ b/repos/effect/packages/effect/src/unstable/eventlog/EventLogRemote.ts @@ -119,7 +119,7 @@ const makeAuthenticate = Effect.fnUntraced(function*(options: { * Use to provide the RPC client used by remote event-log replicas to * authenticate, write entries, and subscribe to changes. * - * @category RPC client + * @category services * @since 4.0.0 */ export class EventLogRemoteClient extends Context.Service< @@ -307,14 +307,14 @@ export const makeEncrypted = Effect.gen(function*(): Effect.fn.Return< return yield* makeWith({ encodeWrite: (options) => encryption.encrypt(options.identity, options.entries).pipe( - Effect.flatMap((msg) => + Effect.flatMap((encryptedEntries) => new WriteEntries({ publicKey: options.identity.publicKey, storeId: options.storeId, - iv: msg.iv, - encryptedEntries: msg.encryptedEntries.map((entry, i) => ({ + encryptedEntries: encryptedEntries.map((entry, i) => ({ entryId: options.entries[i].id, - encryptedEntry: entry + iv: entry.iv, + encryptedEntry: entry.encryptedEntry })) }).encoded ) diff --git a/repos/effect/packages/effect/src/unstable/eventlog/EventLogServer.ts b/repos/effect/packages/effect/src/unstable/eventlog/EventLogServer.ts index b41a6731c1..ed39ad80da 100644 --- a/repos/effect/packages/effect/src/unstable/eventlog/EventLogServer.ts +++ b/repos/effect/packages/effect/src/unstable/eventlog/EventLogServer.ts @@ -80,7 +80,8 @@ export const layerRpcHandlers = (options: { signingPublicKey: Uint8Array ) => Effect.Effect> readonly onWrite: ( - data: Uint8Array + data: Uint8Array, + authenticatedPublicKeys: ReadonlySet ) => Effect.Effect readonly changes: (options: { readonly publicKey: string @@ -161,24 +162,46 @@ export const layerRpcHandlers = (options: { }) } + const authenticatedIdentities = new Set( + Context.getOrUndefined(client.annotations, AuthenticatedIdentities) + ) + authenticatedIdentities.add(request.publicKey) void client .annotate(EventLog.Identity, { publicKey: request.publicKey, privateKey: constEmptyPrivateKey }) + .annotate(AuthenticatedIdentities, authenticatedIdentities) .annotate(ChunkedMessageState, new Map()) }), - "EventLog.WriteSingle": Effect.fnUntraced(function*(request) { - yield* options.onWrite(request.data) + "EventLog.WriteSingle": Effect.fnUntraced(function*(request, { client }) { + yield* options.onWrite( + request.data, + Context.getOrUndefined(client.annotations, AuthenticatedIdentities) ?? new Set() + ) }), "EventLog.WriteChunked": Effect.fnUntraced(function*(request, { client }) { const state = Context.get(client.annotations, ChunkedMessageState) const data = ChunkedMessage.join(state, request) if (!data) return - yield* options.onWrite(data) + yield* options.onWrite( + data, + Context.getOrUndefined(client.annotations, AuthenticatedIdentities) ?? new Set() + ) }), - "EventLog.Changes": (request) => - options.changes({ + "EventLog.Changes": (request, { client }) => { + const authenticatedIdentities = Context.getOrUndefined(client.annotations, AuthenticatedIdentities) + if (!authenticatedIdentities?.has(request.publicKey)) { + return Stream.fail( + new EventLogProtocolError({ + requestTag: "Changes", + publicKey: request.publicKey, + code: "Forbidden", + message: "Identity is not authenticated" + }) + ) + } + return options.changes({ publicKey: request.publicKey, storeId: request.storeId, startSequence: request.startSequence @@ -200,6 +223,7 @@ export const layerRpcHandlers = (options: { ) ) ) + } }) })).pipe( Layer.merge(layerAuthMiddleware) @@ -214,7 +238,7 @@ export const layerRpcHandlers = (options: { * Use to keep per-client chunk assembly state while handling chunked event-log * writes. * - * @category chunked message state + * @category services * @since 4.0.0 */ export class ChunkedMessageState extends Context.Reference< @@ -227,6 +251,16 @@ export class ChunkedMessageState extends Context.Reference< defaultValue: () => new Map() }) {} +/** + * Annotation containing the public keys authenticated on an RPC connection. + * + * @category services + * @since 4.0.0 + */ +export class AuthenticatedIdentities extends Context.Service>()( + "effect/eventlog/EventLogServer/AuthenticatedIdentities" +) {} + class SessionAuthCacheKey extends Data.Class<{ readonly publicKey: string readonly signingPublicKey: Uint8Array diff --git a/repos/effect/packages/effect/src/unstable/eventlog/EventLogServerEncrypted.ts b/repos/effect/packages/effect/src/unstable/eventlog/EventLogServerEncrypted.ts index 0b23b69e2b..642306b4ca 100644 --- a/repos/effect/packages/effect/src/unstable/eventlog/EventLogServerEncrypted.ts +++ b/repos/effect/packages/effect/src/unstable/eventlog/EventLogServerEncrypted.ts @@ -47,7 +47,7 @@ export const layerRpcHandlers = Layer.unwrap(Effect.gen(function*() { remoteId, getOrCreateSessionAuthBinding: (publicKey, signingPublicKey) => storage.getOrCreateSessionAuthBinding(publicKey, signingPublicKey), - onWrite: Effect.fnUntraced(function*(data) { + onWrite: Effect.fnUntraced(function*(data, authenticatedPublicKeys) { const request = yield* WriteEntries.decode(data).pipe( Effect.mapError((_) => new EventLogProtocolError({ @@ -58,11 +58,19 @@ export const layerRpcHandlers = Layer.unwrap(Effect.gen(function*() { }) ) ) + if (!authenticatedPublicKeys.has(request.publicKey)) { + return yield* new EventLogProtocolError({ + requestTag: "WriteEntries", + publicKey: request.publicKey, + code: "Forbidden", + message: "Identity is not authenticated" + }) + } if (request.encryptedEntries.length === 0) return - const entries = request.encryptedEntries.map(({ encryptedEntry, entryId }) => + const entries = request.encryptedEntries.map(({ encryptedEntry, entryId, iv }) => new PersistedEntry({ entryId, - iv: request.iv, + iv, encryptedEntry }) ) @@ -116,7 +124,7 @@ export const layer: Layer.Layer = Rp /** * Schema for encrypted entries persisted by the encrypted event-log server. * - * @category storage + * @category models * @since 4.0.0 */ export class PersistedEntry extends Schema.Class( @@ -150,7 +158,7 @@ export class PersistedEntry extends Schema.Class( * persists encrypted entries, and streams encrypted changes for a public key and * store id. * - * @category storage + * @category services * @since 4.0.0 */ export class Storage extends Context.Service = Effect.gen(function*() { @@ -255,7 +263,7 @@ export const makeStorageMemory: Effect.Effect = Layer.effect(Storage)(makeStorageMemory) diff --git a/repos/effect/packages/effect/src/unstable/eventlog/EventLogServerUnencrypted.ts b/repos/effect/packages/effect/src/unstable/eventlog/EventLogServerUnencrypted.ts index 0f9278a9c8..6965ab1545 100644 --- a/repos/effect/packages/effect/src/unstable/eventlog/EventLogServerUnencrypted.ts +++ b/repos/effect/packages/effect/src/unstable/eventlog/EventLogServerUnencrypted.ts @@ -74,7 +74,7 @@ export class EventLogServerUnencrypted extends Context.Service( @@ -146,7 +146,7 @@ export const layerRpcHandlers: Layer.Layer< remoteId, getOrCreateSessionAuthBinding: (publicKey, signingPublicKey) => storage.getOrCreateSessionAuthBinding(publicKey, signingPublicKey), - onWrite: Effect.fnUntraced(function*(data) { + onWrite: Effect.fnUntraced(function*(data, authenticatedPublicKeys) { const request = yield* WriteEntriesUnencrypted.decode(data).pipe( Effect.mapError((_) => new EventLogProtocolError({ @@ -157,6 +157,14 @@ export const layerRpcHandlers: Layer.Layer< }) ) ) + if (!authenticatedPublicKeys.has(request.publicKey)) { + return yield* new EventLogProtocolError({ + requestTag: "WriteEntries", + publicKey: request.publicKey, + code: "Forbidden", + message: "Identity is not authenticated" + }) + } if (!Arr.isReadonlyArrayNonEmpty(request.entries)) return const resolvedStoreId = yield* mapping.resolve({ @@ -322,7 +330,7 @@ const toStoreNotFoundError = (options: { * Provides a `StoreMapping` that accepts only one configured store id and fails * all other store ids as not found. * - * @category store + * @category layers * @since 4.0.0 */ export const layerStoreMappingStatic = (options: { @@ -352,7 +360,7 @@ export const layerStoreMappingStatic = (options: { * allocates remote sequence numbers, persists entries, streams changes, and * exposes a transaction boundary. * - * @category storage + * @category services * @since 4.0.0 */ export class Storage extends Context.Service = [] for (let j = 0; j < newHistory.length; j++) { - const scannedEntry = history[j]! + const scannedEntry = newHistory[j]! if (scannedEntry.event === originEntry.event && scannedEntry.primaryKey === originEntry.primaryKey) { conflicts.push(scannedEntry) } @@ -556,7 +564,7 @@ export const compactBacklog = Effect.fnUntraced(function*(options: { * in memory, publishes live changes, and serializes transactions with a * semaphore. * - * @category storage + * @category constructors * @since 4.0.0 */ export const makeStorageMemory: Effect.Effect = Effect.gen(function*() { @@ -663,7 +671,7 @@ export const makeStorageMemory: Effect.Effect = Layer.effect(Storage)(makeStorageMemory) diff --git a/repos/effect/packages/effect/src/unstable/eventlog/EventLogSessionAuth.ts b/repos/effect/packages/effect/src/unstable/eventlog/EventLogSessionAuth.ts index e749955443..2e0182f5e2 100644 --- a/repos/effect/packages/effect/src/unstable/eventlog/EventLogSessionAuth.ts +++ b/repos/effect/packages/effect/src/unstable/eventlog/EventLogSessionAuth.ts @@ -479,7 +479,7 @@ export const verifySessionAuthPayload = ( /** * Generates a random session authentication challenge using `globalThis.crypto`. * - * @category challenge + * @category constructors * @since 4.0.0 */ export const makeSessionAuthChallenge: Effect.Effect< diff --git a/repos/effect/packages/effect/src/unstable/eventlog/SqlEventJournal.ts b/repos/effect/packages/effect/src/unstable/eventlog/SqlEventJournal.ts index d60357457a..23beb8a379 100644 --- a/repos/effect/packages/effect/src/unstable/eventlog/SqlEventJournal.ts +++ b/repos/effect/packages/effect/src/unstable/eventlog/SqlEventJournal.ts @@ -220,10 +220,12 @@ export const make = (options?: { primaryKey, payload }, { disableChecks: true }) - yield* insertEntry(toEntryRow(entry)) - const value = yield* effect(entry) - yield* PubSub.publish(pubsub, entry) - return value + return yield* Effect.uninterruptibleMask((restore) => + restore(effect(entry)).pipe( + Effect.tap(insertEntry(toEntryRow(entry))), + Effect.tap(PubSub.publish(pubsub, entry)) + ) + ) }, withTracerDisabled, Effect.mapError((cause) => new EventJournal.EventJournalError({ cause, method: "write" })) @@ -319,7 +321,7 @@ const EntryRow = Schema.Struct({ event: Schema.String, primary_key: Schema.String, payload: Schema.Uint8Array, - timestamp: Schema.Number + timestamp: Schema.Int }) const EntryRowArray = Schema.Array(EntryRow) @@ -345,7 +347,7 @@ const toEntryRow = (entry: EventJournal.Entry): EntryRow => ({ const RemoteRow = Schema.Struct({ remote_id: EventJournal.RemoteId, entry_id: EventJournal.EntryId, - sequence: Schema.Number + sequence: Schema.Natural }) const RemoteRowArray = Schema.Array(RemoteRow) diff --git a/repos/effect/packages/effect/src/unstable/eventlog/SqlEventLogServerEncrypted.ts b/repos/effect/packages/effect/src/unstable/eventlog/SqlEventLogServerEncrypted.ts index 892391aa79..d1b8972a13 100644 --- a/repos/effect/packages/effect/src/unstable/eventlog/SqlEventLogServerEncrypted.ts +++ b/repos/effect/packages/effect/src/unstable/eventlog/SqlEventLogServerEncrypted.ts @@ -269,7 +269,7 @@ export const makeStorage = (options?: { }).pipe(withTracerDisabled) const EncryptedRemoteEntrySql = Schema.Struct({ - sequence: Schema.Number, + sequence: Schema.Natural, iv: Schema.Uint8Array, entry_id: EntryId, encrypted_entry: Schema.Uint8Array diff --git a/repos/effect/packages/effect/src/unstable/eventlog/SqlEventLogServerUnencrypted.ts b/repos/effect/packages/effect/src/unstable/eventlog/SqlEventLogServerUnencrypted.ts index ba38af32b8..8c7e44fbeb 100644 --- a/repos/effect/packages/effect/src/unstable/eventlog/SqlEventLogServerUnencrypted.ts +++ b/repos/effect/packages/effect/src/unstable/eventlog/SqlEventLogServerUnencrypted.ts @@ -461,17 +461,20 @@ const EntrySql = Schema.Struct({ type EntrySql = Schema.Schema.Type -const SqlNumber = Schema.Union([Schema.Number, Schema.NumberFromString]) +const SqlNatural = Schema.Union([ + Schema.Natural, + Schema.FiniteFromString.check(Schema.isInt(), Schema.isGreaterThanOrEqualTo(0)) +]) const RemoteEntrySql = Schema.Struct({ ...EntrySql.fields, - sequence: SqlNumber + sequence: SqlNatural }) type RemoteEntrySql = Schema.Schema.Type const StoreSequenceSql = Schema.Struct({ - next_sequence: SqlNumber + next_sequence: SqlNatural }) const SessionAuthBindingSql = Schema.Struct({ diff --git a/repos/effect/packages/effect/src/unstable/http/Cookies.ts b/repos/effect/packages/effect/src/unstable/http/Cookies.ts index 2da2fe004a..580570471e 100644 --- a/repos/effect/packages/effect/src/unstable/http/Cookies.ts +++ b/repos/effect/packages/effect/src/unstable/http/Cookies.ts @@ -13,6 +13,7 @@ import * as Data from "../../Data.ts" import * as Duration from "../../Duration.ts" import { dual } from "../../Function.ts" import * as Inspectable from "../../Inspectable.ts" +import * as InternalRecord from "../../internal/record.ts" import * as Option from "../../Option.ts" import { type Pipeable, pipeArguments } from "../../Pipeable.ts" import * as Predicate from "../../Predicate.ts" @@ -27,7 +28,7 @@ const TypeId = "~effect/http/Cookies" /** * Returns `true` when a value is a `Cookies` collection. * - * @category refinements + * @category guards * @since 4.0.0 */ export const isCookies = (u: unknown): u is Cookies => Predicate.hasProperty(u, TypeId) @@ -65,15 +66,15 @@ export interface CookiesSchema extends Schema.declare ({ + runtime: "Cookies.CookiesSchema", + Type: "Cookies.Cookies", + importDeclarations: [`import * as Cookies from "effect/unstable/http/Cookies"`] + }), expected: "Cookies", toCodecJson: () => Schema.link()( @@ -146,14 +147,15 @@ export interface CookieSchema extends Schema.declare {} export const CookieSchema: CookieSchema = Schema.declare( isCookie, { - typeConstructor: { - _tag: "effect/http/Cookie" - }, - generation: { - runtime: `Cookies.CookieSchema`, - Type: `Cookies.Cookie`, - importDeclaration: `import * as Cookie from "effect/unstable/http/Cookies"` + representation: { + id: "effect/http/Cookie", + payload: null }, + toCode: () => ({ + runtime: "Cookies.CookieSchema", + Type: "Cookies.Cookie", + importDeclarations: [`import * as Cookies from "effect/unstable/http/Cookies"`] + }), expected: "Cookie" } ) @@ -251,7 +253,7 @@ export const fromReadonlyRecord = (cookies: Record.ReadonlyRecord): Cookies => { const record: Record = {} for (const cookie of cookies) { - record[cookie.name] = cookie + InternalRecord.assignProperty(record, cookie.name, cookie) } return fromReadonlyRecord(record) } @@ -417,13 +419,17 @@ export const empty: Cookies = fromIterable([]) /** * Returns `true` when the `Cookies` collection contains no cookies. * - * @category refinements + * @category predicates * @since 4.0.0 */ export const isEmpty = (self: Cookies): boolean => Record.isEmptyRecord(self.cookies) // oxlint-disable-next-line no-control-regex const fieldContentRegExp = /^[\u0009\u0020-\u007e\u0080-\u00ff]+$/ +const cookieNameRegExp = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/ +// oxlint-disable-next-line no-control-regex +const cookieDomainRegExp = /^[\u0009\u0020-\u003a\u003c-\u007e\u0080-\u00ff]+$/ +const cookiePathRegExp = /^[\u0020-\u003a\u003c-\u007e]+$/ const CookieProto = { [CookieTypeId]: CookieTypeId, @@ -453,26 +459,10 @@ export function makeCookie( value: string, options?: Cookie["options"] | undefined ): Result.Result { - if (!fieldContentRegExp.test(name)) { - return Result.fail(CookiesError.fromReason("InvalidCookieName")) - } const encodedValue = encodeURIComponent(value) - if (encodedValue && !fieldContentRegExp.test(encodedValue)) { - return Result.fail(CookiesError.fromReason("InvalidCookieValue")) - } - - if (options !== undefined) { - if (options.domain !== undefined && !fieldContentRegExp.test(options.domain)) { - return Result.fail(CookiesError.fromReason("InvalidCookieDomain")) - } - - if (options.path !== undefined && !fieldContentRegExp.test(options.path)) { - return Result.fail(CookiesError.fromReason("InvalidCookiePath")) - } - - if (options.maxAge !== undefined && !Duration.isFinite(Duration.fromInputUnsafe(options.maxAge))) { - return Result.fail(CookiesError.fromReason("CookieInfinityMaxAge")) - } + const error = validateCookie(name, encodedValue, options) + if (error !== undefined) { + return Result.fail(error) } return Result.succeed(Object.assign(Object.create(CookieProto), { @@ -483,6 +473,28 @@ export function makeCookie( })) } +function validateCookie( + name: string, + encodedValue: string, + options: Cookie["options"] | undefined +): CookiesError | undefined { + if (!cookieNameRegExp.test(name)) { + return CookiesError.fromReason("InvalidCookieName") + } + if (encodedValue && !fieldContentRegExp.test(encodedValue)) { + return CookiesError.fromReason("InvalidCookieValue") + } + if (options?.domain !== undefined && !cookieDomainRegExp.test(options.domain)) { + return CookiesError.fromReason("InvalidCookieDomain") + } + if (options?.path !== undefined && !cookiePathRegExp.test(options.path)) { + return CookiesError.fromReason("InvalidCookiePath") + } + if (options?.maxAge !== undefined && !Duration.isFinite(Duration.fromInputUnsafe(options.maxAge))) { + return CookiesError.fromReason("CookieInfinityMaxAge") + } +} + /** * Create a new cookie, throwing an error if invalid * @@ -526,7 +538,7 @@ export const setAllCookie: { } = dual(2, (self: Cookies, cookies: Iterable) => { const record = { ...self.cookies } for (const cookie of cookies) { - record[cookie.name] = cookie + InternalRecord.assignProperty(record, cookie.name, cookie) } return fromReadonlyRecord(record) }) @@ -568,7 +580,8 @@ export const get: { (self: Cookies, name: string): Option.Option } = dual( (args) => isCookies(args[0]), - (self: Cookies, name: string): Option.Option => Option.fromUndefinedOr(self.cookies[name]) + (self: Cookies, name: string): Option.Option => + Option.fromUndefinedOr(Object.hasOwn(self.cookies, name) ? self.cookies[name] : undefined) ) /** @@ -742,7 +755,7 @@ export const setAll: { if (Result.isFailure(result)) { return result as Result.Failure } - record[name] = result.success + InternalRecord.assignProperty(record, name, result.success) } return Result.succeed(fromReadonlyRecord(record)) } @@ -776,6 +789,10 @@ export const setAllUnsafe: { * @since 4.0.0 */ export function serializeCookie(self: Cookie): string { + const error = validateCookie(self.name, self.valueEncoded, self.options) + if (error !== undefined) { + throw error + } let str = self.name + "=" + self.valueEncoded if (self.options === undefined) { @@ -865,7 +882,7 @@ export const toRecord = (self: Cookies): Record => { const cookies = Object.values(self.cookies) for (let index = 0; index < cookies.length; index++) { const cookie = cookies[index] - record[cookie.name] = cookie.value + InternalRecord.assignProperty(record, cookie.name, cookie.value) } return record } @@ -926,14 +943,16 @@ export function parseHeader(header: string): Record { } const key = header.substring(pos, eqIdx++).trim() - if (result[key] === undefined) { + if (!Object.hasOwn(result, key)) { const val = header.charCodeAt(eqIdx) === 0x22 ? header.substring(eqIdx + 1, terminatorPos - 1).trim() : header.substring(eqIdx, terminatorPos).trim() - result[key] = !(val.indexOf("%") === -1) - ? tryDecodeURIComponent(val) - : val + InternalRecord.assignProperty( + result, + key, + !(val.indexOf("%") === -1) ? tryDecodeURIComponent(val) : val + ) } pos = terminatorPos + 1 diff --git a/repos/effect/packages/effect/src/unstable/http/Etag.ts b/repos/effect/packages/effect/src/unstable/http/Etag.ts index 0a8545b600..c35f0ba330 100644 --- a/repos/effect/packages/effect/src/unstable/http/Etag.ts +++ b/repos/effect/packages/effect/src/unstable/http/Etag.ts @@ -71,7 +71,7 @@ export const toString = (self: Etag): string => { /** * Service for generating ETags from filesystem file information or Web `File`-like metadata. * - * @category models + * @category services * @since 4.0.0 */ export class Generator extends Context.Service { const fetch = fiber.getRef(Fetch) const options: globalThis.RequestInit = fiber.context.mapUnsafe.get(RequestInit.key) ?? {} - let headers = options.headers ? Headers.merge(Headers.fromInput(options.headers), request.headers) : request.headers + let headers = options.headers + ? Headers.merge(Headers.fromInput(options.headers as Headers.Input), request.headers) + : request.headers if (headers["content-length"]) { headers = Headers.remove(headers, "content-length") } diff --git a/repos/effect/packages/effect/src/unstable/http/FindMyWay.ts b/repos/effect/packages/effect/src/unstable/http/FindMyWay.ts index 7507cb4fc8..c4a1c92e4f 100644 --- a/repos/effect/packages/effect/src/unstable/http/FindMyWay.ts +++ b/repos/effect/packages/effect/src/unstable/http/FindMyWay.ts @@ -1,15 +1,74 @@ /** - * Re-exports the `find-my-way-ts` router package used by the unstable HTTP - * routing modules. + * A radix-tree HTTP router used by the unstable HTTP routing modules. * - * This module keeps the router types and helpers available from the Effect HTTP - * namespace without wrapping or changing them. + * @since 4.0.0 + */ +import * as internal from "./FindMyWay/internal/router.ts" + +/* + * MIT License + * + * Copyright (c) 2017-2019 Tomas Della Vedova + * + * 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. + */ + +/** + * @since 4.0.0 + * @category models + */ +export interface RouterConfig { + readonly ignoreTrailingSlash: boolean + readonly ignoreDuplicateSlashes: boolean + readonly caseSensitive: boolean + readonly maxParamLength: number +} + +/** + * @since 4.0.0 + * @category models + */ +export type PathInput = `/${string}` | "*" + +/** + * @since 4.0.0 + * @category models + */ +export interface Router { + readonly on: (method: string | Iterable, path: PathInput, handler: A) => void + readonly all: (path: PathInput, handler: A) => void + readonly find: (method: string, url: string) => FindResult | undefined + readonly has: (method: string, url: string) => boolean +} + +/** * @since 4.0.0 + * @category models */ +export interface FindResult { + readonly handler: A + readonly params: Record + readonly searchParams: Record> +} /** - * @category re-exports * @since 4.0.0 + * @category constructors */ -export * from "find-my-way-ts" +export const make: (options?: Partial) => Router = internal.make diff --git a/repos/effect/packages/effect/src/unstable/http/FindMyWay/internal/queryString.ts b/repos/effect/packages/effect/src/unstable/http/FindMyWay/internal/queryString.ts new file mode 100644 index 0000000000..eeda1cb2b9 --- /dev/null +++ b/repos/effect/packages/effect/src/unstable/http/FindMyWay/internal/queryString.ts @@ -0,0 +1,420 @@ +/* + * MIT License + * + * Copyright (c) 2017-2019 Tomas Della Vedova + * + * 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. + */ + +/** + * @since 1.0.0 + */ +// Taken from https://github.com/anonrig/fast-querystring under MIT License +const plusRegex = /\+/g +const Empty: new() => Record = function() {} as any +Empty.prototype = Object.create(null) + +/** + * @category parsing + * @since 1.0.0 + */ +export function parse(input: string) { + // Optimization: Use new Empty() instead of Object.create(null) for performance + // v8 has a better optimization for initializing functions compared to Object + const result = new Empty() + + if (typeof input !== "string") { + return result + } + + const inputLength = input.length + let key = "" + let value = "" + let startingIndex = -1 + let equalityIndex = -1 + let shouldDecodeKey = false + let shouldDecodeValue = false + let keyHasPlus = false + let valueHasPlus = false + let hasBothKeyValuePair = false + let c = 0 + + // Have a boundary of input.length + 1 to access last pair inside the loop. + for (let i = 0; i < inputLength + 1; i++) { + c = i !== inputLength ? input.charCodeAt(i) : 38 + + // Handle '&' and end of line to pass the current values to result + if (c === 38) { + hasBothKeyValuePair = equalityIndex > startingIndex + + // Optimization: Reuse equality index to store the end of key + if (!hasBothKeyValuePair) { + equalityIndex = i + } + + key = input.slice(startingIndex + 1, equalityIndex) + + // Add key/value pair only if the range size is greater than 1; a.k.a. contains at least "=" + if (hasBothKeyValuePair || key.length > 0) { + // Optimization: Replace '+' with space + if (keyHasPlus) { + key = key.replace(plusRegex, " ") + } + + // Optimization: Do not decode if it's not necessary. + if (shouldDecodeKey) { + try { + key = decodeURIComponent(key) || key + } catch {} + } + + if (hasBothKeyValuePair) { + value = input.slice(equalityIndex + 1, i) + + if (valueHasPlus) { + value = value.replace(plusRegex, " ") + } + + if (shouldDecodeValue) { + try { + value = decodeURIComponent(value) || value + } catch {} + } + } + const currentValue = result[key] + + if (currentValue === undefined) { + result[key] = value + } else { + // Optimization: value.pop is faster than Array.isArray(value) + if (currentValue.pop) { + currentValue.push(value) + } else { + result[key] = [currentValue, value] + } + } + } + + // Reset reading key value pairs + value = "" + startingIndex = i + equalityIndex = i + shouldDecodeKey = false + shouldDecodeValue = false + keyHasPlus = false + valueHasPlus = false + } // Check '=' + else if (c === 61) { + if (equalityIndex <= startingIndex) { + equalityIndex = i + } // If '=' character occurs again, we should decode the input. + else { + shouldDecodeValue = true + } + } // Check '+', and remember to replace it with empty space. + else if (c === 43) { + if (equalityIndex > startingIndex) { + valueHasPlus = true + } else { + keyHasPlus = true + } + } // Check '%' character for encoding + else if (c === 37) { + if (equalityIndex > startingIndex) { + shouldDecodeValue = true + } else { + shouldDecodeKey = true + } + } + } + + return result +} + +function getAsPrimitive(value: any) { + const type = typeof value + + if (type === "string") { + // Length check is handled inside encodeString function + return encodeString(value) + } else if (type === "bigint" || type === "boolean") { + return "" + value + } else if (type === "number" && Number.isFinite(value)) { + return value < 1e21 ? "" + value : encodeString("" + value) + } + + return "" +} + +/** + * @category encoding + * @since 1.0.0 + */ +export function stringify(input: Record): string { + let result = "" + + if (input === null || typeof input !== "object") { + return result + } + + const separator = "&" + const keys = Object.keys(input) + const keyLength = keys.length + let valueLength = 0 + + for (let i = 0; i < keyLength; i++) { + const key = keys[i] + const value = input[key] + const encodedKey = encodeString(key) + "=" + + if (i) { + result += separator + } + + if (Array.isArray(value)) { + valueLength = value.length + for (let j = 0; j < valueLength; j++) { + if (j) { + result += separator + } + + // Optimization: Dividing into multiple lines improves the performance. + // Since v8 does not need to care about the '+' character if it was one-liner. + result += encodedKey + result += getAsPrimitive(value[j]) + } + } else { + result += encodedKey + result += getAsPrimitive(value) + } + } + + return result +} + +// ----------------------------------------------------------------------------- + +// This has been taken from Node.js project. +// Full implementation can be found from https://github.com/nodejs/node/blob/main/lib/internal/querystring.js + +const hexTable = Array.from( + { length: 256 }, + (_, i) => "%" + ((i < 16 ? "0" : "") + i.toString(16)).toUpperCase() +) + +// These characters do not need escaping when generating query strings: +// ! - . _ ~ +// ' ( ) * +// digits +// alpha (uppercase) +// alpha (lowercase) +// biome-ignore format: the array should not be formatted +const noEscape = new Int8Array([ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, // 0 - 15 + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, // 16 - 31 + 0, + 1, + 0, + 0, + 0, + 0, + 0, + 1, + 1, + 1, + 1, + 0, + 0, + 1, + 1, + 0, // 32 - 47 + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 0, + 0, + 0, + 0, + 0, + 0, // 48 - 63 + 0, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, // 64 - 79 + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 0, + 0, + 0, + 0, + 1, // 80 - 95 + 0, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, // 96 - 111 + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 0, + 0, + 0, + 1, + 0 // 112 - 127 +]) + +function encodeString(str: string) { + const len = str.length + if (len === 0) return "" + + let out = "" + let lastPos = 0 + let i = 0 + + outer: for (; i < len; i++) { + let c = str.charCodeAt(i) + + // ASCII + while (c < 0x80) { + if (noEscape[c] !== 1) { + if (lastPos < i) out += str.slice(lastPos, i) + lastPos = i + 1 + out += hexTable[c] + } + + if (++i === len) break outer + + c = str.charCodeAt(i) + } + + if (lastPos < i) out += str.slice(lastPos, i) + + // Multi-byte characters ... + if (c < 0x800) { + lastPos = i + 1 + out += hexTable[0xc0 | (c >> 6)] + hexTable[0x80 | (c & 0x3f)] + continue + } + if (c < 0xd800 || c >= 0xe000) { + lastPos = i + 1 + out += hexTable[0xe0 | (c >> 12)] + + hexTable[0x80 | ((c >> 6) & 0x3f)] + + hexTable[0x80 | (c & 0x3f)] + continue + } + // Surrogate pair + ++i + + // This branch should never happen because all URLSearchParams entries + // should already be converted to USVString. But, included for + // completion's sake anyway. + if (i >= len) { + throw new Error("URI malformed") + } + + const c2 = str.charCodeAt(i) & 0x3ff + + lastPos = i + 1 + c = 0x10000 + (((c & 0x3ff) << 10) | c2) + out += hexTable[0xf0 | (c >> 18)] + + hexTable[0x80 | ((c >> 12) & 0x3f)] + + hexTable[0x80 | ((c >> 6) & 0x3f)] + + hexTable[0x80 | (c & 0x3f)] + } + if (lastPos === 0) return str + if (lastPos < len) return out + str.slice(lastPos) + return out +} diff --git a/repos/effect/packages/effect/src/unstable/http/FindMyWay/internal/router.ts b/repos/effect/packages/effect/src/unstable/http/FindMyWay/internal/router.ts new file mode 100644 index 0000000000..8008f9f5fd --- /dev/null +++ b/repos/effect/packages/effect/src/unstable/http/FindMyWay/internal/router.ts @@ -0,0 +1,950 @@ +/* + * MIT License + * + * Copyright (c) 2017-2019 Tomas Della Vedova + * + * 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. + */ + +import type * as Router from "../../FindMyWay.ts" +import * as QS from "./queryString.ts" + +const FULL_PATH_REGEXP = /^https?:\/\/.*?\// +const OPTIONAL_PARAM_REGEXP = /(\/:[^/()]*?)\?(\/?)/ + +interface Route { + readonly method: string + readonly path: Router.PathInput + readonly pattern: string + readonly handler: A + readonly params: ReadonlyArray +} + +/** @internal */ +export const make = ( + options: Partial = {} +): Router.Router => new RouterImpl(options) + +class RouterImpl implements Router.Router { + constructor(options: Partial = {}) { + this.options = { + ignoreTrailingSlash: true, + ignoreDuplicateSlashes: true, + caseSensitive: false, + maxParamLength: 100, + ...options + } + } + + readonly options: Router.RouterConfig + routes: Array = [] + trees: Record = Object.create(null) + + on( + method: string | Iterable, + path: Router.PathInput, + handler: A + ): void { + const optionalParamMatch = path.match(OPTIONAL_PARAM_REGEXP) + if (optionalParamMatch && optionalParamMatch.index !== undefined) { + assert( + path.length === optionalParamMatch.index + optionalParamMatch[0].length, + "Optional Parameter needs to be the last parameter of the path" + ) + + const pathFull = path.replace( + OPTIONAL_PARAM_REGEXP, + "$1$2" + ) as Router.PathInput + const pathOptional = (path.replace( + OPTIONAL_PARAM_REGEXP, + "$2" + ) || "/") as Router.PathInput + + this.on(method, pathFull, handler) + this.on(method, pathOptional, handler) + return + } + + if (this.options.ignoreDuplicateSlashes) { + path = removeDuplicateSlashes(path) + } + + if (this.options.ignoreTrailingSlash) { + path = trimLastSlash(path) + } + + const methods = typeof method === "string" ? [method] : method + for (const method of methods) { + this._on(method, path, handler) + } + } + + all(path: Router.PathInput, handler: A) { + this.on(httpMethods, path, handler) + } + + private _on(method: string, path: Router.PathInput, handler: A): void { + if (this.trees[method] === undefined) { + this.trees[method] = new StaticNode("/") + } + + let pattern = path + if (pattern === "*" && this.trees[method].prefix.length !== 0) { + const currentRoot = this.trees[method] + this.trees[method] = new StaticNode("") + this.trees[method].staticChildren["/"] = currentRoot + } + + let parentNodePathIndex = this.trees[method].prefix.length + let currentNode: Node = this.trees[method] + + const params = [] + for (let i = 0; i <= pattern.length; i++) { + if (pattern.charCodeAt(i) === 58 && pattern.charCodeAt(i + 1) === 58) { + // It's a double colon + i++ + continue + } + + const isParametricNode = pattern.charCodeAt(i) === 58 && pattern.charCodeAt(i + 1) !== 58 + const isWildcardNode = pattern.charCodeAt(i) === 42 + + if ( + isParametricNode || + isWildcardNode || + (i === pattern.length && i !== parentNodePathIndex) + ) { + let staticNodePath = pattern.slice(parentNodePathIndex, i) + if (!this.options.caseSensitive) { + staticNodePath = staticNodePath.toLowerCase() + } + staticNodePath = staticNodePath.split("::").join(":") + staticNodePath = staticNodePath.split("%").join("%25") + // add the static part of the route to the tree + currentNode = (currentNode as StaticNode).createStaticChild( + staticNodePath + ) + } + + if (isParametricNode) { + let isRegexNode = false + let isParamSafe = true + let backtrack = "" + const regexps = [] + let nodePatternParts = "" + + let lastParamStartIndex = i + 1 + for (let j = lastParamStartIndex;; j++) { + const charCode = pattern.charCodeAt(j) + + const isRegexParam = charCode === 40 + const isStaticPart = charCode === 45 || charCode === 46 + const isEndOfNode = charCode === 47 || j === pattern.length + + if (isRegexParam || isStaticPart || isEndOfNode) { + const paramName = pattern.slice(lastParamStartIndex, j) + params.push(paramName) + + isRegexNode = isRegexNode || isRegexParam || isStaticPart + + if (isRegexParam) { + const endOfRegexIndex = getClosingParenthensePosition(pattern, j) + const regexString = pattern.slice(j, endOfRegexIndex + 1) + + regexps.push(trimRegExpStartAndEnd(regexString)) + + j = endOfRegexIndex + 1 + isParamSafe = true + } else { + regexps.push(isParamSafe ? "(.*?)" : `(${backtrack}|(?:(?!${backtrack}).)*)`) + isParamSafe = false + } + + const staticPartStartIndex = j + for (; j < pattern.length; j++) { + const charCode = pattern.charCodeAt(j) + if (charCode === 47) break + if (charCode === 58) { + const nextCharCode = pattern.charCodeAt(j + 1) + if (nextCharCode === 58) j++ + else break + } + } + + let staticPart = pattern.slice(staticPartStartIndex, j) + if (staticPart) { + staticPart = staticPart.split("::").join(":") + staticPart = staticPart.split("%").join("%25") + regexps.push(backtrack = escapeRegExp(staticPart)) + } + + lastParamStartIndex = j + 1 + nodePatternParts += "()" + staticPart + + if ( + isEndOfNode || + pattern.charCodeAt(j) === 47 || + j === pattern.length + ) { + const nodePattern = isRegexNode ? nodePatternParts : staticPart + const nodePath = pattern.slice(i, j) + + pattern = pattern.slice(0, i + 1) + nodePattern + pattern.slice(j) + i += nodePattern.length + + const regex = isRegexNode + ? new RegExp("^" + regexps.join("") + "$") + : undefined + currentNode = (currentNode as StaticNode).createParametricChild( + regex, + staticPart, + nodePath + ) + parentNodePathIndex = i + 1 + break + } + } + } + } else if (isWildcardNode) { + // add the wildcard parameter + params.push("*") + currentNode = (currentNode as StaticNode).createWildcardChild() + parentNodePathIndex = i + 1 + + if (i !== pattern.length - 1) { + throw new Error("Wildcard must be the last character in the route") + } + } + } + + if (!this.options.caseSensitive) { + pattern = pattern.toLowerCase() as Router.PathInput + } + + if (pattern === "*") { + pattern = "/*" + } + + for (const existRoute of this.routes) { + if (existRoute.method === method && existRoute.pattern === pattern) { + throw new Error( + `Method '${method}' already declared for route '${pattern}'` + ) + } + } + + const route = { method, path, pattern, params, handler } + this.routes.push(route) + currentNode.addRoute(route) + } + + has(method: string, path: string): boolean { + const node = this.trees[method] + if (node === undefined) { + return false + } + + const staticNode = node.getStaticChild(path) + if (staticNode === undefined) { + return false + } + + return staticNode.isLeafNode + } + + find(method: string, path: string): Router.FindResult | undefined { + let currentNode: Node | undefined = this.trees[method] + if (currentNode === undefined) return undefined + + if (path.charCodeAt(0) !== 47) { + // 47 is '/' + path = path.replace(FULL_PATH_REGEXP, "/") + } + + // This must be run before sanitizeUrl as the resulting function + // .sliceParameter must be constructed with same URL string used + // throughout the rest of this function. + if (this.options.ignoreDuplicateSlashes) { + path = removeDuplicateSlashes(path) + } + + let sanitizedUrl + let querystring + let shouldDecodeParam + + try { + sanitizedUrl = safeDecodeURI(path) + path = sanitizedUrl.path + querystring = sanitizedUrl.querystring + shouldDecodeParam = sanitizedUrl.shouldDecodeParam + } catch (error) { + return undefined + } + + if (this.options.ignoreTrailingSlash) { + path = trimLastSlash(path) + } + + const originPath = path + + if (this.options.caseSensitive === false) { + path = path.toLowerCase() + } + + const maxParamLength = this.options.maxParamLength + + let pathIndex = (currentNode as StaticNode).prefix.length + const params = [] + const pathLen = path.length + + const brothersNodesStack: Array = [] + + while (true) { + if (pathIndex === pathLen && currentNode.isLeafNode) { + const handle = currentNode.handlerStorage?.find() + if (handle !== undefined) { + return { + handler: handle.handler as A, + params: handle.createParams(params), + searchParams: QS.parse(querystring) + } as const + } + } + + let node: Node | undefined = currentNode.getNextNode( + path, + pathIndex, + brothersNodesStack, + params.length + ) + + if (node === undefined) { + if (brothersNodesStack.length === 0) { + return undefined + } + + const brotherNodeState = brothersNodesStack.pop()! + pathIndex = brotherNodeState.brotherPathIndex + params.splice(brotherNodeState.paramsCount) + node = brotherNodeState.brotherNode + } + + currentNode = node + + while (true) { + if (currentNode._tag === "StaticNode") { + pathIndex += currentNode.prefix.length + break + } + + if (currentNode._tag === "WildcardNode") { + let param = originPath.slice(pathIndex) + if (shouldDecodeParam) { + param = safeDecodeURIComponent(param) + } + + params.push(param) + pathIndex = pathLen + break + } + + let paramEndIndex = originPath.indexOf("/", pathIndex) + if (paramEndIndex === -1) { + paramEndIndex = pathLen + } + + let param = originPath.slice(pathIndex, paramEndIndex) + if (shouldDecodeParam) { + param = safeDecodeURIComponent(param) + } + + if (currentNode.regex !== undefined) { + const matchedParameters: RegExpExecArray | null = currentNode.regex.exec(param) + if (matchedParameters === null) { + if (brothersNodesStack.length === 0) { + return undefined + } + + const brotherNodeState = brothersNodesStack.pop()! + pathIndex = brotherNodeState.brotherPathIndex + params.splice(brotherNodeState.paramsCount) + currentNode = brotherNodeState.brotherNode + continue + } + + let maxParamLengthExceeded = false + for (let i = 1; i < matchedParameters.length; i++) { + const matchedParam = matchedParameters[i] ?? "" + if (matchedParam.length > maxParamLength) { + maxParamLengthExceeded = true + break + } + } + + if (maxParamLengthExceeded) { + if (brothersNodesStack.length === 0) { + return undefined + } + + const brotherNodeState = brothersNodesStack.pop()! + pathIndex = brotherNodeState.brotherPathIndex + params.splice(brotherNodeState.paramsCount) + currentNode = brotherNodeState.brotherNode + continue + } + + for (let i = 1; i < matchedParameters.length; i++) { + params.push(matchedParameters[i] ?? "") + } + } else { + if (param.length > maxParamLength) { + if (brothersNodesStack.length === 0) { + return undefined + } + + const brotherNodeState = brothersNodesStack.pop()! + pathIndex = brotherNodeState.brotherPathIndex + params.splice(brotherNodeState.paramsCount) + currentNode = brotherNodeState.brotherNode + continue + } + params.push(param) + } + + pathIndex = paramEndIndex + break + } + } + } +} + +// ---------------------------------------------------------------------------- +// Handler storage +// ---------------------------------------------------------------------------- + +interface Handler { + readonly params: ReadonlyArray + readonly handler: unknown + readonly createParams: ( + paramsArray: ReadonlyArray + ) => Record +} + +class HandlerStorage { + readonly handlers: Array = [] + unconstrainedHandler: Handler | undefined + + find() { + return this.unconstrainedHandler + } + + add(route: Route) { + const handler: Handler = { + params: route.params, + handler: route.handler, + createParams: compileCreateParams(route.params) + } + this.handlers.push(handler) + this.unconstrainedHandler = this.handlers[0] + } +} + +// ---------------------------------------------------------------------------- +// Nodes +// ---------------------------------------------------------------------------- + +interface BrotherNode { + readonly paramsCount: number + readonly brotherPathIndex: number + readonly brotherNode: Node +} + +type Node = StaticNode | ParametricNode | WildcardNode + +abstract class NodeBase { + isLeafNode = false + routes: Array | undefined + handlerStorage: HandlerStorage | undefined + + addRoute(route: Route) { + if (this.routes === undefined) { + this.routes = [route] + } else { + this.routes.push(route) + } + + if (this.handlerStorage === undefined) { + this.handlerStorage = new HandlerStorage() + } + this.isLeafNode = true + this.handlerStorage.add(route) + } + + abstract getNextNode( + path: string, + pathIndex: number, + nodeStack: any, + paramsCount: number + ): Node | undefined +} + +abstract class ParentNode extends NodeBase { + readonly staticChildren: Record = Object.create(null) + + findStaticMatchingChild( + path: string, + pathIndex: number + ): StaticNode | undefined { + const staticChild = this.staticChildren[path.charAt(pathIndex)] + if ( + staticChild === undefined || + !staticChild.matchPrefix(path, pathIndex) + ) { + return undefined + } + return staticChild + } + + getStaticChild(path: string, pathIndex = 0): StaticNode | undefined { + if (path.length === pathIndex) { + return this as any + } + + const staticChild = this.findStaticMatchingChild(path, pathIndex) + if (staticChild === undefined) { + return undefined + } + + return staticChild.getStaticChild( + path, + pathIndex + staticChild.prefix.length + ) + } + + createStaticChild(path: string): StaticNode { + if (path.length === 0) { + return this as any + } + + let staticChild = this.staticChildren[path.charAt(0)] + if (staticChild) { + let i = 1 + for (; i < staticChild.prefix.length; i++) { + if (path.charCodeAt(i) !== staticChild.prefix.charCodeAt(i)) { + staticChild = staticChild.split(this, i) + break + } + } + return staticChild.createStaticChild(path.slice(i)) + } + + const label = path.charAt(0) + this.staticChildren[label] = new StaticNode(path) + return this.staticChildren[label] + } +} + +class StaticNode extends ParentNode { + readonly _tag = "StaticNode" + constructor(prefix: string) { + super() + this.setPrefix(prefix) + } + + prefix!: string + matchPrefix!: (path: string, pathIndex: number) => boolean + readonly parametricChildren: Array = [] + + wildcardChild: WildcardNode | undefined + + private setPrefix(prefix: string) { + this.prefix = prefix + + if (prefix.length === 1) { + this.matchPrefix = (_path, _pathIndex) => true + } else { + const len = prefix.length + this.matchPrefix = function(path, pathIndex) { + for (let i = 1; i < len; i++) { + if (path.charCodeAt(pathIndex + i) !== this.prefix.charCodeAt(i)) { + return false + } + } + return true + } + } + } + + getParametricChild(regex: RegExp | undefined): ParametricNode | undefined { + if (regex === undefined) { + return this.parametricChildren.find((child) => child.isRegex === false) + } + + const source = regex.source + return this.parametricChildren.find((child) => { + if (child.regex === undefined) { + return false + } + return child.regex.source === source + }) + } + + createParametricChild( + regex: RegExp | undefined, + staticSuffix: string | undefined, + nodePath: string + ) { + let child = this.getParametricChild(regex) + if (child !== undefined) { + child.nodePaths.add(nodePath) + return child + } + + child = new ParametricNode(regex, staticSuffix, nodePath) + this.parametricChildren.push(child) + this.parametricChildren.sort((child1, child2) => { + if (!child1.isRegex) return 1 + if (!child2.isRegex) return -1 + + if (child1.staticSuffix === undefined) return 1 + if (child2.staticSuffix === undefined) return -1 + + if (child2.staticSuffix.endsWith(child1.staticSuffix)) return 1 + if (child1.staticSuffix.endsWith(child2.staticSuffix)) return -1 + + return 0 + }) + + return child + } + + createWildcardChild() { + if (this.wildcardChild === undefined) { + this.wildcardChild = new WildcardNode() + } + return this.wildcardChild + } + + split(parentNode: ParentNode, length: number) { + const parentPrefix = this.prefix.slice(0, length) + const childPrefix = this.prefix.slice(length) + + this.setPrefix(childPrefix) + + const staticNode = new StaticNode(parentPrefix) + staticNode.staticChildren[childPrefix.charAt(0)] = this + parentNode.staticChildren[parentPrefix.charAt(0)] = staticNode + + return staticNode + } + + getNextNode( + path: string, + pathIndex: number, + nodeStack: Array, + paramsCount: number + ): Node | undefined { + let node: Node | undefined = this.findStaticMatchingChild(path, pathIndex) + let parametricBrotherNodeIndex = 0 + + if (node === undefined) { + if (this.parametricChildren.length === 0) { + return this.wildcardChild + } + + node = this.parametricChildren[0] + parametricBrotherNodeIndex = 1 + } + + if (this.wildcardChild !== undefined) { + nodeStack.push({ + paramsCount, + brotherPathIndex: pathIndex, + brotherNode: this.wildcardChild + }) + } + + for ( + let i = this.parametricChildren.length - 1; + i >= parametricBrotherNodeIndex; + i-- + ) { + nodeStack.push({ + paramsCount, + brotherPathIndex: pathIndex, + brotherNode: this.parametricChildren[i] + }) + } + + return node + } +} + +class ParametricNode extends ParentNode { + readonly _tag = "ParametricNode" + readonly regex: RegExp | undefined + readonly staticSuffix: string | undefined + constructor( + regex: RegExp | undefined, + staticSuffix: string | undefined, + nodePath: string + ) { + super() + this.regex = regex + this.staticSuffix = staticSuffix + this.isRegex = !!regex + this.nodePaths = new Set([nodePath]) + } + + readonly isRegex: boolean + readonly nodePaths: Set + + getNextNode(path: string, pathIndex: number) { + return this.findStaticMatchingChild(path, pathIndex) + } +} + +class WildcardNode extends NodeBase { + readonly _tag = "WildcardNode" + getNextNode( + _path: string, + _pathIndex: number, + _nodeStack: any, + _paramsCount: number + ): Node | undefined { + return undefined + } +} + +// -- + +interface Assert { + (condition: any, message?: string): asserts condition +} +const assert: Assert = (condition, message) => { + if (!condition) { + throw new Error(message) + } +} + +function removeDuplicateSlashes(path: string): Router.PathInput { + return path.replace(/\/\/+/g, "/") as Router.PathInput +} + +function trimLastSlash(path: string): Router.PathInput { + if (path.length > 1 && path.charCodeAt(path.length - 1) === 47) { + return path.slice(0, -1) as Router.PathInput + } + return path as Router.PathInput +} + +function compileCreateParams( + params: ReadonlyArray +): (paramsArray: ReadonlyArray) => Record { + const len = params.length + return function(paramsArray) { + const paramsObject: Record = Object.create(null) + for (let i = 0; i < len; i++) { + paramsObject[params[i]] = paramsArray[i] + } + return paramsObject + } +} + +function getClosingParenthensePosition(path: string, idx: number) { + // `path.indexOf()` will always return the first position of the closing parenthese, + // but it's inefficient for grouped or wrong regexp expressions. + // see issues #62 and #63 for more info + + let parentheses = 1 + + while (idx < path.length) { + idx++ + + // ignore skipped chars + if (path[idx] === "\\") { + idx++ + continue + } + + if (path[idx] === ")") { + parentheses-- + } else if (path[idx] === "(") { + parentheses++ + } + + if (!parentheses) return idx + } + + throw new TypeError("Invalid regexp expression in \"" + path + "\"") +} + +function trimRegExpStartAndEnd(regexString: string) { + // removes chars that marks start "^" and end "$" of regexp + if (regexString.charCodeAt(1) === 94) { + regexString = regexString.slice(0, 1) + regexString.slice(2) + } + + if (regexString.charCodeAt(regexString.length - 2) === 36) { + regexString = regexString.slice(0, regexString.length - 2) + + regexString.slice(regexString.length - 1) + } + + return regexString +} + +function escapeRegExp(string: string) { + return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") +} + +// It must spot all the chars where decodeURIComponent(x) !== decodeURI(x) +// The chars are: # $ & + , / : ; = ? @ +function decodeComponentChar(highCharCode: number, lowCharCode: number) { + if (highCharCode === 50) { + if (lowCharCode === 53) return "%" + + if (lowCharCode === 51) return "#" + if (lowCharCode === 52) return "$" + if (lowCharCode === 54) return "&" + if (lowCharCode === 66) return "+" + if (lowCharCode === 98) return "+" + if (lowCharCode === 67) return "," + if (lowCharCode === 99) return "," + if (lowCharCode === 70) return "/" + if (lowCharCode === 102) return "/" + return undefined + } + if (highCharCode === 51) { + if (lowCharCode === 65) return ":" + if (lowCharCode === 97) return ":" + if (lowCharCode === 66) return ";" + if (lowCharCode === 98) return ";" + if (lowCharCode === 68) return "=" + if (lowCharCode === 100) return "=" + if (lowCharCode === 70) return "?" + if (lowCharCode === 102) return "?" + return undefined + } + if (highCharCode === 52 && lowCharCode === 48) { + return "@" + } + return undefined +} + +function safeDecodeURI(path: string) { + let shouldDecode = false + let shouldDecodeParam = false + + let querystring = "" + + for (let i = 1; i < path.length; i++) { + const charCode = path.charCodeAt(i) + + if (charCode === 37) { + const highCharCode = path.charCodeAt(i + 1) + const lowCharCode = path.charCodeAt(i + 2) + + if (decodeComponentChar(highCharCode, lowCharCode) === undefined) { + shouldDecode = true + } else { + shouldDecodeParam = true + // %25 - encoded % char. We need to encode one more time to prevent double decoding + if (highCharCode === 50 && lowCharCode === 53) { + shouldDecode = true + path = path.slice(0, i + 1) + "25" + path.slice(i + 1) + i += 2 + } + i += 2 + } + // Some systems do not follow RFC and separate the path and query + // string with a `;` character (code 59), e.g. `/foo;jsessionid=123456`. + // Thus, we need to split on `;` as well as `?` and `#`. + } else if (charCode === 63 || charCode === 59 || charCode === 35) { + querystring = path.slice(i + 1) + path = path.slice(0, i) + break + } + } + const decodedPath = shouldDecode ? decodeURI(path) : path + return { path: decodedPath, querystring, shouldDecodeParam } as const +} + +function safeDecodeURIComponent(uriComponent: string) { + const startIndex = uriComponent.indexOf("%") + if (startIndex === -1) return uriComponent + + let decoded = "" + let lastIndex = startIndex + + for (let i = startIndex; i < uriComponent.length; i++) { + if (uriComponent.charCodeAt(i) === 37) { + if (i + 2 >= uriComponent.length) break + + const highCharCode = uriComponent.charCodeAt(i + 1) + const lowCharCode = uriComponent.charCodeAt(i + 2) + + const decodedChar = decodeComponentChar(highCharCode, lowCharCode) + decoded += uriComponent.slice(lastIndex, i) + decodedChar + + lastIndex = i + 3 + } + } + return ( + uriComponent.slice(0, startIndex) + decoded + uriComponent.slice(lastIndex) + ) +} + +const httpMethods = [ + "ACL", + "BIND", + "CHECKOUT", + "CONNECT", + "COPY", + "DELETE", + "GET", + "HEAD", + "LINK", + "LOCK", + "M-SEARCH", + "MERGE", + "MKACTIVITY", + "MKCALENDAR", + "MKCOL", + "MOVE", + "NOTIFY", + "OPTIONS", + "PATCH", + "POST", + "PROPFIND", + "PROPPATCH", + "PURGE", + "PUT", + "QUERY", + "REBIND", + "REPORT", + "SEARCH", + "SOURCE", + "SUBSCRIBE", + "TRACE", + "UNBIND", + "UNLINK", + "UNLOCK", + "UNSUBSCRIBE" +] as const diff --git a/repos/effect/packages/effect/src/unstable/http/Headers.ts b/repos/effect/packages/effect/src/unstable/http/Headers.ts index f0fb26744b..d46772524e 100644 --- a/repos/effect/packages/effect/src/unstable/http/Headers.ts +++ b/repos/effect/packages/effect/src/unstable/http/Headers.ts @@ -42,7 +42,7 @@ export type TypeId = typeof TypeId /** * Returns `true` if the provided value is a `Headers` value. * - * @category refinements + * @category guards * @since 4.0.0 */ export const isHeaders = (u: unknown): u is Headers => Predicate.hasProperty(u, TypeId) @@ -130,15 +130,15 @@ export interface HeadersSchema extends Schema.declare ({ + runtime: "Headers.HeadersSchema", + Type: "Headers.Headers", + importDeclarations: [`import * as Headers from "effect/unstable/http/Headers"`] + }), expected: "Headers", toEquivalence: () => Equivalence, toCodec: () => @@ -423,7 +423,7 @@ export const redact: { * * Defaults include `authorization`, `cookie`, `set-cookie`, and `x-api-key`. * - * @category fiber refs + * @category services * @since 4.0.0 */ export const CurrentRedactedNames = Context.Reference< diff --git a/repos/effect/packages/effect/src/unstable/http/HttpBody.ts b/repos/effect/packages/effect/src/unstable/http/HttpBody.ts index 57d6d3254b..ae9db9374c 100644 --- a/repos/effect/packages/effect/src/unstable/http/HttpBody.ts +++ b/repos/effect/packages/effect/src/unstable/http/HttpBody.ts @@ -30,7 +30,7 @@ const TypeId = "~effect/http/HttpBody" /** * Returns `true` if the provided value is an `HttpBody`. * - * @category refinements + * @category guards * @since 4.0.0 */ export const isHttpBody = (u: unknown): u is HttpBody => Predicate.hasProperty(u, TypeId) @@ -335,8 +335,8 @@ export const jsonSchema = ( * @category constructors * @since 4.0.0 */ -export const urlParams = (urlParams: UrlParams.UrlParams, contentType?: string): Uint8Array => - text(UrlParams.toString(urlParams), contentType ?? "application/x-www-form-urlencoded") +export const urlParams = (urlParams: UrlParams.Input, contentType?: string): Uint8Array => + text(UrlParams.toString(UrlParams.fromInput(urlParams)), contentType ?? "application/x-www-form-urlencoded") /** * HTTP body variant backed by Web `FormData`. @@ -484,12 +484,26 @@ export const stream = ( contentLength?: number ): Stream => new Stream(body, contentType ?? "application/octet-stream", contentLength) +const fileContentLength = ( + size: FileSystem.SizeInput, + options?: { + readonly bytesToRead?: FileSystem.SizeInput | undefined + readonly offset?: FileSystem.SizeInput | undefined + } +): number => { + const available = Math.max(0, Number(size) - Number(options?.offset ?? 0)) + return options?.bytesToRead === undefined + ? available + : Math.min(available, Math.max(0, Number(options.bytesToRead))) +} + /** * Creates a streaming HTTP body for a file path. * * **Details** * - * The effect requires `FileSystem`, stats the file to set the content length, and can fail with `PlatformError`. + * The effect requires `FileSystem`, stats the file to set the selected content length, and can fail with + * `PlatformError`. * * @category constructors * @since 4.0.0 @@ -510,7 +524,7 @@ export const file = ( stream( fs.stream(path, options), options?.contentType, - Number(info.size) + fileContentLength(info.size, options) )) ) @@ -519,7 +533,8 @@ export const file = ( * * **Details** * - * The effect requires `FileSystem`, uses the provided file size as the content length, and can fail with `PlatformError`. + * The effect requires `FileSystem`, uses the provided file size to determine the selected content length, and can + * fail with `PlatformError`. * * @category constructors * @since 4.0.0 @@ -540,6 +555,6 @@ export const fileFromInfo = ( stream( fs.stream(path, options), options?.contentType, - Number(info.size) + fileContentLength(info.size, options) ) ) diff --git a/repos/effect/packages/effect/src/unstable/http/HttpClient.ts b/repos/effect/packages/effect/src/unstable/http/HttpClient.ts index a34bb07e8b..c3a1a37c1f 100644 --- a/repos/effect/packages/effect/src/unstable/http/HttpClient.ts +++ b/repos/effect/packages/effect/src/unstable/http/HttpClient.ts @@ -34,13 +34,14 @@ import type { EqualsWith, ExcludeTag, ExtractTag, NoExcessProperties, NoInfer, T import type * as RateLimiter from "../persistence/RateLimiter.ts" import * as Cookies from "./Cookies.ts" import * as Headers from "./Headers.ts" +import * as HttpBody from "./HttpBody.ts" import * as Error from "./HttpClientError.ts" import * as HttpClientRequest from "./HttpClientRequest.ts" import * as HttpClientResponse from "./HttpClientResponse.ts" import * as HttpIncomingMessage from "./HttpIncomingMessage.ts" import * as HttpMethod from "./HttpMethod.ts" import * as TraceContext from "./HttpTraceContext.ts" -import * as UrlParams from "./UrlParams.ts" +import * as Url from "./Url.ts" const TypeId = "~effect/http/HttpClient" @@ -257,7 +258,7 @@ export const options: (url: string | URL, options?: HttpClientRequest.Options.No * * The transformation receives both the response effect and the original request, allowing it to change success, error, and environment behavior. * - * @category mapping & sequencing + * @category mapping * @since 4.0.0 */ export const transform: { @@ -289,7 +290,7 @@ export const transform: { /** * Transforms a client by applying an effectful transformation to each response effect. * - * @category mapping & sequencing + * @category mapping * @since 4.0.0 */ export const transformResponse: { @@ -476,7 +477,7 @@ export const catchTags: { /** * Filters the result of a response, or runs an alternative effect if the predicate fails. * - * @category filters + * @category filtering * @since 4.0.0 */ export const filterOrElse: { @@ -521,7 +522,7 @@ export const filterOrElse: { /** * Filters successful responses, or fails with the error produced by `orFailWith` when the predicate does not match. * - * @category filters + * @category filtering * @since 4.0.0 */ export const filterOrFail: { @@ -548,7 +549,7 @@ export const filterOrFail: { /** * Filters responses by HTTP status code. * - * @category filters + * @category filtering * @since 4.0.0 */ export const filterStatus: { @@ -563,7 +564,7 @@ export const filterStatus: { /** * Filters responses that return a 2xx status code. * - * @category filters + * @category filtering * @since 4.0.0 */ export const filterStatusOk: (self: HttpClient.With) => HttpClient.With = @@ -637,7 +638,7 @@ export const make = ( Effect.withFiber((fiber) => { const scopedController = scopedRequests.get(request) const controller = scopedController ?? new AbortController() - const urlResult = UrlParams.makeUrl(request.url, request.urlParams, Option.getOrUndefined(request.hash)) + const urlResult = Url.make(request.url, request.urlParams, Option.getOrUndefined(request.hash)) if (Result.isFailure(urlResult)) { return Effect.fail( new Error.HttpClientError({ @@ -686,8 +687,10 @@ export const make = ( span.attribute("url.query", query) } const redactedHeaderNames = fiber.getRef(Headers.CurrentRedactedNames) + const headerFilter = fiber.getRef(TracerHeaderFilter) const redactedHeaders = Headers.redact(request.headers, redactedHeaderNames) for (const name in redactedHeaders) { + if (!headerFilter(name, "request")) continue span.attribute(`http.request.header.${name}`, String(redactedHeaders[name])) } request = fiber.getRef(TracerPropagationEnabled) @@ -701,6 +704,7 @@ export const make = ( span.attribute("http.response.status_code", response.status) const redactedHeaders = Headers.redact(response.headers, redactedHeaderNames) for (const name in redactedHeaders) { + if (!headerFilter(name, "response")) continue span.attribute(`http.response.header.${name}`, String(redactedHeaders[name])) } @@ -724,7 +728,7 @@ export const make = ( /** * Appends a transformation of the request object before sending it. * - * @category mapping & sequencing + * @category mapping * @since 4.0.0 */ export const mapRequest: { @@ -746,7 +750,7 @@ export const mapRequest: { /** * Appends an effectful transformation of the request object before sending it. * - * @category mapping & sequencing + * @category mapping * @since 4.0.0 */ export const mapRequestEffect: { @@ -769,7 +773,7 @@ export const mapRequestEffect: { /** * Prepends a transformation of the request object before sending it. * - * @category mapping & sequencing + * @category mapping * @since 4.0.0 */ export const mapRequestInput: { @@ -791,7 +795,7 @@ export const mapRequestInput: { /** * Prepends an effectful transformation of the request object before sending it. * - * @category mapping & sequencing + * @category mapping * @since 4.0.0 */ export const mapRequestInputEffect: { @@ -988,7 +992,8 @@ export declare namespace WithRateLimiter { * * **Details** * - * They define the backing limiter, initial limit window, keying strategy, algorithm, token cost, and whether response headers update future limits. + * They define the backing limiter, initial limit window, keying strategy, + * algorithm, token cost, retry count, and response header inspection. * * @category rate limiting * @since 4.0.0 @@ -1021,11 +1026,48 @@ export declare namespace WithRateLimiter { */ readonly tokens?: number | ((request: HttpClientRequest.HttpClientRequest) => number) | undefined /** - * Disable automatic limits updates from response headers. + * The maximum number of automatic retries after an HTTP `429` response. + * Use a non-negative integer. Defaults to an unlimited number of retries. + * Set to `0` to disable automatic retries. + */ + readonly times?: number | undefined + /** + * Overrides the response header names used for rate limit inspection. + * Header names are matched case-insensitively. An omitted field continues + * using the built-in names, while a configured field replaces them for + * that value. Header value formats are unchanged. + */ + readonly responseHeaders?: { + /** + * Header containing the maximum number of requests in a window. + */ + readonly limit?: string | undefined + /** + * Header containing the number of requests remaining in a window. + */ + readonly remaining?: string | undefined + /** + * Header containing the rate limit reset time. + */ + readonly reset?: string | undefined + /** + * Header containing the number of seconds until the rate limit resets. + */ + readonly resetAfter?: string | undefined + /** + * Header containing the retry delay for an HTTP `429` response. + */ + readonly retryAfter?: string | undefined + } | undefined + /** + * Disables automatic limit updates, `Retry-After` delays, and adaptive + * feedback from response headers. This does not disable automatic HTTP + * `429` retries. Set `times` to `0` to disable them. */ readonly disableResponseInspection?: boolean | undefined /** - * Disable adaptive learning from `Retry-After` responses. + * Disables adaptive learning from `Retry-After` responses. Response + * inspection and direct `Retry-After` delays remain enabled. */ readonly disableAdaptiveLearning?: boolean | undefined } @@ -1040,6 +1082,12 @@ export declare namespace WithRateLimiter { * automatically retries HTTP `429` responses (or `HttpClientError` values * wrapping a `429` response) by forcing the retry back through the limiter. * + * **Gotchas** + * + * Automatic HTTP `429` retries are unlimited unless `times` is specified. + * Disabling response inspection does not disable retries; set `times` to `0` + * to return or fail with the first `429`. + * * @category rate limiting * @since 4.0.0 */ @@ -1071,6 +1119,7 @@ export const withRateLimiter: { ? tokensOption : constant(tokensOption ?? 1) const adaptiveLearningEnabled = !options.disableAdaptiveLearning + const headerNames = resolveRateLimiterHeaderNames(options.responseHeaders) const getState = (key: string): RateLimiterState => { const current = states.get(key) @@ -1085,13 +1134,13 @@ export const withRateLimiter: { ? undefined : (clock: Clock, key: string, headers: Headers.Headers, tokens: number) => { const current = getState(key) - const next = parseRateLimiterState(current, clock, headers, tokens) + const next = parseRateLimiterState(current, clock, headers, tokens, headerNames) if (next.limit !== current.limit || !Duration.equals(next.window, current.window)) { states.set(key, next) } } - return transform(self, function loop(effect, request): Effect.Effect< + return transform(self, function loop(effect, request, retries = 0): Effect.Effect< HttpClientResponse.HttpClientResponse, E | RateLimiter.RateLimiterError, R @@ -1101,11 +1150,11 @@ export const withRateLimiter: { const key = resolveKey(request) const tokens = Math.max(resolveTokens(request), 1) const current = getState(key) + const canRetry = options.times === undefined || retries < options.times function retry(retryAfter: Duration.Duration | undefined) { - if (options.disableResponseInspection) return loop(effect, request) return retryAfter - ? Effect.flatMap(Effect.sleep(retryAfter), () => loop(effect, request)) - : loop(effect, request) + ? Effect.flatMap(Effect.sleep(retryAfter), () => loop(effect, request, retries + 1)) + : loop(effect, request, retries + 1) } const inspectResponse = ( response: HttpClientResponse.HttpClientResponse, @@ -1115,11 +1164,11 @@ export const withRateLimiter: { if (options.disableResponseInspection || response.status !== 429) { return Effect.succeed(undefined) } - const retryAfter = parseRetryAfter(clock, getHeader(response.headers, "retry-after")) + const retryAfter = parseRetryAfter(clock, getHeader(response.headers, ...headerNames.retryAfter)) if (retryAfter === undefined) { return Effect.succeed(undefined) } - const delay = parseRateLimitWindow(clock, response.headers) ?? retryAfter + const delay = parseRateLimitWindow(clock, response.headers, headerNames) ?? retryAfter if (adaptive === undefined) { return Effect.succeed(delay) } @@ -1153,7 +1202,7 @@ export const withRateLimiter: { const request = Effect.matchEffect(effect, { onSuccess(response) { return Effect.flatMap(inspectResponse(response, adaptive), (retryAfter) => { - if (response.status !== 429) return Effect.succeed(response) + if (response.status !== 429 || !canRetry) return Effect.succeed(response) return retry(retryAfter) }) }, @@ -1161,7 +1210,7 @@ export const withRateLimiter: { if (isTooManyRequestsHttpClientError(error)) { return Effect.flatMap( inspectResponse(error.reason.response, adaptive), - (retryAfter) => retry(retryAfter) + (retryAfter) => canRetry ? retry(retryAfter) : Effect.fail(error) ) } return Effect.fail(error) @@ -1195,6 +1244,34 @@ export const withRateLimiter: { }) }) +interface RateLimiterHeaderNames { + readonly limit: ReadonlyArray + readonly remaining: ReadonlyArray + readonly reset: ReadonlyArray + readonly resetAfter: ReadonlyArray + readonly retryAfter: ReadonlyArray +} + +const resolveRateLimiterHeaderNames = ( + responseHeaders: WithRateLimiter.Options["responseHeaders"] +): RateLimiterHeaderNames => ({ + limit: responseHeaders?.limit === undefined + ? ["ratelimit-limit", "x-ratelimit-limit"] + : [responseHeaders.limit.toLowerCase()], + remaining: responseHeaders?.remaining === undefined + ? ["ratelimit-remaining", "x-ratelimit-remaining"] + : [responseHeaders.remaining.toLowerCase()], + reset: responseHeaders?.reset === undefined + ? ["ratelimit-reset", "x-ratelimit-reset"] + : [responseHeaders.reset.toLowerCase()], + resetAfter: responseHeaders?.resetAfter === undefined + ? ["ratelimit-reset-after", "x-ratelimit-reset-after"] + : [responseHeaders.resetAfter.toLowerCase()], + retryAfter: responseHeaders?.retryAfter === undefined + ? ["retry-after"] + : [responseHeaders.retryAfter.toLowerCase()] +}) + interface RateLimiterState { readonly limit: number readonly window: Duration.Duration @@ -1205,10 +1282,11 @@ const parseRateLimiterState = ( state: RateLimiterState, clock: Clock, headers: Headers.Headers, - tokens: number + tokens: number, + headerNames: RateLimiterHeaderNames ): RateLimiterState => { - const limit = parseRateLimitLimit(state, headers, tokens) ?? state.limit - const window = parseRateLimitWindow(clock, headers) ?? state.window + const limit = parseRateLimitLimit(state, headers, tokens, headerNames) ?? state.limit + const window = parseRateLimitWindow(clock, headers, headerNames) ?? state.window if (limit === state.limit && Duration.equals(window, state.window)) { return state } @@ -1218,35 +1296,40 @@ const parseRateLimiterState = ( const parseRateLimitLimit = ( state: RateLimiterState, headers: Headers.Headers, - tokens: number + tokens: number, + headerNames: RateLimiterHeaderNames ): number | undefined => { - const raw = getHeader(headers, "ratelimit-limit", "x-ratelimit-limit") + const raw = getHeader(headers, ...headerNames.limit) const value = parseNumberHeader(raw) if (value !== undefined && value > 0) { return value } - const remaining = parseRateLimitRemaining(headers) + const remaining = parseRateLimitRemaining(headers, headerNames) if (remaining === undefined) { return undefined } return state.initial ? remaining + tokens : Math.max(remaining + tokens, state.limit) } -const parseRateLimitRemaining = (headers: Headers.Headers): number | undefined => { - const raw = getHeader(headers, "ratelimit-remaining", "x-ratelimit-remaining") +const parseRateLimitRemaining = ( + headers: Headers.Headers, + headerNames: RateLimiterHeaderNames +): number | undefined => { + const raw = getHeader(headers, ...headerNames.remaining) const value = parseNumberHeader(raw) return value !== undefined && value >= 0 ? value : undefined } const parseRateLimitWindow = ( clock: Clock, - headers: Headers.Headers + headers: Headers.Headers, + headerNames: RateLimiterHeaderNames ): Duration.Duration | undefined => { - const resetAfter = parseResetAfter(getHeader(headers, "ratelimit-reset-after", "x-ratelimit-reset-after")) + const resetAfter = parseResetAfter(getHeader(headers, ...headerNames.resetAfter)) if (resetAfter !== undefined) { return resetAfter } - return parseResetHeader(clock, getHeader(headers, "ratelimit-reset", "x-ratelimit-reset")) + return parseResetHeader(clock, getHeader(headers, ...headerNames.reset)) } const parseRetryAfter = ( @@ -1322,7 +1405,7 @@ const getHeader = (headers: Headers.Headers, ...keys: Array): string | u /** * Performs an additional effect after a successful request. * - * @category mapping & sequencing + * @category sequencing * @since 4.0.0 */ export const tap: { @@ -1344,7 +1427,7 @@ export const tap: { /** * Performs an additional effect after an unsuccessful request. * - * @category mapping & sequencing + * @category sequencing * @since 4.0.0 */ export const tapError: { @@ -1366,7 +1449,7 @@ export const tapError: { /** * Performs an additional effect on the request before sending it. * - * @category mapping & sequencing + * @category sequencing * @since 4.0.0 */ export const tapRequest: { @@ -1466,17 +1549,29 @@ export const followRedirects: { ): Effect.Effect => Effect.flatMap( self.postprocess(Effect.succeed(request)), - (response) => - response.status >= 300 && response.status < 400 && response.headers.location && - redirects < (maxRedirects ?? 10) - ? loop( - HttpClientRequest.setUrl( - request, - new URL(response.headers.location, response.request.url) - ), - redirects + 1 - ) - : Effect.succeed(response) + (response) => { + if ( + response.status < 300 || response.status >= 400 || !response.headers.location || + redirects >= (maxRedirects ?? 10) + ) { + return Effect.succeed(response) + } + const url = new URL(response.headers.location, response.request.url) + let nextRequest = request + if ( + ((response.status === 301 || response.status === 302) && request.method === "POST") || + (response.status === 303 && request.method !== "GET" && request.method !== "HEAD") + ) { + nextRequest = HttpClientRequest.setMethod(nextRequest, "GET") + nextRequest = HttpClientRequest.setBody(nextRequest, HttpBody.empty) + } + if (url.origin !== new URL(response.request.url).origin) { + nextRequest = HttpClientRequest.removeHeader(nextRequest, "authorization") + nextRequest = HttpClientRequest.removeHeader(nextRequest, "proxy-authorization") + nextRequest = HttpClientRequest.removeHeader(nextRequest, "cookie") + } + return loop(HttpClientRequest.setUrl(nextRequest, url), redirects + 1) + } ) return Effect.flatMap(request, (request) => loop(request, 0)) }, @@ -1486,7 +1581,7 @@ export const followRedirects: { /** * Context reference for a predicate that disables client-side tracing for matching outgoing requests. * - * @category references + * @category services * @since 4.0.0 */ export const TracerDisabledWhen = Context.Reference< @@ -1495,10 +1590,22 @@ export const TracerDisabledWhen = Context.Reference< defaultValue: () => constFalse }) +/** + * Context reference for filtering request and response headers added to client spans. + * + * @category services + * @since 4.0.0 + */ +export const TracerHeaderFilter = Context.Reference< + (headerName: string, phase: "request" | "response") => boolean +>("effect/http/HttpClient/TracerHeaderFilter", { + defaultValue: () => constTrue +}) + /** * Context reference that controls whether outgoing client spans are propagated to request headers. * - * @category references + * @category services * @since 4.0.0 */ export const TracerPropagationEnabled = Context.Reference("effect/HttpClient/TracerPropagationEnabled", { @@ -1508,7 +1615,7 @@ export const TracerPropagationEnabled = Context.Reference("effect/HttpC /** * Context reference for generating the span name used for outgoing client request spans. * - * @category references + * @category services * @since 4.0.0 */ export const SpanNameGenerator = Context.Reference< diff --git a/repos/effect/packages/effect/src/unstable/http/HttpClientError.ts b/repos/effect/packages/effect/src/unstable/http/HttpClientError.ts index e4909af4f2..52135c5dad 100644 --- a/repos/effect/packages/effect/src/unstable/http/HttpClientError.ts +++ b/repos/effect/packages/effect/src/unstable/http/HttpClientError.ts @@ -299,7 +299,7 @@ export type HttpClientErrorReason = RequestError | ResponseError * @category schemas * @since 4.0.0 */ -export class HttpClientErrorSchema extends Schema.ErrorClass(TypeId)({ +export class HttpClientErrorSchema extends Schema.Error(TypeId)({ _tag: Schema.tag("HttpError"), kind: Schema.Literals( [ diff --git a/repos/effect/packages/effect/src/unstable/http/HttpClientRequest.ts b/repos/effect/packages/effect/src/unstable/http/HttpClientRequest.ts index 22ba2b8f15..46751c3b3c 100644 --- a/repos/effect/packages/effect/src/unstable/http/HttpClientRequest.ts +++ b/repos/effect/packages/effect/src/unstable/http/HttpClientRequest.ts @@ -28,6 +28,8 @@ import * as Stream from "../../Stream.ts" import * as Headers from "./Headers.ts" import * as HttpBody from "./HttpBody.ts" import { hasBody, type HttpMethod } from "./HttpMethod.ts" +import * as bodyInternal from "./internal/httpBody.ts" +import * as Url from "./Url.ts" import * as UrlParams from "./UrlParams.ts" const TypeId = "~effect/http/HttpClientRequest" @@ -116,7 +118,7 @@ const Proto = { export function makeWith( method: HttpMethod, url: string, - urlParams: UrlParams.UrlParams, + urlParams: UrlParams.Input, hash: Option.Option, headers: Headers.Headers, body: HttpBody.HttpBody @@ -326,6 +328,39 @@ export const setHeaders: { self.body )) +/** + * Transforms the request headers with the provided function, returning a new request. + * + * @category combinators + * @since 4.0.0 + */ +export const updateHeaders: { + (f: (headers: Headers.Headers) => Headers.Headers): (self: HttpClientRequest) => HttpClientRequest + (self: HttpClientRequest, f: (headers: Headers.Headers) => Headers.Headers): HttpClientRequest +} = dual(2, (self: HttpClientRequest, f: (headers: Headers.Headers) => Headers.Headers): HttpClientRequest => + makeWith( + self.method, + self.url, + self.urlParams, + self.hash, + f(self.headers), + self.body + )) + +/** + * Removes a single request header by name, returning a new request. + * + * @category combinators + * @since 4.0.0 + */ +export const removeHeader: { + (key: string): (self: HttpClientRequest) => HttpClientRequest + (self: HttpClientRequest, key: string): HttpClientRequest +} = dual( + 2, + (self: HttpClientRequest, key: string): HttpClientRequest => updateHeaders(self, Headers.remove(key)) +) + /** * Sets the `Authorization` header using HTTP Basic authentication credentials. * @@ -615,23 +650,12 @@ export const setBody: { (body: HttpBody.HttpBody): (self: HttpClientRequest) => HttpClientRequest (self: HttpClientRequest, body: HttpBody.HttpBody): HttpClientRequest } = dual(2, (self: HttpClientRequest, body: HttpBody.HttpBody): HttpClientRequest => { - let headers = self.headers - if (body._tag === "Empty" || body._tag === "FormData") { - headers = Headers.remove(Headers.remove(headers, "Content-Type"), "Content-length") - } else { - if (body.contentType) { - headers = Headers.set(headers, "content-type", body.contentType) - } - if (body.contentLength !== undefined) { - headers = Headers.set(headers, "content-length", body.contentLength.toString()) - } - } return makeWith( self.method, self.url, self.urlParams, self.hash, - headers, + bodyInternal.updateHeaders(self.headers, body), body ) }) @@ -853,7 +877,7 @@ export const bodyFile: { * @since 4.0.0 */ export function toUrl(self: HttpClientRequest): Option.Option { - const r = UrlParams.makeUrl(self.url, self.urlParams, Option.getOrUndefined(self.hash)) + const r = Url.make(self.url, self.urlParams, Option.getOrUndefined(self.hash)) if (Result.isSuccess(r)) { return Option.some(r.success) } @@ -903,8 +927,8 @@ const parseContentLength = (contentLength: string | null): number | undefined => export const toWebResult = (self: HttpClientRequest, options?: { readonly signal?: AbortSignal | undefined readonly context?: Context.Context | undefined -}): Result.Result => { - const url = UrlParams.makeUrl(self.url, self.urlParams, Option.getOrUndefined(self.hash)) +}): Result.Result => { + const url = Url.make(self.url, self.urlParams, Option.getOrUndefined(self.hash)) if (Result.isFailure(url)) { return Result.fail(url.failure) } @@ -944,7 +968,7 @@ export const toWebResult = (self: HttpClientRequest, options?: { } return Result.try({ try: () => new Request(url.success, requestInit), - catch: (cause) => new UrlParams.UrlParamsError({ cause }) + catch: (cause) => new Url.UrlError({ cause }) }) } @@ -952,14 +976,14 @@ const isReadableStream = (u: unknown): u is ReadableStream => typeof ReadableStream !== "undefined" && u instanceof ReadableStream /** - * Converts an `HttpClientRequest` to a Web `Request`, failing with `UrlParamsError` when the request URL is invalid. + * Converts an `HttpClientRequest` to a Web `Request`, failing with `UrlError` when the request URL is invalid. * * @category converting * @since 4.0.0 */ export const toWeb = (self: HttpClientRequest, options?: { readonly signal?: AbortSignal | undefined -}): Effect.Effect => +}): Effect.Effect => Effect.contextWith((context) => Effect.fromResult(toWebResult(self, { context: context, diff --git a/repos/effect/packages/effect/src/unstable/http/HttpClientResponse.ts b/repos/effect/packages/effect/src/unstable/http/HttpClientResponse.ts index 4fabb07230..f070fe645d 100644 --- a/repos/effect/packages/effect/src/unstable/http/HttpClientResponse.ts +++ b/repos/effect/packages/effect/src/unstable/http/HttpClientResponse.ts @@ -93,10 +93,9 @@ export const schemaJson = < readonly headers?: Readonly> | undefined readonly body?: unknown }, - RD, - RE + RD >( - schema: Schema.Codec, + schema: Schema.ConstraintCodec, options?: ParseOptions | undefined ) => { const decode = Schema.decodeEffect(Schema.toCodecJson(schema).annotate({ options })) @@ -202,7 +201,7 @@ export const matchStatus: { /** * Succeeds with the response when its status satisfies the predicate, otherwise fails with `HttpClientError`. * - * @category filters + * @category filtering * @since 4.0.0 */ export const filterStatus: { @@ -229,7 +228,7 @@ export const filterStatus: { /** * Succeeds with the response only when its status is in the 2xx range, otherwise fails with `HttpClientError`. * - * @category filters + * @category filtering * @since 4.0.0 */ export const filterStatusOk = (self: HttpClientResponse): Effect.Effect => @@ -334,22 +333,7 @@ class WebHttpClientResponse extends Inspectable.Class implements HttpClientRespo private textBody?: Effect.Effect get text(): Effect.Effect { - if (this.textBody) { - return this.textBody - } - this.textBody = Effect.tryPromise({ - try: () => this.source.text(), - catch: (cause) => - new Error.HttpClientError({ - reason: new Error.DecodeError({ - request: this.request, - response: this, - cause - }) - }) - }).pipe(Effect.cached, Effect.runSync) - this.arrayBufferBody = Effect.map(this.textBody, (_) => new TextEncoder().encode(_).buffer) - return this.textBody + return this.textBody ??= Effect.map(this.arrayBuffer, (_) => new TextDecoder().decode(_)) } get urlParamsBody(): Effect.Effect { @@ -398,7 +382,6 @@ class WebHttpClientResponse extends Inspectable.Class implements HttpClientRespo }) }) }).pipe(Effect.cached, Effect.runSync) - this.textBody = Effect.map(this.arrayBufferBody, (_) => new TextDecoder().decode(_)) return this.arrayBufferBody } diff --git a/repos/effect/packages/effect/src/unstable/http/HttpEffect.ts b/repos/effect/packages/effect/src/unstable/http/HttpEffect.ts index 27afb26fff..872493ea5f 100644 --- a/repos/effect/packages/effect/src/unstable/http/HttpEffect.ts +++ b/repos/effect/packages/effect/src/unstable/http/HttpEffect.ts @@ -25,7 +25,7 @@ import { HttpServerRequest } from "./HttpServerRequest.ts" import * as Request from "./HttpServerRequest.ts" import type { HttpServerResponse } from "./HttpServerResponse.ts" import * as Response from "./HttpServerResponse.ts" -import { appendPreResponseHandlerUnsafe, requestPreResponseHandlers } from "./internal/preResponseHandler.ts" +import * as preResponseHandler from "./internal/preResponseHandler.ts" /** * Runs an HTTP server effect, sends the produced response with the supplied handler, and converts failures into HTTP responses. @@ -46,7 +46,7 @@ export const toHandled = ( const fiber = Fiber.getCurrent()! reportCauseUnsafe(fiber, cause) const request = Context.getUnsafe(fiber.context, HttpServerRequest) - const handler = requestPreResponseHandlers.get(request.source) + const handler = preResponseHandler.requestPreResponseHandlers.get(request.source) const cont = cause.reasons.length === 0 ? Effect.succeed(response) : Effect.failCause(cause) if (handler === undefined) { ;(request as any)[handledSymbol] = true @@ -69,7 +69,7 @@ export const toHandled = ( onSuccess: (response) => { const fiber = Fiber.getCurrent()! const request = Context.getUnsafe(fiber.context, HttpServerRequest) - const handler = requestPreResponseHandlers.get(request.source) + const handler = preResponseHandler.requestPreResponseHandlers.get(request.source) if (handler === undefined) { ;(request as any)[handledSymbol] = true return Effect.mapEager(handleResponse(request, response), () => response) @@ -172,7 +172,7 @@ const scoped = (effect: Effect.Effect) => /** * Function run with the current request and response just before the response is sent, allowing the response to be replaced or failing with `HttpServerError`. * - * @category Pre-response handlers + * @category handlers * @since 4.0.0 */ export type PreResponseHandler = ( @@ -183,7 +183,7 @@ export type PreResponseHandler = ( /** * Registers an additional pre-response handler for the current HTTP server request. * - * @category fiber refs + * @category handlers * @since 4.0.0 */ export const appendPreResponseHandler = (handler: PreResponseHandler): Effect.Effect => @@ -192,20 +192,21 @@ export const appendPreResponseHandler = (handler: PreResponseHandler): Effect.Ef return Effect.void }) -export { - /** - * Registers a pre-response handler for the supplied HTTP server request. - * - * @category fiber refs - * @since 4.0.0 - */ - appendPreResponseHandlerUnsafe -} +/** + * Registers a pre-response handler for the supplied HTTP server request. + * + * @category unsafe + * @since 4.0.0 + */ +export const appendPreResponseHandlerUnsafe: ( + request: HttpServerRequest, + handler: PreResponseHandler +) => void = preResponseHandler.appendPreResponseHandlerUnsafe /** * Runs an effect after registering a pre-response handler for the current HTTP server request. * - * @category fiber refs + * @category handlers * @since 4.0.0 */ export const withPreResponseHandler: { diff --git a/repos/effect/packages/effect/src/unstable/http/HttpIncomingMessage.ts b/repos/effect/packages/effect/src/unstable/http/HttpIncomingMessage.ts index efbb731903..c1b2f5ec37 100644 --- a/repos/effect/packages/effect/src/unstable/http/HttpIncomingMessage.ts +++ b/repos/effect/packages/effect/src/unstable/http/HttpIncomingMessage.ts @@ -78,10 +78,9 @@ export const schemaBodyJson = (schema: S, options?: export const schemaBodyUrlParams = < A, I extends Readonly | undefined>>, - RD, - RE + RD >( - schema: Schema.Codec, + schema: Schema.ConstraintCodec, options?: ParseOptions | undefined ) => { const decode = UrlParams.schemaRecord.pipe( @@ -98,8 +97,8 @@ export const schemaBodyUrlParams = < * @category schemas * @since 4.0.0 */ -export const schemaHeaders = >, RD, RE>( - schema: Schema.Codec, +export const schemaHeaders = >, RD>( + schema: Schema.ConstraintCodec, options?: ParseOptions | undefined ) => { const decode = Schema.decodeUnknownEffect(schema) diff --git a/repos/effect/packages/effect/src/unstable/http/HttpMethod.ts b/repos/effect/packages/effect/src/unstable/http/HttpMethod.ts index b491a52717..0b87104ec9 100644 --- a/repos/effect/packages/effect/src/unstable/http/HttpMethod.ts +++ b/repos/effect/packages/effect/src/unstable/http/HttpMethod.ts @@ -51,7 +51,7 @@ export declare namespace HttpMethod { /** * Returns `true` when a method can carry a request body and narrows it to `HttpMethod.WithBody`. * - * @category predicates + * @category guards * @since 4.0.0 */ export const hasBody = (method: HttpMethod): method is HttpMethod.WithBody => @@ -107,18 +107,15 @@ export const allShort = [ * * **Example** (Checking HTTP method values) * - * ```ts + * ```ts import.meta.vitest * import { HttpMethod } from "effect/unstable/http" * - * console.log(HttpMethod.isHttpMethod("GET")) - * // true - * console.log(HttpMethod.isHttpMethod("get")) - * // false - * console.log(HttpMethod.isHttpMethod(1)) - * // false + * HttpMethod.isHttpMethod("GET") // => true + * HttpMethod.isHttpMethod("get") // => false + * HttpMethod.isHttpMethod(1) // => false * ``` * - * @category refinements + * @category guards * @since 4.0.0 */ export const isHttpMethod = (u: unknown): u is HttpMethod => all.has(u as HttpMethod) diff --git a/repos/effect/packages/effect/src/unstable/http/HttpMiddleware.ts b/repos/effect/packages/effect/src/unstable/http/HttpMiddleware.ts index 4d09d91a29..e30ef3f1c9 100644 --- a/repos/effect/packages/effect/src/unstable/http/HttpMiddleware.ts +++ b/repos/effect/packages/effect/src/unstable/http/HttpMiddleware.ts @@ -23,12 +23,15 @@ import type { ReadonlyRecord } from "../../Record.ts" import { TracerEnabled } from "../../References.ts" import { ParentSpan } from "../../Tracer.ts" import * as Headers from "./Headers.ts" +import type { CompressionAlgorithm } from "./HttpPlatform.ts" +import { HttpPlatform } from "./HttpPlatform.ts" import { causeResponseStripped } from "./HttpServerError.ts" import { HttpServerRequest } from "./HttpServerRequest.ts" import * as Request from "./HttpServerRequest.ts" import * as Response from "./HttpServerResponse.ts" import type { HttpServerResponse } from "./HttpServerResponse.ts" import * as TraceContext from "./HttpTraceContext.ts" +import * as compressionInternal from "./internal/compression.ts" import { appendPreResponseHandlerUnsafe } from "./internal/preResponseHandler.ts" /** @@ -84,7 +87,7 @@ const stripSearchAndHash = (url: string): string => { /** * Runs an effect with HTTP response logging disabled for the current server request. * - * @category Logger + * @category logging * @since 4.0.0 */ export const withLoggerDisabled = (self: Effect.Effect): Effect.Effect => @@ -97,7 +100,7 @@ export const withLoggerDisabled = (self: Effect.Effect): Effec /** * Context reference for a predicate that disables server-side tracing for matching requests. * - * @category Tracer + * @category services * @since 4.0.0 */ export const TracerDisabledWhen = Context.Reference>( @@ -108,7 +111,7 @@ export const TracerDisabledWhen = Context.Reference /** * Creates a layer that disables server-side tracing for requests whose URL exactly matches one of the supplied URLs. * - * @category Tracer + * @category layers * @since 4.0.0 */ export const layerTracerDisabledForUrls = ( @@ -118,7 +121,7 @@ export const layerTracerDisabledForUrls = ( /** * Context reference for generating server span names from HTTP server requests. * - * @category Tracer + * @category services * @since 4.0.0 */ export const SpanNameGenerator = Context.Reference<(request: HttpServerRequest) => string>( @@ -129,7 +132,7 @@ export const SpanNameGenerator = Context.Reference<(request: HttpServerRequest) /** * Middleware that logs sent HTTP responses with request method, request URL, and response status annotations. * - * @category Logger + * @category logging * @since 4.0.0 */ export const logger: ( @@ -170,7 +173,7 @@ export const logger: ( /** * Middleware that creates a server trace span for each request and records request and response HTTP attributes. * - * @category Tracer + * @category tracing * @since 4.0.0 */ export const tracer: ( @@ -193,32 +196,6 @@ export const tracer: ( fiber.setContext(prevServices) const endTime = fiber.getRef(Clock).currentTimeNanosUnsafe() fiber.currentDispatcher.scheduleTask(() => { - const url = Request.toURL(request) - if (Option.isSome(url) && (url.value.username !== "" || url.value.password !== "")) { - url.value.username = "REDACTED" - url.value.password = "REDACTED" - } - const redactedHeaderNames = fiber.getRef(Headers.CurrentRedactedNames) - const requestHeaders = Headers.redact(request.headers, redactedHeaderNames) - span.attribute("http.request.method", request.method) - if (Option.isSome(url)) { - span.attribute("url.full", url.value.toString()) - span.attribute("url.path", url.value.pathname) - const query = url.value.search.slice(1) - if (query !== "") { - span.attribute("url.query", url.value.search.slice(1)) - } - span.attribute("url.scheme", url.value.protocol.slice(0, -1)) - } - if (request.headers["user-agent"] !== undefined) { - span.attribute("user_agent.original", request.headers["user-agent"]) - } - for (const name in requestHeaders) { - span.attribute(`http.request.header.${name}`, String(requestHeaders[name])) - } - if (Option.isSome(request.remoteAddress)) { - span.attribute("client.address", request.remoteAddress.value) - } let response: HttpServerResponse let spanExit = exit if (Exit.isFailure(exit)) { @@ -228,10 +205,38 @@ export const tracer: ( } else { response = exit.value } - span.attribute("http.response.status_code", response.status) - const responseHeaders = Headers.redact(response.headers, redactedHeaderNames) - for (const name in responseHeaders) { - span.attribute(`http.response.header.${name}`, String(responseHeaders[name])) + if (span.sampled) { + const url = Request.toURL(request) + if (Option.isSome(url) && (url.value.username !== "" || url.value.password !== "")) { + url.value.username = "REDACTED" + url.value.password = "REDACTED" + } + const redactedHeaderNames = fiber.getRef(Headers.CurrentRedactedNames) + const requestHeaders = Headers.redact(request.headers, redactedHeaderNames) + span.attribute("http.request.method", request.method) + if (Option.isSome(url)) { + span.attribute("url.full", url.value.toString()) + span.attribute("url.path", url.value.pathname) + const query = url.value.search.slice(1) + if (query !== "") { + span.attribute("url.query", url.value.search.slice(1)) + } + span.attribute("url.scheme", url.value.protocol.slice(0, -1)) + } + if (request.headers["user-agent"] !== undefined) { + span.attribute("user_agent.original", request.headers["user-agent"]) + } + for (const name in requestHeaders) { + span.attribute(`http.request.header.${name}`, String(requestHeaders[name])) + } + if (Option.isSome(request.remoteAddress)) { + span.attribute("client.address", request.remoteAddress.value) + } + span.attribute("http.response.status_code", response.status) + const responseHeaders = Headers.redact(response.headers, redactedHeaderNames) + for (const name in responseHeaders) { + span.attribute(`http.response.header.${name}`, String(responseHeaders[name])) + } } span.end(endTime, spanExit) }, 0) @@ -243,7 +248,7 @@ export const tracer: ( /** * Middleware that trusts `X-Forwarded-Host` and `X-Forwarded-For`, updating the request host header and remote address. * - * @category Proxying + * @category proxying * @since 4.0.0 */ export const xForwardedHeaders = make((httpApp) => @@ -263,7 +268,7 @@ export const xForwardedHeaders = make((httpApp) => /** * Middleware that parses the current request URL's search parameters and provides them as `ParsedSearchParams`. * - * @category search params + * @category parsing * @since 4.0.0 */ export const searchParamsParser = ( @@ -283,7 +288,7 @@ export const searchParamsParser = ( /** * Middleware that handles CORS preflight requests and adds configured CORS headers to HTTP responses. * - * @category CORS + * @category middleware * @since 4.0.0 */ export const cors = (options?: { @@ -401,3 +406,161 @@ export const cors = (options?: { return httpApp }) } + +/** + * Middleware that compresses HTTP response bodies based on the request's + * `Accept-Encoding` header. + * + * **Details** + * + * Content negotiation follows RFC 9110: the first algorithm in server + * preference order that the client accepts with a positive q-value and the + * platform supports is used. When no algorithm is acceptable the response is + * sent uncompressed. + * + * The body transform is performed by the `HttpPlatform` service. + * + * Responses are skipped when the status is 1xx, 204, 206, or 304, when a + * `Content-Encoding` is already present, when `Cache-Control: no-transform` + * is set, when the content type is absent or not compressible, when a known + * body length is below `minSize`, or when the body is empty or `FormData`. A + * response carrying `Content-Encoding: identity` opts out of compression and + * has the header stripped before sending. + * + * `Vary: Accept-Encoding` is set on every response that was eligible by + * status and content type, including ones skipped by negotiation or + * `minSize`. + * + * Do not combine this middleware with Deno's automatic response compression. + * On edge runtimes that already apply automatic compression, the middleware + * is unnecessary. Platforms backed by Web `CompressionStream` also cannot + * explicitly flush each input chunk, so incremental delivery depends on the + * runtime's implementation. + * + * **Security** + * + * Compression can expose secrets through BREACH-style attacks when one + * response contains both secret data and attacker-controlled input and an + * attacker can observe the compressed response length. For affected routes, + * disable compression with `Content-Encoding: identity` or + * `Cache-Control: no-transform`, or use `compressible` to restrict which + * response content types can be compressed. + * + * @category compression + * @since 4.0.0 + */ +export const compression = ( + options?: { + /** + * Server preference order. Negotiation picks the first accepted algorithm + * supported by the platform. Defaults to `["br", "gzip", "deflate"]`; + * `zstd` must be explicitly opted into. + */ + readonly algorithms?: ReadonlyArray | undefined + /** + * Minimum body size in bytes when the length is known. Unknown-length bodies + * are always compressed. Defaults to `1024`. + */ + readonly minSize?: number | undefined + /** Replaces the default content-type predicate. */ + readonly compressible?: ((contentType: string) => boolean) | undefined + /** Per-algorithm levels. Platforms without a level knob ignore them. */ + readonly levels?: { + readonly gzip?: number | undefined + readonly deflate?: number | undefined + readonly br?: number | undefined + readonly zstd?: number | undefined + } | undefined + } | undefined +): ( + httpApp: Effect.Effect +) => Effect.Effect => { + const preferred = options?.algorithms ?? defaultAlgorithms + const minSize = options?.minSize ?? 1024 + const compressible = options?.compressible ?? compressionInternal.defaultCompressible + const levels = { ...defaultLevels, ...options?.levels } + const levelOptions: Record = { + gzip: { level: levels.gzip }, + deflate: { level: levels.deflate }, + br: { level: levels.br }, + zstd: { level: levels.zstd } + } + const transform = ( + compression: HttpPlatform["Service"]["compression"], + acceptEncoding: string | undefined, + response: HttpServerResponse + ): Effect.Effect => { + if ( + response.status < 200 || + response.status === 204 || + response.status === 206 || + response.status === 304 + ) { + return Effect.succeed(response) + } + const currentEncoding = response.headers["content-encoding"] + if (currentEncoding !== undefined) { + return Effect.succeed( + currentEncoding.trim().toLowerCase() === "identity" + ? Response.removeHeader(response, "content-encoding") + : response + ) + } + const body = response.body + if (body._tag === "Empty" || body._tag === "FormData") { + return Effect.succeed(response) + } + const cacheControl = response.headers["cache-control"] + if (cacheControl !== undefined && noTransformRegex.test(cacheControl)) { + return Effect.succeed(response) + } + const contentType = response.headers["content-type"] ?? body.contentType + if (contentType === undefined || !compressible(contentType)) { + return Effect.succeed(response) + } + const algorithm = compressionInternal.negotiate(acceptEncoding, preferred, compression.algorithms) + if (algorithm === undefined) { + return Effect.succeed(withVary(response)) + } + const contentLength = body.contentLength ?? contentLengthHeader(response.headers) + if (contentLength !== undefined && contentLength < minSize) { + return Effect.succeed(withVary(response)) + } + return compression.compressResponse(response, algorithm, levelOptions[algorithm]) + } + return ( + httpApp: Effect.Effect + ): Effect.Effect => + Effect.withFiber((fiber) => { + const request = Context.getUnsafe(fiber.context, HttpServerRequest) + const compression = Context.getUnsafe(fiber.context, HttpPlatform).compression + appendPreResponseHandlerUnsafe(request, (request, response) => + transform(compression, request.headers["accept-encoding"], response)) + return httpApp + }) +} + +const withVary = (response: HttpServerResponse): HttpServerResponse => { + const vary = compressionInternal.varyAcceptEncoding(response.headers) + return vary === undefined ? response : Response.setHeader(response, "vary", vary) +} + +const defaultAlgorithms: ReadonlyArray = ["br", "gzip", "deflate"] + +const defaultLevels = { + gzip: 6, + deflate: 6, + br: 4, + zstd: 3 +} as const + +const noTransformRegex = /(?:^|[\s,])no-transform(?:$|[\s,;])/i + +const contentLengthHeader = (headers: Headers.Headers): number | undefined => { + const value = headers["content-length"] + if (value === undefined) { + return undefined + } + const parsed = Number(value) + return Number.isFinite(parsed) && parsed >= 0 ? parsed : undefined +} diff --git a/repos/effect/packages/effect/src/unstable/http/HttpPlatform.ts b/repos/effect/packages/effect/src/unstable/http/HttpPlatform.ts index d4c8b2d7ae..7c687888fc 100644 --- a/repos/effect/packages/effect/src/unstable/http/HttpPlatform.ts +++ b/repos/effect/packages/effect/src/unstable/http/HttpPlatform.ts @@ -21,6 +21,7 @@ import * as Etag from "./Etag.ts" import * as Headers from "./Headers.ts" import type * as Body from "./HttpBody.ts" import * as Response from "./HttpServerResponse.ts" +import * as internal from "./internal/compression.ts" /** * Service for platform-specific HTTP response helpers, including file-backed server responses. @@ -29,6 +30,8 @@ import * as Response from "./HttpServerResponse.ts" * @since 4.0.0 */ export class HttpPlatform extends Context.Service make({ + platform: "web", + compression: internal.compressionWeb, fileResponse(path, status, statusText, headers, start, end, contentLength) { return Response.stream( fs.stream(path, { @@ -152,14 +161,128 @@ export const layer = Layer.effect(HttpPlatform)( { contentLength, headers, status, statusText } ) }, - fileWebResponse(file, status, statusText, headers, _options) { - return Response.stream( - Stream.fromReadableStream({ + fileWebResponse(file, status, statusText, headers, options) { + const offset = Number(options?.offset ?? 0) + const bytesToRead = options?.bytesToRead !== undefined ? Number(options.bytesToRead) : undefined + const chunkSize = options?.chunkSize !== undefined ? Math.max(1, Number(options.chunkSize)) : Infinity + const end = offset + (bytesToRead ?? Infinity) + const stream = end <= offset + ? Stream.empty + : Stream.fromReadableStream({ evaluate: () => file.stream() as ReadableStream, onError: identity - }), - { headers, status, statusText } - ) + }).pipe( + Stream.mapAccum( + () => 0, + (position, bytes) => { + const next = position + bytes.length + const start = Math.min(Math.max(offset - position, 0), bytes.length) + const stop = Math.min(Math.max(end - position, 0), bytes.length) + const chunks: Array<{ readonly bytes: Uint8Array; readonly done: boolean }> = [] + for (let index = start; index < stop; index += chunkSize) { + chunks.push({ + bytes: bytes.subarray(index, Math.min(index + chunkSize, stop)), + done: next >= end && index + chunkSize >= stop + }) + } + return [next, chunks] + } + ), + Stream.takeUntil((chunk) => chunk.done), + Stream.map((chunk) => chunk.bytes) + ) + return Response.stream(stream, { + contentLength: bytesToRead ?? file.size - offset, + headers, + status, + statusText + }) } })) ).pipe(Layer.provide(Etag.layerWeak)) + +/** + * Content codings that HTTP response compression can apply. + * + * @category compression + * @since 4.0.0 + */ +export type CompressionAlgorithm = "gzip" | "deflate" | "br" | "zstd" + +/** + * Options passed to a platform when compressing a response body. + * + * **Details** + * + * The `level` scale depends on the algorithm. Platforms without a level knob, + * such as the Web `CompressionStream` implementation, ignore it. + * + * @category compression + * @since 4.0.0 + */ +export interface CompressionOptions { + readonly level?: number | undefined +} + +/** + * Platform primitive for HTTP response compression. + * + * **Details** + * + * `algorithms` advertises what the platform can encode; content negotiation + * happens in the shared `HttpMiddleware.compression` middleware. + * + * `compressResponse` is only called when compression is definitely happening — + * all skip logic runs in the shared middleware first. The platform owns the + * body transform and removes `Content-Length` when the compressed size is not + * known in advance. The `make` wrapper owns the `Content-Encoding` and `Vary` + * headers. + * + * @category compression + * @since 4.0.0 + */ +export interface Compression { + readonly algorithms: ReadonlySet + readonly compressResponse: ( + response: Response.HttpServerResponse, + algorithm: CompressionAlgorithm, + options?: CompressionOptions | undefined + ) => Effect.Effect +} + +/** + * Creates a compression body transform backed by the Web `CompressionStream` + * API, for use with `makeCompressionWeb`. + * + * **Details** + * + * The format string is passed through to the runtime, so runtime-specific + * formats such as Bun's `"brotli"` and `"zstd"` are usable. `CompressionStream` + * has no compression level knob, so `CompressionOptions.level` does not apply. + * + * @category compression + * @since 4.0.0 + */ +export const compressionTransformWeb: ( + format: string +) => (stream: ReadableStream) => ReadableStream = internal.compressionTransformWeb + +/** + * Creates a `Compression` implementation from Web `ReadableStream` + * transforms. + * + * **Details** + * + * All supported bodies are transformed as streams. The `Content-Length` + * header is dropped in every case. + * + * @category compression + * @since 4.0.0 + */ +export const makeCompressionWeb: (options: { + readonly algorithms: Iterable + readonly transform: ( + algorithm: CompressionAlgorithm, + options?: CompressionOptions | undefined + ) => (stream: ReadableStream) => ReadableStream +}) => Compression = internal.makeCompressionWeb diff --git a/repos/effect/packages/effect/src/unstable/http/HttpRouter.ts b/repos/effect/packages/effect/src/unstable/http/HttpRouter.ts index 3a52c4acd7..742d5fd404 100644 --- a/repos/effect/packages/effect/src/unstable/http/HttpRouter.ts +++ b/repos/effect/packages/effect/src/unstable/http/HttpRouter.ts @@ -41,7 +41,7 @@ const TypeId = "~effect/http/HttpRouter" * and expose the registered routes as an Effect that handles the current server * request. * - * @category HttpRouter + * @category services * @since 4.0.0 */ export interface HttpRouter { @@ -97,7 +97,7 @@ export interface HttpRouter { * Route and middleware layers require this service to register themselves with * the router. * - * @category HttpRouter + * @category services * @since 4.0.0 */ export const HttpRouter: Context.Service = Context.Service( @@ -112,7 +112,7 @@ export const HttpRouter: Context.Service = Context.Servi * The returned router accepts route and middleware registrations and later routes * the current `HttpServerRequest` to the matching `HttpServerResponse`. * - * @category HttpRouter + * @category constructors * @since 4.0.0 */ export const make = Effect.gen(function*() { @@ -190,8 +190,8 @@ export const make = Effect.gen(function*() { }), asHttpEffect() { let handler = Effect.withFiber((fiber) => { - const contextMap = new Map(fiber.context.mapUnsafe) - const request = contextMap.get(HttpServerRequest.HttpServerRequest.key) as HttpServerRequest.HttpServerRequest + let context = fiber.context + const request = Context.getUnsafe(context, HttpServerRequest.HttpServerRequest) let result = router.find(request.method, request.url) if (result === undefined && request.method === "HEAD") { result = router.find("GET", request.url) @@ -205,26 +205,30 @@ export const make = Effect.gen(function*() { } const route = result.handler if (Option.isSome(route.prefix)) { - contextMap.set(HttpServerRequest.HttpServerRequest.key, sliceRequestUrl(request, route.prefix.value)) + context = Context.add( + context, + HttpServerRequest.HttpServerRequest, + sliceRequestUrl(request, route.prefix.value) + ) } - contextMap.set(HttpServerRequest.ParsedSearchParams.key, result.searchParams) - contextMap.set(RouteContext.key, { + context = Context.add(context, HttpServerRequest.ParsedSearchParams, result.searchParams) + context = Context.add(context, RouteContext, { route, params: result.params }) - const span = contextMap.get(Tracer.ParentSpan.key) as Tracer.Span | undefined + const span = Context.getOrUndefined(context, Tracer.ParentSpan) if (span && span._tag === "Span") { span.attribute("http.route", route.path) } - return Effect.provideContext( + return Effect.updateContext( (route.uninterruptible ? route.handler : Effect.interruptible(route.handler)) as Effect.Effect< HttpServerResponse.HttpServerResponse, unknown >, - Context.makeUnsafe(contextMap) + () => context ) }) if (middleware.size === 0) return handler @@ -249,7 +253,7 @@ function sliceRequestUrl(request: HttpServerRequest.HttpServerRequest, prefix: s * The value is passed to the route matcher when an `HttpRouter` is created and * defaults to an empty configuration. * - * @category configuration + * @category services * @since 4.0.0 */ export const RouterConfig = Context.Reference>( @@ -313,10 +317,9 @@ export const schemaJson = < readonly searchParams: Readonly | undefined>> readonly body: any }>, - RD, - RE + RD >( - schema: Schema.Codec, + schema: Schema.ConstraintCodec, options?: ParseOptions | undefined ): Effect.Effect< A, @@ -368,10 +371,9 @@ export const schemaNoBody = < readonly pathParams: Readonly> readonly searchParams: Readonly | undefined>> }>, - RD, - RE + RD >( - schema: Schema.Codec, + schema: Schema.ConstraintCodec, options?: ParseOptions | undefined ): Effect.Effect< A, @@ -410,8 +412,8 @@ export const schemaNoBody = < * @category schemas * @since 4.0.0 */ -export const schemaParams = | undefined>>, RD, RE>( - schema: Schema.Codec, +export const schemaParams = | undefined>>, RD>( + schema: Schema.ConstraintCodec, options?: ParseOptions | undefined ): Effect.Effect => { const parse = Schema.decodeUnknownEffect(schema) @@ -429,8 +431,8 @@ export const schemaParams = >, RD, RE>( - schema: Schema.Codec, +export const schemaPathParams = >, RD>( + schema: Schema.ConstraintCodec, options?: ParseOptions | undefined ): Effect.Effect => { const parse = Schema.decodeUnknownEffect(schema) @@ -448,18 +450,29 @@ export const schemaPathParams = + * router.add("GET", "/health", HttpServerResponse.text("ready")) + * ) * - * // then use `yield* router.add(...)` to add a route - * })) + * const program = Effect.acquireUseRelease( + * Effect.sync(() => HttpRouter.toWebHandler(Routes, { disableLogger: true })), + * ({ handler }) => + * Effect.gen(function*() { + * const response = yield* Effect.promise(() => handler(new Request("http://localhost/health"))) + * const body = yield* Effect.promise(() => response.text()) + * return body + * }), + * ({ dispose }) => Effect.promise(dispose) + * ) + * + * await Effect.runPromise(program) // => "ready" * ``` * - * @category HttpRouter + * @category layers * @since 4.0.0 */ export const use = ( @@ -471,18 +484,19 @@ export const use = ( * * **Example** (Adding a GET route) * - * ```ts - * import { Effect } from "effect" + * ```ts import.meta.vitest + * import { Layer } from "effect" * import { HttpRouter, HttpServerResponse } from "effect/unstable/http" * * const Route = HttpRouter.add( * "GET", * "/hello", - * Effect.succeed(HttpServerResponse.text("Hello, World!")) + * HttpServerResponse.text("Hello, World!") * ) + * Layer.isLayer(Route) // => true * ``` * - * @category HttpRouter + * @category layers * @since 4.0.0 */ export const add = ( @@ -503,20 +517,21 @@ export const add = ( * * **Example** (Adding multiple routes) * - * ```ts - * import { Effect } from "effect" + * ```ts import.meta.vitest + * import { Layer } from "effect" * import { HttpRouter, HttpServerResponse } from "effect/unstable/http" * * const Routes = HttpRouter.addAll([ * HttpRouter.route( * "GET", * "/hello", - * Effect.succeed(HttpServerResponse.text("Hello, World!")) + * HttpServerResponse.text("Hello, World!") * ) * ]) + * Layer.isLayer(Routes) // => true * ``` * - * @category HttpRouter + * @category layers * @since 4.0.0 */ export const addAll = >, EX = never, RX = never>( @@ -544,7 +559,7 @@ export const addAll = >, EX = never /** * Layer that provides a newly constructed `HttpRouter`. * - * @category HttpRouter + * @category layers * @since 4.0.0 */ export const layer: Layer.Layer = Layer.effect(HttpRouter)(make) @@ -559,7 +574,7 @@ export const layer: Layer.Layer = Layer.effect(HttpRouter)(make) * `Scope`; route request markers are converted into the ordinary requirements of * the returned handler. * - * @category HttpRouter + * @category converting * @since 4.0.0 */ export const toHttpEffect = ( @@ -590,7 +605,7 @@ const RouteTypeId = "~effect/http/HttpRouter/Route" * A route pairs an HTTP method and path pattern with a response handler, plus * metadata used for prefix handling and interruptibility. * - * @category Route + * @category routes * @since 4.0.0 */ export interface Route { @@ -612,7 +627,7 @@ export declare namespace Route { /** * Extracts the error type produced by a `Route` handler. * - * @category Route + * @category routes * @since 4.0.0 */ export type Error> = R extends Route ? E : never @@ -620,7 +635,7 @@ export declare namespace Route { /** * Extracts the context requirements of a `Route` handler. * - * @category Route + * @category routes * @since 4.0.0 */ export type Context> = T extends Route ? R : never @@ -648,7 +663,7 @@ const makeRoute = (options: { * function from the current request to a response effect. Set `uninterruptible` to * prevent the route handler from being made interruptible while it runs. * - * @category Route + * @category routes * @since 4.0.0 */ export const route = ( @@ -678,7 +693,7 @@ export const route = ( * Path pattern accepted by the router. Routes must use an absolute path * beginning with `/` or the wildcard `*`. * - * @category PathInput + * @category models * @since 4.0.0 */ export type PathInput = `/${string}` | "*" @@ -695,7 +710,7 @@ const removeTrailingSlash = ( * Trailing slashes are removed from the prefix; `/` becomes the prefix itself and * `*` becomes a wildcard route under the prefix. * - * @category PathInput + * @category transforming * @since 4.0.0 */ export const prefixPath: { @@ -717,7 +732,7 @@ export const prefixPath: { * request, the matched prefix can be removed from the request URL seen by the * handler. * - * @category Route + * @category routes * @since 4.0.0 */ export const prefixRoute: { @@ -737,7 +752,7 @@ export const prefixRoute: { * Represents a request-level dependency, that needs to be provided by * middleware. * - * @category Request types + * @category utility types * @since 4.0.0 */ export interface Request { @@ -756,7 +771,7 @@ export declare namespace Request { /** * Wraps a type in a request-level marker of the supplied kind. * - * @category Request types + * @category utility types * @since 4.0.0 */ export type From = R extends infer T ? Request : never @@ -765,7 +780,7 @@ export declare namespace Request { * Extracts the payload types from request-level markers that have the supplied * kind. * - * @category Request types + * @category utility types * @since 4.0.0 */ export type Only = A extends Request ? T : never @@ -774,7 +789,7 @@ export declare namespace Request { * Removes request-level markers from a union, leaving only ordinary requirement * or error types. * - * @category Request types + * @category utility types * @since 4.0.0 */ export type Without = A extends Request ? never : A @@ -784,7 +799,7 @@ export declare namespace Request { * Services provided by the HTTP router, which are available in the * request context. * - * @category Request types + * @category utility types * @since 4.0.0 */ export type Provided = @@ -796,7 +811,7 @@ export type Provided = /** * Services provided to global middleware. * - * @category Request types + * @category utility types * @since 4.0.0 */ export type GlobalProvided = @@ -869,50 +884,35 @@ export interface Middleware< * * **Example** (Applying route and global middleware) * - * ```ts - * import { Context, Effect, Layer } from "effect" - * import { HttpMiddleware, HttpRouter, HttpServerResponse } from "effect/unstable/http" - * - * // Here we are defining a CORS middleware - * const CorsMiddleware = HttpRouter.middleware(HttpMiddleware.cors()).layer - * // You can also use HttpRouter.cors() to create a CORS middleware - * - * class CurrentSession extends Context.Service()("CurrentSession") {} - * - * // You can create middleware that provides a service to the HTTP requests. - * const SessionMiddleware = HttpRouter.middleware<{ - * provides: CurrentSession - * }>()( - * Effect.gen(function*() { - * yield* Effect.log("SessionMiddleware initialized") - * - * return (httpEffect) => - * Effect.provideService(httpEffect, CurrentSession, { - * token: "dummy-token" - * }) - * }) + * ```ts import.meta.vitest + * import { Effect, Layer } from "effect" + * import { HttpRouter, HttpServerResponse } from "effect/unstable/http" + * + * const RouteMiddleware = HttpRouter.middleware((httpEffect) => + * Effect.map(httpEffect, HttpServerResponse.setHeader("x-route", "route")) * ).layer * - * Effect.gen(function*() { - * const router = yield* HttpRouter.HttpRouter - * yield* router.add( - * "GET", - * "/hello", + * const GlobalMiddleware = HttpRouter.middleware( + * (httpEffect) => Effect.map(httpEffect, HttpServerResponse.setHeader("x-global", "global")), + * { global: true } + * ) + * + * const Routes = HttpRouter.add("GET", "/hello", HttpServerResponse.text("Hello")).pipe( + * Layer.provide(RouteMiddleware) + * ) + * const App = Layer.mergeAll(Routes, GlobalMiddleware) + * + * const program = Effect.acquireUseRelease( + * Effect.sync(() => HttpRouter.toWebHandler(App, { disableLogger: true })), + * ({ handler }) => * Effect.gen(function*() { - * // Requests can now access the current session - * const session = yield* CurrentSession - * return HttpServerResponse.text( - * `Hello, World! Your token is ${session.token}` - * ) - * }) - * ) - * }).pipe( - * Layer.effectDiscard, - * // Provide the SessionMiddleware & CorsMiddleware to some routes - * Layer.provide([SessionMiddleware, CorsMiddleware]) + * const response = yield* Effect.promise(() => handler(new Request("http://localhost/hello"))) + * return [response.headers.get("x-route"), response.headers.get("x-global")] + * }), + * ({ dispose }) => Effect.promise(dispose) * ) + * + * await Effect.runPromise(program) // => ["route", "global"] * ``` * * @category middleware @@ -1149,7 +1149,7 @@ export declare namespace middleware { /** * Middleware that applies CORS headers to the HTTP response. * - * @category middleware + * @category layers * @since 4.0.0 */ export const cors = ( @@ -1168,21 +1168,22 @@ export const cors = ( * * **Example** (Disabling route logging) * - * ```ts - * import { Effect, Layer } from "effect" + * ```ts import.meta.vitest + * import { Layer } from "effect" * import { HttpRouter, HttpServerResponse } from "effect/unstable/http" * * const Route = HttpRouter.add( * "GET", * "/hello", - * Effect.succeed(HttpServerResponse.text("Hello, World!")) + * HttpServerResponse.text("Hello, World!") * ).pipe( * // disable the logger for this route * Layer.provide(HttpRouter.disableLogger) * ) + * Layer.isLayer(Route) // => true * ``` * - * @category middleware + * @category layers * @since 4.0.0 */ export const disableLogger: Layer.Layer = middleware(HttpMiddleware.withLoggerDisabled).layer @@ -1190,7 +1191,7 @@ export const disableLogger: Layer.Layer = middleware(HttpMiddleware.withL /** * Provides request-level dependencies to some routes. * - * @category middleware + * @category layers * @since 4.0.0 */ export const provideRequest = @@ -1215,7 +1216,7 @@ export const provideRequest = /** * Runs the provided application layer as an HTTP server. * - * @category server + * @category layers * @since 4.0.0 */ export const serve = | Request.Only<"GlobalRequires", R>>( @@ -1279,7 +1280,7 @@ export const serve = | Request.On * Web `Response` values and a `dispose` function for releasing the layer * resources. * - * @category server + * @category converting * @since 4.0.0 */ export const toWebHandler = < @@ -1292,7 +1293,8 @@ export const toWebHandler = < | Request<"Error", any> | Request<"GlobalError", any>, HE, - HR = Exclude | Request.Only<"GlobalRequires", R>, A> + HR = Exclude | Request.Only<"GlobalRequires", R>, A>, + ReqR = Exclude >( appLayer: Layer.Layer, options?: { @@ -1318,12 +1320,15 @@ export const toWebHandler = < | Request.Only<"Requires", R> | Request.Only<"GlobalRequires", R> > - ) => Effect.Effect + ) => Effect.Effect } ): { - readonly handler: [HR] extends [never] + readonly handler: [ReqR] extends [never] ? ((request: globalThis.Request, context?: Context.Context | undefined) => Promise) - : ((request: globalThis.Request, context: Context.Context


) => Promise) + : (( + request: globalThis.Request, + context: Context.Context + ) => Promise) readonly dispose: () => Promise } => { let middleware: any = options?.middleware diff --git a/repos/effect/packages/effect/src/unstable/http/HttpServer.ts b/repos/effect/packages/effect/src/unstable/http/HttpServer.ts index dc40fb9d84..f8de5dbb35 100644 --- a/repos/effect/packages/effect/src/unstable/http/HttpServer.ts +++ b/repos/effect/packages/effect/src/unstable/http/HttpServer.ts @@ -32,7 +32,7 @@ import type { HttpServerResponse } from "./HttpServerResponse.ts" * The service can serve an HTTP response effect and exposes the address where the * server is listening. * - * @category models + * @category services * @since 4.0.0 */ export class HttpServer extends Context.Service { @@ -226,7 +226,7 @@ export const formatAddress = (address: Address): string => { * Reads the current server address, formats it with `formatAddress`, and passes * the formatted address to the supplied effectful function. * - * @category address + * @category accessors * @since 4.0.0 */ export const addressFormattedWith = ( @@ -240,7 +240,7 @@ export const addressFormattedWith = ( /** * Logs the formatted address of the current HTTP server. * - * @category address + * @category logging * @since 4.0.0 */ export const logAddress: Effect.Effect = addressFormattedWith((_) => @@ -250,7 +250,7 @@ export const logAddress: Effect.Effect = addressFormatt /** * Adds address logging to a layer that provides an `HttpServer`. * - * @category address + * @category layers * @since 4.0.0 */ export const withLogAddress = ( diff --git a/repos/effect/packages/effect/src/unstable/http/HttpServerError.ts b/repos/effect/packages/effect/src/unstable/http/HttpServerError.ts index 7fb6aeffa8..ad39dbef9d 100644 --- a/repos/effect/packages/effect/src/unstable/http/HttpServerError.ts +++ b/repos/effect/packages/effect/src/unstable/http/HttpServerError.ts @@ -178,7 +178,7 @@ export class InternalError extends Data.TaggedError("InternalError")<{ /** * Returns `true` when the supplied value is an `HttpServerError`. * - * @category predicates + * @category guards * @since 4.0.0 */ export const isHttpServerError = (u: unknown): u is HttpServerError => hasProperty(u, TypeId) @@ -249,7 +249,7 @@ export class ServeError extends Data.TaggedError("ServeError")<{ * `causeResponse` uses this annotation to map a pure client abort to a `499` * response instead of a server abort response. * - * @category annotations + * @category services * @since 4.0.0 */ export class ClientAbort extends Context.Service()("effect/http/HttpServerError/ClientAbort") { diff --git a/repos/effect/packages/effect/src/unstable/http/HttpServerRequest.ts b/repos/effect/packages/effect/src/unstable/http/HttpServerRequest.ts index 83e6c3e6f9..ff4904116c 100644 --- a/repos/effect/packages/effect/src/unstable/http/HttpServerRequest.ts +++ b/repos/effect/packages/effect/src/unstable/http/HttpServerRequest.ts @@ -16,6 +16,7 @@ import * as Context from "../../Context.ts" import * as Effect from "../../Effect.ts" import type * as FileSystem from "../../FileSystem.ts" import * as Inspectable from "../../Inspectable.ts" +import * as InternalRecord from "../../internal/record.ts" import * as Option from "../../Option.ts" import type * as Path from "../../Path.ts" import type { ReadonlyRecord } from "../../Record.ts" @@ -44,7 +45,7 @@ export { * Use to configure the maximum body size accepted while reading server * request bodies. * - * @category fiber refs + * @category references * @since 4.0.0 */ MaxBodySize @@ -104,7 +105,7 @@ export interface HttpServerRequest extends HttpIncomingMessage.HttpIncomingMessa * Use to access the request currently being handled by HTTP server routes and * middleware. * - * @category context + * @category services * @since 4.0.0 */ export const HttpServerRequest: Context.Service = Context.Service( @@ -124,7 +125,7 @@ export const HttpServerRequest: Context.Service> => { const out: Record> = {} for (const [key, value] of url.searchParams.entries()) { - const entry = out[key] - if (entry !== undefined) { + if (Object.hasOwn(out, key)) { + const entry = out[key] if (Array.isArray(entry)) { entry.push(value) } else { - out[key] = [entry, value] + InternalRecord.assignProperty(out, key, [entry, value]) } } else { - out[key] = value + InternalRecord.assignProperty(out, key, value) } } return out @@ -191,8 +192,8 @@ export const upgradeChannel = (): Channel.Channel< * @category schemas * @since 4.0.0 */ -export const schemaCookies = >, RD, RE>( - schema: Schema.Codec, +export const schemaCookies = >, RD>( + schema: Schema.ConstraintCodec, options?: ParseOptions | undefined ): Effect.Effect => { const parse = Schema.decodeUnknownEffect(schema) @@ -205,8 +206,8 @@ export const schemaCookies = >, RD, RE>( - schema: Schema.Codec, +export const schemaHeaders = >, RD>( + schema: Schema.ConstraintCodec, options?: ParseOptions | undefined ): Effect.Effect => { const parse = Schema.decodeUnknownEffect(schema) @@ -222,10 +223,9 @@ export const schemaHeaders = | undefined>>, - RD, - RE + RD >( - schema: Schema.Codec, + schema: Schema.ConstraintCodec, options?: ParseOptions | undefined ): Effect.Effect => { const parse = Schema.decodeUnknownEffect(schema) @@ -242,8 +242,8 @@ export const schemaSearchParams = < * @category schemas * @since 4.0.0 */ -export const schemaBodyJson = ( - schema: Schema.Codec, +export const schemaBodyJson = ( + schema: Schema.ConstraintDecoder, options?: ParseOptions | undefined ): Effect.Effect => { const parse = HttpIncomingMessage.schemaBodyJson(schema, options) @@ -265,12 +265,15 @@ const isMultipart = (request: HttpServerRequest) => * @category schemas * @since 4.0.0 */ -export const schemaBodyForm = , RD, RE>( - schema: Schema.Codec, +export const schemaBodyForm = , RD>( + schema: Schema.ConstraintCodec, options?: ParseOptions | undefined ) => { const parseMultipart = Multipart.schemaPersisted(schema) - const parseUrlParams = HttpIncomingMessage.schemaBodyUrlParams(schema as Schema.Codec, options) + const parseUrlParams = HttpIncomingMessage.schemaBodyUrlParams( + schema as Schema.ConstraintCodec, + options + ) return Effect.flatMap(HttpServerRequest, (request): Effect.Effect< A, Multipart.MultipartError | Schema.SchemaError | HttpServerError, @@ -293,10 +296,9 @@ export const schemaBodyForm = , RD, RE export const schemaBodyUrlParams = < A, I extends Readonly | undefined>>, - RD, - RE + RD >( - schema: Schema.Codec, + schema: Schema.ConstraintCodec, options?: ParseOptions | undefined ): Effect.Effect => { const parse = HttpIncomingMessage.schemaBodyUrlParams(schema, options) @@ -315,8 +317,8 @@ export const schemaBodyUrlParams = < * @category schemas * @since 4.0.0 */ -export const schemaBodyMultipart = , RD, RE>( - schema: Schema.Codec, +export const schemaBodyMultipart = , RD>( + schema: Schema.ConstraintCodec, options?: ParseOptions | undefined ): Effect.Effect< A, @@ -342,8 +344,8 @@ export const schemaBodyMultipart = , R * @category schemas * @since 4.0.0 */ -export const schemaBodyFormJson = ( - schema: Schema.Codec, +export const schemaBodyFormJson = ( + schema: Schema.ConstraintDecoder, options?: ParseOptions | undefined ) => { const parseMultipart = Multipart.schemaJson(schema, options) @@ -434,14 +436,19 @@ export const toClientRequest = (request: HttpServerRequest): HttpClientRequest.H Option.getOrElse(toURL(request), () => request.url) ) -const toClientBody = (request: HttpServerRequest): HttpBody.HttpBody => - hasBody(request.method) +const toClientBody = (request: HttpServerRequest): HttpBody.HttpBody => { + if (!hasBody(request.method)) { + return HttpBody.empty + } + const formData = getFormDataBody(request) + return formData === undefined ? HttpBody.stream( request.stream, request.headers["content-type"], parseContentLength(request.headers["content-length"]) ) - : HttpBody.empty + : HttpBody.formData(formData) +} const parseContentLength = (contentLength: string | undefined): number | undefined => { if (contentLength === undefined) { @@ -861,25 +868,25 @@ const rawBodyStream = (request: HttpServerRequest, body: unknown): Stream.Stream if (body instanceof Request) { return streamFromReadable(request, body.body) } - if (isFormData(body)) { - return streamFromReadable(request, new Response(body).body) - } if (isReadableStream(body)) { return streamFromReadable(request, body) } + if (isBodyInit(body)) { + return streamFromReadable(request, new Response(body).body) + } return Stream.fail(requestParseError(request, "Unsupported body type")) } const rawBodyBytes = (request: HttpServerRequest, body: unknown): Effect.Effect => { - if (body instanceof Blob) { - return bytesFromBodyInit(request, body) - } if (body instanceof Request) { return Effect.tryPromise({ try: () => body.arrayBuffer().then((buffer) => new Uint8Array(buffer)), catch: (cause) => requestParseError(request, undefined, cause) }) } + if (isBodyInit(body)) { + return bytesFromBodyInit(request, body) + } return Effect.fail(requestParseError(request, "Unsupported body type")) } @@ -993,6 +1000,14 @@ const isReadableStream = (u: unknown): u is ReadableStream => const isFormData = (u: unknown): u is FormData => typeof FormData !== "undefined" && u instanceof FormData +const isBodyInit = (u: unknown): u is BodyInit => + typeof u === "string" || + (typeof ArrayBuffer !== "undefined" && (u instanceof ArrayBuffer || ArrayBuffer.isView(u))) || + (typeof Blob !== "undefined" && u instanceof Blob) || + isFormData(u) || + (typeof URLSearchParams !== "undefined" && u instanceof URLSearchParams) || + isReadableStream(u) + const textDecoder = new TextDecoder() /** diff --git a/repos/effect/packages/effect/src/unstable/http/HttpServerResponse.ts b/repos/effect/packages/effect/src/unstable/http/HttpServerResponse.ts index ef9d4343da..14c8c427d8 100644 --- a/repos/effect/packages/effect/src/unstable/http/HttpServerResponse.ts +++ b/repos/effect/packages/effect/src/unstable/http/HttpServerResponse.ts @@ -34,6 +34,7 @@ import * as HttpClientRequest from "./HttpClientRequest.ts" import * as HttpClientResponse from "./HttpClientResponse.ts" import * as HttpIncomingMessage from "./HttpIncomingMessage.ts" import type { HttpPlatform } from "./HttpPlatform.ts" +import * as bodyInternal from "./internal/httpBody.ts" import * as Template from "./Template.ts" import * as UrlParams from "./UrlParams.ts" @@ -313,8 +314,8 @@ export const json = ( * @category constructors * @since 4.0.0 */ -export const schemaJson = ( - schema: Schema.Codec, +export const schemaJson = ( + schema: Schema.ConstraintCodec, options?: ParseOptions | undefined ) => { const encode = Body.jsonSchema(schema, options) @@ -533,6 +534,24 @@ export const setHeader: { makeResponse({ ...self, headers: Headers.set(self.headers, key, value) + }, true) +) + +/** + * Returns a response with the specified header removed. + * + * @category combinators + * @since 4.0.0 + */ +export const removeHeader: { + (key: string): (self: HttpServerResponse) => HttpServerResponse + (self: HttpServerResponse, key: string): HttpServerResponse +} = dual( + 2, + (self: HttpServerResponse, key: string): HttpServerResponse => + makeResponse({ + ...self, + headers: Headers.remove(self.headers, key) }) ) @@ -551,7 +570,7 @@ export const setHeaders: { makeResponse({ ...self, headers: Headers.setAll(self.headers, input) - }) + }, true) ) /** @@ -913,7 +932,8 @@ export const setBody: { (self: HttpServerResponse, body: Body.HttpBody): HttpServerResponse } = dual( 2, - (self: HttpServerResponse, body: Body.HttpBody): HttpServerResponse => makeResponse({ ...self, body }) + (self: HttpServerResponse, body: Body.HttpBody): HttpServerResponse => + makeResponse({ ...self, headers: bodyInternal.updateHeaders(self.headers, body), body }) ) /** @@ -1319,7 +1339,7 @@ const makeResponse = (options: { readonly headers?: Headers.Headers | undefined readonly cookies?: Cookies.Cookies | undefined readonly body?: Body.HttpBody | undefined -}) => { +}, preferHeaders = false) => { const self = Object.create(Proto) as Mutable self.status = options.status self.statusText = options.statusText @@ -1327,13 +1347,13 @@ const makeResponse = (options: { self.body = options.body ?? Body.empty if ( self.body._tag !== "Empty" && - (self.body.contentType || self.body.contentLength) + (self.body.contentType || self.body.contentLength !== undefined) ) { const newHeaders = Headers.fromRecordUnsafe({ ...options.headers }) as any - if (self.body.contentType) { + if (self.body.contentType && (!preferHeaders || newHeaders["content-type"] === undefined)) { newHeaders["content-type"] = self.body.contentType } - if (self.body.contentLength) { + if (self.body.contentLength !== undefined && (!preferHeaders || newHeaders["content-length"] === undefined)) { newHeaders["content-length"] = self.body.contentLength.toString() } self.headers = newHeaders diff --git a/repos/effect/packages/effect/src/unstable/http/HttpStaticServer.ts b/repos/effect/packages/effect/src/unstable/http/HttpStaticServer.ts index ee3c5c6c7c..b1f047634b 100644 --- a/repos/effect/packages/effect/src/unstable/http/HttpStaticServer.ts +++ b/repos/effect/packages/effect/src/unstable/http/HttpStaticServer.ts @@ -26,14 +26,41 @@ import * as HttpServerResponse from "./HttpServerResponse.ts" * * **Example** (Serving files from a directory) * - * ```ts - * import { Effect } from "effect" - * import { HttpStaticServer } from "effect/unstable/http" + * ```ts import.meta.vitest + * import { Effect, FileSystem, Layer, Path } from "effect" + * import { + * HttpEffect, + * HttpPlatform, + * HttpServerResponse, + * HttpStaticServer + * } from "effect/unstable/http" * - * const program = Effect.gen(function*() { - * const app = yield* HttpStaticServer.make({ root: "./public" }) - * return app + * const TestFileSystem = FileSystem.layerNoop({ + * stat: () => + * Effect.succeed({ + * type: "File", + * size: FileSystem.Size(20) + * } as FileSystem.File.Info) * }) + * const TestHttpPlatform = Layer.succeed( + * HttpPlatform.HttpPlatform, + * HttpPlatform.HttpPlatform.of({ + * platform: "web", + * fileResponse: (path) => Effect.succeed(HttpServerResponse.text(`Serving ${path}`)), + * fileWebResponse: () => Effect.die("unused") + * }) + * ) + * const TestServices = Layer.mergeAll(Path.layer, TestFileSystem, TestHttpPlatform) + * + * const program = Effect.gen(function*() { + * const app = yield* HttpStaticServer.make({ root: "/public" }) + * const handler = HttpEffect.toWebHandler(app) + * const response = yield* Effect.promise(() => handler(new Request("http://localhost/guide.txt"))) + * const body = yield* Effect.promise(() => response.text()) + * return body + * }).pipe(Effect.provide(TestServices)) + * + * await Effect.runPromise(program) // => "Serving /public/guide.txt" * ``` * * @category constructors @@ -179,7 +206,7 @@ export const make: (options: { * * **Example** (Mounting static files on a router) * - * ```ts + * ```ts import.meta.vitest * import { Layer } from "effect" * import { HttpRouter, HttpServerResponse, HttpStaticServer } from "effect/unstable/http" * @@ -191,6 +218,7 @@ export const make: (options: { * }) * * const AppLayer = Layer.mergeAll(ApiLayer, StaticFilesLayer) + * Layer.isLayer(AppLayer) // => true * ``` * * @category layers diff --git a/repos/effect/packages/effect/src/unstable/http/Multipart.ts b/repos/effect/packages/effect/src/unstable/http/Multipart.ts index 5d87fe6663..344efc4bf7 100644 --- a/repos/effect/packages/effect/src/unstable/http/Multipart.ts +++ b/repos/effect/packages/effect/src/unstable/http/Multipart.ts @@ -16,6 +16,7 @@ import * as Channel from "../../Channel.ts" import * as Context from "../../Context.ts" import * as Data from "../../Data.ts" import * as Effect from "../../Effect.ts" +import * as ErrorReporter from "../../ErrorReporter.ts" import * as Exit from "../../Exit.ts" import * as FileSystem from "../../FileSystem.ts" import { constant, dual } from "../../Function.ts" @@ -31,7 +32,9 @@ import type * as Scope from "../../Scope.ts" import * as Stream from "../../Stream.ts" import * as UndefinedOr from "../../UndefinedOr.ts" import * as IncomingMessage from "./HttpIncomingMessage.ts" -import * as MP from "./Multipasta.ts" +import * as HttpServerRespondable from "./HttpServerRespondable.ts" +import * as HttpServerResponse from "./HttpServerResponse.ts" +import * as MP from "./MultipartParser.ts" /** * Type identifier used to brand multipart part values. @@ -199,19 +202,30 @@ export class MultipartErrorReason extends Data.Error<{ readonly cause?: unknown }> {} +const responseStatusByReason = { + FileTooLarge: 413, + FieldTooLarge: 413, + BodyTooLarge: 413, + TooManyParts: 413, + InternalError: 500, + Parse: 400 +} as const satisfies Record + /** * Error raised while parsing, streaming, or persisting multipart form data. * * **Details** * - * The `reason` field contains the concrete `MultipartErrorReason`. + * The `reason` field contains the concrete `MultipartErrorReason`. When used as + * a server response, parse errors render as `400`, limit errors as `413`, and + * internal errors as `500`. Multipart errors are ignored by the error reporter. * * @category errors * @since 4.0.0 */ export class MultipartError extends Data.TaggedError("MultipartError")<{ readonly reason: MultipartErrorReason -}> { +}> implements HttpServerRespondable.Respondable { /** * Creates a multipart error from a reason tag and optional cause. * @@ -228,6 +242,22 @@ export class MultipartError extends Data.TaggedError("MultipartError")<{ */ readonly [MultipartErrorTypeId] = MultipartErrorTypeId + override readonly [ErrorReporter.ignore] = true; + + /** + * Converts the multipart error into an HTTP response based on its reason. + * + * **Details** + * + * Parse errors produce `400`, size and part-count limits produce `413`, and + * internal errors produce `500`. + * + * @since 4.0.0 + */ + [HttpServerRespondable.symbol]() { + return Effect.succeed(HttpServerResponse.empty({ status: responseStatusByReason[this.reason._tag] })) + } + /** * Uses the concrete multipart error reason as the public message. * @@ -246,6 +276,13 @@ export class MultipartError extends Data.TaggedError("MultipartError")<{ */ export interface PersistedFileSchema extends Schema.declare {} +const PersistedFileEncoded = Schema.Struct({ + key: Schema.String, + name: Schema.String, + contentType: Schema.String.annotate({ contentEncoding: "binary" }), + path: Schema.String +}) + /** * Schema for persisted multipart files. * @@ -260,23 +297,19 @@ export interface PersistedFileSchema extends Schema.declare {} export const PersistedFileSchema: PersistedFileSchema = Schema.declare( isPersistedFile, { - typeConstructor: { - _tag: "effect/http/PersistedFile" - }, - generation: { - runtime: `Multipart.PersistedFileSchema`, - Type: `Multipart.PersistedFile`, - importDeclaration: `import * as Multipart from "effect/unstable/http/Multipart"` + representation: { + id: "effect/http/PersistedFile", + payload: null }, + toCode: () => ({ + runtime: "Multipart.PersistedFileSchema", + Type: "Multipart.PersistedFile", + importDeclarations: [`import * as Multipart from "effect/unstable/http/Multipart"`] + }), expected: "PersistedFile", toCodecJson: () => Schema.link()( - Schema.Struct({ - key: Schema.String, - name: Schema.String, - contentType: Schema.String.annotate({ contentEncoding: "binary" }), - path: Schema.String - }), + PersistedFileEncoded, SchemaTransformation.transform({ decode: ({ contentType, key, name, path }) => new PersistedFileImpl(key, name, contentType, path), encode: (file) => ({ @@ -333,8 +366,8 @@ export const SingleFileSchema: Schema.decodeTo, RD, RE>( - schema: Schema.Codec +export const schemaPersisted = , RD>( + schema: Schema.ConstraintCodec ): (input: unknown, options?: ParseOptions) => Effect.Effect => Schema.decodeUnknownEffect(schema) @@ -349,7 +382,7 @@ export const schemaPersisted = , RD, RE>( * @category schemas * @since 4.0.0 */ -export const schemaJson = (schema: Schema.Codec, options?: ParseOptions | undefined): { +export const schemaJson = (schema: Schema.ConstraintDecoder, options?: ParseOptions | undefined): { ( field: string ): (persisted: Persisted) => Effect.Effect @@ -405,7 +438,7 @@ export const makeConfig = ( * non-empty batches of parsed `Part` values, failing with `MultipartError` for * parser and limit failures. * - * @category Parsers + * @category parsing * @since 4.0.0 */ export const makeChannel = (headers: Record): Channel.Channel< @@ -452,7 +485,9 @@ export const makeChannel = (headers: Record): Channel.Channe exit = Option.some(Exit.fail(convertError(error_))) }, onDone() { - exit = Option.some(Exit.fail(Cause.Done())) + if (Option.isNone(exit)) { + exit = Option.some(Exit.fail(Cause.Done())) + } } }) @@ -567,7 +602,7 @@ class FileImpl extends PartBase implements File { this.contentType = info.contentType this.content = Stream.fromChannel(channel) this.contentEffect = channel.pipe( - collectUint8Array, + Channel.mkUint8Array, Effect.mapError((cause) => MultipartError.fromReason("InternalError", cause)) ) } @@ -600,24 +635,15 @@ const defaultWriteFile = (path: string, file: File) => * **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 converting * @since 4.0.0 */ export const collectUint8Array = ( self: Channel.Channel, OE, OD, unknown, unknown, unknown, R> -): Effect.Effect, OE, R> => - Channel.runFold(self, constant(new Uint8Array(0)), (accumulator, chunk) => { - const totalLength = chunk.reduce((sum, element) => sum + element.length, accumulator.length) - const newAccumulator = new Uint8Array(totalLength) - newAccumulator.set(accumulator, 0) - let offset = accumulator.length - for (const element of chunk) { - newAccumulator.set(element, offset) - offset += element.length - } - return newAccumulator - }) +): Effect.Effect, OE, R> => Channel.mkUint8Array(self) /** * Persists a stream of multipart parts into a record. @@ -643,6 +669,8 @@ export const toPersisted = ( const path_ = yield* Path.Path const dir = yield* fs.makeTempDirectoryScoped() const persisted: Record | Array | string> = Object.create(null) + const usedPaths = new Set() + let fileIndex = 0 yield* Stream.runForEach(stream, (part) => { if (part._tag === "Field") { if (!(part.key in persisted)) { @@ -657,7 +685,12 @@ export const toPersisted = ( return Effect.void } const file = part - const path = path_.join(dir, path_.basename(file.name).slice(-128)) + const fileName = path_.basename(file.name).slice(-128) + let path = path_.join(dir, fileName) + while (usedPaths.has(path)) { + path = path_.join(dir, `${fileIndex++}-${fileName}`) + } + usedPaths.add(path) const filePart = new PersistedFileImpl( file.key, file.name, @@ -759,7 +792,7 @@ export declare namespace withLimits { * These settings control maximum part count, field size, file size, total body * size, and MIME types that should be treated as fields instead of files. * - * @category fiber refs + * @category options * @since 4.0.0 */ export type Options = { @@ -778,7 +811,7 @@ export declare namespace withLimits { * * The default is `undefined`, meaning no explicit part-count limit. * - * @category references + * @category services * @since 4.0.0 */ export const MaxParts = Context.Reference("effect/http/Multipart/MaxParts", { @@ -792,7 +825,7 @@ export const MaxParts = Context.Reference("effect/http/Multi * * The default limit is 10 MiB. * - * @category references + * @category services * @since 4.0.0 */ export const MaxFieldSize = Context.Reference("effect/http/Multipart/MaxFieldSize", { @@ -806,7 +839,7 @@ export const MaxFieldSize = Context.Reference("effect/http * * The default is `undefined`, meaning no explicit per-file limit. * - * @category references + * @category services * @since 4.0.0 */ export const MaxFileSize = Context.Reference( @@ -822,7 +855,7 @@ export const MaxFileSize = Context.Reference( * * The default treats `application/json` parts as fields. * - * @category references + * @category services * @since 4.0.0 */ export const FieldMimeTypes = Context.Reference>("effect/http/Multipart/FieldMimeTypes", { diff --git a/repos/effect/packages/effect/src/unstable/http/MultipartParser.ts b/repos/effect/packages/effect/src/unstable/http/MultipartParser.ts new file mode 100644 index 0000000000..01cf7d1c5a --- /dev/null +++ b/repos/effect/packages/effect/src/unstable/http/MultipartParser.ts @@ -0,0 +1,115 @@ +/** + * Low-level parser for HTTP `multipart/form-data` bodies. + * + * @since 4.0.0 + */ +import type * as HeadersParser from "./MultipartParser/HeadersParser.ts" +import * as internal from "./MultipartParser/internal/multipart.ts" + +/** + * Metadata describing a multipart form part. + * + * @category models + * @since 4.0.0 + */ +export interface PartInfo { + readonly name: string + readonly filename?: string | undefined + readonly contentType: string + readonly contentTypeParameters: Record + readonly contentDisposition: string + readonly contentDispositionParameters: Record + readonly headers: Record> +} + +/** + * An error produced while parsing a multipart body. + * + * @category errors + * @since 4.0.0 + */ +export type MultipartError = + | { + readonly _tag: "InvalidBoundary" + } + | { + readonly _tag: "BadHeaders" + readonly error: HeadersParser.Failure + } + | { + readonly _tag: "InvalidDisposition" + } + | { + readonly _tag: "ReachedLimit" + readonly limit: + | "MaxParts" + | "MaxTotalSize" + | "MaxPartSize" + | "MaxFieldSize" + } + | { + readonly _tag: "EndNotReached" + } + +/** + * Shared multipart parser configuration. + * + * @category models + * @since 4.0.0 + */ +export type BaseConfig = { + readonly headers: Record + readonly isFile?: ((info: PartInfo) => boolean) | undefined + readonly maxParts?: number | undefined + readonly maxTotalSize?: number | undefined + readonly maxPartSize?: number | undefined + readonly maxFieldSize?: number | undefined +} + +/** + * Multipart parser configuration with event callbacks. + * + * @category models + * @since 4.0.0 + */ +export type Config = BaseConfig & { + readonly onField: (info: PartInfo, value: Uint8Array) => void + readonly onFile: (info: PartInfo) => (chunk: Uint8Array | null) => void + readonly onError: (error: MultipartError) => void + readonly onDone: () => void +} + +/** + * A streaming multipart parser. + * + * @category models + * @since 4.0.0 + */ +export interface Parser { + readonly write: (chunk: Uint8Array) => void + readonly end: () => void +} + +/** + * Creates a streaming multipart parser. + * + * @category constructors + * @since 4.0.0 + */ +export const make: (options: Config) => Parser = internal.make + +/** + * Determines whether a multipart part should be treated as a file. + * + * @category utilities + * @since 4.0.0 + */ +export const defaultIsFile: (info: PartInfo) => boolean = internal.defaultIsFile + +/** + * Decodes a multipart field using its declared character set. + * + * @category utilities + * @since 4.0.0 + */ +export const decodeField: (info: PartInfo, value: Uint8Array) => string = internal.decodeField diff --git a/repos/effect/packages/effect/src/unstable/http/MultipartParser/HeadersParser.ts b/repos/effect/packages/effect/src/unstable/http/MultipartParser/HeadersParser.ts new file mode 100644 index 0000000000..c42d561254 --- /dev/null +++ b/repos/effect/packages/effect/src/unstable/http/MultipartParser/HeadersParser.ts @@ -0,0 +1,68 @@ +/** + * Low-level parser for multipart header blocks. + * + * @since 4.0.0 + */ +import * as internal from "./internal/headers.ts" + +/** + * The reason a multipart header block could not be parsed. + * + * @category errors + * @since 4.0.0 + */ +export type FailureReason = + | "TooManyHeaders" + | "HeaderTooLarge" + | "InvalidHeaderName" + | "InvalidHeaderValue" + +/** + * Indicates that the parser needs more input. + * + * @category models + * @since 4.0.0 + */ +export interface Continue { + readonly _tag: "Continue" +} + +/** + * A multipart header parsing failure. + * + * @category errors + * @since 4.0.0 + */ +export interface Failure { + readonly _tag: "Failure" + readonly reason: FailureReason + readonly headers: Record> +} + +/** + * A successfully parsed multipart header block. + * + * @category models + * @since 4.0.0 + */ +export interface Headers { + readonly _tag: "Headers" + readonly headers: Record> + readonly endPosition: number +} + +/** + * The result of parsing a multipart header block. + * + * @category models + * @since 4.0.0 + */ +export type ReturnValue = Continue | Failure | Headers + +/** + * Creates an incremental multipart header parser. + * + * @category constructors + * @since 4.0.0 + */ +export const make: () => (chunk: Uint8Array, start: number) => ReturnValue = internal.make diff --git a/repos/effect/packages/effect/src/unstable/http/MultipartParser/Search.ts b/repos/effect/packages/effect/src/unstable/http/MultipartParser/Search.ts new file mode 100644 index 0000000000..47ca06d331 --- /dev/null +++ b/repos/effect/packages/effect/src/unstable/http/MultipartParser/Search.ts @@ -0,0 +1,17 @@ +/** + * Low-level byte search used by the multipart parser. + * + * @since 4.0.0 + */ +import * as internal from "./internal/search.ts" + +/** + * Creates an incremental byte search for a string boundary. + * + * @category constructors + * @since 4.0.0 + */ +export const make: ( + needle: string, + callback: (index: number, chunk: Uint8Array) => void +) => { readonly write: (chunk: Uint8Array) => void; readonly end: () => void } = internal.make diff --git a/repos/effect/packages/effect/src/unstable/http/MultipartParser/internal/contentType.ts b/repos/effect/packages/effect/src/unstable/http/MultipartParser/internal/contentType.ts new file mode 100644 index 0000000000..2ea96a1973 --- /dev/null +++ b/repos/effect/packages/effect/src/unstable/http/MultipartParser/internal/contentType.ts @@ -0,0 +1,103 @@ +// taken from https://github.com/fastify/fast-content-type-parse +// under the MIT license + +/** + * RegExp to match *( ";" parameter ) in RFC 7231 sec 3.1.1.1 + * + * parameter = token "=" ( token / quoted-string ) + * token = 1*tchar + * tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" + * / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~" + * / DIGIT / ALPHA + * ; any VCHAR, except delimiters + * quoted-string = DQUOTE *( qdtext / quoted-pair ) DQUOTE + * qdtext = HTAB / SP / %x21 / %x23-5B / %x5D-7E / obs-text + * obs-text = %x80-FF + * quoted-pair = "\" ( HTAB / SP / VCHAR / obs-text ) + */ +const paramRE = + /; *([!#$%&'*+.^\w`|~-]+)=("(?:[\v\u0020\u0021\u0023-\u005b\u005d-\u007e\u0080-\u{10ffff}]|\\[\v\u0020-\u{10ffff}])*"|[!#$%&'*+.^\w`|~-]+) */gu + +/** + * RegExp to match quoted-pair in RFC 7230 sec 3.2.6 + * + * quoted-pair = "\" ( HTAB / SP / VCHAR / obs-text ) + * obs-text = %x80-FF + */ +const quotedPairRE = /\\([\v\u0020-\u{10ffff}])/gu + +/** + * RegExp to match type in RFC 7231 sec 3.1.1.1 + * + * media-type = type "/" subtype + * type = token + * subtype = token + */ +const mediaTypeRE = /^[!#$%&'*+.^\w|~-]+\/[!#$%&'*+.^\w|~-]+$/u +const mediaTypeRENoSlash = /^[!#$%&'*+.^\w|~-]+$/u + +// default ContentType to prevent repeated object creation +const defaultContentType = { value: "", parameters: Object.create(null) } + +export function parse( + header: string | undefined, + withoutSlash = false +): { + readonly value: string + readonly parameters: Record +} { + if (typeof header !== "string") { + return defaultContentType + } + + let index = header.indexOf(";") + const type = index !== -1 ? header.slice(0, index).trim() : header.trim() + const mediaRE = withoutSlash ? mediaTypeRENoSlash : mediaTypeRE + + if (mediaRE.test(type) === false) { + return defaultContentType + } + + const result = { + value: type.toLowerCase(), + parameters: Object.create(null) + } + + // parse parameters + if (index === -1) { + return result + } + + let key: string + let match: RegExpExecArray | null + let value: string + + paramRE.lastIndex = index + + while ((match = paramRE.exec(header))) { + if (match.index !== index) { + return defaultContentType + } + + index += match[0].length + key = match[1].toLowerCase() + value = match[2] + + if (value[0] === "\"") { + // remove quotes and escapes + value = value.slice(1, value.length - 1) + + if (!withoutSlash && quotedPairRE.test(value)) { + value = value.replace(quotedPairRE, "$1") + } + } + + result.parameters[key] = value + } + + if (index !== header.length) { + return defaultContentType + } + + return result +} diff --git a/repos/effect/packages/effect/src/unstable/http/MultipartParser/internal/headers.ts b/repos/effect/packages/effect/src/unstable/http/MultipartParser/internal/headers.ts new file mode 100644 index 0000000000..312d2dc4af --- /dev/null +++ b/repos/effect/packages/effect/src/unstable/http/MultipartParser/internal/headers.ts @@ -0,0 +1,244 @@ +import type { Continue, FailureReason, ReturnValue } from "../HeadersParser.ts" + +const constMaxPairs = 100 +const constMaxSize = 16 * 1024 + +const State = { + key: 0, + whitespace: 1, + value: 2 +} as const +type State = (typeof State)[keyof typeof State] + +const constContinue: Continue = { _tag: "Continue" } + +// RFC 7230 token characters, allowed in header names +// The previous table stopped at byte 126; zero-filled entries for bytes 127-255 +// preserve its rejection behavior because callers accept only entries equal to 1. +const constNameChars = new Uint8Array(256) +for (const char of "!#$%&'*+-.^_`|~0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") { + constNameChars[char.charCodeAt(0)] = 1 +} + +// HTAB, visible ASCII and obs-text, allowed in header values +const constValueChars = new Uint8Array(256) +constValueChars[9] = 1 +for (let i = 32; i <= 126; i++) { + constValueChars[i] = 1 +} +for (let i = 128; i <= 255; i++) { + constValueChars[i] = 1 +} + +export function make() { + const decoder = new TextDecoder() + const state = { + state: State.key as State, + headers: Object.create(null) as Record>, + key: "", + value: undefined as undefined | Uint8Array, + crlf: 0, + previousChunk: undefined as undefined | Uint8Array, + pairs: 0, + size: 0 + } + + function reset(value: ReturnValue): ReturnValue { + state.state = State.key + state.headers = Object.create(null) + state.key = "" + state.value = undefined + state.crlf = 0 + state.previousChunk = undefined + state.pairs = 0 + state.size = 0 + return value + } + + function concatUint8Array(a: Uint8Array, b: Uint8Array): Uint8Array { + const newUint8Array = new Uint8Array(a.length + b.length) + newUint8Array.set(a) + newUint8Array.set(b, a.length) + return newUint8Array + } + + function error(reason: FailureReason) { + return reset({ _tag: "Failure", reason, headers: state.headers }) + } + + return function write(chunk: Uint8Array, start: number): ReturnValue { + let endOffset = 0 + let previousCursor: number | undefined + if (state.previousChunk !== undefined) { + endOffset = state.previousChunk.length + previousCursor = endOffset + + const newChunk = new Uint8Array(chunk.length + endOffset) + newChunk.set(state.previousChunk) + newChunk.set(chunk, endOffset) + state.previousChunk = undefined + chunk = newChunk + } + const end = chunk.length + + outer: while (start < end) { + if (state.state === State.key) { + let i = start + for (; i < end; i++) { + if (state.size++ > constMaxSize) { + return error("HeaderTooLarge") + } + + if (chunk[i] === 58) { + state.key += decoder.decode(chunk.subarray(start, i)).toLowerCase() + if (state.key.length === 0) { + return error("InvalidHeaderName") + } + + if ( + chunk[i + 1] === 32 && + chunk[i + 2] !== 32 && + chunk[i + 2] !== 9 + ) { + start = i + 2 + state.state = State.value + state.size++ + } else if (chunk[i + 1] !== 32 && chunk[i + 1] !== 9) { + start = i + 1 + state.state = State.value + } else { + start = i + 1 + state.state = State.whitespace + } + + break + } else if (constNameChars[chunk[i]] !== 1) { + return error("InvalidHeaderName") + } + } + if (i === end) { + state.key += decoder.decode(chunk.subarray(start, end)).toLowerCase() + return constContinue + } + } + + if (state.state === State.whitespace) { + for (; start < end; start++) { + if (state.size++ > constMaxSize) { + return error("HeaderTooLarge") + } + + if (chunk[start] !== 32 && chunk[start] !== 9) { + state.state = State.value + break + } + } + if (start === end) { + return constContinue + } + } + + if (state.state === State.value) { + let i = start + if (previousCursor !== undefined) { + i = previousCursor + previousCursor = undefined + } + for (; i < end; i++) { + if (state.size++ > constMaxSize) { + return error("HeaderTooLarge") + } + + if (chunk[i] === 13 || state.crlf > 0) { + let byte = chunk[i] + + if (byte === 13 && state.crlf === 0) { + state.crlf = 1 + i++ + state.size++ + byte = chunk[i] + } + if (byte === 10 && state.crlf === 1) { + state.crlf = 2 + i++ + state.size++ + byte = chunk[i] + } + if (byte === 13 && state.crlf === 2) { + state.crlf = 3 + i++ + state.size++ + byte = chunk[i] + } + if (byte === 10 && state.crlf === 3) { + state.crlf = 4 + i++ + state.size++ + } + + if (state.crlf < 4 && i >= end) { + state.previousChunk = chunk.subarray(start) + return constContinue + } else if (state.crlf >= 2) { + state.value = state.value === undefined + ? chunk.subarray(start, i - state.crlf) + : concatUint8Array( + state.value, + chunk.subarray(start, i - state.crlf) + ) + const value = decoder.decode(state.value) + if (state.headers[state.key] === undefined) { + state.headers[state.key] = value + } else if (typeof state.headers[state.key] === "string") { + state.headers[state.key] = [ + state.headers[state.key] as string, + value + ] + } else { + ;(state.headers[state.key] as Array).push(value) + } + + start = i + state.size-- + + if (state.crlf !== 4 && state.pairs === constMaxPairs) { + return error("TooManyHeaders") + } else if (state.crlf === 3) { + return error("InvalidHeaderValue") + } else if (state.crlf === 4) { + return reset({ + _tag: "Headers", + headers: state.headers, + endPosition: start - endOffset + }) + } + + state.pairs++ + state.key = "" + state.value = undefined + state.crlf = 0 + state.state = State.key + + continue outer + } + } else if (constValueChars[chunk[i]] !== 1) { + return error("InvalidHeaderValue") + } + } + + if (i === end) { + state.value = state.value === undefined + ? chunk.subarray(start, end) + : concatUint8Array(state.value, chunk.subarray(start, end)) + return constContinue + } + } + } + + if (start > end) { + state.size += end - start + } + + return constContinue + } +} diff --git a/repos/effect/packages/effect/src/unstable/http/MultipartParser/internal/multipart.ts b/repos/effect/packages/effect/src/unstable/http/MultipartParser/internal/multipart.ts new file mode 100644 index 0000000000..78d7fb0774 --- /dev/null +++ b/repos/effect/packages/effect/src/unstable/http/MultipartParser/internal/multipart.ts @@ -0,0 +1,279 @@ +import type { Config, MultipartError, PartInfo } from "../../MultipartParser.ts" +import * as CT from "./contentType.ts" +import * as HP from "./headers.ts" +import * as Search from "./search.ts" + +const State = { + headers: 0, + body: 1 +} as const +type State = (typeof State)[keyof typeof State] + +const errInvalidDisposition: MultipartError = { _tag: "InvalidDisposition" } +const errEndNotReached: MultipartError = { _tag: "EndNotReached" } +const errMaxParts: MultipartError = { _tag: "ReachedLimit", limit: "MaxParts" } +const errMaxTotalSize: MultipartError = { + _tag: "ReachedLimit", + limit: "MaxTotalSize" +} +const errMaxPartSize: MultipartError = { + _tag: "ReachedLimit", + limit: "MaxPartSize" +} +const errMaxFieldSize: MultipartError = { + _tag: "ReachedLimit", + limit: "MaxFieldSize" +} + +const constCR = new TextEncoder().encode("\r\n") + +export function defaultIsFile(info: PartInfo) { + return ( + info.filename !== undefined || + info.contentType === "application/octet-stream" + ) +} + +function parseBoundary(headers: Record) { + const contentType = CT.parse(headers["content-type"]) + return contentType.parameters.boundary +} + +function noopOnChunk(_chunk: Uint8Array | null) {} + +export function make({ + headers, + onFile: onPart, + onField, + onError, + onDone, + isFile = defaultIsFile, + maxParts = Infinity, + maxTotalSize = Infinity, + maxPartSize = Infinity, + maxFieldSize = 1024 * 1024 +}: Config) { + const boundary = parseBoundary(headers) + if (boundary === undefined) { + onError({ _tag: "InvalidBoundary" }) + return { + write: noopOnChunk, + end() {} + } + } + + const state = { + state: State.headers as State, + index: 0, + parts: 0, + onChunk: noopOnChunk, + info: undefined as any as PartInfo, + headerSkip: 0, + partSize: 0, + totalSize: 0, + isFile: false, + fieldChunks: [] as Array, + fieldSize: 0, + done: false, + stopped: false + } + + function skipBody() { + state.state = State.body + state.isFile = true + state.onChunk = noopOnChunk + } + + function stop(error: MultipartError) { + state.stopped = true + if (state.state === State.body && state.isFile) { + state.onChunk(null) + } + onError(error) + } + + const headerParser = HP.make() + + const split = Search.make( + `\r\n--${boundary}`, + function(index, chunk) { + if (state.stopped) { + return + } + + if (index === 0) { + // data before the first boundary + skipBody() + return + } else if (index !== state.index) { + if (state.index > 0) { + if (state.isFile) { + state.onChunk(null) + } else { + if (state.fieldChunks.length === 1) { + onField(state.info, state.fieldChunks[0]) + } else { + const buf = new Uint8Array(state.fieldSize) + let offset = 0 + for (let i = 0; i < state.fieldChunks.length; i++) { + const chunk = state.fieldChunks[i] + buf.set(chunk, offset) + offset += chunk.length + } + onField(state.info, buf) + } + state.fieldSize = 0 + state.fieldChunks = [] + } + } + state.partSize = 0 + + state.state = State.headers + state.index = index + state.headerSkip = 2 // skip the first \r\n + + // trailing -- + if (chunk[0] === 45 && chunk[1] === 45) { + state.done = true + return onDone() + } + + state.parts++ + if (state.parts > maxParts) { + return stop(errMaxParts) + } + } + + if ((state.partSize += chunk.length) > maxPartSize) { + return stop(errMaxPartSize) + } + + if (state.state === State.headers) { + const result = headerParser(chunk, state.headerSkip) + state.headerSkip = 0 + + if (result._tag === "Continue") { + return + } else if (result._tag === "Failure") { + skipBody() + return onError({ _tag: "BadHeaders", error: result }) + } + + const contentType = CT.parse(result.headers["content-type"] as string) + const contentDisposition = CT.parse( + result.headers["content-disposition"] as string, + true + ) + + if ( + "form-data" === contentDisposition.value && + !("name" in contentDisposition.parameters) + ) { + skipBody() + return onError(errInvalidDisposition) + } + + let encodedFilename: string | undefined + if ("filename*" in contentDisposition.parameters) { + const parts = contentDisposition.parameters["filename*"].split("''") + if (parts.length === 2) { + try { + encodedFilename = decodeURIComponent(parts[1]) + } catch { + encodedFilename = parts[1] + } + } + } + + state.info = { + name: contentDisposition.parameters.name ?? "", + filename: encodedFilename ?? contentDisposition.parameters.filename, + contentType: contentType.value === "" + ? contentDisposition.parameters.filename !== undefined + ? "application/octet-stream" + : "text/plain" + : contentType.value, + contentTypeParameters: contentType.parameters, + contentDisposition: contentDisposition.value, + contentDispositionParameters: contentDisposition.parameters as any, + headers: result.headers + } + + state.state = State.body + state.isFile = isFile(state.info) + + if (state.isFile) { + state.onChunk = onPart(state.info) + } + + if (result.endPosition < chunk.length) { + if (state.isFile) { + state.onChunk(chunk.subarray(result.endPosition)) + } else { + const buf = chunk.subarray(result.endPosition) + if ((state.fieldSize += buf.length) > maxFieldSize) { + return stop(errMaxFieldSize) + } + state.fieldChunks.push(buf) + } + } + } else if (state.isFile) { + state.onChunk(chunk) + } else { + if ((state.fieldSize += chunk.length) > maxFieldSize) { + return stop(errMaxFieldSize) + } + state.fieldChunks.push(chunk) + } + }, + constCR, + 2 + ) + + return { + write(chunk: Uint8Array) { + if (state.stopped) { + return + } + if ((state.totalSize += chunk.length) > maxTotalSize) { + return stop(errMaxTotalSize) + } + return split.write(chunk) + }, + end() { + split.end() + if (!state.done && !state.stopped) { + stop(errEndNotReached) + } + + state.state = State.headers + state.index = 0 + state.parts = 0 + state.onChunk = noopOnChunk + state.info = undefined as any as PartInfo + state.totalSize = 0 + state.partSize = 0 + state.fieldChunks = [] + state.fieldSize = 0 + state.done = false + state.stopped = false + } + } as const +} + +const utf8Decoder = new TextDecoder("utf-8") +function getDecoder(charset: string) { + if (charset === "utf-8" || charset === "utf8" || charset === "") { + return utf8Decoder + } + + try { + return new TextDecoder(charset) + } catch (error) { + return utf8Decoder + } +} + +export function decodeField(info: PartInfo, value: Uint8Array): string { + return getDecoder(info.contentTypeParameters.charset ?? "utf-8").decode(value) +} diff --git a/repos/effect/packages/effect/src/unstable/http/MultipartParser/internal/search.ts b/repos/effect/packages/effect/src/unstable/http/MultipartParser/internal/search.ts new file mode 100644 index 0000000000..be7530e227 --- /dev/null +++ b/repos/effect/packages/effect/src/unstable/http/MultipartParser/internal/search.ts @@ -0,0 +1,167 @@ +interface SearchState { + readonly needle: Uint8Array + readonly needleLength: number + readonly indexes: Record> + readonly firstByte: number + + previousChunk: Uint8Array | undefined + previousChunkLength: number + matchIndex: number +} + +function makeState(needle_: string): SearchState { + const needle = new TextEncoder().encode(needle_) + const needleLength = needle.length + + const indexes: Record> = {} + for (let i = 0; i < needleLength; i++) { + const b = needle[i] + if (indexes[b] === undefined) indexes[b] = [] + indexes[b].push(i) + } + + return { + needle, + needleLength, + indexes, + firstByte: needle[0], + previousChunk: undefined, + previousChunkLength: 0, + matchIndex: 0 + } +} + +export function make( + needle: string, + callback: (index: number, chunk: Uint8Array) => void, + seed?: Uint8Array, + minimumChunkLength?: number +) { + const state = makeState(needle) + const minChunkLength = minimumChunkLength ?? state.needleLength + if (seed !== undefined) { + state.previousChunk = seed + state.previousChunkLength = seed.length + } + + function makeIndexOf(): ( + chunk: Uint8Array, + needle: Uint8Array, + fromIndex: number + ) => number { + // on node.js use the Buffer api + if ( + "Buffer" in globalThis && + !("Bun" in globalThis || "Deno" in globalThis) + ) { + return function(chunk, needle, fromIndex) { + return Buffer.prototype.indexOf.call(chunk, needle, fromIndex) + } + } + + const skipTable = new Uint8Array(256).fill(state.needle.length) + for (let i = 0, lastIndex = state.needle.length - 1; i < lastIndex; ++i) { + skipTable[state.needle[i]] = lastIndex - i + } + + return function(chunk, needle, fromIndex) { + const lengthTotal = chunk.length + let i = fromIndex + state.needleLength - 1 + + while (i < lengthTotal) { + for ( + let j = state.needleLength - 1, k = i; + j >= 0 && chunk[k] === needle[j]; + j--, k-- + ) { + if (j === 0) return k + } + i += skipTable[chunk[i]] + } + + return -1 + } + } + + const indexOf = makeIndexOf() + + function write(chunk: Uint8Array): void { + let chunkLength = chunk.length + + if (state.previousChunk !== undefined) { + const newChunk = new Uint8Array(state.previousChunkLength + chunkLength) + newChunk.set(state.previousChunk) + newChunk.set(chunk, state.previousChunkLength) + chunk = newChunk + chunkLength = state.previousChunkLength + chunkLength + state.previousChunk = undefined + } + + let pos = 0 + while (pos < chunkLength) { + const remaining = chunkLength - pos + if (remaining < minChunkLength) { + state.previousChunk = chunk.subarray(pos) + state.previousChunkLength = remaining + return + } + + const match = indexOf(chunk, state.needle, pos) + + if (match > -1) { + if (match > pos) { + callback(state.matchIndex, chunk.subarray(pos, match)) + } + state.matchIndex += 1 + pos = match + state.needleLength + continue + } else if (chunk[chunkLength - 1] in state.indexes) { + const indexes = state.indexes[chunk[chunkLength - 1]] + let earliestIndex = -1 + for (let i = 0, len = indexes.length; i < len; i++) { + const index = indexes[i] + if ( + chunk[chunkLength - 1 - index] === state.firstByte && + i > earliestIndex + ) { + earliestIndex = index + } + } + if (earliestIndex === -1) { + if (pos === 0) { + callback(state.matchIndex, chunk) + } else { + callback(state.matchIndex, chunk.subarray(pos)) + } + } else { + if (chunkLength - 1 - earliestIndex > pos) { + callback( + state.matchIndex, + chunk.subarray(pos, chunkLength - 1 - earliestIndex) + ) + } + state.previousChunk = chunk.subarray(chunkLength - 1 - earliestIndex) + state.previousChunkLength = earliestIndex + 1 + } + } else if (pos === 0) { + callback(state.matchIndex, chunk) + } else { + callback(state.matchIndex, chunk.subarray(pos)) + } + + break + } + } + + function end(): void { + if (state.previousChunk !== undefined && state.previousChunk !== seed) { + callback(state.matchIndex, state.previousChunk) + } + + state.previousChunk = seed + state.previousChunkLength = seed?.length ?? 0 + state.matchIndex = 0 + } + + return { write, end } as const +} diff --git a/repos/effect/packages/effect/src/unstable/http/Multipasta.ts b/repos/effect/packages/effect/src/unstable/http/Multipasta.ts deleted file mode 100644 index 53d5ff8b25..0000000000 --- a/repos/effect/packages/effect/src/unstable/http/Multipasta.ts +++ /dev/null @@ -1,15 +0,0 @@ -/** - * Re-exports the `multipasta` multipart parser used by the unstable HTTP - * modules. - * - * This module keeps the parser types and helpers available from the Effect HTTP - * namespace without wrapping or changing them. - * - * @since 4.0.0 - */ - -/** - * @category re-exports - * @since 4.0.0 - */ -export * from "multipasta" diff --git a/repos/effect/packages/effect/src/unstable/http/Multipasta/HeadersParser.ts b/repos/effect/packages/effect/src/unstable/http/Multipasta/HeadersParser.ts deleted file mode 100644 index 094cae5f5a..0000000000 --- a/repos/effect/packages/effect/src/unstable/http/Multipasta/HeadersParser.ts +++ /dev/null @@ -1,14 +0,0 @@ -/** - * Re-exports the `multipasta` header parser used by multipart HTTP parsing. - * - * This module keeps the parser available from the Effect HTTP namespace without - * wrapping or changing it. - * - * @since 4.0.0 - */ - -/** - * @category re-exports - * @since 4.0.0 - */ -export * from "multipasta/HeadersParser" diff --git a/repos/effect/packages/effect/src/unstable/http/Multipasta/Node.ts b/repos/effect/packages/effect/src/unstable/http/Multipasta/Node.ts deleted file mode 100644 index effc1f465b..0000000000 --- a/repos/effect/packages/effect/src/unstable/http/Multipasta/Node.ts +++ /dev/null @@ -1,14 +0,0 @@ -/** - * Re-exports the Node.js multipart helpers from `multipasta`. - * - * This module keeps the Node-specific parser helpers available from the Effect - * HTTP namespace without wrapping or changing them. - * - * @since 4.0.0 - */ - -/** - * @category re-exports - * @since 4.0.0 - */ -export * from "multipasta/node" diff --git a/repos/effect/packages/effect/src/unstable/http/Multipasta/Search.ts b/repos/effect/packages/effect/src/unstable/http/Multipasta/Search.ts deleted file mode 100644 index 4a1c3013a4..0000000000 --- a/repos/effect/packages/effect/src/unstable/http/Multipasta/Search.ts +++ /dev/null @@ -1,14 +0,0 @@ -/** - * Re-exports the multipart search helpers from `multipasta`. - * - * This module keeps the search helpers available from the Effect HTTP namespace - * without wrapping or changing them. - * - * @since 4.0.0 - */ - -/** - * @category re-exports - * @since 4.0.0 - */ -export * from "multipasta/Search" diff --git a/repos/effect/packages/effect/src/unstable/http/Multipasta/Web.ts b/repos/effect/packages/effect/src/unstable/http/Multipasta/Web.ts deleted file mode 100644 index 30e60a5afb..0000000000 --- a/repos/effect/packages/effect/src/unstable/http/Multipasta/Web.ts +++ /dev/null @@ -1,14 +0,0 @@ -/** - * Re-exports the Web multipart helpers from `multipasta`. - * - * This module keeps the Web-specific parser helpers available from the Effect - * HTTP namespace without wrapping or changing them. - * - * @since 4.0.0 - */ - -/** - * @category re-exports - * @since 4.0.0 - */ -export * from "multipasta/web" diff --git a/repos/effect/packages/effect/src/unstable/http/Template.ts b/repos/effect/packages/effect/src/unstable/http/Template.ts index 38d3f95e9a..994ecd461f 100644 --- a/repos/effect/packages/effect/src/unstable/http/Template.ts +++ b/repos/effect/packages/effect/src/unstable/http/Template.ts @@ -154,7 +154,7 @@ export function make>( values[index] = primitiveToString(value) })), { - concurrency: "inherit", + concurrency: "unbounded", discard: true } ), @@ -215,11 +215,12 @@ export function stream>( buffer = "" } - return Stream.flatMap( - Stream.fromIterable(chunks), - (chunk) => - typeof chunk === "string" ? Stream.succeed(chunk) : Effect.isEffect(chunk) ? Stream.fromEffect(chunk) : chunk, - { concurrency: "unbounded" } + return Stream.fromIterable(chunks).pipe( + Stream.mapEffect( + (chunk) => Effect.isEffect(chunk) ? chunk : Effect.succeed(chunk), + { concurrency: "unbounded" } + ), + Stream.flatMap((chunk) => typeof chunk === "string" ? Stream.succeed(chunk) : chunk) ) } diff --git a/repos/effect/packages/effect/src/unstable/http/Url.ts b/repos/effect/packages/effect/src/unstable/http/Url.ts index 284d1a3a9d..4b1e63cf2e 100644 --- a/repos/effect/packages/effect/src/unstable/http/Url.ts +++ b/repos/effect/packages/effect/src/unstable/http/Url.ts @@ -9,11 +9,66 @@ * @since 4.0.0 */ import * as Cause from "../../Cause.ts" +import * as Data from "../../Data.ts" import { dual } from "../../Function.ts" import * as Redacted from "../../Redacted.ts" import * as Result from "../../Result.ts" import * as UrlParams from "./UrlParams.ts" +/** + * Error returned when constructing a `URL` fails. + * + * @category errors + * @since 4.0.0 + */ +export class UrlError extends Data.TaggedError("UrlError")<{ + readonly cause: unknown +}> {} + +/** + * Creates a `URL` safely by appending `UrlParams` and an optional hash to a URL string. + * + * **Details** + * + * Returns a `Result` that fails with `UrlError` if the URL cannot be constructed. + * + * @category constructors + * @since 4.0.0 + */ +export const make = ( + url: string, + params: UrlParams.UrlParams, + hash: string | undefined +): Result.Result => + Result.try({ + try: () => { + const urlInstance = new URL(url, baseUrl()) + for (let i = 0; i < params.params.length; i++) { + const [key, value] = params.params[i] + if (value !== undefined) { + urlInstance.searchParams.append(key, value) + } + } + if (hash !== undefined) { + urlInstance.hash = hash + } + return urlInstance + }, + catch: (cause) => new UrlError({ cause }) + }) + +const baseUrl = (): string | undefined => { + if ( + "location" in globalThis && + globalThis.location !== undefined && + globalThis.location.origin !== undefined && + globalThis.location.pathname !== undefined + ) { + return location.origin + location.pathname + } + return undefined +} + /** * Parses a URL string safely into a `URL` object, returning a `Result` type for * error handling. @@ -33,7 +88,7 @@ import * as UrlParams from "./UrlParams.ts" * * **Example** (Parsing absolute and relative URLs) * - * ```ts + * ```ts import.meta.vitest * import { Result } from "effect" * import { Url } from "effect/unstable/http" * @@ -43,22 +98,12 @@ import * as UrlParams from "./UrlParams.ts" * // ▼ * const parsed = Url.fromString("https://example.com/path") * - * if (Result.isSuccess(parsed)) { - * console.log("Parsed URL:", parsed.success.toString()) - * } else { - * console.log("Error:", parsed.failure.message) - * } - * // Output: Parsed URL: https://example.com/path + * Result.map(parsed, (url) => url.toString()) // => Result.succeed("https://example.com/path") * * // Parse a relative URL with a base * const relativeParsed = Url.fromString("/relative-path", "https://example.com") * - * if (Result.isSuccess(relativeParsed)) { - * console.log("Parsed relative URL:", relativeParsed.success.toString()) - * } else { - * console.log("Error:", relativeParsed.failure.message) - * } - * // Output: Parsed relative URL: https://example.com/relative-path + * Result.map(relativeParsed, (url) => url.toString()) // => Result.succeed("https://example.com/relative-path") * ``` * * @category constructors @@ -78,7 +123,7 @@ export const fromString: { * * **Example** (Mutating URL credentials) * - * ```ts + * ```ts import.meta.vitest * import { Url } from "effect/unstable/http" * * const myUrl = new URL("https://example.com") @@ -88,11 +133,10 @@ export const fromString: { * url.password = "pass" * }) * - * console.log("Mutated:", mutatedUrl.toString()) - * // Output: Mutated: https://user:pass@example.com/ + * mutatedUrl.toString() // => "https://user:pass@example.com/" * ``` * - * @category modifiers + * @category transforming * @since 4.0.0 */ export const mutate: { @@ -240,7 +284,7 @@ export const setUsername: { * * **Example** (Replacing query parameters) * - * ```ts + * ```ts import.meta.vitest * import { Url, UrlParams } from "effect/unstable/http" * * const myUrl = new URL("https://example.com?foo=bar") @@ -251,19 +295,18 @@ export const setUsername: { * UrlParams.fromInput([["key", "value"]]) * ) * - * console.log(updatedUrl.toString()) - * // Output: https://example.com/?key=value + * updatedUrl.toString() // => "https://example.com/?key=value" * ``` * * @category setters * @since 4.0.0 */ export const setUrlParams: { - (urlParams: UrlParams.UrlParams): (url: URL) => URL - (url: URL, urlParams: UrlParams.UrlParams): URL -} = dual(2, (url: URL, searchParams: UrlParams.UrlParams) => + (urlParams: UrlParams.Input): (url: URL) => URL + (url: URL, urlParams: UrlParams.Input): URL +} = dual(2, (url: URL, urlParams: UrlParams.Input) => mutate(url, (url) => { - url.search = UrlParams.toString(searchParams) + url.search = UrlParams.toString(UrlParams.fromInput(urlParams)) })) /** @@ -277,16 +320,15 @@ export const setUrlParams: { * * **Example** (Reading query parameters) * - * ```ts - * import { Url } from "effect/unstable/http" + * ```ts import.meta.vitest + * import { Url, UrlParams } from "effect/unstable/http" * * const myUrl = new URL("https://example.com?foo=bar") * * // Read parameters * const params = Url.urlParams(myUrl) * - * console.log(params) - * // Output: [ [ 'foo', 'bar' ] ] + * UrlParams.toString(params) // => "foo=bar" * ``` * * @category getters @@ -306,24 +348,23 @@ export const urlParams = (url: URL): UrlParams.UrlParams => UrlParams.fromInput( * * **Example** (Modifying query parameters) * - * ```ts + * ```ts import.meta.vitest * import { Url, UrlParams } from "effect/unstable/http" * * const myUrl = new URL("https://example.com?foo=bar") * * const changedUrl = Url.modifyUrlParams(myUrl, UrlParams.append("key", "value")) * - * console.log(changedUrl.toString()) - * // Output: https://example.com/?foo=bar&key=value + * changedUrl.toString() // => "https://example.com/?foo=bar&key=value" * ``` * - * @category modifiers + * @category transforming * @since 4.0.0 */ export const modifyUrlParams: { - (f: (urlParams: UrlParams.UrlParams) => UrlParams.UrlParams): (url: URL) => URL - (url: URL, f: (urlParams: UrlParams.UrlParams) => UrlParams.UrlParams): URL -} = dual(2, (url: URL, f: (urlParams: UrlParams.UrlParams) => UrlParams.UrlParams) => + (f: (urlParams: UrlParams.UrlParams) => UrlParams.Input): (url: URL) => URL + (url: URL, f: (urlParams: UrlParams.UrlParams) => UrlParams.Input): URL +} = dual(2, (url: URL, f: (urlParams: UrlParams.UrlParams) => UrlParams.Input) => mutate(url, (url) => { const params = f(UrlParams.fromInput(url.searchParams)) url.search = UrlParams.toString(params) diff --git a/repos/effect/packages/effect/src/unstable/http/UrlParams.ts b/repos/effect/packages/effect/src/unstable/http/UrlParams.ts index c95bf8b80d..d83dfcea2e 100644 --- a/repos/effect/packages/effect/src/unstable/http/UrlParams.ts +++ b/repos/effect/packages/effect/src/unstable/http/UrlParams.ts @@ -9,7 +9,6 @@ * @since 4.0.0 */ import * as Arr from "../../Array.ts" -import * as Data from "../../Data.ts" import * as Effect from "../../Effect.ts" import * as Equal from "../../Equal.ts" import * as Equ from "../../Equivalence.ts" @@ -17,11 +16,11 @@ import { dual } from "../../Function.ts" import * as Hash from "../../Hash.ts" import type { Inspectable } from "../../Inspectable.ts" import { PipeInspectableProto } from "../../internal/core.ts" +import * as InternalRecord from "../../internal/record.ts" import * as Option from "../../Option.ts" import type { Pipeable } from "../../Pipeable.ts" import { hasProperty } from "../../Predicate.ts" import type { ReadonlyRecord } from "../../Record.ts" -import * as Result from "../../Result.ts" import * as Schema from "../../Schema.ts" import * as SchemaIssue from "../../SchemaIssue.ts" import * as SchemaTransformation from "../../SchemaTransformation.ts" @@ -65,6 +64,7 @@ export const isUrlParams = (u: unknown): u is UrlParams => hasProperty(u, TypeId * @since 4.0.0 */ export type Input = + | UrlParams | CoercibleRecordInput | Iterable | URLSearchParams @@ -157,6 +157,9 @@ export const make = (params: ReadonlyArray): UrlParam * @since 4.0.0 */ export const fromInput = (input: Input): UrlParams => { + if (isUrlParams(input)) { + return input + } const parsed = fromInputNested(input) const out: Array<[string, string]> = [] for (let i = 0; i < parsed.length; i++) { @@ -234,15 +237,15 @@ export interface UrlParamsSchema extends Schema.declare ({ + runtime: "UrlParams.UrlParamsSchema", + Type: "UrlParams.UrlParams", + importDeclarations: [`import * as UrlParams from "effect/unstable/http/UrlParams"`] + }), expected: "UrlParams", toEquivalence: () => Equivalence, toCodec: () => @@ -446,68 +449,13 @@ export const remove: { (self: UrlParams, key: string): UrlParams } = dual(2, (self: UrlParams, key: string): UrlParams => transform(self, Arr.filter(([k]) => k !== key))) -/** - * Error returned when constructing a `URL` from `UrlParams` fails. - * - * @category errors - * @since 4.0.0 - */ -export class UrlParamsError extends Data.TaggedError("UrlParamsError")<{ - cause: unknown -}> {} - -/** - * Creates a `URL` safely by appending `UrlParams` and an optional hash to a URL string. - * - * **Details** - * - * Returns a `Result` that fails with `UrlParamsError` if the URL cannot be - * constructed. - * - * @category converting - * @since 4.0.0 - */ -export const makeUrl = ( - url: string, - params: UrlParams, - hash: string | undefined -): Result.Result => { - try { - const urlInstance = new URL(url, baseUrl()) - for (let i = 0; i < params.params.length; i++) { - const [key, value] = params.params[i] - if (value !== undefined) { - urlInstance.searchParams.append(key, value) - } - } - if (hash !== undefined) { - urlInstance.hash = hash - } - return Result.succeed(urlInstance) - } catch (e) { - return Result.fail(new UrlParamsError({ cause: e })) - } -} - /** * Serializes `UrlParams` to a URL query string without a leading question mark. * * @category converting * @since 4.0.0 */ -export const toString = (self: UrlParams): string => new URLSearchParams(self.params as any).toString() - -const baseUrl = (): string | undefined => { - if ( - "location" in globalThis && - globalThis.location !== undefined && - globalThis.location.origin !== undefined && - globalThis.location.pathname !== undefined - ) { - return location.origin + location.pathname - } - return undefined -} +export const toString = (input: Input): string => new URLSearchParams(fromInput(input).params as any).toString() /** * Builds a `Record` containing all the key-value pairs in the given `UrlParams` @@ -516,9 +464,8 @@ const baseUrl = (): string | undefined => { * * **Example** (Converting parameters to a record) * - * ```ts + * ```ts import.meta.vitest * import { UrlParams } from "effect/unstable/http" - * import * as assert from "node:assert" * * const urlParams = UrlParams.fromInput({ * a: 1, @@ -526,12 +473,7 @@ const baseUrl = (): string | undefined => { * c: "string", * e: [1, 2, 3] * }) - * const result = UrlParams.toRecord(urlParams) - * - * assert.deepStrictEqual( - * result, - * { "a": "1", "b": "true", "c": "string", "e": ["1", "2", "3"] } - * ) + * UrlParams.toRecord(urlParams) // => { a: "1", b: "true", c: "string", e: ["1", "2", "3"] } * ``` * * @category converting @@ -540,13 +482,15 @@ const baseUrl = (): string | undefined => { export const toRecord = (self: UrlParams): Record> => { const out: Record> = {} for (const [k, value] of self.params) { - const curr = out[k] - if (curr === undefined) { - out[k] = value - } else if (typeof curr === "string") { - out[k] = [curr, value] + if (!Object.hasOwn(out, k)) { + InternalRecord.assignProperty(out, k, value) } else { - curr.push(value) + const current = out[k] + if (typeof current === "string") { + InternalRecord.assignProperty(out, k, [current, value]) + } else { + current.push(value) + } } } return out @@ -572,7 +516,7 @@ export const toReadonlyRecord: (self: UrlParams) => ReadonlyRecord {} +export interface schemaJsonField extends Schema.decodeTo, UrlParamsSchema> {} /** * Extracts a JSON value from the first occurrence of the given `field` in the @@ -580,7 +524,7 @@ export interface schemaJsonField extends Schema.decodeTo ["bar", 42] * ``` * * @category schemas @@ -642,7 +585,7 @@ export interface schemaRecord extends * * **Example** (Decoding URL parameters to a record) * - * ```ts + * ```ts import.meta.vitest * import { Schema } from "effect" * import { UrlParams } from "effect/unstable/http" * @@ -653,12 +596,11 @@ export interface schemaRecord extends * })) * ) * - * console.log( - * Schema.decodeSync(toStruct)(UrlParams.fromInput({ + * const decoded = Schema.decodeSync(toStruct)(UrlParams.fromInput({ * some: "value", * number: 42 * })) - * ) + * const result = [decoded.some, decoded.number] // => ["value", 42] * ``` * * @category schemas diff --git a/repos/effect/packages/effect/src/unstable/http/index.ts b/repos/effect/packages/effect/src/unstable/http/index.ts index 462637685f..30cbeead09 100644 --- a/repos/effect/packages/effect/src/unstable/http/index.ts +++ b/repos/effect/packages/effect/src/unstable/http/index.ts @@ -127,7 +127,7 @@ export * as Multipart from "./Multipart.ts" /** * @since 4.0.0 */ -export * as Multipasta from "./Multipasta.ts" +export * as MultipartParser from "./MultipartParser.ts" /** * @since 4.0.0 diff --git a/repos/effect/packages/effect/src/unstable/http/internal/compression.ts b/repos/effect/packages/effect/src/unstable/http/internal/compression.ts new file mode 100644 index 0000000000..0dcdf222bc --- /dev/null +++ b/repos/effect/packages/effect/src/unstable/http/internal/compression.ts @@ -0,0 +1,196 @@ +import * as Effect from "../../../Effect.ts" +import { identity } from "../../../Function.ts" +import * as Stream from "../../../Stream.ts" +import type * as Headers from "../Headers.ts" +import * as HttpBody from "../HttpBody.ts" +import type { Compression, CompressionAlgorithm, CompressionOptions } from "../HttpPlatform.ts" +import * as Response from "../HttpServerResponse.ts" + +/** @internal */ +export const varyAcceptEncoding = (headers: Headers.Headers): string | undefined => { + const vary = headers["vary"] + if (vary === undefined) { + return "Accept-Encoding" + } + const members = vary.split(",").map((member) => member.trim().toLowerCase()) + return members.includes("*") || members.includes("accept-encoding") + ? undefined + : `${vary}, Accept-Encoding` +} + +/** @internal */ +export const wrapCompression = (impl: Compression): Compression => ({ + algorithms: impl.algorithms, + compressResponse(response, algorithm, options) { + return Effect.map(impl.compressResponse(response, algorithm, options), (compressed) => { + if (compressed === response) { + return response + } + const headers: Record = { "content-encoding": algorithm } + const vary = varyAcceptEncoding(compressed.headers) + if (vary !== undefined) { + headers["vary"] = vary + } + const etag = compressed.headers["etag"] + if (etag !== undefined && !etag.startsWith("W/")) { + headers["etag"] = `W/${etag}` + } + return Response.setHeaders(compressed, headers) + }) + } +}) + +/** @internal */ +export const compressionTransformWeb = + (format: string) => (stream: ReadableStream): ReadableStream => + stream.pipeThrough( + new CompressionStream(format as CompressionFormat) as unknown as ReadableWritablePair + ) + +/** @internal */ +export const setBodyWithoutLength = ( + response: Response.HttpServerResponse, + body: HttpBody.HttpBody +): Response.HttpServerResponse => Response.removeHeader(Response.setBody(response, body), "content-length") + +/** @internal */ +export const makeCompressionWeb = (options: { + readonly algorithms: Iterable + readonly transform: ( + algorithm: CompressionAlgorithm, + options?: CompressionOptions | undefined + ) => (stream: ReadableStream) => ReadableStream +}): Compression => ({ + algorithms: new Set(options.algorithms), + compressResponse(response, algorithm, opts) { + const body = response.body + switch (body._tag) { + case "Uint8Array": { + const data = body.body + return Effect.succeed(streamBody( + response, + () => options.transform(algorithm, opts)(singleChunkStream(data)), + body.contentType + )) + } + case "Stream": { + const stream = body.stream + return Effect.succeed(streamBody( + response, + () => options.transform(algorithm, opts)(Stream.toReadableStream(stream)), + body.contentType + )) + } + case "Raw": { + const readable = rawReadableStream(body.body) + if (readable === undefined) { + return Effect.succeed(response) + } + return Effect.succeed(setBodyWithoutLength( + response, + HttpBody.raw(options.transform(algorithm, opts)(readable), { contentType: body.contentType }) + )) + } + default: { + return Effect.succeed(response) + } + } + } +}) + +const streamBody = ( + response: Response.HttpServerResponse, + evaluate: () => ReadableStream, + contentType: string | undefined +): Response.HttpServerResponse => + setBodyWithoutLength( + response, + HttpBody.stream(Stream.fromReadableStream({ evaluate, onError: identity }), contentType) + ) + +const singleChunkStream = (data: Uint8Array): ReadableStream => + new ReadableStream({ + start(controller) { + controller.enqueue(data) + controller.close() + } + }) + +const rawReadableStream = (raw: unknown): ReadableStream | undefined => { + if (typeof ReadableStream !== "undefined" && raw instanceof ReadableStream) { + return raw + } else if (raw instanceof globalThis.Response) { + return raw.body ?? undefined + } + return new globalThis.Response(raw as BodyInit).body ?? undefined +} + +/** @internal */ +export const compressionWeb: Compression = makeCompressionWeb({ + algorithms: ["gzip", "deflate"], + transform: (algorithm) => compressionTransformWeb(algorithm) +}) + +/** @internal */ +export const defaultCompressible = (contentType: string): boolean => { + const semi = contentType.indexOf(";") + const type = (semi === -1 ? contentType : contentType.slice(0, semi)).trim().toLowerCase() + if (type.startsWith("text/")) { + return true + } + switch (type) { + case "application/json": + case "application/javascript": + case "application/xml": + case "image/svg+xml": + case "application/wasm": { + return true + } + } + return type.endsWith("+json") || type.endsWith("+xml") +} + +const acceptMember = /^([a-z0-9!#$%&'*+.^_`|~-]+)(?:;q=(0(?:\.[0-9]{0,3})?|1(?:\.0{0,3})?))?$/ + +/** @internal */ +export const parseAcceptEncoding = (header: string): ReadonlyMap | undefined => { + const trimmed = header.trim() + if (trimmed === "") { + return undefined + } + const accepted = new Map() + for (const part of trimmed.split(",")) { + const member = part.trim().toLowerCase().replace(/[ \t]*;[ \t]*/g, ";") + const match = acceptMember.exec(member) + if (match === null) { + return undefined + } + accepted.set(match[1], match[2] === undefined ? 1 : Number(match[2])) + } + return accepted +} + +/** @internal */ +export const negotiate = ( + header: string | undefined, + preferred: ReadonlyArray, + supported: ReadonlySet +): CompressionAlgorithm | undefined => { + if (header === undefined) { + return undefined + } + const accepted = parseAcceptEncoding(header) + if (accepted === undefined) { + return undefined + } + for (const algorithm of preferred) { + if (!supported.has(algorithm)) { + continue + } + const quality = accepted.get(algorithm) ?? accepted.get("*") + if (quality !== undefined && quality > 0) { + return algorithm + } + } + return undefined +} diff --git a/repos/effect/packages/effect/src/unstable/http/internal/httpBody.ts b/repos/effect/packages/effect/src/unstable/http/internal/httpBody.ts new file mode 100644 index 0000000000..e033e4415b --- /dev/null +++ b/repos/effect/packages/effect/src/unstable/http/internal/httpBody.ts @@ -0,0 +1,15 @@ +import * as Headers from "../Headers.ts" +import type * as HttpBody from "../HttpBody.ts" + +/** @internal */ +export const updateHeaders = (headers: Headers.Headers, body: HttpBody.HttpBody): Headers.Headers => { + if (body._tag === "Empty" || body._tag === "FormData") { + return Headers.remove(Headers.remove(headers, "content-type"), "content-length") + } + headers = body.contentType === undefined + ? Headers.remove(headers, "content-type") + : Headers.set(headers, "content-type", body.contentType) + return body.contentLength === undefined + ? Headers.remove(headers, "content-length") + : Headers.set(headers, "content-length", body.contentLength.toString()) +} diff --git a/repos/effect/packages/effect/src/unstable/httpapi/HttpApi.ts b/repos/effect/packages/effect/src/unstable/httpapi/HttpApi.ts index 0cbd0404c9..ab23c6e88f 100644 --- a/repos/effect/packages/effect/src/unstable/httpapi/HttpApi.ts +++ b/repos/effect/packages/effect/src/unstable/httpapi/HttpApi.ts @@ -10,12 +10,11 @@ */ import type { NonEmptyReadonlyArray } from "../../Array.ts" import * as Context from "../../Context.ts" +import * as InternalRecord from "../../internal/record.ts" import { type Pipeable, pipeArguments } from "../../Pipeable.ts" import * as Predicate from "../../Predicate.ts" import * as Record from "../../Record.ts" import type * as Schema from "../../Schema.ts" -import type * as SchemaAST from "../../SchemaAST.ts" -import type { Mutable } from "../../Types.ts" import type { PathInput } from "../http/HttpRouter.ts" import * as HttpApiEndpoint from "./HttpApiEndpoint.ts" import type * as HttpApiGroup from "./HttpApiGroup.ts" @@ -30,7 +29,14 @@ const TypeId = "~effect/httpapi/HttpApi" * @category guards * @since 4.0.0 */ -export const isHttpApi = (u: unknown): u is Any => Predicate.hasProperty(u, TypeId) +export const isHttpApi = (u: unknown): u is Top => Predicate.hasProperty(u, TypeId) + +/** + * Groups indexed by their identifier. + */ +type GroupMap = { + readonly [Group in Groups as HttpApiGroup.Identifier]: Group +} /** * An `HttpApi` is a collection of HTTP API groups and endpoints that represents a @@ -46,23 +52,38 @@ export const isHttpApi = (u: unknown): u is Any => Predicate.hasProperty(u, Type */ export interface HttpApi< out Id extends string, - out Groups extends HttpApiGroup.Any = never + in out Groups extends HttpApiGroup.Constraint = never > extends Pipeable { new(_: never): {} readonly [TypeId]: typeof TypeId readonly identifier: Id - readonly groups: Record.ReadonlyRecord + readonly groups: GroupMap readonly annotations: Context.Context /** * Add a `HttpApiGroup` to the `HttpApi`. */ - add>(...groups: A): HttpApi + add>(...groups: A): HttpApi /** - * Add another `HttpApi` to the `HttpApi`. + * Adds every group from another `HttpApi` while preserving its annotation scope. + * + * **When to use** + * + * Use when you want to compose an API from groups declared and annotated under another API. + * + * **Details** + * + * The added API is flattened into this API rather than retained as a nested value. Each added group + * is copied with the added API's annotations, leaving the added API unchanged. Annotation precedence + * from least to most specific is this API, the added API, the group, and then the endpoint. + * + * **Gotchas** + * + * Annotations from the added API do not become top-level annotations of the result and do not affect + * groups already present in this API. They remain scoped to the groups and endpoints being added. */ - addHttpApi( + addHttpApi( api: HttpApi ): HttpApi @@ -99,7 +120,7 @@ export interface HttpApi< * @category models * @since 4.0.0 */ -export interface Any { +export interface Constraint { readonly [TypeId]: typeof TypeId } @@ -110,7 +131,7 @@ export interface Any { * @category models * @since 4.0.0 */ -export type AnyWithProps = HttpApi +export interface Top extends HttpApi {} const Proto = { [TypeId]: TypeId, @@ -118,77 +139,78 @@ const Proto = { return pipeArguments(this, arguments) }, add( - this: AnyWithProps, - ...toAdd: NonEmptyReadonlyArray + this: Top, + ...toAdd: NonEmptyReadonlyArray ) { const groups = { ...this.groups } for (const group of toAdd) { - groups[group.identifier] = group + InternalRecord.assignProperty(groups, group.identifier, group) } return makeProto({ - identifier: this.identifier, - groups, - annotations: this.annotations + ...optionsFromApi(this), + groups }) }, addHttpApi( - this: AnyWithProps, - api: AnyWithProps + this: Top, + api: Top ) { const newGroups = { ...this.groups } - for (const key in api.groups) { - const newGroup: Mutable = api.groups[key] - newGroup.annotations = Context.merge(api.annotations, newGroup.annotations) - newGroups[key] = newGroup as any + for (const key of Object.keys(api.groups)) { + const group = api.groups[key] + InternalRecord.assignProperty( + newGroups, + key, + group.annotateMerge(Context.merge(api.annotations, group.annotations)) + ) } return makeProto({ - identifier: this.identifier, - groups: newGroups, - annotations: this.annotations + ...optionsFromApi(this), + groups: newGroups }) }, - prefix(this: AnyWithProps, prefix: PathInput) { + prefix(this: Top, prefix: PathInput) { return makeProto({ - identifier: this.identifier, - groups: Record.map(this.groups, (group) => group.prefix(prefix)), - annotations: this.annotations + ...optionsFromApi(this), + groups: Record.map(this.groups, (group) => group.prefix(prefix)) }) }, - middleware(this: AnyWithProps, tag: HttpApiMiddleware.AnyService) { + middleware(this: Top, tag: HttpApiMiddleware.AnyService) { return makeProto({ - identifier: this.identifier, - groups: Record.map(this.groups, (group) => group.middleware(tag as any)), - annotations: this.annotations + ...optionsFromApi(this), + groups: Record.map(this.groups, (group) => group.middleware(tag as any)) }) }, - annotate(this: AnyWithProps, key: Context.Key, value: any) { + annotate(this: Top, key: Context.Key, value: any) { return makeProto({ - identifier: this.identifier, - groups: this.groups, + ...optionsFromApi(this), annotations: Context.add(this.annotations, key, value) }) }, - annotateMerge(this: AnyWithProps, annotations: Context.Context) { + annotateMerge(this: Top, annotations: Context.Context) { return makeProto({ - identifier: this.identifier, - groups: this.groups, + ...optionsFromApi(this), annotations: Context.merge(this.annotations, annotations) }) } } -const makeProto = ( +const optionsFromApi = (api: Top) => ({ + identifier: api.identifier, + groups: api.groups, + annotations: api.annotations +}) + +const makeProto = ( options: { readonly identifier: Id - readonly groups: Record.ReadonlyRecord + readonly groups: Record.ReadonlyRecord readonly annotations: Context.Context } ): HttpApi => { function HttpApi() {} Object.setPrototypeOf(HttpApi, Proto) - HttpApi.groups = options.groups - HttpApi.annotations = options.annotations - return HttpApi as any + return Object.assign(HttpApi, options) as any } /** @@ -206,7 +228,7 @@ const makeProto = ( export const make = (identifier: Id): HttpApi => makeProto({ identifier, - groups: new Map() as any, + groups: {}, annotations: Context.empty() }) @@ -221,22 +243,22 @@ export const make = (identifier: Id): HttpApi( +export const reflect = ( self: HttpApi, options: { readonly predicate?: | Predicate.Predicate<{ - readonly endpoint: HttpApiEndpoint.AnyWithProps - readonly group: HttpApiGroup.AnyWithProps + readonly endpoint: HttpApiEndpoint.Top + readonly group: HttpApiGroup.Top }> | undefined readonly onGroup: (options: { - readonly group: HttpApiGroup.AnyWithProps + readonly group: HttpApiGroup.Top readonly mergedAnnotations: Context.Context }) => void readonly onEndpoint: (options: { - readonly group: HttpApiGroup.AnyWithProps - readonly endpoint: HttpApiEndpoint.AnyWithProps + readonly group: HttpApiGroup.Top + readonly endpoint: HttpApiEndpoint.Top readonly mergedAnnotations: Context.Context readonly middleware: ReadonlySet readonly successes: ReadonlyMap]> @@ -244,14 +266,14 @@ export const reflect = ( }) => void } ) => { - const groups = Object.values(self.groups) as any as Array + const groups = Object.values(self.groups) as any as Array for (const group of groups) { const groupAnnotations = Context.merge(self.annotations, group.annotations) options.onGroup({ group, mergedAnnotations: groupAnnotations }) - const endpoints = Object.values(group.endpoints) as Iterable + const endpoints = Object.values(group.endpoints) as Iterable for (const endpoint of endpoints) { if ( options.predicate && !options.predicate({ @@ -267,11 +289,11 @@ export const reflect = ( mergedAnnotations: Context.merge(groupAnnotations, endpoint.annotations), successes: extractResponseContent( HttpApiEndpoint.getSuccessSchemas(endpoint), - HttpApiSchema.getStatusSuccess + HttpApiSchema.getStatusSuccessSchema ), errors: extractResponseContent( HttpApiEndpoint.getErrorSchemas(endpoint), - HttpApiSchema.getStatusError + HttpApiSchema.getStatusErrorSchema ) }) } @@ -282,7 +304,7 @@ export const reflect = ( const extractResponseContent = ( schemas: Array, - getStatus: (ast: SchemaAST.AST) => number + getStatus: (schema: Schema.Constraint) => number ): ReadonlyMap]> => { const map = new Map]>() @@ -291,9 +313,9 @@ const extractResponseContent = ( return map function add(schema: Schema.Top) { - if (HttpApiSchema.isStreamSchema(schema)) return - const ast = schema.ast - const status = getStatus(ast) + const body = HttpApiSchema.isWithHeaders(schema) ? schema.schema : schema + if (HttpApiSchema.isStreamSchema(body)) return + const status = getStatus(schema) const schemas = map.get(status) if (schemas === undefined) { map.set(status, [schema]) diff --git a/repos/effect/packages/effect/src/unstable/httpapi/HttpApiBuilder.ts b/repos/effect/packages/effect/src/unstable/httpapi/HttpApiBuilder.ts index 7b3ca6ee1f..24553a1fc3 100644 --- a/repos/effect/packages/effect/src/unstable/httpapi/HttpApiBuilder.ts +++ b/repos/effect/packages/effect/src/unstable/httpapi/HttpApiBuilder.ts @@ -18,9 +18,9 @@ import type { FileSystem } from "../../FileSystem.ts" import { identity } from "../../Function.ts" import { stringOrRedacted } from "../../internal/redacted.ts" import * as Layer from "../../Layer.ts" -import * as Option from "../../Option.ts" import type { Path } from "../../Path.ts" import { type Pipeable, pipeArguments } from "../../Pipeable.ts" +import { hasProperty } from "../../Predicate.ts" import * as Redacted from "../../Redacted.ts" import * as Result from "../../Result.ts" import * as Schema from "../../Schema.ts" @@ -29,7 +29,7 @@ import * as SchemaIssue from "../../SchemaIssue.ts" import * as SchemaTransformation from "../../SchemaTransformation.ts" import * as Scope from "../../Scope.ts" import * as Stream from "../../Stream.ts" -import type { Covariant, NoInfer } from "../../Types.ts" +import type { NoInfer } from "../../Types.ts" import * as UndefinedOr from "../../UndefinedOr.ts" import * as Sse from "../encoding/Sse.ts" import type { Cookie } from "../http/Cookies.ts" @@ -51,15 +51,16 @@ import type * as HttpApiGroup from "./HttpApiGroup.ts" import * as HttpApiMiddleware from "./HttpApiMiddleware.ts" import * as HttpApiSchema from "./HttpApiSchema.ts" import type * as HttpApiSecurity from "./HttpApiSecurity.ts" +import * as MediaType from "./internal/mediaType.ts" import * as OpenApi from "./OpenApi.ts" /** * Registers an `HttpApi` with a `HttpRouter`. * - * @category constructors + * @category layers * @since 4.0.0 */ -export const layer = ( +export const layer = ( api: HttpApi.HttpApi, options?: { readonly openapiPath?: `/${string}` | undefined @@ -86,7 +87,8 @@ export const layer = ( const availableGroups = Array.from(services.mapUnsafe.keys()).filter((key) => key.startsWith("effect/httpapi/HttpApiGroup/") ) - for (const group of Object.values(api.groups)) { + const groups = Object.values(api.groups) as ReadonlyArray + for (const group of groups) { const groupRoutes = services.mapUnsafe.get(group.key)?.routes as Array> if (groupRoutes === undefined) { const available = availableGroups.length === 0 ? "none" : availableGroups.join(", ") @@ -117,17 +119,17 @@ export const layer = ( */ export const group = < ApiId extends string, - Groups extends HttpApiGroup.Any, - const Name extends HttpApiGroup.Name, + Groups extends HttpApiGroup.Constraint, + const Identifier extends HttpApiGroup.Identifier, Return >( api: HttpApi.HttpApi, - groupName: Name, + groupIdentifier: Identifier, build: ( - handlers: Handlers.FromGroup> + handlers: Handlers.FromGroup> ) => Handlers.ValidateReturn ): Layer.Layer< - HttpApiGroup.ApiGroup, + HttpApiGroup.Service, Handlers.Error, Exclude, Scope.Scope> > => @@ -135,9 +137,9 @@ export const group = < const services = (yield* Effect.context()).pipe( Context.omit(Scope.Scope) ) - const group = api.groups[groupName]! + const group = api.groups[groupIdentifier] as HttpApiGroup.Constraint const result = build(makeHandlers(group)) - const handlers: Handlers = Effect.isEffect(result) + const handlers: Handlers = Effect.isEffect(result) ? (yield* result as Effect.Effect) : result const routes: Array> = [] @@ -152,21 +154,96 @@ export const group = < ) })) as any -/** - * Type identifier symbol used to brand `Handlers` values. - * - * @category type IDs - * @since 4.0.0 - */ -export const HandlersTypeId: unique symbol = Symbol.for("@effect/platform/HttpApiBuilder/Handlers") +const HandlersTypeId = "~effect/httpapi/HttpApiBuilder/Handlers" -/** - * Type of the `Handlers` type identifier symbol. - * - * @category type IDs - * @since 4.0.0 - */ -export type HandlersTypeId = typeof HandlersTypeId +type EndpointMap = { + readonly [Endpoint in Endpoints as HttpApiEndpoint.Identifier]: Endpoint +} + +type HandlerRequirements< + Endpoint extends HttpApiEndpoint.Constraint, + R1, + R = HttpApiEndpoint.ExcludeProvided< + Endpoint, + R1 | HttpApiEndpoint.ServerServices + > +> = + | HttpApiEndpoint.Middleware + | HttpApiEndpoint.MiddlewareServices + | ([R] extends [never] ? never : HttpRouter.Request.From<"Requires", R>) + +interface HandlerOptions { + readonly uninterruptible?: boolean | undefined +} + +/** @internal */ +export interface HandlerRuntime { + readonly endpoint: HttpApiEndpoint.Top + readonly handler: HttpApiEndpoint.Handler + readonly isRaw: boolean + readonly uninterruptible: boolean +} + +type HandleAllEntry = + | HttpApiEndpoint.Handler< + Endpoint, + HttpApiEndpoint.MiddlewareError, + unknown + > + | { + readonly handler: HttpApiEndpoint.Handler< + Endpoint, + HttpApiEndpoint.MiddlewareError, + unknown + > + readonly options?: HandlerOptions | undefined + } + +type HandleAllHandlers> = { + readonly [Identifier in keyof EndpointsByIdentifier]?: HandleAllEntry +} + +type HandleAllExtraKeys< + EndpointsByIdentifier extends Record, + HandlersByIdentifier +> = { + readonly [Identifier in Exclude]: never +} + +type NotHandledIdentifier = Identifier extends + HandledIdentifiers ? never : + unknown + +type HandleAllEntryHandler = Entry extends { readonly handler: infer Handler } ? Handler : Entry + +type HandleAllRequirements< + EndpointsByIdentifier extends Record, + HandlersByIdentifier extends HandleAllHandlers +> = { + readonly [Identifier in keyof HandlersByIdentifier & keyof EndpointsByIdentifier]: HandleAllEntryHandler< + HandlersByIdentifier[Identifier] + > extends HttpApiEndpoint.Handler< + EndpointsByIdentifier[Identifier], + HttpApiEndpoint.MiddlewareError, + infer R1 + > ? HandlerRequirements : + never +}[keyof HandlersByIdentifier & keyof EndpointsByIdentifier] + +type HandlersResult = A extends Effect.Effect ? H : A + +type MissingHandlerNames> = Exclude< + keyof H["~EndpointsByIdentifier"], + H["~HandledIdentifiers"] +> + +type ValidateHandlersReturn< + A, + H = HandlersResult, + Missing = H extends Handlers ? MissingHandlerNames : never +> = H extends Handlers ? [Missing] extends [never] ? A + : `Endpoint not handled: ${Missing & string}` + : `Must return the implemented handlers` /** * Mutable handler collection for one `HttpApi` group. @@ -174,59 +251,77 @@ export type HandlersTypeId = typeof HandlersTypeId * **Details** * * Each call to `handle` or `handleRaw` registers an endpoint implementation and - * removes that endpoint from the type-level set of endpoints still requiring - * handlers. + * adds that endpoint identifier to the type-level set of implemented endpoints. + * Endpoint identifiers that were already handled are rejected at the type level. * * @category handlers * @since 4.0.0 */ export interface Handlers< R, - Endpoints extends HttpApiEndpoint.Any = never + EndpointsByIdentifier extends Record = {}, + HandledIdentifiers extends keyof EndpointsByIdentifier = never > extends Pipeable { - readonly [HandlersTypeId]: { - _Endpoints: Covariant - } - readonly group: HttpApiGroup.AnyWithProps - readonly handlers: Map> + readonly [HandlersTypeId]: typeof HandlersTypeId + readonly "~EndpointsByIdentifier": EndpointsByIdentifier + readonly "~HandledIdentifiers": HandledIdentifiers + /** @internal */ + readonly group: HttpApiGroup.Top + /** @internal */ + readonly handlers: Map /** - * Add the implementation for an `HttpApiEndpoint` to a `Handlers` group. + * Add the implementation for an unhandled `HttpApiEndpoint` to a `Handlers` group. */ - handle, R1>( - name: Name, - handler: HttpApiEndpoint.HandlerWithName, R1>, + handle< + Identifier extends keyof EndpointsByIdentifier, + R1 + >( + identifier: Identifier & NotHandledIdentifier, + handler: HttpApiEndpoint.Handler< + EndpointsByIdentifier[Identifier], + HttpApiEndpoint.MiddlewareError, + R1 + >, options?: { readonly uninterruptible?: boolean | undefined } | undefined ): Handlers< - | R - | HttpApiEndpoint.MiddlewareWithName - | HttpApiEndpoint.MiddlewareServicesWithName - | (HttpApiEndpoint.ExcludeProvidedWithName< - Endpoints, - Name, - R1 | HttpApiEndpoint.ServerServicesWithName - > extends infer _R ? _R extends never ? never : HttpRouter.Request<"Requires", _R> : never), - HttpApiEndpoint.ExcludeName + R | HandlerRequirements, + EndpointsByIdentifier, + HandledIdentifiers | Identifier > /** - * Add the implementation for an `HttpApiEndpoint` to a `Handlers` group. + * Add implementations for unhandled `HttpApiEndpoint`s in a `Handlers` group. + */ + handleAll>>( + handlers: + & HandlersByIdentifier + & HandleAllExtraKeys, HandlersByIdentifier> + ): Handlers< + R | HandleAllRequirements, + EndpointsByIdentifier, + HandledIdentifiers | keyof HandlersByIdentifier & keyof EndpointsByIdentifier + > + + /** + * Add the implementation for an unhandled `HttpApiEndpoint` to a `Handlers` group. * This version opts out of automatic payload decoding and provides the raw request. */ - handleRaw, R1>( - name: Name, - handler: HttpApiEndpoint.HandlerRawWithName, R1>, + handleRaw< + Identifier extends keyof EndpointsByIdentifier, + R1 + >( + identifier: Identifier & NotHandledIdentifier, + handler: HttpApiEndpoint.HandlerRaw< + EndpointsByIdentifier[Identifier], + HttpApiEndpoint.MiddlewareError, + R1 + >, options?: { readonly uninterruptible?: boolean | undefined } | undefined ): Handlers< - | R - | HttpApiEndpoint.MiddlewareWithName - | HttpApiEndpoint.MiddlewareServicesWithName - | (HttpApiEndpoint.ExcludeProvidedWithName< - Endpoints, - Name, - R1 | HttpApiEndpoint.ServerServicesWithName - > extends infer _R ? _R extends never ? never : HttpRouter.Request<"Requires", _R> : never), - HttpApiEndpoint.ExcludeName + R | HandlerRequirements, + EndpointsByIdentifier, + HandledIdentifiers | Identifier > } @@ -236,34 +331,6 @@ export interface Handlers< * @since 4.0.0 */ export declare namespace Handlers { - /** - * A `Handlers` value with its context and endpoint types erased. - * - * @category handlers - * @since 4.0.0 - */ - export interface Any { - readonly [HandlersTypeId]: any - } - - /** - * Record stored for a registered endpoint handler. - * - * **Details** - * - * It keeps the endpoint metadata, handler function, whether raw request handling - * is used, and whether the handler should run uninterruptibly. - * - * @category handlers - * @since 4.0.0 - */ - export type Item = { - readonly endpoint: HttpApiEndpoint.AnyWithProps - readonly handler: HttpApiEndpoint.Handler - readonly isRaw: boolean - readonly uninterruptible: boolean - } - /** * Creates a handler collection for a group where every endpoint in the group is * still awaiting an implementation. @@ -271,9 +338,9 @@ export declare namespace Handlers { * @category handlers * @since 4.0.0 */ - export type FromGroup = Handlers< + export type FromGroup = Handlers< never, - HttpApiGroup.Endpoints + Group["endpoints"] > /** @@ -284,22 +351,7 @@ export declare namespace Handlers { * @category handlers * @since 4.0.0 */ - export type ValidateReturn = A extends ( - | Handlers< - infer _R, - infer _Endpoints - > - | Effect.Effect< - Handlers< - infer _R, - infer _Endpoints - >, - infer _EX, - infer _RX - > - ) ? [_Endpoints] extends [never] ? A - : `Endpoint not handled: ${HttpApiEndpoint.Name<_Endpoints>}` : - `Must return the implemented handlers` + export type ValidateReturn = ValidateHandlersReturn /** * Extracts the error channel from an effect that produces a `Handlers` @@ -311,7 +363,8 @@ export declare namespace Handlers { export type Error = A extends Effect.Effect< Handlers< infer _R, - infer _Endpoints + infer _EndpointsByIdentifier, + infer _HandledIdentifiers >, infer _EX, infer _RX @@ -327,12 +380,14 @@ export declare namespace Handlers { */ export type Context = A extends Handlers< infer _R, - infer _Endpoints + infer _EndpointsByIdentifier, + infer _HandledIdentifiers > ? _R : A extends Effect.Effect< Handlers< infer _R, - infer _Endpoints + infer _EndpointsByIdentifier, + infer _HandledIdentifiers >, infer _EX, infer _RX @@ -340,34 +395,18 @@ export declare namespace Handlers { never } -/** - * Builds the server-side HTTP effect for a single endpoint in an API group using - * the endpoint metadata, middleware, codecs, and supplied handler. - * - * @category handlers - * @since 4.0.0 - */ -export const endpoint = < - ApiId extends string, - Groups extends HttpApiGroup.Any, - const GroupName extends HttpApiGroup.Name, - const EndpointName extends HttpApiEndpoint.Name>>, +type EndpointReturn< + Groups extends HttpApiGroup.Constraint, + GroupIdentifier extends HttpApiGroup.Identifier, + EndpointIdentifier extends HttpApiGroup.Endpoints< + HttpApiGroup.WithIdentifier + >["identifier"], R, - Group extends HttpApiGroup.Any = HttpApiGroup.WithName, - Endpoint extends HttpApiEndpoint.Any = HttpApiEndpoint.WithName, EndpointName> ->( - api: HttpApi.HttpApi, - groupName: GroupName, - endpointName: EndpointName, - handler: NoInfer< - HttpApiEndpoint.HandlerWithName< - HttpApiGroup.Endpoints>, - EndpointName, - never, - R - > + Endpoint extends HttpApiEndpoint.Constraint = HttpApiEndpoint.WithIdentifier< + HttpApiGroup.Endpoints>, + EndpointIdentifier > -): Effect.Effect< +> = Effect.Effect< Effect.Effect< HttpServerResponse, never, @@ -384,10 +423,39 @@ export const endpoint = < | FileSystem | HttpPlatform | Path -> => +> + +/** + * Builds the server-side HTTP effect for a single endpoint in an API group using + * the endpoint metadata, middleware, codecs, and supplied handler. + * + * @category handlers + * @since 4.0.0 + */ +export const endpoint = < + ApiId extends string, + Groups extends HttpApiGroup.Constraint, + const GroupIdentifier extends HttpApiGroup.Identifier, + const EndpointIdentifier extends HttpApiGroup.Endpoints< + HttpApiGroup.WithIdentifier + >["identifier"], + R +>( + api: HttpApi.HttpApi, + groupIdentifier: GroupIdentifier, + endpointIdentifier: EndpointIdentifier, + handler: NoInfer< + HttpApiEndpoint.HandlerWithIdentifier< + HttpApiGroup.Endpoints>, + EndpointIdentifier, + never, + R + > + > +): EndpointReturn => Effect.contextWith((context: Context.Context) => { - const group = api.groups[groupName] as unknown as HttpApiGroup.AnyWithProps - const endpoint = group.endpoints[endpointName] as unknown as HttpApiEndpoint.AnyWithProps + const group = api.groups[groupIdentifier] as unknown as HttpApiGroup.Top + const endpoint = group.endpoints[endpointIdentifier] return Effect.succeed(handlerToHttpEffect( group, endpoint, @@ -415,8 +483,10 @@ export const securityDecode = case "Http": { return Effect.map( HttpServerRequest, - // schemeLength + space - (request) => Redacted.make((request.headers.authorization ?? "").slice(self.schemeLength + 1)) as any + (request) => + Redacted.make( + getAuthorizationCredential(request.headers.authorization, self.scheme) ?? "" + ) as any ) } case "ApiKey": { @@ -443,24 +513,20 @@ export const securityDecode = username: "", password: Redacted.make("") } as any - return HttpServerRequest.pipe( - Effect.flatMap((request) => - Effect.fromResult(Encoding.decodeBase64String((request.headers.authorization ?? "").slice(basicLen))) - ), - Effect.match({ - onFailure: () => empty, - onSuccess: (header) => { - const parts = header.split(":") - if (parts.length !== 2) { - return empty - } - return { - username: parts[0], - password: Redacted.make(parts[1]) - } as any - } - }) - ) + return Effect.map(HttpServerRequest, (request) => { + const encoded = getAuthorizationCredential(request.headers.authorization, basicScheme) + if (encoded === undefined) return empty + const decoded = Result.getOrUndefined(Encoding.decodeBase64String(encoded)) + if (decoded === undefined) return empty + // RFC 7617, Section 2: only the first colon separates the user-id from the password. + // https://www.rfc-editor.org/rfc/rfc7617.html#section-2 + const separator = decoded.indexOf(":") + if (separator === -1) return empty + return { + username: decoded.slice(0, separator), + password: Redacted.make(decoded.slice(separator + 1)) + } as any + }) } } } @@ -491,53 +557,101 @@ export const securitySetCookie = ( // Internal // ----------------------------------------------------------------------------- -const basicLen = `Basic `.length +const basicScheme = "Basic" + +function getAuthorizationCredential( + authorization: string | undefined, + scheme: string +): string | undefined { + const schemeLength = scheme.length + // RFC 9110, Section 11.1: schemes are case-insensitive and credentials follow 1*SP. + // https://www.rfc-editor.org/rfc/rfc9110.html#section-11.1 + if ( + authorization === undefined || + authorization.length <= schemeLength || + authorization.charCodeAt(schemeLength) !== 32 || + authorization.slice(0, schemeLength).toLowerCase() !== scheme.toLowerCase() + ) { + return undefined + } + let credentialStart = schemeLength + 1 + while (authorization.charCodeAt(credentialStart) === 32) { + credentialStart++ + } + return credentialStart === authorization.length ? undefined : authorization.slice(credentialStart) +} + +const registerHandler = ( + self: Handlers, + identifier: string, + handler: HttpApiEndpoint.Handler, + isRaw: boolean, + options?: HandlerOptions | undefined +) => { + if (!Object.hasOwn(self.group.endpoints, identifier)) { + throw new Error(`HttpApiEndpoint "${identifier}" not found in HttpApiGroup "${self.group.identifier}"`) + } + if (self.handlers.has(identifier)) { + throw new Error( + `Handler for HttpApiEndpoint "${identifier}" is already registered in HttpApiGroup "${self.group.identifier}"` + ) + } + const endpoint = self.group.endpoints[identifier] + self.handlers.set(identifier, { + endpoint, + handler, + isRaw, + uninterruptible: options?.uninterruptible ?? false + }) + return self +} const HandlersProto = { - [HandlersTypeId]: { - _Endpoints: identity - }, + [HandlersTypeId]: HandlersTypeId, pipe() { return pipeArguments(this, arguments) }, handle( - this: Handlers, - name: string, - handler: HttpApiEndpoint.Handler, + this: Handlers, + identifier: string, + handler: HttpApiEndpoint.Handler, options?: { readonly uninterruptible?: boolean | undefined } | undefined ) { - const endpoint = this.group.endpoints[name] - this.handlers.set(name, { - endpoint, - handler, - isRaw: false, - uninterruptible: options?.uninterruptible ?? false - }) + return registerHandler(this, identifier, handler, false, options) + }, + handleAll( + this: Handlers, + handlers: Record< + string, + HttpApiEndpoint.Handler | { + readonly handler: HttpApiEndpoint.Handler + readonly options?: HandlerOptions | undefined + } + > + ) { + for (const [identifier, entry] of Object.entries(handlers)) { + const handler = typeof entry === "function" ? entry : entry.handler + const options = typeof entry === "function" ? undefined : entry.options + registerHandler(this, identifier, handler, false, options) + } return this }, handleRaw( - this: Handlers, - name: string, - handler: HttpApiEndpoint.Handler, + this: Handlers, + identifier: string, + handler: HttpApiEndpoint.Handler, options?: { readonly uninterruptible?: boolean | undefined } | undefined ) { - const endpoint = this.group.endpoints[name] - this.handlers.set(name, { - endpoint, - handler, - isRaw: true, - uninterruptible: options?.uninterruptible ?? false - }) - return this + return registerHandler(this, identifier, handler, true, options) } } -const makeHandlers = ( - group: HttpApiGroup.Any -): Handlers => { +const makeHandlers = ( + group: Group +): Handlers>> => { const self = Object.create(HandlersProto) self.group = group - self.handlers = new Map>() + self.handlers = new Map() return self } @@ -580,7 +694,7 @@ function decodePayload( ): Effect.Effect | HttpServerResponse | undefined { const hasBody = HttpMethod.hasBody(httpRequest.method) const contentType = hasBody - ? getRequestMediaType(httpRequest) + ? MediaType.normalize(httpRequest.headers["content-type"] ?? "application/json") : "application/x-www-form-urlencoded" const existing = payloadBy.get(contentType) if (!existing) { @@ -603,13 +717,20 @@ function decodePayload( ) } case "Json": - const json = Effect.orDie(Effect.flatMap(httpRequest.text, (text) => { + return Effect.flatMap(Effect.orDie(httpRequest.text), (text) => { if (text === "") { - return existing.nullOnEmpty ? Effect.succeed(null) : Effect.undefined + return decode(existing.nullOnEmpty ? null : undefined) } - return Effect.succeed(JSON.parse(text)) - })) - return Effect.flatMap(json, decode) + try { + return decode(JSON.parse(text)) + } catch { + return Effect.fail( + new Schema.SchemaError( + new SchemaIssue.InvalidValue({ message: "Expected a valid JSON body" }) + ) + ) + } + }) case "Text": return Effect.flatMap(Effect.orDie(httpRequest.text), decode) case "FormUrlEncoded": { @@ -627,10 +748,10 @@ function decodePayload( } function handlerToHttpEffect( - group: HttpApiGroup.AnyWithProps, - endpoint: HttpApiEndpoint.AnyWithProps, + group: HttpApiGroup.Top, + endpoint: HttpApiEndpoint.Top, context: Context.Context, - handler: HttpApiEndpoint.Handler, + handler: HttpApiEndpoint.Handler, isRaw: boolean ) { const encodeSuccess = Schema.encodeUnknownEffect(makeSuccessSchema(endpoint)) @@ -639,6 +760,7 @@ function handlerToHttpEffect( const decodeHeaders = UndefinedOr.map(endpoint.headers, Schema.decodeUnknownEffect) const decodeQuery = UndefinedOr.map(endpoint.query, Schema.decodeUnknownEffect) const encodeStream = makeStreamEncoder(endpoint) + const encodeWithHeaders = makeWithHeadersEncoder(endpoint) const shouldParsePayload = endpoint.payload.size > 0 && !isRaw const payloadBy = shouldParsePayload ? buildPayloadDecoders(endpoint.payload) : undefined @@ -676,15 +798,26 @@ function handlerToHttpEffect( request.payload = yield* HttpApiSchemaError.wrap("Payload", result) } } - const response = yield* handler(request) + let response = yield* handler(request) if (Response.isHttpServerResponse(response)) { return response } - const streamResponse = encodeStream?.(response, context) - if (streamResponse !== undefined) { - return yield* HttpApiSchemaError.wrap("Body", streamResponse) + let responseHeaders: unknown | undefined + if (encodeWithHeaders !== undefined && HttpApiSchema.isWithHeadersValue(response)) { + responseHeaders = response.headers + response = response.body } - return yield* HttpApiSchemaError.wrap("Body", encodeSuccess(response)) + const encoded = yield* HttpApiSchemaError.wrap( + "Body", + encodeStream?.(response, context) ?? + (responseHeaders !== undefined ? encodeWithHeaders!.encodeBody(response) : encodeSuccess(response)) + ) + if (encodeWithHeaders === undefined || responseHeaders === undefined) return encoded + const encodedHeaders = yield* HttpApiSchemaError.wrap( + "ResponseHeaders", + encodeWithHeaders.encodeHeaders.get(encoded.status)!(responseHeaders) + ) + return Response.setHeaders(encoded, encodedHeaders as any) }) ).pipe( Effect.withErrorReporting, @@ -698,8 +831,8 @@ function handlerToHttpEffect( /** @internal */ export function handlerToRoute( - group: HttpApiGroup.AnyWithProps, - handler: Handlers.Item, + group: HttpApiGroup.Top, + handler: HandlerRuntime, context: Context.Context ): HttpRouter.Route { const endpoint = handler.endpoint @@ -711,31 +844,20 @@ export function handlerToRoute( ) } -const getRequestContentType = (request: HttpServerRequest): string => - request.headers["content-type"] - ? request.headers["content-type"].toLowerCase().trim() - : "application/json" - -const getRequestMediaType = (request: HttpServerRequest): string => { - const contentType = getRequestContentType(request) - const index = contentType.indexOf(";") - return index === -1 ? contentType : contentType.slice(0, index).trim() -} - -const applyMiddleware = >( - group: HttpApiGroup.AnyWithProps, - endpoint: HttpApiEndpoint.AnyWithProps, +const applyMiddleware = >( + group: Group, + endpoint: HttpApiEndpoint.Top, context: Context.Context, handler: A ) => { const options = { group, endpoint } for (const key_ of endpoint.middlewares) { - const key = key_ as any as HttpApiMiddleware.AnyService - const service = Context.getUnsafe(context, key as any) as HttpApiMiddleware.HttpApiMiddleware + const key = key_ as HttpApiMiddleware.AnyService + const service = Context.getUnsafe(context, key) const apply = HttpApiMiddleware.isSecurity(key) - ? makeSecurityMiddleware(key, service as any) + ? makeSecurityMiddleware(key, service) : service - handler = apply(handler, options) as A + handler = apply(handler, options) } return handler } @@ -763,9 +885,10 @@ const makeSecurityMiddleware = ( } const middleware = Effect.fnUntraced(function*(handler: Effect.Effect, options: { - readonly group: HttpApiGroup.AnyWithProps - readonly endpoint: HttpApiEndpoint.AnyWithProps + readonly group: HttpApiGroup.Top + readonly endpoint: HttpApiEndpoint.Top }) { + handler = Effect.mapError(handler, (error) => new HandlerError(error)) let lastResult: Result.Result | undefined for (let i = 0; i < entries.length; i++) { const { decode, middleware } = entries[i] @@ -776,6 +899,9 @@ const makeSecurityMiddleware = ( group: options.group }))) if (Result.isFailure(result)) { + if (isHandlerError(result.failure)) { + return yield* Effect.fail(result.failure.error) + } lastResult = result continue } @@ -788,13 +914,54 @@ const makeSecurityMiddleware = ( return middleware } +const HandlerErrorTypeId = "~effect/httpapi/HttpApiBuilder/HandlerError" as const +class HandlerError { + readonly [HandlerErrorTypeId] = HandlerErrorTypeId + readonly error: unknown + constructor(error: unknown) { + this.error = error + } +} +const isHandlerError = (value: unknown): value is HandlerError => hasProperty(value, HandlerErrorTypeId) + const $HttpServerResponse = Schema.declare(Response.isHttpServerResponse) type StreamEncoder = (response: unknown, context: Context.Context) => | Effect.Effect | undefined -function makeStreamEncoder(endpoint: HttpApiEndpoint.AnyWithProps): StreamEncoder | undefined { +type WithHeadersEncoder = (headers: unknown) => Effect.Effect + +interface WithHeadersEncoders { + readonly encodeBody: (body: unknown) => Effect.Effect + readonly encodeHeaders: ReadonlyMap +} + +function makeWithHeadersEncoder(endpoint: HttpApiEndpoint.Top): WithHeadersEncoders | undefined { + const encodeHeaders = new Map() + const bodySchemas: Array> = [] + for (const schema of endpoint.success) { + if (!HttpApiSchema.isWithHeaders(schema)) continue + encodeHeaders.set(HttpApiSchema.getStatusSuccessSchema(schema), Schema.encodeUnknownEffect(schema.headers)) + if (!HttpApiSchema.isStreamSchema(schema.schema)) { + bodySchemas.push(toResponseSuccessSchema(schema)) + } + } + if (encodeHeaders.size === 0) return undefined + // Branded response bodies encode against the header-carrying members only, so + // the encoded status always has a matching header schema. + const bodySchema: Schema.ConstraintEncoder = bodySchemas.length === 0 + ? Schema.Never + : bodySchemas.length === 1 + ? bodySchemas[0] + : Schema.Union(bodySchemas) + return { + encodeBody: Schema.encodeUnknownEffect(bodySchema), + encodeHeaders + } +} + +function makeStreamEncoder(endpoint: HttpApiEndpoint.Top): StreamEncoder | undefined { const streamSchema = getStreamSuccessSchema(endpoint) if (streamSchema === undefined) { return undefined @@ -807,7 +974,9 @@ function makeStreamEncoder(endpoint: HttpApiEndpoint.AnyWithProps): StreamEncode if (HttpApiSchema.isStreamUint8Array(streamSchema)) { return (response, context) => { if (!Stream.isStream(response)) { - return hasBuffered ? undefined : expectedStreamResponse(response) + return hasBuffered ? undefined : new Schema.SchemaError( + new SchemaIssue.InvalidValue({ message: "Expected a streaming response" }) + ) } return Effect.succeed(Response.stream( @@ -824,7 +993,9 @@ function makeStreamEncoder(endpoint: HttpApiEndpoint.AnyWithProps): StreamEncode return (response, context) => { if (!Stream.isStream(response)) { - return hasBuffered ? undefined : expectedStreamResponse(response) + return hasBuffered ? undefined : new Schema.SchemaError( + new SchemaIssue.InvalidValue({ message: "Expected a streaming response" }) + ) } return Effect.succeed(Response.stream( @@ -837,31 +1008,23 @@ function makeStreamEncoder(endpoint: HttpApiEndpoint.AnyWithProps): StreamEncode } } -function getStreamSuccessSchema(endpoint: HttpApiEndpoint.AnyWithProps) { +function getStreamSuccessSchema(endpoint: HttpApiEndpoint.Top) { for (const schema of endpoint.success) { - if (HttpApiSchema.isStreamSchema(schema)) { - return schema + const body = HttpApiSchema.isWithHeaders(schema) ? schema.schema : schema + if (HttpApiSchema.isStreamSchema(body)) { + return body } } } -function hasBufferedSuccess(endpoint: HttpApiEndpoint.AnyWithProps): boolean { +function hasBufferedSuccess(endpoint: HttpApiEndpoint.Top): boolean { for (const schema of endpoint.success) { - if (Schema.isSchema(schema) && !HttpApiSchema.isStreamSchema(schema)) return true + const body = HttpApiSchema.isWithHeaders(schema) ? schema.schema : schema + if (Schema.isSchema(body) && !HttpApiSchema.isStreamSchema(body)) return true } return endpoint.success.size === 0 } -function expectedStreamResponse(response: unknown) { - return Effect.fail( - makeSchemaError( - new SchemaIssue.InvalidValue(Option.some(response), { - message: "Expected a streaming response" - }) - ) - ) -} - interface SseStreamEncoder { readonly sseMode: HttpApiSchema.StreamSseMode readonly encodeEvents: ( @@ -922,77 +1085,147 @@ function renderSseEvent(event: Sse.EventEncoded) { }) } -function makeSchemaError(issue: SchemaIssue.Issue): Schema.SchemaError { - return new Schema.SchemaError(issue) -} +const toResponseSuccessSchema = toResponseSchema(HttpApiSchema.getStatusSuccessSchema) +const toResponseErrorSchemaPlain = toResponseSchema(HttpApiSchema.getStatusErrorSchema) -const toResponseSuccessSchema = toResponseSchema(HttpApiSchema.getStatusSuccess) -const toResponseErrorSchema = toResponseSchema(HttpApiSchema.getStatusError) +function toResponseErrorSchema( + schema: Schema.Constraint +): Schema.ConstraintEncoder { + if (!HttpApiSchema.isWithHeaders(schema)) return toResponseErrorSchemaPlain(schema) + + const encodeBody = Schema.encodeUnknownEffect(schema.schema) + const encodeHeaders = Schema.encodeUnknownEffect(schema.headers) + const encodeResponse = getResponseEncode( + HttpApiSchema.getStatusErrorSchema(schema), + HttpApiSchema.getResponseEncodingSchema(schema), + HttpApiSchema.isNoContent(schema.schema.ast) + ) + const transformation = withHeadersTransformation>( + (body, options) => + encodeBody(body, options).pipe( + Effect.mapError((error) => error.issue), + Effect.flatMap((body) => encodeResponse(body, options)) + ), + encodeHeaders + ) + return $HttpServerResponse.pipe(Schema.decodeTo(schema, transformation)) +} function makeSuccessSchema( - endpoint: HttpApiEndpoint.AnyWithProps + endpoint: HttpApiEndpoint.Top ): Schema.ConstraintEncoder { const schemas = HttpApiEndpoint.getSuccessSchemas(endpoint).map(toResponseSuccessSchema) return schemas.length === 1 ? schemas[0] : Schema.Union(schemas) } function makeErrorSchema( - endpoint: HttpApiEndpoint.AnyWithProps + endpoint: HttpApiEndpoint.Top ): Schema.ConstraintEncoder { const schemas = HttpApiEndpoint.getErrorSchemas(endpoint).map(toResponseErrorSchema) if (schemas.length === 0) return Schema.Never return schemas.length === 1 ? schemas[0] : Schema.Union(schemas) } -function toResponseSchema(getStatus: (ast: SchemaAST.AST) => number) { - const cache = new WeakMap() +function toResponseSchema(getStatus: (schema: Schema.Constraint) => number) { + // WithHeaders wrappers share a single declaration AST, so they are cached by instance + const cache = new WeakMap() return (schema: Schema.Constraint): Schema.ConstraintEncoder => { - const cached = cache.get(schema.ast) + const key = HttpApiSchema.isWithHeaders(schema) ? schema : schema.ast + const cached = cache.get(key) if (cached !== undefined) { return cached as any } + const bodySchema = HttpApiSchema.isWithHeaders(schema) ? schema.schema : schema const responseSchema = $HttpServerResponse.pipe( - Schema.decodeTo(schema, getResponseTransformation(getStatus, schema)) + Schema.decodeTo(bodySchema, getResponseTransformation(getStatus, schema)) ) - cache.set(responseSchema.ast, responseSchema) + cache.set(key, responseSchema) return responseSchema } } function getResponseTransformation( - getStatus: (ast: SchemaAST.AST) => number, + getStatus: (schema: Schema.Constraint) => number, schema: Schema.Constraint -): SchemaTransformation.Transformation { - const ast = schema.ast +): SchemaTransformation.Transformation { + const withHeaders = HttpApiSchema.getWithHeadersAnnotation(schema.ast) + if (withHeaders !== undefined) { + const encodeBody = getResponseEncode( + getStatus(withHeaders.body), + HttpApiSchema.getResponseEncoding(withHeaders.body.ast), + HttpApiSchema.isNoContent(withHeaders.body.ast) + ) + return withHeadersTransformation(encodeBody, Schema.encodeUnknownEffect(withHeaders.headersCodec)) + } + + const bodySchema = HttpApiSchema.isWithHeaders(schema) ? schema.schema : schema const encode = getResponseEncode( - getStatus(ast), - HttpApiSchema.getResponseEncoding(ast), - HttpApiSchema.isNoContent(ast) + getStatus(schema), + HttpApiSchema.getResponseEncodingSchema(schema), + HttpApiSchema.isNoContent(bodySchema.ast) ) return SchemaTransformation.transformOrFail({ - decode: (res) => Effect.fail(new SchemaIssue.Forbidden(Option.some(res), { message: "Encode only schema" })), + decode: (input, options) => + Effect.fail( + new SchemaIssue.Forbidden({ message: "Encode only schema" }, input, options) + ), encode }) } +function withHeadersTransformation( + encodeBody: ( + body: unknown, + options?: SchemaAST.ParseOptions + ) => Effect.Effect, + encodeHeaders: ( + headers: unknown, + options?: SchemaAST.ParseOptions + ) => Effect.Effect +): SchemaTransformation.Transformation { + return SchemaTransformation.transformOrFail({ + decode: (input, options) => + Effect.fail( + new SchemaIssue.Forbidden({ message: "Encode only schema" }, input, options) + ), + encode: (value, options) => { + const pair = value as { readonly body: unknown; readonly headers: unknown } + return Effect.flatMap(encodeBody(pair.body, options), (response) => + Effect.map( + encodeHeaders(pair.headers, options).pipe(Effect.mapError((error) => error.issue)), + (headers) => Response.setHeaders(response, headers as any) + )) + } + }) +} + function getResponseEncode( status: number, encoding: HttpApiSchema.ResponseEncoding, isNoContent: boolean -): (e: E) => Effect.Effect { +): ( + e: E, + options?: SchemaAST.ParseOptions +) => Effect.Effect { switch (encoding._tag) { case "Json": { - return ((e) => { + return ((e, options) => { if (e === undefined || isNoContent) { return Effect.succeed(Response.empty({ status })) } try { const s = JSON.stringify(e) return Effect.succeed(Response.text(s, { status, contentType: encoding.contentType })) - } catch (error) { - return Effect.fail(new SchemaIssue.InvalidValue(Option.some(e), { message: globalThis.String(error) })) + } catch { + return Effect.fail( + new SchemaIssue.InvalidValue( + { expected: "a JSON-serializable response body" }, + e, + options + ) + ) } }) } diff --git a/repos/effect/packages/effect/src/unstable/httpapi/HttpApiClient.ts b/repos/effect/packages/effect/src/unstable/httpapi/HttpApiClient.ts index d1de48b839..365329af41 100644 --- a/repos/effect/packages/effect/src/unstable/httpapi/HttpApiClient.ts +++ b/repos/effect/packages/effect/src/unstable/httpapi/HttpApiClient.ts @@ -15,7 +15,7 @@ import * as Cause from "../../Cause.ts" import type * as Context from "../../Context.ts" import * as Effect from "../../Effect.ts" import { identity } from "../../Function.ts" -import * as Option from "../../Option.ts" +import * as InternalRecord from "../../internal/record.ts" import * as Predicate from "../../Predicate.ts" import * as Schema from "../../Schema.ts" import * as SchemaAST from "../../SchemaAST.ts" @@ -37,6 +37,7 @@ import * as HttpApiEndpoint from "./HttpApiEndpoint.ts" import type * as HttpApiGroup from "./HttpApiGroup.ts" import type * as HttpApiMiddleware from "./HttpApiMiddleware.ts" import * as HttpApiSchema from "./HttpApiSchema.ts" +import * as MediaType from "./internal/mediaType.ts" /** * The type-safe client shape generated from HTTP API groups, with non-top-level @@ -45,40 +46,41 @@ import * as HttpApiSchema from "./HttpApiSchema.ts" * @category models * @since 4.0.0 */ -export type Client = Simplify< +export type Client = Simplify< & { - readonly [Group in Extract as HttpApiGroup.Name]: Client.Group< + readonly [Group in Extract as HttpApiGroup.Identifier]: Client.Group< Group, - Group["identifier"], E, R > } - & { - readonly [Method in Client.TopLevelMethods as Method[0]]: Method[1] - } + & Client.TopLevelMethods > /** * Derives the typed client interface for an `HttpApi`, preserving any additional * client error and service requirements supplied by the caller. * - * @category models + * @category utility types * @since 4.0.0 */ -export type ForApi = Api extends +export type ForApi = Api extends HttpApi.HttpApi ? Client : never -type SuccessType = S extends HttpApiSchema.StreamSse< - infer _Events, - infer _Error, - infer _Value -> ? Stream.Stream< - _Value, - _Error["Type"] | HttpClientError.HttpClientError | Schema.SchemaError | Sse.Retry, - never - > +type SuccessType = S extends HttpApiSchema.WithHeaders< + infer _Inner, + infer _Headers +> ? HttpApiSchema.withHeaders, _Headers["Type"]> + : S extends HttpApiSchema.StreamSse< + infer _Events, + infer _Error, + infer _Value + > ? Stream.Stream< + _Value, + _Error["Type"] | HttpClientError.HttpClientError | Schema.SchemaError | Sse.Retry | Sse.SseError, + never + > : S extends HttpApiSchema.StreamUint8Array ? Stream.Stream : S extends Schema.Constraint ? S["Type"] : never @@ -122,19 +124,43 @@ export declare namespace Client { : [Mode] extends ["response-only"] ? HttpClientResponse.HttpClientResponse : Success + type GroupByEndpoint = Group["endpoints"] extends + infer Endpoints extends Readonly> ? { + readonly [Identifier in keyof Endpoints]: Method + } + : {} + /** - * The client object for one API group, mapping each endpoint name in that group to - * its typed client method. + * The client object for one API group, mapping each endpoint identifier in that + * group to its typed client method. * * @category models * @since 4.0.0 */ - export type Group = - [HttpApiGroup.WithName] extends [HttpApiGroup.HttpApiGroup] ? - { - readonly [Endpoint in _Endpoints as HttpApiEndpoint.Name]: Method - } : - never + export type Group = GroupByEndpoint + + type MethodReturn< + Endpoint extends HttpApiEndpoint.ConstraintRequest, + E, + R, + Mode extends ResponseMode + > = Effect.Effect< + Response, Mode>, + | HttpApiMiddleware.Error + | HttpApiMiddleware.ClientError + | E + | HttpClientError.HttpClientError + | ([Mode] extends ["response-only"] ? never : Endpoint["~Error"]["Type"] | Schema.SchemaError), + | R + | Endpoint["~Params"]["EncodingServices"] + | Endpoint["~Query"]["EncodingServices"] + | Endpoint["~Payload"]["EncodingServices"] + | Endpoint["~Headers"]["EncodingServices"] + | ([Mode] extends ["response-only"] ? never + : + | SuccessDecodingServices + | Endpoint["~Error"]["DecodingServices"]) + > /** * The typed function generated for an endpoint, accepting the endpoint request @@ -144,40 +170,21 @@ export declare namespace Client { * @category models * @since 4.0.0 */ - export type Method = [Endpoint] extends [ - HttpApiEndpoint.HttpApiEndpoint< - infer _Name, - infer _Method, - infer _Path, - infer _Params, - infer _Query, - infer _Payload, - infer _Headers, - infer _Success, - infer _Error, - infer _Middleware, - infer _MR + export type Method< + Endpoint extends HttpApiEndpoint.ConstraintRequest, + E, + R + > = ( + request: Simplify< + HttpApiEndpoint.ClientRequest< + Endpoint["~Params"], + Endpoint["~Query"], + Endpoint["~Payload"], + Endpoint["~Headers"], + Mode + > > - ] ? ( - request: Simplify> - ) => Effect.Effect< - Response, Mode>, - | HttpApiMiddleware.Error<_Middleware> - | HttpApiMiddleware.ClientError<_Middleware> - | E - | HttpClientError.HttpClientError - | ([Mode] extends ["response-only"] ? never : _Error["Type"] | Schema.SchemaError), - | R - | _Params["EncodingServices"] - | _Query["EncodingServices"] - | _Payload["EncodingServices"] - | _Headers["EncodingServices"] - | ([Mode] extends ["response-only"] ? never - : - | SuccessDecodingServices<_Success> - | _Error["DecodingServices"]) - > : - never + ) => MethodReturn /** * Extracts client methods for endpoints in top-level groups so they can be exposed @@ -186,25 +193,31 @@ export declare namespace Client { * @category models * @since 4.0.0 */ - export type TopLevelMethods = - Extract extends - HttpApiGroup.HttpApiGroup ? - _Endpoints extends infer Endpoint ? [HttpApiEndpoint.Name, Method] - : never : - never + export type TopLevelMethods = { + readonly [ + Endpoint in Extract< + HttpApiGroup.Endpoints>, + HttpApiEndpoint.ConstraintRequest + > as Endpoint["identifier"] + ]: Method + } } -type UrlBuilderRequest = ( - & ([HttpApiEndpoint.Params["Type"]] extends [never] ? {} - : { readonly params: HttpApiEndpoint.Params["Type"] }) - & ([HttpApiEndpoint.Query["Type"]] extends [never] ? {} - : { readonly query: HttpApiEndpoint.Query["Type"] }) +type UrlBuilderRequestPart = [Value] extends [never] ? {} + : { readonly [K in Key]: Value } + +type UrlBuilderRequest< + Endpoint extends HttpApiEndpoint.Constraint, + Params = HttpApiEndpoint.Params["Type"], + Query = HttpApiEndpoint.Query["Type"] +> = ( + & UrlBuilderRequestPart<"params", Params> + & UrlBuilderRequestPart<"query", Query> ) extends infer Request ? keyof Request extends never ? void | undefined : Request : never -type UrlBuilderArgs = [UrlBuilderRequest] extends [void | undefined] ? - [request?: UrlBuilderRequest] - : [request: UrlBuilderRequest] +type UrlBuilderArgs = [Request] extends [void | undefined] ? [request?: Request] + : [request: Request] /** * The type-safe URL builder shape for an HTTP API, mirroring the generated client @@ -213,49 +226,50 @@ type UrlBuilderArgs = [UrlBuilderRequest = Api extends HttpApi.HttpApi ? Simplify< - & { - readonly [Group in Extract as HttpApiGroup.Name]: UrlBuilderGroup< - HttpApiGroup.Endpoints - > - } - & { - readonly [Method in UrlBuilderTopLevelMethods as Method[0]]: Method[1] - } - > +export type UrlBuilder = Api extends HttpApi.HttpApi ? + [Extract] extends [never] ? UrlBuilderGroups + : [Extract] extends [never] ? UrlBuilderTopLevelMethods + : Simplify & UrlBuilderTopLevelMethods> : never -type UrlBuilderGroup = { - readonly [Endpoint in Endpoints as HttpApiEndpoint.Name]: UrlBuilderMethod +type UrlBuilderGroups = { + readonly [Group in Extract as HttpApiGroup.Identifier]: UrlBuilderGroup< + HttpApiGroup.Endpoints + > +} + +type UrlBuilderGroup = { + readonly [Endpoint in Endpoints as HttpApiEndpoint.Identifier]: UrlBuilderMethod } -type UrlBuilderMethod = ( - ...args: UrlBuilderArgs +type UrlBuilderMethod = ( + ...args: UrlBuilderArgs> ) => string -type UrlBuilderTopLevelMethods = Extract extends - HttpApiGroup.HttpApiGroup ? - _Endpoints extends infer Endpoint extends HttpApiEndpoint.Any ? - [HttpApiEndpoint.Name, UrlBuilderMethod] - : never : - never +type UrlBuilderTopLevelMethods = { + readonly [ + Endpoint in HttpApiGroup.Endpoints> as HttpApiEndpoint.Identifier< + Endpoint + > + ]: UrlBuilderMethod +} /** @internal */ -export const makeClient = ( +export const makeClient = ( api: HttpApi.HttpApi, options: { readonly httpClient: HttpClient.HttpClient.With readonly predicate?: Predicate.Predicate<{ - readonly endpoint: HttpApiEndpoint.AnyWithProps - readonly group: HttpApiGroup.AnyWithProps + readonly endpoint: HttpApiEndpoint.Top + readonly group: HttpApiGroup.Top }> readonly onGroup?: (options: { - readonly group: HttpApiGroup.AnyWithProps + readonly group: HttpApiGroup.Top readonly mergedAnnotations: Context.Context }) => void readonly onEndpoint: (options: { - readonly group: HttpApiGroup.AnyWithProps - readonly endpoint: HttpApiEndpoint.AnyWithProps + readonly group: HttpApiGroup.Top + readonly endpoint: HttpApiEndpoint.Top readonly mergedAnnotations: Context.Context readonly middleware: ReadonlySet readonly successes: ReadonlyMap]> @@ -280,8 +294,8 @@ export const makeClient = , index: number @@ -321,9 +335,15 @@ export const makeClient = Effect.Effect > = { orElse: statusOrElse } const decodeResponse = HttpClientResponse.matchStatus(decodeMap) - errors.forEach((schemas, status) => { - // decoders - const decode = schemasToResponse(schemas) + const errorAlternatives = new Map>() + for (const [status, schemas] of errors.entries()) { + const grouped = groupSchemasByContentType(schemas) + for (const [contentType, schemas] of grouped.entries()) { + addResponseAlternative(errorAlternatives, status, contentType, schemasToResponse(schemas)) + } + } + for (const [status, alternatives] of errorAlternatives.entries()) { + const decode = makeResponseDecoder(alternatives) decodeMap[status] = (response) => Effect.flatMap( Effect.catchCause(decode(response), (cause) => @@ -340,7 +360,7 @@ export const makeClient = >() for (const [status, schemas] of successes.entries()) { @@ -350,10 +370,11 @@ export const makeClient = ( +export const make = ( api: HttpApi.HttpApi, options?: { readonly transformClient?: ((client: HttpClient.HttpClient) => HttpClient.HttpClient) | undefined @@ -484,7 +505,7 @@ export const make = ( * @category constructors * @since 4.0.0 */ -export const makeWith = ( +export const makeWith = ( api: HttpApi.HttpApi, options: { readonly httpClient: HttpClient.HttpClient.With @@ -493,16 +514,24 @@ export const makeWith = , never, HttpApiGroup.MiddlewareClient> => { +): Effect.Effect< + Client, R>, + never, + HttpApiGroup.MiddlewareClient +> => { const client: Record> = {} return makeClient(api, { ...options, onGroup({ group }) { if (group.topLevel) return - client[group.identifier] = {} + InternalRecord.assignProperty(client, group.identifier, {}) }, onEndpoint({ endpoint, endpointFn, group }) { - ;(group.topLevel ? client : client[group.identifier])[endpoint.name] = endpointFn + InternalRecord.assignProperty( + group.topLevel ? client : client[group.identifier], + endpoint.identifier, + endpointFn + ) } }).pipe(Effect.as(client)) as any } @@ -516,14 +545,14 @@ export const makeWith = , + Groups extends HttpApiGroup.Constraint, + const GroupIdentifier extends HttpApiGroup.Identifier, E, R >( api: HttpApi.HttpApi, options: { - readonly group: GroupName + readonly group: GroupIdentifier readonly httpClient: HttpClient.HttpClient.With readonly transformResponse?: | ((effect: Effect.Effect) => Effect.Effect) @@ -531,20 +560,32 @@ export const group = < readonly baseUrl?: URL | string | undefined } ): Effect.Effect< - Client.Group, + Client.Group, E, R>, never, - HttpApiGroup.MiddlewareClient> + HttpApiGroup.MiddlewareClient> > => { const client: Record = {} return makeClient(api, { ...options, predicate: ({ group }) => group.identifier === options.group, onEndpoint({ endpoint, endpointFn }) { - client[endpoint.name] = endpointFn + InternalRecord.assignProperty(client, endpoint.identifier, endpointFn) } }).pipe(Effect.map(() => client)) as any } +type EndpointReturn< + Groups extends HttpApiGroup.Constraint, + GroupIdentifier extends HttpApiGroup.Identifier, + EndpointIdentifier extends HttpApiGroup.EndpointsWithIdentifier["identifier"], + E, + R, + Endpoint extends HttpApiEndpoint.ConstraintRequest = Extract< + HttpApiEndpoint.WithIdentifier, EndpointIdentifier>, + HttpApiEndpoint.ConstraintRequest + > +> = Effect.Effect, never, HttpApiEndpoint.MiddlewareClient> + /** * Builds the typed client method for one endpoint in one API group, using the * supplied `HttpClient` and endpoint metadata. @@ -554,38 +595,33 @@ export const group = < */ export const endpoint = < ApiId extends string, - Groups extends HttpApiGroup.Any, - const GroupName extends HttpApiGroup.Name, - const EndpointName extends HttpApiEndpoint.Name>, + Groups extends HttpApiGroup.Constraint, + const GroupIdentifier extends HttpApiGroup.Identifier, + const EndpointIdentifier extends HttpApiGroup.EndpointsWithIdentifier["identifier"], E, R >( api: HttpApi.HttpApi, options: { - readonly group: GroupName - readonly endpoint: EndpointName + readonly group: GroupIdentifier + readonly endpoint: EndpointIdentifier readonly httpClient: HttpClient.HttpClient.With - readonly transformClient?: ((client: HttpClient.HttpClient) => HttpClient.HttpClient) | undefined + readonly transformClient?: + | ((client: HttpClient.HttpClient.With) => HttpClient.HttpClient.With) + | undefined readonly transformResponse?: | ((effect: Effect.Effect) => Effect.Effect) | undefined readonly baseUrl?: URL | string | undefined } -): Effect.Effect< - Client.Method< - HttpApiEndpoint.WithName>, EndpointName>, - E, - R - >, - never, - HttpApiEndpoint.MiddlewareClient< - HttpApiEndpoint.WithName>, EndpointName> - > -> => { +): EndpointReturn => { let client: any = undefined return makeClient(api, { ...options, - predicate: ({ endpoint, group }) => group.identifier === options.group && endpoint.name === options.endpoint, + httpClient: options.transformClient + ? options.transformClient(options.httpClient) + : options.httpClient, + predicate: ({ endpoint, group }) => group.identifier === options.group && endpoint.identifier === options.endpoint, onEndpoint({ endpointFn }) { client = endpointFn } @@ -597,7 +633,7 @@ export const endpoint = < * * **Example** (Building typed URLs) * - * ```ts + * ```ts import.meta.vitest * import { Schema } from "effect" * import { HttpApi, HttpApiClient, HttpApiEndpoint, HttpApiGroup } from "effect/unstable/httpapi" * @@ -615,31 +651,30 @@ export const endpoint = < * * buildUrl.users.getUser({ * params: { id: "123" } - * }) - * //=> "https://api.example.com/users/123" + * }) // => "https://api.example.com/users/123" * ``` * * @category constructors * @since 4.0.0 */ -export const urlBuilder = (api: Api, options?: { +export const urlBuilder = (api: Api, options?: { readonly baseUrl?: URL | string | undefined }): UrlBuilder => { const builder: Record = {} - HttpApi.reflect(api as unknown as HttpApi.AnyWithProps, { + HttpApi.reflect(api as unknown as HttpApi.Top, { onGroup({ group }) { if (group.topLevel) return - builder[group.identifier] = {} + InternalRecord.assignProperty(builder, group.identifier, {}) }, onEndpoint({ group, endpoint }) { const makeUrl = compilePath(endpoint.path) const encodeParams = endpoint.params === undefined ? undefined - : Schema.encodeSync(endpoint.params as Schema.Codec) + : Schema.encodeSync(endpoint.params as unknown as Schema.ConstraintEncoder) const encodeQuery = endpoint.query === undefined ? undefined - : Schema.encodeSync(endpoint.query as Schema.Codec) + : Schema.encodeSync(endpoint.query as unknown as Schema.ConstraintEncoder) const endpointBuilder = (request?: { readonly params?: unknown @@ -656,7 +691,11 @@ export const urlBuilder = (api: Api, options?: { const url = query === "" ? path : `${path}?${query}` return options?.baseUrl === undefined ? url : new URL(url, options.baseUrl.toString()).toString() } - ;(group.topLevel ? builder : builder[group.identifier])[endpoint.name] = endpointBuilder + InternalRecord.assignProperty( + group.topLevel ? builder : builder[group.identifier], + endpoint.identifier, + endpointBuilder + ) } }) @@ -688,9 +727,44 @@ const compilePath = (path: string) => { } function schemasToResponse(schemas: readonly [Schema.Constraint, ...Array]) { - const codec = toCodecArrayBuffer(schemas) + const hasWithHeaders = schemas.some((schema) => + HttpApiSchema.isWithHeaders(schema) || HttpApiSchema.getWithHeadersAnnotation(schema.ast) !== undefined + ) + const codec = hasWithHeaders + ? Schema.Union(schemas.map(toCodecArrayBufferWithHeaders)) + : toCodecArrayBuffer(schemas) const decode = Schema.decodeEffect(codec) - return (response: HttpClientResponse.HttpClientResponse) => Effect.flatMap(response.arrayBuffer, decode) + return (response: HttpClientResponse.HttpClientResponse) => + Effect.flatMap( + response.arrayBuffer, + hasWithHeaders + ? (body) => decode({ body, headers: response.headers }) + : decode + ) +} + +function toCodecArrayBufferWithHeaders(schema: Schema.Constraint): Schema.Top { + const isWithHeaders = HttpApiSchema.isWithHeaders(schema) + const annotation = HttpApiSchema.getWithHeadersAnnotation(schema.ast) + if (annotation !== undefined) { + return Schema.Struct({ + body: fromArrayBuffer(annotation.body), + headers: annotation.headersCodec + }).pipe(Schema.decodeTo(schema)) + } + const body = isWithHeaders ? schema.schema : schema + return Schema.Struct({ + body: fromArrayBuffer(body).pipe(Schema.decodeTo(body)), + headers: isWithHeaders ? schema.headers : Schema.Unknown + }).pipe( + Schema.decodeTo( + isWithHeaders ? schema : Schema.toType(schema), + SchemaTransformation.transform({ + decode: (value) => isWithHeaders ? HttpApiSchema.withHeaders(value) : value.body, + encode: (value: any) => isWithHeaders ? value : { body: value, headers: undefined } + }) as any + ) + ) } type ResponseDecoder = (response: HttpClientResponse.HttpClientResponse) => Effect.Effect @@ -706,7 +780,7 @@ function addResponseAlternative( contentType: string, decode: ResponseDecoder ) { - const normalizedContentType = normalizeContentType(contentType) + const normalizedContentType = MediaType.normalize(contentType) const alternatives = map.get(status) if (alternatives === undefined) { map.set(status, [{ contentType: normalizedContentType, decode }]) @@ -721,7 +795,7 @@ function makeResponseDecoder(alternatives: ReadonlyArray): return first.decode } return (response) => { - const contentType = normalizeContentType(response.headers["content-type"] ?? "") + const contentType = MediaType.normalize(response.headers["content-type"] ?? "") const alternative = alternatives.find((alternative) => alternative.contentType === contentType) return alternative === undefined ? failUnsupportedContentType(response, contentType, alternatives) @@ -734,7 +808,10 @@ function groupSchemasByContentType( ): Map> { const grouped = new Map]>() for (const schema of schemas) { - const contentType = HttpApiSchema.getResponseEncoding(schema.ast).contentType + const body = HttpApiSchema.isWithHeaders(schema) ? schema.schema : schema + const contentType = HttpApiSchema.isNoContent(body.ast) + ? "" + : MediaType.normalize(HttpApiSchema.getResponseEncodingSchema(schema).contentType) const existing = grouped.get(contentType) if (existing === undefined) { grouped.set(contentType, [schema]) @@ -745,12 +822,6 @@ function groupSchemasByContentType( return grouped } -function normalizeContentType(contentType: string): string { - const normalized = contentType.toLowerCase().trim() - const index = normalized.indexOf(";") - return index === -1 ? normalized : normalized.slice(0, index).trim() -} - function failUnsupportedContentType( response: HttpClientResponse.HttpClientResponse, contentType: string, @@ -772,58 +843,90 @@ function failUnsupportedContentType( const reservedStreamFailureEvent = "effect/httpapi/stream/failure" -function getStreamSuccessSchemas(endpoint: HttpApiEndpoint.AnyWithProps): Array { - const schemas: Array = [] +type StreamSuccessSchema = + | HttpApiSchema.StreamSchema + | HttpApiSchema.WithHeaders + +const isWithHeadersStreamSuccess = ( + schema: StreamSuccessSchema +): schema is HttpApiSchema.WithHeaders => HttpApiSchema.isWithHeaders(schema) + +function getStreamSuccessSchemas(endpoint: HttpApiEndpoint.Top): Array { + const schemas: Array = [] for (const schema of endpoint.success) { - if (HttpApiSchema.isStreamSchema(schema)) { - schemas.push(schema) + const body = HttpApiSchema.isWithHeaders(schema) ? schema.schema : schema + if (HttpApiSchema.isStreamSchema(body)) { + schemas.push(schema as StreamSuccessSchema) } } return schemas } -function streamToResponse(streamSchema: HttpApiSchema.StreamSchema) { - return (response: HttpClientResponse.HttpClientResponse) => +function streamToResponse(successSchema: StreamSuccessSchema) { + const isWithHeaders = isWithHeadersStreamSuccess(successSchema) + const streamSchema = isWithHeaders ? successSchema.schema : successSchema + const sse = HttpApiSchema.isStreamUint8Array(streamSchema) + ? undefined + : { + declaration: streamSchema, + decoder: makeSseDecoder(streamSchema) + } + const toStream = (response: HttpClientResponse.HttpClientResponse) => Effect.map(Effect.context(), (context) => Stream.provideContext( - HttpApiSchema.isStreamUint8Array(streamSchema) ? + sse === undefined ? response.stream : - decodeSseStream(response.stream, streamSchema), + decodeSseStream(response.stream, sse.declaration, sse.decoder), context as Context.Context )) + if (!isWithHeaders) return toStream + + const decodeHeaders = Schema.decodeUnknownEffect(successSchema.headers) + return (response: HttpClientResponse.HttpClientResponse) => + Effect.flatMap( + decodeHeaders(response.headers), + (headers) => Effect.map(toStream(response), (body) => HttpApiSchema.withHeaders({ body, headers })) + ) } -function decodeSseStream( - stream: Stream.Stream, +function makeSseDecoder( declaration: HttpApiSchema.StreamSse -): Stream.Stream { +) { const Event = Schema.Union([ - declaration.events, Schema.Struct({ event: Schema.Literal(reservedStreamFailureEvent), data: Schema.fromJsonString(Schema.toCodecJson(Schema.Cause(declaration.error, Schema.Defect()))) - }) + }), + declaration.events ]) + return Sse.decodeSchema(Event) +} + +function decodeSseStream( + stream: Stream.Stream, + declaration: HttpApiSchema.StreamSse, + decoder: ReturnType +): Stream.Stream { const events = Stream.transformPull( stream.pipe( Stream.decodeText, - Stream.pipeThroughChannel(Sse.decodeSchema(Event)) + Stream.pipeThroughChannel(decoder) ), (pull) => Effect.sync(() => { - let failureCause: Cause.Cause | undefined = undefined + let pendingFailureCause: Cause.Cause | undefined = undefined return Effect.suspend(() => { - if (failureCause) { - return Effect.failCause(failureCause) + if (pendingFailureCause !== undefined) { + return Effect.failCause(pendingFailureCause) } return Effect.flatMap(pull, (events) => { for (let i = 0; i < events.length; i++) { const event = events[i] - if (event.event === reservedStreamFailureEvent) { + if (event.event === reservedStreamFailureEvent && Cause.isCause(event.data)) { if (i === 0) { return Effect.failCause(event.data) } - failureCause = event.data + pendingFailureCause = event.data events = events.slice(0, i) as any break } @@ -897,31 +1000,34 @@ function toCodecArrayBuffer(schemas: readonly [Schema.Constraint, ...Array a === undefined ? null : a, encode: (a) => a === null ? undefined : a - }) as any : - undefined - )) - } - case "FormUrlEncoded": - return StringFromArrayBuffer.pipe( - Schema.decodeTo(UrlParams.schemaRecord), - Schema.decodeTo(schema) + }) as any + ) ) - case "Uint8Array": - return Uint8ArrayFromArrayBuffer.pipe(Schema.decodeTo(schema)) - case "Text": - return StringFromArrayBuffer.pipe(Schema.decodeTo(schema)) + : UnknownFromArrayBuffer } + case "FormUrlEncoded": + return StringFromArrayBuffer.pipe(Schema.decodeTo(UrlParams.schemaRecord)) + case "Uint8Array": + return Uint8ArrayFromArrayBuffer + case "Text": + return StringFromArrayBuffer } } @@ -959,39 +1065,59 @@ function getEncodePayloadSchemaFromBody( const out = $HttpBody.pipe(Schema.decodeTo( schema, SchemaTransformation.transformOrFail({ - decode(httpBody) { - return Effect.fail(new SchemaIssue.Forbidden(Option.some(httpBody), { message: "Encode only schema" })) + decode(input, options) { + return Effect.fail( + new SchemaIssue.Forbidden({ message: "Encode only schema" }, input, options) + ) }, - encode(t) { + encode(t, options) { switch (encoding._tag) { case "Multipart": - return Effect.fail(new SchemaIssue.Forbidden(Option.some(t), { message: "Payload must be a FormData" })) + return Effect.fail( + new SchemaIssue.Forbidden( + { message: "Payload must be a FormData" }, + t, + options + ) + ) case "Json": { try { const body = JSON.stringify(t) return Effect.succeed(HttpBody.text(body, encoding.contentType)) - } catch (error) { - return Effect.fail(new SchemaIssue.InvalidValue(Option.some(t), { message: globalThis.String(error) })) + } catch { + return Effect.fail( + new SchemaIssue.InvalidValue( + { expected: "a JSON-serializable request body" }, + t, + options + ) + ) } } case "Text": { if (typeof t !== "string") { return Effect.fail( - new SchemaIssue.InvalidValue(Option.some(t), { message: "Expected a string" }) + new SchemaIssue.InvalidValue({ message: "Expected a string" }, t, options) ) } return Effect.succeed(HttpBody.text(t, encoding.contentType)) } case "FormUrlEncoded": { if (!Predicate.isObject(t)) { - return Effect.fail(new SchemaIssue.InvalidValue(Option.some(t), { message: "Expected a record" })) + return Effect.fail( + new SchemaIssue.InvalidValue({ message: "Expected a record" }, t, options) + ) } - return Effect.succeed(HttpBody.urlParams(UrlParams.fromInput(t as any))) + return Effect.succeed(HttpBody.urlParams(UrlParams.fromInput(t as any), encoding.contentType)) } case "Uint8Array": { if (!(t instanceof Uint8Array)) { return Effect.fail( - new SchemaIssue.InvalidValue(Option.some(t), { message: "Expected a Uint8Array" }) + new SchemaIssue.InvalidValue( + { message: "Expected a Uint8Array" }, + t, + options + ) ) } return Effect.succeed(HttpBody.uint8Array(t, encoding.contentType)) diff --git a/repos/effect/packages/effect/src/unstable/httpapi/HttpApiEndpoint.ts b/repos/effect/packages/effect/src/unstable/httpapi/HttpApiEndpoint.ts index 7b148ee037..331b4ff84e 100644 --- a/repos/effect/packages/effect/src/unstable/httpapi/HttpApiEndpoint.ts +++ b/repos/effect/packages/effect/src/unstable/httpapi/HttpApiEndpoint.ts @@ -1,7 +1,7 @@ /** * Defines endpoint declarations used inside an HTTP API group. * - * An endpoint records a stable name, HTTP method, router path, request schemas, + * An endpoint records a stable identifier, HTTP method, router path, request schemas, * response schemas, declared errors, middleware, and annotations. Endpoint * values are declarations, not handlers: builders use them to decode requests, * type handler input, encode responses, generate OpenAPI metadata, and derive @@ -21,6 +21,7 @@ import * as Predicate from "../../Predicate.ts" import * as Schema from "../../Schema.ts" import * as AST from "../../SchemaAST.ts" import type * as Stream from "../../Stream.ts" +import type { Simplify } from "../../Struct.ts" import type * as Types from "../../Types.ts" import type { HttpMethod } from "../http/HttpMethod.ts" import * as HttpRouter from "../http/HttpRouter.ts" @@ -30,6 +31,7 @@ import type * as Multipart from "../http/Multipart.ts" import type * as HttpApiGroup from "./HttpApiGroup.ts" import type * as HttpApiMiddleware from "./HttpApiMiddleware.ts" import * as HttpApiSchema from "./HttpApiSchema.ts" +import * as MediaType from "./internal/mediaType.ts" const TypeId = "~effect/httpapi/HttpApiEndpoint" @@ -40,25 +42,17 @@ const TypeId = "~effect/httpapi/HttpApiEndpoint" * @category guards * @since 4.0.0 */ -export const isHttpApiEndpoint = (u: unknown): u is HttpApiEndpoint => Predicate.hasProperty(u, TypeId) - -/** - * Maps content types to the payload encoding strategy and one or more schemas that - * can decode or encode payloads for that content type. - * - * @category models - * @since 4.0.0 - */ -export type PayloadMap = ReadonlyMap] -}> - -type SuccessType = S extends HttpApiSchema.StreamSse< - infer _Events, - infer _Error, - infer _Value -> ? Stream.Stream<_Value, _Error["Type"], never> +export const isHttpApiEndpoint = (u: unknown): u is Top => Predicate.hasProperty(u, TypeId) + +type SuccessType = S extends HttpApiSchema.WithHeaders< + infer _Inner, + infer _Headers +> ? HttpApiSchema.withHeaders, _Headers["Type"]> + : S extends HttpApiSchema.StreamSse< + infer _Events, + infer _Error, + infer _Value + > ? Stream.Stream<_Value, _Error["Type"], never> : S extends HttpApiSchema.StreamUint8Array ? Stream.Stream : S extends Schema.Constraint ? S["Type"] : never @@ -81,20 +75,73 @@ type SuccessDecodingServices = S extends HttpApiSchema.StreamSse< : S extends Schema.Constraint ? S["DecodingServices"] : never -type ExtractSuccessOrArray = S extends ReadonlyArray ? S[number] : S +type UnwrapReadonlyArray = S extends ReadonlyArray ? A : S type ExtractBufferedSuccess = Exclude< - Extract, Schema.Top>, - HttpApiSchema.StreamSchema + Extract, Schema.Top>, + HttpApiSchema.StreamSchema | HttpApiSchema.WithHeaders > -type ExtractStreamSuccess = ExtractSuccessOrArray extends infer Success ? +type ExtractStreamSuccess = UnwrapReadonlyArray extends infer Success ? Success extends HttpApiSchema.StreamSchema ? Success : never : never -type JsonSuccessOrArray = [ExtractBufferedSuccess] extends [never] ? - ExtractStreamSuccess - : Json> | ExtractStreamSuccess +type ExtractWithHeadersSuccess = UnwrapReadonlyArray extends infer Success ? + Success extends HttpApiSchema.WithHeaders ? HttpApiSchema.WithHeaders< + _Inner extends HttpApiSchema.StreamSchema ? _Inner : Schema.toCodecJson<_Inner>, + Schema.toCodecStringTree<_Headers> + > : + never + : never + +type ToSuccessCodec = [ExtractBufferedSuccess] extends [never] ? + ExtractStreamSuccess | ExtractWithHeadersSuccess + : Schema.toCodecJson> | ExtractStreamSuccess | ExtractWithHeadersSuccess + +type ToJsonCodec = [S] extends [never] ? never + : [S] extends [Schema.Constraint] ? Schema.toCodecJson + : never + +type ToStringTreeCodec = [S] extends [never] ? never + : [S] extends [Schema.Struct.Fields] ? Schema.toCodecStringTree> + : [S] extends [Schema.Constraint] ? Schema.toCodecStringTree + : never + +type RequestFromParts = + & ([ParamsType] extends [never] ? {} : { readonly params: Simplify }) + & ([QueryType] extends [never] ? {} : { readonly query: Simplify }) + & ([PayloadType] extends [never] ? {} + : PayloadType extends Brand ? + { readonly payload: Stream.Stream } + : { readonly payload: Simplify }) + & ([HeadersType] extends [never] ? {} : { readonly headers: Simplify }) + & { + readonly request: HttpServerRequest + readonly endpoint: Endpoint + readonly group: HttpApiGroup.Top + } + +type RequestRawFromParts = + & ([ParamsType] extends [never] ? {} : { readonly params: Simplify }) + & ([QueryType] extends [never] ? {} : { readonly query: Simplify }) + & ([HeadersType] extends [never] ? {} : { readonly headers: Simplify }) + & { + readonly request: HttpServerRequest + readonly endpoint: Endpoint + readonly group: HttpApiGroup.Top + } + +/** + * Maps normalized media types to a payload encoding strategy and one or more + * schemas. Each schema retains its declared content type in its encoding annotation. + * + * @category models + * @since 4.0.0 + */ +export type PayloadMap = ReadonlyMap] +}> /** * Represents an API endpoint. An API endpoint is mapped to a single route on @@ -104,7 +151,7 @@ type JsonSuccessOrArray = [ExtractBufferedSuccess extends Pipeable { - readonly [TypeId]: { - readonly _MiddlewareR: Types.Covariant - } + new(_: never): {} + readonly [TypeId]: typeof TypeId readonly "~Params": Params readonly "~Query": Query readonly "~Headers": Headers readonly "~Payload": Payload readonly "~Success": Success readonly "~Error": Error + readonly "~Middleware": Middleware + readonly "~MiddlewareServices": MiddlewareServices + readonly "~Request": RequestFromParts + readonly "~RequestRaw": RequestRawFromParts - readonly name: Name + readonly identifier: Identifier readonly path: Path readonly method: Method readonly params: Schema.Top | undefined @@ -144,7 +194,7 @@ export interface HttpApiEndpoint< prefix( prefix: Prefix ): HttpApiEndpoint< - Name, + Identifier, Method, `${Prefix}${Path}`, Params, @@ -154,14 +204,14 @@ export interface HttpApiEndpoint< Success, Error, Middleware, - MiddlewareR + MiddlewareServices > /** * Add an `HttpApiMiddleware` to the endpoint. */ middleware(middleware: Context.Key): HttpApiEndpoint< - Name, + Identifier, Method, Path, Params, @@ -171,7 +221,7 @@ export interface HttpApiEndpoint< Success, Error, Middleware | I, - HttpApiMiddleware.ApplyServices + HttpApiMiddleware.ApplyServices > /** @@ -181,7 +231,7 @@ export interface HttpApiEndpoint< key: Context.Key, value: Types.NoInfer ): HttpApiEndpoint< - Name, + Identifier, Method, Path, Params, @@ -191,7 +241,7 @@ export interface HttpApiEndpoint< Success, Error, Middleware, - MiddlewareR + MiddlewareServices > /** @@ -200,7 +250,7 @@ export interface HttpApiEndpoint< annotateMerge( annotations: Context.Context ): HttpApiEndpoint< - Name, + Identifier, Method, Path, Params, @@ -210,12 +260,12 @@ export interface HttpApiEndpoint< Success, Error, Middleware, - MiddlewareR + MiddlewareServices > } /** @internal */ -export function getPayloadSchemas(endpoint: AnyWithProps): Array { +export function getPayloadSchemas(endpoint: Top): Array { const result: Array = [] for (const { schemas } of endpoint.payload.values()) { result.push(...schemas) @@ -224,13 +274,13 @@ export function getPayloadSchemas(endpoint: AnyWithProps): Array { } /** @internal */ -export function getSuccessSchemas(endpoint: AnyWithProps): [Schema.Top, ...Array] { +export function getSuccessSchemas(endpoint: Top): [Schema.Top, ...Array] { const schemas = Array.from(endpoint.success) return Arr.isArrayNonEmpty(schemas) ? schemas : [HttpApiSchema.NoContent] } /** @internal */ -export function getErrorSchemas(endpoint: AnyWithProps): Array { +export function getErrorSchemas(endpoint: Top): Array { const schemas = new Set(endpoint.error) for (const middleware of endpoint.middlewares) { const key = middleware as any as HttpApiMiddleware.AnyService @@ -248,11 +298,27 @@ export function getErrorSchemas(endpoint: AnyWithProps): Array { * @category models * @since 4.0.0 */ -export interface Any extends Pipeable { - readonly [TypeId]: any - readonly name: string - readonly ["~Success"]: Schema.Top - readonly ["~Error"]: Schema.Top +export interface Constraint { + readonly [TypeId]: typeof TypeId + readonly identifier: string + readonly ["~Success"]: Schema.Constraint + readonly ["~Error"]: Schema.Constraint + readonly ["~Request"]: unknown + readonly ["~RequestRaw"]: unknown +} + +/** + * A widened endpoint type that preserves request and middleware pipeline phantom fields. + * + * @category models + * @since 4.0.0 + */ +export interface ConstraintRequest extends Constraint { + readonly ["~Params"]: Schema.Constraint + readonly ["~Query"]: Schema.Constraint + readonly ["~Payload"]: Schema.Constraint + readonly ["~Headers"]: Schema.Constraint + readonly ["~Middleware"]: unknown } /** @@ -262,7 +328,7 @@ export interface Any extends Pipeable { * @category models * @since 4.0.0 */ -export interface AnyWithProps extends +export interface Top extends HttpApiEndpoint< string, HttpMethod, @@ -271,246 +337,128 @@ export interface AnyWithProps extends Schema.Top, Schema.Top, Schema.Top, + Schema.Top, + Schema.Top, any, - any + unknown > {} /** - * Extracts the name literal from an `HttpApiEndpoint`. + * Extracts the endpoint identifier literal from an `HttpApiEndpoint`. * - * @category models + * @category utility types * @since 4.0.0 */ -export type Name = Endpoint extends HttpApiEndpoint< - infer _Name, - infer _Method, - infer _Path, - infer _Params, - infer _Query, - infer _Payload, - infer _Headers, - infer _Success, - infer _Error, - infer _M, - infer _MR -> ? _Name - : never +export type Identifier = Endpoint extends Constraint ? Endpoint["identifier"] : never /** * Extracts the success schema associated with an endpoint. * - * @category models + * @category utility types * @since 4.0.0 */ -export type Success = Endpoint extends HttpApiEndpoint< - infer _Name, - infer _Method, - infer _Path, - infer _Params, - infer _Query, - infer _Payload, - infer _Headers, - infer _Success, - infer _Error, - infer _M, - infer _MR -> ? _Success - : never +export type Success = Endpoint extends Constraint ? Endpoint["~Success"] : never /** * Extracts the error schema associated with an endpoint. * - * @category models + * @category utility types * @since 4.0.0 */ -export type Error = Endpoint extends HttpApiEndpoint< - infer _Name, - infer _Method, - infer _Path, - infer _Params, - infer _Query, - infer _Payload, - infer _Headers, - infer _Success, - infer _Error, - infer _M, - infer _MR -> ? _Error - : never +export type Error = Endpoint extends Constraint ? Endpoint["~Error"] : never /** * Extracts the schema used for an endpoint's path parameters. * - * @category models + * @category utility types * @since 4.0.0 */ -export type Params = Endpoint extends HttpApiEndpoint< - infer _Name, - infer _Method, - infer _Path, - infer _Params, - infer _Query, - infer _Payload, - infer _Headers, - infer _Success, - infer _Error, - infer _M, - infer _MR -> ? _Params +export type Params = Endpoint extends ConstraintRequest ? Endpoint["~Params"] : never /** * Extracts the schema used for an endpoint's query parameters. * - * @category models + * @category utility types * @since 4.0.0 */ -export type Query = Endpoint extends HttpApiEndpoint< - infer _Name, - infer _Method, - infer _Path, - infer _Params, - infer _Query, - infer _Payload, - infer _Headers, - infer _Success, - infer _Error, - infer _M, - infer _MR -> ? _Query +export type Query = Endpoint extends ConstraintRequest ? Endpoint["~Query"] : never /** * Extracts the schema used for an endpoint's request payload. * - * @category models + * @category utility types * @since 4.0.0 */ -export type Payload = Endpoint extends HttpApiEndpoint< - infer _Name, - infer _Method, - infer _Path, - infer _Params, - infer _Query, - infer _Payload, - infer _Headers, - infer _Success, - infer _Error, - infer _M, - infer _MR -> ? _Payload +export type Payload = Endpoint extends ConstraintRequest ? Endpoint["~Payload"] : never /** * Extracts the schema used for an endpoint's request headers. * - * @category models + * @category utility types * @since 4.0.0 */ -export type Headers = Endpoint extends HttpApiEndpoint< - infer _Name, - infer _Method, - infer _Path, - infer _Params, - infer _Query, - infer _Payload, - infer _Headers, - infer _Success, - infer _Error, - infer _M, - infer _MR -> ? _Headers +export type Headers = Endpoint extends ConstraintRequest ? Endpoint["~Headers"] : never /** * Extracts the middleware identifiers attached to an endpoint. * - * @category models + * @category utility types * @since 4.0.0 */ -export type Middleware = Endpoint extends HttpApiEndpoint< - infer _Name, - infer _Method, - infer _Path, - infer _Params, - infer _Query, - infer _Payload, - infer _Headers, - infer _Success, - infer _Error, - infer _M, - infer _MR -> ? _M +export type Middleware = Endpoint extends { readonly "~Middleware": infer M } ? M : never /** * Computes the services provided by the middleware attached to an endpoint. * - * @category models + * @category utility types * @since 4.0.0 */ -export type MiddlewareProvides = HttpApiMiddleware.Provides> +export type MiddlewareProvides = HttpApiMiddleware.Provides> /** * Computes the client-side middleware services required by an endpoint. * - * @category models + * @category utility types * @since 4.0.0 */ -export type MiddlewareClient = HttpApiMiddleware.MiddlewareClient> +export type MiddlewareClient = HttpApiMiddleware.MiddlewareClient> /** * Computes the error types that can be produced by the middleware attached to an * endpoint. * - * @category models + * @category utility types * @since 4.0.0 */ -export type MiddlewareError = HttpApiMiddleware.Error> +export type MiddlewareError = HttpApiMiddleware.Error> /** * Computes the full error value union for an endpoint, including the endpoint * error schema's type and errors introduced by middleware. * - * @category models + * @category utility types * @since 4.0.0 */ -export type Errors = Endpoint extends HttpApiEndpoint< - infer _Name, - infer _Method, - infer _Path, - infer _Params, - infer _Query, - infer _Payload, - infer _Headers, - infer _Success, - infer _Error, - infer _M, - infer _MR -> ? _Error["Type"] | HttpApiMiddleware.Error> +export type Errors = Endpoint extends ConstraintRequest ? + Endpoint["~Error"]["Type"] | HttpApiMiddleware.Error : never /** * Computes the services required to encode an endpoint's error responses, * including services required by middleware error encoders. * - * @category models + * @category utility types * @since 4.0.0 */ -export type ErrorServicesEncode = Endpoint extends HttpApiEndpoint< - infer _Name, - infer _Method, - infer _Path, - infer _Params, - infer _Query, - infer _Payload, - infer _Headers, - infer _Success, - infer _Error, - infer _M, - infer _MR -> ? _Error["EncodingServices"] | HttpApiMiddleware.ErrorServicesEncode> +export type ErrorServicesEncode = Endpoint extends ConstraintRequest ? + | Endpoint["~Error"]["EncodingServices"] + | HttpApiMiddleware.ErrorServicesEncode : never /** @@ -518,34 +466,10 @@ export type ErrorServicesEncode = Endpoint extends HttpApi * available params, query, payload, headers, the raw request, endpoint, and group. * Multipart stream payloads are exposed as streams of parts. * - * @category models + * @category utility types * @since 4.0.0 */ -export type Request = Endpoint extends HttpApiEndpoint< - infer _Name, - infer _Method, - infer _Path, - infer _Params, - infer _Query, - infer _Payload, - infer _Headers, - infer _Success, - infer _Error, - infer _M, - infer _MR -> ? - & ([_Params["Type"]] extends [never] ? {} : { readonly params: _Params["Type"] }) - & ([_Query["Type"]] extends [never] ? {} : { readonly query: _Query["Type"] }) - & ([_Payload["Type"]] extends [never] ? {} - : _Payload["Type"] extends Brand ? - { readonly payload: Stream.Stream } - : { readonly payload: _Payload["Type"] }) - & ([_Headers] extends [never] ? {} : { readonly headers: _Headers["Type"] }) - & { - readonly request: HttpServerRequest - readonly endpoint: Endpoint - readonly group: HttpApiGroup.AnyWithProps - } +export type Request = Endpoint extends ConstraintRequest ? Endpoint["~Request"] : {} /** @@ -553,30 +477,10 @@ export type Request = Endpoint extends HttpApiEndpoint< * params, query, and headers plus the raw request, endpoint, and group, while * leaving payload handling to the raw request. * - * @category models + * @category utility types * @since 4.0.0 */ -export type RequestRaw = Endpoint extends HttpApiEndpoint< - infer _Name, - infer _Method, - infer _Path, - infer _Params, - infer _Query, - infer _Payload, - infer _Headers, - infer _Success, - infer _Error, - infer _M, - infer _MR -> ? - & ([_Params["Type"]] extends [never] ? {} : { readonly params: _Params["Type"] }) - & ([_Query["Type"]] extends [never] ? {} : { readonly query: _Query["Type"] }) - & ([_Headers["Type"]] extends [never] ? {} : { readonly headers: _Headers["Type"] }) - & { - readonly request: HttpServerRequest - readonly endpoint: Endpoint - readonly group: HttpApiGroup.AnyWithProps - } +export type RequestRaw = Endpoint extends ConstraintRequest ? Endpoint["~RequestRaw"] : {} /** @@ -584,7 +488,7 @@ export type RequestRaw = Endpoint extends HttpApiEndpoint< * the params, query, headers, payload, and response mode fields required by the * endpoint. Multipart payloads are supplied as `FormData`. * - * @category models + * @category utility types * @since 4.0.0 */ export type ClientRequest< @@ -620,226 +524,189 @@ export type ClientResponseMode = "decoded-only" | "decoded-and-response" | "resp * Computes the services required on the server to decode endpoint inputs and * encode endpoint success, error, and middleware error responses. * - * @category models + * @category utility types * @since 4.0.0 */ -export type ServerServices = Endpoint extends HttpApiEndpoint< - infer _Name, - infer _Method, - infer _Path, - infer _Params, - infer _Query, - infer _Payload, - infer _Headers, - infer _Success, - infer _Error, - infer _M, - infer _MR -> ? - | _Params["DecodingServices"] - | _Query["DecodingServices"] - | _Payload["DecodingServices"] - | _Headers["DecodingServices"] - | SuccessEncodingServices<_Success> - | _Error["EncodingServices"] - | HttpApiMiddleware.ErrorServicesEncode<_M> +export type ServerServices = Endpoint extends ConstraintRequest ? + | Endpoint["~Params"]["DecodingServices"] + | Endpoint["~Query"]["DecodingServices"] + | Endpoint["~Payload"]["DecodingServices"] + | Endpoint["~Headers"]["DecodingServices"] + | SuccessEncodingServices + | Endpoint["~Error"]["EncodingServices"] + | HttpApiMiddleware.ErrorServicesEncode : never /** * Computes the services required on the client to encode endpoint requests and * decode endpoint success or error responses. * - * @category models + * @category utility types * @since 4.0.0 */ -export type ClientServices = Endpoint extends HttpApiEndpoint< - infer _Name, - infer _Method, - infer _Path, - infer _Params, - infer _Query, - infer _Payload, - infer _Headers, - infer _Success, - infer _Error, - infer _M, - infer _MR -> ? - | _Params["EncodingServices"] - | _Query["EncodingServices"] - | _Payload["EncodingServices"] - | _Headers["EncodingServices"] - | SuccessDecodingServices<_Success> - | _Error["DecodingServices"] +export type ClientServices = Endpoint extends ConstraintRequest ? + | Endpoint["~Params"]["EncodingServices"] + | Endpoint["~Query"]["EncodingServices"] + | Endpoint["~Payload"]["EncodingServices"] + | Endpoint["~Headers"]["EncodingServices"] + | SuccessDecodingServices + | Endpoint["~Error"]["DecodingServices"] : never /** * Extracts the additional services required by middleware applied to an endpoint. * - * @category models + * @category utility types * @since 4.0.0 */ -export type MiddlewareServices = Endpoint extends HttpApiEndpoint< - infer _Name, - infer _Method, - infer _Path, - infer _Params, - infer _Query, - infer _Payload, - infer _Headers, - infer _Success, - infer _Error, - infer _M, - infer _MR -> ? _MR +export type MiddlewareServices = Endpoint extends { readonly "~MiddlewareServices": infer R } ? R : never /** * Computes the services required to decode an endpoint's error responses, * including services required by middleware error decoders. * - * @category models + * @category utility types * @since 4.0.0 */ -export type ErrorServicesDecode = Endpoint extends HttpApiEndpoint< - infer _Name, - infer _Method, - infer _Path, - infer _Params, - infer _Query, - infer _Payload, - infer _Headers, - infer _Success, - infer _Error, - infer _M, - infer _MR -> ? _Error["DecodingServices"] | HttpApiMiddleware.ErrorServicesDecode> +export type ErrorServicesDecode = Endpoint extends ConstraintRequest ? + | Endpoint["~Error"]["DecodingServices"] + | HttpApiMiddleware.ErrorServicesDecode : never /** * The normal server handler for an endpoint, accepting the decoded request shape * and returning either the endpoint success value or a custom `HttpServerResponse`. * - * @category models + * @category utility types * @since 4.0.0 */ -export type Handler = ( - request: Types.Simplify> +export type Handler = ( + request: Simplify ) => Effect | HttpServerResponse, Endpoint["~Error"]["Type"] | E, R> /** * The raw server handler for an endpoint, receiving a request shape without a * decoded payload so the handler can read the raw `HttpServerRequest` directly. * - * @category models + * @category utility types * @since 4.0.0 */ -export type HandlerRaw = ( - request: Types.Simplify> +export type HandlerRaw = ( + request: Simplify ) => Effect | HttpServerResponse, Endpoint["~Error"]["Type"] | E, R> /** - * Selects the endpoint with the specified name from a union of endpoints. + * Selects the endpoint with the specified identifier from a union of endpoints. * - * @category models + * @category utility types * @since 4.0.0 */ -export type WithName = Extract +export type WithIdentifier = Extract< + Endpoints, + { readonly identifier: Identifier } +> /** - * Removes endpoints with the specified name from a union of endpoints. + * Removes endpoints with the specified identifier from a union of endpoints. * - * @category models + * @category utility types * @since 4.0.0 */ -export type ExcludeName = Exclude +export type ExcludeIdentifier = Exclude< + Endpoints, + { readonly identifier: Identifier } +> /** - * Derives the normal handler type for the endpoint with the specified name in an - * endpoint union. + * Derives the normal handler type for the endpoint with the specified identifier + * in an endpoint union. * - * @category models + * @category utility types * @since 4.0.0 */ -export type HandlerWithName = Handler< - WithName, +export type HandlerWithIdentifier = Handler< + WithIdentifier, E, R > /** - * Derives the raw handler type for the endpoint with the specified name in an - * endpoint union. + * Derives the raw handler type for the endpoint with the specified identifier in + * an endpoint union. * - * @category models + * @category utility types * @since 4.0.0 */ -export type HandlerRawWithName = HandlerRaw< - WithName, +export type HandlerRawWithIdentifier = HandlerRaw< + WithIdentifier, E, R > /** - * Extracts the decoded success value type for the endpoint with the specified name - * in an endpoint union. + * Extracts the decoded success value type for the endpoint with the specified + * identifier in an endpoint union. * - * @category models + * @category utility types * @since 4.0.0 */ -export type SuccessWithName = Success< - WithName +export type SuccessWithIdentifier = Success< + WithIdentifier > extends infer S ? SuccessType : never /** - * Computes the full error value union for the endpoint with the specified name in - * an endpoint union. + * Computes the full error value union for the endpoint with the specified + * identifier in an endpoint union. * - * @category models + * @category utility types * @since 4.0.0 */ -export type ErrorsWithName = Errors> +export type ErrorsWithIdentifier = Errors< + WithIdentifier +> /** * Computes the server-side service requirements for the endpoint with the - * specified name in an endpoint union. + * specified identifier in an endpoint union. * - * @category models + * @category utility types * @since 4.0.0 */ -export type ServerServicesWithName = ServerServices< - WithName +export type ServerServicesWithIdentifier = ServerServices< + WithIdentifier > /** - * Extracts the middleware identifiers for the endpoint with the specified name in - * an endpoint union. + * Extracts the middleware identifiers for the endpoint with the specified + * identifier in an endpoint union. * - * @category models + * @category utility types * @since 4.0.0 */ -export type MiddlewareWithName = Middleware> +export type MiddlewareWithIdentifier = Middleware< + WithIdentifier +> /** - * Extracts the middleware service requirements for the endpoint with the specified - * name in an endpoint union. + * Extracts the middleware service requirements for the endpoint with the + * specified identifier in an endpoint union. * - * @category models + * @category utility types * @since 4.0.0 */ -export type MiddlewareServicesWithName = MiddlewareServices< - WithName -> +export type MiddlewareServicesWithIdentifier = + MiddlewareServices> /** - * Removes services provided by the HTTP router and the named endpoint's middleware - * from a service requirement union. + * Removes services provided by the HTTP router and the selected endpoint's + * middleware from a service requirement union. * - * @category models + * @category utility types * @since 4.0.0 */ -export type ExcludeProvidedWithName = ExcludeProvided< - WithName, +export type ExcludeProvidedWithIdentifier = ExcludeProvided< + WithIdentifier, R > @@ -847,10 +714,10 @@ export type ExcludeProvidedWithName = Exclude< +export type ExcludeProvided = Exclude< R, | HttpRouter.Provided | HttpApiMiddleware.Provides> @@ -860,11 +727,11 @@ export type ExcludeProvided = Exclude< * Returns an endpoint type with the supplied path prefix prepended while * preserving the endpoint's schemas, method, errors, and middleware. * - * @category models + * @category utility types * @since 4.0.0 */ -export type AddPrefix = Endpoint extends HttpApiEndpoint< - infer _Name, +export type AddPrefix = Endpoint extends HttpApiEndpoint< + infer _Identifier, infer _Method, infer _Path, infer _Params, @@ -876,7 +743,7 @@ export type AddPrefix infer _M, infer _MR > ? HttpApiEndpoint< - _Name, + _Identifier, _Method, `${Prefix}${_Path}`, _Params, @@ -890,49 +757,15 @@ export type AddPrefix > : never -/** - * Returns an endpoint type with an additional error schema added to the endpoint's - * existing error schema union. - * - * @category models - * @since 4.0.0 - */ -export type AddError = Endpoint extends HttpApiEndpoint< - infer _Name, - infer _Method, - infer _Path, - infer _Params, - infer _Query, - infer _Payload, - infer _Headers, - infer _Success, - infer _Error, - infer _M, - infer _MR -> ? HttpApiEndpoint< - _Name, - _Method, - _Path, - _Params, - _Query, - _Payload, - _Headers, - _Success, - _Error | E, - _M, - _MR - > : - never - /** * Returns an endpoint type with additional middleware applied and the endpoint's * middleware service requirements updated accordingly. * - * @category models + * @category utility types * @since 4.0.0 */ -export type AddMiddleware = Endpoint extends HttpApiEndpoint< - infer _Name, +export type AddMiddleware = Endpoint extends HttpApiEndpoint< + infer _Identifier, infer _Method, infer _Path, infer _Params, @@ -944,7 +777,7 @@ export type AddMiddleware ? HttpApiEndpoint< - _Name, + _Identifier, _Method, _Path, _Params, @@ -963,34 +796,48 @@ const Proto = { pipe() { return pipeArguments(this, arguments) }, - prefix(this: AnyWithProps, prefix: HttpRouter.PathInput) { + prefix(this: Top, prefix: HttpRouter.PathInput) { return makeProto({ - ...this, + ...optionsFromEndpoint(this), path: HttpRouter.prefixPath(this.path, prefix) }) }, - middleware(this: AnyWithProps, middleware: HttpApiMiddleware.AnyService) { + middleware(this: Top, middleware: HttpApiMiddleware.AnyService) { return makeProto({ - ...this, + ...optionsFromEndpoint(this), middlewares: new Set([...this.middlewares, middleware as any]) }) }, - annotate(this: AnyWithProps, key: Context.Key, value: any) { + annotate(this: Top, key: Context.Key, value: any) { return makeProto({ - ...this, + ...optionsFromEndpoint(this), annotations: Context.add(this.annotations, key, value) }) }, - annotateMerge(this: AnyWithProps, annotations: Context.Context) { + annotateMerge(this: Top, annotations: Context.Context) { return makeProto({ - ...this, + ...optionsFromEndpoint(this), annotations: Context.merge(this.annotations, annotations) }) } } +const optionsFromEndpoint = (endpoint: Top) => ({ + identifier: endpoint.identifier, + path: endpoint.path, + method: endpoint.method, + params: endpoint.params, + query: endpoint.query, + headers: endpoint.headers, + payload: endpoint.payload, + success: endpoint.success, + error: endpoint.error, + annotations: endpoint.annotations, + middlewares: endpoint.middlewares +}) + function makeProto< - Name extends string, + Identifier extends string, Method extends HttpMethod, const Path extends string, Params extends Schema.Top, @@ -1002,7 +849,7 @@ function makeProto< Middleware, MiddlewareR >(options: { - readonly name: Name + readonly identifier: Identifier readonly path: Path readonly method: Method readonly params: Schema.Top | undefined @@ -1014,7 +861,7 @@ function makeProto< readonly annotations: Context.Context readonly middlewares: ReadonlySet> }): HttpApiEndpoint< - Name, + Identifier, Method, Path, Params, @@ -1026,41 +873,43 @@ function makeProto< Middleware, MiddlewareR > { - return Object.assign(Object.create(Proto), options) + function HttpApiEndpoint() {} + Object.setPrototypeOf(HttpApiEndpoint, Proto) + return Object.assign(HttpApiEndpoint, options) as any } /** * Constraint for path parameter schemas: each parameter must encode to * `string | undefined`, or the schema must encode to a record of those values. * - * @category constraints + * @category utility types * @since 4.0.0 */ export type ParamsConstraint = - | Record> - | Schema.Codec, unknown, unknown> + | Record> + | Schema.Encoder, unknown> /** * Constraint for header schemas: each header must encode to `string | undefined`, * or the schema must encode to a record of those values. * - * @category constraints + * @category utility types * @since 4.0.0 */ export type HeadersConstraint = - | Record> - | Schema.Codec, unknown, unknown> + | Record> + | Schema.Encoder, unknown> /** * Constraint for query schemas: each field must encode to `string`, an array of * strings, or `undefined`, or the schema must encode to a record of those values. * - * @category constraints + * @category utility types * @since 4.0.0 */ export type QueryConstraint = - | Record | undefined, unknown, unknown>> - | Schema.Codec | undefined, unknown, unknown> + | Record | undefined, unknown>> + | Schema.Encoder | undefined, unknown> /** * Payload schema depends on the HTTP method: @@ -1070,12 +919,12 @@ export type QueryConstraint = * - for body methods, payload may be any `Schema.Top` (or content-type keyed * schemas) and OpenAPI uses `requestBody` instead of `parameters` * - * @category constraints + * @category utility types * @since 4.0.0 */ export type PayloadConstraint = Method extends HttpMethod.NoBody ? Record< string, - Schema.Codec | undefined, unknown, unknown> + Schema.Encoder | undefined, unknown> > : Schema.Top | ReadonlyArray @@ -1084,7 +933,7 @@ export type PayloadConstraint = Method extends HttpMe * accept field records for query-style encoding, while body methods accept one or * more schemas. * - * @category constraints + * @category utility types * @since 4.0.0 */ export type PayloadConstraintCodecs = Method extends HttpMethod.NoBody ? @@ -1095,7 +944,7 @@ export type PayloadConstraintCodecs = Method extends * Constraint for success response schemas, allowing either a single schema or a * readonly array of schemas. * - * @category constraints + * @category utility types * @since 4.0.0 */ export type SuccessConstraint = Schema.Top | ReadonlyArray @@ -1104,21 +953,23 @@ export type SuccessConstraint = Schema.Top | ReadonlyArray * Constraint for error response schemas, allowing either a single schema or a * readonly array of schemas. * - * @category constraints + * @category utility types * @since 4.0.0 */ export type ErrorConstraint = Schema.Top | ReadonlyArray +type ErrorSchema = S extends HttpApiSchema.WithHeaders ? Inner : S + type ErrorNoStream = [ Extract< - S extends ReadonlyArray ? S[number] : S, + ErrorSchema ? S[number] : S>, HttpApiSchema.StreamSchema > ] extends [never] ? S : never /** * Creates endpoint constructors for a specific HTTP method. The resulting - * constructor builds an `HttpApiEndpoint` from a name, path, and optional request + * constructor builds an `HttpApiEndpoint` from an identifier, path, and optional request * and response schemas, applying automatic JSON or string-tree codecs unless * `disableCodecs` is enabled. * @@ -1127,7 +978,7 @@ type ErrorNoStream = [ */ export const make = (method: Method): { < - const Name extends string, + const Identifier extends string, const Path extends HttpRouter.PathInput, Params extends Schema.Top | Schema.Struct.Fields = never, Query extends Schema.Top | Schema.Struct.Fields = never, @@ -1136,7 +987,7 @@ export const make = (method: Method): { const Success extends SuccessConstraint = HttpApiSchema.NoContent, const Error extends Schema.Top | ReadonlyArray = never >( - name: Name, + identifier: Identifier, path: Path, options?: { readonly disableCodecs?: false | undefined @@ -1148,19 +999,19 @@ export const make = (method: Method): { readonly error?: (Error & ErrorNoStream>) | undefined } ): HttpApiEndpoint< - Name, + Identifier, Method, Path, - StringTree : Params>, - StringTree : Query>, - Method extends HttpMethod.WithBody ? Json> - : StringTree>, - StringTree : Headers>, - JsonSuccessOrArray, - Json ? Error[number] : Error> + ToStringTreeCodec, + ToStringTreeCodec, + Method extends HttpMethod.WithBody ? ToJsonCodec> + : ToStringTreeCodec>, + ToStringTreeCodec, + ToSuccessCodec, + ToJsonCodec ? Error[number] : Error> > < - const Name extends string, + const Identifier extends string, const Path extends HttpRouter.PathInput, Params extends ParamsConstraint = never, Query extends QueryConstraint = never, @@ -1169,7 +1020,7 @@ export const make = (method: Method): { const Success extends SuccessConstraint = HttpApiSchema.NoContent, const Error extends ErrorConstraint = never >( - name: Name, + identifier: Identifier, path: Path, options?: { readonly disableCodecs: true @@ -1181,19 +1032,19 @@ export const make = (method: Method): { readonly error?: (Error & ErrorNoStream>) | undefined } ): HttpApiEndpoint< - Name, + Identifier, Method, Path, Params extends Schema.Struct.Fields ? Schema.Struct : Params, Query extends Schema.Struct.Fields ? Schema.Struct : Query, - ExtractSchemaOrArray, - ExtractSchemaOrArray, - ExtractSuccessOrArray, + ToSchema, + ToSchema, + UnwrapReadonlyArray, Error extends ReadonlyArray ? Error[number] : Error > } => < - const Name extends string, + const Identifier extends string, const Path extends HttpRouter.PathInput, Params extends ParamsConstraint = never, Query extends QueryConstraint = never, @@ -1202,7 +1053,7 @@ export const make = (method: Method): { const Success extends SuccessConstraint = HttpApiSchema.NoContent, const Error extends ErrorConstraint = never >( - name: Name, + identifier: Identifier, path: Path, options?: { readonly disableCodecs?: boolean | undefined @@ -1214,7 +1065,7 @@ export const make = (method: Method): { readonly error?: (Error & ErrorNoStream>) | undefined } ): HttpApiEndpoint< - Name, + Identifier, Method, Path, Params extends Schema.Struct.Fields ? Schema.Struct : Params, @@ -1223,13 +1074,13 @@ export const make = (method: Method): { : Payload extends ReadonlyArray ? Payload[number] : Payload, Headers extends Schema.Struct.Fields ? Schema.Struct : Headers, - ExtractSuccessOrArray, + UnwrapReadonlyArray, Error extends ReadonlyArray ? Error[number] : Error > => { const disableCodecs = options?.disableCodecs ?? false const transformStringTree = disableCodecs ? identity : Schema.toCodecStringTree return makeProto({ - name, + identifier, path, method, params: ensureStruct(options?.params, transformStringTree), @@ -1243,37 +1094,10 @@ export const make = (method: Method): { }) } -type ExtractSchemaOrArray> = - S extends Schema.Struct.Fields ? Schema.Struct - : S extends ReadonlyArray ? S[number] - : S - -/** - * A schema codec that decodes and encodes the schema's value type through JSON - * transport values. - * - * @category Codecs - * @since 4.0.0 - */ -export interface Json - extends Schema.Codec -{} - -/** - * A schema codec that decodes and encodes the schema's value type through - * `Schema.StringTree` transport values. - * - * @category Codecs - * @since 4.0.0 - */ -export interface StringTree extends - Schema.Codec< - S["Type"], - Schema.StringTree, - S["DecodingServices"], - S["EncodingServices"] - > -{} +type ToSchema> = S extends + Schema.Struct.Fields ? Schema.Struct + : S extends ReadonlyArray ? S[number] + : S function ensureStruct( params: Schema.Struct.Fields | Schema.Top | undefined, @@ -1303,7 +1127,8 @@ function getPayload( for (const schema of schemas) { const encoding = HttpApiSchema.getPayloadEncoding(schema.ast, method) - const existing = result.get(encoding.contentType) + const contentType = MediaType.normalize(encoding.contentType) + const existing = result.get(contentType) if (existing) { if (existing.encoding._tag !== encoding._tag) { throw new Error(`Multiple payload encodings for content-type: ${encoding.contentType}`) @@ -1313,7 +1138,7 @@ function getPayload( } existing.schemas.push(transform(schema, method)) } else { - result.set(encoding.contentType, { encoding, schemas: [transform(schema, method)] }) + result.set(contentType, { encoding, schemas: [transform(schema, method)] }) } } return result @@ -1329,11 +1154,18 @@ function getSuccessResponse( if (success === undefined) return new Set() const schemas = Arr.ensure(success) validateSuccessResponse(schemas, method) - return new Set( - disableCodecs ? - schemas : - schemas.map((schema) => HttpApiSchema.isStreamSchema(schema) ? schema : transformResponse(schema)) - ) + return new Set(disableCodecs ? schemas : schemas.map(transformResponseSchema)) +} + +function transformResponseSchema(schema: Schema.Top): Schema.Top { + if (HttpApiSchema.isStreamSchema(schema)) return schema + if (HttpApiSchema.isWithHeaders(schema)) { + const inner = HttpApiSchema.isStreamSchema(schema.schema) + ? schema.schema + : applyResponseEncoding(schema.schema, HttpApiSchema.getResponseEncodingSchema(schema)) + return HttpApiSchema.rebuildWithHeaders(schema, inner, Schema.toCodecStringTree(schema.headers)) + } + return transformResponse(schema) } function getErrorResponse( @@ -1343,14 +1175,17 @@ function getErrorResponse( if (error === undefined) return new Set() const schemas = Arr.ensure(error) for (const schema of schemas) { - if (HttpApiSchema.isStreamSchema(schema)) { + const body = HttpApiSchema.isWithHeaders(schema) ? schema.schema : schema + if (HttpApiSchema.isStreamSchema(body)) { throw new Error("Streaming schemas are not supported in error responses") } } - return new Set(disableCodecs ? schemas : schemas.map(transformResponse)) + validateResponseExclusivity(schemas, HttpApiSchema.getStatusErrorSchema) + return new Set(disableCodecs ? schemas : schemas.map(transformResponseSchema)) } function validateSuccessResponse(schemas: ReadonlyArray, method: HttpMethod) { + let hasStream = false const statuses = new Map @@ -1358,33 +1193,34 @@ function validateSuccessResponse(schemas: ReadonlyArray, meth }>() for (const schema of schemas) { - if (HttpApiSchema.isStreamSchema(schema)) { - validateStreamSuccess(schema, method) - const status = HttpApiSchema.getStatusStream(schema) - const entry = getStatusEntry(statuses, status) - if (entry.stream !== undefined) { - throw new Error(`Multiple streaming success responses for status: ${status}`) + const inner = HttpApiSchema.isWithHeaders(schema) ? schema.schema : schema + const status = HttpApiSchema.getStatusSuccessSchema(schema) + if (HttpApiSchema.isStreamSchema(inner)) { + validateStreamSuccess(inner, method) + if (hasStream) { + throw new Error("Multiple streaming success responses are not supported") } + hasStream = true + const entry = getStatusEntry(statuses, status) if (entry.noContent) { throw new Error(`Cannot combine no-content and streaming success responses for status: ${status}`) } - if (entry.bufferedContentTypes.has(normalizeResponseContentType(schema.contentType))) { + if (entry.bufferedContentTypes.has(MediaType.normalize(inner.contentType))) { throw new Error( - `Cannot combine buffered and streaming success responses for status ${status} and content-type: ${schema.contentType}` + `Cannot combine buffered and streaming success responses for status ${status} and content-type: ${inner.contentType}` ) } - statuses.set(status, { ...entry, stream: schema }) + statuses.set(status, { ...entry, stream: inner }) } else { - const status = HttpApiSchema.getStatusSuccess(schema.ast) const entry = getStatusEntry(statuses, status) - const noContent = HttpApiSchema.isNoContent(schema.ast) + const noContent = HttpApiSchema.isNoContent(inner.ast) if (entry.stream !== undefined) { if (noContent) { throw new Error(`Cannot combine no-content and streaming success responses for status: ${status}`) } - const encoding = HttpApiSchema.getResponseEncoding(schema.ast) + const encoding = HttpApiSchema.getResponseEncodingSchema(schema) if ( - normalizeResponseContentType(encoding.contentType) === normalizeResponseContentType(entry.stream.contentType) + MediaType.normalize(encoding.contentType) === MediaType.normalize(entry.stream.contentType) ) { throw new Error( `Cannot combine buffered and streaming success responses for status ${status} and content-type: ${encoding.contentType}` @@ -1393,18 +1229,14 @@ function validateSuccessResponse(schemas: ReadonlyArray, meth } if (!noContent) { entry.bufferedContentTypes.add( - normalizeResponseContentType(HttpApiSchema.getResponseEncoding(schema.ast).contentType) + MediaType.normalize(HttpApiSchema.getResponseEncodingSchema(schema).contentType) ) } entry.noContent = entry.noContent || noContent } } -} -function normalizeResponseContentType(contentType: string): string { - const normalized = contentType.toLowerCase().trim() - const index = normalized.indexOf(";") - return index === -1 ? normalized : normalized.slice(0, index).trim() + validateResponseExclusivity(schemas, HttpApiSchema.getStatusSuccessSchema) } function getStatusEntry( @@ -1423,6 +1255,49 @@ function getStatusEntry( return entry } +function validateResponseExclusivity( + schemas: ReadonlyArray, + getStatus: (schema: Schema.Constraint) => number +) { + const statuses = new Map + }>() + for (const schema of schemas) { + const status = getStatus(schema) + const withHeadersAnnotation = HttpApiSchema.getWithHeadersAnnotation(schema.ast) + const body = HttpApiSchema.isWithHeaders(schema) ? schema.schema : withHeadersAnnotation?.body ?? schema + const contentType = HttpApiSchema.isNoContent(body.ast) + ? "" + : MediaType.normalize( + HttpApiSchema.isStreamSchema(body) + ? body.contentType + : HttpApiSchema.getResponseEncodingSchema(schema).contentType + ) + let entry = statuses.get(status) + if (entry === undefined) { + entry = { headerContentType: undefined, plainContentTypes: new Set() } + statuses.set(status, entry) + } + const combineError = () => + new Error( + `Cannot combine a response with headers with another response for status ${status} and content-type: ${ + contentType || "" + }` + ) + if (HttpApiSchema.isWithHeaders(schema) || withHeadersAnnotation !== undefined) { + if (entry.headerContentType !== undefined) { + throw new Error(`Cannot declare multiple responses with headers for status ${status}`) + } + if (entry.plainContentTypes.has(contentType)) throw combineError() + entry.headerContentType = contentType + } else { + if (entry.headerContentType === contentType) throw combineError() + entry.plainContentTypes.add(contentType) + } + } +} + function validateStreamSuccess(schema: HttpApiSchema.StreamSchema, method: HttpMethod) { if (method === "HEAD") { throw new Error("HEAD endpoints cannot declare streaming success responses") @@ -1471,6 +1346,23 @@ function hasReservedEventLiteral(ast: AST.AST, seen: Set): boolean { function transformResponse(schema: Schema.Top): Schema.Top { const encoding = HttpApiSchema.getResponseEncoding(schema.ast) + const withHeaders = HttpApiSchema.getWithHeadersAnnotation(schema.ast) + if (withHeaders === undefined) { + return applyResponseEncoding(schema, encoding) + } + const headers = Schema.toEncoded(withHeaders.headers) + return Schema.Struct({ + body: applyResponseEncoding(Schema.toEncoded(withHeaders.body), encoding), + headers + }).pipe(Schema.decodeTo(schema)).annotate({ + "~httpApiWithHeaders": { + ...withHeaders, + headersCodec: Schema.toCodecStringTree(headers) + } + }) +} + +function applyResponseEncoding(schema: Schema.Top, encoding: HttpApiSchema.ResponseEncoding): Schema.Top { switch (encoding._tag) { case "Json": return Schema.toCodecJson(schema) diff --git a/repos/effect/packages/effect/src/unstable/httpapi/HttpApiError.ts b/repos/effect/packages/effect/src/unstable/httpapi/HttpApiError.ts index a1f7bc6eea..20f90066db 100644 --- a/repos/effect/packages/effect/src/unstable/httpapi/HttpApiError.ts +++ b/repos/effect/packages/effect/src/unstable/httpapi/HttpApiError.ts @@ -1,7 +1,7 @@ /** * Built-in error schemas for common HTTP API failure responses. * - * This module provides reusable `Schema.ErrorClass` values for common HTTP + * This module provides reusable `Schema.Error` values for common HTTP * status codes, plus `HttpApiSchemaError` for request decoding failures raised * by the HTTP API runtime. The status errors can be used in endpoint or * middleware error declarations and are understood by builders, generated @@ -27,6 +27,7 @@ const notAcceptableResponse = HttpServerResponse.empty({ status: 406 }) const requestTimeoutResponse = HttpServerResponse.empty({ status: 408 }) const conflictResponse = HttpServerResponse.empty({ status: 409 }) const goneResponse = HttpServerResponse.empty({ status: 410 }) +const unprocessableEntityResponse = HttpServerResponse.empty({ status: 422 }) const internalServerErrorResponse = HttpServerResponse.empty({ status: 500 }) const notImplementedResponse = HttpServerResponse.empty({ status: 501 }) const serviceUnavailableResponse = HttpServerResponse.empty({ status: 503 }) @@ -38,7 +39,7 @@ const serviceUnavailableResponse = HttpServerResponse.empty({ status: 503 }) * @category errors * @since 4.0.0 */ -export class BadRequest extends Schema.ErrorClass("effect/HttpApiError/BadRequest")({ +export class BadRequest extends Schema.Error("effect/HttpApiError/BadRequest")({ _tag: Schema.tag("BadRequest") }, { description: "BadRequest", @@ -55,7 +56,7 @@ export class BadRequest extends Schema.ErrorClass("effect/HttpApiErr * No-content schema variant for `BadRequest`, decoding an empty 400 response into * a `BadRequest` error value. * - * @category NoContent errors + * @category schemas * @since 4.0.0 */ export const BadRequestNoContent = BadRequest.pipe(HttpApiSchema.asNoContent({ @@ -69,7 +70,7 @@ export const BadRequestNoContent = BadRequest.pipe(HttpApiSchema.asNoContent({ * @category errors * @since 4.0.0 */ -export class Unauthorized extends Schema.ErrorClass("effect/HttpApiError/Unauthorized")({ +export class Unauthorized extends Schema.Error("effect/HttpApiError/Unauthorized")({ _tag: Schema.tag("Unauthorized") }, { description: "Unauthorized", @@ -85,7 +86,7 @@ export class Unauthorized extends Schema.ErrorClass("effect/HttpAp * No-content schema variant for `Unauthorized`, decoding an empty 401 response * into an `Unauthorized` error value. * - * @category NoContent errors + * @category schemas * @since 4.0.0 */ export const UnauthorizedNoContent = Unauthorized.pipe(HttpApiSchema.asNoContent({ @@ -99,7 +100,7 @@ export const UnauthorizedNoContent = Unauthorized.pipe(HttpApiSchema.asNoContent * @category errors * @since 4.0.0 */ -export class Forbidden extends Schema.ErrorClass("effect/HttpApiError/Forbidden")({ +export class Forbidden extends Schema.Error("effect/HttpApiError/Forbidden")({ _tag: Schema.tag("Forbidden") }, { description: "Forbidden", @@ -115,7 +116,7 @@ export class Forbidden extends Schema.ErrorClass("effect/HttpApiError * No-content schema variant for `Forbidden`, decoding an empty 403 response into a * `Forbidden` error value. * - * @category NoContent errors + * @category schemas * @since 4.0.0 */ export const ForbiddenNoContent = Forbidden.pipe(HttpApiSchema.asNoContent({ @@ -129,7 +130,7 @@ export const ForbiddenNoContent = Forbidden.pipe(HttpApiSchema.asNoContent({ * @category errors * @since 4.0.0 */ -export class NotFound extends Schema.ErrorClass("effect/HttpApiError/NotFound")({ +export class NotFound extends Schema.Error("effect/HttpApiError/NotFound")({ _tag: Schema.tag("NotFound") }, { description: "NotFound", @@ -145,7 +146,7 @@ export class NotFound extends Schema.ErrorClass("effect/HttpApiError/N * No-content schema variant for `NotFound`, decoding an empty 404 response into a * `NotFound` error value. * - * @category NoContent errors + * @category schemas * @since 4.0.0 */ export const NotFoundNoContent = NotFound.pipe(HttpApiSchema.asNoContent({ @@ -159,7 +160,7 @@ export const NotFoundNoContent = NotFound.pipe(HttpApiSchema.asNoContent({ * @category errors * @since 4.0.0 */ -export class MethodNotAllowed extends Schema.ErrorClass("effect/HttpApiError/MethodNotAllowed")({ +export class MethodNotAllowed extends Schema.Error("effect/HttpApiError/MethodNotAllowed")({ _tag: Schema.tag("MethodNotAllowed") }, { description: "MethodNotAllowed", @@ -175,7 +176,7 @@ export class MethodNotAllowed extends Schema.ErrorClass("effec * No-content schema variant for `MethodNotAllowed`, decoding an empty 405 response * into a `MethodNotAllowed` error value. * - * @category NoContent errors + * @category schemas * @since 4.0.0 */ export const MethodNotAllowedNoContent = MethodNotAllowed.pipe(HttpApiSchema.asNoContent({ @@ -189,7 +190,7 @@ export const MethodNotAllowedNoContent = MethodNotAllowed.pipe(HttpApiSchema.asN * @category errors * @since 4.0.0 */ -export class NotAcceptable extends Schema.ErrorClass("effect/HttpApiError/NotAcceptable")({ +export class NotAcceptable extends Schema.Error("effect/HttpApiError/NotAcceptable")({ _tag: Schema.tag("NotAcceptable") }, { description: "NotAcceptable", @@ -205,7 +206,7 @@ export class NotAcceptable extends Schema.ErrorClass("effect/Http * No-content schema variant for `NotAcceptable`, decoding an empty 406 response * into a `NotAcceptable` error value. * - * @category NoContent errors + * @category schemas * @since 4.0.0 */ export const NotAcceptableNoContent = NotAcceptable.pipe(HttpApiSchema.asNoContent({ @@ -219,7 +220,7 @@ export const NotAcceptableNoContent = NotAcceptable.pipe(HttpApiSchema.asNoConte * @category errors * @since 4.0.0 */ -export class RequestTimeout extends Schema.ErrorClass("effect/HttpApiError/RequestTimeout")({ +export class RequestTimeout extends Schema.Error("effect/HttpApiError/RequestTimeout")({ _tag: Schema.tag("RequestTimeout") }, { description: "RequestTimeout", @@ -235,7 +236,7 @@ export class RequestTimeout extends Schema.ErrorClass("effect/Ht * No-content schema variant for `RequestTimeout`, decoding an empty 408 response * into a `RequestTimeout` error value. * - * @category NoContent errors + * @category schemas * @since 4.0.0 */ export const RequestTimeoutNoContent = RequestTimeout.pipe(HttpApiSchema.asNoContent({ @@ -249,7 +250,7 @@ export const RequestTimeoutNoContent = RequestTimeout.pipe(HttpApiSchema.asNoCon * @category errors * @since 4.0.0 */ -export class Conflict extends Schema.ErrorClass("effect/HttpApiError/Conflict")({ +export class Conflict extends Schema.Error("effect/HttpApiError/Conflict")({ _tag: Schema.tag("Conflict") }, { description: "Conflict", @@ -265,7 +266,7 @@ export class Conflict extends Schema.ErrorClass("effect/HttpApiError/C * No-content schema variant for `Conflict`, decoding an empty 409 response into a * `Conflict` error value. * - * @category NoContent errors + * @category schemas * @since 4.0.0 */ export const ConflictNoContent = Conflict.pipe(HttpApiSchema.asNoContent({ @@ -279,7 +280,7 @@ export const ConflictNoContent = Conflict.pipe(HttpApiSchema.asNoContent({ * @category errors * @since 4.0.0 */ -export class Gone extends Schema.ErrorClass("effect/HttpApiError/Gone")({ +export class Gone extends Schema.Error("effect/HttpApiError/Gone")({ _tag: Schema.tag("Gone") }, { description: "Gone", @@ -295,13 +296,43 @@ export class Gone extends Schema.ErrorClass("effect/HttpApiError/Gone")({ * No-content schema variant for `Gone`, decoding an empty 410 response into a * `Gone` error value. * - * @category NoContent errors + * @category schemas * @since 4.0.0 */ export const GoneNoContent = Gone.pipe(HttpApiSchema.asNoContent({ decode: () => new Gone({}) })) +/** + * Built-in HTTP API error for a `422 Unprocessable Entity` response. When used + * directly as a server response, it renders as an empty response with status 422. + * + * @category errors + * @since 4.0.0 + */ +export class UnprocessableEntity extends Schema.Error("effect/HttpApiError/UnprocessableEntity")({ + _tag: Schema.tag("UnprocessableEntity") +}, { + description: "UnprocessableEntity", + httpApiStatus: 422 +}) { + override readonly [ErrorReporter.ignore] = true; + [HttpServerRespondable.symbol]() { + return Effect.succeed(unprocessableEntityResponse) + } +} + +/** + * No-content schema variant for `UnprocessableEntity`, decoding an empty 422 + * response into an `UnprocessableEntity` error value. + * + * @category schemas + * @since 4.0.0 + */ +export const UnprocessableEntityNoContent = UnprocessableEntity.pipe(HttpApiSchema.asNoContent({ + decode: () => new UnprocessableEntity({}) +})) + /** * Built-in HTTP API error for a `500 Internal Server Error` response. When used * directly as a server response, it renders as an empty response with status 500. @@ -309,14 +340,12 @@ export const GoneNoContent = Gone.pipe(HttpApiSchema.asNoContent({ * @category errors * @since 4.0.0 */ -export class InternalServerError - extends Schema.ErrorClass("effect/HttpApiError/InternalServerError")({ - _tag: Schema.tag("InternalServerError") - }, { - description: "InternalServerError", - httpApiStatus: 500 - }) -{ +export class InternalServerError extends Schema.Error("effect/HttpApiError/InternalServerError")({ + _tag: Schema.tag("InternalServerError") +}, { + description: "InternalServerError", + httpApiStatus: 500 +}) { [HttpServerRespondable.symbol]() { return Effect.succeed(internalServerErrorResponse) } @@ -326,7 +355,7 @@ export class InternalServerError * No-content schema variant for `InternalServerError`, decoding an empty 500 * response into an `InternalServerError` error value. * - * @category NoContent errors + * @category schemas * @since 4.0.0 */ export const InternalServerErrorNoContent = InternalServerError.pipe(HttpApiSchema.asNoContent({ @@ -340,7 +369,7 @@ export const InternalServerErrorNoContent = InternalServerError.pipe(HttpApiSche * @category errors * @since 4.0.0 */ -export class NotImplemented extends Schema.ErrorClass("effect/HttpApiError/NotImplemented")({ +export class NotImplemented extends Schema.Error("effect/HttpApiError/NotImplemented")({ _tag: Schema.tag("NotImplemented") }, { description: "NotImplemented", @@ -355,7 +384,7 @@ export class NotImplemented extends Schema.ErrorClass("effect/Ht * No-content schema variant for `NotImplemented`, decoding an empty 501 response * into a `NotImplemented` error value. * - * @category NoContent errors + * @category schemas * @since 4.0.0 */ export const NotImplementedNoContent = NotImplemented.pipe(HttpApiSchema.asNoContent({ @@ -369,14 +398,12 @@ export const NotImplementedNoContent = NotImplemented.pipe(HttpApiSchema.asNoCon * @category errors * @since 4.0.0 */ -export class ServiceUnavailable - extends Schema.ErrorClass("effect/HttpApiError/ServiceUnavailable")({ - _tag: Schema.tag("ServiceUnavailable") - }, { - description: "ServiceUnavailable", - httpApiStatus: 503 - }) -{ +export class ServiceUnavailable extends Schema.Error("effect/HttpApiError/ServiceUnavailable")({ + _tag: Schema.tag("ServiceUnavailable") +}, { + description: "ServiceUnavailable", + httpApiStatus: 503 +}) { [HttpServerRespondable.symbol]() { return Effect.succeed(serviceUnavailableResponse) } @@ -386,7 +413,7 @@ export class ServiceUnavailable * No-content schema variant for `ServiceUnavailable`, decoding an empty 503 * response into a `ServiceUnavailable` error value. * - * @category NoContent errors + * @category schemas * @since 4.0.0 */ export const ServiceUnavailableNoContent = ServiceUnavailable.pipe(HttpApiSchema.asNoContent({ @@ -410,15 +437,15 @@ export type HttpApiSchemaErrorTypeId = "~effect/httpapi/HttpApiError/HttpApiSche export const HttpApiSchemaErrorTypeId: HttpApiSchemaErrorTypeId = "~effect/httpapi/HttpApiError/HttpApiSchemaError" /** - * Error raised when an HTTP API request component fails schema decoding. It records - * which component failed and responds as an empty `400 Bad Request` when rendered - * as a server response. + * Error raised when an HTTP API request or response component fails schema + * decoding or encoding. It records which component failed and responds as an + * empty `400 Bad Request` when rendered as a server response. * * @category errors * @since 4.0.0 */ export class HttpApiSchemaError extends Data.TaggedClass("HttpApiSchemaError")<{ - readonly kind: "Params" | "Headers" | "Query" | "Body" | "Payload" + readonly kind: "Params" | "Headers" | "Query" | "Body" | "Payload" | "ResponseHeaders" readonly cause: Schema.SchemaError }> { readonly [HttpApiSchemaErrorTypeId]: HttpApiSchemaErrorTypeId = HttpApiSchemaErrorTypeId diff --git a/repos/effect/packages/effect/src/unstable/httpapi/HttpApiGroup.ts b/repos/effect/packages/effect/src/unstable/httpapi/HttpApiGroup.ts index 9634518342..66feb7486a 100644 --- a/repos/effect/packages/effect/src/unstable/httpapi/HttpApiGroup.ts +++ b/repos/effect/packages/effect/src/unstable/httpapi/HttpApiGroup.ts @@ -12,6 +12,7 @@ */ import type { NonEmptyReadonlyArray } from "../../Array.ts" import * as Context from "../../Context.ts" +import * as InternalRecord from "../../internal/record.ts" import { type Pipeable, pipeArguments } from "../../Pipeable.ts" import * as Predicate from "../../Predicate.ts" import * as Record from "../../Record.ts" @@ -28,7 +29,14 @@ const TypeId = "~effect/httpapi/HttpApiGroup" * @category guards * @since 4.0.0 */ -export const isHttpApiGroup = (u: unknown): u is Any => Predicate.hasProperty(u, TypeId) +export const isHttpApiGroup = (u: unknown): u is Top => Predicate.hasProperty(u, TypeId) + +/** + * Endpoints indexed by their identifier. + */ +type EndpointMap = { + readonly [Endpoint in Endpoints as HttpApiEndpoint.Identifier]: Endpoint +} /** * An `HttpApiGroup` is a named collection of `HttpApiEndpoint`s that represents @@ -43,21 +51,26 @@ export const isHttpApiGroup = (u: unknown): u is Any => Predicate.hasProperty(u, */ export interface HttpApiGroup< out Id extends string, - out Endpoints extends HttpApiEndpoint.Any = never, + in out Endpoints extends HttpApiEndpoint.Constraint = never, out TopLevel extends boolean = false > extends Pipeable { new(_: never): {} readonly [TypeId]: typeof TypeId + /** + * Stable group identifier. This field intentionally is not named `name` + * because `HttpApiGroup` values can be extended as classes, where `name` + * would collide with JavaScript's built-in `Function.name`. + */ readonly identifier: Id readonly key: string readonly topLevel: TopLevel - readonly endpoints: Record.ReadonlyRecord + readonly endpoints: EndpointMap readonly annotations: Context.Context /** * Add an `HttpApiEndpoint` to an `HttpApiGroup`. */ - add>( + add>( ...endpoints: A ): HttpApiGroup @@ -113,30 +126,53 @@ export interface HttpApiGroup< } /** - * Type-level identity for a group within an HTTP API, pairing the API id with the - * group name for service derivation. + * Type-level service produced by the layer that implements one group of an HTTP + * API. * - * @category models + * **Details** + * + * `HttpApiBuilder.group` provides this service, and `HttpApiBuilder.layer` + * requires one service for each group in the API. The type carries both the API + * id and the group identifier so the relationship between an API and its + * implemented groups is checked at compile time. + * + * @category services * @since 4.0.0 */ -export interface ApiGroup { +export interface Service { readonly _: unique symbol readonly apiId: ApiId - readonly name: Name + readonly identifier: Identifier } /** - * A widened `HttpApiGroup` type used when the concrete group name, endpoints, and - * top-level flag are not needed. + * Derives the group implementation service required for each group in an HTTP + * API. + * + * **Details** + * + * When given an API id and a group or union of groups, this type maps each group + * to the `Service` identity that must be provided by `HttpApiBuilder.group`. + * + * @category utility types + * @since 4.0.0 + */ +export type ToService = Group extends Constraint ? + Service + : never + +/** + * A widened `HttpApiGroup` type used when the concrete group identifier, + * endpoints, and top-level flag are not needed. * * @category models * @since 4.0.0 */ -export interface Any { +export interface Constraint { readonly [TypeId]: typeof TypeId readonly identifier: string readonly key: string - readonly endpoints: Record.ReadonlyRecord + readonly endpoints: Record.ReadonlyRecord } /** @@ -146,49 +182,39 @@ export interface Any { * @category models * @since 4.0.0 */ -export type AnyWithProps = HttpApiGroup - -/** - * Derives the API-specific `ApiGroup` service identity for an HTTP API group. - * - * @category models - * @since 4.0.0 - */ -export type ToService = A extends HttpApiGroup ? - ApiGroup - : never +export interface Top extends HttpApiGroup {} /** * Selects the group with the specified identifier from a union of groups. * - * @category models + * @category utility types * @since 4.0.0 */ -export type WithName = Extract +export type WithIdentifier = Extract /** * Extracts the identifier literal from an `HttpApiGroup`. * - * @category models + * @category utility types * @since 4.0.0 */ -export type Name = Group extends HttpApiGroup ? _Name - : never +export type Identifier = Group extends Constraint ? Group["identifier"] : never /** * Extracts the endpoint union contained in an `HttpApiGroup`. * - * @category models + * @category utility types * @since 4.0.0 */ -export type Endpoints = Group extends HttpApiGroup ? _Endpoints +export type Endpoints = Group extends HttpApiGroup ? + _Endpoints : never /** * Computes the services required to encode error responses for every endpoint in a * group. * - * @category models + * @category utility types * @since 4.0.0 */ export type ErrorServicesEncode = HttpApiEndpoint.ErrorServicesEncode> @@ -197,7 +223,7 @@ export type ErrorServicesEncode = HttpApiEndpoint.ErrorServicesEncode = HttpApiEndpoint.ErrorServicesDecode> @@ -205,7 +231,7 @@ export type ErrorServicesDecode = HttpApiEndpoint.ErrorServicesDecode = HttpApiEndpoint.MiddlewareError> @@ -214,7 +240,7 @@ export type MiddlewareError = HttpApiEndpoint.MiddlewareError = HttpApiEndpoint.MiddlewareProvides> @@ -222,7 +248,7 @@ export type MiddlewareProvides = HttpApiEndpoint.MiddlewareProvides = HttpApiEndpoint.MiddlewareClient> @@ -230,111 +256,99 @@ export type MiddlewareClient = HttpApiEndpoint.MiddlewareClient = HttpApiEndpoint.MiddlewareServices> /** - * Selects the endpoints in a group whose `name` matches the provided endpoint name. + * Extracts the endpoint union from the group with the specified identifier. * - * @category models + * @category utility types * @since 4.0.0 */ -export type EndpointsWithName = Endpoints> +export type EndpointsWithIdentifier = Endpoints< + WithIdentifier +> /** * Computes the schema encoding and decoding services required by clients for all endpoints in a group. * - * @category models + * @category utility types * @since 4.0.0 */ -export type ClientServices = Group extends HttpApiGroup ? +export type ClientServices = Group extends HttpApiGroup ? HttpApiEndpoint.ClientServices<_Endpoints> : never /** * Returns the type of a group after adding the supplied path prefix to each endpoint in the group. * - * @category models + * @category utility types * @since 4.0.0 */ export type AddPrefix = Group extends - HttpApiGroup ? - HttpApiGroup<_Name, HttpApiEndpoint.AddPrefix<_Endpoints, Prefix>, _TopLevel> + HttpApiGroup ? + HttpApiGroup<_Identifier, HttpApiEndpoint.AddPrefix<_Endpoints, Prefix>, _TopLevel> : never /** * Returns the type of a group after applying a middleware identifier to every endpoint in the group. * - * @category models + * @category utility types * @since 4.0.0 */ export type AddMiddleware = Group extends - HttpApiGroup ? - HttpApiGroup<_Name, HttpApiEndpoint.AddMiddleware<_Endpoints, Id>, _TopLevel> + HttpApiGroup ? + HttpApiGroup<_Identifier, HttpApiEndpoint.AddMiddleware<_Endpoints, Id>, _TopLevel> : never const Proto = { [TypeId]: TypeId, - add(this: AnyWithProps, ...toAdd: NonEmptyReadonlyArray) { + add(this: Top, ...toAdd: NonEmptyReadonlyArray) { const endpoints = { ...this.endpoints } for (const endpoint of toAdd) { - endpoints[endpoint.name] = endpoint + InternalRecord.assignProperty(endpoints, endpoint.identifier, endpoint) } return makeProto({ - identifier: this.identifier, - topLevel: this.topLevel, - endpoints, - annotations: this.annotations + ...optionsFromGroup(this), + endpoints }) }, - prefix(this: AnyWithProps, prefix: PathInput) { + prefix(this: Top, prefix: PathInput) { return makeProto({ - identifier: this.identifier, - topLevel: this.topLevel, - endpoints: Record.map(this.endpoints, (endpoint) => endpoint.prefix(prefix)), - annotations: this.annotations + ...optionsFromGroup(this), + endpoints: Record.map(this.endpoints, (endpoint) => endpoint.prefix(prefix)) }) }, - middleware(this: AnyWithProps, middleware: HttpApiMiddleware.AnyService) { + middleware(this: Top, middleware: HttpApiMiddleware.AnyService) { return makeProto({ - identifier: this.identifier, - topLevel: this.topLevel, - endpoints: Record.map(this.endpoints, (endpoint) => endpoint.middleware(middleware as any)), - annotations: this.annotations + ...optionsFromGroup(this), + endpoints: Record.map(this.endpoints, (endpoint) => endpoint.middleware(middleware as any)) }) }, - annotateMerge(this: AnyWithProps, annotations: Context.Context) { + annotateMerge(this: Top, annotations: Context.Context) { return makeProto({ - identifier: this.identifier, - topLevel: this.topLevel, - endpoints: this.endpoints, + ...optionsFromGroup(this), annotations: Context.merge(this.annotations, annotations) }) }, - annotate(this: AnyWithProps, annotation: Context.Key, value: S) { + annotate(this: Top, annotation: Context.Key, value: S) { return makeProto({ - identifier: this.identifier, - topLevel: this.topLevel, - endpoints: this.endpoints, + ...optionsFromGroup(this), annotations: Context.add(this.annotations, annotation, value) }) }, - annotateEndpointsMerge(this: AnyWithProps, annotations: Context.Context) { + annotateEndpointsMerge(this: Top, annotations: Context.Context) { return makeProto({ - identifier: this.identifier, - topLevel: this.topLevel, - endpoints: Record.map(this.endpoints, (endpoint) => endpoint.annotateMerge(annotations)), - annotations: this.annotations + ...optionsFromGroup(this), + endpoints: Record.map(this.endpoints, (endpoint) => endpoint.annotateMerge(annotations)) }) }, - annotateEndpoints(this: AnyWithProps, annotation: Context.Key, value: S) { + annotateEndpoints(this: Top, annotation: Context.Key, value: S) { return makeProto({ - identifier: this.identifier, - topLevel: this.topLevel, - endpoints: Record.map(this.endpoints, (endpoint) => endpoint.annotate(annotation, value)), - annotations: this.annotations + ...optionsFromGroup(this), + endpoints: Record.map(this.endpoints, (endpoint) => endpoint.annotate(annotation, value)) }) }, pipe() { @@ -342,14 +356,21 @@ const Proto = { } } +const optionsFromGroup = (group: Top) => ({ + identifier: group.identifier, + topLevel: group.topLevel, + endpoints: group.endpoints, + annotations: group.annotations +}) + const makeProto = < Id extends string, - Endpoints extends HttpApiEndpoint.Any, + Endpoints extends HttpApiEndpoint.Constraint, TopLevel extends (true | false) >(options: { readonly identifier: Id readonly topLevel: TopLevel - readonly endpoints: Record.ReadonlyRecord + readonly endpoints: Record.ReadonlyRecord readonly annotations: Context.Context }): HttpApiGroup => { function HttpApiGroup() {} @@ -365,7 +386,7 @@ const makeProto = < * * Add endpoints with `add`, provide implementations with `HttpApiBuilder.group`, * and set `topLevel` when the generated client should expose endpoint methods - * directly instead of nesting them under the group name. + * directly instead of nesting them under the group identifier. * * @category constructors * @since 4.0.0 @@ -375,7 +396,7 @@ export const make = => makeProto({ identifier, - topLevel: options?.topLevel ?? false as any, - endpoints: Record.empty(), + topLevel: (options?.topLevel ?? false) as TopLevel, + endpoints: {}, annotations: Context.empty() - }) as any + }) diff --git a/repos/effect/packages/effect/src/unstable/httpapi/HttpApiMiddleware.ts b/repos/effect/packages/effect/src/unstable/httpapi/HttpApiMiddleware.ts index ee7875e829..24b3f0fb64 100644 --- a/repos/effect/packages/effect/src/unstable/httpapi/HttpApiMiddleware.ts +++ b/repos/effect/packages/effect/src/unstable/httpapi/HttpApiMiddleware.ts @@ -64,8 +64,8 @@ type ErrorSchemaFromConstraint = E extends ReadonlyArray ? export type HttpApiMiddleware = ( httpEffect: Effect.Effect, options: { - readonly endpoint: HttpApiEndpoint.AnyWithProps - readonly group: HttpApiGroup.AnyWithProps + readonly endpoint: HttpApiEndpoint.Top + readonly group: HttpApiGroup.Top } ) => Effect.Effect["Type"], Requires | HttpRouter.Provided> @@ -90,8 +90,8 @@ export type HttpApiMiddlewareSecurity< httpEffect: Effect.Effect, options: { readonly credential: HttpApiSecurity.HttpApiSecurity.Type - readonly endpoint: HttpApiEndpoint.AnyWithProps - readonly group: HttpApiGroup.AnyWithProps + readonly endpoint: HttpApiEndpoint.Top + readonly group: HttpApiGroup.Top } ) => Effect.Effect< HttpServerResponse, @@ -113,8 +113,8 @@ export type HttpApiMiddlewareSecurity< */ export interface HttpApiMiddlewareClient<_E, CE, R> { (options: { - readonly endpoint: HttpApiEndpoint.AnyWithProps - readonly group: HttpApiGroup.AnyWithProps + readonly endpoint: HttpApiEndpoint.Top + readonly group: HttpApiGroup.Top readonly request: HttpClientRequest.HttpClientRequest readonly next: ( request: HttpClientRequest.HttpClientRequest @@ -125,7 +125,7 @@ export interface HttpApiMiddlewareClient<_E, CE, R> { /** * Client-side service marker required when a middleware declares `requiredForClient`. * - * @category models + * @category utility types * @since 4.0.0 */ export interface ForClient { @@ -136,7 +136,7 @@ export interface ForClient { /** * Base service key shape for HTTP API middleware services, including provided services, declared error schemas, and client requirements. * - * @category models + * @category services * @since 4.0.0 */ export interface AnyService extends Context.Key { @@ -150,7 +150,7 @@ export interface AnyService extends Context.Key { /** * Middleware service key shape for security middleware, including the security schemes handled by the service. * - * @category models + * @category services * @since 4.0.0 */ export interface AnyServiceSecurity extends AnyService { @@ -161,7 +161,7 @@ export interface AnyServiceSecurity extends AnyService { /** * Type-level identifier carried by middleware services to track provided services, required services, errors, client errors, and client requirements. * - * @category models + * @category utility types * @since 4.0.0 */ export interface AnyId { @@ -177,7 +177,7 @@ export interface AnyId { /** * Extracts the services provided by a middleware identifier. * - * @category models + * @category utility types * @since 4.0.0 */ export type Provides = A extends { readonly [TypeId]: { readonly provides: infer P } } ? P : never @@ -185,7 +185,7 @@ export type Provides = A extends { readonly [TypeId]: { readonly provides: in /** * Extracts the services required to run a middleware implementation. * - * @category models + * @category utility types * @since 4.0.0 */ export type Requires = A extends { readonly [TypeId]: { readonly requires: infer R } } ? R : never @@ -193,7 +193,7 @@ export type Requires = A extends { readonly [TypeId]: { readonly requires: in /** * Applies a middleware's service changes to an existing requirement type by removing services it provides and adding services it requires. * - * @category models + * @category utility types * @since 4.0.0 */ export type ApplyServices = Exclude> | Requires @@ -201,7 +201,7 @@ export type ApplyServices = Exclude> | Requir /** * Extracts the schema or schema union used for errors declared by a middleware identifier. * - * @category models + * @category utility types * @since 4.0.0 */ export type ErrorSchema = A extends { readonly [TypeId]: { readonly error: infer E } } ? ErrorSchemaFromConstraint @@ -210,7 +210,7 @@ export type ErrorSchema = A extends { readonly [TypeId]: { readonly error: in /** * Extracts the decoded error type declared by a middleware identifier. * - * @category models + * @category utility types * @since 4.0.0 */ export type Error = ErrorSchema["Type"] @@ -218,7 +218,7 @@ export type Error = ErrorSchema["Type"] /** * Extracts the client-side error type for middleware that is required on generated clients. * - * @category models + * @category utility types * @since 4.0.0 */ export type ClientError = A extends { @@ -232,7 +232,7 @@ export type ClientError = A extends { /** * Computes the client-side service marker required for middleware that must also run in generated clients. * - * @category models + * @category utility types * @since 4.0.0 */ export type MiddlewareClient = A extends { @@ -245,7 +245,7 @@ export type MiddlewareClient = A extends { /** * Extracts the schema services required to encode errors declared by a middleware identifier. * - * @category models + * @category utility types * @since 4.0.0 */ export type ErrorServicesEncode = ErrorSchema["EncodingServices"] @@ -253,7 +253,7 @@ export type ErrorServicesEncode = ErrorSchema["EncodingServices"] /** * Extracts the schema services required to decode errors declared by a middleware identifier. * - * @category models + * @category utility types * @since 4.0.0 */ export type ErrorServicesDecode = ErrorSchema["DecodingServices"] @@ -266,7 +266,7 @@ export type ErrorServicesDecode = ErrorSchema["DecodingServices"] * It combines a `Context.Service` class with the middleware metadata used by * endpoints, builders, and generated clients. * - * @category schemas + * @category services * @since 4.0.0 */ export type ServiceClass< @@ -314,7 +314,7 @@ export type ServiceClass< * required services, provided services, typed error schemas, security schemes, * client errors, or a matching client middleware requirement. * - * @category schemas + * @category constructors * @since 4.0.0 */ export const Service = < @@ -394,33 +394,83 @@ function getError(error: ErrorConstraint | undefined): ReadonlySet { * * **Example** (Mapping schema errors to custom errors) * - * ```ts - * import { Effect, Schema } from "effect" - * import { HttpApiMiddleware } from "effect/unstable/httpapi" + * ```ts import.meta.vitest + * import { Effect, Schema, type Types } from "effect" + * import { HttpRouter, HttpServerResponse } from "effect/unstable/http" + * import { + * HttpApiEndpoint, + * HttpApiError, + * HttpApiGroup, + * HttpApiMiddleware + * } from "effect/unstable/httpapi" * - * export class CustomError extends Schema.TaggedErrorClass()("CustomError", {}) {} + * class CustomError extends Schema.TaggedError()("CustomError", {}) {} * - * export class ErrorHandler extends HttpApiMiddleware.Service()("api/ErrorHandler", { + * class ErrorHandler extends HttpApiMiddleware.Service()("api/ErrorHandler", { * error: CustomError * }) {} * - * export const ErrorHandlerLayer = HttpApiMiddleware.layerSchemaErrorTransform( + * const messages: Array = [] + * const ErrorHandlerLayer = HttpApiMiddleware.layerSchemaErrorTransform( * ErrorHandler, * (schemaError) => - * Effect.log("Got SchemaError", schemaError).pipe( + * Effect.sync(() => messages.push(`Mapping ${schemaError.kind} schema error`)).pipe( * Effect.andThen(Effect.fail(new CustomError())) * ) * ) + * + * const endpoint = HttpApiEndpoint.get("example", "/") + * const group = HttpApiGroup.make("examples").add(endpoint) + * const middlewareContext = { + * endpoint: endpoint as unknown as HttpApiEndpoint.Top, + * group: group as unknown as HttpApiGroup.Top + * } + * const Routes = HttpRouter.add( + * "GET", + * "/", + * Effect.gen(function*() { + * const applySchemaErrorTransform = yield* ErrorHandler + * const schemaError = yield* HttpApiError.HttpApiSchemaError.wrap( + * "Body", + * Schema.decodeUnknownEffect(Schema.String)(42) + * ).pipe(Effect.flip) + * const failingResponse = Effect.fail(schemaError as unknown as Types.unhandled) + * const result = yield* applySchemaErrorTransform(failingResponse, middlewareContext).pipe( + * Effect.match({ + * onFailure: (error) => error instanceof CustomError ? error._tag : "UnexpectedError", + * onSuccess: () => "Success" + * }) + * ) + * return HttpServerResponse.text(result) + * }) + * ).pipe(HttpRouter.provideRequest(ErrorHandlerLayer)) + * + * const program = Effect.acquireUseRelease( + * Effect.sync(() => HttpRouter.toWebHandler(Routes, { disableLogger: true })), + * ({ handler }) => + * Effect.gen(function*() { + * const response = yield* Effect.promise(() => handler(new Request("http://localhost/"))) + * const body = yield* Effect.promise(() => response.text()) + * return body + * }), + * ({ dispose }) => Effect.promise(dispose) + * ) + * + * const body = await Effect.runPromise(program) + * const result = [messages, body] // => [["Mapping Body schema error"], "CustomError"] * ``` * - * @category SchemaError transform + * @category layers * @since 4.0.0 */ export const layerSchemaErrorTransform = ( service: Context.Service>, transform: ( error: HttpApiSchemaError, - context: { readonly endpoint: HttpApiEndpoint.AnyWithProps; readonly group: HttpApiGroup.AnyWithProps } + context: { + readonly endpoint: HttpApiEndpoint.Top + readonly group: HttpApiGroup.Top + } ) => Effect.Effect< HttpServerResponse, ErrorSchemaFromConstraint["Type"] | HttpApiSchemaError, @@ -448,7 +498,7 @@ export const layerSchemaErrorTransform = ( diff --git a/repos/effect/packages/effect/src/unstable/httpapi/HttpApiScalar.ts b/repos/effect/packages/effect/src/unstable/httpapi/HttpApiScalar.ts index e1f96b1e77..22900eedef 100644 --- a/repos/effect/packages/effect/src/unstable/httpapi/HttpApiScalar.ts +++ b/repos/effect/packages/effect/src/unstable/httpapi/HttpApiScalar.ts @@ -87,12 +87,6 @@ export type ScalarConfig = { /** * Path to a favicon image. * - * **Example** (Setting a relative favicon) - * - * ```ts - * const favicon = "/favicon.svg" - * ``` - * * @default undefined */ favicon?: string @@ -107,12 +101,6 @@ export type ScalarConfig = { * Browsers can derive the origin from `window.location.origin`; server * rendering needs this value supplied explicitly. * - * **Example** (Setting a local server URL) - * - * ```ts - * const baseServerURL = "http://localhost:3000" - * ``` - * * @default undefined */ baseServerURL?: string @@ -151,7 +139,7 @@ type ScalarSource = readonly source: string } -const makeHandler = (options: { +const makeHandler = (options: { readonly api: HttpApi.HttpApi readonly source: ScalarSource readonly scalar: ScalarConfig | undefined @@ -162,6 +150,15 @@ const makeHandler = (options _integration: "html", ...scalar } + const scalarScript = options.source._tag === "Cdn" + ? `` + : `` const response = HttpServerResponse.html(` @@ -170,12 +167,12 @@ const makeHandler = (options ${ !spec.info.description ? "" - : `` + : `` } ${ !spec.info.description ? "" - : `` + : `` } (options
- ${ - options.source._tag === "Cdn" - ? `` - : `` - } + ${scalarScript} ${injectedTag}` + const Api = HttpApi.make("Docs") + .annotate(OpenApi.Title, title) + .annotate(OpenApi.Description, description) + + const html = yield* render(HttpApiScalar.layerCdn(Api)) + + assert.ok(html.includes(`Docs "title" </title><script>`)) + const escapedDescription = + `"quoted" 'single' </script ><script id="script-data-injected">` + assert.ok(html.includes(``)) + assert.ok(html.includes(``)) + assert.ok(!html.includes(``)) + assert.deepStrictEqual(extractSpec(html), OpenApi.fromApi(Api)) + })) + + it.effect("encodes CDN versions before interpolating the script source", () => + Effect.gen(function*() { + const version = `1.2.3">${injectedTag}`) + + const html = yield* render(HttpApiSwagger.layer(Api)) + + assert.ok(!html.includes(``)) + assert.deepStrictEqual(extractSwaggerSpec(html), OpenApi.fromApi(Api)) + })) +}) + +const render = (layer: Layer.Layer) => + Effect.acquireUseRelease( + Effect.sync(() => HttpRouter.toWebHandler(layer, { disableLogger: true })), + ({ handler }) => + Effect.flatMap( + Effect.promise(() => handler(new Request("http://test/docs"))), + (response) => Effect.promise(() => response.text()) + ), + ({ dispose }) => Effect.promise(dispose) + ) + +function extractSpec(html: string): unknown { + const marker = " content: " + const start = html.indexOf(marker) + assert.notStrictEqual(start, -1) + const end = html.indexOf("\n })", start) + assert.notStrictEqual(end, -1) + return JSON.parse(html.slice(start + marker.length, end)) +} + +function extractSwaggerSpec(html: string): unknown { + const marker = ` ", start) + assert.notStrictEqual(end, -1) + return JSON.parse(html.slice(start + marker.length, end)) +} diff --git a/repos/effect/packages/effect/test/unstable/httpapi/HttpApiEndpoint.test.ts b/repos/effect/packages/effect/test/unstable/httpapi/HttpApiEndpoint.test.ts index 4f28d8a932..eeedb9d185 100644 --- a/repos/effect/packages/effect/test/unstable/httpapi/HttpApiEndpoint.test.ts +++ b/repos/effect/packages/effect/test/unstable/httpapi/HttpApiEndpoint.test.ts @@ -10,6 +10,55 @@ const StreamError = Schema.Struct({ reason: Schema.String }) const sse = () => HttpApiSchema.StreamSse({ events: Events, error: StreamError }) +describe("HttpApiEndpoint", () => { + it("stores the supplied identifier", () => { + const endpoint = HttpApiEndpoint.get("getUser", "/users/:id") + + assert.strictEqual(endpoint.identifier, "getUser") + }) + + it("can be extended as a class", () => { + const endpoint = HttpApiEndpoint.get("getUser", "/users/:id") + class GetUser extends endpoint {} + + assert.strictEqual(typeof endpoint, "function") + assert.strictEqual(GetUser.name, "GetUser") + assert.strictEqual(GetUser.identifier, "getUser") + assert.isTrue(HttpApiEndpoint.isHttpApiEndpoint(GetUser)) + + const prefixed = GetUser.prefix("/v1") + assert.strictEqual(prefixed.identifier, "getUser") + assert.strictEqual(prefixed.path, "/v1/users/:id") + }) +}) + +describe("HttpApiEndpoint payload schemas", () => { + it("normalizes payload map keys while preserving the declared content type", () => { + const contentType = "Application/Vnd.Effect+JSON; Charset=UTF-8" + const endpoint = HttpApiEndpoint.post("create", "/", { + payload: Schema.Struct({ name: Schema.String }).pipe(HttpApiSchema.asJson({ contentType })) + }) + + const entry = endpoint.payload.get("application/vnd.effect+json") + assert.isDefined(entry) + assert.strictEqual(entry.encoding.contentType, contentType) + }) + + it("rejects incompatible encodings for equivalent content types", () => { + const JsonPayload = Schema.Struct({ name: Schema.String }).pipe( + HttpApiSchema.asJson({ contentType: "Application/Vnd.Effect+Data; charset=utf-8" }) + ) + const TextPayload = Schema.String.pipe( + HttpApiSchema.asText({ contentType: "application/vnd.effect+data" }) + ) + + assert.throws( + () => HttpApiEndpoint.post("create", "/", { payload: [JsonPayload, TextPayload] }), + /Multiple payload encodings/ + ) + }) +}) + describe("HttpApiEndpoint streaming success schemas", () => { it("GET endpoint accepts StreamSse success", () => { const stream = sse() @@ -129,17 +178,16 @@ describe("HttpApiEndpoint streaming success schemas", () => { ) }) - it("two streaming successes for distinct statuses are allowed", () => { + it("two streaming successes for distinct statuses throw", () => { const stream = HttpApiSchema.status(206)(sse()) const bytes = HttpApiSchema.status(200)( HttpApiSchema.StreamUint8Array({ contentType: "application/custom-stream" }) ) - const endpoint = HttpApiEndpoint.get("events", "/events", { - success: [stream, bytes] - }) - assert.isTrue(endpoint.success.has(stream)) - assert.isTrue(endpoint.success.has(bytes)) + assert.throws( + () => HttpApiEndpoint.get("events", "/events", { success: [stream, bytes] }), + "Multiple streaming success responses are not supported" + ) }) it("statically detectable SSE reserved failure event name throws", () => { @@ -158,3 +206,265 @@ describe("HttpApiEndpoint streaming success schemas", () => { ) }) }) + +describe("HttpApiEndpoint WithHeaders schemas", () => { + it("rejects a WithHeaders success sharing its status and content type", () => { + assert.throws( + () => + HttpApiEndpoint.get("get", "/", { + success: [ + Schema.Struct({ plain: Schema.String }), + HttpApiSchema.WithHeaders( + Schema.Struct({ wrapped: Schema.String }), + { "x-trace-id": Schema.String } + ) + ] + }), + "Cannot combine a response with headers with another response for status 200 and content-type: application/json" + ) + }) + + it("rejects an encodeToWithHeaders error sharing its status and content type", () => { + const ErrorWithHeaders = Schema.String.pipe( + HttpApiSchema.encodeToWithHeaders({ + body: Schema.Struct({ wrapped: Schema.String }), + headers: { "x-trace-id": Schema.String } + }, { + decode: ({ body }) => body.wrapped, + encode: (message) => ({ body: { wrapped: message }, headers: { "x-trace-id": "trace" } }) + }) + ) + + assert.throws( + () => + HttpApiEndpoint.get("get", "/", { + error: [ + Schema.Struct({ plain: Schema.String }), + ErrorWithHeaders + ] + }), + "Cannot combine a response with headers with another response for status 500 and content-type: application/json" + ) + }) + + it("rejects two WithHeaders successes sharing a status across content types", () => { + assert.throws( + () => + HttpApiEndpoint.get("get", "/", { + success: [ + HttpApiSchema.WithHeaders( + Schema.Struct({ value: Schema.String }), + { "x-json": Schema.String } + ), + HttpApiSchema.WithHeaders( + Schema.String.pipe(HttpApiSchema.asText()), + { "x-text": Schema.String } + ) + ] + }), + "Cannot declare multiple responses with headers for status 200" + ) + }) + + it("rejects two encodeToWithHeaders errors sharing a status", () => { + const JsonError = Schema.String.pipe( + HttpApiSchema.encodeToWithHeaders({ + body: Schema.Struct({ message: Schema.String }), + headers: { "x-json": Schema.String } + }, { + decode: ({ body }) => body.message, + encode: (message) => ({ body: { message }, headers: { "x-json": "json" } }) + }) + ) + const TextError = Schema.String.pipe( + HttpApiSchema.encodeToWithHeaders({ + body: Schema.String.pipe(HttpApiSchema.asText()), + headers: { "x-text": Schema.String } + }, { + decode: ({ body }) => body, + encode: (message) => ({ body: message, headers: { "x-text": "text" } }) + }) + ) + + assert.throws( + () => HttpApiEndpoint.get("get", "/", { error: [JsonError, TextError] }), + "Cannot declare multiple responses with headers for status 500" + ) + }) + + it("rejects mixed WithHeaders and encodeToWithHeaders responses sharing a status", () => { + const AnnotatedError = Schema.String.pipe( + HttpApiSchema.encodeToWithHeaders({ + body: Schema.String.pipe(HttpApiSchema.status(429), HttpApiSchema.asText()), + headers: { "retry-after": Schema.String } + }, { + decode: ({ body }) => body, + encode: (message) => ({ body: message, headers: { "retry-after": "30" } }) + }) + ) + + assert.throws( + () => + HttpApiEndpoint.get("get", "/", { + error: [ + HttpApiSchema.WithHeaders( + Schema.Struct({ message: Schema.String }).pipe(HttpApiSchema.status(429)), + { "x-trace-id": Schema.String } + ), + AnnotatedError + ] + }), + "Cannot declare multiple responses with headers for status 429" + ) + }) + + it("allows one response with headers and a plain response for the same status across content types", () => { + const endpoint = HttpApiEndpoint.get("get", "/", { + success: [ + HttpApiSchema.WithHeaders( + Schema.Struct({ value: Schema.String }), + { "x-trace-id": Schema.String } + ), + Schema.String.pipe(HttpApiSchema.asText()) + ] + }) + + assert.strictEqual(endpoint.success.size, 2) + }) + + it("allows responses with headers on distinct statuses", () => { + const endpoint = HttpApiEndpoint.get("get", "/", { + success: [ + HttpApiSchema.WithHeaders( + Schema.Struct({ value: Schema.String }), + { "x-trace-id": Schema.String } + ), + HttpApiSchema.WithHeaders( + Schema.Struct({ created: Schema.Boolean }).pipe(HttpApiSchema.status(201)), + { location: Schema.String } + ) + ] + }) + + assert.strictEqual(endpoint.success.size, 2) + }) + + it("keeps the wrapper in the success set with codec-transformed parts", () => { + const endpoint = HttpApiEndpoint.get("list", "/users", { + success: HttpApiSchema.WithHeaders(Schema.Struct({ a: Schema.String }), { + "x-count": Schema.Int + }) + }) + + const [schema] = Array.from(endpoint.success) + assert.isTrue(HttpApiSchema.isWithHeaders(schema)) + if (HttpApiSchema.isWithHeaders(schema)) { + assert.deepStrictEqual( + Schema.encodeSync(schema.headers as any)({ "x-count": 3 }), + { "x-count": "3" } + ) + } + }) + + it("leaves the wrapper untouched when codecs are disabled", () => { + const wrapped = HttpApiSchema.WithHeaders(Schema.Struct({ a: Schema.String }), { + "x-count": Schema.Int + }) + const endpoint = HttpApiEndpoint.get("list", "/users", { + disableCodecs: true, + success: wrapped + }) + + assert.isTrue(endpoint.success.has(wrapped)) + }) + + it("keeps a wrapped stream schema as the inner schema", () => { + const stream = sse() + const endpoint = HttpApiEndpoint.get("events", "/events", { + success: HttpApiSchema.WithHeaders(stream, { "x-count": Schema.Int }) + }) + + const [schema] = Array.from(endpoint.success) + assert.isTrue(HttpApiSchema.isWithHeaders(schema)) + if (HttpApiSchema.isWithHeaders(schema)) { + assert.strictEqual(schema.schema, stream) + } + }) + + it("preserves wrapper annotations through endpoint construction", () => { + const endpoint = HttpApiEndpoint.get("list", "/users", { + success: HttpApiSchema.WithHeaders(Schema.Struct({ a: Schema.String }), { + "x-count": Schema.Int + }).pipe(HttpApiSchema.status(201)) + }) + + const [schema] = Array.from(endpoint.success) + assert.strictEqual(HttpApiSchema.getStatusSuccessSchema(schema), 201) + }) + + it("validates a wrapped stream like a bare stream", () => { + assert.throws(() => + HttpApiEndpoint.get("events", "/events", { + success: [ + HttpApiSchema.WithHeaders(sse(), { "x-count": Schema.Int }), + HttpApiSchema.StreamUint8Array({ contentType: "application/custom-stream" }) + ] + }) + ) + assert.throws(() => + HttpApiEndpoint.head("events", "/events", { + success: HttpApiSchema.WithHeaders(sse(), { "x-count": Schema.Int }) as any + }) + ) + }) + + it("rejects wrapped and bare streams at distinct statuses", () => { + assert.throws( + () => + HttpApiEndpoint.get("events", "/events", { + success: [ + HttpApiSchema.status(206)( + HttpApiSchema.StreamUint8Array({ contentType: "application/custom-bytes" }) + ), + HttpApiSchema.WithHeaders( + HttpApiSchema.status(201)( + HttpApiSchema.StreamUint8Array({ contentType: "application/other-bytes" }) + ), + { "x-source": Schema.String } + ) + ] + }), + "Multiple streaming success responses are not supported" + ) + + assert.throws( + () => + HttpApiEndpoint.get("events", "/events", { + success: [ + HttpApiSchema.WithHeaders(sse(), { "x-count": Schema.Int }), + sse() + ] + }), + "Multiple streaming success responses are not supported" + ) + }) + + it("keeps WithHeaders in the error set with codec-transformed parts", () => { + const endpoint = HttpApiEndpoint.get("list", "/users", { + error: HttpApiSchema.WithHeaders( + Schema.Struct({ message: Schema.String }).pipe(HttpApiSchema.status(429)), + { "retry-after": Schema.Int } + ) + }) + + const [schema] = Array.from(endpoint.error) + assert.isTrue(HttpApiSchema.isWithHeaders(schema)) + assert.strictEqual(HttpApiSchema.getStatusErrorSchema(schema), 429) + if (HttpApiSchema.isWithHeaders(schema)) { + assert.deepStrictEqual( + Schema.encodeSync(schema.headers as any)({ "retry-after": 30 }), + { "retry-after": "30" } + ) + } + }) +}) diff --git a/repos/effect/packages/effect/test/unstable/httpapi/HttpApiSchema.test.ts b/repos/effect/packages/effect/test/unstable/httpapi/HttpApiSchema.test.ts index 9e60a9e5e0..8facf9e479 100644 --- a/repos/effect/packages/effect/test/unstable/httpapi/HttpApiSchema.test.ts +++ b/repos/effect/packages/effect/test/unstable/httpapi/HttpApiSchema.test.ts @@ -115,6 +115,161 @@ describe("HttpApiSchema", () => { }) }) + describe("WithHeaders", () => { + it("stores the inner schema and headers schema", () => { + const headers = Schema.Struct({ "x-total-count": Schema.FiniteFromString }) + const schema = HttpApiSchema.WithHeaders(Schema.String, headers) + + assert.isTrue(Schema.isSchema(schema)) + assert.isTrue(HttpApiSchema.isWithHeaders(schema)) + assert.strictEqual(schema.schema, Schema.String) + assert.strictEqual(schema.headers, headers) + }) + + it("does not identify other schemas as WithHeaders", () => { + assert.isFalse(HttpApiSchema.isWithHeaders(Schema.String)) + assert.isFalse(HttpApiSchema.isWithHeaders(HttpApiSchema.StreamUint8Array())) + }) + + it("supports a fields shorthand for headers", () => { + const schema = HttpApiSchema.WithHeaders(Schema.String, { + "x-total-count": Schema.FiniteFromString + }) + + assert.isTrue(Schema.isSchema(schema.headers)) + assert.deepStrictEqual(Object.keys(schema.headers.fields), ["x-total-count"]) + }) + + it("rejects nested WithHeaders schemas", () => { + const inner = HttpApiSchema.WithHeaders(Schema.String, { "x-a": Schema.String }) + + assert.throws( + () => HttpApiSchema.WithHeaders(inner, { "x-b": Schema.String }), + "WithHeaders schemas cannot be nested" + ) + }) + + it("accepts only branded values when decoding", () => { + const schema = HttpApiSchema.WithHeaders(Schema.String, { "x-a": Schema.String }) + const value = HttpApiSchema.withHeaders({ body: "a", headers: { "x-a": "1" } }) + + assert.strictEqual(Schema.decodeUnknownSync(schema)(value), value) + assert.throws(() => Schema.decodeUnknownSync(schema)({ body: "a", headers: { "x-a": "1" } })) + }) + + it("keeps the wrapper intact through annotate", () => { + const headers = Schema.Struct({ "x-a": Schema.String }) + const schema = HttpApiSchema.WithHeaders(Schema.String, headers).annotate({ description: "described" }) + + assert.isTrue(HttpApiSchema.isWithHeaders(schema)) + assert.strictEqual(schema.schema, Schema.String) + assert.strictEqual(schema.headers, headers) + }) + + it("resolves the status from the wrapper first, falling through to the inner schema", () => { + const headers = Schema.Struct({ "x-a": Schema.String }) + + const onInner = HttpApiSchema.WithHeaders(Schema.String.pipe(HttpApiSchema.status(201)), headers) + assert.strictEqual(HttpApiSchema.getStatusSuccessSchema(onInner), 201) + + const onWrapper = HttpApiSchema.WithHeaders(Schema.String, headers).pipe(HttpApiSchema.status(202)) + assert.isTrue(HttpApiSchema.isWithHeaders(onWrapper)) + assert.strictEqual(HttpApiSchema.getStatusSuccessSchema(onWrapper), 202) + + const onBoth = HttpApiSchema.WithHeaders(Schema.String.pipe(HttpApiSchema.status(201)), headers) + .pipe(HttpApiSchema.status(202)) + assert.strictEqual(HttpApiSchema.getStatusSuccessSchema(onBoth), 202) + + const onNeither = HttpApiSchema.WithHeaders(Schema.String, headers) + assert.strictEqual(HttpApiSchema.getStatusSuccessSchema(onNeither), 200) + + assert.strictEqual(HttpApiSchema.getStatusSuccessSchema(Schema.String.pipe(HttpApiSchema.status(201))), 201) + }) + + it("resolves the response encoding from the wrapper first, falling through to the inner schema", () => { + const headers = Schema.Struct({ "x-a": Schema.String }) + + const onInner = HttpApiSchema.WithHeaders(Schema.String.pipe(HttpApiSchema.asText()), headers) + assert.strictEqual(HttpApiSchema.getResponseEncodingSchema(onInner)._tag, "Text") + + const onWrapper = HttpApiSchema.WithHeaders(Schema.String, headers).pipe( + HttpApiSchema.asJson({ contentType: "application/vnd.custom+json" }) + ) + assert.isTrue(HttpApiSchema.isWithHeaders(onWrapper)) + assert.strictEqual( + HttpApiSchema.getResponseEncodingSchema(onWrapper).contentType, + "application/vnd.custom+json" + ) + + const onNeither = HttpApiSchema.WithHeaders(Schema.String, headers) + assert.strictEqual(HttpApiSchema.getResponseEncodingSchema(onNeither)._tag, "Json") + }) + }) + + describe("withHeaders", () => { + it("constructs a branded response value", () => { + const value = HttpApiSchema.withHeaders({ body: "a", headers: { "x-a": "1" } }) + + assert.strictEqual(value.body, "a") + assert.deepStrictEqual(value.headers, { "x-a": "1" }) + }) + }) + + describe("encodeToWithHeaders", () => { + class UserNotFound extends Schema.TaggedError()("UserNotFound", { + userId: Schema.Int + }) {} + + const WrappedUserNotFound = UserNotFound.pipe( + HttpApiSchema.encodeToWithHeaders({ + body: HttpApiSchema.Empty(404), + headers: { "x-user-id": Schema.Int } + }, { + decode: ({ headers }) => new UserNotFound({ userId: headers["x-user-id"] }), + encode: (error) => ({ + headers: { "x-user-id": error.userId }, + body: undefined + }) + }) + ) + + it("encodes to a body and headers pair", () => { + assert.deepStrictEqual( + Schema.encodeSync(WrappedUserNotFound)(new UserNotFound({ userId: 1 })), + { body: undefined, headers: { "x-user-id": 1 } } + ) + }) + + it("decodes a pair back to the source type", () => { + assert.deepStrictEqual( + Schema.decodeUnknownSync(WrappedUserNotFound)({ body: undefined, headers: { "x-user-id": 1 } }), + new UserNotFound({ userId: 1 }) + ) + }) + + it("resolves the status from the body schema", () => { + assert.strictEqual(HttpApiSchema.getStatusError(WrappedUserNotFound.ast), 404) + }) + + it("resolves the response encoding from the body schema", () => { + const schema = Schema.String.pipe( + HttpApiSchema.encodeToWithHeaders({ + body: Schema.String.pipe(HttpApiSchema.asText()), + headers: { "x-a": Schema.String } + }, { + decode: ({ body }) => body, + encode: (value) => ({ body: value, headers: { "x-a": "1" } }) + }) + ) + + assert.strictEqual(HttpApiSchema.getResponseEncoding(schema.ast)._tag, "Text") + }) + + it("defaults the response encoding to Json", () => { + assert.strictEqual(HttpApiSchema.getResponseEncoding(WrappedUserNotFound.ast)._tag, "Json") + }) + }) + it("does not identify buffered schemas as stream schemas", () => { assert.isFalse(HttpApiSchema.isStreamSchema(Schema.String)) assert.isFalse(HttpApiSchema.isStreamSchema(Schema.Uint8Array.pipe(HttpApiSchema.asUint8Array()))) diff --git a/repos/effect/packages/effect/test/unstable/httpapi/HttpApiSecurity.test.ts b/repos/effect/packages/effect/test/unstable/httpapi/HttpApiSecurity.test.ts index 6e56c49034..be4265b518 100644 --- a/repos/effect/packages/effect/test/unstable/httpapi/HttpApiSecurity.test.ts +++ b/repos/effect/packages/effect/test/unstable/httpapi/HttpApiSecurity.test.ts @@ -1,9 +1,9 @@ import { assert, describe, it } from "@effect/vitest" -import { Effect, Redacted } from "effect" +import { Effect, Encoding, Redacted } from "effect" import { HttpClientRequest, HttpServerRequest } from "effect/unstable/http" import { HttpApiBuilder, HttpApiSecurity } from "effect/unstable/httpapi" -const decode = (authorization: string, security: HttpApiSecurity.Http = HttpApiSecurity.bearer) => +const decode = (authorization: string, security: Security) => HttpApiBuilder.securityDecode(security).pipe( Effect.provideService( HttpServerRequest.HttpServerRequest, @@ -37,5 +37,46 @@ describe("HttpApiSecurity", () => { assert.strictEqual(Redacted.value(credential), "abc123") })) + + it.effect("matches HTTP schemes case-insensitively", () => + Effect.gen(function*() { + const credential = yield* decode("bEaReR abc123", HttpApiSecurity.bearer) + + assert.strictEqual(Redacted.value(credential), "abc123") + })) + + it.effect("accepts multiple spaces before HTTP credentials", () => + Effect.gen(function*() { + const credential = yield* decode("Bearer abc123", HttpApiSecurity.bearer) + + assert.strictEqual(Redacted.value(credential), "abc123") + })) + + it.effect("rejects mismatched and malformed HTTP schemes", () => + Effect.gen(function*() { + const mismatched = yield* decode("Basic abc123", HttpApiSecurity.bearer) + const malformed = yield* decode("Bearerabc123", HttpApiSecurity.bearer) + + assert.strictEqual(Redacted.value(mismatched), "") + assert.strictEqual(Redacted.value(malformed), "") + })) + + it.effect("decodes Basic credentials using the first colon separator", () => + Effect.gen(function*() { + const encoded = Encoding.encodeBase64("alice:secret:with:colons") + const credential = yield* decode(`Basic ${encoded}`, HttpApiSecurity.basic) + + assert.strictEqual(credential.username, "alice") + assert.strictEqual(Redacted.value(credential.password), "secret:with:colons") + })) + + it.effect("rejects Basic credentials from a different scheme", () => + Effect.gen(function*() { + const encoded = Encoding.encodeBase64("alice:secret") + const credential = yield* decode(`Bearer ${encoded}`, HttpApiSecurity.basic) + + assert.strictEqual(credential.username, "") + assert.strictEqual(Redacted.value(credential.password), "") + })) }) }) diff --git a/repos/effect/packages/effect/test/unstable/httpapi/OpenApi.test.ts b/repos/effect/packages/effect/test/unstable/httpapi/OpenApi.test.ts index 5b6dd0620a..b1d1b88030 100644 --- a/repos/effect/packages/effect/test/unstable/httpapi/OpenApi.test.ts +++ b/repos/effect/packages/effect/test/unstable/httpapi/OpenApi.test.ts @@ -1,8 +1,109 @@ import { assert, describe, it } from "@effect/vitest" -import { Schema } from "effect" -import { HttpApi, HttpApiEndpoint, HttpApiGroup, HttpApiSchema, OpenApi } from "effect/unstable/httpapi" +import { type Context, Schema } from "effect" +import { + HttpApi, + HttpApiEndpoint, + HttpApiGroup, + HttpApiMiddleware, + HttpApiSchema, + HttpApiSecurity, + OpenApi +} from "effect/unstable/httpapi" + +class HeaderAuthA extends HttpApiMiddleware.Service()("HeaderAuthA", { + security: { + auth: HttpApiSecurity.apiKey({ key: "x-api-key" }) + } +}) {} + +class HeaderAuthB extends HttpApiMiddleware.Service()("HeaderAuthB", { + security: { + auth: HttpApiSecurity.apiKey({ key: "x-api-key" }) + } +}) {} + +class HeaderAuthUpper extends HttpApiMiddleware.Service()("HeaderAuthUpper", { + security: { + auth: HttpApiSecurity.apiKey({ key: "X-API-Key" }) + } +}) {} + +class QueryAuth extends HttpApiMiddleware.Service()("QueryAuth", { + security: { + auth: HttpApiSecurity.apiKey({ key: "api_key", in: "query" }) + } +}) {} + +class TokenAuthUpper extends HttpApiMiddleware.Service()("TokenAuthUpper", { + security: { + auth: HttpApiSecurity.http({ scheme: "Token" }) + } +}) {} + +class TokenAuthLower extends HttpApiMiddleware.Service()("TokenAuthLower", { + security: { + auth: HttpApiSecurity.http({ scheme: "token" }) + } +}) {} + +class ProtoHeaderAuth extends HttpApiMiddleware.Service()("ProtoHeaderAuth", { + security: { + ["__proto__"]: HttpApiSecurity.apiKey({ key: "x-api-key" }) + } +}) {} + +class ProtoQueryAuth extends HttpApiMiddleware.Service()("ProtoQueryAuth", { + security: { + ["__proto__"]: HttpApiSecurity.apiKey({ key: "api_key", in: "query" }) + } +}) {} + +const makeSecurityApi = ( + first: Context.Key, + second: Context.Key +) => + HttpApi.make("Api").add( + HttpApiGroup.make("test").add( + HttpApiEndpoint.get("first", "/first").middleware(first), + HttpApiEndpoint.get("second", "/second").middleware(second) + ) + ) describe("OpenApi", () => { + it("preserves every declared payload content type for normalized equivalents", () => { + const profileA = "Application/Vnd.Effect+JSON; Profile=A" + const profileB = "application/vnd.effect+json; profile=b" + const Api = HttpApi.make("Api").add( + HttpApiGroup.make("test").add( + HttpApiEndpoint.post("create", "/create", { + payload: [ + Schema.Struct({ a: Schema.String }).pipe(HttpApiSchema.asJson({ contentType: profileA })), + Schema.Struct({ b: Schema.String }).pipe(HttpApiSchema.asJson({ contentType: profileB })) + ] + }) + ) + ) + + const spec = OpenApi.fromApi(Api) + const content = spec.paths["/create"]?.post?.requestBody?.content + + assert.isDefined(content) + assert.property(content, profileA) + assert.property(content, profileB) + assert.deepStrictEqual(content[profileA]?.schema, { + type: "object", + properties: { a: { type: "string" } }, + required: ["a"], + additionalProperties: false + }) + assert.deepStrictEqual(content[profileB]?.schema, { + type: "object", + properties: { b: { type: "string" } }, + required: ["b"], + additionalProperties: false + }) + }) + it("emits buffered and stream successes with the same status", () => { const Api = HttpApi.make("Api").add( HttpApiGroup.make("test").add( @@ -36,4 +137,274 @@ describe("OpenApi", () => { assert.property(streamExtension, "causeSchema") assert.property(streamExtension, "errorSchema") }) + + it("emits encoded success response headers", () => { + const Api = HttpApi.make("Api").add( + HttpApiGroup.make("test").add( + HttpApiEndpoint.get("success", "/success", { + success: HttpApiSchema.WithHeaders(Schema.String, { + "X-Count": Schema.FiniteFromString, + "X-Optional": Schema.optionalKey(Schema.FiniteFromString), + "Content-Type": Schema.String + }) + }) + ) + ) + + const spec = OpenApi.fromApi(Api) + + assert.deepStrictEqual(spec.paths["/success"]?.get?.responses[200]?.headers, { + "x-count": { + schema: { type: "string" }, + required: true + }, + "x-optional": { + schema: { type: "string" }, + required: false + } + }) + }) + + it("emits encoded error response headers", () => { + class NotFound extends Schema.TaggedError()("NotFound", { + id: Schema.Number + }) {} + const NotFoundWithHeaders = NotFound.pipe( + HttpApiSchema.encodeToWithHeaders({ + body: HttpApiSchema.Empty(404), + headers: { "X-Error-Id": Schema.FiniteFromString } + }, { + decode: ({ headers }) => new NotFound({ id: headers["X-Error-Id"] }), + encode: (error) => ({ body: undefined, headers: { "X-Error-Id": error.id } }) + }) + ) + const Api = HttpApi.make("Api").add( + HttpApiGroup.make("test").add( + HttpApiEndpoint.get("error", "/error", { + error: NotFoundWithHeaders + }) + ) + ) + + const spec = OpenApi.fromApi(Api) + + assert.deepStrictEqual(spec.paths["/error"]?.get?.responses[404]?.headers, { + "x-error-id": { + schema: { type: "string" }, + required: true + } + }) + }) + + it("emits encodeToWithHeaders wire schemas according to codec mode", () => { + const ErrorWithHeaders = Schema.Number.pipe( + HttpApiSchema.encodeToWithHeaders({ + body: HttpApiSchema.Empty(404), + headers: { "X-Error-Id": Schema.Int } + }, { + decode: ({ headers }) => headers["X-Error-Id"], + encode: (id) => ({ body: undefined, headers: { "X-Error-Id": id } }) + }) + ) + const Api = HttpApi.make("Api").add( + HttpApiGroup.make("test") + .add( + HttpApiEndpoint.get("encoded", "/encoded", { + error: ErrorWithHeaders + }) + ) + .add( + HttpApiEndpoint.get("unencoded", "/unencoded", { + disableCodecs: true, + error: ErrorWithHeaders + }) + ) + ) + + const spec = OpenApi.fromApi(Api) + + assert.deepStrictEqual( + spec.paths["/encoded"]?.get?.responses[404]?.headers?.["x-error-id"]?.schema, + { + type: "string", + allOf: [{ pattern: "^[+-]?\\d*\\.?\\d+(?:[Ee][+-]?\\d+)?$" }] + } + ) + assert.deepStrictEqual( + spec.paths["/unencoded"]?.get?.responses[404]?.headers?.["x-error-id"]?.schema, + { type: "integer" } + ) + }) + + it("emits headers for an error wrapped in WithHeaders", () => { + const Api = HttpApi.make("Api").add( + HttpApiGroup.make("test").add( + HttpApiEndpoint.get("error", "/error", { + error: HttpApiSchema.WithHeaders( + Schema.Struct({ message: Schema.String }).pipe(HttpApiSchema.status(429)), + { "X-Retry-After": Schema.Int } + ) + }) + ) + ) + + const spec = OpenApi.fromApi(Api) + + assert.deepStrictEqual(spec.paths["/error"]?.get?.responses[429]?.headers, { + "x-retry-after": { + schema: { + type: "string", + allOf: [{ pattern: "^[+-]?\\d*\\.?\\d+(?:[Ee][+-]?\\d+)?$" }] + }, + required: true + } + }) + }) + + it("emits stream response headers", () => { + const Api = HttpApi.make("Api").add( + HttpApiGroup.make("test").add( + HttpApiEndpoint.get("stream", "/stream", { + success: HttpApiSchema.WithHeaders(HttpApiSchema.StreamUint8Array(), { + "X-Stream-Id": Schema.Int + }) + }) + ) + ) + + const spec = OpenApi.fromApi(Api) + + assert.deepStrictEqual(spec.paths["/stream"]?.get?.responses[200]?.headers, { + "x-stream-id": { + schema: { + type: "string", + allOf: [{ pattern: "^[+-]?\\d*\\.?\\d+(?:[Ee][+-]?\\d+)?$" }] + }, + required: true + } + }) + }) + + it("rejects duplicate method and path pairs", () => { + const Api = HttpApi.make("Api").add( + HttpApiGroup.make("test").add( + HttpApiEndpoint.get("first", "/shared"), + HttpApiEndpoint.get("second", "/shared") + ) + ) + + assert.throws(() => OpenApi.fromApi(Api), /Duplicate OpenAPI operation for GET \/shared/) + }) + + it("rejects equivalent templated method and path pairs", () => { + const Api = HttpApi.make("Api").add( + HttpApiGroup.make("test").add( + HttpApiEndpoint.get("first", "/users/:id"), + HttpApiEndpoint.get("second", "/users/:userId") + ) + ) + + assert.throws(() => OpenApi.fromApi(Api), /Duplicate OpenAPI operation for GET \/users\/\{userId\}/) + }) + + it("rejects duplicate operation identifiers", () => { + const Api = HttpApi.make("Api").add( + HttpApiGroup.make("test").add( + HttpApiEndpoint.get("first", "/first").annotate(OpenApi.Identifier, "shared"), + HttpApiEndpoint.get("second", "/second").annotate(OpenApi.Identifier, "shared") + ) + ) + + assert.throws(() => OpenApi.fromApi(Api), /Duplicate OpenAPI operationId: shared/) + }) + + it("allows operations without identifiers", () => { + const removeOperationId = (operation: Record) => { + const transformed = { ...operation } + delete transformed.operationId + return transformed + } + const Api = HttpApi.make("Api").add( + HttpApiGroup.make("test").add( + HttpApiEndpoint.get("first", "/first").annotate(OpenApi.Transform, removeOperationId), + HttpApiEndpoint.get("second", "/second").annotate(OpenApi.Transform, removeOperationId) + ) + ) + + const spec = OpenApi.fromApi(Api) + const first = spec.paths["/first"]?.get + const second = spec.paths["/second"]?.get + + assert.isDefined(first) + assert.isDefined(second) + assert.isFalse(Object.hasOwn(first, "operationId")) + assert.isFalse(Object.hasOwn(second, "operationId")) + }) + + it("rejects incompatible security schemes with the same name", () => { + const Api = makeSecurityApi(HeaderAuthA, QueryAuth) + + assert.throws(() => OpenApi.fromApi(Api), /Conflicting OpenAPI security scheme: auth/) + }) + + it("deduplicates equivalent security schemes with the same name", () => { + const Api = makeSecurityApi(HeaderAuthA, HeaderAuthB) + + const spec = OpenApi.fromApi(Api) + + assert.deepStrictEqual(spec.components.securitySchemes.auth, { + type: "apiKey", + name: "x-api-key", + in: "header" + }) + }) + + it("deduplicates API key header names case-insensitively", () => { + const Api = makeSecurityApi(HeaderAuthUpper, HeaderAuthA) + + const spec = OpenApi.fromApi(Api) + + assert.deepStrictEqual(spec.components.securitySchemes.auth, { + type: "apiKey", + name: "X-API-Key", + in: "header" + }) + }) + + it("deduplicates HTTP security schemes case-insensitively", () => { + const Api = makeSecurityApi(TokenAuthUpper, TokenAuthLower) + + const spec = OpenApi.fromApi(Api) + + assert.deepStrictEqual(spec.components.securitySchemes.auth, { + type: "http", + scheme: "Token" + }) + }) + + it("stores __proto__ security scheme names as own properties", () => { + const Api = HttpApi.make("Api").add( + HttpApiGroup.make("test").add( + HttpApiEndpoint.get("first", "/first").middleware(ProtoHeaderAuth) + ) + ) + + const spec = OpenApi.fromApi(Api) + const schemes = spec.components.securitySchemes + + assert.isTrue(Object.hasOwn(schemes, "__proto__")) + assert.deepStrictEqual(Object.keys(schemes), ["__proto__"]) + assert.strictEqual(Object.getPrototypeOf(schemes), Object.prototype) + assert.deepStrictEqual(schemes["__proto__"], { + type: "apiKey", + name: "x-api-key", + in: "header" + }) + }) + + it("rejects incompatible __proto__ security schemes", () => { + const Api = makeSecurityApi(ProtoHeaderAuth, ProtoQueryAuth) + + assert.throws(() => OpenApi.fromApi(Api), /Conflicting OpenAPI security scheme: __proto__/) + }) }) diff --git a/repos/effect/packages/effect/test/unstable/httpapi/OpenApiRepresentation.test.ts b/repos/effect/packages/effect/test/unstable/httpapi/OpenApiRepresentation.test.ts new file mode 100644 index 0000000000..fb9b62c6b5 --- /dev/null +++ b/repos/effect/packages/effect/test/unstable/httpapi/OpenApiRepresentation.test.ts @@ -0,0 +1,134 @@ +import { assert, describe, it } from "@effect/vitest" +import { Schema } from "effect" +import { HttpApi, HttpApiEndpoint, HttpApiGroup, HttpApiSchema, OpenApi } from "effect/unstable/httpapi" + +describe("OpenApi representation v2 consumer", () => { + it("uses canonical JSON codecs for additional declaration schemas", () => { + const AdditionalDate = Schema.Date.annotate({ identifier: "AdditionalDate" }) + const Api = HttpApi.make("Api").annotate(HttpApi.AdditionalSchemas, [AdditionalDate]) + + assert.deepStrictEqual(OpenApi.fromApi(Api).components.schemas, { + AdditionalDate: { $ref: "#/components/schemas/AdditionalDateEncoded" }, + AdditionalDateEncoded: { type: "string" } + }) + }) + + it("uses canonical JSON codecs for response declaration schemas", () => { + const Api = HttpApi.make("Api").add( + HttpApiGroup.make("test").add( + HttpApiEndpoint.get("date", "/date", { success: Schema.Date }) + ) + ) + + assert.deepStrictEqual( + OpenApi.fromApi(Api).paths["/date"]?.get?.responses[200]?.content?.["application/json"]?.schema, + { type: "string" } + ) + }) + + it("deduplicates JSON encoding definitions across regular and SSE responses", () => { + const Content = Schema.Struct({ text: Schema.String }).annotate({ identifier: "Tool.Content" }) + const Api = HttpApi.make("Api").add( + HttpApiGroup.make("test").add( + HttpApiEndpoint.get("content", "/content", { + success: Schema.fromJsonString(Content) + }), + HttpApiEndpoint.get("stream", "/stream", { + success: HttpApiSchema.StreamSse({ data: Content }) + }) + ) + ) + const spec = OpenApi.fromApi(Api) + + assert.deepStrictEqual(spec.components.schemas, { + "Tool.ContentEncoded": { + type: "string", + contentMediaType: "application/json" + } + }) + assert.deepStrictEqual( + spec.paths["/content"]?.get?.responses[200]?.content?.["application/json"]?.schema, + { $ref: "#/components/schemas/Tool.ContentEncoded" } + ) + assert.deepStrictEqual( + spec.paths["/stream"]?.get?.responses[200]?.content?.["text/event-stream"]?.schema, + { + type: "object", + properties: { + id: { anyOf: [{ type: "string" }, { type: "null" }] }, + event: { type: "string" }, + data: { $ref: "#/components/schemas/Tool.ContentEncoded" } + }, + required: ["id", "event", "data"], + additionalProperties: false + } + ) + }) + + it("projects request and response schemas to the encoded side", () => { + const Api = HttpApi.make("Api").add( + HttpApiGroup.make("test").add( + HttpApiEndpoint.post("create", "/create", { + payload: Schema.FiniteFromString, + success: Schema.FiniteFromString + }) + ) + ) + + const spec = OpenApi.fromApi(Api) + + assert.deepStrictEqual( + spec.paths["/create"]?.post?.requestBody?.content["application/json"]?.schema, + { type: "string" } + ) + assert.deepStrictEqual( + spec.paths["/create"]?.post?.responses[200]?.content?.["application/json"]?.schema, + { type: "string" } + ) + }) + + it("uses custom JSON Schema compiler annotations", () => { + const CustomString = Schema.String.check(Schema.makeFilter((value) => value.length >= 2, { + representation: { + id: "test/openapi/minTwoCharacters", + payload: null + }, + toJsonSchema: () => ({ minLength: 2 }) + })) + const Api = HttpApi.make("Api").add( + HttpApiGroup.make("test").add( + HttpApiEndpoint.post("create", "/create", { payload: CustomString }) + ) + ) + + assert.deepStrictEqual( + OpenApi.fromApi(Api).paths["/create"]?.post?.requestBody?.content["application/json"]?.schema, + { + type: "string", + allOf: [{ minLength: 2 }] + } + ) + }) + + it("shares definitions and caches by API identity", () => { + const Shared = Schema.Struct({ value: Schema.FiniteFromString }).annotate({ identifier: "Shared" }) + const Api = HttpApi.make("Api").add( + HttpApiGroup.make("test").add( + HttpApiEndpoint.post("shared", "/shared", { + payload: Schema.Struct({ first: Shared, second: Shared }), + success: Shared + }) + ) + ) + + const first = OpenApi.fromApi(Api) + + assert.strictEqual(OpenApi.fromApi(Api), first) + assert.deepStrictEqual(first.components.schemas.Shared, { + type: "object", + properties: { value: { type: "string" } }, + required: ["value"], + additionalProperties: false + }) + }) +}) diff --git a/repos/effect/packages/effect/test/unstable/observability/OtlpEnvHeaders.test.ts b/repos/effect/packages/effect/test/unstable/observability/OtlpEnvHeaders.test.ts new file mode 100644 index 0000000000..bffa981bc7 --- /dev/null +++ b/repos/effect/packages/effect/test/unstable/observability/OtlpEnvHeaders.test.ts @@ -0,0 +1,19 @@ +import { assert, it } from "@effect/vitest" +import { ConfigProvider, Effect } from "effect" +import * as OtlpEnv from "effect/unstable/observability/internal/otlpEnv" + +it.effect("decodes percent-encoded OTLP header values", () => + Effect.gen(function*() { + const headers = yield* OtlpEnv.headers("TRACES").parse( + ConfigProvider.fromEnv({ + env: { + OTEL_EXPORTER_OTLP_TRACES_HEADERS: "authorization=Bearer%20token,x-comma=comma%2Cvalue" + } + }) + ) + + assert.deepStrictEqual(headers, { + authorization: "Bearer token", + "x-comma": "comma,value" + }) + })) diff --git a/repos/effect/packages/effect/test/unstable/observability/OtlpExporter.test.ts b/repos/effect/packages/effect/test/unstable/observability/OtlpExporter.test.ts index 73d84c76dd..072472de94 100644 --- a/repos/effect/packages/effect/test/unstable/observability/OtlpExporter.test.ts +++ b/repos/effect/packages/effect/test/unstable/observability/OtlpExporter.test.ts @@ -1,9 +1,9 @@ import { assert, describe, it } from "@effect/vitest" -import { Effect, Ref } from "effect" +import { Clock, ConfigProvider, Deferred, type Duration, Effect, Exit, Fiber, Layer, Metric, Ref, Scope } from "effect" import { TestClock } from "effect/testing" import { HttpBody, HttpClient, HttpClientResponse } from "effect/unstable/http" import type { HttpClientError } from "effect/unstable/http" -import { OtlpExporter } from "effect/unstable/observability" +import { OtlpExporter, OtlpLogger, OtlpMetrics, OtlpSerialization, OtlpTracer } from "effect/unstable/observability" const makeHttpClient = Effect.fnUntraced(function*(retryAfter: string | undefined) { const attempts = yield* Ref.make(0) @@ -28,81 +28,458 @@ const makeHttpClient = Effect.fnUntraced(function*(retryAfter: string | undefine return { attempts, httpClient } as const }) -const makeExporter = (httpClient: HttpClient.HttpClient) => +const makeExporter = ( + httpClient: HttpClient.HttpClient, + options?: { + readonly exportInterval?: Duration.Input + readonly maxBatchSize?: number | "disabled" + readonly shutdownTimeout?: Duration.Input + readonly body?: (data: Array) => HttpBody.HttpBody + } +) => + OtlpExporter.make({ + label: "OtlpExporterTest", + url: "http://localhost:4318/v1/logs", + headers: undefined, + exportInterval: options?.exportInterval ?? "1 hour", + maxBatchSize: options?.maxBatchSize ?? 1, + body: (data) => [options?.body?.(data) ?? HttpBody.empty, Effect.void], + shutdownTimeout: options?.shutdownTimeout ?? "1 second" + }).pipe( + Effect.provideService(HttpClient.HttpClient, httpClient), + Effect.provide(OtlpExporter.layerFlusher) + ) + +const makeExporterRaw = (maxBatchSize: number | "disabled" = 1) => OtlpExporter.make({ label: "OtlpExporterTest", url: "http://localhost:4318/v1/logs", headers: undefined, exportInterval: "1 hour", - maxBatchSize: 1, - body: () => HttpBody.empty, + maxBatchSize, + body: () => [HttpBody.empty, Effect.void], shutdownTimeout: "1 second" - }).pipe(Effect.provideService(HttpClient.HttpClient, httpClient)) + }) + +const makeStatusHttpClient = Effect.fnUntraced(function*(status: number) { + const attempts = yield* Ref.make(0) + const urls = yield* Ref.make>([]) + + const httpClient = HttpClient.makeWith( + Effect.fnUntraced(function*(requestEffect) { + const request = yield* requestEffect + yield* Ref.update(attempts, (attempts) => attempts + 1) + yield* Ref.update(urls, (urls) => [...urls, request.url]) + return HttpClientResponse.fromWeb(request, new Response(null, { status })) + }), + Effect.succeed as HttpClient.HttpClient.Preprocess + ) + + return { attempts, httpClient, urls } as const +}) const yieldNowN = (times: number) => Effect.forEach(Array.from({ length: times }), () => Effect.yieldNow, { discard: true }) +const makeControlledHttpClient = Effect.fnUntraced(function*(requestCount: number) { + const started = yield* Effect.forEach(Array.from({ length: requestCount }), () => Deferred.make()) + const releases = yield* Effect.forEach(Array.from({ length: requestCount }), () => Deferred.make()) + const interrupted = yield* Ref.make(0) + let requestIndex = 0 + + const httpClient = HttpClient.makeWith( + Effect.fnUntraced(function*(requestEffect) { + const request = yield* requestEffect + const index = requestIndex++ + yield* Deferred.succeed(started[index], undefined) + yield* Deferred.await(releases[index]).pipe( + Effect.onInterrupt(() => Ref.update(interrupted, (count) => count + 1)) + ) + return HttpClientResponse.fromWeb(request, new Response()) + }), + Effect.succeed as HttpClient.HttpClient.Preprocess + ) + + return { httpClient, interrupted, releases, started } as const +}) + describe("OtlpExporter", () => { + it.effect("allows an in-flight timer export to finish during shutdown", () => + Effect.gen(function*() { + const scope = yield* Scope.make() + const { httpClient, releases, started } = yield* makeControlledHttpClient(1) + const exporter = yield* makeExporter(httpClient, { + exportInterval: "1 second", + maxBatchSize: 10 + }).pipe(Scope.provide(scope)) + + exporter.push({ value: 1 }) + yield* TestClock.adjust("1 second") + yield* Deferred.await(started[0]) + + const closeFiber = yield* Effect.forkChild(Scope.close(scope, Exit.void)) + yield* Effect.yieldNow + assert.isUndefined(closeFiber.pollUnsafe()) + + yield* Deferred.succeed(releases[0], undefined) + yield* Fiber.join(closeFiber) + })) + + it.effect("waits for each periodic export before starting the next interval", () => + Effect.gen(function*() { + const scope = yield* Scope.make() + const { httpClient, releases, started } = yield* makeControlledHttpClient(3) + const exporter = yield* makeExporter(httpClient, { + exportInterval: "1 second", + maxBatchSize: "disabled" + }).pipe(Scope.provide(scope)) + + exporter.push({ value: 1 }) + yield* TestClock.adjust("1 second") + yield* Deferred.await(started[0]) + + yield* TestClock.adjust("1 second") + assert.isFalse(yield* Deferred.isDone(started[1])) + + yield* Deferred.succeed(releases[0], undefined) + yield* TestClock.adjust("1 second") + yield* Deferred.await(started[1]) + + const closeFiber = yield* Effect.forkChild(Scope.close(scope, Exit.void)) + yield* Deferred.await(started[2]) + yield* Deferred.succeed(releases[1], undefined) + yield* Deferred.succeed(releases[2], undefined) + yield* Fiber.join(closeFiber) + })) + + it.effect("allows an in-flight batch export to finish during shutdown", () => + Effect.gen(function*() { + const scope = yield* Scope.make() + const { httpClient, releases, started } = yield* makeControlledHttpClient(1) + const exporter = yield* makeExporter(httpClient).pipe(Scope.provide(scope)) + + exporter.push({ value: 1 }) + yield* Deferred.await(started[0]) + + const closeFiber = yield* Effect.forkChild(Scope.close(scope, Exit.void)) + yield* Effect.yieldNow + assert.isUndefined(closeFiber.pollUnsafe()) + + yield* Deferred.succeed(releases[0], undefined) + yield* Fiber.join(closeFiber) + })) + + it.effect("exports remaining telemetry concurrently and waits for all requests", () => + Effect.gen(function*() { + const scope = yield* Scope.make() + const { httpClient, releases, started } = yield* makeControlledHttpClient(2) + const batches: Array> = [] + const exporter = yield* makeExporter(httpClient, { + maxBatchSize: 2, + body(data) { + batches.push(data.map((item) => item.value)) + return HttpBody.empty + } + }).pipe(Scope.provide(scope)) + + exporter.push({ value: 1 }) + exporter.push({ value: 2 }) + yield* Deferred.await(started[0]) + exporter.push({ value: 3 }) + + const closeFiber = yield* Effect.forkChild(Scope.close(scope, Exit.void)) + yield* Deferred.await(started[1]) + assert.deepStrictEqual(batches, [[1, 2], [3]]) + + yield* Deferred.succeed(releases[0], undefined) + yield* Effect.yieldNow + assert.isUndefined(closeFiber.pollUnsafe()) + yield* Deferred.succeed(releases[1], undefined) + yield* Fiber.join(closeFiber) + })) + + it.effect("bounds shutdown waiting and interrupts remaining requests", () => + Effect.gen(function*() { + const scope = yield* Scope.make() + const { httpClient, interrupted, started } = yield* makeControlledHttpClient(1) + const exporter = yield* makeExporter(httpClient).pipe(Scope.provide(scope)) + + exporter.push({ value: 1 }) + yield* Deferred.await(started[0]) + + const closeFiber = yield* Effect.forkChild(Scope.close(scope, Exit.void)) + yield* TestClock.adjust("1 second") + yield* Fiber.join(closeFiber) + assert.strictEqual(yield* Ref.get(interrupted), 1) + })) + + it.effect("does not initiate or wait for delivery when disabled", () => + Effect.gen(function*() { + const scope = yield* Scope.make() + const blocked = yield* Deferred.make() + const blockedInterrupted = yield* Deferred.make() + const failed = yield* Deferred.make() + const attempts = yield* Ref.make(0) + const httpClient = HttpClient.makeWith( + Effect.fnUntraced(function*(requestEffect) { + const request = yield* requestEffect + const attempt = yield* Ref.updateAndGet(attempts, (count) => count + 1) + if (attempt === 1) { + yield* Deferred.succeed(blocked, undefined) + return yield* Effect.never.pipe( + Effect.onInterrupt(() => Deferred.succeed(blockedInterrupted, undefined)) + ) + } + yield* Deferred.succeed(failed, undefined) + return HttpClientResponse.fromWeb(request, new Response(null, { status: 400 })) + }), + Effect.succeed as HttpClient.HttpClient.Preprocess + ) + const exporter = yield* makeExporter(httpClient).pipe(Scope.provide(scope)) + + exporter.push({ value: 1 }) + yield* Deferred.await(blocked) + exporter.push({ value: 2 }) + yield* Deferred.await(failed) + yield* yieldNowN(3) + exporter.push({ value: 3 }) + + yield* Scope.close(scope, Exit.void) + yield* Deferred.await(blockedInterrupted) + assert.strictEqual(yield* Ref.get(attempts), 2) + })) + it.effect("retries status 429 with numeric retry-after delay", () => - Effect.scoped( - Effect.gen(function*() { - const { attempts, httpClient } = yield* makeHttpClient("2") - const exporter = yield* makeExporter(httpClient) + Effect.gen(function*() { + const { attempts, httpClient } = yield* makeHttpClient("2") + const exporter = yield* makeExporter(httpClient) - exporter.push({ value: 1 }) - yield* yieldNowN(3) + exporter.push({ value: 1 }) + yield* yieldNowN(3) - assert.strictEqual(yield* Ref.get(attempts), 1) + assert.strictEqual(yield* Ref.get(attempts), 1) - yield* TestClock.adjust("1 second") - yield* yieldNowN(2) - assert.strictEqual(yield* Ref.get(attempts), 1) + yield* TestClock.adjust("1 second") + yield* yieldNowN(2) + assert.strictEqual(yield* Ref.get(attempts), 1) - yield* TestClock.adjust("1 second") - yield* yieldNowN(2) - assert.strictEqual(yield* Ref.get(attempts), 2) - }) - )) + yield* TestClock.adjust("1 second") + yield* yieldNowN(2) + assert.strictEqual(yield* Ref.get(attempts), 2) + })) + + it.effect("retries status 429 with HTTP-date retry-after delay", () => + Effect.gen(function*() { + const { attempts, httpClient } = yield* makeHttpClient("Thu, 01 Jan 1970 00:01:00 GMT") + const exporter = yield* makeExporter(httpClient) + + exporter.push({ value: 1 }) + yield* yieldNowN(3) + + assert.strictEqual(yield* Ref.get(attempts), 1) + + yield* TestClock.adjust("5 seconds") + yield* yieldNowN(2) + assert.strictEqual(yield* Ref.get(attempts), 1) + + yield* TestClock.adjust("55 seconds") + yield* yieldNowN(2) + assert.strictEqual(yield* Ref.get(attempts), 2) + })) it.effect("uses fallback retry-after delay when header is non-numeric", () => - Effect.scoped( - Effect.gen(function*() { - const { attempts, httpClient } = yield* makeHttpClient("soon") - const exporter = yield* makeExporter(httpClient) + Effect.gen(function*() { + const { attempts, httpClient } = yield* makeHttpClient("soon") + const exporter = yield* makeExporter(httpClient) - exporter.push({ value: 1 }) - yield* yieldNowN(3) + exporter.push({ value: 1 }) + yield* yieldNowN(3) - assert.strictEqual(yield* Ref.get(attempts), 1) + assert.strictEqual(yield* Ref.get(attempts), 1) - yield* TestClock.adjust("4 seconds") - yield* yieldNowN(2) - assert.strictEqual(yield* Ref.get(attempts), 1) + yield* TestClock.adjust("4 seconds") + yield* yieldNowN(2) + assert.strictEqual(yield* Ref.get(attempts), 1) - yield* TestClock.adjust("1 second") - yield* yieldNowN(2) - assert.strictEqual(yield* Ref.get(attempts), 2) - }) - )) + yield* TestClock.adjust("1 second") + yield* yieldNowN(2) + assert.strictEqual(yield* Ref.get(attempts), 2) + })) it.effect("uses fallback retry-after delay when header is missing", () => - Effect.scoped( + Effect.gen(function*() { + const { attempts, httpClient } = yield* makeHttpClient(undefined) + const exporter = yield* makeExporter(httpClient) + + exporter.push({ value: 1 }) + yield* yieldNowN(3) + + assert.strictEqual(yield* Ref.get(attempts), 1) + + yield* TestClock.adjust("4 seconds") + yield* yieldNowN(2) + assert.strictEqual(yield* Ref.get(attempts), 1) + + yield* TestClock.adjust("1 second") + yield* yieldNowN(2) + assert.strictEqual(yield* Ref.get(attempts), 2) + })) + + describe("flush", () => { + it.effect("exports buffered items without advancing to the export interval", () => Effect.gen(function*() { - const { attempts, httpClient } = yield* makeHttpClient(undefined) - const exporter = yield* makeExporter(httpClient) + const { attempts, httpClient } = yield* makeStatusHttpClient(200) + yield* Effect.scoped( + Effect.gen(function*() { + const exporter = yield* makeExporterRaw(10) + const flusher = yield* OtlpExporter.Flusher + const before = yield* Clock.currentTimeMillis + + exporter.push({ value: 1 }) + yield* flusher.flush + + assert.strictEqual(yield* Clock.currentTimeMillis, before) + assert.strictEqual(yield* Ref.get(attempts), 1) + }).pipe( + Effect.provideService(HttpClient.HttpClient, httpClient), + Effect.provide(OtlpExporter.layerFlusher) + ) + ) + })) - exporter.push({ value: 1 }) - yield* yieldNowN(3) + it.effect("shares one registry across traces, logs and metrics", () => + Effect.gen(function*() { + const { httpClient, urls } = yield* makeStatusHttpClient(200) + const signals = Layer.mergeAll( + OtlpTracer.layer({ + url: "http://localhost:4318/v1/traces", + resource: { serviceName: "test" }, + exportInterval: "1 hour", + maxBatchSize: 100 + }), + OtlpLogger.layer({ + url: "http://localhost:4318/v1/logs", + resource: { serviceName: "test" }, + exportInterval: "1 hour", + maxBatchSize: 100, + mergeWithExisting: false + }), + OtlpMetrics.layer({ + url: "http://localhost:4318/v1/metrics", + resource: { serviceName: "test" }, + exportInterval: "1 hour" + }) + ).pipe( + Layer.provide(OtlpSerialization.layerJson), + Layer.provideMerge(Layer.succeed(HttpClient.HttpClient, httpClient)) + ) + yield* Effect.gen(function*() { + yield* Effect.log("flush test") + yield* Metric.update(Metric.counter("otlp_flush_test"), 1) + yield* Effect.void.pipe(Effect.withSpan("flush test")) + + const flusher = yield* OtlpExporter.Flusher + yield* flusher.flush + + assert.deepStrictEqual( + [...yield* Ref.get(urls)].sort(), + [ + "http://localhost:4318/v1/logs", + "http://localhost:4318/v1/metrics", + "http://localhost:4318/v1/traces" + ] + ) + }).pipe(Effect.provide(signals)) + })) + + it.effect("is a no-op while the exporter is disabled", () => + Effect.gen(function*() { + const { attempts, httpClient } = yield* makeStatusHttpClient(400) + yield* Effect.scoped( + Effect.gen(function*() { + const exporter = yield* makeExporterRaw(10) + const flusher = yield* OtlpExporter.Flusher + + exporter.push({ value: 1 }) + yield* flusher.flush + assert.strictEqual(yield* Ref.get(attempts), 1) + + exporter.push({ value: 2 }) + yield* flusher.flush + assert.strictEqual(yield* Ref.get(attempts), 1) + }).pipe( + Effect.provideService(HttpClient.HttpClient, httpClient), + Effect.provide(OtlpExporter.layerFlusher) + ) + ) + })) + + it.effect("deregisters an exporter when its scope closes", () => + Effect.gen(function*() { + const { attempts, httpClient } = yield* makeStatusHttpClient(200) + const flusher = yield* OtlpExporter.Flusher + const scope = yield* Scope.make() + + yield* makeExporterRaw("disabled").pipe( + Effect.provideService(HttpClient.HttpClient, httpClient), + Effect.provideService(Scope.Scope, scope) + ) + + yield* Scope.close(scope, Exit.void) assert.strictEqual(yield* Ref.get(attempts), 1) - yield* TestClock.adjust("4 seconds") - yield* yieldNowN(2) + yield* flusher.flush assert.strictEqual(yield* Ref.get(attempts), 1) + }).pipe(Effect.provide(OtlpExporter.layerFlusher))) - yield* TestClock.adjust("1 second") - yield* yieldNowN(2) - assert.strictEqual(yield* Ref.get(attempts), 2) - }) - )) + it.effect("succeeds with no registered exporters", () => + Effect.gen(function*() { + const flusher = yield* OtlpExporter.Flusher + yield* flusher.flush + }).pipe(Effect.provide(OtlpExporter.layerFlusher))) + + it.effect("is available when the SDK is disabled", () => + Effect.gen(function*() { + const { attempts, httpClient } = yield* makeStatusHttpClient(200) + const layer = OtlpTracer.layerFromConfig().pipe( + Layer.provide(OtlpSerialization.layerJson), + Layer.provideMerge(Layer.succeed(HttpClient.HttpClient, httpClient)), + Layer.provideMerge(ConfigProvider.layer(ConfigProvider.fromEnv({ + env: { OTEL_SDK_DISABLED: "true" } + }))) + ) + + yield* Effect.gen(function*() { + const flusher = yield* OtlpExporter.Flusher + yield* flusher.flush + }).pipe(Effect.provide(layer)) + + assert.strictEqual(yield* Ref.get(attempts), 0) + })) + + it.effect("does not fail when the collector returns 500", () => + Effect.gen(function*() { + const { attempts, httpClient } = yield* makeStatusHttpClient(500) + yield* Effect.scoped( + Effect.gen(function*() { + const exporter = yield* makeExporterRaw(10) + const flusher = yield* OtlpExporter.Flusher + + exporter.push({ value: 1 }) + const fiber = yield* Effect.forkChild(flusher.flush) + yield* yieldNowN(3) + yield* TestClock.adjust("3 seconds") + yield* Fiber.join(fiber) + assert.strictEqual(yield* Ref.get(attempts), 4) + + yield* flusher.flush + assert.strictEqual(yield* Ref.get(attempts), 4) + }).pipe( + Effect.provideService(HttpClient.HttpClient, httpClient), + Effect.provide(OtlpExporter.layerFlusher) + ) + ) + })) + }) }) diff --git a/repos/effect/packages/effect/test/unstable/observability/OtlpMetrics.test.ts b/repos/effect/packages/effect/test/unstable/observability/OtlpMetrics.test.ts index 328db59228..510a0a0fc2 100644 --- a/repos/effect/packages/effect/test/unstable/observability/OtlpMetrics.test.ts +++ b/repos/effect/packages/effect/test/unstable/observability/OtlpMetrics.test.ts @@ -1,10 +1,134 @@ import { assert, describe, it } from "@effect/vitest" -import { Array, Context, Effect, Layer, Metric, Predicate, Ref } from "effect" +import { Array, Context, Deferred, Effect, Fiber, Layer, Metric, Predicate, Ref } from "effect" import { TestClock } from "effect/testing" import { HttpClient, type HttpClientError, HttpClientResponse } from "effect/unstable/http" -import { OtlpMetrics, OtlpSerialization } from "effect/unstable/observability" +import { OtlpExporter, OtlpMetrics, OtlpSerialization } from "effect/unstable/observability" describe("OtlpMetrics", () => { + it.effect("retains delta checkpoints after a failed export", () => + Effect.gen(function*() { + const bodies = yield* Ref.make>([]) + const attempts = yield* Ref.make(0) + const client = HttpClient.makeWith( + Effect.fnUntraced(function*(requestEffect) { + const request = yield* requestEffect + if (request.body._tag === "Uint8Array") { + const body = JSON.parse(new TextDecoder().decode(request.body.body)) as OtlpExportRequest + yield* Ref.update(bodies, Array.append(body)) + } + const attempt = yield* Ref.updateAndGet(attempts, (n) => n + 1) + return HttpClientResponse.fromWeb(request, new Response(null, { status: attempt === 1 ? 400 : 200 })) + }), + Effect.succeed as HttpClient.HttpClient.Preprocess + ) + const layer = OtlpMetrics.layer({ + url: "http://localhost:4318/v1/metrics", + resource: { serviceName: "repro" }, + temporality: "delta", + exportInterval: "1 hour" + }).pipe( + Layer.provide(OtlpSerialization.layerJson), + Layer.provideMerge(Layer.succeed(HttpClient.HttpClient, client)) + ) + yield* Effect.gen(function*() { + yield* Metric.update(Metric.counter("repro_counter"), 5) + const histogram = Metric.histogram("repro_histogram", { boundaries: [10, 50, 100] }) + yield* Metric.update(histogram, 25) + yield* Metric.update(histogram, 75) + const frequency = Metric.frequency("repro_frequency") + yield* Metric.update(frequency, "a") + yield* Metric.update(frequency, "a") + yield* Metric.update(frequency, "b") + const summary = Metric.summary("repro_summary", { + maxAge: "1 minute", + maxSize: 100, + quantiles: [0.5] + }) + yield* Metric.update(summary, 10) + yield* Metric.update(summary, 20) + yield* Metric.update(summary, 30) + const flusher = yield* OtlpExporter.Flusher + yield* flusher.flush + yield* TestClock.adjust("60 seconds") + yield* flusher.flush + }).pipe(Effect.provide(layer), Effect.provideService(Metric.MetricRegistry, new Map())) + const [first, second] = yield* Ref.get(bodies) + assert.strictEqual(findMetric(first, "repro_counter")?.sum?.dataPoints[0].asDouble, 5) + assert.strictEqual(findMetric(second, "repro_counter")?.sum?.dataPoints[0].asDouble, 5) + assert.strictEqual(findMetric(first, "repro_histogram")?.histogram?.dataPoints[0].count, 2) + assert.strictEqual(findMetric(second, "repro_histogram")?.histogram?.dataPoints[0].count, 2) + assert.strictEqual(findMetric(first, "repro_histogram")?.histogram?.dataPoints[0].sum, 100) + assert.strictEqual(findMetric(second, "repro_histogram")?.histogram?.dataPoints[0].sum, 100) + assert.strictEqual(findFrequencyValue(first, "repro_frequency", "a"), 2) + assert.strictEqual(findFrequencyValue(second, "repro_frequency", "a"), 2) + assert.strictEqual(findFrequencyValue(first, "repro_frequency", "b"), 1) + assert.strictEqual(findFrequencyValue(second, "repro_frequency", "b"), 1) + assert.strictEqual(findMetric(first, "repro_summary_count")?.sum?.dataPoints[0].asInt, 3) + assert.strictEqual(findMetric(second, "repro_summary_count")?.sum?.dataPoints[0].asInt, 3) + assert.strictEqual(findMetric(first, "repro_summary_sum")?.sum?.dataPoints[0].asDouble, 60) + assert.strictEqual(findMetric(second, "repro_summary_sum")?.sum?.dataPoints[0].asDouble, 60) + assert.strictEqual( + findMetric(second, "repro_counter")?.sum?.dataPoints[0].startTimeUnixNano, + findMetric(first, "repro_counter")?.sum?.dataPoints[0].startTimeUnixNano + ) + })) + + it.effect("does not regress delta checkpoints when exports complete out of order", () => + Effect.scoped(Effect.gen(function*() { + const bodies = yield* Ref.make>([]) + const started = yield* Effect.forEach([0, 1], () => Deferred.make()) + const releases = yield* Effect.forEach([0, 1], () => Deferred.make()) + let requestIndex = 0 + const client = HttpClient.makeWith( + Effect.fnUntraced(function*(requestEffect) { + const request = yield* requestEffect + if (request.body._tag === "Uint8Array") { + const body = JSON.parse(new TextDecoder().decode(request.body.body)) as OtlpExportRequest + yield* Ref.update(bodies, Array.append(body)) + } + const index = requestIndex++ + if (index < 2) { + yield* Deferred.succeed(started[index], undefined) + yield* Deferred.await(releases[index]) + } + return HttpClientResponse.fromWeb(request, new Response()) + }), + Effect.succeed as HttpClient.HttpClient.Preprocess + ) + const layer = OtlpMetrics.layer({ + url: "http://localhost:4318/v1/metrics", + resource: { serviceName: "repro" }, + temporality: "delta", + exportInterval: "1 hour" + }).pipe( + Layer.provide(OtlpSerialization.layerJson), + Layer.provideMerge(Layer.succeed(HttpClient.HttpClient, client)) + ) + yield* Effect.gen(function*() { + const counter = Metric.counter("concurrent_delta") + const flusher = yield* OtlpExporter.Flusher + yield* Metric.update(counter, 1) + const first = yield* Effect.forkChild(flusher.flush) + yield* Deferred.await(started[0]) + + yield* TestClock.adjust("1 second") + yield* Metric.update(counter, 1) + const second = yield* Effect.forkChild(flusher.flush) + yield* Deferred.await(started[1]) + + yield* Deferred.succeed(releases[1], undefined) + yield* Fiber.join(second) + yield* Deferred.succeed(releases[0], undefined) + yield* Fiber.join(first) + + yield* TestClock.adjust("1 second") + yield* flusher.flush + }).pipe(Effect.provide(layer), Effect.provideService(Metric.MetricRegistry, new Map())) + + const requests = yield* Ref.get(bodies) + assert.strictEqual(findMetric(requests[2], "concurrent_delta")?.sum?.dataPoints[0].asDouble, 0) + }))) + describe("cumulative temporality", () => { it.effect("reports counter totals across export intervals", () => Effect.gen(function*() { @@ -516,3 +640,12 @@ const findMetric = (request: OtlpExportRequest, name: string): OtlpMetric | unde } return undefined } + +const findFrequencyValue = (request: OtlpExportRequest, name: string, key: string): number | undefined => + findMetric(request, name)?.sum?.dataPoints.find((dataPoint) => + dataPoint.attributes.some((attribute) => + attribute.key === "key" && + Predicate.hasProperty(attribute.value, "stringValue") && + attribute.value.stringValue === key + ) + )?.asInt diff --git a/repos/effect/packages/effect/test/unstable/observability/OtlpResource.test.ts b/repos/effect/packages/effect/test/unstable/observability/OtlpResource.test.ts index 5ca8967569..fec532a9ff 100644 --- a/repos/effect/packages/effect/test/unstable/observability/OtlpResource.test.ts +++ b/repos/effect/packages/effect/test/unstable/observability/OtlpResource.test.ts @@ -7,20 +7,43 @@ const attributesRecord = (resource: OtlpResource.Resource): Record { describe("fromConfig", () => { - it.effect("uses OTEL service variables before explicit options", () => + it.effect("decodes percent-encoded OTEL_RESOURCE_ATTRIBUTES", () => + Effect.gen(function*() { + const resource = yield* OtlpResource.fromConfig() + const attributes = Object.fromEntries( + resource.attributes.map((attribute) => [attribute.key, attribute.value.stringValue]) + ) + + assert.strictEqual(attributes.message, "hello world") + assert.strictEqual(attributes.comma, "comma,value") + }).pipe( + Effect.provideService( + ConfigProvider.ConfigProvider, + ConfigProvider.fromEnv({ + env: { + OTEL_SERVICE_NAME: "repro", + OTEL_RESOURCE_ATTRIBUTES: "message=hello%20world,comma=comma%2Cvalue" + } + }) + ) + )) + + it.effect("uses explicit service options before attributes and environment variables", () => Effect.gen(function*() { const resource = yield* OtlpResource.fromConfig({ serviceName: "explicit-service", serviceVersion: "explicit-version", attributes: { - "custom.attribute": "explicit" + "custom.attribute": "explicit", + "service.name": "explicit-attribute-service", + "service.version": "explicit-attribute-version" } }) assert.deepStrictEqual(attributesRecord(resource), { "custom.attribute": "explicit", - "service.name": "env-service", - "service.version": "env-version" + "service.name": "explicit-service", + "service.version": "explicit-version" }) }).pipe( Effect.provideService( @@ -28,17 +51,16 @@ describe("OtlpResource", () => { ConfigProvider.fromEnv({ env: { OTEL_SERVICE_NAME: "env-service", - OTEL_SERVICE_VERSION: "env-version" + OTEL_SERVICE_VERSION: "env-version", + OTEL_RESOURCE_ATTRIBUTES: "service.name=env-attribute-service,service.version=env-attribute-version" } }) ) )) - it.effect("uses OTEL resource attributes before explicit options", () => + it.effect("uses explicit attributes before environment variables", () => Effect.gen(function*() { const resource = yield* OtlpResource.fromConfig({ - serviceName: "explicit-service", - serviceVersion: "explicit-version", attributes: { "custom.attribute": "explicit", "service.name": "explicit-attribute-service", @@ -47,15 +69,17 @@ describe("OtlpResource", () => { }) assert.deepStrictEqual(attributesRecord(resource), { - "custom.attribute": "env", - "service.name": "env-attribute-service", - "service.version": "env-attribute-version" + "custom.attribute": "explicit", + "service.name": "explicit-attribute-service", + "service.version": "explicit-attribute-version" }) }).pipe( Effect.provideService( ConfigProvider.ConfigProvider, ConfigProvider.fromEnv({ env: { + OTEL_SERVICE_NAME: "env-service", + OTEL_SERVICE_VERSION: "env-version", OTEL_RESOURCE_ATTRIBUTES: "service.name=env-attribute-service,service.version=env-attribute-version,custom.attribute=env" } @@ -63,6 +87,27 @@ describe("OtlpResource", () => { ) )) + it.effect("uses dedicated service variables before OTEL resource attributes", () => + Effect.gen(function*() { + const resource = yield* OtlpResource.fromConfig() + + assert.deepStrictEqual(attributesRecord(resource), { + "service.name": "env-service", + "service.version": "env-version" + }) + }).pipe( + Effect.provideService( + ConfigProvider.ConfigProvider, + ConfigProvider.fromEnv({ + env: { + OTEL_SERVICE_NAME: "env-service", + OTEL_SERVICE_VERSION: "env-version", + OTEL_RESOURCE_ATTRIBUTES: "service.name=env-attribute-service,service.version=env-attribute-version" + } + }) + ) + )) + it.effect("omits service.version when it is not configured", () => Effect.gen(function*() { const resource = yield* OtlpResource.fromConfig({ @@ -81,4 +126,13 @@ describe("OtlpResource", () => { ) )) }) + + describe("unknownToAttributeValue", () => { + it("preserves bigint attribute precision", () => { + const input = 9_007_199_254_740_993n + const output = OtlpResource.unknownToAttributeValue(input) + + assert.strictEqual(String(output.intValue), input.toString()) + }) + }) }) diff --git a/repos/effect/packages/effect/test/unstable/observability/OtlpSerialization.test.ts b/repos/effect/packages/effect/test/unstable/observability/OtlpSerialization.test.ts new file mode 100644 index 0000000000..44b04d8049 --- /dev/null +++ b/repos/effect/packages/effect/test/unstable/observability/OtlpSerialization.test.ts @@ -0,0 +1,10 @@ +import { assert, describe, it } from "@effect/vitest" +import { encodeAnyValue } from "effect/unstable/observability/internal/otlpProtobuf" + +describe("OtlpSerialization", () => { + it("encodes a negative protobuf int64 as a ten-byte varint", () => { + const encoded = encodeAnyValue({ intValue: -1 }) + assert.strictEqual(encoded.length, 11) + assert.deepStrictEqual(Array.from(encoded), [0x18, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01]) + }) +}) diff --git a/repos/effect/packages/effect/test/unstable/persistence/KeyValueStore.test.ts b/repos/effect/packages/effect/test/unstable/persistence/KeyValueStore.test.ts index 2c78422efa..d0def431f9 100644 --- a/repos/effect/packages/effect/test/unstable/persistence/KeyValueStore.test.ts +++ b/repos/effect/packages/effect/test/unstable/persistence/KeyValueStore.test.ts @@ -1,8 +1,8 @@ import { afterEach, describe, it } from "@effect/vitest" import { assertTrue, deepStrictEqual, strictEqual } from "@effect/vitest/utils" -import type { Layer } from "effect" -import { Effect, Option, Schema } from "effect" +import { Effect, type Layer, Option, Schema } from "effect" import * as KeyValueStore from "effect/unstable/persistence/KeyValueStore" +import * as Persistence from "effect/unstable/persistence/Persistence" export const testLayer = (layer: Layer.Layer) => { const run = (effect: Effect.Effect) => @@ -84,12 +84,27 @@ export const testLayer = (layer: Layer.Layer) strictEqual(value, undefined) strictEqual(length, 0) }))) + + it("setMany stores entries without a TTL", () => + run( + Effect.gen(function*() { + const backing = yield* Persistence.BackingPersistence + const store = yield* backing.make("store") + + yield* store.setMany([["key", { value: 1 }, undefined]]) + + deepStrictEqual(yield* store.get("key"), { value: 1 }) + }).pipe( + Effect.scoped, + Effect.provide(Persistence.layerBackingKvs) + ) + )) } describe("KeyValueStore / layerMemory", () => testLayer(KeyValueStore.layerMemory)) describe("KeyValueStore / prefix", () => { - it("prefixes the keys", () => + it.effect("prefixes the keys", () => Effect.gen(function*() { const store = yield* (KeyValueStore.KeyValueStore) const prefixed = KeyValueStore.prefix(store, "prefix/") @@ -103,8 +118,7 @@ describe("KeyValueStore / prefix", () => { strictEqual(yield* (store.get("prefix/foo")), "barbar") assertTrue(yield* (store.has("prefix/foo"))) }).pipe( - Effect.provide(KeyValueStore.layerMemory), - Effect.runPromise + Effect.provide(KeyValueStore.layerMemory) )) }) @@ -114,7 +128,7 @@ describe("toSchemaStore", () => { age: Schema.Number }) {} - it("encodes & decodes", () => + it.effect("encodes & decodes", () => Effect.gen(function*() { const store = yield* KeyValueStore.KeyValueStore const schemaStore = KeyValueStore.toSchemaStore(store, User) @@ -126,11 +140,10 @@ describe("toSchemaStore", () => { strictEqual(value.value.name, "foo") strictEqual(value.value.age, 43) }).pipe( - Effect.provide(KeyValueStore.layerMemory), - Effect.runPromise + Effect.provide(KeyValueStore.layerMemory) )) - it("prefix", () => + it.effect("prefix", () => Effect.gen(function*() { const store = yield* KeyValueStore.KeyValueStore const schemaStore = KeyValueStore.toSchemaStore(store, User) @@ -142,11 +155,10 @@ describe("toSchemaStore", () => { strictEqual(value.value.age, 42) } }).pipe( - Effect.provide(KeyValueStore.layerMemory), - Effect.runPromise + Effect.provide(KeyValueStore.layerMemory) )) - it("json compliant", () => + it.effect("json compliant", () => Effect.gen(function*() { const store = yield* KeyValueStore.KeyValueStore const schema = Schema.Struct({ @@ -158,7 +170,6 @@ describe("toSchemaStore", () => { assertTrue(Option.isSome(value)) deepStrictEqual(value.value.a, new Date(0)) }).pipe( - Effect.provide(KeyValueStore.layerMemory), - Effect.runPromise + Effect.provide(KeyValueStore.layerMemory) )) }) diff --git a/repos/effect/packages/effect/test/unstable/persistence/PersistedCacheTest.ts b/repos/effect/packages/effect/test/unstable/persistence/PersistedCacheTest.ts index e5a996f18c..318c4444a5 100644 --- a/repos/effect/packages/effect/test/unstable/persistence/PersistedCacheTest.ts +++ b/repos/effect/packages/effect/test/unstable/persistence/PersistedCacheTest.ts @@ -1,6 +1,7 @@ -import { assert, describe, it } from "@effect/vitest" -import type { Layer } from "effect" -import { Context, Data, Effect, Exit, Result, Schema } from "effect" +import { assert, it } from "@effect/vitest" +import type { Vitest } from "@effect/vitest" +import type { Duration, Layer } from "effect" +import { Context, Data, Effect, Exit, Schema } from "effect" import { Persistable, PersistedCache, Persistence } from "effect/unstable/persistence" class User extends Schema.Class("User")({ @@ -20,8 +21,13 @@ export class TransientError extends Data.TaggedError("TransientError") {} class LookupService extends Context.Service()("LookupService") {} -export const suite = (storeId: string, layer: Layer.Layer) => - describe(`PersistedCache (${storeId})`, { timeout: 30_000 }, () => { +export const suiteWith = ( + storeId: string, + layer: Layer.Layer, + testApi: Vitest.MethodsNonLive, + timeout: Duration.Input = "60 seconds" +) => + testApi.layer(layer, { timeout })(`PersistedCache (${storeId})`, (it) => { it.effect("smoke test", () => Effect.gen(function*() { const persistence = yield* Persistence.Persistence @@ -65,11 +71,7 @@ export const suite = (storeId: string, layer: Layer.Layer e instanceof TransientError ? Result.succeed(e) : Result.fail(e), () => Effect.void), - flakyTest - )) + }), 30_000) it.effect("requireServicesAt: 'lookup' requires lookup services at get-time", () => Effect.gen(function*() { @@ -95,17 +97,8 @@ export const suite = (storeId: string, layer: Layer.Layer e instanceof TransientError ? Result.succeed(e) : Result.fail(e), () => Effect.void), - flakyTest - )) + }), 30_000) }) -const flakyTest = (effect: Effect.Effect) => - effect.pipe( - Effect.timeoutOrElse({ - duration: "10 seconds", - orElse: () => Effect.void - }) - ) +export const suite = (storeId: string, layer: Layer.Layer) => + suiteWith(storeId, layer, it) diff --git a/repos/effect/packages/effect/test/unstable/persistence/PersistedQueueTest.ts b/repos/effect/packages/effect/test/unstable/persistence/PersistedQueueTest.ts index 894044d535..c897e7f5fe 100644 --- a/repos/effect/packages/effect/test/unstable/persistence/PersistedQueueTest.ts +++ b/repos/effect/packages/effect/test/unstable/persistence/PersistedQueueTest.ts @@ -1,14 +1,21 @@ import { assert, it } from "@effect/vitest" +import type { Vitest } from "@effect/vitest" import { Effect, Fiber, Latch, Layer, Schema } from "effect" +import type { Duration } from "effect" import { TestClock } from "effect/testing" import { PersistedQueue } from "effect/unstable/persistence" -export const suite = (name: string, layer: Layer.Layer) => - it.layer( +export const suiteWith = ( + name: string, + layer: Layer.Layer, + testApi: Vitest.MethodsNonLive, + timeout: Duration.Input = "30 seconds" +) => + testApi.layer( PersistedQueue.layer.pipe( Layer.provideMerge(layer) ), - { timeout: "30 seconds" } + { timeout } )(`PersistedQueue (${name})`, (it) => { it.effect("offer + take", () => Effect.gen(function*() { @@ -97,6 +104,111 @@ export const suite = (name: string, layer: Layer.Layer + Effect.gen(function*() { + const first = yield* PersistedQueue.make({ name: "custom-id-first", schema: Item }) + const second = yield* PersistedQueue.make({ name: "custom-id-second", schema: Item }) + + yield* first.offer({ n: 1n }, { id: "shared-custom-id" }) + yield* second.offer({ n: 2n }, { id: "shared-custom-id" }) + + const fiber = yield* second.take(Effect.succeed).pipe(Effect.forkScoped) + yield* TestClock.adjust(1000) + yield* Effect.sleep(1000).pipe(TestClock.withLive) + + assert.isDefined(fiber.pollUnsafe()) + assert.deepStrictEqual(yield* Fiber.join(fiber), { n: 2n }) + })) + + it.effect("does not redeliver in-flight elements", () => + Effect.gen(function*() { + const queue = yield* PersistedQueue.make({ + name: "test-queue-reset", + schema: Item + }) + + yield* queue.offer({ n: 42n }) + + const taken = Latch.makeUnsafe() + const release = Latch.makeUnsafe() + const fiber = yield* queue.take(() => Effect.andThen(taken.open, release.await)).pipe(Effect.forkScoped) + yield* taken.await + + const fiber2 = yield* queue.take((val) => Effect.succeed(val)).pipe(Effect.forkScoped) + + // allow any periodic reset in the store to run while the element is + // still being processed + yield* TestClock.adjust(1000) + yield* Effect.sleep(1000).pipe( + TestClock.withLive + ) + + assert.isUndefined(fiber2.pollUnsafe()) + + // after a successful take the element should not be delivered again + yield* release.open + yield* Fiber.join(fiber) + yield* TestClock.adjust(1000) + yield* Effect.sleep(1000).pipe( + TestClock.withLive + ) + + assert.isUndefined(fiber2.pollUnsafe()) + + yield* Fiber.interrupt(fiber2) + })) + + it.effect("stops delivering when maxAttempts is exhausted", () => + Effect.gen(function*() { + const queue = yield* PersistedQueue.make({ + name: "test-queue-exhausted", + schema: Item + }) + + yield* queue.offer({ n: 42n }) + + const error = yield* queue + .take(() => Effect.fail("boom"), { maxAttempts: 1 }) + .pipe(Effect.flip) + + assert.strictEqual(error, "boom") + + const fiber = yield* queue.take((val) => Effect.succeed(val), { maxAttempts: 1 }).pipe(Effect.forkScoped) + + yield* TestClock.adjust(1000) + yield* Effect.sleep(1000).pipe( + TestClock.withLive + ) + + assert.isUndefined(fiber.pollUnsafe()) + })) + + it.effect("counts schema decode failures as attempts", () => + Effect.gen(function*() { + const store = yield* PersistedQueue.PersistedQueueStore + const queue = yield* PersistedQueue.make({ + name: "test-queue-decode-failure", + schema: Item + }) + + yield* store.offer({ + name: "test-queue-decode-failure", + id: crypto.randomUUID(), + element: { n: null }, + isCustomId: false + }) + + const error = yield* queue.take(Effect.succeed, { maxAttempts: 1 }).pipe(Effect.flip) + assert.isTrue(Schema.isSchemaError(error)) + + const fiber = yield* queue.take(Effect.succeed, { maxAttempts: 1 }).pipe(Effect.forkScoped) + + yield* TestClock.adjust(1000) + yield* Effect.sleep(1000).pipe(TestClock.withLive) + assert.isUndefined(fiber.pollUnsafe()) })) }) @@ -104,3 +216,6 @@ export const suite = (name: string, layer: Layer.Layer) => + suiteWith(name, layer, it) diff --git a/repos/effect/packages/effect/test/unstable/persistence/RateLimiter.test.ts b/repos/effect/packages/effect/test/unstable/persistence/RateLimiter.test.ts index 3f82770f74..7117d58ffa 100644 --- a/repos/effect/packages/effect/test/unstable/persistence/RateLimiter.test.ts +++ b/repos/effect/packages/effect/test/unstable/persistence/RateLimiter.test.ts @@ -4,6 +4,37 @@ import { TestClock } from "effect/testing" import { RateLimiter } from "effect/unstable/persistence" describe(`RateLimiter`, () => { + it.effect("supports partially applied sleep", () => + Effect.gen(function*() { + const limiter = yield* RateLimiter.make + const sleep = RateLimiter.sleep(limiter) + const result = yield* sleep({ + algorithm: "fixed-window", + window: "1 minute", + limit: 5, + key: "partial" + }) + + assert.strictEqual(result.remaining, 4) + }).pipe( + Effect.provide(RateLimiter.layerStoreMemory) + )) + + it.effect("supports uncurried sleep", () => + Effect.gen(function*() { + const limiter = yield* RateLimiter.make + const result = yield* RateLimiter.sleep(limiter, { + algorithm: "fixed-window", + window: "1 minute", + limit: 5, + key: "direct" + }) + + assert.strictEqual(result.remaining, 4) + }).pipe( + Effect.provide(RateLimiter.layerStoreMemory) + )) + describe("fixed-window", () => { it.effect("returns accumulated delays after the fixed window is exceeded", () => Effect.gen(function*() { diff --git a/repos/effect/packages/effect/test/unstable/persistence/Redis.test.ts b/repos/effect/packages/effect/test/unstable/persistence/Redis.test.ts index b187746d2c..176462cfb4 100644 --- a/repos/effect/packages/effect/test/unstable/persistence/Redis.test.ts +++ b/repos/effect/packages/effect/test/unstable/persistence/Redis.test.ts @@ -1,8 +1,113 @@ import { assert, describe, it } from "@effect/vitest" -import { Effect } from "effect" -import { Redis } from "effect/unstable/persistence" +import { Duration, Effect, Layer } from "effect" +import { Persistence, Redis } from "effect/unstable/persistence" describe("Redis", () => { + it("preserves __proto__ as an own script option", () => { + const value = { polluted: true } + const script = Redis.script(() => [], { + ["__proto__"]: value, + lua: "return nil", + numberOfKeys: 0 + } as any) + + assert.isTrue(Object.hasOwn(script, "__proto__")) + assert.strictEqual((script as any)["__proto__"], value) + }) + + it.effect("clearing an empty persistence store succeeds", () => { + const redis = Redis.Redis.of({ + send:
(command: string, ...args: ReadonlyArray) => { + if (command.toUpperCase() === "KEYS") return Effect.succeed([] as unknown as A) + if (command.toUpperCase() === "DEL" && args.length === 0) { + return Effect.fail(new Redis.RedisError({ cause: "ERR wrong number of arguments for 'del' command" })) + } + return Effect.succeed(undefined as unknown as A) + }, + eval: () => () => Effect.die("unused") + }) + return Effect.gen(function*() { + const backing = yield* Persistence.BackingPersistence + const store = yield* backing.make("empty") + yield* store.clear + }).pipe( + Effect.provide(Persistence.layerBackingRedis.pipe(Layer.provide(Layer.succeed(Redis.Redis, redis)))) + ) + }) + + it.effect("rounds fractional persistence TTLs up to whole milliseconds", () => { + const commands: Array]> = [] + const scripts: Array = [] + const redis = Redis.Redis.of({ + send: (command: string, ...args: ReadonlyArray) => { + commands.push([command, args]) + return Effect.succeed(undefined as unknown as A) + }, + eval: + ; readonly result: unknown }>() => + (...params: Config["params"]) => { + scripts.push(params[0]) + return Effect.succeed(undefined as Config["result"]) + } + }) + return Effect.gen(function*() { + const backing = yield* Persistence.BackingPersistence + const store = yield* backing.make("ttl") + const ttl = Duration.nanos(1_500_000n) + + yield* store.set("single", {}, ttl) + yield* store.setMany([["batch", {}, ttl]]) + + assert.deepStrictEqual( + commands.filter(([command]) => command === "SET"), + [["SET", ["ttl:single", "{}", "PX", "2"]]] + ) + assert.deepStrictEqual(scripts, [{ + sets: new Map([["ttl:batch", "{}"]]), + expires: new Map([["ttl:batch", 2]]) + }]) + }).pipe( + Effect.provide(Persistence.layerBackingRedis.pipe(Layer.provide(Layer.succeed(Redis.Redis, redis)))) + ) + }) + + it.effect("retries script loading when SCRIPT LOAD fails", () => + Effect.gen(function*() { + const commands: Array]> = [] + let scriptLoadAttempts = 0 + const redis = yield* Redis.make({ + send: (command, ...args) => + Effect.suspend(() => { + commands.push([command, args]) + if (command === "SCRIPT") { + scriptLoadAttempts += 1 + if (scriptLoadAttempts === 1) { + return Effect.fail(new Redis.RedisError({ cause: new Error("ERR transient script load failure") })) + } + return Effect.succeed("sha" as any) + } + return Effect.succeed("ok") + }) + }) + const evalScript = redis.eval( + Redis.script((key: string) => [key], { + lua: "return KEYS[1]", + numberOfKeys: 1 + }).withReturnType() + ) + + const first = yield* Effect.result(evalScript("key")) + const second = yield* evalScript("key") + + assert.strictEqual(first._tag, "Failure") + assert.strictEqual(second, "ok") + assert.deepStrictEqual(commands.map(([command]) => command), [ + "SCRIPT", + "SCRIPT", + "EVALSHA" + ]) + })) + it.effect("reloads and retries scripts when EVALSHA returns NOSCRIPT", () => Effect.gen(function*() { const commands: Array]> = [] diff --git a/repos/effect/packages/effect/test/unstable/persistence/SqlCleanupTest.ts b/repos/effect/packages/effect/test/unstable/persistence/SqlCleanupTest.ts new file mode 100644 index 0000000000..f23152a60a --- /dev/null +++ b/repos/effect/packages/effect/test/unstable/persistence/SqlCleanupTest.ts @@ -0,0 +1,22 @@ +import { Duration, Effect, Schedule } from "effect" +import { sqlCleanupBatchSize } from "effect/internal/persistence" +import { TestClock } from "effect/testing" + +export const expiredEntryCount = sqlCleanupBatchSize + 1 +export const expiredAtEpoch = 0 +export const futureExpiresAt = Number.MAX_SAFE_INTEGER +export const cleanupBatchDelay = Duration.millis(10) +export const testTimeout = 30_000 + +export const waitForCount = ( + effect: Effect.Effect, + predicate: (count: number) => boolean +) => + effect.pipe( + Effect.repeat({ + until: predicate, + schedule: Schedule.spaced(cleanupBatchDelay) + }), + Effect.timeout(Duration.millis(testTimeout)), + TestClock.withLive + ) diff --git a/repos/effect/packages/effect/test/unstable/process/ChildProcess.test.ts b/repos/effect/packages/effect/test/unstable/process/ChildProcess.test.ts index e48c7f095b..31ed43eb8d 100644 --- a/repos/effect/packages/effect/test/unstable/process/ChildProcess.test.ts +++ b/repos/effect/packages/effect/test/unstable/process/ChildProcess.test.ts @@ -83,21 +83,21 @@ describe("ChildProcess", () => { const cmd = ChildProcess.make`echo hello` const handle = yield* cmd assert.strictEqual(handle.pid, ChildProcessSpawner.ProcessId(12345)) - }).pipe(Effect.scoped, Effect.provide(MockExecutorLayer))) + }).pipe(Effect.provide(MockExecutorLayer))) it.effect("should spawn a standard command", () => Effect.gen(function*() { const cmd = ChildProcess.make("node", ["--version"]) const handle = yield* cmd assert.strictEqual(handle.pid, ChildProcessSpawner.ProcessId(12345)) - }).pipe(Effect.scoped, Effect.provide(MockExecutorLayer))) + }).pipe(Effect.provide(MockExecutorLayer))) it.effect("should return a process handle", () => Effect.gen(function*() { const cmd = ChildProcess.make`long-running-process` const handle = yield* cmd assert.strictEqual(handle.pid, ChildProcessSpawner.ProcessId(12345)) - }).pipe(Effect.scoped, Effect.provide(MockExecutorLayer))) + }).pipe(Effect.provide(MockExecutorLayer))) it.effect("collects stdout through Stream APIs", () => Effect.gen(function*() { @@ -105,7 +105,7 @@ describe("ChildProcess", () => { const handle = yield* cmd const chunks = yield* Stream.runCollect(handle.stdout) assert.isTrue(chunks.length > 0) - }).pipe(Effect.scoped, Effect.provide(MockExecutorLayer))) + }).pipe(Effect.provide(MockExecutorLayer))) it.effect("should allow waiting for exit code", () => Effect.gen(function*() { @@ -113,7 +113,7 @@ describe("ChildProcess", () => { const handle = yield* cmd const exitCode = yield* handle.exitCode assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) - }).pipe(Effect.scoped, Effect.provide(MockExecutorLayer))) + }).pipe(Effect.provide(MockExecutorLayer))) it.effect("should unref a process and return a reref effect", () => Effect.gen(function*() { @@ -122,7 +122,7 @@ describe("ChildProcess", () => { const reref = yield* handle.unref assert.isDefined(reref) yield* reref - }).pipe(Effect.scoped, Effect.provide(MockExecutorLayer))) + }).pipe(Effect.provide(MockExecutorLayer))) it.effect("should allow restoring the reference within acquireRelease", () => Effect.gen(function*() { @@ -148,7 +148,7 @@ describe("ChildProcess", () => { ) const handle = yield* pipeline assert.strictEqual(handle.pid, ChildProcessSpawner.ProcessId(12345)) - }).pipe(Effect.scoped, Effect.provide(MockExecutorLayer))) + }).pipe(Effect.provide(MockExecutorLayer))) }) describe("setCwd", () => { @@ -346,7 +346,7 @@ describe("ChildProcess", () => { const handle = yield* cmd assert.isDefined(handle.getInputFd) assert.isDefined(handle.getOutputFd) - }).pipe(Effect.scoped, Effect.provide(MockExecutorLayer))) + }).pipe(Effect.provide(MockExecutorLayer))) }) }) }) diff --git a/repos/effect/packages/effect/test/unstable/process/ChildProcessSpawnerTest.ts b/repos/effect/packages/effect/test/unstable/process/ChildProcessSpawnerTest.ts new file mode 100644 index 0000000000..9c24aa48eb --- /dev/null +++ b/repos/effect/packages/effect/test/unstable/process/ChildProcessSpawnerTest.ts @@ -0,0 +1,1170 @@ +import { assert, describe, it } from "@effect/vitest" +import * as Effect from "effect/Effect" +import * as Exit from "effect/Exit" +import * as FileSystem from "effect/FileSystem" +import type * as Layer from "effect/Layer" +import * as Path from "effect/Path" +import type * as PlatformError from "effect/PlatformError" +import * as Schedule from "effect/Schedule" +import * as Scope from "effect/Scope" +import * as Stream from "effect/Stream" +import * as TestClock from "effect/testing/TestClock" +import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process" + +export interface Options { + readonly processGroups: boolean + readonly additionalFds?: boolean | undefined +} + +// Helper to collect stream output into a string +const decodeByteStream = Effect.fnUntraced( + function*( + stream: Stream.Stream + ) { + const chunks = yield* Stream.runCollect(stream) + const totalLength = chunks.reduce((acc, c) => acc + c.length, 0) + const result = new Uint8Array(totalLength) + let offset = 0 + for (const chunk of chunks) { + result.set(chunk, offset) + offset += chunk.length + } + return new TextDecoder().decode(result).trim() + } +) + +export const suite = ( + name: string, + layer: Layer.Layer, + options: Options +) => + describe(name, () => { + it.layer(layer)((it) => { + describe("spawn", () => { + describe("basic spawning", () => { + it.effect("should spawn a simple command and collect output", () => + Effect.gen(function*() { + const handle = yield* ChildProcess.make("sh", ["-c", "printf portable"]) + const output = yield* decodeByteStream(handle.stdout) + const exitCode = yield* handle.exitCode + + assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) + assert.strictEqual(output, "portable") + })) + + it.effect("should spawn echo command", () => + Effect.gen(function*() { + const handle = yield* ChildProcess.make("echo", ["hello", "world"]) + const output = yield* decodeByteStream(handle.stdout) + const exitCode = yield* handle.exitCode + + assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) + assert.strictEqual(output, "hello world") + })) + + it.effect("should spawn with template literal", () => + Effect.gen(function*() { + const handle = yield* ChildProcess.make`echo spawned` + const output = yield* decodeByteStream(handle.stdout) + const exitCode = yield* handle.exitCode + + assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) + assert.strictEqual(output, "spawned") + })) + }) + + describe("cwd option", () => { + it.effect("should handle command with working directory", () => + Effect.gen(function*() { + const handle = yield* ChildProcess.make("pwd", [], { cwd: "/tmp" }) + const output = yield* decodeByteStream(handle.stdout) + const exitCode = yield* handle.exitCode + + assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) + // On macOS, /tmp is a symlink to /private/tmp + assert.isTrue(output.includes("tmp")) + })) + + it.effect("should use cwd with template literal form", () => + Effect.gen(function*() { + const handle = yield* ChildProcess.make({ cwd: "/tmp" })`pwd` + const output = yield* decodeByteStream(handle.stdout) + const exitCode = yield* handle.exitCode + + assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) + assert.isTrue(output.includes("tmp")) + })) + }) + + describe("env option", () => { + it.effect("should handle environment variables", () => + Effect.gen(function*() { + const handle = yield* ChildProcess.make("sh", ["-c", "echo $TEST_VAR"], { + env: { TEST_VAR: "test_value" }, + extendEnv: true + }) + const output = yield* decodeByteStream(handle.stdout) + const exitCode = yield* handle.exitCode + + assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) + assert.strictEqual(output, "test_value") + })) + + it.effect("should handle multiple environment variables", () => + Effect.gen(function*() { + const handle = yield* ChildProcess.make("sh", ["-c", "echo $VAR1-$VAR2-$VAR3"], { + env: { VAR1: "one", VAR2: "two", VAR3: "three" }, + extendEnv: true + }) + const output = yield* decodeByteStream(handle.stdout) + const exitCode = yield* handle.exitCode + + assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) + assert.strictEqual(output, "one-two-three") + })) + + it.effect("should merge environment variables with setEnv", () => + Effect.gen(function*() { + const command = ChildProcess.make("sh", ["-c", "echo $VAR1-$VAR2-$VAR3"], { + env: { VAR1: "one", VAR2: "two" }, + extendEnv: true + }).pipe(ChildProcess.setEnv({ VAR2: "override", VAR3: "three" })) + const handle = yield* command + const output = yield* decodeByteStream(handle.stdout) + const exitCode = yield* handle.exitCode + + assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) + assert.strictEqual(output, "one-override-three") + })) + }) + + describe("shell option", () => { + it.effect("should execute with shell when using sh -c", () => + Effect.gen(function*() { + // Use sh -c to test shell expansion without triggering deprecation warning + const handle = yield* ChildProcess.make("sh", ["-c", "echo $SHELL_EXPANSION_VAR"], { + env: { SHELL_EXPANSION_VAR: "expanded" }, + extendEnv: true + }) + const output = yield* decodeByteStream(handle.stdout) + const exitCode = yield* handle.exitCode + + assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) + assert.strictEqual(output, "expanded") + })) + + it.effect("should not expand variables without shell", () => + Effect.gen(function*() { + const handle = yield* ChildProcess.make("echo", ["$HOME"], { shell: false }) + const output = yield* decodeByteStream(handle.stdout) + const exitCode = yield* handle.exitCode + + assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) + // Without shell, $HOME should not be expanded + assert.strictEqual(output, "$HOME") + })) + + it.effect("should allow piping with shell", () => + Effect.gen(function*() { + const handle = yield* ChildProcess.make("sh", ["-c", "echo hello | tr a-z A-Z"]) + const output = yield* decodeByteStream(handle.stdout) + const exitCode = yield* handle.exitCode + + assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) + assert.strictEqual(output, "HELLO") + })) + }) + + describe("template literal forms", () => { + it.effect("should work with template literal form", () => + Effect.gen(function*() { + const handle = yield* ChildProcess.make`echo hello` + const output = yield* decodeByteStream(handle.stdout) + const exitCode = yield* handle.exitCode + + assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) + assert.strictEqual(output, "hello") + })) + + it.effect("should handle string interpolation", () => + Effect.gen(function*() { + const name = "world" + const handle = yield* ChildProcess.make`echo hello ${name}` + const output = yield* decodeByteStream(handle.stdout) + const exitCode = yield* handle.exitCode + + assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) + assert.strictEqual(output, "hello world") + })) + + it.effect("should handle number interpolation", () => + Effect.gen(function*() { + const count = 42 + const handle = yield* ChildProcess.make`echo count is ${count}` + const output = yield* decodeByteStream(handle.stdout) + const exitCode = yield* handle.exitCode + + assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) + assert.strictEqual(output, "count is 42") + })) + + it.effect("should handle array interpolation", () => + Effect.gen(function*() { + const fs = yield* FileSystem.FileSystem + const path = yield* Path.Path + const dir = yield* fs.makeTempDirectoryScoped() + const file = path.join(dir, "array-interpolation.txt") + const args = ["-l", "-a"] + yield* fs.writeFile(file, new TextEncoder().encode("test")) + + const handle = yield* ChildProcess.make`ls ${args} ${dir}` + const output = yield* decodeByteStream(handle.stdout) + const exitCode = yield* handle.exitCode + + assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) + assert.isTrue(output.includes("array-interpolation.txt")) + })) + + it.effect("should handle multiple interpolations", () => + Effect.gen(function*() { + const greeting = "hello" + const target = "world" + const handle = yield* ChildProcess.make`echo ${greeting} ${target}` + const output = yield* decodeByteStream(handle.stdout) + const exitCode = yield* handle.exitCode + + assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) + assert.strictEqual(output, "hello world") + })) + + it.effect("should handle options with template literal", () => + Effect.gen(function*() { + const filename = "test.txt" + const handle = yield* ChildProcess.make({ cwd: "/tmp" })`echo ${filename}` + const output = yield* decodeByteStream(handle.stdout) + const exitCode = yield* handle.exitCode + + assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) + assert.strictEqual(output, "test.txt") + })) + }) + + describe("stderr streaming", () => { + it.effect("should capture stderr output", () => + Effect.gen(function*() { + const handle = yield* ChildProcess.make("sh", ["-c", "echo error message >&2"]) + const stderr = yield* decodeByteStream(handle.stderr) + const exitCode = yield* handle.exitCode + + assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) + assert.strictEqual(stderr, "error message") + })) + + it.effect("should capture both stdout and stderr", () => + Effect.gen(function*() { + const handle = yield* ChildProcess.make("sh", ["-c", "echo stdout; echo stderr >&2"]) + const [stdout, stderr] = yield* Effect.all([ + decodeByteStream(handle.stdout), + decodeByteStream(handle.stderr) + ], { concurrency: "unbounded" }) + const exitCode = yield* handle.exitCode + + assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) + assert.strictEqual(stdout, "stdout") + assert.strictEqual(stderr, "stderr") + })) + + it.effect("should handle more stdout than stderr", () => + Effect.gen(function*() { + // Process outputs many lines to stdout but only one to stderr + const handle = yield* ChildProcess.make( + "sh", + ["-c", "echo line1; echo line2; echo line3; echo line4; echo line5; echo error >&2"] + ) + const [stdout, stderr] = yield* Effect.all([ + decodeByteStream(handle.stdout), + decodeByteStream(handle.stderr) + ], { concurrency: "unbounded" }) + const exitCode = yield* handle.exitCode + + assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) + assert.strictEqual(stdout, ["line1", "line2", "line3", "line4", "line5"].join("\n")) + assert.strictEqual(stderr, "error") + })) + + it.effect("should handle more stderr than stdout", () => + Effect.gen(function*() { + // Process outputs many lines to stderr but only one to stdout + const handle = yield* ChildProcess.make( + "sh", + ["-c", "echo output; echo err1 >&2; echo err2 >&2; echo err3 >&2; echo err4 >&2; echo err5 >&2"] + ) + const [stdout, stderr] = yield* Effect.all([ + decodeByteStream(handle.stdout), + decodeByteStream(handle.stderr) + ], { concurrency: "unbounded" }) + const exitCode = yield* handle.exitCode + + assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) + assert.strictEqual(stdout, "output") + assert.strictEqual(stderr, ["err1", "err2", "err3", "err4", "err5"].join("\n")) + })) + + it.effect("should allow reading only stdout when stderr is empty", () => + Effect.gen(function*() { + const handle = yield* ChildProcess.make("echo", ["only stdout"]) + // Read streams in parallel to avoid deadlock when one stream is empty + const [stdout, stderr] = yield* Effect.all([ + decodeByteStream(handle.stdout), + decodeByteStream(handle.stderr) + ], { concurrency: "unbounded" }) + const exitCode = yield* handle.exitCode + + assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) + assert.strictEqual(stdout, "only stdout") + assert.strictEqual(stderr, "") + })) + + it.effect("should allow reading only stderr when stdout is empty", () => + Effect.gen(function*() { + const handle = yield* ChildProcess.make("sh", ["-c", "echo only stderr >&2"]) + // Read streams in parallel to avoid deadlock when one stream is empty + const [stdout, stderr] = yield* Effect.all([ + decodeByteStream(handle.stdout), + decodeByteStream(handle.stderr) + ], { concurrency: "unbounded" }) + const exitCode = yield* handle.exitCode + + assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) + assert.strictEqual(stdout, "") + assert.strictEqual(stderr, "only stderr") + })) + }) + + describe("combined output (all)", () => { + it.effect("should read interspersed stdout and stderr via .all", () => + Effect.gen(function*() { + // Use sleep to force buffer flushes between writes, ensuring + // stdout and stderr chunks arrive separately for proper interleaving + const handle = yield* ChildProcess.make( + "sh", + [ + "-c", + [ + "echo stdout1; sleep 0.01;", + "echo stderr1 >&2; sleep 0.01;", + "echo stdout2; sleep 0.01;", + "echo stderr2 >&2" + ].join(" ") + ] + ) + const all = yield* decodeByteStream(handle.all) + const exitCode = yield* handle.exitCode + + assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) + const lines = all.split("\n") + assert.strictEqual(lines.length, 4) + assert.deepStrictEqual(lines.filter((line) => line.startsWith("stdout")), ["stdout1", "stdout2"]) + assert.deepStrictEqual(lines.filter((line) => line.startsWith("stderr")), ["stderr1", "stderr2"]) + })) + + it.effect("should capture only stdout via .all when no stderr", () => + Effect.gen(function*() { + const handle = yield* ChildProcess.make("echo", ["hello from stdout"]) + const all = yield* decodeByteStream(handle.all) + const exitCode = yield* handle.exitCode + + assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) + assert.strictEqual(all, "hello from stdout") + })) + + it.effect("should capture only stderr via .all when no stdout", () => + Effect.gen(function*() { + const handle = yield* ChildProcess.make("sh", ["-c", "echo hello from stderr >&2"]) + const all = yield* decodeByteStream(handle.all) + const exitCode = yield* handle.exitCode + + assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) + assert.strictEqual(all, "hello from stderr") + })) + + it.effect("should handle many lines of interspersed output via .all", () => + Effect.gen(function*() { + // Use sleep to force buffer flushes, ensuring interleaved arrival + const handle = yield* ChildProcess.make( + "sh", + ["-c", "for i in 1 2 3 4 5; do echo stdout$i; sleep 0.01; echo stderr$i >&2; sleep 0.01; done"] + ) + const all = yield* decodeByteStream(handle.all) + const exitCode = yield* handle.exitCode + + assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) + const lines = all.split("\n") + assert.strictEqual(lines.length, 10) + assert.deepStrictEqual( + lines.filter((line) => line.startsWith("stdout")), + ["stdout1", "stdout2", "stdout3", "stdout4", "stdout5"] + ) + assert.deepStrictEqual( + lines.filter((line) => line.startsWith("stderr")), + ["stderr1", "stderr2", "stderr3", "stderr4", "stderr5"] + ) + })) + + it.effect("should allow reading .all independently", () => + Effect.gen(function*() { + const handle = yield* ChildProcess.make( + "sh", + ["-c", "echo out; sleep 0.01; echo err >&2"] + ) + const all = yield* decodeByteStream(handle.all) + const exitCode = yield* handle.exitCode + + assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) + assert.strictEqual(all, ["out", "err"].join("\n")) + })) + }) + + describe("stdout streaming", () => { + it.effect("should stream stdout", () => + Effect.gen(function*() { + const handle = yield* ChildProcess.make("echo", ["streaming output"]) + const output = yield* decodeByteStream(handle.stdout) + + assert.strictEqual(output, "streaming output") + })) + + it.effect("should stream multiple lines", () => + Effect.gen(function*() { + const handle = yield* ChildProcess.make("sh", ["-c", "echo line1; echo line2; echo line3"]) + const output = yield* decodeByteStream(handle.stdout) + + assert.strictEqual(output, ["line1", "line2", "line3"].join("\n")) + })) + }) + + describe("process control", () => { + it.effect("should kill a process", () => + Effect.gen(function*() { + const handle = yield* ChildProcess.make("sleep", ["10"]) + + yield* handle.kill() + + // After killing, exitCode should eventually resolve (with signal error) + const exit = yield* Effect.exit(handle.exitCode) + assert.isTrue(exit._tag === "Failure") + })) + + it.effect("should kill with specific signal", () => + Effect.gen(function*() { + const handle = yield* ChildProcess.make("sleep", ["10"]) + + yield* handle.kill({ killSignal: "SIGKILL" }) + + const exit = yield* Effect.exit(handle.exitCode) + assert.isTrue(exit._tag === "Failure") + })) + + it.effect("should force kill a process after the initial signal times out", () => + Effect.gen(function*() { + const fs = yield* FileSystem.FileSystem + const path = yield* Path.Path + const directory = yield* fs.makeTempDirectoryScoped() + const ready = path.join(directory, "ready") + const handle = yield* ChildProcess.make("sh", [ + "-c", + "trap '' TERM; : > \"$1\"; while :; do sleep 1; done", + "force-kill", + ready + ], { + killSignal: "SIGKILL", + stdout: "ignore", + stderr: "ignore" + }) + + yield* fs.exists(ready).pipe( + Effect.repeat({ + while: (exists) => !exists, + schedule: Schedule.spaced("10 millis") + }), + Effect.timeout("1 second"), + TestClock.withLive + ) + + const completed = yield* handle.kill({ + killSignal: "SIGTERM", + forceKillAfter: "50 millis" + }).pipe( + Effect.as(true), + Effect.timeoutOrElse({ + duration: "1 second", + orElse: () => Effect.succeed(false) + }), + TestClock.withLive + ) + + assert.isTrue(completed) + })) + + it.effect("should force kill a process when its scope closes", () => + Effect.gen(function*() { + const fs = yield* FileSystem.FileSystem + const path = yield* Path.Path + const directory = yield* fs.makeTempDirectoryScoped() + const ready = path.join(directory, "ready") + const completed = yield* Effect.scoped(Effect.gen(function*() { + yield* ChildProcess.make("sh", [ + "-c", + "trap '' TERM; : > \"$1\"; sleep 2", + "force-kill", + ready + ], { + killSignal: "SIGTERM", + forceKillAfter: "50 millis", + stdout: "ignore", + stderr: "ignore" + }) + + yield* fs.exists(ready).pipe( + Effect.repeat({ + while: (exists) => !exists, + schedule: Schedule.spaced("10 millis") + }), + Effect.timeout("1 second") + ) + })).pipe( + Effect.as(true), + Effect.timeoutOrElse({ + duration: "1 second", + orElse: () => Effect.succeed(false) + }), + TestClock.withLive + ) + + assert.isTrue(completed) + })) + }) + }) + + describe("pipeline spawning", () => { + it.effect("should spawn a simple pipeline", () => + Effect.gen(function*() { + const handle = yield* ChildProcess.make`echo hello world`.pipe( + ChildProcess.pipeTo(ChildProcess.make`tr a-z A-Z`) + ) + const output = yield* decodeByteStream(handle.stdout) + const exitCode = yield* handle.exitCode + + assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) + assert.strictEqual(output, "HELLO WORLD") + })) + + it.effect("should spawn a three-stage pipeline", () => + Effect.gen(function*() { + const handle = yield* ChildProcess.make`echo hello world`.pipe( + ChildProcess.pipeTo(ChildProcess.make`tr a-z A-Z`), + ChildProcess.pipeTo(ChildProcess.make("tr", [" ", "-"])) + ) + const output = yield* decodeByteStream(handle.stdout) + const exitCode = yield* handle.exitCode + + assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) + assert.strictEqual(output, "HELLO-WORLD") + })) + + it.effect("should pipe grep output", () => + Effect.gen(function*() { + const handle = yield* ChildProcess.make("echo", ["line1\nline2\nline3"]).pipe( + ChildProcess.pipeTo(ChildProcess.make`grep line2`) + ) + const output = yield* decodeByteStream(handle.stdout) + const exitCode = yield* handle.exitCode + + assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) + assert.strictEqual(output, "line2") + })) + + it.effect("should handle mixed command forms in pipeline", () => + Effect.gen(function*() { + const handle = yield* ChildProcess.make("echo", ["hello"]).pipe( + ChildProcess.pipeTo(ChildProcess.make`tr a-z A-Z`) + ) + const output = yield* decodeByteStream(handle.stdout) + const exitCode = yield* handle.exitCode + + assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) + assert.strictEqual(output, "HELLO") + })) + }) + + describe("pipeline pipe options", () => { + it.effect("should pipe stderr to stdin with { from: 'stderr' }", () => + Effect.gen(function*() { + // Command that writes "error" to stderr + const handle = yield* ChildProcess.make("sh", ["-c", "echo error >&2"]).pipe( + ChildProcess.pipeTo(ChildProcess.make`cat`, { from: "stderr" }) + ) + const output = yield* decodeByteStream(handle.stdout) + const exitCode = yield* handle.exitCode + + assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) + assert.strictEqual(output, "error") + })) + + it.effect("should pipe combined output with { from: 'all' }", () => + Effect.gen(function*() { + // Command that writes to both stdout and stderr with small delays + const handle = yield* ChildProcess.make("sh", [ + "-c", + "echo out1; sleep 0.01; echo err1 >&2; sleep 0.01; echo out2" + ]).pipe( + ChildProcess.pipeTo(ChildProcess.make`cat`, { from: "all" }) + ) + const output = yield* decodeByteStream(handle.stdout) + const exitCode = yield* handle.exitCode + + assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) + const lines = output.split("\n") + assert.strictEqual(lines.length, 3) + assert.deepStrictEqual(lines.filter((line) => line.startsWith("out")), ["out1", "out2"]) + assert.deepStrictEqual(lines.filter((line) => line.startsWith("err")), ["err1"]) + })) + + it.effect("should default to stdout when no options provided", () => + Effect.gen(function*() { + // Command that writes to both stdout and stderr + const handle = yield* ChildProcess.make("sh", ["-c", "echo stdout; echo stderr >&2"]).pipe( + ChildProcess.pipeTo(ChildProcess.make`cat`) + ) + const output = yield* decodeByteStream(handle.stdout) + const exitCode = yield* handle.exitCode + + assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) + // Only stdout should be piped (default behavior) + assert.strictEqual(output, "stdout") + })) + + it.effect("should work with empty options object", () => + Effect.gen(function*() { + const handle = yield* ChildProcess.make`echo hello`.pipe( + ChildProcess.pipeTo(ChildProcess.make`tr a-z A-Z`, {}) + ) + const output = yield* decodeByteStream(handle.stdout) + const exitCode = yield* handle.exitCode + + assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) + assert.strictEqual(output, "HELLO") + })) + + it.effect("should work with explicit { from: 'stdout' }", () => + Effect.gen(function*() { + const handle = yield* ChildProcess.make`echo hello`.pipe( + ChildProcess.pipeTo(ChildProcess.make`tr a-z A-Z`, { from: "stdout" }) + ) + const output = yield* decodeByteStream(handle.stdout) + const exitCode = yield* handle.exitCode + + assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) + assert.strictEqual(output, "HELLO") + })) + + it.effect("should work with explicit { to: 'stdin' }", () => + Effect.gen(function*() { + const handle = yield* ChildProcess.make`echo hello`.pipe( + ChildProcess.pipeTo(ChildProcess.make`tr a-z A-Z`, { to: "stdin" }) + ) + const output = yield* decodeByteStream(handle.stdout) + const exitCode = yield* handle.exitCode + + assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) + assert.strictEqual(output, "HELLO") + })) + + it.effect("should support chained pipes with different options", () => + Effect.gen(function*() { + // First pipe: stdout to stdin (default) + // Second pipe: from stderr + const handle = yield* ChildProcess.make`echo hello`.pipe( + ChildProcess.pipeTo(ChildProcess.make("sh", ["-c", "cat; echo error >&2"])), + ChildProcess.pipeTo(ChildProcess.make`cat`, { from: "stderr" }) + ) + const output = yield* decodeByteStream(handle.stdout) + const exitCode = yield* handle.exitCode + + assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) + assert.strictEqual(output, "error") + })) + }) + + describe("error handling", () => { + it.effect("should return non-zero exit code", () => + Effect.gen(function*() { + const handle = yield* ChildProcess.make("sh", ["-c", "exit 1"]) + const exitCode = yield* handle.exitCode + + assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(1)) + })) + + it.effect("should fail for invalid command", () => + Effect.gen(function*() { + const exit = yield* Effect.exit( + ChildProcess.make("nonexistent-command-12345") + ) + + assert.isTrue(exit._tag === "Failure") + })) + + it.effect("should handle spawn error with invalid cwd", () => + Effect.gen(function*() { + const exit = yield* Effect.exit( + ChildProcess.make("echo", ["test"], { cwd: "/nonexistent/directory/path" }) + ) + + assert.isTrue(exit._tag === "Failure") + })) + + it.effect("should throw permission denied as a typed error", () => + Effect.gen(function*() { + const path = yield* Path.Path + const cwd = yield* path.fromFileUrl(new URL("./fixtures/bash/", import.meta.url)) + + const command = ChildProcess.make({ cwd })`./no-permissions.sh` + const result = yield* Effect.flip(command) + + assert.strictEqual(result.reason._tag, "PermissionDenied") + assert.strictEqual(result.reason.module, "ChildProcess") + assert.strictEqual(result.reason.method, "spawn") + })) + }) + + describe("stdin", () => { + it.effect("allows providing standard input to a command", () => + Effect.gen(function*() { + const input = "a b c" + const stdin = Stream.make(new TextEncoder().encode(input)) + const handle = yield* ChildProcess.make("cat", { stdin }) + const output = yield* decodeByteStream(handle.stdout) + const exitCode = yield* handle.exitCode + + assert.deepStrictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) + assert.strictEqual(output, input) + })) + }) + + describe.skipIf(options.additionalFds === false)("additionalFds", () => { + it.effect("should read data from an output fd (fd3)", () => + Effect.gen(function*() { + // Use a shell script that writes to fd3 + // The script echoes "hello from fd3" to file descriptor 3 + const handle = yield* ChildProcess.make("sh", ["-c", "echo 'hello from fd3' >&3"], { + additionalFds: { fd3: { type: "output" } } + }) + + const fd3Output = yield* decodeByteStream(handle.getOutputFd(3)) + const exitCode = yield* handle.exitCode + + assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) + assert.strictEqual(fd3Output, "hello from fd3") + })) + + it.effect("should write data to an input fd (fd3)", () => + Effect.gen(function*() { + // Use a shell script that reads from fd3 and echoes it to stdout + // The script reads from file descriptor 3 and outputs to stdout + const inputData = "data from parent" + const inputStream = Stream.make(new TextEncoder().encode(inputData)) + + const handle = yield* ChildProcess.make("sh", ["-c", "cat <&3"], { + additionalFds: { + fd3: { type: "input", stream: inputStream } + } + }) + + const stdout = yield* decodeByteStream(handle.stdout) + const exitCode = yield* handle.exitCode + + assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) + assert.strictEqual(stdout, inputData) + })) + + it.effect("should handle multiple additional fds", () => + Effect.gen(function*() { + // Script that writes different messages to fd3 and fd4 + const handle = yield* ChildProcess.make( + "sh", + ["-c", "echo 'output on fd3' >&3; echo 'output on fd4' >&4"], + { + additionalFds: { + fd3: { type: "output" }, + fd4: { type: "output" } + } + } + ) + + const fd3Output = yield* decodeByteStream(handle.getOutputFd(3)) + const fd4Output = yield* decodeByteStream(handle.getOutputFd(4)) + const exitCode = yield* handle.exitCode + + assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) + assert.strictEqual(fd3Output, "output on fd3") + assert.strictEqual(fd4Output, "output on fd4") + })) + + it.effect("should handle fd gaps (e.g., fd3 and fd5 without fd4)", () => + Effect.gen(function*() { + // Script that writes to fd3 and fd5, skipping fd4 + const handle = yield* ChildProcess.make( + "sh", + ["-c", "echo 'on fd3' >&3; echo 'on fd5' >&5"], + { + additionalFds: { + fd3: { type: "output" }, + fd5: { type: "output" } + } + } + ) + + const fd3Output = yield* decodeByteStream(handle.getOutputFd(3)) + const fd5Output = yield* decodeByteStream(handle.getOutputFd(5)) + const exitCode = yield* handle.exitCode + + assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) + assert.strictEqual(fd3Output, "on fd3") + assert.strictEqual(fd5Output, "on fd5") + })) + + it.effect("should return empty stream for unconfigured output fd", () => + Effect.gen(function*() { + const handle = yield* ChildProcess.make("echo", ["test"]) + + // fd3 was not configured, should return empty stream + const fd3Output = yield* decodeByteStream(handle.getOutputFd(3)) + const exitCode = yield* handle.exitCode + + assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) + assert.strictEqual(fd3Output, "") + })) + + it.effect("should handle bidirectional communication via separate fds", () => + Effect.gen(function*() { + // Script that reads from fd3, transforms it, and writes to fd4 + const inputData = "hello" + const inputStream = Stream.make(new TextEncoder().encode(inputData)) + + const handle = yield* ChildProcess.make( + "sh", + ["-c", "cat <&3 | tr a-z A-Z >&4"], + { + additionalFds: { + fd3: { type: "input", stream: inputStream }, + fd4: { type: "output" } + } + } + ) + + const fd4Output = yield* decodeByteStream(handle.getOutputFd(4)) + const exitCode = yield* handle.exitCode + + assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) + assert.strictEqual(fd4Output, "HELLO") + })) + + it.effect("should work alongside normal stdin/stdout/stderr", () => + Effect.gen(function*() { + // Script that uses all standard streams plus fd3 + const handle = yield* ChildProcess.make( + "sh", + ["-c", "echo 'stdout'; echo 'stderr' >&2; echo 'fd3' >&3"], + { additionalFds: { fd3: { type: "output" } } } + ) + + const [stdout, stderr, fd3Output] = yield* Effect.all([ + decodeByteStream(handle.stdout), + decodeByteStream(handle.stderr), + decodeByteStream(handle.getOutputFd(3)) + ], { concurrency: "unbounded" }) + const exitCode = yield* handle.exitCode + + assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) + assert.strictEqual(stdout, "stdout") + assert.strictEqual(stderr, "stderr") + assert.strictEqual(fd3Output, "fd3") + })) + }) + + describe.sequential("process supervision", () => { + const countMatchingProcesses = (pattern: string) => + Effect.gen(function*() { + const handle = yield* ChildProcess.make("bash", [ + "-c", + `ps aux | grep '${pattern}' | grep -v grep | wc -l` + ]) + const output = yield* decodeByteStream(handle.stdout) + return Number.parseInt(output.trim()) + }).pipe(Effect.orElseSucceed(() => 0)) + + const killMatchingProcesses = (pattern: string) => { + if (!/^[A-Za-z0-9_-]+$/.test(pattern)) { + return Effect.die(new Error(`Invalid process pattern: ${pattern}`)) + } + return Effect.gen(function*() { + const escaped = `[${pattern[0]}]${pattern.slice(1)}` + const handle = yield* ChildProcess.make("bash", ["-c", `pkill -f '${escaped}' || true`]) + yield* Effect.ignore(handle.exitCode) + }).pipe(Effect.asVoid) + } + + const longRunningCommand = () => + ChildProcess.make("sh", ["-c", "sleep 30", "long-running-command"], { + stdin: "ignore", + stdout: "ignore", + stderr: "ignore" + }) + + it.effect.skipIf(!options.processGroups)("should kill all child processes in process group", () => + Effect.gen(function*() { + const path = yield* Path.Path + const cwd = yield* path.fromFileUrl(new URL("./fixtures/bash/", import.meta.url)) + + // Start the process that spawns children and grandchildren + const handle = yield* ChildProcess.make("./spawn-children.sh", { cwd }) + + // Give it time to spawn all processes + yield* TestClock.withLive(Effect.sleep("100 millis")) + + // Verify the main process is running + const isRunningBeforeKill = yield* handle.isRunning + assert.isTrue(isRunningBeforeKill) + + // Count processes before killing - should be at least 7 (1 parent + 3 children + 3 grandchildren) + const beforeKillHandle = yield* ChildProcess.make("bash", [ + "-c", + "ps aux | grep spawn-children.sh | grep -v grep | wc -l" + ]) + const beforeKill = yield* decodeByteStream(beforeKillHandle.stdout).pipe( + Effect.map((s) => + Number.parseInt(s.trim()) + ), + Effect.orElseSucceed(() => 0) + ) + assert.isAtLeast(beforeKill, 7) + + // Kill the main process + yield* handle.kill() + + // Verify the main process is no longer running + const isRunningAfterKill = yield* handle.isRunning + assert.isFalse(isRunningAfterKill) + + // Give a moment for cleanup to complete + yield* TestClock.withLive(Effect.sleep("100 millis")) + + // Check that no processes from the script are still running + const afterKillHandle = yield* ChildProcess.make("bash", [ + "-c", + "ps aux | grep spawn-children.sh | grep -v grep | wc -l" + ]) + const afterKill = yield* decodeByteStream(afterKillHandle.stdout).pipe( + Effect.map((s) => Number.parseInt(s.trim())), + Effect.orElseSucceed(() => 0) + ) + assert.strictEqual(afterKill, 0) + })) + + it.effect.skipIf(!options.processGroups)( + "should cleanup child processes when parent exits with non-zero code", + () => + Effect.gen(function*() { + const path = yield* Path.Path + const cwd = yield* path.fromFileUrl(new URL("./fixtures/bash/", import.meta.url)) + + // Count processes before running the command + const beforeRunHandle = yield* ChildProcess.make("bash", [ + "-c", + "ps aux | grep parent-exits-early.sh | grep -v grep | wc -l" + ]) + const beforeRun = yield* decodeByteStream(beforeRunHandle.stdout).pipe( + Effect.map((s) => Number.parseInt(s.trim())), + Effect.orElseSucceed(() => 0) + ) + assert.strictEqual(beforeRun, 0) + + // Run command in a separate scope so cleanup happens before we check + const exitCode = yield* Effect.scoped(Effect.gen(function*() { + const handle = yield* ChildProcess.make({ cwd })`./parent-exits-early.sh` + return yield* handle.exitCode + })) + + assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(1)) + + // Allow cleanup to occur + yield* TestClock.withLive(Effect.sleep("100 millis")) + + const afterExitHandle = yield* ChildProcess.make("bash", [ + "-c", + "ps aux | grep 'parent-exits-early-' | grep -v grep | wc -l" + ]) + const afterExit = yield* decodeByteStream(afterExitHandle.stdout).pipe( + Effect.map((s) => Number.parseInt(s.trim())), + Effect.orElseSucceed(() => 0) + ) + // Child processes should be cleaned up after non-zero exit + assert.strictEqual(afterExit, 0) + }) + ) + + it.effect("should not kill an unrefed process when scope closes", () => + Effect.gen(function*() { + const scope = yield* Scope.make() + const handle = yield* Scope.provide(scope)(Effect.gen(function*() { + return yield* longRunningCommand() + })).pipe( + Effect.provide(layer) + ) + + yield* Effect.gen(function*() { + // @effect-diagnostics-next-line floatingEffect:off + yield* Scope.provide(scope)(handle.unref).pipe(Effect.provide(layer)) + yield* Scope.close(scope, Exit.void) + yield* TestClock.withLive(Effect.sleep("100 millis")) + + const isRunning = yield* handle.isRunning + assert.isTrue(isRunning) + }).pipe(Effect.ensuring(Effect.ignore(handle.kill({ killSignal: "SIGKILL" })))) + }).pipe(Effect.provide(layer))) + + it.effect("should kill a restored process when scope closes", () => + Effect.gen(function*() { + const scope = yield* Scope.make() + const handle = yield* Scope.provide(scope)(Effect.gen(function*() { + return yield* longRunningCommand() + })).pipe( + Effect.provide(layer) + ) + + const reref = yield* Scope.provide(scope)(handle.unref).pipe(Effect.provide(layer)) + yield* reref + yield* Scope.close(scope, Exit.void) + yield* TestClock.withLive(Effect.sleep("100 millis")) + + const isRunning = yield* handle.isRunning + assert.isFalse(isRunning) + }).pipe(Effect.provide(layer))) + + it.effect("should resolve exitCode after closing the original scope of an unrefed process", () => + Effect.gen(function*() { + const scope = yield* Scope.make() + const handle = yield* Scope.provide(scope)(Effect.gen(function*() { + return yield* ChildProcess.make("sh", ["-c", "sleep 0.05"], { + stdin: "ignore", + stdout: "ignore", + stderr: "ignore" + }) + })).pipe(Effect.provide(layer)) + + // @effect-diagnostics-next-line floatingEffect:off + yield* Scope.provide(scope)(handle.unref).pipe(Effect.provide(layer)) + yield* Scope.close(scope, Exit.void) + + const exitCode = yield* handle.exitCode + assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) + }).pipe(Effect.provide(layer))) + + it.effect.skipIf(!options.processGroups)( + "should cleanup descendants after an unrefed parent exits non-zero", + () => + Effect.gen(function*() { + const path = yield* Path.Path + const cwd = yield* path.fromFileUrl(new URL("./fixtures/bash/", import.meta.url)) + const scope = yield* Scope.make() + + const handle = yield* Scope.provide(scope)(Effect.gen(function*() { + return yield* ChildProcess.make({ cwd })`./parent-exits-early.sh` + })).pipe(Effect.provide(layer)) + + // @effect-diagnostics-next-line floatingEffect:off + yield* Scope.provide(scope)(handle.unref).pipe(Effect.provide(layer)) + yield* Scope.close(scope, Exit.void) + + const exitCode = yield* handle.exitCode + assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(1)) + + yield* TestClock.withLive(Effect.sleep("100 millis")) + + const remaining = yield* countMatchingProcesses("parent-exits-early-") + assert.strictEqual(remaining, 0) + }).pipe(Effect.provide(layer)) + ) + + it.effect("should unref every process in a pipeline", () => + Effect.gen(function*() { + const scope = yield* Scope.make() + const rootMarker = "pipeline-unref-root" + const tailMarker = "pipeline-unref-tail" + + const handle = yield* Scope.provide(scope)(Effect.gen(function*() { + return yield* ChildProcess.make("sh", ["-c", "sleep 30; :", rootMarker], { + stdin: "ignore", + stdout: "pipe", + stderr: "ignore" + }).pipe( + ChildProcess.pipeTo( + ChildProcess.make("sh", ["-c", "sleep 30; :", tailMarker], { + stdin: "pipe", + stdout: "ignore", + stderr: "ignore" + }) + ) + ) + })).pipe(Effect.provide(layer)) + + yield* Effect.gen(function*() { + // @effect-diagnostics-next-line floatingEffect:off + yield* Scope.provide(scope)(handle.unref).pipe(Effect.provide(layer)) + yield* Scope.close(scope, Exit.void) + yield* TestClock.withLive(Effect.sleep("100 millis")) + + const rootCount = yield* countMatchingProcesses(rootMarker) + const tailCount = yield* countMatchingProcesses(tailMarker) + assert.strictEqual(rootCount, 1) + assert.strictEqual(tailCount, 1) + }).pipe( + Effect.ensuring(Effect.ignore(Effect.all([ + killMatchingProcesses(rootMarker), + killMatchingProcesses(tailMarker) + ], { discard: true }))) + ) + }).pipe(Effect.provide(layer))) + }) + + it.effect("should not deadlock on large stdout output", () => + Effect.gen(function*() { + // Generate ~5MB of output — enough to exceed the default PassThrough + // highWaterMark (16KB) many times over. Without the fix, the unread + // combinedPassThrough (.all) would exert backpressure on the source + // stream, blocking stdout too. + const handle = yield* ChildProcess.make("sh", ["-c", "seq 1 100000"]) + const output = yield* handle.stdout.pipe( + Stream.decodeText(), + Stream.runFold(() => "", (acc, chunk) => acc + chunk) + ) + const exitCode = yield* handle.exitCode + + assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) + const lines = output.trim().split("\n") + assert.strictEqual(lines.length, 100000) + assert.strictEqual(lines[0], "1") + assert.strictEqual(lines[99999], "100000") + }), { timeout: 10_000 }) + + it.effect("ChildProcess.string should not deadlock on large output", () => + Effect.gen(function*() { + const spawner = yield* ChildProcessSpawner.ChildProcessSpawner + const output = yield* spawner.string(ChildProcess.make("sh", ["-c", "seq 1 100000"])) + const lines = output.trim().split("\n") + assert.strictEqual(lines.length, 100000) + assert.strictEqual(lines[0], "1") + assert.strictEqual(lines[99999], "100000") + }), { timeout: 10_000 }) + }) + }) diff --git a/repos/effect/packages/platform-node-shared/test/fixtures/bash/no-permissions.sh b/repos/effect/packages/effect/test/unstable/process/fixtures/bash/no-permissions.sh similarity index 100% rename from repos/effect/packages/platform-node-shared/test/fixtures/bash/no-permissions.sh rename to repos/effect/packages/effect/test/unstable/process/fixtures/bash/no-permissions.sh diff --git a/repos/effect/packages/effect/test/unstable/process/fixtures/bash/parent-exits-early.sh b/repos/effect/packages/effect/test/unstable/process/fixtures/bash/parent-exits-early.sh new file mode 100755 index 0000000000..121c181a94 --- /dev/null +++ b/repos/effect/packages/effect/test/unstable/process/fixtures/bash/parent-exits-early.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash + +# This script spawns child processes and then exits early +echo "Parent process started with PID $$" + +# Spawn multiple child processes that will outlive the parent +for i in {1..3}; do + ( + # Child process + child_pid=$BASHPID + echo "Child $i started with PID $child_pid" + + # Spawn a grandchild that runs for a long time + ( + grandchild_pid=$BASHPID + echo "Grandchild of child $i started with PID $grandchild_pid" + # Keep running for 30 seconds with a marker for process assertions + sh -c 'sleep 30; :' parent-exits-early-grandchild + ) & + + # Keep the child running with a marker for process assertions + sh -c 'sleep 30; :' parent-exits-early-child + ) & +done + +# Give children time to start +sleep 0.5 + +# Exit early (simulating a crash or early termination) +echo "Parent exiting early with status 1..." +exit 1 diff --git a/repos/effect/packages/platform-node-shared/test/fixtures/bash/spawn-children.sh b/repos/effect/packages/effect/test/unstable/process/fixtures/bash/spawn-children.sh similarity index 92% rename from repos/effect/packages/platform-node-shared/test/fixtures/bash/spawn-children.sh rename to repos/effect/packages/effect/test/unstable/process/fixtures/bash/spawn-children.sh index b192a58f7b..dcc6675819 100755 --- a/repos/effect/packages/platform-node-shared/test/fixtures/bash/spawn-children.sh +++ b/repos/effect/packages/effect/test/unstable/process/fixtures/bash/spawn-children.sh @@ -15,13 +15,13 @@ for i in {1..3}; do grandchild_pid=$BASHPID echo "Grandchild of child $i started with PID $grandchild_pid" # Keep running for 60 seconds - for j in {1..60}; do + for _ in {1..60}; do sleep 1 done ) & # Keep the child running - for j in {1..60}; do + for _ in {1..60}; do sleep 1 done ) & diff --git a/repos/effect/packages/platform-node-shared/test/fixtures/config/SHOUTING b/repos/effect/packages/effect/test/unstable/process/fixtures/config/SHOUTING similarity index 100% rename from repos/effect/packages/platform-node-shared/test/fixtures/config/SHOUTING rename to repos/effect/packages/effect/test/unstable/process/fixtures/config/SHOUTING diff --git a/repos/effect/packages/platform-node-shared/test/fixtures/config/integer b/repos/effect/packages/effect/test/unstable/process/fixtures/config/integer similarity index 100% rename from repos/effect/packages/platform-node-shared/test/fixtures/config/integer rename to repos/effect/packages/effect/test/unstable/process/fixtures/config/integer diff --git a/repos/effect/packages/platform-node-shared/test/fixtures/config/nested/config b/repos/effect/packages/effect/test/unstable/process/fixtures/config/nested/config similarity index 100% rename from repos/effect/packages/platform-node-shared/test/fixtures/config/nested/config rename to repos/effect/packages/effect/test/unstable/process/fixtures/config/nested/config diff --git a/repos/effect/packages/platform-node-shared/test/fixtures/config/secret b/repos/effect/packages/effect/test/unstable/process/fixtures/config/secret similarity index 100% rename from repos/effect/packages/platform-node-shared/test/fixtures/config/secret rename to repos/effect/packages/effect/test/unstable/process/fixtures/config/secret diff --git a/repos/effect/packages/effect/test/unstable/schema/VariantSchema.test.ts b/repos/effect/packages/effect/test/unstable/schema/VariantSchema.test.ts index e897b1b289..9d6bc6e3f3 100644 --- a/repos/effect/packages/effect/test/unstable/schema/VariantSchema.test.ts +++ b/repos/effect/packages/effect/test/unstable/schema/VariantSchema.test.ts @@ -18,6 +18,69 @@ describe("VariantSchema", () => { assert.deepStrictEqual(Object.keys(Test.extract(struct, "b").fields), ["common", "onlyB", "exceptC"]) assert.deepStrictEqual(Object.keys(Test.extract(struct, "c").fields), ["common"]) }) + + it("Class preserves class and variant schema behavior", () => { + const Test = VariantSchema.make({ + variants: ["a", "b"], + defaultVariant: "a" + }) + class User extends Test.Class("User")({ + id: Test.FieldOnly(["a"])(Schema.Number), + name: Schema.String + }) {} + + const user = User.make({ id: 1, name: "Alice" }) + + assert.isTrue(user instanceof User) + assert.deepStrictEqual(user, new User({ id: 1, name: "Alice" })) + assert.deepStrictEqual(Schema.decodeSync(User)({ id: 1, name: "Alice" }), user) + assert.deepStrictEqual(Schema.decodeSync(User.b)({ name: "Alice" }), { name: "Alice" }) + assert.deepStrictEqual(Object.keys(User.fields), ["id", "name"]) + }) + + it("includes plain variant structs in the default union", () => { + const Test = VariantSchema.make({ variants: ["a", "b"], defaultVariant: "a" }) + const first = Test.Struct({ value: Schema.String }) + const second = Test.Struct({ value: Schema.Number }) + const union = Test.Union([first, second]) + + assert.strictEqual(union.members.length, 2) + assert.deepStrictEqual(Schema.decodeUnknownSync(union)({ value: "foo" }), { value: "foo" }) + assert.deepStrictEqual(Schema.decodeUnknownSync(union)({ value: 42 }), { value: 42 }) + }) + + it("omits undefined fields accepted by VariantSchema.Struct", () => { + const Test = VariantSchema.make({ variants: ["a"], defaultVariant: "a" }) + const struct = Test.Struct({ value: Schema.String, skipped: undefined }) + + assert.deepStrictEqual(Object.keys(Test.extract(struct, "a").fields), ["value"]) + }) + + it("omits undefined fields selected by VariantSchema.Field", () => { + const Test = VariantSchema.make({ variants: ["a"], defaultVariant: "a" }) + const struct = Test.Struct({ value: Schema.String, skipped: Test.Field({ a: undefined }) }) + + assert.deepStrictEqual(Object.keys(Test.extract(struct, "a").fields), ["value"]) + }) + + it("does not collide the __default variant with the default-schema cache entry", () => { + const Test = VariantSchema.make({ variants: ["a", "__default"], defaultVariant: "a" }) + const defaultFirst = Test.Struct({ + value: Test.Field({ a: Schema.String, __default: Schema.Number }) + }) + + Test.extract(defaultFirst, "a") + + assert.strictEqual(Test.extract(defaultFirst, "__default").fields.value, Schema.Number) + + const namedFirst = Test.Struct({ + value: Test.Field({ a: Schema.String, __default: Schema.Number }) + }) + + Test.extract(namedFirst, "__default") + + assert.strictEqual(Test.extract(namedFirst, "a").fields.value, Schema.String) + }) }) describe("Model", () => { diff --git a/repos/effect/packages/effect/test/unstable/sql/SqlResolver.test.ts b/repos/effect/packages/effect/test/unstable/sql/SqlResolver.test.ts index 87743e061f..8dad85424a 100644 --- a/repos/effect/packages/effect/test/unstable/sql/SqlResolver.test.ts +++ b/repos/effect/packages/effect/test/unstable/sql/SqlResolver.test.ts @@ -1,10 +1,69 @@ import { assert, describe, it } from "@effect/vitest" -import { Effect } from "effect" +import { Effect, Exit } from "effect" import * as Schema from "effect/Schema" import { SqlResolver } from "effect/unstable/sql" describe("SqlResolver", () => { + describe("grouped", () => { + it.effect("does not execute a batch when every request fails encoding", () => + Effect.gen(function*() { + let executions = 0 + const resolver = SqlResolver.grouped({ + Request: Schema.Number.check(Schema.isGreaterThan(0)), + RequestGroupKey: (request) => request, + Result: Schema.Number, + ResultGroupKey: (result) => result, + execute: (inputs) => { + executions++ + return Effect.succeed(inputs) + } + }) + + const error = yield* Effect.flip(SqlResolver.request(-1, resolver)) + + assert.strictEqual(error._tag, "SchemaError") + assert.strictEqual(executions, 0) + })) + }) + describe("findById", () => { + it.effect("does not execute a batch when every request fails encoding", () => + Effect.gen(function*() { + let executions = 0 + const resolver = SqlResolver.findById({ + Id: Schema.Number.check(Schema.isGreaterThan(0)), + Result: Schema.Struct({ id: Schema.Number }), + ResultId: (result) => result.id, + execute: (inputs) => { + executions++ + return Effect.succeed(inputs.map((id) => ({ id }))) + } + }) + + const error = yield* Effect.flip(SqlResolver.request(-1, resolver)) + + assert.strictEqual(error._tag, "SchemaError") + assert.strictEqual(executions, 0) + })) + + it.effect("completes duplicate requests when id encoding fails", () => + Effect.gen(function*() { + const resolver = SqlResolver.findById({ + Id: Schema.Number.check(Schema.isGreaterThan(0)), + Result: Schema.Struct({ id: Schema.Number }), + ResultId: (result) => result.id, + execute: (inputs) => Effect.succeed(inputs.map((id) => ({ id }))) + }) + const execute = SqlResolver.request(resolver) + + const errors = yield* Effect.all([ + Effect.flip(execute(-1)), + Effect.flip(execute(-1)) + ], { concurrency: "unbounded" }) + + assert.deepStrictEqual(errors.map((error) => error._tag), ["SchemaError", "SchemaError"]) + })) + it.effect("deduplicates requests by id", () => Effect.gen(function*() { const batches: Array> = [] @@ -34,4 +93,61 @@ describe("SqlResolver", () => { assert.deepStrictEqual(batches, [[1, 2]]) })) }) + + describe("ordered", () => { + it.effect("does not execute a batch when every request fails encoding", () => + Effect.gen(function*() { + let executions = 0 + const resolver = SqlResolver.ordered({ + Request: Schema.Number.check(Schema.isGreaterThan(0)), + Result: Schema.String, + execute: (inputs) => { + executions++ + return Effect.succeed(inputs.map(String)) + } + }) + + const error = yield* Effect.flip(SqlResolver.request(-1, resolver)) + + assert.strictEqual(error._tag, "SchemaError") + assert.strictEqual(executions, 0) + })) + + it.effect("keeps valid results aligned when another request fails encoding", () => + Effect.gen(function*() { + const resolver = SqlResolver.ordered({ + Request: Schema.Number.check(Schema.isGreaterThan(0)), + Result: Schema.String, + execute: (inputs) => Effect.succeed(inputs.map((input) => `value-${input}`)) + }) + const execute = SqlResolver.request(resolver) + const [invalid, valid] = yield* Effect.all([ + Effect.exit(execute(-1)), + Effect.exit(execute(2)) + ], { concurrency: "unbounded" }) + + assert(Exit.isFailure(invalid)) + assert(Exit.isSuccess(valid)) + assert.strictEqual(valid.value, "value-2") + })) + }) + + describe("void", () => { + it.effect("does not execute a batch when every request fails encoding", () => + Effect.gen(function*() { + let executions = 0 + const resolver = SqlResolver.void({ + Request: Schema.Number.check(Schema.isGreaterThan(0)), + execute: (inputs) => { + executions++ + return Effect.succeed(inputs) + } + }) + + const error = yield* Effect.flip(SqlResolver.request(-1, resolver)) + + assert.strictEqual(error._tag, "SchemaError") + assert.strictEqual(executions, 0) + })) + }) }) diff --git a/repos/effect/packages/effect/test/unstable/sql/Statement.test.ts b/repos/effect/packages/effect/test/unstable/sql/Statement.test.ts new file mode 100644 index 0000000000..0f15afc2ec --- /dev/null +++ b/repos/effect/packages/effect/test/unstable/sql/Statement.test.ts @@ -0,0 +1,35 @@ +import { assert, describe, it } from "@effect/vitest" +import * as Statement from "effect/unstable/sql/Statement" + +describe("Statement", () => { + it("defaultTransforms ignores inherited properties", () => { + const row = Object.create({ inherited: 1 }) + row.own = 2 + + const nested = Statement.defaultTransforms((key) => key.toUpperCase()) + const flat = Statement.defaultTransforms((key) => key.toUpperCase(), false) + + assert.deepStrictEqual(nested.object(row), { OWN: 2 }) + assert.deepStrictEqual(nested.array([row]), [{ OWN: 2 }]) + assert.deepStrictEqual(nested.array([[row]]), [[{ OWN: 2 }]]) + assert.deepStrictEqual(flat.array([row]), [{ OWN: 2 }]) + }) + + it("compiles one fragment independently for each compiler", () => { + const postgres = Statement.makeCompiler({ + dialect: "pg", + placeholder: (index) => `$${index}`, + onIdentifier: Statement.defaultEscape("\""), + onRecordUpdate: () => ["", []], + onCustom: () => ["", []] + }) + const sqlite = Statement.makeCompilerSqlite() + const fragment = Statement.fragment([ + Statement.identifier("value"), + Statement.parameter(1) + ]) + + assert.deepStrictEqual(postgres.compile(fragment, false), ["\"value\"$1", [1]]) + assert.deepStrictEqual(sqlite.compile(fragment, false), ["\"value\"?", [1]]) + }) +}) diff --git a/repos/effect/packages/effect/test/unstable/workers/WorkerError.test.ts b/repos/effect/packages/effect/test/unstable/workers/WorkerError.test.ts index 7088cf930e..9ef0b36824 100644 --- a/repos/effect/packages/effect/test/unstable/workers/WorkerError.test.ts +++ b/repos/effect/packages/effect/test/unstable/workers/WorkerError.test.ts @@ -1,5 +1,6 @@ import { assert, describe, it } from "@effect/vitest" -import { Effect, Schema } from "effect" +import { Cause, Deferred, Effect, Exit, Fiber, Schema } from "effect" +import * as Worker from "effect/unstable/workers/Worker" import { isWorkerError, WorkerError, @@ -87,4 +88,43 @@ describe("WorkerError", () => { assert.strictEqual(decoded.message, "Failed to send message") })) }) + + describe("buffered sends", () => { + it.effect("reports postMessage failures as WorkerSendError", () => + Effect.gen(function*() { + const listening = yield* Deferred.make() + let emit!: (message: Worker.PlatformMessage) => void + const platform = Worker.makePlatform()({ + setup: () => + Effect.succeed({ + postMessage() { + throw new Error("post failed") + } + }), + listen: (options) => + Effect.sync(() => { + emit = options.emit + }).pipe(Effect.andThen(Deferred.succeed(listening, void 0))) + }) + const worker = yield* platform.spawn(0).pipe( + Effect.provideService(Worker.Spawner, () => ({})) + ) + + yield* worker.send("buffered") + const run = yield* Effect.forkChild(worker.run(() => Effect.void)) + yield* Deferred.await(listening) + yield* Effect.sync(() => emit([0])) + const exit = yield* Fiber.await(run) + + assert(Exit.isFailure(exit)) + if (Exit.isFailure(exit)) { + assert.isFalse(Cause.hasDies(exit.cause)) + const error = Cause.squash(exit.cause) + assert.isTrue(isWorkerError(error)) + if (isWorkerError(error)) { + assert.strictEqual(error.reason._tag, "WorkerSendError") + } + } + })) + }) }) diff --git a/repos/effect/packages/effect/test/unstable/workflow/WorkflowEngine.test.ts b/repos/effect/packages/effect/test/unstable/workflow/WorkflowEngine.test.ts index fe14b492cb..f417a3d90e 100644 --- a/repos/effect/packages/effect/test/unstable/workflow/WorkflowEngine.test.ts +++ b/repos/effect/packages/effect/test/unstable/workflow/WorkflowEngine.test.ts @@ -1,6 +1,6 @@ import { assert, describe, it } from "@effect/vitest" import { Effect, Exit, Layer, Option, Schema } from "effect" -import { Workflow, WorkflowEngine } from "effect/unstable/workflow" +import { DurableDeferred, Workflow, WorkflowEngine } from "effect/unstable/workflow" describe("WorkflowEngine", () => { const IncrementWorkflow = Workflow.make("WorkflowEngine/IncrementWorkflow", { @@ -57,4 +57,38 @@ describe("WorkflowEngine", () => { Layer.provideMerge(WorkflowEngine.layerMemory) )) )) + + it.effect("layerMemory closes finalizers registered before suspension", () => + Effect.gen(function*() { + const gate = DurableDeferred.make("WorkflowEngine/SuspendedScope/Gate") + const finalized: Array = [] + let runs = 0 + const Suspends = Workflow.make("WorkflowEngine/SuspendedScope", { + payload: { id: Schema.String }, + idempotencyKey: ({ id }) => id + }) + const layer = Suspends.toLayer(() => + Effect.gen(function*() { + const run = ++runs + yield* Workflow.addFinalizer(() => Effect.sync(() => finalized.push(run))) + yield* DurableDeferred.await(gate) + }) + ).pipe(Layer.provideMerge(WorkflowEngine.layerMemory)) + + yield* Effect.gen(function*() { + const payload = { id: "one" } + const executionId = yield* Suspends.execute(payload, { discard: true }) + let result = yield* Suspends.poll(executionId) + while (Option.isNone(result) || result.value._tag !== "Suspended") { + yield* Effect.yieldNow + result = yield* Suspends.poll(executionId) + } + + const token = DurableDeferred.tokenFromExecutionId(gate, { workflow: Suspends, executionId }) + yield* DurableDeferred.succeed(gate, { token, value: void 0 }) + yield* Suspends.execute(payload) + + assert.deepStrictEqual(finalized, [2, 1]) + }).pipe(Effect.provide(layer)) + })) }) diff --git a/repos/effect/packages/effect/test/utils/assert.ts b/repos/effect/packages/effect/test/utils/assert.ts index c4475f13a8..acb52784cd 100644 --- a/repos/effect/packages/effect/test/utils/assert.ts +++ b/repos/effect/packages/effect/test/utils/assert.ts @@ -1,4 +1,4 @@ -import { Cause, Equal, Option, Predicate, Result } from "effect" +import { Cause, Equal, Option, Predicate, Result, SchemaIssue } from "effect" import * as Exit from "effect/Exit" import * as assert from "node:assert" import { assert as vassert } from "vitest" @@ -15,15 +15,27 @@ export function fail(message: string) { } export function deepStrictEqual(actual: A, expected: A, message?: string, ..._: Array) { - assert.deepStrictEqual(actual, expected, message) + if (message === undefined) { + assert.deepStrictEqual(actual, expected) + } else { + assert.deepStrictEqual(actual, expected, message) + } } export function notDeepStrictEqual(actual: A, expected: A, message?: string, ..._: Array) { - assert.notDeepStrictEqual(actual, expected, message) + if (message !== undefined) { + assert.notDeepStrictEqual(actual, expected, message) + } else { + assert.notDeepStrictEqual(actual, expected) + } } export function strictEqual(actual: A, expected: A, message?: string, ..._: Array) { - assert.strictEqual(actual, expected, message) + if (message !== undefined) { + assert.strictEqual(actual, expected, message) + } else { + assert.strictEqual(actual, expected) + } } /** @@ -64,6 +76,17 @@ export function assertFalse(self: boolean, message?: string, ..._: Array) strictEqual(self, false, message) } +export function assertSchemaIssueError( + self: unknown, + expectedIssueMessage: string, + ..._: Array +): asserts self is Error & { readonly cause: SchemaIssue.Issue } { + assertInstanceOf(self, Error) + strictEqual(self.message, "Schema validation failed") + assertTrue(SchemaIssue.isIssue(self.cause)) + strictEqual(SchemaIssue.defaultFormatter(self.cause), expectedIssueMessage) +} + export function assertInclude(actual: string | undefined, expected: string, ..._: Array) { if (Predicate.isString(expected)) { if (!actual?.includes(expected)) { diff --git a/repos/effect/packages/effect/tsconfig.json b/repos/effect/packages/effect/tsconfig.json index b8984d44d9..1d6699a5a8 100644 --- a/repos/effect/packages/effect/tsconfig.json +++ b/repos/effect/packages/effect/tsconfig.json @@ -1,5 +1,5 @@ { - "$schema": "http://json.schemastore.org/tsconfig", + "$schema": "https://json.schemastore.org/tsconfig", "extends": "../../tsconfig.base.json", "include": ["src"], "compilerOptions": { diff --git a/repos/effect/packages/effect/typeperf/README.md b/repos/effect/packages/effect/typeperf/README.md index e33ee0e82c..fdbb4e9fa1 100644 --- a/repos/effect/packages/effect/typeperf/README.md +++ b/repos/effect/packages/effect/typeperf/README.md @@ -1,18 +1,24 @@ # Effect Type Performance -This harness measures TypeScript type instantiations for focused fixtures. +This harness measures deterministic TypeScript diagnostics for focused +fixtures. Type instantiations and materialized types are regression gates, while +symbols are reported for additional context. -Use it when a Schema type-level change needs a small regression guard for the -specific type path that was optimized. +Use it when a type-level change needs a small regression guard for the specific +type path that was optimized. ## Core Model -Each suite has one shared baseline. Fixture cost is reported as: +Each suite has one shared baseline. Fixture cost is reported separately for +instantiations, types, and symbols as: ```txt -delta = fixture instantiations - suite baseline instantiations +metric delta = fixture metric - suite baseline metric ``` +Threshold files store exact maximum deltas for instantiations and types. Symbol +deltas are informational and do not affect the command status. + The runner compiles each baseline and fixture as an isolated TypeScript program by generating temporary `tsconfig` files under `tmp/typeperf`. @@ -50,8 +56,31 @@ pnpm typeperf schema/struct-required --update Without a target, the runner checks every suite and fixture. A target can be a suite name (`schema`) or an individual fixture (`schema/struct-required`). -`--update` writes exact measured deltas to threshold files. When a target is -provided, only the selected suite or fixture is updated. +`--update` writes exact measured instantiation and type deltas to threshold +files. When a target is provided, only the selected suite or fixture is updated. + +## Cross-Ref Comparisons + +Use the separate comparison runner when a changeset needs measurements against +another Git revision: + +```sh +pnpm typeperf-compare httpapi --base main --head HEAD +pnpm typeperf-compare httpapi/endpoint-count-100 --base main --head HEAD +``` + +The runner creates detached worktrees under `tmp/typeperf-compare`, copies the +same generated fixture sources into both worktrees, and compiles them with the +TypeScript installation from the current checkout. Each fixture and baseline is +compiled once per revision. The normal `pnpm typeperf` suite and its thresholds +are not affected. + +Comparison fixtures use only APIs shared by both revisions. They also contain +type-level assertions that reject degenerate results such as missing methods or +`never` endpoint selections. A fixture is comparable only when the exact same +source compiles on both revisions. Results are printed to the terminal and +written as JSON under `tmp/typeperf-compare/results/`, including the resolved +Git SHAs, TypeScript version, and fixture-suite hash. ## Adding A Fixture @@ -116,8 +145,8 @@ Then register it: ## Adding A Suite Add a suite when fixtures need a different shared baseline. For example, -Schema-specific fixtures share the `schema` baseline, while a future HTTP API -suite may need a different import and warmup. +Schema-specific fixtures share the `schema` baseline, while HTTP API fixtures +use a different import and warmup. 1. Create `suites//baseline.ts`. 2. Create `suites//fixtures/`. @@ -156,8 +185,17 @@ pnpm typeperf schema/struct-required --update pnpm typeperf schema/struct-required ``` -`--update` writes the exact measured delta as `maxDelta`. It does not add a -margin. +`--update` writes the exact measured deltas as `maxInstantiationsDelta` and +`maxTypesDelta`. It does not add a margin: + +```json +{ + "struct-required": { + "maxInstantiationsDelta": 457, + "maxTypesDelta": 50 + } +} +``` ## Validation Checklist diff --git a/repos/effect/packages/effect/typeperf/compare.mjs b/repos/effect/packages/effect/typeperf/compare.mjs new file mode 100644 index 0000000000..62c86c3cac --- /dev/null +++ b/repos/effect/packages/effect/typeperf/compare.mjs @@ -0,0 +1,390 @@ +import { spawnSync } from "node:child_process" +import { createHash } from "node:crypto" +import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, symlinkSync, writeFileSync } from "node:fs" +import { createRequire } from "node:module" +import { dirname, join, relative, resolve } from "node:path" +import { fileURLToPath } from "node:url" +import { httpapi } from "./compare/httpapi.mjs" + +const require = createRequire(import.meta.url) +const args = process.argv.slice(2) +const help = args.includes("--help") || args.includes("-h") + +if (help) { + console.log(`Usage: pnpm typeperf-compare [suite[/fixture]] [--base ] [--head ] + +Options: + --base Base Git ref. Defaults to main. + --head Head Git ref. Defaults to HEAD. +`) + process.exit(0) +} + +let baseRef = "main" +let headRef = "HEAD" +const targets = [] + +for (let index = 0; index < args.length; index++) { + const arg = args[index] + if (arg === "--base" || arg === "--head") { + const value = args[++index] + if (value === undefined || value.startsWith("-")) { + console.error(`Missing value for ${arg}`) + process.exit(1) + } + if (arg === "--base") { + baseRef = value + } else { + headRef = value + } + } else if (arg.startsWith("-")) { + console.error(`Unknown option: ${arg}`) + process.exit(1) + } else { + targets.push(arg) + } +} + +if (targets.length > 1) { + console.error(`Expected at most one target, got: ${targets.join(", ")}`) + process.exit(1) +} + +const typeperfDir = dirname(fileURLToPath(import.meta.url)) +const effectDir = resolve(typeperfDir, "..") +const repoRoot = resolve(effectDir, "../..") +const tmpRoot = join(repoRoot, "tmp", "typeperf-compare") +const worktreesRoot = join(tmpRoot, "worktrees") +const resultsRoot = join(tmpRoot, "results") +const suites = [httpapi] +const selectedTarget = targets[0] +const [selectedSuiteName, selectedFixtureName] = selectedTarget === undefined ? [] : selectedTarget.split("/") + +if (selectedTarget !== undefined && (selectedSuiteName === "" || selectedFixtureName === "")) { + console.error(`Invalid target: ${selectedTarget}`) + process.exit(1) +} + +const run = (command, commandArgs, options = {}) => + spawnSync(command, commandArgs, { + encoding: "utf8", + maxBuffer: 50 * 1024 * 1024, + ...options + }) + +const runGit = (gitArgs) => { + const result = run("git", gitArgs, { 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 readJson = (path) => JSON.parse(readFileSync(path, "utf8")) + +const writeJson = (path, value) => { + mkdirSync(dirname(path), { recursive: true }) + writeFileSync(path, `${JSON.stringify(value, null, 2)}\n`) +} + +const writeSource = (path, source) => { + mkdirSync(dirname(path), { recursive: true }) + writeFileSync(path, source.endsWith("\n") ? source : `${source}\n`) +} + +const sanitize = (name) => name.replace(/[^a-zA-Z0-9._-]/g, "-") + +const suiteHash = (suite) => { + const hash = createHash("sha256") + hash.update(`baseline.ts\0${suite.baseline}\0`) + for (const fixture of suite.fixtures) { + hash.update(`fixtures/${fixture.name}.ts\0${fixture.source}\0`) + } + return hash.digest("hex") +} + +const typescriptPackagePath = require.resolve("typescript/package.json") +const typescriptDir = dirname(typescriptPackagePath) +const typescriptVersion = readJson(typescriptPackagePath).version +const tscPath = join(typescriptDir, "lib", "tsc.js") + +const linkDirectory = (source, target) => { + if (existsSync(target)) { + return + } + symlinkSync(source, target, process.platform === "win32" ? "junction" : "dir") +} + +const materializeSuite = (targetRepoRoot, suite) => { + const targetEffectDir = join(targetRepoRoot, "packages", "effect") + const suiteDir = join(targetEffectDir, ".typeperf-compare", suite.name) + writeSource(join(suiteDir, "baseline.ts"), suite.baseline) + for (const fixture of suite.fixtures) { + writeSource(join(suiteDir, "fixtures", `${fixture.name}.ts`), fixture.source) + } + return { + effectDir: targetEffectDir, + suiteDir + } +} + +const writeTempTsconfig = (target, suiteName, subjectName, sourcePath) => { + const configDir = join(target.effectDir, ".typeperf-compare", "configs") + const tsconfigPath = join(configDir, `${sanitize(`${suiteName}-${subjectName}`)}.json`) + writeJson(tsconfigPath, { + extends: join(target.effectDir, "tsconfig.json"), + files: [sourcePath], + include: [], + compilerOptions: { + noEmit: true, + declaration: false, + declarationMap: false, + sourceMap: false, + plugins: [], + rootDir: target.effectDir, + incremental: false, + composite: false + } + }) + return tsconfigPath +} + +const readMetric = (output, metric) => { + const match = output.match(new RegExp(`^${metric}:\\s+(\\d+)$`, "m")) + return match === null ? undefined : Number(match[1]) +} + +const measure = (target, suiteName, subjectName, sourcePath) => { + const tsconfigPath = writeTempTsconfig(target, suiteName, subjectName, sourcePath) + const result = run(process.execPath, [tscPath, "-p", tsconfigPath, "--extendedDiagnostics", "--noEmit"], { + cwd: target.effectDir + }) + const output = `${result.stdout ?? ""}${result.stderr ?? ""}` + if (result.error) { + return { ok: false, error: result.error.message, output } + } + if (result.status !== 0) { + return { + ok: false, + exitCode: result.status, + signal: result.signal ?? undefined, + output + } + } + const instantiations = readMetric(output, "Instantiations") + const types = readMetric(output, "Types") + const symbols = readMetric(output, "Symbols") + if (instantiations === undefined || types === undefined || symbols === undefined) { + return { ok: false, error: "Could not read extended diagnostics", output } + } + return { + ok: true, + diagnostics: { instantiations, types, symbols } + } +} + +const subtractDiagnostics = (total, baseline) => ({ + instantiations: total.instantiations - baseline.instantiations, + types: total.types - baseline.types, + symbols: total.symbols - baseline.symbols +}) + +const failureLabel = (measurement) => { + if (measurement.signal !== undefined) { + return `signal:${measurement.signal}` + } + if (measurement.exitCode !== undefined) { + return `exit:${measurement.exitCode}` + } + return measurement.error ?? "failed" +} + +const formatNumber = (value) => value.toLocaleString("en-US") +const formatSigned = (value) => value === 0 ? "0" : `${value > 0 ? "+" : ""}${formatNumber(value)}` + +const printTable = (rows) => { + const table = [ + ["suite", "fixture", "baseInst", "headInst", "instDiff", "baseTypes", "headTypes", "typesDiff", "symbolsDiff", "status"], + ...rows.map((row) => [ + row.suite, + row.fixture, + row.base?.instantiations === undefined ? "-" : formatNumber(row.base.instantiations), + row.head?.instantiations === undefined ? "-" : formatNumber(row.head.instantiations), + row.diff?.instantiations === undefined ? "-" : formatSigned(row.diff.instantiations), + row.base?.types === undefined ? "-" : formatNumber(row.base.types), + row.head?.types === undefined ? "-" : formatNumber(row.head.types), + row.diff?.types === undefined ? "-" : formatSigned(row.diff.types), + row.diff?.symbols === undefined ? "-" : formatSigned(row.diff.symbols), + row.status + ]) + ] + const widths = table[0].map((_, index) => Math.max(...table.map((row) => row[index].length))) + for (const [index, row] of table.entries()) { + console.log(row.map((cell, cellIndex) => cell.padEnd(widths[cellIndex])).join(" ")) + if (index === 0) { + console.log(widths.map((width) => "-".repeat(width)).join(" ")) + } + } +} + +const createWorktree = (runRoot, name, sha) => { + const path = join(runRoot, name) + runGit(["worktree", "add", "--detach", path, sha]) + 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 removeWorktree = (path) => { + const result = run("git", ["worktree", "remove", "--force", path], { cwd: repoRoot }) + if (result.status !== 0) { + process.stderr.write(`${result.stdout ?? ""}${result.stderr ?? ""}`) + } +} + +const main = () => { + const baseSha = resolveRef(baseRef) + const headSha = resolveRef(headRef) + mkdirSync(worktreesRoot, { recursive: true }) + const runRoot = mkdtempSync(join(worktreesRoot, "run-")) + const worktrees = [] + const rows = [] + let failed = false + + try { + const baseWorktree = createWorktree(runRoot, "base", baseSha) + worktrees.push(baseWorktree) + const headWorktree = createWorktree(runRoot, "head", headSha) + worktrees.push(headWorktree) + + const baseTargetRoot = materializeSuite(baseWorktree, httpapi) + const headTargetRoot = materializeSuite(headWorktree, httpapi) + const matchedSuites = suites.filter((suite) => selectedSuiteName === undefined || suite.name === selectedSuiteName) + if (matchedSuites.length === 0) { + throw new Error(`Unknown comparison suite: ${selectedSuiteName}`) + } + + for (const suite of matchedSuites) { + const baseTarget = suite.name === httpapi.name ? baseTargetRoot : materializeSuite(baseWorktree, suite) + const headTarget = suite.name === httpapi.name ? headTargetRoot : materializeSuite(headWorktree, suite) + const baselineRelativePath = "baseline.ts" + const baseBaseline = measure( + baseTarget, + suite.name, + "baseline", + join(baseTarget.suiteDir, baselineRelativePath) + ) + const headBaseline = measure( + headTarget, + suite.name, + "baseline", + join(headTarget.suiteDir, baselineRelativePath) + ) + if (!baseBaseline.ok || !headBaseline.ok) { + if (!baseBaseline.ok) { + process.stderr.write(baseBaseline.output) + } + if (!headBaseline.ok) { + process.stderr.write(headBaseline.output) + } + throw new Error(`Baseline failed for comparison suite ${suite.name}`) + } + + const fixtures = suite.fixtures.filter((fixture) => + selectedFixtureName === undefined || fixture.name === selectedFixtureName + ) + if (selectedFixtureName !== undefined && fixtures.length === 0) { + throw new Error(`Unknown comparison fixture: ${suite.name}/${selectedFixtureName}`) + } + + for (const fixture of fixtures) { + const baseMeasurement = measure( + baseTarget, + suite.name, + fixture.name, + join(baseTarget.suiteDir, "fixtures", `${fixture.name}.ts`) + ) + const headMeasurement = measure( + headTarget, + suite.name, + fixture.name, + join(headTarget.suiteDir, "fixtures", `${fixture.name}.ts`) + ) + let base + let head + let diff + let status + + if (baseMeasurement.ok && headMeasurement.ok) { + base = subtractDiagnostics(baseMeasurement.diagnostics, baseBaseline.diagnostics) + head = subtractDiagnostics(headMeasurement.diagnostics, headBaseline.diagnostics) + diff = subtractDiagnostics(head, base) + status = "ok" + } else if (!baseMeasurement.ok && headMeasurement.ok) { + head = subtractDiagnostics(headMeasurement.diagnostics, headBaseline.diagnostics) + status = `base:${failureLabel(baseMeasurement)}` + failed = true + } else if (baseMeasurement.ok && !headMeasurement.ok) { + base = subtractDiagnostics(baseMeasurement.diagnostics, baseBaseline.diagnostics) + status = `head:${failureLabel(headMeasurement)}` + failed = true + } else { + status = `base:${failureLabel(baseMeasurement)},head:${failureLabel(headMeasurement)}` + failed = true + } + + if (!baseMeasurement.ok && baseMeasurement.output !== "") { + process.stderr.write(baseMeasurement.output) + } + if (!headMeasurement.ok && headMeasurement.output !== "") { + process.stderr.write(headMeasurement.output) + } + rows.push({ suite: suite.name, fixture: fixture.name, base, head, diff, status }) + } + } + + const report = { + compiler: { + name: "typescript", + version: typescriptVersion + }, + base: { ref: baseRef, sha: baseSha }, + head: { ref: headRef, sha: headSha }, + target: selectedTarget ?? null, + suites: matchedSuites.map((suite) => ({ name: suite.name, hash: suiteHash(suite) })), + rows + } + const reportTarget = sanitize(selectedTarget ?? "all") + const reportName = `${baseSha.slice(0, 12)}..${headSha.slice(0, 12)}-${reportTarget}.json` + const reportPath = join(resultsRoot, reportName) + writeJson(reportPath, report) + printTable(rows) + console.log(`\nReport: ${relative(repoRoot, reportPath)}`) + } finally { + for (const worktree of worktrees.reverse()) { + removeWorktree(worktree) + } + rmSync(runRoot, { recursive: true, force: true }) + } + + if (failed) { + process.exitCode = 1 + } +} + +try { + main() +} catch (error) { + console.error(error instanceof Error ? error.message : error) + process.exitCode = 1 +} diff --git a/repos/effect/packages/effect/typeperf/compare/httpapi.mjs b/repos/effect/packages/effect/typeperf/compare/httpapi.mjs new file mode 100644 index 0000000000..b2d03e0462 --- /dev/null +++ b/repos/effect/packages/effect/typeperf/compare/httpapi.mjs @@ -0,0 +1,269 @@ +const endpointIdentifier = (index) => `getUser${String(index).padStart(4, "0")}` + +const endpoint = (index, indent = " ") => `${indent}HttpApiEndpoint.get("${endpointIdentifier(index)}", "/users/:id", { +${indent} params: Params, +${indent} success: User +${indent}})` + +const endpoints = (count, indent = " ") => + Array.from({ length: count }, (_, index) => endpoint(index + 1, indent)).join(",\n") + +const imports = ({ builder = false, client = false, effect = false, httpClient = false } = {}) => { + const effectImports = effect ? "Effect, Schema" : "Schema" + const httpApiImports = ["HttpApi", builder ? "HttpApiBuilder" : undefined, client ? "HttpApiClient" : undefined, + "HttpApiEndpoint", "HttpApiGroup"].filter((name) => name !== undefined).join(", ") + return `import { ${effectImports} } from "effect" +${httpClient ? `import { HttpClient } from "effect/unstable/http"\n` : ""}import { ${httpApiImports} } from "effect/unstable/httpapi"` +} + +const warmup = `Schema.String +HttpApi.make("Api") +HttpApiGroup.make("users") +HttpApiEndpoint.get("warmup", "/warmup")` + +const schemas = `const Params = Schema.Struct({ + id: Schema.FiniteFromString +}) + +const User = Schema.Struct({ + id: Schema.String, + name: Schema.String +})` + +const assertions = `type Assert = T +type IsNever = [T] extends [never] ? true : false` + +const api = (count, options = {}) => { + const groupIdentifier = options.groupIdentifier ?? "users" + const topLevel = options.topLevel === true ? ", { topLevel: true }" : "" + return `const api = HttpApi.make("Api").add( + HttpApiGroup.make("${groupIdentifier}"${topLevel}).add( +${endpoints(count)} + ) +)` +} + +const endpointDeclarationFixture = (count) => `// Compares endpoint declaration for ${count} same-shaped endpoints. +${imports()} + +${warmup} + +${schemas} + +${api(count)} + +type Groups = typeof api extends HttpApi.HttpApi ? Groups : never +type EndpointUnion = HttpApiGroup.Endpoints + +${assertions} + +export type Api = typeof api +export type EndpointsExist = Assert extends false ? true : false> +export type EndpointRequests = HttpApiEndpoint.Request +export type ServerServices = HttpApiEndpoint.ServerServices +export type ClientServices = HttpApiEndpoint.ClientServices +` + +const clientMethodsFixture = () => `// Compares grouped client method derivation from 500 same-shaped endpoints. +${imports({ client: true })} + +${warmup} + +${schemas} + +${api(500)} + +type Client = HttpApiClient.ForApi +type Users = Client["users"] +type Methods = Users[keyof Users] + +${assertions} + +export type HasUsersGroup = Assert<"users" extends keyof Client ? true : false> +export type HasLastEndpoint = Assert<"getUser0500" extends keyof Users ? true : false> +export type MethodsExist = Assert extends false ? true : false> +export type ClientMethodRequests = Parameters[0] +export type ClientMethodResults = ReturnType +` + +const topLevelClientMethodsFixture = () => `// Compares top-level client method derivation from 500 same-shaped endpoints. +${imports({ client: true })} + +${warmup} + +${schemas} + +${api(500, { groupIdentifier: "top", topLevel: true })} + +type Client = HttpApiClient.ForApi +type Methods = Client[keyof Client] + +${assertions} + +export type HasLastEndpoint = Assert<"getUser0500" extends keyof Client ? true : false> +export type MethodsExist = Assert extends false ? true : false> +export type ClientMethodRequests = Parameters[0] +export type ClientMethodResults = ReturnType +` + +const group = (groupIndex, endpointCount) => { + const identifier = `group${String(groupIndex).padStart(3, "0")}` + return ` HttpApiGroup.make("${identifier}").add( +${endpoints(endpointCount)} + )` +} + +const multipleGroupsApi = (groupCount, endpointCount) => `const api = HttpApi.make("Api").add( +${Array.from({ length: groupCount }, (_, index) => group(index + 1, endpointCount)).join(",\n")} +)` + +const clientGroupsFixture = () => `// Compares client derivation for 100 groups with 5 same-shaped endpoints each. +${imports({ client: true })} + +${warmup} + +${schemas} + +${multipleGroupsApi(100, 5)} + +type Client = HttpApiClient.ForApi +type GroupClients = Client[keyof Client] +type Methods = GroupClients[keyof GroupClients] + +${assertions} + +export type HasLastGroup = Assert<"group100" extends keyof Client ? true : false> +export type HasLastEndpoint = Assert<"getUser0005" extends keyof GroupClients ? true : false> +export type MethodsExist = Assert extends false ? true : false> +export type ClientMethodRequests = Parameters[0] +export type ClientMethodResults = ReturnType +` + +const clientEndpointFixture = () => `// Compares HttpApiClient.endpoint selection from 500 same-shaped endpoints. +${imports({ client: true, effect: true, httpClient: true })} + +${warmup} + +${schemas} + +${api(500)} + +declare const httpClient: HttpClient.HttpClient + +const endpointClient = HttpApiClient.endpoint(api, { + group: "users", + endpoint: "getUser0500", + httpClient +}) + +type Method = Effect.Success + +${assertions} + +export type MethodExists = Assert extends false ? true : false> +export type EndpointClientRequest = Parameters[0] +export type EndpointClientResult = ReturnType +` + +const urlBuilderFixture = (topLevel) => `// Compares ${topLevel ? "top-level" : "grouped"} URL builder derivation from 500 same-shaped endpoints. +${imports({ client: true })} + +${warmup} + +${schemas} + +${api(500, topLevel ? { groupIdentifier: "top", topLevel: true } : {})} + +type UrlBuilder = HttpApiClient.UrlBuilder +type Methods = ${topLevel ? "UrlBuilder[keyof UrlBuilder]" : "UrlBuilder[\"users\"][keyof UrlBuilder[\"users\"]]"} + +${assertions} + +export type HasLastEndpoint = Assert<"getUser0500" extends ${topLevel ? "keyof UrlBuilder" : "keyof UrlBuilder[\"users\"]"} ? true : false> +export type MethodsExist = Assert extends false ? true : false> +export type UrlBuilderMethodRequests = Parameters[0] +export type UrlBuilderMethodResults = ReturnType +` + +const builderEndpointFixture = () => `// Compares HttpApiBuilder.endpoint selection from 500 same-shaped endpoints. +${imports({ builder: true, effect: true })} + +${warmup} + +${schemas} + +${api(500)} + +const endpointHandler = HttpApiBuilder.endpoint( + api, + "users", + "getUser0500", + ({ params }) => Effect.succeed({ id: String(params.id), name: "Ada" }) +) + +type HttpEffect = Effect.Success + +${assertions} + +export type EndpointHandlerExists = Assert extends false ? true : false> +export type HttpEffectExists = Assert extends false ? true : false> +export type EndpointHandlerServices = Effect.Services +export type HttpEffectServices = Effect.Services +` + +const handlerCall = (index, raw) => ` .${raw ? "handleRaw" : "handle"}("${endpointIdentifier(index)}", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }))` + +const handlersFixture = (count, raw = false) => `// Compares chained builder ${raw ? "handleRaw" : "handle"} registration for ${count} same-shaped endpoints. +${imports({ builder: true, effect: true })} + +${warmup} + +${schemas} + +const group = HttpApiGroup.make("users").add( +${endpoints(count, " ")} +) + +const api = HttpApi.make("Api").add(group) + +const layer = HttpApiBuilder.group(api, "users", (handlers) => + handlers +${Array.from({ length: count }, (_, index) => handlerCall(index + 1, raw)).join("\n")} +) + +${assertions} + +export type LayerExists = Assert extends false ? true : false> +export type Layer = typeof layer +` + +export const httpapi = { + name: "httpapi", + baseline: `// Shared cross-ref HttpApi baseline. +${imports()} + +${warmup} +`, + fixtures: [ + ...[10, 50, 100, 500].map((count) => ({ + name: `endpoint-count-${count}`, + source: endpointDeclarationFixture(count) + })), + { name: "client-methods-count-500", source: clientMethodsFixture() }, + { name: "client-top-level-methods-count-500", source: topLevelClientMethodsFixture() }, + { name: "client-groups-100x5-count-500", source: clientGroupsFixture() }, + { name: "client-endpoint-method-count-500", source: clientEndpointFixture() }, + { name: "url-builder-count-500", source: urlBuilderFixture(false) }, + { name: "url-builder-top-level-count-500", source: urlBuilderFixture(true) }, + { name: "builder-endpoint-count-500", source: builderEndpointFixture() }, + ...[10, 50, 100, 500].map((count) => ({ + name: `builder-handlers-count-${count}`, + source: handlersFixture(count) + })), + { name: "builder-raw-handlers-count-500", source: handlersFixture(500, true) } + ] +} diff --git a/repos/effect/packages/effect/typeperf/config.json b/repos/effect/packages/effect/typeperf/config.json index 9115b501cf..4fad18c175 100644 --- a/repos/effect/packages/effect/typeperf/config.json +++ b/repos/effect/packages/effect/typeperf/config.json @@ -94,6 +94,73 @@ "file": "suites/schema/fixtures/exit.ts" } ] + }, + { + "name": "httpapi", + "baseline": "suites/httpapi/baseline.ts", + "thresholds": "suites/httpapi/thresholds.json", + "fixtures": [ + { + "name": "endpoint-count-500", + "file": "suites/httpapi/fixtures/endpoint-count-500.ts" + }, + { + "name": "endpoint-class-count-500", + "file": "suites/httpapi/fixtures/endpoint-class-count-500.ts" + }, + { + "name": "group-endpoints-count-500", + "file": "suites/httpapi/fixtures/group-endpoints-count-500.ts" + }, + { + "name": "client-methods-count-500", + "file": "suites/httpapi/fixtures/client-methods-count-500.ts" + }, + { + "name": "client-top-level-count-500", + "file": "suites/httpapi/fixtures/client-top-level-count-500.ts" + }, + { + "name": "client-groups-100x5-count-500", + "file": "suites/httpapi/fixtures/client-groups-100x5-count-500.ts" + }, + { + "name": "client-group-count-500", + "file": "suites/httpapi/fixtures/client-group-count-500.ts" + }, + { + "name": "client-endpoint-count-500", + "file": "suites/httpapi/fixtures/client-endpoint-count-500.ts" + }, + { + "name": "url-builder-count-500", + "file": "suites/httpapi/fixtures/url-builder-count-500.ts" + }, + { + "name": "url-builder-top-level-count-500", + "file": "suites/httpapi/fixtures/url-builder-top-level-count-500.ts" + }, + { + "name": "builder-endpoint-count-500", + "file": "suites/httpapi/fixtures/builder-endpoint-count-500.ts" + }, + { + "name": "builder-handlers-count-100", + "file": "suites/httpapi/fixtures/builder-handlers-count-100.ts" + }, + { + "name": "builder-raw-handlers-count-100", + "file": "suites/httpapi/fixtures/builder-raw-handlers-count-100.ts" + }, + { + "name": "builder-handle-all-count-500", + "file": "suites/httpapi/fixtures/builder-handle-all-count-500.ts" + }, + { + "name": "builder-handle-all-two-batches-count-500", + "file": "suites/httpapi/fixtures/builder-handle-all-two-batches-count-500.ts" + } + ] } ] } diff --git a/repos/effect/packages/effect/typeperf/run.mjs b/repos/effect/packages/effect/typeperf/run.mjs index 5a7a33a63d..6f202bc73e 100644 --- a/repos/effect/packages/effect/typeperf/run.mjs +++ b/repos/effect/packages/effect/typeperf/run.mjs @@ -54,6 +54,7 @@ const writeTempTsconfig = (suiteName, subjectName, sourcePath) => { declaration: false, declarationMap: false, sourceMap: false, + plugins: [], rootDir: effectDir, incremental: false, composite: false, @@ -63,14 +64,20 @@ const writeTempTsconfig = (suiteName, subjectName, sourcePath) => { return tsconfigPath } -const readInstantiations = (output, label) => { - const match = output.match(/^Instantiations:\s+(\d+)$/m) +const readMetric = (output, label, metric) => { + const match = output.match(new RegExp(`^${metric}:\\s+(\\d+)$`, "m")) if (!match) { - throw new Error(`Could not read Instantiations from compiler output for ${label}`) + throw new Error(`Could not read ${metric} from compiler output for ${label}`) } return Number(match[1]) } +const readDiagnostics = (output, label) => ({ + instantiations: readMetric(output, label, "Instantiations"), + types: readMetric(output, label, "Types"), + symbols: readMetric(output, label, "Symbols") +}) + const measure = (suiteName, subjectName, sourcePath) => { const tsconfigPath = writeTempTsconfig(suiteName, subjectName, sourcePath) const result = spawnSync("pnpm", ["exec", "tsc", "-p", tsconfigPath, "--extendedDiagnostics", "--noEmit"], { @@ -85,21 +92,28 @@ const measure = (suiteName, subjectName, sourcePath) => { process.stderr.write(output) throw new Error(`Compiler failed for ${suiteName}/${subjectName}`) } - return readInstantiations(output, `${suiteName}/${subjectName}`) + return readDiagnostics(output, `${suiteName}/${subjectName}`) } +const subtractDiagnostics = (total, baseline) => ({ + instantiations: total.instantiations - baseline.instantiations, + types: total.types - baseline.types, + symbols: total.symbols - baseline.symbols +}) + const formatNumber = (n) => n.toLocaleString("en-US") const printTable = (rows) => { const table = [ - ["suite", "fixture", "baseline", "total", "delta", "maxDelta", "status"], + ["suite", "fixture", "instDelta", "instMax", "typesDelta", "typesMax", "symbolsDelta", "status"], ...rows.map((row) => [ row.suite, row.fixture, - formatNumber(row.baseline), - formatNumber(row.total), - formatNumber(row.delta), - row.maxDelta === undefined ? "-" : formatNumber(row.maxDelta), + formatNumber(row.delta.instantiations), + row.maxInstantiationsDelta === undefined ? "-" : formatNumber(row.maxInstantiationsDelta), + formatNumber(row.delta.types), + row.maxTypesDelta === undefined ? "-" : formatNumber(row.maxTypesDelta), + formatNumber(row.delta.symbols), row.status ]) ] @@ -143,31 +157,49 @@ for (const suite of config.suites) { matchedTarget = true const fixturePath = toAbsolute(fixture.file) const total = measure(suite.name, fixture.name, fixturePath) - const delta = total - baseline + const delta = subtractDiagnostics(total, baseline) const threshold = thresholds[fixture.name] - const maxDelta = typeof threshold?.maxDelta === "number" ? threshold.maxDelta : undefined + const maxInstantiationsDelta = typeof threshold?.maxInstantiationsDelta === "number" + ? threshold.maxInstantiationsDelta + : undefined + const maxTypesDelta = typeof threshold?.maxTypesDelta === "number" ? threshold.maxTypesDelta : undefined let status if (update) { - thresholds[fixture.name] = { maxDelta: delta } + thresholds[fixture.name] = { + maxInstantiationsDelta: delta.instantiations, + maxTypesDelta: delta.types + } status = "updated" - } else if (maxDelta === undefined) { - status = "missing" - failed = true - } else if (delta <= maxDelta) { - status = "ok" } else { - status = "fail" - failed = true + const missing = [ + maxInstantiationsDelta === undefined ? "instantiations" : undefined, + maxTypesDelta === undefined ? "types" : undefined + ].filter((metric) => metric !== undefined) + const exceeded = [ + maxInstantiationsDelta !== undefined && delta.instantiations > maxInstantiationsDelta + ? "instantiations" + : undefined, + maxTypesDelta !== undefined && delta.types > maxTypesDelta ? "types" : undefined + ].filter((metric) => metric !== undefined) + + if (missing.length > 0) { + status = `missing:${missing.join(",")}` + failed = true + } else if (exceeded.length > 0) { + status = `fail:${exceeded.join(",")}` + failed = true + } else { + status = "ok" + } } rows.push({ suite: suite.name, fixture: fixture.name, - baseline, - total, delta, - maxDelta: update ? delta : maxDelta, + maxInstantiationsDelta: update ? delta.instantiations : maxInstantiationsDelta, + maxTypesDelta: update ? delta.types : maxTypesDelta, status }) } diff --git a/repos/effect/packages/effect/typeperf/suites/httpapi/baseline.ts b/repos/effect/packages/effect/typeperf/suites/httpapi/baseline.ts new file mode 100644 index 0000000000..bc340abeb5 --- /dev/null +++ b/repos/effect/packages/effect/typeperf/suites/httpapi/baseline.ts @@ -0,0 +1,8 @@ +// Shared HttpApi suite baseline that touches each public module used by the fixtures. +import { Schema } from "effect" +import { HttpApi, HttpApiEndpoint, HttpApiGroup } from "effect/unstable/httpapi" + +Schema.String +HttpApi.make("Api") +HttpApiGroup.make("users") +HttpApiEndpoint.get("warmup", "/warmup") diff --git a/repos/effect/packages/effect/typeperf/suites/httpapi/fixtures/_endpoint-selection-types.ts b/repos/effect/packages/effect/typeperf/suites/httpapi/fixtures/_endpoint-selection-types.ts new file mode 100644 index 0000000000..96cf3c9562 --- /dev/null +++ b/repos/effect/packages/effect/typeperf/suites/httpapi/fixtures/_endpoint-selection-types.ts @@ -0,0 +1,5 @@ +// Shared type-only APIs for client and builder endpoint-selection fixtures. +import type { HttpApi } from "effect/unstable/httpapi" +import type { Group } from "./_grouped-api-types.ts" + +export type Api = HttpApi.HttpApi<"Api", Group> diff --git a/repos/effect/packages/effect/typeperf/suites/httpapi/fixtures/_grouped-api-500.ts b/repos/effect/packages/effect/typeperf/suites/httpapi/fixtures/_grouped-api-500.ts new file mode 100644 index 0000000000..9915999b61 --- /dev/null +++ b/repos/effect/packages/effect/typeperf/suites/httpapi/fixtures/_grouped-api-500.ts @@ -0,0 +1,2022 @@ +// Shared API with one group containing 500 same-shaped endpoints. +import { Schema } from "effect" +import { HttpApi, HttpApiEndpoint, HttpApiGroup } from "effect/unstable/httpapi" + +const Params = Schema.Struct({ + id: Schema.FiniteFromString +}) + +const User = Schema.Struct({ + id: Schema.String, + name: Schema.String +}) + +export const api = HttpApi.make("Api").add( + HttpApiGroup.make("users").add( + HttpApiEndpoint.get("getUser0001", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0002", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0003", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0004", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0005", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0006", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0007", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0008", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0009", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0010", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0011", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0012", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0013", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0014", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0015", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0016", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0017", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0018", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0019", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0020", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0021", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0022", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0023", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0024", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0025", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0026", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0027", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0028", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0029", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0030", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0031", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0032", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0033", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0034", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0035", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0036", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0037", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0038", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0039", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0040", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0041", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0042", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0043", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0044", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0045", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0046", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0047", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0048", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0049", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0050", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0051", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0052", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0053", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0054", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0055", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0056", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0057", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0058", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0059", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0060", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0061", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0062", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0063", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0064", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0065", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0066", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0067", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0068", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0069", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0070", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0071", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0072", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0073", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0074", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0075", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0076", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0077", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0078", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0079", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0080", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0081", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0082", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0083", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0084", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0085", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0086", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0087", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0088", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0089", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0090", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0091", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0092", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0093", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0094", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0095", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0096", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0097", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0098", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0099", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0100", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0101", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0102", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0103", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0104", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0105", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0106", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0107", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0108", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0109", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0110", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0111", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0112", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0113", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0114", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0115", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0116", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0117", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0118", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0119", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0120", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0121", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0122", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0123", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0124", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0125", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0126", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0127", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0128", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0129", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0130", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0131", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0132", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0133", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0134", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0135", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0136", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0137", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0138", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0139", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0140", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0141", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0142", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0143", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0144", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0145", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0146", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0147", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0148", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0149", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0150", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0151", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0152", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0153", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0154", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0155", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0156", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0157", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0158", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0159", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0160", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0161", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0162", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0163", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0164", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0165", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0166", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0167", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0168", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0169", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0170", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0171", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0172", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0173", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0174", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0175", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0176", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0177", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0178", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0179", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0180", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0181", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0182", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0183", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0184", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0185", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0186", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0187", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0188", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0189", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0190", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0191", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0192", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0193", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0194", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0195", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0196", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0197", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0198", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0199", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0200", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0201", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0202", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0203", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0204", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0205", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0206", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0207", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0208", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0209", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0210", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0211", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0212", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0213", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0214", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0215", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0216", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0217", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0218", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0219", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0220", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0221", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0222", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0223", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0224", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0225", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0226", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0227", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0228", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0229", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0230", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0231", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0232", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0233", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0234", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0235", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0236", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0237", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0238", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0239", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0240", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0241", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0242", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0243", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0244", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0245", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0246", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0247", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0248", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0249", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0250", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0251", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0252", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0253", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0254", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0255", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0256", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0257", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0258", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0259", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0260", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0261", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0262", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0263", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0264", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0265", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0266", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0267", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0268", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0269", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0270", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0271", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0272", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0273", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0274", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0275", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0276", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0277", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0278", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0279", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0280", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0281", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0282", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0283", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0284", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0285", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0286", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0287", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0288", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0289", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0290", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0291", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0292", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0293", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0294", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0295", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0296", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0297", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0298", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0299", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0300", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0301", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0302", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0303", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0304", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0305", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0306", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0307", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0308", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0309", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0310", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0311", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0312", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0313", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0314", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0315", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0316", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0317", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0318", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0319", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0320", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0321", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0322", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0323", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0324", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0325", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0326", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0327", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0328", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0329", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0330", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0331", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0332", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0333", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0334", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0335", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0336", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0337", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0338", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0339", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0340", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0341", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0342", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0343", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0344", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0345", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0346", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0347", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0348", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0349", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0350", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0351", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0352", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0353", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0354", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0355", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0356", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0357", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0358", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0359", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0360", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0361", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0362", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0363", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0364", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0365", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0366", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0367", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0368", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0369", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0370", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0371", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0372", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0373", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0374", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0375", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0376", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0377", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0378", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0379", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0380", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0381", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0382", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0383", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0384", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0385", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0386", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0387", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0388", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0389", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0390", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0391", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0392", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0393", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0394", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0395", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0396", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0397", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0398", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0399", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0400", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0401", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0402", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0403", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0404", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0405", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0406", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0407", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0408", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0409", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0410", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0411", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0412", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0413", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0414", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0415", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0416", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0417", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0418", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0419", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0420", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0421", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0422", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0423", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0424", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0425", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0426", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0427", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0428", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0429", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0430", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0431", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0432", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0433", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0434", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0435", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0436", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0437", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0438", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0439", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0440", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0441", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0442", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0443", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0444", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0445", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0446", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0447", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0448", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0449", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0450", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0451", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0452", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0453", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0454", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0455", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0456", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0457", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0458", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0459", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0460", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0461", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0462", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0463", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0464", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0465", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0466", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0467", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0468", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0469", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0470", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0471", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0472", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0473", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0474", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0475", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0476", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0477", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0478", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0479", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0480", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0481", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0482", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0483", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0484", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0485", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0486", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0487", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0488", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0489", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0490", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0491", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0492", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0493", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0494", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0495", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0496", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0497", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0498", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0499", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0500", "/users/:id", { + params: Params, + success: User + }) + ) +) + +export type Api = typeof api +export type Groups = typeof api extends HttpApi.HttpApi ? Groups : never +export type UsersGroup = HttpApiGroup.WithIdentifier +export type Endpoint = Extract, HttpApiEndpoint.ConstraintRequest> diff --git a/repos/effect/packages/effect/typeperf/suites/httpapi/fixtures/_grouped-api-types.ts b/repos/effect/packages/effect/typeperf/suites/httpapi/fixtures/_grouped-api-types.ts new file mode 100644 index 0000000000..a9df91ccae --- /dev/null +++ b/repos/effect/packages/effect/typeperf/suites/httpapi/fixtures/_grouped-api-types.ts @@ -0,0 +1,41 @@ +// Shared type-only endpoint groups for client-group and endpoint-selection fixtures. +import type { Schema } from "effect" +import type { HttpApiEndpoint, HttpApiGroup } from "effect/unstable/httpapi" + +type EndpointCount = 10 | 50 | 100 | 500 + +type Digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" +type ZeroToFour = "0" | "1" | "2" | "3" | "4" + +type EndpointIdentifiers10 = Exclude<`getUser000${Digit}`, "getUser0000"> | "getUser0010" +type EndpointIdentifiers50 = Exclude<`getUser00${ZeroToFour}${Digit}`, "getUser0000"> | "getUser0050" +type EndpointIdentifiers100 = Exclude<`getUser00${Digit}${Digit}`, "getUser0000"> | "getUser0100" +type EndpointIdentifiers500 = + | Exclude<`getUser0${ZeroToFour}${Digit}${Digit}`, "getUser0000"> + | "getUser0500" + +type EndpointIdentifiers = Count extends 10 ? EndpointIdentifiers10 + : Count extends 50 ? EndpointIdentifiers50 + : Count extends 100 ? EndpointIdentifiers100 + : EndpointIdentifiers500 + +type Params = Schema.toCodecStringTree> +type Success = Schema.toCodecJson> + +type Endpoint = Identifier extends string ? HttpApiEndpoint.HttpApiEndpoint< + Identifier, + "GET", + "/users/:id", + Params, + never, + never, + never, + Success + > + : never + +export type Group = HttpApiGroup.HttpApiGroup< + "users", + Endpoint>, + TopLevel +> diff --git a/repos/effect/packages/effect/typeperf/suites/httpapi/fixtures/builder-endpoint-count-500.ts b/repos/effect/packages/effect/typeperf/suites/httpapi/fixtures/builder-endpoint-count-500.ts new file mode 100644 index 0000000000..c26960041a --- /dev/null +++ b/repos/effect/packages/effect/typeperf/suites/httpapi/fixtures/builder-endpoint-count-500.ts @@ -0,0 +1,25 @@ +// Measures HttpApiBuilder.endpoint selection from one type-only group with 500 same-shaped endpoints. +import { Effect, Schema } from "effect" +import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiGroup } from "effect/unstable/httpapi" +import type { Api } from "./_endpoint-selection-types.ts" + +Schema.String +HttpApi.make("Api") +HttpApiGroup.make("users") +HttpApiEndpoint.get("warmup", "/warmup") + +declare const api: Api<500> + +const endpointHandler = HttpApiBuilder.endpoint( + api, + "users", + "getUser0500", + ({ params }) => Effect.succeed({ id: String(params.id), name: "Ada" }) +) + +type HttpEffect = Effect.Success + +export type BuilderEndpoint = typeof endpointHandler +export type BuilderEndpointServices = Effect.Services +export type BuilderHttpEffect = HttpEffect +export type BuilderHttpEffectServices = Effect.Services diff --git a/repos/effect/packages/effect/typeperf/suites/httpapi/fixtures/builder-handle-all-count-500.ts b/repos/effect/packages/effect/typeperf/suites/httpapi/fixtures/builder-handle-all-count-500.ts new file mode 100644 index 0000000000..1d07c3b4fb --- /dev/null +++ b/repos/effect/packages/effect/typeperf/suites/httpapi/fixtures/builder-handle-all-count-500.ts @@ -0,0 +1,4529 @@ +// Measures builder handleAll registration for 500 same-shaped endpoints in one group. +import { Effect, Schema } from "effect" +import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiGroup } from "effect/unstable/httpapi" + +Schema.String +HttpApi.make("Api") +HttpApiGroup.make("users") +HttpApiEndpoint.get("warmup", "/warmup") + +const Params = Schema.Struct({ + id: Schema.FiniteFromString +}) + +const User = Schema.Struct({ + id: Schema.String, + name: Schema.String +}) + +const group = HttpApiGroup.make("users").add( + HttpApiEndpoint.get("getUser0001", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0002", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0003", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0004", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0005", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0006", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0007", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0008", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0009", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0010", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0011", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0012", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0013", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0014", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0015", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0016", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0017", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0018", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0019", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0020", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0021", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0022", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0023", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0024", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0025", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0026", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0027", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0028", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0029", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0030", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0031", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0032", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0033", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0034", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0035", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0036", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0037", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0038", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0039", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0040", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0041", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0042", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0043", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0044", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0045", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0046", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0047", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0048", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0049", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0050", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0051", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0052", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0053", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0054", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0055", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0056", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0057", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0058", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0059", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0060", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0061", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0062", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0063", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0064", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0065", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0066", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0067", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0068", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0069", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0070", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0071", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0072", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0073", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0074", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0075", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0076", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0077", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0078", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0079", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0080", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0081", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0082", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0083", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0084", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0085", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0086", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0087", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0088", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0089", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0090", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0091", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0092", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0093", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0094", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0095", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0096", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0097", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0098", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0099", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0100", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0101", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0102", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0103", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0104", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0105", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0106", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0107", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0108", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0109", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0110", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0111", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0112", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0113", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0114", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0115", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0116", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0117", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0118", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0119", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0120", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0121", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0122", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0123", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0124", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0125", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0126", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0127", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0128", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0129", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0130", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0131", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0132", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0133", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0134", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0135", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0136", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0137", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0138", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0139", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0140", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0141", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0142", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0143", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0144", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0145", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0146", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0147", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0148", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0149", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0150", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0151", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0152", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0153", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0154", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0155", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0156", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0157", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0158", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0159", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0160", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0161", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0162", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0163", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0164", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0165", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0166", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0167", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0168", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0169", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0170", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0171", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0172", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0173", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0174", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0175", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0176", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0177", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0178", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0179", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0180", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0181", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0182", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0183", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0184", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0185", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0186", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0187", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0188", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0189", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0190", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0191", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0192", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0193", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0194", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0195", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0196", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0197", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0198", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0199", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0200", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0201", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0202", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0203", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0204", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0205", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0206", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0207", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0208", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0209", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0210", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0211", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0212", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0213", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0214", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0215", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0216", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0217", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0218", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0219", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0220", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0221", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0222", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0223", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0224", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0225", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0226", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0227", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0228", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0229", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0230", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0231", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0232", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0233", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0234", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0235", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0236", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0237", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0238", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0239", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0240", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0241", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0242", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0243", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0244", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0245", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0246", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0247", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0248", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0249", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0250", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0251", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0252", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0253", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0254", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0255", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0256", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0257", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0258", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0259", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0260", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0261", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0262", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0263", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0264", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0265", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0266", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0267", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0268", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0269", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0270", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0271", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0272", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0273", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0274", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0275", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0276", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0277", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0278", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0279", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0280", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0281", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0282", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0283", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0284", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0285", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0286", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0287", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0288", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0289", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0290", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0291", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0292", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0293", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0294", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0295", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0296", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0297", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0298", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0299", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0300", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0301", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0302", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0303", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0304", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0305", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0306", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0307", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0308", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0309", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0310", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0311", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0312", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0313", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0314", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0315", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0316", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0317", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0318", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0319", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0320", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0321", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0322", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0323", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0324", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0325", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0326", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0327", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0328", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0329", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0330", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0331", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0332", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0333", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0334", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0335", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0336", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0337", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0338", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0339", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0340", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0341", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0342", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0343", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0344", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0345", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0346", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0347", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0348", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0349", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0350", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0351", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0352", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0353", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0354", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0355", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0356", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0357", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0358", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0359", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0360", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0361", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0362", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0363", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0364", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0365", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0366", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0367", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0368", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0369", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0370", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0371", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0372", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0373", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0374", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0375", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0376", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0377", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0378", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0379", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0380", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0381", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0382", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0383", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0384", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0385", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0386", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0387", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0388", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0389", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0390", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0391", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0392", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0393", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0394", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0395", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0396", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0397", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0398", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0399", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0400", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0401", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0402", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0403", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0404", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0405", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0406", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0407", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0408", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0409", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0410", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0411", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0412", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0413", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0414", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0415", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0416", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0417", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0418", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0419", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0420", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0421", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0422", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0423", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0424", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0425", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0426", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0427", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0428", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0429", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0430", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0431", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0432", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0433", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0434", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0435", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0436", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0437", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0438", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0439", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0440", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0441", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0442", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0443", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0444", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0445", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0446", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0447", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0448", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0449", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0450", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0451", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0452", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0453", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0454", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0455", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0456", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0457", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0458", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0459", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0460", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0461", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0462", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0463", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0464", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0465", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0466", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0467", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0468", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0469", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0470", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0471", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0472", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0473", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0474", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0475", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0476", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0477", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0478", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0479", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0480", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0481", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0482", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0483", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0484", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0485", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0486", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0487", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0488", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0489", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0490", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0491", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0492", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0493", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0494", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0495", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0496", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0497", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0498", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0499", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0500", "/users/:id", { + params: Params, + success: User + }) +) + +const api = HttpApi.make("Api").add(group) + +const layer = HttpApiBuilder.group(api, "users", (handlers) => + handlers.handleAll({ + getUser0001: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0002: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0003: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0004: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0005: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0006: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0007: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0008: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0009: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0010: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0011: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0012: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0013: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0014: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0015: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0016: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0017: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0018: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0019: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0020: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0021: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0022: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0023: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0024: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0025: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0026: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0027: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0028: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0029: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0030: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0031: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0032: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0033: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0034: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0035: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0036: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0037: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0038: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0039: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0040: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0041: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0042: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0043: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0044: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0045: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0046: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0047: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0048: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0049: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0050: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0051: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0052: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0053: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0054: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0055: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0056: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0057: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0058: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0059: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0060: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0061: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0062: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0063: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0064: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0065: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0066: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0067: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0068: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0069: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0070: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0071: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0072: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0073: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0074: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0075: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0076: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0077: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0078: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0079: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0080: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0081: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0082: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0083: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0084: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0085: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0086: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0087: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0088: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0089: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0090: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0091: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0092: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0093: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0094: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0095: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0096: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0097: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0098: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0099: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0100: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0101: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0102: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0103: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0104: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0105: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0106: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0107: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0108: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0109: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0110: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0111: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0112: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0113: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0114: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0115: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0116: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0117: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0118: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0119: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0120: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0121: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0122: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0123: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0124: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0125: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0126: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0127: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0128: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0129: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0130: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0131: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0132: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0133: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0134: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0135: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0136: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0137: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0138: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0139: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0140: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0141: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0142: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0143: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0144: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0145: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0146: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0147: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0148: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0149: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0150: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0151: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0152: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0153: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0154: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0155: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0156: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0157: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0158: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0159: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0160: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0161: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0162: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0163: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0164: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0165: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0166: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0167: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0168: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0169: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0170: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0171: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0172: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0173: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0174: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0175: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0176: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0177: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0178: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0179: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0180: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0181: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0182: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0183: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0184: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0185: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0186: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0187: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0188: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0189: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0190: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0191: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0192: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0193: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0194: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0195: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0196: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0197: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0198: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0199: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0200: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0201: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0202: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0203: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0204: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0205: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0206: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0207: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0208: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0209: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0210: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0211: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0212: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0213: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0214: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0215: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0216: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0217: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0218: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0219: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0220: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0221: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0222: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0223: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0224: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0225: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0226: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0227: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0228: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0229: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0230: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0231: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0232: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0233: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0234: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0235: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0236: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0237: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0238: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0239: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0240: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0241: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0242: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0243: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0244: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0245: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0246: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0247: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0248: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0249: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0250: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0251: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0252: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0253: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0254: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0255: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0256: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0257: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0258: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0259: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0260: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0261: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0262: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0263: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0264: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0265: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0266: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0267: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0268: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0269: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0270: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0271: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0272: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0273: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0274: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0275: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0276: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0277: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0278: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0279: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0280: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0281: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0282: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0283: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0284: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0285: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0286: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0287: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0288: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0289: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0290: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0291: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0292: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0293: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0294: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0295: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0296: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0297: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0298: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0299: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0300: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0301: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0302: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0303: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0304: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0305: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0306: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0307: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0308: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0309: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0310: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0311: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0312: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0313: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0314: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0315: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0316: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0317: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0318: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0319: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0320: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0321: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0322: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0323: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0324: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0325: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0326: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0327: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0328: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0329: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0330: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0331: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0332: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0333: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0334: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0335: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0336: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0337: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0338: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0339: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0340: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0341: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0342: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0343: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0344: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0345: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0346: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0347: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0348: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0349: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0350: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0351: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0352: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0353: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0354: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0355: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0356: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0357: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0358: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0359: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0360: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0361: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0362: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0363: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0364: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0365: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0366: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0367: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0368: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0369: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0370: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0371: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0372: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0373: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0374: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0375: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0376: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0377: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0378: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0379: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0380: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0381: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0382: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0383: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0384: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0385: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0386: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0387: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0388: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0389: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0390: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0391: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0392: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0393: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0394: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0395: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0396: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0397: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0398: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0399: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0400: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0401: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0402: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0403: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0404: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0405: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0406: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0407: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0408: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0409: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0410: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0411: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0412: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0413: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0414: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0415: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0416: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0417: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0418: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0419: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0420: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0421: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0422: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0423: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0424: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0425: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0426: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0427: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0428: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0429: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0430: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0431: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0432: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0433: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0434: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0435: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0436: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0437: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0438: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0439: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0440: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0441: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0442: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0443: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0444: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0445: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0446: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0447: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0448: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0449: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0450: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0451: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0452: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0453: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0454: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0455: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0456: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0457: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0458: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0459: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0460: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0461: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0462: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0463: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0464: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0465: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0466: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0467: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0468: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0469: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0470: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0471: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0472: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0473: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0474: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0475: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0476: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0477: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0478: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0479: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0480: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0481: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0482: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0483: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0484: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0485: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0486: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0487: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0488: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0489: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0490: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0491: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0492: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0493: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0494: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0495: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0496: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0497: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0498: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0499: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0500: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }) + })) + +export type Layer = typeof layer +export type Handlers = HttpApiBuilder.Handlers.FromGroup diff --git a/repos/effect/packages/effect/typeperf/suites/httpapi/fixtures/builder-handle-all-two-batches-count-500.ts b/repos/effect/packages/effect/typeperf/suites/httpapi/fixtures/builder-handle-all-two-batches-count-500.ts new file mode 100644 index 0000000000..2b01282878 --- /dev/null +++ b/repos/effect/packages/effect/typeperf/suites/httpapi/fixtures/builder-handle-all-two-batches-count-500.ts @@ -0,0 +1,4532 @@ +// Measures builder handleAll registration across two chained batches of 250 endpoints each. +import { Effect, Schema } from "effect" +import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiGroup } from "effect/unstable/httpapi" + +Schema.String +HttpApi.make("Api") +HttpApiGroup.make("users") +HttpApiEndpoint.get("warmup", "/warmup") + +const Params = Schema.Struct({ + id: Schema.FiniteFromString +}) + +const User = Schema.Struct({ + id: Schema.String, + name: Schema.String +}) + +const group = HttpApiGroup.make("users").add( + HttpApiEndpoint.get("getUser0001", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0002", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0003", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0004", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0005", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0006", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0007", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0008", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0009", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0010", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0011", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0012", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0013", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0014", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0015", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0016", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0017", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0018", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0019", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0020", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0021", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0022", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0023", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0024", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0025", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0026", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0027", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0028", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0029", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0030", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0031", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0032", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0033", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0034", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0035", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0036", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0037", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0038", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0039", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0040", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0041", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0042", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0043", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0044", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0045", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0046", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0047", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0048", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0049", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0050", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0051", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0052", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0053", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0054", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0055", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0056", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0057", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0058", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0059", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0060", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0061", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0062", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0063", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0064", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0065", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0066", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0067", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0068", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0069", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0070", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0071", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0072", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0073", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0074", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0075", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0076", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0077", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0078", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0079", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0080", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0081", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0082", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0083", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0084", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0085", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0086", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0087", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0088", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0089", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0090", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0091", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0092", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0093", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0094", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0095", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0096", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0097", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0098", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0099", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0100", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0101", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0102", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0103", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0104", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0105", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0106", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0107", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0108", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0109", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0110", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0111", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0112", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0113", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0114", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0115", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0116", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0117", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0118", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0119", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0120", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0121", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0122", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0123", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0124", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0125", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0126", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0127", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0128", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0129", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0130", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0131", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0132", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0133", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0134", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0135", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0136", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0137", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0138", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0139", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0140", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0141", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0142", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0143", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0144", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0145", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0146", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0147", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0148", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0149", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0150", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0151", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0152", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0153", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0154", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0155", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0156", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0157", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0158", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0159", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0160", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0161", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0162", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0163", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0164", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0165", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0166", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0167", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0168", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0169", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0170", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0171", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0172", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0173", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0174", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0175", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0176", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0177", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0178", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0179", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0180", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0181", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0182", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0183", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0184", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0185", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0186", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0187", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0188", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0189", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0190", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0191", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0192", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0193", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0194", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0195", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0196", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0197", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0198", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0199", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0200", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0201", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0202", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0203", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0204", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0205", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0206", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0207", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0208", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0209", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0210", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0211", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0212", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0213", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0214", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0215", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0216", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0217", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0218", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0219", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0220", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0221", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0222", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0223", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0224", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0225", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0226", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0227", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0228", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0229", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0230", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0231", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0232", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0233", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0234", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0235", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0236", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0237", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0238", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0239", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0240", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0241", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0242", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0243", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0244", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0245", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0246", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0247", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0248", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0249", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0250", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0251", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0252", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0253", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0254", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0255", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0256", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0257", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0258", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0259", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0260", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0261", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0262", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0263", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0264", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0265", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0266", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0267", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0268", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0269", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0270", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0271", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0272", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0273", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0274", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0275", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0276", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0277", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0278", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0279", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0280", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0281", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0282", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0283", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0284", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0285", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0286", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0287", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0288", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0289", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0290", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0291", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0292", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0293", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0294", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0295", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0296", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0297", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0298", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0299", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0300", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0301", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0302", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0303", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0304", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0305", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0306", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0307", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0308", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0309", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0310", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0311", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0312", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0313", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0314", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0315", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0316", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0317", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0318", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0319", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0320", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0321", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0322", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0323", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0324", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0325", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0326", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0327", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0328", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0329", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0330", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0331", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0332", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0333", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0334", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0335", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0336", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0337", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0338", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0339", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0340", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0341", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0342", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0343", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0344", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0345", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0346", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0347", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0348", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0349", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0350", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0351", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0352", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0353", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0354", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0355", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0356", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0357", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0358", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0359", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0360", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0361", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0362", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0363", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0364", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0365", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0366", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0367", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0368", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0369", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0370", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0371", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0372", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0373", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0374", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0375", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0376", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0377", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0378", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0379", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0380", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0381", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0382", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0383", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0384", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0385", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0386", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0387", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0388", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0389", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0390", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0391", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0392", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0393", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0394", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0395", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0396", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0397", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0398", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0399", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0400", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0401", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0402", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0403", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0404", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0405", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0406", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0407", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0408", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0409", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0410", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0411", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0412", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0413", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0414", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0415", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0416", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0417", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0418", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0419", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0420", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0421", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0422", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0423", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0424", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0425", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0426", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0427", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0428", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0429", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0430", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0431", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0432", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0433", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0434", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0435", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0436", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0437", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0438", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0439", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0440", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0441", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0442", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0443", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0444", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0445", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0446", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0447", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0448", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0449", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0450", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0451", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0452", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0453", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0454", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0455", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0456", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0457", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0458", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0459", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0460", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0461", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0462", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0463", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0464", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0465", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0466", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0467", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0468", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0469", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0470", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0471", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0472", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0473", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0474", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0475", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0476", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0477", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0478", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0479", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0480", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0481", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0482", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0483", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0484", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0485", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0486", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0487", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0488", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0489", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0490", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0491", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0492", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0493", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0494", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0495", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0496", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0497", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0498", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0499", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0500", "/users/:id", { + params: Params, + success: User + }) +) + +const api = HttpApi.make("Api").add(group) + +export const layer = HttpApiBuilder.group(api, "users", (handlers) => + handlers + .handleAll({ + getUser0001: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0002: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0003: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0004: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0005: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0006: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0007: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0008: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0009: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0010: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0011: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0012: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0013: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0014: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0015: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0016: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0017: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0018: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0019: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0020: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0021: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0022: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0023: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0024: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0025: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0026: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0027: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0028: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0029: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0030: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0031: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0032: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0033: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0034: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0035: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0036: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0037: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0038: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0039: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0040: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0041: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0042: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0043: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0044: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0045: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0046: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0047: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0048: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0049: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0050: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0051: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0052: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0053: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0054: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0055: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0056: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0057: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0058: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0059: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0060: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0061: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0062: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0063: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0064: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0065: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0066: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0067: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0068: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0069: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0070: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0071: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0072: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0073: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0074: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0075: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0076: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0077: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0078: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0079: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0080: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0081: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0082: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0083: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0084: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0085: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0086: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0087: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0088: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0089: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0090: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0091: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0092: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0093: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0094: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0095: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0096: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0097: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0098: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0099: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0100: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0101: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0102: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0103: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0104: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0105: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0106: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0107: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0108: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0109: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0110: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0111: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0112: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0113: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0114: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0115: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0116: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0117: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0118: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0119: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0120: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0121: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0122: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0123: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0124: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0125: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0126: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0127: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0128: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0129: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0130: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0131: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0132: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0133: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0134: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0135: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0136: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0137: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0138: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0139: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0140: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0141: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0142: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0143: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0144: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0145: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0146: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0147: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0148: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0149: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0150: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0151: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0152: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0153: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0154: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0155: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0156: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0157: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0158: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0159: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0160: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0161: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0162: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0163: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0164: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0165: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0166: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0167: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0168: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0169: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0170: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0171: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0172: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0173: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0174: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0175: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0176: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0177: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0178: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0179: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0180: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0181: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0182: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0183: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0184: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0185: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0186: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0187: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0188: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0189: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0190: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0191: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0192: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0193: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0194: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0195: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0196: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0197: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0198: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0199: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0200: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0201: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0202: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0203: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0204: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0205: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0206: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0207: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0208: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0209: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0210: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0211: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0212: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0213: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0214: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0215: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0216: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0217: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0218: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0219: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0220: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0221: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0222: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0223: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0224: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0225: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0226: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0227: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0228: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0229: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0230: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0231: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0232: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0233: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0234: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0235: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0236: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0237: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0238: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0239: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0240: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0241: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0242: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0243: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0244: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0245: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0246: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0247: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0248: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0249: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0250: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }) + }) + .handleAll({ + getUser0251: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0252: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0253: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0254: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0255: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0256: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0257: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0258: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0259: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0260: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0261: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0262: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0263: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0264: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0265: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0266: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0267: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0268: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0269: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0270: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0271: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0272: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0273: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0274: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0275: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0276: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0277: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0278: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0279: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0280: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0281: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0282: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0283: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0284: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0285: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0286: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0287: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0288: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0289: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0290: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0291: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0292: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0293: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0294: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0295: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0296: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0297: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0298: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0299: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0300: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0301: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0302: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0303: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0304: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0305: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0306: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0307: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0308: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0309: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0310: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0311: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0312: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0313: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0314: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0315: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0316: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0317: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0318: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0319: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0320: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0321: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0322: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0323: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0324: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0325: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0326: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0327: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0328: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0329: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0330: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0331: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0332: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0333: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0334: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0335: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0336: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0337: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0338: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0339: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0340: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0341: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0342: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0343: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0344: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0345: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0346: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0347: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0348: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0349: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0350: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0351: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0352: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0353: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0354: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0355: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0356: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0357: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0358: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0359: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0360: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0361: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0362: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0363: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0364: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0365: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0366: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0367: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0368: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0369: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0370: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0371: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0372: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0373: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0374: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0375: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0376: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0377: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0378: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0379: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0380: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0381: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0382: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0383: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0384: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0385: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0386: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0387: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0388: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0389: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0390: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0391: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0392: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0393: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0394: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0395: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0396: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0397: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0398: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0399: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0400: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0401: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0402: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0403: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0404: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0405: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0406: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0407: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0408: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0409: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0410: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0411: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0412: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0413: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0414: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0415: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0416: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0417: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0418: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0419: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0420: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0421: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0422: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0423: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0424: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0425: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0426: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0427: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0428: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0429: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0430: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0431: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0432: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0433: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0434: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0435: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0436: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0437: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0438: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0439: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0440: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0441: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0442: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0443: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0444: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0445: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0446: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0447: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0448: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0449: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0450: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0451: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0452: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0453: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0454: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0455: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0456: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0457: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0458: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0459: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0460: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0461: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0462: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0463: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0464: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0465: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0466: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0467: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0468: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0469: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0470: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0471: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0472: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0473: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0474: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0475: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0476: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0477: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0478: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0479: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0480: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0481: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0482: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0483: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0484: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0485: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0486: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0487: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0488: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0489: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0490: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0491: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0492: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0493: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0494: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0495: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0496: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0497: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0498: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0499: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }), + getUser0500: ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }) + })) + +export type Layer = typeof layer +export type Handlers = HttpApiBuilder.Handlers.FromGroup diff --git a/repos/effect/packages/effect/typeperf/suites/httpapi/fixtures/builder-handlers-count-100.ts b/repos/effect/packages/effect/typeperf/suites/httpapi/fixtures/builder-handlers-count-100.ts new file mode 100644 index 0000000000..b2c690896f --- /dev/null +++ b/repos/effect/packages/effect/typeperf/suites/httpapi/fixtures/builder-handlers-count-100.ts @@ -0,0 +1,928 @@ +// Measures chained builder handle registration for 100 same-shaped endpoints in one group. +import { Effect, Schema } from "effect" +import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiGroup } from "effect/unstable/httpapi" + +Schema.String +HttpApi.make("Api") +HttpApiGroup.make("users") +HttpApiEndpoint.get("warmup", "/warmup") + +const Params = Schema.Struct({ + id: Schema.FiniteFromString +}) + +const User = Schema.Struct({ + id: Schema.String, + name: Schema.String +}) + +const group = HttpApiGroup.make("users").add( + HttpApiEndpoint.get("getUser0001", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0002", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0003", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0004", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0005", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0006", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0007", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0008", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0009", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0010", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0011", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0012", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0013", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0014", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0015", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0016", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0017", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0018", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0019", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0020", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0021", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0022", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0023", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0024", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0025", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0026", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0027", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0028", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0029", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0030", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0031", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0032", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0033", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0034", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0035", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0036", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0037", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0038", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0039", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0040", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0041", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0042", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0043", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0044", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0045", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0046", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0047", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0048", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0049", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0050", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0051", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0052", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0053", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0054", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0055", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0056", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0057", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0058", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0059", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0060", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0061", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0062", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0063", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0064", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0065", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0066", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0067", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0068", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0069", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0070", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0071", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0072", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0073", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0074", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0075", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0076", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0077", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0078", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0079", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0080", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0081", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0082", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0083", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0084", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0085", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0086", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0087", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0088", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0089", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0090", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0091", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0092", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0093", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0094", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0095", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0096", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0097", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0098", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0099", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0100", "/users/:id", { + params: Params, + success: User + }) +) + +const api = HttpApi.make("Api").add(group) + +const layer = HttpApiBuilder.group(api, "users", (handlers) => + handlers + .handle("getUser0001", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0002", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0003", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0004", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0005", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0006", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0007", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0008", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0009", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0010", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0011", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0012", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0013", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0014", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0015", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0016", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0017", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0018", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0019", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0020", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0021", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0022", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0023", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0024", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0025", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0026", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0027", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0028", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0029", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0030", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0031", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0032", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0033", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0034", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0035", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0036", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0037", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0038", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0039", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0040", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0041", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0042", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0043", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0044", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0045", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0046", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0047", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0048", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0049", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0050", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0051", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0052", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0053", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0054", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0055", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0056", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0057", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0058", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0059", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0060", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0061", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0062", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0063", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0064", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0065", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0066", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0067", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0068", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0069", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0070", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0071", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0072", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0073", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0074", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0075", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0076", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0077", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0078", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0079", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0080", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0081", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0082", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0083", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0084", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0085", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0086", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0087", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0088", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0089", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0090", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0091", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0092", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0093", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0094", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0095", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0096", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0097", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0098", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0099", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handle("getUser0100", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }))) + +export type Layer = typeof layer +export type Handlers = HttpApiBuilder.Handlers.FromGroup diff --git a/repos/effect/packages/effect/typeperf/suites/httpapi/fixtures/builder-raw-handlers-count-100.ts b/repos/effect/packages/effect/typeperf/suites/httpapi/fixtures/builder-raw-handlers-count-100.ts new file mode 100644 index 0000000000..afdab132db --- /dev/null +++ b/repos/effect/packages/effect/typeperf/suites/httpapi/fixtures/builder-raw-handlers-count-100.ts @@ -0,0 +1,928 @@ +// Measures chained builder handleRaw registration for 100 same-shaped endpoints in one group. +import { Effect, Schema } from "effect" +import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiGroup } from "effect/unstable/httpapi" + +Schema.String +HttpApi.make("Api") +HttpApiGroup.make("users") +HttpApiEndpoint.get("warmup", "/warmup") + +const Params = Schema.Struct({ + id: Schema.FiniteFromString +}) + +const User = Schema.Struct({ + id: Schema.String, + name: Schema.String +}) + +const group = HttpApiGroup.make("users").add( + HttpApiEndpoint.get("getUser0001", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0002", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0003", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0004", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0005", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0006", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0007", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0008", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0009", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0010", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0011", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0012", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0013", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0014", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0015", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0016", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0017", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0018", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0019", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0020", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0021", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0022", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0023", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0024", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0025", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0026", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0027", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0028", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0029", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0030", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0031", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0032", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0033", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0034", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0035", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0036", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0037", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0038", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0039", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0040", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0041", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0042", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0043", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0044", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0045", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0046", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0047", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0048", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0049", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0050", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0051", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0052", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0053", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0054", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0055", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0056", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0057", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0058", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0059", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0060", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0061", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0062", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0063", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0064", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0065", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0066", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0067", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0068", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0069", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0070", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0071", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0072", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0073", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0074", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0075", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0076", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0077", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0078", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0079", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0080", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0081", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0082", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0083", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0084", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0085", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0086", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0087", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0088", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0089", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0090", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0091", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0092", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0093", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0094", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0095", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0096", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0097", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0098", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0099", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0100", "/users/:id", { + params: Params, + success: User + }) +) + +const api = HttpApi.make("Api").add(group) + +const layer = HttpApiBuilder.group(api, "users", (handlers) => + handlers + .handleRaw("getUser0001", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0002", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0003", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0004", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0005", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0006", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0007", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0008", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0009", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0010", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0011", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0012", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0013", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0014", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0015", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0016", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0017", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0018", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0019", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0020", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0021", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0022", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0023", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0024", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0025", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0026", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0027", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0028", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0029", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0030", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0031", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0032", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0033", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0034", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0035", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0036", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0037", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0038", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0039", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0040", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0041", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0042", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0043", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0044", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0045", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0046", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0047", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0048", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0049", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0050", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0051", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0052", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0053", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0054", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0055", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0056", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0057", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0058", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0059", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0060", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0061", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0062", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0063", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0064", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0065", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0066", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0067", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0068", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0069", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0070", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0071", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0072", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0073", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0074", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0075", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0076", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0077", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0078", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0079", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0080", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0081", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0082", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0083", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0084", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0085", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0086", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0087", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0088", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0089", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0090", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0091", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0092", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0093", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0094", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0095", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0096", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0097", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0098", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0099", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + })) + .handleRaw("getUser0100", ({ params }) => + Effect.succeed({ + id: String(params.id), + name: "Ada" + }))) + +export type Layer = typeof layer +export type Handlers = HttpApiBuilder.Handlers.FromGroup diff --git a/repos/effect/packages/effect/typeperf/suites/httpapi/fixtures/client-endpoint-count-500.ts b/repos/effect/packages/effect/typeperf/suites/httpapi/fixtures/client-endpoint-count-500.ts new file mode 100644 index 0000000000..8d2cdd5aba --- /dev/null +++ b/repos/effect/packages/effect/typeperf/suites/httpapi/fixtures/client-endpoint-count-500.ts @@ -0,0 +1,26 @@ +// Measures HttpApiClient.endpoint selection from one type-only group with 500 same-shaped endpoints. +import { Effect, Schema } from "effect" +import { HttpClient } from "effect/unstable/http" +import { HttpApi, HttpApiClient, HttpApiEndpoint, HttpApiGroup } from "effect/unstable/httpapi" +import type { Api } from "./_endpoint-selection-types.ts" + +Schema.String +HttpApi.make("Api") +HttpApiGroup.make("users") +HttpApiEndpoint.get("warmup", "/warmup") + +declare const api: Api<500> +declare const httpClient: HttpClient.HttpClient + +const endpointClient = HttpApiClient.endpoint(api, { + group: "users", + endpoint: "getUser0500", + httpClient +}) + +type Method = Effect.Success + +export type GeneratedEndpointClient = typeof endpointClient +export type EndpointClientMethod = Method +export type EndpointClientRequest = Parameters[0] +export type EndpointClientResult = ReturnType diff --git a/repos/effect/packages/effect/typeperf/suites/httpapi/fixtures/client-group-count-500.ts b/repos/effect/packages/effect/typeperf/suites/httpapi/fixtures/client-group-count-500.ts new file mode 100644 index 0000000000..2e7d0a10e4 --- /dev/null +++ b/repos/effect/packages/effect/typeperf/suites/httpapi/fixtures/client-group-count-500.ts @@ -0,0 +1,17 @@ +// Measures Client.Group derivation from one type-only group with 500 same-shaped endpoints. +import { Schema } from "effect" +import { HttpApi, HttpApiClient, HttpApiEndpoint, HttpApiGroup } from "effect/unstable/httpapi" +import type { Group } from "./_grouped-api-types.ts" + +Schema.String +HttpApi.make("Api") +HttpApiGroup.make("users") +HttpApiEndpoint.get("warmup", "/warmup") + +type Client = HttpApiClient.Client.Group, never, never> +type Methods = Client[keyof Client] + +export type GeneratedClient = Client +export type ClientMethodNames = keyof Client +export type ClientMethodRequests = Parameters[0] +export type ClientMethodResults = ReturnType diff --git a/repos/effect/packages/effect/typeperf/suites/httpapi/fixtures/client-groups-100x5-count-500.ts b/repos/effect/packages/effect/typeperf/suites/httpapi/fixtures/client-groups-100x5-count-500.ts new file mode 100644 index 0000000000..fe2352a004 --- /dev/null +++ b/repos/effect/packages/effect/typeperf/suites/httpapi/fixtures/client-groups-100x5-count-500.ts @@ -0,0 +1,30 @@ +// Measures client group derivation for 100 groups with 5 same-shaped endpoints each. +import { Schema } from "effect" +import { HttpApi, HttpApiClient, HttpApiEndpoint, HttpApiGroup } from "effect/unstable/httpapi" +import type { Endpoint } from "./_grouped-api-500.ts" + +Schema.String +HttpApi.make("Api") +HttpApiGroup.make("group001") +HttpApiEndpoint.get("warmup", "/warmup") + +type Digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" +type NonZeroDigit = "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" +type TwoDigit = `0${NonZeroDigit}` | `${NonZeroDigit}${Digit}` +type GroupIdentifier = `group0${TwoDigit}` | "group100" +type EndpointIdentifier = "getUser0001" | "getUser0002" | "getUser0003" | "getUser0004" | "getUser0005" +type FiveEndpoints = Extract +type Groups = { + readonly [Identifier in GroupIdentifier]: HttpApiGroup.HttpApiGroup +}[GroupIdentifier] +type Client = HttpApiClient.Client +type GroupClients = Client[keyof Client] +type Methods = GroupClients[keyof GroupClients] + +export type GeneratedClient = Client +export type ClientGroupIdentifiers = keyof Client +export type ClientGroups = GroupClients +export type ClientMethodIdentifiers = keyof GroupClients +export type ClientMethods = Methods +export type ClientMethodRequests = Parameters[0] +export type ClientMethodResults = ReturnType diff --git a/repos/effect/packages/effect/typeperf/suites/httpapi/fixtures/client-methods-count-500.ts b/repos/effect/packages/effect/typeperf/suites/httpapi/fixtures/client-methods-count-500.ts new file mode 100644 index 0000000000..609aad148d --- /dev/null +++ b/repos/effect/packages/effect/typeperf/suites/httpapi/fixtures/client-methods-count-500.ts @@ -0,0 +1,20 @@ +// Measures grouped client method derivation from one group with 500 same-shaped endpoints. +import { Schema } from "effect" +import { HttpApi, HttpApiClient, HttpApiEndpoint, HttpApiGroup } from "effect/unstable/httpapi" +import { api } from "./_grouped-api-500.ts" + +Schema.String +HttpApi.make("Api") +HttpApiGroup.make("users") +HttpApiEndpoint.get("warmup", "/warmup") + +type Client = HttpApiClient.ForApi +type Users = Client["users"] +type Methods = Users[keyof Users] + +export type Api = typeof api +export type GeneratedClient = Client +export type ClientMethodNames = keyof Users +export type ClientMethods = Methods +export type ClientMethodRequests = Parameters[0] +export type ClientMethodResults = ReturnType diff --git a/repos/effect/packages/effect/typeperf/suites/httpapi/fixtures/client-top-level-count-500.ts b/repos/effect/packages/effect/typeperf/suites/httpapi/fixtures/client-top-level-count-500.ts new file mode 100644 index 0000000000..3dcd24d239 --- /dev/null +++ b/repos/effect/packages/effect/typeperf/suites/httpapi/fixtures/client-top-level-count-500.ts @@ -0,0 +1,19 @@ +// Measures Client.TopLevelMethods derivation from one type-only top-level group with 500 same-shaped endpoints. +import { Schema } from "effect" +import { HttpApi, HttpApiClient, HttpApiEndpoint, HttpApiGroup } from "effect/unstable/httpapi" +import type { Group } from "./_grouped-api-types.ts" + +Schema.String +HttpApi.make("Api") +HttpApiGroup.make("top", { topLevel: true }) +HttpApiEndpoint.get("warmup", "/warmup") + +type TopGroup = Group<500, true> +type Client = HttpApiClient.Client.TopLevelMethods +type Methods = Client[keyof Client] + +export type GeneratedClient = Client +export type ClientMethodNames = keyof Client +export type ClientMethods = Methods +export type ClientMethodRequests = Parameters[0] +export type ClientMethodResults = ReturnType diff --git a/repos/effect/packages/effect/typeperf/suites/httpapi/fixtures/endpoint-class-count-500.ts b/repos/effect/packages/effect/typeperf/suites/httpapi/fixtures/endpoint-class-count-500.ts new file mode 100644 index 0000000000..0597ebd63d --- /dev/null +++ b/repos/effect/packages/effect/typeperf/suites/httpapi/fixtures/endpoint-class-count-500.ts @@ -0,0 +1,3031 @@ +// Measures class-like endpoint declaration for 500 same-shaped endpoints. +import { Schema } from "effect" +import { HttpApi, HttpApiEndpoint, HttpApiGroup } from "effect/unstable/httpapi" + +Schema.String +HttpApi.make("Api") +HttpApiGroup.make("users") +HttpApiEndpoint.get("warmup", "/warmup") + +const Params = Schema.Struct({ + id: Schema.FiniteFromString +}) + +const User = Schema.Struct({ + id: Schema.String, + name: Schema.String +}) + +class GetUser0001 extends HttpApiEndpoint.get("getUser0001", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0002 extends HttpApiEndpoint.get("getUser0002", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0003 extends HttpApiEndpoint.get("getUser0003", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0004 extends HttpApiEndpoint.get("getUser0004", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0005 extends HttpApiEndpoint.get("getUser0005", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0006 extends HttpApiEndpoint.get("getUser0006", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0007 extends HttpApiEndpoint.get("getUser0007", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0008 extends HttpApiEndpoint.get("getUser0008", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0009 extends HttpApiEndpoint.get("getUser0009", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0010 extends HttpApiEndpoint.get("getUser0010", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0011 extends HttpApiEndpoint.get("getUser0011", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0012 extends HttpApiEndpoint.get("getUser0012", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0013 extends HttpApiEndpoint.get("getUser0013", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0014 extends HttpApiEndpoint.get("getUser0014", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0015 extends HttpApiEndpoint.get("getUser0015", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0016 extends HttpApiEndpoint.get("getUser0016", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0017 extends HttpApiEndpoint.get("getUser0017", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0018 extends HttpApiEndpoint.get("getUser0018", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0019 extends HttpApiEndpoint.get("getUser0019", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0020 extends HttpApiEndpoint.get("getUser0020", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0021 extends HttpApiEndpoint.get("getUser0021", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0022 extends HttpApiEndpoint.get("getUser0022", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0023 extends HttpApiEndpoint.get("getUser0023", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0024 extends HttpApiEndpoint.get("getUser0024", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0025 extends HttpApiEndpoint.get("getUser0025", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0026 extends HttpApiEndpoint.get("getUser0026", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0027 extends HttpApiEndpoint.get("getUser0027", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0028 extends HttpApiEndpoint.get("getUser0028", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0029 extends HttpApiEndpoint.get("getUser0029", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0030 extends HttpApiEndpoint.get("getUser0030", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0031 extends HttpApiEndpoint.get("getUser0031", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0032 extends HttpApiEndpoint.get("getUser0032", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0033 extends HttpApiEndpoint.get("getUser0033", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0034 extends HttpApiEndpoint.get("getUser0034", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0035 extends HttpApiEndpoint.get("getUser0035", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0036 extends HttpApiEndpoint.get("getUser0036", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0037 extends HttpApiEndpoint.get("getUser0037", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0038 extends HttpApiEndpoint.get("getUser0038", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0039 extends HttpApiEndpoint.get("getUser0039", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0040 extends HttpApiEndpoint.get("getUser0040", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0041 extends HttpApiEndpoint.get("getUser0041", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0042 extends HttpApiEndpoint.get("getUser0042", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0043 extends HttpApiEndpoint.get("getUser0043", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0044 extends HttpApiEndpoint.get("getUser0044", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0045 extends HttpApiEndpoint.get("getUser0045", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0046 extends HttpApiEndpoint.get("getUser0046", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0047 extends HttpApiEndpoint.get("getUser0047", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0048 extends HttpApiEndpoint.get("getUser0048", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0049 extends HttpApiEndpoint.get("getUser0049", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0050 extends HttpApiEndpoint.get("getUser0050", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0051 extends HttpApiEndpoint.get("getUser0051", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0052 extends HttpApiEndpoint.get("getUser0052", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0053 extends HttpApiEndpoint.get("getUser0053", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0054 extends HttpApiEndpoint.get("getUser0054", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0055 extends HttpApiEndpoint.get("getUser0055", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0056 extends HttpApiEndpoint.get("getUser0056", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0057 extends HttpApiEndpoint.get("getUser0057", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0058 extends HttpApiEndpoint.get("getUser0058", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0059 extends HttpApiEndpoint.get("getUser0059", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0060 extends HttpApiEndpoint.get("getUser0060", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0061 extends HttpApiEndpoint.get("getUser0061", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0062 extends HttpApiEndpoint.get("getUser0062", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0063 extends HttpApiEndpoint.get("getUser0063", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0064 extends HttpApiEndpoint.get("getUser0064", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0065 extends HttpApiEndpoint.get("getUser0065", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0066 extends HttpApiEndpoint.get("getUser0066", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0067 extends HttpApiEndpoint.get("getUser0067", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0068 extends HttpApiEndpoint.get("getUser0068", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0069 extends HttpApiEndpoint.get("getUser0069", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0070 extends HttpApiEndpoint.get("getUser0070", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0071 extends HttpApiEndpoint.get("getUser0071", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0072 extends HttpApiEndpoint.get("getUser0072", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0073 extends HttpApiEndpoint.get("getUser0073", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0074 extends HttpApiEndpoint.get("getUser0074", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0075 extends HttpApiEndpoint.get("getUser0075", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0076 extends HttpApiEndpoint.get("getUser0076", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0077 extends HttpApiEndpoint.get("getUser0077", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0078 extends HttpApiEndpoint.get("getUser0078", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0079 extends HttpApiEndpoint.get("getUser0079", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0080 extends HttpApiEndpoint.get("getUser0080", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0081 extends HttpApiEndpoint.get("getUser0081", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0082 extends HttpApiEndpoint.get("getUser0082", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0083 extends HttpApiEndpoint.get("getUser0083", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0084 extends HttpApiEndpoint.get("getUser0084", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0085 extends HttpApiEndpoint.get("getUser0085", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0086 extends HttpApiEndpoint.get("getUser0086", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0087 extends HttpApiEndpoint.get("getUser0087", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0088 extends HttpApiEndpoint.get("getUser0088", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0089 extends HttpApiEndpoint.get("getUser0089", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0090 extends HttpApiEndpoint.get("getUser0090", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0091 extends HttpApiEndpoint.get("getUser0091", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0092 extends HttpApiEndpoint.get("getUser0092", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0093 extends HttpApiEndpoint.get("getUser0093", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0094 extends HttpApiEndpoint.get("getUser0094", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0095 extends HttpApiEndpoint.get("getUser0095", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0096 extends HttpApiEndpoint.get("getUser0096", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0097 extends HttpApiEndpoint.get("getUser0097", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0098 extends HttpApiEndpoint.get("getUser0098", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0099 extends HttpApiEndpoint.get("getUser0099", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0100 extends HttpApiEndpoint.get("getUser0100", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0101 extends HttpApiEndpoint.get("getUser0101", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0102 extends HttpApiEndpoint.get("getUser0102", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0103 extends HttpApiEndpoint.get("getUser0103", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0104 extends HttpApiEndpoint.get("getUser0104", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0105 extends HttpApiEndpoint.get("getUser0105", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0106 extends HttpApiEndpoint.get("getUser0106", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0107 extends HttpApiEndpoint.get("getUser0107", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0108 extends HttpApiEndpoint.get("getUser0108", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0109 extends HttpApiEndpoint.get("getUser0109", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0110 extends HttpApiEndpoint.get("getUser0110", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0111 extends HttpApiEndpoint.get("getUser0111", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0112 extends HttpApiEndpoint.get("getUser0112", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0113 extends HttpApiEndpoint.get("getUser0113", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0114 extends HttpApiEndpoint.get("getUser0114", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0115 extends HttpApiEndpoint.get("getUser0115", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0116 extends HttpApiEndpoint.get("getUser0116", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0117 extends HttpApiEndpoint.get("getUser0117", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0118 extends HttpApiEndpoint.get("getUser0118", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0119 extends HttpApiEndpoint.get("getUser0119", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0120 extends HttpApiEndpoint.get("getUser0120", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0121 extends HttpApiEndpoint.get("getUser0121", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0122 extends HttpApiEndpoint.get("getUser0122", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0123 extends HttpApiEndpoint.get("getUser0123", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0124 extends HttpApiEndpoint.get("getUser0124", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0125 extends HttpApiEndpoint.get("getUser0125", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0126 extends HttpApiEndpoint.get("getUser0126", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0127 extends HttpApiEndpoint.get("getUser0127", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0128 extends HttpApiEndpoint.get("getUser0128", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0129 extends HttpApiEndpoint.get("getUser0129", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0130 extends HttpApiEndpoint.get("getUser0130", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0131 extends HttpApiEndpoint.get("getUser0131", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0132 extends HttpApiEndpoint.get("getUser0132", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0133 extends HttpApiEndpoint.get("getUser0133", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0134 extends HttpApiEndpoint.get("getUser0134", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0135 extends HttpApiEndpoint.get("getUser0135", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0136 extends HttpApiEndpoint.get("getUser0136", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0137 extends HttpApiEndpoint.get("getUser0137", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0138 extends HttpApiEndpoint.get("getUser0138", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0139 extends HttpApiEndpoint.get("getUser0139", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0140 extends HttpApiEndpoint.get("getUser0140", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0141 extends HttpApiEndpoint.get("getUser0141", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0142 extends HttpApiEndpoint.get("getUser0142", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0143 extends HttpApiEndpoint.get("getUser0143", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0144 extends HttpApiEndpoint.get("getUser0144", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0145 extends HttpApiEndpoint.get("getUser0145", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0146 extends HttpApiEndpoint.get("getUser0146", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0147 extends HttpApiEndpoint.get("getUser0147", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0148 extends HttpApiEndpoint.get("getUser0148", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0149 extends HttpApiEndpoint.get("getUser0149", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0150 extends HttpApiEndpoint.get("getUser0150", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0151 extends HttpApiEndpoint.get("getUser0151", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0152 extends HttpApiEndpoint.get("getUser0152", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0153 extends HttpApiEndpoint.get("getUser0153", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0154 extends HttpApiEndpoint.get("getUser0154", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0155 extends HttpApiEndpoint.get("getUser0155", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0156 extends HttpApiEndpoint.get("getUser0156", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0157 extends HttpApiEndpoint.get("getUser0157", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0158 extends HttpApiEndpoint.get("getUser0158", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0159 extends HttpApiEndpoint.get("getUser0159", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0160 extends HttpApiEndpoint.get("getUser0160", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0161 extends HttpApiEndpoint.get("getUser0161", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0162 extends HttpApiEndpoint.get("getUser0162", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0163 extends HttpApiEndpoint.get("getUser0163", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0164 extends HttpApiEndpoint.get("getUser0164", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0165 extends HttpApiEndpoint.get("getUser0165", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0166 extends HttpApiEndpoint.get("getUser0166", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0167 extends HttpApiEndpoint.get("getUser0167", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0168 extends HttpApiEndpoint.get("getUser0168", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0169 extends HttpApiEndpoint.get("getUser0169", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0170 extends HttpApiEndpoint.get("getUser0170", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0171 extends HttpApiEndpoint.get("getUser0171", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0172 extends HttpApiEndpoint.get("getUser0172", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0173 extends HttpApiEndpoint.get("getUser0173", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0174 extends HttpApiEndpoint.get("getUser0174", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0175 extends HttpApiEndpoint.get("getUser0175", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0176 extends HttpApiEndpoint.get("getUser0176", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0177 extends HttpApiEndpoint.get("getUser0177", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0178 extends HttpApiEndpoint.get("getUser0178", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0179 extends HttpApiEndpoint.get("getUser0179", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0180 extends HttpApiEndpoint.get("getUser0180", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0181 extends HttpApiEndpoint.get("getUser0181", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0182 extends HttpApiEndpoint.get("getUser0182", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0183 extends HttpApiEndpoint.get("getUser0183", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0184 extends HttpApiEndpoint.get("getUser0184", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0185 extends HttpApiEndpoint.get("getUser0185", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0186 extends HttpApiEndpoint.get("getUser0186", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0187 extends HttpApiEndpoint.get("getUser0187", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0188 extends HttpApiEndpoint.get("getUser0188", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0189 extends HttpApiEndpoint.get("getUser0189", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0190 extends HttpApiEndpoint.get("getUser0190", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0191 extends HttpApiEndpoint.get("getUser0191", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0192 extends HttpApiEndpoint.get("getUser0192", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0193 extends HttpApiEndpoint.get("getUser0193", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0194 extends HttpApiEndpoint.get("getUser0194", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0195 extends HttpApiEndpoint.get("getUser0195", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0196 extends HttpApiEndpoint.get("getUser0196", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0197 extends HttpApiEndpoint.get("getUser0197", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0198 extends HttpApiEndpoint.get("getUser0198", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0199 extends HttpApiEndpoint.get("getUser0199", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0200 extends HttpApiEndpoint.get("getUser0200", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0201 extends HttpApiEndpoint.get("getUser0201", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0202 extends HttpApiEndpoint.get("getUser0202", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0203 extends HttpApiEndpoint.get("getUser0203", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0204 extends HttpApiEndpoint.get("getUser0204", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0205 extends HttpApiEndpoint.get("getUser0205", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0206 extends HttpApiEndpoint.get("getUser0206", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0207 extends HttpApiEndpoint.get("getUser0207", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0208 extends HttpApiEndpoint.get("getUser0208", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0209 extends HttpApiEndpoint.get("getUser0209", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0210 extends HttpApiEndpoint.get("getUser0210", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0211 extends HttpApiEndpoint.get("getUser0211", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0212 extends HttpApiEndpoint.get("getUser0212", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0213 extends HttpApiEndpoint.get("getUser0213", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0214 extends HttpApiEndpoint.get("getUser0214", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0215 extends HttpApiEndpoint.get("getUser0215", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0216 extends HttpApiEndpoint.get("getUser0216", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0217 extends HttpApiEndpoint.get("getUser0217", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0218 extends HttpApiEndpoint.get("getUser0218", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0219 extends HttpApiEndpoint.get("getUser0219", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0220 extends HttpApiEndpoint.get("getUser0220", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0221 extends HttpApiEndpoint.get("getUser0221", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0222 extends HttpApiEndpoint.get("getUser0222", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0223 extends HttpApiEndpoint.get("getUser0223", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0224 extends HttpApiEndpoint.get("getUser0224", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0225 extends HttpApiEndpoint.get("getUser0225", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0226 extends HttpApiEndpoint.get("getUser0226", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0227 extends HttpApiEndpoint.get("getUser0227", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0228 extends HttpApiEndpoint.get("getUser0228", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0229 extends HttpApiEndpoint.get("getUser0229", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0230 extends HttpApiEndpoint.get("getUser0230", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0231 extends HttpApiEndpoint.get("getUser0231", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0232 extends HttpApiEndpoint.get("getUser0232", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0233 extends HttpApiEndpoint.get("getUser0233", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0234 extends HttpApiEndpoint.get("getUser0234", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0235 extends HttpApiEndpoint.get("getUser0235", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0236 extends HttpApiEndpoint.get("getUser0236", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0237 extends HttpApiEndpoint.get("getUser0237", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0238 extends HttpApiEndpoint.get("getUser0238", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0239 extends HttpApiEndpoint.get("getUser0239", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0240 extends HttpApiEndpoint.get("getUser0240", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0241 extends HttpApiEndpoint.get("getUser0241", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0242 extends HttpApiEndpoint.get("getUser0242", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0243 extends HttpApiEndpoint.get("getUser0243", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0244 extends HttpApiEndpoint.get("getUser0244", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0245 extends HttpApiEndpoint.get("getUser0245", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0246 extends HttpApiEndpoint.get("getUser0246", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0247 extends HttpApiEndpoint.get("getUser0247", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0248 extends HttpApiEndpoint.get("getUser0248", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0249 extends HttpApiEndpoint.get("getUser0249", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0250 extends HttpApiEndpoint.get("getUser0250", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0251 extends HttpApiEndpoint.get("getUser0251", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0252 extends HttpApiEndpoint.get("getUser0252", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0253 extends HttpApiEndpoint.get("getUser0253", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0254 extends HttpApiEndpoint.get("getUser0254", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0255 extends HttpApiEndpoint.get("getUser0255", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0256 extends HttpApiEndpoint.get("getUser0256", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0257 extends HttpApiEndpoint.get("getUser0257", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0258 extends HttpApiEndpoint.get("getUser0258", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0259 extends HttpApiEndpoint.get("getUser0259", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0260 extends HttpApiEndpoint.get("getUser0260", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0261 extends HttpApiEndpoint.get("getUser0261", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0262 extends HttpApiEndpoint.get("getUser0262", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0263 extends HttpApiEndpoint.get("getUser0263", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0264 extends HttpApiEndpoint.get("getUser0264", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0265 extends HttpApiEndpoint.get("getUser0265", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0266 extends HttpApiEndpoint.get("getUser0266", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0267 extends HttpApiEndpoint.get("getUser0267", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0268 extends HttpApiEndpoint.get("getUser0268", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0269 extends HttpApiEndpoint.get("getUser0269", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0270 extends HttpApiEndpoint.get("getUser0270", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0271 extends HttpApiEndpoint.get("getUser0271", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0272 extends HttpApiEndpoint.get("getUser0272", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0273 extends HttpApiEndpoint.get("getUser0273", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0274 extends HttpApiEndpoint.get("getUser0274", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0275 extends HttpApiEndpoint.get("getUser0275", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0276 extends HttpApiEndpoint.get("getUser0276", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0277 extends HttpApiEndpoint.get("getUser0277", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0278 extends HttpApiEndpoint.get("getUser0278", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0279 extends HttpApiEndpoint.get("getUser0279", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0280 extends HttpApiEndpoint.get("getUser0280", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0281 extends HttpApiEndpoint.get("getUser0281", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0282 extends HttpApiEndpoint.get("getUser0282", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0283 extends HttpApiEndpoint.get("getUser0283", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0284 extends HttpApiEndpoint.get("getUser0284", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0285 extends HttpApiEndpoint.get("getUser0285", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0286 extends HttpApiEndpoint.get("getUser0286", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0287 extends HttpApiEndpoint.get("getUser0287", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0288 extends HttpApiEndpoint.get("getUser0288", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0289 extends HttpApiEndpoint.get("getUser0289", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0290 extends HttpApiEndpoint.get("getUser0290", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0291 extends HttpApiEndpoint.get("getUser0291", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0292 extends HttpApiEndpoint.get("getUser0292", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0293 extends HttpApiEndpoint.get("getUser0293", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0294 extends HttpApiEndpoint.get("getUser0294", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0295 extends HttpApiEndpoint.get("getUser0295", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0296 extends HttpApiEndpoint.get("getUser0296", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0297 extends HttpApiEndpoint.get("getUser0297", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0298 extends HttpApiEndpoint.get("getUser0298", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0299 extends HttpApiEndpoint.get("getUser0299", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0300 extends HttpApiEndpoint.get("getUser0300", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0301 extends HttpApiEndpoint.get("getUser0301", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0302 extends HttpApiEndpoint.get("getUser0302", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0303 extends HttpApiEndpoint.get("getUser0303", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0304 extends HttpApiEndpoint.get("getUser0304", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0305 extends HttpApiEndpoint.get("getUser0305", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0306 extends HttpApiEndpoint.get("getUser0306", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0307 extends HttpApiEndpoint.get("getUser0307", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0308 extends HttpApiEndpoint.get("getUser0308", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0309 extends HttpApiEndpoint.get("getUser0309", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0310 extends HttpApiEndpoint.get("getUser0310", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0311 extends HttpApiEndpoint.get("getUser0311", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0312 extends HttpApiEndpoint.get("getUser0312", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0313 extends HttpApiEndpoint.get("getUser0313", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0314 extends HttpApiEndpoint.get("getUser0314", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0315 extends HttpApiEndpoint.get("getUser0315", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0316 extends HttpApiEndpoint.get("getUser0316", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0317 extends HttpApiEndpoint.get("getUser0317", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0318 extends HttpApiEndpoint.get("getUser0318", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0319 extends HttpApiEndpoint.get("getUser0319", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0320 extends HttpApiEndpoint.get("getUser0320", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0321 extends HttpApiEndpoint.get("getUser0321", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0322 extends HttpApiEndpoint.get("getUser0322", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0323 extends HttpApiEndpoint.get("getUser0323", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0324 extends HttpApiEndpoint.get("getUser0324", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0325 extends HttpApiEndpoint.get("getUser0325", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0326 extends HttpApiEndpoint.get("getUser0326", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0327 extends HttpApiEndpoint.get("getUser0327", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0328 extends HttpApiEndpoint.get("getUser0328", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0329 extends HttpApiEndpoint.get("getUser0329", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0330 extends HttpApiEndpoint.get("getUser0330", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0331 extends HttpApiEndpoint.get("getUser0331", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0332 extends HttpApiEndpoint.get("getUser0332", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0333 extends HttpApiEndpoint.get("getUser0333", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0334 extends HttpApiEndpoint.get("getUser0334", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0335 extends HttpApiEndpoint.get("getUser0335", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0336 extends HttpApiEndpoint.get("getUser0336", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0337 extends HttpApiEndpoint.get("getUser0337", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0338 extends HttpApiEndpoint.get("getUser0338", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0339 extends HttpApiEndpoint.get("getUser0339", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0340 extends HttpApiEndpoint.get("getUser0340", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0341 extends HttpApiEndpoint.get("getUser0341", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0342 extends HttpApiEndpoint.get("getUser0342", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0343 extends HttpApiEndpoint.get("getUser0343", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0344 extends HttpApiEndpoint.get("getUser0344", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0345 extends HttpApiEndpoint.get("getUser0345", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0346 extends HttpApiEndpoint.get("getUser0346", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0347 extends HttpApiEndpoint.get("getUser0347", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0348 extends HttpApiEndpoint.get("getUser0348", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0349 extends HttpApiEndpoint.get("getUser0349", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0350 extends HttpApiEndpoint.get("getUser0350", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0351 extends HttpApiEndpoint.get("getUser0351", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0352 extends HttpApiEndpoint.get("getUser0352", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0353 extends HttpApiEndpoint.get("getUser0353", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0354 extends HttpApiEndpoint.get("getUser0354", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0355 extends HttpApiEndpoint.get("getUser0355", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0356 extends HttpApiEndpoint.get("getUser0356", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0357 extends HttpApiEndpoint.get("getUser0357", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0358 extends HttpApiEndpoint.get("getUser0358", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0359 extends HttpApiEndpoint.get("getUser0359", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0360 extends HttpApiEndpoint.get("getUser0360", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0361 extends HttpApiEndpoint.get("getUser0361", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0362 extends HttpApiEndpoint.get("getUser0362", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0363 extends HttpApiEndpoint.get("getUser0363", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0364 extends HttpApiEndpoint.get("getUser0364", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0365 extends HttpApiEndpoint.get("getUser0365", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0366 extends HttpApiEndpoint.get("getUser0366", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0367 extends HttpApiEndpoint.get("getUser0367", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0368 extends HttpApiEndpoint.get("getUser0368", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0369 extends HttpApiEndpoint.get("getUser0369", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0370 extends HttpApiEndpoint.get("getUser0370", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0371 extends HttpApiEndpoint.get("getUser0371", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0372 extends HttpApiEndpoint.get("getUser0372", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0373 extends HttpApiEndpoint.get("getUser0373", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0374 extends HttpApiEndpoint.get("getUser0374", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0375 extends HttpApiEndpoint.get("getUser0375", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0376 extends HttpApiEndpoint.get("getUser0376", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0377 extends HttpApiEndpoint.get("getUser0377", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0378 extends HttpApiEndpoint.get("getUser0378", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0379 extends HttpApiEndpoint.get("getUser0379", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0380 extends HttpApiEndpoint.get("getUser0380", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0381 extends HttpApiEndpoint.get("getUser0381", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0382 extends HttpApiEndpoint.get("getUser0382", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0383 extends HttpApiEndpoint.get("getUser0383", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0384 extends HttpApiEndpoint.get("getUser0384", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0385 extends HttpApiEndpoint.get("getUser0385", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0386 extends HttpApiEndpoint.get("getUser0386", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0387 extends HttpApiEndpoint.get("getUser0387", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0388 extends HttpApiEndpoint.get("getUser0388", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0389 extends HttpApiEndpoint.get("getUser0389", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0390 extends HttpApiEndpoint.get("getUser0390", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0391 extends HttpApiEndpoint.get("getUser0391", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0392 extends HttpApiEndpoint.get("getUser0392", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0393 extends HttpApiEndpoint.get("getUser0393", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0394 extends HttpApiEndpoint.get("getUser0394", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0395 extends HttpApiEndpoint.get("getUser0395", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0396 extends HttpApiEndpoint.get("getUser0396", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0397 extends HttpApiEndpoint.get("getUser0397", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0398 extends HttpApiEndpoint.get("getUser0398", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0399 extends HttpApiEndpoint.get("getUser0399", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0400 extends HttpApiEndpoint.get("getUser0400", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0401 extends HttpApiEndpoint.get("getUser0401", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0402 extends HttpApiEndpoint.get("getUser0402", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0403 extends HttpApiEndpoint.get("getUser0403", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0404 extends HttpApiEndpoint.get("getUser0404", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0405 extends HttpApiEndpoint.get("getUser0405", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0406 extends HttpApiEndpoint.get("getUser0406", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0407 extends HttpApiEndpoint.get("getUser0407", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0408 extends HttpApiEndpoint.get("getUser0408", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0409 extends HttpApiEndpoint.get("getUser0409", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0410 extends HttpApiEndpoint.get("getUser0410", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0411 extends HttpApiEndpoint.get("getUser0411", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0412 extends HttpApiEndpoint.get("getUser0412", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0413 extends HttpApiEndpoint.get("getUser0413", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0414 extends HttpApiEndpoint.get("getUser0414", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0415 extends HttpApiEndpoint.get("getUser0415", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0416 extends HttpApiEndpoint.get("getUser0416", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0417 extends HttpApiEndpoint.get("getUser0417", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0418 extends HttpApiEndpoint.get("getUser0418", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0419 extends HttpApiEndpoint.get("getUser0419", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0420 extends HttpApiEndpoint.get("getUser0420", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0421 extends HttpApiEndpoint.get("getUser0421", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0422 extends HttpApiEndpoint.get("getUser0422", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0423 extends HttpApiEndpoint.get("getUser0423", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0424 extends HttpApiEndpoint.get("getUser0424", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0425 extends HttpApiEndpoint.get("getUser0425", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0426 extends HttpApiEndpoint.get("getUser0426", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0427 extends HttpApiEndpoint.get("getUser0427", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0428 extends HttpApiEndpoint.get("getUser0428", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0429 extends HttpApiEndpoint.get("getUser0429", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0430 extends HttpApiEndpoint.get("getUser0430", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0431 extends HttpApiEndpoint.get("getUser0431", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0432 extends HttpApiEndpoint.get("getUser0432", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0433 extends HttpApiEndpoint.get("getUser0433", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0434 extends HttpApiEndpoint.get("getUser0434", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0435 extends HttpApiEndpoint.get("getUser0435", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0436 extends HttpApiEndpoint.get("getUser0436", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0437 extends HttpApiEndpoint.get("getUser0437", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0438 extends HttpApiEndpoint.get("getUser0438", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0439 extends HttpApiEndpoint.get("getUser0439", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0440 extends HttpApiEndpoint.get("getUser0440", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0441 extends HttpApiEndpoint.get("getUser0441", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0442 extends HttpApiEndpoint.get("getUser0442", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0443 extends HttpApiEndpoint.get("getUser0443", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0444 extends HttpApiEndpoint.get("getUser0444", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0445 extends HttpApiEndpoint.get("getUser0445", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0446 extends HttpApiEndpoint.get("getUser0446", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0447 extends HttpApiEndpoint.get("getUser0447", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0448 extends HttpApiEndpoint.get("getUser0448", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0449 extends HttpApiEndpoint.get("getUser0449", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0450 extends HttpApiEndpoint.get("getUser0450", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0451 extends HttpApiEndpoint.get("getUser0451", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0452 extends HttpApiEndpoint.get("getUser0452", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0453 extends HttpApiEndpoint.get("getUser0453", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0454 extends HttpApiEndpoint.get("getUser0454", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0455 extends HttpApiEndpoint.get("getUser0455", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0456 extends HttpApiEndpoint.get("getUser0456", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0457 extends HttpApiEndpoint.get("getUser0457", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0458 extends HttpApiEndpoint.get("getUser0458", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0459 extends HttpApiEndpoint.get("getUser0459", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0460 extends HttpApiEndpoint.get("getUser0460", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0461 extends HttpApiEndpoint.get("getUser0461", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0462 extends HttpApiEndpoint.get("getUser0462", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0463 extends HttpApiEndpoint.get("getUser0463", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0464 extends HttpApiEndpoint.get("getUser0464", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0465 extends HttpApiEndpoint.get("getUser0465", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0466 extends HttpApiEndpoint.get("getUser0466", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0467 extends HttpApiEndpoint.get("getUser0467", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0468 extends HttpApiEndpoint.get("getUser0468", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0469 extends HttpApiEndpoint.get("getUser0469", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0470 extends HttpApiEndpoint.get("getUser0470", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0471 extends HttpApiEndpoint.get("getUser0471", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0472 extends HttpApiEndpoint.get("getUser0472", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0473 extends HttpApiEndpoint.get("getUser0473", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0474 extends HttpApiEndpoint.get("getUser0474", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0475 extends HttpApiEndpoint.get("getUser0475", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0476 extends HttpApiEndpoint.get("getUser0476", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0477 extends HttpApiEndpoint.get("getUser0477", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0478 extends HttpApiEndpoint.get("getUser0478", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0479 extends HttpApiEndpoint.get("getUser0479", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0480 extends HttpApiEndpoint.get("getUser0480", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0481 extends HttpApiEndpoint.get("getUser0481", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0482 extends HttpApiEndpoint.get("getUser0482", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0483 extends HttpApiEndpoint.get("getUser0483", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0484 extends HttpApiEndpoint.get("getUser0484", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0485 extends HttpApiEndpoint.get("getUser0485", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0486 extends HttpApiEndpoint.get("getUser0486", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0487 extends HttpApiEndpoint.get("getUser0487", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0488 extends HttpApiEndpoint.get("getUser0488", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0489 extends HttpApiEndpoint.get("getUser0489", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0490 extends HttpApiEndpoint.get("getUser0490", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0491 extends HttpApiEndpoint.get("getUser0491", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0492 extends HttpApiEndpoint.get("getUser0492", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0493 extends HttpApiEndpoint.get("getUser0493", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0494 extends HttpApiEndpoint.get("getUser0494", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0495 extends HttpApiEndpoint.get("getUser0495", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0496 extends HttpApiEndpoint.get("getUser0496", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0497 extends HttpApiEndpoint.get("getUser0497", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0498 extends HttpApiEndpoint.get("getUser0498", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0499 extends HttpApiEndpoint.get("getUser0499", "/users/:id", { + params: Params, + success: User +}) {} + +class GetUser0500 extends HttpApiEndpoint.get("getUser0500", "/users/:id", { + params: Params, + success: User +}) {} + +const api = HttpApi.make("Api").add( + HttpApiGroup.make("users").add( + GetUser0001, + GetUser0002, + GetUser0003, + GetUser0004, + GetUser0005, + GetUser0006, + GetUser0007, + GetUser0008, + GetUser0009, + GetUser0010, + GetUser0011, + GetUser0012, + GetUser0013, + GetUser0014, + GetUser0015, + GetUser0016, + GetUser0017, + GetUser0018, + GetUser0019, + GetUser0020, + GetUser0021, + GetUser0022, + GetUser0023, + GetUser0024, + GetUser0025, + GetUser0026, + GetUser0027, + GetUser0028, + GetUser0029, + GetUser0030, + GetUser0031, + GetUser0032, + GetUser0033, + GetUser0034, + GetUser0035, + GetUser0036, + GetUser0037, + GetUser0038, + GetUser0039, + GetUser0040, + GetUser0041, + GetUser0042, + GetUser0043, + GetUser0044, + GetUser0045, + GetUser0046, + GetUser0047, + GetUser0048, + GetUser0049, + GetUser0050, + GetUser0051, + GetUser0052, + GetUser0053, + GetUser0054, + GetUser0055, + GetUser0056, + GetUser0057, + GetUser0058, + GetUser0059, + GetUser0060, + GetUser0061, + GetUser0062, + GetUser0063, + GetUser0064, + GetUser0065, + GetUser0066, + GetUser0067, + GetUser0068, + GetUser0069, + GetUser0070, + GetUser0071, + GetUser0072, + GetUser0073, + GetUser0074, + GetUser0075, + GetUser0076, + GetUser0077, + GetUser0078, + GetUser0079, + GetUser0080, + GetUser0081, + GetUser0082, + GetUser0083, + GetUser0084, + GetUser0085, + GetUser0086, + GetUser0087, + GetUser0088, + GetUser0089, + GetUser0090, + GetUser0091, + GetUser0092, + GetUser0093, + GetUser0094, + GetUser0095, + GetUser0096, + GetUser0097, + GetUser0098, + GetUser0099, + GetUser0100, + GetUser0101, + GetUser0102, + GetUser0103, + GetUser0104, + GetUser0105, + GetUser0106, + GetUser0107, + GetUser0108, + GetUser0109, + GetUser0110, + GetUser0111, + GetUser0112, + GetUser0113, + GetUser0114, + GetUser0115, + GetUser0116, + GetUser0117, + GetUser0118, + GetUser0119, + GetUser0120, + GetUser0121, + GetUser0122, + GetUser0123, + GetUser0124, + GetUser0125, + GetUser0126, + GetUser0127, + GetUser0128, + GetUser0129, + GetUser0130, + GetUser0131, + GetUser0132, + GetUser0133, + GetUser0134, + GetUser0135, + GetUser0136, + GetUser0137, + GetUser0138, + GetUser0139, + GetUser0140, + GetUser0141, + GetUser0142, + GetUser0143, + GetUser0144, + GetUser0145, + GetUser0146, + GetUser0147, + GetUser0148, + GetUser0149, + GetUser0150, + GetUser0151, + GetUser0152, + GetUser0153, + GetUser0154, + GetUser0155, + GetUser0156, + GetUser0157, + GetUser0158, + GetUser0159, + GetUser0160, + GetUser0161, + GetUser0162, + GetUser0163, + GetUser0164, + GetUser0165, + GetUser0166, + GetUser0167, + GetUser0168, + GetUser0169, + GetUser0170, + GetUser0171, + GetUser0172, + GetUser0173, + GetUser0174, + GetUser0175, + GetUser0176, + GetUser0177, + GetUser0178, + GetUser0179, + GetUser0180, + GetUser0181, + GetUser0182, + GetUser0183, + GetUser0184, + GetUser0185, + GetUser0186, + GetUser0187, + GetUser0188, + GetUser0189, + GetUser0190, + GetUser0191, + GetUser0192, + GetUser0193, + GetUser0194, + GetUser0195, + GetUser0196, + GetUser0197, + GetUser0198, + GetUser0199, + GetUser0200, + GetUser0201, + GetUser0202, + GetUser0203, + GetUser0204, + GetUser0205, + GetUser0206, + GetUser0207, + GetUser0208, + GetUser0209, + GetUser0210, + GetUser0211, + GetUser0212, + GetUser0213, + GetUser0214, + GetUser0215, + GetUser0216, + GetUser0217, + GetUser0218, + GetUser0219, + GetUser0220, + GetUser0221, + GetUser0222, + GetUser0223, + GetUser0224, + GetUser0225, + GetUser0226, + GetUser0227, + GetUser0228, + GetUser0229, + GetUser0230, + GetUser0231, + GetUser0232, + GetUser0233, + GetUser0234, + GetUser0235, + GetUser0236, + GetUser0237, + GetUser0238, + GetUser0239, + GetUser0240, + GetUser0241, + GetUser0242, + GetUser0243, + GetUser0244, + GetUser0245, + GetUser0246, + GetUser0247, + GetUser0248, + GetUser0249, + GetUser0250, + GetUser0251, + GetUser0252, + GetUser0253, + GetUser0254, + GetUser0255, + GetUser0256, + GetUser0257, + GetUser0258, + GetUser0259, + GetUser0260, + GetUser0261, + GetUser0262, + GetUser0263, + GetUser0264, + GetUser0265, + GetUser0266, + GetUser0267, + GetUser0268, + GetUser0269, + GetUser0270, + GetUser0271, + GetUser0272, + GetUser0273, + GetUser0274, + GetUser0275, + GetUser0276, + GetUser0277, + GetUser0278, + GetUser0279, + GetUser0280, + GetUser0281, + GetUser0282, + GetUser0283, + GetUser0284, + GetUser0285, + GetUser0286, + GetUser0287, + GetUser0288, + GetUser0289, + GetUser0290, + GetUser0291, + GetUser0292, + GetUser0293, + GetUser0294, + GetUser0295, + GetUser0296, + GetUser0297, + GetUser0298, + GetUser0299, + GetUser0300, + GetUser0301, + GetUser0302, + GetUser0303, + GetUser0304, + GetUser0305, + GetUser0306, + GetUser0307, + GetUser0308, + GetUser0309, + GetUser0310, + GetUser0311, + GetUser0312, + GetUser0313, + GetUser0314, + GetUser0315, + GetUser0316, + GetUser0317, + GetUser0318, + GetUser0319, + GetUser0320, + GetUser0321, + GetUser0322, + GetUser0323, + GetUser0324, + GetUser0325, + GetUser0326, + GetUser0327, + GetUser0328, + GetUser0329, + GetUser0330, + GetUser0331, + GetUser0332, + GetUser0333, + GetUser0334, + GetUser0335, + GetUser0336, + GetUser0337, + GetUser0338, + GetUser0339, + GetUser0340, + GetUser0341, + GetUser0342, + GetUser0343, + GetUser0344, + GetUser0345, + GetUser0346, + GetUser0347, + GetUser0348, + GetUser0349, + GetUser0350, + GetUser0351, + GetUser0352, + GetUser0353, + GetUser0354, + GetUser0355, + GetUser0356, + GetUser0357, + GetUser0358, + GetUser0359, + GetUser0360, + GetUser0361, + GetUser0362, + GetUser0363, + GetUser0364, + GetUser0365, + GetUser0366, + GetUser0367, + GetUser0368, + GetUser0369, + GetUser0370, + GetUser0371, + GetUser0372, + GetUser0373, + GetUser0374, + GetUser0375, + GetUser0376, + GetUser0377, + GetUser0378, + GetUser0379, + GetUser0380, + GetUser0381, + GetUser0382, + GetUser0383, + GetUser0384, + GetUser0385, + GetUser0386, + GetUser0387, + GetUser0388, + GetUser0389, + GetUser0390, + GetUser0391, + GetUser0392, + GetUser0393, + GetUser0394, + GetUser0395, + GetUser0396, + GetUser0397, + GetUser0398, + GetUser0399, + GetUser0400, + GetUser0401, + GetUser0402, + GetUser0403, + GetUser0404, + GetUser0405, + GetUser0406, + GetUser0407, + GetUser0408, + GetUser0409, + GetUser0410, + GetUser0411, + GetUser0412, + GetUser0413, + GetUser0414, + GetUser0415, + GetUser0416, + GetUser0417, + GetUser0418, + GetUser0419, + GetUser0420, + GetUser0421, + GetUser0422, + GetUser0423, + GetUser0424, + GetUser0425, + GetUser0426, + GetUser0427, + GetUser0428, + GetUser0429, + GetUser0430, + GetUser0431, + GetUser0432, + GetUser0433, + GetUser0434, + GetUser0435, + GetUser0436, + GetUser0437, + GetUser0438, + GetUser0439, + GetUser0440, + GetUser0441, + GetUser0442, + GetUser0443, + GetUser0444, + GetUser0445, + GetUser0446, + GetUser0447, + GetUser0448, + GetUser0449, + GetUser0450, + GetUser0451, + GetUser0452, + GetUser0453, + GetUser0454, + GetUser0455, + GetUser0456, + GetUser0457, + GetUser0458, + GetUser0459, + GetUser0460, + GetUser0461, + GetUser0462, + GetUser0463, + GetUser0464, + GetUser0465, + GetUser0466, + GetUser0467, + GetUser0468, + GetUser0469, + GetUser0470, + GetUser0471, + GetUser0472, + GetUser0473, + GetUser0474, + GetUser0475, + GetUser0476, + GetUser0477, + GetUser0478, + GetUser0479, + GetUser0480, + GetUser0481, + GetUser0482, + GetUser0483, + GetUser0484, + GetUser0485, + GetUser0486, + GetUser0487, + GetUser0488, + GetUser0489, + GetUser0490, + GetUser0491, + GetUser0492, + GetUser0493, + GetUser0494, + GetUser0495, + GetUser0496, + GetUser0497, + GetUser0498, + GetUser0499, + GetUser0500 + ) +) + +type Groups = typeof api extends HttpApi.HttpApi ? Groups : never +type Endpoints = HttpApiGroup.Endpoints + +export type Api = typeof api +export type EndpointIdentifiers = HttpApiEndpoint.Identifier +export type EndpointRequests = HttpApiEndpoint.Request +export type ServerServices = HttpApiEndpoint.ServerServices +export type ClientServices = HttpApiEndpoint.ClientServices diff --git a/repos/effect/packages/effect/typeperf/suites/httpapi/fixtures/endpoint-count-500.ts b/repos/effect/packages/effect/typeperf/suites/httpapi/fixtures/endpoint-count-500.ts new file mode 100644 index 0000000000..91b9680dca --- /dev/null +++ b/repos/effect/packages/effect/typeperf/suites/httpapi/fixtures/endpoint-count-500.ts @@ -0,0 +1,2031 @@ +// Measures endpoint declaration for 500 same-shaped endpoints in one group. +import { Schema } from "effect" +import { HttpApi, HttpApiEndpoint, HttpApiGroup } from "effect/unstable/httpapi" + +Schema.String +HttpApi.make("Api") +HttpApiGroup.make("users") +HttpApiEndpoint.get("warmup", "/warmup") + +const Params = Schema.Struct({ + id: Schema.FiniteFromString +}) + +const User = Schema.Struct({ + id: Schema.String, + name: Schema.String +}) + +const api = HttpApi.make("Api").add( + HttpApiGroup.make("users").add( + HttpApiEndpoint.get("getUser0001", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0002", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0003", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0004", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0005", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0006", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0007", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0008", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0009", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0010", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0011", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0012", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0013", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0014", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0015", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0016", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0017", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0018", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0019", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0020", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0021", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0022", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0023", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0024", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0025", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0026", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0027", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0028", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0029", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0030", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0031", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0032", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0033", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0034", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0035", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0036", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0037", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0038", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0039", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0040", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0041", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0042", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0043", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0044", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0045", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0046", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0047", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0048", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0049", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0050", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0051", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0052", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0053", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0054", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0055", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0056", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0057", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0058", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0059", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0060", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0061", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0062", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0063", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0064", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0065", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0066", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0067", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0068", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0069", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0070", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0071", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0072", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0073", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0074", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0075", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0076", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0077", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0078", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0079", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0080", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0081", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0082", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0083", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0084", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0085", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0086", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0087", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0088", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0089", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0090", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0091", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0092", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0093", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0094", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0095", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0096", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0097", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0098", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0099", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0100", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0101", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0102", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0103", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0104", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0105", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0106", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0107", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0108", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0109", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0110", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0111", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0112", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0113", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0114", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0115", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0116", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0117", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0118", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0119", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0120", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0121", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0122", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0123", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0124", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0125", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0126", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0127", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0128", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0129", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0130", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0131", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0132", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0133", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0134", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0135", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0136", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0137", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0138", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0139", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0140", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0141", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0142", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0143", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0144", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0145", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0146", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0147", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0148", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0149", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0150", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0151", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0152", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0153", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0154", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0155", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0156", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0157", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0158", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0159", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0160", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0161", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0162", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0163", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0164", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0165", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0166", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0167", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0168", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0169", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0170", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0171", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0172", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0173", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0174", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0175", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0176", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0177", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0178", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0179", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0180", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0181", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0182", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0183", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0184", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0185", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0186", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0187", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0188", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0189", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0190", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0191", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0192", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0193", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0194", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0195", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0196", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0197", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0198", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0199", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0200", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0201", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0202", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0203", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0204", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0205", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0206", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0207", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0208", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0209", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0210", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0211", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0212", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0213", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0214", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0215", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0216", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0217", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0218", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0219", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0220", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0221", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0222", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0223", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0224", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0225", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0226", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0227", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0228", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0229", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0230", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0231", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0232", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0233", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0234", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0235", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0236", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0237", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0238", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0239", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0240", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0241", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0242", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0243", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0244", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0245", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0246", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0247", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0248", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0249", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0250", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0251", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0252", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0253", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0254", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0255", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0256", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0257", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0258", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0259", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0260", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0261", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0262", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0263", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0264", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0265", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0266", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0267", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0268", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0269", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0270", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0271", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0272", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0273", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0274", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0275", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0276", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0277", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0278", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0279", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0280", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0281", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0282", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0283", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0284", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0285", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0286", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0287", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0288", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0289", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0290", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0291", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0292", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0293", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0294", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0295", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0296", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0297", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0298", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0299", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0300", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0301", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0302", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0303", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0304", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0305", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0306", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0307", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0308", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0309", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0310", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0311", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0312", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0313", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0314", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0315", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0316", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0317", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0318", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0319", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0320", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0321", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0322", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0323", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0324", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0325", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0326", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0327", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0328", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0329", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0330", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0331", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0332", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0333", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0334", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0335", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0336", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0337", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0338", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0339", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0340", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0341", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0342", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0343", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0344", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0345", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0346", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0347", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0348", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0349", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0350", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0351", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0352", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0353", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0354", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0355", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0356", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0357", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0358", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0359", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0360", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0361", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0362", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0363", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0364", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0365", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0366", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0367", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0368", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0369", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0370", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0371", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0372", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0373", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0374", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0375", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0376", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0377", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0378", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0379", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0380", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0381", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0382", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0383", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0384", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0385", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0386", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0387", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0388", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0389", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0390", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0391", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0392", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0393", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0394", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0395", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0396", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0397", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0398", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0399", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0400", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0401", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0402", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0403", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0404", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0405", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0406", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0407", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0408", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0409", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0410", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0411", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0412", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0413", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0414", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0415", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0416", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0417", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0418", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0419", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0420", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0421", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0422", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0423", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0424", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0425", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0426", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0427", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0428", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0429", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0430", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0431", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0432", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0433", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0434", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0435", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0436", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0437", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0438", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0439", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0440", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0441", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0442", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0443", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0444", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0445", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0446", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0447", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0448", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0449", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0450", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0451", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0452", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0453", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0454", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0455", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0456", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0457", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0458", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0459", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0460", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0461", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0462", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0463", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0464", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0465", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0466", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0467", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0468", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0469", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0470", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0471", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0472", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0473", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0474", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0475", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0476", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0477", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0478", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0479", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0480", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0481", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0482", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0483", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0484", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0485", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0486", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0487", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0488", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0489", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0490", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0491", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0492", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0493", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0494", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0495", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0496", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0497", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0498", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0499", "/users/:id", { + params: Params, + success: User + }), + HttpApiEndpoint.get("getUser0500", "/users/:id", { + params: Params, + success: User + }) + ) +) + +type Groups = typeof api extends HttpApi.HttpApi ? Groups : never +type Endpoints = HttpApiGroup.Endpoints + +export type Api = typeof api +export type EndpointIdentifiers = HttpApiEndpoint.Identifier +export type EndpointRequests = HttpApiEndpoint.Request +export type ServerServices = HttpApiEndpoint.ServerServices +export type ClientServices = HttpApiEndpoint.ClientServices diff --git a/repos/effect/packages/effect/typeperf/suites/httpapi/fixtures/group-endpoints-count-500.ts b/repos/effect/packages/effect/typeperf/suites/httpapi/fixtures/group-endpoints-count-500.ts new file mode 100644 index 0000000000..729d253a68 --- /dev/null +++ b/repos/effect/packages/effect/typeperf/suites/httpapi/fixtures/group-endpoints-count-500.ts @@ -0,0 +1,14 @@ +// Measures HttpApiGroup.Endpoints extraction from one type-only group with 500 endpoints. +import { Schema } from "effect" +import { HttpApi, HttpApiEndpoint, HttpApiGroup } from "effect/unstable/httpapi" +import type { Group } from "./_grouped-api-types.ts" + +Schema.String +HttpApi.make("Api") +HttpApiGroup.make("users") +HttpApiEndpoint.get("warmup", "/warmup") + +type Assert = T +type Endpoints = HttpApiGroup.Endpoints> + +export type HasLastEndpoint = Assert<"getUser0500" extends HttpApiEndpoint.Identifier ? true : false> diff --git a/repos/effect/packages/effect/typeperf/suites/httpapi/fixtures/url-builder-count-500.ts b/repos/effect/packages/effect/typeperf/suites/httpapi/fixtures/url-builder-count-500.ts new file mode 100644 index 0000000000..98d4e1f568 --- /dev/null +++ b/repos/effect/packages/effect/typeperf/suites/httpapi/fixtures/url-builder-count-500.ts @@ -0,0 +1,20 @@ +// Measures grouped URL builder method derivation from one group with 500 same-shaped endpoints. +import { Schema } from "effect" +import { HttpApi, HttpApiClient, HttpApiEndpoint, HttpApiGroup } from "effect/unstable/httpapi" +import { api } from "./_grouped-api-500.ts" + +Schema.String +HttpApi.make("Api") +HttpApiGroup.make("users") +HttpApiEndpoint.get("warmup", "/warmup") + +type UrlBuilder = HttpApiClient.UrlBuilder +type Users = UrlBuilder["users"] +type Methods = Users[keyof Users] + +export type Api = typeof api +export type GeneratedUrlBuilder = UrlBuilder +export type UrlBuilderMethodNames = keyof Users +export type UrlBuilderMethods = Methods +export type UrlBuilderMethodRequests = Parameters[0] +export type UrlBuilderMethodResults = ReturnType diff --git a/repos/effect/packages/effect/typeperf/suites/httpapi/fixtures/url-builder-top-level-count-500.ts b/repos/effect/packages/effect/typeperf/suites/httpapi/fixtures/url-builder-top-level-count-500.ts new file mode 100644 index 0000000000..1cc6b61107 --- /dev/null +++ b/repos/effect/packages/effect/typeperf/suites/httpapi/fixtures/url-builder-top-level-count-500.ts @@ -0,0 +1,20 @@ +// Measures top-level URL builder method derivation from 500 same-shaped endpoints. +import { Schema } from "effect" +import { HttpApi, HttpApiClient, HttpApiEndpoint, HttpApiGroup } from "effect/unstable/httpapi" +import type { Endpoint } from "./_grouped-api-500.ts" + +Schema.String +HttpApi.make("Api") +HttpApiGroup.make("top", { topLevel: true }) +HttpApiEndpoint.get("warmup", "/warmup") + +type TopGroup = HttpApiGroup.HttpApiGroup<"top", Endpoint, true> +type Api = HttpApi.HttpApi<"Api", TopGroup> +type UrlBuilder = HttpApiClient.UrlBuilder +type Methods = UrlBuilder[keyof UrlBuilder] + +export type GeneratedUrlBuilder = UrlBuilder +export type UrlBuilderMethodNames = keyof UrlBuilder +export type UrlBuilderMethods = Methods +export type UrlBuilderMethodRequests = Parameters[0] +export type UrlBuilderMethodResults = ReturnType diff --git a/repos/effect/packages/effect/typeperf/suites/httpapi/thresholds.json b/repos/effect/packages/effect/typeperf/suites/httpapi/thresholds.json new file mode 100644 index 0000000000..179d04eb95 --- /dev/null +++ b/repos/effect/packages/effect/typeperf/suites/httpapi/thresholds.json @@ -0,0 +1,62 @@ +{ + "endpoint-count-500": { + "maxInstantiationsDelta": 82199, + "maxTypesDelta": 5916 + }, + "endpoint-class-count-500": { + "maxInstantiationsDelta": 71842, + "maxTypesDelta": 7675 + }, + "group-endpoints-count-500": { + "maxInstantiationsDelta": 15690, + "maxTypesDelta": 6358 + }, + "client-methods-count-500": { + "maxInstantiationsDelta": 239391, + "maxTypesDelta": 22030 + }, + "client-top-level-count-500": { + "maxInstantiationsDelta": 94551, + "maxTypesDelta": 14513 + }, + "client-groups-100x5-count-500": { + "maxInstantiationsDelta": 125576, + "maxTypesDelta": 14795 + }, + "client-group-count-500": { + "maxInstantiationsDelta": 93389, + "maxTypesDelta": 14487 + }, + "client-endpoint-count-500": { + "maxInstantiationsDelta": 31913, + "maxTypesDelta": 10357 + }, + "url-builder-count-500": { + "maxInstantiationsDelta": 151035, + "maxTypesDelta": 14336 + }, + "url-builder-top-level-count-500": { + "maxInstantiationsDelta": 156640, + "maxTypesDelta": 14351 + }, + "builder-endpoint-count-500": { + "maxInstantiationsDelta": 25513, + "maxTypesDelta": 8008 + }, + "builder-handlers-count-100": { + "maxInstantiationsDelta": 182841, + "maxTypesDelta": 8087 + }, + "builder-handle-all-count-500": { + "maxInstantiationsDelta": 204648, + "maxTypesDelta": 14436 + }, + "builder-handle-all-two-batches-count-500": { + "maxInstantiationsDelta": 223605, + "maxTypesDelta": 14977 + }, + "builder-raw-handlers-count-100": { + "maxInstantiationsDelta": 182539, + "maxTypesDelta": 8087 + } +} diff --git a/repos/effect/packages/effect/typeperf/suites/schema/thresholds.json b/repos/effect/packages/effect/typeperf/suites/schema/thresholds.json index 2c94b228f9..dc5deeddf3 100644 --- a/repos/effect/packages/effect/typeperf/suites/schema/thresholds.json +++ b/repos/effect/packages/effect/typeperf/suites/schema/thresholds.json @@ -1,68 +1,90 @@ { "struct-required": { - "maxDelta": 401 + "maxInstantiationsDelta": 457, + "maxTypesDelta": 50 }, "struct-optional-only": { - "maxDelta": 647 + "maxInstantiationsDelta": 742, + "maxTypesDelta": 110 }, "struct-mutable-only": { - "maxDelta": 659 + "maxInstantiationsDelta": 725, + "maxTypesDelta": 101 }, "struct-mixed": { - "maxDelta": 953 + "maxInstantiationsDelta": 1056, + "maxTypesDelta": 169 }, "union": { - "maxDelta": 907 + "maxInstantiationsDelta": 935, + "maxTypesDelta": 156 }, "tagged-union": { - "maxDelta": 924 + "maxInstantiationsDelta": 967, + "maxTypesDelta": 223 }, "record": { - "maxDelta": 289 + "maxInstantiationsDelta": 319, + "maxTypesDelta": 94 }, "struct-with-rest": { - "maxDelta": 634 + "maxInstantiationsDelta": 762, + "maxTypesDelta": 152 }, "tuple": { - "maxDelta": 417 + "maxInstantiationsDelta": 453, + "maxTypesDelta": 116 }, "tuple-with-rest": { - "maxDelta": 408 + "maxInstantiationsDelta": 450, + "maxTypesDelta": 112 }, "array": { - "maxDelta": 199 + "maxInstantiationsDelta": 227, + "maxTypesDelta": 44 }, "non-empty-array": { - "maxDelta": 199 + "maxInstantiationsDelta": 227, + "maxTypesDelta": 44 }, "to-type": { - "maxDelta": 31 + "maxInstantiationsDelta": 231, + "maxTypesDelta": 64 }, "to-encoded": { - "maxDelta": 29 + "maxInstantiationsDelta": 59, + "maxTypesDelta": 13 }, "to-codec-json": { - "maxDelta": 117 + "maxInstantiationsDelta": 145, + "maxTypesDelta": 30 }, "to-codec-string-tree": { - "maxDelta": 59 + "maxInstantiationsDelta": 89, + "maxTypesDelta": 19 }, "to-codec-array-from-single": { - "maxDelta": 96 + "maxInstantiationsDelta": 163, + "maxTypesDelta": 60 }, "brand": { - "maxDelta": 82 + "maxInstantiationsDelta": 690, + "maxTypesDelta": 169 }, "decode-unknown-effect": { - "maxDelta": 2 + "maxInstantiationsDelta": 30, + "maxTypesDelta": 16 }, "encode-unknown-effect": { - "maxDelta": 2 + "maxInstantiationsDelta": 30, + "maxTypesDelta": 16 }, "to-standard-json-schema": { - "maxDelta": 128 + "maxInstantiationsDelta": 156, + "maxTypesDelta": 41 }, "exit": { - "maxDelta": 359 + "maxInstantiationsDelta": 411, + "maxTypesDelta": 77 } } diff --git a/repos/effect/packages/effect/typetest/Channel.tst.ts b/repos/effect/packages/effect/typetest/Channel.tst.ts index fec89edf89..7124e9972d 100644 --- a/repos/effect/packages/effect/typetest/Channel.tst.ts +++ b/repos/effect/packages/effect/typetest/Channel.tst.ts @@ -1,4 +1,4 @@ -import { Channel, Data, pipe, Result } from "effect" +import { Channel, Data, type Effect, pipe, Result } from "effect" import { describe, expect, it } from "tstyche" class ErrorA extends Data.TaggedError("ErrorA")<{ readonly message: string }> {} @@ -110,3 +110,9 @@ describe("Channel.catchReasons", () => { expect(result).type.toBe>() }) }) + +describe("Channel.runCount", () => { + it("returns the output count", () => { + expect(Channel.runCount(Channel.fromIterable([1, 2, 3]))).type.toBe>() + }) +}) diff --git a/repos/effect/packages/effect/typetest/ChannelSchema.tst.ts b/repos/effect/packages/effect/typetest/ChannelSchema.tst.ts new file mode 100644 index 0000000000..8946e1ab15 --- /dev/null +++ b/repos/effect/packages/effect/typetest/ChannelSchema.tst.ts @@ -0,0 +1,32 @@ +import { type Array, type Channel, ChannelSchema, Schema } from "effect" +import { expect, it } from "tstyche" + +it("decodeUnknown accepts unknown input chunks", () => { + const channel = ChannelSchema.decodeUnknown(Schema.NumberFromString)() + + expect(channel).type.toBe< + Channel.Channel< + Array.NonEmptyReadonlyArray, + Schema.SchemaError, + unknown, + Array.NonEmptyReadonlyArray, + never, + unknown + > + >() +}) + +it("decode preserves the schema encoded input type", () => { + const channel = ChannelSchema.decode(Schema.NumberFromString)() + + expect(channel).type.toBe< + Channel.Channel< + Array.NonEmptyReadonlyArray, + Schema.SchemaError, + unknown, + Array.NonEmptyReadonlyArray, + never, + unknown + > + >() +}) diff --git a/repos/effect/packages/effect/typetest/Clock.tst.ts b/repos/effect/packages/effect/typetest/Clock.tst.ts new file mode 100644 index 0000000000..beb01dc800 --- /dev/null +++ b/repos/effect/packages/effect/typetest/Clock.tst.ts @@ -0,0 +1,13 @@ +import type { Effect } from "effect" +import { Clock } from "effect" +import { describe, expect, it } from "tstyche" + +describe("Clock", () => { + it("exposes monotonic nanosecond time", () => { + expect(Clock.monotonicTimeNanos).type.toBe>() + + const clock = null as unknown as Clock.Clock + expect(clock.monotonicTimeNanosUnsafe()).type.toBe() + expect(clock.monotonicTimeNanos).type.toBe>() + }) +}) diff --git a/repos/effect/packages/effect/typetest/Config.tst.ts b/repos/effect/packages/effect/typetest/Config.tst.ts index aa760eafa3..b3ccab9abb 100644 --- a/repos/effect/packages/effect/typetest/Config.tst.ts +++ b/repos/effect/packages/effect/typetest/Config.tst.ts @@ -1,4 +1,4 @@ -import { Config, Schema } from "effect" +import { Config, ConfigProvider, Schema } from "effect" import { describe, expect, it } from "tstyche" describe("Config", () => { @@ -40,4 +40,13 @@ describe("Config", () => { expect(c).type.toBe>>() }) + + it("parse", () => { + const config = Config.string("a") + const provider = ConfigProvider.fromUnknown({ a: "value" }) + + config.parse(provider) + // @ts-expect-error Expected 1 arguments, but got 2. + config.parse(provider, ["prefix"]) + }) }) diff --git a/repos/effect/packages/effect/typetest/ConfigProvider.tst.ts b/repos/effect/packages/effect/typetest/ConfigProvider.tst.ts new file mode 100644 index 0000000000..5a01754415 --- /dev/null +++ b/repos/effect/packages/effect/typetest/ConfigProvider.tst.ts @@ -0,0 +1,20 @@ +import { ConfigProvider, Effect } from "effect" +import { describe, expect, it } from "tstyche" + +declare const process: { readonly env: Record } + +describe("ConfigProvider", () => { + it("exposes lookup absence and input transformation", () => { + const provider = ConfigProvider.make((_path) => Effect.succeed(ConfigProvider.makeValue("value"))) + + expect(provider.load([])) + .type.toBe>() + expect(provider.mapInput((path) => path)) + .type.toBe() + }) + + it("accepts process.env as an explicit environment record", () => { + expect(ConfigProvider.fromEnvRecord(process.env)) + .type.toBe() + }) +}) diff --git a/repos/effect/packages/effect/typetest/Context.tst.ts b/repos/effect/packages/effect/typetest/Context.tst.ts new file mode 100644 index 0000000000..0c1c89cc3f --- /dev/null +++ b/repos/effect/packages/effect/typetest/Context.tst.ts @@ -0,0 +1,11 @@ +import { Context, Option } from "effect" +import { expect, it } from "tstyche" + +it("does not type a service removed with addOrOmit as present", () => { + const Service = Context.Service<{ readonly value: number }>("TestService") + const context = Context.make(Service, { value: 1 }).pipe(Context.addOrOmit(Service, Option.none())) + const dataFirst = Context.addOrOmit(Context.make(Service, { value: 1 }), Service, Option.none()) + + expect(Context.get).type.not.toBeCallableWith(context, Service) + expect(Context.get).type.not.toBeCallableWith(dataFirst, Service) +}) diff --git a/repos/effect/packages/effect/typetest/Effect.tst.ts b/repos/effect/packages/effect/typetest/Effect.tst.ts index abdad17194..86de6e1aa7 100644 --- a/repos/effect/packages/effect/typetest/Effect.tst.ts +++ b/repos/effect/packages/effect/typetest/Effect.tst.ts @@ -5,11 +5,13 @@ import { Context, Data, Effect, + type ExecutionPlan, Fiber, type Layer, type Option, pipe, Result, + type Schedule, type Scope, type Sink, type Stream, @@ -74,6 +76,18 @@ class AcquireReleaseDependency extends Context.Service()( + "UpdateServiceScopedService" +) {} + +const UpdateServiceScopedReference = Context.Reference("UpdateServiceScopedReference", { + defaultValue: () => 0 +}) + +class ProvideServiceEffectServiceLiteral extends Context.Service()( + "ProvideServiceEffectServiceLiteral" +) {} + describe("Types", () => { describe("ReasonOf", () => { it("extracts reason type", () => { @@ -600,6 +614,34 @@ describe("Effect.annotateLogsScoped", () => { }) }) +describe("Effect.updateServiceScoped", () => { + it("adds a Context.Service to the requirements", () => { + const result = Effect.updateServiceScoped(UpdateServiceScopedService, (value) => value + 1) + expect(result).type.toBe>() + }) + + it("does not add a Context.Reference to the requirements", () => { + const result = Effect.updateServiceScoped(UpdateServiceScopedReference, (value) => value + 1) + expect(result).type.toBe>() + }) + + it("types the reset values from the service", () => { + const result = Effect.updateServiceScoped( + UpdateServiceScopedService, + (value) => value + 1, + { + reset: (original, updated, current) => { + expect(original).type.toBe() + expect(updated).type.toBe() + expect(current).type.toBe() + return current + } + } + ) + expect(result).type.toBe>() + }) +}) + describe("Effect.forkScoped", () => { it("adds Scope to requirements in data-first usage", () => { const result = pipe( @@ -793,6 +835,25 @@ describe("Effect.partition", () => { }) }) +describe("Effect.reduce", () => { + it("data-first", () => { + const result = Effect.reduce( + [1, 2, 3], + () => "", + (acc, n) => string.pipe(Effect.map((value) => `${acc}${value}${n}`)) + ) + expect(result).type.toBe>() + }) + + it("data-last", () => { + const result = pipe( + [1, 2, 3], + Effect.reduce(() => "", (acc, n) => string.pipe(Effect.map((value) => `${acc}${value}${n}`))) + ) + expect(result).type.toBe>() + }) +}) + describe("Effect.findFirst", () => { it("data-first", () => { const result = Effect.findFirst( @@ -1044,3 +1105,121 @@ describe("Effect.retry", () => { expect(result).type.toBe>() }) }) + +describe("Effect.schedule", () => { + it("includes schedule errors in data-first usage", () => { + const schedule = null as unknown as Schedule.Schedule + expect(Effect.schedule(Effect.fail("effect-error" as const), schedule)) + .type.toBe>() + }) + + it("includes schedule errors in data-last usage", () => { + const schedule = null as unknown as Schedule.Schedule + expect(Effect.fail("effect-error" as const).pipe(Effect.schedule(schedule))) + .type.toBe>() + }) +}) + +describe("Effect.scheduleFrom", () => { + it("includes schedule errors in data-first usage", () => { + const schedule = null as unknown as Schedule.Schedule + expect(Effect.scheduleFrom(Effect.fail("effect-error" as const), "initial", schedule)) + .type.toBe>() + }) + + it("includes schedule errors in data-last usage", () => { + const schedule = null as unknown as Schedule.Schedule + expect(Effect.fail("effect-error" as const).pipe(Effect.scheduleFrom("initial", schedule))) + .type.toBe>() + }) +}) + +describe("Effect.provideServiceEffect", () => { + it("data-first disallows supertype return", () => { + Effect.provideServiceEffect( + Effect.void, + ProvideServiceEffectServiceLiteral, + // @ts-expect-error Argument of type 'Effect' is not assignable to parameter of type 'Effect<"LITERAL", never, never>' + Effect.gen(function*() { + return "test" + }) + ) + }) + + it("data-last disallows supertype return", () => { + Effect.provideServiceEffect( + ProvideServiceEffectServiceLiteral, + // @ts-expect-error Argument of type 'Effect' is not assignable to parameter of type 'Effect<"LITERAL", never, never>' + Effect.gen(function*() { + return "test" + }) + ) + }) +}) + +describe("Effect.updateService", () => { + it("data-first disallows supertype return", () => { + Effect.updateService( + Effect.void, + ProvideServiceEffectServiceLiteral, + // @ts-expect-error Type 'string' is not assignable to type '"LITERAL"' + () => "test" + ) + }) + + it("data-last disallows supertype return", () => { + Effect.updateService( + ProvideServiceEffectServiceLiteral, + // @ts-expect-error Type 'string' is not assignable to type '"LITERAL"' + () => "test" + ) + }) +}) + +describe("Effect.updateServiceScoped", () => { + it("disallows supertype return", () => { + Effect.updateServiceScoped( + ProvideServiceEffectServiceLiteral, + // @ts-expect-error Type 'string' is not assignable to type '"LITERAL"' + () => "test" + ) + }) +}) + +describe("Effect.withExecutionPlan", () => { + const plan = null as unknown as ExecutionPlan.ExecutionPlan<{ + provides: "provided" + input: string + error: "plan-error" + requirements: "plan-dep" + }> + const self = null as unknown as Effect.Effect + + it("data-first adds handler requirements to R", () => { + const result = Effect.withExecutionPlan(self, plan, { + onEvent: (event) => { + expect(event).type.toBe>() + return null as unknown as Effect.Effect + } + }) + expect(result).type.toBe>() + }) + + it("data-last adds handler requirements to R", () => { + const result = pipe( + self, + Effect.withExecutionPlan(plan, { + onEvent: (event) => { + expect(event).type.toBe>() + return null as unknown as Effect.Effect + } + }) + ) + expect(result).type.toBe>() + }) + + it("without options the requirements are unchanged", () => { + const result = Effect.withExecutionPlan(self, plan) + expect(result).type.toBe>() + }) +}) diff --git a/repos/effect/packages/effect/typetest/Fiber.tst.ts b/repos/effect/packages/effect/typetest/Fiber.tst.ts new file mode 100644 index 0000000000..ffbbe17845 --- /dev/null +++ b/repos/effect/packages/effect/typetest/Fiber.tst.ts @@ -0,0 +1,13 @@ +import type { Effect } from "effect" +import { Fiber } from "effect" +import { expect, it } from "tstyche" + +it("joinAll preserves input fiber errors", () => { + const fibers = null as unknown as readonly [ + Fiber.Fiber, + Fiber.Fiber + ] + const result = Fiber.joinAll(fibers) + + expect>().type.toBe<"err-1" | "err-2">() +}) diff --git a/repos/effect/packages/effect/typetest/Function.tst.ts b/repos/effect/packages/effect/typetest/Function.tst.ts new file mode 100644 index 0000000000..a7aa422308 --- /dev/null +++ b/repos/effect/packages/effect/typetest/Function.tst.ts @@ -0,0 +1,26 @@ +import { Function } from "effect" +import { describe, expect, it } from "tstyche" + +describe("Function", () => { + it("memoize", () => { + const memoized = Function.memoize((input: { readonly n: number }) => input.n) + expect(memoized).type.toBe<(input: { readonly n: number }) => number>() + + const nullable = Function.memoize((_input: object) => null) + expect(nullable).type.toBe<(input: object) => null>() + + expect(Function.memoize).type.not.toBeCallableWith((_input: object): undefined => undefined) + expect(Function.memoize).type.not.toBeCallableWith((_input: object): number | undefined => undefined) + }) + + it("memoizeIdempotent", () => { + const memoized = Function.memoizeIdempotent((input: { readonly n: number }) => input) + expect(memoized).type.toBe<(input: { readonly n: number }) => { readonly n: number }>() + + const generic = Function.memoizeIdempotent((input: A): A => input) + type Generic = (input: A) => A + expect(generic).type.toBe() + + expect(Function.memoizeIdempotent).type.not.toBeCallableWith((_input: object): number => 1) + }) +}) diff --git a/repos/effect/packages/effect/typetest/Graph.tst.ts b/repos/effect/packages/effect/typetest/Graph.tst.ts new file mode 100644 index 0000000000..e6b58da6bf --- /dev/null +++ b/repos/effect/packages/effect/typetest/Graph.tst.ts @@ -0,0 +1,270 @@ +import { Graph, hole, type Option, pipe } from "effect" +import { describe, expect, it } from "tstyche" + +declare const directed: Graph.DirectedGraph +declare const undirected: Graph.UndirectedGraph +declare const mutableDirected: Graph.MutableDirectedGraph +declare const mutableUndirected: Graph.MutableUndirectedGraph +declare const unknownKind: Graph.Graph +declare const mixedKind: Graph.DirectedGraph | Graph.UndirectedGraph + +interface Node { + readonly id: string +} + +interface Animal { + readonly name: string +} + +interface Dog extends Animal { + readonly breed: string +} + +declare const directedNodes: Graph.DirectedGraph +declare const undirectedNodes: Graph.UndirectedGraph +declare const dogGraph: Graph.DirectedGraph +declare const animalGraph: Graph.DirectedGraph +declare const dogMutableGraph: Graph.MutableDirectedGraph +declare const animalMutableGraph: Graph.MutableDirectedGraph + +describe("Graph", () => { + it("make", () => { + expect( + Graph.make("directed")((mutable) => { + expect(mutable).type.toBe>() + }) + ).type.toBe>() + + expect( + Graph.make("undirected")((mutable) => { + expect(mutable).type.toBe>() + }) + ).type.toBe>() + + expect(Graph.make("directed")).type.not.toBeCallableWith(async () => {}) + expect(Graph.make("undirected")).type.not.toBeCallableWith(() => Promise.resolve()) + }) + + it("mutation callbacks must be synchronous", () => { + expect(Graph.directed).type.not.toBeCallableWith(async () => {}) + expect(Graph.undirected).type.not.toBeCallableWith(() => Promise.resolve()) + expect(Graph.mutate).type.not.toBeCallableWith(directed, async () => {}) + expect(Graph.mutate).type.not.toBeCallableWith(async () => {}) + + expect(Graph.mutate(directed, (mutable) => { + expect(mutable).type.toBe>() + })).type.toBe>() + expect(pipe( + directed, + Graph.mutate((mutable) => { + expect(mutable).type.toBe>() + }) + )).type.toBe>() + }) + + it("opaque interface", () => { + expect(Graph.nodes(directed)).type.toBe>() + expect(Graph.edges(directed)).type.toBe>() + + // @ts-expect-error Property 'nodes' does not exist on type 'DirectedGraph' + const _nodes: unknown = directed.nodes + // @ts-expect-error Property 'edges' does not exist on type 'DirectedGraph' + const _edges: unknown = directed.edges + // @ts-expect-error Property 'adjacency' does not exist on type 'DirectedGraph' + const _adjacency: unknown = directed.adjacency + // @ts-expect-error Property 'nodes' does not exist on type 'MutableGraph' + const _mutableNodes: unknown = Graph.beginMutation(directed).nodes + + expect(_nodes).type.toBe() + expect(_edges).type.toBe() + expect(_adjacency).type.toBe() + expect(_mutableNodes).type.toBe() + }) + + it("variance", () => { + expect(dogGraph).type.toBeAssignableTo>() + expect(animalGraph).type.not.toBeAssignableTo>() + expect(dogMutableGraph).type.not.toBeAssignableTo>() + expect(animalMutableGraph).type.not.toBeAssignableTo>() + }) + + it("standalone data-last getters", () => { + const getNode = Graph.getNode(0) + const getEdge = Graph.getEdge(0) + + expect(getNode(directed)).type.toBe>() + expect(getNode(undirected)).type.toBe>() + expect(getNode(Graph.beginMutation(directed))).type.toBe>() + expect(getNode(Graph.beginMutation(undirected))).type.toBe>() + + expect(getEdge(directed)).type.toBe>>() + expect(getEdge(undirected)).type.toBe>>() + expect(getEdge(Graph.beginMutation(directed))).type.toBe>>() + expect(getEdge(Graph.beginMutation(undirected))).type.toBe>>() + }) + + it("guards", () => { + const input = hole() + if (Graph.isGraph(input)) { + expect(input).type.toBe< + Graph.Graph | Graph.MutableGraph + >() + } + + const known = hole< + | Graph.UndirectedGraph + | Graph.MutableUndirectedGraph + | { readonly _tag: "other" } + >() + if (Graph.isGraph(known)) { + expect(known).type.toBe | Graph.MutableUndirectedGraph>() + } + + const mixed = hole< + | Graph.DirectedGraph + | Graph.UndirectedGraph + | Graph.MutableDirectedGraph + | Graph.MutableUndirectedGraph + | { readonly _tag: "other" } + >() + if (Graph.isGraph(mixed)) { + expect(mixed.type).type.toBe() + if (mixed.type === "undirected") { + expect(mixed.type).type.toBe<"undirected">() + } + } + }) + + it("compose", () => { + expect(Graph.compose(directedNodes, directedNodes, { + nodeIdentity: (node) => { + expect(node).type.toBe() + return node.id + }, + edgeIdentity: (edge) => { + expect(edge).type.toBe() + return edge + } + })).type.toBe>() + + expect(pipe( + undirectedNodes, + Graph.compose(undirectedNodes, { + nodeIdentity: (node) => { + expect(node).type.toBe() + return node.id + } + }) + )).type.toBe>() + }) + + it("intersection", () => { + expect(Graph.intersection(directedNodes, directedNodes)).type.toBe>() + + expect(pipe( + undirectedNodes, + Graph.intersection(undirectedNodes) + )).type.toBe>() + }) + + it("difference", () => { + expect(Graph.difference(directedNodes, directedNodes, { + nodeIdentity: (node) => { + expect(node).type.toBe() + return node.id + } + })).type.toBe>() + + expect(pipe( + undirectedNodes, + Graph.difference(undirectedNodes, { + nodeIdentity: (node) => { + expect(node).type.toBe() + return node.id + } + }) + )).type.toBe>() + }) + + it("symmetricDifference", () => { + expect(Graph.symmetricDifference(directedNodes, directedNodes, { + nodeIdentity: (node) => { + expect(node).type.toBe() + return node.id + } + })).type.toBe>() + + expect(pipe( + undirectedNodes, + Graph.symmetricDifference(undirectedNodes, { + nodeIdentity: (node) => { + expect(node).type.toBe() + return node.id + } + }) + )).type.toBe>() + }) + + it("complement", () => { + expect(Graph.complement(directed, (source, target) => { + expect(source).type.toBe() + expect(target).type.toBe() + return source.length + target.length + })).type.toBe>() + + expect(pipe( + undirected, + Graph.complement((source, target) => { + expect(source).type.toBe() + expect(target).type.toBe() + return source.length + target.length + }) + )).type.toBe>() + }) + + it("neighborhood", () => { + expect(Graph.neighborhood(directed, 0)).type.toBe>() + expect(Graph.neighborhood(directed, 0, { radius: 2, direction: "undirected" })).type.toBe< + Graph.DirectedGraph + >() + expect(pipe(undirected, Graph.neighborhood(0, { radius: 2, direction: "outgoing" }))).type.toBe< + Graph.UndirectedGraph + >() + }) + + it("undirected traversal", () => { + expect(Graph.dfs(directed, { direction: "undirected", radius: 1 })).type.toBe>() + expect(Graph.bfs(directed, { direction: "undirected", radius: 1 })).type.toBe>() + expect(Graph.dfsPostOrder(directed, { direction: "undirected", radius: 1 })).type.toBe>() + }) + + it("topo", () => { + expect(Graph.topo(directed)).type.toBe>() + expect(Graph.topo(directed, { initials: [0] })).type.toBe>() + expect(Graph.topo(mutableDirected)).type.toBe>() + + expect(pipe(directed, Graph.topo())).type.toBe>() + expect(pipe(directed, Graph.topo({ initials: [0] }))).type.toBe>() + expect(pipe(mutableDirected, Graph.topo())).type.toBe>() + + // @ts-expect-error! Topological sorting requires a directed graph + Graph.topo(undirected) + // @ts-expect-error! Topological sorting requires a directed graph + Graph.topo(mutableUndirected) + // @ts-expect-error! Topological sorting requires a directed graph + pipe(undirected, Graph.topo()) + // @ts-expect-error! Topological sorting requires a directed graph + pipe(mutableUndirected, Graph.topo({ initials: [0] })) + // @ts-expect-error! The graph kind must first be narrowed to directed + Graph.topo(unknownKind) + + if (mixedKind.type === "directed") { + expect(Graph.topo(mixedKind)).type.toBe>() + } + }) + + it("sum", () => { + expect(Graph.sum(directed, directed)).type.toBe>() + expect(pipe(undirected, Graph.sum(undirected))).type.toBe>() + }) +}) diff --git a/repos/effect/packages/effect/typetest/Optic.tst.ts b/repos/effect/packages/effect/typetest/Optic.tst.ts index af61413df1..10752fe776 100644 --- a/repos/effect/packages/effect/typetest/Optic.tst.ts +++ b/repos/effect/packages/effect/typetest/Optic.tst.ts @@ -1,8 +1,59 @@ -import { Optic, Schema } from "effect" -import type { Option, Result } from "effect" +import { Optic, type Option, Result, Schema, type SchemaIssue } from "effect" import { describe, expect, it } from "tstyche" describe("Optic", () => { + describe("compose", () => { + const iso = Optic.makeIso((n) => n, (n) => n) + const lens = Optic.makeLens((n) => n, (n) => n) + const prism = Optic.makePrism(Result.succeed, (n) => n) + const optional = Optic.makeOptional(Result.succeed, (n) => Result.succeed(n)) + + it("preserves the optic kind matrix", () => { + expect(iso.compose(iso)).type.toBe>() + expect(iso.compose(lens)).type.toBe>() + expect(iso.compose(prism)).type.toBe>() + expect(iso.compose(optional)).type.toBe>() + + expect(lens.compose(iso)).type.toBe>() + expect(lens.compose(lens)).type.toBe>() + expect(lens.compose(prism)).type.toBe>() + expect(lens.compose(optional)).type.toBe>() + + expect(prism.compose(iso)).type.toBe>() + expect(prism.compose(lens)).type.toBe>() + expect(prism.compose(prism)).type.toBe>() + expect(prism.compose(optional)).type.toBe>() + + expect(optional.compose(iso)).type.toBe>() + expect(optional.compose(lens)).type.toBe>() + expect(optional.compose(prism)).type.toBe>() + expect(optional.compose(optional)).type.toBe>() + }) + + it("does not expose internal nodes", () => { + expect(iso).type.not.toHaveProperty("node") + expect(lens).type.not.toHaveProperty("node") + expect(prism).type.not.toHaveProperty("node") + expect(optional).type.not.toHaveProperty("node") + }) + + it("uses structured issues for failures", () => { + expect(prism.getResult).type.toBe<(source: number) => Result.Result>() + expect(optional.getResult).type.toBe<(source: number) => Result.Result>() + expect(optional.replaceResult) + .type.toBe<(value: number, source: number) => Result.Result>() + + expect(Optic.makePrism).type.not.toBeCallableWith( + (_: number) => Result.fail("failure"), + (n: number) => n + ) + expect(Optic.makeOptional).type.not.toBeCallableWith( + (_: number) => Result.fail("failure"), + (n: number) => Result.succeed(n) + ) + }) + }) + describe("key", () => { it("should not be allowed on union types", () => { type S = { readonly _tag: "A"; readonly a?: string } | { readonly _tag: "B"; readonly a?: number } @@ -94,9 +145,17 @@ describe("Optic", () => { }) }) - it("notUndefined", () => { - const optic = Optic.id().notUndefined() - expect(optic).type.toBe>() + describe("notUndefined", () => { + it("Prism", () => { + const optic = Optic.id().notUndefined() + expect(optic).type.toBe>() + }) + + it("Optional", () => { + type S = Record + const optic = Optic.id().at("a").notUndefined() + expect(optic).type.toBe>() + }) }) it("fromChecks", () => { diff --git a/repos/effect/packages/effect/typetest/Schedule.tst.ts b/repos/effect/packages/effect/typetest/Schedule.tst.ts index 816201f58b..bbbebf2547 100644 --- a/repos/effect/packages/effect/typetest/Schedule.tst.ts +++ b/repos/effect/packages/effect/typetest/Schedule.tst.ts @@ -1,4 +1,5 @@ import { type Effect, hole } from "effect" +import type * as Duration from "effect/Duration" import * as Schedule from "effect/Schedule" import { describe, expect, it } from "tstyche" @@ -18,4 +19,58 @@ describe("Schedule", () => { }) expect(schedule).type.toBe>() }) + + it("modifyDelay", () => { + const self = hole>() + const schedule = Schedule.modifyDelay(self, (metadata) => { + expect(metadata).type.toBe>() + return hole>() + }) + expect(schedule).type.toBe< + Schedule.Schedule + >() + }) + + it("addDelay", () => { + const self = hole>() + const schedule = Schedule.addDelay(self, (metadata) => { + expect(metadata).type.toBe>() + return hole>() + }) + expect(schedule).type.toBe< + Schedule.Schedule + >() + }) + + it("type extractors", () => { + type TestSchedule = Schedule.Schedule + expect>().type.toBe() + expect>().type.toBe() + expect>().type.toBe<"error">() + expect>().type.toBe<"service">() + }) + + it("max", () => { + const first = hole>() + const second = hole>() + const schedule = Schedule.max([first, second]) + expect(schedule).type.toBe< + Schedule.Schedule< + Duration.Duration, + { readonly first: string } & { readonly second: number }, + "firstError" | "secondError", + "firstService" | "secondService" + > + >() + }) + + it("upTo", () => { + const self = hole>() + + expect(Schedule.upTo(self, { times: 3 })).type.toBe>() + expect(Schedule.upTo({ duration: "1 second" })(self)).type.toBe< + Schedule.Schedule + >() + expect(Schedule.upTo({})(self)).type.toBe>() + }) }) diff --git a/repos/effect/packages/effect/typetest/Sink.tst.ts b/repos/effect/packages/effect/typetest/Sink.tst.ts new file mode 100644 index 0000000000..4ed4dd6b32 --- /dev/null +++ b/repos/effect/packages/effect/typetest/Sink.tst.ts @@ -0,0 +1,13 @@ +import { Effect, Sink } from "effect" +import { describe, expect, it } from "tstyche" + +describe("Sink", () => { + it("curried catch replaces the handled error type", () => { + const sink = null as unknown as Sink.Sink + const recovered = Sink.catch<"old-error", never, "new-error", never>( + (_) => Effect.fail("new-error" as const) + )(sink) + + expect(recovered).type.toBe>() + }) +}) diff --git a/repos/effect/packages/effect/typetest/Stream.tst.ts b/repos/effect/packages/effect/typetest/Stream.tst.ts index 13454bb51d..ebabd421a2 100644 --- a/repos/effect/packages/effect/typetest/Stream.tst.ts +++ b/repos/effect/packages/effect/typetest/Stream.tst.ts @@ -1,4 +1,4 @@ -import { type Cause, Data, type Effect, pipe, type Queue, Result, type Scope, Stream } from "effect" +import { type Cause, Data, type Effect, type ExecutionPlan, pipe, type Queue, Result, type Scope, Stream } from "effect" import { describe, expect, it } from "tstyche" class ErrorA extends Data.TaggedError("ErrorA")<{ @@ -195,3 +195,41 @@ describe("Stream.toQueue", () => { >() }) }) + +describe("Stream.withExecutionPlan", () => { + const plan = null as unknown as ExecutionPlan.ExecutionPlan<{ + provides: "provided" + input: string + error: "plan-error" + requirements: "plan-dep" + }> + const self = null as unknown as Stream.Stream + + it("data-first adds handler requirements to R", () => { + const result = Stream.withExecutionPlan(self, plan, { + onEvent: (event) => { + expect(event).type.toBe>() + return null as unknown as Effect.Effect + } + }) + expect(result).type.toBe>() + }) + + it("data-last adds handler requirements to R", () => { + const result = pipe( + self, + Stream.withExecutionPlan(plan, { + onEvent: (event) => { + expect(event).type.toBe>() + return null as unknown as Effect.Effect + } + }) + ) + expect(result).type.toBe>() + }) + + it("without options the requirements are unchanged", () => { + const result = Stream.withExecutionPlan(self, plan) + expect(result).type.toBe>() + }) +}) diff --git a/repos/effect/packages/effect/typetest/TestSchema.tst.ts b/repos/effect/packages/effect/typetest/TestSchema.tst.ts new file mode 100644 index 0000000000..4ed7ccc1d7 --- /dev/null +++ b/repos/effect/packages/effect/typetest/TestSchema.tst.ts @@ -0,0 +1,12 @@ +import type { Effect, SchemaIssue } from "effect" +import { Schema } from "effect" +import { TestSchema } from "effect/testing" +import { describe, expect, it } from "tstyche" + +describe("TestSchema", () => { + it("types Encoding.encodeUnknownEffect with the encoded output", () => { + const encoding = new TestSchema.Asserts(Schema.NumberFromString).encoding() + + expect(encoding.encodeUnknownEffect(1)).type.toBe>() + }) +}) diff --git a/repos/effect/packages/effect/typetest/Tuple.tst.ts b/repos/effect/packages/effect/typetest/Tuple.tst.ts index 75bd47260b..4c39227600 100644 --- a/repos/effect/packages/effect/typetest/Tuple.tst.ts +++ b/repos/effect/packages/effect/typetest/Tuple.tst.ts @@ -7,6 +7,10 @@ const tuple = ["a", 2, true] as [string, number, boolean] const optionalTuple = ["a", 2, true] as [string?, number?, boolean?] describe("Tuple", () => { + it("make preserves argument literals", () => { + expect(Tuple.make("a", 1, true)).type.toBe<["a", 1, true]>() + }) + describe("get", () => { it("errors", () => { pipe( @@ -54,6 +58,10 @@ describe("Tuple", () => { it("data-last", () => { expect(pipe(tuple, Tuple.pick([0, 2]))).type.toBe<[string, boolean]>() }) + + it("preserves index order and duplicates", () => { + expect(Tuple.pick(tuple, [2, 0, 2])).type.toBe<[boolean, string, boolean]>() + }) }) describe("omit", () => { diff --git a/repos/effect/packages/effect/typetest/VariantSchema.tst.ts b/repos/effect/packages/effect/typetest/VariantSchema.tst.ts index 6f3d8db35f..5d0233c257 100644 --- a/repos/effect/packages/effect/typetest/VariantSchema.tst.ts +++ b/repos/effect/packages/effect/typetest/VariantSchema.tst.ts @@ -26,9 +26,36 @@ describe("VariantSchema", () => { const second = Test.Struct({ value: Test.FieldOnly(["a", "b"])(Schema.Number) }) + const union = Test.Union([first, second]) expect(Test.Union).type.toBeCallableWith([first, second]) expect(Test.Union).type.not.toBeCallableWith(first, second) + expect>().type.toBe< + { readonly value: string } | { readonly value: number } + >() + }) + + it("Class preserves constructor and variant schema types", () => { + const Test = VariantSchema.make({ + variants: ["a", "b"], + defaultVariant: "a" + }) + class User extends Test.Class("User")({ + id: Test.FieldOnly(["a"])(Schema.Number), + name: Schema.String + }) {} + + expect(User).type.toBeConstructableWith({ id: 1, name: "Alice" }) + expect(User).type.not.toBeConstructableWith({ name: "Alice" }) + expect(User.make({ id: 1, name: "Alice" })).type.toBe() + expect>().type.toBe() + expect>().type.toBe< + { readonly id: number; readonly name: string } + >() + expect>().type.toBe< + { readonly id: number; readonly name: string } + >() + expect>().type.toBe<{ readonly name: string }>() }) }) diff --git a/repos/effect/packages/effect/typetest/schema/FromJsonSchema.tst.ts b/repos/effect/packages/effect/typetest/schema/FromJsonSchema.tst.ts new file mode 100644 index 0000000000..ccb7c5d2a6 --- /dev/null +++ b/repos/effect/packages/effect/typetest/schema/FromJsonSchema.tst.ts @@ -0,0 +1,49 @@ +import { type JsonSchema, type Schema, SchemaRepresentation } from "effect" +import { describe, expect, it } from "tstyche" + +describe("JSON Schema importer", () => { + it("exposes exact synchronous signatures", () => { + const fromDocument: ( + document: JsonSchema.Document<"draft-2020-12">, + options?: SchemaRepresentation.FromJsonSchemaOptions + ) => Schema.Top = SchemaRepresentation.fromJsonSchemaDocument + const fromMultiDocument: ( + document: JsonSchema.MultiDocument<"draft-2020-12">, + options?: SchemaRepresentation.FromJsonSchemaOptions + ) => readonly [Schema.Top, ...Array] = SchemaRepresentation.fromJsonSchemaMultiDocument + expect(fromDocument).type.toBe< + ( + document: JsonSchema.Document<"draft-2020-12">, + options?: SchemaRepresentation.FromJsonSchemaOptions + ) => Schema.Top + >() + expect(fromMultiDocument).type.toBe< + ( + document: JsonSchema.MultiDocument<"draft-2020-12">, + options?: SchemaRepresentation.FromJsonSchemaOptions + ) => readonly [Schema.Top, ...Array] + >() + }) + + it("keeps onEnter limited to JSON Schema nodes", () => { + const options: SchemaRepresentation.FromJsonSchemaOptions = { + onEnter: (schema) => ({ ...schema, description: "entered" }) + } + + expect(options.onEnter).type.toBe< + ((schema: JsonSchema.JsonSchema) => JsonSchema.JsonSchema) | undefined + >() + }) + + it("limits the pattern policy to the supported modes", () => { + const options: SchemaRepresentation.FromJsonSchemaOptions = { patterns: "error" } + + expect(options.patterns).type.toBe<"error" | "ignore" | "apply" | undefined>() + + const invalidOptions: SchemaRepresentation.FromJsonSchemaOptions = { + // @ts-expect-error Type '"safe"' is not assignable to type + patterns: "safe" + } + void invalidOptions + }) +}) diff --git a/repos/effect/packages/effect/typetest/schema/Schema.tst.ts b/repos/effect/packages/effect/typetest/schema/Schema.tst.ts index ff42b15ed5..5afd42204c 100644 --- a/repos/effect/packages/effect/typetest/schema/Schema.tst.ts +++ b/repos/effect/packages/effect/typetest/schema/Schema.tst.ts @@ -1,4 +1,3 @@ -import type { SchemaAST } from "effect" import { Brand, Context, @@ -7,7 +6,9 @@ import { Option, Predicate, Schema, + type SchemaAST, SchemaGetter, + type SchemaIssue, SchemaTransformation, Struct, Tuple @@ -19,13 +20,19 @@ type Make = (input: In, options?: Schema.MakeOptions | undefined) => Ou type MakeEffect = ( input: In, options?: Schema.MakeOptions | undefined -) => Effect.Effect +) => Effect.Effect const revealClass = , Inherited>( klass: Schema.Class ): Schema.Class => klass describe("Schema", () => { + it("RedactedFromValue", () => { + const schema = Schema.RedactedFromValue(Schema.String) + expect(schema).type.toBe>() + expect(schema.from).type.toBe() + }) + describe("variance", () => { it("Type", () => { const f1 = hole< @@ -332,16 +339,16 @@ describe("Schema", () => { }) }) - describe("ErrorClass", () => { + describe("Error", () => { it("make with void input", () => { - class E extends Schema.ErrorClass("E")({}) {} + class E extends Schema.Error("E")({}) {} expect(E.make).type.toBe>() }) }) - describe("TaggedErrorClass", () => { + describe("TaggedError", () => { it("make with void input", () => { - class E extends Schema.TaggedErrorClass()("E", {}) {} + class E extends Schema.TaggedError()("E", {}) {} expect(E.make).type.toBe>() }) }) @@ -1435,7 +1442,7 @@ describe("Schema", () => { describe("Error", () => { it("extend Fields", () => { - class E extends Schema.ErrorClass("E")({ + class E extends Schema.Error("E")({ a: Schema.String }) {} @@ -1449,7 +1456,7 @@ describe("Schema", () => { }) it("extend Struct", () => { - class E extends Schema.ErrorClass("E")(Schema.Struct({ + class E extends Schema.Error("E")(Schema.Struct({ a: Schema.String })) {} @@ -1463,7 +1470,7 @@ describe("Schema", () => { }) it("should reject non existing props", () => { - class E extends Schema.ErrorClass("E")({ + class E extends Schema.Error("E")({ a: Schema.String }) {} @@ -1472,7 +1479,7 @@ describe("Schema", () => { }) it("mutable field", () => { - class E extends Schema.ErrorClass("E")({ + class E extends Schema.Error("E")({ a: Schema.String.pipe(Schema.mutableKey) }) {} @@ -1500,15 +1507,15 @@ describe("Schema", () => { ) }) - it("ErrorClass", () => { - expect(Schema.ErrorClass("A")({})).type.toBe( - "Missing `Self` generic - use `class Self extends Schema.ErrorClass(...)`" + it("Error", () => { + expect(Schema.Error("A")({})).type.toBe( + "Missing `Self` generic - use `class Self extends Schema.Error(...)`" ) }) - it("TaggedErrorClass", () => { - expect(Schema.TaggedErrorClass("A")("A", {})).type.toBe( - "Missing `Self` generic - use `class Self extends Schema.TaggedErrorClass(...)`" + it("TaggedError", () => { + expect(Schema.TaggedError("A")("A", {})).type.toBe( + "Missing `Self` generic - use `class Self extends Schema.TaggedError(...)`" ) }) }) @@ -1846,26 +1853,115 @@ describe("Schema", () => { }) }) - describe("asClass", () => { - it("preserves schema Type", () => { - class A extends Schema.asClass(Schema.String) {} - expect(Schema.revealCodec(A)).type.toBe>() + describe("class extension", () => { + it("keeps protocol bases constructor-free", () => { + const bottomWithoutNew: Schema.BottomWithoutNew< + string, + string, + never, + never, + (typeof Schema.String)["ast"], + Schema.String + > = Schema.String + expect(bottomWithoutNew).type.not.toBeAssignableTo< + abstract new(...args: Array) => unknown + >() + + const struct = Schema.Struct({ name: Schema.String }) + const bottomLazyWithoutNew: Schema.BottomLazyWithoutNew< + (typeof struct)["ast"], + (typeof struct)["Rebuild"] + > = struct + expect(bottomLazyWithoutNew).type.not.toBeAssignableTo< + abstract new(...args: Array) => unknown + >() + }) + + it("keeps class-compatible bases extendable", () => { + const bottom: Schema.Bottom< + string, + string, + never, + never, + (typeof Schema.String)["ast"], + Schema.String + > = Schema.String + class A extends bottom {} + + const struct = Schema.Struct({ name: Schema.String }) + const bottomLazy: Schema.BottomLazy< + (typeof struct)["ast"], + (typeof struct)["Rebuild"] + > = struct + class B extends bottomLazy {} + + expect(Schema.revealCodec(A)).type.toBe>() + expect(B).type.toBeAssignableTo() + }) + + it("keeps Opaque assignable to Top", () => { + class A extends Schema.Opaque()(Schema.Struct({ name: Schema.String })) {} + + expect(A).type.toBeAssignableTo() + }) + + it("keeps Schema.Class assignable to Top", () => { + class A extends Schema.Class("A")({ name: Schema.String }) {} - class B extends Schema.asClass(Schema.Struct({ name: Schema.String })) {} - expect(Schema.revealCodec(B)).type.toBe< - Schema.Codec<{ readonly name: string }, { readonly name: string }, never, never> + expect(A).type.toBeAssignableTo() + }) + + it("preserves codec parameters", () => { + interface DecodingService { + readonly DecodingService: unique symbol + } + interface EncodingService { + readonly EncodingService: unique symbol + } + + const schema = Schema.FiniteFromString.pipe( + Schema.middlewareDecoding((effect) => + Effect.andThen( + Effect.context(), + effect + ) + ), + Schema.middlewareEncoding((effect) => + Effect.andThen( + Effect.context(), + effect + ) + ) + ) + + class A extends schema {} + + expect(Schema.revealCodec(A)).type.toBe< + Schema.Codec >() + }) + + it("preserves Struct fields", () => { + class B extends Schema.Struct({ name: Schema.String }) {} + expect(B.fields).type.toBe<{ readonly name: Schema.String }>() }) + it("cannot be constructed", () => { + class A extends Schema.String {} + + expect(A).type.not.toBeConstructableWith() + expect(A).type.not.toBeConstructableWith("a") + }) + it("annotate returns the original schema type", () => { - class A extends Schema.asClass(Schema.String) {} + class A extends Schema.String {} expect(A.annotate({})).type.toBe() }) it("should support static methods", () => { - class A extends Schema.asClass(Schema.FiniteFromString) { + class A extends Schema.FiniteFromString { static readonly decodeUnknownSync = Schema.decodeUnknownSync(this) static get encodeSync() { return Schema.encodeSync(this) diff --git a/repos/effect/packages/effect/typetest/schema/SchemaBuiltInAtomicDeclarationRevivers.tst.ts b/repos/effect/packages/effect/typetest/schema/SchemaBuiltInAtomicDeclarationRevivers.tst.ts new file mode 100644 index 0000000000..6eb183e48e --- /dev/null +++ b/repos/effect/packages/effect/typetest/schema/SchemaBuiltInAtomicDeclarationRevivers.tst.ts @@ -0,0 +1,21 @@ +import { Schema, type SchemaRepresentation } from "effect" +import { describe, expect, it } from "tstyche" + +describe("Schema built-in atomic declaration revivers", () => { + it("composes every atomic declaration reviver without casts", () => { + const revivers: ReadonlyArray = [ + Schema.DateReviver, + Schema.FileReviver, + Schema.FormDataReviver, + Schema.RegExpReviver, + Schema.Uint8ArrayReviver, + Schema.URLReviver, + Schema.URLSearchParamsReviver + ] + + expect(revivers).type.toBe>() + expect(Schema.DateReviver).type.toBe>() + expect(Schema.FileReviver).type.toBe>() + expect(Schema.FormDataReviver).type.toBe>() + }) +}) diff --git a/repos/effect/packages/effect/typetest/schema/SchemaBuiltInBigDecimalDurationChunkDeclarationRevivers.tst.ts b/repos/effect/packages/effect/typetest/schema/SchemaBuiltInBigDecimalDurationChunkDeclarationRevivers.tst.ts new file mode 100644 index 0000000000..3d3972be6a --- /dev/null +++ b/repos/effect/packages/effect/typetest/schema/SchemaBuiltInBigDecimalDurationChunkDeclarationRevivers.tst.ts @@ -0,0 +1,17 @@ +import { Schema, type SchemaRepresentation } from "effect" +import { describe, expect, it } from "tstyche" + +describe("Schema built-in BigDecimal, Duration and Chunk declaration revivers", () => { + it("composes every declaration reviver without casts", () => { + const revivers: ReadonlyArray = [ + Schema.BigDecimalReviver, + Schema.DurationReviver, + Schema.ChunkReviver + ] + + expect(revivers).type.toBe>() + expect(Schema.BigDecimalReviver).type.toBe>() + expect(Schema.DurationReviver).type.toBe>() + expect(Schema.ChunkReviver).type.toBe>() + }) +}) diff --git a/repos/effect/packages/effect/typetest/schema/SchemaBuiltInBigIntRevivers.tst.ts b/repos/effect/packages/effect/typetest/schema/SchemaBuiltInBigIntRevivers.tst.ts new file mode 100644 index 0000000000..dd3c0af3fd --- /dev/null +++ b/repos/effect/packages/effect/typetest/schema/SchemaBuiltInBigIntRevivers.tst.ts @@ -0,0 +1,27 @@ +import { Schema, type SchemaRepresentation } from "effect" +import { describe, expect, it } from "tstyche" + +describe("Schema built-in BigInt revivers", () => { + it("composes every BigInt check reviver without casts", () => { + const revivers: ReadonlyArray = [ + Schema.isGreaterThanBigIntReviver, + Schema.isGreaterThanOrEqualToBigIntReviver, + Schema.isLessThanBigIntReviver, + Schema.isLessThanOrEqualToBigIntReviver, + Schema.isBetweenBigIntReviver + ] + + expect(revivers).type.toBe>() + expect(Schema.isGreaterThanBigIntReviver).type.toBe< + SchemaRepresentation.FilterReviver<{ readonly exclusiveMinimum: bigint }> + >() + expect(Schema.isBetweenBigIntReviver).type.toBe< + SchemaRepresentation.FilterReviver<{ + readonly minimum: bigint + readonly maximum: bigint + readonly exclusiveMinimum?: true | undefined + readonly exclusiveMaximum?: true | undefined + }> + >() + }) +}) diff --git a/repos/effect/packages/effect/typetest/schema/SchemaBuiltInCauseAndExitDeclarationRevivers.tst.ts b/repos/effect/packages/effect/typetest/schema/SchemaBuiltInCauseAndExitDeclarationRevivers.tst.ts new file mode 100644 index 0000000000..f9ee5e1c52 --- /dev/null +++ b/repos/effect/packages/effect/typetest/schema/SchemaBuiltInCauseAndExitDeclarationRevivers.tst.ts @@ -0,0 +1,17 @@ +import { Schema, type SchemaRepresentation } from "effect" +import { describe, expect, it } from "tstyche" + +describe("Schema built-in Cause and Exit declaration revivers", () => { + it("exposes exact null payload reviver types", () => { + const revivers: ReadonlyArray = [ + Schema.CauseReasonReviver, + Schema.CauseReviver, + Schema.ExitReviver + ] + + expect(revivers).type.toBe>() + expect(Schema.CauseReasonReviver).type.toBe>() + expect(Schema.CauseReviver).type.toBe>() + expect(Schema.ExitReviver).type.toBe>() + }) +}) diff --git a/repos/effect/packages/effect/typetest/schema/SchemaBuiltInCollectionRevivers.tst.ts b/repos/effect/packages/effect/typetest/schema/SchemaBuiltInCollectionRevivers.tst.ts new file mode 100644 index 0000000000..00e5e35630 --- /dev/null +++ b/repos/effect/packages/effect/typetest/schema/SchemaBuiltInCollectionRevivers.tst.ts @@ -0,0 +1,25 @@ +import { Schema, type SchemaRepresentation } from "effect" +import { describe, expect, it } from "tstyche" + +describe("Schema built-in collection revivers", () => { + it("composes every collection check reviver without casts", () => { + const revivers: ReadonlyArray = [ + Schema.isMinSizeReviver, + Schema.isMaxSizeReviver, + Schema.isSizeBetweenReviver, + Schema.isUniqueReviver + ] + + expect(revivers).type.toBe>() + expect(Schema.isMinSizeReviver).type.toBe< + SchemaRepresentation.FilterReviver<{ readonly minSize: number }> + >() + expect(Schema.isSizeBetweenReviver).type.toBe< + SchemaRepresentation.FilterReviver<{ + readonly minimum: number + readonly maximum: number + }> + >() + expect(Schema.isUniqueReviver).type.toBe>() + }) +}) diff --git a/repos/effect/packages/effect/typetest/schema/SchemaBuiltInDateRevivers.tst.ts b/repos/effect/packages/effect/typetest/schema/SchemaBuiltInDateRevivers.tst.ts new file mode 100644 index 0000000000..7fc656de9d --- /dev/null +++ b/repos/effect/packages/effect/typetest/schema/SchemaBuiltInDateRevivers.tst.ts @@ -0,0 +1,27 @@ +import { Schema, type SchemaRepresentation } from "effect" +import { describe, expect, it } from "tstyche" + +describe("Schema built-in Date revivers", () => { + it("composes every Date check reviver without casts", () => { + const revivers: ReadonlyArray = [ + Schema.isGreaterThanDateReviver, + Schema.isGreaterThanOrEqualToDateReviver, + Schema.isLessThanDateReviver, + Schema.isLessThanOrEqualToDateReviver, + Schema.isBetweenDateReviver + ] + + expect(revivers).type.toBe>() + expect(Schema.isGreaterThanDateReviver).type.toBe< + SchemaRepresentation.FilterReviver<{ readonly exclusiveMinimum: globalThis.Date }> + >() + expect(Schema.isBetweenDateReviver).type.toBe< + SchemaRepresentation.FilterReviver<{ + readonly minimum: globalThis.Date + readonly maximum: globalThis.Date + readonly exclusiveMinimum?: true | undefined + readonly exclusiveMaximum?: true | undefined + }> + >() + }) +}) diff --git a/repos/effect/packages/effect/typetest/schema/SchemaBuiltInDateTimeDeclarationRevivers.tst.ts b/repos/effect/packages/effect/typetest/schema/SchemaBuiltInDateTimeDeclarationRevivers.tst.ts new file mode 100644 index 0000000000..a9d877f9ed --- /dev/null +++ b/repos/effect/packages/effect/typetest/schema/SchemaBuiltInDateTimeDeclarationRevivers.tst.ts @@ -0,0 +1,21 @@ +import { Schema, type SchemaRepresentation } from "effect" +import { describe, expect, it } from "tstyche" + +describe("Schema built-in DateTime declaration revivers", () => { + it("composes every DateTime declaration reviver without casts", () => { + const revivers: ReadonlyArray = [ + Schema.TimeZoneReviver, + Schema.TimeZoneNamedReviver, + Schema.TimeZoneOffsetReviver, + Schema.DateTimeUtcReviver, + Schema.DateTimeZonedReviver + ] + + expect(revivers).type.toBe>() + expect(Schema.TimeZoneReviver).type.toBe>() + expect(Schema.TimeZoneNamedReviver).type.toBe>() + expect(Schema.TimeZoneOffsetReviver).type.toBe>() + expect(Schema.DateTimeUtcReviver).type.toBe>() + expect(Schema.DateTimeZonedReviver).type.toBe>() + }) +}) diff --git a/repos/effect/packages/effect/typetest/schema/SchemaBuiltInErrorAndCollectionDeclarationRevivers.tst.ts b/repos/effect/packages/effect/typetest/schema/SchemaBuiltInErrorAndCollectionDeclarationRevivers.tst.ts new file mode 100644 index 0000000000..aea6028087 --- /dev/null +++ b/repos/effect/packages/effect/typetest/schema/SchemaBuiltInErrorAndCollectionDeclarationRevivers.tst.ts @@ -0,0 +1,25 @@ +import { Schema, type SchemaRepresentation } from "effect" +import { describe, expect, it } from "tstyche" + +describe("Schema built-in Error and collection declaration revivers", () => { + it("exposes exact payload and declaration reviver types", () => { + const revivers: ReadonlyArray = [ + Schema.ErrorInstanceReviver, + Schema.ReadonlyMapReviver, + Schema.ReadonlySetReviver + ] + + expect(revivers).type.toBe>() + expect(Schema.ErrorInstanceReviver).type.toBe< + SchemaRepresentation.DeclarationReviver< + | null + | { + readonly includeStack?: true | undefined + readonly excludeCause?: true | undefined + } + > + >() + expect(Schema.ReadonlyMapReviver).type.toBe>() + expect(Schema.ReadonlySetReviver).type.toBe>() + }) +}) diff --git a/repos/effect/packages/effect/typetest/schema/SchemaBuiltInJsonAndHashDeclarationRevivers.tst.ts b/repos/effect/packages/effect/typetest/schema/SchemaBuiltInJsonAndHashDeclarationRevivers.tst.ts new file mode 100644 index 0000000000..4afae11c8a --- /dev/null +++ b/repos/effect/packages/effect/typetest/schema/SchemaBuiltInJsonAndHashDeclarationRevivers.tst.ts @@ -0,0 +1,19 @@ +import { Schema, type SchemaRepresentation } from "effect" +import { describe, expect, it } from "tstyche" + +describe("Schema built-in JSON and hash collection declaration revivers", () => { + it("exposes exact null payload reviver types", () => { + const revivers: ReadonlyArray = [ + Schema.JsonReviver, + Schema.MutableJsonReviver, + Schema.HashMapReviver, + Schema.HashSetReviver + ] + + expect(revivers).type.toBe>() + expect(Schema.JsonReviver).type.toBe>() + expect(Schema.MutableJsonReviver).type.toBe>() + expect(Schema.HashMapReviver).type.toBe>() + expect(Schema.HashSetReviver).type.toBe>() + }) +}) diff --git a/repos/effect/packages/effect/typetest/schema/SchemaBuiltInNumberRevivers.tst.ts b/repos/effect/packages/effect/typetest/schema/SchemaBuiltInNumberRevivers.tst.ts new file mode 100644 index 0000000000..4b8c6ae2d3 --- /dev/null +++ b/repos/effect/packages/effect/typetest/schema/SchemaBuiltInNumberRevivers.tst.ts @@ -0,0 +1,33 @@ +import { Schema, type SchemaRepresentation } from "effect" +import { describe, expect, it } from "tstyche" + +describe("Schema built-in number revivers", () => { + it("composes every number check reviver without casts", () => { + const revivers: ReadonlyArray = [ + Schema.isFiniteReviver, + Schema.isIntReviver, + Schema.isMultipleOfReviver, + Schema.isGreaterThanReviver, + Schema.isGreaterThanOrEqualToReviver, + Schema.isLessThanReviver, + Schema.isLessThanOrEqualToReviver, + Schema.isBetweenReviver + ] + + expect(revivers).type.toBe>() + expect(Schema.isMultipleOfReviver).type.toBe< + SchemaRepresentation.FilterReviver<{ readonly divisor: number }> + >() + expect(Schema.isGreaterThanReviver).type.toBe< + SchemaRepresentation.FilterReviver<{ readonly exclusiveMinimum: number }> + >() + expect(Schema.isBetweenReviver).type.toBe< + SchemaRepresentation.FilterReviver<{ + readonly minimum: number + readonly maximum: number + readonly exclusiveMinimum?: true | undefined + readonly exclusiveMaximum?: true | undefined + }> + >() + }) +}) diff --git a/repos/effect/packages/effect/typetest/schema/SchemaBuiltInObjectRevivers.tst.ts b/repos/effect/packages/effect/typetest/schema/SchemaBuiltInObjectRevivers.tst.ts new file mode 100644 index 0000000000..917ff9f33d --- /dev/null +++ b/repos/effect/packages/effect/typetest/schema/SchemaBuiltInObjectRevivers.tst.ts @@ -0,0 +1,25 @@ +import { Schema, type SchemaRepresentation } from "effect" +import { describe, expect, it } from "tstyche" + +describe("Schema built-in object revivers", () => { + it("composes every object check reviver without casts", () => { + const revivers: ReadonlyArray = [ + Schema.isMinPropertiesReviver, + Schema.isMaxPropertiesReviver, + Schema.isPropertiesLengthBetweenReviver, + Schema.isPropertyNamesReviver + ] + + expect(revivers).type.toBe>() + expect(Schema.isMinPropertiesReviver).type.toBe< + SchemaRepresentation.FilterReviver<{ readonly minProperties: number }> + >() + expect(Schema.isPropertiesLengthBetweenReviver).type.toBe< + SchemaRepresentation.FilterReviver<{ + readonly minimum: number + readonly maximum: number + }> + >() + expect(Schema.isPropertyNamesReviver).type.toBe>() + }) +}) diff --git a/repos/effect/packages/effect/typetest/schema/SchemaBuiltInResultAndRedactedDeclarationRevivers.tst.ts b/repos/effect/packages/effect/typetest/schema/SchemaBuiltInResultAndRedactedDeclarationRevivers.tst.ts new file mode 100644 index 0000000000..02662ca51f --- /dev/null +++ b/repos/effect/packages/effect/typetest/schema/SchemaBuiltInResultAndRedactedDeclarationRevivers.tst.ts @@ -0,0 +1,23 @@ +import { Schema, type SchemaRepresentation } from "effect" +import { describe, expect, it } from "tstyche" + +describe("Schema built-in Result and Redacted declaration revivers", () => { + it("exposes exact payload and declaration reviver types", () => { + const revivers: ReadonlyArray = [ + Schema.ResultReviver, + Schema.RedactedReviver + ] + + expect(revivers).type.toBe>() + expect(Schema.ResultReviver).type.toBe>() + expect(Schema.RedactedReviver).type.toBe< + SchemaRepresentation.DeclarationReviver< + | null + | { + readonly label?: string | undefined + readonly disallowJsonEncode?: true | undefined + } + > + >() + }) +}) diff --git a/repos/effect/packages/effect/typetest/schema/SchemaBuiltInRevivers.tst.ts b/repos/effect/packages/effect/typetest/schema/SchemaBuiltInRevivers.tst.ts new file mode 100644 index 0000000000..cd10d73a52 --- /dev/null +++ b/repos/effect/packages/effect/typetest/schema/SchemaBuiltInRevivers.tst.ts @@ -0,0 +1,17 @@ +import { Schema, type SchemaRepresentation } from "effect" +import { describe, expect, it } from "tstyche" + +describe("Schema built-in revivers", () => { + it("exports the isPattern and Option revivers with concrete payload types", () => { + expect(Schema.isPatternReviver).type.toBe< + SchemaRepresentation.FilterReviver<{ readonly source: string; readonly flags: string }> + >() + expect(Schema.OptionReviver).type.toBe>() + + const revivers: ReadonlyArray = [ + Schema.isPatternReviver, + Schema.OptionReviver + ] + expect(revivers).type.toBe>() + }) +}) diff --git a/repos/effect/packages/effect/typetest/schema/SchemaBuiltInStringRevivers.tst.ts b/repos/effect/packages/effect/typetest/schema/SchemaBuiltInStringRevivers.tst.ts new file mode 100644 index 0000000000..768db4cd79 --- /dev/null +++ b/repos/effect/packages/effect/typetest/schema/SchemaBuiltInStringRevivers.tst.ts @@ -0,0 +1,42 @@ +import { Schema, type SchemaRepresentation } from "effect" +import { describe, expect, it } from "tstyche" + +describe("Schema built-in string revivers", () => { + it("composes every string check reviver without casts", () => { + const revivers: ReadonlyArray = [ + Schema.isStringFiniteReviver, + Schema.isStringBigIntReviver, + Schema.isStringSymbolReviver, + Schema.isMinLengthReviver, + Schema.isMaxLengthReviver, + Schema.isLengthBetweenReviver, + Schema.isPatternReviver, + Schema.isTrimmedReviver, + Schema.isUUIDReviver, + Schema.isGUIDReviver, + Schema.isULIDReviver, + Schema.isBase64Reviver, + Schema.isBase64UrlReviver, + Schema.isStartsWithReviver, + Schema.isEndsWithReviver, + Schema.isIncludesReviver, + Schema.isUppercasedReviver, + Schema.isLowercasedReviver, + Schema.isCapitalizedReviver, + Schema.isUncapitalizedReviver + ] + + expect(revivers).type.toBe>() + expect(Schema.isMinLengthReviver).type.toBe< + SchemaRepresentation.FilterReviver<{ readonly minLength: number }> + >() + expect(Schema.isLengthBetweenReviver).type.toBe< + SchemaRepresentation.FilterReviver<{ readonly minimum: number; readonly maximum: number }> + >() + expect(Schema.isUUIDReviver).type.toBe< + SchemaRepresentation.FilterReviver<{ + readonly version: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | null + }> + >() + }) +}) diff --git a/repos/effect/packages/effect/typetest/schema/SchemaJsonSchemaConsumer.tst.ts b/repos/effect/packages/effect/typetest/schema/SchemaJsonSchemaConsumer.tst.ts new file mode 100644 index 0000000000..fbf869cd13 --- /dev/null +++ b/repos/effect/packages/effect/typetest/schema/SchemaJsonSchemaConsumer.tst.ts @@ -0,0 +1,22 @@ +import { type JsonSchema, Schema, type SchemaRepresentation } from "effect" +import { describe, expect, it } from "tstyche" + +describe("Schema JSON Schema consumer", () => { + it("exposes exact synchronous high-level signatures", () => { + const toJsonSchema: ( + schema: Schema.Constraint, + options?: Schema.ToJsonSchemaOptions + ) => JsonSchema.Document<"draft-2020-12"> = Schema.toJsonSchemaDocument + + expect(Schema.toRepresentation(Schema.String)).type.toBe< + SchemaRepresentation.Document + >() + expect(Schema.toJsonSchemaDocument(Schema.String)).type.toBe>() + expect(toJsonSchema).type.toBe< + ( + schema: Schema.Constraint, + options?: Schema.ToJsonSchemaOptions + ) => JsonSchema.Document<"draft-2020-12"> + >() + }) +}) diff --git a/repos/effect/packages/effect/typetest/schema/SchemaRepresentation.tst.ts b/repos/effect/packages/effect/typetest/schema/SchemaRepresentation.tst.ts new file mode 100644 index 0000000000..6040e7abc7 --- /dev/null +++ b/repos/effect/packages/effect/typetest/schema/SchemaRepresentation.tst.ts @@ -0,0 +1,91 @@ +import { type Schema, type SchemaAST, SchemaRepresentation } from "effect" +import { describe, expect, it } from "tstyche" + +describe("SchemaRepresentation persisted wire", () => { + it("exposes exact construction signatures", () => { + expect(SchemaRepresentation.toRepresentation).type.toBe< + (ast: SchemaAST.AST) => SchemaRepresentation.Document + >() + expect(SchemaRepresentation.toRepresentations).type.toBe< + ( + asts: readonly [SchemaAST.AST, ...Array] + ) => SchemaRepresentation.MultiDocument + >() + }) + + it("keeps projection explicit for single and multi documents", () => { + expect(SchemaRepresentation.toJson).type.toBe< + (document: SchemaRepresentation.Document) => Schema.Json + >() + expect(SchemaRepresentation.toJsonMultiDocument).type.toBe< + (document: SchemaRepresentation.MultiDocument) => Schema.Json + >() + }) + + it("wraps a document as a multi-document", () => { + expect(SchemaRepresentation.toMultiDocument).type.toBe< + (document: SchemaRepresentation.Document) => SchemaRepresentation.MultiDocument + >() + }) + + it("constructs generated code", () => { + expect(SchemaRepresentation.makeCode).type.toBe< + (runtime: string, Type: string) => SchemaRepresentation.Code + >() + }) + + it("keeps representation metadata separate from annotations", () => { + const declaration = null as unknown as SchemaRepresentation.Declaration + const filter = null as unknown as SchemaRepresentation.Filter + const group = null as unknown as SchemaRepresentation.FilterGroup + + expect(declaration.representation).type.toBe< + SchemaRepresentation.RepresentationAnnotation | undefined + >() + expect(filter.representation).type.toBe< + SchemaRepresentation.CheckRepresentationAnnotation | undefined + >() + expect(group.representation).type.toBe< + SchemaRepresentation.CheckRepresentationAnnotation | undefined + >() + }) + + it("uses native literal values", () => { + const literal = null as unknown as SchemaRepresentation.Literal + expect(literal.literal).type.toBe() + + expect( + { + _tag: "Literal", + literal: "value", + checks: [] + } as const + ).type.toBeAssignableTo() + + expect( + { + _tag: "Literal", + literal: { type: "string", value: "value" }, + checks: [] + } as const + ).type.not.toBeAssignableTo() + }) + + it("uses native Enum values", () => { + const representation = null as unknown as SchemaRepresentation.Enum + expect(representation.enums).type.toBe>() + expect([["A", 1]] as const).type.toBeAssignableTo() + expect([["A", { type: "number", value: 1 }]] as const).type.not.toBeAssignableTo< + SchemaRepresentation.Enum["enums"] + >() + }) + + it("uses native property keys", () => { + const property = null as unknown as SchemaRepresentation.PropertySignature + expect(property.name).type.toBe() + expect(Symbol.for("key")).type.toBeAssignableTo() + expect({ type: "symbol", value: Symbol.for("key") } as const).type.not.toBeAssignableTo< + SchemaRepresentation.PropertySignature["name"] + >() + }) +}) diff --git a/repos/effect/packages/effect/typetest/schema/SchemaRepresentationCompilers.tst.ts b/repos/effect/packages/effect/typetest/schema/SchemaRepresentationCompilers.tst.ts new file mode 100644 index 0000000000..a30c66a247 --- /dev/null +++ b/repos/effect/packages/effect/typetest/schema/SchemaRepresentationCompilers.tst.ts @@ -0,0 +1,49 @@ +import { type JsonSchema, Schema, SchemaRepresentation } from "effect" +import { describe, expect, it } from "tstyche" + +describe("SchemaRepresentation compilers", () => { + it("exposes exact compiler signatures", () => { + expect(SchemaRepresentation.toJsonSchemaDocument).type.toBe< + ( + document: SchemaRepresentation.Document, + options?: Schema.ToJsonSchemaOptions + ) => JsonSchema.Document<"draft-2020-12"> + >() + expect(SchemaRepresentation.toJsonSchemaMultiDocument).type.toBe< + ( + document: SchemaRepresentation.MultiDocument, + options?: Schema.ToJsonSchemaOptions + ) => JsonSchema.MultiDocument<"draft-2020-12"> + >() + expect(SchemaRepresentation.toCodeDocument).type.toBe< + (document: SchemaRepresentation.MultiDocument) => SchemaRepresentation.CodeDocument + >() + }) + + it("distinguishes live compiler inputs and their outputs", () => { + const document = SchemaRepresentation.toRepresentation(Schema.String.ast) + const multiDocument = SchemaRepresentation.toRepresentations([Schema.String.ast]) + + expect(SchemaRepresentation.toJsonSchemaDocument(document)).type.toBe< + JsonSchema.Document<"draft-2020-12"> + >() + expect(SchemaRepresentation.toJsonSchemaMultiDocument(multiDocument)).type.toBe< + JsonSchema.MultiDocument<"draft-2020-12"> + >() + expect(SchemaRepresentation.toCodeDocument(multiDocument)).type.toBe< + SchemaRepresentation.CodeDocument + >() + }) + + it("exposes node code generation through toCode annotations", () => { + const declaration: Schema.Annotations.Declaration = { + toCode: () => ({ runtime: "Custom", Type: "unknown" }) + } + const filter: Schema.Annotations.Filter = { + toCode: () => ({ runtime: "Custom.check()" }) + } + + expect(declaration.toCode).type.toBe() + expect(filter.toCode).type.toBe() + }) +}) diff --git a/repos/effect/packages/effect/typetest/schema/SchemaRepresentationReviver.tst.ts b/repos/effect/packages/effect/typetest/schema/SchemaRepresentationReviver.tst.ts new file mode 100644 index 0000000000..39273f44e9 --- /dev/null +++ b/repos/effect/packages/effect/typetest/schema/SchemaRepresentationReviver.tst.ts @@ -0,0 +1,77 @@ +import { Schema, SchemaRepresentation } from "effect" +import { describe, expect, it } from "tstyche" + +describe("SchemaRepresentation revivers", () => { + it("infers payload types from reviver constructors", () => { + const declaration = SchemaRepresentation.makeDeclarationReviver( + "acme/schema/Box", + Schema.Struct({ label: Schema.String }), + ({ payload }) => { + expect(payload).type.toBe<{ readonly label: string }>() + return Schema.String + } + ) + expect(declaration).type.toBe>() + + const filter = SchemaRepresentation.makeFilterReviver( + "acme/schema/minLength", + Schema.Struct({ minimum: Schema.Number }), + ({ annotations, payload }) => { + expect(payload).type.toBe<{ readonly minimum: number }>() + return Schema.isMinLength(payload.minimum, annotations) + } + ) + expect(filter).type.toBe>() + + const filterGroup = SchemaRepresentation.makeFilterGroupReviver( + "acme/schema/nonEmpty", + Schema.Null, + ({ annotations, payload }) => { + expect(payload).type.toBe() + return Schema.makeFilterGroup([Schema.isMinLength(1)], annotations) + } + ) + expect(filterGroup).type.toBe>() + }) + + it("accepts concrete payload revivers at the erased collection boundary", () => { + const reviver: SchemaRepresentation.FilterReviver<{ readonly source: string }> = { + id: "acme/schema/isPattern", + payloadSchema: Schema.Struct({ source: Schema.String }), + revive: ({ payload, annotations }) => Schema.isPattern(new RegExp(payload.source), annotations) + } + const revivers: ReadonlyArray = [reviver] + + expect(revivers).type.toBe>() + }) + + it("separates JSON decoding from schema revival", () => { + expect(SchemaRepresentation.fromJson).type.toBe< + (input: Schema.Json) => SchemaRepresentation.Document + >() + expect(SchemaRepresentation.fromJsonMultiDocument).type.toBe< + (input: Schema.Json) => SchemaRepresentation.MultiDocument + >() + expect(SchemaRepresentation.fromRepresentation).type.toBe< + ( + document: SchemaRepresentation.Document, + options: { readonly revivers: ReadonlyArray } + ) => Schema.Top + >() + expect(SchemaRepresentation.fromRepresentations).type.toBe< + ( + document: SchemaRepresentation.MultiDocument, + options: { readonly revivers: ReadonlyArray } + ) => readonly [Schema.Top, ...Array] + >() + + const document = SchemaRepresentation.fromJson({ representation: { _tag: "String", checks: [] }, references: {} }) + // @ts-expect-error Expected 2 arguments, but got 1. + SchemaRepresentation.fromRepresentation(document) + // @ts-expect-error Expected 2 arguments, but got 1. + SchemaRepresentation.fromRepresentations({ + representations: [{ _tag: "String", checks: [] }], + references: {} + }) + }) +}) diff --git a/repos/effect/packages/effect/typetest/schema/Struct.tst.ts b/repos/effect/packages/effect/typetest/schema/Struct.tst.ts index 10c3790dbf..ea6d95390e 100644 --- a/repos/effect/packages/effect/typetest/schema/Struct.tst.ts +++ b/repos/effect/packages/effect/typetest/schema/Struct.tst.ts @@ -46,6 +46,31 @@ describe("Struct", () => { >() }) + it("simplifies readonly & required views", () => { + const schema = Schema.Union([ + Schema.Struct({ name: Schema.Literal("a"), a: Schema.String }), + Schema.Struct({ name: Schema.Literal("b"), b: Schema.Finite }) + ]) + + // @ts-expect-error Type '{ readonly name: "a"; readonly a: string; } | { readonly name: "b"; readonly b: number; }' + const type: never = null as unknown as typeof schema.Type + // @ts-expect-error Type '{ readonly name: "a"; readonly a: string; } | { readonly name: "b"; readonly b: number; }' + const encoded: never = null as unknown as typeof schema.Encoded + // @ts-expect-error Type '{ readonly name: "a"; readonly a: string; } | { readonly name: "b"; readonly b: number; }' + const iso: never = null as unknown as typeof schema.Iso + + void [type, encoded, iso] + }) + + it("simplifies readonly & required make input", () => { + const schema = Schema.Struct({ a: Schema.String, b: Schema.Number }) + + // @ts-expect-error Type '{ readonly a: string; readonly b: number; }' + const makeIn: never = null as unknown as Schema.Struct.MakeIn + + void makeIn + }) + it("readonly & optionalKey field", () => { const schema = Schema.Struct({ a: Schema.optionalKey(Schema.String) diff --git a/repos/effect/packages/effect/typetest/schema/Union.tst.ts b/repos/effect/packages/effect/typetest/schema/Union.tst.ts index 02bd9fc66b..aef23cb44b 100644 --- a/repos/effect/packages/effect/typetest/schema/Union.tst.ts +++ b/repos/effect/packages/effect/typetest/schema/Union.tst.ts @@ -121,6 +121,8 @@ describe("Union", () => { const schema = original.pipe(Schema.toTaggedUnion("_tag")) + expect(schema.discriminants).type.toBe() + expect(Schema.revealCodec(schema)).type.toBe< Schema.Codec< | { readonly _tag: "A"; readonly a: string } @@ -160,6 +162,25 @@ describe("Union", () => { >() } }) + + it("should flatten discriminants", () => { + const schema = Schema.Union([ + Schema.Struct({ event: Schema.Literal("A") }), + Schema.Union([ + Schema.Struct({ event: Schema.Literal("B") }), + Schema.Struct({ event: Schema.Literal("C") }) + ]) + ]).pipe(Schema.toTaggedUnion("event")) + + expect(schema.discriminants).type.toBe() + expect(Schema.Literals(schema.discriminants)).type.toBe>() + }) + + it("should support empty unions", () => { + const schema = Schema.Union([]).pipe(Schema.toTaggedUnion("event")) + + expect(schema.discriminants).type.toBe() + }) }) describe("match", () => { diff --git a/repos/effect/packages/effect/typetest/schema/toArbitrary.tst.ts b/repos/effect/packages/effect/typetest/schema/toArbitrary.tst.ts index afa0d0175f..6a5c0e7eb0 100644 --- a/repos/effect/packages/effect/typetest/schema/toArbitrary.tst.ts +++ b/repos/effect/packages/effect/typetest/schema/toArbitrary.tst.ts @@ -11,30 +11,13 @@ describe("toArbitrary", () => { const arbitrary = Schema.toArbitrary(schema) expect(arbitrary).type.toBe< - FastCheck.Arbitrary<{ + Schema.Arbitrary<{ readonly name: string readonly age: number }> >() }) - it("returns a report when requested", () => { - const schema = Schema.Struct({ - name: Schema.String, - age: Schema.Number - }) - const result = Schema.toArbitrary(schema, { report: true }) - - expect(result).type.toBe< - Schema.Annotations.ToArbitrary.WithReport< - FastCheck.Arbitrary<{ - readonly name: string - readonly age: number - }> - > - >() - }) - it("passes recursion metadata in the arbitrary context", () => { Schema.String.annotate({ toArbitrary: () => (fc, context) => { diff --git a/repos/effect/packages/effect/typetest/schema/toIso.tst.ts b/repos/effect/packages/effect/typetest/schema/toIso.tst.ts index 6dec45c8e2..86516741cc 100644 --- a/repos/effect/packages/effect/typetest/schema/toIso.tst.ts +++ b/repos/effect/packages/effect/typetest/schema/toIso.tst.ts @@ -1,9 +1,9 @@ import type { Brand, Cause, Exit, Optic, Option } from "effect" -import { Data, Schema, SchemaUtils } from "effect" +import { Schema } from "effect" import { describe, expect, it } from "tstyche" class Value extends Schema.Class("Value")({ - a: Schema.DateValid + a: Schema.Date }) {} describe("toIso", () => { @@ -239,19 +239,19 @@ it("Cause", () => { >() }) -it("Error", () => { - const schema = Schema.Error() +it("ErrorInstance", () => { + const schema = Schema.ErrorInstance() const optic = Schema.toIso(schema) expect(optic).type.toBe>() }) it("Exit", () => { - const schema = Schema.Exit(Value, Schema.Error(), Schema.Defect()) + const schema = Schema.Exit(Value, Schema.ErrorInstance(), Schema.Defect()) const optic = Schema.toIso(schema) expect(optic).type.toBe< - Optic.Iso, Schema.ExitIso> + Optic.Iso, Schema.ExitIso> >() }) @@ -263,20 +263,3 @@ it("ReadonlyMap", () => { Optic.Iso, ReadonlyArray> >() }) - -it("getNativeClassSchema", () => { - const Props = Schema.Struct({ - message: Schema.String - }) - class Err extends Data.Error { - constructor(props: typeof Props.Type) { - super(Props.make(props)) - } - } - const schema = SchemaUtils.getNativeClassSchema(Err, { encoding: Props }) - const optic = Schema.toIso(schema) - - expect(optic).type.toBe< - Optic.Iso - >() -}) diff --git a/repos/effect/packages/effect/typetest/unstable/ai/McpServer.tst.ts b/repos/effect/packages/effect/typetest/unstable/ai/McpServer.tst.ts new file mode 100644 index 0000000000..14a5630947 --- /dev/null +++ b/repos/effect/packages/effect/typetest/unstable/ai/McpServer.tst.ts @@ -0,0 +1,106 @@ +import type * as Cause from "effect/Cause" +import type * as Effect from "effect/Effect" +import type * as Layer from "effect/Layer" +import * as Schema from "effect/Schema" +import type * as Scope from "effect/Scope" +import { McpProtocol, McpSchema, McpServer } from "effect/unstable/ai" +import * as McpProtocolInternal from "effect/unstable/ai/internal/mcpProtocol" +import * as Rpc from "effect/unstable/rpc/Rpc" +import * as RpcGroup from "effect/unstable/rpc/RpcGroup" +import { describe, expect, it } from "tstyche" + +const serverOptions = { + name: "TestServer", + version: "1.0.0", + protocols: [McpProtocol.v2025_06_18] +} as const + +describe("McpServer", () => { + describe("protocol configuration", () => { + it("should accept a non-empty protocol declaration when constructing any server", () => { + expect(McpServer.run).type.toBeCallableWith(serverOptions) + expect(McpServer.layer).type.toBeCallableWith(serverOptions) + expect(McpServer.layerStdio).type.toBeCallableWith(serverOptions) + expect(McpServer.layerHttp).type.toBeCallableWith({ + ...serverOptions, + path: "/mcp", + allowedOrigins: ["https://mcp.example"] + }) + }) + + it("should require a protocol declaration when constructing a server", () => { + expect(McpServer.layer).type.not.toBeCallableWith({ + name: "TestServer", + version: "1.0.0" + }) + }) + + it("should reject an empty protocol declaration when constructing a server", () => { + expect(McpServer.layer).type.not.toBeCallableWith({ + name: "TestServer", + version: "1.0.0", + protocols: [] as const + }) + }) + + it("should reject version strings when protocol adapter values are required", () => { + expect(McpServer.layer).type.not.toBeCallableWith({ + name: "TestServer", + version: "1.0.0", + protocols: ["2025-06-18"] as const + }) + }) + + it("should require client notification RPCs to be included in the complete client RPC group", () => { + const Request = Rpc.make("request", { + payload: Schema.Struct({}), + success: Schema.Struct({}) + }) + const Notification = Rpc.make("notification", { + payload: Schema.Struct({}), + success: Schema.Struct({}) + }) + + expect(McpProtocolInternal.make).type.not.toBeCallableWith({ + protocolVersion: "test", + clientRpcs: RpcGroup.make(Request), + clientNotificationRpcs: RpcGroup.make(Notification), + serverRequestRpcs: RpcGroup.make(), + serverNotificationRpcs: RpcGroup.make() + }) + }) + + it("should expose the supported protocol adapter", () => { + expect<"v2025_06_18">().type.toBeAssignableTo() + expect().type.toBe<"2025-06-18">() + }) + + it("should expose invalid protocol declarations as typed constructor failures", () => { + const run = McpServer.run(serverOptions) + const layer = McpServer.layer(serverOptions) + + expect>().type.toBe() + expect>().type.toBe() + }) + }) + + describe("request context", () => { + it("should expose the selected protocol version when a handler reads its client", () => { + expect(McpSchema.McpServerClient.useSync((client) => client.protocolVersion)).type.toBe< + Effect.Effect + >() + }) + + it("should expose initialization data and the generated reverse RPC client", () => { + expect(McpSchema.McpServerClient.useSync((client) => client.initializePayload.capabilities)).type.toBe< + Effect.Effect + >() + expect(McpSchema.McpServerClient.useSync((client) => client.initializePayload.clientInfo)).type.toBe< + Effect.Effect + >() + expect(McpSchema.McpServerClient.use((client) => client.getClient)).type.toBeAssignableTo< + Effect.Effect + >() + }) + }) +}) diff --git a/repos/effect/packages/effect/typetest/unstable/cli/Command.tst.ts b/repos/effect/packages/effect/typetest/unstable/cli/Command.tst.ts index 30fd339231..a2f85052f7 100644 --- a/repos/effect/packages/effect/typetest/unstable/cli/Command.tst.ts +++ b/repos/effect/packages/effect/typetest/unstable/cli/Command.tst.ts @@ -1,8 +1,28 @@ -import { Effect } from "effect" +import { Context, Effect } from "effect" import { Argument, Command, Flag, GlobalFlag } from "effect/unstable/cli" import { describe, expect, it } from "tstyche" describe("Command", () => { + describe("withSubcommands", () => { + it("unions errors and requirements across multiple subcommands", () => { + class _ServiceA extends Context.Service<_ServiceA, string>()("ServiceA") {} + class _ServiceB extends Context.Service<_ServiceB, string>()("ServiceB") {} + class _ServiceC extends Context.Service<_ServiceC, string>()("ServiceC") {} + + const childA = Command.make("child-a", {}, () => Effect.void as Effect.Effect) + const childB = Command.make("child-b", {}, () => Effect.void as Effect.Effect) + const childC = Command.make("child-c", {}, () => Effect.void as Effect.Effect) + + const root = Command.make("root").pipe( + Command.withSubcommands([childA, childB, childC]) + ) + + expect(root).type.toBe< + Command.Command<"root", {}, {}, "err-a" | "err-b" | "err-c", _ServiceA | _ServiceB | _ServiceC> + >() + }) + }) + describe("withSharedFlags", () => { it("adds shared flags to command input and parent context", () => { const root = Command.make("root", { @@ -121,4 +141,27 @@ describe("Command", () => { expect(command).type.toBe>() }) }) + + describe("built-in global flags", () => { + it("strips built-in setting context from Command.make handlers", () => { + const command = Command.make("example", {}, () => + Effect.gen(function*() { + yield* GlobalFlag.LogLevel + })) + + expect(command).type.toBe>() + }) + + it("strips built-in setting context from Command.withHandler handlers", () => { + const command = Command.make("example").pipe( + Command.withHandler(() => + Effect.gen(function*() { + yield* GlobalFlag.LogLevel + }) + ) + ) + + expect(command).type.toBe>() + }) + }) }) diff --git a/repos/effect/packages/effect/typetest/unstable/http/HttpClient.tst.ts b/repos/effect/packages/effect/typetest/unstable/http/HttpClient.tst.ts index db872a2690..f35b662701 100644 --- a/repos/effect/packages/effect/typetest/unstable/http/HttpClient.tst.ts +++ b/repos/effect/packages/effect/typetest/unstable/http/HttpClient.tst.ts @@ -122,7 +122,12 @@ describe("HttpClient", () => { limiter, key: "test", limit: 1, - window: "1 minute" + window: "1 minute", + times: 2, + responseHeaders: { + limit: "x-vendor-limit", + retryAfter: "x-vendor-retry-after" + } } as const const dataLast = client.pipe(HttpClient.withRateLimiter(options)) diff --git a/repos/effect/packages/effect/typetest/unstable/http/HttpRouter.tst.ts b/repos/effect/packages/effect/typetest/unstable/http/HttpRouter.tst.ts new file mode 100644 index 0000000000..58020ce9f2 --- /dev/null +++ b/repos/effect/packages/effect/typetest/unstable/http/HttpRouter.tst.ts @@ -0,0 +1,54 @@ +import { Context, Effect, Layer } from "effect" +import { HttpRouter, HttpServerResponse } from "effect/unstable/http" +import { describe, expect, it } from "tstyche" + +describe("HttpRouter", () => { + describe("toWebHandler", () => { + it("excludes adapter services required by middleware from the request context", () => { + class CurrentUser extends Context.Service()("CurrentUser") {} + + const app = HttpRouter.add( + "GET", + "/", + Effect.gen(function*() { + const user = yield* CurrentUser + return HttpServerResponse.text(user.id) + }) + ) + const { handler } = HttpRouter.toWebHandler(app, { + disableLogger: true, + middleware: (effect) => effect + }) + + expect(handler).type.toBe< + (request: Request, context: Context.Context) => Promise + >() + expect(handler).type.toBeCallableWith( + new Request("http://localhost/"), + Context.make(CurrentUser, { id: "user-1" }) + ) + }) + + it("excludes services provided by the application layer from the request context", () => { + class CurrentUser extends Context.Service()("CurrentUser") {} + + const app = Layer.merge( + HttpRouter.add( + "GET", + "/", + Effect.map(CurrentUser, (user) => HttpServerResponse.text(user.id)) + ), + Layer.succeed(CurrentUser, { id: "user-1" }) + ) + const { handler } = HttpRouter.toWebHandler(app, { + disableLogger: true, + middleware: (effect) => effect + }) + + expect(handler).type.toBe< + (request: Request, context?: Context.Context | undefined) => Promise + >() + expect(handler).type.toBeCallableWith(new Request("http://localhost/")) + }) + }) +}) diff --git a/repos/effect/packages/effect/typetest/unstable/httpapi/HttpApi.tst.ts b/repos/effect/packages/effect/typetest/unstable/httpapi/HttpApi.tst.ts new file mode 100644 index 0000000000..9aaf2cc55f --- /dev/null +++ b/repos/effect/packages/effect/typetest/unstable/httpapi/HttpApi.tst.ts @@ -0,0 +1,61 @@ +import { Schema } from "effect" +import { HttpApi, HttpApiEndpoint, HttpApiGroup } from "effect/unstable/httpapi" +import { describe, expect, it } from "tstyche" + +describe("HttpApi", () => { + describe("groups", () => { + it("preserves group types by identifier", () => { + const User = Schema.Struct({ + id: Schema.String + }) + const Users = HttpApiGroup.make("users").add( + HttpApiEndpoint.get("getUser", "/users/:id", { + params: { + id: Schema.String + }, + success: User + }) + ) + const Admin = HttpApiGroup.make("admin").add( + HttpApiEndpoint.get("getAdmin", "/admin/:id", { + params: { + id: Schema.String + }, + success: User + }) + ) + const Api = HttpApi.make("api").add(Users, Admin) + + expect(Api.groups.users).type.toBe() + expect(Api.groups.admin).type.toBe() + expect(Api).type.not.toBeAssignableTo>() + }) + + it("keeps groups distinct when endpoint identifiers overlap", () => { + const FindUser = HttpApiEndpoint.get("find", "/users/:userId", { + params: { + userId: Schema.String + }, + success: Schema.Struct({ userId: Schema.String }) + }) + const FindAdmin = HttpApiEndpoint.get("find", "/admins", { + query: { + email: Schema.String + }, + success: Schema.Struct({ adminId: Schema.String }) + }) + const Users = HttpApiGroup.make("users").add(FindUser) + const Admins = HttpApiGroup.make("admins").add(FindAdmin) + const Api = HttpApi.make("api").add(Users, Admins) + + expect(Api.groups.users.endpoints.find).type.toBe() + expect(Api.groups.admins.endpoints.find).type.toBe() + expect(Api.groups).type.not.toHaveProperty("missing") + + const Prefixed = Api.prefix("/v1") + + expect(Prefixed.groups.users.endpoints.find).type.toBe>() + expect(Prefixed.groups.admins.endpoints.find).type.toBe>() + }) + }) +}) diff --git a/repos/effect/packages/effect/typetest/unstable/httpapi/HttpApiBuilder.tst.ts b/repos/effect/packages/effect/typetest/unstable/httpapi/HttpApiBuilder.tst.ts index fec4c24c1c..76a2c98c59 100644 --- a/repos/effect/packages/effect/typetest/unstable/httpapi/HttpApiBuilder.tst.ts +++ b/repos/effect/packages/effect/typetest/unstable/httpapi/HttpApiBuilder.tst.ts @@ -1,9 +1,10 @@ -import { Context, Effect, type Layer, Schema } from "effect" +import { Context, Effect, Layer, Schema } from "effect" import type { FileSystem } from "effect/FileSystem" import type { Path } from "effect/Path" import type { Generator } from "effect/unstable/http/Etag" import type { HttpPlatform } from "effect/unstable/http/HttpPlatform" -import type { RouteContext } from "effect/unstable/http/HttpRouter" +import * as HttpRouter from "effect/unstable/http/HttpRouter" +import type { Request as HttpRouterRequest, RouteContext } from "effect/unstable/http/HttpRouter" import type { HttpServerRequest, ParsedSearchParams } from "effect/unstable/http/HttpServerRequest" import type { HttpServerResponse } from "effect/unstable/http/HttpServerResponse" import { @@ -20,7 +21,7 @@ import { describe, expect, it } from "tstyche" describe("HttpApiBuilder", () => { describe("group", () => { it("does not require unknown services for status annotations piped onto errors", () => { - class NotFound extends Schema.TaggedErrorClass()("NotFound", {}) {} + class NotFound extends Schema.TaggedError()("NotFound", {}) {} const Api = HttpApi.make("api").add( HttpApiGroup.make("group").add( HttpApiEndpoint.get("get", "/", { @@ -38,12 +39,517 @@ describe("HttpApiBuilder", () => { }) ) - expect(handlers).type.toBe>>() + expect(handlers).type.toBe>>() + }) + + describe("handle", () => { + it("tracks registrations and preserves request inference", () => { + const User = Schema.Struct({ + id: Schema.String + }) + const CreateUser = Schema.Struct({ + name: Schema.String + }) + const Api = HttpApi.make("api").add( + HttpApiGroup.make("users") + .add( + HttpApiEndpoint.get("getUser", "/users/:id", { + params: { + id: Schema.String + }, + success: User + }) + ) + .add( + HttpApiEndpoint.post("createUser", "/users", { + payload: CreateUser, + success: User + }) + ) + ) + + const handlers = HttpApiBuilder.group( + Api, + "users", + (handlers) => + handlers + .handle("getUser", ({ params }) => { + expect(params.id).type.toBe() + return Effect.succeed({ id: params.id }) + }) + .handle("createUser", ({ payload }) => { + expect(payload.name).type.toBe() + return Effect.succeed({ id: payload.name }) + }) + ) + + expect(handlers).type.toBe>>() + }) + + it("propagates handler service requirements", () => { + class UserRepository extends Context.Service()("UserRepository") {} + const Api = HttpApi.make("api").add( + HttpApiGroup.make("users").add( + HttpApiEndpoint.get("getUser", "/users/:id", { + success: Schema.String + }) + ) + ) + + const handlers = HttpApiBuilder.group( + Api, + "users", + (handlers) => + handlers.handle( + "getUser", + Effect.fnUntraced(function*() { + yield* UserRepository + return "user" + }) + ) + ) + + expect>().type.toBe>() + }) + + it("allows middleware to provide multiple handler service requirements", () => { + class UserRepository extends Context.Service()("UserRepository") {} + class UserPreferences extends Context.Service()("UserPreferences") {} + const Api = HttpApi.make("api").add( + HttpApiGroup.make("users").add( + HttpApiEndpoint.get("getUser", "/users/:id", { + success: Schema.String + }) + ) + ) + + const handlers = HttpApiBuilder.group( + Api, + "users", + (handlers) => + handlers.handle( + "getUser", + Effect.fnUntraced(function*() { + yield* UserRepository + yield* UserPreferences + return "user" + }) + ) + ) + + const middleware = HttpRouter.middleware<{ + provides: UserRepository | UserPreferences + }>()((effect) => + effect.pipe( + Effect.provideService(UserRepository, {}), + Effect.provideService(UserPreferences, {}) + ) + ).layer + + const layer = HttpApiBuilder.layer(Api).pipe( + Layer.provide(handlers), + Layer.provide(middleware) + ) + + expect>().type.toBe< + Generator | FileSystem | HttpPlatform | HttpRouter.HttpRouter | Path + >() + }) + }) + + describe("handleAll", () => { + it("tracks registrations and preserves request inference", () => { + const User = Schema.Struct({ + id: Schema.String + }) + const CreateUser = Schema.Struct({ + name: Schema.String + }) + const Api = HttpApi.make("api").add( + HttpApiGroup.make("users") + .add( + HttpApiEndpoint.get("getUser", "/users/:id", { + params: { + id: Schema.String + }, + success: User + }) + ) + .add( + HttpApiEndpoint.post("createUser", "/users", { + payload: CreateUser, + success: User + }) + ) + ) + + const handlers = HttpApiBuilder.group( + Api, + "users", + (handlers) => + handlers.handleAll({ + getUser: ({ params }) => { + expect(params.id).type.toBe() + return Effect.succeed({ id: params.id }) + }, + createUser: { + handler: ({ payload }) => { + expect(payload.name).type.toBe() + return Effect.succeed({ id: payload.name }) + }, + options: { uninterruptible: true } + } + }) + ) + + expect(handlers).type.toBe>>() + }) + + it("validates partial batches", () => { + const User = Schema.Struct({ + id: Schema.String + }) + const Api = HttpApi.make("api").add( + HttpApiGroup.make("users") + .add( + HttpApiEndpoint.get("getUser", "/users/:id", { + params: { + id: Schema.String + }, + success: User + }) + ) + .add( + HttpApiEndpoint.get("listUsers", "/users", { + success: Schema.Array(User) + }) + ) + ) + + HttpApiBuilder.group( + Api, + "users", + (handlers) => { + expect(handlers.handleAll).type.not.toBeCallableWith({ + getUser: () => Effect.succeed({ id: "id" }), + listUsers: () => Effect.succeed([]), + missing: () => Effect.succeed({ id: "id" }) + }) + + return handlers.handleAll({ + getUser: ({ params }) => Effect.succeed({ id: params.id }), + listUsers: () => Effect.succeed([]) + }) + } + ) + + const incomplete = (handlers: HttpApiBuilder.Handlers.FromGroup>) => + handlers.handleAll({ + getUser: ({ params }) => Effect.succeed({ id: params.id }) + }) + + expect(HttpApiBuilder.group).type.not.toBeCallableWith( + Api, + "users", + incomplete + ) + + const completeInTwoSteps = ( + handlers: HttpApiBuilder.Handlers.FromGroup> + ) => + handlers + .handleAll({ + getUser: ({ params }) => Effect.succeed({ id: params.id }) + }) + .handleAll({ + listUsers: () => Effect.succeed([]) + }) + + expect(HttpApiBuilder.group).type.toBeCallableWith( + Api, + "users", + completeInTwoSteps + ) + + const rejectsDuplicateAcrossBatches = ( + handlers: HttpApiBuilder.Handlers.FromGroup> + ) => { + const afterGetUser = handlers.handleAll({ + getUser: ({ params }) => Effect.succeed({ id: params.id }) + }) + + expect(afterGetUser.handleAll).type.not.toBeCallableWith({ + getUser: () => Effect.succeed({ id: "id" }), + listUsers: () => Effect.succeed([]) + }) + + return afterGetUser.handleAll({ + listUsers: () => Effect.succeed([]) + }) + } + + expect(HttpApiBuilder.group).type.toBeCallableWith( + Api, + "users", + rejectsDuplicateAcrossBatches + ) + }) + + it("propagates all handler service requirements", () => { + class UserRepository extends Context.Service()("UserRepository") {} + class UserPreferences extends Context.Service()("UserPreferences") {} + const Api = HttpApi.make("api").add( + HttpApiGroup.make("users").add( + HttpApiEndpoint.get("getUser", "/users/:id", { + success: Schema.String + }), + HttpApiEndpoint.get("getPreferences", "/users/:id/preferences", { + success: Schema.String + }) + ) + ) + + const handlers = HttpApiBuilder.group( + Api, + "users", + (handlers) => + handlers.handleAll({ + getUser: Effect.fnUntraced(function*() { + yield* UserRepository + return "user" + }), + getPreferences: Effect.fnUntraced(function*() { + yield* UserPreferences + return "preferences" + }) + }) + ) + + type Requirements = + | HttpRouterRequest<"Requires", UserRepository> + | HttpRouterRequest<"Requires", UserPreferences> + + expect>().type.toBeAssignableTo() + expect>().type.toBeAssignableFrom() + }) + }) + + describe("handler validation", () => { + it("rejects incomplete synchronous handler collections", () => { + const User = Schema.Struct({ + id: Schema.String + }) + const Api = HttpApi.make("api").add( + HttpApiGroup.make("users") + .add( + HttpApiEndpoint.get("getUser", "/users/:id", { + params: { + id: Schema.String + }, + success: User + }) + ) + .add( + HttpApiEndpoint.get("listUsers", "/users", { + success: Schema.Array(User) + }) + ) + ) + const build = (handlers: HttpApiBuilder.Handlers.FromGroup>) => + handlers.handle("getUser", ({ params }) => Effect.succeed({ id: params.id })) + + expect(HttpApiBuilder.group).type.not.toBeCallableWith( + Api, + "users", + build + ) + }) + + it("rejects duplicate handlers", () => { + const User = Schema.Struct({ + id: Schema.String + }) + const Api = HttpApi.make("api").add( + HttpApiGroup.make("users") + .add( + HttpApiEndpoint.get("getUser", "/users/:id", { + params: { + id: Schema.String + }, + success: User + }) + ) + .add( + HttpApiEndpoint.get("listUsers", "/users", { + success: Schema.Array(User) + }) + ) + ) + const build = (handlers: HttpApiBuilder.Handlers.FromGroup>) => { + const afterGetUser = handlers.handle("getUser", ({ params }) => Effect.succeed({ id: params.id })) + + expect(afterGetUser.handle).type.not.toBeCallableWith( + "getUser", + () => Effect.succeed({ id: "id" }) + ) + expect(afterGetUser.handleRaw).type.not.toBeCallableWith( + "getUser", + () => Effect.succeed({ id: "id" }) + ) + + return afterGetUser.handle("listUsers", () => Effect.succeed([])) + } + + expect(HttpApiBuilder.group).type.toBeCallableWith( + Api, + "users", + build + ) + }) + + it("rejects incomplete effectful handler collections", () => { + const User = Schema.Struct({ + id: Schema.String + }) + const Api = HttpApi.make("api").add( + HttpApiGroup.make("users") + .add( + HttpApiEndpoint.get("getUser", "/users/:id", { + params: { + id: Schema.String + }, + success: User + }) + ) + .add( + HttpApiEndpoint.get("listUsers", "/users", { + success: Schema.Array(User) + }) + ) + ) + const build = (handlers: HttpApiBuilder.Handlers.FromGroup>) => + Effect.succeed(handlers.handle("getUser", ({ params }) => Effect.succeed({ id: params.id }))) + + expect(HttpApiBuilder.group).type.not.toBeCallableWith( + Api, + "users", + build + ) + }) + + it("accepts complete effectful handler collections", () => { + const User = Schema.Struct({ + id: Schema.String + }) + const Api = HttpApi.make("api").add( + HttpApiGroup.make("users") + .add( + HttpApiEndpoint.get("getUser", "/users/:id", { + params: { + id: Schema.String + }, + success: User + }) + ) + .add( + HttpApiEndpoint.get("listUsers", "/users", { + success: Schema.Array(User) + }) + ) + ) + const build = (handlers: HttpApiBuilder.Handlers.FromGroup>) => + Effect.succeed( + handlers + .handle("getUser", ({ params }) => Effect.succeed({ id: params.id })) + .handle("listUsers", () => Effect.succeed([])) + ) + + expect(HttpApiBuilder.group).type.toBeCallableWith( + Api, + "users", + build + ) + }) + + it("limits handle registrations to remaining endpoint identifiers", () => { + const User = Schema.Struct({ + id: Schema.String + }) + const Api = HttpApi.make("api").add( + HttpApiGroup.make("users") + .add( + HttpApiEndpoint.get("getUser", "/users/:id", { + params: { + id: Schema.String + }, + success: User + }) + ) + .add( + HttpApiEndpoint.get("listUsers", "/users", { + success: Schema.Array(User) + }) + ) + ) + + HttpApiBuilder.group( + Api, + "users", + (handlers) => { + expect(handlers.handle).type.not.toBeCallableWith( + "missing", + () => Effect.succeed({ id: "id" }) + ) + + const remaining = handlers.handle( + "getUser", + ({ params }) => Effect.succeed({ id: params.id }) + ) + + expect(remaining.handle).type.not.toBeCallableWith( + "getUser", + () => Effect.succeed({ id: "id" }) + ) + + return remaining.handle("listUsers", () => Effect.succeed([])) + } + ) + }) + }) + + describe("handleRaw", () => { + it("preserves request inference", () => { + const Api = HttpApi.make("api").add( + HttpApiGroup.make("files").add( + HttpApiEndpoint.post("upload", "/files/:id", { + params: { + id: Schema.String + }, + payload: Schema.String, + success: Schema.String + }) + ) + ) + + const handlers = HttpApiBuilder.group( + Api, + "files", + (handlers) => + handlers.handleRaw("upload", (request) => { + expect(request.params.id).type.toBe() + expect(request.request).type.toBe() + expect(request).type.not.toHaveProperty("payload") + return Effect.succeed("ok") + }) + ) + + expect(handlers).type.toBe>>() + }) }) }) describe("endpoint", () => { - it("middleware & services", () => { + it("preserves selected endpoint middleware and service requirements", () => { const api = HttpApi.make("api").add( HttpApiGroup.make("group").add( HttpApiEndpoint.get("getUser", "/users/:id", { @@ -73,6 +579,129 @@ describe("HttpApiBuilder", () => { > >() }) + + it("selects the intended class-like endpoint when identifiers overlap", () => { + class UsersToken extends Context.Service()("UsersToken") {} + class UsersCurrent extends Context.Service()("UsersCurrent") {} + class AdminsToken extends Context.Service()("AdminsToken") {} + class AdminsCurrent extends Context.Service()("AdminsCurrent") {} + class UsersMiddleware extends HttpApiMiddleware.Service()("UsersMiddleware") {} + class AdminsMiddleware extends HttpApiMiddleware.Service()("AdminsMiddleware") {} + + class UsersLookup extends HttpApiEndpoint.post("lookup", "/users/:userId", { + params: { + userId: Schema.String + }, + query: { + page: Schema.FiniteFromString + }, + payload: Schema.Struct({ name: Schema.String }), + headers: { + "x-user": Schema.String + }, + success: Schema.Struct({ userId: Schema.String, name: Schema.String }) + }).middleware(UsersMiddleware) {} + + const AdminsLookup = HttpApiEndpoint.post("lookup", "/admins/:adminId", { + params: { + adminId: Schema.FiniteFromString + }, + query: { + scope: Schema.String + }, + payload: Schema.Struct({ role: Schema.Literal("admin") }), + headers: { + "x-admin": Schema.String + }, + success: Schema.Struct({ adminId: Schema.Number, role: Schema.String }) + }).middleware(AdminsMiddleware) + const api = HttpApi.make("api").add( + HttpApiGroup.make("users").add(UsersLookup), + HttpApiGroup.make("admins").add(AdminsLookup) + ) + + const usersHandler = HttpApiBuilder.endpoint( + api, + "users", + "lookup", + Effect.fnUntraced(function*(request) { + expect(request.params).type.toBe<{ readonly userId: string }>() + expect(request.query).type.toBe<{ readonly page: number }>() + expect(request.payload).type.toBe<{ readonly name: string }>() + expect(request.headers).type.toBe<{ readonly "x-user": string }>() + expect(request.params).type.not.toHaveProperty("adminId") + const current = yield* UsersCurrent + return { userId: current.userId, name: request.payload.name } + }) + ) + const adminsHandler = HttpApiBuilder.endpoint( + api, + "admins", + "lookup", + Effect.fnUntraced(function*(request) { + expect(request.params).type.toBe<{ readonly adminId: number }>() + expect(request.query).type.toBe<{ readonly scope: string }>() + expect(request.payload).type.toBe<{ readonly role: "admin" }>() + expect(request.headers).type.toBe<{ readonly "x-admin": string }>() + expect(request.params).type.not.toHaveProperty("userId") + const current = yield* AdminsCurrent + return { adminId: current.adminId, role: request.payload.role } + }) + ) + + expect>().type.toBe< + UsersMiddleware | UsersToken | Generator | FileSystem | HttpPlatform | Path + >() + expect>().type.toBe< + AdminsMiddleware | AdminsToken | Generator | FileSystem | HttpPlatform | Path + >() + expect>>().type.toBe< + HttpServerRequest | ParsedSearchParams | RouteContext + >() + expect>>().type.toBe< + HttpServerRequest | ParsedSearchParams | RouteContext + >() + }) + + it("rejects unknown group identifiers", () => { + const api = HttpApi.make("api").add( + HttpApiGroup.make("users").add(HttpApiEndpoint.get("getUser", "/users/:id")) + ) + + expect(HttpApiBuilder.endpoint).type.not.toBeCallableWith( + api, + "missing", + "getUser", + () => Effect.void + ) + }) + + it("rejects unknown endpoint identifiers", () => { + const api = HttpApi.make("api").add( + HttpApiGroup.make("users").add(HttpApiEndpoint.get("getUser", "/users/:id")) + ) + + expect(HttpApiBuilder.endpoint).type.not.toBeCallableWith( + api, + "users", + "missing", + () => Effect.void + ) + }) }) }) diff --git a/repos/effect/packages/effect/typetest/unstable/httpapi/HttpApiClient.tst.ts b/repos/effect/packages/effect/typetest/unstable/httpapi/HttpApiClient.tst.ts index 21a29de966..63a3d2b50c 100644 --- a/repos/effect/packages/effect/typetest/unstable/httpapi/HttpApiClient.tst.ts +++ b/repos/effect/packages/effect/typetest/unstable/httpapi/HttpApiClient.tst.ts @@ -15,8 +15,8 @@ import { describe, expect, it } from "tstyche" type ResponseMode = HttpApiEndpoint.ClientResponseMode describe("HttpApiClient", () => { - describe("path option", () => { - it("should accept a record of fields", () => { + describe("params", () => { + it("derives request params from a field record", () => { const Api = HttpApi.make("Api") .add( HttpApiGroup.make("group") @@ -38,8 +38,8 @@ describe("HttpApiClient", () => { }) }) - describe("query option", () => { - it("should accept a record of fields", () => { + describe("query", () => { + it("derives request query from a field record", () => { const Api = HttpApi.make("Api") .add( HttpApiGroup.make("group") @@ -62,7 +62,7 @@ describe("HttpApiClient", () => { }) describe("urlBuilder", () => { - it("should mirror client shape and use schema input types", () => { + it("mirrors the client shape and uses schema input types", () => { const Api = HttpApi.make("Api") .add( HttpApiGroup.make("users") @@ -76,6 +76,12 @@ describe("HttpApiClient", () => { page: Schema.FiniteFromString } }), + HttpApiEndpoint.get("searchUsers", "/users", { + disableCodecs: true, + query: { + q: Schema.String + } + }), HttpApiEndpoint.get("health", "/health", { disableCodecs: true }) @@ -87,13 +93,24 @@ describe("HttpApiClient", () => { }) expect(builder.users.getUser({ params: { id: 123 }, query: { page: 1 } })).type.toBe() + expect[0]>().type.toBe< + { readonly params: { readonly id: number }; readonly query: { readonly page: number } } + >() + expect(builder.users.searchUsers({ query: { q: "Ada" } })).type.toBe() + expect[0]>().type.toBe< + { readonly query: { readonly q: string } } + >() expect(builder.users.health()).type.toBe() + expect[0]>().type.toBe() expect(builder.users.getUser).type.not.toBeCallableWith({ params: { id: "123" }, query: { page: 1 } }) expect(builder.users.getUser).type.not.toBeCallableWith({ params: { id: 123 }, query: { page: "1" } }) + expect(builder.users.searchUsers).type.not.toBeCallableWith({ params: { id: 123 } }) + expect(builder.users.health).type.not.toBeCallableWith({ params: { id: 123 } }) + expect(builder.users.health).type.not.toBeCallableWith({ query: { q: "Ada" } }) expect(builder.users).type.not.toHaveProperty("missing") }) - it("should support prefixes and top-level endpoints", () => { + it("preserves prefixes and exposes top-level endpoints", () => { const Api = HttpApi.make("Api") .add( HttpApiGroup.make("users") @@ -124,8 +141,224 @@ describe("HttpApiClient", () => { }) }) - describe("headers option", () => { - it("should accept a record of fields", () => { + describe("top-level groups", () => { + it("exposes top-level endpoints directly on the generated client", () => { + const Api = HttpApi.make("Api") + .add( + HttpApiGroup.make("users") + .add( + HttpApiEndpoint.get("getUser", "/users/:id", { + params: { + id: Schema.FiniteFromString + }, + success: Schema.Struct({ id: Schema.String }) + }) + ) + ) + .add( + HttpApiGroup.make("top", { topLevel: true }) + .add( + HttpApiEndpoint.get("topHealth", "/top-health", { + success: Schema.Struct({ ok: Schema.Boolean }) + }) + ) + ) + const client = Effect.runSync( + HttpApiClient.make(Api).pipe(Effect.provide(FetchHttpClient.layer)) + ) + + expect(client.users.getUser({ params: { id: 1 } })).type.toBe< + Effect.Effect<{ readonly id: string }, HttpClientError.HttpClientError | Schema.SchemaError> + >() + expect(client.topHealth()).type.toBe< + Effect.Effect<{ readonly ok: boolean }, HttpClientError.HttpClientError | Schema.SchemaError> + >() + expect(client.topHealth({ responseMode: "response-only" })).type.toBe< + Effect.Effect + >() + expect[0]>().type.toBe< + void | { readonly responseMode?: ResponseMode } | undefined + >() + expect(client).type.not.toHaveProperty("top") + }) + + it("keeps nested and top-level methods distinct when identifiers overlap", () => { + const NestedLookup = HttpApiEndpoint.post("lookup", "/users", { + payload: Schema.Struct({ name: Schema.String }), + success: Schema.Struct({ userId: Schema.String }) + }) + const TopLevelLookup = HttpApiEndpoint.get("lookup", "/lookup", { + query: { + token: Schema.String + }, + success: Schema.Struct({ available: Schema.Boolean }) + }) + const Users = HttpApiGroup.make("users").add(NestedLookup) + const Top = HttpApiGroup.make("top", { topLevel: true }).add(TopLevelLookup) + + type GeneratedClient = HttpApiClient.Client + const client = hole() + + expect().type.toBe<"users" | "lookup">() + expect[0]>().type.toBe< + { readonly payload: { readonly name: string }; readonly responseMode?: ResponseMode } + >() + expect[0]>().type.toBe< + { readonly query: { readonly token: string }; readonly responseMode?: ResponseMode } + >() + expect(client.users.lookup({ payload: { name: "Ada" } })).type.toBe< + Effect.Effect<{ readonly userId: string }, HttpClientError.HttpClientError | Schema.SchemaError> + >() + expect(client.lookup({ query: { token: "abc" } })).type.toBe< + Effect.Effect<{ readonly available: boolean }, HttpClientError.HttpClientError | Schema.SchemaError> + >() + expect(client.users.lookup).type.not.toBeCallableWith({ query: { token: "abc" } }) + expect(client.lookup).type.not.toBeCallableWith({ payload: { name: "Ada" } }) + }) + + it("derives top-level methods from class-like endpoints", () => { + class GetUser extends HttpApiEndpoint.get("getUser", "/users/:id", { + params: { + id: Schema.FiniteFromString + }, + success: Schema.Struct({ id: Schema.String }) + }) {} + + const Top = HttpApiGroup.make("top", { topLevel: true }).add(GetUser) + type GeneratedClient = HttpApiClient.Client + const client = hole() + + expect().type.toBe<"getUser">() + expect[0]>().type.toBe< + { readonly params: { readonly id: number }; readonly responseMode?: ResponseMode } + >() + expect(client.getUser({ params: { id: 1 } })).type.toBe< + Effect.Effect<{ readonly id: string }, HttpClientError.HttpClientError | Schema.SchemaError> + >() + expect(client).type.not.toHaveProperty("top") + }) + + it("merges distinct endpoints from multiple top-level groups", () => { + const Users = HttpApiGroup.make("users", { topLevel: true }).add( + HttpApiEndpoint.get("getUser", "/users/:id", { + params: { + id: Schema.FiniteFromString + }, + success: Schema.Struct({ id: Schema.String }) + }) + ) + const Health = HttpApiGroup.make("system", { topLevel: true }).add( + HttpApiEndpoint.get("health", "/health", { + success: Schema.Struct({ ok: Schema.Boolean }) + }) + ) + + type GeneratedClient = HttpApiClient.Client + const client = hole() + + expect().type.toBe<"getUser" | "health">() + expect[0]>().type.toBe< + { readonly params: { readonly id: number }; readonly responseMode?: ResponseMode } + >() + expect[0]>().type.toBe< + void | { readonly responseMode?: ResponseMode } | undefined + >() + expect(client.getUser({ params: { id: 1 } })).type.toBe< + Effect.Effect<{ readonly id: string }, HttpClientError.HttpClientError | Schema.SchemaError> + >() + expect(client.health()).type.toBe< + Effect.Effect<{ readonly ok: boolean }, HttpClientError.HttpClientError | Schema.SchemaError> + >() + expect(client).type.not.toHaveProperty("users") + expect(client).type.not.toHaveProperty("system") + }) + }) + + describe("Client.Group", () => { + it("derives a group client from a concrete group", () => { + const Users = HttpApiGroup.make("users") + .add( + HttpApiEndpoint.get("getUser", "/users/:id", { + params: { + id: Schema.FiniteFromString + }, + success: Schema.Struct({ id: Schema.String }) + }) + ) + + type UsersClient = HttpApiClient.Client.Group + const client = hole() + + expect().type.toBe<"getUser">() + expect[0]>().type.toBe< + { readonly params: { readonly id: number }; readonly responseMode?: ResponseMode } + >() + expect(client.getUser({ params: { id: 1 } })).type.toBe< + Effect.Effect<{ readonly id: string }, HttpClientError.HttpClientError | Schema.SchemaError> + >() + }) + + it("derives methods from class-like endpoints", () => { + class GetUser extends HttpApiEndpoint.get("getUser", "/users/:id", { + params: { + id: Schema.FiniteFromString + }, + success: Schema.Struct({ id: Schema.String }) + }) {} + + const Users = HttpApiGroup.make("users").add(GetUser) + type UsersClient = HttpApiClient.Client.Group + const client = hole() + + expect().type.toBe<"getUser">() + expect[0]>().type.toBe< + { readonly params: { readonly id: number }; readonly responseMode?: ResponseMode } + >() + expect(client.getUser({ params: { id: 1 } })).type.toBe< + Effect.Effect<{ readonly id: string }, HttpClientError.HttpClientError | Schema.SchemaError> + >() + }) + }) + + describe("group", () => { + it("selects one group when endpoint identifiers overlap", () => { + const UsersLookup = HttpApiEndpoint.get("lookup", "/users/:userId", { + params: { + userId: Schema.String + }, + success: Schema.Struct({ userId: Schema.String }) + }) + const AdminsLookup = HttpApiEndpoint.get("lookup", "/admins", { + query: { + email: Schema.String + }, + success: Schema.Struct({ adminId: Schema.String }) + }) + const Api = HttpApi.make("Api").add( + HttpApiGroup.make("users").add(UsersLookup), + HttpApiGroup.make("admins").add(AdminsLookup) + ) + const httpClient = HttpClient.make(() => Effect.die("not used")) + const usersClient = HttpApiClient.group(Api, { group: "users", httpClient }) + const adminsClient = HttpApiClient.group(Api, { group: "admins", httpClient }) + const selectedUsers = hole>() + const selectedAdmins = hole>() + + expect>().type.toBe<"lookup">() + expect>().type.toBe<"lookup">() + expect[0]>().type.toBe< + { readonly params: { readonly userId: string }; readonly responseMode?: ResponseMode } + >() + expect[0]>().type.toBe< + { readonly query: { readonly email: string }; readonly responseMode?: ResponseMode } + >() + expect(selectedUsers.lookup).type.not.toBeCallableWith({ query: { email: "admin@example.com" } }) + expect(selectedAdmins.lookup).type.not.toBeCallableWith({ params: { userId: "1" } }) + }) + }) + + describe("headers", () => { + it("derives request headers from a field record", () => { const Api = HttpApi.make("Api") .add( HttpApiGroup.make("group") @@ -147,8 +380,8 @@ describe("HttpApiClient", () => { }) }) - describe("payload option", () => { - it("should default to void", () => { + describe("payload", () => { + it("defaults requests without a payload to void", () => { const Api = HttpApi.make("Api") .add( HttpApiGroup.make("group") @@ -163,7 +396,7 @@ describe("HttpApiClient", () => { expect[0]>().type.toBe() }) - it("should accept a record of fields", () => { + it("derives the request payload from a field record", () => { const Api = HttpApi.make("Api") .add( HttpApiGroup.make("group") @@ -184,7 +417,7 @@ describe("HttpApiClient", () => { >() }) - it("should accept a multipart", () => { + it("uses FormData for multipart payloads", () => { const Api = HttpApi.make("Api") .add( HttpApiGroup.make("group") @@ -203,7 +436,7 @@ describe("HttpApiClient", () => { >() }) - it("should accept a multipart stream", () => { + it("uses FormData for streaming multipart payloads", () => { const Api = HttpApi.make("Api") .add( HttpApiGroup.make("group") @@ -223,8 +456,8 @@ describe("HttpApiClient", () => { }) }) - describe("success option", () => { - it("should accept a schema", () => { + describe("success", () => { + it("decodes a success schema", () => { const Api = HttpApi.make("Api") .add( HttpApiGroup.make("group") @@ -245,7 +478,7 @@ describe("HttpApiClient", () => { >() }) - it("should accept an array of schemas", () => { + it("decodes multiple success schemas", () => { const Api = HttpApi.make("Api") .add( HttpApiGroup.make("group") @@ -272,7 +505,7 @@ describe("HttpApiClient", () => { >() }) - it("should infer return type from responseMode", () => { + it("infers return types from responseMode", () => { const Api = HttpApi.make("Api") .add( HttpApiGroup.make("group") @@ -306,7 +539,64 @@ describe("HttpApiClient", () => { >() }) - it("should return decoded streams for StreamSse successes", () => { + it("preserves method parameters across response modes", () => { + const Api = HttpApi.make("Api") + .add( + HttpApiGroup.make("group") + .add( + HttpApiEndpoint.get("a", "/a/:id", { + params: { + id: Schema.FiniteFromString + }, + success: Schema.Struct({ a: Schema.FiniteFromString }) + }) + ) + ) + const client = Effect.runSync( + HttpApiClient.make(Api).pipe(Effect.provide(FetchHttpClient.layer)) + ) + const f = client.group.a + + type Request = Parameters[0] + expect().type.toBe< + { readonly params: { readonly id: number }; readonly responseMode?: ResponseMode } + >() + expect(f).type.toBeCallableWith({ params: { id: 1 } }) + expect(f).type.toBeCallableWith({ params: { id: 1 }, responseMode: "decoded-only" }) + expect(f).type.toBeCallableWith({ params: { id: 1 }, responseMode: "decoded-and-response" }) + expect(f).type.toBeCallableWith({ params: { id: 1 }, responseMode: "response-only" }) + expect(f).type.not.toBeCallableWith({ params: { id: "1" } }) + }) + + it("preserves responseMode inference for generic callers", () => { + const Api = HttpApi.make("Api") + .add( + HttpApiGroup.make("group") + .add( + HttpApiEndpoint.get("a", "/a", { + success: Schema.Struct({ a: Schema.FiniteFromString }) + }) + ) + ) + const client = Effect.runSync( + HttpApiClient.make(Api).pipe(Effect.provide(FetchHttpClient.layer)) + ) + const f = client.group.a + + const call = (mode: Mode) => f({ responseMode: mode }) + expect(call).type.toBeAssignableTo< + ( + mode: Mode + ) => Effect.Effect< + HttpApiClient.Client.Response<{ readonly a: number }, Mode>, + | HttpClientError.HttpClientError + | ([Mode] extends ["response-only"] ? never : Schema.SchemaError), + never + > + >() + }) + + it("returns decoded streams for StreamSse successes", () => { const Api = HttpApi.make("Api") .add( HttpApiGroup.make("group") @@ -331,7 +621,7 @@ describe("HttpApiClient", () => { type StreamError = { readonly reason: string } type ClientStream = Stream.Stream< Event, - StreamError | HttpClientError.HttpClientError | Schema.SchemaError | Sse.Retry + StreamError | HttpClientError.HttpClientError | Schema.SchemaError | Sse.Retry | Sse.SseError > expect(f()).type.toBe< @@ -351,7 +641,77 @@ describe("HttpApiClient", () => { >() }) - it("should return decoded data streams for StreamSse data successes", () => { + it("widens only the body stream errors for WithHeaders StreamSse successes", () => { + const Api = HttpApi.make("Api") + .add( + HttpApiGroup.make("group") + .add( + HttpApiEndpoint.get("a", "/a", { + success: HttpApiSchema.WithHeaders( + HttpApiSchema.StreamSse({ + data: Schema.Struct({ id: Schema.String }), + error: Schema.Struct({ reason: Schema.String }) + }), + { "x-count": Schema.Int } + ) + }) + ) + ) + const client = Effect.runSync( + HttpApiClient.make(Api).pipe(Effect.provide(FetchHttpClient.layer)) + ) + const f = client.group.a + + type ClientStream = Stream.Stream< + { readonly id: string }, + | { readonly reason: string } + | HttpClientError.HttpClientError + | Schema.SchemaError + | Sse.Retry + | Sse.SseError + > + type Success = HttpApiSchema.withHeaders + + expect(f()).type.toBe< + Effect.Effect + >() + expect(f({ responseMode: "decoded-and-response" })).type.toBe< + Effect.Effect< + [Success, HttpClientResponse.HttpClientResponse], + HttpClientError.HttpClientError | Schema.SchemaError + > + >() + }) + + it("widens the body transport error for WithHeaders StreamUint8Array successes", () => { + const Api = HttpApi.make("Api") + .add( + HttpApiGroup.make("group") + .add( + HttpApiEndpoint.get("a", "/a", { + success: HttpApiSchema.WithHeaders( + HttpApiSchema.StreamUint8Array(), + { "x-count": Schema.Int } + ) + }) + ) + ) + const client = Effect.runSync( + HttpApiClient.make(Api).pipe(Effect.provide(FetchHttpClient.layer)) + ) + const f = client.group.a + + type Success = HttpApiSchema.withHeaders< + Stream.Stream, + { readonly "x-count": number } + > + + expect(f()).type.toBe< + Effect.Effect + >() + }) + + it("returns decoded data streams for StreamSse data successes", () => { const Api = HttpApi.make("Api") .add( HttpApiGroup.make("group") @@ -373,7 +733,7 @@ describe("HttpApiClient", () => { type StreamError = { readonly reason: string } type ClientStream = Stream.Stream< Data, - StreamError | HttpClientError.HttpClientError | Schema.SchemaError | Sse.Retry + StreamError | HttpClientError.HttpClientError | Schema.SchemaError | Sse.Retry | Sse.SseError > expect(f()).type.toBe< @@ -387,7 +747,7 @@ describe("HttpApiClient", () => { >() }) - it("should return decoded streams without typed stream errors when StreamSse error is omitted", () => { + it("omits typed stream errors when the StreamSse error schema is omitted", () => { const Api = HttpApi.make("Api") .add( HttpApiGroup.make("group") @@ -407,7 +767,7 @@ describe("HttpApiClient", () => { type Data = { readonly id: string } type ClientStream = Stream.Stream< Data, - HttpClientError.HttpClientError | Schema.SchemaError | Sse.Retry + HttpClientError.HttpClientError | Schema.SchemaError | Sse.Retry | Sse.SseError > expect(f()).type.toBe< @@ -421,7 +781,7 @@ describe("HttpApiClient", () => { >() }) - it("should return decoded streams for StreamUint8Array successes", () => { + it("returns decoded streams for StreamUint8Array successes", () => { const Api = HttpApi.make("Api") .add( HttpApiGroup.make("group") @@ -452,7 +812,7 @@ describe("HttpApiClient", () => { >() }) - it("should not require StreamSse success decoding services in response-only mode", () => { + it("omits StreamSse decoding services in response-only mode", () => { type Event = { readonly event: "user.created"; readonly data: string } type StreamError = { readonly reason: string } @@ -474,7 +834,7 @@ describe("HttpApiClient", () => { type ClientStream = Stream.Stream< Event, - StreamError | HttpClientError.HttpClientError | Schema.SchemaError | Sse.Retry + StreamError | HttpClientError.HttpClientError | Schema.SchemaError | Sse.Retry | Sse.SseError > expect(f({ responseMode: "decoded-only" })).type.toBe< @@ -490,8 +850,8 @@ describe("HttpApiClient", () => { }) }) - describe("error option", () => { - it("should default to client and schema errors", () => { + describe("error", () => { + it("defaults to client and schema errors", () => { const Api = HttpApi.make("Api") .add( HttpApiGroup.make("group") @@ -511,7 +871,7 @@ describe("HttpApiClient", () => { >() }) - it("should accept a schema", () => { + it("includes errors decoded from the endpoint schema", () => { const Api = HttpApi.make("Api") .add( HttpApiGroup.make("group") @@ -535,13 +895,345 @@ describe("HttpApiClient", () => { }) }) + describe("makeWith", () => { + it("normalizes the default HttpClientError for generic clients", () => { + const makeClient = ( + api: HttpApi.HttpApi, + httpClient: HttpClient.HttpClient + ): Effect.Effect< + HttpApiClient.Client, + never, + HttpApiGroup.MiddlewareClient + > => HttpApiClient.makeWith(api, { httpClient }) + + expect(makeClient).type.toBeCallableWith( + hole>(), + hole() + ) + }) + + it("preserves custom client errors when normalizing HttpClientError", () => { + class CustomClientError extends Schema.Error("CustomClientError")({ + _tag: Schema.tag("CustomClientError") + }) {} + + const makeClient = ( + api: HttpApi.HttpApi, + httpClient: HttpClient.HttpClient.With + ): Effect.Effect< + HttpApiClient.Client, + never, + HttpApiGroup.MiddlewareClient + > => HttpApiClient.makeWith(api, { httpClient }) + + expect(makeClient).type.toBeCallableWith( + hole>(), + hole>() + ) + }) + }) + + describe("endpoint", () => { + it("selects an endpoint and preserves its request and response types", () => { + const User = Schema.Struct({ + id: Schema.String, + age: Schema.FiniteFromString + }) + + const Api = HttpApi.make("Api") + .add( + HttpApiGroup.make("users") + .add( + HttpApiEndpoint.get("getUser", "/users/:id", { + params: { + id: Schema.String + }, + query: { + page: Schema.FiniteFromString + }, + success: User + }), + HttpApiEndpoint.get("searchUsers", "/users", { + query: { + q: Schema.String + }, + success: Schema.Array(User) + }) + ) + ) + + const TestHttpClient = HttpClient.make(() => Effect.die("not used")) + const getUser = Effect.runSync( + HttpApiClient.endpoint(Api, { group: "users", endpoint: "getUser", httpClient: TestHttpClient }) + ) + + expect[0]>().type.toBe< + { + readonly params: { readonly id: string } + readonly query: { readonly page: number } + readonly responseMode?: ResponseMode + } + >() + expect(getUser({ params: { id: "1" }, query: { page: 1 } })).type.toBe< + Effect.Effect< + { readonly id: string; readonly age: number }, + HttpClientError.HttpClientError | Schema.SchemaError + > + >() + expect(getUser({ params: { id: "1" }, query: { page: 1 }, responseMode: "decoded-and-response" })).type.toBe< + Effect.Effect< + [{ readonly id: string; readonly age: number }, HttpClientResponse.HttpClientResponse], + HttpClientError.HttpClientError | Schema.SchemaError + > + >() + expect(getUser({ params: { id: "1" }, query: { page: 1 }, responseMode: "response-only" })).type.toBe< + Effect.Effect + >() + expect(getUser).type.not.toBeCallableWith({ query: { q: "Ada" } }) + + const searchUsers = Effect.runSync( + HttpApiClient.endpoint(Api, { group: "users", endpoint: "searchUsers", httpClient: TestHttpClient }) + ) + + expect[0]>().type.toBe< + { readonly query: { readonly q: string }; readonly responseMode?: ResponseMode } + >() + expect(searchUsers).type.not.toBeCallableWith({ params: { id: "1" }, query: { page: 1 } }) + }) + + it("selects class-like endpoints", () => { + class GetUser extends HttpApiEndpoint.get("getUser", "/users/:id", { + params: { + id: Schema.FiniteFromString + }, + success: Schema.Struct({ id: Schema.String }) + }) {} + + const Api = HttpApi.make("Api").add(HttpApiGroup.make("users").add(GetUser)) + const httpClient = HttpClient.make(() => Effect.die("not used")) + const endpointClient = HttpApiClient.endpoint(Api, { + group: "users", + endpoint: "getUser", + httpClient + }) + const getUser = hole>() + + expect[0]>().type.toBe< + { readonly params: { readonly id: number }; readonly responseMode?: ResponseMode } + >() + expect(getUser({ params: { id: 1 } })).type.toBe< + Effect.Effect<{ readonly id: string }, HttpClientError.HttpClientError | Schema.SchemaError> + >() + }) + + it("selects within the requested group when endpoint identifiers overlap", () => { + class UsersEndpointError extends Schema.TaggedError()("UsersEndpointError", {}) {} + class AdminsEndpointError extends Schema.TaggedError()("AdminsEndpointError", {}) {} + class UsersClientError extends Schema.TaggedError()("UsersClientError", {}) {} + class AdminsClientError extends Schema.TaggedError()("AdminsClientError", {}) {} + + class UsersMiddleware extends HttpApiMiddleware.Service()("UsersMiddleware", { + error: Schema.Literal("users-middleware-error"), + requiredForClient: true + }) {} + class AdminsMiddleware extends HttpApiMiddleware.Service()("AdminsMiddleware", { + error: Schema.Literal("admins-middleware-error"), + requiredForClient: true + }) {} + + const UsersLookup = HttpApiEndpoint.post("lookup", "/users/:userId", { + params: { + userId: Schema.String + }, + query: { + page: Schema.FiniteFromString + }, + payload: Schema.Struct({ name: Schema.String }), + headers: { + "x-user": Schema.String + }, + success: Schema.Struct({ userId: Schema.String, name: Schema.String }), + error: UsersEndpointError + }).middleware(UsersMiddleware) + const AdminsLookup = HttpApiEndpoint.post("lookup", "/admins/:adminId", { + params: { + adminId: Schema.FiniteFromString + }, + query: { + scope: Schema.String + }, + payload: Schema.Struct({ role: Schema.Literal("admin") }), + headers: { + "x-admin": Schema.String + }, + success: Schema.Struct({ adminId: Schema.Number, role: Schema.String }), + error: AdminsEndpointError + }).middleware(AdminsMiddleware) + const Api = HttpApi.make("Api").add( + HttpApiGroup.make("users").add(UsersLookup), + HttpApiGroup.make("admins").add(AdminsLookup) + ) + const httpClient = HttpClient.make(() => Effect.die("not used")) + + const usersEndpoint = HttpApiClient.endpoint(Api, { + group: "users", + endpoint: "lookup", + httpClient + }) + const adminsEndpoint = HttpApiClient.endpoint(Api, { + group: "admins", + endpoint: "lookup", + httpClient + }) + const usersMethod = hole>() + const adminsMethod = hole>() + + expect>().type.toBe>() + expect>().type.toBe>() + expect[0]>().type.toBe< + { + readonly params: { readonly userId: string } + readonly query: { readonly page: number } + readonly payload: { readonly name: string } + readonly headers: { readonly "x-user": string } + readonly responseMode?: ResponseMode + } + >() + expect[0]>().type.toBe< + { + readonly params: { readonly adminId: number } + readonly query: { readonly scope: string } + readonly payload: { readonly role: "admin" } + readonly headers: { readonly "x-admin": string } + readonly responseMode?: ResponseMode + } + >() + expect(usersMethod({ + params: { userId: "1" }, + query: { page: 1 }, + payload: { name: "Ada" }, + headers: { "x-user": "user" } + })).type.toBe< + Effect.Effect< + { readonly userId: string; readonly name: string }, + | UsersEndpointError + | UsersClientError + | "users-middleware-error" + | HttpClientError.HttpClientError + | Schema.SchemaError + > + >() + expect(adminsMethod({ + params: { adminId: 1 }, + query: { scope: "all" }, + payload: { role: "admin" }, + headers: { "x-admin": "admin" } + })).type.toBe< + Effect.Effect< + { readonly adminId: number; readonly role: string }, + | AdminsEndpointError + | AdminsClientError + | "admins-middleware-error" + | HttpClientError.HttpClientError + | Schema.SchemaError + > + >() + expect(usersMethod).type.not.toBeCallableWith({ + params: { adminId: 1 }, + query: { scope: "all" }, + payload: { role: "admin" }, + headers: { "x-admin": "admin" } + }) + expect(adminsMethod).type.not.toBeCallableWith({ + params: { userId: "1" }, + query: { page: 1 }, + payload: { name: "Ada" }, + headers: { "x-user": "user" } + }) + }) + + it("rejects unknown group identifiers", () => { + const Api = HttpApi.make("Api").add( + HttpApiGroup.make("users").add(HttpApiEndpoint.get("getUser", "/users/:id")) + ) + const httpClient = HttpClient.make(() => Effect.die("not used")) + + expect(HttpApiClient.endpoint).type.not.toBeCallableWith(Api, { + group: "missing", + endpoint: "getUser", + httpClient + }) + }) + + it("rejects unknown endpoint identifiers", () => { + const Api = HttpApi.make("Api").add( + HttpApiGroup.make("users").add(HttpApiEndpoint.get("getUser", "/users/:id")) + ) + const httpClient = HttpClient.make(() => Effect.die("not used")) + + expect(HttpApiClient.endpoint).type.not.toBeCallableWith(Api, { + group: "users", + endpoint: "missing", + httpClient + }) + }) + + it("preserves custom HTTP client errors and requirements", () => { + interface CustomService { + readonly customService: "customService" + } + + class CustomClientError extends Schema.Error("CustomClientError")({ + _tag: Schema.tag("CustomClientError") + }) {} + + const Api = HttpApi.make("Api") + .add( + HttpApiGroup.make("users") + .add( + HttpApiEndpoint.get("getUser", "/users/:id", { + params: { + id: Schema.String + }, + success: Schema.String + }) + ) + ) + + const httpClient = hole>() + const getUser = Effect.runSync( + HttpApiClient.endpoint(Api, { + group: "users", + endpoint: "getUser", + httpClient, + transformClient: (client) => { + expect(client).type.toBe>() + return client + } + }) + ) + + expect(getUser({ params: { id: "1" } })).type.toBe< + Effect.Effect< + string, + CustomClientError | HttpClientError.HttpClientError | Schema.SchemaError, + CustomService + > + >() + }) + }) + describe("client middleware", () => { - it("requiredForClient requires layer and includes required client errors", () => { - class RequiredClientError extends Schema.ErrorClass("RequiredClientError")({ + it("requires layers and includes errors for required client middleware", () => { + class RequiredClientError extends Schema.Error("RequiredClientError")({ _tag: Schema.tag("RequiredClientError") }) {} - class OptionalClientError extends Schema.ErrorClass("OptionalClientError")({ + class OptionalClientError extends Schema.Error("OptionalClientError")({ _tag: Schema.tag("OptionalClientError") }) {} @@ -589,8 +1281,8 @@ describe("HttpApiClient", () => { ) }) - it("requiredForClient is enforced for makeWith, group, and endpoint", () => { - class RequiredClientError extends Schema.ErrorClass("RequiredClientError")({ + it("enforces required middleware for makeWith, group, and endpoint", () => { + class RequiredClientError extends Schema.Error("RequiredClientError")({ _tag: Schema.tag("RequiredClientError") }) {} diff --git a/repos/effect/packages/effect/typetest/unstable/httpapi/HttpApiEndpoint.tst.ts b/repos/effect/packages/effect/typetest/unstable/httpapi/HttpApiEndpoint.tst.ts index 109e8edf10..3dff19a20b 100644 --- a/repos/effect/packages/effect/typetest/unstable/httpapi/HttpApiEndpoint.tst.ts +++ b/repos/effect/packages/effect/typetest/unstable/httpapi/HttpApiEndpoint.tst.ts @@ -1,122 +1,158 @@ import { type Effect, hole, Schema, type Stream, Struct } from "effect" import type { HttpServerResponse } from "effect/unstable/http/HttpServerResponse" -import { HttpApiEndpoint, HttpApiError, HttpApiSchema } from "effect/unstable/httpapi" +import { HttpApiEndpoint, HttpApiError, HttpApiMiddleware, HttpApiSchema } from "effect/unstable/httpapi" import { describe, expect, it } from "tstyche" describe("HttpApiEndpoint", () => { - describe("params option", () => { - it("should default to never", () => { + describe("isHttpApiEndpoint", () => { + it("narrows to Top", () => { + const value: unknown = HttpApiEndpoint.get("a", "/a") + + if (HttpApiEndpoint.isHttpApiEndpoint(value)) { + expect(value).type.toBe() + } + }) + }) + + describe("Identifier", () => { + it("extracts endpoint identifiers", () => { + const a = HttpApiEndpoint.get("a", "/a") + const b = HttpApiEndpoint.get("b", "/b") + + expect>().type.toBe<"a">() + expect>().type.toBe<"a" | "b">() + }) + + it("exposes the identifier literal", () => { + const endpoint = HttpApiEndpoint.get("getUser", "/users/:id") + + expect(endpoint.identifier).type.toBe<"getUser">() + }) + }) + + describe("class extension", () => { + it("supports extending an endpoint as a class", () => { + class GetUser extends HttpApiEndpoint.get("getUser", "/users/:id") {} + + expect(GetUser.identifier).type.toBe<"getUser">() + expect>().type.toBe<"getUser">() + expect(GetUser.prefix("/v1").path).type.toBe<"/v1/users/:id">() + }) + }) + + describe("params", () => { + it("defaults to never", () => { const endpoint = HttpApiEndpoint.get("a", "/a") - expect(endpoint["~Params"]).type.toBe>() + expect(endpoint["~Params"]).type.toBe() }) - it("should accept a record of fields", () => { + it("accepts a field record", () => { const endpoint = HttpApiEndpoint.get("a", "/a", { params: { id: Schema.Finite } }) expect(endpoint["~Params"]).type.toBe< - HttpApiEndpoint.StringTree< + Schema.toCodecStringTree< Schema.Struct<{ id: Schema.Finite }> > >() }) - it("should accept a Struct", () => { + it("accepts a Struct schema", () => { const endpoint = HttpApiEndpoint.get("a", "/a", { params: Schema.Struct({ a: Schema.Finite, b: Schema.Finite }) }) expect(endpoint["~Params"]).type.toBe< - HttpApiEndpoint.StringTree< + Schema.toCodecStringTree< Schema.Struct<{ readonly a: Schema.Finite; readonly b: Schema.Finite }> > >() }) }) - describe("query option", () => { - it("should default to never", () => { + describe("query", () => { + it("defaults to never", () => { const endpoint = HttpApiEndpoint.get("a", "/a") - expect(endpoint["~Query"]).type.toBe>() + expect(endpoint["~Query"]).type.toBe() }) - it("should accept a record of fields", () => { + it("accepts a field record", () => { const endpoint = HttpApiEndpoint.get("a", "/a", { query: { id: Schema.Finite } }) - expect(endpoint["~Query"]).type.toBe>>() + expect(endpoint["~Query"]).type.toBe>>() }) - it("should accept a Struct.Record", () => { + it("accepts a Struct record", () => { const endpoint = HttpApiEndpoint.get("a", "/a", { query: Struct.Record(["a", "b"], Schema.Finite) }) expect(endpoint["~Query"]).type.toBe< - HttpApiEndpoint.StringTree> + Schema.toCodecStringTree> >() }) - it("should accept a Struct", () => { + it("accepts a Struct schema", () => { const endpoint = HttpApiEndpoint.get("a", "/a", { query: Schema.Struct({ a: Schema.Finite, b: Schema.Finite }) }) expect(endpoint["~Query"]).type.toBe< - HttpApiEndpoint.StringTree< + Schema.toCodecStringTree< Schema.Struct<{ readonly a: Schema.Finite; readonly b: Schema.Finite }> > >() }) }) - describe("headers option", () => { - it("should default to never", () => { + describe("headers", () => { + it("defaults to never", () => { const endpoint = HttpApiEndpoint.get("a", "/a") - expect(endpoint["~Headers"]).type.toBe>() + expect(endpoint["~Headers"]).type.toBe() }) - it("should accept a record of fields", () => { + it("accepts a field record", () => { const endpoint = HttpApiEndpoint.get("a", "/a", { headers: { id: Schema.FiniteFromString } }) expect(endpoint["~Headers"]).type.toBe< - HttpApiEndpoint.StringTree> + Schema.toCodecStringTree> >() }) - it("should accept a Struct", () => { + it("accepts a Struct schema", () => { const endpoint = HttpApiEndpoint.get("a", "/a", { headers: Schema.Struct({ a: Schema.FiniteFromString, b: Schema.FiniteFromString }) }) expect(endpoint["~Headers"]).type.toBe< - HttpApiEndpoint.StringTree< + Schema.toCodecStringTree< Schema.Struct<{ readonly a: Schema.FiniteFromString; readonly b: Schema.FiniteFromString }> > >() }) }) - describe("payload option", () => { - it("should default to never", () => { + describe("payload", () => { + it("defaults to never", () => { const endpoint = HttpApiEndpoint.get("a", "/a") - expect(endpoint["~Payload"]).type.toBe>() + expect(endpoint["~Payload"]).type.toBe() }) describe("GET", () => { - it("should accept a record of fields", () => { + it("accepts a field record", () => { const endpoint = HttpApiEndpoint.get("a", "/a", { payload: { id: Schema.Finite } }) - expect(endpoint["~Payload"]).type.toBe>>() + expect(endpoint["~Payload"]).type.toBe>>() }) - it("should not accept any other schema", () => { + it("rejects non-field schemas", () => { expect(HttpApiEndpoint.get).type.not.toBeCallableWith("a", "/a", { payload: Schema.Struct({ id: Schema.String }) }) @@ -124,14 +160,16 @@ describe("HttpApiEndpoint", () => { }) describe("POST", () => { - it("should accept a schema", () => { + it("accepts a schema", () => { const endpoint = HttpApiEndpoint.post("a", "/a", { payload: Schema.Struct({ a: Schema.String }) }) - expect(endpoint["~Payload"]).type.toBe>>() + expect(endpoint["~Payload"]).type.toBe< + Schema.toCodecJson> + >() }) - it("should accept an array of schemas", () => { + it("accepts multiple schemas", () => { const endpoint = HttpApiEndpoint.post("a", "/a", { payload: [ Schema.Struct({ a: Schema.String }), // application/json @@ -140,7 +178,7 @@ describe("HttpApiEndpoint", () => { ] }) expect(endpoint["~Payload"]).type.toBe< - HttpApiEndpoint.Json< + Schema.toCodecJson< Schema.String | Schema.Struct<{ readonly a: Schema.String }> | Schema.Uint8Array > >() @@ -148,16 +186,16 @@ describe("HttpApiEndpoint", () => { }) describe("HEAD", () => { - it("should accept a record of fields", () => { + it("accepts a field record", () => { const endpoint = HttpApiEndpoint.head("a", "/a", { payload: { id: Schema.Finite } }) - expect(endpoint["~Payload"]).type.toBe>>() + expect(endpoint["~Payload"]).type.toBe>>() }) - it("should not accept any other schema", () => { + it("rejects non-field schemas", () => { expect(HttpApiEndpoint.head).type.not.toBeCallableWith("a", "/a", { payload: Schema.Struct({ id: Schema.String }) }) @@ -165,16 +203,16 @@ describe("HttpApiEndpoint", () => { }) describe("OPTIONS", () => { - it("should accept a record of fields", () => { + it("accepts a field record", () => { const endpoint = HttpApiEndpoint.options("a", "/a", { payload: { id: Schema.Finite } }) - expect(endpoint["~Payload"]).type.toBe>>() + expect(endpoint["~Payload"]).type.toBe>>() }) - it("should not accept any other schema", () => { + it("rejects non-field schemas", () => { expect(HttpApiEndpoint.options).type.not.toBeCallableWith("a", "/a", { payload: Schema.Struct({ id: Schema.String }) }) @@ -182,20 +220,144 @@ describe("HttpApiEndpoint", () => { }) }) - describe("success option", () => { - it("should default to HttpApiSchema.NoContent", () => { + describe("disableCodecs", () => { + it("leaves omitted request parts as never", () => { + const endpoint = HttpApiEndpoint.get("a", "/a", { + disableCodecs: true + }) + + expect(endpoint["~Params"]).type.toBe() + expect(endpoint["~Query"]).type.toBe() + expect(endpoint["~Headers"]).type.toBe() + expect(endpoint["~Payload"]).type.toBe() + }) + + it("preserves request schemas without codec wrappers", () => { + const endpoint = HttpApiEndpoint.post("a", "/a", { + disableCodecs: true, + params: { + id: Schema.FiniteFromString + }, + query: { + page: Schema.FiniteFromString + }, + headers: { + authorization: Schema.String + }, + payload: Schema.Struct({ body: Schema.String }) + }) + + expect(endpoint["~Params"]).type.toBe>() + expect(endpoint["~Query"]).type.toBe>() + expect(endpoint["~Headers"]).type.toBe>() + expect(endpoint["~Payload"]).type.toBe>() + }) + }) + + describe("request and handler types", () => { + it("derives decoded request parts with Request", () => { + const endpoint = HttpApiEndpoint.post("a", "/a", { + params: { + id: Schema.String + }, + query: { + page: Schema.FiniteFromString + }, + headers: { + authorization: Schema.String + }, + payload: Schema.Struct({ + name: Schema.String + }), + success: Schema.String + }) + + type Request = HttpApiEndpoint.Request + + expect().type.toBe<{ readonly id: string }>() + expect().type.toBe<{ readonly page: number }>() + expect().type.toBe<{ readonly authorization: string }>() + expect().type.toBe<{ readonly name: string }>() + expect().type.toBe() + }) + + it("omits the payload from RequestRaw", () => { + const endpoint = HttpApiEndpoint.post("a", "/a", { + params: { + id: Schema.String + }, + payload: Schema.Struct({ + name: Schema.String + }), + success: Schema.String + }) + + type Request = HttpApiEndpoint.RequestRaw + + expect().type.toBe<{ readonly id: string }>() + expect().type.not.toHaveProperty("payload") + expect().type.toBe() + }) + + it("passes decoded request parts to Handler", () => { + const endpoint = HttpApiEndpoint.post("a", "/a", { + params: { + id: Schema.String + }, + query: { + page: Schema.FiniteFromString + }, + headers: { + authorization: Schema.String + }, + payload: Schema.Struct({ + name: Schema.String + }), + success: Schema.String + }) + + type Request = Parameters>[0] + + expect().type.toBe<{ readonly id: string }>() + expect().type.toBe<{ readonly page: number }>() + expect().type.toBe<{ readonly authorization: string }>() + expect().type.toBe<{ readonly name: string }>() + expect().type.toBe() + }) + + it("omits the payload from HandlerRaw requests", () => { + const endpoint = HttpApiEndpoint.post("a", "/a", { + params: { + id: Schema.String + }, + payload: Schema.Struct({ + name: Schema.String + }), + success: Schema.String + }) + + type Request = Parameters>[0] + + expect().type.toBe<{ readonly id: string }>() + expect().type.not.toHaveProperty("payload") + expect().type.toBe() + }) + }) + + describe("success", () => { + it("defaults to HttpApiSchema.NoContent", () => { const endpoint = HttpApiEndpoint.get("a", "/a") - expect(endpoint["~Success"]).type.toBe>() + expect(endpoint["~Success"]).type.toBe>() }) - it("should accept a schema", () => { + it("applies the JSON codec to a schema", () => { const endpoint = HttpApiEndpoint.get("a", "/a", { success: Schema.Struct({ a: Schema.String }) }) - expect(endpoint["~Success"]).type.toBe>>() + expect(endpoint["~Success"]).type.toBe>>() }) - it("should accept an array of schemas", () => { + it("applies the JSON codec to multiple buffered schemas", () => { const endpoint = HttpApiEndpoint.get("a", "/a", { success: [ Schema.Struct({ a: Schema.String }), // application/json @@ -204,11 +366,29 @@ describe("HttpApiEndpoint", () => { ] }) expect(endpoint["~Success"]).type.toBe< - HttpApiEndpoint.Json | Schema.Uint8Array> + Schema.toCodecJson | Schema.Uint8Array> + >() + }) + + it("preserves mixed buffered and stream schemas", () => { + const stream = HttpApiSchema.StreamSse({ + data: Schema.Struct({ token: Schema.String }), + error: Schema.Struct({ reason: Schema.String }) + }) + const endpoint = HttpApiEndpoint.get("a", "/a", { + success: [ + Schema.Struct({ message: Schema.String }), + stream + ] + }) + + expect(endpoint["~Success"]).type.toBe< + | Schema.toCodecJson> + | typeof stream >() }) - it("should accept StreamSse", () => { + it("preserves StreamSse schemas", () => { const stream = HttpApiSchema.StreamSse({ events: Schema.Struct({ event: Schema.Literal("user.created"), @@ -222,7 +402,7 @@ describe("HttpApiEndpoint", () => { expect(endpoint["~Success"]).type.toBe() }) - it("should map StreamSse to stream success helper and handler types", () => { + it("maps StreamSse to stream success and handler types", () => { const endpoint = HttpApiEndpoint.get("a", "/a", { success: HttpApiSchema.StreamSse({ events: Schema.Struct({ @@ -237,7 +417,7 @@ describe("HttpApiEndpoint", () => { type StreamError = { readonly reason: string } type Success = Stream.Stream - expect>().type.toBe() + expect>().type.toBe() expect>>().type.toBe< Effect.Effect >() @@ -246,7 +426,7 @@ describe("HttpApiEndpoint", () => { >() }) - it("should map StreamSse data mode to data stream helper and handler types", () => { + it("maps StreamSse data mode to data stream and handler types", () => { const endpoint = HttpApiEndpoint.get("a", "/a", { success: HttpApiSchema.StreamSse({ data: Schema.Struct({ id: Schema.String }), @@ -258,13 +438,13 @@ describe("HttpApiEndpoint", () => { type StreamError = { readonly reason: string } type Success = Stream.Stream - expect>().type.toBe() + expect>().type.toBe() expect>>().type.toBe< Effect.Effect >() }) - it("should map StreamSse without error schema to never stream errors", () => { + it("maps an omitted StreamSse error schema to never", () => { const endpoint = HttpApiEndpoint.get("a", "/a", { success: HttpApiSchema.StreamSse({ data: Schema.Struct({ id: Schema.String }) @@ -274,13 +454,13 @@ describe("HttpApiEndpoint", () => { type Data = { readonly id: string } type Success = Stream.Stream - expect>().type.toBe() + expect>().type.toBe() expect>>().type.toBe< Effect.Effect >() }) - it("should accept StreamUint8Array", () => { + it("preserves StreamUint8Array schemas", () => { const stream = HttpApiSchema.StreamUint8Array() const endpoint = HttpApiEndpoint.get("a", "/a", { success: stream @@ -288,14 +468,83 @@ describe("HttpApiEndpoint", () => { expect(endpoint["~Success"]).type.toBe() }) - it("should map StreamUint8Array to stream success helper and handler types", () => { + it("applies the JSON and string-tree codecs to WithHeaders parts", () => { + const Body = Schema.Struct({ a: Schema.String }) + const Headers = Schema.Struct({ "x-count": Schema.Int }) + const endpoint = HttpApiEndpoint.get("a", "/a", { + success: HttpApiSchema.WithHeaders(Body, Headers) + }) + + expect(endpoint["~Success"]).type.toBe< + HttpApiSchema.WithHeaders, Schema.toCodecStringTree> + >() + }) + + it("maps WithHeaders to branded value success and handler types", () => { + const endpoint = HttpApiEndpoint.get("a", "/a", { + success: HttpApiSchema.WithHeaders(Schema.Struct({ a: Schema.String }), { + "x-count": Schema.Int + }) + }) + + type Success = HttpApiSchema.withHeaders< + { readonly a: string }, + { readonly "x-count": number } + > + + expect>().type.toBe() + expect>>().type.toBe< + Effect.Effect + >() + }) + + it("keeps the stream handler type for a wrapped stream schema", () => { + const endpoint = HttpApiEndpoint.get("a", "/a", { + success: HttpApiSchema.WithHeaders( + HttpApiSchema.StreamSse({ + data: Schema.Struct({ id: Schema.String }), + error: Schema.Struct({ reason: Schema.String }) + }), + { "x-count": Schema.Int } + ) + }) + + type Success = HttpApiSchema.withHeaders< + Stream.Stream<{ readonly id: string }, { readonly reason: string }>, + { readonly "x-count": number } + > + + expect>().type.toBe() + expect>>().type.toBe< + Effect.Effect + >() + }) + + it("preserves mixed WithHeaders and plain schemas", () => { + const endpoint = HttpApiEndpoint.get("a", "/a", { + success: [ + HttpApiSchema.WithHeaders(Schema.Struct({ a: Schema.String }), { + "x-count": Schema.Int + }), + Schema.String.pipe(HttpApiSchema.status(201), HttpApiSchema.asText()) + ] + }) + + type Success = + | HttpApiSchema.withHeaders<{ readonly a: string }, { readonly "x-count": number }> + | string + + expect>().type.toBe() + }) + + it("maps StreamUint8Array to stream success and handler types", () => { const endpoint = HttpApiEndpoint.get("a", "/a", { success: HttpApiSchema.StreamUint8Array() }) type Success = Stream.Stream - expect>().type.toBe() + expect>().type.toBe() expect>>().type.toBe< Effect.Effect >() @@ -304,7 +553,7 @@ describe("HttpApiEndpoint", () => { >() }) - it("should include StreamSse event and error schema services", () => { + it("includes StreamSse event and error schema services", () => { type Event = { readonly event: "user.created"; readonly data: string } type StreamError = { readonly reason: string } @@ -319,19 +568,61 @@ describe("HttpApiEndpoint", () => { }) }) - describe("error option", () => { - it("should accept a schema", () => { + describe("error services", () => { + it("includes endpoint and middleware decoding services", () => { + type EndpointError = { readonly reason: string } + type MiddlewareError = { readonly reason: string } + + const EndpointError = hole>() + const MiddlewareError = hole>() + + class Middleware extends HttpApiMiddleware.Service()("Middleware", { + error: MiddlewareError + }) {} + + const endpoint = HttpApiEndpoint.get("a", "/a", { + error: EndpointError + }).middleware(Middleware) + + expect>().type.toBe< + "EndpointErrorDecoding" | "MiddlewareErrorDecoding" + >() + }) + + it("includes endpoint and middleware encoding services", () => { + type EndpointError = { readonly reason: string } + type MiddlewareError = { readonly reason: string } + + const EndpointError = hole>() + const MiddlewareError = hole>() + + class Middleware extends HttpApiMiddleware.Service()("Middleware", { + error: MiddlewareError + }) {} + + const endpoint = HttpApiEndpoint.get("a", "/a", { + error: EndpointError + }).middleware(Middleware) + + expect>().type.toBe< + "EndpointErrorEncoding" | "MiddlewareErrorEncoding" + >() + }) + }) + + describe("error", () => { + it("applies the JSON codec to a schema", () => { const endpoint = HttpApiEndpoint.get("a", "/a", { error: Schema.Struct({ a: Schema.String }) }) expect(endpoint["~Error"]).type.toBe< - HttpApiEndpoint.Json< + Schema.toCodecJson< Schema.Struct<{ readonly a: Schema.String }> > >() }) - it("should accept an array of schemas", () => { + it("applies the JSON codec to multiple schemas", () => { const endpoint = HttpApiEndpoint.get("a", "/a", { error: [ Schema.Struct({ a: Schema.String }), // application/json @@ -340,7 +631,7 @@ describe("HttpApiEndpoint", () => { ] }) expect(endpoint["~Error"]).type.toBe< - HttpApiEndpoint.Json< + Schema.toCodecJson< | Schema.String | Schema.Struct<{ readonly a: Schema.String }> | Schema.Uint8Array @@ -348,7 +639,27 @@ describe("HttpApiEndpoint", () => { >() }) - it("should infer endpoint errors with mixed buffered and StreamSse success schemas", () => { + it("combines endpoint and middleware errors", () => { + type EndpointError = { readonly _tag: "EndpointError" } + type MiddlewareError = { readonly _tag: "MiddlewareError" } + + const EndpointError = Schema.Struct({ _tag: Schema.Literal("EndpointError") }) + const MiddlewareError = Schema.Struct({ _tag: Schema.Literal("MiddlewareError") }) + + class Middleware extends HttpApiMiddleware.Service()("Middleware", { + error: MiddlewareError + }) {} + + const endpoint = HttpApiEndpoint.get("a", "/a", { + error: EndpointError + }).middleware(Middleware) + + expect>().type.toBe< + EndpointError | MiddlewareError + >() + }) + + it("preserves endpoint errors with mixed buffered and StreamSse successes", () => { const endpoint = HttpApiEndpoint.post("completions", "/completions", { payload: Schema.Struct({ prompt: Schema.String }), success: [ @@ -367,7 +678,7 @@ describe("HttpApiEndpoint", () => { expect>().type.toBe() }) - it("should infer a single endpoint error with StreamSse success", () => { + it("preserves a single endpoint error with a StreamSse success", () => { const endpoint = HttpApiEndpoint.get("a", "/a", { success: HttpApiSchema.StreamSse({ data: Schema.Struct({ token: Schema.String }), @@ -379,7 +690,7 @@ describe("HttpApiEndpoint", () => { expect>().type.toBe() }) - it("should infer endpoint error arrays with StreamSse success", () => { + it("preserves endpoint error unions with a StreamSse success", () => { const endpoint = HttpApiEndpoint.get("a", "/a", { success: HttpApiSchema.StreamSse({ data: Schema.Struct({ token: Schema.String }), @@ -393,7 +704,7 @@ describe("HttpApiEndpoint", () => { >() }) - it("should infer endpoint error arrays with StreamSse first in a success array", () => { + it("preserves endpoint error unions when StreamSse is the first success", () => { const endpoint = HttpApiEndpoint.get("a", "/a", { success: [ HttpApiSchema.StreamSse({ @@ -410,7 +721,7 @@ describe("HttpApiEndpoint", () => { >() }) - it("should infer endpoint error arrays with StreamUint8Array success", () => { + it("preserves endpoint error unions with a StreamUint8Array success", () => { const endpoint = HttpApiEndpoint.get("a", "/a", { success: HttpApiSchema.StreamUint8Array(), error: [HttpApiError.BadRequest, HttpApiError.Conflict] @@ -421,7 +732,7 @@ describe("HttpApiEndpoint", () => { >() }) - it("should infer endpoint errors with disableCodecs enabled", () => { + it("preserves endpoint errors when codecs are disabled", () => { const endpoint = HttpApiEndpoint.post("a", "/a", { disableCodecs: true, payload: Schema.Struct({ prompt: Schema.String }), @@ -441,7 +752,7 @@ describe("HttpApiEndpoint", () => { >() }) - it("should not accept streaming schemas", () => { + it("rejects streaming schemas", () => { expect(HttpApiEndpoint.get).type.not.toBeCallableWith("a", "/a", { error: HttpApiSchema.StreamUint8Array() }) diff --git a/repos/effect/packages/effect/typetest/unstable/httpapi/HttpApiGroup.tst.ts b/repos/effect/packages/effect/typetest/unstable/httpapi/HttpApiGroup.tst.ts new file mode 100644 index 0000000000..61d9f7c070 --- /dev/null +++ b/repos/effect/packages/effect/typetest/unstable/httpapi/HttpApiGroup.tst.ts @@ -0,0 +1,125 @@ +import { Schema } from "effect" +import { HttpApiEndpoint, HttpApiGroup, HttpApiMiddleware } from "effect/unstable/httpapi" +import { describe, expect, it } from "tstyche" + +describe("HttpApiGroup", () => { + describe("isHttpApiGroup", () => { + it("narrows to Top", () => { + const value: unknown = HttpApiGroup.make("users") + + if (HttpApiGroup.isHttpApiGroup(value)) { + expect(value).type.toBe() + } + }) + }) + + describe("Service", () => { + it("tracks the group identifier", () => { + type Users = HttpApiGroup.Service<"api", "users"> + + expect().type.toBe<"users">() + expect().type.not.toHaveProperty("name") + }) + }) + + describe("endpoints", () => { + it("preserves endpoint types by identifier", () => { + const User = Schema.Struct({ + id: Schema.String + }) + const GetUser = HttpApiEndpoint.get("getUser", "/users/:id", { + params: { + id: Schema.String + }, + success: User + }) + const CreateUser = HttpApiEndpoint.post("createUser", "/users", { + payload: User, + success: User + }) + const Group = HttpApiGroup.make("users").add(GetUser, CreateUser) + + expect(Group.endpoints.getUser).type.toBe() + expect(Group.endpoints.createUser).type.toBe() + expect(Group.endpoints).type.not.toHaveProperty("deleteUser") + }) + + it("preserves endpoint types by identifier after group transformations", () => { + class M extends HttpApiMiddleware.Service()("M") {} + + const GetUser = HttpApiEndpoint.get("getUser", "/users/:id", { + params: { + id: Schema.String + } + }) + const CreateUser = HttpApiEndpoint.post("createUser", "/users") + const Group = HttpApiGroup.make("users").add(GetUser, CreateUser) + + const Prefixed = Group.prefix("/v1") + const WithMiddleware = Group.middleware(M) + + expect(Prefixed.endpoints.getUser).type.toBe>() + expect(Prefixed.endpoints.createUser).type.toBe>() + expect(WithMiddleware.endpoints.getUser).type.toBe>() + expect(WithMiddleware.endpoints.createUser).type.toBe>() + }) + + it("preserves class-like endpoints by identifier", () => { + class GetUser extends HttpApiEndpoint.get("getUser", "/users/:id", { + params: { + id: Schema.String + }, + success: Schema.Struct({ id: Schema.String }) + }) {} + + const Group = HttpApiGroup.make("users").add(GetUser) + + expect(Group.endpoints.getUser).type.toBe() + expect>().type.toBe() + }) + }) + + describe("Endpoints", () => { + it("extracts the endpoint union from one group", () => { + const GetUser = HttpApiEndpoint.get("getUser", "/users/:id") + const CreateUser = HttpApiEndpoint.post("createUser", "/users") + const Group = HttpApiGroup.make("users").add(GetUser, CreateUser) + + expect>().type.toBe() + }) + + it("distributes over groups with disjoint endpoint identifiers", () => { + const GetUser = HttpApiEndpoint.get("getUser", "/users/:id") + const GetPost = HttpApiEndpoint.get("getPost", "/posts/:id") + const Users = HttpApiGroup.make("users").add(GetUser) + const Posts = HttpApiGroup.make("posts").add(GetPost) + + type Groups = typeof Users | typeof Posts + + expect>().type.toBe() + }) + + it("preserves endpoints with the same identifier across groups", () => { + const GetUser = HttpApiEndpoint.get("get", "/users/:id", { + success: Schema.Struct({ userId: Schema.String }) + }) + const GetPost = HttpApiEndpoint.get("get", "/posts/:id", { + success: Schema.Struct({ postId: Schema.String }) + }) + const Users = HttpApiGroup.make("users").add(GetUser) + const Posts = HttpApiGroup.make("posts").add(GetPost) + + type Groups = typeof Users | typeof Posts + + expect>().type.toBe() + }) + + it("returns never for an unknown group identifier", () => { + const GetUser = HttpApiEndpoint.get("getUser", "/users/:id") + const Group = HttpApiGroup.make("users").add(GetUser) + type UnknownGroup = HttpApiGroup.WithIdentifier + + expect>().type.toBe() + }) + }) +}) diff --git a/repos/effect/packages/effect/typetest/unstable/httpapi/HttpApiMiddleware.tst.ts b/repos/effect/packages/effect/typetest/unstable/httpapi/HttpApiMiddleware.tst.ts index 888525300c..179f4bdbc7 100644 --- a/repos/effect/packages/effect/typetest/unstable/httpapi/HttpApiMiddleware.tst.ts +++ b/repos/effect/packages/effect/typetest/unstable/httpapi/HttpApiMiddleware.tst.ts @@ -1,4 +1,4 @@ -import { Schema } from "effect" +import { Context, Schema } from "effect" import { HttpApiEndpoint, HttpApiError, @@ -17,7 +17,7 @@ describe("HttpApiMiddleware", () => { expect>().type.toBe() }) - it("error", () => { + it("derives endpoint errors from an error schema", () => { class M extends HttpApiMiddleware.Service()("Http/Logger", { error: Schema.String }) {} @@ -30,7 +30,7 @@ describe("HttpApiMiddleware", () => { }) it("preserves error services for status annotations used with pipe", () => { - class NotFound extends Schema.TaggedErrorClass()("NotFound", {}) {} + class NotFound extends Schema.TaggedError()("NotFound", {}) {} class M extends HttpApiMiddleware.Service()("Http/Logger", { error: NotFound.pipe(HttpApiSchema.status(404)) }) {} @@ -39,7 +39,7 @@ describe("HttpApiMiddleware", () => { expect>().type.toBe() }) - it("security", () => { + it("tracks security without adding middleware errors", () => { class M extends HttpApiMiddleware.Service()("M", { security: { cookie: HttpApiSecurity.apiKey({ @@ -56,7 +56,7 @@ describe("HttpApiMiddleware", () => { expect(M.security).type.toBe<{ readonly cookie: HttpApiSecurity.ApiKey }>() }) - it("error + security", () => { + it("tracks error and security metadata together", () => { class M extends HttpApiMiddleware.Service()("Http/Logger", { error: Schema.String, security: { @@ -74,7 +74,7 @@ describe("HttpApiMiddleware", () => { expect(M.security).type.toBe<{ readonly cookie: HttpApiSecurity.ApiKey }>() }) - it("error array", () => { + it("derives an endpoint error union from an error schema array", () => { class M extends HttpApiMiddleware.Service()("Http/Auth", { error: [HttpApiError.UnauthorizedNoContent, HttpApiError.ForbiddenNoContent] }) {} @@ -86,5 +86,36 @@ describe("HttpApiMiddleware", () => { HttpApiError.Unauthorized | HttpApiError.Forbidden >() }) + + it("tracks endpoint middleware services", () => { + class Token extends Context.Service()("Token") {} + + class CurrentUser extends Context.Service()("CurrentUser") {} + + class NeedsUser extends HttpApiMiddleware.Service()("NeedsUser") {} + + class Auth extends HttpApiMiddleware.Service()("Auth") {} + + const endpoint = HttpApiEndpoint.get("a", "/a", { + success: Schema.String + }) + + const needsUser = endpoint.middleware(NeedsUser) + const authenticated = needsUser.middleware(Auth) + + expect>().type.toBe() + expect>().type.toBe() + expect>().type.toBe() + expect>().type.toBe() + }) }) }) diff --git a/repos/effect/packages/effect/typetest/unstable/httpapi/HttpApiSchema.tst.ts b/repos/effect/packages/effect/typetest/unstable/httpapi/HttpApiSchema.tst.ts index e23e02dcf2..c60f81eb3e 100644 --- a/repos/effect/packages/effect/typetest/unstable/httpapi/HttpApiSchema.tst.ts +++ b/repos/effect/packages/effect/typetest/unstable/httpapi/HttpApiSchema.tst.ts @@ -6,7 +6,7 @@ import { describe, expect, it } from "tstyche" describe("HttpApiSchema", () => { describe("status", () => { it("preserves schema services when used with pipe", () => { - class NotFound extends Schema.TaggedErrorClass()("NotFound", {}) {} + class NotFound extends Schema.TaggedError()("NotFound", {}) {} const schema = NotFound.pipe(HttpApiSchema.status(404)) @@ -117,6 +117,81 @@ describe("HttpApiSchema", () => { }) }) + describe("WithHeaders", () => { + it("preserves the inner schema and headers schema types", () => { + const Headers = Schema.Struct({ "x-total-count": Schema.FiniteFromString }) + const schema = HttpApiSchema.WithHeaders(Schema.String, Headers) + + expect(schema).type.toBe>() + expect(schema.schema).type.toBe() + expect(schema.headers).type.toBe() + expect().type.toBe< + HttpApiSchema.withHeaders + >() + }) + + it("supports a fields shorthand for headers", () => { + const schema = HttpApiSchema.WithHeaders(Schema.String, { + "x-total-count": Schema.FiniteFromString + }) + + expect(schema).type.toBe< + HttpApiSchema.WithHeaders> + >() + }) + + it("preserves the wrapper type when annotated with status", () => { + const Headers = Schema.Struct({ "x-total-count": Schema.FiniteFromString }) + const schema = HttpApiSchema.WithHeaders(Schema.String, Headers).pipe(HttpApiSchema.status(201)) + + expect(schema).type.toBe>() + }) + + it("preserves schema services", () => { + const Headers = Schema.Struct({ "x-total-count": Schema.FiniteFromString }) + const schema = HttpApiSchema.WithHeaders(Schema.String, Headers) + + expect().type.toBe() + expect().type.toBe() + }) + }) + + describe("withHeaders", () => { + it("constructs a branded value", () => { + const value = HttpApiSchema.withHeaders({ body: "a", headers: { "x-a": "1" } }) + + expect(value).type.toBe>() + expect(value.body).type.toBe() + }) + }) + + describe("encodeToWithHeaders", () => { + it("keeps the source Type and encodes to a body and headers pair", () => { + class UserNotFound extends Schema.TaggedError()("UserNotFound", { + userId: Schema.Int + }) {} + + const schema = UserNotFound.pipe( + HttpApiSchema.encodeToWithHeaders({ + body: HttpApiSchema.Empty(404), + headers: { "x-user-id": Schema.Int } + }, { + decode: ({ headers }) => new UserNotFound({ userId: headers["x-user-id"] }), + encode: (error) => ({ + headers: { "x-user-id": error.userId }, + body: undefined + }) + }) + ) + + expect().type.toBe() + expect().type.toBeAssignableTo<{ + readonly body: void + readonly headers: { readonly "x-user-id": number } + }>() + }) + }) + describe("StreamUint8Array", () => { it("constructs the stream schema", () => { const stream = HttpApiSchema.StreamUint8Array() diff --git a/repos/effect/packages/effect/typetest/unstable/httpapi/OpenApiRepresentation.tst.ts b/repos/effect/packages/effect/typetest/unstable/httpapi/OpenApiRepresentation.tst.ts new file mode 100644 index 0000000000..8e7d92656f --- /dev/null +++ b/repos/effect/packages/effect/typetest/unstable/httpapi/OpenApiRepresentation.tst.ts @@ -0,0 +1,12 @@ +import { HttpApi, HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi" +import { describe, expect, it } from "tstyche" + +describe("OpenApi representation consumer", () => { + it("keeps the synchronous fromApi signature", () => { + const Api = HttpApi.make("Api").add( + HttpApiGroup.make("test").add(HttpApiEndpoint.get("health", "/health")) + ) + + expect(OpenApi.fromApi(Api)).type.toBe() + }) +}) diff --git a/repos/effect/packages/effect/typetest/unstable/reactivity/Atom.tst.ts b/repos/effect/packages/effect/typetest/unstable/reactivity/Atom.tst.ts new file mode 100644 index 0000000000..db8f043c94 --- /dev/null +++ b/repos/effect/packages/effect/typetest/unstable/reactivity/Atom.tst.ts @@ -0,0 +1,23 @@ +import { Layer } from "effect" +import { Atom } from "effect/unstable/reactivity" +import { describe, expect, it } from "tstyche" + +describe("Atom", () => { + describe("context", () => { + it("returns a registry runtime factory by default", () => { + expect(Atom.context()).type.toBe() + }) + + it("returns a registry runtime factory for an atom-backed memo map", () => { + const memoMap = Atom.make(() => Layer.makeMemoMapUnsafe()) + + expect(Atom.context({ memoMap })).type.toBe() + }) + + it("returns a shared runtime factory for a concrete memo map", () => { + const memoMap = Layer.makeMemoMapUnsafe() + + expect(Atom.context({ memoMap })).type.toBe() + }) + }) +}) diff --git a/repos/effect/packages/effect/typetest/unstable/reactivity/AtomHttpApi.tst.ts b/repos/effect/packages/effect/typetest/unstable/reactivity/AtomHttpApi.tst.ts index a6e0506a46..510b0086de 100644 --- a/repos/effect/packages/effect/typetest/unstable/reactivity/AtomHttpApi.tst.ts +++ b/repos/effect/packages/effect/typetest/unstable/reactivity/AtomHttpApi.tst.ts @@ -4,15 +4,15 @@ import { HttpApi, HttpApiEndpoint, HttpApiGroup, HttpApiMiddleware } from "effec import { type Atom, AtomHttpApi } from "effect/unstable/reactivity" import { describe, expect, it } from "tstyche" -class EndpointError extends Schema.ErrorClass("EndpointError")({ +class EndpointError extends Schema.Error("EndpointError")({ _tag: Schema.tag("EndpointError") }) {} -class MiddlewareError extends Schema.ErrorClass("MiddlewareError")({ +class MiddlewareError extends Schema.Error("MiddlewareError")({ _tag: Schema.tag("MiddlewareError") }) {} -class MiddlewareClientError extends Schema.ErrorClass("MiddlewareClientError")({ +class MiddlewareClientError extends Schema.Error("MiddlewareClientError")({ _tag: Schema.tag("MiddlewareClientError") }) {} diff --git a/repos/effect/packages/effect/vitest.config.ts b/repos/effect/packages/effect/vitest.config.ts deleted file mode 100644 index 07afe8d381..0000000000 --- a/repos/effect/packages/effect/vitest.config.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { mergeConfig } from "vitest/config" -import shared from "../../vitest.shared.ts" - -const isDeno = process.versions.deno !== undefined - -export default mergeConfig(shared, { - test: { - // @see https://github.com/denoland/deno/issues/23882 - exclude: (isDeno ? ["test/cluster/**"] : []) - } -}) diff --git a/repos/effect/packages/opentelemetry/CHANGELOG.md b/repos/effect/packages/opentelemetry/CHANGELOG.md index 6d0acba8c2..aa9d23f8d0 100644 --- a/repos/effect/packages/opentelemetry/CHANGELOG.md +++ b/repos/effect/packages/opentelemetry/CHANGELOG.md @@ -1,5 +1,145 @@ # @effect/opentelemetry +## 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 + +- [#7112](https://github.com/Effect-TS/effect/pull/7112) [`14278e7`](https://github.com/Effect-TS/effect/commit/14278e7d6213d5752111ee4b2ffdf514e600b370) Thanks @fubhy! - Bound Node tracer provider shutdown by the configured `shutdownTimeout`. + +- [#7118](https://github.com/Effect-TS/effect/pull/7118) [`ac71ede`](https://github.com/Effect-TS/effect/commit/ac71ede1682a5548c6144647556bad95b351320c) Thanks @fubhy! - Prevent log annotations from overwriting active span correlation identifiers. +- 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 + +- [#7064](https://github.com/Effect-TS/effect/pull/7064) [`abc7355`](https://github.com/Effect-TS/effect/commit/abc73555eaa345c11e55100cdacf7fdf5fbf8f7e) Thanks @fubhy! - Ensure Web and Node tracer providers shut down when flushing fails during layer release. + +- [#7046](https://github.com/Effect-TS/effect/pull/7046) [`3159d15`](https://github.com/Effect-TS/effect/commit/3159d15040ac8700560f0f4791bbd95beb30d5d3) Thanks @fubhy! - Ensure logger providers shut down when flushing fails. + +- [#7069](https://github.com/Effect-TS/effect/pull/7069) [`79512bb`](https://github.com/Effect-TS/effect/commit/79512bb207df346310264f046bc4315e9543568d) Thanks @fubhy! - Fix wrapped spans treating non-error OpenTelemetry statuses as errors. +- 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 + +- [#6904](https://github.com/Effect-TS/effect/pull/6904) [`0d83916`](https://github.com/Effect-TS/effect/commit/0d83916b6604c818efd645601cfb69bd48a9879f) Thanks @fubhy! - Isolate delta metric baselines for each registered metric reader + +- [#6902](https://github.com/Effect-TS/effect/pull/6902) [`ab0859b`](https://github.com/Effect-TS/effect/commit/ab0859b97420aaaed5c00b04f12d7bdbba004837) Thanks @fubhy! - Preserve trace state and locality when adapting active OpenTelemetry parent contexts. + +- [#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 + +- [#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 + +- 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 + +- [#2511](https://github.com/Effect-TS/effect-smol/pull/2511) [`ceaffb8`](https://github.com/Effect-TS/effect-smol/commit/ceaffb8e532d77a528eef0c888b9e59683514a25) Thanks @alvarosevilla95! - prefix opentelemetry modules to avoid collisions + +- [#2511](https://github.com/Effect-TS/effect-smol/pull/2511) [`ceaffb8`](https://github.com/Effect-TS/effect-smol/commit/ceaffb8e532d77a528eef0c888b9e59683514a25) Thanks @alvarosevilla95! - Add `includeCauseInStack` option to `Cause.prettyErrors` + +- Updated dependencies [[`00652fe`](https://github.com/Effect-TS/effect-smol/commit/00652fe95c18f87208e91343eb8bf218faa2f677), [`6c58167`](https://github.com/Effect-TS/effect-smol/commit/6c5816746eaf91d2a3c7c899c5720809fa230ae3), [`2bc5415`](https://github.com/Effect-TS/effect-smol/commit/2bc541501a7ef89e542d7cb98e96beb53cd205cc), [`e11cccc`](https://github.com/Effect-TS/effect-smol/commit/e11cccc7d5fe631abccc7d6e3bd296938de0fa2e), [`ba7e77e`](https://github.com/Effect-TS/effect-smol/commit/ba7e77e046b8641a3a4e9750bb88ca4a1d063d3f), [`5713ee7`](https://github.com/Effect-TS/effect-smol/commit/5713ee7edbc3054efde407b2286bbfd45bbc6e1c)]: + - effect@4.0.0-beta.93 + +## 4.0.0-beta.92 + +### Patch Changes + +- Updated dependencies [[`affdc13`](https://github.com/Effect-TS/effect-smol/commit/affdc139045cc325dce321a84a580fdc1b2da7b9)]: + - effect@4.0.0-beta.92 + +## 4.0.0-beta.91 + +### Patch Changes + +- Updated dependencies [[`b135b25`](https://github.com/Effect-TS/effect-smol/commit/b135b2517fca9e7839734ace3699a7dfa75b9075), [`aaa21a3`](https://github.com/Effect-TS/effect-smol/commit/aaa21a369a171c600db294f2a4f640583043e150), [`3475ee6`](https://github.com/Effect-TS/effect-smol/commit/3475ee6c2bda6b05c6d7a12ce30c8bb840b5b1a6)]: + - effect@4.0.0-beta.91 + ## 4.0.0-beta.90 ### Patch Changes diff --git a/repos/effect/packages/opentelemetry/docgen.json b/repos/effect/packages/opentelemetry/docgen.json deleted file mode 100644 index e572b32278..0000000000 --- a/repos/effect/packages/opentelemetry/docgen.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "$schema": "../../node_modules/@effect/docgen/schema.json", - "srcLink": "https://github.com/Effect-TS/effect/tree/main/packages/opentelemetry/src/", - "exclude": ["src/internal/**/*.ts"], - "tscExecutable": "tsgo", - "examplesCompilerOptions": { - "noEmit": true, - "strict": true, - "skipLibCheck": true, - "moduleResolution": "Bundler", - "module": "ES2022", - "target": "ES2022", - "lib": ["ES2022", "DOM", "DOM.Iterable"], - "rewriteRelativeImportExtensions": true, - "allowImportingTsExtensions": true, - "paths": { - "effect": ["../../../effect/src/index.js"], - "effect/*": ["../../../effect/src/*.js"] - }, - "plugins": [ - { - "name": "@effect/language-service", - "includeSuggestionsInTsc": false, - "diagnosticSeverity": { - "globalErrorInEffectFailure": "off" - } - } - ] - } -} diff --git a/repos/effect/packages/opentelemetry/package.json b/repos/effect/packages/opentelemetry/package.json index 558aec4258..ff8468d394 100644 --- a/repos/effect/packages/opentelemetry/package.json +++ b/repos/effect/packages/opentelemetry/package.json @@ -1,17 +1,17 @@ { "name": "@effect/opentelemetry", "type": "module", - "version": "4.0.0-beta.90", + "version": "4.0.0-beta.107", "license": "MIT", "description": "OpenTelemetry integration for Effect", "homepage": "https://effect.website", "repository": { "type": "git", - "url": "https://github.com/Effect-TS/effect-smol.git", + "url": "https://github.com/Effect-TS/effect.git", "directory": "packages/opentelemetry" }, "bugs": { - "url": "https://github.com/Effect-TS/effect-smol/issues" + "url": "https://github.com/Effect-TS/effect/issues" }, "tags": [ "opentelemetry", @@ -40,6 +40,7 @@ ".": "./src/index.ts", "./*": "./src/*.ts", "./internal/*": null, + "./index": null, "./*/index": null }, "files": [ @@ -47,7 +48,10 @@ "dist/**/*.js", "dist/**/*.js.map", "dist/**/*.d.ts", - "dist/**/*.d.ts.map" + "dist/**/*.d.ts.map", + "AGENTS.md", + "CLAUDE.md", + "ai-docs/**/*" ], "publishConfig": { "access": "public", @@ -57,28 +61,26 @@ ".": "./dist/index.js", "./*": "./dist/*.js", "./internal/*": null, + "./index": null, "./*/index": null } }, "scripts": { "codegen": "effect-utils codegen", "build": "tsc -b tsconfig.json && pnpm babel", - "build:tsgo": "tsgo -b tsconfig.json && pnpm babel", "babel": "babel dist --plugins annotate-pure-calls --out-dir dist --source-maps", - "check": "tsc -b tsconfig.json", - "test": "vitest", - "coverage": "vitest --coverage" + "check": "tsc -b tsconfig.json" }, "peerDependencies": { - "@opentelemetry/api": "^1.9", + "@opentelemetry/api": ">=1.9.0 <2.0.0", "@opentelemetry/api-logs": ">=0.203.0 <0.300.0", - "@opentelemetry/resources": "^2.0.0", + "@opentelemetry/resources": ">=2.0.0 <3.0.0", "@opentelemetry/sdk-logs": ">=0.203.0 <0.300.0", - "@opentelemetry/sdk-metrics": "^2.0.0", - "@opentelemetry/sdk-trace-base": "^2.0.0", - "@opentelemetry/sdk-trace-node": "^2.0.0", - "@opentelemetry/sdk-trace-web": "^2.0.0", - "@opentelemetry/semantic-conventions": "^1.33.0", + "@opentelemetry/sdk-metrics": ">=2.0.0 <3.0.0", + "@opentelemetry/sdk-trace-base": ">=2.0.0 <3.0.0", + "@opentelemetry/sdk-trace-node": ">=2.0.0 <3.0.0", + "@opentelemetry/sdk-trace-web": ">=2.0.0 <3.0.0", + "@opentelemetry/semantic-conventions": ">=1.33.0 <2.0.0", "effect": "workspace:^" }, "peerDependenciesMeta": { @@ -109,18 +111,18 @@ }, "devDependencies": { "@opentelemetry/api": "^1.9.1", - "@opentelemetry/api-logs": "^0.217.0", - "@opentelemetry/context-async-hooks": "^2.7.1", - "@opentelemetry/exporter-metrics-otlp-http": "0.217.0", - "@opentelemetry/exporter-prometheus": "^0.217.0", - "@opentelemetry/exporter-trace-otlp-http": "^0.217.0", - "@opentelemetry/otlp-exporter-base": "^0.217.0", - "@opentelemetry/resources": "^2.7.1", - "@opentelemetry/sdk-logs": "^0.217.0", - "@opentelemetry/sdk-metrics": "^2.7.1", - "@opentelemetry/sdk-trace-base": "^2.7.1", - "@opentelemetry/sdk-trace-node": "^2.7.1", - "@opentelemetry/sdk-trace-web": "^2.7.1", - "@opentelemetry/semantic-conventions": "^1.40.0" + "@opentelemetry/api-logs": "^0.220.0", + "@opentelemetry/context-async-hooks": "^2.9.0", + "@opentelemetry/exporter-metrics-otlp-http": "0.220.0", + "@opentelemetry/exporter-prometheus": "^0.220.0", + "@opentelemetry/exporter-trace-otlp-http": "^0.220.0", + "@opentelemetry/otlp-exporter-base": "^0.220.0", + "@opentelemetry/resources": "^2.9.0", + "@opentelemetry/sdk-logs": "^0.220.0", + "@opentelemetry/sdk-metrics": "^2.9.0", + "@opentelemetry/sdk-trace-base": "^2.9.0", + "@opentelemetry/sdk-trace-node": "^2.9.0", + "@opentelemetry/sdk-trace-web": "^2.9.0", + "@opentelemetry/semantic-conventions": "^1.42.0" } } diff --git a/repos/effect/packages/opentelemetry/src/Logger.ts b/repos/effect/packages/opentelemetry/src/Logger.ts deleted file mode 100644 index fac7038ec4..0000000000 --- a/repos/effect/packages/opentelemetry/src/Logger.ts +++ /dev/null @@ -1,193 +0,0 @@ -/** - * Connects Effect logging to the OpenTelemetry Logs SDK. - * - * This module turns Effect log events into OpenTelemetry log records. It maps - * Effect log levels to OpenTelemetry severity numbers, provides the - * `OtelLoggerProvider` service, creates an Effect `Logger` with `make`, and - * offers layers for installing that logger or creating a scoped SDK - * `LoggerProvider` from one or more `LogRecordProcessor`s. - * - * @since 4.0.0 - */ -import { SeverityNumber } from "@opentelemetry/api-logs" -import * as Otel from "@opentelemetry/sdk-logs" -import type { NonEmptyReadonlyArray } from "effect/Array" -import * as Arr from "effect/Array" -import * as Clock from "effect/Clock" -import * as Context from "effect/Context" -import type * as Duration from "effect/Duration" -import * as Effect from "effect/Effect" -import * as Layer from "effect/Layer" -import * as Logger from "effect/Logger" -import type * as LogLevel from "effect/LogLevel" -import * as Predicate from "effect/Predicate" -import * as References from "effect/References" -import * as Tracer from "effect/Tracer" -import { nanosToHrTime, unknownToAttributeValue } from "./internal/attributes.ts" -import { Resource } from "./Resource.ts" - -/** - * Context service containing the OpenTelemetry `LoggerProvider` used to emit Effect log records. - * - * @category services - * @since 4.0.0 - */ -export class OtelLoggerProvider extends Context.Service< - OtelLoggerProvider, - Otel.LoggerProvider ->()("@effect/opentelemetry/Logger/OtelLoggerProvider") {} - -/** - * Maps an Effect `LogLevel` to the corresponding OpenTelemetry `SeverityNumber`. - * - * **Details** - * - * OpenTelemetry log severity numbers are in the range `1` through `24`. This - * function maps from Effect's log levels instead of using - * `LogLevel.getOrdinal`, whose internal sort ordinals, such as the `Info` - * ordinal `20000`, fall outside the OpenTelemetry logs data model and can be - * treated as `UNSPECIFIED` by validating backends. - * - * @category converting - * @since 4.0.0 - */ -export const logLevelToSeverityNumber = (level: LogLevel.LogLevel): SeverityNumber => { - switch (level) { - case "Trace": - return SeverityNumber.TRACE - case "Debug": - return SeverityNumber.DEBUG - case "Info": - return SeverityNumber.INFO - case "Warn": - return SeverityNumber.WARN - case "Error": - return SeverityNumber.ERROR - case "Fatal": - return SeverityNumber.FATAL - default: - return SeverityNumber.UNSPECIFIED - } -} - -/** - * Creates an Effect logger that emits log records through the configured OpenTelemetry logger provider. - * - * @category constructors - * @since 4.0.0 - */ -export const make: Effect.Effect< - Logger.Logger, - never, - OtelLoggerProvider -> = Effect.gen(function*() { - const loggerProvider = yield* OtelLoggerProvider - const clock = yield* Clock.Clock - const otelLogger = loggerProvider.getLogger("@effect/opentelemetry") - - return Logger.make((options) => { - const attributes: Record = { - fiberId: options.fiber.id - } - - const span = Context.getOrUndefined(options.fiber.context, Tracer.ParentSpan) - - if (Predicate.isNotUndefined(span)) { - attributes.spanId = span.spanId - attributes.traceId = span.traceId - } - - for (const [key, value] of Object.entries(options.fiber.getRef(References.CurrentLogAnnotations))) { - attributes[key] = unknownToAttributeValue(value) - } - const now = options.date.getTime() - for (const [label, startTime] of options.fiber.getRef(References.CurrentLogSpans)) { - attributes[`logSpan.${label}`] = `${now - startTime}ms` - } - - const message = Arr.ensure(options.message).map(unknownToAttributeValue) - const hrTime = nanosToHrTime(clock.currentTimeNanosUnsafe()) - otelLogger.emit({ - body: message.length === 1 ? message[0] : message, - severityText: options.logLevel, - severityNumber: logLevelToSeverityNumber(options.logLevel), - timestamp: hrTime, - observedTimestamp: hrTime, - attributes - }) - }) -}) - -/** - * Creates a layer that installs the OpenTelemetry-backed Effect logger, merging with existing loggers by default. - * - * **When to use** - * - * Use to install the OpenTelemetry-backed Effect logger in an application that - * has an `OtelLoggerProvider`, so standard Effect logging emits OpenTelemetry - * log records. - * - * **Details** - * - * The layer installs the logger created by `make`. `mergeWithExisting` defaults - * to `true`; set it to `false` to replace the current logger set. - * - * @see {@link make} for constructing the logger directly - * @see {@link layerLoggerProvider} for creating the required logger provider - * - * @category layers - * @since 4.0.0 - */ -export const layer = (options: { - /** - * Whether to merge the OpenTelemetry logger with existing loggers. - * - * **Details** - * - * If set to `true`, the OpenTelemetry logger will be merged with existing - * loggers in the application. - * - * If set to `false`, the OpenTelemetry logger will replace all existing - * loggers in the application. - * - * Defaults to `true`. - */ - readonly mergeWithExisting?: boolean | undefined -}): Layer.Layer => - Logger.layer([make], { - mergeWithExisting: options.mergeWithExisting ?? true - }) - -/** - * Creates a scoped OpenTelemetry logger provider from one or more log record processors, using the current `Resource` and flushing and shutting down the provider when the layer is released. - * - * @category layers - * @since 4.0.0 - */ -export const layerLoggerProvider = ( - processor: Otel.LogRecordProcessor | NonEmptyReadonlyArray, - config?: Omit & { - readonly shutdownTimeout?: Duration.Input | undefined - } -): Layer.Layer => - Layer.effect( - OtelLoggerProvider, - Effect.gen(function*() { - const resource = yield* Resource - return yield* Effect.acquireRelease( - Effect.sync(() => - new Otel.LoggerProvider({ - ...(config ?? undefined), - processors: Arr.ensure(processor), - resource - }) - ), - (provider) => - Effect.promise(() => provider.forceFlush().then(() => provider.shutdown())).pipe( - Effect.ignore, - Effect.interruptible, - Effect.timeoutOption(config?.shutdownTimeout ?? 3000) - ) - ) - }) - ) diff --git a/repos/effect/packages/opentelemetry/src/Metrics.ts b/repos/effect/packages/opentelemetry/src/Metrics.ts deleted file mode 100644 index fc8183eff6..0000000000 --- a/repos/effect/packages/opentelemetry/src/Metrics.ts +++ /dev/null @@ -1,138 +0,0 @@ -/** - * OpenTelemetry support for Effect metrics. - * - * This module exposes Effect metrics as an OpenTelemetry `MetricProducer`. - * `makeProducer` creates the producer, `registerProducer` attaches it to one - * or more SDK `MetricReader`s, and `layer` manages that setup in a scoped - * layer. The `TemporalityPreference` type lets callers choose cumulative or - * delta metric values. - * - * @since 4.0.0 - */ -import type { MetricProducer, MetricReader } from "@opentelemetry/sdk-metrics" -import type * as Arr from "effect/Array" -import type * as Duration from "effect/Duration" -import * as Effect from "effect/Effect" -import type { LazyArg } from "effect/Function" -import * as Layer from "effect/Layer" -import type * as Scope from "effect/Scope" -import { MetricProducerImpl } from "./internal/metrics.ts" -import { Resource } from "./Resource.ts" - -/** - * Determines how metric values relate to the time interval over which they - * are aggregated. - * - * **Details** - * - * `cumulative` reports total since a fixed start time. Each data point depends - * on all previous measurements. This is the default behavior. `delta` reports - * changes since the last export. Each interval is independent with no - * dependency on previous measurements. - * - * @category models - * @since 4.0.0 - */ -export type TemporalityPreference = "cumulative" | "delta" - -/** - * Creates an OpenTelemetry metric producer from Effect metrics. - * - * **When to use** - * - * Use when you need a `MetricProducer` for manually wiring Effect metrics into - * OpenTelemetry instead of using the scoped `layer` helper. - * - * **Details** - * - * Requires the current OpenTelemetry `Resource`, captures the current Effect - * context, and uses cumulative temporality by default. Pass `"delta"` for - * interval-based values. - * - * @see {@link registerProducer} for attaching a producer to metric readers - * @see {@link layer} for creating and registering a producer in a scoped layer - * - * @category constructors - * @since 4.0.0 - */ -export const makeProducer = (temporality?: TemporalityPreference): Effect.Effect => - Effect.gen(function*() { - const resource = yield* Resource - const services = yield* Effect.context() - return new MetricProducerImpl(resource, services, temporality) - }) - -/** - * Registers a metric producer with one or more metric readers. - * - * @category constructors - * @since 4.0.0 - */ -export const registerProducer = ( - self: MetricProducer, - metricReader: LazyArg>, - options?: { - readonly shutdownTimeout?: Duration.Input | undefined - } -): Effect.Effect, never, Scope.Scope> => - Effect.acquireRelease( - Effect.sync(() => { - const reader = metricReader() - const readers: Array = Array.isArray(reader) ? reader : [reader] as any - readers.forEach((reader) => reader.setMetricProducer(self)) - return readers - }), - (readers) => - Effect.promise(() => - Promise.all( - readers.map((reader) => reader.shutdown()) - ) - ).pipe( - Effect.ignore, - Effect.interruptible, - Effect.timeoutOption(options?.shutdownTimeout ?? 3000) - ) - ) - -/** - * Creates a Layer that registers a metric producer with metric readers. - * - * **Example** (Creating a metrics layer with temporality) - * - * ```ts - * import { Metrics } from "@effect/opentelemetry" - * import { PeriodicExportingMetricReader } from "@opentelemetry/sdk-metrics" - * import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-http" - * - * const metricExporter = new OTLPMetricExporter({ url: "" }) - * - * // Use delta temporality for backends like Datadog or Dynatrace - * const metricsLayer = Metrics.layer( - * () => new PeriodicExportingMetricReader({ - * exporter: metricExporter, - * exportIntervalMillis: 10000 - * }), - * { temporality: "delta" } - * ) - * - * // Use cumulative temporality for backends like Prometheus (default) - * const cumulativeLayer = Metrics.layer( - * () => new PeriodicExportingMetricReader({ exporter: metricExporter }), - * { temporality: "cumulative" } - * ) - * ``` - * - * @category layers - * @since 4.0.0 - */ -export const layer = ( - evaluate: LazyArg>, - options?: { - readonly shutdownTimeout?: Duration.Input | undefined - readonly temporality?: TemporalityPreference | undefined - } -): Layer.Layer => - Layer.effectDiscard(Effect.flatMap( - makeProducer(options?.temporality), - (producer) => registerProducer(producer, evaluate, options) - )) diff --git a/repos/effect/packages/opentelemetry/src/NodeSdk.ts b/repos/effect/packages/opentelemetry/src/NodeSdk.ts index e455d3d09a..cd48d71993 100644 --- a/repos/effect/packages/opentelemetry/src/NodeSdk.ts +++ b/repos/effect/packages/opentelemetry/src/NodeSdk.ts @@ -21,10 +21,10 @@ import * as Effect from "effect/Effect" import { constant, type LazyArg } from "effect/Function" import * as Layer from "effect/Layer" import { isNonEmpty } from "./internal/utilities.ts" -import * as Logger from "./Logger.ts" -import * as Metrics from "./Metrics.ts" +import * as Logger from "./OtelLogger.ts" +import * as Metrics from "./OtelMetrics.ts" +import * as Tracer from "./OtelTracer.ts" import * as Resource from "./Resource.ts" -import * as Tracer from "./Tracer.ts" /** * Configuration for the Node OpenTelemetry layer, including optional tracing, metrics, logging, resource, and shutdown settings. @@ -74,7 +74,7 @@ export const layerTracerProvider = ( return provider }), (provider) => - Effect.promise(() => provider.forceFlush().then(() => provider.shutdown())).pipe( + Effect.promise(() => provider.forceFlush().finally(() => provider.shutdown())).pipe( Effect.ignore, Effect.interruptible, Effect.timeoutOption(config?.shutdownTimeout ?? 3000) diff --git a/repos/effect/packages/opentelemetry/src/OtelLogger.ts b/repos/effect/packages/opentelemetry/src/OtelLogger.ts new file mode 100644 index 0000000000..8ccd6ee82b --- /dev/null +++ b/repos/effect/packages/opentelemetry/src/OtelLogger.ts @@ -0,0 +1,195 @@ +/** + * Connects Effect logging to the OpenTelemetry Logs SDK. + * + * This module turns Effect log events into OpenTelemetry log records. It maps + * Effect log levels to OpenTelemetry severity numbers, provides the + * `OtelLoggerProvider` service, creates an Effect `Logger` with `make`, and + * offers layers for installing that logger or creating a scoped SDK + * `LoggerProvider` from one or more `LogRecordProcessor`s. + * + * @since 4.0.0 + */ +import { SeverityNumber } from "@opentelemetry/api-logs" +import * as Otel from "@opentelemetry/sdk-logs" +import type { NonEmptyReadonlyArray } from "effect/Array" +import * as Arr from "effect/Array" +import * as Clock from "effect/Clock" +import * as Context from "effect/Context" +import type * as Duration from "effect/Duration" +import * as Effect from "effect/Effect" +import * as Layer from "effect/Layer" +import * as Logger from "effect/Logger" +import type * as LogLevel from "effect/LogLevel" +import * as Predicate from "effect/Predicate" +import * as Rec from "effect/Record" +import * as References from "effect/References" +import * as Tracer from "effect/Tracer" +import { nanosToHrTime, unknownToAttributeValue } from "./internal/attributes.ts" +import { Resource } from "./Resource.ts" + +/** + * Context service containing the OpenTelemetry `LoggerProvider` used to emit Effect log records. + * + * @category services + * @since 4.0.0 + */ +export class OtelLoggerProvider extends Context.Service< + OtelLoggerProvider, + Otel.LoggerProvider +>()("@effect/opentelemetry/Logger/OtelLoggerProvider") {} + +/** + * Maps an Effect `LogLevel` to the corresponding OpenTelemetry `SeverityNumber`. + * + * **Details** + * + * OpenTelemetry log severity numbers are in the range `1` through `24`. This + * function maps from Effect's log levels instead of using + * `LogLevel.getOrdinal`, whose internal sort ordinals, such as the `Info` + * ordinal `20000`, fall outside the OpenTelemetry logs data model and can be + * treated as `UNSPECIFIED` by validating backends. + * + * @category converting + * @since 4.0.0 + */ +export const logLevelToSeverityNumber = (level: LogLevel.LogLevel): SeverityNumber => { + switch (level) { + case "Trace": + return SeverityNumber.TRACE + case "Debug": + return SeverityNumber.DEBUG + case "Info": + return SeverityNumber.INFO + case "Warn": + return SeverityNumber.WARN + case "Error": + return SeverityNumber.ERROR + case "Fatal": + return SeverityNumber.FATAL + default: + return SeverityNumber.UNSPECIFIED + } +} + +/** + * Creates an Effect logger that emits log records through the configured OpenTelemetry logger provider. + * + * @category constructors + * @since 4.0.0 + */ +export const make: Effect.Effect< + Logger.Logger, + never, + OtelLoggerProvider +> = Effect.gen(function*() { + const loggerProvider = yield* OtelLoggerProvider + const clock = yield* Clock.Clock + const otelLogger = loggerProvider.getLogger("@effect/opentelemetry") + + return Logger.make((options) => { + const attributes: Record = { + fiberId: options.fiber.id + } + + for (const [key, value] of Object.entries(options.fiber.getRef(References.CurrentLogAnnotations))) { + Rec.assignProperty(attributes, key, unknownToAttributeValue(value)) + } + + const span = Context.getOrUndefined(options.fiber.context, Tracer.ParentSpan) + + if (Predicate.isNotUndefined(span)) { + attributes.spanId = span.spanId + attributes.traceId = span.traceId + } + + const now = options.date.getTime() + for (const [label, startTime] of options.fiber.getRef(References.CurrentLogSpans)) { + attributes[`logSpan.${label}`] = `${now - startTime}ms` + } + + const message = Arr.ensure(options.message).map(unknownToAttributeValue) + const hrTime = nanosToHrTime(clock.currentTimeNanosUnsafe()) + otelLogger.emit({ + body: message.length === 1 ? message[0] : message, + severityText: options.logLevel, + severityNumber: logLevelToSeverityNumber(options.logLevel), + timestamp: hrTime, + observedTimestamp: hrTime, + attributes + }) + }) +}) + +/** + * Creates a layer that installs the OpenTelemetry-backed Effect logger, merging with existing loggers by default. + * + * **When to use** + * + * Use to install the OpenTelemetry-backed Effect logger in an application that + * has an `OtelLoggerProvider`, so standard Effect logging emits OpenTelemetry + * log records. + * + * **Details** + * + * The layer installs the logger created by `make`. `mergeWithExisting` defaults + * to `true`; set it to `false` to replace the current logger set. + * + * @see {@link make} for constructing the logger directly + * @see {@link layerLoggerProvider} for creating the required logger provider + * + * @category layers + * @since 4.0.0 + */ +export const layer = (options: { + /** + * Whether to merge the OpenTelemetry logger with existing loggers. + * + * **Details** + * + * If set to `true`, the OpenTelemetry logger will be merged with existing + * loggers in the application. + * + * If set to `false`, the OpenTelemetry logger will replace all existing + * loggers in the application. + * + * Defaults to `true`. + */ + readonly mergeWithExisting?: boolean | undefined +}): Layer.Layer => + Logger.layer([make], { + mergeWithExisting: options.mergeWithExisting ?? true + }) + +/** + * Creates a scoped OpenTelemetry logger provider from one or more log record processors, using the current `Resource` and flushing and shutting down the provider when the layer is released. + * + * @category layers + * @since 4.0.0 + */ +export const layerLoggerProvider = ( + processor: Otel.LogRecordProcessor | NonEmptyReadonlyArray, + config?: Omit & { + readonly shutdownTimeout?: Duration.Input | undefined + } +): Layer.Layer => + Layer.effect( + OtelLoggerProvider, + Effect.gen(function*() { + const resource = yield* Resource + return yield* Effect.acquireRelease( + Effect.sync(() => + new Otel.LoggerProvider({ + ...(config ?? undefined), + processors: Arr.ensure(processor), + resource + }) + ), + (provider) => + Effect.promise(() => provider.forceFlush().finally(() => provider.shutdown())).pipe( + Effect.ignore, + Effect.interruptible, + Effect.timeoutOption(config?.shutdownTimeout ?? 3000) + ) + ) + }) + ) diff --git a/repos/effect/packages/opentelemetry/src/OtelMetrics.ts b/repos/effect/packages/opentelemetry/src/OtelMetrics.ts new file mode 100644 index 0000000000..da76f6b7c6 --- /dev/null +++ b/repos/effect/packages/opentelemetry/src/OtelMetrics.ts @@ -0,0 +1,149 @@ +/** + * OpenTelemetry support for Effect metrics. + * + * This module exposes Effect metrics as an OpenTelemetry `MetricProducer`. + * `makeProducer` creates the producer, `registerProducer` attaches it to one + * or more SDK `MetricReader`s, and `layer` manages that setup in a scoped + * layer. The `TemporalityPreference` type lets callers choose cumulative or + * delta metric values. + * + * @since 4.0.0 + */ +import type { MetricProducer, MetricReader } from "@opentelemetry/sdk-metrics" +import type * as Arr from "effect/Array" +import type * as Duration from "effect/Duration" +import * as Effect from "effect/Effect" +import type { LazyArg } from "effect/Function" +import * as Layer from "effect/Layer" +import type * as Scope from "effect/Scope" +import { MetricProducerImpl } from "./internal/metrics.ts" +import { Resource } from "./Resource.ts" + +/** + * Determines how metric values relate to the time interval over which they + * are aggregated. + * + * **Details** + * + * `cumulative` reports total since a fixed start time. Each data point depends + * on all previous measurements. This is the default behavior. `delta` reports + * changes since the last export. Each interval is independent with no + * dependency on previous measurements. + * + * @category models + * @since 4.0.0 + */ +export type TemporalityPreference = "cumulative" | "delta" + +/** + * Creates an OpenTelemetry metric producer from Effect metrics. + * + * **When to use** + * + * Use when you need a `MetricProducer` for manually wiring Effect metrics into + * OpenTelemetry instead of using the scoped `layer` helper. + * + * **Details** + * + * Requires the current OpenTelemetry `Resource`, captures the current Effect + * context, and uses cumulative temporality by default. Pass `"delta"` for + * interval-based values. + * + * @see {@link registerProducer} for attaching a producer to metric readers + * @see {@link layer} for creating and registering a producer in a scoped layer + * + * @category constructors + * @since 4.0.0 + */ +export const makeProducer = (temporality?: TemporalityPreference): Effect.Effect => + Effect.gen(function*() { + const resource = yield* Resource + const services = yield* Effect.context() + return new MetricProducerImpl(resource, services, temporality) + }) + +/** + * Registers a metric producer with one or more metric readers. + * + * @category resource management + * @since 4.0.0 + */ +export const registerProducer = ( + self: MetricProducer, + metricReader: LazyArg>, + options?: { + readonly shutdownTimeout?: Duration.Input | undefined + } +): Effect.Effect, never, Scope.Scope> => + Effect.acquireRelease( + Effect.sync(() => { + const reader = metricReader() + const readers: Array = Array.isArray(reader) ? reader : [reader] as any + readers.forEach((reader) => reader.setMetricProducer(self instanceof MetricProducerImpl ? self.fork() : self)) + return readers + }), + (readers) => + Effect.promise(() => + Promise.all( + readers.map((reader) => reader.shutdown()) + ) + ).pipe( + Effect.ignore, + Effect.interruptible, + Effect.timeoutOption(options?.shutdownTimeout ?? 3000) + ) + ) + +/** + * Creates a Layer that registers a metric producer with metric readers. + * + * **Example** (Exporting delta metrics) + * + * ```ts import.meta.vitest + * import { OtelMetrics, Resource } from "@effect/opentelemetry" + * import { + * AggregationTemporality, + * InMemoryMetricExporter, + * PeriodicExportingMetricReader + * } from "@opentelemetry/sdk-metrics" + * import { Effect, Layer, Metric } from "effect" + * + * const exporter = new InMemoryMetricExporter(AggregationTemporality.DELTA) + * const reader = new PeriodicExportingMetricReader({ + * exporter, + * exportIntervalMillis: 60_000 + * }) + * const metricsLayer = OtelMetrics.layer(() => reader, { temporality: "delta" }).pipe( + * Layer.provide(Resource.layerEmpty) + * ) + * + * const program = Effect.gen(function*() { + * yield* Metric.update(Metric.counter("docs.requests", { incremental: true }), 2) + * yield* Effect.promise(() => reader.forceFlush()) + * + * const metric = exporter.getMetrics()[0]?.scopeMetrics[0]?.metrics.find( + * (metric) => metric.descriptor.name === "docs.requests" + * ) + * return [metric?.descriptor.name, metric?.aggregationTemporality, metric?.dataPoints[0]?.value] as const + * }).pipe( + * Effect.provide(metricsLayer), + * Effect.provideService(Metric.MetricRegistry, new Map()) + * ) + * + * await Effect.runPromise(program) // => ["docs.requests", AggregationTemporality.DELTA, 2] + * ``` + * + * @category layers + * @since 4.0.0 + */ +export const layer = ( + evaluate: LazyArg>, + options?: { + readonly shutdownTimeout?: Duration.Input | undefined + readonly temporality?: TemporalityPreference | undefined + } +): Layer.Layer => + Layer.effectDiscard(Effect.flatMap( + makeProducer(options?.temporality), + (producer) => registerProducer(producer, evaluate, options) + )) diff --git a/repos/effect/packages/opentelemetry/src/OtelTracer.ts b/repos/effect/packages/opentelemetry/src/OtelTracer.ts new file mode 100644 index 0000000000..074916ecfc --- /dev/null +++ b/repos/effect/packages/opentelemetry/src/OtelTracer.ts @@ -0,0 +1,610 @@ +/** + * Bridges Effect tracing into OpenTelemetry by installing an Effect `Tracer` + * that creates OpenTelemetry spans, records attributes, events, links, errors, + * and status, and keeps OpenTelemetry context active while traced effects run. + * Use this module when an application already has an OpenTelemetry + * `TracerProvider`, or when the Node and Web SDK layers should expose Effect + * spans to OTLP, console, or other OpenTelemetry-compatible exporters. + * + * The layer constructors wire Effect's tracer service to either the global + * OpenTelemetry tracer provider or an explicitly provided `OtelTracer`. This + * module does not create exporters or span processors by itself, so spans are + * exported only when the provider has been configured by the application or by + * the Node/Web SDK layers. Parentage is taken from Effect spans first and can + * also attach to the active OpenTelemetry context, while `makeExternalSpan` and + * `withSpanContext` are the entry points for continuing an incoming remote + * trace. Preserve `traceFlags` and `traceState` when building external spans; + * otherwise sampling defaults to sampled and trace state cannot be propagated. + * + * @since 4.0.0 + */ +import * as Otel from "@opentelemetry/api" +import * as OtelSemConv from "@opentelemetry/semantic-conventions" +import * as Cause from "effect/Cause" +import type * as Clock from "effect/Clock" +import * as Context from "effect/Context" +import * as Effect from "effect/Effect" +import * as Exit from "effect/Exit" +import { constTrue, dual } from "effect/Function" +import * as Layer from "effect/Layer" +import * as Option from "effect/Option" +import * as Predicate from "effect/Predicate" +import * as Tracer from "effect/Tracer" +import { nanosToHrTime, recordToAttributes, unknownToAttributeValue } from "./internal/attributes.ts" +import { Resource } from "./Resource.ts" + +// ============================================================================= +// Service Definitions +// ============================================================================= + +/** + * Context service containing the OpenTelemetry `Tracer` used to create spans for Effect tracing. + * + * @category services + * @since 4.0.0 + */ +export class OtelTracer extends Context.Service< + OtelTracer, + Otel.Tracer +>()("@effect/opentelemetry/Tracer") {} + +/** + * Context service containing the OpenTelemetry `TracerProvider` used to obtain tracers. + * + * @category services + * @since 4.0.0 + */ +export class OtelTracerProvider extends Context.Service< + OtelTracerProvider, + Otel.TracerProvider +>()("@effect/opentelemetry/Tracer/OtelTracerProvider") {} + +/** + * Context service containing OpenTelemetry trace flags used when constructing external span contexts. + * + * @category services + * @since 4.0.0 + */ +export class OtelTraceFlags extends Context.Service< + OtelTraceFlags, + Otel.TraceFlags +>()("@effect/opentelemetry/Tracer/OtelTraceFlags") {} + +/** + * Context service containing OpenTelemetry trace state used when constructing external span contexts. + * + * @category services + * @since 4.0.0 + */ +export class OtelTraceState extends Context.Service< + OtelTraceState, + Otel.TraceState +>()("@effect/opentelemetry/Tracer/OtelTraceState") {} + +// ============================================================================= +// Constructors +// ============================================================================= + +/** + * Creates an Effect `Tracer` implementation backed by the configured OpenTelemetry tracer. + * + * @category constructors + * @since 4.0.0 + */ +export const make: Effect.Effect = Effect.map( + Effect.service(OtelTracer), + (tracer) => + Tracer.make({ + span(options) { + return new OtelSpan( + Otel.context, + Otel.trace, + tracer, + options + ) + }, + context(primitive, fiber) { + const currentSpan = fiber.currentSpan + + if (currentSpan === undefined) { + return primitive["~effect/Effect/evaluate"](fiber) + } + + return Otel.context.with( + populateContext(Otel.context.active(), currentSpan), + () => primitive["~effect/Effect/evaluate"](fiber) + ) + } + }) +) + +/** + * Creates an Effect external span from an OpenTelemetry span context, preserving trace flags and trace state when provided. + * + * @category constructors + * @since 4.0.0 + */ +export const makeExternalSpan = (options: { + readonly traceId: string + readonly spanId: string + readonly traceFlags?: number | undefined + readonly traceState?: string | Otel.TraceState | undefined +}): Tracer.ExternalSpan => { + let annotations = Context.empty() + if (options.traceFlags !== undefined) { + annotations = Context.add(annotations, OtelTraceFlags, options.traceFlags) + } + + if (typeof options.traceState === "string") { + try { + annotations = Context.add(annotations, OtelTraceState, Otel.createTraceState(options.traceState)) + } catch { + // + } + } else if (options.traceState) { + annotations = Context.add(annotations, OtelTraceState, options.traceState) + } + + return { + _tag: "ExternalSpan", + traceId: options.traceId, + spanId: options.spanId, + sampled: Predicate.isNotUndefined(options.traceFlags) ? isSampled(options.traceFlags) : true, + annotations + } +} + +// ============================================================================= +// Layers +// ============================================================================= + +/** + * Layer that provides the current global OpenTelemetry tracer provider. + * + * @category layers + * @since 4.0.0 + */ +export const layerGlobalProvider: Layer.Layer = Layer.sync( + OtelTracerProvider, + () => Otel.trace.getTracerProvider() +) + +/** + * Layer that creates an OpenTelemetry tracer from the provided tracer provider and resource metadata. + * + * @category layers + * @since 4.0.0 + */ +export const layerTracer: Layer.Layer = Layer.effect( + OtelTracer, + Effect.gen(function*() { + const resource = yield* Resource + const provider = yield* OtelTracerProvider + return provider.getTracer( + resource.attributes[OtelSemConv.ATTR_SERVICE_NAME] as string, + resource.attributes[OtelSemConv.ATTR_SERVICE_VERSION] as string + ) + }) +) + +/** + * Layer that creates an OpenTelemetry tracer from the global tracer provider and the current resource. + * + * @category layers + * @since 4.0.0 + */ +export const layerGlobalTracer: Layer.Layer = layerTracer.pipe( + Layer.provide(layerGlobalProvider) +) + +/** + * Layer that installs an Effect tracer backed by the global OpenTelemetry tracer provider. + * + * @category layers + * @since 4.0.0 + */ +export const layerGlobal: Layer.Layer = Layer.effect(Tracer.Tracer, make).pipe( + Layer.provideMerge(layerGlobalTracer) +) + +/** + * Layer that installs the Effect tracer using an `OtelTracer` already provided in the environment. + * + * @category layers + * @since 4.0.0 + */ +export const layerWithoutOtelTracer: Layer.Layer = Layer.effect(Tracer.Tracer, make) + +/** + * Layer that creates an OpenTelemetry tracer from a provider and resource, then installs it as the Effect tracer. + * + * **When to use** + * + * Use when you already provide an `OtelTracerProvider` and a `Resource`, and + * want Effect spans backed by a tracer derived from them. + * + * @see {@link layerTracer} for creating only the OpenTelemetry tracer service + * @see {@link layerGlobal} for installing the Effect tracer from the global provider + * @see {@link layerWithoutOtelTracer} for installing an already-provided `OtelTracer` + * + * @category layers + * @since 4.0.0 + */ +export const layer: Layer.Layer = layerWithoutOtelTracer.pipe( + Layer.provideMerge(layerTracer) +) + +// ============================================================================= +// Utilities / Combinators +// ============================================================================= + +const bigint1e6 = BigInt(1_000_000) +const bigint1e9 = BigInt(1_000_000_000) + +/** + * Gets the current OpenTelemetry span. + * + * **Details** + * + * This accessor works with both the official OpenTelemetry API, such as + * `Tracer.layer` and `NodeSdk.layer`, and the lightweight OTLP module, such as + * `OtlpTracer.layer`. When using OTLP, the returned span is a wrapper that + * conforms to the OpenTelemetry `Span` interface. + * + * @category accessors + * @since 4.0.0 + */ +export const currentOtelSpan: Effect.Effect = Effect.clockWith((clock) => + Effect.map(Effect.currentSpan, (span) => + OtelSpanTypeId in span + ? (span as OtelSpan).span + : makeOtelSpan(span, clock)) +) + +const makeOtelSpan = (span: Tracer.Span, clock: Clock.Clock): Otel.Span => { + const spanContext: Otel.SpanContext = { + traceId: span.traceId, + spanId: span.spanId, + traceFlags: span.sampled ? Otel.TraceFlags.SAMPLED : Otel.TraceFlags.NONE, + isRemote: false + } + + let exit = Exit.void + + const self: Otel.Span = { + spanContext: () => spanContext, + setAttribute(key, value) { + span.attribute(key, value) + return self + }, + setAttributes(attributes) { + for (const [key, value] of Object.entries(attributes)) { + span.attribute(key, value) + } + return self + }, + addEvent(name) { + let attributes: Otel.Attributes | undefined = undefined + let startTime: Otel.TimeInput | undefined = undefined + if (arguments.length === 3) { + attributes = arguments[1] + startTime = arguments[2] + } else { + startTime = arguments[1] + } + span.event(name, convertOtelTimeInput(startTime, clock), attributes) + return self + }, + addLink(link) { + span.addLinks([{ + span: makeExternalSpan(link.context), + attributes: link.attributes ?? {} + }]) + return self + }, + addLinks(links) { + span.addLinks(links.map((link) => ({ + span: makeExternalSpan(link.context), + attributes: link.attributes ?? {} + }))) + return self + }, + setStatus(status) { + exit = status.code === Otel.SpanStatusCode.ERROR + ? Exit.die(status.message ?? "Unknown error") + : Exit.void + return self + }, + updateName: () => self, + end(endTime) { + const time = convertOtelTimeInput(endTime, clock) + span.end(time, exit) + return self + }, + isRecording: constTrue, + recordException(exception, timeInput) { + const time = convertOtelTimeInput(timeInput, clock) + const cause = Cause.fail(exception) + const error = Cause.prettyErrors(cause, { + includeCauseInStack: true + })[0] + span.event(error.message, time, { + "exception.type": error.name, + "exception.message": error.message, + "exception.stacktrace": error.stack ?? "" + }) + } + } + return self +} + +const convertOtelTimeInput = (input: Otel.TimeInput | undefined, clock: Clock.Clock): bigint => { + if (input === undefined) { + return clock.currentTimeNanosUnsafe() + } else if (typeof input === "number") { + return BigInt(Math.round(input * 1_000_000)) + } else if (input instanceof Date) { + return BigInt(input.getTime()) * bigint1e6 + } + const [seconds, nanos] = input + return BigInt(seconds) * bigint1e9 + BigInt(nanos) +} + +/** + * Sets an effect's parent span from the given OpenTelemetry `SpanContext`. + * + * **When to use** + * + * Use when you need an effect to continue a trace from a parent span context + * produced by OpenTelemetry instrumentation outside Effect. + * + * @category propagation + * @since 4.0.0 + */ +export const withSpanContext: { + ( + spanContext: Otel.SpanContext + ): ( + self: Effect.Effect + ) => Effect.Effect> + ( + self: Effect.Effect, + spanContext: Otel.SpanContext + ): Effect.Effect> +} = dual(2, ( + self: Effect.Effect, + spanContext: Otel.SpanContext +) => Effect.withParentSpan(self, makeExternalSpan(spanContext))) + +// ============================================================================= +// Internals +// ============================================================================= + +const OtelSpanTypeId = "~@effect/opentelemetry/Tracer/OtelSpan" + +const kindMap = { + "internal": Otel.SpanKind.INTERNAL, + "client": Otel.SpanKind.CLIENT, + "server": Otel.SpanKind.SERVER, + "producer": Otel.SpanKind.PRODUCER, + "consumer": Otel.SpanKind.CONSUMER +} + +/** @internal */ +export class OtelSpan implements Tracer.Span { + readonly [OtelSpanTypeId]: typeof OtelSpanTypeId + readonly _tag = "Span" + + readonly name: string + readonly kind: Tracer.SpanKind + readonly annotations: Context.Context + readonly links: Array + readonly span: Otel.Span + readonly spanId: string + readonly traceId: string + readonly attributes = new Map() + readonly sampled: boolean + readonly parent: Option.Option + status: Tracer.SpanStatus + + constructor( + contextApi: Otel.ContextAPI, + traceApi: Otel.TraceAPI, + tracer: Otel.Tracer, + options: Parameters[0] + ) { + this[OtelSpanTypeId] = OtelSpanTypeId + this.name = options.name + this.annotations = options.annotations + this.links = options.links + this.kind = options.kind + const active = contextApi.active() + this.parent = options.root !== true + ? Option.orElse(options.parent, () => getOtelParent(traceApi, active, options.annotations)) + : options.parent + this.span = tracer.startSpan( + options.name, + { + startTime: nanosToHrTime(options.startTime), + links: options.links.length > 0 + ? options.links.map((link) => ({ + context: makeSpanContext(link.span), + attributes: recordToAttributes(link.attributes) + })) + : undefined as any, + kind: kindMap[this.kind] + }, + Option.isSome(this.parent) ? + populateContext(active, this.parent.value, options.annotations) : + Otel.trace.deleteSpan(active) + ) + const spanContext = this.span.spanContext() + this.spanId = spanContext.spanId + this.traceId = spanContext.traceId + this.status = { + _tag: "Started", + startTime: options.startTime + } + this.sampled = isSampled(spanContext.traceFlags) + } + + attribute(key: string, value: unknown) { + this.span.setAttribute(key, unknownToAttributeValue(value)) + this.attributes.set(key, value) + } + + addLinks(links: ReadonlyArray): void { + // oxlint-disable-next-line no-restricted-syntax + this.links.push(...links) + this.span.addLinks(links.map((link) => ({ + context: makeSpanContext(link.span), + attributes: recordToAttributes(link.attributes) + }))) + } + + end(endTime: bigint, exit: Exit.Exit) { + const hrTime = nanosToHrTime(endTime) + this.status = { + _tag: "Ended", + endTime, + exit, + startTime: this.status.startTime + } + + if (exit._tag === "Success") { + this.span.setStatus({ code: Otel.SpanStatusCode.OK }) + } else { + if (Cause.hasInterruptsOnly(exit.cause)) { + this.span.setStatus({ + code: Otel.SpanStatusCode.OK, + message: Cause.pretty(exit.cause) + }) + this.span.setAttribute("span.label", "⚠︎ Interrupted") + this.span.setAttribute("status.interrupted", true) + } else { + const errors = Cause.prettyErrors(exit.cause, { + includeCauseInStack: true + }) + if (errors.length > 0) { + for (const error of errors) { + this.span.recordException(error, hrTime) + } + this.span.setStatus({ + code: Otel.SpanStatusCode.ERROR, + message: errors[0].message + }) + } else { + // empty cause means no error + this.span.setStatus({ code: Otel.SpanStatusCode.OK }) + } + } + } + this.span.end(hrTime) + } + + event(name: string, startTime: bigint, attributes?: Record) { + this.span.addEvent( + name, + attributes ? recordToAttributes(attributes) : undefined, + nanosToHrTime(startTime) + ) + } +} + +const isSampled = (traceFlags: Otel.TraceFlags): boolean => + (traceFlags & Otel.TraceFlags.SAMPLED) === Otel.TraceFlags.SAMPLED + +class OtelParentSpanContext extends Context.Service< + OtelParentSpanContext, + Otel.SpanContext +>()("@effect/opentelemetry/Tracer/OtelParentSpanContext") {} + +const getOtelParent = ( + tracer: Otel.TraceAPI, + context: Otel.Context, + annotations: Context.Context +): Option.Option => { + const otelParent = tracer.getSpanContext(context) + if (!otelParent) return Option.none() + return Option.some(Tracer.externalSpan({ + spanId: otelParent.spanId, + traceId: otelParent.traceId, + sampled: isSampled(otelParent.traceFlags), + annotations: Context.add(annotations, OtelParentSpanContext, otelParent) + })) +} + +const makeSpanContext = ( + span: Tracer.AnySpan, + annotations?: Context.Context +): Otel.SpanContext => { + const otelParent = Context.getOrUndefined(span.annotations, OtelParentSpanContext) + if (otelParent !== undefined) { + if (annotations === undefined) return otelParent + const traceFlags = extractTraceService(span, annotations, OtelTraceFlags) + const traceState = extractTraceService(span, annotations, OtelTraceState) + return { + ...otelParent, + traceFlags: traceFlags ?? otelParent.traceFlags, + traceState: traceState ?? otelParent.traceState! + } + } + const traceFlags = makeTraceFlags(span, annotations) + const traceState = makeTraceState(span, annotations)! + return ({ + spanId: span.spanId, + traceId: span.traceId, + isRemote: span._tag === "ExternalSpan", + traceFlags, + traceState + }) +} + +const makeTraceFlags = ( + span: Tracer.AnySpan, + annotations: Context.Context | undefined +): Otel.TraceFlags => { + let traceFlags: Otel.TraceFlags | undefined + if (Predicate.isNotUndefined(annotations)) { + traceFlags = extractTraceService(span, annotations, OtelTraceFlags) + if (Predicate.isUndefined(traceFlags)) { + traceFlags = Context.getOrUndefined(span.annotations, OtelTraceFlags) + } + } + return traceFlags ?? (span.sampled ? Otel.TraceFlags.SAMPLED : Otel.TraceFlags.NONE) +} + +const makeTraceState = ( + span: Tracer.AnySpan, + annotations: Context.Context | undefined +): Otel.TraceState | undefined => { + let traceState: Otel.TraceState | undefined + if (Predicate.isNotUndefined(annotations)) { + traceState = extractTraceService(span, annotations, OtelTraceState) + if (Predicate.isUndefined(traceState)) { + traceState = Context.getOrUndefined(span.annotations, OtelTraceState) + } + } + return traceState +} + +const extractTraceService = ( + parent: Tracer.AnySpan, + annotations: Context.Context, + service: Context.Service +) => { + const instance = Context.getOrUndefined(annotations, service) + if (Predicate.isNotUndefined(instance)) { + return instance + } + return Context.getOrUndefined(parent.annotations, service) +} + +const populateContext = ( + context: Otel.Context, + span: Tracer.AnySpan, + annotations?: Context.Context | undefined +): Otel.Context => + span instanceof OtelSpan ? + Otel.trace.setSpan(context, span.span) : + Otel.trace.setSpanContext(context, makeSpanContext(span, annotations)) diff --git a/repos/effect/packages/opentelemetry/src/Resource.ts b/repos/effect/packages/opentelemetry/src/Resource.ts index 9a4c758df1..0aa5e50892 100644 --- a/repos/effect/packages/opentelemetry/src/Resource.ts +++ b/repos/effect/packages/opentelemetry/src/Resource.ts @@ -18,6 +18,7 @@ import * as Config from "effect/Config" import * as Context from "effect/Context" import * as Effect from "effect/Effect" import * as Layer from "effect/Layer" +import * as Rec from "effect/Record" /** * Service tag for OpenTelemetry metadata attached to emitted telemetry. @@ -74,7 +75,7 @@ export const layer = (config: { * @see {@link layer} for creating a `Resource` layer from explicit metadata * @see {@link layerFromEnv} for merging attributes with OpenTelemetry environment variables * - * @category configuration + * @category converting * @since 4.0.0 */ export const configToAttributes = (options: { @@ -120,7 +121,7 @@ export const layerFromEnv = ( if (parts.length !== 2) { return acc } - acc[parts[0].trim()] = parts[1].trim() + Rec.assignProperty(acc, parts[0].trim(), parts[1].trim()) return acc }) }) @@ -128,10 +129,10 @@ export const layerFromEnv = ( if (serviceName._tag === "Some") { attributes[OtelSemConv.ATTR_SERVICE_NAME] = serviceName.value } - if (additionalAttributes) { - Object.assign(attributes, additionalAttributes) - } - return Resources.resourceFromAttributes(attributes) + return Resources.resourceFromAttributes({ + ...attributes, + ...additionalAttributes + }) }).pipe(Effect.orDie) ) diff --git a/repos/effect/packages/opentelemetry/src/Tracer.ts b/repos/effect/packages/opentelemetry/src/Tracer.ts deleted file mode 100644 index 1b1c4bab71..0000000000 --- a/repos/effect/packages/opentelemetry/src/Tracer.ts +++ /dev/null @@ -1,594 +0,0 @@ -/** - * Bridges Effect tracing into OpenTelemetry by installing an Effect `Tracer` - * that creates OpenTelemetry spans, records attributes, events, links, errors, - * and status, and keeps OpenTelemetry context active while traced effects run. - * Use this module when an application already has an OpenTelemetry - * `TracerProvider`, or when the Node and Web SDK layers should expose Effect - * spans to OTLP, console, or other OpenTelemetry-compatible exporters. - * - * The layer constructors wire Effect's tracer service to either the global - * OpenTelemetry tracer provider or an explicitly provided `OtelTracer`. This - * module does not create exporters or span processors by itself, so spans are - * exported only when the provider has been configured by the application or by - * the Node/Web SDK layers. Parentage is taken from Effect spans first and can - * also attach to the active OpenTelemetry context, while `makeExternalSpan` and - * `withSpanContext` are the entry points for continuing an incoming remote - * trace. Preserve `traceFlags` and `traceState` when building external spans; - * otherwise sampling defaults to sampled and trace state cannot be propagated. - * - * @since 4.0.0 - */ -import * as Otel from "@opentelemetry/api" -import * as OtelSemConv from "@opentelemetry/semantic-conventions" -import * as Cause from "effect/Cause" -import type * as Clock from "effect/Clock" -import * as Context from "effect/Context" -import * as Effect from "effect/Effect" -import * as Exit from "effect/Exit" -import { constTrue, dual } from "effect/Function" -import * as Layer from "effect/Layer" -import * as Option from "effect/Option" -import * as Predicate from "effect/Predicate" -import * as Tracer from "effect/Tracer" -import { nanosToHrTime, recordToAttributes, unknownToAttributeValue } from "./internal/attributes.ts" -import { Resource } from "./Resource.ts" - -// ============================================================================= -// Service Definitions -// ============================================================================= - -/** - * Context service containing the OpenTelemetry `Tracer` used to create spans for Effect tracing. - * - * @category services - * @since 4.0.0 - */ -export class OtelTracer extends Context.Service< - OtelTracer, - Otel.Tracer ->()("@effect/opentelemetry/Tracer") {} - -/** - * Context service containing the OpenTelemetry `TracerProvider` used to obtain tracers. - * - * @category services - * @since 4.0.0 - */ -export class OtelTracerProvider extends Context.Service< - OtelTracerProvider, - Otel.TracerProvider ->()("@effect/opentelemetry/Tracer/OtelTracerProvider") {} - -/** - * Context service containing OpenTelemetry trace flags used when constructing external span contexts. - * - * @category services - * @since 4.0.0 - */ -export class OtelTraceFlags extends Context.Service< - OtelTraceFlags, - Otel.TraceFlags ->()("@effect/opentelemetry/Tracer/OtelTraceFlags") {} - -/** - * Context service containing OpenTelemetry trace state used when constructing external span contexts. - * - * @category services - * @since 4.0.0 - */ -export class OtelTraceState extends Context.Service< - OtelTraceState, - Otel.TraceState ->()("@effect/opentelemetry/Tracer/OtelTraceState") {} - -// ============================================================================= -// Constructors -// ============================================================================= - -/** - * Creates an Effect `Tracer` implementation backed by the configured OpenTelemetry tracer. - * - * @category constructors - * @since 4.0.0 - */ -export const make: Effect.Effect = Effect.map( - Effect.service(OtelTracer), - (tracer) => - Tracer.make({ - span(options) { - return new OtelSpan( - Otel.context, - Otel.trace, - tracer, - options - ) - }, - context(primitive, fiber) { - const currentSpan = fiber.currentSpan - - if (currentSpan === undefined) { - return primitive["~effect/Effect/evaluate"](fiber) - } - - return Otel.context.with( - populateContext(Otel.context.active(), currentSpan), - () => primitive["~effect/Effect/evaluate"](fiber) - ) - } - }) -) - -/** - * Creates an Effect external span from an OpenTelemetry span context, preserving trace flags and trace state when provided. - * - * @category constructors - * @since 4.0.0 - */ -export const makeExternalSpan = (options: { - readonly traceId: string - readonly spanId: string - readonly traceFlags?: number | undefined - readonly traceState?: string | Otel.TraceState | undefined -}): Tracer.ExternalSpan => { - const annotations = Context.mutate(Context.empty(), (annotations) => { - let next = annotations - if (options.traceFlags !== undefined) { - next = Context.add(next, OtelTraceFlags, options.traceFlags) - } - - if (typeof options.traceState === "string") { - try { - next = Context.add(next, OtelTraceState, Otel.createTraceState(options.traceState)) - } catch { - // - } - } else if (options.traceState) { - next = Context.add(next, OtelTraceState, options.traceState) - } - - return next - }) - - return { - _tag: "ExternalSpan", - traceId: options.traceId, - spanId: options.spanId, - sampled: Predicate.isNotUndefined(options.traceFlags) ? isSampled(options.traceFlags) : true, - annotations - } -} - -// ============================================================================= -// Layers -// ============================================================================= - -/** - * Layer that provides the current global OpenTelemetry tracer provider. - * - * @category layers - * @since 4.0.0 - */ -export const layerGlobalProvider: Layer.Layer = Layer.sync( - OtelTracerProvider, - () => Otel.trace.getTracerProvider() -) - -/** - * Layer that creates an OpenTelemetry tracer from the provided tracer provider and resource metadata. - * - * @category layers - * @since 4.0.0 - */ -export const layerTracer: Layer.Layer = Layer.effect( - OtelTracer, - Effect.gen(function*() { - const resource = yield* Resource - const provider = yield* OtelTracerProvider - return provider.getTracer( - resource.attributes[OtelSemConv.ATTR_SERVICE_NAME] as string, - resource.attributes[OtelSemConv.ATTR_SERVICE_VERSION] as string - ) - }) -) - -/** - * Layer that creates an OpenTelemetry tracer from the global tracer provider and the current resource. - * - * @category layers - * @since 4.0.0 - */ -export const layerGlobalTracer: Layer.Layer = layerTracer.pipe( - Layer.provide(layerGlobalProvider) -) - -/** - * Layer that installs an Effect tracer backed by the global OpenTelemetry tracer provider. - * - * @category layers - * @since 4.0.0 - */ -export const layerGlobal: Layer.Layer = Layer.effect(Tracer.Tracer, make).pipe( - Layer.provideMerge(layerGlobalTracer) -) - -/** - * Layer that installs the Effect tracer using an `OtelTracer` already provided in the environment. - * - * @category layers - * @since 4.0.0 - */ -export const layerWithoutOtelTracer: Layer.Layer = Layer.effect(Tracer.Tracer, make) - -/** - * Layer that creates an OpenTelemetry tracer from a provider and resource, then installs it as the Effect tracer. - * - * **When to use** - * - * Use when you already provide an `OtelTracerProvider` and a `Resource`, and - * want Effect spans backed by a tracer derived from them. - * - * @see {@link layerTracer} for creating only the OpenTelemetry tracer service - * @see {@link layerGlobal} for installing the Effect tracer from the global provider - * @see {@link layerWithoutOtelTracer} for installing an already-provided `OtelTracer` - * - * @category layers - * @since 4.0.0 - */ -export const layer: Layer.Layer = layerWithoutOtelTracer.pipe( - Layer.provideMerge(layerTracer) -) - -// ============================================================================= -// Utilities / Combinators -// ============================================================================= - -const bigint1e6 = BigInt(1_000_000) -const bigint1e9 = BigInt(1_000_000_000) - -/** - * Gets the current OpenTelemetry span. - * - * **Details** - * - * This accessor works with both the official OpenTelemetry API, such as - * `Tracer.layer` and `NodeSdk.layer`, and the lightweight OTLP module, such as - * `OtlpTracer.layer`. When using OTLP, the returned span is a wrapper that - * conforms to the OpenTelemetry `Span` interface. - * - * @category accessors - * @since 4.0.0 - */ -export const currentOtelSpan: Effect.Effect = Effect.clockWith((clock) => - Effect.map(Effect.currentSpan, (span) => - OtelSpanTypeId in span - ? (span as OtelSpan).span - : makeOtelSpan(span, clock)) -) - -const makeOtelSpan = (span: Tracer.Span, clock: Clock.Clock): Otel.Span => { - const spanContext: Otel.SpanContext = { - traceId: span.traceId, - spanId: span.spanId, - traceFlags: span.sampled ? Otel.TraceFlags.SAMPLED : Otel.TraceFlags.NONE, - isRemote: false - } - - let exit = Exit.void - - const self: Otel.Span = { - spanContext: () => spanContext, - setAttribute(key, value) { - span.attribute(key, value) - return self - }, - setAttributes(attributes) { - for (const [key, value] of Object.entries(attributes)) { - span.attribute(key, value) - } - return self - }, - addEvent(name) { - let attributes: Otel.Attributes | undefined = undefined - let startTime: Otel.TimeInput | undefined = undefined - if (arguments.length === 3) { - attributes = arguments[1] - startTime = arguments[2] - } else { - startTime = arguments[1] - } - span.event(name, convertOtelTimeInput(startTime, clock), attributes) - return self - }, - addLink(link) { - span.addLinks([{ - span: makeExternalSpan(link.context), - attributes: link.attributes ?? {} - }]) - return self - }, - addLinks(links) { - span.addLinks(links.map((link) => ({ - span: makeExternalSpan(link.context), - attributes: link.attributes ?? {} - }))) - return self - }, - setStatus(status) { - exit = Otel.SpanStatusCode.ERROR - ? Exit.die(status.message ?? "Unknown error") - : Exit.void - return self - }, - updateName: () => self, - end(endTime) { - const time = convertOtelTimeInput(endTime, clock) - span.end(time, exit) - return self - }, - isRecording: constTrue, - recordException(exception, timeInput) { - const time = convertOtelTimeInput(timeInput, clock) - const cause = Cause.fail(exception) - const error = Cause.prettyErrors(cause)[0] - span.event(error.message, time, { - "exception.type": error.name, - "exception.message": error.message, - "exception.stacktrace": error.stack ?? "" - }) - } - } - return self -} - -const convertOtelTimeInput = (input: Otel.TimeInput | undefined, clock: Clock.Clock): bigint => { - if (input === undefined) { - return clock.currentTimeNanosUnsafe() - } else if (typeof input === "number") { - return BigInt(Math.round(input * 1_000_000)) - } else if (input instanceof Date) { - return BigInt(input.getTime()) * bigint1e6 - } - const [seconds, nanos] = input - return BigInt(seconds) * bigint1e9 + BigInt(nanos) -} - -/** - * Sets an effect's parent span from the given OpenTelemetry `SpanContext`. - * - * **When to use** - * - * Use when you need an effect to continue a trace from a parent span context - * produced by OpenTelemetry instrumentation outside Effect. - * - * @category propagation - * @since 4.0.0 - */ -export const withSpanContext: { - ( - spanContext: Otel.SpanContext - ): ( - self: Effect.Effect - ) => Effect.Effect> - ( - self: Effect.Effect, - spanContext: Otel.SpanContext - ): Effect.Effect> -} = dual(2, ( - self: Effect.Effect, - spanContext: Otel.SpanContext -) => Effect.withParentSpan(self, makeExternalSpan(spanContext))) - -// ============================================================================= -// Internals -// ============================================================================= - -const OtelSpanTypeId = "~@effect/opentelemetry/Tracer/OtelSpan" - -const kindMap = { - "internal": Otel.SpanKind.INTERNAL, - "client": Otel.SpanKind.CLIENT, - "server": Otel.SpanKind.SERVER, - "producer": Otel.SpanKind.PRODUCER, - "consumer": Otel.SpanKind.CONSUMER -} - -/** @internal */ -export class OtelSpan implements Tracer.Span { - readonly [OtelSpanTypeId]: typeof OtelSpanTypeId - readonly _tag = "Span" - - readonly name: string - readonly kind: Tracer.SpanKind - readonly annotations: Context.Context - readonly links: Array - readonly span: Otel.Span - readonly spanId: string - readonly traceId: string - readonly attributes = new Map() - readonly sampled: boolean - readonly parent: Option.Option - status: Tracer.SpanStatus - - constructor( - contextApi: Otel.ContextAPI, - traceApi: Otel.TraceAPI, - tracer: Otel.Tracer, - options: Parameters[0] - ) { - this[OtelSpanTypeId] = OtelSpanTypeId - this.name = options.name - this.annotations = options.annotations - this.links = options.links - this.kind = options.kind - const active = contextApi.active() - this.parent = options.root !== true - ? Option.orElse(options.parent, () => getOtelParent(traceApi, active, options.annotations)) - : options.parent - this.span = tracer.startSpan( - options.name, - { - startTime: nanosToHrTime(options.startTime), - links: options.links.length > 0 - ? options.links.map((link) => ({ - context: makeSpanContext(link.span), - attributes: recordToAttributes(link.attributes) - })) - : undefined as any, - kind: kindMap[this.kind] - }, - Option.isSome(this.parent) ? - populateContext(active, this.parent.value, options.annotations) : - Otel.trace.deleteSpan(active) - ) - const spanContext = this.span.spanContext() - this.spanId = spanContext.spanId - this.traceId = spanContext.traceId - this.status = { - _tag: "Started", - startTime: options.startTime - } - this.sampled = isSampled(spanContext.traceFlags) - } - - attribute(key: string, value: unknown) { - this.span.setAttribute(key, unknownToAttributeValue(value)) - this.attributes.set(key, value) - } - - addLinks(links: ReadonlyArray): void { - // oxlint-disable-next-line no-restricted-syntax - this.links.push(...links) - this.span.addLinks(links.map((link) => ({ - context: makeSpanContext(link.span), - attributes: recordToAttributes(link.attributes) - }))) - } - - end(endTime: bigint, exit: Exit.Exit) { - const hrTime = nanosToHrTime(endTime) - this.status = { - _tag: "Ended", - endTime, - exit, - startTime: this.status.startTime - } - - if (exit._tag === "Success") { - this.span.setStatus({ code: Otel.SpanStatusCode.OK }) - } else { - if (Cause.hasInterruptsOnly(exit.cause)) { - this.span.setStatus({ - code: Otel.SpanStatusCode.OK, - message: Cause.pretty(exit.cause) - }) - this.span.setAttribute("span.label", "⚠︎ Interrupted") - this.span.setAttribute("status.interrupted", true) - } else { - const errors = Cause.prettyErrors(exit.cause) - if (errors.length > 0) { - for (const error of errors) { - this.span.recordException(error, hrTime) - } - this.span.setStatus({ - code: Otel.SpanStatusCode.ERROR, - message: errors[0].message - }) - } else { - // empty cause means no error - this.span.setStatus({ code: Otel.SpanStatusCode.OK }) - } - } - } - this.span.end(hrTime) - } - - event(name: string, startTime: bigint, attributes?: Record) { - this.span.addEvent( - name, - attributes ? recordToAttributes(attributes) : undefined, - nanosToHrTime(startTime) - ) - } -} - -const isSampled = (traceFlags: Otel.TraceFlags): boolean => - (traceFlags & Otel.TraceFlags.SAMPLED) === Otel.TraceFlags.SAMPLED - -const getOtelParent = ( - tracer: Otel.TraceAPI, - context: Otel.Context, - annotations: Context.Context -): Option.Option => { - const otelParent = tracer.getSpan(context)?.spanContext() - if (!otelParent) return Option.none() - return Option.some(Tracer.externalSpan({ - spanId: otelParent.spanId, - traceId: otelParent.traceId, - sampled: (otelParent.traceFlags & 1) === 1, - annotations - })) -} - -const makeSpanContext = ( - span: Tracer.AnySpan, - annotations?: Context.Context -): Otel.SpanContext => { - const traceFlags = makeTraceFlags(span, annotations) - const traceState = makeTraceState(span, annotations)! - return ({ - spanId: span.spanId, - traceId: span.traceId, - isRemote: span._tag === "ExternalSpan", - traceFlags, - traceState - }) -} - -const makeTraceFlags = ( - span: Tracer.AnySpan, - annotations: Context.Context | undefined -): Otel.TraceFlags => { - let traceFlags: Otel.TraceFlags | undefined - if (Predicate.isNotUndefined(annotations)) { - traceFlags = extractTraceService(span, annotations, OtelTraceFlags) - if (Predicate.isUndefined(traceFlags)) { - traceFlags = Context.getOrUndefined(span.annotations, OtelTraceFlags) - } - } - return traceFlags ?? (span.sampled ? Otel.TraceFlags.SAMPLED : Otel.TraceFlags.NONE) -} - -const makeTraceState = ( - span: Tracer.AnySpan, - annotations: Context.Context | undefined -): Otel.TraceState | undefined => { - let traceState: Otel.TraceState | undefined - if (Predicate.isNotUndefined(annotations)) { - traceState = extractTraceService(span, annotations, OtelTraceState) - if (Predicate.isUndefined(traceState)) { - traceState = Context.getOrUndefined(span.annotations, OtelTraceState) - } - } - return traceState -} - -const extractTraceService = ( - parent: Tracer.AnySpan, - annotations: Context.Context, - service: Context.Service -) => { - const instance = Context.getOrUndefined(annotations, service) - if (Predicate.isNotUndefined(instance)) { - return instance - } - return Context.getOrUndefined(parent.annotations, service) -} - -const populateContext = ( - context: Otel.Context, - span: Tracer.AnySpan, - annotations?: Context.Context | undefined -): Otel.Context => - span instanceof OtelSpan ? - Otel.trace.setSpan(context, span.span) : - Otel.trace.setSpanContext(context, makeSpanContext(span, annotations)) diff --git a/repos/effect/packages/opentelemetry/src/WebSdk.ts b/repos/effect/packages/opentelemetry/src/WebSdk.ts index 85e248dcc5..ea44ab7e7c 100644 --- a/repos/effect/packages/opentelemetry/src/WebSdk.ts +++ b/repos/effect/packages/opentelemetry/src/WebSdk.ts @@ -19,10 +19,10 @@ import * as Effect from "effect/Effect" import { constant, type LazyArg } from "effect/Function" import * as Layer from "effect/Layer" import { isNonEmpty } from "./internal/utilities.ts" -import * as Logger from "./Logger.ts" -import * as Metrics from "./Metrics.ts" +import * as Logger from "./OtelLogger.ts" +import * as Metrics from "./OtelMetrics.ts" +import * as Tracer from "./OtelTracer.ts" import * as Resource from "./Resource.ts" -import * as Tracer from "./Tracer.ts" /** * Configuration for the Web OpenTelemetry layer, including resource metadata and optional tracing, metrics, and logging settings. @@ -69,8 +69,9 @@ export const layerTracerProvider = ( return provider }), (provider) => - Effect.ignore( - Effect.promise(() => provider.forceFlush().then(() => provider.shutdown())) + Effect.promise(() => provider.forceFlush()).pipe( + Effect.ensuring(Effect.promise(() => provider.shutdown())), + Effect.ignore ) ) }) diff --git a/repos/effect/packages/opentelemetry/src/index.ts b/repos/effect/packages/opentelemetry/src/index.ts index f181a380a8..0fc343dc96 100644 --- a/repos/effect/packages/opentelemetry/src/index.ts +++ b/repos/effect/packages/opentelemetry/src/index.ts @@ -7,27 +7,27 @@ /** * @since 4.0.0 */ -export * as Logger from "./Logger.ts" +export * as NodeSdk from "./NodeSdk.ts" /** * @since 4.0.0 */ -export * as Metrics from "./Metrics.ts" +export * as OtelLogger from "./OtelLogger.ts" /** * @since 4.0.0 */ -export * as NodeSdk from "./NodeSdk.ts" +export * as OtelMetrics from "./OtelMetrics.ts" /** * @since 4.0.0 */ -export * as Resource from "./Resource.ts" +export * as OtelTracer from "./OtelTracer.ts" /** * @since 4.0.0 */ -export * as Tracer from "./Tracer.ts" +export * as Resource from "./Resource.ts" /** * @since 4.0.0 diff --git a/repos/effect/packages/opentelemetry/src/internal/attributes.ts b/repos/effect/packages/opentelemetry/src/internal/attributes.ts index 2d01dc224f..d8fa209154 100644 --- a/repos/effect/packages/opentelemetry/src/internal/attributes.ts +++ b/repos/effect/packages/opentelemetry/src/internal/attributes.ts @@ -1,5 +1,6 @@ import type * as Otel from "@opentelemetry/api" import * as Inspectable from "effect/Inspectable" +import * as Rec from "effect/Record" const bigint1e9 = BigInt(1_000_000_000) @@ -12,7 +13,7 @@ export const nanosToHrTime = (timestamp: bigint): Otel.HrTime => { export const recordToAttributes = (record: Record): Otel.Attributes => { const attributes: Otel.Attributes = {} for (const [key, value] of Object.entries(record)) { - attributes[key] = unknownToAttributeValue(value) + Rec.assignProperty(attributes, key, unknownToAttributeValue(value)) } return attributes } diff --git a/repos/effect/packages/opentelemetry/src/internal/metrics.ts b/repos/effect/packages/opentelemetry/src/internal/metrics.ts index 13f3e4ebd3..3fd560a7a7 100644 --- a/repos/effect/packages/opentelemetry/src/internal/metrics.ts +++ b/repos/effect/packages/opentelemetry/src/internal/metrics.ts @@ -13,7 +13,8 @@ import type { InstrumentDescriptor } from "@opentelemetry/sdk-metrics/build/src/ import * as Arr from "effect/Array" import type * as Context from "effect/Context" import * as Metric from "effect/Metric" -import type * as Metrics from "../Metrics.ts" +import * as Rec from "effect/Record" +import type * as Metrics from "../OtelMetrics.ts" const sdkName = "@effect/opentelemetry/Metrics" @@ -64,6 +65,10 @@ export class MetricProducerImpl implements MetricProducer { this.previousSummaryState = new Map() } + fork(): MetricProducerImpl { + return new MetricProducerImpl(this.resource, this.context, this.temporality) + } + startTimeFor(name: string, hrTime: HrTime) { if (this.startTimes.has(name)) { return this.startTimes.get(name)! @@ -94,7 +99,7 @@ export class MetricProducerImpl implements MetricProducer { const state = snapshot[i] const attributes = state.attributes ? Arr.reduce(Object.entries(state.attributes), {} as Record, (acc, [key, value]) => { - acc[key] = String(value) + Rec.assignProperty(acc, key, String(value)) return acc }) : {} diff --git a/repos/effect/packages/opentelemetry/test/Logger.test.ts b/repos/effect/packages/opentelemetry/test/Logger.test.ts deleted file mode 100644 index 1dadd27716..0000000000 --- a/repos/effect/packages/opentelemetry/test/Logger.test.ts +++ /dev/null @@ -1,122 +0,0 @@ -import * as NodeSdk from "@effect/opentelemetry/NodeSdk" -import { assert, describe, it } from "@effect/vitest" -import { SeverityNumber } from "@opentelemetry/api-logs" -import { InMemoryLogRecordExporter, SimpleLogRecordProcessor } from "@opentelemetry/sdk-logs" -import { InMemorySpanExporter, SimpleSpanProcessor } from "@opentelemetry/sdk-trace-base" -import * as Clock from "effect/Clock" -import * as Effect from "effect/Effect" -import * as Layer from "effect/Layer" -import * as References from "effect/References" - -describe("Logger", () => { - describe("provided", () => { - const exporter = new InMemoryLogRecordExporter() - - const TracingLive = NodeSdk.layer(Effect.sync(() => ({ - resource: { - serviceName: "test" - }, - logRecordProcessor: [new SimpleLogRecordProcessor(exporter)] - }))) - - it.effect("emits log records", () => - Effect.gen(function*() { - yield* Effect.log("test").pipe( - Effect.repeat({ times: 9 }) - ) - assert.lengthOf(exporter.getFinishedLogRecords(), 10) - }).pipe(Effect.provide(TracingLive))) - - it.effect("maps Effect LogLevel to OTel SeverityNumber spec values", () => { - const severityExporter = new InMemoryLogRecordExporter() - const SeverityLayer = NodeSdk.layer(Effect.sync(() => ({ - resource: { serviceName: "test" }, - logRecordProcessor: [new SimpleLogRecordProcessor(severityExporter)] - }))) - - return Effect.gen(function*() { - yield* Effect.logTrace("trace") - yield* Effect.logDebug("debug") - yield* Effect.logInfo("info") - yield* Effect.logWarning("warn") - yield* Effect.logError("error") - yield* Effect.logFatal("fatal") - - const records = severityExporter.getFinishedLogRecords() - const byText = Object.fromEntries(records.map((r) => [r.severityText, r.severityNumber])) - - assert.strictEqual(byText.Trace, SeverityNumber.TRACE) - assert.strictEqual(byText.Debug, SeverityNumber.DEBUG) - assert.strictEqual(byText.Info, SeverityNumber.INFO) - assert.strictEqual(byText.Warn, SeverityNumber.WARN) - assert.strictEqual(byText.Error, SeverityNumber.ERROR) - assert.strictEqual(byText.Fatal, SeverityNumber.FATAL) - }).pipe( - Effect.provide(SeverityLayer.pipe(Layer.provideMerge(Layer.succeed(References.MinimumLogLevel, "Trace")))) - ) - }) - - it.effect("uses monotonic clock timestamps and keeps them aligned with spans", () => { - const logExporter = new InMemoryLogRecordExporter() - const spanExporter = new InMemorySpanExporter() - const timeNanos = 1_735_689_600_123_456_789n - const expectedTime: readonly [number, number] = [ - Number(timeNanos / BigInt(1_000_000_000)), - Number(timeNanos % BigInt(1_000_000_000)) - ] - const skewedClock: Clock.Clock = { - currentTimeMillisUnsafe: () => 1, - currentTimeMillis: Effect.succeed(1), - currentTimeNanosUnsafe: () => timeNanos, - currentTimeNanos: Effect.succeed(timeNanos), - sleep: () => Effect.void - } - - const TracingLive = NodeSdk.layer(Effect.sync(() => ({ - resource: { - serviceName: "test" - }, - spanProcessor: [new SimpleSpanProcessor(spanExporter)], - logRecordProcessor: [new SimpleLogRecordProcessor(logExporter)] - }))) - - return Effect.gen(function*() { - yield* Effect.log("test").pipe( - Effect.withSpan("parent") - ) - - const logs = logExporter.getFinishedLogRecords() - const spans = spanExporter.getFinishedSpans() - assert.lengthOf(logs, 1) - assert.lengthOf(spans, 1) - const log = logs[0]! - const span = spans[0]! - - assert.deepStrictEqual(log.hrTime, expectedTime) - assert.deepStrictEqual(log.hrTimeObserved, expectedTime) - assert.deepStrictEqual(log.hrTime, span.startTime) - assert.strictEqual(log.attributes.spanId, span.spanContext().spanId) - assert.strictEqual(log.attributes.traceId, span.spanContext().traceId) - }).pipe( - Effect.provide(TracingLive), - Effect.provideService(Clock.Clock, skewedClock) - ) - }) - }) - - describe("not provided", () => { - const exporter = new InMemoryLogRecordExporter() - - const TracingLive = NodeSdk.layer(Effect.sync(() => ({ - resource: { - serviceName: "test" - } - }))) - - it.effect("withSpan", () => - Effect.gen(function*() { - yield* Effect.log("test") - assert.lengthOf(exporter.getFinishedLogRecords(), 0) - }).pipe(Effect.provide(TracingLive))) - }) -}) diff --git a/repos/effect/packages/opentelemetry/test/Metrics.test.ts b/repos/effect/packages/opentelemetry/test/Metrics.test.ts deleted file mode 100644 index c57d003766..0000000000 --- a/repos/effect/packages/opentelemetry/test/Metrics.test.ts +++ /dev/null @@ -1,339 +0,0 @@ -import * as internal from "@effect/opentelemetry/internal/metrics" -import { assert, describe, it } from "@effect/vitest" -import { ValueType } from "@opentelemetry/api" -import { resourceFromAttributes } from "@opentelemetry/resources" -import * as Effect from "effect/Effect" -import * as Metric from "effect/Metric" - -const findMetric = (metrics: any, name: string) => - metrics.resourceMetrics.scopeMetrics[0].metrics.find((_: any) => _.descriptor.name === name) - -describe("Metrics", () => { - it.effect("gauge", () => - Effect.gen(function*() { - const services = yield* Effect.context() - const resource = resourceFromAttributes({ - name: "test", - version: "1.0.0" - }) - const producer = new internal.MetricProducerImpl(resource, services) - const gauge = Metric.gauge("rps") - - yield* Metric.withAttributes(gauge, { - key: "value", - unit: "requests" - }).pipe(Metric.update(10)) - yield* Metric.withAttributes(gauge, { - key: "value" - }).pipe(Metric.update(10)) - yield* Metric.withAttributes(gauge, { - key: "value" - }).pipe(Metric.update(20)) - - const results = yield* Effect.promise(() => producer.collect()) - const object = JSON.parse(JSON.stringify(results)) - assert.deepEqual(object.resourceMetrics.resource._rawAttributes, [ - ["name", "test"], - ["version", "1.0.0"] - ]) - assert.equal(object.resourceMetrics.scopeMetrics.length, 1) - const metric = findMetric(object, "rps") - assert.deepStrictEqual(metric, { - "dataPointType": 2, - "descriptor": { - "advice": {}, - "name": "rps", - "description": "", - "unit": "requests", - "type": "OBSERVABLE_GAUGE", - "valueType": ValueType.DOUBLE - }, - "aggregationTemporality": 1, - "dataPoints": [ - { - "startTime": metric.dataPoints[0].startTime, - "endTime": metric.dataPoints[0].endTime, - "attributes": { - "unit": "requests", - "key": "value" - }, - "value": 10 - }, - { - "startTime": metric.dataPoints[0].startTime, - "endTime": metric.dataPoints[0].endTime, - "attributes": { - "key": "value" - }, - "value": 20 - } - ] - }) - })) - - it.effect("gauge bigint", () => - Effect.gen(function*() { - const services = yield* Effect.context() - const producer = new internal.MetricProducerImpl( - resourceFromAttributes({ - name: "test", - version: "1.0.0" - }), - services - ) - const gauge = Metric.gauge("rps-bigint", { bigint: true }) - - yield* Metric.withAttributes(gauge, { - key: "value", - unit: "requests" - }).pipe(Metric.update(10n)) - yield* Metric.withAttributes(gauge, { - key: "value" - }).pipe(Metric.update(10n)) - yield* Metric.withAttributes(gauge, { - key: "value" - }).pipe(Metric.update(20n)) - - const results = yield* Effect.promise(() => producer.collect()) - const object = JSON.parse(JSON.stringify(results)) - assert.deepEqual(object.resourceMetrics.resource._rawAttributes, [ - ["name", "test"], - ["version", "1.0.0"] - ]) - assert.equal(object.resourceMetrics.scopeMetrics.length, 1) - const metric = findMetric(object, "rps-bigint") - assert.deepEqual(metric, { - "dataPointType": 2, - "descriptor": { - "advice": {}, - "name": "rps-bigint", - "description": "", - "unit": "requests", - "type": "OBSERVABLE_GAUGE", - "valueType": ValueType.INT - }, - "aggregationTemporality": 1, - "dataPoints": [ - { - "startTime": metric.dataPoints[0].startTime, - "endTime": metric.dataPoints[0].endTime, - "attributes": { - "unit": "requests", - "key": "value" - }, - "value": 10 - }, - { - "startTime": metric.dataPoints[0].startTime, - "endTime": metric.dataPoints[0].endTime, - "attributes": { - "key": "value" - }, - "value": 20 - } - ] - }) - })) - - it.effect("counter", () => - Effect.gen(function*() { - const services = yield* Effect.context() - const producer = new internal.MetricProducerImpl( - resourceFromAttributes({ - name: "test", - version: "1.0.0" - }), - services - ) - const counter = Metric.counter("counter", { description: "Example" }) - - yield* Metric.withAttributes(counter, { - key: "value", - unit: "requests" - }).pipe(Metric.update(1)) - yield* Metric.withAttributes(counter, { - key: "value" - }).pipe(Metric.update(1)) - yield* Metric.withAttributes(counter, { - key: "value" - }).pipe(Metric.update(1)) - - const results = yield* Effect.promise(() => producer.collect()) - const object = JSON.parse(JSON.stringify(results)) - assert.deepEqual(object.resourceMetrics.resource._rawAttributes, [ - ["name", "test"], - ["version", "1.0.0"] - ]) - assert.equal(object.resourceMetrics.scopeMetrics.length, 1) - const metric = findMetric(object, "counter") - assert.deepEqual(metric, { - "dataPointType": 3, - "descriptor": { - "advice": {}, - "name": "counter", - "description": "Example", - "unit": "requests", - "type": "UP_DOWN_COUNTER", - "valueType": ValueType.DOUBLE - }, - "isMonotonic": false, - "aggregationTemporality": 1, - "dataPoints": [ - { - "startTime": metric.dataPoints[0].startTime, - "endTime": metric.dataPoints[0].endTime, - "attributes": { - "unit": "requests", - "key": "value" - }, - "value": 1 - }, - { - "startTime": metric.dataPoints[0].startTime, - "endTime": metric.dataPoints[0].endTime, - "attributes": { - "key": "value" - }, - "value": 2 - } - ] - }) - })) - - it.effect("counter-inc", () => - Effect.gen(function*() { - const services = yield* Effect.context() - const producer = new internal.MetricProducerImpl( - resourceFromAttributes({ - name: "test", - version: "1.0.0" - }), - services - ) - const counter = Metric.counter("counter-inc", { - description: "Example", - incremental: true - }) - - yield* Metric.withAttributes(counter, { - key: "value", - unit: "requests" - }).pipe(Metric.update(1)) - yield* Metric.withAttributes(counter, { - key: "value" - }).pipe(Metric.update(1)) - yield* Metric.withAttributes(counter, { - key: "value" - }).pipe(Metric.update(1)) - - const results = yield* Effect.promise(() => producer.collect()) - const object = JSON.parse(JSON.stringify(results)) - assert.deepEqual(object.resourceMetrics.resource._rawAttributes, [ - ["name", "test"], - ["version", "1.0.0"] - ]) - assert.equal(object.resourceMetrics.scopeMetrics.length, 1) - const metric = findMetric(object, "counter-inc") - assert.deepEqual(metric, { - "dataPointType": 3, - "descriptor": { - "advice": {}, - "name": "counter-inc", - "description": "Example", - "unit": "requests", - "type": "COUNTER", - "valueType": ValueType.DOUBLE - }, - "isMonotonic": true, - "aggregationTemporality": 1, - "dataPoints": [ - { - "startTime": metric.dataPoints[0].startTime, - "endTime": metric.dataPoints[0].endTime, - "attributes": { - "unit": "requests", - "key": "value" - }, - "value": 1 - }, - { - "startTime": metric.dataPoints[0].startTime, - "endTime": metric.dataPoints[0].endTime, - "attributes": { - "key": "value" - }, - "value": 2 - } - ] - }) - })) - - it.effect("counter-bigint", () => - Effect.gen(function*() { - const services = yield* Effect.context() - const producer = new internal.MetricProducerImpl( - resourceFromAttributes({ - name: "test", - version: "1.0.0" - }), - services - ) - const counter = Metric.counter("counter-bigint", { - description: "Example", - incremental: true, - bigint: true - }) - - yield* Metric.withAttributes(counter, { - key: "value", - unit: "requests" - }).pipe(Metric.update(1n)) - yield* Metric.withAttributes(counter, { - key: "value" - }).pipe(Metric.update(1n)) - yield* Metric.withAttributes(counter, { - key: "value" - }).pipe(Metric.update(1n)) - - const results = yield* Effect.promise(() => producer.collect()) - const object = JSON.parse(JSON.stringify(results)) - assert.deepEqual(object.resourceMetrics.resource._rawAttributes, [ - ["name", "test"], - ["version", "1.0.0"] - ]) - assert.equal(object.resourceMetrics.scopeMetrics.length, 1) - const metric = findMetric(object, "counter-bigint") - assert.deepEqual(metric, { - "dataPointType": 3, - "descriptor": { - "advice": {}, - "name": "counter-bigint", - "description": "Example", - "unit": "requests", - "type": "COUNTER", - "valueType": ValueType.INT - }, - "isMonotonic": true, - "aggregationTemporality": 1, - "dataPoints": [ - { - "startTime": metric.dataPoints[0].startTime, - "endTime": metric.dataPoints[0].endTime, - "attributes": { - "unit": "requests", - "key": "value" - }, - "value": 1 - }, - { - "startTime": metric.dataPoints[0].startTime, - "endTime": metric.dataPoints[0].endTime, - "attributes": { - "key": "value" - }, - "value": 2 - } - ] - }) - })) -}) diff --git a/repos/effect/packages/opentelemetry/test/OtelLogger.test.ts b/repos/effect/packages/opentelemetry/test/OtelLogger.test.ts new file mode 100644 index 0000000000..fa09d2f5f5 --- /dev/null +++ b/repos/effect/packages/opentelemetry/test/OtelLogger.test.ts @@ -0,0 +1,166 @@ +import * as NodeSdk from "@effect/opentelemetry/NodeSdk" +import * as OtelLogger from "@effect/opentelemetry/OtelLogger" +import * as Resource from "@effect/opentelemetry/Resource" +import { assert, describe, it } from "@effect/vitest" +import { SeverityNumber } from "@opentelemetry/api-logs" +import { InMemoryLogRecordExporter, type LogRecordProcessor, SimpleLogRecordProcessor } from "@opentelemetry/sdk-logs" +import { InMemorySpanExporter, SimpleSpanProcessor } from "@opentelemetry/sdk-trace-base" +import * as Clock from "effect/Clock" +import * as Effect from "effect/Effect" +import * as Layer from "effect/Layer" +import * as References from "effect/References" + +describe("Logger", () => { + it.effect("shuts down the logger provider after forceFlush rejects", () => + Effect.gen(function*() { + let shutdowns = 0 + const processor: LogRecordProcessor = { + onEmit() {}, + forceFlush: () => Promise.reject(new Error("flush failed")), + shutdown: () => { + shutdowns++ + return Promise.resolve() + } + } + yield* Effect.exit( + Effect.scoped(Layer.build(OtelLogger.layerLoggerProvider(processor)).pipe(Effect.provide(Resource.layerEmpty))) + ) + assert.strictEqual(shutdowns, 1) + })) + + describe("provided", () => { + const exporter = new InMemoryLogRecordExporter() + + const TracingLive = NodeSdk.layer(Effect.sync(() => ({ + resource: { + serviceName: "test" + }, + logRecordProcessor: [new SimpleLogRecordProcessor({ exporter })] + }))) + + it.effect("emits log records", () => + Effect.gen(function*() { + yield* Effect.log("test").pipe( + Effect.repeat({ times: 9 }) + ) + assert.lengthOf(exporter.getFinishedLogRecords(), 10) + }).pipe(Effect.provide(TracingLive))) + + it.effect("maps Effect LogLevel to OTel SeverityNumber spec values", () => { + const severityExporter = new InMemoryLogRecordExporter() + const SeverityLayer = NodeSdk.layer(Effect.sync(() => ({ + resource: { serviceName: "test" }, + logRecordProcessor: [new SimpleLogRecordProcessor({ exporter: severityExporter })] + }))) + + return Effect.gen(function*() { + yield* Effect.logTrace("trace") + yield* Effect.logDebug("debug") + yield* Effect.logInfo("info") + yield* Effect.logWarning("warn") + yield* Effect.logError("error") + yield* Effect.logFatal("fatal") + + const records = severityExporter.getFinishedLogRecords() + const byText = Object.fromEntries(records.map((r) => [r.severityText, r.severityNumber])) + + assert.strictEqual(byText.Trace, SeverityNumber.TRACE) + assert.strictEqual(byText.Debug, SeverityNumber.DEBUG) + assert.strictEqual(byText.Info, SeverityNumber.INFO) + assert.strictEqual(byText.Warn, SeverityNumber.WARN) + assert.strictEqual(byText.Error, SeverityNumber.ERROR) + assert.strictEqual(byText.Fatal, SeverityNumber.FATAL) + }).pipe( + Effect.provide(SeverityLayer.pipe(Layer.provideMerge(Layer.succeed(References.MinimumLogLevel, "Trace")))) + ) + }) + + it.effect("uses wall-clock timestamps and keeps them aligned with spans", () => { + const logExporter = new InMemoryLogRecordExporter() + const spanExporter = new InMemorySpanExporter() + const wallTimeNanos = 1_735_689_600_123_456_789n + const monotonicTimeNanos = 123_456_789n + const expectedTime: readonly [number, number] = [ + Number(wallTimeNanos / BigInt(1_000_000_000)), + Number(wallTimeNanos % BigInt(1_000_000_000)) + ] + const skewedClock: Clock.Clock = { + currentTimeMillisUnsafe: () => 1, + currentTimeMillis: Effect.succeed(1), + currentTimeNanosUnsafe: () => wallTimeNanos, + currentTimeNanos: Effect.succeed(wallTimeNanos), + monotonicTimeNanosUnsafe: () => monotonicTimeNanos, + monotonicTimeNanos: Effect.succeed(monotonicTimeNanos), + sleep: () => Effect.void + } + + const TracingLive = NodeSdk.layer(Effect.sync(() => ({ + resource: { + serviceName: "test" + }, + spanProcessor: [new SimpleSpanProcessor(spanExporter)], + logRecordProcessor: [new SimpleLogRecordProcessor({ exporter: logExporter })] + }))) + + return Effect.gen(function*() { + yield* Effect.log("test").pipe( + Effect.withSpan("parent") + ) + + const logs = logExporter.getFinishedLogRecords() + const spans = spanExporter.getFinishedSpans() + assert.lengthOf(logs, 1) + assert.lengthOf(spans, 1) + const log = logs[0]! + const span = spans[0]! + + assert.deepStrictEqual(log.hrTime, expectedTime) + assert.deepStrictEqual(log.hrTimeObserved, expectedTime) + assert.deepStrictEqual(log.hrTime, span.startTime) + assert.strictEqual(log.attributes.spanId, span.spanContext().spanId) + assert.strictEqual(log.attributes.traceId, span.spanContext().traceId) + }).pipe( + Effect.provide(TracingLive), + Effect.provideService(Clock.Clock, skewedClock) + ) + }) + + it.effect("does not let annotations overwrite active span correlation", () => { + const logExporter = new InMemoryLogRecordExporter() + const spanExporter = new InMemorySpanExporter() + const TracingLive = NodeSdk.layer(Effect.sync(() => ({ + resource: { serviceName: "test" }, + spanProcessor: [new SimpleSpanProcessor(spanExporter)], + logRecordProcessor: [new SimpleLogRecordProcessor({ exporter: logExporter })] + }))) + + return Effect.gen(function*() { + yield* Effect.log("test").pipe( + Effect.annotateLogs({ traceId: "spoof-trace", spanId: "spoof-span" }), + Effect.withSpan("parent") + ) + + const log = logExporter.getFinishedLogRecords()[0]! + const span = spanExporter.getFinishedSpans()[0]! + assert.strictEqual(log.attributes.traceId, span.spanContext().traceId) + assert.strictEqual(log.attributes.spanId, span.spanContext().spanId) + }).pipe(Effect.provide(TracingLive)) + }) + }) + + describe("not provided", () => { + const exporter = new InMemoryLogRecordExporter() + + const TracingLive = NodeSdk.layer(Effect.sync(() => ({ + resource: { + serviceName: "test" + } + }))) + + it.effect("withSpan", () => + Effect.gen(function*() { + yield* Effect.log("test") + assert.lengthOf(exporter.getFinishedLogRecords(), 0) + }).pipe(Effect.provide(TracingLive))) + }) +}) diff --git a/repos/effect/packages/opentelemetry/test/OtelMetrics.test.ts b/repos/effect/packages/opentelemetry/test/OtelMetrics.test.ts new file mode 100644 index 0000000000..df8947529a --- /dev/null +++ b/repos/effect/packages/opentelemetry/test/OtelMetrics.test.ts @@ -0,0 +1,366 @@ +import * as internal from "@effect/opentelemetry/internal/metrics" +import * as OtelMetrics from "@effect/opentelemetry/OtelMetrics" +import { assert, describe, it } from "@effect/vitest" +import { ValueType } from "@opentelemetry/api" +import { resourceFromAttributes } from "@opentelemetry/resources" +import { MetricReader } from "@opentelemetry/sdk-metrics" +import * as Effect from "effect/Effect" +import * as Metric from "effect/Metric" + +const findMetric = (metrics: any, name: string) => + metrics.resourceMetrics.scopeMetrics[0].metrics.find((_: any) => _.descriptor.name === name) + +describe("Metrics", () => { + it.effect("gauge", () => + Effect.gen(function*() { + const services = yield* Effect.context() + const resource = resourceFromAttributes({ + name: "test", + version: "1.0.0" + }) + const producer = new internal.MetricProducerImpl(resource, services) + const gauge = Metric.gauge("rps") + + yield* Metric.withAttributes(gauge, { + key: "value", + unit: "requests" + }).pipe(Metric.update(10)) + yield* Metric.withAttributes(gauge, { + key: "value" + }).pipe(Metric.update(10)) + yield* Metric.withAttributes(gauge, { + key: "value" + }).pipe(Metric.update(20)) + + const results = yield* Effect.promise(() => producer.collect()) + const object = JSON.parse(JSON.stringify(results)) + assert.deepEqual(object.resourceMetrics.resource._rawAttributes, [ + ["name", "test"], + ["version", "1.0.0"] + ]) + assert.equal(object.resourceMetrics.scopeMetrics.length, 1) + const metric = findMetric(object, "rps") + assert.deepStrictEqual(metric, { + "dataPointType": 2, + "descriptor": { + "advice": {}, + "name": "rps", + "description": "", + "unit": "requests", + "type": "OBSERVABLE_GAUGE", + "valueType": ValueType.DOUBLE + }, + "aggregationTemporality": 1, + "dataPoints": [ + { + "startTime": metric.dataPoints[0].startTime, + "endTime": metric.dataPoints[0].endTime, + "attributes": { + "unit": "requests", + "key": "value" + }, + "value": 10 + }, + { + "startTime": metric.dataPoints[0].startTime, + "endTime": metric.dataPoints[0].endTime, + "attributes": { + "key": "value" + }, + "value": 20 + } + ] + }) + })) + + it.effect("gauge bigint", () => + Effect.gen(function*() { + const services = yield* Effect.context() + const producer = new internal.MetricProducerImpl( + resourceFromAttributes({ + name: "test", + version: "1.0.0" + }), + services + ) + const gauge = Metric.gauge("rps-bigint", { bigint: true }) + + yield* Metric.withAttributes(gauge, { + key: "value", + unit: "requests" + }).pipe(Metric.update(10n)) + yield* Metric.withAttributes(gauge, { + key: "value" + }).pipe(Metric.update(10n)) + yield* Metric.withAttributes(gauge, { + key: "value" + }).pipe(Metric.update(20n)) + + const results = yield* Effect.promise(() => producer.collect()) + const object = JSON.parse(JSON.stringify(results)) + assert.deepEqual(object.resourceMetrics.resource._rawAttributes, [ + ["name", "test"], + ["version", "1.0.0"] + ]) + assert.equal(object.resourceMetrics.scopeMetrics.length, 1) + const metric = findMetric(object, "rps-bigint") + assert.deepEqual(metric, { + "dataPointType": 2, + "descriptor": { + "advice": {}, + "name": "rps-bigint", + "description": "", + "unit": "requests", + "type": "OBSERVABLE_GAUGE", + "valueType": ValueType.INT + }, + "aggregationTemporality": 1, + "dataPoints": [ + { + "startTime": metric.dataPoints[0].startTime, + "endTime": metric.dataPoints[0].endTime, + "attributes": { + "unit": "requests", + "key": "value" + }, + "value": 10 + }, + { + "startTime": metric.dataPoints[0].startTime, + "endTime": metric.dataPoints[0].endTime, + "attributes": { + "key": "value" + }, + "value": 20 + } + ] + }) + })) + + it.effect("counter", () => + Effect.gen(function*() { + const services = yield* Effect.context() + const producer = new internal.MetricProducerImpl( + resourceFromAttributes({ + name: "test", + version: "1.0.0" + }), + services + ) + const counter = Metric.counter("counter", { description: "Example" }) + + yield* Metric.withAttributes(counter, { + key: "value", + unit: "requests" + }).pipe(Metric.update(1)) + yield* Metric.withAttributes(counter, { + key: "value" + }).pipe(Metric.update(1)) + yield* Metric.withAttributes(counter, { + key: "value" + }).pipe(Metric.update(1)) + + const results = yield* Effect.promise(() => producer.collect()) + const object = JSON.parse(JSON.stringify(results)) + assert.deepEqual(object.resourceMetrics.resource._rawAttributes, [ + ["name", "test"], + ["version", "1.0.0"] + ]) + assert.equal(object.resourceMetrics.scopeMetrics.length, 1) + const metric = findMetric(object, "counter") + assert.deepEqual(metric, { + "dataPointType": 3, + "descriptor": { + "advice": {}, + "name": "counter", + "description": "Example", + "unit": "requests", + "type": "UP_DOWN_COUNTER", + "valueType": ValueType.DOUBLE + }, + "isMonotonic": false, + "aggregationTemporality": 1, + "dataPoints": [ + { + "startTime": metric.dataPoints[0].startTime, + "endTime": metric.dataPoints[0].endTime, + "attributes": { + "unit": "requests", + "key": "value" + }, + "value": 1 + }, + { + "startTime": metric.dataPoints[0].startTime, + "endTime": metric.dataPoints[0].endTime, + "attributes": { + "key": "value" + }, + "value": 2 + } + ] + }) + })) + + it.effect("counter-inc", () => + Effect.gen(function*() { + const services = yield* Effect.context() + const producer = new internal.MetricProducerImpl( + resourceFromAttributes({ + name: "test", + version: "1.0.0" + }), + services + ) + const counter = Metric.counter("counter-inc", { + description: "Example", + incremental: true + }) + + yield* Metric.withAttributes(counter, { + key: "value", + unit: "requests" + }).pipe(Metric.update(1)) + yield* Metric.withAttributes(counter, { + key: "value" + }).pipe(Metric.update(1)) + yield* Metric.withAttributes(counter, { + key: "value" + }).pipe(Metric.update(1)) + + const results = yield* Effect.promise(() => producer.collect()) + const object = JSON.parse(JSON.stringify(results)) + assert.deepEqual(object.resourceMetrics.resource._rawAttributes, [ + ["name", "test"], + ["version", "1.0.0"] + ]) + assert.equal(object.resourceMetrics.scopeMetrics.length, 1) + const metric = findMetric(object, "counter-inc") + assert.deepEqual(metric, { + "dataPointType": 3, + "descriptor": { + "advice": {}, + "name": "counter-inc", + "description": "Example", + "unit": "requests", + "type": "COUNTER", + "valueType": ValueType.DOUBLE + }, + "isMonotonic": true, + "aggregationTemporality": 1, + "dataPoints": [ + { + "startTime": metric.dataPoints[0].startTime, + "endTime": metric.dataPoints[0].endTime, + "attributes": { + "unit": "requests", + "key": "value" + }, + "value": 1 + }, + { + "startTime": metric.dataPoints[0].startTime, + "endTime": metric.dataPoints[0].endTime, + "attributes": { + "key": "value" + }, + "value": 2 + } + ] + }) + })) + + it.effect("counter-bigint", () => + Effect.gen(function*() { + const services = yield* Effect.context() + const producer = new internal.MetricProducerImpl( + resourceFromAttributes({ + name: "test", + version: "1.0.0" + }), + services + ) + const counter = Metric.counter("counter-bigint", { + description: "Example", + incremental: true, + bigint: true + }) + + yield* Metric.withAttributes(counter, { + key: "value", + unit: "requests" + }).pipe(Metric.update(1n)) + yield* Metric.withAttributes(counter, { + key: "value" + }).pipe(Metric.update(1n)) + yield* Metric.withAttributes(counter, { + key: "value" + }).pipe(Metric.update(1n)) + + const results = yield* Effect.promise(() => producer.collect()) + const object = JSON.parse(JSON.stringify(results)) + assert.deepEqual(object.resourceMetrics.resource._rawAttributes, [ + ["name", "test"], + ["version", "1.0.0"] + ]) + assert.equal(object.resourceMetrics.scopeMetrics.length, 1) + const metric = findMetric(object, "counter-bigint") + assert.deepEqual(metric, { + "dataPointType": 3, + "descriptor": { + "advice": {}, + "name": "counter-bigint", + "description": "Example", + "unit": "requests", + "type": "COUNTER", + "valueType": ValueType.INT + }, + "isMonotonic": true, + "aggregationTemporality": 1, + "dataPoints": [ + { + "startTime": metric.dataPoints[0].startTime, + "endTime": metric.dataPoints[0].endTime, + "attributes": { + "unit": "requests", + "key": "value" + }, + "value": 1 + }, + { + "startTime": metric.dataPoints[0].startTime, + "endTime": metric.dataPoints[0].endTime, + "attributes": { + "key": "value" + }, + "value": 2 + } + ] + }) + })) + + it.effect("reports the same delta to every registered reader", () => + Effect.gen(function*() { + class Reader extends MetricReader { + protected onShutdown(): Promise { + return Promise.resolve() + } + protected onForceFlush(): Promise { + return Promise.resolve() + } + } + + const services = yield* Effect.context() + const producer = new internal.MetricProducerImpl(resourceFromAttributes({}), services, "delta") + const first = new Reader() + const second = new Reader() + yield* OtelMetrics.registerProducer(producer, () => [first, second]) + yield* Metric.update(Metric.counter("requests", { incremental: true }), 1) + + const firstResult = yield* Effect.promise(() => first.collect()) + const secondResult = yield* Effect.promise(() => second.collect()) + const firstValue = (firstResult.resourceMetrics.scopeMetrics[0]!.metrics[0] as any).dataPoints[0].value + const secondValue = (secondResult.resourceMetrics.scopeMetrics[0]!.metrics[0] as any).dataPoints[0].value + assert.deepStrictEqual([firstValue, secondValue], [1, 1]) + }).pipe(Effect.provideService(Metric.MetricRegistry, new Map()))) +}) diff --git a/repos/effect/packages/opentelemetry/test/OtelTracer.test.ts b/repos/effect/packages/opentelemetry/test/OtelTracer.test.ts new file mode 100644 index 0000000000..a4647691be --- /dev/null +++ b/repos/effect/packages/opentelemetry/test/OtelTracer.test.ts @@ -0,0 +1,285 @@ +import * as NodeSdk from "@effect/opentelemetry/NodeSdk" +import * as OtelTracer from "@effect/opentelemetry/OtelTracer" +import { assert, describe, it } from "@effect/vitest" +import * as OtelApi from "@opentelemetry/api" +import { AsyncHooksContextManager } from "@opentelemetry/context-async-hooks" +import { InMemorySpanExporter, SimpleSpanProcessor } from "@opentelemetry/sdk-trace-base" +import * as Cause from "effect/Cause" +import * as EffectContext from "effect/Context" +import * as Effect from "effect/Effect" +import * as Option from "effect/Option" +import * as EffectTracer from "effect/Tracer" + +const TracingLive = NodeSdk.layer(Effect.sync(() => ({ + resource: { + serviceName: "test" + }, + spanProcessor: [new SimpleSpanProcessor(new InMemorySpanExporter())] +}))) + +// needed to test context propagation +const contextManager = new AsyncHooksContextManager() +OtelApi.context.setGlobalContextManager(contextManager) + +describe("Tracer", () => { + describe("provided", () => { + it.effect("withSpan", () => + Effect.gen(function*() { + const span = yield* Effect.currentSpan + assert.instanceOf(span, OtelTracer.OtelSpan) + }).pipe( + Effect.withSpan("ok"), + Effect.provide(TracingLive) + )) + + it.effect("withSpan links", () => + Effect.gen(function*() { + const linkedSpan = yield* Effect.makeSpanScoped("B") + const span = yield* Effect.currentSpan.pipe( + Effect.withSpan("A"), + Effect.linkSpans(linkedSpan) + ) + assert.instanceOf(span, OtelTracer.OtelSpan) + assert.lengthOf(span.links, 1) + }).pipe( + Effect.provide(TracingLive) + )) + + it.effect("nested withSpan sets correct parent chain", () => + Effect.gen(function*() { + const child = yield* Effect.currentSpan.pipe( + Effect.withSpan("child"), + Effect.withSpan("parent") + ) + assert.instanceOf(child, OtelTracer.OtelSpan) + assert.strictEqual(child.name, "child") + assert.isDefined(child.parent) + assert.strictEqual((child.parent.valueOrUndefined! as OtelTracer.OtelSpan).name, "parent") + }).pipe( + Effect.provide(TracingLive) + )) + + it.effect("supervisor sets context", () => + Effect.sync(() => { + const context = OtelApi.context.active() + assert.isDefined(OtelApi.trace.getSpan(context)) + }).pipe( + Effect.withSpan("ok"), + Effect.provide(TracingLive) + )) + + it.effect("supervisor sets context generator", () => + Effect.gen(function*() { + yield* Effect.yieldNow + const context = OtelApi.context.active() + assert.isDefined(OtelApi.trace.getSpan(context)) + }).pipe( + Effect.withSpan("ok"), + Effect.provide(TracingLive) + )) + + it.effect("currentOtelSpan", () => + Effect.gen(function*() { + const span = yield* Effect.currentSpan + const otelSpan = yield* OtelTracer.currentOtelSpan + assert.strictEqual((span as OtelTracer.OtelSpan).span, otelSpan) + }).pipe( + Effect.withSpan("ok"), + Effect.provide(TracingLive) + )) + + it.effect.each([OtelApi.SpanStatusCode.UNSET, OtelApi.SpanStatusCode.OK])( + "honors non-error wrapper status %s", + (status) => + Effect.gen(function*() { + const span = yield* Effect.currentSpan + const wrapper = yield* OtelTracer.currentOtelSpan + wrapper.setStatus({ code: status }) + wrapper.end() + assert.strictEqual(span.status._tag, "Ended") + if (span.status._tag === "Ended") { + assert.strictEqual(span.status.exit._tag, "Success") + } + }).pipe(Effect.withSpan("repro")) + ) + + it.effect("honors an error wrapper status", () => + Effect.gen(function*() { + const span = yield* Effect.currentSpan + const wrapper = yield* OtelTracer.currentOtelSpan + wrapper.setStatus({ code: OtelApi.SpanStatusCode.ERROR }) + wrapper.end() + assert.strictEqual(span.status._tag, "Ended") + if (span.status._tag === "Ended") { + assert.strictEqual(span.status.exit._tag, "Failure") + } + }).pipe(Effect.withSpan("repro"))) + + it.effect("preserves the sampling decision of generic external spans", () => + Effect.gen(function*() { + const span = yield* Effect.currentSpan + assert.instanceOf(span, OtelTracer.OtelSpan) + assert.strictEqual(span.span.spanContext().traceFlags, OtelApi.TraceFlags.NONE) + }).pipe( + Effect.withSpan("child"), + Effect.withParentSpan(EffectTracer.externalSpan({ + traceId: "1".repeat(32), + spanId: "2".repeat(16), + sampled: false + })), + Effect.provide(TracingLive) + )) + + it("preserves trace state and locality on an active OpenTelemetry parent", () => { + const parent: OtelApi.SpanContext = { + traceId: "1".repeat(32), + spanId: "2".repeat(16), + traceFlags: OtelApi.TraceFlags.SAMPLED, + traceState: OtelApi.createTraceState("vendor=value"), + isRemote: false + } + const active = OtelApi.trace.setSpanContext(OtelApi.ROOT_CONTEXT, parent) + let receivedParent: OtelApi.SpanContext | undefined + const tracer = { + startSpan(_name: string, _options: unknown, context: OtelApi.Context) { + receivedParent = OtelApi.trace.getSpanContext(context) + return { + spanContext: () => ({ + traceId: "3".repeat(32), + spanId: "4".repeat(16), + traceFlags: OtelApi.TraceFlags.SAMPLED + }) + } as OtelApi.Span + } + } as OtelApi.Tracer + + const child = new OtelTracer.OtelSpan( + { active: () => active } as OtelApi.ContextAPI, + OtelApi.trace, + tracer, + { + name: "child", + parent: Option.none(), + annotations: EffectContext.empty(), + links: [], + startTime: 0n, + kind: "internal", + root: false, + sampled: true + } + ) + + assert.instanceOf(child, OtelTracer.OtelSpan) + assert.deepStrictEqual( + [receivedParent?.traceState?.serialize(), receivedParent?.isRemote], + ["vendor=value", false] + ) + }) + + it.effect("records every pretty error", () => + Effect.gen(function*() { + const exporter = new InMemorySpanExporter() + const spanProcessor = new SimpleSpanProcessor(exporter) + const firstFailure = Cause.fail(new Error("first")) + const secondFailure = Cause.fail(new Error("second")) + const cause = Cause.combine(firstFailure, secondFailure) + + yield* Effect.failCause(cause).pipe( + Effect.withSpan("error-span"), + Effect.andThen(Effect.never), // keep the exporter alive + Effect.provide(NodeSdk.layer(() => ({ + resource: { + serviceName: "test" + }, + spanProcessor: [spanProcessor] + }))), + Effect.forkChild({ startImmediately: true }) + ) + + const spanData = exporter.getFinishedSpans()[0] + if (spanData === undefined) { + return yield* Effect.die("Missing span data") + } + const exceptionEvents = spanData.events.filter((event) => event.name === "exception") + assert.lengthOf(exceptionEvents, 2) + assert.strictEqual(spanData.status.message, "first") + })) + + it.effect("renders nested error causes in the stacktrace", () => + Effect.gen(function*() { + const exporter = new InMemorySpanExporter() + const spanProcessor = new SimpleSpanProcessor(exporter) + const error = new Error("outer failure", { cause: new Error("inner cause") }) + + yield* Effect.die(error).pipe( + Effect.withSpan("error-span"), + Effect.andThen(Effect.never), // keep the exporter alive + Effect.provide(NodeSdk.layer(() => ({ + resource: { + serviceName: "test" + }, + spanProcessor: [spanProcessor] + }))), + Effect.forkChild({ startImmediately: true }) + ) + + const spanData = exporter.getFinishedSpans()[0] + if (spanData === undefined) { + return yield* Effect.die("Missing span data") + } + const exceptionEvent = spanData.events.find((event) => event.name === "exception") + assert(exceptionEvent !== undefined) + const stacktrace = exceptionEvent.attributes?.["exception.stacktrace"] + assert.isString(stacktrace) + assert.include(stacktrace as string, "[cause]: Error: inner cause") + })) + + it.effect("withSpanContext", () => + Effect.gen(function*() { + const effect = Effect.gen(function*() { + const span = yield* Effect.currentParentSpan + assert(span._tag === "Span") + if (span.parent._tag === "None") { + return yield* Effect.die("No parent span") + } + return span.parent.value + }).pipe(Effect.withSpan("child")) + + const services = yield* Effect.context() + + yield* Effect.promise(async () => { + await OtelApi.trace.getTracer("test").startActiveSpan("otel-span", { + root: true, + attributes: { "root": "yes" } + }, async (span) => { + try { + const parent = await effect.pipe( + OtelTracer.withSpanContext(span.spanContext()), + Effect.provideContext(services), + Effect.runPromise + ) + const { spanId, traceId } = span.spanContext() + assert.containsSubset(parent, { + spanId, + traceId + }) + } finally { + span.end() + } + }) + }) + }).pipe( + Effect.provide(TracingLive) + )) + }) + + describe("not provided", () => { + it.effect("withSpan", () => + Effect.gen(function*() { + const span = yield* Effect.currentSpan + assert.notInstanceOf(span, OtelTracer.OtelSpan) + }).pipe( + Effect.withSpan("ok") + )) + }) +}) diff --git a/repos/effect/packages/opentelemetry/test/Tracer.test.ts b/repos/effect/packages/opentelemetry/test/Tracer.test.ts deleted file mode 100644 index 0404038b30..0000000000 --- a/repos/effect/packages/opentelemetry/test/Tracer.test.ts +++ /dev/null @@ -1,183 +0,0 @@ -import * as NodeSdk from "@effect/opentelemetry/NodeSdk" -import * as Tracer from "@effect/opentelemetry/Tracer" -import { assert, describe, it } from "@effect/vitest" -import * as OtelApi from "@opentelemetry/api" -import { AsyncHooksContextManager } from "@opentelemetry/context-async-hooks" -import { InMemorySpanExporter, SimpleSpanProcessor } from "@opentelemetry/sdk-trace-base" -import * as Cause from "effect/Cause" -import * as Effect from "effect/Effect" -import * as EffectTracer from "effect/Tracer" - -const TracingLive = NodeSdk.layer(Effect.sync(() => ({ - resource: { - serviceName: "test" - }, - spanProcessor: [new SimpleSpanProcessor(new InMemorySpanExporter())] -}))) - -// needed to test context propagation -const contextManager = new AsyncHooksContextManager() -OtelApi.context.setGlobalContextManager(contextManager) - -describe("Tracer", () => { - describe("provided", () => { - it.effect("withSpan", () => - Effect.gen(function*() { - const span = yield* Effect.currentSpan - assert.instanceOf(span, Tracer.OtelSpan) - }).pipe( - Effect.withSpan("ok"), - Effect.provide(TracingLive) - )) - - it.effect("withSpan links", () => - Effect.gen(function*() { - const linkedSpan = yield* Effect.makeSpanScoped("B") - const span = yield* Effect.currentSpan.pipe( - Effect.withSpan("A"), - Effect.linkSpans(linkedSpan) - ) - assert.instanceOf(span, Tracer.OtelSpan) - assert.lengthOf(span.links, 1) - }).pipe( - Effect.scoped, - Effect.provide(TracingLive) - )) - - it.effect("nested withSpan sets correct parent chain", () => - Effect.gen(function*() { - const child = yield* Effect.currentSpan.pipe( - Effect.withSpan("child"), - Effect.withSpan("parent") - ) - assert.instanceOf(child, Tracer.OtelSpan) - assert.strictEqual(child.name, "child") - assert.isDefined(child.parent) - assert.strictEqual((child.parent.valueOrUndefined! as Tracer.OtelSpan).name, "parent") - }).pipe( - Effect.provide(TracingLive) - )) - - it.effect("supervisor sets context", () => - Effect.sync(() => { - const context = OtelApi.context.active() - assert.isDefined(OtelApi.trace.getSpan(context)) - }).pipe( - Effect.withSpan("ok"), - Effect.provide(TracingLive) - )) - - it.effect("supervisor sets context generator", () => - Effect.gen(function*() { - yield* Effect.yieldNow - const context = OtelApi.context.active() - assert.isDefined(OtelApi.trace.getSpan(context)) - }).pipe( - Effect.withSpan("ok"), - Effect.provide(TracingLive) - )) - - it.effect("currentOtelSpan", () => - Effect.gen(function*() { - const span = yield* Effect.currentSpan - const otelSpan = yield* Tracer.currentOtelSpan - assert.strictEqual((span as Tracer.OtelSpan).span, otelSpan) - }).pipe( - Effect.withSpan("ok"), - Effect.provide(TracingLive) - )) - - it.effect("preserves the sampling decision of generic external spans", () => - Effect.gen(function*() { - const span = yield* Effect.currentSpan - assert.instanceOf(span, Tracer.OtelSpan) - assert.strictEqual(span.span.spanContext().traceFlags, OtelApi.TraceFlags.NONE) - }).pipe( - Effect.withSpan("child"), - Effect.withParentSpan(EffectTracer.externalSpan({ - traceId: "1".repeat(32), - spanId: "2".repeat(16), - sampled: false - })), - Effect.provide(TracingLive) - )) - - it.effect("records every pretty error", () => - Effect.gen(function*() { - const exporter = new InMemorySpanExporter() - const spanProcessor = new SimpleSpanProcessor(exporter) - const firstFailure = Cause.fail(new Error("first")) - const secondFailure = Cause.fail(new Error("second")) - const cause = Cause.combine(firstFailure, secondFailure) - - yield* Effect.failCause(cause).pipe( - Effect.withSpan("error-span"), - Effect.andThen(Effect.never), // keep the exporter alive - Effect.provide(NodeSdk.layer(() => ({ - resource: { - serviceName: "test" - }, - spanProcessor: [spanProcessor] - }))), - Effect.forkChild({ startImmediately: true }) - ) - - const spanData = exporter.getFinishedSpans()[0] - if (spanData === undefined) { - return yield* Effect.die("Missing span data") - } - const exceptionEvents = spanData.events.filter((event) => event.name === "exception") - assert.lengthOf(exceptionEvents, 2) - assert.strictEqual(spanData.status.message, "first") - })) - - it.effect("withSpanContext", () => - Effect.gen(function*() { - const effect = Effect.gen(function*() { - const span = yield* Effect.currentParentSpan - assert(span._tag === "Span") - if (span.parent._tag === "None") { - return yield* Effect.die("No parent span") - } - return span.parent.value - }).pipe(Effect.withSpan("child")) - - const services = yield* Effect.context() - - yield* Effect.promise(async () => { - await OtelApi.trace.getTracer("test").startActiveSpan("otel-span", { - root: true, - attributes: { "root": "yes" } - }, async (span) => { - try { - const parent = await effect.pipe( - Tracer.withSpanContext(span.spanContext()), - Effect.provideContext(services), - Effect.runPromise - ) - const { spanId, traceId } = span.spanContext() - assert.containsSubset(parent, { - spanId, - traceId - }) - } finally { - span.end() - } - }) - }) - }).pipe( - Effect.scoped, - Effect.provide(TracingLive) - )) - }) - - describe("not provided", () => { - it.effect("withSpan", () => - Effect.gen(function*() { - const span = yield* Effect.currentSpan - assert.notInstanceOf(span, Tracer.OtelSpan) - }).pipe( - Effect.withSpan("ok") - )) - }) -}) diff --git a/repos/effect/packages/opentelemetry/tsconfig.json b/repos/effect/packages/opentelemetry/tsconfig.json index ae1d58ea9d..d27a5f7d0a 100644 --- a/repos/effect/packages/opentelemetry/tsconfig.json +++ b/repos/effect/packages/opentelemetry/tsconfig.json @@ -1,5 +1,5 @@ { - "$schema": "http://json.schemastore.org/tsconfig", + "$schema": "https://json.schemastore.org/tsconfig", "extends": "../../tsconfig.base.json", "include": ["src"], "references": [ diff --git a/repos/effect/packages/opentelemetry/vitest.config.ts b/repos/effect/packages/opentelemetry/vitest.config.ts deleted file mode 100644 index fb966ae875..0000000000 --- a/repos/effect/packages/opentelemetry/vitest.config.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { mergeConfig, type ViteUserConfig } from "vitest/config" -import shared from "../../vitest.shared.ts" - -const config: ViteUserConfig = {} - -export default mergeConfig(shared, config) diff --git a/repos/effect/packages/platform-browser/CHANGELOG.md b/repos/effect/packages/platform-browser/CHANGELOG.md index f97f39dba8..6219dcc6d2 100644 --- a/repos/effect/packages/platform-browser/CHANGELOG.md +++ b/repos/effect/packages/platform-browser/CHANGELOG.md @@ -1,5 +1,141 @@ # @effect/platform-browser +## 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 + +- 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 + +- [#7075](https://github.com/Effect-TS/effect/pull/7075) [`2f9016d`](https://github.com/Effect-TS/effect/commit/2f9016d23cdbde159153fd8b03507d03a066966f) Thanks @fubhy! - Fix form data decoding for XMLHttpRequest client responses. + +- [#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. +- 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 + +- [#6857](https://github.com/Effect-TS/effect/pull/6857) [`79db8e5`](https://github.com/Effect-TS/effect/commit/79db8e5e42b300888401b6487e7628e0a05180ab) Thanks @fubhy! - Fix IndexedDB-backed key-value writes to wait for transaction commit before reporting success. + +- [#6852](https://github.com/Effect-TS/effect/pull/6852) [`ef6eca7`](https://github.com/Effect-TS/effect/commit/ef6eca71dcb9abef7c2685d127a89e25fb6f0e62) Thanks @fubhy! - Fix IndexedDB query range, ordering, streaming, and transaction semantics. + +- [#6683](https://github.com/Effect-TS/effect/pull/6683) [`5f87bc7`](https://github.com/Effect-TS/effect/commit/5f87bc7a6608f129e2ee4eaa6745c61d920bac53) Thanks @tim-smart! - Fix `BrowserCrypto.randomBytes` for requests larger than the Web Crypto per-call limit. + +- [#6680](https://github.com/Effect-TS/effect/pull/6680) [`23e176a`](https://github.com/Effect-TS/effect/commit/23e176a4f05ed3e81cc13a5d70111099692ea9a5) Thanks @tim-smart! - Fix worker runner disconnect notifications and event listener cleanup. + +- [#6854](https://github.com/Effect-TS/effect/pull/6854) [`39ed94a`](https://github.com/Effect-TS/effect/commit/39ed94a0751ae060ae8db24eb74865a636acf1cd) Thanks @fubhy! - Abort IndexedDB versionchange transactions when schema migrations fail. + +- [#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. + +- [#6701](https://github.com/Effect-TS/effect/pull/6701) [`9867b9f`](https://github.com/Effect-TS/effect/commit/9867b9fc69f9cc6c443594fc7eccc7be0c674d9c) Thanks @fubhy! - Removed explicit ./index entrypoints + +- Updated dependencies [[`e56cd8f`](https://github.com/Effect-TS/effect/commit/e56cd8f90c3559baccf8fcf2852ea911235d5944), [`f77c120`](https://github.com/Effect-TS/effect/commit/f77c120d8e04779ddeb8bce8e9cde932f268e4b6), [`b2f95a9`](https://github.com/Effect-TS/effect/commit/b2f95a9c2f2581deb89dc3bae9e89cf819e82923), [`04fd44a`](https://github.com/Effect-TS/effect/commit/04fd44a42abfa8dc2642300dcf49ee48c8ef4539), [`b74333d`](https://github.com/Effect-TS/effect/commit/b74333d83e15b9d042e4698ad23040de60454afe), [`1c40b28`](https://github.com/Effect-TS/effect/commit/1c40b2809503d6aa1358777196fc66317906e657), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`b3901d2`](https://github.com/Effect-TS/effect/commit/b3901d29c543fd5bd05ceec669a17896c8e19006), [`4a0984a`](https://github.com/Effect-TS/effect/commit/4a0984af62738fedf4bd3e87adb4d4d641ce9147), [`fffd88b`](https://github.com/Effect-TS/effect/commit/fffd88b3135abdf928ca7c4b0e00e610985091c7), [`f3f6c1e`](https://github.com/Effect-TS/effect/commit/f3f6c1e02cb543423fcffef5dc2db03fac503588), [`ef07642`](https://github.com/Effect-TS/effect/commit/ef07642dfe671d5258b65d1c1480c4d05c495f15), [`f1bc827`](https://github.com/Effect-TS/effect/commit/f1bc8274a608813d7b09d28dcca04adbf62f8c92), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`081f4d8`](https://github.com/Effect-TS/effect/commit/081f4d8cd06a2ac222d2810b46e61efcee26939e), [`5287b24`](https://github.com/Effect-TS/effect/commit/5287b24f5f8fa094ba20e117bfb1a80fba6d2cf5), [`13d31cf`](https://github.com/Effect-TS/effect/commit/13d31cfc2dde46210e94391b5b6767ae9aeaf2c9), [`acee269`](https://github.com/Effect-TS/effect/commit/acee26944bc89ee554d7b9fadab7443f9edc28a9), [`31170c1`](https://github.com/Effect-TS/effect/commit/31170c19b236c37abb5476c821bc6f5bfa2735ab), [`205ebc7`](https://github.com/Effect-TS/effect/commit/205ebc776062012581e98fced7ced19adfc44ee7), [`ed0ebf8`](https://github.com/Effect-TS/effect/commit/ed0ebf8e5c864d46fed1f232e99c0e680f10a58f), [`a3fd084`](https://github.com/Effect-TS/effect/commit/a3fd08482157bd78b089f77c7b173d54ef68b5cd), [`ee29ddf`](https://github.com/Effect-TS/effect/commit/ee29ddf862c3723ad466abc93ab6f6fe723b2319), [`6086309`](https://github.com/Effect-TS/effect/commit/60863090af8e5af0bfa1435f08dc5390f9993e30), [`4a57af2`](https://github.com/Effect-TS/effect/commit/4a57af24011db1d66e947289d2f7ffc2074696d2), [`660875b`](https://github.com/Effect-TS/effect/commit/660875b4325e6eebb3f04513998301cd2a0847ec), [`8e7c706`](https://github.com/Effect-TS/effect/commit/8e7c706b0aca855489b53d987404566d3e9cb5e7), [`5f63adb`](https://github.com/Effect-TS/effect/commit/5f63adbe75fc9d50d23706a52b3e483ad2a1a01c), [`053bc42`](https://github.com/Effect-TS/effect/commit/053bc42e2a964755611a216e78ed214322efee37), [`c0a1534`](https://github.com/Effect-TS/effect/commit/c0a153494484ecf9f0d0f20895a7a648b4be363b), [`f1e3a37`](https://github.com/Effect-TS/effect/commit/f1e3a378c144f974a6122b299f421b75595af20f), [`cedb01a`](https://github.com/Effect-TS/effect/commit/cedb01a025492a1faf9e59eb23eb96bc3b5e2fff), [`1747440`](https://github.com/Effect-TS/effect/commit/1747440de9a51a56ed3660da748cc01b256adce7), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`b4f1ee2`](https://github.com/Effect-TS/effect/commit/b4f1ee238d96aa78c5f040158cb78671d75b381e), [`a4757f1`](https://github.com/Effect-TS/effect/commit/a4757f1c47067d8d016a6c4a2c541bb8ae520f9b), [`cd122b9`](https://github.com/Effect-TS/effect/commit/cd122b90300d995a237993a2edb7a049785ab6a4), [`5de588b`](https://github.com/Effect-TS/effect/commit/5de588b2472fb0f4eb919766eb8472583a044772), [`3895b9c`](https://github.com/Effect-TS/effect/commit/3895b9cf179262cd277a9c6daafe9050dcf8265e), [`89ce5f3`](https://github.com/Effect-TS/effect/commit/89ce5f3e16e23a193daa475dc72ea8133ae1dacd), [`985de09`](https://github.com/Effect-TS/effect/commit/985de097d75906db2aed784841f81e23cc978b43), [`9800e3a`](https://github.com/Effect-TS/effect/commit/9800e3acc8f36530f671bc8b91558cb112f449a7), [`4dc35f6`](https://github.com/Effect-TS/effect/commit/4dc35f64641746366f867ea3dbfedb9cd4685ada), [`e8eb62b`](https://github.com/Effect-TS/effect/commit/e8eb62b3d0ef27e9761cdc2eb93bdec52d6ee204), [`ecd9993`](https://github.com/Effect-TS/effect/commit/ecd99936112cb69efdb02de3a2fd57f47baefdf3), [`5ab9c08`](https://github.com/Effect-TS/effect/commit/5ab9c08463ce049c45f3502676954a7b72c6b024), [`f5cf965`](https://github.com/Effect-TS/effect/commit/f5cf96548afd51f4b3cf1aea11b04d7f8549ce90), [`a94cbed`](https://github.com/Effect-TS/effect/commit/a94cbed84e9e49bea4bff925599c0f19c4e3deab), [`9160ad7`](https://github.com/Effect-TS/effect/commit/9160ad7d146d4376dd12f7510c025e5b2f638a70), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`52494be`](https://github.com/Effect-TS/effect/commit/52494be9e8eb3bb542d06a3dfefc6bca4e168984), [`5441c8e`](https://github.com/Effect-TS/effect/commit/5441c8e656a6418c0d27feb2df67565a3e1155f4), [`c9b56ab`](https://github.com/Effect-TS/effect/commit/c9b56ab507f224426ee8388dc450da447ec4715f), [`8ef7257`](https://github.com/Effect-TS/effect/commit/8ef72577d1f43212cab87951d659e54e3c8d7d91), [`1519406`](https://github.com/Effect-TS/effect/commit/1519406fed6e8b017ae178dc20bcaa2cf318b570), [`9716990`](https://github.com/Effect-TS/effect/commit/97169902eec3c99baa7f0b2c7b45a0a5eae75819), [`733f75b`](https://github.com/Effect-TS/effect/commit/733f75b7125e3016a975fdd251c0179ae5393786), [`48155c8`](https://github.com/Effect-TS/effect/commit/48155c8ccfc12dcca8a00fa358d50b20c30874e4), [`951d06b`](https://github.com/Effect-TS/effect/commit/951d06b83d459d3e8fa9024e727a5db1662d3322), [`d767b65`](https://github.com/Effect-TS/effect/commit/d767b65a7687e38be23f0b0ee3d52ab5f2360cbe), [`5d52d9d`](https://github.com/Effect-TS/effect/commit/5d52d9d148aaa7f736ed8c310fc8bfa9dc81badf), [`f4151e1`](https://github.com/Effect-TS/effect/commit/f4151e1937c26de14f1d64566f8126173f1b5014), [`e02fbb6`](https://github.com/Effect-TS/effect/commit/e02fbb66f5a0f13dba6c33ef63528a37a17a0676), [`724ce09`](https://github.com/Effect-TS/effect/commit/724ce09650a458d4565e5c7331ea92ca04f08e68), [`dbe91f6`](https://github.com/Effect-TS/effect/commit/dbe91f6961ef9f7e8da910ee5758d9c0d385fca8), [`4c008d2`](https://github.com/Effect-TS/effect/commit/4c008d28b370d817f7ae4579db09836fe084c8d2), [`b650832`](https://github.com/Effect-TS/effect/commit/b6508328708a842f3163467b72486bd228f1a289), [`b46c92f`](https://github.com/Effect-TS/effect/commit/b46c92f3b314f4ffd612b831efa55dd856c587a3), [`5335797`](https://github.com/Effect-TS/effect/commit/5335797003076d9c6fd170da98d779696d555596), [`4b3460d`](https://github.com/Effect-TS/effect/commit/4b3460daa434ec465a95a50704fe1103a9275999), [`6301fd7`](https://github.com/Effect-TS/effect/commit/6301fd710b4325718de2c42997dac28a9e9aa250), [`aebc5c6`](https://github.com/Effect-TS/effect/commit/aebc5c61664b89a840465ec65b79ce635a5ceee8), [`52b2d7b`](https://github.com/Effect-TS/effect/commit/52b2d7b5bd3c7cce3bd5b69c6ab3941004da70f3), [`eec5744`](https://github.com/Effect-TS/effect/commit/eec57445dfa0ef3c5977195ad69415b7e7d42bb6), [`24e0e93`](https://github.com/Effect-TS/effect/commit/24e0e93dc307dc2c2ae86caacb7289e1dab3c103), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`1a7ce81`](https://github.com/Effect-TS/effect/commit/1a7ce8150e3977586c44d8ccb9a8384389bb4d49), [`48f22a7`](https://github.com/Effect-TS/effect/commit/48f22a7d16ae57ee2175d450dafbdeb69e187d2a), [`c96b7f6`](https://github.com/Effect-TS/effect/commit/c96b7f6359662053c3e09344f61dddc7a6caf4ac), [`6d2a942`](https://github.com/Effect-TS/effect/commit/6d2a942ed7cd33b8fd79d549edba33bc9e2a7e3e), [`cc27b19`](https://github.com/Effect-TS/effect/commit/cc27b194b9d13fa3a66ab037e853fca9d41700ff), [`8f9499f`](https://github.com/Effect-TS/effect/commit/8f9499f562729f5f7b08d8bcc4db86b4aeff8a21), [`3eeea73`](https://github.com/Effect-TS/effect/commit/3eeea73cfc3e9b126975c2ddbdb7f7c8c92026e2), [`0a532e5`](https://github.com/Effect-TS/effect/commit/0a532e503f165fdea485a5343fc2f420917e8376), [`f398149`](https://github.com/Effect-TS/effect/commit/f398149c134fd9b67b6cdc52eae3f3248d5c7bbe), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`ace903e`](https://github.com/Effect-TS/effect/commit/ace903e09c2549ceebdec380797beb027cd29f3d), [`e8eb62b`](https://github.com/Effect-TS/effect/commit/e8eb62b3d0ef27e9761cdc2eb93bdec52d6ee204), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`48f22a7`](https://github.com/Effect-TS/effect/commit/48f22a7d16ae57ee2175d450dafbdeb69e187d2a), [`d48506d`](https://github.com/Effect-TS/effect/commit/d48506d97525040aa714305e928126df799795b4), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`d48506d`](https://github.com/Effect-TS/effect/commit/d48506d97525040aa714305e928126df799795b4), [`d48506d`](https://github.com/Effect-TS/effect/commit/d48506d97525040aa714305e928126df799795b4), [`52262be`](https://github.com/Effect-TS/effect/commit/52262be2edce0e350c6ac10f8f725678606399c5), [`1284aa1`](https://github.com/Effect-TS/effect/commit/1284aa183451955ad7921bbe01fd0e095695d444), [`9867b9f`](https://github.com/Effect-TS/effect/commit/9867b9fc69f9cc6c443594fc7eccc7be0c674d9c), [`d0f1a22`](https://github.com/Effect-TS/effect/commit/d0f1a2295155c350b04efb46852cb40032805273), [`979ce39`](https://github.com/Effect-TS/effect/commit/979ce3985d7d62ce2bf240681ca19feda3027452), [`b6d3e67`](https://github.com/Effect-TS/effect/commit/b6d3e67c7cc143cd8470cdf704324e79d23954a9), [`adf6c6c`](https://github.com/Effect-TS/effect/commit/adf6c6cd388af8a3c0c546492e71555368556f6a), [`7314d60`](https://github.com/Effect-TS/effect/commit/7314d605284717aaafe7fc34b88c3c93397e865c), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`1acbd8b`](https://github.com/Effect-TS/effect/commit/1acbd8b44c68ebb23735e9810476b870dbe58aea), [`7bde6cc`](https://github.com/Effect-TS/effect/commit/7bde6ccb2b144fe953ff30a7ef5e1ecc97697146), [`a959a8b`](https://github.com/Effect-TS/effect/commit/a959a8bf21cdb976369f494dc949fa00a050d3e0)]: + - effect@4.0.0-beta.103 + +## 4.0.0-beta.102 + +### Patch Changes + +- Updated dependencies [[`b6392e1`](https://github.com/Effect-TS/effect/commit/b6392e119704553edec1b4fd2869ac0dbec621ef), [`7ed9450`](https://github.com/Effect-TS/effect/commit/7ed945044eb56aa9aeaf62d4746a011c96c58628), [`45762bd`](https://github.com/Effect-TS/effect/commit/45762bd78df9ecd87c98b8d3738cdeeac7d81128), [`a6e8391`](https://github.com/Effect-TS/effect/commit/a6e8391cd31acd898fae18b3f8e7ca4c6f14f065), [`4ac7e8b`](https://github.com/Effect-TS/effect/commit/4ac7e8b136c61a26c3e438c013dfd7349b38e999), [`4cd40f5`](https://github.com/Effect-TS/effect/commit/4cd40f5692477783bef84fed3c5ef1c0cf5602e6), [`6956bc0`](https://github.com/Effect-TS/effect/commit/6956bc0e6cb27f53fbec39d9b18545940f9f598f), [`0e50ec7`](https://github.com/Effect-TS/effect/commit/0e50ec7dbb94390666f292cf9120719bf30a7246), [`9fcdade`](https://github.com/Effect-TS/effect/commit/9fcdade4a8af772b9ccd8b8a24fe8cee0e5d8470), [`57367d5`](https://github.com/Effect-TS/effect/commit/57367d54de55047ff0c5fce9685475e236bf354c), [`35c445f`](https://github.com/Effect-TS/effect/commit/35c445ff18029d192900ea0914c993f58d5cf1a5), [`c917bb9`](https://github.com/Effect-TS/effect/commit/c917bb94a4c1c4e0a24372a8ebb8a5ca232e36b5), [`bc1f358`](https://github.com/Effect-TS/effect/commit/bc1f3583e63344cb2c398d9040d9c975488ed123), [`0e0c9d7`](https://github.com/Effect-TS/effect/commit/0e0c9d7922ff463c1093d9e0576fae12cb0698d5), [`73d40aa`](https://github.com/Effect-TS/effect/commit/73d40aacd8fcae1b48c23f5b0a5c542127401d1d), [`4f1e318`](https://github.com/Effect-TS/effect/commit/4f1e3183f7123591c46224e9c587df7594562a5f), [`9d8d85c`](https://github.com/Effect-TS/effect/commit/9d8d85c1bb7da51970845b8ea830e386e777514a), [`6079fda`](https://github.com/Effect-TS/effect/commit/6079fda7b02f2f01ad91c15ab8c307336f3ba252), [`5101e92`](https://github.com/Effect-TS/effect/commit/5101e92c9c149c153423f43dd7a94f6194653c06), [`d0b3265`](https://github.com/Effect-TS/effect/commit/d0b3265c3262670761471ab3518cf933b1b3b20a), [`7a03c89`](https://github.com/Effect-TS/effect/commit/7a03c893ce6492bf94c0ebfb00b63bf25dcbf83e), [`cea1d9c`](https://github.com/Effect-TS/effect/commit/cea1d9c92601e69ebda040af8a1d860d604d885c), [`078e1f5`](https://github.com/Effect-TS/effect/commit/078e1f5636e31b76a86722a636afc37a8cc25580), [`97bafea`](https://github.com/Effect-TS/effect/commit/97bafeab460833b9781527b437d1cb9cbee63260), [`fab0ab8`](https://github.com/Effect-TS/effect/commit/fab0ab8f7ab15ae596faa4ccf75615a494d11b0b), [`c323d8b`](https://github.com/Effect-TS/effect/commit/c323d8b30dbbe85f9df25b67288b93d5332de333), [`6966353`](https://github.com/Effect-TS/effect/commit/69663534d626003eb10a5e55ab1f13e0379fead1), [`0444004`](https://github.com/Effect-TS/effect/commit/04440041989c1785fe4db286379f2be2c15baa85), [`028bbb3`](https://github.com/Effect-TS/effect/commit/028bbb391e161185da10d974ab33381f769940d7), [`ff5d6e2`](https://github.com/Effect-TS/effect/commit/ff5d6e278a1fdff714315dc1a17075012f05c1f0), [`1bfce93`](https://github.com/Effect-TS/effect/commit/1bfce93e6d2bf0794c11733daf51c2390e7de375), [`7ce815c`](https://github.com/Effect-TS/effect/commit/7ce815cd5af6af991dfc13b890fd22345fc77c20), [`7271a7f`](https://github.com/Effect-TS/effect/commit/7271a7faf1080aa75f2f53ca6a0b5ec9334c1d38), [`475fe5c`](https://github.com/Effect-TS/effect/commit/475fe5c12c2d6504c475797c0634f90da01e1797)]: + - effect@4.0.0-beta.102 + +## 4.0.0-beta.101 + +### Patch Changes + +- Updated dependencies [[`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`b35ed29`](https://github.com/Effect-TS/effect/commit/b35ed2904f01536d303b21f288daf343cf740462), [`dd44624`](https://github.com/Effect-TS/effect/commit/dd446245736a0e88c807a02f03c21450bb9340fa), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`2bae1ac`](https://github.com/Effect-TS/effect/commit/2bae1accce9d3b72cf6d5aefc9b2161af6d88436)]: + - effect@4.0.0-beta.101 + +## 4.0.0-beta.100 + +### Patch Changes + +- Updated dependencies [[`c1288dd`](https://github.com/Effect-TS/effect/commit/c1288dd1a52a2811ab7df57fc4ce236c6be4c745), [`2b58a3d`](https://github.com/Effect-TS/effect/commit/2b58a3dab6bc99776dddaf76e27d811e0f47f3d8), [`6dc83f2`](https://github.com/Effect-TS/effect/commit/6dc83f26ddf20d48db28cf761dd8f3716e5273fb), [`c1e2fe0`](https://github.com/Effect-TS/effect/commit/c1e2fe0cf93564f4d919e3998874c3e70b0cf30f), [`f3fbae8`](https://github.com/Effect-TS/effect/commit/f3fbae8d7bae0d77cb4f35a1598b26c58e3bf94d), [`e000f80`](https://github.com/Effect-TS/effect/commit/e000f80fd55bcd8edc699fdbf4cd109004f4f754), [`f4ee765`](https://github.com/Effect-TS/effect/commit/f4ee7655ee052cf9ba726fd602bb87c89c7c62a9), [`510b55f`](https://github.com/Effect-TS/effect/commit/510b55f3e21750685dbfd5f476a130c1c5af9dbd), [`31d3fc4`](https://github.com/Effect-TS/effect/commit/31d3fc4327c50867bb8d881fa7353aeb03ea2826), [`875e618`](https://github.com/Effect-TS/effect/commit/875e618c3764a7b817ac863d0af86924449528f2), [`688d46a`](https://github.com/Effect-TS/effect/commit/688d46afd0ef923d983ad3d7385f52f217b28d70), [`6ff5023`](https://github.com/Effect-TS/effect/commit/6ff502363b9840a5a5ee0a24bc6cae734ac3a3eb), [`c0333e7`](https://github.com/Effect-TS/effect/commit/c0333e7f755f42ddcca7051e029da8b4eed527bf), [`06e7e8c`](https://github.com/Effect-TS/effect/commit/06e7e8c66015ee318f871b9d2218dee82df2b108), [`eb9b102`](https://github.com/Effect-TS/effect/commit/eb9b10256c8558881b441c2fef833b7037174400), [`8b155da`](https://github.com/Effect-TS/effect/commit/8b155da06e0740c354ec562957a45ab65eb4573b), [`3a87335`](https://github.com/Effect-TS/effect/commit/3a8733564c5db35271aa20564ed0d344daa2a79f)]: + - effect@4.0.0-beta.100 + +## 4.0.0-beta.99 + +### Patch Changes + +- Updated dependencies [[`8ce4795`](https://github.com/Effect-TS/effect/commit/8ce4795ccbaebca4292757db568c005a992546a4), [`80b539f`](https://github.com/Effect-TS/effect/commit/80b539f8aba68f478c75c35c2b4140c4ffc4fada), [`88a54cc`](https://github.com/Effect-TS/effect/commit/88a54cc341006e3ebcb13482c618f62a680ce199), [`e6e6dba`](https://github.com/Effect-TS/effect/commit/e6e6dba6e9d86e7c2ad27dcedf289db76a19697f), [`bfb203e`](https://github.com/Effect-TS/effect/commit/bfb203e95aa439f731acad37fc3a9a831a190f1c), [`2e9a34a`](https://github.com/Effect-TS/effect/commit/2e9a34ac2bece4f3a206160480c991e3841dc67a), [`55d4eb3`](https://github.com/Effect-TS/effect/commit/55d4eb34f2c64d54f6a25a305b5c5438ebd7934e), [`bddb010`](https://github.com/Effect-TS/effect/commit/bddb010eac3d4436cb094edbbee7460c5440c162), [`a328835`](https://github.com/Effect-TS/effect/commit/a328835e50d76bc96648a1c1550456e8c9f81210), [`5560d05`](https://github.com/Effect-TS/effect/commit/5560d05aa6abdd29466d9c3412cc5e648b0adbde), [`8f6e3ad`](https://github.com/Effect-TS/effect/commit/8f6e3adb185b16e8820b98c509b308086f7ff1af), [`46997fa`](https://github.com/Effect-TS/effect/commit/46997fa60401f5e3c93daa4b61f7df8e31caaab4), [`9e6e12d`](https://github.com/Effect-TS/effect/commit/9e6e12d75c118cd265496f2880490d1f33a5c8bf), [`3394b93`](https://github.com/Effect-TS/effect/commit/3394b93d97d6f24fc38670641d1490289ffca7f1), [`febeabc`](https://github.com/Effect-TS/effect/commit/febeabc3f7c31094da000a23edeaabfe2ab00a38), [`54161c9`](https://github.com/Effect-TS/effect/commit/54161c98f6f3569e0c31842f54e6a257f9421c4c), [`385f7a4`](https://github.com/Effect-TS/effect/commit/385f7a4ee4a7359928597ea56d151dbaf5eb5802), [`7eea4d0`](https://github.com/Effect-TS/effect/commit/7eea4d0b73ec554915d7066a71f46326ce2ba45f), [`7543afe`](https://github.com/Effect-TS/effect/commit/7543afea6f4d97d1f1ad876224323838a48daadd), [`44b9cf3`](https://github.com/Effect-TS/effect/commit/44b9cf3d240d726997b4bbcd0ede48e074d3c456), [`7eea4d0`](https://github.com/Effect-TS/effect/commit/7eea4d0b73ec554915d7066a71f46326ce2ba45f), [`0a8aa6a`](https://github.com/Effect-TS/effect/commit/0a8aa6acb90a72b91c24d17133c950e4cacd8abd), [`c8d9fcf`](https://github.com/Effect-TS/effect/commit/c8d9fcf7b030f7c474effbab2764ce7aee1c7209), [`9ca7f9a`](https://github.com/Effect-TS/effect/commit/9ca7f9a69363e4485645966d5a93b8f9597c5206), [`e7aca89`](https://github.com/Effect-TS/effect/commit/e7aca894bb32fbb785b5830837e6061c415a6015), [`55d7560`](https://github.com/Effect-TS/effect/commit/55d75609b8acf8a1b54c1b1c7fbbb65ec741aa3e), [`f809189`](https://github.com/Effect-TS/effect/commit/f809189ddf6b6011ba43a9901baaa734e315da2a), [`88a54cc`](https://github.com/Effect-TS/effect/commit/88a54cc341006e3ebcb13482c618f62a680ce199), [`0ebdbe7`](https://github.com/Effect-TS/effect/commit/0ebdbe74463dc84385956d0b1e8c2b79ebab5400), [`7517d09`](https://github.com/Effect-TS/effect/commit/7517d09f12a0b183a81bd425962c4e280a68b05d), [`212493b`](https://github.com/Effect-TS/effect/commit/212493b9a1eb98cd1ef6959c707a2e5784a5ae91), [`88a54cc`](https://github.com/Effect-TS/effect/commit/88a54cc341006e3ebcb13482c618f62a680ce199), [`80ea8cb`](https://github.com/Effect-TS/effect/commit/80ea8cb9222ca73f564c8267ab2f82966fea027a), [`8df19f4`](https://github.com/Effect-TS/effect/commit/8df19f4fe81d90cc33ace88b9a77e5534f82d604)]: + - effect@4.0.0-beta.99 + +## 4.0.0-beta.98 + +### Patch Changes + +- Updated dependencies [[`989603b`](https://github.com/Effect-TS/effect-smol/commit/989603b60ab1197b64acf214208e0d370cd1f842), [`214c458`](https://github.com/Effect-TS/effect-smol/commit/214c458084bb6995d543cd37d1055f24be3d454e), [`a037273`](https://github.com/Effect-TS/effect-smol/commit/a0372736ac34796969b051bbba4717d7983f1ebe), [`97fdaa9`](https://github.com/Effect-TS/effect-smol/commit/97fdaa9c1f522c65e579365d314a07878e2b904f), [`b24d248`](https://github.com/Effect-TS/effect-smol/commit/b24d248c8df44222ce642087cde2bd859a2dc709), [`19c222c`](https://github.com/Effect-TS/effect-smol/commit/19c222cac2353a3d7b7733caecb00556fffe9a5c), [`eec85dd`](https://github.com/Effect-TS/effect-smol/commit/eec85ddba09ea326fd268ee33eeffd47e50d4671), [`0082f4f`](https://github.com/Effect-TS/effect-smol/commit/0082f4f74fb139fd578f87f0a790e845133983dc), [`8849052`](https://github.com/Effect-TS/effect-smol/commit/884905232d1e9a365e046d8dde27bf9c5707f57f), [`c15e16a`](https://github.com/Effect-TS/effect-smol/commit/c15e16ad130d1fbde25d912b7ac55995066cb35b), [`01d00a3`](https://github.com/Effect-TS/effect-smol/commit/01d00a3abfbf1f37996cdbe738ea5137c646cdd7), [`8bd4589`](https://github.com/Effect-TS/effect-smol/commit/8bd458975a1b3a8ed042eccf317b93d28ded91e7), [`0082f4f`](https://github.com/Effect-TS/effect-smol/commit/0082f4f74fb139fd578f87f0a790e845133983dc), [`6e08428`](https://github.com/Effect-TS/effect-smol/commit/6e08428d980501b856f846ad3f3f0e4ea46e7786), [`388dcf9`](https://github.com/Effect-TS/effect-smol/commit/388dcf953f65d317547f34d40e6443c5f264205f), [`2b7ce2b`](https://github.com/Effect-TS/effect-smol/commit/2b7ce2b513e7ec2a77822f1116dc6ffb6ba93f4e), [`87bea7e`](https://github.com/Effect-TS/effect-smol/commit/87bea7e16259246f3bcdf565446394751abca953), [`ce38dc3`](https://github.com/Effect-TS/effect-smol/commit/ce38dc33bda805a684432cca071f4dc3c6b9a1ba), [`a807cd1`](https://github.com/Effect-TS/effect-smol/commit/a807cd170341deca8a1cfb52c4222585f2431bb9), [`fd8a356`](https://github.com/Effect-TS/effect-smol/commit/fd8a356f06a8c9ce4e7e0a13fc4021c178ed31de), [`c2a5edc`](https://github.com/Effect-TS/effect-smol/commit/c2a5edc3abd31ad5bc123362bc1213e03e4095c3), [`5946da3`](https://github.com/Effect-TS/effect-smol/commit/5946da3804a1be5e752b05b96bd058cdba50a1bf), [`4ae0c5f`](https://github.com/Effect-TS/effect-smol/commit/4ae0c5ffcbe6c56ddfcb05c639112a079483539e), [`5b2a0bc`](https://github.com/Effect-TS/effect-smol/commit/5b2a0bceea3a28a33a58555210c90a415dc74a76), [`72ac585`](https://github.com/Effect-TS/effect-smol/commit/72ac585884befde6af9208da738699a93f1bae79), [`5e8c1b8`](https://github.com/Effect-TS/effect-smol/commit/5e8c1b82bfafa121311f987a49ab75395e3647a7), [`0f9c078`](https://github.com/Effect-TS/effect-smol/commit/0f9c07841b04183f485ee6e6458de73b290b09f5)]: + - effect@4.0.0-beta.98 + +## 4.0.0-beta.97 + +### Patch Changes + +- Updated dependencies []: + - effect@4.0.0-beta.97 + +## 4.0.0-beta.96 + +### Patch Changes + +- Updated dependencies [[`1503f45`](https://github.com/Effect-TS/effect-smol/commit/1503f45cb5bb2a74f4705252ec505a1f0ade7e62), [`57fe793`](https://github.com/Effect-TS/effect-smol/commit/57fe79316ffbc380b30626a168981fb26ae97459), [`0c2f78f`](https://github.com/Effect-TS/effect-smol/commit/0c2f78f695ec474e1ff5474da183577975e418f5), [`0c2f78f`](https://github.com/Effect-TS/effect-smol/commit/0c2f78f695ec474e1ff5474da183577975e418f5), [`0c2f78f`](https://github.com/Effect-TS/effect-smol/commit/0c2f78f695ec474e1ff5474da183577975e418f5), [`97f29df`](https://github.com/Effect-TS/effect-smol/commit/97f29df457f7ffd07cfb4b379315c12c086af805)]: + - effect@4.0.0-beta.96 + +## 4.0.0-beta.95 + +### Patch Changes + +- Updated dependencies [[`a482442`](https://github.com/Effect-TS/effect-smol/commit/a482442abdeb490e9652b854ec3495e4aa7273e7), [`fbefa85`](https://github.com/Effect-TS/effect-smol/commit/fbefa850fab2f0a302c20614496aeaaa2a8b5590), [`0b4a32f`](https://github.com/Effect-TS/effect-smol/commit/0b4a32f4260f0d8500942a133001b0d349328102), [`18a49e1`](https://github.com/Effect-TS/effect-smol/commit/18a49e1786679456258002ff9397faf02f678c2d), [`266cb90`](https://github.com/Effect-TS/effect-smol/commit/266cb90bb2c17aabc40563c32db334f09ba3d74b), [`912f095`](https://github.com/Effect-TS/effect-smol/commit/912f095a34572bbd3cedf6edb27878443e3e4a95), [`a6718f9`](https://github.com/Effect-TS/effect-smol/commit/a6718f9e00a15ca903b0732da46116cbf3d6aca7), [`bef5154`](https://github.com/Effect-TS/effect-smol/commit/bef51540a243aa2f872a00c01d0cd58b7a769baa), [`18e0564`](https://github.com/Effect-TS/effect-smol/commit/18e0564bd0f8ebbdfcaf1e2c21529948e9e4a81d), [`fb50f14`](https://github.com/Effect-TS/effect-smol/commit/fb50f14fc3657c1973785aa5b72ecf0b0d28e0b2)]: + - effect@4.0.0-beta.95 + +## 4.0.0-beta.94 + +### Patch Changes + +- Updated dependencies [[`95a0e9b`](https://github.com/Effect-TS/effect-smol/commit/95a0e9bb62797af0e81c9998773405f248f218c5), [`a0a3490`](https://github.com/Effect-TS/effect-smol/commit/a0a3490bbce765f199d8e077aceac504f0462e63), [`f11ce73`](https://github.com/Effect-TS/effect-smol/commit/f11ce73af60823754dc24194f4ffc561b9ea1c2d), [`ff30b6e`](https://github.com/Effect-TS/effect-smol/commit/ff30b6e7c2c63ffc56a4c5818d6d86b01b5ad528), [`1caab3c`](https://github.com/Effect-TS/effect-smol/commit/1caab3cc30f626efbf15e59d74f539a487e5c85c), [`aa80c47`](https://github.com/Effect-TS/effect-smol/commit/aa80c4775a04db87553e5568764cab7e32a72814), [`c2ae4fc`](https://github.com/Effect-TS/effect-smol/commit/c2ae4fce2f03a4cd1861c2b1179da7df656e662d), [`a0a3490`](https://github.com/Effect-TS/effect-smol/commit/a0a3490bbce765f199d8e077aceac504f0462e63)]: + - effect@4.0.0-beta.94 + +## 4.0.0-beta.93 + +### Patch Changes + +- Updated dependencies [[`00652fe`](https://github.com/Effect-TS/effect-smol/commit/00652fe95c18f87208e91343eb8bf218faa2f677), [`6c58167`](https://github.com/Effect-TS/effect-smol/commit/6c5816746eaf91d2a3c7c899c5720809fa230ae3), [`2bc5415`](https://github.com/Effect-TS/effect-smol/commit/2bc541501a7ef89e542d7cb98e96beb53cd205cc), [`e11cccc`](https://github.com/Effect-TS/effect-smol/commit/e11cccc7d5fe631abccc7d6e3bd296938de0fa2e), [`ba7e77e`](https://github.com/Effect-TS/effect-smol/commit/ba7e77e046b8641a3a4e9750bb88ca4a1d063d3f), [`5713ee7`](https://github.com/Effect-TS/effect-smol/commit/5713ee7edbc3054efde407b2286bbfd45bbc6e1c)]: + - effect@4.0.0-beta.93 + +## 4.0.0-beta.92 + +### Patch Changes + +- Updated dependencies [[`affdc13`](https://github.com/Effect-TS/effect-smol/commit/affdc139045cc325dce321a84a580fdc1b2da7b9)]: + - effect@4.0.0-beta.92 + +## 4.0.0-beta.91 + +### Patch Changes + +- Updated dependencies [[`b135b25`](https://github.com/Effect-TS/effect-smol/commit/b135b2517fca9e7839734ace3699a7dfa75b9075), [`aaa21a3`](https://github.com/Effect-TS/effect-smol/commit/aaa21a369a171c600db294f2a4f640583043e150), [`3475ee6`](https://github.com/Effect-TS/effect-smol/commit/3475ee6c2bda6b05c6d7a12ce30c8bb840b5b1a6)]: + - effect@4.0.0-beta.91 + ## 4.0.0-beta.90 ### Patch Changes diff --git a/repos/effect/packages/platform-browser/docgen.json b/repos/effect/packages/platform-browser/docgen.json deleted file mode 100644 index 408bcceb8d..0000000000 --- a/repos/effect/packages/platform-browser/docgen.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "$schema": "../../node_modules/@effect/docgen/schema.json", - "srcLink": "https://github.com/Effect-TS/effect/tree/main/packages/platform-browser/src/", - "exclude": ["src/internal/**/*.ts"], - "tscExecutable": "tsgo", - "examplesCompilerOptions": { - "noEmit": true, - "strict": true, - "skipLibCheck": true, - "moduleResolution": "Bundler", - "module": "ES2022", - "target": "ES2022", - "lib": ["ESNext", "DOM", "DOM.Iterable"], - "types": ["node"], - "rewriteRelativeImportExtensions": true, - "allowImportingTsExtensions": true, - "plugins": [ - { "name": "@effect/language-service", "includeSuggestionsInTsc": false } - ] - } -} diff --git a/repos/effect/packages/platform-browser/package.json b/repos/effect/packages/platform-browser/package.json index 20c64de9f3..05ec087eb8 100644 --- a/repos/effect/packages/platform-browser/package.json +++ b/repos/effect/packages/platform-browser/package.json @@ -1,17 +1,17 @@ { "name": "@effect/platform-browser", "type": "module", - "version": "4.0.0-beta.90", + "version": "4.0.0-beta.107", "license": "MIT", "description": "Platform specific implementations for the browser", "homepage": "https://effect.website", "repository": { "type": "git", - "url": "https://github.com/Effect-TS/effect-smol.git", + "url": "https://github.com/Effect-TS/effect.git", "directory": "packages/platform-browser" }, "bugs": { - "url": "https://github.com/Effect-TS/effect-smol/issues" + "url": "https://github.com/Effect-TS/effect/issues" }, "tags": [ "browser", @@ -31,6 +31,7 @@ ".": "./src/index.ts", "./*": "./src/*.ts", "./internal/*": null, + "./index": null, "./*/index": null }, "files": [ @@ -38,7 +39,10 @@ "dist/**/*.js", "dist/**/*.js.map", "dist/**/*.d.ts", - "dist/**/*.d.ts.map" + "dist/**/*.d.ts.map", + "AGENTS.md", + "CLAUDE.md", + "ai-docs/**/*" ], "publishConfig": { "access": "public", @@ -48,27 +52,22 @@ ".": "./dist/index.js", "./*": "./dist/*.js", "./internal/*": null, + "./index": null, "./*/index": null } }, "scripts": { "codegen": "effect-utils codegen", "build": "tsc -b tsconfig.json && pnpm babel", - "build:tsgo": "tsgo -b tsconfig.json && pnpm babel", "babel": "babel dist --plugins annotate-pure-calls --out-dir dist --source-maps", - "check": "tsc -b tsconfig.json", - "test": "vitest", - "coverage": "vitest --coverage" + "check": "tsc -b tsconfig.json" }, "peerDependencies": { "effect": "workspace:^" }, "devDependencies": { "effect": "workspace:^", - "mock-xmlhttprequest": "^8.4.1", - "fake-indexeddb": "^6.2.5" - }, - "dependencies": { - "multipasta": "^0.2.7" + "fake-indexeddb": "^6.2.5", + "mock-xmlhttprequest": "^8.4.1" } } diff --git a/repos/effect/packages/platform-browser/src/BrowserCrypto.ts b/repos/effect/packages/platform-browser/src/BrowserCrypto.ts index 2734cada34..bf00b051ac 100644 --- a/repos/effect/packages/platform-browser/src/BrowserCrypto.ts +++ b/repos/effect/packages/platform-browser/src/BrowserCrypto.ts @@ -22,7 +22,7 @@ import * as PlatformError from "effect/PlatformError" * Use to override the browser `Crypto` object used by the platform crypto * layer. * - * @category references + * @category services * @since 1.0.0 */ export const WebCrypto = Context.Reference("@effect/platform-browser/Crypto/WebCrypto", { @@ -60,7 +60,9 @@ export const layer: Layer.Layer = Layer.effect( } const randomBytes = (size: number): Uint8Array => { const bytes = new Uint8Array(size) - crypto.getRandomValues(bytes) + for (let i = 0; i < bytes.length; i += 65_536) { + crypto.getRandomValues(bytes.subarray(i, i + 65_536)) + } return bytes } diff --git a/repos/effect/packages/platform-browser/src/BrowserHttpClient.ts b/repos/effect/packages/platform-browser/src/BrowserHttpClient.ts index 29e68516b4..2d506a0783 100644 --- a/repos/effect/packages/platform-browser/src/BrowserHttpClient.ts +++ b/repos/effect/packages/platform-browser/src/BrowserHttpClient.ts @@ -28,8 +28,8 @@ import * as HttpClientError from "effect/unstable/http/HttpClientError" import type * as HttpClientRequest from "effect/unstable/http/HttpClientRequest" import * as HttpClientResponse from "effect/unstable/http/HttpClientResponse" import * as HttpIncomingMessage from "effect/unstable/http/HttpIncomingMessage" +import * as HeaderParser from "effect/unstable/http/MultipartParser/HeadersParser" import * as UrlParams from "effect/unstable/http/UrlParams" -import * as HeaderParser from "multipasta/HeadersParser" // ============================================================================= // Fetch @@ -39,14 +39,14 @@ export { /** * Context reference for the `fetch` implementation used by the fetch-based HTTP client. * - * @category fetch + * @category services * @since 4.0.0 */ Fetch, /** * Layer that provides an `HttpClient` implementation backed by the configured `Fetch` function. * - * @category fetch + * @category layers * @since 4.0.0 */ layer as layerFetch, @@ -58,7 +58,7 @@ export { * Use to provide default credentials, cache, redirect, integrity, or other * fetch options for browser HTTP requests. * - * @category fetch + * @category services * @since 4.0.0 */ RequestInit @@ -87,7 +87,7 @@ export type XHRResponseType = "arraybuffer" | "text" * @see {@link XHRResponseType} for the allowed response body modes * @see {@link withXHRArrayBuffer} for scoping XHR response handling to `ArrayBuffer` * - * @category references + * @category services * @since 4.0.0 */ export const CurrentXHRResponseType: Context.Reference = Context.Reference( @@ -98,7 +98,7 @@ export const CurrentXHRResponseType: Context.Reference = Contex /** * Runs an effect with `CurrentXHRResponseType` set to `"arraybuffer"` so the XHR HTTP client receives response bodies as `ArrayBuffer` values. * - * @category references + * @category providing services * @since 4.0.0 */ export const withXHRArrayBuffer = ( @@ -399,7 +399,11 @@ class ClientResponseImpl extends IncomingMessageImpl { - return Effect.die("Not implemented") + return Effect.flatMap(this.arrayBuffer, (body) => + Effect.tryPromise({ + try: () => new globalThis.Response(body, { headers: this.headers }).formData(), + catch: this.onError + })) } override toString(): string { diff --git a/repos/effect/packages/platform-browser/src/BrowserKeyValueStore.ts b/repos/effect/packages/platform-browser/src/BrowserKeyValueStore.ts index 72840e3968..dd63cbffe3 100644 --- a/repos/effect/packages/platform-browser/src/BrowserKeyValueStore.ts +++ b/repos/effect/packages/platform-browser/src/BrowserKeyValueStore.ts @@ -74,8 +74,11 @@ export const layerIndexedDb = (options?: { return KeyValueStore.make({ clear: Effect.suspend(() => { - const store = getKvsEntriesStore(db, "readwrite") - return idbRequest({ method: "clear", message: "Failed to clear backing store" }, () => store.clear()) + return idbWriteRequest( + db, + { method: "clear", message: "Failed to clear backing store" }, + (store) => store.clear() + ) }), get: (key: string) => Effect.map( @@ -103,10 +106,10 @@ export const layerIndexedDb = (options?: { ), set: (key: string, value: string | Uint8Array) => Effect.asVoid(Effect.suspend(() => { - const store = getKvsEntriesStore(db, "readwrite") - return idbRequest( + return idbWriteRequest( + db, { method: "set", message: "Failed to set value in backing store", key }, - () => store.put({ key, value }) + (store) => store.put({ key, value }) ) })), size: Effect.suspend(() => { @@ -118,10 +121,10 @@ export const layerIndexedDb = (options?: { }), remove: (key: string) => Effect.asVoid(Effect.suspend(() => { - const store = getKvsEntriesStore(db, "readwrite") - return idbRequest( + return idbWriteRequest( + db, { method: "remove", message: "Failed to remove value from backing store", key }, - () => store.delete(key) + (store) => store.delete(key) ) })) }) @@ -171,6 +174,45 @@ const idbRequest = ( )) }) +const idbWriteRequest = ( + db: IDBDatabase, + failArgs: { method: string; message: string; key?: string }, + evaluate: (store: IDBObjectStore) => IDBRequest +): Effect.Effect => + Effect.callback((resume) => { + const transaction = db.transaction(entriesStoreName, "readwrite") + const request = evaluate(transaction.objectStore(entriesStoreName)) + let result: A + let done = false + + const fail = (cause: unknown) => { + if (done) return + done = true + resume(Effect.fail(new KeyValueStore.KeyValueStoreError({ ...failArgs, cause }))) + } + + if (request.readyState === "done") { + result = request.result + } else { + request.onsuccess = () => { + result = request.result + } + request.onerror = () => fail(request.error) + } + + transaction.oncomplete = () => { + if (done) return + done = true + resume(Effect.succeed(result!)) + } + transaction.onerror = () => fail(transaction.error) + transaction.onabort = () => fail(transaction.error) + + return Effect.sync(() => { + if (!done) transaction.abort() + }) + }) + const getKvsEntriesStore = (db: IDBDatabase, mode: IDBTransactionMode) => { const transaction = db.transaction(entriesStoreName, mode) return transaction.objectStore(entriesStoreName) diff --git a/repos/effect/packages/platform-browser/src/BrowserRuntime.ts b/repos/effect/packages/platform-browser/src/BrowserRuntime.ts index 0cc99d7f82..ef1474e66d 100644 --- a/repos/effect/packages/platform-browser/src/BrowserRuntime.ts +++ b/repos/effect/packages/platform-browser/src/BrowserRuntime.ts @@ -29,7 +29,7 @@ import { makeRunMain, type Teardown } from "effect/Runtime" * The `beforeunload` interruption is best-effort. Browser teardown may prevent * asynchronous finalizers, network work, timers, or prompts from completing. * - * @category Runtime + * @category running * @since 4.0.0 */ export const runMain: { diff --git a/repos/effect/packages/platform-browser/src/BrowserStream.ts b/repos/effect/packages/platform-browser/src/BrowserStream.ts index 5b2fa79dd7..56d7333c8e 100644 --- a/repos/effect/packages/platform-browser/src/BrowserStream.ts +++ b/repos/effect/packages/platform-browser/src/BrowserStream.ts @@ -19,7 +19,7 @@ import * as Stream from "effect/Stream" * buffer size by passing an object as the second argument with the `bufferSize` * field. * - * @category streams + * @category constructors * @since 4.0.0 */ export const fromEventListenerWindow = ( @@ -41,7 +41,7 @@ export const fromEventListenerWindow = ( * buffer size by passing an object as the second argument with the `bufferSize` * field. * - * @category streams + * @category constructors * @since 4.0.0 */ export const fromEventListenerDocument = ( diff --git a/repos/effect/packages/platform-browser/src/BrowserWorkerRunner.ts b/repos/effect/packages/platform-browser/src/BrowserWorkerRunner.ts index e565fb9c85..0c51c8b031 100644 --- a/repos/effect/packages/platform-browser/src/BrowserWorkerRunner.ts +++ b/repos/effect/packages/platform-browser/src/BrowserWorkerRunner.ts @@ -81,6 +81,7 @@ export const make = (self: MessagePort | Window): WorkerRunner.WorkerRunnerPlatf return Deferred.doneUnsafe(closeLatch, Exit.void) } ports.delete(portId) + Queue.offerUnsafe(disconnects, portId) Effect.runFork(Scope.close(port[1], Exit.void)) } } @@ -122,7 +123,7 @@ export const make = (self: MessagePort | Window): WorkerRunner.WorkerRunnerPlatf portScope, Effect.sync(() => { port.removeEventListener("message", onMsg) - port.removeEventListener("messageerror", onError) + port.removeEventListener("messageerror", onMessageError) port.close() }) )) diff --git a/repos/effect/packages/platform-browser/src/Clipboard.ts b/repos/effect/packages/platform-browser/src/Clipboard.ts index 3a24d8733c..a68c41937d 100644 --- a/repos/effect/packages/platform-browser/src/Clipboard.ts +++ b/repos/effect/packages/platform-browser/src/Clipboard.ts @@ -38,7 +38,7 @@ const ErrorTypeId = "~@effect/platform-browser/Clipboard/ClipboardError" * MIME type support varies by browser. Failed browser operations are surfaced * as `ClipboardError`. * - * @category models + * @category services * @since 4.0.0 */ export interface Clipboard { diff --git a/repos/effect/packages/platform-browser/src/Geolocation.ts b/repos/effect/packages/platform-browser/src/Geolocation.ts index 309af083e7..dc687bee65 100644 --- a/repos/effect/packages/platform-browser/src/Geolocation.ts +++ b/repos/effect/packages/platform-browser/src/Geolocation.ts @@ -44,7 +44,7 @@ const ErrorTypeId = "~@effect/platform-browser/Geolocation/GeolocationError" * @see {@link GeolocationError} for represented browser geolocation failures * @see {@link layer} for the browser-backed service implementation * - * @category models + * @category services * @since 4.0.0 */ export interface Geolocation { diff --git a/repos/effect/packages/platform-browser/src/IndexedDb.ts b/repos/effect/packages/platform-browser/src/IndexedDb.ts index 6fdf18d233..aaa96d079a 100644 --- a/repos/effect/packages/platform-browser/src/IndexedDb.ts +++ b/repos/effect/packages/platform-browser/src/IndexedDb.ts @@ -19,7 +19,7 @@ const TypeId = "~@effect/platform-browser/IndexedDb" /** * Service interface that provides the browser `indexedDB` factory and `IDBKeyRange` constructor. * - * @category models + * @category services * @since 4.0.0 */ export interface IndexedDb { @@ -40,7 +40,7 @@ export const IndexedDb: Context.Service = Context.Service< const IDBFlatKey = Schema.Union([ Schema.String, Schema.Number.check(Schema.makeFilter((input) => !Number.isNaN(input))), - Schema.DateValid, + Schema.Date, Schema.declare( (input): input is BufferSource => input instanceof ArrayBuffer || @@ -93,7 +93,7 @@ export const make = (impl: Omit): IndexedDb => Indexed /** * Layer that provides `IndexedDb` from `window.indexedDB` and `window.IDBKeyRange`, failing with a config error when they are unavailable. * - * @category constructors + * @category layers * @since 4.0.0 */ export const layerWindow: Layer.Layer = Layer.effect( diff --git a/repos/effect/packages/platform-browser/src/IndexedDbDatabase.ts b/repos/effect/packages/platform-browser/src/IndexedDbDatabase.ts index 0e98f997da..77f07cc76c 100644 --- a/repos/effect/packages/platform-browser/src/IndexedDbDatabase.ts +++ b/repos/effect/packages/platform-browser/src/IndexedDbDatabase.ts @@ -31,12 +31,6 @@ const SchemaProto = { [TypeId]: { _A: (_: never) => _ }, - ...Effectable.Prototype>({ - label: "IndexedDbSchema", - evaluate() { - return this.getQueryBuilder - } - }), get getQueryBuilder() { const self = this as unknown as IndexedDbSchema return IndexedDbDatabase.useSync(({ database, IDBKeyRange, reactivity }) => @@ -48,6 +42,12 @@ const SchemaProto = { }) ) }, + ...Effectable.Prototype>({ + label: "IndexedDbSchema", + evaluate() { + return this.getQueryBuilder + } + }), add( this: IndexedDbSchema, version: Version, @@ -126,7 +126,7 @@ export class IndexedDbDatabaseError extends Data.TaggedError( * @see {@link IndexedDb.IndexedDb} for the lower-level browser IndexedDB primitives * @see {@link make} for creating a schema that provides this service as a layer * - * @category models + * @category services * @since 4.0.0 */ export class IndexedDbDatabase extends Context.Service< @@ -234,7 +234,7 @@ export interface Transaction< /** * Extracts the string-literal index names defined by an `IndexedDbTable`. * - * @category models + * @category utility types * @since 4.0.0 */ export type IndexFromTable = IsStringLiteral< @@ -245,7 +245,7 @@ export type IndexFromTable
= IsString /** * Extracts the valid index names for a table name within an IndexedDB version. * - * @category models + * @category utility types * @since 4.0.0 */ export type IndexFromTableName< @@ -478,6 +478,12 @@ const layer = ( Effect.provideService(IndexedDbQueryBuilder.IndexedDbTransaction, transaction) ) fiber = runForkWith(effect) + fiber.addObserver((exit) => { + if (exit._tag === "Failure") { + transaction.abort() + resume(exit) + } + }) fiber.currentDispatcher.flush() } diff --git a/repos/effect/packages/platform-browser/src/IndexedDbQueryBuilder.ts b/repos/effect/packages/platform-browser/src/IndexedDbQueryBuilder.ts index e5682d9a23..a294a6aaf6 100644 --- a/repos/effect/packages/platform-browser/src/IndexedDbQueryBuilder.ts +++ b/repos/effect/packages/platform-browser/src/IndexedDbQueryBuilder.ts @@ -136,13 +136,17 @@ export interface IndexedDbQueryBuilder< readonly tables: Tables readonly mode: Mode readonly durability?: IDBTransactionDurability - }) => (effect: Effect.Effect) => Effect.Effect> + }) => (effect: Effect.Effect) => Effect.Effect< + A, + E | IndexedDbQueryError, + Exclude + > } /** * Valid key-path type for a table schema, using encoded fields whose values are IndexedDB-valid keys. * - * @category models + * @category utility types * @since 4.0.0 */ export type KeyPath = @@ -152,7 +156,7 @@ export type KeyPath = /** * Valid numeric key-path type for a table schema, used for auto-increment key paths. * - * @category models + * @category utility types * @since 4.0.0 */ export type KeyPathNumber = @@ -168,7 +172,7 @@ export declare namespace IndexedDbQuery { /** * Decoded row type returned by select queries, adding a `key` field when the table does not define a key path. * - * @category models + * @category utility types * @since 4.0.0 */ export type SelectType< @@ -181,7 +185,7 @@ export declare namespace IndexedDbQuery { /** * Input type for insert and upsert operations, adjusted for auto-increment keys and out-of-line keys. * - * @category models + * @category utility types * @since 4.0.0 */ export type ModifyType< @@ -215,7 +219,7 @@ export declare namespace IndexedDbQuery { /** * Value type accepted by `equals` comparisons for a table key path or index. * - * @category models + * @category utility types * @since 4.0.0 */ export type EqualsType< @@ -229,7 +233,7 @@ export declare namespace IndexedDbQuery { /** * Value type accepted by range comparisons for a table key path or index, including partial tuples for compound indexes. * - * @category models + * @category utility types * @since 4.0.0 */ export type ExtractIndexType< @@ -248,7 +252,7 @@ export declare namespace IndexedDbQuery { /** * Mutation input type for insert and upsert operations, including any required key fields. * - * @category models + * @category utility types * @since 4.0.0 */ export type ModifyWithKey
= ModifyType
@@ -719,7 +723,7 @@ export declare namespace IndexedDbQuery { /** * Service tag for the active `IDBTransaction` used to share a transaction across IndexedDB query effects. * - * @category models + * @category services * @since 4.0.0 */ export class IndexedDbTransaction extends Context.Service()( @@ -752,6 +756,7 @@ const applyDelete = (query: IndexedDbQuery.Delete) => durability: query.delete.from.table.durability }) const objectStore = transaction.objectStore(query.delete.from.table.tableName) + const store = query.delete.index === undefined ? objectStore : objectStore.index(query.delete.index) const predicate = query.predicate let keyRange: globalThis.IDBKeyRange | undefined = undefined @@ -782,8 +787,8 @@ const applyDelete = (query: IndexedDbQuery.Delete) => let request: globalThis.IDBRequest - if (query.limitValue !== undefined || predicate) { - const cursorRequest = objectStore.openCursor() + if (query.delete.index !== undefined || query.limitValue !== undefined || predicate) { + const cursorRequest = store.openCursor(keyRange) let count = 0 cursorRequest.onerror = () => { @@ -897,7 +902,7 @@ const applySelect = Effect.fnUntraced(function*( const keyPath = query.from.table.keyPath const predicate = query.predicate - const data = predicate || keyPath === undefined || query.offsetValue !== undefined ? + const data = predicate || keyPath === undefined || query.offsetValue !== undefined || query.reverseValue ? yield* Effect.callback, IndexedDbQueryError>((resume) => { const { keyRange, store } = getReadonlyObjectStore(query) @@ -1001,7 +1006,13 @@ const applyFirst = Effect.fnUntraced(function*( } request.onsuccess = () => { - resume(Effect.succeed(request.result)) + if (request.result === undefined) { + resume( + Effect.fail(new Cause.NoSuchElementError(`No such element in table ${query.select.from.table.tableName}`)) + ) + } else { + resume(Effect.succeed(request.result)) + } } } else { const request = store.openCursor() @@ -1331,7 +1342,6 @@ const FromProto: Omit< | "countCache" | "deleteCache" > = { - ...CommonProto, select>( this: IndexedDbQuery.From, index?: Index @@ -1392,7 +1402,8 @@ const FromProto: Omit< database: self.database.current, table: self.table }) - } + }, + ...CommonProto } const makeFrom = < @@ -1516,7 +1527,7 @@ const DeleteProto: Omit< filter(this: IndexedDbQuery.Delete, filter: (value: IndexedDbTable.Encoded) => boolean) { const prev = this.predicate return makeDelete({ - delete: this.delete, + ...this, predicate: prev ? (item) => prev(item) && filter(item) : filter }) }, @@ -1760,18 +1771,22 @@ const SelectProto: Omit< }) { const limit = this.limitValue const chunkSize = Math.min(options?.chunkSize ?? 100, limit ?? Number.MAX_SAFE_INTEGER) - const initial = this.limit(chunkSize) + const initialOffset = this.offsetValue ?? 0 return Stream.suspend(() => { let total = 0 + const initial = this.limit(chunkSize) return Stream.paginate(initial, (select) => Effect.map( applySelect(select as any), (data) => { total += data.length - ;(select as any).offsetValue = total const reachedLimit = limit && total >= limit const isPartial = data.length < chunkSize - return [data, isPartial || reachedLimit ? Option.none() : Option.some(select)] as const + const next = makeSelect({ + ...select, + offsetValue: initialOffset + total + }) + return [data, isPartial || reachedLimit ? Option.none() : Option.some(next)] as const } )) }) @@ -1951,7 +1966,6 @@ const QueryBuilderProto: Omit< | "IDBTransaction" | "reactivity" > = { - ...CommonProto, use(this: IndexedDbQueryBuilder, f: (database: globalThis.IDBDatabase) => any) { return Effect.try({ try: () => f(this.database.current), @@ -1979,6 +1993,7 @@ const QueryBuilderProto: Omit< const self = this as IndexedDbQueryBuilder return applyClearAll({ database: self.database.current }) }, + ...CommonProto, withTransaction(this: IndexedDbQueryBuilder, options: { readonly tables: NonEmptyReadonlyArray readonly mode: globalThis.IDBTransactionMode @@ -1987,7 +2002,11 @@ const QueryBuilderProto: Omit< return (effect) => Effect.suspend(() => { const transaction = this.database.current.transaction(options.tables, options.mode, options) - return Effect.provideService(effect, IndexedDbTransaction, transaction) + return Effect.provideService(effect, IndexedDbTransaction, transaction).pipe( + Effect.onExit((exit) => + exit._tag === "Success" ? awaitTransaction(transaction) : abortTransaction(transaction) + ) + ) }).pipe( // To prevent async gaps between transaction queries Effect.provideService(References.PreventSchedulerYield, true) @@ -1995,6 +2014,30 @@ const QueryBuilderProto: Omit< } } +const abortTransaction = (transaction: globalThis.IDBTransaction) => + Effect.try({ + try: () => transaction.abort(), + catch: () => undefined + }).pipe(Effect.ignore) + +const awaitTransaction = (transaction: globalThis.IDBTransaction) => + Effect.callback((resume) => { + transaction.oncomplete = () => { + resume(Effect.void) + } + transaction.onabort = () => { + resume( + Effect.fail( + new IndexedDbQueryError({ + reason: "TransactionError", + cause: transaction.error + }) + ) + ) + } + return abortTransaction(transaction) + }) + /** * Creates an `IndexedDbQueryBuilder` from an open database reference, key-range constructor, table map, and reactivity service. * diff --git a/repos/effect/packages/platform-browser/src/IndexedDbTable.ts b/repos/effect/packages/platform-browser/src/IndexedDbTable.ts index 125d881b54..12be0e9871 100644 --- a/repos/effect/packages/platform-browser/src/IndexedDbTable.ts +++ b/repos/effect/packages/platform-browser/src/IndexedDbTable.ts @@ -23,7 +23,7 @@ const TypeId = "~@effect/platform-browser/IndexedDbTable" /** * Typed IndexedDB table definition containing its name, schema, key path, indexes, auto-increment setting, and transaction durability. * - * @category interface + * @category models * @since 4.0.0 */ export interface IndexedDbTable< @@ -94,14 +94,14 @@ export type AnyWithProps = IndexedDbTable< /** * Extracts the table name type from an `IndexedDbTable`. * - * @category models + * @category utility types * @since 4.0.0 */ export type TableName
= Table["tableName"] /** * Extracts the key-path type from an `IndexedDbTable`. * - * @category models + * @category utility types * @since 4.0.0 */ export type KeyPath
= Table["keyPath"] @@ -109,7 +109,7 @@ export type KeyPath
= Table["keyPath"] /** * Extracts the auto-increment flag type from an `IndexedDbTable`. * - * @category models + * @category utility types * @since 4.0.0 */ export type AutoIncrement
= Table["autoIncrement"] @@ -117,14 +117,14 @@ export type AutoIncrement
= Table["autoIncrement"] /** * Extracts the schema type from an `IndexedDbTable`. * - * @category models + * @category utility types * @since 4.0.0 */ export type TableSchema
= Table["tableSchema"] /** * Extracts the decoding or encoding service requirements needed by an `IndexedDbTable` schema. * - * @category models + * @category utility types * @since 4.0.0 */ export type Context
= @@ -134,7 +134,7 @@ export type Context
= /** * Extracts the encoded row type from an `IndexedDbTable` schema. * - * @category models + * @category utility types * @since 4.0.0 */ export type Encoded
= Table["tableSchema"]["Encoded"] @@ -142,7 +142,7 @@ export type Encoded
= Table["tableSchema"]["Encoded"] /** * Extracts the index definition map from an `IndexedDbTable`. * - * @category models + * @category utility types * @since 4.0.0 */ export type Indexes
= Table["indexes"] @@ -150,7 +150,7 @@ export type Indexes
= Table["indexes"] /** * Selects the table with the given name from a union of `IndexedDbTable` types. * - * @category models + * @category utility types * @since 4.0.0 */ export type WithName
= Extract< diff --git a/repos/effect/packages/platform-browser/src/IndexedDbVersion.ts b/repos/effect/packages/platform-browser/src/IndexedDbVersion.ts index 1e471d7f68..ebc3b2358d 100644 --- a/repos/effect/packages/platform-browser/src/IndexedDbVersion.ts +++ b/repos/effect/packages/platform-browser/src/IndexedDbVersion.ts @@ -27,7 +27,7 @@ const TypeId = "~@effect/platform-browser/IndexedDbVersion" /** * Typed IndexedDB version definition containing the tables available in that schema version. * - * @category interface + * @category models * @since 4.0.0 */ export interface IndexedDbVersion< @@ -59,7 +59,7 @@ export type AnyWithProps = IndexedDbVersion /** * Extracts the table union from an `IndexedDbVersion`. * - * @category models + * @category utility types * @since 4.0.0 */ export type Tables = Db extends IndexedDbVersion ? _Tables : never @@ -67,7 +67,7 @@ export type Tables = Db extends IndexedDbVersion /** * Selects a table by name from an `IndexedDbVersion`. * - * @category models + * @category utility types * @since 4.0.0 */ export type TableWithName< @@ -78,7 +78,7 @@ export type TableWithName< /** * Extracts the schema for a named table within an `IndexedDbVersion`. * - * @category models + * @category utility types * @since 4.0.0 */ export type SchemaWithName< diff --git a/repos/effect/packages/platform-browser/src/Permissions.ts b/repos/effect/packages/platform-browser/src/Permissions.ts index 20449a7880..b58b7e46f4 100644 --- a/repos/effect/packages/platform-browser/src/Permissions.ts +++ b/repos/effect/packages/platform-browser/src/Permissions.ts @@ -21,7 +21,7 @@ const ErrorTypeId = "~@effect/platform-browser/Permissions/PermissionsError" * Wrapper on the Permission API (`navigator.permissions`) with methods for * querying status of permissions. * - * @category models + * @category services * @since 4.0.0 */ export interface Permissions { diff --git a/repos/effect/packages/platform-browser/test/BrowserCrypto.test.ts b/repos/effect/packages/platform-browser/test/BrowserCrypto.test.ts index 45650777cc..3c9b78d528 100644 --- a/repos/effect/packages/platform-browser/test/BrowserCrypto.test.ts +++ b/repos/effect/packages/platform-browser/test/BrowserCrypto.test.ts @@ -18,6 +18,32 @@ const getRandomValues = (array: T): T => { } describe("BrowserCrypto", () => { + it.effect("generates random bytes at and above the getRandomValues limit", () => { + const chunks: Array = [] + + return Effect.gen(function*() { + const crypto = yield* Crypto.Crypto + for (const size of [65_536, 65_537, 70_000]) { + const bytes = yield* crypto.randomBytes(size) + assert.strictEqual(bytes.length, size) + assert.strictEqual(bytes[0], 0) + assert.strictEqual(bytes[size - 1], (size - 1) & 0xff) + } + assert.deepStrictEqual(chunks, [65_536, 65_536, 1, 65_536, 4_464]) + }).pipe(Effect.provide(BrowserCrypto.layer.pipe( + Layer.provide(Layer.succeed(BrowserCrypto.WebCrypto, { + ...crypto, + getRandomValues(array: T): T { + if (array !== null) { + assert.ok(array.byteLength <= 65_536) + chunks.push(array.byteLength) + } + return getRandomValues(array) + } + })) + ))) + }) + it.effect("generates UUIDv4 values from getRandomValues", () => Effect.gen(function*() { const crypto = yield* Crypto.Crypto @@ -27,9 +53,7 @@ describe("BrowserCrypto", () => { }).pipe(Effect.provide(BrowserCrypto.layer.pipe( Layer.provide(Layer.succeed(BrowserCrypto.WebCrypto, { ...crypto, - getRandomValues(array) { - return getRandomValues(array) - } + getRandomValues })) )))) @@ -43,9 +67,7 @@ describe("BrowserCrypto", () => { }).pipe(Effect.provide(BrowserCrypto.layer.pipe( Layer.provide(Layer.succeed(BrowserCrypto.WebCrypto, { ...crypto, - getRandomValues(array) { - return getRandomValues(array) - } + getRandomValues })) )))) diff --git a/repos/effect/packages/platform-browser/test/BrowserHttpClient.test.ts b/repos/effect/packages/platform-browser/test/BrowserHttpClient.test.ts index 74fb349911..4ae16f07b8 100644 --- a/repos/effect/packages/platform-browser/test/BrowserHttpClient.test.ts +++ b/repos/effect/packages/platform-browser/test/BrowserHttpClient.test.ts @@ -88,4 +88,17 @@ describe("BrowserHttpClient", () => { body: "{ \"message\": \"Success!\" }" }] })))) + + it.effect("decodes an XHR response as FormData", () => + Effect.gen(function*() { + const formData = yield* HttpClient.get("http://localhost/form").pipe( + Effect.flatMap((response) => response.formData) + ) + assert.strictEqual(formData.get("value"), "test") + }).pipe(Effect.provide(layer({ + get: ["http://localhost/form", { + headers: { "content-type": "multipart/form-data; boundary=x" }, + body: "--x\r\nContent-Disposition: form-data; name=\"value\"\r\n\r\ntest\r\n--x--\r\n" + }] + })))) }) diff --git a/repos/effect/packages/platform-browser/test/BrowserKeyValueStore.test.ts b/repos/effect/packages/platform-browser/test/BrowserKeyValueStore.test.ts index eabe563676..f4ca6281fb 100644 --- a/repos/effect/packages/platform-browser/test/BrowserKeyValueStore.test.ts +++ b/repos/effect/packages/platform-browser/test/BrowserKeyValueStore.test.ts @@ -1,8 +1,11 @@ import * as BrowserKeyValueStore from "@effect/platform-browser/BrowserKeyValueStore" import * as IndexedDb from "@effect/platform-browser/IndexedDb" -import { describe } from "@effect/vitest" +import { assert, describe, it } from "@effect/vitest" import { Layer } from "effect" import { testLayer } from "effect-test/unstable/persistence/KeyValueStore.test" +import * as Effect from "effect/Effect" +import * as Result from "effect/Result" +import * as KeyValueStore from "effect/unstable/persistence/KeyValueStore" import { IDBKeyRange, indexedDB } from "fake-indexeddb" describe("KeyValueStore / layerLocalStorage", () => testLayer(BrowserKeyValueStore.layerLocalStorage)) @@ -20,4 +23,69 @@ describe("KeyValueStore / layerIndexedDb", () => { Layer.provide(layerFakeIndexedDb) ) ) + + it.effect("does not report a write before its transaction commits", () => { + const db = { + objectStoreNames: { contains: () => true }, + close() {}, + transaction() { + const transaction = { + error: null as unknown, + onabort: null as null | (() => void), + objectStore() { + return { + put() { + const request = { + readyState: "pending", + result: undefined, + error: null, + onsuccess: null as null | (() => void), + onerror: null as null | (() => void) + } + queueMicrotask(() => { + request.readyState = "done" + request.onsuccess?.() + transaction.error = new DOMException("Commit failed", "AbortError") + transaction.onabort?.() + }) + return request + } + } + } + } + return transaction + } + } + const failingIndexedDb = { + open() { + const request = { + readyState: "pending", + result: undefined as unknown, + error: null, + onsuccess: null as null | (() => void), + onerror: null as null | (() => void), + onupgradeneeded: null as null | (() => void) + } + queueMicrotask(() => { + request.readyState = "done" + request.result = db + request.onsuccess?.() + }) + return request + } + } + const layer = BrowserKeyValueStore.layerIndexedDb({ database: "transaction_repro" }).pipe( + Layer.provide(Layer.succeed( + IndexedDb.IndexedDb, + IndexedDb.make({ indexedDB: failingIndexedDb as unknown as IDBFactory, IDBKeyRange }) + )) + ) + + return Effect.gen(function*() { + const store = yield* KeyValueStore.KeyValueStore + const result = yield* Effect.result(store.set("key", "value")) + yield* Effect.yieldNow + assert.isTrue(Result.isFailure(result), "the aborted transaction was reported as successful") + }).pipe(Effect.provide(layer)) + }) }) diff --git a/repos/effect/packages/platform-browser/test/BrowserPersistencePersistedCache.test.ts b/repos/effect/packages/platform-browser/test/BrowserPersistencePersistedCache.test.ts index 7306781a0e..01324b67f2 100644 --- a/repos/effect/packages/platform-browser/test/BrowserPersistencePersistedCache.test.ts +++ b/repos/effect/packages/platform-browser/test/BrowserPersistencePersistedCache.test.ts @@ -1,5 +1,5 @@ import * as BrowserPersistence from "@effect/platform-browser/BrowserPersistence" -import { afterEach, beforeEach, describe } from "@effect/vitest" +import { afterAll, beforeAll, describe } from "@effect/vitest" import * as PersistedCacheTest from "effect-test/unstable/persistence/PersistedCacheTest" import { indexedDB as fakeIndexedDb } from "fake-indexeddb" @@ -7,12 +7,12 @@ const database = "effect_persistence_integration" let previousIndexedDb: unknown -beforeEach(() => { +beforeAll(() => { previousIndexedDb = Reflect.get(globalThis, "indexedDB") Reflect.set(globalThis, "indexedDB", fakeIndexedDb) }) -afterEach(() => { +afterAll(() => { fakeIndexedDb.deleteDatabase(database) if (previousIndexedDb === undefined) { Reflect.deleteProperty(globalThis, "indexedDB") diff --git a/repos/effect/packages/platform-browser/test/BrowserWorkerRunner.test.ts b/repos/effect/packages/platform-browser/test/BrowserWorkerRunner.test.ts new file mode 100644 index 0000000000..3707e7ec21 --- /dev/null +++ b/repos/effect/packages/platform-browser/test/BrowserWorkerRunner.test.ts @@ -0,0 +1,101 @@ +import * as BrowserWorkerRunner from "@effect/platform-browser/BrowserWorkerRunner" +import { assert, describe, it } from "@effect/vitest" +import * as Effect from "effect/Effect" +import * as Fiber from "effect/Fiber" +import * as Queue from "effect/Queue" + +type Listener = EventListenerOrEventListenerObject + +const makePort = () => { + const listeners = new Map>() + const added: Array = [] + const removed: Array = [] + const port = { + addEventListener(type: string, listener: Listener | null) { + if (listener === null) return + added.push([type, listener]) + const eventListeners = listeners.get(type) ?? new Set() + eventListeners.add(listener) + listeners.set(type, eventListeners) + }, + removeEventListener(type: string, listener: Listener | null) { + if (listener === null) return + removed.push([type, listener]) + listeners.get(type)?.delete(listener) + }, + postMessage() {}, + start() {}, + close() {} + } as unknown as MessagePort + + return { + added, + removed, + port, + emit(type: string, data: unknown) { + const event = { data } as MessageEvent + for (const listener of listeners.get(type) ?? []) { + if (typeof listener === "function") { + listener(event) + } else { + listener.handleEvent(event) + } + } + } + } +} + +const makeSharedWorker = () => { + const worker = { + onconnect: null as ((event: MessageEvent) => void) | null, + addEventListener() {}, + removeEventListener() {}, + close() {} + } + return { + worker: worker as unknown as Window, + connect(port: MessagePort) { + worker.onconnect?.({ ports: [port] } as unknown as MessageEvent) + } + } +} + +describe("BrowserWorkerRunner", () => { + it.effect("removes the registered messageerror listener", () => + Effect.gen(function*() { + const fake = makePort() + const runner = yield* BrowserWorkerRunner.make(fake.port).start() + const fiber = yield* Effect.forkChild(runner.run(() => {})) + yield* Effect.yieldNow + + fake.emit("message", [1]) + yield* Fiber.join(fiber) + + const added = fake.added.find(([type]) => type === "messageerror") + const removed = fake.removed.find(([type]) => type === "messageerror") + assert.isDefined(added) + assert.isDefined(removed) + assert.strictEqual(removed[1], added[1]) + })) + + it.effect("emits disconnects for closed SharedWorker ports", () => + Effect.gen(function*() { + const sharedWorker = makeSharedWorker() + const first = makePort() + const second = makePort() + const runner = yield* BrowserWorkerRunner.make(sharedWorker.worker).start() + const fiber = yield* Effect.forkChild(runner.run(() => {})) + yield* Effect.yieldNow + + sharedWorker.connect(first.port) + sharedWorker.connect(second.port) + first.emit("message", [1]) + + const disconnects = runner.disconnects + assert.isDefined(disconnects) + assert.strictEqual(yield* Queue.take(disconnects), 0) + + second.emit("message", [1]) + yield* Fiber.join(fiber) + })) +}) diff --git a/repos/effect/packages/platform-browser/test/IndexedDbDatabase.test.ts b/repos/effect/packages/platform-browser/test/IndexedDbDatabase.test.ts index 25d85ca47c..2b6464d699 100644 --- a/repos/effect/packages/platform-browser/test/IndexedDbDatabase.test.ts +++ b/repos/effect/packages/platform-browser/test/IndexedDbDatabase.test.ts @@ -1,6 +1,6 @@ import { IndexedDb, IndexedDbDatabase, IndexedDbTable, IndexedDbVersion } from "@effect/platform-browser" import { afterEach, assert, describe, it } from "@effect/vitest" -import { Effect, Layer, Schema } from "effect" +import { Effect, Layer, Result, Schema } from "effect" import { IDBKeyRange, indexedDB } from "fake-indexeddb" const databaseName = "db" @@ -250,4 +250,36 @@ describe.sequential("IndexedDbDatabase", () => { assert.deepStrictEqual(Array.from(objectStoreNames), ["user"]) }).pipe(provideMigration(Migration)) }) + + it.effect("aborts the versionchange transaction when a migration fails", () => { + const Table = IndexedDbTable.make({ + name: "entries", + schema: Schema.Struct({ id: Schema.Number }), + keyPath: "id" + }) + const Version = IndexedDbVersion.make(Table) + class Database extends IndexedDbDatabase.make(Version, (transaction) => + Effect.andThen( + transaction.createObjectStore("entries"), + Effect.fail("migration failed") + )) + {} + const open = Effect.callback((resume) => { + const request = indexedDB.open(databaseName) + request.onerror = () => resume(Effect.fail(request.error ?? new Error("IndexedDB open request failed"))) + request.onblocked = () => resume(Effect.fail(new Error("IndexedDB open request blocked"))) + request.onsuccess = () => resume(Effect.succeed(request.result)) + }) + + return Effect.gen(function*() { + const migration = yield* Effect.result( + Effect.service(IndexedDbDatabase.IndexedDbDatabase).pipe(provideMigration(Database)) + ) + assert.isTrue(Result.isFailure(migration)) + + const database = yield* open + assert.deepStrictEqual(Array.from(database.objectStoreNames), []) + database.close() + }) + }) }) diff --git a/repos/effect/packages/platform-browser/test/IndexedDbQueryBuilder.test.ts b/repos/effect/packages/platform-browser/test/IndexedDbQueryBuilder.test.ts index 45f8aea79a..9760eeb548 100644 --- a/repos/effect/packages/platform-browser/test/IndexedDbQueryBuilder.test.ts +++ b/repos/effect/packages/platform-browser/test/IndexedDbQueryBuilder.test.ts @@ -2,12 +2,12 @@ import { IndexedDb, IndexedDbDatabase, IndexedDbTable, IndexedDbVersion } from " import { afterEach, assert, describe, it } from "@effect/vitest" import { Array, + Cause, Context, DateTime, Effect, Fiber, Layer, - Option, Schema, SchemaGetter, SchemaIssue, @@ -68,7 +68,7 @@ const VerifyId = Schema.String.pipe( const { maxLength } = yield* VerifyContext if (s.length > maxLength) { return yield* Effect.fail( - new SchemaIssue.InvalidValue(Option.some(s), { + new SchemaIssue.InvalidValue({ message: "Max length exceeded" }) ) @@ -1456,4 +1456,95 @@ describe.sequential("IndexedDbQueryBuilder", () => { assert.deepStrictEqual(data2, { id: 3, title: "test2", count: 3, completed: false }) }).pipe(provideDb(Db)) }) + + it.effect("rolls back withTransaction writes when the effect fails", () => { + class Db extends IndexedDbDatabase.make(V1, (api) => api.createObjectStore("todo")) {} + + return Effect.gen(function*() { + const api = yield* Db + yield* Effect.result( + api.withTransaction({ tables: ["todo"], mode: "readwrite" })( + Effect.andThen( + api.from("todo").insert({ id: 1, title: "committed", count: 1, completed: false }), + Effect.fail("rollback") + ) + ) + ) + + assert.deepStrictEqual(yield* api.from("todo").select(), []) + }).pipe(provideDb(Db)) + }) + + it.effect("applies reverse before a select limit", () => { + class Db extends IndexedDbDatabase.make( + V1, + Effect.fn(function*(api) { + yield* api.createObjectStore("todo") + yield* api.from("todo").insertAll([ + { id: 1, title: "one", count: 1, completed: false }, + { id: 2, title: "two", count: 2, completed: false }, + { id: 3, title: "three", count: 3, completed: false } + ]) + }) + ) {} + + return Effect.gen(function*() { + const api = yield* Db + const rows = yield* api.from("todo").select().reverse().limit(2) + assert.deepStrictEqual(rows.map((row) => row.id), [3, 2]) + }).pipe(provideDb(Db)) + }) + + it.effect("honors an indexed range when delete has a limit", () => { + class Db extends IndexedDbDatabase.make( + V1, + Effect.fn(function*(api) { + yield* api.createObjectStore("todo") + yield* api.createIndex("todo", "titleIndex") + yield* api.from("todo").insertAll([ + { id: 1, title: "keep", count: 1, completed: false }, + { id: 2, title: "delete", count: 2, completed: false } + ]) + }) + ) {} + + return Effect.gen(function*() { + const api = yield* Db + yield* api.from("todo").delete("titleIndex").equals("delete").limit(1) + const rows = yield* api.from("todo").select() + assert.deepStrictEqual(rows.map((row) => row.id), [1]) + }).pipe(provideDb(Db)) + }) + + it.effect("can consume the same paged select stream twice", () => { + class Db extends IndexedDbDatabase.make( + V1, + Effect.fn(function*(api) { + yield* api.createObjectStore("todo") + yield* api.from("todo").insertAll([ + { id: 1, title: "one", count: 1, completed: false }, + { id: 2, title: "two", count: 2, completed: false }, + { id: 3, title: "three", count: 3, completed: false } + ]) + }) + ) {} + + return Effect.gen(function*() { + const api = yield* Db + const stream = api.from("todo").select().stream({ chunkSize: 2 }) + const first = yield* Stream.runCollect(stream) + const second = yield* Stream.runCollect(stream) + assert.deepStrictEqual(second.map((row) => row.id), first.map((row) => row.id)) + }).pipe(provideDb(Db)) + }) + + it.effect("reports NoSuchElementError for an empty ranged first query", () => { + class Db extends IndexedDbDatabase.make(V1, (api) => api.createObjectStore("todo")) {} + + return Effect.gen(function*() { + const api = yield* Db + const error = yield* Effect.flip(api.from("todo").select().equals(1).first()) + assert.instanceOf(error, Cause.NoSuchElementError) + }).pipe(provideDb(Db)) + }) }) diff --git a/repos/effect/packages/platform-browser/test/fixtures/rpc-schemas.ts b/repos/effect/packages/platform-browser/test/fixtures/rpc-schemas.ts index 1bbe326fac..9f69d20155 100644 --- a/repos/effect/packages/platform-browser/test/fixtures/rpc-schemas.ts +++ b/repos/effect/packages/platform-browser/test/fixtures/rpc-schemas.ts @@ -20,7 +20,7 @@ class StreamUsers extends Rpc.make("StreamUsers", { class CurrentUser extends Context.Service()("CurrentUser") {} -class Unauthorized extends Schema.ErrorClass("Unauthorized")({ +class Unauthorized extends Schema.Error("Unauthorized")({ _tag: Schema.tag("Unauthorized") }) {} diff --git a/repos/effect/packages/platform-browser/tsconfig.json b/repos/effect/packages/platform-browser/tsconfig.json index ae1d58ea9d..d27a5f7d0a 100644 --- a/repos/effect/packages/platform-browser/tsconfig.json +++ b/repos/effect/packages/platform-browser/tsconfig.json @@ -1,5 +1,5 @@ { - "$schema": "http://json.schemastore.org/tsconfig", + "$schema": "https://json.schemastore.org/tsconfig", "extends": "../../tsconfig.base.json", "include": ["src"], "references": [ diff --git a/repos/effect/packages/platform-browser/vitest.config.ts b/repos/effect/packages/platform-browser/vitest.config.ts deleted file mode 100644 index 2281aacb08..0000000000 --- a/repos/effect/packages/platform-browser/vitest.config.ts +++ /dev/null @@ -1,17 +0,0 @@ -import * as os from "node:os" -import * as path from "node:path" -import { mergeConfig, type ViteUserConfig } from "vitest/config" -import shared from "../../vitest.shared.ts" - -const config: ViteUserConfig = { - test: { - environment: "happy-dom", - execArgv: [ - "--localstorage-file", - path.resolve(os.tmpdir(), `vitest-${process.pid}.localstorage`) - ], - setupFiles: "./vitest.setup.ts" - } -} - -export default mergeConfig(shared, config) diff --git a/repos/effect/packages/platform-bun/CHANGELOG.md b/repos/effect/packages/platform-bun/CHANGELOG.md index ff357162d4..55361580a8 100644 --- a/repos/effect/packages/platform-bun/CHANGELOG.md +++ b/repos/effect/packages/platform-bun/CHANGELOG.md @@ -1,5 +1,186 @@ # @effect/platform-bun +## 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), [`e74c302`](https://github.com/Effect-TS/effect/commit/e74c302afe0368e5d3f15d18c10fc54cf33f9003)]: + - effect@4.0.0-beta.107 + - @effect/platform-node-shared@4.0.0-beta.107 + +## 4.0.0-beta.106 + +### Patch Changes + +- [#7113](https://github.com/Effect-TS/effect/pull/7113) [`b9875ea`](https://github.com/Effect-TS/effect/commit/b9875ea4538087227149db56e6e7e3a05d72b94a) Thanks @fubhy! - Fix Bun HTTP server handler restoration and defer shutdown while serve scopes remain active. + +- [#7122](https://github.com/Effect-TS/effect/pull/7122) [`c1a13f3`](https://github.com/Effect-TS/effect/commit/c1a13f3157463b2bdadca41cefb505bd04733636) Thanks @fubhy! - Honor offset and byte-count options in Bun Web File 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), [`5f1775c`](https://github.com/Effect-TS/effect/commit/5f1775cb060cb3dbf96adb067fd97967da7eca2f), [`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), [`089313e`](https://github.com/Effect-TS/effect/commit/089313ec2a4c307393c7c5e00c725ec23840c9c1), [`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 + - @effect/platform-node-shared@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 + - @effect/platform-node-shared@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), [`e2ec131`](https://github.com/Effect-TS/effect/commit/e2ec1311bed9bb8709c26396e71b15b4241a9185), [`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), [`e508589`](https://github.com/Effect-TS/effect/commit/e50858905fed68f29ec202ecdc9c902e44bfedd8), [`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 + - @effect/platform-node-shared@4.0.0-beta.104 + +## 4.0.0-beta.103 + +### Patch Changes + +- [#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 + +- [#6802](https://github.com/Effect-TS/effect/pull/6802) [`f1bc827`](https://github.com/Effect-TS/effect/commit/f1bc8274a608813d7b09d28dcca04adbf62f8c92) Thanks @tim-smart! - Allow configuring cluster RPC serialization buffer limits. + +- [#6726](https://github.com/Effect-TS/effect/pull/6726) [`955eb69`](https://github.com/Effect-TS/effect/commit/955eb69b795734b13965e0cb83f6396441b4e413) Thanks @tim-smart! - Construct an empty multipart stream for each bodiless Bun request. + +- [#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. + +- [#6680](https://github.com/Effect-TS/effect/pull/6680) [`23e176a`](https://github.com/Effect-TS/effect/commit/23e176a4f05ed3e81cc13a5d70111099692ea9a5) Thanks @tim-smart! - Fix worker runner disconnect notifications and event listener cleanup. + +- [#6691](https://github.com/Effect-TS/effect/pull/6691) [`6a5e86f`](https://github.com/Effect-TS/effect/commit/6a5e86f896c573c391e0fc7888f13e9ae0f07531) Thanks @t3dotgg! - 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 }, + }); + ``` + +- [#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. + +- [#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 + - @effect/platform-node-shared@4.0.0-beta.103 + +## 4.0.0-beta.102 + +### Patch Changes + +- [#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. + +- 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 + - @effect/platform-node-shared@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 + - @effect/platform-node-shared@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 + - @effect/platform-node-shared@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), [`b330264`](https://github.com/Effect-TS/effect/commit/b3302645fc1c23f7d03a3693dabdcb88763d5cbd), [`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 + - @effect/platform-node-shared@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 + - @effect/platform-node-shared@4.0.0-beta.98 + +## 4.0.0-beta.97 + +### Patch Changes + +- Updated dependencies []: + - effect@4.0.0-beta.97 + - @effect/platform-node-shared@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 + - @effect/platform-node-shared@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 + - @effect/platform-node-shared@4.0.0-beta.95 + +## 4.0.0-beta.94 + +### Patch Changes + +- [#2537](https://github.com/Effect-TS/effect-smol/pull/2537) [`6d2c614`](https://github.com/Effect-TS/effect-smol/commit/6d2c614fab3da932afbb52e849c2663cf32d7d57) Thanks @tim-smart! - optimize bun stream reading + +- 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 + - @effect/platform-node-shared@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 + - @effect/platform-node-shared@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 + - @effect/platform-node-shared@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 + - @effect/platform-node-shared@4.0.0-beta.91 + ## 4.0.0-beta.90 ### Patch Changes diff --git a/repos/effect/packages/platform-bun/README.md b/repos/effect/packages/platform-bun/README.md index b2cc3711b5..7b953f9a04 100644 --- a/repos/effect/packages/platform-bun/README.md +++ b/repos/effect/packages/platform-bun/README.md @@ -1,6 +1,6 @@ # `@effect/platform-bun` -Provides Bun-specific implementations for the abstractions defined in [`@effect/platform`](https://github.com/Effect-TS/effect/tree/main/packages/platform), allowing you to write platform-independent code that runs smoothly in Bun environments. +Provides Bun-specific implementations for Effect's platform abstractions, allowing you to write platform-independent code that runs smoothly in Bun environments. ## Documentation diff --git a/repos/effect/packages/platform-bun/docgen.json b/repos/effect/packages/platform-bun/docgen.json deleted file mode 100644 index eca8c92978..0000000000 --- a/repos/effect/packages/platform-bun/docgen.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "$schema": "../../node_modules/@effect/docgen/schema.json", - "srcLink": "https://github.com/Effect-TS/effect/tree/main/packages/platform-bun/src/", - "exclude": ["src/internal/**/*.ts"], - "tscExecutable": "tsgo", - "examplesCompilerOptions": { - "noEmit": true, - "strict": true, - "skipLibCheck": true, - "moduleResolution": "Bundler", - "module": "ES2022", - "target": "ES2022", - "lib": ["ES2022", "DOM", "DOM.Iterable"], - "rewriteRelativeImportExtensions": true, - "allowImportingTsExtensions": true, - "plugins": [ - { "name": "@effect/language-service", "includeSuggestionsInTsc": false } - ] - } -} diff --git a/repos/effect/packages/platform-bun/package.json b/repos/effect/packages/platform-bun/package.json index dfe2f783d3..330c43b3ff 100644 --- a/repos/effect/packages/platform-bun/package.json +++ b/repos/effect/packages/platform-bun/package.json @@ -1,17 +1,17 @@ { "name": "@effect/platform-bun", "type": "module", - "version": "4.0.0-beta.90", + "version": "4.0.0-beta.107", "license": "MIT", "description": "Platform specific implementations for the Bun runtime", "homepage": "https://effect.website", "repository": { "type": "git", - "url": "https://github.com/Effect-TS/effect-smol.git", + "url": "https://github.com/Effect-TS/effect.git", "directory": "packages/platform-bun" }, "bugs": { - "url": "https://github.com/Effect-TS/effect-smol/issues" + "url": "https://github.com/Effect-TS/effect/issues" }, "tags": [ "bun", @@ -31,6 +31,7 @@ ".": "./src/index.ts", "./*": "./src/*.ts", "./internal/*": null, + "./index": null, "./*/index": null }, "files": [ @@ -38,7 +39,10 @@ "dist/**/*.js", "dist/**/*.js.map", "dist/**/*.d.ts", - "dist/**/*.d.ts.map" + "dist/**/*.d.ts.map", + "AGENTS.md", + "CLAUDE.md", + "ai-docs/**/*" ], "publishConfig": { "access": "public", @@ -48,17 +52,15 @@ ".": "./dist/index.js", "./*": "./dist/*.js", "./internal/*": null, + "./index": null, "./*/index": null } }, "scripts": { "codegen": "effect-utils codegen", "build": "tsc -b tsconfig.json && pnpm babel", - "build:tsgo": "tsgo -b tsconfig.json && pnpm babel", "babel": "babel dist --plugins annotate-pure-calls --out-dir dist --source-maps", - "check": "tsc -b tsconfig.json", - "test": "vitest", - "coverage": "vitest --coverage" + "check": "tsc -b tsconfig.json" }, "peerDependencies": { "effect": "workspace:^" @@ -67,7 +69,7 @@ "@effect/platform-node-shared": "workspace:^" }, "devDependencies": { - "@types/bun": "^1.3.13", + "@types/bun": "^1.3.14", "effect": "workspace:^" } } diff --git a/repos/effect/packages/platform-bun/src/BunChildProcessSpawner.ts b/repos/effect/packages/platform-bun/src/BunChildProcessSpawner.ts index 19e498e43d..62d7e5855d 100644 --- a/repos/effect/packages/platform-bun/src/BunChildProcessSpawner.ts +++ b/repos/effect/packages/platform-bun/src/BunChildProcessSpawner.ts @@ -1,5 +1,5 @@ /** - * Node.js implementation of `ChildProcessSpawner`. + * Bun implementation of `ChildProcessSpawner`. * * @since 4.0.0 */ diff --git a/repos/effect/packages/platform-bun/src/BunClusterHttp.ts b/repos/effect/packages/platform-bun/src/BunClusterHttp.ts index ca727c8e89..e89e0a1362 100644 --- a/repos/effect/packages/platform-bun/src/BunClusterHttp.ts +++ b/repos/effect/packages/platform-bun/src/BunClusterHttp.ts @@ -29,6 +29,7 @@ import type { ServeError } from "effect/unstable/http/HttpServerError" import * as RpcSerialization from "effect/unstable/rpc/RpcSerialization" import type { SqlClient } from "effect/unstable/sql/SqlClient" import { layerK8sHttpClient } from "./BunClusterSocket.ts" +import * as BunCrypto from "./BunCrypto.ts" import * as BunFileSystem from "./BunFileSystem.ts" import * as BunHttpServer from "./BunHttpServer.ts" import type { BunServices } from "./BunServices.ts" @@ -102,6 +103,7 @@ export const layer = < >(options: { readonly transport: "http" | "websocket" readonly serialization?: "msgpack" | "ndjson" | undefined + readonly serializationMaxBufferSize?: number | "unbounded" | undefined readonly clientOnly?: ClientOnly | undefined readonly storage?: Storage | undefined readonly runnerHealth?: "ping" | "k8s" | undefined @@ -157,7 +159,7 @@ export const layer = < ? MessageStorage.layerNoop : options?.storage === "byo" ? Layer.empty - : Layer.orDie(SqlMessageStorage.layer) + : Layer.orDie(SqlMessageStorage.layer).pipe(Layer.provide(BunCrypto.layer)) ), Layer.provide( options?.storage === "local" @@ -168,7 +170,9 @@ export const layer = < ), Layer.provide(ShardingConfig.layerFromEnv(options?.shardingConfig)), Layer.provide( - options?.serialization === "ndjson" ? RpcSerialization.layerNdjson : RpcSerialization.layerMsgPack + options?.serialization === "ndjson" + ? RpcSerialization.layerNdjsonWith({ maxBufferSize: options.serializationMaxBufferSize }) + : RpcSerialization.layerMsgPackWith({ maxBufferSize: options.serializationMaxBufferSize }) ) ) as any } diff --git a/repos/effect/packages/platform-bun/src/BunClusterSocket.ts b/repos/effect/packages/platform-bun/src/BunClusterSocket.ts index 22f3bfa30d..44a5627900 100644 --- a/repos/effect/packages/platform-bun/src/BunClusterSocket.ts +++ b/repos/effect/packages/platform-bun/src/BunClusterSocket.ts @@ -28,6 +28,7 @@ import * as FetchHttpClient from "effect/unstable/http/FetchHttpClient" import * as RpcSerialization from "effect/unstable/rpc/RpcSerialization" import type * as SocketServer from "effect/unstable/socket/SocketServer" import type { SqlClient } from "effect/unstable/sql/SqlClient" +import * as BunCrypto from "./BunCrypto.ts" import * as BunFileSystem from "./BunFileSystem.ts" export { @@ -61,6 +62,7 @@ export const layer = < >( options?: { readonly serialization?: "msgpack" | "ndjson" | undefined + readonly serializationMaxBufferSize?: number | "unbounded" | undefined readonly clientOnly?: ClientOnly | undefined readonly storage?: Storage | undefined readonly runnerHealth?: "ping" | "k8s" | undefined @@ -109,7 +111,7 @@ export const layer = < ? MessageStorage.layerNoop : options?.storage === "byo" ? Layer.empty - : Layer.orDie(SqlMessageStorage.layer) + : Layer.orDie(SqlMessageStorage.layer).pipe(Layer.provide(BunCrypto.layer)) ), Layer.provide( options?.storage === "local" @@ -120,7 +122,9 @@ export const layer = < ), Layer.provide(ShardingConfig.layerFromEnv(options?.shardingConfig)), Layer.provide( - options?.serialization === "ndjson" ? RpcSerialization.layerNdjson : RpcSerialization.layerMsgPack + options?.serialization === "ndjson" + ? RpcSerialization.layerNdjsonWith({ maxBufferSize: options?.serializationMaxBufferSize }) + : RpcSerialization.layerMsgPackWith({ maxBufferSize: options?.serializationMaxBufferSize }) ) ) as any } diff --git a/repos/effect/packages/platform-bun/src/BunHttpPlatform.ts b/repos/effect/packages/platform-bun/src/BunHttpPlatform.ts index 4fe1e85cea..905d2c407b 100644 --- a/repos/effect/packages/platform-bun/src/BunHttpPlatform.ts +++ b/repos/effect/packages/platform-bun/src/BunHttpPlatform.ts @@ -9,7 +9,8 @@ * * @since 4.0.0 */ -import type { Effect } from "effect" +import * as NodeHttpCompression from "@effect/platform-node-shared/NodeHttpCompression" +import type * as Effect from "effect/Effect" import type { FileSystem } from "effect/FileSystem" import * as Layer from "effect/Layer" import * as Etag from "effect/unstable/http/Etag" @@ -17,6 +18,13 @@ import * as Platform from "effect/unstable/http/HttpPlatform" import * as Response from "effect/unstable/http/HttpServerResponse" import * as BunFileSystem from "./BunFileSystem.ts" +// Bun's CompressionStream supports an extended format set covering brotli and +// zstd +const compression = NodeHttpCompression.make(Platform.makeCompressionWeb({ + algorithms: ["gzip", "deflate", "br", "zstd"], + transform: (algorithm) => Platform.compressionTransformWeb(algorithm === "br" ? "brotli" : algorithm) +})) + /** * @category constructors * @since 4.0.0 @@ -26,6 +34,8 @@ const make: Effect.Effect< never, FileSystem | Etag.Generator > = Platform.make({ + platform: "bun", + compression, fileResponse(path, status, statusText, headers, start, end, _contentLength) { let file = Bun.file(path) if (start > 0 || end !== undefined) { @@ -33,8 +43,13 @@ const make: Effect.Effect< } return Response.raw(file, { headers, status, statusText }) }, - fileWebResponse(file, status, statusText, headers, _options) { - return Response.raw(file, { headers, status, statusText }) + fileWebResponse(file, status, statusText, headers, options) { + const start = Number(options?.offset ?? 0) + const end = options?.bytesToRead !== undefined ? start + Number(options.bytesToRead) : undefined + const body = start > 0 || end !== undefined + ? (file as File).slice(start, end, file.type) + : file + return Response.raw(body, { headers, status, statusText }) } }) diff --git a/repos/effect/packages/platform-bun/src/BunHttpServer.ts b/repos/effect/packages/platform-bun/src/BunHttpServer.ts index 9f2ac3dfc0..4ccfcf16b8 100644 --- a/repos/effect/packages/platform-bun/src/BunHttpServer.ts +++ b/repos/effect/packages/platform-bun/src/BunHttpServer.ts @@ -66,6 +66,25 @@ export type ServeOptions = ) & { readonly routes?: Bun.Serve.Routes } +/** + * WebSocket tuning options forwarded to `Bun.serve`'s `websocket` handler. + * + * **Details** + * + * The lifecycle handlers (`open`, `message`, `close`, ...) are managed by the + * server and cannot be overridden; everything else — such as + * `perMessageDeflate` compression, payload limits, and idle timeouts — passes + * through, e.g. + * `BunHttpServer.layer({ port: 3000, websocket: { perMessageDeflate: true } })`. + * + * @category options + * @since 4.0.0 + */ +export type WebSocketOptions = Omit< + Bun.WebSocketHandler, + "open" | "message" | "close" | "drain" | "ping" | "pong" | "data" | "binaryType" +> + /** * Creates a scoped Bun `HttpServer` from `Bun.serve` options, stopping the server on scope finalization with optional graceful shutdown settings. * @@ -77,6 +96,7 @@ export const make = Effect.fnUntraced( options: ServeOptions & { readonly disablePreemptiveShutdown?: boolean | undefined readonly gracefulShutdownTimeout?: Duration.Input | undefined + readonly websocket?: WebSocketOptions | undefined } ) { const scope = yield* Effect.scope @@ -90,6 +110,7 @@ export const make = Effect.fnUntraced( ...options as ServeOptions, fetch: handlerStack[0], websocket: { + ...options.websocket, open(ws) { Deferred.doneUnsafe(ws.data.deferred, Exit.succeed(ws)) }, @@ -137,12 +158,12 @@ export const make = Effect.fnUntraced( function handler(request: Request, server: BunServer) { return new Promise((resolve, _reject) => { - const map = new Map(services.mapUnsafe) - map.set( - ServerRequest.HttpServerRequest.key, + const context = Context.add( + services, + ServerRequest.HttpServerRequest, new BunServerRequest(request, resolve, removeHost(request.url), server) ) - const fiber = Fiber.runIn(Effect.runForkWith(Context.makeUnsafe(map))(httpEffect), scope) + const fiber = Fiber.runIn(Effect.runForkWith(context)(httpEffect), scope) request.signal.addEventListener("abort", () => { fiber.interruptUnsafe(parent.id, Error.ClientAbort.annotation) }, { once: true }) @@ -150,9 +171,10 @@ export const make = Effect.fnUntraced( } yield* Scope.addFinalizerExit(serveScope, () => { - handlerStack.pop() + const index = handlerStack.indexOf(handler) + if (index !== -1) handlerStack.splice(index, 1) server.reload({ fetch: handlerStack[handlerStack.length - 1] }) - return preemptiveShutdown + return handlerStack.length === 1 ? preemptiveShutdown : Effect.void }) handlerStack.push(handler) server.reload({ fetch: handler }) @@ -233,6 +255,7 @@ export const layerServer: ( options: ServeOptions & { readonly disablePreemptiveShutdown?: boolean | undefined readonly gracefulShutdownTimeout?: Duration.Input | undefined + readonly websocket?: WebSocketOptions | undefined } ) => Layer.Layer = flow(make, Layer.effect(Server.HttpServer)) as any @@ -262,6 +285,7 @@ export const layer = ( options: ServeOptions & { readonly disablePreemptiveShutdown?: boolean | undefined readonly gracefulShutdownTimeout?: Duration.Input | undefined + readonly websocket?: WebSocketOptions | undefined } ): Layer.Layer< | Server.HttpServer @@ -273,7 +297,7 @@ export const layer = ( /** * Layer that starts a Bun HTTP server on an ephemeral port for tests. * - * @category layers + * @category testing * @since 4.0.0 */ export const layerTest: Layer.Layer< @@ -296,6 +320,7 @@ export const layerConfig = ( ServeOptions & { readonly disablePreemptiveShutdown?: boolean | undefined readonly gracefulShutdownTimeout?: Duration.Input | undefined + readonly websocket?: WebSocketOptions | undefined } > ): Layer.Layer< diff --git a/repos/effect/packages/platform-bun/src/BunMultipart.ts b/repos/effect/packages/platform-bun/src/BunMultipart.ts index 080eea6b96..37162cc6bf 100644 --- a/repos/effect/packages/platform-bun/src/BunMultipart.ts +++ b/repos/effect/packages/platform-bun/src/BunMultipart.ts @@ -24,19 +24,18 @@ import * as BunStream from "./BunStream.ts" */ export const stream = (source: Request): Stream.Stream => BunStream.fromReadableStream({ - evaluate: () => source.body ?? emptyReadbleStream, + evaluate: () => + source.body ?? new ReadableStream({ + start(controller) { + controller.enqueue(new Uint8Array()) + controller.close() + } + }), onError: (cause) => Multipart.MultipartError.fromReason("InternalError", cause) }).pipe( Stream.pipeThroughChannel(Multipart.makeChannel(Object.fromEntries(source.headers))) ) -const emptyReadbleStream = new ReadableStream({ - start(controller) { - controller.enqueue(new Uint8Array()) - controller.close() - } -}) - /** * Parses and persists multipart data from a Bun `Request`, requiring file-system, path, and scope services. * diff --git a/repos/effect/packages/platform-bun/src/BunStream.ts b/repos/effect/packages/platform-bun/src/BunStream.ts index 33ec53bb6a..ee43ffceb8 100644 --- a/repos/effect/packages/platform-bun/src/BunStream.ts +++ b/repos/effect/packages/platform-bun/src/BunStream.ts @@ -43,26 +43,25 @@ export const fromReadableStream = ( scope, options.releaseLockOnEnd ? Effect.sync(() => reader.releaseLock()) : Effect.promise(() => reader.cancel()) ) - const readMany = Effect.callback, E>((resume) => { + function readMany(): Pull.Pull, E> { const result = reader.readMany() if ("then" in result) { - result.then((_) => resume(Effect.succeed(_)), (e) => resume(Effect.fail(options.onError(e)))) - } else { - resume(Effect.succeed(result)) + return Effect.callback, E | Cause.Done>((resume) => { + result.then((_) => resume(handleResult(_)), (e) => resume(Effect.fail(options.onError(e)))) + }) } - }) - // @effect-diagnostics-next-line returnEffectInGen:off - return Effect.flatMap( - readMany, - function loop( - { done, value } - ): Pull.Pull, E> { - if (done) { - return Cause.done() - } else if (!Arr.isReadonlyArrayNonEmpty(value)) { - return Effect.flatMap(readMany, loop) - } - return Effect.succeed(value) + return handleResult(result) + } + function handleResult( + result: Bun.ReadableStreamDefaultReadManyResult + ): Pull.Pull, E> { + if (result.done) { + return Cause.done() + } else if (!Arr.isReadonlyArrayNonEmpty(result.value)) { + return readMany() } - ) + return Effect.succeed(result.value) + } + // @effect-diagnostics-next-line returnEffectInGen:off + return Effect.suspend(readMany) }))) diff --git a/repos/effect/packages/platform-bun/src/BunWorkerRunner.ts b/repos/effect/packages/platform-bun/src/BunWorkerRunner.ts index 255b54a9f6..f6473cfebd 100644 --- a/repos/effect/packages/platform-bun/src/BunWorkerRunner.ts +++ b/repos/effect/packages/platform-bun/src/BunWorkerRunner.ts @@ -77,22 +77,11 @@ export const layer: Layer.Layer = Layer.succe }) ) } - function onError(error: MessageEvent) { - Deferred.doneUnsafe( - closeLatch, - new WorkerError({ - reason: new WorkerReceiveError({ - message: "received error event", - cause: error.data - }) - }) - ) - } yield* Scope.addFinalizer( scope, Effect.sync(() => { port.removeEventListener("message", onMessage) - port.removeEventListener("messageerror", onError) + port.removeEventListener("messageerror", onMessageError) }) ) port.addEventListener("message", onMessage) diff --git a/repos/effect/packages/platform-bun/test/BunHttpCompression.test.ts b/repos/effect/packages/platform-bun/test/BunHttpCompression.test.ts new file mode 100644 index 0000000000..d1a6310a9a --- /dev/null +++ b/repos/effect/packages/platform-bun/test/BunHttpCompression.test.ts @@ -0,0 +1,110 @@ +import * as BunHttpPlatform from "@effect/platform-bun/BunHttpPlatform" +import { assert, describe, it } from "@effect/vitest" +import * as Effect from "effect/Effect" +import * as Stream from "effect/Stream" +import * as HttpEffect from "effect/unstable/http/HttpEffect" +import * as HttpMiddleware from "effect/unstable/http/HttpMiddleware" +import * as HttpPlatform from "effect/unstable/http/HttpPlatform" +import type { HttpServerRequest } from "effect/unstable/http/HttpServerRequest" +import * as HttpServerResponse from "effect/unstable/http/HttpServerResponse" +import * as Fs from "node:fs" +import { fileURLToPath } from "node:url" +import * as Zlib from "node:zlib" + +const bigJson = JSON.stringify({ text: "All work and no play makes Jack a dull boy. ".repeat(100) }) +const bigJsonApp = Effect.succeed(HttpServerResponse.text(bigJson, { contentType: "application/json" })) + +type App = Effect.Effect +type CompressionOptions = Parameters[0] +type Algorithm = HttpPlatform.CompressionAlgorithm + +const algorithms: ReadonlyArray = ["gzip", "deflate", "br", "zstd"] + +const decompress = (algorithm: Algorithm, data: Uint8Array): string => { + switch (algorithm) { + case "gzip": + return Zlib.gunzipSync(data).toString() + case "deflate": + return Zlib.inflateSync(data).toString() + case "br": + return Zlib.brotliDecompressSync(data).toString() + case "zstd": + return Zlib.zstdDecompressSync(data).toString() + } +} + +const withHandler = async ( + app: App, + options: CompressionOptions, + run: (handler: (request: Request) => Promise) => Promise +) => { + const { dispose, handler } = HttpEffect.toWebHandlerLayer( + app as Effect.Effect, + BunHttpPlatform.layer, + { middleware: HttpMiddleware.compression(options) } + ) + try { + await run(handler) + } finally { + await dispose() + } +} + +const get = ( + handler: (request: Request) => Promise, + algorithm: Algorithm +) => handler(new Request("http://localhost/", { headers: { "accept-encoding": algorithm } })) + +// Bun's CompressionStream does not expose an explicit flush operation, so +// incremental SSE-style delivery is runtime-defined and intentionally is not +// guaranteed by this suite. +describe("BunHttpCompression", () => { + it.effect("advertises Bun's supported algorithms", () => + Effect.gen(function*() { + const platform = yield* HttpPlatform.HttpPlatform + assert.deepStrictEqual([...platform.compression.algorithms], algorithms) + }).pipe(Effect.provide(BunHttpPlatform.layer))) + + for (const algorithm of algorithms) { + it(`compresses one-shot bodies with ${algorithm}`, () => + withHandler(bigJsonApp, { algorithms }, async (handler) => { + const response = await get(handler, algorithm) + assert.strictEqual(response.headers.get("content-encoding"), algorithm) + assert.strictEqual(response.headers.get("vary"), "Accept-Encoding") + const compressed = new Uint8Array(await response.arrayBuffer()) + assert.strictEqual(response.headers.get("content-length"), compressed.byteLength.toString()) + assert.strictEqual(decompress(algorithm, compressed), bigJson) + })) + + it(`compresses stream bodies with ${algorithm}`, () => + withHandler( + Effect.succeed(HttpServerResponse.stream( + Stream.fromArray([new TextEncoder().encode(bigJson)]), + { contentType: "application/json" } + )), + { algorithms }, + async (handler) => { + const response = await get(handler, algorithm) + assert.strictEqual(response.headers.get("content-encoding"), algorithm) + assert.strictEqual(response.headers.get("content-length"), null) + assert.strictEqual(decompress(algorithm, new Uint8Array(await response.arrayBuffer())), bigJson) + } + )) + } + + it("compresses Bun.file responses", async () => { + const path = fileURLToPath(new URL("./BunHttpCompression.test.ts", import.meta.url)) + const contents = Fs.readFileSync(path, "utf8") + await withHandler( + HttpServerResponse.file(path, { headers: { "content-type": "text/plain" } }), + { algorithms, minSize: 0 }, + async (handler) => { + const response = await get(handler, "gzip") + assert.strictEqual(response.headers.get("content-encoding"), "gzip") + assert.strictEqual(response.headers.get("content-length"), null) + assert.isTrue(response.headers.get("etag")!.startsWith("W/")) + assert.strictEqual(decompress("gzip", new Uint8Array(await response.arrayBuffer())), contents) + } + ) + }) +}) diff --git a/repos/effect/packages/platform-bun/test/BunHttpPlatform.test.ts b/repos/effect/packages/platform-bun/test/BunHttpPlatform.test.ts new file mode 100644 index 0000000000..9f9656fa80 --- /dev/null +++ b/repos/effect/packages/platform-bun/test/BunHttpPlatform.test.ts @@ -0,0 +1,25 @@ +import * as BunHttpPlatform from "@effect/platform-bun/BunHttpPlatform" +import { assert, describe, it } from "@effect/vitest" +import * as Effect from "effect/Effect" +import type * as HttpBody from "effect/unstable/http/HttpBody" +import * as HttpPlatform from "effect/unstable/http/HttpPlatform" + +const readBody = (body: HttpBody.HttpBody) => { + assert.strictEqual(body._tag, "Raw") + return Effect.promise(() => new Response((body as HttpBody.Raw).body as BodyInit).text()) +} + +describe("BunHttpPlatform", () => { + it.effect("fileWebResponse honors offset and bytesToRead including zero", () => + Effect.gen(function*() { + const platform = yield* HttpPlatform.HttpPlatform + const file = new File(["abcd"], "file.txt", { type: "text/plain", lastModified: 0 }) + const sliced = yield* platform.fileWebResponse(file, { offset: 1, bytesToRead: 2 }) + const empty = yield* platform.fileWebResponse(file, { offset: 1, bytesToRead: 0 }) + + assert.deepStrictEqual( + { sliced: yield* readBody(sliced.body), empty: yield* readBody(empty.body) }, + { sliced: "bc", empty: "" } + ) + }).pipe(Effect.provide(BunHttpPlatform.layer))) +}) diff --git a/repos/effect/packages/platform-bun/test/BunHttpServer.test.ts b/repos/effect/packages/platform-bun/test/BunHttpServer.test.ts new file mode 100644 index 0000000000..ca1e8d1128 --- /dev/null +++ b/repos/effect/packages/platform-bun/test/BunHttpServer.test.ts @@ -0,0 +1,50 @@ +import * as BunHttpServer from "@effect/platform-bun/BunHttpServer" +import { assert, describe, it } from "@effect/vitest" +import * as Effect from "effect/Effect" +import * as Exit from "effect/Exit" +import * as Scope from "effect/Scope" +import * as HttpServer from "effect/unstable/http/HttpServer" +import * as HttpServerResponse from "effect/unstable/http/HttpServerResponse" + +const fetchText = (url: string) => + Effect.promise(() => fetch(url, { headers: { connection: "close" } }).then((response) => response.text())) + +describe("BunHttpServer", () => { + it.effect("closing an older serve scope keeps the newer handler active", () => + Effect.gen(function*() { + const ownerScope = yield* Effect.scope + const server = yield* BunHttpServer.make({ + hostname: "127.0.0.1", + port: 0 + }) + const firstScope = yield* Scope.fork(ownerScope) + const secondScope = yield* Scope.fork(ownerScope) + + yield* server.serve(Effect.succeed(HttpServerResponse.text("first"))).pipe(Scope.provide(firstScope)) + yield* server.serve(Effect.succeed(HttpServerResponse.text("second"))).pipe(Scope.provide(secondScope)) + const url = HttpServer.formatAddress(server.address) + + assert.strictEqual(yield* fetchText(url), "second") + yield* Scope.close(firstScope, Exit.void) + assert.strictEqual(yield* fetchText(url), "second") + })) + + it.effect("closing the newer serve scope restores the older handler", () => + Effect.gen(function*() { + const ownerScope = yield* Effect.scope + const server = yield* BunHttpServer.make({ + hostname: "127.0.0.1", + port: 0 + }) + const firstScope = yield* Scope.fork(ownerScope) + const secondScope = yield* Scope.fork(ownerScope) + + yield* server.serve(Effect.succeed(HttpServerResponse.text("first"))).pipe(Scope.provide(firstScope)) + yield* server.serve(Effect.succeed(HttpServerResponse.text("second"))).pipe(Scope.provide(secondScope)) + const url = HttpServer.formatAddress(server.address) + + assert.strictEqual(yield* fetchText(url), "second") + yield* Scope.close(secondScope, Exit.void) + assert.strictEqual(yield* fetchText(url), "first") + })) +}) diff --git a/repos/effect/packages/platform-bun/tsconfig.json b/repos/effect/packages/platform-bun/tsconfig.json index 7ed30dd4a4..af9a7c09af 100644 --- a/repos/effect/packages/platform-bun/tsconfig.json +++ b/repos/effect/packages/platform-bun/tsconfig.json @@ -1,5 +1,5 @@ { - "$schema": "http://json.schemastore.org/tsconfig", + "$schema": "https://json.schemastore.org/tsconfig", "extends": "../../tsconfig.base.json", "include": ["src"], "references": [ diff --git a/repos/effect/packages/platform-deno/CHANGELOG.md b/repos/effect/packages/platform-deno/CHANGELOG.md new file mode 100644 index 0000000000..11cee3205b --- /dev/null +++ b/repos/effect/packages/platform-deno/CHANGELOG.md @@ -0,0 +1,98 @@ +# @effect/platform-deno + +## 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), [`e74c302`](https://github.com/Effect-TS/effect/commit/e74c302afe0368e5d3f15d18c10fc54cf33f9003)]: + - effect@4.0.0-beta.107 + - @effect/platform-node-shared@4.0.0-beta.107 + +## 4.0.0-beta.106 + +### Patch Changes + +- [#7123](https://github.com/Effect-TS/effect/pull/7123) [`4bca71d`](https://github.com/Effect-TS/effect/commit/4bca71d4910ef3afff647076ee9c507e5c060827) Thanks @fubhy! - Honor `offset` and `bytesToRead` when creating Deno Web file 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), [`5f1775c`](https://github.com/Effect-TS/effect/commit/5f1775cb060cb3dbf96adb067fd97967da7eca2f), [`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), [`089313e`](https://github.com/Effect-TS/effect/commit/089313ec2a4c307393c7c5e00c725ec23840c9c1), [`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 + - @effect/platform-node-shared@4.0.0-beta.106 + +## 4.0.0-beta.105 + +### Patch Changes + +- [#7086](https://github.com/Effect-TS/effect/pull/7086) [`c91f401`](https://github.com/Effect-TS/effect/commit/c91f4015f871b66cdc24cceeca46be26cbf23a76) Thanks @tim-smart! - Preserve high-level filesystem error context for `writeFile` and normalize Deno `AlreadyExists` errors from `copy`. + +- [#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. +- 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 + - @effect/platform-node-shared@4.0.0-beta.105 + +## 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 + +- [#7080](https://github.com/Effect-TS/effect/pull/7080) [`c2c966d`](https://github.com/Effect-TS/effect/commit/c2c966d70b157651bedd0b280c196823ac6b6432) Thanks @tim-smart! - Honor `FileSystem.writeFile` open flags in the Deno implementation. +- 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), [`e2ec131`](https://github.com/Effect-TS/effect/commit/e2ec1311bed9bb8709c26396e71b15b4241a9185), [`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), [`e508589`](https://github.com/Effect-TS/effect/commit/e50858905fed68f29ec202ecdc9c902e44bfedd8), [`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 + - @effect/platform-node-shared@4.0.0-beta.104 + +## 4.0.0-beta.103 + +### Patch Changes + +- [#6682](https://github.com/Effect-TS/effect/pull/6682) [`cd98622`](https://github.com/Effect-TS/effect/commit/cd9862247f3faee43ed6bf7bc44c3d5385613916) Thanks @tim-smart! - Add a Deno-backed FileSystem layer. + +- [#6685](https://github.com/Effect-TS/effect/pull/6685) [`45e7810`](https://github.com/Effect-TS/effect/commit/45e78108823d24abe075cfb69a75733223960573) Thanks @tim-smart! - 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. + +- [#6725](https://github.com/Effect-TS/effect/pull/6725) [`d435467`](https://github.com/Effect-TS/effect/commit/d435467e634f47b383fc4e8ece1b37fe3eecc7ac) Thanks @tim-smart! - Add web-standard multipart request parsing helpers for Deno. + +- [#6724](https://github.com/Effect-TS/effect/pull/6724) [`7870557`](https://github.com/Effect-TS/effect/commit/7870557987ec0801b0ea2f8c4e15fd2ab6b11aac) Thanks @tim-smart! - Add native Deno TCP, Unix, and TLS socket server adapters. + +- [#6721](https://github.com/Effect-TS/effect/pull/6721) [`35141f1`](https://github.com/Effect-TS/effect/commit/35141f1a02d367daba069b321f5a6b09b379a580) Thanks @tim-smart! - Add native Deno TCP, Unix, and WebSocket integrations for Effect sockets. + +- [#6412](https://github.com/Effect-TS/effect/pull/6412) [`ec656dc`](https://github.com/Effect-TS/effect/commit/ec656dccf17cfdf0be4152e042c56f7a21f169ac) Thanks @lishaduck! - Add Deno platform integrations for paths, runtime execution, workers, and Web Storage. + +- [#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 + +- [#6802](https://github.com/Effect-TS/effect/pull/6802) [`f1bc827`](https://github.com/Effect-TS/effect/commit/f1bc8274a608813d7b09d28dcca04adbf62f8c92) Thanks @tim-smart! - Allow configuring cluster RPC serialization buffer limits. + +- [#6684](https://github.com/Effect-TS/effect/pull/6684) [`dbd6ea4`](https://github.com/Effect-TS/effect/commit/dbd6ea4ef6f78f41070d57bd9a5bfa2699df268d) Thanks @tim-smart! - Add a Deno Web Crypto implementation of the `Crypto` service. + +- [#6688](https://github.com/Effect-TS/effect/pull/6688) [`504343b`](https://github.com/Effect-TS/effect/commit/504343b0cdf9a0306191c069c31b7d569eba0ed7) Thanks @tim-smart! - Add a native Deno `ChildProcessSpawner` implementation and shared process conformance coverage. + +- [#6715](https://github.com/Effect-TS/effect/pull/6715) [`75f340e`](https://github.com/Effect-TS/effect/commit/75f340ef3d6dc350337ff6ff930ae533046a969f) Thanks @tim-smart! - Add a Deno `Terminal` implementation and keep `NodeTerminal` input readers alive until stdin ends under Deno. + +- [#6700](https://github.com/Effect-TS/effect/pull/6700) [`d51329b`](https://github.com/Effect-TS/effect/commit/d51329b4339b4072944682e2950149acaa152971) Thanks @tim-smart! - Add a native Deno implementation of the `Stdio` service. + +- [#6716](https://github.com/Effect-TS/effect/pull/6716) [`897b35c`](https://github.com/Effect-TS/effect/commit/897b35ca373bfed7d3f3da60ee90a43c5a8d48ca) Thanks @tim-smart! - Add the aggregate Deno platform services layer. + +- [#6720](https://github.com/Effect-TS/effect/pull/6720) [`87ca79e`](https://github.com/Effect-TS/effect/commit/87ca79ec8fac0546f865f82c97734c53c8bed068) Thanks @tim-smart! - Add a native Deno `HttpPlatform` layer with resource-backed file responses. + +- [#6686](https://github.com/Effect-TS/effect/pull/6686) [`a149f89`](https://github.com/Effect-TS/effect/commit/a149f89295910003dff3d7bb16c73ee3750d9ab2) Thanks @tim-smart! - Add a native Deno Redis integration backed by `@db/redis`. + +- [#6730](https://github.com/Effect-TS/effect/pull/6730) [`6086309`](https://github.com/Effect-TS/effect/commit/60863090af8e5af0bfa1435f08dc5390f9993e30) Thanks @tim-smart! - Add a native Deno HTTP server with multipart requests, file responses, and WebSocket upgrades. + +- [#6731](https://github.com/Effect-TS/effect/pull/6731) [`a3fabe2`](https://github.com/Effect-TS/effect/commit/a3fabe25add28461ef624c37bf52129a9b00435f) Thanks @tim-smart! - Add native Deno HTTP and WebSocket layers for Effect Cluster runners. + +- [#6728](https://github.com/Effect-TS/effect/pull/6728) [`e0426da`](https://github.com/Effect-TS/effect/commit/e0426da103e931d43ef8c85023c9d3439bd8533d) Thanks @tim-smart! - Add native Deno socket layers for Effect Cluster runners. + +- [#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. + +- [#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. + +- [#6701](https://github.com/Effect-TS/effect/pull/6701) [`9867b9f`](https://github.com/Effect-TS/effect/commit/9867b9fc69f9cc6c443594fc7eccc7be0c674d9c) Thanks @fubhy! - Removed explicit ./index entrypoints + +- [#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. + +- 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 + - @effect/platform-node-shared@4.0.0-beta.103 diff --git a/repos/effect/packages/platform-deno/LICENSE b/repos/effect/packages/platform-deno/LICENSE new file mode 100644 index 0000000000..2d1e0c682d --- /dev/null +++ b/repos/effect/packages/platform-deno/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 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/platform-deno/README.md b/repos/effect/packages/platform-deno/README.md new file mode 100644 index 0000000000..69f9d080a3 --- /dev/null +++ b/repos/effect/packages/platform-deno/README.md @@ -0,0 +1,13 @@ +# `@effect/platform-deno` + +Provides Deno-specific implementations for the abstractions defined in [`@effect/platform`](https://github.com/Effect-TS/effect/tree/main/packages/platform), allowing you to write platform-independent code that integrates smoothly with Deno. + +## Documentation + +- **API Reference**: [View the full documentation](https://effect-ts.github.io/effect/docs/platform-deno). + +## Known divergences + +- `ChildProcess` rejects `detached` because `Deno.Command` cannot create a detached process group. +- `ChildProcess` rejects `additionalFds` because Deno supports only stdin, stdout, and stderr configuration. +- Killing a `ChildProcess` terminates only the direct child. Descendants are left running because Deno has no portable process-group isolation API. diff --git a/repos/effect/packages/platform-deno/package.json b/repos/effect/packages/platform-deno/package.json new file mode 100644 index 0000000000..0d8bcf7355 --- /dev/null +++ b/repos/effect/packages/platform-deno/package.json @@ -0,0 +1,83 @@ +{ + "name": "@effect/platform-deno", + "type": "module", + "version": "4.0.0-beta.107", + "license": "MIT", + "description": "Platform specific implementations for the Deno runtime", + "homepage": "https://effect.website", + "repository": { + "type": "git", + "url": "https://github.com/Effect-TS/effect.git", + "directory": "packages/platform-deno" + }, + "bugs": { + "url": "https://github.com/Effect-TS/effect/issues" + }, + "tags": [ + "deno", + "typescript", + "algebraic-data-types", + "functional-programming" + ], + "keywords": [ + "deno", + "typescript", + "algebraic-data-types", + "functional-programming" + ], + "engines": { + "deno": ">=2.5.0" + }, + "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", + "check": "deno check" + }, + "dependencies": { + "@db/redis": "jsr:^0.41.2", + "@effect/platform-node-shared": "workspace:^", + "@std/fs": "jsr:^1.0.24", + "@std/media-types": "jsr:^1.1.0", + "@std/path": "jsr:^1.1.6", + "@std/streams": "jsr:^1.1.1" + }, + "peerDependencies": { + "effect": "workspace:^" + }, + "devDependencies": { + "@std/assert": "jsr:^1.0.16", + "@testcontainers/redis": "^12.0.4", + "@types/deno": "^2.7.0", + "effect": "workspace:^" + } +} diff --git a/repos/effect/packages/platform-deno/src/DenoChildProcessSpawner.ts b/repos/effect/packages/platform-deno/src/DenoChildProcessSpawner.ts new file mode 100644 index 0000000000..3941f8acb5 --- /dev/null +++ b/repos/effect/packages/platform-deno/src/DenoChildProcessSpawner.ts @@ -0,0 +1,439 @@ +/** + * Deno implementation of the child process spawner service. + * + * This module uses `Deno.Command` and Web Streams directly. Deno cannot create + * detached process groups, so killing a handle terminates only its direct child; + * descendants spawned by that child are left running. + * + * @since 4.0.0 + */ +import type * as Arr from "effect/Array" +import * as Deferred from "effect/Deferred" +import * as Effect from "effect/Effect" +import * as Exit from "effect/Exit" +import * as Layer from "effect/Layer" +import * as Path from "effect/Path" +import * as PlatformError from "effect/PlatformError" +import * as Predicate from "effect/Predicate" +import type * as Scope from "effect/Scope" +import * as Sink from "effect/Sink" +import * as Stream from "effect/Stream" +import * as ChildProcess from "effect/unstable/process/ChildProcess" +import type { ChildProcessHandle } from "effect/unstable/process/ChildProcessSpawner" +import { + ChildProcessSpawner, + ExitCode, + make as makeSpawner, + makeHandle, + ProcessId +} from "effect/unstable/process/ChildProcessSpawner" + +const commandString = (command: ChildProcess.Command): string => { + const { commands } = flattenCommand(command) + return commands.map((command) => `${command.command} ${command.args.join(" ")}`).join(" | ") +} + +const toPlatformError = ( + method: string, + cause: unknown, + command: ChildProcess.Command +): PlatformError.PlatformError => { + const errorName = Predicate.hasProperty(cause, "name") ? cause.name : undefined + const tag = errorName === "NotFound" ? + "NotFound" : + errorName === "PermissionDenied" ? + "PermissionDenied" : + errorName === "TimedOut" + ? "TimedOut" + : "Unknown" + return PlatformError.systemError({ + _tag: tag, + module: "ChildProcess", + method, + pathOrDescriptor: commandString(command), + syscall: `${method} ${commandString(command).trim()}`, + cause + }) +} + +const unsupported = (option: "additionalFds" | "detached") => + PlatformError.badArgument({ + module: "ChildProcessSpawner", + method: "spawn", + description: `The ${option} option is unsupported because Deno has no equivalent` + }) + +const make = Effect.gen(function*() { + const path = yield* Path.Path + + const resolveWorkingDirectory = (options: ChildProcess.CommandOptions) => + options.cwd === undefined ? undefined : path.resolve(options.cwd) + + const resolveEnvironment = (options: ChildProcess.CommandOptions) => { + if (options.env === undefined) return undefined + const env: Record = {} + for (const [key, value] of Object.entries(options.env)) { + if (value !== undefined) env[key] = value + } + return env + } + + const resolveStdinOption = (options: ChildProcess.CommandOptions): ChildProcess.StdinConfig => { + const defaultConfig: ChildProcess.StdinConfig = { stream: "pipe", encoding: "utf-8", endOnDone: true } + if (options.stdin === undefined) { + return defaultConfig + } + if (typeof options.stdin === "string") { + return { ...defaultConfig, stream: options.stdin } + } + if (Stream.isStream(options.stdin)) { + return { ...defaultConfig, stream: options.stdin } + } + return { + stream: options.stdin.stream, + encoding: options.stdin.encoding ?? defaultConfig.encoding, + endOnDone: options.stdin.endOnDone ?? defaultConfig.endOnDone + } + } + + const resolveOutputOption = ( + options: ChildProcess.CommandOptions, + streamName: "stdout" | "stderr" + ): ChildProcess.StdoutConfig => { + const option = options[streamName] + if (option === undefined) return { stream: "pipe" } + if (typeof option === "string" || Sink.isSink(option)) return { stream: option } + return { stream: option.stream } + } + + const inputToStdioOption = ( + input: ChildProcess.CommandInput + ): "piped" | "inherit" | "null" => + Stream.isStream(input) || input === "pipe" || input === "overlapped" ? + "piped" : + input === "ignore" + ? "null" + : "inherit" + + const outputToStdioOption = ( + output: ChildProcess.CommandOutput | undefined + ): "piped" | "inherit" | "null" => + Sink.isSink(output) || output === "pipe" || output === "overlapped" || output === undefined ? + "piped" : + output === "ignore" + ? "null" + : "inherit" + + const setupChildStdin = ( + command: ChildProcess.StandardCommand, + childProcess: Deno.ChildProcess, + config: ChildProcess.StdinConfig + ) => + Effect.suspend(() => { + if (inputToStdioOption(config.stream) !== "piped") return Effect.succeed(Sink.drain) + const sink = Sink.fromWritableStream({ + evaluate: () => childProcess.stdin, + onError: (cause) => toPlatformError("fromWritable(stdin)", cause, command), + closeOnDone: config.endOnDone + }) + return Stream.isStream(config.stream) + ? Effect.as(Effect.forkScoped(Stream.run(config.stream, sink)), sink) + : Effect.succeed(sink) + }) + + const setupChildOutputStreams = ( + command: ChildProcess.StandardCommand, + childProcess: Deno.ChildProcess, + stdoutConfig: ChildProcess.StdoutConfig, + stderrConfig: ChildProcess.StderrConfig + ) => { + let stdout: Stream.Stream = + outputToStdioOption(stdoutConfig.stream) === "piped" + ? Stream.fromReadableStream({ + evaluate: () => childProcess.stdout, + onError: (cause) => toPlatformError("fromReadable(stdout)", cause, command) + }) + : Stream.empty + let stderr: Stream.Stream = + outputToStdioOption(stderrConfig.stream) === "piped" + ? Stream.fromReadableStream({ + evaluate: () => childProcess.stderr, + onError: (cause) => toPlatformError("fromReadable(stderr)", cause, command) + }) + : Stream.empty + + if (Sink.isSink(stdoutConfig.stream)) stdout = Stream.transduce(stdout, stdoutConfig.stream) + if (Sink.isSink(stderrConfig.stream)) stderr = Stream.transduce(stderr, stderrConfig.stream) + + return { stdout, stderr, all: Stream.merge(stdout, stderr) } + } + + const spawn = ( + command: ChildProcess.StandardCommand, + executable: string, + args: ReadonlyArray, + options: Deno.CommandOptions + ) => + Effect.try({ + try: () => { + const childProcess = new Deno.Command(executable, { ...options, args: [...args] }).spawn() + const exitSignal = Deferred.makeUnsafe() + childProcess.status.then( + (status) => Deferred.doneUnsafe(exitSignal, Exit.succeed(status)), + (cause) => Deferred.doneUnsafe(exitSignal, Exit.fail(toPlatformError("status", cause, command))) + ) + return [childProcess, exitSignal] as const + }, + catch: (cause) => toPlatformError("spawn", cause, command) + }) + + const killProcess = ( + command: ChildProcess.StandardCommand, + childProcess: Deno.ChildProcess, + signal: ChildProcess.Signal + ) => + Effect.try({ + try: () => childProcess.kill(signal as Deno.Signal), + catch: (cause) => toPlatformError("kill", cause, command) + }) + + const withTimeout = ( + childProcess: Deno.ChildProcess, + command: ChildProcess.StandardCommand, + options: ChildProcess.KillOptions | undefined + ) => + ( + kill: ( + command: ChildProcess.StandardCommand, + childProcess: Deno.ChildProcess, + signal: ChildProcess.Signal + ) => Effect.Effect + ) => { + const killSignal = options?.killSignal ?? "SIGTERM" + return options?.forceKillAfter === undefined + ? kill(command, childProcess, killSignal) + : Effect.timeoutOrElse(kill(command, childProcess, killSignal), { + duration: options.forceKillAfter, + orElse: () => kill(command, childProcess, "SIGKILL") + }) + } + + const getSourceStream = ( + handle: ChildProcessHandle, + from: "stdout" | "stderr" | "all" + ): Stream.Stream => { + switch (from) { + case "stdout": + return handle.stdout + case "stderr": + return handle.stderr + case "all": + return handle.all + } + } + + const spawnCommand: ( + command: ChildProcess.Command + ) => Effect.Effect = Effect.fnUntraced(function*(cmd) { + switch (cmd._tag) { + case "StandardCommand": { + if (cmd.options.additionalFds !== undefined) { + return yield* Effect.fail(unsupported("additionalFds")) + } + if (cmd.options.detached !== undefined) { + return yield* Effect.fail(unsupported("detached")) + } + + const stdinConfig = resolveStdinOption(cmd.options) + const stdoutConfig = resolveOutputOption(cmd.options, "stdout") + const stderrConfig = resolveOutputOption(cmd.options, "stderr") + let isReferenced = true + + let executable = cmd.command + let args = cmd.args + let windowsRawArguments = false + if (cmd.options.shell) { + const shellCommand = `${cmd.command} ${cmd.args.join(" ")}` + if (Deno.build.os === "windows") { + executable = typeof cmd.options.shell === "string" ? cmd.options.shell : "cmd.exe" + args = ["/d", "/s", "/c", shellCommand] + windowsRawArguments = true + } else { + executable = typeof cmd.options.shell === "string" ? cmd.options.shell : "/bin/sh" + args = ["-c", shellCommand] + } + } + + const cwd = resolveWorkingDirectory(cmd.options) + const env = resolveEnvironment(cmd.options) + const [childProcess, exitSignal] = yield* Effect.acquireRelease( + spawn(cmd, executable, args, { + ...(cwd === undefined ? undefined : { cwd }), + ...(env === undefined ? undefined : { env }), + clearEnv: cmd.options.env !== undefined && cmd.options.extendEnv !== true, + stdin: inputToStdioOption(stdinConfig.stream), + stdout: outputToStdioOption(stdoutConfig.stream), + stderr: outputToStdioOption(stderrConfig.stream), + windowsRawArguments + }), + Effect.fnUntraced(function*([childProcess, exitSignal]) { + const exited = yield* Deferred.isDone(exitSignal) + if (exited || !isReferenced) return + const killWithTimeout = withTimeout(childProcess, cmd, cmd.options) + yield* killWithTimeout((command, childProcess, signal) => + killProcess(command, childProcess, signal).pipe( + Effect.andThen(Deferred.await(exitSignal)) + ) + ).pipe(Effect.ignore) + }) + ) + + const reref = Effect.sync(() => { + if (!isReferenced) { + childProcess.ref() + isReferenced = true + } + }) + const unref = Effect.sync(() => { + if (isReferenced) { + childProcess.unref() + isReferenced = false + } + return reref + }) + const stdin = yield* setupChildStdin(cmd, childProcess, stdinConfig) + const { all, stderr, stdout } = setupChildOutputStreams(cmd, childProcess, stdoutConfig, stderrConfig) + const isRunning = Effect.map(Deferred.isDone(exitSignal), (done) => !done) + const exitCode = Effect.flatMap(Deferred.await(exitSignal), (status) => + Predicate.isNull(status.signal) + ? Effect.succeed(ExitCode(status.code)) + : Effect.fail(toPlatformError( + "exitCode", + new globalThis.Error(`Process interrupted due to receipt of signal: '${status.signal}'`), + cmd + ))) + const kill = (options?: ChildProcess.KillOptions | undefined) => { + const killWithTimeout = withTimeout(childProcess, cmd, options) + return killWithTimeout((command, childProcess, signal) => + killProcess(command, childProcess, signal).pipe( + Effect.andThen(Deferred.await(exitSignal)) + ) + ).pipe(Effect.asVoid) + } + + return makeHandle({ + pid: ProcessId(childProcess.pid), + exitCode, + isRunning, + kill, + stdin, + stdout, + stderr, + all, + getInputFd: () => Sink.drain, + getOutputFd: () => Stream.empty, + unref + }) + } + case "PipedCommand": { + const { commands, pipeOptions } = flattenCommand(cmd) + const [root, ...pipeline] = commands + const handles = [yield* spawnCommand(root)] + + for (let i = 0; i < pipeline.length; i++) { + const command = pipeline[i] + const options = pipeOptions[i] ?? {} + const stdinConfig = resolveStdinOption(command.options) + const from = options.from ?? "stdout" + + if ( + (from !== "stdout" && from !== "stderr" && from !== "all") || + (options.to ?? "stdin") !== "stdin" + ) { + return yield* Effect.fail(unsupported("additionalFds")) + } + const sourceStream = getSourceStream(handles[handles.length - 1], from) + handles.push( + yield* spawnCommand(ChildProcess.make(command.command, command.args, { + ...command.options, + stdin: { ...stdinConfig, stream: sourceStream } + })) + ) + } + + const handle = handles[handles.length - 1] + const kill = (options?: ChildProcess.KillOptions | undefined) => + Effect.forEach([...handles].reverse(), (handle) => Effect.ignore(handle.kill(options)), { discard: true }) + const unref = Effect.gen(function*() { + const rerefs: Array> = [] + for (const handle of handles) rerefs.push(yield* handle.unref) + return Effect.forEach([...rerefs].reverse(), (reref) => reref, { discard: true }) + }) + + return makeHandle({ + pid: handle.pid, + exitCode: handle.exitCode, + isRunning: handle.isRunning, + kill, + stdin: handle.stdin, + stdout: handle.stdout, + stderr: handle.stderr, + all: handle.all, + getInputFd: handle.getInputFd, + getOutputFd: handle.getOutputFd, + unref + }) + } + } + }) + + return makeSpawner(spawnCommand) +}) + +/** + * Layer that provides the Deno child process spawner. + * + * @category layers + * @since 4.0.0 + */ +export const layer: Layer.Layer = Layer.effect(ChildProcessSpawner, make) + +/** + * Result of flattening a pipeline of commands. + * + * @category models + * @since 4.0.0 + */ +export interface FlattenedPipeline { + readonly commands: Arr.NonEmptyReadonlyArray + readonly pipeOptions: ReadonlyArray +} + +/** + * Flattens a command into standard commands and their pipe options. + * + * @category transforming + * @since 4.0.0 + */ +export const flattenCommand = (command: ChildProcess.Command): FlattenedPipeline => { + const commands: Array = [] + const pipeOptions: Array = [] + + const flatten = (command: ChildProcess.Command): void => { + switch (command._tag) { + case "StandardCommand": + commands.push(command) + break + case "PipedCommand": + flatten(command.left) + pipeOptions.push(command.options) + flatten(command.right) + break + } + } + flatten(command) + + const [first, ...rest] = commands + if (first === undefined) throw new Error("flattenCommand produced empty commands array") + return { commands: [first, ...rest], pipeOptions } +} diff --git a/repos/effect/packages/platform-deno/src/DenoClusterHttp.ts b/repos/effect/packages/platform-deno/src/DenoClusterHttp.ts new file mode 100644 index 0000000000..0114b9ff89 --- /dev/null +++ b/repos/effect/packages/platform-deno/src/DenoClusterHttp.ts @@ -0,0 +1,156 @@ +/** + * Native Deno HTTP and WebSocket layers for Effect Cluster runners. + * + * `layerHttpServer` provides the Deno HTTP server used by cluster runners. The + * main `layer` builds a sharding layer for HTTP or WebSocket transport, + * choosing serialization, runner health checks, runner storage, message + * storage, and optional client-only mode from the supplied options. + * + * @since 4.0.0 + */ +import type * as Config from "effect/Config" +import * as Effect from "effect/Effect" +import * as Layer from "effect/Layer" +import * as Option from "effect/Option" +import * as HttpRunner from "effect/unstable/cluster/HttpRunner" +import * as MessageStorage from "effect/unstable/cluster/MessageStorage" +import * as RunnerHealth from "effect/unstable/cluster/RunnerHealth" +import * as Runners from "effect/unstable/cluster/Runners" +import * as RunnerStorage from "effect/unstable/cluster/RunnerStorage" +import type { Sharding } from "effect/unstable/cluster/Sharding" +import * as ShardingConfig from "effect/unstable/cluster/ShardingConfig" +import * as SqlMessageStorage from "effect/unstable/cluster/SqlMessageStorage" +import * as SqlRunnerStorage from "effect/unstable/cluster/SqlRunnerStorage" +import type * as Etag from "effect/unstable/http/Etag" +import type { HttpPlatform } from "effect/unstable/http/HttpPlatform" +import type { HttpServer } from "effect/unstable/http/HttpServer" +import type { ServeError } from "effect/unstable/http/HttpServerError" +import * as RpcSerialization from "effect/unstable/rpc/RpcSerialization" +import type { SqlClient } from "effect/unstable/sql/SqlClient" +import { layerK8sHttpClient } from "./DenoClusterSocket.ts" +import * as DenoCrypto from "./DenoCrypto.ts" +import * as DenoHttpClient from "./DenoHttpClient.ts" +import * as DenoHttpServer from "./DenoHttpServer.ts" +import type { DenoServices } from "./DenoServices.ts" +import * as DenoSocket from "./DenoSocket.ts" + +export { + /** + * Layer that provides a Kubernetes HTTP client for runner health checks. + * + * @category re-exports + * @since 4.0.0 + */ + layerK8sHttpClient +} + +/** + * Layer that provides a native Deno HTTP server for cluster runners. + * + * @category layers + * @since 4.0.0 + */ +export const layerHttpServer: Layer.Layer< + | HttpPlatform + | Etag.Generator + | DenoServices + | HttpServer, + ServeError, + ShardingConfig.ShardingConfig +> = Effect.gen(function*() { + const config = yield* ShardingConfig.ShardingConfig + const listenAddress = Option.orElse(config.runnerListenAddress, () => config.runnerAddress) + if (Option.isNone(listenAddress)) { + return yield* Effect.die("DenoClusterHttp.layerHttpServer: ShardingConfig.runnerAddress is None") + } + return DenoHttpServer.layer({ + hostname: listenAddress.value.host, + port: listenAddress.value.port, + onListen: () => {} + }) +}).pipe(Layer.unwrap) + +/** + * Creates Deno cluster layers for HTTP or WebSocket transport, configuring + * serialization, storage, runner health, and optional client-only mode. + * + * @category layers + * @since 4.0.0 + */ +export const layer = < + const ClientOnly extends boolean = false, + const Storage extends "local" | "sql" | "byo" = never +>(options: { + readonly transport: "http" | "websocket" + readonly serialization?: "msgpack" | "ndjson" | undefined + readonly serializationMaxBufferSize?: number | "unbounded" | undefined + readonly clientOnly?: ClientOnly | undefined + readonly storage?: Storage | undefined + readonly runnerHealth?: "ping" | "k8s" | undefined + readonly runnerHealthK8s?: { + readonly namespace?: string | undefined + readonly labelSelector?: string | undefined + } | undefined + readonly shardingConfig?: Partial | undefined +}): ClientOnly extends true ? Layer.Layer< + Sharding | Runners.Runners | ("byo" extends Storage ? never : MessageStorage.MessageStorage), + Config.ConfigError, + "local" extends Storage ? never + : "byo" extends Storage ? (MessageStorage.MessageStorage | RunnerStorage.RunnerStorage) + : SqlClient + > : + Layer.Layer< + Sharding | Runners.Runners | ("byo" extends Storage ? never : MessageStorage.MessageStorage), + ServeError | Config.ConfigError, + "local" extends Storage ? never + : "byo" extends Storage ? (MessageStorage.MessageStorage | RunnerStorage.RunnerStorage) + : SqlClient + > => +{ + const layer: Layer.Layer = options.clientOnly + ? options.transport === "http" + ? Layer.provide(HttpRunner.layerHttpClientOnly, DenoHttpClient.layer) + : Layer.provide(HttpRunner.layerWebsocketClientOnly, DenoSocket.layerWebSocketConstructor) + : options.transport === "http" + ? Layer.provide(HttpRunner.layerHttp, [layerHttpServer, DenoHttpClient.layer]) + : Layer.provide(HttpRunner.layerWebsocket, [layerHttpServer, DenoSocket.layerWebSocketConstructor]) + + const runnerHealth: Layer.Layer = options.clientOnly + ? Layer.empty as any + : options.runnerHealth === "k8s" + ? RunnerHealth.layerK8s(options.runnerHealthK8s).pipe( + Layer.provide(layerK8sHttpClient) + ) + : RunnerHealth.layerPing.pipe( + Layer.provide(Runners.layerRpc), + Layer.provide( + options.transport === "http" + ? HttpRunner.layerClientProtocolHttpDefault.pipe(Layer.provide(DenoHttpClient.layer)) + : HttpRunner.layerClientProtocolWebsocketDefault.pipe(Layer.provide(DenoSocket.layerWebSocketConstructor)) + ) + ) + + return layer.pipe( + Layer.provide(runnerHealth), + Layer.provideMerge( + options.storage === "local" + ? MessageStorage.layerNoop + : options.storage === "byo" + ? Layer.empty + : Layer.orDie(SqlMessageStorage.layer).pipe(Layer.provide(DenoCrypto.layer)) + ), + Layer.provide( + options.storage === "local" + ? RunnerStorage.layerMemory + : options.storage === "byo" + ? Layer.empty + : Layer.orDie(SqlRunnerStorage.layer) + ), + Layer.provide(ShardingConfig.layerFromEnv(options.shardingConfig)), + Layer.provide( + options.serialization === "ndjson" + ? RpcSerialization.layerNdjsonWith({ maxBufferSize: options.serializationMaxBufferSize }) + : RpcSerialization.layerMsgPackWith({ maxBufferSize: options.serializationMaxBufferSize }) + ) + ) as any +} diff --git a/repos/effect/packages/platform-deno/src/DenoClusterSocket.ts b/repos/effect/packages/platform-deno/src/DenoClusterSocket.ts new file mode 100644 index 0000000000..fbb86a2dbd --- /dev/null +++ b/repos/effect/packages/platform-deno/src/DenoClusterSocket.ts @@ -0,0 +1,197 @@ +/** + * Native Deno socket layers for Effect Cluster runners. + * + * The main `layer` builds a sharding layer for socket transport, choosing + * serialization, runner health checks, runner storage, message storage, and + * optional client-only mode from the supplied options. Unlike Node sockets, + * Deno connections have no native idle-timeout option, so peer connections use + * only the one-second open timeout. + * + * @since 4.0.0 + */ +import type * as Config from "effect/Config" +import * as Effect from "effect/Effect" +import * as FileSystem from "effect/FileSystem" +import * as Layer from "effect/Layer" +import * as Option from "effect/Option" +import * as K8sHttpClient from "effect/unstable/cluster/K8sHttpClient" +import * as MessageStorage from "effect/unstable/cluster/MessageStorage" +import * as RunnerHealth from "effect/unstable/cluster/RunnerHealth" +import * as Runners from "effect/unstable/cluster/Runners" +import * as RunnerStorage from "effect/unstable/cluster/RunnerStorage" +import type { Sharding } from "effect/unstable/cluster/Sharding" +import * as ShardingConfig from "effect/unstable/cluster/ShardingConfig" +import * as SocketRunner from "effect/unstable/cluster/SocketRunner" +import * as SqlMessageStorage from "effect/unstable/cluster/SqlMessageStorage" +import * as SqlRunnerStorage from "effect/unstable/cluster/SqlRunnerStorage" +import * as RpcClient from "effect/unstable/rpc/RpcClient" +import * as RpcSerialization from "effect/unstable/rpc/RpcSerialization" +import { Socket } from "effect/unstable/socket/Socket" +import type * as SocketServer from "effect/unstable/socket/SocketServer" +import type { SqlClient } from "effect/unstable/sql/SqlClient" +import * as DenoCrypto from "./DenoCrypto.ts" +import * as DenoFileSystem from "./DenoFileSystem.ts" +import * as DenoHttpClient from "./DenoHttpClient.ts" +import * as DenoSocket from "./DenoSocket.ts" +import * as DenoSocketServer from "./DenoSocketServer.ts" + +/** + * Provides the cluster `RpcClientProtocol` using native Deno TCP sockets. + * + * @category layers + * @since 4.0.0 + */ +export const layerClientProtocol: Layer.Layer< + Runners.RpcClientProtocol, + never, + RpcSerialization.RpcSerialization +> = Layer.effect(Runners.RpcClientProtocol)( + Effect.gen(function*() { + const serialization = yield* RpcSerialization.RpcSerialization + return Effect.fnUntraced(function*(address) { + const socket = yield* DenoSocket.makeTcp({ + openTimeout: 1000, + hostname: address.host, + port: address.port + }) + return yield* RpcClient.makeProtocolSocket().pipe( + Effect.provideService(Socket, socket), + Effect.provideService(RpcSerialization.RpcSerialization, serialization) + ) + }, Effect.orDie) + }) +) + +/** + * Provides the native Deno socket server used by cluster runners, listening on + * `ShardingConfig.runnerListenAddress` or `runnerAddress`. + * + * @category layers + * @since 4.0.0 + */ +export const layerSocketServer: Layer.Layer< + SocketServer.SocketServer, + SocketServer.SocketServerError, + ShardingConfig.ShardingConfig +> = Effect.gen(function*() { + const config = yield* ShardingConfig.ShardingConfig + const listenAddress = Option.orElse(config.runnerListenAddress, () => config.runnerAddress) + if (Option.isNone(listenAddress)) { + return yield* Effect.die("layerSocketServer: ShardingConfig.runnerListenAddress is None") + } + return DenoSocketServer.layer({ + hostname: listenAddress.value.host, + port: listenAddress.value.port + }) +}).pipe(Layer.unwrap) + +/** + * Creates Deno socket cluster layers, configuring serialization, storage, + * runner health, and optional client-only mode. + * + * @category layers + * @since 4.0.0 + */ +export const layer = < + const ClientOnly extends boolean = false, + const Storage extends "local" | "sql" | "byo" = never +>( + options?: { + readonly serialization?: "msgpack" | "ndjson" | undefined + readonly serializationMaxBufferSize?: number | "unbounded" | undefined + readonly clientOnly?: ClientOnly | undefined + readonly storage?: Storage | undefined + readonly runnerHealth?: "ping" | "k8s" | undefined + readonly runnerHealthK8s?: { + readonly namespace?: string | undefined + readonly labelSelector?: string | undefined + } | undefined + readonly shardingConfig?: Partial | undefined + } +): ClientOnly extends true ? Layer.Layer< + Sharding | Runners.Runners | ("byo" extends Storage ? never : MessageStorage.MessageStorage), + Config.ConfigError, + "local" extends Storage ? never + : "byo" extends Storage ? (MessageStorage.MessageStorage | RunnerStorage.RunnerStorage) + : SqlClient + > : + Layer.Layer< + Sharding | Runners.Runners | ("byo" extends Storage ? never : MessageStorage.MessageStorage), + SocketServer.SocketServerError | Config.ConfigError, + "local" extends Storage ? never + : "byo" extends Storage ? (MessageStorage.MessageStorage | RunnerStorage.RunnerStorage) + : SqlClient + > => +{ + const layer: Layer.Layer = options?.clientOnly + ? Layer.provide(SocketRunner.layerClientOnly, layerClientProtocol) + : Layer.provide(SocketRunner.layer, [layerSocketServer, layerClientProtocol]) + + const runnerHealth: Layer.Layer = options?.clientOnly + ? Layer.empty as any + : options?.runnerHealth === "k8s" + ? RunnerHealth.layerK8s(options.runnerHealthK8s).pipe( + Layer.provide(layerK8sHttpClient) + ) + : RunnerHealth.layerPing.pipe( + Layer.provide(Runners.layerRpc), + Layer.provide(layerClientProtocol) + ) + + return layer.pipe( + Layer.provide(runnerHealth), + Layer.provideMerge( + options?.storage === "local" + ? MessageStorage.layerNoop + : options?.storage === "byo" + ? Layer.empty + : Layer.orDie(SqlMessageStorage.layer).pipe(Layer.provide(DenoCrypto.layer)) + ), + Layer.provide( + options?.storage === "local" + ? RunnerStorage.layerMemory + : options?.storage === "byo" + ? Layer.empty + : Layer.orDie(SqlRunnerStorage.layer) + ), + Layer.provide(ShardingConfig.layerFromEnv(options?.shardingConfig)), + Layer.provide( + options?.serialization === "ndjson" + ? RpcSerialization.layerNdjsonWith({ maxBufferSize: options?.serializationMaxBufferSize }) + : RpcSerialization.layerMsgPackWith({ maxBufferSize: options?.serializationMaxBufferSize }) + ) + ) as any +} + +/** + * Layer that provides `K8sHttpClient`, using a scoped native Deno HTTP client + * with the Kubernetes service-account CA certificate when it is available. + * + * @category layers + * @since 4.0.0 + */ +export const layerK8sHttpClient: Layer.Layer = K8sHttpClient.layer.pipe( + Layer.provide( + Layer.fresh(DenoHttpClient.layer).pipe( + Layer.provide(Layer.effect( + DenoHttpClient.Fetch, + Effect.gen(function*() { + const fs = yield* FileSystem.FileSystem + const caCertOption = yield* fs.readFileString("/var/run/secrets/kubernetes.io/serviceaccount/ca.crt").pipe( + Effect.option + ) + if (Option.isNone(caCertOption)) { + return globalThis.fetch + } + + const client = yield* Effect.acquireRelease( + Effect.sync(() => Deno.createHttpClient({ caCerts: [caCertOption.value] })), + (client) => Effect.sync(() => client.close()) + ) + return ((input, init) => globalThis.fetch(input, { ...init, client } as any)) as typeof globalThis.fetch + }) + )) + ) + ), + Layer.provide(DenoFileSystem.layer) +) diff --git a/repos/effect/packages/platform-deno/src/DenoCrypto.ts b/repos/effect/packages/platform-deno/src/DenoCrypto.ts new file mode 100644 index 0000000000..1a0d0dafdc --- /dev/null +++ b/repos/effect/packages/platform-deno/src/DenoCrypto.ts @@ -0,0 +1,63 @@ +/** + * Deno-backed implementation of Effect's Crypto service. + * + * This module uses Deno's global Web Crypto API. + * + * @since 4.0.0 + */ +import * as Context from "effect/Context" +import * as EffectCrypto from "effect/Crypto" +import * as Effect from "effect/Effect" +import * as Layer from "effect/Layer" +import * as PlatformError from "effect/PlatformError" + +/** + * Provides the Web Crypto API used by the Crypto service implementation. + * + * @category services + * @since 4.0.0 + */ +export const WebCrypto = Context.Reference("@effect/platform-deno/Crypto/WebCrypto", { + defaultValue: () => globalThis.crypto +}) + +/** + * A layer that provides Effect's Crypto service using Deno's Web Crypto API. + * + * @category layers + * @since 4.0.0 + */ +export const layer: Layer.Layer = Layer.effect( + EffectCrypto.Crypto, + Effect.gen(function*() { + const crypto = yield* WebCrypto + const randomBytes = (size: number): Uint8Array => { + const bytes = new Uint8Array(size) + for (let offset = 0; offset < bytes.length; offset += 65_536) { + crypto.getRandomValues(bytes.subarray(offset, offset + 65_536)) + } + return bytes + } + + const digest: EffectCrypto.Crypto["digest"] = (algorithm, data) => + Effect.map( + Effect.tryPromise({ + try: () => crypto.subtle.digest(algorithm, new Uint8Array(data)), + catch: (cause) => + PlatformError.systemError({ + module: "Crypto", + method: "digest", + _tag: "Unknown", + description: "Could not compute digest", + cause + }) + }), + (buffer) => new Uint8Array(buffer) + ) + + return EffectCrypto.make({ + randomBytes, + digest + }) + }) +) diff --git a/repos/effect/packages/platform-deno/src/DenoFileSystem.ts b/repos/effect/packages/platform-deno/src/DenoFileSystem.ts new file mode 100644 index 0000000000..9e263a7c35 --- /dev/null +++ b/repos/effect/packages/platform-deno/src/DenoFileSystem.ts @@ -0,0 +1,504 @@ +/** + * Deno implementation of Effect's `FileSystem` service. + * + * @since 4.0.0 + */ +import { copy as denoCopy, expandGlob, walk } from "@std/fs" +import { relative } from "@std/path" +import * as Effect from "effect/Effect" +import * as FileSystem from "effect/FileSystem" +import * as Layer from "effect/Layer" +import * as Option from "effect/Option" +import * as PlatformError from "effect/PlatformError" +import * as Stream from "effect/Stream" +import { handleError } from "./internal/error.ts" + +const tryPromise = ( + method: string, + pathOrDescriptor: string | number | undefined, + evaluate: (signal: AbortSignal) => PromiseLike +) => + Effect.tryPromise({ + try: evaluate, + catch: handleError("FileSystem", method, pathOrDescriptor) + }) + +const close = (file: Deno.FsFile, method: string, pathOrDescriptor: string | number) => + Effect.orDie(Effect.try({ + try: () => file.close(), + catch: handleError("FileSystem", method, pathOrDescriptor) + })) + +const collectAsyncIterable = ( + method: string, + pathOrDescriptor: string | number | undefined, + evaluate: () => AsyncIterable +) => + Effect.tryPromise({ + try: async () => { + const values = new Array() + for await (const value of evaluate()) { + values.push(value) + } + return values + }, + catch: handleError("FileSystem", method, pathOrDescriptor) + }) + +const access: FileSystem.FileSystem["access"] = (path, options) => { + if (!options?.readable && !options?.writable) { + return Effect.asVoid(tryPromise("access", path, () => Deno.stat(path))) + } + return Effect.acquireUseRelease( + tryPromise("access", path, () => + Deno.open(path, { + ...(options.readable ? { read: true } : {}), + ...(options.writable ? { write: true } : {}) + })), + () => Effect.void, + (file) => close(file, "access", path) + ) +} + +const copy: FileSystem.FileSystem["copy"] = (fromPath, toPath, options) => + tryPromise("copy", fromPath, () => + denoCopy(fromPath, toPath, { + overwrite: options?.overwrite ?? false, + preserveTimestamps: options?.preserveTimestamps ?? false + })) + +const copyFile: FileSystem.FileSystem["copyFile"] = (fromPath, toPath) => + tryPromise("copyFile", fromPath, () => Deno.copyFile(fromPath, toPath)) + +const chmod: FileSystem.FileSystem["chmod"] = (path, mode) => tryPromise("chmod", path, () => Deno.chmod(path, mode)) + +const chown: FileSystem.FileSystem["chown"] = (path, uid, gid) => + tryPromise("chown", path, () => Deno.chown(path, uid, gid)) + +const glob: FileSystem.FileSystem["glob"] = (pattern, options) => + Effect.map( + collectAsyncIterable("glob", pattern, () => + expandGlob(pattern, { + root: options?.root ?? Deno.cwd(), + exclude: options?.exclude ? [...options.exclude] : [] + })), + (entries) => entries.map((entry) => entry.path) + ) + +const link: FileSystem.FileSystem["link"] = (existingPath, newPath) => + tryPromise("link", existingPath, () => Deno.link(existingPath, newPath)) + +const makeDirectory: FileSystem.FileSystem["makeDirectory"] = (path, options) => + tryPromise("makeDirectory", path, () => + Deno.mkdir(path, { + recursive: options?.recursive ?? false, + ...(options?.mode === undefined ? {} : { mode: options.mode }) + })) + +const makeTempDirectoryFactory = (method: string): FileSystem.FileSystem["makeTempDirectory"] => (options) => + tryPromise(method, options?.directory, () => + Deno.makeTempDir({ + ...(options?.directory === undefined ? {} : { dir: options.directory }), + ...(options?.prefix === undefined ? {} : { prefix: options.prefix }) + })) + +const makeTempDirectory = makeTempDirectoryFactory("makeTempDirectory") + +const removeFactory = (method: string): FileSystem.FileSystem["remove"] => (path, options) => { + const effect = tryPromise(method, path, () => Deno.remove(path, { recursive: options?.recursive ?? false })) + return options?.force + ? Effect.catchTag( + effect, + "PlatformError", + (error) => error.reason._tag === "NotFound" ? Effect.void : Effect.fail(error) + ) + : effect +} + +const remove = removeFactory("remove") + +const makeTempDirectoryScoped: FileSystem.FileSystem["makeTempDirectoryScoped"] = (options) => + Effect.acquireRelease( + makeTempDirectoryFactory("makeTempDirectoryScoped")(options), + (directory) => Effect.orDie(removeFactory("makeTempDirectoryScoped")(directory, { recursive: true })) + ) + +const openOptions = (flag: FileSystem.OpenFlag = "r", mode?: number): Deno.OpenOptions => { + const modeOption = mode === undefined ? {} : { mode } + switch (flag) { + case "r": + return { read: true, ...modeOption } + case "r+": + return { read: true, write: true, ...modeOption } + case "w": + return { write: true, create: true, truncate: true, ...modeOption } + case "wx": + return { write: true, createNew: true, ...modeOption } + case "w+": + return { read: true, write: true, create: true, truncate: true, ...modeOption } + case "wx+": + return { read: true, write: true, createNew: true, ...modeOption } + case "a": + return { append: true, create: true, ...modeOption } + case "ax": + return { append: true, createNew: true, ...modeOption } + case "a+": + return { read: true, append: true, create: true, ...modeOption } + case "ax+": + return { read: true, append: true, createNew: true, ...modeOption } + } +} + +const makeFileInfo = (info: Deno.FileInfo): FileSystem.File.Info => ({ + type: info.isFile ? + "File" : + info.isDirectory ? + "Directory" : + info.isSymlink ? + "SymbolicLink" : + info.isBlockDevice ? + "BlockDevice" : + info.isCharDevice ? + "CharacterDevice" : + info.isFifo ? + "FIFO" : + info.isSocket ? + "Socket" : + "Unknown", + mtime: Option.fromNullishOr(info.mtime), + atime: Option.fromNullishOr(info.atime), + birthtime: Option.fromNullishOr(info.birthtime), + dev: info.dev, + rdev: Option.fromNullishOr(info.rdev), + ino: Option.fromNullishOr(info.ino), + mode: info.mode ?? 0, + nlink: Option.fromNullishOr(info.nlink), + uid: Option.fromNullishOr(info.uid), + gid: Option.fromNullishOr(info.gid), + size: FileSystem.Size(info.size), + blksize: Option.map(Option.fromNullishOr(info.blksize), FileSystem.Size), + blocks: Option.fromNullishOr(info.blocks) +}) + +/** A file handle is stateful and must not be used concurrently. */ +class FileImpl implements FileSystem.File { + readonly [FileSystem.FileTypeId]: typeof FileSystem.FileTypeId = FileSystem.FileTypeId + private readonly file: Deno.FsFile + private readonly append: boolean + private position = BigInt(0) + private nativePosition: bigint | undefined = undefined + + constructor( + file: Deno.FsFile, + append: boolean + ) { + this.file = file + this.append = append + } + + get stat() { + return Effect.map( + tryPromise("stat", undefined, () => this.file.stat()), + makeFileInfo + ) + } + + get sync() { + return tryPromise("sync", undefined, () => this.file.sync()) + } + + seek(offset: FileSystem.SizeInput, from: FileSystem.SeekMode) { + const size = FileSystem.Size(offset) + return Effect.sync(() => { + if (from === "start") { + this.position = size + } else { + this.position += size + } + return FileSystem.Size(this.position) + }) + } + + private readChunk(method: string, buffer: Uint8Array) { + return Effect.suspend(() => { + const position = this.position + return Effect.map( + tryPromise( + method, + undefined, + async () => { + if (this.nativePosition !== position) { + this.file.seekSync(position, Deno.SeekMode.Start) + } + this.nativePosition = undefined + return await this.file.read(buffer) + } + ), + (bytesRead) => { + const sizeRead = FileSystem.Size(bytesRead ?? 0) + this.position = this.nativePosition = position + sizeRead + return sizeRead + } + ) + }) + } + + read(buffer: Uint8Array) { + return this.readChunk("read", buffer) + } + + readAlloc(size: FileSystem.SizeInput) { + const sizeNumber = Number(size) + return Effect.suspend(() => { + const buffer = new Uint8Array(sizeNumber) + return Effect.map(this.readChunk("readAlloc", buffer), (bytesRead) => { + if (bytesRead === BigInt(0)) { + return Option.none() + } + return Option.some(bytesRead === BigInt(sizeNumber) ? buffer : buffer.subarray(0, Number(bytesRead))) + }) + }) + } + + truncate(length?: FileSystem.SizeInput) { + const size = FileSystem.Size(length ?? 0) + return Effect.map( + tryPromise("truncate", undefined, () => this.file.truncate(Number(size))), + () => { + if (!this.append && this.position > size) { + this.position = size + } + } + ) + } + + private writeChunk(method: string, buffer: Uint8Array) { + return Effect.suspend(() => { + const position = this.position + return Effect.map( + tryPromise( + method, + undefined, + async () => { + if (!this.append && this.nativePosition !== position) { + this.file.seekSync(position, Deno.SeekMode.Start) + } + this.nativePosition = undefined + return await this.file.write(buffer) + } + ), + (bytesWritten) => { + const sizeWritten = FileSystem.Size(bytesWritten) + if (this.append) { + this.nativePosition = undefined + } else { + this.position = this.nativePosition = position + sizeWritten + } + return sizeWritten + } + ) + }) + } + + write(buffer: Uint8Array) { + return this.writeChunk("write", buffer) + } + + private writeAllChunk(buffer: Uint8Array): Effect.Effect { + return Effect.flatMap(this.writeChunk("writeAll", buffer), (bytesWritten) => { + if (bytesWritten === BigInt(0)) { + return Effect.fail(PlatformError.systemError({ + module: "FileSystem", + method: "writeAll", + _tag: "WriteZero", + description: "write returned 0 bytes written" + })) + } + return bytesWritten < buffer.length + ? this.writeAllChunk(buffer.subarray(Number(bytesWritten))) + : Effect.void + }) + } + + writeAll(buffer: Uint8Array) { + return buffer.length === 0 ? Effect.void : this.writeAllChunk(buffer) + } +} + +const open: FileSystem.FileSystem["open"] = (path, options) => { + const append = options?.flag?.startsWith("a") ?? false + return Effect.map( + Effect.acquireRelease( + tryPromise("open", path, () => Deno.open(path, openOptions(options?.flag, options?.mode))), + (file) => close(file, "open", path) + ), + (file) => new FileImpl(file, append) + ) +} + +const makeTempFileFactory = (method: string): FileSystem.FileSystem["makeTempFile"] => (options) => + tryPromise(method, options?.directory, () => + Deno.makeTempFile({ + ...(options?.directory === undefined ? {} : { dir: options.directory }), + ...(options?.prefix === undefined ? {} : { prefix: options.prefix }), + ...(options?.suffix === undefined ? {} : { suffix: options.suffix }) + })) + +const makeTempFile = makeTempFileFactory("makeTempFile") + +const makeTempFileScoped: FileSystem.FileSystem["makeTempFileScoped"] = (options) => + Effect.acquireRelease( + makeTempFileFactory("makeTempFileScoped")(options), + (file) => Effect.orDie(removeFactory("makeTempFileScoped")(file, { force: true })) + ) + +const readDirectory: FileSystem.FileSystem["readDirectory"] = (path, options) => { + if (options?.recursive) { + return Effect.map( + collectAsyncIterable("readDirectory", path, () => walk(path)), + (entries) => entries.map((entry) => relative(path, entry.path)).filter(Boolean) + ) + } + return Effect.map( + collectAsyncIterable("readDirectory", path, () => Deno.readDir(path)), + (entries) => entries.map((entry) => entry.name) + ) +} + +const readFile: FileSystem.FileSystem["readFile"] = (path) => + tryPromise("readFile", path, (signal) => Deno.readFile(path, { signal })) + +const readLink: FileSystem.FileSystem["readLink"] = (path) => tryPromise("readLink", path, () => Deno.readLink(path)) + +const realPath: FileSystem.FileSystem["realPath"] = (path) => tryPromise("realPath", path, () => Deno.realPath(path)) + +const rename: FileSystem.FileSystem["rename"] = (oldPath, newPath) => + tryPromise("rename", oldPath, () => Deno.rename(oldPath, newPath)) + +const stat: FileSystem.FileSystem["stat"] = (path) => + Effect.map( + tryPromise("stat", path, () => Deno.stat(path)), + makeFileInfo + ) + +const symlink: FileSystem.FileSystem["symlink"] = (target, path) => + tryPromise("symlink", target, () => Deno.symlink(target, path)) + +const truncate: FileSystem.FileSystem["truncate"] = (path, length) => + tryPromise("truncate", path, () => Deno.truncate(path, length === undefined ? undefined : Number(length))) + +const utimes: FileSystem.FileSystem["utimes"] = (path, atime, mtime) => + tryPromise("utimes", path, () => Deno.utime(path, atime, mtime)) + +const watchNative = ( + path: string, + options?: FileSystem.WatchOptions +): Stream.Stream => + Stream.unwrap( + Effect.map( + Effect.try({ + try: () => Deno.watchFs(path, { recursive: options?.recursive ?? false }), + catch: handleError("FileSystem", "watch", path) + }), + (watcher) => + Stream.fromAsyncIterable(watcher, handleError("FileSystem", "watch", path)).pipe( + Stream.flatMap((event): Stream.Stream => { + switch (event.kind) { + case "create": + return Stream.map(Stream.fromIterable(event.paths), (path) => ({ _tag: "Create" as const, path })) + case "modify": + return Stream.map(Stream.fromIterable(event.paths), (path) => ({ _tag: "Update" as const, path })) + case "remove": + return Stream.map(Stream.fromIterable(event.paths), (path) => ({ _tag: "Remove" as const, path })) + case "rename": + return Stream.mapEffect(Stream.fromIterable(event.paths), (path) => + Effect.match(stat(path), { + onFailure: () => ({ _tag: "Remove" as const, path }), + onSuccess: () => ({ _tag: "Create" as const, path }) + })) + default: + return Stream.empty + } + }) + ) + ) + ) + +const watch = ( + backend: Option.Option, + path: string, + options?: FileSystem.WatchOptions +) => + stat(path).pipe( + Effect.map((info) => + backend.pipe( + Option.flatMap((backend) => backend.register(path, info, options)), + Option.getOrElse(() => watchNative(path, options)) + ) + ), + Stream.unwrap + ) + +const writeFile: FileSystem.FileSystem["writeFile"] = (path, data, options) => { + const flag = options?.flag ?? "w" + if (flag === "w" || flag === "wx" || flag === "a" || flag === "ax") { + return tryPromise("writeFile", path, (signal) => + Deno.writeFile(path, data, { + append: flag.startsWith("a"), + createNew: flag.includes("x"), + ...(options?.mode === undefined ? {} : { mode: options.mode }), + signal + })) + } + return Effect.acquireUseRelease( + tryPromise("writeFile", path, () => Deno.open(path, openOptions(flag, options?.mode))), + (file) => + new FileImpl(file, flag.startsWith("a")).writeAll(data).pipe( + Effect.mapError((error) => + error.reason._tag !== "BadArgument" + ? PlatformError.systemError({ ...error.reason, method: "writeFile", pathOrDescriptor: path }) + : error + ) + ), + (file) => close(file, "writeFile", path) + ) +} + +const makeFileSystem = Effect.map(Effect.serviceOption(FileSystem.WatchBackend), (backend) => + FileSystem.make({ + access, + chmod, + chown, + copy, + copyFile, + glob, + link, + makeDirectory, + makeTempDirectory, + makeTempDirectoryScoped, + makeTempFile, + makeTempFileScoped, + open, + readDirectory, + readFile, + readLink, + realPath, + remove, + rename, + stat, + symlink, + truncate, + utimes, + watch(path, options) { + return watch(backend, path, options) + }, + writeFile + })) + +/** + * Provides the `FileSystem` service backed by Deno filesystem APIs. + * + * @category layers + * @since 4.0.0 + */ +export const layer: Layer.Layer = Layer.effect(FileSystem.FileSystem)(makeFileSystem) diff --git a/repos/effect/packages/platform-deno/src/DenoHttpClient.ts b/repos/effect/packages/platform-deno/src/DenoHttpClient.ts new file mode 100644 index 0000000000..34232a7dec --- /dev/null +++ b/repos/effect/packages/platform-deno/src/DenoHttpClient.ts @@ -0,0 +1,9 @@ +/** + * @since 4.0.0 + */ + +/** + * @category re-exports + * @since 4.0.0 + */ +export * from "effect/unstable/http/FetchHttpClient" diff --git a/repos/effect/packages/platform-deno/src/DenoHttpPlatform.ts b/repos/effect/packages/platform-deno/src/DenoHttpPlatform.ts new file mode 100644 index 0000000000..f0dd88f78a --- /dev/null +++ b/repos/effect/packages/platform-deno/src/DenoHttpPlatform.ts @@ -0,0 +1,110 @@ +/** + * Deno implementation of the Effect HTTP platform service. + * + * File responses use `Deno.FsFile.readable` directly so full-file responses + * retain Deno's resource-backed fast path. Deno closes the file after the body + * is sent, but server adapters must cancel a raw body when they do not send it, + * including when handling `HEAD` requests. + * + * The provided layer uses strong ETags, unlike the portable `HttpPlatform` + * layer, which uses weak ETags. + * + * @since 4.0.0 + */ +import * as NodeHttpCompression from "@effect/platform-node-shared/NodeHttpCompression" +import { contentType } from "@std/media-types" +import { extname } from "@std/path" +import { ByteSliceStream } from "@std/streams" +import * as Layer from "effect/Layer" +import * as Etag from "effect/unstable/http/Etag" +import * as Platform from "effect/unstable/http/HttpPlatform" +import * as Response from "effect/unstable/http/HttpServerResponse" +import * as DenoFileSystem from "./DenoFileSystem.ts" + +// gzip and deflate use the native CompressionStream, which does not expose a +// per-chunk flush control. br and zstd go through node:zlib compatibility +// streams and flush each input chunk. +const compression = NodeHttpCompression.make(Platform.makeCompressionWeb({ + algorithms: NodeHttpCompression.algorithms, + transform: (algorithm, options) => + algorithm === "gzip" || algorithm === "deflate" + ? Platform.compressionTransformWeb(algorithm) + : NodeHttpCompression.compressTransformWeb(algorithm, options) +})) + +/** + * Creates the Deno `HttpPlatform`, serving file responses from resource-backed + * readable streams and adding content type and content length headers. + * + * @category constructors + * @since 4.0.0 + */ +export const make = Platform.make({ + platform: "deno", + compression, + fileResponse(path, status, statusText, headers, start, end, contentLength) { + let body: ReadableStream + if (contentLength === 0) { + body = new ReadableStream({ + start(controller) { + controller.close() + } + }) + } else { + const file = Deno.openSync(path) + file.seekSync(start, Deno.SeekMode.Start) + body = end === undefined + ? file.readable + : file.readable.pipeThrough(new ByteSliceStream(0, contentLength - 1)) + } + return Response.raw(body, { + headers: { + ...headers, + "content-type": headers["content-type"] ?? contentType(extname(path)) ?? "application/octet-stream", + "content-length": contentLength.toString() + }, + status, + statusText + }) + }, + fileWebResponse(file, status, statusText, headers, options) { + const offset = Number(options?.offset ?? 0) + const available = Math.max(0, file.size - offset) + const contentLength = options?.bytesToRead === undefined + ? available + : Math.min(available, Math.max(0, Number(options.bytesToRead))) + let body: typeof file | ReadableStream = file + if (contentLength === 0) { + body = new ReadableStream({ + start(controller) { + controller.close() + } + }) + } else if (offset > 0 || options?.bytesToRead !== undefined) { + body = (file.stream() as ReadableStream).pipeThrough( + new ByteSliceStream(offset, offset + contentLength - 1) + ) + } + return Response.raw(body, { + headers: { + ...headers, + "content-type": file.type, + "content-length": contentLength.toString() + }, + status, + statusText + }) + } +}) + +/** + * Provides the Deno `HttpPlatform` together with its filesystem and strong ETag + * services. + * + * @category layers + * @since 4.0.0 + */ +export const layer: Layer.Layer = Layer.effect(Platform.HttpPlatform)(make).pipe( + Layer.provide(DenoFileSystem.layer), + Layer.provide(Etag.layer) +) diff --git a/repos/effect/packages/platform-deno/src/DenoHttpServer.ts b/repos/effect/packages/platform-deno/src/DenoHttpServer.ts new file mode 100644 index 0000000000..a212627110 --- /dev/null +++ b/repos/effect/packages/platform-deno/src/DenoHttpServer.ts @@ -0,0 +1,535 @@ +/** + * Native Deno implementation of the Effect `HttpServer`. + * + * @since 4.0.0 + */ +import * as Config from "effect/Config" +import type { ConfigError } from "effect/Config" +import * as Context from "effect/Context" +import * as Duration from "effect/Duration" +import * as Effect from "effect/Effect" +import * as Fiber from "effect/Fiber" +import type * as FileSystem from "effect/FileSystem" +import { flow } from "effect/Function" +import * as Inspectable from "effect/Inspectable" +import * as Layer from "effect/Layer" +import * as Option from "effect/Option" +import type * as Path from "effect/Path" +import type * as Record from "effect/Record" +import type * as Schema from "effect/Schema" +import * as Scope from "effect/Scope" +import * as Stream from "effect/Stream" +import * as Cookies from "effect/unstable/http/Cookies" +import * as Etag from "effect/unstable/http/Etag" +import * as FetchHttpClient from "effect/unstable/http/FetchHttpClient" +import * as Headers from "effect/unstable/http/Headers" +import type * as HttpBody from "effect/unstable/http/HttpBody" +import type { HttpClient } from "effect/unstable/http/HttpClient" +import * as HttpEffect from "effect/unstable/http/HttpEffect" +import * as IncomingMessage from "effect/unstable/http/HttpIncomingMessage" +import type { HttpMethod } from "effect/unstable/http/HttpMethod" +import type { HttpPlatform } from "effect/unstable/http/HttpPlatform" +import * as Server from "effect/unstable/http/HttpServer" +import * as Error from "effect/unstable/http/HttpServerError" +import * as ServerRequest from "effect/unstable/http/HttpServerRequest" +import type * as ServerResponse from "effect/unstable/http/HttpServerResponse" +import type * as Multipart from "effect/unstable/http/Multipart" +import * as UrlParams from "effect/unstable/http/UrlParams" +import * as Socket from "effect/unstable/socket/Socket" +import * as Platform from "./DenoHttpPlatform.ts" +import * as DenoMultipart from "./DenoMultipart.ts" +import * as DenoServices from "./DenoServices.ts" + +/** + * Native Deno TCP, TLS, or Unix serve options managed by the scoped server. + * + * **Details** + * + * `signal` and `onError` are omitted because the scope and Effect HTTP handler + * own server and failure lifecycles. Server constructors additionally accept + * WebSocket settings that apply to every upgrade; `protocol` therefore cannot + * vary per request. + * + * @category options + * @since 4.0.0 + */ +export type ServeOptions = + | (Omit & Partial) + | Omit + +/** + * Creates a scoped native Deno HTTP server. + * + * @category constructors + * @since 4.0.0 + */ +export const make = Effect.fnUntraced(function*( + options: ServeOptions & { + readonly disablePreemptiveShutdown?: boolean | undefined + readonly gracefulShutdownTimeout?: Duration.Input | undefined + readonly websocket?: Deno.UpgradeWebSocketOptions | undefined + } +) { + const scope = yield* Effect.scope + type Handler = (request: Request, info: Deno.ServeHandlerInfo) => Promise + const notFound: Handler = (_request, _info) => Promise.resolve(new Response("not found", { status: 404 })) + const handlerStack: Array = [notFound] + let currentHandler = notFound + const { + disablePreemptiveShutdown, + gracefulShutdownTimeout, + websocket, + ...serveOptions + } = options + const server = Deno.serve( + serveOptions as Deno.ServeTcpOptions, + (request, info) => currentHandler(request, info) + ) as Deno.HttpServer + + const shutdown = yield* Effect.promise(() => server.shutdown()).pipe(Effect.cached) + const preemptiveShutdown = disablePreemptiveShutdown ? Effect.void : Effect.timeoutOrElse(shutdown, { + duration: gracefulShutdownTimeout ?? Duration.seconds(20), + orElse: () => Effect.void + }) + + yield* Scope.addFinalizer(scope, shutdown) + + const serverAddress = server.addr + const address: Server.Address = serverAddress.transport === "unix" + ? { _tag: "UnixAddress", path: serverAddress.path } + : { + _tag: "TcpAddress", + port: (serverAddress as Deno.NetAddr).port, + hostname: (serverAddress as Deno.NetAddr).hostname + } + + return Server.make({ + address, + serve: Effect.fnUntraced(function*(httpApp, middleware) { + const parent = yield* Effect.fiber + const services = parent.context + const serveScope = Context.getUnsafe(services, Scope.Scope) + const scope = Scope.forkUnsafe(serveScope, "parallel") + + const httpEffect = HttpEffect.toHandled(httpApp, (request, response) => { + const denoRequest = request as DenoServerRequest + if (denoRequest.upgraded) return cancelResponseBody(response.body) + return Effect.flatMap( + makeResponse(request, response, services, scope), + (response) => Effect.sync(() => denoRequest.resolve(response)) + ) + }, middleware) + + function handler( + request: Request, + info: Deno.ServeHandlerInfo + ): Promise { + return new Promise((resolve) => { + const context = Context.add( + services, + ServerRequest.HttpServerRequest, + new DenoServerRequest(request, info.remoteAddr, resolve, removeHost(request.url), websocket) + ) + const fiber = Fiber.runIn(Effect.runForkWith(context)(httpEffect), scope) + request.signal.addEventListener("abort", () => { + fiber.interruptUnsafe(parent.id, Error.ClientAbort.annotation) + }, { once: true }) + }) + } + + yield* Scope.addFinalizerExit(serveScope, () => { + const index = handlerStack.lastIndexOf(handler) + if (index !== -1) handlerStack.splice(index, 1) + currentHandler = handlerStack[handlerStack.length - 1] ?? notFound + return preemptiveShutdown + }) + handlerStack.push(handler) + currentHandler = handler + }) + }) +}) + +const makeResponse = Effect.fnUntraced(function*( + request: ServerRequest.HttpServerRequest, + response: ServerResponse.HttpServerResponse, + context: Context.Context, + scope: Scope.Scope +) { + const fields: { + headers: globalThis.Headers + status?: number + statusText?: string + } = { + headers: new globalThis.Headers(response.headers), + status: response.status + } + + if (!Cookies.isEmpty(response.cookies)) { + for (const header of Cookies.toSetCookieHeaders(response.cookies)) { + fields.headers.append("set-cookie", header) + } + } + if (response.statusText !== undefined) fields.statusText = response.statusText + + if (request.method === "HEAD") { + yield* cancelResponseBody(response.body) + return new Response(undefined, fields) + } + response = HttpEffect.scopeTransferToStream(response) + const body = response.body + switch (body._tag) { + case "Empty": + return new Response(undefined, fields) + case "Uint8Array": + case "Raw": { + if (body.body instanceof Response) { + for (const [key, value] of fields.headers.entries()) body.body.headers.set(key, value) + return body.body + } + return new Response(body.body as any, fields) + } + case "FormData": + return new Response(body.formData as any, fields) + case "Stream": + return new Response( + Stream.toReadableStreamWith( + Stream.unwrap(Effect.withFiber((fiber) => { + Fiber.runIn(fiber, scope) + return Effect.succeed(body.stream) + })), + context + ), + fields + ) + } +}) + +/** + * Provides only the native Deno HTTP server. + * + * @category layers + * @since 4.0.0 + */ +export const layerServer: ( + options: ServeOptions & { + readonly disablePreemptiveShutdown?: boolean | undefined + readonly gracefulShutdownTimeout?: Duration.Input | undefined + readonly websocket?: Deno.UpgradeWebSocketOptions | undefined + } +) => Layer.Layer = flow( + make, + Layer.effect(Server.HttpServer) +) + +/** + * Provides Deno HTTP platform services and the standard Deno service set. + * + * @category layers + * @since 4.0.0 + */ +export const layerHttpServices: Layer.Layer = Layer.mergeAll( + Platform.layer, + Etag.layerWeak, + DenoServices.layer +) + +/** + * Provides a native Deno HTTP server together with Deno HTTP services. + * + * @category layers + * @since 4.0.0 + */ +export const layer = ( + options: ServeOptions & { + readonly disablePreemptiveShutdown?: boolean | undefined + readonly gracefulShutdownTimeout?: Duration.Input | undefined + readonly websocket?: Deno.UpgradeWebSocketOptions | undefined + } +): Layer.Layer => + Layer.mergeAll(layerServer(options), layerHttpServices) + +/** + * Starts a Deno HTTP server on an ephemeral loopback port for tests. + * + * @category testing + * @since 4.0.0 + */ +export const layerTest: Layer.Layer< + Server.HttpServer | HttpPlatform | FileSystem.FileSystem | Etag.Generator | Path.Path | HttpClient +> = Server.layerTestClient.pipe( + Layer.provide(FetchHttpClient.layer.pipe( + Layer.provide(Layer.succeed(FetchHttpClient.RequestInit)({ keepalive: false })) + )), + Layer.provideMerge(layer({ hostname: "127.0.0.1", port: 0, onListen: () => {} })) +) + +/** + * Creates the Deno HTTP server and support-services layer from configurable options. + * + * @category layers + * @since 4.0.0 + */ +export const layerConfig = ( + options: Config.Wrap< + ServeOptions & { + readonly disablePreemptiveShutdown?: boolean | undefined + readonly gracefulShutdownTimeout?: Duration.Input | undefined + readonly websocket?: Deno.UpgradeWebSocketOptions | undefined + } + > +): Layer.Layer< + Server.HttpServer | HttpPlatform | FileSystem.FileSystem | Etag.Generator | Path.Path, + ConfigError +> => + Layer.mergeAll( + Layer.effect(Server.HttpServer)(Effect.flatMap(Config.unwrap(options), make)), + layerHttpServices + ) + +class DenoServerRequest extends Inspectable.Class implements ServerRequest.HttpServerRequest { + readonly [ServerRequest.TypeId]: typeof ServerRequest.TypeId + readonly [IncomingMessage.TypeId]: typeof IncomingMessage.TypeId + readonly source: Request + readonly remoteAddr: Deno.NetAddr | Deno.UnixAddr + readonly url: string + readonly websocketOptions: Deno.UpgradeWebSocketOptions | undefined + public resolve: (response: Response) => void + public upgraded = false + public headersOverride?: Headers.Headers | undefined + private remoteAddressOverride?: Option.Option | undefined + + constructor( + source: Request, + remoteAddr: Deno.NetAddr | Deno.UnixAddr, + resolve: (response: Response) => void, + url: string, + websocketOptions: Deno.UpgradeWebSocketOptions | undefined, + headersOverride?: Headers.Headers, + remoteAddressOverride?: Option.Option + ) { + super() + this[ServerRequest.TypeId] = ServerRequest.TypeId + this[IncomingMessage.TypeId] = IncomingMessage.TypeId + this.source = source + this.remoteAddr = remoteAddr + this.resolve = resolve + this.url = url + this.websocketOptions = websocketOptions + this.headersOverride = headersOverride + this.remoteAddressOverride = remoteAddressOverride + } + toJSON(): unknown { + return IncomingMessage.inspect(this, { + _id: "HttpServerRequest", + method: this.method, + url: this.originalUrl + }) + } + modify(options: { + readonly url?: string | undefined + readonly headers?: Headers.Headers | undefined + readonly remoteAddress?: Option.Option | undefined + }) { + return new DenoServerRequest( + this.source, + this.remoteAddr, + this.resolve, + options.url ?? this.url, + this.websocketOptions, + options.headers ?? this.headersOverride, + "remoteAddress" in options ? options.remoteAddress : this.remoteAddressOverride + ) + } + get method(): HttpMethod { + return this.source.method.toUpperCase() as HttpMethod + } + get originalUrl() { + return this.source.url + } + get remoteAddress(): Option.Option { + return this.remoteAddressOverride ?? (this.remoteAddr.transport === "tcp" + ? Option.some(this.remoteAddr.hostname) + : Option.none()) + } + get headers(): Headers.Headers { + this.headersOverride ??= Headers.fromInput(this.source.headers) + return this.headersOverride + } + + private cachedCookies: Record.ReadonlyRecord | undefined + get cookies() { + return this.cachedCookies ??= Cookies.parseHeader(this.headers.cookie ?? "") + } + + get stream(): Stream.Stream { + return this.source.body + ? Stream.fromReadableStream({ + evaluate: () => this.source.body ?? emptyReadableStream, + onError: (cause) => + new Error.HttpServerError({ + reason: new Error.RequestParseError({ request: this, cause }) + }) + }) + : Stream.fail( + new Error.HttpServerError({ + reason: new Error.RequestParseError({ + request: this, + description: "can not create stream from empty body" + }) + }) + ) + } + + private textEffect: Effect.Effect | undefined + get text(): Effect.Effect { + return this.textEffect ??= Effect.runSync(Effect.cached( + Effect.tryPromise({ + try: () => this.source.text(), + catch: (cause) => + new Error.HttpServerError({ + reason: new Error.RequestParseError({ request: this, cause }) + }) + }) + )) + } + get json(): Effect.Effect { + return Effect.flatMap(this.text, (_) => + Effect.try({ + try: () => JSON.parse(_) as Schema.Json, + catch: (cause) => + new Error.HttpServerError({ + reason: new Error.RequestParseError({ request: this, cause }) + }) + })) + } + get urlParamsBody(): Effect.Effect { + return Effect.flatMap(this.text, (_) => + Effect.try({ + try: () => UrlParams.fromInput(new URLSearchParams(_)), + catch: (cause) => + new Error.HttpServerError({ + reason: new Error.RequestParseError({ request: this, cause }) + }) + })) + } + + private multipartEffect: + | Effect.Effect + | undefined + get multipart(): Effect.Effect< + Multipart.Persisted, + Multipart.MultipartError, + Scope.Scope | FileSystem.FileSystem | Path.Path + > { + return this.multipartEffect ??= Effect.runSync(Effect.cached(DenoMultipart.persisted(this.source))) + } + get multipartStream(): Stream.Stream { + return DenoMultipart.stream(this.source) + } + + private arrayBufferEffect: Effect.Effect | undefined + get arrayBuffer(): Effect.Effect { + if (this.arrayBufferEffect) return this.arrayBufferEffect + this.arrayBufferEffect = Effect.runSync(Effect.cached( + Effect.tryPromise({ + try: () => this.source.arrayBuffer(), + catch: (cause) => + new Error.HttpServerError({ + reason: new Error.RequestParseError({ request: this, cause }) + }) + }) + )) + this.textEffect = Effect.map(this.arrayBufferEffect, (_) => new TextDecoder().decode(_)) + return this.arrayBufferEffect + } + + get upgrade(): Effect.Effect { + return Effect.flatMap( + Effect.try({ + try: () => Deno.upgradeWebSocket(this.source, this.websocketOptions), + catch: (cause) => + new Error.HttpServerError({ + reason: new Error.RequestParseError({ + request: this, + cause, + description: "Not an upgradeable ServerRequest" + }) + }) + }), + (upgrade) => { + const buffered: Array = [] + const buffer = (event: MessageEvent) => buffered.push(event) + upgrade.socket.addEventListener("message", buffer) + this.upgraded = true + this.resolve(upgrade.response) + + return Effect.callback((resume) => { + const cleanup = () => { + upgrade.socket.removeEventListener("open", onOpen) + upgrade.socket.removeEventListener("error", onFailure) + upgrade.socket.removeEventListener("close", onFailure) + } + const onFailure = (cause: Event) => { + cleanup() + upgrade.socket.removeEventListener("message", buffer) + buffered.length = 0 + resume(Effect.fail( + new Error.HttpServerError({ + reason: new Error.RequestParseError({ + request: this, + cause, + description: "WebSocket upgrade failed before open" + }) + }) + )) + } + const onOpen = () => { + cleanup() + resume(Socket.fromWebSocket( + Effect.acquireRelease( + Effect.succeed(upgrade.socket), + (socket) => Effect.sync(() => socket.close(1000)) + ), + { + onInitialRun: (socket) => { + socket.removeEventListener("message", buffer) + return buffered.splice(0) + } + } + )) + } + upgrade.socket.addEventListener("open", onOpen, { once: true }) + upgrade.socket.addEventListener("error", onFailure, { once: true }) + upgrade.socket.addEventListener("close", onFailure, { once: true }) + return Effect.sync(() => { + cleanup() + upgrade.socket.removeEventListener("message", buffer) + buffered.length = 0 + upgrade.socket.close() + }) + }) + } + ) + } +} + +const cancelResponseBody = (body: HttpBody.HttpBody): Effect.Effect => { + const stream = (body as any).body + if ((body._tag === "Raw" || body._tag === "Uint8Array") && stream instanceof ReadableStream) { + return Effect.ignoreCause(Effect.promise(() => stream.cancel())) + } + return Effect.void +} + +const emptyReadableStream = new ReadableStream({ + start(controller) { + controller.enqueue(new Uint8Array()) + controller.close() + } +}) + +const removeHost = (url: string) => { + if (url[0] === "/") return url + const index = url.indexOf("/", url.indexOf("//") + 2) + return index === -1 ? "/" : url.slice(index) +} diff --git a/repos/effect/packages/platform-deno/src/DenoHttpServerRequest.ts b/repos/effect/packages/platform-deno/src/DenoHttpServerRequest.ts new file mode 100644 index 0000000000..d9fc059180 --- /dev/null +++ b/repos/effect/packages/platform-deno/src/DenoHttpServerRequest.ts @@ -0,0 +1,18 @@ +/** + * Accessor for the web-standard request behind a Deno HTTP server request. + * + * Unlike Bun's accessor, this function has no route-pattern generic because + * `Deno.serve` always receives a standard `Request`. Connection information is + * intentionally kept internal to `DenoHttpServer`. + * + * @since 4.0.0 + */ +import type { HttpServerRequest } from "effect/unstable/http/HttpServerRequest" + +/** + * Returns the underlying web-standard `Request` from an Effect `HttpServerRequest`. + * + * @category accessors + * @since 4.0.0 + */ +export const toDenoServerRequest = (self: HttpServerRequest): Request => (self as any).source diff --git a/repos/effect/packages/platform-deno/src/DenoKeyValueStore.ts b/repos/effect/packages/platform-deno/src/DenoKeyValueStore.ts new file mode 100644 index 0000000000..cc221c48c7 --- /dev/null +++ b/repos/effect/packages/platform-deno/src/DenoKeyValueStore.ts @@ -0,0 +1,28 @@ +/** + * Deno-backed `KeyValueStore` layers using Web Storage APIs. + * + * @since 4.0.0 + */ + +import type * as Layer from "effect/Layer" +import * as KeyValueStore from "effect/unstable/persistence/KeyValueStore" + +/** + * Creates a `KeyValueStore` layer backed by `localStorage`, with values stored between sessions. + * + * @category layers + * @since 4.0.0 + */ +export const layerLocalStorage: Layer.Layer = KeyValueStore.layerStorage(() => + localStorage +) + +/** + * Creates a `KeyValueStore` layer backed by `sessionStorage`, with values stored only for the current session. + * + * @category layers + * @since 4.0.0 + */ +export const layerSessionStorage: Layer.Layer = KeyValueStore.layerStorage(() => + sessionStorage +) diff --git a/repos/effect/packages/platform-deno/src/DenoMultipart.ts b/repos/effect/packages/platform-deno/src/DenoMultipart.ts new file mode 100644 index 0000000000..8189c61a17 --- /dev/null +++ b/repos/effect/packages/platform-deno/src/DenoMultipart.ts @@ -0,0 +1,53 @@ +/** + * Web-standard helpers for parsing HTTP `multipart/form-data` request bodies. + * + * This module mirrors `BunMultipart`, adapting a web `Request` body and headers + * into the shared `Multipart` model. `stream` returns multipart parts as a + * `Stream`, while `persisted` collects the form and writes file parts to scoped + * temporary files through the current `FileSystem`, `Path`, and `Scope` + * services. + * + * @since 4.0.0 + */ +import type * as Effect from "effect/Effect" +import type { FileSystem } from "effect/FileSystem" +import type { Path } from "effect/Path" +import type * as Scope from "effect/Scope" +import * as Stream from "effect/Stream" +import * as Multipart from "effect/unstable/http/Multipart" + +/** + * Parses a web `Request` body as multipart data and returns a stream of multipart parts. + * + * @category constructors + * @since 4.0.0 + */ +export const stream = (source: Request): Stream.Stream => + Stream.fromReadableStream({ + evaluate: () => + source.body ?? new ReadableStream({ + start(controller) { + controller.enqueue(new Uint8Array()) + controller.close() + } + }), + onError: (cause) => Multipart.MultipartError.fromReason("InternalError", cause) + }).pipe( + Stream.pipeThroughChannel(Multipart.makeChannel(Object.fromEntries(source.headers))) + ) + +/** + * Parses and persists multipart data from a web `Request`, requiring file-system, path, and scope services. + * + * @category constructors + * @since 4.0.0 + */ +export const persisted = ( + source: Request +): Effect.Effect< + Multipart.Persisted, + Multipart.MultipartError, + | FileSystem + | Path + | Scope.Scope +> => Multipart.toPersisted(stream(source)) diff --git a/repos/effect/packages/platform-deno/src/DenoPath.ts b/repos/effect/packages/platform-deno/src/DenoPath.ts new file mode 100644 index 0000000000..fcf4e4d92d --- /dev/null +++ b/repos/effect/packages/platform-deno/src/DenoPath.ts @@ -0,0 +1,102 @@ +/** + * Path service layers backed by the Deno Standard Library. + * + * **Example** (Using Deno path operations) + * + * ```ts import.meta.vitest + * import { Effect, Path } from "effect" + * import { DenoPath } from "@effect/platform-deno" + * + * const program = Effect.gen(function*() { + * const path = yield* Path.Path + * return path.extname("file.txt") + * }).pipe(Effect.provide(DenoPath.layer)) + * + * Effect.runSync(program) // => ".txt" + * ``` + * + * @since 4.0.0 + */ + +import * as DenoPath from "@std/path" +import * as DenoPathPosix from "@std/path/posix" +import * as DenoPathWin from "@std/path/windows" +import * as Effect from "effect/Effect" +import * as Layer from "effect/Layer" +import * as Path from "effect/Path" +import * as PlatformError from "effect/PlatformError" + +const fileUrlOps = (impl: { + readonly fromFileUrl: typeof DenoPath.fromFileUrl + readonly toFileUrl: typeof DenoPath.toFileUrl +}) => ({ + fromFileUrl: (url: URL): Effect.Effect => + Effect.try({ + try: () => impl.fromFileUrl(url), + catch: (cause) => + new PlatformError.BadArgument({ + module: "Path", + method: "fromFileUrl", + cause + }) + }), + toFileUrl: (path: string): Effect.Effect => + Effect.try({ + try: (): URL => impl.toFileUrl(path), + catch: (cause): PlatformError.BadArgument => + new PlatformError.BadArgument({ + module: "Path", + method: "toFileUrl", + cause + }) + }) +}) + +/** + * A {@linkplain Layer.Layer | layer} that provides POSIX path operations. + * + * @category layers + * @since 4.0.0 + */ +export const layerPosix: Layer.Layer = Layer.succeed(Path.Path)( + Path.Path.of({ + [Path.TypeId]: Path.TypeId, + ...DenoPathPosix, + sep: DenoPathPosix.SEPARATOR, + ...fileUrlOps(DenoPathPosix) + }) +) + +/** + * A {@linkplain Layer.Layer | layer} that provides Windows path operations. + * + * @category layers + * @since 4.0.0 + */ +export const layerWin32: Layer.Layer = Layer.succeed( + Path.Path +)( + Path.Path.of({ + [Path.TypeId]: Path.TypeId, + ...DenoPathWin, + sep: DenoPathWin.SEPARATOR, + ...fileUrlOps(DenoPathWin) + }) +) + +/** + * A {@linkplain Layer.Layer | layer} that provides OS-agnostic path operations. + * + * @category layers + * @since 4.0.0 + */ +export const layer: Layer.Layer = Layer.succeed( + Path.Path +)( + Path.Path.of({ + [Path.TypeId]: Path.TypeId, + ...DenoPath, + sep: DenoPath.SEPARATOR, + ...fileUrlOps(DenoPath) + }) +) diff --git a/repos/effect/packages/platform-deno/src/DenoRedis.ts b/repos/effect/packages/platform-deno/src/DenoRedis.ts new file mode 100644 index 0000000000..5c41ba9f56 --- /dev/null +++ b/repos/effect/packages/platform-deno/src/DenoRedis.ts @@ -0,0 +1,113 @@ +/** + * Deno Redis integration backed by `@db/redis`. + * + * This module creates a scoped, native Deno Redis client and exposes it as + * both the portable `Redis` service and the Deno-specific {@link DenoRedis} + * service for direct access to the raw client. Unlike Bun's built-in client, + * `@db/redis` connects eagerly using RESP2, so layer construction can fail + * with a `RedisError`. + * + * @since 4.0.0 + */ +import { connect, parseURL, type Redis as RedisClient, type RedisConnectOptions } from "@db/redis" +import * as Config from "effect/Config" +import * as Context from "effect/Context" +import * as Effect from "effect/Effect" +import * as Fn from "effect/Function" +import * as Layer from "effect/Layer" +import * as Predicate from "effect/Predicate" +import * as Record from "effect/Record" +import * as Redis from "effect/unstable/persistence/Redis" + +/** + * Options for connecting to Redis, including a Redis URL or individual + * connection settings. Explicit settings override values from the URL. + * + * @category models + * @since 4.0.0 + */ +export type RedisOptions = Omit & { + readonly hostname?: string + readonly url?: string +} + +/** + * Service tag for Deno Redis integration, exposing the raw `@db/redis` client + * and a `use` helper that maps client promise failures to `RedisError`. + * + * @category services + * @since 4.0.0 + */ +export class DenoRedis extends Context.Service(f: (client: RedisClient) => Promise) => Effect.Effect +}>()("@effect/platform-deno/DenoRedis") {} + +const make = Effect.fnUntraced(function*(options: RedisOptions = {}) { + const client = yield* Effect.acquireRelease( + Effect.tryPromise({ + try: () => { + const { url, ...connectOptions } = options + const { name, ...parsed } = url === undefined ? { hostname: "localhost" } : parseURL(url) + return connect({ + ...parsed, + ...(name === undefined ? {} : { username: name }), + ...Record.filter(connectOptions, Predicate.isNotUndefined) + }) + }, + catch: (cause) => new Redis.RedisError({ cause }) + }), + (client) => Effect.sync(() => client.close()) + ) + + const use = (f: (client: RedisClient) => Promise) => + Effect.tryPromise({ + try: () => f(client), + catch: (cause) => new Redis.RedisError({ cause }) + }) + + const redis = yield* Redis.make({ + send: (command: string, ...args: ReadonlyArray) => + Effect.tryPromise({ + try: () => client.sendCommand(command, args as Array) as Promise, + catch: (cause) => new Redis.RedisError({ cause }) + }) + }) + + const denoRedis = Fn.identity({ + client, + use + }) + + return Context.make(DenoRedis, denoRedis).pipe( + Context.add(Redis.Redis, redis) + ) +}) + +/** + * Provides `Redis` and `DenoRedis` services backed by an `@db/redis` client, + * closing the client when the layer scope ends. URL-derived options can be + * overridden by other supplied options. + * + * @category layers + * @since 4.0.0 + */ +export const layer = ( + options?: RedisOptions | undefined +): Layer.Layer => Layer.effectContext(make(options)) + +/** + * Provides `Redis` and `DenoRedis` services from `Config`-backed options, + * closing the client when the layer scope ends. + * + * @category layers + * @since 4.0.0 + */ +export const layerConfig = ( + options: Config.Wrap +): Layer.Layer => + Layer.effectContext( + Config.unwrap(options).pipe( + Effect.flatMap(make) + ) + ) diff --git a/repos/effect/packages/platform-deno/src/DenoRuntime.ts b/repos/effect/packages/platform-deno/src/DenoRuntime.ts new file mode 100644 index 0000000000..2b4355026b --- /dev/null +++ b/repos/effect/packages/platform-deno/src/DenoRuntime.ts @@ -0,0 +1,59 @@ +/** + * Deno helper for running a root Effect program. + * + * @since 4.0.0 + */ + +import type { Effect } from "effect/Effect" +import * as Runtime from "effect/Runtime" + +/** + * Run an Effect as the entrypoint to a Deno application. + * + * @category running + * @since 4.0.0 + */ + +export const runMain: { + ( + options?: { + readonly disableErrorReporting?: boolean | undefined + readonly teardown?: Runtime.Teardown | undefined + } + ): (effect: Effect) => void + ( + effect: Effect, + options?: { + readonly disableErrorReporting?: boolean | undefined + readonly teardown?: Runtime.Teardown | undefined + } + ): void +} = Runtime.makeRunMain(({ + fiber, + teardown +}) => { + let receivedSignal = false + + fiber.addObserver((exit) => { + if (!receivedSignal) { + Deno.removeSignalListener("SIGINT", onSigint) + Deno.removeSignalListener("SIGTERM", onSigint) + } + + teardown(exit, (code) => { + if (receivedSignal || code !== 0) { + Deno.exit(code) + } + }) + }) + + function onSigint(): void { + receivedSignal = true + Deno.removeSignalListener("SIGINT", onSigint) + Deno.removeSignalListener("SIGTERM", onSigint) + fiber.interruptUnsafe(fiber.id) + } + + Deno.addSignalListener("SIGINT", onSigint) + Deno.addSignalListener("SIGTERM", onSigint) +}) diff --git a/repos/effect/packages/platform-deno/src/DenoServices.ts b/repos/effect/packages/platform-deno/src/DenoServices.ts new file mode 100644 index 0000000000..17cd7dbefc --- /dev/null +++ b/repos/effect/packages/platform-deno/src/DenoServices.ts @@ -0,0 +1,49 @@ +/** + * Aggregate Deno platform services layer. + * + * This module defines the `DenoServices` union and a single `layer` that + * provides Deno-backed child process spawning, crypto, filesystem, path, stdio, + * and terminal services. Use the layer when a Deno program wants the standard + * platform services from one place. + * + * @since 4.0.0 + */ +import type { Crypto } from "effect/Crypto" +import type { FileSystem } from "effect/FileSystem" +import * as Layer from "effect/Layer" +import type { Path } from "effect/Path" +import type { Stdio } from "effect/Stdio" +import type { Terminal } from "effect/Terminal" +import type { ChildProcessSpawner } from "effect/unstable/process/ChildProcessSpawner" +import * as DenoChildProcessSpawner from "./DenoChildProcessSpawner.ts" +import * as DenoCrypto from "./DenoCrypto.ts" +import * as DenoFileSystem from "./DenoFileSystem.ts" +import * as DenoPath from "./DenoPath.ts" +import * as DenoStdio from "./DenoStdio.ts" +import * as DenoTerminal from "./DenoTerminal.ts" + +/** + * The union of core services provided by the Deno platform layer, including + * child process spawning, crypto, filesystem, path, stdio, and terminal services. + * + * @category models + * @since 4.0.0 + */ +export type DenoServices = ChildProcessSpawner | Crypto | FileSystem | Path | Terminal | Stdio + +/** + * Provides the default Deno implementations for child process spawning, + * crypto, filesystem, path, stdio, and terminal services. + * + * @category layers + * @since 4.0.0 + */ +export const layer: Layer.Layer = DenoChildProcessSpawner.layer.pipe( + Layer.provideMerge(Layer.mergeAll( + DenoFileSystem.layer, + DenoCrypto.layer, + DenoPath.layer, + DenoStdio.layer, + DenoTerminal.layer + )) +) diff --git a/repos/effect/packages/platform-deno/src/DenoSocket.ts b/repos/effect/packages/platform-deno/src/DenoSocket.ts new file mode 100644 index 0000000000..fa2e94e9ac --- /dev/null +++ b/repos/effect/packages/platform-deno/src/DenoSocket.ts @@ -0,0 +1,318 @@ +/** + * Native Deno socket adapters for Effect sockets. + * + * This module uses Deno-specific names: `makeTcp`, `makeTcpChannel`, + * `layerTcp`, and `fromConn` correspond to the Node platform's net and duplex + * APIs. Deno does not support `keepAliveInitialDelay`, and the `noDelay` and + * `keepAlive` options have no effect on Unix connections. A `CloseEvent` always + * closes gracefully because Deno has no equivalent of Node's reset-on-close. + * + * @since 4.0.0 + */ +import type { Array } from "effect" +import * as Channel from "effect/Channel" +import * as Context from "effect/Context" +import * as Deferred from "effect/Deferred" +import type * as Duration from "effect/Duration" +import * as Effect from "effect/Effect" +import * as FiberSet from "effect/FiberSet" +import * as Function from "effect/Function" +import * as Latch from "effect/Latch" +import * as Layer from "effect/Layer" +import * as Scope from "effect/Scope" +import * as Socket from "effect/unstable/socket/Socket" + +/** + * Options for opening a TCP or Unix connection. + * + * @category options + * @since 4.0.0 + */ +export type ConnectOptions = (Deno.ConnectOptions | Deno.UnixConnectOptions) & { + readonly noDelay?: boolean | undefined + readonly keepAlive?: boolean | undefined +} + +/** + * Options for opening a TCP or Unix connection. + * + * @category options + * @since 4.0.0 + */ +export type TcpOptions = ConnectOptions & { + readonly openTimeout?: Duration.Input | undefined +} + +/** + * Service tag for the underlying Deno connection. + * + * @category services + * @since 4.0.0 + */ +export class Conn extends Context.Service()( + "@effect/platform-deno/DenoSocket/Conn" +) {} + +/** + * Adapts a Deno connection into an Effect socket. + * + * @category constructors + * @since 4.0.0 + */ +export const fromConn = ( + open: Effect.Effect +): Effect.Effect> => + Effect.withFiber>((fiber) => { + let current: { + readonly conn: Deno.Conn + readonly writer: WritableStreamDefaultWriter + } | undefined + let tearingDown = false + let writeClosed = false + const latch = Latch.makeUnsafe(false) + const openServices = fiber.context as Context.Context + + const run = (handler: (_: Uint8Array) => Effect.Effect<_, E, R> | void, options?: { + readonly onOpen?: Effect.Effect | undefined + }) => + Effect.scopedWith(Effect.fnUntraced(function*(scope) { + const fiberSet = yield* FiberSet.make().pipe( + Scope.provide(scope) + ) + let conn: Deno.Conn | undefined + yield* Scope.addFinalizer( + scope, + Effect.suspend(() => { + tearingDown = true + return conn === undefined ? Effect.void : close(conn) + }) + ) + conn = yield* Scope.provide(open, scope) + const reader = conn.readable.getReader() + const writer = conn.writable.getWriter() + const runFork = yield* Effect.provideService(FiberSet.runtime(fiberSet)(), Conn, conn) + + current = { conn, writer } + tearingDown = false + if (writeClosed) { + writeClosed = false + writer.releaseLock() + yield* closeWrite(conn) + } + latch.openUnsafe() + + const read = Effect.tryPromise( + () => reader.read() + ).pipe( + Effect.catchIf( + (error) => tearingDown && isTeardownError(error.cause), + () => Effect.succeed({ done: true, value: undefined } as ReadableStreamReadDoneResult) + ), + Effect.mapError((error) => + new Socket.SocketError({ + reason: new Socket.SocketReadError({ cause: error.cause }) + }) + ) + ) + const readLoop: Effect.Effect = Effect.suspend(() => + Effect.flatMap(read, ({ done, value }) => { + if (done) { + Deferred.doneUnsafe(fiberSet.deferred, Effect.void) + return Effect.void + } + const result = handler(value) + if (Effect.isEffect(result)) { + runFork(result) + } + return readLoop + }) + ) + yield* FiberSet.run(fiberSet, readLoop) + + if (options?.onOpen) { + yield* options.onOpen + } + return yield* FiberSet.join(fiberSet) + })).pipe( + Effect.updateContext((input: Context.Context) => Context.merge(openServices, input)), + Effect.onExit(() => + Effect.sync(() => { + tearingDown = true + latch.closeUnsafe() + current = undefined + }) + ) + ) + + const write = (chunk: Uint8Array | string | Socket.CloseEvent) => + latch.whenOpen(Effect.suspend(() => { + const { conn, writer } = current! + if (Socket.isCloseEvent(chunk)) { + tearingDown = true + return close(conn) + } + const bytes = typeof chunk === "string" ? encoder.encode(chunk) : chunk + return Effect.tryPromise({ + try: () => writer.ready.then(() => writer.write(bytes)), + catch: (cause) => + new Socket.SocketError({ + reason: new Socket.SocketWriteError({ cause }) + }) + }) + })) + + const writer = Effect.acquireRelease( + Effect.sync(() => { + writeClosed = false + return write + }), + () => + Effect.suspend(() => { + if (current === undefined) { + writeClosed = true + return Effect.void + } + const { conn, writer } = current + writer.releaseLock() + return closeWrite(conn) + }) + ) + + return Effect.succeed(Socket.make({ + run, + runRaw: run, + writer + })) + }) + +/** + * Opens a native Deno TCP or Unix connection as an Effect socket. + * + * **Details** + * + * An `openTimeout` interrupts acquisition but cannot cancel the in-flight + * `Deno.connect` promise. The scope finalizer closes a connection that arrives + * after the timeout. + * + * @category constructors + * @since 4.0.0 + */ +export const makeTcp = (options: TcpOptions): Effect.Effect => { + const { keepAlive, noDelay, openTimeout, ...connectOptions } = options + const acquire = Effect.contextWith((context: Context.Context) => { + let conn: Deno.Conn | undefined + let finalized = false + return Scope.addFinalizer( + Context.get(context, Scope.Scope), + Effect.suspend(() => { + finalized = true + return conn === undefined ? Effect.void : close(conn) + }) + ).pipe( + Effect.andThen(Effect.tryPromise({ + try: () => { + const connecting = connectOptions.transport === "unix" + ? Deno.connect(connectOptions) + : Deno.connect(connectOptions) + return connecting.then((connection) => { + conn = connection + if (finalized) connection.close() + return connection + }) + }, + catch: (cause) => + new Socket.SocketError({ + reason: new Socket.SocketOpenError({ kind: "Unknown", cause }) + }) + })), + Effect.tap((connection) => + Effect.sync(() => { + if ("setNoDelay" in connection && noDelay !== undefined) { + connection.setNoDelay(noDelay) + } + if ("setKeepAlive" in connection && keepAlive !== undefined) { + connection.setKeepAlive(keepAlive) + } + }) + ) + ) + }).pipe( + openTimeout === undefined + ? Function.identity + : Effect.timeoutOrElse({ + duration: openTimeout, + orElse: () => + Effect.fail( + new Socket.SocketError({ + reason: new Socket.SocketOpenError({ kind: "Timeout", cause: new Error("Connection timed out") }) + }) + ) + }) + ) + return fromConn(acquire) +} + +/** + * Creates a channel over a native Deno TCP or Unix connection. + * + * @category constructors + * @since 4.0.0 + */ +export const makeTcpChannel = ( + options: ConnectOptions +): Channel.Channel< + Array.NonEmptyReadonlyArray, + Socket.SocketError | IE, + void, + Array.NonEmptyReadonlyArray, + IE +> => Channel.unwrap(Effect.map(makeTcp(options), Socket.toChannelWith())) + +/** + * Provides a socket by opening a native Deno TCP or Unix connection. + * + * @category layers + * @since 4.0.0 + */ +export const layerTcp: (options: ConnectOptions) => Layer.Layer< + Socket.Socket, + Socket.SocketError +> = Function.flow(makeTcp, Layer.effect(Socket.Socket)) + +/** + * Creates a socket layer connected to a URL with Deno's global WebSocket. + * + * @category layers + * @since 4.0.0 + */ +export const layerWebSocket = (url: string, options?: { + readonly closeCodeIsError?: (code: number) => boolean +}): Layer.Layer => + Layer.effect(Socket.Socket, Socket.makeWebSocket(url, options)).pipe( + Layer.provide(layerWebSocketConstructor) + ) + +/** + * Provides the WebSocket constructor backed by `globalThis.WebSocket`. + * + * @category layers + * @since 4.0.0 + */ +export const layerWebSocketConstructor: Layer.Layer = + Socket.layerWebSocketConstructorGlobal + +const encoder = new TextEncoder() + +const isBadResource = (cause: unknown): cause is Deno.errors.BadResource => cause instanceof Deno.errors.BadResource + +const isTeardownError = (cause: unknown): boolean => cause instanceof Deno.errors.Interrupted || isBadResource(cause) + +const close = (conn: Deno.Conn): Effect.Effect => + Effect.try(() => conn.close()).pipe( + Effect.catch(({ cause }) => isBadResource(cause) ? Effect.void : Effect.die(cause)) + ) + +const closeWrite = (conn: Deno.Conn): Effect.Effect => + Effect.tryPromise(() => conn.closeWrite()).pipe( + Effect.catch((error) => isBadResource(error) ? Effect.void : Effect.die(error)) + ) diff --git a/repos/effect/packages/platform-deno/src/DenoSocketServer.ts b/repos/effect/packages/platform-deno/src/DenoSocketServer.ts new file mode 100644 index 0000000000..f944ec0f13 --- /dev/null +++ b/repos/effect/packages/platform-deno/src/DenoSocketServer.ts @@ -0,0 +1,120 @@ +/** + * Native Deno TCP and Unix socket servers for Effect's unstable socket server + * API. + * + * The plain `make` and `layer` names intentionally differ from + * `DenoSocket.makeTcp` and `DenoSocket.layerTcp` because this module has only + * one server kind. Deno has no standalone WebSocket server, so WebSocket + * constructors are omitted, and there is no `IncomingMessage` analogue. + * Connections made before `run` starts remain in the kernel backlog instead of + * being accepted and buffered as they are by the Node implementation. A second + * concurrent `run` waits for the first because listener access is guarded by a + * semaphore. + * + * Deno-native TLS server constructors are included even though the Node socket + * server has no corresponding dedicated constructors. TLS is deliberately not + * tested here because the repository has no certificate fixture. + * + * @since 4.0.0 + */ +import * as Effect from "effect/Effect" +import * as Function from "effect/Function" +import * as Layer from "effect/Layer" +import type * as Scope from "effect/Scope" +import * as SocketServer from "effect/unstable/socket/SocketServer" +import { closeListener, fromListener } from "./internal/denoSocketServer.ts" + +/** + * Native Deno options for listening on a TCP or Unix socket. + * + * @category models + * @since 4.0.0 + */ +export type ListenOptions = + | (Deno.TcpListenOptions & { transport?: "tcp" }) + | (Deno.UnixListenOptions & { transport: "unix" }) + +/** + * Native Deno options and certified key material for listening with TLS. + * + * @category models + * @since 4.0.0 + */ +export type TlsListenOptions = Deno.ListenTlsOptions & Deno.TlsCertifiedKeyPem + +/** + * Creates a scoped socket server using a native Deno TCP or Unix listener. + * + * @category constructors + * @since 4.0.0 + */ +export const make: ( + options: ListenOptions +) => Effect.Effect< + SocketServer.SocketServer["Service"], + SocketServer.SocketServerError, + Scope.Scope +> = Effect.fnUntraced(function*(options) { + const listener = yield* Effect.acquireRelease( + Effect.try({ + try: () => options.transport === "unix" ? Deno.listen(options) : Deno.listen(options), + catch: openError + }), + closeListener + ) + return fromListener(listener) +}) + +/** + * Provides a socket server using a scoped native Deno TCP or Unix listener. + * + * @category layers + * @since 4.0.0 + */ +export const layer: ( + options: ListenOptions +) => Layer.Layer = Function.flow( + make, + Layer.effect(SocketServer.SocketServer) +) + +/** + * Creates a scoped TLS socket server using a native Deno TLS listener. + * + * @category constructors + * @since 4.0.0 + */ +export const makeTls: ( + options: TlsListenOptions +) => Effect.Effect< + SocketServer.SocketServer["Service"], + SocketServer.SocketServerError, + Scope.Scope +> = Effect.fnUntraced(function*(options) { + const listener = yield* Effect.acquireRelease( + Effect.try({ + try: () => Deno.listenTls(options), + catch: openError + }), + closeListener + ) + return fromListener(listener) +}) + +/** + * Provides a TLS socket server using a scoped native Deno TLS listener. + * + * @category layers + * @since 4.0.0 + */ +export const layerTls: ( + options: TlsListenOptions +) => Layer.Layer = Function.flow( + makeTls, + Layer.effect(SocketServer.SocketServer) +) + +const openError = (cause: unknown) => + new SocketServer.SocketServerError({ + reason: new SocketServer.SocketServerOpenError({ cause }) + }) diff --git a/repos/effect/packages/platform-deno/src/DenoStdio.ts b/repos/effect/packages/platform-deno/src/DenoStdio.ts new file mode 100644 index 0000000000..8a79a6050c --- /dev/null +++ b/repos/effect/packages/platform-deno/src/DenoStdio.ts @@ -0,0 +1,53 @@ +/** + * Process stdio for Deno applications. + * + * This module provides Effect's `Stdio` service using Deno's native process + * arguments and Web Streams. Standard input remains open, and standard output + * and error output are not closed unless requested through `endOnDone`. + * + * @since 4.0.0 + */ +import * as Effect from "effect/Effect" +import * as Layer from "effect/Layer" +import * as Sink from "effect/Sink" +import * as Stdio from "effect/Stdio" +import * as Stream from "effect/Stream" +import { handleError } from "./internal/error.ts" + +const encoder = new TextEncoder() + +const output = ( + evaluate: () => WritableStream, + method: "stdout" | "stderr", + options?: { readonly endOnDone?: boolean | undefined } +) => + Sink.fromWritableStream({ + evaluate, + onError: handleError("Stdio", method), + closeOnDone: options?.endOnDone ?? false + }).pipe( + Sink.mapInput((input: string | Uint8Array) => typeof input === "string" ? encoder.encode(input) : input) + ) + +/** + * Provides the `Stdio` service backed by `Deno.args`, `Deno.stdin`, + * `Deno.stdout`, and `Deno.stderr`. + * + * @category layers + * @since 4.0.0 + */ +export const layer: Layer.Layer = Layer.succeed( + Stdio.Stdio, + Stdio.make({ + args: Effect.sync(() => Deno.args), + stdinIsTerminal: Effect.sync(() => Deno.stdin.isTerminal()), + stdoutIsTerminal: Effect.sync(() => Deno.stdout.isTerminal()), + stdout: (options) => output(() => Deno.stdout.writable, "stdout", options), + stderr: (options) => output(() => Deno.stderr.writable, "stderr", options), + stdin: Stream.fromReadableStream({ + evaluate: () => Deno.stdin.readable, + onError: handleError("Stdio", "stdin"), + releaseLockOnEnd: true + }) + }) +) diff --git a/repos/effect/packages/platform-deno/src/DenoTerminal.ts b/repos/effect/packages/platform-deno/src/DenoTerminal.ts new file mode 100644 index 0000000000..cb02cf1196 --- /dev/null +++ b/repos/effect/packages/platform-deno/src/DenoTerminal.ts @@ -0,0 +1,32 @@ +/** + * Deno-backed implementation of Effect's `Terminal` service. + * + * This module reuses the shared Node terminal implementation for Deno. `make` + * creates a scoped process-backed `Terminal` service, and `layer` provides the + * default terminal service with the standard quit behavior for key input. + * + * @since 4.0.0 + */ +import * as NodeTerminal from "@effect/platform-node-shared/NodeTerminal" +import type { Effect } from "effect/Effect" +import type { Layer } from "effect/Layer" +import type { Scope } from "effect/Scope" +import type { Terminal, UserInput } from "effect/Terminal" + +/** + * Creates a scoped `Terminal` service backed by process stdin/stdout, using the + * optional predicate to decide when key input should end the input stream. + * + * @category constructors + * @since 4.0.0 + */ +export const make: (shouldQuit?: (input: UserInput) => boolean) => Effect = NodeTerminal.make + +/** + * Provides the default process-backed `Terminal` service, ending key input on + * the default quit keys. + * + * @category layers + * @since 4.0.0 + */ +export const layer: Layer = NodeTerminal.layer diff --git a/repos/effect/packages/platform-deno/src/DenoWorker.ts b/repos/effect/packages/platform-deno/src/DenoWorker.ts new file mode 100644 index 0000000000..7f4d40c6f4 --- /dev/null +++ b/repos/effect/packages/platform-deno/src/DenoWorker.ts @@ -0,0 +1,75 @@ +/** + * Parent-side Deno platform for Effect workers. + * + * @since 4.0.0 + */ +import * as Deferred from "effect/Deferred" +import * as Effect from "effect/Effect" +import * as Layer from "effect/Layer" +import * as Scope from "effect/Scope" +import * as Worker from "effect/unstable/workers/Worker" +import { WorkerError, WorkerReceiveError } from "effect/unstable/workers/WorkerError" + +/** + * Creates Deno worker layers by combining the default worker platform with a spawner. + * + * @category layers + * @since 4.0.0 + */ +export const layer = ( + spawn: (id: number) => globalThis.Worker | MessagePort +): Layer.Layer => + Layer.merge( + layerPlatform, + Worker.layerSpawner(spawn) + ) + +/** + * Layer that provides the Deno worker platform. + * + * @category layers + * @since 4.0.0 + */ +export const layerPlatform: Layer.Layer = Layer.succeed(Worker.WorkerPlatform)( + Worker.makePlatform()({ + setup({ scope, worker }) { + return Effect.as( + Scope.addFinalizer( + scope, + Effect.sync(() => { + worker.postMessage([1]) + }) + ), + worker + ) + }, + listen({ deferred, emit, port, scope }) { + function onMessage(event: MessageEvent) { + emit(event.data) + } + function onError(event: ErrorEvent) { + Deferred.doneUnsafe( + deferred, + new WorkerError({ + reason: new WorkerReceiveError({ + message: "An error event was emitted", + cause: event.error ?? event.message + }) + }) + ) + } + port.addEventListener("message", onMessage as any) + port.addEventListener("error", onError as any) + if ("start" in port) { + port.start() + } + return Scope.addFinalizer( + scope, + Effect.sync(() => { + port.removeEventListener("message", onMessage as any) + port.removeEventListener("error", onError as any) + }) + ) + } + }) +) diff --git a/repos/effect/packages/platform-deno/src/DenoWorkerRunner.ts b/repos/effect/packages/platform-deno/src/DenoWorkerRunner.ts new file mode 100644 index 0000000000..6e75475411 --- /dev/null +++ b/repos/effect/packages/platform-deno/src/DenoWorkerRunner.ts @@ -0,0 +1,175 @@ +/** + * Runner-side Deno platform for Effect worker handlers using the standard `MessagePort` API. + * + * @since 4.0.0 + */ +import * as Cause from "effect/Cause" +import * as Deferred from "effect/Deferred" +import * as Effect from "effect/Effect" +import * as Exit from "effect/Exit" +import * as Fiber from "effect/Fiber" +import { identity } from "effect/Function" +import * as Layer from "effect/Layer" +import * as Queue from "effect/Queue" +import * as Scope from "effect/Scope" +import { WorkerError, WorkerReceiveError } from "effect/unstable/workers/WorkerError" +import * as WorkerRunner from "effect/unstable/workers/WorkerRunner" + +const cachedPorts = new Set() +function globalHandleConnect(event: MessageEvent) { + cachedPorts.add(event.ports[0]) +} +if (typeof self !== "undefined" && "onconnect" in self) { + self.onconnect = globalHandleConnect +} + +/** + * Creates a worker runner platform over a `MessagePort`. + * + * @category constructors + * @since 4.0.0 + */ +export const make = (self: MessagePort): WorkerRunner.WorkerRunnerPlatform["Service"] => ({ + start: Effect.fnUntraced(function*() { + const disconnects = yield* Queue.make() + let currentPortId = 0 + + const ports = new Map() + const sendUnsafe = (portId: number, message: O, transfer?: ReadonlyArray) => + (ports.get(portId)?.[0] ?? self).postMessage([1, message], { + transfer: transfer as any + }) + const send = (portId: number, message: O, transfer?: ReadonlyArray) => + Effect.sync(() => sendUnsafe(portId, message, transfer)) + + const run = ( + handler: (portId: number, message: I) => Effect.Effect | void + ) => + Effect.scopedWith(Effect.fnUntraced(function*(scope) { + const closeLatch = Deferred.makeUnsafe() + const trackFiber = Fiber.runIn(scope) + const services = yield* Effect.context() + const runFork = Effect.runForkWith(services) + const onExit = (exit: Exit.Exit) => { + if (exit._tag === "Failure" && !Cause.hasInterruptsOnly(exit.cause)) { + runFork(Effect.logError("unhandled error in worker", exit.cause)) + } + } + + function onMessage(portId: number) { + return function(event: MessageEvent) { + const message = event.data as WorkerRunner.PlatformMessage + if (message[0] === 0) { + const result = handler(portId, message[1]) + if (Effect.isEffect(result)) { + const fiber = runFork(result) + fiber.addObserver(onExit) + trackFiber(fiber) + } + } else { + const port = ports.get(portId) + if (!port) { + return + } else if (ports.size === 1) { + // let the last port close with the outer scope + return Deferred.doneUnsafe(closeLatch, Exit.void) + } + ports.delete(portId) + Queue.offerUnsafe(disconnects, portId) + Effect.runFork(Scope.close(port[1], Exit.void)) + } + } + } + function onMessageError(error: MessageEvent) { + Deferred.doneUnsafe( + closeLatch, + new WorkerError({ + reason: new WorkerReceiveError({ + message: "An messageerror event was emitted", + cause: error.data + }) + }) + ) + } + function onError(error: any) { + Deferred.doneUnsafe( + closeLatch, + new WorkerError({ + reason: new WorkerReceiveError({ + message: "An error event was emitted", + cause: error.data + }) + }) + ) + } + function handlePort(port: MessagePort) { + const portScope = Scope.forkUnsafe(scope) + const portId = currentPortId++ + ports.set(portId, [port, portScope]) + const onMsg = onMessage(portId) + port.addEventListener("message", onMsg) + port.addEventListener("messageerror", onMessageError) + if ("start" in port) { + port.start() + } + port.postMessage([0]) + Effect.runSync(Scope.addFinalizer( + portScope, + Effect.sync(() => { + port.removeEventListener("message", onMsg) + port.removeEventListener("messageerror", onMessageError) + port.close() + }) + )) + } + self.addEventListener("error", onError) + let prevOnConnect: unknown | undefined + if ("onconnect" in self) { + prevOnConnect = self.onconnect + self.onconnect = function(event: MessageEvent) { + const port = (event as MessageEvent).ports[0] + handlePort(port) + } + for (const port of cachedPorts) { + handlePort(port) + } + cachedPorts.clear() + } else { + handlePort(self as any) + } + yield* Scope.addFinalizer( + scope, + Effect.sync(() => { + self.removeEventListener("error", onError) + if ("onconnect" in self) { + self.close() + self.onconnect = prevOnConnect + } + }) + ) + + yield* Deferred.await(closeLatch) + })) + + return identity>({ run, send, sendUnsafe, disconnects }) + }) as any +}) + +/** + * Layer that provides the worker runner platform using the global worker scope. + * + * @category layers + * @since 4.0.0 + */ +export const layer: Layer.Layer = Layer.sync(WorkerRunner.WorkerRunnerPlatform)(() => + make(self as unknown as MessagePort) +) + +/** + * Layer that provides the worker runner platform using the supplied `MessagePort`. + * + * @category layers + * @since 4.0.0 + */ +export const layerMessagePort = (port: MessagePort): Layer.Layer => + Layer.succeed(WorkerRunner.WorkerRunnerPlatform)(make(port)) diff --git a/repos/effect/packages/platform-deno/src/index.ts b/repos/effect/packages/platform-deno/src/index.ts new file mode 100644 index 0000000000..1113412762 --- /dev/null +++ b/repos/effect/packages/platform-deno/src/index.ts @@ -0,0 +1,110 @@ +/** + * @since 4.0.0 + */ + +// @barrel: Auto-generated exports. Do not edit manually. + +/** + * @since 4.0.0 + */ +export * as DenoChildProcessSpawner from "./DenoChildProcessSpawner.ts" + +/** + * @since 4.0.0 + */ +export * as DenoClusterHttp from "./DenoClusterHttp.ts" + +/** + * @since 4.0.0 + */ +export * as DenoClusterSocket from "./DenoClusterSocket.ts" + +/** + * @since 4.0.0 + */ +export * as DenoCrypto from "./DenoCrypto.ts" + +/** + * @since 4.0.0 + */ +export * as DenoFileSystem from "./DenoFileSystem.ts" + +/** + * @since 4.0.0 + */ +export * as DenoHttpClient from "./DenoHttpClient.ts" + +/** + * @since 4.0.0 + */ +export * as DenoHttpPlatform from "./DenoHttpPlatform.ts" + +/** + * @since 4.0.0 + */ +export * as DenoHttpServer from "./DenoHttpServer.ts" + +/** + * @since 4.0.0 + */ +export * as DenoHttpServerRequest from "./DenoHttpServerRequest.ts" + +/** + * @since 4.0.0 + */ +export * as DenoKeyValueStore from "./DenoKeyValueStore.ts" + +/** + * @since 4.0.0 + */ +export * as DenoMultipart from "./DenoMultipart.ts" + +/** + * @since 4.0.0 + */ +export * as DenoPath from "./DenoPath.ts" + +/** + * @since 4.0.0 + */ +export * as DenoRedis from "./DenoRedis.ts" + +/** + * @since 4.0.0 + */ +export * as DenoRuntime from "./DenoRuntime.ts" + +/** + * @since 4.0.0 + */ +export * as DenoServices from "./DenoServices.ts" + +/** + * @since 4.0.0 + */ +export * as DenoSocket from "./DenoSocket.ts" + +/** + * @since 4.0.0 + */ +export * as DenoSocketServer from "./DenoSocketServer.ts" + +/** + * @since 4.0.0 + */ +export * as DenoStdio from "./DenoStdio.ts" + +/** + * @since 4.0.0 + */ +export * as DenoTerminal from "./DenoTerminal.ts" + +/** + * @since 4.0.0 + */ +export * as DenoWorker from "./DenoWorker.ts" + +/** + * @since 4.0.0 + */ +export * as DenoWorkerRunner from "./DenoWorkerRunner.ts" diff --git a/repos/effect/packages/platform-deno/src/internal/denoSocketServer.ts b/repos/effect/packages/platform-deno/src/internal/denoSocketServer.ts new file mode 100644 index 0000000000..0d4a13c3be --- /dev/null +++ b/repos/effect/packages/platform-deno/src/internal/denoSocketServer.ts @@ -0,0 +1,89 @@ +import * as Cause from "effect/Cause" +import * as Context from "effect/Context" +import * as Effect from "effect/Effect" +import * as Exit from "effect/Exit" +import * as Fiber from "effect/Fiber" +import { pipe } from "effect/Function" +import * as References from "effect/References" +import * as Scope from "effect/Scope" +import * as Semaphore from "effect/Semaphore" +import type * as Socket from "effect/unstable/socket/Socket" +import * as SocketServer from "effect/unstable/socket/SocketServer" +import * as DenoSocket from "../DenoSocket.ts" + +const initialBackoffMillis = 10 +const maximumBackoffMillis = 1_000 + +type Listener = Deno.Listener + +export const fromListener = (listener: Listener): SocketServer.SocketServer["Service"] => { + const semaphore = Semaphore.makeUnsafe(1) + + const run = (handler: (socket: Socket.Socket) => Effect.Effect<_, E, R>) => + semaphore.withPermit(Effect.gen(function*() { + const scope = yield* Scope.make() + const services = Context.omit(Scope.Scope)(yield* Effect.context()) as Context.Context + const trackFiber = Fiber.runIn(scope) + let failures = 0 + + const loop: Effect.Effect = Effect.suspend(() => + Effect.tryPromise(() => listener.accept()).pipe( + Effect.matchEffect({ + onFailure: (error) => { + const cause = error.cause + if (isTeardownError(cause)) return Effect.never + const backoff = Math.min(initialBackoffMillis * 2 ** failures++, maximumBackoffMillis) + return reportUnhandledError(Cause.fail(cause)).pipe( + Effect.andThen(Effect.sleep(backoff)), + Effect.andThen(loop) + ) + }, + onSuccess: (conn) => { + failures = 0 + pipe( + DenoSocket.fromConn(Effect.acquireRelease(Effect.succeed(conn), closeConn)), + Effect.flatMap(handler), + Effect.ensuring(closeConn(conn)), + Effect.catchCause(reportUnhandledError), + Effect.runForkWith(Context.add(services, DenoSocket.Conn, conn)), + trackFiber + ) + return loop + } + }) + ) + ) + + return yield* loop.pipe(Effect.ensuring(Scope.close(scope, Exit.void))) + })) + + const address = listener.addr + return SocketServer.SocketServer.of({ + address: "path" in address + ? { _tag: "UnixAddress", path: address.path } + : { _tag: "TcpAddress", hostname: address.hostname, port: address.port }, + run + }) +} + +export const closeListener = (listener: Listener): Effect.Effect => + Effect.try(() => listener.close()).pipe( + Effect.catch(({ cause }) => isTeardownError(cause) ? Effect.void : Effect.die(cause)) + ) + +const closeConn = (conn: Deno.Conn): Effect.Effect => + Effect.try(() => conn.close()).pipe( + Effect.catch(({ cause }) => isTeardownError(cause) ? Effect.void : Effect.die(cause)) + ) + +const isTeardownError = (cause: unknown): boolean => + cause instanceof Deno.errors.BadResource || cause instanceof Deno.errors.Interrupted + +const reportUnhandledError = (cause: Cause.Cause) => + Effect.withFiber((fiber) => { + const unhandledLogLevel = fiber.getRef(References.UnhandledLogLevel) + if (unhandledLogLevel) { + return Effect.logWithLevel(unhandledLogLevel)(cause, "Unhandled error in SocketServer") + } + return Effect.void + }) diff --git a/repos/effect/packages/platform-deno/src/internal/error.ts b/repos/effect/packages/platform-deno/src/internal/error.ts new file mode 100644 index 0000000000..11a0f5a0a8 --- /dev/null +++ b/repos/effect/packages/platform-deno/src/internal/error.ts @@ -0,0 +1,66 @@ +import type { SystemError, SystemErrorTag } from "effect/PlatformError" +import * as PlatformError from "effect/PlatformError" + +interface DenoError { + readonly code?: unknown + readonly name?: unknown +} + +export const handleError = ( + module: SystemError["module"], + method: string, + pathOrDescriptor?: string | number +) => +(error: unknown): PlatformError.PlatformError => { + const denoError = error as DenoError + let tag: SystemErrorTag = "Unknown" + + switch (denoError?.name) { + case "AlreadyExists": + tag = "AlreadyExists" + break + case "NotCapable": + tag = "PermissionDenied" + break + case "BadResource": + case "InvalidData": + case "TimedOut": + case "UnexpectedEof": + case "WouldBlock": + case "WriteZero": + tag = denoError.name + break + default: + switch (denoError?.code) { + case "ENOENT": + tag = "NotFound" + break + + case "EACCES": + tag = "PermissionDenied" + break + + case "EEXIST": + tag = "AlreadyExists" + break + + case "EISDIR": + case "ENOTDIR": + case "ELOOP": + tag = "BadResource" + break + + case "EBUSY": + tag = "Busy" + break + } + } + + return PlatformError.systemError({ + _tag: tag, + module, + method, + pathOrDescriptor, + cause: error + }) +} diff --git a/repos/effect/packages/platform-deno/test/DenoChildProcessSpawner.test.ts b/repos/effect/packages/platform-deno/test/DenoChildProcessSpawner.test.ts new file mode 100644 index 0000000000..44fe526081 --- /dev/null +++ b/repos/effect/packages/platform-deno/test/DenoChildProcessSpawner.test.ts @@ -0,0 +1,153 @@ +import * as DenoChildProcessSpawner from "@effect/platform-deno/DenoChildProcessSpawner" +import * as DenoPath from "@effect/platform-deno/DenoPath" +import { assert, describe, it } from "@effect/vitest" +import * as Effect from "effect/Effect" +import * as FileSystem from "effect/FileSystem" +import * as Layer from "effect/Layer" +import * as PlatformError from "effect/PlatformError" +import * as Predicate from "effect/Predicate" +import * as Stream from "effect/Stream" +import * as TestClock from "effect/testing/TestClock" +import { ChildProcess } from "effect/unstable/process" +import * as ChildProcessSpawnerTest from "../../effect/test/unstable/process/ChildProcessSpawnerTest.ts" + +const platformError = (method: string, path: string, cause: unknown) => + PlatformError.systemError({ + _tag: Predicate.hasProperty(cause, "name") && cause.name === "NotFound" ? "NotFound" : "Unknown", + module: "FileSystem", + method, + pathOrDescriptor: path, + cause + }) + +const fileSystem = FileSystem.layerNoop({ + access: (path) => + Effect.tryPromise({ + try: () => Deno.stat(path), + catch: (cause) => platformError("access", path, cause) + }).pipe(Effect.asVoid), + exists: (path) => + Effect.tryPromise({ + try: () => Deno.stat(path).then(() => true), + catch: (cause) => platformError("exists", path, cause) + }).pipe(Effect.catchTag("PlatformError", (error) => + error.reason._tag === "NotFound" ? Effect.succeed(false) : Effect.fail(error))), + makeTempDirectoryScoped: (options) => + Effect.acquireRelease( + Effect.tryPromise({ + try: () => Deno.makeTempDir({ dir: options?.directory, prefix: options?.prefix }), + catch: (cause) => platformError("makeTempDirectoryScoped", options?.directory ?? "", cause) + }), + (path) => Effect.promise(() => Deno.remove(path, { recursive: true })).pipe(Effect.ignore) + ), + writeFile: (path, data) => + Effect.tryPromise({ + try: () => Deno.writeFile(path, data), + catch: (cause) => platformError("writeFile", path, cause) + }) +}) + +const layer = DenoChildProcessSpawner.layer.pipe( + Layer.provideMerge(Layer.mergeAll(DenoPath.layer, fileSystem)) +) + +const output = (command: ChildProcess.Command) => + Effect.gen(function*() { + const handle = yield* command + return yield* handle.stdout.pipe(Stream.decodeText(), Stream.mkString) + }).pipe(Effect.scoped, Effect.provide(layer)) + +ChildProcessSpawnerTest.suite("DenoChildProcessSpawner", layer, { + processGroups: false, + additionalFds: false +}) + +describe("DenoChildProcessSpawner options", () => { + it.effect("rejects detached", () => + Effect.gen(function*() { + const error = yield* ChildProcess.make("echo", ["test"], { detached: false }).pipe(Effect.flip) + assert.deepStrictEqual( + error.reason, + new PlatformError.BadArgument({ + module: "ChildProcessSpawner", + method: "spawn", + description: "The detached option is unsupported because Deno has no equivalent" + }) + ) + }).pipe(Effect.provide(layer))) + + it.effect("rejects additionalFds", () => + Effect.gen(function*() { + const error = yield* ChildProcess.make("echo", ["test"], { + additionalFds: { fd3: { type: "output" } } + }).pipe(Effect.flip) + assert.deepStrictEqual( + error.reason, + new PlatformError.BadArgument({ + module: "ChildProcessSpawner", + method: "spawn", + description: "The additionalFds option is unsupported because Deno has no equivalent" + }) + ) + }).pipe(Effect.provide(layer))) + + it.effect("rejects additional fd pipe sources", () => + Effect.gen(function*() { + const error = yield* ChildProcess.make("echo", ["test"]).pipe( + ChildProcess.pipeTo(ChildProcess.make("cat"), { from: "fd3" }), + Effect.flip + ) + assert.deepStrictEqual( + error.reason, + new PlatformError.BadArgument({ + module: "ChildProcessSpawner", + method: "spawn", + description: "The additionalFds option is unsupported because Deno has no equivalent" + }) + ) + }).pipe(Effect.provide(layer))) + + it.effect("kills every process in a pipeline", () => + Effect.gen(function*() { + const directory = yield* Effect.acquireRelease( + Effect.promise(() => Deno.makeTempDir()), + (path) => Effect.promise(() => Deno.remove(path, { recursive: true })).pipe(Effect.ignore) + ) + const rootHeartbeat = `${directory}/root-heartbeat` + const childHeartbeat = `${directory}/child-heartbeat` + const handle = yield* ChildProcess.make( + "sh", + ["-c", "while :; do printf x >> \"$1\"; sleep 0.01; done", "pipeline-root", rootHeartbeat] + ).pipe( + ChildProcess.pipeTo( + ChildProcess.make( + "sh", + ["-c", "while :; do printf x >> \"$1\"; sleep 0.01; done", "pipeline-child", childHeartbeat] + ) + ) + ) + + yield* TestClock.withLive(Effect.sleep("100 millis")) + yield* handle.kill({ killSignal: "SIGKILL" }) + const rootSizeAfterKill = (yield* Effect.promise(() => Deno.stat(rootHeartbeat))).size + const childSizeAfterKill = (yield* Effect.promise(() => Deno.stat(childHeartbeat))).size + yield* TestClock.withLive(Effect.sleep("100 millis")) + const rootFinalSize = (yield* Effect.promise(() => Deno.stat(rootHeartbeat))).size + const childFinalSize = (yield* Effect.promise(() => Deno.stat(childHeartbeat))).size + + assert.strictEqual(rootFinalSize, rootSizeAfterKill) + assert.strictEqual(childFinalSize, childSizeAfterKill) + }).pipe(Effect.provide(layer))) + + it.effect("emulates shell true without escaping joined arguments", () => + Effect.gen(function*() { + const result = yield* output(ChildProcess.make("printf", ["[%s]", "a b"], { shell: true })) + assert.strictEqual(result, "[a][b]") + })) + + it.effect("uses a custom shell", () => + Effect.gen(function*() { + const result = yield* output(ChildProcess.make("printf", ["custom-shell"], { shell: "/bin/sh" })) + assert.strictEqual(result, "custom-shell") + })) +}) diff --git a/repos/effect/packages/platform-deno/test/DenoCrypto.test.ts b/repos/effect/packages/platform-deno/test/DenoCrypto.test.ts new file mode 100644 index 0000000000..4e8a0c37f0 --- /dev/null +++ b/repos/effect/packages/platform-deno/test/DenoCrypto.test.ts @@ -0,0 +1,37 @@ +import * as DenoCrypto from "@effect/platform-deno/DenoCrypto" +import { assert, describe, it } from "@effect/vitest" +import * as Crypto from "effect/Crypto" +import * as Effect from "effect/Effect" + +const uuidV4Regex = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/ +const uuidV7Regex = /^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/ + +const hex = (bytes: Uint8Array): string => Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("") + +describe("DenoCrypto", () => { + it.effect("computes SHA-256 digests", () => + Effect.gen(function*() { + const crypto = yield* Crypto.Crypto + const digest = yield* crypto.digest("SHA-256", new TextEncoder().encode("hello")) + assert.strictEqual(hex(digest), "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824") + }).pipe(Effect.provide(DenoCrypto.layer))) + + it.effect("generates random bytes larger than the Web Crypto quota", () => + Effect.gen(function*() { + const crypto = yield* Crypto.Crypto + const bytes = yield* crypto.randomBytes(70_000) + assert.strictEqual(bytes.length, 70_000) + }).pipe(Effect.provide(DenoCrypto.layer))) + + it.effect("generates UUIDv4 values", () => + Effect.gen(function*() { + const crypto = yield* Crypto.Crypto + assert.match(yield* crypto.randomUUIDv4, uuidV4Regex) + }).pipe(Effect.provide(DenoCrypto.layer))) + + it.effect("generates UUIDv7 values", () => + Effect.gen(function*() { + const crypto = yield* Crypto.Crypto + assert.match(yield* crypto.randomUUIDv7, uuidV7Regex) + }).pipe(Effect.provide(DenoCrypto.layer))) +}) diff --git a/repos/effect/packages/platform-deno/test/DenoFileSystem.test.ts b/repos/effect/packages/platform-deno/test/DenoFileSystem.test.ts new file mode 100644 index 0000000000..96843f6c24 --- /dev/null +++ b/repos/effect/packages/platform-deno/test/DenoFileSystem.test.ts @@ -0,0 +1,9 @@ +import * as DenoFileSystem from "@effect/platform-deno/DenoFileSystem" +import { describe } from "@effect/vitest" +import { testLayer } from "../../effect/test/FileSystem.test-utils.ts" + +describe("FileSystem", () => + testLayer(DenoFileSystem.layer, { + accessOnDirectory: false, + tempFileScopedRemovesDirectory: false + })) diff --git a/repos/effect/packages/platform-deno/test/DenoHttpCompression.test.ts b/repos/effect/packages/platform-deno/test/DenoHttpCompression.test.ts new file mode 100644 index 0000000000..6092b902f1 --- /dev/null +++ b/repos/effect/packages/platform-deno/test/DenoHttpCompression.test.ts @@ -0,0 +1,158 @@ +import * as DenoHttpPlatform from "@effect/platform-deno/DenoHttpPlatform" +import * as DenoHttpServer from "@effect/platform-deno/DenoHttpServer" +import { assert, describe, it } from "@effect/vitest" +import * as Effect from "effect/Effect" +import * as Layer from "effect/Layer" +import * as Stream from "effect/Stream" +import * as HttpClient from "effect/unstable/http/HttpClient" +import * as HttpEffect from "effect/unstable/http/HttpEffect" +import * as HttpMiddleware from "effect/unstable/http/HttpMiddleware" +import * as HttpPlatform from "effect/unstable/http/HttpPlatform" +import * as HttpRouter from "effect/unstable/http/HttpRouter" +import type { HttpServerRequest } from "effect/unstable/http/HttpServerRequest" +import * as HttpServerResponse from "effect/unstable/http/HttpServerResponse" +import { fileURLToPath } from "node:url" +import * as Zlib from "node:zlib" + +const bigJson = JSON.stringify({ text: "All work and no play makes Jack a dull boy. ".repeat(100) }) +const bigJsonApp = Effect.succeed(HttpServerResponse.text(bigJson, { contentType: "application/json" })) + +const zstdSupported = typeof Zlib.zstdCompress === "function" + +type App = Effect.Effect +type CompressionOptions = Parameters[0] + +const withHandler = async ( + app: App, + options: CompressionOptions, + run: (handler: (request: Request) => Promise) => Promise +) => { + const { dispose, handler } = HttpEffect.toWebHandlerLayer( + app as Effect.Effect, + DenoHttpPlatform.layer, + { middleware: HttpMiddleware.compression(options) } + ) + try { + await run(handler) + } finally { + await dispose() + } +} + +const get = ( + handler: (request: Request) => Promise, + headers?: Record +) => handler(new Request("http://localhost/", headers === undefined ? {} : { headers })) + +describe("DenoHttpCompression", () => { + it.effect("advertises supported algorithms", () => + Effect.gen(function*() { + const platform = yield* HttpPlatform.HttpPlatform + assert.isTrue(platform.compression.algorithms.has("gzip")) + assert.isTrue(platform.compression.algorithms.has("deflate")) + assert.isTrue(platform.compression.algorithms.has("br")) + assert.strictEqual(platform.compression.algorithms.has("zstd"), zstdSupported) + }).pipe(Effect.provide(DenoHttpPlatform.layer))) + + it("compresses one-shot bodies asynchronously with gzip and an exact Content-Length", () => + withHandler(bigJsonApp, undefined, async (handler) => { + const response = await get(handler, { "accept-encoding": "gzip" }) + assert.strictEqual(response.headers.get("content-encoding"), "gzip") + assert.strictEqual(response.headers.get("vary"), "Accept-Encoding") + const compressed = new Uint8Array(await response.arrayBuffer()) + assert.strictEqual(response.headers.get("content-length"), compressed.byteLength.toString()) + assert.strictEqual(Zlib.gunzipSync(compressed).toString(), bigJson) + })) + + it("prefers br over gzip in server order", () => + withHandler(bigJsonApp, undefined, async (handler) => { + const response = await get(handler, { "accept-encoding": "gzip, br" }) + assert.strictEqual(response.headers.get("content-encoding"), "br") + const compressed = new Uint8Array(await response.arrayBuffer()) + assert.strictEqual(response.headers.get("content-length"), compressed.byteLength.toString()) + assert.strictEqual(Zlib.brotliDecompressSync(compressed).toString(), bigJson) + })) + + it.skipIf(!zstdSupported)( + "compresses with zstd when opted in", + () => + withHandler(bigJsonApp, { algorithms: ["zstd", "gzip"] }, async (handler) => { + const response = await get(handler, { "accept-encoding": "zstd" }) + assert.strictEqual(response.headers.get("content-encoding"), "zstd") + const compressed = new Uint8Array(await response.arrayBuffer()) + assert.strictEqual(response.headers.get("content-length"), compressed.byteLength.toString()) + assert.strictEqual(Zlib.zstdDecompressSync(compressed).toString(), bigJson) + }) + ) + + it("compresses stream bodies with gzip via CompressionStream", () => + withHandler( + Effect.succeed(HttpServerResponse.stream( + Stream.fromArray([new TextEncoder().encode(bigJson)]), + { contentType: "application/json" } + )), + undefined, + async (handler) => { + const response = await get(handler, { "accept-encoding": "gzip" }) + assert.strictEqual(response.headers.get("content-encoding"), "gzip") + assert.strictEqual(response.headers.get("content-length"), null) + assert.strictEqual(Zlib.gunzipSync(new Uint8Array(await response.arrayBuffer())).toString(), bigJson) + } + )) + + it("compresses stream bodies with br via node:zlib streams", () => + withHandler( + Effect.succeed(HttpServerResponse.stream( + Stream.fromArray([new TextEncoder().encode(bigJson)]), + { contentType: "application/json" } + )), + undefined, + async (handler) => { + const response = await get(handler, { "accept-encoding": "br" }) + assert.strictEqual(response.headers.get("content-encoding"), "br") + assert.strictEqual(response.headers.get("content-length"), null) + assert.strictEqual(Zlib.brotliDecompressSync(new Uint8Array(await response.arrayBuffer())).toString(), bigJson) + } + )) + + it("compresses file responses through the streaming path and weakens the ETag", async () => { + const path = fileURLToPath(new URL("./DenoHttpCompression.test.ts", import.meta.url)) + const contents = await Deno.readTextFile(path) + await withHandler( + HttpServerResponse.file(path, { headers: { "content-type": "text/plain" } }), + undefined, + async (handler) => { + const response = await get(handler, { "accept-encoding": "gzip" }) + assert.strictEqual(response.headers.get("content-encoding"), "gzip") + assert.strictEqual(response.headers.get("content-length"), null) + assert.isTrue(response.headers.get("etag")!.startsWith("W/")) + assert.strictEqual(Zlib.gunzipSync(new Uint8Array(await response.arrayBuffer())).toString(), contents) + } + ) + }) + + it.effect("compresses end-to-end through the Deno HTTP server", () => + Effect.gen(function*() { + yield* HttpRouter.add("GET", "/json", bigJsonApp).pipe( + (self) => + HttpRouter.serve(self, { + middleware: HttpMiddleware.compression({ algorithms: ["zstd", "br", "gzip", "deflate"] }) + }), + Layer.build + ) + const client = yield* HttpClient.HttpClient + // Deno's fetch transparently decompresses gzip and strips the + // Content-Encoding header, so assert on the round-tripped body + const gzip = yield* client.get("/json", { headers: { "accept-encoding": "gzip" } }) + assert.strictEqual(gzip.headers["vary"], "Accept-Encoding") + assert.strictEqual(yield* gzip.text, bigJson) + if (zstdSupported) { + // Deno's fetch does not decode zstd, so the raw compressed payload and + // its headers are observable + const zstd = yield* client.get("/json", { headers: { "accept-encoding": "zstd" } }) + assert.strictEqual(zstd.headers["content-encoding"], "zstd") + const compressed = new Uint8Array(yield* zstd.arrayBuffer) + assert.strictEqual(Zlib.zstdDecompressSync(compressed).toString(), bigJson) + } + }).pipe(Effect.provide(DenoHttpServer.layerTest))) +}) diff --git a/repos/effect/packages/platform-deno/test/DenoHttpPlatform.test.ts b/repos/effect/packages/platform-deno/test/DenoHttpPlatform.test.ts new file mode 100644 index 0000000000..76a421f1f5 --- /dev/null +++ b/repos/effect/packages/platform-deno/test/DenoHttpPlatform.test.ts @@ -0,0 +1,68 @@ +import * as DenoHttpPlatform from "@effect/platform-deno/DenoHttpPlatform" +import { assert, describe, it } from "@effect/vitest" +import * as Effect from "effect/Effect" +import type * as HttpBody from "effect/unstable/http/HttpBody" +import * as HttpPlatform from "effect/unstable/http/HttpPlatform" + +const fixture = `${import.meta.dirname}/fixtures/text.txt` + +const readStream = (stream: ReadableStream) => Effect.promise(() => new globalThis.Response(stream).text()) + +const readBody = (body: HttpBody.HttpBody) => { + assert.strictEqual(body._tag, "Raw") + return Effect.promise(() => new Response((body as HttpBody.Raw).body as BodyInit).text()) +} + +describe("DenoHttpPlatform", () => { + it.effect("fileWebResponse honors offset and bytesToRead including zero", () => + Effect.gen(function*() { + const platform = yield* HttpPlatform.HttpPlatform + const file = new File(["abcd"], "file.txt", { type: "text/plain", lastModified: 0 }) + const sliced = yield* platform.fileWebResponse(file, { offset: 1, bytesToRead: 2 }) + const empty = yield* platform.fileWebResponse(file, { offset: 1, bytesToRead: 0 }) + + assert.deepStrictEqual( + { + slicedLength: sliced.headers["content-length"], + slicedBody: yield* readBody(sliced.body), + emptyLength: empty.headers["content-length"], + emptyBody: yield* readBody(empty.body) + }, + { slicedLength: "2", slicedBody: "bc", emptyLength: "0", emptyBody: "" } + ) + }).pipe(Effect.provide(DenoHttpPlatform.layer))) + + it.effect("fileResponse reads exact bytesToRead", () => + Effect.gen(function*() { + const platform = yield* HttpPlatform.HttpPlatform + const response = yield* platform.fileResponse(fixture, { + offset: 6, + bytesToRead: 5 + }) + + assert.strictEqual(response.headers["content-length"], "5") + assert.strictEqual(response.body._tag, "Raw") + const body = (response.body as HttpBody.Raw).body + assert(body instanceof ReadableStream) + + const text = yield* readStream(body) + assert.strictEqual(text, "ipsum") + }).pipe(Effect.provide(DenoHttpPlatform.layer))) + + it.effect("fileResponse supports zero bytesToRead", () => + Effect.gen(function*() { + const platform = yield* HttpPlatform.HttpPlatform + const response = yield* platform.fileResponse(fixture, { + offset: 6, + bytesToRead: 0 + }) + + assert.strictEqual(response.headers["content-length"], "0") + assert.strictEqual(response.body._tag, "Raw") + const body = (response.body as HttpBody.Raw).body + assert(body instanceof ReadableStream) + + const text = yield* readStream(body) + assert.strictEqual(text, "") + }).pipe(Effect.provide(DenoHttpPlatform.layer))) +}) diff --git a/repos/effect/packages/platform-deno/test/DenoHttpServer.test.ts b/repos/effect/packages/platform-deno/test/DenoHttpServer.test.ts new file mode 100644 index 0000000000..6af9c53b57 --- /dev/null +++ b/repos/effect/packages/platform-deno/test/DenoHttpServer.test.ts @@ -0,0 +1,735 @@ +import * as DenoHttpServer from "@effect/platform-deno/DenoHttpServer" +import { assert, describe, it } from "@effect/vitest" +import * as Duration from "effect/Duration" +import * as Effect from "effect/Effect" +import * as Fiber from "effect/Fiber" +import * as Latch from "effect/Latch" +import * as Layer from "effect/Layer" +import * as ManagedRuntime from "effect/ManagedRuntime" +import * as Queue from "effect/Queue" +import * as Schema from "effect/Schema" +import type * as Scope from "effect/Scope" +import * as Stream from "effect/Stream" +import * as Tracer from "effect/Tracer" +import * as Cookies from "effect/unstable/http/Cookies" +import * as Etag from "effect/unstable/http/Etag" +import * as FetchHttpClient from "effect/unstable/http/FetchHttpClient" +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 HttpPlatform from "effect/unstable/http/HttpPlatform" +import * as HttpRouter from "effect/unstable/http/HttpRouter" +import * as HttpServer from "effect/unstable/http/HttpServer" +import * as HttpServerRequest from "effect/unstable/http/HttpServerRequest" +import * as HttpServerRespondable from "effect/unstable/http/HttpServerRespondable" +import * as HttpServerResponse from "effect/unstable/http/HttpServerResponse" +import * as Multipart from "effect/unstable/http/Multipart" +import * as UrlParams from "effect/unstable/http/UrlParams" +import * as HttpApiError from "effect/unstable/httpapi/HttpApiError" +import type * as Socket from "effect/unstable/socket/Socket" + +const Todo = Schema.Struct({ + id: Schema.Number, + title: Schema.String +}) +const IdParams = Schema.Struct({ + id: Schema.FiniteFromString +}) +const todoResponse = HttpServerResponse.schemaJson(Todo) +const fixture = `${import.meta.dirname}/fixtures/text.txt` + +describe("DenoHttpServer", () => { + it.effect("schema", () => + Effect.gen(function*() { + yield* HttpRouter.add( + "GET", + "/todos/:id", + Effect.flatMap(HttpRouter.schemaParams(IdParams), ({ id }) => todoResponse({ id, title: "test" })) + ).pipe(HttpRouter.serve, Layer.build) + const todo = yield* HttpClient.get("/todos/1").pipe( + Effect.flatMap(HttpClientResponse.schemaBodyJson(Todo)) + ) + assert.deepStrictEqual(todo, { id: 1, title: "test" }) + }).pipe(Effect.provide(DenoHttpServer.layerTest))) + + it.effect("exports a weak ETag generator", () => + Effect.gen(function*() { + const generator = yield* Etag.Generator + const etag = yield* generator.fromFileWeb(new File(["test"], "test.txt", { lastModified: 0 })) + assert.strictEqual(etag._tag, "Weak") + }).pipe(Effect.provide(DenoHttpServer.layerHttpServices))) + + it.effect("formData", () => + Effect.gen(function*() { + yield* HttpRouter.add( + "POST", + "/upload", + Effect.gen(function*() { + const request = yield* HttpServerRequest.HttpServerRequest + const formData = yield* request.multipart + const part = formData.file + assert(typeof part !== "string") + const file = part[0] + assert(typeof file !== "string") + assert(file.path.endsWith("/test.txt")) + assert.strictEqual(file.contentType, "text/plain") + assert.strictEqual(yield* Effect.promise(() => Deno.readTextFile(file.path)), "test") + return yield* HttpServerResponse.json({ ok: "file" in formData }) + }) + ).pipe(HttpRouter.serve, Layer.build) + const formData = new FormData() + formData.append("file", new Blob(["test"], { type: "text/plain" }), "test.txt") + const response = yield* HttpClient.post("/upload", { body: HttpBody.formData(formData) }) + assert.deepStrictEqual(yield* response.json, { ok: true }) + }).pipe(Effect.provide(DenoHttpServer.layerTest))) + + it.effect("multipartStream", () => + Effect.gen(function*() { + yield* HttpRouter.add( + "POST", + "/upload", + Effect.gen(function*() { + const request = yield* HttpServerRequest.HttpServerRequest + const parts = yield* Stream.runCollect(request.multipartStream) + assert.strictEqual(parts.length, 2) + const field = parts[0] + const file = parts[1] + assert(Multipart.isField(field)) + assert.deepStrictEqual({ key: field.key, value: field.value }, { key: "name", value: "value" }) + assert(Multipart.isFile(file)) + assert.deepStrictEqual( + { key: file.key, name: file.name, contentType: file.contentType }, + { key: "file", name: "test.txt", contentType: "text/plain" } + ) + assert.strictEqual(new TextDecoder().decode(yield* file.contentEffect), "test") + return HttpServerResponse.empty() + }) + ).pipe(HttpRouter.serve, Layer.build) + const formData = new FormData() + formData.append("name", "value") + formData.append("file", new Blob(["test"], { type: "text/plain" }), "test.txt") + const response = yield* HttpClient.post("/upload", { body: HttpBody.formData(formData) }) + assert.strictEqual(response.status, 204) + }).pipe(Effect.provide(DenoHttpServer.layerTest))) + + it.effect("schemaBodyForm", () => + Effect.gen(function*() { + yield* HttpRouter.add( + "POST", + "/upload", + Effect.gen(function*() { + const files = yield* HttpServerRequest.schemaBodyForm(Schema.Struct({ + file: Multipart.FilesSchema, + test: Schema.String + })) + assert("file" in files) + assert.strictEqual(files.test, "test") + return HttpServerResponse.empty() + }) + ).pipe(HttpRouter.serve, Layer.build) + const formData = new FormData() + formData.append("file", new Blob(["test"], { type: "text/plain" }), "test.txt") + formData.append("test", "test") + const response = yield* HttpClient.post("/upload", { body: HttpBody.formData(formData) }) + assert.strictEqual(response.status, 204) + }).pipe(Effect.provide(DenoHttpServer.layerTest))) + + it.effect("formData withMaxFileSize", () => + Effect.gen(function*() { + yield* HttpRouter.add( + "POST", + "/upload", + Effect.gen(function*() { + const request = yield* HttpServerRequest.HttpServerRequest + yield* request.multipart + return HttpServerResponse.empty() + }).pipe(Effect.catchTag("MultipartError", (error) => + error.reason._tag === "FileTooLarge" + ? Effect.succeed(HttpServerResponse.empty({ status: 413 })) + : Effect.fail(error))) + ).pipe( + HttpRouter.serve, + Layer.build, + Effect.provideService(Multipart.MaxFileSize, 100) + ) + const formData = new FormData() + formData.append("file", new Blob([new Uint8Array(1000)], { type: "text/plain" }), "test.txt") + const response = yield* HttpClient.post("/upload", { body: HttpBody.formData(formData) }) + assert.strictEqual(response.status, 413) + }).pipe(Effect.provide(DenoHttpServer.layerTest))) + + it.effect("formData withMaxFieldSize", () => + Effect.gen(function*() { + yield* HttpRouter.add( + "POST", + "/upload", + Effect.gen(function*() { + const request = yield* HttpServerRequest.HttpServerRequest + yield* request.multipart + return HttpServerResponse.empty() + }).pipe(Effect.catchTag("MultipartError", (error) => + error.reason._tag === "FieldTooLarge" + ? Effect.succeed(HttpServerResponse.empty({ status: 413 })) + : Effect.fail(error))) + ).pipe( + HttpRouter.serve, + Layer.build, + Effect.provideService(Multipart.MaxFieldSize, 100) + ) + const formData = new FormData() + formData.append("file", "x".repeat(1000)) + const response = yield* HttpClient.post("/upload", { body: HttpBody.formData(formData) }) + assert.strictEqual(response.status, 413) + }).pipe(Effect.provide(DenoHttpServer.layerTest))) + + it.effect("mountApp", () => + Effect.gen(function*() { + const child = Effect.map(HttpServerRequest.HttpServerRequest, (_) => HttpServerResponse.text(_.url)) + yield* HttpRouter.use((router) => router.prefixed("/child").add("*", "*", child)).pipe( + HttpRouter.serve, + Layer.build + ) + assert.strictEqual(yield* HttpClient.get("/child/1").pipe(Effect.flatMap((_) => _.text)), "/1") + assert.strictEqual(yield* HttpClient.get("/child").pipe(Effect.flatMap((_) => _.text)), "/") + assert.strictEqual(yield* HttpClient.get("/child?foo=bar").pipe(Effect.flatMap((_) => _.text)), "?foo=bar") + assert.strictEqual(yield* HttpClient.get("/child/").pipe(Effect.flatMap((_) => _.text)), "/") + assert.strictEqual( + yield* HttpClient.get("/child1/", { urlParams: { foo: "bar" } }).pipe(Effect.map((_) => _.status)), + 404 + ) + }).pipe(Effect.provide(DenoHttpServer.layerTest))) + + it.effect("file", () => + Effect.gen(function*() { + yield* (yield* HttpServerResponse.file(fixture).pipe( + Effect.updateService(HttpPlatform.HttpPlatform, (_) => ({ + ..._, + fileResponse: (path, options) => + Effect.map(_.fileResponse(path, options), (response) => { + ;(response as any).headers.etag = "\"etag\"" + return response + }) + })) + )).pipe(Effect.succeed, HttpServer.serveEffect()) + const response = yield* HttpClient.get("/", { + headers: { "accept-encoding": "identity" } + }) + assert.strictEqual(response.status, 200) + assert.strictEqual(response.headers["content-type"], "text/plain; charset=UTF-8") + assert.strictEqual(response.headers.etag, "\"etag\"") + assert.strictEqual((yield* response.text).trim(), "lorem ipsum dolar sit amet") + }).pipe(Effect.provide(DenoHttpServer.layerTest))) + + it.effect("fileWeb", () => + Effect.gen(function*() { + const now = new Date() + const file = new File([new TextEncoder().encode("test")], "test.txt", { + type: "text/plain", + lastModified: now.getTime() + }) + yield* HttpServerResponse.fileWeb(file).pipe( + Effect.updateService(HttpPlatform.HttpPlatform, (_) => ({ + ..._, + fileWebResponse: (file, options) => + Effect.map(_.fileWebResponse(file, options), (response) => ({ + ...response, + headers: { ...response.headers, etag: "W/\"etag\"" } + })) + })), + HttpServer.serveEffect() + ) + const response = yield* HttpClient.get("/") + assert.strictEqual(response.status, 200) + assert.strictEqual(response.headers["content-type"], "text/plain") + assert.strictEqual(response.headers["last-modified"], now.toUTCString()) + assert.strictEqual(response.headers.etag, "W/\"etag\"") + assert.strictEqual(yield* response.text, "test") + }).pipe(Effect.provide(DenoHttpServer.layerTest))) + + it.effect("schemaBodyUrlParams", () => + Effect.gen(function*() { + yield* HttpRouter.add( + "POST", + "/todos", + Effect.flatMap( + HttpServerRequest.schemaBodyUrlParams(Schema.Struct({ + id: Schema.FiniteFromString, + title: Schema.String + })), + ({ id, title }) => todoResponse({ id, title }) + ) + ).pipe(HttpRouter.serve, Layer.build) + const todo = yield* HttpClientRequest.post("/todos").pipe( + HttpClientRequest.bodyUrlParams({ id: "1", title: "test" }), + HttpClient.execute, + Effect.flatMap(HttpClientResponse.schemaBodyJson(Todo)) + ) + assert.deepStrictEqual(todo, { id: 1, title: "test" }) + }).pipe(Effect.provide(DenoHttpServer.layerTest))) + + it.effect("schemaBodyUrlParams error", () => + Effect.gen(function*() { + yield* HttpRouter.add( + "GET", + "/todos", + Effect.flatMap( + HttpServerRequest.schemaBodyUrlParams(Schema.Struct({ + id: Schema.FiniteFromString, + title: Schema.String + })), + ({ id, title }) => todoResponse({ id, title }) + ).pipe(Effect.catchTag("SchemaError", (error) => + Effect.succeed(HttpServerResponse.jsonUnsafe({ error }, { status: 400 })))) + ).pipe(HttpRouter.serve, Layer.build) + assert.strictEqual((yield* HttpClient.get("/todos")).status, 400) + }).pipe(Effect.provide(DenoHttpServer.layerTest))) + + it.effect("schemaBodyFormJson", () => + Effect.gen(function*() { + yield* HttpRouter.add( + "POST", + "/upload", + Effect.gen(function*() { + const result = yield* HttpServerRequest.schemaBodyFormJson(Schema.Struct({ test: Schema.String }))("json") + assert.strictEqual(result.test, "content") + return HttpServerResponse.empty() + }) + ).pipe(HttpRouter.serve, Layer.build) + const formData = new FormData() + formData.append("json", JSON.stringify({ test: "content" })) + assert.strictEqual((yield* HttpClient.post("/upload", { body: HttpBody.formData(formData) })).status, 204) + }).pipe(Effect.provide(DenoHttpServer.layerTest))) + + it.effect("schemaBodyFormJson file", () => + Effect.gen(function*() { + yield* HttpRouter.add( + "POST", + "/upload", + Effect.gen(function*() { + const result = yield* HttpServerRequest.schemaBodyFormJson(Schema.Struct({ test: Schema.String }))("json") + assert.strictEqual(result.test, "content") + return HttpServerResponse.empty() + }) + ).pipe(HttpRouter.serve, Layer.build) + const formData = new FormData() + formData.append( + "json", + new Blob([JSON.stringify({ test: "content" })], { type: "application/json" }), + "test.json" + ) + assert.strictEqual((yield* HttpClient.post("/upload", { body: HttpBody.formData(formData) })).status, 204) + }).pipe(Effect.provide(DenoHttpServer.layerTest))) + + it.effect("schemaBodyFormJson url encoded", () => + Effect.gen(function*() { + yield* HttpRouter.add( + "POST", + "/upload", + Effect.gen(function*() { + const result = yield* HttpServerRequest.schemaBodyFormJson(Schema.Struct({ test: Schema.String }))("json") + assert.strictEqual(result.test, "content") + return HttpServerResponse.empty() + }) + ).pipe(HttpRouter.serve, Layer.build) + const response = yield* HttpClient.post("/upload", { + body: HttpBody.urlParams(UrlParams.fromInput({ json: JSON.stringify({ test: "content" }) })) + }) + assert.strictEqual(response.status, 204) + }).pipe(Effect.provide(DenoHttpServer.layerTest))) + + it.effect("tracing", () => + Effect.gen(function*() { + yield* HttpRouter.add( + "GET", + "/", + Effect.flatMap(Effect.currentSpan, (_) => HttpServerResponse.json({ spanId: _.spanId, parent: _.parent })) + ).pipe(HttpRouter.serve, Layer.build) + const requestSpan = yield* Effect.makeSpan("client request") + const body = yield* HttpClient.get("/").pipe( + Effect.flatMap((response) => response.json), + Effect.provideService( + Tracer.Tracer, + Tracer.make({ + span(options) { + assert.strictEqual(options.name, "http.client GET") + assert.strictEqual(options.kind, "client") + assert(options.parent._tag === "Some") + if (options.parent.value._tag !== "Span") throw new Error("Expected span parent") + assert.strictEqual(options.parent.value.name, "request parent") + return requestSpan + } + }) + ), + Effect.withSpan("request parent"), + Effect.repeat({ times: 2 }) + ) + assert.strictEqual((body as any).parent._tag, "Some") + assert.strictEqual((body as any).parent.value.spanId, requestSpan.spanId) + }).pipe(Effect.provide(DenoHttpServer.layerTest))) + + it.effect("html", () => + Effect.gen(function*() { + yield* HttpRouter.addAll([ + HttpRouter.route("GET", "/home", HttpServerResponse.html("")), + HttpRouter.route( + "GET", + "/about", + HttpServerResponse.html`${Effect.succeed("")}` + ), + HttpRouter.route( + "GET", + "/stream", + HttpServerResponse.htmlStream`${Stream.make("", 123, "hello")}` + ) + ]).pipe(HttpRouter.serve, Layer.build) + assert.strictEqual(yield* HttpClient.get("/home").pipe(Effect.flatMap((_) => _.text)), "") + assert.strictEqual(yield* HttpClient.get("/about").pipe(Effect.flatMap((_) => _.text)), "") + assert.strictEqual( + yield* HttpClient.get("/stream").pipe(Effect.flatMap((_) => _.text)), + "123hello" + ) + }).pipe(Effect.provide(DenoHttpServer.layerTest))) + + it.effect("setCookie", () => + Effect.gen(function*() { + yield* HttpRouter.add( + "GET", + "/home", + HttpServerResponse.empty().pipe( + HttpServerResponse.setCookieUnsafe("test", "value"), + HttpServerResponse.setCookieUnsafe("test2", "value2", { + httpOnly: true, + secure: true, + sameSite: "lax", + partitioned: true, + path: "/", + domain: "example.com", + expires: new Date(2022, 1, 1), + maxAge: "5 minutes" + }) + ) + ).pipe(HttpRouter.serve, Layer.build) + const response = yield* HttpClient.get("/home") + assert.deepStrictEqual( + response.cookies.toJSON(), + Cookies.fromReadonlyRecord({ + test: Cookies.makeCookieUnsafe("test", "value"), + test2: Cookies.makeCookieUnsafe("test2", "value2", { + httpOnly: true, + secure: true, + sameSite: "lax", + partitioned: true, + path: "/", + domain: "example.com", + expires: new Date(2022, 1, 1), + maxAge: Duration.minutes(5) + }) + }).toJSON() + ) + }).pipe(Effect.provide(DenoHttpServer.layerTest))) + + it.live("uninterruptible routes", () => + Effect.gen(function*() { + yield* HttpRouter.add( + "GET", + "/home", + Effect.gen(function*() { + const fiber = Fiber.getCurrent()! + setTimeout(() => fiber.interruptUnsafe(fiber.id), 10) + yield* Effect.sleep(50) + return HttpServerResponse.empty() + }), + { uninterruptible: true } + ).pipe(HttpRouter.serve, Layer.build) + assert.strictEqual((yield* HttpClient.get("/home")).status, 204) + }).pipe(Effect.provide(DenoHttpServer.layerTest))) + + it.live("disposes after a client aborts a handler awaiting an upstream request", () => + Effect.gen(function*() { + const upstreamStarted = Latch.makeUnsafe() + const upstream = yield* Effect.acquireRelease( + Effect.sync(() => { + const controller = new AbortController() + const server = Deno.serve( + { hostname: "127.0.0.1", port: 0, onListen: () => {}, signal: controller.signal }, + (request) => { + upstreamStarted.openUnsafe() + return new Promise((resolve) => { + request.signal.addEventListener("abort", () => resolve(new Response()), { once: true }) + controller.signal.addEventListener("abort", () => resolve(new Response()), { once: true }) + }) + } + ) + return { controller, server } + }), + ({ controller, server }) => + Effect.sync(() => controller.abort()).pipe(Effect.andThen(Effect.promise(() => server.finished))) + ) + const upstreamPort = (upstream.server.addr as Deno.NetAddr).port + const router = HttpRouter.use((router) => + router.add( + "GET", + "/", + Effect.gen(function*() { + yield* HttpClient.head(`http://127.0.0.1:${upstreamPort}`) + return HttpServerResponse.empty() + }) + ) + ) + const serverLayer = DenoHttpServer.layer({ + hostname: "127.0.0.1", + port: 0, + onListen: () => {}, + gracefulShutdownTimeout: "100 millis" + }) + const services = Layer.merge(serverLayer, FetchHttpClient.layer) + const runtime = yield* Effect.acquireRelease( + Effect.sync(() => + ManagedRuntime.make(Layer.merge( + services, + HttpRouter.serve(router).pipe(Layer.provide(services)) + )) + ), + (runtime) => Effect.promise(() => runtime.dispose()) + ) + yield* Effect.promise(() => runtime.context()) + const downstreamServer = yield* Effect.promise(() => runtime.runPromise(HttpServer.HttpServer)) + const downstreamPort = (downstreamServer.address as HttpServer.TcpAddress).port + + const controller = new AbortController() + const downstream = fetch(`http://127.0.0.1:${downstreamPort}`, { + signal: controller.signal + }).catch(() => undefined) + yield* upstreamStarted.await + controller.abort() + yield* Effect.promise(() => downstream) + + const disposed = yield* Effect.promise(() => runtime.dispose()).pipe(Effect.timeoutOption("2 seconds")) + assert.strictEqual(disposed._tag, "Some") + })) + + describe("HttpServerRespondable", () => { + it.effect("error/schema", () => + Effect.gen(function*() { + class CustomError extends Schema.Error("CustomError")({ + _tag: Schema.tag("CustomError"), + name: Schema.String + }) { + [HttpServerRespondable.symbol]() { + return HttpServerResponse.schemaJson(CustomError)(this, { status: 599 }) + } + } + yield* HttpRouter.add("GET", "/home", new CustomError({ name: "test" })).pipe( + HttpRouter.serve, + Layer.build + ) + const response = yield* HttpClient.get("/home") + assert.strictEqual(response.status, 599) + assert.deepStrictEqual( + yield* HttpClientResponse.schemaBodyJson(CustomError)(response), + new CustomError({ name: "test" }) + ) + }).pipe(Effect.provide(DenoHttpServer.layerTest))) + + it.effect("httpapi error", () => + Effect.gen(function*() { + yield* HttpRouter.add("GET", "/home", new HttpApiError.BadRequest({})).pipe( + HttpRouter.serve, + Layer.build + ) + assert.strictEqual((yield* HttpClient.get("/home")).status, 400) + }).pipe(Effect.provide(DenoHttpServer.layerTest))) + }) + + it.effect("RouterConfig", () => + Effect.gen(function*() { + yield* HttpRouter.add("GET", "/:param", Effect.succeed(HttpServerResponse.empty())).pipe( + HttpRouter.serve, + Layer.build + ) + assert.strictEqual((yield* HttpClient.get("/123456")).status, 404) + assert.strictEqual((yield* HttpClient.get("/12345")).status, 204) + }).pipe( + Effect.provide([ + DenoHttpServer.layerTest, + Layer.succeed(HttpRouter.RouterConfig)({ maxParamLength: 5 }) + ]) + )) + + it.effect("HttpRouter prefixed", () => + Effect.gen(function*() { + const handler = HttpRouter.serve(HttpRouter.use(Effect.fnUntraced(function*(router_) { + const router = router_.prefixed("/todos") + yield* router.add( + "GET", + "/:id", + Effect.flatMap(HttpRouter.schemaParams(IdParams), ({ id }) => todoResponse({ id, title: "test" })) + ) + yield* router.addAll([ + HttpRouter.route("GET", "/", Effect.succeed(HttpServerResponse.text("root"))) + ]) + }))) + yield* Layer.build(handler) + assert.deepStrictEqual( + yield* HttpClient.get("/todos/1").pipe(Effect.flatMap(HttpClientResponse.schemaBodyJson(Todo))), + { id: 1, title: "test" } + ) + assert.strictEqual(yield* HttpClient.get("/todos").pipe(Effect.flatMap((_) => _.text)), "root") + }).pipe(Effect.provide(DenoHttpServer.layerTest))) + + it.effect("cancels a file body for HEAD requests", () => + Effect.gen(function*() { + let cancelled = false + const fileResponse = yield* HttpServerResponse.file(fixture).pipe( + Effect.updateService(HttpPlatform.HttpPlatform, (platform) => ({ + ...platform, + fileResponse: (path, options) => + Effect.map(platform.fileResponse(path, options), (response) => { + assert.strictEqual(response.body._tag, "Raw") + const source = (response.body as HttpBody.Raw).body + assert(source instanceof ReadableStream) + const reader = source.getReader() + const body = new ReadableStream({ + pull(controller) { + return reader.read().then(({ done, value }) => { + if (done) controller.close() + else controller.enqueue(value) + }) + }, + cancel(reason) { + cancelled = true + return reader.cancel(reason) + } + }) + return HttpServerResponse.raw(body, { + status: response.status, + statusText: response.statusText, + headers: response.headers, + cookies: response.cookies + }) + }) + })) + ) + yield* fileResponse.pipe( + Effect.succeed, + HttpServer.serveEffect() + ) + const response = yield* HttpClient.head("/") + assert.strictEqual(response.status, 200) + assert(cancelled) + }).pipe(Effect.provide(DenoHttpServer.layerTest))) + + it.effect("ignores cancellation errors for HEAD response bodies", () => { + const body = new ReadableStream() + const reader = body.getReader() + return Effect.gen(function*() { + yield* HttpServerResponse.raw(body).pipe( + Effect.succeed, + HttpServer.serveEffect() + ) + const response = yield* HttpClient.head("/") + assert.strictEqual(response.status, 200) + }).pipe( + Effect.ensuring(Effect.sync(() => reader.releaseLock())), + Effect.provide(DenoHttpServer.layerTest) + ) + }) + + it.effect("round trips WebSocket frames and closes cleanly", () => + Effect.gen(function*() { + yield* serveWebSocket(Effect.fnUntraced(function*(socket) { + const write = yield* socket.writer + yield* socket.runRaw((message) => write(message)) + })) + const server = yield* HttpServer.HttpServer + const port = (server.address as HttpServer.TcpAddress).port + const messages = yield* connectWebSocket(`ws://127.0.0.1:${port}/`, (socket) => socket.send("hello"), 1) + assert.deepStrictEqual(messages, ["hello"]) + }).pipe(Effect.provide(DenoHttpServer.layerTest))) + + it.effect("preserves eager WebSocket frames across an async boundary", () => + Effect.gen(function*() { + yield* serveWebSocket(Effect.fnUntraced(function*(socket) { + yield* Effect.promise(() => new Promise((resolve) => setTimeout(resolve, 0))) + const write = yield* socket.writer + yield* socket.runRaw((message) => write(message)) + })) + + const server = yield* HttpServer.HttpServer + const port = (server.address as HttpServer.TcpAddress).port + const messages = yield* connectWebSocket(`ws://127.0.0.1:${port}/`, (socket) => { + socket.send("first") + socket.send("second") + }, 2) + + assert.deepStrictEqual(messages, ["first", "second"]) + }).pipe(Effect.provide(DenoHttpServer.layerTest))) + + it.effect("delivers binary WebSocket frames as Uint8Array", () => + Effect.gen(function*() { + const received = yield* Queue.unbounded() + yield* serveWebSocket(Effect.fnUntraced(function*(socket) { + yield* socket.runRaw((message) => { + assert(message instanceof Uint8Array) + return Queue.offer(received, message) + }) + })) + + const server = yield* HttpServer.HttpServer + const port = (server.address as HttpServer.TcpAddress).port + const socket = yield* openWebSocket(`ws://127.0.0.1:${port}/`) + socket.send(new Uint8Array([1, 2, 3])) + + assert.deepStrictEqual(yield* Queue.take(received), new Uint8Array([1, 2, 3])) + socket.close() + }).pipe(Effect.provide(DenoHttpServer.layerTest))) +}) + +const serveWebSocket = ( + run: (socket: Socket.Socket) => Effect.Effect +) => + HttpRouter.add( + "GET", + "/", + Effect.gen(function*() { + const request = yield* HttpServerRequest.HttpServerRequest + const socket = yield* request.upgrade + yield* run(socket) + return HttpServerResponse.empty() + }) + ).pipe(HttpRouter.serve, Layer.build) + +const openWebSocket = (url: string) => + Effect.acquireRelease( + Effect.callback((resume) => { + const socket = new WebSocket(url) + socket.addEventListener("open", () => resume(Effect.succeed(socket)), { once: true }) + socket.addEventListener("error", () => resume(Effect.fail(new Error("WebSocket connection failed"))), { + once: true + }) + }), + (socket) => Effect.sync(() => socket.close()) + ) + +const connectWebSocket = (url: string, onOpen: (socket: WebSocket) => void, messageCount: number) => + Effect.acquireUseRelease( + openWebSocket(url), + (socket) => + Effect.gen(function*() { + const messages: Array = [] + const fiber = yield* Effect.callback((resume) => { + socket.addEventListener("message", (event) => { + messages.push(event.data) + if (messages.length === messageCount) resume(Effect.void) + }) + socket.addEventListener("error", () => resume(Effect.fail(new Error("WebSocket connection failed"))), { + once: true + }) + onOpen(socket) + }).pipe(Effect.forkChild) + yield* Fiber.join(fiber) + return messages + }), + (socket) => Effect.sync(() => socket.close()) + ) diff --git a/repos/effect/packages/platform-deno/test/DenoKeyValueStore.test.ts b/repos/effect/packages/platform-deno/test/DenoKeyValueStore.test.ts new file mode 100644 index 0000000000..14497eaeb1 --- /dev/null +++ b/repos/effect/packages/platform-deno/test/DenoKeyValueStore.test.ts @@ -0,0 +1,12 @@ +import * as DenoKeyValueStore from "@effect/platform-deno/DenoKeyValueStore" +import { beforeAll, describe } from "@effect/vitest" +import { testLayer } from "../../effect/test/unstable/persistence/KeyValueStore.test.ts" + +beforeAll(() => { + localStorage.clear() + sessionStorage.clear() +}) + +describe("KeyValueStore / layerLocalStorage", () => testLayer(DenoKeyValueStore.layerLocalStorage)) + +describe("KeyValueStore / layerSessionStorage", () => testLayer(DenoKeyValueStore.layerSessionStorage)) diff --git a/repos/effect/packages/platform-deno/test/DenoPath.test.ts b/repos/effect/packages/platform-deno/test/DenoPath.test.ts new file mode 100644 index 0000000000..986a7cb157 --- /dev/null +++ b/repos/effect/packages/platform-deno/test/DenoPath.test.ts @@ -0,0 +1,39 @@ +import * as DenoPath from "@effect/platform-deno/DenoPath" +import { assert, it } from "@effect/vitest" +import { Effect, Path } from "effect" + +it.layer(DenoPath.layer)("Integration", (it) => { + it.effect("runs the demo program", () => + Effect.gen(function*() { + // Access the Path service + const path = yield* Path.Path + + // Join parts of a path to create a complete file path + const tmpPath = path.join("tmp", "file.txt") + + assert.strictEqual(tmpPath, "tmp/file.txt") + })) +}) + +it.layer(DenoPath.layerPosix)("POSIX file URLs", (it) => { + it.effect("uses POSIX conversions", () => + Effect.gen(function*() { + const path = yield* Path.Path + + assert.strictEqual(yield* path.fromFileUrl(new URL("file:///tmp/file.txt")), "/tmp/file.txt") + assert.strictEqual((yield* path.toFileUrl("/tmp/file.txt")).href, "file:///tmp/file.txt") + })) +}) + +it.layer(DenoPath.layerWin32)("Windows file URLs", (it) => { + it.effect("uses Windows conversions", () => + Effect.gen(function*() { + const path = yield* Path.Path + + assert.strictEqual(yield* path.fromFileUrl(new URL("file:///C:/Users/me/file.txt")), "C:\\Users\\me\\file.txt") + assert.strictEqual( + (yield* path.toFileUrl("C:\\Users\\me\\file.txt")).href, + "file:///C:/Users/me/file.txt" + ) + })) +}) diff --git a/repos/effect/packages/platform-deno/test/DenoRedis.integration.test.ts b/repos/effect/packages/platform-deno/test/DenoRedis.integration.test.ts new file mode 100644 index 0000000000..67398a959d --- /dev/null +++ b/repos/effect/packages/platform-deno/test/DenoRedis.integration.test.ts @@ -0,0 +1,174 @@ +import * as DenoRedis from "@effect/platform-deno/DenoRedis" +import { assert, it } from "@effect/vitest" +import { RedisContainer } from "@testcontainers/redis" +import { Deferred, Effect, Fiber, Layer, Schema } from "effect" +import { PersistedQueue, Persistence } from "effect/unstable/persistence" +import * as PersistedCacheTest from "../../effect/test/unstable/persistence/PersistedCacheTest.ts" +import * as PersistedQueueTest from "../../effect/test/unstable/persistence/PersistedQueueTest.ts" + +const RedisLayer = Layer.unwrap( + Effect.gen(function*() { + const container = yield* Effect.acquireRelease( + Effect.promise(() => new RedisContainer("redis:alpine").start()), + (container) => Effect.promise(() => container.stop()) + ) + return DenoRedis.layer({ + url: `redis://${container.getHost()}:${container.getMappedPort(6379)}` + }) + }).pipe( + Effect.catchCause(() => Effect.fail(new PersistedCacheTest.TransientError())) + ) +) + +PersistedCacheTest.suite( + "DenoRedis", + Persistence.layerRedis.pipe(Layer.provide(RedisLayer)) +) + +PersistedQueueTest.suite( + "DenoRedis", + // short intervals so the periodic reset runs while the suite's takes are + // in flight + PersistedQueue.layerStoreRedis({ + pollInterval: "50 millis", + lockRefreshInterval: "100 millis" + }).pipe(Layer.provide(RedisLayer)) +) + +const PersistedQueueRedisLayer = Layer.mergeAll( + RedisLayer, + PersistedQueue.layer.pipe( + Layer.provideMerge( + PersistedQueue.layerStoreRedis().pipe(Layer.provide(RedisLayer)) + ) + ) +) + +it.layer(PersistedQueueRedisLayer, { timeout: "30 seconds" })( + "PersistedQueue (DenoRedis)", + (it) => { + it.effect("moves exhausted elements to the failed list", () => + Effect.gen(function*() { + const redis = yield* DenoRedis.DenoRedis + const queueName = "test-redis-failed" + + const queue = yield* PersistedQueue.make({ + name: queueName, + schema: RedisItem + }) + const id = yield* queue.offer({ n: 42 }) + const error = yield* queue.take(() => Effect.fail("boom"), { maxAttempts: 1 }).pipe(Effect.flip) + assert.strictEqual(error, "boom") + + const failed = yield* redis.use((client) => client.lrange(`effectq:${queueName}:failed`, 0, -1)) + assert.strictEqual(failed.length, 1) + const failedItem = JSON.parse(failed[0]) + assert.strictEqual(failedItem.id, id) + assert.deepStrictEqual(failedItem.element, { n: 42 }) + assert.strictEqual(failedItem.attempts, 1) + + const pending = yield* redis.use((client) => client.hlen(`effectq:${queueName}:pending`)) + assert.strictEqual(pending, 0) + })) + } +) + +it.effect("closes the connection when interrupted during acquisition", () => + Effect.gen(function*() { + const listener = yield* makeListener + const authReceived = yield* Deferred.make() + const sendAuthReply = yield* Deferred.make() + const server = yield* Effect.gen(function*() { + const connection = yield* accept(listener) + yield* read(connection) + yield* Deferred.succeed(authReceived, void 0) + yield* Deferred.await(sendAuthReply) + yield* write(connection, "+OK\r\n") + return yield* read(connection) + }).pipe(Effect.forkChild) + + const port = (listener.addr as Deno.NetAddr).port + const layerFiber = yield* Layer.build(DenoRedis.layer({ + url: `redis://:secret@127.0.0.1:${port}` + })).pipe(Effect.forkChild({ startImmediately: true })) + + yield* Deferred.await(authReceived) + const interruptFiber = yield* Fiber.interrupt(layerFiber).pipe( + Effect.forkChild({ startImmediately: true }) + ) + yield* Deferred.succeed(sendAuthReply, void 0) + yield* Fiber.join(interruptFiber) + + assert.isNull(yield* Fiber.join(server)) + })) + +it.effect("uses the URL username for two-argument AUTH", () => + Effect.gen(function*() { + const listener = yield* makeListener + const server = yield* Effect.gen(function*() { + const connection = yield* accept(listener) + const request = yield* read(connection) + yield* write(connection, "+OK\r\n") + return request + }).pipe(Effect.forkChild) + + const port = (listener.addr as Deno.NetAddr).port + yield* Layer.build(DenoRedis.layer({ + url: `redis://alice:secret@127.0.0.1:${port}`, + password: undefined + })) + + assert.strictEqual( + yield* Fiber.join(server), + "*3\r\n$4\r\nAUTH\r\n$5\r\nalice\r\n$6\r\nsecret\r\n" + ) + })) + +it.effect("prefers explicit credentials over URL credentials", () => + Effect.gen(function*() { + const listener = yield* makeListener + const server = yield* Effect.gen(function*() { + const connection = yield* accept(listener) + const request = yield* read(connection) + yield* write(connection, "+OK\r\n") + return request + }).pipe(Effect.forkChild) + + const port = (listener.addr as Deno.NetAddr).port + yield* Layer.build(DenoRedis.layer({ + url: `redis://alice:secret@127.0.0.1:${port}`, + username: "bob", + password: "other" + })) + + assert.strictEqual( + yield* Fiber.join(server), + "*3\r\n$4\r\nAUTH\r\n$3\r\nbob\r\n$5\r\nother\r\n" + ) + })) + +const RedisItem = Schema.Struct({ + n: Schema.Number +}) + +const encoder = new TextEncoder() +const decoder = new TextDecoder() + +const makeListener = Effect.acquireRelease( + Effect.sync(() => Deno.listen({ hostname: "127.0.0.1", port: 0 })), + (listener) => Effect.sync(() => listener.close()) +) + +const accept = (listener: Deno.TcpListener) => + Effect.acquireRelease( + Effect.promise(() => listener.accept()), + (connection) => Effect.sync(() => connection.close()) + ) + +const read = (connection: Deno.TcpConn) => + Effect.promise(() => { + const buffer = new Uint8Array(256) + return connection.read(buffer).then((size) => size === null ? null : decoder.decode(buffer.subarray(0, size))) + }) + +const write = (connection: Deno.TcpConn, value: string) => Effect.promise(() => connection.write(encoder.encode(value))) diff --git a/repos/effect/packages/platform-deno/test/DenoSocket.test.ts b/repos/effect/packages/platform-deno/test/DenoSocket.test.ts new file mode 100644 index 0000000000..a47fac063e --- /dev/null +++ b/repos/effect/packages/platform-deno/test/DenoSocket.test.ts @@ -0,0 +1,332 @@ +import * as DenoSocket from "@effect/platform-deno/DenoSocket" +import { assert, describe, it } from "@effect/vitest" +import { Deferred, Effect, Fiber, Queue } from "effect" +import * as Stream from "effect/Stream" +import * as Socket from "effect/unstable/socket/Socket" + +const makeTcpServer = Effect.acquireRelease( + Effect.sync(() => Deno.listen({ hostname: "127.0.0.1", port: 0 })), + (listener) => Effect.sync(() => listener.close()) +) + +const makeTempDir = Effect.acquireRelease( + Effect.promise(() => Deno.makeTempDir()), + (path) => Effect.promise(() => Deno.remove(path, { recursive: true })) +) + +const runEchoServer = (listener: Deno.Listener) => + Effect.forever( + Effect.promise(() => listener.accept().then((conn) => conn.readable.pipeTo(conn.writable))) + ) + +const makeTestConn = (options?: { + readonly data?: Uint8Array | undefined + readonly pendingRead?: boolean | undefined + readonly closeReadableOnCloseWrite?: boolean | undefined + readonly setNoDelay?: ((value?: boolean) => void) | undefined + readonly setKeepAlive?: ((value?: boolean) => void) | undefined +}) => { + let controller!: ReadableStreamDefaultController + let readableClosed = false + let closeWrites = 0 + const readable = new ReadableStream({ + start(value) { + controller = value + if (options?.data) value.enqueue(options.data) + if (options?.pendingRead !== true) { + readableClosed = true + value.close() + } + } + }) + const conn = { + readable, + writable: new WritableStream(), + read: () => Promise.resolve(null), + write: (data: Uint8Array) => Promise.resolve(data.length), + close() { + if (readableClosed) return + readableClosed = true + controller.close() + }, + closeWrite() { + closeWrites++ + if (options?.closeReadableOnCloseWrite && !readableClosed) { + readableClosed = true + controller.close() + } + return Promise.resolve() + }, + ref() {}, + unref() {}, + localAddr: { transport: "tcp", hostname: "127.0.0.1", port: 0 }, + remoteAddr: { transport: "tcp", hostname: "127.0.0.1", port: 0 }, + [Symbol.dispose]() { + this.close() + }, + ...(options?.setNoDelay ? { setNoDelay: options.setNoDelay } : {}), + ...(options?.setKeepAlive ? { setKeepAlive: options.setKeepAlive } : {}) + } as Deno.Conn + return { conn, closeWrites: () => closeWrites } +} + +const replaceDenoConnect = ( + connect: (options: Deno.ConnectOptions | Deno.UnixConnectOptions) => Promise +) => + Effect.acquireRelease( + Effect.sync(() => { + const descriptor = Object.getOwnPropertyDescriptor(Deno, "connect")! + Object.defineProperty(Deno, "connect", { ...descriptor, value: connect }) + return descriptor + }), + (descriptor) => Effect.sync(() => Object.defineProperty(Deno, "connect", descriptor)) + ) + +describe("DenoSocket", () => { + it.effect("echoes over TCP", () => + Effect.gen(function*() { + const listener = yield* makeTcpServer + yield* runEchoServer(listener).pipe(Effect.forkScoped) + const address = listener.addr as Deno.NetAddr + + const output = yield* Stream.make("Hello", "World").pipe( + Stream.encodeText, + Stream.pipeThroughChannel(DenoSocket.makeTcpChannel({ hostname: address.hostname, port: address.port })), + Stream.decodeText(), + Stream.mkString + ) + + assert.strictEqual(output, "HelloWorld") + })) + + it.effect("keeps reading after the write side closes", () => + Effect.gen(function*() { + const listener = yield* makeTcpServer + const address = listener.addr as Deno.NetAddr + const writeClosed = yield* Deferred.make() + const encoder = new TextEncoder() + const decoder = new TextDecoder() + + yield* Effect.gen(function*() { + const conn = yield* Effect.promise(() => listener.accept()) + const chunks: Array = [] + const read: Effect.Effect = Effect.suspend(() => { + const buffer = new Uint8Array(5) + return Effect.promise(() => conn.read(buffer)).pipe( + Effect.flatMap((size) => { + if (size === null) return Effect.void + chunks.push(decoder.decode(buffer.subarray(0, size))) + return read + }) + ) + }) + yield* read + assert.strictEqual(chunks.join(""), "Hello") + yield* Deferred.succeed(writeClosed, undefined) + const writer = conn.writable.getWriter() + yield* Effect.promise(() => writer.ready.then(() => writer.write(encoder.encode("Hello")))) + writer.releaseLock() + yield* Effect.promise(() => conn.closeWrite()) + }).pipe(Effect.forkScoped) + + const socket = yield* DenoSocket.makeTcp({ hostname: address.hostname, port: address.port }) + const messages = yield* Queue.unbounded() + const runFiber = yield* socket.run((chunk) => Queue.offer(messages, chunk)).pipe(Effect.forkChild) + yield* Effect.scoped( + socket.writer.pipe(Effect.flatMap((write) => write(encoder.encode("Hello")))) + ) + yield* Deferred.await(writeClosed) + + assert.deepStrictEqual(yield* Queue.take(messages), encoder.encode("Hello")) + yield* Fiber.join(runFiber) + })) + + it.effect("half-closes an empty channel input", () => + Effect.gen(function*() { + const listener = yield* makeTcpServer + const address = listener.addr as Deno.NetAddr + const encoder = new TextEncoder() + + yield* Effect.gen(function*() { + const conn = yield* Effect.promise(() => listener.accept()) + assert.strictEqual(yield* Effect.promise(() => conn.read(new Uint8Array(1))), null) + const writer = conn.writable.getWriter() + yield* Effect.promise(() => writer.write(encoder.encode("Closed"))) + writer.releaseLock() + yield* Effect.promise(() => conn.closeWrite()) + }).pipe(Effect.forkScoped) + + const output = yield* Stream.empty.pipe( + Stream.pipeThroughChannel(DenoSocket.makeTcpChannel({ hostname: address.hostname, port: address.port })), + Stream.decodeText(), + Stream.mkString + ) + + assert.strictEqual(output, "Closed") + })) + + it.effect("does not carry a consumed half-close into a second run", () => + Effect.gen(function*() { + const encoder = new TextEncoder() + const first = makeTestConn({ pendingRead: true, closeReadableOnCloseWrite: true }) + const second = makeTestConn({ data: encoder.encode("Second") }) + const connections = [first.conn, second.conn] + let index = 0 + const socket = yield* DenoSocket.fromConn(Effect.sync(() => connections[index++]!)) + const opened = yield* Deferred.make() + + const firstRun = yield* socket.run(() => {}, { + onOpen: Deferred.succeed(opened, undefined) + }).pipe(Effect.forkChild) + yield* Deferred.await(opened) + yield* Effect.scoped(socket.writer) + yield* Fiber.join(firstRun) + + const received: Array = [] + yield* socket.run((chunk) => Effect.sync(() => received.push(chunk))) + + assert.deepStrictEqual(received, [encoder.encode("Second")]) + assert.strictEqual(first.closeWrites(), 1) + assert.strictEqual(second.closeWrites(), 0) + })) + + it.effect("maps connection refusal to SocketOpenError", () => + Effect.gen(function*() { + const listener = Deno.listen({ hostname: "127.0.0.1", port: 0 }) + const address = listener.addr as Deno.NetAddr + listener.close() + const socket = yield* DenoSocket.makeTcp({ hostname: address.hostname, port: address.port }) + + const error = yield* socket.run(() => {}).pipe(Effect.flip) + + assert.instanceOf(error, Socket.SocketError) + assert.strictEqual(error.reason._tag, "SocketOpenError") + if (error.reason._tag === "SocketOpenError") { + assert.strictEqual(error.reason.kind, "Unknown") + assert.instanceOf(error.reason.cause, Deno.errors.ConnectionRefused) + } + })) + + it.effect("applies TCP tuning options and ignores them for Unix", () => + Effect.gen(function*() { + const noDelay: Array = [] + const keepAlive: Array = [] + const tcp = makeTestConn({ + setNoDelay: (value) => noDelay.push(value), + setKeepAlive: (value) => keepAlive.push(value) + }) + const unix = makeTestConn() + const connections = [tcp.conn, unix.conn] + const connectOptions: Array = [] + let index = 0 + yield* replaceDenoConnect((options) => { + connectOptions.push(options) + return Promise.resolve(connections[index++]!) + }) + + const tcpSocket = yield* DenoSocket.makeTcp({ port: 1, noDelay: false, keepAlive: true }) + yield* tcpSocket.run(() => {}) + const unixSocket = yield* DenoSocket.makeTcp({ + transport: "unix", + path: "/unused.sock", + noDelay: true, + keepAlive: false + }) + yield* unixSocket.run(() => {}) + + assert.deepStrictEqual(noDelay, [false]) + assert.deepStrictEqual(keepAlive, [true]) + assert.deepStrictEqual(connectOptions, [ + { port: 1 }, + { transport: "unix", path: "/unused.sock" } + ]) + })) + + it.effect("echoes over Unix sockets", () => + Effect.gen(function*() { + const directory = yield* makeTempDir + const path = `${directory}/echo.sock` + const listener = yield* Effect.acquireRelease( + Effect.sync(() => Deno.listen({ transport: "unix", path })), + (listener) => Effect.sync(() => listener.close()) + ) + yield* runEchoServer(listener).pipe(Effect.forkScoped) + + const output = yield* Stream.make("Hello", "Unix").pipe( + Stream.encodeText, + Stream.pipeThroughChannel(DenoSocket.makeTcpChannel({ transport: "unix", path })), + Stream.decodeText(), + Stream.mkString + ) + + assert.strictEqual(output, "HelloUnix") + })) + + it.effect("uses Deno's native WebSocket", () => + Effect.gen(function*() { + const server = yield* Effect.acquireRelease( + Effect.sync(() => + Deno.serve( + { hostname: "127.0.0.1", port: 0, onListen: () => {} }, + (request) => { + const { response, socket } = Deno.upgradeWebSocket(request) + socket.onmessage = (event) => socket.send(event.data) + return response + } + ) + ), + (server) => Effect.promise(() => server.shutdown()) + ) + const address = server.addr as Deno.NetAddr + const messages = yield* Queue.unbounded() + + yield* Effect.gen(function*() { + const socket = yield* Socket.Socket + const runFiber = yield* socket.run((chunk) => Queue.offer(messages, chunk)).pipe(Effect.forkChild) + const write = yield* socket.writer + yield* write("Hello WebSocket") + + assert.deepStrictEqual(yield* Queue.take(messages), new TextEncoder().encode("Hello WebSocket")) + yield* Fiber.interrupt(runFiber) + }).pipe( + Effect.scoped, + Effect.provide(DenoSocket.layerWebSocket(`ws://${address.hostname}:${address.port}`, { + closeCodeIsError: () => false + })) + ) + })) + + it.effect("adapts a TransformStream", () => + Effect.gen(function*() { + const readable = Stream.make("A", "B", "C").pipe( + Stream.tap(() => Effect.sleep(50)), + Stream.toReadableStream() + ) + const decoder = new TextDecoder() + const chunks: Array = [] + const writable = new WritableStream({ + write(chunk) { + chunks.push(decoder.decode(chunk)) + } + }) + + const socket = yield* Socket.fromTransformStream( + Effect.succeed({ readable, writable }), + { closeCodeIsError: () => false } + ) + yield* socket.writer.pipe( + Effect.tap((write) => write("Hello").pipe(Effect.andThen(write("World")))), + Effect.scoped, + Effect.forkChild + ) + const received: Array = [] + yield* socket.run((chunk) => + Effect.sync(() => { + received.push(decoder.decode(chunk)) + }) + ).pipe(Effect.scoped) + + assert.deepStrictEqual(chunks, ["Hello", "World"]) + assert.deepStrictEqual(received, ["A", "B", "C"]) + })) +}) diff --git a/repos/effect/packages/platform-deno/test/DenoSocketServer.test.ts b/repos/effect/packages/platform-deno/test/DenoSocketServer.test.ts new file mode 100644 index 0000000000..84482d2e09 --- /dev/null +++ b/repos/effect/packages/platform-deno/test/DenoSocketServer.test.ts @@ -0,0 +1,266 @@ +import * as DenoSocket from "@effect/platform-deno/DenoSocket" +import * as DenoSocketServer from "@effect/platform-deno/DenoSocketServer" +import { assert, describe, it } from "@effect/vitest" +import { Cause, Deferred, Effect, Exit, Fiber, Logger, Option, References, Scope } from "effect" +import * as Stream from "effect/Stream" +import { TestClock } from "effect/testing" +import type * as Socket from "effect/unstable/socket/Socket" +import { fromListener } from "../src/internal/denoSocketServer.ts" + +const makeTempDir = Effect.acquireRelease( + Effect.promise(() => Deno.makeTempDir()), + (path) => Effect.promise(() => Deno.remove(path, { recursive: true })) +) + +const echo = (socket: Socket.Socket) => + Effect.scoped( + Effect.gen(function*() { + const write = yield* socket.writer + yield* socket.run(write) + }) + ) + +const makeTestConn = (onClose?: () => void): Deno.Conn => { + const transform = new TransformStream() + return { + readable: transform.readable, + writable: transform.writable, + read: () => Promise.resolve(null), + write: (data) => Promise.resolve(data.length), + close() { + onClose?.() + }, + closeWrite: () => Promise.resolve(), + ref() {}, + unref() {}, + localAddr: { transport: "tcp", hostname: "127.0.0.1", port: 1 }, + remoteAddr: { transport: "tcp", hostname: "127.0.0.1", port: 2 }, + [Symbol.dispose]() {} + } +} + +const makeTestListener = ( + accepts: ReadonlyArray<() => Promise>, + called: ReadonlyArray> +): Deno.Listener => { + let index = 0 + return { + addr: { transport: "tcp", hostname: "127.0.0.1", port: 1 }, + accept() { + Deferred.doneUnsafe(called[index], Exit.void) + return accepts[index++]!() + }, + close() {}, + ref() {}, + unref() {}, + [Symbol.dispose]() {} + } as unknown as Deno.Listener +} + +const neverAccept = () => new Promise(() => {}) +const settlePromises = Effect.promise(() => Promise.resolve()) + +describe("DenoSocketServer", () => { + it.effect("echoes over TCP and reports its address", () => + Effect.gen(function*() { + const server = yield* DenoSocketServer.make({ hostname: "127.0.0.1", port: 0 }) + const address = server.address + assert.strictEqual(address._tag, "TcpAddress") + if (address._tag !== "TcpAddress") return + assert.strictEqual(address.hostname, "127.0.0.1") + assert.notStrictEqual(address.port, 0) + yield* server.run(echo).pipe(Effect.forkScoped) + + const output = yield* Stream.make("Hello", "Deno").pipe( + Stream.encodeText, + Stream.pipeThroughChannel(DenoSocket.makeTcpChannel({ + hostname: address.hostname, + port: address.port + })), + Stream.decodeText(), + Stream.mkString + ) + + assert.strictEqual(output, "HelloDeno") + })) + + it.effect("echoes over Unix sockets and reports its address", () => + Effect.gen(function*() { + const directory = yield* makeTempDir + const path = `${directory}/echo.sock` + const server = yield* DenoSocketServer.make({ transport: "unix", path }) + assert.deepStrictEqual(server.address, { _tag: "UnixAddress", path }) + yield* server.run(echo).pipe(Effect.forkScoped) + + const output = yield* Stream.make("Hello", "Unix").pipe( + Stream.encodeText, + Stream.pipeThroughChannel(DenoSocket.makeTcpChannel({ transport: "unix", path })), + Stream.decodeText(), + Stream.mkString + ) + + assert.strictEqual(output, "HelloUnix") + })) + + it.effect("interrupts handler fibers when run is interrupted", () => + Effect.gen(function*() { + const server = yield* DenoSocketServer.make({ hostname: "127.0.0.1", port: 0 }) + const address = server.address + assert.strictEqual(address._tag, "TcpAddress") + if (address._tag !== "TcpAddress") return + const started = yield* Deferred.make() + const interrupted = yield* Deferred.make() + const runFiber = yield* server.run(() => + Deferred.succeed(started, undefined).pipe( + Effect.andThen(Effect.never), + Effect.onInterrupt(() => Deferred.succeed(interrupted, undefined)) + ) + ).pipe(Effect.forkChild) + const conn = yield* Effect.acquireRelease( + Effect.promise(() => Deno.connect({ hostname: address.hostname, port: address.port })), + (conn) => Effect.sync(() => conn.close()) + ) + void conn + yield* Deferred.await(started) + + yield* Fiber.interrupt(runFiber) + + assert.isTrue(yield* Deferred.isDone(interrupted)) + })) + + it.effect("does not surface BadResource when the listener is closed", () => + Effect.gen(function*() { + const scope = yield* Scope.make() + const server = yield* DenoSocketServer.make({ hostname: "127.0.0.1", port: 0 }).pipe(Scope.provide(scope)) + const runFiber = yield* server.run(() => Effect.void).pipe(Effect.forkChild) + + yield* Scope.close(scope, Exit.void) + yield* Effect.yieldNow + + assert.isUndefined(runFiber.pollUnsafe()) + yield* Fiber.interrupt(runFiber) + })) + + it.effect("closes with a pending pre-run connection", () => + Effect.gen(function*() { + const scope = yield* Scope.make() + const server = yield* DenoSocketServer.make({ hostname: "127.0.0.1", port: 0 }).pipe(Scope.provide(scope)) + const address = server.address + assert.strictEqual(address._tag, "TcpAddress") + if (address._tag !== "TcpAddress") return + const conn = yield* Effect.acquireRelease( + Effect.promise(() => Deno.connect({ hostname: address.hostname, port: address.port })), + (conn) => Effect.sync(() => conn.close()) + ) + void conn + + yield* Scope.close(scope, Exit.void).pipe(Effect.timeout("1 second")) + })) + + it.effect("closes the connection after its handler completes", () => + Effect.gen(function*() { + const called = [Deferred.makeUnsafe(), Deferred.makeUnsafe()] + const handled = yield* Deferred.make() + const closed = yield* Deferred.make() + const listener = makeTestListener([ + () => Promise.resolve(makeTestConn(() => Deferred.doneUnsafe(closed, Exit.void))), + neverAccept + ], called) + const server = fromListener(listener) + yield* server.run(() => Deferred.succeed(handled, undefined)).pipe(Effect.forkChild) + + yield* Deferred.await(handled) + yield* Effect.yieldNow + + assert.isTrue(yield* Deferred.isDone(closed)) + })) + + it.effect("logs accept failures and continues accepting", () => + Effect.gen(function*() { + const called = [Deferred.makeUnsafe(), Deferred.makeUnsafe(), Deferred.makeUnsafe()] + const failure = new Error("accept failed") + const handled = yield* Deferred.make() + const logs: Array<{ readonly cause: Cause.Cause; readonly message: unknown }> = [] + const logger = Logger.make((options) => + logs.push({ cause: options.cause, message: options.message }) + ) + const listener = makeTestListener([ + () => Promise.reject(failure), + () => Promise.resolve(makeTestConn()), + neverAccept + ], called) + const server = fromListener(listener) + yield* server.run(() => Deferred.succeed(handled, undefined)).pipe( + Effect.provide(Logger.layer([logger])), + Effect.provideService(References.UnhandledLogLevel, "Error"), + Effect.forkChild + ) + + yield* Deferred.await(called[0]) + yield* settlePromises + yield* TestClock.adjust(10) + assert.isTrue(yield* Deferred.isDone(handled)) + + assert.strictEqual(logs.length, 1) + assert.deepStrictEqual(logs[0]!.message, ["Unhandled error in SocketServer"]) + assert.deepStrictEqual(Cause.findErrorOption(logs[0]!.cause), Option.some(failure)) + })) + + it.effect("backs off consecutive accept failures", () => + Effect.gen(function*() { + const called = [Deferred.makeUnsafe(), Deferred.makeUnsafe(), Deferred.makeUnsafe()] + const listener = makeTestListener([ + () => Promise.reject(new Error("first")), + () => Promise.reject(new Error("second")), + neverAccept + ], called) + const server = fromListener(listener) + yield* server.run(() => Effect.void).pipe( + Effect.provideService(References.UnhandledLogLevel, undefined), + Effect.forkChild + ) + + yield* Deferred.await(called[0]) + yield* settlePromises + yield* TestClock.adjust(9) + assert.isFalse(yield* Deferred.isDone(called[1])) + yield* TestClock.adjust(1) + assert.isTrue(yield* Deferred.isDone(called[1])) + yield* settlePromises + yield* TestClock.adjust(19) + assert.isFalse(yield* Deferred.isDone(called[2])) + yield* TestClock.adjust(1) + assert.isTrue(yield* Deferred.isDone(called[2])) + })) + + it.effect("resets accept backoff after a successful connection", () => + Effect.gen(function*() { + const called = [ + Deferred.makeUnsafe(), + Deferred.makeUnsafe(), + Deferred.makeUnsafe(), + Deferred.makeUnsafe() + ] + const listener = makeTestListener([ + () => Promise.reject(new Error("first")), + () => Promise.resolve(makeTestConn()), + () => Promise.reject(new Error("after success")), + neverAccept + ], called) + const server = fromListener(listener) + yield* server.run(() => Effect.void).pipe( + Effect.provideService(References.UnhandledLogLevel, undefined), + Effect.forkChild + ) + + yield* Deferred.await(called[0]) + yield* settlePromises + yield* TestClock.adjust(10) + assert.isTrue(yield* Deferred.isDone(called[2])) + yield* settlePromises + yield* TestClock.adjust(9) + assert.isFalse(yield* Deferred.isDone(called[3])) + yield* TestClock.adjust(1) + assert.isTrue(yield* Deferred.isDone(called[3])) + })) +}) diff --git a/repos/effect/packages/platform-deno/test/DenoStdio.test.ts b/repos/effect/packages/platform-deno/test/DenoStdio.test.ts new file mode 100644 index 0000000000..936c1ddedb --- /dev/null +++ b/repos/effect/packages/platform-deno/test/DenoStdio.test.ts @@ -0,0 +1,50 @@ +import * as DenoStdio from "@effect/platform-deno/DenoStdio" +import { assert, describe, it } from "@effect/vitest" +import * as Effect from "effect/Effect" +import * as Stdio from "effect/Stdio" + +const streams = [Deno.stdin, Deno.stdout] as const + +const setIsTerminal = (stdin: boolean, stdout: boolean) => + Effect.sync(() => { + Object.defineProperty(streams[0], "isTerminal", { configurable: true, value: () => stdin }) + Object.defineProperty(streams[1], "isTerminal", { configurable: true, value: () => stdout }) + }) + +const withRestoredIsTerminal = (effect: Effect.Effect) => + Effect.acquireUseRelease( + Effect.sync(() => streams.map((stream) => Object.getOwnPropertyDescriptor(stream, "isTerminal"))), + () => effect, + (descriptors) => + Effect.sync(() => { + streams.forEach((stream, index) => { + const descriptor = descriptors[index] + if (descriptor === undefined) { + Reflect.deleteProperty(stream, "isTerminal") + } else { + Object.defineProperty(stream, "isTerminal", descriptor) + } + }) + }) + ) + +describe("DenoStdio", () => { + it.effect("reads terminal state when the effects run", () => + withRestoredIsTerminal( + Effect.gen(function*() { + const stdio = yield* Stdio.Stdio + + yield* setIsTerminal(true, false) + assert.deepStrictEqual( + yield* Effect.all([stdio.stdinIsTerminal, stdio.stdoutIsTerminal]), + [true, false] + ) + + yield* setIsTerminal(false, true) + assert.deepStrictEqual( + yield* Effect.all([stdio.stdinIsTerminal, stdio.stdoutIsTerminal]), + [false, true] + ) + }).pipe(Effect.provide(DenoStdio.layer)) + )) +}) diff --git a/repos/effect/packages/platform-deno/test/DenoTerminal.test.ts b/repos/effect/packages/platform-deno/test/DenoTerminal.test.ts new file mode 100644 index 0000000000..58653d17bb --- /dev/null +++ b/repos/effect/packages/platform-deno/test/DenoTerminal.test.ts @@ -0,0 +1,100 @@ +import { assert, describe, it } from "@effect/vitest" +import * as Effect from "effect/Effect" +import { spawn, spawnSync } from "node:child_process" +import { fileURLToPath } from "node:url" + +const fixture = fileURLToPath(new URL("fixtures/deno-terminal.ts", import.meta.url)) + +const runFixture = (mode: string, input: string) => + spawnSync(Deno.execPath(), [fixture, mode], { + encoding: "utf8", + input, + timeout: 2_000 + }) + +const assertResult = (mode: string, input: string, expected: string) => { + const result = runFixture(mode, input) + assert.isUndefined(result.error) + assert.strictEqual(result.status, 0, result.stderr) + assert.isTrue(result.stderr.includes(`RESULT ${expected}`), result.stderr) +} + +const assertOpenResult = (mode: string, input: string, expected: string) => + Effect.callback((resume) => { + const child = spawn(Deno.execPath(), [fixture, mode]) + let stderr = "" + child.stderr.setEncoding("utf8") + child.stderr.on("data", (data) => { + stderr += data + if (stderr.includes("RESULT ")) { + child.stdin.end() + } + }) + child.on("exit", (code) => { + resume( + code === 0 && stderr.includes(`RESULT ${expected}`) + ? Effect.void + : Effect.die(new Error(stderr)) + ) + }) + child.stdin.write(input) + return Effect.sync(() => child.kill()) + }) + +const assertInteropResult = Effect.callback((resume) => { + const child = spawn(Deno.execPath(), [fixture, "interop"]) + let stderr = "" + let sentTerminalInput = false + child.stderr.setEncoding("utf8") + child.stderr.on("data", (data) => { + stderr += data + if (!sentTerminalInput && stderr.includes("READY")) { + sentTerminalInput = true + child.stdin.end("terminal\n") + } + }) + child.on("exit", (code) => { + resume( + code === 0 && stderr.includes("RESULT {\"stdio\":\"stdio\",\"terminal\":\"terminal\"}") + ? Effect.void + : Effect.die(new Error(stderr)) + ) + }) + child.stdin.write("stdio\n") + return Effect.sync(() => child.kill()) +}) + +describe("DenoTerminal", () => { + it("does not install a readline interface until the terminal is used", () => { + assertResult("unused", "", "{\"dataListeners\":0}") + }) + + it("fails a prompt with QuitError after piped input is exhausted", () => { + assertResult("prompts", "y\n", "{\"first\":true,\"second\":\"QuitError\"}") + }) + + it("delivers buffered keypresses before ending the input queue", () => { + assertResult("read-input", "yn", "{\"first\":\"y\",\"second\":\"n\",\"ended\":true}") + }) + + it("flushes an unterminated line before failing readLine with QuitError at EOF", () => { + assertResult("read-line", "last line", "{\"first\":\"last line\",\"second\":\"QuitError\"}") + }) + + it("preserves lines buffered between sequential readLine calls", () => { + assertResult("read-lines", "first\nsecond\n", "{\"first\":\"first\",\"second\":\"second\"}") + }) + + it("fails readLine with QuitError when stdin ended before initialization", () => { + assertResult("read-line-after-end", "", "\"QuitError\"") + }) + + it.effect("disposes readline after its idle TTL", () => + assertOpenResult( + "read-line-disposed", + "line\n", + "{\"line\":\"line\",\"duringTtl\":1,\"dataListeners\":0}" + )) + + it.effect("interoperates with DenoStdio on stdin", () => assertInteropResult) +}) diff --git a/repos/effect/packages/platform-deno/test/DenoWorkerRunner.test.ts b/repos/effect/packages/platform-deno/test/DenoWorkerRunner.test.ts new file mode 100644 index 0000000000..78eb72e397 --- /dev/null +++ b/repos/effect/packages/platform-deno/test/DenoWorkerRunner.test.ts @@ -0,0 +1,101 @@ +import * as DenoWorkerRunner from "@effect/platform-deno/DenoWorkerRunner" +import { assert, describe, it } from "@effect/vitest" +import * as Effect from "effect/Effect" +import * as Fiber from "effect/Fiber" +import * as Queue from "effect/Queue" + +type Listener = EventListenerOrEventListenerObject + +const makePort = () => { + const listeners = new Map>() + const added: Array = [] + const removed: Array = [] + const port = { + addEventListener(type: string, listener: Listener | null) { + if (listener === null) return + added.push([type, listener]) + const eventListeners = listeners.get(type) ?? new Set() + eventListeners.add(listener) + listeners.set(type, eventListeners) + }, + removeEventListener(type: string, listener: Listener | null) { + if (listener === null) return + removed.push([type, listener]) + listeners.get(type)?.delete(listener) + }, + postMessage() {}, + start() {}, + close() {} + } as unknown as MessagePort + + return { + added, + removed, + port, + emit(type: string, data: unknown) { + const event = { data } as MessageEvent + for (const listener of listeners.get(type) ?? []) { + if (typeof listener === "function") { + listener(event) + } else { + listener.handleEvent(event) + } + } + } + } +} + +const makeSharedWorker = () => { + const worker = { + onconnect: null as ((event: MessageEvent) => void) | null, + addEventListener() {}, + removeEventListener() {}, + close() {} + } + return { + worker: worker as unknown as MessagePort, + connect(port: MessagePort) { + worker.onconnect?.({ ports: [port] } as unknown as MessageEvent) + } + } +} + +describe("DenoWorkerRunner", () => { + it.effect("removes the registered messageerror listener", () => + Effect.gen(function*() { + const fake = makePort() + const runner = yield* DenoWorkerRunner.make(fake.port).start() + const fiber = yield* Effect.forkChild(runner.run(() => {})) + yield* Effect.yieldNow + + fake.emit("message", [1]) + yield* Fiber.join(fiber) + + const added = fake.added.find(([type]) => type === "messageerror") + const removed = fake.removed.find(([type]) => type === "messageerror") + assert.isDefined(added) + assert.isDefined(removed) + assert.strictEqual(removed[1], added[1]) + })) + + it.effect("emits disconnects for closed SharedWorker ports", () => + Effect.gen(function*() { + const sharedWorker = makeSharedWorker() + const first = makePort() + const second = makePort() + const runner = yield* DenoWorkerRunner.make(sharedWorker.worker).start() + const fiber = yield* Effect.forkChild(runner.run(() => {})) + yield* Effect.yieldNow + + sharedWorker.connect(first.port) + sharedWorker.connect(second.port) + first.emit("message", [1]) + + const disconnects = runner.disconnects + assert.isDefined(disconnects) + assert.strictEqual(yield* Queue.take(disconnects), 0) + + second.emit("message", [1]) + yield* Fiber.join(fiber) + })) +}) diff --git a/repos/effect/packages/platform-deno/test/RpcWorker.test.ts b/repos/effect/packages/platform-deno/test/RpcWorker.test.ts new file mode 100644 index 0000000000..23e5c749c9 --- /dev/null +++ b/repos/effect/packages/platform-deno/test/RpcWorker.test.ts @@ -0,0 +1,21 @@ +import * as DenoWorker from "@effect/platform-deno/DenoWorker" +import { describe } from "@effect/vitest" +import * as Layer from "effect/Layer" +import * as RpcClient from "effect/unstable/rpc/RpcClient" +import * as RpcServer from "effect/unstable/rpc/RpcServer" +import { e2eSuite, UsersClient } from "./fixtures/rpc-e2e.ts" + +describe("RpcWorker", () => { + const WorkerClient = UsersClient.layer.pipe( + Layer.provide(RpcClient.layerProtocolWorker({ size: 1 })), + Layer.provide( + DenoWorker.layer(() => new Worker(new URL("./fixtures/rpc-worker.ts", import.meta.url), { type: "module" })) + ), + Layer.merge( + Layer.succeed(RpcServer.Protocol)({ + supportsAck: true + } as any) + ) + ) + e2eSuite("e2e worker", WorkerClient, false) +}) diff --git a/repos/effect/packages/platform-deno/test/cluster/SocketRunner.test.ts b/repos/effect/packages/platform-deno/test/cluster/SocketRunner.test.ts new file mode 100644 index 0000000000..20b7181ff2 --- /dev/null +++ b/repos/effect/packages/platform-deno/test/cluster/SocketRunner.test.ts @@ -0,0 +1,165 @@ +import { DenoClusterSocket } from "@effect/platform-deno" +import { assert, describe, it } from "@effect/vitest" +import { BigDecimal, Cause, Effect, Exit, Fiber, Layer, Option, PrimaryKey, Schema } from "effect" +import type { Sharding } from "effect/unstable/cluster" +import { + ClusterSchema, + Entity, + MessageStorage, + RunnerAddress, + RunnerHealth, + RunnerStorage, + ShardingConfig, + SocketRunner +} from "effect/unstable/cluster" +import { Rpc, RpcSerialization } from "effect/unstable/rpc" + +class TestPayload extends Schema.Class("TestPayload")({ + id: Schema.String, + amount: Schema.BigDecimal +}) { + [PrimaryKey.symbol]() { + return this.id + } +} + +const TestEntity = Entity + .make("TestEntity", [ + Rpc.make("Process", { + payload: TestPayload, + success: Schema.Void + }) + ]) + .annotateRpcs(ClusterSchema.Persisted, true) + .annotateRpcs(ClusterSchema.Uninterruptible, true) + +const TestEntityLayer = TestEntity.toLayer( + Effect.succeed({ + Process: () => Effect.void + }) +) + +const RUNNER_PORT = 50_125 +const SharedStorage = Layer.mergeAll( + RunnerStorage.layerMemory, + MessageStorage.layerMemory +).pipe( + Layer.provide(ShardingConfig.layerDefaults) +) + +const makeRunnerLayer = (port: number, entities: Layer.Layer) => + entities.pipe( + Layer.provideMerge(SocketRunner.layer), + Layer.provide(RunnerHealth.layerNoop), + Layer.provide(DenoClusterSocket.layerSocketServer), + Layer.provide(DenoClusterSocket.layerClientProtocol), + Layer.provide(ShardingConfig.layer({ + runnerAddress: Option.some(RunnerAddress.make("127.0.0.1", port)), + entityTerminationTimeout: 0, + entityMessagePollInterval: 5000, + sendRetryInterval: 100 + })), + Layer.provide(RpcSerialization.layerMsgPack) + ) + +const makeClientLayer = (port: number) => + SocketRunner.layerClientOnly.pipe( + Layer.provide(DenoClusterSocket.layerClientProtocol), + Layer.provide(ShardingConfig.layer({ + runnerAddress: Option.some(RunnerAddress.make("127.0.0.1", port)), + runnerListenAddress: Option.some(RunnerAddress.make("127.0.0.1", port)), + entityTerminationTimeout: 0, + entityMessagePollInterval: 5000, + sendRetryInterval: 100 + })), + Layer.provide(RpcSerialization.layerMsgPack) + ) + +const IsolationEntity = Entity + .make("IsolationEntity", [ + Rpc.make("BadReply", { + payload: { id: Schema.Number }, + success: Schema.Int + }), + Rpc.make("Slow", { + success: Schema.String + }) + ]) + .annotateRpcs(ClusterSchema.Persisted, false) + +const IsolationEntityLayer = IsolationEntity.toLayer( + Effect.succeed({ + BadReply: () => Effect.succeed(1.5), + Slow: () => Effect.as(Effect.sleep("2 seconds"), "done") + }) +) + +const ISOLATION_PORT = 50_126 + +describe("SocketRunner", () => { + it.live( + "entity call with BigDecimal and discard should not stack overflow", + () => + Effect.gen(function*() { + yield* Layer.launch(makeRunnerLayer(RUNNER_PORT, TestEntityLayer)).pipe(Effect.forkScoped) + + yield* Effect.sleep("2 seconds") + yield* Effect.gen(function*() { + yield* Effect.sleep("2 seconds") + const makeClient = yield* TestEntity.client + yield* Effect.sleep("3 seconds") + const client = makeClient("entity-1") + + const amount = BigDecimal.fromStringUnsafe("123.45") + yield* client.Process( + TestPayload.make({ id: "req-1", amount }), + { discard: true } + ) + }).pipe( + Effect.provide(makeClientLayer(RUNNER_PORT)), + Effect.scoped + ) + }).pipe(Effect.provide(SharedStorage)), + 30_000 + ) + + it.live( + "a reply serialization failure fails only its own request", + () => + Effect.gen(function*() { + yield* Layer.launch(makeRunnerLayer(ISOLATION_PORT, IsolationEntityLayer)).pipe(Effect.forkScoped) + yield* Effect.sleep("2 seconds") + + yield* Effect.gen(function*() { + const makeClient = yield* IsolationEntity.client + yield* Effect.sleep("3 seconds") + + const slowFiber = yield* makeClient("slow-entity").Slow().pipe(Effect.forkChild) + yield* Effect.sleep("300 millis") + + const badExit = yield* makeClient("bad-entity").BadReply({ id: 1 }).pipe(Effect.exit) + assert.isTrue(Exit.isFailure(badExit), "the unencodable reply must fail the request") + const failure = Exit.isFailure(badExit) ? String(Cause.squash(badExit.cause)) : "" + assert.include(failure, "MalformedMessage", "the caller must receive the real encode error") + assert.notInclude( + failure, + "AlreadyProcessingMessage", + "the request must not be re-sent into the entity's dedup guard" + ) + + const slowExit = yield* Fiber.await(slowFiber) + assert.isTrue( + Exit.isSuccess(slowExit), + "a sibling in-flight request on the same connection must be unaffected" + ) + if (Exit.isSuccess(slowExit)) { + assert.strictEqual(slowExit.value, "done") + } + }).pipe( + Effect.provide(makeClientLayer(ISOLATION_PORT)), + Effect.scoped + ) + }).pipe(Effect.provide(SharedStorage)), + 30_000 + ) +}) diff --git a/repos/effect/packages/platform-deno/test/fixtures/deno-terminal.ts b/repos/effect/packages/platform-deno/test/fixtures/deno-terminal.ts new file mode 100644 index 0000000000..3264499d16 --- /dev/null +++ b/repos/effect/packages/platform-deno/test/fixtures/deno-terminal.ts @@ -0,0 +1,127 @@ +import * as DenoStdio from "@effect/platform-deno/DenoStdio" +import * as DenoTerminal from "@effect/platform-deno/DenoTerminal" +import * as Effect from "effect/Effect" +import * as Exit from "effect/Exit" +import * as FileSystem from "effect/FileSystem" +import * as Layer from "effect/Layer" +import * as Option from "effect/Option" +import * as Path from "effect/Path" +import * as Queue from "effect/Queue" +import * as Stdio from "effect/Stdio" +import * as Stream from "effect/Stream" +import * as Terminal from "effect/Terminal" +import { Prompt } from "effect/unstable/cli" + +const TerminalLayer = Layer.mergeAll( + DenoStdio.layer, + DenoTerminal.layer, + FileSystem.layerNoop({}), + Path.layer +) + +const prompts = Effect.gen(function*() { + const first = yield* Prompt.run(Prompt.confirm({ message: "First" })) + const second = yield* Prompt.run(Prompt.confirm({ message: "Second" })).pipe(Effect.flip) + return { first, second: second._tag } +}) + +const readInput = Effect.scoped( + Effect.gen(function*() { + const terminal = yield* Terminal.Terminal + const input = yield* terminal.readInput + const first = yield* Queue.take(input) + const second = yield* Queue.take(input) + const end = yield* Effect.exit(Queue.take(input)) + return { + first: Option.getOrNull(first.input), + second: Option.getOrNull(second.input), + ended: Exit.isFailure(end) + } + }) +) + +const readLine = Effect.gen(function*() { + const terminal = yield* Terminal.Terminal + const first = yield* terminal.readLine + const second = yield* terminal.readLine.pipe(Effect.flip) + return { first, second: second._tag } +}) + +const readLines = Effect.gen(function*() { + const terminal = yield* Terminal.Terminal + const first = yield* terminal.readLine + const second = yield* terminal.readLine + return { first, second } +}) + +const unused = Effect.gen(function*() { + yield* Terminal.Terminal + return { dataListeners: process.stdin.listenerCount("data") } +}) + +const readLineAfterEnd = Effect.gen(function*() { + const terminal = yield* Terminal.Terminal + yield* Effect.callback((resume) => { + if (process.stdin.readableEnded) { + resume(Effect.void) + return + } + const onEnd = () => resume(Effect.void) + process.stdin.once("end", onEnd) + process.stdin.resume() + return Effect.sync(() => process.stdin.off("end", onEnd)) + }) + const error = yield* terminal.readLine.pipe(Effect.flip) + return error._tag +}) + +const readLineDisposed = Effect.gen(function*() { + const terminal = yield* Terminal.Terminal + const line = yield* terminal.readLine + const duringTtl = process.stdin.listenerCount("data") + yield* Effect.sleep("20 millis") + return { line, duringTtl, dataListeners: process.stdin.listenerCount("data") } +}) + +const interop = Effect.gen(function*() { + const stdio = yield* Stdio.Stdio + const terminal = yield* Terminal.Terminal + const line = yield* stdio.stdin.pipe( + Stream.decodeText(), + Stream.splitLines, + Stream.runHead + ) + yield* Effect.sync(() => process.stderr.write("READY\n")) + const terminalLine = yield* terminal.readLine + return { stdio: Option.getOrNull(line), terminal: terminalLine } +}) + +const mode = process.argv[2] +const program = Effect.gen(function*() { + if (mode === "prompts") { + return yield* prompts + } else if (mode === "read-input") { + return yield* readInput + } else if (mode === "read-line") { + return yield* readLine + } else if (mode === "read-lines") { + return yield* readLines + } else if (mode === "unused") { + return yield* unused + } else if (mode === "read-line-after-end") { + return yield* readLineAfterEnd + } else if (mode === "read-line-disposed") { + return yield* readLineDisposed + } else if (mode === "interop") { + return yield* interop + } + return yield* Effect.die(`Unknown mode: ${mode}`) +}) + +Effect.runPromise(program.pipe(Effect.provide(TerminalLayer))).then( + (result) => process.stderr.write(`RESULT ${JSON.stringify(result)}\n`), + (cause) => { + process.stderr.write(`ERROR ${String(cause)}\n`) + process.exitCode = 1 + } +) diff --git a/repos/effect/packages/platform-deno/test/fixtures/rpc-e2e.ts b/repos/effect/packages/platform-deno/test/fixtures/rpc-e2e.ts new file mode 100644 index 0000000000..78af7570a6 --- /dev/null +++ b/repos/effect/packages/platform-deno/test/fixtures/rpc-e2e.ts @@ -0,0 +1,142 @@ +import { assert, describe, it } from "@effect/vitest" +import { Cause, Context, Effect, Fiber, Option, Schedule, Stream } from "effect" +import * as Layer from "effect/Layer" +import * as RpcClient from "effect/unstable/rpc/RpcClient" +import type { RpcClientError } from "effect/unstable/rpc/RpcClientError" +import type * as RpcGroup from "effect/unstable/rpc/RpcGroup" +import * as RpcServer from "effect/unstable/rpc/RpcServer" +import * as RpcTest from "effect/unstable/rpc/RpcTest" +import { AuthClient, AuthLive, TimingLive, User, UserRpcs, UsersLive } from "./rpc-schemas.ts" + +export class UsersClient extends Context.Service< + UsersClient, + RpcClient.RpcClient, RpcClientError> +>()("UsersClient") { + static readonly layer = Layer.effect(UsersClient)(RpcClient.make(UserRpcs)).pipe( + Layer.provide(AuthClient) + ) + static layerTest = Layer.effect(UsersClient)(RpcTest.makeClient(UserRpcs)).pipe( + Layer.provide([UsersLive, AuthLive, TimingLive, AuthClient]) + ) +} + +export const e2eSuite = ( + name: string, + layer: Layer.Layer, + concurrent = true +) => { + describe(name, { concurrent, timeout: 30_000 }, () => { + it.effect("should get user", () => + Effect.gen(function*() { + const client = yield* UsersClient + const user = yield* client.GetUser({ id: "1" }) + assert.instanceOf(user, User) + assert.deepStrictEqual(user, new User({ id: "1", name: "Logged in user" })) + }).pipe(Effect.provide(layer))) + + it.effect("nested method", () => + Effect.gen(function*() { + const client = yield* UsersClient + yield* client["nested.test"]() + }).pipe(Effect.provide(layer))) + + it.effect("should not flatten Option", () => + Effect.gen(function*() { + const client = yield* UsersClient + const user = yield* client.GetUserOption({ id: "1" }) + assert.deepStrictEqual(user, Option.some(new User({ id: "1", name: "John" }))) + }).pipe(Effect.provide(layer))) + + it.effect("headers", () => + Effect.gen(function*() { + const client = yield* UsersClient + const user = yield* client.GetUser({ id: "1" }) + assert.instanceOf(user, User) + assert.deepStrictEqual(user, new User({ id: "123", name: "Logged in user" })) + }).pipe( + RpcClient.withHeaders({ userId: "123" }), + Effect.provide(layer) + )) + + it.live("Stream", () => + Effect.gen(function*() { + const client = yield* UsersClient + const users: Array = [] + const fiber = yield* client.StreamUsers({ id: "1" }).pipe( + Stream.take(5), + Stream.runForEach((user) => + Effect.sync(() => { + users.push(user) + }) + ), + Effect.forkChild + ) + yield* Fiber.join(fiber) + assert.lengthOf(users, 5) + + // test interrupts + const interrupts = yield* client.GetInterrupts() + assert.equal(interrupts, 1) + + const { supportsAck } = yield* RpcServer.Protocol + + // test backpressure + if (supportsAck) { + const emits = yield* client.GetEmits() + assert.equal(emits, 5) + } + }).pipe(Effect.provide(layer)), { timeout: 20000 }) + + it.effect("defect", () => + Effect.gen(function*() { + const client = yield* UsersClient + const cause = yield* client.ProduceDefect().pipe( + Effect.sandbox, + Effect.flip + ) + assert.deepStrictEqual(cause, Cause.die("boom")) + }).pipe( + RpcClient.withHeaders({ userId: "123" }), + Effect.provide(layer) + )) + + it.live("never", () => + Effect.gen(function*() { + const client = yield* UsersClient + const fiber = yield* client.Never().pipe( + Effect.forkChild + ) + yield* Effect.sleep(500) + assert.isUndefined(fiber.pollUnsafe()) + + yield* Fiber.interrupt(fiber) + + const { supportsAck } = yield* RpcServer.Protocol + if (supportsAck) { + const interrupts = yield* Effect.retry( + Effect.flatMap( + client.GetInterrupts(), + (interrupts) => interrupts === 1 ? Effect.succeed(interrupts) : Effect.fail(interrupts) + ), + { schedule: Schedule.spaced("10 millis"), times: 100 } + ) + assert.equal(interrupts, 1) + } + }).pipe( + RpcClient.withHeaders({ userId: "123" }), + Effect.provide(layer) + )) + + it.effect("timing middleware", () => + Effect.gen(function*() { + const client = yield* UsersClient + const result = yield* client.TimedMethod({ shouldFail: false }) + assert.equal(result, 1) + yield* client.TimedMethod({ shouldFail: true }).pipe(Effect.exit) + const { count, defect, success } = yield* client.GetTimingMiddlewareMetrics() + assert.notEqual(count, 0) + assert.notEqual(defect, 0) + assert.notEqual(success, 0) + }).pipe(Effect.provide(layer))) + }) +} diff --git a/repos/effect/packages/platform-deno/test/fixtures/rpc-schemas.ts b/repos/effect/packages/platform-deno/test/fixtures/rpc-schemas.ts new file mode 100644 index 0000000000..35e43a2afc --- /dev/null +++ b/repos/effect/packages/platform-deno/test/fixtures/rpc-schemas.ts @@ -0,0 +1,157 @@ +import { Context, Effect, Layer, Metric, Option, Queue, Schema } from "effect" +import * as Headers from "effect/unstable/http/Headers" +import * as Rpc from "effect/unstable/rpc/Rpc" +import * as RpcGroup from "effect/unstable/rpc/RpcGroup" +import * as RpcMiddleware from "effect/unstable/rpc/RpcMiddleware" +import * as RpcServer from "effect/unstable/rpc/RpcServer" + +export class User extends Schema.Class("User")({ + id: Schema.String, + name: Schema.String +}) {} + +class StreamUsers extends Rpc.make("StreamUsers", { + success: User, + payload: { + id: Schema.String + }, + stream: true +}) {} + +class CurrentUser extends Context.Service()("CurrentUser") {} + +class Unauthorized extends Schema.Error("Unauthorized")({ + _tag: Schema.tag("Unauthorized") +}) {} + +class AuthMiddleware extends RpcMiddleware.Service()("AuthMiddleware", { + error: Unauthorized, + requiredForClient: true +}) {} + +class TimingMiddleware extends RpcMiddleware.Service()("TimingMiddleware") {} + +class GetUser extends Rpc.make("GetUser", { + success: User, + payload: { id: Schema.String } +}) {} + +export const UserRpcs = RpcGroup.make( + GetUser, + Rpc.make("GetUserOption", { + success: Schema.Option(User), + payload: { id: Schema.String } + }), + StreamUsers, + Rpc.make("GetInterrupts", { + success: Schema.Number + }), + Rpc.make("GetEmits", { + success: Schema.Number + }), + Rpc.make("ProduceDefect"), + Rpc.make("Never"), + Rpc.make("nested.test"), + Rpc.make("TimedMethod", { + payload: { + shouldFail: Schema.Boolean + }, + success: Schema.Number + }).middleware(TimingMiddleware), + Rpc.make("GetTimingMiddlewareMetrics", { + success: Schema.Struct({ + success: Schema.Number, + defect: Schema.Number, + count: Schema.Number + }) + }) +).middleware(AuthMiddleware) + +export const AuthLive = Layer.succeed(AuthMiddleware)( + AuthMiddleware.of((effect, options) => + Effect.provideService( + effect, + CurrentUser, + new User({ id: options.headers.userid ?? "1", name: options.headers.name ?? "Fallback name" }) + ) + ) +) + +const rpcSuccesses = Metric.counter("rpc_middleware_success") +const rpcDefects = Metric.counter("rpc_middleware_defects") +const rpcCount = Metric.counter("rpc_middleware_count") +export const TimingLive = Layer.succeed(TimingMiddleware)( + TimingMiddleware.of((effect) => + effect.pipe( + Effect.tap(Metric.update(rpcSuccesses, 1)), + Effect.tapDefect(() => Metric.update(rpcDefects, 1)), + Effect.ensuring(Metric.update(rpcCount, 1)) + ) + ) +) + +export const UsersLive = UserRpcs.toLayer(Effect.gen(function*() { + let interrupts = 0 + let emits = 0 + return UserRpcs.of({ + GetUser: (_) => + CurrentUser.pipe( + Rpc.fork + ), + GetUserOption: Effect.fnUntraced(function*(req) { + return Option.some(new User({ id: req.id, name: "John" })) + }), + StreamUsers: Effect.fnUntraced(function*(req, _) { + const mailbox = yield* Queue.bounded(0) + + yield* Effect.addFinalizer(() => + Effect.sync(() => { + interrupts++ + }) + ) + + yield* Queue.offer(mailbox, new User({ id: req.id, name: "John" })).pipe( + Effect.tap(() => + Effect.sync(() => { + emits++ + }) + ), + Effect.delay(100), + Effect.forever, + Effect.forkScoped + ) + + return mailbox + }), + GetInterrupts: () => Effect.sync(() => interrupts), + GetEmits: () => Effect.sync(() => emits), + ProduceDefect: () => Effect.die("boom"), + Never: () => Effect.never.pipe(Effect.onInterrupt(() => Effect.sync(() => interrupts++))), + "nested.test": () => Effect.void, + TimedMethod: (_) => _.shouldFail ? Effect.die("boom") : Effect.succeed(1), + GetTimingMiddlewareMetrics: () => + Effect.all({ + defect: Metric.value(rpcDefects).pipe(Effect.map((_) => _.count)), + success: Metric.value(rpcSuccesses).pipe(Effect.map((_) => _.count)), + count: Metric.value(rpcCount).pipe(Effect.map((_) => _.count)) + }) + }) +})) + +export const RpcLive = RpcServer.layer(UserRpcs, { + disableFatalDefects: true +}).pipe( + Layer.provide([ + UsersLive, + AuthLive, + TimingLive + ]) +) + +export const AuthClient = RpcMiddleware.layerClient(AuthMiddleware, ({ next, request }) => + next({ + ...request, + headers: Headers.set(request.headers, "name", "Logged in user") + })) diff --git a/repos/effect/packages/platform-deno/test/fixtures/rpc-worker.ts b/repos/effect/packages/platform-deno/test/fixtures/rpc-worker.ts new file mode 100644 index 0000000000..2282c4bed7 --- /dev/null +++ b/repos/effect/packages/platform-deno/test/fixtures/rpc-worker.ts @@ -0,0 +1,12 @@ +import * as DenoWorkerRunner from "@effect/platform-deno/DenoWorkerRunner" +import * as Effect from "effect/Effect" +import * as Layer from "effect/Layer" +import * as RpcServer from "effect/unstable/rpc/RpcServer" +import { RpcLive } from "./rpc-schemas.ts" + +const MainLive = RpcLive.pipe( + Layer.provide(RpcServer.layerProtocolWorkerRunner), + Layer.provide(DenoWorkerRunner.layer) +) + +Effect.runFork(Layer.launch(MainLive)) diff --git a/repos/effect/packages/platform-deno/test/fixtures/text.txt b/repos/effect/packages/platform-deno/test/fixtures/text.txt new file mode 100644 index 0000000000..72b190ee61 --- /dev/null +++ b/repos/effect/packages/platform-deno/test/fixtures/text.txt @@ -0,0 +1 @@ +lorem ipsum dolar sit amet diff --git a/repos/effect/packages/platform-deno/test/internal/error.test.ts b/repos/effect/packages/platform-deno/test/internal/error.test.ts new file mode 100644 index 0000000000..991974ed99 --- /dev/null +++ b/repos/effect/packages/platform-deno/test/internal/error.test.ts @@ -0,0 +1,39 @@ +import { assert, describe, it } from "@effect/vitest" +import { SystemError, type SystemErrorTag } from "effect/PlatformError" +import { handleError } from "../../src/internal/error.ts" + +const withCode = (error: Error, code: string): Error & { readonly code: string } => Object.assign(error, { code }) + +describe("handleError", () => { + const mapError = handleError("FileSystem", "test", "/tmp/test") + const cases: ReadonlyArray = [ + [withCode(new Deno.errors.NotFound(), "ENOENT"), "NotFound"], + [withCode(new Deno.errors.NotADirectory(), "ENOTDIR"), "BadResource"], + [new Deno.errors.AlreadyExists(), "AlreadyExists"], + [withCode(new Deno.errors.AlreadyExists(), "EEXIST"), "AlreadyExists"], + [withCode(new Deno.errors.IsADirectory(), "EISDIR"), "BadResource"], + [withCode(new Deno.errors.PermissionDenied(), "EACCES"), "PermissionDenied"], + [new Deno.errors.NotCapable(), "PermissionDenied"], + [new Deno.errors.BadResource(), "BadResource"], + [new Deno.errors.InvalidData(), "InvalidData"], + [new Deno.errors.TimedOut(), "TimedOut"], + [new Deno.errors.UnexpectedEof(), "UnexpectedEof"], + [new Deno.errors.WouldBlock(), "WouldBlock"], + [new Deno.errors.WriteZero(), "WriteZero"], + [new Error("unrecognised"), "Unknown"] + ] + + for (const [error, tag] of cases) { + it(`maps ${error.name} to ${tag}`, () => { + const platformError = mapError(error) + const reason = platformError.reason + + assert(reason instanceof SystemError) + assert.strictEqual(reason._tag, tag) + assert.strictEqual(reason.module, "FileSystem") + assert.strictEqual(reason.method, "test") + assert.strictEqual(reason.pathOrDescriptor, "/tmp/test") + assert.strictEqual(reason.cause, error) + }) + } +}) diff --git a/repos/effect/packages/platform-deno/tsconfig.json b/repos/effect/packages/platform-deno/tsconfig.json new file mode 100644 index 0000000000..14d4c72786 --- /dev/null +++ b/repos/effect/packages/platform-deno/tsconfig.json @@ -0,0 +1,12 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "../../tsconfig.base.json", + "include": ["src"], + "references": [ + { "path": "../effect" }, + { "path": "../platform-node-shared" } + ], + "compilerOptions": { + "types": ["deno"] + } +} diff --git a/repos/effect/packages/platform-node-shared/CHANGELOG.md b/repos/effect/packages/platform-node-shared/CHANGELOG.md index 3829bf5336..86ffcba6d7 100644 --- a/repos/effect/packages/platform-node-shared/CHANGELOG.md +++ b/repos/effect/packages/platform-node-shared/CHANGELOG.md @@ -1,5 +1,156 @@ # @effect/platform-node-shared +## 4.0.0-beta.107 + +### Patch Changes + +- [#7154](https://github.com/Effect-TS/effect/pull/7154) [`e74c302`](https://github.com/Effect-TS/effect/commit/e74c302afe0368e5d3f15d18c10fc54cf33f9003) Thanks @CDVolvik! - 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. +- 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 + +- [#7106](https://github.com/Effect-TS/effect/pull/7106) [`5f1775c`](https://github.com/Effect-TS/effect/commit/5f1775cb060cb3dbf96adb067fd97967da7eca2f) Thanks @tim-smart! - Keep NodeTerminal's readline interface alive briefly between adjacent prompts to avoid a Windows TTY raw-mode hang. + +- [#7143](https://github.com/Effect-TS/effect/pull/7143) [`089313e`](https://github.com/Effect-TS/effect/commit/089313ec2a4c307393c7c5e00c725ec23840c9c1) Thanks @fubhy! - Fix `NodeStream.toString` registering a duplicate `error` event listener. +- 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 + +- [#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. +- 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 + +### 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 + +- [#7073](https://github.com/Effect-TS/effect/pull/7073) [`e2ec131`](https://github.com/Effect-TS/effect/commit/e2ec1311bed9bb8709c26396e71b15b4241a9185) Thanks @fubhy! - Kill every process in a Node child process pipeline when killing its aggregate handle. + +- [#7067](https://github.com/Effect-TS/effect/pull/7067) [`e508589`](https://github.com/Effect-TS/effect/commit/e50858905fed68f29ec202ecdc9c902e44bfedd8) Thanks @fubhy! - Close pending TCP and WebSocket connections when a scoped socket server shuts down +- 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 + +- [#6715](https://github.com/Effect-TS/effect/pull/6715) [`75f340e`](https://github.com/Effect-TS/effect/commit/75f340ef3d6dc350337ff6ff930ae533046a969f) Thanks @tim-smart! - Add a Deno `Terminal` implementation and keep `NodeTerminal` input readers alive until stdin ends under Deno. + +- [#6914](https://github.com/Effect-TS/effect/pull/6914) [`0b4a3c4`](https://github.com/Effect-TS/effect/commit/0b4a3c4b1b1f1ad010fcc15cd9bb2fb39261c380) Thanks @spencerbeggs! - NodePath: `layerPosix` and `layerWin32` now convert between paths and `file:` URLs using their own platform flavor instead of the host's. + +- [#6679](https://github.com/Effect-TS/effect/pull/6679) [`9f02491`](https://github.com/Effect-TS/effect/commit/9f02491ae33eb930071dd7b81ee3f9683b2d6900) Thanks @tim-smart! - NodeTerminal: preserve buffered input across sequential `readLine` calls + +- [#6676](https://github.com/Effect-TS/effect/pull/6676) [`381c141`](https://github.com/Effect-TS/effect/commit/381c14141bc9bad437d77a98f6beb4d547cbaa75) Thanks @chenxin-yan! - NodeTerminal: end key input and fail `readLine` with `QuitError` at stdin EOF instead of hanging + +- [#6701](https://github.com/Effect-TS/effect/pull/6701) [`9867b9f`](https://github.com/Effect-TS/effect/commit/9867b9fc69f9cc6c443594fc7eccc7be0c674d9c) Thanks @fubhy! - Removed explicit ./index entrypoints + +- [#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 + +- [#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. + +- 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 + +- [#6411](https://github.com/Effect-TS/effect/pull/6411) [`b330264`](https://github.com/Effect-TS/effect/commit/b3302645fc1c23f7d03a3693dabdcb88763d5cbd) Thanks @sbking! - Fix child process termination to escalate to `SIGKILL` when the initial signal does not stop the process within `forceKillAfter`. + +- 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 + +- [#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 + +- Updated dependencies [[`95a0e9b`](https://github.com/Effect-TS/effect-smol/commit/95a0e9bb62797af0e81c9998773405f248f218c5), [`a0a3490`](https://github.com/Effect-TS/effect-smol/commit/a0a3490bbce765f199d8e077aceac504f0462e63), [`f11ce73`](https://github.com/Effect-TS/effect-smol/commit/f11ce73af60823754dc24194f4ffc561b9ea1c2d), [`ff30b6e`](https://github.com/Effect-TS/effect-smol/commit/ff30b6e7c2c63ffc56a4c5818d6d86b01b5ad528), [`1caab3c`](https://github.com/Effect-TS/effect-smol/commit/1caab3cc30f626efbf15e59d74f539a487e5c85c), [`aa80c47`](https://github.com/Effect-TS/effect-smol/commit/aa80c4775a04db87553e5568764cab7e32a72814), [`c2ae4fc`](https://github.com/Effect-TS/effect-smol/commit/c2ae4fce2f03a4cd1861c2b1179da7df656e662d), [`a0a3490`](https://github.com/Effect-TS/effect-smol/commit/a0a3490bbce765f199d8e077aceac504f0462e63)]: + - effect@4.0.0-beta.94 + +## 4.0.0-beta.93 + +### Patch Changes + +- Updated dependencies [[`00652fe`](https://github.com/Effect-TS/effect-smol/commit/00652fe95c18f87208e91343eb8bf218faa2f677), [`6c58167`](https://github.com/Effect-TS/effect-smol/commit/6c5816746eaf91d2a3c7c899c5720809fa230ae3), [`2bc5415`](https://github.com/Effect-TS/effect-smol/commit/2bc541501a7ef89e542d7cb98e96beb53cd205cc), [`e11cccc`](https://github.com/Effect-TS/effect-smol/commit/e11cccc7d5fe631abccc7d6e3bd296938de0fa2e), [`ba7e77e`](https://github.com/Effect-TS/effect-smol/commit/ba7e77e046b8641a3a4e9750bb88ca4a1d063d3f), [`5713ee7`](https://github.com/Effect-TS/effect-smol/commit/5713ee7edbc3054efde407b2286bbfd45bbc6e1c)]: + - effect@4.0.0-beta.93 + +## 4.0.0-beta.92 + +### Patch Changes + +- Updated dependencies [[`affdc13`](https://github.com/Effect-TS/effect-smol/commit/affdc139045cc325dce321a84a580fdc1b2da7b9)]: + - effect@4.0.0-beta.92 + +## 4.0.0-beta.91 + +### Patch Changes + +- Updated dependencies [[`b135b25`](https://github.com/Effect-TS/effect-smol/commit/b135b2517fca9e7839734ace3699a7dfa75b9075), [`aaa21a3`](https://github.com/Effect-TS/effect-smol/commit/aaa21a369a171c600db294f2a4f640583043e150), [`3475ee6`](https://github.com/Effect-TS/effect-smol/commit/3475ee6c2bda6b05c6d7a12ce30c8bb840b5b1a6)]: + - effect@4.0.0-beta.91 + ## 4.0.0-beta.90 ### Patch Changes diff --git a/repos/effect/packages/platform-node-shared/README.md b/repos/effect/packages/platform-node-shared/README.md index 8df2065481..52425e592e 100644 --- a/repos/effect/packages/platform-node-shared/README.md +++ b/repos/effect/packages/platform-node-shared/README.md @@ -1,7 +1,7 @@ -# `@effect/platform-node` +# `@effect/platform-node-shared` -Provides Node.js-specific implementations for the abstractions defined in [`@effect/platform`](https://github.com/Effect-TS/effect/tree/main/packages/platform), allowing you to write platform-independent code that integrates smoothly with Node.js. +Provides shared Node.js-compatible implementations used by the Effect Node.js and Bun platform packages. ## Documentation -- **API Reference**: [View the full documentation](https://effect-ts.github.io/effect/docs/platform-node). +- **API Reference**: [View the full documentation](https://effect-ts.github.io/effect/docs/platform-node-shared). diff --git a/repos/effect/packages/platform-node-shared/docgen.json b/repos/effect/packages/platform-node-shared/docgen.json deleted file mode 100644 index 0c60248f18..0000000000 --- a/repos/effect/packages/platform-node-shared/docgen.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "$schema": "../../node_modules/@effect/docgen/schema.json", - "srcLink": "https://github.com/Effect-TS/effect/tree/main/packages/platform-node/src/", - "exclude": ["src/internal/**/*.ts"], - "tscExecutable": "tsgo", - "examplesCompilerOptions": { - "noEmit": true, - "strict": true, - "skipLibCheck": true, - "moduleResolution": "Bundler", - "module": "ES2022", - "target": "ES2022", - "lib": ["ES2022", "DOM", "DOM.Iterable"], - "rewriteRelativeImportExtensions": true, - "allowImportingTsExtensions": true, - "paths": { - "effect": ["../../../effect/src/index.js"], - "effect/*": ["../../../effect/src/*.js"], - "@effect/cluster": ["../../../cluster/src/index.js"], - "@effect/cluster/*": ["../../../cluster/src/*.js"], - "@effect/experimental": ["../../../experimental/src/index.js"], - "@effect/experimental/*": ["../../../experimental/src/*.js"], - "@effect/platform": ["../../../platform/src/index.js"], - "@effect/platform/*": ["../../../platform/src/*.js"], - "@effect/platform-node": ["../../../platform-node/src/index.js"], - "@effect/platform-node/*": ["../../../platform-node/src/*.js"], - "@effect/platform-node-shared": ["../../../platform-node-shared/src/index.js"], - "@effect/platform-node-shared/*": ["../../../platform-node-shared/src/*.js"], - "@effect/rpc": ["../../../rpc/src/index.js"], - "@effect/rpc/*": ["../../../rpc/src/*.js"], - "effect/unstable/sql": ["../../../sql/src/index.js"], - "effect/unstable/sql/*": ["../../../sql/src/*.js"] - }, - "plugins": [ - { "name": "@effect/language-service", "includeSuggestionsInTsc": false } - ] - } -} diff --git a/repos/effect/packages/platform-node-shared/package.json b/repos/effect/packages/platform-node-shared/package.json index b350b9471a..0bcf5e7254 100644 --- a/repos/effect/packages/platform-node-shared/package.json +++ b/repos/effect/packages/platform-node-shared/package.json @@ -1,17 +1,17 @@ { "name": "@effect/platform-node-shared", "type": "module", - "version": "4.0.0-beta.90", + "version": "4.0.0-beta.107", "license": "MIT", "description": "Unified interfaces for common platform-specific services", "homepage": "https://effect.website", "repository": { "type": "git", - "url": "https://github.com/Effect-TS/effect-smol.git", + "url": "https://github.com/Effect-TS/effect.git", "directory": "packages/platform-node-shared" }, "bugs": { - "url": "https://github.com/Effect-TS/effect-smol/issues" + "url": "https://github.com/Effect-TS/effect/issues" }, "tags": [ "typescript", @@ -32,6 +32,7 @@ ".": "./src/index.ts", "./*": "./src/*.ts", "./internal/*": null, + "./index": null, "./*/index": null }, "files": [ @@ -39,7 +40,10 @@ "dist/**/*.js", "dist/**/*.js.map", "dist/**/*.d.ts", - "dist/**/*.d.ts.map" + "dist/**/*.d.ts.map", + "AGENTS.md", + "CLAUDE.md", + "ai-docs/**/*" ], "publishConfig": { "access": "public", @@ -49,28 +53,26 @@ ".": "./dist/index.js", "./*": "./dist/*.js", "./internal/*": null, + "./index": null, "./*/index": null } }, "scripts": { "codegen": "effect-utils codegen", "build": "tsc -b tsconfig.json && pnpm babel", - "build:tsgo": "tsgo -b tsconfig.json && pnpm babel", "babel": "babel dist --plugins annotate-pure-calls --out-dir dist --source-maps", - "check": "tsc -b tsconfig.json", - "test": "vitest", - "coverage": "vitest --coverage" + "check": "tsc -b tsconfig.json" }, "peerDependencies": { "effect": "workspace:^" }, "devDependencies": { - "@types/node": "^25.7.0", + "@types/node": "^26.1.2", "effect": "workspace:^", - "tar": "^7.5.15" + "tar": "^7.5.19" }, "dependencies": { "@types/ws": "^8.18.1", - "ws": "^8.20.0" + "ws": "^8.21.0" } } diff --git a/repos/effect/packages/platform-node-shared/src/NodeChildProcessSpawner.ts b/repos/effect/packages/platform-node-shared/src/NodeChildProcessSpawner.ts index 142c463151..38299e217c 100644 --- a/repos/effect/packages/platform-node-shared/src/NodeChildProcessSpawner.ts +++ b/repos/effect/packages/platform-node-shared/src/NodeChildProcessSpawner.ts @@ -39,6 +39,8 @@ import { ProcessId } from "effect/unstable/process/ChildProcessSpawner" import * as NodeChildProcess from "node:child_process" +import { PassThrough } from "node:stream" +import { buildSpawnOptions } from "./internal/nodeChildProcessSpawner.ts" import { handleErrnoException } from "./internal/utils.ts" import * as NodeSink from "./NodeSink.ts" import * as NodeStream from "./NodeStream.ts" @@ -64,6 +66,17 @@ const toPlatformError = ( type ExitCodeWithSignal = readonly [code: number | null, signal: NodeJS.Signals | null] type ExitSignal = Deferred.Deferred +const taskkill = ( + childProcess: NodeChildProcess.ChildProcess, + onExit: (error: NodeChildProcess.ExecException | null) => void = () => {} +) => + NodeChildProcess.execFile( + "taskkill", + ["/pid", String(childProcess.pid!), "/T", "/F"], + { windowsHide: true }, + onExit + ) + const make = Effect.gen(function*() { const fs = yield* FileSystem.FileSystem const path = yield* Path.Path @@ -220,8 +233,11 @@ const make = Effect.gen(function*() { // Create a stream to read from for output file descriptors let stream: Stream.Stream = Stream.empty if (nodeStream && "read" in nodeStream) { + const passThrough = new PassThrough() + nodeStream.on("error", (error) => passThrough.destroy(error)) + nodeStream.pipe(passThrough) stream = NodeStream.fromReadable({ - evaluate: () => nodeStream, + evaluate: () => passThrough, onError: (error) => toPlatformError(`fromReadable(fd${fd})`, toError(error), command) }) } @@ -281,16 +297,26 @@ const make = Effect.gen(function*() { all: Stream.Stream } => { let stdout = childProcess.stdout ? - NodeStream.fromReadable({ - evaluate: () => childProcess.stdout!, - onError: (error) => toPlatformError("fromReadable(stdout)", toError(error), command) - }) : + (() => { + const passThrough = new PassThrough() + childProcess.stdout!.on("error", (error) => passThrough.destroy(error)) + childProcess.stdout!.pipe(passThrough) + return NodeStream.fromReadable({ + evaluate: () => passThrough, + onError: (error) => toPlatformError("fromReadable(stdout)", toError(error), command) + }) + })() : Stream.empty let stderr = childProcess.stderr ? - NodeStream.fromReadable({ - evaluate: () => childProcess.stderr!, - onError: (error) => toPlatformError("fromReadable(stderr)", toError(error), command) - }) : + (() => { + const passThrough = new PassThrough() + childProcess.stderr!.on("error", (error) => passThrough.destroy(error)) + childProcess.stderr!.pipe(passThrough) + return NodeStream.fromReadable({ + evaluate: () => passThrough, + onError: (error) => toPlatformError("fromReadable(stderr)", toError(error), command) + }) + })() : Stream.empty if (Sink.isSink(stdoutConfig.stream)) { @@ -340,7 +366,7 @@ const make = Effect.gen(function*() { ) => { if (globalThis.process.platform === "win32") { return Effect.callback((resume) => { - NodeChildProcess.exec(`taskkill /pid ${childProcess.pid} /T /F`, (error) => { + taskkill(childProcess, (error) => { if (error) { resume(Effect.fail(toPlatformError("kill", toError(error), command))) } else { @@ -362,9 +388,8 @@ const make = Effect.gen(function*() { signal: NodeJS.Signals ): void => { if (globalThis.process.platform === "win32") { - NodeChildProcess.exec(`taskkill /pid ${childProcess.pid} /T /F`, () => { - // ignore errors during best-effort cleanup - }) + // ignore errors during best-effort cleanup + taskkill(childProcess) return } try { @@ -451,20 +476,13 @@ const make = Effect.gen(function*() { const stderrConfig = resolveOutputOption(cmd.options, "stderr") const resolvedAdditionalFds = resolveAdditionalFds(cmd.options) let isReferenced = true - let cleanupOnNonZeroExit = false const cwd = yield* resolveWorkingDirectory(cmd.options) const env = resolveEnvironment(cmd.options) const stdio = buildStdioArray(stdinConfig, stdoutConfig, stderrConfig, resolvedAdditionalFds) const [childProcess, exitSignal] = yield* Effect.acquireRelease( - spawn(cmd, { - cwd, - env, - stdio, - detached: cmd.options.detached ?? process.platform !== "win32", - shell: cmd.options.shell - }), + spawn(cmd, buildSpawnOptions(cmd.options, { cwd, env, stdio }, process.platform)), Effect.fnUntraced(function*([childProcess, exitSignal]) { const exited = yield* Deferred.isDone(exitSignal) const killWithTimeout = withTimeout(childProcess, cmd, cmd.options) @@ -482,12 +500,11 @@ const make = Effect.gen(function*() { } // Process is still running, kill it return yield* killWithTimeout((command, childProcess, signal) => - Effect.catch( - killProcessGroup(command, childProcess, signal), - () => killProcess(command, childProcess, signal) + killProcessGroup(command, childProcess, signal).pipe( + Effect.catch(() => killProcess(command, childProcess, signal)), + Effect.andThen(Deferred.await(exitSignal)) ) ).pipe( - Effect.andThen(Deferred.await(exitSignal)), Effect.ignore ) }) @@ -495,7 +512,7 @@ const make = Effect.gen(function*() { const pid = ProcessId(childProcess.pid!) childProcess.on("exit", (code) => { - if (cleanupOnNonZeroExit && code !== 0 && Predicate.isNotNull(code)) { + if (code !== 0 && Predicate.isNotNull(code)) { killProcessGroupOnExit(childProcess, cmd.options.killSignal ?? "SIGTERM") } }) @@ -503,14 +520,12 @@ const make = Effect.gen(function*() { if (!isReferenced) { childProcess.ref() isReferenced = true - cleanupOnNonZeroExit = false } }) const unref = Effect.sync(() => { if (isReferenced) { childProcess.unref() isReferenced = false - cleanupOnNonZeroExit = true } return reref }) @@ -531,12 +546,11 @@ const make = Effect.gen(function*() { const kill = (options?: ChildProcess.KillOptions | undefined) => { const killWithTimeout = withTimeout(childProcess, cmd, options) return killWithTimeout((command, childProcess, signal) => - Effect.catch( - killProcessGroup(command, childProcess, signal), - () => killProcess(command, childProcess, signal) + killProcessGroup(command, childProcess, signal).pipe( + Effect.catch(() => killProcess(command, childProcess, signal)), + Effect.andThen(Deferred.await(exitSignal)) ) ).pipe( - Effect.andThen(Deferred.await(exitSignal)), Effect.asVoid ) } @@ -610,6 +624,8 @@ const make = Effect.gen(function*() { } const handle = handles[handles.length - 1] + const kill = (options?: ChildProcess.KillOptions | undefined) => + Effect.forEach([...handles].reverse(), (handle) => Effect.ignore(handle.kill(options)), { discard: true }) const unref = Effect.gen(function*() { const rerefs: Array> = [] for (const handle of handles) { @@ -622,7 +638,7 @@ const make = Effect.gen(function*() { pid: handle.pid, exitCode: handle.exitCode, isRunning: handle.isRunning, - kill: handle.kill, + kill, stdin: handle.stdin, stdout: handle.stdout, stderr: handle.stderr, diff --git a/repos/effect/packages/platform-node-shared/src/NodeFileSystem.ts b/repos/effect/packages/platform-node-shared/src/NodeFileSystem.ts index babba4a845..8be146df34 100644 --- a/repos/effect/packages/platform-node-shared/src/NodeFileSystem.ts +++ b/repos/effect/packages/platform-node-shared/src/NodeFileSystem.ts @@ -101,6 +101,21 @@ const chown = (() => { return (path: string, uid: number, gid: number) => nodeChown(path, uid, gid) })() +// == glob + +const glob = ((): FileSystem.FileSystem["glob"] => { + const nodeGlob = effectify( + NFS.glob, + handleErrnoException("FileSystem", "glob"), + handleBadArgument("glob") + ) + return (pattern: string, options) => + nodeGlob(pattern, { + cwd: options?.root, + exclude: options?.exclude + }) +})() + // == link const link = (() => { @@ -195,7 +210,7 @@ const openFactory = (method: string): FileSystem.FileSystem["open"] => { nodeOpen(path, options?.flag ?? "r", options?.mode), (fd) => Effect.orDie(nodeClose(fd)) ), - Effect.map((fd) => makeFile(FileSystem.FileDescriptor(fd), options?.flag?.startsWith("a") ?? false)) + Effect.map((fd) => makeFile(fd, options?.flag?.startsWith("a") ?? false)) ) } const open = openFactory("open") @@ -237,13 +252,13 @@ const makeFile = (() => { class FileImpl implements FileSystem.File { readonly [FileSystem.FileTypeId]: typeof FileSystem.FileTypeId - readonly fd: FileSystem.File.Descriptor + readonly fd: number private readonly append: boolean private position: bigint = BigInt(0) constructor( - fd: FileSystem.File.Descriptor, + fd: number, append: boolean ) { this[FileSystem.FileTypeId] = FileSystem.FileTypeId @@ -268,7 +283,7 @@ const makeFile = (() => { this.position = this.position + offsetSize } - return this.position + return FileSystem.Size(this.position) }) } @@ -371,7 +386,7 @@ const makeFile = (() => { } } - return (fd: FileSystem.File.Descriptor, append: boolean): FileSystem.File => new FileImpl(fd, append) + return (fd: number, append: boolean): FileSystem.File => new FileImpl(fd, append) })() // == makeTempFile @@ -535,12 +550,12 @@ const utimes = (() => { // == watch -const watchNode = (path: string) => +const watchNode = (path: string, options?: FileSystem.WatchOptions) => Stream.callback((queue) => Effect.acquireRelease( Effect.sync(() => { const watcher = NFS.watch(path, { - recursive: true + recursive: options?.recursive ?? false }, (event, path) => { if (!path) return switch (event) { @@ -580,12 +595,16 @@ const watchNode = (path: string) => ) ) -const watch = (backend: Option.Option, path: string) => +const watch = ( + backend: Option.Option, + path: string, + options?: FileSystem.WatchOptions +) => stat(path).pipe( Effect.map((stat) => backend.pipe( - Option.flatMap((_) => _.register(path, stat)), - Option.getOrElse(() => watchNode(path)) + Option.flatMap((_) => _.register(path, stat, options)), + Option.getOrElse(() => watchNode(path, options)) ) ), Stream.unwrap @@ -619,6 +638,7 @@ const makeFileSystem = Effect.map(Effect.serviceOption(FileSystem.WatchBackend), chown, copy, copyFile, + glob, link, makeDirectory, makeTempDirectory, @@ -636,8 +656,8 @@ const makeFileSystem = Effect.map(Effect.serviceOption(FileSystem.WatchBackend), symlink, truncate, utimes, - watch(path) { - return watch(backend, path) + watch(path, options) { + return watch(backend, path, options) }, writeFile })) diff --git a/repos/effect/packages/platform-node-shared/src/NodeHttpCompression.ts b/repos/effect/packages/platform-node-shared/src/NodeHttpCompression.ts new file mode 100644 index 0000000000..009fa8f616 --- /dev/null +++ b/repos/effect/packages/platform-node-shared/src/NodeHttpCompression.ts @@ -0,0 +1,153 @@ +/** + * HTTP response compression backed by `node:zlib`, shared by the Node.js, Bun, + * and Deno platforms. + * + * Byte-array bodies are compressed in one shot with the asynchronous + * `node:zlib` APIs, preserving an exact `Content-Length`. Streaming bodies go + * through `node:zlib` transform streams that flush each input chunk. + * + * @since 4.0.0 + */ +import * as Effect from "effect/Effect" +import * as HttpBody from "effect/unstable/http/HttpBody" +import type * as Platform from "effect/unstable/http/HttpPlatform" +import * as Response from "effect/unstable/http/HttpServerResponse" +import type { Duplex } from "node:stream" +import { Readable } from "node:stream" +import * as Zlib from "node:zlib" + +/** + * The compression algorithms supported by the runtime's `node:zlib`. `zstd` + * requires Node.js 22.15 or newer. + * + * @category constants + * @since 4.0.0 + */ +export const algorithms: ReadonlySet = new Set( + typeof Zlib.zstdCompress === "function" + ? ["gzip", "deflate", "br", "zstd"] + : ["gzip", "deflate", "br"] +) + +const brotliParams = (level: number | undefined, sizeHint?: number): Zlib.BrotliOptions => { + const params: Record = {} + if (level !== undefined) { + params[Zlib.constants.BROTLI_PARAM_QUALITY] = level + } + if (sizeHint !== undefined) { + params[Zlib.constants.BROTLI_PARAM_SIZE_HINT] = sizeHint + } + return { params } +} + +const zstdParams = (level: number | undefined): Zlib.ZstdOptions | undefined => + level === undefined || level === 3 ? undefined : { params: { [Zlib.constants.ZSTD_c_compressionLevel]: level } } + +const compress = ( + data: Uint8Array, + algorithm: Platform.CompressionAlgorithm, + options?: Platform.CompressionOptions | undefined +): Effect.Effect => + Effect.callback((resume) => { + const complete = (error: Error | null, result: Uint8Array) => + resume(error === null ? Effect.succeed(result) : Effect.die(error)) + switch (algorithm) { + case "gzip": { + Zlib.gzip(data, { level: options?.level }, complete) + break + } + case "deflate": { + Zlib.deflate(data, { level: options?.level }, complete) + break + } + case "br": { + Zlib.brotliCompress(data, brotliParams(options?.level, data.byteLength), complete) + break + } + case "zstd": { + const params = zstdParams(options?.level) + if (params === undefined) { + Zlib.zstdCompress(data, complete) + } else { + Zlib.zstdCompress(data, params, complete) + } + break + } + } + }) + +/** + * Creates a `Compression` that compresses byte-array bodies in one shot with + * the asynchronous `node:zlib` APIs, setting the exact `Content-Length` of the + * compressed body. All other bodies are delegated to `fallback`. + * + * @category constructors + * @since 4.0.0 + */ +export const make = (fallback: Platform.Compression): Platform.Compression => ({ + algorithms: fallback.algorithms, + compressResponse(response, algorithm, options) { + const body = response.body + if (body._tag !== "Uint8Array") { + return fallback.compressResponse(response, algorithm, options) + } + return Effect.map(compress(body.body, algorithm, options), (result) => + Response.setHeader( + Response.setBody(response, HttpBody.uint8Array(result, body.contentType)), + "content-length", + result.byteLength.toString() + )) + } +}) + +/** + * Creates a `node:zlib` compression transform stream that flushes each input + * chunk, for streaming response bodies. + * + * @category constructors + * @since 4.0.0 + */ +export const compressTransform = ( + algorithm: Platform.CompressionAlgorithm, + options?: Platform.CompressionOptions | undefined +): Duplex => { + switch (algorithm) { + case "gzip": { + return Zlib.createGzip({ level: options?.level, flush: Zlib.constants.Z_SYNC_FLUSH }) + } + case "deflate": { + return Zlib.createDeflate({ level: options?.level, flush: Zlib.constants.Z_SYNC_FLUSH }) + } + case "br": { + return Zlib.createBrotliCompress({ + ...brotliParams(options?.level), + flush: Zlib.constants.BROTLI_OPERATION_FLUSH + }) + } + case "zstd": { + return Zlib.createZstdCompress({ + ...zstdParams(options?.level), + flush: Zlib.constants.ZSTD_e_flush + }) + } + } +} + +/** + * A Web `ReadableStream` version of `compressTransform`, for platforms that + * stream response bodies as Web streams. + * + * @category constructors + * @since 4.0.0 + */ +export const compressTransformWeb = ( + algorithm: Platform.CompressionAlgorithm, + options?: Platform.CompressionOptions | undefined +) => +(stream: ReadableStream): ReadableStream => { + const transform = compressTransform(algorithm, options) + const source = Readable.fromWeb(stream as any) + source.on("error", (cause) => transform.destroy(cause)) + transform.on("close", () => source.destroy()) + return Readable.toWeb(source.pipe(transform)) as unknown as ReadableStream +} diff --git a/repos/effect/packages/platform-node-shared/src/NodePath.ts b/repos/effect/packages/platform-node-shared/src/NodePath.ts index c0dc5a43b3..2a29c60f4b 100644 --- a/repos/effect/packages/platform-node-shared/src/NodePath.ts +++ b/repos/effect/packages/platform-node-shared/src/NodePath.ts @@ -15,27 +15,28 @@ import { BadArgument } from "effect/PlatformError" import * as NodePath from "node:path" import * as NodeUrl from "node:url" -const fromFileUrl = (url: URL): Effect.Effect => - Effect.try({ - try: () => NodeUrl.fileURLToPath(url), - catch: (cause) => - new BadArgument({ - module: "Path", - method: "fromFileUrl", - cause - }) - }) - -const toFileUrl = (path: string): Effect.Effect => - Effect.try({ - try: () => NodeUrl.pathToFileURL(path), - catch: (cause) => - new BadArgument({ - module: "Path", - method: "toFileUrl", - cause - }) - }) +const fileUrlOps = (windows: boolean | undefined) => ({ + fromFileUrl: (url: URL): Effect.Effect => + Effect.try({ + try: () => NodeUrl.fileURLToPath(url, { windows }), + catch: (cause) => + new BadArgument({ + module: "Path", + method: "fromFileUrl", + cause + }) + }), + toFileUrl: (path: string): Effect.Effect => + Effect.try({ + try: () => NodeUrl.pathToFileURL(path, { windows }), + catch: (cause) => + new BadArgument({ + module: "Path", + method: "toFileUrl", + cause + }) + }) +}) /** * Provides the `Path` service using Node's POSIX path implementation plus @@ -47,8 +48,7 @@ const toFileUrl = (path: string): Effect.Effect => export const layerPosix: Layer.Layer = Layer.succeed(Path)({ [TypeId]: TypeId, ...NodePath.posix, - fromFileUrl, - toFileUrl + ...fileUrlOps(false) }) /** @@ -61,8 +61,7 @@ export const layerPosix: Layer.Layer = Layer.succeed(Path)({ export const layerWin32: Layer.Layer = Layer.succeed(Path)({ [TypeId]: TypeId, ...NodePath.win32, - fromFileUrl, - toFileUrl + ...fileUrlOps(true) }) /** @@ -75,6 +74,5 @@ export const layerWin32: Layer.Layer = Layer.succeed(Path)({ export const layer: Layer.Layer = Layer.succeed(Path)({ [TypeId]: TypeId, ...NodePath, - fromFileUrl, - toFileUrl + ...fileUrlOps(undefined) }) diff --git a/repos/effect/packages/platform-node-shared/src/NodeSocketServer.ts b/repos/effect/packages/platform-node-shared/src/NodeSocketServer.ts index 2d908e3161..854455d617 100644 --- a/repos/effect/packages/platform-node-shared/src/NodeSocketServer.ts +++ b/repos/effect/packages/platform-node-shared/src/NodeSocketServer.ts @@ -52,12 +52,14 @@ export const make = Effect.fnUntraced(function*( options: Net.ServerOpts & Net.ListenOptions ) { const errorDeferred = Deferred.makeUnsafe() - const pending = new Set() + const pending = new Map void>() function defaultOnConnection(conn: Net.Socket) { - pending.add(conn) const remove = () => { pending.delete(conn) + conn.off("close", remove) + conn.off("error", remove) } + pending.set(conn, remove) conn.on("close", remove) conn.on("error", remove) } @@ -66,6 +68,10 @@ export const make = Effect.fnUntraced(function*( let server: Net.Server | undefined yield* Effect.addFinalizer(() => Effect.callback((resume) => { + pending.forEach((remove, conn) => { + remove() + conn.destroy() + }) server?.close(() => resume(Effect.void)) }) ) @@ -129,12 +135,10 @@ export const make = Effect.fnUntraced(function*( trackFiber ) } - pending.forEach((conn) => { - conn.removeAllListeners("error") - conn.removeAllListeners("close") + pending.forEach((remove, conn) => { + remove() onConnection(conn) }) - pending.clear() return yield* Effect.callback((_resume) => { return Effect.suspend(() => { onConnection = prevOnConnection @@ -190,20 +194,29 @@ export const makeWebSocket: ( > = Effect.fnUntraced(function*( options: NodeWS.ServerOptions ) { + const pendingConnections = new Map< + globalThis.WebSocket, + readonly [request: Http.IncomingMessage, remove: () => void] + >() const server = yield* Effect.acquireRelease( Effect.sync(() => new NodeWS.WebSocketServer(options)), (server) => Effect.callback((resume) => { + pendingConnections.forEach(([, remove], conn) => { + remove() + const socket = conn as unknown as NodeWS.WebSocket + socket.terminate() + }) server.close(() => resume(Effect.void)) }) ) - const pendingConnections = new Set() function defaultHandler(conn: globalThis.WebSocket, req: Http.IncomingMessage) { - const entry = [conn, req] as const - pendingConnections.add(entry) - conn.addEventListener("close", () => { - pendingConnections.delete(entry) - }) + const remove = () => { + pendingConnections.delete(conn) + conn.removeEventListener("close", remove) + } + pendingConnections.set(conn, [req, remove]) + conn.addEventListener("close", remove) } let onConnection = defaultHandler server.on("connection", (conn, req) => onConnection(conn as any, req)) @@ -248,10 +261,10 @@ export const makeWebSocket: ( trackFiber ) } - for (const [conn, req] of pendingConnections) { + pendingConnections.forEach(([req, remove], conn) => { + remove() onConnection(conn, req) - } - pendingConnections.clear() + }) return yield* Effect.callback((_resume) => { return Effect.sync(() => { onConnection = prevOnConnection diff --git a/repos/effect/packages/platform-node-shared/src/NodeStdio.ts b/repos/effect/packages/platform-node-shared/src/NodeStdio.ts index fcd083a437..cd05c6e3b1 100644 --- a/repos/effect/packages/platform-node-shared/src/NodeStdio.ts +++ b/repos/effect/packages/platform-node-shared/src/NodeStdio.ts @@ -28,6 +28,8 @@ export const layer: Layer.Layer = Layer.succeed( Stdio.Stdio, Stdio.make({ args: Effect.sync(() => process.argv.slice(2)), + stdinIsTerminal: Effect.sync(() => process.stdin.isTTY === true), + stdoutIsTerminal: Effect.sync(() => process.stdout.isTTY === true), stdout: (options) => fromWritable({ evaluate: () => process.stdout, diff --git a/repos/effect/packages/platform-node-shared/src/NodeStream.ts b/repos/effect/packages/platform-node-shared/src/NodeStream.ts index 342eeabadb..8aa14ba1e6 100644 --- a/repos/effect/packages/platform-node-shared/src/NodeStream.ts +++ b/repos/effect/packages/platform-node-shared/src/NodeStream.ts @@ -235,9 +235,6 @@ export const toString = ( } resume(Effect.fail(onError(err) as E)) }) - stream.once("error", (err) => { - resume(Effect.fail(onError(err) as E)) - }) let string = "" let bytes = 0 @@ -338,7 +335,6 @@ const readableToPullUnsafe = (options: { readonly closeOnDone?: boolean | undefined }) => { const readable = options.readable as Readable - if (readable.readableEnded) return Effect.succeed(Cause.done()) const closeOnDone = options.closeOnDone ?? true const exit = options.exit ?? MutableRef.make(undefined) @@ -364,6 +360,9 @@ const readableToPullUnsafe = (options: { if (exit.current) { return exit.current } + if (readable.readableEnded) { + return Effect.fail(Cause.Done()) + } latch.closeUnsafe() return Effect.flatMap(latch.await, loop) } diff --git a/repos/effect/packages/platform-node-shared/src/NodeTerminal.ts b/repos/effect/packages/platform-node-shared/src/NodeTerminal.ts index 1a5e962b4e..3cd23e66e4 100644 --- a/repos/effect/packages/platform-node-shared/src/NodeTerminal.ts +++ b/repos/effect/packages/platform-node-shared/src/NodeTerminal.ts @@ -35,34 +35,61 @@ export const make: ( function*(shouldQuit: (input: Terminal.UserInput) => boolean = defaultShouldQuit) { const stdin = process.stdin const stdout = process.stdout + const lines = yield* Queue.make() + + // stdin "end" fires once per process, so remember end-of-input for readers + // created after the event (Bun never sets `readableEnded`). + let inputEnded = stdin.readableEnded + let readlineActive = false + const onStdinEnd = () => { + inputEnded = true + if (!readlineActive) { + Queue.endUnsafe(lines) + } + } + stdin.once("end", onStdinEnd) + yield* Effect.addFinalizer(() => Effect.sync(() => stdin.off("end", onStdinEnd))) - // Acquire readline interface with TTY setup/cleanup inside the scope const rlRef = yield* RcRef.make({ acquire: Effect.acquireRelease( Effect.sync(() => { const rl = readline.createInterface({ input: stdin, escapeCodeTimeout: 50 }) + const onLine = (line: string) => Queue.offerUnsafe(lines, line) + const onClose = () => { + readlineActive = false + Queue.endUnsafe(lines) + } + readlineActive = true readline.emitKeypressEvents(stdin, rl) + rl.on("line", onLine) + rl.once("close", onClose) if (stdin.isTTY) { stdin.setRawMode(true) } - return rl + return { rl, onClose, onLine } }), - (rl) => + ({ rl, onClose, onLine }) => Effect.sync(() => { + readlineActive = false + rl.off("line", onLine) + rl.off("close", onClose) if (stdin.isTTY) { stdin.setRawMode(false) } rl.close() + if (inputEnded) { + Queue.endUnsafe(lines) + } }) - ) + ), + idleTimeToLive: "10 millis" }) const columns = Effect.sync(() => stdout.columns ?? 0) const rows = Effect.sync(() => stdout.rows ?? 0) const readInput = Effect.gen(function*() { - yield* RcRef.get(rlRef) const queue = yield* Queue.make() const handleKeypress = (s: string | undefined, k: readline.Key) => { const userInput = { @@ -74,18 +101,40 @@ export const make: ( Queue.endUnsafe(queue) } } - yield* Effect.addFinalizer(() => Effect.sync(() => stdin.off("keypress", handleKeypress))) + // Deno's `process.stdin` shim does not keep the event loop alive, so a + // program blocked on input can exit before `end` is ever delivered. A + // timer holds the loop open for as long as this reader is active. + const keepAlive = setInterval(() => {}, 2147483647) + // Without this, consumers (e.g. `Prompt.run`) hang forever on closed stdin. + const handleEnd = () => { + clearInterval(keepAlive) + Queue.endUnsafe(queue) + } + yield* Effect.addFinalizer(() => + Effect.sync(() => { + clearInterval(keepAlive) + stdin.off("keypress", handleKeypress) + stdin.off("end", handleEnd) + }) + ) stdin.on("keypress", handleKeypress) + if (inputEnded) { + handleEnd() + } else { + yield* RcRef.get(rlRef) + stdin.once("end", handleEnd) + } return queue as Queue.Dequeue }) - const readLine = Effect.scoped( - Effect.flatMap(RcRef.get(rlRef), (readlineInterface) => - Effect.callback((resume) => { - const onLine = (line: string) => resume(Effect.succeed(line)) - readlineInterface.once("line", onLine) - return Effect.sync(() => readlineInterface.off("line", onLine)) - })) + const readLine = Effect.suspend(() => + Queue.poll(lines).pipe( + Effect.flatMap(Option.match({ + onNone: () => Effect.scoped(Effect.andThen(RcRef.get(rlRef), Queue.take(lines))), + onSome: Effect.succeed + })), + Effect.mapError(() => new Terminal.QuitError({})) + ) ) const display = (prompt: string) => diff --git a/repos/effect/packages/platform-node-shared/src/index.ts b/repos/effect/packages/platform-node-shared/src/index.ts index e3c162e9ed..759e93b65b 100644 --- a/repos/effect/packages/platform-node-shared/src/index.ts +++ b/repos/effect/packages/platform-node-shared/src/index.ts @@ -24,6 +24,11 @@ export * as NodeCrypto from "./NodeCrypto.ts" */ export * as NodeFileSystem from "./NodeFileSystem.ts" +/** + * @since 4.0.0 + */ +export * as NodeHttpCompression from "./NodeHttpCompression.ts" + /** * @since 4.0.0 */ diff --git a/repos/effect/packages/platform-node-shared/src/internal/nodeChildProcessSpawner.ts b/repos/effect/packages/platform-node-shared/src/internal/nodeChildProcessSpawner.ts new file mode 100644 index 0000000000..7cd1e9e95d --- /dev/null +++ b/repos/effect/packages/platform-node-shared/src/internal/nodeChildProcessSpawner.ts @@ -0,0 +1,16 @@ +import type * as ChildProcess from "effect/unstable/process/ChildProcess" +import type * as NodeChildProcess from "node:child_process" + +export const buildSpawnOptions = ( + options: ChildProcess.CommandOptions, + base: Pick, + platform: NodeJS.Platform +): NodeChildProcess.SpawnOptions => { + const detached = options.detached ?? platform !== "win32" + return { + ...base, + detached, + shell: options.shell, + windowsHide: options.windowsHide ?? !detached + } +} diff --git a/repos/effect/packages/platform-node-shared/src/internal/utils.ts b/repos/effect/packages/platform-node-shared/src/internal/utils.ts index 757d3c3cee..f62bb8bfe9 100644 --- a/repos/effect/packages/platform-node-shared/src/internal/utils.ts +++ b/repos/effect/packages/platform-node-shared/src/internal/utils.ts @@ -6,7 +6,7 @@ import type { PathLike } from "node:fs" export const handleErrnoException = (module: SystemError["module"], method: string) => ( err: NodeJS.ErrnoException, - [path]: [path: PathLike | number, ...args: Array] + [path]: [path: PathLike | number | string | ReadonlyArray, ...args: Array] ): PlatformError.PlatformError => { let reason: SystemErrorTag = "Unknown" diff --git a/repos/effect/packages/platform-node-shared/test/NodeChildProcessSpawner.test.ts b/repos/effect/packages/platform-node-shared/test/NodeChildProcessSpawner.test.ts index 348991b6af..7c646284d6 100644 --- a/repos/effect/packages/platform-node-shared/test/NodeChildProcessSpawner.test.ts +++ b/repos/effect/packages/platform-node-shared/test/NodeChildProcessSpawner.test.ts @@ -1,19 +1,13 @@ +import { buildSpawnOptions } from "@effect/platform-node-shared/internal/nodeChildProcessSpawner" import * as NodeChildProcessSpawner from "@effect/platform-node-shared/NodeChildProcessSpawner" import * as NodeFileSystem from "@effect/platform-node-shared/NodeFileSystem" import * as NodePath from "@effect/platform-node-shared/NodePath" import { assert, describe, it } from "@effect/vitest" +import * as ChildProcessSpawnerTest from "effect-test/unstable/process/ChildProcessSpawnerTest" import * as Effect from "effect/Effect" -import * as Exit from "effect/Exit" import * as FileSystem from "effect/FileSystem" import * as Layer from "effect/Layer" -import * as Path from "effect/Path" -import * as PlatformError from "effect/PlatformError" -import * as Scope from "effect/Scope" -import * as Stream from "effect/Stream" -import * as TestClock from "effect/testing/TestClock" -import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process" - -const TEST_BASH_SCRIPTS_PATH = [__dirname, "fixtures", "bash"] +import * as ChildProcess from "effect/unstable/process/ChildProcess" const NodeServices = NodeChildProcessSpawner.layer.pipe( Layer.provideMerge(Layer.mergeAll( @@ -22,1048 +16,77 @@ const NodeServices = NodeChildProcessSpawner.layer.pipe( )) ) -// Helper to collect stream output into a string -const decodeByteStream = Effect.fnUntraced( - function*( - stream: Stream.Stream, - encoding: ChildProcess.Encoding = "utf-8" - ) { - const chunks = yield* Stream.runCollect(stream) - const totalLength = chunks.reduce((acc, c) => acc + c.length, 0) - const result = new Uint8Array(totalLength) - let offset = 0 - for (const chunk of chunks) { - result.set(chunk, offset) - offset += chunk.length - } - return new TextDecoder(encoding).decode(result).trim() - } -) - -describe("NodeChildProcessSpawner", () => { - it.layer(NodeServices)((it) => { - describe("spawn", () => { - describe("basic spawning", () => { - it.effect("should spawn a simple command and collect output", () => - Effect.gen(function*() { - const handle = yield* ChildProcess.make("node", ["--version"]) - const output = yield* decodeByteStream(handle.stdout) - const exitCode = yield* handle.exitCode - - assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) - // Verify it contains "v" (version string starts with v) - assert.isTrue(output.includes("v")) - }).pipe(Effect.scoped)) - - it.effect("should spawn echo command", () => - Effect.gen(function*() { - const handle = yield* ChildProcess.make("echo", ["hello", "world"]) - const output = yield* decodeByteStream(handle.stdout) - const exitCode = yield* handle.exitCode - - assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) - assert.strictEqual(output, "hello world") - }).pipe(Effect.scoped)) - - it.effect("should spawn with template literal", () => - Effect.gen(function*() { - const handle = yield* ChildProcess.make`echo spawned` - const output = yield* decodeByteStream(handle.stdout) - const exitCode = yield* handle.exitCode - - assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) - assert.strictEqual(output, "spawned") - }).pipe(Effect.scoped)) - }) - - describe("cwd option", () => { - it.effect("should handle command with working directory", () => - Effect.gen(function*() { - const handle = yield* ChildProcess.make("pwd", [], { cwd: "/tmp" }) - const output = yield* decodeByteStream(handle.stdout) - const exitCode = yield* handle.exitCode - - assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) - // On macOS, /tmp is a symlink to /private/tmp - assert.isTrue(output.includes("tmp")) - }).pipe(Effect.scoped)) - - it.effect("should use cwd with template literal form", () => - Effect.gen(function*() { - const handle = yield* ChildProcess.make({ cwd: "/tmp" })`pwd` - const output = yield* decodeByteStream(handle.stdout) - const exitCode = yield* handle.exitCode - - assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) - assert.isTrue(output.includes("tmp")) - }).pipe(Effect.scoped)) - }) - - describe("env option", () => { - it.effect("should handle environment variables", () => - Effect.gen(function*() { - const handle = yield* ChildProcess.make("sh", ["-c", "echo $TEST_VAR"], { - env: { TEST_VAR: "test_value" }, - extendEnv: true - }) - const output = yield* decodeByteStream(handle.stdout) - const exitCode = yield* handle.exitCode - - assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) - assert.strictEqual(output, "test_value") - }).pipe(Effect.scoped)) - - it.effect("should handle multiple environment variables", () => - Effect.gen(function*() { - const handle = yield* ChildProcess.make("sh", ["-c", "echo $VAR1-$VAR2-$VAR3"], { - env: { VAR1: "one", VAR2: "two", VAR3: "three" }, - extendEnv: true - }) - const output = yield* decodeByteStream(handle.stdout) - const exitCode = yield* handle.exitCode - - assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) - assert.strictEqual(output, "one-two-three") - }).pipe(Effect.scoped)) - - it.effect("should merge environment variables with setEnv", () => - Effect.gen(function*() { - const command = ChildProcess.make("sh", ["-c", "echo $VAR1-$VAR2-$VAR3"], { - env: { VAR1: "one", VAR2: "two" }, - extendEnv: true - }).pipe(ChildProcess.setEnv({ VAR2: "override", VAR3: "three" })) - const handle = yield* command - const output = yield* decodeByteStream(handle.stdout) - const exitCode = yield* handle.exitCode - - assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) - assert.strictEqual(output, "one-override-three") - }).pipe(Effect.scoped)) - }) - - describe("shell option", () => { - it.effect("should execute with shell when using sh -c", () => - Effect.gen(function*() { - // Use sh -c to test shell expansion without triggering deprecation warning - const handle = yield* ChildProcess.make("sh", ["-c", "echo $HOME"]) - const output = yield* decodeByteStream(handle.stdout) - const exitCode = yield* handle.exitCode - - assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) - // With shell, $HOME should be expanded - assert.isTrue(output.length > 0) - assert.isFalse(output.includes("$HOME")) - }).pipe(Effect.scoped)) - - it.effect("should not expand variables without shell", () => - Effect.gen(function*() { - const handle = yield* ChildProcess.make("echo", ["$HOME"], { shell: false }) - const output = yield* decodeByteStream(handle.stdout) - const exitCode = yield* handle.exitCode - - assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) - // Without shell, $HOME should not be expanded - assert.strictEqual(output, "$HOME") - }).pipe(Effect.scoped)) - - it.effect("should allow piping with shell", () => - Effect.gen(function*() { - const handle = yield* ChildProcess.make("sh", ["-c", "echo hello | tr a-z A-Z"]) - const output = yield* decodeByteStream(handle.stdout) - const exitCode = yield* handle.exitCode - - assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) - assert.strictEqual(output, "HELLO") - }).pipe(Effect.scoped)) - }) - - describe("template literal forms", () => { - it.effect("should work with template literal form", () => - Effect.gen(function*() { - const handle = yield* ChildProcess.make`echo hello` - const output = yield* decodeByteStream(handle.stdout) - const exitCode = yield* handle.exitCode - - assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) - assert.strictEqual(output, "hello") - }).pipe(Effect.scoped)) - - it.effect("should handle string interpolation", () => - Effect.gen(function*() { - const name = "world" - const handle = yield* ChildProcess.make`echo hello ${name}` - const output = yield* decodeByteStream(handle.stdout) - const exitCode = yield* handle.exitCode - - assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) - assert.strictEqual(output, "hello world") - }).pipe(Effect.scoped)) - - it.effect("should handle number interpolation", () => - Effect.gen(function*() { - const count = 42 - const handle = yield* ChildProcess.make`echo count is ${count}` - const output = yield* decodeByteStream(handle.stdout) - const exitCode = yield* handle.exitCode - - assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) - assert.strictEqual(output, "count is 42") - }).pipe(Effect.scoped)) - - it.effect("should handle array interpolation", () => - Effect.gen(function*() { - const fs = yield* FileSystem.FileSystem - const path = yield* Path.Path - const dir = yield* fs.makeTempDirectoryScoped() - const file = path.join(dir, "array-interpolation.txt") - const args = ["-l", "-a"] - yield* fs.writeFile(file, new TextEncoder().encode("test")) - - const handle = yield* ChildProcess.make`ls ${args} ${dir}` - const exitCode = yield* handle.exitCode - const output = yield* decodeByteStream(handle.stdout) - - assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) - assert.isTrue(output.includes("array-interpolation.txt")) - }).pipe(Effect.scoped)) - - it.effect("should handle multiple interpolations", () => - Effect.gen(function*() { - const greeting = "hello" - const target = "world" - const handle = yield* ChildProcess.make`echo ${greeting} ${target}` - const output = yield* decodeByteStream(handle.stdout) - const exitCode = yield* handle.exitCode - - assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) - assert.strictEqual(output, "hello world") - }).pipe(Effect.scoped)) - - it.effect("should handle options with template literal", () => - Effect.gen(function*() { - const filename = "test.txt" - const handle = yield* ChildProcess.make({ cwd: "/tmp" })`echo ${filename}` - const output = yield* decodeByteStream(handle.stdout) - const exitCode = yield* handle.exitCode - - assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) - assert.strictEqual(output, "test.txt") - }).pipe(Effect.scoped)) - }) - - describe("stderr streaming", () => { - it.effect("should capture stderr output", () => - Effect.gen(function*() { - const handle = yield* ChildProcess.make("sh", ["-c", "echo error message >&2"]) - const stderr = yield* decodeByteStream(handle.stderr) - const exitCode = yield* handle.exitCode - - assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) - assert.strictEqual(stderr, "error message") - }).pipe(Effect.scoped)) - - it.effect("should capture both stdout and stderr", () => - Effect.gen(function*() { - const handle = yield* ChildProcess.make("sh", ["-c", "echo stdout; echo stderr >&2"]) - const stdout = yield* decodeByteStream(handle.stdout) - const stderr = yield* decodeByteStream(handle.stderr) - const exitCode = yield* handle.exitCode - - assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) - assert.strictEqual(stdout, "stdout") - assert.strictEqual(stderr, "stderr") - }).pipe(Effect.scoped)) - - it.effect("should handle more stdout than stderr", () => - Effect.gen(function*() { - // Process outputs many lines to stdout but only one to stderr - const handle = yield* ChildProcess.make( - "sh", - ["-c", "echo line1; echo line2; echo line3; echo line4; echo line5; echo error >&2"] - ) - const [stdout, stderr] = yield* Effect.all([ - decodeByteStream(handle.stdout), - decodeByteStream(handle.stderr) - ], { concurrency: "unbounded" }) - const exitCode = yield* handle.exitCode - - assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) - assert.strictEqual(stdout, ["line1", "line2", "line3", "line4", "line5"].join("\n")) - assert.strictEqual(stderr, "error") - }).pipe(Effect.scoped)) - - it.effect("should handle more stderr than stdout", () => - Effect.gen(function*() { - // Process outputs many lines to stderr but only one to stdout - const handle = yield* ChildProcess.make( - "sh", - ["-c", "echo output; echo err1 >&2; echo err2 >&2; echo err3 >&2; echo err4 >&2; echo err5 >&2"] - ) - const stdout = yield* decodeByteStream(handle.stdout) - const stderr = yield* decodeByteStream(handle.stderr) - const exitCode = yield* handle.exitCode - - assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) - assert.strictEqual(stdout, "output") - assert.strictEqual(stderr, ["err1", "err2", "err3", "err4", "err5"].join("\n")) - }).pipe(Effect.scoped)) - - it.effect("should allow reading only stdout when stderr is empty", () => - Effect.gen(function*() { - const handle = yield* ChildProcess.make("echo", ["only stdout"]) - // Read streams in parallel to avoid deadlock when one stream is empty - const [stdout, stderr] = yield* Effect.all([ - decodeByteStream(handle.stdout), - decodeByteStream(handle.stderr) - ], { concurrency: "unbounded" }) - const exitCode = yield* handle.exitCode - - assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) - assert.strictEqual(stdout, "only stdout") - assert.strictEqual(stderr, "") - }).pipe(Effect.scoped)) - - it.effect("should allow reading only stderr when stdout is empty", () => - Effect.gen(function*() { - const handle = yield* ChildProcess.make("sh", ["-c", "echo only stderr >&2"]) - // Read streams in parallel to avoid deadlock when one stream is empty - const [stdout, stderr] = yield* Effect.all([ - decodeByteStream(handle.stdout), - decodeByteStream(handle.stderr) - ], { concurrency: "unbounded" }) - const exitCode = yield* handle.exitCode - - assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) - assert.strictEqual(stdout, "") - assert.strictEqual(stderr, "only stderr") - }).pipe(Effect.scoped)) - }) - - describe("combined output (all)", () => { - it.effect("should read interspersed stdout and stderr via .all", () => - Effect.gen(function*() { - // Use sleep to force buffer flushes between writes, ensuring - // stdout and stderr chunks arrive separately for proper interleaving - const handle = yield* ChildProcess.make( - "sh", - [ - "-c", - [ - "echo stdout1; sleep 0.01;", - "echo stderr1 >&2; sleep 0.01;", - "echo stdout2; sleep 0.01;", - "echo stderr2 >&2" - ].join(" ") - ] - ) - const all = yield* decodeByteStream(handle.all) - const exitCode = yield* handle.exitCode - - assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) - // With delays forcing buffer flushes, we should see proper interleaving - assert.strictEqual(all, ["stdout1", "stderr1", "stdout2", "stderr2"].join("\n")) - }).pipe(Effect.scoped)) - - it.effect("should capture only stdout via .all when no stderr", () => - Effect.gen(function*() { - const handle = yield* ChildProcess.make("echo", ["hello from stdout"]) - const all = yield* decodeByteStream(handle.all) - const exitCode = yield* handle.exitCode - - assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) - assert.strictEqual(all, "hello from stdout") - }).pipe(Effect.scoped)) - - it.effect("should capture only stderr via .all when no stdout", () => - Effect.gen(function*() { - const handle = yield* ChildProcess.make("sh", ["-c", "echo hello from stderr >&2"]) - const all = yield* decodeByteStream(handle.all) - const exitCode = yield* handle.exitCode - - assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) - assert.strictEqual(all, "hello from stderr") - }).pipe(Effect.scoped)) - - it.effect("should handle many lines of interspersed output via .all", () => - Effect.gen(function*() { - // Use sleep to force buffer flushes, ensuring interleaved arrival - const handle = yield* ChildProcess.make( - "sh", - ["-c", "for i in 1 2 3 4 5; do echo stdout$i; sleep 0.01; echo stderr$i >&2; sleep 0.01; done"] - ) - const all = yield* decodeByteStream(handle.all) - const exitCode = yield* handle.exitCode - - assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) - // Verify all lines are present in interleaved order - const expected = [] - for (let i = 1; i <= 5; i++) { - expected.push(`stdout${i}`, `stderr${i}`) - } - assert.strictEqual(all, expected.join("\n")) - }).pipe(Effect.scoped)) - - it.effect("should allow reading .all independently", () => - Effect.gen(function*() { - const handle = yield* ChildProcess.make( - "sh", - ["-c", "echo out; sleep 0.01; echo err >&2"] - ) - const all = yield* decodeByteStream(handle.all) - const exitCode = yield* handle.exitCode - - assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) - assert.strictEqual(all, ["out", "err"].join("\n")) - }).pipe(Effect.scoped)) - }) - - describe("stdout streaming", () => { - it.effect("should stream stdout", () => - Effect.gen(function*() { - const handle = yield* ChildProcess.make("echo", ["streaming output"]) - const output = yield* decodeByteStream(handle.stdout) - - assert.strictEqual(output, "streaming output") - }).pipe(Effect.scoped)) - - it.effect("should stream multiple lines", () => - Effect.gen(function*() { - const handle = yield* ChildProcess.make("sh", ["-c", "echo line1; echo line2; echo line3"]) - const output = yield* decodeByteStream(handle.stdout) - - assert.strictEqual(output, ["line1", "line2", "line3"].join("\n")) - }).pipe(Effect.scoped)) - }) - - describe("process control", () => { - it.effect("should kill a process", () => - Effect.gen(function*() { - const handle = yield* ChildProcess.make("sleep", ["10"]) - - yield* handle.kill() - - // After killing, exitCode should eventually resolve (with signal error) - const exit = yield* Effect.exit(handle.exitCode) - assert.isTrue(exit._tag === "Failure") - }).pipe(Effect.scoped)) - - it.effect("should kill with specific signal", () => - Effect.gen(function*() { - const handle = yield* ChildProcess.make("sleep", ["10"]) - - yield* handle.kill({ killSignal: "SIGKILL" }) - - const exit = yield* Effect.exit(handle.exitCode) - assert.isTrue(exit._tag === "Failure") - }).pipe(Effect.scoped)) - }) - }) - - describe("pipeline spawning", () => { - it.effect("should spawn a simple pipeline", () => - Effect.gen(function*() { - const handle = yield* ChildProcess.make`echo hello world`.pipe( - ChildProcess.pipeTo(ChildProcess.make`tr a-z A-Z`) - ) - const output = yield* decodeByteStream(handle.stdout) - const exitCode = yield* handle.exitCode - - assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) - assert.strictEqual(output, "HELLO WORLD") - }).pipe(Effect.scoped)) - - it.effect("should spawn a three-stage pipeline", () => - Effect.gen(function*() { - const handle = yield* ChildProcess.make`echo hello world`.pipe( - ChildProcess.pipeTo(ChildProcess.make`tr a-z A-Z`), - ChildProcess.pipeTo(ChildProcess.make("tr", [" ", "-"])) - ) - const output = yield* decodeByteStream(handle.stdout) - const exitCode = yield* handle.exitCode - - assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) - assert.strictEqual(output, "HELLO-WORLD") - }).pipe(Effect.scoped)) - - it.effect("should pipe grep output", () => - Effect.gen(function*() { - const handle = yield* ChildProcess.make("echo", ["line1\nline2\nline3"]).pipe( - ChildProcess.pipeTo(ChildProcess.make`grep line2`) - ) - const output = yield* decodeByteStream(handle.stdout) - const exitCode = yield* handle.exitCode - - assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) - assert.strictEqual(output, "line2") - }).pipe(Effect.scoped)) - - it.effect("should handle mixed command forms in pipeline", () => - Effect.gen(function*() { - const handle = yield* ChildProcess.make("echo", ["hello"]).pipe( - ChildProcess.pipeTo(ChildProcess.make`tr a-z A-Z`) - ) - const output = yield* decodeByteStream(handle.stdout) - const exitCode = yield* handle.exitCode - - assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) - assert.strictEqual(output, "HELLO") - }).pipe(Effect.scoped)) - }) - - describe("pipeline pipe options", () => { - it.effect("should pipe stderr to stdin with { from: 'stderr' }", () => - Effect.gen(function*() { - // Command that writes "error" to stderr - const handle = yield* ChildProcess.make("sh", ["-c", "echo error >&2"]).pipe( - ChildProcess.pipeTo(ChildProcess.make`cat`, { from: "stderr" }) - ) - const output = yield* decodeByteStream(handle.stdout) - const exitCode = yield* handle.exitCode - - assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) - assert.strictEqual(output, "error") - }).pipe(Effect.scoped)) - - it.effect("should pipe combined output with { from: 'all' }", () => - Effect.gen(function*() { - // Command that writes to both stdout and stderr with small delays - const handle = yield* ChildProcess.make("sh", [ - "-c", - "echo out1; sleep 0.01; echo err1 >&2; sleep 0.01; echo out2" - ]).pipe( - ChildProcess.pipeTo(ChildProcess.make`cat`, { from: "all" }) - ) - const output = yield* decodeByteStream(handle.stdout) - const exitCode = yield* handle.exitCode - - assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) - assert.strictEqual(output, ["out1", "err1", "out2"].join("\n")) - }).pipe(Effect.scoped)) - - it.effect("should default to stdout when no options provided", () => - Effect.gen(function*() { - // Command that writes to both stdout and stderr - const handle = yield* ChildProcess.make("sh", ["-c", "echo stdout; echo stderr >&2"]).pipe( - ChildProcess.pipeTo(ChildProcess.make`cat`) - ) - const output = yield* decodeByteStream(handle.stdout) - const exitCode = yield* handle.exitCode - - assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) - // Only stdout should be piped (default behavior) - assert.strictEqual(output, "stdout") - }).pipe(Effect.scoped)) - - it.effect("should work with empty options object", () => - Effect.gen(function*() { - const handle = yield* ChildProcess.make`echo hello`.pipe( - ChildProcess.pipeTo(ChildProcess.make`tr a-z A-Z`, {}) - ) - const output = yield* decodeByteStream(handle.stdout) - const exitCode = yield* handle.exitCode - - assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) - assert.strictEqual(output, "HELLO") - }).pipe(Effect.scoped)) - - it.effect("should work with explicit { from: 'stdout' }", () => - Effect.gen(function*() { - const handle = yield* ChildProcess.make`echo hello`.pipe( - ChildProcess.pipeTo(ChildProcess.make`tr a-z A-Z`, { from: "stdout" }) - ) - const output = yield* decodeByteStream(handle.stdout) - const exitCode = yield* handle.exitCode - - assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) - assert.strictEqual(output, "HELLO") - }).pipe(Effect.scoped)) - - it.effect("should work with explicit { to: 'stdin' }", () => - Effect.gen(function*() { - const handle = yield* ChildProcess.make`echo hello`.pipe( - ChildProcess.pipeTo(ChildProcess.make`tr a-z A-Z`, { to: "stdin" }) - ) - const output = yield* decodeByteStream(handle.stdout) - const exitCode = yield* handle.exitCode - - assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) - assert.strictEqual(output, "HELLO") - }).pipe(Effect.scoped)) +ChildProcessSpawnerTest.suite("NodeChildProcessSpawner", NodeServices, { + processGroups: true +}) - it.effect("should support chained pipes with different options", () => - Effect.gen(function*() { - // First pipe: stdout to stdin (default) - // Second pipe: from stderr - const handle = yield* ChildProcess.make`echo hello`.pipe( - ChildProcess.pipeTo(ChildProcess.make("sh", ["-c", "cat; echo error >&2"])), - ChildProcess.pipeTo(ChildProcess.make`cat`, { from: "stderr" }) - ) - const output = yield* decodeByteStream(handle.stdout) - const exitCode = yield* handle.exitCode +describe("buildSpawnOptions", () => { + const base = { stdio: "pipe" } as const - assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) - assert.strictEqual(output, "error") - }).pipe(Effect.scoped)) + it("defaults to hiding non-detached Windows children", () => { + assert.deepStrictEqual(buildSpawnOptions({}, base, "win32"), { + stdio: "pipe", + detached: false, + shell: undefined, + windowsHide: true }) - - describe("error handling", () => { - it.effect("should return non-zero exit code", () => - Effect.gen(function*() { - const handle = yield* ChildProcess.make("sh", ["-c", "exit 1"]) - const exitCode = yield* handle.exitCode - - assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(1)) - }).pipe(Effect.scoped)) - - it.effect("should fail for invalid command", () => - Effect.gen(function*() { - const exit = yield* Effect.exit( - ChildProcess.make("nonexistent-command-12345") - ) - - assert.isTrue(exit._tag === "Failure") - }).pipe(Effect.scoped)) - - it.effect("should handle spawn error with invalid cwd", () => - Effect.gen(function*() { - const exit = yield* Effect.exit( - ChildProcess.make("echo", ["test"], { cwd: "/nonexistent/directory/path" }) - ) - - assert.isTrue(exit._tag === "Failure") - }).pipe(Effect.scoped)) - - it.effect("should throw permission denied as a typed error", () => - Effect.gen(function*() { - const path = yield* Path.Path - const cwd = path.join(...TEST_BASH_SCRIPTS_PATH) - - const command = ChildProcess.make({ cwd })`./no-permissions.sh` - const result = yield* Effect.flip(command) - - assert.deepStrictEqual( - result, - PlatformError.systemError({ - _tag: "PermissionDenied", - module: "ChildProcess", - method: "spawn", - pathOrDescriptor: "./no-permissions.sh ", - syscall: "spawn ./no-permissions.sh" - }) - ) - }).pipe(Effect.scoped)) + assert.deepStrictEqual(buildSpawnOptions({ detached: true }, base, "win32"), { + stdio: "pipe", + detached: true, + shell: undefined, + windowsHide: false }) - - describe("stdin", () => { - it.effect("allows providing standard input to a command", () => - Effect.gen(function*() { - const input = "a b c" - const stdin = Stream.make(Buffer.from(input, "utf-8")) - const handle = yield* ChildProcess.make("cat", { stdin }) - const output = yield* decodeByteStream(handle.stdout) - const exitCode = yield* handle.exitCode - - assert.deepStrictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) - assert.strictEqual(output, input) - }).pipe(Effect.scoped)) + assert.deepStrictEqual(buildSpawnOptions({ detached: false }, base, "win32"), { + stdio: "pipe", + detached: false, + shell: undefined, + windowsHide: true }) + }) - describe("additionalFds", () => { - it.effect("should read data from an output fd (fd3)", () => - Effect.gen(function*() { - // Use a shell script that writes to fd3 - // The script echoes "hello from fd3" to file descriptor 3 - const handle = yield* ChildProcess.make("sh", ["-c", "echo 'hello from fd3' >&3"], { - additionalFds: { fd3: { type: "output" } } - }) - - const fd3Output = yield* decodeByteStream(handle.getOutputFd(3)) - const exitCode = yield* handle.exitCode - - assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) - assert.strictEqual(fd3Output, "hello from fd3") - }).pipe(Effect.scoped)) - - it.effect("should write data to an input fd (fd3)", () => - Effect.gen(function*() { - // Use a shell script that reads from fd3 and echoes it to stdout - // The script reads from file descriptor 3 and outputs to stdout - const inputData = "data from parent" - const inputStream = Stream.make(new TextEncoder().encode(inputData)) - - const handle = yield* ChildProcess.make("sh", ["-c", "cat <&3"], { - additionalFds: { - fd3: { type: "input", stream: inputStream } - } - }) - - const stdout = yield* decodeByteStream(handle.stdout) - const exitCode = yield* handle.exitCode - - assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) - assert.strictEqual(stdout, inputData) - }).pipe(Effect.scoped)) - - it.effect("should handle multiple additional fds", () => - Effect.gen(function*() { - // Script that writes different messages to fd3 and fd4 - const handle = yield* ChildProcess.make( - "sh", - ["-c", "echo 'output on fd3' >&3; echo 'output on fd4' >&4"], - { - additionalFds: { - fd3: { type: "output" }, - fd4: { type: "output" } - } - } - ) - - const fd3Output = yield* decodeByteStream(handle.getOutputFd(3)) - const fd4Output = yield* decodeByteStream(handle.getOutputFd(4)) - const exitCode = yield* handle.exitCode - - assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) - assert.strictEqual(fd3Output, "output on fd3") - assert.strictEqual(fd4Output, "output on fd4") - }).pipe(Effect.scoped)) - - it.effect("should handle fd gaps (e.g., fd3 and fd5 without fd4)", () => - Effect.gen(function*() { - // Script that writes to fd3 and fd5, skipping fd4 - const handle = yield* ChildProcess.make( - "sh", - ["-c", "echo 'on fd3' >&3; echo 'on fd5' >&5"], - { - additionalFds: { - fd3: { type: "output" }, - fd5: { type: "output" } - } - } - ) - - const fd3Output = yield* decodeByteStream(handle.getOutputFd(3)) - const fd5Output = yield* decodeByteStream(handle.getOutputFd(5)) - const exitCode = yield* handle.exitCode - - assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) - assert.strictEqual(fd3Output, "on fd3") - assert.strictEqual(fd5Output, "on fd5") - }).pipe(Effect.scoped)) - - it.effect("should return empty stream for unconfigured output fd", () => - Effect.gen(function*() { - const handle = yield* ChildProcess.make("echo", ["test"]) - - // fd3 was not configured, should return empty stream - const fd3Output = yield* decodeByteStream(handle.getOutputFd(3)) - const exitCode = yield* handle.exitCode - - assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) - assert.strictEqual(fd3Output, "") - }).pipe(Effect.scoped)) - - it.effect("should handle bidirectional communication via separate fds", () => - Effect.gen(function*() { - // Script that reads from fd3, transforms it, and writes to fd4 - const inputData = "hello" - const inputStream = Stream.make(new TextEncoder().encode(inputData)) - - const handle = yield* ChildProcess.make( - "sh", - ["-c", "cat <&3 | tr a-z A-Z >&4"], - { - additionalFds: { - fd3: { type: "input", stream: inputStream }, - fd4: { type: "output" } - } - } - ) - - const fd4Output = yield* decodeByteStream(handle.getOutputFd(4)) - const exitCode = yield* handle.exitCode - - assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) - assert.strictEqual(fd4Output, "HELLO") - }).pipe(Effect.scoped)) - - it.effect("should work alongside normal stdin/stdout/stderr", () => - Effect.gen(function*() { - // Script that uses all standard streams plus fd3 - const handle = yield* ChildProcess.make( - "sh", - ["-c", "echo 'stdout'; echo 'stderr' >&2; echo 'fd3' >&3"], - { additionalFds: { fd3: { type: "output" } } } - ) - - const stdout = yield* decodeByteStream(handle.stdout) - const stderr = yield* decodeByteStream(handle.stderr) - const fd3Output = yield* decodeByteStream(handle.getOutputFd(3)) - const exitCode = yield* handle.exitCode - - assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) - assert.strictEqual(stdout, "stdout") - assert.strictEqual(stderr, "stderr") - assert.strictEqual(fd3Output, "fd3") - }).pipe(Effect.scoped)) - }) - - describe("process supervision", () => { - const countMatchingProcesses = (pattern: string) => - Effect.gen(function*() { - const handle = yield* ChildProcess.make("bash", [ - "-c", - `ps aux | grep '${pattern}' | grep -v grep | wc -l` - ]) - const output = yield* decodeByteStream(handle.stdout) - return Number.parseInt(output.trim()) - }).pipe(Effect.orElseSucceed(() => 0)) - - const killMatchingProcesses = (pattern: string) => - Effect.gen(function*() { - const escaped = `[${pattern[0]}]${pattern.slice(1)}` - const handle = yield* ChildProcess.make("bash", ["-c", `pkill -f '${escaped}' || true`]) - yield* Effect.ignore(handle.exitCode) - }).pipe(Effect.asVoid) - - const longRunningCommand = () => - ChildProcess.make("node", ["-e", "setTimeout(() => {}, 30000)"], { - stdin: "ignore", - stdout: "ignore", - stderr: "ignore" - }) - - it.effect("should kill all child processes in process group", () => - Effect.gen(function*() { - const path = yield* Path.Path - const cwd = path.join(...TEST_BASH_SCRIPTS_PATH) - - // Start the process that spawns children and grandchildren - const handle = yield* ChildProcess.make("./spawn-children.sh", { cwd }) - - // Give it time to spawn all processes - yield* TestClock.withLive(Effect.sleep("100 millis")) - - // Verify the main process is running - const isRunningBeforeKill = yield* handle.isRunning - assert.isTrue(isRunningBeforeKill) - - // Count processes before killing - should be at least 7 (1 parent + 3 children + 3 grandchildren) - const beforeKillHandle = yield* ChildProcess.make("bash", [ - "-c", - "ps aux | grep spawn-children.sh | grep -v grep | wc -l" - ]) - const beforeKill = yield* decodeByteStream(beforeKillHandle.stdout).pipe( - Effect.map((s) => Number.parseInt(s.trim())), - Effect.orElseSucceed(() => 0) - ) - assert.isAtLeast(beforeKill, 7) - - // Kill the main process - yield* handle.kill() - - // Verify the main process is no longer running - const isRunningAfterKill = yield* handle.isRunning - assert.isFalse(isRunningAfterKill) - - // Give a moment for cleanup to complete - yield* TestClock.withLive(Effect.sleep("100 millis")) - - // Check that no processes from the script are still running - const afterKillHandle = yield* ChildProcess.make("bash", [ - "-c", - "ps aux | grep spawn-children.sh | grep -v grep | wc -l" - ]) - const afterKill = yield* decodeByteStream(afterKillHandle.stdout).pipe( - Effect.map((s) => Number.parseInt(s.trim())), - Effect.orElseSucceed(() => 0) - ) - assert.strictEqual(afterKill, 0) - }).pipe(Effect.scoped)) - - it.effect("should cleanup child processes when parent exits with non-zero code", () => - Effect.gen(function*() { - const path = yield* Path.Path - const cwd = path.join(...TEST_BASH_SCRIPTS_PATH) - - // Count processes before running the command - const beforeRunHandle = yield* ChildProcess.make("bash", [ - "-c", - "ps aux | grep parent-exits-early.sh | grep -v grep | wc -l" - ]) - const beforeRun = yield* decodeByteStream(beforeRunHandle.stdout).pipe( - Effect.map((s) => Number.parseInt(s.trim())), - Effect.orElseSucceed(() => 0) - ) - assert.strictEqual(beforeRun, 0) - - // Run command in a separate scope so cleanup happens before we check - const exitCode = yield* Effect.scoped(Effect.gen(function*() { - const handle = yield* ChildProcess.make({ cwd })`./parent-exits-early.sh` - return yield* handle.exitCode - })) - - assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(1)) - - // Allow cleanup to occur - yield* TestClock.withLive(Effect.sleep("10 millis")) - - const afterExitHandle = yield* ChildProcess.make("bash", [ - "-c", - "ps aux | grep 'sleep 30' | grep -v grep | wc -l" - ]) - const afterExit = yield* decodeByteStream(afterExitHandle.stdout).pipe( - Effect.map((s) => Number.parseInt(s.trim())), - Effect.orElseSucceed(() => 0) - ) - // Child processes should be cleaned up after non-zero exit - assert.strictEqual(afterExit, 0) - }).pipe(Effect.scoped)) - - it.effect("should not kill an unrefed process when scope closes", () => - Effect.gen(function*() { - const scope = yield* Scope.make() - const handle = yield* Scope.provide(scope)(Effect.gen(function*() { - return yield* longRunningCommand() - })).pipe( - Effect.provide(NodeServices) - ) - - // @effect-diagnostics-next-line floatingEffect:off - yield* Scope.provide(scope)(handle.unref).pipe(Effect.provide(NodeServices)) - yield* Scope.close(scope, Exit.void) - yield* TestClock.withLive(Effect.sleep("100 millis")) - - const isRunning = yield* handle.isRunning - assert.isTrue(isRunning) - - yield* handle.kill({ killSignal: "SIGKILL" }) - }).pipe(Effect.provide(NodeServices))) - - it.effect("should kill a restored process when scope closes", () => - Effect.gen(function*() { - const scope = yield* Scope.make() - const handle = yield* Scope.provide(scope)(Effect.gen(function*() { - return yield* longRunningCommand() - })).pipe( - Effect.provide(NodeServices) - ) - - const reref = yield* Scope.provide(scope)(handle.unref).pipe(Effect.provide(NodeServices)) - yield* reref - yield* Scope.close(scope, Exit.void) - yield* TestClock.withLive(Effect.sleep("100 millis")) - - const isRunning = yield* handle.isRunning - assert.isFalse(isRunning) - }).pipe(Effect.provide(NodeServices))) - - it.effect("should resolve exitCode after closing the original scope of an unrefed process", () => - Effect.gen(function*() { - const scope = yield* Scope.make() - const handle = yield* Scope.provide(scope)(Effect.gen(function*() { - return yield* ChildProcess.make("node", ["-e", "setTimeout(() => process.exit(0), 50)"], { - stdin: "ignore", - stdout: "ignore", - stderr: "ignore" - }) - })).pipe(Effect.provide(NodeServices)) - - // @effect-diagnostics-next-line floatingEffect:off - yield* Scope.provide(scope)(handle.unref).pipe(Effect.provide(NodeServices)) - yield* Scope.close(scope, Exit.void) - - const exitCode = yield* handle.exitCode - assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) - }).pipe(Effect.provide(NodeServices))) - - it.effect("should cleanup descendants after an unrefed parent exits non-zero", () => - Effect.gen(function*() { - const path = yield* Path.Path - const cwd = path.join(...TEST_BASH_SCRIPTS_PATH) - const scope = yield* Scope.make() - - const handle = yield* Scope.provide(scope)(Effect.gen(function*() { - return yield* ChildProcess.make({ cwd })`./parent-exits-early.sh` - })).pipe(Effect.provide(NodeServices)) - - // @effect-diagnostics-next-line floatingEffect:off - yield* Scope.provide(scope)(handle.unref).pipe(Effect.provide(NodeServices)) - yield* Scope.close(scope, Exit.void) - - const exitCode = yield* handle.exitCode - assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(1)) - - yield* TestClock.withLive(Effect.sleep("100 millis")) - - const remaining = yield* countMatchingProcesses("sleep 30") - assert.strictEqual(remaining, 0) - }).pipe(Effect.provide(NodeServices))) - - it.effect("should unref every process in a pipeline", () => - Effect.gen(function*() { - const scope = yield* Scope.make() - const rootMarker = "pipeline-unref-root" - const tailMarker = "pipeline-unref-tail" - - const handle = yield* Scope.provide(scope)(Effect.gen(function*() { - return yield* ChildProcess.make("node", ["-e", "setTimeout(() => {}, 30000)", rootMarker], { - stdin: "ignore", - stdout: "pipe", - stderr: "ignore" - }).pipe( - ChildProcess.pipeTo( - ChildProcess.make("node", ["-e", "setTimeout(() => {}, 30000)", tailMarker], { - stdin: "pipe", - stdout: "ignore", - stderr: "ignore" - }) - ) - ) - })).pipe(Effect.provide(NodeServices)) - - // @effect-diagnostics-next-line floatingEffect:off - yield* Scope.provide(scope)(handle.unref).pipe(Effect.provide(NodeServices)) - yield* Scope.close(scope, Exit.void) - yield* TestClock.withLive(Effect.sleep("100 millis")) - - const rootCount = yield* countMatchingProcesses(rootMarker) - const tailCount = yield* countMatchingProcesses(tailMarker) - assert.strictEqual(rootCount, 1) - assert.strictEqual(tailCount, 1) - - yield* killMatchingProcesses(rootMarker) - yield* killMatchingProcesses(tailMarker) - }).pipe(Effect.provide(NodeServices))) - }) - - it.effect("should not deadlock on large stdout output", () => - Effect.gen(function*() { - // Generate ~5MB of output — enough to exceed the default PassThrough - // highWaterMark (16KB) many times over. Without the fix, the unread - // combinedPassThrough (.all) would exert backpressure on the source - // stream, blocking stdout too. - const handle = yield* ChildProcess.make("sh", ["-c", "seq 1 100000"]) - const output = yield* handle.stdout.pipe( - Stream.decodeText(), - Stream.runFold(() => "", (acc, chunk) => acc + chunk) - ) - const exitCode = yield* handle.exitCode - - assert.strictEqual(exitCode, ChildProcessSpawner.ExitCode(0)) - const lines = output.trim().split("\n") - assert.strictEqual(lines.length, 100000) - assert.strictEqual(lines[0], "1") - assert.strictEqual(lines[99999], "100000") - }).pipe(Effect.scoped), { timeout: 10_000 }) - - it.effect("ChildProcess.string should not deadlock on large output", () => - Effect.gen(function*() { - const spawner = yield* ChildProcessSpawner.ChildProcessSpawner - const output = yield* spawner.string(ChildProcess.make("sh", ["-c", "seq 1 100000"])) - const lines = output.trim().split("\n") - assert.strictEqual(lines.length, 100000) - assert.strictEqual(lines[0], "1") - assert.strictEqual(lines[99999], "100000") - }), { timeout: 10_000 }) + it("allows windowsHide to be configured independently of detached", () => { + assert.deepStrictEqual( + buildSpawnOptions({ detached: false, windowsHide: false }, base, "win32"), + { + stdio: "pipe", + detached: false, + shell: undefined, + windowsHide: false + } + ) + assert.deepStrictEqual( + buildSpawnOptions({ detached: true, windowsHide: true }, base, "win32"), + { + stdio: "pipe", + detached: true, + shell: undefined, + windowsHide: true + } + ) }) }) + +it.live("kills every process in a pipeline", () => + Effect.gen(function*() { + const fs = yield* FileSystem.FileSystem + const directory = yield* fs.makeTempDirectoryScoped() + const rootHeartbeat = `${directory}/root-heartbeat` + const childHeartbeat = `${directory}/child-heartbeat` + const handle = yield* ChildProcess.make( + "sh", + ["-c", "while :; do printf x >> \"$1\"; sleep 0.01; done", "pipeline-root", rootHeartbeat] + ).pipe(ChildProcess.pipeTo(ChildProcess.make( + "sh", + ["-c", "while :; do printf x >> \"$1\"; sleep 0.01; done", "pipeline-child", childHeartbeat] + ))) + yield* Effect.sleep("100 millis") + yield* handle.kill({ killSignal: "SIGKILL" }) + const rootSizeAfterKill = (yield* fs.stat(rootHeartbeat)).size + const childSizeAfterKill = (yield* fs.stat(childHeartbeat)).size + yield* Effect.sleep("100 millis") + const rootFinalSize = (yield* fs.stat(rootHeartbeat)).size + const childFinalSize = (yield* fs.stat(childHeartbeat)).size + + assert.strictEqual(rootFinalSize, rootSizeAfterKill) + assert.strictEqual(childFinalSize, childSizeAfterKill) + }).pipe(Effect.scoped, Effect.provide(NodeServices))) diff --git a/repos/effect/packages/platform-node-shared/test/NodeFileSystem.test.ts b/repos/effect/packages/platform-node-shared/test/NodeFileSystem.test.ts index 017f2eee94..3349d294c9 100644 --- a/repos/effect/packages/platform-node-shared/test/NodeFileSystem.test.ts +++ b/repos/effect/packages/platform-node-shared/test/NodeFileSystem.test.ts @@ -1,214 +1,99 @@ import * as NodeFileSystem from "@effect/platform-node-shared/NodeFileSystem" -import { assert, describe, expect, it } from "@effect/vitest" -import { Array } from "effect" +import { assert, describe, it } from "@effect/vitest" +import * as Deferred from "effect/Deferred" import * as Effect from "effect/Effect" -import * as Fs from "effect/FileSystem" +import * as Fiber from "effect/Fiber" +import * as FileSystem from "effect/FileSystem" import * as Stream from "effect/Stream" - -const runPromise = (self: Effect.Effect) => - Effect.runPromise( - Effect.provide(self, NodeFileSystem.layer) - ) +import * as TestClock from "effect/testing/TestClock" +import { testLayer } from "../../effect/test/FileSystem.test-utils.ts" + +const startWatch = ( + fs: FileSystem.FileSystem, + root: string, + watch: () => Stream.Stream +) => + Effect.gen(function*() { + const ready = yield* Deferred.make() + const readyName = ".watch-ready" + const fiber = yield* watch().pipe( + Stream.tap((event) => + event.path === readyName + ? Deferred.succeed(ready, undefined) + : Effect.void + ), + Stream.dropUntil((event) => event.path === readyName), + Stream.filter((event) => event.path !== readyName), + Stream.runHead, + Effect.flatMap(Effect.fromOption), + Effect.forkChild + ) + const signalFiber = yield* Effect.sleep("10 millis").pipe( + TestClock.withLive, + Effect.andThen(fs.writeFileString(`${root}/${readyName}`, "")), + Effect.forever, + Effect.forkChild + ) + yield* Deferred.await(ready).pipe( + Effect.raceFirst(Fiber.join(fiber).pipe(Effect.asVoid)), + Effect.ensuring(Fiber.interrupt(signalFiber)) + ) + return fiber + }) describe("FileSystem", () => { - it("readFile", () => - runPromise(Effect.gen(function*() { - const fs = yield* Fs.FileSystem - const data = yield* fs.readFile(`${__dirname}/fixtures/text.txt`) - const text = new TextDecoder().decode(data) - expect(text.trim()).toEqual("lorem ipsum dolar sit amet") - }))) - - it("makeTempDirectory", () => - runPromise(Effect.gen(function*() { - const fs = yield* Fs.FileSystem - let dir = "" - yield* Effect.scoped(Effect.gen(function*() { - dir = yield* fs.makeTempDirectory() - const stat = yield* fs.stat(dir) - expect(stat.type).toEqual("Directory") - })) - const stat = yield* fs.stat(dir) - expect(stat.type).toEqual("Directory") - }))) - - it("makeTempDirectoryScoped", () => - runPromise(Effect.gen(function*() { - const fs = yield* Fs.FileSystem - let dir = "" - yield* Effect.scoped( - Effect.gen(function*() { - dir = yield* fs.makeTempDirectoryScoped() - const stat = yield* fs.stat(dir) - expect(stat.type).toEqual("Directory") - }) - ) - const error = yield* Effect.flip(fs.stat(dir)) - assert(error.reason._tag === "NotFound") - }))) - - it("truncate", () => - runPromise(Effect.gen(function*() { - const fs = yield* Fs.FileSystem - const file = yield* fs.makeTempFile() - - const text = "hello world" - yield* fs.writeFile(file, new TextEncoder().encode(text)) - - const before = yield* Effect.map(fs.readFile(file), (_) => new TextDecoder().decode(_)) - expect(before).toEqual(text) - - yield* fs.truncate(file) - - const after = yield* Effect.map(fs.readFile(file), (_) => new TextDecoder().decode(_)) - expect(after).toEqual("") - }))) - - it("should track the cursor position when reading", () => - runPromise(Effect.gen(function*() { - const fs = yield* Fs.FileSystem - - yield* Effect.gen(function*() { - let text: string - const file = yield* fs.open(`${__dirname}/fixtures/text.txt`) - - text = yield* file.readAlloc(Fs.Size(5)).pipe( - Effect.flatMap(Effect.fromOption), - Effect.map((_) => new TextDecoder().decode(_)) - ) - expect(text).toBe("lorem") - - yield* file.seek(Fs.Size(7), "current") - text = yield* file.readAlloc(Fs.Size(5)).pipe( - Effect.flatMap(Effect.fromOption), - Effect.map((_) => new TextDecoder().decode(_)) - ) - expect(text).toBe("dolar") - - yield* file.seek(Fs.Size(1), "current") - text = yield* file.readAlloc(Fs.Size(8)).pipe( - Effect.flatMap(Effect.fromOption), - Effect.map((_) => new TextDecoder().decode(_)) - ) - expect(text).toBe("sit amet") - - yield* file.seek(Fs.Size(0), "start") - text = yield* file.readAlloc(Fs.Size(11)).pipe( - Effect.flatMap(Effect.fromOption), - Effect.map((_) => new TextDecoder().decode(_)) - ) - expect(text).toBe("lorem ipsum") - - text = yield* fs.stream(`${__dirname}/fixtures/text.txt`, { offset: Fs.Size(6), bytesToRead: Fs.Size(5) }).pipe( - Stream.map((_) => new TextDecoder().decode(_)), - Stream.runCollect, - Effect.map(Array.join("")) - ) - expect(text).toBe("ipsum") - }).pipe( - Effect.scoped - ) - }))) - - it("should track the cursor position when writing", () => - runPromise(Effect.gen(function*() { - const fs = yield* Fs.FileSystem - - yield* Effect.gen(function*() { - let text: string - const path = yield* fs.makeTempFileScoped() - const file = yield* fs.open(path, { flag: "w+" }) - - yield* file.write(new TextEncoder().encode("lorem ipsum")) - yield* file.write(new TextEncoder().encode(" ")) - yield* file.write(new TextEncoder().encode("dolor sit amet")) - text = yield* fs.readFileString(path) - expect(text).toBe("lorem ipsum dolor sit amet") - - yield* file.seek(Fs.Size(-4), "current") - yield* file.write(new TextEncoder().encode("hello world")) - text = yield* fs.readFileString(path) - expect(text).toBe("lorem ipsum dolor sit hello world") - - yield* file.seek(Fs.Size(6), "start") - yield* file.write(new TextEncoder().encode("blabl")) - text = yield* fs.readFileString(path) - expect(text).toBe("lorem blabl dolor sit hello world") - }).pipe( - Effect.scoped - ) - }))) - - it("should maintain a read cursor in append mode", () => - runPromise(Effect.gen(function*() { - const fs = yield* Fs.FileSystem - - yield* Effect.gen(function*() { - let text: string - const path = yield* fs.makeTempFileScoped() - const file = yield* fs.open(path, { flag: "a+" }) - - yield* file.write(new TextEncoder().encode("foo")) - yield* file.seek(Fs.Size(0), "start") - - yield* file.write(new TextEncoder().encode("bar")) - text = yield* fs.readFileString(path) - expect(text).toBe("foobar") - - text = yield* file.readAlloc(Fs.Size(3)).pipe( - Effect.flatMap(Effect.fromOption), - Effect.map((_) => new TextDecoder().decode(_)) - ) - expect(text).toBe("foo") - - yield* file.write(new TextEncoder().encode("baz")) - text = yield* fs.readFileString(path) - expect(text).toBe("foobarbaz") - - text = yield* file.readAlloc(Fs.Size(6)).pipe( - Effect.flatMap(Effect.fromOption), - Effect.map((_) => new TextDecoder().decode(_)) - ) - expect(text).toBe("barbaz") - }).pipe( - Effect.scoped - ) - }))) - - it("should keep the current cursor if truncating doesn't affect it", () => - runPromise(Effect.gen(function*() { - const fs = yield* Fs.FileSystem - - yield* Effect.gen(function*() { - const path = yield* fs.makeTempFileScoped() - const file = yield* fs.open(path, { flag: "w+" }) - - yield* file.write(new TextEncoder().encode("lorem ipsum dolor sit amet")) - yield* file.seek(Fs.Size(6), "start") - yield* file.truncate(Fs.Size(11)) - - const cursor = yield* file.seek(Fs.Size(0), "current") - expect(cursor).toBe(Fs.Size(6)) - }).pipe( - Effect.scoped - ) - }))) - - it("should update the current cursor if truncating affects it", () => - runPromise(Effect.gen(function*() { - const fs = yield* Fs.FileSystem - - yield* Effect.gen(function*() { - const path = yield* fs.makeTempFileScoped() - const file = yield* fs.open(path, { flag: "w+" }) - - yield* file.write(new TextEncoder().encode("lorem ipsum dolor sit amet")) - yield* file.truncate(Fs.Size(11)) - - const cursor = yield* file.seek(Fs.Size(0), "current") - expect(cursor).toBe(Fs.Size(11)) - }).pipe( - Effect.scoped - ) - }))) + testLayer(NodeFileSystem.layer) + + it.effect("watch does not report nested changes when recursive is false", () => + Effect.gen(function*() { + const fs = yield* FileSystem.FileSystem + const root = yield* fs.makeTempDirectoryScoped() + const nested = `${root}/nested` + yield* fs.makeDirectory(nested) + + const fiber = yield* startWatch(fs, root, () => fs.watch(root, { recursive: false })) + + yield* fs.writeFileString(`${nested}/nested.txt`, "") + yield* fs.writeFileString(`${root}/direct.txt`, "") + + const event = yield* Fiber.join(fiber) + assert.strictEqual(event.path, "direct.txt") + }).pipe( + Effect.provide(NodeFileSystem.layer) + )) + + it.effect("watch is non-recursive when options are omitted", () => + Effect.gen(function*() { + const fs = yield* FileSystem.FileSystem + const root = yield* fs.makeTempDirectoryScoped() + const nested = `${root}/nested` + yield* fs.makeDirectory(nested) + + const fiber = yield* startWatch(fs, root, () => fs.watch(root)) + + yield* fs.writeFileString(`${nested}/nested.txt`, "") + yield* fs.writeFileString(`${root}/direct.txt`, "") + + const event = yield* Fiber.join(fiber) + assert.strictEqual(event.path, "direct.txt") + }).pipe( + Effect.provide(NodeFileSystem.layer) + )) + + it.effect("watch reports nested changes when recursive is true", () => + Effect.gen(function*() { + const fs = yield* FileSystem.FileSystem + const root = yield* fs.makeTempDirectoryScoped() + const nested = `${root}/nested` + yield* fs.makeDirectory(nested) + + const fiber = yield* startWatch(fs, root, () => fs.watch(root, { recursive: true })) + + yield* fs.writeFileString(`${nested}/nested.txt`, "") + + const event = yield* Fiber.join(fiber) + assert(event.path.endsWith("nested.txt")) + }).pipe( + Effect.provide(NodeFileSystem.layer) + )) }) diff --git a/repos/effect/packages/platform-node-shared/test/NodePath.test.ts b/repos/effect/packages/platform-node-shared/test/NodePath.test.ts new file mode 100644 index 0000000000..d8b9cadf6f --- /dev/null +++ b/repos/effect/packages/platform-node-shared/test/NodePath.test.ts @@ -0,0 +1,27 @@ +import * as NodePath from "@effect/platform-node-shared/NodePath" +import { assert, it } from "@effect/vitest" +import * as Effect from "effect/Effect" +import * as Path from "effect/Path" + +it.layer(NodePath.layerPosix)("POSIX file URLs", (it) => { + it.effect("uses POSIX conversions", () => + Effect.gen(function*() { + const path = yield* Path.Path + + assert.strictEqual(yield* path.fromFileUrl(new URL("file:///tmp/file.txt")), "/tmp/file.txt") + assert.strictEqual((yield* path.toFileUrl("/tmp/file.txt")).href, "file:///tmp/file.txt") + })) +}) + +it.layer(NodePath.layerWin32)("Windows file URLs", (it) => { + it.effect("uses Windows conversions", () => + Effect.gen(function*() { + const path = yield* Path.Path + + assert.strictEqual(yield* path.fromFileUrl(new URL("file:///C:/Users/me/file.txt")), "C:\\Users\\me\\file.txt") + assert.strictEqual( + (yield* path.toFileUrl("C:\\Users\\me\\file.txt")).href, + "file:///C:/Users/me/file.txt" + ) + })) +}) diff --git a/repos/effect/packages/platform-node-shared/test/NodeStdio.test.ts b/repos/effect/packages/platform-node-shared/test/NodeStdio.test.ts new file mode 100644 index 0000000000..5d822b8acb --- /dev/null +++ b/repos/effect/packages/platform-node-shared/test/NodeStdio.test.ts @@ -0,0 +1,50 @@ +import * as NodeStdio from "@effect/platform-node-shared/NodeStdio" +import { assert, describe, it } from "@effect/vitest" +import * as Effect from "effect/Effect" +import * as Stdio from "effect/Stdio" + +const streams = [process.stdin, process.stdout] as const + +const setIsTTY = (stdin: boolean, stdout: boolean) => + Effect.sync(() => { + Object.defineProperty(streams[0], "isTTY", { configurable: true, value: stdin }) + Object.defineProperty(streams[1], "isTTY", { configurable: true, value: stdout }) + }) + +const withRestoredIsTTY = (effect: Effect.Effect) => + Effect.acquireUseRelease( + Effect.sync(() => streams.map((stream) => Object.getOwnPropertyDescriptor(stream, "isTTY"))), + () => effect, + (descriptors) => + Effect.sync(() => { + streams.forEach((stream, index) => { + const descriptor = descriptors[index] + if (descriptor === undefined) { + Reflect.deleteProperty(stream, "isTTY") + } else { + Object.defineProperty(stream, "isTTY", descriptor) + } + }) + }) + ) + +describe("NodeStdio", () => { + it.effect("reads terminal state when the effects run", () => + withRestoredIsTTY( + Effect.gen(function*() { + const stdio = yield* Stdio.Stdio + + yield* setIsTTY(true, false) + assert.deepStrictEqual( + yield* Effect.all([stdio.stdinIsTerminal, stdio.stdoutIsTerminal]), + [true, false] + ) + + yield* setIsTTY(false, true) + assert.deepStrictEqual( + yield* Effect.all([stdio.stdinIsTerminal, stdio.stdoutIsTerminal]), + [false, true] + ) + }).pipe(Effect.provide(NodeStdio.layer)) + )) +}) diff --git a/repos/effect/packages/platform-node-shared/test/NodeStream.test.ts b/repos/effect/packages/platform-node-shared/test/NodeStream.test.ts index edd098bcb0..84fefb0f0e 100644 --- a/repos/effect/packages/platform-node-shared/test/NodeStream.test.ts +++ b/repos/effect/packages/platform-node-shared/test/NodeStream.test.ts @@ -170,4 +170,14 @@ describe("Stream", () => { ) assert.deepEqual(error.cause, "error") })) + + it.effect("toString registers one error listener", () => + Effect.gen(function*() { + const stream = new Readable({ + read() {} + }) + yield* NodeStream.toString(() => stream).pipe(Effect.forkChild) + yield* Effect.yieldNow + assert.strictEqual(stream.listenerCount("error"), 1) + })) }) diff --git a/repos/effect/packages/platform-node-shared/test/NodeTerminal.test.ts b/repos/effect/packages/platform-node-shared/test/NodeTerminal.test.ts new file mode 100644 index 0000000000..4e82a977fd --- /dev/null +++ b/repos/effect/packages/platform-node-shared/test/NodeTerminal.test.ts @@ -0,0 +1,75 @@ +import { assert, describe, it } from "@effect/vitest" +import * as Effect from "effect/Effect" +import { spawn, spawnSync } from "node:child_process" +import { join } from "node:path" + +const fixture = join(__dirname, "fixtures", "node-terminal.ts") + +const runFixture = (mode: string, input: string) => + spawnSync(process.execPath, [fixture, mode], { + encoding: "utf8", + input, + timeout: 2_000 + }) + +const assertResult = (mode: string, input: string, expected: string) => { + const result = runFixture(mode, input) + assert.isUndefined(result.error) + assert.strictEqual(result.status, 0, result.stderr) + assert.isTrue(result.stderr.includes(`RESULT ${expected}`), result.stderr) +} + +const assertOpenResult = (mode: string, input: string, expected: string) => + Effect.callback((resume) => { + const child = spawn(process.execPath, [fixture, mode]) + let stderr = "" + child.stderr.setEncoding("utf8") + child.stderr.on("data", (data) => { + stderr += data + if (stderr.includes("RESULT ")) { + child.stdin.end() + } + }) + child.on("exit", (code) => { + resume( + code === 0 && stderr.includes(`RESULT ${expected}`) + ? Effect.void + : Effect.die(new Error(stderr)) + ) + }) + child.stdin.write(input) + return Effect.sync(() => child.kill()) + }) + +describe("NodeTerminal", () => { + it("does not install a readline interface until the terminal is used", () => { + assertResult("unused", "", "{\"dataListeners\":0}") + }) + + it("fails a prompt with QuitError after piped input is exhausted", () => { + assertResult("prompts", "y\n", "{\"first\":true,\"second\":\"QuitError\"}") + }) + + it("delivers buffered keypresses before ending the input queue", () => { + assertResult("read-input", "yn", "{\"first\":\"y\",\"second\":\"n\",\"ended\":true}") + }) + + it("flushes an unterminated line before failing readLine with QuitError at EOF", () => { + assertResult("read-line", "last line", "{\"first\":\"last line\",\"second\":\"QuitError\"}") + }) + + it("preserves lines buffered between sequential readLine calls", () => { + assertResult("read-lines", "first\nsecond\n", "{\"first\":\"first\",\"second\":\"second\"}") + }) + + it("fails readLine with QuitError when stdin ended before initialization", () => { + assertResult("read-line-after-end", "", "\"QuitError\"") + }) + + it.effect("disposes readline after its idle TTL", () => + assertOpenResult( + "read-line-disposed", + "line\n", + "{\"line\":\"line\",\"duringTtl\":1,\"dataListeners\":0}" + )) +}) diff --git a/repos/effect/packages/platform-node-shared/test/fixtures/bash/parent-exits-early.sh b/repos/effect/packages/platform-node-shared/test/fixtures/bash/parent-exits-early.sh deleted file mode 100755 index 7f2eabb0cd..0000000000 --- a/repos/effect/packages/platform-node-shared/test/fixtures/bash/parent-exits-early.sh +++ /dev/null @@ -1,31 +0,0 @@ -#!/usr/bin/env bash - -# This script spawns child processes and then exits early -echo "Parent process started with PID $$" - -# Spawn multiple child processes that will outlive the parent -for i in {1..3}; do - ( - # Child process - child_pid=$BASHPID - echo "Child $i started with PID $child_pid" - - # Spawn a grandchild that runs for a long time - ( - grandchild_pid=$BASHPID - echo "Grandchild of child $i started with PID $grandchild_pid" - # Keep running for 30 seconds - sleep 30 - ) & - - # Keep the child running - sleep 30 - ) & -done - -# Give children time to start -sleep 0.5 - -# Exit early (simulating a crash or early termination) -echo "Parent exiting early with status 1..." -exit 1 diff --git a/repos/effect/packages/platform-node-shared/test/fixtures/node-terminal.ts b/repos/effect/packages/platform-node-shared/test/fixtures/node-terminal.ts new file mode 100644 index 0000000000..63f6ea590d --- /dev/null +++ b/repos/effect/packages/platform-node-shared/test/fixtures/node-terminal.ts @@ -0,0 +1,108 @@ +import * as NodeTerminal from "@effect/platform-node-shared/NodeTerminal" +import * as Effect from "effect/Effect" +import * as Exit from "effect/Exit" +import * as FileSystem from "effect/FileSystem" +import * as Layer from "effect/Layer" +import * as Option from "effect/Option" +import * as Path from "effect/Path" +import * as Queue from "effect/Queue" +import * as Terminal from "effect/Terminal" +import { Prompt } from "effect/unstable/cli" + +const TerminalLayer = Layer.mergeAll( + NodeTerminal.layer, + FileSystem.layerNoop({}), + Path.layer +) + +const prompts = Effect.gen(function*() { + const first = yield* Prompt.run(Prompt.confirm({ message: "First" })) + const second = yield* Prompt.run(Prompt.confirm({ message: "Second" })).pipe(Effect.flip) + return { first, second: second._tag } +}) + +const readInput = Effect.scoped( + Effect.gen(function*() { + const terminal = yield* Terminal.Terminal + const input = yield* terminal.readInput + const first = yield* Queue.take(input) + const second = yield* Queue.take(input) + const end = yield* Effect.exit(Queue.take(input)) + return { + first: Option.getOrNull(first.input), + second: Option.getOrNull(second.input), + ended: Exit.isFailure(end) + } + }) +) + +const readLine = Effect.gen(function*() { + const terminal = yield* Terminal.Terminal + const first = yield* terminal.readLine + const second = yield* terminal.readLine.pipe(Effect.flip) + return { first, second: second._tag } +}) + +const readLines = Effect.gen(function*() { + const terminal = yield* Terminal.Terminal + const first = yield* terminal.readLine + const second = yield* terminal.readLine + return { first, second } +}) + +const unused = Effect.gen(function*() { + yield* Terminal.Terminal + return { dataListeners: process.stdin.listenerCount("data") } +}) + +const readLineAfterEnd = Effect.gen(function*() { + const terminal = yield* Terminal.Terminal + yield* Effect.callback((resume) => { + if (process.stdin.readableEnded) { + resume(Effect.void) + return + } + const onEnd = () => resume(Effect.void) + process.stdin.once("end", onEnd) + process.stdin.resume() + return Effect.sync(() => process.stdin.off("end", onEnd)) + }) + const error = yield* terminal.readLine.pipe(Effect.flip) + return error._tag +}) + +const readLineDisposed = Effect.gen(function*() { + const terminal = yield* Terminal.Terminal + const line = yield* terminal.readLine + const duringTtl = process.stdin.listenerCount("data") + yield* Effect.sleep("20 millis") + return { line, duringTtl, dataListeners: process.stdin.listenerCount("data") } +}) + +const mode = process.argv[2] +const program = Effect.gen(function*() { + if (mode === "prompts") { + return yield* prompts + } else if (mode === "read-input") { + return yield* readInput + } else if (mode === "read-line") { + return yield* readLine + } else if (mode === "read-lines") { + return yield* readLines + } else if (mode === "unused") { + return yield* unused + } else if (mode === "read-line-after-end") { + return yield* readLineAfterEnd + } else if (mode === "read-line-disposed") { + return yield* readLineDisposed + } + return yield* Effect.die(`Unknown mode: ${mode}`) +}) + +Effect.runPromise(program.pipe(Effect.provide(TerminalLayer))).then( + (result) => process.stderr.write(`RESULT ${JSON.stringify(result)}\n`), + (cause) => { + process.stderr.write(`ERROR ${String(cause)}\n`) + process.exitCode = 1 + } +) diff --git a/repos/effect/packages/platform-node-shared/tsconfig.json b/repos/effect/packages/platform-node-shared/tsconfig.json index be73d74c7f..f1ef563eb2 100644 --- a/repos/effect/packages/platform-node-shared/tsconfig.json +++ b/repos/effect/packages/platform-node-shared/tsconfig.json @@ -1,5 +1,5 @@ { - "$schema": "http://json.schemastore.org/tsconfig", + "$schema": "https://json.schemastore.org/tsconfig", "extends": "../../tsconfig.base.json", "include": ["src"], "references": [ diff --git a/repos/effect/packages/platform-node-shared/vitest.config.ts b/repos/effect/packages/platform-node-shared/vitest.config.ts deleted file mode 100644 index fb966ae875..0000000000 --- a/repos/effect/packages/platform-node-shared/vitest.config.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { mergeConfig, type ViteUserConfig } from "vitest/config" -import shared from "../../vitest.shared.ts" - -const config: ViteUserConfig = {} - -export default mergeConfig(shared, config) diff --git a/repos/effect/packages/platform-node/CHANGELOG.md b/repos/effect/packages/platform-node/CHANGELOG.md index 69c9c6d3b9..ea6575ce08 100644 --- a/repos/effect/packages/platform-node/CHANGELOG.md +++ b/repos/effect/packages/platform-node/CHANGELOG.md @@ -1,5 +1,189 @@ # @effect/platform-node +## 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), [`e74c302`](https://github.com/Effect-TS/effect/commit/e74c302afe0368e5d3f15d18c10fc54cf33f9003)]: + - effect@4.0.0-beta.107 + - @effect/platform-node-shared@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), [`5f1775c`](https://github.com/Effect-TS/effect/commit/5f1775cb060cb3dbf96adb067fd97967da7eca2f), [`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), [`089313e`](https://github.com/Effect-TS/effect/commit/089313ec2a4c307393c7c5e00c725ec23840c9c1), [`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 + - @effect/platform-node-shared@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 + - @effect/platform-node-shared@4.0.0-beta.105 + +## 4.0.0-beta.104 + +### Patch Changes + +- [#7049](https://github.com/Effect-TS/effect/pull/7049) [`545d876`](https://github.com/Effect-TS/effect/commit/545d8767648cbbbf1820361662c0c1e10768db6a) Thanks @fubhy! - Fix Node HTTP client requests hanging when a streamed request body fails. + +- [#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. + +- [#6927](https://github.com/Effect-TS/effect/pull/6927) [`721b9f0`](https://github.com/Effect-TS/effect/commit/721b9f0d320e50f3e2324c2cd1f43c6643af3ca0) Thanks @longtngo! - Stop a reset upgrade connection from crashing the process in `NodeHttpServer` +- 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), [`e2ec131`](https://github.com/Effect-TS/effect/commit/e2ec1311bed9bb8709c26396e71b15b4241a9185), [`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), [`e508589`](https://github.com/Effect-TS/effect/commit/e50858905fed68f29ec202ecdc9c902e44bfedd8), [`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 + - @effect/platform-node-shared@4.0.0-beta.104 + +## 4.0.0-beta.103 + +### Patch Changes + +- [#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 + +- [#6802](https://github.com/Effect-TS/effect/pull/6802) [`f1bc827`](https://github.com/Effect-TS/effect/commit/f1bc8274a608813d7b09d28dcca04adbf62f8c92) Thanks @tim-smart! - Allow configuring cluster RPC serialization buffer limits. + +- [#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. + +- [#6680](https://github.com/Effect-TS/effect/pull/6680) [`23e176a`](https://github.com/Effect-TS/effect/commit/23e176a4f05ed3e81cc13a5d70111099692ea9a5) Thanks @tim-smart! - Fix worker runner disconnect notifications and event listener cleanup. + +- [#6691](https://github.com/Effect-TS/effect/pull/6691) [`6a5e86f`](https://github.com/Effect-TS/effect/commit/6a5e86f896c573c391e0fc7888f13e9ae0f07531) Thanks @t3dotgg! - 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 }, + }); + ``` + +- [#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. + +- [#6813](https://github.com/Effect-TS/effect/pull/6813) [`705d1f1`](https://github.com/Effect-TS/effect/commit/705d1f13a100defe694d7e5c04d4b59cf4e2113b) Thanks @tim-smart! - Optimize Node HTTP streaming responses and ensure HEAD completion and stream backpressure are handled once. + +- [#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 + - @effect/platform-node-shared@4.0.0-beta.103 + +## 4.0.0-beta.102 + +### Patch Changes + +- [#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. + +- 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 + - @effect/platform-node-shared@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 + - @effect/platform-node-shared@4.0.0-beta.101 + +## 4.0.0-beta.100 + +### Patch Changes + +- [#6506](https://github.com/Effect-TS/effect/pull/6506) [`b0f1a50`](https://github.com/Effect-TS/effect/commit/b0f1a50dacece60aa2393a15da853d82891a7a34) Thanks @tim-smart! - Ensure aborted `HEAD` responses do not block `NodeHttpServer` disposal. + +- 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 + - @effect/platform-node-shared@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), [`b330264`](https://github.com/Effect-TS/effect/commit/b3302645fc1c23f7d03a3693dabdcb88763d5cbd), [`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 + - @effect/platform-node-shared@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 + - @effect/platform-node-shared@4.0.0-beta.98 + +## 4.0.0-beta.97 + +### Patch Changes + +- Updated dependencies []: + - effect@4.0.0-beta.97 + - @effect/platform-node-shared@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 + - @effect/platform-node-shared@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 + - @effect/platform-node-shared@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 + - @effect/platform-node-shared@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 + - @effect/platform-node-shared@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 + - @effect/platform-node-shared@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 + - @effect/platform-node-shared@4.0.0-beta.91 + ## 4.0.0-beta.90 ### Patch Changes diff --git a/repos/effect/packages/platform-node/README.md b/repos/effect/packages/platform-node/README.md index 8df2065481..d97ee42b88 100644 --- a/repos/effect/packages/platform-node/README.md +++ b/repos/effect/packages/platform-node/README.md @@ -1,6 +1,6 @@ # `@effect/platform-node` -Provides Node.js-specific implementations for the abstractions defined in [`@effect/platform`](https://github.com/Effect-TS/effect/tree/main/packages/platform), allowing you to write platform-independent code that integrates smoothly with Node.js. +Provides Node.js-specific implementations for Effect's platform abstractions, allowing you to write platform-independent code that integrates smoothly with Node.js. ## Documentation diff --git a/repos/effect/packages/platform-node/docgen.json b/repos/effect/packages/platform-node/docgen.json deleted file mode 100644 index 13f31070f3..0000000000 --- a/repos/effect/packages/platform-node/docgen.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "$schema": "../../node_modules/@effect/docgen/schema.json", - "srcLink": "https://github.com/Effect-TS/effect/tree/main/packages/platform-node/src/", - "exclude": ["src/internal/**/*.ts"], - "tscExecutable": "tsgo", - "examplesCompilerOptions": { - "noEmit": true, - "strict": true, - "skipLibCheck": true, - "moduleResolution": "Bundler", - "module": "ES2022", - "target": "ES2022", - "lib": ["ES2022", "DOM", "DOM.Iterable"], - "rewriteRelativeImportExtensions": true, - "allowImportingTsExtensions": true, - "paths": { - "effect": ["../../../effect/src/index.js"], - "effect/*": ["../../../effect/src/*.js"] - }, - "plugins": [ - { "name": "@effect/language-service", "includeSuggestionsInTsc": false } - ] - } -} diff --git a/repos/effect/packages/platform-node/package.json b/repos/effect/packages/platform-node/package.json index 1fa4f4b29d..7385ab0823 100644 --- a/repos/effect/packages/platform-node/package.json +++ b/repos/effect/packages/platform-node/package.json @@ -1,17 +1,17 @@ { "name": "@effect/platform-node", "type": "module", - "version": "4.0.0-beta.90", + "version": "4.0.0-beta.107", "license": "MIT", "description": "Platform specific implementations for the Node.js runtime", "homepage": "https://effect.website", "repository": { "type": "git", - "url": "https://github.com/Effect-TS/effect-smol.git", + "url": "https://github.com/Effect-TS/effect.git", "directory": "packages/platform-node" }, "bugs": { - "url": "https://github.com/Effect-TS/effect-smol/issues" + "url": "https://github.com/Effect-TS/effect/issues" }, "tags": [ "node", @@ -34,6 +34,7 @@ ".": "./src/index.ts", "./*": "./src/*.ts", "./internal/*": null, + "./index": null, "./*/index": null }, "files": [ @@ -41,7 +42,10 @@ "dist/**/*.js", "dist/**/*.js.map", "dist/**/*.d.ts", - "dist/**/*.d.ts.map" + "dist/**/*.d.ts.map", + "AGENTS.md", + "CLAUDE.md", + "ai-docs/**/*" ], "publishConfig": { "access": "public", @@ -51,32 +55,30 @@ ".": "./dist/index.js", "./*": "./dist/*.js", "./internal/*": null, + "./index": null, "./*/index": null } }, "scripts": { "codegen": "effect-utils codegen", "build": "tsc -b tsconfig.json && pnpm babel", - "build:tsgo": "tsgo -b tsconfig.json && pnpm babel", "babel": "babel dist --plugins annotate-pure-calls --out-dir dist --source-maps", - "check": "tsc -b tsconfig.json", - "test": "vitest", - "coverage": "vitest --coverage" + "check": "tsc -b tsconfig.json" }, "dependencies": { "@effect/platform-node-shared": "workspace:^", "mime": "^4.1.0", - "undici": "^8.2.0" + "undici": "^8.7.0" }, "peerDependencies": { "effect": "workspace:^", - "ioredis": "^5.7.0" + "ioredis": ">=5.7.0 <6.0.0" }, "devDependencies": { - "@testcontainers/mysql": "^11.14.0", - "@testcontainers/postgresql": "^11.14.0", - "@testcontainers/redis": "^11.14.0", - "@types/node": "^25.7.0", + "@testcontainers/mysql": "^12.0.4", + "@testcontainers/postgresql": "^12.0.4", + "@testcontainers/redis": "^12.0.4", + "@types/node": "^26.1.2", "effect": "workspace:^" } } diff --git a/repos/effect/packages/platform-node/src/NodeClusterHttp.ts b/repos/effect/packages/platform-node/src/NodeClusterHttp.ts index 82543c8061..8f31ccf87a 100644 --- a/repos/effect/packages/platform-node/src/NodeClusterHttp.ts +++ b/repos/effect/packages/platform-node/src/NodeClusterHttp.ts @@ -31,6 +31,7 @@ import * as RpcSerialization from "effect/unstable/rpc/RpcSerialization" import type { SqlClient } from "effect/unstable/sql/SqlClient" import { createServer } from "node:http" import { layerK8sHttpClient } from "./NodeClusterSocket.ts" +import * as NodeCrypto from "./NodeCrypto.ts" import * as NodeHttpClient from "./NodeHttpClient.ts" import * as NodeHttpServer from "./NodeHttpServer.ts" import type { NodeServices } from "./NodeServices.ts" @@ -60,6 +61,7 @@ export const layer = < >(options: { readonly transport: "http" | "websocket" readonly serialization?: "msgpack" | "ndjson" | undefined + readonly serializationMaxBufferSize?: number | "unbounded" | undefined readonly clientOnly?: ClientOnly | undefined readonly storage?: Storage | undefined readonly runnerHealth?: "ping" | "k8s" | undefined @@ -115,7 +117,7 @@ export const layer = < ? MessageStorage.layerNoop : options?.storage === "byo" ? Layer.empty - : Layer.orDie(SqlMessageStorage.layer) + : Layer.orDie(SqlMessageStorage.layer).pipe(Layer.provide(NodeCrypto.layer)) ), Layer.provide( options?.storage === "local" @@ -126,7 +128,9 @@ export const layer = < ), Layer.provide(ShardingConfig.layerFromEnv(options?.shardingConfig)), Layer.provide( - options?.serialization === "ndjson" ? RpcSerialization.layerNdjson : RpcSerialization.layerMsgPack + options?.serialization === "ndjson" + ? RpcSerialization.layerNdjsonWith({ maxBufferSize: options.serializationMaxBufferSize }) + : RpcSerialization.layerMsgPackWith({ maxBufferSize: options.serializationMaxBufferSize }) ) ) as any } diff --git a/repos/effect/packages/platform-node/src/NodeClusterSocket.ts b/repos/effect/packages/platform-node/src/NodeClusterSocket.ts index cd8f1d9762..5faaf5a439 100644 --- a/repos/effect/packages/platform-node/src/NodeClusterSocket.ts +++ b/repos/effect/packages/platform-node/src/NodeClusterSocket.ts @@ -28,6 +28,7 @@ import * as SqlRunnerStorage from "effect/unstable/cluster/SqlRunnerStorage" import * as RpcSerialization from "effect/unstable/rpc/RpcSerialization" import type * as SocketServer from "effect/unstable/socket/SocketServer" import type { SqlClient } from "effect/unstable/sql/SqlClient" +import * as NodeCrypto from "./NodeCrypto.ts" import * as NodeFileSystem from "./NodeFileSystem.ts" import * as NodeHttpClient from "./NodeHttpClient.ts" import * as Undici from "./Undici.ts" @@ -65,6 +66,7 @@ export const layer = < >( options?: { readonly serialization?: "msgpack" | "ndjson" | undefined + readonly serializationMaxBufferSize?: number | "unbounded" | undefined readonly clientOnly?: ClientOnly | undefined readonly storage?: Storage | undefined readonly runnerHealth?: "ping" | "k8s" | undefined @@ -113,7 +115,7 @@ export const layer = < ? MessageStorage.layerNoop : options?.storage === "byo" ? Layer.empty - : Layer.orDie(SqlMessageStorage.layer) + : Layer.orDie(SqlMessageStorage.layer).pipe(Layer.provide(NodeCrypto.layer)) ), Layer.provide( options?.storage === "local" @@ -124,7 +126,9 @@ export const layer = < ), Layer.provide(ShardingConfig.layerFromEnv(options?.shardingConfig)), Layer.provide( - options?.serialization === "ndjson" ? RpcSerialization.layerNdjson : RpcSerialization.layerMsgPack + options?.serialization === "ndjson" + ? RpcSerialization.layerNdjsonWith({ maxBufferSize: options?.serializationMaxBufferSize }) + : RpcSerialization.layerMsgPackWith({ maxBufferSize: options?.serializationMaxBufferSize }) ) ) as any } @@ -146,6 +150,8 @@ export const layerDispatcherK8s: Layer.Layer = Layer. if (caCertOption._tag === "Some") { return yield* Effect.acquireRelease( Effect.sync(() => + // oxlint cannot resolve values re-exported through the local Undici facade. + // oxlint-disable-next-line import/namespace new Undici.Agent({ connect: { ca: caCertOption.value diff --git a/repos/effect/packages/platform-node/src/NodeHttpClient.ts b/repos/effect/packages/platform-node/src/NodeHttpClient.ts index 420e41dac8..27fb8e097a 100644 --- a/repos/effect/packages/platform-node/src/NodeHttpClient.ts +++ b/repos/effect/packages/platform-node/src/NodeHttpClient.ts @@ -51,14 +51,14 @@ export { * Use to access or override the fetch implementation used by the Node * fetch-based HTTP client. * - * @category fetch + * @category services * @since 4.0.0 */ Fetch, /** * Layer that provides the fetch-based HTTP client implementation. * - * @category fetch + * @category layers * @since 4.0.0 */ layer as layerFetch, @@ -69,7 +69,7 @@ export { * * Use to provide default fetch request options for Node HTTP requests. * - * @category fetch + * @category services * @since 4.0.0 */ RequestInit @@ -83,7 +83,7 @@ export { * Service tag for the Undici `Dispatcher` used by the Undici-backed HTTP * client. * - * @category Dispatcher + * @category services * @since 4.0.0 */ export class Dispatcher extends Context.Service()( @@ -94,10 +94,12 @@ export class Dispatcher extends Context.Service() * Acquires a new Undici `Agent` dispatcher and destroys it when the enclosing * scope is finalized. * - * @category Dispatcher + * @category resource management * @since 4.0.0 */ export const makeDispatcher: Effect.Effect = Effect.acquireRelease( + // oxlint cannot resolve values re-exported through the local Undici facade. + // oxlint-disable-next-line import/namespace Effect.sync(() => new Undici.Agent()), (dispatcher) => Effect.promise(() => dispatcher.destroy()) ) @@ -105,7 +107,7 @@ export const makeDispatcher: Effect.Effect = Layer.effect(Dispatcher)(makeDispatcher) @@ -114,16 +116,18 @@ export const layerDispatcher: Layer.Layer = Layer.effect(Dispatcher) * Provides the `Dispatcher` service from Undici's process-global dispatcher, * without creating or owning a new agent. * - * @category Dispatcher + * @category layers * @since 4.0.0 */ +// oxlint cannot resolve values re-exported through the local Undici facade. +// oxlint-disable-next-line import/namespace export const dispatcherLayerGlobal: Layer.Layer = Layer.sync(Dispatcher)(() => Undici.getGlobalDispatcher()) /** * Fiber reference containing default Undici request options applied to requests * sent by `makeUndici`. * - * @category Undici + * @category services * @since 4.0.0 */ export const UndiciOptions = Context.Reference>( @@ -136,7 +140,7 @@ export const UndiciOptions = Context.Reference = Layer.provide(layerUndiciNoDispatcher, layerDispatcher) @@ -379,7 +383,7 @@ export const layerUndici: Layer.Layer = Layer.provide(layerUn * Service tag for the paired Node `http` and `https` agents used by the * node:http-backed HTTP client. * - * @category HttpAgent + * @category services * @since 4.0.0 */ export class HttpAgent extends Context.Service => @@ -411,7 +415,7 @@ export const makeAgent = (options?: Https.AgentOptions): Effect.Effect Layer.Layer< @@ -422,7 +426,7 @@ export const layerAgentOptions: (options?: Https.AgentOptions | undefined) => La * Provides the `HttpAgent` service using default scoped Node `http` and * `https` agents. * - * @category HttpAgent + * @category layers * @since 4.0.0 */ export const layerAgent: Layer.Layer = layerAgentOptions() @@ -432,7 +436,7 @@ export const layerAgent: Layer.Layer = layerAgentOptions() * current `HttpAgent`, streaming request bodies, and wrapping Node responses * as `HttpClientResponse` values. * - * @category node:http + * @category constructors * @since 4.0.0 */ export const makeNodeHttp = Effect.gen(function*() { @@ -451,8 +455,11 @@ export const makeNodeHttp = Effect.gen(function*() { headers: request.headers, signal }) - return Effect.forkChild(sendBody(nodeRequest, request, request.body)).pipe( - Effect.flatMap(() => waitForResponse(nodeRequest, request)), + return Effect.raceFirst( + waitForResponse(nodeRequest, request), + sendBody(nodeRequest, request, request.body).pipe(Effect.andThen(Effect.never)) + ).pipe( + Effect.onError(() => Effect.sync(() => nodeRequest.destroy())), Effect.map((_) => new NodeHttpResponse(request, _)) ) }) @@ -645,7 +652,7 @@ class NodeHttpResponse extends NodeHttpIncomingMessage im * Provides a node:http-backed `HttpClient` using the current `HttpAgent` * service. * - * @category node:http + * @category layers * @since 4.0.0 */ export const layerNodeHttpNoAgent: Layer.Layer< @@ -658,7 +665,7 @@ export const layerNodeHttpNoAgent: Layer.Layer< * Provides a node:http-backed `HttpClient` together with default scoped Node * `http` and `https` agents. * - * @category node:http + * @category layers * @since 4.0.0 */ export const layerNodeHttp: Layer.Layer = Layer.provide(layerNodeHttpNoAgent, layerAgent) diff --git a/repos/effect/packages/platform-node/src/NodeHttpPlatform.ts b/repos/effect/packages/platform-node/src/NodeHttpPlatform.ts index 2901851b67..8421b7c323 100644 --- a/repos/effect/packages/platform-node/src/NodeHttpPlatform.ts +++ b/repos/effect/packages/platform-node/src/NodeHttpPlatform.ts @@ -8,16 +8,63 @@ * * @since 4.0.0 */ +import * as NodeHttpCompression from "@effect/platform-node-shared/NodeHttpCompression" +import * as Effect from "effect/Effect" import { pipe } from "effect/Function" import * as Layer from "effect/Layer" import * as EtagImpl from "effect/unstable/http/Etag" import * as Headers from "effect/unstable/http/Headers" +import * as HttpBody from "effect/unstable/http/HttpBody" import * as Platform from "effect/unstable/http/HttpPlatform" import * as ServerResponse from "effect/unstable/http/HttpServerResponse" import * as Fs from "node:fs" import { Readable } from "node:stream" import Mime from "./Mime.ts" import * as NodeFileSystem from "./NodeFileSystem.ts" +import * as NodeStream from "./NodeStream.ts" + +// replaces the response body while keeping every other field, dropping the +// now-stale Content-Length header +const compressedBody = ( + response: ServerResponse.HttpServerResponse, + body: HttpBody.HttpBody +): ServerResponse.HttpServerResponse => + ServerResponse.removeHeader(ServerResponse.setBody(response, body), "content-length") + +const compression = NodeHttpCompression.make({ + algorithms: NodeHttpCompression.algorithms, + compressResponse(response, algorithm, options) { + const body = response.body + switch (body._tag) { + case "Stream": { + return Effect.succeed(compressedBody( + response, + HttpBody.stream( + NodeStream.pipeThroughDuplex(body.stream, { + evaluate: () => NodeHttpCompression.compressTransform(algorithm, options) + }), + body.contentType + ) + )) + } + case "Raw": { + const readable = body.body instanceof Readable + ? body.body + : Readable.fromWeb(new Response(body.body as BodyInit).body as any) + const transform = NodeHttpCompression.compressTransform(algorithm, options) + readable.on("error", (cause) => transform.destroy(cause)) + transform.on("error", (cause) => readable.destroy(cause)) + transform.on("close", () => readable.destroy()) + return Effect.succeed( + compressedBody(response, HttpBody.raw(readable.pipe(transform), { contentType: body.contentType })) + ) + } + default: { + return Effect.succeed(response) + } + } + } +}) /** * Creates the Node `HttpPlatform`, serving file responses from Node readable @@ -27,6 +74,8 @@ import * as NodeFileSystem from "./NodeFileSystem.ts" * @since 4.0.0 */ export const make = Platform.make({ + platform: "node", + compression, fileResponse(path, status, statusText, headers, start, end, contentLength) { const stream = contentLength === 0 ? Readable.from([]) diff --git a/repos/effect/packages/platform-node/src/NodeHttpServer.ts b/repos/effect/packages/platform-node/src/NodeHttpServer.ts index d9f8f59e63..ed106d3b8a 100644 --- a/repos/effect/packages/platform-node/src/NodeHttpServer.ts +++ b/repos/effect/packages/platform-node/src/NodeHttpServer.ts @@ -62,6 +62,26 @@ import * as NodeMultipart from "./NodeMultipart.ts" import * as NodeServices from "./NodeServices.ts" import { NodeWS } from "./NodeSocket.ts" +/** + * Options accepted by the Node `HttpServer` constructors and layers. + * + * @category options + * @since 4.0.0 + */ +export interface Options extends Net.ListenOptions { + readonly disablePreemptiveShutdown?: boolean | undefined + readonly gracefulShutdownTimeout?: Duration.Input | undefined + /** + * Options forwarded to the underlying `ws` `WebSocketServer`, minus the + * wiring options the server manages itself. Use this to enable + * `permessage-deflate` compression or tune payload limits, e.g. + * `websocket: { perMessageDeflate: true }`. + */ + readonly websocket?: + | Omit + | undefined +} + /** * Creates a scoped `HttpServer` from a Node `http.Server`, starts listening * with the supplied options, registers request and upgrade handling, and closes @@ -72,10 +92,7 @@ import { NodeWS } from "./NodeSocket.ts" */ export const make = Effect.fnUntraced(function*( evaluate: LazyArg, - options: Net.ListenOptions & { - readonly disablePreemptiveShutdown?: boolean | undefined - readonly gracefulShutdownTimeout?: Duration.Input | undefined - } + options: Options ) { const scope = yield* Effect.scope const server = evaluate() @@ -116,7 +133,7 @@ export const make = Effect.fnUntraced(function*( const address = server.address()! const wss = yield* Effect.acquireRelease( - Effect.sync(() => new NodeWS.WebSocketServer({ noServer: true })), + Effect.sync(() => new NodeWS.WebSocketServer({ ...options.websocket, noServer: true })), (wss) => Effect.callback((resume) => { wss.close(() => resume(Effect.void)) @@ -189,9 +206,8 @@ export const makeHandler = < nodeRequest: Http.IncomingMessage, nodeResponse: Http.ServerResponse ) { - const map = new Map(services.mapUnsafe) - map.set(HttpServerRequest.key, new ServerRequestImpl(nodeRequest, nodeResponse)) - const fiber = Fiber.runIn(Effect.runForkWith(Context.makeUnsafe(map))(handled), options.scope) + const context = Context.add(services, HttpServerRequest, new ServerRequestImpl(nodeRequest, nodeResponse)) + const fiber = Fiber.runIn(Effect.runForkWith(context as Context.Context)(handled), options.scope) nodeResponse.on("close", () => { if (!nodeResponse.writableEnded) { fiber.interruptUnsafe(parent.id, ClientAbort.annotation) @@ -256,9 +272,13 @@ export const makeUpgradeHandler = < (ws) => Effect.sync(() => ws.close()) ) )) - const map = new Map(services.mapUnsafe) - map.set(HttpServerRequest.key, new ServerRequestImpl(nodeRequest, nodeResponse, upgradeEffect)) - const fiber = Fiber.runIn(Effect.runForkWith(Context.makeUnsafe(map))(handledApp), options.scope) + const context = Context.add( + services, + HttpServerRequest, + new ServerRequestImpl(nodeRequest, nodeResponse, upgradeEffect) + ) + const fiber = Fiber.runIn(Effect.runForkWith(context as Context.Context)(handledApp), options.scope) + socket.on("error", () => {}) socket.on("close", () => { if (!socket.writableEnded) { fiber.interruptUnsafe(parent.id, ClientAbort.annotation) @@ -330,8 +350,9 @@ class ServerRequestImpl extends NodeHttpIncomingMessage impleme return this.source.url! } + private cachedMethod: HttpMethod | undefined get method(): HttpMethod { - return this.source.method!.toUpperCase() as HttpMethod + return this.cachedMethod ??= this.source.method!.toUpperCase() as HttpMethod } override get headers(): Headers.Headers { @@ -397,10 +418,7 @@ class ServerRequestImpl extends NodeHttpIncomingMessage impleme */ export const layerServer: ( evaluate: LazyArg>, - options: Net.ListenOptions & { - readonly disablePreemptiveShutdown?: boolean | undefined - readonly gracefulShutdownTimeout?: Duration.Input | undefined - } + options: Options ) => Layer.Layer = flow(make, Layer.effect(HttpServer.HttpServer)) /** @@ -427,10 +445,7 @@ export const layerHttpServices: Layer.Layer< */ export const layer = ( evaluate: LazyArg, - options: Net.ListenOptions & { - readonly disablePreemptiveShutdown?: boolean | undefined - readonly gracefulShutdownTimeout?: Duration.Input | undefined - } + options: Options ): Layer.Layer< HttpServer.HttpServer | NodeServices.NodeServices | HttpPlatform.HttpPlatform | Etag.Generator, ServeError @@ -450,12 +465,7 @@ export const layer = ( */ export const layerConfig = ( evaluate: LazyArg, - options: Config.Wrap< - Net.ListenOptions & { - readonly disablePreemptiveShutdown?: boolean | undefined - readonly gracefulShutdownTimeout?: Duration.Input | undefined - } - > + options: Config.Wrap ): Layer.Layer< HttpServer.HttpServer | NodeServices.NodeServices | HttpPlatform.HttpPlatform | Etag.Generator, ServeError | Config.ConfigError @@ -517,9 +527,20 @@ const handleResponse = ( if (request.method === "HEAD") { nodeResponse.writeHead(response.status, headers) - return Effect.callback((resume) => { - nodeResponse.end(() => resume(Effect.void)) - }) + return Effect.andThen( + cancelResponseBody(response.body), + Effect.callback((resume) => { + let completed = false + const done = () => { + if (completed) return + completed = true + nodeResponse.off("close", done) + resume(Effect.void) + } + nodeResponse.once("close", done) + nodeResponse.end(done) + }) + ) } const body = response.body switch (body._tag) { @@ -603,17 +624,9 @@ const handleResponse = ( return body.stream.pipe( Stream.orDie, Stream.runForEachArray((array) => { - let needDrain = false - for (let i = 0; i < array.length; i++) { - const written = nodeResponse.write(array[i]) - if (!written && !needDrain) { - needDrain = true - drainLatch.closeUnsafe() - } else if (written && needDrain) { - needDrain = false - } - } - if (!needDrain) return Effect.void + const chunk = array.length > 1 ? Buffer.concat(array) : array[0] + if (nodeResponse.write(chunk)) return Effect.void + drainLatch.closeUnsafe() return drainLatch.await }), Effect.interruptible, @@ -626,6 +639,14 @@ const handleResponse = ( } } +const cancelResponseBody = (body: HttpServerResponse["body"]): Effect.Effect => { + const stream = body._tag === "Raw" ? body.body : undefined + if (stream instanceof Readable) { + return Effect.sync(() => stream.destroy()) + } + return Effect.void +} + const handleCause = ( nodeResponse: Http.ServerResponse, originalResponse: HttpServerResponse diff --git a/repos/effect/packages/platform-node/src/NodeMultipart.ts b/repos/effect/packages/platform-node/src/NodeMultipart.ts index c0f411420f..e7a43e109b 100644 --- a/repos/effect/packages/platform-node/src/NodeMultipart.ts +++ b/repos/effect/packages/platform-node/src/NodeMultipart.ts @@ -17,11 +17,12 @@ import type * as Path from "effect/Path" import type * as Scope from "effect/Scope" import * as Stream from "effect/Stream" import * as Multipart from "effect/unstable/http/Multipart" -import * as MP from "effect/unstable/http/Multipasta/Node" +import * as MultipartParser from "effect/unstable/http/MultipartParser" import * as NFS from "node:fs" import type { IncomingHttpHeaders } from "node:http" import type { Readable } from "node:stream" import * as NodeStreamP from "node:stream/promises" +import * as MP from "./NodeMultipartParser.ts" import * as NodeStream from "./NodeStream.ts" /** @@ -103,13 +104,13 @@ class FieldImpl extends PartBase implements Multipart.Field { readonly value: string constructor( - info: MP.PartInfo, + info: MultipartParser.PartInfo, value: Uint8Array ) { super() this.key = info.name this.contentType = info.contentType - this.value = MP.decodeField(info, value) + this.value = MultipartParser.decodeField(info, value) } toJSON(): unknown { @@ -158,7 +159,7 @@ class FileImpl extends PartBase implements Multipart.File { } } -function convertError(cause: MP.MultipartError): Multipart.MultipartError { +function convertError(cause: MultipartParser.MultipartError): Multipart.MultipartError { switch (cause._tag) { case "ReachedLimit": { switch (cause.limit) { diff --git a/repos/effect/packages/platform-node/src/NodeMultipartParser.ts b/repos/effect/packages/platform-node/src/NodeMultipartParser.ts new file mode 100644 index 0000000000..b1dfbd5cbe --- /dev/null +++ b/repos/effect/packages/platform-node/src/NodeMultipartParser.ts @@ -0,0 +1,184 @@ +/** + * Node.js streams adapter for the low-level multipart parser. + * + * @since 4.0.0 + */ +// oxlint-disable typescript/no-unsafe-declaration-merging +/// +import type { BaseConfig, MultipartError, Parser, PartInfo } from "effect/unstable/http/MultipartParser" +import { make as makeParser } from "effect/unstable/http/MultipartParser" +import type { IncomingHttpHeaders } from "node:http" +import { Duplex, Readable } from "node:stream" + +/** + * A part emitted by the Node.js multipart parser. + * + * @category models + * @since 4.0.0 + */ +export type Part = Field | FileStream + +/** + * A parsed multipart field. + * + * @category models + * @since 4.0.0 + */ +export interface Field { + readonly _tag: "Field" + readonly info: PartInfo + readonly value: Uint8Array +} + +/** + * A Node.js duplex stream that emits parsed multipart parts. + * + * @category models + * @since 4.0.0 + */ +export interface MultipartStream extends Duplex { + [Symbol.asyncIterator](): NodeJS.AsyncIterator + + on(event: "field", listener: (field: Field) => void): this + on(event: "file", listener: (file: FileStream) => void): this + on(event: "close", listener: () => void): this + on(event: "data", listener: (part: Part) => void): this + on(event: "drain", listener: () => void): this + on(event: "end", listener: () => void): this + on(event: "error", listener: (err: MultipartError) => void): this + on(event: "finish", listener: () => void): this + on(event: "pause", listener: () => void): this + on(event: "pipe", listener: (src: Readable) => void): this + on(event: "readable", listener: () => void): this + on(event: "resume", listener: () => void): this + on(event: "unpipe", listener: (src: Readable) => void): this + on(event: string | symbol, listener: (...args: Array) => void): this + + read(size?: number): Part | null +} + +/** + * Configuration for the Node.js multipart parser. + * + * @category models + * @since 4.0.0 + */ +export type NodeConfig = Omit & { + readonly headers: IncomingHttpHeaders +} + +/** + * A Node.js duplex stream that parses multipart input. + * + * @category models + * @since 4.0.0 + */ +export class MultipartStream extends Duplex { + private _parser: Parser + _canWrite = true + private _writeCallback: (() => void) | undefined + + constructor(config: NodeConfig) { + super({ readableObjectMode: true }) + let currentError: MultipartError | undefined + let currentFile: FileStream | undefined + this._parser = makeParser({ + ...(config as any), + onField: (info, value) => { + if (currentError !== undefined) return + const field: Field = { _tag: "Field", info, value } + this.push(field) + this.emit("field", field) + }, + onFile: (info) => { + if (currentError !== undefined) return (_) => {} + const file = new FileStream(info, this) + currentFile = file + this.push(file) + this.emit("file", file) + return (chunk) => { + if (currentError !== undefined) return + this._canWrite = file.push(chunk) + if (chunk === null && !this._canWrite) { + currentFile = undefined + this._resume() + } + } + }, + onError: (error) => { + this.emit("error", error) + currentFile?.emit("error", error) + currentError = error + }, + onDone: () => { + this.push(null) + } + }) + } + + _resume() { + this._canWrite = true + if (this._writeCallback !== undefined) { + const callback = this._writeCallback + this._writeCallback = undefined + callback() + } + } + + override _read(_size: number) {} + + override _write( + chunk: any, + encoding: BufferEncoding, + callback: (error?: Error | null | undefined) => void + ): void { + this._parser.write( + chunk instanceof Uint8Array ? chunk : Buffer.from(chunk, encoding) + ) + if (this._canWrite) { + callback() + } else { + this._writeCallback = callback + } + } + + override _final(callback: (error?: Error | null | undefined) => void): void { + this._parser.end() + callback() + } +} + +/** + * Creates a Node.js multipart parser stream. + * + * @category constructors + * @since 4.0.0 + */ +export const make = (config: NodeConfig): MultipartStream => new MultipartStream(config) + +/** + * A readable stream containing a parsed multipart file. + * + * @category models + * @since 4.0.0 + */ +export class FileStream extends Readable { + readonly _tag = "File" + readonly filename: string | undefined + readonly info: PartInfo + private _parent: MultipartStream + constructor( + info: PartInfo, + parent: MultipartStream + ) { + super() + this.info = info + this._parent = parent + this.filename = info.filename + } + override _read(_size: number) { + if (this._parent._canWrite === false) { + this._parent._resume() + } + } +} diff --git a/repos/effect/packages/platform-node/src/NodeWorkerRunner.ts b/repos/effect/packages/platform-node/src/NodeWorkerRunner.ts index b6ff66e501..f41ef5e5bc 100644 --- a/repos/effect/packages/platform-node/src/NodeWorkerRunner.ts +++ b/repos/effect/packages/platform-node/src/NodeWorkerRunner.ts @@ -16,6 +16,7 @@ import * as Effect from "effect/Effect" import * as Exit from "effect/Exit" import * as Fiber from "effect/Fiber" import * as Layer from "effect/Layer" +import * as Scope from "effect/Scope" import { WorkerError, WorkerReceiveError, WorkerSpawnError } from "effect/unstable/workers/WorkerError" import * as WorkerRunner from "effect/unstable/workers/WorkerRunner" import * as WorkerThreads from "node:worker_threads" @@ -56,7 +57,8 @@ export const layer: Layer.Layer = Layer.succe runFork(Effect.logError("unhandled error in worker", exit.cause)) } } - ;(WorkerThreads.parentPort ?? process).on("message", (message: WorkerRunner.PlatformMessage) => { + const port = WorkerThreads.parentPort ?? process + function onMessage(message: WorkerRunner.PlatformMessage) { if (message[0] === 0) { const result = handler(0, message[1]) if (Effect.isEffect(result)) { @@ -72,32 +74,46 @@ export const layer: Layer.Layer = Layer.succe } Deferred.doneUnsafe(closeLatch, Exit.void) } - }) + } + port.on("message", onMessage) - if (WorkerThreads.parentPort) { - WorkerThreads.parentPort.on("messageerror", (cause) => { - Deferred.doneUnsafe( - closeLatch, - new WorkerError({ - reason: new WorkerReceiveError({ - message: "received messageerror event", - cause - }) + function onMessageError(cause: unknown) { + Deferred.doneUnsafe( + closeLatch, + new WorkerError({ + reason: new WorkerReceiveError({ + message: "received messageerror event", + cause }) - ) - }) - WorkerThreads.parentPort.on("error", (cause) => { - Deferred.doneUnsafe( - closeLatch, - new WorkerError({ - reason: new WorkerReceiveError({ - message: "received messageerror event", - cause - }) + }) + ) + } + function onError(cause: unknown) { + Deferred.doneUnsafe( + closeLatch, + new WorkerError({ + reason: new WorkerReceiveError({ + message: "received error event", + cause }) - ) - }) + }) + ) } + if (WorkerThreads.parentPort) { + WorkerThreads.parentPort.on("messageerror", onMessageError) + WorkerThreads.parentPort.on("error", onError) + } + + yield* Scope.addFinalizer( + scope, + Effect.sync(() => { + port.off("message", onMessage) + if (WorkerThreads.parentPort) { + WorkerThreads.parentPort.off("messageerror", onMessageError) + WorkerThreads.parentPort.off("error", onError) + } + }) + ) sendUnsafe(0, [0]) diff --git a/repos/effect/packages/platform-node/src/Undici.ts b/repos/effect/packages/platform-node/src/Undici.ts index 90a7560596..0caaab4095 100644 --- a/repos/effect/packages/platform-node/src/Undici.ts +++ b/repos/effect/packages/platform-node/src/Undici.ts @@ -17,13 +17,13 @@ import Undici from "undici" /** - * @category Undici + * @category re-exports * @since 4.0.0 */ export * from "undici" /** - * @category Undici + * @category re-exports * @since 4.0.0 */ export default Undici diff --git a/repos/effect/packages/platform-node/src/index.ts b/repos/effect/packages/platform-node/src/index.ts index c626aaed1a..c006c3d54b 100644 --- a/repos/effect/packages/platform-node/src/index.ts +++ b/repos/effect/packages/platform-node/src/index.ts @@ -64,6 +64,11 @@ export * as NodeHttpServerRequest from "./NodeHttpServerRequest.ts" */ export * as NodeMultipart from "./NodeMultipart.ts" +/** + * @since 4.0.0 + */ +export * as NodeMultipartParser from "./NodeMultipartParser.ts" + /** * @since 4.0.0 */ diff --git a/repos/effect/packages/platform-node/test/HttpApi.test.ts b/repos/effect/packages/platform-node/test/HttpApi.test.ts index bece20878b..f00a4f505b 100644 --- a/repos/effect/packages/platform-node/test/HttpApi.test.ts +++ b/repos/effect/packages/platform-node/test/HttpApi.test.ts @@ -170,7 +170,7 @@ describe("HttpApi", () => { const MLive = Layer.succeed( M, (_, { endpoint }) => - endpoint.name === "unauthorized" + endpoint.identifier === "unauthorized" ? Effect.fail(new HttpApiError.Unauthorized({})) : Effect.fail(new HttpApiError.Forbidden({})) ) @@ -222,7 +222,7 @@ describe("HttpApi", () => { const MClient = HttpApiMiddleware.layerClient( M, Effect.fnUntraced(function*({ endpoint, group, next, request }) { - yield* Ref.set(metadata, { group: group.identifier, endpoint: endpoint.name }) + yield* Ref.set(metadata, { group: group.identifier, endpoint: endpoint.identifier }) return yield* next(HttpClientRequest.setHeader(request, "x-client", "from-client")) }) ) @@ -307,7 +307,7 @@ describe("HttpApi", () => { }) it.effect("required client middleware can fail with typed clientError", () => { - class ClientFailure extends Schema.ErrorClass("ClientFailure")({ + class ClientFailure extends Schema.Error("ClientFailure")({ _tag: Schema.tag("ClientFailure") }) {} @@ -597,14 +597,14 @@ describe("HttpApi", () => { const M1Live = Layer.succeed( M1, (effect, { endpoint, group }) => - Effect.sync(() => calls.push(`m1:${group.identifier}.${endpoint.name}`)).pipe( + Effect.sync(() => calls.push(`m1:${group.identifier}.${endpoint.identifier}`)).pipe( Effect.flatMap(() => effect) ) ) const M2Live = Layer.succeed( M2, (effect, { endpoint, group }) => - Effect.sync(() => calls.push(`m2:${group.identifier}.${endpoint.name}`)).pipe( + Effect.sync(() => calls.push(`m2:${group.identifier}.${endpoint.identifier}`)).pipe( Effect.flatMap(() => effect) ) ) @@ -1421,7 +1421,7 @@ describe("HttpApi", () => { }) it.effect("error from plain text", () => { - class RateLimitError extends Schema.ErrorClass("RateLimitError")({ + class RateLimitError extends Schema.Error("RateLimitError")({ _tag: Schema.tag("RateLimitError"), message: Schema.String }) {} @@ -1488,17 +1488,17 @@ describe("HttpApi", () => { }) }) -class UserError extends Schema.ErrorClass("UserError")({ +class UserError extends Schema.Error("UserError")({ _tag: Schema.tag("UserError") }, { httpApiStatus: 400 }) {} -class GroupError extends Schema.ErrorClass("GroupError")({ +class GroupError extends Schema.Error("GroupError")({ _tag: Schema.tag("GroupError") }, { httpApiStatus: 418 }) {} -class NoStatusError extends Schema.ErrorClass("NoStatusError")({ +class NoStatusError extends Schema.Error("NoStatusError")({ _tag: Schema.tag("NoStatusError") }) {} diff --git a/repos/effect/packages/platform-node/test/HttpStaticServerConditional.test.ts b/repos/effect/packages/platform-node/test/HttpStaticServerConditional.test.ts index b1c8449f48..695ba74678 100644 --- a/repos/effect/packages/platform-node/test/HttpStaticServerConditional.test.ts +++ b/repos/effect/packages/platform-node/test/HttpStaticServerConditional.test.ts @@ -55,12 +55,19 @@ const fileInfo: FileSystem.File.Info = { blocks: Option.none() } +const stubCompression: HttpPlatform.Compression = { + algorithms: new Set(), + compressResponse: Effect.succeed +} + const makeHandler = async () => { const fileSystem = FileSystem.makeNoop({ stat: (path) => path === filePath ? Effect.succeed(fileInfo) : Effect.fail(notFoundError(path)) }) const httpPlatform = HttpPlatform.HttpPlatform.of({ + platform: "node", + compression: stubCompression, fileResponse: (_path, options) => Effect.succeed(HttpServerResponse.text(fileBody, { status: options?.status, @@ -100,6 +107,8 @@ const makeFailingApp = async (options: { }) const httpPlatform = HttpPlatform.HttpPlatform.of({ + platform: "node", + compression: stubCompression, fileResponse: (_path, fileOptions) => { if (options.fileResponseError !== undefined) { return Effect.fail(options.fileResponseError) @@ -137,6 +146,8 @@ const makeLayerHandler = (options: { } }) const httpPlatform = HttpPlatform.HttpPlatform.of({ + platform: "node", + compression: stubCompression, fileResponse: () => options.fileResponseError !== undefined ? Effect.fail(options.fileResponseError) diff --git a/repos/effect/packages/platform-node/test/KeyValueStore.test.ts b/repos/effect/packages/platform-node/test/KeyValueStore.test.ts new file mode 100644 index 0000000000..8f6c7c6b29 --- /dev/null +++ b/repos/effect/packages/platform-node/test/KeyValueStore.test.ts @@ -0,0 +1,50 @@ +import * as NodeFileSystem from "@effect/platform-node/NodeFileSystem" +import * as NodePath from "@effect/platform-node/NodePath" +import { assert, describe, it } from "@effect/vitest" +import * as Effect from "effect/Effect" +import * as FileSystem from "effect/FileSystem" +import * as Layer from "effect/Layer" +import * as KeyValueStore from "effect/unstable/persistence/KeyValueStore" + +const platformLayer = Layer.merge(NodeFileSystem.layer, NodePath.layer) + +describe("KeyValueStore / layerFileSystem", () => { + it.effect("rejects invalid keys without modifying the file system", () => + Effect.gen(function*() { + const fs = yield* FileSystem.FileSystem + const root = yield* fs.makeTempDirectoryScoped() + const directory = `${root}/store` + const sibling = `${root}/sibling.txt` + yield* fs.makeDirectory(directory) + yield* fs.writeFileString(sibling, "sibling") + + yield* Effect.gen(function*() { + const store = yield* KeyValueStore.KeyValueStore + + for (const key of ["", ".", ".."]) { + const operations = [ + ["get", Effect.asVoid(store.get(key))], + ["getUint8Array", Effect.asVoid(store.getUint8Array(key))], + ["set", Effect.asVoid(store.set(key, "value"))], + ["remove", Effect.asVoid(store.remove(key))], + ["has", Effect.asVoid(store.has(key))] + ] as const + + for (const [method, operation] of operations) { + const error = yield* Effect.flip(operation) + assert.instanceOf(error, KeyValueStore.KeyValueStoreError) + assert.strictEqual(error.method, method) + assert.strictEqual(error.key, key) + } + } + }).pipe( + Effect.provide(KeyValueStore.layerFileSystem(directory).pipe(Layer.provide(platformLayer))) + ) + + assert.isTrue(yield* fs.exists(directory)) + assert.deepStrictEqual(yield* fs.readDirectory(directory), []) + assert.strictEqual(yield* fs.readFileString(sibling), "sibling") + }).pipe( + Effect.provide(NodeFileSystem.layer) + )) +}) diff --git a/repos/effect/packages/platform-node/test/MultipartParser.test.ts b/repos/effect/packages/platform-node/test/MultipartParser.test.ts new file mode 100644 index 0000000000..3a5a38a1ee --- /dev/null +++ b/repos/effect/packages/platform-node/test/MultipartParser.test.ts @@ -0,0 +1,885 @@ +import * as Node from "@effect/platform-node/NodeMultipartParser" +import * as Multipart from "effect/unstable/http/MultipartParser" +import { assert, describe, expectTypeOf, test } from "vitest" + +type Expected = Array< + | [type: "field", name: string, value: string, contentType: string] + | [ + type: "file", + name: string, + bytesReceived: number, + filename: string, + contentType: string + ] +> + +interface MultipartCase { + readonly config?: Partial + readonly name: string + readonly source: ReadonlyArray + readonly boundary: string + readonly expected: Expected + readonly errors?: ReadonlyArray +} + +const cases: ReadonlyArray = [ + { + source: [ + [ + "-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k", + "Content-Disposition: form-data; name=\"file_name_0\"", + "", + "super alpha file", + "-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k", + "Content-Disposition: form-data; name=\"file_name_1\"", + "", + "super beta file", + "-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k", + "Content-Disposition: form-data; name=\"upload_file_0\"; filename=\"1k_a.dat\"", + "Content-Type: application/octet-stream", + "", + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + "-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k", + "Content-Disposition: form-data; name=\"upload_file_1\"; filename=\"1k_b.dat\"", + "Content-Type: application/octet-stream", + "", + "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB", + "-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--" + ].join("\r\n") + ], + boundary: "---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k", + expected: [ + ["field", "file_name_0", "super alpha file", "text/plain"], + ["field", "file_name_1", "super beta file", "text/plain"], + ["file", "upload_file_0", 1023, "1k_a.dat", "application/octet-stream"], + ["file", "upload_file_1", 1023, "1k_b.dat", "application/octet-stream"] + ], + name: "Fields and files" + }, + { + source: [ + "-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k\r\n", + `Content-Disposition: form-data; name="file_name_0"\r\n`, + "\r\n", + "super alpha file\r\n", + "-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k\r\n", + "Content-Disposition: form-data; name=\"file_name_1\"\r\n", + "\r\n", + "super beta file\r\n", + "-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k\r\n", + `Content-Disposition: form-data; name="upload_file_0"; filename="1k_a.dat"\r\n`, + "Content-Type: application/octet-stream\r\n", + "\r\n", + "A".repeat(1024 * 1024), + "A".repeat(1024 * 1024), + "A".repeat(1024 * 1024), + "A".repeat(1024 * 1024), + "A".repeat(1024 * 1024), + "A".repeat(1024 * 1024), + "A".repeat(1024 * 1024), + "A".repeat(1024 * 1024), + "A".repeat(1024 * 1024), + "A".repeat(1024 * 1024), + "\r\n-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--" + ], + boundary: "---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k", + expected: [ + ["field", "file_name_0", "super alpha file", "text/plain"], + ["field", "file_name_1", "super beta file", "text/plain"], + [ + "file", + "upload_file_0", + 1024 * 1024 * 10, + "1k_a.dat", + "application/octet-stream" + ] + ], + name: "Fields and large file" + }, + { + source: [ + "-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k\r\n", + "Content-Disposition: form-data; name=\"file_name_0\"\r\n", + "\r\n", + "super alpha file\r\n", + "-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--" + ], + boundary: "---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k", + expected: [["field", "file_name_0", "super alpha file", "text/plain"]], + name: "Headers over multiple chunks", + errors: [] + }, + { + source: [ + "-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k", + "Content-Disposition: form-data; name=\"file_name_0\"", + "", + "super alpha file", + "-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--" + ] + .join("\r\n") + .split(""), + + boundary: "---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k", + expected: [["field", "file_name_0", "super alpha file", "text/plain"]], + name: "Headers over single byte chunks", + errors: [] + }, + { + source: [ + [ + "------WebKitFormBoundaryTB2MiQ36fnSJlrhY", + "Content-Disposition: form-data; name=\"cont\"", + "", + "some random content", + "------WebKitFormBoundaryTB2MiQ36fnSJlrhY", + "Content-Disposition: form-data; name=\"pass\"", + "", + "some random pass", + "------WebKitFormBoundaryTB2MiQ36fnSJlrhY", + "Content-Disposition: form-data; name=\"bit\"", + "", + "2", + "------WebKitFormBoundaryTB2MiQ36fnSJlrhY--" + ].join("\r\n") + ], + boundary: "----WebKitFormBoundaryTB2MiQ36fnSJlrhY", + expected: [ + ["field", "cont", "some random content", "text/plain"], + ["field", "pass", "some random pass", "text/plain"], + ["field", "bit", "2", "text/plain"] + ], + name: "Fields only" + }, + { + source: [ + [ + "------WebKitFormBoundaryTB2MiQ36fnSJlrhY", + "Content-Disposition: form-data; name=\"first\"", + "", + "A".repeat(32), + "------WebKitFormBoundaryTB2MiQ36fnSJlrhY", + "Content-Disposition: form-data; name=\"second\"", + "", + "B".repeat(32), + "------WebKitFormBoundaryTB2MiQ36fnSJlrhY--" + ].join("\r\n") + ], + boundary: "----WebKitFormBoundaryTB2MiQ36fnSJlrhY", + config: { + maxPartSize: 100 + }, + expected: [ + ["field", "first", "A".repeat(32), "text/plain"], + ["field", "second", "B".repeat(32), "text/plain"] + ], + errors: [], + name: "Resets maxPartSize between field parts" + }, + { + source: [], + boundary: "----WebKitFormBoundaryTB2MiQ36fnSJlrhY", + expected: [], + errors: ["EndNotReached"], + name: "No fields and no files" + }, + { + source: [ + "--boundary\r\ncontent-disposition: form-data; nam" + ], + boundary: "boundary", + expected: [], + errors: ["EndNotReached"], + name: "Truncated mid-header" + }, + { + source: [ + [ + "-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k", + "Content-Disposition: form-data; name=\"file_name_0\"", + "", + "super alpha file", + "-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k", + "Content-Disposition: form-data; name=\"upload_file_0\"; filename=\"1k_a.dat\"", + "Content-Type: application/octet-stream", + "", + "ABCDEFGHIJKLMNOPQRSTUVWXYZ", + "-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--" + ].join("\r\n") + ], + boundary: "---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k", + config: { + maxFieldSize: 5 + }, + expected: [], + errors: ["ReachedLimit"], + name: "stops after maxFieldSize is exceeded" + }, + { + source: [ + [ + "-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k", + "Content-Disposition: form-data; name=\"upload_file_0\"; filename=\"1k_a.dat\"", + "Content-Type: application/octet-stream", + "", + "ABCDEFGHIJKLMNOPQRSTUVWXYZ", + "-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--" + ].join("\r\n") + ], + boundary: "---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k", + config: { + maxPartSize: 13 + }, + expected: [], + errors: ["ReachedLimit"], + name: "stops after maxPartSize is exceeded" + }, + { + source: [ + [ + "-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k", + "Content-Disposition: form-data; name=\"file_name_0\"", + "", + "super alpha file", + "-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k", + "Content-Disposition: form-data; name=\"upload_file_0\"; filename=\"1k_a.dat\"", + "Content-Type: application/octet-stream", + "", + "ABCDEFGHIJKLMNOPQRSTUVWXYZ", + "-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--" + ].join("\r\n") + ], + boundary: "---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k", + config: { + maxParts: 0 + }, + expected: [], + errors: ["ReachedLimit"], + name: "stops before the first part when maxParts is 0" + }, + { + source: [ + [ + "-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k", + "Content-Disposition: form-data; name=\"file_name_0\"", + "", + "super alpha file", + "-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k", + "Content-Disposition: form-data; name=\"file_name_1\"", + "", + "super beta file", + "-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k", + "Content-Disposition: form-data; name=\"upload_file_0\"; filename=\"1k_a.dat\"", + "Content-Type: application/octet-stream", + "", + "ABCDEFGHIJKLMNOPQRSTUVWXYZ", + "-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--" + ].join("\r\n") + ], + boundary: "---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k", + config: { + maxParts: 1 + }, + errors: ["ReachedLimit"], + expected: [["field", "file_name_0", "super alpha file", "text/plain"]], + name: "stops after maxParts is exceeded" + }, + { + source: [ + [ + "-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k", + "Content-Disposition: form-data; name=\"upload_file_0\"; filename=\"/absolute/1k_a.dat\"", + "Content-Type: application/octet-stream", + "", + "ABCDEFGHIJKLMNOPQRSTUVWXYZ", + "-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k", + "Content-Disposition: form-data; name=\"upload_file_1\"; filename=\"C:\\absolute\\1k_b.dat\"", + "Content-Type: application/octet-stream", + "", + "ABCDEFGHIJKLMNOPQRSTUVWXYZ", + "-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k", + "Content-Disposition: form-data; name=\"upload_file_2\"; filename=\"relative/1k_c.dat\"", + "Content-Type: application/octet-stream", + "", + "ABCDEFGHIJKLMNOPQRSTUVWXYZ", + "-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--" + ].join("\r\n") + ], + boundary: "---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k", + expected: [ + [ + "file", + "upload_file_0", + 26, + "/absolute/1k_a.dat", + "application/octet-stream" + ], + [ + "file", + "upload_file_1", + 26, + "C:\\absolute\\1k_b.dat", + "application/octet-stream" + ], + [ + "file", + "upload_file_2", + 26, + "relative/1k_c.dat", + "application/octet-stream" + ] + ], + name: "Paths to be preserved" + }, + { + source: [ + [ + "------WebKitFormBoundaryTB2MiQ36fnSJlrhY", + "Content-Disposition: form-data; name=\"cont\"", + "Content-Type: ", + "", + "some random content", + "------WebKitFormBoundaryTB2MiQ36fnSJlrhY", + "Content-Disposition: ", + "", + "some random pass", + "------WebKitFormBoundaryTB2MiQ36fnSJlrhY--" + ].join("\r\n") + ], + boundary: "----WebKitFormBoundaryTB2MiQ36fnSJlrhY", + expected: [ + ["field", "cont", "some random content", "text/plain"], + ["field", "", "some random pass", "text/plain"] + ], + name: "Empty content-type and empty content-disposition" + }, + { + config: { + isFile: (_) => _.name !== "upload_file_0" + }, + source: [ + [ + "-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k", + "Content-Disposition: form-data; name=\"upload_file_0\"; filename=\"blob\"", + "Content-Type: application/json", + "", + "ABCDEFGHIJKLMNOPQRSTUVWXYZ", + "-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--" + ].join("\r\n") + ], + boundary: "---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k", + expected: [ + [ + "field", + "upload_file_0", + "ABCDEFGHIJKLMNOPQRSTUVWXYZ", + "application/json" + ] + ], + name: "Blob uploads should be handled as fields if isFile is provided." + }, + { + config: { + isFile: (_) => _.name !== "upload_file_0" + }, + source: [ + [ + "-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k", + "Content-Disposition: form-data; name=\"upload_file_0\"; filename=\"blob\"", + "Content-Type: application/json", + "", + "ABCDEFGHIJKLMNOPQRSTUVWXYZ", + "-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k", + "Content-Disposition: form-data; name=\"file\"; filename*=utf-8''n%C3%A4me.txt", + "Content-Type: application/octet-stream", + "", + "ABCDEFGHIJKLMNOPQRSTUVWXYZ", + "-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--" + ].join("\r\n") + ], + boundary: "---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k", + expected: [ + [ + "field", + "upload_file_0", + "ABCDEFGHIJKLMNOPQRSTUVWXYZ", + "application/json" + ], + ["file", "file", 26, "näme.txt", "application/octet-stream"] + ], + name: "Blob uploads should be handled as fields if isFile is provided. Other parts should be files." + }, + { + config: { + isFile: (_) => _.name === "upload_file_0" + }, + source: [ + [ + "-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k", + "Content-Disposition: form-data; name=\"upload_file_0\"; filename=\"blob\"", + "Content-Type: application/json", + "", + "ABCDEFGHIJKLMNOPQRSTUVWXYZ", + "-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k", + "Content-Disposition: form-data; name=\"file\"; filename*=utf-8''n%C3%A4me.txt", + "Content-Type: application/octet-stream", + "", + "ABCDEFGHIJKLMNOPQRSTUVWXYZ", + "-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--" + ].join("\r\n") + ], + boundary: "---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k", + expected: [ + ["file", "upload_file_0", 26, "blob", "application/json"], + [ + "field", + "file", + "ABCDEFGHIJKLMNOPQRSTUVWXYZ", + "application/octet-stream" + ] + ], + name: "Blob uploads sould be handled as files if corresponding isFile is provided. Other parts should be fields." + }, + { + source: [ + [ + "-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k", + "Content-Disposition: form-data; name=\"file\"; filename*=utf-8''n%C3%A4me.txt", + "Content-Type: application/octet-stream", + "", + "ABCDEFGHIJKLMNOPQRSTUVWXYZ", + "-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--" + ].join("\r\n") + ], + boundary: "---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k", + expected: [["file", "file", 26, "näme.txt", "application/octet-stream"]], + name: "Unicode filenames" + }, + { + source: [ + [ + "-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k", + "Content-Disposition: form-data; name=\"file\"; filename*=utf-8''%ZZ", + "Content-Type: application/octet-stream", + "", + "ABCDEFGHIJKLMNOPQRSTUVWXYZ", + "-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--" + ].join("\r\n") + ], + boundary: "---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k", + expected: [["file", "file", 26, "%ZZ", "application/octet-stream"]], + name: "Malformed encoded filenames" + }, + { + source: [ + [ + "--asdasdasdasd\r\n", + "Content-Type: text/plain\r\n", + "Content-Disposition: form-data; name=\"foo\"\r\n", + "\r\n", + "asd\r\n", + "--asdasdasdasd--" + ].join(":)") + ], + boundary: "asdasdasdasd", + expected: [], + errors: ["BadHeaders", "EndNotReached"], + name: "Stopped mid-header" + }, + { + source: [ + [ + "------WebKitFormBoundaryTB2MiQ36fnSJlrhY", + "Content-Disposition: form-data; name=\"cont\"", + "Content-Type: application/json", + "", + "{}", + "------WebKitFormBoundaryTB2MiQ36fnSJlrhY--" + ].join("\r\n") + ], + boundary: "----WebKitFormBoundaryTB2MiQ36fnSJlrhY", + expected: [["field", "cont", "{}", "application/json"]], + name: "content-type for fields" + }, + { + source: ["------WebKitFormBoundaryTB2MiQ36fnSJlrhY--\r\n"], + boundary: "----WebKitFormBoundaryTB2MiQ36fnSJlrhY", + expected: [], + name: "empty form" + }, + { + source: [ + [ + "-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k", + "Content-Disposition: form-data; name=\"field1\"", + "content-type: text/plain; charset=utf-8", + "", + "Aufklärung ist der Ausgang des Menschen aus seiner selbstverschuldeten Unmündigkeit.", + "-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k", + "Content-Disposition: form-data; name=\"field2\"", + "content-type: text/plain; charset=iso-8859-1", + "", + "sapere aude!", + "-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--" + ].join("\r\n") + ], + boundary: "---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k", + expected: [ + [ + "field", + "field1", + "Aufklärung ist der Ausgang des Menschen aus seiner selbstverschuldeten Unmündigkeit.", + "text/plain" + ], + ["field", "field2", "sapere aude!", "text/plain"] + ], + name: "Fields and files" + }, + { + source: [ + [ + "------WebKitFormBoundaryzca7IDMnT6QwqBp7", + "Content-Disposition: form-data; name=\"regsubmit\"", + "", + "yes", + "------WebKitFormBoundaryzca7IDMnT6QwqBp7", + "------WebKitFormBoundaryzca7IDMnT6QwqBp7", + "Content-Disposition: form-data; name=\"referer\"", + "", + "http://domainExample/./", + "------WebKitFormBoundaryzca7IDMnT6QwqBp7", + "Content-Disposition: form-data; name=\"activationauth\"", + "", + "", + "------WebKitFormBoundaryzca7IDMnT6QwqBp7", + "Content-Disposition: form-data; name=\"seccodemodid\"", + "", + "member::register", + "------WebKitFormBoundaryzca7IDMnT6QwqBp7--" + ].join("\r\n") + ], + boundary: "----WebKitFormBoundaryzca7IDMnT6QwqBp7", + expected: [ + ["field", "regsubmit", "yes", "text/plain"], + ["field", "referer", "http://domainExample/./", "text/plain"], + ["field", "activationauth", "", "text/plain"], + ["field", "seccodemodid", "member::register", "text/plain"] + ], + name: "one empty part should get ignored" + }, + { + source: [" ------WebKitFormBoundaryTB2MiQ36fnSJlrhY--\r\n"], + boundary: "----WebKitFormBoundaryTB2MiQ36fnSJlrhY", + expected: [], + errors: ["EndNotReached"], + name: "empty form with preceding whitespace" + }, + { + source: ["------WebKitFormBoundaryTB2MiQ36fnSJlrhY--\r\n"], + boundary: "----WebKitFormBoundaryTB2MiQ36fnSJlrhYY", + expected: [], + errors: ["EndNotReached"], + name: "empty form with wrong boundary (extra Y)" + }, + { + source: [ + [ + "------WebKitFormBoundaryzca7IDMnT6QwqBp7", + "Content-Disposition: form-data; name=\"regsubmit\"", + "", + "yes", + "------WebKitFormBoundaryzca7IDMnT6QwqBp7", + "------WebKitFormBoundaryzca7IDMnT6QwqBp7", + "------WebKitFormBoundaryzca7IDMnT6QwqBp7", + "------WebKitFormBoundaryzca7IDMnT6QwqBp7", + "Content-Disposition: form-data; name=\"referer\"", + "", + "http://domainExample/./", + "------WebKitFormBoundaryzca7IDMnT6QwqBp7", + "Content-Disposition: form-data; name=\"activationauth\"", + "", + "", + "------WebKitFormBoundaryzca7IDMnT6QwqBp7", + "Content-Disposition: form-data; name=\"seccodemodid\"", + "", + "member::register", + "------WebKitFormBoundaryzca7IDMnT6QwqBp7--" + ].join("\r\n") + ], + boundary: "----WebKitFormBoundaryzca7IDMnT6QwqBp7", + expected: [ + ["field", "regsubmit", "yes", "text/plain"], + ["field", "referer", "http://domainExample/./", "text/plain"], + ["field", "activationauth", "", "text/plain"], + ["field", "seccodemodid", "member::register", "text/plain"] + ], + name: "multiple empty parts should get ignored" + } +] + +describe("multipart", () => { + test.each(cases)("$name", (opts) => { + const parts: Expected = [] + const errors: Array = [] + + const parser = Multipart.make({ + ...opts.config, + headers: { + "content-type": "multipart/form-data; boundary=" + opts.boundary + }, + onFile: (info) => { + let size = 0 + return (chunk) => { + if (chunk) { + size += chunk.length + } else { + parts.push([ + "file", + info.name, + size, + info.filename!, + info.contentType + ]) + } + } + }, + onField: (info, value) => { + parts.push([ + "field", + info.name, + Multipart.decodeField(info, value), + info.contentType + ]) + }, + onError: (error) => { + errors.push(error._tag) + }, + onDone: () => {} + }) + + opts.source.forEach((chunk) => { + parser.write(new TextEncoder().encode(chunk)) + }) + parser.end() + + assert.deepStrictEqual(opts.expected, parts) + if (opts.errors) { + assert.deepEqual(opts.errors, errors) + } + }) +}) + +describe("node api", () => { + test("exposes missing filenames as undefined", () => { + const parser = Node.make({ + headers: { + "content-type": "multipart/form-data; boundary=boundary" + } + }) + let emitted = false + parser.on("file", (file) => { + emitted = true + expectTypeOf(file.filename).toEqualTypeOf() + assert.strictEqual(file.filename, undefined) + file.resume() + }) + + parser.write( + new TextEncoder().encode( + "--boundary\r\nContent-Disposition: form-data; name=\"file\"\r\nContent-Type: application/octet-stream\r\n\r\ncontent\r\n--boundary--" + ) + ) + parser.end() + + assert.isTrue(emitted) + parser.destroy() + }) + + test("stops sending file chunks after an error", () => { + const parser = Node.make({ + headers: { + "content-type": "multipart/form-data; boundary=boundary" + }, + maxPartSize: 100 + }) + let file: Node.FileStream | undefined + parser.on("file", (part) => { + file = part + part.on("error", () => {}) + }) + parser.on("error", () => {}) + + parser.write( + new TextEncoder().encode( + "--boundary\r\nContent-Disposition: form-data; name=\"file\"; filename=\"a.txt\"\r\n\r\nA" + ) + ) + parser.write(new TextEncoder().encode("B".repeat(128))) + + assert.strictEqual(file?.read().toString(), "A") + file?.destroy() + parser.destroy() + }) + + test.each(cases)("$name", (opts) => { + const parts: Expected = [] + const errors: Array = [] + + const parser = Node.make({ + ...opts.config, + headers: { + "content-type": "multipart/form-data; boundary=" + opts.boundary + } + }) + parser.on("field", (field) => { + parts.push([ + "field", + field.info.name, + Multipart.decodeField(field.info, field.value), + field.info.contentType + ]) + }) + parser.on("file", (file) => { + let size = 0 + file.on("data", (chunk) => { + size += chunk.length + }) + file.on("end", () => { + parts.push([ + "file", + file.info.name, + size, + file.info.filename!, + file.info.contentType + ]) + }) + }) + parser.on("error", (error) => { + errors.push(error._tag) + }) + + parser.on("end", () => { + assert.deepStrictEqual(opts.expected, parts) + if (opts.errors) { + assert.deepEqual(opts.errors, errors) + } + }) + + opts.source.forEach((chunk) => { + parser.write(new TextEncoder().encode(chunk)) + }) + parser.end() + }) +}) + +describe("node async-iterable api", () => { + test.each(cases)("$name", async (opts) => { + const parts: Expected = [] + + const parser = Node.make({ + ...opts.config, + headers: { + "content-type": "multipart/form-data; boundary=" + opts.boundary + } + }) + async function read() { + for await (const part of parser) { + if (part._tag === "Field") { + parts.push([ + "field", + part.info.name, + Multipart.decodeField(part.info, part.value), + part.info.contentType + ]) + } else { + let size = 0 + for await (const chunk of part) { + size += chunk.length + } + parts.push([ + "file", + part.info.name, + size, + part.info.filename!, + part.info.contentType + ]) + } + } + } + + const readPromise = read() + + opts.source.forEach((chunk) => { + parser.write(new TextEncoder().encode(chunk)) + }) + parser.end() + + try { + await readPromise + assert.deepStrictEqual(opts.expected, parts) + } catch (err) { + if (opts.errors) { + assert.strictEqual((err as any)._tag, opts.errors[0]) + } + } + }) +}) + +describe("random data", () => { + test("smoke test", () => { + const boundary = "------WebKitFormBoundaryTB2MiQ36fnSJlrhY--" + let seed = 0x9e3779b9 + const random = () => { + seed ^= seed << 13 + seed ^= seed >>> 17 + seed ^= seed << 5 + return (seed >>> 0) / 0x1_0000_0000 + } + for (let i = 0; i < 100; i++) { + const size = Math.round(random() * 1024 * 1024 * 100) + const data = Buffer.alloc(size, i) + let success = false + + const parser = Multipart.make({ + headers: { + "content-type": `multipart/form-data; boundary=${boundary}` + }, + onDone() { + success = true + }, + onFile() { + return () => {} + }, + onField() {}, + onError() {} + }) + + const buffer = Buffer.concat([ + Buffer.from(`--${boundary}\r\n`), + Buffer.from( + "Content-Disposition: form-data; name=\"file\"; filename=\"blob\"\r\n" + ), + Buffer.from("Content-Type: application/octet-stream\r\n"), + Buffer.from("\r\n"), + data, + Buffer.from(`\r\n--${boundary}--`) + ]) + + let cursor = 0 + while (cursor < buffer.length) { + const maxChunkSize = buffer.length - cursor + const chunkSize = Math.max( + 1, + Math.min( + Math.round(random() * 128 * 1024 * 1024), + maxChunkSize + ) + ) + parser.write(buffer.subarray(cursor, cursor + chunkSize)) + cursor += chunkSize + } + parser.end() + assert.isTrue(success) + } + }, 30_000) +}) diff --git a/repos/effect/packages/platform-node/test/NodeHttpClient.test.ts b/repos/effect/packages/platform-node/test/NodeHttpClient.test.ts index ab559f2482..345204bdb7 100644 --- a/repos/effect/packages/platform-node/test/NodeHttpClient.test.ts +++ b/repos/effect/packages/platform-node/test/NodeHttpClient.test.ts @@ -1,12 +1,23 @@ +import { NodeHttpServer } from "@effect/platform-node" import * as NodeClient from "@effect/platform-node/NodeHttpClient" -import { describe, expect, it } from "@effect/vitest" +import { assert, describe, expect, it } from "@effect/vitest" import { Struct } from "effect" import * as Context from "effect/Context" import * as Effect from "effect/Effect" import * as Layer from "effect/Layer" import * as Schema from "effect/Schema" import * as Stream from "effect/Stream" -import { HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http" +import { + HttpBody, + HttpClient, + HttpClientRequest, + HttpClientResponse, + HttpRouter, + HttpServer, + HttpServerRequest, + HttpServerResponse +} from "effect/unstable/http" +import * as Http from "node:http" const Todo = Schema.Struct({ userId: Schema.Number, @@ -17,12 +28,10 @@ const Todo = Schema.Struct({ const TodoWithoutId = Schema.Struct({ ...Struct.omit(Todo.fields, ["id"]) }) +const largeResponseBody = "a".repeat(10 * 1024 * 1024) -const makeJsonPlaceholder = Effect.gen(function*() { - const defaultClient = yield* HttpClient.HttpClient - const client = defaultClient.pipe( - HttpClient.mapRequest(HttpClientRequest.prependUrl("https://jsonplaceholder.typicode.com")) - ) +const makeLocalServerClient = Effect.gen(function*() { + const client = yield* HttpClient.HttpClient const createTodo = (todo: typeof TodoWithoutId.Type) => HttpClientRequest.post("/todos").pipe( HttpClientRequest.schemaBodyJson(TodoWithoutId)(todo), @@ -34,9 +43,35 @@ const makeJsonPlaceholder = Effect.gen(function*() { createTodo } as const }) -interface JsonPlaceholder extends Effect.Success {} -const JsonPlaceholder = Context.Service("test/JsonPlaceholder") -const JsonPlaceholderLive = Layer.effect(JsonPlaceholder)(makeJsonPlaceholder) +interface LocalServerClient extends Effect.Success {} +const LocalServerClient = Context.Service("test/LocalServerClient") +const LocalServerClientLive = Layer.effect(LocalServerClient)(makeLocalServerClient) +const LocalServerRoutes = HttpRouter.serve(HttpRouter.addAll([ + HttpRouter.route( + "GET", + "/todos/1", + Effect.succeed(HttpServerResponse.jsonUnsafe({ + userId: 1, + id: 1, + title: "test", + completed: false + })) + ), + HttpRouter.route( + "POST", + "/todos", + Effect.gen(function*() { + const todo = yield* HttpServerRequest.schemaBodyJson(TodoWithoutId) + return HttpServerResponse.jsonUnsafe({ ...todo, id: 201 }) + }) + ), + HttpRouter.route("GET", "/text", Effect.succeed(HttpServerResponse.text("test"))), + HttpRouter.route("GET", "/large", Effect.succeed(HttpServerResponse.text(largeResponseBody))), + HttpRouter.route("GET", "/hang", Effect.never), + HttpRouter.route("GET", "/redirect", Effect.succeed(HttpServerResponse.redirect("/redirected"))), + HttpRouter.route("GET", "/redirected", Effect.succeed(HttpServerResponse.text("redirected"))), + HttpRouter.route("HEAD", "/todos", Effect.succeed(HttpServerResponse.empty({ status: 200 }))) +])) ;[ { name: "fetch", @@ -51,107 +86,115 @@ const JsonPlaceholderLive = Layer.effect(JsonPlaceholder)(makeJsonPlaceholder) layer: NodeClient.layerUndici } ].forEach(({ layer, name }) => { + const layerTest = HttpServer.layerTestClient.pipe( + Layer.provide(layer), + Layer.provideMerge(NodeHttpServer.layer(Http.createServer, { port: 0 })) + ) + const localServerTestLayer = Layer.merge(LocalServerClientLive, LocalServerRoutes).pipe( + Layer.provideMerge(layerTest) + ) + describe(`NodeHttpClient - ${name}`, () => { - it.effect("google", () => + it.effect("text", () => Effect.gen(function*() { - const response = yield* HttpClient.get("https://www.google.com/").pipe( + const response = yield* HttpClient.get("/text").pipe( Effect.flatMap((_) => _.text) ) - expect(response).toContain("Google") - }).pipe(Effect.provide(layer), flaky)) + expect(response).toBe("test") + }).pipe(Effect.provide(localServerTestLayer))) - it.effect("google followRedirects", () => - flaky( - Effect.gen(function*() { - const client = (yield* HttpClient.HttpClient).pipe( - HttpClient.followRedirects() - ) - const response = yield* client.get("http://google.com/").pipe( - Effect.flatMap((_) => _.text) - ) - expect(response).toContain("Google") - }).pipe(Effect.provide(layer)) - )) + it.effect("local server followRedirects", () => + Effect.gen(function*() { + const client = (yield* HttpClient.HttpClient).pipe( + HttpClient.followRedirects() + ) + const response = yield* client.get("/redirect").pipe( + Effect.flatMap((_) => _.text) + ) + expect(response).toBe("redirected") + }).pipe(Effect.provide(localServerTestLayer))) - it.effect("google stream", () => - flaky( - Effect.gen(function*() { - const client = yield* HttpClient.HttpClient - const response = yield* client.get("https://www.google.com/").pipe( - Effect.map((_) => _.stream), - Stream.unwrap, - Stream.decodeText(), - Stream.mkString - ) - expect(response).toContain("Google") - }).pipe(Effect.provide(layer)) - )) + it.effect("text stream", () => + Effect.gen(function*() { + const client = yield* HttpClient.HttpClient + const response = yield* client.get("/text").pipe( + Effect.map((_) => _.stream), + Stream.unwrap, + Stream.decodeText(), + Stream.mkString + ) + expect(response).toBe("test") + }).pipe(Effect.provide(localServerTestLayer))) - it.effect("jsonplaceholder", () => + it.effect("local server", () => Effect.gen(function*() { - const jp = yield* JsonPlaceholder - const response = yield* jp.client.get("/todos/1").pipe( + const local = yield* LocalServerClient + const response = yield* local.client.get("/todos/1").pipe( Effect.flatMap(HttpClientResponse.schemaBodyJson(Todo)) ) expect(response.id).toBe(1) }).pipe( - Effect.provide(JsonPlaceholderLive.pipe( - Layer.provide(layer) - )), - flaky + Effect.provide(localServerTestLayer) )) - it.effect("jsonplaceholder schemaBodyJson", () => + it.effect("local server schemaBodyJson", () => Effect.gen(function*() { - const jp = yield* JsonPlaceholder - const response = yield* jp.createTodo({ + const local = yield* LocalServerClient + const response = yield* local.createTodo({ userId: 1, title: "test", completed: false }) expect(response.title).toBe("test") }).pipe( - Effect.provide(JsonPlaceholderLive.pipe( - Layer.provide(layer) - )), - flaky + Effect.provide(localServerTestLayer) )) it.effect("head request with schemaJson", () => Effect.gen(function*() { const client = yield* HttpClient.HttpClient - const response = yield* client.head("https://jsonplaceholder.typicode.com/todos").pipe( + const response = yield* client.head("/todos").pipe( Effect.flatMap( HttpClientResponse.schemaJson(Schema.Struct({ status: Schema.Literal(200) })) ) ) expect(response).toEqual({ status: 200 }) - }).pipe(Effect.provide(layer), flaky)) + }).pipe(Effect.provide(localServerTestLayer))) it.live("interrupt", () => Effect.gen(function*() { const client = yield* HttpClient.HttpClient - const response = yield* client.get("https://www.google.com/").pipe( + const response = yield* client.get("/hang").pipe( Effect.flatMap((_) => _.text), Effect.timeout(1), Effect.asSome, Effect.catchTag("TimeoutError", () => Effect.succeedNone) ) expect(response._tag).toEqual("None") - }).pipe(Effect.provide(layer), flaky)) + }).pipe(Effect.provide(localServerTestLayer))) it.effect("close early", () => Effect.gen(function*() { - const response = yield* HttpClient.get("https://www.google.com/") + const response = yield* HttpClient.get("/large") expect(response.status).toBe(200) - }).pipe(Effect.provide(layer), flaky)) + }).pipe(Effect.provide(localServerTestLayer))) }) }) -const flaky = (effect: Effect.Effect) => - effect.pipe( - Effect.timeoutOrElse({ - duration: "10 seconds", - orElse: () => Effect.void - }) - ) +it.live("returns a stream body encoding failure", () => + Effect.gen(function*() { + yield* Effect.gen(function*() { + const request = yield* HttpServerRequest.HttpServerRequest + yield* request.text + return HttpServerResponse.empty() + }).pipe(HttpServer.serveEffect()) + const error = yield* HttpClient.post("/", { + body: HttpBody.stream(Stream.fail("encode failure")) + }).pipe(Effect.timeout("1 second"), Effect.flip) + assert(error._tag === "HttpClientError") + assert.strictEqual(error.reason._tag, "EncodeError") + assert.strictEqual(error.reason.cause, "encode failure") + }).pipe(Effect.provide(HttpServer.layerTestClient.pipe( + Layer.provide(NodeClient.layerNodeHttp), + Layer.provideMerge(NodeHttpServer.layer(Http.createServer, { port: 0 })) + )))) diff --git a/repos/effect/packages/platform-node/test/NodeHttpCompression.test.ts b/repos/effect/packages/platform-node/test/NodeHttpCompression.test.ts new file mode 100644 index 0000000000..6a70c0996a --- /dev/null +++ b/repos/effect/packages/platform-node/test/NodeHttpCompression.test.ts @@ -0,0 +1,208 @@ +import { NodeHttpServer } from "@effect/platform-node" +import * as NodeHttpPlatform from "@effect/platform-node/NodeHttpPlatform" +import { assert, describe, it } from "@effect/vitest" +import * as Effect from "effect/Effect" +import * as Latch from "effect/Latch" +import * as Layer from "effect/Layer" +import * as Stream from "effect/Stream" +import * as HttpClient from "effect/unstable/http/HttpClient" +import * as HttpEffect from "effect/unstable/http/HttpEffect" +import * as HttpMiddleware from "effect/unstable/http/HttpMiddleware" +import * as HttpPlatform from "effect/unstable/http/HttpPlatform" +import * as HttpRouter from "effect/unstable/http/HttpRouter" +import type { HttpServerRequest } from "effect/unstable/http/HttpServerRequest" +import * as HttpServerResponse from "effect/unstable/http/HttpServerResponse" +import * as Crypto from "node:crypto" +import * as Fs from "node:fs" +import * as Os from "node:os" +import * as Path from "node:path" +import { fileURLToPath } from "node:url" +import * as Zlib from "node:zlib" + +const bigJson = JSON.stringify({ text: "All work and no play makes Jack a dull boy. ".repeat(100) }) +const bigJsonApp = Effect.succeed(HttpServerResponse.text(bigJson, { contentType: "application/json" })) + +const zstdSupported = typeof Zlib.zstdCompress === "function" + +type App = Effect.Effect +type CompressionOptions = Parameters[0] + +const withHandler = async ( + app: App, + options: CompressionOptions, + run: (handler: (request: Request) => Promise) => Promise +) => { + const { dispose, handler } = HttpEffect.toWebHandlerLayer( + app as Effect.Effect, + NodeHttpPlatform.layer, + { middleware: HttpMiddleware.compression(options) } + ) + try { + await run(handler) + } finally { + await dispose() + } +} + +const get = ( + handler: (request: Request) => Promise, + headers?: Record +) => handler(new Request("http://localhost/", headers === undefined ? {} : { headers })) + +describe("NodeHttpCompression", () => { + it.effect("advertises supported algorithms", () => + Effect.gen(function*() { + const platform = yield* HttpPlatform.HttpPlatform + assert.isTrue(platform.compression.algorithms.has("gzip")) + assert.isTrue(platform.compression.algorithms.has("deflate")) + assert.isTrue(platform.compression.algorithms.has("br")) + assert.strictEqual(platform.compression.algorithms.has("zstd"), zstdSupported) + }).pipe(Effect.provide(NodeHttpPlatform.layer))) + + it("compresses one-shot bodies asynchronously with gzip and an exact Content-Length", () => + withHandler(bigJsonApp, undefined, async (handler) => { + const response = await get(handler, { "accept-encoding": "gzip" }) + assert.strictEqual(response.headers.get("content-encoding"), "gzip") + assert.strictEqual(response.headers.get("vary"), "Accept-Encoding") + const compressed = new Uint8Array(await response.arrayBuffer()) + assert.strictEqual(response.headers.get("content-length"), compressed.byteLength.toString()) + assert.strictEqual(Zlib.gunzipSync(compressed).toString(), bigJson) + })) + + it("prefers br over gzip in server order", () => + withHandler(bigJsonApp, undefined, async (handler) => { + const response = await get(handler, { "accept-encoding": "gzip, br" }) + assert.strictEqual(response.headers.get("content-encoding"), "br") + const compressed = new Uint8Array(await response.arrayBuffer()) + assert.strictEqual(response.headers.get("content-length"), compressed.byteLength.toString()) + assert.strictEqual(Zlib.brotliDecompressSync(compressed).toString(), bigJson) + })) + + it("compresses with deflate", () => + withHandler(bigJsonApp, undefined, async (handler) => { + const response = await get(handler, { "accept-encoding": "deflate" }) + assert.strictEqual(response.headers.get("content-encoding"), "deflate") + const compressed = new Uint8Array(await response.arrayBuffer()) + assert.strictEqual(response.headers.get("content-length"), compressed.byteLength.toString()) + assert.strictEqual(Zlib.inflateSync(compressed).toString(), bigJson) + })) + + it.skipIf(!zstdSupported)( + "compresses with zstd when opted in", + () => + withHandler(bigJsonApp, { algorithms: ["zstd", "gzip"] }, async (handler) => { + const response = await get(handler, { "accept-encoding": "zstd" }) + assert.strictEqual(response.headers.get("content-encoding"), "zstd") + const compressed = new Uint8Array(await response.arrayBuffer()) + assert.strictEqual(response.headers.get("content-length"), compressed.byteLength.toString()) + assert.strictEqual(Zlib.zstdDecompressSync(compressed).toString(), bigJson) + }) + ) + + it("excludes zstd unless opted in", () => + withHandler(bigJsonApp, undefined, async (handler) => { + const response = await get(handler, { "accept-encoding": "zstd, gzip" }) + assert.strictEqual(response.headers.get("content-encoding"), "gzip") + })) + + it("compresses stream bodies without a Content-Length", () => + withHandler( + Effect.succeed(HttpServerResponse.stream( + Stream.fromArray([new TextEncoder().encode(bigJson)]), + { contentType: "application/json" } + )), + undefined, + async (handler) => { + const response = await get(handler, { "accept-encoding": "gzip" }) + assert.strictEqual(response.headers.get("content-encoding"), "gzip") + assert.strictEqual(response.headers.get("content-length"), null) + assert.strictEqual(Zlib.gunzipSync(new Uint8Array(await response.arrayBuffer())).toString(), bigJson) + } + )) + + it("compresses file responses through the streaming path and weakens the ETag", async () => { + const path = fileURLToPath(new URL("./NodeHttpCompression.test.ts", import.meta.url)) + const contents = Fs.readFileSync(path).toString() + await withHandler( + HttpServerResponse.file(path, { headers: { "content-type": "text/plain" } }), + undefined, + async (handler) => { + const response = await get(handler, { "accept-encoding": "gzip" }) + assert.strictEqual(response.headers.get("content-encoding"), "gzip") + assert.strictEqual(response.headers.get("content-length"), null) + assert.isTrue(response.headers.get("etag")!.startsWith("W/")) + assert.strictEqual(Zlib.gunzipSync(new Uint8Array(await response.arrayBuffer())).toString(), contents) + } + ) + }) + + it.effect("closes compressed file bodies for HEAD requests", () => + Effect.gen(function*() { + const directory = Fs.mkdtempSync(Path.join(Os.tmpdir(), "effect-http-compression-")) + yield* Effect.addFinalizer(() => Effect.sync(() => Fs.rmSync(directory, { recursive: true }))) + const path = Path.join(directory, "random.bin") + Fs.writeFileSync(path, Crypto.randomBytes(1024 * 1024)) + + const closed = yield* Latch.make(false) + const response = yield* HttpServerResponse.file(path, { headers: { "content-type": "text/plain" } }) + .pipe( + Effect.tap((response) => + Effect.sync(() => { + if (response.body._tag !== "Raw") { + throw new Error(`Expected a Raw body, received ${response.body._tag}`) + } + const readable = response.body.body as Fs.ReadStream + readable.once("close", () => closed.openUnsafe()) + }) + ) + ) + + yield* HttpRouter.add("GET", "/file", Effect.succeed(response)).pipe( + (self) => HttpRouter.serve(self, { middleware: HttpMiddleware.compression({ minSize: 0 }) }), + Layer.build + ) + const head = yield* HttpClient.head("/file", { headers: { "accept-encoding": "gzip" } }) + assert.strictEqual(head.status, 200) + const result = yield* closed.await.pipe(Effect.timeoutOption("1 second")) + assert.strictEqual(result._tag, "Some") + }).pipe(Effect.provide(NodeHttpServer.layerTest))) + + it.effect("flushes compressed chunks incrementally over the wire", () => + Effect.gen(function*() { + const latch = yield* Latch.make(false) + const encoder = new TextEncoder() + yield* HttpRouter.add( + "GET", + "/sse", + Effect.succeed(HttpServerResponse.stream( + Stream.concat( + Stream.succeed(encoder.encode("data: first\n\n")), + Stream.concat( + Stream.drain(Stream.fromEffect(latch.await)), + Stream.succeed(encoder.encode("data: second\n\n")) + ) + ), + { contentType: "text/event-stream" } + )) + ).pipe( + (self) => HttpRouter.serve(self, { middleware: HttpMiddleware.compression() }), + Layer.build + ) + const client = yield* HttpClient.HttpClient + const response = yield* client.get("/sse", { headers: { "accept-encoding": "gzip" } }) + assert.strictEqual(response.headers["content-encoding"], "gzip") + // the source stream only ends after the latch opens, and the latch only + // opens once the first decoded event arrives, so this deadlocks unless + // compressed chunks flush incrementally + const received: Array = [] + yield* response.stream.pipe( + Stream.runForEach((chunk) => + Effect.suspend(() => { + received.push(new TextDecoder().decode(chunk)) + return latch.open + }) + ) + ) + assert.strictEqual(received.join(""), "data: first\n\ndata: second\n\n") + }).pipe(Effect.provide(NodeHttpServer.layerTest))) +}) diff --git a/repos/effect/packages/platform-node/test/NodeHttpServer.test.ts b/repos/effect/packages/platform-node/test/NodeHttpServer.test.ts index 401a13c81d..e41d6afdb6 100644 --- a/repos/effect/packages/platform-node/test/NodeHttpServer.test.ts +++ b/repos/effect/packages/platform-node/test/NodeHttpServer.test.ts @@ -1,15 +1,19 @@ /** @effect-diagnostics preferSchemaOverJson:skip-file */ import { NodeHttpServer } from "@effect/platform-node" +import { NodeWS } from "@effect/platform-node/NodeSocket" import { assert, describe, expect, it } from "@effect/vitest" import { Effect } from "effect" import * as Duration from "effect/Duration" import * as Fiber from "effect/Fiber" +import * as Latch from "effect/Latch" import * as Layer from "effect/Layer" +import * as ManagedRuntime from "effect/ManagedRuntime" import * as Schema from "effect/Schema" import * as Stream from "effect/Stream" import * as Tracer from "effect/Tracer" import { Cookies, + FetchHttpClient, HttpBody, HttpClient, HttpClientRequest, @@ -25,6 +29,9 @@ import { } from "effect/unstable/http" import * as HttpApiError from "effect/unstable/httpapi/HttpApiError" import * as Buffer from "node:buffer" +import { EventEmitter } from "node:events" +import * as Http from "node:http" +import * as Net from "node:net" const Todo = Schema.Struct({ id: Schema.Number, @@ -504,10 +511,198 @@ describe("HttpServer", () => { assert.strictEqual(res.status, 204) }).pipe(Effect.provide(NodeHttpServer.layerTest))) + it.effect("completes a HEAD response once when close precedes the end callback", () => + Effect.gen(function*() { + const scope = yield* Effect.scope + const handler = yield* NodeHttpServer.makeHandler( + Effect.succeed(HttpServerResponse.empty()), + { scope } + ) + const completed = Latch.makeUnsafe() + let writableEnded = false + const nodeResponse = Object.defineProperty(new EventEmitter(), "writableEnded", { + get: () => writableEnded + }) as Http.ServerResponse + let closeListenerRemovals = 0 + nodeResponse.writeHead = () => nodeResponse + nodeResponse.off = ((event: string | symbol, listener: (...args: Array) => void) => { + if (event === "close") { + closeListenerRemovals++ + } + return EventEmitter.prototype.off.call(nodeResponse, event, listener) as Http.ServerResponse + }) as Http.ServerResponse["off"] + nodeResponse.end = ((callback: () => void) => { + writableEnded = true + nodeResponse.emit("close") + callback() + completed.openUnsafe() + return nodeResponse + }) as Http.ServerResponse["end"] + + handler( + { method: "HEAD", url: "/", headers: {}, socket: {} } as Http.IncomingMessage, + nodeResponse + ) + yield* completed.await + + assert.strictEqual(closeListenerRemovals, 1) + })) + + it.effect("coalesces streaming chunks from the same pull", () => + Effect.gen(function*() { + const scope = yield* Effect.scope + const handler = yield* NodeHttpServer.makeHandler( + Effect.succeed(HttpServerResponse.stream(Stream.make( + Buffer.Buffer.from("a"), + Buffer.Buffer.from("b") + ))), + { scope } + ) + const completed = Latch.makeUnsafe() + const writes: Array = [] + let writableEnded = false + const nodeResponse = Object.defineProperty(new EventEmitter(), "writableEnded", { + get: () => writableEnded + }) as Http.ServerResponse + nodeResponse.writeHead = () => nodeResponse + nodeResponse.write = ((chunk: Uint8Array) => { + writes.push(chunk) + return true + }) as Http.ServerResponse["write"] + nodeResponse.end = (() => { + writableEnded = true + completed.openUnsafe() + return nodeResponse + }) as Http.ServerResponse["end"] + + handler( + { method: "GET", url: "/", headers: {}, socket: {} } as Http.IncomingMessage, + nodeResponse + ) + yield* completed.await + + assert.deepStrictEqual(writes.map((chunk) => Buffer.Buffer.from(chunk).toString()), ["ab"]) + })) + + it.effect("waits for drain after a streaming write applies backpressure", () => + Effect.gen(function*() { + const scope = yield* Effect.scope + const handler = yield* NodeHttpServer.makeHandler( + Effect.succeed(HttpServerResponse.stream(Stream.make( + Buffer.Buffer.from("a"), + Buffer.Buffer.from("b") + ))), + { scope } + ) + const writeObserved = Latch.makeUnsafe() + const completed = Latch.makeUnsafe() + let writableEnded = false + const nodeResponse = Object.defineProperty(new EventEmitter(), "writableEnded", { + get: () => writableEnded + }) as Http.ServerResponse + let writeCount = 0 + nodeResponse.writeHead = () => nodeResponse + nodeResponse.write = (() => { + writeCount++ + queueMicrotask(() => writeObserved.openUnsafe()) + return writeCount > 1 + }) as Http.ServerResponse["write"] + nodeResponse.end = (() => { + writableEnded = true + completed.openUnsafe() + return nodeResponse + }) as Http.ServerResponse["end"] + + handler( + { method: "GET", url: "/", headers: {}, socket: {} } as Http.IncomingMessage, + nodeResponse + ) + yield* writeObserved.await + assert.strictEqual(nodeResponse.writableEnded, false) + + nodeResponse.emit("drain") + yield* completed.await + assert.strictEqual(writeCount, 1) + })) + + it.live("disposes after a client aborts a handler awaiting an upstream request", () => { + const upstreamStarted = Latch.makeUnsafe() + const upstream = Http.createServer(() => { + upstreamStarted.openUnsafe() + }) + const server = Http.createServer() + const router = HttpRouter.use((router) => + router.add( + "GET", + "/", + Effect.gen(function*() { + const request = yield* HttpServerRequest.HttpServerRequest + if (request.method !== "HEAD") { + return HttpServerResponse.text("ok") + } + yield* HttpClient.head(`http://localhost:${tcpPort(upstream)}`) + return HttpServerResponse.empty() + }) + ) + ) + const runtime = ManagedRuntime.make( + Layer.effectDiscard( + HttpRouter.serve(router).pipe( + Layer.provide(NodeHttpServer.layer(() => server, { + port: 0, + gracefulShutdownTimeout: "100 millis" + })), + Layer.provide(FetchHttpClient.layer), + Layer.launch, + Effect.forkScoped, + Effect.asVoid + ) + ) + ) + + return Effect.gen(function*() { + yield* Effect.callback((resume) => { + upstream.listen(0, () => resume(Effect.void)) + }) + yield* Effect.promise(() => runtime.context()) + yield* Effect.callback((resume) => { + if (server.listening) { + resume(Effect.void) + } else { + server.once("listening", () => resume(Effect.void)) + } + }) + + const downstreamClosed = Latch.makeUnsafe() + const downstream = Http.request({ method: "HEAD", port: tcpPort(server), path: "/" }) + downstream.on("error", () => {}) + downstream.on("close", () => downstreamClosed.openUnsafe()) + downstream.end() + yield* upstreamStarted.await + downstream.destroy() + yield* downstreamClosed.await + + const disposed = yield* Effect.promise(() => runtime.dispose()).pipe( + Effect.timeoutOption("2 seconds") + ) + assert.strictEqual(disposed._tag, "Some") + }).pipe( + Effect.ensuring( + Effect.sync(() => { + upstream.closeAllConnections() + upstream.close() + }).pipe( + Effect.andThen(Effect.promise(() => runtime.dispose())), + Effect.timeoutOrElse({ duration: "2 seconds", orElse: () => Effect.void }) + ) + ) + ) + }) + describe("HttpServerRespondable", () => { it.effect("error/schema", () => Effect.gen(function*() { - class CustomError extends Schema.ErrorClass("CustomError")({ + class CustomError extends Schema.Error("CustomError")({ _tag: Schema.tag("CustomError"), name: Schema.String }) { @@ -595,4 +790,97 @@ describe("HttpServer", () => { ) expect(root).toEqual("root") }).pipe(Effect.provide(NodeHttpServer.layerTest))) + + it.effect("websocket options are forwarded to the WebSocketServer", () => + Effect.gen(function*() { + yield* HttpRouter.add( + "GET", + "/ws", + Effect.gen(function*() { + const request = yield* HttpServerRequest.HttpServerRequest + const socket = yield* Effect.orDie(request.upgrade) + yield* Effect.orDie(socket.run(() => Effect.void)) + return HttpServerResponse.empty() + }) + ).pipe( + HttpRouter.serve, + Layer.build + ) + const server = yield* HttpServer.HttpServer + const port = (server.address as HttpServer.TcpAddress).port + + const connect = (perMessageDeflate: boolean) => + Effect.acquireRelease( + Effect.callback((resume) => { + const ws = new NodeWS.WebSocket(`ws://127.0.0.1:${port}/ws`, { perMessageDeflate }) + ws.on("open", () => resume(Effect.succeed(ws))) + ws.on("error", (error) => resume(Effect.fail(error))) + }), + (ws) => Effect.sync(() => ws.close()) + ) + + // layerTest configures websocket: { perMessageDeflate: true }, so the + // server accepts the extension when the client offers it... + const compressed = yield* connect(true) + expect(compressed.extensions).toContain("permessage-deflate") + + // ...and clients that do not offer it still connect uncompressed. + const plain = yield* connect(false) + expect(plain.extensions).not.toContain("permessage-deflate") + }).pipe(Effect.scoped, Effect.provide(layerTestWebsocket))) + + it.effect("an upgrade connection reset by the peer does not crash the process", () => + Effect.gen(function*() { + yield* HttpRouter.add("GET", "/", HttpServerResponse.text("ok")).pipe( + HttpRouter.serve, + Layer.build + ) + const server = yield* HttpServer.HttpServer + const port = (server.address as HttpServer.TcpAddress).port + + const uncaught: Array = [] + const onUncaught = (error: unknown) => uncaught.push(error) + process.on("uncaughtException", onUncaught) + yield* Effect.addFinalizer(() => Effect.sync(() => process.off("uncaughtException", onUncaught))) + + yield* Effect.callback((resume) => { + const socket = Net.connect({ port, host: "127.0.0.1" }, () => { + socket.write( + "GET /ws HTTP/1.1\r\n" + + "Host: 127.0.0.1\r\n" + + "Upgrade: websocket\r\n" + + "Connection: Upgrade\r\n" + + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" + + "Sec-WebSocket-Version: 13\r\n\r\n" + ) + setTimeout(() => { + socket.resetAndDestroy() + setTimeout(() => resume(Effect.void), 50) + }, 50) + }) + socket.on("error", () => {}) + }) + + expect(uncaught).toEqual([]) + const response = yield* HttpClient.get("/") + expect(response.status).toEqual(200) + }).pipe(Effect.provide(layerTestWebsocket))) }) + +const layerTestWebsocket = HttpServer.layerTestClient.pipe( + Layer.provide( + Layer.fresh(FetchHttpClient.layer).pipe( + Layer.provide(Layer.succeed(FetchHttpClient.RequestInit)({ keepalive: false })) + ) + ), + Layer.provideMerge(NodeHttpServer.layer(Http.createServer, { + port: 0, + websocket: { perMessageDeflate: true } + })) +) + +const tcpPort = (server: Http.Server): number => { + const address = server.address() + assert(address !== null && typeof address !== "string") + return address.port +} diff --git a/repos/effect/packages/platform-node/test/NodeRedis.integration.test.ts b/repos/effect/packages/platform-node/test/NodeRedis.integration.test.ts new file mode 100644 index 0000000000..838b623e7f --- /dev/null +++ b/repos/effect/packages/platform-node/test/NodeRedis.integration.test.ts @@ -0,0 +1,84 @@ +import { NodeRedis } from "@effect/platform-node" +import { assert, it } from "@effect/vitest" +import { RedisContainer } from "@testcontainers/redis" +import { Effect, Layer, Schema } from "effect" +import * as PersistedCacheTest from "effect-test/unstable/persistence/PersistedCacheTest" +import * as PersistedQueueTest from "effect-test/unstable/persistence/PersistedQueueTest" +import { PersistedQueue, Persistence } from "effect/unstable/persistence" + +const RedisLayer = Layer.unwrap( + Effect.gen(function*() { + const container = yield* Effect.acquireRelease( + Effect.promise(() => new RedisContainer("redis:alpine").start()), + (container) => Effect.promise(() => container.stop()) + ) + return NodeRedis.layer({ + host: container.getHost(), + port: container.getMappedPort(6379) + }) + }).pipe( + Effect.catchCause(() => Effect.fail(new PersistedCacheTest.TransientError())) + ) +) + +PersistedCacheTest.suite( + "NodeRedis", + Persistence.layerRedis.pipe(Layer.provide(RedisLayer)) +) + +PersistedQueueTest.suite( + "NodeRedis", + // short intervals so the periodic reset runs while the suite's takes are + // in flight + PersistedQueue.layerStoreRedis({ + pollInterval: "50 millis", + lockRefreshInterval: "100 millis" + }).pipe(Layer.provide(RedisLayer)) +) + +const PersistedQueueRedisLayer = Layer.mergeAll( + RedisLayer, + PersistedQueue.layer.pipe( + Layer.provideMerge( + PersistedQueue.layerStoreRedis().pipe(Layer.provide(RedisLayer)) + ) + ) +) + +it.layer(PersistedQueueRedisLayer, { timeout: "30 seconds" })( + "PersistedQueue (NodeRedis)", + (it) => { + // The shared PersistedQueue suite can only assert that exhausted elements + // are no longer delivered, which is also true if they are silently + // dropped. There is no public API for reading failed elements, so + // verifying they are preserved in the dead-letter list requires + // inspecting Redis directly. + it.effect("moves exhausted elements to the failed list", () => + Effect.gen(function*() { + const redis = yield* NodeRedis.NodeRedis + const queueName = "test-redis-failed" + + const queue = yield* PersistedQueue.make({ + name: queueName, + schema: RedisItem + }) + const id = yield* queue.offer({ n: 42 }) + const error = yield* queue.take(() => Effect.fail("boom"), { maxAttempts: 1 }).pipe(Effect.flip) + assert.strictEqual(error, "boom") + + const failed = yield* redis.use((client) => client.lrange(`effectq:${queueName}:failed`, 0, -1)) + assert.strictEqual(failed.length, 1) + const failedItem = JSON.parse(failed[0]) + assert.strictEqual(failedItem.id, id) + assert.deepStrictEqual(failedItem.element, { n: 42 }) + assert.strictEqual(failedItem.attempts, 1) + + const pending = yield* redis.use((client) => client.hlen(`effectq:${queueName}:pending`)) + assert.strictEqual(pending, 0) + })) + } +) + +const RedisItem = Schema.Struct({ + n: Schema.Number +}) diff --git a/repos/effect/packages/platform-node/test/NodeRedis.test.ts b/repos/effect/packages/platform-node/test/NodeRedis.test.ts deleted file mode 100644 index d00cf05885..0000000000 --- a/repos/effect/packages/platform-node/test/NodeRedis.test.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { NodeRedis } from "@effect/platform-node" -import { RedisContainer } from "@testcontainers/redis" -import { Effect, Layer } from "effect" -import * as PersistedCacheTest from "effect-test/unstable/persistence/PersistedCacheTest" -import * as PersistedQueueTest from "effect-test/unstable/persistence/PersistedQueueTest" -import { PersistedQueue, Persistence } from "effect/unstable/persistence" - -const RedisLayer = Layer.unwrap( - Effect.gen(function*() { - const container = yield* Effect.acquireRelease( - Effect.promise(() => new RedisContainer("redis:alpine").start()), - (container) => Effect.promise(() => container.stop()) - ) - return NodeRedis.layer({ - host: container.getHost(), - port: container.getMappedPort(6379) - }) - }).pipe( - Effect.catchCause(() => Effect.fail(new PersistedCacheTest.TransientError())) - ) -) - -PersistedCacheTest.suite( - "NodeRedis", - Persistence.layerRedis.pipe(Layer.provide(RedisLayer)) -) - -PersistedQueueTest.suite( - "NodeRedis", - PersistedQueue.layerStoreRedis().pipe(Layer.provide(RedisLayer)) -) diff --git a/repos/effect/packages/platform-node/test/NodeSocket.test.ts b/repos/effect/packages/platform-node/test/NodeSocket.test.ts index 5b2e06235e..943aa758eb 100644 --- a/repos/effect/packages/platform-node/test/NodeSocket.test.ts +++ b/repos/effect/packages/platform-node/test/NodeSocket.test.ts @@ -1,9 +1,12 @@ import { NodeSocket, NodeSocketServer } from "@effect/platform-node" -import { assert, describe, expect, it } from "@effect/vitest" +import { assert, describe, it } from "@effect/vitest" import { Effect, Queue } from "effect" +import * as Exit from "effect/Exit" import * as Fiber from "effect/Fiber" +import * as Scope from "effect/Scope" import * as Stream from "effect/Stream" import { Socket, type SocketServer } from "effect/unstable/socket" +import * as Net from "node:net" import { WS } from "vitest-websocket-mock" const makeServer = Effect.gen(function*() { @@ -18,6 +21,48 @@ const makeServer = Effect.gen(function*() { }) describe("Socket", () => { + it.live("closes with a pending pre-run socket", () => + Effect.gen(function*() { + const scope = yield* Scope.make() + const server = yield* NodeSocketServer.make({ host: "127.0.0.1", port: 0 }).pipe(Scope.provide(scope)) + assert.strictEqual(server.address._tag, "TcpAddress") + if (server.address._tag !== "TcpAddress") return + const socket = Net.createConnection({ host: "127.0.0.1", port: server.address.port }) + yield* Effect.promise(() => + new Promise((resolve, reject) => { + socket.once("connect", resolve) + socket.once("error", reject) + }) + ) + const closing = yield* Scope.close(scope, Exit.void).pipe(Effect.forkChild) + const exit = yield* Fiber.await(closing).pipe( + Effect.timeout("1 second"), + Effect.ensuring(Effect.sync(() => socket.destroy())) + ) + assert.isTrue(Exit.isSuccess(exit)) + })) + + it.live("closes with a pending pre-run WebSocket", () => + Effect.gen(function*() { + const scope = yield* Scope.make() + const server = yield* NodeSocketServer.makeWebSocket({ host: "127.0.0.1", port: 0 }).pipe(Scope.provide(scope)) + assert.strictEqual(server.address._tag, "TcpAddress") + if (server.address._tag !== "TcpAddress") return + const socket = new NodeSocket.NodeWS.WebSocket(`ws://127.0.0.1:${server.address.port}`) + yield* Effect.promise(() => + new Promise((resolve, reject) => { + socket.once("open", resolve) + socket.once("error", reject) + }) + ) + const closing = yield* Scope.close(scope, Exit.void).pipe(Effect.forkChild) + const exit = yield* Fiber.await(closing).pipe( + Effect.timeout("1 second"), + Effect.ensuring(Effect.sync(() => socket.terminate())) + ) + assert.isTrue(Exit.isSuccess(exit)) + })) + it.effect("open", () => Effect.gen(function*() { const server = yield* makeServer @@ -59,10 +104,8 @@ describe("Socket", () => { yield* write(new TextEncoder().encode("Hello")) yield* write(new TextEncoder().encode("World")) }).pipe(Effect.scoped) - yield* Effect.promise(async () => { - await expect(server).toReceiveMessage(new TextEncoder().encode("Hello")) - await expect(server).toReceiveMessage(new TextEncoder().encode("World")) - }) + assert.deepStrictEqual(yield* Effect.promise(() => server.nextMessage), new TextEncoder().encode("Hello")) + assert.deepStrictEqual(yield* Effect.promise(() => server.nextMessage), new TextEncoder().encode("World")) server.send("Right back at you!") let message = yield* Queue.take(messages) @@ -104,6 +147,43 @@ describe("Socket", () => { }).pipe( Effect.provideService(Socket.WebSocketConstructor, (url) => new globalThis.WebSocket(url)) )) + + it.effect("reports send errors as SocketError", () => + Effect.gen(function*() { + class ThrowingWebSocket extends EventTarget { + readonly readyState = globalThis.WebSocket.OPEN + + close(): void {} + + send(): void { + throw new Error("send failed") + } + } + const webSocket = new ThrowingWebSocket() + const socket = yield* Socket.makeWebSocket(Effect.succeed(url), { closeCodeIsError: () => false }).pipe( + Effect.provideService( + Socket.WebSocketConstructor, + () => webSocket as unknown as globalThis.WebSocket + ) + ) + const exit = yield* Effect.scoped(Effect.gen(function*() { + const run = yield* Effect.forkChild(socket.runRaw(() => {})) + const write = yield* socket.writer + const exit = yield* Effect.exit(write(new Uint8Array([1]))) + webSocket.dispatchEvent(new CloseEvent("close", { code: 1000 })) + yield* Fiber.join(run) + return exit + })) + assert.strictEqual(exit._tag, "Failure") + if (exit._tag === "Failure") { + assert.strictEqual(exit.cause.reasons[0]?._tag, "Fail") + const reason = exit.cause.reasons[0] + if (reason?._tag === "Fail") { + assert.isTrue(Socket.SocketError.is(reason.error)) + assert.strictEqual(reason.error.reason._tag, "SocketWriteError") + } + } + })) }) describe("TransformStream", () => { @@ -149,5 +229,31 @@ describe("Socket", () => { assert.deepStrictEqual(chunks, ["Hello", "World"]) assert.deepStrictEqual(received, ["A", "B", "C"]) })) + + it.effect("reports writable stream rejection as SocketError", () => + Effect.gen(function*() { + const socket = yield* Socket.fromTransformStream(Effect.succeed({ + readable: new ReadableStream({}), + writable: new WritableStream({ + write: () => Promise.reject(new Error("write failed")) + }) + })) + const exit = yield* Effect.scoped(Effect.gen(function*() { + const run = yield* Effect.forkChild(socket.runRaw(() => {})) + const write = yield* socket.writer + const exit = yield* Effect.exit(write(new Uint8Array([1]))) + yield* Fiber.interrupt(run) + return exit + })) + assert.strictEqual(exit._tag, "Failure") + if (exit._tag === "Failure") { + assert.strictEqual(exit.cause.reasons[0]?._tag, "Fail") + const reason = exit.cause.reasons[0] + if (reason?._tag === "Fail") { + assert.isTrue(Socket.SocketError.is(reason.error)) + assert.strictEqual(reason.error.reason._tag, "SocketWriteError") + } + } + })) }) }) diff --git a/repos/effect/packages/platform-node/test/RpcServer.test.ts b/repos/effect/packages/platform-node/test/RpcServer.test.ts index b4104c316c..1390216e2a 100644 --- a/repos/effect/packages/platform-node/test/RpcServer.test.ts +++ b/repos/effect/packages/platform-node/test/RpcServer.test.ts @@ -1,6 +1,6 @@ import { NodeHttpServer, NodeSocket, NodeSocketServer } from "@effect/platform-node" import { assert, describe, it } from "@effect/vitest" -import { Cause, Deferred, Effect, Fiber, Layer, Ref, Schedule, Schema, Stream } from "effect" +import { Cause, Deferred, Effect, Exit, Fiber, Layer, Ref, Schedule, Schema, Stream } from "effect" import { Entity, EntityProxy, EntityProxyServer, Sharding } from "effect/unstable/cluster" import { HttpClient, HttpClientRequest, HttpRouter, HttpServer } from "effect/unstable/http" import { Rpc, RpcClient, RpcGroup, RpcSerialization, RpcServer, RpcTest } from "effect/unstable/rpc" @@ -211,11 +211,69 @@ describe("RpcServer", () => { })) }) - describe("unknown-tag isolation", () => { - const Ticker = Rpc.make("Ticker", { - success: Schema.Number, - stream: true + // Asserts a failing sibling call does not affect an in-flight Ticker stream on the same connection + const Ticker = Rpc.make("Ticker", { + success: Schema.Number, + stream: true + }) + + const IsolationClient = RpcClient.layerProtocolSocket().pipe( + Layer.provide( + Effect.gen(function*() { + const server = yield* SocketServer.SocketServer + const address = server.address as SocketServer.TcpAddress + return NodeSocket.layerNet({ port: address.port }) + }).pipe(Layer.unwrap) + ), + Layer.provide(RpcSerialization.layerNdjson) + ) + + const assertTickerSurvives = ( + setup: Effect.Effect< + { + readonly ticker: Stream.Stream + readonly failingCall: Effect.Effect + }, + never, + R + >, + label: string + ) => + Effect.gen(function*() { + const { failingCall, ticker } = yield* setup + + const received = yield* Ref.make>([]) + + const tickerFiber = yield* ticker.pipe( + Stream.runForEach((value) => Ref.update(received, (xs) => [...xs, value])), + Effect.forkChild + ) + + yield* Effect.retry( + Effect.flatMap( + Ref.get(received), + (xs) => xs.length >= 2 ? Effect.void : Effect.fail("not enough ticks yet") + ), + { schedule: Schedule.spaced("50 millis"), times: 200 } + ) + + const ticksBefore = (yield* Ref.get(received)).length + assert.isAtLeast(ticksBefore, 2) + + yield* failingCall + + yield* Effect.sleep("300 millis") + + const ticksAfter = (yield* Ref.get(received)).length + const tickerStatus = tickerFiber.pollUnsafe() + + yield* Fiber.interrupt(tickerFiber) + + assert.isUndefined(tickerStatus, `Ticker stream must still be running after ${label}`) + assert.isAbove(ticksAfter, ticksBefore, `Ticker stream must keep emitting after ${label}`) }) + + describe("unknown-tag isolation", () => { const Ghost = Rpc.make("Ghost", { payload: { value: Schema.String }, success: Schema.String @@ -234,58 +292,63 @@ describe("RpcServer", () => { Layer.provideMerge(NodeSocketServer.layer({ port: 0 })), Layer.provide(RpcSerialization.layerNdjson) ) - const IsolationClient = RpcClient.layerProtocolSocket().pipe( - Layer.provide( - Effect.gen(function*() { - const server = yield* SocketServer.SocketServer - const address = server.address as SocketServer.TcpAddress - return NodeSocket.layerNet({ port: address.port }) - }).pipe(Layer.unwrap) - ), - Layer.provide(RpcSerialization.layerNdjson) - ) it.live( "an unknown request tag fails only its own request, not other in-flight streams on the same connection", () => - Effect.gen(function*() { - const client = yield* RpcClient.make(clientGroup) - - const received = yield* Ref.make>([]) - - const tickerFiber = yield* client.Ticker().pipe( - Stream.runForEach((value) => Ref.update(received, (xs) => [...xs, value])), - Effect.forkChild - ) - - yield* Effect.retry( - Effect.flatMap( - Ref.get(received), - (xs) => xs.length >= 2 ? Effect.void : Effect.fail("not enough ticks yet") - ), - { schedule: Schedule.spaced("50 millis"), times: 200 } - ) - - const ticksBeforeGhost = (yield* Ref.get(received)).length - assert.isAtLeast(ticksBeforeGhost, 2) + assertTickerSurvives( + Effect.map(RpcClient.make(clientGroup), (client) => ({ + ticker: client.Ticker(), + failingCall: Effect.gen(function*() { + const ghostExit = yield* client.Ghost({ value: "boo" }).pipe(Effect.exit) + assert.isTrue(Exit.isFailure(ghostExit), "Ghost call should fail with the routing miss") + }) + })), + "the unknown-tag failure" + ).pipe(Effect.provide(IsolationClient.pipe(Layer.provideMerge(IsolationServer)))), + { timeout: 30_000 } + ) + }) - const ghostExit = yield* client.Ghost({ value: "boo" }).pipe(Effect.exit) - assert.isTrue(ghostExit._tag === "Failure", "Ghost call should fail with the routing miss") + describe("fatal-defect isolation", () => { + const Boom = Rpc.make("Boom", { + success: Schema.String + }) - yield* Effect.sleep("300 millis") + const group = RpcGroup.make(Ticker, Boom) - const ticksAfterGhost = (yield* Ref.get(received)).length - const tickerStatus = tickerFiber.pollUnsafe() + const Handlers = group.toLayer({ + Ticker: () => Stream.fromSchedule(Schedule.spaced("60 millis")), + Boom: () => Effect.die("boom") + }) - yield* Fiber.interrupt(tickerFiber) + const DefectServer = RpcServer.layer(group, { disableFatalDefects: true }).pipe( + Layer.provide(Handlers), + Layer.provideMerge(RpcServer.layerProtocolSocketServer), + Layer.provideMerge(NodeSocketServer.layer({ port: 0 })), + Layer.provide(RpcSerialization.layerNdjson) + ) - assert.isUndefined(tickerStatus, "Ticker stream must still be running after the unknown-tag failure") - assert.isAbove( - ticksAfterGhost, - ticksBeforeGhost, - "Ticker stream must keep emitting after the unknown-tag failure" - ) - }).pipe(Effect.provide(IsolationClient.pipe(Layer.provideMerge(IsolationServer)))), + it.live( + "with disableFatalDefects a handler defect fails only its own request, not other in-flight streams", + () => + assertTickerSurvives( + Effect.map(RpcClient.make(group), (client) => ({ + ticker: client.Ticker(), + failingCall: Effect.gen(function*() { + const boomExit = yield* client.Boom().pipe(Effect.exit) + if (!Exit.isFailure(boomExit)) { + return assert.fail("Boom call must fail with the handler defect") + } + assert.include( + String(Cause.squash(boomExit.cause)), + "boom", + "the caller must receive the handler defect" + ) + }) + })), + "the handler defect" + ).pipe(Effect.provide(IsolationClient.pipe(Layer.provideMerge(DefectServer)))), { timeout: 30_000 } ) }) diff --git a/repos/effect/packages/platform-node/test/__snapshots__/HttpApi.test.ts.snap b/repos/effect/packages/platform-node/test/__snapshots__/HttpApi.test.ts.snap index 9765aadd09..4afc5ec7d3 100644 --- a/repos/effect/packages/platform-node/test/__snapshots__/HttpApi.test.ts.snap +++ b/repos/effect/packages/platform-node/test/__snapshots__/HttpApi.test.ts.snap @@ -20,7 +20,7 @@ exports[`HttpApi > original tests > OpenAPI spec > fixture 1`] = ` ], "type": "object", }, - "Group": { + "GroupEncoded": { "additionalProperties": false, "properties": { "id": { @@ -36,7 +36,7 @@ exports[`HttpApi > original tests > OpenAPI spec > fixture 1`] = ` ], "type": "object", }, - "NoStatusError": { + "NoStatusErrorEncoded": { "additionalProperties": false, "properties": { "_tag": { @@ -51,7 +51,17 @@ exports[`HttpApi > original tests > OpenAPI spec > fixture 1`] = ` ], "type": "object", }, - "User": { + "Union_": { + "anyOf": [ + { + "type": "string", + }, + { + "type": "null", + }, + ], + }, + "UserEncoded": { "additionalProperties": false, "properties": { "createdAt": { @@ -64,14 +74,7 @@ exports[`HttpApi > original tests > OpenAPI spec > fixture 1`] = ` "type": "string", }, "uuid": { - "anyOf": [ - { - "type": "string", - }, - { - "type": "null", - }, - ], + "$ref": "#/components/schemas/Union_", }, }, "required": [ @@ -81,7 +84,7 @@ exports[`HttpApi > original tests > OpenAPI spec > fixture 1`] = ` ], "type": "object", }, - "UserError": { + "UserErrorEncoded": { "additionalProperties": false, "properties": { "_tag": { @@ -168,7 +171,7 @@ exports[`HttpApi > original tests > OpenAPI spec > fixture 1`] = ` "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/Group", + "$ref": "#/components/schemas/GroupEncoded", }, }, }, @@ -325,7 +328,7 @@ exports[`HttpApi > original tests > OpenAPI spec > fixture 1`] = ` "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/Group", + "$ref": "#/components/schemas/GroupEncoded", }, }, }, @@ -397,7 +400,7 @@ exports[`HttpApi > original tests > OpenAPI spec > fixture 1`] = ` "application/json": { "schema": { "items": { - "$ref": "#/components/schemas/User", + "$ref": "#/components/schemas/UserEncoded", }, "type": "array", }, @@ -409,7 +412,7 @@ exports[`HttpApi > original tests > OpenAPI spec > fixture 1`] = ` "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/NoStatusError", + "$ref": "#/components/schemas/NoStatusErrorEncoded", }, }, }, @@ -453,14 +456,7 @@ exports[`HttpApi > original tests > OpenAPI spec > fixture 1`] = ` "type": "string", }, "uuid": { - "anyOf": [ - { - "type": "string", - }, - { - "type": "null", - }, - ], + "$ref": "#/components/schemas/Union_", }, }, "required": [ @@ -477,7 +473,7 @@ exports[`HttpApi > original tests > OpenAPI spec > fixture 1`] = ` "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/User", + "$ref": "#/components/schemas/UserEncoded", }, }, }, @@ -489,10 +485,10 @@ exports[`HttpApi > original tests > OpenAPI spec > fixture 1`] = ` "schema": { "anyOf": [ { - "$ref": "#/components/schemas/UserError", + "$ref": "#/components/schemas/UserErrorEncoded", }, { - "$ref": "#/components/schemas/UserError", + "$ref": "#/components/schemas/UserErrorEncoded", }, ], }, @@ -520,14 +516,7 @@ exports[`HttpApi > original tests > OpenAPI spec > fixture 1`] = ` "name": "0", "required": true, "schema": { - "anyOf": [ - { - "type": "string", - }, - { - "type": "null", - }, - ], + "$ref": "#/components/schemas/Union_", }, }, ], @@ -668,7 +657,7 @@ exports[`HttpApi > original tests > OpenAPI spec > fixture 1`] = ` "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/User", + "$ref": "#/components/schemas/UserEncoded", }, }, }, @@ -678,7 +667,7 @@ exports[`HttpApi > original tests > OpenAPI spec > fixture 1`] = ` "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/UserError", + "$ref": "#/components/schemas/UserErrorEncoded", }, }, }, diff --git a/repos/effect/packages/platform-node/test/cluster-integration/ClusterCron.test.ts b/repos/effect/packages/platform-node/test/cluster-integration/ClusterCron.test.ts new file mode 100644 index 0000000000..8ecb051d46 --- /dev/null +++ b/repos/effect/packages/platform-node/test/cluster-integration/ClusterCron.test.ts @@ -0,0 +1,301 @@ +import { assert, describe, it } from "@effect/vitest" +import { Clock, Context, Cron, DateTime, Duration, Effect, Latch, Layer } from "effect" +import { ClusterCron, ClusterSchema, Entity } from "effect/unstable/cluster" +import { type Backend, make } from "./harness.ts" + +interface Tick { + readonly at: number + readonly runner: string + readonly scheduled: string +} + +const everySecond = Cron.parseUnsafe("* * * * * *", "UTC") +const testConfig = { shardsPerGroup: 12 } as const + +const addressString = (address: { readonly host: string; readonly port: number }) => `${address.host}:${address.port}` + +const recordTick = (ticks: Array) => + Effect.contextWith((context: Context.Context) => + Effect.gen(function*() { + const address = Context.getUnsafe(context, Entity.CurrentAddress) + const runner = Context.getUnsafe(context, Entity.CurrentRunnerAddress) + const at = yield* Clock.currentTimeMillis + const tick = { + at, + runner: addressString(runner), + scheduled: String(address.entityId) + } + const isFirst = ticks.length === 0 + ticks.push(tick) + return isFirst + }) + ) + +const cronProbe = (name: string, shardGroup = "default") => + Entity.make(`ClusterCron/${name}`, []).annotate(ClusterSchema.ShardGroup, () => shardGroup) + +const nextScheduled = (cron: Cron.Cron, after: DateTime.DateTime.Input) => + DateTime.formatIso(DateTime.fromDateUnsafe(Cron.next(cron, after))) + +const assertScheduledFromExecutionTime = ( + cron: Cron.Cron, + ticks: ReadonlyArray, + startIndex = 1 +) => { + for (let index = startIndex; index < ticks.length; index++) { + assert.strictEqual(ticks[index].scheduled, nextScheduled(cron, ticks[index - 1].at)) + } +} + +const assertScheduledFromPrevious = (cron: Cron.Cron, ticks: ReadonlyArray) => { + assert.strictEqual(ticks[0].scheduled, "initial") + for (let index = 2; index < ticks.length; index++) { + assert.strictEqual(ticks[index].scheduled, nextScheduled(cron, ticks[index - 1].scheduled)) + } +} + +describe("cluster cron integration", () => { + for (const backend of ["pg", "mysql"] satisfies ReadonlyArray) { + it.live(`${backend}: runs once per scheduled instant and continues after failure`, () => + Effect.gen(function*() { + const ticks: Array = [] + let failingAttempts = 0 + let successfulAttempts = 0 + const cron = ClusterCron.make({ + name: `basic-${backend}`, + cron: everySecond, + execute: recordTick(ticks) + }) + const failingCron = ClusterCron.make({ + name: `failure-${backend}`, + cron: everySecond, + execute: Effect.suspend(() => { + failingAttempts++ + if (failingAttempts === 1) return Effect.fail("expected cron failure") + successfulAttempts++ + return Effect.void + }) + }) + const cluster = yield* make({ + backend, + config: testConfig, + entities: Layer.merge(cron, failingCron) + }) + yield* cluster.start(3) + yield* cluster.waitForStableAssignments() + yield* cluster.waitUntil( + "The cron jobs did not continue through four scheduled instants", + Effect.sync(() => ticks.length >= 4 && failingAttempts >= 4) + ) + + const firstFour = ticks.slice(0, 4) + assert.strictEqual(new Set(firstFour.map((tick) => tick.scheduled)).size, firstFour.length) + assertScheduledFromExecutionTime(everySecond, firstFour) + assert.strictEqual(successfulAttempts, failingAttempts - 1) + })) + + it.live(`${backend}: calculates the next run from the previous instant or the current time`, () => + Effect.gen(function*() { + const previousTicks: Array = [] + const currentTicks: Array = [] + const gate = Latch.makeUnsafe() + let entered = 0 + const blockedExecution = Effect.fnUntraced( + function*(ticks: Array) { + if (yield* recordTick(ticks)) { + entered++ + yield* gate.await + } + }, + Effect.uninterruptible + ) + const previousCron = ClusterCron.make({ + name: `previous-${backend}`, + cron: everySecond, + calculateNextRunFromPrevious: true, + execute: blockedExecution(previousTicks) + }) + const currentCron = ClusterCron.make({ + name: `current-${backend}`, + cron: everySecond, + execute: blockedExecution(currentTicks) + }) + const cluster = yield* make({ + backend, + config: testConfig, + entities: Layer.merge(previousCron, currentCron) + }) + yield* cluster.start(3) + yield* cluster.waitForStableAssignments() + yield* cluster.waitUntil( + "Both cron executions did not enter their first run", + Effect.sync(() => entered === 2) + ) + const blockedAt = yield* Clock.currentTimeMillis + yield* cluster.waitUntil( + "The cron executions were not held across several scheduled instants", + Effect.map(Clock.currentTimeMillis, (now) => now >= blockedAt + 3_200), + "5 seconds" + ) + gate.openUnsafe() + yield* cluster.waitUntil( + "The cron jobs did not resume after their first execution", + Effect.sync(() => previousTicks.length >= 4 && currentTicks.length >= 4), + "12 seconds" + ) + + const previousSecond = DateTime.toEpochMillis(DateTime.makeUnsafe(previousTicks[1].scheduled)) + const currentSecond = DateTime.toEpochMillis(DateTime.makeUnsafe(currentTicks[1].scheduled)) + assertScheduledFromPrevious(everySecond, previousTicks) + assertScheduledFromExecutionTime(everySecond, currentTicks, 2) + assert.isAtLeast(previousTicks[1].at - previousSecond, 2_000) + assert.isAtMost(currentTicks[1].at - currentSecond, 1_000) + })) + + it.live(`${backend}: catches up or skips stale runs and preserves the schedule across restart`, () => + Effect.gen(function*() { + const catchUpTicks: Array = [] + const skipTicks: Array = [] + const catchUpCron = ClusterCron.make({ + name: `catch-up-${backend}`, + cron: everySecond, + calculateNextRunFromPrevious: true, + execute: recordTick(catchUpTicks) + }) + const skipCron = ClusterCron.make({ + name: `skip-stale-${backend}`, + cron: everySecond, + calculateNextRunFromPrevious: true, + skipIfOlderThan: Duration.millis(500), + execute: recordTick(skipTicks) + }) + const cluster = yield* make({ + backend, + config: testConfig, + entities: Layer.merge(catchUpCron, skipCron) + }) + const runners = yield* cluster.start(3) + yield* cluster.waitForStableAssignments() + yield* cluster.waitUntil( + "The cron schedules did not start", + Effect.sync(() => catchUpTicks.length >= 2 && skipTicks.length >= 2) + ) + for (const runner of runners) { + yield* cluster.kill(runner) + } + const catchUpBefore = catchUpTicks.length + const skipBefore = skipTicks.length + const lastScheduledBeforeRestart = catchUpTicks[catchUpBefore - 1].scheduled + const stoppedAt = yield* Clock.currentTimeMillis + yield* cluster.waitUntil( + "The cluster downtime window did not elapse", + Effect.map(Clock.currentTimeMillis, (now) => now >= stoppedAt + 3_200), + "5 seconds" + ) + assert.strictEqual(catchUpTicks.length, catchUpBefore) + assert.strictEqual(skipTicks.length, skipBefore) + + yield* cluster.start(3) + yield* cluster.waitForStableAssignments() + yield* cluster.waitUntil( + "The restarted cluster did not expose the catch-up and stale-skip difference", + Effect.sync(() => { + const caughtUp = catchUpTicks.length - catchUpBefore + const skipped = skipTicks.length - skipBefore + return caughtUp >= 3 && caughtUp >= skipped + 2 + }), + "15 seconds" + ) + yield* cluster.waitUntil( + "The stale-skipping cron did not resume", + Effect.sync(() => skipTicks.length > skipBefore) + ) + + const firstCatchUp = catchUpTicks[catchUpBefore] + const firstAfterSkip = skipTicks[skipBefore] + const firstAfterSkipScheduledAt = DateTime.toEpochMillis(DateTime.makeUnsafe(firstAfterSkip.scheduled)) + assert.strictEqual( + firstCatchUp.scheduled, + nextScheduled(everySecond, lastScheduledBeforeRestart) + ) + assert.isAtLeast(firstCatchUp.at - DateTime.toEpochMillis(DateTime.makeUnsafe(firstCatchUp.scheduled)), 2_000) + assert.isAtMost(firstAfterSkip.at - firstAfterSkipScheduledAt, 750) + assertScheduledFromPrevious(everySecond, catchUpTicks) + })) + + it.live(`${backend}: resumes without duplicate or missing ticks after the singleton owner dies`, () => + Effect.gen(function*() { + const name = `owner-failover-${backend}` + const ticks: Array = [] + const cron = ClusterCron.make({ + name, + cron: everySecond, + calculateNextRunFromPrevious: true, + execute: recordTick(ticks) + }) + const cluster = yield* make({ backend, config: testConfig, entities: cron }) + yield* cluster.start(3) + yield* cluster.waitForStableAssignments() + yield* cluster.waitUntil( + "The cron did not execute before failover", + Effect.sync(() => ticks.length >= 2) + ) + const owner = yield* cluster.ownerOfEntity(cronProbe(name), name) + assert.isDefined(owner) + yield* cluster.kill(owner!) + const afterKill = ticks.length + yield* cluster.waitUntil( + "The cron did not resume after its singleton owner died", + Effect.sync(() => ticks.length >= afterKill + 3), + "12 seconds" + ) + yield* cluster.waitForStableAssignments() + + assertScheduledFromPrevious(everySecond, ticks) + assert.strictEqual(new Set(ticks.map((tick) => tick.scheduled)).size, ticks.length) + assert.isTrue(ticks.slice(afterKill).every((tick) => tick.runner !== addressString(owner!.address))) + })) + + it.live(`${backend}: assigns cron singletons and executions to their shard groups`, () => + Effect.gen(function*() { + const defaultName = `default-group-${backend}` + const specialName = `special-group-${backend}` + const defaultTicks: Array = [] + const specialTicks: Array = [] + const defaultCron = ClusterCron.make({ + name: defaultName, + cron: everySecond, + execute: recordTick(defaultTicks) + }) + const specialCron = ClusterCron.make({ + name: specialName, + cron: everySecond, + shardGroup: "special", + execute: recordTick(specialTicks) + }) + const cluster = yield* make({ + backend, + config: { + availableShardGroups: ["default", "special"], + shardsPerGroup: 12 + }, + entities: Layer.merge(defaultCron, specialCron) + }) + const [defaultRunner] = yield* cluster.start(1, { assignedShardGroups: ["default"] }) + const [specialRunner] = yield* cluster.start(1, { assignedShardGroups: ["special"] }) + yield* cluster.waitForStableAssignments() + yield* cluster.waitUntil( + "The shard-group cron jobs did not execute", + Effect.sync(() => defaultTicks.length >= 2 && specialTicks.length >= 2) + ) + + assert.strictEqual(yield* cluster.ownerOfEntity(cronProbe(defaultName), defaultName), defaultRunner) + assert.strictEqual( + yield* cluster.ownerOfEntity(cronProbe(specialName, "special"), specialName), + specialRunner + ) + assert.isTrue(defaultTicks.every((tick) => tick.runner === addressString(defaultRunner.address))) + assert.isTrue(specialTicks.every((tick) => tick.runner === addressString(specialRunner.address))) + })) + } +}) diff --git a/repos/effect/packages/platform-node/test/cluster-integration/Entity.test.ts b/repos/effect/packages/platform-node/test/cluster-integration/Entity.test.ts new file mode 100644 index 0000000000..a0f89435f4 --- /dev/null +++ b/repos/effect/packages/platform-node/test/cluster-integration/Entity.test.ts @@ -0,0 +1,426 @@ +import { assert, describe, it } from "@effect/vitest" +import { Clock, Effect, Fiber, Latch, Layer, PrimaryKey, Schema, Scope } from "effect" +import { ClusterSchema, Entity, EntityResource, Singleton } from "effect/unstable/cluster" +import { Rpc } from "effect/unstable/rpc" +import { type Backend, type ClusterRunner, make } from "./harness.ts" + +class Request extends Schema.Class("ClusterEntityRequest")({ + id: Schema.String, + sequence: Schema.Number +}) { + [PrimaryKey.symbol]() { + return this.id + } +} + +const StateReply = Schema.Struct({ + generation: Schema.Number, + runner: Schema.String, + value: Schema.Number +}) + +const StateEntity = Entity.make("ClusterIntegrationState", [ + Rpc.make("Increment", { + payload: Request, + success: StateReply + }), + Rpc.make("Ordered", { + payload: Request, + success: Schema.Number + }) +]).annotateRpcs(ClusterSchema.Persisted, true) + +let orderGate = Latch.makeUnsafe(true) +let orderEntered = Latch.makeUnsafe() +let order: Array = [] +const generations = new Map() + +const addressString = (address: { readonly host: string; readonly port: number }) => `${address.host}:${address.port}` + +const StateEntityLayer = StateEntity.toLayer( + Effect.gen(function*() { + const address = yield* Entity.CurrentAddress + const runner = yield* Entity.CurrentRunnerAddress + const entityId = String(address.entityId) + const generation = (generations.get(entityId) ?? 0) + 1 + generations.set(entityId, generation) + let value = 0 + return { + Increment: () => + Effect.sync(() => ({ + generation, + runner: addressString(runner), + value: ++value + })), + Ordered: Effect.fnUntraced(function*({ payload }) { + order.push(payload.sequence) + if (payload.sequence === 1) { + orderEntered.openUnsafe() + yield* orderGate.await + } + return payload.sequence + }) + } + }), + { maxIdleTime: "1 second" } +) + +const MailboxEntity = Entity.make("ClusterIntegrationMailbox", [ + Rpc.make("Hold", { + payload: Request, + success: Schema.Number + }) +]) + +let mailboxGate = Latch.makeUnsafe(true) +let mailboxEntered = Latch.makeUnsafe() + +const MailboxEntityLayer = MailboxEntity.toLayer({ + Hold: Effect.fnUntraced(function*({ payload }) { + mailboxEntered.openUnsafe() + yield* mailboxGate.await + return payload.sequence + }) +}, { mailboxCapacity: 1 }) + +const GroupEntity = Entity.make("ClusterIntegrationSpecialGroup", [ + Rpc.make("Runner", { success: Schema.String }) +]).annotate(ClusterSchema.ShardGroup, () => "special") + +const GroupEntityLayer = GroupEntity.toLayer(Effect.gen(function*() { + const runner = yield* Entity.CurrentRunnerAddress + return { Runner: () => Effect.succeed(addressString(runner)) } +})) + +const ResourceEntity = Entity.make("ClusterIntegrationResource", [ + Rpc.make("Get", { success: Schema.Number }), + Rpc.make("Close", { success: Schema.Void }) +]) + +const resourceState = { acquired: 0, released: 0 } + +const ResourceEntityLayer = ResourceEntity.toLayer(Effect.gen(function*() { + const resource = yield* EntityResource.make({ + acquire: Effect.gen(function*() { + const closeScope = yield* EntityResource.CloseScope + return yield* Effect.acquireRelease( + Effect.sync(() => ++resourceState.acquired), + () => Effect.sync(() => resourceState.released++) + ).pipe(Scope.provide(closeScope)) + }) + }) + return { + Close: () => resource.close, + Get: () => Effect.scoped(resource.get) + } +})) + +const StandardEntities = Layer.mergeAll(StateEntityLayer, MailboxEntityLayer, ResourceEntityLayer) + +const resetOrder = () => { + orderGate = Latch.makeUnsafe() + orderEntered = Latch.makeUnsafe() + order = [] +} + +const resetMailbox = () => { + mailboxGate = Latch.makeUnsafe() + mailboxEntered = Latch.makeUnsafe() +} + +const findIdsByRunner = Effect.fnUntraced(function*( + cluster: Effect.Success>, + runners: ReadonlyArray +) { + const found = new Map() + for (let index = 0; index < 2_000 && found.size < runners.length; index++) { + const id = `entity-${index}` + const owner = yield* cluster.ownerOfEntity(StateEntity, id) + if (owner && runners.includes(owner) && !found.has(owner)) found.set(owner, id) + } + assert.strictEqual(found.size, runners.length) + return found +}) + +describe("cluster entity integration", () => { + for (const backend of ["pg", "mysql"] satisfies ReadonlyArray) { + it.live(`${backend}: routes by entity id, isolates state, and preserves mailbox order`, () => + Effect.gen(function*() { + generations.clear() + resetOrder() + const cluster = yield* make({ backend, entities: StandardEntities }) + const runners = yield* cluster.start(3) + yield* cluster.waitForStableAssignments() + const ids = yield* findIdsByRunner(cluster, runners) + const client = yield* cluster.getClient(StateEntity) + + for (const [runner, id] of ids) { + const first = yield* client(id).Increment(new Request({ id: `${id}-1`, sequence: 0 })) + const second = yield* client(id).Increment(new Request({ id: `${id}-2`, sequence: 0 })) + assert.strictEqual(first.runner, addressString(runner.address)) + assert.strictEqual(second.runner, first.runner) + assert.strictEqual(first.value, 1) + assert.strictEqual(second.value, 2) + assert.isFalse(cluster.clientSharding.hasShardId(yield* cluster.shardOfEntity(StateEntity, id))) + } + + const orderedId = ids.values().next().value! + const first = yield* client(orderedId).Ordered( + new Request({ id: `${backend}-ordered-1`, sequence: 1 }) + ).pipe(Effect.forkChild({ startImmediately: true })) + yield* cluster.waitUntil("The first ordered request did not start", Effect.as(orderEntered.await, true)) + const second = yield* client(orderedId).Ordered( + new Request({ id: `${backend}-ordered-2`, sequence: 2 }) + ).pipe(Effect.forkChild({ startImmediately: true })) + orderGate.openUnsafe() + assert.strictEqual(yield* Fiber.join(first), 1) + assert.strictEqual(yield* Fiber.join(second), 2) + assert.deepStrictEqual(order, [1, 2]) + + const registrations = (yield* cluster.diagnostics()).registrations + assert.strictEqual(registrations.length, runners.length) + })) + + it.live(`${backend}: reports mailbox saturation and revives idle entities with fresh state`, () => + Effect.gen(function*() { + generations.clear() + resetMailbox() + const cluster = yield* make({ backend, entities: StandardEntities }) + yield* cluster.start(2) + yield* cluster.waitForStableAssignments() + + const mailbox = yield* cluster.getClient(MailboxEntity) + const held = yield* mailbox("full").Hold( + new Request({ id: `${backend}-held`, sequence: 1 }) + ).pipe(Effect.forkChild({ startImmediately: true })) + yield* cluster.waitUntil("The mailbox request did not start", Effect.as(mailboxEntered.await, true)) + const error = yield* mailbox("full").Hold( + new Request({ id: `${backend}-rejected`, sequence: 2 }) + ).pipe(Effect.flip) + assert.strictEqual(error._tag, "MailboxFull") + mailboxGate.openUnsafe() + assert.strictEqual(yield* Fiber.join(held), 1) + + const state = yield* cluster.getClient(StateEntity) + const first = yield* state("idle").Increment( + new Request({ id: `${backend}-idle-1`, sequence: 0 }) + ) + const owner = yield* cluster.ownerOfEntity(StateEntity, "idle") + yield* cluster.waitUntil( + "The idle entity was not reaped", + Effect.map(owner!.sharding.activeEntityCount, (count) => count === 0), + "12 seconds" + ) + const revived = yield* state("idle").Increment( + new Request({ id: `${backend}-idle-2`, sequence: 0 }) + ) + assert.strictEqual(first.generation, 1) + assert.strictEqual(revived.generation, 2) + assert.strictEqual(revived.value, 1) + })) + + it.live(`${backend}: rebalances on runner addition, graceful stop, and abrupt death`, () => + Effect.gen(function*() { + resetOrder() + const cluster = yield* make({ backend, entities: StandardEntities }) + const initial = yield* cluster.start(2) + yield* cluster.waitForStableAssignments() + const before = new Map() + for (let index = 0; index < 2_000; index++) { + const id = `moving-${index}` + before.set(id, (yield* cluster.ownerOfEntity(StateEntity, id))!) + } + + const [added] = yield* cluster.start(1) + assert.strictEqual(added.index, 2) + yield* cluster.waitForStableAssignments() + let movedId: string | undefined + for (const [id, old] of before) { + if (old !== added && (yield* cluster.ownerOfEntity(StateEntity, id)) === added) { + movedId = id + break + } + } + assert.isDefined(movedId) + const client = yield* cluster.getClient(StateEntity) + const moved = yield* client(movedId!).Increment( + new Request({ id: `${backend}-moved`, sequence: 0 }) + ) + assert.strictEqual(moved.runner, addressString(added.address)) + + const stopId = (yield* findIdsByRunner(cluster, initial)).get(initial[0])! + const request = yield* client(stopId).Ordered( + new Request({ id: `${backend}-stop`, sequence: 1 }) + ).pipe(Effect.forkChild({ startImmediately: true })) + yield* cluster.waitUntil("The handover request did not start", Effect.as(orderEntered.await, true)) + const stopping = yield* cluster.stop(initial[0]).pipe(Effect.forkChild({ startImmediately: true })) + yield* cluster.waitUntil( + "The stopped runner did not hand over its entity", + Effect.map(cluster.ownerOfEntity(StateEntity, stopId), (owner) => owner !== undefined && owner !== initial[0]) + ) + orderGate.openUnsafe() + assert.strictEqual(yield* Fiber.join(request), 1) + yield* Fiber.join(stopping) + + const killId = `kill-${backend}` + const killed = yield* cluster.ownerOfEntity(StateEntity, killId) + yield* cluster.kill(killed!) + const reply = yield* client(killId).Increment( + new Request({ id: `${backend}-kill`, sequence: 0 }) + ) + assert.notStrictEqual(reply.runner, addressString(killed!.address)) + yield* cluster.waitForStableAssignments() + assert.strictEqual((yield* cluster.messageCounts()).unprocessed, 0) + })) + + it.live(`${backend}: transfers frozen row locks after expiry`, () => + Effect.gen(function*() { + const cluster = yield* make({ backend, entities: StandardEntities, lockMode: "row" }) + yield* cluster.start(2) + yield* cluster.waitForStableAssignments() + const id = `frozen-${backend}` + const oldOwner = yield* cluster.ownerOfEntity(StateEntity, id) + const shard = yield* cluster.shardOfEntity(StateEntity, id) + yield* cluster.freeze(oldOwner!) + yield* cluster.waitUntil( + "The frozen runner's row lock did not expire", + Effect.map(cluster.ownerOfEntity(StateEntity, id), (owner) => owner !== undefined && owner !== oldOwner), + "12 seconds" + ) + const nextOwner = yield* cluster.ownerOfEntity(StateEntity, id) + assert.strictEqual(cluster.ownersOfShard(shard).length, 1) + assert.strictEqual(cluster.ownersOfShard(shard)[0], nextOwner) + const client = yield* cluster.getClient(StateEntity) + const reply = yield* client(id).Increment(new Request({ id: `${backend}-freeze`, sequence: 0 })) + assert.strictEqual(reply.runner, addressString(nextOwner!.address)) + yield* cluster.kill(oldOwner!) + })) + + it.live(`${backend}: retains frozen advisory locks until the session closes`, () => + Effect.gen(function*() { + const cluster = yield* make({ backend, entities: StandardEntities }) + yield* cluster.start(2) + yield* cluster.waitForStableAssignments() + const id = `frozen-advisory-${backend}` + const oldOwner = yield* cluster.ownerOfEntity(StateEntity, id) + const shard = yield* cluster.shardOfEntity(StateEntity, id) + yield* cluster.freeze(oldOwner!) + const deadline = (yield* Clock.currentTimeMillis) + 3_000 + yield* cluster.waitUntil( + "The advisory-lock observation window did not elapse", + Effect.map(Clock.currentTimeMillis, (now) => now >= deadline), + "5 seconds" + ) + assert.strictEqual(cluster.ownersOfShard(shard).length, 0) + assert.deepStrictEqual(cluster.ownersOfShard(shard, true), [oldOwner]) + yield* cluster.kill(oldOwner!) + yield* cluster.waitUntil( + "The advisory lock was not handed over after its session closed", + Effect.map(cluster.ownerOfEntity(StateEntity, id), (owner) => owner !== undefined && owner !== oldOwner) + ) + })) + } + + it.live("assigns annotated entities only to runners in their shard group", () => + Effect.gen(function*() { + const entities = Layer.mergeAll(StateEntityLayer, GroupEntityLayer) + const cluster = yield* make({ + backend: "pg", + config: { availableShardGroups: ["default", "special"], shardsPerGroup: 30 }, + entities + }) + const [defaultRunner] = yield* cluster.start(1, { assignedShardGroups: ["default"] }) + const [specialRunner] = yield* cluster.start(1, { assignedShardGroups: ["special"] }) + yield* cluster.waitForStableAssignments() + const client = yield* cluster.getClient(GroupEntity) + assert.strictEqual(yield* client("grouped").Runner(), addressString(specialRunner.address)) + assert.strictEqual(yield* cluster.ownerOfEntity(GroupEntity, "grouped"), specialRunner) + assert.strictEqual(yield* cluster.ownerOfEntity(StateEntity, "default"), defaultRunner) + })) + + it.live("runs one singleton and migrates it after owner death", () => + Effect.gen(function*() { + const singleton = { active: 0, maxActive: 0, starts: 0 } + const singletonLayer = Singleton.make( + "cluster-integration-singleton", + Effect.acquireRelease( + Effect.sync(() => { + singleton.active++ + singleton.starts++ + singleton.maxActive = Math.max(singleton.maxActive, singleton.active) + }), + () => Effect.sync(() => singleton.active--) + ) + ) + const cluster = yield* make({ + backend: "pg", + entities: Layer.merge(StateEntityLayer, singletonLayer) + }) + const runners = yield* cluster.start(3) + yield* cluster.waitForStableAssignments() + yield* cluster.waitUntil("The singleton did not start", Effect.sync(() => singleton.active === 1)) + const firstOwner = yield* cluster.ownerOfEntity(StateEntity, "cluster-integration-singleton") + assert.isTrue(runners.includes(firstOwner!)) + yield* cluster.kill(firstOwner!) + yield* cluster.waitUntil( + "The singleton did not migrate", + Effect.sync(() => singleton.starts >= 2 && singleton.active === 1) + ) + assert.notStrictEqual( + yield* cluster.ownerOfEntity(StateEntity, "cluster-integration-singleton"), + firstOwner + ) + assert.strictEqual(singleton.maxActive, 1) + })) + + it.live("keeps EntityResource alive during movement and releases it explicitly", () => + Effect.gen(function*() { + resourceState.acquired = 0 + resourceState.released = 0 + const cluster = yield* make({ backend: "pg", entities: ResourceEntityLayer }) + yield* cluster.start(2) + yield* cluster.waitForStableAssignments() + const client = yield* cluster.getClient(ResourceEntity) + assert.strictEqual(yield* client("resource").Get(), 1) + const owner = yield* cluster.ownerOfEntity(ResourceEntity, "resource") + yield* cluster.stop(owner!) + yield* cluster.waitUntil( + "The resource entity did not move", + Effect.map(cluster.ownerOfEntity(ResourceEntity, "resource"), (next) => next !== undefined && next !== owner) + ) + assert.strictEqual(resourceState.released, 0) + assert.strictEqual(yield* client("resource").Get(), 2) + yield* client("resource").Close() + yield* cluster.waitUntil( + "The entity resource was not released", + Effect.sync(() => resourceState.released === 1) + ) + })) + + for (const backend of ["pg", "mysql"] satisfies ReadonlyArray) { + it.live(`${backend}: isolates clusters with different table prefixes`, () => + Effect.gen(function*() { + const first = yield* make({ backend, entities: StateEntityLayer, config: { shardsPerGroup: 30 } }) + const second = yield* make({ backend, entities: StateEntityLayer, config: { shardsPerGroup: 30 } }) + const [firstRunner] = yield* first.start(1) + const [secondRunner] = yield* second.start(1) + yield* first.waitForStableAssignments() + yield* second.waitForStableAssignments() + assert.notStrictEqual(first.prefix, second.prefix) + const firstRegistrations = (yield* first.diagnostics()).registrations + const secondRegistrations = (yield* second.diagnostics()).registrations + assert.deepStrictEqual(firstRegistrations.map((row) => row.address), [addressString(firstRunner.address)]) + assert.deepStrictEqual(secondRegistrations.map((row) => row.address), [addressString(secondRunner.address)]) + const firstClient = yield* first.getClient(StateEntity) + const secondClient = yield* second.getClient(StateEntity) + const firstReply = yield* firstClient("same-id").Increment( + new Request({ id: `${backend}-prefix-first`, sequence: 0 }) + ) + const secondReply = yield* secondClient("same-id").Increment( + new Request({ id: `${backend}-prefix-second`, sequence: 0 }) + ) + assert.strictEqual(firstReply.runner, addressString(firstRunner.address)) + assert.strictEqual(secondReply.runner, addressString(secondRunner.address)) + })) + } +}) diff --git a/repos/effect/packages/platform-node/test/cluster-integration/Persistence.test.ts b/repos/effect/packages/platform-node/test/cluster-integration/Persistence.test.ts new file mode 100644 index 0000000000..850de48f06 --- /dev/null +++ b/repos/effect/packages/platform-node/test/cluster-integration/Persistence.test.ts @@ -0,0 +1,466 @@ +import { assert, describe, it } from "@effect/vitest" +import { Cause, Clock, DateTime, Effect, Exit, Fiber, Latch, Option, PrimaryKey, Schema, Stream } from "effect" +import { ClusterSchema, DeliverAt, Entity } from "effect/unstable/cluster" +import { Rpc, RpcSchema } from "effect/unstable/rpc" +import { type Backend, make } from "./harness.ts" + +class KeyedPayload extends Schema.Class("ClusterPersistenceKeyedPayload")({ + id: Schema.String +}) { + [PrimaryKey.symbol]() { + return this.id + } +} + +class ScheduledPayload extends Schema.Class("ClusterPersistenceScheduledPayload")({ + deliverAt: Schema.Number, + id: Schema.String +}) { + [PrimaryKey.symbol]() { + return this.id + } + + [DeliverAt.symbol]() { + return DateTime.makeUnsafe(this.deliverAt) + } +} + +const PersistedRpc = Rpc.make("Persisted", { + payload: KeyedPayload, + success: Schema.String +}) + +const StoredReplyRpc = Rpc.make("StoredReply", { + payload: KeyedPayload, + success: Schema.String +}) + +const UninterruptibleRpc = Rpc.make("Uninterruptible", { + payload: KeyedPayload, + success: Schema.String +}).annotate(ClusterSchema.Uninterruptible, true) + +const VolatileRpc = Rpc.make("Volatile", { + payload: KeyedPayload, + success: Schema.String +}).annotate(ClusterSchema.Persisted, false) + +const TypedFailureRpc = Rpc.make("TypedFailure", { + error: Schema.String, + payload: KeyedPayload, + success: Schema.Never +}) + +const DefectRpc = Rpc.make("Defect", { + payload: KeyedPayload, + success: Schema.Never +}) + +const HealthyRpc = Rpc.make("Healthy", { + payload: KeyedPayload, + success: Schema.String +}) + +const StreamedRpc = Rpc.make("Streamed", { + payload: KeyedPayload, + success: RpcSchema.Stream(Schema.Number, Schema.Never) +}) + +const ScheduledRpc = Rpc.make("Scheduled", { + payload: ScheduledPayload, + success: Schema.Number +}) + +const PersistenceEntity = Entity.make("ClusterIntegrationPersistence", [ + PersistedRpc, + StoredReplyRpc, + UninterruptibleRpc, + VolatileRpc, + TypedFailureRpc, + DefectRpc, + HealthyRpc, + StreamedRpc, + ScheduledRpc +]).annotateRpcs(ClusterSchema.Persisted, true) + +const freshState = () => ({ + completedUninterruptible: 0, + completedVolatile: 0, + counts: new Map(), + scheduledDeliveries: [] as Array, + streamThirdEntered: Latch.makeUnsafe(), + streamThirdGate: Latch.makeUnsafe(), + uninterruptibleEntered: Latch.makeUnsafe(), + uninterruptibleGate: Latch.makeUnsafe(), + volatileEntered: Latch.makeUnsafe(), + volatileGate: Latch.makeUnsafe() +}) + +let state = freshState() + +const resetState = () => { + state = freshState() +} + +const increment = (tag: string, id: string) => { + const key = `${tag}:${id}` + const next = (state.counts.get(key) ?? 0) + 1 + state.counts.set(key, next) + return next +} + +const count = (tag: string, id: string) => state.counts.get(`${tag}:${id}`) ?? 0 + +const PersistenceEntityLayer = PersistenceEntity.toLayer({ + Defect: ({ payload }) => + Effect.sync(() => increment("Defect", payload.id)).pipe( + Effect.andThen(Effect.die(`defect:${payload.id}`)) + ), + Healthy: ({ payload }) => + Effect.sync(() => { + increment("Healthy", payload.id) + return `healthy:${payload.id}` + }), + Persisted: ({ payload }) => + Effect.sync(() => { + increment("Persisted", payload.id) + return `persisted:${payload.id}` + }), + Scheduled: Effect.fnUntraced(function*({ payload }) { + increment("Scheduled", payload.id) + const deliveredAt = yield* Clock.currentTimeMillis + state.scheduledDeliveries.push(deliveredAt) + return deliveredAt + }), + StoredReply: ({ payload }) => + Effect.sync(() => { + increment("StoredReply", payload.id) + return `stored:${payload.id}` + }), + Streamed: (request) => { + increment("Streamed", request.payload.id) + const start = Option.match(request.lastSentChunkValue, { + onNone: () => 0, + onSome: (value) => value + 1 + }) + return Stream.fromIterable([0, 1, 2, 3, 4].slice(start)).pipe( + Stream.mapEffect((value) => { + if (request.payload.id.endsWith("-restart") && value === 2) { + state.streamThirdEntered.openUnsafe() + return Effect.as(state.streamThirdGate.await, value) + } + return Effect.succeed(value) + }), + Stream.rechunk(1) + ) + }, + TypedFailure: ({ payload }) => + Effect.sync(() => increment("TypedFailure", payload.id)).pipe( + Effect.andThen(Effect.fail(`typed:${payload.id}`)) + ), + Uninterruptible: Effect.fnUntraced(function*({ payload }) { + increment("Uninterruptible", payload.id) + state.uninterruptibleEntered.openUnsafe() + yield* state.uninterruptibleGate.await + state.completedUninterruptible++ + return `uninterruptible:${payload.id}` + }), + Volatile: Effect.fnUntraced(function*({ payload }) { + increment("Volatile", payload.id) + state.volatileEntered.openUnsafe() + yield* state.volatileGate.await + state.completedVolatile++ + return `volatile:${payload.id}` + }) +}, { disableFatalDefects: true }) + +describe("cluster message persistence integration", () => { + for (const backend of ["pg", "mysql"] satisfies ReadonlyArray) { + it.live(`${backend}: delivers a persisted request sent while its runner is down exactly once`, () => + Effect.gen(function*() { + resetState() + const cluster = yield* make({ backend, entities: PersistenceEntityLayer }) + yield* cluster.start(1) + yield* cluster.waitForStableAssignments() + const owner = yield* cluster.ownerOfEntity(PersistenceEntity, "restart") + yield* cluster.kill(owner!) + + const client = yield* cluster.getClient(PersistenceEntity) + const replyFiber = yield* client("restart").Persisted( + new KeyedPayload({ id: `${backend}-restart` }) + ).pipe(Effect.forkChild({ startImmediately: true })) + yield* cluster.waitUntil( + "The request was not persisted while the runner was down", + Effect.map(cluster.unprocessedMessageCount, (value) => value === 1) + ) + + yield* cluster.start(1) + yield* cluster.waitForStableAssignments() + assert.strictEqual(yield* Fiber.join(replyFiber), `persisted:${backend}-restart`) + yield* cluster.waitUntil( + "The persisted reply was not recorded", + Effect.map(cluster.repliedMessageCount, (value) => value === 1) + ) + assert.strictEqual(count("Persisted", `${backend}-restart`), 1) + assert.deepStrictEqual(yield* cluster.messageCounts(), { + failed: 0, + replied: 1, + unprocessed: 0 + }) + })) + + it.live(`${backend}: serves primary-key duplicates from the stored reply`, () => + Effect.gen(function*() { + resetState() + const cluster = yield* make({ backend, entities: PersistenceEntityLayer }) + yield* cluster.start(2) + yield* cluster.waitForStableAssignments() + const client = yield* cluster.getClient(PersistenceEntity) + const id = `${backend}-stored` + const payload = new KeyedPayload({ id }) + + assert.strictEqual(yield* client("stored").StoredReply(payload), `stored:${id}`) + const firstOwner = yield* cluster.ownerOfEntity(PersistenceEntity, "stored") + yield* cluster.kill(firstOwner!) + yield* cluster.waitForStableAssignments() + + assert.strictEqual(yield* client("stored").StoredReply(payload), `stored:${id}`) + assert.strictEqual(count("StoredReply", id), 1) + assert.deepStrictEqual(yield* cluster.messageCounts(), { + failed: 0, + replied: 1, + unprocessed: 0 + }) + })) + + it.live(`${backend}: does not lose an uninterruptible request during runner shutdown`, () => + Effect.gen(function*() { + resetState() + const cluster = yield* make({ + backend, + config: { entityTerminationTimeout: 100 }, + entities: PersistenceEntityLayer + }) + const [owner] = yield* cluster.start(1) + yield* cluster.waitForStableAssignments() + const client = yield* cluster.getClient(PersistenceEntity) + const id = `${backend}-uninterruptible` + const replyFiber = yield* client("uninterruptible").Uninterruptible( + new KeyedPayload({ id }) + ).pipe(Effect.forkChild({ startImmediately: true })) + yield* cluster.waitUntil( + "The uninterruptible handler did not start", + Effect.as(state.uninterruptibleEntered.await, true) + ) + + const stopping = yield* cluster.stop(owner).pipe(Effect.forkChild({ startImmediately: true })) + yield* cluster.start(1) + yield* cluster.waitForStableAssignments() + yield* cluster.waitUntil( + "The uninterruptible request was not resumed by the replacement runner", + Effect.sync(() => count("Uninterruptible", id) >= 2) + ) + state.uninterruptibleGate.openUnsafe() + yield* cluster.waitUntil( + "The resumed uninterruptible request did not complete", + Effect.sync(() => state.completedUninterruptible === 1) + ) + + assert.strictEqual(yield* Fiber.join(replyFiber), `uninterruptible:${id}`) + yield* Fiber.join(stopping) + assert.strictEqual(yield* cluster.repliedMessageCount, 1) + })) + + it.live(`${backend}: does not store or redeliver a volatile request after runner failure`, () => + Effect.gen(function*() { + resetState() + const cluster = yield* make({ + backend, + config: { entityTerminationTimeout: 100 }, + entities: PersistenceEntityLayer + }) + const [owner] = yield* cluster.start(1) + yield* cluster.waitForStableAssignments() + const client = yield* cluster.getClient(PersistenceEntity) + const id = `${backend}-volatile` + const requestFiber = yield* client("volatile").Volatile( + new KeyedPayload({ id }), + { discard: true } + ).pipe(Effect.forkChild({ startImmediately: true })) + yield* cluster.waitUntil( + "The volatile handler did not start", + Effect.as(state.volatileEntered.await, true) + ) + + yield* cluster.kill(owner) + yield* Fiber.interrupt(requestFiber) + state.volatileGate.openUnsafe() + yield* cluster.start(1) + yield* cluster.waitForStableAssignments() + const observationDeadline = (yield* Clock.currentTimeMillis) + 1_000 + yield* cluster.waitUntil( + "The volatile redelivery observation window did not elapse", + Effect.map(Clock.currentTimeMillis, (now) => now >= observationDeadline), + "2 seconds" + ) + + assert.strictEqual(count("Volatile", id), 1) + assert.strictEqual(state.completedVolatile, 0) + assert.deepStrictEqual(yield* cluster.messageCounts(), { + failed: 0, + replied: 0, + unprocessed: 0 + }) + })) + + it.live(`${backend}: persists typed failures and defects without wedging the mailbox`, () => + Effect.gen(function*() { + resetState() + const cluster = yield* make({ backend, entities: PersistenceEntityLayer }) + yield* cluster.start(2) + yield* cluster.waitForStableAssignments() + const client = yield* cluster.getClient(PersistenceEntity) + const typedId = `${backend}-typed` + const defectId = `${backend}-defect` + + assert.strictEqual( + yield* client("failures").TypedFailure(new KeyedPayload({ id: typedId })).pipe(Effect.flip), + `typed:${typedId}` + ) + assert.strictEqual( + yield* client("failures").TypedFailure(new KeyedPayload({ id: typedId })).pipe(Effect.flip), + `typed:${typedId}` + ) + assert.strictEqual(count("TypedFailure", typedId), 1) + + const firstDefect = yield* client("failures").Defect( + new KeyedPayload({ id: defectId }) + ).pipe(Effect.exit) + assert.isTrue(Exit.isFailure(firstDefect)) + if (Exit.isFailure(firstDefect)) { + assert.include(Cause.pretty(firstDefect.cause), `defect:${defectId}`) + } + const storedDefect = yield* client("failures").Defect( + new KeyedPayload({ id: defectId }) + ).pipe(Effect.exit) + assert.isTrue(Exit.isFailure(storedDefect)) + if (Exit.isFailure(storedDefect)) { + assert.include(Cause.pretty(storedDefect.cause), `defect:${defectId}`) + } + assert.strictEqual(count("Defect", defectId), 1) + + assert.strictEqual( + yield* client("failures").Healthy(new KeyedPayload({ id: `${backend}-healthy` })), + `healthy:${backend}-healthy` + ) + yield* cluster.waitUntil( + "The failure replies were not persisted", + Effect.map(cluster.messageCounts(), (counts) => counts.failed === 2 && counts.replied === 1) + ) + assert.deepStrictEqual(yield* cluster.messageCounts(), { + failed: 2, + replied: 1, + unprocessed: 0 + }) + })) + + it.live(`${backend}: round-trips a chunked reply through storage`, () => + Effect.gen(function*() { + resetState() + const cluster = yield* make({ backend, entities: PersistenceEntityLayer }) + yield* cluster.start(2) + yield* cluster.waitForStableAssignments() + const client = yield* cluster.getClient(PersistenceEntity) + const id = `${backend}-stream` + const values = yield* client("stream").Streamed(new KeyedPayload({ id })).pipe(Stream.runCollect) + assert.deepStrictEqual(Array.from(values), [0, 1, 2, 3, 4]) + yield* cluster.waitUntil( + "The terminal stream reply was not persisted", + Effect.map(cluster.repliedMessageCount, (value) => value === 1) + ) + assert.strictEqual(count("Streamed", id), 1) + })) + + it.live(`${backend}: resumes a persisted stream after its runner is killed`, () => + Effect.gen(function*() { + resetState() + const cluster = yield* make({ backend, entities: PersistenceEntityLayer }) + const [owner] = yield* cluster.start(1) + yield* cluster.waitForStableAssignments() + const client = yield* cluster.getClient(PersistenceEntity) + const id = `${backend}-stream-restart` + const received: Array = [] + const valuesFiber = yield* client("stream-restart").Streamed(new KeyedPayload({ id })).pipe( + Stream.tap((value) => Effect.sync(() => received.push(value))), + Stream.runCollect, + Effect.forkChild({ startImmediately: true }) + ) + yield* cluster.waitUntil( + "The stream did not deliver two chunks before blocking the third", + Effect.sync(() => received.length === 2 && received[0] === 0 && received[1] === 1) + ) + yield* cluster.waitUntil( + "The stream handler did not block before delivering its third chunk", + Effect.as(state.streamThirdEntered.await, true) + ) + + yield* cluster.kill(owner) + yield* cluster.start(1) + yield* cluster.waitForStableAssignments() + yield* cluster.waitUntil( + "The replacement runner did not resume the persisted stream", + Effect.sync(() => count("Streamed", id) === 2) + ) + state.streamThirdGate.openUnsafe() + + assert.deepStrictEqual(Array.from(yield* Fiber.join(valuesFiber)), [0, 1, 2, 3, 4]) + yield* cluster.waitUntil( + "The terminal stream reply was not persisted after recovery", + Effect.map( + cluster.messageCounts(), + (counts) => counts.replied === 1 && counts.unprocessed === 0 + ) + ) + assert.deepStrictEqual(yield* cluster.messageCounts(), { + failed: 0, + replied: 1, + unprocessed: 0 + }) + })) + + it.live(`${backend}: delivers scheduled messages only after their deadline`, () => + Effect.gen(function*() { + resetState() + const cluster = yield* make({ backend, entities: PersistenceEntityLayer }) + yield* cluster.start(2) + yield* cluster.waitForStableAssignments() + const client = yield* cluster.getClient(PersistenceEntity) + const id = `${backend}-scheduled` + const deliverAt = (yield* Clock.currentTimeMillis) + 1_500 + const replyFiber = yield* client("scheduled").Scheduled( + new ScheduledPayload({ deliverAt, id }) + ).pipe(Effect.forkChild({ startImmediately: true })) + + yield* cluster.waitUntil( + "The early-delivery observation point was not reached", + Effect.map(Clock.currentTimeMillis, (now) => now >= deliverAt - 500), + "2 seconds" + ) + assert.strictEqual(count("Scheduled", id), 0) + assert.deepStrictEqual(state.scheduledDeliveries, []) + + yield* cluster.waitUntil( + "The scheduled message was not delivered after its deadline", + Effect.sync(() => state.scheduledDeliveries.length === 1), + "5 seconds" + ) + yield* cluster.waitUntil( + "The scheduled reply was not persisted", + Effect.map(cluster.repliedMessageCount, (value) => value === 1) + ) + const deliveredAt = yield* Fiber.join(replyFiber) + assert.isAtLeast(deliveredAt, deliverAt) + assert.strictEqual(state.scheduledDeliveries[0], deliveredAt) + })) + } +}) diff --git a/repos/effect/packages/platform-node/test/cluster-integration/README.md b/repos/effect/packages/platform-node/test/cluster-integration/README.md new file mode 100644 index 0000000000..590f6da633 --- /dev/null +++ b/repos/effect/packages/platform-node/test/cluster-integration/README.md @@ -0,0 +1,36 @@ +# Cluster integration tests + +This suite runs multi-runner clusters against shared PostgreSQL and MySQL +containers. It is excluded from the default test projects and only registered +when `EFFECT_CLUSTER_TESTS=1`. + +```sh +EFFECT_CLUSTER_TESTS=1 pnpm test-cluster +``` + +Docker must be running. Vitest global setup starts one `postgres:alpine` and one +`mysql:lts` container for the entire project. Every cluster uses a unique table +prefix, and every runner listens on an operating-system-assigned port. + +## Harness + +`harness.ts` exposes: + +- `make({ backend, entities, lockMode, config })` to create a scoped cluster harness. +- `start(count, { assignedShardGroups, runnerShardWeight })` to start in-process socket runners and a client over msgpack. +- `stop(runner)` for graceful deregistration and shard handoff. +- `kill(runner)` for abrupt teardown without deregistration or explicit lock cleanup. +- `freeze(runner)` to suspend SQL heartbeats and lock refresh while leaving the runner's sockets and reserved SQL connection open. +- `waitUntil`, `waitForStableAssignments`, and `waitForEntityOwner` for deadline-based polling with cluster diagnostics on failure. +- `clientSharding` and `ownersOfShard` for direct shard ownership assertions. +- `messageCounts`, `unprocessedMessageCount`, `repliedMessageCount`, and `failedMessageCount` for storage assertions scoped to the cluster prefix. + +Advisory locks are owned by the reserved database session. A frozen advisory-lock +runner therefore keeps its locks until it is stopped or killed. Row-lock mode +uses expiry-driven takeover while frozen. + +## Adding a test + +Add `*.test.ts` under this directory. Test files define entities and assertions; +all cluster startup, lifecycle, waiting, and storage inspection belongs in the +harness. Use the harness polling helpers instead of calling `Effect.sleep`. diff --git a/repos/effect/packages/platform-node/test/cluster-integration/Smoke.test.ts b/repos/effect/packages/platform-node/test/cluster-integration/Smoke.test.ts new file mode 100644 index 0000000000..58cc5215eb --- /dev/null +++ b/repos/effect/packages/platform-node/test/cluster-integration/Smoke.test.ts @@ -0,0 +1,55 @@ +import { assert, describe, it } from "@effect/vitest" +import { Effect, PrimaryKey, Schema } from "effect" +import { ClusterSchema, Entity } from "effect/unstable/cluster" +import { Rpc } from "effect/unstable/rpc" +import { type Backend, make } from "./harness.ts" + +class Ping extends Schema.Class("Ping")({ + id: Schema.String +}) { + [PrimaryKey.symbol]() { + return this.id + } +} + +const TestEntity = Entity.make("ClusterIntegrationSmoke", [ + Rpc.make("Ping", { + payload: Ping, + success: Schema.String + }) +]).annotateRpcs(ClusterSchema.Persisted, true) + +const TestEntityLayer = TestEntity.toLayer({ + Ping: ({ payload }) => Effect.succeed(`pong:${payload.id}`) +}) + +describe("cluster integration smoke", () => { + for (const backend of ["pg", "mysql"] satisfies ReadonlyArray) { + it.live(`${backend}: persists messages and rebalances after an abrupt runner death`, () => + Effect.gen(function*() { + const cluster = yield* make({ backend, entities: TestEntityLayer }) + yield* cluster.start(3) + yield* cluster.waitForStableAssignments() + + const client = yield* cluster.getClient(TestEntity) + assert.strictEqual( + yield* client("entity-1").Ping(new Ping({ id: "request-1" })), + "pong:request-1" + ) + + yield* cluster.waitUntil( + "The smoke entity had no active owner", + Effect.map(cluster.ownerOfEntity(TestEntity, "entity-1"), (owner) => owner !== undefined) + ) + const owner = yield* cluster.ownerOfEntity(TestEntity, "entity-1") + yield* cluster.kill(owner!) + yield* cluster.waitForStableAssignments() + + assert.strictEqual( + yield* client("entity-1").Ping(new Ping({ id: "request-2" })), + "pong:request-2" + ) + assert.strictEqual(yield* cluster.repliedMessageCount, 2) + })) + } +}) diff --git a/repos/effect/packages/platform-node/test/cluster-integration/Workflow.test.ts b/repos/effect/packages/platform-node/test/cluster-integration/Workflow.test.ts new file mode 100644 index 0000000000..c831377592 --- /dev/null +++ b/repos/effect/packages/platform-node/test/cluster-integration/Workflow.test.ts @@ -0,0 +1,426 @@ +import { assert, describe, it } from "@effect/vitest" +import { Cause, Clock, Duration, Effect, Exit, Fiber, Latch, Layer, Option, Schema } from "effect" +import { ClusterWorkflowEngine, Entity, EntityId } from "effect/unstable/cluster" +import { PersistedQueue } from "effect/unstable/persistence" +import { Rpc } from "effect/unstable/rpc" +import { + Activity, + DurableClock, + DurableDeferred, + DurableQueue, + Workflow, + WorkflowEngine +} from "effect/unstable/workflow" +import { type Backend, type ClusterRunner, make } from "./harness.ts" + +const EndToEndWorkflow = Workflow.make("ClusterIntegrationEndToEnd", { + payload: { + id: Schema.String, + value: Schema.Number + }, + success: Schema.Number, + idempotencyKey: ({ id }) => id +}) + +let endToEndGate = Latch.makeUnsafe(true) +let endToEndEntered = Latch.makeUnsafe() +const endToEndRuns = new Map() + +const EndToEndWorkflowLayer = EndToEndWorkflow.toLayer(({ id, value }) => + Activity.make({ + name: "EndToEnd", + success: Schema.Number, + execute: Effect.gen(function*() { + endToEndRuns.set(id, (endToEndRuns.get(id) ?? 0) + 1) + endToEndEntered.openUnsafe() + yield* endToEndGate.await + return value + 1 + }) + }) +) + +const ReplayGate = DurableDeferred.make("ClusterIntegrationReplayGate", { + success: Schema.String +}) + +const ReplayWorkflow = Workflow.make("ClusterIntegrationReplay", { + payload: { id: Schema.String }, + success: Schema.String, + idempotencyKey: ({ id }) => id +}) + +const replayRuns = new Map() + +const ReplayWorkflowLayer = ReplayWorkflow.toLayer(Effect.fnUntraced(function*({ id }) { + yield* Activity.make({ + name: "BeforeSuspension", + execute: Effect.sync(() => replayRuns.set(id, (replayRuns.get(id) ?? 0) + 1)) + }) + return yield* DurableDeferred.await(ReplayGate) +})) + +const RestartGate = DurableDeferred.make("ClusterIntegrationRestartGate", { + success: Schema.String +}) + +const RestartWorkflow = Workflow.make("ClusterIntegrationRestart", { + payload: { id: Schema.String }, + success: Schema.String, + idempotencyKey: ({ id }) => id +}) + +const RestartWorkflowLayer = RestartWorkflow.toLayer(() => DurableDeferred.await(RestartGate)) + +class RetryError extends Schema.Error("ClusterIntegrationRetryError")({ + _tag: Schema.tag("ClusterIntegrationRetryError"), + attempt: Schema.Number +}) {} + +const RetryWorkflow = Workflow.make("ClusterIntegrationRetry", { + payload: { + id: Schema.String, + succeed: Schema.Boolean + }, + success: Schema.Number, + error: RetryError, + idempotencyKey: ({ id }) => id +}) + +const retryAttempts = new Map>() + +const RetryWorkflowLayer = RetryWorkflow.toLayer(({ id, succeed }) => + Activity.make({ + name: "Retry", + success: Schema.Number, + error: RetryError, + execute: Effect.gen(function*() { + const attempt = yield* Activity.CurrentAttempt + const attempts = retryAttempts.get(id) ?? [] + attempts.push(attempt) + retryAttempts.set(id, attempts) + if (succeed && attempt === 3) return attempt + return yield* new RetryError({ attempt }) + }) + }).pipe(Activity.retry({ times: 2 })) +) + +const ClockWorkflow = Workflow.make("ClusterIntegrationClock", { + payload: { id: Schema.String }, + success: Schema.Number, + idempotencyKey: ({ id }) => id +}) + +const ClockWorkflowLayer = ClockWorkflow.toLayer(() => + Effect.gen(function*() { + yield* DurableClock.sleep({ + name: "RestartSleep", + duration: "1 second", + inMemoryThreshold: Duration.zero + }) + return yield* Clock.currentTimeMillis + }) +) + +const Queue = DurableQueue.make({ + name: "ClusterIntegrationQueue", + payload: { id: Schema.String }, + success: Schema.String, + idempotencyKey: ({ id }) => id +}) + +const QueueWorkflow = Workflow.make("ClusterIntegrationQueue", { + payload: { id: Schema.String }, + success: Schema.String, + idempotencyKey: ({ id }) => id +}) + +const QueueWorkflowLayer = QueueWorkflow.toLayer(({ id }) => DurableQueue.process(Queue, { id })) +const queueRuns = new Map() +let queueWorkerGate = Latch.makeUnsafe() + +const QueueWorkerLayer = Layer.effectDiscard( + Effect.forkScoped( + Effect.suspend(() => queueWorkerGate.await).pipe( + Effect.andThen(DurableQueue.makeWorker(Queue, ({ id }) => + Effect.sync(() => { + queueRuns.set(id, (queueRuns.get(id) ?? 0) + 1) + return `processed:${id}` + }))) + ) + ) +) + +const InterruptGate = DurableDeferred.make("ClusterIntegrationInterruptGate") + +const InterruptWorkflow = Workflow.make("ClusterIntegrationInterrupt", { + payload: { id: Schema.String }, + idempotencyKey: ({ id }) => id +}) + +const InterruptWorkflowLayer = InterruptWorkflow.toLayer(() => DurableDeferred.await(InterruptGate)) + +const CompleteDeferred = Rpc.make("CompleteDeferred", { + payload: { + token: DurableDeferred.Token, + value: Schema.String + }, + success: Schema.String +}) + +const DeferredControl = Entity.make("ClusterIntegrationDeferredControl", [CompleteDeferred]) + +const DeferredControlLayer = DeferredControl.toLayer(Effect.gen(function*() { + const runner = yield* Entity.CurrentRunnerAddress + return { + CompleteDeferred: ({ payload }) => + DurableDeferred.succeed(RestartGate, payload).pipe( + Effect.as(`${runner.host}:${runner.port}`) + ) + } +})) + +const Workflows = Layer.mergeAll( + EndToEndWorkflowLayer, + ReplayWorkflowLayer, + RestartWorkflowLayer, + RetryWorkflowLayer, + ClockWorkflowLayer, + QueueWorkflowLayer, + QueueWorkerLayer, + InterruptWorkflowLayer, + DeferredControlLayer +) + +const entities = ({ prefix }: { readonly prefix: string }) => { + const queue = PersistedQueue.layer.pipe( + Layer.provideMerge(PersistedQueue.layerStoreSql({ + tableName: `${prefix}_workflow_queue`, + pollInterval: 100, + lockRefreshInterval: 500, + lockExpiration: 1_750 + })) + ) + return Workflows.pipe( + Layer.provide(queue), + Layer.provide(ClusterWorkflowEngine.layer), + Layer.orDie + ) +} + +type Cluster = Effect.Success> + +const withWorkflow = ( + cluster: Cluster, + effect: Effect.Effect +) => Effect.provideService(effect, WorkflowEngine.WorkflowEngine, cluster.workflowEngine) + +const waitForSuspended = Effect.fnUntraced(function*< + Name extends string, + Payload extends Workflow.AnyStructSchema, + Success extends Schema.Top, + Error extends Schema.Top +>( + cluster: Cluster, + workflow: Workflow.Workflow, + executionId: string +) { + yield* cluster.waitUntil( + `${workflow._tag}/${executionId} did not suspend`, + Effect.map(workflow.poll(executionId), (result) => Option.isSome(result) && result.value._tag === "Suspended") + ) +}) + +const waitForComplete = Effect.fnUntraced(function*< + Name extends string, + Payload extends Workflow.AnyStructSchema, + Success extends Schema.Top, + Error extends Schema.Top +>( + cluster: Cluster, + workflow: Workflow.Workflow, + executionId: string +) { + let complete: Workflow.Complete | undefined + yield* cluster.waitUntil( + `${workflow._tag}/${executionId} did not complete`, + Effect.map(workflow.poll(executionId), (result) => { + if (Option.isNone(result) || result.value._tag !== "Complete") return false + complete = result.value + return true + }) + ) + return complete! +}) + +const restart = Effect.fnUntraced(function*(cluster: Cluster) { + const running = cluster.runners.filter((runner) => runner.state() === "running") + yield* Effect.forEach(running, cluster.kill, { concurrency: "unbounded", discard: true }) + const replacements = yield* cluster.start(3) + yield* cluster.waitForStableAssignments() + return replacements +}) + +const workflowOwner = (cluster: Cluster, executionId: string) => { + const shard = cluster.clientSharding.getShardId(EntityId.make(executionId), "default") + return cluster.ownersOfShard(shard)[0] +} + +const findControlOnAnotherRunner = Effect.fnUntraced(function*( + cluster: Cluster, + workflowRunner: ClusterRunner +) { + for (let index = 0; index < 2_000; index++) { + const id = `control-${index}` + const owner = yield* cluster.ownerOfEntity(DeferredControl, id) + if (owner !== undefined && owner !== workflowRunner) return [id, owner] as const + } + return yield* Effect.die("Could not route deferred control to another runner") +}) + +describe("cluster workflow integration", () => { + for (const backend of ["pg", "mysql"] satisfies ReadonlyArray) { + it.live(`${backend}: executes end to end and deduplicates concurrent callers`, () => + Effect.gen(function*() { + const id = `${backend}-end-to-end` + endToEndGate = Latch.makeUnsafe() + endToEndEntered = Latch.makeUnsafe() + const cluster = yield* make({ backend, entities }) + yield* cluster.start(3) + yield* cluster.waitForStableAssignments() + + const first = yield* withWorkflow(cluster, EndToEndWorkflow.execute({ id, value: 41 })).pipe( + Effect.forkChild({ startImmediately: true }) + ) + yield* cluster.waitUntil("The end-to-end activity did not start", Effect.as(endToEndEntered.await, true)) + const second = yield* withWorkflow(cluster, EndToEndWorkflow.execute({ id, value: 41 })).pipe( + Effect.forkChild({ startImmediately: true }) + ) + endToEndGate.openUnsafe() + + assert.strictEqual(yield* Fiber.join(first), 42) + assert.strictEqual(yield* Fiber.join(second), 42) + assert.strictEqual(endToEndRuns.get(id), 1) + })) + + it.live(`${backend}: replays completed activities after the owner dies`, () => + Effect.gen(function*() { + const id = `${backend}-replay` + const cluster = yield* make({ backend, entities }) + yield* cluster.start(3) + yield* cluster.waitForStableAssignments() + const executionId = yield* withWorkflow(cluster, ReplayWorkflow.execute({ id }, { discard: true })) + yield* waitForSuspended(cluster, ReplayWorkflow, executionId) + assert.strictEqual(replayRuns.get(id), 1) + + const owner = workflowOwner(cluster, executionId) + assert.isDefined(owner) + yield* cluster.kill(owner!) + yield* cluster.waitForStableAssignments() + const token = DurableDeferred.tokenFromExecutionId(ReplayGate, { workflow: ReplayWorkflow, executionId }) + yield* withWorkflow(cluster, DurableDeferred.succeed(ReplayGate, { token, value: "resumed" })) + const result = yield* waitForComplete(cluster, ReplayWorkflow, executionId) + + assert.deepStrictEqual(result.exit, Exit.succeed("resumed")) + assert.strictEqual(replayRuns.get(id), 1) + })) + + it.live(`${backend}: resumes deferred workflows across a whole-cluster restart from another runner`, () => + Effect.gen(function*() { + const id = `${backend}-restart` + const cluster = yield* make({ backend, entities }) + yield* cluster.start(3) + yield* cluster.waitForStableAssignments() + const executionId = yield* withWorkflow(cluster, RestartWorkflow.execute({ id }, { discard: true })) + yield* waitForSuspended(cluster, RestartWorkflow, executionId) + yield* restart(cluster) + + const owner = workflowOwner(cluster, executionId) + assert.isDefined(owner) + const [controlId, controlOwner] = yield* findControlOnAnotherRunner(cluster, owner!) + const control = yield* cluster.getClient(DeferredControl) + const token = DurableDeferred.tokenFromExecutionId(RestartGate, { workflow: RestartWorkflow, executionId }) + const completedBy = yield* control(controlId).CompleteDeferred({ token, value: "after-restart" }) + assert.strictEqual(completedBy, `${controlOwner.address.host}:${controlOwner.address.port}`) + + const result = yield* waitForComplete(cluster, RestartWorkflow, executionId) + assert.deepStrictEqual(result.exit, Exit.succeed("after-restart")) + })) + + it.live(`${backend}: applies activity retry policy and preserves the exhausted error`, () => + Effect.gen(function*() { + const cluster = yield* make({ backend, entities }) + yield* cluster.start(3) + yield* cluster.waitForStableAssignments() + const successId = `${backend}-retry-success` + const failureId = `${backend}-retry-failure` + + assert.strictEqual( + yield* withWorkflow(cluster, RetryWorkflow.execute({ id: successId, succeed: true })), + 3 + ) + const error = yield* withWorkflow( + cluster, + RetryWorkflow.execute({ id: failureId, succeed: false }) + ).pipe(Effect.flip) + + assert.deepStrictEqual(retryAttempts.get(successId), [1, 2, 3]) + assert.deepStrictEqual(retryAttempts.get(failureId), [1, 2, 3]) + assert.strictEqual(error._tag, "ClusterIntegrationRetryError") + assert.strictEqual(error.attempt, 3) + })) + + it.live(`${backend}: wakes a durable clock after a whole-cluster restart`, () => + Effect.gen(function*() { + const cluster = yield* make({ backend, entities }) + yield* cluster.start(3) + yield* cluster.waitForStableAssignments() + const started = yield* Clock.currentTimeMillis + const executionId = yield* withWorkflow( + cluster, + ClockWorkflow.execute({ id: `${backend}-clock` }, { discard: true }) + ) + yield* waitForSuspended(cluster, ClockWorkflow, executionId) + yield* restart(cluster) + + const result = yield* waitForComplete(cluster, ClockWorkflow, executionId) + assert(Exit.isSuccess(result.exit)) + assert.isAtLeast(result.exit.value - started, 900) + })) + + it.live(`${backend}: persists queued work across restart and consumes it once`, () => + Effect.gen(function*() { + const id = `${backend}-queue` + queueWorkerGate = Latch.makeUnsafe() + const cluster = yield* make({ backend, entities }) + yield* cluster.start(3) + yield* cluster.waitForStableAssignments() + const executionId = yield* withWorkflow(cluster, QueueWorkflow.execute({ id }, { discard: true })) + yield* waitForSuspended(cluster, QueueWorkflow, executionId) + yield* restart(cluster) + queueWorkerGate.openUnsafe() + + const result = yield* waitForComplete(cluster, QueueWorkflow, executionId) + assert.deepStrictEqual(result.exit, Exit.succeed(`processed:${id}`)) + assert.strictEqual(queueRuns.get(id), 1) + })) + + it.live(`${backend}: persists interruption across a whole-cluster restart`, () => + Effect.gen(function*() { + const cluster = yield* make({ backend, entities }) + yield* cluster.start(3) + yield* cluster.waitForStableAssignments() + const executionId = yield* withWorkflow( + cluster, + InterruptWorkflow.execute({ id: `${backend}-interrupt` }, { discard: true }) + ) + yield* waitForSuspended(cluster, InterruptWorkflow, executionId) + yield* withWorkflow(cluster, InterruptWorkflow.interrupt(executionId)) + yield* restart(cluster) + + const result = yield* waitForComplete(cluster, InterruptWorkflow, executionId) + assert(Exit.isFailure(result.exit)) + assert.isTrue(Exit.hasInterrupts(result.exit)) + assert.isTrue(Cause.hasInterrupts(result.exit.cause)) + })) + } +}) diff --git a/repos/effect/packages/platform-node/test/cluster-integration/globalSetup.ts b/repos/effect/packages/platform-node/test/cluster-integration/globalSetup.ts new file mode 100644 index 0000000000..52dcf8b86a --- /dev/null +++ b/repos/effect/packages/platform-node/test/cluster-integration/globalSetup.ts @@ -0,0 +1,38 @@ +import { MySqlContainer } from "@testcontainers/mysql" +import { PostgreSqlContainer } from "@testcontainers/postgresql" +import type { TestProject } from "vitest/node" + +export interface ClusterDatabases { + readonly mysql: string + readonly pg: string +} + +declare module "vitest" { + export interface ProvidedContext { + readonly clusterDatabases: ClusterDatabases + } +} + +const makeMysqlContainer = () => + new MySqlContainer("mysql:lts").withHealthCheck({ + test: [ + "CMD-SHELL", + "MYSQL_PWD=\"$MYSQL_ROOT_PASSWORD\" mysqladmin ping --protocol TCP --host 127.0.0.1 --user root --silent" + ], + interval: 250, + timeout: 1000, + retries: 1000 + }) + +export default function setup(project: TestProject) { + return Promise.all([ + new PostgreSqlContainer("postgres:alpine").start(), + makeMysqlContainer().start() + ]).then(([pg, mysql]) => { + project.provide("clusterDatabases", { + mysql: mysql.getConnectionUri(), + pg: pg.getConnectionUri() + }) + return () => Promise.all([pg.stop(), mysql.stop()]).then(() => undefined) + }) +} diff --git a/repos/effect/packages/platform-node/test/cluster-integration/harness.ts b/repos/effect/packages/platform-node/test/cluster-integration/harness.ts new file mode 100644 index 0000000000..11992f7ea9 --- /dev/null +++ b/repos/effect/packages/platform-node/test/cluster-integration/harness.ts @@ -0,0 +1,469 @@ +import { NodeClusterSocket, NodeCrypto, NodeSocketServer } from "@effect/platform-node" +import { MysqlClient } from "@effect/sql-mysql2" +import { PgClient } from "@effect/sql-pg" +import { Clock, Context, Duration, Effect, Exit, Latch, Layer, Option, Redacted, Scope } from "effect" +import { + ClusterWorkflowEngine, + type Entity, + EntityId, + type MessageStorage, + type Runner as RunnerModel, + RunnerAddress, + RunnerHealth, + Runners, + RunnerStorage, + ShardId, + Sharding, + ShardingConfig, + SocketRunner, + SqlMessageStorage, + SqlRunnerStorage +} from "effect/unstable/cluster" +import type { Rpc } from "effect/unstable/rpc" +import { RpcSerialization } from "effect/unstable/rpc" +import * as SocketServer from "effect/unstable/socket/SocketServer" +import { SqlClient } from "effect/unstable/sql" +import { WorkflowEngine } from "effect/unstable/workflow" +import { inject } from "vitest" + +export type Backend = "mysql" | "pg" +export type LockMode = "advisory" | "row" + +export interface ClusterRunner { + readonly address: RunnerAddress.RunnerAddress + readonly index: number + readonly shardGroups: ReadonlyArray + readonly sharding: Sharding.Sharding["Service"] + readonly state: () => "frozen" | "killed" | "running" | "stopped" +} + +export interface MessageCounts { + readonly failed: number + readonly replied: number + readonly unprocessed: number +} + +export interface MakeOptions { + readonly backend: Backend + readonly config?: Partial | undefined + readonly entities: + | RunnerEntities + | ((options: { readonly prefix: string }) => RunnerEntities) + readonly lockMode?: LockMode | undefined + readonly runnerLayer?: RunnerLayer | undefined +} + +export interface StartOptions { + readonly assignedShardGroups?: ReadonlyArray | undefined + readonly runnerShardWeight?: number | undefined +} + +type HarnessConfig = Partial & { + readonly shardLockDisableAdvisory: boolean +} + +type RunnerEntities = Layer.Layer< + never, + never, + Sharding.Sharding | MessageStorage.MessageStorage | SqlClient.SqlClient +> + +interface RegistrationRow { + readonly address: string + readonly healthy: boolean | number + readonly last_heartbeat: Date | string + readonly runner: unknown +} + +interface MessageRow { + readonly processed: boolean | number + readonly reply_payload: string | Record | null +} + +interface RunnerEntry extends ClusterRunner { + readonly controller: ReturnType + readonly scope: Scope.Closeable + setState(state: ReturnType): void +} + +const clusterConfig = { + entityMaxIdleTime: 3_000, + entityMessagePollInterval: 500, + refreshAssignmentsInterval: 150, + shardLockExpiration: 1_750, + shardLockRefreshInterval: 500 +} as const + +let nextCluster = 0 + +const makeRunnerStorageController = (storage: RunnerStorage.RunnerStorage["Service"]) => { + const gate = Latch.makeUnsafe(true) + const refreshPaused = Latch.makeUnsafe() + const syncPaused = Latch.makeUnsafe() + let mode: "frozen" | "killed" | "running" = "running" + let lastRunners: Array = [] + + const waitWhileFrozen = ( + paused: Latch.Latch, + effect: Effect.Effect, + onKilled: () => A + ): Effect.Effect => + Effect.suspend(() => { + if (mode === "running") return effect + if (mode === "killed") return Effect.succeed(onKilled()) + paused.openUnsafe() + return gate.await.pipe( + Effect.uninterruptible, + Effect.andThen(Effect.suspend(() => mode === "running" ? effect : Effect.succeed(onKilled()))) + ) + }) + + const controlled = RunnerStorage.RunnerStorage.of({ + ...storage, + getRunners: waitWhileFrozen( + syncPaused, + storage.getRunners.pipe(Effect.tap((runners) => Effect.sync(() => lastRunners = runners))), + () => lastRunners + ), + refresh: (address, shardIds) => { + const shards = Array.from(shardIds) + return waitWhileFrozen(refreshPaused, storage.refresh(address, shards), () => shards) + }, + release: (address, shardId) => mode === "killed" ? Effect.void : storage.release(address, shardId), + releaseAll: (address) => mode === "killed" ? Effect.void : storage.releaseAll(address), + unregister: (address) => mode === "killed" ? Effect.void : storage.unregister(address) + }) + + return { + controlled, + freeze: Effect.sync(() => { + mode = "frozen" + gate.closeUnsafe() + }).pipe(Effect.andThen(Effect.all([refreshPaused.await, syncPaused.await], { discard: true }))), + kill: Effect.sync(() => { + mode = "killed" + gate.openUnsafe() + }), + resume: Effect.sync(() => { + mode = "running" + gate.openUnsafe() + }) + } +} + +const RunnerHealthLive = RunnerHealth.layerPing.pipe( + Layer.provide(Runners.layerRpc), + Layer.provide(NodeClusterSocket.layerClientProtocol) +) + +export const socketRunnerLayer = ( + address: RunnerAddress.RunnerAddress, + entities: RunnerEntities, + socketServer: SocketServer.SocketServer["Service"], + config: HarnessConfig +) => + entities.pipe( + Layer.provideMerge(SocketRunner.layer), + Layer.provide(RunnerHealthLive), + Layer.provide(Layer.succeed(SocketServer.SocketServer, socketServer)), + Layer.provide(NodeClusterSocket.layerClientProtocol), + Layer.provide(ShardingConfig.layer({ + ...config, + runnerAddress: Option.some(address) + })), + Layer.provide(RpcSerialization.layerMsgPack) + ) + +export type RunnerLayer = typeof socketRunnerLayer + +const clientLayer = (config: HarnessConfig) => + SocketRunner.layerClientOnly.pipe( + Layer.provide(NodeClusterSocket.layerClientProtocol), + Layer.provide(ShardingConfig.layer(config)), + Layer.provide(RpcSerialization.layerMsgPack) + ) + +const parseReply = (payload: MessageRow["reply_payload"]): Record | undefined => { + if (payload === null) return undefined + return typeof payload === "string" ? JSON.parse(payload) : payload +} + +export const make = Effect.fnUntraced(function*(options: MakeOptions) { + const parentScope = yield* Effect.scope + const prefix = `cluster_${process.pid}_${nextCluster++}` + const config = { + ...clusterConfig, + ...options.config, + shardLockDisableAdvisory: options.lockMode === "row" + } + const databases = inject("clusterDatabases") + const databaseLayer = options.backend === "pg" + ? PgClient.layer({ url: Redacted.make(databases.pg), maxConnections: 32 }) + : MysqlClient.layer({ url: Redacted.make(databases.mysql), maxConnections: 32 }) + const database = yield* Layer.buildWithScope(databaseLayer, parentScope) + const sql = Context.get(database, SqlClient.SqlClient).withoutTransforms() + const messageStorage = yield* SqlMessageStorage.layerWith({ prefix }).pipe( + Layer.provide(NodeCrypto.layer), + Layer.provide(ShardingConfig.layer(config)), + Layer.buildWithScope(parentScope), + Effect.provide(database) + ) + const shared = Context.merge(database, messageStorage) + const entities = typeof options.entities === "function" ? options.entities({ prefix }) : options.entities + const runners: Array = [] + + const makeRunnerStorage = Effect.fnUntraced(function*( + scope: Scope.Closeable, + storageConfig: HarnessConfig = config + ) { + return yield* SqlRunnerStorage.layerWith({ prefix }).pipe( + Layer.provide(ShardingConfig.layer(storageConfig)), + Layer.orDie, + Layer.buildWithScope(scope), + Effect.provide(database) + ) + }) + + const clientScope = yield* Scope.fork(parentScope) + const clientStorage = yield* makeRunnerStorage(clientScope) + const clientBase = yield* clientLayer(config).pipe( + Layer.buildWithScope(clientScope), + Effect.provide(Context.merge(shared, clientStorage)) + ) + const workflowEngine = yield* ClusterWorkflowEngine.make.pipe( + Effect.provide(Context.merge(shared, clientBase)) + ) + const client = Context.add(clientBase, WorkflowEngine.WorkflowEngine, workflowEngine) + const clientSharding = Context.get(client, Sharding.Sharding) + let nextRunnerIndex = 0 + const startRunner = Effect.fnUntraced(function*(index: number, startOptions?: StartOptions) { + const scope = yield* Scope.fork(parentScope) + const runnerConfig: HarnessConfig = { + ...config, + ...(startOptions?.assignedShardGroups === undefined + ? undefined + : { assignedShardGroups: startOptions.assignedShardGroups }), + ...(startOptions?.runnerShardWeight === undefined + ? undefined + : { runnerShardWeight: startOptions.runnerShardWeight }) + } + const serverContext = yield* NodeSocketServer.layer({ host: "127.0.0.1", port: 0 }).pipe( + Layer.buildWithScope(scope) + ) + const socketServer = Context.get(serverContext, SocketServer.SocketServer) + if (socketServer.address._tag !== "TcpAddress") { + return yield* Effect.die("Expected a TCP socket server") + } + const address = RunnerAddress.make("127.0.0.1", socketServer.address.port) + const rawStorageContext = yield* makeRunnerStorage(scope, runnerConfig) + const controller = makeRunnerStorageController(Context.get(rawStorageContext, RunnerStorage.RunnerStorage)) + const storage = Context.make(RunnerStorage.RunnerStorage, controller.controlled) + const context = yield* (options.runnerLayer ?? socketRunnerLayer)( + address, + entities, + socketServer, + runnerConfig + ).pipe( + Layer.buildWithScope(scope), + Effect.provide(Context.mergeAll(shared, storage)) + ) + const sharding = Context.get(context, Sharding.Sharding) + let state: ReturnType = "running" + const runner: RunnerEntry = { + address, + controller, + index, + shardGroups: runnerConfig.assignedShardGroups ?? ShardingConfig.defaults.assignedShardGroups, + scope, + setState(next) { + state = next + }, + sharding, + state: () => state + } + runners.push(runner) + return runner as ClusterRunner + }) + + const start = Effect.fnUntraced(function*(runnerCount: number, startOptions?: StartOptions) { + return yield* Effect.forEach( + Array.from({ length: runnerCount }, () => nextRunnerIndex++), + (index) => startRunner(index, startOptions) + ) + }) + + const entryFor = (runner: ClusterRunner) => runners.find((entry) => entry === runner)! + + const stop = Effect.fnUntraced(function*(runner: ClusterRunner) { + const entry = entryFor(runner) + if (entry.state() === "stopped" || entry.state() === "killed") return + if (entry.state() === "frozen") yield* entry.controller.resume + entry.setState("stopped") + yield* Scope.close(entry.scope, Exit.void) + }) + + const kill = Effect.fnUntraced(function*(runner: ClusterRunner) { + const entry = entryFor(runner) + if (entry.state() === "stopped" || entry.state() === "killed") return + entry.setState("killed") + yield* entry.controller.kill + yield* Scope.close(entry.scope, Exit.void) + }) + + const freeze = Effect.fnUntraced(function*(runner: ClusterRunner) { + const entry = entryFor(runner) + if (entry.state() !== "running") return + entry.setState("frozen") + yield* entry.controller.freeze + }) + + const assignmentMap = () => { + const assignments: Record> = {} + const groups = config.availableShardGroups ?? ShardingConfig.defaults.availableShardGroups + const shardsPerGroup = config.shardsPerGroup ?? ShardingConfig.defaults.shardsPerGroup + for (const group of groups) { + for (let id = 1; id <= shardsPerGroup; id++) { + const shard = ShardId.make(group, id) + assignments[shard.toString()] = runners + .filter((runner) => runner.state() === "running" && runner.sharding.hasShardId(shard)) + .map((runner) => `${runner.address.host}:${runner.address.port}`) + } + } + return assignments + } + + const ownersOfShard = (shard: ShardId.ShardId, includeInactive = false) => + runners.filter((runner) => (includeInactive || runner.state() === "running") && runner.sharding.hasShardId(shard)) + + const messageCounts = Effect.fnUntraced(function*() { + const messages = sql(`${prefix}_messages`) + const replies = sql(`${prefix}_replies`) + const rows = yield* sql` + SELECT m.processed, r.payload AS reply_payload + FROM ${messages} m + LEFT JOIN ${replies} r ON r.id = m.last_reply_id + WHERE m.kind = 0 + ` + let failed = 0 + let replied = 0 + let unprocessed = 0 + for (const row of rows) { + if (!row.processed) { + unprocessed++ + continue + } + const reply = parseReply(row.reply_payload) + if (reply?._tag === "Success") replied++ + if (reply?._tag === "Failure") failed++ + } + return { failed, replied, unprocessed } satisfies MessageCounts + }) + + const diagnostics = Effect.fnUntraced(function*() { + const table = sql(`${prefix}_runners`) + const registrations = yield* sql` + SELECT address, runner, healthy, last_heartbeat + FROM ${table} + ORDER BY address + ` + return { + assignments: assignmentMap(), + messageCounts: yield* messageCounts(), + registrations + } + }) + + const waitUntil = Effect.fnUntraced(function*( + description: string, + condition: Effect.Effect, + timeout: Duration.Input = "15 seconds" + ) { + const started = yield* Clock.currentTimeMillis + const deadline = started + Duration.toMillis(Duration.fromInputUnsafe(timeout)) + if (yield* Effect.provide(condition, client)) return + while ((yield* Clock.currentTimeMillis) < deadline) { + yield* Effect.sleep(100) + if (yield* Effect.provide(condition, client)) return + } + const state = yield* diagnostics() + return yield* Effect.fail(new Error(`${description}\n${JSON.stringify(state, null, 2)}`)) + }) + + const waitForStableAssignments = Effect.fnUntraced(function*(timeout?: Duration.Input) { + let previous = "" + let stablePolls = 0 + yield* waitUntil( + "Shard assignments did not stabilize before the deadline", + Effect.sync(() => { + const current = assignmentMap() + const owners = new Set(Object.values(current).flat()) + const complete = Object.values(current).every((owners) => owners.length === 1) && + runners.every((runner) => + runner.state() !== "running" || owners.has(`${runner.address.host}:${runner.address.port}`) + ) + const encoded = complete ? JSON.stringify(current) : "" + stablePolls = encoded !== "" && encoded === previous ? stablePolls + 1 : 0 + previous = encoded + return stablePolls >= 3 + }), + timeout + ) + return assignmentMap() + }) + + const shardOfEntity = ( + entity: Entity.Entity, + entityId: string + ) => entity.getShardId(EntityId.make(entityId)).pipe(Effect.provide(client)) + + const ownerOfEntity = Effect.fnUntraced(function*( + entity: Entity.Entity, + entityId: string + ) { + const shardId = yield* shardOfEntity(entity, entityId) + return runners.find((runner) => runner.state() === "running" && runner.sharding.hasShardId(shardId)) as + | ClusterRunner + | undefined + }) + + const waitForEntityOwner = ( + entity: Entity.Entity, + entityId: string, + runner: ClusterRunner, + timeout?: Duration.Input + ) => + waitUntil( + `Entity ${entity.type}/${entityId} was not owned by runner ${runner.index} before the deadline`, + Effect.map(ownerOfEntity(entity, entityId), (owner) => owner === runner), + timeout + ) + + const getClient = (entity: Entity.Entity) => + entity.client.pipe(Effect.provide(client)) + + return { + assignmentMap, + backend: options.backend, + clientSharding, + diagnostics, + failedMessageCount: Effect.map(messageCounts(), (counts) => counts.failed), + freeze, + getClient, + kill, + lockMode: options.lockMode ?? "advisory", + messageCounts, + ownerOfEntity, + ownersOfShard, + prefix, + repliedMessageCount: Effect.map(messageCounts(), (counts) => counts.replied), + runners: runners as ReadonlyArray, + shardOfEntity, + start, + stop, + unprocessedMessageCount: Effect.map(messageCounts(), (counts) => counts.unprocessed), + waitForEntityOwner, + waitForStableAssignments, + waitUntil, + workflowEngine + } as const +}) diff --git a/repos/effect/packages/platform-node/test/cluster/MessageStorageTest.ts b/repos/effect/packages/platform-node/test/cluster/MessageStorageTest.ts index f580519bc7..972719fda0 100644 --- a/repos/effect/packages/platform-node/test/cluster/MessageStorageTest.ts +++ b/repos/effect/packages/platform-node/test/cluster/MessageStorageTest.ts @@ -136,6 +136,13 @@ export class PrimaryKeyTest extends Rpc.make("PrimaryKeyTest", { primaryKey: (value) => value.id.toString() }) {} +export class LongKeyRpc extends Rpc.make("LongKeyRpc", { + payload: { + id: Schema.String + }, + primaryKey: (value) => value.id +}) {} + export class StreamRpc extends Rpc.make("StreamTest", { success: RpcSchema.Stream(Schema.Void, Schema.Never), payload: { diff --git a/repos/effect/packages/platform-node/test/cluster/SocketRunner.test.ts b/repos/effect/packages/platform-node/test/cluster/SocketRunner.test.ts index 721cbf443d..57d7fda93d 100644 --- a/repos/effect/packages/platform-node/test/cluster/SocketRunner.test.ts +++ b/repos/effect/packages/platform-node/test/cluster/SocketRunner.test.ts @@ -1,6 +1,7 @@ import { NodeClusterSocket } from "@effect/platform-node" -import { describe, it } from "@effect/vitest" -import { BigDecimal, Effect, Layer, Option, PrimaryKey, Schema } from "effect" +import { assert, describe, it } from "@effect/vitest" +import { BigDecimal, Cause, Effect, Exit, Fiber, Layer, Option, PrimaryKey, Schema } from "effect" +import type { Sharding } from "effect/unstable/cluster" import { ClusterSchema, Entity, @@ -48,8 +49,8 @@ const SharedStorage = Layer.mergeAll( Layer.provide(ShardingConfig.layerDefaults) ) -const makeRunnerLayer = (port: number) => - TestEntityLayer.pipe( +const makeRunnerLayer = (port: number, entities: Layer.Layer) => + entities.pipe( Layer.provideMerge(SocketRunner.layer), Layer.provide(RunnerHealth.layerNoop), Layer.provide(NodeClusterSocket.layerSocketServer), @@ -76,6 +77,30 @@ const makeClientLayer = (port: number) => Layer.provide(RpcSerialization.layerMsgPack) ) +// An entity whose reply cannot be serialized: the handler returns a +// non-integer for a `Schema.Int` success schema, so `Reply.serialize` fails +// on the host runner when encoding the reply for the wire. +const IsolationEntity = Entity + .make("IsolationEntity", [ + Rpc.make("BadReply", { + payload: { id: Schema.Number }, + success: Schema.Int + }), + Rpc.make("Slow", { + success: Schema.String + }) + ]) + .annotateRpcs(ClusterSchema.Persisted, false) + +const IsolationEntityLayer = IsolationEntity.toLayer( + Effect.succeed({ + BadReply: () => Effect.succeed(1.5), + Slow: () => Effect.as(Effect.sleep("2 seconds"), "done") + }) +) + +const ISOLATION_PORT = 50_124 + // BigDecimal.normalize creates a circular `normalized` self-reference. // When a persisted message is sent with discard: true, the notify path in Runners.makeRpc // passes the raw envelope (with circular BigDecimal payload) to the runner via msgpack, @@ -86,7 +111,7 @@ describe("SocketRunner", () => { () => Effect.gen(function*() { // Start the runner (with socket server and entity handler) - yield* Layer.launch(makeRunnerLayer(RUNNER_PORT)).pipe(Effect.forkScoped) + yield* Layer.launch(makeRunnerLayer(RUNNER_PORT, TestEntityLayer)).pipe(Effect.forkScoped) // Give the runner time to start and acquire shards yield* Effect.sleep("2 seconds") @@ -117,4 +142,49 @@ describe("SocketRunner", () => { )), 30_000 ) + + it.live( + "a reply serialization failure fails only its own request", + () => + Effect.gen(function*() { + // Start the runner hosting the entities + yield* Layer.launch(makeRunnerLayer(ISOLATION_PORT, IsolationEntityLayer)).pipe(Effect.forkScoped) + yield* Effect.sleep("2 seconds") + + yield* Effect.gen(function*() { + const makeClient = yield* IsolationEntity.client + // Give the client time to discover the runner + yield* Effect.sleep("3 seconds") + + // a sibling request in flight on the same runner-to-runner connection + const slowFiber = yield* makeClient("slow-entity").Slow().pipe(Effect.forkChild) + yield* Effect.sleep("300 millis") + + const badExit = yield* makeClient("bad-entity").BadReply({ id: 1 }).pipe(Effect.exit) + assert.isTrue(Exit.isFailure(badExit), "the unencodable reply must fail the request") + const failure = Exit.isFailure(badExit) ? String(Cause.squash(badExit.cause)) : "" + assert.include(failure, "MalformedMessage", "the caller must receive the real encode error") + assert.notInclude( + failure, + "AlreadyProcessingMessage", + "the request must not be re-sent into the entity's dedup guard" + ) + + const slowExit = yield* Fiber.await(slowFiber) + assert.isTrue( + Exit.isSuccess(slowExit), + "a sibling in-flight request on the same connection must be unaffected" + ) + if (Exit.isSuccess(slowExit)) { + assert.strictEqual(slowExit.value, "done") + } + }).pipe( + Effect.provide(makeClientLayer(ISOLATION_PORT)), + Effect.scoped + ) + }).pipe(Effect.provide( + SharedStorage + )), + 30_000 + ) }) diff --git a/repos/effect/packages/platform-node/test/cluster/SqlMessageStorage.integration.test.ts b/repos/effect/packages/platform-node/test/cluster/SqlMessageStorage.integration.test.ts new file mode 100644 index 0000000000..6c88a13aac --- /dev/null +++ b/repos/effect/packages/platform-node/test/cluster/SqlMessageStorage.integration.test.ts @@ -0,0 +1,390 @@ +import { NodeCrypto, NodeFileSystem } from "@effect/platform-node" +import { SqliteClient } from "@effect/sql-sqlite-node" +import { assert, describe, expect, it } from "@effect/vitest" +import { Effect, Fiber, FileSystem, Latch, Layer, Option } from "effect" +import { TestClock } from "effect/testing" +import { + Entity, + Envelope, + Message, + MessageStorage, + RunnerHealth, + Runners, + RunnerStorage, + Sharding, + ShardingConfig, + Snowflake, + SqlMessageStorage +} from "effect/unstable/cluster" +import { SqlClient } from "effect/unstable/sql" +import { MysqlContainer } from "../fixtures/mysql2-utils.ts" +import { PgContainer } from "../fixtures/pg-utils.ts" +import { + GetUserRpc, + LongKeyRpc, + makeAckChunk, + makeChunkReply, + makeReply, + makeRequest, + PrimaryKeyTest, + StreamRpc +} from "./MessageStorageTest.ts" + +const TestEntity = Entity.make("test", [GetUserRpc]) +const TestEntityLayer = TestEntity.toLayer({ + GetUser: () => Effect.void +}) + +const StorageLive = SqlMessageStorage.layer.pipe( + Layer.provideMerge(Snowflake.layerGenerator), + Layer.provide([ShardingConfig.layerDefaults, NodeCrypto.layer]) +) + +const truncate = Effect.gen(function*() { + const sql = yield* SqlClient.SqlClient + yield* sql`DELETE FROM cluster_replies` + yield* sql`DELETE FROM cluster_messages` +}) + +describe("SqlMessageStorage", () => { + ;([ + ["pg", Layer.orDie(PgContainer.layerClient)], + ["mysql", Layer.orDie(MysqlContainer.layerClient)], + ["sqlite", Layer.orDie(SqliteLayer)] + ] as const).forEach(([label, layer]) => { + it.layer(StorageLive.pipe(Layer.provideMerge(layer)), { + timeout: 120000 + })(label, (it) => { + it.effect("saveRequest", () => + Effect.gen(function*() { + const storage = yield* MessageStorage.MessageStorage + const request = yield* makeRequest({ payload: { id: 1 } }) + const result = yield* storage.saveRequest(request) + expect(result._tag).toEqual("Success") + + for (let i = 2; i <= 5; i++) { + yield* storage.saveRequest(yield* makeRequest({ payload: { id: i } })) + } + + yield* storage.saveReply(yield* makeReply(request)) + + let messages = yield* storage.unprocessedMessages([request.envelope.address.shardId]) + expect(messages).toHaveLength(4) + expect(messages.map((m: any) => m.envelope.payload.id)).toEqual([2, 3, 4, 5]) + + for (let i = 6; i <= 10; i++) { + yield* storage.saveRequest(yield* makeRequest({ payload: { id: i } })) + } + messages = yield* storage.unprocessedMessages([request.envelope.address.shardId]) + expect(messages).toHaveLength(5) + expect(messages.map((m: any) => m.envelope.payload.id)).toEqual([6, 7, 8, 9, 10]) + })) + + it.effect("saveReply + saveRequest duplicate", () => + Effect.gen(function*() { + const sql = yield* SqlClient.SqlClient + const storage = yield* MessageStorage.MessageStorage + const request = yield* makeRequest({ + rpc: StreamRpc, + payload: StreamRpc.payloadSchema.make({ id: 123 }) + }) + let result = yield* storage.saveRequest(request) + expect(result._tag).toEqual("Success") + + let chunk = yield* makeChunkReply(request, 0) + yield* storage.saveReply(chunk) + const ackChunk = yield* makeAckChunk(request, chunk) + yield* storage.saveEnvelope(ackChunk) + + chunk = yield* makeChunkReply(request, 1) + yield* storage.saveReply(chunk) + + result = yield* storage.saveRequest( + yield* makeRequest({ + rpc: StreamRpc, + payload: StreamRpc.payloadSchema.make({ id: 123 }) + }) + ) + assert(result._tag === "Duplicate" && Option.isSome(result.lastReceivedReply)) + expect(result.lastReceivedReply.value._tag).toEqual("Chunk") + + // get the un-acked chunk + const replies = yield* storage.repliesFor([request]) + expect(replies).toHaveLength(1) + + yield* storage.saveReply(yield* makeReply(request)) + + result = yield* storage.saveRequest( + yield* makeRequest({ + rpc: StreamRpc, + payload: StreamRpc.payloadSchema.make({ id: 123 }) + }) + ) + assert(result._tag === "Duplicate" && Option.isSome(result.lastReceivedReply)) + expect(result.lastReceivedReply.value._tag).toEqual("WithExit") + + // duplicate WithExit + const fiber = yield* storage.saveReply(yield* makeReply(request)).pipe(Effect.forkChild) + yield* TestClock.adjust(1) + while (!fiber.pollUnsafe()) { + yield* sql`SELECT 1` + yield* TestClock.adjust(1000) + } + const error = yield* Effect.flip(Fiber.join(fiber)) + expect(error._tag).toEqual("PersistenceError") + })) + + it.effect("detects duplicates", () => + Effect.gen(function*() { + yield* truncate + + const storage = yield* MessageStorage.MessageStorage + yield* storage.saveRequest( + yield* makeRequest({ + rpc: PrimaryKeyTest, + payload: PrimaryKeyTest.payloadSchema.make({ id: 123 }) + }) + ) + const result = yield* storage.saveRequest( + yield* makeRequest({ + rpc: PrimaryKeyTest, + payload: PrimaryKeyTest.payloadSchema.make({ id: 123 }) + }) + ) + expect(result._tag).toEqual("Duplicate") + })) + + it.effect("hashes primary keys longer than the message_id column", () => + Effect.gen(function*() { + yield* truncate + + const storage = yield* MessageStorage.MessageStorage + const longId = "long-key-".repeat(50) + const request = yield* makeRequest({ + rpc: LongKeyRpc, + payload: LongKeyRpc.payloadSchema.make({ id: longId }) + }) + const result = yield* storage.saveRequest(request) + expect(result._tag).toEqual("Success") + + const duplicate = yield* storage.saveRequest( + yield* makeRequest({ + rpc: LongKeyRpc, + payload: LongKeyRpc.payloadSchema.make({ id: longId }) + }) + ) + expect(duplicate._tag).toEqual("Duplicate") + + const requestId = yield* storage.requestIdForPrimaryKey({ + address: request.envelope.address, + tag: request.envelope.tag, + id: longId + }) + expect(requestId).toEqual(Option.some(request.envelope.requestId)) + + const sql = yield* SqlClient.SqlClient + const rows = yield* sql<{ message_id: string }>`SELECT message_id FROM cluster_messages` + expect(rows).toHaveLength(1) + expect(rows[0].message_id).toMatch(/^[0-9a-f]{64}$/) + })) + + it.effect("keeps primary keys within the column width as plaintext", () => + Effect.gen(function*() { + yield* truncate + + const sql = yield* SqlClient.SqlClient + const storage = yield* MessageStorage.MessageStorage + const request = yield* makeRequest({ + rpc: PrimaryKeyTest, + payload: PrimaryKeyTest.payloadSchema.make({ id: 456 }) + }) + yield* storage.saveRequest(request) + + // rows written by previous versions store the plaintext composed + // key, so short keys must stay byte-identical to keep deduplicating + const plaintext = Envelope.primaryKey(request.envelope)! + const rows = yield* sql<{ message_id: string }>`SELECT message_id FROM cluster_messages` + expect(rows).toHaveLength(1) + expect(rows[0].message_id).toEqual(plaintext) + + const result = yield* storage.saveRequest( + yield* makeRequest({ + rpc: PrimaryKeyTest, + payload: PrimaryKeyTest.payloadSchema.make({ id: 456 }) + }) + ) + assert(result._tag === "Duplicate") + expect(result.originalId).toEqual(request.envelope.requestId) + + const requestId = yield* storage.requestIdForPrimaryKey({ + address: request.envelope.address, + tag: request.envelope.tag, + id: "456" + }) + expect(requestId).toEqual(Option.some(request.envelope.requestId)) + })) + + if (label === "sqlite") { + // sqlite's TEXT message_id column stored over-long plaintext keys + // before hashing, so the legacy fallback must also cover keys longer + // than the 255-character limit of the width-enforcing dialects + it.effect("detects duplicates for legacy long-key plaintext rows", () => + Effect.gen(function*() { + yield* truncate + + const sql = yield* SqlClient.SqlClient + const storage = yield* MessageStorage.MessageStorage + const longId = "legacy-long-key-".repeat(30) + const request = yield* makeRequest({ + rpc: LongKeyRpc, + payload: LongKeyRpc.payloadSchema.make({ id: longId }) + }) + yield* storage.saveRequest(request) + + // simulate a row written before message_id values were hashed + const plaintext = Envelope.primaryKey(request.envelope)! + expect(plaintext.length).toBeGreaterThan(255) + yield* sql`UPDATE cluster_messages SET message_id = ${plaintext} WHERE id = ${ + String(request.envelope.requestId) + }` + + const result = yield* storage.saveRequest( + yield* makeRequest({ + rpc: LongKeyRpc, + payload: LongKeyRpc.payloadSchema.make({ id: longId }) + }) + ) + assert(result._tag === "Duplicate") + expect(result.originalId).toEqual(request.envelope.requestId) + + const requestId = yield* storage.requestIdForPrimaryKey({ + address: request.envelope.address, + tag: request.envelope.tag, + id: longId + }) + expect(requestId).toEqual(Option.some(request.envelope.requestId)) + })) + } + + it.effect("unprocessedMessages", () => + Effect.gen(function*() { + yield* truncate + + const storage = yield* MessageStorage.MessageStorage + const request = yield* makeRequest() + yield* storage.saveRequest(request) + let messages = yield* storage.unprocessedMessages([request.envelope.address.shardId]) + expect(messages).toHaveLength(1) + messages = yield* storage.unprocessedMessages([request.envelope.address.shardId]) + expect(messages).toHaveLength(0) + yield* storage.saveRequest(yield* makeRequest()) + messages = yield* storage.unprocessedMessages([request.envelope.address.shardId]) + expect(messages).toHaveLength(1) + })) + + it.effect("unprocessedMessages excludes complete requests", () => + Effect.gen(function*() { + yield* truncate + + const storage = yield* MessageStorage.MessageStorage + const request = yield* makeRequest() + yield* storage.saveRequest(request) + yield* storage.saveReply(yield* makeReply(request)) + const messages = yield* storage.unprocessedMessages([request.envelope.address.shardId]) + expect(messages).toHaveLength(0) + })) + + it.effect("repliesFor", () => + Effect.gen(function*() { + yield* truncate + + const storage = yield* MessageStorage.MessageStorage + const request = yield* makeRequest() + yield* storage.saveRequest(request) + let replies = yield* storage.repliesFor([request]) + expect(replies).toHaveLength(0) + yield* storage.saveReply(yield* makeReply(request)) + replies = yield* storage.repliesFor([request]) + expect(replies).toHaveLength(1) + expect(replies[0].requestId).toEqual(request.envelope.requestId) + })) + + it.effect("registerReplyHandler", () => + Effect.gen(function*() { + const storage = yield* MessageStorage.MessageStorage + const latch = yield* Latch.make() + const request = yield* makeRequest() + yield* storage.saveRequest(request) + const fiber = yield* storage.registerReplyHandler( + new Message.OutgoingRequest({ + ...request, + respond: () => latch.open + }) + ).pipe(Effect.forkChild) + yield* TestClock.adjust(1) + yield* storage.saveReply(yield* makeReply(request)) + yield* latch.await + yield* Fiber.await(fiber) + })) + + it.effect("unprocessedMessagesById", () => + Effect.gen(function*() { + yield* truncate + + const storage = yield* MessageStorage.MessageStorage + const request = yield* makeRequest() + yield* storage.saveRequest(request) + let messages = yield* storage.unprocessedMessagesById([request.envelope.requestId]) + expect(messages).toHaveLength(1) + yield* storage.saveReply(yield* makeReply(request)) + messages = yield* storage.unprocessedMessagesById([request.envelope.requestId]) + expect(messages).toHaveLength(0) + })) + }) + }) + + it.effect("keeps held messages readable while entity layers build", () => + Effect.gen(function*() { + const config = ShardingConfig.layer({ + entityRegistrationTimeout: 6000, + entityTerminationTimeout: 0, + entityMessagePollInterval: 100, + refreshAssignmentsInterval: 0 + }) + const delayedEnv = TestEntityLayer.pipe( + Layer.provide(Layer.effectDiscard(Effect.sleep(10_000))), + Layer.provideMerge(Sharding.layer), + Layer.provide(RunnerStorage.layerMemory), + Layer.provide(RunnerHealth.layerNoop), + Layer.provide(Runners.layerNoop), + Layer.provide(config) + ) + const storage = yield* MessageStorage.MessageStorage + const request = yield* makeRequest() + yield* storage.saveRequest(request) + + const fiber = yield* Effect.never.pipe( + Effect.provide(delayedEnv), + Effect.scoped, + Effect.forkChild({ startImmediately: true }) + ) + + yield* TestClock.adjust(7500) + expect(yield* storage.repliesFor([request])).toHaveLength(0) + + yield* TestClock.adjust(2500) + yield* TestClock.adjust(100) + expect(yield* storage.repliesFor([request])).toHaveLength(1) + yield* Fiber.interrupt(fiber) + }).pipe(Effect.provide(StorageLive.pipe( + Layer.provideMerge(SqliteLayer) + )))) +}) + +const SqliteLayer = Effect.gen(function*() { + const fs = yield* FileSystem.FileSystem + const dir = yield* fs.makeTempDirectoryScoped() + return SqliteClient.layer({ + filename: dir + "/test.db" + }) +}).pipe(Layer.unwrap, Layer.provide(NodeFileSystem.layer)) diff --git a/repos/effect/packages/platform-node/test/cluster/SqlMessageStorage.test.ts b/repos/effect/packages/platform-node/test/cluster/SqlMessageStorage.test.ts deleted file mode 100644 index 16deaaa863..0000000000 --- a/repos/effect/packages/platform-node/test/cluster/SqlMessageStorage.test.ts +++ /dev/null @@ -1,222 +0,0 @@ -import { NodeFileSystem } from "@effect/platform-node" -import { SqliteClient } from "@effect/sql-sqlite-node" -import { assert, describe, expect, it } from "@effect/vitest" -import { Effect, Fiber, FileSystem, Latch, Layer, Option } from "effect" -import { TestClock } from "effect/testing" -import { Message, MessageStorage, ShardingConfig, Snowflake, SqlMessageStorage } from "effect/unstable/cluster" -import { SqlClient } from "effect/unstable/sql" -import { MysqlContainer } from "../fixtures/mysql2-utils.ts" -import { PgContainer } from "../fixtures/pg-utils.ts" -import { - makeAckChunk, - makeChunkReply, - makeReply, - makeRequest, - PrimaryKeyTest, - StreamRpc -} from "./MessageStorageTest.ts" - -const StorageLive = SqlMessageStorage.layer.pipe( - Layer.provideMerge(Snowflake.layerGenerator), - Layer.provide(ShardingConfig.layerDefaults) -) - -const truncate = Effect.gen(function*() { - const sql = yield* SqlClient.SqlClient - yield* sql`DELETE FROM cluster_replies` - yield* sql`DELETE FROM cluster_messages` -}) - -describe("SqlMessageStorage", () => { - ;([ - ["pg", Layer.orDie(PgContainer.layerClient)], - ["mysql", Layer.orDie(MysqlContainer.layerClient)], - ["sqlite", Layer.orDie(SqliteLayer)] - ] as const).forEach(([label, layer]) => { - it.layer(StorageLive.pipe(Layer.provideMerge(layer)), { - timeout: 120000 - })(label, (it) => { - it.effect("saveRequest", () => - Effect.gen(function*() { - const storage = yield* MessageStorage.MessageStorage - const request = yield* makeRequest({ payload: { id: 1 } }) - const result = yield* storage.saveRequest(request) - expect(result._tag).toEqual("Success") - - for (let i = 2; i <= 5; i++) { - yield* storage.saveRequest(yield* makeRequest({ payload: { id: i } })) - } - - yield* storage.saveReply(yield* makeReply(request)) - - let messages = yield* storage.unprocessedMessages([request.envelope.address.shardId]) - expect(messages).toHaveLength(4) - expect(messages.map((m: any) => m.envelope.payload.id)).toEqual([2, 3, 4, 5]) - - for (let i = 6; i <= 10; i++) { - yield* storage.saveRequest(yield* makeRequest({ payload: { id: i } })) - } - messages = yield* storage.unprocessedMessages([request.envelope.address.shardId]) - expect(messages).toHaveLength(5) - expect(messages.map((m: any) => m.envelope.payload.id)).toEqual([6, 7, 8, 9, 10]) - })) - - it.effect("saveReply + saveRequest duplicate", () => - Effect.gen(function*() { - const sql = yield* SqlClient.SqlClient - const storage = yield* MessageStorage.MessageStorage - const request = yield* makeRequest({ - rpc: StreamRpc, - payload: StreamRpc.payloadSchema.make({ id: 123 }) - }) - let result = yield* storage.saveRequest(request) - expect(result._tag).toEqual("Success") - - let chunk = yield* makeChunkReply(request, 0) - yield* storage.saveReply(chunk) - const ackChunk = yield* makeAckChunk(request, chunk) - yield* storage.saveEnvelope(ackChunk) - - chunk = yield* makeChunkReply(request, 1) - yield* storage.saveReply(chunk) - - result = yield* storage.saveRequest( - yield* makeRequest({ - rpc: StreamRpc, - payload: StreamRpc.payloadSchema.make({ id: 123 }) - }) - ) - assert(result._tag === "Duplicate" && Option.isSome(result.lastReceivedReply)) - expect(result.lastReceivedReply.value._tag).toEqual("Chunk") - - // get the un-acked chunk - const replies = yield* storage.repliesFor([request]) - expect(replies).toHaveLength(1) - - yield* storage.saveReply(yield* makeReply(request)) - - result = yield* storage.saveRequest( - yield* makeRequest({ - rpc: StreamRpc, - payload: StreamRpc.payloadSchema.make({ id: 123 }) - }) - ) - assert(result._tag === "Duplicate" && Option.isSome(result.lastReceivedReply)) - expect(result.lastReceivedReply.value._tag).toEqual("WithExit") - - // duplicate WithExit - const fiber = yield* storage.saveReply(yield* makeReply(request)).pipe(Effect.forkChild) - yield* TestClock.adjust(1) - while (!fiber.pollUnsafe()) { - yield* sql`SELECT 1` - yield* TestClock.adjust(1000) - } - const error = yield* Effect.flip(Fiber.join(fiber)) - expect(error._tag).toEqual("PersistenceError") - })) - - it.effect("detects duplicates", () => - Effect.gen(function*() { - yield* truncate - - const storage = yield* MessageStorage.MessageStorage - yield* storage.saveRequest( - yield* makeRequest({ - rpc: PrimaryKeyTest, - payload: PrimaryKeyTest.payloadSchema.make({ id: 123 }) - }) - ) - const result = yield* storage.saveRequest( - yield* makeRequest({ - rpc: PrimaryKeyTest, - payload: PrimaryKeyTest.payloadSchema.make({ id: 123 }) - }) - ) - expect(result._tag).toEqual("Duplicate") - })) - - it.effect("unprocessedMessages", () => - Effect.gen(function*() { - yield* truncate - - const storage = yield* MessageStorage.MessageStorage - const request = yield* makeRequest() - yield* storage.saveRequest(request) - let messages = yield* storage.unprocessedMessages([request.envelope.address.shardId]) - expect(messages).toHaveLength(1) - messages = yield* storage.unprocessedMessages([request.envelope.address.shardId]) - expect(messages).toHaveLength(0) - yield* storage.saveRequest(yield* makeRequest()) - messages = yield* storage.unprocessedMessages([request.envelope.address.shardId]) - expect(messages).toHaveLength(1) - })) - - it.effect("unprocessedMessages excludes complete requests", () => - Effect.gen(function*() { - yield* truncate - - const storage = yield* MessageStorage.MessageStorage - const request = yield* makeRequest() - yield* storage.saveRequest(request) - yield* storage.saveReply(yield* makeReply(request)) - const messages = yield* storage.unprocessedMessages([request.envelope.address.shardId]) - expect(messages).toHaveLength(0) - })) - - it.effect("repliesFor", () => - Effect.gen(function*() { - yield* truncate - - const storage = yield* MessageStorage.MessageStorage - const request = yield* makeRequest() - yield* storage.saveRequest(request) - let replies = yield* storage.repliesFor([request]) - expect(replies).toHaveLength(0) - yield* storage.saveReply(yield* makeReply(request)) - replies = yield* storage.repliesFor([request]) - expect(replies).toHaveLength(1) - expect(replies[0].requestId).toEqual(request.envelope.requestId) - })) - - it.effect("registerReplyHandler", () => - Effect.gen(function*() { - const storage = yield* MessageStorage.MessageStorage - const latch = yield* Latch.make() - const request = yield* makeRequest() - yield* storage.saveRequest(request) - const fiber = yield* storage.registerReplyHandler( - new Message.OutgoingRequest({ - ...request, - respond: () => latch.open - }) - ).pipe(Effect.forkChild) - yield* TestClock.adjust(1) - yield* storage.saveReply(yield* makeReply(request)) - yield* latch.await - yield* Fiber.await(fiber) - })) - - it.effect("unprocessedMessagesById", () => - Effect.gen(function*() { - yield* truncate - - const storage = yield* MessageStorage.MessageStorage - const request = yield* makeRequest() - yield* storage.saveRequest(request) - let messages = yield* storage.unprocessedMessagesById([request.envelope.requestId]) - expect(messages).toHaveLength(1) - yield* storage.saveReply(yield* makeReply(request)) - messages = yield* storage.unprocessedMessagesById([request.envelope.requestId]) - expect(messages).toHaveLength(0) - })) - }) - }) -}) - -const SqliteLayer = Effect.gen(function*() { - const fs = yield* FileSystem.FileSystem - const dir = yield* fs.makeTempDirectoryScoped() - return SqliteClient.layer({ - filename: dir + "/test.db" - }) -}).pipe(Layer.unwrap, Layer.provide(NodeFileSystem.layer)) diff --git a/repos/effect/packages/platform-node/test/cluster/SqlRunnerStorage.integration.test.ts b/repos/effect/packages/platform-node/test/cluster/SqlRunnerStorage.integration.test.ts new file mode 100644 index 0000000000..d95f993636 --- /dev/null +++ b/repos/effect/packages/platform-node/test/cluster/SqlRunnerStorage.integration.test.ts @@ -0,0 +1,459 @@ +import { NodeFileSystem } from "@effect/platform-node" +import { SqliteClient } from "@effect/sql-sqlite-node" +import { assert, describe, expect, it } from "@effect/vitest" +import { Cause, Duration, Effect, Exit, FileSystem, Layer, Schedule } from "effect" +import { TestClock } from "effect/testing" +import { + ClusterError, + Runner, + RunnerAddress, + RunnerStorage, + ShardId, + ShardingConfig, + SqlRunnerStorage +} from "effect/unstable/cluster" +import { SqlClient, type SqlConnection, SqlError } from "effect/unstable/sql" +import { MysqlContainer } from "../fixtures/mysql2-utils.ts" +import { PgContainer } from "../fixtures/pg-utils.ts" + +const StorageLive = SqlRunnerStorage.layer + +describe("SqlRunnerStorage", () => { + it.effect("bounds shard lock operations and rebuilds an unresponsive reserved connection", () => { + const partitioned = makePartitionState() + const layer = StorageLive.pipe( + Layer.provideMerge(blackholeReservedConnection(partitioned, true)), + Layer.provide(ShardingConfig.layer({ + shardLockExpiration: 1000, + shardLockRefreshInterval: 100 + })) + ) + + return Effect.gen(function*() { + const storage = yield* RunnerStorage.RunnerStorage + const runner = Runner.make({ + address: runnerAddress1, + groups: ["default"], + weight: 1 + }) + const shards = [ShardId.make("default", 1)] + + yield* storage.register(runner, true) + yield* storage.acquire(runnerAddress1, shards) + partitioned.current = true + + const expectDeadline = Effect.fnUntraced(function*(operation: Effect.Effect) { + const [elapsed, exit] = yield* operation.pipe( + Effect.exit, + Effect.timed, + TestClock.withLive + ) + assert(Exit.isFailure(exit)) + const error = Cause.squash(exit.cause) + assert(error instanceof ClusterError.PersistenceError) + assert.isBelow(Duration.toMillis(elapsed), 1000) + yield* Effect.sleep(20).pipe(TestClock.withLive) + }) + + yield* expectDeadline(storage.refresh(runnerAddress1, shards)) + yield* expectDeadline(storage.refresh(runnerAddress1, shards)) + yield* expectDeadline(storage.refresh(runnerAddress1, shards)) + + assert.isAtLeast(partitioned.interruptedQueries, 1) + assert.isAtMost(partitioned.maxActiveQueries, 1) + + // Rebuilding is asynchronous, so wait until the replacement connection + // is ready before checking that lock operations recover. + const usableConnections = partitioned.usableConnections + partitioned.current = false + yield* waitUntil(() => partitioned.usableConnections > usableConnections) + expect(yield* storage.refresh(runnerAddress1, shards).pipe(TestClock.withLive)).toEqual(shards) + + partitioned.current = true + yield* expectDeadline(storage.acquire(runnerAddress1, [ShardId.make("default", 2)])) + const usableConnectionsAfterAcquire = partitioned.usableConnections + partitioned.current = false + yield* waitUntil(() => partitioned.usableConnections > usableConnectionsAfterAcquire) + yield* storage.refresh(runnerAddress1, shards).pipe(TestClock.withLive) + + partitioned.current = true + yield* expectDeadline(storage.release(runnerAddress1, shards[0])) + const usableConnectionsAfterRelease = partitioned.usableConnections + partitioned.current = false + yield* waitUntil(() => partitioned.usableConnections > usableConnectionsAfterRelease) + yield* storage.refresh(runnerAddress1, shards).pipe(TestClock.withLive) + yield* storage.release(runnerAddress1, shards[0]).pipe(TestClock.withLive) + + assert.strictEqual(partitioned.activeQueries, 0) + }).pipe( + // Ensure layer teardown cannot mask a body failure with Vitest's timeout. + Effect.ensuring(Effect.sync(() => { + partitioned.current = false + })), + Effect.provide(layer) + ) + }, 60_000) + + it.effect("recovers when a blackholed query cannot resume after the partition clears", () => { + const partitioned = makePartitionState() + const layer = StorageLive.pipe( + Layer.provideMerge(blackholeReservedConnection(partitioned, false)), + Layer.provide(ShardingConfig.layer({ + shardLockDisableAdvisory: true, + shardLockExpiration: 1000, + shardLockRefreshInterval: 100 + })) + ) + + return Effect.gen(function*() { + const storage = yield* RunnerStorage.RunnerStorage + const runner = Runner.make({ + address: runnerAddress1, + groups: ["default"], + weight: 1 + }) + const shards = [ShardId.make("default", 1)] + + yield* storage.register(runner, true) + yield* storage.acquire(runnerAddress1, shards) + partitioned.current = true + yield* storage.refresh(runnerAddress1, shards).pipe(Effect.exit, TestClock.withLive) + yield* waitUntil(() => partitioned.activeQueries === 0) + + partitioned.current = false + expect( + yield* storage.refresh(runnerAddress1, shards).pipe( + Effect.retry({ times: 5, schedule: Schedule.spaced(20) }), + TestClock.withLive + ) + ).toEqual(shards) + assert.isAtLeast(partitioned.interruptedQueries, 1) + assert.isAtMost(partitioned.maxActiveQueries, 1) + }).pipe(Effect.provide(layer)) + }, 60_000) + + it.effect("rebuilds the reserved connection again when a rebuilt connection stops responding", () => { + const partitioned = makePartitionState() + const layer = StorageLive.pipe( + Layer.provideMerge(blackholeReservedConnection(partitioned, false)), + Layer.provide(ShardingConfig.layer({ + shardLockExpiration: 1000, + shardLockRefreshInterval: 100 + })) + ) + + return Effect.gen(function*() { + const storage = yield* RunnerStorage.RunnerStorage + const runner = Runner.make({ + address: runnerAddress1, + groups: ["default"], + weight: 1 + }) + const shards = [ShardId.make("default", 1)] + + yield* storage.register(runner, true) + yield* storage.acquire(runnerAddress1, shards) + + // a failing lock operation rebuilds the reserved connection + partitioned.failNextQueries = 1 + yield* storage.refresh(runnerAddress1, shards).pipe(Effect.exit, TestClock.withLive) + yield* waitUntil(() => partitioned.usableConnections === 2) + + // the rebuilt connection then wedges, without any lock operation + // succeeding in between - a further rebuild still has to be attempted + const reserved = partitioned.reservedConnections + partitioned.current = true + yield* storage.refresh(runnerAddress1, shards).pipe(Effect.exit, TestClock.withLive) + yield* storage.refresh(runnerAddress1, shards).pipe(Effect.exit, TestClock.withLive) + partitioned.current = false + yield* waitUntil(() => partitioned.reservedConnections > reserved) + + expect( + yield* storage.refresh(runnerAddress1, shards).pipe( + Effect.retry({ times: 5, schedule: Schedule.spaced(20) }), + TestClock.withLive + ) + ).toEqual(shards) + }).pipe(Effect.provide(layer)) + }, 60_000) + + it.effect("rebuilds the reserved connection when releasing the previous one hangs", () => { + const partitioned = makePartitionState() + const layer = StorageLive.pipe( + Layer.provideMerge(blackholeReservedConnection(partitioned, false)), + Layer.provide(ShardingConfig.layer({ + shardLockDisableAdvisory: true, + shardLockExpiration: 1000, + shardLockRefreshInterval: 100 + })) + ) + + return Effect.gen(function*() { + const storage = yield* RunnerStorage.RunnerStorage + const runner = Runner.make({ + address: runnerAddress1, + groups: ["default"], + weight: 1 + }) + const shards = [ShardId.make("default", 1)] + + yield* storage.register(runner, true) + yield* storage.acquire(runnerAddress1, shards) + + // the connection wedges and the driver never releases it back to the + // pool, so the rebuild stalls closing the previous scope + partitioned.current = true + partitioned.blockRelease = true + yield* storage.refresh(runnerAddress1, shards).pipe(Effect.exit, TestClock.withLive) + yield* Effect.sleep(150).pipe(TestClock.withLive) + + // the stalled release must not disable further rebuilds + partitioned.current = false + const reserved = partitioned.reservedConnections + yield* storage.refresh(runnerAddress1, shards).pipe(Effect.exit, TestClock.withLive) + yield* waitUntil(() => partitioned.reservedConnections > reserved) + + expect( + yield* storage.refresh(runnerAddress1, shards).pipe( + Effect.retry({ times: 5, schedule: Schedule.spaced(20) }), + TestClock.withLive + ) + ).toEqual(shards) + }).pipe( + // let the stalled release finish so the layer can be torn down + Effect.ensuring(Effect.sync(() => { + partitioned.blockRelease = false + })), + Effect.provide(layer) + ) + }, 60_000) + + it.effect("isolates advisory shard locks by prefix", () => + Effect.gen(function*() { + const storageA = yield* SqlRunnerStorage.make({ prefix: "cluster" }) + const storageB = yield* SqlRunnerStorage.make({ prefix: "other" }) + const shard = ShardId.make("default", 1) + + expect(yield* storageA.acquire(runnerAddress1, [shard])).toEqual([shard]) + expect(yield* storageB.acquire(runnerAddress2, [shard])).toEqual([shard]) + }).pipe( + // Release the advisory-lock connections before the PostgreSQL layer. + Effect.scoped, + Effect.provide(PgContainer.layerClient), + Effect.provide(ShardingConfig.layer()) + ), 60_000) + + it.effect("excludes other storages using the same prefix", () => + Effect.gen(function*() { + const storageA = yield* SqlRunnerStorage.make({ prefix: "cluster" }) + const storageB = yield* SqlRunnerStorage.make({ prefix: "cluster" }) + const shard = ShardId.make("default", 1) + + expect(yield* storageA.acquire(runnerAddress1, [shard])).toEqual([shard]) + expect(yield* storageB.acquire(runnerAddress2, [shard])).toEqual([]) + }).pipe( + // Release the advisory-lock connections before the PostgreSQL layer. + Effect.scoped, + Effect.provide(PgContainer.layerClient), + Effect.provide(ShardingConfig.layer()) + ), 60_000) + ;([ + ["pg", Layer.orDie(PgContainer.layerClient)], + ["mysql", Layer.orDie(MysqlContainer.layerClient)], + ["vitess", Layer.orDie(MysqlContainer.layerClientVitess)], + ["sqlite", Layer.orDie(SqliteLayer)] + ] as const).flatMap(([label, layer]) => + [ + [label, StorageLive.pipe(Layer.provideMerge(layer), Layer.provide(ShardingConfig.layer()))], + [ + label + " (no advisory)", + StorageLive.pipe( + Layer.provideMerge(layer), + Layer.provide(ShardingConfig.layer({ + shardLockDisableAdvisory: true + })) + ) + ] + ] as const + ).forEach(([label, layer]) => { + it.layer(layer, { + timeout: 60000 + })(label, (it) => { + it.effect("getRunners", () => + Effect.gen(function*() { + const storage = yield* RunnerStorage.RunnerStorage + + const runner = Runner.make({ + address: runnerAddress1, + groups: ["default"], + weight: 1 + }) + const machineId = yield* storage.register(runner, true) + yield* storage.register(runner, true) + expect(machineId).toEqual(1) + expect(yield* storage.getRunners).toEqual([[runner, true]]) + + yield* storage.setRunnerHealth(runnerAddress1, false) + expect(yield* storage.getRunners).toEqual([[runner, false]]) + + yield* storage.unregister(runnerAddress1) + expect(yield* storage.getRunners).toEqual([]) + }), 30_000) + + it.effect("acquireShards", () => + Effect.gen(function*() { + const storage = yield* RunnerStorage.RunnerStorage + + let acquired = yield* storage.acquire(runnerAddress1, [ + ShardId.make("default", 1), + ShardId.make("default", 2), + ShardId.make("default", 3) + ]) + expect(acquired.map((_) => _.id)).toEqual([1, 2, 3]) + acquired = yield* storage.acquire(runnerAddress1, [ + ShardId.make("default", 1), + ShardId.make("default", 2), + ShardId.make("default", 3) + ]) + expect(acquired.map((_) => _.id)).toEqual([1, 2, 3]) + + const refreshed = yield* storage.refresh(runnerAddress1, [ + ShardId.make("default", 1), + ShardId.make("default", 2), + ShardId.make("default", 3) + ]) + expect(refreshed.map((_) => _.id)).toEqual([1, 2, 3]) + + // smoke test release + yield* storage.release(runnerAddress1, ShardId.make("default", 2)) + })) + }) + }) +}) + +const runnerAddress1 = RunnerAddress.make("localhost", 1234) +const runnerAddress2 = RunnerAddress.make("localhost", 5678) + +interface PartitionState { + current: boolean + blockRelease: boolean + activeQueries: number + maxActiveQueries: number + interruptedQueries: number + failNextQueries: number + reservedConnections: number + usableConnections: number +} + +const makePartitionState = (): PartitionState => ({ + current: false, + blockRelease: false, + activeQueries: 0, + maxActiveQueries: 0, + interruptedQueries: 0, + failNextQueries: 0, + reservedConnections: 0, + usableConnections: 0 +}) + +const waitUntil = Effect.fnUntraced( + function*(predicate: () => boolean) { + while (!predicate()) { + yield* Effect.sleep(20) + } + }, + Effect.timeoutOrElse({ + duration: 10_000, + orElse: () => Effect.die("timed out waiting for condition") + }), + TestClock.withLive +) + +const blackholeReservedConnection = (partitioned: PartitionState, resumePending: boolean) => + Layer.effect( + SqlClient.SqlClient, + Effect.gen(function*() { + const sql = yield* SqlClient.SqlClient + const wrapConnection = (connection: SqlConnection.Connection): SqlConnection.Connection => { + let usable = false + const execute = (effect: Effect.Effect) => + Effect.suspend((): Effect.Effect => { + if (partitioned.failNextQueries > 0) { + partitioned.failNextQueries-- + return Effect.fail( + new SqlError.SqlError({ + reason: new SqlError.ConnectionError({ cause: new Error("connection lost") }) + }) + ) + } + partitioned.activeQueries++ + partitioned.maxActiveQueries = Math.max(partitioned.maxActiveQueries, partitioned.activeQueries) + return Effect.suspend(function waitForConnection(): Effect.Effect { + if (!partitioned.current) return effect + return resumePending + ? Effect.andThen(Effect.sleep(5), waitForConnection) + : Effect.never + }).pipe( + Effect.onExit((exit) => + Effect.sync(() => { + partitioned.activeQueries-- + if (Exit.hasInterrupts(exit)) { + partitioned.interruptedQueries++ + } + if (Exit.isSuccess(exit) && !usable) { + usable = true + partitioned.usableConnections++ + } + }) + ) + ) + }) + return { + ...connection, + execute: (...args) => execute(connection.execute(...args)), + executeRaw: (...args) => execute(connection.executeRaw(...args)), + executeValues: (...args) => execute(connection.executeValues(...args)), + executeValuesUnprepared: (...args) => execute(connection.executeValuesUnprepared(...args)), + executeUnprepared: (...args) => execute(connection.executeUnprepared(...args)) + } + } + let client: SqlClient.SqlClient + client = new Proxy(sql, { + get(target, property, receiver) { + if (property === "reserve") { + return Effect.andThen( + // simulates a driver that stalls uninterruptibly while tearing + // down a reserved connection, so closing its scope cannot be + // interrupted + Effect.addFinalizer(() => + Effect.uninterruptible(Effect.suspend(function waitForRelease(): Effect.Effect { + if (!partitioned.blockRelease) return Effect.void + return Effect.andThen( + Effect.promise(() => new Promise((resolve) => setTimeout(resolve, 5))), + waitForRelease + ) + })) + ), + Effect.map(target.reserve, (connection) => { + partitioned.reservedConnections++ + return wrapConnection(connection) + }) + ) + } + if (property === "withoutTransforms") { + return () => client + } + return Reflect.get(target, property, receiver) + } + }) + return client + }) + ).pipe(Layer.provide(PgContainer.layerClient)) + +const SqliteLayer = Effect.gen(function*() { + const fs = yield* FileSystem.FileSystem + const dir = yield* fs.makeTempDirectoryScoped() + return SqliteClient.layer({ + filename: dir + "/test.db" + }) +}).pipe(Layer.unwrap, Layer.provide(NodeFileSystem.layer)) diff --git a/repos/effect/packages/platform-node/test/cluster/SqlRunnerStorage.test.ts b/repos/effect/packages/platform-node/test/cluster/SqlRunnerStorage.test.ts deleted file mode 100644 index 913899875e..0000000000 --- a/repos/effect/packages/platform-node/test/cluster/SqlRunnerStorage.test.ts +++ /dev/null @@ -1,101 +0,0 @@ -import { NodeFileSystem } from "@effect/platform-node" -import { SqliteClient } from "@effect/sql-sqlite-node" -import { describe, expect, it } from "@effect/vitest" -import { Effect, FileSystem, Layer } from "effect" -import { - Runner, - RunnerAddress, - RunnerStorage, - ShardId, - ShardingConfig, - SqlRunnerStorage -} from "effect/unstable/cluster" -import { MysqlContainer } from "../fixtures/mysql2-utils.ts" -import { PgContainer } from "../fixtures/pg-utils.ts" - -const StorageLive = SqlRunnerStorage.layer - -describe("SqlRunnerStorage", () => { - ;([ - ["pg", Layer.orDie(PgContainer.layerClient)], - ["mysql", Layer.orDie(MysqlContainer.layerClient)], - ["vitess", Layer.orDie(MysqlContainer.layerClientVitess)], - ["sqlite", Layer.orDie(SqliteLayer)] - ] as const).flatMap(([label, layer]) => - [ - [label, StorageLive.pipe(Layer.provideMerge(layer), Layer.provide(ShardingConfig.layer()))], - [ - label + " (no advisory)", - StorageLive.pipe( - Layer.provideMerge(layer), - Layer.provide(ShardingConfig.layer({ - shardLockDisableAdvisory: true - })) - ) - ] - ] as const - ).forEach(([label, layer]) => { - it.layer(layer, { - timeout: 60000 - })(label, (it) => { - it.effect("getRunners", () => - Effect.gen(function*() { - const storage = yield* RunnerStorage.RunnerStorage - - const runner = Runner.make({ - address: runnerAddress1, - groups: ["default"], - weight: 1 - }) - const machineId = yield* storage.register(runner, true) - yield* storage.register(runner, true) - expect(machineId).toEqual(1) - expect(yield* storage.getRunners).toEqual([[runner, true]]) - - yield* storage.setRunnerHealth(runnerAddress1, false) - expect(yield* storage.getRunners).toEqual([[runner, false]]) - - yield* storage.unregister(runnerAddress1) - expect(yield* storage.getRunners).toEqual([]) - }), 30_000) - - it.effect("acquireShards", () => - Effect.gen(function*() { - const storage = yield* RunnerStorage.RunnerStorage - - let acquired = yield* storage.acquire(runnerAddress1, [ - ShardId.make("default", 1), - ShardId.make("default", 2), - ShardId.make("default", 3) - ]) - expect(acquired.map((_) => _.id)).toEqual([1, 2, 3]) - acquired = yield* storage.acquire(runnerAddress1, [ - ShardId.make("default", 1), - ShardId.make("default", 2), - ShardId.make("default", 3) - ]) - expect(acquired.map((_) => _.id)).toEqual([1, 2, 3]) - - const refreshed = yield* storage.refresh(runnerAddress1, [ - ShardId.make("default", 1), - ShardId.make("default", 2), - ShardId.make("default", 3) - ]) - expect(refreshed.map((_) => _.id)).toEqual([1, 2, 3]) - - // smoke test release - yield* storage.release(runnerAddress1, ShardId.make("default", 2)) - })) - }) - }) -}) - -const runnerAddress1 = RunnerAddress.make("localhost", 1234) - -const SqliteLayer = Effect.gen(function*() { - const fs = yield* FileSystem.FileSystem - const dir = yield* fs.makeTempDirectoryScoped() - return SqliteClient.layer({ - filename: dir + "/test.db" - }) -}).pipe(Layer.unwrap, Layer.provide(NodeFileSystem.layer)) diff --git a/repos/effect/packages/platform-node/test/fixtures/mysql2-utils.ts b/repos/effect/packages/platform-node/test/fixtures/mysql2-utils.ts index 344145a5d3..ef749fcce0 100644 --- a/repos/effect/packages/platform-node/test/fixtures/mysql2-utils.ts +++ b/repos/effect/packages/platform-node/test/fixtures/mysql2-utils.ts @@ -7,6 +7,17 @@ export class ContainerError extends Data.TaggedError("ContainerError")<{ cause: unknown }> {} +const makeMysqlContainer = () => + new MySqlContainer("mysql:lts").withHealthCheck({ + test: [ + "CMD-SHELL", + "MYSQL_PWD=\"$MYSQL_ROOT_PASSWORD\" mysqladmin ping --protocol TCP --host 127.0.0.1 --user root --silent" + ], + interval: 250, + timeout: 1000, + retries: 1000 + }) + export class MysqlContainer extends Context.Service< MysqlContainer, StartedMySqlContainer @@ -14,7 +25,7 @@ export class MysqlContainer extends Context.Service< static readonly layer = Layer.effect(this)( Effect.acquireRelease( Effect.tryPromise({ - try: () => new MySqlContainer("mysql:lts").start(), + try: () => makeMysqlContainer().start(), catch: (cause) => new ContainerError({ cause }) }), (container) => Effect.promise(() => container.stop()) diff --git a/repos/effect/packages/platform-node/test/fixtures/rpc-schemas.ts b/repos/effect/packages/platform-node/test/fixtures/rpc-schemas.ts index 4becda4d68..668b0eae2d 100644 --- a/repos/effect/packages/platform-node/test/fixtures/rpc-schemas.ts +++ b/repos/effect/packages/platform-node/test/fixtures/rpc-schemas.ts @@ -20,7 +20,7 @@ class StreamUsers extends Rpc.make("StreamUsers", { class CurrentUser extends Context.Service()("CurrentUser") {} -class Unauthorized extends Schema.ErrorClass("Unauthorized")({ +class Unauthorized extends Schema.Error("Unauthorized")({ _tag: Schema.tag("Unauthorized") }) {} diff --git a/repos/effect/packages/platform-node/tsconfig.json b/repos/effect/packages/platform-node/tsconfig.json index 2b4745a058..534394767b 100644 --- a/repos/effect/packages/platform-node/tsconfig.json +++ b/repos/effect/packages/platform-node/tsconfig.json @@ -1,5 +1,5 @@ { - "$schema": "http://json.schemastore.org/tsconfig", + "$schema": "https://json.schemastore.org/tsconfig", "extends": "../../tsconfig.base.json", "include": ["src"], "references": [ diff --git a/repos/effect/packages/platform-node/vitest.config.ts b/repos/effect/packages/platform-node/vitest.config.ts deleted file mode 100644 index fb966ae875..0000000000 --- a/repos/effect/packages/platform-node/vitest.config.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { mergeConfig, type ViteUserConfig } from "vitest/config" -import shared from "../../vitest.shared.ts" - -const config: ViteUserConfig = {} - -export default mergeConfig(shared, config) diff --git a/repos/effect/packages/sql/clickhouse/CHANGELOG.md b/repos/effect/packages/sql/clickhouse/CHANGELOG.md index 75a32c3477..8ecdb75bfa 100644 --- a/repos/effect/packages/sql/clickhouse/CHANGELOG.md +++ b/repos/effect/packages/sql/clickhouse/CHANGELOG.md @@ -1,5 +1,150 @@ # @effect/sql-clickhouse +## 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 + - @effect/platform-node@4.0.0-beta.107 + +## 4.0.0-beta.106 + +### Patch Changes + +- [#7138](https://github.com/Effect-TS/effect/pull/7138) [`9f15190`](https://github.com/Effect-TS/effect/commit/9f151906ebfbce4c7981af23be5e1e00b31212fd) Thanks @fubhy! - Parameterize ClickHouse query IDs when cancelling queries and inserts. +- 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 + - @effect/platform-node@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), [`31efc5c`](https://github.com/Effect-TS/effect/commit/31efc5c4eabbe37f9ddab030ef760926e6ff3d24), [`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 + - @effect/platform-node@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), [`545d876`](https://github.com/Effect-TS/effect/commit/545d8767648cbbbf1820361662c0c1e10768db6a), [`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), [`721b9f0`](https://github.com/Effect-TS/effect/commit/721b9f0d320e50f3e2324c2cd1f43c6643af3ca0), [`01af079`](https://github.com/Effect-TS/effect/commit/01af079c189d1fc5067d3b1933b2870c4baf2693), [`32a59e8`](https://github.com/Effect-TS/effect/commit/32a59e8058b1ec9738cb083cf1cb116b393ca114)]: + - effect@4.0.0-beta.104 + - @effect/platform-node@4.0.0-beta.104 + +## 4.0.0-beta.103 + +### Patch Changes + +- [#6908](https://github.com/Effect-TS/effect/pull/6908) [`8a45ec3`](https://github.com/Effect-TS/effect/commit/8a45ec3117bab83552f77ed49f61009129e4cefb) Thanks @fubhy! - Preserve fractional JavaScript numbers in inferred ClickHouse parameters. + +- [#6907](https://github.com/Effect-TS/effect/pull/6907) [`c8fd57e`](https://github.com/Effect-TS/effect/commit/c8fd57e07ef86d741482d76c41cfce5374305af0) Thanks @fubhy! - Close the ClickHouse client when the startup connection check times out. + +- [#6906](https://github.com/Effect-TS/effect/pull/6906) [`5ed5692`](https://github.com/Effect-TS/effect/commit/5ed5692f1a4361759cb19b83ee86d4bbbb78f074) Thanks @fubhy! - Propagate ClickHouse result decoding failures as `SqlError` values. + +- [#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 + - @effect/platform-node@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 + - @effect/platform-node@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 + - @effect/platform-node@4.0.0-beta.101 + +## 4.0.0-beta.100 + +### Patch Changes + +- Updated dependencies [[`c1288dd`](https://github.com/Effect-TS/effect/commit/c1288dd1a52a2811ab7df57fc4ce236c6be4c745), [`b0f1a50`](https://github.com/Effect-TS/effect/commit/b0f1a50dacece60aa2393a15da853d82891a7a34), [`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 + - @effect/platform-node@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 + - @effect/platform-node@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 + - @effect/platform-node@4.0.0-beta.98 + +## 4.0.0-beta.97 + +### Patch Changes + +- Updated dependencies []: + - effect@4.0.0-beta.97 + - @effect/platform-node@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 + - @effect/platform-node@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 + - @effect/platform-node@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 + - @effect/platform-node@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 + - @effect/platform-node@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 + - @effect/platform-node@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 + - @effect/platform-node@4.0.0-beta.91 + ## 4.0.0-beta.90 ### Patch Changes diff --git a/repos/effect/packages/sql/clickhouse/README.md b/repos/effect/packages/sql/clickhouse/README.md index 15d5a7faa8..7166ccc93a 100644 --- a/repos/effect/packages/sql/clickhouse/README.md +++ b/repos/effect/packages/sql/clickhouse/README.md @@ -1,6 +1,6 @@ # `@effect/sql-clickhouse` -An `@effect/sql` implementation for [ClickHouse](https://clickhouse.com/). +An Effect SQL implementation for [ClickHouse](https://clickhouse.com/). ## Documentation diff --git a/repos/effect/packages/sql/clickhouse/docgen.json b/repos/effect/packages/sql/clickhouse/docgen.json deleted file mode 100644 index c04d51ddf8..0000000000 --- a/repos/effect/packages/sql/clickhouse/docgen.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "$schema": "../../node_modules/@effect/docgen/schema.json", - "srcLink": "https://github.com/Effect-TS/effect/tree/main/packages/sql/clickhouse/src/", - "exclude": ["src/internal/**/*.ts"], - "tscExecutable": "tsgo", - "examplesCompilerOptions": { - "noEmit": true, - "strict": true, - "skipLibCheck": true, - "moduleResolution": "Bundler", - "module": "ES2022", - "target": "ES2022", - "lib": ["ES2022", "DOM", "DOM.Iterable"], - "rewriteRelativeImportExtensions": true, - "allowImportingTsExtensions": true, - "paths": { - "effect": ["../../../effect/src/index.js"], - "effect/*": ["../../../effect/src/*.js"] - }, - "plugins": [ - { "name": "@effect/language-service", "includeSuggestionsInTsc": false } - ] - } -} diff --git a/repos/effect/packages/sql/clickhouse/package.json b/repos/effect/packages/sql/clickhouse/package.json index a6c2a815e3..4dfc2137d7 100644 --- a/repos/effect/packages/sql/clickhouse/package.json +++ b/repos/effect/packages/sql/clickhouse/package.json @@ -1,17 +1,17 @@ { "name": "@effect/sql-clickhouse", - "version": "4.0.0-beta.90", + "version": "4.0.0-beta.107", "type": "module", "license": "MIT", "description": "A Clickhouse toolkit for Effect", "homepage": "https://effect.website", "repository": { "type": "git", - "url": "https://github.com/Effect-TS/effect-smol.git", + "url": "https://github.com/Effect-TS/effect.git", "directory": "packages/sql/clickhouse" }, "bugs": { - "url": "https://github.com/Effect-TS/effect-smol/issues" + "url": "https://github.com/Effect-TS/effect/issues" }, "tags": [ "typescript", @@ -29,6 +29,7 @@ ".": "./src/index.ts", "./*": "./src/*.ts", "./internal/*": null, + "./index": null, "./*/index": null }, "files": [ @@ -36,7 +37,10 @@ "dist/**/*.js", "dist/**/*.js.map", "dist/**/*.d.ts", - "dist/**/*.d.ts.map" + "dist/**/*.d.ts.map", + "AGENTS.md", + "CLAUDE.md", + "ai-docs/**/*" ], "publishConfig": { "access": "public", @@ -46,17 +50,15 @@ ".": "./dist/index.js", "./*": "./dist/*.js", "./internal/*": null, + "./index": null, "./*/index": null } }, "scripts": { "codegen": "effect-utils codegen", "build": "tsc -b tsconfig.json && pnpm babel", - "build:tsgo": "tsgo -b tsconfig.json && pnpm babel", "babel": "babel dist --plugins annotate-pure-calls --out-dir dist --source-maps", - "check": "tsc -b tsconfig.json", - "test": "vitest", - "coverage": "vitest --coverage" + "check": "tsc -b tsconfig.json" }, "devDependencies": { "@effect/platform-node": "workspace:^", @@ -67,6 +69,6 @@ "effect": "workspace:^" }, "dependencies": { - "@clickhouse/client": "^1.18.4" + "@clickhouse/client": "^1.23.1" } } diff --git a/repos/effect/packages/sql/clickhouse/src/ClickhouseClient.ts b/repos/effect/packages/sql/clickhouse/src/ClickhouseClient.ts index fb5883eb12..a84ee2763c 100644 --- a/repos/effect/packages/sql/clickhouse/src/ClickhouseClient.ts +++ b/repos/effect/packages/sql/clickhouse/src/ClickhouseClient.ts @@ -104,7 +104,7 @@ export type TypeId = "~@effect/sql-clickhouse/ClickhouseClient" * typed parameter fragments, command-mode execution, insert queries, and * per-effect query ID and ClickHouse settings. * - * @category models + * @category services * @since 4.0.0 */ export interface ClickhouseClient extends Client.SqlClient { @@ -149,7 +149,7 @@ export const ClickhouseClient = Context.Service("@effect/sql-c * `@clickhouse/client` options with optional span attributes and query/result * name transforms. * - * @category constructors + * @category models * @since 4.0.0 */ export interface ClickhouseClientConfig extends Clickhouse.ClickHouseClientConfigOptions { @@ -175,16 +175,16 @@ export const make = ( ? Statement.defaultTransforms(options.transformResultNames).array : undefined - const client = Clickhouse.createClient(options) + const client = yield* Effect.acquireRelease( + Effect.sync(() => Clickhouse.createClient(options)), + (client) => Effect.promise(() => client.close()) + ) - yield* Effect.acquireRelease( - Effect.tryPromise({ - try: () => client.exec({ query: "SELECT 1" }), - catch: (cause) => - new SqlError({ reason: classifyError(cause, "ClickhouseClient: Failed to connect", "connect", "connection") }) - }), - () => Effect.promise(() => client.close()) - ).pipe( + yield* Effect.tryPromise({ + try: () => client.exec({ query: "SELECT 1" }), + catch: (cause) => + new SqlError({ reason: classifyError(cause, "ClickhouseClient: Failed to connect", "connect", "connection") }) + }).pipe( Effect.timeoutOrElse({ duration: Duration.seconds(5), orElse: () => @@ -253,7 +253,12 @@ export const make = ( } return Effect.suspend(() => { controller.abort() - return Effect.promise(() => this.conn.command({ query: `KILL QUERY WHERE query_id = '${queryId}'` })) + return Effect.promise(() => + this.conn.command({ + query: "KILL QUERY WHERE query_id = {queryId:String}", + query_params: { queryId } + }) + ) }) }) }) @@ -263,12 +268,11 @@ export const make = ( return this.runRaw(sql, params, format).pipe( Effect.flatMap((result) => { if ("json" in result) { - return Effect.promise(() => - result.json().then( - (result) => "data" in result ? result.data : result as any, - () => [] - ) - ) + return Effect.tryPromise({ + try: () => result.json().then((result) => "data" in result ? result.data : result as any), + catch: (cause) => + new SqlError({ reason: classifyError(cause, "Failed to parse result", "parseResult") }) + }) } return Effect.succeed([]) }) @@ -376,7 +380,12 @@ export const make = ( ) return Effect.suspend(() => { controller.abort() - return Effect.promise(() => client.command({ query: `KILL QUERY WHERE query_id = '${queryId}'` })) + return Effect.promise(() => + client.command({ + query: "KILL QUERY WHERE query_id = {queryId:String}", + query_params: { queryId } + }) + ) }) }) }, @@ -399,7 +408,7 @@ export const make = ( * Fiber reference read by the low-level ClickHouse connection to choose query * or command execution for statements; defaults to `query`. * - * @category references + * @category services * @since 4.0.0 */ export const ClientMethod = Context.Reference<"query" | "command" | "insert">( @@ -413,7 +422,7 @@ export const ClientMethod = Context.Reference<"query" | "command" | "insert">( * Fiber reference for the ClickHouse `query_id` applied to queries and * inserts; a random UUID is generated when no query ID is set. * - * @category references + * @category services * @since 4.0.0 */ export const QueryId = Context.Reference( @@ -425,7 +434,7 @@ export const QueryId = Context.Reference( * Fiber reference containing ClickHouse settings to attach to queries, * commands, and inserts. * - * @category references + * @category services * @since 4.0.0 */ export const ClickhouseSettings: Context.Reference< @@ -484,7 +493,7 @@ const typeFromUnknown = (value: unknown): string => { } switch (typeof value) { case "number": - return "Decimal" + return "Float64" case "bigint": return "Int64" case "boolean": @@ -504,7 +513,7 @@ const typeFromUnknown = (value: unknown): string => { * `{pN: Type}` placeholders and escaping identifiers with an optional query * name transform. * - * @category compiler + * @category constructors * @since 4.0.0 */ export const makeCompiler = (transform?: (_: string) => string) => @@ -534,13 +543,13 @@ const escape = Statement.defaultEscape("\"") * Custom SQL fragment type used for ClickHouse typed parameters created by * `ClickhouseClient.param`. * - * @category custom types + * @category models * @since 4.0.0 */ export type ClickhouseCustom = ClickhouseParam /** - * @category custom types + * @category models * @since 4.0.0 */ interface ClickhouseParam extends Statement.Custom<"ClickhouseParam", string, unknown> {} diff --git a/repos/effect/packages/sql/clickhouse/src/ClickhouseMigrator.ts b/repos/effect/packages/sql/clickhouse/src/ClickhouseMigrator.ts index 687ae50072..c57a484fe8 100644 --- a/repos/effect/packages/sql/clickhouse/src/ClickhouseMigrator.ts +++ b/repos/effect/packages/sql/clickhouse/src/ClickhouseMigrator.ts @@ -24,7 +24,7 @@ export * from "effect/unstable/sql/Migrator" * Runs SQL migrations for ClickHouse using the supplied migrator options and * returns the applied migration IDs and names. * - * @category constructors + * @category running * @since 4.0.0 */ export const run: ( diff --git a/repos/effect/packages/sql/clickhouse/test/Client.test.ts b/repos/effect/packages/sql/clickhouse/test/Client.test.ts index 097065a237..c9c6f28c4e 100644 --- a/repos/effect/packages/sql/clickhouse/test/Client.test.ts +++ b/repos/effect/packages/sql/clickhouse/test/Client.test.ts @@ -1,6 +1,94 @@ -import { describe, it } from "@effect/vitest" +import { ClickhouseClient } from "@effect/sql-clickhouse" +import { assert, describe, it } from "@effect/vitest" +import { Effect, Fiber } from "effect" +import { TestClock } from "effect/testing" +import * as Reactivity from "effect/unstable/reactivity/Reactivity" +import * as Statement from "effect/unstable/sql/Statement" +import { vi } from "vitest" + +let closeCalls = 0 +let connectImmediately = false +const commandCalls: Array> = [] + +vi.mock("@clickhouse/client", () => ({ + createClient: () => ({ + exec: () => connectImmediately ? Promise.resolve({}) : new Promise(() => {}), + query: () => new Promise(() => {}), + insert: () => new Promise(() => {}), + command: (options: Record) => { + commandCalls.push(options) + return Promise.resolve({}) + }, + close: () => { + closeCalls++ + return Promise.resolve() + } + }) +})) describe("ClickhouseClient", () => { - it("should work", () => { + it("preserves fractional JavaScript numbers in inferred parameters", () => { + const sql = Statement.make(Effect.void as any, ClickhouseClient.makeCompiler(), [], undefined) + const [query] = sql`SELECT ${1.5}`.compile() + + assert.strictEqual(query, "SELECT {p1: Float64}") }) + + it.effect("closes the client when the connection check times out", () => + Effect.gen(function*() { + connectImmediately = false + closeCalls = 0 + const fiber = yield* Effect.forkDetach( + ClickhouseClient.make({ url: "http://localhost:8123" }).pipe(Effect.scoped) + ) + yield* Effect.yieldNow + yield* TestClock.adjust("5 seconds") + const result = fiber.pollUnsafe() + + assert.isDefined(result) + assert.strictEqual(closeCalls, 1) + }).pipe(Effect.provide(Reactivity.layer))) + + it.effect("parameterizes the query id when cancelling a query", () => + Effect.gen(function*() { + connectImmediately = true + commandCalls.length = 0 + const queryId = "id' OR 1 = 1 --" + const client = yield* ClickhouseClient.make({ url: "http://localhost:8123" }) + const fiber = yield* client.withQueryId(client.unsafe("SELECT 1"), queryId).pipe(Effect.forkScoped) + yield* Effect.yieldNow + + yield* Fiber.interrupt(fiber) + + assert.deepStrictEqual(commandCalls, [{ + query: "KILL QUERY WHERE query_id = {queryId:String}", + query_params: { queryId } + }]) + }).pipe( + Effect.scoped, + Effect.provide(Reactivity.layer) + )) + + it.effect("parameterizes the query id when cancelling an insert", () => + Effect.gen(function*() { + connectImmediately = true + commandCalls.length = 0 + const queryId = "id' OR 1 = 1 --" + const client = yield* ClickhouseClient.make({ url: "http://localhost:8123" }) + const fiber = yield* client.withQueryId( + client.insertQuery({ table: "test", values: [] }), + queryId + ).pipe(Effect.forkScoped) + yield* Effect.yieldNow + + yield* Fiber.interrupt(fiber) + + assert.deepStrictEqual(commandCalls, [{ + query: "KILL QUERY WHERE query_id = {queryId:String}", + query_params: { queryId } + }]) + }).pipe( + Effect.scoped, + Effect.provide(Reactivity.layer) + )) }) diff --git a/repos/effect/packages/sql/clickhouse/test/SqlErrorClassification.test.ts b/repos/effect/packages/sql/clickhouse/test/SqlErrorClassification.test.ts index f2e3f0484a..d0461345c4 100644 --- a/repos/effect/packages/sql/clickhouse/test/SqlErrorClassification.test.ts +++ b/repos/effect/packages/sql/clickhouse/test/SqlErrorClassification.test.ts @@ -1,14 +1,17 @@ import { assert, describe, it } from "@effect/vitest" import { Effect } from "effect" import * as Reactivity from "effect/unstable/reactivity/Reactivity" +import { SqlError } from "effect/unstable/sql/SqlError" import { vi } from "vitest" const state: { connectCause: unknown queryCause: unknown + resultJsonCause: unknown } = { connectCause: null, - queryCause: null + queryCause: null, + resultJsonCause: null } vi.mock("@clickhouse/client", () => ({ @@ -19,7 +22,7 @@ vi.mock("@clickhouse/client", () => ({ state.queryCause ? Promise.reject(state.queryCause) : Promise.resolve({ - json: () => Promise.resolve({ data: [] }) + json: () => state.resultJsonCause ? Promise.reject(state.resultJsonCause) : Promise.resolve({ data: [] }) }), command: () => Promise.resolve({}), insert: () => Promise.resolve({}) @@ -30,6 +33,7 @@ const connectFailureReasonTag = (code: number) => Effect.gen(function*() { state.connectCause = { code } state.queryCause = null + state.resultJsonCause = null const { ClickhouseClient } = yield* Effect.promise(() => import("@effect/sql-clickhouse")) const error = yield* Effect.flip(ClickhouseClient.make({ url: "http://localhost:8123" })) return error.reason._tag @@ -42,6 +46,7 @@ const queryFailureReasonTag = (code: number) => Effect.gen(function*() { state.connectCause = null state.queryCause = { code } + state.resultJsonCause = null const { ClickhouseClient } = yield* Effect.promise(() => import("@effect/sql-clickhouse")) const client = yield* ClickhouseClient.make({ url: "http://localhost:8123" }) const error = yield* Effect.flip(client`SELECT 1`) @@ -72,4 +77,21 @@ describe("ClickhouseClient SqlError classification", () => { const tag = yield* queryFailureReasonTag(999) assert.strictEqual(tag, "UnknownError") })) + + it.effect("maps result decoding failures to SqlError", () => + Effect.gen(function*() { + state.connectCause = null + state.queryCause = null + state.resultJsonCause = new SyntaxError("invalid JSON response") + const { ClickhouseClient } = yield* Effect.promise(() => import("@effect/sql-clickhouse")) + const client = yield* ClickhouseClient.make({ url: "http://localhost:8123" }) + const error = yield* Effect.flip(client`SELECT 1`) + + assert(error instanceof SqlError) + assert.strictEqual(error.reason._tag, "UnknownError") + assert.strictEqual(error.reason.cause, state.resultJsonCause) + }).pipe( + Effect.scoped, + Effect.provide(Reactivity.layer) + )) }) diff --git a/repos/effect/packages/sql/clickhouse/tsconfig.json b/repos/effect/packages/sql/clickhouse/tsconfig.json index 0040406ea7..d77a0e3c1f 100644 --- a/repos/effect/packages/sql/clickhouse/tsconfig.json +++ b/repos/effect/packages/sql/clickhouse/tsconfig.json @@ -1,5 +1,5 @@ { - "$schema": "http://json.schemastore.org/tsconfig", + "$schema": "https://json.schemastore.org/tsconfig", "extends": "../../../tsconfig.base.json", "include": ["src"], "references": [ diff --git a/repos/effect/packages/sql/clickhouse/vitest.config.ts b/repos/effect/packages/sql/clickhouse/vitest.config.ts deleted file mode 100644 index c8a52c1826..0000000000 --- a/repos/effect/packages/sql/clickhouse/vitest.config.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { mergeConfig, type ViteUserConfig } from "vitest/config" -import shared from "../../../vitest.shared.ts" - -const config: ViteUserConfig = {} - -export default mergeConfig(shared, config) diff --git a/repos/effect/packages/sql/d1/CHANGELOG.md b/repos/effect/packages/sql/d1/CHANGELOG.md index 271417dce1..3a0532182e 100644 --- a/repos/effect/packages/sql/d1/CHANGELOG.md +++ b/repos/effect/packages/sql/d1/CHANGELOG.md @@ -1,5 +1,130 @@ # @effect/sql-d1 +## 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 + +- 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 + +### Minor Changes + +- [#6524](https://github.com/Effect-TS/effect/pull/6524) [`c5bf174`](https://github.com/Effect-TS/effect/commit/c5bf174cb3f2c02278d0daf6294ee1d0747a4ed8) Thanks @nr1brolyfan! - Add `D1Client.batch` for executing a collection of SQL statements as a single atomic D1 batch. + +### Patch Changes + +- [#6701](https://github.com/Effect-TS/effect/pull/6701) [`9867b9f`](https://github.com/Effect-TS/effect/commit/9867b9fc69f9cc6c443594fc7eccc7be0c674d9c) Thanks @fubhy! - Removed explicit ./index entrypoints + +- Updated dependencies [[`e56cd8f`](https://github.com/Effect-TS/effect/commit/e56cd8f90c3559baccf8fcf2852ea911235d5944), [`f77c120`](https://github.com/Effect-TS/effect/commit/f77c120d8e04779ddeb8bce8e9cde932f268e4b6), [`b2f95a9`](https://github.com/Effect-TS/effect/commit/b2f95a9c2f2581deb89dc3bae9e89cf819e82923), [`04fd44a`](https://github.com/Effect-TS/effect/commit/04fd44a42abfa8dc2642300dcf49ee48c8ef4539), [`b74333d`](https://github.com/Effect-TS/effect/commit/b74333d83e15b9d042e4698ad23040de60454afe), [`1c40b28`](https://github.com/Effect-TS/effect/commit/1c40b2809503d6aa1358777196fc66317906e657), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`b3901d2`](https://github.com/Effect-TS/effect/commit/b3901d29c543fd5bd05ceec669a17896c8e19006), [`4a0984a`](https://github.com/Effect-TS/effect/commit/4a0984af62738fedf4bd3e87adb4d4d641ce9147), [`fffd88b`](https://github.com/Effect-TS/effect/commit/fffd88b3135abdf928ca7c4b0e00e610985091c7), [`f3f6c1e`](https://github.com/Effect-TS/effect/commit/f3f6c1e02cb543423fcffef5dc2db03fac503588), [`ef07642`](https://github.com/Effect-TS/effect/commit/ef07642dfe671d5258b65d1c1480c4d05c495f15), [`f1bc827`](https://github.com/Effect-TS/effect/commit/f1bc8274a608813d7b09d28dcca04adbf62f8c92), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`081f4d8`](https://github.com/Effect-TS/effect/commit/081f4d8cd06a2ac222d2810b46e61efcee26939e), [`5287b24`](https://github.com/Effect-TS/effect/commit/5287b24f5f8fa094ba20e117bfb1a80fba6d2cf5), [`13d31cf`](https://github.com/Effect-TS/effect/commit/13d31cfc2dde46210e94391b5b6767ae9aeaf2c9), [`acee269`](https://github.com/Effect-TS/effect/commit/acee26944bc89ee554d7b9fadab7443f9edc28a9), [`31170c1`](https://github.com/Effect-TS/effect/commit/31170c19b236c37abb5476c821bc6f5bfa2735ab), [`205ebc7`](https://github.com/Effect-TS/effect/commit/205ebc776062012581e98fced7ced19adfc44ee7), [`ed0ebf8`](https://github.com/Effect-TS/effect/commit/ed0ebf8e5c864d46fed1f232e99c0e680f10a58f), [`a3fd084`](https://github.com/Effect-TS/effect/commit/a3fd08482157bd78b089f77c7b173d54ef68b5cd), [`ee29ddf`](https://github.com/Effect-TS/effect/commit/ee29ddf862c3723ad466abc93ab6f6fe723b2319), [`6086309`](https://github.com/Effect-TS/effect/commit/60863090af8e5af0bfa1435f08dc5390f9993e30), [`4a57af2`](https://github.com/Effect-TS/effect/commit/4a57af24011db1d66e947289d2f7ffc2074696d2), [`660875b`](https://github.com/Effect-TS/effect/commit/660875b4325e6eebb3f04513998301cd2a0847ec), [`8e7c706`](https://github.com/Effect-TS/effect/commit/8e7c706b0aca855489b53d987404566d3e9cb5e7), [`5f63adb`](https://github.com/Effect-TS/effect/commit/5f63adbe75fc9d50d23706a52b3e483ad2a1a01c), [`053bc42`](https://github.com/Effect-TS/effect/commit/053bc42e2a964755611a216e78ed214322efee37), [`c0a1534`](https://github.com/Effect-TS/effect/commit/c0a153494484ecf9f0d0f20895a7a648b4be363b), [`f1e3a37`](https://github.com/Effect-TS/effect/commit/f1e3a378c144f974a6122b299f421b75595af20f), [`cedb01a`](https://github.com/Effect-TS/effect/commit/cedb01a025492a1faf9e59eb23eb96bc3b5e2fff), [`1747440`](https://github.com/Effect-TS/effect/commit/1747440de9a51a56ed3660da748cc01b256adce7), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`b4f1ee2`](https://github.com/Effect-TS/effect/commit/b4f1ee238d96aa78c5f040158cb78671d75b381e), [`a4757f1`](https://github.com/Effect-TS/effect/commit/a4757f1c47067d8d016a6c4a2c541bb8ae520f9b), [`cd122b9`](https://github.com/Effect-TS/effect/commit/cd122b90300d995a237993a2edb7a049785ab6a4), [`5de588b`](https://github.com/Effect-TS/effect/commit/5de588b2472fb0f4eb919766eb8472583a044772), [`3895b9c`](https://github.com/Effect-TS/effect/commit/3895b9cf179262cd277a9c6daafe9050dcf8265e), [`89ce5f3`](https://github.com/Effect-TS/effect/commit/89ce5f3e16e23a193daa475dc72ea8133ae1dacd), [`985de09`](https://github.com/Effect-TS/effect/commit/985de097d75906db2aed784841f81e23cc978b43), [`9800e3a`](https://github.com/Effect-TS/effect/commit/9800e3acc8f36530f671bc8b91558cb112f449a7), [`4dc35f6`](https://github.com/Effect-TS/effect/commit/4dc35f64641746366f867ea3dbfedb9cd4685ada), [`e8eb62b`](https://github.com/Effect-TS/effect/commit/e8eb62b3d0ef27e9761cdc2eb93bdec52d6ee204), [`ecd9993`](https://github.com/Effect-TS/effect/commit/ecd99936112cb69efdb02de3a2fd57f47baefdf3), [`5ab9c08`](https://github.com/Effect-TS/effect/commit/5ab9c08463ce049c45f3502676954a7b72c6b024), [`f5cf965`](https://github.com/Effect-TS/effect/commit/f5cf96548afd51f4b3cf1aea11b04d7f8549ce90), [`a94cbed`](https://github.com/Effect-TS/effect/commit/a94cbed84e9e49bea4bff925599c0f19c4e3deab), [`9160ad7`](https://github.com/Effect-TS/effect/commit/9160ad7d146d4376dd12f7510c025e5b2f638a70), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`52494be`](https://github.com/Effect-TS/effect/commit/52494be9e8eb3bb542d06a3dfefc6bca4e168984), [`5441c8e`](https://github.com/Effect-TS/effect/commit/5441c8e656a6418c0d27feb2df67565a3e1155f4), [`c9b56ab`](https://github.com/Effect-TS/effect/commit/c9b56ab507f224426ee8388dc450da447ec4715f), [`8ef7257`](https://github.com/Effect-TS/effect/commit/8ef72577d1f43212cab87951d659e54e3c8d7d91), [`1519406`](https://github.com/Effect-TS/effect/commit/1519406fed6e8b017ae178dc20bcaa2cf318b570), [`9716990`](https://github.com/Effect-TS/effect/commit/97169902eec3c99baa7f0b2c7b45a0a5eae75819), [`733f75b`](https://github.com/Effect-TS/effect/commit/733f75b7125e3016a975fdd251c0179ae5393786), [`48155c8`](https://github.com/Effect-TS/effect/commit/48155c8ccfc12dcca8a00fa358d50b20c30874e4), [`951d06b`](https://github.com/Effect-TS/effect/commit/951d06b83d459d3e8fa9024e727a5db1662d3322), [`d767b65`](https://github.com/Effect-TS/effect/commit/d767b65a7687e38be23f0b0ee3d52ab5f2360cbe), [`5d52d9d`](https://github.com/Effect-TS/effect/commit/5d52d9d148aaa7f736ed8c310fc8bfa9dc81badf), [`f4151e1`](https://github.com/Effect-TS/effect/commit/f4151e1937c26de14f1d64566f8126173f1b5014), [`e02fbb6`](https://github.com/Effect-TS/effect/commit/e02fbb66f5a0f13dba6c33ef63528a37a17a0676), [`724ce09`](https://github.com/Effect-TS/effect/commit/724ce09650a458d4565e5c7331ea92ca04f08e68), [`dbe91f6`](https://github.com/Effect-TS/effect/commit/dbe91f6961ef9f7e8da910ee5758d9c0d385fca8), [`4c008d2`](https://github.com/Effect-TS/effect/commit/4c008d28b370d817f7ae4579db09836fe084c8d2), [`b650832`](https://github.com/Effect-TS/effect/commit/b6508328708a842f3163467b72486bd228f1a289), [`b46c92f`](https://github.com/Effect-TS/effect/commit/b46c92f3b314f4ffd612b831efa55dd856c587a3), [`5335797`](https://github.com/Effect-TS/effect/commit/5335797003076d9c6fd170da98d779696d555596), [`4b3460d`](https://github.com/Effect-TS/effect/commit/4b3460daa434ec465a95a50704fe1103a9275999), [`6301fd7`](https://github.com/Effect-TS/effect/commit/6301fd710b4325718de2c42997dac28a9e9aa250), [`aebc5c6`](https://github.com/Effect-TS/effect/commit/aebc5c61664b89a840465ec65b79ce635a5ceee8), [`52b2d7b`](https://github.com/Effect-TS/effect/commit/52b2d7b5bd3c7cce3bd5b69c6ab3941004da70f3), [`eec5744`](https://github.com/Effect-TS/effect/commit/eec57445dfa0ef3c5977195ad69415b7e7d42bb6), [`24e0e93`](https://github.com/Effect-TS/effect/commit/24e0e93dc307dc2c2ae86caacb7289e1dab3c103), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`1a7ce81`](https://github.com/Effect-TS/effect/commit/1a7ce8150e3977586c44d8ccb9a8384389bb4d49), [`48f22a7`](https://github.com/Effect-TS/effect/commit/48f22a7d16ae57ee2175d450dafbdeb69e187d2a), [`c96b7f6`](https://github.com/Effect-TS/effect/commit/c96b7f6359662053c3e09344f61dddc7a6caf4ac), [`6d2a942`](https://github.com/Effect-TS/effect/commit/6d2a942ed7cd33b8fd79d549edba33bc9e2a7e3e), [`cc27b19`](https://github.com/Effect-TS/effect/commit/cc27b194b9d13fa3a66ab037e853fca9d41700ff), [`8f9499f`](https://github.com/Effect-TS/effect/commit/8f9499f562729f5f7b08d8bcc4db86b4aeff8a21), [`3eeea73`](https://github.com/Effect-TS/effect/commit/3eeea73cfc3e9b126975c2ddbdb7f7c8c92026e2), [`0a532e5`](https://github.com/Effect-TS/effect/commit/0a532e503f165fdea485a5343fc2f420917e8376), [`f398149`](https://github.com/Effect-TS/effect/commit/f398149c134fd9b67b6cdc52eae3f3248d5c7bbe), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`ace903e`](https://github.com/Effect-TS/effect/commit/ace903e09c2549ceebdec380797beb027cd29f3d), [`e8eb62b`](https://github.com/Effect-TS/effect/commit/e8eb62b3d0ef27e9761cdc2eb93bdec52d6ee204), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`48f22a7`](https://github.com/Effect-TS/effect/commit/48f22a7d16ae57ee2175d450dafbdeb69e187d2a), [`d48506d`](https://github.com/Effect-TS/effect/commit/d48506d97525040aa714305e928126df799795b4), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`d48506d`](https://github.com/Effect-TS/effect/commit/d48506d97525040aa714305e928126df799795b4), [`d48506d`](https://github.com/Effect-TS/effect/commit/d48506d97525040aa714305e928126df799795b4), [`52262be`](https://github.com/Effect-TS/effect/commit/52262be2edce0e350c6ac10f8f725678606399c5), [`1284aa1`](https://github.com/Effect-TS/effect/commit/1284aa183451955ad7921bbe01fd0e095695d444), [`9867b9f`](https://github.com/Effect-TS/effect/commit/9867b9fc69f9cc6c443594fc7eccc7be0c674d9c), [`d0f1a22`](https://github.com/Effect-TS/effect/commit/d0f1a2295155c350b04efb46852cb40032805273), [`979ce39`](https://github.com/Effect-TS/effect/commit/979ce3985d7d62ce2bf240681ca19feda3027452), [`b6d3e67`](https://github.com/Effect-TS/effect/commit/b6d3e67c7cc143cd8470cdf704324e79d23954a9), [`adf6c6c`](https://github.com/Effect-TS/effect/commit/adf6c6cd388af8a3c0c546492e71555368556f6a), [`7314d60`](https://github.com/Effect-TS/effect/commit/7314d605284717aaafe7fc34b88c3c93397e865c), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`1acbd8b`](https://github.com/Effect-TS/effect/commit/1acbd8b44c68ebb23735e9810476b870dbe58aea), [`7bde6cc`](https://github.com/Effect-TS/effect/commit/7bde6ccb2b144fe953ff30a7ef5e1ecc97697146), [`a959a8b`](https://github.com/Effect-TS/effect/commit/a959a8bf21cdb976369f494dc949fa00a050d3e0)]: + - effect@4.0.0-beta.103 + +## 4.0.0-beta.102 + +### Patch Changes + +- Updated dependencies [[`b6392e1`](https://github.com/Effect-TS/effect/commit/b6392e119704553edec1b4fd2869ac0dbec621ef), [`7ed9450`](https://github.com/Effect-TS/effect/commit/7ed945044eb56aa9aeaf62d4746a011c96c58628), [`45762bd`](https://github.com/Effect-TS/effect/commit/45762bd78df9ecd87c98b8d3738cdeeac7d81128), [`a6e8391`](https://github.com/Effect-TS/effect/commit/a6e8391cd31acd898fae18b3f8e7ca4c6f14f065), [`4ac7e8b`](https://github.com/Effect-TS/effect/commit/4ac7e8b136c61a26c3e438c013dfd7349b38e999), [`4cd40f5`](https://github.com/Effect-TS/effect/commit/4cd40f5692477783bef84fed3c5ef1c0cf5602e6), [`6956bc0`](https://github.com/Effect-TS/effect/commit/6956bc0e6cb27f53fbec39d9b18545940f9f598f), [`0e50ec7`](https://github.com/Effect-TS/effect/commit/0e50ec7dbb94390666f292cf9120719bf30a7246), [`9fcdade`](https://github.com/Effect-TS/effect/commit/9fcdade4a8af772b9ccd8b8a24fe8cee0e5d8470), [`57367d5`](https://github.com/Effect-TS/effect/commit/57367d54de55047ff0c5fce9685475e236bf354c), [`35c445f`](https://github.com/Effect-TS/effect/commit/35c445ff18029d192900ea0914c993f58d5cf1a5), [`c917bb9`](https://github.com/Effect-TS/effect/commit/c917bb94a4c1c4e0a24372a8ebb8a5ca232e36b5), [`bc1f358`](https://github.com/Effect-TS/effect/commit/bc1f3583e63344cb2c398d9040d9c975488ed123), [`0e0c9d7`](https://github.com/Effect-TS/effect/commit/0e0c9d7922ff463c1093d9e0576fae12cb0698d5), [`73d40aa`](https://github.com/Effect-TS/effect/commit/73d40aacd8fcae1b48c23f5b0a5c542127401d1d), [`4f1e318`](https://github.com/Effect-TS/effect/commit/4f1e3183f7123591c46224e9c587df7594562a5f), [`9d8d85c`](https://github.com/Effect-TS/effect/commit/9d8d85c1bb7da51970845b8ea830e386e777514a), [`6079fda`](https://github.com/Effect-TS/effect/commit/6079fda7b02f2f01ad91c15ab8c307336f3ba252), [`5101e92`](https://github.com/Effect-TS/effect/commit/5101e92c9c149c153423f43dd7a94f6194653c06), [`d0b3265`](https://github.com/Effect-TS/effect/commit/d0b3265c3262670761471ab3518cf933b1b3b20a), [`7a03c89`](https://github.com/Effect-TS/effect/commit/7a03c893ce6492bf94c0ebfb00b63bf25dcbf83e), [`cea1d9c`](https://github.com/Effect-TS/effect/commit/cea1d9c92601e69ebda040af8a1d860d604d885c), [`078e1f5`](https://github.com/Effect-TS/effect/commit/078e1f5636e31b76a86722a636afc37a8cc25580), [`97bafea`](https://github.com/Effect-TS/effect/commit/97bafeab460833b9781527b437d1cb9cbee63260), [`fab0ab8`](https://github.com/Effect-TS/effect/commit/fab0ab8f7ab15ae596faa4ccf75615a494d11b0b), [`c323d8b`](https://github.com/Effect-TS/effect/commit/c323d8b30dbbe85f9df25b67288b93d5332de333), [`6966353`](https://github.com/Effect-TS/effect/commit/69663534d626003eb10a5e55ab1f13e0379fead1), [`0444004`](https://github.com/Effect-TS/effect/commit/04440041989c1785fe4db286379f2be2c15baa85), [`028bbb3`](https://github.com/Effect-TS/effect/commit/028bbb391e161185da10d974ab33381f769940d7), [`ff5d6e2`](https://github.com/Effect-TS/effect/commit/ff5d6e278a1fdff714315dc1a17075012f05c1f0), [`1bfce93`](https://github.com/Effect-TS/effect/commit/1bfce93e6d2bf0794c11733daf51c2390e7de375), [`7ce815c`](https://github.com/Effect-TS/effect/commit/7ce815cd5af6af991dfc13b890fd22345fc77c20), [`7271a7f`](https://github.com/Effect-TS/effect/commit/7271a7faf1080aa75f2f53ca6a0b5ec9334c1d38), [`475fe5c`](https://github.com/Effect-TS/effect/commit/475fe5c12c2d6504c475797c0634f90da01e1797)]: + - effect@4.0.0-beta.102 + +## 4.0.0-beta.101 + +### Patch Changes + +- Updated dependencies [[`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`b35ed29`](https://github.com/Effect-TS/effect/commit/b35ed2904f01536d303b21f288daf343cf740462), [`dd44624`](https://github.com/Effect-TS/effect/commit/dd446245736a0e88c807a02f03c21450bb9340fa), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`2bae1ac`](https://github.com/Effect-TS/effect/commit/2bae1accce9d3b72cf6d5aefc9b2161af6d88436)]: + - effect@4.0.0-beta.101 + +## 4.0.0-beta.100 + +### Patch Changes + +- Updated dependencies [[`c1288dd`](https://github.com/Effect-TS/effect/commit/c1288dd1a52a2811ab7df57fc4ce236c6be4c745), [`2b58a3d`](https://github.com/Effect-TS/effect/commit/2b58a3dab6bc99776dddaf76e27d811e0f47f3d8), [`6dc83f2`](https://github.com/Effect-TS/effect/commit/6dc83f26ddf20d48db28cf761dd8f3716e5273fb), [`c1e2fe0`](https://github.com/Effect-TS/effect/commit/c1e2fe0cf93564f4d919e3998874c3e70b0cf30f), [`f3fbae8`](https://github.com/Effect-TS/effect/commit/f3fbae8d7bae0d77cb4f35a1598b26c58e3bf94d), [`e000f80`](https://github.com/Effect-TS/effect/commit/e000f80fd55bcd8edc699fdbf4cd109004f4f754), [`f4ee765`](https://github.com/Effect-TS/effect/commit/f4ee7655ee052cf9ba726fd602bb87c89c7c62a9), [`510b55f`](https://github.com/Effect-TS/effect/commit/510b55f3e21750685dbfd5f476a130c1c5af9dbd), [`31d3fc4`](https://github.com/Effect-TS/effect/commit/31d3fc4327c50867bb8d881fa7353aeb03ea2826), [`875e618`](https://github.com/Effect-TS/effect/commit/875e618c3764a7b817ac863d0af86924449528f2), [`688d46a`](https://github.com/Effect-TS/effect/commit/688d46afd0ef923d983ad3d7385f52f217b28d70), [`6ff5023`](https://github.com/Effect-TS/effect/commit/6ff502363b9840a5a5ee0a24bc6cae734ac3a3eb), [`c0333e7`](https://github.com/Effect-TS/effect/commit/c0333e7f755f42ddcca7051e029da8b4eed527bf), [`06e7e8c`](https://github.com/Effect-TS/effect/commit/06e7e8c66015ee318f871b9d2218dee82df2b108), [`eb9b102`](https://github.com/Effect-TS/effect/commit/eb9b10256c8558881b441c2fef833b7037174400), [`8b155da`](https://github.com/Effect-TS/effect/commit/8b155da06e0740c354ec562957a45ab65eb4573b), [`3a87335`](https://github.com/Effect-TS/effect/commit/3a8733564c5db35271aa20564ed0d344daa2a79f)]: + - effect@4.0.0-beta.100 + +## 4.0.0-beta.99 + +### Patch Changes + +- Updated dependencies [[`8ce4795`](https://github.com/Effect-TS/effect/commit/8ce4795ccbaebca4292757db568c005a992546a4), [`80b539f`](https://github.com/Effect-TS/effect/commit/80b539f8aba68f478c75c35c2b4140c4ffc4fada), [`88a54cc`](https://github.com/Effect-TS/effect/commit/88a54cc341006e3ebcb13482c618f62a680ce199), [`e6e6dba`](https://github.com/Effect-TS/effect/commit/e6e6dba6e9d86e7c2ad27dcedf289db76a19697f), [`bfb203e`](https://github.com/Effect-TS/effect/commit/bfb203e95aa439f731acad37fc3a9a831a190f1c), [`2e9a34a`](https://github.com/Effect-TS/effect/commit/2e9a34ac2bece4f3a206160480c991e3841dc67a), [`55d4eb3`](https://github.com/Effect-TS/effect/commit/55d4eb34f2c64d54f6a25a305b5c5438ebd7934e), [`bddb010`](https://github.com/Effect-TS/effect/commit/bddb010eac3d4436cb094edbbee7460c5440c162), [`a328835`](https://github.com/Effect-TS/effect/commit/a328835e50d76bc96648a1c1550456e8c9f81210), [`5560d05`](https://github.com/Effect-TS/effect/commit/5560d05aa6abdd29466d9c3412cc5e648b0adbde), [`8f6e3ad`](https://github.com/Effect-TS/effect/commit/8f6e3adb185b16e8820b98c509b308086f7ff1af), [`46997fa`](https://github.com/Effect-TS/effect/commit/46997fa60401f5e3c93daa4b61f7df8e31caaab4), [`9e6e12d`](https://github.com/Effect-TS/effect/commit/9e6e12d75c118cd265496f2880490d1f33a5c8bf), [`3394b93`](https://github.com/Effect-TS/effect/commit/3394b93d97d6f24fc38670641d1490289ffca7f1), [`febeabc`](https://github.com/Effect-TS/effect/commit/febeabc3f7c31094da000a23edeaabfe2ab00a38), [`54161c9`](https://github.com/Effect-TS/effect/commit/54161c98f6f3569e0c31842f54e6a257f9421c4c), [`385f7a4`](https://github.com/Effect-TS/effect/commit/385f7a4ee4a7359928597ea56d151dbaf5eb5802), [`7eea4d0`](https://github.com/Effect-TS/effect/commit/7eea4d0b73ec554915d7066a71f46326ce2ba45f), [`7543afe`](https://github.com/Effect-TS/effect/commit/7543afea6f4d97d1f1ad876224323838a48daadd), [`44b9cf3`](https://github.com/Effect-TS/effect/commit/44b9cf3d240d726997b4bbcd0ede48e074d3c456), [`7eea4d0`](https://github.com/Effect-TS/effect/commit/7eea4d0b73ec554915d7066a71f46326ce2ba45f), [`0a8aa6a`](https://github.com/Effect-TS/effect/commit/0a8aa6acb90a72b91c24d17133c950e4cacd8abd), [`c8d9fcf`](https://github.com/Effect-TS/effect/commit/c8d9fcf7b030f7c474effbab2764ce7aee1c7209), [`9ca7f9a`](https://github.com/Effect-TS/effect/commit/9ca7f9a69363e4485645966d5a93b8f9597c5206), [`e7aca89`](https://github.com/Effect-TS/effect/commit/e7aca894bb32fbb785b5830837e6061c415a6015), [`55d7560`](https://github.com/Effect-TS/effect/commit/55d75609b8acf8a1b54c1b1c7fbbb65ec741aa3e), [`f809189`](https://github.com/Effect-TS/effect/commit/f809189ddf6b6011ba43a9901baaa734e315da2a), [`88a54cc`](https://github.com/Effect-TS/effect/commit/88a54cc341006e3ebcb13482c618f62a680ce199), [`0ebdbe7`](https://github.com/Effect-TS/effect/commit/0ebdbe74463dc84385956d0b1e8c2b79ebab5400), [`7517d09`](https://github.com/Effect-TS/effect/commit/7517d09f12a0b183a81bd425962c4e280a68b05d), [`212493b`](https://github.com/Effect-TS/effect/commit/212493b9a1eb98cd1ef6959c707a2e5784a5ae91), [`88a54cc`](https://github.com/Effect-TS/effect/commit/88a54cc341006e3ebcb13482c618f62a680ce199), [`80ea8cb`](https://github.com/Effect-TS/effect/commit/80ea8cb9222ca73f564c8267ab2f82966fea027a), [`8df19f4`](https://github.com/Effect-TS/effect/commit/8df19f4fe81d90cc33ace88b9a77e5534f82d604)]: + - effect@4.0.0-beta.99 + +## 4.0.0-beta.98 + +### Patch Changes + +- Updated dependencies [[`989603b`](https://github.com/Effect-TS/effect-smol/commit/989603b60ab1197b64acf214208e0d370cd1f842), [`214c458`](https://github.com/Effect-TS/effect-smol/commit/214c458084bb6995d543cd37d1055f24be3d454e), [`a037273`](https://github.com/Effect-TS/effect-smol/commit/a0372736ac34796969b051bbba4717d7983f1ebe), [`97fdaa9`](https://github.com/Effect-TS/effect-smol/commit/97fdaa9c1f522c65e579365d314a07878e2b904f), [`b24d248`](https://github.com/Effect-TS/effect-smol/commit/b24d248c8df44222ce642087cde2bd859a2dc709), [`19c222c`](https://github.com/Effect-TS/effect-smol/commit/19c222cac2353a3d7b7733caecb00556fffe9a5c), [`eec85dd`](https://github.com/Effect-TS/effect-smol/commit/eec85ddba09ea326fd268ee33eeffd47e50d4671), [`0082f4f`](https://github.com/Effect-TS/effect-smol/commit/0082f4f74fb139fd578f87f0a790e845133983dc), [`8849052`](https://github.com/Effect-TS/effect-smol/commit/884905232d1e9a365e046d8dde27bf9c5707f57f), [`c15e16a`](https://github.com/Effect-TS/effect-smol/commit/c15e16ad130d1fbde25d912b7ac55995066cb35b), [`01d00a3`](https://github.com/Effect-TS/effect-smol/commit/01d00a3abfbf1f37996cdbe738ea5137c646cdd7), [`8bd4589`](https://github.com/Effect-TS/effect-smol/commit/8bd458975a1b3a8ed042eccf317b93d28ded91e7), [`0082f4f`](https://github.com/Effect-TS/effect-smol/commit/0082f4f74fb139fd578f87f0a790e845133983dc), [`6e08428`](https://github.com/Effect-TS/effect-smol/commit/6e08428d980501b856f846ad3f3f0e4ea46e7786), [`388dcf9`](https://github.com/Effect-TS/effect-smol/commit/388dcf953f65d317547f34d40e6443c5f264205f), [`2b7ce2b`](https://github.com/Effect-TS/effect-smol/commit/2b7ce2b513e7ec2a77822f1116dc6ffb6ba93f4e), [`87bea7e`](https://github.com/Effect-TS/effect-smol/commit/87bea7e16259246f3bcdf565446394751abca953), [`ce38dc3`](https://github.com/Effect-TS/effect-smol/commit/ce38dc33bda805a684432cca071f4dc3c6b9a1ba), [`a807cd1`](https://github.com/Effect-TS/effect-smol/commit/a807cd170341deca8a1cfb52c4222585f2431bb9), [`fd8a356`](https://github.com/Effect-TS/effect-smol/commit/fd8a356f06a8c9ce4e7e0a13fc4021c178ed31de), [`c2a5edc`](https://github.com/Effect-TS/effect-smol/commit/c2a5edc3abd31ad5bc123362bc1213e03e4095c3), [`5946da3`](https://github.com/Effect-TS/effect-smol/commit/5946da3804a1be5e752b05b96bd058cdba50a1bf), [`4ae0c5f`](https://github.com/Effect-TS/effect-smol/commit/4ae0c5ffcbe6c56ddfcb05c639112a079483539e), [`5b2a0bc`](https://github.com/Effect-TS/effect-smol/commit/5b2a0bceea3a28a33a58555210c90a415dc74a76), [`72ac585`](https://github.com/Effect-TS/effect-smol/commit/72ac585884befde6af9208da738699a93f1bae79), [`5e8c1b8`](https://github.com/Effect-TS/effect-smol/commit/5e8c1b82bfafa121311f987a49ab75395e3647a7), [`0f9c078`](https://github.com/Effect-TS/effect-smol/commit/0f9c07841b04183f485ee6e6458de73b290b09f5)]: + - effect@4.0.0-beta.98 + +## 4.0.0-beta.97 + +### Patch Changes + +- Updated dependencies []: + - effect@4.0.0-beta.97 + +## 4.0.0-beta.96 + +### Patch Changes + +- Updated dependencies [[`1503f45`](https://github.com/Effect-TS/effect-smol/commit/1503f45cb5bb2a74f4705252ec505a1f0ade7e62), [`57fe793`](https://github.com/Effect-TS/effect-smol/commit/57fe79316ffbc380b30626a168981fb26ae97459), [`0c2f78f`](https://github.com/Effect-TS/effect-smol/commit/0c2f78f695ec474e1ff5474da183577975e418f5), [`0c2f78f`](https://github.com/Effect-TS/effect-smol/commit/0c2f78f695ec474e1ff5474da183577975e418f5), [`0c2f78f`](https://github.com/Effect-TS/effect-smol/commit/0c2f78f695ec474e1ff5474da183577975e418f5), [`97f29df`](https://github.com/Effect-TS/effect-smol/commit/97f29df457f7ffd07cfb4b379315c12c086af805)]: + - effect@4.0.0-beta.96 + +## 4.0.0-beta.95 + +### Patch Changes + +- Updated dependencies [[`a482442`](https://github.com/Effect-TS/effect-smol/commit/a482442abdeb490e9652b854ec3495e4aa7273e7), [`fbefa85`](https://github.com/Effect-TS/effect-smol/commit/fbefa850fab2f0a302c20614496aeaaa2a8b5590), [`0b4a32f`](https://github.com/Effect-TS/effect-smol/commit/0b4a32f4260f0d8500942a133001b0d349328102), [`18a49e1`](https://github.com/Effect-TS/effect-smol/commit/18a49e1786679456258002ff9397faf02f678c2d), [`266cb90`](https://github.com/Effect-TS/effect-smol/commit/266cb90bb2c17aabc40563c32db334f09ba3d74b), [`912f095`](https://github.com/Effect-TS/effect-smol/commit/912f095a34572bbd3cedf6edb27878443e3e4a95), [`a6718f9`](https://github.com/Effect-TS/effect-smol/commit/a6718f9e00a15ca903b0732da46116cbf3d6aca7), [`bef5154`](https://github.com/Effect-TS/effect-smol/commit/bef51540a243aa2f872a00c01d0cd58b7a769baa), [`18e0564`](https://github.com/Effect-TS/effect-smol/commit/18e0564bd0f8ebbdfcaf1e2c21529948e9e4a81d), [`fb50f14`](https://github.com/Effect-TS/effect-smol/commit/fb50f14fc3657c1973785aa5b72ecf0b0d28e0b2)]: + - effect@4.0.0-beta.95 + +## 4.0.0-beta.94 + +### Patch Changes + +- Updated dependencies [[`95a0e9b`](https://github.com/Effect-TS/effect-smol/commit/95a0e9bb62797af0e81c9998773405f248f218c5), [`a0a3490`](https://github.com/Effect-TS/effect-smol/commit/a0a3490bbce765f199d8e077aceac504f0462e63), [`f11ce73`](https://github.com/Effect-TS/effect-smol/commit/f11ce73af60823754dc24194f4ffc561b9ea1c2d), [`ff30b6e`](https://github.com/Effect-TS/effect-smol/commit/ff30b6e7c2c63ffc56a4c5818d6d86b01b5ad528), [`1caab3c`](https://github.com/Effect-TS/effect-smol/commit/1caab3cc30f626efbf15e59d74f539a487e5c85c), [`aa80c47`](https://github.com/Effect-TS/effect-smol/commit/aa80c4775a04db87553e5568764cab7e32a72814), [`c2ae4fc`](https://github.com/Effect-TS/effect-smol/commit/c2ae4fce2f03a4cd1861c2b1179da7df656e662d), [`a0a3490`](https://github.com/Effect-TS/effect-smol/commit/a0a3490bbce765f199d8e077aceac504f0462e63)]: + - effect@4.0.0-beta.94 + +## 4.0.0-beta.93 + +### Patch Changes + +- Updated dependencies [[`00652fe`](https://github.com/Effect-TS/effect-smol/commit/00652fe95c18f87208e91343eb8bf218faa2f677), [`6c58167`](https://github.com/Effect-TS/effect-smol/commit/6c5816746eaf91d2a3c7c899c5720809fa230ae3), [`2bc5415`](https://github.com/Effect-TS/effect-smol/commit/2bc541501a7ef89e542d7cb98e96beb53cd205cc), [`e11cccc`](https://github.com/Effect-TS/effect-smol/commit/e11cccc7d5fe631abccc7d6e3bd296938de0fa2e), [`ba7e77e`](https://github.com/Effect-TS/effect-smol/commit/ba7e77e046b8641a3a4e9750bb88ca4a1d063d3f), [`5713ee7`](https://github.com/Effect-TS/effect-smol/commit/5713ee7edbc3054efde407b2286bbfd45bbc6e1c)]: + - effect@4.0.0-beta.93 + +## 4.0.0-beta.92 + +### Patch Changes + +- Updated dependencies [[`affdc13`](https://github.com/Effect-TS/effect-smol/commit/affdc139045cc325dce321a84a580fdc1b2da7b9)]: + - effect@4.0.0-beta.92 + +## 4.0.0-beta.91 + +### Patch Changes + +- Updated dependencies [[`b135b25`](https://github.com/Effect-TS/effect-smol/commit/b135b2517fca9e7839734ace3699a7dfa75b9075), [`aaa21a3`](https://github.com/Effect-TS/effect-smol/commit/aaa21a369a171c600db294f2a4f640583043e150), [`3475ee6`](https://github.com/Effect-TS/effect-smol/commit/3475ee6c2bda6b05c6d7a12ce30c8bb840b5b1a6)]: + - effect@4.0.0-beta.91 + ## 4.0.0-beta.90 ### Patch Changes diff --git a/repos/effect/packages/sql/d1/README.md b/repos/effect/packages/sql/d1/README.md index 54b8abfca9..9981ef4339 100644 --- a/repos/effect/packages/sql/d1/README.md +++ b/repos/effect/packages/sql/d1/README.md @@ -1,6 +1,6 @@ # `@effect/sql-d1` -An `@effect/sql` implementation for [Cloudflare D1](https://developers.cloudflare.com/d1/). +An Effect SQL implementation for [Cloudflare D1](https://developers.cloudflare.com/d1/). ## Documentation diff --git a/repos/effect/packages/sql/d1/docgen.json b/repos/effect/packages/sql/d1/docgen.json deleted file mode 100644 index 0f9590af28..0000000000 --- a/repos/effect/packages/sql/d1/docgen.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "$schema": "../../node_modules/@effect/docgen/schema.json", - "srcLink": "https://github.com/Effect-TS/effect/tree/main/packages/sql/d1/src/", - "exclude": ["src/internal/**/*.ts"], - "tscExecutable": "tsgo", - "examplesCompilerOptions": { - "noEmit": true, - "strict": true, - "skipLibCheck": true, - "moduleResolution": "Bundler", - "module": "ES2022", - "target": "ES2022", - "lib": ["ES2022", "DOM", "DOM.Iterable"], - "rewriteRelativeImportExtensions": true, - "allowImportingTsExtensions": true, - "paths": { - "effect": ["../../../effect/src/index.js"], - "effect/*": ["../../../effect/src/*.js"] - }, - "plugins": [ - { "name": "@effect/language-service", "includeSuggestionsInTsc": false } - ] - } -} diff --git a/repos/effect/packages/sql/d1/package.json b/repos/effect/packages/sql/d1/package.json index fab0ae4b6a..87babcfb61 100644 --- a/repos/effect/packages/sql/d1/package.json +++ b/repos/effect/packages/sql/d1/package.json @@ -1,17 +1,17 @@ { "name": "@effect/sql-d1", - "version": "4.0.0-beta.90", + "version": "4.0.0-beta.107", "type": "module", "license": "MIT", "description": "A Cloudflare D1 integration for Effect", "homepage": "https://effect.website", "repository": { "type": "git", - "url": "https://github.com/Effect-TS/effect-smol.git", + "url": "https://github.com/Effect-TS/effect.git", "directory": "packages/sql/d1" }, "bugs": { - "url": "https://github.com/Effect-TS/effect-smol/issues" + "url": "https://github.com/Effect-TS/effect/issues" }, "tags": [ "typescript", @@ -33,6 +33,7 @@ ".": "./src/index.ts", "./*": "./src/*.ts", "./internal/*": null, + "./index": null, "./*/index": null }, "files": [ @@ -40,7 +41,10 @@ "dist/**/*.js", "dist/**/*.js.map", "dist/**/*.d.ts", - "dist/**/*.d.ts.map" + "dist/**/*.d.ts.map", + "AGENTS.md", + "CLAUDE.md", + "ai-docs/**/*" ], "publishConfig": { "access": "public", @@ -50,26 +54,24 @@ ".": "./dist/index.js", "./*": "./dist/*.js", "./internal/*": null, + "./index": null, "./*/index": null } }, "scripts": { "codegen": "effect-utils codegen", "build": "tsc -b tsconfig.json && pnpm babel", - "build:tsgo": "tsgo -b tsconfig.json && pnpm babel", "babel": "babel dist --plugins annotate-pure-calls --out-dir dist --source-maps", - "check": "tsc -b tsconfig.json", - "test": "vitest", - "coverage": "vitest --coverage" + "check": "tsc -b tsconfig.json" }, "devDependencies": { "effect": "workspace:^", - "miniflare": "^4.20260507.1" + "miniflare": "^4.20260706.0" }, "peerDependencies": { "effect": "workspace:^" }, "dependencies": { - "@cloudflare/workers-types": "^4.20260511.1" + "@cloudflare/workers-types": "^5.20260708.1" } } diff --git a/repos/effect/packages/sql/d1/src/D1Client.ts b/repos/effect/packages/sql/d1/src/D1Client.ts index 50078f5bc6..9ffffa4677 100644 --- a/repos/effect/packages/sql/d1/src/D1Client.ts +++ b/repos/effect/packages/sql/d1/src/D1Client.ts @@ -27,6 +27,8 @@ import { SqlError, UnknownError } from "effect/unstable/sql/SqlError" import * as Statement from "effect/unstable/sql/Statement" const ATTR_DB_SYSTEM_NAME = "db.system.name" +const ATTR_DB_OPERATION_NAME = "db.operation.name" +const ATTR_DB_QUERY_TEXT = "db.query.text" const classifyError = (cause: unknown, message: string, operation: string) => new UnknownError({ cause, message, operation }) @@ -50,13 +52,38 @@ export type TypeId = "~@effect/sql-d1/D1Client" /** * Cloudflare D1 SQL client service, extending `SqlClient` with its D1 configuration and no `updateValues` support. * - * @category models + * @category services * @since 4.0.0 */ export interface D1Client extends Client.SqlClient { readonly [TypeId]: TypeId readonly config: D1ClientConfig + /** + * Executes SQL statements as a single atomic D1 batch and returns their row results in order. + * + * **When to use** + * + * Use when you have a fixed collection of statements that should run in one + * request and roll back together if any statement fails. + * + * **Gotchas** + * + * Each statement uses the query and result name transformations from the + * client that created it. Mixing clients can produce differently shaped row + * results within the same batch. + * + * @since 4.0.0 + */ + readonly batch: >>( + statements: Statements + ) => Effect.Effect< + { + readonly [K in keyof Statements]: Effect.Success + }, + SqlError + > + /** Not supported in d1 */ readonly updateValues: never } @@ -90,6 +117,78 @@ export interface D1ClientConfig { readonly transformQueryNames?: ((str: string) => string) | undefined } +type TransformRows = (rows: ReadonlyArray) => ReadonlyArray + +type BatchResults>> = { + readonly [K in keyof Statements]: Effect.Success +} + +interface StatementWithTransformRows extends Statement.Statement { + readonly transformRows: TransformRows | undefined +} + +const makeBatch = (options: { + readonly db: D1Database + readonly prepareCache: Cache.Cache + readonly spanAttributes: ReadonlyArray + readonly getClient: () => D1Client +}): D1Client["batch"] => +>>( + statements: Statements +) => { + if (statements.length === 0) { + return Effect.succeed([] as unknown as BatchResults) + } + return Effect.useSpan( + "sql.execute", + { kind: "client" }, + (span) => + Effect.withFiber(Effect.fnUntraced(function*(fiber) { + const transformer = fiber.getRef(Statement.CurrentTransformer) + const prepared: Array = [] + const transforms: Array = [] + const queryTexts: Array = [] + + for (const original of statements) { + const statement = transformer === undefined + ? original + : yield* transformer(original, options.getClient(), fiber, span) + const [sql, params] = statement.compile() + queryTexts.push(sql) + transforms.push((statement as StatementWithTransformRows).transformRows) + prepared.push((yield* Cache.get(options.prepareCache, sql)).bind(...params)) + } + + for (const [key, value] of options.spanAttributes) { + span.attribute(key, value) + } + span.attribute(ATTR_DB_OPERATION_NAME, "batch") + span.attribute(ATTR_DB_QUERY_TEXT, queryTexts.join("; ")) + + // D1 batches execute on the binding directly and intentionally cannot participate in SqlClient transactions. + const responses = yield* Effect.tryPromise({ + try: () => + options.db.batch>(prepared).then((responses) => { + for (const response of responses) { + if (response.error) { + throw response.error + } + } + return responses + }), + catch: (cause) => new SqlError({ reason: classifyError(cause, "Failed to execute batch", "execute") }) + }) + + const results = responses.map((response, index) => { + const rows = response.results || [] + const transformRows = transforms[index] + return transformRows ? transformRows(rows) : rows + }) + return results as BatchResults + })) + ) +} + /** * Creates a scoped Cloudflare D1 SQL client. Prepared statements are cached, while transactions and streaming queries are not supported by this driver. * @@ -104,6 +203,10 @@ export const make = ( const transformRows = options.transformResultNames ? Statement.defaultTransforms(options.transformResultNames).array : undefined + const spanAttributes: Array = [ + ...(options.spanAttributes ? Object.entries(options.spanAttributes) : []), + [ATTR_DB_SYSTEM_NAME, "sqlite"] + ] const makeConnection = Effect.gen(function*() { const db = options.db @@ -182,7 +285,7 @@ export const make = ( catch: (cause) => new SqlError({ reason: classifyError(cause, "Failed to execute statement", "execute") }) }) - return identity({ + const connection = identity({ execute(sql, params, transformRows) { return transformRows ? Effect.map(runCached(sql, params), transformRows) @@ -206,28 +309,60 @@ export const make = ( return Stream.die("executeStream not implemented") } }) + return { connection, prepareCache } as const }) - const connection = yield* makeConnection + const { connection, prepareCache } = yield* makeConnection const acquirer = Effect.succeed(connection) const transactionAcquirer = Effect.die("transactions are not supported in D1") - return Object.assign( + let client!: D1Client + client = Object.assign( (yield* Client.make({ acquirer, compiler, transactionAcquirer, - spanAttributes: [ - ...(options.spanAttributes ? Object.entries(options.spanAttributes) : []), - [ATTR_DB_SYSTEM_NAME, "sqlite"] - ], + spanAttributes, transformRows })) as D1Client, { [TypeId]: TypeId as TypeId, - config: options + config: options, + batch: makeBatch({ + db: options.db, + prepareCache, + spanAttributes, + getClient: () => client + }) } ) + + if (options.transformQueryNames !== undefined || transformRows !== undefined) { + const clientWithoutTransformsBase = yield* Client.make({ + acquirer: Effect.succeed(connection), + compiler: compiler.withoutTransform, + transactionAcquirer, + spanAttributes, + transformRows: undefined + }) + let clientWithoutTransforms!: D1Client + clientWithoutTransforms = Object.assign(clientWithoutTransformsBase as D1Client, { + [TypeId]: TypeId as TypeId, + config: options, + batch: makeBatch({ + db: options.db, + prepareCache, + spanAttributes, + getClient: () => clientWithoutTransforms + }), + withoutTransforms: () => clientWithoutTransforms + }) + Object.assign(client, { + withoutTransforms: () => clientWithoutTransforms + }) + } + + return client }) /** diff --git a/repos/effect/packages/sql/d1/test/Client.test.ts b/repos/effect/packages/sql/d1/test/Client.test.ts index a8678b24c9..7c658d2273 100644 --- a/repos/effect/packages/sql/d1/test/Client.test.ts +++ b/repos/effect/packages/sql/d1/test/Client.test.ts @@ -2,6 +2,7 @@ import { D1Client } from "@effect/sql-d1" import { assert, describe, it } from "@effect/vitest" import { Cause, Effect } from "effect" import * as Reactivity from "effect/unstable/reactivity/Reactivity" +import { Statement } from "effect/unstable/sql" import { D1Miniflare } from "./utils.ts" describe("Client", () => { @@ -49,6 +50,137 @@ describe("Client", () => { assert.deepStrictEqual(rows, [{ id: 1, name: "hello" }]) }).pipe(Effect.provide(D1Miniflare.layerClient))) + it.effect("should execute statements in a batch", () => + Effect.gen(function*() { + const sql = yield* D1Client.D1Client + yield* sql`CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT)` + + const results: readonly [ + ReadonlyArray<{ id: number; name: string }>, + ReadonlyArray<{ id: number; name: string }>, + ReadonlyArray<{ count: number }> + ] = yield* sql.batch( + [ + sql<{ id: number; name: string }>`INSERT INTO test (name) VALUES (${"hello"}) RETURNING *`, + sql<{ id: number; name: string }>`INSERT INTO test (name) VALUES (${"world"}) RETURNING *`, + sql<{ count: number }>`SELECT COUNT(*) AS count FROM test` + ] as const + ) + + assert.deepStrictEqual(results, [ + [{ id: 1, name: "hello" }], + [{ id: 2, name: "world" }], + [{ count: 2 }] + ]) + }).pipe(Effect.provide(D1Miniflare.layerClient))) + + it.effect("should apply result transforms to batch results", () => + Effect.gen(function*() { + const sql = yield* D1Client.D1Client + yield* sql`CREATE TABLE test (first_name TEXT, "firstName" TEXT)` + yield* sql`INSERT INTO test (first_name, "firstName") VALUES ('John', 'Jane')` + + yield* Effect.gen(function*() { + const transformed = yield* D1Client.make({ + db: sql.config.db, + transformQueryNames: (name) => name === "firstName" ? "first_name" : name, + transformResultNames: (name) => name === "first_name" ? "firstName" : name + }) + const [rows] = yield* transformed.batch([ + transformed<{ firstName: string }>`SELECT ${transformed("firstName")} FROM test` + ]) + assert.deepStrictEqual(rows, [{ firstName: "John" }]) + + const withoutTransforms = transformed.withoutTransforms() + const [rawRows] = yield* withoutTransforms.batch([ + withoutTransforms<{ firstName: string }>`SELECT ${withoutTransforms("firstName")} FROM test` + ]) + assert.deepStrictEqual(rawRows, [{ firstName: "Jane" }]) + + const [rawRowsFromTransformedBatch] = yield* transformed.batch([ + withoutTransforms<{ first_name: string }>`SELECT ${withoutTransforms("first_name")} FROM test` + ]) + assert.deepStrictEqual(rawRowsFromTransformedBatch, [{ first_name: "John" }]) + + const [transformedRowsFromRawBatch] = yield* withoutTransforms.batch([ + transformed<{ firstName: string }>`SELECT ${transformed("firstName")} FROM test` + ]) + assert.deepStrictEqual(transformedRowsFromRawBatch, [{ firstName: "John" }]) + }).pipe( + Effect.scoped, + Effect.provide(Reactivity.layer) + ) + }).pipe(Effect.provide(D1Miniflare.layerClient))) + + it.effect("should disable query-only transforms", () => + Effect.gen(function*() { + const sql = yield* D1Client.D1Client + yield* sql`CREATE TABLE test (first_name TEXT, "firstName" TEXT)` + yield* sql`INSERT INTO test (first_name, "firstName") VALUES ('John', 'Jane')` + + yield* Effect.gen(function*() { + const transformed = yield* D1Client.make({ + db: sql.config.db, + transformQueryNames: (name) => name === "firstName" ? "first_name" : name + }) + const [transformedRows] = yield* transformed.batch([ + transformed<{ first_name: string }>`SELECT ${transformed("firstName")} FROM test` + ]) + assert.deepStrictEqual(transformedRows, [{ first_name: "John" }]) + + const withoutTransforms = transformed.withoutTransforms() + const [rawRows] = yield* withoutTransforms.batch([ + withoutTransforms<{ firstName: string }>`SELECT ${withoutTransforms("firstName")} FROM test` + ]) + assert.deepStrictEqual(rawRows, [{ firstName: "Jane" }]) + }).pipe( + Effect.scoped, + Effect.provide(Reactivity.layer) + ) + }).pipe(Effect.provide(D1Miniflare.layerClient))) + + it.effect("should roll back a failed batch", () => + Effect.gen(function*() { + const sql = yield* D1Client.D1Client + yield* sql`CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT UNIQUE)` + + const error = yield* sql.batch([ + sql`INSERT INTO test (name) VALUES (${"duplicate"})`, + sql`INSERT INTO test (name) VALUES (${"duplicate"})` + ]).pipe(Effect.flip) + + assert.strictEqual(error.reason._tag, "UnknownError") + assert.strictEqual(error.reason.operation, "execute") + const rows = yield* sql`SELECT * FROM test` + assert.deepStrictEqual(rows, []) + }).pipe(Effect.provide(D1Miniflare.layerClient))) + + it.effect("should support an empty batch", () => + Effect.gen(function*() { + const sql = yield* D1Client.D1Client + const results: readonly [] = yield* sql.batch([]) + assert.deepStrictEqual(results, []) + }).pipe(Effect.provide(D1Miniflare.layerClient))) + + it.effect("should apply statement transformers in a batch", () => + Effect.gen(function*() { + const sql = yield* D1Client.D1Client + const queries: Array = [] + + const results = yield* sql.batch([ + sql`SELECT ${1} AS value`, + sql`SELECT ${2} AS value` + ]).pipe( + Effect.provideService(Statement.CurrentTransformer, (statement, sql) => + Effect.sync(() => { + queries.push(statement.compile()[0]) + }).pipe(Effect.as(sql`SELECT ${3} AS value`))) + ) + + assert.deepStrictEqual(queries, ["SELECT ? AS value", "SELECT ? AS value"]) + assert.deepStrictEqual(results, [[{ value: 3 }], [{ value: 3 }]]) + }).pipe(Effect.provide(D1Miniflare.layerClient))) + it.effect("should defect on transactions", () => Effect.gen(function*() { const sql = yield* D1Client.D1Client @@ -62,4 +194,20 @@ describe("Client", () => { assert.deepStrictEqual(rows, []) assert.equal(Cause.hasDies(res), true) }).pipe(Effect.provide(D1Miniflare.layerClient))) + + it.effect("should defect when batching in a transaction", () => + Effect.gen(function*() { + const sql = yield* D1Client.D1Client + yield* sql`CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT)` + const res = yield* sql.batch([ + sql`INSERT INTO test ${sql.insert({ name: "hello" })}` + ]).pipe( + sql.withTransaction, + Effect.sandbox, + Effect.flip + ) + const rows = yield* sql`SELECT * FROM test` + assert.deepStrictEqual(rows, []) + assert.equal(Cause.hasDies(res), true) + }).pipe(Effect.provide(D1Miniflare.layerClient))) }) diff --git a/repos/effect/packages/sql/d1/test/Resolver.test.ts b/repos/effect/packages/sql/d1/test/Resolver.test.ts index 047f538eee..0f203f285d 100644 --- a/repos/effect/packages/sql/d1/test/Resolver.test.ts +++ b/repos/effect/packages/sql/d1/test/Resolver.test.ts @@ -1,6 +1,6 @@ import { D1Client } from "@effect/sql-d1" import { assert, describe, it } from "@effect/vitest" -import { Cause, Effect, Iterable } from "effect" +import { Cause, Effect } from "effect" import * as Schema from "effect/Schema" import { SqlError, SqlResolver } from "effect/unstable/sql" import { D1Miniflare } from "./utils.ts" @@ -8,9 +8,14 @@ import { D1Miniflare } from "./utils.ts" const seededClient = Effect.gen(function*() { const sql = yield* D1Client.D1Client yield* sql`CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT)` - for (const id of Iterable.range(1, 100)) { - yield* sql`INSERT INTO test ${sql.insert({ id, name: `name${id}` })}` - } + yield* sql`INSERT INTO test ${ + sql.insert([ + { id: 1, name: "name1" }, + { id: 2, name: "name2" }, + { id: 3, name: "name3" }, + { id: 100, name: "name100" } + ]) + }` return sql }) diff --git a/repos/effect/packages/sql/d1/tsconfig.json b/repos/effect/packages/sql/d1/tsconfig.json index 19a2f5dbca..e2a8ca19a0 100644 --- a/repos/effect/packages/sql/d1/tsconfig.json +++ b/repos/effect/packages/sql/d1/tsconfig.json @@ -1,5 +1,5 @@ { - "$schema": "http://json.schemastore.org/tsconfig", + "$schema": "https://json.schemastore.org/tsconfig", "extends": "../../../tsconfig.base.json", "include": ["src"], "references": [ diff --git a/repos/effect/packages/sql/d1/vitest.config.ts b/repos/effect/packages/sql/d1/vitest.config.ts deleted file mode 100644 index c8a52c1826..0000000000 --- a/repos/effect/packages/sql/d1/vitest.config.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { mergeConfig, type ViteUserConfig } from "vitest/config" -import shared from "../../../vitest.shared.ts" - -const config: ViteUserConfig = {} - -export default mergeConfig(shared, config) diff --git a/repos/effect/packages/sql/libsql/CHANGELOG.md b/repos/effect/packages/sql/libsql/CHANGELOG.md index 59096ecb8a..bede71e81e 100644 --- a/repos/effect/packages/sql/libsql/CHANGELOG.md +++ b/repos/effect/packages/sql/libsql/CHANGELOG.md @@ -1,5 +1,128 @@ # @effect/sql-libsql +## 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 + +- 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 + +- [#6858](https://github.com/Effect-TS/effect/pull/6858) [`9fb59a5`](https://github.com/Effect-TS/effect/commit/9fb59a5bab412182320239c86068da23df5b2172) Thanks @fubhy! - Release transaction serialization when beginning a libSQL transaction fails, allowing later operations to retry. + +- Updated dependencies [[`e56cd8f`](https://github.com/Effect-TS/effect/commit/e56cd8f90c3559baccf8fcf2852ea911235d5944), [`f77c120`](https://github.com/Effect-TS/effect/commit/f77c120d8e04779ddeb8bce8e9cde932f268e4b6), [`b2f95a9`](https://github.com/Effect-TS/effect/commit/b2f95a9c2f2581deb89dc3bae9e89cf819e82923), [`04fd44a`](https://github.com/Effect-TS/effect/commit/04fd44a42abfa8dc2642300dcf49ee48c8ef4539), [`b74333d`](https://github.com/Effect-TS/effect/commit/b74333d83e15b9d042e4698ad23040de60454afe), [`1c40b28`](https://github.com/Effect-TS/effect/commit/1c40b2809503d6aa1358777196fc66317906e657), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`b3901d2`](https://github.com/Effect-TS/effect/commit/b3901d29c543fd5bd05ceec669a17896c8e19006), [`4a0984a`](https://github.com/Effect-TS/effect/commit/4a0984af62738fedf4bd3e87adb4d4d641ce9147), [`fffd88b`](https://github.com/Effect-TS/effect/commit/fffd88b3135abdf928ca7c4b0e00e610985091c7), [`f3f6c1e`](https://github.com/Effect-TS/effect/commit/f3f6c1e02cb543423fcffef5dc2db03fac503588), [`ef07642`](https://github.com/Effect-TS/effect/commit/ef07642dfe671d5258b65d1c1480c4d05c495f15), [`f1bc827`](https://github.com/Effect-TS/effect/commit/f1bc8274a608813d7b09d28dcca04adbf62f8c92), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`081f4d8`](https://github.com/Effect-TS/effect/commit/081f4d8cd06a2ac222d2810b46e61efcee26939e), [`5287b24`](https://github.com/Effect-TS/effect/commit/5287b24f5f8fa094ba20e117bfb1a80fba6d2cf5), [`13d31cf`](https://github.com/Effect-TS/effect/commit/13d31cfc2dde46210e94391b5b6767ae9aeaf2c9), [`acee269`](https://github.com/Effect-TS/effect/commit/acee26944bc89ee554d7b9fadab7443f9edc28a9), [`31170c1`](https://github.com/Effect-TS/effect/commit/31170c19b236c37abb5476c821bc6f5bfa2735ab), [`205ebc7`](https://github.com/Effect-TS/effect/commit/205ebc776062012581e98fced7ced19adfc44ee7), [`ed0ebf8`](https://github.com/Effect-TS/effect/commit/ed0ebf8e5c864d46fed1f232e99c0e680f10a58f), [`a3fd084`](https://github.com/Effect-TS/effect/commit/a3fd08482157bd78b089f77c7b173d54ef68b5cd), [`ee29ddf`](https://github.com/Effect-TS/effect/commit/ee29ddf862c3723ad466abc93ab6f6fe723b2319), [`6086309`](https://github.com/Effect-TS/effect/commit/60863090af8e5af0bfa1435f08dc5390f9993e30), [`4a57af2`](https://github.com/Effect-TS/effect/commit/4a57af24011db1d66e947289d2f7ffc2074696d2), [`660875b`](https://github.com/Effect-TS/effect/commit/660875b4325e6eebb3f04513998301cd2a0847ec), [`8e7c706`](https://github.com/Effect-TS/effect/commit/8e7c706b0aca855489b53d987404566d3e9cb5e7), [`5f63adb`](https://github.com/Effect-TS/effect/commit/5f63adbe75fc9d50d23706a52b3e483ad2a1a01c), [`053bc42`](https://github.com/Effect-TS/effect/commit/053bc42e2a964755611a216e78ed214322efee37), [`c0a1534`](https://github.com/Effect-TS/effect/commit/c0a153494484ecf9f0d0f20895a7a648b4be363b), [`f1e3a37`](https://github.com/Effect-TS/effect/commit/f1e3a378c144f974a6122b299f421b75595af20f), [`cedb01a`](https://github.com/Effect-TS/effect/commit/cedb01a025492a1faf9e59eb23eb96bc3b5e2fff), [`1747440`](https://github.com/Effect-TS/effect/commit/1747440de9a51a56ed3660da748cc01b256adce7), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`b4f1ee2`](https://github.com/Effect-TS/effect/commit/b4f1ee238d96aa78c5f040158cb78671d75b381e), [`a4757f1`](https://github.com/Effect-TS/effect/commit/a4757f1c47067d8d016a6c4a2c541bb8ae520f9b), [`cd122b9`](https://github.com/Effect-TS/effect/commit/cd122b90300d995a237993a2edb7a049785ab6a4), [`5de588b`](https://github.com/Effect-TS/effect/commit/5de588b2472fb0f4eb919766eb8472583a044772), [`3895b9c`](https://github.com/Effect-TS/effect/commit/3895b9cf179262cd277a9c6daafe9050dcf8265e), [`89ce5f3`](https://github.com/Effect-TS/effect/commit/89ce5f3e16e23a193daa475dc72ea8133ae1dacd), [`985de09`](https://github.com/Effect-TS/effect/commit/985de097d75906db2aed784841f81e23cc978b43), [`9800e3a`](https://github.com/Effect-TS/effect/commit/9800e3acc8f36530f671bc8b91558cb112f449a7), [`4dc35f6`](https://github.com/Effect-TS/effect/commit/4dc35f64641746366f867ea3dbfedb9cd4685ada), [`e8eb62b`](https://github.com/Effect-TS/effect/commit/e8eb62b3d0ef27e9761cdc2eb93bdec52d6ee204), [`ecd9993`](https://github.com/Effect-TS/effect/commit/ecd99936112cb69efdb02de3a2fd57f47baefdf3), [`5ab9c08`](https://github.com/Effect-TS/effect/commit/5ab9c08463ce049c45f3502676954a7b72c6b024), [`f5cf965`](https://github.com/Effect-TS/effect/commit/f5cf96548afd51f4b3cf1aea11b04d7f8549ce90), [`a94cbed`](https://github.com/Effect-TS/effect/commit/a94cbed84e9e49bea4bff925599c0f19c4e3deab), [`9160ad7`](https://github.com/Effect-TS/effect/commit/9160ad7d146d4376dd12f7510c025e5b2f638a70), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`52494be`](https://github.com/Effect-TS/effect/commit/52494be9e8eb3bb542d06a3dfefc6bca4e168984), [`5441c8e`](https://github.com/Effect-TS/effect/commit/5441c8e656a6418c0d27feb2df67565a3e1155f4), [`c9b56ab`](https://github.com/Effect-TS/effect/commit/c9b56ab507f224426ee8388dc450da447ec4715f), [`8ef7257`](https://github.com/Effect-TS/effect/commit/8ef72577d1f43212cab87951d659e54e3c8d7d91), [`1519406`](https://github.com/Effect-TS/effect/commit/1519406fed6e8b017ae178dc20bcaa2cf318b570), [`9716990`](https://github.com/Effect-TS/effect/commit/97169902eec3c99baa7f0b2c7b45a0a5eae75819), [`733f75b`](https://github.com/Effect-TS/effect/commit/733f75b7125e3016a975fdd251c0179ae5393786), [`48155c8`](https://github.com/Effect-TS/effect/commit/48155c8ccfc12dcca8a00fa358d50b20c30874e4), [`951d06b`](https://github.com/Effect-TS/effect/commit/951d06b83d459d3e8fa9024e727a5db1662d3322), [`d767b65`](https://github.com/Effect-TS/effect/commit/d767b65a7687e38be23f0b0ee3d52ab5f2360cbe), [`5d52d9d`](https://github.com/Effect-TS/effect/commit/5d52d9d148aaa7f736ed8c310fc8bfa9dc81badf), [`f4151e1`](https://github.com/Effect-TS/effect/commit/f4151e1937c26de14f1d64566f8126173f1b5014), [`e02fbb6`](https://github.com/Effect-TS/effect/commit/e02fbb66f5a0f13dba6c33ef63528a37a17a0676), [`724ce09`](https://github.com/Effect-TS/effect/commit/724ce09650a458d4565e5c7331ea92ca04f08e68), [`dbe91f6`](https://github.com/Effect-TS/effect/commit/dbe91f6961ef9f7e8da910ee5758d9c0d385fca8), [`4c008d2`](https://github.com/Effect-TS/effect/commit/4c008d28b370d817f7ae4579db09836fe084c8d2), [`b650832`](https://github.com/Effect-TS/effect/commit/b6508328708a842f3163467b72486bd228f1a289), [`b46c92f`](https://github.com/Effect-TS/effect/commit/b46c92f3b314f4ffd612b831efa55dd856c587a3), [`5335797`](https://github.com/Effect-TS/effect/commit/5335797003076d9c6fd170da98d779696d555596), [`4b3460d`](https://github.com/Effect-TS/effect/commit/4b3460daa434ec465a95a50704fe1103a9275999), [`6301fd7`](https://github.com/Effect-TS/effect/commit/6301fd710b4325718de2c42997dac28a9e9aa250), [`aebc5c6`](https://github.com/Effect-TS/effect/commit/aebc5c61664b89a840465ec65b79ce635a5ceee8), [`52b2d7b`](https://github.com/Effect-TS/effect/commit/52b2d7b5bd3c7cce3bd5b69c6ab3941004da70f3), [`eec5744`](https://github.com/Effect-TS/effect/commit/eec57445dfa0ef3c5977195ad69415b7e7d42bb6), [`24e0e93`](https://github.com/Effect-TS/effect/commit/24e0e93dc307dc2c2ae86caacb7289e1dab3c103), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`1a7ce81`](https://github.com/Effect-TS/effect/commit/1a7ce8150e3977586c44d8ccb9a8384389bb4d49), [`48f22a7`](https://github.com/Effect-TS/effect/commit/48f22a7d16ae57ee2175d450dafbdeb69e187d2a), [`c96b7f6`](https://github.com/Effect-TS/effect/commit/c96b7f6359662053c3e09344f61dddc7a6caf4ac), [`6d2a942`](https://github.com/Effect-TS/effect/commit/6d2a942ed7cd33b8fd79d549edba33bc9e2a7e3e), [`cc27b19`](https://github.com/Effect-TS/effect/commit/cc27b194b9d13fa3a66ab037e853fca9d41700ff), [`8f9499f`](https://github.com/Effect-TS/effect/commit/8f9499f562729f5f7b08d8bcc4db86b4aeff8a21), [`3eeea73`](https://github.com/Effect-TS/effect/commit/3eeea73cfc3e9b126975c2ddbdb7f7c8c92026e2), [`0a532e5`](https://github.com/Effect-TS/effect/commit/0a532e503f165fdea485a5343fc2f420917e8376), [`f398149`](https://github.com/Effect-TS/effect/commit/f398149c134fd9b67b6cdc52eae3f3248d5c7bbe), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`ace903e`](https://github.com/Effect-TS/effect/commit/ace903e09c2549ceebdec380797beb027cd29f3d), [`e8eb62b`](https://github.com/Effect-TS/effect/commit/e8eb62b3d0ef27e9761cdc2eb93bdec52d6ee204), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`48f22a7`](https://github.com/Effect-TS/effect/commit/48f22a7d16ae57ee2175d450dafbdeb69e187d2a), [`d48506d`](https://github.com/Effect-TS/effect/commit/d48506d97525040aa714305e928126df799795b4), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`d48506d`](https://github.com/Effect-TS/effect/commit/d48506d97525040aa714305e928126df799795b4), [`d48506d`](https://github.com/Effect-TS/effect/commit/d48506d97525040aa714305e928126df799795b4), [`52262be`](https://github.com/Effect-TS/effect/commit/52262be2edce0e350c6ac10f8f725678606399c5), [`1284aa1`](https://github.com/Effect-TS/effect/commit/1284aa183451955ad7921bbe01fd0e095695d444), [`9867b9f`](https://github.com/Effect-TS/effect/commit/9867b9fc69f9cc6c443594fc7eccc7be0c674d9c), [`d0f1a22`](https://github.com/Effect-TS/effect/commit/d0f1a2295155c350b04efb46852cb40032805273), [`979ce39`](https://github.com/Effect-TS/effect/commit/979ce3985d7d62ce2bf240681ca19feda3027452), [`b6d3e67`](https://github.com/Effect-TS/effect/commit/b6d3e67c7cc143cd8470cdf704324e79d23954a9), [`adf6c6c`](https://github.com/Effect-TS/effect/commit/adf6c6cd388af8a3c0c546492e71555368556f6a), [`7314d60`](https://github.com/Effect-TS/effect/commit/7314d605284717aaafe7fc34b88c3c93397e865c), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`1acbd8b`](https://github.com/Effect-TS/effect/commit/1acbd8b44c68ebb23735e9810476b870dbe58aea), [`7bde6cc`](https://github.com/Effect-TS/effect/commit/7bde6ccb2b144fe953ff30a7ef5e1ecc97697146), [`a959a8b`](https://github.com/Effect-TS/effect/commit/a959a8bf21cdb976369f494dc949fa00a050d3e0)]: + - effect@4.0.0-beta.103 + +## 4.0.0-beta.102 + +### Patch Changes + +- Updated dependencies [[`b6392e1`](https://github.com/Effect-TS/effect/commit/b6392e119704553edec1b4fd2869ac0dbec621ef), [`7ed9450`](https://github.com/Effect-TS/effect/commit/7ed945044eb56aa9aeaf62d4746a011c96c58628), [`45762bd`](https://github.com/Effect-TS/effect/commit/45762bd78df9ecd87c98b8d3738cdeeac7d81128), [`a6e8391`](https://github.com/Effect-TS/effect/commit/a6e8391cd31acd898fae18b3f8e7ca4c6f14f065), [`4ac7e8b`](https://github.com/Effect-TS/effect/commit/4ac7e8b136c61a26c3e438c013dfd7349b38e999), [`4cd40f5`](https://github.com/Effect-TS/effect/commit/4cd40f5692477783bef84fed3c5ef1c0cf5602e6), [`6956bc0`](https://github.com/Effect-TS/effect/commit/6956bc0e6cb27f53fbec39d9b18545940f9f598f), [`0e50ec7`](https://github.com/Effect-TS/effect/commit/0e50ec7dbb94390666f292cf9120719bf30a7246), [`9fcdade`](https://github.com/Effect-TS/effect/commit/9fcdade4a8af772b9ccd8b8a24fe8cee0e5d8470), [`57367d5`](https://github.com/Effect-TS/effect/commit/57367d54de55047ff0c5fce9685475e236bf354c), [`35c445f`](https://github.com/Effect-TS/effect/commit/35c445ff18029d192900ea0914c993f58d5cf1a5), [`c917bb9`](https://github.com/Effect-TS/effect/commit/c917bb94a4c1c4e0a24372a8ebb8a5ca232e36b5), [`bc1f358`](https://github.com/Effect-TS/effect/commit/bc1f3583e63344cb2c398d9040d9c975488ed123), [`0e0c9d7`](https://github.com/Effect-TS/effect/commit/0e0c9d7922ff463c1093d9e0576fae12cb0698d5), [`73d40aa`](https://github.com/Effect-TS/effect/commit/73d40aacd8fcae1b48c23f5b0a5c542127401d1d), [`4f1e318`](https://github.com/Effect-TS/effect/commit/4f1e3183f7123591c46224e9c587df7594562a5f), [`9d8d85c`](https://github.com/Effect-TS/effect/commit/9d8d85c1bb7da51970845b8ea830e386e777514a), [`6079fda`](https://github.com/Effect-TS/effect/commit/6079fda7b02f2f01ad91c15ab8c307336f3ba252), [`5101e92`](https://github.com/Effect-TS/effect/commit/5101e92c9c149c153423f43dd7a94f6194653c06), [`d0b3265`](https://github.com/Effect-TS/effect/commit/d0b3265c3262670761471ab3518cf933b1b3b20a), [`7a03c89`](https://github.com/Effect-TS/effect/commit/7a03c893ce6492bf94c0ebfb00b63bf25dcbf83e), [`cea1d9c`](https://github.com/Effect-TS/effect/commit/cea1d9c92601e69ebda040af8a1d860d604d885c), [`078e1f5`](https://github.com/Effect-TS/effect/commit/078e1f5636e31b76a86722a636afc37a8cc25580), [`97bafea`](https://github.com/Effect-TS/effect/commit/97bafeab460833b9781527b437d1cb9cbee63260), [`fab0ab8`](https://github.com/Effect-TS/effect/commit/fab0ab8f7ab15ae596faa4ccf75615a494d11b0b), [`c323d8b`](https://github.com/Effect-TS/effect/commit/c323d8b30dbbe85f9df25b67288b93d5332de333), [`6966353`](https://github.com/Effect-TS/effect/commit/69663534d626003eb10a5e55ab1f13e0379fead1), [`0444004`](https://github.com/Effect-TS/effect/commit/04440041989c1785fe4db286379f2be2c15baa85), [`028bbb3`](https://github.com/Effect-TS/effect/commit/028bbb391e161185da10d974ab33381f769940d7), [`ff5d6e2`](https://github.com/Effect-TS/effect/commit/ff5d6e278a1fdff714315dc1a17075012f05c1f0), [`1bfce93`](https://github.com/Effect-TS/effect/commit/1bfce93e6d2bf0794c11733daf51c2390e7de375), [`7ce815c`](https://github.com/Effect-TS/effect/commit/7ce815cd5af6af991dfc13b890fd22345fc77c20), [`7271a7f`](https://github.com/Effect-TS/effect/commit/7271a7faf1080aa75f2f53ca6a0b5ec9334c1d38), [`475fe5c`](https://github.com/Effect-TS/effect/commit/475fe5c12c2d6504c475797c0634f90da01e1797)]: + - effect@4.0.0-beta.102 + +## 4.0.0-beta.101 + +### Patch Changes + +- Updated dependencies [[`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`b35ed29`](https://github.com/Effect-TS/effect/commit/b35ed2904f01536d303b21f288daf343cf740462), [`dd44624`](https://github.com/Effect-TS/effect/commit/dd446245736a0e88c807a02f03c21450bb9340fa), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`2bae1ac`](https://github.com/Effect-TS/effect/commit/2bae1accce9d3b72cf6d5aefc9b2161af6d88436)]: + - effect@4.0.0-beta.101 + +## 4.0.0-beta.100 + +### Patch Changes + +- Updated dependencies [[`c1288dd`](https://github.com/Effect-TS/effect/commit/c1288dd1a52a2811ab7df57fc4ce236c6be4c745), [`2b58a3d`](https://github.com/Effect-TS/effect/commit/2b58a3dab6bc99776dddaf76e27d811e0f47f3d8), [`6dc83f2`](https://github.com/Effect-TS/effect/commit/6dc83f26ddf20d48db28cf761dd8f3716e5273fb), [`c1e2fe0`](https://github.com/Effect-TS/effect/commit/c1e2fe0cf93564f4d919e3998874c3e70b0cf30f), [`f3fbae8`](https://github.com/Effect-TS/effect/commit/f3fbae8d7bae0d77cb4f35a1598b26c58e3bf94d), [`e000f80`](https://github.com/Effect-TS/effect/commit/e000f80fd55bcd8edc699fdbf4cd109004f4f754), [`f4ee765`](https://github.com/Effect-TS/effect/commit/f4ee7655ee052cf9ba726fd602bb87c89c7c62a9), [`510b55f`](https://github.com/Effect-TS/effect/commit/510b55f3e21750685dbfd5f476a130c1c5af9dbd), [`31d3fc4`](https://github.com/Effect-TS/effect/commit/31d3fc4327c50867bb8d881fa7353aeb03ea2826), [`875e618`](https://github.com/Effect-TS/effect/commit/875e618c3764a7b817ac863d0af86924449528f2), [`688d46a`](https://github.com/Effect-TS/effect/commit/688d46afd0ef923d983ad3d7385f52f217b28d70), [`6ff5023`](https://github.com/Effect-TS/effect/commit/6ff502363b9840a5a5ee0a24bc6cae734ac3a3eb), [`c0333e7`](https://github.com/Effect-TS/effect/commit/c0333e7f755f42ddcca7051e029da8b4eed527bf), [`06e7e8c`](https://github.com/Effect-TS/effect/commit/06e7e8c66015ee318f871b9d2218dee82df2b108), [`eb9b102`](https://github.com/Effect-TS/effect/commit/eb9b10256c8558881b441c2fef833b7037174400), [`8b155da`](https://github.com/Effect-TS/effect/commit/8b155da06e0740c354ec562957a45ab65eb4573b), [`3a87335`](https://github.com/Effect-TS/effect/commit/3a8733564c5db35271aa20564ed0d344daa2a79f)]: + - effect@4.0.0-beta.100 + +## 4.0.0-beta.99 + +### Patch Changes + +- Updated dependencies [[`8ce4795`](https://github.com/Effect-TS/effect/commit/8ce4795ccbaebca4292757db568c005a992546a4), [`80b539f`](https://github.com/Effect-TS/effect/commit/80b539f8aba68f478c75c35c2b4140c4ffc4fada), [`88a54cc`](https://github.com/Effect-TS/effect/commit/88a54cc341006e3ebcb13482c618f62a680ce199), [`e6e6dba`](https://github.com/Effect-TS/effect/commit/e6e6dba6e9d86e7c2ad27dcedf289db76a19697f), [`bfb203e`](https://github.com/Effect-TS/effect/commit/bfb203e95aa439f731acad37fc3a9a831a190f1c), [`2e9a34a`](https://github.com/Effect-TS/effect/commit/2e9a34ac2bece4f3a206160480c991e3841dc67a), [`55d4eb3`](https://github.com/Effect-TS/effect/commit/55d4eb34f2c64d54f6a25a305b5c5438ebd7934e), [`bddb010`](https://github.com/Effect-TS/effect/commit/bddb010eac3d4436cb094edbbee7460c5440c162), [`a328835`](https://github.com/Effect-TS/effect/commit/a328835e50d76bc96648a1c1550456e8c9f81210), [`5560d05`](https://github.com/Effect-TS/effect/commit/5560d05aa6abdd29466d9c3412cc5e648b0adbde), [`8f6e3ad`](https://github.com/Effect-TS/effect/commit/8f6e3adb185b16e8820b98c509b308086f7ff1af), [`46997fa`](https://github.com/Effect-TS/effect/commit/46997fa60401f5e3c93daa4b61f7df8e31caaab4), [`9e6e12d`](https://github.com/Effect-TS/effect/commit/9e6e12d75c118cd265496f2880490d1f33a5c8bf), [`3394b93`](https://github.com/Effect-TS/effect/commit/3394b93d97d6f24fc38670641d1490289ffca7f1), [`febeabc`](https://github.com/Effect-TS/effect/commit/febeabc3f7c31094da000a23edeaabfe2ab00a38), [`54161c9`](https://github.com/Effect-TS/effect/commit/54161c98f6f3569e0c31842f54e6a257f9421c4c), [`385f7a4`](https://github.com/Effect-TS/effect/commit/385f7a4ee4a7359928597ea56d151dbaf5eb5802), [`7eea4d0`](https://github.com/Effect-TS/effect/commit/7eea4d0b73ec554915d7066a71f46326ce2ba45f), [`7543afe`](https://github.com/Effect-TS/effect/commit/7543afea6f4d97d1f1ad876224323838a48daadd), [`44b9cf3`](https://github.com/Effect-TS/effect/commit/44b9cf3d240d726997b4bbcd0ede48e074d3c456), [`7eea4d0`](https://github.com/Effect-TS/effect/commit/7eea4d0b73ec554915d7066a71f46326ce2ba45f), [`0a8aa6a`](https://github.com/Effect-TS/effect/commit/0a8aa6acb90a72b91c24d17133c950e4cacd8abd), [`c8d9fcf`](https://github.com/Effect-TS/effect/commit/c8d9fcf7b030f7c474effbab2764ce7aee1c7209), [`9ca7f9a`](https://github.com/Effect-TS/effect/commit/9ca7f9a69363e4485645966d5a93b8f9597c5206), [`e7aca89`](https://github.com/Effect-TS/effect/commit/e7aca894bb32fbb785b5830837e6061c415a6015), [`55d7560`](https://github.com/Effect-TS/effect/commit/55d75609b8acf8a1b54c1b1c7fbbb65ec741aa3e), [`f809189`](https://github.com/Effect-TS/effect/commit/f809189ddf6b6011ba43a9901baaa734e315da2a), [`88a54cc`](https://github.com/Effect-TS/effect/commit/88a54cc341006e3ebcb13482c618f62a680ce199), [`0ebdbe7`](https://github.com/Effect-TS/effect/commit/0ebdbe74463dc84385956d0b1e8c2b79ebab5400), [`7517d09`](https://github.com/Effect-TS/effect/commit/7517d09f12a0b183a81bd425962c4e280a68b05d), [`212493b`](https://github.com/Effect-TS/effect/commit/212493b9a1eb98cd1ef6959c707a2e5784a5ae91), [`88a54cc`](https://github.com/Effect-TS/effect/commit/88a54cc341006e3ebcb13482c618f62a680ce199), [`80ea8cb`](https://github.com/Effect-TS/effect/commit/80ea8cb9222ca73f564c8267ab2f82966fea027a), [`8df19f4`](https://github.com/Effect-TS/effect/commit/8df19f4fe81d90cc33ace88b9a77e5534f82d604)]: + - effect@4.0.0-beta.99 + +## 4.0.0-beta.98 + +### Patch Changes + +- Updated dependencies [[`989603b`](https://github.com/Effect-TS/effect-smol/commit/989603b60ab1197b64acf214208e0d370cd1f842), [`214c458`](https://github.com/Effect-TS/effect-smol/commit/214c458084bb6995d543cd37d1055f24be3d454e), [`a037273`](https://github.com/Effect-TS/effect-smol/commit/a0372736ac34796969b051bbba4717d7983f1ebe), [`97fdaa9`](https://github.com/Effect-TS/effect-smol/commit/97fdaa9c1f522c65e579365d314a07878e2b904f), [`b24d248`](https://github.com/Effect-TS/effect-smol/commit/b24d248c8df44222ce642087cde2bd859a2dc709), [`19c222c`](https://github.com/Effect-TS/effect-smol/commit/19c222cac2353a3d7b7733caecb00556fffe9a5c), [`eec85dd`](https://github.com/Effect-TS/effect-smol/commit/eec85ddba09ea326fd268ee33eeffd47e50d4671), [`0082f4f`](https://github.com/Effect-TS/effect-smol/commit/0082f4f74fb139fd578f87f0a790e845133983dc), [`8849052`](https://github.com/Effect-TS/effect-smol/commit/884905232d1e9a365e046d8dde27bf9c5707f57f), [`c15e16a`](https://github.com/Effect-TS/effect-smol/commit/c15e16ad130d1fbde25d912b7ac55995066cb35b), [`01d00a3`](https://github.com/Effect-TS/effect-smol/commit/01d00a3abfbf1f37996cdbe738ea5137c646cdd7), [`8bd4589`](https://github.com/Effect-TS/effect-smol/commit/8bd458975a1b3a8ed042eccf317b93d28ded91e7), [`0082f4f`](https://github.com/Effect-TS/effect-smol/commit/0082f4f74fb139fd578f87f0a790e845133983dc), [`6e08428`](https://github.com/Effect-TS/effect-smol/commit/6e08428d980501b856f846ad3f3f0e4ea46e7786), [`388dcf9`](https://github.com/Effect-TS/effect-smol/commit/388dcf953f65d317547f34d40e6443c5f264205f), [`2b7ce2b`](https://github.com/Effect-TS/effect-smol/commit/2b7ce2b513e7ec2a77822f1116dc6ffb6ba93f4e), [`87bea7e`](https://github.com/Effect-TS/effect-smol/commit/87bea7e16259246f3bcdf565446394751abca953), [`ce38dc3`](https://github.com/Effect-TS/effect-smol/commit/ce38dc33bda805a684432cca071f4dc3c6b9a1ba), [`a807cd1`](https://github.com/Effect-TS/effect-smol/commit/a807cd170341deca8a1cfb52c4222585f2431bb9), [`fd8a356`](https://github.com/Effect-TS/effect-smol/commit/fd8a356f06a8c9ce4e7e0a13fc4021c178ed31de), [`c2a5edc`](https://github.com/Effect-TS/effect-smol/commit/c2a5edc3abd31ad5bc123362bc1213e03e4095c3), [`5946da3`](https://github.com/Effect-TS/effect-smol/commit/5946da3804a1be5e752b05b96bd058cdba50a1bf), [`4ae0c5f`](https://github.com/Effect-TS/effect-smol/commit/4ae0c5ffcbe6c56ddfcb05c639112a079483539e), [`5b2a0bc`](https://github.com/Effect-TS/effect-smol/commit/5b2a0bceea3a28a33a58555210c90a415dc74a76), [`72ac585`](https://github.com/Effect-TS/effect-smol/commit/72ac585884befde6af9208da738699a93f1bae79), [`5e8c1b8`](https://github.com/Effect-TS/effect-smol/commit/5e8c1b82bfafa121311f987a49ab75395e3647a7), [`0f9c078`](https://github.com/Effect-TS/effect-smol/commit/0f9c07841b04183f485ee6e6458de73b290b09f5)]: + - effect@4.0.0-beta.98 + +## 4.0.0-beta.97 + +### Patch Changes + +- Updated dependencies []: + - effect@4.0.0-beta.97 + +## 4.0.0-beta.96 + +### Patch Changes + +- Updated dependencies [[`1503f45`](https://github.com/Effect-TS/effect-smol/commit/1503f45cb5bb2a74f4705252ec505a1f0ade7e62), [`57fe793`](https://github.com/Effect-TS/effect-smol/commit/57fe79316ffbc380b30626a168981fb26ae97459), [`0c2f78f`](https://github.com/Effect-TS/effect-smol/commit/0c2f78f695ec474e1ff5474da183577975e418f5), [`0c2f78f`](https://github.com/Effect-TS/effect-smol/commit/0c2f78f695ec474e1ff5474da183577975e418f5), [`0c2f78f`](https://github.com/Effect-TS/effect-smol/commit/0c2f78f695ec474e1ff5474da183577975e418f5), [`97f29df`](https://github.com/Effect-TS/effect-smol/commit/97f29df457f7ffd07cfb4b379315c12c086af805)]: + - effect@4.0.0-beta.96 + +## 4.0.0-beta.95 + +### Patch Changes + +- Updated dependencies [[`a482442`](https://github.com/Effect-TS/effect-smol/commit/a482442abdeb490e9652b854ec3495e4aa7273e7), [`fbefa85`](https://github.com/Effect-TS/effect-smol/commit/fbefa850fab2f0a302c20614496aeaaa2a8b5590), [`0b4a32f`](https://github.com/Effect-TS/effect-smol/commit/0b4a32f4260f0d8500942a133001b0d349328102), [`18a49e1`](https://github.com/Effect-TS/effect-smol/commit/18a49e1786679456258002ff9397faf02f678c2d), [`266cb90`](https://github.com/Effect-TS/effect-smol/commit/266cb90bb2c17aabc40563c32db334f09ba3d74b), [`912f095`](https://github.com/Effect-TS/effect-smol/commit/912f095a34572bbd3cedf6edb27878443e3e4a95), [`a6718f9`](https://github.com/Effect-TS/effect-smol/commit/a6718f9e00a15ca903b0732da46116cbf3d6aca7), [`bef5154`](https://github.com/Effect-TS/effect-smol/commit/bef51540a243aa2f872a00c01d0cd58b7a769baa), [`18e0564`](https://github.com/Effect-TS/effect-smol/commit/18e0564bd0f8ebbdfcaf1e2c21529948e9e4a81d), [`fb50f14`](https://github.com/Effect-TS/effect-smol/commit/fb50f14fc3657c1973785aa5b72ecf0b0d28e0b2)]: + - effect@4.0.0-beta.95 + +## 4.0.0-beta.94 + +### Patch Changes + +- Updated dependencies [[`95a0e9b`](https://github.com/Effect-TS/effect-smol/commit/95a0e9bb62797af0e81c9998773405f248f218c5), [`a0a3490`](https://github.com/Effect-TS/effect-smol/commit/a0a3490bbce765f199d8e077aceac504f0462e63), [`f11ce73`](https://github.com/Effect-TS/effect-smol/commit/f11ce73af60823754dc24194f4ffc561b9ea1c2d), [`ff30b6e`](https://github.com/Effect-TS/effect-smol/commit/ff30b6e7c2c63ffc56a4c5818d6d86b01b5ad528), [`1caab3c`](https://github.com/Effect-TS/effect-smol/commit/1caab3cc30f626efbf15e59d74f539a487e5c85c), [`aa80c47`](https://github.com/Effect-TS/effect-smol/commit/aa80c4775a04db87553e5568764cab7e32a72814), [`c2ae4fc`](https://github.com/Effect-TS/effect-smol/commit/c2ae4fce2f03a4cd1861c2b1179da7df656e662d), [`a0a3490`](https://github.com/Effect-TS/effect-smol/commit/a0a3490bbce765f199d8e077aceac504f0462e63)]: + - effect@4.0.0-beta.94 + +## 4.0.0-beta.93 + +### Patch Changes + +- Updated dependencies [[`00652fe`](https://github.com/Effect-TS/effect-smol/commit/00652fe95c18f87208e91343eb8bf218faa2f677), [`6c58167`](https://github.com/Effect-TS/effect-smol/commit/6c5816746eaf91d2a3c7c899c5720809fa230ae3), [`2bc5415`](https://github.com/Effect-TS/effect-smol/commit/2bc541501a7ef89e542d7cb98e96beb53cd205cc), [`e11cccc`](https://github.com/Effect-TS/effect-smol/commit/e11cccc7d5fe631abccc7d6e3bd296938de0fa2e), [`ba7e77e`](https://github.com/Effect-TS/effect-smol/commit/ba7e77e046b8641a3a4e9750bb88ca4a1d063d3f), [`5713ee7`](https://github.com/Effect-TS/effect-smol/commit/5713ee7edbc3054efde407b2286bbfd45bbc6e1c)]: + - effect@4.0.0-beta.93 + +## 4.0.0-beta.92 + +### Patch Changes + +- Updated dependencies [[`affdc13`](https://github.com/Effect-TS/effect-smol/commit/affdc139045cc325dce321a84a580fdc1b2da7b9)]: + - effect@4.0.0-beta.92 + +## 4.0.0-beta.91 + +### Patch Changes + +- Updated dependencies [[`b135b25`](https://github.com/Effect-TS/effect-smol/commit/b135b2517fca9e7839734ace3699a7dfa75b9075), [`aaa21a3`](https://github.com/Effect-TS/effect-smol/commit/aaa21a369a171c600db294f2a4f640583043e150), [`3475ee6`](https://github.com/Effect-TS/effect-smol/commit/3475ee6c2bda6b05c6d7a12ce30c8bb840b5b1a6)]: + - effect@4.0.0-beta.91 + ## 4.0.0-beta.90 ### Patch Changes diff --git a/repos/effect/packages/sql/libsql/README.md b/repos/effect/packages/sql/libsql/README.md index cea29b5afa..02a2379bf9 100644 --- a/repos/effect/packages/sql/libsql/README.md +++ b/repos/effect/packages/sql/libsql/README.md @@ -1,6 +1,6 @@ # `@effect/sql-libsql` -An `@effect/sql` implementation using the `@libsql/client` library. +An Effect SQL implementation using the `@libsql/client` library. ## Documentation diff --git a/repos/effect/packages/sql/libsql/docgen.json b/repos/effect/packages/sql/libsql/docgen.json deleted file mode 100644 index 32c66e4fc0..0000000000 --- a/repos/effect/packages/sql/libsql/docgen.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "$schema": "../../node_modules/@effect/docgen/schema.json", - "srcLink": "https://github.com/Effect-TS/effect/tree/main/packages/sql/libsql/src/", - "exclude": ["src/internal/**/*.ts"], - "tscExecutable": "tsgo", - "examplesCompilerOptions": { - "noEmit": true, - "strict": true, - "skipLibCheck": true, - "moduleResolution": "Bundler", - "module": "ES2022", - "target": "ES2022", - "lib": ["ES2022", "DOM", "DOM.Iterable"], - "rewriteRelativeImportExtensions": true, - "allowImportingTsExtensions": true, - "paths": { - "effect": ["../../../effect/src/index.js"], - "effect/*": ["../../../effect/src/*.js"] - }, - "plugins": [ - { "name": "@effect/language-service", "includeSuggestionsInTsc": false } - ] - } -} diff --git a/repos/effect/packages/sql/libsql/package.json b/repos/effect/packages/sql/libsql/package.json index 200886f2a9..6ec44a445f 100644 --- a/repos/effect/packages/sql/libsql/package.json +++ b/repos/effect/packages/sql/libsql/package.json @@ -1,17 +1,17 @@ { "name": "@effect/sql-libsql", - "version": "4.0.0-beta.90", + "version": "4.0.0-beta.107", "type": "module", "license": "MIT", "description": "A libSQL toolkit for Effect", "homepage": "https://effect.website", "repository": { "type": "git", - "url": "https://github.com/Effect-TS/effect-smol.git", + "url": "https://github.com/Effect-TS/effect.git", "directory": "packages/sql/libsql" }, "bugs": { - "url": "https://github.com/Effect-TS/effect-smol/issues" + "url": "https://github.com/Effect-TS/effect/issues" }, "tags": [ "typescript", @@ -31,6 +31,7 @@ ".": "./src/index.ts", "./*": "./src/*.ts", "./internal/*": null, + "./index": null, "./*/index": null }, "files": [ @@ -38,7 +39,10 @@ "dist/**/*.js", "dist/**/*.js.map", "dist/**/*.d.ts", - "dist/**/*.d.ts.map" + "dist/**/*.d.ts.map", + "AGENTS.md", + "CLAUDE.md", + "ai-docs/**/*" ], "publishConfig": { "access": "public", @@ -48,26 +52,24 @@ ".": "./dist/index.js", "./*": "./dist/*.js", "./internal/*": null, + "./index": null, "./*/index": null } }, "scripts": { "codegen": "effect-utils codegen", "build": "tsc -b tsconfig.json && pnpm babel", - "build:tsgo": "tsgo -b tsconfig.json && pnpm babel", "babel": "babel dist --plugins annotate-pure-calls --out-dir dist --source-maps", - "check": "tsc -b tsconfig.json", - "test": "vitest", - "coverage": "vitest --coverage" + "check": "tsc -b tsconfig.json" }, "devDependencies": { "effect": "workspace:^", - "testcontainers": "^11.14.0" + "testcontainers": "^12.0.4" }, "peerDependencies": { "effect": "workspace:^" }, "dependencies": { - "@libsql/client": "^0.17.3" + "@libsql/client": "^0.17.4" } } diff --git a/repos/effect/packages/sql/libsql/src/LibsqlClient.ts b/repos/effect/packages/sql/libsql/src/LibsqlClient.ts index 4b574e039f..55c12997ee 100644 --- a/repos/effect/packages/sql/libsql/src/LibsqlClient.ts +++ b/repos/effect/packages/sql/libsql/src/LibsqlClient.ts @@ -13,6 +13,7 @@ import * as Libsql from "@libsql/client" import * as Config from "effect/Config" import * as Context from "effect/Context" import * as Effect from "effect/Effect" +import * as Exit from "effect/Exit" import * as Layer from "effect/Layer" import * as Option from "effect/Option" import * as Redacted from "effect/Redacted" @@ -49,7 +50,7 @@ export type TypeId = "~@effect/sql-libsql/LibsqlClient" /** * libSQL-backed SQL client service, extending `SqlClient` with its runtime type marker and client configuration. * - * @category models + * @category services * @since 4.0.0 */ export interface LibsqlClient extends Client.SqlClient { @@ -311,7 +312,9 @@ export const make = ( const scope = Scope.makeUnsafe() yield* restore(semaphore.take(1)) yield* Scope.addFinalizer(scope, semaphore.release(1)) - const conn = yield* connection.beginTransaction + const conn = yield* connection.beginTransaction.pipe( + Effect.tapCause((cause) => Scope.close(scope, Exit.failCause(cause))) + ) return [scope, conn] as const })), begin: () => Effect.void, // already begun in acquireConnection diff --git a/repos/effect/packages/sql/libsql/src/LibsqlMigrator.ts b/repos/effect/packages/sql/libsql/src/LibsqlMigrator.ts index 9d7082436d..210ca70fe5 100644 --- a/repos/effect/packages/sql/libsql/src/LibsqlMigrator.ts +++ b/repos/effect/packages/sql/libsql/src/LibsqlMigrator.ts @@ -24,7 +24,7 @@ export * from "effect/unstable/sql/Migrator" /** * Runs SQL migrations using the configured `SqlClient`, returning the migrations that were applied. * - * @category constructors + * @category running * @since 4.0.0 */ export const run: ( @@ -38,7 +38,7 @@ export const run: ( /** * Creates a layer that runs the configured SQL migrations during layer construction. * - * @category constructors + * @category layers * @since 4.0.0 */ export const layer = ( diff --git a/repos/effect/packages/sql/libsql/test/Client.integration.test.ts b/repos/effect/packages/sql/libsql/test/Client.integration.test.ts new file mode 100644 index 0000000000..352f647d56 --- /dev/null +++ b/repos/effect/packages/sql/libsql/test/Client.integration.test.ts @@ -0,0 +1,139 @@ +import { LibsqlClient } from "@effect/sql-libsql" +import { assert, describe, it, layer } from "@effect/vitest" +import { Effect, Exit, Layer } from "effect" +import * as Reactivity from "effect/unstable/reactivity/Reactivity" +import { LibsqlContainer } from "./util.ts" + +const Migrations = Layer.effectDiscard( + LibsqlClient.LibsqlClient.pipe( + Effect.andThen((sql) => + Effect.acquireRelease( + sql`CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT)`, + () => sql`DROP TABLE test;`.pipe(Effect.ignore) + ) + ) + ) +) + +describe("Client", () => { + it.effect("releases transaction serialization after begin fails", () => { + let transactionCalls = 0 + const transaction = { + execute: () => Promise.resolve({ rows: [] }), + commit: () => Promise.resolve(), + rollback: () => Promise.resolve() + } + const liveClient = { + execute: () => Promise.resolve({ rows: [] }), + transaction: () => { + transactionCalls++ + return transactionCalls === 1 + ? Promise.reject(new Error("transient begin failure")) + : Promise.resolve(transaction) + } + } + + return Effect.gen(function*() { + const client = yield* LibsqlClient.make({ liveClient: liveClient as any }) + const first = yield* Effect.exit(client.withTransaction(Effect.void)) + assert.isTrue(Exit.isFailure(first)) + + yield* Effect.forkChild(client.withTransaction(Effect.void)) + yield* Effect.yieldNow + + assert.strictEqual(transactionCalls, 2) + }).pipe( + Effect.scoped, + Effect.provide(Reactivity.layer) + ) + }) + + layer(LibsqlContainer.layerClient, { timeout: "30 seconds" })((it) => { + it.effect("should work", () => + Effect.gen(function*() { + const sql = yield* LibsqlClient.LibsqlClient + let response = yield* sql`INSERT INTO test (name) VALUES ('hello')` + assert.deepStrictEqual(response, []) + response = yield* sql`SELECT * FROM test` + assert.deepStrictEqual(response, [{ id: 1, name: "hello" }]) + response = yield* sql`SELECT * FROM test` + assert.deepStrictEqual(yield* sql`select * from test`.values, [ + [1, "hello"] + ]) + }).pipe(Effect.provide(Migrations))) + + it.effect("should work with raw", () => + Effect.gen(function*() { + const sql = yield* LibsqlClient.LibsqlClient + let response: any + response = yield* sql`CREATE TABLE test2 (id INTEGER PRIMARY KEY, name TEXT)`.raw + yield* Effect.addFinalizer(() => sql`DROP TABLE test2;`.pipe(Effect.ignore)) + assert.deepStrictEqual(response.toJSON(), { + columnTypes: [], + columns: [], + lastInsertRowid: null, + rows: [], + rowsAffected: 0 + }) + response = yield* sql`INSERT INTO test (name) VALUES ('hello')`.raw + assert.deepStrictEqual(response.toJSON(), { + columnTypes: [], + columns: [], + lastInsertRowid: "1", + rows: [], + rowsAffected: 1 + }) + response = yield* sql`SELECT * FROM test`.raw + assert.deepStrictEqual(response.toJSON(), { + columnTypes: ["INTEGER", "TEXT"], + columns: ["id", "name"], + lastInsertRowid: null, + rows: [[1, "hello"]], + rowsAffected: 0 + }) + }).pipe(Effect.provide(Migrations))) + + it.effect("withTransaction", () => + Effect.gen(function*() { + const sql = yield* LibsqlClient.LibsqlClient + yield* sql.withTransaction(sql`INSERT INTO test (name) VALUES ('hello')`) + const rows = yield* sql`SELECT * FROM test` + assert.deepStrictEqual(rows, [{ id: 1, name: "hello" }]) + }).pipe(Effect.provide(Migrations))) + + it.effect("withTransaction rollback", () => + Effect.gen(function*() { + const sql = yield* LibsqlClient.LibsqlClient + yield* sql`INSERT INTO test (name) VALUES ('hello')`.pipe( + Effect.andThen(Effect.fail("boom")), + sql.withTransaction, + Effect.ignore + ) + const rows = yield* sql`SELECT * FROM test` + assert.deepStrictEqual(rows, []) + }).pipe(Effect.provide(Migrations))) + + it.effect("withTransaction nested", () => + Effect.gen(function*() { + const sql = yield* LibsqlClient.LibsqlClient + const stmt = sql`INSERT INTO test (name) VALUES ('hello')` + + yield* stmt.pipe(Effect.andThen(() => stmt.pipe(sql.withTransaction)), sql.withTransaction) + const rows = yield* sql<{ total_rows: number }>`select count(*) as total_rows FROM test` + assert.deepStrictEqual(rows.at(0)?.total_rows, 2) + }).pipe(Effect.provide(Migrations))) + + it.effect("withTransaction nested rollback", () => + Effect.gen(function*() { + const sql = yield* LibsqlClient.LibsqlClient + const stmt = sql`INSERT INTO test (name) VALUES ('hello')` + + yield* stmt.pipe( + Effect.andThen(() => stmt.pipe(Effect.andThen(Effect.fail("boom")), sql.withTransaction, Effect.ignore)), + sql.withTransaction + ) + const rows = yield* sql<{ total_rows: number }>`select count(*) as total_rows FROM test` + assert.deepStrictEqual(rows.at(0)?.total_rows, 1) + }).pipe(Effect.provide(Migrations))) + }) +}) diff --git a/repos/effect/packages/sql/libsql/test/Client.test.ts b/repos/effect/packages/sql/libsql/test/Client.test.ts deleted file mode 100644 index c2f925f7bd..0000000000 --- a/repos/effect/packages/sql/libsql/test/Client.test.ts +++ /dev/null @@ -1,106 +0,0 @@ -import { LibsqlClient } from "@effect/sql-libsql" -import { assert, describe, layer } from "@effect/vitest" -import { Effect, Layer } from "effect" -import { LibsqlContainer } from "./util.ts" - -const Migrations = Layer.effectDiscard( - LibsqlClient.LibsqlClient.pipe( - Effect.andThen((sql) => - Effect.acquireRelease( - sql`CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT)`, - () => sql`DROP TABLE test;`.pipe(Effect.ignore) - ) - ) - ) -) - -describe("Client", () => { - layer(LibsqlContainer.layerClient, { timeout: "30 seconds" })((it) => { - it.effect("should work", () => - Effect.gen(function*() { - const sql = yield* LibsqlClient.LibsqlClient - let response = yield* sql`INSERT INTO test (name) VALUES ('hello')` - assert.deepStrictEqual(response, []) - response = yield* sql`SELECT * FROM test` - assert.deepStrictEqual(response, [{ id: 1, name: "hello" }]) - response = yield* sql`SELECT * FROM test` - assert.deepStrictEqual(yield* sql`select * from test`.values, [ - [1, "hello"] - ]) - }).pipe(Effect.provide(Migrations))) - - it.effect("should work with raw", () => - Effect.gen(function*() { - const sql = yield* LibsqlClient.LibsqlClient - let response: any - response = yield* sql`CREATE TABLE test2 (id INTEGER PRIMARY KEY, name TEXT)`.raw - yield* Effect.addFinalizer(() => sql`DROP TABLE test2;`.pipe(Effect.ignore)) - assert.deepStrictEqual(response.toJSON(), { - columnTypes: [], - columns: [], - lastInsertRowid: null, - rows: [], - rowsAffected: 0 - }) - response = yield* sql`INSERT INTO test (name) VALUES ('hello')`.raw - assert.deepStrictEqual(response.toJSON(), { - columnTypes: [], - columns: [], - lastInsertRowid: "1", - rows: [], - rowsAffected: 1 - }) - response = yield* sql`SELECT * FROM test`.raw - assert.deepStrictEqual(response.toJSON(), { - columnTypes: ["INTEGER", "TEXT"], - columns: ["id", "name"], - lastInsertRowid: null, - rows: [[1, "hello"]], - rowsAffected: 0 - }) - }).pipe(Effect.provide(Migrations))) - - it.effect("withTransaction", () => - Effect.gen(function*() { - const sql = yield* LibsqlClient.LibsqlClient - yield* sql.withTransaction(sql`INSERT INTO test (name) VALUES ('hello')`) - const rows = yield* sql`SELECT * FROM test` - assert.deepStrictEqual(rows, [{ id: 1, name: "hello" }]) - }).pipe(Effect.provide(Migrations))) - - it.effect("withTransaction rollback", () => - Effect.gen(function*() { - const sql = yield* LibsqlClient.LibsqlClient - yield* sql`INSERT INTO test (name) VALUES ('hello')`.pipe( - Effect.andThen(Effect.fail("boom")), - sql.withTransaction, - Effect.ignore - ) - const rows = yield* sql`SELECT * FROM test` - assert.deepStrictEqual(rows, []) - }).pipe(Effect.provide(Migrations))) - - it.effect("withTransaction nested", () => - Effect.gen(function*() { - const sql = yield* LibsqlClient.LibsqlClient - const stmt = sql`INSERT INTO test (name) VALUES ('hello')` - - yield* stmt.pipe(Effect.andThen(() => stmt.pipe(sql.withTransaction)), sql.withTransaction) - const rows = yield* sql<{ total_rows: number }>`select count(*) as total_rows FROM test` - assert.deepStrictEqual(rows.at(0)?.total_rows, 2) - }).pipe(Effect.provide(Migrations))) - - it.effect("withTransaction nested rollback", () => - Effect.gen(function*() { - const sql = yield* LibsqlClient.LibsqlClient - const stmt = sql`INSERT INTO test (name) VALUES ('hello')` - - yield* stmt.pipe( - Effect.andThen(() => stmt.pipe(Effect.andThen(Effect.fail("boom")), sql.withTransaction, Effect.ignore)), - sql.withTransaction - ) - const rows = yield* sql<{ total_rows: number }>`select count(*) as total_rows FROM test` - assert.deepStrictEqual(rows.at(0)?.total_rows, 1) - }).pipe(Effect.provide(Migrations))) - }) -}) diff --git a/repos/effect/packages/sql/libsql/test/Resolver.integration.test.ts b/repos/effect/packages/sql/libsql/test/Resolver.integration.test.ts new file mode 100644 index 0000000000..6e98a9eacd --- /dev/null +++ b/repos/effect/packages/sql/libsql/test/Resolver.integration.test.ts @@ -0,0 +1,182 @@ +import { LibsqlClient } from "@effect/sql-libsql" +import { assert, describe, layer } from "@effect/vitest" +import { Cause, Effect } from "effect" +import * as Schema from "effect/Schema" +import { SqlError, SqlResolver } from "effect/unstable/sql" +import { LibsqlContainer } from "./util.ts" + +const seededClient = Effect.gen(function*() { + const sql = yield* LibsqlClient.LibsqlClient + yield* sql`CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT)` + yield* sql`INSERT INTO test ${ + sql.insert([ + { id: 1, name: "name1" }, + { id: 2, name: "name2" }, + { id: 3, name: "name3" }, + { id: 100, name: "name100" } + ]) + }` + yield* Effect.addFinalizer(() => sql`DROP TABLE test;`.pipe(Effect.orDie)) + return sql +}) + +layer(LibsqlContainer.layerClient, { timeout: "30 seconds" })("Resolver", (it) => { + describe.sequential("ordered", () => { + it.effect("insert", () => + Effect.gen(function*() { + const batches: Array> = [] + const sql = yield* seededClient + const Insert = SqlResolver.ordered({ + Request: Schema.String, + Result: Schema.Struct({ id: Schema.Number, name: Schema.String }), + execute: (names) => { + batches.push(names) + return sql`INSERT INTO test ${sql.insert(names.map((name) => ({ name })))} RETURNING *` + } + }) + const execute = SqlResolver.request(Insert) + assert.deepStrictEqual( + yield* Effect.all({ + one: execute("one"), + two: execute("two") + }, { concurrency: "unbounded" }), + { + one: { id: 101, name: "one" }, + two: { id: 102, name: "two" } + } + ) + assert.deepStrictEqual(batches, [["one", "two"]]) + })) + + it.effect("result length mismatch", () => + Effect.gen(function*() { + const batches: Array> = [] + const sql = yield* seededClient + const Select = SqlResolver.ordered({ + Request: Schema.Number, + Result: Schema.Struct({ id: Schema.Number, name: Schema.String }), + execute: (ids) => { + batches.push(ids) + return sql`SELECT * FROM test WHERE id IN ${sql.in(ids)}` + } + }) + const execute = SqlResolver.request(Select) + const error = yield* Effect.all([ + execute(1), + execute(2), + execute(3), + execute(101) + ], { concurrency: "unbounded" }).pipe( + Effect.flip + ) + assert(error instanceof SqlError.ResultLengthMismatch) + assert.strictEqual(error.actual, 3) + assert.strictEqual(error.expected, 4) + assert.deepStrictEqual(batches, [[1, 2, 3, 101]]) + })) + }) + + describe.sequential("grouped", () => { + it.effect("find by name", () => + Effect.gen(function*() { + const sql = yield* seededClient + const FindByName = SqlResolver.grouped({ + Request: Schema.String, + RequestGroupKey: (name) => name, + Result: Schema.Struct({ id: Schema.Number, name: Schema.String }), + ResultGroupKey: (result) => result.name, + execute: (names) => sql`SELECT * FROM test WHERE name IN ${sql.in(names)}` + }) + yield* sql`INSERT INTO test ${sql.insert({ name: "name1" })}` + const execute = SqlResolver.request(FindByName) + assert.deepStrictEqual( + yield* Effect.all({ + one: execute("name1"), + two: execute("name2"), + three: Effect.flip(execute("name0")) + }, { concurrency: "unbounded" }), + { + one: [{ id: 1, name: "name1" }, { id: 101, name: "name1" }], + two: [{ id: 2, name: "name2" }], + three: new Cause.NoSuchElementError() + } + ) + })) + + it.effect("using raw rows", () => + Effect.gen(function*() { + const sql = yield* seededClient + const FindByName = SqlResolver.grouped({ + Request: Schema.String, + RequestGroupKey: (name) => name, + Result: Schema.Struct({ id: Schema.Number, name: Schema.String }), + ResultGroupKey: (_, result: any) => result.name, + execute: (names) => sql`SELECT * FROM test WHERE name IN ${sql.in(names)}` + }) + yield* sql`INSERT INTO test ${sql.insert({ name: "name1" })}` + const execute = SqlResolver.request(FindByName) + assert.deepStrictEqual( + yield* Effect.all({ + one: execute("name1"), + two: execute("name2"), + three: Effect.flip(execute("name0")) + }, { concurrency: "unbounded" }), + { + one: [{ id: 1, name: "name1" }, { id: 101, name: "name1" }], + two: [{ id: 2, name: "name2" }], + three: new Cause.NoSuchElementError() + } + ) + })) + }) + + describe.sequential("findById", () => { + it.effect("find by id", () => + Effect.gen(function*() { + const sql = yield* seededClient + const FindById = SqlResolver.findById({ + Id: Schema.Number, + Result: Schema.Struct({ id: Schema.Number, name: Schema.String }), + ResultId: (result) => result.id, + execute: (ids) => sql`SELECT * FROM test WHERE id IN ${sql.in(ids)}` + }) + const execute = SqlResolver.request(FindById) + assert.deepStrictEqual( + yield* Effect.all({ + one: execute(1), + two: execute(2), + three: Effect.flip(execute(101)) + }, { concurrency: "unbounded" }), + { + one: { id: 1, name: "name1" }, + two: { id: 2, name: "name2" }, + three: new Cause.NoSuchElementError() + } + ) + })) + + it.effect("using raw rows", () => + Effect.gen(function*() { + const sql = yield* seededClient + const FindById = SqlResolver.findById({ + Id: Schema.Number, + Result: Schema.Struct({ id: Schema.Number, name: Schema.String }), + ResultId: (_, result: any) => result.id, + execute: (ids) => sql`SELECT * FROM test WHERE id IN ${sql.in(ids)}` + }) + const execute = SqlResolver.request(FindById) + assert.deepStrictEqual( + yield* Effect.all({ + one: execute(1), + two: execute(2), + three: Effect.flip(execute(101)) + }, { concurrency: "unbounded" }), + { + one: { id: 1, name: "name1" }, + two: { id: 2, name: "name2" }, + three: new Cause.NoSuchElementError() + } + ) + })) + }) +}) diff --git a/repos/effect/packages/sql/libsql/test/Resolver.test.ts b/repos/effect/packages/sql/libsql/test/Resolver.test.ts deleted file mode 100644 index af8b19bc21..0000000000 --- a/repos/effect/packages/sql/libsql/test/Resolver.test.ts +++ /dev/null @@ -1,177 +0,0 @@ -import { LibsqlClient } from "@effect/sql-libsql" -import { assert, describe, layer } from "@effect/vitest" -import { Cause, Effect, Iterable } from "effect" -import * as Schema from "effect/Schema" -import { SqlError, SqlResolver } from "effect/unstable/sql" -import { LibsqlContainer } from "./util.ts" - -const seededClient = Effect.gen(function*() { - const sql = yield* LibsqlClient.LibsqlClient - yield* sql`CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT)` - for (const id of Iterable.range(1, 100)) { - yield* sql`INSERT INTO test ${sql.insert({ id, name: `name${id}` })}` - } - yield* Effect.addFinalizer(() => sql`DROP TABLE test;`.pipe(Effect.orDie)) - return sql -}) - -layer(LibsqlContainer.layerClient, { timeout: "30 seconds" })("Resolver", (it) => { - describe.sequential("ordered", () => { - it.effect("insert", () => - Effect.gen(function*() { - const batches: Array> = [] - const sql = yield* seededClient - const Insert = SqlResolver.ordered({ - Request: Schema.String, - Result: Schema.Struct({ id: Schema.Number, name: Schema.String }), - execute: (names) => { - batches.push(names) - return sql`INSERT INTO test ${sql.insert(names.map((name) => ({ name })))} RETURNING *` - } - }) - const execute = SqlResolver.request(Insert) - assert.deepStrictEqual( - yield* Effect.all({ - one: execute("one"), - two: execute("two") - }, { concurrency: "unbounded" }), - { - one: { id: 101, name: "one" }, - two: { id: 102, name: "two" } - } - ) - assert.deepStrictEqual(batches, [["one", "two"]]) - })) - - it.effect("result length mismatch", () => - Effect.gen(function*() { - const batches: Array> = [] - const sql = yield* seededClient - const Select = SqlResolver.ordered({ - Request: Schema.Number, - Result: Schema.Struct({ id: Schema.Number, name: Schema.String }), - execute: (ids) => { - batches.push(ids) - return sql`SELECT * FROM test WHERE id IN ${sql.in(ids)}` - } - }) - const execute = SqlResolver.request(Select) - const error = yield* Effect.all([ - execute(1), - execute(2), - execute(3), - execute(101) - ], { concurrency: "unbounded" }).pipe( - Effect.flip - ) - assert(error instanceof SqlError.ResultLengthMismatch) - assert.strictEqual(error.actual, 3) - assert.strictEqual(error.expected, 4) - assert.deepStrictEqual(batches, [[1, 2, 3, 101]]) - })) - }) - - describe.sequential("grouped", () => { - it.effect("find by name", () => - Effect.gen(function*() { - const sql = yield* seededClient - const FindByName = SqlResolver.grouped({ - Request: Schema.String, - RequestGroupKey: (name) => name, - Result: Schema.Struct({ id: Schema.Number, name: Schema.String }), - ResultGroupKey: (result) => result.name, - execute: (names) => sql`SELECT * FROM test WHERE name IN ${sql.in(names)}` - }) - yield* sql`INSERT INTO test ${sql.insert({ name: "name1" })}` - const execute = SqlResolver.request(FindByName) - assert.deepStrictEqual( - yield* Effect.all({ - one: execute("name1"), - two: execute("name2"), - three: Effect.flip(execute("name0")) - }, { concurrency: "unbounded" }), - { - one: [{ id: 1, name: "name1" }, { id: 101, name: "name1" }], - two: [{ id: 2, name: "name2" }], - three: new Cause.NoSuchElementError() - } - ) - })) - - it.effect("using raw rows", () => - Effect.gen(function*() { - const sql = yield* seededClient - const FindByName = SqlResolver.grouped({ - Request: Schema.String, - RequestGroupKey: (name) => name, - Result: Schema.Struct({ id: Schema.Number, name: Schema.String }), - ResultGroupKey: (_, result: any) => result.name, - execute: (names) => sql`SELECT * FROM test WHERE name IN ${sql.in(names)}` - }) - yield* sql`INSERT INTO test ${sql.insert({ name: "name1" })}` - const execute = SqlResolver.request(FindByName) - assert.deepStrictEqual( - yield* Effect.all({ - one: execute("name1"), - two: execute("name2"), - three: Effect.flip(execute("name0")) - }, { concurrency: "unbounded" }), - { - one: [{ id: 1, name: "name1" }, { id: 101, name: "name1" }], - two: [{ id: 2, name: "name2" }], - three: new Cause.NoSuchElementError() - } - ) - })) - }) - - describe.sequential("findById", () => { - it.effect("find by id", () => - Effect.gen(function*() { - const sql = yield* seededClient - const FindById = SqlResolver.findById({ - Id: Schema.Number, - Result: Schema.Struct({ id: Schema.Number, name: Schema.String }), - ResultId: (result) => result.id, - execute: (ids) => sql`SELECT * FROM test WHERE id IN ${sql.in(ids)}` - }) - const execute = SqlResolver.request(FindById) - assert.deepStrictEqual( - yield* Effect.all({ - one: execute(1), - two: execute(2), - three: Effect.flip(execute(101)) - }, { concurrency: "unbounded" }), - { - one: { id: 1, name: "name1" }, - two: { id: 2, name: "name2" }, - three: new Cause.NoSuchElementError() - } - ) - })) - - it.effect("using raw rows", () => - Effect.gen(function*() { - const sql = yield* seededClient - const FindById = SqlResolver.findById({ - Id: Schema.Number, - Result: Schema.Struct({ id: Schema.Number, name: Schema.String }), - ResultId: (_, result: any) => result.id, - execute: (ids) => sql`SELECT * FROM test WHERE id IN ${sql.in(ids)}` - }) - const execute = SqlResolver.request(FindById) - assert.deepStrictEqual( - yield* Effect.all({ - one: execute(1), - two: execute(2), - three: Effect.flip(execute(101)) - }, { concurrency: "unbounded" }), - { - one: { id: 1, name: "name1" }, - two: { id: 2, name: "name2" }, - three: new Cause.NoSuchElementError() - } - ) - })) - }) -}) diff --git a/repos/effect/packages/sql/libsql/tsconfig.json b/repos/effect/packages/sql/libsql/tsconfig.json index 739eb6b55d..f9e09522b0 100644 --- a/repos/effect/packages/sql/libsql/tsconfig.json +++ b/repos/effect/packages/sql/libsql/tsconfig.json @@ -1,5 +1,5 @@ { - "$schema": "http://json.schemastore.org/tsconfig", + "$schema": "https://json.schemastore.org/tsconfig", "extends": "../../../tsconfig.base.json", "include": ["src"], "references": [ diff --git a/repos/effect/packages/sql/libsql/vitest.config.ts b/repos/effect/packages/sql/libsql/vitest.config.ts deleted file mode 100644 index c8a52c1826..0000000000 --- a/repos/effect/packages/sql/libsql/vitest.config.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { mergeConfig, type ViteUserConfig } from "vitest/config" -import shared from "../../../vitest.shared.ts" - -const config: ViteUserConfig = {} - -export default mergeConfig(shared, config) diff --git a/repos/effect/packages/sql/mssql/CHANGELOG.md b/repos/effect/packages/sql/mssql/CHANGELOG.md index a5c30fa806..88c5a086be 100644 --- a/repos/effect/packages/sql/mssql/CHANGELOG.md +++ b/repos/effect/packages/sql/mssql/CHANGELOG.md @@ -1,5 +1,137 @@ # @effect/sql-mssql +## 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 + +- 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 + +- [#6999](https://github.com/Effect-TS/effect/pull/6999) [`e184f73`](https://github.com/Effect-TS/effect/commit/e184f7367e837623d1d3704224e6d27cec99faa1) Thanks @fubhy! - Cancel in-flight Tedious requests when their Effects are interrupted. + +- [#6995](https://github.com/Effect-TS/effect/pull/6995) [`6e7f2e2`](https://github.com/Effect-TS/effect/commit/6e7f2e2d1dacde69cbbbe1ba572aa5f52491dc33) Thanks @fubhy! - Return MSSQL procedure values through the output property. +- 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 + +- [#6766](https://github.com/Effect-TS/effect/pull/6766) [`f9ba412`](https://github.com/Effect-TS/effect/commit/f9ba41282e9ea207ddcf97fb0bcf369dd3c6a551) Thanks @tim-smart! - **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`. + +- [#6922](https://github.com/Effect-TS/effect/pull/6922) [`c53591f`](https://github.com/Effect-TS/effect/commit/c53591f2b02cfa2c6ff294b040ea45a8deb2df9c) Thanks @fubhy! - Preserve fractional numbers and Unicode strings in default Microsoft SQL Server 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 + +- [#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 + +- Updated dependencies [[`989603b`](https://github.com/Effect-TS/effect-smol/commit/989603b60ab1197b64acf214208e0d370cd1f842), [`214c458`](https://github.com/Effect-TS/effect-smol/commit/214c458084bb6995d543cd37d1055f24be3d454e), [`a037273`](https://github.com/Effect-TS/effect-smol/commit/a0372736ac34796969b051bbba4717d7983f1ebe), [`97fdaa9`](https://github.com/Effect-TS/effect-smol/commit/97fdaa9c1f522c65e579365d314a07878e2b904f), [`b24d248`](https://github.com/Effect-TS/effect-smol/commit/b24d248c8df44222ce642087cde2bd859a2dc709), [`19c222c`](https://github.com/Effect-TS/effect-smol/commit/19c222cac2353a3d7b7733caecb00556fffe9a5c), [`eec85dd`](https://github.com/Effect-TS/effect-smol/commit/eec85ddba09ea326fd268ee33eeffd47e50d4671), [`0082f4f`](https://github.com/Effect-TS/effect-smol/commit/0082f4f74fb139fd578f87f0a790e845133983dc), [`8849052`](https://github.com/Effect-TS/effect-smol/commit/884905232d1e9a365e046d8dde27bf9c5707f57f), [`c15e16a`](https://github.com/Effect-TS/effect-smol/commit/c15e16ad130d1fbde25d912b7ac55995066cb35b), [`01d00a3`](https://github.com/Effect-TS/effect-smol/commit/01d00a3abfbf1f37996cdbe738ea5137c646cdd7), [`8bd4589`](https://github.com/Effect-TS/effect-smol/commit/8bd458975a1b3a8ed042eccf317b93d28ded91e7), [`0082f4f`](https://github.com/Effect-TS/effect-smol/commit/0082f4f74fb139fd578f87f0a790e845133983dc), [`6e08428`](https://github.com/Effect-TS/effect-smol/commit/6e08428d980501b856f846ad3f3f0e4ea46e7786), [`388dcf9`](https://github.com/Effect-TS/effect-smol/commit/388dcf953f65d317547f34d40e6443c5f264205f), [`2b7ce2b`](https://github.com/Effect-TS/effect-smol/commit/2b7ce2b513e7ec2a77822f1116dc6ffb6ba93f4e), [`87bea7e`](https://github.com/Effect-TS/effect-smol/commit/87bea7e16259246f3bcdf565446394751abca953), [`ce38dc3`](https://github.com/Effect-TS/effect-smol/commit/ce38dc33bda805a684432cca071f4dc3c6b9a1ba), [`a807cd1`](https://github.com/Effect-TS/effect-smol/commit/a807cd170341deca8a1cfb52c4222585f2431bb9), [`fd8a356`](https://github.com/Effect-TS/effect-smol/commit/fd8a356f06a8c9ce4e7e0a13fc4021c178ed31de), [`c2a5edc`](https://github.com/Effect-TS/effect-smol/commit/c2a5edc3abd31ad5bc123362bc1213e03e4095c3), [`5946da3`](https://github.com/Effect-TS/effect-smol/commit/5946da3804a1be5e752b05b96bd058cdba50a1bf), [`4ae0c5f`](https://github.com/Effect-TS/effect-smol/commit/4ae0c5ffcbe6c56ddfcb05c639112a079483539e), [`5b2a0bc`](https://github.com/Effect-TS/effect-smol/commit/5b2a0bceea3a28a33a58555210c90a415dc74a76), [`72ac585`](https://github.com/Effect-TS/effect-smol/commit/72ac585884befde6af9208da738699a93f1bae79), [`5e8c1b8`](https://github.com/Effect-TS/effect-smol/commit/5e8c1b82bfafa121311f987a49ab75395e3647a7), [`0f9c078`](https://github.com/Effect-TS/effect-smol/commit/0f9c07841b04183f485ee6e6458de73b290b09f5)]: + - effect@4.0.0-beta.98 + +## 4.0.0-beta.97 + +### Patch Changes + +- Updated dependencies []: + - effect@4.0.0-beta.97 + +## 4.0.0-beta.96 + +### Patch Changes + +- Updated dependencies [[`1503f45`](https://github.com/Effect-TS/effect-smol/commit/1503f45cb5bb2a74f4705252ec505a1f0ade7e62), [`57fe793`](https://github.com/Effect-TS/effect-smol/commit/57fe79316ffbc380b30626a168981fb26ae97459), [`0c2f78f`](https://github.com/Effect-TS/effect-smol/commit/0c2f78f695ec474e1ff5474da183577975e418f5), [`0c2f78f`](https://github.com/Effect-TS/effect-smol/commit/0c2f78f695ec474e1ff5474da183577975e418f5), [`0c2f78f`](https://github.com/Effect-TS/effect-smol/commit/0c2f78f695ec474e1ff5474da183577975e418f5), [`97f29df`](https://github.com/Effect-TS/effect-smol/commit/97f29df457f7ffd07cfb4b379315c12c086af805)]: + - effect@4.0.0-beta.96 + +## 4.0.0-beta.95 + +### Patch Changes + +- Updated dependencies [[`a482442`](https://github.com/Effect-TS/effect-smol/commit/a482442abdeb490e9652b854ec3495e4aa7273e7), [`fbefa85`](https://github.com/Effect-TS/effect-smol/commit/fbefa850fab2f0a302c20614496aeaaa2a8b5590), [`0b4a32f`](https://github.com/Effect-TS/effect-smol/commit/0b4a32f4260f0d8500942a133001b0d349328102), [`18a49e1`](https://github.com/Effect-TS/effect-smol/commit/18a49e1786679456258002ff9397faf02f678c2d), [`266cb90`](https://github.com/Effect-TS/effect-smol/commit/266cb90bb2c17aabc40563c32db334f09ba3d74b), [`912f095`](https://github.com/Effect-TS/effect-smol/commit/912f095a34572bbd3cedf6edb27878443e3e4a95), [`a6718f9`](https://github.com/Effect-TS/effect-smol/commit/a6718f9e00a15ca903b0732da46116cbf3d6aca7), [`bef5154`](https://github.com/Effect-TS/effect-smol/commit/bef51540a243aa2f872a00c01d0cd58b7a769baa), [`18e0564`](https://github.com/Effect-TS/effect-smol/commit/18e0564bd0f8ebbdfcaf1e2c21529948e9e4a81d), [`fb50f14`](https://github.com/Effect-TS/effect-smol/commit/fb50f14fc3657c1973785aa5b72ecf0b0d28e0b2)]: + - effect@4.0.0-beta.95 + +## 4.0.0-beta.94 + +### Patch Changes + +- Updated dependencies [[`95a0e9b`](https://github.com/Effect-TS/effect-smol/commit/95a0e9bb62797af0e81c9998773405f248f218c5), [`a0a3490`](https://github.com/Effect-TS/effect-smol/commit/a0a3490bbce765f199d8e077aceac504f0462e63), [`f11ce73`](https://github.com/Effect-TS/effect-smol/commit/f11ce73af60823754dc24194f4ffc561b9ea1c2d), [`ff30b6e`](https://github.com/Effect-TS/effect-smol/commit/ff30b6e7c2c63ffc56a4c5818d6d86b01b5ad528), [`1caab3c`](https://github.com/Effect-TS/effect-smol/commit/1caab3cc30f626efbf15e59d74f539a487e5c85c), [`aa80c47`](https://github.com/Effect-TS/effect-smol/commit/aa80c4775a04db87553e5568764cab7e32a72814), [`c2ae4fc`](https://github.com/Effect-TS/effect-smol/commit/c2ae4fce2f03a4cd1861c2b1179da7df656e662d), [`a0a3490`](https://github.com/Effect-TS/effect-smol/commit/a0a3490bbce765f199d8e077aceac504f0462e63)]: + - effect@4.0.0-beta.94 + +## 4.0.0-beta.93 + +### Patch Changes + +- Updated dependencies [[`00652fe`](https://github.com/Effect-TS/effect-smol/commit/00652fe95c18f87208e91343eb8bf218faa2f677), [`6c58167`](https://github.com/Effect-TS/effect-smol/commit/6c5816746eaf91d2a3c7c899c5720809fa230ae3), [`2bc5415`](https://github.com/Effect-TS/effect-smol/commit/2bc541501a7ef89e542d7cb98e96beb53cd205cc), [`e11cccc`](https://github.com/Effect-TS/effect-smol/commit/e11cccc7d5fe631abccc7d6e3bd296938de0fa2e), [`ba7e77e`](https://github.com/Effect-TS/effect-smol/commit/ba7e77e046b8641a3a4e9750bb88ca4a1d063d3f), [`5713ee7`](https://github.com/Effect-TS/effect-smol/commit/5713ee7edbc3054efde407b2286bbfd45bbc6e1c)]: + - effect@4.0.0-beta.93 + +## 4.0.0-beta.92 + +### Patch Changes + +- Updated dependencies [[`affdc13`](https://github.com/Effect-TS/effect-smol/commit/affdc139045cc325dce321a84a580fdc1b2da7b9)]: + - effect@4.0.0-beta.92 + +## 4.0.0-beta.91 + +### Patch Changes + +- Updated dependencies [[`b135b25`](https://github.com/Effect-TS/effect-smol/commit/b135b2517fca9e7839734ace3699a7dfa75b9075), [`aaa21a3`](https://github.com/Effect-TS/effect-smol/commit/aaa21a369a171c600db294f2a4f640583043e150), [`3475ee6`](https://github.com/Effect-TS/effect-smol/commit/3475ee6c2bda6b05c6d7a12ce30c8bb840b5b1a6)]: + - effect@4.0.0-beta.91 + ## 4.0.0-beta.90 ### Patch Changes diff --git a/repos/effect/packages/sql/mssql/README.md b/repos/effect/packages/sql/mssql/README.md index 40a8f8bcbe..ef8f1f269b 100644 --- a/repos/effect/packages/sql/mssql/README.md +++ b/repos/effect/packages/sql/mssql/README.md @@ -1,6 +1,6 @@ # `@effect/sql-mssql` -An `@effect/sql` implementation using the mssql `tedious` library. +An Effect SQL implementation using the mssql `tedious` library. ## Documentation diff --git a/repos/effect/packages/sql/mssql/docgen.json b/repos/effect/packages/sql/mssql/docgen.json deleted file mode 100644 index 0f9590af28..0000000000 --- a/repos/effect/packages/sql/mssql/docgen.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "$schema": "../../node_modules/@effect/docgen/schema.json", - "srcLink": "https://github.com/Effect-TS/effect/tree/main/packages/sql/d1/src/", - "exclude": ["src/internal/**/*.ts"], - "tscExecutable": "tsgo", - "examplesCompilerOptions": { - "noEmit": true, - "strict": true, - "skipLibCheck": true, - "moduleResolution": "Bundler", - "module": "ES2022", - "target": "ES2022", - "lib": ["ES2022", "DOM", "DOM.Iterable"], - "rewriteRelativeImportExtensions": true, - "allowImportingTsExtensions": true, - "paths": { - "effect": ["../../../effect/src/index.js"], - "effect/*": ["../../../effect/src/*.js"] - }, - "plugins": [ - { "name": "@effect/language-service", "includeSuggestionsInTsc": false } - ] - } -} diff --git a/repos/effect/packages/sql/mssql/package.json b/repos/effect/packages/sql/mssql/package.json index e8bbc4efb3..ad79a1d615 100644 --- a/repos/effect/packages/sql/mssql/package.json +++ b/repos/effect/packages/sql/mssql/package.json @@ -1,17 +1,17 @@ { "name": "@effect/sql-mssql", - "version": "4.0.0-beta.90", + "version": "4.0.0-beta.107", "type": "module", "license": "MIT", "description": "A Microsoft SQL Server toolkit for Effect", "homepage": "https://effect.website", "repository": { "type": "git", - "url": "https://github.com/Effect-TS/effect-smol.git", + "url": "https://github.com/Effect-TS/effect.git", "directory": "packages/sql/mssql" }, "bugs": { - "url": "https://github.com/Effect-TS/effect-smol/issues" + "url": "https://github.com/Effect-TS/effect/issues" }, "tags": [ "typescript", @@ -29,6 +29,7 @@ ".": "./src/index.ts", "./*": "./src/*.ts", "./internal/*": null, + "./index": null, "./*/index": null }, "files": [ @@ -36,7 +37,10 @@ "dist/**/*.js", "dist/**/*.js.map", "dist/**/*.d.ts", - "dist/**/*.d.ts.map" + "dist/**/*.d.ts.map", + "AGENTS.md", + "CLAUDE.md", + "ai-docs/**/*" ], "publishConfig": { "access": "public", @@ -46,19 +50,18 @@ ".": "./dist/index.js", "./*": "./dist/*.js", "./internal/*": null, + "./index": null, "./*/index": null } }, "scripts": { "codegen": "effect-utils codegen", "build": "tsc -b tsconfig.json && pnpm babel", - "build:tsgo": "tsgo -b tsconfig.json && pnpm babel", "babel": "babel dist --plugins annotate-pure-calls --out-dir dist --source-maps", - "check": "tsc -b tsconfig.json", - "test": "vitest", - "coverage": "vitest --coverage" + "check": "tsc -b tsconfig.json" }, "devDependencies": { + "@testcontainers/mssqlserver": "^12.0.4", "effect": "workspace:^" }, "peerDependencies": { diff --git a/repos/effect/packages/sql/mssql/src/MssqlClient.ts b/repos/effect/packages/sql/mssql/src/MssqlClient.ts index d58c0f734e..481e0e7b49 100644 --- a/repos/effect/packages/sql/mssql/src/MssqlClient.ts +++ b/repos/effect/packages/sql/mssql/src/MssqlClient.ts @@ -19,6 +19,7 @@ import * as Effect from "effect/Effect" import { identity } from "effect/Function" import * as Layer from "effect/Layer" import * as Pool from "effect/Pool" +import * as Rec from "effect/Record" import * as Redacted from "effect/Redacted" import * as Scope from "effect/Scope" import * as Stream from "effect/Stream" @@ -159,7 +160,7 @@ export type TypeId = typeof TypeId /** * Microsoft SQL Server client service, extending `SqlClient` with typed parameter fragments and stored procedure calls. * - * @category models + * @category services * @since 4.0.0 */ export interface MssqlClient extends Client.SqlClient { @@ -205,7 +206,13 @@ export interface MssqlClientConfig { readonly domain?: string | undefined readonly server: string readonly instanceName?: string | undefined + /** + * Whether to encrypt traffic between the client and server. Defaults to `true`. Setting this to `false` disables transport encryption and transmits credentials in cleartext. + */ readonly encrypt?: boolean | undefined + /** + * Whether to trust the server certificate without validating it. Defaults to `false`. Setting this to `true` disables TLS certificate validation. + */ readonly trustServer?: boolean | undefined readonly port?: number | undefined readonly authType?: string | undefined @@ -283,7 +290,7 @@ export const make = ( options: { port: options.port, database: options.database, - trustServerCertificate: options.trustServer ?? true, + trustServerCertificate: options.trustServer ?? false, multiSubnetFailover: options.multiSubnetFailover, connectTimeout: options.connectTimeout ? Duration.toMillis(Duration.fromInputUnsafe(options.connectTimeout)) @@ -291,7 +298,7 @@ export const make = ( rowCollectionOnRequestCompletion: true, useColumnNames: false, instanceName: options.instanceName, - encrypt: options.encrypt ?? false, + encrypt: options.encrypt ?? true, cancelTimeout: options.cancelTimeout ? Duration.toMillis(Duration.fromInputUnsafe(options.cancelTimeout)) : undefined, @@ -366,6 +373,7 @@ export const make = ( conn.cancel() conn.execSql(req) + return Effect.sync(() => conn.cancel()) }) const runProcedure = ( @@ -389,7 +397,7 @@ export const make = ( } resume( Effect.succeed({ - params: result, + output: result, rows }) ) @@ -409,11 +417,12 @@ export const make = ( } req.on("returnValue", (name, value) => { - result[name] = value + Rec.assignProperty(result, name, value) }) conn.cancel() conn.callProcedure(req) + return Effect.sync(() => conn.cancel()) }) const connection = identity({ @@ -648,7 +657,7 @@ export const layer = ( /** * Creates the SQL Server statement compiler, using `@1`-style placeholders, bracket-escaped identifiers, and SQL Server `OUTPUT INSERTED` returning clauses. * - * @category compiler + * @category constructors * @since 4.0.0 */ export const makeCompiler = (transform?: (_: string) => string) => @@ -700,12 +709,12 @@ function numberToParamName(n: number) { /** * Default mapping from Effect SQL primitive value kinds to Tedious SQL Server parameter data types. * - * @category configuration + * @category constants * @since 4.0.0 */ export const defaultParameterTypes: Record = { - string: Tedious.TYPES.VarChar, - number: Tedious.TYPES.Int, + string: Tedious.TYPES.NVarChar, + number: Tedious.TYPES.Float, bigint: Tedious.TYPES.BigInt, boolean: Tedious.TYPES.Bit, Date: Tedious.TYPES.DateTime, @@ -738,7 +747,7 @@ function rowsToObjects(rows: ReadonlyArray) { const newRow: any = {} for (let j = 0, columnLen = row.length; j < columnLen; j++) { const column = row[j] - newRow[column.metadata.colName] = column.value + Rec.assignProperty(newRow, column.metadata.colName, column.value) } newRows[i] = newRow } diff --git a/repos/effect/packages/sql/mssql/src/MssqlMigrator.ts b/repos/effect/packages/sql/mssql/src/MssqlMigrator.ts index 2d947b9002..9b0e8a1abc 100644 --- a/repos/effect/packages/sql/mssql/src/MssqlMigrator.ts +++ b/repos/effect/packages/sql/mssql/src/MssqlMigrator.ts @@ -23,7 +23,7 @@ export * from "effect/unstable/sql/Migrator" /** * Runs SQL migrations using the configured `SqlClient`, returning the migrations that were applied. * - * @category constructors + * @category running * @since 4.0.0 */ export const run: ( diff --git a/repos/effect/packages/sql/mssql/test/Client.test.ts b/repos/effect/packages/sql/mssql/test/Client.test.ts index 41a6d37b70..6618bb1354 100644 --- a/repos/effect/packages/sql/mssql/test/Client.test.ts +++ b/repos/effect/packages/sql/mssql/test/Client.test.ts @@ -1,11 +1,85 @@ -import { MssqlClient } from "@effect/sql-mssql" -import { describe, expect, it } from "@effect/vitest" -import { Effect } from "effect" +import { MssqlClient, Procedure } from "@effect/sql-mssql" +import { assert, describe, expect, it } from "@effect/vitest" +import { Effect, Fiber } from "effect" +import * as Reactivity from "effect/unstable/reactivity/Reactivity" import * as Statement from "effect/unstable/sql/Statement" +import type * as Tedious from "tedious" +import { vi } from "vitest" + +const state = vi.hoisted(() => ({ cancelCalls: 0, completeRequests: true, type: {} })) + +vi.mock("tedious", async (importOriginal) => { + const original = await importOriginal() + + class MockRequest { + readonly listeners: Record) => void> = {} + + constructor( + readonly sql: string, + readonly callback: (cause: unknown, rowCount: number, rows: ReadonlyArray) => void + ) {} + + addParameter() {} + addOutputParameter() {} + on(event: string, listener: (...args: Array) => void) { + this.listeners[event] = listener + } + } + + class MockConnection { + connect(callback: (cause: unknown) => void) { + callback(null) + } + close() {} + on() {} + cancel() { + state.cancelCalls++ + } + execSql(request: MockRequest) { + if (state.completeRequests) { + request.callback(null, 0, []) + } + } + callProcedure(request: MockRequest) { + request.listeners.returnValue("answer", 42) + request.callback(null, 0, []) + } + beginTransaction(callback: (cause: unknown) => void) { + callback(null) + } + commitTransaction(callback: (cause: unknown) => void) { + callback(null) + } + saveTransaction(callback: (cause: unknown) => void) { + callback(null) + } + rollbackTransaction(callback: (cause: unknown) => void) { + callback(null) + } + } + + return { + ...original, + Connection: MockConnection, + Request: MockRequest + } +}) const sql = Statement.make(Effect.void as any, MssqlClient.makeCompiler(), [], undefined) describe("mssql", () => { + it("preserves fractional JavaScript numbers with the default parameter mapping", () => { + const value = MssqlClient.defaultParameterTypes.number.validate(1.5, undefined) + + expect(value).toBe(1.5) + }) + + it("preserves Unicode JavaScript strings with the default parameter mapping", () => { + const value = MssqlClient.defaultParameterTypes.string.validate("lambda: \u03bb", undefined) + + expect(value).toBe("lambda: \u03bb") + }) + it("insert helper", () => { const [query, params] = sql`INSERT INTO ${sql("people")} ${sql.insert({ name: "Tim", age: 10 })}`.compile() expect(query).toEqual( @@ -88,4 +162,33 @@ describe("mssql", () => { const [query] = sql`SELECT * FROM ${sql("peo[]ple.te[st]ing")}`.compile() expect(query).toEqual(`SELECT * FROM [peo[]]ple].[te[st]]ing]`) }) + + it.effect("returns stored procedure output parameters under output", () => + Effect.gen(function*() { + const client = yield* MssqlClient.make({ server: "localhost" }) + const definition = Procedure.outputParam()("answer", state.type as any)(Procedure.make("get_answer")) + const result = yield* client.call(Procedure.compile(definition)({})) + + assert.deepStrictEqual(result, { output: { answer: 42 }, rows: [] }) + }).pipe( + Effect.scoped, + Effect.provide(Reactivity.layer) + )) + + it.effect("cancels an in-flight Tedious request when interrupted", () => + Effect.gen(function*() { + state.cancelCalls = 0 + state.completeRequests = true + const client = yield* MssqlClient.make({ server: "localhost" }) + state.completeRequests = false + const fiber = yield* Effect.forkChild(client`WAITFOR DELAY '00:01:00'`) + yield* Effect.yieldNow + const callsBeforeInterrupt = state.cancelCalls + yield* Fiber.interrupt(fiber) + + assert.strictEqual(state.cancelCalls, callsBeforeInterrupt + 1) + }).pipe( + Effect.scoped, + Effect.provide(Reactivity.layer) + )) }) diff --git a/repos/effect/packages/sql/mssql/test/Persistence.integration.test.ts b/repos/effect/packages/sql/mssql/test/Persistence.integration.test.ts new file mode 100644 index 0000000000..7a39ac8fa4 --- /dev/null +++ b/repos/effect/packages/sql/mssql/test/Persistence.integration.test.ts @@ -0,0 +1,14 @@ +import { Layer } from "effect" +import * as PersistedCacheTest from "effect-test/unstable/persistence/PersistedCacheTest" +import { Persistence } from "effect/unstable/persistence" +import { MssqlContainer } from "./utils.ts" + +PersistedCacheTest.suite( + "sql-mssql-multi", + Persistence.layerSqlMultiTable.pipe(Layer.provide(MssqlContainer.layerClient)) +) + +PersistedCacheTest.suite( + "sql-mssql-single", + Persistence.layerSql.pipe(Layer.provide(MssqlContainer.layerClient)) +) diff --git a/repos/effect/packages/sql/mssql/test/utils.ts b/repos/effect/packages/sql/mssql/test/utils.ts new file mode 100644 index 0000000000..2941f0bd34 --- /dev/null +++ b/repos/effect/packages/sql/mssql/test/utils.ts @@ -0,0 +1,36 @@ +import { MssqlClient } from "@effect/sql-mssql" +import { MSSQLServerContainer } from "@testcontainers/mssqlserver" +import { Context, Data, Effect, Layer, Redacted } from "effect" + +export class ContainerError extends Data.TaggedError("ContainerError")<{ + cause: unknown +}> {} + +export class MssqlContainer extends Context.Service()("test/MssqlContainer", { + make: Effect.acquireRelease( + Effect.tryPromise({ + try: () => + new MSSQLServerContainer("mcr.microsoft.com/mssql/server:2022-latest") + .acceptLicense() + .start(), + catch: (cause) => new ContainerError({ cause }) + }), + (container) => Effect.promise(() => container.stop()) + ) +}) { + static readonly layer = Layer.effect(this)(this.make) + + static layerClient = Layer.unwrap( + Effect.gen(function*() { + const container = yield* MssqlContainer + return MssqlClient.layer({ + server: container.getHost(), + port: container.getPort(), + database: container.getDatabase(), + username: container.getUsername(), + password: Redacted.make(container.getPassword()), + trustServer: true + }) + }) + ).pipe(Layer.provide(this.layer)) +} diff --git a/repos/effect/packages/sql/mssql/tsconfig.json b/repos/effect/packages/sql/mssql/tsconfig.json index 19a2f5dbca..e2a8ca19a0 100644 --- a/repos/effect/packages/sql/mssql/tsconfig.json +++ b/repos/effect/packages/sql/mssql/tsconfig.json @@ -1,5 +1,5 @@ { - "$schema": "http://json.schemastore.org/tsconfig", + "$schema": "https://json.schemastore.org/tsconfig", "extends": "../../../tsconfig.base.json", "include": ["src"], "references": [ diff --git a/repos/effect/packages/sql/mssql/vitest.config.ts b/repos/effect/packages/sql/mssql/vitest.config.ts deleted file mode 100644 index c8a52c1826..0000000000 --- a/repos/effect/packages/sql/mssql/vitest.config.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { mergeConfig, type ViteUserConfig } from "vitest/config" -import shared from "../../../vitest.shared.ts" - -const config: ViteUserConfig = {} - -export default mergeConfig(shared, config) diff --git a/repos/effect/packages/sql/mysql2/CHANGELOG.md b/repos/effect/packages/sql/mysql2/CHANGELOG.md index 4d8ecaea97..22f7d6df66 100644 --- a/repos/effect/packages/sql/mysql2/CHANGELOG.md +++ b/repos/effect/packages/sql/mysql2/CHANGELOG.md @@ -1,5 +1,128 @@ # @effect/sql-mysql2 +## 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 + +- 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 + +- [#6510](https://github.com/Effect-TS/effect/pull/6510) [`a808666`](https://github.com/Effect-TS/effect/commit/a80866655f414888336eb0f1bdd03dfdf1a742b8) Thanks @sam-goodwin! - Add `disablePreparedStatements` to `MysqlClientConfig`, to completely disable prepared statements + +- Updated dependencies [[`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`b35ed29`](https://github.com/Effect-TS/effect/commit/b35ed2904f01536d303b21f288daf343cf740462), [`dd44624`](https://github.com/Effect-TS/effect/commit/dd446245736a0e88c807a02f03c21450bb9340fa), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`2bae1ac`](https://github.com/Effect-TS/effect/commit/2bae1accce9d3b72cf6d5aefc9b2161af6d88436)]: + - effect@4.0.0-beta.101 + +## 4.0.0-beta.100 + +### Patch Changes + +- Updated dependencies [[`c1288dd`](https://github.com/Effect-TS/effect/commit/c1288dd1a52a2811ab7df57fc4ce236c6be4c745), [`2b58a3d`](https://github.com/Effect-TS/effect/commit/2b58a3dab6bc99776dddaf76e27d811e0f47f3d8), [`6dc83f2`](https://github.com/Effect-TS/effect/commit/6dc83f26ddf20d48db28cf761dd8f3716e5273fb), [`c1e2fe0`](https://github.com/Effect-TS/effect/commit/c1e2fe0cf93564f4d919e3998874c3e70b0cf30f), [`f3fbae8`](https://github.com/Effect-TS/effect/commit/f3fbae8d7bae0d77cb4f35a1598b26c58e3bf94d), [`e000f80`](https://github.com/Effect-TS/effect/commit/e000f80fd55bcd8edc699fdbf4cd109004f4f754), [`f4ee765`](https://github.com/Effect-TS/effect/commit/f4ee7655ee052cf9ba726fd602bb87c89c7c62a9), [`510b55f`](https://github.com/Effect-TS/effect/commit/510b55f3e21750685dbfd5f476a130c1c5af9dbd), [`31d3fc4`](https://github.com/Effect-TS/effect/commit/31d3fc4327c50867bb8d881fa7353aeb03ea2826), [`875e618`](https://github.com/Effect-TS/effect/commit/875e618c3764a7b817ac863d0af86924449528f2), [`688d46a`](https://github.com/Effect-TS/effect/commit/688d46afd0ef923d983ad3d7385f52f217b28d70), [`6ff5023`](https://github.com/Effect-TS/effect/commit/6ff502363b9840a5a5ee0a24bc6cae734ac3a3eb), [`c0333e7`](https://github.com/Effect-TS/effect/commit/c0333e7f755f42ddcca7051e029da8b4eed527bf), [`06e7e8c`](https://github.com/Effect-TS/effect/commit/06e7e8c66015ee318f871b9d2218dee82df2b108), [`eb9b102`](https://github.com/Effect-TS/effect/commit/eb9b10256c8558881b441c2fef833b7037174400), [`8b155da`](https://github.com/Effect-TS/effect/commit/8b155da06e0740c354ec562957a45ab65eb4573b), [`3a87335`](https://github.com/Effect-TS/effect/commit/3a8733564c5db35271aa20564ed0d344daa2a79f)]: + - effect@4.0.0-beta.100 + +## 4.0.0-beta.99 + +### Patch Changes + +- Updated dependencies [[`8ce4795`](https://github.com/Effect-TS/effect/commit/8ce4795ccbaebca4292757db568c005a992546a4), [`80b539f`](https://github.com/Effect-TS/effect/commit/80b539f8aba68f478c75c35c2b4140c4ffc4fada), [`88a54cc`](https://github.com/Effect-TS/effect/commit/88a54cc341006e3ebcb13482c618f62a680ce199), [`e6e6dba`](https://github.com/Effect-TS/effect/commit/e6e6dba6e9d86e7c2ad27dcedf289db76a19697f), [`bfb203e`](https://github.com/Effect-TS/effect/commit/bfb203e95aa439f731acad37fc3a9a831a190f1c), [`2e9a34a`](https://github.com/Effect-TS/effect/commit/2e9a34ac2bece4f3a206160480c991e3841dc67a), [`55d4eb3`](https://github.com/Effect-TS/effect/commit/55d4eb34f2c64d54f6a25a305b5c5438ebd7934e), [`bddb010`](https://github.com/Effect-TS/effect/commit/bddb010eac3d4436cb094edbbee7460c5440c162), [`a328835`](https://github.com/Effect-TS/effect/commit/a328835e50d76bc96648a1c1550456e8c9f81210), [`5560d05`](https://github.com/Effect-TS/effect/commit/5560d05aa6abdd29466d9c3412cc5e648b0adbde), [`8f6e3ad`](https://github.com/Effect-TS/effect/commit/8f6e3adb185b16e8820b98c509b308086f7ff1af), [`46997fa`](https://github.com/Effect-TS/effect/commit/46997fa60401f5e3c93daa4b61f7df8e31caaab4), [`9e6e12d`](https://github.com/Effect-TS/effect/commit/9e6e12d75c118cd265496f2880490d1f33a5c8bf), [`3394b93`](https://github.com/Effect-TS/effect/commit/3394b93d97d6f24fc38670641d1490289ffca7f1), [`febeabc`](https://github.com/Effect-TS/effect/commit/febeabc3f7c31094da000a23edeaabfe2ab00a38), [`54161c9`](https://github.com/Effect-TS/effect/commit/54161c98f6f3569e0c31842f54e6a257f9421c4c), [`385f7a4`](https://github.com/Effect-TS/effect/commit/385f7a4ee4a7359928597ea56d151dbaf5eb5802), [`7eea4d0`](https://github.com/Effect-TS/effect/commit/7eea4d0b73ec554915d7066a71f46326ce2ba45f), [`7543afe`](https://github.com/Effect-TS/effect/commit/7543afea6f4d97d1f1ad876224323838a48daadd), [`44b9cf3`](https://github.com/Effect-TS/effect/commit/44b9cf3d240d726997b4bbcd0ede48e074d3c456), [`7eea4d0`](https://github.com/Effect-TS/effect/commit/7eea4d0b73ec554915d7066a71f46326ce2ba45f), [`0a8aa6a`](https://github.com/Effect-TS/effect/commit/0a8aa6acb90a72b91c24d17133c950e4cacd8abd), [`c8d9fcf`](https://github.com/Effect-TS/effect/commit/c8d9fcf7b030f7c474effbab2764ce7aee1c7209), [`9ca7f9a`](https://github.com/Effect-TS/effect/commit/9ca7f9a69363e4485645966d5a93b8f9597c5206), [`e7aca89`](https://github.com/Effect-TS/effect/commit/e7aca894bb32fbb785b5830837e6061c415a6015), [`55d7560`](https://github.com/Effect-TS/effect/commit/55d75609b8acf8a1b54c1b1c7fbbb65ec741aa3e), [`f809189`](https://github.com/Effect-TS/effect/commit/f809189ddf6b6011ba43a9901baaa734e315da2a), [`88a54cc`](https://github.com/Effect-TS/effect/commit/88a54cc341006e3ebcb13482c618f62a680ce199), [`0ebdbe7`](https://github.com/Effect-TS/effect/commit/0ebdbe74463dc84385956d0b1e8c2b79ebab5400), [`7517d09`](https://github.com/Effect-TS/effect/commit/7517d09f12a0b183a81bd425962c4e280a68b05d), [`212493b`](https://github.com/Effect-TS/effect/commit/212493b9a1eb98cd1ef6959c707a2e5784a5ae91), [`88a54cc`](https://github.com/Effect-TS/effect/commit/88a54cc341006e3ebcb13482c618f62a680ce199), [`80ea8cb`](https://github.com/Effect-TS/effect/commit/80ea8cb9222ca73f564c8267ab2f82966fea027a), [`8df19f4`](https://github.com/Effect-TS/effect/commit/8df19f4fe81d90cc33ace88b9a77e5534f82d604)]: + - effect@4.0.0-beta.99 + +## 4.0.0-beta.98 + +### Patch Changes + +- Updated dependencies [[`989603b`](https://github.com/Effect-TS/effect-smol/commit/989603b60ab1197b64acf214208e0d370cd1f842), [`214c458`](https://github.com/Effect-TS/effect-smol/commit/214c458084bb6995d543cd37d1055f24be3d454e), [`a037273`](https://github.com/Effect-TS/effect-smol/commit/a0372736ac34796969b051bbba4717d7983f1ebe), [`97fdaa9`](https://github.com/Effect-TS/effect-smol/commit/97fdaa9c1f522c65e579365d314a07878e2b904f), [`b24d248`](https://github.com/Effect-TS/effect-smol/commit/b24d248c8df44222ce642087cde2bd859a2dc709), [`19c222c`](https://github.com/Effect-TS/effect-smol/commit/19c222cac2353a3d7b7733caecb00556fffe9a5c), [`eec85dd`](https://github.com/Effect-TS/effect-smol/commit/eec85ddba09ea326fd268ee33eeffd47e50d4671), [`0082f4f`](https://github.com/Effect-TS/effect-smol/commit/0082f4f74fb139fd578f87f0a790e845133983dc), [`8849052`](https://github.com/Effect-TS/effect-smol/commit/884905232d1e9a365e046d8dde27bf9c5707f57f), [`c15e16a`](https://github.com/Effect-TS/effect-smol/commit/c15e16ad130d1fbde25d912b7ac55995066cb35b), [`01d00a3`](https://github.com/Effect-TS/effect-smol/commit/01d00a3abfbf1f37996cdbe738ea5137c646cdd7), [`8bd4589`](https://github.com/Effect-TS/effect-smol/commit/8bd458975a1b3a8ed042eccf317b93d28ded91e7), [`0082f4f`](https://github.com/Effect-TS/effect-smol/commit/0082f4f74fb139fd578f87f0a790e845133983dc), [`6e08428`](https://github.com/Effect-TS/effect-smol/commit/6e08428d980501b856f846ad3f3f0e4ea46e7786), [`388dcf9`](https://github.com/Effect-TS/effect-smol/commit/388dcf953f65d317547f34d40e6443c5f264205f), [`2b7ce2b`](https://github.com/Effect-TS/effect-smol/commit/2b7ce2b513e7ec2a77822f1116dc6ffb6ba93f4e), [`87bea7e`](https://github.com/Effect-TS/effect-smol/commit/87bea7e16259246f3bcdf565446394751abca953), [`ce38dc3`](https://github.com/Effect-TS/effect-smol/commit/ce38dc33bda805a684432cca071f4dc3c6b9a1ba), [`a807cd1`](https://github.com/Effect-TS/effect-smol/commit/a807cd170341deca8a1cfb52c4222585f2431bb9), [`fd8a356`](https://github.com/Effect-TS/effect-smol/commit/fd8a356f06a8c9ce4e7e0a13fc4021c178ed31de), [`c2a5edc`](https://github.com/Effect-TS/effect-smol/commit/c2a5edc3abd31ad5bc123362bc1213e03e4095c3), [`5946da3`](https://github.com/Effect-TS/effect-smol/commit/5946da3804a1be5e752b05b96bd058cdba50a1bf), [`4ae0c5f`](https://github.com/Effect-TS/effect-smol/commit/4ae0c5ffcbe6c56ddfcb05c639112a079483539e), [`5b2a0bc`](https://github.com/Effect-TS/effect-smol/commit/5b2a0bceea3a28a33a58555210c90a415dc74a76), [`72ac585`](https://github.com/Effect-TS/effect-smol/commit/72ac585884befde6af9208da738699a93f1bae79), [`5e8c1b8`](https://github.com/Effect-TS/effect-smol/commit/5e8c1b82bfafa121311f987a49ab75395e3647a7), [`0f9c078`](https://github.com/Effect-TS/effect-smol/commit/0f9c07841b04183f485ee6e6458de73b290b09f5)]: + - effect@4.0.0-beta.98 + +## 4.0.0-beta.97 + +### Patch Changes + +- Updated dependencies []: + - effect@4.0.0-beta.97 + +## 4.0.0-beta.96 + +### Patch Changes + +- Updated dependencies [[`1503f45`](https://github.com/Effect-TS/effect-smol/commit/1503f45cb5bb2a74f4705252ec505a1f0ade7e62), [`57fe793`](https://github.com/Effect-TS/effect-smol/commit/57fe79316ffbc380b30626a168981fb26ae97459), [`0c2f78f`](https://github.com/Effect-TS/effect-smol/commit/0c2f78f695ec474e1ff5474da183577975e418f5), [`0c2f78f`](https://github.com/Effect-TS/effect-smol/commit/0c2f78f695ec474e1ff5474da183577975e418f5), [`0c2f78f`](https://github.com/Effect-TS/effect-smol/commit/0c2f78f695ec474e1ff5474da183577975e418f5), [`97f29df`](https://github.com/Effect-TS/effect-smol/commit/97f29df457f7ffd07cfb4b379315c12c086af805)]: + - effect@4.0.0-beta.96 + +## 4.0.0-beta.95 + +### Patch Changes + +- Updated dependencies [[`a482442`](https://github.com/Effect-TS/effect-smol/commit/a482442abdeb490e9652b854ec3495e4aa7273e7), [`fbefa85`](https://github.com/Effect-TS/effect-smol/commit/fbefa850fab2f0a302c20614496aeaaa2a8b5590), [`0b4a32f`](https://github.com/Effect-TS/effect-smol/commit/0b4a32f4260f0d8500942a133001b0d349328102), [`18a49e1`](https://github.com/Effect-TS/effect-smol/commit/18a49e1786679456258002ff9397faf02f678c2d), [`266cb90`](https://github.com/Effect-TS/effect-smol/commit/266cb90bb2c17aabc40563c32db334f09ba3d74b), [`912f095`](https://github.com/Effect-TS/effect-smol/commit/912f095a34572bbd3cedf6edb27878443e3e4a95), [`a6718f9`](https://github.com/Effect-TS/effect-smol/commit/a6718f9e00a15ca903b0732da46116cbf3d6aca7), [`bef5154`](https://github.com/Effect-TS/effect-smol/commit/bef51540a243aa2f872a00c01d0cd58b7a769baa), [`18e0564`](https://github.com/Effect-TS/effect-smol/commit/18e0564bd0f8ebbdfcaf1e2c21529948e9e4a81d), [`fb50f14`](https://github.com/Effect-TS/effect-smol/commit/fb50f14fc3657c1973785aa5b72ecf0b0d28e0b2)]: + - effect@4.0.0-beta.95 + +## 4.0.0-beta.94 + +### Patch Changes + +- Updated dependencies [[`95a0e9b`](https://github.com/Effect-TS/effect-smol/commit/95a0e9bb62797af0e81c9998773405f248f218c5), [`a0a3490`](https://github.com/Effect-TS/effect-smol/commit/a0a3490bbce765f199d8e077aceac504f0462e63), [`f11ce73`](https://github.com/Effect-TS/effect-smol/commit/f11ce73af60823754dc24194f4ffc561b9ea1c2d), [`ff30b6e`](https://github.com/Effect-TS/effect-smol/commit/ff30b6e7c2c63ffc56a4c5818d6d86b01b5ad528), [`1caab3c`](https://github.com/Effect-TS/effect-smol/commit/1caab3cc30f626efbf15e59d74f539a487e5c85c), [`aa80c47`](https://github.com/Effect-TS/effect-smol/commit/aa80c4775a04db87553e5568764cab7e32a72814), [`c2ae4fc`](https://github.com/Effect-TS/effect-smol/commit/c2ae4fce2f03a4cd1861c2b1179da7df656e662d), [`a0a3490`](https://github.com/Effect-TS/effect-smol/commit/a0a3490bbce765f199d8e077aceac504f0462e63)]: + - effect@4.0.0-beta.94 + +## 4.0.0-beta.93 + +### Patch Changes + +- Updated dependencies [[`00652fe`](https://github.com/Effect-TS/effect-smol/commit/00652fe95c18f87208e91343eb8bf218faa2f677), [`6c58167`](https://github.com/Effect-TS/effect-smol/commit/6c5816746eaf91d2a3c7c899c5720809fa230ae3), [`2bc5415`](https://github.com/Effect-TS/effect-smol/commit/2bc541501a7ef89e542d7cb98e96beb53cd205cc), [`e11cccc`](https://github.com/Effect-TS/effect-smol/commit/e11cccc7d5fe631abccc7d6e3bd296938de0fa2e), [`ba7e77e`](https://github.com/Effect-TS/effect-smol/commit/ba7e77e046b8641a3a4e9750bb88ca4a1d063d3f), [`5713ee7`](https://github.com/Effect-TS/effect-smol/commit/5713ee7edbc3054efde407b2286bbfd45bbc6e1c)]: + - effect@4.0.0-beta.93 + +## 4.0.0-beta.92 + +### Patch Changes + +- Updated dependencies [[`affdc13`](https://github.com/Effect-TS/effect-smol/commit/affdc139045cc325dce321a84a580fdc1b2da7b9)]: + - effect@4.0.0-beta.92 + +## 4.0.0-beta.91 + +### Patch Changes + +- Updated dependencies [[`b135b25`](https://github.com/Effect-TS/effect-smol/commit/b135b2517fca9e7839734ace3699a7dfa75b9075), [`aaa21a3`](https://github.com/Effect-TS/effect-smol/commit/aaa21a369a171c600db294f2a4f640583043e150), [`3475ee6`](https://github.com/Effect-TS/effect-smol/commit/3475ee6c2bda6b05c6d7a12ce30c8bb840b5b1a6)]: + - effect@4.0.0-beta.91 + ## 4.0.0-beta.90 ### Patch Changes diff --git a/repos/effect/packages/sql/mysql2/README.md b/repos/effect/packages/sql/mysql2/README.md index 9b4f3206c9..074afd269a 100644 --- a/repos/effect/packages/sql/mysql2/README.md +++ b/repos/effect/packages/sql/mysql2/README.md @@ -1,6 +1,6 @@ # `@effect/sql-mysql2` -An `@effect/sql` implementation using the `mysql2` library. +An Effect SQL implementation using the `mysql2` library. ## Documentation diff --git a/repos/effect/packages/sql/mysql2/docgen.json b/repos/effect/packages/sql/mysql2/docgen.json deleted file mode 100644 index 5a040acd1c..0000000000 --- a/repos/effect/packages/sql/mysql2/docgen.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "$schema": "../../node_modules/@effect/docgen/schema.json", - "srcLink": "https://github.com/Effect-TS/effect/tree/main/packages/sql/mysql2/src/", - "exclude": ["src/internal/**/*.ts"], - "tscExecutable": "tsgo", - "examplesCompilerOptions": { - "noEmit": true, - "strict": true, - "skipLibCheck": true, - "moduleResolution": "Bundler", - "module": "ES2022", - "target": "ES2022", - "lib": ["ES2022", "DOM", "DOM.Iterable"], - "rewriteRelativeImportExtensions": true, - "allowImportingTsExtensions": true, - "paths": { - "effect": ["../../../effect/src/index.js"], - "effect/*": ["../../../effect/src/*.js"] - }, - "plugins": [ - { "name": "@effect/language-service", "includeSuggestionsInTsc": false } - ] - } -} diff --git a/repos/effect/packages/sql/mysql2/package.json b/repos/effect/packages/sql/mysql2/package.json index 6dc2389fe9..865ae14fd6 100644 --- a/repos/effect/packages/sql/mysql2/package.json +++ b/repos/effect/packages/sql/mysql2/package.json @@ -1,17 +1,17 @@ { "name": "@effect/sql-mysql2", - "version": "4.0.0-beta.90", + "version": "4.0.0-beta.107", "type": "module", "license": "MIT", "description": "A MySQL toolkit for Effect", "homepage": "https://effect.website", "repository": { "type": "git", - "url": "https://github.com/Effect-TS/effect-smol.git", + "url": "https://github.com/Effect-TS/effect.git", "directory": "packages/sql/mysql2" }, "bugs": { - "url": "https://github.com/Effect-TS/effect-smol/issues" + "url": "https://github.com/Effect-TS/effect/issues" }, "tags": [ "typescript", @@ -29,6 +29,7 @@ ".": "./src/index.ts", "./*": "./src/*.ts", "./internal/*": null, + "./index": null, "./*/index": null }, "files": [ @@ -36,7 +37,10 @@ "dist/**/*.js", "dist/**/*.js.map", "dist/**/*.d.ts", - "dist/**/*.d.ts.map" + "dist/**/*.d.ts.map", + "AGENTS.md", + "CLAUDE.md", + "ai-docs/**/*" ], "publishConfig": { "access": "public", @@ -46,26 +50,24 @@ ".": "./dist/index.js", "./*": "./dist/*.js", "./internal/*": null, + "./index": null, "./*/index": null } }, "scripts": { "codegen": "effect-utils codegen", "build": "tsc -b tsconfig.json && pnpm babel", - "build:tsgo": "tsgo -b tsconfig.json && pnpm babel", "babel": "babel dist --plugins annotate-pure-calls --out-dir dist --source-maps", - "check": "tsc -b tsconfig.json", - "test": "vitest", - "coverage": "vitest --coverage" + "check": "tsc -b tsconfig.json" }, "devDependencies": { - "@testcontainers/mysql": "^11.14.0", + "@testcontainers/mysql": "^12.0.4", "effect": "workspace:^" }, "peerDependencies": { "effect": "workspace:^" }, "dependencies": { - "mysql2": "^3.22.3" + "mysql2": "^3.22.6" } } diff --git a/repos/effect/packages/sql/mysql2/src/MysqlClient.ts b/repos/effect/packages/sql/mysql2/src/MysqlClient.ts index 928f6c7254..afd682f66c 100644 --- a/repos/effect/packages/sql/mysql2/src/MysqlClient.ts +++ b/repos/effect/packages/sql/mysql2/src/MysqlClient.ts @@ -153,7 +153,7 @@ export type TypeId = "~@effect/sql-mysql2/MysqlClient" /** * mysql2-backed SQL client service, extending `SqlClient` with its runtime type marker and client configuration. * - * @category models + * @category services * @since 4.0.0 */ export interface MysqlClient extends Client.SqlClient { @@ -196,6 +196,12 @@ export interface MysqlClientConfig { readonly poolConfig?: Mysql.PoolOptions | undefined + /** + * Use the text protocol instead of prepared statements, for proxies like + * Cloudflare Hyperdrive that do not support `COM_STMT_PREPARE`. + */ + readonly disablePreparedStatements?: boolean | undefined + readonly spanAttributes?: Record | undefined readonly transformResultNames?: ((str: string) => string) | undefined @@ -218,6 +224,7 @@ export const make = ( options.transformResultNames ).array : undefined + const defaultMethod: "execute" | "query" = options.disablePreparedStatements === true ? "query" : "execute" class ConnectionImpl implements Connection { readonly conn: Mysql.PoolConnection | Mysql.Pool @@ -229,7 +236,7 @@ export const make = ( sql: string, values?: ReadonlyArray, rowsAsArray = false, - method: "execute" | "query" = "execute" + method: "execute" | "query" = defaultMethod ) { return Effect.callback((resume) => { const operation = method === "query" ? "executeUnprepared" : "execute" @@ -253,7 +260,7 @@ export const make = ( sql: string, values?: ReadonlyArray, rowsAsArray = false, - method: "execute" | "query" = "execute" + method: "execute" | "query" = defaultMethod ) { return this.runRaw(sql, values, rowsAsArray, method).pipe( Effect.map((results) => Array.isArray(results) ? results : []) @@ -320,10 +327,10 @@ export const make = ( : undefined, multipleStatements: true, supportBigNumbers: true, - connectionLimit: options.maxConnections, + connectionLimit: options.maxConnections ?? options.poolConfig?.connectionLimit, idleTimeout: options.connectionTTL ? Duration.toMillis(Duration.fromInputUnsafe(options.connectionTTL)) - : undefined + : options.poolConfig?.idleTimeout } as Mysql.PoolOptions) yield* Effect.acquireRelease( @@ -448,7 +455,7 @@ export const layer = ( /** * Creates the MySQL statement compiler, using `?` placeholders and backtick-escaped identifiers. * - * @category compiler + * @category constructors * @since 4.0.0 */ export const makeCompiler = (transform?: (_: string) => string) => diff --git a/repos/effect/packages/sql/mysql2/src/MysqlMigrator.ts b/repos/effect/packages/sql/mysql2/src/MysqlMigrator.ts index ca2fe74944..bea60d6225 100644 --- a/repos/effect/packages/sql/mysql2/src/MysqlMigrator.ts +++ b/repos/effect/packages/sql/mysql2/src/MysqlMigrator.ts @@ -23,7 +23,7 @@ export * from "effect/unstable/sql/Migrator" /** * Runs SQL migrations using the configured `SqlClient`, returning the migrations that were applied. * - * @category constructors + * @category running * @since 4.0.0 */ export const run: ( diff --git a/repos/effect/packages/sql/mysql2/test/Client.test.ts b/repos/effect/packages/sql/mysql2/test/Client.integration.test.ts similarity index 100% rename from repos/effect/packages/sql/mysql2/test/Client.test.ts rename to repos/effect/packages/sql/mysql2/test/Client.integration.test.ts diff --git a/repos/effect/packages/sql/mysql2/test/KeyValueStore.test.ts b/repos/effect/packages/sql/mysql2/test/KeyValueStore.integration.test.ts similarity index 100% rename from repos/effect/packages/sql/mysql2/test/KeyValueStore.test.ts rename to repos/effect/packages/sql/mysql2/test/KeyValueStore.integration.test.ts diff --git a/repos/effect/packages/sql/mysql2/test/Model.test.ts b/repos/effect/packages/sql/mysql2/test/Model.integration.test.ts similarity index 100% rename from repos/effect/packages/sql/mysql2/test/Model.test.ts rename to repos/effect/packages/sql/mysql2/test/Model.integration.test.ts diff --git a/repos/effect/packages/sql/mysql2/test/MysqlClient.integration.test.ts b/repos/effect/packages/sql/mysql2/test/MysqlClient.integration.test.ts new file mode 100644 index 0000000000..699f351c57 --- /dev/null +++ b/repos/effect/packages/sql/mysql2/test/MysqlClient.integration.test.ts @@ -0,0 +1,66 @@ +import { MysqlClient } from "@effect/sql-mysql2" +import { assert, describe, it } from "@effect/vitest" +import { Effect, Stream } from "effect" +import * as Mysql from "mysql2" +import { vi } from "vitest" + +import { MysqlContainer } from "./utils.ts" + +vi.mock("mysql2", { spy: true }) + +describe("MysqlClient", () => { + it.effect("stream returns same rows as direct execution", () => + Effect.gen(function*() { + const sql = yield* MysqlClient.MysqlClient + + yield* sql`DROP TABLE IF EXISTS stream_regression_test` + yield* sql`CREATE TABLE stream_regression_test (id INT PRIMARY KEY AUTO_INCREMENT, value VARCHAR(255))` + + const testData = Array.from({ length: 100 }, (_, i) => ({ value: `row_${i}` })) + yield* sql`INSERT INTO stream_regression_test ${sql.insert(testData)}` + + const directResults = yield* sql`SELECT * FROM stream_regression_test` + + const streamResults = yield* Stream.runCollect( + sql`SELECT * FROM stream_regression_test`.stream + ) + + yield* sql`DROP TABLE stream_regression_test` + + assert.strictEqual( + streamResults.length, + directResults.length, + `Stream returned ${streamResults.length} rows, expected ${directResults.length}` + ) + }).pipe( + Effect.provide(MysqlContainer.layerClient), + Effect.catchTag("ContainerError", () => Effect.void) + ), { timeout: 60_000 }) + + it.effect("uses query when prepared statements are disabled", () => + Effect.gen(function*() { + const calls: Array<"execute" | "query"> = [] + vi.mocked(Mysql.createPool).mockReturnValueOnce({ + query: (options: string | object, callback: (cause: null, rows?: Array) => void) => { + if (typeof options !== "string") { + calls.push("query") + } + callback(null, []) + }, + execute: (_options: object, callback: (cause: null, rows: Array) => void) => { + calls.push("execute") + callback(null, []) + }, + end: (callback: () => void) => callback() + } as unknown as Mysql.Pool) + + yield* Effect.gen(function*() { + const sql = yield* MysqlClient.MysqlClient + yield* sql`SELECT ${1}` + yield* sql`SELECT ${1}`.values + yield* sql.unsafe("SELECT ?", [1]).raw + }).pipe(Effect.provide(MysqlClient.layer({ disablePreparedStatements: true }))) + + assert.deepStrictEqual(calls, ["query", "query", "query"]) + })) +}) diff --git a/repos/effect/packages/sql/mysql2/test/MysqlClient.test.ts b/repos/effect/packages/sql/mysql2/test/MysqlClient.test.ts deleted file mode 100644 index 4ceb6ad69d..0000000000 --- a/repos/effect/packages/sql/mysql2/test/MysqlClient.test.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { MysqlClient } from "@effect/sql-mysql2" -import { assert, describe, it } from "@effect/vitest" -import { Effect, Stream } from "effect" - -import { MysqlContainer } from "./utils.ts" - -describe("MysqlClient", () => { - it.effect("stream returns same rows as direct execution", () => - Effect.gen(function*() { - const sql = yield* MysqlClient.MysqlClient - - yield* sql`DROP TABLE IF EXISTS stream_regression_test` - yield* sql`CREATE TABLE stream_regression_test (id INT PRIMARY KEY AUTO_INCREMENT, value VARCHAR(255))` - - const testData = Array.from({ length: 100 }, (_, i) => ({ value: `row_${i}` })) - yield* sql`INSERT INTO stream_regression_test ${sql.insert(testData)}` - - const directResults = yield* sql`SELECT * FROM stream_regression_test` - - const streamResults = yield* Stream.runCollect( - sql`SELECT * FROM stream_regression_test`.stream - ) - - yield* sql`DROP TABLE stream_regression_test` - - assert.strictEqual( - streamResults.length, - directResults.length, - `Stream returned ${streamResults.length} rows, expected ${directResults.length}` - ) - }).pipe( - Effect.provide(MysqlContainer.layerClient), - Effect.catchTag("ContainerError", () => Effect.void) - ), { timeout: 60_000 }) -}) diff --git a/repos/effect/packages/sql/mysql2/test/Persistence.integration.test.ts b/repos/effect/packages/sql/mysql2/test/Persistence.integration.test.ts new file mode 100644 index 0000000000..61ac5457cf --- /dev/null +++ b/repos/effect/packages/sql/mysql2/test/Persistence.integration.test.ts @@ -0,0 +1,66 @@ +import { assert, it } from "@effect/vitest" +import { Effect, Layer } from "effect" +import * as PersistedCacheTest from "effect-test/unstable/persistence/PersistedCacheTest" +import * as PersistedQueueTest from "effect-test/unstable/persistence/PersistedQueueTest" +import * as SqlCleanupTest from "effect-test/unstable/persistence/SqlCleanupTest" +import { TestClock } from "effect/testing" +import { PersistedQueue, Persistence } from "effect/unstable/persistence" +import { SqlClient } from "effect/unstable/sql" +import { MysqlContainer } from "./utils.ts" + +it.layer(MysqlContainer.layerClient, { timeout: "90 seconds" })("Persistence", (it) => { + PersistedCacheTest.suiteWith("sql-mysql2-multi", Persistence.layerSqlMultiTable, it) + + PersistedCacheTest.suiteWith("sql-mysql2-single", Persistence.layerSql, it) + + PersistedQueueTest.suiteWith("sql-mysql2", PersistedQueue.layerStoreSql(), it) + + it.effect("deletes expired entries in batches", () => + Effect.gen(function*() { + const sql = (yield* SqlClient.SqlClient).withoutTransforms() + const table = sql("effect_persistence") + const expiredCount = sql<{ readonly count: number }>` + SELECT COUNT(*) AS count FROM ${table} WHERE store_id = 'expired' + `.pipe(Effect.map((rows) => Number(rows[0].count))) + // Reset the table left by the single-table cache suite so cleanup builds its own schema and index. + yield* sql`DROP TABLE IF EXISTS ${table}` + yield* sql` + CREATE TABLE ${table} ( + store_id VARCHAR(191) NOT NULL, + id VARCHAR(191) NOT NULL, + value TEXT NOT NULL, + expires BIGINT, + PRIMARY KEY (store_id, id) + ) + ` + + const entries = Array.from({ length: SqlCleanupTest.expiredEntryCount }, (_, i) => ({ + store_id: "expired", + id: String(i), + value: "{}", + expires: SqlCleanupTest.expiredAtEpoch + })) + yield* sql`INSERT INTO ${table} ${sql.insert(entries)}`.unprepared + yield* sql` + INSERT INTO ${table} (store_id, id, value, expires) + VALUES ('live', 'live', '{}', NULL), ('live', 'future', '{}', ${SqlCleanupTest.futureExpiresAt}) + ` + + yield* Layer.build(Persistence.layerBackingSql).pipe(TestClock.withLive) + + const expired = yield* SqlCleanupTest.waitForCount(expiredCount, (count) => count === 0) + assert.strictEqual(expired, 0) + const live = yield* sql<{ readonly count: number }>` + SELECT COUNT(*) AS count FROM ${table} WHERE store_id = 'live' + ` + assert.strictEqual(Number(live[0].count), 2) + + const indexes = yield* sql<{ readonly count: number }>` + SELECT COUNT(*) AS count FROM information_schema.statistics + WHERE table_schema = DATABASE() + AND table_name = 'effect_persistence' + AND index_name = 'effect_persistence_expires_idx' + ` + assert.strictEqual(Number(indexes[0].count), 1) + }), { timeout: SqlCleanupTest.testTimeout }) +}) diff --git a/repos/effect/packages/sql/mysql2/test/Persistence.test.ts b/repos/effect/packages/sql/mysql2/test/Persistence.test.ts deleted file mode 100644 index b0e9b76250..0000000000 --- a/repos/effect/packages/sql/mysql2/test/Persistence.test.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Layer } from "effect" -import * as PersistedCacheTest from "effect-test/unstable/persistence/PersistedCacheTest" -import * as PersistedQueueTest from "effect-test/unstable/persistence/PersistedQueueTest" -import { PersistedQueue, Persistence } from "effect/unstable/persistence" -import { MysqlContainer } from "./utils.ts" - -PersistedCacheTest.suite( - "sql-mysql2-multi", - Persistence.layerSqlMultiTable.pipe(Layer.provide(MysqlContainer.layerClient)) -) - -PersistedCacheTest.suite( - "sql-mysql2-single", - Persistence.layerSql.pipe(Layer.provide(MysqlContainer.layerClient)) -) - -PersistedQueueTest.suite( - "sql-mysql2", - PersistedQueue.layerStoreSql().pipe( - Layer.provide(MysqlContainer.layerClient) - ) -) diff --git a/repos/effect/packages/sql/mysql2/test/SqlEventLogServerUnencrypted.test.ts b/repos/effect/packages/sql/mysql2/test/SqlEventLogServerUnencrypted.integration.test.ts similarity index 100% rename from repos/effect/packages/sql/mysql2/test/SqlEventLogServerUnencrypted.test.ts rename to repos/effect/packages/sql/mysql2/test/SqlEventLogServerUnencrypted.integration.test.ts diff --git a/repos/effect/packages/sql/mysql2/test/utils.ts b/repos/effect/packages/sql/mysql2/test/utils.ts index 344145a5d3..ef749fcce0 100644 --- a/repos/effect/packages/sql/mysql2/test/utils.ts +++ b/repos/effect/packages/sql/mysql2/test/utils.ts @@ -7,6 +7,17 @@ export class ContainerError extends Data.TaggedError("ContainerError")<{ cause: unknown }> {} +const makeMysqlContainer = () => + new MySqlContainer("mysql:lts").withHealthCheck({ + test: [ + "CMD-SHELL", + "MYSQL_PWD=\"$MYSQL_ROOT_PASSWORD\" mysqladmin ping --protocol TCP --host 127.0.0.1 --user root --silent" + ], + interval: 250, + timeout: 1000, + retries: 1000 + }) + export class MysqlContainer extends Context.Service< MysqlContainer, StartedMySqlContainer @@ -14,7 +25,7 @@ export class MysqlContainer extends Context.Service< static readonly layer = Layer.effect(this)( Effect.acquireRelease( Effect.tryPromise({ - try: () => new MySqlContainer("mysql:lts").start(), + try: () => makeMysqlContainer().start(), catch: (cause) => new ContainerError({ cause }) }), (container) => Effect.promise(() => container.stop()) diff --git a/repos/effect/packages/sql/mysql2/tsconfig.json b/repos/effect/packages/sql/mysql2/tsconfig.json index 739eb6b55d..f9e09522b0 100644 --- a/repos/effect/packages/sql/mysql2/tsconfig.json +++ b/repos/effect/packages/sql/mysql2/tsconfig.json @@ -1,5 +1,5 @@ { - "$schema": "http://json.schemastore.org/tsconfig", + "$schema": "https://json.schemastore.org/tsconfig", "extends": "../../../tsconfig.base.json", "include": ["src"], "references": [ diff --git a/repos/effect/packages/sql/mysql2/vitest.config.ts b/repos/effect/packages/sql/mysql2/vitest.config.ts deleted file mode 100644 index c8a52c1826..0000000000 --- a/repos/effect/packages/sql/mysql2/vitest.config.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { mergeConfig, type ViteUserConfig } from "vitest/config" -import shared from "../../../vitest.shared.ts" - -const config: ViteUserConfig = {} - -export default mergeConfig(shared, config) diff --git a/repos/effect/packages/sql/pg/CHANGELOG.md b/repos/effect/packages/sql/pg/CHANGELOG.md index f311f2f429..1f06490730 100644 --- a/repos/effect/packages/sql/pg/CHANGELOG.md +++ b/repos/effect/packages/sql/pg/CHANGELOG.md @@ -1,5 +1,131 @@ # @effect/sql-pg +## 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 + +- [#7142](https://github.com/Effect-TS/effect/pull/7142) [`c25b84c`](https://github.com/Effect-TS/effect/commit/c25b84c09bbbb68343e157bda9cd4c42e1e5e515) Thanks @fubhy! - Prevent unhandled `pg` client error events while `PgClient.makeClient` is connecting. +- 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 + +- [#6991](https://github.com/Effect-TS/effect/pull/6991) [`4a17049`](https://github.com/Effect-TS/effect/commit/4a17049157d83f55d44a6e8b1c58c28df2ffd65a) Thanks @fubhy! - Hold the shared PostgreSQL client permit for the full transaction lifetime. +- 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 + +- [#6451](https://github.com/Effect-TS/effect/pull/6451) [`0e135bd`](https://github.com/Effect-TS/effect/commit/0e135bd2425abf976d38cdb68d011435034b0537) Thanks @steinerl! - Fix `PgClient.makeClient` to connect the underlying `pg.Client` during resource acquisition. + +- Updated dependencies [[`8ce4795`](https://github.com/Effect-TS/effect/commit/8ce4795ccbaebca4292757db568c005a992546a4), [`80b539f`](https://github.com/Effect-TS/effect/commit/80b539f8aba68f478c75c35c2b4140c4ffc4fada), [`88a54cc`](https://github.com/Effect-TS/effect/commit/88a54cc341006e3ebcb13482c618f62a680ce199), [`e6e6dba`](https://github.com/Effect-TS/effect/commit/e6e6dba6e9d86e7c2ad27dcedf289db76a19697f), [`bfb203e`](https://github.com/Effect-TS/effect/commit/bfb203e95aa439f731acad37fc3a9a831a190f1c), [`2e9a34a`](https://github.com/Effect-TS/effect/commit/2e9a34ac2bece4f3a206160480c991e3841dc67a), [`55d4eb3`](https://github.com/Effect-TS/effect/commit/55d4eb34f2c64d54f6a25a305b5c5438ebd7934e), [`bddb010`](https://github.com/Effect-TS/effect/commit/bddb010eac3d4436cb094edbbee7460c5440c162), [`a328835`](https://github.com/Effect-TS/effect/commit/a328835e50d76bc96648a1c1550456e8c9f81210), [`5560d05`](https://github.com/Effect-TS/effect/commit/5560d05aa6abdd29466d9c3412cc5e648b0adbde), [`8f6e3ad`](https://github.com/Effect-TS/effect/commit/8f6e3adb185b16e8820b98c509b308086f7ff1af), [`46997fa`](https://github.com/Effect-TS/effect/commit/46997fa60401f5e3c93daa4b61f7df8e31caaab4), [`9e6e12d`](https://github.com/Effect-TS/effect/commit/9e6e12d75c118cd265496f2880490d1f33a5c8bf), [`3394b93`](https://github.com/Effect-TS/effect/commit/3394b93d97d6f24fc38670641d1490289ffca7f1), [`febeabc`](https://github.com/Effect-TS/effect/commit/febeabc3f7c31094da000a23edeaabfe2ab00a38), [`54161c9`](https://github.com/Effect-TS/effect/commit/54161c98f6f3569e0c31842f54e6a257f9421c4c), [`385f7a4`](https://github.com/Effect-TS/effect/commit/385f7a4ee4a7359928597ea56d151dbaf5eb5802), [`7eea4d0`](https://github.com/Effect-TS/effect/commit/7eea4d0b73ec554915d7066a71f46326ce2ba45f), [`7543afe`](https://github.com/Effect-TS/effect/commit/7543afea6f4d97d1f1ad876224323838a48daadd), [`44b9cf3`](https://github.com/Effect-TS/effect/commit/44b9cf3d240d726997b4bbcd0ede48e074d3c456), [`7eea4d0`](https://github.com/Effect-TS/effect/commit/7eea4d0b73ec554915d7066a71f46326ce2ba45f), [`0a8aa6a`](https://github.com/Effect-TS/effect/commit/0a8aa6acb90a72b91c24d17133c950e4cacd8abd), [`c8d9fcf`](https://github.com/Effect-TS/effect/commit/c8d9fcf7b030f7c474effbab2764ce7aee1c7209), [`9ca7f9a`](https://github.com/Effect-TS/effect/commit/9ca7f9a69363e4485645966d5a93b8f9597c5206), [`e7aca89`](https://github.com/Effect-TS/effect/commit/e7aca894bb32fbb785b5830837e6061c415a6015), [`55d7560`](https://github.com/Effect-TS/effect/commit/55d75609b8acf8a1b54c1b1c7fbbb65ec741aa3e), [`f809189`](https://github.com/Effect-TS/effect/commit/f809189ddf6b6011ba43a9901baaa734e315da2a), [`88a54cc`](https://github.com/Effect-TS/effect/commit/88a54cc341006e3ebcb13482c618f62a680ce199), [`0ebdbe7`](https://github.com/Effect-TS/effect/commit/0ebdbe74463dc84385956d0b1e8c2b79ebab5400), [`7517d09`](https://github.com/Effect-TS/effect/commit/7517d09f12a0b183a81bd425962c4e280a68b05d), [`212493b`](https://github.com/Effect-TS/effect/commit/212493b9a1eb98cd1ef6959c707a2e5784a5ae91), [`88a54cc`](https://github.com/Effect-TS/effect/commit/88a54cc341006e3ebcb13482c618f62a680ce199), [`80ea8cb`](https://github.com/Effect-TS/effect/commit/80ea8cb9222ca73f564c8267ab2f82966fea027a), [`8df19f4`](https://github.com/Effect-TS/effect/commit/8df19f4fe81d90cc33ace88b9a77e5534f82d604)]: + - effect@4.0.0-beta.99 + +## 4.0.0-beta.98 + +### Patch Changes + +- Updated dependencies [[`989603b`](https://github.com/Effect-TS/effect-smol/commit/989603b60ab1197b64acf214208e0d370cd1f842), [`214c458`](https://github.com/Effect-TS/effect-smol/commit/214c458084bb6995d543cd37d1055f24be3d454e), [`a037273`](https://github.com/Effect-TS/effect-smol/commit/a0372736ac34796969b051bbba4717d7983f1ebe), [`97fdaa9`](https://github.com/Effect-TS/effect-smol/commit/97fdaa9c1f522c65e579365d314a07878e2b904f), [`b24d248`](https://github.com/Effect-TS/effect-smol/commit/b24d248c8df44222ce642087cde2bd859a2dc709), [`19c222c`](https://github.com/Effect-TS/effect-smol/commit/19c222cac2353a3d7b7733caecb00556fffe9a5c), [`eec85dd`](https://github.com/Effect-TS/effect-smol/commit/eec85ddba09ea326fd268ee33eeffd47e50d4671), [`0082f4f`](https://github.com/Effect-TS/effect-smol/commit/0082f4f74fb139fd578f87f0a790e845133983dc), [`8849052`](https://github.com/Effect-TS/effect-smol/commit/884905232d1e9a365e046d8dde27bf9c5707f57f), [`c15e16a`](https://github.com/Effect-TS/effect-smol/commit/c15e16ad130d1fbde25d912b7ac55995066cb35b), [`01d00a3`](https://github.com/Effect-TS/effect-smol/commit/01d00a3abfbf1f37996cdbe738ea5137c646cdd7), [`8bd4589`](https://github.com/Effect-TS/effect-smol/commit/8bd458975a1b3a8ed042eccf317b93d28ded91e7), [`0082f4f`](https://github.com/Effect-TS/effect-smol/commit/0082f4f74fb139fd578f87f0a790e845133983dc), [`6e08428`](https://github.com/Effect-TS/effect-smol/commit/6e08428d980501b856f846ad3f3f0e4ea46e7786), [`388dcf9`](https://github.com/Effect-TS/effect-smol/commit/388dcf953f65d317547f34d40e6443c5f264205f), [`2b7ce2b`](https://github.com/Effect-TS/effect-smol/commit/2b7ce2b513e7ec2a77822f1116dc6ffb6ba93f4e), [`87bea7e`](https://github.com/Effect-TS/effect-smol/commit/87bea7e16259246f3bcdf565446394751abca953), [`ce38dc3`](https://github.com/Effect-TS/effect-smol/commit/ce38dc33bda805a684432cca071f4dc3c6b9a1ba), [`a807cd1`](https://github.com/Effect-TS/effect-smol/commit/a807cd170341deca8a1cfb52c4222585f2431bb9), [`fd8a356`](https://github.com/Effect-TS/effect-smol/commit/fd8a356f06a8c9ce4e7e0a13fc4021c178ed31de), [`c2a5edc`](https://github.com/Effect-TS/effect-smol/commit/c2a5edc3abd31ad5bc123362bc1213e03e4095c3), [`5946da3`](https://github.com/Effect-TS/effect-smol/commit/5946da3804a1be5e752b05b96bd058cdba50a1bf), [`4ae0c5f`](https://github.com/Effect-TS/effect-smol/commit/4ae0c5ffcbe6c56ddfcb05c639112a079483539e), [`5b2a0bc`](https://github.com/Effect-TS/effect-smol/commit/5b2a0bceea3a28a33a58555210c90a415dc74a76), [`72ac585`](https://github.com/Effect-TS/effect-smol/commit/72ac585884befde6af9208da738699a93f1bae79), [`5e8c1b8`](https://github.com/Effect-TS/effect-smol/commit/5e8c1b82bfafa121311f987a49ab75395e3647a7), [`0f9c078`](https://github.com/Effect-TS/effect-smol/commit/0f9c07841b04183f485ee6e6458de73b290b09f5)]: + - effect@4.0.0-beta.98 + +## 4.0.0-beta.97 + +### Patch Changes + +- Updated dependencies []: + - effect@4.0.0-beta.97 + +## 4.0.0-beta.96 + +### Patch Changes + +- Updated dependencies [[`1503f45`](https://github.com/Effect-TS/effect-smol/commit/1503f45cb5bb2a74f4705252ec505a1f0ade7e62), [`57fe793`](https://github.com/Effect-TS/effect-smol/commit/57fe79316ffbc380b30626a168981fb26ae97459), [`0c2f78f`](https://github.com/Effect-TS/effect-smol/commit/0c2f78f695ec474e1ff5474da183577975e418f5), [`0c2f78f`](https://github.com/Effect-TS/effect-smol/commit/0c2f78f695ec474e1ff5474da183577975e418f5), [`0c2f78f`](https://github.com/Effect-TS/effect-smol/commit/0c2f78f695ec474e1ff5474da183577975e418f5), [`97f29df`](https://github.com/Effect-TS/effect-smol/commit/97f29df457f7ffd07cfb4b379315c12c086af805)]: + - effect@4.0.0-beta.96 + +## 4.0.0-beta.95 + +### Patch Changes + +- Updated dependencies [[`a482442`](https://github.com/Effect-TS/effect-smol/commit/a482442abdeb490e9652b854ec3495e4aa7273e7), [`fbefa85`](https://github.com/Effect-TS/effect-smol/commit/fbefa850fab2f0a302c20614496aeaaa2a8b5590), [`0b4a32f`](https://github.com/Effect-TS/effect-smol/commit/0b4a32f4260f0d8500942a133001b0d349328102), [`18a49e1`](https://github.com/Effect-TS/effect-smol/commit/18a49e1786679456258002ff9397faf02f678c2d), [`266cb90`](https://github.com/Effect-TS/effect-smol/commit/266cb90bb2c17aabc40563c32db334f09ba3d74b), [`912f095`](https://github.com/Effect-TS/effect-smol/commit/912f095a34572bbd3cedf6edb27878443e3e4a95), [`a6718f9`](https://github.com/Effect-TS/effect-smol/commit/a6718f9e00a15ca903b0732da46116cbf3d6aca7), [`bef5154`](https://github.com/Effect-TS/effect-smol/commit/bef51540a243aa2f872a00c01d0cd58b7a769baa), [`18e0564`](https://github.com/Effect-TS/effect-smol/commit/18e0564bd0f8ebbdfcaf1e2c21529948e9e4a81d), [`fb50f14`](https://github.com/Effect-TS/effect-smol/commit/fb50f14fc3657c1973785aa5b72ecf0b0d28e0b2)]: + - effect@4.0.0-beta.95 + +## 4.0.0-beta.94 + +### Patch Changes + +- Updated dependencies [[`95a0e9b`](https://github.com/Effect-TS/effect-smol/commit/95a0e9bb62797af0e81c9998773405f248f218c5), [`a0a3490`](https://github.com/Effect-TS/effect-smol/commit/a0a3490bbce765f199d8e077aceac504f0462e63), [`f11ce73`](https://github.com/Effect-TS/effect-smol/commit/f11ce73af60823754dc24194f4ffc561b9ea1c2d), [`ff30b6e`](https://github.com/Effect-TS/effect-smol/commit/ff30b6e7c2c63ffc56a4c5818d6d86b01b5ad528), [`1caab3c`](https://github.com/Effect-TS/effect-smol/commit/1caab3cc30f626efbf15e59d74f539a487e5c85c), [`aa80c47`](https://github.com/Effect-TS/effect-smol/commit/aa80c4775a04db87553e5568764cab7e32a72814), [`c2ae4fc`](https://github.com/Effect-TS/effect-smol/commit/c2ae4fce2f03a4cd1861c2b1179da7df656e662d), [`a0a3490`](https://github.com/Effect-TS/effect-smol/commit/a0a3490bbce765f199d8e077aceac504f0462e63)]: + - effect@4.0.0-beta.94 + +## 4.0.0-beta.93 + +### Patch Changes + +- Updated dependencies [[`00652fe`](https://github.com/Effect-TS/effect-smol/commit/00652fe95c18f87208e91343eb8bf218faa2f677), [`6c58167`](https://github.com/Effect-TS/effect-smol/commit/6c5816746eaf91d2a3c7c899c5720809fa230ae3), [`2bc5415`](https://github.com/Effect-TS/effect-smol/commit/2bc541501a7ef89e542d7cb98e96beb53cd205cc), [`e11cccc`](https://github.com/Effect-TS/effect-smol/commit/e11cccc7d5fe631abccc7d6e3bd296938de0fa2e), [`ba7e77e`](https://github.com/Effect-TS/effect-smol/commit/ba7e77e046b8641a3a4e9750bb88ca4a1d063d3f), [`5713ee7`](https://github.com/Effect-TS/effect-smol/commit/5713ee7edbc3054efde407b2286bbfd45bbc6e1c)]: + - effect@4.0.0-beta.93 + +## 4.0.0-beta.92 + +### Patch Changes + +- Updated dependencies [[`affdc13`](https://github.com/Effect-TS/effect-smol/commit/affdc139045cc325dce321a84a580fdc1b2da7b9)]: + - effect@4.0.0-beta.92 + +## 4.0.0-beta.91 + +### Patch Changes + +- Updated dependencies [[`b135b25`](https://github.com/Effect-TS/effect-smol/commit/b135b2517fca9e7839734ace3699a7dfa75b9075), [`aaa21a3`](https://github.com/Effect-TS/effect-smol/commit/aaa21a369a171c600db294f2a4f640583043e150), [`3475ee6`](https://github.com/Effect-TS/effect-smol/commit/3475ee6c2bda6b05c6d7a12ce30c8bb840b5b1a6)]: + - effect@4.0.0-beta.91 + ## 4.0.0-beta.90 ### Patch Changes diff --git a/repos/effect/packages/sql/pg/README.md b/repos/effect/packages/sql/pg/README.md index be3c29a8ad..9017bb4b8e 100644 --- a/repos/effect/packages/sql/pg/README.md +++ b/repos/effect/packages/sql/pg/README.md @@ -1,6 +1,6 @@ # `@effect/sql-pg` -An `@effect/sql` implementation using the `pg` library. +An Effect SQL implementation using the `pg` library. ## Documentation diff --git a/repos/effect/packages/sql/pg/docgen.json b/repos/effect/packages/sql/pg/docgen.json deleted file mode 100644 index cff842f886..0000000000 --- a/repos/effect/packages/sql/pg/docgen.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "$schema": "../../node_modules/@effect/docgen/schema.json", - "srcLink": "https://github.com/Effect-TS/effect/tree/main/packages/sql/pg/src/", - "exclude": ["src/internal/**/*.ts"], - "tscExecutable": "tsgo", - "examplesCompilerOptions": { - "noEmit": true, - "strict": true, - "skipLibCheck": true, - "moduleResolution": "Bundler", - "module": "ES2022", - "target": "ES2022", - "lib": ["ES2022", "DOM", "DOM.Iterable"], - "rewriteRelativeImportExtensions": true, - "allowImportingTsExtensions": true, - "paths": { - "effect": ["../../../effect/src/index.js"], - "effect/*": ["../../../effect/src/*.js"] - }, - "plugins": [ - { "name": "@effect/language-service", "includeSuggestionsInTsc": false } - ] - } -} diff --git a/repos/effect/packages/sql/pg/package.json b/repos/effect/packages/sql/pg/package.json index c67dad6bc7..1e956bb184 100644 --- a/repos/effect/packages/sql/pg/package.json +++ b/repos/effect/packages/sql/pg/package.json @@ -1,17 +1,17 @@ { "name": "@effect/sql-pg", - "version": "4.0.0-beta.90", + "version": "4.0.0-beta.107", "type": "module", "license": "MIT", "description": "A PostgreSQL toolkit for Effect", "homepage": "https://effect.website", "repository": { "type": "git", - "url": "https://github.com/Effect-TS/effect-smol.git", + "url": "https://github.com/Effect-TS/effect.git", "directory": "packages/sql/pg" }, "bugs": { - "url": "https://github.com/Effect-TS/effect-smol/issues" + "url": "https://github.com/Effect-TS/effect/issues" }, "tags": [ "typescript", @@ -29,6 +29,7 @@ ".": "./src/index.ts", "./*": "./src/*.ts", "./internal/*": null, + "./index": null, "./*/index": null }, "files": [ @@ -36,7 +37,10 @@ "dist/**/*.js", "dist/**/*.js.map", "dist/**/*.d.ts", - "dist/**/*.d.ts.map" + "dist/**/*.d.ts.map", + "AGENTS.md", + "CLAUDE.md", + "ai-docs/**/*" ], "publishConfig": { "access": "public", @@ -46,20 +50,18 @@ ".": "./dist/index.js", "./*": "./dist/*.js", "./internal/*": null, + "./index": null, "./*/index": null } }, "scripts": { "codegen": "effect-utils codegen", "build": "tsc -b tsconfig.json && pnpm babel", - "build:tsgo": "tsgo -b tsconfig.json && pnpm babel", "babel": "babel dist --plugins annotate-pure-calls --out-dir dist --source-maps", - "check": "tsc -b tsconfig.json", - "test": "vitest", - "coverage": "vitest --coverage" + "check": "tsc -b tsconfig.json" }, "devDependencies": { - "@testcontainers/postgresql": "^11.14.0", + "@testcontainers/postgresql": "^12.0.4", "@types/pg": "^8.20.0", "@types/pg-cursor": "^2.7.2", "effect": "workspace:^" @@ -68,10 +70,10 @@ "effect": "workspace:^" }, "dependencies": { - "pg": "^8.20.0", - "pg-connection-string": "2.12.0", - "pg-cursor": "^2.19.0", - "pg-pool": "^3.13.0", + "pg": "^8.22.0", + "pg-connection-string": "^2.14.0", + "pg-cursor": "^2.21.0", + "pg-pool": "^3.14.0", "pg-types": "^4.1.0" } } diff --git a/repos/effect/packages/sql/pg/src/PgClient.ts b/repos/effect/packages/sql/pg/src/PgClient.ts index ea5f3f5434..8fae62d0c8 100644 --- a/repos/effect/packages/sql/pg/src/PgClient.ts +++ b/repos/effect/packages/sql/pg/src/PgClient.ts @@ -73,7 +73,7 @@ export type TypeId = "~@effect/sql-pg/PgClient" /** * PostgreSQL client service, extending `SqlClient` with JSON parameter fragments and LISTEN/NOTIFY helpers. * - * @category models + * @category services * @since 4.0.0 */ export interface PgClient extends Client.SqlClient { @@ -99,7 +99,7 @@ export const PgClient = Context.Service("@effect/sql-pg/PgClient") /** * Configuration for a PostgreSQL client, including connection, TLS, custom stream, application name, type parser, JSON transform, and query/result name transform options. * - * @category constructors + * @category models * @since 4.0.0 */ export interface PgClientConfig { @@ -129,7 +129,7 @@ export interface PgClientConfig { /** * PostgreSQL pool configuration, extending `PgClientConfig` with idle timeout, pool size, and connection lifetime settings. * - * @category constructors + * @category models * @since 4.0.0 */ export interface PgPoolConfig extends PgClientConfig { @@ -219,52 +219,57 @@ export const makeClient = ( */ readonly acquireForStream?: boolean | undefined } -): Effect.Effect => - fromClient({ +): Effect.Effect => { + function onError() {} + return fromClient({ ...options, - acquire: Effect.gen(function*() { - const client = new Pg.Client({ - connectionString: options.url ? Redacted.value(options.url) : undefined, - user: options.username, - host: options.host, - database: options.database, - password: options.password ? Redacted.value(options.password) : undefined, - ssl: options.ssl, - port: options.port, - ...(options.stream ? { stream: options.stream } : {}), - application_name: options.applicationName ?? "@effect/sql-pg", - types: options.types - }) - yield* Effect.acquireRelease( - Effect.tryPromise({ - try: () => client.query("SELECT 1"), - catch: (cause) => new SqlError({ reason: classifyError(cause, "PgClient: Failed to connect", "connect") }) - }), - () => - Effect.promise(() => client.end()).pipe( - Effect.timeoutOption(1000) - ), - { interruptible: true } - ).pipe( - Effect.timeoutOrElse({ - duration: options.connectTimeout ?? Duration.seconds(5), - orElse: () => - Effect.fail( - new SqlError({ - reason: new ConnectionError({ - cause: new Error("Connection timed out"), - message: "PgClient: Connection timed out", - operation: "connect" - }) + acquire: Effect.acquireRelease( + Effect.tryPromise({ + try: async () => { + const client = new Pg.Client({ + connectionString: options.url ? Redacted.value(options.url) : undefined, + user: options.username, + host: options.host, + database: options.database, + password: options.password ? Redacted.value(options.password) : undefined, + ssl: options.ssl, + port: options.port, + ...(options.stream ? { stream: options.stream } : {}), + application_name: options.applicationName ?? "@effect/sql-pg", + types: options.types + }) + client.on("error", onError) + await client.connect() + return client + }, + catch: (cause) => new SqlError({ reason: classifyError(cause, "PgClient: Failed to connect", "connect") }) + }), + (client) => + Effect.promise(() => { + client.off("error", onError) + return client.end() + }).pipe( + Effect.timeoutOption(1000) + ), + { interruptible: true } + ).pipe( + Effect.timeoutOrElse({ + duration: options.connectTimeout ?? Duration.seconds(5), + orElse: () => + Effect.fail( + new SqlError({ + reason: new ConnectionError({ + cause: new Error("Connection timed out"), + message: "PgClient: Connection timed out", + operation: "connect" }) - ) - }) - ) - - return client - }), + }) + ) + }) + ), acquireForStream: options.acquireForStream ?? false }) +} /** * Builds a PostgreSQL client from a scoped `pg` pool acquisition effect, deriving transaction, streaming, and LISTEN/NOTIFY support from that pool. @@ -523,6 +528,17 @@ export const fromClient = Effect.fnUntraced(function*( ) const connection = makeConection(client) const acquirer = semaphore.withPermit(Effect.succeed(connection)) + const transactionAcquirer = Effect.uninterruptibleMask((restore) => { + const fiber = Fiber.getCurrent()! + const scope = Context.getUnsafe(fiber.context, Scope.Scope) + return Effect.as( + Effect.tap( + restore(semaphore.take(1)), + () => Scope.addFinalizer(scope, semaphore.release(1)) + ), + connection + ) + }) const config: PgClientConfig = { ...options, @@ -536,7 +552,7 @@ export const fromClient = Effect.fnUntraced(function*( return yield* makeWith({ acquirer, - transactionAcquirer: acquirer, + transactionAcquirer, listenAcquirer: streamClient, config, spanAttributes: options.spanAttributes, @@ -866,18 +882,18 @@ const escape = Statement.defaultEscape("\"") /** * PostgreSQL-specific custom statement fragments supported by the compiler, currently JSON parameter fragments. * - * @category custom types + * @category models * @since 4.0.0 */ export type PgCustom = PgJson /** - * @category custom types + * @category models * @since 4.0.0 */ interface PgJson extends Custom<"PgJson", unknown> {} /** - * @category custom types + * @category constructors * @since 4.0.0 */ const PgJson = Statement.custom("PgJson") diff --git a/repos/effect/packages/sql/pg/src/PgMigrator.ts b/repos/effect/packages/sql/pg/src/PgMigrator.ts index 00b38a7f46..3751edeaaf 100644 --- a/repos/effect/packages/sql/pg/src/PgMigrator.ts +++ b/repos/effect/packages/sql/pg/src/PgMigrator.ts @@ -29,7 +29,7 @@ export * from "effect/unstable/sql/Migrator" /** * Runs PostgreSQL SQL migrations using the configured clients. Schema dumps use `pg_dump` and require child process, filesystem, and path services. * - * @category constructors + * @category running * @since 4.0.0 */ export const run: ( diff --git a/repos/effect/packages/sql/pg/test/Client.integration.test.ts b/repos/effect/packages/sql/pg/test/Client.integration.test.ts new file mode 100644 index 0000000000..de27f8300d --- /dev/null +++ b/repos/effect/packages/sql/pg/test/Client.integration.test.ts @@ -0,0 +1,495 @@ +import { PgClient } from "@effect/sql-pg" +import { assert, expect, it } from "@effect/vitest" +import { Deferred, Effect, Fiber, Option, Redacted, Stream, String } from "effect" +import { TestClock } from "effect/testing" +import * as Reactivity from "effect/unstable/reactivity/Reactivity" +import { SqlClient } from "effect/unstable/sql" +import * as Statement from "effect/unstable/sql/Statement" +import * as Pg from "pg" +import { parse as parsePgConnectionString } from "pg-connection-string" +import { vi } from "vitest" +import { PgContainer } from "./utils.ts" + +const compilerTransform = PgClient.makeCompiler(String.camelToSnake) +const transformsNested = Statement.defaultTransforms(String.snakeToCamel) +const transforms = Statement.defaultTransforms(String.snakeToCamel, false) + +it.layer(PgContainer.layerClient, { timeout: "30 seconds" })("PgClient", (it) => { + it.effect("insert helper", () => + Effect.gen(function*() { + const sql = yield* PgClient.PgClient + const [query, params] = sql`INSERT INTO people ${sql.insert({ name: "Tim", age: 10 })}`.compile() + expect(query).toEqual(`INSERT INTO people ("name","age") VALUES ($1,$2)`) + expect(params).toEqual(["Tim", 10]) + })) + + it.effect("updateValues helper", () => + Effect.gen(function*() { + const sql = yield* PgClient.PgClient + const [query, params] = sql`UPDATE people SET name = data.name FROM ${ + sql.updateValues( + [{ name: "Tim" }, { name: "John" }], + "data" + ) + }`.compile() + expect(query).toEqual( + `UPDATE people SET name = data.name FROM (values ($1),($2)) AS data("name")` + ) + expect(params).toEqual(["Tim", "John"]) + })) + + it.effect("updateValues helper returning", () => + Effect.gen(function*() { + const sql = yield* PgClient.PgClient + const [query, params] = sql`UPDATE people SET name = data.name FROM ${ + sql.updateValues( + [{ name: "Tim" }, { name: "John" }], + "data" + ).returning("*") + }`.compile() + expect(query).toEqual( + `UPDATE people SET name = data.name FROM (values ($1),($2)) AS data("name") RETURNING *` + ) + expect(params).toEqual(["Tim", "John"]) + })) + + it.effect("update helper", () => + Effect.gen(function*() { + const sql = yield* PgClient.PgClient + let result = sql`UPDATE people SET ${sql.update({ name: "Tim" })}`.compile() + expect(result[0]).toEqual(`UPDATE people SET "name" = $1`) + expect(result[1]).toEqual(["Tim"]) + + result = sql`UPDATE people SET ${sql.update({ name: "Tim", age: 10 }, ["age"])}`.compile() + expect(result[0]).toEqual(`UPDATE people SET "name" = $1`) + expect(result[1]).toEqual(["Tim"]) + })) + + it.effect("update helper returning", () => + Effect.gen(function*() { + const sql = yield* PgClient.PgClient + const result = sql`UPDATE people SET ${sql.update({ name: "Tim" }).returning("*")}`.compile() + expect(result[0]).toEqual(`UPDATE people SET "name" = $1 RETURNING *`) + expect(result[1]).toEqual(["Tim"]) + })) + + it.effect("array helper", () => + Effect.gen(function*() { + const sql = yield* PgClient.PgClient + const [query, params] = sql`SELECT * FROM ${sql("people")} WHERE id IN ${sql.in([1, 2, "string"])}`.compile() + expect(query).toEqual(`SELECT * FROM "people" WHERE id IN ($1,$2,$3)`) + expect(params).toEqual([1, 2, "string"]) + })) + + it.effect("array helper with column", () => + Effect.gen(function*() { + const sql = yield* PgClient.PgClient + let result = sql`SELECT * FROM ${sql("people")} WHERE ${sql.in("id", [1, 2, "string"])}`.compile() + expect(result[0]).toEqual(`SELECT * FROM "people" WHERE "id" IN ($1,$2,$3)`) + expect(result[1]).toEqual([1, 2, "string"]) + + result = sql`SELECT * FROM ${sql("people")} WHERE ${sql.in("id", [])}`.compile() + expect(result[0]).toEqual(`SELECT * FROM "people" WHERE 1=0`) + expect(result[1]).toEqual([]) + })) + + it.effect("and", () => + Effect.gen(function*() { + const sql = yield* PgClient.PgClient + const now = new Date() + const result = sql`SELECT * FROM ${sql("people")} WHERE ${ + sql.and([ + sql.in("name", ["Tim", "John"]), + sql`created_at < ${now}` + ]) + }`.compile() + expect(result[0]).toEqual(`SELECT * FROM "people" WHERE ("name" IN ($1,$2) AND created_at < $3)`) + expect(result[1]).toEqual(["Tim", "John", now]) + })) + + it("transform nested", () => { + assert.deepEqual( + transformsNested.array([ + { + a_key: 1, + nested: [{ b_key: 2 }], + arr_primitive: [1, "2", true] + } + ]) as any, + [ + { + aKey: 1, + nested: [{ bKey: 2 }], + arrPrimitive: [1, "2", true] + } + ] + ) + }) + + it("transform non nested", () => { + assert.deepEqual( + transforms.array([ + { + a_key: 1, + nested: [{ b_key: 2 }], + arr_primitive: [1, "2", true] + } + ]) as any, + [ + { + aKey: 1, + nested: [{ b_key: 2 }], + arrPrimitive: [1, "2", true] + } + ] + ) + + assert.deepEqual( + transforms.array([ + { + json_field: { + test_value: [1, true, null, "text"], + test_nested: { + test_value: [1, true, null, "text"] + } + } + } + ]) as any, + [ + { + jsonField: { + test_value: [1, true, null, "text"], + test_nested: { + test_value: [1, true, null, "text"] + } + } + } + ] + ) + }) + + it.effect("insert fragments", () => + Effect.gen(function*() { + const sql = yield* PgClient.PgClient + const [query, params] = sql`INSERT INTO people ${ + sql.insert({ + name: "Tim", + age: 10, + json: sql.json({ a: 1 }) + }) + }`.compile() + assert.strictEqual( + query, + "INSERT INTO people (\"name\",\"age\",\"json\") VALUES ($1,$2,$3)" + ) + assert.lengthOf(params, 3) + })) + + it.effect("update fragments", () => + Effect.gen(function*() { + const sql = yield* PgClient.PgClient + const now = new Date() + const [query, params] = sql`UPDATE people SET json = data.json FROM ${ + sql.updateValues( + [{ json: sql.json({ a: 1 }) }, { json: sql.json({ b: 1 }) }], + "data" + ) + } WHERE created_at > ${now}`.compile() + assert.strictEqual( + query, + `UPDATE people SET json = data.json FROM (values ($1),($2)) AS data("json") WHERE created_at > $3` + ) + assert.lengthOf(params, 3) + })) + + it.effect("onDialect", () => + Effect.gen(function*() { + const sql = yield* PgClient.PgClient + assert.strictEqual( + sql.onDialect({ + sqlite: () => "A", + pg: () => "B", + mysql: () => "C", + mssql: () => "D", + clickhouse: () => "E" + }), + "B" + ) + assert.strictEqual( + sql.onDialectOrElse({ + orElse: () => "A", + pg: () => "B" + }), + "B" + ) + })) + + it.effect("identifier transform", () => + Effect.gen(function*() { + const sql = yield* PgClient.PgClient + const [query] = compilerTransform.compile( + sql`SELECT * from ${sql("peopleTest")}`, + false + ) + expect(query).toEqual(`SELECT * from "people_test"`) + })) + + it.effect("jsonb", () => + Effect.gen(function*() { + const sql = yield* PgClient.PgClient + const rows = yield* sql<{ json: unknown }>`select ${{ testValue: 123 }}::jsonb as json` + expect(rows[0].json).toEqual({ testValue: 123 }) + })) + + it.effect("stream", () => + Effect.gen(function*() { + const sql = yield* SqlClient.SqlClient + const rows = yield* sql`SELECT generate_series(1, 3)`.stream.pipe( + Stream.runCollect + ) + expect(rows).toEqual([ + { "generate_series": 1 }, + { "generate_series": 2 }, + { "generate_series": 3 } + ]) + })) + + it.effect("preserves successful concurrent nested transactions", () => + Effect.gen(function*() { + const sql = yield* PgClient.PgClient + const firstStarted = yield* Deferred.make() + const firstInserted = yield* Deferred.make() + + const rows = yield* sql.withTransaction( + Effect.gen(function*() { + yield* sql`CREATE TEMP TABLE nested_transactions (value TEXT) ON COMMIT DROP` + yield* Effect.all([ + sql.withTransaction( + Effect.gen(function*() { + yield* Deferred.succeed(firstStarted, undefined) + yield* Effect.sleep("100 millis") + yield* sql`INSERT INTO nested_transactions VALUES ('first')` + yield* Deferred.succeed(firstInserted, undefined) + }) + ), + Deferred.await(firstStarted).pipe( + Effect.andThen(sql.withTransaction( + Deferred.await(firstInserted).pipe( + Effect.andThen(Effect.fail("rollback")) + ) + )) + ) + ], { concurrency: "unbounded" }).pipe(Effect.catch(() => Effect.void)) + return yield* sql<{ value: string }>`SELECT value FROM nested_transactions` + }) + ) + + assert.deepStrictEqual(rows, [{ value: "first" }]) + }).pipe(TestClock.withLive)) +}) + +it.layer(PgContainer.layerMakeClient, { timeout: "30 seconds" })("PgClient.makeClient", (it) => { + it.effect("connects before executing queries", () => + Effect.gen(function*() { + const sql = yield* PgClient.PgClient + const rows = yield* sql<{ value: number }>`SELECT 1 AS value` + assert.deepStrictEqual(rows, [{ value: 1 }]) + })) +}) + +it.effect("PgClient.makeClient handles errors emitted while connecting", () => + Effect.acquireUseRelease( + Effect.sync(() => ({ + connect: vi.spyOn(Pg.Client.prototype, "connect").mockImplementation(function(this: Pg.Client) { + return new Promise((resolve, reject) => { + queueMicrotask(() => { + try { + this.emit("error", new Error("connection failed")) + resolve() + } catch (cause) { + reject(cause) + } + }) + }) + }), + end: vi.spyOn(Pg.Client.prototype, "end").mockResolvedValue(undefined) + })), + () => + PgClient.makeClient({ host: "localhost" }).pipe( + Effect.scoped, + Effect.provide(Reactivity.layer) + ), + ({ connect, end }) => + Effect.sync(() => { + connect.mockRestore() + end.mockRestore() + }) + )) + +it.layer(PgContainer.layerClientWithTransforms, { timeout: "30 seconds" })("PgClient transforms", (it) => { + it.effect("insert helper", () => + Effect.gen(function*() { + const sql = yield* PgClient.PgClient + const [query, params] = sql`INSERT INTO people ${sql.insert({ firstName: "Tim", age: 10 })}`.compile() + expect(query).toEqual(`INSERT INTO people ("first_name","age") VALUES ($1,$2)`) + expect(params).toEqual(["Tim", 10]) + })) + + it.effect("insert helper withoutTransforms", () => + Effect.gen(function*() { + const sql = (yield* PgClient.PgClient).withoutTransforms() + const [query, params] = sql`INSERT INTO people ${sql.insert({ first_name: "Tim", age: 10 })}`.compile() + expect(query).toEqual(`INSERT INTO people ("first_name","age") VALUES ($1,$2)`) + expect(params).toEqual(["Tim", 10]) + })) + + it.effect("multi-statement queries", () => + Effect.gen(function*() { + const sql = yield* SqlClient.SqlClient + + const result = yield* sql<{ id: string; name: string }>` + CREATE TABLE test_multi (id TEXT PRIMARY KEY, name TEXT); + INSERT INTO test_multi (id, name) VALUES ('id1', 'test1') RETURNING *; + INSERT INTO test_multi (id, name) VALUES ('id2', 'test2') RETURNING *; + ` + + expect(result).toHaveLength(3) + expect(result[0]).toEqual([]) + expect(result[1]).toEqual([{ id: "id1", name: "test1" }]) + expect(result[2]).toEqual([{ id: "id2", name: "test2" }]) + })) + + it.effect("interruption", () => + Effect.gen(function*() { + const sql = yield* SqlClient.SqlClient + const conn = yield* sql.reserve + yield* conn.executeRaw("select pg_sleep(1000)", []).pipe( + Effect.timeoutOption("50 millis"), + TestClock.withLive + ) + const value = yield* conn.executeValues("select 1", []) + expect(value).toEqual([[1]]) + })) + + it.effect("Should populate config", () => + Effect.gen(function*() { + const sql = yield* PgClient.PgClient + + assert.isDefined(sql.config.url) + + const parsedConfig = parsePgConnectionString(Redacted.value(sql.config.url)) + + expect(sql.config.host).toEqual(parsedConfig.host) + assert.isNotNull(parsedConfig.port) + assert.isDefined(parsedConfig.port) + expect(sql.config.port).toEqual(parseInt(parsedConfig.port)) + expect(sql.config.username).toEqual(parsedConfig.user) + assert.isDefined(sql.config.password) + expect(Redacted.value(sql.config.password)).toEqual(parsedConfig.password) + expect(sql.config.database).toEqual(parsedConfig.database) + })) +}) + +it.layer(PgContainer.layerClientSingleConnection, { timeout: "30 seconds" })("PgClient listen", (it) => { + it.effect("listen does not reserve a pool connection", () => + Effect.gen(function*() { + const sql = yield* PgClient.PgClient + const channel = "pool_connection_listen" + + const listenFiber = yield* sql.listen(channel).pipe( + Stream.take(1), + Stream.runCollect, + Effect.forkScoped + ) + + yield* Effect.sleep("250 millis") + + const rows = yield* sql<{ value: number }>`SELECT 1 as value`.pipe( + Effect.timeoutOrElse({ + duration: "3 seconds", + orElse: () => Effect.fail(new Error("query timed out while listener was active")) + }) + ) + expect(rows).toEqual([{ value: 1 }]) + + yield* sql.notify(channel, "payload") + const payloads = yield* Fiber.join(listenFiber).pipe( + Effect.timeoutOrElse({ + duration: "3 seconds", + orElse: () => Effect.fail(new Error("listener did not receive notification in time")) + }) + ) + expect(Array.from(payloads)).toEqual(["payload"]) + }).pipe(TestClock.withLive), 20_000) + + it.effect("notify sends payload", () => + Effect.gen(function*() { + const sql = yield* PgClient.PgClient + const channel = "pool_connection_notify" + + const listenFiber = yield* sql.listen(channel).pipe( + Stream.take(1), + Stream.runCollect, + Effect.forkScoped + ) + + yield* Effect.sleep("250 millis") + yield* sql.notify(channel, "payload") + + const payloads = yield* Fiber.join(listenFiber).pipe( + Effect.timeoutOrElse({ + duration: "3 seconds", + orElse: () => Effect.fail(new Error("listener did not receive notification in time")) + }) + ) + expect(Array.from(payloads)).toEqual(["payload"]) + }).pipe(TestClock.withLive), 20_000) +}) + +it.effect("serializes transactions that share one pg.Client", () => + Effect.gen(function*() { + const secondBegin = yield* Deferred.make() + const firstBodyStarted = yield* Deferred.make() + const releaseFirstBody = yield* Deferred.make() + let beginCalls = 0 + const pg = { + host: "localhost", + port: 5432, + database: "postgres", + user: "postgres", + password: undefined, + ssl: false, + on() {}, + off() {}, + query(sql: string, _params: ReadonlyArray, callback: (error: null, result: unknown) => void) { + if (sql === "BEGIN" && ++beginCalls === 2) { + Deferred.doneUnsafe(secondBegin, Effect.void) + } + callback(null, { rows: [] }) + } + } + + const sql = yield* PgClient.fromClient({ + acquire: Effect.succeed(pg as any), + acquireForStream: false + }) + const first = yield* sql.withTransaction(Effect.gen(function*() { + yield* Deferred.succeed(firstBodyStarted, undefined) + yield* Deferred.await(releaseFirstBody) + })).pipe(Effect.forkScoped) + yield* Deferred.await(firstBodyStarted) + const second = yield* sql.withTransaction(Effect.void).pipe(Effect.forkScoped) + + const overlap = yield* Deferred.await(secondBegin).pipe( + Effect.timeoutOption("100 millis"), + TestClock.withLive + ) + yield* Deferred.succeed(releaseFirstBody, undefined) + yield* Fiber.join(first) + yield* Fiber.join(second) + + assert.isTrue(Option.isNone(overlap)) + }).pipe( + Effect.scoped, + Effect.provide(Reactivity.layer) + )) diff --git a/repos/effect/packages/sql/pg/test/Client.test.ts b/repos/effect/packages/sql/pg/test/Client.test.ts deleted file mode 100644 index b0758969d4..0000000000 --- a/repos/effect/packages/sql/pg/test/Client.test.ts +++ /dev/null @@ -1,373 +0,0 @@ -import { PgClient } from "@effect/sql-pg" -import { assert, expect, it } from "@effect/vitest" -import { Effect, Fiber, Redacted, Stream, String } from "effect" -import { TestClock } from "effect/testing" -import { SqlClient } from "effect/unstable/sql" -import * as Statement from "effect/unstable/sql/Statement" -import { parse as parsePgConnectionString } from "pg-connection-string" -import { PgContainer } from "./utils.ts" - -const compilerTransform = PgClient.makeCompiler(String.camelToSnake) -const transformsNested = Statement.defaultTransforms(String.snakeToCamel) -const transforms = Statement.defaultTransforms(String.snakeToCamel, false) - -it.layer(PgContainer.layerClient, { timeout: "30 seconds" })("PgClient", (it) => { - it.effect("insert helper", () => - Effect.gen(function*() { - const sql = yield* PgClient.PgClient - const [query, params] = sql`INSERT INTO people ${sql.insert({ name: "Tim", age: 10 })}`.compile() - expect(query).toEqual(`INSERT INTO people ("name","age") VALUES ($1,$2)`) - expect(params).toEqual(["Tim", 10]) - })) - - it.effect("updateValues helper", () => - Effect.gen(function*() { - const sql = yield* PgClient.PgClient - const [query, params] = sql`UPDATE people SET name = data.name FROM ${ - sql.updateValues( - [{ name: "Tim" }, { name: "John" }], - "data" - ) - }`.compile() - expect(query).toEqual( - `UPDATE people SET name = data.name FROM (values ($1),($2)) AS data("name")` - ) - expect(params).toEqual(["Tim", "John"]) - })) - - it.effect("updateValues helper returning", () => - Effect.gen(function*() { - const sql = yield* PgClient.PgClient - const [query, params] = sql`UPDATE people SET name = data.name FROM ${ - sql.updateValues( - [{ name: "Tim" }, { name: "John" }], - "data" - ).returning("*") - }`.compile() - expect(query).toEqual( - `UPDATE people SET name = data.name FROM (values ($1),($2)) AS data("name") RETURNING *` - ) - expect(params).toEqual(["Tim", "John"]) - })) - - it.effect("update helper", () => - Effect.gen(function*() { - const sql = yield* PgClient.PgClient - let result = sql`UPDATE people SET ${sql.update({ name: "Tim" })}`.compile() - expect(result[0]).toEqual(`UPDATE people SET "name" = $1`) - expect(result[1]).toEqual(["Tim"]) - - result = sql`UPDATE people SET ${sql.update({ name: "Tim", age: 10 }, ["age"])}`.compile() - expect(result[0]).toEqual(`UPDATE people SET "name" = $1`) - expect(result[1]).toEqual(["Tim"]) - })) - - it.effect("update helper returning", () => - Effect.gen(function*() { - const sql = yield* PgClient.PgClient - const result = sql`UPDATE people SET ${sql.update({ name: "Tim" }).returning("*")}`.compile() - expect(result[0]).toEqual(`UPDATE people SET "name" = $1 RETURNING *`) - expect(result[1]).toEqual(["Tim"]) - })) - - it.effect("array helper", () => - Effect.gen(function*() { - const sql = yield* PgClient.PgClient - const [query, params] = sql`SELECT * FROM ${sql("people")} WHERE id IN ${sql.in([1, 2, "string"])}`.compile() - expect(query).toEqual(`SELECT * FROM "people" WHERE id IN ($1,$2,$3)`) - expect(params).toEqual([1, 2, "string"]) - })) - - it.effect("array helper with column", () => - Effect.gen(function*() { - const sql = yield* PgClient.PgClient - let result = sql`SELECT * FROM ${sql("people")} WHERE ${sql.in("id", [1, 2, "string"])}`.compile() - expect(result[0]).toEqual(`SELECT * FROM "people" WHERE "id" IN ($1,$2,$3)`) - expect(result[1]).toEqual([1, 2, "string"]) - - result = sql`SELECT * FROM ${sql("people")} WHERE ${sql.in("id", [])}`.compile() - expect(result[0]).toEqual(`SELECT * FROM "people" WHERE 1=0`) - expect(result[1]).toEqual([]) - })) - - it.effect("and", () => - Effect.gen(function*() { - const sql = yield* PgClient.PgClient - const now = new Date() - const result = sql`SELECT * FROM ${sql("people")} WHERE ${ - sql.and([ - sql.in("name", ["Tim", "John"]), - sql`created_at < ${now}` - ]) - }`.compile() - expect(result[0]).toEqual(`SELECT * FROM "people" WHERE ("name" IN ($1,$2) AND created_at < $3)`) - expect(result[1]).toEqual(["Tim", "John", now]) - })) - - it("transform nested", () => { - assert.deepEqual( - transformsNested.array([ - { - a_key: 1, - nested: [{ b_key: 2 }], - arr_primitive: [1, "2", true] - } - ]) as any, - [ - { - aKey: 1, - nested: [{ bKey: 2 }], - arrPrimitive: [1, "2", true] - } - ] - ) - }) - - it("transform non nested", () => { - assert.deepEqual( - transforms.array([ - { - a_key: 1, - nested: [{ b_key: 2 }], - arr_primitive: [1, "2", true] - } - ]) as any, - [ - { - aKey: 1, - nested: [{ b_key: 2 }], - arrPrimitive: [1, "2", true] - } - ] - ) - - assert.deepEqual( - transforms.array([ - { - json_field: { - test_value: [1, true, null, "text"], - test_nested: { - test_value: [1, true, null, "text"] - } - } - } - ]) as any, - [ - { - jsonField: { - test_value: [1, true, null, "text"], - test_nested: { - test_value: [1, true, null, "text"] - } - } - } - ] - ) - }) - - it.effect("insert fragments", () => - Effect.gen(function*() { - const sql = yield* PgClient.PgClient - const [query, params] = sql`INSERT INTO people ${ - sql.insert({ - name: "Tim", - age: 10, - json: sql.json({ a: 1 }) - }) - }`.compile() - assert.strictEqual( - query, - "INSERT INTO people (\"name\",\"age\",\"json\") VALUES ($1,$2,$3)" - ) - assert.lengthOf(params, 3) - })) - - it.effect("update fragments", () => - Effect.gen(function*() { - const sql = yield* PgClient.PgClient - const now = new Date() - const [query, params] = sql`UPDATE people SET json = data.json FROM ${ - sql.updateValues( - [{ json: sql.json({ a: 1 }) }, { json: sql.json({ b: 1 }) }], - "data" - ) - } WHERE created_at > ${now}`.compile() - assert.strictEqual( - query, - `UPDATE people SET json = data.json FROM (values ($1),($2)) AS data("json") WHERE created_at > $3` - ) - assert.lengthOf(params, 3) - })) - - it.effect("onDialect", () => - Effect.gen(function*() { - const sql = yield* PgClient.PgClient - assert.strictEqual( - sql.onDialect({ - sqlite: () => "A", - pg: () => "B", - mysql: () => "C", - mssql: () => "D", - clickhouse: () => "E" - }), - "B" - ) - assert.strictEqual( - sql.onDialectOrElse({ - orElse: () => "A", - pg: () => "B" - }), - "B" - ) - })) - - it.effect("identifier transform", () => - Effect.gen(function*() { - const sql = yield* PgClient.PgClient - const [query] = compilerTransform.compile( - sql`SELECT * from ${sql("peopleTest")}`, - false - ) - expect(query).toEqual(`SELECT * from "people_test"`) - })) - - it.effect("jsonb", () => - Effect.gen(function*() { - const sql = yield* PgClient.PgClient - const rows = yield* sql<{ json: unknown }>`select ${{ testValue: 123 }}::jsonb as json` - expect(rows[0].json).toEqual({ testValue: 123 }) - })) - - it.effect("stream", () => - Effect.gen(function*() { - const sql = yield* SqlClient.SqlClient - const rows = yield* sql`SELECT generate_series(1, 3)`.stream.pipe( - Stream.runCollect - ) - expect(rows).toEqual([ - { "generate_series": 1 }, - { "generate_series": 2 }, - { "generate_series": 3 } - ]) - })) -}) - -it.layer(PgContainer.layerClientWithTransforms, { timeout: "30 seconds" })("PgClient transforms", (it) => { - it.effect("insert helper", () => - Effect.gen(function*() { - const sql = yield* PgClient.PgClient - const [query, params] = sql`INSERT INTO people ${sql.insert({ firstName: "Tim", age: 10 })}`.compile() - expect(query).toEqual(`INSERT INTO people ("first_name","age") VALUES ($1,$2)`) - expect(params).toEqual(["Tim", 10]) - })) - - it.effect("insert helper withoutTransforms", () => - Effect.gen(function*() { - const sql = (yield* PgClient.PgClient).withoutTransforms() - const [query, params] = sql`INSERT INTO people ${sql.insert({ first_name: "Tim", age: 10 })}`.compile() - expect(query).toEqual(`INSERT INTO people ("first_name","age") VALUES ($1,$2)`) - expect(params).toEqual(["Tim", 10]) - })) - - it.effect("multi-statement queries", () => - Effect.gen(function*() { - const sql = yield* SqlClient.SqlClient - - const result = yield* sql<{ id: string; name: string }>` - CREATE TABLE test_multi (id TEXT PRIMARY KEY, name TEXT); - INSERT INTO test_multi (id, name) VALUES ('id1', 'test1') RETURNING *; - INSERT INTO test_multi (id, name) VALUES ('id2', 'test2') RETURNING *; - ` - - expect(result).toHaveLength(3) - expect(result[0]).toEqual([]) - expect(result[1]).toEqual([{ id: "id1", name: "test1" }]) - expect(result[2]).toEqual([{ id: "id2", name: "test2" }]) - })) - - it.effect("interruption", () => - Effect.gen(function*() { - const sql = yield* SqlClient.SqlClient - const conn = yield* sql.reserve - yield* conn.executeRaw("select pg_sleep(1000)", []).pipe( - Effect.timeoutOption("50 millis"), - TestClock.withLive - ) - const value = yield* conn.executeValues("select 1", []) - expect(value).toEqual([[1]]) - })) - - it.effect("Should populate config", () => - Effect.gen(function*() { - const sql = yield* PgClient.PgClient - - assert.isDefined(sql.config.url) - - const parsedConfig = parsePgConnectionString(Redacted.value(sql.config.url)) - - expect(sql.config.host).toEqual(parsedConfig.host) - assert.isNotNull(parsedConfig.port) - assert.isDefined(parsedConfig.port) - expect(sql.config.port).toEqual(parseInt(parsedConfig.port)) - expect(sql.config.username).toEqual(parsedConfig.user) - assert.isDefined(sql.config.password) - expect(Redacted.value(sql.config.password)).toEqual(parsedConfig.password) - expect(sql.config.database).toEqual(parsedConfig.database) - })) -}) - -it.layer(PgContainer.layerClientSingleConnection, { timeout: "30 seconds" })("PgClient listen", (it) => { - it.effect("listen does not reserve a pool connection", () => - Effect.gen(function*() { - const sql = yield* PgClient.PgClient - const channel = "pool_connection_listen" - - const listenFiber = yield* sql.listen(channel).pipe( - Stream.take(1), - Stream.runCollect, - Effect.forkScoped - ) - - yield* Effect.sleep("250 millis") - - const rows = yield* sql<{ value: number }>`SELECT 1 as value`.pipe( - Effect.timeoutOrElse({ - duration: "3 seconds", - orElse: () => Effect.fail(new Error("query timed out while listener was active")) - }) - ) - expect(rows).toEqual([{ value: 1 }]) - - yield* sql.notify(channel, "payload") - const payloads = yield* Fiber.join(listenFiber).pipe( - Effect.timeoutOrElse({ - duration: "3 seconds", - orElse: () => Effect.fail(new Error("listener did not receive notification in time")) - }) - ) - expect(Array.from(payloads)).toEqual(["payload"]) - }).pipe(TestClock.withLive), 20_000) - - it.effect("notify sends payload", () => - Effect.gen(function*() { - const sql = yield* PgClient.PgClient - const channel = "pool_connection_notify" - - const listenFiber = yield* sql.listen(channel).pipe( - Stream.take(1), - Stream.runCollect, - Effect.forkScoped - ) - - yield* Effect.sleep("250 millis") - yield* sql.notify(channel, "payload") - - const payloads = yield* Fiber.join(listenFiber).pipe( - Effect.timeoutOrElse({ - duration: "3 seconds", - orElse: () => Effect.fail(new Error("listener did not receive notification in time")) - }) - ) - expect(Array.from(payloads)).toEqual(["payload"]) - }).pipe(TestClock.withLive), 20_000) -}) diff --git a/repos/effect/packages/sql/pg/test/KeyValueStore.test.ts b/repos/effect/packages/sql/pg/test/KeyValueStore.integration.test.ts similarity index 100% rename from repos/effect/packages/sql/pg/test/KeyValueStore.test.ts rename to repos/effect/packages/sql/pg/test/KeyValueStore.integration.test.ts diff --git a/repos/effect/packages/sql/pg/test/Persistence.integration.test.ts b/repos/effect/packages/sql/pg/test/Persistence.integration.test.ts new file mode 100644 index 0000000000..5ffada648a --- /dev/null +++ b/repos/effect/packages/sql/pg/test/Persistence.integration.test.ts @@ -0,0 +1,155 @@ +import { assert, it } from "@effect/vitest" +import { Effect, Exit, Fiber, Latch, Layer, Schema } from "effect" +import * as PersistedCacheTest from "effect-test/unstable/persistence/PersistedCacheTest" +import * as PersistedQueueTest from "effect-test/unstable/persistence/PersistedQueueTest" +import * as SqlCleanupTest from "effect-test/unstable/persistence/SqlCleanupTest" +import { TestClock } from "effect/testing" +import { PersistedQueue, Persistence } from "effect/unstable/persistence" +import { SqlClient } from "effect/unstable/sql" +import { PgContainer } from "./utils.ts" + +PersistedCacheTest.suite( + "sql-pg-multi", + Persistence.layerSqlMultiTable.pipe(Layer.provide(PgContainer.layerClient)) +) + +PersistedCacheTest.suite( + "sql-pg-single", + Persistence.layerSql.pipe(Layer.provide(PgContainer.layerClient)) +) + +PersistedQueueTest.suite( + "sql-pg", + PersistedQueue.layerStoreSql().pipe(Layer.provide(PgContainer.layerClient)) +) + +it.layer(PgContainer.layerClient, { timeout: "30 seconds" })("PersistedQueue SQL locks", (it) => { + it.effect("refreshes locks for acquired elements", () => + Effect.gen(function*() { + const options = { + tableName: "effect_queue_lock_refresh", + pollInterval: "10 millis", + lockRefreshInterval: "100 millis", + lockExpiration: "1 second" + } as const + const store1 = yield* PersistedQueue.makeStoreSql(options) + const store2 = yield* PersistedQueue.makeStoreSql(options) + const element = { message: "hello" } + + yield* store1.offer({ + name: "lock-refresh", + id: crypto.randomUUID(), + element, + isCustomId: false + }) + + const acquired = Latch.makeUnsafe() + const first = yield* Effect.scoped(Effect.gen(function*() { + yield* store1.take({ name: "lock-refresh", maxAttempts: 10 }) + yield* acquired.open + return yield* Effect.never + })).pipe(Effect.forkScoped) + + yield* acquired.await + + const second = yield* Effect.scoped( + store2.take({ name: "lock-refresh", maxAttempts: 10 }) + ).pipe(Effect.forkScoped) + + yield* Effect.sleep("1500 millis") + assert.isUndefined(second.pollUnsafe()) + + yield* Fiber.interrupt(first) + const received = yield* Fiber.join(second) + assert.deepStrictEqual(received.element, element) + }).pipe(TestClock.withLive)) + + it.effect("counts malformed JSON as an attempt and continues", () => + Effect.gen(function*() { + const tableName = "effect_queue_invalid_json" + const store = yield* PersistedQueue.makeStoreSql({ + tableName, + pollInterval: "10 millis" + }) + const factory = yield* PersistedQueue.makeFactory.pipe( + Effect.provideService(PersistedQueue.PersistedQueueStore, store) + ) + const queue = yield* factory.make({ + name: "invalid-json", + schema: Schema.String + }) + const sql = (yield* SqlClient.SqlClient).withoutTransforms() + const table = sql(tableName) + const poisonId = crypto.randomUUID() + + yield* store.offer({ + name: "invalid-json", + id: poisonId, + element: "poison", + isCustomId: false + }) + yield* sql`UPDATE ${table} SET element = ${"{"} WHERE id = ${poisonId}` + yield* queue.offer("valid") + + const malformed = yield* Effect.exit(queue.take(Effect.succeed, { maxAttempts: 1 })) + assert.isTrue(Exit.isFailure(malformed)) + + const rows = yield* sql<{ + readonly attempts: number + readonly last_failure: string | null + }>`SELECT attempts, last_failure FROM ${table} WHERE id = ${poisonId}` + assert.strictEqual(rows[0].attempts, 1) + assert.isNotNull(rows[0].last_failure) + + const value = yield* queue.take(Effect.succeed, { maxAttempts: 1 }) + assert.strictEqual(value, "valid") + }).pipe(TestClock.withLive)) +}) + +it.layer(PgContainer.layerClient, { timeout: "30 seconds" })("Persistence SQL cleanup", (it) => { + it.effect("deletes expired entries in batches", () => + Effect.gen(function*() { + const sql = (yield* SqlClient.SqlClient).withoutTransforms() + const table = sql("effect_persistence") + const expiredCount = sql<{ readonly count: number }>` + SELECT COUNT(*)::INT AS count FROM ${table} WHERE store_id = 'expired' + `.pipe(Effect.map((rows) => rows[0].count)) + yield* sql` + CREATE TABLE ${table} ( + store_id TEXT NOT NULL, + id TEXT NOT NULL, + value TEXT NOT NULL, + expires BIGINT, + PRIMARY KEY (store_id, id) + ) + ` + + const entries = Array.from({ length: SqlCleanupTest.expiredEntryCount }, (_, i) => ({ + store_id: "expired", + id: String(i), + value: "{}", + expires: SqlCleanupTest.expiredAtEpoch + })) + yield* sql`INSERT INTO ${table} ${sql.insert(entries)}`.unprepared + yield* sql` + INSERT INTO ${table} (store_id, id, value, expires) + VALUES ('live', 'live', '{}', NULL), ('live', 'future', '{}', ${SqlCleanupTest.futureExpiresAt}) + ` + + yield* Layer.build(Persistence.layerBackingSql).pipe(TestClock.withLive) + + const expired = yield* SqlCleanupTest.waitForCount(expiredCount, (count) => count === 0) + assert.strictEqual(expired, 0) + const live = yield* sql<{ readonly count: number }>` + SELECT COUNT(*)::INT AS count FROM ${table} WHERE store_id = 'live' + ` + assert.strictEqual(live[0].count, 2) + + const indexes = yield* sql<{ readonly count: number }>` + SELECT COUNT(*)::INT AS count FROM pg_indexes + WHERE tablename = 'effect_persistence' + AND indexname = 'effect_persistence_expires_idx' + ` + assert.strictEqual(indexes[0].count, 1) + }), { timeout: SqlCleanupTest.testTimeout }) +}) diff --git a/repos/effect/packages/sql/pg/test/Persistence.test.ts b/repos/effect/packages/sql/pg/test/Persistence.test.ts deleted file mode 100644 index beed73c02e..0000000000 --- a/repos/effect/packages/sql/pg/test/Persistence.test.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Layer } from "effect" -import * as PersistedCacheTest from "effect-test/unstable/persistence/PersistedCacheTest" -import * as PersistedQueueTest from "effect-test/unstable/persistence/PersistedQueueTest" -import { PersistedQueue, Persistence } from "effect/unstable/persistence" -import { PgContainer } from "./utils.ts" - -PersistedCacheTest.suite( - "sql-pg-multi", - Persistence.layerSqlMultiTable.pipe(Layer.provide(PgContainer.layerClient)) -) - -PersistedCacheTest.suite( - "sql-pg-single", - Persistence.layerSql.pipe(Layer.provide(PgContainer.layerClient)) -) - -PersistedQueueTest.suite( - "sql-pg", - PersistedQueue.layerStoreSql().pipe(Layer.provide(PgContainer.layerClient)) -) diff --git a/repos/effect/packages/sql/pg/test/SqlEventLogServerUnencrypted.test.ts b/repos/effect/packages/sql/pg/test/SqlEventLogServerUnencrypted.integration.test.ts similarity index 100% rename from repos/effect/packages/sql/pg/test/SqlEventLogServerUnencrypted.test.ts rename to repos/effect/packages/sql/pg/test/SqlEventLogServerUnencrypted.integration.test.ts diff --git a/repos/effect/packages/sql/pg/test/utils.ts b/repos/effect/packages/sql/pg/test/utils.ts index 1dca047e45..cd89648439 100644 --- a/repos/effect/packages/sql/pg/test/utils.ts +++ b/repos/effect/packages/sql/pg/test/utils.ts @@ -26,6 +26,15 @@ export class PgContainer extends Context.Service()("test/PgContaine }) ).pipe(Layer.provide(this.layer)) + static layerMakeClient = Layer.unwrap( + Effect.gen(function*() { + const container = yield* PgContainer + return PgClient.layerFrom(PgClient.makeClient({ + url: Redacted.make(container.getConnectionUri()) + })) + }) + ).pipe(Layer.provide(this.layer)) + static layerClientWithTransforms = Layer.unwrap( Effect.gen(function*() { const container = yield* PgContainer diff --git a/repos/effect/packages/sql/pg/tsconfig.json b/repos/effect/packages/sql/pg/tsconfig.json index 8380d95771..a731188da0 100644 --- a/repos/effect/packages/sql/pg/tsconfig.json +++ b/repos/effect/packages/sql/pg/tsconfig.json @@ -1,5 +1,5 @@ { - "$schema": "http://json.schemastore.org/tsconfig", + "$schema": "https://json.schemastore.org/tsconfig", "extends": "../../../tsconfig.base.json", "include": ["src"], "references": [ diff --git a/repos/effect/packages/sql/pg/vitest.config.ts b/repos/effect/packages/sql/pg/vitest.config.ts deleted file mode 100644 index c8a52c1826..0000000000 --- a/repos/effect/packages/sql/pg/vitest.config.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { mergeConfig, type ViteUserConfig } from "vitest/config" -import shared from "../../../vitest.shared.ts" - -const config: ViteUserConfig = {} - -export default mergeConfig(shared, config) diff --git a/repos/effect/packages/sql/pglite/CHANGELOG.md b/repos/effect/packages/sql/pglite/CHANGELOG.md index 7129e4cf1c..8417177550 100644 --- a/repos/effect/packages/sql/pglite/CHANGELOG.md +++ b/repos/effect/packages/sql/pglite/CHANGELOG.md @@ -1,5 +1,126 @@ # @effect/sql-pglite +## 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 + +- Updated dependencies [[`0418564`](https://github.com/Effect-TS/effect/commit/04185644dabb8e4169f1ef6cbbc0b36c4db2f7f4), [`d334a85`](https://github.com/Effect-TS/effect/commit/d334a8593aafcd60753995a9449b654c67bfdcc1), [`f0be855`](https://github.com/Effect-TS/effect/commit/f0be8554da6ee00293a6b23869ac46a0b0d97dc8), [`b206fa5`](https://github.com/Effect-TS/effect/commit/b206fa5d7655c1634c9993410a9203f6616a5ca2), [`b938c8a`](https://github.com/Effect-TS/effect/commit/b938c8ad2823bd88493187922f7d9090eff037b6), [`8525f05`](https://github.com/Effect-TS/effect/commit/8525f05d1e14ea12298e9e1a0df497bfaac2ce9a)]: + - effect@4.0.0-beta.105 + +## 4.0.0-beta.104 + +### Patch Changes + +- Updated dependencies [[`1001bcc`](https://github.com/Effect-TS/effect/commit/1001bccb9e874918d59dbb36860f1c5d4499ac20), [`993ba60`](https://github.com/Effect-TS/effect/commit/993ba60ee6c7ca6eb84522040f8b0d268b6ba7d4), [`67faacd`](https://github.com/Effect-TS/effect/commit/67faacd4679242559bee31717c05a5b10b990322), [`b78acdf`](https://github.com/Effect-TS/effect/commit/b78acdf422568f10ae8684fd3f10d52b065f0b56), [`fbb9ce5`](https://github.com/Effect-TS/effect/commit/fbb9ce5e625d1a7d6b7005bda42cbb6cd31476c5), [`722ea48`](https://github.com/Effect-TS/effect/commit/722ea484c9d01364c9242d929c0a564f7831a57c), [`3058fd5`](https://github.com/Effect-TS/effect/commit/3058fd594f5a683034212d71d957017fcc084006), [`62d0575`](https://github.com/Effect-TS/effect/commit/62d057566c241405c23ecf0bf4156186bd2be924), [`99dd6b5`](https://github.com/Effect-TS/effect/commit/99dd6b580434f97c5b40adc919f429e4abc3dfe7), [`7963ce1`](https://github.com/Effect-TS/effect/commit/7963ce1cd95f037fbefea67a29ead49cce4d16cb), [`af14e75`](https://github.com/Effect-TS/effect/commit/af14e752edd65e2b652e960411afafc88975a8d8), [`24e22d2`](https://github.com/Effect-TS/effect/commit/24e22d23a73a2e93ebf6d8edd2246a4a406942c8), [`647d14e`](https://github.com/Effect-TS/effect/commit/647d14e572c8004fa92fba256e00552b42bf34b7), [`1434eec`](https://github.com/Effect-TS/effect/commit/1434eecbd368e00839c24b3950f0b7a69218669a), [`a5278b1`](https://github.com/Effect-TS/effect/commit/a5278b18242011d1b2b08304c7c128151f9a4370), [`6af04a5`](https://github.com/Effect-TS/effect/commit/6af04a50bd019238f6acdb9cbda40439a3c09210), [`cb6c837`](https://github.com/Effect-TS/effect/commit/cb6c8376b2f322d4e7cbfc0973fc3b4f2951ee6e), [`d44cead`](https://github.com/Effect-TS/effect/commit/d44cead7e0e0ce61f0d980906e494f49a07e7899), [`88c7632`](https://github.com/Effect-TS/effect/commit/88c7632c2b59a49fcc40d250865bd8d0dccf31b0), [`abcbb2a`](https://github.com/Effect-TS/effect/commit/abcbb2abe16f1b6c587c15007df14371e1e70e93), [`8f63cce`](https://github.com/Effect-TS/effect/commit/8f63cce636700fde26b140b82e350ef916989d86), [`d56dfcf`](https://github.com/Effect-TS/effect/commit/d56dfcf54c2b9c53c3d098ce4b0ffcc84496c5f7), [`a98cda9`](https://github.com/Effect-TS/effect/commit/a98cda9422e1352f22e81696f759f326ffcfb667), [`6704bb8`](https://github.com/Effect-TS/effect/commit/6704bb84c320547f83cf50e8586ffc4c5e4c3cc5), [`6143de2`](https://github.com/Effect-TS/effect/commit/6143de21ee22038b45a8d4eba86f5aade6238eba), [`936b135`](https://github.com/Effect-TS/effect/commit/936b1358396eb0a1a7c8e0878ba63297e2106812), [`1bbae84`](https://github.com/Effect-TS/effect/commit/1bbae84f88b577a26d04ceb2e76d3143d09c4a20), [`d795ee7`](https://github.com/Effect-TS/effect/commit/d795ee771701ea62bd187ef7c0307d9737f68c1a), [`0a82d88`](https://github.com/Effect-TS/effect/commit/0a82d88b7da73278b6f270118e396d5ed4a64747), [`9215bc5`](https://github.com/Effect-TS/effect/commit/9215bc5da7dd10aa45f07fe44b98f06b6e433d62), [`a1b5df2`](https://github.com/Effect-TS/effect/commit/a1b5df2064d92431cfc6e638af613cc3114313d7), [`92a9ac5`](https://github.com/Effect-TS/effect/commit/92a9ac5ac0aa63d8975b9ba7a094d6a8f59a98f2), [`6bde7f2`](https://github.com/Effect-TS/effect/commit/6bde7f27f3243427203e53fe74472990e5c2a349), [`a712131`](https://github.com/Effect-TS/effect/commit/a7121310dbb60cbd819bbd702f97663098ec7bb8), [`2e6f760`](https://github.com/Effect-TS/effect/commit/2e6f760dcb44e2b984f3311a8af03a1d68a2ec7e), [`aa05804`](https://github.com/Effect-TS/effect/commit/aa0580497e027ed30b756058db0067c3fe07664f), [`badd3bf`](https://github.com/Effect-TS/effect/commit/badd3bf65fac4dd1e66e1f602db43659722dfced), [`02b0265`](https://github.com/Effect-TS/effect/commit/02b02651ede46a5a2dd3ef8081d0ad89648d0cbf), [`3437e21`](https://github.com/Effect-TS/effect/commit/3437e21a56d805781c5e5946a6189795a1dfd411), [`41a550d`](https://github.com/Effect-TS/effect/commit/41a550d1fed31e829929a8f5362b5340303164ac), [`17b5d50`](https://github.com/Effect-TS/effect/commit/17b5d50219ad49533cf9e33d01924a3e16af5eb3), [`96e5e95`](https://github.com/Effect-TS/effect/commit/96e5e9576b0315c747462761a61940ff9fe32dd1), [`e4d589e`](https://github.com/Effect-TS/effect/commit/e4d589e0ea08dc57c4793053b395dc0fcc499f34), [`ae4cf7b`](https://github.com/Effect-TS/effect/commit/ae4cf7b5e2cb5f8c55657e31a61789ad21c38c18), [`6ef5f1a`](https://github.com/Effect-TS/effect/commit/6ef5f1a041f3a40bf03fadd0b1feb275c277c635), [`2235a29`](https://github.com/Effect-TS/effect/commit/2235a29502c3f33cf6468511ad931089013a7916), [`b32f4cb`](https://github.com/Effect-TS/effect/commit/b32f4cb7b2d8ebe817075322622498e3beb05336), [`7f4c095`](https://github.com/Effect-TS/effect/commit/7f4c095b62da43780dd7fc2a5d1785ddfce60edf), [`5f3fb81`](https://github.com/Effect-TS/effect/commit/5f3fb814d18d8a54946c1c1cd0b41459cdb24006), [`17f0b91`](https://github.com/Effect-TS/effect/commit/17f0b91a243ccfe4a38d27debdc983adf434e738), [`0cdadd7`](https://github.com/Effect-TS/effect/commit/0cdadd75bc8abbbcad7956a4bc71f4e7a9b13250), [`39b57d7`](https://github.com/Effect-TS/effect/commit/39b57d7857358040558b67dd33eafc7bb5457830), [`5a6a573`](https://github.com/Effect-TS/effect/commit/5a6a5738e5bfc39e3a37ae7ba99081601fa19ac3), [`59f5e99`](https://github.com/Effect-TS/effect/commit/59f5e9981913b92d7a9beb2214a21d658b999d3a), [`45379d6`](https://github.com/Effect-TS/effect/commit/45379d6179ee4df2cbd3f848bd39ff7149c24a38), [`1949439`](https://github.com/Effect-TS/effect/commit/1949439175809ef81ab9c6411ed5559109edb4c9), [`e443403`](https://github.com/Effect-TS/effect/commit/e443403cf0e4effea14bb6cd950c5ac1c86cc748), [`03af7e8`](https://github.com/Effect-TS/effect/commit/03af7e85551204c605ea2fa2c43c10a4538ac8fb), [`0f721d4`](https://github.com/Effect-TS/effect/commit/0f721d406df8703ea92ca28777b3f09599e2056d), [`130b28d`](https://github.com/Effect-TS/effect/commit/130b28df552d7053407b041a96ff09dae82575e5), [`c987a12`](https://github.com/Effect-TS/effect/commit/c987a12a01b6a52ad53d29edf02613b03574dbcc), [`4158562`](https://github.com/Effect-TS/effect/commit/41585620977de9b84171f76619b72e29cc2284e5), [`306014a`](https://github.com/Effect-TS/effect/commit/306014a1ce4d5cb956c76bdc20e4e28ab3e61a6a), [`729a663`](https://github.com/Effect-TS/effect/commit/729a663275dd31f2357c446fe69664429220a83d), [`caf84b6`](https://github.com/Effect-TS/effect/commit/caf84b660044089e8d7f4067b279b27b8b50e8fd), [`ce067f7`](https://github.com/Effect-TS/effect/commit/ce067f799ea27735d4194345298a216aaf429f01), [`7a41f5a`](https://github.com/Effect-TS/effect/commit/7a41f5aa72d540ecf2746992ecc3fa3e6b40d31f), [`781022a`](https://github.com/Effect-TS/effect/commit/781022acdd3537ca18c88e2fa3681bafa6ef1b21), [`39f1297`](https://github.com/Effect-TS/effect/commit/39f1297acc08864feb12de6b8cf2bf73434f6cf5), [`2db266b`](https://github.com/Effect-TS/effect/commit/2db266b1bfbc81868bc1778c37c76032a267c79f), [`2141e28`](https://github.com/Effect-TS/effect/commit/2141e28903754d72604acf81673ceb2c62a56646), [`3c5e429`](https://github.com/Effect-TS/effect/commit/3c5e429878669ffcf5e0da4ddfbf50bde5bbcaad), [`20ddc63`](https://github.com/Effect-TS/effect/commit/20ddc630584f8fe488162ba384adcae53fc6810a), [`841b3ea`](https://github.com/Effect-TS/effect/commit/841b3ea6ae19a784bc1c20497b02f632af0c91e9), [`82a3fbf`](https://github.com/Effect-TS/effect/commit/82a3fbfce8b9df33e587076b7d7168ecd6799e17), [`eb9ee83`](https://github.com/Effect-TS/effect/commit/eb9ee83b38844a71d1cd5653a229309cfcb04a36), [`64dc7c7`](https://github.com/Effect-TS/effect/commit/64dc7c76dc5c89887b9e7c181d1873dcbb7820d1), [`84dc8ab`](https://github.com/Effect-TS/effect/commit/84dc8ab7accc682bc668c78a97e4a1776b633be8), [`b4463f4`](https://github.com/Effect-TS/effect/commit/b4463f46fc33d3b01ea5eadd7d012a5abda347a3), [`592dd36`](https://github.com/Effect-TS/effect/commit/592dd361645739ac0cd8e6babb084cd27403c172), [`85d2b44`](https://github.com/Effect-TS/effect/commit/85d2b446e3059de4919be730105868f79728308d), [`32e4a69`](https://github.com/Effect-TS/effect/commit/32e4a69b3151b7ec4058af2213b96a41d11e9e06), [`13c5872`](https://github.com/Effect-TS/effect/commit/13c5872ed30830360367ad89af2dab68a003c351), [`3454cdb`](https://github.com/Effect-TS/effect/commit/3454cdb528fdb5d3ed0c5c5c8169bc47de41fbd8), [`e930804`](https://github.com/Effect-TS/effect/commit/e9308045be1d8a00c0b4046f1e8ff22cf68c93da), [`7f12d4b`](https://github.com/Effect-TS/effect/commit/7f12d4b4e731dc3a213ae5c3f60db9edc50292d2), [`181c9ef`](https://github.com/Effect-TS/effect/commit/181c9ef5e5d4ab247bf4aec06424f15b0a1e802e), [`dd9f891`](https://github.com/Effect-TS/effect/commit/dd9f891e23f316abb6192893008f0e33ece9d97d), [`433fb81`](https://github.com/Effect-TS/effect/commit/433fb81ca4c15c681a8ae097ce3ff9bd3a9c9aa5), [`8459cdb`](https://github.com/Effect-TS/effect/commit/8459cdbae8a476dc04b6247fffe6a1668dcb1217), [`6124ab3`](https://github.com/Effect-TS/effect/commit/6124ab39eb64688fbd5d688d24766542f9cb5a2c), [`01bd954`](https://github.com/Effect-TS/effect/commit/01bd9546f142706fca1628f7261e6d1cb9638948), [`ba2c3aa`](https://github.com/Effect-TS/effect/commit/ba2c3aa05eb87ec05d263b960017ecf29746f66a), [`0a45ef3`](https://github.com/Effect-TS/effect/commit/0a45ef3bb4a1ae9b345c43c548db4336a31b3191), [`8459cdb`](https://github.com/Effect-TS/effect/commit/8459cdbae8a476dc04b6247fffe6a1668dcb1217), [`eaa7e71`](https://github.com/Effect-TS/effect/commit/eaa7e71b88bf59b24610128c6115a2a126432731), [`db4c2cc`](https://github.com/Effect-TS/effect/commit/db4c2ccdec77d813b6f4cc72a302ee7c4fe6e39d), [`22f150a`](https://github.com/Effect-TS/effect/commit/22f150a0936cef30517e87eaca73bff1c5e4873a), [`90ffb08`](https://github.com/Effect-TS/effect/commit/90ffb083b3091c211300f50a42ba7bf56536c0ee), [`d517692`](https://github.com/Effect-TS/effect/commit/d517692ef75f45d5f6d9d68b32d41fa0ccc56c99), [`01af079`](https://github.com/Effect-TS/effect/commit/01af079c189d1fc5067d3b1933b2870c4baf2693), [`32a59e8`](https://github.com/Effect-TS/effect/commit/32a59e8058b1ec9738cb083cf1cb116b393ca114)]: + - effect@4.0.0-beta.104 + +## 4.0.0-beta.103 + +### Patch Changes + +- [#6701](https://github.com/Effect-TS/effect/pull/6701) [`9867b9f`](https://github.com/Effect-TS/effect/commit/9867b9fc69f9cc6c443594fc7eccc7be0c674d9c) Thanks @fubhy! - Removed explicit ./index entrypoints + +- Updated dependencies [[`e56cd8f`](https://github.com/Effect-TS/effect/commit/e56cd8f90c3559baccf8fcf2852ea911235d5944), [`f77c120`](https://github.com/Effect-TS/effect/commit/f77c120d8e04779ddeb8bce8e9cde932f268e4b6), [`b2f95a9`](https://github.com/Effect-TS/effect/commit/b2f95a9c2f2581deb89dc3bae9e89cf819e82923), [`04fd44a`](https://github.com/Effect-TS/effect/commit/04fd44a42abfa8dc2642300dcf49ee48c8ef4539), [`b74333d`](https://github.com/Effect-TS/effect/commit/b74333d83e15b9d042e4698ad23040de60454afe), [`1c40b28`](https://github.com/Effect-TS/effect/commit/1c40b2809503d6aa1358777196fc66317906e657), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`b3901d2`](https://github.com/Effect-TS/effect/commit/b3901d29c543fd5bd05ceec669a17896c8e19006), [`4a0984a`](https://github.com/Effect-TS/effect/commit/4a0984af62738fedf4bd3e87adb4d4d641ce9147), [`fffd88b`](https://github.com/Effect-TS/effect/commit/fffd88b3135abdf928ca7c4b0e00e610985091c7), [`f3f6c1e`](https://github.com/Effect-TS/effect/commit/f3f6c1e02cb543423fcffef5dc2db03fac503588), [`ef07642`](https://github.com/Effect-TS/effect/commit/ef07642dfe671d5258b65d1c1480c4d05c495f15), [`f1bc827`](https://github.com/Effect-TS/effect/commit/f1bc8274a608813d7b09d28dcca04adbf62f8c92), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`081f4d8`](https://github.com/Effect-TS/effect/commit/081f4d8cd06a2ac222d2810b46e61efcee26939e), [`5287b24`](https://github.com/Effect-TS/effect/commit/5287b24f5f8fa094ba20e117bfb1a80fba6d2cf5), [`13d31cf`](https://github.com/Effect-TS/effect/commit/13d31cfc2dde46210e94391b5b6767ae9aeaf2c9), [`acee269`](https://github.com/Effect-TS/effect/commit/acee26944bc89ee554d7b9fadab7443f9edc28a9), [`31170c1`](https://github.com/Effect-TS/effect/commit/31170c19b236c37abb5476c821bc6f5bfa2735ab), [`205ebc7`](https://github.com/Effect-TS/effect/commit/205ebc776062012581e98fced7ced19adfc44ee7), [`ed0ebf8`](https://github.com/Effect-TS/effect/commit/ed0ebf8e5c864d46fed1f232e99c0e680f10a58f), [`a3fd084`](https://github.com/Effect-TS/effect/commit/a3fd08482157bd78b089f77c7b173d54ef68b5cd), [`ee29ddf`](https://github.com/Effect-TS/effect/commit/ee29ddf862c3723ad466abc93ab6f6fe723b2319), [`6086309`](https://github.com/Effect-TS/effect/commit/60863090af8e5af0bfa1435f08dc5390f9993e30), [`4a57af2`](https://github.com/Effect-TS/effect/commit/4a57af24011db1d66e947289d2f7ffc2074696d2), [`660875b`](https://github.com/Effect-TS/effect/commit/660875b4325e6eebb3f04513998301cd2a0847ec), [`8e7c706`](https://github.com/Effect-TS/effect/commit/8e7c706b0aca855489b53d987404566d3e9cb5e7), [`5f63adb`](https://github.com/Effect-TS/effect/commit/5f63adbe75fc9d50d23706a52b3e483ad2a1a01c), [`053bc42`](https://github.com/Effect-TS/effect/commit/053bc42e2a964755611a216e78ed214322efee37), [`c0a1534`](https://github.com/Effect-TS/effect/commit/c0a153494484ecf9f0d0f20895a7a648b4be363b), [`f1e3a37`](https://github.com/Effect-TS/effect/commit/f1e3a378c144f974a6122b299f421b75595af20f), [`cedb01a`](https://github.com/Effect-TS/effect/commit/cedb01a025492a1faf9e59eb23eb96bc3b5e2fff), [`1747440`](https://github.com/Effect-TS/effect/commit/1747440de9a51a56ed3660da748cc01b256adce7), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`b4f1ee2`](https://github.com/Effect-TS/effect/commit/b4f1ee238d96aa78c5f040158cb78671d75b381e), [`a4757f1`](https://github.com/Effect-TS/effect/commit/a4757f1c47067d8d016a6c4a2c541bb8ae520f9b), [`cd122b9`](https://github.com/Effect-TS/effect/commit/cd122b90300d995a237993a2edb7a049785ab6a4), [`5de588b`](https://github.com/Effect-TS/effect/commit/5de588b2472fb0f4eb919766eb8472583a044772), [`3895b9c`](https://github.com/Effect-TS/effect/commit/3895b9cf179262cd277a9c6daafe9050dcf8265e), [`89ce5f3`](https://github.com/Effect-TS/effect/commit/89ce5f3e16e23a193daa475dc72ea8133ae1dacd), [`985de09`](https://github.com/Effect-TS/effect/commit/985de097d75906db2aed784841f81e23cc978b43), [`9800e3a`](https://github.com/Effect-TS/effect/commit/9800e3acc8f36530f671bc8b91558cb112f449a7), [`4dc35f6`](https://github.com/Effect-TS/effect/commit/4dc35f64641746366f867ea3dbfedb9cd4685ada), [`e8eb62b`](https://github.com/Effect-TS/effect/commit/e8eb62b3d0ef27e9761cdc2eb93bdec52d6ee204), [`ecd9993`](https://github.com/Effect-TS/effect/commit/ecd99936112cb69efdb02de3a2fd57f47baefdf3), [`5ab9c08`](https://github.com/Effect-TS/effect/commit/5ab9c08463ce049c45f3502676954a7b72c6b024), [`f5cf965`](https://github.com/Effect-TS/effect/commit/f5cf96548afd51f4b3cf1aea11b04d7f8549ce90), [`a94cbed`](https://github.com/Effect-TS/effect/commit/a94cbed84e9e49bea4bff925599c0f19c4e3deab), [`9160ad7`](https://github.com/Effect-TS/effect/commit/9160ad7d146d4376dd12f7510c025e5b2f638a70), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`52494be`](https://github.com/Effect-TS/effect/commit/52494be9e8eb3bb542d06a3dfefc6bca4e168984), [`5441c8e`](https://github.com/Effect-TS/effect/commit/5441c8e656a6418c0d27feb2df67565a3e1155f4), [`c9b56ab`](https://github.com/Effect-TS/effect/commit/c9b56ab507f224426ee8388dc450da447ec4715f), [`8ef7257`](https://github.com/Effect-TS/effect/commit/8ef72577d1f43212cab87951d659e54e3c8d7d91), [`1519406`](https://github.com/Effect-TS/effect/commit/1519406fed6e8b017ae178dc20bcaa2cf318b570), [`9716990`](https://github.com/Effect-TS/effect/commit/97169902eec3c99baa7f0b2c7b45a0a5eae75819), [`733f75b`](https://github.com/Effect-TS/effect/commit/733f75b7125e3016a975fdd251c0179ae5393786), [`48155c8`](https://github.com/Effect-TS/effect/commit/48155c8ccfc12dcca8a00fa358d50b20c30874e4), [`951d06b`](https://github.com/Effect-TS/effect/commit/951d06b83d459d3e8fa9024e727a5db1662d3322), [`d767b65`](https://github.com/Effect-TS/effect/commit/d767b65a7687e38be23f0b0ee3d52ab5f2360cbe), [`5d52d9d`](https://github.com/Effect-TS/effect/commit/5d52d9d148aaa7f736ed8c310fc8bfa9dc81badf), [`f4151e1`](https://github.com/Effect-TS/effect/commit/f4151e1937c26de14f1d64566f8126173f1b5014), [`e02fbb6`](https://github.com/Effect-TS/effect/commit/e02fbb66f5a0f13dba6c33ef63528a37a17a0676), [`724ce09`](https://github.com/Effect-TS/effect/commit/724ce09650a458d4565e5c7331ea92ca04f08e68), [`dbe91f6`](https://github.com/Effect-TS/effect/commit/dbe91f6961ef9f7e8da910ee5758d9c0d385fca8), [`4c008d2`](https://github.com/Effect-TS/effect/commit/4c008d28b370d817f7ae4579db09836fe084c8d2), [`b650832`](https://github.com/Effect-TS/effect/commit/b6508328708a842f3163467b72486bd228f1a289), [`b46c92f`](https://github.com/Effect-TS/effect/commit/b46c92f3b314f4ffd612b831efa55dd856c587a3), [`5335797`](https://github.com/Effect-TS/effect/commit/5335797003076d9c6fd170da98d779696d555596), [`4b3460d`](https://github.com/Effect-TS/effect/commit/4b3460daa434ec465a95a50704fe1103a9275999), [`6301fd7`](https://github.com/Effect-TS/effect/commit/6301fd710b4325718de2c42997dac28a9e9aa250), [`aebc5c6`](https://github.com/Effect-TS/effect/commit/aebc5c61664b89a840465ec65b79ce635a5ceee8), [`52b2d7b`](https://github.com/Effect-TS/effect/commit/52b2d7b5bd3c7cce3bd5b69c6ab3941004da70f3), [`eec5744`](https://github.com/Effect-TS/effect/commit/eec57445dfa0ef3c5977195ad69415b7e7d42bb6), [`24e0e93`](https://github.com/Effect-TS/effect/commit/24e0e93dc307dc2c2ae86caacb7289e1dab3c103), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`1a7ce81`](https://github.com/Effect-TS/effect/commit/1a7ce8150e3977586c44d8ccb9a8384389bb4d49), [`48f22a7`](https://github.com/Effect-TS/effect/commit/48f22a7d16ae57ee2175d450dafbdeb69e187d2a), [`c96b7f6`](https://github.com/Effect-TS/effect/commit/c96b7f6359662053c3e09344f61dddc7a6caf4ac), [`6d2a942`](https://github.com/Effect-TS/effect/commit/6d2a942ed7cd33b8fd79d549edba33bc9e2a7e3e), [`cc27b19`](https://github.com/Effect-TS/effect/commit/cc27b194b9d13fa3a66ab037e853fca9d41700ff), [`8f9499f`](https://github.com/Effect-TS/effect/commit/8f9499f562729f5f7b08d8bcc4db86b4aeff8a21), [`3eeea73`](https://github.com/Effect-TS/effect/commit/3eeea73cfc3e9b126975c2ddbdb7f7c8c92026e2), [`0a532e5`](https://github.com/Effect-TS/effect/commit/0a532e503f165fdea485a5343fc2f420917e8376), [`f398149`](https://github.com/Effect-TS/effect/commit/f398149c134fd9b67b6cdc52eae3f3248d5c7bbe), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`ace903e`](https://github.com/Effect-TS/effect/commit/ace903e09c2549ceebdec380797beb027cd29f3d), [`e8eb62b`](https://github.com/Effect-TS/effect/commit/e8eb62b3d0ef27e9761cdc2eb93bdec52d6ee204), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`48f22a7`](https://github.com/Effect-TS/effect/commit/48f22a7d16ae57ee2175d450dafbdeb69e187d2a), [`d48506d`](https://github.com/Effect-TS/effect/commit/d48506d97525040aa714305e928126df799795b4), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`d48506d`](https://github.com/Effect-TS/effect/commit/d48506d97525040aa714305e928126df799795b4), [`d48506d`](https://github.com/Effect-TS/effect/commit/d48506d97525040aa714305e928126df799795b4), [`52262be`](https://github.com/Effect-TS/effect/commit/52262be2edce0e350c6ac10f8f725678606399c5), [`1284aa1`](https://github.com/Effect-TS/effect/commit/1284aa183451955ad7921bbe01fd0e095695d444), [`9867b9f`](https://github.com/Effect-TS/effect/commit/9867b9fc69f9cc6c443594fc7eccc7be0c674d9c), [`d0f1a22`](https://github.com/Effect-TS/effect/commit/d0f1a2295155c350b04efb46852cb40032805273), [`979ce39`](https://github.com/Effect-TS/effect/commit/979ce3985d7d62ce2bf240681ca19feda3027452), [`b6d3e67`](https://github.com/Effect-TS/effect/commit/b6d3e67c7cc143cd8470cdf704324e79d23954a9), [`adf6c6c`](https://github.com/Effect-TS/effect/commit/adf6c6cd388af8a3c0c546492e71555368556f6a), [`7314d60`](https://github.com/Effect-TS/effect/commit/7314d605284717aaafe7fc34b88c3c93397e865c), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`1acbd8b`](https://github.com/Effect-TS/effect/commit/1acbd8b44c68ebb23735e9810476b870dbe58aea), [`7bde6cc`](https://github.com/Effect-TS/effect/commit/7bde6ccb2b144fe953ff30a7ef5e1ecc97697146), [`a959a8b`](https://github.com/Effect-TS/effect/commit/a959a8bf21cdb976369f494dc949fa00a050d3e0)]: + - effect@4.0.0-beta.103 + +## 4.0.0-beta.102 + +### Patch Changes + +- Updated dependencies [[`b6392e1`](https://github.com/Effect-TS/effect/commit/b6392e119704553edec1b4fd2869ac0dbec621ef), [`7ed9450`](https://github.com/Effect-TS/effect/commit/7ed945044eb56aa9aeaf62d4746a011c96c58628), [`45762bd`](https://github.com/Effect-TS/effect/commit/45762bd78df9ecd87c98b8d3738cdeeac7d81128), [`a6e8391`](https://github.com/Effect-TS/effect/commit/a6e8391cd31acd898fae18b3f8e7ca4c6f14f065), [`4ac7e8b`](https://github.com/Effect-TS/effect/commit/4ac7e8b136c61a26c3e438c013dfd7349b38e999), [`4cd40f5`](https://github.com/Effect-TS/effect/commit/4cd40f5692477783bef84fed3c5ef1c0cf5602e6), [`6956bc0`](https://github.com/Effect-TS/effect/commit/6956bc0e6cb27f53fbec39d9b18545940f9f598f), [`0e50ec7`](https://github.com/Effect-TS/effect/commit/0e50ec7dbb94390666f292cf9120719bf30a7246), [`9fcdade`](https://github.com/Effect-TS/effect/commit/9fcdade4a8af772b9ccd8b8a24fe8cee0e5d8470), [`57367d5`](https://github.com/Effect-TS/effect/commit/57367d54de55047ff0c5fce9685475e236bf354c), [`35c445f`](https://github.com/Effect-TS/effect/commit/35c445ff18029d192900ea0914c993f58d5cf1a5), [`c917bb9`](https://github.com/Effect-TS/effect/commit/c917bb94a4c1c4e0a24372a8ebb8a5ca232e36b5), [`bc1f358`](https://github.com/Effect-TS/effect/commit/bc1f3583e63344cb2c398d9040d9c975488ed123), [`0e0c9d7`](https://github.com/Effect-TS/effect/commit/0e0c9d7922ff463c1093d9e0576fae12cb0698d5), [`73d40aa`](https://github.com/Effect-TS/effect/commit/73d40aacd8fcae1b48c23f5b0a5c542127401d1d), [`4f1e318`](https://github.com/Effect-TS/effect/commit/4f1e3183f7123591c46224e9c587df7594562a5f), [`9d8d85c`](https://github.com/Effect-TS/effect/commit/9d8d85c1bb7da51970845b8ea830e386e777514a), [`6079fda`](https://github.com/Effect-TS/effect/commit/6079fda7b02f2f01ad91c15ab8c307336f3ba252), [`5101e92`](https://github.com/Effect-TS/effect/commit/5101e92c9c149c153423f43dd7a94f6194653c06), [`d0b3265`](https://github.com/Effect-TS/effect/commit/d0b3265c3262670761471ab3518cf933b1b3b20a), [`7a03c89`](https://github.com/Effect-TS/effect/commit/7a03c893ce6492bf94c0ebfb00b63bf25dcbf83e), [`cea1d9c`](https://github.com/Effect-TS/effect/commit/cea1d9c92601e69ebda040af8a1d860d604d885c), [`078e1f5`](https://github.com/Effect-TS/effect/commit/078e1f5636e31b76a86722a636afc37a8cc25580), [`97bafea`](https://github.com/Effect-TS/effect/commit/97bafeab460833b9781527b437d1cb9cbee63260), [`fab0ab8`](https://github.com/Effect-TS/effect/commit/fab0ab8f7ab15ae596faa4ccf75615a494d11b0b), [`c323d8b`](https://github.com/Effect-TS/effect/commit/c323d8b30dbbe85f9df25b67288b93d5332de333), [`6966353`](https://github.com/Effect-TS/effect/commit/69663534d626003eb10a5e55ab1f13e0379fead1), [`0444004`](https://github.com/Effect-TS/effect/commit/04440041989c1785fe4db286379f2be2c15baa85), [`028bbb3`](https://github.com/Effect-TS/effect/commit/028bbb391e161185da10d974ab33381f769940d7), [`ff5d6e2`](https://github.com/Effect-TS/effect/commit/ff5d6e278a1fdff714315dc1a17075012f05c1f0), [`1bfce93`](https://github.com/Effect-TS/effect/commit/1bfce93e6d2bf0794c11733daf51c2390e7de375), [`7ce815c`](https://github.com/Effect-TS/effect/commit/7ce815cd5af6af991dfc13b890fd22345fc77c20), [`7271a7f`](https://github.com/Effect-TS/effect/commit/7271a7faf1080aa75f2f53ca6a0b5ec9334c1d38), [`475fe5c`](https://github.com/Effect-TS/effect/commit/475fe5c12c2d6504c475797c0634f90da01e1797)]: + - effect@4.0.0-beta.102 + +## 4.0.0-beta.101 + +### Patch Changes + +- Updated dependencies [[`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`b35ed29`](https://github.com/Effect-TS/effect/commit/b35ed2904f01536d303b21f288daf343cf740462), [`dd44624`](https://github.com/Effect-TS/effect/commit/dd446245736a0e88c807a02f03c21450bb9340fa), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`2bae1ac`](https://github.com/Effect-TS/effect/commit/2bae1accce9d3b72cf6d5aefc9b2161af6d88436)]: + - effect@4.0.0-beta.101 + +## 4.0.0-beta.100 + +### Patch Changes + +- Updated dependencies [[`c1288dd`](https://github.com/Effect-TS/effect/commit/c1288dd1a52a2811ab7df57fc4ce236c6be4c745), [`2b58a3d`](https://github.com/Effect-TS/effect/commit/2b58a3dab6bc99776dddaf76e27d811e0f47f3d8), [`6dc83f2`](https://github.com/Effect-TS/effect/commit/6dc83f26ddf20d48db28cf761dd8f3716e5273fb), [`c1e2fe0`](https://github.com/Effect-TS/effect/commit/c1e2fe0cf93564f4d919e3998874c3e70b0cf30f), [`f3fbae8`](https://github.com/Effect-TS/effect/commit/f3fbae8d7bae0d77cb4f35a1598b26c58e3bf94d), [`e000f80`](https://github.com/Effect-TS/effect/commit/e000f80fd55bcd8edc699fdbf4cd109004f4f754), [`f4ee765`](https://github.com/Effect-TS/effect/commit/f4ee7655ee052cf9ba726fd602bb87c89c7c62a9), [`510b55f`](https://github.com/Effect-TS/effect/commit/510b55f3e21750685dbfd5f476a130c1c5af9dbd), [`31d3fc4`](https://github.com/Effect-TS/effect/commit/31d3fc4327c50867bb8d881fa7353aeb03ea2826), [`875e618`](https://github.com/Effect-TS/effect/commit/875e618c3764a7b817ac863d0af86924449528f2), [`688d46a`](https://github.com/Effect-TS/effect/commit/688d46afd0ef923d983ad3d7385f52f217b28d70), [`6ff5023`](https://github.com/Effect-TS/effect/commit/6ff502363b9840a5a5ee0a24bc6cae734ac3a3eb), [`c0333e7`](https://github.com/Effect-TS/effect/commit/c0333e7f755f42ddcca7051e029da8b4eed527bf), [`06e7e8c`](https://github.com/Effect-TS/effect/commit/06e7e8c66015ee318f871b9d2218dee82df2b108), [`eb9b102`](https://github.com/Effect-TS/effect/commit/eb9b10256c8558881b441c2fef833b7037174400), [`8b155da`](https://github.com/Effect-TS/effect/commit/8b155da06e0740c354ec562957a45ab65eb4573b), [`3a87335`](https://github.com/Effect-TS/effect/commit/3a8733564c5db35271aa20564ed0d344daa2a79f)]: + - effect@4.0.0-beta.100 + +## 4.0.0-beta.99 + +### Patch Changes + +- Updated dependencies [[`8ce4795`](https://github.com/Effect-TS/effect/commit/8ce4795ccbaebca4292757db568c005a992546a4), [`80b539f`](https://github.com/Effect-TS/effect/commit/80b539f8aba68f478c75c35c2b4140c4ffc4fada), [`88a54cc`](https://github.com/Effect-TS/effect/commit/88a54cc341006e3ebcb13482c618f62a680ce199), [`e6e6dba`](https://github.com/Effect-TS/effect/commit/e6e6dba6e9d86e7c2ad27dcedf289db76a19697f), [`bfb203e`](https://github.com/Effect-TS/effect/commit/bfb203e95aa439f731acad37fc3a9a831a190f1c), [`2e9a34a`](https://github.com/Effect-TS/effect/commit/2e9a34ac2bece4f3a206160480c991e3841dc67a), [`55d4eb3`](https://github.com/Effect-TS/effect/commit/55d4eb34f2c64d54f6a25a305b5c5438ebd7934e), [`bddb010`](https://github.com/Effect-TS/effect/commit/bddb010eac3d4436cb094edbbee7460c5440c162), [`a328835`](https://github.com/Effect-TS/effect/commit/a328835e50d76bc96648a1c1550456e8c9f81210), [`5560d05`](https://github.com/Effect-TS/effect/commit/5560d05aa6abdd29466d9c3412cc5e648b0adbde), [`8f6e3ad`](https://github.com/Effect-TS/effect/commit/8f6e3adb185b16e8820b98c509b308086f7ff1af), [`46997fa`](https://github.com/Effect-TS/effect/commit/46997fa60401f5e3c93daa4b61f7df8e31caaab4), [`9e6e12d`](https://github.com/Effect-TS/effect/commit/9e6e12d75c118cd265496f2880490d1f33a5c8bf), [`3394b93`](https://github.com/Effect-TS/effect/commit/3394b93d97d6f24fc38670641d1490289ffca7f1), [`febeabc`](https://github.com/Effect-TS/effect/commit/febeabc3f7c31094da000a23edeaabfe2ab00a38), [`54161c9`](https://github.com/Effect-TS/effect/commit/54161c98f6f3569e0c31842f54e6a257f9421c4c), [`385f7a4`](https://github.com/Effect-TS/effect/commit/385f7a4ee4a7359928597ea56d151dbaf5eb5802), [`7eea4d0`](https://github.com/Effect-TS/effect/commit/7eea4d0b73ec554915d7066a71f46326ce2ba45f), [`7543afe`](https://github.com/Effect-TS/effect/commit/7543afea6f4d97d1f1ad876224323838a48daadd), [`44b9cf3`](https://github.com/Effect-TS/effect/commit/44b9cf3d240d726997b4bbcd0ede48e074d3c456), [`7eea4d0`](https://github.com/Effect-TS/effect/commit/7eea4d0b73ec554915d7066a71f46326ce2ba45f), [`0a8aa6a`](https://github.com/Effect-TS/effect/commit/0a8aa6acb90a72b91c24d17133c950e4cacd8abd), [`c8d9fcf`](https://github.com/Effect-TS/effect/commit/c8d9fcf7b030f7c474effbab2764ce7aee1c7209), [`9ca7f9a`](https://github.com/Effect-TS/effect/commit/9ca7f9a69363e4485645966d5a93b8f9597c5206), [`e7aca89`](https://github.com/Effect-TS/effect/commit/e7aca894bb32fbb785b5830837e6061c415a6015), [`55d7560`](https://github.com/Effect-TS/effect/commit/55d75609b8acf8a1b54c1b1c7fbbb65ec741aa3e), [`f809189`](https://github.com/Effect-TS/effect/commit/f809189ddf6b6011ba43a9901baaa734e315da2a), [`88a54cc`](https://github.com/Effect-TS/effect/commit/88a54cc341006e3ebcb13482c618f62a680ce199), [`0ebdbe7`](https://github.com/Effect-TS/effect/commit/0ebdbe74463dc84385956d0b1e8c2b79ebab5400), [`7517d09`](https://github.com/Effect-TS/effect/commit/7517d09f12a0b183a81bd425962c4e280a68b05d), [`212493b`](https://github.com/Effect-TS/effect/commit/212493b9a1eb98cd1ef6959c707a2e5784a5ae91), [`88a54cc`](https://github.com/Effect-TS/effect/commit/88a54cc341006e3ebcb13482c618f62a680ce199), [`80ea8cb`](https://github.com/Effect-TS/effect/commit/80ea8cb9222ca73f564c8267ab2f82966fea027a), [`8df19f4`](https://github.com/Effect-TS/effect/commit/8df19f4fe81d90cc33ace88b9a77e5534f82d604)]: + - effect@4.0.0-beta.99 + +## 4.0.0-beta.98 + +### Patch Changes + +- Updated dependencies [[`989603b`](https://github.com/Effect-TS/effect-smol/commit/989603b60ab1197b64acf214208e0d370cd1f842), [`214c458`](https://github.com/Effect-TS/effect-smol/commit/214c458084bb6995d543cd37d1055f24be3d454e), [`a037273`](https://github.com/Effect-TS/effect-smol/commit/a0372736ac34796969b051bbba4717d7983f1ebe), [`97fdaa9`](https://github.com/Effect-TS/effect-smol/commit/97fdaa9c1f522c65e579365d314a07878e2b904f), [`b24d248`](https://github.com/Effect-TS/effect-smol/commit/b24d248c8df44222ce642087cde2bd859a2dc709), [`19c222c`](https://github.com/Effect-TS/effect-smol/commit/19c222cac2353a3d7b7733caecb00556fffe9a5c), [`eec85dd`](https://github.com/Effect-TS/effect-smol/commit/eec85ddba09ea326fd268ee33eeffd47e50d4671), [`0082f4f`](https://github.com/Effect-TS/effect-smol/commit/0082f4f74fb139fd578f87f0a790e845133983dc), [`8849052`](https://github.com/Effect-TS/effect-smol/commit/884905232d1e9a365e046d8dde27bf9c5707f57f), [`c15e16a`](https://github.com/Effect-TS/effect-smol/commit/c15e16ad130d1fbde25d912b7ac55995066cb35b), [`01d00a3`](https://github.com/Effect-TS/effect-smol/commit/01d00a3abfbf1f37996cdbe738ea5137c646cdd7), [`8bd4589`](https://github.com/Effect-TS/effect-smol/commit/8bd458975a1b3a8ed042eccf317b93d28ded91e7), [`0082f4f`](https://github.com/Effect-TS/effect-smol/commit/0082f4f74fb139fd578f87f0a790e845133983dc), [`6e08428`](https://github.com/Effect-TS/effect-smol/commit/6e08428d980501b856f846ad3f3f0e4ea46e7786), [`388dcf9`](https://github.com/Effect-TS/effect-smol/commit/388dcf953f65d317547f34d40e6443c5f264205f), [`2b7ce2b`](https://github.com/Effect-TS/effect-smol/commit/2b7ce2b513e7ec2a77822f1116dc6ffb6ba93f4e), [`87bea7e`](https://github.com/Effect-TS/effect-smol/commit/87bea7e16259246f3bcdf565446394751abca953), [`ce38dc3`](https://github.com/Effect-TS/effect-smol/commit/ce38dc33bda805a684432cca071f4dc3c6b9a1ba), [`a807cd1`](https://github.com/Effect-TS/effect-smol/commit/a807cd170341deca8a1cfb52c4222585f2431bb9), [`fd8a356`](https://github.com/Effect-TS/effect-smol/commit/fd8a356f06a8c9ce4e7e0a13fc4021c178ed31de), [`c2a5edc`](https://github.com/Effect-TS/effect-smol/commit/c2a5edc3abd31ad5bc123362bc1213e03e4095c3), [`5946da3`](https://github.com/Effect-TS/effect-smol/commit/5946da3804a1be5e752b05b96bd058cdba50a1bf), [`4ae0c5f`](https://github.com/Effect-TS/effect-smol/commit/4ae0c5ffcbe6c56ddfcb05c639112a079483539e), [`5b2a0bc`](https://github.com/Effect-TS/effect-smol/commit/5b2a0bceea3a28a33a58555210c90a415dc74a76), [`72ac585`](https://github.com/Effect-TS/effect-smol/commit/72ac585884befde6af9208da738699a93f1bae79), [`5e8c1b8`](https://github.com/Effect-TS/effect-smol/commit/5e8c1b82bfafa121311f987a49ab75395e3647a7), [`0f9c078`](https://github.com/Effect-TS/effect-smol/commit/0f9c07841b04183f485ee6e6458de73b290b09f5)]: + - effect@4.0.0-beta.98 + +## 4.0.0-beta.97 + +### Patch Changes + +- Updated dependencies []: + - effect@4.0.0-beta.97 + +## 4.0.0-beta.96 + +### Patch Changes + +- Updated dependencies [[`1503f45`](https://github.com/Effect-TS/effect-smol/commit/1503f45cb5bb2a74f4705252ec505a1f0ade7e62), [`57fe793`](https://github.com/Effect-TS/effect-smol/commit/57fe79316ffbc380b30626a168981fb26ae97459), [`0c2f78f`](https://github.com/Effect-TS/effect-smol/commit/0c2f78f695ec474e1ff5474da183577975e418f5), [`0c2f78f`](https://github.com/Effect-TS/effect-smol/commit/0c2f78f695ec474e1ff5474da183577975e418f5), [`0c2f78f`](https://github.com/Effect-TS/effect-smol/commit/0c2f78f695ec474e1ff5474da183577975e418f5), [`97f29df`](https://github.com/Effect-TS/effect-smol/commit/97f29df457f7ffd07cfb4b379315c12c086af805)]: + - effect@4.0.0-beta.96 + +## 4.0.0-beta.95 + +### Patch Changes + +- Updated dependencies [[`a482442`](https://github.com/Effect-TS/effect-smol/commit/a482442abdeb490e9652b854ec3495e4aa7273e7), [`fbefa85`](https://github.com/Effect-TS/effect-smol/commit/fbefa850fab2f0a302c20614496aeaaa2a8b5590), [`0b4a32f`](https://github.com/Effect-TS/effect-smol/commit/0b4a32f4260f0d8500942a133001b0d349328102), [`18a49e1`](https://github.com/Effect-TS/effect-smol/commit/18a49e1786679456258002ff9397faf02f678c2d), [`266cb90`](https://github.com/Effect-TS/effect-smol/commit/266cb90bb2c17aabc40563c32db334f09ba3d74b), [`912f095`](https://github.com/Effect-TS/effect-smol/commit/912f095a34572bbd3cedf6edb27878443e3e4a95), [`a6718f9`](https://github.com/Effect-TS/effect-smol/commit/a6718f9e00a15ca903b0732da46116cbf3d6aca7), [`bef5154`](https://github.com/Effect-TS/effect-smol/commit/bef51540a243aa2f872a00c01d0cd58b7a769baa), [`18e0564`](https://github.com/Effect-TS/effect-smol/commit/18e0564bd0f8ebbdfcaf1e2c21529948e9e4a81d), [`fb50f14`](https://github.com/Effect-TS/effect-smol/commit/fb50f14fc3657c1973785aa5b72ecf0b0d28e0b2)]: + - effect@4.0.0-beta.95 + +## 4.0.0-beta.94 + +### Patch Changes + +- Updated dependencies [[`95a0e9b`](https://github.com/Effect-TS/effect-smol/commit/95a0e9bb62797af0e81c9998773405f248f218c5), [`a0a3490`](https://github.com/Effect-TS/effect-smol/commit/a0a3490bbce765f199d8e077aceac504f0462e63), [`f11ce73`](https://github.com/Effect-TS/effect-smol/commit/f11ce73af60823754dc24194f4ffc561b9ea1c2d), [`ff30b6e`](https://github.com/Effect-TS/effect-smol/commit/ff30b6e7c2c63ffc56a4c5818d6d86b01b5ad528), [`1caab3c`](https://github.com/Effect-TS/effect-smol/commit/1caab3cc30f626efbf15e59d74f539a487e5c85c), [`aa80c47`](https://github.com/Effect-TS/effect-smol/commit/aa80c4775a04db87553e5568764cab7e32a72814), [`c2ae4fc`](https://github.com/Effect-TS/effect-smol/commit/c2ae4fce2f03a4cd1861c2b1179da7df656e662d), [`a0a3490`](https://github.com/Effect-TS/effect-smol/commit/a0a3490bbce765f199d8e077aceac504f0462e63)]: + - effect@4.0.0-beta.94 + +## 4.0.0-beta.93 + +### Patch Changes + +- Updated dependencies [[`00652fe`](https://github.com/Effect-TS/effect-smol/commit/00652fe95c18f87208e91343eb8bf218faa2f677), [`6c58167`](https://github.com/Effect-TS/effect-smol/commit/6c5816746eaf91d2a3c7c899c5720809fa230ae3), [`2bc5415`](https://github.com/Effect-TS/effect-smol/commit/2bc541501a7ef89e542d7cb98e96beb53cd205cc), [`e11cccc`](https://github.com/Effect-TS/effect-smol/commit/e11cccc7d5fe631abccc7d6e3bd296938de0fa2e), [`ba7e77e`](https://github.com/Effect-TS/effect-smol/commit/ba7e77e046b8641a3a4e9750bb88ca4a1d063d3f), [`5713ee7`](https://github.com/Effect-TS/effect-smol/commit/5713ee7edbc3054efde407b2286bbfd45bbc6e1c)]: + - effect@4.0.0-beta.93 + +## 4.0.0-beta.92 + +### Patch Changes + +- Updated dependencies [[`affdc13`](https://github.com/Effect-TS/effect-smol/commit/affdc139045cc325dce321a84a580fdc1b2da7b9)]: + - effect@4.0.0-beta.92 + +## 4.0.0-beta.91 + +### Patch Changes + +- Updated dependencies [[`b135b25`](https://github.com/Effect-TS/effect-smol/commit/b135b2517fca9e7839734ace3699a7dfa75b9075), [`aaa21a3`](https://github.com/Effect-TS/effect-smol/commit/aaa21a369a171c600db294f2a4f640583043e150), [`3475ee6`](https://github.com/Effect-TS/effect-smol/commit/3475ee6c2bda6b05c6d7a12ce30c8bb840b5b1a6)]: + - effect@4.0.0-beta.91 + ## 4.0.0-beta.90 ### Patch Changes diff --git a/repos/effect/packages/sql/pglite/README.md b/repos/effect/packages/sql/pglite/README.md index ef2c920d27..cb6b3d5ee5 100644 --- a/repos/effect/packages/sql/pglite/README.md +++ b/repos/effect/packages/sql/pglite/README.md @@ -1,6 +1,6 @@ # `@effect/sql-pglite` -An `@effect/sql` implementation using the `@electric-sql/pglite` library. +An Effect SQL implementation using the `@electric-sql/pglite` library. ## Documentation diff --git a/repos/effect/packages/sql/pglite/docgen.json b/repos/effect/packages/sql/pglite/docgen.json deleted file mode 100644 index fe017a64c1..0000000000 --- a/repos/effect/packages/sql/pglite/docgen.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "$schema": "../../node_modules/@effect/docgen/schema.json", - "srcLink": "https://github.com/Effect-TS/effect-smol/tree/main/packages/sql/pglite/src/", - "exclude": ["src/internal/**/*.ts"], - "tscExecutable": "tsgo", - "examplesCompilerOptions": { - "noEmit": true, - "strict": true, - "skipLibCheck": true, - "moduleResolution": "Bundler", - "module": "ES2022", - "target": "ES2022", - "lib": ["ES2022", "DOM", "DOM.Iterable"], - "rewriteRelativeImportExtensions": true, - "allowImportingTsExtensions": true, - "paths": { - "effect": ["../../../effect/src/index.js"], - "effect/*": ["../../../effect/src/*.js"] - }, - "plugins": [ - { "name": "@effect/language-service", "includeSuggestionsInTsc": false } - ] - } -} diff --git a/repos/effect/packages/sql/pglite/package.json b/repos/effect/packages/sql/pglite/package.json index d63b165f56..c4674ad29f 100644 --- a/repos/effect/packages/sql/pglite/package.json +++ b/repos/effect/packages/sql/pglite/package.json @@ -1,17 +1,17 @@ { "name": "@effect/sql-pglite", - "version": "4.0.0-beta.90", + "version": "4.0.0-beta.107", "type": "module", "license": "MIT", "description": "A PGlite toolkit for Effect", "homepage": "https://effect.website", "repository": { "type": "git", - "url": "https://github.com/Effect-TS/effect-smol.git", + "url": "https://github.com/Effect-TS/effect.git", "directory": "packages/sql/pglite" }, "bugs": { - "url": "https://github.com/Effect-TS/effect-smol/issues" + "url": "https://github.com/Effect-TS/effect/issues" }, "tags": [ "typescript", @@ -33,6 +33,7 @@ ".": "./src/index.ts", "./*": "./src/*.ts", "./internal/*": null, + "./index": null, "./*/index": null }, "files": [ @@ -40,7 +41,10 @@ "dist/**/*.js", "dist/**/*.js.map", "dist/**/*.d.ts", - "dist/**/*.d.ts.map" + "dist/**/*.d.ts.map", + "AGENTS.md", + "CLAUDE.md", + "ai-docs/**/*" ], "publishConfig": { "access": "public", @@ -50,17 +54,15 @@ ".": "./dist/index.js", "./*": "./dist/*.js", "./internal/*": null, + "./index": null, "./*/index": null } }, "scripts": { "codegen": "effect-utils codegen", "build": "tsc -b tsconfig.json && pnpm babel", - "build:tsgo": "tsgo -b tsconfig.json && pnpm babel", "babel": "babel dist --plugins annotate-pure-calls --out-dir dist --source-maps", - "check": "tsc -b tsconfig.json", - "test": "vitest", - "coverage": "vitest --coverage" + "check": "tsc -b tsconfig.json" }, "devDependencies": { "effect": "workspace:^" @@ -69,6 +71,6 @@ "effect": "workspace:^" }, "dependencies": { - "@electric-sql/pglite": "^0.4.5" + "@electric-sql/pglite": "^0.5.4" } } diff --git a/repos/effect/packages/sql/pglite/src/PgliteClient.ts b/repos/effect/packages/sql/pglite/src/PgliteClient.ts index 95aa38a980..84dcc063f9 100644 --- a/repos/effect/packages/sql/pglite/src/PgliteClient.ts +++ b/repos/effect/packages/sql/pglite/src/PgliteClient.ts @@ -60,7 +60,7 @@ export type TypeId = "~@effect/sql-pglite/PgliteClient" /** * PGlite-backed PostgreSQL client service, extending `SqlClient` with access to the PGlite instance, JSON fragments, LISTEN/NOTIFY, data directory dumps, and array type refresh. * - * @category models + * @category services * @since 4.0.0 */ export interface PgliteClient extends Client.SqlClient { @@ -436,13 +436,13 @@ const escapeLiteral = (value: string) => `'${value.replace(/'/g, "''")}'` /** * PGlite-specific custom statement fragments supported by the compiler, currently JSON parameter fragments. * - * @category custom types + * @category models * @since 4.0.0 */ export type PgCustom = PgJson /** - * @category custom types + * @category models * @since 4.0.0 */ interface PgJson extends Custom<"PgJson", unknown> {} diff --git a/repos/effect/packages/sql/pglite/src/PgliteMigrator.ts b/repos/effect/packages/sql/pglite/src/PgliteMigrator.ts index c6f9f15f65..c4da7bb243 100644 --- a/repos/effect/packages/sql/pglite/src/PgliteMigrator.ts +++ b/repos/effect/packages/sql/pglite/src/PgliteMigrator.ts @@ -22,7 +22,7 @@ export * from "effect/unstable/sql/Migrator" /** * Runs SQL migrations using the configured `SqlClient`, returning the migrations that were applied. * - * @category constructors + * @category running * @since 4.0.0 */ export const run: ( @@ -36,7 +36,7 @@ export const run: ( /** * Creates a layer that runs the configured SQL migrations during layer construction. * - * @category constructors + * @category layers * @since 4.0.0 */ export const layer = ( diff --git a/repos/effect/packages/sql/pglite/test/Transaction.test.ts b/repos/effect/packages/sql/pglite/test/Transaction.test.ts index 9a9cc45143..0e2023b2aa 100644 --- a/repos/effect/packages/sql/pglite/test/Transaction.test.ts +++ b/repos/effect/packages/sql/pglite/test/Transaction.test.ts @@ -1,6 +1,7 @@ import { PgliteClient } from "@effect/sql-pglite" import { assert, describe, layer } from "@effect/vitest" -import { Effect } from "effect" +import { Deferred, Effect } from "effect" +import { TestClock } from "effect/testing" const ClientLayer = PgliteClient.layer({}) @@ -58,5 +59,35 @@ describe("PgliteClient transactions", () => { ) assert.strictEqual(rows.at(0)?.total, 1) })) + + it.effect("preserves successful concurrent nested transactions", () => + Effect.gen(function*() { + const sql = yield* setup("tx_nested_concurrent") + const firstStarted = yield* Deferred.make() + const firstInserted = yield* Deferred.make() + + yield* sql.withTransaction( + Effect.all([ + sql.withTransaction( + Effect.gen(function*() { + yield* Deferred.succeed(firstStarted, undefined) + yield* Effect.sleep("100 millis") + yield* sql.unsafe(`INSERT INTO tx_nested_concurrent (name) VALUES ('first')`) + yield* Deferred.succeed(firstInserted, undefined) + }) + ), + Deferred.await(firstStarted).pipe( + Effect.andThen(sql.withTransaction( + Deferred.await(firstInserted).pipe( + Effect.andThen(Effect.fail("rollback")) + ) + )) + ) + ], { concurrency: "unbounded" }).pipe(Effect.catch(() => Effect.void)) + ) + + const rows = yield* sql.unsafe<{ name: string }>(`SELECT name FROM tx_nested_concurrent`) + assert.deepStrictEqual(rows, [{ name: "first" }]) + }).pipe(TestClock.withLive)) }) }) diff --git a/repos/effect/packages/sql/pglite/tsconfig.json b/repos/effect/packages/sql/pglite/tsconfig.json index 19a2f5dbca..e2a8ca19a0 100644 --- a/repos/effect/packages/sql/pglite/tsconfig.json +++ b/repos/effect/packages/sql/pglite/tsconfig.json @@ -1,5 +1,5 @@ { - "$schema": "http://json.schemastore.org/tsconfig", + "$schema": "https://json.schemastore.org/tsconfig", "extends": "../../../tsconfig.base.json", "include": ["src"], "references": [ diff --git a/repos/effect/packages/sql/pglite/vitest.config.ts b/repos/effect/packages/sql/pglite/vitest.config.ts deleted file mode 100644 index c8a52c1826..0000000000 --- a/repos/effect/packages/sql/pglite/vitest.config.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { mergeConfig, type ViteUserConfig } from "vitest/config" -import shared from "../../../vitest.shared.ts" - -const config: ViteUserConfig = {} - -export default mergeConfig(shared, config) diff --git a/repos/effect/packages/sql/sqlite-bun/CHANGELOG.md b/repos/effect/packages/sql/sqlite-bun/CHANGELOG.md index da54fec5e5..945f8342d6 100644 --- a/repos/effect/packages/sql/sqlite-bun/CHANGELOG.md +++ b/repos/effect/packages/sql/sqlite-bun/CHANGELOG.md @@ -1,5 +1,128 @@ # @effect/sql-sqlite-bun +## 4.0.0-beta.107 + +### Patch Changes + +- [#7162](https://github.com/Effect-TS/effect/pull/7162) [`c30386d`](https://github.com/Effect-TS/effect/commit/c30386df4be367350ee480fa357ddeae7a8f0d08) Thanks @tim-smart! - 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. +- 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 + +- 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 + +- [#6993](https://github.com/Effect-TS/effect/pull/6993) [`5d6fe77`](https://github.com/Effect-TS/effect/commit/5d6fe775b77528936ec3cb1e19bf817f65ecadb3) Thanks @fubhy! - Enforce read-only mode when opening Bun SQLite databases. +- Updated dependencies [[`1001bcc`](https://github.com/Effect-TS/effect/commit/1001bccb9e874918d59dbb36860f1c5d4499ac20), [`993ba60`](https://github.com/Effect-TS/effect/commit/993ba60ee6c7ca6eb84522040f8b0d268b6ba7d4), [`67faacd`](https://github.com/Effect-TS/effect/commit/67faacd4679242559bee31717c05a5b10b990322), [`b78acdf`](https://github.com/Effect-TS/effect/commit/b78acdf422568f10ae8684fd3f10d52b065f0b56), [`fbb9ce5`](https://github.com/Effect-TS/effect/commit/fbb9ce5e625d1a7d6b7005bda42cbb6cd31476c5), [`722ea48`](https://github.com/Effect-TS/effect/commit/722ea484c9d01364c9242d929c0a564f7831a57c), [`3058fd5`](https://github.com/Effect-TS/effect/commit/3058fd594f5a683034212d71d957017fcc084006), [`62d0575`](https://github.com/Effect-TS/effect/commit/62d057566c241405c23ecf0bf4156186bd2be924), [`99dd6b5`](https://github.com/Effect-TS/effect/commit/99dd6b580434f97c5b40adc919f429e4abc3dfe7), [`7963ce1`](https://github.com/Effect-TS/effect/commit/7963ce1cd95f037fbefea67a29ead49cce4d16cb), [`af14e75`](https://github.com/Effect-TS/effect/commit/af14e752edd65e2b652e960411afafc88975a8d8), [`24e22d2`](https://github.com/Effect-TS/effect/commit/24e22d23a73a2e93ebf6d8edd2246a4a406942c8), [`647d14e`](https://github.com/Effect-TS/effect/commit/647d14e572c8004fa92fba256e00552b42bf34b7), [`1434eec`](https://github.com/Effect-TS/effect/commit/1434eecbd368e00839c24b3950f0b7a69218669a), [`a5278b1`](https://github.com/Effect-TS/effect/commit/a5278b18242011d1b2b08304c7c128151f9a4370), [`6af04a5`](https://github.com/Effect-TS/effect/commit/6af04a50bd019238f6acdb9cbda40439a3c09210), [`cb6c837`](https://github.com/Effect-TS/effect/commit/cb6c8376b2f322d4e7cbfc0973fc3b4f2951ee6e), [`d44cead`](https://github.com/Effect-TS/effect/commit/d44cead7e0e0ce61f0d980906e494f49a07e7899), [`88c7632`](https://github.com/Effect-TS/effect/commit/88c7632c2b59a49fcc40d250865bd8d0dccf31b0), [`abcbb2a`](https://github.com/Effect-TS/effect/commit/abcbb2abe16f1b6c587c15007df14371e1e70e93), [`8f63cce`](https://github.com/Effect-TS/effect/commit/8f63cce636700fde26b140b82e350ef916989d86), [`d56dfcf`](https://github.com/Effect-TS/effect/commit/d56dfcf54c2b9c53c3d098ce4b0ffcc84496c5f7), [`a98cda9`](https://github.com/Effect-TS/effect/commit/a98cda9422e1352f22e81696f759f326ffcfb667), [`6704bb8`](https://github.com/Effect-TS/effect/commit/6704bb84c320547f83cf50e8586ffc4c5e4c3cc5), [`6143de2`](https://github.com/Effect-TS/effect/commit/6143de21ee22038b45a8d4eba86f5aade6238eba), [`936b135`](https://github.com/Effect-TS/effect/commit/936b1358396eb0a1a7c8e0878ba63297e2106812), [`1bbae84`](https://github.com/Effect-TS/effect/commit/1bbae84f88b577a26d04ceb2e76d3143d09c4a20), [`d795ee7`](https://github.com/Effect-TS/effect/commit/d795ee771701ea62bd187ef7c0307d9737f68c1a), [`0a82d88`](https://github.com/Effect-TS/effect/commit/0a82d88b7da73278b6f270118e396d5ed4a64747), [`9215bc5`](https://github.com/Effect-TS/effect/commit/9215bc5da7dd10aa45f07fe44b98f06b6e433d62), [`a1b5df2`](https://github.com/Effect-TS/effect/commit/a1b5df2064d92431cfc6e638af613cc3114313d7), [`92a9ac5`](https://github.com/Effect-TS/effect/commit/92a9ac5ac0aa63d8975b9ba7a094d6a8f59a98f2), [`6bde7f2`](https://github.com/Effect-TS/effect/commit/6bde7f27f3243427203e53fe74472990e5c2a349), [`a712131`](https://github.com/Effect-TS/effect/commit/a7121310dbb60cbd819bbd702f97663098ec7bb8), [`2e6f760`](https://github.com/Effect-TS/effect/commit/2e6f760dcb44e2b984f3311a8af03a1d68a2ec7e), [`aa05804`](https://github.com/Effect-TS/effect/commit/aa0580497e027ed30b756058db0067c3fe07664f), [`badd3bf`](https://github.com/Effect-TS/effect/commit/badd3bf65fac4dd1e66e1f602db43659722dfced), [`02b0265`](https://github.com/Effect-TS/effect/commit/02b02651ede46a5a2dd3ef8081d0ad89648d0cbf), [`3437e21`](https://github.com/Effect-TS/effect/commit/3437e21a56d805781c5e5946a6189795a1dfd411), [`41a550d`](https://github.com/Effect-TS/effect/commit/41a550d1fed31e829929a8f5362b5340303164ac), [`17b5d50`](https://github.com/Effect-TS/effect/commit/17b5d50219ad49533cf9e33d01924a3e16af5eb3), [`96e5e95`](https://github.com/Effect-TS/effect/commit/96e5e9576b0315c747462761a61940ff9fe32dd1), [`e4d589e`](https://github.com/Effect-TS/effect/commit/e4d589e0ea08dc57c4793053b395dc0fcc499f34), [`ae4cf7b`](https://github.com/Effect-TS/effect/commit/ae4cf7b5e2cb5f8c55657e31a61789ad21c38c18), [`6ef5f1a`](https://github.com/Effect-TS/effect/commit/6ef5f1a041f3a40bf03fadd0b1feb275c277c635), [`2235a29`](https://github.com/Effect-TS/effect/commit/2235a29502c3f33cf6468511ad931089013a7916), [`b32f4cb`](https://github.com/Effect-TS/effect/commit/b32f4cb7b2d8ebe817075322622498e3beb05336), [`7f4c095`](https://github.com/Effect-TS/effect/commit/7f4c095b62da43780dd7fc2a5d1785ddfce60edf), [`5f3fb81`](https://github.com/Effect-TS/effect/commit/5f3fb814d18d8a54946c1c1cd0b41459cdb24006), [`17f0b91`](https://github.com/Effect-TS/effect/commit/17f0b91a243ccfe4a38d27debdc983adf434e738), [`0cdadd7`](https://github.com/Effect-TS/effect/commit/0cdadd75bc8abbbcad7956a4bc71f4e7a9b13250), [`39b57d7`](https://github.com/Effect-TS/effect/commit/39b57d7857358040558b67dd33eafc7bb5457830), [`5a6a573`](https://github.com/Effect-TS/effect/commit/5a6a5738e5bfc39e3a37ae7ba99081601fa19ac3), [`59f5e99`](https://github.com/Effect-TS/effect/commit/59f5e9981913b92d7a9beb2214a21d658b999d3a), [`45379d6`](https://github.com/Effect-TS/effect/commit/45379d6179ee4df2cbd3f848bd39ff7149c24a38), [`1949439`](https://github.com/Effect-TS/effect/commit/1949439175809ef81ab9c6411ed5559109edb4c9), [`e443403`](https://github.com/Effect-TS/effect/commit/e443403cf0e4effea14bb6cd950c5ac1c86cc748), [`03af7e8`](https://github.com/Effect-TS/effect/commit/03af7e85551204c605ea2fa2c43c10a4538ac8fb), [`0f721d4`](https://github.com/Effect-TS/effect/commit/0f721d406df8703ea92ca28777b3f09599e2056d), [`130b28d`](https://github.com/Effect-TS/effect/commit/130b28df552d7053407b041a96ff09dae82575e5), [`c987a12`](https://github.com/Effect-TS/effect/commit/c987a12a01b6a52ad53d29edf02613b03574dbcc), [`4158562`](https://github.com/Effect-TS/effect/commit/41585620977de9b84171f76619b72e29cc2284e5), [`306014a`](https://github.com/Effect-TS/effect/commit/306014a1ce4d5cb956c76bdc20e4e28ab3e61a6a), [`729a663`](https://github.com/Effect-TS/effect/commit/729a663275dd31f2357c446fe69664429220a83d), [`caf84b6`](https://github.com/Effect-TS/effect/commit/caf84b660044089e8d7f4067b279b27b8b50e8fd), [`ce067f7`](https://github.com/Effect-TS/effect/commit/ce067f799ea27735d4194345298a216aaf429f01), [`7a41f5a`](https://github.com/Effect-TS/effect/commit/7a41f5aa72d540ecf2746992ecc3fa3e6b40d31f), [`781022a`](https://github.com/Effect-TS/effect/commit/781022acdd3537ca18c88e2fa3681bafa6ef1b21), [`39f1297`](https://github.com/Effect-TS/effect/commit/39f1297acc08864feb12de6b8cf2bf73434f6cf5), [`2db266b`](https://github.com/Effect-TS/effect/commit/2db266b1bfbc81868bc1778c37c76032a267c79f), [`2141e28`](https://github.com/Effect-TS/effect/commit/2141e28903754d72604acf81673ceb2c62a56646), [`3c5e429`](https://github.com/Effect-TS/effect/commit/3c5e429878669ffcf5e0da4ddfbf50bde5bbcaad), [`20ddc63`](https://github.com/Effect-TS/effect/commit/20ddc630584f8fe488162ba384adcae53fc6810a), [`841b3ea`](https://github.com/Effect-TS/effect/commit/841b3ea6ae19a784bc1c20497b02f632af0c91e9), [`82a3fbf`](https://github.com/Effect-TS/effect/commit/82a3fbfce8b9df33e587076b7d7168ecd6799e17), [`eb9ee83`](https://github.com/Effect-TS/effect/commit/eb9ee83b38844a71d1cd5653a229309cfcb04a36), [`64dc7c7`](https://github.com/Effect-TS/effect/commit/64dc7c76dc5c89887b9e7c181d1873dcbb7820d1), [`84dc8ab`](https://github.com/Effect-TS/effect/commit/84dc8ab7accc682bc668c78a97e4a1776b633be8), [`b4463f4`](https://github.com/Effect-TS/effect/commit/b4463f46fc33d3b01ea5eadd7d012a5abda347a3), [`592dd36`](https://github.com/Effect-TS/effect/commit/592dd361645739ac0cd8e6babb084cd27403c172), [`85d2b44`](https://github.com/Effect-TS/effect/commit/85d2b446e3059de4919be730105868f79728308d), [`32e4a69`](https://github.com/Effect-TS/effect/commit/32e4a69b3151b7ec4058af2213b96a41d11e9e06), [`13c5872`](https://github.com/Effect-TS/effect/commit/13c5872ed30830360367ad89af2dab68a003c351), [`3454cdb`](https://github.com/Effect-TS/effect/commit/3454cdb528fdb5d3ed0c5c5c8169bc47de41fbd8), [`e930804`](https://github.com/Effect-TS/effect/commit/e9308045be1d8a00c0b4046f1e8ff22cf68c93da), [`7f12d4b`](https://github.com/Effect-TS/effect/commit/7f12d4b4e731dc3a213ae5c3f60db9edc50292d2), [`181c9ef`](https://github.com/Effect-TS/effect/commit/181c9ef5e5d4ab247bf4aec06424f15b0a1e802e), [`dd9f891`](https://github.com/Effect-TS/effect/commit/dd9f891e23f316abb6192893008f0e33ece9d97d), [`433fb81`](https://github.com/Effect-TS/effect/commit/433fb81ca4c15c681a8ae097ce3ff9bd3a9c9aa5), [`8459cdb`](https://github.com/Effect-TS/effect/commit/8459cdbae8a476dc04b6247fffe6a1668dcb1217), [`6124ab3`](https://github.com/Effect-TS/effect/commit/6124ab39eb64688fbd5d688d24766542f9cb5a2c), [`01bd954`](https://github.com/Effect-TS/effect/commit/01bd9546f142706fca1628f7261e6d1cb9638948), [`ba2c3aa`](https://github.com/Effect-TS/effect/commit/ba2c3aa05eb87ec05d263b960017ecf29746f66a), [`0a45ef3`](https://github.com/Effect-TS/effect/commit/0a45ef3bb4a1ae9b345c43c548db4336a31b3191), [`8459cdb`](https://github.com/Effect-TS/effect/commit/8459cdbae8a476dc04b6247fffe6a1668dcb1217), [`eaa7e71`](https://github.com/Effect-TS/effect/commit/eaa7e71b88bf59b24610128c6115a2a126432731), [`db4c2cc`](https://github.com/Effect-TS/effect/commit/db4c2ccdec77d813b6f4cc72a302ee7c4fe6e39d), [`22f150a`](https://github.com/Effect-TS/effect/commit/22f150a0936cef30517e87eaca73bff1c5e4873a), [`90ffb08`](https://github.com/Effect-TS/effect/commit/90ffb083b3091c211300f50a42ba7bf56536c0ee), [`d517692`](https://github.com/Effect-TS/effect/commit/d517692ef75f45d5f6d9d68b32d41fa0ccc56c99), [`01af079`](https://github.com/Effect-TS/effect/commit/01af079c189d1fc5067d3b1933b2870c4baf2693), [`32a59e8`](https://github.com/Effect-TS/effect/commit/32a59e8058b1ec9738cb083cf1cb116b393ca114)]: + - effect@4.0.0-beta.104 + +## 4.0.0-beta.103 + +### Patch Changes + +- [#6701](https://github.com/Effect-TS/effect/pull/6701) [`9867b9f`](https://github.com/Effect-TS/effect/commit/9867b9fc69f9cc6c443594fc7eccc7be0c674d9c) Thanks @fubhy! - Removed explicit ./index entrypoints + +- Updated dependencies [[`e56cd8f`](https://github.com/Effect-TS/effect/commit/e56cd8f90c3559baccf8fcf2852ea911235d5944), [`f77c120`](https://github.com/Effect-TS/effect/commit/f77c120d8e04779ddeb8bce8e9cde932f268e4b6), [`b2f95a9`](https://github.com/Effect-TS/effect/commit/b2f95a9c2f2581deb89dc3bae9e89cf819e82923), [`04fd44a`](https://github.com/Effect-TS/effect/commit/04fd44a42abfa8dc2642300dcf49ee48c8ef4539), [`b74333d`](https://github.com/Effect-TS/effect/commit/b74333d83e15b9d042e4698ad23040de60454afe), [`1c40b28`](https://github.com/Effect-TS/effect/commit/1c40b2809503d6aa1358777196fc66317906e657), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`b3901d2`](https://github.com/Effect-TS/effect/commit/b3901d29c543fd5bd05ceec669a17896c8e19006), [`4a0984a`](https://github.com/Effect-TS/effect/commit/4a0984af62738fedf4bd3e87adb4d4d641ce9147), [`fffd88b`](https://github.com/Effect-TS/effect/commit/fffd88b3135abdf928ca7c4b0e00e610985091c7), [`f3f6c1e`](https://github.com/Effect-TS/effect/commit/f3f6c1e02cb543423fcffef5dc2db03fac503588), [`ef07642`](https://github.com/Effect-TS/effect/commit/ef07642dfe671d5258b65d1c1480c4d05c495f15), [`f1bc827`](https://github.com/Effect-TS/effect/commit/f1bc8274a608813d7b09d28dcca04adbf62f8c92), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`081f4d8`](https://github.com/Effect-TS/effect/commit/081f4d8cd06a2ac222d2810b46e61efcee26939e), [`5287b24`](https://github.com/Effect-TS/effect/commit/5287b24f5f8fa094ba20e117bfb1a80fba6d2cf5), [`13d31cf`](https://github.com/Effect-TS/effect/commit/13d31cfc2dde46210e94391b5b6767ae9aeaf2c9), [`acee269`](https://github.com/Effect-TS/effect/commit/acee26944bc89ee554d7b9fadab7443f9edc28a9), [`31170c1`](https://github.com/Effect-TS/effect/commit/31170c19b236c37abb5476c821bc6f5bfa2735ab), [`205ebc7`](https://github.com/Effect-TS/effect/commit/205ebc776062012581e98fced7ced19adfc44ee7), [`ed0ebf8`](https://github.com/Effect-TS/effect/commit/ed0ebf8e5c864d46fed1f232e99c0e680f10a58f), [`a3fd084`](https://github.com/Effect-TS/effect/commit/a3fd08482157bd78b089f77c7b173d54ef68b5cd), [`ee29ddf`](https://github.com/Effect-TS/effect/commit/ee29ddf862c3723ad466abc93ab6f6fe723b2319), [`6086309`](https://github.com/Effect-TS/effect/commit/60863090af8e5af0bfa1435f08dc5390f9993e30), [`4a57af2`](https://github.com/Effect-TS/effect/commit/4a57af24011db1d66e947289d2f7ffc2074696d2), [`660875b`](https://github.com/Effect-TS/effect/commit/660875b4325e6eebb3f04513998301cd2a0847ec), [`8e7c706`](https://github.com/Effect-TS/effect/commit/8e7c706b0aca855489b53d987404566d3e9cb5e7), [`5f63adb`](https://github.com/Effect-TS/effect/commit/5f63adbe75fc9d50d23706a52b3e483ad2a1a01c), [`053bc42`](https://github.com/Effect-TS/effect/commit/053bc42e2a964755611a216e78ed214322efee37), [`c0a1534`](https://github.com/Effect-TS/effect/commit/c0a153494484ecf9f0d0f20895a7a648b4be363b), [`f1e3a37`](https://github.com/Effect-TS/effect/commit/f1e3a378c144f974a6122b299f421b75595af20f), [`cedb01a`](https://github.com/Effect-TS/effect/commit/cedb01a025492a1faf9e59eb23eb96bc3b5e2fff), [`1747440`](https://github.com/Effect-TS/effect/commit/1747440de9a51a56ed3660da748cc01b256adce7), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`b4f1ee2`](https://github.com/Effect-TS/effect/commit/b4f1ee238d96aa78c5f040158cb78671d75b381e), [`a4757f1`](https://github.com/Effect-TS/effect/commit/a4757f1c47067d8d016a6c4a2c541bb8ae520f9b), [`cd122b9`](https://github.com/Effect-TS/effect/commit/cd122b90300d995a237993a2edb7a049785ab6a4), [`5de588b`](https://github.com/Effect-TS/effect/commit/5de588b2472fb0f4eb919766eb8472583a044772), [`3895b9c`](https://github.com/Effect-TS/effect/commit/3895b9cf179262cd277a9c6daafe9050dcf8265e), [`89ce5f3`](https://github.com/Effect-TS/effect/commit/89ce5f3e16e23a193daa475dc72ea8133ae1dacd), [`985de09`](https://github.com/Effect-TS/effect/commit/985de097d75906db2aed784841f81e23cc978b43), [`9800e3a`](https://github.com/Effect-TS/effect/commit/9800e3acc8f36530f671bc8b91558cb112f449a7), [`4dc35f6`](https://github.com/Effect-TS/effect/commit/4dc35f64641746366f867ea3dbfedb9cd4685ada), [`e8eb62b`](https://github.com/Effect-TS/effect/commit/e8eb62b3d0ef27e9761cdc2eb93bdec52d6ee204), [`ecd9993`](https://github.com/Effect-TS/effect/commit/ecd99936112cb69efdb02de3a2fd57f47baefdf3), [`5ab9c08`](https://github.com/Effect-TS/effect/commit/5ab9c08463ce049c45f3502676954a7b72c6b024), [`f5cf965`](https://github.com/Effect-TS/effect/commit/f5cf96548afd51f4b3cf1aea11b04d7f8549ce90), [`a94cbed`](https://github.com/Effect-TS/effect/commit/a94cbed84e9e49bea4bff925599c0f19c4e3deab), [`9160ad7`](https://github.com/Effect-TS/effect/commit/9160ad7d146d4376dd12f7510c025e5b2f638a70), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`52494be`](https://github.com/Effect-TS/effect/commit/52494be9e8eb3bb542d06a3dfefc6bca4e168984), [`5441c8e`](https://github.com/Effect-TS/effect/commit/5441c8e656a6418c0d27feb2df67565a3e1155f4), [`c9b56ab`](https://github.com/Effect-TS/effect/commit/c9b56ab507f224426ee8388dc450da447ec4715f), [`8ef7257`](https://github.com/Effect-TS/effect/commit/8ef72577d1f43212cab87951d659e54e3c8d7d91), [`1519406`](https://github.com/Effect-TS/effect/commit/1519406fed6e8b017ae178dc20bcaa2cf318b570), [`9716990`](https://github.com/Effect-TS/effect/commit/97169902eec3c99baa7f0b2c7b45a0a5eae75819), [`733f75b`](https://github.com/Effect-TS/effect/commit/733f75b7125e3016a975fdd251c0179ae5393786), [`48155c8`](https://github.com/Effect-TS/effect/commit/48155c8ccfc12dcca8a00fa358d50b20c30874e4), [`951d06b`](https://github.com/Effect-TS/effect/commit/951d06b83d459d3e8fa9024e727a5db1662d3322), [`d767b65`](https://github.com/Effect-TS/effect/commit/d767b65a7687e38be23f0b0ee3d52ab5f2360cbe), [`5d52d9d`](https://github.com/Effect-TS/effect/commit/5d52d9d148aaa7f736ed8c310fc8bfa9dc81badf), [`f4151e1`](https://github.com/Effect-TS/effect/commit/f4151e1937c26de14f1d64566f8126173f1b5014), [`e02fbb6`](https://github.com/Effect-TS/effect/commit/e02fbb66f5a0f13dba6c33ef63528a37a17a0676), [`724ce09`](https://github.com/Effect-TS/effect/commit/724ce09650a458d4565e5c7331ea92ca04f08e68), [`dbe91f6`](https://github.com/Effect-TS/effect/commit/dbe91f6961ef9f7e8da910ee5758d9c0d385fca8), [`4c008d2`](https://github.com/Effect-TS/effect/commit/4c008d28b370d817f7ae4579db09836fe084c8d2), [`b650832`](https://github.com/Effect-TS/effect/commit/b6508328708a842f3163467b72486bd228f1a289), [`b46c92f`](https://github.com/Effect-TS/effect/commit/b46c92f3b314f4ffd612b831efa55dd856c587a3), [`5335797`](https://github.com/Effect-TS/effect/commit/5335797003076d9c6fd170da98d779696d555596), [`4b3460d`](https://github.com/Effect-TS/effect/commit/4b3460daa434ec465a95a50704fe1103a9275999), [`6301fd7`](https://github.com/Effect-TS/effect/commit/6301fd710b4325718de2c42997dac28a9e9aa250), [`aebc5c6`](https://github.com/Effect-TS/effect/commit/aebc5c61664b89a840465ec65b79ce635a5ceee8), [`52b2d7b`](https://github.com/Effect-TS/effect/commit/52b2d7b5bd3c7cce3bd5b69c6ab3941004da70f3), [`eec5744`](https://github.com/Effect-TS/effect/commit/eec57445dfa0ef3c5977195ad69415b7e7d42bb6), [`24e0e93`](https://github.com/Effect-TS/effect/commit/24e0e93dc307dc2c2ae86caacb7289e1dab3c103), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`1a7ce81`](https://github.com/Effect-TS/effect/commit/1a7ce8150e3977586c44d8ccb9a8384389bb4d49), [`48f22a7`](https://github.com/Effect-TS/effect/commit/48f22a7d16ae57ee2175d450dafbdeb69e187d2a), [`c96b7f6`](https://github.com/Effect-TS/effect/commit/c96b7f6359662053c3e09344f61dddc7a6caf4ac), [`6d2a942`](https://github.com/Effect-TS/effect/commit/6d2a942ed7cd33b8fd79d549edba33bc9e2a7e3e), [`cc27b19`](https://github.com/Effect-TS/effect/commit/cc27b194b9d13fa3a66ab037e853fca9d41700ff), [`8f9499f`](https://github.com/Effect-TS/effect/commit/8f9499f562729f5f7b08d8bcc4db86b4aeff8a21), [`3eeea73`](https://github.com/Effect-TS/effect/commit/3eeea73cfc3e9b126975c2ddbdb7f7c8c92026e2), [`0a532e5`](https://github.com/Effect-TS/effect/commit/0a532e503f165fdea485a5343fc2f420917e8376), [`f398149`](https://github.com/Effect-TS/effect/commit/f398149c134fd9b67b6cdc52eae3f3248d5c7bbe), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`ace903e`](https://github.com/Effect-TS/effect/commit/ace903e09c2549ceebdec380797beb027cd29f3d), [`e8eb62b`](https://github.com/Effect-TS/effect/commit/e8eb62b3d0ef27e9761cdc2eb93bdec52d6ee204), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`48f22a7`](https://github.com/Effect-TS/effect/commit/48f22a7d16ae57ee2175d450dafbdeb69e187d2a), [`d48506d`](https://github.com/Effect-TS/effect/commit/d48506d97525040aa714305e928126df799795b4), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`d48506d`](https://github.com/Effect-TS/effect/commit/d48506d97525040aa714305e928126df799795b4), [`d48506d`](https://github.com/Effect-TS/effect/commit/d48506d97525040aa714305e928126df799795b4), [`52262be`](https://github.com/Effect-TS/effect/commit/52262be2edce0e350c6ac10f8f725678606399c5), [`1284aa1`](https://github.com/Effect-TS/effect/commit/1284aa183451955ad7921bbe01fd0e095695d444), [`9867b9f`](https://github.com/Effect-TS/effect/commit/9867b9fc69f9cc6c443594fc7eccc7be0c674d9c), [`d0f1a22`](https://github.com/Effect-TS/effect/commit/d0f1a2295155c350b04efb46852cb40032805273), [`979ce39`](https://github.com/Effect-TS/effect/commit/979ce3985d7d62ce2bf240681ca19feda3027452), [`b6d3e67`](https://github.com/Effect-TS/effect/commit/b6d3e67c7cc143cd8470cdf704324e79d23954a9), [`adf6c6c`](https://github.com/Effect-TS/effect/commit/adf6c6cd388af8a3c0c546492e71555368556f6a), [`7314d60`](https://github.com/Effect-TS/effect/commit/7314d605284717aaafe7fc34b88c3c93397e865c), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`1acbd8b`](https://github.com/Effect-TS/effect/commit/1acbd8b44c68ebb23735e9810476b870dbe58aea), [`7bde6cc`](https://github.com/Effect-TS/effect/commit/7bde6ccb2b144fe953ff30a7ef5e1ecc97697146), [`a959a8b`](https://github.com/Effect-TS/effect/commit/a959a8bf21cdb976369f494dc949fa00a050d3e0)]: + - effect@4.0.0-beta.103 + +## 4.0.0-beta.102 + +### Patch Changes + +- Updated dependencies [[`b6392e1`](https://github.com/Effect-TS/effect/commit/b6392e119704553edec1b4fd2869ac0dbec621ef), [`7ed9450`](https://github.com/Effect-TS/effect/commit/7ed945044eb56aa9aeaf62d4746a011c96c58628), [`45762bd`](https://github.com/Effect-TS/effect/commit/45762bd78df9ecd87c98b8d3738cdeeac7d81128), [`a6e8391`](https://github.com/Effect-TS/effect/commit/a6e8391cd31acd898fae18b3f8e7ca4c6f14f065), [`4ac7e8b`](https://github.com/Effect-TS/effect/commit/4ac7e8b136c61a26c3e438c013dfd7349b38e999), [`4cd40f5`](https://github.com/Effect-TS/effect/commit/4cd40f5692477783bef84fed3c5ef1c0cf5602e6), [`6956bc0`](https://github.com/Effect-TS/effect/commit/6956bc0e6cb27f53fbec39d9b18545940f9f598f), [`0e50ec7`](https://github.com/Effect-TS/effect/commit/0e50ec7dbb94390666f292cf9120719bf30a7246), [`9fcdade`](https://github.com/Effect-TS/effect/commit/9fcdade4a8af772b9ccd8b8a24fe8cee0e5d8470), [`57367d5`](https://github.com/Effect-TS/effect/commit/57367d54de55047ff0c5fce9685475e236bf354c), [`35c445f`](https://github.com/Effect-TS/effect/commit/35c445ff18029d192900ea0914c993f58d5cf1a5), [`c917bb9`](https://github.com/Effect-TS/effect/commit/c917bb94a4c1c4e0a24372a8ebb8a5ca232e36b5), [`bc1f358`](https://github.com/Effect-TS/effect/commit/bc1f3583e63344cb2c398d9040d9c975488ed123), [`0e0c9d7`](https://github.com/Effect-TS/effect/commit/0e0c9d7922ff463c1093d9e0576fae12cb0698d5), [`73d40aa`](https://github.com/Effect-TS/effect/commit/73d40aacd8fcae1b48c23f5b0a5c542127401d1d), [`4f1e318`](https://github.com/Effect-TS/effect/commit/4f1e3183f7123591c46224e9c587df7594562a5f), [`9d8d85c`](https://github.com/Effect-TS/effect/commit/9d8d85c1bb7da51970845b8ea830e386e777514a), [`6079fda`](https://github.com/Effect-TS/effect/commit/6079fda7b02f2f01ad91c15ab8c307336f3ba252), [`5101e92`](https://github.com/Effect-TS/effect/commit/5101e92c9c149c153423f43dd7a94f6194653c06), [`d0b3265`](https://github.com/Effect-TS/effect/commit/d0b3265c3262670761471ab3518cf933b1b3b20a), [`7a03c89`](https://github.com/Effect-TS/effect/commit/7a03c893ce6492bf94c0ebfb00b63bf25dcbf83e), [`cea1d9c`](https://github.com/Effect-TS/effect/commit/cea1d9c92601e69ebda040af8a1d860d604d885c), [`078e1f5`](https://github.com/Effect-TS/effect/commit/078e1f5636e31b76a86722a636afc37a8cc25580), [`97bafea`](https://github.com/Effect-TS/effect/commit/97bafeab460833b9781527b437d1cb9cbee63260), [`fab0ab8`](https://github.com/Effect-TS/effect/commit/fab0ab8f7ab15ae596faa4ccf75615a494d11b0b), [`c323d8b`](https://github.com/Effect-TS/effect/commit/c323d8b30dbbe85f9df25b67288b93d5332de333), [`6966353`](https://github.com/Effect-TS/effect/commit/69663534d626003eb10a5e55ab1f13e0379fead1), [`0444004`](https://github.com/Effect-TS/effect/commit/04440041989c1785fe4db286379f2be2c15baa85), [`028bbb3`](https://github.com/Effect-TS/effect/commit/028bbb391e161185da10d974ab33381f769940d7), [`ff5d6e2`](https://github.com/Effect-TS/effect/commit/ff5d6e278a1fdff714315dc1a17075012f05c1f0), [`1bfce93`](https://github.com/Effect-TS/effect/commit/1bfce93e6d2bf0794c11733daf51c2390e7de375), [`7ce815c`](https://github.com/Effect-TS/effect/commit/7ce815cd5af6af991dfc13b890fd22345fc77c20), [`7271a7f`](https://github.com/Effect-TS/effect/commit/7271a7faf1080aa75f2f53ca6a0b5ec9334c1d38), [`475fe5c`](https://github.com/Effect-TS/effect/commit/475fe5c12c2d6504c475797c0634f90da01e1797)]: + - effect@4.0.0-beta.102 + +## 4.0.0-beta.101 + +### Patch Changes + +- Updated dependencies [[`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`b35ed29`](https://github.com/Effect-TS/effect/commit/b35ed2904f01536d303b21f288daf343cf740462), [`dd44624`](https://github.com/Effect-TS/effect/commit/dd446245736a0e88c807a02f03c21450bb9340fa), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`2bae1ac`](https://github.com/Effect-TS/effect/commit/2bae1accce9d3b72cf6d5aefc9b2161af6d88436)]: + - effect@4.0.0-beta.101 + +## 4.0.0-beta.100 + +### Patch Changes + +- Updated dependencies [[`c1288dd`](https://github.com/Effect-TS/effect/commit/c1288dd1a52a2811ab7df57fc4ce236c6be4c745), [`2b58a3d`](https://github.com/Effect-TS/effect/commit/2b58a3dab6bc99776dddaf76e27d811e0f47f3d8), [`6dc83f2`](https://github.com/Effect-TS/effect/commit/6dc83f26ddf20d48db28cf761dd8f3716e5273fb), [`c1e2fe0`](https://github.com/Effect-TS/effect/commit/c1e2fe0cf93564f4d919e3998874c3e70b0cf30f), [`f3fbae8`](https://github.com/Effect-TS/effect/commit/f3fbae8d7bae0d77cb4f35a1598b26c58e3bf94d), [`e000f80`](https://github.com/Effect-TS/effect/commit/e000f80fd55bcd8edc699fdbf4cd109004f4f754), [`f4ee765`](https://github.com/Effect-TS/effect/commit/f4ee7655ee052cf9ba726fd602bb87c89c7c62a9), [`510b55f`](https://github.com/Effect-TS/effect/commit/510b55f3e21750685dbfd5f476a130c1c5af9dbd), [`31d3fc4`](https://github.com/Effect-TS/effect/commit/31d3fc4327c50867bb8d881fa7353aeb03ea2826), [`875e618`](https://github.com/Effect-TS/effect/commit/875e618c3764a7b817ac863d0af86924449528f2), [`688d46a`](https://github.com/Effect-TS/effect/commit/688d46afd0ef923d983ad3d7385f52f217b28d70), [`6ff5023`](https://github.com/Effect-TS/effect/commit/6ff502363b9840a5a5ee0a24bc6cae734ac3a3eb), [`c0333e7`](https://github.com/Effect-TS/effect/commit/c0333e7f755f42ddcca7051e029da8b4eed527bf), [`06e7e8c`](https://github.com/Effect-TS/effect/commit/06e7e8c66015ee318f871b9d2218dee82df2b108), [`eb9b102`](https://github.com/Effect-TS/effect/commit/eb9b10256c8558881b441c2fef833b7037174400), [`8b155da`](https://github.com/Effect-TS/effect/commit/8b155da06e0740c354ec562957a45ab65eb4573b), [`3a87335`](https://github.com/Effect-TS/effect/commit/3a8733564c5db35271aa20564ed0d344daa2a79f)]: + - effect@4.0.0-beta.100 + +## 4.0.0-beta.99 + +### Patch Changes + +- Updated dependencies [[`8ce4795`](https://github.com/Effect-TS/effect/commit/8ce4795ccbaebca4292757db568c005a992546a4), [`80b539f`](https://github.com/Effect-TS/effect/commit/80b539f8aba68f478c75c35c2b4140c4ffc4fada), [`88a54cc`](https://github.com/Effect-TS/effect/commit/88a54cc341006e3ebcb13482c618f62a680ce199), [`e6e6dba`](https://github.com/Effect-TS/effect/commit/e6e6dba6e9d86e7c2ad27dcedf289db76a19697f), [`bfb203e`](https://github.com/Effect-TS/effect/commit/bfb203e95aa439f731acad37fc3a9a831a190f1c), [`2e9a34a`](https://github.com/Effect-TS/effect/commit/2e9a34ac2bece4f3a206160480c991e3841dc67a), [`55d4eb3`](https://github.com/Effect-TS/effect/commit/55d4eb34f2c64d54f6a25a305b5c5438ebd7934e), [`bddb010`](https://github.com/Effect-TS/effect/commit/bddb010eac3d4436cb094edbbee7460c5440c162), [`a328835`](https://github.com/Effect-TS/effect/commit/a328835e50d76bc96648a1c1550456e8c9f81210), [`5560d05`](https://github.com/Effect-TS/effect/commit/5560d05aa6abdd29466d9c3412cc5e648b0adbde), [`8f6e3ad`](https://github.com/Effect-TS/effect/commit/8f6e3adb185b16e8820b98c509b308086f7ff1af), [`46997fa`](https://github.com/Effect-TS/effect/commit/46997fa60401f5e3c93daa4b61f7df8e31caaab4), [`9e6e12d`](https://github.com/Effect-TS/effect/commit/9e6e12d75c118cd265496f2880490d1f33a5c8bf), [`3394b93`](https://github.com/Effect-TS/effect/commit/3394b93d97d6f24fc38670641d1490289ffca7f1), [`febeabc`](https://github.com/Effect-TS/effect/commit/febeabc3f7c31094da000a23edeaabfe2ab00a38), [`54161c9`](https://github.com/Effect-TS/effect/commit/54161c98f6f3569e0c31842f54e6a257f9421c4c), [`385f7a4`](https://github.com/Effect-TS/effect/commit/385f7a4ee4a7359928597ea56d151dbaf5eb5802), [`7eea4d0`](https://github.com/Effect-TS/effect/commit/7eea4d0b73ec554915d7066a71f46326ce2ba45f), [`7543afe`](https://github.com/Effect-TS/effect/commit/7543afea6f4d97d1f1ad876224323838a48daadd), [`44b9cf3`](https://github.com/Effect-TS/effect/commit/44b9cf3d240d726997b4bbcd0ede48e074d3c456), [`7eea4d0`](https://github.com/Effect-TS/effect/commit/7eea4d0b73ec554915d7066a71f46326ce2ba45f), [`0a8aa6a`](https://github.com/Effect-TS/effect/commit/0a8aa6acb90a72b91c24d17133c950e4cacd8abd), [`c8d9fcf`](https://github.com/Effect-TS/effect/commit/c8d9fcf7b030f7c474effbab2764ce7aee1c7209), [`9ca7f9a`](https://github.com/Effect-TS/effect/commit/9ca7f9a69363e4485645966d5a93b8f9597c5206), [`e7aca89`](https://github.com/Effect-TS/effect/commit/e7aca894bb32fbb785b5830837e6061c415a6015), [`55d7560`](https://github.com/Effect-TS/effect/commit/55d75609b8acf8a1b54c1b1c7fbbb65ec741aa3e), [`f809189`](https://github.com/Effect-TS/effect/commit/f809189ddf6b6011ba43a9901baaa734e315da2a), [`88a54cc`](https://github.com/Effect-TS/effect/commit/88a54cc341006e3ebcb13482c618f62a680ce199), [`0ebdbe7`](https://github.com/Effect-TS/effect/commit/0ebdbe74463dc84385956d0b1e8c2b79ebab5400), [`7517d09`](https://github.com/Effect-TS/effect/commit/7517d09f12a0b183a81bd425962c4e280a68b05d), [`212493b`](https://github.com/Effect-TS/effect/commit/212493b9a1eb98cd1ef6959c707a2e5784a5ae91), [`88a54cc`](https://github.com/Effect-TS/effect/commit/88a54cc341006e3ebcb13482c618f62a680ce199), [`80ea8cb`](https://github.com/Effect-TS/effect/commit/80ea8cb9222ca73f564c8267ab2f82966fea027a), [`8df19f4`](https://github.com/Effect-TS/effect/commit/8df19f4fe81d90cc33ace88b9a77e5534f82d604)]: + - effect@4.0.0-beta.99 + +## 4.0.0-beta.98 + +### Patch Changes + +- Updated dependencies [[`989603b`](https://github.com/Effect-TS/effect-smol/commit/989603b60ab1197b64acf214208e0d370cd1f842), [`214c458`](https://github.com/Effect-TS/effect-smol/commit/214c458084bb6995d543cd37d1055f24be3d454e), [`a037273`](https://github.com/Effect-TS/effect-smol/commit/a0372736ac34796969b051bbba4717d7983f1ebe), [`97fdaa9`](https://github.com/Effect-TS/effect-smol/commit/97fdaa9c1f522c65e579365d314a07878e2b904f), [`b24d248`](https://github.com/Effect-TS/effect-smol/commit/b24d248c8df44222ce642087cde2bd859a2dc709), [`19c222c`](https://github.com/Effect-TS/effect-smol/commit/19c222cac2353a3d7b7733caecb00556fffe9a5c), [`eec85dd`](https://github.com/Effect-TS/effect-smol/commit/eec85ddba09ea326fd268ee33eeffd47e50d4671), [`0082f4f`](https://github.com/Effect-TS/effect-smol/commit/0082f4f74fb139fd578f87f0a790e845133983dc), [`8849052`](https://github.com/Effect-TS/effect-smol/commit/884905232d1e9a365e046d8dde27bf9c5707f57f), [`c15e16a`](https://github.com/Effect-TS/effect-smol/commit/c15e16ad130d1fbde25d912b7ac55995066cb35b), [`01d00a3`](https://github.com/Effect-TS/effect-smol/commit/01d00a3abfbf1f37996cdbe738ea5137c646cdd7), [`8bd4589`](https://github.com/Effect-TS/effect-smol/commit/8bd458975a1b3a8ed042eccf317b93d28ded91e7), [`0082f4f`](https://github.com/Effect-TS/effect-smol/commit/0082f4f74fb139fd578f87f0a790e845133983dc), [`6e08428`](https://github.com/Effect-TS/effect-smol/commit/6e08428d980501b856f846ad3f3f0e4ea46e7786), [`388dcf9`](https://github.com/Effect-TS/effect-smol/commit/388dcf953f65d317547f34d40e6443c5f264205f), [`2b7ce2b`](https://github.com/Effect-TS/effect-smol/commit/2b7ce2b513e7ec2a77822f1116dc6ffb6ba93f4e), [`87bea7e`](https://github.com/Effect-TS/effect-smol/commit/87bea7e16259246f3bcdf565446394751abca953), [`ce38dc3`](https://github.com/Effect-TS/effect-smol/commit/ce38dc33bda805a684432cca071f4dc3c6b9a1ba), [`a807cd1`](https://github.com/Effect-TS/effect-smol/commit/a807cd170341deca8a1cfb52c4222585f2431bb9), [`fd8a356`](https://github.com/Effect-TS/effect-smol/commit/fd8a356f06a8c9ce4e7e0a13fc4021c178ed31de), [`c2a5edc`](https://github.com/Effect-TS/effect-smol/commit/c2a5edc3abd31ad5bc123362bc1213e03e4095c3), [`5946da3`](https://github.com/Effect-TS/effect-smol/commit/5946da3804a1be5e752b05b96bd058cdba50a1bf), [`4ae0c5f`](https://github.com/Effect-TS/effect-smol/commit/4ae0c5ffcbe6c56ddfcb05c639112a079483539e), [`5b2a0bc`](https://github.com/Effect-TS/effect-smol/commit/5b2a0bceea3a28a33a58555210c90a415dc74a76), [`72ac585`](https://github.com/Effect-TS/effect-smol/commit/72ac585884befde6af9208da738699a93f1bae79), [`5e8c1b8`](https://github.com/Effect-TS/effect-smol/commit/5e8c1b82bfafa121311f987a49ab75395e3647a7), [`0f9c078`](https://github.com/Effect-TS/effect-smol/commit/0f9c07841b04183f485ee6e6458de73b290b09f5)]: + - effect@4.0.0-beta.98 + +## 4.0.0-beta.97 + +### Patch Changes + +- Updated dependencies []: + - effect@4.0.0-beta.97 + +## 4.0.0-beta.96 + +### Patch Changes + +- Updated dependencies [[`1503f45`](https://github.com/Effect-TS/effect-smol/commit/1503f45cb5bb2a74f4705252ec505a1f0ade7e62), [`57fe793`](https://github.com/Effect-TS/effect-smol/commit/57fe79316ffbc380b30626a168981fb26ae97459), [`0c2f78f`](https://github.com/Effect-TS/effect-smol/commit/0c2f78f695ec474e1ff5474da183577975e418f5), [`0c2f78f`](https://github.com/Effect-TS/effect-smol/commit/0c2f78f695ec474e1ff5474da183577975e418f5), [`0c2f78f`](https://github.com/Effect-TS/effect-smol/commit/0c2f78f695ec474e1ff5474da183577975e418f5), [`97f29df`](https://github.com/Effect-TS/effect-smol/commit/97f29df457f7ffd07cfb4b379315c12c086af805)]: + - effect@4.0.0-beta.96 + +## 4.0.0-beta.95 + +### Patch Changes + +- Updated dependencies [[`a482442`](https://github.com/Effect-TS/effect-smol/commit/a482442abdeb490e9652b854ec3495e4aa7273e7), [`fbefa85`](https://github.com/Effect-TS/effect-smol/commit/fbefa850fab2f0a302c20614496aeaaa2a8b5590), [`0b4a32f`](https://github.com/Effect-TS/effect-smol/commit/0b4a32f4260f0d8500942a133001b0d349328102), [`18a49e1`](https://github.com/Effect-TS/effect-smol/commit/18a49e1786679456258002ff9397faf02f678c2d), [`266cb90`](https://github.com/Effect-TS/effect-smol/commit/266cb90bb2c17aabc40563c32db334f09ba3d74b), [`912f095`](https://github.com/Effect-TS/effect-smol/commit/912f095a34572bbd3cedf6edb27878443e3e4a95), [`a6718f9`](https://github.com/Effect-TS/effect-smol/commit/a6718f9e00a15ca903b0732da46116cbf3d6aca7), [`bef5154`](https://github.com/Effect-TS/effect-smol/commit/bef51540a243aa2f872a00c01d0cd58b7a769baa), [`18e0564`](https://github.com/Effect-TS/effect-smol/commit/18e0564bd0f8ebbdfcaf1e2c21529948e9e4a81d), [`fb50f14`](https://github.com/Effect-TS/effect-smol/commit/fb50f14fc3657c1973785aa5b72ecf0b0d28e0b2)]: + - effect@4.0.0-beta.95 + +## 4.0.0-beta.94 + +### Patch Changes + +- Updated dependencies [[`95a0e9b`](https://github.com/Effect-TS/effect-smol/commit/95a0e9bb62797af0e81c9998773405f248f218c5), [`a0a3490`](https://github.com/Effect-TS/effect-smol/commit/a0a3490bbce765f199d8e077aceac504f0462e63), [`f11ce73`](https://github.com/Effect-TS/effect-smol/commit/f11ce73af60823754dc24194f4ffc561b9ea1c2d), [`ff30b6e`](https://github.com/Effect-TS/effect-smol/commit/ff30b6e7c2c63ffc56a4c5818d6d86b01b5ad528), [`1caab3c`](https://github.com/Effect-TS/effect-smol/commit/1caab3cc30f626efbf15e59d74f539a487e5c85c), [`aa80c47`](https://github.com/Effect-TS/effect-smol/commit/aa80c4775a04db87553e5568764cab7e32a72814), [`c2ae4fc`](https://github.com/Effect-TS/effect-smol/commit/c2ae4fce2f03a4cd1861c2b1179da7df656e662d), [`a0a3490`](https://github.com/Effect-TS/effect-smol/commit/a0a3490bbce765f199d8e077aceac504f0462e63)]: + - effect@4.0.0-beta.94 + +## 4.0.0-beta.93 + +### Patch Changes + +- Updated dependencies [[`00652fe`](https://github.com/Effect-TS/effect-smol/commit/00652fe95c18f87208e91343eb8bf218faa2f677), [`6c58167`](https://github.com/Effect-TS/effect-smol/commit/6c5816746eaf91d2a3c7c899c5720809fa230ae3), [`2bc5415`](https://github.com/Effect-TS/effect-smol/commit/2bc541501a7ef89e542d7cb98e96beb53cd205cc), [`e11cccc`](https://github.com/Effect-TS/effect-smol/commit/e11cccc7d5fe631abccc7d6e3bd296938de0fa2e), [`ba7e77e`](https://github.com/Effect-TS/effect-smol/commit/ba7e77e046b8641a3a4e9750bb88ca4a1d063d3f), [`5713ee7`](https://github.com/Effect-TS/effect-smol/commit/5713ee7edbc3054efde407b2286bbfd45bbc6e1c)]: + - effect@4.0.0-beta.93 + +## 4.0.0-beta.92 + +### Patch Changes + +- Updated dependencies [[`affdc13`](https://github.com/Effect-TS/effect-smol/commit/affdc139045cc325dce321a84a580fdc1b2da7b9)]: + - effect@4.0.0-beta.92 + +## 4.0.0-beta.91 + +### Patch Changes + +- Updated dependencies [[`b135b25`](https://github.com/Effect-TS/effect-smol/commit/b135b2517fca9e7839734ace3699a7dfa75b9075), [`aaa21a3`](https://github.com/Effect-TS/effect-smol/commit/aaa21a369a171c600db294f2a4f640583043e150), [`3475ee6`](https://github.com/Effect-TS/effect-smol/commit/3475ee6c2bda6b05c6d7a12ce30c8bb840b5b1a6)]: + - effect@4.0.0-beta.91 + ## 4.0.0-beta.90 ### Patch Changes diff --git a/repos/effect/packages/sql/sqlite-bun/README.md b/repos/effect/packages/sql/sqlite-bun/README.md index 4062c8c4c7..303b5e35ba 100644 --- a/repos/effect/packages/sql/sqlite-bun/README.md +++ b/repos/effect/packages/sql/sqlite-bun/README.md @@ -1,6 +1,6 @@ # `@effect/sql-sqlite-bun` -An `@effect/sql` implementation using the `bun:sqlite` library. +An Effect SQL implementation using the `bun:sqlite` library. ## Documentation diff --git a/repos/effect/packages/sql/sqlite-bun/docgen.json b/repos/effect/packages/sql/sqlite-bun/docgen.json deleted file mode 100644 index ccf1befa53..0000000000 --- a/repos/effect/packages/sql/sqlite-bun/docgen.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "$schema": "../../node_modules/@effect/docgen/schema.json", - "srcLink": "https://github.com/Effect-TS/effect/tree/main/packages/sql/sqlite-bun/src/", - "exclude": ["src/internal/**/*.ts"], - "tscExecutable": "tsgo", - "examplesCompilerOptions": { - "noEmit": true, - "strict": true, - "skipLibCheck": true, - "moduleResolution": "Bundler", - "module": "ES2022", - "target": "ES2022", - "lib": ["ES2022", "DOM", "DOM.Iterable"], - "rewriteRelativeImportExtensions": true, - "allowImportingTsExtensions": true, - "paths": { - "effect": ["../../../effect/src/index.js"], - "effect/*": ["../../../effect/src/*.js"] - }, - "plugins": [ - { "name": "@effect/language-service", "includeSuggestionsInTsc": false } - ] - } -} diff --git a/repos/effect/packages/sql/sqlite-bun/package.json b/repos/effect/packages/sql/sqlite-bun/package.json index c6c9ee8912..9beb45fd8f 100644 --- a/repos/effect/packages/sql/sqlite-bun/package.json +++ b/repos/effect/packages/sql/sqlite-bun/package.json @@ -1,17 +1,17 @@ { "name": "@effect/sql-sqlite-bun", - "version": "4.0.0-beta.90", + "version": "4.0.0-beta.107", "type": "module", "license": "MIT", "description": "A SQLite toolkit for Effect", "homepage": "https://effect.website", "repository": { "type": "git", - "url": "https://github.com/Effect-TS/effect-smol.git", + "url": "https://github.com/Effect-TS/effect.git", "directory": "packages/sql/sqlite-bun" }, "bugs": { - "url": "https://github.com/Effect-TS/effect-smol/issues" + "url": "https://github.com/Effect-TS/effect/issues" }, "tags": [ "typescript", @@ -29,6 +29,7 @@ ".": "./src/index.ts", "./*": "./src/*.ts", "./internal/*": null, + "./index": null, "./*/index": null }, "files": [ @@ -36,7 +37,10 @@ "dist/**/*.js", "dist/**/*.js.map", "dist/**/*.d.ts", - "dist/**/*.d.ts.map" + "dist/**/*.d.ts.map", + "AGENTS.md", + "CLAUDE.md", + "ai-docs/**/*" ], "publishConfig": { "access": "public", @@ -46,21 +50,19 @@ ".": "./dist/index.js", "./*": "./dist/*.js", "./internal/*": null, + "./index": null, "./*/index": null } }, "scripts": { "codegen": "effect-utils codegen", "build": "tsc -b tsconfig.json && pnpm babel", - "build:tsgo": "tsgo -b tsconfig.json && pnpm babel", "babel": "babel dist --plugins annotate-pure-calls --out-dir dist --source-maps", - "check": "tsc -b tsconfig.json", - "test": "vitest", - "coverage": "vitest --coverage" + "check": "tsc -b tsconfig.json" }, "devDependencies": { "@effect/platform-bun": "workspace:^", - "@types/bun": "^1.3.13", + "@types/bun": "^1.3.14", "effect": "workspace:^" }, "peerDependencies": { diff --git a/repos/effect/packages/sql/sqlite-bun/src/SqliteClient.ts b/repos/effect/packages/sql/sqlite-bun/src/SqliteClient.ts index 1e3493e41a..e54047a112 100644 --- a/repos/effect/packages/sql/sqlite-bun/src/SqliteClient.ts +++ b/repos/effect/packages/sql/sqlite-bun/src/SqliteClient.ts @@ -3,14 +3,20 @@ * * This module opens a SQLite database and exposes it as both `SqliteClient` and * the generic Effect SQL client. It serializes access to the database, enables - * WAL mode unless disabled, and supports database export and extension loading. - * Streaming queries and `updateValues` are not supported by this driver. + * WAL mode unless disabled, and waits up to five seconds for busy databases by + * default. Explicit transactions on writable connections use `BEGIN IMMEDIATE` + * to avoid read-to-write lock upgrades, which serializes them behind other + * writers even when they only read. Clients opened with `readonly: true` are + * unaffected. Busy waits block the event loop because `bun:sqlite` is + * synchronous. Database export and extension loading are supported; streaming + * queries and `updateValues` are not. * * @since 4.0.0 */ import { Database } from "bun:sqlite" import * as Config from "effect/Config" import * as Context from "effect/Context" +import * as Duration from "effect/Duration" import * as Effect from "effect/Effect" import * as Fiber from "effect/Fiber" import { identity } from "effect/Function" @@ -25,6 +31,7 @@ import { classifySqliteError, SqlError } from "effect/unstable/sql/SqlError" import * as Statement from "effect/unstable/sql/Statement" const ATTR_DB_SYSTEM_NAME = "db.system.name" +const MAX_BUSY_TIMEOUT = 2_147_483_647 const classifyError = (cause: unknown, message: string, operation: string) => classifySqliteError(cause, { message, operation }) @@ -48,7 +55,7 @@ export type TypeId = "~@effect/sql-sqlite-bun/SqliteClient" /** * Bun SQLite client service, extending `SqlClient` with database export and extension loading helpers. `updateValues` is not supported. * - * @category models + * @category services * @since 4.0.0 */ export interface SqliteClient extends Client.SqlClient { @@ -74,7 +81,7 @@ export interface SqliteClient extends Client.SqlClient { export const SqliteClient = Context.Service("@effect/sql-sqlite-bun/Client") /** - * Configuration for a Bun SQLite client, including filename, open mode flags, WAL behavior, span attributes, and query/result name transforms. + * Configuration for a Bun SQLite client, including filename, open mode flags, WAL and busy timeout behavior, span attributes, and query/result name transforms. * * @category models * @since 4.0.0 @@ -85,6 +92,12 @@ export interface SqliteClientConfig { readonly create?: boolean | undefined readonly readwrite?: boolean | undefined readonly disableWAL?: boolean | undefined + /** + * How long SQLite waits when the database is busy. Defaults to 5 seconds. + * `Duration.infinity` is clamped to SQLite's maximum timeout. + * Waiting blocks the event loop because `bun:sqlite` is synchronous. + */ + readonly busyTimeout?: Duration.Input | undefined readonly spanAttributes?: Record | undefined @@ -98,7 +111,7 @@ interface SqliteConnection extends Connection { } /** - * Creates a scoped Bun SQLite client for a database file, enabling WAL by default and serializing access. Streaming queries are not implemented. + * Creates a scoped Bun SQLite client for a database file, enabling WAL and a 5-second busy timeout by default. Explicit transactions on writable connections take the write lock for their duration, even when they only read; clients opened with `readonly: true` are unaffected. Streaming queries are not implemented. * * @category constructors * @since 4.0.0 @@ -115,14 +128,20 @@ export const make = ( undefined const makeConnection = Effect.gen(function*() { + const readonly = options.readonly === true const db = new Database(options.filename, { - readonly: options.readonly, - readwrite: options.readwrite ?? true, - create: options.create ?? true + readonly, + readwrite: readonly ? false : options.readwrite ?? true, + create: readonly ? false : options.create ?? true } as any) yield* Effect.addFinalizer(() => Effect.sync(() => db.close())) + const busyTimeout = Math.min( + MAX_BUSY_TIMEOUT, + Math.max(0, Math.round(Duration.toMillis(options.busyTimeout ?? Duration.seconds(5)))) + ) + db.run(`PRAGMA busy_timeout = ${busyTimeout};`) - if (options.disableWAL !== true) { + if (options.disableWAL !== true && !readonly) { db.run("PRAGMA journal_mode = WAL;") } @@ -216,6 +235,7 @@ export const make = ( acquirer, compiler, transactionAcquirer, + beginTransaction: "BEGIN IMMEDIATE", spanAttributes: [ ...(options.spanAttributes ? Object.entries(options.spanAttributes) : []), [ATTR_DB_SYSTEM_NAME, "sqlite"] diff --git a/repos/effect/packages/sql/sqlite-bun/src/SqliteMigrator.ts b/repos/effect/packages/sql/sqlite-bun/src/SqliteMigrator.ts index a07cb54d62..7d90ca4c65 100644 --- a/repos/effect/packages/sql/sqlite-bun/src/SqliteMigrator.ts +++ b/repos/effect/packages/sql/sqlite-bun/src/SqliteMigrator.ts @@ -22,7 +22,7 @@ export * from "effect/unstable/sql/Migrator" /** * Runs SQL migrations using the configured `SqlClient`, returning the migrations that were applied. * - * @category constructors + * @category running * @since 4.0.0 */ export const run: ( @@ -78,7 +78,7 @@ export const run: ( /** * Creates a layer that runs the configured SQL migrations during layer construction. * - * @category constructors + * @category layers * @since 4.0.0 */ export const layer = ( diff --git a/repos/effect/packages/sql/sqlite-bun/test/Client.test.ts b/repos/effect/packages/sql/sqlite-bun/test/Client.test.ts index fc3285a360..905a4a5e69 100644 --- a/repos/effect/packages/sql/sqlite-bun/test/Client.test.ts +++ b/repos/effect/packages/sql/sqlite-bun/test/Client.test.ts @@ -1,6 +1,72 @@ -import { describe, it } from "@effect/vitest" -import { Effect } from "effect" +import { assert, describe, it } from "@effect/vitest" +import { Duration, Effect } from "effect" +import { Reactivity } from "effect/unstable/reactivity" +import { rm } from "node:fs/promises" + +const isBun = "bun" in process.versions describe("Client", () => { it.effect("should work", () => Effect.void) + + it.effect.skipIf(!isBun)("uses a 5 second busy timeout", () => + Effect.gen(function*() { + const { SqliteClient } = yield* Effect.promise(() => import("@effect/sql-sqlite-bun")) + const sql = yield* SqliteClient.make({ filename: ":memory:" }) + assert.deepStrictEqual(yield* sql`PRAGMA busy_timeout`, [{ timeout: 5000 }]) + + const custom = yield* SqliteClient.make({ filename: ":memory:", busyTimeout: "1 second" }) + assert.deepStrictEqual(yield* custom`PRAGMA busy_timeout`, [{ timeout: 1000 }]) + + const infinite = yield* SqliteClient.make({ filename: ":memory:", busyTimeout: Duration.infinity }) + assert.deepStrictEqual(yield* infinite`PRAGMA busy_timeout`, [{ timeout: 2_147_483_647 }]) + }).pipe(Effect.provide(Reactivity.layer))) + + it.effect.skipIf(!isBun)("starts transactions immediately", () => + Effect.gen(function*() { + const { SqliteClient } = yield* Effect.promise(() => import("@effect/sql-sqlite-bun")) + const filename = `/tmp/effect-sqlite-bun-transaction-${crypto.randomUUID()}.db` + yield* Effect.acquireRelease( + Effect.void, + () => Effect.promise(() => rm(filename, { force: true })) + ) + + const client = yield* SqliteClient.make({ filename }) + const contender = yield* SqliteClient.make({ filename }) + yield* contender`PRAGMA busy_timeout = 1` + + yield* client.withTransaction( + Effect.gen(function*() { + const error = yield* Effect.flip(contender`BEGIN IMMEDIATE`) + assert.strictEqual(error._tag, "SqlError") + assert(error.reason.cause instanceof Error) + assert.match(error.reason.cause.message, /database is locked/i) + }) + ) + }).pipe(Effect.provide(Reactivity.layer))) + + it.effect.skipIf(!isBun)("readonly clients reject writes", () => + Effect.gen(function*() { + const { SqliteClient } = yield* Effect.promise(() => import("@effect/sql-sqlite-bun")) + const filename = `/tmp/effect-sqlite-bun-readonly-${crypto.randomUUID()}.db` + yield* Effect.acquireRelease( + Effect.void, + () => Effect.promise(() => rm(filename, { force: true })) + ) + + yield* Effect.scoped( + Effect.gen(function*() { + const sql = yield* SqliteClient.make({ filename }) + yield* sql`CREATE TABLE test (id INTEGER PRIMARY KEY)` + }) + ) + + const sql = yield* SqliteClient.make({ filename, readonly: true }) + assert.deepStrictEqual(yield* sql`SELECT * FROM test`, []) + assert.deepStrictEqual(yield* sql.withTransaction(sql`SELECT * FROM test`), []) + + const error = yield* Effect.flip(sql`INSERT INTO test DEFAULT VALUES`) + assert.strictEqual(error._tag, "SqlError") + assert(error.reason.cause instanceof Error) + assert.match(error.reason.cause.message, /attempt to write a readonly database/i) + }).pipe(Effect.provide(Reactivity.layer))) }) diff --git a/repos/effect/packages/sql/sqlite-bun/tsconfig.json b/repos/effect/packages/sql/sqlite-bun/tsconfig.json index cca75f8a01..aee873cc41 100644 --- a/repos/effect/packages/sql/sqlite-bun/tsconfig.json +++ b/repos/effect/packages/sql/sqlite-bun/tsconfig.json @@ -1,5 +1,5 @@ { - "$schema": "http://json.schemastore.org/tsconfig", + "$schema": "https://json.schemastore.org/tsconfig", "extends": "../../../tsconfig.base.json", "include": ["src"], "references": [ diff --git a/repos/effect/packages/sql/sqlite-bun/vitest.config.ts b/repos/effect/packages/sql/sqlite-bun/vitest.config.ts deleted file mode 100644 index c8a52c1826..0000000000 --- a/repos/effect/packages/sql/sqlite-bun/vitest.config.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { mergeConfig, type ViteUserConfig } from "vitest/config" -import shared from "../../../vitest.shared.ts" - -const config: ViteUserConfig = {} - -export default mergeConfig(shared, config) diff --git a/repos/effect/packages/sql/sqlite-do/CHANGELOG.md b/repos/effect/packages/sql/sqlite-do/CHANGELOG.md index d834796c11..fa106feb06 100644 --- a/repos/effect/packages/sql/sqlite-do/CHANGELOG.md +++ b/repos/effect/packages/sql/sqlite-do/CHANGELOG.md @@ -1,5 +1,128 @@ # @effect/sql-sqlite-do +## 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 + +- 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 + +- [#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 + +- Updated dependencies [[`989603b`](https://github.com/Effect-TS/effect-smol/commit/989603b60ab1197b64acf214208e0d370cd1f842), [`214c458`](https://github.com/Effect-TS/effect-smol/commit/214c458084bb6995d543cd37d1055f24be3d454e), [`a037273`](https://github.com/Effect-TS/effect-smol/commit/a0372736ac34796969b051bbba4717d7983f1ebe), [`97fdaa9`](https://github.com/Effect-TS/effect-smol/commit/97fdaa9c1f522c65e579365d314a07878e2b904f), [`b24d248`](https://github.com/Effect-TS/effect-smol/commit/b24d248c8df44222ce642087cde2bd859a2dc709), [`19c222c`](https://github.com/Effect-TS/effect-smol/commit/19c222cac2353a3d7b7733caecb00556fffe9a5c), [`eec85dd`](https://github.com/Effect-TS/effect-smol/commit/eec85ddba09ea326fd268ee33eeffd47e50d4671), [`0082f4f`](https://github.com/Effect-TS/effect-smol/commit/0082f4f74fb139fd578f87f0a790e845133983dc), [`8849052`](https://github.com/Effect-TS/effect-smol/commit/884905232d1e9a365e046d8dde27bf9c5707f57f), [`c15e16a`](https://github.com/Effect-TS/effect-smol/commit/c15e16ad130d1fbde25d912b7ac55995066cb35b), [`01d00a3`](https://github.com/Effect-TS/effect-smol/commit/01d00a3abfbf1f37996cdbe738ea5137c646cdd7), [`8bd4589`](https://github.com/Effect-TS/effect-smol/commit/8bd458975a1b3a8ed042eccf317b93d28ded91e7), [`0082f4f`](https://github.com/Effect-TS/effect-smol/commit/0082f4f74fb139fd578f87f0a790e845133983dc), [`6e08428`](https://github.com/Effect-TS/effect-smol/commit/6e08428d980501b856f846ad3f3f0e4ea46e7786), [`388dcf9`](https://github.com/Effect-TS/effect-smol/commit/388dcf953f65d317547f34d40e6443c5f264205f), [`2b7ce2b`](https://github.com/Effect-TS/effect-smol/commit/2b7ce2b513e7ec2a77822f1116dc6ffb6ba93f4e), [`87bea7e`](https://github.com/Effect-TS/effect-smol/commit/87bea7e16259246f3bcdf565446394751abca953), [`ce38dc3`](https://github.com/Effect-TS/effect-smol/commit/ce38dc33bda805a684432cca071f4dc3c6b9a1ba), [`a807cd1`](https://github.com/Effect-TS/effect-smol/commit/a807cd170341deca8a1cfb52c4222585f2431bb9), [`fd8a356`](https://github.com/Effect-TS/effect-smol/commit/fd8a356f06a8c9ce4e7e0a13fc4021c178ed31de), [`c2a5edc`](https://github.com/Effect-TS/effect-smol/commit/c2a5edc3abd31ad5bc123362bc1213e03e4095c3), [`5946da3`](https://github.com/Effect-TS/effect-smol/commit/5946da3804a1be5e752b05b96bd058cdba50a1bf), [`4ae0c5f`](https://github.com/Effect-TS/effect-smol/commit/4ae0c5ffcbe6c56ddfcb05c639112a079483539e), [`5b2a0bc`](https://github.com/Effect-TS/effect-smol/commit/5b2a0bceea3a28a33a58555210c90a415dc74a76), [`72ac585`](https://github.com/Effect-TS/effect-smol/commit/72ac585884befde6af9208da738699a93f1bae79), [`5e8c1b8`](https://github.com/Effect-TS/effect-smol/commit/5e8c1b82bfafa121311f987a49ab75395e3647a7), [`0f9c078`](https://github.com/Effect-TS/effect-smol/commit/0f9c07841b04183f485ee6e6458de73b290b09f5)]: + - effect@4.0.0-beta.98 + +## 4.0.0-beta.97 + +### Patch Changes + +- Updated dependencies []: + - effect@4.0.0-beta.97 + +## 4.0.0-beta.96 + +### Patch Changes + +- Updated dependencies [[`1503f45`](https://github.com/Effect-TS/effect-smol/commit/1503f45cb5bb2a74f4705252ec505a1f0ade7e62), [`57fe793`](https://github.com/Effect-TS/effect-smol/commit/57fe79316ffbc380b30626a168981fb26ae97459), [`0c2f78f`](https://github.com/Effect-TS/effect-smol/commit/0c2f78f695ec474e1ff5474da183577975e418f5), [`0c2f78f`](https://github.com/Effect-TS/effect-smol/commit/0c2f78f695ec474e1ff5474da183577975e418f5), [`0c2f78f`](https://github.com/Effect-TS/effect-smol/commit/0c2f78f695ec474e1ff5474da183577975e418f5), [`97f29df`](https://github.com/Effect-TS/effect-smol/commit/97f29df457f7ffd07cfb4b379315c12c086af805)]: + - effect@4.0.0-beta.96 + +## 4.0.0-beta.95 + +### Patch Changes + +- Updated dependencies [[`a482442`](https://github.com/Effect-TS/effect-smol/commit/a482442abdeb490e9652b854ec3495e4aa7273e7), [`fbefa85`](https://github.com/Effect-TS/effect-smol/commit/fbefa850fab2f0a302c20614496aeaaa2a8b5590), [`0b4a32f`](https://github.com/Effect-TS/effect-smol/commit/0b4a32f4260f0d8500942a133001b0d349328102), [`18a49e1`](https://github.com/Effect-TS/effect-smol/commit/18a49e1786679456258002ff9397faf02f678c2d), [`266cb90`](https://github.com/Effect-TS/effect-smol/commit/266cb90bb2c17aabc40563c32db334f09ba3d74b), [`912f095`](https://github.com/Effect-TS/effect-smol/commit/912f095a34572bbd3cedf6edb27878443e3e4a95), [`a6718f9`](https://github.com/Effect-TS/effect-smol/commit/a6718f9e00a15ca903b0732da46116cbf3d6aca7), [`bef5154`](https://github.com/Effect-TS/effect-smol/commit/bef51540a243aa2f872a00c01d0cd58b7a769baa), [`18e0564`](https://github.com/Effect-TS/effect-smol/commit/18e0564bd0f8ebbdfcaf1e2c21529948e9e4a81d), [`fb50f14`](https://github.com/Effect-TS/effect-smol/commit/fb50f14fc3657c1973785aa5b72ecf0b0d28e0b2)]: + - effect@4.0.0-beta.95 + +## 4.0.0-beta.94 + +### Patch Changes + +- Updated dependencies [[`95a0e9b`](https://github.com/Effect-TS/effect-smol/commit/95a0e9bb62797af0e81c9998773405f248f218c5), [`a0a3490`](https://github.com/Effect-TS/effect-smol/commit/a0a3490bbce765f199d8e077aceac504f0462e63), [`f11ce73`](https://github.com/Effect-TS/effect-smol/commit/f11ce73af60823754dc24194f4ffc561b9ea1c2d), [`ff30b6e`](https://github.com/Effect-TS/effect-smol/commit/ff30b6e7c2c63ffc56a4c5818d6d86b01b5ad528), [`1caab3c`](https://github.com/Effect-TS/effect-smol/commit/1caab3cc30f626efbf15e59d74f539a487e5c85c), [`aa80c47`](https://github.com/Effect-TS/effect-smol/commit/aa80c4775a04db87553e5568764cab7e32a72814), [`c2ae4fc`](https://github.com/Effect-TS/effect-smol/commit/c2ae4fce2f03a4cd1861c2b1179da7df656e662d), [`a0a3490`](https://github.com/Effect-TS/effect-smol/commit/a0a3490bbce765f199d8e077aceac504f0462e63)]: + - effect@4.0.0-beta.94 + +## 4.0.0-beta.93 + +### Patch Changes + +- Updated dependencies [[`00652fe`](https://github.com/Effect-TS/effect-smol/commit/00652fe95c18f87208e91343eb8bf218faa2f677), [`6c58167`](https://github.com/Effect-TS/effect-smol/commit/6c5816746eaf91d2a3c7c899c5720809fa230ae3), [`2bc5415`](https://github.com/Effect-TS/effect-smol/commit/2bc541501a7ef89e542d7cb98e96beb53cd205cc), [`e11cccc`](https://github.com/Effect-TS/effect-smol/commit/e11cccc7d5fe631abccc7d6e3bd296938de0fa2e), [`ba7e77e`](https://github.com/Effect-TS/effect-smol/commit/ba7e77e046b8641a3a4e9750bb88ca4a1d063d3f), [`5713ee7`](https://github.com/Effect-TS/effect-smol/commit/5713ee7edbc3054efde407b2286bbfd45bbc6e1c)]: + - effect@4.0.0-beta.93 + +## 4.0.0-beta.92 + +### Patch Changes + +- Updated dependencies [[`affdc13`](https://github.com/Effect-TS/effect-smol/commit/affdc139045cc325dce321a84a580fdc1b2da7b9)]: + - effect@4.0.0-beta.92 + +## 4.0.0-beta.91 + +### Patch Changes + +- Updated dependencies [[`b135b25`](https://github.com/Effect-TS/effect-smol/commit/b135b2517fca9e7839734ace3699a7dfa75b9075), [`aaa21a3`](https://github.com/Effect-TS/effect-smol/commit/aaa21a369a171c600db294f2a4f640583043e150), [`3475ee6`](https://github.com/Effect-TS/effect-smol/commit/3475ee6c2bda6b05c6d7a12ce30c8bb840b5b1a6)]: + - effect@4.0.0-beta.91 + ## 4.0.0-beta.90 ### Patch Changes diff --git a/repos/effect/packages/sql/sqlite-do/README.md b/repos/effect/packages/sql/sqlite-do/README.md index e3c47c190a..33f3cef870 100644 --- a/repos/effect/packages/sql/sqlite-do/README.md +++ b/repos/effect/packages/sql/sqlite-do/README.md @@ -1,6 +1,6 @@ # `@effect/sql-sqlite-do` -An `@effect/sql` implementation for Cloudflare Durable Objects sqlite storage. +An Effect SQL implementation for Cloudflare Durable Objects SQLite storage. ## Documentation diff --git a/repos/effect/packages/sql/sqlite-do/docgen.json b/repos/effect/packages/sql/sqlite-do/docgen.json deleted file mode 100644 index d89f871a47..0000000000 --- a/repos/effect/packages/sql/sqlite-do/docgen.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "$schema": "../../node_modules/@effect/docgen/schema.json", - "srcLink": "https://github.com/Effect-TS/effect/tree/main/packages/sql/do/src/", - "exclude": ["src/internal/**/*.ts"], - "tscExecutable": "tsgo", - "examplesCompilerOptions": { - "noEmit": true, - "strict": true, - "skipLibCheck": true, - "moduleResolution": "Bundler", - "module": "ES2022", - "target": "ES2022", - "lib": ["ES2022", "DOM", "DOM.Iterable"], - "rewriteRelativeImportExtensions": true, - "allowImportingTsExtensions": true, - "paths": { - "effect": ["../../../effect/src/index.js"], - "effect/*": ["../../../effect/src/*.js"] - }, - "plugins": [ - { "name": "@effect/language-service", "includeSuggestionsInTsc": false } - ] - } -} diff --git a/repos/effect/packages/sql/sqlite-do/package.json b/repos/effect/packages/sql/sqlite-do/package.json index 9b5fca8339..9b98ba6d46 100644 --- a/repos/effect/packages/sql/sqlite-do/package.json +++ b/repos/effect/packages/sql/sqlite-do/package.json @@ -1,17 +1,17 @@ { "name": "@effect/sql-sqlite-do", - "version": "4.0.0-beta.90", + "version": "4.0.0-beta.107", "type": "module", "license": "MIT", "description": "A SQLite toolkit for Effect", "homepage": "https://effect.website", "repository": { "type": "git", - "url": "https://github.com/Effect-TS/effect-smol.git", + "url": "https://github.com/Effect-TS/effect.git", "directory": "packages/sql/sqlite-do" }, "bugs": { - "url": "https://github.com/Effect-TS/effect-smol/issues" + "url": "https://github.com/Effect-TS/effect/issues" }, "tags": [ "typescript", @@ -29,6 +29,7 @@ ".": "./src/index.ts", "./*": "./src/*.ts", "./internal/*": null, + "./index": null, "./*/index": null }, "files": [ @@ -36,7 +37,10 @@ "dist/**/*.js", "dist/**/*.js.map", "dist/**/*.d.ts", - "dist/**/*.d.ts.map" + "dist/**/*.d.ts.map", + "AGENTS.md", + "CLAUDE.md", + "ai-docs/**/*" ], "publishConfig": { "access": "public", @@ -46,20 +50,18 @@ ".": "./dist/index.js", "./*": "./dist/*.js", "./internal/*": null, + "./index": null, "./*/index": null } }, "scripts": { "codegen": "effect-utils codegen", "build": "tsc -b tsconfig.json && pnpm babel", - "build:tsgo": "tsgo -b tsconfig.json && pnpm babel", "babel": "babel dist --plugins annotate-pure-calls --out-dir dist --source-maps", - "check": "tsc -b tsconfig.json", - "test": "vitest", - "coverage": "vitest --coverage" + "check": "tsc -b tsconfig.json" }, "devDependencies": { - "@cloudflare/workers-types": "^4.20260511.1", + "@cloudflare/workers-types": "^5.20260708.1", "effect": "workspace:^" }, "peerDependencies": { diff --git a/repos/effect/packages/sql/sqlite-do/src/SqliteClient.ts b/repos/effect/packages/sql/sqlite-do/src/SqliteClient.ts index 9df97d2698..7a4c1a253c 100644 --- a/repos/effect/packages/sql/sqlite-do/src/SqliteClient.ts +++ b/repos/effect/packages/sql/sqlite-do/src/SqliteClient.ts @@ -28,6 +28,7 @@ import * as Exit from "effect/Exit" import * as Fiber from "effect/Fiber" import { identity } from "effect/Function" import * as Layer from "effect/Layer" +import * as Rec from "effect/Record" import * as Scope from "effect/Scope" import * as Semaphore from "effect/Semaphore" import * as Stream from "effect/Stream" @@ -61,7 +62,7 @@ export type TypeId = "~@effect/sql-sqlite-do/SqliteClient" /** * Cloudflare Durable Object SQLite client service, extending `SqlClient` with its configuration. `updateValues` is not supported. * - * @category models + * @category services * @since 4.0.0 */ export interface SqliteClient extends Client.SqlClient { @@ -199,7 +200,7 @@ export const make = ( const obj: any = {} for (let i = 0; i < columns.length; i++) { const value = result[i] - obj[columns[i]] = value instanceof ArrayBuffer ? new Uint8Array(value) : value + Rec.assignProperty(obj, columns[i], value instanceof ArrayBuffer ? new Uint8Array(value) : value) } yield obj } diff --git a/repos/effect/packages/sql/sqlite-do/src/SqliteMigrator.ts b/repos/effect/packages/sql/sqlite-do/src/SqliteMigrator.ts index c02c167278..234e276a31 100644 --- a/repos/effect/packages/sql/sqlite-do/src/SqliteMigrator.ts +++ b/repos/effect/packages/sql/sqlite-do/src/SqliteMigrator.ts @@ -38,7 +38,7 @@ export * from "effect/unstable/sql/Migrator" /** * Runs SQL migrations using the configured `SqlClient`, returning the migrations that were applied. * - * @category constructors + * @category running * @since 4.0.0 */ export const run: ( @@ -52,7 +52,7 @@ export const run: ( /** * Creates a layer that runs the configured SQL migrations during layer construction. * - * @category constructors + * @category layers * @since 4.0.0 */ export const layer = ( diff --git a/repos/effect/packages/sql/sqlite-do/tsconfig.json b/repos/effect/packages/sql/sqlite-do/tsconfig.json index 19a2f5dbca..e2a8ca19a0 100644 --- a/repos/effect/packages/sql/sqlite-do/tsconfig.json +++ b/repos/effect/packages/sql/sqlite-do/tsconfig.json @@ -1,5 +1,5 @@ { - "$schema": "http://json.schemastore.org/tsconfig", + "$schema": "https://json.schemastore.org/tsconfig", "extends": "../../../tsconfig.base.json", "include": ["src"], "references": [ diff --git a/repos/effect/packages/sql/sqlite-do/vitest.config.ts b/repos/effect/packages/sql/sqlite-do/vitest.config.ts deleted file mode 100644 index c8a52c1826..0000000000 --- a/repos/effect/packages/sql/sqlite-do/vitest.config.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { mergeConfig, type ViteUserConfig } from "vitest/config" -import shared from "../../../vitest.shared.ts" - -const config: ViteUserConfig = {} - -export default mergeConfig(shared, config) diff --git a/repos/effect/packages/sql/sqlite-node/CHANGELOG.md b/repos/effect/packages/sql/sqlite-node/CHANGELOG.md index bb504050bf..bc94eb7ced 100644 --- a/repos/effect/packages/sql/sqlite-node/CHANGELOG.md +++ b/repos/effect/packages/sql/sqlite-node/CHANGELOG.md @@ -1,5 +1,129 @@ # @effect/sql-sqlite-node +## 4.0.0-beta.107 + +### Patch Changes + +- [#7162](https://github.com/Effect-TS/effect/pull/7162) [`c30386d`](https://github.com/Effect-TS/effect/commit/c30386df4be367350ee480fa357ddeae7a8f0d08) Thanks @tim-smart! - 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. +- 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 + +- 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 + +- [#2487](https://github.com/Effect-TS/effect-smol/pull/2487) [`51f6d19`](https://github.com/Effect-TS/effect-smol/commit/51f6d197b7572a3cd568f7b358cccdfb426f712e) Thanks @tim-smart! - Replace the `better-sqlite3` dependency with Node.js' built-in `node:sqlite` module. + +- Updated dependencies [[`95a0e9b`](https://github.com/Effect-TS/effect-smol/commit/95a0e9bb62797af0e81c9998773405f248f218c5), [`a0a3490`](https://github.com/Effect-TS/effect-smol/commit/a0a3490bbce765f199d8e077aceac504f0462e63), [`f11ce73`](https://github.com/Effect-TS/effect-smol/commit/f11ce73af60823754dc24194f4ffc561b9ea1c2d), [`ff30b6e`](https://github.com/Effect-TS/effect-smol/commit/ff30b6e7c2c63ffc56a4c5818d6d86b01b5ad528), [`1caab3c`](https://github.com/Effect-TS/effect-smol/commit/1caab3cc30f626efbf15e59d74f539a487e5c85c), [`aa80c47`](https://github.com/Effect-TS/effect-smol/commit/aa80c4775a04db87553e5568764cab7e32a72814), [`c2ae4fc`](https://github.com/Effect-TS/effect-smol/commit/c2ae4fce2f03a4cd1861c2b1179da7df656e662d), [`a0a3490`](https://github.com/Effect-TS/effect-smol/commit/a0a3490bbce765f199d8e077aceac504f0462e63)]: + - effect@4.0.0-beta.94 + +## 4.0.0-beta.93 + +### Patch Changes + +- Updated dependencies [[`00652fe`](https://github.com/Effect-TS/effect-smol/commit/00652fe95c18f87208e91343eb8bf218faa2f677), [`6c58167`](https://github.com/Effect-TS/effect-smol/commit/6c5816746eaf91d2a3c7c899c5720809fa230ae3), [`2bc5415`](https://github.com/Effect-TS/effect-smol/commit/2bc541501a7ef89e542d7cb98e96beb53cd205cc), [`e11cccc`](https://github.com/Effect-TS/effect-smol/commit/e11cccc7d5fe631abccc7d6e3bd296938de0fa2e), [`ba7e77e`](https://github.com/Effect-TS/effect-smol/commit/ba7e77e046b8641a3a4e9750bb88ca4a1d063d3f), [`5713ee7`](https://github.com/Effect-TS/effect-smol/commit/5713ee7edbc3054efde407b2286bbfd45bbc6e1c)]: + - effect@4.0.0-beta.93 + +## 4.0.0-beta.92 + +### Patch Changes + +- Updated dependencies [[`affdc13`](https://github.com/Effect-TS/effect-smol/commit/affdc139045cc325dce321a84a580fdc1b2da7b9)]: + - effect@4.0.0-beta.92 + +## 4.0.0-beta.91 + +### Patch Changes + +- Updated dependencies [[`b135b25`](https://github.com/Effect-TS/effect-smol/commit/b135b2517fca9e7839734ace3699a7dfa75b9075), [`aaa21a3`](https://github.com/Effect-TS/effect-smol/commit/aaa21a369a171c600db294f2a4f640583043e150), [`3475ee6`](https://github.com/Effect-TS/effect-smol/commit/3475ee6c2bda6b05c6d7a12ce30c8bb840b5b1a6)]: + - effect@4.0.0-beta.91 + ## 4.0.0-beta.90 ### Patch Changes diff --git a/repos/effect/packages/sql/sqlite-node/README.md b/repos/effect/packages/sql/sqlite-node/README.md index 30e2311ca2..8c4dd1432f 100644 --- a/repos/effect/packages/sql/sqlite-node/README.md +++ b/repos/effect/packages/sql/sqlite-node/README.md @@ -1,6 +1,6 @@ # `@effect/sql-sqlite-node` -An `@effect/sql` implementation using the `better-sqlite3` library. +An Effect SQL implementation using Node.js' built-in `node:sqlite` module. ## Documentation diff --git a/repos/effect/packages/sql/sqlite-node/docgen.json b/repos/effect/packages/sql/sqlite-node/docgen.json deleted file mode 100644 index 689fa4240e..0000000000 --- a/repos/effect/packages/sql/sqlite-node/docgen.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "$schema": "../../node_modules/@effect/docgen/schema.json", - "srcLink": "https://github.com/Effect-TS/effect/tree/main/packages/sql/sqlite-node/src/", - "exclude": ["src/internal/**/*.ts"], - "tscExecutable": "tsgo", - "examplesCompilerOptions": { - "noEmit": true, - "strict": true, - "skipLibCheck": true, - "moduleResolution": "Bundler", - "module": "ES2022", - "target": "ES2022", - "lib": ["ES2022", "DOM", "DOM.Iterable"], - "rewriteRelativeImportExtensions": true, - "allowImportingTsExtensions": true, - "paths": { - "effect": ["../../../effect/src/index.js"], - "effect/*": ["../../../effect/src/*.js"] - }, - "plugins": [ - { "name": "@effect/language-service", "includeSuggestionsInTsc": false } - ] - } -} diff --git a/repos/effect/packages/sql/sqlite-node/package.json b/repos/effect/packages/sql/sqlite-node/package.json index b654c347d4..14765cfcb0 100644 --- a/repos/effect/packages/sql/sqlite-node/package.json +++ b/repos/effect/packages/sql/sqlite-node/package.json @@ -1,17 +1,17 @@ { "name": "@effect/sql-sqlite-node", - "version": "4.0.0-beta.90", + "version": "4.0.0-beta.107", "type": "module", "license": "MIT", "description": "A SQLite toolkit for Effect", "homepage": "https://effect.website", "repository": { "type": "git", - "url": "https://github.com/Effect-TS/effect-smol.git", + "url": "https://github.com/Effect-TS/effect.git", "directory": "packages/sql/sqlite-node" }, "bugs": { - "url": "https://github.com/Effect-TS/effect-smol/issues" + "url": "https://github.com/Effect-TS/effect/issues" }, "tags": [ "typescript", @@ -29,6 +29,7 @@ ".": "./src/index.ts", "./*": "./src/*.ts", "./internal/*": null, + "./index": null, "./*/index": null }, "files": [ @@ -36,7 +37,10 @@ "dist/**/*.js", "dist/**/*.js.map", "dist/**/*.d.ts", - "dist/**/*.d.ts.map" + "dist/**/*.d.ts.map", + "AGENTS.md", + "CLAUDE.md", + "ai-docs/**/*" ], "publishConfig": { "access": "public", @@ -46,27 +50,22 @@ ".": "./dist/index.js", "./*": "./dist/*.js", "./internal/*": null, + "./index": null, "./*/index": null } }, "scripts": { "codegen": "effect-utils codegen", "build": "tsc -b tsconfig.json && pnpm babel", - "build:tsgo": "tsgo -b tsconfig.json && pnpm babel", "babel": "babel dist --plugins annotate-pure-calls --out-dir dist --source-maps", - "check": "tsc -b tsconfig.json", - "test": "vitest", - "coverage": "vitest --coverage" + "check": "tsc -b tsconfig.json" }, "devDependencies": { "@effect/platform-node": "workspace:^", - "@types/better-sqlite3": "^7.6.13", + "@types/node": "^26.1.2", "effect": "workspace:^" }, "peerDependencies": { "effect": "workspace:^" - }, - "dependencies": { - "better-sqlite3": "^12.9.0" } } diff --git a/repos/effect/packages/sql/sqlite-node/src/SqliteClient.ts b/repos/effect/packages/sql/sqlite-node/src/SqliteClient.ts index 518384c2da..9124443ab7 100644 --- a/repos/effect/packages/sql/sqlite-node/src/SqliteClient.ts +++ b/repos/effect/packages/sql/sqlite-node/src/SqliteClient.ts @@ -1,15 +1,19 @@ /** - * Connects Effect SQL to SQLite on Node.js using `better-sqlite3`. + * Connects Effect SQL to SQLite on Node.js using `node:sqlite`. * * This module opens a SQLite database and exposes it as both `SqliteClient` and * the generic Effect SQL client. It serializes access through one connection, - * caches prepared statements, enables WAL mode unless disabled, and supports - * database export, backup, and extension loading. Streaming queries and - * `updateValues` are not supported by this driver. + * caches prepared statements, enables WAL mode unless disabled, and waits up + * to five seconds for busy databases by default. Explicit transactions on + * writable connections use `BEGIN IMMEDIATE` to avoid read-to-write lock + * upgrades, which serializes them behind other writers even when they only + * read. Clients opened with `readonly: true` are unaffected. Busy waits block + * the Node.js event loop because `node:sqlite` is synchronous. Database backup + * and extension loading are supported; streaming queries and `updateValues` + * are not. * * @since 4.0.0 */ -import Sqlite from "better-sqlite3" import * as Cache from "effect/Cache" import * as Config from "effect/Config" import * as Context from "effect/Context" @@ -26,11 +30,11 @@ import * as Client from "effect/unstable/sql/SqlClient" import type { Connection } from "effect/unstable/sql/SqlConnection" import { classifySqliteError, SqlError } from "effect/unstable/sql/SqlError" import * as Statement from "effect/unstable/sql/Statement" +import { backup as backupDatabase, DatabaseSync } from "node:sqlite" +import type { StatementSync } from "node:sqlite" const ATTR_DB_SYSTEM_NAME = "db.system.name" - -const classifyError = (cause: unknown, message: string, operation: string) => - classifySqliteError(cause, { message, operation }) +const MAX_BUSY_TIMEOUT = 2_147_483_647 /** * Runtime type identifier used to mark Node `SqliteClient` values. @@ -51,13 +55,12 @@ export type TypeId = "~@effect/sql-sqlite-node/SqliteClient" /** * Node SQLite client service, extending `SqlClient` with database export, backup, and extension loading helpers. `updateValues` is not supported. * - * @category models + * @category services * @since 4.0.0 */ export interface SqliteClient extends Client.SqlClient { readonly [TypeId]: TypeId readonly config: SqliteClientConfig - readonly export: Effect.Effect readonly backup: (destination: string) => Effect.Effect readonly loadExtension: (path: string) => Effect.Effect @@ -85,7 +88,7 @@ export interface BackupMetadata { export const SqliteClient = Context.Service("@effect/sql-sqlite-node/SqliteClient") /** - * Configuration for a node SQLite client backed by `better-sqlite3`, including the database filename, read-only mode, statement cache settings, WAL behavior, span attributes, and query/result name transforms. + * Configuration for a node SQLite client backed by `node:sqlite`, including the database filename, read-only mode, statement cache settings, WAL and busy timeout behavior, span attributes, and query/result name transforms. * * @category models * @since 4.0.0 @@ -96,6 +99,12 @@ export interface SqliteClientConfig { readonly prepareCacheSize?: number | undefined readonly prepareCacheTTL?: Duration.Input | undefined readonly disableWAL?: boolean | undefined + /** + * How long SQLite waits when the database is busy. Defaults to 5 seconds. + * `Duration.infinity` is clamped to SQLite's maximum timeout. + * Waiting blocks the Node.js event loop because `node:sqlite` is synchronous. + */ + readonly busyTimeout?: Duration.Input | undefined readonly spanAttributes?: Record | undefined readonly transformResultNames?: ((str: string) => string) | undefined @@ -103,13 +112,12 @@ export interface SqliteClientConfig { } interface SqliteConnection extends Connection { - readonly export: Effect.Effect readonly backup: (destination: string) => Effect.Effect readonly loadExtension: (path: string) => Effect.Effect } /** - * Creates a scoped node SQLite client from the supplied configuration, using a single serialized connection with WAL enabled by default and exposing SQLite-specific `export`, `backup`, and `loadExtension` operations. + * Creates a scoped node SQLite client from the supplied configuration, using a single serialized connection with WAL and a 5-second busy timeout enabled by default. Explicit transactions on writable connections take the write lock for their duration, even when they only read; clients opened with `readonly: true` are unaffected. * * @category constructors * @since 4.0.0 @@ -127,13 +135,20 @@ export const make = ( const makeConnection = Effect.gen(function*() { const scope = yield* Effect.scope - const db = new Sqlite(options.filename, { - readonly: options.readonly ?? false + const db = new DatabaseSync(options.filename, { + readOnly: options.readonly ?? false, + allowExtension: true }) yield* Scope.addFinalizer(scope, Effect.sync(() => db.close())) + db.enableLoadExtension(false) + const busyTimeout = Math.min( + MAX_BUSY_TIMEOUT, + Math.max(0, Math.round(Duration.toMillis(options.busyTimeout ?? Duration.seconds(5)))) + ) + db.exec(`PRAGMA busy_timeout = ${busyTimeout}`) if (options.disableWAL !== true) { - db.pragma("journal_mode = WAL") + db.exec("PRAGMA journal_mode = WAL") } const prepareCache = yield* Cache.make({ @@ -147,23 +162,62 @@ export const make = ( }) const runStatement = ( - statement: Sqlite.Statement, + statement: StatementSync, params: ReadonlyArray, raw: boolean ) => Effect.withFiber, SqlError>((fiber) => { - if (Context.get(fiber.context, Client.SafeIntegers)) { - statement.safeIntegers(true) - } - try { - if (statement.reader) { - return Effect.succeed(statement.all(...params)) - } - const result = statement.run(...params) - return Effect.succeed(raw ? result as unknown as ReadonlyArray : []) - } catch (cause) { - return Effect.fail(new SqlError({ reason: classifyError(cause, "Failed to execute statement", "execute") })) - } + const useSafeIntegers = Context.get(fiber.context, Client.SafeIntegers) + return Effect.try({ + try: () => { + statement.setReadBigInts(useSafeIntegers) + if (statement.columns().length > 0) { + return statement.all(...(params as Array)) as ReadonlyArray + } + const result = statement.run(...(params as Array)) + return raw ? { changes: result.changes, lastInsertRowid: result.lastInsertRowid } as any : [] + }, + catch: (cause) => new SqlError({ reason: classifyError(cause, "Failed to execute statement", "execute") }) + }) + }) + + const runStatementValues = ( + statement: StatementSync, + params: ReadonlyArray + ) => + Effect.withFiber>, SqlError>((fiber) => { + const useSafeIntegers = Context.get(fiber.context, Client.SafeIntegers) + return Effect.try({ + try: () => { + statement.setReadBigInts(useSafeIntegers) + if (statement.columns().length > 0) { + return statement.all(...(params as Array)) as unknown as ReadonlyArray> + } + statement.run(...(params as Array)) + return [] + }, + catch: (cause) => new SqlError({ reason: classifyError(cause, "Failed to execute statement", "execute") }) + }) + }) + + const runStatementValuesUnprepared = ( + statement: StatementSync, + params: ReadonlyArray + ) => + Effect.withFiber>, SqlError>((fiber) => { + const useSafeIntegers = Context.get(fiber.context, Client.SafeIntegers) + return Effect.try({ + try: () => { + statement.setReadBigInts(useSafeIntegers) + statement.setReturnArrays(true) + if (statement.columns().length > 0) { + return statement.all(...(params as Array)) as unknown as ReadonlyArray> + } + statement.run(...(params as Array)) + return [] + }, + catch: (cause) => new SqlError({ reason: classifyError(cause, "Failed to execute statement", "execute") }) + }) }) const run = ( @@ -182,41 +236,17 @@ export const make = ( ) => Effect.acquireUseRelease( Cache.get(prepareCache, sql), - (statement) => - Effect.try({ - try: () => { - if (statement.reader) { - statement.raw(true) - return statement.all(...params) as ReadonlyArray< - ReadonlyArray - > - } - statement.run(...params) - return [] - }, - catch: (cause) => new SqlError({ reason: classifyError(cause, "Failed to execute statement", "execute") }) - }), - (statement) => Effect.sync(() => statement.reader && statement.raw(false)) + (statement) => { + statement.setReturnArrays(true) + return runStatementValues(statement, params) + }, + (statement) => Effect.sync(() => statement.setReturnArrays(false)) ) const runValuesUnprepared = ( sql: string, params: ReadonlyArray - ) => - Effect.try({ - try: () => { - const statement = db.prepare(sql) - if (statement.reader) { - statement.raw(true) - return statement.all(...params) as ReadonlyArray< - ReadonlyArray - > - } - statement.run(...params) - return [] - }, - catch: (cause) => new SqlError({ reason: classifyError(cause, "Failed to execute statement", "execute") }) - }) + ) => runStatementValuesUnprepared(db.prepare(sql), params) return identity({ execute(sql, params, transformRows) { @@ -240,22 +270,31 @@ export const make = ( executeStream(_sql, _params) { return Stream.die("executeStream not implemented") }, - export: Effect.try({ - try: () => db.serialize(), - catch: (cause) => new SqlError({ reason: classifyError(cause, "Failed to export database", "export") }) - }), backup(destination) { - return Effect.tryPromise({ - try: () => db.backup(destination), - catch: (cause) => new SqlError({ reason: classifyError(cause, "Failed to backup database", "backup") }) + return Effect.suspend(() => { + let totalPages = 0 + return Effect.tryPromise({ + try: () => + backupDatabase(db, destination, { + progress: (progress) => { + totalPages = progress.totalPages + } + }).then((pages): BackupMetadata => ({ totalPages: totalPages || pages, remainingPages: 0 })), + catch: (cause) => new SqlError({ reason: classifyError(cause, "Failed to backup database", "backup") }) + }) }) }, loadExtension(path) { - return Effect.try({ - try: () => db.loadExtension(path), - catch: (cause) => - new SqlError({ reason: classifyError(cause, "Failed to load extension", "loadExtension") }) - }) + return Effect.acquireUseRelease( + Effect.sync(() => db.enableLoadExtension(true)), + () => + Effect.try({ + try: () => db.loadExtension(path), + catch: (cause) => + new SqlError({ reason: classifyError(cause, "Failed to load extension", "loadExtension") }) + }), + () => Effect.sync(() => db.enableLoadExtension(false)) + ) } }) }) @@ -281,6 +320,7 @@ export const make = ( acquirer, compiler, transactionAcquirer, + beginTransaction: "BEGIN IMMEDIATE", spanAttributes: [ ...(options.spanAttributes ? Object.entries(options.spanAttributes) : []), [ATTR_DB_SYSTEM_NAME, "sqlite"] @@ -290,7 +330,6 @@ export const make = ( { [TypeId]: TypeId as TypeId, config: options, - export: Effect.flatMap(acquirer, (_) => _.export), backup: (destination: string) => Effect.flatMap(acquirer, (_) => _.backup(destination)), loadExtension: (path: string) => Effect.flatMap(acquirer, (_) => _.loadExtension(path)) } @@ -332,3 +371,19 @@ export const layer = ( Context.add(Client.SqlClient, client) )) ).pipe(Layer.provide(Reactivity.layer)) + +// internal + +const classifyError = (cause: unknown, message: string, operation: string) => + classifySqliteError(sqliteCauseWithErrno(cause), { message, operation }) + +const sqliteCauseWithErrno = (cause: unknown): unknown => { + if (typeof cause !== "object" || cause === null || !("errcode" in cause) || "errno" in cause) { + return cause + } + const errcode = (cause as { readonly errcode: unknown }).errcode + if (typeof errcode !== "number") { + return cause + } + return Object.assign(cause, { errno: errcode }) +} diff --git a/repos/effect/packages/sql/sqlite-node/src/SqliteMigrator.ts b/repos/effect/packages/sql/sqlite-node/src/SqliteMigrator.ts index 8ea3e7801d..ba8441a2af 100644 --- a/repos/effect/packages/sql/sqlite-node/src/SqliteMigrator.ts +++ b/repos/effect/packages/sql/sqlite-node/src/SqliteMigrator.ts @@ -22,7 +22,7 @@ export * from "effect/unstable/sql/Migrator" /** * Runs SQL migrations for a SQLite database using the shared `Migrator` implementation and the current `SqlClient`. * - * @category constructors + * @category running * @since 4.0.0 */ export const run: ( @@ -78,7 +78,7 @@ export const run: ( /** * Creates a layer that runs the configured SQLite migrations during layer construction and provides no services. * - * @category constructors + * @category layers * @since 4.0.0 */ export const layer = ( diff --git a/repos/effect/packages/sql/sqlite-node/test/Client.test.ts b/repos/effect/packages/sql/sqlite-node/test/Client.test.ts index 97c758c58f..3a20cec55e 100644 --- a/repos/effect/packages/sql/sqlite-node/test/Client.test.ts +++ b/repos/effect/packages/sql/sqlite-node/test/Client.test.ts @@ -1,7 +1,7 @@ import { NodeFileSystem } from "@effect/platform-node" import { SqliteClient } from "@effect/sql-sqlite-node" import { assert, describe, it } from "@effect/vitest" -import { Effect, FileSystem } from "effect" +import { Duration, Effect, FileSystem } from "effect" import { Reactivity } from "effect/unstable/reactivity" const makeClient = Effect.gen(function*() { @@ -12,6 +12,16 @@ const makeClient = Effect.gen(function*() { }) }).pipe(Effect.provide([NodeFileSystem.layer, Reactivity.layer])) +const makeClients = Effect.gen(function*() { + const fs = yield* FileSystem.FileSystem + const dir = yield* fs.makeTempDirectoryScoped() + const filename = dir + "/test.db" + return { + client: yield* SqliteClient.make({ filename }), + contender: yield* SqliteClient.make({ filename }) + } +}).pipe(Effect.provide([NodeFileSystem.layer, Reactivity.layer])) + describe("Client", () => { it.effect("should work", () => Effect.gen(function*() { @@ -74,4 +84,63 @@ describe("Client", () => { const rows = yield* sql`SELECT * FROM test` assert.deepStrictEqual(rows, []) })) + + it.effect("uses a 5 second busy timeout", () => + Effect.gen(function*() { + const sql = yield* makeClient + assert.deepStrictEqual(yield* sql`PRAGMA busy_timeout`, [{ timeout: 5000 }]) + + const custom = yield* SqliteClient.make({ filename: ":memory:", busyTimeout: "1 second" }).pipe( + Effect.provide(Reactivity.layer) + ) + assert.deepStrictEqual(yield* custom`PRAGMA busy_timeout`, [{ timeout: 1000 }]) + + const infinite = yield* SqliteClient.make({ filename: ":memory:", busyTimeout: Duration.infinity }).pipe( + Effect.provide(Reactivity.layer) + ) + assert.deepStrictEqual(yield* infinite`PRAGMA busy_timeout`, [{ timeout: 2_147_483_647 }]) + })) + + it.effect("starts transactions immediately", () => + Effect.gen(function*() { + const { client, contender } = yield* makeClients + yield* contender`PRAGMA busy_timeout = 1` + + yield* client.withTransaction( + Effect.gen(function*() { + const error = yield* Effect.flip(contender`BEGIN IMMEDIATE`) + assert.strictEqual(error._tag, "SqlError") + assert(error.reason.cause instanceof Error) + assert.match(error.reason.cause.message, /database is locked/i) + }) + ) + })) + + it.effect("supports transactions on readonly clients", () => + Effect.gen(function*() { + const fs = yield* FileSystem.FileSystem + const dir = yield* fs.makeTempDirectoryScoped() + const filename = dir + "/test.db" + + yield* Effect.scoped( + Effect.gen(function*() { + const sql = yield* SqliteClient.make({ filename }) + yield* sql`CREATE TABLE test (id INTEGER PRIMARY KEY)` + }) + ) + + const sql = yield* SqliteClient.make({ filename, readonly: true }) + assert.deepStrictEqual(yield* sql.withTransaction(sql`SELECT * FROM test`), []) + }).pipe(Effect.provide([NodeFileSystem.layer, Reactivity.layer]))) + + it.effect("supports backup and export", () => + Effect.gen(function*() { + const sql = yield* makeClient + yield* sql`CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT)` + yield* sql`INSERT INTO test (name) VALUES ('hello')` + + const metadata = yield* sql.backup(sql.config.filename + ".backup") + assert(metadata.totalPages > 0) + assert.strictEqual(metadata.remainingPages, 0) + })) }) diff --git a/repos/effect/packages/sql/sqlite-node/test/Persistence.test.ts b/repos/effect/packages/sql/sqlite-node/test/Persistence.test.ts index 78313b98a0..0acc117ace 100644 --- a/repos/effect/packages/sql/sqlite-node/test/Persistence.test.ts +++ b/repos/effect/packages/sql/sqlite-node/test/Persistence.test.ts @@ -1,11 +1,12 @@ import { NodeFileSystem } from "@effect/platform-node" import { SqliteClient } from "@effect/sql-sqlite-node" -import { expect, it } from "@effect/vitest" +import { assert, expect, it } from "@effect/vitest" import { Duration, Effect, FileSystem, Layer } from "effect" +import * as SqlCleanupTest from "effect-test/unstable/persistence/SqlCleanupTest" import { TestClock } from "effect/testing" import { Persistence } from "effect/unstable/persistence" import { Reactivity } from "effect/unstable/reactivity" -import type * as SqlClient from "effect/unstable/sql/SqlClient" +import * as SqlClient from "effect/unstable/sql/SqlClient" const ClientLayer = Effect.gen(function*() { const fs = yield* FileSystem.FileSystem @@ -53,6 +54,15 @@ const suite = (name: string, layer: Layer.Layer + Effect.gen(function*() { + const persistence = yield* Persistence.BackingPersistence + const store = yield* persistence.make("test_store_duplicate_keys") + yield* store.set("key", { value: 1 }, undefined) + + expect(yield* store.getMany(["key", "key"])).toEqual([{ value: 1 }, { value: 1 }]) + })) + it.effect("remove", () => Effect.gen(function*() { const persistence = yield* Persistence.BackingPersistence @@ -107,3 +117,51 @@ const suite = (name: string, layer: Layer.Layer { + it.effect("deletes expired entries in batches", () => + Effect.gen(function*() { + const sql = (yield* SqlClient.SqlClient).withoutTransforms() + const table = sql("effect_persistence") + const expiredCount = sql<{ readonly count: number }>` + SELECT COUNT(*) AS count FROM ${table} WHERE store_id = 'expired' + `.pipe(Effect.map((rows) => rows[0].count)) + yield* sql` + CREATE TABLE ${table} ( + store_id TEXT NOT NULL, + id TEXT NOT NULL, + value TEXT NOT NULL, + expires INTEGER, + PRIMARY KEY (store_id, id) + ) + ` + + const entries = Array.from({ length: SqlCleanupTest.expiredEntryCount }, (_, i) => ({ + store_id: "expired", + id: String(i), + value: "{}", + expires: SqlCleanupTest.expiredAtEpoch + })) + yield* sql`INSERT INTO ${table} ${sql.insert(entries)}`.unprepared + yield* sql` + INSERT INTO ${table} (store_id, id, value, expires) + VALUES ('live', 'live', '{}', NULL), ('live', 'future', '{}', ${SqlCleanupTest.futureExpiresAt}) + ` + + yield* Layer.build(Persistence.layerBackingSql).pipe(TestClock.withLive) + + const expired = yield* SqlCleanupTest.waitForCount(expiredCount, (count) => count === 0) + assert.strictEqual(expired, 0) + const live = yield* sql<{ readonly count: number }>` + SELECT COUNT(*) AS count FROM ${table} WHERE store_id = 'live' + ` + assert.strictEqual(live[0].count, 2) + + const indexes = yield* sql<{ readonly count: number }>` + SELECT COUNT(*) AS count FROM sqlite_master + WHERE type = 'index' + AND name = 'effect_persistence_expires_idx' + ` + assert.strictEqual(indexes[0].count, 1) + }), { timeout: SqlCleanupTest.testTimeout }) +}) diff --git a/repos/effect/packages/sql/sqlite-node/test/SqlEventJournal.test.ts b/repos/effect/packages/sql/sqlite-node/test/SqlEventJournal.test.ts index 5eeb0a294a..0b43277806 100644 --- a/repos/effect/packages/sql/sqlite-node/test/SqlEventJournal.test.ts +++ b/repos/effect/packages/sql/sqlite-node/test/SqlEventJournal.test.ts @@ -14,6 +14,19 @@ const makeJournal = Effect.gen(function*() { }).pipe(Effect.provide(Reactivity.layer)) describe("SqlEventJournal", () => { + it.effect("commits only after the write callback succeeds", () => + Effect.gen(function*() { + const sql = yield* SqliteClient.make({ filename: ":memory:" }) + const journal = yield* SqlEventJournal.make().pipe(Effect.provideService(SqlClient.SqlClient, sql)) + yield* Effect.exit(journal.write({ + event: "Repro", + primaryKey: "key", + payload: new Uint8Array([1]), + effect: () => Effect.fail("callback failed") + })) + assert.deepStrictEqual(yield* journal.entries, []) + }).pipe(Effect.provide(Reactivity.layer))) + it.effect("writes and reads entries", () => Effect.gen(function*() { const journal = yield* makeJournal diff --git a/repos/effect/packages/sql/sqlite-node/test/SqlEventLogServerEncrypted.test.ts b/repos/effect/packages/sql/sqlite-node/test/SqlEventLogServerEncrypted.test.ts index 57bc47d896..0db9b95c69 100644 --- a/repos/effect/packages/sql/sqlite-node/test/SqlEventLogServerEncrypted.test.ts +++ b/repos/effect/packages/sql/sqlite-node/test/SqlEventLogServerEncrypted.test.ts @@ -32,15 +32,32 @@ const persistEntries = ( ) => Effect.gen(function*() { const encrypted = yield* encryption.encrypt(identity, entries) - return encrypted.encryptedEntries.map((encryptedEntry, index) => + return encrypted.map(({ encryptedEntry, iv }, index) => new EventLogServer.PersistedEntry({ entryId: entries[index].id, - iv: encrypted.iv, + iv, encryptedEntry }) ) }) +const encodeWrite = Effect.fnUntraced(function*( + encryption: EventLogEncryption.EventLogEncryption["Service"], + identity: EventLog.Identity["Service"], + entry: EventJournal.Entry +) { + const encrypted = yield* encryption.encrypt(identity, [entry]) + return yield* new EventLogMessage.WriteEntries({ + publicKey: identity.publicKey, + storeId: storeIdA, + encryptedEntries: [{ + entryId: entry.id, + iv: encrypted[0].iv, + encryptedEntry: encrypted[0].encryptedEntry + }] + }).encoded +}) + const makePersistedEntry = (index: number, entryId = EventJournal.makeEntryIdUnsafe()) => new EventLogServer.PersistedEntry({ entryId, @@ -71,7 +88,201 @@ const makeAuthenticateRequest = Effect.fnUntraced(function*(options: { }) }) +const makeAuthenticatedRpcClient = Effect.fnUntraced(function*( + storage: EventLogServer.Storage["Service"], + identities: ReadonlyArray +) { + const rpcClient = yield* RpcTest.makeClient(EventLogMessage.EventLogRemoteRpcs).pipe( + Effect.provide( + EventLogServer.layerRpcHandlers.pipe( + Layer.provide(Layer.succeed(EventLogServer.Storage, storage)) + ) + ) + ) + for (const identity of identities) { + const hello = yield* rpcClient["EventLog.Hello"]() + yield* rpcClient["EventLog.Authenticate"]( + yield* makeAuthenticateRequest({ + identity, + challenge: hello.challenge, + remoteId: hello.remoteId + }) + ) + } + return rpcClient +}) + +const assertForbidden = Effect.fnUntraced(function*( + effect: Effect.Effect +) { + const error = yield* Effect.flip(effect) + assert.instanceOf(error, EventLogMessage.EventLogProtocolError) + assert.strictEqual(error.code, "Forbidden") +}) + describe("SqlEventLogServer", () => { + it.effect("forbids reading another identity's changes", () => + Effect.gen(function*() { + const sql = yield* SqliteClient.make({ filename: ":memory:" }) + const storage = yield* SqlEventLogServer.makeStorage().pipe( + Effect.provideService(SqlClient.SqlClient, sql) + ) + const encryption = yield* EventLogEncryption.EventLogEncryption + const identityA = yield* encryption.generateIdentity + const identityB = yield* encryption.generateIdentity + const rpcClient = yield* makeAuthenticatedRpcClient(storage, [identityA]) + + yield* storage.write(identityB.publicKey, storeIdA, [makePersistedEntry(1)]) + const error = yield* rpcClient["EventLog.Changes"]({ + publicKey: identityB.publicKey, + storeId: storeIdA, + startSequence: 0 + }).pipe( + Stream.take(1), + Stream.runCollect, + Effect.flip + ) + + assert.instanceOf(error, EventLogMessage.EventLogProtocolError) + assert.strictEqual(error.code, "Forbidden") + }).pipe(Effect.provide([Reactivity.layer, EventLogEncryption.layerSubtle]))) + + it.effect("forbids writing entries for another identity", () => + Effect.gen(function*() { + const sql = yield* SqliteClient.make({ filename: ":memory:" }) + const storage = yield* SqlEventLogServer.makeStorage().pipe( + Effect.provideService(SqlClient.SqlClient, sql) + ) + const encryption = yield* EventLogEncryption.EventLogEncryption + const identityA = yield* encryption.generateIdentity + const identityB = yield* encryption.generateIdentity + const rpcClient = yield* makeAuthenticatedRpcClient(storage, [identityA]) + const entry = makeEntry(1) + const data = yield* encodeWrite(encryption, identityB, entry) + + const error = yield* rpcClient["EventLog.WriteSingle"]({ data }).pipe(Effect.flip) + + assert.instanceOf(error, EventLogMessage.EventLogProtocolError) + assert.strictEqual(error.code, "Forbidden") + const written = yield* storage.write(identityB.publicKey, storeIdA, [makePersistedEntry(2)]) + assert.deepStrictEqual(written.map((entry) => entry.sequence), [1]) + }).pipe(Effect.provide([Reactivity.layer, EventLogEncryption.layerSubtle]))) + + it.effect("forbids writing chunked entries for another identity", () => + Effect.gen(function*() { + const sql = yield* SqliteClient.make({ filename: ":memory:" }) + const storage = yield* SqlEventLogServer.makeStorage().pipe( + Effect.provideService(SqlClient.SqlClient, sql) + ) + const encryption = yield* EventLogEncryption.EventLogEncryption + const identityA = yield* encryption.generateIdentity + const identityB = yield* encryption.generateIdentity + const rpcClient = yield* makeAuthenticatedRpcClient(storage, [identityA]) + const entry = makeEntry(1) + const data = yield* encodeWrite(encryption, identityB, entry) + const midpoint = Math.ceil(data.byteLength / 2) + const parts = [ + new EventLogMessage.ChunkedMessage({ id: 1, part: [0, 2], data: data.subarray(0, midpoint) }), + new EventLogMessage.ChunkedMessage({ id: 1, part: [1, 2], data: data.subarray(midpoint) }) + ] as const + + yield* rpcClient["EventLog.WriteChunked"](parts[0]) + const error = yield* rpcClient["EventLog.WriteChunked"](parts[1]).pipe(Effect.flip) + + assert.instanceOf(error, EventLogMessage.EventLogProtocolError) + assert.strictEqual(error.code, "Forbidden") + const written = yield* storage.write(identityB.publicKey, storeIdA, [makePersistedEntry(2)]) + assert.deepStrictEqual(written.map((entry) => entry.sequence), [1]) + }).pipe(Effect.provide([Reactivity.layer, EventLogEncryption.layerSubtle]))) + + it.effect("isolates authenticated identities between connections", () => + Effect.gen(function*() { + const sql = yield* SqliteClient.make({ filename: ":memory:" }) + const storage = yield* SqlEventLogServer.makeStorage().pipe( + Effect.provideService(SqlClient.SqlClient, sql) + ) + const encryption = yield* EventLogEncryption.EventLogEncryption + const identityA = yield* encryption.generateIdentity + const identityB = yield* encryption.generateIdentity + const handlers = yield* Layer.build( + EventLogServer.layerRpcHandlers.pipe( + Layer.provide(Layer.succeed(EventLogServer.Storage, storage)) + ) + ) + const rpcClientA = yield* RpcTest.makeClient(EventLogMessage.EventLogRemoteRpcs).pipe( + Effect.provide(handlers) + ) + const rpcClientB = yield* RpcTest.makeClient(EventLogMessage.EventLogRemoteRpcs).pipe( + Effect.provide(handlers) + ) + const helloA = yield* rpcClientA["EventLog.Hello"]() + yield* rpcClientA["EventLog.Authenticate"]( + yield* makeAuthenticateRequest({ + identity: identityA, + challenge: helloA.challenge, + remoteId: helloA.remoteId + }) + ) + const helloB = yield* rpcClientB["EventLog.Hello"]() + yield* rpcClientB["EventLog.Authenticate"]( + yield* makeAuthenticateRequest({ + identity: identityB, + challenge: helloB.challenge, + remoteId: helloB.remoteId + }) + ) + const data = yield* encodeWrite(encryption, identityB, makeEntry(1)) + const midpoint = Math.ceil(data.byteLength / 2) + const parts = [ + new EventLogMessage.ChunkedMessage({ id: 1, part: [0, 2], data: data.subarray(0, midpoint) }), + new EventLogMessage.ChunkedMessage({ id: 1, part: [1, 2], data: data.subarray(midpoint) }) + ] as const + + yield* storage.write(identityB.publicKey, storeIdA, [makePersistedEntry(1)]) + yield* assertForbidden( + rpcClientA["EventLog.Changes"]({ + publicKey: identityB.publicKey, + storeId: storeIdA, + startSequence: 0 + }).pipe(Stream.take(1), Stream.runCollect) + ) + yield* assertForbidden(rpcClientA["EventLog.WriteSingle"]({ data })) + yield* rpcClientA["EventLog.WriteChunked"](parts[0]) + yield* assertForbidden(rpcClientA["EventLog.WriteChunked"](parts[1])) + }).pipe(Effect.provide([Reactivity.layer, EventLogEncryption.layerSubtle]))) + + it.effect("supports multiple authenticated identities on one connection", () => + Effect.gen(function*() { + const sql = yield* SqliteClient.make({ filename: ":memory:" }) + const storage = yield* SqlEventLogServer.makeStorage().pipe( + Effect.provideService(SqlClient.SqlClient, sql) + ) + const encryption = yield* EventLogEncryption.EventLogEncryption + const identityA = yield* encryption.generateIdentity + const identityB = yield* encryption.generateIdentity + const rpcClient = yield* makeAuthenticatedRpcClient(storage, [identityA, identityB]) + const entryA = makeEntry(1) + const entryB = makeEntry(2) + const dataA = yield* encodeWrite(encryption, identityA, entryA) + const dataB = yield* encodeWrite(encryption, identityB, entryB) + + yield* rpcClient["EventLog.WriteSingle"]({ data: dataA }) + yield* rpcClient["EventLog.WriteSingle"]({ data: dataB }) + const changesA = yield* rpcClient["EventLog.Changes"]({ + publicKey: identityA.publicKey, + storeId: storeIdA, + startSequence: 0 + }).pipe(Stream.take(1), Stream.runCollect) + const changesB = yield* rpcClient["EventLog.Changes"]({ + publicKey: identityB.publicKey, + storeId: storeIdA, + startSequence: 0 + }).pipe(Stream.take(1), Stream.runCollect) + + assert.strictEqual(changesA.length, 1) + assert.strictEqual(changesB.length, 1) + }).pipe(Effect.provide([Reactivity.layer, EventLogEncryption.layerSubtle]))) + it.effect("persists remote id across storage instances", () => Effect.gen(function*() { const sql = yield* SqliteClient.make({ filename: ":memory:" }) diff --git a/repos/effect/packages/sql/sqlite-node/test/SqlEventLogServerUnencrypted.test.ts b/repos/effect/packages/sql/sqlite-node/test/SqlEventLogServerUnencrypted.test.ts index 9b9a69ddc7..68b36de34a 100644 --- a/repos/effect/packages/sql/sqlite-node/test/SqlEventLogServerUnencrypted.test.ts +++ b/repos/effect/packages/sql/sqlite-node/test/SqlEventLogServerUnencrypted.test.ts @@ -2,7 +2,7 @@ import { SqliteClient } from "@effect/sql-sqlite-node" import { assert, describe, it } from "@effect/vitest" import { Effect, Layer, Redacted } from "effect" import * as SqlEventLogServerUnencryptedStorageTest from "effect-test/unstable/eventlog/SqlEventLogServerUnencryptedStorageTest" -import type * as EventJournal from "effect/unstable/eventlog/EventJournal" +import * as EventJournal from "effect/unstable/eventlog/EventJournal" import * as EventLog from "effect/unstable/eventlog/EventLog" import * as EventLogEncryption from "effect/unstable/eventlog/EventLogEncryption" import * as EventLogMessage from "effect/unstable/eventlog/EventLogMessage" @@ -22,6 +22,15 @@ SqlEventLogServerUnencryptedStorageTest.suite( ) const getIdentityRootSecretMaterial = makeGetIdentityRootSecretMaterial(globalThis.crypto) +const storeId = EventLogMessage.StoreId.make("store-a") + +const makeEntry = (value: number) => + new EventJournal.Entry({ + id: EventJournal.makeEntryIdUnsafe(), + event: "UserCreated", + primaryKey: `user-${value}`, + payload: new Uint8Array([value]) + }, { disableChecks: true }) const makeAuthenticateRequest = Effect.fnUntraced(function*(options: { readonly identity: EventLog.Identity["Service"] @@ -45,6 +54,54 @@ const makeAuthenticateRequest = Effect.fnUntraced(function*(options: { }) describe("SqlEventLogServerUnencrypted (sql-sqlite-node)", () => { + it.effect("forbids writing entries for another identity", () => + Effect.gen(function*() { + const sql = yield* SqliteClient.make({ filename: ":memory:" }) + const storage = yield* SqlEventLogServerUnencrypted.makeStorage().pipe( + Effect.provideService(SqlClient.SqlClient, sql), + Effect.orDie + ) + const rpcClient = yield* RpcTest.makeClient(EventLogMessage.EventLogRemoteRpcs).pipe( + Effect.provide( + EventLogServerUnencrypted.layerRpcHandlers.pipe( + Layer.provideMerge(EventLog.layerRegistry), + Layer.provide(Layer.succeed(EventLogServerUnencrypted.Storage, storage)), + Layer.provide(Layer.succeed(EventLogServerUnencrypted.StoreMapping, { + resolve: ({ storeId }) => Effect.succeed(storeId), + hasStore: () => Effect.succeed(true) + })), + Layer.provide(Layer.succeed(EventLogServerUnencrypted.EventLogServerAuthorization, { + authorizeWrite: () => Effect.void, + authorizeRead: () => Effect.void, + authorizeIdentity: () => Effect.void + })) + ) + ) + ) + const identityA = yield* EventLog.makeIdentity + const identityB = yield* EventLog.makeIdentity + const hello = yield* rpcClient["EventLog.Hello"]() + yield* rpcClient["EventLog.Authenticate"]( + yield* makeAuthenticateRequest({ + identity: identityA, + challenge: hello.challenge, + remoteId: hello.remoteId + }) + ) + const data = yield* new EventLogMessage.WriteEntriesUnencrypted({ + publicKey: identityB.publicKey, + storeId, + entries: [makeEntry(1)] + }).encoded + + const error = yield* rpcClient["EventLog.WriteSingle"]({ data }).pipe(Effect.flip) + + assert.instanceOf(error, EventLogMessage.EventLogProtocolError) + assert.strictEqual(error.code, "Forbidden") + const written = yield* storage.write(storeId, [makeEntry(2)]) + assert.deepStrictEqual(written.map((entry) => entry.remoteSequence), [1]) + }).pipe(Effect.provide([Reactivity.layer, EventLogEncryption.layerSubtle]))) + it.effect("rejects session-auth rebinding for an existing publicKey", () => Effect.gen(function*() { const sql = yield* SqliteClient.make({ filename: ":memory:" }) diff --git a/repos/effect/packages/sql/sqlite-node/tsconfig.json b/repos/effect/packages/sql/sqlite-node/tsconfig.json index 19a2f5dbca..0ec4ad2bc0 100644 --- a/repos/effect/packages/sql/sqlite-node/tsconfig.json +++ b/repos/effect/packages/sql/sqlite-node/tsconfig.json @@ -1,8 +1,11 @@ { - "$schema": "http://json.schemastore.org/tsconfig", + "$schema": "https://json.schemastore.org/tsconfig", "extends": "../../../tsconfig.base.json", "include": ["src"], "references": [ { "path": "../../effect" } - ] + ], + "compilerOptions": { + "types": ["node"] + } } diff --git a/repos/effect/packages/sql/sqlite-node/vitest.config.ts b/repos/effect/packages/sql/sqlite-node/vitest.config.ts deleted file mode 100644 index c8a52c1826..0000000000 --- a/repos/effect/packages/sql/sqlite-node/vitest.config.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { mergeConfig, type ViteUserConfig } from "vitest/config" -import shared from "../../../vitest.shared.ts" - -const config: ViteUserConfig = {} - -export default mergeConfig(shared, config) diff --git a/repos/effect/packages/sql/sqlite-react-native/CHANGELOG.md b/repos/effect/packages/sql/sqlite-react-native/CHANGELOG.md index 1ab142f5a0..f1ee07d911 100644 --- a/repos/effect/packages/sql/sqlite-react-native/CHANGELOG.md +++ b/repos/effect/packages/sql/sqlite-react-native/CHANGELOG.md @@ -1,5 +1,128 @@ # @effect/sql-sqlite-react-native +## 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 + +- [#6992](https://github.com/Effect-TS/effect/pull/6992) [`fd652ce`](https://github.com/Effect-TS/effect/commit/fd652ce2bd9a6ce89dc753061fe4b18ce29d2abf) Thanks @fubhy! - Return selected rows from synchronous and asynchronous value queries. +- Updated dependencies [[`1001bcc`](https://github.com/Effect-TS/effect/commit/1001bccb9e874918d59dbb36860f1c5d4499ac20), [`993ba60`](https://github.com/Effect-TS/effect/commit/993ba60ee6c7ca6eb84522040f8b0d268b6ba7d4), [`67faacd`](https://github.com/Effect-TS/effect/commit/67faacd4679242559bee31717c05a5b10b990322), [`b78acdf`](https://github.com/Effect-TS/effect/commit/b78acdf422568f10ae8684fd3f10d52b065f0b56), [`fbb9ce5`](https://github.com/Effect-TS/effect/commit/fbb9ce5e625d1a7d6b7005bda42cbb6cd31476c5), [`722ea48`](https://github.com/Effect-TS/effect/commit/722ea484c9d01364c9242d929c0a564f7831a57c), [`3058fd5`](https://github.com/Effect-TS/effect/commit/3058fd594f5a683034212d71d957017fcc084006), [`62d0575`](https://github.com/Effect-TS/effect/commit/62d057566c241405c23ecf0bf4156186bd2be924), [`99dd6b5`](https://github.com/Effect-TS/effect/commit/99dd6b580434f97c5b40adc919f429e4abc3dfe7), [`7963ce1`](https://github.com/Effect-TS/effect/commit/7963ce1cd95f037fbefea67a29ead49cce4d16cb), [`af14e75`](https://github.com/Effect-TS/effect/commit/af14e752edd65e2b652e960411afafc88975a8d8), [`24e22d2`](https://github.com/Effect-TS/effect/commit/24e22d23a73a2e93ebf6d8edd2246a4a406942c8), [`647d14e`](https://github.com/Effect-TS/effect/commit/647d14e572c8004fa92fba256e00552b42bf34b7), [`1434eec`](https://github.com/Effect-TS/effect/commit/1434eecbd368e00839c24b3950f0b7a69218669a), [`a5278b1`](https://github.com/Effect-TS/effect/commit/a5278b18242011d1b2b08304c7c128151f9a4370), [`6af04a5`](https://github.com/Effect-TS/effect/commit/6af04a50bd019238f6acdb9cbda40439a3c09210), [`cb6c837`](https://github.com/Effect-TS/effect/commit/cb6c8376b2f322d4e7cbfc0973fc3b4f2951ee6e), [`d44cead`](https://github.com/Effect-TS/effect/commit/d44cead7e0e0ce61f0d980906e494f49a07e7899), [`88c7632`](https://github.com/Effect-TS/effect/commit/88c7632c2b59a49fcc40d250865bd8d0dccf31b0), [`abcbb2a`](https://github.com/Effect-TS/effect/commit/abcbb2abe16f1b6c587c15007df14371e1e70e93), [`8f63cce`](https://github.com/Effect-TS/effect/commit/8f63cce636700fde26b140b82e350ef916989d86), [`d56dfcf`](https://github.com/Effect-TS/effect/commit/d56dfcf54c2b9c53c3d098ce4b0ffcc84496c5f7), [`a98cda9`](https://github.com/Effect-TS/effect/commit/a98cda9422e1352f22e81696f759f326ffcfb667), [`6704bb8`](https://github.com/Effect-TS/effect/commit/6704bb84c320547f83cf50e8586ffc4c5e4c3cc5), [`6143de2`](https://github.com/Effect-TS/effect/commit/6143de21ee22038b45a8d4eba86f5aade6238eba), [`936b135`](https://github.com/Effect-TS/effect/commit/936b1358396eb0a1a7c8e0878ba63297e2106812), [`1bbae84`](https://github.com/Effect-TS/effect/commit/1bbae84f88b577a26d04ceb2e76d3143d09c4a20), [`d795ee7`](https://github.com/Effect-TS/effect/commit/d795ee771701ea62bd187ef7c0307d9737f68c1a), [`0a82d88`](https://github.com/Effect-TS/effect/commit/0a82d88b7da73278b6f270118e396d5ed4a64747), [`9215bc5`](https://github.com/Effect-TS/effect/commit/9215bc5da7dd10aa45f07fe44b98f06b6e433d62), [`a1b5df2`](https://github.com/Effect-TS/effect/commit/a1b5df2064d92431cfc6e638af613cc3114313d7), [`92a9ac5`](https://github.com/Effect-TS/effect/commit/92a9ac5ac0aa63d8975b9ba7a094d6a8f59a98f2), [`6bde7f2`](https://github.com/Effect-TS/effect/commit/6bde7f27f3243427203e53fe74472990e5c2a349), [`a712131`](https://github.com/Effect-TS/effect/commit/a7121310dbb60cbd819bbd702f97663098ec7bb8), [`2e6f760`](https://github.com/Effect-TS/effect/commit/2e6f760dcb44e2b984f3311a8af03a1d68a2ec7e), [`aa05804`](https://github.com/Effect-TS/effect/commit/aa0580497e027ed30b756058db0067c3fe07664f), [`badd3bf`](https://github.com/Effect-TS/effect/commit/badd3bf65fac4dd1e66e1f602db43659722dfced), [`02b0265`](https://github.com/Effect-TS/effect/commit/02b02651ede46a5a2dd3ef8081d0ad89648d0cbf), [`3437e21`](https://github.com/Effect-TS/effect/commit/3437e21a56d805781c5e5946a6189795a1dfd411), [`41a550d`](https://github.com/Effect-TS/effect/commit/41a550d1fed31e829929a8f5362b5340303164ac), [`17b5d50`](https://github.com/Effect-TS/effect/commit/17b5d50219ad49533cf9e33d01924a3e16af5eb3), [`96e5e95`](https://github.com/Effect-TS/effect/commit/96e5e9576b0315c747462761a61940ff9fe32dd1), [`e4d589e`](https://github.com/Effect-TS/effect/commit/e4d589e0ea08dc57c4793053b395dc0fcc499f34), [`ae4cf7b`](https://github.com/Effect-TS/effect/commit/ae4cf7b5e2cb5f8c55657e31a61789ad21c38c18), [`6ef5f1a`](https://github.com/Effect-TS/effect/commit/6ef5f1a041f3a40bf03fadd0b1feb275c277c635), [`2235a29`](https://github.com/Effect-TS/effect/commit/2235a29502c3f33cf6468511ad931089013a7916), [`b32f4cb`](https://github.com/Effect-TS/effect/commit/b32f4cb7b2d8ebe817075322622498e3beb05336), [`7f4c095`](https://github.com/Effect-TS/effect/commit/7f4c095b62da43780dd7fc2a5d1785ddfce60edf), [`5f3fb81`](https://github.com/Effect-TS/effect/commit/5f3fb814d18d8a54946c1c1cd0b41459cdb24006), [`17f0b91`](https://github.com/Effect-TS/effect/commit/17f0b91a243ccfe4a38d27debdc983adf434e738), [`0cdadd7`](https://github.com/Effect-TS/effect/commit/0cdadd75bc8abbbcad7956a4bc71f4e7a9b13250), [`39b57d7`](https://github.com/Effect-TS/effect/commit/39b57d7857358040558b67dd33eafc7bb5457830), [`5a6a573`](https://github.com/Effect-TS/effect/commit/5a6a5738e5bfc39e3a37ae7ba99081601fa19ac3), [`59f5e99`](https://github.com/Effect-TS/effect/commit/59f5e9981913b92d7a9beb2214a21d658b999d3a), [`45379d6`](https://github.com/Effect-TS/effect/commit/45379d6179ee4df2cbd3f848bd39ff7149c24a38), [`1949439`](https://github.com/Effect-TS/effect/commit/1949439175809ef81ab9c6411ed5559109edb4c9), [`e443403`](https://github.com/Effect-TS/effect/commit/e443403cf0e4effea14bb6cd950c5ac1c86cc748), [`03af7e8`](https://github.com/Effect-TS/effect/commit/03af7e85551204c605ea2fa2c43c10a4538ac8fb), [`0f721d4`](https://github.com/Effect-TS/effect/commit/0f721d406df8703ea92ca28777b3f09599e2056d), [`130b28d`](https://github.com/Effect-TS/effect/commit/130b28df552d7053407b041a96ff09dae82575e5), [`c987a12`](https://github.com/Effect-TS/effect/commit/c987a12a01b6a52ad53d29edf02613b03574dbcc), [`4158562`](https://github.com/Effect-TS/effect/commit/41585620977de9b84171f76619b72e29cc2284e5), [`306014a`](https://github.com/Effect-TS/effect/commit/306014a1ce4d5cb956c76bdc20e4e28ab3e61a6a), [`729a663`](https://github.com/Effect-TS/effect/commit/729a663275dd31f2357c446fe69664429220a83d), [`caf84b6`](https://github.com/Effect-TS/effect/commit/caf84b660044089e8d7f4067b279b27b8b50e8fd), [`ce067f7`](https://github.com/Effect-TS/effect/commit/ce067f799ea27735d4194345298a216aaf429f01), [`7a41f5a`](https://github.com/Effect-TS/effect/commit/7a41f5aa72d540ecf2746992ecc3fa3e6b40d31f), [`781022a`](https://github.com/Effect-TS/effect/commit/781022acdd3537ca18c88e2fa3681bafa6ef1b21), [`39f1297`](https://github.com/Effect-TS/effect/commit/39f1297acc08864feb12de6b8cf2bf73434f6cf5), [`2db266b`](https://github.com/Effect-TS/effect/commit/2db266b1bfbc81868bc1778c37c76032a267c79f), [`2141e28`](https://github.com/Effect-TS/effect/commit/2141e28903754d72604acf81673ceb2c62a56646), [`3c5e429`](https://github.com/Effect-TS/effect/commit/3c5e429878669ffcf5e0da4ddfbf50bde5bbcaad), [`20ddc63`](https://github.com/Effect-TS/effect/commit/20ddc630584f8fe488162ba384adcae53fc6810a), [`841b3ea`](https://github.com/Effect-TS/effect/commit/841b3ea6ae19a784bc1c20497b02f632af0c91e9), [`82a3fbf`](https://github.com/Effect-TS/effect/commit/82a3fbfce8b9df33e587076b7d7168ecd6799e17), [`eb9ee83`](https://github.com/Effect-TS/effect/commit/eb9ee83b38844a71d1cd5653a229309cfcb04a36), [`64dc7c7`](https://github.com/Effect-TS/effect/commit/64dc7c76dc5c89887b9e7c181d1873dcbb7820d1), [`84dc8ab`](https://github.com/Effect-TS/effect/commit/84dc8ab7accc682bc668c78a97e4a1776b633be8), [`b4463f4`](https://github.com/Effect-TS/effect/commit/b4463f46fc33d3b01ea5eadd7d012a5abda347a3), [`592dd36`](https://github.com/Effect-TS/effect/commit/592dd361645739ac0cd8e6babb084cd27403c172), [`85d2b44`](https://github.com/Effect-TS/effect/commit/85d2b446e3059de4919be730105868f79728308d), [`32e4a69`](https://github.com/Effect-TS/effect/commit/32e4a69b3151b7ec4058af2213b96a41d11e9e06), [`13c5872`](https://github.com/Effect-TS/effect/commit/13c5872ed30830360367ad89af2dab68a003c351), [`3454cdb`](https://github.com/Effect-TS/effect/commit/3454cdb528fdb5d3ed0c5c5c8169bc47de41fbd8), [`e930804`](https://github.com/Effect-TS/effect/commit/e9308045be1d8a00c0b4046f1e8ff22cf68c93da), [`7f12d4b`](https://github.com/Effect-TS/effect/commit/7f12d4b4e731dc3a213ae5c3f60db9edc50292d2), [`181c9ef`](https://github.com/Effect-TS/effect/commit/181c9ef5e5d4ab247bf4aec06424f15b0a1e802e), [`dd9f891`](https://github.com/Effect-TS/effect/commit/dd9f891e23f316abb6192893008f0e33ece9d97d), [`433fb81`](https://github.com/Effect-TS/effect/commit/433fb81ca4c15c681a8ae097ce3ff9bd3a9c9aa5), [`8459cdb`](https://github.com/Effect-TS/effect/commit/8459cdbae8a476dc04b6247fffe6a1668dcb1217), [`6124ab3`](https://github.com/Effect-TS/effect/commit/6124ab39eb64688fbd5d688d24766542f9cb5a2c), [`01bd954`](https://github.com/Effect-TS/effect/commit/01bd9546f142706fca1628f7261e6d1cb9638948), [`ba2c3aa`](https://github.com/Effect-TS/effect/commit/ba2c3aa05eb87ec05d263b960017ecf29746f66a), [`0a45ef3`](https://github.com/Effect-TS/effect/commit/0a45ef3bb4a1ae9b345c43c548db4336a31b3191), [`8459cdb`](https://github.com/Effect-TS/effect/commit/8459cdbae8a476dc04b6247fffe6a1668dcb1217), [`eaa7e71`](https://github.com/Effect-TS/effect/commit/eaa7e71b88bf59b24610128c6115a2a126432731), [`db4c2cc`](https://github.com/Effect-TS/effect/commit/db4c2ccdec77d813b6f4cc72a302ee7c4fe6e39d), [`22f150a`](https://github.com/Effect-TS/effect/commit/22f150a0936cef30517e87eaca73bff1c5e4873a), [`90ffb08`](https://github.com/Effect-TS/effect/commit/90ffb083b3091c211300f50a42ba7bf56536c0ee), [`d517692`](https://github.com/Effect-TS/effect/commit/d517692ef75f45d5f6d9d68b32d41fa0ccc56c99), [`01af079`](https://github.com/Effect-TS/effect/commit/01af079c189d1fc5067d3b1933b2870c4baf2693), [`32a59e8`](https://github.com/Effect-TS/effect/commit/32a59e8058b1ec9738cb083cf1cb116b393ca114)]: + - effect@4.0.0-beta.104 + +## 4.0.0-beta.103 + +### Patch Changes + +- [#6701](https://github.com/Effect-TS/effect/pull/6701) [`9867b9f`](https://github.com/Effect-TS/effect/commit/9867b9fc69f9cc6c443594fc7eccc7be0c674d9c) Thanks @fubhy! - Removed explicit ./index entrypoints + +- Updated dependencies [[`e56cd8f`](https://github.com/Effect-TS/effect/commit/e56cd8f90c3559baccf8fcf2852ea911235d5944), [`f77c120`](https://github.com/Effect-TS/effect/commit/f77c120d8e04779ddeb8bce8e9cde932f268e4b6), [`b2f95a9`](https://github.com/Effect-TS/effect/commit/b2f95a9c2f2581deb89dc3bae9e89cf819e82923), [`04fd44a`](https://github.com/Effect-TS/effect/commit/04fd44a42abfa8dc2642300dcf49ee48c8ef4539), [`b74333d`](https://github.com/Effect-TS/effect/commit/b74333d83e15b9d042e4698ad23040de60454afe), [`1c40b28`](https://github.com/Effect-TS/effect/commit/1c40b2809503d6aa1358777196fc66317906e657), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`b3901d2`](https://github.com/Effect-TS/effect/commit/b3901d29c543fd5bd05ceec669a17896c8e19006), [`4a0984a`](https://github.com/Effect-TS/effect/commit/4a0984af62738fedf4bd3e87adb4d4d641ce9147), [`fffd88b`](https://github.com/Effect-TS/effect/commit/fffd88b3135abdf928ca7c4b0e00e610985091c7), [`f3f6c1e`](https://github.com/Effect-TS/effect/commit/f3f6c1e02cb543423fcffef5dc2db03fac503588), [`ef07642`](https://github.com/Effect-TS/effect/commit/ef07642dfe671d5258b65d1c1480c4d05c495f15), [`f1bc827`](https://github.com/Effect-TS/effect/commit/f1bc8274a608813d7b09d28dcca04adbf62f8c92), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`081f4d8`](https://github.com/Effect-TS/effect/commit/081f4d8cd06a2ac222d2810b46e61efcee26939e), [`5287b24`](https://github.com/Effect-TS/effect/commit/5287b24f5f8fa094ba20e117bfb1a80fba6d2cf5), [`13d31cf`](https://github.com/Effect-TS/effect/commit/13d31cfc2dde46210e94391b5b6767ae9aeaf2c9), [`acee269`](https://github.com/Effect-TS/effect/commit/acee26944bc89ee554d7b9fadab7443f9edc28a9), [`31170c1`](https://github.com/Effect-TS/effect/commit/31170c19b236c37abb5476c821bc6f5bfa2735ab), [`205ebc7`](https://github.com/Effect-TS/effect/commit/205ebc776062012581e98fced7ced19adfc44ee7), [`ed0ebf8`](https://github.com/Effect-TS/effect/commit/ed0ebf8e5c864d46fed1f232e99c0e680f10a58f), [`a3fd084`](https://github.com/Effect-TS/effect/commit/a3fd08482157bd78b089f77c7b173d54ef68b5cd), [`ee29ddf`](https://github.com/Effect-TS/effect/commit/ee29ddf862c3723ad466abc93ab6f6fe723b2319), [`6086309`](https://github.com/Effect-TS/effect/commit/60863090af8e5af0bfa1435f08dc5390f9993e30), [`4a57af2`](https://github.com/Effect-TS/effect/commit/4a57af24011db1d66e947289d2f7ffc2074696d2), [`660875b`](https://github.com/Effect-TS/effect/commit/660875b4325e6eebb3f04513998301cd2a0847ec), [`8e7c706`](https://github.com/Effect-TS/effect/commit/8e7c706b0aca855489b53d987404566d3e9cb5e7), [`5f63adb`](https://github.com/Effect-TS/effect/commit/5f63adbe75fc9d50d23706a52b3e483ad2a1a01c), [`053bc42`](https://github.com/Effect-TS/effect/commit/053bc42e2a964755611a216e78ed214322efee37), [`c0a1534`](https://github.com/Effect-TS/effect/commit/c0a153494484ecf9f0d0f20895a7a648b4be363b), [`f1e3a37`](https://github.com/Effect-TS/effect/commit/f1e3a378c144f974a6122b299f421b75595af20f), [`cedb01a`](https://github.com/Effect-TS/effect/commit/cedb01a025492a1faf9e59eb23eb96bc3b5e2fff), [`1747440`](https://github.com/Effect-TS/effect/commit/1747440de9a51a56ed3660da748cc01b256adce7), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`b4f1ee2`](https://github.com/Effect-TS/effect/commit/b4f1ee238d96aa78c5f040158cb78671d75b381e), [`a4757f1`](https://github.com/Effect-TS/effect/commit/a4757f1c47067d8d016a6c4a2c541bb8ae520f9b), [`cd122b9`](https://github.com/Effect-TS/effect/commit/cd122b90300d995a237993a2edb7a049785ab6a4), [`5de588b`](https://github.com/Effect-TS/effect/commit/5de588b2472fb0f4eb919766eb8472583a044772), [`3895b9c`](https://github.com/Effect-TS/effect/commit/3895b9cf179262cd277a9c6daafe9050dcf8265e), [`89ce5f3`](https://github.com/Effect-TS/effect/commit/89ce5f3e16e23a193daa475dc72ea8133ae1dacd), [`985de09`](https://github.com/Effect-TS/effect/commit/985de097d75906db2aed784841f81e23cc978b43), [`9800e3a`](https://github.com/Effect-TS/effect/commit/9800e3acc8f36530f671bc8b91558cb112f449a7), [`4dc35f6`](https://github.com/Effect-TS/effect/commit/4dc35f64641746366f867ea3dbfedb9cd4685ada), [`e8eb62b`](https://github.com/Effect-TS/effect/commit/e8eb62b3d0ef27e9761cdc2eb93bdec52d6ee204), [`ecd9993`](https://github.com/Effect-TS/effect/commit/ecd99936112cb69efdb02de3a2fd57f47baefdf3), [`5ab9c08`](https://github.com/Effect-TS/effect/commit/5ab9c08463ce049c45f3502676954a7b72c6b024), [`f5cf965`](https://github.com/Effect-TS/effect/commit/f5cf96548afd51f4b3cf1aea11b04d7f8549ce90), [`a94cbed`](https://github.com/Effect-TS/effect/commit/a94cbed84e9e49bea4bff925599c0f19c4e3deab), [`9160ad7`](https://github.com/Effect-TS/effect/commit/9160ad7d146d4376dd12f7510c025e5b2f638a70), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`52494be`](https://github.com/Effect-TS/effect/commit/52494be9e8eb3bb542d06a3dfefc6bca4e168984), [`5441c8e`](https://github.com/Effect-TS/effect/commit/5441c8e656a6418c0d27feb2df67565a3e1155f4), [`c9b56ab`](https://github.com/Effect-TS/effect/commit/c9b56ab507f224426ee8388dc450da447ec4715f), [`8ef7257`](https://github.com/Effect-TS/effect/commit/8ef72577d1f43212cab87951d659e54e3c8d7d91), [`1519406`](https://github.com/Effect-TS/effect/commit/1519406fed6e8b017ae178dc20bcaa2cf318b570), [`9716990`](https://github.com/Effect-TS/effect/commit/97169902eec3c99baa7f0b2c7b45a0a5eae75819), [`733f75b`](https://github.com/Effect-TS/effect/commit/733f75b7125e3016a975fdd251c0179ae5393786), [`48155c8`](https://github.com/Effect-TS/effect/commit/48155c8ccfc12dcca8a00fa358d50b20c30874e4), [`951d06b`](https://github.com/Effect-TS/effect/commit/951d06b83d459d3e8fa9024e727a5db1662d3322), [`d767b65`](https://github.com/Effect-TS/effect/commit/d767b65a7687e38be23f0b0ee3d52ab5f2360cbe), [`5d52d9d`](https://github.com/Effect-TS/effect/commit/5d52d9d148aaa7f736ed8c310fc8bfa9dc81badf), [`f4151e1`](https://github.com/Effect-TS/effect/commit/f4151e1937c26de14f1d64566f8126173f1b5014), [`e02fbb6`](https://github.com/Effect-TS/effect/commit/e02fbb66f5a0f13dba6c33ef63528a37a17a0676), [`724ce09`](https://github.com/Effect-TS/effect/commit/724ce09650a458d4565e5c7331ea92ca04f08e68), [`dbe91f6`](https://github.com/Effect-TS/effect/commit/dbe91f6961ef9f7e8da910ee5758d9c0d385fca8), [`4c008d2`](https://github.com/Effect-TS/effect/commit/4c008d28b370d817f7ae4579db09836fe084c8d2), [`b650832`](https://github.com/Effect-TS/effect/commit/b6508328708a842f3163467b72486bd228f1a289), [`b46c92f`](https://github.com/Effect-TS/effect/commit/b46c92f3b314f4ffd612b831efa55dd856c587a3), [`5335797`](https://github.com/Effect-TS/effect/commit/5335797003076d9c6fd170da98d779696d555596), [`4b3460d`](https://github.com/Effect-TS/effect/commit/4b3460daa434ec465a95a50704fe1103a9275999), [`6301fd7`](https://github.com/Effect-TS/effect/commit/6301fd710b4325718de2c42997dac28a9e9aa250), [`aebc5c6`](https://github.com/Effect-TS/effect/commit/aebc5c61664b89a840465ec65b79ce635a5ceee8), [`52b2d7b`](https://github.com/Effect-TS/effect/commit/52b2d7b5bd3c7cce3bd5b69c6ab3941004da70f3), [`eec5744`](https://github.com/Effect-TS/effect/commit/eec57445dfa0ef3c5977195ad69415b7e7d42bb6), [`24e0e93`](https://github.com/Effect-TS/effect/commit/24e0e93dc307dc2c2ae86caacb7289e1dab3c103), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`1a7ce81`](https://github.com/Effect-TS/effect/commit/1a7ce8150e3977586c44d8ccb9a8384389bb4d49), [`48f22a7`](https://github.com/Effect-TS/effect/commit/48f22a7d16ae57ee2175d450dafbdeb69e187d2a), [`c96b7f6`](https://github.com/Effect-TS/effect/commit/c96b7f6359662053c3e09344f61dddc7a6caf4ac), [`6d2a942`](https://github.com/Effect-TS/effect/commit/6d2a942ed7cd33b8fd79d549edba33bc9e2a7e3e), [`cc27b19`](https://github.com/Effect-TS/effect/commit/cc27b194b9d13fa3a66ab037e853fca9d41700ff), [`8f9499f`](https://github.com/Effect-TS/effect/commit/8f9499f562729f5f7b08d8bcc4db86b4aeff8a21), [`3eeea73`](https://github.com/Effect-TS/effect/commit/3eeea73cfc3e9b126975c2ddbdb7f7c8c92026e2), [`0a532e5`](https://github.com/Effect-TS/effect/commit/0a532e503f165fdea485a5343fc2f420917e8376), [`f398149`](https://github.com/Effect-TS/effect/commit/f398149c134fd9b67b6cdc52eae3f3248d5c7bbe), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`ace903e`](https://github.com/Effect-TS/effect/commit/ace903e09c2549ceebdec380797beb027cd29f3d), [`e8eb62b`](https://github.com/Effect-TS/effect/commit/e8eb62b3d0ef27e9761cdc2eb93bdec52d6ee204), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`48f22a7`](https://github.com/Effect-TS/effect/commit/48f22a7d16ae57ee2175d450dafbdeb69e187d2a), [`d48506d`](https://github.com/Effect-TS/effect/commit/d48506d97525040aa714305e928126df799795b4), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`d48506d`](https://github.com/Effect-TS/effect/commit/d48506d97525040aa714305e928126df799795b4), [`d48506d`](https://github.com/Effect-TS/effect/commit/d48506d97525040aa714305e928126df799795b4), [`52262be`](https://github.com/Effect-TS/effect/commit/52262be2edce0e350c6ac10f8f725678606399c5), [`1284aa1`](https://github.com/Effect-TS/effect/commit/1284aa183451955ad7921bbe01fd0e095695d444), [`9867b9f`](https://github.com/Effect-TS/effect/commit/9867b9fc69f9cc6c443594fc7eccc7be0c674d9c), [`d0f1a22`](https://github.com/Effect-TS/effect/commit/d0f1a2295155c350b04efb46852cb40032805273), [`979ce39`](https://github.com/Effect-TS/effect/commit/979ce3985d7d62ce2bf240681ca19feda3027452), [`b6d3e67`](https://github.com/Effect-TS/effect/commit/b6d3e67c7cc143cd8470cdf704324e79d23954a9), [`adf6c6c`](https://github.com/Effect-TS/effect/commit/adf6c6cd388af8a3c0c546492e71555368556f6a), [`7314d60`](https://github.com/Effect-TS/effect/commit/7314d605284717aaafe7fc34b88c3c93397e865c), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`1acbd8b`](https://github.com/Effect-TS/effect/commit/1acbd8b44c68ebb23735e9810476b870dbe58aea), [`7bde6cc`](https://github.com/Effect-TS/effect/commit/7bde6ccb2b144fe953ff30a7ef5e1ecc97697146), [`a959a8b`](https://github.com/Effect-TS/effect/commit/a959a8bf21cdb976369f494dc949fa00a050d3e0)]: + - effect@4.0.0-beta.103 + +## 4.0.0-beta.102 + +### Patch Changes + +- Updated dependencies [[`b6392e1`](https://github.com/Effect-TS/effect/commit/b6392e119704553edec1b4fd2869ac0dbec621ef), [`7ed9450`](https://github.com/Effect-TS/effect/commit/7ed945044eb56aa9aeaf62d4746a011c96c58628), [`45762bd`](https://github.com/Effect-TS/effect/commit/45762bd78df9ecd87c98b8d3738cdeeac7d81128), [`a6e8391`](https://github.com/Effect-TS/effect/commit/a6e8391cd31acd898fae18b3f8e7ca4c6f14f065), [`4ac7e8b`](https://github.com/Effect-TS/effect/commit/4ac7e8b136c61a26c3e438c013dfd7349b38e999), [`4cd40f5`](https://github.com/Effect-TS/effect/commit/4cd40f5692477783bef84fed3c5ef1c0cf5602e6), [`6956bc0`](https://github.com/Effect-TS/effect/commit/6956bc0e6cb27f53fbec39d9b18545940f9f598f), [`0e50ec7`](https://github.com/Effect-TS/effect/commit/0e50ec7dbb94390666f292cf9120719bf30a7246), [`9fcdade`](https://github.com/Effect-TS/effect/commit/9fcdade4a8af772b9ccd8b8a24fe8cee0e5d8470), [`57367d5`](https://github.com/Effect-TS/effect/commit/57367d54de55047ff0c5fce9685475e236bf354c), [`35c445f`](https://github.com/Effect-TS/effect/commit/35c445ff18029d192900ea0914c993f58d5cf1a5), [`c917bb9`](https://github.com/Effect-TS/effect/commit/c917bb94a4c1c4e0a24372a8ebb8a5ca232e36b5), [`bc1f358`](https://github.com/Effect-TS/effect/commit/bc1f3583e63344cb2c398d9040d9c975488ed123), [`0e0c9d7`](https://github.com/Effect-TS/effect/commit/0e0c9d7922ff463c1093d9e0576fae12cb0698d5), [`73d40aa`](https://github.com/Effect-TS/effect/commit/73d40aacd8fcae1b48c23f5b0a5c542127401d1d), [`4f1e318`](https://github.com/Effect-TS/effect/commit/4f1e3183f7123591c46224e9c587df7594562a5f), [`9d8d85c`](https://github.com/Effect-TS/effect/commit/9d8d85c1bb7da51970845b8ea830e386e777514a), [`6079fda`](https://github.com/Effect-TS/effect/commit/6079fda7b02f2f01ad91c15ab8c307336f3ba252), [`5101e92`](https://github.com/Effect-TS/effect/commit/5101e92c9c149c153423f43dd7a94f6194653c06), [`d0b3265`](https://github.com/Effect-TS/effect/commit/d0b3265c3262670761471ab3518cf933b1b3b20a), [`7a03c89`](https://github.com/Effect-TS/effect/commit/7a03c893ce6492bf94c0ebfb00b63bf25dcbf83e), [`cea1d9c`](https://github.com/Effect-TS/effect/commit/cea1d9c92601e69ebda040af8a1d860d604d885c), [`078e1f5`](https://github.com/Effect-TS/effect/commit/078e1f5636e31b76a86722a636afc37a8cc25580), [`97bafea`](https://github.com/Effect-TS/effect/commit/97bafeab460833b9781527b437d1cb9cbee63260), [`fab0ab8`](https://github.com/Effect-TS/effect/commit/fab0ab8f7ab15ae596faa4ccf75615a494d11b0b), [`c323d8b`](https://github.com/Effect-TS/effect/commit/c323d8b30dbbe85f9df25b67288b93d5332de333), [`6966353`](https://github.com/Effect-TS/effect/commit/69663534d626003eb10a5e55ab1f13e0379fead1), [`0444004`](https://github.com/Effect-TS/effect/commit/04440041989c1785fe4db286379f2be2c15baa85), [`028bbb3`](https://github.com/Effect-TS/effect/commit/028bbb391e161185da10d974ab33381f769940d7), [`ff5d6e2`](https://github.com/Effect-TS/effect/commit/ff5d6e278a1fdff714315dc1a17075012f05c1f0), [`1bfce93`](https://github.com/Effect-TS/effect/commit/1bfce93e6d2bf0794c11733daf51c2390e7de375), [`7ce815c`](https://github.com/Effect-TS/effect/commit/7ce815cd5af6af991dfc13b890fd22345fc77c20), [`7271a7f`](https://github.com/Effect-TS/effect/commit/7271a7faf1080aa75f2f53ca6a0b5ec9334c1d38), [`475fe5c`](https://github.com/Effect-TS/effect/commit/475fe5c12c2d6504c475797c0634f90da01e1797)]: + - effect@4.0.0-beta.102 + +## 4.0.0-beta.101 + +### Patch Changes + +- Updated dependencies [[`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`b35ed29`](https://github.com/Effect-TS/effect/commit/b35ed2904f01536d303b21f288daf343cf740462), [`dd44624`](https://github.com/Effect-TS/effect/commit/dd446245736a0e88c807a02f03c21450bb9340fa), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`2bae1ac`](https://github.com/Effect-TS/effect/commit/2bae1accce9d3b72cf6d5aefc9b2161af6d88436)]: + - effect@4.0.0-beta.101 + +## 4.0.0-beta.100 + +### Patch Changes + +- Updated dependencies [[`c1288dd`](https://github.com/Effect-TS/effect/commit/c1288dd1a52a2811ab7df57fc4ce236c6be4c745), [`2b58a3d`](https://github.com/Effect-TS/effect/commit/2b58a3dab6bc99776dddaf76e27d811e0f47f3d8), [`6dc83f2`](https://github.com/Effect-TS/effect/commit/6dc83f26ddf20d48db28cf761dd8f3716e5273fb), [`c1e2fe0`](https://github.com/Effect-TS/effect/commit/c1e2fe0cf93564f4d919e3998874c3e70b0cf30f), [`f3fbae8`](https://github.com/Effect-TS/effect/commit/f3fbae8d7bae0d77cb4f35a1598b26c58e3bf94d), [`e000f80`](https://github.com/Effect-TS/effect/commit/e000f80fd55bcd8edc699fdbf4cd109004f4f754), [`f4ee765`](https://github.com/Effect-TS/effect/commit/f4ee7655ee052cf9ba726fd602bb87c89c7c62a9), [`510b55f`](https://github.com/Effect-TS/effect/commit/510b55f3e21750685dbfd5f476a130c1c5af9dbd), [`31d3fc4`](https://github.com/Effect-TS/effect/commit/31d3fc4327c50867bb8d881fa7353aeb03ea2826), [`875e618`](https://github.com/Effect-TS/effect/commit/875e618c3764a7b817ac863d0af86924449528f2), [`688d46a`](https://github.com/Effect-TS/effect/commit/688d46afd0ef923d983ad3d7385f52f217b28d70), [`6ff5023`](https://github.com/Effect-TS/effect/commit/6ff502363b9840a5a5ee0a24bc6cae734ac3a3eb), [`c0333e7`](https://github.com/Effect-TS/effect/commit/c0333e7f755f42ddcca7051e029da8b4eed527bf), [`06e7e8c`](https://github.com/Effect-TS/effect/commit/06e7e8c66015ee318f871b9d2218dee82df2b108), [`eb9b102`](https://github.com/Effect-TS/effect/commit/eb9b10256c8558881b441c2fef833b7037174400), [`8b155da`](https://github.com/Effect-TS/effect/commit/8b155da06e0740c354ec562957a45ab65eb4573b), [`3a87335`](https://github.com/Effect-TS/effect/commit/3a8733564c5db35271aa20564ed0d344daa2a79f)]: + - effect@4.0.0-beta.100 + +## 4.0.0-beta.99 + +### Patch Changes + +- Updated dependencies [[`8ce4795`](https://github.com/Effect-TS/effect/commit/8ce4795ccbaebca4292757db568c005a992546a4), [`80b539f`](https://github.com/Effect-TS/effect/commit/80b539f8aba68f478c75c35c2b4140c4ffc4fada), [`88a54cc`](https://github.com/Effect-TS/effect/commit/88a54cc341006e3ebcb13482c618f62a680ce199), [`e6e6dba`](https://github.com/Effect-TS/effect/commit/e6e6dba6e9d86e7c2ad27dcedf289db76a19697f), [`bfb203e`](https://github.com/Effect-TS/effect/commit/bfb203e95aa439f731acad37fc3a9a831a190f1c), [`2e9a34a`](https://github.com/Effect-TS/effect/commit/2e9a34ac2bece4f3a206160480c991e3841dc67a), [`55d4eb3`](https://github.com/Effect-TS/effect/commit/55d4eb34f2c64d54f6a25a305b5c5438ebd7934e), [`bddb010`](https://github.com/Effect-TS/effect/commit/bddb010eac3d4436cb094edbbee7460c5440c162), [`a328835`](https://github.com/Effect-TS/effect/commit/a328835e50d76bc96648a1c1550456e8c9f81210), [`5560d05`](https://github.com/Effect-TS/effect/commit/5560d05aa6abdd29466d9c3412cc5e648b0adbde), [`8f6e3ad`](https://github.com/Effect-TS/effect/commit/8f6e3adb185b16e8820b98c509b308086f7ff1af), [`46997fa`](https://github.com/Effect-TS/effect/commit/46997fa60401f5e3c93daa4b61f7df8e31caaab4), [`9e6e12d`](https://github.com/Effect-TS/effect/commit/9e6e12d75c118cd265496f2880490d1f33a5c8bf), [`3394b93`](https://github.com/Effect-TS/effect/commit/3394b93d97d6f24fc38670641d1490289ffca7f1), [`febeabc`](https://github.com/Effect-TS/effect/commit/febeabc3f7c31094da000a23edeaabfe2ab00a38), [`54161c9`](https://github.com/Effect-TS/effect/commit/54161c98f6f3569e0c31842f54e6a257f9421c4c), [`385f7a4`](https://github.com/Effect-TS/effect/commit/385f7a4ee4a7359928597ea56d151dbaf5eb5802), [`7eea4d0`](https://github.com/Effect-TS/effect/commit/7eea4d0b73ec554915d7066a71f46326ce2ba45f), [`7543afe`](https://github.com/Effect-TS/effect/commit/7543afea6f4d97d1f1ad876224323838a48daadd), [`44b9cf3`](https://github.com/Effect-TS/effect/commit/44b9cf3d240d726997b4bbcd0ede48e074d3c456), [`7eea4d0`](https://github.com/Effect-TS/effect/commit/7eea4d0b73ec554915d7066a71f46326ce2ba45f), [`0a8aa6a`](https://github.com/Effect-TS/effect/commit/0a8aa6acb90a72b91c24d17133c950e4cacd8abd), [`c8d9fcf`](https://github.com/Effect-TS/effect/commit/c8d9fcf7b030f7c474effbab2764ce7aee1c7209), [`9ca7f9a`](https://github.com/Effect-TS/effect/commit/9ca7f9a69363e4485645966d5a93b8f9597c5206), [`e7aca89`](https://github.com/Effect-TS/effect/commit/e7aca894bb32fbb785b5830837e6061c415a6015), [`55d7560`](https://github.com/Effect-TS/effect/commit/55d75609b8acf8a1b54c1b1c7fbbb65ec741aa3e), [`f809189`](https://github.com/Effect-TS/effect/commit/f809189ddf6b6011ba43a9901baaa734e315da2a), [`88a54cc`](https://github.com/Effect-TS/effect/commit/88a54cc341006e3ebcb13482c618f62a680ce199), [`0ebdbe7`](https://github.com/Effect-TS/effect/commit/0ebdbe74463dc84385956d0b1e8c2b79ebab5400), [`7517d09`](https://github.com/Effect-TS/effect/commit/7517d09f12a0b183a81bd425962c4e280a68b05d), [`212493b`](https://github.com/Effect-TS/effect/commit/212493b9a1eb98cd1ef6959c707a2e5784a5ae91), [`88a54cc`](https://github.com/Effect-TS/effect/commit/88a54cc341006e3ebcb13482c618f62a680ce199), [`80ea8cb`](https://github.com/Effect-TS/effect/commit/80ea8cb9222ca73f564c8267ab2f82966fea027a), [`8df19f4`](https://github.com/Effect-TS/effect/commit/8df19f4fe81d90cc33ace88b9a77e5534f82d604)]: + - effect@4.0.0-beta.99 + +## 4.0.0-beta.98 + +### Patch Changes + +- Updated dependencies [[`989603b`](https://github.com/Effect-TS/effect-smol/commit/989603b60ab1197b64acf214208e0d370cd1f842), [`214c458`](https://github.com/Effect-TS/effect-smol/commit/214c458084bb6995d543cd37d1055f24be3d454e), [`a037273`](https://github.com/Effect-TS/effect-smol/commit/a0372736ac34796969b051bbba4717d7983f1ebe), [`97fdaa9`](https://github.com/Effect-TS/effect-smol/commit/97fdaa9c1f522c65e579365d314a07878e2b904f), [`b24d248`](https://github.com/Effect-TS/effect-smol/commit/b24d248c8df44222ce642087cde2bd859a2dc709), [`19c222c`](https://github.com/Effect-TS/effect-smol/commit/19c222cac2353a3d7b7733caecb00556fffe9a5c), [`eec85dd`](https://github.com/Effect-TS/effect-smol/commit/eec85ddba09ea326fd268ee33eeffd47e50d4671), [`0082f4f`](https://github.com/Effect-TS/effect-smol/commit/0082f4f74fb139fd578f87f0a790e845133983dc), [`8849052`](https://github.com/Effect-TS/effect-smol/commit/884905232d1e9a365e046d8dde27bf9c5707f57f), [`c15e16a`](https://github.com/Effect-TS/effect-smol/commit/c15e16ad130d1fbde25d912b7ac55995066cb35b), [`01d00a3`](https://github.com/Effect-TS/effect-smol/commit/01d00a3abfbf1f37996cdbe738ea5137c646cdd7), [`8bd4589`](https://github.com/Effect-TS/effect-smol/commit/8bd458975a1b3a8ed042eccf317b93d28ded91e7), [`0082f4f`](https://github.com/Effect-TS/effect-smol/commit/0082f4f74fb139fd578f87f0a790e845133983dc), [`6e08428`](https://github.com/Effect-TS/effect-smol/commit/6e08428d980501b856f846ad3f3f0e4ea46e7786), [`388dcf9`](https://github.com/Effect-TS/effect-smol/commit/388dcf953f65d317547f34d40e6443c5f264205f), [`2b7ce2b`](https://github.com/Effect-TS/effect-smol/commit/2b7ce2b513e7ec2a77822f1116dc6ffb6ba93f4e), [`87bea7e`](https://github.com/Effect-TS/effect-smol/commit/87bea7e16259246f3bcdf565446394751abca953), [`ce38dc3`](https://github.com/Effect-TS/effect-smol/commit/ce38dc33bda805a684432cca071f4dc3c6b9a1ba), [`a807cd1`](https://github.com/Effect-TS/effect-smol/commit/a807cd170341deca8a1cfb52c4222585f2431bb9), [`fd8a356`](https://github.com/Effect-TS/effect-smol/commit/fd8a356f06a8c9ce4e7e0a13fc4021c178ed31de), [`c2a5edc`](https://github.com/Effect-TS/effect-smol/commit/c2a5edc3abd31ad5bc123362bc1213e03e4095c3), [`5946da3`](https://github.com/Effect-TS/effect-smol/commit/5946da3804a1be5e752b05b96bd058cdba50a1bf), [`4ae0c5f`](https://github.com/Effect-TS/effect-smol/commit/4ae0c5ffcbe6c56ddfcb05c639112a079483539e), [`5b2a0bc`](https://github.com/Effect-TS/effect-smol/commit/5b2a0bceea3a28a33a58555210c90a415dc74a76), [`72ac585`](https://github.com/Effect-TS/effect-smol/commit/72ac585884befde6af9208da738699a93f1bae79), [`5e8c1b8`](https://github.com/Effect-TS/effect-smol/commit/5e8c1b82bfafa121311f987a49ab75395e3647a7), [`0f9c078`](https://github.com/Effect-TS/effect-smol/commit/0f9c07841b04183f485ee6e6458de73b290b09f5)]: + - effect@4.0.0-beta.98 + +## 4.0.0-beta.97 + +### Patch Changes + +- Updated dependencies []: + - effect@4.0.0-beta.97 + +## 4.0.0-beta.96 + +### Patch Changes + +- Updated dependencies [[`1503f45`](https://github.com/Effect-TS/effect-smol/commit/1503f45cb5bb2a74f4705252ec505a1f0ade7e62), [`57fe793`](https://github.com/Effect-TS/effect-smol/commit/57fe79316ffbc380b30626a168981fb26ae97459), [`0c2f78f`](https://github.com/Effect-TS/effect-smol/commit/0c2f78f695ec474e1ff5474da183577975e418f5), [`0c2f78f`](https://github.com/Effect-TS/effect-smol/commit/0c2f78f695ec474e1ff5474da183577975e418f5), [`0c2f78f`](https://github.com/Effect-TS/effect-smol/commit/0c2f78f695ec474e1ff5474da183577975e418f5), [`97f29df`](https://github.com/Effect-TS/effect-smol/commit/97f29df457f7ffd07cfb4b379315c12c086af805)]: + - effect@4.0.0-beta.96 + +## 4.0.0-beta.95 + +### Patch Changes + +- Updated dependencies [[`a482442`](https://github.com/Effect-TS/effect-smol/commit/a482442abdeb490e9652b854ec3495e4aa7273e7), [`fbefa85`](https://github.com/Effect-TS/effect-smol/commit/fbefa850fab2f0a302c20614496aeaaa2a8b5590), [`0b4a32f`](https://github.com/Effect-TS/effect-smol/commit/0b4a32f4260f0d8500942a133001b0d349328102), [`18a49e1`](https://github.com/Effect-TS/effect-smol/commit/18a49e1786679456258002ff9397faf02f678c2d), [`266cb90`](https://github.com/Effect-TS/effect-smol/commit/266cb90bb2c17aabc40563c32db334f09ba3d74b), [`912f095`](https://github.com/Effect-TS/effect-smol/commit/912f095a34572bbd3cedf6edb27878443e3e4a95), [`a6718f9`](https://github.com/Effect-TS/effect-smol/commit/a6718f9e00a15ca903b0732da46116cbf3d6aca7), [`bef5154`](https://github.com/Effect-TS/effect-smol/commit/bef51540a243aa2f872a00c01d0cd58b7a769baa), [`18e0564`](https://github.com/Effect-TS/effect-smol/commit/18e0564bd0f8ebbdfcaf1e2c21529948e9e4a81d), [`fb50f14`](https://github.com/Effect-TS/effect-smol/commit/fb50f14fc3657c1973785aa5b72ecf0b0d28e0b2)]: + - effect@4.0.0-beta.95 + +## 4.0.0-beta.94 + +### Patch Changes + +- Updated dependencies [[`95a0e9b`](https://github.com/Effect-TS/effect-smol/commit/95a0e9bb62797af0e81c9998773405f248f218c5), [`a0a3490`](https://github.com/Effect-TS/effect-smol/commit/a0a3490bbce765f199d8e077aceac504f0462e63), [`f11ce73`](https://github.com/Effect-TS/effect-smol/commit/f11ce73af60823754dc24194f4ffc561b9ea1c2d), [`ff30b6e`](https://github.com/Effect-TS/effect-smol/commit/ff30b6e7c2c63ffc56a4c5818d6d86b01b5ad528), [`1caab3c`](https://github.com/Effect-TS/effect-smol/commit/1caab3cc30f626efbf15e59d74f539a487e5c85c), [`aa80c47`](https://github.com/Effect-TS/effect-smol/commit/aa80c4775a04db87553e5568764cab7e32a72814), [`c2ae4fc`](https://github.com/Effect-TS/effect-smol/commit/c2ae4fce2f03a4cd1861c2b1179da7df656e662d), [`a0a3490`](https://github.com/Effect-TS/effect-smol/commit/a0a3490bbce765f199d8e077aceac504f0462e63)]: + - effect@4.0.0-beta.94 + +## 4.0.0-beta.93 + +### Patch Changes + +- Updated dependencies [[`00652fe`](https://github.com/Effect-TS/effect-smol/commit/00652fe95c18f87208e91343eb8bf218faa2f677), [`6c58167`](https://github.com/Effect-TS/effect-smol/commit/6c5816746eaf91d2a3c7c899c5720809fa230ae3), [`2bc5415`](https://github.com/Effect-TS/effect-smol/commit/2bc541501a7ef89e542d7cb98e96beb53cd205cc), [`e11cccc`](https://github.com/Effect-TS/effect-smol/commit/e11cccc7d5fe631abccc7d6e3bd296938de0fa2e), [`ba7e77e`](https://github.com/Effect-TS/effect-smol/commit/ba7e77e046b8641a3a4e9750bb88ca4a1d063d3f), [`5713ee7`](https://github.com/Effect-TS/effect-smol/commit/5713ee7edbc3054efde407b2286bbfd45bbc6e1c)]: + - effect@4.0.0-beta.93 + +## 4.0.0-beta.92 + +### Patch Changes + +- Updated dependencies [[`affdc13`](https://github.com/Effect-TS/effect-smol/commit/affdc139045cc325dce321a84a580fdc1b2da7b9)]: + - effect@4.0.0-beta.92 + +## 4.0.0-beta.91 + +### Patch Changes + +- Updated dependencies [[`b135b25`](https://github.com/Effect-TS/effect-smol/commit/b135b2517fca9e7839734ace3699a7dfa75b9075), [`aaa21a3`](https://github.com/Effect-TS/effect-smol/commit/aaa21a369a171c600db294f2a4f640583043e150), [`3475ee6`](https://github.com/Effect-TS/effect-smol/commit/3475ee6c2bda6b05c6d7a12ce30c8bb840b5b1a6)]: + - effect@4.0.0-beta.91 + ## 4.0.0-beta.90 ### Patch Changes diff --git a/repos/effect/packages/sql/sqlite-react-native/README.md b/repos/effect/packages/sql/sqlite-react-native/README.md index db45354523..8a948d7662 100644 --- a/repos/effect/packages/sql/sqlite-react-native/README.md +++ b/repos/effect/packages/sql/sqlite-react-native/README.md @@ -1,6 +1,6 @@ # `@effect/sql-sqlite-react-native` -An `@effect/sql` implementation using the `@op-engineering/op-sqlite` library. +An Effect SQL implementation using the `@op-engineering/op-sqlite` library. ## Documentation diff --git a/repos/effect/packages/sql/sqlite-react-native/docgen.json b/repos/effect/packages/sql/sqlite-react-native/docgen.json deleted file mode 100644 index 5b4cc0946a..0000000000 --- a/repos/effect/packages/sql/sqlite-react-native/docgen.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "$schema": "../../node_modules/@effect/docgen/schema.json", - "srcLink": "https://github.com/Effect-TS/effect/tree/main/packages/sql/sqlite-react-native/src/", - "exclude": ["src/internal/**/*.ts"], - "tscExecutable": "tsgo", - "examplesCompilerOptions": { - "noEmit": true, - "strict": true, - "skipLibCheck": true, - "moduleResolution": "Bundler", - "module": "ES2022", - "target": "ES2022", - "lib": ["ES2022", "DOM", "DOM.Iterable"], - "rewriteRelativeImportExtensions": true, - "allowImportingTsExtensions": true, - "paths": { - "effect": ["../../../effect/src/index.js"], - "effect/*": ["../../../effect/src/*.js"] - }, - "plugins": [ - { "name": "@effect/language-service", "includeSuggestionsInTsc": false } - ] - } -} diff --git a/repos/effect/packages/sql/sqlite-react-native/package.json b/repos/effect/packages/sql/sqlite-react-native/package.json index f1ea591c71..5d3e8a3e45 100644 --- a/repos/effect/packages/sql/sqlite-react-native/package.json +++ b/repos/effect/packages/sql/sqlite-react-native/package.json @@ -1,17 +1,17 @@ { "name": "@effect/sql-sqlite-react-native", - "version": "4.0.0-beta.90", + "version": "4.0.0-beta.107", "type": "module", "license": "MIT", "description": "A SQLite toolkit for Effect", "homepage": "https://effect.website", "repository": { "type": "git", - "url": "https://github.com/Effect-TS/effect-smol.git", + "url": "https://github.com/Effect-TS/effect.git", "directory": "packages/sql/sqlite-react-native" }, "bugs": { - "url": "https://github.com/Effect-TS/effect-smol/issues" + "url": "https://github.com/Effect-TS/effect/issues" }, "tags": [ "typescript", @@ -29,6 +29,7 @@ ".": "./src/index.ts", "./*": "./src/*.ts", "./internal/*": null, + "./index": null, "./*/index": null }, "files": [ @@ -36,7 +37,10 @@ "dist/**/*.js", "dist/**/*.js.map", "dist/**/*.d.ts", - "dist/**/*.d.ts.map" + "dist/**/*.d.ts.map", + "AGENTS.md", + "CLAUDE.md", + "ai-docs/**/*" ], "publishConfig": { "access": "public", @@ -46,24 +50,22 @@ ".": "./dist/index.js", "./*": "./dist/*.js", "./internal/*": null, + "./index": null, "./*/index": null } }, "scripts": { "codegen": "effect-utils codegen", "build": "tsc -b tsconfig.json && pnpm babel", - "build:tsgo": "tsgo -b tsconfig.json && pnpm babel", "babel": "babel dist --plugins annotate-pure-calls --out-dir dist --source-maps", - "check": "tsc -b tsconfig.json", - "test": "vitest", - "coverage": "vitest --coverage" + "check": "tsc -b tsconfig.json" }, "devDependencies": { - "@op-engineering/op-sqlite": "15.2.12", + "@op-engineering/op-sqlite": "^17.1.2", "effect": "workspace:^" }, "peerDependencies": { - "@op-engineering/op-sqlite": "15.0.4", + "@op-engineering/op-sqlite": ">=17.1.2 <18.0.0", "effect": "workspace:^" } } diff --git a/repos/effect/packages/sql/sqlite-react-native/src/SqliteClient.ts b/repos/effect/packages/sql/sqlite-react-native/src/SqliteClient.ts index 9ea80d2a9b..f3aacd31d4 100644 --- a/repos/effect/packages/sql/sqlite-react-native/src/SqliteClient.ts +++ b/repos/effect/packages/sql/sqlite-react-native/src/SqliteClient.ts @@ -51,7 +51,7 @@ export type TypeId = "~@effect/sql-sqlite-react-native/SqliteClient" /** * React Native SQLite client service interface, extending `SqlClient` with its configuration and marking `updateValues` as unsupported for SQLite. * - * @category models + * @category services * @since 4.0.0 */ export interface SqliteClient extends Client.SqlClient { @@ -93,7 +93,7 @@ export interface SqliteClientConfig { * Use to switch React Native SQLite query execution to the asynchronous driver * API for a scoped effect. * - * @category fiber refs + * @category services * @since 4.0.0 */ export const AsyncQuery = Context.Reference( @@ -104,7 +104,7 @@ export const AsyncQuery = Context.Reference( /** * Runs an effect with `AsyncQuery` enabled, causing React Native SQLite queries in that effect to use the asynchronous driver API. * - * @category fiber refs + * @category providing services * @since 4.0.0 */ export const withAsyncQuery = (effect: Effect.Effect) => @@ -143,8 +143,7 @@ export const make = ( const run = ( sql: string, - params: ReadonlyArray = [], - values = false + params: ReadonlyArray = [] ) => Effect.withFiber, SqlError>((fiber) => { if (fiber.getRef(AsyncQuery)) { @@ -154,14 +153,29 @@ export const make = ( catch: (cause) => new SqlError({ reason: classifyError(cause, "Failed to execute statement (async)", "execute") }) }), - (result) => values ? result.rawRows ?? [] : result.rows + (result) => result.rows ) } return Effect.try({ - try: () => { - const result = db.executeSync(sql, params as Array) - return values ? result.rawRows ?? [] : result.rows - }, + try: () => db.executeSync(sql, params as Array).rows, + catch: (cause) => new SqlError({ reason: classifyError(cause, "Failed to execute statement", "execute") }) + }) + }) + + const runValues = ( + sql: string, + params: ReadonlyArray = [] + ) => + Effect.withFiber, SqlError>((fiber) => { + if (fiber.getRef(AsyncQuery)) { + return Effect.tryPromise({ + try: () => db.executeRaw(sql, params as Array), + catch: (cause) => + new SqlError({ reason: classifyError(cause, "Failed to execute statement (async)", "execute") }) + }) + } + return Effect.try({ + try: () => db.executeRawSync(sql, params as Array), catch: (cause) => new SqlError({ reason: classifyError(cause, "Failed to execute statement", "execute") }) }) }) @@ -176,10 +190,10 @@ export const make = ( return run(sql, params) }, executeValues(sql, params) { - return run(sql, params, true) + return runValues(sql, params) }, executeValuesUnprepared(sql, params) { - return run(sql, params, true) + return runValues(sql, params) }, executeUnprepared(sql, params, transformRows) { return this.execute(sql, params, transformRows) diff --git a/repos/effect/packages/sql/sqlite-react-native/src/SqliteMigrator.ts b/repos/effect/packages/sql/sqlite-react-native/src/SqliteMigrator.ts index 23c4dcadc4..2df7376530 100644 --- a/repos/effect/packages/sql/sqlite-react-native/src/SqliteMigrator.ts +++ b/repos/effect/packages/sql/sqlite-react-native/src/SqliteMigrator.ts @@ -33,7 +33,7 @@ export * from "effect/unstable/sql/Migrator" /** * Runs SQL migrations for a React Native SQLite database using the shared `Migrator` implementation and the current `SqlClient`. * - * @category constructors + * @category running * @since 4.0.0 */ export const run: ( @@ -47,7 +47,7 @@ export const run: ( /** * Creates a layer that runs the configured React Native SQLite migrations during layer construction and provides no services. * - * @category constructors + * @category layers * @since 4.0.0 */ export const layer = ( diff --git a/repos/effect/packages/sql/sqlite-react-native/test/Client.test.ts b/repos/effect/packages/sql/sqlite-react-native/test/Client.test.ts index fc3285a360..82db4961fb 100644 --- a/repos/effect/packages/sql/sqlite-react-native/test/Client.test.ts +++ b/repos/effect/packages/sql/sqlite-react-native/test/Client.test.ts @@ -1,6 +1,42 @@ -import { describe, it } from "@effect/vitest" +import { assert, describe, it } from "@effect/vitest" import { Effect } from "effect" +import { Reactivity } from "effect/unstable/reactivity" +import { vi } from "vitest" + +const state = vi.hoisted(() => ({ + database: { + close() {}, + execute: async () => ({ rowsAffected: 0, rows: [{ value: 1 }] }), + executeRaw: async () => [[1]], + executeRawSync: () => [[1]], + executeSync: () => ({ rowsAffected: 0, rows: [{ value: 1 }] }) + } +})) + +vi.mock("@op-engineering/op-sqlite", () => ({ + open: () => state.database +})) + +import { SqliteClient } from "@effect/sql-sqlite-react-native" describe("Client", () => { it.effect("should work", () => Effect.void) + + it.effect("returns array rows from synchronous values queries", () => + Effect.gen(function*() { + const sql = yield* SqliteClient.make({ filename: "test.db" }) + const rows = yield* sql`SELECT 1 AS value`.values + const unpreparedRows = yield* sql`SELECT 1 AS value`.valuesUnprepared + assert.deepStrictEqual(rows, [[1]]) + assert.deepStrictEqual(unpreparedRows, [[1]]) + }).pipe(Effect.provide(Reactivity.layer))) + + it.effect("returns array rows from asynchronous values queries", () => + Effect.gen(function*() { + const sql = yield* SqliteClient.make({ filename: "test.db" }) + const rows = yield* SqliteClient.withAsyncQuery(sql`SELECT 1 AS value`.values) + const unpreparedRows = yield* SqliteClient.withAsyncQuery(sql`SELECT 1 AS value`.valuesUnprepared) + assert.deepStrictEqual(rows, [[1]]) + assert.deepStrictEqual(unpreparedRows, [[1]]) + }).pipe(Effect.provide(Reactivity.layer))) }) diff --git a/repos/effect/packages/sql/sqlite-react-native/tsconfig.json b/repos/effect/packages/sql/sqlite-react-native/tsconfig.json index 19a2f5dbca..e2a8ca19a0 100644 --- a/repos/effect/packages/sql/sqlite-react-native/tsconfig.json +++ b/repos/effect/packages/sql/sqlite-react-native/tsconfig.json @@ -1,5 +1,5 @@ { - "$schema": "http://json.schemastore.org/tsconfig", + "$schema": "https://json.schemastore.org/tsconfig", "extends": "../../../tsconfig.base.json", "include": ["src"], "references": [ diff --git a/repos/effect/packages/sql/sqlite-react-native/vitest.config.ts b/repos/effect/packages/sql/sqlite-react-native/vitest.config.ts deleted file mode 100644 index c8a52c1826..0000000000 --- a/repos/effect/packages/sql/sqlite-react-native/vitest.config.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { mergeConfig, type ViteUserConfig } from "vitest/config" -import shared from "../../../vitest.shared.ts" - -const config: ViteUserConfig = {} - -export default mergeConfig(shared, config) diff --git a/repos/effect/packages/sql/sqlite-wasm/CHANGELOG.md b/repos/effect/packages/sql/sqlite-wasm/CHANGELOG.md index cd6163b1d7..a9ce0eb3a6 100644 --- a/repos/effect/packages/sql/sqlite-wasm/CHANGELOG.md +++ b/repos/effect/packages/sql/sqlite-wasm/CHANGELOG.md @@ -1,5 +1,132 @@ # @effect/sql-sqlite-wasm +## 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 + +- [#6994](https://github.com/Effect-TS/effect/pull/6994) [`3dd1ddc`](https://github.com/Effect-TS/effect/commit/3dd1ddce893aa04abf6b13f31b43c5fc2d3ae7cd) Thanks @fubhy! - Settle pending SQLite WASM requests before replacing failed workers. +- 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 + +- [#6923](https://github.com/Effect-TS/effect/pull/6923) [`34ced69`](https://github.com/Effect-TS/effect/commit/34ced6990ed7cc3bbd4d9fc7a5517608cb18b5ef) Thanks @fubhy! - Close OPFS access handles when the SQLite worker shuts down. + +- [#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 + +- [#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 + +- Updated dependencies [[`989603b`](https://github.com/Effect-TS/effect-smol/commit/989603b60ab1197b64acf214208e0d370cd1f842), [`214c458`](https://github.com/Effect-TS/effect-smol/commit/214c458084bb6995d543cd37d1055f24be3d454e), [`a037273`](https://github.com/Effect-TS/effect-smol/commit/a0372736ac34796969b051bbba4717d7983f1ebe), [`97fdaa9`](https://github.com/Effect-TS/effect-smol/commit/97fdaa9c1f522c65e579365d314a07878e2b904f), [`b24d248`](https://github.com/Effect-TS/effect-smol/commit/b24d248c8df44222ce642087cde2bd859a2dc709), [`19c222c`](https://github.com/Effect-TS/effect-smol/commit/19c222cac2353a3d7b7733caecb00556fffe9a5c), [`eec85dd`](https://github.com/Effect-TS/effect-smol/commit/eec85ddba09ea326fd268ee33eeffd47e50d4671), [`0082f4f`](https://github.com/Effect-TS/effect-smol/commit/0082f4f74fb139fd578f87f0a790e845133983dc), [`8849052`](https://github.com/Effect-TS/effect-smol/commit/884905232d1e9a365e046d8dde27bf9c5707f57f), [`c15e16a`](https://github.com/Effect-TS/effect-smol/commit/c15e16ad130d1fbde25d912b7ac55995066cb35b), [`01d00a3`](https://github.com/Effect-TS/effect-smol/commit/01d00a3abfbf1f37996cdbe738ea5137c646cdd7), [`8bd4589`](https://github.com/Effect-TS/effect-smol/commit/8bd458975a1b3a8ed042eccf317b93d28ded91e7), [`0082f4f`](https://github.com/Effect-TS/effect-smol/commit/0082f4f74fb139fd578f87f0a790e845133983dc), [`6e08428`](https://github.com/Effect-TS/effect-smol/commit/6e08428d980501b856f846ad3f3f0e4ea46e7786), [`388dcf9`](https://github.com/Effect-TS/effect-smol/commit/388dcf953f65d317547f34d40e6443c5f264205f), [`2b7ce2b`](https://github.com/Effect-TS/effect-smol/commit/2b7ce2b513e7ec2a77822f1116dc6ffb6ba93f4e), [`87bea7e`](https://github.com/Effect-TS/effect-smol/commit/87bea7e16259246f3bcdf565446394751abca953), [`ce38dc3`](https://github.com/Effect-TS/effect-smol/commit/ce38dc33bda805a684432cca071f4dc3c6b9a1ba), [`a807cd1`](https://github.com/Effect-TS/effect-smol/commit/a807cd170341deca8a1cfb52c4222585f2431bb9), [`fd8a356`](https://github.com/Effect-TS/effect-smol/commit/fd8a356f06a8c9ce4e7e0a13fc4021c178ed31de), [`c2a5edc`](https://github.com/Effect-TS/effect-smol/commit/c2a5edc3abd31ad5bc123362bc1213e03e4095c3), [`5946da3`](https://github.com/Effect-TS/effect-smol/commit/5946da3804a1be5e752b05b96bd058cdba50a1bf), [`4ae0c5f`](https://github.com/Effect-TS/effect-smol/commit/4ae0c5ffcbe6c56ddfcb05c639112a079483539e), [`5b2a0bc`](https://github.com/Effect-TS/effect-smol/commit/5b2a0bceea3a28a33a58555210c90a415dc74a76), [`72ac585`](https://github.com/Effect-TS/effect-smol/commit/72ac585884befde6af9208da738699a93f1bae79), [`5e8c1b8`](https://github.com/Effect-TS/effect-smol/commit/5e8c1b82bfafa121311f987a49ab75395e3647a7), [`0f9c078`](https://github.com/Effect-TS/effect-smol/commit/0f9c07841b04183f485ee6e6458de73b290b09f5)]: + - effect@4.0.0-beta.98 + +## 4.0.0-beta.97 + +### Patch Changes + +- Updated dependencies []: + - effect@4.0.0-beta.97 + +## 4.0.0-beta.96 + +### Patch Changes + +- Updated dependencies [[`1503f45`](https://github.com/Effect-TS/effect-smol/commit/1503f45cb5bb2a74f4705252ec505a1f0ade7e62), [`57fe793`](https://github.com/Effect-TS/effect-smol/commit/57fe79316ffbc380b30626a168981fb26ae97459), [`0c2f78f`](https://github.com/Effect-TS/effect-smol/commit/0c2f78f695ec474e1ff5474da183577975e418f5), [`0c2f78f`](https://github.com/Effect-TS/effect-smol/commit/0c2f78f695ec474e1ff5474da183577975e418f5), [`0c2f78f`](https://github.com/Effect-TS/effect-smol/commit/0c2f78f695ec474e1ff5474da183577975e418f5), [`97f29df`](https://github.com/Effect-TS/effect-smol/commit/97f29df457f7ffd07cfb4b379315c12c086af805)]: + - effect@4.0.0-beta.96 + +## 4.0.0-beta.95 + +### Patch Changes + +- Updated dependencies [[`a482442`](https://github.com/Effect-TS/effect-smol/commit/a482442abdeb490e9652b854ec3495e4aa7273e7), [`fbefa85`](https://github.com/Effect-TS/effect-smol/commit/fbefa850fab2f0a302c20614496aeaaa2a8b5590), [`0b4a32f`](https://github.com/Effect-TS/effect-smol/commit/0b4a32f4260f0d8500942a133001b0d349328102), [`18a49e1`](https://github.com/Effect-TS/effect-smol/commit/18a49e1786679456258002ff9397faf02f678c2d), [`266cb90`](https://github.com/Effect-TS/effect-smol/commit/266cb90bb2c17aabc40563c32db334f09ba3d74b), [`912f095`](https://github.com/Effect-TS/effect-smol/commit/912f095a34572bbd3cedf6edb27878443e3e4a95), [`a6718f9`](https://github.com/Effect-TS/effect-smol/commit/a6718f9e00a15ca903b0732da46116cbf3d6aca7), [`bef5154`](https://github.com/Effect-TS/effect-smol/commit/bef51540a243aa2f872a00c01d0cd58b7a769baa), [`18e0564`](https://github.com/Effect-TS/effect-smol/commit/18e0564bd0f8ebbdfcaf1e2c21529948e9e4a81d), [`fb50f14`](https://github.com/Effect-TS/effect-smol/commit/fb50f14fc3657c1973785aa5b72ecf0b0d28e0b2)]: + - effect@4.0.0-beta.95 + +## 4.0.0-beta.94 + +### Patch Changes + +- Updated dependencies [[`95a0e9b`](https://github.com/Effect-TS/effect-smol/commit/95a0e9bb62797af0e81c9998773405f248f218c5), [`a0a3490`](https://github.com/Effect-TS/effect-smol/commit/a0a3490bbce765f199d8e077aceac504f0462e63), [`f11ce73`](https://github.com/Effect-TS/effect-smol/commit/f11ce73af60823754dc24194f4ffc561b9ea1c2d), [`ff30b6e`](https://github.com/Effect-TS/effect-smol/commit/ff30b6e7c2c63ffc56a4c5818d6d86b01b5ad528), [`1caab3c`](https://github.com/Effect-TS/effect-smol/commit/1caab3cc30f626efbf15e59d74f539a487e5c85c), [`aa80c47`](https://github.com/Effect-TS/effect-smol/commit/aa80c4775a04db87553e5568764cab7e32a72814), [`c2ae4fc`](https://github.com/Effect-TS/effect-smol/commit/c2ae4fce2f03a4cd1861c2b1179da7df656e662d), [`a0a3490`](https://github.com/Effect-TS/effect-smol/commit/a0a3490bbce765f199d8e077aceac504f0462e63)]: + - effect@4.0.0-beta.94 + +## 4.0.0-beta.93 + +### Patch Changes + +- Updated dependencies [[`00652fe`](https://github.com/Effect-TS/effect-smol/commit/00652fe95c18f87208e91343eb8bf218faa2f677), [`6c58167`](https://github.com/Effect-TS/effect-smol/commit/6c5816746eaf91d2a3c7c899c5720809fa230ae3), [`2bc5415`](https://github.com/Effect-TS/effect-smol/commit/2bc541501a7ef89e542d7cb98e96beb53cd205cc), [`e11cccc`](https://github.com/Effect-TS/effect-smol/commit/e11cccc7d5fe631abccc7d6e3bd296938de0fa2e), [`ba7e77e`](https://github.com/Effect-TS/effect-smol/commit/ba7e77e046b8641a3a4e9750bb88ca4a1d063d3f), [`5713ee7`](https://github.com/Effect-TS/effect-smol/commit/5713ee7edbc3054efde407b2286bbfd45bbc6e1c)]: + - effect@4.0.0-beta.93 + +## 4.0.0-beta.92 + +### Patch Changes + +- Updated dependencies [[`affdc13`](https://github.com/Effect-TS/effect-smol/commit/affdc139045cc325dce321a84a580fdc1b2da7b9)]: + - effect@4.0.0-beta.92 + +## 4.0.0-beta.91 + +### Patch Changes + +- Updated dependencies [[`b135b25`](https://github.com/Effect-TS/effect-smol/commit/b135b2517fca9e7839734ace3699a7dfa75b9075), [`aaa21a3`](https://github.com/Effect-TS/effect-smol/commit/aaa21a369a171c600db294f2a4f640583043e150), [`3475ee6`](https://github.com/Effect-TS/effect-smol/commit/3475ee6c2bda6b05c6d7a12ce30c8bb840b5b1a6)]: + - effect@4.0.0-beta.91 + ## 4.0.0-beta.90 ### Patch Changes diff --git a/repos/effect/packages/sql/sqlite-wasm/README.md b/repos/effect/packages/sql/sqlite-wasm/README.md index 68aa1a3180..fc2073bfcb 100644 --- a/repos/effect/packages/sql/sqlite-wasm/README.md +++ b/repos/effect/packages/sql/sqlite-wasm/README.md @@ -1,6 +1,6 @@ # `@effect/sql-sqlite-wasm` -An `@effect/sql` implementation using the `@sqlite.org/sqlite-wasm` library. +An Effect SQL implementation using the `@sqlite.org/sqlite-wasm` library. ## Documentation diff --git a/repos/effect/packages/sql/sqlite-wasm/docgen.json b/repos/effect/packages/sql/sqlite-wasm/docgen.json deleted file mode 100644 index d89f871a47..0000000000 --- a/repos/effect/packages/sql/sqlite-wasm/docgen.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "$schema": "../../node_modules/@effect/docgen/schema.json", - "srcLink": "https://github.com/Effect-TS/effect/tree/main/packages/sql/do/src/", - "exclude": ["src/internal/**/*.ts"], - "tscExecutable": "tsgo", - "examplesCompilerOptions": { - "noEmit": true, - "strict": true, - "skipLibCheck": true, - "moduleResolution": "Bundler", - "module": "ES2022", - "target": "ES2022", - "lib": ["ES2022", "DOM", "DOM.Iterable"], - "rewriteRelativeImportExtensions": true, - "allowImportingTsExtensions": true, - "paths": { - "effect": ["../../../effect/src/index.js"], - "effect/*": ["../../../effect/src/*.js"] - }, - "plugins": [ - { "name": "@effect/language-service", "includeSuggestionsInTsc": false } - ] - } -} diff --git a/repos/effect/packages/sql/sqlite-wasm/package.json b/repos/effect/packages/sql/sqlite-wasm/package.json index 5efe8f5c26..b6014958e8 100644 --- a/repos/effect/packages/sql/sqlite-wasm/package.json +++ b/repos/effect/packages/sql/sqlite-wasm/package.json @@ -1,17 +1,17 @@ { "name": "@effect/sql-sqlite-wasm", - "version": "4.0.0-beta.90", + "version": "4.0.0-beta.107", "type": "module", "license": "MIT", "description": "A SQLite toolkit for Effect", "homepage": "https://effect.website", "repository": { "type": "git", - "url": "https://github.com/Effect-TS/effect-smol.git", + "url": "https://github.com/Effect-TS/effect.git", "directory": "packages/sql/sqlite-wasm" }, "bugs": { - "url": "https://github.com/Effect-TS/effect-smol/issues" + "url": "https://github.com/Effect-TS/effect/issues" }, "tags": [ "typescript", @@ -29,6 +29,7 @@ ".": "./src/index.ts", "./*": "./src/*.ts", "./internal/*": null, + "./index": null, "./*/index": null }, "files": [ @@ -36,7 +37,10 @@ "dist/**/*.js", "dist/**/*.js.map", "dist/**/*.d.ts", - "dist/**/*.d.ts.map" + "dist/**/*.d.ts.map", + "AGENTS.md", + "CLAUDE.md", + "ai-docs/**/*" ], "publishConfig": { "access": "public", @@ -46,24 +50,22 @@ ".": "./dist/index.js", "./*": "./dist/*.js", "./internal/*": null, + "./index": null, "./*/index": null } }, "scripts": { "codegen": "effect-utils codegen", "build": "tsc -b tsconfig.json && pnpm babel", - "build:tsgo": "tsgo -b tsconfig.json && pnpm babel", "babel": "babel dist --plugins annotate-pure-calls --out-dir dist --source-maps", - "check": "tsc -b tsconfig.json", - "test": "vitest", - "coverage": "vitest --coverage" + "check": "tsc -b tsconfig.json" }, "devDependencies": { "@effect/wa-sqlite": "^0.2.1", "effect": "workspace:^" }, "peerDependencies": { - "@effect/wa-sqlite": "^0.1.2", + "@effect/wa-sqlite": ">=0.1.2 <0.2.0", "effect": "workspace:^" } } diff --git a/repos/effect/packages/sql/sqlite-wasm/src/OpfsWorker.ts b/repos/effect/packages/sql/sqlite-wasm/src/OpfsWorker.ts index 59fc97afeb..0e078ab6b7 100644 --- a/repos/effect/packages/sql/sqlite-wasm/src/OpfsWorker.ts +++ b/repos/effect/packages/sql/sqlite-wasm/src/OpfsWorker.ts @@ -36,7 +36,7 @@ export interface OpfsWorkerConfig { /** * Runs the SQLite OPFS worker loop, opening the configured database, posting a ready message, handling query/import/export/update-hook messages, and closing when a close message is received. * - * @category constructors + * @category running * @since 4.0.0 */ export const run = ( @@ -45,7 +45,10 @@ export const run = ( Effect.gen(function*() { const factory = yield* Effect.promise(() => SQLiteESMFactory()) const sqlite3 = WaSqlite.Factory(factory) - const vfs = yield* Effect.promise(() => AccessHandlePoolVFS.create("opfs", factory)) + const vfs = yield* Effect.acquireRelease( + Effect.promise(() => AccessHandlePoolVFS.create("opfs", factory)), + (vfs) => Effect.promise(() => vfs.close()) + ) sqlite3.vfs_register(vfs, false) const db = yield* Effect.acquireRelease( Effect.try({ diff --git a/repos/effect/packages/sql/sqlite-wasm/src/SqliteClient.ts b/repos/effect/packages/sql/sqlite-wasm/src/SqliteClient.ts index d68f540b82..55fa88d060 100644 --- a/repos/effect/packages/sql/sqlite-wasm/src/SqliteClient.ts +++ b/repos/effect/packages/sql/sqlite-wasm/src/SqliteClient.ts @@ -24,6 +24,7 @@ import * as Exit from "effect/Exit" import * as Fiber from "effect/Fiber" import { identity } from "effect/Function" import * as Layer from "effect/Layer" +import * as Rec from "effect/Record" import * as Scope from "effect/Scope" import * as ScopedRef from "effect/ScopedRef" import * as Semaphore from "effect/Semaphore" @@ -59,7 +60,7 @@ export type TypeId = "~@effect/sql-sqlite-wasm/SqliteClient" /** * SQLite WASM client service interface, extending `SqlClient` with database `export` and `import` operations and marking `updateValues` as unsupported for SQLite. * - * @category models + * @category services * @since 4.0.0 */ export interface SqliteClient extends Client.SqlClient { @@ -183,7 +184,7 @@ export const makeMemory = ( if (rowMode === "object") { const obj: Record = {} for (let i = 0; i < columns.length; i++) { - obj[columns[i]] = row[i] + Rec.assignProperty(obj, columns[i], row[i]) } results.push(obj) } else { @@ -224,7 +225,7 @@ export const makeMemory = ( const row = sqlite3.row(stmt) const obj: Record = {} for (let i = 0; i < columns.length; i++) { - obj[columns[i]] = row[i] + Rec.assignProperty(obj, columns[i], row[i]) } yield obj } @@ -305,10 +306,9 @@ export const make = ( const transformRows = options.transformResultNames ? Statement.defaultTransforms(options.transformResultNames).array : undefined - const pending = new Map) => void>() - const makeConnection = Effect.gen(function*() { let currentId = 0 + const pending = new Map) => void>() const scope = yield* Effect.scope const readyDeferred = yield* Deferred.make() @@ -344,7 +344,15 @@ export const make = ( } port.addEventListener("message", onMessage) - function onError() { + function onError(cause: Event) { + const exit = Exit.fail( + new SqlError({ reason: classifyError(cause, "SQLite WASM worker failed", "worker") }) + ) + const requests = Array.from(pending.values()) + pending.clear() + for (const resume of requests) { + resume(exit) + } Effect.runFork(ScopedRef.set(connectionRef, makeConnection)) } if ("onerror" in worker) { @@ -456,7 +464,7 @@ export const make = ( function rowToObject(columns: Array, row: Array) { const obj: Record = {} for (let i = 0; i < columns.length; i++) { - obj[columns[i]] = row[i] + Rec.assignProperty(obj, columns[i], row[i]) } return obj } @@ -466,7 +474,7 @@ const extractRows = (rows: [Array, Array]) => rows[1] /** * Fiber reference that stores transferables to include with worker-backed SQLite WASM query messages. * - * @category transferables + * @category services * @since 4.0.0 */ export const Transferables = Context.Reference>( diff --git a/repos/effect/packages/sql/sqlite-wasm/src/SqliteMigrator.ts b/repos/effect/packages/sql/sqlite-wasm/src/SqliteMigrator.ts index 5074d5a924..361c662b75 100644 --- a/repos/effect/packages/sql/sqlite-wasm/src/SqliteMigrator.ts +++ b/repos/effect/packages/sql/sqlite-wasm/src/SqliteMigrator.ts @@ -33,7 +33,7 @@ export * from "effect/unstable/sql/Migrator" /** * Runs SQL migrations for a SQLite WASM database using the shared `Migrator` implementation and the current `SqlClient`. * - * @category constructors + * @category running * @since 4.0.0 */ export const run: ( @@ -47,7 +47,7 @@ export const run: ( /** * Creates a layer that runs the configured SQLite WASM migrations during layer construction and provides no services. * - * @category constructors + * @category layers * @since 4.0.0 */ export const layer = ( diff --git a/repos/effect/packages/sql/sqlite-wasm/test/Client.test.ts b/repos/effect/packages/sql/sqlite-wasm/test/Client.test.ts index fc3285a360..d126c97484 100644 --- a/repos/effect/packages/sql/sqlite-wasm/test/Client.test.ts +++ b/repos/effect/packages/sql/sqlite-wasm/test/Client.test.ts @@ -1,6 +1,89 @@ -import { describe, it } from "@effect/vitest" -import { Effect } from "effect" +import { assert, describe, it } from "@effect/vitest" +import { Deferred, Effect, Exit, Fiber, Option } from "effect" +import { TestClock } from "effect/testing" +import { Reactivity } from "effect/unstable/reactivity" +import { vi } from "vitest" + +const state = vi.hoisted(() => ({ vfsCloseCalls: 0 })) + +vi.mock("@effect/wa-sqlite/dist/wa-sqlite.mjs", () => ({ default: async () => ({}) })) +vi.mock("@effect/wa-sqlite/src/examples/AccessHandlePoolVFS.js", () => ({ + AccessHandlePoolVFS: { + create: async () => ({ + close: async () => { + state.vfsCloseCalls++ + } + }) + } +})) +vi.mock("@effect/wa-sqlite", () => ({ + Factory: () => ({ + close() {}, + open_v2: () => 1, + vfs_register() {} + }) +})) + +import { OpfsWorker, SqliteClient } from "@effect/sql-sqlite-wasm" + +class FakePort extends EventTarget { + close() {} + + postMessage(message: ReadonlyArray): void { + if (message[0] === "ready") { + queueMicrotask(() => this.dispatchEvent(new MessageEvent("message", { data: ["close"] }))) + } + } +} + +class FakeWorker extends EventTarget { + onerror: unknown = null + + constructor(readonly queryPosted: Deferred.Deferred) { + super() + } + + override addEventListener( + type: string, + listener: EventListenerOrEventListenerObject | null, + options?: unknown + ): void { + super.addEventListener(type, listener, options as boolean) + if (type === "message") { + queueMicrotask(() => this.dispatchEvent(new MessageEvent("message", { data: ["ready"] }))) + } + } + + postMessage(message: ReadonlyArray): void { + if (typeof message[0] === "number") { + Effect.runFork(Deferred.succeed(this.queryPosted, undefined)) + } + } +} describe("Client", () => { it.effect("should work", () => Effect.void) + + it.effect("closes the OPFS VFS when the worker loop closes", () => + Effect.gen(function*() { + yield* OpfsWorker.run({ port: new FakePort(), dbName: "test.db" }) + assert.strictEqual(state.vfsCloseCalls, 1) + })) + + it.effect("settles an in-flight query when the worker errors and reconnects", () => + Effect.gen(function*() { + const queryPosted = yield* Deferred.make() + const worker = new FakeWorker(queryPosted) + const sql = yield* SqliteClient.make({ worker: Effect.succeed(worker as unknown as Worker) }) + const fiber = yield* Effect.forkChild(sql`SELECT 1`) + + yield* Deferred.await(queryPosted) + worker.dispatchEvent(new Event("error")) + + const joinFiber = yield* Fiber.join(fiber).pipe(Effect.exit, Effect.timeoutOption("100 millis"), Effect.forkChild) + yield* TestClock.adjust("100 millis") + const result = yield* Fiber.join(joinFiber) + assert(Option.isSome(result), "the request remained pending after worker replacement") + assert(Exit.isFailure(result.value)) + }).pipe(Effect.provide(Reactivity.layer))) }) diff --git a/repos/effect/packages/sql/sqlite-wasm/tsconfig.json b/repos/effect/packages/sql/sqlite-wasm/tsconfig.json index 19a2f5dbca..e2a8ca19a0 100644 --- a/repos/effect/packages/sql/sqlite-wasm/tsconfig.json +++ b/repos/effect/packages/sql/sqlite-wasm/tsconfig.json @@ -1,5 +1,5 @@ { - "$schema": "http://json.schemastore.org/tsconfig", + "$schema": "https://json.schemastore.org/tsconfig", "extends": "../../../tsconfig.base.json", "include": ["src"], "references": [ diff --git a/repos/effect/packages/sql/sqlite-wasm/vitest.config.ts b/repos/effect/packages/sql/sqlite-wasm/vitest.config.ts deleted file mode 100644 index c8a52c1826..0000000000 --- a/repos/effect/packages/sql/sqlite-wasm/vitest.config.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { mergeConfig, type ViteUserConfig } from "vitest/config" -import shared from "../../../vitest.shared.ts" - -const config: ViteUserConfig = {} - -export default mergeConfig(shared, config) diff --git a/repos/effect/packages/tools/ai-codegen/docgen.json b/repos/effect/packages/tools/ai-codegen/docgen.json deleted file mode 100644 index 4dd36175ad..0000000000 --- a/repos/effect/packages/tools/ai-codegen/docgen.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "$schema": "../../../node_modules/@effect/docgen/schema.json", - "exclude": ["**/*.ts"] -} diff --git a/repos/effect/packages/tools/ai-codegen/package.json b/repos/effect/packages/tools/ai-codegen/package.json index 784aec34e0..689fd72e94 100644 --- a/repos/effect/packages/tools/ai-codegen/package.json +++ b/repos/effect/packages/tools/ai-codegen/package.json @@ -8,7 +8,7 @@ "homepage": "https://effect.website", "repository": { "type": "git", - "url": "https://github.com/Effect-TS/effect-smol.git", + "url": "https://github.com/Effect-TS/effect.git", "directory": "packages/tools/ai-codegen" }, "sideEffects": [], @@ -42,11 +42,8 @@ }, "scripts": { "build": "tsc -b tsconfig.json && pnpm babel", - "build:tsgo": "tsgo -b tsconfig.json && pnpm babel", "babel": "babel dist --plugins annotate-pure-calls --out-dir dist --source-maps", - "check": "tsc -b tsconfig.json", - "test": "vitest", - "coverage": "vitest --coverage" + "check": "tsc -b tsconfig.json" }, "dependencies": { "@effect/openapi-generator": "workspace:^", @@ -56,6 +53,6 @@ "yaml": "^2.9.0" }, "devDependencies": { - "@types/node": "^25.7.0" + "@types/node": "^26.1.2" } } diff --git a/repos/effect/packages/tools/ai-codegen/src/Config.ts b/repos/effect/packages/tools/ai-codegen/src/Config.ts index 5cb417aa98..166187550c 100644 --- a/repos/effect/packages/tools/ai-codegen/src/Config.ts +++ b/repos/effect/packages/tools/ai-codegen/src/Config.ts @@ -34,7 +34,7 @@ export const SpecSourceConfig = Schema.Struct({ * * **Example** (Decoding a codegen configuration) * - * ```ts + * ```ts import.meta.vitest * import * as Config from "@effect/ai-codegen/Config" * import { Schema } from "effect" * @@ -44,8 +44,7 @@ export const SpecSourceConfig = Schema.Struct({ * name: "MyClient" * }) * - * console.log(config.spec) - * // "https://example.com/openapi.json" + * config.spec // => "https://example.com/openapi.json" * ``` * * @category models @@ -179,7 +178,7 @@ export declare namespace SpecSource { * * **Example** (Creating spec sources) * - * ```ts + * ```ts import.meta.vitest * import * as Config from "@effect/ai-codegen/Config" * * // Create a URL-based source @@ -187,6 +186,9 @@ export declare namespace SpecSource { * * // Create a file-based source * const fileSource = Config.SpecSource.File("/path/to/spec.json") + * + * urlSource._tag // => "Url" + * fileSource._tag // => "File" * ``` * * @category constructors @@ -252,13 +254,16 @@ export const SpecSource = { * * **Example** (Creating a config parse error) * - * ```ts + * ```ts import.meta.vitest * import * as Config from "@effect/ai-codegen/Config" * * const error = new Config.ConfigParseError({ * path: "/path/to/codegen.json", * cause: new Error("Invalid JSON") * }) + * + * error._tag // => "ConfigParseError" + * error.path // => "/path/to/codegen.json" * ``` * * @category errors @@ -274,13 +279,16 @@ export class ConfigParseError extends Data.TaggedError("ConfigParseError")<{ * * **Example** (Creating a config not found error) * - * ```ts + * ```ts import.meta.vitest * import * as Config from "@effect/ai-codegen/Config" * * const error = new Config.ConfigNotFoundError({ * provider: "openai", * expectedPath: "/path/to/packages/ai/openai/codegen.json" * }) + * + * error._tag // => "ConfigNotFoundError" + * error.provider // => "openai" * ``` * * @category errors diff --git a/repos/effect/packages/tools/ai-codegen/src/Discovery.ts b/repos/effect/packages/tools/ai-codegen/src/Discovery.ts index 74270e536d..09c19365cd 100644 --- a/repos/effect/packages/tools/ai-codegen/src/Discovery.ts +++ b/repos/effect/packages/tools/ai-codegen/src/Discovery.ts @@ -19,13 +19,23 @@ import * as Glob from "./Glob.ts" * * **Example** (Inspecting a discovered provider) * - * ```ts + * ```ts import.meta.vitest + * import * as Config from "@effect/ai-codegen/Config" * import type * as Discovery from "@effect/ai-codegen/Discovery" * - * declare const provider: Discovery.DiscoveredProvider - * - * console.log(provider.name) // "openai" - * console.log(provider.specSource._tag) // "Url" | "File" + * const provider: Discovery.DiscoveredProvider = { + * name: "openai", + * packagePath: "packages/ai/openai", + * config: new Config.CodegenConfig({ + * spec: "https://example.com/openapi.json", + * output: "Generated.ts" + * }), + * specSource: Config.SpecSource.Url("https://example.com/openapi.json"), + * outputPath: "packages/ai/openai/src/Generated.ts" + * } + * + * provider.name // => "openai" + * provider.specSource._tag // => "Url" * ``` * * @category models @@ -42,7 +52,7 @@ export interface DiscoveredProvider { /** * Service for discovering AI provider configurations. * - * @category models + * @category services * @since 4.0.0 */ export interface ProviderDiscovery { @@ -73,13 +83,16 @@ export const ProviderDiscovery: Context.Service "DiscoveryError" + * error.message // => "Failed to parse config" * ``` * * @category errors @@ -95,13 +108,16 @@ export class DiscoveryError extends Data.TaggedError("DiscoveryError")<{ * * **Example** (Creating a provider not found error) * - * ```ts + * ```ts import.meta.vitest * import * as Discovery from "@effect/ai-codegen/Discovery" * * const error = new Discovery.ProviderNotFoundError({ * provider: "openai", * available: ["anthropic", "google"] * }) + * + * error.provider // => "openai" + * error.available // => ["anthropic", "google"] * ``` * * @category errors diff --git a/repos/effect/packages/tools/ai-codegen/src/Generator.ts b/repos/effect/packages/tools/ai-codegen/src/Generator.ts index f8c3b2803d..ed705d2e8a 100644 --- a/repos/effect/packages/tools/ai-codegen/src/Generator.ts +++ b/repos/effect/packages/tools/ai-codegen/src/Generator.ts @@ -12,6 +12,7 @@ import type * as FileSystem from "effect/FileSystem" import type * as JsonSchema from "effect/JsonSchema" import * as Layer from "effect/Layer" import * as Path_ from "effect/Path" +import * as Predicate from "effect/Predicate" import type * as Schema from "effect/Schema" import type { DiscoveredProvider } from "./Discovery.ts" @@ -20,13 +21,16 @@ import type { DiscoveredProvider } from "./Discovery.ts" * * **Example** (Creating a generation error) * - * ```ts + * ```ts import.meta.vitest * import * as Generator from "@effect/ai-codegen/Generator" * * const error = new Generator.GenerationError({ * provider: "openai", * cause: new Error("Invalid spec") * }) + * + * error._tag // => "GenerationError" + * error.provider // => "openai" * ``` * * @category errors @@ -42,13 +46,16 @@ export class GenerationError extends Data.TaggedError("GenerationError")<{ * * **Example** (Creating a patch error) * - * ```ts + * ```ts import.meta.vitest * import * as Generator from "@effect/ai-codegen/Generator" * * const error = new Generator.PatchError({ * provider: "openai", * cause: new Error("Invalid patch") * }) + * + * error._tag // => "PatchError" + * error.provider // => "openai" * ``` * * @category errors @@ -62,7 +69,7 @@ export class PatchError extends Data.TaggedError("PatchError")<{ /** * Service for generating Effect code from OpenAPI specs. * - * @category models + * @category services * @since 4.0.0 */ export interface CodeGenerator { @@ -82,6 +89,46 @@ export const CodeGenerator: Context.Service = Cont "@effect/ai-codegen/CodeGenerator" ) +const isRecord = (u: unknown): u is { readonly [x: string]: unknown } => + Predicate.isObjectOrArray(u) && !Array.isArray(u) + +/** A bare `{ "type": "string" }` branch - the open half of an open enum. */ +const isOpenStringBranch = (branch: unknown): boolean => + isRecord(branch) && branch.type === "string" && Object.keys(branch).length === 1 + +/** The value of a `{ "const": "..." }` branch, when it is a string const. */ +const constBranchValue = (branch: unknown): string | undefined => + isRecord(branch) && Predicate.isString(branch.const) ? branch.const : undefined + +/** + * Collapse an open enum's const branches into a single `enum` branch. + * + * Stainless-generated specs encode an open enum as + * `anyOf: [{ type: "string" }, { const: "a" }, { const: "b" }]` - "any string is valid, and these are + * the known values". Left as-is, each const becomes its own `Schema.Literal` member, so consumers + * cannot recover the literal union from the generated schema (the union's `Type` is just `string`). + * + * Rewriting the const branches to `anyOf: [{ type: "string" }, { enum: ["a", "b"] }]` emits + * `Schema.Union([Schema.String, Schema.Literals(["a", "b"])])` instead, which decodes any string while + * still exposing the known values as `members[1]` for autocomplete. + */ +const normalizeOpenEnum = (js: JsonSchema.JsonSchema): JsonSchema.JsonSchema => { + const anyOf = js.anyOf + if (!Array.isArray(anyOf) || anyOf.length < 2) return js + + const [head, ...tail] = anyOf + if (!isOpenStringBranch(head)) return js + + const literals: Array = [] + for (const branch of tail) { + const value = constBranchValue(branch) + if (value === undefined) return js + literals.push(value) + } + + return { ...js, anyOf: [head, { enum: literals }] } +} + /** * Layer providing the CodeGenerator service. * @@ -135,18 +182,16 @@ export const layer: Layer.Layer< const disableAdditionalProperties = provider.config.shouldDisableAdditionalProperties const exclude = excludeAnnotations ? new Set(excludeAnnotations) : undefined - const onEnter = (exclude || disableAdditionalProperties) - ? (js: JsonSchema.JsonSchema): JsonSchema.JsonSchema => { - const out = { ...js } - if (exclude) { - for (const key of exclude) delete out[key] - } - if (disableAdditionalProperties && out.type === "object") { - out.additionalProperties = false - } - return out + const onEnter = (js: JsonSchema.JsonSchema): JsonSchema.JsonSchema => { + const out = { ...normalizeOpenEnum(js) } + if (exclude) { + for (const key of exclude) delete out[key] } - : undefined + if (disableAdditionalProperties && out.type === "object") { + out.additionalProperties = false + } + return out + } return yield* openApiGen .generate(patchedSpec as unknown as Parameters[0], { diff --git a/repos/effect/packages/tools/ai-codegen/src/Glob.ts b/repos/effect/packages/tools/ai-codegen/src/Glob.ts index 08d0e4a67b..822d075c8d 100644 --- a/repos/effect/packages/tools/ai-codegen/src/Glob.ts +++ b/repos/effect/packages/tools/ai-codegen/src/Glob.ts @@ -23,7 +23,7 @@ export class GlobError extends Data.TaggedError("GlobError")<{ /** * Service for glob pattern matching. * - * @category models + * @category services * @since 4.0.0 */ export interface Glob { diff --git a/repos/effect/packages/tools/ai-codegen/src/PostProcess.ts b/repos/effect/packages/tools/ai-codegen/src/PostProcess.ts index 9ddd4524bd..f887920136 100644 --- a/repos/effect/packages/tools/ai-codegen/src/PostProcess.ts +++ b/repos/effect/packages/tools/ai-codegen/src/PostProcess.ts @@ -16,7 +16,7 @@ import * as ChildProcessSpawner from "effect/unstable/process/ChildProcessSpawne * * **Example** (Creating a post-process error) * - * ```ts + * ```ts import.meta.vitest * import * as PostProcess from "@effect/ai-codegen/PostProcess" * * const error = new PostProcess.PostProcessError({ @@ -28,6 +28,9 @@ import * as ChildProcessSpawner from "effect/unstable/process/ChildProcessSpawne * stderr: "error: some lint error", * cause: new Error("Lint failed") * }) + * + * error.step // => "lint" + * error.exitCode // => 1 * ``` * * @category errors @@ -63,7 +66,7 @@ export class PostProcessError extends Data.TaggedError("PostProcessError")<{ /** * Service for post-processing generated code. * - * @category models + * @category services * @since 4.0.0 */ export interface PostProcessor { diff --git a/repos/effect/packages/tools/ai-codegen/src/SpecFetcher.ts b/repos/effect/packages/tools/ai-codegen/src/SpecFetcher.ts index 0cc8089706..a7207ed768 100644 --- a/repos/effect/packages/tools/ai-codegen/src/SpecFetcher.ts +++ b/repos/effect/packages/tools/ai-codegen/src/SpecFetcher.ts @@ -18,7 +18,7 @@ import type { SpecSource } from "./Config.ts" * * **Example** (Creating a spec fetch error) * - * ```ts + * ```ts import.meta.vitest * import * as SpecFetcher from "@effect/ai-codegen/SpecFetcher" * * const error = new SpecFetcher.SpecFetchError({ @@ -26,6 +26,9 @@ import type { SpecSource } from "./Config.ts" * source: "https://example.com/openapi.json", * cause: new Error("Network error") * }) + * + * error._tag // => "SpecFetchError" + * error.provider // => "openai" * ``` * * @category errors @@ -40,7 +43,7 @@ export class SpecFetchError extends Data.TaggedError("SpecFetchError")<{ /** * Service for fetching OpenAPI specifications. * - * @category models + * @category services * @since 4.0.0 */ export interface SpecFetcher { diff --git a/repos/effect/packages/tools/ai-codegen/src/main.ts b/repos/effect/packages/tools/ai-codegen/src/main.ts index 714578c9d7..9157e7629c 100644 --- a/repos/effect/packages/tools/ai-codegen/src/main.ts +++ b/repos/effect/packages/tools/ai-codegen/src/main.ts @@ -229,7 +229,7 @@ const ServicesLayer = Layer.mergeAll( /** * Run the CLI. * - * @category execution + * @category running * @since 4.0.0 */ export const run = Command.run(root, { version: "0.0.0" }).pipe( diff --git a/repos/effect/packages/tools/ai-codegen/tsconfig.json b/repos/effect/packages/tools/ai-codegen/tsconfig.json index 6a3a164616..3e6c323833 100644 --- a/repos/effect/packages/tools/ai-codegen/tsconfig.json +++ b/repos/effect/packages/tools/ai-codegen/tsconfig.json @@ -1,5 +1,5 @@ { - "$schema": "http://json.schemastore.org/tsconfig", + "$schema": "https://json.schemastore.org/tsconfig", "extends": "../../../tsconfig.base.json", "include": ["src"], "references": [ diff --git a/repos/effect/packages/tools/ai-docgen/docgen.json b/repos/effect/packages/tools/ai-docgen/docgen.json deleted file mode 100644 index 4dd36175ad..0000000000 --- a/repos/effect/packages/tools/ai-docgen/docgen.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "$schema": "../../../node_modules/@effect/docgen/schema.json", - "exclude": ["**/*.ts"] -} diff --git a/repos/effect/packages/tools/ai-docgen/package.json b/repos/effect/packages/tools/ai-docgen/package.json index 3e0e88a3a4..c3c544adfb 100644 --- a/repos/effect/packages/tools/ai-docgen/package.json +++ b/repos/effect/packages/tools/ai-docgen/package.json @@ -8,7 +8,7 @@ "homepage": "https://effect.website", "repository": { "type": "git", - "url": "https://github.com/Effect-TS/effect-smol.git", + "url": "https://github.com/Effect-TS/effect.git", "directory": "packages/tools/ai-docgen" }, "sideEffects": [], @@ -42,11 +42,8 @@ }, "scripts": { "build": "tsc -b tsconfig.json && pnpm babel", - "build:tsgo": "tsgo -b tsconfig.json && pnpm babel", "babel": "babel dist --plugins annotate-pure-calls --out-dir dist --source-maps", - "check": "tsc -b tsconfig.json", - "test": "vitest", - "coverage": "vitest --coverage" + "check": "tsc -b tsconfig.json" }, "dependencies": { "@effect/platform-node": "workspace:^", @@ -55,6 +52,6 @@ "yaml": "^2.9.0" }, "devDependencies": { - "@types/node": "^25.7.0" + "@types/node": "^26.1.2" } } diff --git a/repos/effect/packages/tools/ai-docgen/src/main.ts b/repos/effect/packages/tools/ai-docgen/src/main.ts index 1824c58424..32c8544820 100755 --- a/repos/effect/packages/tools/ai-docgen/src/main.ts +++ b/repos/effect/packages/tools/ai-docgen/src/main.ts @@ -60,7 +60,9 @@ Command.make("effect-ai-docgen", { directory, output, watch }).pipe( ) const directoryToMarkdown = Effect.fn("directoryToMarkdown")( - function*(directory): Effect.fn.Return { + function*( + directory: string + ): Effect.fn.Return { const pathService = yield* Path.Path const fs = yield* FileSystem.FileSystem diff --git a/repos/effect/packages/tools/ai-docgen/tsconfig.json b/repos/effect/packages/tools/ai-docgen/tsconfig.json index aa14dc8ca2..7b35a8952e 100644 --- a/repos/effect/packages/tools/ai-docgen/tsconfig.json +++ b/repos/effect/packages/tools/ai-docgen/tsconfig.json @@ -1,5 +1,5 @@ { - "$schema": "http://json.schemastore.org/tsconfig", + "$schema": "https://json.schemastore.org/tsconfig", "extends": "../../../tsconfig.base.json", "include": ["src"], "references": [ diff --git a/repos/effect/packages/tools/api-diff/README.md b/repos/effect/packages/tools/api-diff/README.md new file mode 100644 index 0000000000..b4841ec8ca --- /dev/null +++ b/repos/effect/packages/tools/api-diff/README.md @@ -0,0 +1,51 @@ +# API Diff + +`@effect/api-diff` compares the consumer-visible TypeScript declarations emitted +by two repository revisions. Its JSON output is canonical; the Markdown report +is intended for migration review and does not make semantic-version +compatibility claims. + +Run a complete comparison from the repository root: + +```sh +pnpm api-diff \ + --base-ref v3 \ + --head-ref origin/main \ + --output tmp/api-diff/run +``` + +Both refs are required and are resolved to commit SHAs before work starts. The +tool builds detached disposable worktrees with each branch's native build, +discovers every public package entrypoint independently, extracts both snapshots +with one pinned TypeScript compiler API, and caches successful snapshots by +commit and compiler. + +The command writes: + +- `base.snapshot.json` +- `head.snapshot.json` +- `diff.json` +- `report.md` + +Generate the agent-facing v3-to-v4 migration reference from a fresh diff and +the YAML files in `migration/annotations`: + +```sh +pnpm api-diff --write-doc migration/v3-to-v4.md +``` + +The document command defaults to refs `v3` and `main`, records their resolved +SHAs, preserves the existing import map sections, and replaces the API +reference in place. It does not write `diff.json` unless `--output` is also +provided. + +List missing annotations, grouped by v3 module, and exit non-zero when any are +missing: + +```sh +pnpm api-diff --check +``` + +Unmatched APIs are reported as removals from the base and additions in the +head. Likely replacements are reported separately for review, including +cross-module moves and the paired type/value facets of class-style APIs. diff --git a/repos/effect/packages/tools/api-diff/package.json b/repos/effect/packages/tools/api-diff/package.json new file mode 100644 index 0000000000..3062e861cc --- /dev/null +++ b/repos/effect/packages/tools/api-diff/package.json @@ -0,0 +1,30 @@ +{ + "name": "@effect/api-diff", + "version": "0.0.0", + "private": true, + "homepage": "https://effect.website", + "type": "module", + "exports": { + ".": "./src/Cli.ts", + "./*": "./src/*.ts", + "./bin": null + }, + "bin": { + "effect-api-diff": "./src/bin.ts" + }, + "scripts": { + "check": "tsc -b tsconfig.json" + }, + "dependencies": { + "@effect/platform-node": "workspace:^", + "effect": "workspace:^", + "yaml": "^2.9.0" + }, + "devDependencies": { + "@effect/vitest": "workspace:^", + "@types/node": "^26.1.2", + "typescript": "^6.0.3", + "typescript-compiler": "npm:typescript@6.0.3", + "vitest": "4.1.10" + } +} diff --git a/repos/effect/packages/tools/api-diff/src/Annotations.ts b/repos/effect/packages/tools/api-diff/src/Annotations.ts new file mode 100644 index 0000000000..86bcbc096e --- /dev/null +++ b/repos/effect/packages/tools/api-diff/src/Annotations.ts @@ -0,0 +1,72 @@ +import * as Effect from "effect/Effect" +import * as FileSystem from "effect/FileSystem" +import * as Path from "effect/Path" +import * as Yaml from "yaml" +import { ApiDiffError, isApiDiffError } from "./Error.ts" + +export interface MigrationAnnotation { + readonly replacement: string + readonly note: string + readonly example?: string | undefined +} + +const compareStrings = (left: string, right: string): number => left < right ? -1 : left > right ? 1 : 0 + +const parseAnnotation = (id: string, value: unknown, file: string): MigrationAnnotation => { + if ( + typeof value !== "object" || value === null || + typeof Reflect.get(value, "replacement") !== "string" || + typeof Reflect.get(value, "note") !== "string" || + (Reflect.get(value, "example") !== undefined && typeof Reflect.get(value, "example") !== "string") + ) { + throw new Error(`Invalid annotation for ${id} in ${file}`) + } + return { + replacement: Reflect.get(value, "replacement"), + note: Reflect.get(value, "note"), + ...(Reflect.get(value, "example") === undefined ? {} : { example: Reflect.get(value, "example") }) + } +} + +const loadAnnotationsInternal = Effect.fnUntraced(function*(directory: string) { + const fs = yield* FileSystem.FileSystem + const path = yield* Path.Path + if (!(yield* fs.exists(directory))) { + return new Map() + } + + const annotations = new Map() + const files = (yield* fs.readDirectory(directory)) + .filter((file) => file.endsWith(".yaml") || file.endsWith(".yml")) + .sort(compareStrings) + for (const file of files) { + const source = yield* fs.readFileString(path.join(directory, file)) + const document = yield* Effect.try({ + try: () => Yaml.parse(source) as unknown, + catch: (cause) => new ApiDiffError({ message: `Could not parse annotation file ${file}`, cause }) + }) + if (typeof document !== "object" || document === null || Array.isArray(document)) { + return yield* new ApiDiffError({ message: `Annotation file ${file} must contain an object` }) + } + for (const [id, value] of Object.entries(document).sort(([left], [right]) => compareStrings(left, right))) { + if (annotations.has(id)) { + return yield* new ApiDiffError({ message: `Duplicate annotation id ${id}` }) + } + const annotation = yield* Effect.try({ + try: () => parseAnnotation(id, value, file), + catch: (cause) => new ApiDiffError({ message: String(cause), cause }) + }) + annotations.set(id, annotation) + } + } + return annotations +}) + +export const loadAnnotations = (directory: string) => + loadAnnotationsInternal(directory).pipe( + Effect.mapError((cause) => + isApiDiffError(cause) + ? cause + : new ApiDiffError({ message: `Could not load annotations from ${directory}`, cause }) + ) + ) diff --git a/repos/effect/packages/tools/api-diff/src/ApiDiff.ts b/repos/effect/packages/tools/api-diff/src/ApiDiff.ts new file mode 100644 index 0000000000..3a05ebb30e --- /dev/null +++ b/repos/effect/packages/tools/api-diff/src/ApiDiff.ts @@ -0,0 +1,170 @@ +import * as Console from "effect/Console" +import * as Context from "effect/Context" +import * as Effect from "effect/Effect" +import * as FileSystem from "effect/FileSystem" +import * as Layer from "effect/Layer" +import * as Path from "effect/Path" +import { loadAnnotations } from "./Annotations.ts" +import { diffSnapshots } from "./Diff.ts" +import { ApiDiffError, isApiDiffError } from "./Error.ts" +import { prettyJson } from "./Json.ts" +import { + extractImportMapSections, + markdownSafetyIssues, + renderMigrationDocument, + renderMissingAnnotations, + unannotatedApiIds, + unannotatedModuleIds +} from "./MigrationDoc.ts" +import { renderMarkdownReport } from "./Report.ts" +import { Worktrees } from "./Worktrees.ts" + +export interface ApiDiffOptions { + readonly baseRef: string + readonly headRef: string + readonly output?: string | undefined + readonly writeDoc?: string | undefined + readonly check: boolean +} + +export class ApiDiff extends Context.Service Effect.Effect +}>()("@effect/api-diff/ApiDiff") { + static readonly layerNoDependencies = Layer.effect( + ApiDiff, + Effect.gen(function*() { + const fs = yield* FileSystem.FileSystem + const path = yield* Path.Path + const worktrees = yield* Worktrees + + const absolute = (repoRoot: string, location: string): string => + path.isAbsolute(location) ? location : path.resolve(repoRoot, location) + + const findRepoRoot = Effect.fnUntraced(function*() { + let current = path.resolve() + while (true) { + if ( + (yield* fs.exists(path.join(current, ".git"))) || + (yield* fs.exists(path.join(current, "pnpm-workspace.yaml"))) + ) { + return current + } + const parent = path.dirname(current) + if (parent === current) { + return yield* new ApiDiffError({ + message: `Could not locate repository root from ${path.resolve()}` + }) + } + current = parent + } + }) + + const runInternal = Effect.fnUntraced(function*(options: ApiDiffOptions) { + const repoRoot = yield* findRepoRoot() + if (options.output === undefined && options.writeDoc === undefined && !options.check) { + return yield* new ApiDiffError({ message: "Specify --output, --write-doc, or --check" }) + } + + const baseSha = yield* worktrees.resolveRef(repoRoot, options.baseRef) + const headSha = yield* (options.headRef === "main" + ? worktrees.resolveRef(repoRoot, "origin/main").pipe( + Effect.catch(() => worktrees.resolveRef(repoRoot, options.headRef)) + ) + : worktrees.resolveRef(repoRoot, options.headRef)) + const toolRoot = path.join(repoRoot, "tmp", "api-diff") + const cacheRoot = path.join(toolRoot, "cache") + const worktreesRoot = path.join(toolRoot, "worktrees") + yield* Console.log( + `Base ${options.baseRef}: ${baseSha}\nHead ${options.headRef}: ${headSha}` + ) + const base = yield* worktrees.prepareSnapshot({ + repoRoot, + cacheRoot, + worktreesRoot, + name: "base", + ref: options.baseRef, + sha: baseSha + }) + const head = yield* worktrees.prepareSnapshot({ + repoRoot, + cacheRoot, + worktreesRoot, + name: "head", + ref: options.headRef, + sha: headSha + }) + const diff = diffSnapshots(base, head) + + if (options.output !== undefined) { + const output = absolute(repoRoot, options.output) + yield* fs.makeDirectory(output, { recursive: true }) + yield* fs.writeFileString(path.join(output, "base.snapshot.json"), prettyJson(base)) + yield* fs.writeFileString(path.join(output, "head.snapshot.json"), prettyJson(head)) + yield* fs.writeFileString(path.join(output, "diff.json"), prettyJson(diff)) + yield* fs.writeFileString(path.join(output, "report.md"), renderMarkdownReport(diff)) + yield* Console.log(`Wrote ${path.relative(repoRoot, output)} (${diff.changes.length} changes)`) + } + + if (options.writeDoc !== undefined || options.check) { + const annotations = yield* loadAnnotations(path.join(repoRoot, "migration", "annotations")).pipe( + Effect.provideService(FileSystem.FileSystem, fs), + Effect.provideService(Path.Path, path) + ) + const documentPath = options.writeDoc === undefined + ? path.join(repoRoot, "migration", "v3-to-v4.md") + : absolute(repoRoot, options.writeDoc) + const existing = (yield* fs.exists(documentPath)) ? yield* fs.readFileString(documentPath) : "" + const importMapSections = extractImportMapSections(existing) + let documentForCheck = existing + if (options.writeDoc !== undefined) { + const document = renderMigrationDocument(diff, annotations, importMapSections) + const unsafeMarkdown = markdownSafetyIssues(document) + if (unsafeMarkdown.length > 0) { + return yield* new ApiDiffError({ + message: `Generated migration document has unsafe Markdown:\n${unsafeMarkdown.join("\n")}` + }) + } + documentForCheck = document + yield* fs.makeDirectory(path.dirname(documentPath), { recursive: true }) + yield* fs.writeFileString(documentPath, document) + yield* Console.log(`Wrote ${path.relative(repoRoot, documentPath)} (${diff.changes.length} changes)`) + } + if (options.check) { + const unsafeMarkdown = markdownSafetyIssues(documentForCheck) + if (unsafeMarkdown.length > 0) { + return yield* new ApiDiffError({ + message: `Migration document has unsafe Markdown:\n${unsafeMarkdown.join("\n")}` + }) + } + const missingApis = unannotatedApiIds(diff, annotations, importMapSections) + const missingModules = unannotatedModuleIds(diff, annotations, importMapSections) + yield* Console.log(renderMissingAnnotations(diff, annotations, importMapSections).trimEnd()) + if (missingApis.size > 0 || missingModules.length > 0) { + return yield* new ApiDiffError({ + message: `${missingApis.size} API groups and ${missingModules.length} modules need migration guidance` + }) + } + } + } + }) + + const run = (options: ApiDiffOptions): Effect.Effect => + runInternal(options).pipe( + Effect.mapError((cause) => + isApiDiffError(cause) + ? cause + : new ApiDiffError({ + message: "API diff failed", + cause + }) + ) + ) + + return ApiDiff.of({ run }) + }) + ) + + static readonly layer = this.layerNoDependencies.pipe( + Layer.provide(Worktrees.layer) + ) +} diff --git a/repos/effect/packages/tools/api-diff/src/Cli.ts b/repos/effect/packages/tools/api-diff/src/Cli.ts new file mode 100644 index 0000000000..a220035fd3 --- /dev/null +++ b/repos/effect/packages/tools/api-diff/src/Cli.ts @@ -0,0 +1,61 @@ +import * as Effect from "effect/Effect" +import * as Option from "effect/Option" +import * as Command from "effect/unstable/cli/Command" +import * as Flag from "effect/unstable/cli/Flag" +import { ApiDiff, type ApiDiffOptions } from "./ApiDiff.ts" + +const baseRef = Flag.string("base-ref").pipe( + Flag.withDescription("Explicit base Git ref"), + Flag.withDefault("v3") +) + +const headRef = Flag.string("head-ref").pipe( + Flag.withDescription("Explicit head Git ref"), + Flag.withDefault("main") +) + +const output = Flag.string("output").pipe( + Flag.withMetavar("DIRECTORY"), + Flag.withDescription("Report output directory"), + Flag.optional +) + +const writeDoc = Flag.string("write-doc").pipe( + Flag.withMetavar("FILE"), + Flag.withDescription("Write the annotation-driven migration reference"), + Flag.optional +) + +const check = Flag.boolean("check").pipe( + Flag.withDescription("List APIs without migration annotations and fail if any remain") +) + +const runApiDiff = Effect.fnUntraced(function*(options: { + readonly baseRef: string + readonly headRef: string + readonly output: Option.Option + readonly writeDoc: Option.Option + readonly check: boolean +}) { + const apiDiff = yield* ApiDiff + yield* apiDiff.run( + { + baseRef: options.baseRef, + headRef: options.headRef, + output: Option.getOrUndefined(options.output), + writeDoc: Option.getOrUndefined(options.writeDoc), + check: options.check + } satisfies ApiDiffOptions + ) +}) + +export const cli = Command.make("api-diff", { + baseRef, + headRef, + output, + writeDoc, + check +}).pipe( + Command.withDescription("Compare the consumer-visible TypeScript API of two repository revisions"), + Command.withHandler(runApiDiff) +) diff --git a/repos/effect/packages/tools/api-diff/src/Diff.ts b/repos/effect/packages/tools/api-diff/src/Diff.ts new file mode 100644 index 0000000000..acabe93d07 --- /dev/null +++ b/repos/effect/packages/tools/api-diff/src/Diff.ts @@ -0,0 +1,504 @@ +import { fingerprint, stableJson } from "./Json.ts" +import type { ApiChange, ApiDiff, ApiEntity, ApiSnapshot, ChangeClassification, DeclarationModel } from "./Model.ts" + +interface Match { + readonly base: ApiEntity + readonly head: ApiEntity + readonly confidence: number + readonly authoritative: boolean + readonly note?: string +} + +interface ApiGroup { + readonly module: string + readonly path: ReadonlyArray + readonly entities: ReadonlyArray +} + +const pathName = (entity: ApiEntity): string => entity.path.at(-1) ?? "" + +const levenshtein = (left: string, right: string): number => { + const row = Array.from({ length: right.length + 1 }, (_, index) => index) + for (let leftIndex = 1; leftIndex <= left.length; leftIndex++) { + let previous = row[0]! + row[0] = leftIndex + for (let rightIndex = 1; rightIndex <= right.length; rightIndex++) { + const above = row[rightIndex]! + const diagonal = previous + previous = above + row[rightIndex] = left[leftIndex - 1] === right[rightIndex - 1] + ? diagonal + : Math.min(diagonal, above, row[rightIndex - 1]!) + 1 + } + } + return row[right.length]! +} + +const nameSimilarity = (left: string, right: string): number => { + const length = Math.max(left.length, right.length) + return length === 0 ? 1 : 1 - levenshtein(left.toLowerCase(), right.toLowerCase()) / length +} + +const entityFeatureCache = new WeakMap>() + +const entityFeatures = (entity: ApiEntity): ReadonlySet => { + const cached = entityFeatureCache.get(entity) + if (cached !== undefined) { + return cached + } + const features = new Set() + for (const declaration of entity.declarations) { + features.add(`declaration:${declaration.kind}`) + for (const member of declaration.members ?? []) { + features.add(`member:${member.kind}:${member.name}`) + } + } + entityFeatureCache.set(entity, features) + return features +} + +const setSimilarity = (left: ReadonlySet, right: ReadonlySet): number => { + if (left.size === 0 && right.size === 0) { + return 1 + } + let shared = 0 + for (const value of left) { + if (right.has(value)) { + shared++ + } + } + return shared / (left.size + right.size - shared) +} + +const documentationTokenCache = new WeakMap>() + +const documentationTokens = (entity: ApiEntity): ReadonlySet => { + const cached = documentationTokenCache.get(entity) + if (cached !== undefined) { + return cached + } + const tokens = new Set( + (entity.documentation.summary ?? "") + .toLowerCase() + .match(/[a-z][a-z0-9]+/g) + ?.filter((token) => token.length >= 4) ?? [] + ) + documentationTokenCache.set(entity, tokens) + return tokens +} + +const groupEntities = (entities: Iterable): ReadonlyArray => { + const groups = new Map>() + for (const entity of entities) { + const key = `${entity.module}#${entity.path.join(".")}` + const group = groups.get(key) + if (group === undefined) { + groups.set(key, [entity]) + } else { + group.push(entity) + } + } + return [...groups.values()].map((group) => ({ + module: group[0]!.module, + path: group[0]!.path, + entities: group.sort((left, right) => left.bucket.localeCompare(right.bucket)) + })) +} + +const groupName = (group: ApiGroup): string => group.path.at(-1) ?? "" + +const groupSimilarity = ( + base: ApiGroup, + head: ApiGroup +): number => { + const facets = base.entities.flatMap((baseEntity) => { + const headEntity = head.entities.find((candidate) => candidate.bucket === baseEntity.bucket) + return headEntity === undefined ? [] : [{ base: baseEntity, head: headEntity }] + }) + if (facets.length === 0) { + return 0 + } + const sameKind = facets.filter(({ base, head }) => base.declarationKind === head.declarationKind).length / + facets.length + const sameFingerprint = facets.filter(({ base, head }) => base.fingerprint === head.fingerprint).length / + facets.length + const structure = facets.reduce( + (total, { base, head }) => total + setSimilarity(entityFeatures(base), entityFeatures(head)), + 0 + ) / facets.length + const documentation = facets.reduce( + (total, { base, head }) => total + setSimilarity(documentationTokens(base), documentationTokens(head)), + 0 + ) / facets.length + const sameCategory = facets.some(({ base, head }) => + base.documentation.category !== undefined && + base.documentation.category === head.documentation.category + ) + const headModuleName = head.module.split("/").at(-1)?.toLowerCase() + const mentionsHeadModule = headModuleName !== undefined && + facets.some(({ base }) => documentationTokens(base).has(headModuleName)) + const moduleScore = base.module === head.module ? 0.25 : 0 + const packageScore = base.entities[0]!.packageName === head.entities[0]!.packageName ? 0.05 : 0 + return Math.min( + 0.99, + 0.2 * nameSimilarity(groupName(base), groupName(head)) + + (groupName(base) === groupName(head) ? 0.15 : 0) + + moduleScore + + packageScore + + 0.4 * sameFingerprint + + 0.15 * sameKind + + 0.15 * structure + + 0.025 * documentation + + (sameCategory ? 0.05 : 0) + + (mentionsHeadModule ? 0.075 : 0) + ) +} + +const changeId = ( + classification: ChangeClassification, + base?: ApiEntity, + head?: ApiEntity, + suffix = "" +): string => `change-${fingerprint([classification, base?.id ?? null, head?.id ?? null, suffix]).slice(0, 16)}` + +const makeChange = ( + classification: ChangeClassification, + match: Partial, + delta?: unknown, + suffix = "" +): ApiChange => ({ + id: changeId(classification, match.base, match.head, suffix), + classification, + confidence: match.confidence ?? 1, + baseApiId: match.base?.id, + headApiId: match.head?.id, + before: match.base?.displaySignature, + after: match.head?.displaySignature, + delta, + baseSource: match.base?.source, + headSource: match.head?.source, + reviewNotes: match.note, + authoritative: match.authoritative ?? true +}) + +const declarationHash = (value: unknown): string => fingerprint(value) + +const memberMap = (declarations: ReadonlyArray): Map => + new Map( + declarations.flatMap((declaration) => declaration.members ?? []) + .map((member) => [`${member.kind}:${member.name}`, member]) + ) + +const classifyStructure = (match: Match): ReadonlyArray => { + const base = match.base + const head = match.head + const changes: Array = [] + if (base.bucket !== head.bucket) { + changes.push(makeChange("bucket-changed", match, { before: base.bucket, after: head.bucket })) + } + if (base.declarationKind !== head.declarationKind) { + changes.push(makeChange("declaration-kind-changed", match, { + before: base.declarationKind, + after: head.declarationKind + })) + } + + const baseDeclarations = base.declarations + const headDeclarations = head.declarations + const baseSignatures = baseDeclarations.filter((declaration) => + declaration.kind === "function" || declaration.kind === "method" + ) + const headSignatures = headDeclarations.filter((declaration) => + declaration.kind === "function" || declaration.kind === "method" + ) + if (headSignatures.length > baseSignatures.length) { + changes.push(makeChange("overload-added", match, { + before: baseSignatures.length, + after: headSignatures.length + })) + } else if (headSignatures.length < baseSignatures.length) { + changes.push(makeChange("overload-removed", match, { + before: baseSignatures.length, + after: headSignatures.length + })) + } else if ( + baseSignatures.length > 1 && + stableJson(baseSignatures.map(declarationHash).sort()) === stableJson(headSignatures.map(declarationHash).sort()) && + stableJson(baseSignatures.map(declarationHash)) !== stableJson(headSignatures.map(declarationHash)) + ) { + changes.push(makeChange("overload-reordered", match)) + } + + const comparedSignatures = Math.min(baseSignatures.length, headSignatures.length) + for (let index = 0; index < comparedSignatures; index++) { + const before = baseSignatures[index]! + const after = headSignatures[index]! + const beforeParameters = before.parameters ?? [] + const afterParameters = after.parameters ?? [] + if (afterParameters.length > beforeParameters.length) { + changes.push(makeChange("parameter-added", match, { + overload: index, + before: beforeParameters, + after: afterParameters + }, String(index))) + } else if (afterParameters.length < beforeParameters.length) { + changes.push(makeChange("parameter-removed", match, { + overload: index, + before: beforeParameters, + after: afterParameters + }, String(index))) + } else if ( + stableJson(beforeParameters.map((parameter) => parameter.name).sort()) === + stableJson(afterParameters.map((parameter) => parameter.name).sort()) && + stableJson(beforeParameters.map((parameter) => parameter.name)) !== + stableJson(afterParameters.map((parameter) => parameter.name)) + ) { + changes.push(makeChange("parameter-reordered", match, { + overload: index, + before: beforeParameters, + after: afterParameters + }, String(index))) + } else if (stableJson(beforeParameters) !== stableJson(afterParameters)) { + changes.push(makeChange("parameter-changed", match, { + overload: index, + before: beforeParameters, + after: afterParameters + }, String(index))) + } + if (stableJson(before.returnType) !== stableJson(after.returnType)) { + changes.push(makeChange("return-type-changed", match, { + overload: index, + before: before.returnType, + after: after.returnType + }, String(index))) + } + if (stableJson(before.typeParameters) !== stableJson(after.typeParameters)) { + changes.push(makeChange("generic-parameter-changed", match, { + overload: index, + before: before.typeParameters, + after: after.typeParameters + }, String(index))) + } + } + + const baseMembers = memberMap(baseDeclarations) + const headMembers = memberMap(headDeclarations) + for (const [key, member] of baseMembers) { + const next = headMembers.get(key) + if (next === undefined) { + changes.push(makeChange("member-removed", match, { member }, key)) + } else if (stableJson(member) !== stableJson(next)) { + changes.push(makeChange("member-changed", match, { before: member, after: next }, key)) + } + } + for (const [key, member] of headMembers) { + if (!baseMembers.has(key)) { + changes.push(makeChange("member-added", match, { member }, key)) + } + } + + const baseHeritage = baseDeclarations.flatMap((declaration) => declaration.heritage ?? []) + const headHeritage = headDeclarations.flatMap((declaration) => declaration.heritage ?? []) + if (stableJson(baseHeritage) !== stableJson(headHeritage)) { + changes.push(makeChange("heritage-changed", match, { before: baseHeritage, after: headHeritage })) + } + + const baseTypes = baseDeclarations.map((declaration) => declaration.type) + const headTypes = headDeclarations.map((declaration) => declaration.type) + const baseKinds = new Set(baseTypes.flatMap((type) => type?.kind === undefined ? [] : [type.kind])) + const headKinds = new Set(headTypes.flatMap((type) => type?.kind === undefined ? [] : [type.kind])) + if ((baseKinds.has("union") || headKinds.has("union")) && stableJson(baseTypes) !== stableJson(headTypes)) { + changes.push(makeChange("union-member-changed", match, { before: baseTypes, after: headTypes })) + } + if ( + (baseKinds.has("intersection") || headKinds.has("intersection")) && + stableJson(baseTypes) !== stableJson(headTypes) + ) { + changes.push(makeChange("intersection-member-changed", match, { before: baseTypes, after: headTypes })) + } + if (stableJson(base.documentation) !== stableJson(head.documentation)) { + changes.push(makeChange("documentation-changed", match, { + before: base.documentation, + after: head.documentation + })) + } + if (base.fingerprint !== head.fingerprint && changes.length === 0) { + changes.push(makeChange("structural-change", match, { + before: base.declarations, + after: head.declarations + })) + } + return changes +} + +const movementChange = (match: Match): ApiChange | undefined => { + const beforeName = pathName(match.base) + const afterName = pathName(match.head) + if (beforeName !== afterName) { + return makeChange("api-renamed", match) + } + if (match.base.module !== match.head.module || match.base.path.join(".") !== match.head.path.join(".")) { + return makeChange("api-moved", match) + } + return undefined +} + +const moduleChanges = (base: ApiSnapshot, head: ApiSnapshot): ReadonlyArray => { + const changes: Array = [] + const basePackages = new Set(base.packages) + const headPackages = new Set(head.packages) + for (const packageName of basePackages) { + if (!headPackages.has(packageName)) { + changes.push({ + id: `change-${fingerprint(["package-removed", packageName]).slice(0, 16)}`, + classification: "package-removed", + confidence: 1, + delta: { packageName }, + authoritative: true + }) + } + } + for (const packageName of headPackages) { + if (!basePackages.has(packageName)) { + changes.push({ + id: `change-${fingerprint(["package-added", packageName]).slice(0, 16)}`, + classification: "package-added", + confidence: 1, + delta: { packageName }, + authoritative: true + }) + } + } + const baseModules = new Set(base.entrypoints.map((entrypoint) => entrypoint.module)) + const headModules = new Set(head.entrypoints.map((entrypoint) => entrypoint.module)) + for (const module of baseModules) { + if (!headModules.has(module)) { + changes.push({ + id: `change-${fingerprint(["module-removed", module]).slice(0, 16)}`, + classification: "module-removed", + confidence: 1, + delta: { from: module, to: [] }, + authoritative: true + }) + } + } + for (const module of headModules) { + if (!baseModules.has(module)) { + changes.push({ + id: `change-${fingerprint(["module-added", module]).slice(0, 16)}`, + classification: "module-added", + confidence: 1, + delta: { to: [module] }, + authoritative: true + }) + } + } + return changes +} + +export const diffSnapshots = (base: ApiSnapshot, head: ApiSnapshot): ApiDiff => { + const unmatchedBase = new Map(base.entities.map((entity) => [entity.id, entity])) + const unmatchedHead = new Map(head.entities.map((entity) => [entity.id, entity])) + const matches: Array = [] + const suggestedMatches: Array = [] + + const addMatch = ( + baseEntity: ApiEntity | undefined, + headEntity: ApiEntity | undefined, + details: Omit + ): boolean => { + if ( + baseEntity === undefined || headEntity === undefined || + !unmatchedBase.has(baseEntity.id) || !unmatchedHead.has(headEntity.id) + ) { + return false + } + unmatchedBase.delete(baseEntity.id) + unmatchedHead.delete(headEntity.id) + matches.push({ base: baseEntity, head: headEntity, ...details }) + return true + } + + for (const baseEntity of unmatchedBase.values()) { + addMatch(baseEntity, unmatchedHead.get(baseEntity.id), { + confidence: 1, + authoritative: true + }) + } + + const headGroups = groupEntities(unmatchedHead.values()) + for (const baseGroup of groupEntities(unmatchedBase.values())) { + const ranked = headGroups + .filter((group) => + groupName(group) === groupName(baseGroup) || + group.module === baseGroup.module || + baseGroup.entities.some((baseEntity) => + group.entities.some((headEntity) => + baseEntity.bucket === headEntity.bucket && + baseEntity.fingerprint === headEntity.fingerprint + ) + ) + ) + .map((group) => ({ group, score: groupSimilarity(baseGroup, group) })) + .filter(({ score }) => score > 0) + .sort((left, right) => + right.score - left.score || + left.group.module.localeCompare(right.group.module) || + left.group.path.join(".").localeCompare(right.group.path.join(".")) + ) + const best = ranked[0] + const next = ranked[1] + if (best === undefined || best.score < 0.5 || (next !== undefined && best.score - next.score < 0.05)) { + continue + } + for (const baseEntity of baseGroup.entities) { + const headEntity = best.group.entities.find((candidate) => candidate.bucket === baseEntity.bucket) + if (headEntity !== undefined) { + suggestedMatches.push({ + base: baseEntity, + head: headEntity, + confidence: Number(best.score.toFixed(3)), + authoritative: false, + note: "Suggested replacement for removed API; requires review" + }) + } + } + } + + const changes = [...moduleChanges(base, head)] + for (const match of matches) { + changes.push(...classifyStructure(match)) + } + for (const match of suggestedMatches) { + const movement = movementChange(match) + if (movement !== undefined) { + changes.push(movement) + } + } + for (const entity of unmatchedBase.values()) { + changes.push(makeChange("api-removed", { + base: entity, + confidence: 1, + authoritative: true + })) + } + for (const entity of unmatchedHead.values()) { + changes.push(makeChange("api-added", { + head: entity, + confidence: 1, + authoritative: true + })) + } + + return { + version: 1, + base: { ref: base.ref, sha: base.sha }, + head: { ref: head.ref, sha: head.sha }, + changes: changes.sort((left, right) => + left.classification.localeCompare(right.classification) || + (left.baseApiId ?? "").localeCompare(right.baseApiId ?? "") || + (left.headApiId ?? "").localeCompare(right.headApiId ?? "") || + left.id.localeCompare(right.id) + ) + } +} diff --git a/repos/effect/packages/tools/api-diff/src/Discovery.ts b/repos/effect/packages/tools/api-diff/src/Discovery.ts new file mode 100644 index 0000000000..15cba603d9 --- /dev/null +++ b/repos/effect/packages/tools/api-diff/src/Discovery.ts @@ -0,0 +1,242 @@ +import * as Context from "effect/Context" +import * as Effect from "effect/Effect" +import * as FileSystem from "effect/FileSystem" +import * as Layer from "effect/Layer" +import * as Path from "effect/Path" +import type * as PlatformError from "effect/PlatformError" +import * as Schema from "effect/Schema" +import { ApiDiffError } from "./Error.ts" +import type { Entrypoint } from "./Model.ts" + +const PackageManifest = Schema.Struct({ + name: Schema.optional(Schema.String), + private: Schema.optional(Schema.Boolean), + exports: Schema.optional(Schema.Unknown), + publishConfig: Schema.optional(Schema.Struct({ + exports: Schema.optional(Schema.Unknown) + })) +}) + +type PackageManifest = typeof PackageManifest.Type + +interface PackageInfo { + readonly name: string + readonly root: string + readonly manifest: PackageManifest + readonly exports: unknown +} + +export interface DiscoveryResult { + readonly entrypoints: ReadonlyArray + readonly missing: ReadonlyArray +} + +const decodePackageManifest = Schema.decodeUnknownEffect(Schema.fromJsonString(PackageManifest)) + +const exportedTarget = (value: unknown): string | null | undefined => { + if (value === null || typeof value === "string") { + return value + } + if (typeof value !== "object" || value === null) { + return undefined + } + for (const condition of ["types", "import", "default", "node", "browser"]) { + const target = exportedTarget(Reflect.get(value, condition)) + if (target !== undefined) { + return target + } + } + return undefined +} + +const matchPattern = (pattern: string, value: string): string | undefined => { + const star = pattern.indexOf("*") + if (star === -1) { + return pattern === value ? "" : undefined + } + const prefix = pattern.slice(0, star) + const suffix = pattern.slice(star + 1) + return value.startsWith(prefix) && value.endsWith(suffix) + ? value.slice(prefix.length, value.length - suffix.length) + : undefined +} + +export class Discovery extends Context.Service + ) => Effect.Effect +}>()("@effect/api-diff/Discovery") { + static readonly layer = Layer.effect( + Discovery, + Effect.gen(function*() { + const fs = yield* FileSystem.FileSystem + const path = yield* Path.Path + + const readManifest = Effect.fnUntraced(function*(location: string) { + const source = yield* fs.readFileString(location) + return yield* decodePackageManifest(source) + }) + + const walk = Effect.fnUntraced(function*( + root: string, + predicate: (location: string) => boolean + ) { + const output: Array = [] + const visit: (directory: string) => Effect.Effect = Effect.fnUntraced( + function*(directory: string) { + const entries = yield* fs.readDirectory(directory) + for (const entry of entries) { + if (entry === "node_modules" || entry === ".git") { + continue + } + const location = path.join(directory, entry) + const info = yield* fs.stat(location) + if (info.type === "Directory") { + yield* visit(location) + } else if (predicate(location)) { + output.push(location) + } + } + } + ) + if (yield* fs.exists(root)) { + yield* visit(root) + } + return output.sort() + }) + + const packagesIn = Effect.fnUntraced(function*(repoRoot: string) { + const manifestPaths = yield* walk( + path.join(repoRoot, "packages"), + (location) => location.endsWith(`${path.sep}package.json`) + ) + const packages: Array = [] + for (const manifestPath of manifestPaths) { + const sourceRoot = path.dirname(manifestPath) + if ( + path.basename(sourceRoot) === "dist" && + (yield* fs.exists(path.join(path.dirname(sourceRoot), "package.json"))) + ) { + continue + } + const packedPath = path.join(sourceRoot, "dist", "package.json") + const packed = yield* fs.exists(packedPath) + const manifest = yield* readManifest(packed ? packedPath : manifestPath) + if (manifest.name === undefined || manifest.private === true) { + continue + } + packages.push({ + name: manifest.name, + root: packed ? path.join(sourceRoot, "dist") : sourceRoot, + exports: packed ? manifest.exports : (manifest.publishConfig?.exports ?? manifest.exports), + manifest + }) + } + return packages.sort((left, right) => right.name.length - left.name.length) + }) + + const targetToDeclaration = (packageInfo: PackageInfo, target: string): string | undefined => { + const relativeTarget = target.replace(/^\.\//, "") + const declaration = relativeTarget.endsWith(".d.ts") + ? relativeTarget + : relativeTarget.replace(/\.(?:mjs|cjs|js|mts|cts|ts)$/, ".d.ts") + return declaration.endsWith(".d.ts") ? path.resolve(packageInfo.root, declaration) : undefined + } + + const expandPattern = Effect.fnUntraced(function*( + packageInfo: PackageInfo, + keyPattern: string, + targetPattern: string, + requested: ReadonlySet | undefined, + exportsMap: Readonly> + ) { + const candidates = yield* walk(packageInfo.root, (location) => location.endsWith(".d.ts")) + const targetNormalized = targetPattern.endsWith(".d.ts") + ? targetPattern + : targetPattern.replace(/\.(?:mjs|cjs|js|mts|cts|ts)$/, ".d.ts") + const entries: Array = [] + for (const declarationFile of candidates) { + const relativeFile = `./${path.relative(packageInfo.root, declarationFile).split(path.sep).join("/")}` + const capture = matchPattern(targetNormalized, relativeFile) + if (capture === undefined) { + continue + } + const key = keyPattern.replace("*", capture) + const module = key === "." ? packageInfo.name : `${packageInfo.name}${key.slice(1)}` + const excluded = Object.entries(exportsMap).some(([excludedKey, excludedTarget]) => + excludedTarget === null && matchPattern(excludedKey, key) !== undefined + ) + if (!excluded && (requested === undefined || requested.has(module))) { + entries.push({ packageName: packageInfo.name, module, declarationFile }) + } + } + return entries + }) + + const discoverEntrypointsInternal = Effect.fnUntraced(function*( + repoRoot: string, + requestedModules?: ReadonlyArray + ) { + const packages = yield* packagesIn(repoRoot) + const requested = requestedModules === undefined ? undefined : new Set(requestedModules) + const output = new Map() + + for (const packageInfo of packages) { + if (typeof packageInfo.exports !== "object" || packageInfo.exports === null) { + continue + } + const exportsMap = packageInfo.exports as Record + for (const [keyPattern, rawTarget] of Object.entries(exportsMap)) { + const target = exportedTarget(rawTarget) + if ( + !keyPattern.includes("*") && + typeof target === "string" && + (requested === undefined || requested.has( + keyPattern === "." ? packageInfo.name : `${packageInfo.name}${keyPattern.slice(1)}` + )) + ) { + const module = keyPattern === "." ? packageInfo.name : `${packageInfo.name}${keyPattern.slice(1)}` + const declarationFile = targetToDeclaration(packageInfo, target) + if ( + declarationFile !== undefined && + !output.has(module) && + (yield* fs.exists(declarationFile)) && + (yield* fs.stat(declarationFile)).type === "File" + ) { + output.set(module, { packageName: packageInfo.name, module, declarationFile }) + } + } + if (keyPattern.includes("*") && typeof target === "string" && target.includes("*")) { + for (const entry of yield* expandPattern(packageInfo, keyPattern, target, requested, exportsMap)) { + if (!output.has(entry.module)) { + output.set(entry.module, entry) + } + } + } + } + } + + return { + entrypoints: [...output.values()].sort((left, right) => left.module.localeCompare(right.module)), + missing: requestedModules?.filter((module) => !output.has(module)).sort() ?? [] + } + }) + + const discoverEntrypoints = ( + repoRoot: string, + requestedModules?: ReadonlyArray + ): Effect.Effect => + discoverEntrypointsInternal(repoRoot, requestedModules).pipe( + Effect.mapError((cause) => + new ApiDiffError({ + message: `Could not discover API entrypoints in ${repoRoot}`, + cause + }) + ) + ) + + return Discovery.of({ discoverEntrypoints }) + }) + ) +} diff --git a/repos/effect/packages/tools/api-diff/src/Error.ts b/repos/effect/packages/tools/api-diff/src/Error.ts new file mode 100644 index 0000000000..d2d4444c40 --- /dev/null +++ b/repos/effect/packages/tools/api-diff/src/Error.ts @@ -0,0 +1,9 @@ +import * as Predicate from "effect/Predicate" +import * as Schema from "effect/Schema" + +export class ApiDiffError extends Schema.TaggedError()("ApiDiffError", { + message: Schema.String, + cause: Schema.optional(Schema.Defect()) +}) {} + +export const isApiDiffError = (u: unknown): u is ApiDiffError => Predicate.isTagged(u, "ApiDiffError") diff --git a/repos/effect/packages/tools/api-diff/src/Json.ts b/repos/effect/packages/tools/api-diff/src/Json.ts new file mode 100644 index 0000000000..2d151929a9 --- /dev/null +++ b/repos/effect/packages/tools/api-diff/src/Json.ts @@ -0,0 +1,26 @@ +import * as Schema from "effect/Schema" +import { createHash } from "node:crypto" + +export const decodeJson = Schema.decodeUnknownEffect(Schema.fromJsonString(Schema.Unknown)) + +export const stableJson = (value: unknown): string => { + const visit = (input: unknown): unknown => { + if (Array.isArray(input)) { + return input.map(visit) + } + if (input !== null && typeof input === "object") { + return Object.fromEntries( + Object.entries(input) + .filter(([, entry]) => entry !== undefined) + .sort(([left], [right]) => left.localeCompare(right)) + .map(([key, entry]) => [key, visit(entry)]) + ) + } + return input + } + return JSON.stringify(visit(value)) +} + +export const fingerprint = (value: unknown): string => createHash("sha256").update(stableJson(value)).digest("hex") + +export const prettyJson = (value: unknown): string => `${JSON.stringify(value, null, 2)}\n` diff --git a/repos/effect/packages/tools/api-diff/src/MigrationDoc.ts b/repos/effect/packages/tools/api-diff/src/MigrationDoc.ts new file mode 100644 index 0000000000..eacb0038c8 --- /dev/null +++ b/repos/effect/packages/tools/api-diff/src/MigrationDoc.ts @@ -0,0 +1,451 @@ +import type { MigrationAnnotation } from "./Annotations.ts" +import type { ApiChange, ApiDiff, ChangeClassification } from "./Model.ts" + +const breakingClassifications = new Set([ + "bucket-changed", + "declaration-kind-changed", + "overload-removed", + "overload-reordered", + "parameter-removed", + "parameter-reordered", + "parameter-changed", + "return-type-changed", + "generic-parameter-changed", + "member-removed", + "member-changed", + "heritage-changed", + "union-member-changed", + "intersection-member-changed", + "structural-change" +]) + +const stableApiId = (id: string): string => id.replace(/#(?:type|value)$/, "") + +const displayApiId = (id: string): string => { + const separator = id.indexOf("#") + if (separator === -1) { + return id + } + const module = id.slice(0, separator).split("/").at(-1)! + return `${module}.${id.slice(separator + 1)}` +} + +const codeSpan = (value: string): string => { + const delimiter = "`".repeat(Math.max(1, ...(value.match(/`+/g)?.map((run) => run.length + 1) ?? []))) + const content = value.startsWith("`") || value.endsWith("`") ? ` ${value} ` : value + return `${delimiter}${content}${delimiter}` +} + +const escapeMarkdownText = (value: string): string => value.replace(/\\/g, "\\\\").replace(/[~<>*_]/g, "\\$&") + +const escapeAnnotationText = (value: string): string => { + const output: Array = [] + let index = 0 + while (index < value.length) { + const start = value.indexOf("`", index) + if (start === -1) { + output.push(escapeMarkdownText(value.slice(index))) + break + } + output.push(escapeMarkdownText(value.slice(index, start))) + let end = start + 1 + while (value[end] === "`") { + end++ + } + const delimiter = value.slice(start, end) + const close = value.indexOf(delimiter, end) + if (close === -1) { + output.push("\\`".repeat(delimiter.length)) + index = end + } else { + output.push(value.slice(start, close + delimiter.length)) + index = close + delimiter.length + } + } + return output.join("") +} + +const compareStrings = (left: string, right: string): number => left < right ? -1 : left > right ? 1 : 0 + +const isBreakingChange = (change: ApiChange): boolean => { + if (change.classification !== "parameter-added") { + return breakingClassifications.has(change.classification) + } + const delta = change.delta as { + readonly before?: ReadonlyArray + readonly after?: ReadonlyArray<{ readonly optional?: boolean; readonly rest?: boolean }> + } | undefined + const beforeLength = delta?.before?.length ?? 0 + return delta?.after?.slice(beforeLength).some((parameter) => !parameter.optional && !parameter.rest) ?? true +} + +interface MigrationEntry { + readonly id: string + readonly module: string + readonly rename: ApiChange | undefined +} + +const importMapReplacements = (importMapSections: string): ReadonlyMap> => { + const replacements = new Map>() + for (const line of importMapSections.split("\n")) { + const match = /^(\S+) -> (\S+)(?: \(barrel: [^)]+\))?$/.exec(line) + if (match === null) { + continue + } + const targets = replacements.get(match[1]!) + if (targets === undefined) { + replacements.set(match[1]!, [match[2]!]) + } else if (!targets.includes(match[2]!)) { + targets.push(match[2]!) + } + } + return replacements +} + +const removedModules = (diff: ApiDiff): ReadonlyArray => + [ + ...new Set(diff.changes.flatMap((change) => { + if (change.classification !== "module-removed") { + return [] + } + const from = (change.delta as { readonly from?: unknown } | undefined)?.from + return typeof from === "string" ? [from] : [] + })) + ].sort(compareStrings) + +const sameStrings = (left: ReadonlyArray, right: ReadonlyArray): boolean => + left.length === right.length && left.every((value, index) => value === right[index]) + +const addedApiSignatures = (diff: ApiDiff): ReadonlyMap> => { + const signatures = new Map>() + for (const change of diff.changes) { + if (change.classification !== "api-added" || change.headApiId === undefined || change.after === undefined) { + continue + } + const id = stableApiId(change.headApiId) + const group = signatures.get(id) + if (group === undefined) { + signatures.set(id, [change.after]) + } else { + group.push(change.after) + } + } + for (const group of signatures.values()) { + group.sort(compareStrings) + } + return signatures +} + +const isUnchangedModuleMove = ( + id: string, + changes: ReadonlyArray, + removed: ReadonlySet, + replacements: ReadonlyMap>, + addedSignatures: ReadonlyMap> +): boolean => { + const separator = id.indexOf("#") + const module = id.slice(0, separator) + const targets = replacements.get(module) + if (separator === -1 || !removed.has(module) || targets === undefined) { + return false + } + const before = changes + .filter((change) => change.classification === "api-removed" && change.before !== undefined) + .map((change) => change.before!) + .sort(compareStrings) + if (before.length === 0) { + return false + } + const path = id.slice(separator + 1) + return targets.some((target) => { + const after = addedSignatures.get(`${target}#${path}`) + return after !== undefined && sameStrings(before, after) + }) +} + +const isRemovalCoveredByModule = ( + id: string, + removed: ReadonlySet, + annotations: ReadonlyMap +): boolean => { + const separator = id.indexOf("#") + const module = id.slice(0, separator) + if (separator === -1 || !removed.has(module) || annotations.get(module)?.replacement !== "none") { + return false + } + const annotation = annotations.get(id) + return annotation === undefined || annotation.replacement === "none" +} + +const migrationEntries = ( + diff: ApiDiff, + annotations: ReadonlyMap, + importMapSections: string +): ReadonlyArray => { + const grouped = new Map>() + for (const change of diff.changes) { + if (change.baseApiId === undefined) { + continue + } + const id = stableApiId(change.baseApiId) + const changes = grouped.get(id) + if (changes === undefined) { + grouped.set(id, [change]) + } else { + changes.push(change) + } + } + + const entries: Array = [] + const removedModuleSet = new Set(removedModules(diff)) + const replacements = importMapReplacements(importMapSections) + const addedSignatures = addedApiSignatures(diff) + for (const [id, changes] of grouped) { + const rename = changes.find((change) => change.classification === "api-renamed") + const breaking = changes.some(isBreakingChange) + const removed = changes.some((change) => change.classification === "api-removed") + const importMove = changes.some((change) => change.classification === "api-moved") + if ( + isUnchangedModuleMove(id, changes, removedModuleSet, replacements, addedSignatures) || + (removed && isRemovalCoveredByModule(id, removedModuleSet, annotations)) || + (!breaking && rename === undefined && !removed) || + (importMove && rename === undefined && !breaking && !annotations.has(id)) + ) { + continue + } + entries.push({ + id, + module: id.split("#")[0]!, + rename + }) + } + return entries.sort((left, right) => compareStrings(left.module, right.module) || compareStrings(left.id, right.id)) +} + +const renderDetailedEntry = ( + entry: MigrationEntry, + annotation: MigrationAnnotation, + example: string +): ReadonlyArray => [ + `#### ${codeSpan(displayApiId(entry.id))}`, + "", + `**Replacement:** ${codeSpan(annotation.replacement)}`, + "", + escapeAnnotationText(annotation.note), + "", + "**Example**", + "", + "```ts", + example, + "```", + "" +] + +const renderCompactEntry = ( + entry: MigrationEntry, + annotation: MigrationAnnotation | undefined +): string => { + if (annotation !== undefined) { + return `- ${codeSpan(displayApiId(entry.id))} -> ${codeSpan(annotation.replacement)}: ${ + escapeAnnotationText(annotation.note) + }` + } + const target = entry.rename?.headApiId + return target === undefined + ? `- ${codeSpan(displayApiId(entry.id))}: TODO: needs guidance` + : `- ${codeSpan(displayApiId(entry.id))} -> ${codeSpan(displayApiId(stableApiId(target)))}: TODO: needs guidance` +} + +export const extractImportMapSections = (document: string): string => { + const start = document.indexOf("## Import Map") + if (start === -1) { + return "## Import Map\n\nNo import map is available.\n" + } + const apiRenames = document.indexOf("\n## API Renames", start) + const removedModules = document.indexOf("\n## Removed Modules", start) + const apiReference = document.indexOf("\n## API Reference", start) + const ends = [apiRenames, removedModules, apiReference].filter((index) => index !== -1) + const end = ends.length === 0 ? document.length : Math.min(...ends) + return `${document.slice(start, end).trim()}\n` +} + +export const renderMigrationDocument = ( + diff: ApiDiff, + annotations: ReadonlyMap, + importMapSections: string +): string => { + const entries = migrationEntries(diff, annotations, importMapSections) + const replacements = importMapReplacements(importMapSections) + const modulesRemoved = removedModules(diff) + const modules = new Map>() + for (const entry of entries) { + const group = modules.get(entry.module) + if (group === undefined) { + modules.set(entry.module, [entry]) + } else { + group.push(entry) + } + } + const lines = [ + "", + "", + "# v3 to v4 Migration Reference", + "", + `Base: \`${diff.base.ref}\` (\`${diff.base.sha}\`)`, + "", + `Head: \`${diff.head.ref}\` (\`${diff.head.sha}\`)`, + "", + "This file is generated from the API diff and `migration/annotations/*.yaml`.", + "", + importMapSections.trim(), + "", + "## Removed Modules", + "" + ] + for (const module of modulesRemoved) { + const annotation = annotations.get(module) + const targets = replacements.get(module) + if (annotation !== undefined) { + lines.push( + `- ${codeSpan(module)} -> ${codeSpan(annotation.replacement)}: ${escapeAnnotationText(annotation.note)}` + ) + } else if (targets !== undefined) { + lines.push(`- ${codeSpan(module)} -> ${targets.map(codeSpan).join(", ")}`) + } else if ([...annotations.keys()].some((id) => id.startsWith(`${module}#`))) { + lines.push(`- ${codeSpan(module)}: No single module replacement; follow the curated per-API guidance below.`) + } else { + lines.push(`- ${codeSpan(module)}: TODO: needs module guidance`) + } + } + if (modulesRemoved.length === 0) { + lines.push("No modules were removed.") + } + lines.push( + "", + "## API Reference", + "" + ) + for (const [module, moduleEntries] of modules) { + lines.push(`### ${codeSpan(module)}`, "") + for (const entry of moduleEntries) { + const annotation = annotations.get(entry.id) + if (annotation?.example !== undefined) { + lines.push(...renderDetailedEntry(entry, annotation, annotation.example)) + } else { + lines.push(renderCompactEntry(entry, annotation), "") + } + } + } + return `${lines.join("\n").trim()}\n` +} + +export const markdownSafetyIssues = (document: string): ReadonlyArray => { + const issues: Array = [] + let fence: string | undefined + const lines = document.split("\n") + for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) { + const line = lines[lineIndex]! + const fenceMatch = /^\s*(`{3,})(?:[^`]*)$/.exec(line) + if (fence !== undefined) { + if (line.trim() === fence) { + fence = undefined + } + continue + } + if (fenceMatch !== null) { + fence = fenceMatch[1]! + continue + } + if ( + line === "" || + line.startsWith("**Replacement:** ") || + line === "**Example**" + ) { + continue + } + let index = 0 + while (index < line.length) { + const character = line[index]! + if (character === "\\") { + index += 2 + continue + } + if (character === "`") { + let end = index + 1 + while (line[end] === "`") { + end++ + } + const delimiter = line.slice(index, end) + const close = line.indexOf(delimiter, end) + if (close === -1) { + issues.push(`line ${lineIndex + 1}: unclosed inline code span`) + break + } + index = close + delimiter.length + continue + } + if (character === "~" || character === "<" || character === "*" || character === "_") { + issues.push(`line ${lineIndex + 1}: unescaped ${JSON.stringify(character)}`) + } + index++ + } + } + if (fence !== undefined) { + issues.push(`unclosed ${fence} code fence`) + } + return issues +} + +export const unannotatedApiIds = ( + diff: ApiDiff, + annotations: ReadonlyMap, + importMapSections = "" +): ReadonlyMap> => { + const modules = new Map>() + for (const entry of migrationEntries(diff, annotations, importMapSections)) { + if (annotations.has(entry.id)) { + continue + } + const ids = modules.get(entry.module) + if (ids === undefined) { + modules.set(entry.module, [entry.id]) + } else { + ids.push(entry.id) + } + } + return modules +} + +export const unannotatedModuleIds = ( + diff: ApiDiff, + annotations: ReadonlyMap, + importMapSections: string +): ReadonlyArray => { + const replacements = importMapReplacements(importMapSections) + return removedModules(diff).filter((module) => + !annotations.has(module) && + !replacements.has(module) && + ![...annotations.keys()].some((id) => id.startsWith(`${module}#`)) + ) +} + +export const renderMissingAnnotations = ( + diff: ApiDiff, + annotations: ReadonlyMap, + importMapSections = "" +): string => { + const missingApis = unannotatedApiIds(diff, annotations, importMapSections) + const missingModules = unannotatedModuleIds(diff, annotations, importMapSections) + if (missingApis.size === 0 && missingModules.length === 0) { + return "All migration APIs and removed modules have guidance.\n" + } + return `${ + [ + ...(missingModules.length === 0 ? [] : ["Removed modules:", ...missingModules.map((module) => ` - ${module}`)]), + ...[...missingApis].flatMap(([module, ids]) => [ + `${module}:`, + ...ids.map((id) => ` - ${id}`) + ]) + ].join("\n") + }\n` +} diff --git a/repos/effect/packages/tools/api-diff/src/Model.ts b/repos/effect/packages/tools/api-diff/src/Model.ts new file mode 100644 index 0000000000..e2b88b527b --- /dev/null +++ b/repos/effect/packages/tools/api-diff/src/Model.ts @@ -0,0 +1,156 @@ +import * as Schema from "effect/Schema" + +export const Bucket = Schema.Literals(["type", "value"]) + +export type Bucket = typeof Bucket.Type + +export const SourceLocation = Schema.Struct({ + file: Schema.String, + line: Schema.Number, + column: Schema.Number +}) + +export type SourceLocation = typeof SourceLocation.Type + +export interface Documentation { + readonly summary?: string | undefined + readonly deprecated?: string | undefined + readonly since?: string | undefined + readonly category?: string | undefined + readonly stability?: "stable" | "unstable" | undefined +} + +export interface TypeModel { + readonly kind: string + readonly [key: string]: unknown +} + +export interface DeclarationModel { + readonly kind: string + readonly name: string + readonly typeParameters?: ReadonlyArray | undefined + readonly parameters?: ReadonlyArray | undefined + readonly returnType?: TypeModel | undefined + readonly type?: TypeModel | undefined + readonly members?: ReadonlyArray | undefined + readonly overloads?: ReadonlyArray | undefined + readonly heritage?: ReadonlyArray | undefined + readonly modifiers?: ReadonlyArray | undefined + readonly value?: string | number | undefined +} + +export interface TypeParameterModel { + readonly id: string + readonly displayName: string + readonly constraint?: TypeModel | undefined + readonly default?: TypeModel | undefined +} + +export interface ParameterModel { + readonly name: string + readonly type: TypeModel + readonly optional: boolean + readonly rest: boolean + readonly modifiers?: ReadonlyArray | undefined +} + +export interface ImportRoute { + readonly module: string + readonly path: ReadonlyArray +} + +export interface ApiEntity { + readonly id: string + readonly packageName: string + readonly module: string + readonly path: ReadonlyArray + readonly bucket: Bucket + readonly declarationKind: string + readonly importRoutes: ReadonlyArray + readonly declarations: ReadonlyArray + readonly displaySignature: string + readonly fingerprint: string + readonly documentation: Documentation + readonly source: SourceLocation +} + +export interface Entrypoint { + readonly packageName: string + readonly module: string + readonly declarationFile: string +} + +export const SnapshotDiagnostic = Schema.Struct({ + code: Schema.String, + message: Schema.String, + module: Schema.optional(Schema.String), + path: Schema.optional(Schema.Array(Schema.String)), + source: Schema.optional(SourceLocation) +}) + +export type SnapshotDiagnostic = typeof SnapshotDiagnostic.Type + +export interface ApiSnapshot { + readonly version: 1 + readonly compiler: { + readonly name: "typescript" + readonly version: string + } + readonly ref: string + readonly sha: string + readonly packages: ReadonlyArray + readonly entrypoints: ReadonlyArray + readonly entities: ReadonlyArray + readonly diagnostics: ReadonlyArray +} + +export type ChangeClassification = + | "package-added" + | "package-removed" + | "module-added" + | "module-removed" + | "api-added" + | "api-removed" + | "api-moved" + | "api-renamed" + | "bucket-changed" + | "declaration-kind-changed" + | "overload-added" + | "overload-removed" + | "overload-reordered" + | "parameter-added" + | "parameter-removed" + | "parameter-reordered" + | "parameter-changed" + | "return-type-changed" + | "generic-parameter-changed" + | "member-added" + | "member-removed" + | "member-changed" + | "heritage-changed" + | "union-member-changed" + | "intersection-member-changed" + | "documentation-changed" + | "structural-change" + +export interface ApiChange { + readonly id: string + readonly classification: ChangeClassification + readonly confidence: number + readonly baseApiId?: string | undefined + readonly headApiId?: string | undefined + readonly before?: string | undefined + readonly after?: string | undefined + readonly delta?: unknown + readonly baseSource?: SourceLocation | undefined + readonly headSource?: SourceLocation | undefined + readonly reviewNotes?: string | undefined + readonly authoritative: boolean +} + +export interface ApiDiff { + readonly version: 1 + readonly base: { readonly ref: string; readonly sha: string } + readonly head: { readonly ref: string; readonly sha: string } + readonly changes: ReadonlyArray +} diff --git a/repos/effect/packages/tools/api-diff/src/Report.ts b/repos/effect/packages/tools/api-diff/src/Report.ts new file mode 100644 index 0000000000..146a121e0f --- /dev/null +++ b/repos/effect/packages/tools/api-diff/src/Report.ts @@ -0,0 +1,141 @@ +import type { ApiChange, ApiDiff } from "./Model.ts" + +const escapeCell = (value: string): string => value.replaceAll("|", "\\|").replaceAll("\n", " ") + +const changeLabel = (change: ApiChange): string => + change.baseApiId === undefined + ? change.headApiId ?? JSON.stringify(change.delta) + : change.headApiId === undefined + ? change.baseApiId + : `${change.baseApiId} → ${change.headApiId}` + +const signature = (label: string, source: string | undefined): ReadonlyArray => + source === undefined ? [] : ["", `${label}:`, "", "```ts", source, "```"] + +const renderChange = (change: ApiChange, level = 4): ReadonlyArray => [ + `${"#".repeat(level)} ${change.classification}: \`${changeLabel(change)}\``, + "", + `Confidence: ${change.confidence.toFixed(3)} · ${change.authoritative ? "authoritative" : "review required"}`, + ...(change.reviewNotes === undefined ? [] : ["", change.reviewNotes]), + ...signature("Before", change.before), + ...signature("After", change.after), + "" +] + +const orderedGroups = [ + ["Renames and moves", new Set(["api-moved", "api-renamed"])], + ["Removals", new Set(["package-removed", "module-removed", "api-removed"])], + ["Additions", new Set(["package-added", "module-added", "api-added"])], + [ + "Signature and structural changes", + new Set([ + "bucket-changed", + "declaration-kind-changed", + "overload-added", + "overload-removed", + "overload-reordered", + "parameter-added", + "parameter-removed", + "parameter-reordered", + "parameter-changed", + "return-type-changed", + "generic-parameter-changed", + "member-added", + "member-removed", + "member-changed", + "heritage-changed", + "union-member-changed", + "intersection-member-changed", + "structural-change" + ]) + ], + ["Documentation changes", new Set(["documentation-changed"])] +] as const + +export const renderMarkdownReport = (diff: ApiDiff): string => { + const counts = new Map() + for (const change of diff.changes) { + counts.set(change.classification, (counts.get(change.classification) ?? 0) + 1) + } + const authoritative = diff.changes.filter((change) => change.authoritative) + const suggested = diff.changes.filter((change) => !change.authoritative) + const moduleCounts = new Map() + for (const change of diff.changes) { + const id = change.headApiId ?? change.baseApiId + const delta = change.delta as { + readonly packageName?: string + readonly from?: string + readonly to?: ReadonlyArray + } | undefined + const module = id?.split("#")[0] ?? delta?.packageName ?? delta?.from ?? delta?.to?.[0] ?? "" + moduleCounts.set(module, (moduleCounts.get(module) ?? 0) + 1) + } + const lines: Array = [ + "# TypeScript API Diff", + "", + `Base: \`${diff.base.ref}\` (\`${diff.base.sha}\`)`, + "", + `Head: \`${diff.head.ref}\` (\`${diff.head.sha}\`)`, + "", + "This report describes structural API changes and review confidence; it is not a semantic-version compatibility claim.", + "", + "## Summary", + "", + "| Classification | Count |", + "| --- | ---: |", + ...[...counts].sort(([left], [right]) => left.localeCompare(right)) + .map(([classification, count]) => `| ${escapeCell(classification)} | ${count} |`), + "", + "## Changes by module", + "", + "| Domain | Module | Count |", + "| --- | --- | ---: |", + ...[...moduleCounts].sort(([left], [right]) => left.localeCompare(right)).map(([module, count]) => { + const unstable = module.match(/^effect\/unstable\/([^/]+)/) + const domain = unstable?.[1] === undefined + ? module.startsWith("@effect/") + ? module.split("/").slice(0, 2).join("/") + : "stable" + : `unstable/${unstable[1]}` + return `| ${escapeCell(domain)} | ${escapeCell(module)} | ${count} |` + }), + "" + ] + const sections = [ + [ + "Stable API changes", + authoritative.filter((change) => + !(change.baseApiId ?? change.headApiId ?? JSON.stringify(change.delta)).includes("/unstable/") + ) + ], + [ + "Unstable API changes", + authoritative.filter((change) => + (change.baseApiId ?? change.headApiId ?? JSON.stringify(change.delta)).includes("/unstable/") + ) + ] + ] as const + for (const [section, sectionChanges] of sections) { + if (sectionChanges.length === 0) { + continue + } + lines.push(`## ${section}`, "") + for (const [title, classifications] of orderedGroups) { + const changes = sectionChanges.filter((change) => classifications.has(change.classification)) + if (changes.length === 0) { + continue + } + lines.push(`### ${title}`, "") + for (const change of changes) { + lines.push(...renderChange(change)) + } + } + } + if (suggested.length > 0) { + lines.push("## Suggested replacements for removed APIs", "") + for (const change of suggested) { + lines.push(...renderChange(change, 3)) + } + } + return `${lines.join("\n").trim()}\n` +} diff --git a/repos/effect/packages/tools/api-diff/src/Snapshot.ts b/repos/effect/packages/tools/api-diff/src/Snapshot.ts new file mode 100644 index 0000000000..445d580970 --- /dev/null +++ b/repos/effect/packages/tools/api-diff/src/Snapshot.ts @@ -0,0 +1,884 @@ +import * as Context from "effect/Context" +import * as Effect from "effect/Effect" +import * as Layer from "effect/Layer" +import * as Path from "effect/Path" +import * as Predicate from "effect/Predicate" +import * as Schema from "effect/Schema" +import ts from "typescript-compiler" +import { Discovery, type DiscoveryResult } from "./Discovery.ts" +import { ApiDiffError } from "./Error.ts" +import { fingerprint, stableJson } from "./Json.ts" +import { SnapshotDiagnostic as SnapshotDiagnosticSchema } from "./Model.ts" +import type { + ApiEntity, + ApiSnapshot, + Bucket, + DeclarationModel, + Documentation, + Entrypoint, + ImportRoute, + ParameterModel, + SnapshotDiagnostic, + SourceLocation, + TypeModel, + TypeParameterModel +} from "./Model.ts" + +interface SerializationContext { + readonly checker: ts.TypeChecker + readonly path: Path.Path + readonly repoRoot: string + readonly typeParameters: ReadonlyMap + readonly publicSymbols: ReadonlyMap +} + +const normalizedPath = (path: Path.Path, root: string, location: string): string => + path.relative(root, location).split(path.sep).join("/") + +const sourceLocation = (path: Path.Path, root: string, node: ts.Node): SourceLocation => { + const source = node.getSourceFile() + const position = source.getLineAndCharacterOfPosition(node.getStart(source, false)) + return { + file: normalizedPath(path, root, source.fileName), + line: position.line + 1, + column: position.character + 1 + } +} + +const modifiers = (node: ts.Node): ReadonlyArray | undefined => { + if (!ts.canHaveModifiers(node)) { + return undefined + } + const values = ts.getModifiers(node)?.map((modifier) => + ts.tokenToString(modifier.kind) ?? ts.SyntaxKind[modifier.kind] + ) + .filter((modifier) => modifier !== "export" && modifier !== "declare") + .sort() + return values === undefined || values.length === 0 ? undefined : values +} + +const nameText = (name: ts.DeclarationName | ts.BindingName | undefined): string => { + if (name === undefined) { + return "" + } + if (ts.isIdentifier(name) || ts.isPrivateIdentifier(name)) { + return name.text + } + if (ts.isStringLiteral(name) || ts.isNumericLiteral(name)) { + return name.text + } + return name.getText() +} + +const sortModels = (values: ReadonlyArray): ReadonlyArray => + [...values].sort((left, right) => stableJson(left).localeCompare(stableJson(right))) + +const referenceResolution = (name: ts.EntityName, context: SerializationContext): unknown => { + let symbol = context.checker.getSymbolAtLocation(name) + if (symbol === undefined) { + return { unresolved: true } + } + if ((symbol.flags & ts.SymbolFlags.Alias) !== 0) { + symbol = context.checker.getAliasedSymbol(symbol) + } + const declaration = symbol?.declarations?.[0] + if (declaration === undefined) { + return { unresolved: true } + } + const publicApiId = context.publicSymbols.get(symbolIdentity(symbol, "type")) ?? + context.publicSymbols.get(symbolIdentity(symbol, "value")) + if (publicApiId !== undefined) { + return { apiId: publicApiId } + } + const file = declaration.getSourceFile().fileName + const separator = context.path.sep + const nodeModules = file.lastIndexOf(`${separator}node_modules${separator}`) + if (nodeModules !== -1) { + const remainder = file.slice(nodeModules + `${separator}node_modules${separator}`.length).split(separator) + return { + externalPackage: remainder[0]?.startsWith("@") ? remainder.slice(0, 2).join("/") : remainder[0] + } + } + return { declaration: normalizedPath(context.path, context.repoRoot, file) } +} + +const primitiveKinds = new Map([ + [ts.SyntaxKind.AnyKeyword, "any"], + [ts.SyntaxKind.BigIntKeyword, "bigint"], + [ts.SyntaxKind.BooleanKeyword, "boolean"], + [ts.SyntaxKind.IntrinsicKeyword, "intrinsic"], + [ts.SyntaxKind.NeverKeyword, "never"], + [ts.SyntaxKind.NumberKeyword, "number"], + [ts.SyntaxKind.ObjectKeyword, "object"], + [ts.SyntaxKind.StringKeyword, "string"], + [ts.SyntaxKind.SymbolKeyword, "symbol"], + [ts.SyntaxKind.UndefinedKeyword, "undefined"], + [ts.SyntaxKind.UnknownKeyword, "unknown"], + [ts.SyntaxKind.VoidKeyword, "void"] +]) + +const serializeTypeParameters = ( + nodes: ts.NodeArray | undefined, + context: SerializationContext +): { readonly parameters?: ReadonlyArray; readonly context: SerializationContext } => { + if (nodes === undefined || nodes.length === 0) { + return { context } + } + const names = new Map(context.typeParameters) + nodes.forEach((node, index) => names.set(node.name.text, `T${index}`)) + const next = { ...context, typeParameters: names } + return { + context: next, + parameters: nodes.map((node, index) => ({ + id: `T${index}`, + displayName: node.name.text, + constraint: node.constraint === undefined ? undefined : serializeType(node.constraint, next), + default: node.default === undefined ? undefined : serializeType(node.default, next) + })) + } +} + +const serializeParameters = ( + nodes: ts.NodeArray, + context: SerializationContext +): ReadonlyArray => + nodes.map((node) => ({ + name: nameText(node.name), + type: node.type === undefined ? { kind: "unknown" } : serializeType(node.type, context), + optional: node.questionToken !== undefined || node.initializer !== undefined, + rest: node.dotDotDotToken !== undefined, + modifiers: modifiers(node) + })) + +const serializeSignature = ( + node: ts.SignatureDeclarationBase, + context: SerializationContext, + kind: string, + name = "" +): DeclarationModel => { + const generic = serializeTypeParameters(node.typeParameters, context) + return { + kind, + name, + typeParameters: generic.parameters, + parameters: serializeParameters(node.parameters, generic.context), + returnType: node.type === undefined ? { kind: "unknown" } : serializeType(node.type, generic.context), + modifiers: modifiers(node) + } +} + +const serializeTypeMember = (node: ts.TypeElement, context: SerializationContext): DeclarationModel => { + if (ts.isPropertySignature(node)) { + return { + kind: "property", + name: nameText(node.name), + type: node.type === undefined ? { kind: "unknown" } : serializeType(node.type, context), + modifiers: [ + ...(modifiers(node) ?? []), + ...(node.questionToken === undefined ? [] : ["optional"]) + ].sort() + } + } + if (ts.isMethodSignature(node)) { + return { + ...serializeSignature(node, context, "method", nameText(node.name)), + modifiers: [ + ...(modifiers(node) ?? []), + ...(node.questionToken === undefined ? [] : ["optional"]) + ].sort() + } + } + if (ts.isCallSignatureDeclaration(node)) { + return serializeSignature(node, context, "call-signature") + } + if (ts.isConstructSignatureDeclaration(node)) { + return serializeSignature(node, context, "construct-signature") + } + if (ts.isIndexSignatureDeclaration(node)) { + return serializeSignature(node, context, "index-signature") + } + if (ts.isGetAccessorDeclaration(node)) { + return serializeSignature(node, context, "getter", nameText(node.name)) + } + if (ts.isSetAccessorDeclaration(node)) { + return serializeSignature(node, context, "setter", nameText(node.name)) + } + throw new Error(`Unsupported public type member: ${ts.SyntaxKind[node.kind]}`) +} + +const serializeObjectMembers = ( + members: ts.NodeArray, + context: SerializationContext +): ReadonlyArray => + members.map((member) => serializeTypeMember(member, context)) + .sort((left, right) => + `${left.kind}:${left.name}:${stableJson(left)}`.localeCompare( + `${right.kind}:${right.name}:${stableJson(right)}` + ) + ) + +export const serializeType = (node: ts.TypeNode, context: SerializationContext): TypeModel => { + const primitive = primitiveKinds.get(node.kind) + if (primitive !== undefined) { + return { kind: "primitive", name: primitive } + } + if (ts.isParenthesizedTypeNode(node)) { + return serializeType(node.type, context) + } + if (ts.isLiteralTypeNode(node)) { + const literal = node.literal + return { + kind: "literal", + value: literal.kind === ts.SyntaxKind.TrueKeyword + ? true + : literal.kind === ts.SyntaxKind.FalseKeyword + ? false + : ts.isPrefixUnaryExpression(literal) + ? literal.getText() + : Reflect.get(literal, "text") ?? literal.getText() + } + } + if (ts.isTypeReferenceNode(node)) { + const rawName = node.typeName.getText() + const genericId = context.typeParameters.get(rawName) + if (genericId !== undefined) { + return { kind: "type-parameter", id: genericId, displayName: rawName } + } + return { + kind: "reference", + name: rawName, + arguments: node.typeArguments?.map((argument) => serializeType(argument, context)) ?? [], + resolution: referenceResolution(node.typeName, context) + } + } + if (ts.isUnionTypeNode(node) || ts.isIntersectionTypeNode(node)) { + return { + kind: ts.isUnionTypeNode(node) ? "union" : "intersection", + members: sortModels(node.types.map((type) => serializeType(type, context))) + } + } + if (ts.isArrayTypeNode(node)) { + return { kind: "array", element: serializeType(node.elementType, context) } + } + if (ts.isTupleTypeNode(node)) { + return { kind: "tuple", elements: node.elements.map((element) => serializeType(element, context)) } + } + if (ts.isNamedTupleMember(node)) { + return { + kind: "named-tuple-member", + name: node.name.text, + optional: node.questionToken !== undefined, + rest: node.dotDotDotToken !== undefined, + type: serializeType(node.type, context) + } + } + if (ts.isOptionalTypeNode(node)) { + return { kind: "optional", type: serializeType(node.type, context) } + } + if (ts.isRestTypeNode(node)) { + return { kind: "rest", type: serializeType(node.type, context) } + } + if (ts.isFunctionTypeNode(node) || ts.isConstructorTypeNode(node)) { + return { + kind: ts.isFunctionTypeNode(node) ? "function" : "constructor", + signature: serializeSignature(node, context, "signature") + } + } + if (ts.isTypeLiteralNode(node)) { + return { kind: "object", members: serializeObjectMembers(node.members, context) } + } + if (ts.isMappedTypeNode(node)) { + const generic = serializeTypeParameters(ts.factory.createNodeArray([node.typeParameter]), context) + return { + kind: "mapped", + typeParameter: generic.parameters?.[0], + readonly: node.readonlyToken?.kind === ts.SyntaxKind.MinusToken + ? "remove" + : node.readonlyToken === undefined + ? "none" + : "add", + optional: node.questionToken?.kind === ts.SyntaxKind.MinusToken + ? "remove" + : node.questionToken === undefined + ? "none" + : "add", + nameType: node.nameType === undefined ? undefined : serializeType(node.nameType, generic.context), + type: node.type === undefined ? undefined : serializeType(node.type, generic.context) + } + } + if (ts.isConditionalTypeNode(node)) { + return { + kind: "conditional", + check: serializeType(node.checkType, context), + extends: serializeType(node.extendsType, context), + trueType: serializeType(node.trueType, context), + falseType: serializeType(node.falseType, context) + } + } + if (ts.isIndexedAccessTypeNode(node)) { + return { + kind: "indexed-access", + object: serializeType(node.objectType, context), + index: serializeType(node.indexType, context) + } + } + if (ts.isTypeOperatorNode(node)) { + return { + kind: "operator", + operator: ts.tokenToString(node.operator) ?? ts.SyntaxKind[node.operator], + type: serializeType(node.type, context) + } + } + if (ts.isTypeQueryNode(node)) { + return { kind: "type-query", expression: node.exprName.getText() } + } + if (ts.isImportTypeNode(node)) { + const argument = node.argument.getText().replace(/^["']|["']$/g, "") + const parts = argument.split("/") + return { + kind: "import", + argument, + externalPackage: argument.startsWith(".") + ? undefined + : parts[0]?.startsWith("@") + ? parts.slice(0, 2).join("/") + : parts[0], + qualifier: node.qualifier?.getText(), + attributes: node.attributes?.getText(), + arguments: node.typeArguments?.map((argument) => serializeType(argument, context)) ?? [], + typeof: node.isTypeOf + } + } + if (ts.isTemplateLiteralTypeNode(node)) { + return { + kind: "template-literal", + head: node.head.text, + spans: node.templateSpans.map((span) => ({ + type: serializeType(span.type, context), + literal: span.literal.text + })) + } + } + if (ts.isInferTypeNode(node)) { + const generic = serializeTypeParameters(ts.factory.createNodeArray([node.typeParameter]), context) + return { kind: "infer", typeParameter: generic.parameters?.[0] } + } + if (ts.isTypePredicateNode(node)) { + return { + kind: "predicate", + asserts: node.assertsModifier !== undefined, + parameter: node.parameterName.getText(), + type: node.type === undefined ? undefined : serializeType(node.type, context) + } + } + if (ts.isExpressionWithTypeArguments(node)) { + return { + kind: "heritage-reference", + name: node.expression.getText(), + arguments: node.typeArguments?.map((argument) => serializeType(argument, context)) ?? [] + } + } + if (node.kind === ts.SyntaxKind.ThisType) { + return { kind: "this" } + } + throw new Error(`Unsupported public type syntax: ${ts.SyntaxKind[node.kind]}`) +} + +const serializeClassMember = ( + node: ts.ClassElement, + context: SerializationContext +): DeclarationModel | undefined => { + const visibility = modifiers(node) + if (visibility?.includes("private")) { + return undefined + } + if (ts.isPropertyDeclaration(node)) { + return { + kind: "property", + name: nameText(node.name), + type: node.type === undefined ? { kind: "unknown" } : serializeType(node.type, context), + modifiers: visibility + } + } + if (ts.isMethodDeclaration(node)) { + return serializeSignature(node, context, "method", nameText(node.name)) + } + if (ts.isConstructorDeclaration(node)) { + return serializeSignature(node, context, "constructor", "constructor") + } + if (ts.isGetAccessorDeclaration(node)) { + return serializeSignature(node, context, "getter", nameText(node.name)) + } + if (ts.isSetAccessorDeclaration(node)) { + return serializeSignature(node, context, "setter", nameText(node.name)) + } + if (ts.isIndexSignatureDeclaration(node)) { + return serializeSignature(node, context, "index-signature") + } + if (ts.isClassStaticBlockDeclaration(node) || ts.isSemicolonClassElement(node)) { + return undefined + } + throw new Error(`Unsupported public class member: ${ts.SyntaxKind[node.kind]}`) +} + +const serializeHeritage = ( + clauses: ts.NodeArray | undefined, + context: SerializationContext +): ReadonlyArray | undefined => { + if (clauses === undefined) { + return undefined + } + return clauses.flatMap((clause) => + clause.types.map((type) => ({ + ...serializeType(type, context), + clause: ts.tokenToString(clause.token) ?? ts.SyntaxKind[clause.token] + })) + ).sort((left, right) => stableJson(left).localeCompare(stableJson(right))) +} + +const moduleMembers = ( + node: ts.ModuleDeclaration, + context: SerializationContext +): ReadonlyArray => { + let body = node.body + while (body !== undefined && ts.isModuleDeclaration(body)) { + body = body.body + } + if (body === undefined || !ts.isModuleBlock(body)) { + return [] + } + return body.statements.flatMap((statement) => { + if ( + ts.isFunctionDeclaration(statement) || ts.isInterfaceDeclaration(statement) || + ts.isTypeAliasDeclaration(statement) || ts.isClassDeclaration(statement) || + ts.isEnumDeclaration(statement) || ts.isModuleDeclaration(statement) || + ts.isVariableStatement(statement) + ) { + if (ts.isVariableStatement(statement)) { + return statement.declarationList.declarations.map((declaration) => serializeDeclaration(declaration, context)) + } + return [serializeDeclaration(statement, context)] + } + if (ts.isExportDeclaration(statement) || ts.isImportDeclaration(statement)) { + return [] + } + throw new Error(`Unsupported public namespace statement: ${ts.SyntaxKind[statement.kind]}`) + }).sort((left, right) => `${left.kind}:${left.name}`.localeCompare(`${right.kind}:${right.name}`)) +} + +const serializeDeclaration = (node: ts.Declaration, context: SerializationContext): DeclarationModel => { + if (ts.isFunctionDeclaration(node)) { + return serializeSignature(node, context, "function", nameText(node.name)) + } + if (ts.isVariableDeclaration(node)) { + return { + kind: "variable", + name: nameText(node.name), + type: node.type === undefined ? { kind: "unknown" } : serializeType(node.type, context) + } + } + if (ts.isTypeAliasDeclaration(node)) { + const generic = serializeTypeParameters(node.typeParameters, context) + return { + kind: "type-alias", + name: node.name.text, + typeParameters: generic.parameters, + type: serializeType(node.type, generic.context), + modifiers: modifiers(node) + } + } + if (ts.isInterfaceDeclaration(node)) { + const generic = serializeTypeParameters(node.typeParameters, context) + return { + kind: "interface", + name: node.name.text, + typeParameters: generic.parameters, + members: serializeObjectMembers(node.members, generic.context), + heritage: serializeHeritage(node.heritageClauses, generic.context), + modifiers: modifiers(node) + } + } + if (ts.isClassDeclaration(node)) { + const generic = serializeTypeParameters(node.typeParameters, context) + return { + kind: "class", + name: nameText(node.name), + typeParameters: generic.parameters, + members: node.members.flatMap((member) => { + const serialized = serializeClassMember(member, generic.context) + return serialized === undefined ? [] : [serialized] + }).sort((left, right) => + `${left.kind}:${left.name}:${stableJson(left)}`.localeCompare( + `${right.kind}:${right.name}:${stableJson(right)}` + ) + ), + heritage: serializeHeritage(node.heritageClauses, generic.context), + modifiers: modifiers(node) + } + } + if (ts.isEnumDeclaration(node)) { + return { + kind: "enum", + name: node.name.text, + members: node.members.map((member) => ({ + kind: "enum-member", + name: nameText(member.name), + value: member.initializer === undefined + ? undefined + : ts.isStringLiteral(member.initializer) || ts.isNumericLiteral(member.initializer) + ? member.initializer.text + : member.initializer.getText() + })) + } + } + if (ts.isModuleDeclaration(node)) { + return { kind: "namespace", name: nameText(node.name), members: moduleMembers(node, context) } + } + if (ts.isExportAssignment(node)) { + return { kind: "export-assignment", name: "default", value: node.expression.getText() } + } + throw new Error(`Unsupported public declaration: ${ts.SyntaxKind[node.kind]}`) +} + +const declarationKind = (declarations: ReadonlyArray): string => + [...new Set(declarations.map((declaration) => declaration.kind))].sort().join("+") + +const documentation = (symbol: ts.Symbol, checker: ts.TypeChecker, module: string): Documentation => { + const tags = new Map( + symbol.getJsDocTags(checker).map((tag) => [ + tag.name, + tag.text?.map((part) => part.text).join("").trim() ?? "" + ]) + ) + const summary = ts.displayPartsToString(symbol.getDocumentationComment(checker)).trim() + return { + summary: summary === "" ? undefined : summary, + deprecated: tags.get("deprecated"), + since: tags.get("since"), + category: tags.get("category"), + stability: module.includes("/unstable/") ? "unstable" : "stable" + } +} + +const bucketDeclarations = (symbol: ts.Symbol, bucket: Bucket): ReadonlyArray => { + const declarations = symbol.declarations ?? [] + return declarations.filter((declaration) => { + if (bucket === "type") { + return ts.isInterfaceDeclaration(declaration) || ts.isTypeAliasDeclaration(declaration) || + ts.isClassDeclaration(declaration) || ts.isEnumDeclaration(declaration) || + ts.isModuleDeclaration(declaration) + } + return ts.isFunctionDeclaration(declaration) || ts.isVariableDeclaration(declaration) || + ts.isClassDeclaration(declaration) || ts.isEnumDeclaration(declaration) || + ts.isModuleDeclaration(declaration) || ts.isExportAssignment(declaration) + }) +} + +const symbolBuckets = (symbol: ts.Symbol): ReadonlyArray => { + const output: Array = [] + if ((symbol.flags & ts.SymbolFlags.Type) !== 0) { + output.push("type") + } + if ((symbol.flags & ts.SymbolFlags.Value) !== 0 || (symbol.flags & ts.SymbolFlags.Namespace) !== 0) { + output.push("value") + } + return output +} + +const displayDeclarations = (nodes: ReadonlyArray): string => + nodes.map((node) => node.getText().replace(/\/\*\*[\s\S]*?\*\//g, "").replace(/\s+/g, " ").trim()).join("\n") + +const fingerprintDeclarations = (declarations: ReadonlyArray): string => { + const normalize = (value: unknown, parent?: string): unknown => { + if (Array.isArray(value)) { + return value.map((entry) => normalize(entry, parent)) + } + if (value !== null && typeof value === "object") { + const input = value as Record + return Object.fromEntries( + Object.entries(input).flatMap(([key, entry]) => { + if ((parent === "parameters" && key === "name") || key === "displayName") { + return [] + } + return [[key, normalize(entry, key)]] + }) + ) + } + return value + } + return fingerprint( + declarations.map((declaration) => ({ + ...declaration, + name: "", + parameters: declaration.parameters?.map((parameter) => ({ ...parameter, name: "" })) + })).map((declaration) => normalize(declaration)) + ) +} + +interface PendingEntity { + readonly symbol: ts.Symbol + readonly bucket: Bucket + readonly packageName: string + readonly routes: Array + readonly declarations: ReadonlyArray +} + +const symbolIdentity = (symbol: ts.Symbol, bucket: Bucket): string => { + const declarations = symbol.declarations ?? [] + return `${bucket}:${ + declarations.map((node) => `${node.getSourceFile().fileName}:${node.pos}:${node.end}`).sort().join("|") + }` +} + +const collectExports = ( + checker: ts.TypeChecker, + moduleSymbol: ts.Symbol, + entrypoint: Entrypoint, + pending: Map, + path: ReadonlyArray = [], + seenNamespaces: ReadonlySet = new Set() +): void => { + for ( + const exported of checker.getExportsOfModule(moduleSymbol).sort((left, right) => + left.name.localeCompare(right.name) + ) + ) { + let symbol = exported + if ((symbol.flags & ts.SymbolFlags.Alias) !== 0) { + symbol = checker.getAliasedSymbol(symbol) + } + const exportPath = [...path, exported.name] + const declarations = symbol.declarations ?? [] + for (const bucket of symbolBuckets(symbol)) { + const selected = bucketDeclarations(symbol, bucket) + if (selected.length === 0) { + continue + } + const key = symbolIdentity(symbol, bucket) + const existing = pending.get(key) + const route = { module: entrypoint.module, path: exportPath } + if (existing === undefined) { + pending.set(key, { + symbol, + bucket, + packageName: entrypoint.packageName, + routes: [route], + declarations: selected + }) + } else if ( + !existing.routes.some((candidate) => + candidate.module === route.module && candidate.path.join(".") === route.path.join(".") + ) + ) { + existing.routes.push(route) + } + } + const isNamespace = (symbol.flags & ts.SymbolFlags.Module) !== 0 + if (isNamespace && !seenNamespaces.has(symbol) && declarations.length > 0) { + collectExports( + checker, + symbol, + entrypoint, + pending, + exportPath, + new Set([...seenNamespaces, symbol]) + ) + } + } +} + +const canonicalRoute = (routes: ReadonlyArray): ImportRoute => + [...routes].sort((left, right) => + left.path.length - right.path.length || + right.module.split("/").length - left.module.split("/").length || + left.module.localeCompare(right.module) || + left.path.join(".").localeCompare(right.path.join(".")) + )[0]! + +export interface ExtractSnapshotOptions { + readonly repoRoot: string + readonly ref: string + readonly sha: string + readonly modules?: ReadonlyArray +} + +export class SnapshotExtractionError extends Schema.TaggedError()( + "SnapshotExtractionError", + { + message: Schema.String, + diagnostics: Schema.Array(SnapshotDiagnosticSchema) + } +) {} + +export const isSnapshotExtractionError = (u: unknown): u is SnapshotExtractionError => + Predicate.isTagged(u, "SnapshotExtractionError") + +const snapshotExtractionError = (diagnostics: ReadonlyArray): SnapshotExtractionError => + new SnapshotExtractionError({ + message: diagnostics.map((diagnostic) => `${diagnostic.code}: ${diagnostic.message}`).join("\n"), + diagnostics + }) + +const extractSnapshot = ( + options: ExtractSnapshotOptions, + discovered: DiscoveryResult, + path: Path.Path +): ApiSnapshot => { + const diagnostics: Array = discovered.missing.map((module) => ({ + code: "missing-entrypoint", + message: `Consumer-visible declaration entrypoint not found: ${module}`, + module + })) + if (diagnostics.length > 0) { + throw snapshotExtractionError(diagnostics) + } + const program = ts.createProgram({ + rootNames: discovered.entrypoints.map((entrypoint) => entrypoint.declarationFile), + options: { + module: ts.ModuleKind.NodeNext, + moduleResolution: ts.ModuleResolutionKind.NodeNext, + target: ts.ScriptTarget.ESNext, + skipLibCheck: true, + types: [], + noEmit: true + } + }) + // The declarations were already checked by the branch-native build. Re-checking + // them with the pinned extractor compiler would introduce lib-version drift. + const compilerDiagnostics = program.getSyntacticDiagnostics() + .filter((diagnostic) => diagnostic.category === ts.DiagnosticCategory.Error) + if (compilerDiagnostics.length > 0) { + throw snapshotExtractionError(compilerDiagnostics.map((diagnostic) => ({ + code: `typescript-${diagnostic.code}`, + message: ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n"), + source: diagnostic.file === undefined + ? undefined + : sourceLocation(path, options.repoRoot, diagnostic.file) + }))) + } + const checker = program.getTypeChecker() + const pending = new Map() + for (const entrypoint of discovered.entrypoints) { + const source = program.getSourceFile(entrypoint.declarationFile) + const symbol = source === undefined ? undefined : checker.getSymbolAtLocation(source) + if (source === undefined || symbol === undefined) { + diagnostics.push({ + code: "missing-module-symbol", + message: `Could not load module symbol for ${entrypoint.module}`, + module: entrypoint.module + }) + continue + } + collectExports(checker, symbol, entrypoint, pending) + } + + const entities: Array = [] + const publicSymbols = new Map() + for (const [key, pendingEntity] of pending) { + const route = canonicalRoute(pendingEntity.routes) + publicSymbols.set(key, `${route.module}#${route.path.join(".")}#${pendingEntity.bucket}`) + } + for (const pendingEntity of pending.values()) { + const route = canonicalRoute(pendingEntity.routes) + const declaration = pendingEntity.declarations[0] + if (declaration === undefined) { + continue + } + let serialized: ReadonlyArray + try { + serialized = pendingEntity.declarations.map((node) => + serializeDeclaration(node, { + checker, + path, + repoRoot: options.repoRoot, + typeParameters: new Map(), + publicSymbols + }) + ) + } catch (error) { + diagnostics.push({ + code: "unsupported-public-declaration", + message: error instanceof Error ? error.message : String(error), + module: route.module, + path: route.path, + source: sourceLocation(path, options.repoRoot, declaration) + }) + continue + } + const id = `${route.module}#${route.path.join(".")}#${pendingEntity.bucket}` + entities.push({ + id, + packageName: pendingEntity.packageName, + module: route.module, + path: route.path, + bucket: pendingEntity.bucket, + declarationKind: declarationKind(serialized), + importRoutes: [...pendingEntity.routes].sort((left, right) => + left.module.localeCompare(right.module) || left.path.join(".").localeCompare(right.path.join(".")) + ), + declarations: serialized, + displaySignature: displayDeclarations(pendingEntity.declarations), + fingerprint: fingerprintDeclarations(serialized), + documentation: documentation(pendingEntity.symbol, checker, route.module), + source: sourceLocation(path, options.repoRoot, declaration) + }) + } + if (diagnostics.length > 0) { + throw snapshotExtractionError(diagnostics) + } + + const packages = [...new Set(discovered.entrypoints.map((entrypoint) => entrypoint.packageName))].sort() + return { + version: 1, + compiler: { name: "typescript", version: ts.version }, + ref: options.ref, + sha: options.sha, + packages, + entrypoints: discovered.entrypoints.map((entrypoint) => ({ + ...entrypoint, + declarationFile: normalizedPath(path, options.repoRoot, entrypoint.declarationFile) + })), + entities: entities.sort((left, right) => left.id.localeCompare(right.id)), + diagnostics: [] + } +} + +export const snapshotCacheKey = ( + sha: string, + modules?: ReadonlyArray +): string => fingerprint(["snapshot-v4", sha, ts.version, modules === undefined ? "all" : [...modules].sort()]) + +export class Snapshotter extends Context.Service Effect.Effect< + ApiSnapshot, + ApiDiffError | SnapshotExtractionError + > +}>()("@effect/api-diff/Snapshotter") { + static readonly layerNoDependencies = Layer.effect( + Snapshotter, + Effect.gen(function*() { + const discovery = yield* Discovery + const path = yield* Path.Path + + const extract = Effect.fnUntraced(function*(options: ExtractSnapshotOptions) { + const discovered = yield* discovery.discoverEntrypoints(options.repoRoot, options.modules) + return yield* Effect.try({ + try: () => extractSnapshot(options, discovered, path), + catch: (cause) => + isSnapshotExtractionError(cause) + ? cause + : new ApiDiffError({ + message: `Could not extract the API snapshot for ${options.ref}`, + cause + }) + }) + }) + + return Snapshotter.of({ extract }) + }) + ) + + static readonly layer = this.layerNoDependencies.pipe( + Layer.provide(Discovery.layer) + ) +} diff --git a/repos/effect/packages/tools/api-diff/src/Worktrees.ts b/repos/effect/packages/tools/api-diff/src/Worktrees.ts new file mode 100644 index 0000000000..837cc98539 --- /dev/null +++ b/repos/effect/packages/tools/api-diff/src/Worktrees.ts @@ -0,0 +1,243 @@ +import * as Console from "effect/Console" +import * as Context from "effect/Context" +import * as Effect from "effect/Effect" +import * as FileSystem from "effect/FileSystem" +import * as Layer from "effect/Layer" +import * as Path from "effect/Path" +import * as Stream from "effect/Stream" +import * as ChildProcess from "effect/unstable/process/ChildProcess" +import * as ChildProcessSpawner from "effect/unstable/process/ChildProcessSpawner" +import { ApiDiffError, isApiDiffError } from "./Error.ts" +import { decodeJson } from "./Json.ts" +import type { ApiSnapshot } from "./Model.ts" +import { snapshotCacheKey, Snapshotter } from "./Snapshot.ts" + +export interface PrepareSnapshotOptions { + readonly repoRoot: string + readonly cacheRoot: string + readonly worktreesRoot: string + readonly name: "base" | "head" + readonly ref: string + readonly sha: string + readonly modules?: ReadonlyArray +} + +export class Worktrees extends Context.Service Effect.Effect + readonly prepareSnapshot: (options: PrepareSnapshotOptions) => Effect.Effect +}>()("@effect/api-diff/Worktrees") { + static readonly layerNoDependencies = Layer.effect( + Worktrees, + Effect.gen(function*() { + const fs = yield* FileSystem.FileSystem + const path = yield* Path.Path + const snapshotter = yield* Snapshotter + const spawner = yield* ChildProcessSpawner.ChildProcessSpawner + + const runCommandScoped = Effect.fnUntraced(function*( + command: string, + args: ReadonlyArray, + cwd: string + ) { + const display = `${command} ${args.join(" ")}` + const handle = yield* spawner.spawn( + ChildProcess.make(command, args, { cwd }) + ).pipe( + Effect.mapError((cause) => + new ApiDiffError({ + message: `Could not start command (${display})`, + cause + }) + ) + ) + const [output, exitCode] = yield* Effect.all([ + Stream.mkString(Stream.decodeText(handle.all)), + handle.exitCode + ], { concurrency: "unbounded" }).pipe( + Effect.mapError((cause) => + new ApiDiffError({ + message: `Could not run command (${display})`, + cause + }) + ) + ) + return { display, exitCode, output } + }) + + const runCommand = ( + command: string, + args: ReadonlyArray, + cwd: string + ): Effect.Effect<{ + readonly display: string + readonly exitCode: ChildProcessSpawner.ExitCode + readonly output: string + }, ApiDiffError> => Effect.scoped(runCommandScoped(command, args, cwd)) + + const runChecked = Effect.fnUntraced(function*( + command: string, + args: ReadonlyArray, + cwd: string + ) { + const result = yield* runCommand(command, args, cwd) + if (result.exitCode !== ChildProcessSpawner.ExitCode(0)) { + return yield* new ApiDiffError({ + message: `Command failed (${result.display}):\n${result.output}`.trim() + }) + } + return result.output.trim() + }) + + const enableStripInternal = Effect.fnUntraced(function*(worktree: string) { + const baseConfig = path.join(worktree, "tsconfig.base.json") + if (!(yield* fs.exists(baseConfig))) { + return + } + const source = yield* fs.readFileString(baseConfig) + if (/"stripInternal"\s*:\s*true/.test(source)) { + return + } + const updated = source.replace(/("stripInternal"\s*:\s*)false/, "$1true") + if (updated !== source) { + yield* fs.writeFileString(baseConfig, updated) + } + }) + + const hasProductionStripInternal = Effect.fnUntraced(function*(worktree: string) { + const candidates = [ + path.join(worktree, "tsconfig.base.json"), + path.join(worktree, "tsconfig.build.json"), + path.join(worktree, "packages", "effect", "tsconfig.build.json") + ] + for (const candidate of candidates) { + if ((yield* fs.exists(candidate)) && /"stripInternal"\s*:\s*true/.test(yield* fs.readFileString(candidate))) { + return true + } + } + return false + }) + + const buildWorktree = Effect.fnUntraced(function*(worktree: string) { + yield* enableStripInternal(worktree) + if (!(yield* hasProductionStripInternal(worktree))) { + return yield* new ApiDiffError({ + message: `No production TypeScript configuration enables stripInternal in ${worktree}` + }) + } + // Native test-only dependencies in old branches may not build on the + // current Node runtime. Declaration emission does not require dependency lifecycle scripts. + yield* runChecked("pnpm", ["install", "--frozen-lockfile", "--ignore-scripts"], worktree) + yield* runChecked("pnpm", ["build"], worktree) + }) + + const removeWorktree = (repoRoot: string, worktree: string): Effect.Effect => + runCommand("git", ["worktree", "remove", "--force", worktree], repoRoot).pipe( + Effect.flatMap((result) => + result.exitCode === ChildProcessSpawner.ExitCode(0) + ? Effect.void + : Console.error(result.output) + ), + Effect.catch((error) => Console.error(error.message)) + ) + + const resolveRef = Effect.fnUntraced(function*(repoRoot: string, ref: string) { + return yield* runChecked("git", ["rev-parse", "--verify", `${ref}^{commit}`], repoRoot) + }) + + const prepareSnapshotInternal = Effect.fnUntraced(function*(options: PrepareSnapshotOptions) { + const key = snapshotCacheKey(options.sha, options.modules) + const cacheLocation = path.join(options.cacheRoot, key, "snapshot.json") + if (yield* fs.exists(cacheLocation)) { + const source = yield* fs.readFileString(cacheLocation).pipe( + Effect.mapError((cause) => + new ApiDiffError({ + message: `Could not read cached snapshot ${cacheLocation}`, + cause + }) + ) + ) + const cached = (yield* decodeJson(source).pipe( + Effect.mapError((cause) => + new ApiDiffError({ + message: `Could not parse cached snapshot ${cacheLocation}`, + cause + }) + ) + )) as ApiSnapshot + return { ...cached, ref: options.ref, sha: options.sha } + } + + yield* fs.makeDirectory(options.worktreesRoot, { recursive: true }) + return yield* Effect.scoped(Effect.gen(function*() { + const runRoot = yield* fs.makeTempDirectoryScoped({ + directory: options.worktreesRoot, + prefix: `${options.name}-` + }) + const worktree = path.join(runRoot, "repo") + return yield* Effect.acquireUseRelease( + runChecked("git", ["worktree", "add", "--detach", worktree, options.sha], options.repoRoot), + () => + Effect.gen(function*() { + yield* buildWorktree(worktree) + const snapshot = yield* snapshotter.extract({ + repoRoot: worktree, + ref: options.ref, + sha: options.sha, + ...(options.modules === undefined ? {} : { modules: options.modules }) + }).pipe( + Effect.mapError((cause) => + isApiDiffError(cause) + ? cause + : new ApiDiffError({ + message: `Could not extract the ${options.name} API snapshot`, + cause + }) + ) + ) + let encoded: string | undefined + try { + encoded = JSON.stringify(snapshot) + } catch { + encoded = undefined + } + if (encoded !== undefined) { + yield* fs.makeDirectory(path.dirname(cacheLocation), { recursive: true }) + yield* fs.writeFileString(cacheLocation, encoded).pipe( + Effect.mapError((cause) => + new ApiDiffError({ + message: `Could not cache the ${options.name} API snapshot`, + cause + }) + ) + ) + } + return snapshot + }), + () => removeWorktree(options.repoRoot, worktree) + ) + })) + }) + + const prepareSnapshot = (options: PrepareSnapshotOptions): Effect.Effect => + prepareSnapshotInternal(options).pipe( + Effect.mapError((cause) => + isApiDiffError(cause) + ? cause + : new ApiDiffError({ + message: `Could not prepare the ${options.name} API snapshot`, + cause + }) + ) + ) + + return Worktrees.of({ + prepareSnapshot, + resolveRef + }) + }) + ) + + static readonly layer = this.layerNoDependencies.pipe( + Layer.provide(Snapshotter.layer) + ) +} diff --git a/repos/effect/packages/tools/api-diff/src/bin.ts b/repos/effect/packages/tools/api-diff/src/bin.ts new file mode 100644 index 0000000000..4455c3f21f --- /dev/null +++ b/repos/effect/packages/tools/api-diff/src/bin.ts @@ -0,0 +1,18 @@ +#!/usr/bin/env node +import * as NodeRuntime from "@effect/platform-node/NodeRuntime" +import * as NodeServices from "@effect/platform-node/NodeServices" +import * as Effect from "effect/Effect" +import * as Layer from "effect/Layer" +import * as Command from "effect/unstable/cli/Command" +import PackageJson from "../package.json" with { type: "json" } +import { ApiDiff } from "./ApiDiff.ts" +import { cli } from "./Cli.ts" + +const MainLayer = ApiDiff.layer.pipe( + Layer.provideMerge(NodeServices.layer) +) + +Command.run(cli, { version: PackageJson.version }).pipe( + Effect.provide(MainLayer), + NodeRuntime.runMain +) diff --git a/repos/effect/packages/tools/api-diff/test/Annotations.test.ts b/repos/effect/packages/tools/api-diff/test/Annotations.test.ts new file mode 100644 index 0000000000..97cf90f656 --- /dev/null +++ b/repos/effect/packages/tools/api-diff/test/Annotations.test.ts @@ -0,0 +1,59 @@ +import { loadAnnotations } from "@effect/api-diff/Annotations" +import * as NodeServices from "@effect/platform-node/NodeServices" +import { assert, describe, it } from "@effect/vitest" +import * as Effect from "effect/Effect" +import * as FileSystem from "effect/FileSystem" +import * as Path from "effect/Path" + +describe("migration annotations", () => { + it.effect("loads and merges YAML files", () => + Effect.gen(function*() { + const fs = yield* FileSystem.FileSystem + const path = yield* Path.Path + const root = yield* fs.makeTempDirectoryScoped({ prefix: "api-diff-annotations-" }) + yield* fs.writeFileString( + path.join(root, "Effect.yaml"), + [ + "effect/Effect#async:", + " replacement: Effect.callback", + " note: Rename the callback constructor.", + " example: Effect.callback((resume) => resume(Effect.void))", + "" + ].join("\n") + ) + yield* fs.writeFileString( + path.join(root, "Stream.yaml"), + [ + "effect/Stream#async:", + " replacement: Stream.callback", + " note: Rename the stream constructor.", + "" + ].join("\n") + ) + + const annotations = yield* loadAnnotations(root) + + assert.deepStrictEqual([...annotations], [ + ["effect/Effect#async", { + replacement: "Effect.callback", + note: "Rename the callback constructor.", + example: "Effect.callback((resume) => resume(Effect.void))" + }], + ["effect/Stream#async", { + replacement: "Stream.callback", + note: "Rename the stream constructor." + }] + ]) + }).pipe(Effect.provide(NodeServices.layer))) + + it.effect("treats a missing directory as no annotations", () => + Effect.gen(function*() { + const fs = yield* FileSystem.FileSystem + const path = yield* Path.Path + const root = yield* fs.makeTempDirectoryScoped({ prefix: "api-diff-annotations-" }) + + const annotations = yield* loadAnnotations(path.join(root, "missing")) + + assert.strictEqual(annotations.size, 0) + }).pipe(Effect.provide(NodeServices.layer))) +}) diff --git a/repos/effect/packages/tools/api-diff/test/Cli.test.ts b/repos/effect/packages/tools/api-diff/test/Cli.test.ts new file mode 100644 index 0000000000..568ba25b8b --- /dev/null +++ b/repos/effect/packages/tools/api-diff/test/Cli.test.ts @@ -0,0 +1,111 @@ +import { cli } from "@effect/api-diff" +import { ApiDiff } from "@effect/api-diff/ApiDiff" +import type { ApiEntity, ApiSnapshot } from "@effect/api-diff/Model" +import { Worktrees } from "@effect/api-diff/Worktrees" +import * as NodeServices from "@effect/platform-node/NodeServices" +import { assert, it } from "@effect/vitest" +import * as Effect from "effect/Effect" +import * as Exit from "effect/Exit" +import * as FileSystem from "effect/FileSystem" +import * as Layer from "effect/Layer" +import * as Path from "effect/Path" +import * as Command from "effect/unstable/cli/Command" + +const snapshot = (ref: string, sha: string, entities: ReadonlyArray = []): ApiSnapshot => ({ + version: 1, + compiler: { name: "typescript", version: "fixture" }, + ref, + sha, + packages: [], + entrypoints: [], + entities, + diagnostics: [] +}) + +const WorktreesTest = Layer.succeed( + Worktrees, + Worktrees.of({ + resolveRef: (_repoRoot, ref) => Effect.succeed(ref.repeat(40).slice(0, 40)), + prepareSnapshot: (options) => Effect.succeed(snapshot(options.ref, options.sha)) + }) +) + +const MainLayer = ApiDiff.layerNoDependencies.pipe( + Layer.provide(WorktreesTest), + Layer.provideMerge(NodeServices.layer) +) + +const removed: ApiEntity = { + id: "effect/Effect#removed#value", + packageName: "effect", + module: "effect/Effect", + path: ["removed"], + bucket: "value", + declarationKind: "variable", + importRoutes: [{ module: "effect/Effect", path: ["removed"] }], + declarations: [{ kind: "variable", name: "removed", type: { kind: "primitive", name: "string" } }], + displaySignature: "declare const removed: string", + fingerprint: "removed", + documentation: { stability: "stable" }, + source: { file: "Effect.d.ts", line: 1, column: 1 } +} + +const CheckLayer = ApiDiff.layerNoDependencies.pipe( + Layer.provide(Layer.succeed( + Worktrees, + Worktrees.of({ + resolveRef: (_repoRoot, ref) => Effect.succeed(ref.repeat(40).slice(0, 40)), + prepareSnapshot: (options) => + Effect.succeed(snapshot(options.ref, options.sha, options.name === "base" ? [removed] : [])) + }) + )), + Layer.provideMerge(NodeServices.layer) +) + +it.effect("compares refs without a migration map", () => + Effect.gen(function*() { + const fs = yield* FileSystem.FileSystem + const path = yield* Path.Path + const root = yield* fs.makeTempDirectoryScoped({ prefix: "api-diff-cli-" }) + const output = path.join(root, "output") + + yield* Command.runWith(cli, { version: "0.0.0" })([ + "--base-ref", + "a", + "--head-ref", + "b", + "--output", + output + ]) + + const diff = JSON.parse(yield* fs.readFileString(path.join(output, "diff.json"))) + assert.deepStrictEqual(diff.base, { ref: "a", sha: "a".repeat(40) }) + assert.deepStrictEqual(diff.head, { ref: "b", sha: "b".repeat(40) }) + }).pipe(Effect.provide(MainLayer))) + +it.effect("writes the migration document with default refs", () => + Effect.gen(function*() { + const fs = yield* FileSystem.FileSystem + const path = yield* Path.Path + const root = yield* fs.makeTempDirectoryScoped({ prefix: "api-diff-cli-" }) + const document = path.join(root, "v3-to-v4.md") + yield* fs.writeFileString(document, "# Existing\n\n## Import Map\n\nKeep this map.\n\n## API Renames\n") + + yield* Command.runWith(cli, { version: "0.0.0" })([ + "--write-doc", + document + ]) + + const source = yield* fs.readFileString(document) + assert(source.includes(`Base: \`v3\` (\`${"v3".repeat(40).slice(0, 40)}\`)`)) + assert(source.includes(`Head: \`main\` (\`${"origin/main".repeat(40).slice(0, 40)}\`)`)) + assert(source.includes("## Import Map\n\nKeep this map.")) + assert(source.includes("## API Reference")) + }).pipe(Effect.provide(MainLayer))) + +it.effect("fails check mode when guidance is missing", () => + Effect.gen(function*() { + const result = yield* Effect.exit(Command.runWith(cli, { version: "0.0.0" })(["--check"])) + + assert(Exit.isFailure(result)) + }).pipe(Effect.provide(CheckLayer))) diff --git a/repos/effect/packages/tools/api-diff/test/Diff.test.ts b/repos/effect/packages/tools/api-diff/test/Diff.test.ts new file mode 100644 index 0000000000..c06f644b26 --- /dev/null +++ b/repos/effect/packages/tools/api-diff/test/Diff.test.ts @@ -0,0 +1,219 @@ +import { diffSnapshots } from "@effect/api-diff/Diff" +import type { ApiDiff, ApiEntity, ApiSnapshot, DeclarationModel } from "@effect/api-diff/Model" +import { renderMarkdownReport } from "@effect/api-diff/Report" +import { assert, describe, it } from "@effect/vitest" + +const entity = ( + module: string, + name: string, + declaration: DeclarationModel, + hash: string +): ApiEntity => ({ + id: `${module}#${name}#value`, + packageName: module.split("/")[0]!, + module, + path: [name], + bucket: "value", + declarationKind: declaration.kind, + importRoutes: [{ module, path: [name] }], + declarations: [declaration], + displaySignature: `declare const ${name}: unknown`, + fingerprint: hash, + documentation: { stability: "stable" }, + source: { file: `${name}.d.ts`, line: 1, column: 1 } +}) + +const snapshot = (ref: string, entities: ReadonlyArray): ApiSnapshot => ({ + version: 1, + compiler: { name: "typescript", version: "fixture" }, + ref, + sha: ref.repeat(40).slice(0, 40), + packages: ["old", "new"], + entrypoints: [], + entities, + diagnostics: [] +}) + +describe("snapshot diff", () => { + it("matches renames, classifies signature changes, and separates suggestions", () => { + const base = snapshot("a", [ + entity("old/A", "changed", { + kind: "function", + name: "changed", + parameters: [{ name: "value", type: { kind: "primitive", name: "string" }, optional: false, rest: false }], + returnType: { kind: "primitive", name: "string" } + }, "before"), + entity("old/A", "similarName", { + kind: "variable", + name: "similarName", + type: { kind: "primitive", name: "string" } + }, "x"), + entity( + "old/A", + "removed", + { kind: "variable", name: "removed", type: { kind: "primitive", name: "string" } }, + "r" + ) + ]) + const head = snapshot("b", [ + entity("old/A", "changed", { + kind: "function", + name: "changed", + parameters: [ + { name: "value", type: { kind: "primitive", name: "string" }, optional: false, rest: false }, + { name: "count", type: { kind: "primitive", name: "number" }, optional: true, rest: false } + ], + returnType: { kind: "primitive", name: "number" } + }, "different"), + entity("old/A", "similarNames", { + kind: "variable", + name: "similarNames", + type: { kind: "primitive", name: "number" } + }, "y"), + entity("old/A", "added", { kind: "variable", name: "added", type: { kind: "primitive", name: "string" } }, "a") + ]) + const diff = diffSnapshots(base, head) + assert(diff.changes.some((change) => change.classification === "parameter-added")) + assert(diff.changes.some((change) => change.classification === "return-type-changed")) + assert(diff.changes.some((change) => change.baseApiId?.includes("similarName") && !change.authoritative)) + assert( + diff.changes.some((change) => + change.classification === "api-removed" && change.baseApiId?.includes("similarName") + ) + ) + assert( + diff.changes.some((change) => change.classification === "api-added" && change.headApiId?.includes("similarNames")) + ) + const report = renderMarkdownReport(diff) + assert(report.includes("Suggested replacements for removed APIs")) + assert(report.includes(base.sha)) + assert.deepStrictEqual(diff, diffSnapshots(base, head)) + }) + + it("suggests replacements across modules and preserves class facets", () => { + const variable = (name: string): DeclarationModel => ({ + kind: "variable", + name, + type: { kind: "primitive", name: "unknown" } + }) + const serviceInterface = (name: string, extraMember?: string): DeclarationModel => ({ + kind: "interface", + name, + members: [ + { + kind: "method", + name: "context", + parameters: [], + returnType: { kind: "primitive", name: "unknown" } + }, + { + kind: "method", + name: "of", + parameters: [], + returnType: { kind: "primitive", name: "unknown" } + }, + ...(extraMember === undefined + ? [] + : [{ + kind: "method", + name: extraMember, + parameters: [], + returnType: { kind: "primitive", name: "unknown" } + }]) + ] + }) + const withBucket = (api: ApiEntity, bucket: ApiEntity["bucket"]): ApiEntity => ({ + ...api, + id: `${api.module}#${api.path.join(".")}#${bucket}`, + bucket + }) + const effectService = { + ...entity("effect/Effect", "Service", variable("Service"), "effect-service"), + documentation: { + summary: "Creates a Context Tag and Layer for a service.", + stability: "stable" as const + } + } + const contextTagValue = entity("effect/Context", "Tag", variable("Tag"), "tag-value") + const contextTagType = withBucket( + entity("effect/Context", "Tag", serviceInterface("Tag"), "tag-type"), + "type" + ) + const contextServiceValue = entity("effect/Context", "Service", variable("Service"), "service-value") + const contextServiceType = withBucket( + entity("effect/Context", "Service", serviceInterface("Service", "use"), "service-type"), + "type" + ) + const layerMapService = entity("effect/LayerMap", "Service", variable("Service"), "layer-map-service") + const diff = diffSnapshots( + snapshot("a", [effectService, contextTagType, contextTagValue]), + snapshot("b", [contextServiceType, contextServiceValue, layerMapService]) + ) + const suggestions = diff.changes.filter((change) => !change.authoritative) + assert(suggestions.some((change) => + change.baseApiId === "effect/Effect#Service#value" && + change.headApiId === "effect/Context#Service#value" + )) + assert(suggestions.some((change) => + change.baseApiId === "effect/Context#Tag#type" && + change.headApiId === "effect/Context#Service#type" + )) + assert(suggestions.some((change) => + change.baseApiId === "effect/Context#Tag#value" && + change.headApiId === "effect/Context#Service#value" + )) + assert(!suggestions.some((change) => change.headApiId === "effect/LayerMap#Service#value")) + assert.strictEqual(diff.changes.filter((change) => change.classification === "api-removed").length, 3) + assert.strictEqual(diff.changes.filter((change) => change.classification === "api-added").length, 3) + }) + + it("classifies overload and parameter reordering", () => { + const signature = (name: string, parameters: ReadonlyArray<"left" | "right">): DeclarationModel => ({ + kind: "function", + name, + parameters: parameters.map((parameter) => ({ + name: parameter, + type: { kind: "primitive", name: "string" }, + optional: false, + rest: false + })), + returnType: { kind: "primitive", name: "string" } + }) + const before = entity("old/A", "ordered", signature("ordered", ["left", "right"]), "before") + const after = entity("old/A", "ordered", signature("ordered", ["right", "left"]), "after") + const diff = diffSnapshots( + snapshot("a", [before]), + snapshot("b", [after]) + ) + assert(diff.changes.some((change) => change.classification === "parameter-reordered")) + }) + + it("groups package and module changes by their delta names", () => { + const report = renderMarkdownReport( + { + version: 1, + base: { ref: "a", sha: "a".repeat(40) }, + head: { ref: "b", sha: "b".repeat(40) }, + changes: [ + { + id: "package-removed", + classification: "package-removed", + confidence: 1, + delta: { packageName: "@effect/old" }, + authoritative: true + }, + { + id: "module-added", + classification: "module-added", + confidence: 1, + delta: { to: ["effect/New"] }, + authoritative: true + } + ] + } satisfies ApiDiff + ) + assert(report.includes("| @effect/old | @effect/old | 1 |")) + assert(report.includes("| stable | effect/New | 1 |")) + assert(!report.includes("")) + }) +}) diff --git a/repos/effect/packages/tools/api-diff/test/Discovery.test.ts b/repos/effect/packages/tools/api-diff/test/Discovery.test.ts new file mode 100644 index 0000000000..0586fa66bb --- /dev/null +++ b/repos/effect/packages/tools/api-diff/test/Discovery.test.ts @@ -0,0 +1,62 @@ +import { Discovery } from "@effect/api-diff/Discovery" +import * as NodeServices from "@effect/platform-node/NodeServices" +import { assert, describe, it } from "@effect/vitest" +import * as Effect from "effect/Effect" +import * as FileSystem from "effect/FileSystem" +import * as Layer from "effect/Layer" +import { writeFixturePackage } from "./utils.ts" + +const MainLayer = Discovery.layer.pipe( + Layer.provideMerge(NodeServices.layer) +) + +describe("entrypoint discovery", () => { + it.effect("expands wildcards and respects null exclusions", () => + Effect.gen(function*() { + const fs = yield* FileSystem.FileSystem + const discovery = yield* Discovery + const root = yield* fs.makeTempDirectoryScoped({ prefix: "api-diff-discovery-" }) + yield* writeFixturePackage(root, { + "index.d.ts": `export * as Foo from "./Foo.js"\n`, + "Foo.d.ts": `export declare const value: string\n`, + "internal/Secret.d.ts": `export declare const secret: string\n` + }) + + const result = yield* discovery.discoverEntrypoints(root, [ + "@fixture/sample", + "@fixture/sample/Foo", + "@fixture/sample/internal/Secret" + ]) + assert.deepStrictEqual(result.entrypoints.map((entrypoint) => entrypoint.module), [ + "@fixture/sample", + "@fixture/sample/Foo" + ]) + assert.deepStrictEqual(result.missing, ["@fixture/sample/internal/Secret"]) + + const all = yield* discovery.discoverEntrypoints(root) + assert.deepStrictEqual(all.entrypoints.map((entrypoint) => entrypoint.module), [ + "@fixture/sample", + "@fixture/sample/Foo", + "@fixture/sample/index" + ]) + assert.deepStrictEqual(all.missing, []) + }).pipe(Effect.provide(MainLayer))) + + it.effect("supports conditional export targets", () => + Effect.gen(function*() { + const fs = yield* FileSystem.FileSystem + const discovery = yield* Discovery + const root = yield* fs.makeTempDirectoryScoped({ prefix: "api-diff-discovery-" }) + yield* writeFixturePackage(root, { + "Foo.d.ts": `export declare const value: string\n` + }, { + "./Foo": { + types: "./Foo.d.ts", + import: "./Foo.js" + } + }) + const result = yield* discovery.discoverEntrypoints(root, ["@fixture/sample/Foo"]) + assert.strictEqual(result.missing.length, 0) + assert.strictEqual(result.entrypoints[0]?.module, "@fixture/sample/Foo") + }).pipe(Effect.provide(MainLayer))) +}) diff --git a/repos/effect/packages/tools/api-diff/test/Error.test.ts b/repos/effect/packages/tools/api-diff/test/Error.test.ts new file mode 100644 index 0000000000..d9b5e6433a --- /dev/null +++ b/repos/effect/packages/tools/api-diff/test/Error.test.ts @@ -0,0 +1,25 @@ +import { ApiDiffError, isApiDiffError } from "@effect/api-diff/Error" +import { isSnapshotExtractionError, SnapshotExtractionError } from "@effect/api-diff/Snapshot" +import { assert, describe, it } from "@effect/vitest" +import { vi } from "vitest" +import type * as ApiDiffErrorModule from "../src/Error.ts" +import type * as SnapshotModule from "../src/Snapshot.ts" + +describe("tagged errors", () => { + it("recognizes errors from a reloaded module copy", async () => { + vi.resetModules() + const ForeignApiDiffError = await vi.importActual("../src/Error.ts") + const ForeignSnapshot = await vi.importActual("../src/Snapshot.ts") + + const apiDiffError = new ForeignApiDiffError.ApiDiffError({ message: "boom" }) + assert.isFalse(apiDiffError instanceof ApiDiffError) + assert.isTrue(isApiDiffError(apiDiffError)) + + const snapshotError = new ForeignSnapshot.SnapshotExtractionError({ + message: "boom", + diagnostics: [] + }) + assert.isFalse(snapshotError instanceof SnapshotExtractionError) + assert.isTrue(isSnapshotExtractionError(snapshotError)) + }) +}) diff --git a/repos/effect/packages/tools/api-diff/test/MigrationDoc.test.ts b/repos/effect/packages/tools/api-diff/test/MigrationDoc.test.ts new file mode 100644 index 0000000000..89f1e53e9f --- /dev/null +++ b/repos/effect/packages/tools/api-diff/test/MigrationDoc.test.ts @@ -0,0 +1,364 @@ +import type { MigrationAnnotation } from "@effect/api-diff/Annotations" +import { + extractImportMapSections, + markdownSafetyIssues, + renderMigrationDocument, + renderMissingAnnotations +} from "@effect/api-diff/MigrationDoc" +import type { ApiChange, ApiDiff } from "@effect/api-diff/Model" +import { assert, describe, it } from "@effect/vitest" + +const change = (change: Partial & Pick): ApiChange => ({ + id: `change-${change.classification}-${change.baseApiId ?? change.headApiId}`, + confidence: 1, + authoritative: true, + ...change +}) + +const diff: ApiDiff = { + version: 1, + base: { ref: "v3", sha: "a".repeat(40) }, + head: { ref: "main", sha: "b".repeat(40) }, + changes: [ + change({ + classification: "parameter-removed", + baseApiId: "effect/Zeta#changed#value", + headApiId: "effect/Zeta#changed#value", + before: "declare const changed: (value: string) => string", + after: "declare const changed: () => string" + }), + change({ + classification: "api-renamed", + baseApiId: "effect/Alpha#oldName#value", + headApiId: "effect/Alpha#newName#value" + }), + change({ classification: "api-removed", baseApiId: "effect/Alpha#oldName#value" }), + change({ + classification: "api-removed", + baseApiId: "effect/Beta#removed#value", + before: "declare const removed: string" + }), + change({ + classification: "api-moved", + baseApiId: "effect/Legacy#keptName#value", + headApiId: "effect/Current#keptName#value" + }), + change({ classification: "api-removed", baseApiId: "effect/Legacy#keptName#value" }), + change({ classification: "documentation-changed", baseApiId: "effect/Zeta#documented#value" }), + change({ + classification: "parameter-added", + baseApiId: "effect/Zeta#optional#value", + headApiId: "effect/Zeta#optional#value", + delta: { + before: [], + after: [{ name: "options", type: { kind: "primitive", name: "string" }, optional: true, rest: false }] + } + }), + change({ classification: "api-added", headApiId: "effect/Zeta#added#value" }) + ] +} + +const annotations = new Map([ + ["effect/Alpha#oldName", { + replacement: "Alpha.newName", + note: "Use the renamed function." + }] +]) + +describe("migration document", () => { + it("renders the selected API changes deterministically", () => { + const document = renderMigrationDocument( + diff, + annotations, + "## Import Map\n\n```text\neffect/Alpha -> effect/Alpha\n```\n" + ) + + assert.strictEqual( + document, + [ + "", + "", + "# v3 to v4 Migration Reference", + "", + `Base: \`v3\` (\`${"a".repeat(40)}\`)`, + "", + `Head: \`main\` (\`${"b".repeat(40)}\`)`, + "", + "This file is generated from the API diff and `migration/annotations/*.yaml`.", + "", + "## Import Map", + "", + "```text", + "effect/Alpha -> effect/Alpha", + "```", + "", + "## Removed Modules", + "", + "No modules were removed.", + "", + "## API Reference", + "", + "### `effect/Alpha`", + "", + "- `Alpha.oldName` -> `Alpha.newName`: Use the renamed function.", + "", + "### `effect/Beta`", + "", + "- `Beta.removed`: TODO: needs guidance", + "", + "### `effect/Zeta`", + "", + "- `Zeta.changed`: TODO: needs guidance", + "" + ].join("\n") + ) + assert(!document.includes("keptName")) + assert(!document.includes("documented")) + assert(!document.includes("optional")) + assert(!document.includes("added")) + }) + + it("lists unannotated ids grouped by module", () => { + assert.strictEqual( + renderMissingAnnotations(diff, annotations), + "effect/Beta:\n - effect/Beta#removed\neffect/Zeta:\n - effect/Zeta#changed\n" + ) + }) + + it("renders guidance and examples for detailed changes", () => { + const document = renderMigrationDocument( + diff, + new Map([...annotations, ["effect/Zeta#changed", { + replacement: "Zeta.changed()", + note: "Call the zero-argument form.", + example: "Zeta.changed()" + }]]), + "## Import Map\n" + ) + + assert(document.includes("**Replacement:** `Zeta.changed()`")) + assert(document.includes("Call the zero-argument form.")) + assert(document.includes("**Example**\n\n```ts\nZeta.changed()\n```")) + assert(!document.includes("**Before**")) + assert(!document.includes("**After**")) + }) + + it("escapes annotation prose while preserving inline code and fenced examples", () => { + const document = renderMigrationDocument( + diff, + new Map([...annotations, ["effect/Zeta#changed", { + replacement: "Zeta.changed()", + note: "Use ~effect/Zeta with Effect, *literal*, snake_case, `Context.Context`, and a bare ` marker.", + example: "const marker = `~effect/Zeta<*>`" + }]]), + "## Import Map\n" + ) + + assert(document.includes( + "Use \\~effect/Zeta with Effect\\, \\*literal\\*, snake\\_case, `Context.Context`, and a bare \\` marker." + )) + assert(document.includes("```ts\nconst marker = `~effect/Zeta<*>`\n```")) + assert.deepStrictEqual(markdownSafetyIssues(document), []) + assert.deepStrictEqual(markdownSafetyIssues("Unsafe ~~note and Effect"), [ + "line 1: unescaped \"~\"", + "line 1: unescaped \"~\"", + "line 1: unescaped \"<\"" + ]) + }) + + it("retains annotated removals that also have move suggestions", () => { + const serviceDiff: ApiDiff = { + ...diff, + changes: [ + change({ + classification: "api-moved", + baseApiId: "effect/Effect#Service#value", + headApiId: "effect/Context#Service#value", + authoritative: false + }), + change({ + classification: "api-removed", + baseApiId: "effect/Effect#Service#value", + before: "declare const Service: unknown" + }) + ] + } + const document = renderMigrationDocument( + serviceDiff, + new Map([["effect/Effect#Service", { + replacement: "Context.Service", + note: "Use the v4 service constructor." + }]]), + "## Import Map\n" + ) + + assert(document.includes("- `Effect.Service` -> `Context.Service`: Use the v4 service constructor.")) + }) + + it("renders removed modules and omits unchanged APIs moved with them", () => { + const moduleDiff: ApiDiff = { + ...diff, + changes: [ + change({ classification: "module-removed", delta: { from: "effect/Legacy", to: [] } }), + change({ + classification: "api-removed", + baseApiId: "effect/Legacy#unchanged#value", + before: "declare const unchanged: string" + }), + change({ + classification: "api-added", + headApiId: "effect/Current#unchanged#value", + after: "declare const unchanged: string" + }), + change({ + classification: "api-removed", + baseApiId: "effect/Legacy#changed#value", + before: "declare const changed: string" + }), + change({ + classification: "api-added", + headApiId: "effect/Current#changed#value", + after: "declare const changed: number" + }) + ] + } + const document = renderMigrationDocument( + moduleDiff, + new Map([["effect/Legacy#changed", { + replacement: "Current.changed", + note: "Use the changed API." + }]]), + "## Import Map\n\neffect/Legacy -> effect/Current\n" + ) + + assert(document.includes("## Removed Modules\n\n- `effect/Legacy` -> `effect/Current`")) + assert(!document.includes("Legacy.unchanged")) + assert(document.includes("- `Legacy.changed` -> `Current.changed`: Use the changed API.")) + assert.strictEqual( + extractImportMapSections(document), + "## Import Map\n\neffect/Legacy -> effect/Current\n" + ) + }) + + it("uses exact removed-module guidance for APIs without replacements", () => { + const moduleDiff: ApiDiff = { + ...diff, + changes: [ + change({ classification: "module-removed", delta: { from: "effect/Legacy", to: [] } }), + change({ + classification: "api-removed", + baseApiId: "effect/Legacy#removed#value", + before: "declare const removed: string" + }), + change({ + classification: "api-removed", + baseApiId: "effect/Legacy#replaced#value", + before: "declare const replaced: string" + }), + change({ + classification: "api-removed", + baseApiId: "effect/LegacyExtra#removed#value", + before: "declare const removed: string" + }), + change({ + classification: "api-removed", + baseApiId: "effect/Current#removed#value", + before: "declare const removed: string" + }) + ] + } + const moduleAnnotations = new Map([ + ["effect/Legacy", { + replacement: "none", + note: "The module was removed." + }], + ["effect/Legacy#replaced", { + replacement: "Current.replaced", + note: "Use the replacement." + }], + ["effect/LegacyExtra#removed", { + replacement: "none", + note: "This similarly prefixed module still exists." + }], + ["effect/Current#removed", { + replacement: "none", + note: "This module still exists." + }] + ]) + const document = renderMigrationDocument(moduleDiff, moduleAnnotations, "## Import Map\n") + + assert(document.includes("- `effect/Legacy` -> `none`: The module was removed.")) + assert(!document.includes("Legacy.removed")) + assert(document.includes("- `Legacy.replaced` -> `Current.replaced`: Use the replacement.")) + assert(document.includes("- `LegacyExtra.removed` -> `none`: This similarly prefixed module still exists.")) + assert(document.includes("- `Current.removed` -> `none`: This module still exists.")) + assert.strictEqual( + renderMissingAnnotations(moduleDiff, moduleAnnotations), + "All migration APIs and removed modules have guidance.\n" + ) + }) + + it("keeps removals from relocated modules and modules without module guidance", () => { + const moduleDiff: ApiDiff = { + ...diff, + changes: [ + change({ classification: "module-removed", delta: { from: "effect/Relocated", to: [] } }), + change({ classification: "module-removed", delta: { from: "effect/Unguided", to: [] } }), + change({ classification: "api-removed", baseApiId: "effect/Relocated#removed#value" }), + change({ classification: "api-removed", baseApiId: "effect/Unguided#removed#value" }) + ] + } + const moduleAnnotations = new Map([ + ["effect/Relocated", { + replacement: "effect/Current", + note: "The module moved." + }], + ["effect/Relocated#removed", { + replacement: "none", + note: "This API was not moved." + }], + ["effect/Unguided#removed", { + replacement: "none", + note: "This API was removed." + }] + ]) + const document = renderMigrationDocument(moduleDiff, moduleAnnotations, "## Import Map\n") + + assert(document.includes("- `effect/Relocated` -> `effect/Current`: The module moved.")) + assert(document.includes("- `Relocated.removed` -> `none`: This API was not moved.")) + assert(document.includes("- `Unguided.removed` -> `none`: This API was removed.")) + assert.strictEqual( + renderMissingAnnotations(moduleDiff, moduleAnnotations), + "All migration APIs and removed modules have guidance.\n" + ) + }) + + it("uses per-API annotations for split modules and reports missing module guidance", () => { + const moduleDiff: ApiDiff = { + ...diff, + changes: [ + change({ classification: "module-removed", delta: { from: "effect/Split", to: [] } }), + change({ classification: "module-removed", delta: { from: "effect/Unknown", to: [] } }), + change({ + classification: "api-removed", + baseApiId: "effect/Split#removed#value", + before: "declare const removed: string" + }) + ] + } + const splitAnnotations = new Map([["effect/Split#removed", { + replacement: "Current.removed", + note: "Use the split replacement." + }]]) + const document = renderMigrationDocument(moduleDiff, splitAnnotations, "## Import Map\n") + + assert( + document.includes("- `effect/Split`: No single module replacement; follow the curated per-API guidance below.") + ) + assert(document.includes("- `effect/Unknown`: TODO: needs module guidance")) + assert.strictEqual( + renderMissingAnnotations(moduleDiff, splitAnnotations), + "Removed modules:\n - effect/Unknown\n" + ) + }) +}) diff --git a/repos/effect/packages/tools/api-diff/test/Snapshot.test.ts b/repos/effect/packages/tools/api-diff/test/Snapshot.test.ts new file mode 100644 index 0000000000..4ba6129410 --- /dev/null +++ b/repos/effect/packages/tools/api-diff/test/Snapshot.test.ts @@ -0,0 +1,130 @@ +import { SnapshotExtractionError, Snapshotter } from "@effect/api-diff/Snapshot" +import * as NodeServices from "@effect/platform-node/NodeServices" +import { assert, describe, it } from "@effect/vitest" +import * as Effect from "effect/Effect" +import * as FileSystem from "effect/FileSystem" +import * as Layer from "effect/Layer" +import { writeFixturePackage } from "./utils.ts" + +const MainLayer = Snapshotter.layer.pipe( + Layer.provideMerge(NodeServices.layer) +) + +const source = ` +export declare function overloaded(value: A): A +export declare function overloaded(value: number, radix?: number): string + +export interface Parent { + readonly parent: string +} + +export interface Service extends Parent { + readonly value?: A + run(input: A): Promise + [key: \`item-\${string}\`]: unknown +} + +export declare class Client implements Service { + static readonly version: string + readonly parent: string + readonly value?: A + constructor(value?: A) + run(input: A): Promise +} + +export type Recursive = A | ReadonlyArray> +export type Conditional = A extends readonly [infer Head, ...infer Tail] ? Head | Tail[number] : never +export type Mapped = { readonly [K in keyof A as \`get\${Capitalize}\`]?: A[K] } +export type Imported = import("node:fs").PathLike +export declare const token: unique symbol + +export declare namespace merged { + const value: string + interface Options { + readonly enabled: boolean + } +} +export declare function merged(value: string): string +` + +describe("canonical snapshot", () => { + it.effect("extracts declarations, overloads, namespaces, re-exports, and canonical types deterministically", () => + Effect.gen(function*() { + const fs = yield* FileSystem.FileSystem + const snapshotter = yield* Snapshotter + const root = yield* fs.makeTempDirectoryScoped({ prefix: "api-diff-snapshot-" }) + yield* writeFixturePackage(root, { + "index.d.ts": `export * as Foo from "./Foo.js"\n`, + "Foo.d.ts": source + }) + + const options = { + repoRoot: root, + ref: "fixture", + sha: "0000000000000000000000000000000000000000", + modules: ["@fixture/sample", "@fixture/sample/Foo"] + } + const first = yield* snapshotter.extract(options) + const second = yield* snapshotter.extract(options) + assert.deepStrictEqual(first, second) + assert(first.entities.some((entity) => + entity.module === "@fixture/sample/Foo" && + entity.path.join(".") === "overloaded" && + entity.declarations.length === 2 + )) + const overloaded = first.entities.find((entity) => entity.id === "@fixture/sample/Foo#overloaded#value") + assert.deepStrictEqual(overloaded?.importRoutes, [ + { module: "@fixture/sample", path: ["Foo", "overloaded"] }, + { module: "@fixture/sample/Foo", path: ["overloaded"] } + ]) + const client = first.entities.filter((entity) => entity.path.join(".") === "Client") + assert.deepStrictEqual(client.map((entity) => entity.bucket), ["type", "value"]) + assert(first.entities.some((entity) => entity.path.join(".") === "merged.Options" && entity.bucket === "type")) + assert(first.entities.some((entity) => JSON.stringify(entity.declarations).includes("\"kind\":\"conditional\""))) + assert(first.entities.some((entity) => JSON.stringify(entity.declarations).includes("\"kind\":\"mapped\""))) + const imported = first.entities.find((entity) => entity.path.join(".") === "Imported") + assert(imported !== undefined, JSON.stringify(first.entities.map((entity) => entity.id))) + assert(JSON.stringify(imported.declarations).includes("\"externalPackage\":\"node:fs\"")) + }).pipe(Effect.provide(MainLayer))) + + it.effect("fails rather than silently omitting unsupported public declarations", () => + Effect.gen(function*() { + const fs = yield* FileSystem.FileSystem + const snapshotter = yield* Snapshotter + const root = yield* fs.makeTempDirectoryScoped({ prefix: "api-diff-snapshot-" }) + yield* writeFixturePackage(root, { + "Bad.d.ts": ` +declare namespace Other {} +export declare namespace Bad { + export import Alias = Other +} +` + }) + const failure = yield* Effect.flip(snapshotter.extract({ + repoRoot: root, + ref: "fixture", + sha: "0000000000000000000000000000000000000000", + modules: ["@fixture/sample/Bad"] + })) + assert(failure instanceof SnapshotExtractionError) + assert(failure.diagnostics.some((diagnostic) => diagnostic.code === "unsupported-public-declaration")) + }).pipe(Effect.provide(MainLayer))) + + it.effect("normalizes union order in structural fingerprints", () => + Effect.gen(function*() { + const fs = yield* FileSystem.FileSystem + const snapshotter = yield* Snapshotter + const leftRoot = yield* fs.makeTempDirectoryScoped({ prefix: "api-diff-snapshot-" }) + const rightRoot = yield* fs.makeTempDirectoryScoped({ prefix: "api-diff-snapshot-" }) + yield* writeFixturePackage(leftRoot, { "Choice.d.ts": "export type Choice = string | number\n" }) + yield* writeFixturePackage(rightRoot, { "Choice.d.ts": "export type Choice = number | string\n" }) + const extract = (repoRoot: string) => + snapshotter.extract({ + repoRoot, + ref: "fixture", + sha: "0000000000000000000000000000000000000000", + modules: ["@fixture/sample/Choice"] + }).pipe(Effect.map((snapshot) => snapshot.entities[0]?.fingerprint)) + assert.strictEqual(yield* extract(leftRoot), yield* extract(rightRoot)) + }).pipe(Effect.provide(MainLayer))) +}) diff --git a/repos/effect/packages/tools/api-diff/test/Worktrees.test.ts b/repos/effect/packages/tools/api-diff/test/Worktrees.test.ts new file mode 100644 index 0000000000..87d0c8f122 --- /dev/null +++ b/repos/effect/packages/tools/api-diff/test/Worktrees.test.ts @@ -0,0 +1,86 @@ +import type { ApiSnapshot } from "@effect/api-diff/Model" +import { Snapshotter } from "@effect/api-diff/Snapshot" +import { Worktrees } from "@effect/api-diff/Worktrees" +import { assert, it } from "@effect/vitest" +import * as Effect from "effect/Effect" +import * as FileSystem from "effect/FileSystem" +import * as Layer from "effect/Layer" +import * as Path from "effect/Path" +import * as Sink from "effect/Sink" +import * as Stream from "effect/Stream" +import * as ChildProcessSpawner from "effect/unstable/process/ChildProcessSpawner" + +const snapshot = { + version: 1, + compiler: { name: "typescript", version: "fixture" }, + ref: "main", + sha: "a".repeat(40), + packages: [], + entrypoints: [], + entities: [], + diagnostics: [], + toJSON() { + throw new RangeError("Invalid string length") + } +} as ApiSnapshot + +const SnapshotterTest = Layer.succeed( + Snapshotter, + Snapshotter.of({ extract: () => Effect.succeed(snapshot) }) +) + +const ChildProcessSpawnerTest = Layer.succeed( + ChildProcessSpawner.ChildProcessSpawner, + ChildProcessSpawner.make(() => + Effect.succeed(ChildProcessSpawner.makeHandle({ + pid: ChildProcessSpawner.ProcessId(1), + stdin: Sink.drain, + stdout: Stream.empty, + stderr: Stream.empty, + all: Stream.empty, + exitCode: Effect.succeed(ChildProcessSpawner.ExitCode(0)), + isRunning: Effect.succeed(false), + kill: () => Effect.void, + getInputFd: () => Sink.drain, + getOutputFd: () => Stream.empty, + unref: Effect.succeed(Effect.void) + })) + ) +) + +it.effect("skips the cache write when a snapshot is too large to serialize", () => { + let writes = 0 + const FileSystemTest = FileSystem.layerNoop({ + exists: (location) => Effect.succeed(location.endsWith("tsconfig.base.json")), + makeDirectory: () => Effect.void, + makeTempDirectoryScoped: () => Effect.succeed("/worktrees/head-1"), + readFileString: () => Effect.succeed(`{"compilerOptions":{"stripInternal":true}}`), + writeFileString: () => + Effect.sync(() => { + writes++ + }) + }) + const MainLayer = Worktrees.layerNoDependencies.pipe( + Layer.provide(Layer.mergeAll( + FileSystemTest, + Path.layer, + SnapshotterTest, + ChildProcessSpawnerTest + )) + ) + + return Effect.gen(function*() { + const worktrees = yield* Worktrees + const result = yield* worktrees.prepareSnapshot({ + repoRoot: "/repo", + cacheRoot: "/cache", + worktreesRoot: "/worktrees", + name: "head", + ref: snapshot.ref, + sha: snapshot.sha + }) + + assert.strictEqual(result, snapshot) + assert.strictEqual(writes, 0) + }).pipe(Effect.provide(MainLayer)) +}) diff --git a/repos/effect/packages/tools/api-diff/test/utils.ts b/repos/effect/packages/tools/api-diff/test/utils.ts new file mode 100644 index 0000000000..c296e4d89d --- /dev/null +++ b/repos/effect/packages/tools/api-diff/test/utils.ts @@ -0,0 +1,37 @@ +import * as Effect from "effect/Effect" +import * as FileSystem from "effect/FileSystem" +import * as Path from "effect/Path" + +export const writeFixturePackage = Effect.fnUntraced(function*( + repoRoot: string, + files: Readonly>, + exports: Readonly> = { + ".": "./index.js", + "./*": "./*.js", + "./internal/*": null + } +) { + const fs = yield* FileSystem.FileSystem + const path = yield* Path.Path + const root = path.join(repoRoot, "packages", "sample", "dist") + yield* fs.makeDirectory(root, { recursive: true }) + yield* fs.writeFileString( + path.join(root, "package.json"), + `${ + JSON.stringify( + { + name: "@fixture/sample", + version: "1.0.0", + exports + }, + null, + 2 + ) + }\n` + ) + for (const [name, source] of Object.entries(files)) { + const location = path.join(root, name) + yield* fs.makeDirectory(path.dirname(location), { recursive: true }) + yield* fs.writeFileString(location, source) + } +}) diff --git a/repos/effect/packages/tools/api-diff/tsconfig.json b/repos/effect/packages/tools/api-diff/tsconfig.json new file mode 100644 index 0000000000..b512ffd916 --- /dev/null +++ b/repos/effect/packages/tools/api-diff/tsconfig.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "../../../tsconfig.base.json", + "include": ["src"], + "compilerOptions": { + "types": ["node"] + } +} diff --git a/repos/effect/packages/tools/bundle/README.md b/repos/effect/packages/tools/bundle/README.md index d182b129ef..705744c8a2 100644 --- a/repos/effect/packages/tools/bundle/README.md +++ b/repos/effect/packages/tools/bundle/README.md @@ -53,7 +53,7 @@ pnpm bundle-analyze \ scratchpad/schema-arbitrary.ts ``` -The wrapper builds the current checkout with `pnpm build:tsgo` before generating +The wrapper builds the current checkout with `pnpm build` before generating analysis artifacts, so the bundle is not produced from stale `dist` files. By default, artifacts are written to `tmp/bundle-analysis`. Use `--output-dir` @@ -134,7 +134,7 @@ git worktree remove --force tmp/bundle-base The wrapper: -- builds the current checkout with `pnpm build:tsgo`; +- builds the current checkout with `pnpm build`; - creates or reuses `tmp/bundle-base` at the requested base ref; - builds the base checkout when needed and writes `tmp/bundle-base/.bundle-build-stamp` only after a successful build; diff --git a/repos/effect/packages/tools/bundle/docgen.json b/repos/effect/packages/tools/bundle/docgen.json deleted file mode 100644 index 0685d6d3b3..0000000000 --- a/repos/effect/packages/tools/bundle/docgen.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "$schema": "../../../node_modules/@effect/docgen/schema.json", - "exclude": ["src/bin.ts"] -} diff --git a/repos/effect/packages/tools/bundle/fixtures/brand.ts b/repos/effect/packages/tools/bundle/fixtures/brand.ts index 97f6fda732..2f15b21d49 100644 --- a/repos/effect/packages/tools/bundle/fixtures/brand.ts +++ b/repos/effect/packages/tools/bundle/fixtures/brand.ts @@ -2,4 +2,4 @@ import * as Brand from "effect/Brand" import * as Schema from "effect/Schema" type Positive = number & Brand.Brand<"Positive"> -const Positive = Brand.check(Schema.isGreaterThan(0)) +export const Positive = Brand.check(Schema.isGreaterThan(0)) diff --git a/repos/effect/packages/tools/bundle/fixtures/differ.ts b/repos/effect/packages/tools/bundle/fixtures/differ.ts index 30166be432..fae164af81 100644 --- a/repos/effect/packages/tools/bundle/fixtures/differ.ts +++ b/repos/effect/packages/tools/bundle/fixtures/differ.ts @@ -1,9 +1,9 @@ import * as Schema from "effect/Schema" const schema = Schema.Struct({ - id: Schema.Number, - name: Schema.String, - price: Schema.Number + a: Schema.String, + b: Schema.optional(Schema.FiniteFromString), + c: Schema.Array(Schema.String) }) -Schema.toDifferJsonPatch(schema) +export const differ = Schema.toDifferJsonPatch(schema) diff --git a/repos/effect/packages/tools/bundle/fixtures/optic.ts b/repos/effect/packages/tools/bundle/fixtures/optic.ts index de04dc36d0..2f0c786de1 100644 --- a/repos/effect/packages/tools/bundle/fixtures/optic.ts +++ b/repos/effect/packages/tools/bundle/fixtures/optic.ts @@ -3,4 +3,4 @@ import * as Optic from "effect/Optic" type S = { readonly a: number } const optic = Optic.id().key("a") -optic.getResult({ a: 1 }) +export const result = optic.getResult({ a: 1 }) diff --git a/repos/effect/packages/tools/bundle/fixtures/schema-class.ts b/repos/effect/packages/tools/bundle/fixtures/schema-class.ts new file mode 100644 index 0000000000..a20c480c01 --- /dev/null +++ b/repos/effect/packages/tools/bundle/fixtures/schema-class.ts @@ -0,0 +1,12 @@ +import * as Effect from "effect/Effect" +import * as Schema from "effect/Schema" + +class A extends Schema.Class("A")({ + a: Schema.String, + b: Schema.optional(Schema.FiniteFromString), + c: Schema.Array(Schema.String) +}) {} + +Schema.decodeUnknownEffect(A)({ a: "a", b: 1, c: ["c"] }).pipe( + Effect.runFork +) diff --git a/repos/effect/packages/tools/bundle/fixtures/schema-fromJsonSchemaDocument.ts b/repos/effect/packages/tools/bundle/fixtures/schema-fromJsonSchemaDocument.ts index fbde730416..78005b9717 100644 --- a/repos/effect/packages/tools/bundle/fixtures/schema-fromJsonSchemaDocument.ts +++ b/repos/effect/packages/tools/bundle/fixtures/schema-fromJsonSchemaDocument.ts @@ -1,6 +1,6 @@ import * as SchemaRepresentation from "effect/SchemaRepresentation" -const doc = SchemaRepresentation.fromJsonSchemaDocument({ +export const schema = SchemaRepresentation.fromJsonSchemaDocument({ "dialect": "draft-2020-12", "schema": { "type": "object", @@ -12,5 +12,3 @@ const doc = SchemaRepresentation.fromJsonSchemaDocument({ }, "definitions": {} }) - -console.dir(doc, { depth: null }) diff --git a/repos/effect/packages/tools/bundle/fixtures/schema-representation-roundtrip.ts b/repos/effect/packages/tools/bundle/fixtures/schema-representation-roundtrip.ts index 4a66b9c46b..a22278cce9 100644 --- a/repos/effect/packages/tools/bundle/fixtures/schema-representation-roundtrip.ts +++ b/repos/effect/packages/tools/bundle/fixtures/schema-representation-roundtrip.ts @@ -7,10 +7,9 @@ const schema = Schema.toCodecJson(Schema.Struct({ c: Schema.Array(Schema.String) })) -const json = Schema.encodeSync(SchemaRepresentation.DocumentFromJson)( - SchemaRepresentation.fromAST(schema.ast) -) +const json = SchemaRepresentation.toJson(SchemaRepresentation.toRepresentation(schema.ast)) -SchemaRepresentation.toSchema( - Schema.decodeSync(SchemaRepresentation.DocumentFromJson)(JSON.parse(JSON.stringify(json))) +export const roundtrip = SchemaRepresentation.fromRepresentation( + SchemaRepresentation.fromJson(JSON.parse(JSON.stringify(json))), + { revivers: [Schema.isFiniteReviver] } ) diff --git a/repos/effect/packages/tools/bundle/fixtures/schema-string-transformation.ts b/repos/effect/packages/tools/bundle/fixtures/schema-string-transformation.ts index fcab3b277e..305148efa2 100644 --- a/repos/effect/packages/tools/bundle/fixtures/schema-string-transformation.ts +++ b/repos/effect/packages/tools/bundle/fixtures/schema-string-transformation.ts @@ -15,6 +15,6 @@ const schema = Schema.String.pipe(Schema.decodeTo( }) )) -Schema.decodeUnknownEffect(schema)({ a: "a", b: 1, c: ["c"] }).pipe( +Schema.decodeUnknownEffect(schema)("a").pipe( Effect.runFork ) diff --git a/repos/effect/packages/tools/bundle/fixtures/schema-toArbitrary.ts b/repos/effect/packages/tools/bundle/fixtures/schema-toArbitrary.ts new file mode 100644 index 0000000000..191ad957de --- /dev/null +++ b/repos/effect/packages/tools/bundle/fixtures/schema-toArbitrary.ts @@ -0,0 +1,9 @@ +import * as Schema from "effect/Schema" + +const schema = Schema.Struct({ + a: Schema.String, + b: Schema.optional(Schema.FiniteFromString), + c: Schema.Array(Schema.String) +}) + +export const arbitrary = Schema.toArbitrary(schema) diff --git a/repos/effect/packages/tools/bundle/fixtures/schema-toArbitraryLazy.ts b/repos/effect/packages/tools/bundle/fixtures/schema-toArbitraryLazy.ts deleted file mode 100644 index 9c016cd66e..0000000000 --- a/repos/effect/packages/tools/bundle/fixtures/schema-toArbitraryLazy.ts +++ /dev/null @@ -1,9 +0,0 @@ -import * as Schema from "effect/Schema" - -const schema = Schema.Struct({ - a: Schema.String, - b: Schema.optional(Schema.FiniteFromString), - c: Schema.Array(Schema.String) -}) - -Schema.toArbitraryLazy(schema) diff --git a/repos/effect/packages/tools/bundle/fixtures/schema-toCodeDocument.ts b/repos/effect/packages/tools/bundle/fixtures/schema-toCodeDocument.ts index a664245ebf..f00de9532e 100644 --- a/repos/effect/packages/tools/bundle/fixtures/schema-toCodeDocument.ts +++ b/repos/effect/packages/tools/bundle/fixtures/schema-toCodeDocument.ts @@ -7,6 +7,4 @@ const schema = Schema.Struct({ c: Schema.Array(Schema.String) }) -const representation = Schema.toRepresentation(schema) - -SchemaRepresentation.toCodeDocument(SchemaRepresentation.toMultiDocument(representation)) +export const document = SchemaRepresentation.toCodeDocument(SchemaRepresentation.toRepresentations([schema.ast])) diff --git a/repos/effect/packages/tools/bundle/fixtures/schema-toCodecJson.ts b/repos/effect/packages/tools/bundle/fixtures/schema-toCodecJson.ts index 1babb48bd2..394831df73 100644 --- a/repos/effect/packages/tools/bundle/fixtures/schema-toCodecJson.ts +++ b/repos/effect/packages/tools/bundle/fixtures/schema-toCodecJson.ts @@ -6,4 +6,4 @@ const schema = Schema.Struct({ c: Schema.Array(Schema.String) }) -Schema.toCodecJson(schema) +export const codec = Schema.toCodecJson(schema) diff --git a/repos/effect/packages/tools/bundle/fixtures/schema-toEquivalence.ts b/repos/effect/packages/tools/bundle/fixtures/schema-toEquivalence.ts index a6f470c3c2..58ea7753f8 100644 --- a/repos/effect/packages/tools/bundle/fixtures/schema-toEquivalence.ts +++ b/repos/effect/packages/tools/bundle/fixtures/schema-toEquivalence.ts @@ -6,4 +6,4 @@ const schema = Schema.Struct({ c: Schema.Array(Schema.String) }) -Schema.toEquivalence(schema) +export const equivalence = Schema.toEquivalence(schema) diff --git a/repos/effect/packages/tools/bundle/fixtures/schema-toFormatter.ts b/repos/effect/packages/tools/bundle/fixtures/schema-toFormatter.ts index ee619b5206..258b7a6aac 100644 --- a/repos/effect/packages/tools/bundle/fixtures/schema-toFormatter.ts +++ b/repos/effect/packages/tools/bundle/fixtures/schema-toFormatter.ts @@ -6,4 +6,4 @@ const schema = Schema.Struct({ c: Schema.Array(Schema.String) }) -Schema.toFormatter(schema) +export const formatter = Schema.toFormatter(schema) diff --git a/repos/effect/packages/tools/bundle/fixtures/schema-toJsonSchemaDocument.ts b/repos/effect/packages/tools/bundle/fixtures/schema-toJsonSchemaDocument.ts index 53590fb958..33c7fd90ce 100644 --- a/repos/effect/packages/tools/bundle/fixtures/schema-toJsonSchemaDocument.ts +++ b/repos/effect/packages/tools/bundle/fixtures/schema-toJsonSchemaDocument.ts @@ -6,4 +6,4 @@ const schema = Schema.Struct({ c: Schema.Array(Schema.String) }) -Schema.toJsonSchemaDocument(schema) +export const document = Schema.toJsonSchemaDocument(schema) diff --git a/repos/effect/packages/tools/bundle/fixtures/schema-toRepresentation.ts b/repos/effect/packages/tools/bundle/fixtures/schema-toRepresentation.ts index cf54a10128..63f72369bd 100644 --- a/repos/effect/packages/tools/bundle/fixtures/schema-toRepresentation.ts +++ b/repos/effect/packages/tools/bundle/fixtures/schema-toRepresentation.ts @@ -6,4 +6,4 @@ const schema = Schema.Struct({ c: Schema.Array(Schema.String) }) -Schema.toRepresentation(schema) +export const representation = Schema.toRepresentation(schema) diff --git a/repos/effect/packages/tools/bundle/package.json b/repos/effect/packages/tools/bundle/package.json index a32e5d2307..8ad9b76840 100644 --- a/repos/effect/packages/tools/bundle/package.json +++ b/repos/effect/packages/tools/bundle/package.json @@ -8,7 +8,7 @@ "homepage": "https://effect.website", "repository": { "type": "git", - "url": "https://github.com/Effect-TS/effect-smol.git", + "url": "https://github.com/Effect-TS/effect.git", "directory": "packages/tools/bundle" }, "sideEffects": [], @@ -28,7 +28,6 @@ ], "scripts": { "build": "tsc -b tsconfig.src.json && pnpm babel", - "build:tsgo": "tsgo -b tsconfig.src.json && pnpm babel", "babel": "babel dist --plugins annotate-pure-calls --out-dir dist --source-maps", "check": "tsc -b tsconfig.json", "compare": "node src/bin.ts compare", @@ -42,11 +41,11 @@ "@rollup/plugin-terser": "^1.0.0", "effect": "workspace:^", "glob": "^13.0.6", - "rollup": "^4.60.3", + "rollup": "^4.62.3", "rollup-plugin-esbuild": "^6.2.1", "rollup-plugin-visualizer": "^7.0.1" }, "devDependencies": { - "@types/node": "^25.7.0" + "@types/node": "^26.1.2" } } diff --git a/repos/effect/packages/tools/bundle/tsconfig.fixtures.json b/repos/effect/packages/tools/bundle/tsconfig.fixtures.json index d96a29d400..545dd84e79 100644 --- a/repos/effect/packages/tools/bundle/tsconfig.fixtures.json +++ b/repos/effect/packages/tools/bundle/tsconfig.fixtures.json @@ -1,5 +1,5 @@ { - "$schema": "http://json.schemastore.org/tsconfig", + "$schema": "https://json.schemastore.org/tsconfig", "extends": "../../../tsconfig.base.json", "include": ["fixtures"], "compilerOptions": { diff --git a/repos/effect/packages/tools/bundle/tsconfig.json b/repos/effect/packages/tools/bundle/tsconfig.json index 8857581181..eef9e2be39 100644 --- a/repos/effect/packages/tools/bundle/tsconfig.json +++ b/repos/effect/packages/tools/bundle/tsconfig.json @@ -1,5 +1,5 @@ { - "$schema": "http://json.schemastore.org/tsconfig", + "$schema": "https://json.schemastore.org/tsconfig", "extends": "../../../tsconfig.base.json", "include": [], "references": [ diff --git a/repos/effect/packages/tools/bundle/tsconfig.src.json b/repos/effect/packages/tools/bundle/tsconfig.src.json index fd2e4d0172..0d98c0e107 100644 --- a/repos/effect/packages/tools/bundle/tsconfig.src.json +++ b/repos/effect/packages/tools/bundle/tsconfig.src.json @@ -1,5 +1,5 @@ { - "$schema": "http://json.schemastore.org/tsconfig", + "$schema": "https://json.schemastore.org/tsconfig", "extends": "../../../tsconfig.base.json", "include": ["src"], "compilerOptions": { diff --git a/repos/effect/packages/tools/bundle/vitest.config.ts b/repos/effect/packages/tools/bundle/vitest.config.ts deleted file mode 100644 index c8a52c1826..0000000000 --- a/repos/effect/packages/tools/bundle/vitest.config.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { mergeConfig, type ViteUserConfig } from "vitest/config" -import shared from "../../../vitest.shared.ts" - -const config: ViteUserConfig = {} - -export default mergeConfig(shared, config) diff --git a/repos/effect/packages/tools/docgen/CHANGELOG.md b/repos/effect/packages/tools/docgen/CHANGELOG.md new file mode 100644 index 0000000000..c122149386 --- /dev/null +++ b/repos/effect/packages/tools/docgen/CHANGELOG.md @@ -0,0 +1,277 @@ +# @effect/docgen + +## 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 + - @effect/platform-node@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 + - @effect/platform-node@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), [`31efc5c`](https://github.com/Effect-TS/effect/commit/31efc5c4eabbe37f9ddab030ef760926e6ff3d24), [`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 + - @effect/platform-node@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), [`545d876`](https://github.com/Effect-TS/effect/commit/545d8767648cbbbf1820361662c0c1e10768db6a), [`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), [`721b9f0`](https://github.com/Effect-TS/effect/commit/721b9f0d320e50f3e2324c2cd1f43c6643af3ca0), [`01af079`](https://github.com/Effect-TS/effect/commit/01af079c189d1fc5067d3b1933b2870c4baf2693), [`32a59e8`](https://github.com/Effect-TS/effect/commit/32a59e8058b1ec9738cb083cf1cb116b393ca114)]: + - effect@4.0.0-beta.104 + - @effect/platform-node@4.0.0-beta.104 + +## 4.0.0-beta.103 + +### Patch Changes + +- [#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. + +- [#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 + - @effect/platform-node@4.0.0-beta.103 + +## 4.0.0-beta.102 + +### Major Changes + +- [#6556](https://github.com/Effect-TS/effect/pull/6556) [`19eeda6`](https://github.com/Effect-TS/effect/commit/19eeda68bac6d65bd24b9a9ca30565f35dbae565) Thanks @fubhy! - Migrate `@effect/docgen` into the Effect monorepo and update it to Effect 4 while retaining existing behavior. + +### Patch Changes + +- [#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 + - @effect/platform-node@4.0.0-beta.102 + +## 0.5.2 + +### Patch Changes + +- 3b595aa: Remove duplicate logger + +## 0.5.1 + +### Patch Changes + +- 0b3b34e: Typecheck examples deeply nested within namespaces + +## 0.5.0 + +### Minor Changes + +- 8a0eb55: Support custom code fences when rendering examples + +## 0.4.7 + +### Patch Changes + +- e3ae139: Typecheck namespace examples + +## 0.4.6 + +### Patch Changes + +- fcd5649: Support examples enclosed in Extended Markdown [code blocks](https://www.markdownguide.org/extended-syntax/#fenced-code-blocks) +- 95f136e: Support deeply nested namespaces. + + Previously, the docgen would fail with a `[Markdown] Unsupported namespace nesting: 4` error. With this change all namespace headers at depth level 3 and above would be rendered using H4 elements. + +## 0.4.5 + +### Patch Changes + +- 8959440: Fixes the type checking and execution of examples on Windows + +## 0.4.4 + +### Patch Changes + +- 00ce7a0: upgrade ts-morph to 23.0.0 +- 5b888e5: srcDir and outDir fields in docgen.json are currently ignored. With this patch, they are taken into account + +## 0.4.3 + +### Patch Changes + +- 7add2b9: update dependencies + +## 0.4.2 + +### Patch Changes + +- 619a0e3: use @effect/markdown-toc instead of github dependency + +## 0.4.1 + +### Patch Changes + +- b9bfab0: add reporting of `tsc` and `tsx` errors, closes #66 + +## 0.4.0 + +### Minor Changes + +- 5fbec18: update effect + +### Patch Changes + +- 08e8347: use ConfigProvider to load configuration for docgen + +## 0.3.8 + +### Patch Changes + +- 8abf24f: Core: do not swallow examples errors + +## 0.3.7 + +### Patch Changes + +- 2573662: update effect + +## 0.3.6 + +### Patch Changes + +- d58b355: chore: add defaults to `schema.json` + +## 0.3.5 + +### Patch Changes + +- bcaf971: fix glob pattern on windows + +## 0.3.4 + +### Patch Changes + +- 4e72aee: Re-added schema.json + +## 0.3.3 + +### Patch Changes + +- 3ee6dd1: Improve error output when spawning child process fails +- 73a1d93: build with tsup + +## 0.3.2 + +### Patch Changes + +- 16fc976: Updated dependencies + +## 0.3.1 + +### Patch Changes + +- b799243: add `--no-examples` option + +## 0.3.0 + +### Minor Changes + +- e08edb1: Modernized and switched to a `tsc` and `tsx` based setup with support for `NodeNext` module resolution. + +## 0.2.1 + +### Patch Changes + +- 2677b9d: updated effect +- 743ce06: change theme default + +## 0.2.0 + +### Minor Changes + +- ecd00a5: update effect and add effect/platform-node dependency + +## 0.1.8 + +### Patch Changes + +- 5411c71: Support for parsing "export \* as namespace" + +## 0.1.7 + +### Patch Changes + +- b94de9f: add support for `export * from ...` + +## 0.1.6 + +### Patch Changes + +- 172ac81: Fix parsing regression caused by compilerOptions parsing + +## 0.1.5 + +### Patch Changes + +- 85301ea: Add support for resolving compilerOptions from tsconfig files + +## 0.1.4 + +### Patch Changes + +- 8be0092: add support for namespaces +- 8be0092: BugFix: remove stale modules from /docs folder + +## 0.1.3 + +### Patch Changes + +- d8006f3: patch markdown-toc to prevent duplicate links +- 514f73f: update to effect framework package + +## 0.1.2 + +### Patch Changes + +- 115b996: fix formatting of the \_config.yml output by docgen +- 115b996: upgrade dependencies + +## 0.1.1 + +### Patch Changes + +- 2a909a1: fix config handling + +## 0.1.0 + +### Minor Changes + +- eb5ef08: rename docs-ts.json to docgen.json + +## 0.0.3 + +### Patch Changes + +- d139f78: ignore internal classes + +## 0.0.2 + +### Patch Changes + +- 4e88501: fix shebang line + +## 0.0.1 + +### Patch Changes + +- 4faa066: add initial code diff --git a/repos/effect/packages/tools/docgen/LICENSE b/repos/effect/packages/tools/docgen/LICENSE new file mode 100644 index 0000000000..f8f4392a07 --- /dev/null +++ b/repos/effect/packages/tools/docgen/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020-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/tools/docgen/README.md b/repos/effect/packages/tools/docgen/README.md new file mode 100644 index 0000000000..391b144aff --- /dev/null +++ b/repos/effect/packages/tools/docgen/README.md @@ -0,0 +1,139 @@ +An opinionated documentation generator for Effect projects. + +# Credits + +This library was inspired by the following projects: + +- [docs-ts](https://github.com/gcanti/docs-ts) + +# Setup + +1. Install `@effect/docgen` as a dev dependency: + +```shell +pnpm add @effect/docgen -D +``` + +2. (Optional) Add a `docgen.json` configuration file. + +```json +{ + "$schema": "node_modules/@effect/docgen/schema.json" +} +``` + +3. Add the following script to your `package.json` file: + +```json +{ + "scripts": { + "docgen": "docgen" + } +} +``` + +> [!WARNING] +> To use "@effect/docgen", Node.js v18 or above is required. + +## Example Configuration + +The `docgen.json` configuration file allows you to customize `docgen`'s behavior. Here's an example configuration: + +```json +{ + "exclude": ["src/internal/**/*.ts"], + "parseCompilerOptions": { + "noEmit": true, + "strict": true, + "skipLibCheck": true, + "moduleResolution": "Bundler", + "target": "ES2022", + "lib": ["ES2022", "DOM"], + "paths": { + "@effect/": ["./src/index.js"], + "@effect//test/*": ["./test/*.js"], + "@effect//examples/*": ["./examples/*.js"], + "@effect//*": ["./src/*.js"] + } + }, + "examplesCompilerOptions": { + "noEmit": true, + "strict": true, + "skipLibCheck": true, + "moduleResolution": "Bundler", + "target": "ES2022", + "lib": ["ES2022", "DOM"], + "paths": { + "@effect/": ["../../src/index.js"], + "@effect//test/*": ["../../test/*.js"], + "@effect//examples/*": ["../../examples/*.js"], + "@effect//*": ["../../src/*.js"] + } + } +} +``` + +# Supported JSDoc Tags + +| Tag | Description | Default | +| ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------- | +| `@category` | Groups associated module exports together in the generated documentation. | `'utils'` | +| `@example` | Allows usage examples to be provided for your source code. All examples are type checked using `tsc`. Examples are also run using `tsx` and the NodeJS [assert](https://nodejs.org/api/assert.html) module can be used for on-the-fly testing. | | +| `@since` | Allows for documenting most recent library version in which a given piece of source code was updated. | | +| `@deprecated` | Marks source code as deprecated, which will ~~strikethrough~~ the name of the annotated module or function in the generated documentation. | `false` | +| `@internal` | Prevents `docgen` from generating documentation for the annotated block of code. Additionally, if the `stripInternal` flag is set to `true` in `tsconfig.json`, TypeScript will not emit declarations for the annotated code. | | +| `@ignore` | Prevents `docgen` from generating documentation for the annotated block of code. | | + +By default, `docgen` will search for files in the `src` directory and will output generated files into a `docs` directory. For information on how to configure `docgen`, see the [Configuration](#configuration) section below. + +# Configuration + +`docgen` is meant to be a zero-configuration command-line tool by default. However, there are several configuration settings that can be specified for `docgen`. To customize the configuration of `docgen`, create a `docgen.json` file in the root directory of your project and indicate the custom configuration parameters that the tool should use when generating documentation. + +The `docgen.json` configuration file adheres to the following interface: + +```ts +interface Config { + readonly projectHomepage?: string + readonly srcLink?: string + readonly srcDir?: string + readonly outDir?: string + readonly theme?: string + readonly enableSearch?: boolean + readonly enforceDescriptions?: boolean + readonly enforceExamples?: boolean + readonly enforceVersion?: boolean + readonly tscExecutable?: string + readonly exclude?: ReadonlyArray + readonly parseCompilerOptions?: string | Record + readonly examplesCompilerOptions?: string | Record +} +``` + +The following table describes each configuration parameter, its purpose, and its default value. + +| Parameter | Description | Default Value | +| :---------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :--------------------------------- | +| projectHomepage | Will link to the project homepage from the [Auxiliary Links](https://pmarsceill.github.io/just-the-docs/docs/navigation-structure/#auxiliary-links) of the generated documentation. | `homepage` in `package.json` | +| srcLink | Will link to the project source code. | `{projectHomepage}/blob/main/src/` | +| srcDir | The directory in which `docgen` will search for TypeScript files to parse. | `'src'` | +| outDir | The directory to which `docgen` will generate its output markdown documents. | `'docs'` | +| theme | The theme that `docgen` will specify should be used for GitHub Docs in the generated `_config.yml` file. | `'mikearnaldi/just-the-docs'` | +| enableSearch | Whether or not search should be enabled for GitHub Docs in the generated `_config.yml` file. | `true` | +| enforceDescriptions | Whether or not descriptions for each module export should be required. | `false` | +| enforceExamples | Whether or not `@example` tags for each module export should be required. (**Note**: examples will not be enforced in module documentation) | `false` | +| enforceVersion | Whether or not `@since` tags for each module export should be required. | `true` | +| tscExecutable | The path to the TypeScript compiler executable that docgen should use when invoking the compiler programmatically. | `'tsc'` | +| exclude | An array of glob strings specifying files that should be excluded from the documentation. | `[]` | +| parseCompilerOptions | tsconfig for parsing options (or path to a tsconfig) | {} | +| examplesCompilerOptions | tsconfig for the examples options (or path to a tsconfig) | {} | + +# FAQ + +**Q:** For functions that have overloaded definitions, is it possible to document each overload separately? + +**A:** No, `docgen` will use the documentation provided for the first overload of a function in its generated output. + +# License + +The MIT License (MIT) diff --git a/repos/effect/packages/tools/docgen/package.json b/repos/effect/packages/tools/docgen/package.json new file mode 100644 index 0000000000..2023025261 --- /dev/null +++ b/repos/effect/packages/tools/docgen/package.json @@ -0,0 +1,84 @@ +{ + "name": "@effect/docgen", + "version": "4.0.0-beta.107", + "type": "module", + "license": "MIT", + "description": "An opinionated documentation generator for Effect projects", + "engines": { + "node": ">=18.0.0" + }, + "homepage": "https://effect.website", + "repository": { + "type": "git", + "url": "https://github.com/Effect-TS/effect.git", + "directory": "packages/tools/docgen" + }, + "sideEffects": [], + "bin": { + "docgen": "./src/bin.ts" + }, + "exports": { + "./package.json": "./package.json", + "./schema.json": "./schema.json", + ".": "./src/index.ts", + "./*": "./src/*.ts", + "./index": null, + "./bin": null + }, + "files": [ + "src/**/*.ts", + "schema.json", + "dist/**/*.js", + "dist/**/*.js.map", + "dist/**/*.d.ts", + "dist/**/*.d.ts.map", + "AGENTS.md", + "CLAUDE.md", + "ai-docs/**/*" + ], + "publishConfig": { + "access": "public", + "provenance": true, + "bin": { + "docgen": "./dist/bin.js" + }, + "exports": { + "./package.json": "./package.json", + "./schema.json": "./schema.json", + ".": "./dist/index.js", + "./*": "./dist/*.js", + "./index": null, + "./bin": 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" + }, + "dependencies": { + "@babel/code-frame": "^8.0.0", + "@effect/markdown-toc": "^0.1.0", + "@effect/platform-node": "workspace:^", + "chalk": "^5.6.2", + "doctrine": "^3.0.0", + "effect": "workspace:^", + "glob": "^13.0.6", + "prettier": "^3.6.2", + "ts-morph": "^27.0.2", + "tsconfck": "^3.1.6" + }, + "peerDependencies": { + "tsx": ">=4.19.3 <5.0.0", + "typescript": ">=5.8.2 <7.0.0" + }, + "devDependencies": { + "@effect/vitest": "workspace:^", + "@types/babel__code-frame": "^7.0.6", + "@types/doctrine": "^0.0.9", + "@types/node": "^26.1.2", + "tsx": "^4.23.1", + "typescript": "^6.0.3", + "vitest": "^4.1.10" + } +} diff --git a/repos/effect/packages/tools/docgen/schema.json b/repos/effect/packages/tools/docgen/schema.json new file mode 100644 index 0000000000..ac151cdecb --- /dev/null +++ b/repos/effect/packages/tools/docgen/schema.json @@ -0,0 +1,113 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$defs": { + "ConfigurationSchema": { + "type": "object", + "required": [], + "properties": { + "$schema": { + "type": "string" + }, + "projectHomepage": { + "type": "string", + "description": "Will link to the project homepage from the Auxiliary Links of the generated documentation." + }, + "srcLink": { + "type": "string", + "description": "Will link to the project source code." + }, + "srcDir": { + "type": "string", + "description": "The directory in which docgen will search for TypeScript files to parse.", + "default": "src" + }, + "outDir": { + "type": "string", + "description": "The directory to which docgen will generate its output markdown documents.", + "default": "docs" + }, + "theme": { + "type": "string", + "description": "The theme that docgen will specify should be used for GitHub Docs in the generated _config.yml file.", + "default": "mikearnaldi/just-the-docs" + }, + "enableSearch": { + "type": "boolean", + "description": "Whether or not search should be enabled for GitHub Docs in the generated _config.yml file.", + "default": true + }, + "enforceDescriptions": { + "type": "boolean", + "description": "Whether or not descriptions for each module export should be required.", + "default": false + }, + "enforceExamples": { + "type": "boolean", + "description": "Whether or not @example tags for each module export should be required. (Note: examples will not be enforced in module documentation)", + "default": false + }, + "enforceVersion": { + "type": "boolean", + "description": "Whether or not @since tags for each module export should be required.", + "default": true + }, + "tscExecutable": { + "type": "string", + "description": "The path to the TypeScript compiler executable that docgen should use when invoking the compiler programmatically.", + "default": "tsc" + }, + "runExamples": { + "type": "boolean", + "description": "Whether or not docgen should attempt to run example code snippets and include the output in the generated documentation.", + "default": false + }, + "exclude": { + "type": "array", + "items": { + "type": "string" + }, + "description": "An array of glob strings specifying files that should be excluded from the documentation.", + "default": [] + }, + "parseCompilerOptions": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object", + "required": [], + "properties": {}, + "additionalProperties": { + "$id": "/schemas/unknown", + "title": "unknown" + } + } + ], + "description": "tsconfig for parsing options (or path to a tsconfig)", + "default": {} + }, + "examplesCompilerOptions": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object", + "required": [], + "properties": {}, + "additionalProperties": { + "$id": "/schemas/unknown", + "title": "unknown" + } + } + ], + "description": "tsconfig for the examples options (or path to a tsconfig)", + "default": {} + } + }, + "additionalProperties": false + } + }, + "$ref": "#/$defs/ConfigurationSchema" +} diff --git a/repos/effect/packages/tools/docgen/src/CLI.ts b/repos/effect/packages/tools/docgen/src/CLI.ts new file mode 100644 index 0000000000..7b2caa858b --- /dev/null +++ b/repos/effect/packages/tools/docgen/src/CLI.ts @@ -0,0 +1,244 @@ +#!/usr/bin/env node + +/** + * Command-line interface for generating Effect API documentation. + * + * @since 0.6.0 + */ +import * as Array from "effect/Array" +import * as Config from "effect/Config" +import * as Effect from "effect/Effect" +import * as Option from "effect/Option" +import * as Schema from "effect/Schema" +import * as CliError from "effect/unstable/cli/CliError" +import * as Command from "effect/unstable/cli/Command" +import * as Flag from "effect/unstable/cli/Flag" +import PackageJson from "../package.json" with { type: "json" } +import * as Configuration from "./Configuration.ts" +import * as Core from "./Core.ts" +import * as Domain from "./Domain.ts" + +const projectHomepage = Flag.string("homepage").pipe( + Flag.withFallbackConfig(Config.string("projectHomepage")), + Flag.withDescription( + "The link to the project homepage (will be shown in the Auxiliary Links of the generated documentation)" + ), + Flag.optional +) + +const srcLink = Flag.string("srcLink").pipe( + Flag.withFallbackConfig(Config.string("srcLink")), + Flag.withDescription("The link to the project source code"), + Flag.optional +) + +const srcDir = Flag.directory("src", { mustExist: true }).pipe( + Flag.withFallbackConfig(Config.string("src").pipe(Config.withDefault("src"))), + Flag.withDescription("The directory in which docgen will search for TypeScript files to parse") +) + +const outDir = Flag.directory("out").pipe( + Flag.withFallbackConfig(Config.string("out").pipe(Config.withDefault("docs"))), + Flag.withDescription("The directory to which docgen will generate its output markdown documents") +) + +const theme = Flag.string("theme").pipe( + Flag.withFallbackConfig(Config.string("theme").pipe(Config.withDefault(Configuration.DEFAULT_THEME))), + Flag.withDescription("The Jekyll theme that should be used for the generated documentation") +) + +const disableSearch = Flag.boolean("disable-search").pipe( + Flag.withDescription("Whether or not search should be enabled in the generated documentation"), + Flag.optional +) + +const enableSearchAlias = Flag.boolean("enable-search").pipe( + Flag.withDescription("Whether or not search should be enabled in the generated documentation"), + Flag.optional +) + +const enforceDescriptions = Flag.boolean("enforce-descriptions").pipe( + Flag.withDescription("Whether or not a description for each module export should be required"), + Flag.optional +) + +const enforceExamples = Flag.boolean("enforce-examples").pipe( + Flag.withDescription( + "Whether or not @example tags for each module export should be required " + + "(Note: examples will not be enforced in module documentation)" + ), + Flag.optional +) + +const noEnforceVersion = Flag.boolean("no-enforce-version").pipe( + Flag.withDescription("Whether or not @since tags for each module export should be required"), + Flag.optional +) + +const enforceVersionAlias = Flag.boolean("enforce-version").pipe( + Flag.withDescription("Whether or not @since tags for each module export should be required"), + Flag.optional +) + +const runExamples = Flag.boolean("run-examples").pipe( + Flag.withDescription("Whether or not to execute examples discovered in the TypeScript source files"), + Flag.optional +) + +const exclude = Flag.string("exclude").pipe( + Flag.between(0, Infinity), + Flag.withFallbackConfig( + Config.schema(Config.Array(Schema.String), "exclude").pipe( + Config.withDefault(Array.empty()) + ) + ), + Flag.withDescription( + "An array of glob patterns specifying files that should be excluded from the generated documentation" + ) +) + +const compilerOptionsSchema = Schema.fromJsonString(Schema.Record(Schema.String, Schema.Unknown)) + +const parseCompilerOptionsFlag = (name: string, description: string) => + Flag.string(name).pipe( + Flag.withDescription(description), + Flag.mapEffect((value) => + Schema.decodeUnknownEffect(compilerOptionsSchema)(value).pipe( + Effect.mapError((error) => + new CliError.InvalidValue({ + option: name, + value, + expected: `a JSON record (${globalThis.String(error)})`, + kind: "flag" + }) + ) + ) + ) + ) + +const parseCompilerOptionsFile = Flag.file("parse-tsconfig-file", { mustExist: true }).pipe( + Flag.withDescription("The TypeScript TSConfig file to use for parsing source files"), + Flag.optional +) + +const parseCompilerOptionsInline = parseCompilerOptionsFlag( + "parse-compiler-options", + "The TypeScript compiler options to use for parsing source files" +).pipe(Flag.optional) + +const examplesCompilerOptionsFile = Flag.file("examples-tsconfig-file", { mustExist: true }).pipe( + Flag.withDescription("The TypeScript TSConfig file to use for examples"), + Flag.optional +) + +const examplesCompilerOptionsInline = parseCompilerOptionsFlag( + "examples-compiler-options", + "The TypeScript compiler options to use for examples" +).pipe(Flag.optional) + +const options = { + projectHomepage, + srcLink, + srcDir, + outDir, + theme, + disableSearch, + enableSearchAlias, + enforceDescriptions, + enforceExamples, + noEnforceVersion, + enforceVersionAlias, + runExamples, + exclude, + parseCompilerOptionsFile, + parseCompilerOptionsInline, + examplesCompilerOptionsFile, + examplesCompilerOptionsInline +} + +/** @internal */ +export const docgenCommand = Command.make("docgen", options) + +/** @internal */ +export const loadConfiguration = Effect.fnUntraced(function*( + args: Command.Command.Config.Infer +) { + const { + enableSearchAlias, + disableSearch, + enforceDescriptions, + enforceExamples, + enforceVersionAlias, + noEnforceVersion, + runExamples, + examplesCompilerOptionsFile, + examplesCompilerOptionsInline, + parseCompilerOptionsFile, + parseCompilerOptionsInline, + ...config + } = args + if (Option.isSome(parseCompilerOptionsFile) && Option.isSome(parseCompilerOptionsInline)) { + return yield* new CliError.InvalidValue({ + option: "parse-compiler-options", + value: JSON.stringify(parseCompilerOptionsInline.value), + expected: "only one of --parse-tsconfig-file or --parse-compiler-options", + kind: "flag" + }) + } + if (Option.isSome(examplesCompilerOptionsFile) && Option.isSome(examplesCompilerOptionsInline)) { + return yield* new CliError.InvalidValue({ + option: "examples-compiler-options", + value: JSON.stringify(examplesCompilerOptionsInline.value), + expected: "only one of --examples-tsconfig-file or --examples-compiler-options", + kind: "flag" + }) + } + const configuredEnableSearch = yield* Config.boolean("enableSearch").pipe(Effect.orElseSucceed(() => true)) + const configuredEnforceDescriptions = yield* Config.boolean("enforceDescriptions").pipe( + Effect.orElseSucceed(() => false) + ) + const configuredEnforceExamples = yield* Config.boolean("enforceExamples").pipe( + Effect.orElseSucceed(() => false) + ) + const configuredEnforceVersion = yield* Config.boolean("enforceVersion").pipe(Effect.orElseSucceed(() => true)) + const configuredRunExamples = yield* Config.boolean("runExamples").pipe(Effect.orElseSucceed(() => false)) + return yield* Configuration.load({ + ...config, + enableSearch: Option.match(disableSearch, { + onNone: () => Option.getOrElse(enableSearchAlias, () => configuredEnableSearch), + onSome: (disabled) => !disabled + }), + enforceDescriptions: Option.getOrElse(enforceDescriptions, () => configuredEnforceDescriptions), + enforceExamples: Option.getOrElse(enforceExamples, () => configuredEnforceExamples), + enforceVersion: Option.match(noEnforceVersion, { + onNone: () => Option.getOrElse(enforceVersionAlias, () => configuredEnforceVersion), + onSome: (disabled) => !disabled + }), + runExamples: Option.getOrElse(runExamples, () => configuredRunExamples), + parseCompilerOptions: Option.orElse(parseCompilerOptionsFile, () => parseCompilerOptionsInline), + examplesCompilerOptions: Option.orElse(examplesCompilerOptionsFile, () => examplesCompilerOptionsInline) + }) +}) + +const command = docgenCommand.pipe( + Command.withHandler(() => + Effect.scoped(Core.program).pipe( + Effect.catchTag("DocgenError", (error) => + Effect.gen(function*() { + const config = yield* Configuration.Configuration + return yield* new Domain.DocgenError({ message: `[${config.projectName}] ${error.message}` }) + })) + ) + ), + Command.provideEffect(Configuration.Configuration, loadConfiguration) +) + +/** + * Runs the docgen command-line program. + * + * @category running + * @since 0.6.0 + */ +export const cli = Command.runWith(command, { + version: PackageJson["version"] +}) diff --git a/repos/effect/packages/tools/docgen/src/Checker.ts b/repos/effect/packages/tools/docgen/src/Checker.ts new file mode 100644 index 0000000000..5c1455a348 --- /dev/null +++ b/repos/effect/packages/tools/docgen/src/Checker.ts @@ -0,0 +1,248 @@ +/** + * Validates parsed documentation against the configured requirements. + * + * @since 0.6.0 + */ +import { codeFrameColumns } from "@babel/code-frame" +import * as Array from "effect/Array" +import * as Effect from "effect/Effect" +import * as Configuration from "./Configuration.ts" +import type * as Domain from "./Domain.ts" +import * as Parser from "./Parser.ts" + +const makeError = ( + source: Parser.SourceShape, + position: Domain.Position, + message: (filePath: string, frame: string) => string +) => { + const location = { start: position } + const frame = codeFrameColumns(source.sourceFile.getFullText(), location) + return [message(source.sourceFile.getFilePath(), frame)] +} + +type Entry = { + readonly doc: Domain.Doc + readonly position: Domain.Position +} + +function checkEntry(model: Entry, options: { + readonly enforceVersion: boolean +}) { + return Effect.gen(function*() { + const source = yield* Parser.Source + const config = yield* Configuration.Configuration + + let errors: Array = [] + + // description + if (config.enforceDescriptions) { + if (model.doc.description === undefined) { + errors = errors.concat(makeError( + source, + model.position, + (filePath, frame) => `Missing description in file ${filePath}:\n\n${frame}` + )) + } + } + + // @example tags + if (config.enforceExamples) { + if (model.doc.examples.length === 0) { + errors = errors.concat(makeError( + source, + model.position, + (filePath, frame) => `Missing examples in file ${filePath}:\n\n${frame}` + )) + } + } + + // @since tags + if (config.enforceVersion && options.enforceVersion !== false) { + const since = model.doc.since + if (since.length === 0) { + errors = errors.concat(makeError( + source, + model.position, + (filePath, frame) => `Missing \`@since\` tag in file ${filePath}:\n\n${frame}` + )) + } + } + + return errors + }) +} + +function checkEntries(models: ReadonlyArray, options: { + readonly enforceVersion: boolean +}) { + return Effect.forEach(models, (model) => checkEntry(model, options)).pipe(Effect.map(Array.flatten)) +} + +function checkFunction(model: Domain.Function) { + return checkEntry(model, { + enforceVersion: true + }) +} + +/** + * Validates documentation for function declarations. + * + * @category validation + * @since 0.6.0 + */ +export function checkFunctions(models: ReadonlyArray) { + return Effect.forEach(models, checkFunction).pipe(Effect.map(Array.flatten)) +} + +function checkClass(model: Domain.Class) { + return Effect.gen(function*() { + const docErrors = yield* checkEntry(model, { + enforceVersion: true + }) + const staticMethodsErrors = yield* checkEntries(model.staticMethods, { + enforceVersion: false + }) + const methodsErrors = yield* checkEntries(model.methods, { + enforceVersion: false + }) + const propertiesErrors = yield* checkEntries(model.properties, { + enforceVersion: false + }) + return Array.flatten([docErrors, staticMethodsErrors, methodsErrors, propertiesErrors]) + }) +} + +/** + * Validates documentation for class declarations and their documented members. + * + * @category validation + * @since 0.6.0 + */ +export function checkClasses(models: ReadonlyArray) { + return Effect.forEach(models, checkClass).pipe(Effect.map(Array.flatten)) +} + +function checkConstant(model: Domain.Constant) { + return checkEntry(model, { + enforceVersion: true + }) +} + +/** + * Validates documentation for constant declarations. + * + * @category validation + * @since 0.6.0 + */ +export function checkConstants(models: ReadonlyArray) { + return Effect.forEach(models, checkConstant).pipe(Effect.map(Array.flatten)) +} + +function checkInterface(model: Domain.Interface) { + return checkEntry(model, { + enforceVersion: true + }) +} + +/** + * Validates documentation for interface declarations. + * + * @category validation + * @since 0.6.0 + */ +export function checkInterfaces(models: ReadonlyArray) { + return Effect.forEach(models, checkInterface).pipe(Effect.map(Array.flatten)) +} + +function checkTypeAlias(model: Domain.TypeAlias) { + return checkEntry(model, { + enforceVersion: true + }) +} + +/** + * Validates documentation for type alias declarations. + * + * @category validation + * @since 0.6.0 + */ +export function checkTypeAliases(models: ReadonlyArray) { + return Effect.forEach(models, checkTypeAlias).pipe(Effect.map(Array.flatten)) +} + +function checkNamespace( + model: Domain.Namespace +): Effect.Effect, never, Parser.Source | Configuration.Configuration> { + return Effect.gen(function*() { + const docErrors = yield* checkEntry(model, { + enforceVersion: true + }) + const interfacesErrors = yield* checkInterfaces(model.interfaces) + const typeAliasesErrors = yield* checkTypeAliases(model.typeAliases) + const namespacesErrors = yield* checkNamespaces(model.namespaces) + return Array.flatten([docErrors, interfacesErrors, typeAliasesErrors, namespacesErrors]) + }) +} + +/** + * Validates documentation for namespaces and their nested declarations. + * + * @category validation + * @since 0.6.0 + */ +export function checkNamespaces(models: ReadonlyArray) { + return Effect.forEach(models, checkNamespace).pipe(Effect.map(Array.flatten)) +} + +function checkExport(model: Domain.Export) { + return checkEntry(model, { + enforceVersion: true + }) +} + +/** + * Validates documentation for explicit export declarations. + * + * @category validation + * @since 0.6.0 + */ +export function checkExports(models: ReadonlyArray) { + return Effect.forEach(models, checkExport).pipe(Effect.map(Array.flatten)) +} + +/** + * Validates every documented declaration in a parsed module. + * + * @category validation + * @since 0.6.0 + */ +export function checkModule(module: Domain.Module) { + return Effect.gen(function*() { + const functionsErrors = yield* checkFunctions(module.functions) + const classesErrors = yield* checkClasses(module.classes) + const constantsErrors = yield* checkConstants(module.constants) + const interfacesErrors = yield* checkInterfaces(module.interfaces) + const typeAliasesErrors = yield* checkTypeAliases(module.typeAliases) + const namespacesErrors = yield* checkNamespaces(module.namespaces) + const exportsErrors = yield* checkExports(module.exports) + return Array.flatten([ + functionsErrors, + classesErrors, + constantsErrors, + interfacesErrors, + typeAliasesErrors, + namespacesErrors, + exportsErrors + ]) + }).pipe(Effect.provideService(Parser.Source, module.source)) +} + +/** + * Validates every documented declaration in a collection of parsed modules. + * + * @category validation + * @since 0.6.0 + */ +export function checkModules(modules: ReadonlyArray) { + return Effect.forEach(modules, checkModule).pipe(Effect.map(Array.flatten)) +} diff --git a/repos/effect/packages/tools/docgen/src/Configuration.ts b/repos/effect/packages/tools/docgen/src/Configuration.ts new file mode 100644 index 0000000000..19c45035d4 --- /dev/null +++ b/repos/effect/packages/tools/docgen/src/Configuration.ts @@ -0,0 +1,380 @@ +/** + * Loads and provides configuration for documentation generation. + * + * @since 0.6.0 + */ + +import * as Array from "effect/Array" +import * as Config from "effect/Config" +import * as ConfigProvider from "effect/ConfigProvider" +import * as Context from "effect/Context" +import * as Effect from "effect/Effect" +import * as FileSystem from "effect/FileSystem" +import { pipe } from "effect/Function" +import * as Layer from "effect/Layer" +import * as Option from "effect/Option" +import * as Path from "effect/Path" +import * as Result from "effect/Result" +import * as Schema from "effect/Schema" +import * as tsconfck from "tsconfck" +import * as Domain from "./Domain.ts" +import { DocgenError } from "./Domain.ts" + +/** + * Default GitHub Pages theme written to generated configuration. + * + * @category constants + * @since 0.6.0 + */ +export const DEFAULT_THEME = "mikearnaldi/just-the-docs" + +const PACKAGE_JSON_FILE_NAME = "package.json" +const CONFIG_FILE_NAME = "docgen.json" + +const compilerOptionsSchema = Schema.Union([ + Schema.String, + Schema.Record(Schema.String, Schema.Unknown) +]) + +/** + * Schema for docgen configuration files. + * + * @category schemas + * @since 0.6.0 + */ +export const ConfigurationSchema = Schema.Struct({ + "$schema": Schema.optional(Schema.String), + projectHomepage: Schema.optional(Schema.String.annotate({ + description: "Will link to the project homepage from the Auxiliary Links of the generated documentation." + })), + srcLink: Schema.optional(Schema.String.annotate({ + description: "Will link to the project source code." + })), + srcDir: Schema.optional(Schema.String.annotate({ + description: "The directory in which docgen will search for TypeScript files to parse.", + default: "src" + })), + outDir: Schema.optional(Schema.String.annotate({ + description: "The directory to which docgen will generate its output markdown documents.", + default: "docs" + })), + theme: Schema.optional(Schema.String.annotate({ + description: "The theme that docgen will specify should be used for GitHub Docs in the generated _config.yml file.", + default: DEFAULT_THEME + })), + enableSearch: Schema.optional(Schema.Boolean.annotate({ + description: "Whether or not search should be enabled for GitHub Docs in the generated _config.yml file.", + default: true + })), + enforceDescriptions: Schema.optional(Schema.Boolean.annotate({ + description: "Whether or not descriptions for each module export should be required.", + default: false + })), + enforceExamples: Schema.optional(Schema.Boolean.annotate({ + description: + "Whether or not @example tags for each module export should be required. (Note: examples will not be enforced in module documentation)", + default: false + })), + enforceVersion: Schema.optional(Schema.Boolean.annotate({ + description: "Whether or not @since tags for each module export should be required.", + default: true + })), + tscExecutable: Schema.optional(Schema.String.annotate({ + description: + "The path to the TypeScript compiler executable that docgen should use when invoking the compiler programmatically.", + default: "tsc" + })), + runExamples: Schema.optional(Schema.Boolean.annotate({ + description: + "Whether or not docgen should attempt to run example code snippets and include the output in the generated documentation.", + default: false + })), + exclude: Schema.optional( + Schema.Array(Schema.String).annotate({ + description: "An array of glob strings specifying files that should be excluded from the documentation.", + default: [] + }) + ), + parseCompilerOptions: Schema.optional(compilerOptionsSchema.annotate({ + description: "tsconfig for parsing options (or path to a tsconfig)", + default: {} + })), + examplesCompilerOptions: Schema.optional(compilerOptionsSchema.annotate({ + description: "tsconfig for the examples options (or path to a tsconfig)", + default: {} + })) +}).annotate({ identifier: "ConfigurationSchema" }) + +/** + * Resolved configuration used by the docgen services. + * + * @category services + * @since 0.6.0 + */ +export interface ConfigurationShape { + readonly projectName: string + readonly projectHomepage: string + readonly srcLink: string + readonly srcDir: string + readonly outDir: string + readonly theme: string + readonly enableSearch: boolean + readonly enforceDescriptions: boolean + readonly enforceExamples: boolean + readonly enforceVersion: boolean + readonly tscExecutable: string + readonly runExamples: boolean + readonly exclude: ReadonlyArray + readonly parseCompilerOptions: Record + readonly examplesCompilerOptions: Record +} + +/** + * Service that provides resolved docgen configuration. + * + * @category services + * @since 0.6.0 + */ +export class Configuration + extends Context.Service()("@effect/docgen/Configuration") +{} + +/** @internal */ +export const defaultCompilerOptions = { + noEmit: true, + strict: true, + skipLibCheck: true, + moduleResolution: "Bundler", + target: "ES2022", + lib: [ + "ES2022", + "DOM" + ] +} + +const readJsonFile = ( + path: string +): Effect.Effect => + Effect.gen(function*() { + const fs = yield* FileSystem.FileSystem + const content = yield* Effect.orDie(fs.readFileString(path)) + return yield* pipe( + Effect.try({ + try: () => JSON.parse(content), + catch: (error) => `[FileSystem] Unable to read and parse JSON file from '${path}': ${String(error)}` + }), + Effect.orDie + ) + }) + +const validateJsonFile = ( + schema: Schema.Codec, + path: string +): Effect.Effect => + Effect.gen(function*() { + const content = yield* readJsonFile(path) + return yield* pipe( + Schema.decodeUnknownEffect(schema)(content), + Effect.mapError((error) => + new DocgenError({ + message: `[Configuration.validateJsonFile]\n${String(error)}` + }) + ), + Effect.orDie + ) + }) + +// TODO: this is invoked twice +const readDocgenConfig = ( + path: string +): Effect.Effect>, never, FileSystem.FileSystem> => { + return Effect.gen(function*() { + const fs = yield* FileSystem.FileSystem + const exists = yield* Effect.orDie(fs.exists(path)) + if (exists) { + const config = yield* validateJsonFile(ConfigurationSchema, path) + return Option.some(config) + } else { + return Option.none() + } + }) +} + +const readTSConfig = (fileName: string): Effect.Effect< + { readonly [x: string]: unknown }, + never, + Path.Path | Domain.Process +> => + Effect.gen(function*() { + const path = yield* Path.Path + const process = yield* Domain.Process + const cwd = yield* process.cwd + return yield* pipe( + Effect.tryPromise(() => tsconfck.parse(path.resolve(cwd, fileName))).pipe( + Effect.map(({ tsconfig }) => tsconfig.compilerOptions ?? defaultCompilerOptions), + Effect.mapError((error) => + new DocgenError({ + message: `[Configuration.readTSConfig] Failed to read TSConfig file\n${String(error)}` + }) + ), + Effect.orDie + ) + ) + }) + +const loadCompilerOptions = (configKey: string) => + Config.string(configKey).pipe( + Effect.flatMap((config) => + Schema.decodeUnknownEffect(JsonRecordSchema)(config).pipe(Effect.orElseSucceed(() => config)) + ) + ) + +const resolveCompilerOptions = ( + configKey: string, + fromCLI: Option.Option>, + fromDocgenJson: Option.Option> +): Effect.Effect<{ readonly [x: string]: unknown }, never, Path.Path | Domain.Process> => { + const fromConfigProvider = loadCompilerOptions(configKey) + return Effect.gen(function*() { + let config: string | Record + if (Option.isSome(fromCLI)) { + config = fromCLI.value + } else { + const provided = yield* Effect.result(fromConfigProvider) + if (Result.isSuccess(provided)) { + config = provided.success + } else if (Option.isSome(fromDocgenJson)) { + config = fromDocgenJson.value + } else { + config = defaultCompilerOptions + } + } + return typeof config === "string" ? yield* readTSConfig(config) : config + }) +} + +const JsonRecordSchema = Schema.fromJsonString(Schema.Record(Schema.String, Schema.Unknown)) + +const PackageJsonSchema = Schema.Struct({ + name: Schema.String, + homepage: Schema.String +}) + +/** @internal */ +export const load = (args: { + readonly projectHomepage: Option.Option + readonly srcLink: Option.Option + readonly srcDir: string + readonly outDir: string + readonly theme: string + readonly enableSearch: boolean + readonly enforceDescriptions: boolean + readonly enforceExamples: boolean + readonly enforceVersion: boolean + readonly runExamples: boolean + readonly exclude: ReadonlyArray + readonly parseCompilerOptions: Option.Option> + readonly examplesCompilerOptions: Option.Option> +}) => + Effect.gen(function*() { + // Extract the requisite services + const process = yield* Domain.Process + const cwd = yield* process.cwd + const path = yield* Path.Path + + // Read and parse the required fields from the `package.json` + const packageJsonPath = path.join(cwd, PACKAGE_JSON_FILE_NAME) + const packageJson = yield* validateJsonFile(PackageJsonSchema, packageJsonPath) + const projectName = packageJson.name + const projectHomepage = Option.getOrElse(args.projectHomepage, () => packageJson.homepage) + const srcLink = Option.getOrElse(args.srcLink, () => `${projectHomepage}/blob/main/src/`) + + // Read the `docgen.json` configuration file to gain access to the TypeScript + // configuration options + const configPath = path.join(cwd, CONFIG_FILE_NAME) + const config = yield* readDocgenConfig(configPath) + + // Resolve the excluded files + const exclude = yield* Array.match(args.exclude, { + onEmpty: () => + Effect.result(Config.schema(Config.Array(Schema.String), "exclude")).pipe( + Effect.map((configured) => + Result.isSuccess(configured) + ? configured.success + : Option.match(config, { + onNone: () => Array.empty(), + onSome: ({ exclude }) => exclude || Array.empty() + }) + ) + ), + onNonEmpty: (exclude) => Effect.succeed(exclude) + }) + + // Resolve the TypeScript configuration options + const examplesCompilerOptions = yield* resolveCompilerOptions( + "examplesCompilerOptions", + args.examplesCompilerOptions, + Option.flatMap(config, (config) => Option.fromNullishOr(config.examplesCompilerOptions)) + ) + const parseCompilerOptions = yield* resolveCompilerOptions( + "parseCompilerOptions", + args.parseCompilerOptions, + Option.flatMap(config, (config) => Option.fromNullishOr(config.parseCompilerOptions)) + ) + + const srcDir = config.pipe( + Option.flatMapNullishOr((config) => config.srcDir), + Option.getOrElse(() => args.srcDir) + ) + + const outDir = config.pipe( + Option.flatMapNullishOr((config) => config.outDir), + Option.getOrElse(() => args.outDir) + ) + + const runExamples = config.pipe( + Option.flatMapNullishOr((config) => config.runExamples), + Option.getOrElse(() => args.runExamples) + ) + + const tscExecutable = config.pipe( + Option.flatMapNullishOr((config) => config.tscExecutable), + Option.getOrElse(() => "tsc") + ) + + return Configuration.of({ + ...args, + srcDir, + outDir, + projectName, + projectHomepage, + srcLink, + exclude, + examplesCompilerOptions, + parseCompilerOptions, + runExamples, + tscExecutable + }) + }) + +/** @internal */ +export const configProviderLayer = Layer.effect(ConfigProvider.ConfigProvider)(Effect.gen(function*() { + // Extract the requisite services + const process = yield* Domain.Process + const cwd = yield* process.cwd + const env = yield* process.env + const path = yield* Path.Path + // Attempt to load the `docgen.json` configuration file + const configPath = path.join(cwd, CONFIG_FILE_NAME) + const maybeConfig = yield* readDocgenConfig(configPath) + // Construct a config provider for the environment + const fromEnv = ConfigProvider.fromEnv({ env }).pipe( + ConfigProvider.nested("DOCGEN"), + ConfigProvider.constantCase + ) + // Construct a config provider for the `docgen.json` file + const fromDocgenJson = ConfigProvider.fromUnknown(Option.getOrElse(maybeConfig, () => ({}))) + // Prefer the environment over the `docgen.json` file + const provider = fromEnv.pipe(ConfigProvider.orElse(fromDocgenJson)) + return provider +})) diff --git a/repos/effect/packages/tools/docgen/src/Core.ts b/repos/effect/packages/tools/docgen/src/Core.ts new file mode 100644 index 0000000000..ad36ea344e --- /dev/null +++ b/repos/effect/packages/tools/docgen/src/Core.ts @@ -0,0 +1,623 @@ +/** + * Coordinates source parsing, validation, example checking, and Markdown generation. + * + * @since 0.6.0 + */ + +import * as NodePath from "@effect/platform-node/NodePath" +import chalk from "chalk" +import * as Array from "effect/Array" +import * as Effect from "effect/Effect" +import * as Fiber from "effect/Fiber" +import * as FileSystem from "effect/FileSystem" +import { pipe } from "effect/Function" +import * as Path from "effect/Path" +import * as Stream from "effect/Stream" +import * as String from "effect/String" +import * as ChildProcess from "effect/unstable/process/ChildProcess" +import * as Glob from "glob" +import * as Checker from "./Checker.ts" +import * as Configuration from "./Configuration.ts" +import * as Domain from "./Domain.ts" +import * as Parser from "./Parser.ts" +import * as Printer from "./Printer.ts" +/** + * Find all files matching the specified `glob` pattern, optionally excluding + * files matching the provided `exclude` patterns. + */ +const glob = (pattern: string, exclude: ReadonlyArray = []) => + Effect.tryPromise(() => + Glob.glob(pattern, { + ignore: exclude.slice(), + withFileTypes: false + }) + ).pipe( + Effect.mapError(() => + new Domain.DocgenError({ + message: `[Core.glob] Unable to execute glob pattern '${pattern}' ` + + `excluding files matching '${exclude}'` + }) + ), + Effect.orDie + ) + +/** @internal */ +export const runCommand = Effect.fnUntraced(function*( + executable: string, + args: ReadonlyArray, + shell: boolean +) { + const handle = yield* ChildProcess.make(executable, args, { shell }) + const [stdout, stderr, exitCode] = yield* Effect.all([ + Stream.mkString(Stream.decodeText(handle.stdout)), + Stream.mkString(Stream.decodeText(handle.stderr)), + handle.exitCode + ], { concurrency: "unbounded" }) + return { stdout, stderr, exitCode } as const +}) + +/** + * Reads all TypeScript files in the source directory and returns an array of file objects. + * Each file object contains the file path and its content. + */ +const readSourceFiles = Effect.gen(function*() { + const config = yield* Configuration.Configuration + const fs = yield* FileSystem.FileSystem + const path = yield* Path.Path.pipe(Effect.provide(NodePath.layerPosix)) + const pattern = path.normalize(path.join(config.srcDir, "**", "*.ts")) + const paths = yield* glob(pattern, config.exclude) + yield* Effect.logInfo(chalk.bold(`${paths.length} module(s) found`)) + return yield* Effect.forEach(paths, (path) => + Effect.map( + fs.readFileString(path), + (content) => new Domain.File(path, content, false) + ), { concurrency: "unbounded" }) +}) + +/** + * Writes a file to the `config.outDir` directory, taking into account the configuration and existing files. + */ +const writeFileToOutDir = (file: Domain.File) => + Effect.gen(function*() { + const config = yield* Configuration.Configuration + const fs = yield* FileSystem.FileSystem + const path = yield* Path.Path + const process = yield* Domain.Process + const cwd = yield* process.cwd + const fileName = path.relative(path.join(cwd, config.outDir), file.path) + + const exists = yield* fs.exists(file.path) + if (exists) { + if (file.isOverwriteable) { + yield* Effect.logDebug(`Overwriting file ${chalk.black(fileName)}...`) + yield* fs.makeDirectory(path.dirname(file.path), { recursive: true }) + yield* fs.writeFileString(file.path, file.content) + } else { + yield* Effect.logDebug( + `File ${chalk.black(fileName)} already exists, skipping creation.` + ) + } + } else { + yield* fs.makeDirectory(path.dirname(file.path), { recursive: true }) + yield* fs.writeFileString(file.path, file.content) + } + }) + +const writeFilesToOutDir = ( + files: ReadonlyArray +) => Effect.forEach(files, writeFileToOutDir, { discard: true }) + +const parseModules = (files: ReadonlyArray) => + Parser.parseFiles(files).pipe( + Effect.mapError((errors) => + new Domain.DocgenError({ + message: "[Core.parseModules] The following error(s) occurred while " + + `parsing the TypeScript source files:\n${errors.map((errors) => errors.join("\n")).join("\n")}` + }) + ) + ) + +/** + * Runs the example files for the given modules, type-checking them before execution. + */ +const typeCheckAndRunExamples = (modules: ReadonlyArray) => + Effect.gen(function*() { + const config = yield* Configuration.Configuration + yield* cleanupExamples + const files = yield* getExampleFiles(modules) + const len = files.length + if (len > 0) { + yield* Effect.logInfo(`${len} example(s) found`) + yield* writeExamplesToOutDir(files) + yield* createExamplesTsConfigJson + yield* Effect.logInfo("Typechecking examples...") + yield* runTscOnExamples + if (config.runExamples) { + yield* Effect.logInfo("Running examples...") + yield* runTsxOnExamples + } else { + yield* Effect.logInfo(chalk.gray("Skipping running examples")) + } + } else { + yield* Effect.logInfo("No examples found.") + } + yield* cleanupExamples + }) + +/** + * Joins an array of strings with a "-" after dropping all empty strings. + */ +const filterJoin = (self: Array) => + pipe( + self, + Array.filter(String.isNonEmpty), + Array.join("-") + ) + +/** + * Extracts deeply nested namespaces with their corresponding namespace prefix + * from a given namespace. + */ +const extractPrefixedNestedNamespaces = ( + doc: Domain.Namespace, + prefix: string +): ReadonlyArray<[string, Domain.Namespace]> => { + const newPrefix = String.isEmpty(prefix) ? doc.name : `${prefix}-${doc.name}` + const namespaces = Array.flatMap( + doc.namespaces, + (namespace) => extractPrefixedNestedNamespaces(namespace, newPrefix) + ) + return Array.prepend(namespaces, [prefix, doc]) +} + +/** + * Fence metadata that excludes an example from docgen type checking. + * + * @category constants + * @since 0.6.0 + */ +export const SKIP_TYPE_CHECKING_FENCE_METADATA = "skip-type-checking" + +/** + * Extracts all fenced code blocks from markdown content. + * Handles both ``` and ~~~ fences, including any metadata like language, title, and other attributes. + * + * @internal + */ +export const extractFencedCode = (content: string): [examples: Array, warnings: Array] => { + // The regex now captures the closing fence (group 3) if present. + // If there's no closing fence, group 3 will be undefined. + const fenceRegex = /(?:```|~~~)(.*?)\n([\s\S]*?)(?:(```|~~~)|$)/g + const matches = Array.fromIterable(content.matchAll(fenceRegex)) + + const warnings: Array = [] + + // Log a warning if a code fence is not properly closed. + for (const match of matches) { + if (match[3] === undefined) { + warnings.push(`Code block does not have a matching closing fence:\n${content}`) + } + } + + return [ + matches + .filter((match) => { + const meta = match[1].toLocaleLowerCase() + const isTypeScript = meta.startsWith("ts") || meta.startsWith("typescript") + const isSkipTypeChecking = meta.includes(SKIP_TYPE_CHECKING_FENCE_METADATA) + return isTypeScript && !isSkipTypeChecking + }) + .map((match) => match[2].trim()), + warnings + ] +} + +/** + * Generates example files for the given modules. + */ +const getExampleFiles = (modules: ReadonlyArray) => + Effect.gen(function*() { + const config = yield* Configuration.Configuration + const path = yield* Path.Path + let warnings: Array = [] + const files = Array.flatMap(modules, (module) => { + const prefix = module.path.join("-") + + const getFiles = + (exampleId: string) => + (namedDoc: { readonly name: string; readonly doc: Domain.Doc }): ReadonlyArray => { + let descriptionExamples: Array = [] + if (namedDoc.doc.description !== undefined) { + const [es, ws] = extractFencedCode(namedDoc.doc.description) + warnings = warnings.concat(ws) + descriptionExamples = es + } + let exampleTagExamples: Array = [] + for (const example of namedDoc.doc.examples) { + const [es, ws] = extractFencedCode(example) + warnings = warnings.concat(ws) + exampleTagExamples = exampleTagExamples.concat(es) + } + const examples = descriptionExamples.concat(exampleTagExamples) + return Array.map( + examples, + (example, i) => { + return new Domain.File( + path.join( + config.outDir, + "examples", + `${prefix}-${exampleId}-${namedDoc.name}-${i}.ts` + ), + example, + true // make the file overwritable + ) + } + ) + } + + const allPrefixedNamespaces = Array.flatMap(module.namespaces, (namespace) => + extractPrefixedNestedNamespaces(namespace, "")) + + const moduleExamples = getFiles("module")(module) + const classExamples = Array.flatMap(module.classes, (c) => + Array.flatten([ + getFiles("class")(c), + Array.flatMap( + c.methods, + getFiles(`${c.name}-method`) + ), + Array.flatMap( + c.staticMethods, + getFiles(`${c.name}-staticmethod`) + ) + ])) + const allPrefixedInterfaces = [ + ...module.interfaces.map((iface) => + ["" as string, iface] as const + ), + ...Array.flatMap(allPrefixedNamespaces, ([prefix, namespace]) => + namespace.interfaces.map((iface) => + [filterJoin([prefix, namespace.name]), iface] as const + )) + ] + const interfacesExamples = Array.flatMap( + allPrefixedInterfaces, + ([ns, doc]) => getFiles(filterJoin(["interface", ns]))(doc) + ) + const allPrefixedTypeAliases = [ + ...module.typeAliases.map((typeAlias) => ["" as string, typeAlias] as const), + ...Array.flatMap(allPrefixedNamespaces, ([prefix, namespace]) => + namespace.typeAliases.map((typeAlias) => + [filterJoin([prefix, namespace.name]), typeAlias] as const + )) + ] + const typeAliasesExamples = Array.flatMap( + allPrefixedTypeAliases, + ([ns, doc]) => + getFiles(filterJoin(["typealias", ns]))(doc) + ) + const constantsExamples = Array.flatMap( + module.constants, + getFiles("constant") + ) + const functionsExamples = Array.flatMap( + module.functions, + getFiles("function") + ) + const exportsExamples = Array.flatMap( + module.exports, + getFiles("export") + ) + const namespacesExamples = Array.flatMap( + allPrefixedNamespaces, + ([ns, doc]) => getFiles(filterJoin(["namespace", ns]))(doc) + ) + + return Array.flatten([ + moduleExamples, + classExamples, + interfacesExamples, + typeAliasesExamples, + constantsExamples, + functionsExamples, + namespacesExamples, + exportsExamples + ]) + }) + + if (warnings.length > 0) { + yield* Effect.logWarning(warnings.join("\n")) + } + + return files + }) + +/** + * Generates an entry point file for the given examples. + */ +const getExamplesEntryPoint = (examples: ReadonlyArray) => + Effect.gen(function*() { + const config = yield* Configuration.Configuration + const path = yield* Path.Path + const content = examples.map((example) => `import './${path.basename(example.path, ".ts")}'`) + .join("\n") + return new Domain.File( + path.normalize(path.join(config.outDir, "examples", "index.ts")), + `${content}\n`, + true // make the file overwritable + ) + }) + +/** + * Removes the "examples" directory from the output directory specified in the configuration. + */ +const cleanupExamples = Effect.gen(function*() { + const fs = yield* FileSystem.FileSystem + const config = yield* Configuration.Configuration + const path = yield* Path.Path + const examplesDir = path.join(config.outDir, "examples") + const exists = yield* Effect.orDie(fs.exists(examplesDir)) + if (exists) { + yield* fs.remove(examplesDir, { recursive: true }) + } +}) + +/** + * Runs tsc on the examples directory. + */ +const runTscOnExamples = Effect.gen(function*() { + const config = yield* Configuration.Configuration + const process = yield* Domain.Process + const cwd = yield* process.cwd + const path = yield* Path.Path + const platform = yield* process.platform + + const tsconfig = path.normalize(path.join(cwd, config.outDir, "examples", "tsconfig.json")) + const options = ["--noEmit", "--project", tsconfig] + yield* Effect.logDebug("Running tsc on examples...") + const result = yield* runCommand( + platform === "win32" ? `${config.tscExecutable}.cmd` : config.tscExecutable, + options, + platform === "win32" + ).pipe(Effect.mapError((error) => + new Domain.DocgenError({ + message: `Something went wrong while running tsc on examples:\n\n${globalThis.String(error)}` + }) + )) + if (result.exitCode !== 0) { + return yield* new Domain.DocgenError({ + message: `Something went wrong while running tsc on examples:\n\n${result.stdout}` + }) + } +}) + +/** + * Runs tsc on the examples directory. + */ +const runTsxOnExamples = Effect.gen(function*() { + const config = yield* Configuration.Configuration + const path = yield* Path.Path + const process = yield* Domain.Process + const cwd = yield* process.cwd + const platform = yield* process.platform + + const examples = path.normalize(path.join(cwd, config.outDir, "examples")) + const tsconfig = path.join(examples, "tsconfig.json") + const index = path.join(examples, "index.ts") + const options = ["--tsconfig", tsconfig, index] + yield* Effect.logDebug("Running tsx on examples...") + const result = yield* runCommand( + platform === "win32" ? "tsx.cmd" : "tsx", + options, + platform === "win32" + ).pipe(Effect.mapError((error) => + new Domain.DocgenError({ + message: `Something went wrong while running tsx on examples:\n\n${globalThis.String(error)}` + }) + )) + if (result.exitCode !== 0) { + return yield* new Domain.DocgenError({ + message: `Something went wrong while running tsx on examples:\n\n${result.stderr}` + }) + } +}) + +const writeExamplesToOutDir = (examples: ReadonlyArray) => + Effect.gen(function*() { + yield* Effect.logDebug("Writing examples...") + const entryPoint = yield* getExamplesEntryPoint(examples) + const files = [entryPoint, ...examples] + yield* writeFilesToOutDir(files) + }) + +const createExamplesTsConfigJson = Effect.gen(function*() { + yield* Effect.logDebug("Writing examples tsconfig...") + const config = yield* Configuration.Configuration + const process = yield* Domain.Process + const cwd = yield* process.cwd + const path = yield* Path.Path + yield* writeFileToOutDir( + new Domain.File( + path.join(cwd, config.outDir, "examples", "tsconfig.json"), + JSON.stringify({ compilerOptions: config.examplesCompilerOptions }, null, 2), + true // make the file overwritable + ) + ) +}) + +const getMarkdown = (modules: ReadonlyArray) => + Effect.gen(function*() { + const homepage = yield* getMarkdownHomepage + const index = yield* getMarkdownIndex + const yml = yield* getMarkdownConfigYML + const moduleFiles = yield* getModuleMarkdownFiles(modules) + return [homepage, index, yml, ...moduleFiles] + }) + +const getMarkdownHomepage = Effect.gen(function*() { + const config = yield* Configuration.Configuration + const process = yield* Domain.Process + const cwd = yield* process.cwd + const path = yield* Path.Path + return new Domain.File( + path.join(cwd, config.outDir, "index.md"), + String.stripMargin( + `|--- + |title: Home + |nav_order: 1 + |--- + |` + ), + false + ) +}) + +const getMarkdownIndex = Effect.gen(function*() { + const config = yield* Configuration.Configuration + const process = yield* Domain.Process + const cwd = yield* process.cwd + const path = yield* Path.Path + return new Domain.File( + path.join(cwd, config.outDir, "modules", "index.md"), + String.stripMargin( + `|--- + |title: Modules + |has_children: true + |permalink: /docs/modules + |nav_order: 2 + |--- + |` + ), + false + ) +}) + +const resolveConfigYML = (content: string) => + Effect.gen(function*() { + const config = yield* Configuration.Configuration + return content + .replace(/^remote_theme:.*$/m, `remote_theme: ${config.theme}`) + .replace( + /^search_enabled:.*$/m, + `search_enabled: ${config.enableSearch}` + ).replace( + /^ {2}'\S* on GitHub':\n {4}- '.*'/m, + ` '${config.projectName} on GitHub':\n - '${config.projectHomepage}'` + ) + }) + +const getHomepageNavigationHeader = (config: Configuration.ConfigurationShape): string => { + const isGitHub = config.projectHomepage.toLowerCase().includes("github") + return isGitHub ? config.projectName + " on GitHub" : "Homepage" +} + +const getMarkdownConfigYML = Effect.gen(function*() { + const config = yield* Configuration.Configuration + const process = yield* Domain.Process + const fs = yield* FileSystem.FileSystem + const cwd = yield* process.cwd + const path = yield* Path.Path + const configPath = path.join(cwd, config.outDir, "_config.yml") + const exists = yield* fs.exists(configPath) + if (exists) { + const content = yield* fs.readFileString(configPath) + const resolved = yield* resolveConfigYML(content) + return new Domain.File(configPath, resolved, true) + } else { + return new Domain.File( + configPath, + String.stripMargin( + `|remote_theme: ${config.theme} + | + |# Enable or disable the site search + |search_enabled: ${config.enableSearch} + | + |# Aux links for the upper right navigation + |aux_links: + |'${getHomepageNavigationHeader(config)}': + | - '${config.projectHomepage}'` + ), + false + ) + } +}) + +const getModuleMarkdownOutputPath = (module: Domain.Module) => { + return Effect.gen(function*() { + const config = yield* Configuration.Configuration + const path = yield* Path.Path + return path.normalize(path.join( + config.outDir, + "modules", + `${module.path.slice(1).join(path.sep)}.md` + )) + }) +} + +const getModuleMarkdownFiles = (modules: ReadonlyArray) => + Effect.forEach(modules, (module, i) => + Effect.gen(function*() { + const outputPath = yield* getModuleMarkdownOutputPath(module) + const moduleContent = yield* Printer.printModule(module) + const tocgen = yield* Effect.promise(() => import("@effect/markdown-toc").then((module) => module.default)).pipe( + Effect.orDie + ) + const toc = tocgen(moduleContent, { bullets: "-" }).content + const frontMatter = Printer.printFrontMatter(module, i + 1) + const content = (frontMatter + "\n\n" + moduleContent).replace( + "", + `--- +## Exports Grouped by Category +${toc} +---` + ) + + const prettified = yield* Printer.prettify(content) + return new Domain.File(outputPath, prettified, true) + })) + +const writeMarkdown = (files: ReadonlyArray) => + Effect.gen(function*() { + const config = yield* Configuration.Configuration + const fileSystem = yield* FileSystem.FileSystem + const path = yield* Path.Path.pipe(Effect.provide(NodePath.layerPosix)) + const pattern = path.normalize(path.join(config.outDir, "**/*.ts.md")) + yield* Effect.logDebug(`Deleting ${chalk.black(pattern)}...`) + const paths = yield* glob(pattern) + yield* Effect.forEach(paths, (path) => fileSystem.remove(path, { recursive: true }), { + concurrency: "unbounded" + }) + return yield* writeFilesToOutDir(files) + }) + +/** @internal */ +export const program = Effect.gen(function*() { + yield* Effect.logInfo("Reading modules...") + const sourceFiles = yield* readSourceFiles + yield* Effect.logInfo("Parsing modules...") + const modules = yield* parseModules(sourceFiles) + + const checkFiber = yield* Effect.gen(function*() { + yield* Effect.logInfo("Checking modules...") + const errors = yield* Checker.checkModules(modules) + if (errors.length > 0) { + return yield* Effect.fail( + new Domain.DocgenError({ + message: `The following errors occurred while checking the modules:\n\n${errors.join("\n\n")}` + }) + ) + } + yield* typeCheckAndRunExamples(modules) + }).pipe(Effect.forkChild) + + const markdownFiber = yield* Effect.gen(function*() { + yield* Effect.logInfo("Creating markdown files...") + const outputFiles = yield* getMarkdown(modules) + yield* Effect.logInfo("Writing markdown files...") + yield* writeMarkdown(outputFiles) + }).pipe(Effect.forkChild) + + yield* Fiber.joinAll([checkFiber, markdownFiber]) + + yield* Effect.logInfo(chalk.bold.green("✓ Docs generation succeeded!")) +}) diff --git a/repos/effect/packages/tools/docgen/src/Domain.ts b/repos/effect/packages/tools/docgen/src/Domain.ts new file mode 100644 index 0000000000..9657d1b336 --- /dev/null +++ b/repos/effect/packages/tools/docgen/src/Domain.ts @@ -0,0 +1,348 @@ +/** + * Data models shared by the docgen parser, checker, and printer. + * + * @since 0.6.0 + */ + +import type * as Array from "effect/Array" +import * as Context from "effect/Context" +import * as Data from "effect/Data" +import * as Effect from "effect/Effect" +import * as Layer from "effect/Layer" +import * as Order from "effect/Order" +import * as Rec from "effect/Record" +import * as String from "effect/String" +import type * as Parser from "./Parser.ts" + +/** + * Base model for a named, documented declaration. + * + * @category models + * @since 0.6.0 + */ +export class DocEntry { + readonly name: string + readonly doc: Doc + readonly signature: string + readonly position: Position + constructor( + name: string, + doc: Doc, + signature: string, + position: Position + ) { + this.name = name + this.doc = doc + this.signature = signature + this.position = position + } +} + +/** + * Parsed JSDoc content attached to a declaration or module. + * + * @category models + * @since 0.6.0 + */ +export class Doc { + readonly description: string | undefined + readonly since: ReadonlyArray + readonly deprecated: ReadonlyArray + readonly examples: ReadonlyArray + readonly category: ReadonlyArray + readonly throws: ReadonlyArray + readonly sees: ReadonlyArray + readonly tags: Record | undefined> + constructor( + description: string | undefined, + since: ReadonlyArray, + deprecated: ReadonlyArray, + examples: ReadonlyArray, + category: ReadonlyArray, + throws: ReadonlyArray, + sees: ReadonlyArray, + tags: Record | undefined> + ) { + this.description = description + this.since = since + this.deprecated = deprecated + this.examples = examples + this.category = category + this.throws = throws + this.sees = sees + this.tags = tags + } + + modifyDescription(description: string | undefined): Doc { + return new Doc( + description, + this.since, + this.deprecated, + this.examples, + this.category, + this.throws, + this.sees, + this.tags + ) + } +} + +/** + * Parsed documentation model for one source module. + * + * @category models + * @since 0.6.0 + */ +export class Module { + readonly source: Parser.SourceShape + readonly name: string + readonly doc: Doc + readonly path: Array.NonEmptyReadonlyArray + readonly classes: ReadonlyArray + readonly interfaces: ReadonlyArray + readonly functions: ReadonlyArray + readonly typeAliases: ReadonlyArray + readonly constants: ReadonlyArray + readonly exports: ReadonlyArray + readonly namespaces: ReadonlyArray + constructor( + source: Parser.SourceShape, + name: string, + doc: Doc, + path: Array.NonEmptyReadonlyArray, + classes: ReadonlyArray, + interfaces: ReadonlyArray, + functions: ReadonlyArray, + typeAliases: ReadonlyArray, + constants: ReadonlyArray, + exports: ReadonlyArray, + namespaces: ReadonlyArray + ) { + this.source = source + this.name = name + this.doc = doc + this.path = path + this.classes = classes + this.interfaces = interfaces + this.functions = functions + this.typeAliases = typeAliases + this.constants = constants + this.exports = exports + this.namespaces = namespaces + } +} + +/** + * Parsed documentation model for a class and its documented members. + * + * @category models + * @since 0.6.0 + */ +export class Class extends DocEntry { + readonly _tag = "Class" + readonly methods: ReadonlyArray + readonly staticMethods: ReadonlyArray + readonly properties: ReadonlyArray + constructor( + name: string, + doc: Doc, + signature: string, + position: Position, + methods: ReadonlyArray, + staticMethods: ReadonlyArray, + properties: ReadonlyArray + ) { + super(name, doc, signature, position) + this.methods = methods + this.staticMethods = staticMethods + this.properties = properties + } +} + +/** + * Parsed documentation model for an interface. + * + * @category models + * @since 0.6.0 + */ +export class Interface extends DocEntry { + readonly _tag = "Interface" +} + +/** + * One-based source position used in diagnostics. + * + * @category models + * @since 0.6.0 + */ +export interface Position { + readonly line: number + readonly column: number +} + +/** + * Parsed documentation model for a function. + * + * @category models + * @since 0.6.0 + */ +export class Function extends DocEntry { + readonly _tag = "Function" +} + +/** + * Parsed documentation model for a type alias. + * + * @category models + * @since 0.6.0 + */ +export class TypeAlias extends DocEntry { + readonly _tag = "TypeAlias" +} + +/** + * Parsed documentation model for a constant. + * + * @category models + * @since 0.6.0 + */ +export class Constant extends DocEntry { + readonly _tag = "Constant" +} + +/** + * Parsed documentation model for an explicit named or namespace export. + * + * @category models + * @since 0.6.0 + */ +export class Export extends DocEntry { + readonly _tag = "Export" + readonly isNamespaceExport: boolean + constructor( + name: string, + doc: Doc, + signature: string, + position: Position, + isNamespaceExport: boolean + ) { + super(name, doc, signature, position) + this.isNamespaceExport = isNamespaceExport + } +} + +/** + * Parsed documentation model for a namespace and its nested declarations. + * + * @category models + * @since 0.6.0 + */ +export class Namespace { + readonly _tag = "Namespace" + readonly name: string + readonly doc: Doc + readonly position: Position + readonly interfaces: ReadonlyArray + readonly typeAliases: ReadonlyArray + readonly namespaces: ReadonlyArray + constructor( + name: string, + doc: Doc, + position: Position, + interfaces: ReadonlyArray, + typeAliases: ReadonlyArray, + namespaces: ReadonlyArray + ) { + this.name = name + this.doc = doc + this.position = position + this.interfaces = interfaces + this.typeAliases = typeAliases + this.namespaces = namespaces + } +} + +/** + * A comparator function for sorting `Module` objects by their file path, represented as a string. + * The file path is converted to lowercase before comparison. + * + * @category sorting + * @since 0.6.0 + */ +export const ByPath: Order.Order = Order.mapInput( + String.Order, + (module: Module) => module.path.join("/").toLowerCase() +) + +/** + * Represents a file which can be optionally overwriteable. + * + * @category models + * @since 0.6.0 + */ +export class File { + readonly path: string + readonly content: string + readonly isOverwriteable: boolean + constructor( + path: string, + content: string, + isOverwriteable: boolean = false + ) { + this.path = path + this.content = content + this.isOverwriteable = isOverwriteable + } +} + +/** + * Type ID for `DocgenError`. + * + * @category symbols + * @since 0.6.0 + */ +export const DocgenErrorTypeId = Symbol.for("@effect/docgen/DocgenError") + +/** + * Type-level representation of `DocgenErrorTypeId`. + * + * @category symbols + * @since 0.6.0 + */ +export type DocgenErrorTypeId = typeof DocgenErrorTypeId + +/** + * Error reported when documentation generation cannot continue. + * + * @category errors + * @since 0.6.0 + */ +export class DocgenError extends Data.TaggedError("DocgenError")<{ + readonly message: string +}> {} + +/** + * Represents a handle to the currently executing process. + * + * @category services + * @since 0.6.0 + */ +export class Process extends Context.Service + readonly platform: Effect.Effect + readonly argv: Effect.Effect> + readonly env: Effect.Effect> +}>()("@effect/docgen/Process") { + static readonly layer = Layer.succeed(Process, { + cwd: Effect.sync(() => process.cwd()), + platform: Effect.sync(() => process.platform), + argv: Effect.sync(() => process.argv), + env: Effect.sync(() => { + const env: Record = {} + for (const [key, value] of Object.entries(process.env)) { + if (value !== undefined) Rec.assignProperty(env, key, value) + } + return env + }) + }) +} diff --git a/repos/effect/packages/tools/docgen/src/Parser.ts b/repos/effect/packages/tools/docgen/src/Parser.ts new file mode 100644 index 0000000000..33f6116c4c --- /dev/null +++ b/repos/effect/packages/tools/docgen/src/Parser.ts @@ -0,0 +1,746 @@ +/** + * Parses TypeScript declarations and JSDoc into docgen models. + * + * @since 0.6.0 + */ +import * as doctrine from "doctrine" +import * as Array from "effect/Array" +import * as Context from "effect/Context" +import * as Effect from "effect/Effect" +import { pipe } from "effect/Function" +import * as Option from "effect/Option" +import * as Path from "effect/Path" +import * as Record from "effect/Record" +import * as String from "effect/String" +import * as ast from "ts-morph" +import * as Configuration from "./Configuration.ts" +import * as Domain from "./Domain.ts" + +/** + * Source file and path currently being parsed. + * + * @category services + * @since 0.6.0 + */ +export interface SourceShape { + readonly path: Array.NonEmptyReadonlyArray + readonly sourceFile: ast.SourceFile +} + +/** @internal */ +export class Source extends Context.Service()("@effect/docgen/Source") {} + +const sortModulesByPath: (self: Iterable) => Array = Array + .sort(Domain.ByPath) + +const getJSDocText: (jsdocs: ReadonlyArray) => string = Array.matchRight({ + onEmpty: () => "", + onNonEmpty: (_, last) => last.getText() +}) + +const getDocComment = (ranges: ReadonlyArray): Option.Option => + pipe( + ranges, + Array.filter((range) => range.getText().startsWith("/**")), + Array.last + ) + +class Comment { + readonly description: string | undefined + readonly tags: Record | undefined> + constructor( + description: string | undefined, + tags: Record | undefined> + ) { + this.description = description + this.tags = tags + } +} + +/** + * @internal + */ +export const parseComment = (text: string): Comment => { + const annotation: doctrine.Annotation = doctrine.parse(text, { + unwrap: true + }) + + const description = pipe( + Option.fromNullishOr(annotation.description), + Option.map((s) => s.trim()), + Option.filter(String.isNonEmpty), + Option.getOrUndefined + ) + + const tags = pipe( + annotation.tags, + Array.groupBy((tag) => tag.title), + Record.map((values) => + Array.map(values, (tag) => + pipe( + Option.fromNullishOr(tag.description), + Option.map(String.trim), + Option.getOrElse(() => "") + )) + ) + ) + + return { description, tags } +} + +const isVariableDeclarationList = ( + u: ast.VariableDeclarationList | ast.CatchClause +): u is ast.VariableDeclarationList => u.getKind() === ast.ts.SyntaxKind.VariableDeclarationList + +const isVariableStatement = ( + u: + | ast.VariableStatement + | ast.ForStatement + | ast.ForOfStatement + | ast.ForInStatement +): u is ast.VariableStatement => u.getKind() === ast.ts.SyntaxKind.VariableStatement + +const parseDoc = (text: string) => { + const comment = parseComment(text) + return new Domain.Doc( + comment.description, + comment.tags["since"] ?? [], + comment.tags["deprecated"] ?? [], + comment.tags["example"] ?? [], + comment.tags["category"] ?? [], + comment.tags["throws"] ?? [], + comment.tags["see"] ?? [], + comment.tags + ) +} + +const shouldIgnore = (doc: Domain.Doc): boolean => { + return Record.has(doc.tags, "internal") || Record.has(doc.tags, "ignore") +} + +const parseInterfaceDeclaration = (id: ast.InterfaceDeclaration) => + Effect.gen(function*() { + const doc = parseDoc(getJSDocText(id.getJsDocs())) + if (shouldIgnore(doc)) { + return [] + } + const name = id.getName() + const signature = id.getText() + const position = yield* parsePosition(id) + return [ + new Domain.Interface( + name, + doc, + signature, + position + ) + ] + }) + +const parseInterfaceDeclarations = (interfaces: ReadonlyArray) => { + const exportedInterfaces = Array.filter( + interfaces, + (id) => id.isExported() + ) + return Effect.forEach(exportedInterfaces, parseInterfaceDeclaration).pipe(Effect.map(Array.flatten)) +} + +/** + * Parses exported interfaces from the current source file. + * + * @category parsing + * @since 0.6.0 + */ +export const parseInterfaces = Effect.flatMap( + Source, + (source) => parseInterfaceDeclarations(source.sourceFile.getInterfaces()) +) + +const getTypeText = (node: ast.Node) => + node.getType().getText( + node, + ast.ts.TypeFormatFlags.NoTruncation + | ast.ts.TypeFormatFlags.WriteArrayAsGenericType + | ast.ts.TypeFormatFlags.UseAliasDefinedOutsideCurrentScope + | ast.ts.TypeFormatFlags.NoTypeReduction + | ast.ts.TypeFormatFlags.AllowUniqueESSymbolType + | ast.ts.TypeFormatFlags.WriteArrowStyleSignature + ) + +const parseType = (node: ast.Node) => { + let text = getTypeText(node) + for (const property of node.getDescendantsOfKind(ast.ts.SyntaxKind.PropertySignature)) { + if (!shouldIgnore(parseDoc(getJSDocText(property.getJsDocs())))) continue + const readonly = property.getFirstModifierByKind(ast.ts.SyntaxKind.ReadonlyKeyword) ? "readonly " : "" + const optional = property.hasQuestionToken() ? "?" : "" + const type = property.getTypeNode()?.getText() ?? getTypeText(property) + const signature = `${readonly}${property.getName()}${optional}: ${type}` + text = text + .replaceAll(`; ${signature}`, "") + .replaceAll(`${signature}; `, "") + .replaceAll(signature, "") + } + return text +} + +const getFunctionDeclarationJSDocs = (fd: ast.FunctionDeclaration): Array => + Array.matchLeft(fd.getOverloads(), { + onEmpty: () => fd.getJsDocs(), + onNonEmpty: (firstOverload) => firstOverload.getJsDocs() + }) + +const parsePosition = (node: ast.Node): Effect.Effect => { + return Effect.gen(function*() { + const source = yield* Source + const startPos = node.getStart() + const position = source.sourceFile.getLineAndColumnAtPos(startPos) + return position + }) +} + +const parseFunctionDeclaration = (fd: ast.FunctionDeclaration) => + Effect.gen(function*() { + const doc = parseDoc(getJSDocText(getFunctionDeclarationJSDocs(fd))) + if (shouldIgnore(doc)) { + return [] + } + const name = fd.getName() + const type = parseType(fd) + const signature = `declare const ${name}: ${type}` + const position = yield* parsePosition(fd) + return [ + new Domain.Function( + name ?? "", + doc, + signature, + position + ) + ] + }) + +const parseFunctionVariableDeclaration = (vd: ast.VariableDeclaration) => + Effect.gen(function*() { + const vs: any = vd.getParent().getParent() + const doc = parseDoc(getJSDocText(vs.getJsDocs())) + if (shouldIgnore(doc)) { + return [] + } + const name = vd.getName() + const type = parseType(vd) + const signature = `declare const ${name}: ${type}` + const startPos = vd.getStart() + const source = yield* Source + const position = source.sourceFile.getLineAndColumnAtPos(startPos) + return [ + new Domain.Function( + name ?? "", + doc, + signature, + position + ) + ] + }) + +const getFunctionDeclarations = Effect.gen(function*() { + const source = yield* Source + const functions = Array.filter( + source.sourceFile.getFunctions(), + (fd) => fd.isExported() + ) + const arrows = pipe( + Array.filter( + source.sourceFile.getVariableDeclarations(), + (vd) => { + if (isVariableDeclarationList(vd.getParent())) { + const vs: any = vd.getParent().getParent() + if (isVariableStatement(vs)) { + return vs.isExported() && + Option.fromNullishOr(vd.getInitializer()).pipe( + Option.filter((expr) => ast.Node.isFunctionLikeDeclaration(expr)), + Option.isSome + ) + } + } + return false + } + ) + ) + return { functions, arrows } +}) + +/** + * Parses exported function declarations and function-valued variables. + * + * @category parsing + * @since 0.6.0 + */ +export const parseFunctions = Effect.gen(function*() { + const { arrows, functions } = yield* getFunctionDeclarations + const functionDeclarations = yield* Effect.forEach(functions, parseFunctionDeclaration).pipe( + Effect.map(Array.flatten) + ) + const functionVariableDeclarations = yield* Effect.forEach(arrows, parseFunctionVariableDeclaration).pipe( + Effect.map(Array.flatten) + ) + return [...functionDeclarations, ...functionVariableDeclarations] +}) + +const parseTypeAliasDeclaration = (ta: ast.TypeAliasDeclaration) => + Effect.gen(function*() { + const doc = parseDoc(getJSDocText(ta.getJsDocs())) + if (shouldIgnore(doc)) { + return [] + } + const name = ta.getName() + const len = ta.getTypeParameters().length + const type = parseType(ta) + const definition = ta.getTypeNode()?.getText() + const signature = `type ${len > 0 ? type : name} = ${definition}` + const position = yield* parsePosition(ta) + return [ + new Domain.TypeAlias( + name, + doc, + signature, + position + ) + ] + }) + +const parseTypeAliasDeclarations = (typeAliases: ReadonlyArray) => { + const exportedTypeAliases = Array.filter( + typeAliases, + (tad) => tad.isExported() + ) + return Effect.forEach(exportedTypeAliases, parseTypeAliasDeclaration).pipe(Effect.map(Array.flatten)) +} + +/** + * Parses exported type aliases from the current source file. + * + * @category parsing + * @since 0.6.0 + */ +export const parseTypeAliases = Effect.flatMap( + Source, + (source) => parseTypeAliasDeclarations(source.sourceFile.getTypeAliases()) +) + +const parseConstantVariableDeclaration = (vd: ast.VariableDeclaration) => + Effect.gen(function*() { + const vs: any = vd.getParent().getParent() + const doc = parseDoc(getJSDocText(vs.getJsDocs())) + if (shouldIgnore(doc)) { + return [] + } + const name = vd.getName() + const type = parseType(vd) + const signature = `declare const ${name}: ${type}` + const position = yield* parsePosition(vd) + return [ + new Domain.Constant( + name, + doc, + signature, + position + ) + ] + }) + +/** + * Parses exported non-function constants from the current source file. + * + * @category parsing + * @since 0.6.0 + */ +export const parseConstants = Effect.gen(function*() { + const source = yield* Source + const variableDeclarations = pipe( + Array.filter( + source.sourceFile.getVariableDeclarations(), + (vd) => { + if (isVariableDeclarationList(vd.getParent())) { + const vs: any = vd.getParent().getParent() + if (isVariableStatement(vs)) { + return vs.isExported() && + Option.fromNullishOr(vd.getInitializer()).pipe( + Option.filter((expr) => !ast.Node.isFunctionLikeDeclaration(expr)), + Option.isSome + ) + } + } + return false + } + ) + ) + return yield* Effect.forEach(variableDeclarations, parseConstantVariableDeclaration).pipe( + Effect.map(Array.flatten) + ) +}) + +const parseExportSpecifier = (es: ast.ExportSpecifier) => + Effect.gen(function*() { + const name = es.compilerNode.name.text + const type = parseType(es) + const oDocComment = getDocComment(es.getLeadingCommentRanges()) + const doc = Option.isSome(oDocComment) ? parseDoc(oDocComment.value.getText()) : parseDoc("") + const signature = `declare const ${name}: ${type}` + const position = yield* parsePosition(es) + return new Domain.Export( + name, + doc, + signature, + position, + false + ) + }) + +const parseExportStar = (ed: ast.ExportDeclaration) => + Effect.gen(function*() { + const es = ed.getModuleSpecifier()! + const name = es.getText() + const namespace = ed.getNamespaceExport()?.getName() + const signature = `export *${namespace === undefined ? "" : ` as ${namespace}`} from ${name}` + const oDocComment = getDocComment(ed.getLeadingCommentRanges()) + const doc = Option.isSome(oDocComment) ? parseDoc(oDocComment.value.getText()) : parseDoc("") + const position = yield* parsePosition(ed) + return new Domain.Export( + namespace ?? name, + doc.modifyDescription( + `Re-exports all named exports from the ${name} module${namespace === undefined ? "" : ` as \`${namespace}\``}.` + ), + signature, + position, + true + ) + }) + +const parseNamedExports = (ed: ast.ExportDeclaration) => { + const namedExports = ed.getNamedExports() + if (namedExports.length === 0) { + if (ed.getModuleSpecifier() !== undefined) { + return parseExportStar(ed).pipe(Effect.map(Array.of)) + } + return Effect.succeed([]) + } + return Effect.forEach(namedExports, parseExportSpecifier) +} + +/** + * Parses explicit export declarations from the current source file. + * + * @category parsing + * @since 0.6.0 + */ +export const parseExports = pipe( + Effect.map(Source, (source) => source.sourceFile.getExportDeclarations()), + Effect.flatMap((exportDeclarations) => Effect.forEach(exportDeclarations, parseNamedExports)), + Effect.map(Array.flatten) +) + +const parseModuleDeclaration = ( + ed: ast.ModuleDeclaration +): Effect.Effect, never, Source | Configuration.Configuration> => { + const doc = parseDoc(getJSDocText(ed.getJsDocs())) + if (shouldIgnore(doc)) { + return Effect.succeed([]) + } + const name = ed.getName() + const getInterfaces = parseInterfaceDeclarations(ed.getInterfaces()) + const getTypeAliases = parseTypeAliasDeclarations(ed.getTypeAliases()) + const getNamespaces = parseModuleDeclarations(ed.getModules()) + return Effect.gen(function*() { + const interfaces = yield* getInterfaces + const typeAliases = yield* getTypeAliases + const namespaces = yield* getNamespaces + const position = yield* parsePosition(ed) + return [ + new Domain.Namespace( + name, + doc, + position, + interfaces, + typeAliases, + namespaces + ) + ] + }) +} + +const parseModuleDeclarations = (namespaces: ReadonlyArray) => { + return Effect.forEach( + namespaces.filter((md) => md.isExported()), + parseModuleDeclaration + ).pipe(Effect.map(Array.flatten)) +} + +/** + * Parses exported namespaces from the current source file. + * + * @category parsing + * @since 0.6.0 + */ +export const parseNamespaces = Effect.gen(function*() { + const source = yield* Source + return yield* parseModuleDeclarations(source.sourceFile.getModules()) +}) + +const getTypeParameters = (tps: ReadonlyArray): string => + tps.length === 0 ? "" : `<${tps.map((p) => p.getName()).join(", ")}>` + +const parseMethod = (md: ast.MethodDeclaration) => + Effect.gen(function*() { + const name = md.getName() + const jsdocs = Array.matchLeft(md.getOverloads(), { + onEmpty: () => md.getJsDocs(), + onNonEmpty: (head) => head.getJsDocs() + }) + const doc = parseDoc(getJSDocText(jsdocs)) + if (shouldIgnore(doc)) { + return Option.none() + } + const type = parseType(md) + const signature = `declare const ${name}: ${type}` + const position = yield* parsePosition(md) + return Option.some( + new Domain.DocEntry( + name, + doc, + signature, + position + ) + ) + }) + +const parseProperty = (pd: ast.PropertyDeclaration) => + Effect.gen(function*() { + const doc = parseDoc(getJSDocText(pd.getJsDocs())) + if (shouldIgnore(doc)) { + return [] + } + const name = pd.getName() + const type = parseType(pd) + const readonly = pipe( + Option.fromNullishOr(pd.getFirstModifierByKind(ast.ts.SyntaxKind.ReadonlyKeyword)), + Option.match({ + onNone: () => "", + onSome: () => "readonly " + }) + ) + const signature = `${readonly}${name}: ${type}` + const position = yield* parsePosition(pd) + return [ + new Domain.DocEntry( + name, + doc, + signature, + position + ) + ] + }) + +const parseProperties = (c: ast.ClassDeclaration) => { + const properties = Array.filter( + c.getProperties(), + (pd) => + !pd.isStatic() && pipe( + pd.getFirstModifierByKind(ast.ts.SyntaxKind.PrivateKeyword), + Option.fromNullishOr, + Option.isNone + ) + ) + return Effect.forEach(properties, parseProperty).pipe(Effect.map(Array.flatten)) +} + +/** + * @internal + */ +export const getConstructorDeclarationSignature = ( + c: ast.ConstructorDeclaration +): string => + pipe( + Option.fromNullishOr(c.compilerNode.body), + Option.match({ + onNone: () => c.getText(), + onSome: (body) => { + const end = body.getStart() - c.getStart() - 1 + return c.getText().substring(0, end) + } + }) + ) + +const getClassDeclarationSignature = (c: ast.ClassDeclaration) => { + const name = c.getName() ?? "" + return pipe( + Effect.succeed(getTypeParameters(c.getTypeParameters())), + Effect.map((typeParameters) => + pipe( + c.getConstructors(), + Array.matchLeft({ + onEmpty: () => `declare class ${name}${typeParameters}`, + onNonEmpty: (head) => + `declare class ${name}${typeParameters} { ${ + getConstructorDeclarationSignature( + head + ) + } }` + }) + ) + ) + ) +} + +const parseClass = (c: ast.ClassDeclaration) => + Effect.gen(function*() { + const doc = parseDoc(getJSDocText(c.getJsDocs())) + if (shouldIgnore(doc)) { + return [] + } + const name = c.getName() ?? "" + const signature = yield* getClassDeclarationSignature(c) + const methods = yield* pipe( + c.getInstanceMethods(), + Effect.forEach(parseMethod), + Effect.map(Array.getSomes) + ) + const staticMethods = yield* pipe( + c.getStaticMethods(), + Effect.forEach(parseMethod), + Effect.map(Array.getSomes) + ) + const properties = yield* parseProperties(c) + const position = yield* parsePosition(c) + return [ + new Domain.Class( + name, + doc, + signature, + position, + methods, + staticMethods, + properties + ) + ] + }) + +/** + * Parses exported classes and their documented members from the current source file. + * + * @category parsing + * @since 0.6.0 + */ +export const parseClasses = Effect.gen(function*() { + const source = yield* Source + const exportedClasses = source.sourceFile.getClasses().filter((cd) => cd.isExported()) + return yield* Effect.forEach(exportedClasses, parseClass).pipe(Effect.map(Array.flatten)) +}) + +/** + * @internal + */ +export const parseModuleDocumentation = Effect.gen(function*() { + const source = yield* Source + const statements = source.sourceFile.getStatements() + const ofirstStatement = Array.head(statements) + if (Option.isSome(ofirstStatement)) { + const oDocComment = getDocComment(ofirstStatement.value.getLeadingCommentRanges()) + if (Option.isSome(oDocComment)) { + return parseDoc(oDocComment.value.getText()) + } + } + return parseDoc("") +}) + +/** + * Parses the current source file into a module documentation model. + * + * @category parsing + * @since 0.6.0 + */ +export const parseModule = Effect.gen(function*() { + const source = yield* Source + const doc = yield* parseModuleDocumentation + const interfaces = yield* parseInterfaces + const functions = yield* parseFunctions + const typeAliases = yield* parseTypeAliases + const classes = yield* parseClasses + const constants = yield* parseConstants + const exports = yield* parseExports + const namespaces = yield* parseNamespaces + const name = source.sourceFile.getBaseName() + return new Domain.Module( + source, + name, + doc, + source.path, + classes, + interfaces, + functions, + typeAliases, + constants, + exports, + namespaces + ) +}) + +/** + * @internal + */ +export const parseFile = + (project: ast.Project) => + (file: Domain.File): Effect.Effect, Configuration.Configuration | Path.Path> => { + return Effect.gen(function*() { + const path = yield* Path.Path + const sourceFile = project.getSourceFile(file.path) + const filePath = file.path.split(path.sep) + if (sourceFile !== undefined && Array.isArrayNonEmpty(filePath)) { + return yield* Effect.provideService(parseModule, Source, { sourceFile, path: filePath }) + } + return yield* Effect.fail([`Unable to locate file: ${file.path}`]) + }) + } + +const createProject = (files: ReadonlyArray) => + Effect.gen(function*() { + const config = yield* Configuration.Configuration + const process = yield* Domain.Process + const cwd = yield* process.cwd + // Convert the raw config into a format that TS/TS-Morph expects + const parsed = ast.ts.parseJsonConfigFileContent( + { + compilerOptions: { + strict: true, + moduleResolution: "node", + ...config.parseCompilerOptions + } + }, + ast.ts.sys, + cwd + ) + + const options: ast.ProjectOptions = { + compilerOptions: parsed.options + } + const project = new ast.Project(options) + for (const file of files) { + project.addSourceFileAtPath(file.path) + } + return project + }) + +/** + * Parses source files into module documentation models sorted by path. + * + * @category parsing + * @since 0.6.0 + */ +export const parseFiles = (files: ReadonlyArray) => + createProject(files).pipe( + Effect.flatMap((project) => + pipe( + files, + Effect.validate(parseFile(project)), + Effect.map(sortModulesByPath) + ) + ) + ) diff --git a/repos/effect/packages/tools/docgen/src/Printer.ts b/repos/effect/packages/tools/docgen/src/Printer.ts new file mode 100644 index 0000000000..97f743ffb6 --- /dev/null +++ b/repos/effect/packages/tools/docgen/src/Printer.ts @@ -0,0 +1,383 @@ +/** + * Renders parsed documentation models as Markdown. + * + * @since 0.6.0 + */ +import * as Array from "effect/Array" +import * as Effect from "effect/Effect" +import { pipe } from "effect/Function" +import * as Order from "effect/Order" +import * as Record from "effect/Record" +import * as String from "effect/String" +import * as Prettier from "prettier" +import * as Configuration from "./Configuration.ts" +import type * as Domain from "./Domain.ts" +import * as Parser from "./Parser.ts" + +/** @internal */ +export type Printable = + | Domain.Class + | Domain.Constant + | Domain.Export + | Domain.Function + | Domain.Interface + | Domain.TypeAlias + | Domain.Namespace + +const Markdown = { + bold: (content: string) => `**${content}**`, + fence: (content: string) => `\`\`\`ts\n${content}\n\`\`\`\n\n`, + strikethrough: (content: string) => `~~${content}~~` +} + +/** + * Replaces the link from a JSDoc link tag with a simple text. + * + * Given "This is a description containing two links to {@link foo} and {@link bar baz}." + * returns "This is a description containing two links to `foo` and `baz`." + */ +function replaceJSDocLinks(text: string): string { + return text.replace(/\{@link\s+([^\s}]+)(?:\s+([^}]+))?\}/g, (_, link, label) => { + // Use the label if provided; otherwise, use the link target + return `\`${(label || link).trim()}\`` + }) +} + +/** + * Removes all extra metadata from fenced code blocks in a Markdown string. + * For each code fence, only the first token (the language identifier) is preserved. + */ +function removeFenceMetadata(markdown: string): string { + return markdown.replace(/^(`{3,})([^\n]*)/gm, (_match, fence, info) => { + // Trim the info string and split by whitespace into tokens. + // The first token (if present) is typically the language identifier. + const tokens = info.trim().split(/\s+/) + // Rebuild the fence line with just the language (if it exists) + return fence + (tokens[0] || "") + }) +} + +const printOptionalDescription = (description: string | undefined) => { + return Effect.gen(function*() { + if (description === undefined) { + return "" + } + const config = yield* Configuration.Configuration + const descriptionWithoutLinks = replaceJSDocLinks(description) + const out = config.theme === Configuration.DEFAULT_THEME + ? removeFenceMetadata(descriptionWithoutLinks) + : descriptionWithoutLinks + return `\n\n${out}` + }) +} + +const printArray = (title: string, ss?: ReadonlyArray): string => { + if (ss === undefined || ss.length === 0) { + return "" + } + return `\n\n${Markdown.bold(title)}\n\n${ss.join("\n")}` +} + +const printFence = (code: string): string => { + if (code.startsWith("```ts") || code.startsWith("~~~ts")) { + return code + } + return "```ts\n" + code + "\n```" +} + +const printOptionalSignature = (signature?: string): string => { + if (signature === undefined) { + return "" + } + return `\n\n${Markdown.bold("Signature")}\n\n${printFence(signature)}` +} + +const printThrowsArray = (throws?: ReadonlyArray): string => printArray("Throws", throws) + +const printExamplesArray = (examples: ReadonlyArray): string => { + if (examples.length === 0) { + return "" + } + return examples.map((ex) => "\n\n**Example**\n\n" + printFence(ex)).join("") +} + +const printOptionalSince = (since: ReadonlyArray): string => { + if (since.length === 0) { + return "" + } + return `\n\nSince v${since.join(", ")}` +} + +const printHeaderByIndentation = (indentation: number) => { + switch (indentation) { + case 0: + return "## " + case 1: + return "### " + default: + return "#### " + } +} + +const printTitle = (s: string, deprecated: ReadonlyArray, postfix?: string): string => { + const name = s.trim() === "hasOwnProperty" ? `${s} (function)` : s + const title = deprecated.length > 0 ? Markdown.strikethrough(name) : name + return postfix === undefined ? title : title + ` ${postfix}` +} + +const printSeesArray = (sees?: ReadonlyArray): string => { + if (sees === undefined || sees.length === 0) { + return "" + } + return `\n\n${Markdown.bold("See")}\n\n${sees.map((see) => `- ${replaceJSDocLinks(see)}`).join("\n")}` +} + +const printOptionalSourceLink = (position?: Domain.Position) => { + return Effect.gen(function*() { + if (position === undefined) { + return "" + } + const config = yield* Configuration.Configuration + const source = yield* Parser.Source + const name = source.sourceFile.getBaseName() + return `\n\n[Source](${config.srcLink}${name}#L${position.line})` + }) +} + +const printModel = (name: string, doc: Domain.Doc, options: { + readonly signature?: string | undefined + readonly position?: Domain.Position | undefined + readonly indentation?: number | undefined + readonly postfix?: string | undefined +}) => { + return Effect.gen(function*() { + const sourceLink = yield* printOptionalSourceLink(options.position) + const description = yield* printOptionalDescription(doc.description) + return printHeaderByIndentation(options.indentation ?? 0) + printTitle(name, doc.deprecated, options.postfix) + + description + + printThrowsArray(doc.throws) + + printExamplesArray(doc.examples) + + printSeesArray(doc.sees) + + printOptionalSignature(options.signature) + + sourceLink + + printOptionalSince(doc.since) + }) +} + +const printEntry = (model: Domain.DocEntry, options: { + readonly indentation?: number | undefined + readonly postfix?: string | undefined +}) => { + return printModel(model.name, model.doc, { + signature: model.signature, + position: model.position, + indentation: options.indentation, + postfix: options.postfix + }) +} + +const printStaticMethod = (model: Domain.DocEntry) => { + return printEntry(model, { + indentation: 1, + postfix: "(static method)" + }) +} + +const printMethod = (model: Domain.DocEntry) => { + return printEntry(model, { + indentation: 1, + postfix: "(method)" + }) +} + +const printProperty = (model: Domain.DocEntry) => { + return printEntry(model, { + indentation: 1, + postfix: "(property)" + }) +} + +const printClass = (model: Domain.Class) => { + return Effect.gen(function*() { + const header = yield* printEntry(model, { + postfix: "(class)" + }) + const staticMethods = yield* Effect.forEach(model.staticMethods, (method) => printStaticMethod(method)) + const methods = yield* Effect.forEach(model.methods, (method) => printMethod(method)) + const properties = yield* Effect.forEach(model.properties, (property) => printProperty(property)) + return header + + staticMethods.map((s) => "\n\n" + s).join("") + + methods.map((s) => "\n\n" + s).join("") + + properties.map((s) => "\n\n" + s).join("") + }) +} + +const printConstant = (model: Domain.Constant) => { + return printEntry(model, {}) +} + +const printExport = (model: Domain.Export) => { + return printEntry(model, { + postfix: model.isNamespaceExport ? "(namespace export)" : undefined + }) +} + +const printFunction = (model: Domain.Function) => { + return printEntry(model, {}) +} + +const printInterface = (model: Domain.Interface, indentation: number) => { + return printEntry(model, { + indentation, + postfix: "(interface)" + }) +} + +const printTypeAlias = (model: Domain.TypeAlias, indentation: number) => { + return printEntry(model, { + indentation, + postfix: "(type alias)" + }) +} + +const printNamespace = ( + model: Domain.Namespace, + indentation: number +): Effect.Effect => { + return Effect.gen(function*() { + const header = yield* printModel(model.name, model.doc, { + position: model.position, + indentation, + postfix: "(namespace)" + }) + const interfaces = yield* Effect.forEach(model.interfaces, (inter) => printInterface(inter, indentation + 1)) + const typeAliases = yield* Effect.forEach( + model.typeAliases, + (typeAlias) => printTypeAlias(typeAlias, indentation + 1) + ) + const namespaces = yield* Effect.forEach( + model.namespaces, + (namespace) => printNamespace(namespace, indentation + 1) + ) + return header + + interfaces.map((s) => "\n\n" + s).join("") + + typeAliases.map((s) => "\n\n" + s).join("") + + namespaces.map((s) => "\n\n" + s).join("") + }) +} + +/** @internal */ +export const print = (p: Printable) => { + switch (p._tag) { + case "Class": + return printClass(p) + case "Constant": + return printConstant(p) + case "Export": + return printExport(p) + case "Function": + return printFunction(p) + case "Interface": + return printInterface(p, 0) + case "TypeAlias": + return printTypeAlias(p, 0) + case "Namespace": + return printNamespace(p, 0) + } +} + +const DEFAULT_CATEGORY = "utils" + +const byCategory = Order.mapInput( + String.Order, + ([category]: [string, ...Array]) => category +) + +const getPrintables = (module: Domain.Module): ReadonlyArray => + Array.flatten([ + module.classes, + module.constants, + module.exports, + module.functions, + module.interfaces, + module.typeAliases, + module.namespaces + ]) + +const sortByName: (self: Iterable) => Array = Array.sort( + pipe( + String.Order, + Order.mapInput(({ name }: { name: string }) => name) + ) +) + +/** + * Renders a parsed module as a Markdown documentation page. + * + * @category printers + * @since 0.6.0 + */ +export const printModule = (module: Domain.Module) => { + return Effect.gen(function*() { + const description = yield* printModel(module.name, module.doc, { + postfix: "overview" + }) + + const printables = pipe( + sortByName(getPrintables(module)), + Array.groupBy((printable) => + printable.doc.category.length === 0 ? DEFAULT_CATEGORY : printable.doc.category.join(", ") + ), + Record.toEntries, + Array.sort(byCategory) + ) + + const strings = yield* Effect.forEach(printables, ([category, printables]) => + Effect.gen(function*() { + const out = `\n\n# ${category}` + const strings = yield* Effect.forEach(sortByName(printables), (printable) => print(printable)) + return out + strings.map((s) => "\n\n" + s).join("") + })) + + const content = strings.join("") + + return `${description} + +${content}` + }).pipe(Effect.provideService(Parser.Source, module.source)) +} + +const defaultPrettierOptions: Prettier.Options = { + parser: "markdown", + semi: false, + singleQuote: false, + printWidth: 120, + trailingComma: "none" +} + +/** + * Renders the front matter for a generated module page. + * + * @category printers + * @since 0.6.0 + */ +export const printFrontMatter = (module: Domain.Module, nav_order: number): string => { + return `--- +title: ${module.name} +nav_order: ${nav_order} +parent: Modules +---` +} + +/** + * Formats generated Markdown with the docgen Prettier settings. + * + * @category printers + * @since 0.6.0 + */ +export function prettify(s: string) { + return Effect.tryPromise({ + try: () => Prettier.format(s, defaultPrettierOptions), + catch: globalThis.String + }).pipe(Effect.orDie) +} diff --git a/repos/effect/packages/tools/docgen/src/bin.ts b/repos/effect/packages/tools/docgen/src/bin.ts new file mode 100755 index 0000000000..91d8e9d98b --- /dev/null +++ b/repos/effect/packages/tools/docgen/src/bin.ts @@ -0,0 +1,22 @@ +#!/usr/bin/env node + +/** + * @since 0.6.0 + */ +import * as NodeRuntime from "@effect/platform-node/NodeRuntime" +import * as NodeServices from "@effect/platform-node/NodeServices" +import * as Effect from "effect/Effect" +import * as Layer from "effect/Layer" +import { cli } from "./CLI.ts" +import * as Configuration from "./Configuration.ts" +import * as Domain from "./Domain.ts" + +const MainLive = Configuration.configProviderLayer.pipe( + Layer.provideMerge(Layer.mergeAll(Domain.Process.layer, NodeServices.layer)) +) + +Effect.sync(() => process.argv.slice(2)).pipe( + Effect.flatMap(cli), + Effect.provide(MainLive), + NodeRuntime.runMain +) diff --git a/repos/effect/packages/tools/docgen/src/index.ts b/repos/effect/packages/tools/docgen/src/index.ts new file mode 100644 index 0000000000..7679c3b12d --- /dev/null +++ b/repos/effect/packages/tools/docgen/src/index.ts @@ -0,0 +1,28 @@ +/** + * @since 0.6.0 + */ + +/** + * @since 0.6.0 + */ +export * as Configuration from "./Configuration.ts" + +/** + * @since 0.6.0 + */ +export * as Core from "./Core.ts" + +/** + * @since 0.6.0 + */ +export * as Domain from "./Domain.ts" + +/** + * @since 0.6.0 + */ +export * as Printer from "./Printer.ts" + +/** + * @since 0.6.0 + */ +export * as Parser from "./Parser.ts" diff --git a/repos/effect/packages/tools/docgen/src/internal/markdown-toc.d.ts b/repos/effect/packages/tools/docgen/src/internal/markdown-toc.d.ts new file mode 100644 index 0000000000..4b50ab81d4 --- /dev/null +++ b/repos/effect/packages/tools/docgen/src/internal/markdown-toc.d.ts @@ -0,0 +1,4 @@ +declare module "@effect/markdown-toc" { + const markdownToc: (content: string, options: { readonly bullets: string }) => { readonly content: string } + export default markdownToc +} diff --git a/repos/effect/packages/tools/docgen/test/Checker.test.ts b/repos/effect/packages/tools/docgen/test/Checker.test.ts new file mode 100644 index 0000000000..5ee3d60adc --- /dev/null +++ b/repos/effect/packages/tools/docgen/test/Checker.test.ts @@ -0,0 +1,220 @@ +import * as Checker from "@effect/docgen/Checker" +import * as Configuration from "@effect/docgen/Configuration" +import * as Parser from "@effect/docgen/Parser" +import { assert, describe, it } from "@effect/vitest" +import { Effect, Predicate } from "effect" +import * as Path from "effect/Path" +import * as ast from "ts-morph" + +const project = new ast.Project({ + compilerOptions: { strict: true }, + useInMemoryFileSystem: true +}) + +const defaultConfig: Configuration.ConfigurationShape = { + projectName: "docgen", + projectHomepage: "https://github.com/effect-ts/docgen", + srcLink: "https://github.com/effect-ts/docgen/blob/main/src/", + srcDir: "src", + outDir: "docs", + theme: "mikearnaldi/just-the-docs", + enableSearch: true, + enforceDescriptions: false, + enforceExamples: false, + enforceVersion: true, + runExamples: false, + tscExecutable: "tsc", + exclude: [], + parseCompilerOptions: {}, + examplesCompilerOptions: {} +} + +const makeSourcefile = (source: string | ast.SourceFile) => { + if (Predicate.isString(source)) { + const filename = `test.ts` + const existing = project.getSourceFile(filename) + if (existing) { + project.removeSourceFile(existing) + } + return project.createSourceFile(filename, source) + } + return source +} + +const makeSource = (source: string | ast.SourceFile) => { + const sourceFile = makeSourcefile(source) + const filename = sourceFile.getBaseName() + return Parser.Source.of({ + path: [filename], + sourceFile + }) +} + +const expectFailure = ( + config: Partial, + sourceText: string, + parser: Effect.Effect, + checker: (a: A) => Effect.Effect, never, Configuration.Configuration | Parser.Source>, + failure: ReadonlyArray +) => { + return Effect.gen(function*() { + const actual = yield* Effect.exit(parser.pipe( + Effect.flatMap(checker), + Effect.provideService(Parser.Source, makeSource(sourceText)), + Effect.provideService(Configuration.Configuration, { ...defaultConfig, ...config }), + Effect.provide(Path.layer) + )) + assert.ok(actual._tag === "Success") + assert.deepStrictEqual(actual.value, failure) + }) +} + +describe("Checker", () => { + describe("checkFunctions", () => { + it.effect("should raise an error if `@since` tag is missing", () => + expectFailure( + {}, + ` +/** @since 1.0.0 */ +export function a() {} + +/** description */ +export function b() {} + `, + Parser.parseFunctions, + Checker.checkFunctions, + [ + "Missing `@since` tag in file /test.ts:\n" + + "\n" + + " 4 |\n" + + " 5 | /** description */\n" + + "> 6 | export function b() {}\n" + + " | ^\n" + + " 7 | " + ] + )) + }) + + describe("checkExports", () => { + it.effect("should raise an error if `@since` tag is missing", () => + expectFailure( + {}, + "export { a }", + Parser.parseExports, + Checker.checkExports, + [ + "Missing `@since` tag in file /test.ts:\n" + + "\n" + + "> 1 | export { a }\n" + + " | ^" + ] + )) + }) + + describe("checkNamespaces", () => { + it.effect("should raise an error if `@since` tag is missing", () => + expectFailure( + {}, + "export namespace A {}", + Parser.parseNamespaces, + Checker.checkNamespaces, + [ + "Missing `@since` tag in file /test.ts:\n" + + "\n" + + "> 1 | export namespace A {}\n" + + " | ^" + ] + )) + + it.effect("should raise an error if `@since` tag is missing on a nested interface", () => + expectFailure( + {}, + ` + /** + * @since 1.0.0 + */ + export namespace A { + export interface B {} + } + `, + Parser.parseNamespaces, + Checker.checkNamespaces, + [ + "Missing `@since` tag in file /test.ts:\n" + + "\n" + + " 4 | */\n" + + " 5 | export namespace A {\n" + + "> 6 | export interface B {}\n" + + " | ^\n" + + " 7 | }\n" + + " 8 | " + ] + )) + + it.effect("should raise an error if `@since` tag is missing on a nested type alias", () => + expectFailure( + {}, + ` + /** + * @since 1.0.0 + */ + export namespace A { + export type B = string + } + `, + Parser.parseNamespaces, + Checker.checkNamespaces, + [ + "Missing `@since` tag in file /test.ts:\n" + + "\n" + + " 4 | */\n" + + " 5 | export namespace A {\n" + + "> 6 | export type B = string\n" + + " | ^\n" + + " 7 | }\n" + + " 8 | " + ] + )) + + it.effect("should raise an error if `@since` tag is missing on a nested namespace", () => + expectFailure( + {}, + ` + /** + * @since 1.0.0 + */ + export namespace A { + export namespace B {} + } + `, + Parser.parseNamespaces, + Checker.checkNamespaces, + [ + "Missing `@since` tag in file /test.ts:\n" + + "\n" + + " 4 | */\n" + + " 5 | export namespace A {\n" + + "> 6 | export namespace B {}\n" + + " | ^\n" + + " 7 | }\n" + + " 8 | " + ] + )) + }) + + describe("checkClasses", () => { + it.effect("should raise an error if `@since` tag is missing", () => + expectFailure( + {}, + `export class MyClass {}`, + Parser.parseClasses, + Checker.checkClasses, + [ + "Missing `@since` tag in file /test.ts:\n" + + "\n" + + "> 1 | export class MyClass {}\n" + + " | ^" + ] + )) + }) +}) diff --git a/repos/effect/packages/tools/docgen/test/Configuration.test.ts b/repos/effect/packages/tools/docgen/test/Configuration.test.ts new file mode 100644 index 0000000000..653c05ce29 --- /dev/null +++ b/repos/effect/packages/tools/docgen/test/Configuration.test.ts @@ -0,0 +1,333 @@ +import * as CLI from "@effect/docgen/CLI" +import * as Configuration from "@effect/docgen/Configuration" +import * as Domain from "@effect/docgen/Domain" +import * as NodeServices from "@effect/platform-node/NodeServices" +import { assert, describe, it } from "@effect/vitest" +import * as Context from "effect/Context" +import * as Effect from "effect/Effect" +import * as Exit from "effect/Exit" +import * as FileSystem from "effect/FileSystem" +import * as Layer from "effect/Layer" +import * as Option from "effect/Option" +import * as Path from "effect/Path" +import * as Result from "effect/Result" +import * as Stdio from "effect/Stdio" +import * as CliOutput from "effect/unstable/cli/CliOutput" +import * as Command from "effect/unstable/cli/Command" + +type DocgenJson = typeof Configuration.ConfigurationSchema.Type + +const existingFile = `${import.meta.dirname}/fixtures/invalid-json.txt` + +const fileInfo: FileSystem.File.Info = { + type: "File", + mtime: Option.none(), + atime: Option.none(), + birthtime: Option.none(), + dev: 0, + ino: Option.none(), + mode: 0, + nlink: Option.none(), + uid: Option.none(), + gid: Option.none(), + rdev: Option.none(), + size: FileSystem.Size(0), + blksize: Option.none(), + blocks: Option.none() +} + +class DocgenJsonTag extends Context.Service()("DocgenJsonTag") {} + +const makeDocgenJson = (config: DocgenJson) => Layer.succeed(DocgenJsonTag, config) + +const TestFileSystem = Layer.effect( + FileSystem.FileSystem, + Effect.gen(function*() { + const path = yield* Path.Path + const context = yield* Effect.context() + const docgenJson = Option.getOrElse(Context.getOption(context, DocgenJsonTag), () => ({} as DocgenJson)) + const readFileString: FileSystem.FileSystem["readFileString"] = (filePath) => { + const fileName = path.basename(filePath) + if (fileName === "package.json") { + return Effect.succeed(JSON.stringify({ name: "name", homepage: "homepage" })) + } else if (fileName === "docgen.json") { + return Effect.succeed(JSON.stringify(docgenJson)) + } + return Effect.die(`file not found: ${filePath}`) + } + const exists: FileSystem.FileSystem["exists"] = (filePath) => { + const fileName = path.basename(filePath) + if (fileName === "invalid-json.txt") { + return Effect.succeed(true) + } + if (fileName === "docgen.json") { + return Effect.succeed(Context.getOption(context, DocgenJsonTag).pipe(Option.isSome)) + } + return Effect.succeed(false) + } + const stat: FileSystem.FileSystem["stat"] = (filePath) => + path.basename(filePath) === "invalid-json.txt" ? Effect.succeed(fileInfo) : Effect.die("file not found") + return FileSystem.makeNoop({ exists, readFileString, stat }) + }) +).pipe(Layer.provide(Path.layer)) + +const makeProcess = (env: Record = {}) => + Layer.succeed(Domain.Process, { + cwd: Effect.sync(() => process.cwd()), + platform: Effect.sync(() => process.platform), + argv: Effect.sync(() => process.argv), + env: Effect.succeed(env) + }) + +const makeTestLive = (env: Record = {}) => + Configuration.configProviderLayer.pipe( + Layer.fresh, + Layer.provideMerge(Layer.mergeAll( + CliOutput.layer(CliOutput.defaultFormatter({ colors: false })), + NodeServices.layer, + Stdio.layerTest({}), + makeProcess(env), + Path.layer, + TestFileSystem + )) + ) + +const testCliFor = ( + program: Effect.Effect +) => { + const command = CLI.docgenCommand.pipe( + Command.withHandler(() => program), + Command.provideEffect(Configuration.Configuration, CLI.loadConfiguration) + ) + return Command.runWith(command, { version: "v1.0.0" }) +} + +describe("Configuration", () => { + it.effect("should use the default configuration if no configuration is provided", () => { + const program = Effect.gen(function*() { + const config = yield* Configuration.Configuration + assert.deepStrictEqual(config, { + projectName: "name", + projectHomepage: "homepage", + srcLink: "homepage/blob/main/src/", + srcDir: "src", + outDir: "docs", + theme: "mikearnaldi/just-the-docs", + enableSearch: true, + enforceDescriptions: false, + enforceExamples: false, + enforceVersion: true, + runExamples: false, + tscExecutable: "tsc", + exclude: [], + parseCompilerOptions: Configuration.defaultCompilerOptions, + examplesCompilerOptions: Configuration.defaultCompilerOptions + }) + }) + return testCliFor(program)([]).pipe(Effect.provide(makeTestLive())) + }) + + it.effect("should use the configuration contained in docgen.json if it exists", () => { + const parseCompilerOptions = { + noEmit: true, + strict: true, + skipLibCheck: true, + exactOptionalPropertyTypes: true, + moduleResolution: "Bundler", + target: "ES2022", + lib: ["ES2022", "DOM"] + } + const program = Effect.gen(function*() { + const config = yield* Configuration.Configuration + assert.deepStrictEqual(config, { + projectName: "name", + projectHomepage: "myproject", + srcLink: "mygithub", + srcDir: "src", + outDir: "docs", + theme: "mikearnaldi/just-the-docs", + enableSearch: true, + enforceDescriptions: false, + enforceExamples: false, + enforceVersion: true, + runExamples: false, + tscExecutable: "tsc", + exclude: [], + parseCompilerOptions, + examplesCompilerOptions: Configuration.defaultCompilerOptions + }) + }) + return testCliFor(program)([]).pipe( + Effect.provide( + makeTestLive().pipe(Layer.provide(makeDocgenJson({ + projectHomepage: "myproject", + srcLink: "mygithub", + parseCompilerOptions + }))) + ) + ) + }) + + it.effect("should raise a validation error if docgen.json is not valid", () => + Effect.gen(function*() { + const result = yield* Effect.exit( + testCliFor(Effect.void)([]).pipe( + Effect.provide(makeTestLive().pipe(Layer.provide(makeDocgenJson({ projectHomepage: 1 } as any)))) + ) + ) + if (Exit.isSuccess(result)) { + return assert.fail("expected configuration validation to fail") + } + assert.include(globalThis.String(result.cause), "Configuration.validateJsonFile") + assert.include(globalThis.String(result.cause), "projectHomepage") + })) + + it.effect("accepts inverse search and version flags", () => { + const program = Effect.gen(function*() { + const config = yield* Configuration.Configuration + assert.isTrue(config.enableSearch) + assert.isTrue(config.enforceVersion) + }) + return testCliFor(program)(["--enable-search", "--enforce-version"]).pipe( + Effect.provide( + makeTestLive().pipe(Layer.provide(makeDocgenJson({ + enableSearch: false, + enforceVersion: false + }))) + ) + ) + }) + + it.effect("retains boolean values from docgen.json", () => { + const program = Effect.gen(function*() { + const config = yield* Configuration.Configuration + assert.isFalse(config.enableSearch) + assert.isFalse(config.enforceVersion) + }) + return testCliFor(program)([]).pipe( + Effect.provide( + makeTestLive().pipe(Layer.provide(makeDocgenJson({ + enableSearch: false, + enforceVersion: false + }))) + ) + ) + }) + + it.effect("retains primary search and version flags", () => { + const program = Effect.gen(function*() { + const config = yield* Configuration.Configuration + assert.isFalse(config.enableSearch) + assert.isFalse(config.enforceVersion) + }) + return testCliFor(program)(["--disable-search", "--no-enforce-version"]).pipe( + Effect.provide(makeTestLive()) + ) + }) + + it.effect("automatic negative flags override true environment configuration", () => { + const program = Effect.gen(function*() { + const config = yield* Configuration.Configuration + assert.isFalse(config.enforceDescriptions) + assert.isFalse(config.enforceExamples) + assert.isFalse(config.runExamples) + }) + return testCliFor(program)([ + "--no-enforce-descriptions", + "--no-enforce-examples", + "--no-run-examples" + ]).pipe(Effect.provide(makeTestLive({ + DOCGEN_ENFORCE_DESCRIPTIONS: "true", + DOCGEN_ENFORCE_EXAMPLES: "true", + DOCGEN_RUN_EXAMPLES: "true" + }))) + }) + + it.effect("docgen.json retains its later runExamples precedence", () => { + const program = Effect.gen(function*() { + const config = yield* Configuration.Configuration + assert.isTrue(config.runExamples) + }) + return testCliFor(program)(["--no-run-examples"]).pipe( + Effect.provide( + makeTestLive({ DOCGEN_RUN_EXAMPLES: "true" }).pipe( + Layer.provide(makeDocgenJson({ runExamples: true })) + ) + ) + ) + }) + + it.effect("loads comma-delimited environment arrays before docgen.json", () => { + const program = Effect.gen(function*() { + const process = yield* Domain.Process + assert.deepStrictEqual(yield* process.env, { DOCGEN_EXCLUDE: "a,b" }) + const config = yield* Configuration.Configuration + assert.deepStrictEqual(config.exclude, ["a", "b"]) + }) + return testCliFor(program)([]).pipe( + Effect.provide( + makeTestLive({ DOCGEN_EXCLUDE: "a,b" }).pipe( + Layer.provide(makeDocgenJson({ exclude: ["from-docgen"] })) + ) + ) + ) + }) + + it.effect("parses inline compiler options as JSON records", () => { + const program = Effect.gen(function*() { + const config = yield* Configuration.Configuration + assert.deepStrictEqual(config.parseCompilerOptions, { strict: false }) + assert.deepStrictEqual(config.examplesCompilerOptions, { module: "ESNext" }) + }) + return testCliFor(program)([ + "--parse-compiler-options", + "{\"strict\":false}", + "--examples-compiler-options", + "{\"module\":\"ESNext\"}" + ]).pipe(Effect.provide(makeTestLive())) + }) + + it.effect("rejects both compiler option forms for the same category", () => + Effect.gen(function*() { + const cases = [ + ["--parse-tsconfig-file", "--parse-compiler-options"], + ["--examples-tsconfig-file", "--examples-compiler-options"] + ] as const + for (const [fileFlag, inlineFlag] of cases) { + const result = yield* Effect.result( + testCliFor(Effect.void)([fileFlag, existingFile, inlineFlag, "{}"]).pipe( + Effect.provide(makeTestLive()) + ) + ) + if (Result.isSuccess(result)) { + return assert.fail(`${fileFlag} and ${inlineFlag} should be mutually exclusive`) + } + assert.strictEqual(result.failure._tag, "InvalidValue") + if (result.failure._tag === "InvalidValue") { + assert.include(result.failure.expected, `only one of ${fileFlag} or ${inlineFlag}`) + } + } + })) + + it.effect("rejects non-record inline compiler options", () => + Effect.gen(function*() { + for (const flag of ["--parse-compiler-options", "--examples-compiler-options"]) { + for (const value of ["null", "[]", "1", "\"text\""]) { + const result = yield* Effect.result( + testCliFor(Effect.void)([flag, value]).pipe(Effect.provide(makeTestLive())) + ) + if (Result.isSuccess(result)) { + return assert.fail(`${flag} should reject ${value}`) + } + assert.strictEqual(result.failure._tag, "ShowHelp") + if (result.failure._tag === "ShowHelp") { + assert.isTrue( + result.failure.errors.some((error) => + error._tag === "InvalidValue" && error.expected.includes("JSON record") + ) + ) + } + } + } + })) +}) diff --git a/repos/effect/packages/tools/docgen/test/Core.test.ts b/repos/effect/packages/tools/docgen/test/Core.test.ts new file mode 100644 index 0000000000..fd16ccf2c1 --- /dev/null +++ b/repos/effect/packages/tools/docgen/test/Core.test.ts @@ -0,0 +1,66 @@ +import * as Core from "@effect/docgen/Core" +import * as NodeServices from "@effect/platform-node/NodeServices" +import { assert, describe, it } from "@effect/vitest" +import * as Effect from "effect/Effect" + +const assertFencedCode = ( + markdown: string, + expectedExamples: ReadonlyArray, + expectedWarnings: ReadonlyArray +) => { + assert.deepStrictEqual(Core.extractFencedCode(markdown), [expectedExamples, expectedWarnings]) +} + +describe("Core", () => { + describe("[internal] extractFencedCode", () => { + it("should extract fenced code blocks from markdown (backticks)", () => { + assertFencedCode("a\n\n```ts\nconst a = 1\n```\n\nb", ["const a = 1"], []) + }) + + it("should extract fenced code blocks from markdown (tildes)", () => { + assertFencedCode("a\n\n~~~ts\nconst a = 1\n~~~~\n\nb", ["const a = 1"], []) + }) + + it("should skip-type-checking (backticks)", () => { + assertFencedCode("a\n\n```ts skip-type-checking a=1\nconst a = 1\n```\n\nb", [], []) + }) + + it("should skip-type-checking (tildes)", () => { + assertFencedCode("a\n\n~~~ts skip-type-checking a=1\nconst a = 1\n~~~~\n\nb", [], []) + }) + + it("should handle metadata (backticks)", () => { + assertFencedCode("a\n\n```ts a=1\nconst a = 1\n```\n\nb", ["const a = 1"], []) + }) + + it("should handle metadata (tildes)", () => { + assertFencedCode("a\n\n~~~ts a=1\nconst a = 1\n~~~~\n\nb", ["const a = 1"], []) + }) + + it("should handle non closing fences (backticks)", () => { + assertFencedCode("a\n\n```ts\nconst a = 1", ["const a = 1"], [ + "Code block does not have a matching closing fence:\na\n\n```ts\nconst a = 1" + ]) + }) + + it("should handle non closing fences (tildes)", () => { + assertFencedCode("a\n\n~~~ts\nconst a = 1", ["const a = 1"], [ + "Code block does not have a matching closing fence:\na\n\n~~~ts\nconst a = 1" + ]) + }) + }) + + describe("[internal] runCommand", () => { + it.effect("streams output without a maxBuffer limit", () => + Effect.gen(function*() { + const size = 1024 * 1024 + 1 + const result = yield* Core.runCommand("node", [ + "-e", + `process.stdout.write("x".repeat(${size})); process.stderr.write("problem"); process.exitCode = 2` + ], false) + assert.strictEqual(result.stdout.length, size) + assert.strictEqual(result.stderr, "problem") + assert.strictEqual(result.exitCode, 2) + }).pipe(Effect.scoped, Effect.provide(NodeServices.layer))) + }) +}) diff --git a/repos/effect/packages/tools/docgen/test/Parser.test.ts b/repos/effect/packages/tools/docgen/test/Parser.test.ts new file mode 100644 index 0000000000..7b831bbd64 --- /dev/null +++ b/repos/effect/packages/tools/docgen/test/Parser.test.ts @@ -0,0 +1,1829 @@ +import * as Configuration from "@effect/docgen/Configuration" +import * as Domain from "@effect/docgen/Domain" +import * as Parser from "@effect/docgen/Parser" +import * as Printer from "@effect/docgen/Printer" +import { assert, describe, it } from "@effect/vitest" +import { Effect, Exit, Predicate } from "effect" +import * as Path from "effect/Path" +import * as ast from "ts-morph" + +let testCounter = 0 + +const project = new ast.Project({ + compilerOptions: { strict: true }, + useInMemoryFileSystem: true +}) + +const defaultConfig: Configuration.ConfigurationShape = { + projectName: "docgen", + projectHomepage: "https://github.com/effect-ts/docgen", + srcLink: "https://github.com/effect-ts/docgen/blob/main/src/", + srcDir: "src", + outDir: "docs", + theme: "mikearnaldi/just-the-docs", + enableSearch: true, + enforceDescriptions: false, + enforceExamples: false, + enforceVersion: true, + runExamples: false, + tscExecutable: "tsc", + exclude: [], + parseCompilerOptions: {}, + examplesCompilerOptions: {} +} + +const makeSourcefile = (source: string | ast.SourceFile) => { + if (Predicate.isString(source)) { + const filename = `test.ts` + const existing = project.getSourceFile(filename) + if (existing) { + project.removeSourceFile(existing) + } + return project.createSourceFile(filename, source) + } + return source +} + +const makeSource = (source: string | ast.SourceFile) => { + const sourceFile = makeSourcefile(source) + const filename = sourceFile.getBaseName() + return Parser.Source.of({ + path: [filename], + sourceFile + }) +} + +const print = (printables: ReadonlyArray) => { + return Effect.gen(function*() { + const strings = yield* Effect.forEach(printables, (printable) => Printer.print(printable)) + return strings.join("\n") + }) +} + +const isModule = (printableOr: ReadonlyArray | Domain.Module): printableOr is Domain.Module => { + return !Array.isArray(printableOr) +} + +const expectMarkdown = Effect.fnUntraced(function*( + eff: Effect.Effect< + ReadonlyArray | Domain.Module, + E, + Parser.Source | Configuration.Configuration | Path.Path + >, + sourceText: string, + expected: string, + config?: Partial +) { + const exit = yield* Effect.exit(eff.pipe( + Effect.flatMap((printableOr) => { + if (isModule(printableOr)) { + return Printer.printModule(printableOr) + } + return print(printableOr) + }), + Effect.provideService(Parser.Source, makeSource(sourceText)), + Effect.provideService(Configuration.Configuration, { ...defaultConfig, ...config }), + Effect.provide(Path.layer) + )) + assert.ok(exit._tag === "Success") + if (exit.value !== expected) { + console.log(exit.value) + } + assert.strictEqual(exit.value, expected) +}) + +describe("Parser", () => { + describe("parseModule", () => { + it.effect("should not require an example for modules when `enforceExamples` is set to true", () => + Effect.gen(function*() { + yield* expectMarkdown( + Parser.parseModule, + `/** +* This is the assert module. +* +* @since 1.0.0 +*/ +import * as assert from 'assert' + +/** + * This is the foo export. + * + * @example + * import { foo } from 'test' + * + * console.log(foo) + * + * @category category + * @since 1.0.0 + */ +export const foo = 'foo'`, + `## test.ts overview + +This is the assert module. + +Since v1.0.0 + + + +# category + +## foo + +This is the foo export. + +**Example** + +\`\`\`ts +import { foo } from 'test' + +console.log(foo) +\`\`\` + +**Signature** + +\`\`\`ts +declare const foo: "foo" +\`\`\` + +[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L19) + +Since v1.0.0` + ) + })) + + it.effect("should ignore non-JSDoc comments above JSDoc comments", () => + Effect.gen(function*() { + yield* expectMarkdown( + Parser.parseModule, + `/** +* This is the assert module. +* +* @since 1.0.0 +*/ +import * as assert from 'assert' + +// This comment should be ignored + +/** + * This is the foo export. + * + * @example + * import { foo } from 'test' + * + * console.log(foo) + * + * @category category + * @since 1.0.0 + */ +export const foo = 'foo'`, + `## test.ts overview + +This is the assert module. + +Since v1.0.0 + + + +# category + +## foo + +This is the foo export. + +**Example** + +\`\`\`ts +import { foo } from 'test' + +console.log(foo) +\`\`\` + +**Signature** + +\`\`\`ts +declare const foo: "foo" +\`\`\` + +[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L21) + +Since v1.0.0` + ) + })) + }) + + describe("parseFunctions", () => { + it.effect("omits internal properties from signatures", () => + Effect.gen(function*() { + yield* expectMarkdown( + Parser.parseFunctions, + `/** + * @since 1.0.0 + */ + export const myfunc: (options: { + readonly visible?: string + /** @internal */ + readonly internal?: boolean + }) => void = () => {}`, + `## myfunc + +**Signature** + +\`\`\`ts +declare const myfunc: (options: { readonly visible?: string; }) => void +\`\`\` + +[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L4) + +Since v1.0.0` + ) + })) + + it.effect(`should remove all metadata from typedcript code blocks when the theme is ${Configuration.DEFAULT_THEME}`, () => + Effect.gen(function*() { + yield* expectMarkdown( + Parser.parseFunctions, + `/** + * \`\`\`ts skip-type-checking a=1 showLineNumbers=true + * const a: string = 1 + * \`\`\` + * + * @since 1.0.0 + */ + export function myfunc() {}`, + `## myfunc + +\`\`\`ts +const a: string = 1 +\`\`\` + +**Signature** + +\`\`\`ts +declare const myfunc: () => void +\`\`\` + +[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L8) + +Since v1.0.0` + ) + })) + it.effect("generics", () => + Effect.gen(function*() { + yield* expectMarkdown( + Parser.parseFunctions, + `/** + * This is a description containing two links to {@link foo} and {@link bar}. + * + * @since 1.2.0 + */ + export function myfunc() {}`, + `## myfunc + +This is a description containing two links to \`foo\` and \`bar\`. + +**Signature** + +\`\`\`ts +declare const myfunc: () => void +\`\`\` + +[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L6) + +Since v1.2.0` + ) + })) + + it.effect("description", () => + Effect.gen(function*() { + yield* expectMarkdown( + Parser.parseFunctions, + `/** + * This is a description containing two links to {@link foo} and {@link bar}. + * + * @since 1.2.0 + */ + export function myfunc() {}`, + `## myfunc + +This is a description containing two links to \`foo\` and \`bar\`. + +**Signature** + +\`\`\`ts +declare const myfunc: () => void +\`\`\` + +[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L6) + +Since v1.2.0` + ) + })) + + it.effect("throws", () => + Effect.gen(function*() { + yield* expectMarkdown( + Parser.parseFunctions, + `/** + * description... + * @throws \`Error1\` - Description 1 + * @throws \`Error2\` - Description 2 + * @since 1.2.0 + */ + export function myfunc() {}`, + `## myfunc + +description... + +**Throws** + +\`Error1\` - Description 1 +\`Error2\` - Description 2 + +**Signature** + +\`\`\`ts +declare const myfunc: () => void +\`\`\` + +[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L7) + +Since v1.2.0` + ) + })) + + it.effect("sees", () => + Effect.gen(function*() { + yield* expectMarkdown( + Parser.parseFunctions, + `/** + * description... + * @see \`foo\` Description 1 + * @see {@link bar} Description 2 + * @see {@link baz quux} Description 2 + * @since 1.2.0 + */ + export function myfunc() {}`, + `## myfunc + +description... + +**See** + +- \`foo\` Description 1 +- \`bar\` Description 2 +- \`quux\` Description 2 + +**Signature** + +\`\`\`ts +declare const myfunc: () => void +\`\`\` + +[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L8) + +Since v1.2.0` + ) + })) + + it.effect("example without fence", () => + Effect.gen(function*() { + yield* expectMarkdown( + Parser.parseFunctions, + `/** + * description... + * @example + * const x = 1 + * @since 1.0.0 + */ + export function myfunc() {}`, + `## myfunc + +description... + +**Example** + +\`\`\`ts +const x = 1 +\`\`\` + +**Signature** + +\`\`\`ts +declare const myfunc: () => void +\`\`\` + +[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L7) + +Since v1.0.0` + ) + })) + + it.effect("example with backtick fence", () => + Effect.gen(function*() { + yield* expectMarkdown( + Parser.parseFunctions, + `/** + * description... + * @example + * \`\`\`ts + * const x = 1 + * \`\`\` + * @since 1.0.0 + */ + export function myfunc() {}`, + `## myfunc + +description... + +**Example** + +\`\`\`ts +const x = 1 +\`\`\` + +**Signature** + +\`\`\`ts +declare const myfunc: () => void +\`\`\` + +[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L9) + +Since v1.0.0` + ) + })) + + it.effect("2 examples", () => + Effect.gen(function*() { + yield* expectMarkdown( + Parser.parseFunctions, + `/** + * description... + * @example + * \`\`\`ts + * const x = 1 + * \`\`\` + * @example + * \`\`\`ts + * const x = 2 + * \`\`\` + * @since 1.0.0 + */ + export function myfunc() {}`, + `## myfunc + +description... + +**Example** + +\`\`\`ts +const x = 1 +\`\`\` + +**Example** + +\`\`\`ts +const x = 2 +\`\`\` + +**Signature** + +\`\`\`ts +declare const myfunc: () => void +\`\`\` + +[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L13) + +Since v1.0.0` + ) + })) + + it.effect("example with metas", () => + Effect.gen(function*() { + yield* expectMarkdown( + Parser.parseFunctions, + `/** + * description... + * @example + * \`\`\`ts a=1 + * const x = 1 + * \`\`\` + * @since 1.0.0 + */ + export function myfunc() {}`, + `## myfunc + +description... + +**Example** + +\`\`\`ts a=1 +const x = 1 +\`\`\` + +**Signature** + +\`\`\`ts +declare const myfunc: () => void +\`\`\` + +[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L9) + +Since v1.0.0` + ) + })) + + it.effect("example with titde fence", () => + Effect.gen(function*() { + yield* expectMarkdown( + Parser.parseFunctions, + `/** + * description... + * @example + * ~~~ts + * const x = 1 + * ~~~ + * @since 1.0.0 + */ + export function myfunc() {}`, + `## myfunc + +description... + +**Example** + +~~~ts +const x = 1 +~~~ + +**Signature** + +\`\`\`ts +declare const myfunc: () => void +\`\`\` + +[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L9) + +Since v1.0.0` + ) + })) + + it.effect("should not return private function declarations", () => + Effect.gen(function*() { + yield* expectMarkdown( + Parser.parseFunctions, + `/** + * description... + */ + function myfunc() {}`, + "" + ) + })) + + it.effect("should not return ignored function declarations", () => + Effect.gen(function*() { + yield* expectMarkdown( + Parser.parseFunctions, + `/** + * @ignore + */ + export function myfunc() {}`, + "" + ) + })) + + it.effect("should not return ignored function declarations with overloads", () => + Effect.gen(function*() { + yield* expectMarkdown( + Parser.parseFunctions, + `/** + * @ignore + */ + export function sum(a: number, b: number) + export function sum(a: number, b: number): number { return a + b }`, + "" + ) + })) + + it.effect("should not return internal function declarations", () => + Effect.gen(function*() { + yield* expectMarkdown( + Parser.parseFunctions, + `/** + * @internal + */ + export function sum(a: number, b: number): number { return a + b }`, + "" + ) + })) + + it.effect("should not return internal function declarations even with overloads", () => + Effect.gen(function*() { + yield* expectMarkdown( + Parser.parseFunctions, + `/** + * @internal + */ + export function sum(a: number, b: number) + export function sum(a: number, b: number): number { return a + b }`, + "" + ) + })) + + it.effect("should not return private const function declarations", () => + Effect.gen(function*() { + yield* expectMarkdown( + Parser.parseFunctions, + `const sum = (a: number, b: number): number => a + b `, + "" + ) + })) + + it.effect("should not return internal const function declarations", () => + Effect.gen(function*() { + yield* expectMarkdown( + Parser.parseFunctions, + `/** + * @internal + */ + export const sum = (a: number, b: number): number => a + b `, + "" + ) + })) + + it.effect("should account for nullable polymorphic return types", () => + Effect.gen(function*() { + yield* expectMarkdown( + Parser.parseFunctions, + `/** + * @since 1.0.0 + */ + export const toNullable = (ma: A | null): A | null => ma`, + `## toNullable + +**Signature** + +\`\`\`ts +declare const toNullable: (ma: A | null) => A | null +\`\`\` + +[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L4) + +Since v1.0.0` + ) + })) + + it.effect("should handle a const function declaration", () => + Effect.gen(function*() { + yield* expectMarkdown( + Parser.parseFunctions, + `/** + * a description... + * @since 1.0.0 + * @example + * assert.deepStrictEqual(f(1, 2), { a: 1, b: 2 }) + * @example + * assert.deepStrictEqual(f(3, 4), { a: 3, b: 4 }) + * @deprecated + */ + export const f = (a: number, b: number): { [key: string]: number } => ({ a, b })`, + `## ~~f~~ + +a description... + +**Example** + +\`\`\`ts +assert.deepStrictEqual(f(1, 2), { a: 1, b: 2 }) +\`\`\` + +**Example** + +\`\`\`ts +assert.deepStrictEqual(f(3, 4), { a: 3, b: 4 }) +\`\`\` + +**Signature** + +\`\`\`ts +declare const f: (a: number, b: number) => { [key: string]: number; } +\`\`\` + +[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L10) + +Since v1.0.0` + ) + })) + + it.effect("should handle a function declaration", () => + Effect.gen(function*() { + yield* expectMarkdown( + Parser.parseFunctions, + `/** + * @since 1.0.0 + */ + export function f(a: number, b: number): { [key: string]: number } { return { a, b } }`, + `## f + +**Signature** + +\`\`\`ts +declare const f: (a: number, b: number) => { [key: string]: number; } +\`\`\` + +[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L4) + +Since v1.0.0` + ) + })) + + it.effect("should handle overloadings", () => + Effect.gen(function*() { + yield* expectMarkdown( + Parser.parseFunctions, + `/** + * a description... + * @since 1.0.0 + * @deprecated + */ + export function f(a: Int, b: Int): { [key: string]: number } + export function f(a: number, b: number): { [key: string]: number } + export function f(a: any, b: any): { [key: string]: number } { return { a, b } }`, + `## ~~f~~ + +a description... + +**Signature** + +\`\`\`ts +declare const f: { (a: Int, b: Int): { [key: string]: number; }; (a: number, b: number): { [key: string]: number; }; } +\`\`\` + +[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L8) + +Since v1.0.0` + ) + })) + }) + + describe("parseConstants", () => { + it.effect("should handle a constant value", () => + Effect.gen(function*() { + yield* expectMarkdown( + Parser.parseConstants, + `/** + * a description... + * @since 1.0.0 + * @deprecated + */ + export const s: string = ''`, + `## ~~s~~ + +a description... + +**Signature** + +\`\`\`ts +declare const s: string +\`\`\` + +[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L6) + +Since v1.0.0` + ) + })) + + it.effect("should support constants with default type parameters", () => + Effect.gen(function*() { + yield* expectMarkdown( + Parser.parseConstants, + `/** + * @since 1.0.0 + */ + export const left: (l: E) => string = T.left`, + `## left + +**Signature** + +\`\`\`ts +declare const left: (l: E) => string +\`\`\` + +[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L4) + +Since v1.0.0` + ) + })) + + it.effect("should support untyped constants", () => + Effect.gen(function*() { + yield* expectMarkdown( + Parser.parseConstants, + ` + class A {} + /** + * @since 1.0.0 + */ + export const empty = new A()`, + `## empty + +**Signature** + +\`\`\`ts +declare const empty: A +\`\`\` + +[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L6) + +Since v1.0.0` + ) + })) + + it.effect("should handle constants with typeof annotations", () => + Effect.gen(function*() { + yield* expectMarkdown( + Parser.parseConstants, + ` const task: { a: number } = { + a: 1 + } + /** + * @since 1.0.0 + */ + export const taskSeq: typeof task = { + ...task, + ap: (mab, ma) => () => mab().then(f => ma().then(a => f(a))) + }`, + `## taskSeq + +**Signature** + +\`\`\`ts +declare const taskSeq: { a: number; } +\`\`\` + +[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L7) + +Since v1.0.0` + ) + })) + + it.effect("should not include variables declared in for loops", () => + Effect.gen(function*() { + yield* expectMarkdown( + Parser.parseConstants, + ` const object = { a: 1, b: 2, c: 3 }; + + for (const property in object) { + console.log(property); + }`, + "" + ) + })) + }) + + describe("parseTypeAliases", () => { + it.effect("should return a type alias", () => + Effect.gen(function*() { + yield* expectMarkdown( + Parser.parseTypeAliases, + ` + type None = { readonly _tag: "None" } + type Some = { readonly _tag: "Some"; readonly value: A } + /** + * a description... + * @since 1.0.0 + * @deprecated + */ + export type Option = None | Some`, + `## ~~Option~~ (type alias) + +a description... + +**Signature** + +\`\`\`ts +type Option = None | Some +\`\`\` + +[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L9) + +Since v1.0.0` + ) + })) + }) + + describe("parseExports", () => { + it.effect("should return no exports if the file is empty", () => + Effect.gen(function*() { + yield* expectMarkdown( + Parser.parseExports, + "", + "" + ) + })) + + it.effect("should return an `Export`", () => + Effect.gen(function*() { + yield* expectMarkdown( + Parser.parseExports, + ` + const a = 1; + const b = 2; + export { + /** + * description_of_a + * \`\`\`ts + * const a: string = 1 + * \`\`\` + * + * @since 1.0.0 + */ + a, + /** + * description_of_b + * @since 2.0.0 + */ + b + }`, + `## a + +description_of_a +\`\`\`ts +const a: string = 1 +\`\`\` + +**Signature** + +\`\`\`ts +declare const a: 1 +\`\`\` + +[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L13) + +Since v1.0.0 +## b + +description_of_b + +**Signature** + +\`\`\`ts +declare const b: 2 +\`\`\` + +[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L18) + +Since v2.0.0` + ) + })) + + it.effect("should handle renamimg", () => + Effect.gen(function*() { + yield* expectMarkdown( + Parser.parseExports, + `const a = 1; + export { + /** + * @since 1.0.0 + */ + a as b + }`, + `## b + +**Signature** + +\`\`\`ts +declare const b: 1 +\`\`\` + +[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L6) + +Since v1.0.0` + ) + })) + + it.effect("should handle a single re-export", () => + Effect.gen(function*() { + project.createSourceFile("a.ts", `export const a = 1`) + const sourceFile = project.createSourceFile( + "b.ts", + `import { a } from './a' + const b = a + export { + /** + * @since 1.0.0 + */ + b + }` + ) + const actual = yield* Effect.exit(Parser.parseExports.pipe( + Effect.provideService(Parser.Source, makeSource(sourceFile)), + Effect.provideService(Configuration.Configuration, defaultConfig) + )) + assert.deepStrictEqual( + actual, + Exit.succeed([ + new Domain.Export( + "b", + new Domain.Doc( + undefined, + ["1.0.0"], + [], + [], + [], + [], + [], + { + "since": ["1.0.0"] + } + ), + "declare const b: 1", + { + "column": 11, + "line": 7 + }, + false + ) + ]) + ) + })) + + it.effect("should handle `export * from ...`", () => + Effect.gen(function*() { + project.createSourceFile("example.ts", `export const a = 1`, { overwrite: true }) + + const sourceFile = project.createSourceFile( + "export-all.ts", + ` + /** + * @since 1.0.0 + */ + export * from './example' + ` + ) + + const actual = yield* Effect.exit(Parser.parseExports.pipe( + Effect.provideService(Parser.Source, makeSource(sourceFile)), + Effect.provideService(Configuration.Configuration, defaultConfig) + )) + + assert.deepStrictEqual( + actual, + Exit.succeed([ + new Domain.Export( + "'./example'", + new Domain.Doc( + "Re-exports all named exports from the './example' module.", + ["1.0.0"], + [], + [], + [], + [], + [], + { + "since": ["1.0.0"] + } + ), + "export * from './example'", + { + "column": 10, + "line": 5 + }, + true + ) + ]) + ) + })) + + it.effect("should handle `export * as ... from ...`", () => + Effect.gen(function*() { + project.createSourceFile("example.ts", `export const a = 1`, { overwrite: true }) + + const sourceFile = project.createSourceFile( + "export-all-namespace.ts", + ` + /** + * @since 1.0.0 + */ + export * as example from './example' + ` + ) + + const actual = yield* Effect.exit(Parser.parseExports.pipe( + Effect.provideService(Parser.Source, makeSource(sourceFile)), + Effect.provideService(Configuration.Configuration, defaultConfig) + )) + + assert.deepStrictEqual( + actual, + Exit.succeed([ + new Domain.Export( + "example", + new Domain.Doc( + "Re-exports all named exports from the './example' module as `example`.", + ["1.0.0"], + [], + [], + [], + [], + [], + { + "since": ["1.0.0"] + } + ), + "export * as example from './example'", + { + "column": 11, + "line": 5 + }, + true + ) + ]) + ) + })) + }) + + describe("parseInterfaces", () => { + it.effect("should return no interfaces if the file is empty", () => + Effect.gen(function*() { + yield* expectMarkdown( + Parser.parseInterfaces, + "", + "" + ) + })) + + it.effect("should return no interfaces if there are no exported interfaces", () => + Effect.gen(function*() { + yield* expectMarkdown( + Parser.parseInterfaces, + "interface A {}", + "" + ) + })) + + it.effect("should return an interface", () => + Effect.gen(function*() { + yield* expectMarkdown( + Parser.parseInterfaces, + `/** + * a description... + * @since 1.0.0 + * @deprecated + */ + export interface A {}`, + `## ~~A~~ (interface) + +a description... + +**Signature** + +\`\`\`ts +export interface A {} +\`\`\` + +[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L6) + +Since v1.0.0` + ) + })) + }) + + describe("parseNamespaces", () => { + it.effect("should return no namespaces if the file is empty", () => + Effect.gen(function*() { + yield* expectMarkdown( + Parser.parseNamespaces, + "", + "" + ) + })) + + it.effect("should return no namespaces if there are no exported namespaces", () => + Effect.gen(function*() { + yield* expectMarkdown( + Parser.parseNamespaces, + "namespace A {}", + "" + ) + })) + + it.effect("should parse an empty Namespace", () => + Effect.gen(function*() { + yield* expectMarkdown( + Parser.parseNamespaces, + ` + /** + * @since 1.0.0 + */ + export namespace A {} + `, + `## A (namespace) + +[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L5) + +Since v1.0.0` + ) + })) + + describe("namespace > interfaces", () => { + it.effect("should ignore not exported interfaces", () => + Effect.gen(function*() { + yield* expectMarkdown( + Parser.parseNamespaces, + ` + /** + * @since 1.0.0 + */ + export namespace A { + interface C {} + } + `, + `## A (namespace) + +[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L5) + +Since v1.0.0` + ) + })) + + it.effect("should parse an interface", () => + Effect.gen(function*() { + yield* expectMarkdown( + Parser.parseNamespaces, + ` +/** + * @since 1.0.0 + */ +export namespace A { + /** + * @since 1.0.1 + */ + export interface B { + readonly d: boolean + } +} + `, + `## A (namespace) + +[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L5) + +Since v1.0.0 + +### B (interface) + +**Signature** + +\`\`\`ts +export interface B { + readonly d: boolean + } +\`\`\` + +[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L9) + +Since v1.0.1` + ) + })) + }) + + describe("namespace > type aliases", () => { + it.effect("should ignore not exported type aliases", () => + Effect.gen(function*() { + yield* expectMarkdown( + Parser.parseNamespaces, + ` + /** + * @since 1.0.0 + */ + export namespace A { + type C = number + } + `, + `## A (namespace) + +[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L5) + +Since v1.0.0` + ) + })) + + it.effect("should parse a type alias", () => + Effect.gen(function*() { + yield* expectMarkdown( + Parser.parseNamespaces, + ` + /** + * @since 1.0.0 + */ + export namespace A { + /** + * @since 1.0.1 + */ + export type B = string + } + `, + `## A (namespace) + +[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L5) + +Since v1.0.0 + +### B (type alias) + +**Signature** + +\`\`\`ts +type B = string +\`\`\` + +[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L9) + +Since v1.0.1` + ) + })) + }) + + describe("namespace > nested namespaces", () => { + it.effect("should ignore not exported namespaces", () => + Effect.gen(function*() { + yield* expectMarkdown( + Parser.parseNamespaces, + ` + /** + * @since 1.0.0 + */ + export namespace A { + namespace B {} + } + `, + `## A (namespace) + +[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L5) + +Since v1.0.0` + ) + })) + + it.effect("should parse a namespace", () => + Effect.gen(function*() { + yield* expectMarkdown( + Parser.parseNamespaces, + ` + /** + * @since 1.0.0 + */ + export namespace A { + /** + * @since 1.0.1 + */ + export namespace B { + /** + * @since 1.0.2 + */ + export type C = string + } + } + `, + `## A (namespace) + +[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L5) + +Since v1.0.0 + +### B (namespace) + +[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L9) + +Since v1.0.1 + +#### C (type alias) + +**Signature** + +\`\`\`ts +type C = string +\`\`\` + +[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L13) + +Since v1.0.2` + ) + })) + }) + }) + + describe("parseClasses", () => { + it.effect("should ignore `@internal` classes", () => + Effect.gen(function*() { + yield* expectMarkdown( + Parser.parseClasses, + `/** @internal */export class MyClass {}`, + "" + ) + })) + + it.effect("should ignore `@ignore` classes", () => + Effect.gen(function*() { + yield* expectMarkdown( + Parser.parseClasses, + ` + /** @ignore */ + export class MyClass {} + `, + "" + ) + })) + + it.effect("should ignore not exported classes", () => + Effect.gen(function*() { + yield* expectMarkdown( + Parser.parseClasses, + ` + class MyClass {} + `, + "" + ) + })) + + it.effect("should skip ignored properties", () => + Effect.gen(function*() { + yield* expectMarkdown( + Parser.parseClasses, + `/** + * @since 1.0.0 + */ + export class MyClass { + /** + * @ignore + */ + readonly _A!: A + }`, + `## MyClass (class) + +**Signature** + +\`\`\`ts +declare class MyClass +\`\`\` + +[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L4) + +Since v1.0.0` + ) + })) + + it.effect("should skip the constructor body", () => + Effect.gen(function*() { + yield* expectMarkdown( + Parser.parseClasses, + `/** + * description + * @since 1.0.0 + */ + export class C { constructor() {} }`, + `## C (class) + +description + +**Signature** + +\`\`\`ts +declare class C { constructor() } +\`\`\` + +[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L5) + +Since v1.0.0` + ) + })) + + it("should get a constructor declaration signature", () => { + const sourceFile = project.createSourceFile( + `test-${testCounter++}.ts`, + ` + /** + * @since 1.0.0 + */ + declare class A { + constructor() + } + ` + ) + + const constructorDeclaration = sourceFile + .getClass("A")! + .getConstructors()[0] + + assert.deepStrictEqual( + Parser.getConstructorDeclarationSignature(constructorDeclaration), + "constructor()" + ) + }) + + it.effect("should handle non-readonly properties", () => + Effect.gen(function*() { + yield* expectMarkdown( + Parser.parseClasses, + `/** + * description + * @since 1.0.0 + */ + export class C { + /** + * @since 1.0.0 + */ + a: string + }`, + `## C (class) + +description + +**Signature** + +\`\`\`ts +declare class C +\`\`\` + +[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L5) + +Since v1.0.0 + +### a (property) + +**Signature** + +\`\`\`ts +a: string +\`\`\` + +[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L9) + +Since v1.0.0` + ) + })) + + it.effect("should return a `Class`", () => + Effect.gen(function*() { + yield* expectMarkdown( + Parser.parseClasses, + `/** + * a class description... + * @since 1.0.0 + * @deprecated + */ + export class Test { + /** + * a property... + * @since 1.1.0 + * @deprecated + */ + readonly a: string + private readonly b: number + /** + * a static method description... + * @since 1.1.0 + * @deprecated + */ + static f(): void {} + constructor(readonly value: string) { } + /** + * a method description... + * @since 1.1.0 + * @deprecated + */ + g(a: number, b: number): { [key: string]: number } { + return { a, b } + } + }`, + `## ~~Test~~ (class) + +a class description... + +**Signature** + +\`\`\`ts +declare class Test { constructor(readonly value: string) } +\`\`\` + +[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L6) + +Since v1.0.0 + +### ~~f~~ (static method) + +a static method description... + +**Signature** + +\`\`\`ts +declare const f: () => void +\`\`\` + +[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L19) + +Since v1.1.0 + +### ~~g~~ (method) + +a method description... + +**Signature** + +\`\`\`ts +declare const g: (a: number, b: number) => { [key: string]: number; } +\`\`\` + +[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L26) + +Since v1.1.0 + +### ~~a~~ (property) + +a property... + +**Signature** + +\`\`\`ts +readonly a: string +\`\`\` + +[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L12) + +Since v1.1.0` + ) + })) + + it.effect("should handle method overloadings", () => + Effect.gen(function*() { + yield* expectMarkdown( + Parser.parseClasses, + `/** + * a class description... + * @since 1.0.0 + * @deprecated + */ + export class Test { + /** + * a static method description... + * @since 1.1.0 + * @deprecated + */ + static f(x: number): number + static f(x: string): string + static f(x: any): any {} + constructor(readonly value: A) { } + /** + * a method description... + * @since 1.1.0 + * @deprecated + */ + map(f: (a: number) => number): Test + map(f: (a: string) => string): Test + map(f: (a: any) => any): any { + return new Test(f(this.value)) + } + }`, + `## ~~Test~~ (class) + +a class description... + +**Signature** + +\`\`\`ts +declare class Test { constructor(readonly value: A) } +\`\`\` + +[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L6) + +Since v1.0.0 + +### ~~f~~ (static method) + +a static method description... + +**Signature** + +\`\`\`ts +declare const f: { (x: number): number; (x: string): string; } +\`\`\` + +[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L14) + +Since v1.1.0 + +### ~~map~~ (method) + +a method description... + +**Signature** + +\`\`\`ts +declare const map: { (f: (a: number) => number): Test; (f: (a: string) => string): Test; } +\`\`\` + +[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L23) + +Since v1.1.0` + ) + })) + + it.effect("should ignore internal/ignored methods (#42)", () => + Effect.gen(function*() { + yield* expectMarkdown( + Parser.parseClasses, + `/** + * a class description... + * @since 1.0.0 + */ + export class Test { + /** + * @since 0.0.1 + * @internal + **/ + private foo(): void {} + /** + * @since 0.0.1 + * @ignore + **/ + private bar(): void {} + }`, + `## Test (class) + +a class description... + +**Signature** + +\`\`\`ts +declare class Test +\`\`\` + +[Source](https://github.com/effect-ts/docgen/blob/main/src/test.ts#L5) + +Since v1.0.0` + ) + })) + }) + + describe("parseFile", () => { + it.effect("should not parse a non-existent file", () => + Effect.gen(function*() { + const file = new Domain.File("non-existent.ts", "") + const project = new ast.Project({ useInMemoryFileSystem: true }) + + assert.deepStrictEqual( + yield* Effect.exit( + Parser.parseFile(project)(file).pipe( + Effect.provideService(Configuration.Configuration, defaultConfig), + Effect.provide(Path.layer) + ) + ), + Exit.fail(["Unable to locate file: non-existent.ts"]) + ) + })) + }) + + describe("utils", () => { + it("parseComment", () => { + assert.deepStrictEqual(Parser.parseComment(""), { + description: undefined, + tags: {} + }) + + assert.deepStrictEqual(Parser.parseComment("/** description */"), { + description: "description", + tags: {} + }) + + assert.deepStrictEqual( + Parser.parseComment("/** description\n * @since 1.0.0\n */"), + { + description: "description", + tags: { + since: ["1.0.0"] + } + } + ) + + assert.deepStrictEqual( + Parser.parseComment("/** description\n * @deprecated\n */"), + { + description: "description", + tags: { + deprecated: [""] + } + } + ) + + assert.deepStrictEqual( + Parser.parseComment("/** description\n * @category instance\n */"), + { + description: "description", + tags: { + category: ["instance"] + } + } + ) + }) + }) +}) diff --git a/repos/effect/packages/tools/docgen/test/fixtures/invalid-json.txt b/repos/effect/packages/tools/docgen/test/fixtures/invalid-json.txt new file mode 100644 index 0000000000..8e93528306 --- /dev/null +++ b/repos/effect/packages/tools/docgen/test/fixtures/invalid-json.txt @@ -0,0 +1 @@ +{] diff --git a/repos/effect/packages/tools/docgen/tsconfig.json b/repos/effect/packages/tools/docgen/tsconfig.json new file mode 100644 index 0000000000..b512ffd916 --- /dev/null +++ b/repos/effect/packages/tools/docgen/tsconfig.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "../../../tsconfig.base.json", + "include": ["src"], + "compilerOptions": { + "types": ["node"] + } +} diff --git a/repos/effect/packages/tools/doctest/CHANGELOG.md b/repos/effect/packages/tools/doctest/CHANGELOG.md new file mode 100644 index 0000000000..f8ef8243ba --- /dev/null +++ b/repos/effect/packages/tools/doctest/CHANGELOG.md @@ -0,0 +1,39 @@ +# @effect/doctest + +## 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 + +- [#6794](https://github.com/Effect-TS/effect/pull/6794) [`8e89cc6`](https://github.com/Effect-TS/effect/commit/8e89cc60a64e93a599af265dd75db3c7c511d7f8) Thanks @fubhy! - Add convention-based `// =>` assertions that compare documentation example values using Effect equality. + +- 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 diff --git a/repos/effect/packages/tools/doctest/README.md b/repos/effect/packages/tools/doctest/README.md new file mode 100644 index 0000000000..e4d75e3383 --- /dev/null +++ b/repos/effect/packages/tools/doctest/README.md @@ -0,0 +1,71 @@ +# `@effect/doctest` + +`@effect/doctest` extracts marked TypeScript examples from JSDoc comments and Markdown files, then runs each example as an isolated Vitest module. + +Mark runnable fences with `import.meta.vitest`: + +````ts +/** + * ```ts import.meta.vitest name="adds two numbers" + * 1 + 1 // => 2 + * ``` + */ +export const value = 1 +```` + +The optional `name="..."` metadata labels the test without appearing in the example body. Unnamed examples use the opening fence line, such as `line 12`; Vitest displays the containing file alongside it. + +## Inline assertions + +Add a trailing `// =>` comment to assert the value of an expression: + +````ts +/** + * ```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() + * ``` + */ +export const value = 1 +```` + +The expected value is a TypeScript expression evaluated in the same lexical scope. Values are compared with Effect's `Equal.equals` semantics, so the convention supports primitives, arrays, plain objects, and Effect data types such as `Option`, `Result`, `Exit`, and `HashMap` without converting them to console output. + +Prefer asserting the API call directly instead of introducing a binding used only by the assertion. Keep bindings for reuse or meaningful multi-step setup, with a blank line before a later assertion block. Keep the call and assertion on one line when it fits within 120 characters, and format expected arrays densely, for example `[1, 2]`, `[[1], [2]]`, and `Option.some([1, 2])`. Preserve runnable markers on type-level examples without adding tautological runtime assertions. + +An assertion may also trail a single initialized `const` declaration with an identifier binding. The initializer is evaluated once and the binding remains available to subsequent code: + +````ts +/** + * ```ts import.meta.vitest + * import { Effect, Option } from "effect" + * + * const result = await Effect.runPromise(Effect.succeed(Option.some(1))) // => Option.some(1) + * Option.isSome(result) // => true + * ``` + */ +export const value = 1 +```` + +Markers must trail a complete expression statement or supported `const` declaration on the same line. Standalone markers, destructuring declarations, multiple declarations, and `let` or `var` declarations are not supported. The transform does not implicitly await promises, run Effects, or consume iterators; write those operations explicitly. Ordinary comments are ignored. Await asynchronous work so all assertions and cleanup occur before the snippet module finishes evaluating. + +Regular tests can use `include` in the same project. Documentation sources use `includeSource`, which lets Vitest discard files without the marker before collection. The plugin resolves imports relative to each example's original TypeScript or Markdown file: + +```ts +import * as Doctest from "@effect/doctest/Plugin" +import { defineConfig } from "vitest/config" + +export default defineConfig({ + plugins: [Doctest.plugin()], + test: { + include: ["test/**/*.test.ts"], + includeSource: ["src/**/*.ts", "docs/**/*.md"] + } +}) +``` + +Source files selected by `includeSource` are collected through generated doctest collectors and are not executed. Native in-source tests using `import.meta.vitest` are therefore not supported by this plugin. Regular test files included through `test.include` continue to run normally. + +The plugin configures `@effect/doctest/Runner` when no test runner is specified. If `test.runner` is already configured, the plugin leaves it unchanged; that runner is then responsible for integrating doctest collection when required. diff --git a/repos/effect/packages/tools/doctest/package.json b/repos/effect/packages/tools/doctest/package.json new file mode 100644 index 0000000000..5295156cfd --- /dev/null +++ b/repos/effect/packages/tools/doctest/package.json @@ -0,0 +1,63 @@ +{ + "name": "@effect/doctest", + "version": "4.0.0-beta.107", + "type": "module", + "license": "MIT", + "description": "Runs TypeScript documentation examples as Vitest tests", + "homepage": "https://effect.website", + "repository": { + "type": "git", + "url": "https://github.com/Effect-TS/effect.git", + "directory": "packages/tools/doctest" + }, + "sideEffects": [], + "exports": { + "./package.json": "./package.json", + ".": "./src/index.ts", + "./*": "./src/*.ts", + "./index": null, + "./internal/*": 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", + "./index": null, + "./internal/*": 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" + }, + "dependencies": { + "rolldown": "^1.1.5" + }, + "peerDependencies": { + "effect": "workspace:^", + "vite": ">=8.1.5 <9.0.0", + "vitest": ">=4.1.10 <5.0.0" + }, + "devDependencies": { + "@effect/vitest": "workspace:^", + "@types/node": "^26.1.2", + "effect": "workspace:^", + "vite": "^8.1.5", + "vitest": "^4.1.10" + } +} diff --git a/repos/effect/packages/tools/doctest/src/Plugin.ts b/repos/effect/packages/tools/doctest/src/Plugin.ts new file mode 100644 index 0000000000..d0735859e0 --- /dev/null +++ b/repos/effect/packages/tools/doctest/src/Plugin.ts @@ -0,0 +1,120 @@ +/** + * @since 4.0.0 + */ + +import { normalizePath, type Plugin } from "vite" +import type { TestUserConfig } from "vitest/config" +import * as Protocol from "./Protocol.ts" +import * as Source from "./Source.ts" +import { transform } from "./Transform.ts" + +const runner = "@effect/doctest/Runner" + +const collectorModule = ( + file: string, + snippets: ReadonlyArray, + version?: string | undefined +): string => { + const tests = snippets.map((snippet, index) => { + const id = JSON.stringify(Protocol.snippetId(file, index, version)) + const label = JSON.stringify(snippet.name ?? `line ${snippet.line}`) + return `test(${label}, () => import(${id}))` + }) + + return `import { test } from "@effect/doctest/Runtime"\n\n${tests.join("\n\n")}\n` +} + +/** + * Creates a Vite plugin that transforms marked documentation snippets into Vitest tests. + * + * **Details** + * + * Use Vitest's `includeSource` option to discover files containing `import.meta.vitest`. The plugin collects marked files through a collector module without executing the source module. Every snippet executes in its own module, with imports resolved relative to the original source file. Native in-source tests are not supported. + * + * @category testing + * @since 4.0.0 + */ +export const plugin = (): Plugin => { + const store = new Map>() + const cache = new Map> + }>() + + const loadExamples = ( + file: string, + version?: string | undefined + ): Promise> => { + const cached = cache.get(file) + if (cached !== undefined && cached.version === version) { + return cached.value + } + + const loaded = Source.extractFile(file).then((snippets) => { + store.set(file, snippets) + return snippets + }) + + cache.set(file, { version, value: loaded }) + return loaded + } + + return { + name: "effect-doctest", + enforce: "pre", + perEnvironmentWatchChangeDuringDev: true, + config(config) { + if (config.test?.runner !== undefined) { + return undefined + } + + return { test: { runner } satisfies TestUserConfig } + }, + resolveId(source, importer, options) { + const collector = Protocol.request(Protocol.collectorPrefix, source) + if (collector !== undefined) { + return Protocol.resolvedId("collector", collector) + } + + const snippet = Protocol.request(Protocol.snippetPrefix, source) + if (snippet !== undefined && store.has(snippet.file)) { + return Protocol.resolvedId("snippet", snippet) + } + + const parent = importer === undefined ? undefined : Protocol.resolvedRequest(importer) + if (parent?.kind !== "snippet") { + return null + } + + return this.resolve(source, parent.file, { ...options, skipSelf: true }) + }, + load(id) { + const loaded = Protocol.resolvedRequest(id) + if (loaded === undefined) { + return null + } + + this.addWatchFile(loaded.file) + return loadExamples(loaded.file, loaded.version).then((snippets) => { + if (loaded.kind === "collector") { + return collectorModule(loaded.file, snippets, loaded.version) + } + + const snippet = loaded.index === undefined ? undefined : snippets[loaded.index] + if (snippet === undefined) { + throw new Error(`Unknown documentation snippet module '${id}'`) + } + + return transform(snippet.source, loaded.file, snippet.line) + }) + }, + watchChange(id) { + const normalized = normalizePath(id) + for (const file of store.keys()) { + if (normalizePath(file) === normalized) { + cache.delete(file) + } + } + } + } +} diff --git a/repos/effect/packages/tools/doctest/src/Protocol.ts b/repos/effect/packages/tools/doctest/src/Protocol.ts new file mode 100644 index 0000000000..6adb1b95cb --- /dev/null +++ b/repos/effect/packages/tools/doctest/src/Protocol.ts @@ -0,0 +1,145 @@ +/** + * @since 4.0.0 + */ + +/** + * Prefix for virtual doctest collector module requests. + * + * @category constants + * @since 4.0.0 + */ +export const collectorPrefix = "virtual:effect-doctest/collector?" + +/** + * Prefix for virtual doctest snippet module requests. + * + * @category constants + * @since 4.0.0 + */ +export const snippetPrefix = "virtual:effect-doctest/snippet?" + +/** + * Query parameter used to identify resolved doctest modules. + * + * @category constants + * @since 4.0.0 + */ +export const resolvedMarker = "effect-doctest" + +/** + * Describes a virtual doctest module request. + * + * @category models + * @since 4.0.0 + */ +export interface Request { + readonly file: string + readonly index?: number | undefined + readonly version?: string | undefined +} + +/** + * Parses a virtual doctest request with the specified prefix. + * + * @category protocols + * @since 4.0.0 + */ +export const request = (prefix: string, id: string): Request | undefined => { + if (!id.startsWith(prefix)) return undefined + const parameters = new URLSearchParams(id.slice(prefix.length)) + const file = parameters.get("file") + const index = parameters.get("index") + const version = parameters.get("version") + if (file === null || (index !== null && !/^\d+$/.test(index))) { + return undefined + } + + return { + file, + index: index === null ? undefined : Number(index), + version: version === null ? undefined : version + } +} + +/** + * Parses the identifier of a resolved doctest module. + * + * @category protocols + * @since 4.0.0 + */ +export const resolvedRequest = ( + id: string +): (Request & { readonly kind: "collector" | "snippet" }) | undefined => { + const query = id.indexOf("?") + if (query === -1) { + return undefined + } + + const parameters = new URLSearchParams(id.slice(query + 1)) + const kind = parameters.get(resolvedMarker) + if (kind !== "collector" && kind !== "snippet") { + return undefined + } + + const index = parameters.get("index") + const version = parameters.get("version") + if (kind === "snippet" && (index === null || !/^\d+$/.test(index))) { + return undefined + } + + return { + file: id.slice(0, query), + index: index === null ? undefined : Number(index), + version: version === null ? undefined : version, + kind + } +} + +/** + * Creates the identifier of a resolved doctest module. + * + * @category protocols + * @since 4.0.0 + */ +export const resolvedId = (kind: "collector" | "snippet", value: Request): string => { + const parameters = new URLSearchParams({ [resolvedMarker]: kind }) + if (value.index !== undefined) { + parameters.set("index", String(value.index)) + } + + if (value.version !== undefined) { + parameters.set("version", value.version) + } + + return `${value.file}?${parameters}` +} + +/** + * Creates a virtual doctest collector module identifier. + * + * @category protocols + * @since 4.0.0 + */ +export const collectorId = (file: string, version?: string | undefined): string => { + const parameters = new URLSearchParams({ file }) + if (version !== undefined) { + parameters.set("version", version) + } + + return `${collectorPrefix}${parameters}` +} + +/** + * Creates a virtual doctest snippet module identifier. + * + * @category protocols + * @since 4.0.0 + */ +export const snippetId = (file: string, index: number, version?: string | undefined): string => { + const parameters = new URLSearchParams({ file, index: String(index) }) + if (version !== undefined) { + parameters.set("version", version) + } + + return `${snippetPrefix}${parameters}` +} diff --git a/repos/effect/packages/tools/doctest/src/Runner.ts b/repos/effect/packages/tools/doctest/src/Runner.ts new file mode 100644 index 0000000000..499dac99f0 --- /dev/null +++ b/repos/effect/packages/tools/doctest/src/Runner.ts @@ -0,0 +1,41 @@ +/** + * @since 4.0.0 + */ + +import { createHash } from "node:crypto" +import { readFile } from "node:fs/promises" +import { TestRunner } from "vitest" +import * as Protocol from "./Protocol.ts" + +/** + * Wraps a Vitest runner so marked documentation files use doctest collectors. + * + * @category testing + * @since 4.0.0 + */ +export const wrap = (Base: typeof TestRunner): typeof TestRunner => { + return class DoctestRunner extends Base { + override importFile(filepath: string, source: "collect" | "setup"): unknown { + if (source !== "collect") { + return super.importFile(filepath, source) + } + + return readFile(filepath).then((contents) => { + if (!contents.includes("import.meta.vitest")) { + return super.importFile(filepath, source) + } + + const version = createHash("sha256").update(contents).digest("hex").slice(0, 16) + return super.importFile(Protocol.collectorId(filepath, version), source) + }) + } + } +} + +/** + * Vitest runner that routes marked documentation files to doctest collectors. + * + * @category testing + * @since 4.0.0 + */ +export default wrap(TestRunner) diff --git a/repos/effect/packages/tools/doctest/src/Runtime.ts b/repos/effect/packages/tools/doctest/src/Runtime.ts new file mode 100644 index 0000000000..5ad8c890a1 --- /dev/null +++ b/repos/effect/packages/tools/doctest/src/Runtime.ts @@ -0,0 +1,33 @@ +/** + * @since 4.0.0 + */ + +import * as Equal from "effect/Equal" +import * as assert from "node:assert" +import { test as vitest } from "vitest" + +/** + * Asserts that two values are equal using Effect's equality semantics. + * + * @category testing + * @since 4.0.0 + */ +export const assertEquals = (actual: A, expected: A): void => { + if (!Equal.equals(actual, expected)) { + assert.deepStrictEqual(actual, expected) + assert.fail("Expected values to be Equal.equals") + } +} + +/** + * Registers a documentation snippet as a test. + * + * @category testing + * @since 4.0.0 + */ +export const test = ( + name: string, + run: () => unknown | PromiseLike +): void => { + vitest(name, run) +} diff --git a/repos/effect/packages/tools/doctest/src/Source.ts b/repos/effect/packages/tools/doctest/src/Source.ts new file mode 100644 index 0000000000..2c8e2b33f8 --- /dev/null +++ b/repos/effect/packages/tools/doctest/src/Source.ts @@ -0,0 +1,101 @@ +/** + * @since 4.0.0 + */ + +import { readFile } from "node:fs/promises" + +/** + * Represents an executable TypeScript code snippet. + * + * @category models + * @since 4.0.0 + */ +export interface Snippet { + readonly source: string + readonly line: number + readonly name: string | undefined +} + +/** + * Identifies how documentation is embedded in the source text. + * + * @category models + * @since 4.0.0 + */ +export type SourceFormat = "jsdoc" | "markdown" + +const jsdocPattern = /\/\*\*[\s\S]*?\*\//g +const fencePattern = /(?:```|~~~)(.*?)\n([\s\S]*?)(?:(?:```|~~~)|$)/g +const runnableMarker = "import.meta.vitest" +const namePattern = /(?:^|\s)name=(?:"([^"]+)"|'([^']+)'|([^\s]+))/ + +const lineNumberAt = (source: string): (offset: number) => number => { + const starts = [0] + for (let index = 0; index < source.length; index++) { + if (source.charCodeAt(index) === 10) starts.push(index + 1) + } + + return (offset) => { + let low = 0 + let high = starts.length + while (low < high) { + const middle = (low + high) >>> 1 + if (starts[middle] <= offset) low = middle + 1 + else high = middle + } + return low + } +} + +const snippets = ( + text: string, + offset: number, + lineAt: (offset: number) => number, + jsdoc = false +): ReadonlyArray => { + return Array.from(text.matchAll(fencePattern)).flatMap((match) => { + const metadata = match[1].toLowerCase() + if ((!metadata.startsWith("ts") && !metadata.startsWith("typescript")) || !metadata.includes(runnableMarker)) { + return [] + } + + const name = namePattern.exec(match[1]) + const source = jsdoc ? match[2].replace(/^[ \t]*\* ?/gm, "").trim() : match[2].trim() + + return [{ + source, + line: lineAt(offset + (match.index ?? 0)), + name: name?.[1] ?? name?.[2] ?? name?.[3] + }] + }) +} + +/** + * Extracts marked TypeScript code snippets from documentation text. + * + * **Details** + * + * Extraction uses an ordered text scan and does not parse the containing source file. + * + * @category extraction + * @since 4.0.0 + */ +export const extract = (source: string, format: SourceFormat = "jsdoc"): ReadonlyArray => { + if (!source.includes(runnableMarker)) { + return [] + } + + const lineAt = lineNumberAt(source) + return format === "markdown" + ? snippets(source, 0, lineAt) + : Array.from(source.matchAll(jsdocPattern)).flatMap((match) => snippets(match[0], match.index ?? 0, lineAt, true)) +} + +/** + * Reads a file and extracts its marked code snippets. + * + * @category extraction + * @since 4.0.0 + */ +export const extractFile = (file: string): Promise> => + readFile(file, "utf8").then((source) => extract(source, file.endsWith(".md") ? "markdown" : "jsdoc")) diff --git a/repos/effect/packages/tools/doctest/src/Transform.ts b/repos/effect/packages/tools/doctest/src/Transform.ts new file mode 100644 index 0000000000..49c78a0c8f --- /dev/null +++ b/repos/effect/packages/tools/doctest/src/Transform.ts @@ -0,0 +1,208 @@ +/** + * @since 4.0.0 + */ + +import { RolldownMagicString } from "rolldown" +import { type ESTree, parseSync } from "rolldown/utils" + +const markerPattern = /^ => (.+)$/ +const helper = "__effect_doctest_assert_" +const statementTypes = new Set([ + "ClassDeclaration", + "ExportAllDeclaration", + "ExportDefaultDeclaration", + "ExportNamedDeclaration", + "FunctionDeclaration", + "ImportDeclaration", + "TSDeclareFunction", + "TSEnumDeclaration", + "TSExportAssignment", + "TSImportEqualsDeclaration", + "TSInterfaceDeclaration", + "TSModuleDeclaration", + "TSNamespaceExportDeclaration", + "TSTypeAliasDeclaration", + "VariableDeclaration" +]) + +interface Node { + readonly type: string + readonly start: number + readonly end: number + readonly [key: string]: unknown +} + +interface Candidate { + readonly node: Node + readonly parent: Node | undefined + readonly depth: number +} + +const isNode = (value: unknown): value is Node => + typeof value === "object" && value !== null && + "type" in value && typeof value.type === "string" && + "start" in value && typeof value.start === "number" && + "end" in value && typeof value.end === "number" + +const isStatement = (node: Node): boolean => node.type.endsWith("Statement") || statementTypes.has(node.type) + +const collect = (program: ESTree.Program): { readonly candidates: Array; readonly names: Set } => { + const candidates: Array = [] + const names = new Set() + + const visit = (node: Node, parent: Node | undefined, depth: number): void => { + if (isStatement(node)) candidates.push({ node, parent, depth }) + if (node.type === "Identifier" && typeof node.name === "string") names.add(node.name) + + for (const value of Object.values(node)) { + if (isNode(value)) { + visit(value, node, depth + 1) + } else if (Array.isArray(value)) { + for (const child of value) { + if (isNode(child)) visit(child, node, depth + 1) + } + } + } + } + + visit(program as unknown as Node, undefined, 0) + return { candidates, names } +} + +const location = (source: string, offset: number, file: string, line: number): string => { + let localLine = 1 + let column = 1 + for (let index = 0; index < offset; index++) { + if (source.charCodeAt(index) === 10) { + localLine++ + column = 1 + } else { + column++ + } + } + return `${file}:${line + localLine}:${column}` +} + +const fail = (source: string, offset: number, file: string, line: number, message: string): never => { + throw new Error(`${location(source, offset, file, line)} ${message}`) +} + +const isUnbracedControlFlow = (parent: Node | undefined): boolean => + parent !== undefined && new Set([ + "DoWhileStatement", + "ForInStatement", + "ForOfStatement", + "ForStatement", + "IfStatement", + "LabeledStatement", + "WhileStatement", + "WithStatement" + ]).has(parent.type) + +const assertionTarget = ( + source: string, + candidates: ReadonlyArray, + comment: { readonly start: number }, + file: string, + line: number +): Candidate => { + const target = candidates + .filter(({ node }) => node.end <= comment.start && /^[ \t]*$/.test(source.slice(node.end, comment.start))) + .sort((left, right) => right.node.end - left.node.end || right.depth - left.depth)[0] + + if (target === undefined) { + return fail(source, comment.start, file, line, "doctest assertion must trail a complete statement on the same line") + } + if (isUnbracedControlFlow(target.parent)) { + return fail(source, comment.start, file, line, "doctest assertions in control flow require an explicit block") + } + return target +} + +const parseExpected = (expected: string, source: string, offset: number, file: string, line: number): void => { + const parsed = parseSync(`${file}?doctest-expected`, `const __expected = (${expected})`, { lang: "ts" }) + const error = parsed.errors[0] + if (error !== undefined) { + fail(source, offset, file, line, `invalid doctest expected expression: ${error.message}`) + } +} + +/** + * Transforms trailing doctest assertion comments into executable assertions. + * + * **Details** + * + * An assertion may trail an expression statement or a single initialized + * `const` identifier. Expected values are TypeScript expressions evaluated in + * the same lexical scope. + * + * @category testing + * @since 4.0.0 + */ +export const transform = (source: string, file: string, line: number): string => { + if (!source.includes("// =>")) return source + + const parsed = parseSync(file, source, { lang: "ts" }) + const parseError = parsed.errors[0] + if (parseError !== undefined) { + fail(source, parseError.labels[0]?.start ?? 0, file, line, `invalid doctest source: ${parseError.message}`) + } + + const markers = parsed.comments.flatMap((comment) => { + if (comment.type !== "Line") return [] + const match = markerPattern.exec(comment.value) + return match === null ? [] : [{ comment, expected: match[1].trim() }] + }) + if (markers.length === 0) return source + + const { candidates, names } = collect(parsed.program) + let index = 0 + while (names.has(`${helper}${index}`)) index++ + const alias = `${helper}${index}` + const output = new RolldownMagicString(source) + + for (const { comment, expected } of markers) { + parseExpected(expected, source, comment.start, file, line) + const { node, parent } = assertionTarget(source, candidates, comment, file, line) + + if (node.type === "ExpressionStatement") { + if (node.directive !== null) { + fail(source, comment.start, file, line, "doctest assertions cannot target directives") + } + const expression = isNode(node.expression) + ? node.expression + : fail(source, comment.start, file, line, "doctest assertion has no expression") + output.overwrite( + expression.start, + comment.end, + `${alias}(${source.slice(expression.start, expression.end)}, ${expected})` + ) + continue + } + + if (node.type === "VariableDeclaration" && parent?.type !== "ExportNamedDeclaration") { + const declarations = node.declarations + const declaration = Array.isArray(declarations) && declarations.length === 1 ? declarations[0] : undefined + const id = isNode(declaration) && isNode(declaration.id) ? declaration.id : undefined + if ( + node.kind === "const" && node.declare === false && isNode(declaration?.init) && + id?.type === "Identifier" && typeof id.name === "string" + ) { + const indentation = source.slice(source.lastIndexOf("\n", node.start - 1) + 1, node.start) + output.overwrite(node.end, comment.end, `\n${indentation}${alias}(${id.name}, ${expected})`) + continue + } + } + + fail( + source, + comment.start, + file, + line, + "doctest assertions can only target expression statements or a single initialized const identifier" + ) + } + + output.append(`\n\nimport { assertEquals as ${alias} } from "@effect/doctest/Runtime"`) + return output.toString() +} diff --git a/repos/effect/packages/tools/doctest/src/index.ts b/repos/effect/packages/tools/doctest/src/index.ts new file mode 100644 index 0000000000..b29fe396d4 --- /dev/null +++ b/repos/effect/packages/tools/doctest/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 Plugin from "./Plugin.ts" + +/** + * @since 4.0.0 + */ +export * as Protocol from "./Protocol.ts" + +/** + * @since 4.0.0 + */ +export * as Runner from "./Runner.ts" + +/** + * @since 4.0.0 + */ +export * as Runtime from "./Runtime.ts" + +/** + * @since 4.0.0 + */ +export * as Source from "./Source.ts" + +/** + * @since 4.0.0 + */ +export * as Transform from "./Transform.ts" diff --git a/repos/effect/packages/tools/doctest/test/Plugin.test.ts b/repos/effect/packages/tools/doctest/test/Plugin.test.ts new file mode 100644 index 0000000000..f4fe70e46f --- /dev/null +++ b/repos/effect/packages/tools/doctest/test/Plugin.test.ts @@ -0,0 +1,67 @@ +import * as Doctest from "@effect/doctest/Plugin" +import * as Protocol from "@effect/doctest/Protocol" +import { assert, describe, it } from "@effect/vitest" +import { mkdtempSync, rmSync, writeFileSync } from "node:fs" +import { tmpdir } from "node:os" +import { join } from "node:path" + +describe("Plugin", () => { + it("configures the doctest runner by default", () => { + const config = Doctest.plugin().config + if (typeof config !== "function") return assert.fail("expected config hook") + + assert.deepStrictEqual(config.call({} as never, {}, {} as never), { + test: { runner: "@effect/doctest/Runner" } + }) + }) + + it("preserves a configured runner", () => { + const config = Doctest.plugin().config + if (typeof config !== "function") return assert.fail("expected config hook") + + assert.isUndefined(config.call({} as never, { test: { runner: "./custom-runner.ts" } }, {} as never)) + }) + + it("transforms inline assertions in snippet modules", async () => { + const root = mkdtempSync(join(tmpdir(), "effect-doctest-plugin-")) + const file = join(root, "example.ts") + writeFileSync( + file, + [ + "/**", + " * ```ts import.meta.vitest name=asserted", + " * const result = 1 // => 1", + " * ```", + " */" + ].join("\n") + ) + + try { + const plugin = Doctest.plugin() + const resolveId = plugin.resolveId + const load = plugin.load + if (typeof resolveId !== "function" || typeof load !== "function") { + return assert.fail("expected function plugin hooks") + } + const context = { addWatchFile() {} } as never + const id = Protocol.collectorId(file, "test") + const resolved = await resolveId.call(context, id, undefined, {} as never) + if (typeof resolved !== "string") return assert.fail("expected resolved collector ID") + const source = await load.call(context, resolved, {} as never) + if (typeof source !== "string") return assert.fail("expected collector source") + + assert.match(source, /import \{ test \} from "@effect\/doctest\/Runtime"/) + assert.match(source, /test\("asserted", \(\) => import\([^)]*\)\)/) + + const snippetId = Protocol.snippetId(file, 0, "test") + const resolvedSnippet = await resolveId.call(context, snippetId, undefined, {} as never) + if (typeof resolvedSnippet !== "string") return assert.fail("expected resolved snippet ID") + const snippet = await load.call(context, resolvedSnippet, {} as never) + if (typeof snippet !== "string") return assert.fail("expected snippet source") + assert.match(snippet, /const result = 1\n__effect_doctest_assert_0\(result, 1\)/) + assert.match(snippet, /assertEquals as __effect_doctest_assert_0/) + } finally { + rmSync(root, { recursive: true, force: true }) + } + }) +}) diff --git a/repos/effect/packages/tools/doctest/test/Protocol.test.ts b/repos/effect/packages/tools/doctest/test/Protocol.test.ts new file mode 100644 index 0000000000..0bd5c8ad83 --- /dev/null +++ b/repos/effect/packages/tools/doctest/test/Protocol.test.ts @@ -0,0 +1,78 @@ +import * as Protocol from "@effect/doctest/Protocol" +import { assert, describe, it } from "@effect/vitest" + +describe("Protocol", () => { + it("round-trips collector requests", () => { + const id = Protocol.collectorId("/src/a file.ts", "version-1") + + assert.deepStrictEqual(Protocol.request(Protocol.collectorPrefix, id), { + file: "/src/a file.ts", + index: undefined, + version: "version-1" + }) + }) + + it("round-trips snippet requests", () => { + const id = Protocol.snippetId("/src/example.ts", 3, "version-1") + + assert.deepStrictEqual(Protocol.request(Protocol.snippetPrefix, id), { + file: "/src/example.ts", + index: 3, + version: "version-1" + }) + }) + + it("rejects malformed virtual requests", () => { + for ( + const id of [ + "other:file", + Protocol.snippetPrefix, + `${Protocol.snippetPrefix}file=example.ts&index=-1`, + `${Protocol.snippetPrefix}file=example.ts&index=1.5` + ] + ) { + assert.isUndefined(Protocol.request(Protocol.snippetPrefix, id)) + } + }) + + it("round-trips resolved collector identifiers", () => { + const id = Protocol.resolvedId("collector", { + file: "/src/example.ts", + version: "version-1" + }) + + assert.deepStrictEqual(Protocol.resolvedRequest(id), { + file: "/src/example.ts", + index: undefined, + version: "version-1", + kind: "collector" + }) + }) + + it("round-trips resolved snippet identifiers", () => { + const id = Protocol.resolvedId("snippet", { + file: "/src/example.ts", + index: 2 + }) + + assert.deepStrictEqual(Protocol.resolvedRequest(id), { + file: "/src/example.ts", + index: 2, + version: undefined, + kind: "snippet" + }) + }) + + it("rejects malformed resolved identifiers", () => { + for ( + const id of [ + "/src/example.ts", + "/src/example.ts?effect-doctest=unknown", + "/src/example.ts?effect-doctest=snippet", + "/src/example.ts?effect-doctest=snippet&index=one" + ] + ) { + assert.isUndefined(Protocol.resolvedRequest(id)) + } + }) +}) diff --git a/repos/effect/packages/tools/doctest/test/Runtime.test.ts b/repos/effect/packages/tools/doctest/test/Runtime.test.ts new file mode 100644 index 0000000000..c2f15c16c9 --- /dev/null +++ b/repos/effect/packages/tools/doctest/test/Runtime.test.ts @@ -0,0 +1,18 @@ +import * as Runtime from "@effect/doctest/Runtime" +import { assert, describe, it } from "@effect/vitest" +import { HashMap, Option } from "effect" + +describe("Runtime", () => { + it("compares Effect values with Equal.equals", () => { + Runtime.assertEquals(Option.some(1), Option.some(1)) + Runtime.assertEquals(HashMap.make(["a", 1]), HashMap.make(["a", 1])) + }) + + it("reports unequal values", () => { + assert.throws(() => Runtime.assertEquals(Option.some(1), Option.some(2))) + }) +}) + +Runtime.test("registers documentation tests", () => { + Runtime.assertEquals(Option.none(), Option.none()) +}) diff --git a/repos/effect/packages/tools/doctest/test/Source.test.ts b/repos/effect/packages/tools/doctest/test/Source.test.ts new file mode 100644 index 0000000000..18f108a212 --- /dev/null +++ b/repos/effect/packages/tools/doctest/test/Source.test.ts @@ -0,0 +1,119 @@ +import * as Source from "@effect/doctest/Source" +import { assert, describe, it } from "@effect/vitest" + +describe("Source", () => { + describe("JSDoc", () => { + it("extracts marked TypeScript fences in source order", () => { + const source = [ + "/**", + " * First example.", + " * ```ts import.meta.vitest name=first", + " * const first = 1", + " * ```", + " */", + "const first = 1", + "/**", + " * ~~~typescript import.meta.vitest name='second example'", + " * const second = 2", + " * ~~~", + " */", + "const second = 2" + ].join("\n") + + assert.deepStrictEqual(Source.extract(source), [ + { source: "const first = 1", line: 3, name: "first" }, + { source: "const second = 2", line: 9, name: "second example" } + ]) + }) + + it("ignores fences outside JSDoc comments", () => { + const source = [ + "```ts import.meta.vitest", + "const outside = true", + "```", + "/**", + " * ```ts import.meta.vitest", + " * const inside = true", + " * ```", + " */" + ].join("\n") + + assert.deepStrictEqual(Source.extract(source), [ + { source: "const inside = true", line: 5, name: undefined } + ]) + }) + + it("ignores unmarked and non-TypeScript fences", () => { + const source = [ + "/**", + " * ```ts", + " * const unmarked = true", + " * ```", + " * ```js import.meta.vitest", + " * const javascript = true", + " * ```", + " */" + ].join("\n") + + assert.isEmpty(Source.extract(source)) + }) + + it("preserves assertion comments in snippet source", () => { + const source = [ + "/**", + " * ```ts import.meta.vitest", + " * const value = 1 // => 1", + " * ```", + " */" + ].join("\n") + + assert.deepStrictEqual(Source.extract(source), [{ + source: "const value = 1 // => 1", + line: 2, + name: undefined + }]) + }) + + it("extracts inline output and ignores explanatory comments", () => { + const source = [ + "/**", + " * ```ts import.meta.vitest", + " * // Explain the output to the reader.", + " * const value = 0 // ordinary inline comment", + " * console.log(1) // > 1", + " * ```", + " */" + ].join("\n") + + assert.deepStrictEqual(Source.extract(source), [{ + source: [ + "// Explain the output to the reader.", + "const value = 0 // ordinary inline comment", + "console.log(1) // > 1" + ].join("\n"), + line: 2, + name: undefined + }]) + }) + }) + + describe("Markdown", () => { + it("extracts only marked TypeScript fences", () => { + const source = [ + "# Examples", + "", + "```ts", + "const ignored = true", + "```", + "", + "~~~typescript import.meta.vitest name=example", + "const value = 1", + "~~~" + ].join("\n") + + assert.deepStrictEqual(Source.extract(source, "markdown"), [ + { source: "const value = 1", line: 7, name: "example" } + ]) + }) + }) +}) diff --git a/repos/effect/packages/tools/doctest/test/Transform.test.ts b/repos/effect/packages/tools/doctest/test/Transform.test.ts new file mode 100644 index 0000000000..5630140904 --- /dev/null +++ b/repos/effect/packages/tools/doctest/test/Transform.test.ts @@ -0,0 +1,65 @@ +import { transform } from "@effect/doctest/Transform" +import { assert, describe, it } from "@effect/vitest" + +const imported = `import { assertEquals as __effect_doctest_assert_0 } from "@effect/doctest/Runtime"` + +describe("Transform", () => { + it("transforms expression assertions", () => { + assert.strictEqual( + transform("Option.some(1) // => Option.some(1)", "example.ts", 10), + `__effect_doctest_assert_0(Option.some(1), Option.some(1))\n\n${imported}` + ) + }) + + it("transforms const declaration assertions without evaluating the initializer twice", () => { + assert.strictEqual( + transform("const result = makeValue() // => Option.some(1)\nuse(result)", "example.ts", 10), + `const result = makeValue()\n__effect_doctest_assert_0(result, Option.some(1))\nuse(result)\n\n${imported}` + ) + }) + + it("preserves indentation in nested blocks", () => { + assert.strictEqual( + transform("if (enabled) {\n const result = makeValue() // => 1\n}", "example.ts", 10), + `if (enabled) {\n const result = makeValue()\n __effect_doctest_assert_0(result, 1)\n}\n\n${imported}` + ) + }) + + it("chooses an assertion binding that is not used by the snippet", () => { + const source = "const __effect_doctest_assert_0 = 1\nvalue // => 1" + assert.match(transform(source, "example.ts", 10), /assertEquals as __effect_doctest_assert_1/) + }) + + it("ignores ordinary and legacy output comments", () => { + const source = "console.log(value) // > value\nconst text = \"// => not an assertion\"" + assert.strictEqual(transform(source, "example.ts", 10), source) + }) + + it("rejects malformed expected expressions", () => { + assert.throws( + () => transform("value // => Option.some(", "example.ts", 10), + /example\.ts:11:7 invalid doctest expected expression/ + ) + }) + + it("rejects standalone markers", () => { + assert.throws( + () => transform("value\n// => 1", "example.ts", 10), + /doctest assertion must trail a complete statement on the same line/ + ) + }) + + it("rejects unsupported declarations", () => { + assert.throws( + () => transform("let value = 1 // => 1", "example.ts", 10), + /doctest assertions can only target expression statements or a single initialized const identifier/ + ) + }) + + it("rejects assertions in unbraced control flow", () => { + assert.throws( + () => transform("if (enabled) value // => 1", "example.ts", 10), + /doctest assertions in control flow require an explicit block/ + ) + }) +}) diff --git a/repos/effect/packages/tools/doctest/tsconfig.json b/repos/effect/packages/tools/doctest/tsconfig.json new file mode 100644 index 0000000000..b512ffd916 --- /dev/null +++ b/repos/effect/packages/tools/doctest/tsconfig.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "../../../tsconfig.base.json", + "include": ["src"], + "compilerOptions": { + "types": ["node"] + } +} diff --git a/repos/effect/packages/tools/jsdocs/docgen.json b/repos/effect/packages/tools/jsdocs/docgen.json deleted file mode 100644 index 4dd36175ad..0000000000 --- a/repos/effect/packages/tools/jsdocs/docgen.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "$schema": "../../../node_modules/@effect/docgen/schema.json", - "exclude": ["**/*.ts"] -} diff --git a/repos/effect/packages/tools/jsdocs/package.json b/repos/effect/packages/tools/jsdocs/package.json index a959cafad2..556a848381 100644 --- a/repos/effect/packages/tools/jsdocs/package.json +++ b/repos/effect/packages/tools/jsdocs/package.json @@ -8,7 +8,7 @@ "homepage": "https://effect.website", "repository": { "type": "git", - "url": "https://github.com/Effect-TS/effect-smol.git", + "url": "https://github.com/Effect-TS/effect.git", "directory": "packages/tools/jsdocs" }, "sideEffects": [], @@ -42,11 +42,8 @@ }, "scripts": { "build": "tsc -b tsconfig.json && pnpm babel", - "build:tsgo": "tsgo -b tsconfig.json && pnpm babel", "babel": "babel dist --plugins annotate-pure-calls --out-dir dist --source-maps", - "check": "tsc -b tsconfig.json", - "test": "vitest", - "coverage": "vitest --coverage" + "check": "tsc -b tsconfig.json" }, "dependencies": { "effect": "workspace:^", @@ -57,8 +54,8 @@ }, "devDependencies": { "@effect/vitest": "workspace:^", - "@types/node": "^25.7.0", + "@types/node": "^26.1.2", "typescript": "^6.0.3", - "vitest": "^4.1.4" + "vitest": "^4.1.10" } } diff --git a/repos/effect/packages/tools/jsdocs/src/Jsdocs.ts b/repos/effect/packages/tools/jsdocs/src/Jsdocs.ts index ce2686b14d..a5b9d0d0af 100644 --- a/repos/effect/packages/tools/jsdocs/src/Jsdocs.ts +++ b/repos/effect/packages/tools/jsdocs/src/Jsdocs.ts @@ -275,8 +275,20 @@ export interface ParsedJSDocImports { readonly flatNames: ReadonlyArray } +/** + * Kinds of public API records represented in a JSDoc model. + * + * @category models + * @since 4.0.0 + */ export type JSDocApiKind = "root-declaration" | "namespace" | "namespace-declaration" | "member" +/** + * Recommended import declaration and usage for an importable API. + * + * @category models + * @since 4.0.0 + */ export type JSDocApiImportGuidance = | { readonly style: "namespace-barrel" @@ -294,6 +306,12 @@ export type JSDocApiImportGuidance = readonly usage: string } +/** + * Resolution result for a link in an API's `@see` tags. + * + * @category models + * @since 4.0.0 + */ export type JSDocApiSeeLinkResolution = | { readonly _tag: "Resolved" @@ -306,15 +324,33 @@ export type JSDocApiSeeLinkResolution = readonly candidates: ReadonlyArray } +/** + * Parsed `@see` link paired with its public API resolution. + * + * @category models + * @since 4.0.0 + */ export interface JSDocApiSeeLink extends ParsedInlineLink { readonly resolution: JSDocApiSeeLinkResolution } +/** + * Parsed `@see` tag and its resolved links. + * + * @category models + * @since 4.0.0 + */ export interface JSDocApiSeeTag { readonly text: string readonly links: ReadonlyArray } +/** + * Standard tags attached to a public API record. + * + * @category models + * @since 4.0.0 + */ export interface JSDocApiTags { readonly category: string | null readonly since: string | null @@ -322,6 +358,12 @@ export interface JSDocApiTags { readonly default: string | null } +/** + * One public API record in an extracted JSDoc model. + * + * @category models + * @since 4.0.0 + */ export interface JSDocApi { readonly id: string readonly kind: JSDocApiKind @@ -791,12 +833,24 @@ function resolveJSDocImports( } } +/** + * Reads source text from an Oxlint-compatible rule context. + * + * @category getters + * @since 4.0.0 + */ export function getSourceText(context: { readonly sourceCode: { readonly text?: string; getText(node?: unknown): string } }): string { return context.sourceCode.text ?? context.sourceCode.getText() } +/** + * Resolves the working directory from an Oxlint-compatible rule context. + * + * @category getters + * @since 4.0.0 + */ export function getCwd(context: { readonly cwd?: string; getCwd?: () => string }): string { return context.cwd ?? context.getCwd?.() ?? process.cwd() } @@ -828,6 +882,12 @@ function skipDirectiveComments(source: string, end: number): number { return end } +/** + * Finds the JSDoc block immediately preceding an AST node. + * + * @category parsing + * @since 4.0.0 + */ export function findLeadingJSDoc( source: string, node: AstNode, @@ -858,7 +918,9 @@ export function findLeadingJSDoc( * * **Example** (Parsing a block) * - * ```ts + * ```ts import.meta.vitest + * import { parseJSDoc } from "@effect/jsdocs" + * * const rawBlock = [ * "/" + "**", * " * A value.", @@ -868,6 +930,8 @@ export function findLeadingJSDoc( * " *" + "/" * ].join("\n") * const result = parseJSDoc(rawBlock) + * + * result._tag // => "Success" * ``` * * @category parsing @@ -1183,7 +1247,7 @@ function parseSection(lines: Array, headingIndex: number): { while (index < lines.length) { const trimmed = lines[index].trim() if (trimmed.startsWith("```")) { - if (trimmed === "```ts") { + if (isTypeScriptFence(trimmed)) { diagnostics.push(diagnostic("loose-ts-fence", "TypeScript examples must use **Example** (Title) sections")) } inFence = !inFence @@ -1212,6 +1276,8 @@ function parseSection(lines: Array, headingIndex: number): { return { body: joinBody(bodyLines), nextIndex: index, diagnostics } } +const isTypeScriptFence = (line: string): boolean => /^```ts(?:\s.*)?$/.test(line) + function parseExample(lines: Array, headingIndex: number): { readonly example?: ParsedExample readonly nextIndex: number @@ -1232,7 +1298,7 @@ function parseExample(lines: Array, headingIndex: number): { let fenceIndex = -1 while (index < lines.length) { const trimmed = lines[index].trim() - if (trimmed === "```ts") { + if (isTypeScriptFence(trimmed)) { fenceIndex = index break } @@ -1265,7 +1331,7 @@ function parseExample(lines: Array, headingIndex: number): { index = fenceIndex + 1 const codeStart = index while (index < lines.length && lines[index].trim() !== "```") { - if (lines[index].trim() === "```ts") { + if (isTypeScriptFence(lines[index].trim())) { diagnostics.push(diagnostic("malformed-example", "Examples must contain exactly one TypeScript code fence")) } index++ @@ -1521,6 +1587,12 @@ function collectTsConfigFiles(tsconfigPath: string, seen: Set, fileNames return result } +/** + * Loads and caches the TypeScript program for a project configuration. + * + * @category constructors + * @since 4.0.0 + */ export function getProgram(tsconfigPath: string): ProgramCacheEntry { const cached = programCache.get(tsconfigPath) if (cached !== undefined) return cached @@ -1733,10 +1805,22 @@ function attachSeeLinkSymbols } +/** + * File selection and output configuration for JSDoc extraction. + * + * @category configuration + * @since 4.0.0 + */ export interface JSDocConfig { readonly tsconfig: string readonly include: ReadonlyArray @@ -1760,6 +1856,12 @@ export interface JSDocConfig { readonly output: string } +/** + * JSDoc extraction configuration with an optional working directory. + * + * @category configuration + * @since 4.0.0 + */ export interface ExtractJSDocsOptions extends JSDocConfig { readonly cwd?: string } @@ -1771,6 +1873,12 @@ function addInputFile(files: Set, filename: string) { } } +/** + * Computes the cache key for the configured JSDoc extraction inputs. + * + * @category hashing + * @since 4.0.0 + */ export function computeJSDocInputHash(options: ExtractJSDocsOptions): string { const cwd = path.resolve(options.cwd ?? process.cwd()) const hash = crypto.createHash("sha256") @@ -3250,12 +3358,24 @@ function parseSourceFileDocs( } } +/** + * Loads a JSDoc extraction configuration from JSON. + * + * @category configuration + * @since 4.0.0 + */ export function loadJSDocConfig(cwd = process.cwd(), configPath = "jsdocs.config.json"): JSDocConfig { const absolute = path.resolve(cwd, configPath) const parsed = JSON.parse(fs.readFileSync(absolute, "utf8")) as JSDocConfig return parsed } +/** + * Extracts a complete JSDoc model synchronously. + * + * @category extraction + * @since 4.0.0 + */ export function extractJSDocsSync(options: ExtractJSDocsOptions): JSDocModel { const cwd = path.resolve(options.cwd ?? process.cwd()) const tsconfigPath = path.resolve(cwd, options.tsconfig) @@ -3337,15 +3457,33 @@ export function extractJSDocsSync(options: ExtractJSDocsOptions): JSDocModel { } } +/** + * Extracts a complete JSDoc model in `Effect`. + * + * @category extraction + * @since 4.0.0 + */ export const extractJSDocs = (options: ExtractJSDocsOptions): Effect.Effect => Effect.sync(() => extractJSDocsSync(options)) +/** + * Writes a JSDoc model as formatted JSON. + * + * @category persistence + * @since 4.0.0 + */ export function writeJSDocModel(cwd: string, output: string, model: JSDocModel) { const filename = path.resolve(cwd, output) fs.mkdirSync(path.dirname(filename), { recursive: true }) fs.writeFileSync(filename, `${JSON.stringify(model, null, 2)}\n`) } +/** + * Reads and validates the outer structure of a persisted JSDoc model. + * + * @category persistence + * @since 4.0.0 + */ export function readJSDocModel(filename: string): Result { if (!fs.existsSync(filename)) return { _tag: "Failure", error: "missing" } try { @@ -3359,6 +3497,12 @@ export function readJSDocModel(filename: string): Result { } } +/** + * Computes the content hash stored for a source file in a JSDoc model. + * + * @category hashing + * @since 4.0.0 + */ export function sourceHash(source: string): string { return hashSource(source) } diff --git a/repos/effect/packages/tools/jsdocs/test/jsdocs.test.ts b/repos/effect/packages/tools/jsdocs/test/jsdocs.test.ts index dc4b0008ff..a9f86a7a17 100644 --- a/repos/effect/packages/tools/jsdocs/test/jsdocs.test.ts +++ b/repos/effect/packages/tools/jsdocs/test/jsdocs.test.ts @@ -33,6 +33,22 @@ describe("jsdocs", () => { } }) + it("accepts doctest metadata on TypeScript fences", () => { + const result = parseJSDoc(`/** + * Creates a value. + * + * **Example** (Creating a value) + * + * \`\`\`ts import.meta.vitest name="creates a value" + * const value = 1 + * \`\`\` + * + * @category constructors + * @since 1.0.0 + */`) + assert.strictEqual(result._tag, "Success") + }) + it("accepts practical When to use forms", () => { const result = parseJSDoc(`/** * Creates a value. diff --git a/repos/effect/packages/tools/jsdocs/tsconfig.json b/repos/effect/packages/tools/jsdocs/tsconfig.json index 997ff419ca..b512ffd916 100644 --- a/repos/effect/packages/tools/jsdocs/tsconfig.json +++ b/repos/effect/packages/tools/jsdocs/tsconfig.json @@ -1,5 +1,5 @@ { - "$schema": "http://json.schemastore.org/tsconfig", + "$schema": "https://json.schemastore.org/tsconfig", "extends": "../../../tsconfig.base.json", "include": ["src"], "compilerOptions": { diff --git a/repos/effect/packages/tools/jsdocs/vitest.config.ts b/repos/effect/packages/tools/jsdocs/vitest.config.ts deleted file mode 100644 index c8a52c1826..0000000000 --- a/repos/effect/packages/tools/jsdocs/vitest.config.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { mergeConfig, type ViteUserConfig } from "vitest/config" -import shared from "../../../vitest.shared.ts" - -const config: ViteUserConfig = {} - -export default mergeConfig(shared, config) diff --git a/repos/effect/packages/tools/openapi-generator/CHANGELOG.md b/repos/effect/packages/tools/openapi-generator/CHANGELOG.md index 8232de5f04..d8da5caca8 100644 --- a/repos/effect/packages/tools/openapi-generator/CHANGELOG.md +++ b/repos/effect/packages/tools/openapi-generator/CHANGELOG.md @@ -1,5 +1,161 @@ # @effect/openapi-generator +## 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 + - @effect/platform-node@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 + - @effect/platform-node@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), [`31efc5c`](https://github.com/Effect-TS/effect/commit/31efc5c4eabbe37f9ddab030ef760926e6ff3d24), [`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 + - @effect/platform-node@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), [`545d876`](https://github.com/Effect-TS/effect/commit/545d8767648cbbbf1820361662c0c1e10768db6a), [`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), [`721b9f0`](https://github.com/Effect-TS/effect/commit/721b9f0d320e50f3e2324c2cd1f43c6643af3ca0), [`01af079`](https://github.com/Effect-TS/effect/commit/01af079c189d1fc5067d3b1933b2870c4baf2693), [`32a59e8`](https://github.com/Effect-TS/effect/commit/32a59e8058b1ec9738cb083cf1cb116b393ca114)]: + - effect@4.0.0-beta.104 + - @effect/platform-node@4.0.0-beta.104 + +## 4.0.0-beta.103 + +### Patch Changes + +- [#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. + +- [#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 + +- [#6770](https://github.com/Effect-TS/effect/pull/6770) [`f0c8151`](https://github.com/Effect-TS/effect/commit/f0c8151688c9ae50a6325757e3c11fcabb43fab5) Thanks @tim-smart! - Decode Effect SSE event schemas as complete events, including reserved failure events, in generated HTTP clients. + +- 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 + - @effect/platform-node@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. + +- 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 + - @effect/platform-node@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 + - @effect/platform-node@4.0.0-beta.101 + +## 4.0.0-beta.100 + +### Patch Changes + +- [#6483](https://github.com/Effect-TS/effect/pull/6483) [`ce95d88`](https://github.com/Effect-TS/effect/commit/ce95d88603e9facbcd6c462c5444e391792dde6b) Thanks @jpenilla! - Fix generation order for recursive schemas referenced by earlier recursive definitions, closes [#6357](https://github.com/Effect-TS/effect/issues/6357). + +- Updated dependencies [[`c1288dd`](https://github.com/Effect-TS/effect/commit/c1288dd1a52a2811ab7df57fc4ce236c6be4c745), [`b0f1a50`](https://github.com/Effect-TS/effect/commit/b0f1a50dacece60aa2393a15da853d82891a7a34), [`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 + - @effect/platform-node@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 + - @effect/platform-node@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 + - @effect/platform-node@4.0.0-beta.98 + +## 4.0.0-beta.97 + +### Patch Changes + +- Updated dependencies []: + - effect@4.0.0-beta.97 + - @effect/platform-node@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 + - @effect/platform-node@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 + - @effect/platform-node@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 + - @effect/platform-node@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 + - @effect/platform-node@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 + - @effect/platform-node@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 + - @effect/platform-node@4.0.0-beta.91 + ## 4.0.0-beta.90 ### Patch Changes diff --git a/repos/effect/packages/tools/openapi-generator/docgen.json b/repos/effect/packages/tools/openapi-generator/docgen.json deleted file mode 100644 index 4dd36175ad..0000000000 --- a/repos/effect/packages/tools/openapi-generator/docgen.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "$schema": "../../../node_modules/@effect/docgen/schema.json", - "exclude": ["**/*.ts"] -} diff --git a/repos/effect/packages/tools/openapi-generator/package.json b/repos/effect/packages/tools/openapi-generator/package.json index 572f758160..456ae56ec8 100644 --- a/repos/effect/packages/tools/openapi-generator/package.json +++ b/repos/effect/packages/tools/openapi-generator/package.json @@ -1,17 +1,17 @@ { "name": "@effect/openapi-generator", "type": "module", - "version": "4.0.0-beta.90", + "version": "4.0.0-beta.107", "license": "MIT", "description": "Generate Effect Schema types, HTTP clients, and HttpApi modules from OpenAPI specifications", "homepage": "https://effect.website", "repository": { "type": "git", - "url": "https://github.com/Effect-TS/effect-smol.git", + "url": "https://github.com/Effect-TS/effect.git", "directory": "packages/tools/openapi-generator" }, "bugs": { - "url": "https://github.com/Effect-TS/effect-smol/issues" + "url": "https://github.com/Effect-TS/effect/issues" }, "tags": [ "typescript", @@ -38,7 +38,10 @@ "dist/**/*.js", "dist/**/*.js.map", "dist/**/*.d.ts", - "dist/**/*.d.ts.map" + "dist/**/*.d.ts.map", + "AGENTS.md", + "CLAUDE.md", + "ai-docs/**/*" ], "publishConfig": { "access": "public", @@ -55,11 +58,8 @@ }, "scripts": { "build": "tsc -b tsconfig.json && pnpm babel", - "build:tsgo": "tsgo -b tsconfig.json && pnpm babel", "babel": "babel dist --plugins annotate-pure-calls --out-dir dist --source-maps", - "check": "tsc -b tsconfig.json", - "test": "vitest", - "coverage": "vitest --coverage" + "check": "tsc -b tsconfig.json" }, "peerDependencies": { "@effect/platform-node": "workspace:^", diff --git a/repos/effect/packages/tools/openapi-generator/src/JsonSchemaGenerator.ts b/repos/effect/packages/tools/openapi-generator/src/JsonSchemaGenerator.ts index 51ec605dea..df325baf2c 100644 --- a/repos/effect/packages/tools/openapi-generator/src/JsonSchemaGenerator.ts +++ b/repos/effect/packages/tools/openapi-generator/src/JsonSchemaGenerator.ts @@ -24,16 +24,17 @@ import * as Rec from "effect/Record" import * as SchemaRepresentation from "effect/SchemaRepresentation" type Source = "openapi-3.0" | "openapi-3.1" - interface GenerateOptions { readonly onEnter?: ((js: JsonSchema.JsonSchema) => JsonSchema.JsonSchema) | undefined } +interface MultipartSchemaRefs { + readonly singleFile: string + readonly files: string +} + interface GenerateHttpApiOptions extends GenerateOptions { - readonly multipartSchemaRefs?: { - readonly singleFile: string - readonly files: string - } | undefined + readonly multipartSchemaRefs?: MultipartSchemaRefs | undefined } /** @@ -49,10 +50,14 @@ interface GenerateHttpApiOptions extends GenerateOptions { * @since 4.0.0 */ export function make() { - const store: Record = {} + return makeWithRepresentation() +} + +function makeWithRepresentation() { + const store = Object.create(null) as Record function addSchema(name: string, schema: JsonSchema.JsonSchema): string { - if (name in store) { + if (Object.hasOwn(store, name)) { throw new Error(`Schema ${name} already exists`) } store[name] = schema @@ -114,7 +119,8 @@ export function make() { renderSchemaTypeAndRuntime(generated.nameMap[i], code, typeOnly) ) - return render("recursive declarations", recursiveDeclarations) + + return renderImportArtifacts(generated.codeDocument, !typeOnly) + + render("recursive declarations", recursiveDeclarations) + render("non-recursive definitions", nonRecursives) + render("recursive definitions", recursives) + render("schemas", codes) @@ -165,7 +171,8 @@ export function make() { renderSchemaTypeAndRuntime(generated.nameMap[i], code, false, options?.multipartSchemaRefs) ) - return render("recursive declarations", recursiveDeclarations) + + return renderImportArtifacts(generated.codeDocument, true) + + render("recursive declarations", recursiveDeclarations) + render("non-recursive definitions", nonRecursives) + render("recursive definitions", recursives) + render("schemas", codes) @@ -174,7 +181,7 @@ export function make() { function makeCodeDocument( source: Source, components: JsonSchema.Definitions, - options?: GenerateOptions + options?: GenerateHttpApiOptions ): { readonly nameMap: Array readonly codeDocument: SchemaRepresentation.CodeDocument @@ -182,7 +189,7 @@ export function make() { const nameMap: Array = [] const schemas: Array = [] - const definitions: JsonSchema.Definitions = Rec.map( + let definitions: JsonSchema.Definitions = Rec.map( components, (js) => fromSchemaOpenApi(source, js).schema ) @@ -195,24 +202,33 @@ export function make() { if (!Arr.isArrayNonEmpty(schemas)) { return } + if (options?.multipartSchemaRefs !== undefined) { + definitions = omitSupersededMultipartDefinitions(definitions, schemas, options.multipartSchemaRefs) + } - const multiDocument: SchemaRepresentation.MultiDocument = SchemaRepresentation.fromJsonSchemaMultiDocument({ - dialect: "draft-2020-12", + const document = { + dialect: "draft-2020-12" as const, schemas, definitions - }, { - onEnter(js) { + } + const importerOptions: SchemaRepresentation.FromJsonSchemaOptions = { + patterns: "apply", + onEnter(js: JsonSchema.JsonSchema) { const out = { ...js } if (out.type === "object" && out.additionalProperties === undefined) { out.additionalProperties = false } - return options?.onEnter?.(out) ?? out + return options?.onEnter === undefined ? out : options.onEnter(out) } - }) + } + const rootSchemas = SchemaRepresentation.fromJsonSchemaMultiDocument(document, importerOptions) + const codeDocument = SchemaRepresentation.toCodeDocument( + SchemaRepresentation.toRepresentations(Arr.map(rootSchemas, (schema) => schema.ast)) + ) return { nameMap, - codeDocument: SchemaRepresentation.toCodeDocument(multiDocument) + codeDocument } } @@ -232,10 +248,7 @@ function renderSchemaTypeAndRuntime( $ref: string, code: SchemaRepresentation.Code, typeOnly: boolean, - multipartSchemaRefs?: { - readonly singleFile: string - readonly files: string - } + multipartSchemaRefs?: MultipartSchemaRefs ) { if (!typeOnly && multipartSchemaRefs !== undefined) { if ($ref === multipartSchemaRefs.singleFile) { @@ -275,6 +288,72 @@ function render(title: string, as: ReadonlyArray) { return "// " + title + "\n" + as.join("\n") + "\n" } +function renderImportArtifacts(codeDocument: SchemaRepresentation.CodeDocument, enabled: boolean): string { + if (!enabled) return "" + const imports = codeDocument.artifacts.flatMap((artifact) => + artifact._tag === "Import" ? [artifact.importDeclaration] : [] + ) + return imports.length === 0 ? "" : imports.join("\n") + "\n" +} + +function omitSupersededMultipartDefinitions( + definitions: JsonSchema.Definitions, + schemas: ReadonlyArray, + multipartSchemaRefs: MultipartSchemaRefs +): JsonSchema.Definitions { + const rootReferences = collectReferenceKeys(schemas) + const multipartReferences = new Set([multipartSchemaRefs.singleFile, multipartSchemaRefs.files]) + const output: JsonSchema.Definitions = {} + + for (const [key, schema] of Object.entries(definitions)) { + const superseded = !multipartReferences.has(key) && !rootReferences.has(key) && + referencesAny(schema, multipartReferences) + if (!superseded) { + Object.defineProperty(output, key, { + value: schema, + enumerable: true, + configurable: true, + writable: true + }) + } + } + return output +} + +function collectReferenceKeys(input: unknown): Set { + const references = new Set() + visitReferences(input, ($ref) => { + const token = $ref.split("/").at(-1) + if (token !== undefined && token.length > 0) { + references.add(token.replaceAll("~1", "/").replaceAll("~0", "~")) + } + }) + return references +} + +function referencesAny(input: unknown, keys: ReadonlySet): boolean { + const references = collectReferenceKeys(input) + for (const key of references) { + if (keys.has(key)) return true + } + return false +} + +function visitReferences(input: unknown, onReference: ($ref: string) => void): void { + if (Array.isArray(input)) { + for (const value of input) visitReferences(value, onReference) + return + } + if (typeof input !== "object" || input === null) return + for (const [key, value] of Object.entries(input)) { + if (key === "$ref" && typeof value === "string") { + onReference(value) + } else { + visitReferences(value, onReference) + } + } +} + const tokenPattern = /[A-Za-z_$][A-Za-z0-9_$]*/g function collectForwardReferencedRecursives( @@ -285,6 +364,7 @@ function collectForwardReferencedRecursives( recursives: ReadonlyArray ): Set { const recursiveNames = new Set(recursives.map(([name]) => name)) + const recursiveIndexes = new Map(recursives.map(([name], index) => [name, index])) const referenced = new Set() for (const { code } of nonRecursives) { @@ -296,6 +376,17 @@ function collectForwardReferencedRecursives( } } + for (let index = 0; index < recursives.length; index++) { + const [, code] = recursives[index] + for (const token of code.runtime.matchAll(tokenPattern)) { + const identifier = token[0] + const referencedIndex = recursiveIndexes.get(identifier) + if (referencedIndex !== undefined && referencedIndex > index) { + referenced.add(identifier) + } + } + } + return referenced } diff --git a/repos/effect/packages/tools/openapi-generator/src/OpenApiGenerator.ts b/repos/effect/packages/tools/openapi-generator/src/OpenApiGenerator.ts index d447a0452d..a82813ece7 100644 --- a/repos/effect/packages/tools/openapi-generator/src/OpenApiGenerator.ts +++ b/repos/effect/packages/tools/openapi-generator/src/OpenApiGenerator.ts @@ -15,6 +15,7 @@ import * as Effect from "effect/Effect" import type * as JsonSchema from "effect/JsonSchema" import * as Layer from "effect/Layer" import * as Predicate from "effect/Predicate" +import * as Rec from "effect/Record" import * as String from "effect/String" import type { OpenAPISecurityScheme, OpenAPISpec, OpenAPISpecMethodName } from "effect/unstable/httpapi/OpenApi" import SwaggerToOpenApi from "swagger2openapi" @@ -156,7 +157,7 @@ export const make = Effect.gen(function*() { const generation = options.format === "httpapi" ? generator.generateHttpApi( source, - withHttpApiMultipartSchemas(spec.components?.schemas ?? {}, multipartSchemaRefs), + withHttpApiMultipartSchemas(spec.components?.schemas ?? {}, multipartSchemaRefs, resolveRef), { onEnter: options.onEnter, multipartSchemaRefs @@ -604,11 +605,23 @@ const parseOpenApi = ( } } - const sseResponseSchema = content?.["text/event-stream"]?.schema + const sseMediaType = content?.["text/event-stream"] + const sseResponseSchema = sseMediaType?.schema if (!isHttpApi && Predicate.isUndefined(op.sseSchema) && Predicate.isNotUndefined(sseResponseSchema)) { const statusMajorNumber = Number(parsedStatus[0]) if (!Number.isNaN(statusMajorNumber) && statusMajorNumber < 4) { - op.sseSchema = addSchema(`${schemaId}${status}Sse`, sseResponseSchema, op) + const effectStream = sseMediaType["x-effect-stream"] + op.sseSchemaMode = getEffectStreamEncoding(sseMediaType) === "sse" ? "event" : "data" + op.sseSchema = addSchema( + `${schemaId}${status}Sse`, + op.sseSchemaMode === "event" + ? makeSseEventSchema( + resolveReference(sseResponseSchema, resolveRef), + effectStream?.encoding === "sse" ? effectStream.failureEvent : undefined + ) + : sseResponseSchema, + op + ) } } @@ -744,14 +757,14 @@ const buildParameterSchema = < for (const [name, propertySchema] of Object.entries(paramSchema.properties)) { const adjustedName = `${parameter.name}[${name}]` - schema.properties[adjustedName] = propertySchema as JsonSchema.JsonSchema + Rec.assignProperty(schema.properties, adjustedName, propertySchema as JsonSchema.JsonSchema) if (required.includes(name)) { schema.required.push(adjustedName) } added.push(adjustedName) } } else { - schema.properties[parameter.name] = parameter.schema as JsonSchema.JsonSchema + Rec.assignProperty(schema.properties, parameter.name, parameter.schema as JsonSchema.JsonSchema) if (parameter.required) { schema.required.push(parameter.name) } @@ -807,13 +820,14 @@ const toDefinitionRef = (name: string): string => `#/$defs/${name.replaceAll("~" const withHttpApiMultipartSchemas = ( definitions: JsonSchema.Definitions, - multipartSchemaRefs: HttpApiMultipartSchemaRefs | undefined + multipartSchemaRefs: HttpApiMultipartSchemaRefs | undefined, + resolveRef: (ref: string) => unknown ): JsonSchema.Definitions => { if (multipartSchemaRefs === undefined) { return definitions } return { - ...definitions, + ...Rec.map(definitions, (schema) => transformMultipartSchema(schema, multipartSchemaRefs, resolveRef)), [multipartSchemaRefs.singleFile]: { type: "string", format: "binary" @@ -850,18 +864,21 @@ const transformMultipartSchema = ( } if (typeof value.$ref === "string" && value.$ref.startsWith("#/components/schemas/")) { - const cached = cache.get(value.$ref) + const { $ref, ...siblings } = value + const withSiblings = (schema: unknown): unknown => + Object.keys(siblings).length === 0 ? schema : { allOf: [schema, visit(siblings)] } + const cached = cache.get($ref) if (cached !== undefined) { - return cached + return withSiblings(cached) } - if (stack.has(value.$ref)) { + if (stack.has($ref)) { return value } - stack.add(value.$ref) - const transformed = visit(resolveSchemaReference(value.$ref, resolveRef)) - stack.delete(value.$ref) - cache.set(value.$ref, transformed) - return transformed + stack.add($ref) + const transformed = visit(resolveRef($ref)) + stack.delete($ref) + cache.set($ref, transformed) + return withSiblings(transformed) } if (isMultipartBinaryFile(value)) { @@ -870,7 +887,7 @@ const transformMultipartSchema = ( const out: Record = {} for (const [key, current] of Object.entries(value)) { - out[key] = visit(current) + Rec.assignProperty(out, key, visit(current)) } if (isMultipartBinaryFiles(out, singleFileRef)) { @@ -883,19 +900,6 @@ const transformMultipartSchema = ( return visit(schema) as JsonSchema.JsonSchema } -const resolveSchemaReference = (ref: string, resolveRef: (ref: string) => unknown): unknown => { - let current: unknown = { $ref: ref } - const seen = new Set() - while (Predicate.isObject(current) && typeof current.$ref === "string") { - if (seen.has(current.$ref)) { - return current - } - seen.add(current.$ref) - current = resolveRef(current.$ref) - } - return current -} - const isMultipartBinaryFile = (value: unknown): value is JsonSchema.JsonSchema => Predicate.isObject(value) && value.type === "string" && @@ -979,6 +983,59 @@ const getEffectStreamErrorSchema = (mediaType: object): JsonSchema.JsonSchema | return stream.errorSchema as JsonSchema.JsonSchema } +const makeSseEventSchema = ( + input: JsonSchema.JsonSchema, + failureEvent: string | undefined +): JsonSchema.JsonSchema => { + const eventSchema = normalizeSseEventSchema(input) + if (failureEvent === undefined) { + return eventSchema + } + return { + anyOf: [ + eventSchema, + { + type: "object", + properties: { + id: { type: "string" }, + event: { const: failureEvent }, + data: { type: "string" } + }, + required: ["event", "data"], + additionalProperties: false + } + ] + } +} + +const normalizeSseEventSchema = (input: JsonSchema.JsonSchema): JsonSchema.JsonSchema => { + const schema = { ...input } as Record + for (const key of ["allOf", "anyOf", "oneOf"] as const) { + if (Array.isArray(schema[key])) { + schema[key] = schema[key].map(normalizeSseEventSchema) + } + } + if ( + schema.type !== "object" || + !Predicate.isObject(schema.properties) || + !Object.hasOwn(schema.properties, "id") || + !Object.hasOwn(schema.properties, "event") || + !Object.hasOwn(schema.properties, "data") + ) { + return schema as JsonSchema.JsonSchema + } + + // OpenAPI represents `undefined` as required nullable; restore the SSE parser's optional wire field. + schema.properties = { + ...schema.properties, + id: { type: "string" } + } + if (Array.isArray(schema.required)) { + schema.required = schema.required.filter((name: unknown) => name !== "id") + } + return schema as JsonSchema.JsonSchema +} + const resolveReference = (input: unknown, resolveRef: (ref: string) => unknown): any => { let current = input while (Predicate.isObject(current) && typeof current.$ref === "string") { diff --git a/repos/effect/packages/tools/openapi-generator/src/OpenApiPatch.ts b/repos/effect/packages/tools/openapi-generator/src/OpenApiPatch.ts index 2ea7a3fb91..ecc63c7d07 100644 --- a/repos/effect/packages/tools/openapi-generator/src/OpenApiPatch.ts +++ b/repos/effect/packages/tools/openapi-generator/src/OpenApiPatch.ts @@ -35,22 +35,21 @@ import * as Yaml from "yaml" * * **Example** (Creating a parse error) * - * ```ts - * import * as OpenApiPatch from "@effect/openapi-generator/OpenApiPatch" + * ```ts import.meta.vitest + * import { JsonPatchParseError } from "@effect/openapi-generator/OpenApiPatch" * - * const error = new OpenApiPatch.JsonPatchParseError({ + * const error = new JsonPatchParseError({ * source: "./patches/fix.json", * reason: "Unexpected token at position 42" * }) * - * console.log(error.message) - * // "Failed to parse patch from ./patches/fix.json: Unexpected token at position 42" + * error.message // => "Failed to parse patch from ./patches/fix.json: Unexpected token at position 42" * ``` * * @category errors * @since 4.0.0 */ -export class JsonPatchParseError extends Schema.ErrorClass("JsonPatchParseError")({ +export class JsonPatchParseError extends Schema.Error("JsonPatchParseError")({ _tag: Schema.tag("JsonPatchParseError"), source: Schema.String, reason: Schema.String @@ -73,22 +72,21 @@ export class JsonPatchParseError extends Schema.ErrorClass( * * **Example** (Creating a validation error) * - * ```ts - * import * as OpenApiPatch from "@effect/openapi-generator/OpenApiPatch" + * ```ts import.meta.vitest + * import { JsonPatchValidationError } from "@effect/openapi-generator/OpenApiPatch" * - * const error = new OpenApiPatch.JsonPatchValidationError({ + * const error = new JsonPatchValidationError({ * source: "inline", * reason: "Expected 'add' | 'remove' | 'replace' at [0].op, got 'copy'" * }) * - * console.log(error.message) - * // "Invalid JSON Patch from inline: Expected 'add' | 'remove' | 'replace' at [0].op, got 'copy'" + * error.message // => "Invalid JSON Patch from inline: Expected 'add' | 'remove' | 'replace' at [0].op, got 'copy'" * ``` * * @category errors * @since 4.0.0 */ -export class JsonPatchValidationError extends Schema.ErrorClass("JsonPatchValidationError")({ +export class JsonPatchValidationError extends Schema.Error("JsonPatchValidationError")({ _tag: Schema.tag("JsonPatchValidationError"), source: Schema.String, reason: Schema.String @@ -110,10 +108,10 @@ export class JsonPatchValidationError extends Schema.ErrorClass 'Failed to apply patch from ./patches/fix.json: operation 2 (remove at /paths/~1users): Property "users" does not exist' * ``` * * @category errors * @since 4.0.0 */ -export class JsonPatchApplicationError - extends Schema.ErrorClass("JsonPatchApplicationError")({ - _tag: Schema.tag("JsonPatchApplicationError"), - source: Schema.String, - operationIndex: Schema.Number, - operation: Schema.String, - path: Schema.String, - reason: Schema.String - }) -{ +export class JsonPatchApplicationError extends Schema.Error("JsonPatchApplicationError")({ + _tag: Schema.tag("JsonPatchApplicationError"), + source: Schema.String, + operationIndex: Schema.Natural, + operation: Schema.String, + path: Schema.String, + reason: Schema.String +}) { override get message() { return `Failed to apply patch from ${this.source}: operation ${this.operationIndex} ` + `(${this.operation} at ${this.path}): ${this.reason}` @@ -154,19 +149,19 @@ export class JsonPatchApplicationError * * **Example** (Creating an aggregate error) * - * ```ts - * import * as OpenApiPatch from "@effect/openapi-generator/OpenApiPatch" + * ```ts import.meta.vitest + * import { JsonPatchAggregateError, JsonPatchApplicationError } from "@effect/openapi-generator/OpenApiPatch" * - * const error = new OpenApiPatch.JsonPatchAggregateError({ + * const error = new JsonPatchAggregateError({ * errors: [ - * new OpenApiPatch.JsonPatchApplicationError({ + * new JsonPatchApplicationError({ * source: "./fix.json", * operationIndex: 0, * operation: "replace", * path: "/info/x", * reason: "Property does not exist" * }), - * new OpenApiPatch.JsonPatchApplicationError({ + * new JsonPatchApplicationError({ * source: "./fix.json", * operationIndex: 2, * operation: "remove", @@ -176,14 +171,13 @@ export class JsonPatchApplicationError * ] * }) * - * console.log(error.message) - * // "2 patch operations failed:\n 1. ..." + * error.message.split("\n")[0] // => "2 patch operations failed:" * ``` * * @category errors * @since 4.0.0 */ -export class JsonPatchAggregateError extends Schema.ErrorClass("JsonPatchAggregateError")({ +export class JsonPatchAggregateError extends Schema.Error("JsonPatchAggregateError")({ _tag: Schema.tag("JsonPatchAggregateError"), errors: Schema.Array(Schema.Unknown) }) { @@ -283,15 +277,17 @@ export const JsonPatchOperation: Schema.Codec = Sc * * **Example** (Decoding a patch document) * - * ```ts + * ```ts import.meta.vitest * import { Schema } from "effect" - * import * as OpenApiPatch from "@effect/openapi-generator/OpenApiPatch" + * import { JsonPatchDocument } from "@effect/openapi-generator/OpenApiPatch" * - * const patch = Schema.decodeUnknownSync(OpenApiPatch.JsonPatchDocument)([ + * const patch = Schema.decodeUnknownSync(JsonPatchDocument)([ * { op: "add", path: "/foo", value: "bar" }, * { op: "remove", path: "/baz" }, * { op: "replace", path: "/qux", value: 42 } * ]) + * + * patch.map((operation) => operation.op) // => ["add", "remove", "replace"] * ``` * * @category schemas @@ -302,7 +298,7 @@ export const JsonPatchDocument = Schema.Array(JsonPatchOperation) /** * Type for a JSON Patch document. * - * @category types + * @category models * @since 4.0.0 */ export type JsonPatchDocument = typeof JsonPatchDocument.Type @@ -434,23 +430,21 @@ const parseInlinePatch = Effect.fn("parseInlinePatch")(function*(input: string) * * **Example** (Parsing patch input) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" - * import * as OpenApiPatch from "@effect/openapi-generator/OpenApiPatch" + * import { parsePatchInput } from "@effect/openapi-generator/OpenApiPatch" * * // From inline JSON - * const fromInline = OpenApiPatch.parsePatchInput( + * const fromInline = parsePatchInput( * '[{"op":"replace","path":"/info/title","value":"My API"}]' * ) * - * // From file path - * const fromFile = OpenApiPatch.parsePatchInput("./patches/fix-api.json") - * * const program = Effect.gen(function*() { * const patch = yield* fromInline - * console.log(patch) - * // [{ op: "replace", path: "/info/title", value: "My API" }] + * return [patch[0].op, patch[0].path] * }) + * + * Effect.runSync(program) // => ["replace", "/info/title"] * ``` * * @category parsing @@ -481,9 +475,9 @@ export const parsePatchInput = Effect.fn("parsePatchInput")(function*(input: str * * **Example** (Applying patches) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" - * import * as OpenApiPatch from "@effect/openapi-generator/OpenApiPatch" + * import { applyPatches } from "@effect/openapi-generator/OpenApiPatch" * * const document = { info: { title: "Old Title" }, paths: {} } * const patches = [ @@ -494,13 +488,14 @@ export const parsePatchInput = Effect.fn("parsePatchInput")(function*(input: str * ] * * const program = Effect.gen(function*() { - * const result = yield* OpenApiPatch.applyPatches(patches, document) - * console.log(result) - * // { info: { title: "New Title" }, paths: {} } + * const result = yield* applyPatches(patches, document) + * return (result as typeof document).info.title * }) + * + * Effect.runSync(program) // => "New Title" * ``` * - * @category application + * @category transforming * @since 4.0.0 */ export const applyPatches = Effect.fn("applyPatches")(function*( diff --git a/repos/effect/packages/tools/openapi-generator/src/OpenApiTransformer.ts b/repos/effect/packages/tools/openapi-generator/src/OpenApiTransformer.ts index e511a64072..e56c65725f 100644 --- a/repos/effect/packages/tools/openapi-generator/src/OpenApiTransformer.ts +++ b/repos/effect/packages/tools/openapi-generator/src/OpenApiTransformer.ts @@ -26,7 +26,7 @@ import * as Utils from "./Utils.ts" * types, and the implementation body. The generator swaps implementations to * choose between schema-backed clients and type-only clients. * - * @category code generation + * @category services * @since 4.0.0 */ export class OpenApiTransformer extends Context.Service< @@ -40,21 +40,30 @@ export class OpenApiTransformer extends Context.Service< interface ImportRequirements { readonly eventStream: boolean + readonly eventStreamData: boolean + readonly eventStreamSchema: boolean readonly octetStream: boolean } const computeImportRequirements = (operations: ReadonlyArray): ImportRequirements => { let eventStream = false + let eventStreamData = false + let eventStreamSchema = false let octetStream = false for (const op of operations) { if (op.sseSchema) { eventStream = true + if (op.sseSchemaMode === "event") { + eventStreamSchema = true + } else { + eventStreamData = true + } } if (op.binaryResponse) { octetStream = true } } - return { eventStream, octetStream } + return { eventStream, eventStreamData, eventStreamSchema, octetStream } } const requiresStreaming = (requirements: ImportRequirements): boolean => @@ -173,8 +182,11 @@ ${clientErrorSource(name)}` const jsdoc = Utils.toComment(operation.description) const methodKey = `readonly "${operation.id}Sse"` const parameters = args.join(", ") + const value = operation.sseSchemaMode === "event" + ? `typeof ${operation.sseSchema}.Type` + : `{ readonly event: string; readonly id: string | undefined; readonly data: typeof ${operation.sseSchema}.Type }` const returnType = - `Stream.Stream<{ readonly event: string; readonly id: string | undefined; readonly data: typeof ${operation.sseSchema}.Type }, HttpClientError.HttpClientError | SchemaError | Sse.Retry, typeof ${operation.sseSchema}.DecodingServices>` + `Stream.Stream<${value}, HttpClientError.HttpClientError | SchemaError | Sse.Retry | Sse.SseError, typeof ${operation.sseSchema}.DecodingServices>` return `${jsdoc}${methodKey}: (${parameters}) => ${returnType}` } @@ -226,9 +238,12 @@ ${clientErrorSource(name)}` } const helpers: Array = [commonSource] - if (requirements.eventStream) { + if (requirements.eventStreamData) { helpers.push(sseRequestSource(importName)) } + if (requirements.eventStreamSchema) { + helpers.push(sseEventRequestSource) + } if (requirements.octetStream) { helpers.push(binaryRequestSource) } @@ -370,7 +385,7 @@ export const make = ( pipeline.push(`HttpClientRequest.bodyJsonUnsafe(options.payload)`) } - pipeline.push(`sseRequest(${operation.sseSchema})`) + pipeline.push(`${operation.sseSchemaMode === "event" ? "sseEventRequest" : "sseRequest"}(${operation.sseSchema})`) return ( `"${operation.id}Sse": (${params}) => ` + @@ -462,7 +477,7 @@ export const make = ( * Use when you use this layer when generated HttpClient code should perform runtime response * decoding with generated Effect Schema values. * - * @category code generation + * @category layers * @since 4.0.0 */ export const layerTransformerSchema = Layer.sync( @@ -873,7 +888,7 @@ export const make = ( * generated client relies on TypeScript types instead of runtime Schema * decoding. * - * @category code generation + * @category layers * @since 4.0.0 */ export const layerTransformerTs = Layer.sync( @@ -923,7 +938,7 @@ const sseRequestSource = (_importName: string) => request: HttpClientRequest.HttpClientRequest ): Stream.Stream< { readonly event: string; readonly id: string | undefined; readonly data: Type }, - HttpClientError.HttpClientError | SchemaError | Sse.Retry, + HttpClientError.HttpClientError | SchemaError | Sse.Retry | Sse.SseError, DecodingServices > => HttpClient.filterStatusOk(httpClient).execute(request).pipe( @@ -933,6 +948,21 @@ const sseRequestSource = (_importName: string) => Stream.pipeThroughChannel(Sse.decodeDataSchema(schema)) )` +const sseEventRequestSource = `const sseEventRequest = (schema: S) => + ( + request: HttpClientRequest.HttpClientRequest + ): Stream.Stream< + S["Type"], + HttpClientError.HttpClientError | SchemaError | Sse.Retry | Sse.SseError, + S["DecodingServices"] + > => + HttpClient.filterStatusOk(httpClient).execute(request).pipe( + Effect.map((response) => response.stream), + Stream.unwrap, + Stream.decodeText(), + Stream.pipeThroughChannel(Sse.decodeSchema(schema)) + )` + const binaryRequestSource = `const binaryRequest = (request: HttpClientRequest.HttpClientRequest): Stream.Stream => HttpClient.filterStatusOk(httpClient).execute(request).pipe( diff --git a/repos/effect/packages/tools/openapi-generator/src/ParsedOperation.ts b/repos/effect/packages/tools/openapi-generator/src/ParsedOperation.ts index 725133d369..b6812e59f2 100644 --- a/repos/effect/packages/tools/openapi-generator/src/ParsedOperation.ts +++ b/repos/effect/packages/tools/openapi-generator/src/ParsedOperation.ts @@ -221,6 +221,7 @@ export interface ParsedOperation { readonly voidSchemas: ReadonlySet // SSE streaming response schema (text/event-stream) readonly sseSchema?: string + readonly sseSchemaMode: "data" | "event" // Binary stream response (application/octet-stream) readonly binaryResponse: boolean } @@ -273,5 +274,6 @@ export const makeDeepMutable = (options: { errorSchemas: new Map(), voidSchemas: new Set(), paramsOptional: true, + sseSchemaMode: "data", binaryResponse: false }) diff --git a/repos/effect/packages/tools/openapi-generator/src/Utils.ts b/repos/effect/packages/tools/openapi-generator/src/Utils.ts index 958f0faf81..aef310c3bc 100644 --- a/repos/effect/packages/tools/openapi-generator/src/Utils.ts +++ b/repos/effect/packages/tools/openapi-generator/src/Utils.ts @@ -101,7 +101,7 @@ export const toComment = UndefinedOr.match({ * This mutates `destination` directly, which avoids allocating an intermediate * array when generator code needs to merge collections. * - * @category concatenating + * @category mutations * @since 4.0.0 */ export const spreadElementsInto = (source: Array, destination: Array): void => { diff --git a/repos/effect/packages/tools/openapi-generator/test/JsonSchemaGenerator.test.ts b/repos/effect/packages/tools/openapi-generator/test/JsonSchemaGenerator.test.ts index 31e33f4a95..1b4a6aab26 100644 --- a/repos/effect/packages/tools/openapi-generator/test/JsonSchemaGenerator.test.ts +++ b/repos/effect/packages/tools/openapi-generator/test/JsonSchemaGenerator.test.ts @@ -22,7 +22,7 @@ export const A = Schema.String const result = generator.generate("openapi-3.1", definitions, false) expect(result).toBe(`// non-recursive definitions export type B = string -export const B = Schema.String +export const B = Schema.String.annotate({ "identifier": "B" }) // schemas export type A = B export const A = B @@ -108,13 +108,54 @@ export const B = Schema.Struct({ "id": Schema.String })`) const result = generator.generate("openapi-3.1", definitions, false) expect(result).toBe(`// recursive definitions export type B = { readonly "name": string, readonly "children": ReadonlyArray } -export const B = Schema.Struct({ "name": Schema.String, "children": Schema.Array(Schema.suspend((): Schema.Codec => B)) }) +export const B = Schema.Struct({ "name": Schema.String, "children": Schema.Array(Schema.suspend((): Schema.Codec => B)) }).annotate({ "identifier": "B" }) // schemas export type A = B export const A = B `) }) + it("hoists recursive definitions referenced by earlier recursive definitions", () => { + const generator = JsonSchemaGenerator.make() + generator.addSchema("A", { $ref: "#/components/schemas/ResourcesNetworkCard" }) + const definitions = { + ResourcesNetworkCard: { + type: "object", + properties: { + sriov: { + $ref: "#/components/schemas/ResourcesNetworkCardSRIOV" + } + }, + additionalProperties: false + }, + ResourcesNetworkCardSRIOV: { + type: "object", + properties: { + vfs: { + type: "array", + items: { + $ref: "#/components/schemas/ResourcesNetworkCard" + } + } + }, + additionalProperties: false + } + } + + const result = generator.generate("openapi-3.1", definitions, false) + const recursiveDeclaration = + "export const ResourcesNetworkCard = Schema.suspend((): Schema.Codec => __recursive_ResourcesNetworkCard)" + + expect(result).toContain(recursiveDeclaration) + expect(result).toContain("const __recursive_ResourcesNetworkCard =") + expect(result.indexOf(recursiveDeclaration)).toBeLessThan( + result.indexOf("export const ResourcesNetworkCardSRIOV =") + ) + expect(result.indexOf("export const ResourcesNetworkCardSRIOV =")).toBeLessThan( + result.indexOf("const __recursive_ResourcesNetworkCard =") + ) + }) + it("renders recursive definitions before non-recursive references for runtime generation", () => { const generator = JsonSchemaGenerator.make() generator.addSchema("A", { $ref: "#/components/schemas/ErrorResponse" }) diff --git a/repos/effect/packages/tools/openapi-generator/test/JsonSchemaGeneratorRepresentation.test.ts b/repos/effect/packages/tools/openapi-generator/test/JsonSchemaGeneratorRepresentation.test.ts new file mode 100644 index 0000000000..3c9dc9af4a --- /dev/null +++ b/repos/effect/packages/tools/openapi-generator/test/JsonSchemaGeneratorRepresentation.test.ts @@ -0,0 +1,34 @@ +import * as JsonSchemaGenerator from "@effect/openapi-generator/JsonSchemaGenerator" +import { assert, describe, it } from "@effect/vitest" + +describe("JsonSchemaGenerator representation", () => { + it("preserves patterns from code generation inputs", () => { + const generator = JsonSchemaGenerator.make() + generator.addSchema("Root", { type: "string", pattern: "^a+$" }) + + const output = generator.generate("openapi-3.1", {}, false) + + assert.include(output, `Schema.isPattern(new RegExp("^a+$"))`) + }) + + it("emits only reachable definitions", () => { + const generator = JsonSchemaGenerator.make() + generator.addSchema("Root", { $ref: "#/components/schemas/Shared" }) + + const output = generator.generate("openapi-3.1", { + Shared: { type: "string" }, + Unused: { type: "boolean" } + }, false) + + assert.strictEqual( + output, + `// non-recursive definitions +export type Shared = string +export const Shared = Schema.String.annotate({ "identifier": "Shared" }) +// schemas +export type Root = Shared +export const Root = Shared +` + ) + }) +}) diff --git a/repos/effect/packages/tools/openapi-generator/test/OpenApiGenerator.test.ts b/repos/effect/packages/tools/openapi-generator/test/OpenApiGenerator.test.ts index bf93c46b41..46e437b92f 100644 --- a/repos/effect/packages/tools/openapi-generator/test/OpenApiGenerator.test.ts +++ b/repos/effect/packages/tools/openapi-generator/test/OpenApiGenerator.test.ts @@ -518,13 +518,74 @@ export const TestClientError = ( }, [ `import * as Sse from "effect/unstable/encoding/Sse"`, - `readonly "streamEventsSse": () => Stream.Stream<{ readonly event: string; readonly id: string | undefined; readonly data: typeof StreamEvents200Sse.Type }, HttpClientError.HttpClientError | SchemaError | Sse.Retry, typeof StreamEvents200Sse.DecodingServices>`, + `readonly "streamEventsSse": () => Stream.Stream<{ readonly event: string; readonly id: string | undefined; readonly data: typeof StreamEvents200Sse.Type }, HttpClientError.HttpClientError | SchemaError | Sse.Retry | Sse.SseError, typeof StreamEvents200Sse.DecodingServices>`, `"streamEventsSse": () => HttpClientRequest.get(\`/events\`).pipe(`, `sseRequest(StreamEvents200Sse)`, `schema: Schema.ConstraintDecoder` ] )) + it.effect("annotated sse operation decodes the full event schema", () => + assertRuntimeIncludes( + { + openapi: "3.1.0", + info: { + title: "Test API", + version: "1.0.0" + }, + paths: { + "/events": { + get: { + operationId: "streamEvents", + parameters: [], + responses: { + 200: { + description: "Events streamed successfully", + content: { + "text/event-stream": { + schema: { + type: "object", + properties: { + id: { + anyOf: [{ type: "string" }, { type: "null" }] + }, + event: { const: "message" }, + data: { type: "string" } + }, + required: ["id", "event", "data"], + additionalProperties: false + }, + "x-effect-stream": { + encoding: "sse", + errorSchema: {}, + causeSchema: {}, + failureEvent: "effect/httpapi/stream/failure" + } + } + } + } + }, + tags: ["Events"], + security: [] + } + } + }, + components: { + schemas: {}, + securitySchemes: {} + }, + security: [], + tags: [] + }, + [ + `"id": Schema.optionalKey(Schema.String), "event": Schema.Literal("message"), "data": Schema.String`, + `"id": Schema.optionalKey(Schema.String), "event": Schema.Literal("effect/httpapi/stream/failure"), "data": Schema.String`, + `readonly "streamEventsSse": () => Stream.Stream`, + `sseEventRequest(StreamEvents200Sse)`, + `Stream.pipeThroughChannel(Sse.decodeSchema(schema))` + ] + )) + it.effect("form-urlencoded request body generates bodyUrlParams", () => assertRuntimeIncludes( { @@ -1542,6 +1603,144 @@ export const __HttpApiMultipartFiles = Multipart.FilesSchema`, ] )) + it.effect("preserves multipart component reference siblings", () => + assertHttpApiIncludes( + { + openapi: "3.1.0", + info: { + title: "Test API", + version: "1.0.0" + }, + paths: { + "/upload": { + post: { + operationId: "upload", + parameters: [], + requestBody: { + required: true, + content: { + "multipart/form-data": { + schema: { + type: "object", + properties: { + first: { + $ref: "#/components/schemas/UploadBody", + minProperties: 1, + description: "First upload" + }, + second: { + $ref: "#/components/schemas/UploadBody", + minProperties: 2, + description: "Second upload" + } + }, + required: ["first", "second"], + additionalProperties: false + } + } + } + } as any, + responses: { + 200: { + description: "Uploaded" + } + }, + tags: ["Payload"], + security: [] + } + } + }, + components: { + schemas: { + UploadBody: { + type: "object", + properties: { + file: { type: "string", format: "binary" } + }, + required: ["file"], + additionalProperties: false + } + }, + securitySchemes: {} + }, + security: [], + tags: [{ name: "Payload" }] + }, + [ + `"first": Schema.Struct({ "file": __HttpApiMultipartSingleFile })`, + `Schema.isMinProperties(1)`, + `"description": "First upload"`, + `"second": Schema.Struct({ "file": __HttpApiMultipartSingleFile })`, + `Schema.isMinProperties(2)`, + `"description": "Second upload"` + ] + )) + + it.effect("preserves multipart component alias siblings", () => + assertHttpApiIncludes( + { + openapi: "3.1.0", + info: { + title: "Test API", + version: "1.0.0" + }, + paths: { + "/upload": { + post: { + operationId: "upload", + parameters: [], + requestBody: { + required: true, + content: { + "multipart/form-data": { + schema: { + $ref: "#/components/schemas/UploadAlias" + } + } + } + } as any, + responses: { + 200: { + description: "Uploaded" + } + }, + tags: ["Payload"], + security: [] + } + } + }, + components: { + schemas: { + UploadAlias: { + $ref: "#/components/schemas/UploadBody", + minProperties: 1, + description: "Aliased upload" + }, + UploadBody: { + type: "object", + properties: { + file: { type: "string", format: "binary" } + }, + required: ["file"], + additionalProperties: false + } + }, + securitySchemes: {} + }, + security: [], + tags: [{ name: "Payload" }] + }, + [ + `export type UploadRequestFormData = { readonly "file": __HttpApiMultipartSingleFile }`, + `Schema.isMinProperties(1)`, + `"description": "Aliased upload"` + ], + [ + `export type UploadAlias =`, + `export type UploadBody =` + ] + )) + it.effect("maps multipart contentEncoding binary schemas (case-insensitive) to Multipart file schemas", () => assertHttpApiIncludes( { diff --git a/repos/effect/packages/tools/openapi-generator/tsconfig.json b/repos/effect/packages/tools/openapi-generator/tsconfig.json index 739eb6b55d..f9e09522b0 100644 --- a/repos/effect/packages/tools/openapi-generator/tsconfig.json +++ b/repos/effect/packages/tools/openapi-generator/tsconfig.json @@ -1,5 +1,5 @@ { - "$schema": "http://json.schemastore.org/tsconfig", + "$schema": "https://json.schemastore.org/tsconfig", "extends": "../../../tsconfig.base.json", "include": ["src"], "references": [ diff --git a/repos/effect/packages/tools/openapi-generator/vitest.config.ts b/repos/effect/packages/tools/openapi-generator/vitest.config.ts deleted file mode 100644 index c8a52c1826..0000000000 --- a/repos/effect/packages/tools/openapi-generator/vitest.config.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { mergeConfig, type ViteUserConfig } from "vitest/config" -import shared from "../../../vitest.shared.ts" - -const config: ViteUserConfig = {} - -export default mergeConfig(shared, config) diff --git a/repos/effect/packages/tools/oxc/docgen.json b/repos/effect/packages/tools/oxc/docgen.json deleted file mode 100644 index 4dd36175ad..0000000000 --- a/repos/effect/packages/tools/oxc/docgen.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "$schema": "../../../node_modules/@effect/docgen/schema.json", - "exclude": ["**/*.ts"] -} diff --git a/repos/effect/packages/tools/oxc/oxlintrc.json b/repos/effect/packages/tools/oxc/oxlintrc.json index 10cddc50ef..a517e19c59 100644 --- a/repos/effect/packages/tools/oxc/oxlintrc.json +++ b/repos/effect/packages/tools/oxc/oxlintrc.json @@ -21,7 +21,6 @@ "effect/no-js-extension-imports": "error", "effect/no-opaque-instance-fields": "error", "effect/no-unused-internal": "error", - "effect/jsdocs": "error", // Tune native rules // Import rules "typescript/consistent-type-imports": ["error", { @@ -60,7 +59,7 @@ "unicorn/require-post-message-target-origin": "off", "unicorn/prefer-add-event-listener": "off", "unicorn/prefer-set-has": "off", - "no-dangling-underscore": "off", + "no-underscore-dangle": "off", "typescript/no-explicit-any": "off", "typescript/no-empty-interface": "off", "typescript/ban-ts-comment": "off", diff --git a/repos/effect/packages/tools/oxc/package.json b/repos/effect/packages/tools/oxc/package.json index 745d6f7f7b..531874153d 100644 --- a/repos/effect/packages/tools/oxc/package.json +++ b/repos/effect/packages/tools/oxc/package.json @@ -8,12 +8,12 @@ "homepage": "https://effect.website", "repository": { "type": "git", - "url": "https://github.com/Effect-TS/effect-smol.git", + "url": "https://github.com/Effect-TS/effect.git", "directory": "packages/tools/oxc" }, "sideEffects": [], "bugs": { - "url": "https://github.com/Effect-TS/effect-smol/issues" + "url": "https://github.com/Effect-TS/effect/issues" }, "tags": [ "typescript", @@ -52,21 +52,18 @@ }, "scripts": { "build": "tsc -b tsconfig.json && pnpm babel", - "build:tsgo": "tsgo -b tsconfig.json && pnpm babel", "babel": "babel dist --plugins annotate-pure-calls --out-dir dist --source-maps", - "check": "tsc -b tsconfig.json", - "test": "vitest", - "coverage": "vitest --coverage" + "check": "tsc -b tsconfig.json" }, "dependencies": { - "@effect/jsdocs": "workspace:^" + "@oxlint/plugins": "^1.76.0" }, "peerDependencies": { "typescript": ">=5.0.0 <7.0.0" }, "devDependencies": { - "@types/node": "^25.7.0", + "@types/node": "^26.1.2", "typescript": "^6.0.3", - "vitest": "^4.1.4" + "vitest": "^4.1.10" } } diff --git a/repos/effect/packages/tools/oxc/src/oxlint/index.ts b/repos/effect/packages/tools/oxc/src/oxlint/index.ts index 5f8d63651d..205eddce08 100644 --- a/repos/effect/packages/tools/oxc/src/oxlint/index.ts +++ b/repos/effect/packages/tools/oxc/src/oxlint/index.ts @@ -1,4 +1,3 @@ -import jsdocs from "./rules/jsdocs.ts" import noBigIntLiterals from "./rules/no-bigint-literals.ts" import noImportFromBarrelPackage from "./rules/no-import-from-barrel-package.ts" import noJsExtensionImports from "./rules/no-js-extension-imports.ts" @@ -14,7 +13,6 @@ export default { "no-import-from-barrel-package": noImportFromBarrelPackage, "no-js-extension-imports": noJsExtensionImports, "no-opaque-instance-fields": noOpaqueInstanceFields, - "no-unused-internal": noUnusedInternal, - "jsdocs": jsdocs + "no-unused-internal": noUnusedInternal } } diff --git a/repos/effect/packages/tools/oxc/src/oxlint/rules/jsdocs.ts b/repos/effect/packages/tools/oxc/src/oxlint/rules/jsdocs.ts deleted file mode 100644 index 05f000baca..0000000000 --- a/repos/effect/packages/tools/oxc/src/oxlint/rules/jsdocs.ts +++ /dev/null @@ -1,108 +0,0 @@ -import * as Jsdocs from "@effect/jsdocs/Jsdocs" -import * as fs from "node:fs" -import * as path from "node:path" -import type { CreateRule, ESTree, Visitor } from "oxlint" - -interface RuleOptions { - readonly model?: string -} - -interface CachedModel { - readonly result: ReturnType - readonly filesByPath: ReadonlyMap[number]> - readonly mtimeMs: number - readonly size: number -} - -const modelCache = new Map() - -function getSourceText(context: { - readonly sourceCode: { readonly text?: string; getText(node?: unknown): string } -}): string { - return context.sourceCode.text ?? context.sourceCode.getText() -} - -function getCwd(context: { readonly cwd?: string; getCwd?: () => string }): string { - return context.cwd ?? context.getCwd?.() ?? process.cwd() -} - -function normalizePathName(filename: string): string { - return filename.split(path.sep).join("/") -} - -function readCachedModel(modelPath: string): CachedModel["result"] { - if (!fs.existsSync(modelPath)) return { _tag: "Failure", error: "missing" } - const stats = fs.statSync(modelPath) - const cached = modelCache.get(modelPath) - if (cached !== undefined && cached.mtimeMs === stats.mtimeMs && cached.size === stats.size) return cached.result - const result = Jsdocs.readJSDocModel(modelPath) - modelCache.set(modelPath, { - result, - filesByPath: result._tag === "Success" ? new Map(result.value.files.map((file) => [file.file, file])) : new Map(), - mtimeMs: stats.mtimeMs, - size: stats.size - }) - return result -} - -function getCachedFile( - modelPath: string, - file: string -): ReturnType extends infer R - ? R extends { readonly _tag: "Success"; readonly value: { readonly files: ReadonlyArray } } ? A | undefined - : never - : never -{ - return modelCache.get(modelPath)?.filesByPath.get(file) as never -} - -function rangeNode(range: readonly [number, number]): { readonly range: [number, number] } { - return { range: [range[0], range[1]] } -} - -const rule: CreateRule = { - meta: { - type: "problem", - docs: { description: "Enforce Effect's public API JSDoc structure" }, - schema: [ - { - type: "object", - properties: { - model: { type: "string" } - }, - additionalProperties: false - } - ] - }, - create(context) { - const options = (context.options[0] as RuleOptions | undefined) ?? {} - const source = getSourceText(context) - const cwd = getCwd(context) - const modelPath = path.resolve(cwd, options.model ?? ".data/jsdocs.json") - const result = readCachedModel(modelPath) - if (result._tag === "Failure") { - if (result.error === "missing") return {} as Visitor - return { - Program(node: ESTree.Node) { - context.report({ node, message: result.error }) - } - } as Visitor - } - const relative = normalizePathName(path.relative(cwd, context.filename)) - const file = getCachedFile(modelPath, relative) - if (file === undefined) return {} as Visitor - return { - Program(node: ESTree.Node) { - if (file.hash !== Jsdocs.sourceHash(source)) { - context.report({ node, message: "JSDoc model is stale for this file; run `pnpm jsdocs`" }) - return - } - for (const diagnostic of file.diagnostics) { - context.report({ node: rangeNode(diagnostic.range), message: diagnostic.message }) - } - } - } as Visitor - } -} - -export default rule diff --git a/repos/effect/packages/tools/oxc/src/oxlint/rules/no-bigint-literals.ts b/repos/effect/packages/tools/oxc/src/oxlint/rules/no-bigint-literals.ts index cc274b7d66..2139717de3 100644 --- a/repos/effect/packages/tools/oxc/src/oxlint/rules/no-bigint-literals.ts +++ b/repos/effect/packages/tools/oxc/src/oxlint/rules/no-bigint-literals.ts @@ -1,4 +1,4 @@ -import type { CreateRule, Visitor } from "oxlint" +import type { CreateRule, Visitor } from "@oxlint/plugins" const rule: CreateRule = { meta: { diff --git a/repos/effect/packages/tools/oxc/src/oxlint/rules/no-import-from-barrel-package.ts b/repos/effect/packages/tools/oxc/src/oxlint/rules/no-import-from-barrel-package.ts index ab337ad40e..1921966e24 100644 --- a/repos/effect/packages/tools/oxc/src/oxlint/rules/no-import-from-barrel-package.ts +++ b/repos/effect/packages/tools/oxc/src/oxlint/rules/no-import-from-barrel-package.ts @@ -1,6 +1,6 @@ +import type { CreateRule, ESTree, Visitor } from "@oxlint/plugins" import * as fs from "node:fs" import * as path from "node:path" -import type { CreateRule, ESTree, Visitor } from "oxlint" interface RuleOptions { checkPatterns?: Array diff --git a/repos/effect/packages/tools/oxc/src/oxlint/rules/no-js-extension-imports.ts b/repos/effect/packages/tools/oxc/src/oxlint/rules/no-js-extension-imports.ts index f09551308b..4dcf2def0e 100644 --- a/repos/effect/packages/tools/oxc/src/oxlint/rules/no-js-extension-imports.ts +++ b/repos/effect/packages/tools/oxc/src/oxlint/rules/no-js-extension-imports.ts @@ -1,4 +1,4 @@ -import type { CreateRule, ESTree, Fixer, Visitor } from "oxlint" +import type { CreateRule, ESTree, Fixer, Visitor } from "@oxlint/plugins" const jsExtensions = [".js", ".jsx", ".mjs", ".cjs"] const extensionMap: Record = { diff --git a/repos/effect/packages/tools/oxc/src/oxlint/rules/no-opaque-instance-fields.ts b/repos/effect/packages/tools/oxc/src/oxlint/rules/no-opaque-instance-fields.ts index d4985d16f7..4dbd4c1f7d 100644 --- a/repos/effect/packages/tools/oxc/src/oxlint/rules/no-opaque-instance-fields.ts +++ b/repos/effect/packages/tools/oxc/src/oxlint/rules/no-opaque-instance-fields.ts @@ -1,4 +1,4 @@ -import type { CreateRule, ESTree, Visitor } from "oxlint" +import type { CreateRule, ESTree, Visitor } from "@oxlint/plugins" const SCHEMA_SOURCES = new Set(["effect", "effect/Schema"]) const SCHEMA_NAMESPACE_SOURCES = new Set(["effect/Schema"]) diff --git a/repos/effect/packages/tools/oxc/src/oxlint/rules/no-unused-internal.ts b/repos/effect/packages/tools/oxc/src/oxlint/rules/no-unused-internal.ts index 55de8c187e..3fdbd6c822 100644 --- a/repos/effect/packages/tools/oxc/src/oxlint/rules/no-unused-internal.ts +++ b/repos/effect/packages/tools/oxc/src/oxlint/rules/no-unused-internal.ts @@ -1,6 +1,6 @@ +import type { CreateRule, ESTree, Visitor } from "@oxlint/plugins" import * as fs from "node:fs" import * as path from "node:path" -import type { CreateRule, ESTree, Visitor } from "oxlint" import ts from "typescript" interface InternalExport { @@ -286,6 +286,33 @@ function getExports( return fileInfos.get(fileName)?.internalExports.get(exportName) ?? [] } +function getReExportedInternals( + fileInfos: ReadonlyMap, + fileInfo: FileInfo, + workspacePackages: ReadonlyMap, + specifier: ts.ExportSpecifier +): ReadonlyArray { + const exportDeclaration = specifier.parent.parent + const importedName = (specifier.propertyName ?? specifier.name).text + if ( + exportDeclaration.moduleSpecifier !== undefined && + ts.isStringLiteral(exportDeclaration.moduleSpecifier) + ) { + const importedFile = resolveModule( + exportDeclaration.moduleSpecifier.text, + fileInfo.sourceFile.fileName, + workspacePackages + ) + return getExports(fileInfos, importedFile, importedName) + } + + const imported = fileInfo.imports.get(importedName) + return [ + ...(fileInfo.internalExports.get(importedName) ?? []), + ...getExports(fileInfos, imported?.fileName, imported?.importedName ?? "") + ] +} + function markUsed(exports: ReadonlyArray, node: ts.Node) { for (const internal of exports) { if (!isInside(node, internal.declaration)) { @@ -348,6 +375,17 @@ function collectFileInfo( continue } + if ( + !isInternalFile && + ts.isExportDeclaration(statement) && + !hasInternalApiJSDoc(statement) && + statement.exportClause !== undefined && + ts.isNamedExports(statement.exportClause) + ) { + fileInfo.publicDeclarations.push(statement) + continue + } + if (!hasExportModifier(statement)) continue const names = getTopLevelDeclarationNameNodes(statement) @@ -385,9 +423,15 @@ function markNamespaceReference( markUsed(getExports(fileInfos, importedFile, name.text), name) } -function scanUsage(fileInfos: ReadonlyMap, fileInfo: FileInfo) { +function scanUsage( + fileInfos: ReadonlyMap, + fileInfo: FileInfo, + workspacePackages: ReadonlyMap +) { const visit = (node: ts.Node) => { - if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.expression) && ts.isIdentifier(node.name)) { + if (ts.isExportSpecifier(node)) { + markUsed(getReExportedInternals(fileInfos, fileInfo, workspacePackages, node), node) + } else if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.expression) && ts.isIdentifier(node.name)) { markNamespaceReference(fileInfos, fileInfo, node.expression, node.name) } else if (ts.isQualifiedName(node) && ts.isIdentifier(node.left)) { markNamespaceReference(fileInfos, fileInfo, node.left, node.right) @@ -529,7 +573,8 @@ function scanPublicSignature( fileInfos: ReadonlyMap, fileInfo: FileInfo, diagnostics: Array, - node: ts.Node + node: ts.Node, + workspacePackages: ReadonlyMap ) { if (ts.isFunctionDeclaration(node)) { scanFunctionLikeSignature(fileInfos, fileInfo, diagnostics, node) @@ -562,6 +607,21 @@ function scanPublicSignature( for (const member of node.members) { scanClassMemberSignature(fileInfos, fileInfo, diagnostics, member) } + } else if ( + ts.isExportDeclaration(node) && + node.exportClause !== undefined && + ts.isNamedExports(node.exportClause) + ) { + for (const specifier of node.exportClause.elements) { + for (const internal of getReExportedInternals(fileInfos, fileInfo, workspacePackages, specifier)) { + const name = specifier.propertyName ?? specifier.name + diagnostics.push({ + fileName: fileInfo.fileName, + range: [name.getStart(), name.getEnd()], + message: `Do not re-export @internal export "${internal.name}" from a public module` + }) + } + } } } @@ -579,7 +639,7 @@ function analyze(cwd: string): Analysis { } for (const fileInfo of fileInfos.values()) { - scanUsage(fileInfos, fileInfo) + scanUsage(fileInfos, fileInfo, workspacePackages) } const diagnosticsByFile = new Map>() @@ -587,7 +647,7 @@ function analyze(cwd: string): Analysis { for (const fileInfo of fileInfos.values()) { const diagnostics: Array = [] for (const declaration of fileInfo.publicDeclarations) { - scanPublicSignature(fileInfos, fileInfo, diagnostics, declaration) + scanPublicSignature(fileInfos, fileInfo, diagnostics, declaration, workspacePackages) } for (const diagnostic of diagnostics) { addFileDiagnostic(diagnosticsByFile, diagnostic) @@ -619,7 +679,7 @@ const rule: CreateRule = { meta: { type: "problem", docs: { - description: "Disallow unused @internal exports and references to @internal exports from public type signatures" + description: "Disallow unused @internal exports, public re-exports, and references from public type signatures" } }, create(context) { diff --git a/repos/effect/packages/tools/oxc/test/jsdocs.test.ts b/repos/effect/packages/tools/oxc/test/jsdocs.test.ts deleted file mode 100644 index 9f0cea50ec..0000000000 --- a/repos/effect/packages/tools/oxc/test/jsdocs.test.ts +++ /dev/null @@ -1,75 +0,0 @@ -import rule from "@effect/oxc/oxlint/rules/jsdocs" -import * as crypto from "node:crypto" -import * as fs from "node:fs" -import * as os from "node:os" -import * as path from "node:path" -import { describe, expect, it } from "vitest" -import { createTestContext } from "./utils.ts" - -function hash(source: string): string { - return crypto.createHash("sha256").update(source).digest("hex") -} - -function run(source: string, cwd: string, filename: string, model = ".data/jsdocs.json") { - const { context, errors } = createTestContext({ sourceCode: source, cwd, filename, ruleOptions: [{ model }] }) - const visitors = rule.create(context as never) - visitors.Program?.({ type: "Program", range: [0, source.length] } as never) - return errors -} - -describe("jsdocs", () => { - it("skips when the model is missing", () => { - const cwd = fs.mkdtempSync(path.join(os.tmpdir(), "jsdocs-rule-")) - const errors = run("export const a = 1\n", cwd, path.join(cwd, "src/Foo.ts")) - expect(errors).toEqual([]) - }) - - it("reports invalid model files", () => { - const cwd = fs.mkdtempSync(path.join(os.tmpdir(), "jsdocs-rule-")) - fs.mkdirSync(path.join(cwd, ".data"), { recursive: true }) - fs.writeFileSync(path.join(cwd, ".data/jsdocs.json"), "{") - const errors = run("export const a = 1\n", cwd, path.join(cwd, "src/Foo.ts")) - expect(errors.map((error) => error.message)).toEqual([expect.stringContaining("Invalid jsdocs model")]) - }) - - it("reports stale model files", () => { - const cwd = fs.mkdtempSync(path.join(os.tmpdir(), "jsdocs-rule-")) - fs.mkdirSync(path.join(cwd, ".data"), { recursive: true }) - fs.writeFileSync( - path.join(cwd, ".data/jsdocs.json"), - JSON.stringify({ - version: 2, - generatedBy: "@effect/jsdocs", - generatedAt: "now", - files: [{ file: "src/Foo.ts", hash: "old", diagnostics: [], declarations: [], namespaces: [] }], - apis: [] - }) - ) - const errors = run("export const a = 1\n", cwd, path.join(cwd, "src/Foo.ts")) - expect(errors.map((error) => error.message)).toEqual(["JSDoc model is stale for this file; run `pnpm jsdocs`"]) - }) - - it("reports model diagnostics", () => { - const cwd = fs.mkdtempSync(path.join(os.tmpdir(), "jsdocs-rule-")) - const source = "export const a = 1\n" - fs.mkdirSync(path.join(cwd, ".data"), { recursive: true }) - fs.writeFileSync( - path.join(cwd, ".data/jsdocs.json"), - JSON.stringify({ - version: 2, - generatedBy: "@effect/jsdocs", - generatedAt: "now", - apis: [], - files: [{ - file: "src/Foo.ts", - hash: hash(source), - diagnostics: [{ code: "missing-jsdoc", message: "Public JSDoc is required", range: [0, 6] }], - declarations: [], - namespaces: [] - }] - }) - ) - const errors = run(source, cwd, path.join(cwd, "src/Foo.ts")) - expect(errors.map((error) => error.message)).toEqual(["Public JSDoc is required"]) - }) -}) diff --git a/repos/effect/packages/tools/oxc/test/no-unused-internal.test.ts b/repos/effect/packages/tools/oxc/test/no-unused-internal.test.ts index a14ba07d6d..2322ee8b02 100644 --- a/repos/effect/packages/tools/oxc/test/no-unused-internal.test.ts +++ b/repos/effect/packages/tools/oxc/test/no-unused-internal.test.ts @@ -86,6 +86,21 @@ export interface Public { expect(run(cwd, "packages/foo/src/Internal.ts")).toEqual([]) }) + it("reports public re-exports of @internal exports", () => { + const cwd = writeFixture({ + "packages/foo/src/Internal.ts": `/** @internal */ +export const internal = 1 +`, + "packages/foo/src/Public.ts": `export { internal } from "./Internal.ts" +` + }) + + expect(run(cwd, "packages/foo/src/Public.ts")).toEqual([ + `Do not re-export @internal export "internal" from a public module` + ]) + expect(run(cwd, "packages/foo/src/Internal.ts")).toEqual([]) + }) + it("ignores @internal class member type signatures", () => { const cwd = writeFixture({ "packages/foo/src/Internal.ts": `/** @internal */ diff --git a/repos/effect/packages/tools/oxc/test/utils.ts b/repos/effect/packages/tools/oxc/test/utils.ts index c26f6c1123..002c8c956b 100644 --- a/repos/effect/packages/tools/oxc/test/utils.ts +++ b/repos/effect/packages/tools/oxc/test/utils.ts @@ -1,4 +1,4 @@ -import type { CreateRule, Visitor } from "oxlint" +import type { CreateRule, Visitor } from "@oxlint/plugins" export interface ReportedError { node: unknown diff --git a/repos/effect/packages/tools/oxc/tsconfig.json b/repos/effect/packages/tools/oxc/tsconfig.json index 997ff419ca..b512ffd916 100644 --- a/repos/effect/packages/tools/oxc/tsconfig.json +++ b/repos/effect/packages/tools/oxc/tsconfig.json @@ -1,5 +1,5 @@ { - "$schema": "http://json.schemastore.org/tsconfig", + "$schema": "https://json.schemastore.org/tsconfig", "extends": "../../../tsconfig.base.json", "include": ["src"], "compilerOptions": { diff --git a/repos/effect/packages/tools/oxc/vitest.config.ts b/repos/effect/packages/tools/oxc/vitest.config.ts deleted file mode 100644 index c8a52c1826..0000000000 --- a/repos/effect/packages/tools/oxc/vitest.config.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { mergeConfig, type ViteUserConfig } from "vitest/config" -import shared from "../../../vitest.shared.ts" - -const config: ViteUserConfig = {} - -export default mergeConfig(shared, config) diff --git a/repos/effect/packages/tools/utils/docgen.json b/repos/effect/packages/tools/utils/docgen.json deleted file mode 100644 index 4dd36175ad..0000000000 --- a/repos/effect/packages/tools/utils/docgen.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "$schema": "../../../node_modules/@effect/docgen/schema.json", - "exclude": ["**/*.ts"] -} diff --git a/repos/effect/packages/tools/utils/package.json b/repos/effect/packages/tools/utils/package.json index 0dbb5f5475..d5eef440df 100644 --- a/repos/effect/packages/tools/utils/package.json +++ b/repos/effect/packages/tools/utils/package.json @@ -8,7 +8,7 @@ "homepage": "https://effect.website", "repository": { "type": "git", - "url": "https://github.com/Effect-TS/effect-smol.git", + "url": "https://github.com/Effect-TS/effect.git", "directory": "packages/tools/utils" }, "sideEffects": [], @@ -42,11 +42,8 @@ }, "scripts": { "build": "tsc -b tsconfig.json && pnpm babel", - "build:tsgo": "tsgo -b tsconfig.json && pnpm babel", "babel": "babel dist --plugins annotate-pure-calls --out-dir dist --source-maps", - "check": "tsc -b tsconfig.json", - "test": "vitest", - "coverage": "vitest --coverage" + "check": "tsc -b tsconfig.json" }, "dependencies": { "@effect/platform-node": "workspace:^", @@ -54,6 +51,6 @@ "glob": "^13.0.6" }, "devDependencies": { - "@types/node": "^25.7.0" + "@types/node": "^26.1.2" } } diff --git a/repos/effect/packages/tools/utils/src/Codegen.ts b/repos/effect/packages/tools/utils/src/Codegen.ts index 688ffbd370..5c948a3e16 100644 --- a/repos/effect/packages/tools/utils/src/Codegen.ts +++ b/repos/effect/packages/tools/utils/src/Codegen.ts @@ -118,7 +118,7 @@ export interface BarrelFile { /** * Service interface for discovering annotated barrel files and regenerating their export contents. * - * @category models + * @category services * @since 4.0.0 */ export interface BarrelGenerator { diff --git a/repos/effect/packages/tools/utils/src/Glob.ts b/repos/effect/packages/tools/utils/src/Glob.ts index d43caa29d8..0fb6a11d89 100644 --- a/repos/effect/packages/tools/utils/src/Glob.ts +++ b/repos/effect/packages/tools/utils/src/Glob.ts @@ -28,7 +28,7 @@ export class GlobError extends Data.TaggedError("GlobError")<{ /** * Service interface for matching filesystem paths with glob patterns. * - * @category models + * @category services * @since 4.0.0 */ export interface Glob { diff --git a/repos/effect/packages/tools/utils/src/commands/codegen.ts b/repos/effect/packages/tools/utils/src/commands/codegen.ts index f225f5f159..0d30fc57a0 100644 --- a/repos/effect/packages/tools/utils/src/commands/codegen.ts +++ b/repos/effect/packages/tools/utils/src/commands/codegen.ts @@ -38,7 +38,7 @@ export const codegen = Command.make("codegen", { const files = yield* generator.discoverFiles(config.pattern, path.resolve(config.cwd)) yield* Effect.forEach(files, (file) => generator.processFile(file), { - concurrency: "inherit", + concurrency: "unbounded", discard: true }) })).pipe(Command.provide(CodegenLayer)) diff --git a/repos/effect/packages/tools/utils/tsconfig.json b/repos/effect/packages/tools/utils/tsconfig.json index 739eb6b55d..f9e09522b0 100644 --- a/repos/effect/packages/tools/utils/tsconfig.json +++ b/repos/effect/packages/tools/utils/tsconfig.json @@ -1,5 +1,5 @@ { - "$schema": "http://json.schemastore.org/tsconfig", + "$schema": "https://json.schemastore.org/tsconfig", "extends": "../../../tsconfig.base.json", "include": ["src"], "references": [ diff --git a/repos/effect/packages/vitest/CHANGELOG.md b/repos/effect/packages/vitest/CHANGELOG.md index 9600ca9820..e889eb0d26 100644 --- a/repos/effect/packages/vitest/CHANGELOG.md +++ b/repos/effect/packages/vitest/CHANGELOG.md @@ -1,5 +1,139 @@ # @effect/vitest +## 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 + +### Minor Changes + +- [#6668](https://github.com/Effect-TS/effect/pull/6668) [`83c7497`](https://github.com/Effect-TS/effect/commit/83c74972f67cd05513897e43892b63ef48fb7cc1) Thanks @tim-smart! - 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. + +### Patch Changes + +- [#6843](https://github.com/Effect-TS/effect/pull/6843) [`4a394bd`](https://github.com/Effect-TS/effect/commit/4a394bd2980c357bbadbb1684172189511c33381) Thanks @fubhy! - Ensure `throws` and `throwsAsync` fail when the supplied operation returns or resolves without throwing. + +- [#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 + +- [#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 + +- [#6444](https://github.com/Effect-TS/effect/pull/6444) [`eb3ea7a`](https://github.com/Effect-TS/effect/commit/eb3ea7a75ef140e78a939f8f58cb8c176d6be0c9) Thanks @rvaccone! - 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`. + +- 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 + +- [#6416](https://github.com/Effect-TS/effect/pull/6416) [`a79f5ec`](https://github.com/Effect-TS/effect/commit/a79f5ec4f2d9c7a4001eed15452c94e35c0ce4c9) Thanks @coyaSONG! - Fix record-form property tests to convert Schema values to FastCheck arbitraries. + +- Updated dependencies [[`8ce4795`](https://github.com/Effect-TS/effect/commit/8ce4795ccbaebca4292757db568c005a992546a4), [`80b539f`](https://github.com/Effect-TS/effect/commit/80b539f8aba68f478c75c35c2b4140c4ffc4fada), [`88a54cc`](https://github.com/Effect-TS/effect/commit/88a54cc341006e3ebcb13482c618f62a680ce199), [`e6e6dba`](https://github.com/Effect-TS/effect/commit/e6e6dba6e9d86e7c2ad27dcedf289db76a19697f), [`bfb203e`](https://github.com/Effect-TS/effect/commit/bfb203e95aa439f731acad37fc3a9a831a190f1c), [`2e9a34a`](https://github.com/Effect-TS/effect/commit/2e9a34ac2bece4f3a206160480c991e3841dc67a), [`55d4eb3`](https://github.com/Effect-TS/effect/commit/55d4eb34f2c64d54f6a25a305b5c5438ebd7934e), [`bddb010`](https://github.com/Effect-TS/effect/commit/bddb010eac3d4436cb094edbbee7460c5440c162), [`a328835`](https://github.com/Effect-TS/effect/commit/a328835e50d76bc96648a1c1550456e8c9f81210), [`5560d05`](https://github.com/Effect-TS/effect/commit/5560d05aa6abdd29466d9c3412cc5e648b0adbde), [`8f6e3ad`](https://github.com/Effect-TS/effect/commit/8f6e3adb185b16e8820b98c509b308086f7ff1af), [`46997fa`](https://github.com/Effect-TS/effect/commit/46997fa60401f5e3c93daa4b61f7df8e31caaab4), [`9e6e12d`](https://github.com/Effect-TS/effect/commit/9e6e12d75c118cd265496f2880490d1f33a5c8bf), [`3394b93`](https://github.com/Effect-TS/effect/commit/3394b93d97d6f24fc38670641d1490289ffca7f1), [`febeabc`](https://github.com/Effect-TS/effect/commit/febeabc3f7c31094da000a23edeaabfe2ab00a38), [`54161c9`](https://github.com/Effect-TS/effect/commit/54161c98f6f3569e0c31842f54e6a257f9421c4c), [`385f7a4`](https://github.com/Effect-TS/effect/commit/385f7a4ee4a7359928597ea56d151dbaf5eb5802), [`7eea4d0`](https://github.com/Effect-TS/effect/commit/7eea4d0b73ec554915d7066a71f46326ce2ba45f), [`7543afe`](https://github.com/Effect-TS/effect/commit/7543afea6f4d97d1f1ad876224323838a48daadd), [`44b9cf3`](https://github.com/Effect-TS/effect/commit/44b9cf3d240d726997b4bbcd0ede48e074d3c456), [`7eea4d0`](https://github.com/Effect-TS/effect/commit/7eea4d0b73ec554915d7066a71f46326ce2ba45f), [`0a8aa6a`](https://github.com/Effect-TS/effect/commit/0a8aa6acb90a72b91c24d17133c950e4cacd8abd), [`c8d9fcf`](https://github.com/Effect-TS/effect/commit/c8d9fcf7b030f7c474effbab2764ce7aee1c7209), [`9ca7f9a`](https://github.com/Effect-TS/effect/commit/9ca7f9a69363e4485645966d5a93b8f9597c5206), [`e7aca89`](https://github.com/Effect-TS/effect/commit/e7aca894bb32fbb785b5830837e6061c415a6015), [`55d7560`](https://github.com/Effect-TS/effect/commit/55d75609b8acf8a1b54c1b1c7fbbb65ec741aa3e), [`f809189`](https://github.com/Effect-TS/effect/commit/f809189ddf6b6011ba43a9901baaa734e315da2a), [`88a54cc`](https://github.com/Effect-TS/effect/commit/88a54cc341006e3ebcb13482c618f62a680ce199), [`0ebdbe7`](https://github.com/Effect-TS/effect/commit/0ebdbe74463dc84385956d0b1e8c2b79ebab5400), [`7517d09`](https://github.com/Effect-TS/effect/commit/7517d09f12a0b183a81bd425962c4e280a68b05d), [`212493b`](https://github.com/Effect-TS/effect/commit/212493b9a1eb98cd1ef6959c707a2e5784a5ae91), [`88a54cc`](https://github.com/Effect-TS/effect/commit/88a54cc341006e3ebcb13482c618f62a680ce199), [`80ea8cb`](https://github.com/Effect-TS/effect/commit/80ea8cb9222ca73f564c8267ab2f82966fea027a), [`8df19f4`](https://github.com/Effect-TS/effect/commit/8df19f4fe81d90cc33ace88b9a77e5534f82d604)]: + - effect@4.0.0-beta.99 + +## 4.0.0-beta.98 + +### Patch Changes + +- Updated dependencies [[`989603b`](https://github.com/Effect-TS/effect-smol/commit/989603b60ab1197b64acf214208e0d370cd1f842), [`214c458`](https://github.com/Effect-TS/effect-smol/commit/214c458084bb6995d543cd37d1055f24be3d454e), [`a037273`](https://github.com/Effect-TS/effect-smol/commit/a0372736ac34796969b051bbba4717d7983f1ebe), [`97fdaa9`](https://github.com/Effect-TS/effect-smol/commit/97fdaa9c1f522c65e579365d314a07878e2b904f), [`b24d248`](https://github.com/Effect-TS/effect-smol/commit/b24d248c8df44222ce642087cde2bd859a2dc709), [`19c222c`](https://github.com/Effect-TS/effect-smol/commit/19c222cac2353a3d7b7733caecb00556fffe9a5c), [`eec85dd`](https://github.com/Effect-TS/effect-smol/commit/eec85ddba09ea326fd268ee33eeffd47e50d4671), [`0082f4f`](https://github.com/Effect-TS/effect-smol/commit/0082f4f74fb139fd578f87f0a790e845133983dc), [`8849052`](https://github.com/Effect-TS/effect-smol/commit/884905232d1e9a365e046d8dde27bf9c5707f57f), [`c15e16a`](https://github.com/Effect-TS/effect-smol/commit/c15e16ad130d1fbde25d912b7ac55995066cb35b), [`01d00a3`](https://github.com/Effect-TS/effect-smol/commit/01d00a3abfbf1f37996cdbe738ea5137c646cdd7), [`8bd4589`](https://github.com/Effect-TS/effect-smol/commit/8bd458975a1b3a8ed042eccf317b93d28ded91e7), [`0082f4f`](https://github.com/Effect-TS/effect-smol/commit/0082f4f74fb139fd578f87f0a790e845133983dc), [`6e08428`](https://github.com/Effect-TS/effect-smol/commit/6e08428d980501b856f846ad3f3f0e4ea46e7786), [`388dcf9`](https://github.com/Effect-TS/effect-smol/commit/388dcf953f65d317547f34d40e6443c5f264205f), [`2b7ce2b`](https://github.com/Effect-TS/effect-smol/commit/2b7ce2b513e7ec2a77822f1116dc6ffb6ba93f4e), [`87bea7e`](https://github.com/Effect-TS/effect-smol/commit/87bea7e16259246f3bcdf565446394751abca953), [`ce38dc3`](https://github.com/Effect-TS/effect-smol/commit/ce38dc33bda805a684432cca071f4dc3c6b9a1ba), [`a807cd1`](https://github.com/Effect-TS/effect-smol/commit/a807cd170341deca8a1cfb52c4222585f2431bb9), [`fd8a356`](https://github.com/Effect-TS/effect-smol/commit/fd8a356f06a8c9ce4e7e0a13fc4021c178ed31de), [`c2a5edc`](https://github.com/Effect-TS/effect-smol/commit/c2a5edc3abd31ad5bc123362bc1213e03e4095c3), [`5946da3`](https://github.com/Effect-TS/effect-smol/commit/5946da3804a1be5e752b05b96bd058cdba50a1bf), [`4ae0c5f`](https://github.com/Effect-TS/effect-smol/commit/4ae0c5ffcbe6c56ddfcb05c639112a079483539e), [`5b2a0bc`](https://github.com/Effect-TS/effect-smol/commit/5b2a0bceea3a28a33a58555210c90a415dc74a76), [`72ac585`](https://github.com/Effect-TS/effect-smol/commit/72ac585884befde6af9208da738699a93f1bae79), [`5e8c1b8`](https://github.com/Effect-TS/effect-smol/commit/5e8c1b82bfafa121311f987a49ab75395e3647a7), [`0f9c078`](https://github.com/Effect-TS/effect-smol/commit/0f9c07841b04183f485ee6e6458de73b290b09f5)]: + - effect@4.0.0-beta.98 + +## 4.0.0-beta.97 + +### Patch Changes + +- Updated dependencies []: + - effect@4.0.0-beta.97 + +## 4.0.0-beta.96 + +### Patch Changes + +- Updated dependencies [[`1503f45`](https://github.com/Effect-TS/effect-smol/commit/1503f45cb5bb2a74f4705252ec505a1f0ade7e62), [`57fe793`](https://github.com/Effect-TS/effect-smol/commit/57fe79316ffbc380b30626a168981fb26ae97459), [`0c2f78f`](https://github.com/Effect-TS/effect-smol/commit/0c2f78f695ec474e1ff5474da183577975e418f5), [`0c2f78f`](https://github.com/Effect-TS/effect-smol/commit/0c2f78f695ec474e1ff5474da183577975e418f5), [`0c2f78f`](https://github.com/Effect-TS/effect-smol/commit/0c2f78f695ec474e1ff5474da183577975e418f5), [`97f29df`](https://github.com/Effect-TS/effect-smol/commit/97f29df457f7ffd07cfb4b379315c12c086af805)]: + - effect@4.0.0-beta.96 + +## 4.0.0-beta.95 + +### Patch Changes + +- Updated dependencies [[`a482442`](https://github.com/Effect-TS/effect-smol/commit/a482442abdeb490e9652b854ec3495e4aa7273e7), [`fbefa85`](https://github.com/Effect-TS/effect-smol/commit/fbefa850fab2f0a302c20614496aeaaa2a8b5590), [`0b4a32f`](https://github.com/Effect-TS/effect-smol/commit/0b4a32f4260f0d8500942a133001b0d349328102), [`18a49e1`](https://github.com/Effect-TS/effect-smol/commit/18a49e1786679456258002ff9397faf02f678c2d), [`266cb90`](https://github.com/Effect-TS/effect-smol/commit/266cb90bb2c17aabc40563c32db334f09ba3d74b), [`912f095`](https://github.com/Effect-TS/effect-smol/commit/912f095a34572bbd3cedf6edb27878443e3e4a95), [`a6718f9`](https://github.com/Effect-TS/effect-smol/commit/a6718f9e00a15ca903b0732da46116cbf3d6aca7), [`bef5154`](https://github.com/Effect-TS/effect-smol/commit/bef51540a243aa2f872a00c01d0cd58b7a769baa), [`18e0564`](https://github.com/Effect-TS/effect-smol/commit/18e0564bd0f8ebbdfcaf1e2c21529948e9e4a81d), [`fb50f14`](https://github.com/Effect-TS/effect-smol/commit/fb50f14fc3657c1973785aa5b72ecf0b0d28e0b2)]: + - effect@4.0.0-beta.95 + +## 4.0.0-beta.94 + +### Patch Changes + +- Updated dependencies [[`95a0e9b`](https://github.com/Effect-TS/effect-smol/commit/95a0e9bb62797af0e81c9998773405f248f218c5), [`a0a3490`](https://github.com/Effect-TS/effect-smol/commit/a0a3490bbce765f199d8e077aceac504f0462e63), [`f11ce73`](https://github.com/Effect-TS/effect-smol/commit/f11ce73af60823754dc24194f4ffc561b9ea1c2d), [`ff30b6e`](https://github.com/Effect-TS/effect-smol/commit/ff30b6e7c2c63ffc56a4c5818d6d86b01b5ad528), [`1caab3c`](https://github.com/Effect-TS/effect-smol/commit/1caab3cc30f626efbf15e59d74f539a487e5c85c), [`aa80c47`](https://github.com/Effect-TS/effect-smol/commit/aa80c4775a04db87553e5568764cab7e32a72814), [`c2ae4fc`](https://github.com/Effect-TS/effect-smol/commit/c2ae4fce2f03a4cd1861c2b1179da7df656e662d), [`a0a3490`](https://github.com/Effect-TS/effect-smol/commit/a0a3490bbce765f199d8e077aceac504f0462e63)]: + - effect@4.0.0-beta.94 + +## 4.0.0-beta.93 + +### Patch Changes + +- Updated dependencies [[`00652fe`](https://github.com/Effect-TS/effect-smol/commit/00652fe95c18f87208e91343eb8bf218faa2f677), [`6c58167`](https://github.com/Effect-TS/effect-smol/commit/6c5816746eaf91d2a3c7c899c5720809fa230ae3), [`2bc5415`](https://github.com/Effect-TS/effect-smol/commit/2bc541501a7ef89e542d7cb98e96beb53cd205cc), [`e11cccc`](https://github.com/Effect-TS/effect-smol/commit/e11cccc7d5fe631abccc7d6e3bd296938de0fa2e), [`ba7e77e`](https://github.com/Effect-TS/effect-smol/commit/ba7e77e046b8641a3a4e9750bb88ca4a1d063d3f), [`5713ee7`](https://github.com/Effect-TS/effect-smol/commit/5713ee7edbc3054efde407b2286bbfd45bbc6e1c)]: + - effect@4.0.0-beta.93 + +## 4.0.0-beta.92 + +### Patch Changes + +- Updated dependencies [[`affdc13`](https://github.com/Effect-TS/effect-smol/commit/affdc139045cc325dce321a84a580fdc1b2da7b9)]: + - effect@4.0.0-beta.92 + +## 4.0.0-beta.91 + +### Patch Changes + +- Updated dependencies [[`b135b25`](https://github.com/Effect-TS/effect-smol/commit/b135b2517fca9e7839734ace3699a7dfa75b9075), [`aaa21a3`](https://github.com/Effect-TS/effect-smol/commit/aaa21a369a171c600db294f2a4f640583043e150), [`3475ee6`](https://github.com/Effect-TS/effect-smol/commit/3475ee6c2bda6b05c6d7a12ce30c8bb840b5b1a6)]: + - effect@4.0.0-beta.91 + ## 4.0.0-beta.90 ### Patch Changes diff --git a/repos/effect/packages/vitest/README.md b/repos/effect/packages/vitest/README.md index 369a16cc07..a45361d4c5 100644 --- a/repos/effect/packages/vitest/README.md +++ b/repos/effect/packages/vitest/README.md @@ -6,7 +6,7 @@ In this guide, we'll walk you through setting up the necessary dependencies and # Requirements -First, ensure you have [`vitest`](https://vitest.dev/guide/) installed (version `1.6.0` or later). +First, ensure you have a supported [`vitest`](https://vitest.dev/guide/) version installed (`^4.1.0`). ```sh pnpm add -D vitest @@ -28,13 +28,13 @@ import { it } from "@effect/vitest" This import enhances the standard `it` function from `vitest` with several powerful features, including: -| Feature | Description | -| --------------- | ------------------------------------------------------------------------------------------------------ | -| `it.effect` | Automatically injects a `TestContext` (e.g., `TestClock`) when running a test. | -| `it.live` | Runs the test with the live Effect environment. | -| `it.scoped` | Allows running an Effect program that requires a `Scope`. | -| `it.scopedLive` | Combines the features of `scoped` and `live`, using a live Effect environment that requires a `Scope`. | -| `it.flakyTest` | Facilitates the execution of tests that might occasionally fail. | +| Feature | Description | +| -------------- | --------------------------------------------------------------------------------------------------- | +| `it.effect` | Runs a scoped test with test services such as `TestClock` and `TestConsole`. | +| `it.live` | Runs a scoped test with the live Effect environment. | +| `it.layer` | Shares a `Layer` between multiple tests. | +| `it.prop` | Runs property tests using Effect `Schema` values or FastCheck arbitraries. | +| `it.flakyTest` | Retries an Effect that might occasionally fail until it succeeds or reaches the configured timeout. | # Writing Tests with `it.effect` @@ -48,7 +48,7 @@ import { it } from "@effect/vitest" it.effect("test name", () => EffectContainingAssertions, timeout: number | TestOptions = 5_000) ``` -`it.effect` automatically provides a `TestContext`, allowing access to services like [`TestClock`](#using-the-testclock). +`it.effect` automatically provides the Effect test services, including [`TestClock`](#using-the-testclock), and a fresh `Scope` for each test. The scope is closed when the test finishes. ## Testing Successful Operations @@ -110,7 +110,7 @@ it.effect("test failure as Exit", () => ## Using the TestClock -When writing tests with `it.effect`, a `TestContext` is automatically provided. This context gives access to various testing services, including the [`TestClock`](https://effect.website/docs/guides/testing/testclock), which allows you to simulate the passage of time in your tests. +When writing tests with `it.effect`, Effect test services are automatically provided. These include the [`TestClock`](https://effect.website/docs/guides/testing/testclock), which allows you to simulate the passage of time in your tests. **Note**: If you want to use the real-time clock (instead of the simulated one), you can switch to `it.live`. @@ -126,7 +126,8 @@ Here are examples that demonstrate how you can work with time in your tests usin ```ts import { it } from "@effect/vitest" -import { Clock, Effect, TestClock } from "effect" +import { Clock, Effect } from "effect" +import { TestClock } from "effect/testing" // Effect to log the current time const logNow = Effect.gen(function*() { @@ -212,7 +213,7 @@ When adding new failing tests, you might not be able to fix them right away. Ins import { it } from "@effect/vitest" import { Effect, Exit } from "effect" -function divide(a: number, b: number): number { +function divide(a: number, b: number) { if (b === 0) return Effect.fail("Cannot divide by zero") return Effect.succeed(a / b) } @@ -246,7 +247,7 @@ it.effect("providing a logger displays a log", () => Effect.gen(function*() { yield* Effect.log("it.effect with custom logger") // Log will be displayed }).pipe( - Effect.provide(Logger.pretty) // Providing a pretty logger for log output + Effect.provide(Logger.layer([Logger.consolePretty()])) // Providing a pretty logger for log output )) // This test runs using `it.live`, which enables logging by default @@ -256,11 +257,11 @@ it.live("it.live displays a log", () => })) ``` -# Writing Tests with `it.scoped` +# Resource Safety and Scope -The `it.scoped` method is used for tests that involve `Effect` programs needing a `Scope`. A `Scope` ensures that any resources your test acquires are managed properly, meaning they will be released when the test completes. This helps prevent resource leaks and guarantees test isolation. +Both `it.effect` and `it.live` provide a fresh `Scope` and close it after each test. Test bodies can therefore use scoped resources directly. Do not wrap the test body in `Effect.scoped`, because the test runner already manages its scope. -**Example** (Using `it.scoped` to Manage Resource Lifecycle) +**Example** (Managing a Resource Lifecycle) ```ts import { it } from "@effect/vitest" @@ -273,17 +274,10 @@ const release = Console.log("release resource") // Defining a resource that requires proper management const resource = Effect.acquireRelease(acquire, () => release) -// Incorrect usage: This will result in a type error because it lacks a scope it.effect("run with scope", () => Effect.gen(function*() { yield* resource })) - -// Correct usage: Using 'it.scoped' to manage the scope correctly -it.scoped("run with scope", () => - Effect.gen(function*() { - yield* resource - })) ``` # Writing Tests with `it.flakyTest` diff --git a/repos/effect/packages/vitest/docgen.json b/repos/effect/packages/vitest/docgen.json deleted file mode 100644 index d06667eae0..0000000000 --- a/repos/effect/packages/vitest/docgen.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "$schema": "../../node_modules/@effect/docgen/schema.json", - "srcLink": "https://github.com/Effect-TS/effect/tree/main/packages/vitest/src/", - "exclude": ["src/internal/**/*.ts"], - "tscExecutable": "tsgo", - "examplesCompilerOptions": { - "noEmit": true, - "strict": true, - "skipLibCheck": true, - "moduleResolution": "Bundler", - "module": "ES2022", - "target": "ES2022", - "lib": ["ES2022", "DOM"], - "rewriteRelativeImportExtensions": true, - "allowImportingTsExtensions": true, - "paths": { - "effect": ["../../../effect/src/index.js"], - "effect/*": ["../../../effect/src/*.js"] - }, - "plugins": [ - { "name": "@effect/language-service", "includeSuggestionsInTsc": false } - ] - } -} diff --git a/repos/effect/packages/vitest/package.json b/repos/effect/packages/vitest/package.json index 901d9201a4..5da8ac6184 100644 --- a/repos/effect/packages/vitest/package.json +++ b/repos/effect/packages/vitest/package.json @@ -1,17 +1,17 @@ { "name": "@effect/vitest", - "version": "4.0.0-beta.90", + "version": "4.0.0-beta.107", "type": "module", "license": "MIT", "description": "A set of helpers for testing Effects with vitest", "homepage": "https://effect.website", "repository": { "type": "git", - "url": "https://github.com/Effect-TS/effect-smol.git", + "url": "https://github.com/Effect-TS/effect.git", "directory": "packages/vitest" }, "bugs": { - "url": "https://github.com/Effect-TS/effect-smol/issues" + "url": "https://github.com/Effect-TS/effect/issues" }, "sideEffects": [], "exports": { @@ -19,6 +19,7 @@ ".": "./src/index.ts", "./*": "./src/*.ts", "./internal/*": null, + "./index": null, "./*/index": null }, "files": [ @@ -26,7 +27,10 @@ "dist/**/*.js", "dist/**/*.js.map", "dist/**/*.d.ts", - "dist/**/*.d.ts.map" + "dist/**/*.d.ts.map", + "AGENTS.md", + "CLAUDE.md", + "ai-docs/**/*" ], "publishConfig": { "access": "public", @@ -36,25 +40,22 @@ ".": "./dist/index.js", "./*": "./dist/*.js", "./internal/*": null, + "./index": null, "./*/index": null } }, "scripts": { "build": "tsc -b tsconfig.json && pnpm babel", - "build:tsgo": "tsgo -b tsconfig.json && pnpm babel", "babel": "babel dist --plugins annotate-pure-calls --out-dir dist --source-maps", - "check": "tsc -b tsconfig.json", - "test": "vitest", - "coverage": "vitest --coverage" + "check": "tsc -b tsconfig.json" }, "peerDependencies": { "effect": "workspace:^", - "vitest": "^3.0.0 || ^4.0.0" + "vitest": ">=4.1.0 <5.0.0" }, "devDependencies": { - "@types/node": "^25.7.0", - "@vitest/runner": "4.1.4", + "@types/node": "^26.1.2", "effect": "workspace:^", - "vitest": "4.1.4" + "vitest": "4.1.10" } } diff --git a/repos/effect/packages/vitest/src/index.ts b/repos/effect/packages/vitest/src/index.ts index 37886fb6c3..b52478662a 100644 --- a/repos/effect/packages/vitest/src/index.ts +++ b/repos/effect/packages/vitest/src/index.ts @@ -180,14 +180,14 @@ export const live: Vitest.Tester = internal.live * @since 4.0.0 * * ```ts - * import { expect, layer } from "@effect/vitest" + * import { assert, layer } from "@effect/vitest" * import { Effect, Layer, Context } from "effect" * - * class Foo extends Context.Service("Foo")() { + * class Foo extends Context.Service()("Foo") { * static Live = Layer.succeed(Foo, "foo") * } * - * class Bar extends Context.Service("Bar")() { + * class Bar extends Context.Service()("Bar") { * static Live = Layer.effect( * Bar, * Effect.map(Foo, () => "bar" as const) @@ -198,7 +198,7 @@ export const live: Vitest.Tester = internal.live * it.effect("adds context", () => * Effect.gen(function*() { * const foo = yield* Foo - * expect(foo).toEqual("foo") + * assert.strictEqual(foo, "foo") * })) * * it.layer(Bar.Live)("nested", (it) => { @@ -206,8 +206,8 @@ export const live: Vitest.Tester = internal.live * Effect.gen(function*() { * const foo = yield* Foo * const bar = yield* Bar - * expect(foo).toEqual("foo") - * expect(bar).toEqual("bar") + * assert.strictEqual(foo, "foo") + * assert.strictEqual(bar, "bar") * })) * }) * }) diff --git a/repos/effect/packages/vitest/src/internal/internal.ts b/repos/effect/packages/vitest/src/internal/internal.ts index 31c7ea1299..f7717b0a60 100644 --- a/repos/effect/packages/vitest/src/internal/internal.ts +++ b/repos/effect/packages/vitest/src/internal/internal.ts @@ -2,7 +2,6 @@ * @since 4.0.0 */ -import { getCurrentSuite } from "@vitest/runner" import * as Cause from "effect/Cause" import * as Duration from "effect/Duration" import * as Effect from "effect/Effect" @@ -10,6 +9,7 @@ import * as Exit from "effect/Exit" import { flow, pipe } from "effect/Function" import * as Layer from "effect/Layer" import { isObject } from "effect/Predicate" +import * as Rec from "effect/Record" import * as Schedule from "effect/Schedule" import * as Schema from "effect/Schema" import * as Scope from "effect/Scope" @@ -19,6 +19,8 @@ import * as TestConsole from "effect/testing/TestConsole" import * as V from "vitest" import type * as Vitest from "../index.ts" +const getCurrentSuite = V.TestRunner.getCurrentSuite + const runPromise: ( _: Effect.Effect, ctx?: V.TestContext | undefined @@ -61,11 +63,11 @@ const makeItProxy = ( return Reflect.apply(target, thisArg, argArray) }, get(target, property, receiver) { - if (property in overrides) { + if (Object.hasOwn(overrides, property)) { return Reflect.get(overrides, property) } - const value = Reflect.get(target, property, receiver) - return typeof value === "function" ? value.bind(target) : value + // do not bind: binding would strip vitest's static helpers (e.g. `describe.each`) + return Reflect.get(target, property, receiver) } }) @@ -127,7 +129,7 @@ const makeTester = ( if (Array.isArray(arbitraries)) { const arbs = arbitraries.map((arbitrary) => { if (Schema.isSchema(arbitrary)) { - return Schema.toArbitrary(arbitrary) + return Schema.toArbitrary(arbitrary)(fc) } return arbitrary as fc.Arbitrary }) @@ -148,10 +150,7 @@ const makeTester = ( const arbs = fc.record( Object.keys(arbitraries).reduce(function(result, key) { const arb: any = arbitraries[key] - if (Schema.isSchema(arb)) { - result[key] = Schema.toArbitrary(arb) - } - result[key] = arb + Rec.assignProperty(result, key, Schema.isSchema(arb) ? Schema.toArbitrary(arb)(fc) : arb) return result }, {} as Record>) ) @@ -197,7 +196,7 @@ export const prop: Vitest.Vitest.Methods["prop"] = (name, arbitraries, self, tim if (Schema.isSchema(arb)) { throw new Error("Schemas are not supported yet") } - result[key] = arb + Rec.assignProperty(result, key, arb) return result }, {} as Record>) ) diff --git a/repos/effect/packages/vitest/src/utils.ts b/repos/effect/packages/vitest/src/utils.ts index e128c8765b..4a4612cd27 100644 --- a/repos/effect/packages/vitest/src/utils.ts +++ b/repos/effect/packages/vitest/src/utils.ts @@ -39,7 +39,7 @@ export function fail(message: string) { * @since 4.0.0 */ export function deepStrictEqual(actual: A, expected: A, message?: string, ..._: Array) { - assert.deepStrictEqual(actual, expected, message) + assert.deepStrictEqual(actual, expected, message as string) } /** @@ -49,7 +49,7 @@ export function deepStrictEqual(actual: A, expected: A, message?: string, ... * @since 4.0.0 */ export function notDeepStrictEqual(actual: A, expected: A, message?: string, ..._: Array) { - assert.notDeepStrictEqual(actual, expected, message) + assert.notDeepStrictEqual(actual, expected, message as string) } /** @@ -59,7 +59,11 @@ export function notDeepStrictEqual(actual: A, expected: A, message?: string, * @since 4.0.0 */ export function strictEqual(actual: A, expected: A, message?: string, ..._: Array) { - assert.strictEqual(actual, expected, message) + if (message !== undefined) { + assert.strictEqual(actual, expected, message) + } else { + assert.strictEqual(actual, expected) + } } /** @@ -159,7 +163,6 @@ export function assertMatch(actual: string, regExp: RegExp, ..._: Array) export function throws(thunk: () => void, error?: Error | ((u: unknown) => undefined), ..._: Array) { try { thunk() - fail("Expected to throw an error") } catch (e) { if (error !== undefined) { if (Predicate.isFunction(error)) { @@ -170,7 +173,9 @@ export function throws(thunk: () => void, error?: Error | ((u: unknown) => undef throw e } } + return } + fail("Expected to throw an error") } /** @@ -186,7 +191,6 @@ export async function throwsAsync( ) { try { await thunk() - fail("Expected to throw an error") } catch (e) { if (error !== undefined) { if (Predicate.isFunction(error)) { @@ -195,7 +199,9 @@ export async function throwsAsync( deepStrictEqual(e, error) } } + return } + fail("Expected to throw an error") } // ---------------------------- diff --git a/repos/effect/packages/vitest/test/index.test.ts b/repos/effect/packages/vitest/test/index.test.ts index e6d17d7e5b..49446dcca5 100644 --- a/repos/effect/packages/vitest/test/index.test.ts +++ b/repos/effect/packages/vitest/test/index.test.ts @@ -1,5 +1,6 @@ import { afterAll, assert, describe, expect, it, layer } from "@effect/vitest" -import { Clock, Context, Duration, Effect, Fiber, Layer } from "effect" +import * as testAssert from "@effect/vitest/utils" +import { Clock, Context, Duration, Effect, Fiber, Layer, Schema } from "effect" import { FastCheck, TestClock } from "effect/testing" it.effect( @@ -11,6 +12,14 @@ it.live( () => Effect.acquireRelease(Effect.sync(() => expect(1).toEqual(1)), () => Effect.void) ) +it("throws fails when the thunk does not throw", () => { + expect(() => testAssert.throws(() => {})).toThrow() +}) + +it("throwsAsync fails when the promise resolves", async () => { + await expect(testAssert.throwsAsync(() => Promise.resolve())).rejects.toThrow() +}) + // each it.effect.each([1, 2, 3])( @@ -43,6 +52,17 @@ it.effect.skipIf(false)("effect skipIf (false)", () => Effect.sync(() => expect( it.effect.runIf(true)("effect runIf (true)", () => Effect.sync(() => expect(1).toEqual(1))) it.effect.runIf(false)("effect runIf (false)", () => Effect.die("not run anyway")) +// chained helpers + +it.describe.each(["foo", "bar"] as const)("describe.each %s", (text) => { + it.effect("runs an Effect test", () => + Effect.sync(() => { + assert.include(["foo", "bar"], text) + })) +}) + +it.skip.each([1])("skip.each %s", () => assert.fail("skipped anyway")) + // The following test is expected to fail because it simulates a test timeout. // Be aware that eventual "failure" of the test is only logged out. it.live.fails("interrupts on timeout", (ctx) => @@ -199,6 +219,12 @@ it.prop( ({ a, b }) => a + b === b + a ) +it.live.prop( + "schema with object", + { value: Schema.Int }, + ({ value }) => Effect.sync(() => assert.isTrue(Number.isInteger(value))) +) + it.effect.prop("symmetry", [realNumber, FastCheck.integer()], ([a, b]) => Effect.gen(function*() { yield* Effect.void diff --git a/repos/effect/packages/vitest/tsconfig.json b/repos/effect/packages/vitest/tsconfig.json index be73d74c7f..f1ef563eb2 100644 --- a/repos/effect/packages/vitest/tsconfig.json +++ b/repos/effect/packages/vitest/tsconfig.json @@ -1,5 +1,5 @@ { - "$schema": "http://json.schemastore.org/tsconfig", + "$schema": "https://json.schemastore.org/tsconfig", "extends": "../../tsconfig.base.json", "include": ["src"], "references": [ diff --git a/repos/effect/packages/vitest/vitest.config.ts b/repos/effect/packages/vitest/vitest.config.ts deleted file mode 100644 index fb966ae875..0000000000 --- a/repos/effect/packages/vitest/vitest.config.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { mergeConfig, type ViteUserConfig } from "vitest/config" -import shared from "../../vitest.shared.ts" - -const config: ViteUserConfig = {} - -export default mergeConfig(shared, config) diff --git a/repos/effect/patches/@changesets__assemble-release-plan.patch b/repos/effect/patches/@changesets__assemble-release-plan.patch deleted file mode 100644 index 4967232db8..0000000000 --- a/repos/effect/patches/@changesets__assemble-release-plan.patch +++ /dev/null @@ -1,26 +0,0 @@ -diff --git a/dist/changesets-assemble-release-plan.cjs.js b/dist/changesets-assemble-release-plan.cjs.js -index e07ba6e793021b6cfdec898afca517e293386ddb..88d80a95fbe739996918ef4883601b4388926123 100644 ---- a/dist/changesets-assemble-release-plan.cjs.js -+++ b/dist/changesets-assemble-release-plan.cjs.js -@@ -215,7 +215,7 @@ function determineDependents({ - preInfo, - onlyUpdatePeerDependentsWhenOutOfRange: config.___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH.onlyUpdatePeerDependentsWhenOutOfRange - })) { -- type = "major"; -+ type = "minor"; - } else if ((!releases.has(dependent) || releases.get(dependent).type === "none") && (config.___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH.updateInternalDependents === "always" || !semverSatisfies__default["default"](incrementVersion(nextRelease, preInfo), versionRange))) { - switch (depType) { - case "dependencies": -diff --git a/dist/changesets-assemble-release-plan.esm.js b/dist/changesets-assemble-release-plan.esm.js -index ea2be567403c4ef94a65f3218ccb683cf5cb4bc1..b62b66628d8887618b02ee35359faf70cbe685ad 100644 ---- a/dist/changesets-assemble-release-plan.esm.js -+++ b/dist/changesets-assemble-release-plan.esm.js -@@ -204,7 +204,7 @@ function determineDependents({ - preInfo, - onlyUpdatePeerDependentsWhenOutOfRange: config.___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH.onlyUpdatePeerDependentsWhenOutOfRange - })) { -- type = "major"; -+ type = "minor"; - } else if ((!releases.has(dependent) || releases.get(dependent).type === "none") && (config.___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH.updateInternalDependents === "always" || !semverSatisfies(incrementVersion(nextRelease, preInfo), versionRange))) { - switch (depType) { - case "dependencies": diff --git a/repos/effect/patches/@changesets__get-github-info.patch b/repos/effect/patches/@changesets__get-github-info.patch deleted file mode 100644 index 52e437f59e..0000000000 --- a/repos/effect/patches/@changesets__get-github-info.patch +++ /dev/null @@ -1,44 +0,0 @@ -diff --git a/dist/changesets-get-github-info.cjs.js b/dist/changesets-get-github-info.cjs.js -index 4187b5c54532a1c1c179b60cb314026f67e48323..740a894a5f8ac240a5ce1d3f12d2d77cabc0c2aa 100644 ---- a/dist/changesets-get-github-info.cjs.js -+++ b/dist/changesets-get-github-info.cjs.js -@@ -237,16 +237,13 @@ async function getInfo(request) { - b = new Date(b.mergedAt); - return a > b ? 1 : a < b ? -1 : 0; - })[0] : null; -- if (associatedPullRequest) { -- user = associatedPullRequest.author; -- } - return { - user: user ? user.login : null, - pull: associatedPullRequest ? associatedPullRequest.number : null, - links: { - commit: `[\`${request.commit.slice(0, 7)}\`](${data.commitUrl})`, - pull: associatedPullRequest ? `[#${associatedPullRequest.number}](${associatedPullRequest.url})` : null, -- user: user ? `[@${user.login}](${user.url})` : null -+ user: user ? `@${user.login}` : null - } - }; - } -diff --git a/dist/changesets-get-github-info.esm.js b/dist/changesets-get-github-info.esm.js -index 071ec75bb2b5894f09c06d4ead395e56ddbfd1c8..9a6134e78ace53ff341ef21f0352f0bd734b6b2c 100644 ---- a/dist/changesets-get-github-info.esm.js -+++ b/dist/changesets-get-github-info.esm.js -@@ -228,16 +228,13 @@ async function getInfo(request) { - b = new Date(b.mergedAt); - return a > b ? 1 : a < b ? -1 : 0; - })[0] : null; -- if (associatedPullRequest) { -- user = associatedPullRequest.author; -- } - return { - user: user ? user.login : null, - pull: associatedPullRequest ? associatedPullRequest.number : null, - links: { - commit: `[\`${request.commit.slice(0, 7)}\`](${data.commitUrl})`, - pull: associatedPullRequest ? `[#${associatedPullRequest.number}](${associatedPullRequest.url})` : null, -- user: user ? `[@${user.login}](${user.url})` : null -+ user: user ? `@${user.login}` : null - } - }; - } diff --git a/repos/effect/patches/@changesets__get-github-info@1.0.0-next.4.patch b/repos/effect/patches/@changesets__get-github-info@1.0.0-next.4.patch new file mode 100644 index 0000000000..c7a3e44985 --- /dev/null +++ b/repos/effect/patches/@changesets__get-github-info@1.0.0-next.4.patch @@ -0,0 +1,38 @@ +diff --git a/dist/index.mjs b/dist/index.mjs +index 0b8a76abaac754e0a9a5d69ec26e62fc5788aa98..6b8ffafb842aba9b15dde5cc50dc276b6edc3900 100644 +--- a/dist/index.mjs ++++ b/dist/index.mjs +@@ -27,7 +27,14 @@ async function readEnv() { + } + //#endregion + //#region src/dataloader.ts +-const GHDataLoader = new DataLoader(batchLoad, { maxBatchSize: 50 }); ++const GHDataLoader = new DataLoader(batchLoad, { ++ maxBatchSize: 50, ++ cacheKeyFn: (request) => JSON.stringify([ ++ request.repo, ++ request.kind, ++ request.kind === "pull" ? request.pull : request.commit ++ ]) ++}); + async function loadCommitData(options) { + return await GHDataLoader.load({ + ...options, +@@ -166,7 +173,7 @@ async function getCommitInfo(options) { + const bDate = new Date(b.mergedAt); + return aDate.getTime() - bDate.getTime(); + })[0]; +- const author = pr?.author ?? data.author?.user; ++ const author = data.author?.user; + return { + commit: { + sha: options.commit, +@@ -176,7 +183,7 @@ async function getCommitInfo(options) { + author: author ? { + login: author.login, + url: author.url, +- markdownLink: `[@${author.login}](${author.url})` ++ markdownLink: `@${author.login}` + } : void 0, + pull: pr ? { + number: pr.number, diff --git a/repos/effect/patches/@changesets__read@1.0.0-next.10.patch b/repos/effect/patches/@changesets__read@1.0.0-next.10.patch new file mode 100644 index 0000000000..60a6ce56a4 --- /dev/null +++ b/repos/effect/patches/@changesets__read@1.0.0-next.10.patch @@ -0,0 +1,12 @@ +diff --git a/dist/index.mjs b/dist/index.mjs +index 012be78..2c23f84 100644 +--- a/dist/index.mjs ++++ b/dist/index.mjs +@@ -30,7 +30,6 @@ async function readChangesets(rootDir, sinceRef) { + } catch (err) { + if (err.code !== "ENOENT") throw err; + } +- console.log(changesets); + if (sinceRef != null) changesets = await filterChangesetsSinceRef(changesets, changesetBase, sinceRef); + changesets = changesets.filter((file) => { + file = path.basename(file); diff --git a/repos/effect/pnpm-lock.yaml b/repos/effect/pnpm-lock.yaml index da65260513..3c82343539 100644 --- a/repos/effect/pnpm-lock.yaml +++ b/repos/effect/pnpm-lock.yaml @@ -5,35 +5,35 @@ settings: excludeLinksFromLockfile: false patchedDependencies: - '@changesets/assemble-release-plan': - hash: 29cf7a343aefd3c59ad238a4cb067f0a1a0adbeed07b6d57d6f31d6c80a5e25e - path: patches/@changesets__assemble-release-plan.patch - '@changesets/get-github-info': - hash: 314478eeae7ab2776d847d3b2b7ea1cd4231c65907e85597dacd0bea56355bc9 - path: patches/@changesets__get-github-info.patch + '@changesets/get-github-info@1.0.0-next.4': + hash: a5b1907668397f36aab989954aa78993792f578b83e41a164ab7ecd573ac9166 + path: patches/@changesets__get-github-info@1.0.0-next.4.patch + '@changesets/read@1.0.0-next.10': + hash: 00298a7e3295e97aab2f89a36af496eef21b576642bd2eac88ae13ceca78cccb + path: patches/@changesets__read@1.0.0-next.10.patch importers: .: devDependencies: '@babel/cli': - specifier: ^7.28.6 - version: 7.28.6(@babel/core@7.29.0) + specifier: ^8.0.4 + version: 8.0.4(@babel/core@8.0.1) '@babel/core': - specifier: ^7.29.0 - version: 7.29.0 + specifier: ^8.0.1 + version: 8.0.1 '@babel/plugin-transform-export-namespace-from': - specifier: ^7.27.1 - version: 7.27.1(@babel/core@7.29.0) + specifier: ^8.0.1 + version: 8.0.1(@babel/core@8.0.1) '@babel/plugin-transform-modules-commonjs': - specifier: ^7.28.6 - version: 7.28.6(@babel/core@7.29.0) + specifier: ^8.0.1 + version: 8.0.1(@babel/core@8.0.1) '@changesets/changelog-github': - specifier: ^0.7.0 - version: 0.7.0 + specifier: 1.0.0-next.9 + version: 1.0.0-next.9 '@changesets/cli': - specifier: ^2.31.0 - version: 2.31.0(@types/node@25.7.0) + specifier: 3.0.0-next.11 + version: 3.0.0-next.11 '@effect/ai-docgen': specifier: workspace:^ version: link:packages/tools/ai-docgen @@ -41,20 +41,20 @@ importers: specifier: workspace:^ version: link:packages/tools/bundle '@effect/docgen': - specifier: https://pkg.pr.new/Effect-TS/docgen/@effect/docgen@e57e5f5 - version: https://pkg.pr.new/Effect-TS/docgen/@effect/docgen@e57e5f5(tsx@4.21.0)(typescript@6.0.3) + specifier: workspace:^ + version: link:packages/tools/docgen + '@effect/doctest': + specifier: workspace:^ + version: link:packages/tools/doctest '@effect/jsdocs': specifier: workspace:^ version: link:packages/tools/jsdocs - '@effect/language-service': - specifier: ^0.85.1 - version: 0.85.1 '@effect/oxc': specifier: workspace:^ version: link:packages/tools/oxc '@effect/tsgo': - specifier: ^0.7.2 - version: 0.7.2 + specifier: ^0.21.0 + version: 0.21.0 '@effect/utils': specifier: workspace:^ version: link:packages/tools/utils @@ -62,104 +62,98 @@ importers: specifier: workspace:^ version: link:packages/vitest '@faker-js/faker': - specifier: ^10.4.0 - version: 10.4.0 + specifier: ^10.5.0 + version: 10.5.0 '@rollup/plugin-node-resolve': specifier: ^16.0.3 - version: 16.0.3(rollup@4.60.3) + version: 16.0.3(rollup@4.62.3) '@rollup/plugin-replace': specifier: ^6.0.3 - version: 6.0.3(rollup@4.60.3) + version: 6.0.3(rollup@4.62.3) '@rollup/plugin-terser': specifier: ^1.0.0 - version: 1.0.0(rollup@4.60.3) + version: 1.0.0(rollup@4.62.3) '@types/jscodeshift': specifier: ^17.3.0 version: 17.3.0 '@types/node': - specifier: ^25.7.0 - version: 25.7.0 - '@typescript/native-preview': - specifier: 7.0.0-dev.20260512.1 - version: 7.0.0-dev.20260512.1 + specifier: ^26.1.2 + version: 26.1.2 '@vitest/browser': - specifier: 4.1.6 - version: 4.1.6(vite@7.3.3(@types/node@25.7.0)(lightningcss@1.32.0)(terser@5.47.1)(tsx@4.21.0)(yaml@2.9.0))(vitest@4.1.4) + specifier: ^4.1.10 + version: 4.1.10(vite@8.1.5(@types/node@26.1.2)(esbuild@0.28.1)(terser@5.49.0)(tsx@4.23.1)(yaml@2.9.0))(vitest@4.1.10) '@vitest/coverage-v8': - specifier: 4.1.6 - version: 4.1.6(@vitest/browser@4.1.6)(vitest@4.1.4) + specifier: ^4.1.10 + version: 4.1.10(@vitest/browser@4.1.10)(vitest@4.1.10) '@vitest/expect': - specifier: 4.1.6 - version: 4.1.6 + specifier: ^4.1.10 + version: 4.1.10 '@vitest/web-worker': - specifier: 4.1.6 - version: 4.1.6(vitest@4.1.4) + specifier: ^4.1.10 + version: 4.1.10(vitest@4.1.10) ast-types: specifier: ^0.14.2 version: 0.14.2 babel-plugin-annotate-pure-calls: specifier: ^0.5.0 - version: 0.5.0(@babel/core@7.29.0) + version: 0.5.0(@babel/core@8.0.1) dprint: - specifier: ^0.54.0 - version: 0.54.0 + specifier: ^0.55.2 + version: 0.55.2 glob: specifier: ^13.0.6 version: 13.0.6 happy-dom: - specifier: ^20.9.0 - version: 20.9.0 + specifier: ^20.11.1 + version: 20.11.1 jscodeshift: - specifier: ^17.3.0 - version: 17.3.0 + specifier: ^17.4.0 + version: 17.4.0 lalph: - specifier: ^0.3.137 - version: 0.3.137 + specifier: ^0.3.139 + version: 0.3.139 madge: specifier: ^8.0.0 - version: 8.0.0(typescript@6.0.3) + version: 8.0.0(typescript@7.0.2) oxlint: - specifier: 1.42.0 - version: 1.42.0 + specifier: ^1.76.0 + version: 1.76.0 + pkg-pr-new: + specifier: 0.0.78 + version: 0.0.78 playwright: - specifier: ^1.60.0 - version: 1.60.0 + specifier: ^1.62.0 + version: 1.62.0 rollup: - specifier: ^4.60.3 - version: 4.60.3 + specifier: ^4.62.3 + version: 4.62.3 rollup-plugin-bundle-stats: - specifier: ^4.22.1 - version: 4.22.1(core-js@3.47.0)(rolldown@1.0.0)(rollup@4.60.3)(vite@7.3.3(@types/node@25.7.0)(lightningcss@1.32.0)(terser@5.47.1)(tsx@4.21.0)(yaml@2.9.0)) + specifier: ^4.22.2 + version: 4.22.2(core-js@3.47.0)(rolldown@1.1.5)(rollup@4.62.3)(vite@8.1.5(@types/node@26.1.2)(esbuild@0.28.1)(terser@5.49.0)(tsx@4.23.1)(yaml@2.9.0)) rollup-plugin-esbuild: specifier: ^6.2.1 - version: 6.2.1(esbuild@0.27.2)(rollup@4.60.3) + version: 6.2.1(esbuild@0.28.1)(rollup@4.62.3) rollup-plugin-visualizer: specifier: ^7.0.1 - version: 7.0.1(rolldown@1.0.0)(rollup@4.60.3) + version: 7.0.1(rolldown@1.1.5)(rollup@4.62.3) terser: - specifier: ^5.47.1 - version: 5.47.1 - ts-patch: - specifier: ^4.0.1 - version: 4.0.1 + specifier: ^5.49.0 + version: 5.49.0 tstyche: - specifier: ^7.1.0 - version: 7.1.0(typescript@6.0.3) + specifier: ^7.2.2 + version: 7.2.2(typescript@7.0.2) typescript: - specifier: ^6.0.3 - version: 6.0.3 + specifier: ^7.0.2 + version: 7.0.2 vite: - specifier: ^7.3.2 - version: 7.3.3(@types/node@25.7.0)(lightningcss@1.32.0)(terser@5.47.1)(tsx@4.21.0)(yaml@2.9.0) - vite-tsconfig-paths: - specifier: ^6.1.1 - version: 6.1.1(typescript@6.0.3)(vite@7.3.3(@types/node@25.7.0)(lightningcss@1.32.0)(terser@5.47.1)(tsx@4.21.0)(yaml@2.9.0)) + specifier: ^8.1.5 + version: 8.1.5(@types/node@26.1.2)(esbuild@0.28.1)(terser@5.49.0)(tsx@4.23.1)(yaml@2.9.0) vitest: - specifier: 4.1.4 - version: 4.1.4(@edge-runtime/vm@5.0.0)(@opentelemetry/api@1.9.1)(@types/node@25.7.0)(@vitest/coverage-v8@4.1.6)(happy-dom@20.9.0)(jsdom@29.1.1)(vite@7.3.3(@types/node@25.7.0)(lightningcss@1.32.0)(terser@5.47.1)(tsx@4.21.0)(yaml@2.9.0)) + specifier: ^4.1.10 + version: 4.1.10(@edge-runtime/vm@5.0.0)(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/coverage-v8@4.1.10)(happy-dom@20.11.1)(jsdom@30.0.0)(vite@8.1.5(@types/node@26.1.2)(esbuild@0.28.1)(terser@5.49.0)(tsx@4.23.1)(yaml@2.9.0)) vitest-websocket-mock: - specifier: ^0.5.0 - version: 0.5.0(vitest@4.1.4) + specifier: ^0.7.0 + version: 0.7.0(vitest@4.1.10) zod: specifier: ^4.4.3 version: 4.4.3 @@ -236,15 +230,15 @@ importers: specifier: workspace:* version: link:../packages/effect hono: - specifier: ^4.12.18 - version: 4.12.18 + specifier: ^4.12.28 + version: 4.12.28 nodemailer: - specifier: ^8.0.7 - version: 8.0.7 + specifier: ^9.0.3 + version: 9.0.3 devDependencies: '@types/nodemailer': - specifier: ^8.0.0 - version: 8.0.0 + specifier: ^8.0.1 + version: 8.0.1 packages/ai/anthropic: devDependencies: @@ -276,17 +270,17 @@ importers: specifier: ^10.4.1 version: 10.4.1 '@testing-library/jest-dom': - specifier: ^6.9.1 - version: 6.9.1 + specifier: ^7.0.0 + version: 7.0.0(@testing-library/dom@10.4.1) '@testing-library/react': specifier: ^16.3.2 - version: 16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + version: 16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@types/react': - specifier: ^19.2.14 - version: 19.2.14 + specifier: ^19.2.17 + version: 19.2.17 '@types/react-dom': specifier: ^19.2.2 - version: 19.2.3(@types/react@19.2.14) + version: 19.2.3(@types/react@19.2.17) '@types/scheduler': specifier: ^0.26.0 version: 0.26.0 @@ -294,17 +288,17 @@ importers: specifier: workspace:^ version: link:../../effect jsdom: - specifier: ^29.1.1 - version: 29.1.1 + specifier: ^30.0.0 + version: 30.0.0 react: - specifier: 19.2.6 - version: 19.2.6 + specifier: ^19.2.7 + version: 19.2.7 react-dom: - specifier: 19.2.6 - version: 19.2.6(react@19.2.6) + specifier: ^19.2.7 + version: 19.2.7(react@19.2.7) react-error-boundary: - specifier: ^6.1.1 - version: 6.1.1(react@19.2.6) + specifier: ^6.1.2 + version: 6.1.2(react@19.2.7) scheduler: specifier: ^0.27.0 version: 0.27.0 @@ -313,22 +307,22 @@ importers: devDependencies: '@solidjs/testing-library': specifier: ^0.8.0 - version: 0.8.10(solid-js@1.9.12) + version: 0.8.10(solid-js@1.9.14) '@testing-library/dom': specifier: ^10.4.1 version: 10.4.1 '@testing-library/jest-dom': - specifier: ^6.9.1 - version: 6.9.1 + specifier: ^7.0.0 + version: 7.0.0(@testing-library/dom@10.4.1) effect: specifier: workspace:^ version: link:../../effect jsdom: - specifier: ^29.1.1 - version: 29.1.1 + specifier: ^30.0.0 + version: 30.0.0 solid-js: - specifier: ^1.9.12 - version: 1.9.12 + specifier: ^1.9.14 + version: 1.9.14 packages/atom/vue: devDependencies: @@ -336,8 +330,8 @@ importers: specifier: workspace:^ version: link:../../effect vue: - specifier: ^3.5.34 - version: 3.5.34(typescript@6.0.3) + specifier: ^3.5.39 + version: 3.5.39(typescript@7.0.2) packages/effect: dependencies: @@ -345,57 +339,39 @@ importers: specifier: ^1.1.0 version: 1.1.0 fast-check: - specifier: ^4.8.0 - version: 4.8.0 - find-my-way-ts: - specifier: ^0.1.6 - version: 0.1.6 - ini: - specifier: ^7.0.0 - version: 7.0.0 + specifier: ^4.9.0 + version: 4.9.0 kubernetes-types: specifier: ^1.30.0 version: 1.30.0 msgpackr: - specifier: ^2.0.1 - version: 2.0.1 - multipasta: - specifier: ^0.2.7 - version: 0.2.7 - toml: - specifier: ^4.1.1 - version: 4.1.1 + specifier: ^2.0.4 + version: 2.0.4 uuid: - specifier: ^14.0.0 - version: 14.0.0 - yaml: - specifier: ^2.9.0 - version: 2.9.0 + specifier: ^14.0.1 + version: 14.0.1 devDependencies: - '@types/ini': - specifier: ^4.1.1 - version: 4.1.1 '@types/node': - specifier: ^25.7.0 - version: 25.7.0 + specifier: ^26.1.2 + version: 26.1.2 ajv: specifier: ^8.20.0 version: 8.20.0 - arktype: - specifier: ^2.2.0 - version: 2.2.0 + ajv-draft-04: + specifier: ^1.0.0 + version: 1.0.0(ajv@8.20.0) ast-types: specifier: ^0.14.2 version: 0.14.2 immer: - specifier: ^11.1.8 - version: 11.1.8 + specifier: ^11.1.11 + version: 11.1.11 tinybench: - specifier: ^6.0.1 - version: 6.0.1 + specifier: ^6.0.2 + version: 6.0.2 valibot: - specifier: ^1.4.0 - version: 1.4.0(typescript@6.0.3) + specifier: ^1.4.2 + version: 1.4.2(typescript@7.0.2) packages/opentelemetry: dependencies: @@ -407,50 +383,46 @@ importers: specifier: ^1.9.1 version: 1.9.1 '@opentelemetry/api-logs': - specifier: ^0.217.0 - version: 0.217.0 + specifier: ^0.220.0 + version: 0.220.0 '@opentelemetry/context-async-hooks': - specifier: ^2.7.1 - version: 2.7.1(@opentelemetry/api@1.9.1) + specifier: ^2.9.0 + version: 2.9.0(@opentelemetry/api@1.9.1) '@opentelemetry/exporter-metrics-otlp-http': - specifier: 0.217.0 - version: 0.217.0(@opentelemetry/api@1.9.1) + specifier: 0.220.0 + version: 0.220.0(@opentelemetry/api@1.9.1) '@opentelemetry/exporter-prometheus': - specifier: ^0.217.0 - version: 0.217.0(@opentelemetry/api@1.9.1) + specifier: ^0.220.0 + version: 0.220.0(@opentelemetry/api@1.9.1) '@opentelemetry/exporter-trace-otlp-http': - specifier: ^0.217.0 - version: 0.217.0(@opentelemetry/api@1.9.1) + specifier: ^0.220.0 + version: 0.220.0(@opentelemetry/api@1.9.1) '@opentelemetry/otlp-exporter-base': - specifier: ^0.217.0 - version: 0.217.0(@opentelemetry/api@1.9.1) + specifier: ^0.220.0 + version: 0.220.0(@opentelemetry/api@1.9.1) '@opentelemetry/resources': - specifier: ^2.7.1 - version: 2.7.1(@opentelemetry/api@1.9.1) + specifier: ^2.9.0 + version: 2.9.0(@opentelemetry/api@1.9.1) '@opentelemetry/sdk-logs': - specifier: ^0.217.0 - version: 0.217.0(@opentelemetry/api@1.9.1) + specifier: ^0.220.0 + version: 0.220.0(@opentelemetry/api@1.9.1) '@opentelemetry/sdk-metrics': - specifier: ^2.7.1 - version: 2.7.1(@opentelemetry/api@1.9.1) + specifier: ^2.9.0 + version: 2.9.0(@opentelemetry/api@1.9.1) '@opentelemetry/sdk-trace-base': - specifier: ^2.7.1 - version: 2.7.1(@opentelemetry/api@1.9.1) + specifier: ^2.9.0 + version: 2.9.0(@opentelemetry/api@1.9.1) '@opentelemetry/sdk-trace-node': - specifier: ^2.7.1 - version: 2.7.1(@opentelemetry/api@1.9.1) + specifier: ^2.9.0 + version: 2.9.0(@opentelemetry/api@1.9.1) '@opentelemetry/sdk-trace-web': - specifier: ^2.7.1 - version: 2.7.1(@opentelemetry/api@1.9.1) + specifier: ^2.9.0 + version: 2.9.0(@opentelemetry/api@1.9.1) '@opentelemetry/semantic-conventions': - specifier: ^1.40.0 - version: 1.40.0 + specifier: ^1.42.0 + version: 1.42.0 packages/platform-browser: - dependencies: - multipasta: - specifier: ^0.2.7 - version: 0.2.7 devDependencies: effect: specifier: workspace:^ @@ -469,8 +441,42 @@ importers: version: link:../platform-node-shared devDependencies: '@types/bun': - specifier: ^1.3.13 - version: 1.3.13 + specifier: ^1.3.14 + version: 1.3.14 + effect: + specifier: workspace:^ + version: link:../effect + + packages/platform-deno: + dependencies: + '@db/redis': + specifier: jsr:^0.41.2 + version: '@jsr/db__redis@0.41.2' + '@effect/platform-node-shared': + specifier: workspace:^ + version: link:../platform-node-shared + '@std/fs': + specifier: jsr:^1.0.24 + version: '@jsr/std__fs@1.0.24' + '@std/media-types': + specifier: jsr:^1.1.0 + version: '@jsr/std__media-types@1.1.0' + '@std/path': + specifier: jsr:^1.1.6 + version: '@jsr/std__path@1.1.6' + '@std/streams': + specifier: jsr:^1.1.1 + version: '@jsr/std__streams@1.1.1' + devDependencies: + '@std/assert': + specifier: jsr:^1.0.16 + version: '@jsr/std__assert@1.0.19' + '@testcontainers/redis': + specifier: ^12.0.4 + version: 12.0.4 + '@types/deno': + specifier: ^2.7.0 + version: 2.7.0 effect: specifier: workspace:^ version: link:../effect @@ -481,27 +487,27 @@ importers: specifier: workspace:^ version: link:../platform-node-shared ioredis: - specifier: ^5.7.0 + specifier: '>=5.7.0 <6.0.0' version: 5.8.2 mime: specifier: ^4.1.0 version: 4.1.0 undici: - specifier: ^8.2.0 - version: 8.2.0 + specifier: ^8.7.0 + version: 8.7.0 devDependencies: '@testcontainers/mysql': - specifier: ^11.14.0 - version: 11.14.0 + specifier: ^12.0.4 + version: 12.0.4 '@testcontainers/postgresql': - specifier: ^11.14.0 - version: 11.14.0 + specifier: ^12.0.4 + version: 12.0.4 '@testcontainers/redis': - specifier: ^11.14.0 - version: 11.14.0 + specifier: ^12.0.4 + version: 12.0.4 '@types/node': - specifier: ^25.7.0 - version: 25.7.0 + specifier: ^26.1.2 + version: 26.1.2 effect: specifier: workspace:^ version: link:../effect @@ -512,24 +518,24 @@ importers: specifier: ^8.18.1 version: 8.18.1 ws: - specifier: ^8.20.0 - version: 8.20.0 + specifier: ^8.21.0 + version: 8.21.0 devDependencies: '@types/node': - specifier: ^25.7.0 - version: 25.7.0 + specifier: ^26.1.2 + version: 26.1.2 effect: specifier: workspace:^ version: link:../effect tar: - specifier: ^7.5.15 - version: 7.5.15 + specifier: ^7.5.19 + version: 7.5.19 packages/sql/clickhouse: dependencies: '@clickhouse/client': - specifier: ^1.18.4 - version: 1.18.4 + specifier: ^1.23.1 + version: 1.23.1 devDependencies: '@effect/platform-node': specifier: workspace:^ @@ -541,28 +547,28 @@ importers: packages/sql/d1: dependencies: '@cloudflare/workers-types': - specifier: ^4.20260511.1 - version: 4.20260511.1 + specifier: ^5.20260708.1 + version: 5.20260708.1 devDependencies: effect: specifier: workspace:^ version: link:../../effect miniflare: - specifier: ^4.20260507.1 - version: 4.20260507.1 + specifier: ^4.20260706.0 + version: 4.20260706.0 packages/sql/libsql: dependencies: '@libsql/client': - specifier: ^0.17.3 - version: 0.17.3 + specifier: ^0.17.4 + version: 0.17.4 devDependencies: effect: specifier: workspace:^ version: link:../../effect testcontainers: - specifier: ^11.14.0 - version: 11.14.0 + specifier: ^12.0.4 + version: 12.0.4 packages/sql/mssql: dependencies: @@ -570,6 +576,9 @@ importers: specifier: ^19.2.1 version: 19.2.1(@azure/core-client@1.10.1) devDependencies: + '@testcontainers/mssqlserver': + specifier: ^12.0.4 + version: 12.0.4 effect: specifier: workspace:^ version: link:../../effect @@ -577,12 +586,12 @@ importers: packages/sql/mysql2: dependencies: mysql2: - specifier: ^3.22.3 - version: 3.22.3(@types/node@25.9.3) + specifier: ^3.22.6 + version: 3.22.6(@types/node@26.1.2) devDependencies: '@testcontainers/mysql': - specifier: ^11.14.0 - version: 11.14.0 + specifier: ^12.0.4 + version: 12.0.4 effect: specifier: workspace:^ version: link:../../effect @@ -590,24 +599,24 @@ importers: packages/sql/pg: dependencies: pg: - specifier: ^8.20.0 - version: 8.20.0 + specifier: ^8.22.0 + version: 8.22.0 pg-connection-string: - specifier: 2.12.0 - version: 2.12.0 + specifier: ^2.14.0 + version: 2.14.0 pg-cursor: - specifier: ^2.19.0 - version: 2.19.0(pg@8.20.0) + specifier: ^2.21.0 + version: 2.21.0(pg@8.22.0) pg-pool: - specifier: ^3.13.0 - version: 3.13.0(pg@8.20.0) + specifier: ^3.14.0 + version: 3.14.0(pg@8.22.0) pg-types: specifier: ^4.1.0 version: 4.1.0 devDependencies: '@testcontainers/postgresql': - specifier: ^11.14.0 - version: 11.14.0 + specifier: ^12.0.4 + version: 12.0.4 '@types/pg': specifier: ^8.20.0 version: 8.20.0 @@ -621,8 +630,8 @@ importers: packages/sql/pglite: dependencies: '@electric-sql/pglite': - specifier: ^0.4.5 - version: 0.4.5 + specifier: ^0.5.4 + version: 0.5.4 devDependencies: effect: specifier: workspace:^ @@ -634,8 +643,8 @@ importers: specifier: workspace:^ version: link:../../platform-bun '@types/bun': - specifier: ^1.3.13 - version: 1.3.13 + specifier: ^1.3.14 + version: 1.3.14 effect: specifier: workspace:^ version: link:../../effect @@ -643,24 +652,20 @@ importers: packages/sql/sqlite-do: devDependencies: '@cloudflare/workers-types': - specifier: ^4.20260511.1 - version: 4.20260511.1 + specifier: ^5.20260708.1 + version: 5.20260708.1 effect: specifier: workspace:^ version: link:../../effect packages/sql/sqlite-node: - dependencies: - better-sqlite3: - specifier: ^12.9.0 - version: 12.9.0 devDependencies: '@effect/platform-node': specifier: workspace:^ version: link:../../platform-node - '@types/better-sqlite3': - specifier: ^7.6.13 - version: 7.6.13 + '@types/node': + specifier: ^26.1.2 + version: 26.1.2 effect: specifier: workspace:^ version: link:../../effect @@ -668,8 +673,8 @@ importers: packages/sql/sqlite-react-native: devDependencies: '@op-engineering/op-sqlite': - specifier: 15.2.12 - version: 15.2.12(react-native@0.83.0(@babel/core@7.29.7)(@types/react@19.2.14)(react@19.2.6))(react@19.2.6) + specifier: ^17.1.2 + version: 17.1.2(react-native@0.83.0(@babel/core@8.0.1)(@types/react@19.2.17)(react@19.2.7))(react@19.2.7) effect: specifier: workspace:^ version: link:../../effect @@ -702,8 +707,8 @@ importers: version: 2.9.0 devDependencies: '@types/node': - specifier: ^25.7.0 - version: 25.7.0 + specifier: ^26.1.2 + version: 26.1.2 packages/tools/ai-docgen: dependencies: @@ -721,8 +726,36 @@ importers: version: 2.9.0 devDependencies: '@types/node': - specifier: ^25.7.0 - version: 25.7.0 + specifier: ^26.1.2 + version: 26.1.2 + + packages/tools/api-diff: + dependencies: + '@effect/platform-node': + specifier: workspace:^ + version: link:../../platform-node + effect: + specifier: workspace:^ + version: link:../../effect + yaml: + specifier: ^2.9.0 + version: 2.9.0 + devDependencies: + '@effect/vitest': + specifier: workspace:^ + version: link:../../vitest + '@types/node': + specifier: ^26.1.2 + version: 26.1.2 + typescript: + specifier: ^6.0.3 + version: 6.0.3 + typescript-compiler: + specifier: npm:typescript@6.0.3 + version: typescript@6.0.3 + vitest: + specifier: 4.1.10 + version: 4.1.10(@edge-runtime/vm@5.0.0)(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/coverage-v8@4.1.10)(happy-dom@20.11.1)(jsdom@30.0.0)(vite@8.1.5(@types/node@26.1.2)(esbuild@0.28.1)(terser@5.49.0)(tsx@4.23.1)(yaml@2.9.0)) packages/tools/bundle: dependencies: @@ -731,13 +764,13 @@ importers: version: link:../../platform-node '@rollup/plugin-node-resolve': specifier: ^16.0.3 - version: 16.0.3(rollup@4.60.3) + version: 16.0.3(rollup@4.62.3) '@rollup/plugin-replace': specifier: ^6.0.3 - version: 6.0.3(rollup@4.60.3) + version: 6.0.3(rollup@4.62.3) '@rollup/plugin-terser': specifier: ^1.0.0 - version: 1.0.0(rollup@4.60.3) + version: 1.0.0(rollup@4.62.3) effect: specifier: workspace:^ version: link:../../effect @@ -745,18 +778,95 @@ importers: specifier: ^13.0.6 version: 13.0.6 rollup: - specifier: ^4.60.3 - version: 4.60.3 + specifier: ^4.62.3 + version: 4.62.3 rollup-plugin-esbuild: specifier: ^6.2.1 - version: 6.2.1(esbuild@0.27.2)(rollup@4.60.3) + version: 6.2.1(esbuild@0.28.1)(rollup@4.62.3) rollup-plugin-visualizer: specifier: ^7.0.1 - version: 7.0.1(rolldown@1.0.0)(rollup@4.60.3) + version: 7.0.1(rolldown@1.1.5)(rollup@4.62.3) + devDependencies: + '@types/node': + specifier: ^26.1.2 + version: 26.1.2 + + packages/tools/docgen: + dependencies: + '@babel/code-frame': + specifier: ^8.0.0 + version: 8.0.0 + '@effect/markdown-toc': + specifier: ^0.1.0 + version: 0.1.0 + '@effect/platform-node': + specifier: workspace:^ + version: link:../../platform-node + chalk: + specifier: ^5.6.2 + version: 5.6.2 + doctrine: + specifier: ^3.0.0 + version: 3.0.0 + effect: + specifier: workspace:^ + version: link:../../effect + glob: + specifier: ^13.0.6 + version: 13.0.6 + prettier: + specifier: ^3.6.2 + version: 3.9.6 + ts-morph: + specifier: ^27.0.2 + version: 27.0.2 + tsconfck: + specifier: ^3.1.6 + version: 3.1.6(typescript@6.0.3) + devDependencies: + '@effect/vitest': + specifier: workspace:^ + version: link:../../vitest + '@types/babel__code-frame': + specifier: ^7.0.6 + version: 7.27.0 + '@types/doctrine': + specifier: ^0.0.9 + version: 0.0.9 + '@types/node': + specifier: ^26.1.2 + version: 26.1.2 + tsx: + specifier: ^4.23.1 + version: 4.23.1 + typescript: + specifier: ^6.0.3 + version: 6.0.3 + vitest: + specifier: ^4.1.10 + version: 4.1.10(@edge-runtime/vm@5.0.0)(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/coverage-v8@4.1.10)(happy-dom@20.11.1)(jsdom@30.0.0)(vite@8.1.5(@types/node@26.1.2)(esbuild@0.28.1)(terser@5.49.0)(tsx@4.23.1)(yaml@2.9.0)) + + packages/tools/doctest: + dependencies: + rolldown: + specifier: ^1.1.5 + version: 1.1.5 devDependencies: + '@effect/vitest': + specifier: workspace:^ + version: link:../../vitest '@types/node': - specifier: ^25.7.0 - version: 25.7.0 + specifier: ^26.1.2 + version: 26.1.2 + effect: + specifier: workspace:^ + version: link:../../effect + vite: + specifier: ^8.1.5 + version: 8.1.5(@types/node@26.1.2)(esbuild@0.28.1)(terser@5.49.0)(tsx@4.23.1)(yaml@2.9.0) + vitest: + specifier: ^4.1.10 + version: 4.1.10(@edge-runtime/vm@5.0.0)(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/coverage-v8@4.1.10)(happy-dom@20.11.1)(jsdom@30.0.0)(vite@8.1.5(@types/node@26.1.2)(esbuild@0.28.1)(terser@5.49.0)(tsx@4.23.1)(yaml@2.9.0)) packages/tools/jsdocs: dependencies: @@ -771,14 +881,14 @@ importers: specifier: workspace:^ version: link:../../vitest '@types/node': - specifier: ^25.7.0 - version: 25.7.0 + specifier: ^26.1.2 + version: 26.1.2 typescript: specifier: ^6.0.3 version: 6.0.3 vitest: - specifier: ^4.1.4 - version: 4.1.4(@edge-runtime/vm@5.0.0)(@opentelemetry/api@1.9.1)(@types/node@25.7.0)(@vitest/coverage-v8@4.1.4)(happy-dom@20.9.0)(jsdom@29.1.1)(vite@7.3.5(@types/node@25.7.0)(lightningcss@1.32.0)(terser@5.48.0)(tsx@4.21.0)(yaml@2.9.0)) + specifier: ^4.1.10 + version: 4.1.10(@edge-runtime/vm@5.0.0)(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/coverage-v8@4.1.10)(happy-dom@20.11.1)(jsdom@30.0.0)(vite@8.1.5(@types/node@26.1.2)(esbuild@0.28.1)(terser@5.49.0)(tsx@4.23.1)(yaml@2.9.0)) packages/tools/openapi-generator: dependencies: @@ -800,26 +910,26 @@ importers: version: 8.0.2 openapi-typescript: specifier: ^7.13.0 - version: 7.13.0(typescript@6.0.3) + version: 7.13.0(typescript@7.0.2) yaml: specifier: ^2.9.0 version: 2.9.0 packages/tools/oxc: dependencies: - '@effect/jsdocs': - specifier: workspace:^ - version: link:../jsdocs + '@oxlint/plugins': + specifier: ^1.76.0 + version: 1.76.0 devDependencies: '@types/node': - specifier: ^25.7.0 - version: 25.7.0 + specifier: ^26.1.2 + version: 26.1.2 typescript: specifier: ^6.0.3 version: 6.0.3 vitest: - specifier: ^4.1.4 - version: 4.1.4(@edge-runtime/vm@5.0.0)(@opentelemetry/api@1.9.1)(@types/node@25.7.0)(@vitest/coverage-v8@4.1.4)(happy-dom@20.9.0)(jsdom@29.1.1)(vite@7.3.5(@types/node@25.7.0)(lightningcss@1.32.0)(terser@5.48.0)(tsx@4.21.0)(yaml@2.9.0)) + specifier: ^4.1.10 + version: 4.1.10(@edge-runtime/vm@5.0.0)(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/coverage-v8@4.1.10)(happy-dom@20.11.1)(jsdom@30.0.0)(vite@8.1.5(@types/node@26.1.2)(esbuild@0.28.1)(terser@5.49.0)(tsx@4.23.1)(yaml@2.9.0)) packages/tools/utils: dependencies: @@ -834,23 +944,20 @@ importers: version: 13.0.6 devDependencies: '@types/node': - specifier: ^25.7.0 - version: 25.7.0 + specifier: ^26.1.2 + version: 26.1.2 packages/vitest: devDependencies: '@types/node': - specifier: ^25.7.0 - version: 25.7.0 - '@vitest/runner': - specifier: 4.1.4 - version: 4.1.4 + specifier: ^26.1.2 + version: 26.1.2 effect: specifier: workspace:^ version: link:../effect vitest: - specifier: 4.1.4 - version: 4.1.4(@edge-runtime/vm@5.0.0)(@opentelemetry/api@1.9.1)(@types/node@25.7.0)(@vitest/coverage-v8@4.1.4)(happy-dom@20.9.0)(jsdom@29.1.1)(vite@7.3.5(@types/node@25.7.0)(lightningcss@1.32.0)(terser@5.48.0)(tsx@4.21.0)(yaml@2.9.0)) + specifier: 4.1.10 + version: 4.1.10(@edge-runtime/vm@5.0.0)(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/coverage-v8@4.1.10)(happy-dom@20.11.1)(jsdom@30.0.0)(vite@8.1.5(@types/node@26.1.2)(esbuild@0.28.1)(terser@5.49.0)(tsx@4.23.1)(yaml@2.9.0)) scratchpad: dependencies: @@ -962,29 +1069,16 @@ importers: packages: - '@adobe/css-tools@4.4.4': - resolution: {integrity: sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==} - - '@ark/schema@0.56.0': - resolution: {integrity: sha512-ECg3hox/6Z/nLajxXqNhgPtNdHWC9zNsDyskwO28WinoFEnWow4IsERNz9AnXRhTZJnYIlAJ4uGn3nlLk65vZA==} - - '@ark/util@0.56.0': - resolution: {integrity: sha512-BghfRC8b9pNs3vBoDJhcta0/c1J1rsoS1+HgVUreMFPdhz/CRAKReAu57YEllNaSy98rWAdY1gE+gFup7OXpgA==} - - '@asamuzakjp/css-color@5.1.11': - resolution: {integrity: sha512-KVw6qIiCTUQhByfTd78h2yD1/00waTmm9uy/R7Ck/ctUyAPj+AEDLkQIdJW0T8+qGgj3j5bpNKK7Q3G+LedJWg==} - engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} - - '@asamuzakjp/dom-selector@7.1.1': - resolution: {integrity: sha512-67RZDnYRc8H/8MLDgQCDE//zoqVFwajkepHZgmXrbwybzXOEwOWGPYGmALYl9J2DOLfFPPs6kKCqmbzV895hTQ==} - engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + '@adobe/css-tools@4.5.0': + resolution: {integrity: sha512-6OzddxPio9UiWTCemp4N8cYLV2ZN1ncRnV1cVGtve7dhPOtRkleRyx32GQCYSwDYgaHU3USMm84tNsvKzRCa1Q==} - '@asamuzakjp/generational-cache@1.0.1': - resolution: {integrity: sha512-wajfB8KqzMCN2KGNFdLkReeHncd0AslUSrvHVvvYWuU8ghncRJoA50kT3zP9MVL0+9g4/67H+cdvBskj9THPzg==} - engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + '@asamuzakjp/css-color@6.0.5': + resolution: {integrity: sha512-mbhpPMmnw/kwW19aRNmSUl1QzLbdGo1SCuE49BT98MNwqF6zaHb3o2owssFc/PEO/4t2UjqtCNwocuDtJornzA==} + engines: {node: ^22.13.0 || >=24.0.0} - '@asamuzakjp/nwsapi@2.3.9': - resolution: {integrity: sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==} + '@asamuzakjp/dom-selector@8.3.0': + resolution: {integrity: sha512-UJLfKXBhrc8i1vH2eJXuYQMwlsLKWFw3O+CPqXSuVEiikeAim3UgrfWX0k4tA/X8cRFM8iZ7OaqBokFGbYusdg==} + engines: {node: ^22.13.0 || >=24.0.0} '@azure-rest/core-client@2.6.0': resolution: {integrity: sha512-iuFKDm8XPzNxPfRjhyU5/xKZmcRDzSuEghXDHHk4MjBV/wFL34GmYVBZnn9wmuoLBeS1qAw9ceMdaeJBPcB1QQ==} @@ -1057,12 +1151,12 @@ packages: resolution: {integrity: sha512-DoeSJ9U5KPAIZoHsPywvfEj2MhBniQe0+FSpjLUTdWoIkI999GB5USkW6nNEHnIaLVxROHXvprWA1KzdS1VQ4A==} engines: {node: '>=20'} - '@babel/cli@7.28.6': - resolution: {integrity: sha512-6EUNcuBbNkj08Oj4gAZ+BUU8yLCgKzgVX4gaTh09Ya2C8ICM4P+G30g4m3akRxSYAp3A/gnWchrNst7px4/nUQ==} - engines: {node: '>=6.9.0'} + '@babel/cli@8.0.4': + resolution: {integrity: sha512-mgg9G7dJw7xzx/0Sn8eWQkDpEcQrlDdEV4Y4Ii+8Oay88+lK45vNuSavRUj4g+e5Yfw4tkH4U3ObBFOMJhy4oQ==} + engines: {node: ^22.18.0 || >=24.11.0} hasBin: true peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': ^8.0.0 '@babel/code-frame@7.29.0': resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} @@ -1072,73 +1166,71 @@ packages: resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} engines: {node: '>=6.9.0'} - '@babel/compat-data@7.29.0': - resolution: {integrity: sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==} - engines: {node: '>=6.9.0'} + '@babel/code-frame@8.0.0': + resolution: {integrity: sha512-dYYg153EyN2Ekbqw2zAsbd6/JR+9N2SEoC7YV2GyyqMM7x9bLDTjBD6XBhSMLH0wtIVyJj03jWNriQhaN+eoCw==} + engines: {node: ^22.18.0 || >=24.11.0} '@babel/compat-data@7.29.7': resolution: {integrity: sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==} engines: {node: '>=6.9.0'} - '@babel/core@7.29.0': - resolution: {integrity: sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==} - engines: {node: '>=6.9.0'} + '@babel/compat-data@8.0.0': + resolution: {integrity: sha512-DOjnob/cXOUgDOozCDeq/aK2p5y8dUIVdf6tNhEV1HQRd6I8aQ4f4fbtHRVEvb6lP3BGomrKHiS8ICAASSVQSw==} + engines: {node: ^22.18.0 || >=24.11.0} '@babel/core@7.29.7': resolution: {integrity: sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==} engines: {node: '>=6.9.0'} - '@babel/generator@7.29.1': - resolution: {integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==} - engines: {node: '>=6.9.0'} + '@babel/core@8.0.1': + resolution: {integrity: sha512-5FgxM4dLQpMJHSiVATk8foW263dVHQHBVpXYiimNECVWG01f4nFyEbQixeT6Mwvg7TayREJ2gpKl3o2RoMdnqw==} + engines: {node: ^22.18.0 || >=24.11.0} '@babel/generator@7.29.7': resolution: {integrity: sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==} engines: {node: '>=6.9.0'} - '@babel/helper-annotate-as-pure@7.27.3': - resolution: {integrity: sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==} - engines: {node: '>=6.9.0'} + '@babel/generator@8.0.0': + resolution: {integrity: sha512-NT9NrVwJsbSV6Y2FSstWa71EETOnzrjkL5/wX3D2mYHtKM+qvqB1DvR4D0Setb/gDBsHzRICifwEWMO8CnTF6g==} + engines: {node: ^22.18.0 || >=24.11.0} - '@babel/helper-compilation-targets@7.28.6': - resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==} + '@babel/helper-annotate-as-pure@7.29.7': + resolution: {integrity: sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw==} engines: {node: '>=6.9.0'} '@babel/helper-compilation-targets@7.29.7': resolution: {integrity: sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==} engines: {node: '>=6.9.0'} - '@babel/helper-create-class-features-plugin@7.28.6': - resolution: {integrity: sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow==} + '@babel/helper-compilation-targets@8.0.0': + resolution: {integrity: sha512-JwculLABZvyPvyLBpwU/E/IbH2uM3mnxNtIJpxnIfb24y1PrdVxK5Dqjle4DpgqpGRnwgC7G8IkzPdSXZrO1Ew==} + engines: {node: ^22.18.0 || >=24.11.0} + + '@babel/helper-create-class-features-plugin@7.29.7': + resolution: {integrity: sha512-IY3ZD9Tmooqr3TUhc3DUWxiuo8xx1DWLhd5M7hQ+ZWJamqM2BbalrBJb2MisSLoYorOj75U03qULCxQTY9r3hg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 - '@babel/helper-globals@7.28.0': - resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} - engines: {node: '>=6.9.0'} - '@babel/helper-globals@7.29.7': resolution: {integrity: sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==} engines: {node: '>=6.9.0'} - '@babel/helper-member-expression-to-functions@7.28.5': - resolution: {integrity: sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==} - engines: {node: '>=6.9.0'} + '@babel/helper-globals@8.0.0': + resolution: {integrity: sha512-lLozHOM6sWWlxNo8CYqHy4MBZeTvHXNgVPBfPOGsjPKUzHC2Az9QwB6gxdQmpwHl6GlQtbGgS+lj5887guDiLw==} + engines: {node: ^22.18.0 || >=24.11.0} - '@babel/helper-module-imports@7.28.6': - resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==} + '@babel/helper-member-expression-to-functions@7.29.7': + resolution: {integrity: sha512-j+7JYmk1JYDtACIGj0QJqqWZjoUpMoEikQGADMaHgCMCSDqd2+P32rfcibUNrGOMWrlzK1WJBdxrB3JJQZwWtg==} engines: {node: '>=6.9.0'} '@babel/helper-module-imports@7.29.7': resolution: {integrity: sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==} engines: {node: '>=6.9.0'} - '@babel/helper-module-transforms@7.28.6': - resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 + '@babel/helper-module-imports@8.0.0': + resolution: {integrity: sha512-NZ7mSS93o4ndX4KrbD7W8Sf3QT8Qe24PrnFyUcuOPDzK6faqDFKjY9RG7he7+I7FdiQ4llpnosFqzrXa+Vy3Ew==} + engines: {node: ^22.18.0 || >=24.11.0} '@babel/helper-module-transforms@7.29.7': resolution: {integrity: sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==} @@ -1146,70 +1238,78 @@ packages: peerDependencies: '@babel/core': ^7.0.0 - '@babel/helper-optimise-call-expression@7.27.1': - resolution: {integrity: sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==} - engines: {node: '>=6.9.0'} + '@babel/helper-module-transforms@8.0.1': + resolution: {integrity: sha512-UgAhl1kqiW5ciE0yCXqqvnb4H2n3IELJ7lIIQRezwDPilPEZX5i+Rvbja9MFTkwUn2biEiSMeV31aUzR4Lwakw==} + engines: {node: ^22.18.0 || >=24.11.0} + peerDependencies: + '@babel/core': ^8.0.0 - '@babel/helper-plugin-utils@7.28.6': - resolution: {integrity: sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==} + '@babel/helper-optimise-call-expression@7.29.7': + resolution: {integrity: sha512-+kmGVjcT9RGYzoDwdwEqEvGgKe3BYq+O1iGzjFubaNgZHwYHP6lsF2Yghf4kEuv9BV7tYDZ913aBW9am6YKong==} engines: {node: '>=6.9.0'} '@babel/helper-plugin-utils@7.29.7': resolution: {integrity: sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==} engines: {node: '>=6.9.0'} - '@babel/helper-replace-supers@7.28.6': - resolution: {integrity: sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==} - engines: {node: '>=6.9.0'} + '@babel/helper-plugin-utils@8.0.1': + resolution: {integrity: sha512-3PKFgjTyPlhFhorfP+SjKQxLViIL++zWjFOO4hGriYU+Bsm983DxEM1JmDRJVWXV0O9npu+xXRqz7Pbd3mh70g==} + engines: {node: ^22.18.0 || >=24.11.0} peerDependencies: - '@babel/core': ^7.0.0 + '@babel/core': ^8.0.0 - '@babel/helper-skip-transparent-expression-wrappers@7.27.1': - resolution: {integrity: sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==} + '@babel/helper-replace-supers@7.29.7': + resolution: {integrity: sha512-atfGXWSeCiF4DnKZIfmJfQRkSw9b9gNNXR1kqKjbhG4pGYCOnkp8OcTB8E3NXjBu8NpheSnOeNKz8KT7UNFTmQ==} engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 - '@babel/helper-string-parser@7.27.1': - resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + '@babel/helper-skip-transparent-expression-wrappers@7.29.7': + resolution: {integrity: sha512-brcMGQaVzIeUb+6/bs1Av0f8YuNNjKY2JyvfRCsFuFsdKccEQ5Ges2y74D74NZ1Rz8lKJ9ksJkfqwQFJ/iNEyQ==} engines: {node: '>=6.9.0'} '@babel/helper-string-parser@7.29.7': resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} engines: {node: '>=6.9.0'} - '@babel/helper-validator-identifier@7.28.5': - resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} - engines: {node: '>=6.9.0'} + '@babel/helper-string-parser@8.0.0': + resolution: {integrity: sha512-6mJgmFFFIIO82vvoLt9XtRC7/TkzXfts1t/SpRX4IHSzMgqoPYCWesVu1udUPUWioAE/2fcG6WuI8zrkE1gwrg==} + engines: {node: ^22.18.0 || >=24.11.0} '@babel/helper-validator-identifier@7.29.7': resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} engines: {node: '>=6.9.0'} - '@babel/helper-validator-option@7.27.1': - resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} - engines: {node: '>=6.9.0'} + '@babel/helper-validator-identifier@8.0.4': + resolution: {integrity: sha512-4wFaiLd0bVo4cIoTXI3zKI038NIWE/cr3jvBjejOVYVxV/m8Ltav1USiGzG1fmS5J2RhgEOgXNNK46cRPnRsrg==} + engines: {node: ^22.18.0 || >=24.11.0} '@babel/helper-validator-option@7.29.7': resolution: {integrity: sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==} engines: {node: '>=6.9.0'} - '@babel/helpers@7.28.6': - resolution: {integrity: sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==} - engines: {node: '>=6.9.0'} + '@babel/helper-validator-option@8.0.0': + resolution: {integrity: sha512-U4Dybxh4WESWHt5XhBeExi4DrY0/DNK1aHpQbsrQXCUbFHuMweT0TpLEWKvaraV2Y6fS+ZXunsZ8zIuZIgvF2Q==} + engines: {node: ^22.18.0 || >=24.11.0} '@babel/helpers@7.29.7': resolution: {integrity: sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==} engines: {node: '>=6.9.0'} - '@babel/parser@7.29.3': - resolution: {integrity: sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==} - engines: {node: '>=6.0.0'} - hasBin: true + '@babel/helpers@8.0.0': + resolution: {integrity: sha512-wfbi91pM3py96oIiJEz7qIpyXDytgr9zQC1HEWwlGNVRAEmItuU/0a41ZUKu1sJGyhhOIpc4t5vk4PYzt8wpsg==} + engines: {node: ^22.18.0 || >=24.11.0} '@babel/parser@7.29.7': resolution: {integrity: sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==} engines: {node: '>=6.0.0'} hasBin: true + '@babel/parser@8.0.4': + resolution: {integrity: sha512-srpptsAkEbbNIC/q8nT7o+m6CQe8CJUTV/t7MYc9NnWlgYVtHOb7JH6SorxMhN0kuRJjVqXbKClG6xSbPtzz+g==} + engines: {node: ^22.18.0 || >=24.11.0} + hasBin: true + '@babel/plugin-syntax-async-generators@7.8.4': resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} peerDependencies: @@ -1231,8 +1331,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-syntax-flow@7.28.6': - resolution: {integrity: sha512-D+OrJumc9McXNEBI/JmFnc/0uCM2/Y3PEBG3gfV3QIYkKv5pvnpzFrl1kYCrcHJP8nOeFB/SHi1IHz29pNGuew==} + '@babel/plugin-syntax-flow@7.29.7': + resolution: {integrity: sha512-ajMX6QPcyomotqwpzhkYGxcK2i/us0rs1Qo9QvUpa+Fca0FTmqrzKrctoIYLMxcOhGZldGT/BAVkRGTWBiR8gQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1253,8 +1353,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-syntax-jsx@7.28.6': - resolution: {integrity: sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==} + '@babel/plugin-syntax-jsx@7.29.7': + resolution: {integrity: sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1301,74 +1401,80 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-syntax-typescript@7.28.6': - resolution: {integrity: sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==} + '@babel/plugin-syntax-typescript@7.29.7': + resolution: {integrity: sha512-ngr+82Sh0xMz25TPCZi+nC2iTzjfCdWS2ONXTp/PtSCHCgaCNBpdMqgvJ2ccdLlClVZ7sisIgB914j/JFe+RZA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-class-properties@7.28.6': - resolution: {integrity: sha512-dY2wS3I2G7D697VHndN91TJr8/AAfXQNt5ynCTI/MpxMsSzHp+52uNivYT5wCPax3whc47DR8Ba7cmlQMg24bw==} + '@babel/plugin-transform-class-properties@7.29.7': + resolution: {integrity: sha512-GtcpjFvanPfzNQi3eTitsCqtRRmmqzpy/A+yhTR1HaZo1Ly3EA8ZXxlPyHdR8/IuRMYc3E4wdGBewB2QKQjAaA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-export-namespace-from@7.27.1': - resolution: {integrity: sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ==} - engines: {node: '>=6.9.0'} + '@babel/plugin-transform-export-namespace-from@8.0.1': + resolution: {integrity: sha512-bFzznm46bvWGaTYKle3iolbBJ+oPBfUjwCPesxlFE3SQ7DaY9EHf/8Y5ZzrodKJi8JDdcAyaVWaDUSVyhULh0g==} + engines: {node: ^22.18.0 || >=24.11.0} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': ^8.0.0 - '@babel/plugin-transform-flow-strip-types@7.27.1': - resolution: {integrity: sha512-G5eDKsu50udECw7DL2AcsysXiQyB7Nfg521t2OAJ4tbfTJ27doHLeF/vlI1NZGlLdbb/v+ibvtL1YBQqYOwJGg==} + '@babel/plugin-transform-flow-strip-types@7.29.7': + resolution: {integrity: sha512-wRHeUjUjCZnMHmiO5bRgjFLcoEh7JyTdByOW11ahhwNa4V0bmeGEaIvt51yq0zQp2yWIpqfxXXPyUP6GFJZHOQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-modules-commonjs@7.28.6': - resolution: {integrity: sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA==} + '@babel/plugin-transform-modules-commonjs@7.29.7': + resolution: {integrity: sha512-j0vCldybPC5b5dwCQOJ21uKtHzt7hxLygJTg9eF1ScfaikEDNfzn94XoW5Fi+seBR0nCyL23xaBFFkq7dTM8XQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-nullish-coalescing-operator@7.28.6': - resolution: {integrity: sha512-3wKbRgmzYbw24mDJXT7N+ADXw8BC/imU9yo9c9X9NKaLF1fW+e5H1U5QjMUBe4Qo4Ox/o++IyUkl1sVCLgevKg==} + '@babel/plugin-transform-modules-commonjs@8.0.1': + resolution: {integrity: sha512-PMuzulWrrzFNmY3lXSk/tV9NRb7y0eZZLJY4UEo2TKszroxvUZHAPPi+T9FDyrQhod+TQA+t+8/QYaaMpiEuhA==} + engines: {node: ^22.18.0 || >=24.11.0} + peerDependencies: + '@babel/core': ^8.0.0 + + '@babel/plugin-transform-nullish-coalescing-operator@7.29.7': + resolution: {integrity: sha512-idmp1dFaekP9GbcMvG24Kvw2BfhFZjHnNJCkV4WuIY4PskJzwI3f1N5OdgYke38T7rftO6ERulFRn2cFeZwRkg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-optional-chaining@7.28.6': - resolution: {integrity: sha512-A4zobikRGJTsX9uqVFdafzGkqD30t26ck2LmOzAuLL8b2x6k3TIqRiT2xVvA9fNmFeTX484VpsdgmKNA0bS23w==} + '@babel/plugin-transform-optional-chaining@7.29.7': + resolution: {integrity: sha512-6GM1dhvK3gNODkXcEcMCOLEDCLSoZ/sBbro2Ax8HURyasQ4NshagQixkRFdh5niI6E4gmA/jYI/4aT7rRos3ZQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-private-methods@7.28.6': - resolution: {integrity: sha512-piiuapX9CRv7+0st8lmuUlRSmX6mBcVeNQ1b4AYzJxfCMuBfB0vBXDiGSmm03pKJw1v6cZ8KSeM+oUnM6yAExg==} + '@babel/plugin-transform-private-methods@7.29.7': + resolution: {integrity: sha512-/6Rz4DK1ETDEM/bWHsPHcaEe7ZaT1EqSXjtSP/L0DijOYuaUhiRiOKcwpZ8P7zR4xXEHc2ITdiCgBm9Tpyv9ug==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-typescript@7.28.6': - resolution: {integrity: sha512-0YWL2RFxOqEm9Efk5PvreamxPME8OyY0wM5wh5lHjF+VtVhdneCWGzZeSqzOfiobVqQaNCd2z0tQvnI9DaPWPw==} + '@babel/plugin-transform-typescript@7.29.7': + resolution: {integrity: sha512-jK52h8LaLc7JarhQV2ofeFMts4H7vnOXnqZNA6fYglBTZewRBE51KWt3BUltW1P+KoPsYkHoJeXePuz4zo2LMw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/preset-flow@7.27.1': - resolution: {integrity: sha512-ez3a2it5Fn6P54W8QkbfIyyIbxlXvcxyWHHvno1Wg0Ej5eiJY5hBb8ExttoIOJJk7V2dZE6prP7iby5q2aQ0Lg==} + '@babel/preset-flow@7.29.7': + resolution: {integrity: sha512-KYIRV0BuaN68CDdsqFkAD7MU7yipUqQNuNElwATdxaIdpTjhvtY82QvkBJs7zV3Evxj2jFAAZ1iO8nyy0nhjqA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/preset-typescript@7.28.5': - resolution: {integrity: sha512-+bQy5WOI2V6LJZpPVxY+yp66XdZ2yifu0Mc1aP5CQKgjn4QM5IN2i5fAZ4xKop47pr8rpVhiAeu+nDQa12C8+g==} + '@babel/preset-typescript@7.29.7': + resolution: {integrity: sha512-/Foi8vKY2EVbed/1eZx0gJEEwHAIxogrySI7rULcRIvhZzbvoE/b5qG5Ghc0WKAFKOHA9SD1x7RsFlOYdutIiQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/register@7.28.6': - resolution: {integrity: sha512-pgcbbEl/dWQYb6L6Yew6F94rdwygfuv+vJ/tXfwIOYAfPB6TNWpXUMEtEq3YuTeHRdvMIhvz13bkT9CNaS+wqA==} + '@babel/register@7.29.7': + resolution: {integrity: sha512-AMGJoWuES861riy6pcB0fphE1YXybtQnBYQMuIyPv6mKLiosfa79BKTnAOyx215c/3RJPJpdQwoHZ3earVH7AA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1381,30 +1487,30 @@ packages: resolution: {integrity: sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==} engines: {node: '>=6.9.0'} - '@babel/template@7.28.6': - resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} - engines: {node: '>=6.9.0'} - '@babel/template@7.29.7': resolution: {integrity: sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==} engines: {node: '>=6.9.0'} - '@babel/traverse@7.29.0': - resolution: {integrity: sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==} - engines: {node: '>=6.9.0'} + '@babel/template@8.0.0': + resolution: {integrity: sha512-eAD0QW/AlbamBbw0FeGiwasbCVPq5ncW0HNVyLP3B9czqLyh4gvw+5JTSNt6le9+ziAU7mqDZsKTHf3jTb4chQ==} + engines: {node: ^22.18.0 || >=24.11.0} '@babel/traverse@7.29.7': resolution: {integrity: sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==} engines: {node: '>=6.9.0'} - '@babel/types@7.29.0': - resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} - engines: {node: '>=6.9.0'} + '@babel/traverse@8.0.4': + resolution: {integrity: sha512-bZnmqzGG8UZneG1lLxBoWIH0G6Gr1D846Yu4/3XnY6FhCndMR49u26nTY08u/dAxWmLWF9vGQOuC+84FfIUoeg==} + engines: {node: ^22.18.0 || >=24.11.0} '@babel/types@7.29.7': resolution: {integrity: sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==} engines: {node: '>=6.9.0'} + '@babel/types@8.0.4': + resolution: {integrity: sha512-eY+Yn3dCqTGmyiq2QRU66lA5FL8lqqqvecHt0fF3uHONIa7ToYsaCiWV8lOKqAs0Rb2SjixiKFROngnulPtt2g==} + engines: {node: ^22.18.0 || >=24.11.0} + '@balena/dockerignore@1.0.2': resolution: {integrity: sha512-wMue2Sy4GAVTk6Ic4tJVcnfdau+gx2EnG7S+uAEe+TWJFqE4YoWN4/H8MSLj4eYJKxGg26lZwboEniNiNwZQ6Q==} @@ -1419,148 +1525,161 @@ packages: resolution: {integrity: sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==} hasBin: true - '@bundle-stats/cli-utils@4.22.1': - resolution: {integrity: sha512-h7Ny07HXUe1jqJBcuhilB4GTXQCwvYzKT6VW9JXcqwylV5x7K1GsMT0OkGh2OaY8XXMT+VK16vAubWEl/z1jQg==} + '@bundle-stats/cli-utils@4.22.2': + resolution: {integrity: sha512-p0Dxia+5iNcC3dL+eHlhPGpUGFg+Ech3I7nGiKcAg2fSwaZEV8wQvCjcjiL7IU8EBrEcnfiQJsipCvjP842WWQ==} engines: {node: '>= 14.0'} - '@bundle-stats/html-templates@4.22.1': - resolution: {integrity: sha512-TUTXt+mG1X9IB6uZasvCK5tHqraT5pVLBi7t1rH0kogWTeBhSSkU/LCtWrSo7f1uWUO0XKjWAjAxZ34gjQU3bw==} + '@bundle-stats/html-templates@4.22.2': + resolution: {integrity: sha512-oqKVATzwLMaXFUhC1EWaSQsv+f5OAKNIHMLOYrXaDr4U9mBQLIIIcFnQtP2LHoPSPuphXqSDUBqQyh+HHlImoA==} - '@bundle-stats/plugin-webpack-filter@4.22.1': - resolution: {integrity: sha512-VXY72C6J0brjs8VIIdGqSC00xDgjTOAqKY2zDi4Gf60CuF89PDIZ6BzETg4T09//BuHRUJnFNBgloTy2F8Q9Vg==} + '@bundle-stats/plugin-webpack-filter@4.22.2': + resolution: {integrity: sha512-+6X4/IEwvcCKClFm4+hHsnvMUOVPX2S64d9zeR5JiDZUHWwCI3MsQX0ukXQ0hzZCxK1bfbDOX/t1fUphdG5SyA==} engines: {node: '>= 14.0'} peerDependencies: core-js: ^3.0.0 - '@bundle-stats/plugin-webpack-validate@4.22.1': - resolution: {integrity: sha512-Su6JFFI+IiONvjuGbtC/CIRk+YZy1ksJJSWGDd28Zca9YFr92eyVGeEQrz0+OcjeIV9pjgKITB2Ie+hNhyE85g==} + '@bundle-stats/plugin-webpack-validate@4.22.2': + resolution: {integrity: sha512-JyHhcqLMhqJLDXMdQYM1o+cNI0YCY9uh9br8Stj/+Pgtti8W0KIhFzwT4no2WUMKoTGCVgMpiDGvSL3nNLX8zQ==} engines: {node: '>= 14.0'} - '@bundle-stats/utils@4.22.1': - resolution: {integrity: sha512-MNwfZhqIOtTVt+yV7jXOrYLo19peqach4GGPzF+lp47FeJK6D0WULy2/LLHRzMJTx10Dc70P+euw2FbpeqY2kw==} + '@bundle-stats/utils@4.22.2': + resolution: {integrity: sha512-Lkeiz6vx2OssLjZ0nJN3RAOZl0LphVygZknPl7EIonXyAvzlx/CnWE5uoAbmtxXhZW8MpufPAy8/MP55t2Hcwg==} engines: {node: '>= 14.0'} peerDependencies: core-js: ^3.0.0 lodash: ^4.0.0 - '@changesets/apply-release-plan@7.1.1': - resolution: {integrity: sha512-9qPCm/rLx/xoOFXIHGB229+4GOL76S4MC+7tyOuTsR6+1jYlfFDQORdvwR5hDA6y4FL2BPt3qpbcQIS+dW85LA==} + '@changesets/apply-release-plan@8.0.0-next.10': + resolution: {integrity: sha512-Yps335/MoZe8nKMJ8Jt4CCZ4N9zFF+5q0INfmcCuDJebrB/cvCfJMJLMVQ/Pz4lFY7fWgCVFSsvRFHiT23G08g==} + engines: {node: ^22.11 || ^24 || >=26} - '@changesets/assemble-release-plan@6.0.10': - resolution: {integrity: sha512-rSDcqdJ9KbVyjpBIuCidhvZNIiVt1XaIYp73ycVQRIA5n/j6wQaEk0ChRLMUQ1vkxZe51PTQ9OIhbg6HQMW45A==} + '@changesets/assemble-release-plan@7.0.0-next.10': + resolution: {integrity: sha512-zuC3jl9KQi2dc0WeT1mpwPDfk6Y6kswx7sER65YN53AtWY0PJCSa0t9zDMLKfTO0yYeqrk40Z/AEOwum9jA/vg==} + engines: {node: ^22.11 || ^24 || >=26} - '@changesets/changelog-git@0.2.1': - resolution: {integrity: sha512-x/xEleCFLH28c3bQeQIyeZf8lFXyDFVn1SgcBiR2Tw/r4IAWlk1fzxCEZ6NxQAjF2Nwtczoen3OA2qR+UawQ8Q==} + '@changesets/changelog-git@1.0.0-next.9': + resolution: {integrity: sha512-v9YatKMVoljRPRPzI9PoxDHxBsLXZs+ndKBts6nOJlRpGHoGyzScOyC2YYce0krYhFxhn+lOiIto/7AOlTFuwA==} + engines: {node: ^22.11 || ^24 || >=26} - '@changesets/changelog-github@0.7.0': - resolution: {integrity: sha512-rBsbRvc4TVn+FvFnOVM3LxlFJfTXXCp8gfVJ+0BubxWNSVnLuAzowi5j+IEraLLP52w8AAs9QfKbPS3MMiXQJA==} + '@changesets/changelog-github@1.0.0-next.9': + resolution: {integrity: sha512-xGtpDLiJeQdyoOGNfKIKp09+o41J7KTNJ+QXWq8OqrzFbOCuI+KZC1AlyIDAsjZce3tBYLhu9sQtAMkmRnGOew==} + engines: {node: ^22.11 || ^24 || >=26} - '@changesets/cli@2.31.0': - resolution: {integrity: sha512-AhI4enNTgHu2IZr6K4WZyf0EPch4XVMn1yOMFmCD9gsfBGqMYaHXls5HyDv6/CL5axVQABz68eG30eCtbr2wFg==} + '@changesets/cli@3.0.0-next.11': + resolution: {integrity: sha512-yH79OtoLPruqx0BTO0eKE1rxupgk7Qf1SzmVhwdDqikkOoFlPD5/QEAN3zvRXLoGcT0VN0Wuq2zCFTi8CyJgLA==} + engines: {node: ^22.11 || ^24 || >=26, npm: '>=10.9.0', pnpm: '>=10.0.0', yarn: '>=4.5.2'} hasBin: true - '@changesets/config@3.1.4': - resolution: {integrity: sha512-pf0bvD/v6WI2cRlZ6hzpjtZdSlXDXMAJ+Iz7xfFzV4ZxJ8OGGAON+1qYc99ZPrijnt4xp3VGG7eNvAOGS24V1Q==} - - '@changesets/errors@0.2.0': - resolution: {integrity: sha512-6BLOQUscTpZeGljvyQXlWOItQyU71kCdGz7Pi8H8zdw6BI0g3m43iL4xKUVPWtG+qrrL9DTjpdn8eYuCQSRpow==} + '@changesets/config@4.0.0-next.9': + resolution: {integrity: sha512-V/dN3IQ2gnYguztEQuT6z+IQ6vqcA0KUue0CdjWOjLRHA8dQaMrcKBKZlbNecvm31fGc1qIWu9IyXML1Xf3NUw==} + engines: {node: ^22.11 || ^24 || >=26} - '@changesets/get-dependents-graph@2.1.4': - resolution: {integrity: sha512-ZsS00x6WvmHq3sQv8oCMwL0f/z3wbXCVuSVTJwCnnmbC/iBdNJGFx1EcbMG4PC6sXRyH69liM4A2WKXzn/kRPg==} + '@changesets/errors@1.0.0-next.4': + resolution: {integrity: sha512-ZUoabMz+a4t0/Eoz5uUHzufcWfXIlkpCuCUWUMkWErqcCjgB0pwuPWSU2CamhufsG7ZKDUlUoIGVSQWyPc77CA==} + engines: {node: ^22.11 || ^24 || >=26} - '@changesets/get-github-info@0.8.0': - resolution: {integrity: sha512-cRnC+xdF0JIik7coko3iUP9qbnfi1iJQ3sAa6dE+Tx3+ET8bjFEm63PA4WEohgjYcmsOikPHWzPsMWWiZmntOQ==} + '@changesets/format@0.1.1': + resolution: {integrity: sha512-OBN/xfe+lcNWCv8P4Ogop9EOTi5QRKd1c2JJfZRBT9AT3AybzDYDfHeX4Y7j20Q4e16BPu3XvcdOe9sPZmTEig==} + engines: {node: ^22.11 || ^24 || >=26} - '@changesets/get-release-plan@4.0.16': - resolution: {integrity: sha512-2K5Om6CrMPm45rtvckfzWo7e9jOVCKLCnXia5eUPaURH7/LWzri7pK1TycdzAuAtehLkW7VPbWLCSExTHmiI6g==} + '@changesets/get-dependents-graph@3.0.0-next.9': + resolution: {integrity: sha512-zyukhblBz0TO7Ze4rJHS/LN8dLvNpKPGEm3YhYRpZnlRCT+r4X9YtrHJjUFHbnjn2wNRRzzy6eHzsFn43YI3jQ==} + engines: {node: ^22.11 || ^24 || >=26} - '@changesets/get-version-range-type@0.4.0': - resolution: {integrity: sha512-hwawtob9DryoGTpixy1D3ZXbGgJu1Rhr+ySH2PvTLHvkZuQ7sRT4oQwMh0hbqZH1weAooedEjRsbrWcGLCeyVQ==} + '@changesets/get-github-info@1.0.0-next.4': + resolution: {integrity: sha512-Bosh+XOoFvLMzAj301tg6phbrEggBtvEccrnceEvYsKSM7PjcIa32Fy22SU5g+501HZywkdwcAlybdWF+e/zzw==} + engines: {node: ^22.11 || ^24 || >=26} - '@changesets/git@3.0.4': - resolution: {integrity: sha512-BXANzRFkX+XcC1q/d27NKvlJ1yf7PSAgi8JG6dt8EfbHFHi4neau7mufcSca5zRhwOL8j9s6EqsxmT+s+/E6Sw==} + '@changesets/git@4.0.0-next.9': + resolution: {integrity: sha512-7WqrwEVpsKZKbBGR7hPRuHTbkIMiDdoyxSsQDOWrmuBh13wDTGaIV6yYQoOs0I3l56khxydm/68v01c/+4g9jQ==} + engines: {node: ^22.11 || ^24 || >=26} - '@changesets/logger@0.1.1': - resolution: {integrity: sha512-OQtR36ZlnuTxKqoW4Sv6x5YIhOmClRd5pWsjZsddYxpWs517R0HkyiefQPIytCVh4ZcC5x9XaG8KTdd5iRQUfg==} + '@changesets/parse@1.0.0-next.10': + resolution: {integrity: sha512-ENFmw8Ytq6fNY3O+uC99KDdChZ3dM8PFgth3qi1Ms78GCCHsSgik0ojQwGht5VPsP6WhfTkNqW3fCakFt+euHg==} + engines: {node: ^22.11 || ^24 || >=26} - '@changesets/parse@0.4.3': - resolution: {integrity: sha512-ZDmNc53+dXdWEv7fqIUSgRQOLYoUom5Z40gmLgmATmYR9NbL6FJJHwakcCpzaeCy+1D0m0n7mT4jj2B/MQPl7A==} + '@changesets/pre@3.0.0-next.9': + resolution: {integrity: sha512-NznGjw/PCDy6d74DusPriYbLZQl6O8cvRwuv/qsSKUTF/9O9+EvJnXTx+wtHS9SLJnJqRaGprQ1EMSkjRRyDGQ==} + engines: {node: ^22.11 || ^24 || >=26} - '@changesets/pre@2.0.2': - resolution: {integrity: sha512-HaL/gEyFVvkf9KFg6484wR9s0qjAXlZ8qWPDkTyKF6+zqjBe/I2mygg3MbpZ++hdi0ToqNUF8cjj7fBy0dg8Ug==} + '@changesets/read@1.0.0-next.10': + resolution: {integrity: sha512-wmhNbv8gnN6Gv6dpFftmqkyy5OTQfFWFlyfwLa7s+b0vMihA5ZYwU/Zd8VN53AsSlYp6k8s4sc43ZJk0zUIkRA==} + engines: {node: ^22.11 || ^24 || >=26} - '@changesets/read@0.6.7': - resolution: {integrity: sha512-D1G4AUYGrBEk8vj8MGwf75k9GpN6XL3wg8i42P2jZZwFLXnlr2Pn7r9yuQNbaMCarP7ZQWNJbV6XLeysAIMhTA==} + '@changesets/should-skip-package@1.0.0-next.9': + resolution: {integrity: sha512-KaDRCnNy+J4Q8vTZIyKGV3wtDyW79bJN7+XBg919ZKneAj7jJpJLIPkVK+MlyGqyWoxhWEcDBRZ3aTjVkx/tnw==} + engines: {node: ^22.11 || ^24 || >=26} - '@changesets/should-skip-package@0.1.2': - resolution: {integrity: sha512-qAK/WrqWLNCP22UDdBTMPH5f41elVDlsNyat180A33dWxuUDyNpg6fPi/FyTZwRriVjg0L8gnjJn2F9XAoF0qw==} + '@changesets/types@7.0.0-next.9': + resolution: {integrity: sha512-okzQIKr+HZXZne37+QI0o0GrKNNTinP8jqsSdr2jUXEt8qhiwdG/X2o7aukp4WBtG4R395BOGyJVfaCy+xg7IA==} + engines: {node: ^22.11 || ^24 || >=26} - '@changesets/types@4.1.0': - resolution: {integrity: sha512-LDQvVDv5Kb50ny2s25Fhm3d9QSZimsoUGBsUioj6MC3qbMUCuC8GPIvk/M6IvXx3lYhAs0lwWUQLb+VIEUCECw==} + '@changesets/write@1.0.0-next.9': + resolution: {integrity: sha512-vZ7iTrI8FhJydoWla4G9LSk1kJf4yOtA9m3h9Wffvn+0v5DXUe3LrP/7ZBxHlFCu+WY5ypKo+ClLaDvB9I7Zpg==} + engines: {node: ^22.11 || ^24 || >=26} - '@changesets/types@6.1.0': - resolution: {integrity: sha512-rKQcJ+o1nKNgeoYRHKOS07tAMNd3YSN0uHaJOZYjBAgxfV7TUE7JE+z4BzZdQwb5hKaYbayKN5KrYV7ODb2rAA==} + '@clack/core@1.4.3': + resolution: {integrity: sha512-/kr3UWNtdJfxZtPgDqUOmG2pvwlmcLGheex5yiZKdwbzZJxhV+HMNR9QNmyY5cGwTNV6LrR7Jtp+KjhUAP1qBQ==} + engines: {node: '>= 20.12.0'} - '@changesets/write@0.4.0': - resolution: {integrity: sha512-CdTLvIOPiCNuH71pyDu3rA+Q0n65cmAbXnwWH84rKGiFumFzkmHNT8KHTMEchcxN+Kl8I54xGUhJ7l3E7X396Q==} + '@clack/prompts@1.7.0': + resolution: {integrity: sha512-y7/yvZ2TPAnR9+jnc00klvNNLkJiXFFrQA/hlLCcxA9a2A4zQIOimyFQ9XfwYKiGD1fb5GY8vbKIIgO8d5Tb2A==} + engines: {node: '>= 20.12.0'} - '@clickhouse/client-common@1.18.4': - resolution: {integrity: sha512-kPPtv8yQmplNAxfrAJvwBJq5dd+IWRewEbXSpUvtyEJXlrB8lt/ZH63jUS81Nmd+lK5MRvpOFXPoN3iogkvg+A==} - - '@clickhouse/client@1.18.4': - resolution: {integrity: sha512-jjCrddI+e2OVXGh/MQY92K9r8Z/iwqaZtUXNI/MfZ/y9VGYwfbQsXRzp4Jv6w4Hgxvr4sLcz9YwIvkCBQ6X/mw==} - engines: {node: '>=16'} + '@clickhouse/client@1.23.1': + resolution: {integrity: sha512-vs3/Zc1dHvT171btW5nMoPsPCJ6QVJ5pp7obxzO5sjqwFx/jjz9wwCAqcFOdc2DhprugDBaVn+4dVY8hG3A9nw==} + engines: {node: '>=20'} - '@cloudflare/workerd-darwin-64@1.20260507.1': - resolution: {integrity: sha512-S85aMwcaPJUjKWDiG6iMMnioKWtPLACa6m0j/EhHR1GYfVpnxb974cBc6d25L+sf7jHWHJI2u5hGp0UTJ7MtXQ==} + '@cloudflare/workerd-darwin-64@1.20260706.1': + resolution: {integrity: sha512-61XleG5EaE+Zam2Y/fwOcMInMrjd3QMKIG0/+pHycZcVZC5Oxs5GfHSTnvyhcniigGW/bcIkvn1PptWbxlVleQ==} engines: {node: '>=16'} cpu: [x64] os: [darwin] - '@cloudflare/workerd-darwin-arm64@1.20260507.1': - resolution: {integrity: sha512-GMEBu8Zp9Q97HLnf7bWJN4KjWpN5MxpeqdvHjBGWNl8UYprJI0k+Jkp89+Wh5S8vIon+HoVbDfOzPa7VwgL6Eg==} + '@cloudflare/workerd-darwin-arm64@1.20260706.1': + resolution: {integrity: sha512-jbwuMEuhgrXEk+9MD8eHPRsXWlxqtNMqqCMBLJauTHk+NR+U2O6hJXElPqlAVlUTh0A0GPM+DfATupRXm7Ml8w==} engines: {node: '>=16'} cpu: [arm64] os: [darwin] - '@cloudflare/workerd-linux-64@1.20260507.1': - resolution: {integrity: sha512-QlrKEBdgA3uVc0Ok0Q3+0/CW0CTjgj5ySir1i1YY5FXVv0X6GpwtnB5umjunjF2MFprss+L+iFGZzxcSvMC1nA==} + '@cloudflare/workerd-linux-64@1.20260706.1': + resolution: {integrity: sha512-kppCFkt6WHTNmcE83aUj4chWV13hdQKOgXxn53sqpLiVnPi6HEIOWB1DNRAljG9IDt3KMgni9HdzCYVzq2drJA==} engines: {node: '>=16'} cpu: [x64] os: [linux] - '@cloudflare/workerd-linux-arm64@1.20260507.1': - resolution: {integrity: sha512-eGbbupEtK2nh9V9Dhcx3vv3GTKeXqSVNgAEYVCCN0NGS9tl9HbMoHRX/4JL181FKXROMigWBCQVL//qPhsAzBQ==} + '@cloudflare/workerd-linux-arm64@1.20260706.1': + resolution: {integrity: sha512-H4xpocM1vXNfIVrP/JiOhLooWBXi5erE81hBj2wmCvPIUcf+49CuvZEthpVwFYn9SFop/Y8+bUiV0vVrWf8xuw==} engines: {node: '>=16'} cpu: [arm64] os: [linux] - '@cloudflare/workerd-windows-64@1.20260507.1': - resolution: {integrity: sha512-dmClJ/E0BAcuDetQIZFqbeAXejWrG5pysGRMQ6T83Y0IW/7IAamY2zFEkAJ10I5xwZsdHuYsZtzlOxpEXpJs7A==} + '@cloudflare/workerd-windows-64@1.20260706.1': + resolution: {integrity: sha512-SPWuI+kBNtr/mhcKE7XOECgRWOaidmzVmvtmDrRZX5Y4/+XqD/heRAU5Oa17wOZm3eQ1CB8Xzxjrr/6uQaP8YA==} engines: {node: '>=16'} cpu: [x64] os: [win32] - '@cloudflare/workers-types@4.20260511.1': - resolution: {integrity: sha512-FA+si7cOq9i/gtCHhIc0XJL0l1F/ApF+m00752Aj7WZFJrj3ZulT2T8/+rT3BabMT0QEnqFEGIqCgrmqhgEfMg==} + '@cloudflare/workers-types@5.20260708.1': + resolution: {integrity: sha512-FSRyCsxALKmmNnk/2HMkTiX9Iz9yqTiTWX/BJZdffGznMSunXmQgb3mKy9wGBX2BCTLOuRYWL36uh7/3Gm05Eg==} '@cspotcode/source-map-support@0.8.1': resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} engines: {node: '>=12'} - '@csstools/color-helpers@6.0.2': - resolution: {integrity: sha512-LMGQLS9EuADloEFkcTBR3BwV/CGHV7zyDxVRtVDTwdI2Ca4it0CCVTT9wCkxSgokjE5Ho41hEPgb8OEUwoXr6Q==} + '@csstools/color-helpers@6.1.0': + resolution: {integrity: sha512-064IFJdjTfUqnjpCVpMOdbr8FLQBhinbZj6yRv2An2E41O/pLEXqfFRWqGq/SxlE5PEUYTlvWsG2r8MswAVvkg==} engines: {node: '>=20.19.0'} - '@csstools/css-calc@3.2.0': - resolution: {integrity: sha512-bR9e6o2BDB12jzN/gIbjHa5wLJ4UjD1CB9pM7ehlc0ddk6EBz+yYS1EV2MF55/HUxrHcB/hehAyt5vhsA3hx7w==} + '@csstools/css-calc@3.3.0': + resolution: {integrity: sha512-c5ihYsPkdG6JCkU2zTMm4+k6r7RXuGxtWYhu5DHMIiF1FHzrfmHL5so11AoFpUv/tu61xfcmT4AmKoFfMPoqdQ==} engines: {node: '>=20.19.0'} peerDependencies: '@csstools/css-parser-algorithms': ^4.0.0 '@csstools/css-tokenizer': ^4.0.0 - '@csstools/css-color-parser@4.1.0': - resolution: {integrity: sha512-U0KhLYmy2GVj6q4T3WaAe6NPuFYCPQoE3b0dRGxejWDgcPp8TP7S5rVdM5ZrFaqu4N67X8YaPBw14dQSYx3IyQ==} + '@csstools/css-color-parser@4.1.10': + resolution: {integrity: sha512-UZhQLIUyJaaMepqehrCODwCg2KW25vFvLWBmqYFaPclYvvxzj/sG8LBOhBFCp11i9uE7t1EyS+RAoV9tztPFyw==} engines: {node: '>=20.19.0'} peerDependencies: '@csstools/css-parser-algorithms': ^4.0.0 @@ -1572,8 +1691,8 @@ packages: peerDependencies: '@csstools/css-tokenizer': ^4.0.0 - '@csstools/css-syntax-patches-for-csstree@1.1.3': - resolution: {integrity: sha512-SH60bMfrRCJF3morcdk57WklujF4Jr/EsQUzqkarfHXEFcAR1gg7fS/chAE922Sehgzc1/+Tz5H3Ypa1HiEKrg==} + '@csstools/css-syntax-patches-for-csstree@1.1.7': + resolution: {integrity: sha512-fQ+05118eQS1cofO3aJpB5efgpBZMvIzwr/sbC8kDLVA5XLG8q1kJV5yzrUAI1f7lvhPnm8fgIjzFB8/O/5Dig==} peerDependencies: css-tree: ^3.2.1 peerDependenciesMeta: @@ -1584,62 +1703,86 @@ packages: resolution: {integrity: sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==} engines: {node: '>=20.19.0'} - '@dependents/detective-less@5.0.1': - resolution: {integrity: sha512-Y6+WUMsTFWE5jb20IFP4YGa5IrGY/+a/FbOSjDF/wz9gepU2hwCYSXRHP/vPwBvwcY3SVMASt4yXxbXNXigmZQ==} + '@dependents/detective-less@5.0.3': + resolution: {integrity: sha512-v6oD9Ukp+N7V4n6p5I/+mM5fIohSfkrDSGlFm5w/pYmchvbk+sMIHsLxrFJ5Lnujewj1BzWL0K84d88lwZAMQA==} engines: {node: '>=18'} - '@dprint/darwin-arm64@0.54.0': - resolution: {integrity: sha512-yqRI4enH+BDp+4+ZsPVdZM5h873JK1lN7li9l9A5u4C4cvh1oEsiBWAzEPccRkJ2ctF8LgaizBSxO38sqEVYbw==} + '@discoveryjs/json-ext@1.1.0': + resolution: {integrity: sha512-Xc3VhU02wqZ1HvHRJUwL09HkZSTvidqY5Ya0NXBSYOxAp+Ln9dcJr9fySI+CkONzP3PekQo9WdzCv0PGER/mOA==} + engines: {node: '>=14.17.0'} + + '@dprint/android-arm64@0.55.2': + resolution: {integrity: sha512-cywqM0E2h8lCA/b5BMFcuascaGuekm2lnyKb5hRH9juKbsqKeR8A7qf2p3nVWRQ8cM7djOnQwhy8ecQ3qd6j9A==} + cpu: [arm64] + os: [android] + + '@dprint/android-x64@0.55.2': + resolution: {integrity: sha512-BvcqquD94dSYQP2FzBuojU/8vITpwz80uAkub2xCCx5g7zYdUZ98tK+N3GKfRS5AUJ1LpRPJgbO09rRLEo8leA==} + cpu: [x64] + os: [android] + + '@dprint/darwin-arm64@0.55.2': + resolution: {integrity: sha512-HjzasuPaC0EBHxEpS3Px/OPcxqZYln37xuAyYE0GFAhDuaotZEUpM8VE05Tzo4E32y7LKgLPT8CZVtEG5Ck7mQ==} cpu: [arm64] os: [darwin] - '@dprint/darwin-x64@0.54.0': - resolution: {integrity: sha512-W9BARpgHypcQwatg5mnHaCpX6pLX5dBxxiv+tZKruhOmq8MKYOrAYDXlceMuHSowmWREfUF5yL4SRgXDGI6WQw==} + '@dprint/darwin-x64@0.55.2': + resolution: {integrity: sha512-7XSF9XERvimg3XakXNlJRpJM9an5HKibWWShwCJr7Dz1Xp7P3pomjx9AtYV5EeD49GXu9FKdFfDiTC4BJtAVjA==} cpu: [x64] os: [darwin] - '@dprint/linux-arm64-glibc@0.54.0': - resolution: {integrity: sha512-VhM7p70VFuNqxZMdiv1e+nMboPj/hMFlTIBWrRaX7+6VThs9mJr9+94wrUeXgfnfsyaEKSbRFa/dru1PINoSNw==} + '@dprint/linux-arm64-glibc@0.55.2': + resolution: {integrity: sha512-2h5oe4tHGXJOTLU5BxVJLGPP1qQQxxECdjUaRkLxGn2rZIEf/7HwoJ+TjwMUaIVg4QxAFxfiMJJ3MRZjumJpsg==} cpu: [arm64] os: [linux] - '@dprint/linux-arm64-musl@0.54.0': - resolution: {integrity: sha512-QS1A74Lv60/L9oemHCzbHgOLbV2smSJG5IxS5fjf8ZWetyUt918WDzIHBilz/+uiB+OlW2UVTsm952UG0YOrLw==} + '@dprint/linux-arm64-musl@0.55.2': + resolution: {integrity: sha512-Z1X9mNLHWoSI7CHz888H2w27IV2UQ5lL/YbTwgguJM9ApLgvkMer5qr4pLlXF6vEY3+Rm0445eo3uojbjE7PTw==} cpu: [arm64] os: [linux] - '@dprint/linux-loong64-glibc@0.54.0': - resolution: {integrity: sha512-8Myka2/0KbhuZnEKL6jagPXTgDKVpd/tfXDRa0oibUBgaqOSku6iRMzHGa/PhqHL+s14Gcp+/cIHz0zU3Tkgug==} + '@dprint/linux-loong64-glibc@0.55.2': + resolution: {integrity: sha512-qxk4Cg/SjO14DALoG1MFZL+n6pEvCyd7VDHg1cQIpp82mZ+wtRaWkJqSaItWj12x8jihA32wAoQ/8OHz+5FFsQ==} cpu: [loong64] os: [linux] - '@dprint/linux-loong64-musl@0.54.0': - resolution: {integrity: sha512-/AN3xCuMhC4PK7Pbj7/4zBuhFGr4m0OHV/5uGTfzpkKX/3+AXoyKl7PbT2VlNMGXAK0kuRThfjtx23gIwlWk7Q==} + '@dprint/linux-loong64-musl@0.55.2': + resolution: {integrity: sha512-Mr+kRdZnxhUHBmhhADkYOo1g81PYvbh7eBo+lpMStgIvL/5Z93mGAcp/1NFcpeN02pilaI1N59awV08ApJ19cA==} cpu: [loong64] os: [linux] - '@dprint/linux-riscv64-glibc@0.54.0': - resolution: {integrity: sha512-Aw2vXzzwFDpPbXh6ajsSabVCkCc66C3hCyMKprR/IxYvFtjYX80nh1ox0c7iaw6c4HacHMRLGw7FUSXvomPaEQ==} + '@dprint/linux-ppc64-glibc@0.55.2': + resolution: {integrity: sha512-C8XEL3f3yg+MWZD4qlot+ibJ0GDPvCT5jq9ErwPdKteyIPpe6IPSqiEApYSQ194Q7audL8rgS0WdJsW4HNVhmw==} + cpu: [ppc64] + os: [linux] + + '@dprint/linux-ppc64-musl@0.55.2': + resolution: {integrity: sha512-tSGe4bTmL5/EhNH38baCKWBGxOXVB0M5Loxnpls7wsQjuLpzWnzDCw4wiO3C7PrKLbAe4sJUWhbgdDXa3TJ2eQ==} + cpu: [ppc64] + os: [linux] + + '@dprint/linux-riscv64-glibc@0.55.2': + resolution: {integrity: sha512-gSaoq9e3vGTfYm5jcEd86YysFsp8OJF/CGZgBzNNu3NSjLEs8VVgTObDxQI+U7ex2mA17lbpWyr3diejaaK+bg==} cpu: [riscv64] os: [linux] - '@dprint/linux-x64-glibc@0.54.0': - resolution: {integrity: sha512-zZqj3wQELOX8n6QfT2uuWoMf64Wv0lMXNyam3btm+PKkg0P6a54TPL09Bs9XsViOdxgTcamsiQ7HlErt/LEjIA==} + '@dprint/linux-x64-glibc@0.55.2': + resolution: {integrity: sha512-GBLWjuqTT5OGbqh2gQENQihhfRn/AjdDu2SVwjmbZcOFR80HMppIfFrIIolBB3FLyrZk+M8rMk8EAo0tQ3PAXw==} cpu: [x64] os: [linux] - '@dprint/linux-x64-musl@0.54.0': - resolution: {integrity: sha512-it6Qdt06dyW2adbAXpOCb7/KQLxlm4i1UphUAWqWsZk4t3EYetyAza9J0g3Vu9itIWSEIo9MnccgANckQJ6+qw==} + '@dprint/linux-x64-musl@0.55.2': + resolution: {integrity: sha512-GZEUpmtxCOiauRtqOqT/erAjy2ws8dOxx7oQJFf9g5bypisuapwymvr7fUVVb8nqccFqjx7E5kN4IxKDlzntHA==} cpu: [x64] os: [linux] - '@dprint/win32-arm64@0.54.0': - resolution: {integrity: sha512-F5kjV/6I9YtNOTDWHUpTqM2HHHS510BPL7z4NJuU0nDnaVeks7GwNEltGr56CcsG8XQYhkiAsqZytPu6AhA2hQ==} + '@dprint/win32-arm64@0.55.2': + resolution: {integrity: sha512-dxDdxU4a6wQ4K0omdAxW7o0mUdOI03z0/Ah3N6O9Ja7wAMQI5sbfzq7DOJ739UVXtHSB4zBTtYuk1MrBOIyVQQ==} cpu: [arm64] os: [win32] - '@dprint/win32-x64@0.54.0': - resolution: {integrity: sha512-AAr2ye/DtgYXDplRoPS+5U++x7T6W4a3I9FvTFWFxziFmUptvAg5G2c4FcXoAduSruhYZJvjDZrLseR2c3IwXg==} + '@dprint/win32-x64@0.55.2': + resolution: {integrity: sha512-UTiDSShHbrkx+z719/MffgwGYD8PaypdVmG4vVJVjmF5Hoin6EfugHhSoBf8+G6U9rF+uDIIl0idZq+6Bifzwg==} cpu: [x64] os: [win32] @@ -1651,392 +1794,226 @@ packages: resolution: {integrity: sha512-NKBGBSIKUG584qrS1tyxVpX/AKJKQw5HgjYEnPLC0QsTw79JrGn+qUr8CXFb955Iy7GUdiiUv1rJ6JBGvaKb6w==} engines: {node: '>=18'} - '@effect/docgen@https://pkg.pr.new/Effect-TS/docgen/@effect/docgen@e57e5f5': - resolution: {tarball: https://pkg.pr.new/Effect-TS/docgen/@effect/docgen@e57e5f5} - version: 0.5.2 - engines: {node: '>=18.0.0'} - hasBin: true - peerDependencies: - tsx: ^4.19.3 - typescript: ^5.8.2 - - '@effect/language-service@0.85.1': - resolution: {integrity: sha512-EXnJjIy6zQ3nUO/MZ+ynWUb8B895KZPotd1++oTs9JjDkplwM7cb6zo8Zq2zU6piwq+KflO7amXbEfj1UMpHkw==} - hasBin: true - '@effect/markdown-toc@0.1.0': resolution: {integrity: sha512-IRfvvwqQLabVTIw9hhIj4scOGIYPfa13QuEFv+dBWE6p47R+RR0J8jQvfDINFf0Vn80XXVjNRtZxkZpkKXLx2A==} engines: {node: '>=0.10.0'} hasBin: true - '@effect/tsgo-darwin-arm64@0.7.2': - resolution: {integrity: sha512-0cdsRVRarDKk+huPH71M0aDNs6WKTzZL8wMkLBKFLqqW9re4QHcduOc+pF7OSze1u/n+ksdP+h+GM6xP7aF7Bw==} + '@effect/tsgo-darwin-arm64@0.21.0': + resolution: {integrity: sha512-cTh8nsNwsxyCAwKEc8euvon2zMMBBA7ZlHmPKEX5hcX1iLCbAeOg0uOQKse3uGwg2dZG1C8YTMBdGN+JQIdPrA==} cpu: [arm64] os: [darwin] - '@effect/tsgo-darwin-x64@0.7.2': - resolution: {integrity: sha512-QpwhdZhZ/ZqFDejP9wu5zTm0CYSxsLqbSMUp7YvwBqKHucrM26nJTtn1kRby1+cBtHJHPEd8JyfHAuvTdM4/ZA==} + '@effect/tsgo-darwin-x64@0.21.0': + resolution: {integrity: sha512-FlQ9s23fvHI094mtPI1mOTLJVdKWE3+Nk9PyAIwGl3tD4hQ+J9GCEkVBrpAKS1/OGJmBcPSL87LqiwcLE+jU8w==} cpu: [x64] os: [darwin] - '@effect/tsgo-linux-arm64@0.7.2': - resolution: {integrity: sha512-MB+LHRbPeFo8IvTSmu6GR3isrw5fllRNR/xJccjUgIfRcBet+bCJEhc12mwEAp6c0EizZ/+DMIxaSSrX5VxqyA==} + '@effect/tsgo-linux-arm64@0.21.0': + resolution: {integrity: sha512-grfqrxLQvO6bgvxdRaEQhOnAnrZAmKpLbIbwKmYfnCG/cwuwHKjxB1UUlQECr5PfQr+79EFugB9Ja7GzEbeQVg==} cpu: [arm64] os: [linux] - '@effect/tsgo-linux-arm@0.7.2': - resolution: {integrity: sha512-dmGBTShzcWY945Wu3Xj72Awgj5vgxR13W93PiwcAvYhZeRXWYQIkVlgAFfHQT9YspGC1R9lNnXVDILknN2BEzw==} + '@effect/tsgo-linux-arm@0.21.0': + resolution: {integrity: sha512-Ez04FzLvojCpRH5QcUa354G9FkcOVtCwoMT+lpzxER5A7eVM7BwhfOFRtABCBaBqSv+FwHzfHSihv6lZv9CZBg==} cpu: [arm] os: [linux] - '@effect/tsgo-linux-x64@0.7.2': - resolution: {integrity: sha512-q/JSGD5BA1yd8/k9i94T/osiDGcMal8bvoEfrg4BfNOfPpxbpOg7jfP/MzzarqkYbLfco6+TBZomMpQabE16ZQ==} + '@effect/tsgo-linux-x64@0.21.0': + resolution: {integrity: sha512-ZbHTKCrVVAQpQ/Wbm+Q3Gg3vQluXqVHyYaNw8RmmAUrOXQ+pxPnrny2+T1G/u4pixWEtfTCKzX/GOM0/64l8Hw==} cpu: [x64] os: [linux] - '@effect/tsgo-win32-arm64@0.7.2': - resolution: {integrity: sha512-pUez9e/OfMSWI7EZuzJG/g+v+Cnz7n3rW4IHlZvSR3saSmN0UVNrRHebwL2fQnKbUk6l5MaZWM9NNS2ysmQYEw==} + '@effect/tsgo-win32-arm64@0.21.0': + resolution: {integrity: sha512-Yq/NrK0AFmN2sbVJwAUYSATwcuXyl+aSf3yKzkKB72L1UzohsA12UABbKlhe5JLpI8sujT5rCSCuKbRbEDOTTw==} cpu: [arm64] os: [win32] - '@effect/tsgo-win32-x64@0.7.2': - resolution: {integrity: sha512-X1VIA/Y2buqKi1se3sgGlUTWFPTXCI0xR7fIOXe1D6FTDFFXWBmWCHvxeDuxWPnSewBVFM0mjOAQSIpTarxdow==} + '@effect/tsgo-win32-x64@0.21.0': + resolution: {integrity: sha512-vE6hifasQTWw1Y5RN02GfxHcPgcgLxtnrMfX9kWaic709zmFPKtvYUipFhVEFhlftGSxH0tL6ZoSKVRytQNy+Q==} cpu: [x64] os: [win32] - '@effect/tsgo@0.7.2': - resolution: {integrity: sha512-ZTpfpJvn4IPykmCDsPQiZ4RvhCagQfuYL4zlIk77gSf5AyxmUYjrtDshHB2RK9fnEI8qyQ9abWVmVRkVwnjHyw==} + '@effect/tsgo@0.21.0': + resolution: {integrity: sha512-bsctKj3pcr/pz6mzGq/AGpFoa8LFkKukmpYmXKxdCRbPybC2EBJHOLag8mAKkr64ZqKpjHthgb2X4xXEKV+McA==} hasBin: true '@effect/wa-sqlite@0.2.1': resolution: {integrity: sha512-vMnGIHKWSIFnDf7+n7lP2vyXPzORJotkaRI8LKyJw3eNjsvFopHYVaaGSeMSSVTm/w45pC5T4JdLrD6nxSR4oQ==} - '@electric-sql/pglite@0.4.5': - resolution: {integrity: sha512-aGG2zGEyZzGWKy8P+9ZoNUV0jxt1+hgbeTf+bVAYyxVZZLXg3/9aFlfLxb08AYZVAfAkQlQIysmWjhc5hwDG8g==} + '@electric-sql/pglite@0.5.4': + resolution: {integrity: sha512-yYZUyyXrHU7tPlCjwZQJ6hIG9DscdCCn7Uk0mYKwC1FeHX286AbcmFveMiRBEak8e9iPupjsoVImN3yJZVed2g==} - '@emnapi/core@1.10.0': - resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==} + '@emnapi/core@1.11.1': + resolution: {integrity: sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==} - '@emnapi/runtime@1.10.0': - resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==} + '@emnapi/runtime@1.11.1': + resolution: {integrity: sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==} - '@emnapi/wasi-threads@1.2.1': - resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} + '@emnapi/runtime@1.11.3': + resolution: {integrity: sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==} - '@esbuild/aix-ppc64@0.27.2': - resolution: {integrity: sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [aix] + '@emnapi/wasi-threads@1.2.2': + resolution: {integrity: sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==} - '@esbuild/aix-ppc64@0.27.7': - resolution: {integrity: sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==} + '@esbuild/aix-ppc64@0.28.1': + resolution: {integrity: sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] - '@esbuild/android-arm64@0.27.2': - resolution: {integrity: sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==} - engines: {node: '>=18'} - cpu: [arm64] - os: [android] - - '@esbuild/android-arm64@0.27.7': - resolution: {integrity: sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==} + '@esbuild/android-arm64@0.28.1': + resolution: {integrity: sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==} engines: {node: '>=18'} cpu: [arm64] os: [android] - '@esbuild/android-arm@0.27.2': - resolution: {integrity: sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==} - engines: {node: '>=18'} - cpu: [arm] - os: [android] - - '@esbuild/android-arm@0.27.7': - resolution: {integrity: sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==} + '@esbuild/android-arm@0.28.1': + resolution: {integrity: sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==} engines: {node: '>=18'} cpu: [arm] os: [android] - '@esbuild/android-x64@0.27.2': - resolution: {integrity: sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==} - engines: {node: '>=18'} - cpu: [x64] - os: [android] - - '@esbuild/android-x64@0.27.7': - resolution: {integrity: sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==} + '@esbuild/android-x64@0.28.1': + resolution: {integrity: sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==} engines: {node: '>=18'} cpu: [x64] os: [android] - '@esbuild/darwin-arm64@0.27.2': - resolution: {integrity: sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [darwin] - - '@esbuild/darwin-arm64@0.27.7': - resolution: {integrity: sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==} + '@esbuild/darwin-arm64@0.28.1': + resolution: {integrity: sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] - '@esbuild/darwin-x64@0.27.2': - resolution: {integrity: sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==} - engines: {node: '>=18'} - cpu: [x64] - os: [darwin] - - '@esbuild/darwin-x64@0.27.7': - resolution: {integrity: sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==} + '@esbuild/darwin-x64@0.28.1': + resolution: {integrity: sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==} engines: {node: '>=18'} cpu: [x64] os: [darwin] - '@esbuild/freebsd-arm64@0.27.2': - resolution: {integrity: sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==} - engines: {node: '>=18'} - cpu: [arm64] - os: [freebsd] - - '@esbuild/freebsd-arm64@0.27.7': - resolution: {integrity: sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==} + '@esbuild/freebsd-arm64@0.28.1': + resolution: {integrity: sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-x64@0.27.2': - resolution: {integrity: sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==} - engines: {node: '>=18'} - cpu: [x64] - os: [freebsd] - - '@esbuild/freebsd-x64@0.27.7': - resolution: {integrity: sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==} + '@esbuild/freebsd-x64@0.28.1': + resolution: {integrity: sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] - '@esbuild/linux-arm64@0.27.2': - resolution: {integrity: sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [linux] - - '@esbuild/linux-arm64@0.27.7': - resolution: {integrity: sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==} + '@esbuild/linux-arm64@0.28.1': + resolution: {integrity: sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==} engines: {node: '>=18'} cpu: [arm64] os: [linux] - '@esbuild/linux-arm@0.27.2': - resolution: {integrity: sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==} - engines: {node: '>=18'} - cpu: [arm] - os: [linux] - - '@esbuild/linux-arm@0.27.7': - resolution: {integrity: sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==} + '@esbuild/linux-arm@0.28.1': + resolution: {integrity: sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==} engines: {node: '>=18'} cpu: [arm] os: [linux] - '@esbuild/linux-ia32@0.27.2': - resolution: {integrity: sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==} - engines: {node: '>=18'} - cpu: [ia32] - os: [linux] - - '@esbuild/linux-ia32@0.27.7': - resolution: {integrity: sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==} + '@esbuild/linux-ia32@0.28.1': + resolution: {integrity: sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==} engines: {node: '>=18'} cpu: [ia32] os: [linux] - '@esbuild/linux-loong64@0.27.2': - resolution: {integrity: sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==} - engines: {node: '>=18'} - cpu: [loong64] - os: [linux] - - '@esbuild/linux-loong64@0.27.7': - resolution: {integrity: sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==} + '@esbuild/linux-loong64@0.28.1': + resolution: {integrity: sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==} engines: {node: '>=18'} cpu: [loong64] os: [linux] - '@esbuild/linux-mips64el@0.27.2': - resolution: {integrity: sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==} - engines: {node: '>=18'} - cpu: [mips64el] - os: [linux] - - '@esbuild/linux-mips64el@0.27.7': - resolution: {integrity: sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==} + '@esbuild/linux-mips64el@0.28.1': + resolution: {integrity: sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] - '@esbuild/linux-ppc64@0.27.2': - resolution: {integrity: sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [linux] - - '@esbuild/linux-ppc64@0.27.7': - resolution: {integrity: sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==} + '@esbuild/linux-ppc64@0.28.1': + resolution: {integrity: sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] - '@esbuild/linux-riscv64@0.27.2': - resolution: {integrity: sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==} - engines: {node: '>=18'} - cpu: [riscv64] - os: [linux] - - '@esbuild/linux-riscv64@0.27.7': - resolution: {integrity: sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==} + '@esbuild/linux-riscv64@0.28.1': + resolution: {integrity: sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] - '@esbuild/linux-s390x@0.27.2': - resolution: {integrity: sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==} - engines: {node: '>=18'} - cpu: [s390x] - os: [linux] - - '@esbuild/linux-s390x@0.27.7': - resolution: {integrity: sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==} + '@esbuild/linux-s390x@0.28.1': + resolution: {integrity: sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==} engines: {node: '>=18'} cpu: [s390x] os: [linux] - '@esbuild/linux-x64@0.27.2': - resolution: {integrity: sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==} - engines: {node: '>=18'} - cpu: [x64] - os: [linux] - - '@esbuild/linux-x64@0.27.7': - resolution: {integrity: sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==} + '@esbuild/linux-x64@0.28.1': + resolution: {integrity: sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==} engines: {node: '>=18'} cpu: [x64] os: [linux] - '@esbuild/netbsd-arm64@0.27.2': - resolution: {integrity: sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [netbsd] - - '@esbuild/netbsd-arm64@0.27.7': - resolution: {integrity: sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==} + '@esbuild/netbsd-arm64@0.28.1': + resolution: {integrity: sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] - '@esbuild/netbsd-x64@0.27.2': - resolution: {integrity: sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==} - engines: {node: '>=18'} - cpu: [x64] - os: [netbsd] - - '@esbuild/netbsd-x64@0.27.7': - resolution: {integrity: sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==} + '@esbuild/netbsd-x64@0.28.1': + resolution: {integrity: sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] - '@esbuild/openbsd-arm64@0.27.2': - resolution: {integrity: sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==} + '@esbuild/openbsd-arm64@0.28.1': + resolution: {integrity: sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] - '@esbuild/openbsd-arm64@0.27.7': - resolution: {integrity: sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==} + '@esbuild/openbsd-x64@0.28.1': + resolution: {integrity: sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==} engines: {node: '>=18'} - cpu: [arm64] + cpu: [x64] os: [openbsd] - '@esbuild/openbsd-x64@0.27.2': - resolution: {integrity: sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==} + '@esbuild/openharmony-arm64@0.28.1': + resolution: {integrity: sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==} engines: {node: '>=18'} - cpu: [x64] - os: [openbsd] + cpu: [arm64] + os: [openharmony] - '@esbuild/openbsd-x64@0.27.7': - resolution: {integrity: sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==} + '@esbuild/sunos-x64@0.28.1': + resolution: {integrity: sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==} engines: {node: '>=18'} cpu: [x64] - os: [openbsd] + os: [sunos] - '@esbuild/openharmony-arm64@0.27.2': - resolution: {integrity: sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==} + '@esbuild/win32-arm64@0.28.1': + resolution: {integrity: sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==} engines: {node: '>=18'} cpu: [arm64] - os: [openharmony] + os: [win32] - '@esbuild/openharmony-arm64@0.27.7': - resolution: {integrity: sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==} + '@esbuild/win32-ia32@0.28.1': + resolution: {integrity: sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==} engines: {node: '>=18'} - cpu: [arm64] - os: [openharmony] + cpu: [ia32] + os: [win32] - '@esbuild/sunos-x64@0.27.2': - resolution: {integrity: sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==} + '@esbuild/win32-x64@0.28.1': + resolution: {integrity: sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==} engines: {node: '>=18'} cpu: [x64] - os: [sunos] - - '@esbuild/sunos-x64@0.27.7': - resolution: {integrity: sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==} - engines: {node: '>=18'} - cpu: [x64] - os: [sunos] - - '@esbuild/win32-arm64@0.27.2': - resolution: {integrity: sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [win32] - - '@esbuild/win32-arm64@0.27.7': - resolution: {integrity: sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==} - engines: {node: '>=18'} - cpu: [arm64] - os: [win32] - - '@esbuild/win32-ia32@0.27.2': - resolution: {integrity: sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==} - engines: {node: '>=18'} - cpu: [ia32] - os: [win32] - - '@esbuild/win32-ia32@0.27.7': - resolution: {integrity: sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==} - engines: {node: '>=18'} - cpu: [ia32] os: [win32] - '@esbuild/win32-x64@0.27.2': - resolution: {integrity: sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [win32] - - '@esbuild/win32-x64@0.27.7': - resolution: {integrity: sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==} - engines: {node: '>=18'} - cpu: [x64] - os: [win32] - - '@exodus/bytes@1.15.0': - resolution: {integrity: sha512-UY0nlA+feH81UGSHv92sLEPLCeZFjXOuHhrIo0HQydScuQc8s0A7kL/UdgwgDq8g8ilksmuoF35YVTNphV2aBQ==} + '@exodus/bytes@1.15.1': + resolution: {integrity: sha512-S6mL0yNB/Abt9Ei4tq8gDhcczc4S3+vQ4ra7vxnAf+YHC02srtqxKKZghx2Dq6p0e66THKwR6r8N6P95wEty7Q==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} peerDependencies: '@noble/hashes': ^1.8.0 || ^2.0.0 @@ -2047,12 +2024,12 @@ packages: '@exodus/schemasafe@1.3.0': resolution: {integrity: sha512-5Aap/GaRupgNx/feGBwLLTVv8OQFfv3pq2lPRzPg9R+IOBnDgghTGW7l7EuVXOvg5cc/xSAlRW8rBrjIC3Nvqw==} - '@faker-js/faker@10.4.0': - resolution: {integrity: sha512-sDBWI3yLy8EcDzgobvJTWq1MJYzAkQdpjXuPukga9wXonhpMRvd1Izuo2Qgwey2OiEoRIBr35RMU9HJRoOHzpw==} + '@faker-js/faker@10.5.0': + resolution: {integrity: sha512-bsxD8WLS5lIj7aaoCx1YJkktqYj5vlBUE6HWzu2Q51ksrGJ0H737ECCKlFU7Yf8Br45z9t99frBp/J7kzbMPAg==} engines: {node: ^20.19.0 || ^22.13.0 || ^23.5.0 || >=24.0.0, npm: '>=10'} - '@grpc/grpc-js@1.14.3': - resolution: {integrity: sha512-Iq8QQQ/7X3Sac15oB6p0FmUg/klxQvXLeileoqrTRGJYLV+/9tubbr9ipz0GKHjmXVsgFPo/+W+2cA8eNcR+XA==} + '@grpc/grpc-js@1.14.4': + resolution: {integrity: sha512-k9Dj3DV/itK9D06Y8f190Qgop7/Ui+D0njFV3LHMPwPT75DpXLQohE9Wmz0QElrJnzsjB7KPWiKJbOl7IPDArQ==} engines: {node: '>=12.10.0'} '@grpc/proto-loader@0.7.15': @@ -2060,8 +2037,8 @@ packages: engines: {node: '>=6'} hasBin: true - '@grpc/proto-loader@0.8.0': - resolution: {integrity: sha512-rc1hOQtjIWGxcxpb9aHAfLpIctjEnsDehj0DAiVfBlmT84uvR0uUtN2hEi/ecvWVjXUGf5qPF4qEgiLOx1YIMQ==} + '@grpc/proto-loader@0.8.1': + resolution: {integrity: sha512-wtF6h+DY6M3YaDBPAmvuuA6jV8Sif9MjtOI5euKFWRgCDl5PeDpPsHR9u2l6St5ceY8AZgoNDww5+HvEsXFsGg==} engines: {node: '>=6'} hasBin: true @@ -2202,15 +2179,6 @@ packages: cpu: [x64] os: [win32] - '@inquirer/external-editor@1.0.3': - resolution: {integrity: sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==} - engines: {node: '>=18'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - '@ioredis/commands@1.4.0': resolution: {integrity: sha512-aFT2yemJJo+TZCmieA7qnYGQooOS7QfNmYrzGtsYd3g9j5iDP8AimYYAesf79ohjbLG12XxC4nG5DyEnC88AsQ==} @@ -2290,14 +2258,53 @@ packages: resolution: {integrity: sha512-hloP58zRVCRSpgDxmqCWJNlizAlUgJFqG2ypq79DCvyv9tHjRYMDOcPFjzfl/A1/YxDvRCZz8wvZvmapQnKwFQ==} engines: {node: '>=12'} + '@jsr/db__redis@0.41.2': + resolution: {integrity: sha512-pDHlF3sQaU5+VDwDH3jRkETBSD9gAIqdoaC3j0B4nrffr+H/Kh0Pbw7i/p5eD+9W5j38l8yRqZGbo4xgohYttg==, tarball: https://npm.jsr.io/~/11/@jsr/db__redis/0.41.2.tgz} + + '@jsr/std__assert@1.0.19': + resolution: {integrity: sha512-pEj6RPkGbqlgRmyKwATp4cUs6+ijxtdrv3bq8v1d2I2CEcMEyPaO8cVKro61wGRDH4cNg8Zx6haztvK/9m7gkA==, tarball: https://npm.jsr.io/~/11/@jsr/std__assert/1.0.19.tgz} + + '@jsr/std__async@1.5.0': + resolution: {integrity: sha512-I2Qekl1oQYM+dpI2RQUCKi1TBuaA7Od3LNzxcqls9s1e2ytiZyREL4403qHJ6UbLNAwbqO2ZhdWAWDUQAajyiA==, tarball: https://npm.jsr.io/~/11/@jsr/std__async/1.5.0.tgz} + + '@jsr/std__bytes@1.0.6': + resolution: {integrity: sha512-St6yKggjFGhxS52IFLJWvkchRFbAKg2Xh8UxA4S1EGz7GJ2Ui+ssDDldj/w2c8vCxvl6qgR0HaYbKeFJNqujmA==, tarball: https://npm.jsr.io/~/11/@jsr/std__bytes/1.0.6.tgz} + + '@jsr/std__collections@1.3.0': + resolution: {integrity: sha512-oJ5V5ZvsWyO5VLdGk3TaRHaVibfVKHvDC8KJdgmxIucHN2QzAXlMt36ApyTUBncfrhXdN/5Q3yMNvloAKym8RA==, tarball: https://npm.jsr.io/~/11/@jsr/std__collections/1.3.0.tgz} + + '@jsr/std__data-structures@1.1.1': + resolution: {integrity: sha512-ceWGRdAPq3oD2If4k/JvgL/bskN5VohnM6wINlJhYLz4yCW9cmHFcGsrhRgh9HbD9xhXi9sEQoiHQG5B9caHEg==, tarball: https://npm.jsr.io/~/11/@jsr/std__data-structures/1.1.1.tgz} + + '@jsr/std__fs@1.0.24': + resolution: {integrity: sha512-UunW2Rg++9sA74NJnYMGTsMVQgoFRAwPP02wRRohL0PQBNorbyxHiyCMRaxYQCtLepTLkC4nUyMtcwrGKqTR6g==, tarball: https://npm.jsr.io/~/11/@jsr/std__fs/1.0.24.tgz} + + '@jsr/std__internal@1.0.14': + resolution: {integrity: sha512-JT8b/t40WcR9q0GDwRZUooY7aXeXnFal8iidE/5TckdAFtvpVAsaJ71/Xgf1SfoChs41s6CZkuCYM8toaNgdvA==, tarball: https://npm.jsr.io/~/11/@jsr/std__internal/1.0.14.tgz} + + '@jsr/std__io@0.224.5': + resolution: {integrity: sha512-1Y8ZWIjFiQKkaSJwbt7NM/9esDtO0ieKS4vcT929dFArG2ADk7ZKVAV4KluYYh4+kF5VgkOcvJkEm84KeBpqFA==, tarball: https://npm.jsr.io/~/11/@jsr/std__io/0.224.5.tgz} + + '@jsr/std__media-types@1.1.0': + resolution: {integrity: sha512-dHvaxHL7ENWnltgL653uo3KnKFse3ZbopZop2gqsT7yrscx7irZEClu5Cba7gMPPRk4Lg1FbriNcaBViM2RSBw==, tarball: https://npm.jsr.io/~/11/@jsr/std__media-types/1.1.0.tgz} + + '@jsr/std__path@1.1.6': + resolution: {integrity: sha512-H1Cmg6z8jFyIsQbVV+vZB4eOx4k6Qg8lyUM6l7nZysj5MZumg89kb0M1bsPBTGQis3fw6mJFkJELo5+AK6oCFA==, tarball: https://npm.jsr.io/~/11/@jsr/std__path/1.1.6.tgz} + + '@jsr/std__random@0.1.0': + resolution: {integrity: sha512-gt+pI83ha04zLv2+5kSf/i6uPT4c8QEwscpATrYe2IKfOkCVpL++NrFcH7pWHc0vgm2fDGC8aY6OI2Mz7tEHhQ==, tarball: https://npm.jsr.io/~/11/@jsr/std__random/0.1.0.tgz} + + '@jsr/std__streams@1.1.1': + resolution: {integrity: sha512-V9auR/i6gJz6SR1+h5wc4EN42IC5+ObBjXu/h7Buef7UOE0GtX4vRSx+3MJywEiBcJqQC+lCnf+wBAodvBL1SA==, tarball: https://npm.jsr.io/~/11/@jsr/std__streams/1.1.1.tgz} + '@kwsites/file-exists@1.1.1': resolution: {integrity: sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw==} - '@libsql/client@0.17.3': - resolution: {integrity: sha512-HXk9wiAoJbKFbyBH4O+aEhN6ir5ERXuXvwE5OD2eR4/5RUa3Pw/8L9zrnVdU+iNJitRvisPWaIwmhkO3bH7giA==} + '@libsql/client@0.17.4': + resolution: {integrity: sha512-lYayFWasDV78A+TjlEhr6ubb3odBV6OHjb+wdp8VQcyWWAEIjuwbCHaraEUS4m4yWoo0BvZo96It4VdzZRmRWw==} - '@libsql/core@0.17.3': - resolution: {integrity: sha512-2UjK1i7JBkMduJo4WdvvBxMMvVJ31pArBZNONyz/GCJJAH+1UHat2X6vn10S/WpY5fKzIT98WqYFl2vzWRLOfg==} + '@libsql/core@0.17.4': + resolution: {integrity: sha512-LqF9gIvnJ38nmAH1y/ChizHqDO/MO1wLgA96XrraulEEbqXxLjleSH92YWTolbuJKgPUmGu4aJk9W3UnAcxLOQ==} '@libsql/darwin-arm64@0.5.29': resolution: {integrity: sha512-K+2RIB1OGFPYQbfay48GakLhqf3ArcbHqPFu7EZiaUcRgFcdw8RoltsMyvbj5ix2fY0HV3Q3Ioa/ByvQdaSM0A==} @@ -2350,68 +2357,60 @@ packages: cpu: [x64] os: [win32] - '@manypkg/find-root@1.1.0': - resolution: {integrity: sha512-mki5uBvhHzO8kYYix/WRy2WX8S3B5wdVSc9D6KcU5lQNglP2yt58/VfLuAK49glRXChosY8ap2oJ1qgma3GUVA==} + '@manypkg/find-root@3.1.0': + resolution: {integrity: sha512-BcSqCyKhBVZ5YkSzOiheMCV41kqAFptW6xGqYSTjkVTl9XQpr+pqHhwgGCOHQtjDCv7Is6EFyA14Sm5GVbVABA==} + engines: {node: '>=20.0.0'} + + '@manypkg/get-packages@3.1.0': + resolution: {integrity: sha512-0TbBVyvPrP7xGYBI/cP8UP+yl/z+HtbTttAD7FMAJgn/kXOTwh5/60TsqP9ZYY710forNfyV0N8P/IE/ujGZJg==} + engines: {node: '>=20.0.0'} - '@manypkg/get-packages@1.1.3': - resolution: {integrity: sha512-fo+QhuU3qE/2TQMQmbVMqaQ6EWbMhi4ABWP+O4AM1NqPBuy0OrApV5LO6BrrgnhtAHS2NH6RrVk9OL181tTi8A==} + '@manypkg/tools@2.1.2': + resolution: {integrity: sha512-6QEf6yqFbETdwGITKq57aYoPfX/3K8XFNwsAlx0C1M7o8cb79sv1M3w+tWuWvIcSbNqrLF7OD7YpZMVVz335hQ==} + engines: {node: '>=20.0.0'} - '@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3': - resolution: {integrity: sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw==} + '@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.4': + resolution: {integrity: sha512-LCkGo6JDfaBhgST7UpPWgNgLINpcpabaHfyz5OBx75nUYxBsaEPxjnyNjWpeb/xBup/682QnBfRBy2/LvPutZQ==} cpu: [arm64] os: [darwin] - '@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.3': - resolution: {integrity: sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw==} + '@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.4': + resolution: {integrity: sha512-zExlW9zUJKZH/tOtVMttwjKa4Xm/3KcNjnE3dPN92uCktwavMxpgCA3MoJK/DOnTWsQgo224OaST27/mPNAf+w==} cpu: [x64] os: [darwin] - '@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.3': - resolution: {integrity: sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg==} + '@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.4': + resolution: {integrity: sha512-dgX0P/9wGPJeHFBG+ZmhgE6bmtMt7NP5CRBGyyktpopdk/mW4POnrpQsSLtKI1dwpc+pPLuXHDh6vvskyQE/sw==} cpu: [arm64] os: [linux] - '@msgpackr-extract/msgpackr-extract-linux-arm@3.0.3': - resolution: {integrity: sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw==} + '@msgpackr-extract/msgpackr-extract-linux-arm@3.0.4': + resolution: {integrity: sha512-Tg3yX65f5GbtXLkrYEHE5oibZG9epyYWas7FogTTEJeDEF9JlXJzKgXaNhT3UXlTOeA+AfZpYZYZ0uPj7Cfquw==} cpu: [arm] os: [linux] - '@msgpackr-extract/msgpackr-extract-linux-x64@3.0.3': - resolution: {integrity: sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg==} + '@msgpackr-extract/msgpackr-extract-linux-x64@3.0.4': + resolution: {integrity: sha512-8TNXMEjJc3QEy7R/x1INhgiU+XakDAFUzBhaz7+Rbrs8NH5UQeHQxxmzsSBJGyV6I1jW79undiQm8tOI+D+8FQ==} cpu: [x64] os: [linux] - '@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3': - resolution: {integrity: sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ==} + '@msgpackr-extract/msgpackr-extract-win32-x64@3.0.4': + resolution: {integrity: sha512-CmCXPQrkbwExx3j946/PtHWHbYJiCRBRDl4BlkRQcJB/YOwQxJRTpoo7aTsortjgoJ1x7opzTSxn7C+ASSLVjQ==} cpu: [x64] os: [win32] - '@napi-rs/wasm-runtime@1.1.5': - resolution: {integrity: sha512-AWPoBRJ9tsnVhor4sjO7rkni+7p+2IAEFj6cx06UgP10jkQHqay/36uRV/bFkgrh18D9vb4cr8Q0Pthskgzy+Q==} + '@napi-rs/wasm-runtime@1.2.0': + resolution: {integrity: sha512-kDoONqMa+VnZ4vvvu/ZUurpJ4gkZU57e7g69qpNgWhYcZFPUHZM2CEMKm+cG6ufDVALbjMvfmMjFVqaK7uEMnA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=23.5.0} peerDependencies: - '@emnapi/core': ^1.7.1 - '@emnapi/runtime': ^1.7.1 + '@emnapi/core': ^2.0.0-alpha.3 + '@emnapi/runtime': ^2.0.0-alpha.3 '@neon-rs/load@0.0.4': resolution: {integrity: sha512-kTPhdZyTQxB+2wpiRcFWrDcejc4JI6tkPuS7UZCG4l6Zvc5kU/gGQ/ozvHTh1XR5tS+UlfAfGuPajjzQjCiHCw==} - '@nicolo-ribaudo/chokidar-2@2.1.8-no-fsevents.3': - resolution: {integrity: sha512-s88O1aVtXftvp5bCPB7WnmXc5IwOZZ7YPuwNPt+GtOOXpPvad1LfbmjYv+qII7zP6RU2QGnqve27dnLycEnyEQ==} - - '@nodelib/fs.scandir@2.1.5': - resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} - engines: {node: '>= 8'} - - '@nodelib/fs.stat@2.0.5': - resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} - engines: {node: '>= 8'} - - '@nodelib/fs.walk@1.2.8': - resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} - engines: {node: '>= 8'} - - '@op-engineering/op-sqlite@15.2.12': - resolution: {integrity: sha512-QfOzk4NVG4OQ4tDKAByhXHfqIefF3zM6O5ASYp1gExJCuO1OeF+u1Qf/Hz+c9KSNT2LMrFgm0X7LQxWgvh7j2A==} + '@op-engineering/op-sqlite@17.1.2': + resolution: {integrity: sha512-R1E0MO9dGuOSr5K+Wrwi2k2HrEDbG6iJGwboYl+2hJXpuo01k556GcAj1l7nu4RDPH0wdpM7NL7pieIL5+k3Mw==} peerDependencies: '@sqlite.org/sqlite-wasm': '*' react: '*' @@ -2420,143 +2419,231 @@ packages: '@sqlite.org/sqlite-wasm': optional: true - '@opentelemetry/api-logs@0.217.0': - resolution: {integrity: sha512-Cdq0jW2lknrNfrAm92MyEAvpe2cRsKjdnQLHUL6xRA4IVUnsWx6P65E7NcUO0Y+L4w1Aee5iV8FvjSwd+lrs9A==} + '@opentelemetry/api-logs@0.220.0': + resolution: {integrity: sha512-CmVa4ImJ+ynfrPMNaAXHET6Bhb44SwzmfyVJFq9ni2jgXJR/l7C6gfVFddNmHP+ZOkP9cf4f9DBe68qVLTHc9w==} engines: {node: '>=8.0.0'} '@opentelemetry/api@1.9.1': resolution: {integrity: sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==} engines: {node: '>=8.0.0'} - '@opentelemetry/context-async-hooks@2.7.1': - resolution: {integrity: sha512-OPFBYuXEn1E4ja3Y6eeA7O+ZnLBNcXTV5Cgsn1VaqBZ6hC5FnpZPLBNme1LJY8ZtF4aOujPKFoeWN4ik487KuQ==} + '@opentelemetry/context-async-hooks@2.9.0': + resolution: {integrity: sha512-OQ0vzvbZBiUhjqLnUaoNfYmP8553Crr3aggB4y0ZUi815mZ7idpdJXQmoKdeBKJelYttoBlLSSHubmyw3wvX4w==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' - '@opentelemetry/core@2.7.1': - resolution: {integrity: sha512-QAqIj32AtK6+pEVNG7EOVxHdE06RP+FM5qpiEJ4RtDcFIqKUZHYhl7/7UY5efhwmwNAg7j8QbJVBLxMerc0+gw==} + '@opentelemetry/core@2.9.0': + resolution: {integrity: sha512-m2nckMT80NnmjTYSPjJQObBJ+8dgkoajEOUbznL8AHZ3T3yHRk2P7gI1PhEBc1+lOnrYE9UWrWHqJDsmqjmNbw==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' - '@opentelemetry/exporter-metrics-otlp-http@0.217.0': - resolution: {integrity: sha512-1zkMzzhiNJdVmLxuwkltqWGw4fOOam47bqRxmuQNjyKJe/9NmY5cIrZ4kiQV7sVGxoOgT0ZvGUfLcjvtpC/b9Q==} + '@opentelemetry/exporter-metrics-otlp-http@0.220.0': + resolution: {integrity: sha512-Yqt3RBw/bRVncaE9qIIhk4WfjbAQqXuP9FgAaU+IKPndnLEp/cUqZlSC324+bpmduRz7DoTjig8Ub0PeILWXUA==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/exporter-prometheus@0.217.0': - resolution: {integrity: sha512-U9MCXxJu0sBCh5aEkylYRR4xVIL8D1CW6dGwvYXbfFr0qveSorfD0XJchCAWoW6QfAAIcY/yxjf4Dj8OgkHBPw==} + '@opentelemetry/exporter-prometheus@0.220.0': + resolution: {integrity: sha512-JZD5DL/NBpVd2BHefvYosm3G40UZ/KzExLv5tc0eZe0CtrsHHtcOk3YPUxR2EINmUeBf8+w5UReTV8fFPn95lA==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/exporter-trace-otlp-http@0.217.0': - resolution: {integrity: sha512-38YQoqtYjglz2GV94LGUN/djLvxtvGIQO68o6qAFPVshjmwSdX1F2i0c7vn3lEl1L5B/YqjB/bgKXaVx7KO+RQ==} + '@opentelemetry/exporter-trace-otlp-http@0.220.0': + resolution: {integrity: sha512-/+ExB3lRkf+erv4PnoywyL7RHKITidxtUpUTS55k7OQ0dB42S7gEF1gry7swb9MSm1hYLUhJg4QQh9W8SpwwqA==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/otlp-exporter-base@0.217.0': - resolution: {integrity: sha512-eYfqnB3UhKu/5frhd1R6+FprKygbhkomuaceMXDyzxbfXB9tKgZOVmjaJ02CkLA6Tdzumxl+e2H+vo2a8jiMPQ==} + '@opentelemetry/otlp-exporter-base@0.220.0': + resolution: {integrity: sha512-CXYo8UD5Mn9YbgebO2EL4wejtA+gxLmLiu6HCk2KH2BR7XhFN6/6p1UlCb23DYCjeYkndevLHuejCCN1yx4+OQ==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/otlp-transformer@0.217.0': - resolution: {integrity: sha512-MKK8UHKFUOGAvbZRWh90MhwHG+Fxm6OROBdjKPCF+HQobjuJ/Kuf8Chs8CR45X1aqotxrMj7OxTdsXe8sXuGVA==} + '@opentelemetry/otlp-transformer@0.220.0': + resolution: {integrity: sha512-lXGrv7KXZ0gNH9SVNUaa6vv6phVYGvJxfXAlMbzbakiXru75f5MZl8Z7oqiMMQD77riVHJCFlQvbZs/VVN2/4A==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/resources@2.7.1': - resolution: {integrity: sha512-DeT6KKolmC4e/dRQvMQ/RwlnzhaqeiFOXY5ngoOPJ07GgVVKxZOg9EcrNZb5aTzUn+iCrJldAgOfQm1O/QfPAQ==} + '@opentelemetry/resources@2.9.0': + resolution: {integrity: sha512-jyA5MBLQ+Dkl3+JsZkUoUvL7yHvU64kLsvpXKarWm6347Sl1t1bXFTFykUePNpT5WH5pm9a2Qtt03iIYQhZ1Fg==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': '>=1.3.0 <1.10.0' - '@opentelemetry/sdk-logs@0.217.0': - resolution: {integrity: sha512-BB+PcHItcZDL63dPMW+mJvwN9rk37wuIDjRxbVlg6pPDvDR/7GL7UJHbGsllgoggOoTimsKgENaWPoGch/oE1A==} + '@opentelemetry/sdk-logs@0.220.0': + resolution: {integrity: sha512-WywcTkQtv2iNmt+6y5Kcd4rzvx9bLVsBa2Nwcmg01IUaBTkTow3W4d9KE5vNBpEDtb9tp21WcRBY/lANRrApYA==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': '>=1.4.0 <1.10.0' - '@opentelemetry/sdk-metrics@2.7.1': - resolution: {integrity: sha512-MpDJdkiFDs3Pm1RHO3KByuZbuBdJEXEAkiC0+yJdsZGVCdf1RpHR6n+LHDcS7ffmfrt5kVCzJSCfm4z2C7v0uQ==} + '@opentelemetry/sdk-metrics@2.9.0': + resolution: {integrity: sha512-Xx8RGS4H5XEBl01WuCreMIpiah9cCXMbSkeuIePPdD2cUpq/vUzYmj8E/MK1OsbOc93FuAD4jfn2WOacKwLn7Q==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': '>=1.9.0 <1.10.0' - '@opentelemetry/sdk-trace-base@2.7.1': - resolution: {integrity: sha512-NAYIlsF8MPUsKqJMiDQJTMPOmlbawC1Iz/omMLygZ1C9am8fTKYjTaI+OZM+WTY3t3Glo0wnOg/6/pac6RGPPw==} + '@opentelemetry/sdk-trace-base@2.9.0': + resolution: {integrity: sha512-cp9zmTl62R8PJrpvFcmc8N2JQU/xfa0S+61q511Nji+QxCfZ8Ifvg7H27G8cANe4crg4RTrWsVvanHiXjSp6ag==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': '>=1.3.0 <1.10.0' - '@opentelemetry/sdk-trace-node@2.7.1': - resolution: {integrity: sha512-pCpQxU68lV+I9s9svqMyVu5iHdDDUnqUpSxqwyCU8A9ejEsSnMPCbearwsUO4yk08ZJzAIUCFuReMdVQvHrdvg==} + '@opentelemetry/sdk-trace-node@2.9.0': + resolution: {integrity: sha512-ec9a7ps37huy5itYk0MalaZdSLlM6AXWp/FhtEjgMpp5leEGojBDvAl/UWttQnkMZOvFHKzRESn8TD3yKTF5nQ==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' - '@opentelemetry/sdk-trace-web@2.7.1': - resolution: {integrity: sha512-K806OouCSOjMd8Nr7+ZCq3QT22tdAzzS/7h8vprfiKjkgFQ99/dvwU8d12WJANA6D5Qtme65hyBAqAu9CkQuxQ==} + '@opentelemetry/sdk-trace-web@2.9.0': + resolution: {integrity: sha512-LS4XlzOK3e6YYdt84m15AmRR04121rKipmifi4XFZToH1h75f7F2bzHLbB1NMgIEYJ0jSKYm4VsK5mws/5kfTQ==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' - '@opentelemetry/semantic-conventions@1.40.0': - resolution: {integrity: sha512-cifvXDhcqMwwTlTK04GBNeIe7yyo28Mfby85QXFe1Yk8nmi36Ab/5UQwptOx84SsoGNRg+EVSjwzfSZMy6pmlw==} + '@opentelemetry/sdk-trace@2.9.0': + resolution: {integrity: sha512-sGA19HvtrrSKYsseHphluH6j3p6Xa3fqc7c7y8f/7mYWejc1lyDFcpSdD1kYa50HCLUeEo4zA5bW0pniaPszuw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + + '@opentelemetry/semantic-conventions@1.42.0': + resolution: {integrity: sha512-icc5xCzndZfhuJMy5oqk5AvloWquR7jtae74qzpkKkhGp8BivK+oCcEXgGnjCdTfp8hA44l+w8gE8yYJbocJJw==} engines: {node: '>=14'} - '@oxc-project/types@0.129.0': - resolution: {integrity: sha512-3oz8m3FGdr2nDXVqmFUw7jolKliC4MoyXYIG2c7gpjBnzUWQpUGIYcXYKxTdTi+N2jusvt610ckTMkxdwHkYEg==} + '@oxc-project/types@0.139.0': + resolution: {integrity: sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw==} + + '@oxlint/binding-android-arm-eabi@1.76.0': + resolution: {integrity: sha512-ZHIE5Zt9AsPDcY4nOlofXt0YfneEeo+QrKMPcPzLf2Z6Q8VtV2W73d7SFJ920WUwyik783u/doKCs3KXdwG+7w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] - '@oxlint/darwin-arm64@1.42.0': - resolution: {integrity: sha512-ui5CdAcDsXPQwZQEXOOSWsilJWhgj9jqHCvYBm2tDE8zfwZZuF9q58+hGKH1x5y0SV4sRlyobB2Quq6uU6EgeA==} + '@oxlint/binding-android-arm64@1.76.0': + resolution: {integrity: sha512-shm/ngQilHK6bs+ElJWa4oHfNj5vL1Gl/iVEJldTQjpr0/67oSgr0KUpbmcnLig5Fo0v/l6j2567A7TOL89ONA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@oxlint/binding-darwin-arm64@1.76.0': + resolution: {integrity: sha512-rvJmrAPKSQ9aWJ6wIS6CK2tJjwzfW0ApQH9qokq6sfDvmHwoyIHxHFMq7z7i7GiV6fdE6s8qvBqWKPTu8RmT6Q==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@oxlint/darwin-x64@1.42.0': - resolution: {integrity: sha512-wo0M/hcpHRv7vFje99zHHqheOhVEwUOKjOgBKyi0M99xcLizv04kcSm1rTd6HSCeZgOtiJYZRVAlKhQOQw2byQ==} + '@oxlint/binding-darwin-x64@1.76.0': + resolution: {integrity: sha512-U/zYdb7VYKGY6pA9Vd2rYl9O/HlCylcOlb5PGPvVLtg+oLGsk6H3XGKEMHKyqD3nmmtmlmwb/8SwU2vfSAtvMw==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@oxlint/linux-arm64-gnu@1.42.0': - resolution: {integrity: sha512-j4QzfCM8ks+OyM+KKYWDiBEQsm5RCW50H1Wz16wUyoFsobJ+X5qqcJxq6HvkE07m8euYmZelyB0WqsiDoz1v8g==} + '@oxlint/binding-freebsd-x64@1.76.0': + resolution: {integrity: sha512-WvKG9CAriuo0XNiFzpXjDngUZcRGFNpaK2kLyMUsnJlShxkT96u+BpJQ3KqdQwGOrvI14L6V8bAwXwAYNNY6Jg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@oxlint/binding-linux-arm-gnueabihf@1.76.0': + resolution: {integrity: sha512-qJ5+RH99TqFRq3UCDxkW0zJJu9c+OAHFY72vGlxZLEpuO+MpKo3POgqb8sYipL9KYm8XY6ofb0HsOuvY6hQNqQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxlint/binding-linux-arm-musleabihf@1.76.0': + resolution: {integrity: sha512-PvPCVptkgVARsucgIqFQQcSmJ6xc6GtnVB5bRBekRahTc9eObMtjHfMjy5M+C2tHt5UCMttWM9RuSk/H9NqYeg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxlint/binding-linux-arm64-gnu@1.76.0': + resolution: {integrity: sha512-3KeFDx8Bu4HPAXbuHZOr/oHvN+QT+JQhMw/NYPz7Z071xLSsG27Jfh9PIQVEY7hk1I+jr43ExqRIeJ6VKk2yLw==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - '@oxlint/linux-arm64-musl@1.42.0': - resolution: {integrity: sha512-g5b1Uw7zo6yw4Ymzyd1etKzAY7xAaGA3scwB8tAp3QzuY7CYdfTwlhiLKSAKbd7T/JBgxOXAGNcLDorJyVTXcg==} + '@oxlint/binding-linux-arm64-musl@1.76.0': + resolution: {integrity: sha512-oPFkkKTgl0K/EIg9fQ8oA3IGcI05/Mq1en04iFa41mmNPT+6KEiByVazTOZZJiHMBBrbsns1YJ2e1Scqwzesjw==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - '@oxlint/linux-x64-gnu@1.42.0': - resolution: {integrity: sha512-HnD99GD9qAbpV4q9iQil7mXZUJFpoBdDavfcC2CgGLPlawfcV5COzQPNwOgvPVkr7C0cBx6uNCq3S6r9IIiEIg==} + '@oxlint/binding-linux-ppc64-gnu@1.76.0': + resolution: {integrity: sha512-gN7yZ0eqflA5Fhf1wvHxGUltIV3FsvmB1zhNMDEK9vSHhc7E6qg9CuPeBgPZab66Tjzq6w6kHAtNEvnTHf4cyw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + + '@oxlint/binding-linux-riscv64-gnu@1.76.0': + resolution: {integrity: sha512-S/HqMbn22mQrjtErUxEoS/a55u8kIeXvreIxiJu5G7Le3UecEd6SQZxrDIpuhtgaFnsY/nVra3ytP+pRljDilA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + + '@oxlint/binding-linux-riscv64-musl@1.76.0': + resolution: {integrity: sha512-ZIga3097VJZolGZk6SrIAUokIGfRkxRlhiHDUznZptGBfwrhD7pNfD1rzEzsCwvk/1DX0A1bLz+liuNh5QKIVQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + + '@oxlint/binding-linux-s390x-gnu@1.76.0': + resolution: {integrity: sha512-ZGiiA7pFzMJSyMWYZTVlPgbTsx+Vl8ihLGMIujPwaslUF7kIPPWAbVmAlTc+9lWDV+DCiB8Ikixu+lSHeOIIWQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + + '@oxlint/binding-linux-x64-gnu@1.76.0': + resolution: {integrity: sha512-JLiy5WuvEBFTT6ErIFV35SLzi0R7Iri6MKU6dZbTxfIx8pndbbPs3Mj780nMipBFcPkti+okAPOJ9POKkHFEgg==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - '@oxlint/linux-x64-musl@1.42.0': - resolution: {integrity: sha512-8NTe8A78HHFn+nBi+8qMwIjgv9oIBh+9zqCPNLH56ah4vKOPvbePLI6NIv9qSkmzrBuu8SB+FJ2TH/G05UzbNA==} + '@oxlint/binding-linux-x64-musl@1.76.0': + resolution: {integrity: sha512-z7lgKQtbo/I1NIe8G5NHLesxJDv0tRSUWTpXKb9Pm3E9nKFKfO4IOSDtFroKgXtOYb0jQbcdH+0wzTyMXVes+A==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - '@oxlint/win32-arm64@1.42.0': - resolution: {integrity: sha512-lAPS2YAuu+qFqoTNPFcNsxXjwSV0M+dOgAzzVTAN7Yo2ifj+oLOx0GsntWoM78PvQWI7Q827ZxqtU2ImBmDapA==} + '@oxlint/binding-openharmony-arm64@1.76.0': + resolution: {integrity: sha512-JOjKymIpb9QcYfEhZsN6h4V9Ivd474W38cNIBRv6bg2TbIvogbMTH0Mg6YWW9TiRDqfcX+/Hyfsbo5vcSE5guQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@oxlint/binding-win32-arm64-msvc@1.76.0': + resolution: {integrity: sha512-pqDWZiwcmByWUEm1NFUBNiT6aentCcaoMWJv0HbXEmuYermJ4sg8ppVrshubYP2MZ6SHccJJcpr6x469PuDFIw==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@oxlint/win32-x64@1.42.0': - resolution: {integrity: sha512-3/KmyUOHNriL6rLpaFfm9RJxdhpXY2/Ehx9UuorJr2pUA+lrZL15FAEx/DOszYm5r10hfzj40+efAHcCilNvSQ==} + '@oxlint/binding-win32-ia32-msvc@1.76.0': + resolution: {integrity: sha512-Ba0O659kgMv6pwO3z9PdO+K3aMxQRaw9HnG+e6AtOfgwcKFvYilciQYBoUBmxfQvOCKZe1SwjMkuB542NkuDMQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ia32] + os: [win32] + + '@oxlint/binding-win32-x64-msvc@1.76.0': + resolution: {integrity: sha512-5qcirPHO8nKfkoowEVWtpAoVTcYDy6g0UT0NGic450Qv8J2NrOqg4uQ8QppRP4MDTC7Xx47lbZnmadTH03CGGA==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] + '@oxlint/plugins@1.76.0': + resolution: {integrity: sha512-twmbsVrYAjkaOw6I2pDiYSAxVLNOV8kcqMzajJ599SpqXzea+GjDCZVad1VUqNR/4thWjM4PER5n+3/TQtTkZQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + '@pkgjs/parseargs@0.11.0': resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} + '@pnpm/deps.graph-sequencer@1100.0.1': + resolution: {integrity: sha512-pOr5+q1fLYKwFN3LAJuGZEnfXDcQ73zqgDHMtGy+K+uIoUqyY+6MeDCWFwfu+4EFuq76I5EPFofoNAI+Bmmq4A==} + engines: {node: '>=22.13'} + '@polka/url@1.0.0-next.29': resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} @@ -2578,26 +2665,23 @@ packages: '@protobufjs/codegen@2.0.5': resolution: {integrity: sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==} - '@protobufjs/eventemitter@1.1.0': - resolution: {integrity: sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==} + '@protobufjs/eventemitter@1.1.1': + resolution: {integrity: sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==} - '@protobufjs/fetch@1.1.0': - resolution: {integrity: sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==} + '@protobufjs/fetch@1.1.1': + resolution: {integrity: sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==} '@protobufjs/float@1.0.2': resolution: {integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==} - '@protobufjs/inquire@1.1.1': - resolution: {integrity: sha512-mnzgDV26ueAvk7rsbt9L7bE0SuAoqyuys/sMMrmVcN5x9VsxpcG3rqAUSgDyLp0UZlmNfIbQ4fHfCtreVBk8Ew==} - '@protobufjs/path@1.1.2': resolution: {integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==} '@protobufjs/pool@1.1.0': resolution: {integrity: sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==} - '@protobufjs/utf8@1.1.1': - resolution: {integrity: sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==} + '@protobufjs/utf8@1.1.2': + resolution: {integrity: sha512-b1UQwcEZ4yCnMCD8DAL1VlbvBJE9/IX4FTIp7BG1xYpf29SLazLSrqUkj4w7Y5y7cCVP6E5tcqqcI0xemPkHug==} '@react-native/assets-registry@0.83.0': resolution: {integrity: sha512-EmGSKDvmnEnBrTK75T+0Syt6gy/HACOTfziw5+392Kr1Bb28Rv26GyOIkvptnT+bb2VDHU0hx9G0vSy5/S3rmQ==} @@ -2665,97 +2749,97 @@ packages: resolution: {integrity: sha512-V09ayfnb5GyysmvARbt+voFZAjGcf7hSYxOYxSkCc4fbH/DTfq5YWoec8cflvmHHqyIFbqvmGKmYFzqhr9zxDg==} engines: {node: '>=18.17.0', npm: '>=9.5.0'} - '@rolldown/binding-android-arm64@1.0.0': - resolution: {integrity: sha512-TWMZnRLMe63C2Lhyicviu7ZHaU4kxa6PS3rofvc9GmcvptzNN11BcfQ4Sl7MwTOsisQoa2keB/EBdNCAnUo8vA==} + '@rolldown/binding-android-arm64@1.1.5': + resolution: {integrity: sha512-lZg8fqIv2v7FF237bwMgzGZEJvGL79/s5knJ/i6FmsGF4XXlzccZ4jb+TrFIxtSSxFtIpdsgrPZeMk1I9AFcyQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@rolldown/binding-darwin-arm64@1.0.0': - resolution: {integrity: sha512-6XcD+8k0gPVItNagEw78/qqcBDwKcwDYS8V2hRmVsfUSIrd8cWe/CBvRDI5toqFyPfj+FJr6t8U6Xj2P2prEew==} + '@rolldown/binding-darwin-arm64@1.1.5': + resolution: {integrity: sha512-51Bnx9pNiMRKSUNtBfySkNJ9vMU9Hh3I1ozDd6gyPPYzaXCfnptUcEZxXGYFn+ul2dtcMUiqGR1Yai2K10uoTw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@rolldown/binding-darwin-x64@1.0.0': - resolution: {integrity: sha512-iN/tWVXRQDWvmZlKdceP1Dwug9GDpEymhb9p4xnEe6zvCg5lFmzVljl+1qR1NVx3yfGpr2Na+CuLmv5IU8uzfQ==} + '@rolldown/binding-darwin-x64@1.1.5': + resolution: {integrity: sha512-Tm+gbfC0aHu1tBA/JvKQh32S0K6YgCHkiAF4/W6xX0K0RmNuc94VeK419dJoE65R5aRxmo+noZQSWrAMF6yb6g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@rolldown/binding-freebsd-x64@1.0.0': - resolution: {integrity: sha512-jjQMDvvwSOuhOwMszD/klSOjyWMM3zI64hWTj9KT5x4MxRbZAf+7vLQ6qouRhtsLVFHr3f0ILaJAfgENPiQdAQ==} + '@rolldown/binding-freebsd-x64@1.1.5': + resolution: {integrity: sha512-JMzDKCCXq93YccG5gz3hvOs1oXRKAf0XYpfOS88e+wZrC8Iugj6j68867vrYZkvpDDpKn/KoKORThmchMpF6TA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@rolldown/binding-linux-arm-gnueabihf@1.0.0': - resolution: {integrity: sha512-d//Dtg2x6/m3mbV64yUGNnDGNZaDGRpDLLNGerHQUVObuNaIQaaDp25yUiqGXtHEXX+NP2d0wAlmKgpYgIAJ2A==} + '@rolldown/binding-linux-arm-gnueabihf@1.1.5': + resolution: {integrity: sha512-uML21j2K5TfPGutKxub+M+nLjZIrWjXQ5Grx4lCe/nimTj9B4L63zHpjXLl4y0L3mcm2htEQIb06oCG/szerNw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@rolldown/binding-linux-arm64-gnu@1.0.0': - resolution: {integrity: sha512-n7Ofp0mx+aB2cC+Sdy5YtMnXtY9lchnHbY+3Yt0uq9JsWQExf4f5Whu0tK0R8Jdc9S6RchTHjIFY7uc92puOVQ==} + '@rolldown/binding-linux-arm64-gnu@1.1.5': + resolution: {integrity: sha512-navSiuTMogvnQoZoM/v+l3ZWo50/NTwSHSzheABx/RCnmUPaKwq9qSo4Br2OYRs21+Fz8uFqITZM3H4opOB0/Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - '@rolldown/binding-linux-arm64-musl@1.0.0': - resolution: {integrity: sha512-EIVjy2cgd7uuMMo94FVkBp7F6DhcZAUwNURkSG3RwUmvAXR6s0ISxM81U+IydcZByPG0pZIHsf1b6kTxoFDgJA==} + '@rolldown/binding-linux-arm64-musl@1.1.5': + resolution: {integrity: sha512-lAryqH7IteztmCXQXk0etKj4wBQ7Gx5S6LjKhsgp9zb8I5bsuvU/2llH1hDQcjsFeqIsovMVN339/8pUDDBXxA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - '@rolldown/binding-linux-ppc64-gnu@1.0.0': - resolution: {integrity: sha512-JEwwOPcwTLAcpDQlqSmjEmfs63xJnSiUNIGvLcDLUHCWK4XowpS/7c7tUsUH6uT/ct6bMUTdXKfI8967FYj6mg==} + '@rolldown/binding-linux-ppc64-gnu@1.1.5': + resolution: {integrity: sha512-fsK/sNBnxzBlL4O1JNrZakVQxPspqpED5dLtNsZS9oOKmtSpdNIzxH2kkol5HYTWJN47sE20ztMJPxfZ89qGOg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] - '@rolldown/binding-linux-s390x-gnu@1.0.0': - resolution: {integrity: sha512-0wjCFhLrihtAubnT9iA0N++0pSV0z5Hg7tNGdNJ4RFaINceHadoF+kiFGyY1qSSNVIAZtLotG8Ju1bgDPkjnFA==} + '@rolldown/binding-linux-s390x-gnu@1.1.5': + resolution: {integrity: sha512-gLYb4BIadlfTOYT5gO503n8zQjXflgzpD0FcyKh0Mzx3rqCZKnHoJWV9xe1KXUJ5lx2JfcSHr/mhzS0PC/McAA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] - '@rolldown/binding-linux-x64-gnu@1.0.0': - resolution: {integrity: sha512-Dfn7iak9BcMMePxcoJfpSbWqnEyrp/dRF63/8qW/eHBdOZov6x5aShLLEYGYdIeSJ6vMLK/XCVB+lGIxm41bQA==} + '@rolldown/binding-linux-x64-gnu@1.1.5': + resolution: {integrity: sha512-FjcpEKUyJygHgs1o50VYNvkt5+7Le/VEdYt0AkRpkL33MnyQfwr8l5mXwMmfmTbyMPr5vJLC+8/Gd9gXnwU1QQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - '@rolldown/binding-linux-x64-musl@1.0.0': - resolution: {integrity: sha512-5/utzzDmD/pD/bmuaUcbTf/sZYy0aztwIVlfpoW1fTjCZ0BaPOMVWGZL1zvgxyi7ZIVYWlxKONHmSbHuiOh8Jw==} + '@rolldown/binding-linux-x64-musl@1.1.5': + resolution: {integrity: sha512-Me+PfPI2TMeOQk0gYWfLQZtTktrmzbr8cDboqX83XKc7UrgAi55gF+2dUkWdxd19n55Essp2yeca+O9N5rBxHg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - '@rolldown/binding-openharmony-arm64@1.0.0': - resolution: {integrity: sha512-ouJs8VcUomfLfpbUECqFMRqdV4x6aeAK3MA4m6vTrJJjKyWTV5KnxZx7Jd9G+GlDaQQxubcba00x16OyJ1meig==} + '@rolldown/binding-openharmony-arm64@1.1.5': + resolution: {integrity: sha512-yc5WrLzXks6zCQfn9Oxr8pORKyl/pF+QjHmW/Qx3qu0oyrrNC+y2JLTU1E2rcWYAmzlnqngWXHQjy51VzW70Vw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@rolldown/binding-wasm32-wasi@1.0.0': - resolution: {integrity: sha512-E+oHKGiDA+lsKMmFtffDDw91EryDT7uJocrIuCHqhm6bCTM6xFK+3gaCkYOHfPwQr0cCNarSM2xaELoQDz9jJg==} + '@rolldown/binding-wasm32-wasi@1.1.5': + resolution: {integrity: sha512-VbQGPX2b4r48TAMIM2cjgluIM1HYutm4pcTEJsle7iEP7sB1dFqtPLBVbdLAZCxy1txCcPxf4QFf4v8uvltPqA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [wasm32] - '@rolldown/binding-win32-arm64-msvc@1.0.0': - resolution: {integrity: sha512-yYK02n8Rngo+gbm1y6G0+7jk1sJ/2Wt7K0me0Y7k/ErBpyf+LJ2gFpqWVTcRV1rUepBlQRmpgWkTQCiiwrK0Ow==} + '@rolldown/binding-win32-arm64-msvc@1.1.5': + resolution: {integrity: sha512-gHv82k63z4qpV5+Q1y/12KrK0ltWBukVDI8nZcbT7Tt/ZlOIVwppazneq0F93oDxTo3IgAMEDIoQh3E2n6mVsw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@rolldown/binding-win32-x64-msvc@1.0.0': - resolution: {integrity: sha512-14bpChMahXRRXiTwahSl+zzHPW6qQTXtkMuJBFlbo+pqSAews2d4BdCSHfrJ/MBsCZtpmTafsY+1QhBzitcmdg==} + '@rolldown/binding-win32-x64-msvc@1.1.5': + resolution: {integrity: sha512-tTZuDBPw85tEN5PQi1pnEBzDy0Z49HtScLAbD5t6hyeU92A95pRWaSMw1GZZi/RwgSgUIl0xrSlXIT/9QzvYSA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] - '@rolldown/pluginutils@1.0.0': - resolution: {integrity: sha512-aKs/3GSWyV0mrhNmt/96/Z3yczC3yvrzYATCiCXQebBsGyYzjNdUphRVLeJQ67ySKVXRfMxt2lm12pmXvbPFQQ==} + '@rolldown/pluginutils@1.0.1': + resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} '@rollup/plugin-node-resolve@16.0.3': resolution: {integrity: sha512-lUYM3UBGuM93CnMPG1YocWu7X802BrNF3jW2zny5gQyLQgRFJhV1Sq0Zi74+dh/6NBx1DxFC4b4GXg9wUCG5Qg==} @@ -2784,8 +2868,8 @@ packages: rollup: optional: true - '@rollup/pluginutils@5.3.0': - resolution: {integrity: sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==} + '@rollup/pluginutils@5.4.0': + resolution: {integrity: sha512-MfPp06CjRLfXQ3wY0R8vJDYBy/MvVcc9OulEfR0B8Iv9ko+GCNaRZ+EpJYFl27LhKsZK0o420sYCRHCjfCgeUg==} engines: {node: '>=14.0.0'} peerDependencies: rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 @@ -2793,258 +2877,133 @@ packages: rollup: optional: true - '@rollup/rollup-android-arm-eabi@4.60.3': - resolution: {integrity: sha512-x35CNW/ANXG3hE/EZpRU8MXX1JDN86hBb2wMGAtltkz7pc6cxgjpy1OMMfDosOQ+2hWqIkag/fGok1Yady9nGw==} + '@rollup/rollup-android-arm-eabi@4.62.3': + resolution: {integrity: sha512-c0wdcekXtQvvn5Tsrk/+op/gUArrbWaFduBnTLP2l1cKLSQs4diMWjJw3m6A0DdzT8dAAX95KpkJ3qynCePbmw==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm-eabi@4.61.1': - resolution: {integrity: sha512-JnBB8MdXj45cajvTuO5FmPlvFVJRQgvrz1uSEl3NwqFnReAPGwb8EanbGi4z2nRaqLzjJSv5/JmycoTKlRZxHA==} - cpu: [arm] - os: [android] - - '@rollup/rollup-android-arm64@4.60.3': - resolution: {integrity: sha512-xw3xtkDApIOGayehp2+Rz4zimfkaX65r4t47iy+ymQB2G4iJCBBfj0ogVg5jpvjpn8UWn/+q9tprxleYeNp3Hw==} - cpu: [arm64] - os: [android] - - '@rollup/rollup-android-arm64@4.61.1': - resolution: {integrity: sha512-Jx2g7iSjw4AOT0HDPHM9RV3GNjRXwybWtSFZiZAYUTjUwjVrYIwq3kBf+LnhqJlzXFAqTAh2F7IGI+O568exPw==} + '@rollup/rollup-android-arm64@4.62.3': + resolution: {integrity: sha512-3YjElDdWN+qXAFbJ/CzPV+0wspLqh54k/I6GfdYtEJRqg7buSgc1yPM3B+93j1M4neobtkATHZTmxK2AMVGfnA==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.60.3': - resolution: {integrity: sha512-vo6Y5Qfpx7/5EaamIwi0WqW2+zfiusVihKatLvtN1VFVy3D13uERk/6gZLU1UiHRL6fDXqj/ELIeVRGnvcTE1g==} - cpu: [arm64] - os: [darwin] - - '@rollup/rollup-darwin-arm64@4.61.1': - resolution: {integrity: sha512-0F1L/Z3Eqv8mT2n3dCpeO8GcTvHvVqkP5/t6DMsn0KzhYVcg+s7Ncl5DS8qjKYEeio6Az0Gt6nyBORay5qIlCA==} + '@rollup/rollup-darwin-arm64@4.62.3': + resolution: {integrity: sha512-Pch2pFNOxxz1hTjypIdPyRTR6riiwRl84+VcN9djS680fw+Co1nAJINrdpqp7KV0NvyuU8ilZXZCjd7ykJl1GQ==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.60.3': - resolution: {integrity: sha512-D+0QGcZhBzTN82weOnsSlY7V7+RMmPuF1CkbxyMAGE8+ZHeUjyb76ZiWmBlCu//AQQONvxcqRbwZTajZKqjuOw==} - cpu: [x64] - os: [darwin] - - '@rollup/rollup-darwin-x64@4.61.1': - resolution: {integrity: sha512-qLttcH871ujY4YcVfUSShhOw+CsoTatYz8gRbHO7Bb92QH059/P0y5do1KMs41fY0BpD2x4AJH/gID0zFiqVKQ==} + '@rollup/rollup-darwin-x64@4.62.3': + resolution: {integrity: sha512-LEuncFUHFiF8t4yZVZvvZA1wk0pjAscRnsrn1EfTEmN4HXotBi2YtcnLRyaK6UbuczW7xZS5ES+81Rdz8Z0T6g==} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.60.3': - resolution: {integrity: sha512-6HnvHCT7fDyj6R0Ph7A6x8dQS/S38MClRWeDLqc0MdfWkxjiu1HSDYrdPhqSILzjTIC/pnXbbJbo+ft+gy/9hQ==} + '@rollup/rollup-freebsd-arm64@4.62.3': + resolution: {integrity: sha512-zvBUvsQUpOWALdDsk6qbS8bXf2VxmPisuudNDrY7x0p0jBdsoZl8HsHczIOgkQiZldmcacMKtBzpoGVNeIe2bQ==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-arm64@4.61.1': - resolution: {integrity: sha512-fUI4RapGE0Oh3mb8mgfvC1O2nU1RpDZUKnDQm3xB1Ipg7C2wTs5Kstz7G2uWK99a8S2yTMq8/P4uycwNa0nJyw==} - cpu: [arm64] - os: [freebsd] - - '@rollup/rollup-freebsd-x64@4.60.3': - resolution: {integrity: sha512-KHLgC3WKlUYW3ShFKnnosZDOJ0xjg9zp7au3sIm2bs/tGBeC2ipmvRh/N7JKi0t9Ue20C0dpEshi8WUubg+cnA==} + '@rollup/rollup-freebsd-x64@4.62.3': + resolution: {integrity: sha512-C2KmNrcSem/AMg984H/dev+si0lieQGdXdR/lYGJnuumXnFb9Y7QdiI62obFdLlxRYLBv4P0eUVIDbD4c1vVvw==} cpu: [x64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.61.1': - resolution: {integrity: sha512-H5YrdvJaDtI/U9/emrD4b++xkvp3y/JvOe4rizHbxvkyMfRS/CiRYdji+Pl8D0brEaNFWUh1drQxgAGIl6Xudw==} - cpu: [x64] - os: [freebsd] - - '@rollup/rollup-linux-arm-gnueabihf@4.60.3': - resolution: {integrity: sha512-DV6fJoxEYWJOvaZIsok7KrYl0tPvga5OZ2yvKHNNYyk/2roMLqQAbGhr78EQ5YhHpnhLKJD3S1WFusAkmUuV5g==} - cpu: [arm] - os: [linux] - - '@rollup/rollup-linux-arm-gnueabihf@4.61.1': - resolution: {integrity: sha512-Q8CBCCQtDFrYtXoeUXSrnFXKOnyUhx6bz+SkL6A0E7V8kAiCJ5pamq1WtbfpVGhR5TSpXY6ak3avmDc5fHTyJA==} + '@rollup/rollup-linux-arm-gnueabihf@4.62.3': + resolution: {integrity: sha512-ggXnsTAEzNQx74XpunRsiZ9aBZDsI7XIa0hm2nzR9f4WzH5/f/d73ZSDaC5ejJ8YLY4NW+V3wr0tjOaeCq8hqA==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.60.3': - resolution: {integrity: sha512-mQKoJAzvuOs6F+TZybQO4GOTSMUu7v0WdxEk24krQ/uUxXoPTtHjuaUuPmFhtBcM4K0ons8nrE3JyhTuCFtT/w==} + '@rollup/rollup-linux-arm-musleabihf@4.62.3': + resolution: {integrity: sha512-2vng+FlzNUhKZxtej3IUqJgbZoQk2M/dwQM20+ULV0R/E/8tr9/P6uEf2iiGIk4HL0zMKh5Jry7mUHdUOvyGgA==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.61.1': - resolution: {integrity: sha512-nwnhk1581l0FBVellGcVCAT0Oi06onEA3WB53sf01VO3I0UPBkMH9sXONYME2K0ovXcNayJfNtHfm6mpJElatQ==} - cpu: [arm] - os: [linux] - - '@rollup/rollup-linux-arm64-gnu@4.60.3': - resolution: {integrity: sha512-Whjj2qoiJ6+OOJMGptTYazaJvjOJm+iKHpXQM1P3LzGjt7Ff++Tp7nH4N8J/BUA7R9IHfDyx4DJIflifwnbmIA==} + '@rollup/rollup-linux-arm64-gnu@4.62.3': + resolution: {integrity: sha512-LLLFZKt4/Nraf9rxDkhiU8QVgLF4WmCkfr0L4fj0fPfIZFBib0DeiFk1hhaYKd03LFAFJcxHslhDFlNJLylf5Q==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.61.1': - resolution: {integrity: sha512-x5Xr49hwt3hdW75UOZm3395YwwzPyauktslv29KpWL/T+vVAzoT3azLcTWv0eMciBNrx+DYjH4paehHoLpPvpg==} + '@rollup/rollup-linux-arm64-musl@4.62.3': + resolution: {integrity: sha512-WJkdQCvS9sWNOUBJZfQRKpZGFBztRzcowI+nndmflKgU4XY+3a420FgTOSKTsVqJbnzSxeT4vaJalpOaPo2YCQ==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.60.3': - resolution: {integrity: sha512-4YTNHKqGng5+yiZt3mg77nmyuCfmNfX4fPmyUapBcIk+BdwSwmCWGXOUxhXbBEkFHtoN5boLj/5NON+u5QC9tg==} - cpu: [arm64] - os: [linux] - - '@rollup/rollup-linux-arm64-musl@4.61.1': - resolution: {integrity: sha512-unMS3H73DpaoPyyEVPjGKleM/s0mkmsauTENpw4INQY8y4+IuLNjkueQ5QCtC0D3N38Y38yhAU8OoZ20S2Tm6w==} - cpu: [arm64] - os: [linux] - - '@rollup/rollup-linux-loong64-gnu@4.60.3': - resolution: {integrity: sha512-SU3kNlhkpI4UqlUc2VXPGK9o886ZsSeGfMAX2ba2b8DKmMXq4AL7KUrkSWVbb7koVqx41Yczx6dx5PNargIrEA==} - cpu: [loong64] - os: [linux] - - '@rollup/rollup-linux-loong64-gnu@4.61.1': - resolution: {integrity: sha512-zNZzGRnAhwjFEYmvphJRV5XaQGjs62cCmeYYHUT//NbvEnHauw+I85nGG+SiVg5ld4GX8D1IbKIX+ozITQnhMQ==} + '@rollup/rollup-linux-loong64-gnu@4.62.3': + resolution: {integrity: sha512-PwHXCCS2n64/1Ot6rP1YEYA02MGYBcQlr8CSZZyrUG2O7NH6NklYmvr9v3Jy+5e/eDeNchc/ukmKJi9LuflMIQ==} cpu: [loong64] os: [linux] - '@rollup/rollup-linux-loong64-musl@4.60.3': - resolution: {integrity: sha512-6lDLl5h4TXpB1mTf2rQWnAk/LcXrx9vBfu/DT5TIPhvMhRWaZ5MxkIc8u4lJAmBo6klTe1ywXIUHFjylW505sg==} + '@rollup/rollup-linux-loong64-musl@4.62.3': + resolution: {integrity: sha512-vUjxINQu3RC8NZS3ykk1gN65gIz8pAopOq2HXuZhiIxHdx7TFvDG+jgrdSgInu1Eza4/Rfi2VzZgyIgEH4WOaw==} cpu: [loong64] os: [linux] - '@rollup/rollup-linux-loong64-musl@4.61.1': - resolution: {integrity: sha512-LdpWGL8X209B2SIvWjqlc8VZgM6PKfontSerGepuldQmHYrAOtnMCXeJkxXGbC+PPZVOuu5czJo7fNV6aeW8rQ==} - cpu: [loong64] - os: [linux] - - '@rollup/rollup-linux-ppc64-gnu@4.60.3': - resolution: {integrity: sha512-BMo8bOw8evlup/8G+cj5xWtPyp93xPdyoSN16Zy90Q2QZ0ZYRhCt6ZJSwbrRzG9HApFabjwj2p25TUPDWrhzqQ==} - cpu: [ppc64] - os: [linux] - - '@rollup/rollup-linux-ppc64-gnu@4.61.1': - resolution: {integrity: sha512-EC5kTtNaNGOmbMGqar8dvJy6y/hg99GAwjfBz++pxZhQATXGcRjd6c5en5wcbru0vkRmiMGsQKdMJOOf6sza4g==} - cpu: [ppc64] - os: [linux] - - '@rollup/rollup-linux-ppc64-musl@4.60.3': - resolution: {integrity: sha512-E0L8X1dZN1/Rph+5VPF6Xj2G7JJvMACVXtamTJIDrVI44Y3K+G8gQaMEAavbqCGTa16InptiVrX6eM6pmJ+7qA==} + '@rollup/rollup-linux-ppc64-gnu@4.62.3': + resolution: {integrity: sha512-wzko4aJ13+0G3kGnviCg5gnXFKd40izKsrf2uOw12US4XqprkDrmwOpeW14aSNa37V8bfPcz5Fkob6LZ3BAPmA==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-ppc64-musl@4.61.1': - resolution: {integrity: sha512-8hiwp6D4acEcNK78I4rP0/XtS1sknWIAMJBPdR4l6zUtyTm5KiTDr5bXmWt4foY7nAN7AThDHgkLIEZOWKbzWw==} + '@rollup/rollup-linux-ppc64-musl@4.62.3': + resolution: {integrity: sha512-8120ue0JUMSwy11stlwnfdX3pPd+WZYGCDBwEHWtIHi6pOpZmsEF5QKB7a/UN+XFdqvobxz98kv8RTqikyCEBw==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.60.3': - resolution: {integrity: sha512-oZJ/WHaVfHUiRAtmTAeo3DcevNsVvH8mbvodjZy7D5QKvCefO371SiKRpxoDcCxB3PTRTLayWBkvmDQKTcX/sw==} - cpu: [riscv64] - os: [linux] - - '@rollup/rollup-linux-riscv64-gnu@4.61.1': - resolution: {integrity: sha512-10dh/h/BqA7DuMPWSxkR8uks18FRwnwOEqr5zOTEl+NOwP/OMzKX8OFR/Of9xxDA7D5qef1Nzar5WDD2kCCr1g==} + '@rollup/rollup-linux-riscv64-gnu@4.62.3': + resolution: {integrity: sha512-XLFHnR3tXMjbOCh2vtVJHmxt+995uJsTERQyseFDRA0xxMxyTZPLa3OIUlyFaO4mF/Lu0FjmWHCuPXJT1n/IOg==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-riscv64-musl@4.60.3': - resolution: {integrity: sha512-Dhbyh7j9FybM3YaTgaHmVALwA8AkUwTPccyCQ79TG9AJUsMQqgN1DDEZNr4+QUfwiWvLDumW5vdwzoeUF+TNxQ==} + '@rollup/rollup-linux-riscv64-musl@4.62.3': + resolution: {integrity: sha512-se6yXvNGMIl0f+RQzyh7XAmia8/9kplQx424wnG2w0C1oi6XgO6Y8otKhdXFHbHs88Ihavzmvh1NWjuovE76BQ==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-riscv64-musl@4.61.1': - resolution: {integrity: sha512-YKJ5lg35DP17gcAOggnihe+APw9HLyj1Xn7gsmGumBJAUDa6NGXNixJzmkWLhcK9TOuuyQjdamzvJefkO7qHZQ==} - cpu: [riscv64] - os: [linux] - - '@rollup/rollup-linux-s390x-gnu@4.60.3': - resolution: {integrity: sha512-cJd1X5XhHHlltkaypz1UcWLA8AcoIi1aWhsvaWDskD1oz2eKCypnqvTQ8ykMNI0RSmm7NkTdSqSSD7zM0xa6Ig==} - cpu: [s390x] - os: [linux] - - '@rollup/rollup-linux-s390x-gnu@4.61.1': - resolution: {integrity: sha512-Mlil5G2Jj6a7B3LWGctg+XPL9vdXYuzCtNXfxOQ0nPjc2m6ueUktocPGH9bnAM0bNRKb/bAWTujUU7IJQdQA+g==} + '@rollup/rollup-linux-s390x-gnu@4.62.3': + resolution: {integrity: sha512-gNoxRefktVIiGflpONuxWWXZAzIQG++z9qHO3xKwk4WdDMuQja3JHGfE1u0i3PfPDyvhypdk+WrgIJqLhGG7sg==} cpu: [s390x] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.60.3': - resolution: {integrity: sha512-DAZDBHQfG2oQuhY7mc6I3/qB4LU2fQCjRvxbDwd/Jdvb9fypP4IJ4qmtu6lNjes6B531AI8cg1aKC2di97bUxA==} - cpu: [x64] - os: [linux] - - '@rollup/rollup-linux-x64-gnu@4.61.1': - resolution: {integrity: sha512-bVWIOIk6pV01p4CdUbPP7CJ/434z+OooYjDuFcR+44N35YvKUC66G8MGnvcWx5mWKW3g61J+t74l3Kj15Kwn2Q==} + '@rollup/rollup-linux-x64-gnu@4.62.3': + resolution: {integrity: sha512-V4KtWtQfAFMU7+9/A/VDps/VI8CHd3cYz0L8sgJzz8qK7eY7wI4ruFD82UYIYvW9Z4DtlTfhQcsl4XyPHW5uSg==} cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.60.3': - resolution: {integrity: sha512-cRxsE8c13mZOh3vP+wLDxpQBRrOHDIGOWyDL93Sy0Ga8y515fBcC2pjUfFwUe5T7tqvTvWbCpg1URM/AXdWIXA==} + '@rollup/rollup-linux-x64-musl@4.62.3': + resolution: {integrity: sha512-LBx9LYXvj2CBkMkjLdNAWLwH0MLMin7do2VcVo9kVPibGLkY0BQQut2fv7NVqkXqZ/CrAu9LqDHVV1xHCMpCPw==} cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.61.1': - resolution: {integrity: sha512-qy5pBvZbqNFheBz61R1rzsezjm0J7O2oNGoWtGoY89SZYLUfxAJTBAqDChqAIdB4rCiIbi9nF7yZ83GnNiLwSw==} - cpu: [x64] - os: [linux] - - '@rollup/rollup-openbsd-x64@4.60.3': - resolution: {integrity: sha512-QaWcIgRxqEdQdhJqW4DJctsH6HCmo5vHxY0krHSX4jMtOqfzC+dqDGuHM87bu4H8JBeibWx7jFz+h6/4C8wA5Q==} + '@rollup/rollup-openbsd-x64@4.62.3': + resolution: {integrity: sha512-ABVf3Q0RCu7NcyCCOZQI0pJ3GuSdfSl8EXcy88QtdceIMIoCUdfhsJChZ64L9zVM2aJHjde1Bhn5uqSRcX9ySA==} cpu: [x64] os: [openbsd] - '@rollup/rollup-openbsd-x64@4.61.1': - resolution: {integrity: sha512-E83TXjI4zm0+5f2qO+UOudaCYIhYwpJ5jq6YCZNIZ+6CbfhKrkAGezeiASBL9ElxAxFsRS9ZhESv8mfnj6TKeg==} - cpu: [x64] - os: [openbsd] - - '@rollup/rollup-openharmony-arm64@4.60.3': - resolution: {integrity: sha512-AaXwSvUi3QIPtroAUw1t5yHGIyqKEXwH54WUocFolZhpGDruJcs8c+xPNDRn4XiQsS7MEwnYsHW2l0MBLDMkWg==} - cpu: [arm64] - os: [openharmony] - - '@rollup/rollup-openharmony-arm64@4.61.1': - resolution: {integrity: sha512-fbWnKqVkjrJN38vNe3ahkbk6iejS/3b0Nt7EEtPpE6RBacZcGXNKbzfHN3GUUlXOPghUg0j6XUGrtjX9z1sIvA==} + '@rollup/rollup-openharmony-arm64@4.62.3': + resolution: {integrity: sha512-+2Cy/ldweGBLlPIKsQLF8U5N44a0KDdbrk1rAjHOM9M2K+kGdIVjHLmmrZIcx+9Ny3ke/1JomCsDI1ocb11+sg==} cpu: [arm64] os: [openharmony] - '@rollup/rollup-win32-arm64-msvc@4.60.3': - resolution: {integrity: sha512-65LAKM/bAWDqKNEelHlcHvm2V+Vfb8C6INFxQXRHCvaVN1rJfwr4NvdP4FyzUaLqWfaCGaadf6UbTm8xJeYfEg==} - cpu: [arm64] - os: [win32] - - '@rollup/rollup-win32-arm64-msvc@4.61.1': - resolution: {integrity: sha512-ArMl38iVAbk0New1ogihQNY6iphLi4ZaRsa037gUzv5yeKPY8TD3Dmy4x2RNC1VztU/uqm+G+/RwFrSka3Oy2g==} + '@rollup/rollup-win32-arm64-msvc@4.62.3': + resolution: {integrity: sha512-dtZvzc8BedpSaFNy75x6uiWwAGTH+aZHDtdrqP6qk+WcLJrfti6sGje1ZJ9UxyzDLF23d/mV+PaMwuC0hL7UVA==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.60.3': - resolution: {integrity: sha512-EEM2gyhBF5MFnI6vMKdX1LAosE627RGBzIoGMdLloPZkXrUN0Ckqgr2Qi8+J3zip/8NVVro3/FjB+tjhZUgUHA==} + '@rollup/rollup-win32-ia32-msvc@4.62.3': + resolution: {integrity: sha512-Rj8Ra4noo+aYy7sKBggCx0407mws34kAb1ySyWuq5DAtFBQdkSwnsjCgPrhPe9cvgBKZIukpE+CVHvORCS93kQ==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.61.1': - resolution: {integrity: sha512-0mYtjHS9ucAbcATycCNK9IGBk/cCe/ma7EmSLGZdsxnOA8cjRIyU04wDpVAD9NiOfLUR9KTxdiO53uOkherqjQ==} - cpu: [ia32] - os: [win32] - - '@rollup/rollup-win32-x64-gnu@4.60.3': - resolution: {integrity: sha512-E5Eb5H/DpxaoXH++Qkv28RcUJboMopmdDUALBczvHMf7hNIxaDZqwY5lK12UK1BHacSmvupoEWGu+n993Z0y1A==} - cpu: [x64] - os: [win32] - - '@rollup/rollup-win32-x64-gnu@4.61.1': - resolution: {integrity: sha512-gK1iCEPfpoSG9wfBihXxvBMi8ZfcWffYkEsC/Eih+iFENTaewvNcrEQ69lIOWYO5pePHKLHHO7nq5AILGO/HQQ==} - cpu: [x64] - os: [win32] - - '@rollup/rollup-win32-x64-msvc@4.60.3': - resolution: {integrity: sha512-hPt/bgL5cE+Qp+/TPHBqptcAgPzgj46mPcg/16zNUmbQk0j+mOEQV/+Lqu8QRtDV3Ek95Q6FeFITpuhl6OTsAA==} + '@rollup/rollup-win32-x64-gnu@4.62.3': + resolution: {integrity: sha512-vp7N084ew/odXn2gi/mzm9mUkQu9l6AiN6dt4IeUM2Uvm9o+cVmP+YkqbMOteLbiGgqBBlJZjIMYVCfOOIVbVQ==} cpu: [x64] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.61.1': - resolution: {integrity: sha512-X+zaP2x+j4RXGfbp/seSoRHWnPxzApilDszisZxbYH5C/jTxFhCtDNdPGZb9lJyYPs24wGxruPF7Y+sIXt9Gzw==} + '@rollup/rollup-win32-x64-msvc@4.62.3': + resolution: {integrity: sha512-MOG/3gTOn4Fwf574RVOaY61I5o6P90legkFADiTyn1hyjNydT+cerU2rLUwPdZkKKyJ+iT+K9p7WXK4LM1Ka6g==} cpu: [x64] os: [win32] - '@sinclair/typebox@0.27.10': - resolution: {integrity: sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==} + '@sinclair/typebox@0.27.12': + resolution: {integrity: sha512-hhyNJ+nbR6ZR7pToHvllEFun9TL0sbL+tk/ON75lo+Xas054uez98qRbsuNt7MBCyZKK4+8Yli/OAGZhmfBZ/g==} '@sindresorhus/is@7.2.0': resolution: {integrity: sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw==} @@ -3066,8 +3025,8 @@ packages: '@solidjs/router': optional: true - '@speed-highlight/core@1.2.15': - resolution: {integrity: sha512-BMq1K3DsElxDWawkX6eLg9+CKJrTVGCBAWVuHXVUV2u0s2711qiChLSId6ikYPfxhdYocLNt3wWwSvDiTvFabw==} + '@speed-highlight/core@1.2.17': + resolution: {integrity: sha512-Z92FwKpCtfaW1V0jTU/fh3QzYEZN8wDwrzRIBoADCJfn4mJCNcJN/XegifX7BDrQ8/h9Xh/JnbyMchL0FqXrkg==} '@sqliteai/sqlite-vector-darwin-arm64@1.0.0': resolution: {integrity: sha512-Z7tpJrOGexZInUbwWvB1a1/7seZQWzR25TvPk0JWeUQCSHW9auMt6qX/rm37gONIgzNSveqXqbrb9Y+a2cgYDQ==} @@ -3114,22 +3073,27 @@ packages: '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} - '@testcontainers/mysql@11.14.0': - resolution: {integrity: sha512-0/OKd1gOvnl0qS+RIpmT6J0XKWpjkVyIbOtS3cFTVR8t6Ps7W9TV0U+zp7FPxCmitWuWYPXwH/+RQXF+1jZuZQ==} + '@testcontainers/mssqlserver@12.0.4': + resolution: {integrity: sha512-UkxfpOavn4V6yoLQ/uxBPCgc13J8PcWemqTJSe0JCj54ZcYSh/24qjN5eTHzcLsIYSpx/ZXXBP31E2vWKLtIfA==} + + '@testcontainers/mysql@12.0.4': + resolution: {integrity: sha512-XZWh/L6EN8XQa5lsBdESDolDmyUA56bDgYh5SicLzzC2b4jho4lc0yA0GOOVX/bd0zK6VQK6hmDDJE+T3rFrdA==} - '@testcontainers/postgresql@11.14.0': - resolution: {integrity: sha512-wYbJn8GRTj8qfqzfVubxioYWlHJU/ImIjuzPwyy9C5Qfo6g3GLduPZAj+BifvqTZjgT3gd4gFVLCPhBji7dc1w==} + '@testcontainers/postgresql@12.0.4': + resolution: {integrity: sha512-a/pLU6j5lpKKAlUTPwqweqMGhOSjgTSb6HBX69TOrXn32ifU37nnQDmNFTj8ddOAw+BQL9oTRkeOxVbZkqhgZA==} - '@testcontainers/redis@11.14.0': - resolution: {integrity: sha512-WX005slz2JMQPw2avbSjf5awVjpmFhOs5xCxeGSYLcV5ia4W1edv/P6MdOw4dZnvDQDuN5LfqNoV/ut3XGb2pA==} + '@testcontainers/redis@12.0.4': + resolution: {integrity: sha512-44Ov3Lcptb3KqljcCy6xIJ2VxNsN+3Kw/YpBiotZpOcI4iLgLNmcOgvCFPVo+vDoh4rCfgtNUEbIpL5Mu7oRuw==} '@testing-library/dom@10.4.1': resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==} engines: {node: '>=18'} - '@testing-library/jest-dom@6.9.1': - resolution: {integrity: sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==} - engines: {node: '>=14', npm: '>=6', yarn: '>=1'} + '@testing-library/jest-dom@7.0.0': + resolution: {integrity: sha512-HKAH9C6mBo5yBG6yRO5i43L2iisencAo5z+o5P/saHUoY+miC5ivXRxHBJcFyB5ypPNxHJdK3BoF/3O4DIptMg==} + engines: {node: '>=22', npm: '>=6', yarn: '>=1'} + peerDependencies: + '@testing-library/dom': '>=10 <11' '@testing-library/react@16.3.2': resolution: {integrity: sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==} @@ -3162,12 +3126,18 @@ packages: resolution: {integrity: sha512-w071DSzP94YfN6XiWhOxnLpYT3uqtxJBDYdh6Jdjzt+Ce6DNspJsPQgpC7rbts/B8tEkq0LHoYuIF/O5Jh5rPg==} engines: {node: '>=18'} - '@tybys/wasm-util@0.10.2': - resolution: {integrity: sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==} + '@ts-morph/common@0.28.1': + resolution: {integrity: sha512-W74iWf7ILp1ZKNYXY5qbddNaml7e9Sedv5lvU1V8lftlitkc9Pq1A+jlH23ltDgWYeZFFEqGCD1Ies9hqu3O+g==} + + '@tybys/wasm-util@0.10.3': + resolution: {integrity: sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==} '@types/aria-query@5.0.4': resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} + '@types/babel__code-frame@7.27.0': + resolution: {integrity: sha512-Dwlo+LrxDx/0SpfmJ/BKveHf7QXWvLBLc+x03l5sbzykj3oB9nHygCpSECF1a+s+QIxbghe+KHqC90vGtxLRAA==} + '@types/babel__core@7.20.5': resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} @@ -3180,11 +3150,8 @@ packages: '@types/babel__traverse@7.28.0': resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} - '@types/better-sqlite3@7.6.13': - resolution: {integrity: sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA==} - - '@types/bun@1.3.13': - resolution: {integrity: sha512-9fqXWk5YIHGGnUau9TEi+qdlTYDAnOj+xLCmSTwXfAIqXr2x4tytJb43E9uCvt09zJURKXwAtkoH4nLQfzeTXw==} + '@types/bun@1.3.14': + resolution: {integrity: sha512-h1hFqFVcvAvD9j9K7ZW7vd82aSA+rTdznZa+5bwvCwqSB1jmmfLcbIWhOLx1/+boy/xmjgCs/OMUL8hRJSmnPw==} '@types/chai@5.2.3': resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} @@ -3192,24 +3159,27 @@ packages: '@types/deep-eql@4.0.2': resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + '@types/deno@2.7.0': + resolution: {integrity: sha512-Y6fWcV8KpYeO3Lik/RiYnUxtr/LWqoeACciU0CA+dr1U/45tGgaX3HWQQAPCNN53raN4Rr4Fht4y6qsUH57fDA==} + '@types/docker-modem@3.0.6': resolution: {integrity: sha512-yKpAGEuKRSS8wwx0joknWxsmLha78wNMe9R2S3UNsVOkZded8UqOrV8KoeDXoXsjndxwyF3eIhyClGbO1SEhEg==} '@types/dockerode@4.0.1': resolution: {integrity: sha512-cmUpB+dPN955PxBEuXE3f6lKO1hHiIGYJA46IVF3BJpNsZGvtBDcRnlrHYHtOH/B6vtDOyl2kZ2ShAu3mgc27Q==} - '@types/estree@1.0.8': - resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + '@types/doctrine@0.0.9': + resolution: {integrity: sha512-eOIHzCUSH7SMfonMG1LsC2f8vxBFtho6NGBznK41R84YzPuvSBzrhEps33IsQiOW9+VL6NQ9DbjQJznk/S4uRA==} '@types/estree@1.0.9': resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + '@types/gensync@1.0.5': + resolution: {integrity: sha512-MbsRCT7mTikHwKZ0X+LVUTLRrZZRLipTuXEO9qOYO+zmjMVk81axyClMROf6uoPD9MRVu46bx8zoR0Ad9q3NAg==} + '@types/graceful-fs@4.1.9': resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==} - '@types/ini@4.1.1': - resolution: {integrity: sha512-MIyNUZipBTbyUNnhvuXJTY7B6qNI78meck9Jbv3wk0OgNwRyOOVEKDutAkOs1snB/tx0FafyR6/SN4Ps0hZPeg==} - '@types/istanbul-lib-coverage@2.0.6': resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==} @@ -3222,20 +3192,17 @@ packages: '@types/jscodeshift@17.3.0': resolution: {integrity: sha512-ogvGG8VQQqAQQ096uRh+d6tBHrYuZjsumHirKtvBa5qEyTMN3IQJ7apo+sw9lxaB/iKWIhbbLlF3zmAWk9XQIg==} - '@types/node@12.20.55': - resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==} + '@types/jsesc@2.5.1': + resolution: {integrity: sha512-9VN+6yxLOPLOav+7PwjZbxiID2bVaeq0ED4qSQmdQTdjnXJSaCVKTR58t15oqH1H5t8Ng2ZX1SabJVoN9Q34bw==} '@types/node@18.19.130': resolution: {integrity: sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==} - '@types/node@25.7.0': - resolution: {integrity: sha512-z+pdZyxE+RTQE9AcboAZCb4otwcrvgHD+GlBpPgn0emDVt0ohrTMhAwlr2Wd9nZ+nihhYFxO2pThz3C5qSu2Eg==} - - '@types/node@25.9.3': - resolution: {integrity: sha512-603BddQMv3pUcr4U2dhujk83N2tTDVr/34wII2B6bJy6g+8WD6yUb11jszNs0gdi4PesVWl7ABt8nYMVpnLUcg==} + '@types/node@26.1.2': + resolution: {integrity: sha512-Vu4a5UFA9rIIFJ7rB/Vaafh9lrCQszopTCx6KjFboXTGQbPNasehVR5TEiithSDGyd1DEiUByggTZsg8jukeIg==} - '@types/nodemailer@8.0.0': - resolution: {integrity: sha512-fyf8jWULsCo0d0BuoQ75i6IeoHs47qcqxWc7yUdUcV0pOZGjUTTOvwdG1PRXUDqN/8A64yQdQdnA2pZgcdi+cA==} + '@types/nodemailer@8.0.1': + resolution: {integrity: sha512-PxpaInm8V1JQDd4j0ds5HfvWQk8JupS1C0Picb96QJsrrRDjBH+DlK7L4ZdNSqNULhiZRQHc40nLVShaGxXAMw==} '@types/pg-cursor@2.7.2': resolution: {integrity: sha512-m3xT8bVFCvx98LuzbvXyuCdT/Hjdd/v8ml4jL4K1QF70Y8clOfCFdgoaEB1FWdcSwcpoFYZTJQaMD9/GQ27efQ==} @@ -3248,8 +3215,8 @@ packages: peerDependencies: '@types/react': ^19.2.0 - '@types/react@19.2.14': - resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==} + '@types/react@19.2.17': + resolution: {integrity: sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==} '@types/readable-stream@4.0.23': resolution: {integrity: sha512-wwXrtQvbMHxCbBgjHaMGEmImFTQxxpfMOR/ZoQnXxB1woqkUbdLGFDgauo00Py9IudiaqSeiBiulSV9i6XIPig==} @@ -3287,130 +3254,175 @@ packages: '@types/yargs@17.0.35': resolution: {integrity: sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==} - '@typescript-eslint/project-service@8.54.0': - resolution: {integrity: sha512-YPf+rvJ1s7MyiWM4uTRhE4DvBXrEV+d8oC3P9Y2eT7S+HBS0clybdMIPnhiATi9vZOYDc7OQ1L/i6ga6NFYK/g==} + '@typescript-eslint/project-service@8.65.0': + resolution: {integrity: sha512-SxnPhbTsGahizDgbu7oqFH/xVtzIqMd/s+WtnSxNxJZJpLbdT5IPdzg8EZxO3+PoKahXmwJLeNQOpKJb3/bi7Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - typescript: '>=4.8.4 <6.0.0' + typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/tsconfig-utils@8.54.0': - resolution: {integrity: sha512-dRgOyT2hPk/JwxNMZDsIXDgyl9axdJI3ogZ2XWhBPsnZUv+hPesa5iuhdYt2gzwA9t8RE5ytOJ6xB0moV0Ujvw==} + '@typescript-eslint/tsconfig-utils@8.65.0': + resolution: {integrity: sha512-j6GzGqCiRdA7Qhur2VVmKZAkBLfnHFQfx4TaJGL9RMveZqCo48jSHHO0DTgizEnGhtWnqmbtCUSrqSkdiY/0Hg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - typescript: '>=4.8.4 <6.0.0' + typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/types@8.54.0': - resolution: {integrity: sha512-PDUI9R1BVjqu7AUDsRBbKMtwmjWcn4J3le+5LpcFgWULN3LvHC5rkc9gCVxbrsrGmO1jfPybN5s6h4Jy+OnkAA==} + '@typescript-eslint/types@8.65.0': + resolution: {integrity: sha512-JSSwWNy+H0E/01jJEM+hrX6N0OFDzFzeIhHFSAS01tlVaevpG8cFyYRPhS5yjGOvBUx3sqQHVMjCL1CAZZMxBg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.54.0': - resolution: {integrity: sha512-BUwcskRaPvTk6fzVWgDPdUndLjB87KYDrN5EYGetnktoeAvPtO4ONHlAZDnj5VFnUANg0Sjm7j4usBlnoVMHwA==} + '@typescript-eslint/typescript-estree@8.65.0': + resolution: {integrity: sha512-JboAE2swaYt4tb1fHhHTABE2K+OLy09XfcTbhnk4Pw96f9dd2e9iYsJ28gBggHlo5z5x1rkyWvcPoTuNTd4oGg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - typescript: '>=4.8.4 <6.0.0' + typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/visitor-keys@8.54.0': - resolution: {integrity: sha512-VFlhGSl4opC0bprJiItPQ1RfUhGDIBokcPwaFH4yiBCaNPeld/9VeXbiPO1cLyorQi1G1vL+ecBk1x8o1axORA==} + '@typescript-eslint/visitor-keys@8.65.0': + resolution: {integrity: sha512-8C71BQkGjiMmXtop7pHVJu1l2NNShFdkCyD6a2ezzs5vU/L3LRtb69EtcteFwz0mYMPzIgOw0n6OV4VBUWZd7A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript/native-preview-darwin-arm64@7.0.0-dev.20260512.1': - resolution: {integrity: sha512-l9AJi/TIVMPx5R1c7fxZCSA7eUaHeA0C9Mxdxx/oQJo1K/GtbI3mzYe/SiKNltko1KSdKUmWVhPwxTOS289REg==} + '@typescript/typescript-aix-ppc64@7.0.2': + resolution: {integrity: sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==} + engines: {node: '>=16.20.0'} + cpu: [ppc64] + os: [aix] + + '@typescript/typescript-darwin-arm64@7.0.2': + resolution: {integrity: sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA==} engines: {node: '>=16.20.0'} cpu: [arm64] os: [darwin] - '@typescript/native-preview-darwin-x64@7.0.0-dev.20260512.1': - resolution: {integrity: sha512-oABZLQrfB8JN2Ct2CiLK5PyE28Em3sIJlZsAMD45/A2ymtIaa5826dwv8vapE5Wjp54ao0LXxCSuKFm1A8zzCQ==} + '@typescript/typescript-darwin-x64@7.0.2': + resolution: {integrity: sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA==} engines: {node: '>=16.20.0'} cpu: [x64] os: [darwin] - '@typescript/native-preview-linux-arm64@7.0.0-dev.20260512.1': - resolution: {integrity: sha512-xvbwzpTe+5N6bnBI/t9n4zsGzXxz3V6rVbvDUoJmRLfav5fz+ck0QDkGQGUPrQEEIp0KEzQvx7c+AEZnzdvTQA==} + '@typescript/typescript-freebsd-arm64@7.0.2': + resolution: {integrity: sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [freebsd] + + '@typescript/typescript-freebsd-x64@7.0.2': + resolution: {integrity: sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [freebsd] + + '@typescript/typescript-linux-arm64@7.0.2': + resolution: {integrity: sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ==} engines: {node: '>=16.20.0'} cpu: [arm64] os: [linux] - '@typescript/native-preview-linux-arm@7.0.0-dev.20260512.1': - resolution: {integrity: sha512-0Hs1Gqa/t9cthoPdqHud1pFGUr9DgJivBTjwquTUh8jt/6PI2bQxoMNZLiN/bhqeDFDTzdxoMBfCaytsTMcXqw==} + '@typescript/typescript-linux-arm@7.0.2': + resolution: {integrity: sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ==} engines: {node: '>=16.20.0'} cpu: [arm] os: [linux] - '@typescript/native-preview-linux-x64@7.0.0-dev.20260512.1': - resolution: {integrity: sha512-qr5h6FPo74bN/U+EwRuayBhUbxaji8xzFbIbhMOA2oYSc/qozp5ia2g1+9xGw67MXxPPw/IPT+UGvrNK7K1NeQ==} + '@typescript/typescript-linux-loong64@7.0.2': + resolution: {integrity: sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ==} + engines: {node: '>=16.20.0'} + cpu: [loong64] + os: [linux] + + '@typescript/typescript-linux-mips64el@7.0.2': + resolution: {integrity: sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA==} + engines: {node: '>=16.20.0'} + cpu: [mips64el] + os: [linux] + + '@typescript/typescript-linux-ppc64@7.0.2': + resolution: {integrity: sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA==} + engines: {node: '>=16.20.0'} + cpu: [ppc64] + os: [linux] + + '@typescript/typescript-linux-riscv64@7.0.2': + resolution: {integrity: sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ==} + engines: {node: '>=16.20.0'} + cpu: [riscv64] + os: [linux] + + '@typescript/typescript-linux-s390x@7.0.2': + resolution: {integrity: sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw==} + engines: {node: '>=16.20.0'} + cpu: [s390x] + os: [linux] + + '@typescript/typescript-linux-x64@7.0.2': + resolution: {integrity: sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A==} engines: {node: '>=16.20.0'} cpu: [x64] os: [linux] - '@typescript/native-preview-win32-arm64@7.0.0-dev.20260512.1': - resolution: {integrity: sha512-meNWxhNEfaqos2U0JXvfxWvy4JWrKE9fZepCndDZi+t04X+AIiLYp5s6crWnKP67nzVAzMNgTAc8mu8CnGM+/A==} + '@typescript/typescript-netbsd-arm64@7.0.2': + resolution: {integrity: sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA==} engines: {node: '>=16.20.0'} cpu: [arm64] - os: [win32] + os: [netbsd] - '@typescript/native-preview-win32-x64@7.0.0-dev.20260512.1': - resolution: {integrity: sha512-Hp6vBnxJSKEEAVWgIoWMmfqkZXCdkhm6XTivrwgRzBwWfiTVe2ZyZ7byWegIKeNnBbffg/K2KvoM8JAHl059GQ==} + '@typescript/typescript-netbsd-x64@7.0.2': + resolution: {integrity: sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA==} engines: {node: '>=16.20.0'} cpu: [x64] - os: [win32] + os: [netbsd] - '@typescript/native-preview@7.0.0-dev.20260512.1': - resolution: {integrity: sha512-KIzYPGuxZnyiiYkYrozDT94Af2nwbdLXoY1cgGY66RRa9HSEw13RH9WHg8wA8fZhT4wYzF5uF7WY3hz0QhaxGg==} + '@typescript/typescript-openbsd-arm64@7.0.2': + resolution: {integrity: sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ==} engines: {node: '>=16.20.0'} - hasBin: true - - '@typespec/ts-http-runtime@0.3.5': - resolution: {integrity: sha512-yURCknZhvywvQItHMMmFSo+fq5arCUIyz/CVk7jD89MSai7dkaX8ufjCWp3NttLojoTVbcE72ri+be/TnEbMHw==} - engines: {node: '>=20.0.0'} + cpu: [arm64] + os: [openbsd] - '@vitest/browser@4.1.4': - resolution: {integrity: sha512-TrNaY/yVOwxtrxNsDUC/wQ56xSwplpytTeRAqF/197xV/ZddxxulBsxR6TrhVMyniJmp9in8d5u0AcDaNRY30w==} - peerDependencies: - vitest: 4.1.4 + '@typescript/typescript-openbsd-x64@7.0.2': + resolution: {integrity: sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [openbsd] - '@vitest/browser@4.1.6': - resolution: {integrity: sha512-ynsspTubXGSpa58JFJ24xIQt4z4A25epSbugEyaTmmrV1//Wec9EgE/LtoaC6yxUrXi5P7erGHRrkdZIHaVQuA==} - peerDependencies: - vitest: 4.1.6 + '@typescript/typescript-sunos-x64@7.0.2': + resolution: {integrity: sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [sunos] - '@vitest/coverage-v8@4.1.4': - resolution: {integrity: sha512-x7FptB5oDruxNPDNY2+S8tCh0pcq7ymCe1gTHcsp733jYjrJl8V1gMUlVysuCD9Kz46Xz9t1akkv08dPcYDs1w==} - peerDependencies: - '@vitest/browser': 4.1.4 - vitest: 4.1.4 - peerDependenciesMeta: - '@vitest/browser': - optional: true + '@typescript/typescript-win32-arm64@7.0.2': + resolution: {integrity: sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [win32] - '@vitest/coverage-v8@4.1.6': - resolution: {integrity: sha512-36l628fQ/9a/8ihy97eOtEnvWQEdqULQOJtcaxtoNq0G1w3Mxd4szSahOaMM9/NGyZ+hyKcMtIW/WIxq0XQViQ==} - peerDependencies: - '@vitest/browser': 4.1.6 - vitest: 4.1.6 - peerDependenciesMeta: - '@vitest/browser': - optional: true + '@typescript/typescript-win32-x64@7.0.2': + resolution: {integrity: sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [win32] - '@vitest/expect@4.1.4': - resolution: {integrity: sha512-iPBpra+VDuXmBFI3FMKHSFXp3Gx5HfmSCE8X67Dn+bwephCnQCaB7qWK2ldHa+8ncN8hJU8VTMcxjPpyMkUjww==} + '@typespec/ts-http-runtime@0.3.5': + resolution: {integrity: sha512-yURCknZhvywvQItHMMmFSo+fq5arCUIyz/CVk7jD89MSai7dkaX8ufjCWp3NttLojoTVbcE72ri+be/TnEbMHw==} + engines: {node: '>=20.0.0'} - '@vitest/expect@4.1.6': - resolution: {integrity: sha512-7EHDquPthALSV0jhhjgEW8FXaviMx7rSqu8W6oqCoAuOhKov814P99QDV1pxMA3QPv21YudvJngIhjrNI4opLg==} + '@vitest/browser@4.1.10': + resolution: {integrity: sha512-UDwuWGwXj646CBx/bQHOaJSX7np0I8JL/UKQYa1e4QrVHH8VdWtx8eaOuf8sy0ShwDgR6NjJAsp5eF6vjF6qng==} + peerDependencies: + vitest: 4.1.10 - '@vitest/mocker@4.1.4': - resolution: {integrity: sha512-R9HTZBhW6yCSGbGQnDnH3QHfJxokKN4KB+Yvk9Q1le7eQNYwiCyKxmLmurSpFy6BzJanSLuEUDrD+j97Q+ZLPg==} + '@vitest/coverage-v8@4.1.10': + resolution: {integrity: sha512-IM49HmthevbgAO4anp1hwtoT9wYe59w0LR00gr+eagHE+ZJ5lK4sLPeO0ubgoJcwLk6dehU3R24N+FbEEKDc8g==} peerDependencies: - msw: ^2.4.9 - vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + '@vitest/browser': 4.1.10 + vitest: 4.1.10 peerDependenciesMeta: - msw: - optional: true - vite: + '@vitest/browser': optional: true - '@vitest/mocker@4.1.6': - resolution: {integrity: sha512-MCFc63czMjEInOlcY2cpQCvCN+KgbAn+60xu9cMgP4sKaLC5JNAKw7JH8QdAnoAC88hW1IiSNZ+GgVXlN1UcMQ==} + '@vitest/expect@4.1.10': + resolution: {integrity: sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==} + + '@vitest/mocker@4.1.10': + resolution: {integrity: sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow==} peerDependencies: msw: ^2.4.9 vite: ^6.0.0 || ^7.0.0 || ^8.0.0 @@ -3420,69 +3432,69 @@ packages: vite: optional: true - '@vitest/pretty-format@3.2.4': - resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==} + '@vitest/pretty-format@4.1.10': + resolution: {integrity: sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q==} - '@vitest/pretty-format@4.1.4': - resolution: {integrity: sha512-ddmDHU0gjEUyEVLxtZa7xamrpIefdEETu3nZjWtHeZX4QxqJ7tRxSteHVXJOcr8jhiLoGAhkK4WJ3WqBpjx42A==} + '@vitest/runner@4.1.10': + resolution: {integrity: sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg==} - '@vitest/pretty-format@4.1.6': - resolution: {integrity: sha512-h5SxD/IzNhZYnrSZRsUZQIC+vD0GY8cUvq0iwsmkFKixRCKLLWqCXa/FIQ4S1R+sI+PGoojkHsdNrbZiM9Qpgw==} + '@vitest/snapshot@4.1.10': + resolution: {integrity: sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw==} - '@vitest/runner@4.1.4': - resolution: {integrity: sha512-xTp7VZ5aXP5ZJrn15UtJUWlx6qXLnGtF6jNxHepdPHpMfz/aVPx+htHtgcAL2mDXJgKhpoo2e9/hVJsIeFbytQ==} + '@vitest/spy@4.1.10': + resolution: {integrity: sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw==} - '@vitest/snapshot@4.1.4': - resolution: {integrity: sha512-MCjCFgaS8aZz+m5nTcEcgk/xhWv0rEH4Yl53PPlMXOZ1/Ka2VcZU6CJ+MgYCZbcJvzGhQRjVrGQNZqkGPttIKw==} + '@vitest/utils@4.1.10': + resolution: {integrity: sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==} - '@vitest/spy@4.1.4': - resolution: {integrity: sha512-XxNdAsKW7C+FLydqFJLb5KhJtl3PGCMmYwFRfhvIgxJvLSXhhVI1zM8f1qD3Zg7RCjTSzDVyct6sghs9UEgBEQ==} - - '@vitest/spy@4.1.6': - resolution: {integrity: sha512-JFKxMx6udhwKh/Ldo270e17QX710vgunMkuPAvXjHSvC6oqLWAHhVhjg/I71q0u0CBSErIODV1Kjv0FQNSWjdg==} + '@vitest/web-worker@4.1.10': + resolution: {integrity: sha512-b9LiAHvkBMh0YKSDYSdw4+L1F6cUShNdUxJaIKdvZQBgeuDT9ws1nE9k29i437j6vt9U7V94czxcbBm+JwpWvA==} + peerDependencies: + vitest: 4.1.10 - '@vitest/utils@3.2.4': - resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==} + '@vue/compiler-core@3.5.39': + resolution: {integrity: sha512-16KBTEXAJCpDr0mwlw+AZyhu8iyC7R3S2vBwsI7QnWJU6X3WKc9VKeNEZpiMdZ569qWhz9574L3vV55qRL0Vtw==} - '@vitest/utils@4.1.4': - resolution: {integrity: sha512-13QMT+eysM5uVGa1rG4kegGYNp6cnQcsTc67ELFbhNLQO+vgsygtYJx2khvdt4gVQqSSpC/KT5FZZxUpP3Oatw==} + '@vue/compiler-core@3.5.40': + resolution: {integrity: sha512-39E8IgOhTbVDnoJFMKc2DvYnypcZwUqgUhQkccva/0m6FUwtIKSGV7n1hpVmYcFaoRAwf9pBcwnKlCEsN63ZEQ==} - '@vitest/utils@4.1.6': - resolution: {integrity: sha512-FxIY+U81R3LGKCxaHHFRQ5+g6/iRgGLmeHWdp2Amj4ljQRrEIWHmZyDfDYBRZlpyqA7qKxtS9DD1dhk8RnRIVQ==} + '@vue/compiler-dom@3.5.39': + resolution: {integrity: sha512-oQPigALqYbNxTNPvNgSOe+czwVExfbVF02lz8jP0S3AXJiu3jxYDygNUiqSep4ezzW8XgnubqH63My2A7JR/vg==} - '@vitest/web-worker@4.1.6': - resolution: {integrity: sha512-GBr1Sv4erK380EzgFJ5/RFVMMbUsHv43jFVDSWS71ACmzVDAjpiw28/yZxyIz64iOiTprOo77L+KgilRf7iROw==} - peerDependencies: - vitest: 4.1.6 + '@vue/compiler-dom@3.5.40': + resolution: {integrity: sha512-pwkx4vqlqOspFstrcmzwkKLePVMD3PT65imRzLhanU2V1Fj4K13g6OXjanOyzw3aTAuRk84BOmY8f3rEHqPaVA==} - '@vue/compiler-core@3.5.34': - resolution: {integrity: sha512-s9cLyK5mLcvZ4Agva5QgRsQyLKvts9WbU9DB6NqiZkkGEdwmcEiylj5Jbwkp680drF/NNCV8OlAJSe+yMLxaJw==} + '@vue/compiler-sfc@3.5.39': + resolution: {integrity: sha512-d0ki86iOyN8LoZPBmk5SJWNwHP19CnDDCfuo//+2WJa2g5Ke0Jay983PIBIcSSzldC68I8DrD5GrHV3OSDfodg==} - '@vue/compiler-dom@3.5.34': - resolution: {integrity: sha512-EbF/T++k0e2MMZlJsBhzK8Sgwt0HcIPOhzn1CTB/lv6sQcyk+OWf8YeiLxZp3ro7MbbLcAfAJ6sEvjFWuNgUCw==} + '@vue/compiler-sfc@3.5.40': + resolution: {integrity: sha512-gIf497P4kpuALcvs5n3AEg1Vdn0pSY4XbjASIfHNYF1/MP3T2Mf2STERTubysBxCRxzJGJYtF/O7vwJrxFB3Vw==} - '@vue/compiler-sfc@3.5.34': - resolution: {integrity: sha512-D/ihr6uZeIt6r+pVZf46RWT1fAsLFMbUP7k8G1VkiiWexriED9GrX3echHd4Abbt17zjlfiFJ8z7a3BxZOPNjg==} + '@vue/compiler-ssr@3.5.39': + resolution: {integrity: sha512-Ce7/wvwMHai74bdszfXExdazFigYnlF9zgCmEQUcM1j0fOymlouZ7XilTYNo8oUjhlnjYOZbGrcYKuqjz89Ucw==} - '@vue/compiler-ssr@3.5.34': - resolution: {integrity: sha512-cDtTHKibkThKGHH1SP+WdccquNRYQDFH6rRjQCqT9G2ltFAfoR5pUftpab/z+aM5mW9HLLVQW7hfKKQe/1GBeQ==} + '@vue/compiler-ssr@3.5.40': + resolution: {integrity: sha512-rrE5xiXG663+vHCHa3J9p2z5OcBRjXmoqenprJxAFQxg5pSshzeBiCE6pu46axapRJ2Adk0YDA2BRZVjiHXnhg==} - '@vue/reactivity@3.5.34': - resolution: {integrity: sha512-y9XDjCEuBp+98k+UL5dbYkh57AHU4o6cxZedOPXw3bmrZZYLQsVHguGurq7hVrPCSrQtrnz1f9dssyFr+dMXfQ==} + '@vue/reactivity@3.5.39': + resolution: {integrity: sha512-TpsuBJ9gGlZa5d23XcM2y8EXanz9dZeVDQBXRwzy46ItgvM+rWpzs+UVM0wcRLxGvcav0HE5jz2gNL53xlRAog==} - '@vue/runtime-core@3.5.34': - resolution: {integrity: sha512-mKeBYvu8tcMSLhypAHBmriUFfWXKTCF/23Z4jiCoYK3UtWepkliViNLuR90V9XOyD62mUxs9p1jsrpK3CCGIzw==} + '@vue/runtime-core@3.5.39': + resolution: {integrity: sha512-9GLtNyRvPAUMbX+7ono0RC2j0guo2LXVi8LvcmAooImACUKm0oFf0jjwbX8/H0AE/t1nxhAkn8RSl9PMCzzxZw==} - '@vue/runtime-dom@3.5.34': - resolution: {integrity: sha512-e8kZzERmCwUnBRVsgSQlAfrfU2rGoy0FFKPBXSlfEjc/O3KfA7QP0t1/2ZylrbchjmIKB4dPTd07A6WPr0eOrg==} + '@vue/runtime-dom@3.5.39': + resolution: {integrity: sha512-7Y6aAGboKcXAZ3ECuUy7RrS5yy2r47dhTp2SKaJmYxjopImaVFaNa5Ne66NwGovsrxVAl5S5rwc7m22UG7Lmww==} - '@vue/server-renderer@3.5.34': - resolution: {integrity: sha512-nHxmJoTrKsmrkbILRhkC9gY1G3moZbJTqCzDd7DOOzG5KH9oeJ0Unqrff5f9v0pW//jES05ZkJcNtfE8JjOIew==} + '@vue/server-renderer@3.5.39': + resolution: {integrity: sha512-yZSakiAGw85rZfG7UM8akMnIF+FmeiNk47uvHf2nVBBSe+dIKUhZuZq9+XgJhbV3nS5Z4ALH23/MpXofW+mbcw==} peerDependencies: - vue: 3.5.34 + vue: 3.5.39 + + '@vue/shared@3.5.39': + resolution: {integrity: sha512-l1rrBtBfTnmxvtsvdQDXltUUy8S1Y+ZaqdfUzmAnJkTd8Z8rv5v/ytW+TKiqEOWyHPoqtPlNFSs0lhRmYVSHVA==} - '@vue/shared@3.5.34': - resolution: {integrity: sha512-24uqU4OIiX29ryC3MeWid/Xf2fa2EFRUVLb77nRhk+UrTVrh/XiGtFAFmJBAtBRbjwNdsPRP+jj/OL27Eg1NDA==} + '@vue/shared@3.5.40': + resolution: {integrity: sha512-WxnBtruIqOoV3rA4jeKDWzrYI5h7Cp4+pjwDi8kWGHz+IslhiN+wguLVVhtv2l8VoU02rzDCVfDjgCl1lNpZVg==} abort-controller@3.0.0: resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} @@ -3492,11 +3504,6 @@ packages: resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} engines: {node: '>= 0.6'} - acorn@8.16.0: - resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} - engines: {node: '>=0.4.0'} - hasBin: true - acorn@8.17.0: resolution: {integrity: sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==} engines: {node: '>=0.4.0'} @@ -3506,6 +3513,14 @@ packages: resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} engines: {node: '>= 14'} + ajv-draft-04@1.0.0: + resolution: {integrity: sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==} + peerDependencies: + ajv: ^8.5.0 + peerDependenciesMeta: + ajv: + optional: true + ajv@8.20.0: resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==} @@ -3567,16 +3582,6 @@ packages: resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} engines: {node: '>= 0.4'} - arkregex@0.0.5: - resolution: {integrity: sha512-ncYjBdLlh5/QnVsAA8De16Tc9EqmYM7y/WU9j+236KcyYNUXogpz3sC4ATIZYzzLxwI+0sEOaQLEmLmRleaEXw==} - - arktype@2.2.0: - resolution: {integrity: sha512-t54MZ7ti5BhOEvzEkgKnWvqj+UbDfWig+DHr5I34xatymPusKLS0lQpNJd8M6DzmIto2QGszHfNKoFIT8tMCZQ==} - - array-union@2.1.0: - resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} - engines: {node: '>=8'} - asap@2.0.6: resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==} @@ -3587,8 +3592,8 @@ packages: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} - ast-module-types@6.0.1: - resolution: {integrity: sha512-WHw67kLXYbZuHTmcdbIrVArCq5wxo6NEuj3hiYAWr8mwJeC+C2mMCIBIWCiDoCye/OF/xelc+teJ1ERoWmnEIA==} + ast-module-types@6.0.2: + resolution: {integrity: sha512-6KuK/7nZ/2Qh7sGuVEiwxjCxzTY2Pdb5mTo5z1e6/J8BA0tvjR7G8vQJKrQMTqwmnA3UPEyKIFX4YUS1DO1Hvw==} engines: {node: '>=18'} ast-types@0.14.2: @@ -3599,11 +3604,8 @@ packages: resolution: {integrity: sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==} engines: {node: '>=4'} - ast-v8-to-istanbul@1.0.0: - resolution: {integrity: sha512-1fSfIwuDICFA4LKkCzRPO7F0hzFf0B7+Xqrl27ynQaa+Rh0e1Es0v6kWHPott3lU10AyAr7oKHa65OppjLn3Rg==} - - ast-v8-to-istanbul@1.0.4: - resolution: {integrity: sha512-0bC0/4bTSrnwdhU3IsZDwEdojvuPrSg59OYZfKsLRtJZ0u8VBx9DebfqqG8bRdCC0I7vjgxmPi41P0lpkhJHtA==} + ast-v8-to-istanbul@1.0.5: + resolution: {integrity: sha512-UPAgKJFSEGMWSDr3LX4tqnAb4f7KGT8O40Tyx8wbYmmZ/yn58lNCm8h3svs3eXgiGd5AXxz8NDOvXWvicq+rJA==} async-lock@1.4.1: resolution: {integrity: sha512-Az2ZTpuytrtqENulXwO3GGv1Bztugx6TT37NIo7imr/Qo0gsYiGtSdBa2B6fsXhTpVZDNfu1Qn3pk531e3q+nQ==} @@ -3618,8 +3620,8 @@ packages: resolution: {integrity: sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==} engines: {node: '>= 6.0.0'} - b4a@1.8.0: - resolution: {integrity: sha512-qRuSmNSkGQaHwNbM7J78Wwy+ghLEYF1zNrSeMxj4Kgw6y33O3mXcQ6Ie9fRvfU/YnxWkOchPXbaLb73TkIsfdg==} + b4a@1.8.1: + resolution: {integrity: sha512-aiqre1Nr0B/6DgE2N5vwTc+2/oQZ4Wh1t4NznYY4E00y8LCt6NqdRv81so00oo27D8MVKTpUa/MwUUtBLXCoDw==} peerDependencies: react-native-b4a: '*' peerDependenciesMeta: @@ -3667,16 +3669,16 @@ packages: resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} engines: {node: 18 || 20 || >=22} - bare-events@2.8.2: - resolution: {integrity: sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==} + bare-events@2.9.1: + resolution: {integrity: sha512-Z0oHEHAFDZkffN8Qc39zNZjQlMDkPJRyyyZieU1VH7u8c5S+qHZ2S8ixdKIAxEjfHO7FJxXmJWgteOghVanIsg==} peerDependencies: bare-abort-controller: '*' peerDependenciesMeta: bare-abort-controller: optional: true - bare-fs@4.7.0: - resolution: {integrity: sha512-xzqKsCFxAek9aezYhjJuJRXBIaYlg/0OGDTZp+T8eYmYMlm66cs6cYko02drIyjN2CBbi+I6L7YfXyqpqtKRXA==} + bare-fs@4.7.4: + resolution: {integrity: sha512-y1kC+ffIx/tPLdTE693uNjHfzTfr+ravR5tvWlMXe25nELbkqV400S71qHDwbkAQ1FVEZobB1NFRzFbCCcyBCQ==} engines: {bare: '>=1.16.0'} peerDependencies: bare-buffer: '*' @@ -3684,15 +3686,11 @@ packages: bare-buffer: optional: true - bare-os@3.8.7: - resolution: {integrity: sha512-G4Gr1UsGeEy2qtDTZwL7JFLo2wapUarz7iTMcYcMFdS89AIQuBoyjgXZz0Utv7uHs3xA9LckhVbeBi8lEQrC+w==} - engines: {bare: '>=1.14.0'} - - bare-path@3.0.0: - resolution: {integrity: sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==} + bare-path@3.1.1: + resolution: {integrity: sha512-JprUlveX3QjApC1cTpsUOiscADftCGVWkzitbHsRqv84hzYwYHw2mbluddsq5TvI8mH/8Ov1f4BiMAdcB0oYnQ==} - bare-stream@2.13.0: - resolution: {integrity: sha512-3zAJRZMDFGjdn+RVnNpF9kuELw+0Fl3lpndM4NcEOhb9zwtSo/deETfuIwMSE5BXanA0FrN1qVjffGwAg2Y7EA==} + bare-stream@2.13.3: + resolution: {integrity: sha512-Kc+brLqvEqGkjyfiwJmImAOqLZL7OsoLKuavx+hJjgVV3nLTOjloJyPMFxjUPerGGHrNH0fLU06jjykMLWrERQ==} peerDependencies: bare-abort-controller: '*' bare-buffer: '*' @@ -3705,73 +3703,45 @@ packages: bare-events: optional: true - bare-url@2.4.0: - resolution: {integrity: sha512-NSTU5WN+fy/L0DDenfE8SXQna4voXuW0FHM7wH8i3/q9khUSchfPbPezO4zSFMnDGIf9YE+mt/RWhZgNRKRIXA==} + bare-url@2.4.6: + resolution: {integrity: sha512-iQxPClE07hETVpbRoX7JXX3v/ZQViCxe/SYCxylRLzdEx1xJAufPptfiOqR8tqiCtmbtMDANKWszzjLu1PMAZQ==} base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - baseline-browser-mapping@2.10.35: - resolution: {integrity: sha512-honAfLBde0HAFLdNyBEfuuENkF6zR+ozxqxa/2zJKHBe1qzLqyTSeRKpdPEHAP03rlDGyQOPnCSxnVpVqQo9Mg==} + baseline-browser-mapping@2.11.5: + resolution: {integrity: sha512-xJo6a6YZnwZfnyGmQKWMbVOcii7XRibjOskRh+WJ9UHQoX16xrQrcIgAMQOzfvs8XiLMx6ih/fsLPF73iY2D1A==} engines: {node: '>=6.0.0'} hasBin: true - baseline-browser-mapping@2.9.19: - resolution: {integrity: sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==} - hasBin: true - bcrypt-pbkdf@1.0.2: resolution: {integrity: sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==} - better-path-resolve@1.0.0: - resolution: {integrity: sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g==} - engines: {node: '>=4'} - - better-sqlite3@12.10.0: - resolution: {integrity: sha512-CyzaZRQKyHkB2ZInfTTl2nvT33EbDpjkLEbE8/Zck3Ll6O0qqvuGdrJ45HgtH+HykRg88ITY3AdreBGN70aBSQ==} - engines: {node: 20.x || 22.x || 23.x || 24.x || 25.x || 26.x} - - better-sqlite3@12.9.0: - resolution: {integrity: sha512-wqUv4Gm3toFpHDQmaKD4QhZm3g1DjUBI0yzS4UBl6lElUmXFYdTQmmEDpAFa5o8FiFiymURypEnfVHzILKaxqQ==} - engines: {node: 20.x || 22.x || 23.x || 24.x || 25.x} - bidi-js@1.0.3: resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==} - binary-extensions@2.3.0: - resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} - engines: {node: '>=8'} - - bindings@1.5.0: - resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} - bl@4.1.0: resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} bl@6.1.6: resolution: {integrity: sha512-jLsPgN/YSvPUg9UX0Kd73CXpm2Psg9FxMeCSXnk3WBO3CMT10JMwijubhGfHCnFu6TPn1ei3b975dxv7K2pWVg==} - brace-expansion@1.1.12: - resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} + brace-expansion@1.1.16: + resolution: {integrity: sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==} - brace-expansion@2.1.0: - resolution: {integrity: sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==} + brace-expansion@2.1.3: + resolution: {integrity: sha512-DRdx5neNsG/QXbniLFWi2YmC/68oeOOmKz6zOjVk6ZS1ZLXgLIKqVEc6hWsmkjBbgii0SwaBTcJ5XKj5gzY/4A==} - brace-expansion@5.0.5: - resolution: {integrity: sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==} + brace-expansion@5.0.7: + resolution: {integrity: sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==} engines: {node: 18 || 20 || >=22} braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} - browserslist@4.28.1: - resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==} - engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} - hasBin: true - - browserslist@4.28.2: - resolution: {integrity: sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==} + browserslist@4.28.7: + resolution: {integrity: sha512-JxV13hNrFxqjOc8alRbq9dK1MM79NEXYpma2B2J4wAtpWS5zIEIKqWPGCl7N4o7Uc7B7itylh7SuDujATRyyTw==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true @@ -3788,6 +3758,10 @@ packages: buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + buffer-image-size@0.6.4: + resolution: {integrity: sha512-nEh+kZOPY1w+gcCMobZ6ETUp9WfibndnosbpwB1iJk/8Gt5ZF2bhS6+B6bPYz424KtwsR6Rflc3tCz1/ghX2dQ==} + engines: {node: '>=4.0'} + buffer@5.7.1: resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} @@ -3798,8 +3772,8 @@ packages: resolution: {integrity: sha512-lHblz4ahamxpTmnsk+MNTRWsjYKv965MwOrSJyeD588rR3Jcu7swE+0wN5F+PbL5cjgu/9ObkhfzEPuofEMwLA==} engines: {node: '>=10.0.0'} - bun-types@1.3.13: - resolution: {integrity: sha512-QXKeHLlOLqQX9LgYaHJfzdBaV21T63HhFJnvuRCcjZiaUDpbs5ED1MgxbMra71CsryN/1dAoXuJJJwIv/2drVA==} + bun-types@1.3.14: + resolution: {integrity: sha512-4N0ig0fEomHt5R0KCFWjovxow98rIoRwKolrYdCcknNwMekCXRnWEUvgu5soYV8QXtVsrUD8B95MBOZGPvr6KQ==} bundle-name@4.1.0: resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==} @@ -3809,6 +3783,10 @@ packages: resolution: {integrity: sha512-s6webAy+R4SR8XVuJWt2V2rGvhnrhxN+9S15GNuTK3wKPOXFF6RNc+8ug2XhH+2s4f+uudG4kUVYmYOQWL2g0Q==} engines: {node: '>=0.10.0'} + cac@7.0.0: + resolution: {integrity: sha512-tixWYgm5ZoOD+3g6UTea91eow5z6AAHaho3g0V9CNSNb45gM8SmflpAc+GRd1InC4AqN/07Unrgp56Y94N9hJQ==} + engines: {node: '>=20.19.0'} + call-me-maybe@1.0.2: resolution: {integrity: sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==} @@ -3820,11 +3798,8 @@ packages: resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} engines: {node: '>=10'} - caniuse-lite@1.0.30001766: - resolution: {integrity: sha512-4C0lfJ0/YPjJQHagaE9x2Elb69CIqEPZeG0anQt9SIvIoOH4a4uaRl73IavyO+0qZh6MDLH//DrXThEYKHkmYA==} - - caniuse-lite@1.0.30001799: - resolution: {integrity: sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw==} + caniuse-lite@1.0.30001806: + resolution: {integrity: sha512-72Cuvd95zbSYPKq6Fhg8eDJRlzgWDf7/mtoZv6Qe/DYNCEBdNxoA3+rZAU2ZhGCpZlns3EssFavaZomckT5Uuw==} chai@6.2.2: resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} @@ -3834,15 +3809,16 @@ packages: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} + chalk@5.6.2: + resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + change-case@5.4.4: resolution: {integrity: sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==} - chardet@2.1.1: - resolution: {integrity: sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==} - - chokidar@3.6.0: - resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} - engines: {node: '>= 8.10.0'} + chokidar@5.0.0: + resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==} + engines: {node: '>= 20.19.0'} chownr@1.1.4: resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} @@ -3890,10 +3866,17 @@ packages: resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} engines: {node: '>=0.8'} + cluster-key-slot@1.1.0: + resolution: {integrity: sha512-2Nii8p3RwAPiFwsnZvukotvow2rIHM+yQ6ZcBXGHdniadkYGZYiGmkHJIbZPIV9nfv7m/U1IPMVVcAhoWFeklw==} + engines: {node: '>=0.10.0'} + cluster-key-slot@1.1.2: resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==} engines: {node: '>=0.10.0'} + code-block-writer@13.0.3: + resolution: {integrity: sha512-Oofo0pq3IKnsFtuHqSF7TqBfr71aeyZDVJ0HpmqB7FBM2qEigL0iPONSCZSO9pE9dZTAxANe5XHG9Uy0YMv8cg==} + color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} @@ -3908,13 +3891,13 @@ packages: resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==} engines: {node: '>=18'} + commander@14.0.3: + resolution: {integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==} + engines: {node: '>=20'} + commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} - commander@6.2.1: - resolution: {integrity: sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==} - engines: {node: '>= 6'} - commander@7.2.0: resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} engines: {node: '>= 10'} @@ -3984,8 +3967,8 @@ packages: resolution: {integrity: sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} - dataloader@1.4.0: - resolution: {integrity: sha512-68s5jYdlvasItOJnCuI2Q9s4q98g0pCyL3HrcKJu8KNugUl8ahgmZYg38ysLTgQjjXX3H8CJLkAvWrclWfcalw==} + dataloader@2.2.3: + resolution: {integrity: sha512-y2krtASINtPFS1rSDjacrFgn1dcUuoREVabwlOGOe4SdxenREqwjwjElAdwvbGM7kgZz9a3KVicWR7vcz8rnzA==} debug@2.6.9: resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} @@ -4007,10 +3990,6 @@ packages: decimal.js@10.6.0: resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} - decompress-response@6.0.0: - resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} - engines: {node: '>=10'} - deep-extend@0.6.0: resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} engines: {node: '>=4.0.0'} @@ -4042,8 +4021,8 @@ packages: resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} engines: {node: '>= 0.8'} - dependency-tree@11.2.0: - resolution: {integrity: sha512-+C1H3mXhcvMCeu5i2Jpg9dc0N29TWTuT6vJD7mHLAfVmAbo9zW8NlkvQ1tYd3PDMab0IRQM0ccoyX68EZtx9xw==} + dependency-tree@11.5.0: + resolution: {integrity: sha512-K9zBwKDZrot3RkxizugpVSdImxULAg4Ycp3+ydy2r561k96oiiw6nfsOR15fwNDQ5BF2UXe+2JFM/H5Xz4MGQg==} engines: {node: '>=18'} hasBin: true @@ -4055,10 +4034,6 @@ packages: resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} - detect-indent@6.1.0: - resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==} - engines: {node: '>=8'} - detect-libc@2.0.2: resolution: {integrity: sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==} engines: {node: '>=8'} @@ -4067,57 +4042,53 @@ packages: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} - detective-amd@6.0.1: - resolution: {integrity: sha512-TtyZ3OhwUoEEIhTFoc1C9IyJIud3y+xYkSRjmvCt65+ycQuc3VcBrPRTMWoO/AnuCyOB8T5gky+xf7Igxtjd3g==} + detective-amd@6.1.0: + resolution: {integrity: sha512-fmI6LGMvotqd49QaA3ZYw+q0aGp2yXmMjzIuY6fH9j9YFIXY/73yDhMwhX9cPbhWd+AH06NH1Di/LKOuCH0Ubg==} engines: {node: '>=18'} hasBin: true - detective-cjs@6.0.1: - resolution: {integrity: sha512-tLTQsWvd2WMcmn/60T2inEJNhJoi7a//PQ7DwRKEj1yEeiQs4mrONgsUtEJKnZmrGWBBmE0kJ1vqOG/NAxwaJw==} + detective-cjs@6.1.1: + resolution: {integrity: sha512-pSh7mkCKEtLlmANqLu3KDFS3NV8Hx41jy/JF1/gAWOgU+Uo5QTkeI1tWNP4dWGo4L0E9j18Ez9EPsTleautKqA==} engines: {node: '>=18'} - detective-es6@5.0.1: - resolution: {integrity: sha512-XusTPuewnSUdoxRSx8OOI6xIA/uld/wMQwYsouvFN2LAg7HgP06NF1lHRV3x6BZxyL2Kkoih4ewcq8hcbGtwew==} + detective-es6@5.0.2: + resolution: {integrity: sha512-+qHHGYhjupiVs4rnIpI9nZ5B130A4AmE35ZX1w33hb46vcZ7T3jfDbvmPw0FhWtMHn5BS5HHu7ZtnZ53bMcXZA==} engines: {node: '>=18'} - detective-postcss@7.0.1: - resolution: {integrity: sha512-bEOVpHU9picRZux5XnwGsmCN4+8oZo7vSW0O0/Enq/TO5R2pIAP2279NsszpJR7ocnQt4WXU0+nnh/0JuK4KHQ==} - engines: {node: ^14.0.0 || >=16.0.0} + detective-postcss@8.0.4: + resolution: {integrity: sha512-DZ7M/hWPZyr17ZUdoQ+TVXaPj70mYr4XXrAE+GeJbca44haCvZgb191L/jLJmFYewhxRJuBd4lUtNSu986TXag==} + engines: {node: '>=18'} peerDependencies: postcss: ^8.4.47 - detective-sass@6.0.1: - resolution: {integrity: sha512-jSGPO8QDy7K7pztUmGC6aiHkexBQT4GIH+mBAL9ZyBmnUIOFbkfZnO8wPRRJFP/QP83irObgsZHCoDHZ173tRw==} + detective-sass@6.0.2: + resolution: {integrity: sha512-i3xpXHDKS0qI2aFW4asQ7fqlPK00ndOVZELvQapFJCaF0VxYmsNWtd0AmvXbTLMk7bfO5VdIeorhY9KfmHVoVA==} engines: {node: '>=18'} - detective-scss@5.0.1: - resolution: {integrity: sha512-MAyPYRgS6DCiS6n6AoSBJXLGVOydsr9huwXORUlJ37K3YLyiN0vYHpzs3AdJOgHobBfispokoqrEon9rbmKacg==} + detective-scss@5.0.2: + resolution: {integrity: sha512-9JOEMZ8pDh3ShXmftq7hoQqqJsClaGgxo1hghfCeFlmKf5TC/Twtwb0PAaK8dXwpg9Z0uCmEYSrCxO+kel2eEg==} engines: {node: '>=18'} detective-stylus@5.0.1: resolution: {integrity: sha512-Dgn0bUqdGbE3oZJ+WCKf8Dmu7VWLcmRJGc6RCzBgG31DLIyai9WAoEhYRgIHpt/BCRMrnXLbGWGPQuBUrnF0TA==} engines: {node: '>=18'} - detective-typescript@14.0.0: - resolution: {integrity: sha512-pgN43/80MmWVSEi5LUuiVvO/0a9ss5V7fwVfrJ4QzAQRd3cwqU1SfWGXJFcNKUqoD5cS+uIovhw5t/0rSeC5Mw==} + detective-typescript@14.1.2: + resolution: {integrity: sha512-bIeEn0eVi/JRsE1YizBR2ilnMlWRAIBJJ6kXCKNFxEEWhUcEY3R6I3KYIAy48ieURbD1hcb3Ebvl8AqeoPMSzg==} engines: {node: '>=18'} peerDependencies: - typescript: ^5.4.4 + typescript: ^5.4.4 || ^6.0.2 - detective-vue2@2.2.0: - resolution: {integrity: sha512-sVg/t6O2z1zna8a/UIV6xL5KUa2cMTQbdTIIvqNM0NIPswp52fe43Nwmbahzj3ww4D844u/vC2PYfiGLvD3zFA==} + detective-vue2@2.3.0: + resolution: {integrity: sha512-3gwbZPqVTm9sL9XdZsgEJ7x4x99O853VVZHapQAiEkGuMJMpFPjHDrecSgfqnS5JW3FJfYXesLZGvUOibjn49g==} engines: {node: '>=18'} peerDependencies: - typescript: ^5.4.4 + typescript: ^5.4.4 || ^6.0.2 diacritics-map@0.1.0: resolution: {integrity: sha512-3omnDTYrGigU0i4cJjvaKwD52B8aoqyX/NEIkukFFkogBemsIbhSa1O414fpTp5nuszJG6lvQ5vBvDVNCbSsaQ==} engines: {node: '>=0.8.0'} - dir-glob@3.0.1: - resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} - engines: {node: '>=8'} - docker-compose@1.4.2: resolution: {integrity: sha512-rPHigTKGaEHpkUmfd69QgaOp+Os5vGJwG/Ry8lcr8W/382AmI+z/D7qoa9BybKIkqNppaIbs8RYeHSevdQjWww==} engines: {node: '>= 6.0.0'} @@ -4126,9 +4097,9 @@ packages: resolution: {integrity: sha512-XJgGhoR/CLpqshm4d3L7rzH6t8NgDFUIIpztYlLHIApeJjMZKYJMz2zxPsYxnejq5h3ELYSw/RBsi3t5h7gNTA==} engines: {node: '>= 8.0'} - dockerode@4.0.10: - resolution: {integrity: sha512-8L/P9JynLBiG7/coiA4FlQXegHltRqS0a+KqI44P1zgQh8QLHTg7FKOwhkBgSJwZTeHsq30WRoVFLuwkfK0YFg==} - engines: {node: '>= 8.0'} + dockerode@5.0.1: + resolution: {integrity: sha512-avsq/xk4YPIrn0CgleX5bjT9Y8IT1p9PxrNQ++RBQ2WEyFfHCTDsT9kmyxz+H/axnjAwg8wJWEIuPGOUuNupiA==} + engines: {node: '>= 14.17'} doctrine@3.0.0: resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} @@ -4140,12 +4111,8 @@ packages: dom-accessibility-api@0.6.3: resolution: {integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==} - dotenv@8.6.0: - resolution: {integrity: sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==} - engines: {node: '>=10'} - - dprint@0.54.0: - resolution: {integrity: sha512-sIy25poR2gRP/tWPTgP0MPeJoJcpv0xzYDcsboapvthbEt1Qw3Al252CA0xFyIh2cYEGGKyBJtKokryv4ERlJw==} + dprint@0.55.2: + resolution: {integrity: sha512-1d4D4SB9KiD2qFnBWbl3aoYjLvPVpZoth28yxTT00xJv2yXugdz0Xoyv7sGG0w0hzE6hQZMgd6B333GnySDpGQ==} hasBin: true eastasianwidth@0.2.0: @@ -4157,11 +4124,8 @@ packages: ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} - electron-to-chromium@1.5.283: - resolution: {integrity: sha512-3vifjt1HgrGW/h76UEeny+adYApveS9dH2h3p57JYzBSXJIKUJAvtmIytDKjcSCt9xHfrNCFJ7gts6vkhuq++w==} - - electron-to-chromium@1.5.371: - resolution: {integrity: sha512-e9htk9mAYL6AzmkEhSvVVw7IWGSBJ/Bqdn2eRyRLrj1g6sncN4WbFt5qnILYoCktktr45pyjIrOiRvBThQ808w==} + electron-to-chromium@1.5.397: + resolution: {integrity: sha512-khGTy9U9x02KEtsKM8vx5A62BsRmcOsIgDpWr1ImE32Ax8GxHGPHZf+Eu9H8zOOyHJnB0jTbseyTHbq2XCT8yw==} emoji-regex@10.6.0: resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} @@ -4172,6 +4136,10 @@ packages: emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + empathic@2.0.1: + resolution: {integrity: sha512-YGRs8knHhKHVShLkFET/rWAU8kmHbOV5LwN938RHI0pljAJ1Gf6SzXsSmRaEzcXTtOOmVqJ5+WtQPL5uigY50Q==} + engines: {node: '>=14'} + encodeurl@1.0.2: resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} engines: {node: '>= 0.8'} @@ -4183,14 +4151,10 @@ packages: end-of-stream@1.4.5: resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} - enhanced-resolve@5.18.4: - resolution: {integrity: sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q==} + enhanced-resolve@5.24.3: + resolution: {integrity: sha512-PwKooW9JUzh5chmYfHM3IQl5OkK2u2Nm011MgeZrss3JmFraUx/fqrf78kk8GUMYoibx/14MdwTl/1WKkG7TpQ==} engines: {node: '>=10.13.0'} - enquirer@2.4.1: - resolution: {integrity: sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==} - engines: {node: '>=8.6'} - entities@7.0.1: resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==} engines: {node: '>=0.12'} @@ -4212,19 +4176,14 @@ packages: es-module-lexer@1.7.0: resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} - es-module-lexer@2.0.0: - resolution: {integrity: sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==} + es-module-lexer@2.3.1: + resolution: {integrity: sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA==} es6-promise@3.3.1: resolution: {integrity: sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg==} - esbuild@0.27.2: - resolution: {integrity: sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==} - engines: {node: '>=18'} - hasBin: true - - esbuild@0.27.7: - resolution: {integrity: sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==} + esbuild@0.28.1: + resolution: {integrity: sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==} engines: {node: '>=18'} hasBin: true @@ -4248,9 +4207,9 @@ packages: engines: {node: '>=6.0'} hasBin: true - eslint-visitor-keys@4.2.1: - resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + eslint-visitor-keys@5.0.1: + resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} esprima@4.0.1: resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} @@ -4290,12 +4249,8 @@ packages: resolution: {integrity: sha512-AFASGfIlnIbkKPQwX1yHaDjFvh/1gyKJODme52V6IORh69uEYgZp0o9C+qsIGNVEiuuhQU0CSSl++Rlegg1qvA==} engines: {node: '>=0.10.0'} - expand-template@2.0.3: - resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} - engines: {node: '>=6'} - - expect-type@1.3.0: - resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} + expect-type@1.4.0: + resolution: {integrity: sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==} engines: {node: '>=12.0.0'} exponential-backoff@3.1.3: @@ -4305,15 +4260,12 @@ packages: resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==} engines: {node: '>=0.10.0'} - extendable-error@0.1.7: - resolution: {integrity: sha512-UOiS2in6/Q0FK0R0q6UY9vYpQ21mr/Qn1KOnte7vsACuNJf514WvCCUHSRCPcgjPT2bAhNIJdlE6bVap1GKmeg==} - fake-indexeddb@6.2.5: resolution: {integrity: sha512-CGnyrvbhPlWYMngksqrSSUT1BAVP49dZocrHuK0SvtR0D5TMs5wP0o3j7jexDJW01KSadjBp1M/71o/KR3nD1w==} engines: {node: '>=18'} - fast-check@4.8.0: - resolution: {integrity: sha512-GOJ158CUMnN6cSahsv4+ExARvIDuzzinFjkp0E9WtiBa5zcVeLozVkWaE4IzFcc+Y48Wp1EDlUZsXRyAztQcSg==} + fast-check@4.9.0: + resolution: {integrity: sha512-7ms6T7SybUev/PQITciI0yLM2pOSFy5zpG8Ty7tQofcVaQUvrMXp6CBwqF6fThLCLOrfBtuHAtwq6Yu4XPCllg==} engines: {node: '>=12.17.0'} fast-deep-equal@3.1.3: @@ -4322,21 +4274,23 @@ packages: fast-fifo@1.3.2: resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==} - fast-glob@3.3.3: - resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} - engines: {node: '>=8.6.0'} - fast-json-stable-stringify@2.1.0: resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} fast-safe-stringify@2.1.1: resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} + fast-string-truncated-width@3.0.3: + resolution: {integrity: sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==} + + fast-string-width@3.0.2: + resolution: {integrity: sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==} + fast-uri@3.1.2: resolution: {integrity: sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==} - fastq@1.20.1: - resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} + fast-wrap-ansi@0.2.2: + resolution: {integrity: sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==} fb-dotslash@0.5.8: resolution: {integrity: sha512-XHYLKk9J4BupDxi9bSEhkfss0m+Vr9ChTrjhf9l2iw3jB5C7BnY4GVPoMcqbrTutsKJso6yj2nAB6BI/F2oZaA==} @@ -4355,11 +4309,8 @@ packages: picomatch: optional: true - file-uri-to-path@1.0.0: - resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} - - filing-cabinet@5.0.3: - resolution: {integrity: sha512-PlPcMwVWg60NQkhvfoxZs4wEHjhlOO/y7OAm4sKM60o1Z9nttRY4mcdQxp/iZ+kg/Vv6Hw1OAaTbYVM9DA9pYg==} + filing-cabinet@5.5.1: + resolution: {integrity: sha512-PzLBTChlVPn6LnNxF0KWs+XqPziVh3Sfmz/3TXOymHxu6a9yhrDcQn7YwgpcRM6mqhR2WHVGPR8RU4fmcF1IVA==} engines: {node: '>=18'} hasBin: true @@ -4383,9 +4334,6 @@ packages: resolution: {integrity: sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==} engines: {node: '>=8'} - find-my-way-ts@0.1.6: - resolution: {integrity: sha512-a85L9ZoXtNAey3Y6Z+eBWW658kO/MwR7zIafkIUPUMf3isZG0NCs2pjW2wtjxAKuJPxMAsHUIP4ZPGv0o5gyTA==} - find-up@3.0.0: resolution: {integrity: sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==} engines: {node: '>=6'} @@ -4397,8 +4345,12 @@ packages: flow-enums-runtime@0.0.6: resolution: {integrity: sha512-3PYnM29RFXwvAN6Pc/scUfkI7RwhQ/xqyLUyPNlXUp9S40zI8nup9tUSrTLSVnWGBN38FNiGWbwZOB6uR4OGdw==} - flow-parser@0.299.0: - resolution: {integrity: sha512-phGMRoNt6SNglPHGRbCyWm9/pxfe6t/t4++EIYPaBGWT6e0lphLBgUMrvpL62NbRo9R549o3oqrbKHq82kANCw==} + flow-estree@0.323.0: + resolution: {integrity: sha512-VuCvqb38ueB7u9Xnfuj/uV9xWaqRM1XX8fUbch3Q1perZp1BZHAcGKcSkc57RdVXaDrjsfgNOiSEmlvx8Yhk4Q==} + engines: {node: '>=18'} + + flow-parser@0.323.0: + resolution: {integrity: sha512-uTw5ffLYxQDnSe4Ch5mo1Sp7kFOrhOglz3nqgpQRrYpt4ucQTxrQRCLdC0F1hyhcIK7j5GjdxzV1d4DN68eDzA==} engines: {node: '>=0.4.0'} for-in@1.0.2: @@ -4416,17 +4368,6 @@ packages: fs-constants@1.0.0: resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} - fs-extra@7.0.1: - resolution: {integrity: sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==} - engines: {node: '>=6 <7 || >=8'} - - fs-extra@8.1.0: - resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==} - engines: {node: '>=6 <7 || >=8'} - - fs-readdir-recursive@1.1.0: - resolution: {integrity: sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA==} - fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} @@ -4450,16 +4391,16 @@ packages: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} - get-amd-module-type@6.0.1: - resolution: {integrity: sha512-MtjsmYiCXcYDDrGqtNbeIYdAl85n+5mSv2r3FbzER/YV3ZILw4HNNIw34HuV5pyl0jzs6GFYU1VHVEefhgcNHQ==} + get-amd-module-type@6.0.2: + resolution: {integrity: sha512-7zShVYAYtMnj9S65CfN+hvpBCByfuB1OY8xID01nZEzXTZbx4YyysAfi+nMl95JSR6odt4q8TCj2W63KAoyVLQ==} engines: {node: '>=18'} get-caller-file@2.0.5: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} - get-east-asian-width@1.5.0: - resolution: {integrity: sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==} + get-east-asian-width@1.6.0: + resolution: {integrity: sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==} engines: {node: '>=18'} get-own-enumerable-property-symbols@3.0.2: @@ -4469,29 +4410,15 @@ packages: resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} engines: {node: '>=8.0.0'} - get-port@7.2.0: - resolution: {integrity: sha512-afP4W205ONCuMoPBqcR6PSXnzX35KTcJygfJfcp+QY+uwm3p20p1YczWXhlICIzGMCxYBQcySEcOgsJcrkyobg==} - engines: {node: '>=16'} + get-port@5.1.1: + resolution: {integrity: sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==} + engines: {node: '>=8'} get-tsconfig@4.14.0: resolution: {integrity: sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==} - github-from-package@0.0.0: - resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} - - glob-parent@5.1.2: - resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} - engines: {node: '>= 6'} - glob@10.5.0: resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==} - deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me - hasBin: true - - glob@11.1.0: - resolution: {integrity: sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==} - engines: {node: 20 || >=22} - deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me hasBin: true glob@13.0.6: @@ -4502,17 +4429,6 @@ packages: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me - global-prefix@4.0.0: - resolution: {integrity: sha512-w0Uf9Y9/nyHinEk5vMJKRie+wa4kR5hmDbEhGGds/kG1PwGLLHKRoNMeJOyCQjjBkANlnScqgzcFwGHgmgLkVA==} - engines: {node: '>=16'} - - globby@11.1.0: - resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} - engines: {node: '>=10'} - - globrex@0.1.2: - resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} - gonzales-pe@4.3.0: resolution: {integrity: sha512-otgSPpUmdWJ43VXyiNgEYE4luzHCL2pz4wQ0OnDluC6Eg4Ko3Vexy/SrSynglw/eR+OhkzmqFCZa/OFa/RgAOQ==} engines: {node: '>=0.6.0'} @@ -4527,18 +4443,17 @@ packages: gulp-header@1.8.12: resolution: {integrity: sha512-lh9HLdb53sC7XIZOYzTXM4lFuXElv3EVkSDhsd7DoJBj7hm+Ni7D3qYbb+Rr8DuM8nRanBvkVO9d7askreXGnQ==} - deprecated: Removed event-stream from gulp-header - happy-dom@20.9.0: - resolution: {integrity: sha512-GZZ9mKe8r646NUAf/zemnGbjYh4Bt8/MqASJY+pSm5ZDtc3YQox+4gsLI7yi1hba6o+eCsGxpHn5+iEVn31/FQ==} + happy-dom@20.11.1: + resolution: {integrity: sha512-XSt8tMzbW9ymE7687xztkO1ckR7qJNQ3LywY9vlYGhGi3zXrGBHuUo2Cl1ztZaICW+1eAGdkLbj6iwVqDT33kg==} engines: {node: '>=20.0.0'} has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} - hasown@2.0.3: - resolution: {integrity: sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==} + hasown@2.0.4: + resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} engines: {node: '>= 0.4'} hermes-compiler@0.14.0: @@ -4556,8 +4471,8 @@ packages: hermes-parser@0.35.0: resolution: {integrity: sha512-9JLjeHxBx8T4CAsydZR49PNZUaix+WpQJwu9p2010lu+7Kwl6D/7wYFFJxoz+aXkaaClp9Zfg6W6/zVlSJORaA==} - hono@4.12.18: - resolution: {integrity: sha512-RWzP96k/yv0PQfyXnWjs6zot20TqfpfsNXhOnev8d1InAxubW93L11/oNUc3tQqn2G0bSdAOBpX+2uDFHV7kdQ==} + hono@4.12.28: + resolution: {integrity: sha512-YwUvVpSF7m1yOblFPrU3Hbo8XhPheBoiyfGuII6z19LnOr6JpDnyyp7LFNrfV56wS8tpvtBFGRISHN02pDdLOA==} engines: {node: '>=16.9.0'} html-encoding-sniffer@6.0.0: @@ -4582,28 +4497,31 @@ packages: resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} engines: {node: '>= 14'} - human-id@4.1.3: - resolution: {integrity: sha512-tsYlhAYpjCKa//8rXZ9DqKEawhPoSytweBC2eNvcaDK+57RZLHGqNs3PZTQO6yekLFSuvA6AlnAfrw1uBvtb+Q==} + human-id@4.2.0: + resolution: {integrity: sha512-K3GbkIWqyvvlpfhBPlbEvD97TtqBpAYA4kt+cn2lD2x2HuohzZCibcA2nOlnJT6exqvJLggoB5nv2dNf192nEA==} hasBin: true iconv-lite@0.7.2: resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==} engines: {node: '>=0.10.0'} + iconv-lite@0.7.3: + resolution: {integrity: sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==} + engines: {node: '>=0.10.0'} + ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} - ignore@5.3.2: - resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} - engines: {node: '>= 4'} - image-size@1.2.1: resolution: {integrity: sha512-rH+46sQJ2dlwfjfhCyNx5thzrv+dtmBIhPHk0zgRUukHzZ/kRueTJXoYYsclBaKcSMBWuGbOFXtioLpzTb5euw==} engines: {node: '>=16.x'} hasBin: true - immer@11.1.8: - resolution: {integrity: sha512-/tbkHMW7y10Lx6i1crLjD4/OhNkRG+Fo7byZHtah0547nIeXYcpIXaUh0IAQY6gO5459qpGGYapcEOHtFXkIuA==} + immer@11.1.11: + resolution: {integrity: sha512-qzXuyXAkPySAGYkfsAwodDPWT8Zm7/Uo5BNt4BjhMhG5WlWyZZ4wQqnWwdS8kjlQ1Cwu6gjw3A6+0gTQwlyYtw==} + + import-meta-resolve@4.2.0: + resolution: {integrity: sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==} imurmurhash@0.1.4: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} @@ -4627,14 +4545,6 @@ packages: ini@1.3.8: resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} - ini@4.1.3: - resolution: {integrity: sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - - ini@7.0.0: - resolution: {integrity: sha512-ifK0CgjALofS5bkrcTy4RaQ9Vx2Knf/eLeIO+NaswQEpH1UblrtTSCIvN71qQDMq0PeQ/SSPojvEJp9vvvfr+w==} - engines: {node: ^22.22.2 || ^24.15.0 || >=26.0.0} - invariant@2.2.4: resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==} @@ -4642,10 +4552,6 @@ packages: resolution: {integrity: sha512-C6uC+kleiIMmjViJINWk80sOQw5lEzse1ZmvD+S/s8p8CWapftSaC+kocGTx6xrbrJ4WmYQGC08ffHLr6ToR6Q==} engines: {node: '>=12.22.0'} - is-binary-path@2.1.0: - resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} - engines: {node: '>=8'} - is-buffer@1.1.6: resolution: {integrity: sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==} @@ -4671,18 +4577,10 @@ packages: resolution: {integrity: sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==} engines: {node: '>=0.10.0'} - is-extglob@2.1.1: - resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} - engines: {node: '>=0.10.0'} - is-fullwidth-code-point@3.0.0: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} - is-glob@4.0.3: - resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} - engines: {node: '>=0.10.0'} - is-in-ssh@1.0.0: resolution: {integrity: sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw==} engines: {node: '>=20'} @@ -4733,10 +4631,6 @@ packages: resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} engines: {node: '>=8'} - is-subdir@1.2.0: - resolution: {integrity: sha512-2AT6j+gXe/1ueqbW6fLZJiIw3F8iXGJtt0yDrZaBhAZEG1raiTxKWU+IPqMCzQAXOUCKdA4UDMgacKH25XG2Cw==} - engines: {node: '>=4'} - is-unicode-supported@0.1.0: resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} engines: {node: '>=10'} @@ -4745,13 +4639,6 @@ packages: resolution: {integrity: sha512-GI+WjezhPPcbM+tqE9LnmsY5qqjwHzTvjJ36wxYX5ujNXefSUJ/T17r5bqDV8yLhcgB59KTPNOc9O9cmHTPWsA==} engines: {node: '>=10'} - is-url@1.2.4: - resolution: {integrity: sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==} - - is-windows@1.0.2: - resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==} - engines: {node: '>=0.10.0'} - is-wsl@2.2.0: resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} engines: {node: '>=8'} @@ -4766,10 +4653,6 @@ packages: isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - isexe@3.1.5: - resolution: {integrity: sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==} - engines: {node: '>=18'} - isobject@2.1.0: resolution: {integrity: sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA==} engines: {node: '>=0.10.0'} @@ -4797,10 +4680,6 @@ packages: jackspeak@3.4.3: resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} - jackspeak@4.1.1: - resolution: {integrity: sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==} - engines: {node: 20 || >=22} - jest-environment-node@29.7.0: resolution: {integrity: sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -4837,8 +4716,11 @@ packages: resolution: {integrity: sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - js-base64@3.7.8: - resolution: {integrity: sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow==} + jju@1.4.0: + resolution: {integrity: sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==} + + js-base64@3.8.0: + resolution: {integrity: sha512-65kvbemyZhj+ExQt1PEFyBEjL5vAHysu1lJdW1AwhhChkO8ZBPizYk/m9GVrpbS2Je1hF+UYZ+6KywqtZV8mHw==} js-levenshtein@1.1.6: resolution: {integrity: sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==} @@ -4853,8 +4735,8 @@ packages: js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - js-yaml@3.14.2: - resolution: {integrity: sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==} + js-yaml@3.15.0: + resolution: {integrity: sha512-ttBQIIQPDeLjpPOohtUdXuXUVoA2uIB6fEH9HyJ7234s5mBJ5wTx20njxplLZQgLaOfpmPQA7X2t5AX6tIPbog==} hasBin: true js-yaml@4.1.1: @@ -4867,8 +4749,8 @@ packages: jsc-safe-url@0.2.4: resolution: {integrity: sha512-0wM3YBWtYePOjfyXQH5MWQ8H7sdk5EXSwZvmSLKk2RboVQ2Bu239jycHDz5J/8Blf3K0Qnoy2b6xD+z10MFB+Q==} - jscodeshift@17.3.0: - resolution: {integrity: sha512-LjFrGOIORqXBU+jwfC9nbkjmQfFldtMIoS6d9z2LG/lkmyNXsJAySPT+2SWXJEoE68/bCWcxKpXH37npftgmow==} + jscodeshift@17.4.0: + resolution: {integrity: sha512-i3ESKiiTsGynxzTg5BhsZViD0ai72/6SsI1efDZxG6/5KCoElsmquxtyhXK5lpEgoO7MTNpYkjFEdEL97SkBNg==} engines: {node: '>=16'} hasBin: true peerDependencies: @@ -4877,11 +4759,11 @@ packages: '@babel/preset-env': optional: true - jsdom@29.1.1: - resolution: {integrity: sha512-ECi4Fi2f7BdJtUKTflYRTiaMxIB0O6zfR1fX0GXpUrf6flp8QIYn1UT20YQqdSOfk2dfkCwS8LAFoJDEppNK5Q==} - engines: {node: ^20.19.0 || ^22.13.0 || >=24.0.0} + jsdom@30.0.0: + resolution: {integrity: sha512-JQHfRGmmKmaZoUAvIgff5jjG/0SzTQlGz8c7t72KzBzo8ZULEjAjnYE0sNwBOUA4QtWwYE2xoYitg8NFsmiYxA==} + engines: {node: ^22.22.2 || ^24.15.0 || >=26.0.0} peerDependencies: - canvas: ^3.0.0 + canvas: ^3.2.3 peerDependenciesMeta: canvas: optional: true @@ -4902,8 +4784,8 @@ packages: engines: {node: '>=6'} hasBin: true - jsonfile@4.0.0: - resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} + jsonc-parser@3.3.1: + resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==} jsonwebtoken@9.0.3: resolution: {integrity: sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==} @@ -4934,10 +4816,13 @@ packages: kubernetes-types@1.30.0: resolution: {integrity: sha512-Dew1okvhM/SQcIa2rcgujNndZwU8VnSapDgdxlYoB84ZlpAD43U6KLAFqYo17ykSFGHNPrg0qry0bP+GJd9v7Q==} - lalph@0.3.137: - resolution: {integrity: sha512-tmRRsdoGqeArapr3BGymUhJ2vqJQQPLmps8yoi4MRHb4S2oplDeiMe/PYzrCedqgGzTihquNrj4OX/q0ktcAag==} + lalph@0.3.139: + resolution: {integrity: sha512-1Ut5QslLbAYZfrZqnEcG7InUeZgjmDPhODdWNt7DtulTRLVqQtqzSDJXylLa90L0ajZzeGZk36fjSsAB5m/sdQ==} hasBin: true + launch-editor@2.14.1: + resolution: {integrity: sha512-QWBrQsMpH7gPr965dsKD/3cKWiNoTjpATQf++Xq63N6sKRGMwlVXz41O1IZTMfZQgBctD/K5Zt06+/I6pP6+HA==} + lazy-cache@2.0.2: resolution: {integrity: sha512-7vp2Acd2+Kz4XkzxGxaB1FWOi8KjWIWsgdfD5MCb86DWvlLqhRPM+d6Pro3iNEL5VT9mstz5hKAlcd+QR6H3aA==} engines: {node: '>=0.10.0'} @@ -4958,74 +4843,74 @@ packages: lighthouse-logger@1.4.2: resolution: {integrity: sha512-gPWxznF6TKmUHrOQjlVo2UbaL2EJ71mb2CCeRs/2qBpi4L/g4LUVc9+3lKQ6DTUZwJswfM7ainGrLO1+fOqa2g==} - lightningcss-android-arm64@1.32.0: - resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} + lightningcss-android-arm64@1.33.0: + resolution: {integrity: sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [android] - lightningcss-darwin-arm64@1.32.0: - resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} + lightningcss-darwin-arm64@1.33.0: + resolution: {integrity: sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [darwin] - lightningcss-darwin-x64@1.32.0: - resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} + lightningcss-darwin-x64@1.33.0: + resolution: {integrity: sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [darwin] - lightningcss-freebsd-x64@1.32.0: - resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} + lightningcss-freebsd-x64@1.33.0: + resolution: {integrity: sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [freebsd] - lightningcss-linux-arm-gnueabihf@1.32.0: - resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} + lightningcss-linux-arm-gnueabihf@1.33.0: + resolution: {integrity: sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==} engines: {node: '>= 12.0.0'} cpu: [arm] os: [linux] - lightningcss-linux-arm64-gnu@1.32.0: - resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} + lightningcss-linux-arm64-gnu@1.33.0: + resolution: {integrity: sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - lightningcss-linux-arm64-musl@1.32.0: - resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} + lightningcss-linux-arm64-musl@1.33.0: + resolution: {integrity: sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - lightningcss-linux-x64-gnu@1.32.0: - resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} + lightningcss-linux-x64-gnu@1.33.0: + resolution: {integrity: sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - lightningcss-linux-x64-musl@1.32.0: - resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} + lightningcss-linux-x64-musl@1.33.0: + resolution: {integrity: sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - lightningcss-win32-arm64-msvc@1.32.0: - resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} + lightningcss-win32-arm64-msvc@1.33.0: + resolution: {integrity: sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [win32] - lightningcss-win32-x64-msvc@1.32.0: - resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} + lightningcss-win32-x64-msvc@1.33.0: + resolution: {integrity: sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [win32] - lightningcss@1.32.0: - resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} + lightningcss@1.33.0: + resolution: {integrity: sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==} engines: {node: '>= 12.0.0'} list-item@1.1.1: @@ -5073,12 +4958,8 @@ packages: lodash.once@4.1.1: resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==} - lodash.startcase@4.4.0: - resolution: {integrity: sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==} - - lodash.template@4.5.0: - resolution: {integrity: sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A==} - deprecated: This package is deprecated. Use https://socket.dev/npm/package/eta instead. + lodash.template@4.18.1: + resolution: {integrity: sha512-5urZrLnV/VD6zHK5KsVtZgt7H19v51mIzoS0aBNH8yp3I8tbswrEjOABOPY8m8uB7NuibubLrMX+Y0PXsU9X+w==} lodash.templatesettings@4.2.0: resolution: {integrity: sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ==} @@ -5100,14 +4981,11 @@ packages: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true - loupe@3.2.1: - resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==} - lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} - lru-cache@11.3.6: - resolution: {integrity: sha512-Gf/KoL3C/MlI7Bt0PGI9I+TeTC/I6r/csU58N4BSNc4lppLBeKsOdFYkK+dX0ABDUMJNfCHTyPpzwwO21Awd3A==} + lru-cache@11.5.2: + resolution: {integrity: sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==} engines: {node: 20 || >=22} lru-cache@5.1.1: @@ -5134,9 +5012,6 @@ packages: magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} - magicast@0.5.2: - resolution: {integrity: sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ==} - magicast@0.5.3: resolution: {integrity: sha512-pVKE4UdSQ7DvHzivsCIFx2BJn1mHG6KsyrFcaxFx6tONdneEuThrDx0Cj3AMg58KyN4pzYT+LHOotxDQDjNvkw==} @@ -5174,10 +5049,6 @@ packages: merge-stream@2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} - merge2@1.4.1: - resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} - engines: {node: '>= 8'} - metro-babel-transformer@0.83.7: resolution: {integrity: sha512-sBqBkt6kNut/88bv+Ucvm4yqdPetbvAEsHzi3MAgJEifOSYYzX5Z5Kgw3TFOrwf/mHJTOBG2ONlaMHoyfP15TA==} engines: {node: '>=20.19.4'} @@ -5262,16 +5133,12 @@ packages: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} engines: {node: '>=6'} - mimic-response@3.1.0: - resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} - engines: {node: '>=10'} - min-indent@1.0.1: resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} engines: {node: '>=4'} - miniflare@4.20260507.1: - resolution: {integrity: sha512-PSXBiLExTdZ4UGO/raKCHQauUpYL7F880ZRB7j0+78Rv8h7TsdN2E/iEDK9sK2Y+SPQ5wJSeAa+rDeVKoZZoEw==} + miniflare@4.20260706.0: + resolution: {integrity: sha512-UiqGo9Es/D7kJvDVpjhTQ/M2ppCSCsRc5EEKec6i4BvnCkFCRaZHRmFkMHzLhlg+daSZ+zvBaycWmgLZHn/1tQ==} engines: {node: '>=22.0.0'} hasBin: true @@ -5326,20 +5193,16 @@ packages: resolution: {integrity: sha512-2ORxRN+h40+3/Ylw9LKOtYGfQIoX6grGQlmbvMKqaeZ5/l7oeMvqdJxyG/ax3Poy7VbqMTADI6BwTmO7u10Wrw==} engines: {node: '>=16.0.0'} - module-definition@6.0.1: - resolution: {integrity: sha512-FeVc50FTfVVQnolk/WQT8MX+2WVcDnTGiq6Wo+/+lJ2ET1bRVi3HG3YlJUfqagNMc/kUlFSoR96AJkxGpKz13g==} + module-definition@6.0.2: + resolution: {integrity: sha512-SvAU3lB0+Yjbq55yHY3wkRZBOh+fhU1SnIF3IFbTewv6mtAh7yUT8ACHAJ2mGIJ7tCes2QuCL/cl6m0JSZ/ArA==} engines: {node: '>=18'} hasBin: true - module-lookup-amd@9.0.5: - resolution: {integrity: sha512-Rs5FVpVcBYRHPLuhHOjgbRhosaQYLtEo3JIeDIbmNo7mSssi1CTzwMh8v36gAzpbzLGXI9wB/yHh+5+3fY1QVw==} + module-lookup-amd@9.1.3: + resolution: {integrity: sha512-Jc3XmOaR9FdfMJSK8+vyLgsCkzm8z2L0NS6vrlRWi12DjS7MY7TMNE7E1yj8yXx837xtMDbKSSgcdXnFlJ2YLg==} engines: {node: '>=18'} hasBin: true - mri@1.2.0: - resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} - engines: {node: '>=4'} - mrmime@2.0.1: resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} engines: {node: '>=10'} @@ -5350,18 +5213,15 @@ packages: ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - msgpackr-extract@3.0.3: - resolution: {integrity: sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA==} + msgpackr-extract@3.0.4: + resolution: {integrity: sha512-4kmO/MdyUIkLIvTPr8VHLil4AtoKIoniWPIEk5+CDy0xnWC84azhSFmuJ7PxZdsYtiP5kEeQsORAVIeMgxT+Hw==} hasBin: true - msgpackr@2.0.1: - resolution: {integrity: sha512-9J+tqTEsbHqY8YohazYgty7LgerFIWxvMLpUjqETSmjHojtJm2WnX2kK/2a1fLI7CO7ERP1YSEUXMucz4j+yBA==} - - multipasta@0.2.7: - resolution: {integrity: sha512-KPA58d68KgGil15oDqXjkUBEBYc00XvbPj5/X+dyzeo/lWm9Nc25pQRlf1D+gv4OpK7NM0J1odrbu9JNNGvynA==} + msgpackr@2.0.4: + resolution: {integrity: sha512-o1C5KRmuRt+apqMr1HuGSqWStZoRBUpEsCsl15uM9VdAF1qHLtvMOU2En747EnTyEl6c4pzPewRMFF31s1CNbA==} - mysql2@3.22.3: - resolution: {integrity: sha512-uWWxvZSRvRhtBdh2CdcuK83YcOfPdmEeEYB069bAmPnV93QApDGVPuvCQOLjlh7tYHEWdgQPrn6kosDxHBVLkA==} + mysql2@3.22.6: + resolution: {integrity: sha512-fPKmeDGUzvFP7bMD5SASlJ5zIgvCC4hbanTmhbUlEmhyrY1hR4Hi3xLOWgTd3luYjLVifx6uGvMNJ2m/LlqEpg==} engines: {node: '>= 8.0'} peerDependencies: '@types/node': '>= 8' @@ -5370,17 +5230,14 @@ packages: resolution: {integrity: sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w==} engines: {node: '>=8.0.0'} - nan@2.26.2: - resolution: {integrity: sha512-0tTvBTYkt3tdGw22nrAy50x7gpbGCCFH3AFcyS5WiUu7Eu4vWlri1woE6qHBSfy11vksDqkiwjOnlR7WV8G1Hw==} + nan@2.28.0: + resolution: {integrity: sha512-fTsDz99OTq2sVePhGdp4qQhggZFtKr64ZNVyVajRKtMOkJxYekplBh577PiJB12v/D3s2E5cGtOI45LWp6rnLQ==} - nanoid@3.3.12: - resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==} + nanoid@3.3.16: + resolution: {integrity: sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true - napi-build-utils@2.0.0: - resolution: {integrity: sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==} - native-duplexpair@1.0.0: resolution: {integrity: sha512-E7QQoM+3jvNtlmyfqRZ0/U75VFgCls+fSkbml2MpgWkWyz3ox8Y58gNhfuziuQYGNNQAbFZJQck55LHCnCK6CA==} @@ -5391,12 +5248,8 @@ packages: neo-async@2.6.2: resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} - node-abi@3.92.0: - resolution: {integrity: sha512-KdHvFWZjEKDf0cakgFjebl371GPsISX2oZHcuyKqM7DtogIsHrqKeLTo8wBHxaXRAQlY2PsPlZmfo+9ZCxEREQ==} - engines: {node: '>=10'} - - node-addon-api@8.8.0: - resolution: {integrity: sha512-c5Ko1fZJIJmzhFIkhRN76WTq+fC6tWnGy9CXA0fA+XygsWZmEwG8vmbkNqxMyoaa0Tin4djul49NzdVcJJcjeA==} + node-addon-api@8.9.0: + resolution: {integrity: sha512-ekZMeaaIzSQTSpr7X2X3iJM7lTzgnx8ahAG9pJfT/7+14mlEM8ZYQ9cgCDvSSRbReFK0oHli3WrZdCiRsgAT9Q==} engines: {node: ^18 || ^20 || >= 21} node-fetch-h2@2.3.0: @@ -5426,19 +5279,16 @@ packages: node-readfiles@0.2.0: resolution: {integrity: sha512-SU00ZarexNlE4Rjdm83vglt5Y9yiQ+XI1XpflWlb7q7UTN1JUItm69xMeiQCTxtTfnzt+83T8Cx+vI2ED++VDA==} - node-releases@2.0.27: - resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} - - node-releases@2.0.47: - resolution: {integrity: sha512-Uzmd6LXpouKo8EUK68IjH4+E01w/hXyV3R3g/geCJo+rXLNfh1xucB+LOzYEOQPSiUK3h/xZf0cQGcSsmyL2Og==} + node-releases@2.0.51: + resolution: {integrity: sha512-wRNIrw4DmVLKQlbgOMdkMx27Wrpzes2hh5Jtbi2bjPd+4wJstWIqP5A+lscnqbm0xxmT5Bpg8Lec5ItEBwx6BQ==} engines: {node: '>=18'} - node-source-walk@7.0.1: - resolution: {integrity: sha512-3VW/8JpPqPvnJvseXowjZcirPisssnBuDikk6JIZ8jQzF7KJQX52iPFX4RYYxLycYH7IbMRSPUOga/esVjy5Yg==} + node-source-walk@7.0.2: + resolution: {integrity: sha512-71kFFjYaSshDTA8/a2HiTYPLdASWjLJxUyJxGE+ffxU+KhxSBtM9kiLUX+R2yooFdSFKMFpi4n3PFtDy6qXv8A==} engines: {node: '>=18'} - nodemailer@8.0.7: - resolution: {integrity: sha512-pkjE4mkBzQjdJT4/UmlKl3pX0rC9fZmjh7c6C9o7lv66Ac6w9WCnzPzhbPNxwZAzlF4mdq4CSWB5+FbK6FWCow==} + nodemailer@9.0.3: + resolution: {integrity: sha512-n+YP+NKwR5zRWa60k3GiQ6Q3B4KXCoAw40dAKeCtYn020iNN74aWK2liXIC3ZEATeGql7we3tE3t8QwhY0eskw==} engines: {node: '>=6.0.0'} normalize-path@3.0.0: @@ -5475,11 +5325,8 @@ packages: obuf@1.1.2: resolution: {integrity: sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==} - obug@2.1.1: - resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} - - obug@2.1.2: - resolution: {integrity: sha512-AWGB9WFcRXOQs48Z/udjI5ZcZMHXwX8XPByNpOydgcGsDLIzjGizhoMWJyKAWze7AVW/2W1i+/gPX4YtKe5cyg==} + obug@2.1.4: + resolution: {integrity: sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==} engines: {node: '>=12.20.0'} on-finished@2.3.0: @@ -5522,22 +5369,18 @@ packages: resolution: {integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==} engines: {node: '>=10'} - outdent@0.5.0: - resolution: {integrity: sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q==} - - oxlint@1.42.0: - resolution: {integrity: sha512-qnspC/lrp8FgKNaONLLn14dm+W5t0SSlus6V5NJpgI2YNT1tkFYZt4fBf14ESxf9AAh98WBASnW5f0gtw462Lg==} + oxlint@1.76.0: + resolution: {integrity: sha512-6QoFioEU4fNdiUx/2Eo6TRd6NG7H7njnRCz8rhB66cZmMHDTqcm1Rjvl8Wry+ZTQMBAmyb4Mlf62Mk5X+eHSOw==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: - oxlint-tsgolint: '>=0.11.2' + oxlint-tsgolint: '>=7.0.2001' + vite-plus: '*' peerDependenciesMeta: oxlint-tsgolint: optional: true - - p-filter@2.1.0: - resolution: {integrity: sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw==} - engines: {node: '>=8'} + vite-plus: + optional: true p-limit@2.3.0: resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} @@ -5551,10 +5394,6 @@ packages: resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} engines: {node: '>=8'} - p-map@2.1.0: - resolution: {integrity: sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==} - engines: {node: '>=6'} - p-try@2.2.0: resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} engines: {node: '>=6'} @@ -5562,8 +5401,8 @@ packages: package-json-from-dist@1.0.1: resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} - package-manager-detector@0.2.11: - resolution: {integrity: sha512-BEnLolu+yuz22S56CU1SUKq3XC3PkwD5wv4ikR4MfGvnRVcmzXR9DwSlW2fEamyTPyXHomBJRzgapeuBvRNzJQ==} + package-manager-detector@1.8.0: + resolution: {integrity: sha512-yQA4H19AmPEoMUeavPMDIe1higySl/gH/yaQrkT/s07Qp+7pp2hYz30N3z2l5BkjVkF9Ow6o0wjJamm2y7Sn0A==} parse-json@8.3.0: resolution: {integrity: sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==} @@ -5580,6 +5419,9 @@ packages: resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} engines: {node: '>= 0.8'} + path-browserify@1.0.1: + resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} + path-exists@3.0.0: resolution: {integrity: sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==} engines: {node: '>=4'} @@ -5607,21 +5449,17 @@ packages: resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==} engines: {node: 18 || 20 || >=22} - path-type@4.0.0: - resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} - engines: {node: '>=8'} - pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} - pg-cloudflare@1.3.0: - resolution: {integrity: sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ==} + pg-cloudflare@1.4.0: + resolution: {integrity: sha512-Vo7z/6rrQYxpNRylp4Tlob2elzbh+N/MOQbxFVWCxS7oEx6jF53GTJFxK2WWpKuBRkmiin4Mt+xofFDjx09R0A==} - pg-connection-string@2.12.0: - resolution: {integrity: sha512-U7qg+bpswf3Cs5xLzRqbXbQl85ng0mfSV/J0nnA31MCLgvEaAo7CIhmeyrmJpOr7o+zm0rXK+hNnT5l9RHkCkQ==} + pg-connection-string@2.14.0: + resolution: {integrity: sha512-XwWDGcLRGCXAR8F/AM5bG7Q+A3Wm2s6QeEjlOKZLlH3UYcguiqCWKyWXVag5TLTIjR7oOJUY8kcADaZgWPyLeg==} - pg-cursor@2.19.0: - resolution: {integrity: sha512-J5cF1MUz7LRJ9emOqF/06QjabMHMZy587rSPF0UuA8rCwKeeYl2co8Pp+6k5UU9YrAYHMzWkLxilfZB0hqsWWw==} + pg-cursor@2.21.0: + resolution: {integrity: sha512-IYvk/j+Suhtbo/C3uOf4JLsLK/gWxOTUOmYbDsbKnLaVJDq+KwhwK6ngpRfiCk8eDMS3AmGQABZCv0cREEzHQw==} peerDependencies: pg: ^8 @@ -5633,14 +5471,17 @@ packages: resolution: {integrity: sha512-BM/Thnrw5jm2kKLE5uJkXqqExRUY/toLHda65XgFTBTFYZyopbKjBe29Ii3RbkvlsMoFwD+tHeGaCjjv0gHlyw==} engines: {node: '>=4'} - pg-pool@3.13.0: - resolution: {integrity: sha512-gB+R+Xud1gLFuRD/QgOIgGOBE2KCQPaPwkzBBGC9oG69pHTkhQeIuejVIk3/cnDyX39av2AxomQiyPT13WKHQA==} + pg-pool@3.14.0: + resolution: {integrity: sha512-gKtPkFdQPU3DksooVLi9LsjZxrsBUZIpa+7aVx+LV5pNh0KzP4Zleud2po+ConrxbuXGBJ6Hfer6hdgpIBpBaw==} peerDependencies: pg: '>=8.0' pg-protocol@1.13.0: resolution: {integrity: sha512-zzdvXfS6v89r6v7OcFCHfHlyG/wvry1ALxZo4LqgUoy7W9xhBDMaqOuMiF3qEV45VqsN6rdlcehHrfDtlCPc8w==} + pg-protocol@1.15.0: + resolution: {integrity: sha512-cq9sECI5s0+uPUXjbz8ioyPJni6RzsRib0US67i5IoTZKw8fNeYlVE7u8F4dG7vEJJtc5wdD1K189lCCUwqWTQ==} + pg-types@2.2.0: resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==} engines: {node: '>=4'} @@ -5649,8 +5490,8 @@ packages: resolution: {integrity: sha512-o2XFanIMy/3+mThw69O8d4n1E5zsLhdO+OPqswezu7Z5ekP4hYDqlDjlmOpYMbzY2Br0ufCwJLdDIXeNVwcWFg==} engines: {node: '>=10'} - pg@8.20.0: - resolution: {integrity: sha512-ldhMxz2r8fl/6QkXnBD3CR9/xg694oT6DZQ2s6c/RI28OjtSOpxnPrUCGOBJ46RCUxcWdx3p6kw/xnDHjKvaRA==} + pg@8.22.0: + resolution: {integrity: sha512-8wih1vVIBMxoUM2oB4soJsD9tDnDpLv4OXBJ+EJzFsvycD+lfyIreC2gGHq78f8jbLLt+bvlPTFdFZfJkOuzAA==} engines: {node: '>= 16.0.0'} peerDependencies: pg-native: '>=3.0.1' @@ -5668,8 +5509,8 @@ packages: resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==} engines: {node: '>=8.6'} - picomatch@4.0.4: - resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} + picomatch@4.0.5: + resolution: {integrity: sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==} engines: {node: '>=12'} pify@4.0.1: @@ -5688,14 +5529,18 @@ packages: resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} engines: {node: '>=8'} - playwright-core@1.60.0: - resolution: {integrity: sha512-9bW6zvX/m0lEbgTKJ6YppOKx8H3VOPBMOCFh2irXFOT4BbHgrx5hPjwJYLT40Lu+4qtD36qKc/Hn56StUW57IA==} - engines: {node: '>=18'} + pkg-pr-new@0.0.78: + resolution: {integrity: sha512-sBfGGCiCLmJArx99Z/QmNN3jmkqRYhp1TV8Y1IOpSpXfOljqFcEjDyyc4HCVy+nbrmlkw9qXRq/9/L4ahF/d4w==} hasBin: true - playwright@1.60.0: - resolution: {integrity: sha512-hheHdokM8cdqCb0lcE3s+zT4t4W+vvjpGxsZlDnikarzx8tSzMebh3UiFtgqwFwnTnjYQcsyMF8ei2mCO/tpeA==} - engines: {node: '>=18'} + playwright-core@1.62.0: + resolution: {integrity: sha512-nsNRyq0r2zsG8AcRHWknc9QRA5XCueC7gWMrs+Gx2tlZn9hcl8zudfh00lhJPY1DE7NmZ6bDsT9g2yey8mXljA==} + engines: {node: '>=20'} + hasBin: true + + playwright@1.62.0: + resolution: {integrity: sha512-Z14dG305dgaLu6foB1TXQagFiW8JfSUIUaUuPaKQ6NtBPKF1P/qXcqfh6c6K/icPqdy37JmjbiBXf6JNg6Sylw==} + engines: {node: '>=20'} hasBin: true pluralize@8.0.0: @@ -5712,12 +5557,8 @@ packages: peerDependencies: postcss: ^8.2.9 - postcss@8.5.14: - resolution: {integrity: sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==} - engines: {node: ^10 || ^12 || >=14} - - postcss@8.5.15: - resolution: {integrity: sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==} + postcss@8.5.24: + resolution: {integrity: sha512-8RyVklq0owXUTa4xlpzu4l9AaVKIdQvAcOHZWaMh98HgySsUtxRVf/chRe3dsSLqb6i40BzGRzEUddRaI+9TSw==} engines: {node: ^10 || ^12 || >=14} postgres-array@2.0.0: @@ -5759,24 +5600,13 @@ packages: resolution: {integrity: sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==} engines: {node: '>=20'} - prebuild-install@7.1.3: - resolution: {integrity: sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==} - engines: {node: '>=10'} - deprecated: No longer maintained. Please contact the author of the relevant native addon; alternatives are available. - hasBin: true - - precinct@12.2.0: - resolution: {integrity: sha512-NFBMuwIfaJ4SocE9YXPU/n4AcNSoFMVFjP72nvl3cx69j/ke61/hPOWFREVxLkFhhEGnA8ZuVfTqJBa+PK3b5w==} + precinct@12.3.2: + resolution: {integrity: sha512-JbJevI1K80z8e/WIyDt/4vUN/4qcfBSKKqOjJA4mosPPPb7zODKRJQV7YN7apVWN3k58nZYm/vEsLgEGYmnxwg==} engines: {node: '>=18'} hasBin: true - prettier@2.8.8: - resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==} - engines: {node: '>=10.13.0'} - hasBin: true - - prettier@3.8.1: - resolution: {integrity: sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==} + prettier@3.9.6: + resolution: {integrity: sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g==} engines: {node: '>=14'} hasBin: true @@ -5812,12 +5642,8 @@ packages: resolution: {integrity: sha512-WPn+h9RGEExOKdu4bsF4HksG/uzd3cFq3MFtq8PsFeExPse5Ha/VOjQNyHhjboBFwGXGev6muJYTSPAOkROq2g==} engines: {node: '>=18'} - protobufjs@7.5.4: - resolution: {integrity: sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==} - engines: {node: '>=12.0.0'} - - protobufjs@8.0.1: - resolution: {integrity: sha512-NWWCCscLjs+cOKF/s/XVNFRW7Yih0fdH+9brffR5NZCy8k42yRdl5KlWKMVXuI1vfCoy4o1z80XR/W/QUb3V3w==} + protobufjs@7.6.5: + resolution: {integrity: sha512-/FPD0nUc9jH6rfFjji9IBqOz4pcSE3CsT1m7Ep6Mdb0LxSUMj8hgl6GomOvZzpNpAqqGaXA0P3VSrZLFzIhQrw==} engines: {node: '>=12.0.0'} pump@3.0.4: @@ -5827,14 +5653,8 @@ packages: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} - pure-rand@8.4.0: - resolution: {integrity: sha512-IoM8YF/jY0hiugFo/wOWqfmarlE6J0wc6fDK1PhftMk7MGhVZl88sZimmqBBFomLOCSmcCCpsfj7wXASCpvK9A==} - - quansync@0.2.11: - resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==} - - queue-microtask@1.2.3: - resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + pure-rand@8.4.1: + resolution: {integrity: sha512-c58R2+SPFcSIPXoU834QN/KPDDOSd8sXcSrqf6e83Me6Rrp1EYkxukkjXMVrKvKaADs1SOyNkWdfvLf6zY8qLQ==} queue@6.0.2: resolution: {integrity: sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==} @@ -5857,13 +5677,13 @@ packages: react-devtools-core@6.1.5: resolution: {integrity: sha512-ePrwPfxAnB+7hgnEr8vpKxL9cmnp7F322t8oqcPshbIQQhDKgFDW4tjhF2wjVbdXF9O/nyuy3sQWd9JGpiLPvA==} - react-dom@19.2.6: - resolution: {integrity: sha512-0prMI+hvBbPjsWnxDLxlCGyM8PN6UuWjEUCYmZhO67xIV9Xasa/r/vDnq+Xyq4Lo27g8QSbO5YzARu0D1Sps3g==} + react-dom@19.2.7: + resolution: {integrity: sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==} peerDependencies: - react: ^19.2.6 + react: ^19.2.7 - react-error-boundary@6.1.1: - resolution: {integrity: sha512-BrYwPOdXi5mqkk5lw+Uvt0ThHx32rCt3BkukS4X23A2AIWDPSGX6iaWTc0y9TU/mHDA/6qOSGel+B2ERkOvD1w==} + react-error-boundary@6.1.2: + resolution: {integrity: sha512-3DpCr5HVdZ0caUjYE/kIHBEJN0mNP3ZCgf16c48uJ5TbWjorKVp+YG8W3XqlJ7vJAVNw6wNIImyPXmFydwmyng==} peerDependencies: react: ^18.0.0 || ^19.0.0 @@ -5888,14 +5708,10 @@ packages: resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==} engines: {node: '>=0.10.0'} - react@19.2.6: - resolution: {integrity: sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==} + react@19.2.7: + resolution: {integrity: sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==} engines: {node: '>=0.10.0'} - read-yaml-file@1.1.0: - resolution: {integrity: sha512-VIMnQi/Z4HT2Fxuwg5KrY174U1VdUIASQVWXXyqtNRtxSr9IYkn1rsI6Tb6HsrHCmB7gVpNwX6JxPTHcH6IoTA==} - engines: {node: '>=6'} - readable-stream@2.3.8: resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} @@ -5910,12 +5726,12 @@ packages: readdir-glob@1.1.3: resolution: {integrity: sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==} - readdirp@3.6.0: - resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} - engines: {node: '>=8.10.0'} + readdirp@5.0.0: + resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==} + engines: {node: '>= 20.19.0'} - recast@0.23.11: - resolution: {integrity: sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA==} + recast@0.23.12: + resolution: {integrity: sha512-dEWRjcINDu/F4l2dYx57ugBtD7HV9KXESyxhzw/MqWLeglJrsjJKqACPyUPg+6AF8mIgm+Zi0dZ3ACoIg+QtpA==} engines: {node: '>= 4'} redent@3.0.0: @@ -5990,26 +5806,22 @@ packages: resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==} engines: {node: '>= 4'} - reusify@1.1.0: - resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} - engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - rimraf@3.0.2: resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true - rolldown@1.0.0: - resolution: {integrity: sha512-yD986aXDESFGS95spT1LAv0jssywP4npMEjmMHyN2/5+eE8qQJUype2AaKkRiLgBgyD0LFlubwAht7VmY8rGoA==} + rolldown@1.1.5: + resolution: {integrity: sha512-t9z29cJjXf/vxQ8dyhCSpt6H6aSwHTk8cT5I3iy6SMXuFpk5mB6PL6XfC8PCwrPTx93udwKUm9HRteAlTGBLiA==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true - rollup-plugin-bundle-stats@4.22.1: - resolution: {integrity: sha512-cxqJPygLifK56ADuXuhSfpII3I65x5XRK2Dq+FqOihzJ9xxQOGHpmT0MQkSSlIyDJV2tHp/os3rhnxrJvrwcwQ==} + rollup-plugin-bundle-stats@4.22.2: + resolution: {integrity: sha512-5IWrsg/XOSrE2dkImaa2Pf6e1G9O0D9BKIjEfq+Oz6uGMMxo8krMByNNpz1GAgollxfBoPPSH2ct3ggGa3Cahw==} engines: {node: '>= 16.0'} peerDependencies: rolldown: ^1.0.0-beta.0 - rollup: ^3.0.0 || ^4.0.0 + rollup: ^4.60.4 vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 peerDependenciesMeta: rolldown: @@ -6026,8 +5838,8 @@ packages: esbuild: '>=0.18.0' rollup: ^1.20.0 || ^2.0.0 || ^3.0.0 || ^4.0.0 - rollup-plugin-stats@2.1.1: - resolution: {integrity: sha512-YaFOomhAAQHSoMQphemXSEU0/TRVfvzQainb369Ve4Wg8gZ2Nrir2lISxqaGVdYhxQI0ufoDiRciFnZsQygROw==} + rollup-plugin-stats@2.1.2: + resolution: {integrity: sha512-xxeHvcpRheH8MungJfBIx71DSwU27nMVzUbImJt7uAt6jywRuNWwWYHpX8/jEg2Xg8LMazRuvXXpb2n6cn02Bw==} engines: {node: '>=18'} peerDependencies: rolldown: ^1.0.0-beta.0 @@ -6054,8 +5866,8 @@ packages: rollup: optional: true - rollup-plugin-webpack-stats@3.1.1: - resolution: {integrity: sha512-QfQ2n6qX4h4Bya4ugQsJHkPF13x8vVb7u0nZsdUslFAbxDC4/BfAM6QXbZETJSlH5ajS+KE1RWaNxnVWmXrDGg==} + rollup-plugin-webpack-stats@3.1.2: + resolution: {integrity: sha512-giqyWCbYawAJpcu3jjYnh3nlUYhf/CEEOLCOmFWSyNzPepfAQ9vBIg9U2mwey5OKF6smtj0tBeD+SHUvmVv9jw==} engines: {node: '>=18'} peerDependencies: rolldown: ^1.0.0-beta.0 @@ -6069,13 +5881,8 @@ packages: vite: optional: true - rollup@4.60.3: - resolution: {integrity: sha512-pAQK9HalE84QSm4Po3EmWIZPd3FnjkShVkiMlz1iligWYkWQ7wHYd1PF/T7QZ5TVSD6uSTon5gBVMSM4JfBV+A==} - engines: {node: '>=18.0.0', npm: '>=8.0.0'} - hasBin: true - - rollup@4.61.1: - resolution: {integrity: sha512-I4KW6iuRpuu2uHBLraZ1wNZe0DP7lnRha+VJ9tNaYVaVgKhW0aI3h4RYnoRPeql0flHm/Co55b7snEDcOfOJrA==} + rollup@4.62.3: + resolution: {integrity: sha512-Gu0c0iH9FzgX1L1t7ByIbbS3Vmdz+6KHm/EsqmmC71gUQ82yvZRkTK6XzrFObSka91WUVdynqp6nsfilzr5k6Q==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -6083,9 +5890,6 @@ packages: resolution: {integrity: sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==} engines: {node: '>=18'} - run-parallel@1.2.0: - resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} - safe-buffer@5.1.2: resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} @@ -6095,8 +5899,8 @@ packages: safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - sass-lookup@6.1.0: - resolution: {integrity: sha512-Zx+lVyoWqXZxHuYWlTA17Z5sczJ6braNT2C7rmClw+c4E7r/n911Zwss3h1uHI9reR5AgHZyNHF7c2+VIp5AUA==} + sass-lookup@6.1.2: + resolution: {integrity: sha512-GjmndmKQBtlPil79RK72L7yc5kDXZPCQeH97bP8R8DcxtXQJO6vECExb3WP/m6+cxaV9h4ZxrSRvCkPG2v/VSw==} engines: {node: '>=18'} hasBin: true @@ -6115,13 +5919,8 @@ packages: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true - semver@7.8.0: - resolution: {integrity: sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==} - engines: {node: '>=10'} - hasBin: true - - semver@7.8.4: - resolution: {integrity: sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA==} + semver@7.8.5: + resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==} engines: {node: '>=10'} hasBin: true @@ -6133,21 +5932,21 @@ packages: resolution: {integrity: sha512-ghgmKt5o4Tly5yEG/UJp8qTd0AN7Xalw4XBtDEKP655B699qMEtra1WlXeE6WIvdEG481JvRxULKsInq/iNysw==} engines: {node: '>=0.10.0'} - serialize-javascript@7.0.5: - resolution: {integrity: sha512-F4LcB0UqUl1zErq+1nYEEzSHJnIwb3AF2XWB94b+afhrekOUijwooAYqFyRbjYkm2PAKBabx6oYv/xDxNi8IBw==} + serialize-javascript@7.0.7: + resolution: {integrity: sha512-YAy8Od6KV+uuwUuU50np8fGB/Aues6Y0nAhA9y/hId74PlKUcme4pXcBD46NWKr1Q4osN/iseZ17YqO1XfmI8g==} engines: {node: '>=20.0.0'} serialize-query-params@2.0.4: resolution: {integrity: sha512-y9WzzDj3BsGgKLCh0ugiinufS//YqOfao/yVJjkXA4VLuyNCfHOLU/cbulGPxs3aeCqhvROw7qPL04JSZnCo0w==} - seroval-plugins@1.5.2: - resolution: {integrity: sha512-qpY0Cl+fKYFn4GOf3cMiq6l72CpuVaawb6ILjubOQ+diJ54LfOWaSSPsaswN8DRPIPW4Yq+tE1k5aKd7ILyaFg==} + seroval-plugins@1.5.5: + resolution: {integrity: sha512-+BDhqYM6CEn3x09v44dpa9p6974FuUB2dxk+Ctn04k0cO1Zt6QODTXfmEZK0eBaTe/fJBvP4NMGuNJ+R8T+QMg==} engines: {node: '>=10'} peerDependencies: seroval: ^1.0 - seroval@1.5.2: - resolution: {integrity: sha512-xcRN39BdsnO9Tf+VzsE7b3JyTJASItIV1FVFewJKCFcW4s4haIKS3e6vj8PGB9qBwC7tnuOywQMdv5N4qkzi7Q==} + seroval@1.5.5: + resolution: {integrity: sha512-bSjOuPcwPKLSJNhr9+bZxA20nQxVle5J5MNsYRVE6cIg7KpRLXGupymePavu0jrxlPiPsr4xGZSB8yUY2sH2sw==} engines: {node: '>=10'} serve-static@1.16.3: @@ -6177,8 +5976,8 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} - shell-quote@1.8.4: - resolution: {integrity: sha512-VsC6n6vz1ihYYyZZwX7YZSF5l5x36ca17OC+a69h94YqB7X6XLwf+5MOgynYir2SLFUbl8gIYvBo8K8RoNQ6bQ==} + shell-quote@1.10.0: + resolution: {integrity: sha512-w1aiOKwKuRgtwAReIIj89puqg+I7GvX4IbLrvmhXbzQsj1+Zwi4VO3+fa6ZF91TWSjIxoEkKnMeHcLEODK5ZXA==} engines: {node: '>= 0.4'} should-equal@2.0.0: @@ -6209,30 +6008,27 @@ packages: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} - simple-concat@1.0.1: - resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} - - simple-get@4.0.1: - resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==} - sirv@3.0.2: resolution: {integrity: sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==} engines: {node: '>=18'} - slash@2.0.0: - resolution: {integrity: sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==} - engines: {node: '>=6'} + sisteransi@1.0.5: + resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} slash@3.0.0: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} - smob@1.6.1: - resolution: {integrity: sha512-KAkBqZl3c2GvNgNhcoyJae1aKldDW0LO279wF9bk1PnluRTETKBq0WyzRXxEhoQLk56yHaOY4JCBEKDuJIET5g==} + slash@5.1.0: + resolution: {integrity: sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==} + engines: {node: '>=14.16'} + + smob@1.6.2: + resolution: {integrity: sha512-RQsvleCbF8cVHEv+xuDGaA4pOizFqJ0GgjtMSRo6oP8pnN7WsigHgVGey6aILRBKv4W2YOMHLqbKdnB6hpB9fw==} engines: {node: '>=20.0.0'} - solid-js@1.9.12: - resolution: {integrity: sha512-QzKaSJq2/iDrWR1As6MHZQ8fQkdOBf8GReYb7L5iKwMGceg7HxDcaOHk0at66tNgn9U2U7dXo8ZZpLIAmGMzgw==} + solid-js@1.9.14: + resolution: {integrity: sha512-sAEXC0Kk0S1EDg+8ysEWJDbYhA3RRoEjwuySUGlKIemeo0I5YZfOyumNjNs9Sv3y2nmhD+0rW66ag2HsMuQiGQ==} source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} @@ -6253,9 +6049,6 @@ packages: resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} engines: {node: '>= 12'} - spawndamnit@3.0.1: - resolution: {integrity: sha512-MmnduQUuHCoFckZoWnXsTg7JaiLBJrKFj9UI2MbRPGaJeVpsLcVBu6P/IGZovziM/YBsellCmsprgNA+w0CzVg==} - split-ca@1.0.1: resolution: {integrity: sha512-Q5thBSxp5t8WPTTJQS59LrGqOZqOsrhDGDVm8azCqIBjSBd7nd9o2PM+mDulQQkh8h//4U6hFZnc/mul8t5pWQ==} @@ -6269,8 +6062,8 @@ packages: sprintf-js@1.1.3: resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==} - sql-escaper@1.3.3: - resolution: {integrity: sha512-BsTCV265VpTp8tm1wyIm1xqQCS+Q9NHx2Sr+WcnUrgLrQ6yiDIvHYJV5gHxsj1lMBy2zm5twLaZao8Jd+S8JJw==} + sql-escaper@1.4.0: + resolution: {integrity: sha512-Ti/Zx9J3aITMYUMFhCKbkplQjyi7Kk2SyYXp+rzrkyKZetIy19XbQtVZ+0ZR5aVm/178tIJ6+aVvBqXkH+Xs7w==} engines: {bun: '>=1.0.0', deno: '>=2.0.0', node: '>=12.0.0'} ssh-remote-port-forward@1.0.4: @@ -6305,14 +6098,14 @@ packages: resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} engines: {node: '>= 0.8'} - std-env@4.1.0: - resolution: {integrity: sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==} + std-env@4.2.0: + resolution: {integrity: sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==} stream-chain@2.2.5: resolution: {integrity: sha512-1TJmBx6aSWqZ4tx7aTpBDXK0/e2hhcNSTV8+CbFJtDjbb+I1mZ8lHit0Grw9GRT+6JbIrrDd8esncgBi8aBXGA==} - stream-chain@3.6.2: - resolution: {integrity: sha512-MRUXdH/Fcv2I95WSkPthvpxGsuKeBpr50UXcP5Z3ncWafYxrWelMTVI3G7vJFABCs1ipoTPCN3J13ZcLpymrtA==} + stream-chain@3.6.3: + resolution: {integrity: sha512-JZuELdHUuiZL4Olcr4EllGUvj9VKEaDkGHA6QAP5SruD0bgrr8TwtNXwRfH+fCncysEII7HhWll1+aOwvHYyRw==} stream-json@1.9.1: resolution: {integrity: sha512-uWkjJ+2Nt/LO9Z/JyKZbMusL8Dkh97uUBTv3AJQ74y07lVahLY4eEFsPsE97pxYBwr8nnjMAIch5eqI0gPShyw==} @@ -6320,8 +6113,8 @@ packages: stream-to-array@2.3.0: resolution: {integrity: sha512-UsZtOYEn4tWU2RGLOXr/o/xjRBftZRlG3dEWoaHr8j4GuypJ3isitGbVyjQKAuMu+xbiop8q224TjiZWc4XTZA==} - streamx@2.25.0: - resolution: {integrity: sha512-0nQuG6jf1w+wddNEEXCF4nTg3LtufWINB5eFEN+5TNZW7KWJp6x87+JFL43vaAUPyCfH1wID+mNVyW6OHtFamg==} + streamx@2.28.0: + resolution: {integrity: sha512-1Yowhzjf0ivGMrTIkY9hav5TxobO9qIVqUE41fiCGMGgc3CLlf4MY+9AHmZqBWgDTue0fY9zWjYFVyf6Diuobw==} string-width@4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} @@ -6373,8 +6166,8 @@ packages: resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} engines: {node: '>=0.10.0'} - stylus-lookup@6.1.0: - resolution: {integrity: sha512-5QSwgxAzXPMN+yugy61C60PhoANdItfdjSEZR8siFwz7yL9jTmV0UBKDCfn3K8GkGB4g0Y9py7vTCX8rFu4/pQ==} + stylus-lookup@6.1.2: + resolution: {integrity: sha512-O+Q/SJ8s1X2aMLh4213fQ9X/bND9M3dhSsyTRe+O1OXPcewGLiYmAtKCrnP7FDvDBaXB2ZHPkCt3zi4cJXBlCQ==} engines: {node: '>=18'} hasBin: true @@ -6405,25 +6198,25 @@ packages: symbol-tree@3.2.4: resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} - tapable@2.3.0: - resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==} + tapable@2.3.3: + resolution: {integrity: sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==} engines: {node: '>=6'} - tar-fs@2.1.4: - resolution: {integrity: sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==} + tar-fs@2.1.5: + resolution: {integrity: sha512-OboTd8mmMhZDNPV+UjQcK9yKAatXu2aJ+r1w4im1Otd4M4fl2hwvdoXUxIYHFTHWK/3y3FarBP70v3vwmGlOxw==} - tar-fs@3.1.2: - resolution: {integrity: sha512-QGxxTxxyleAdyM3kpFs14ymbYmNFrfY+pHj7Z8FgtbZ7w2//VAgLMac7sT6nRpIHjppXO2AwwEOg0bPFVRcmXw==} + tar-fs@3.1.3: + resolution: {integrity: sha512-/hU4AXnIdZu+Gvl1pk0oI5f5HxWsCJRtY2aFaJdk9VvyL48DWU6iU5WAIPG+wIi1YvWA6eTJvIviP/tMAZZNwQ==} tar-stream@2.2.0: resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} engines: {node: '>=6'} - tar-stream@3.1.8: - resolution: {integrity: sha512-U6QpVRyCGHva435KoNWy9PRoi2IFYCgtEhq9nmrPPpbRacPs9IH4aJ3gbrFC8dPcXvdSZ4XXfXT5Fshbp2MtlQ==} + tar-stream@3.2.0: + resolution: {integrity: sha512-ojzvCvVaNp6aOTFmG7jaRD0meowIAuPc3cMMhSgKiVWws1GyHbGd/xvnyuRKcKlMpt3qvxx6r0hreCNITP9hIg==} - tar@7.5.15: - resolution: {integrity: sha512-dzGK0boVlC4W5QFuQN1EFSl3bIDYsk7Tj40U6eIBnK2k/8ml7TZ5agbI5j5+qnoVcAA+rNtBml8SEiLxZpNqRQ==} + tar@7.5.19: + resolution: {integrity: sha512-4LeEWl96twnS2Q7Bz4MGqgazLqO+hJN63GZxXoIqh1T3VweYD997gbU1ItNsQafqqXTXd5WFyFdReLtwvRBNiw==} engines: {node: '>=18'} tedious@19.2.1: @@ -6433,17 +6226,8 @@ packages: teex@1.0.1: resolution: {integrity: sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==} - term-size@2.2.1: - resolution: {integrity: sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==} - engines: {node: '>=8'} - - terser@5.47.1: - resolution: {integrity: sha512-tPbLXTI6ohPASb/1YViL428oEHu6/qv1OxqYnfaonVCFHqx4+wCd95pHrQWsL5X4pl90CTyW9piSAsS2L0VoMw==} - engines: {node: '>=10'} - hasBin: true - - terser@5.48.0: - resolution: {integrity: sha512-J/9An6vs9Us6wKRriSFXBWdRZapREHqFzdNUKk0pmu804EMR6dr6winwo7e5JDxN4xahxQsuysyYFwlwj4XN/Q==} + terser@5.49.0: + resolution: {integrity: sha512-SNiDnXyHSrxVcIOtVbULzcTmniUiwcV7Nwdyj1twVubeTmbjoa8p69KKDpfkdoOavuM4/GRm1+ykI8qqnavHoA==} engines: {node: '>=10'} hasBin: true @@ -6451,8 +6235,12 @@ packages: resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} engines: {node: '>=8'} - testcontainers@11.14.0: - resolution: {integrity: sha512-r9pniwv/iwzyHaI7gwAvAm4Y+IvjJg3vBWdjrUCaDMc2AXIr4jKbq7jJO18Mw2ybs73pZy1Aj7p/4RVBGMRWjg==} + testcontainers@12.0.4: + resolution: {integrity: sha512-QIR/8xF1+F/26cIM+9B4yyxNTbKJxAv3hygZyhPRgZ8Q2AhlPZjDdpXRuk16V37X4bgJRI3hXFhoEICMBA7Adg==} + + testcontainers@12.1.0: + resolution: {integrity: sha512-YjDLqIITuhGLMnM10yhg3oV6lIG5IMpz1R1DPBZoOOks83q7i7IVpeSWRTiyl7roozjiyLmwIoLK/KY8OnZmIA==} + engines: {node: '>= 22.22'} text-decoder@1.2.7: resolution: {integrity: sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==} @@ -6469,39 +6257,31 @@ packages: tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} - tinybench@6.0.1: - resolution: {integrity: sha512-cMdWsxmysdg8mNWf1pujiWl3TW0cU6m8QuNw55QlnP3I6N96Grb0wnu5N0syHIu3LbiVZCNqlfWzWDq84HZphA==} + tinybench@6.0.2: + resolution: {integrity: sha512-FlHoQpcFvCzeXK5kVPvV7IVgW/hs/B36QWTz876iSdeJguBDfdTSRQmYmaHX+fQNt4hp+gEFB2XXw+8hT4/y8A==} engines: {node: '>=20.0.0'} - tinyexec@1.1.1: - resolution: {integrity: sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg==} + tinyexec@1.2.4: + resolution: {integrity: sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==} engines: {node: '>=18'} - tinyglobby@0.2.16: - resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==} - engines: {node: '>=12.0.0'} - tinyglobby@0.2.17: resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} engines: {node: '>=12.0.0'} - tinyrainbow@2.0.0: - resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} - engines: {node: '>=14.0.0'} - tinyrainbow@3.1.0: resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} engines: {node: '>=14.0.0'} - tldts-core@7.0.30: - resolution: {integrity: sha512-uiHN8PIB1VmWyS98eZYja4xzlYqeFZVjb4OuYlJQnZAuJhMw4PbKQOKgHKhBdJR3FE/t5mUQ1Kd80++B+qhD1Q==} + tldts-core@7.4.9: + resolution: {integrity: sha512-DxKfPBI52p2msTEu7MPhdpdDTBhhVQg1a/8PjQckeyAvO13eMYElX545grIp6nnTGIMZlRvFZPvFhvI/WIz2Vg==} - tldts@7.0.30: - resolution: {integrity: sha512-ELrFxuqsDdHUwoh0XxDbxuLD3Wnz49Z57IFvTtvWy1hJdcMZjXLIuonjilCiWHlT2GbE4Wlv1wKVTzDFnXH1aw==} + tldts@7.4.9: + resolution: {integrity: sha512-3kZ8wQQ/k5DrChD4X4FVvr2D7E5uoRgAqkPyLpSCGUvqOvqu+JEdr3mwMUaVWb+vMHZaKhF5fp2PBigKsui7hA==} hasBin: true - tmp@0.2.5: - resolution: {integrity: sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==} + tmp@0.2.7: + resolution: {integrity: sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw==} engines: {node: '>=14.14'} tmpl@1.0.5: @@ -6519,16 +6299,12 @@ packages: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} engines: {node: '>=0.6'} - toml@4.1.1: - resolution: {integrity: sha512-EBJnVBr3dTXdA89WVFoAIPUqkBjxPMwRqsfuo1r240tKFHXv3zgca4+NJib/h6TyvGF7vOawz0jGuryJCdNHrw==} - engines: {node: '>=20'} - totalist@3.0.1: resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} engines: {node: '>=6'} - tough-cookie@6.0.1: - resolution: {integrity: sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==} + tough-cookie@6.0.2: + resolution: {integrity: sha512-exgYmnmL/sJpR3upZfXG5PoatXQii55xAiXGXzY+sROLZ/Y+SLcp9PgJNI9Vz37HpQ74WvDcLT8eqm+kV3FzrA==} engines: {node: '>=16'} tr46@0.0.3: @@ -6557,8 +6333,8 @@ packages: tree-sitter@0.21.1: resolution: {integrity: sha512-7dxoA6kYvtgWw80265MyqJlkRl4yawIjO7S5MigytjELkX43fV2WsAXzsNfO7sBpPPCF5Gp0+XzHk0DwLCq3xQ==} - ts-api-utils@2.4.0: - resolution: {integrity: sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==} + ts-api-utils@2.5.0: + resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==} engines: {node: '>=18.12'} peerDependencies: typescript: '>=4.8.4' @@ -6567,10 +6343,8 @@ packages: resolution: {integrity: sha512-XyLVuhBVvdJTJr2FJJV2L1pc4MwSjMhcunRVgDE9k4wbb2ee7ORYnPewxMWUav12vxyfUM686MSGsqnVRIInuw==} engines: {node: '>=18'} - ts-patch@4.0.1: - resolution: {integrity: sha512-pzXS6vZlsBwq3U+CUiPJfCAh0MJGco1j1C8bWsLgvtzDZLGZ7II1Usjtwm09mZ8Uoma+sg6sO3BlWHd6apCUww==} - engines: {node: '>=22.15.0'} - hasBin: true + ts-morph@27.0.2: + resolution: {integrity: sha512-fhUhgeljcrdZ+9DZND1De1029PrE+cMkIP7ooqkLRTrRLTqcki2AstsyJm0vRNbTbVCNJ0idGlbBrfqc7/nA8w==} tsconfck@3.1.6: resolution: {integrity: sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==} @@ -6589,9 +6363,9 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - tstyche@7.1.0: - resolution: {integrity: sha512-Ar03KIab85QcPzMRIBrLGqpaKYsnNTbEhhC+wsEGrerSaaLfBDNFDcLtoxEMJHAO/ssjeApAFkjRKUjfxUx4EQ==} - engines: {node: '>=22.12'} + tstyche@7.2.2: + resolution: {integrity: sha512-pd0CZjwjzxCzRiBAH/SEx4IaMS6ti/KA8cWc22Bx1Wa5FF30JeFQa/zAfXpjWkrJNmFqaOJrTaZDF+ji3P2TmA==} + engines: {node: '>=22'} hasBin: true peerDependencies: typescript: '>=5.4' @@ -6599,14 +6373,11 @@ packages: typescript: optional: true - tsx@4.21.0: - resolution: {integrity: sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==} + tsx@4.23.1: + resolution: {integrity: sha512-GQHnkIfxyx1wYCOS/wonik5MVRZU9hi1TEZmzGZSCJB1y9YgoZ8H6itNE/u4suE+yLmOzuE4E5S4TZ/ZX2wcWQ==} engines: {node: '>=18.0.0'} hasBin: true - tunnel-agent@0.6.0: - resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} - tweetnacl@0.14.5: resolution: {integrity: sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==} @@ -6635,30 +6406,28 @@ packages: engines: {node: '>=14.17'} hasBin: true + typescript@7.0.2: + resolution: {integrity: sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==} + engines: {node: '>=16.20.0'} + hasBin: true + undici-types@5.26.5: resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} - undici-types@7.21.0: - resolution: {integrity: sha512-w9IMgQrz4O0YN1LtB7K5P63vhlIOvC7opSmouCJ+ZywlPAlO9gIkJ+otk6LvGpAs2wg4econaCz3TvQ9xPoyuQ==} - - undici-types@7.24.6: - resolution: {integrity: sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==} - - undici@7.24.8: - resolution: {integrity: sha512-6KQ/+QxK49Z/p3HO6E5ZCZWNnCasyZLa5ExaVYyvPxUwKtbCPMKELJOqh7EqOle0t9cH/7d2TaaTRRa6Nhs4YQ==} - engines: {node: '>=20.18.1'} + undici-types@8.3.0: + resolution: {integrity: sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==} - undici@7.25.0: - resolution: {integrity: sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ==} + undici@7.28.0: + resolution: {integrity: sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA==} engines: {node: '>=20.18.1'} - undici@8.2.0: - resolution: {integrity: sha512-Z+4Hx9GE26Lh9Upwfnc8C7SsrpBPGaM/Gm6kMFtiG7c+5IvQKlXi/t+9x9DrrCh29cww5TSP9YdVaBcnLDs5fQ==} + undici@8.7.0: + resolution: {integrity: sha512-N7iQtfyLhIMOFgQubvmLV26svHpO0bqKnAiWotTQCVKCmWrcGbBotPuW1x+xwYZ2VHdSTVUfPQQnlEt1/LouTQ==} engines: {node: '>=22.19.0'} - universalify@0.1.2: - resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} - engines: {node: '>= 4.0.0'} + undici@8.9.0: + resolution: {integrity: sha512-aWZpUj7XoGonMClx4gdDRfgBjqeA+F473aDmROQQbM9n6PRfK/u1q/a0X4wMTgcHfT8H6fpbt98PFuDUwFg2YA==} + engines: {node: '>=22.19.0'} unpipe@1.0.0: resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} @@ -6684,82 +6453,32 @@ packages: resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} engines: {node: '>= 0.4.0'} - uuid@10.0.0: - resolution: {integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==} - deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028). - hasBin: true - - uuid@14.0.0: - resolution: {integrity: sha512-Qo+uWgilfSmAhXCMav1uYFynlQO7fMFiMVZsQqZRMIXp0O7rR7qjkj+cPvBHLgBqi960QCoo/PH2/6ZtVqKvrg==} + uuid@14.0.1: + resolution: {integrity: sha512-6ZxzVpzDXDa3bJWaHilVayA+BH/1zmxCJoVgvmqJnid/gPoKHxUrS/aC/T6LGQtNHT+XHG9fXPJB4d+IrU30Ew==} hasBin: true uuid@8.3.2: resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} - deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028). hasBin: true - valibot@1.4.0: - resolution: {integrity: sha512-iC/x7fVcSyOwlm/VSt7RlHnzNGLGvR9GnxdifUeWoCJo0q4ZZvrVkIHC6faTlkxG47I2Y4UrFquPuVHCrOnrLg==} + valibot@1.4.2: + resolution: {integrity: sha512-gjdCvJ6d3RyHAneqxMYMW9QMCwYMb3jpOO0IyHZV1bnRHFBHrX3VkIILt5XYR0WhwHiH7Mty8ovuPZ/O3gamrg==} peerDependencies: typescript: '>=5' - peerDependenciesMeta: - typescript: - optional: true - - vite-tsconfig-paths@6.1.1: - resolution: {integrity: sha512-2cihq7zliibCCZ8P9cKJrQBkfgdvcFkOOc3Y02o3GWUDLgqjWsZudaoiuOwO/gzTzy17cS5F7ZPo4bsnS4DGkg==} - peerDependencies: - vite: '*' - - vite@7.3.3: - resolution: {integrity: sha512-/4XH147Ui7OGTjg3HbdWe5arnZQSbfuRzdr9Ec7TQi5I7R+ir0Rlc9GIvD4v0XZurELqA035KVXJXpR61xhiTA==} - engines: {node: ^20.19.0 || >=22.12.0} - hasBin: true - peerDependencies: - '@types/node': ^20.19.0 || >=22.12.0 - jiti: '>=1.21.0' - less: ^4.0.0 - lightningcss: ^1.21.0 - sass: ^1.70.0 - sass-embedded: ^1.70.0 - stylus: '>=0.54.8' - sugarss: ^5.0.0 - terser: ^5.16.0 - tsx: ^4.8.1 - yaml: ^2.4.2 - peerDependenciesMeta: - '@types/node': - optional: true - jiti: - optional: true - less: - optional: true - lightningcss: - optional: true - sass: - optional: true - sass-embedded: - optional: true - stylus: - optional: true - sugarss: - optional: true - terser: - optional: true - tsx: - optional: true - yaml: + peerDependenciesMeta: + typescript: optional: true - vite@7.3.5: - resolution: {integrity: sha512-KuOaNhcnGFN2zIPGA7wRmzF+lJA1sea7rHq17aiJ++9lzY1WWG6Jpwqwe1KNbRVPIqHmr8GLYx7jbrQcN/7/ww==} + vite@8.1.5: + resolution: {integrity: sha512-7ULLwsCdYx/nRyrpiEwvqb5TFHrMVZyBt+rg/OAXT7rgj/z+DtTDyKFeLAdDkubDVDKD8jOsndmy7m55XcfUsw==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: '@types/node': ^20.19.0 || >=22.12.0 + '@vitejs/devtools': ^0.3.0 + esbuild: ^0.27.0 || ^0.28.0 jiti: '>=1.21.0' less: ^4.0.0 - lightningcss: ^1.21.0 sass: ^1.70.0 sass-embedded: ^1.70.0 stylus: '>=0.54.8' @@ -6770,12 +6489,14 @@ packages: peerDependenciesMeta: '@types/node': optional: true + '@vitejs/devtools': + optional: true + esbuild: + optional: true jiti: optional: true less: optional: true - lightningcss: - optional: true sass: optional: true sass-embedded: @@ -6791,25 +6512,25 @@ packages: yaml: optional: true - vitest-websocket-mock@0.5.0: - resolution: {integrity: sha512-vzBWeuF/kD/OCOFzB7WAclb7PxfI105qPkZtdOkPMwZdilBskQjJL4l319JtPtmeovDU7ZVhO3hTfGPjM4txQQ==} + vitest-websocket-mock@0.7.0: + resolution: {integrity: sha512-OrDWs/FiZl8MfcCjvxLri1Uq2NpFcqbQdFHuotmU5FgA9Z5PHRxCyHdmu/xSPgBzbxO9pz3ZdHtokFELCT7AOg==} peerDependencies: - vitest: '>=3' + vitest: '>=4' - vitest@4.1.4: - resolution: {integrity: sha512-tFuJqTxKb8AvfyqMfnavXdzfy3h3sWZRWwfluGbkeR7n0HUev+FmNgZ8SDrRBTVrVCjgH5cA21qGbCffMNtWvg==} + vitest@4.1.10: + resolution: {integrity: sha512-R9jUTe5S4Qb0HCd4TNqpC7oGcrMssMRGXLW80ubjWsW9VH5GF8y1Y0SFLY9AbqSk6nt0PnOx4H4WNJYZ13GUPw==} engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' '@opentelemetry/api': ^1.9.0 '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 - '@vitest/browser-playwright': 4.1.4 - '@vitest/browser-preview': 4.1.4 - '@vitest/browser-webdriverio': 4.1.4 - '@vitest/coverage-istanbul': 4.1.4 - '@vitest/coverage-v8': 4.1.4 - '@vitest/ui': 4.1.4 + '@vitest/browser-playwright': 4.1.10 + '@vitest/browser-preview': 4.1.10 + '@vitest/browser-webdriverio': 4.1.10 + '@vitest/coverage-istanbul': 4.1.10 + '@vitest/coverage-v8': 4.1.10 + '@vitest/ui': 4.1.10 happy-dom: '*' jsdom: '*' vite: ^6.0.0 || ^7.0.0 || ^8.0.0 @@ -6840,8 +6561,8 @@ packages: vlq@1.0.1: resolution: {integrity: sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w==} - vue@3.5.34: - resolution: {integrity: sha512-WdLBG9gm02OgJIG9axd5Hpx0TFLdzVgfG2evFFu8Rur5O/IoGc5cMjnjh3tPL6GnRGsYvUhBSKVPYVcxRKpMCA==} + vue@3.5.39: + resolution: {integrity: sha512-xmZCYabFGcirU8r0fTuvl/LICc1OU620rnqepaJDL/a141ZigkG7AyaxQLdqJ02ZRYzWe6YPaDHeQx7MfknQfA==} peerDependencies: typescript: '*' peerDependenciesMeta: @@ -6884,6 +6605,10 @@ packages: resolution: {integrity: sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + whatwg-url@17.1.0: + resolution: {integrity: sha512-3GeworPmc2ZfEEHP7lEbUfBX/L75wdEsi0rLNhXcXxnoN5jyq0SL5gCy06SGW2cyTIZdTvWIDQNQoza++vKeaw==} + engines: {node: ^22.14.0 || >=24.0.0} + whatwg-url@5.0.0: resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} @@ -6892,18 +6617,13 @@ packages: engines: {node: '>= 8'} hasBin: true - which@4.0.0: - resolution: {integrity: sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==} - engines: {node: ^16.13.0 || >=18.0.0} - hasBin: true - why-is-node-running@2.3.0: resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} engines: {node: '>=8'} hasBin: true - workerd@1.20260507.1: - resolution: {integrity: sha512-z7JhsFSe6+X1b5fUHaVpo15VM1IRMJiLofEkq8iKdCo+Veqc+FUg5lIsuz8NwePxuSKrXtO4ZQpGkQLbPVXFhg==} + workerd@1.20260706.1: + resolution: {integrity: sha512-0DOmgysJiPZRwtlg9Bh70aDnHuVech70fKVM4721RbdfewL29ODxn4TpgkusybLYrQAkgHkW53SUt2AdkBa+Og==} engines: {node: '>=16'} hasBin: true @@ -6930,8 +6650,8 @@ packages: resolution: {integrity: sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - ws@7.5.11: - resolution: {integrity: sha512-zS54Oen9bITtp7kp2XM3AydrCIq1D+HwJOuH+c+e4LfpL/lotP5osijd+UoMnxwAam1GN8R4KtLAyIrIcBNpiA==} + ws@7.5.13: + resolution: {integrity: sha512-rsKI6xDBFVf4r/x8XyChGK04QR/XHroxs/jUcoWvtEZM8TPU/X/uIY9B1CsSzYws9ZJb/6bbBu7dPhFW00CAoA==} engines: {node: '>=8.3.0'} peerDependencies: bufferutil: ^4.0.1 @@ -6942,20 +6662,8 @@ packages: utf-8-validate: optional: true - ws@8.18.0: - resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==} - engines: {node: '>=10.0.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: '>=5.0.2' - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - - ws@8.20.0: - resolution: {integrity: sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==} + ws@8.21.0: + resolution: {integrity: sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==} engines: {node: '>=10.0.0'} peerDependencies: bufferutil: ^4.0.1 @@ -6966,8 +6674,8 @@ packages: utf-8-validate: optional: true - ws@8.21.0: - resolution: {integrity: sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==} + ws@8.21.1: + resolution: {integrity: sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw==} engines: {node: '>=10.0.0'} peerDependencies: bufferutil: ^4.0.1 @@ -7032,6 +6740,10 @@ packages: resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} engines: {node: '>=12'} + yargs@17.7.3: + resolution: {integrity: sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==} + engines: {node: '>=12'} + yargs@18.0.0: resolution: {integrity: sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==} engines: {node: ^20.19.0 || ^22.12.0 || >=23} @@ -7051,33 +6763,22 @@ packages: snapshots: - '@adobe/css-tools@4.4.4': {} + '@adobe/css-tools@4.5.0': {} - '@ark/schema@0.56.0': + '@asamuzakjp/css-color@6.0.5': dependencies: - '@ark/util': 0.56.0 - - '@ark/util@0.56.0': {} - - '@asamuzakjp/css-color@5.1.11': - dependencies: - '@asamuzakjp/generational-cache': 1.0.1 - '@csstools/css-calc': 3.2.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) - '@csstools/css-color-parser': 4.1.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-calc': 3.3.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-color-parser': 4.1.10(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) '@csstools/css-tokenizer': 4.0.0 + lru-cache: 11.5.2 - '@asamuzakjp/dom-selector@7.1.1': + '@asamuzakjp/dom-selector@8.3.0': dependencies: - '@asamuzakjp/generational-cache': 1.0.1 - '@asamuzakjp/nwsapi': 2.3.9 bidi-js: 1.0.3 css-tree: 3.2.1 is-potential-custom-element-name: 1.0.1 - - '@asamuzakjp/generational-cache@1.0.1': {} - - '@asamuzakjp/nwsapi@2.3.9': {} + lru-cache: 11.5.2 '@azure-rest/core-client@2.6.0': dependencies: @@ -7223,23 +6924,19 @@ snapshots: jsonwebtoken: 9.0.3 uuid: 8.3.2 - '@babel/cli@7.28.6(@babel/core@7.29.0)': + '@babel/cli@8.0.4(@babel/core@8.0.1)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 8.0.1 '@jridgewell/trace-mapping': 0.3.31 - commander: 6.2.1 + chokidar: 5.0.0 + commander: 14.0.3 convert-source-map: 2.0.0 - fs-readdir-recursive: 1.1.0 - glob: 7.2.3 - make-dir: 2.1.0 - slash: 2.0.0 - optionalDependencies: - '@nicolo-ribaudo/chokidar-2': 2.1.8-no-fsevents.3 - chokidar: 3.6.0 + glob: 13.0.6 + slash: 5.1.0 '@babel/code-frame@7.29.0': dependencies: - '@babel/helper-validator-identifier': 7.28.5 + '@babel/helper-validator-identifier': 7.29.7 js-tokens: 4.0.0 picocolors: 1.1.1 @@ -7249,29 +6946,14 @@ snapshots: js-tokens: 4.0.0 picocolors: 1.1.1 - '@babel/compat-data@7.29.0': {} + '@babel/code-frame@8.0.0': + dependencies: + '@babel/helper-validator-identifier': 8.0.4 + js-tokens: 10.0.0 '@babel/compat-data@7.29.7': {} - '@babel/core@7.29.0': - dependencies: - '@babel/code-frame': 7.29.0 - '@babel/generator': 7.29.1 - '@babel/helper-compilation-targets': 7.28.6 - '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) - '@babel/helpers': 7.28.6 - '@babel/parser': 7.29.3 - '@babel/template': 7.28.6 - '@babel/traverse': 7.29.0 - '@babel/types': 7.29.0 - '@jridgewell/remapping': 2.3.5 - convert-source-map: 2.0.0 - debug: 4.4.3(supports-color@10.2.2) - gensync: 1.0.0-beta.2 - json5: 2.2.3 - semver: 6.3.1 - transitivePeerDependencies: - - supports-color + '@babel/compat-data@8.0.0': {} '@babel/core@7.29.7': dependencies: @@ -7293,13 +6975,24 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/generator@7.29.1': - dependencies: - '@babel/parser': 7.29.3 - '@babel/types': 7.29.0 - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.31 - jsesc: 3.1.0 + '@babel/core@8.0.1': + dependencies: + '@babel/code-frame': 8.0.0 + '@babel/generator': 8.0.0 + '@babel/helper-compilation-targets': 8.0.0 + '@babel/helpers': 8.0.0 + '@babel/parser': 8.0.4 + '@babel/template': 8.0.0 + '@babel/traverse': 8.0.4 + '@babel/types': 8.0.4 + '@types/gensync': 1.0.5 + convert-source-map: 2.0.0 + empathic: 2.0.1 + gensync: 1.0.0-beta.2 + import-meta-resolve: 4.2.0 + json5: 2.2.3 + obug: 2.1.4 + semver: 7.8.5 '@babel/generator@7.29.7': dependencies: @@ -7309,54 +7002,56 @@ snapshots: '@jridgewell/trace-mapping': 0.3.31 jsesc: 3.1.0 - '@babel/helper-annotate-as-pure@7.27.3': + '@babel/generator@8.0.0': dependencies: - '@babel/types': 7.29.0 + '@babel/parser': 8.0.4 + '@babel/types': 8.0.4 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + '@types/jsesc': 2.5.1 + jsesc: 3.1.0 - '@babel/helper-compilation-targets@7.28.6': + '@babel/helper-annotate-as-pure@7.29.7': dependencies: - '@babel/compat-data': 7.29.0 - '@babel/helper-validator-option': 7.27.1 - browserslist: 4.28.1 - lru-cache: 5.1.1 - semver: 6.3.1 + '@babel/types': 7.29.7 '@babel/helper-compilation-targets@7.29.7': dependencies: '@babel/compat-data': 7.29.7 '@babel/helper-validator-option': 7.29.7 - browserslist: 4.28.2 + browserslist: 4.28.7 lru-cache: 5.1.1 semver: 6.3.1 - '@babel/helper-create-class-features-plugin@7.28.6(@babel/core@7.29.0)': + '@babel/helper-compilation-targets@8.0.0': + dependencies: + '@babel/compat-data': 8.0.0 + '@babel/helper-validator-option': 8.0.0 + browserslist: 4.28.7 + lru-cache: 11.5.2 + semver: 7.8.5 + + '@babel/helper-create-class-features-plugin@7.29.7(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-annotate-as-pure': 7.27.3 - '@babel/helper-member-expression-to-functions': 7.28.5 - '@babel/helper-optimise-call-expression': 7.27.1 - '@babel/helper-replace-supers': 7.28.6(@babel/core@7.29.0) - '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 - '@babel/traverse': 7.29.0 + '@babel/core': 7.29.7 + '@babel/helper-annotate-as-pure': 7.29.7 + '@babel/helper-member-expression-to-functions': 7.29.7 + '@babel/helper-optimise-call-expression': 7.29.7 + '@babel/helper-replace-supers': 7.29.7(@babel/core@7.29.7) + '@babel/helper-skip-transparent-expression-wrappers': 7.29.7 + '@babel/traverse': 7.29.7 semver: 6.3.1 transitivePeerDependencies: - supports-color - '@babel/helper-globals@7.28.0': {} - '@babel/helper-globals@7.29.7': {} - '@babel/helper-member-expression-to-functions@7.28.5': - dependencies: - '@babel/traverse': 7.29.0 - '@babel/types': 7.29.0 - transitivePeerDependencies: - - supports-color + '@babel/helper-globals@8.0.0': {} - '@babel/helper-module-imports@7.28.6': + '@babel/helper-member-expression-to-functions@7.29.7': dependencies: - '@babel/traverse': 7.29.0 - '@babel/types': 7.29.0 + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.7 transitivePeerDependencies: - supports-color @@ -7367,14 +7062,10 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/helper-module-transforms@7.28.6(@babel/core@7.29.0)': + '@babel/helper-module-imports@8.0.0': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-module-imports': 7.28.6 - '@babel/helper-validator-identifier': 7.28.5 - '@babel/traverse': 7.29.0 - transitivePeerDependencies: - - supports-color + '@babel/traverse': 8.0.4 + '@babel/types': 8.0.4 '@babel/helper-module-transforms@7.29.7(@babel/core@7.29.7)': dependencies: @@ -7385,230 +7076,245 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/helper-optimise-call-expression@7.27.1': + '@babel/helper-module-transforms@8.0.1(@babel/core@8.0.1)': dependencies: - '@babel/types': 7.29.0 + '@babel/core': 8.0.1 + '@babel/helper-module-imports': 8.0.0 + '@babel/helper-validator-identifier': 8.0.4 + '@babel/traverse': 8.0.4 - '@babel/helper-plugin-utils@7.28.6': {} + '@babel/helper-optimise-call-expression@7.29.7': + dependencies: + '@babel/types': 7.29.7 '@babel/helper-plugin-utils@7.29.7': {} - '@babel/helper-replace-supers@7.28.6(@babel/core@7.29.0)': + '@babel/helper-plugin-utils@8.0.1(@babel/core@8.0.1)': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-member-expression-to-functions': 7.28.5 - '@babel/helper-optimise-call-expression': 7.27.1 - '@babel/traverse': 7.29.0 + '@babel/core': 8.0.1 + + '@babel/helper-replace-supers@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-member-expression-to-functions': 7.29.7 + '@babel/helper-optimise-call-expression': 7.29.7 + '@babel/traverse': 7.29.7 transitivePeerDependencies: - supports-color - '@babel/helper-skip-transparent-expression-wrappers@7.27.1': + '@babel/helper-skip-transparent-expression-wrappers@7.29.7': dependencies: - '@babel/traverse': 7.29.0 - '@babel/types': 7.29.0 + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.7 transitivePeerDependencies: - supports-color - '@babel/helper-string-parser@7.27.1': {} - '@babel/helper-string-parser@7.29.7': {} - '@babel/helper-validator-identifier@7.28.5': {} + '@babel/helper-string-parser@8.0.0': {} '@babel/helper-validator-identifier@7.29.7': {} - '@babel/helper-validator-option@7.27.1': {} + '@babel/helper-validator-identifier@8.0.4': {} '@babel/helper-validator-option@7.29.7': {} - '@babel/helpers@7.28.6': - dependencies: - '@babel/template': 7.28.6 - '@babel/types': 7.29.0 + '@babel/helper-validator-option@8.0.0': {} '@babel/helpers@7.29.7': dependencies: '@babel/template': 7.29.7 '@babel/types': 7.29.7 - '@babel/parser@7.29.3': + '@babel/helpers@8.0.0': dependencies: - '@babel/types': 7.29.0 + '@babel/template': 8.0.0 + '@babel/types': 8.0.4 '@babel/parser@7.29.7': dependencies: '@babel/types': 7.29.7 - '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.29.7)': + '@babel/parser@8.0.4': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/types': 8.0.4 - '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.29.7)': + '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@8.0.1)': dependencies: - '@babel/core': 7.29.7 + '@babel/core': 8.0.1 '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.29.7)': + '@babel/plugin-syntax-bigint@7.8.3(@babel/core@8.0.1)': dependencies: - '@babel/core': 7.29.7 + '@babel/core': 8.0.1 '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.29.7)': + '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@8.0.1)': dependencies: - '@babel/core': 7.29.7 + '@babel/core': 8.0.1 '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-syntax-flow@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@8.0.1)': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/core': 8.0.1 + '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-syntax-import-attributes@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-syntax-flow@7.29.7(@babel/core@7.29.7)': dependencies: '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.29.7)': + '@babel/plugin-syntax-import-attributes@7.29.7(@babel/core@8.0.1)': dependencies: - '@babel/core': 7.29.7 + '@babel/core': 8.0.1 '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.29.7)': + '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@8.0.1)': dependencies: - '@babel/core': 7.29.7 + '@babel/core': 8.0.1 '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@8.0.1)': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/core': 8.0.1 + '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.29.7)': + '@babel/plugin-syntax-jsx@7.29.7(@babel/core@7.29.7)': dependencies: '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.29.7)': + '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@8.0.1)': dependencies: - '@babel/core': 7.29.7 + '@babel/core': 8.0.1 '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.29.7)': + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@8.0.1)': dependencies: - '@babel/core': 7.29.7 + '@babel/core': 8.0.1 '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.29.7)': + '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@8.0.1)': dependencies: - '@babel/core': 7.29.7 + '@babel/core': 8.0.1 '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.29.7)': + '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@8.0.1)': dependencies: - '@babel/core': 7.29.7 + '@babel/core': 8.0.1 '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.29.7)': + '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@8.0.1)': dependencies: - '@babel/core': 7.29.7 + '@babel/core': 8.0.1 '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.29.7)': + '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@8.0.1)': dependencies: - '@babel/core': 7.29.7 + '@babel/core': 8.0.1 '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.29.7)': + '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@8.0.1)': dependencies: - '@babel/core': 7.29.7 + '@babel/core': 8.0.1 '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-syntax-typescript@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@8.0.1)': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/core': 8.0.1 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-typescript@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-transform-class-properties@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-transform-class-properties@7.29.7(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0) - '@babel/helper-plugin-utils': 7.28.6 + '@babel/core': 7.29.7 + '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.29.7 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-export-namespace-from@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-transform-export-namespace-from@8.0.1(@babel/core@8.0.1)': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/core': 8.0.1 + '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.1) - '@babel/plugin-transform-flow-strip-types@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-transform-flow-strip-types@7.29.7(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-flow': 7.28.6(@babel/core@7.29.0) + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-syntax-flow': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-modules-commonjs@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-transform-modules-commonjs@7.29.7(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) - '@babel/helper-plugin-utils': 7.28.6 + '@babel/core': 7.29.7 + '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.29.7 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-nullish-coalescing-operator@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-transform-modules-commonjs@8.0.1(@babel/core@8.0.1)': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/core': 8.0.1 + '@babel/helper-module-transforms': 8.0.1(@babel/core@8.0.1) + '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.1) + + '@babel/plugin-transform-nullish-coalescing-operator@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-transform-optional-chaining@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-transform-optional-chaining@7.29.7(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 - '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-skip-transparent-expression-wrappers': 7.29.7 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-private-methods@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-transform-private-methods@7.29.7(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0) - '@babel/helper-plugin-utils': 7.28.6 + '@babel/core': 7.29.7 + '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.29.7 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-typescript@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-transform-typescript@7.29.7(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-annotate-as-pure': 7.27.3 - '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0) - '@babel/helper-plugin-utils': 7.28.6 - '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 - '@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.29.0) + '@babel/core': 7.29.7 + '@babel/helper-annotate-as-pure': 7.29.7 + '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-skip-transparent-expression-wrappers': 7.29.7 + '@babel/plugin-syntax-typescript': 7.29.7(@babel/core@7.29.7) transitivePeerDependencies: - supports-color - '@babel/preset-flow@7.27.1(@babel/core@7.29.0)': + '@babel/preset-flow@7.29.7(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 - '@babel/helper-validator-option': 7.27.1 - '@babel/plugin-transform-flow-strip-types': 7.27.1(@babel/core@7.29.0) + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-validator-option': 7.29.7 + '@babel/plugin-transform-flow-strip-types': 7.29.7(@babel/core@7.29.7) - '@babel/preset-typescript@7.28.5(@babel/core@7.29.0)': + '@babel/preset-typescript@7.29.7(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 - '@babel/helper-validator-option': 7.27.1 - '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-modules-commonjs': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-typescript': 7.28.6(@babel/core@7.29.0) + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-validator-option': 7.29.7 + '@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-modules-commonjs': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-typescript': 7.29.7(@babel/core@7.29.7) transitivePeerDependencies: - supports-color - '@babel/register@7.28.6(@babel/core@7.29.0)': + '@babel/register@7.29.7(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.7 clone-deep: 4.0.1 find-cache-dir: 2.1.0 make-dir: 2.1.0 @@ -7619,29 +7325,17 @@ snapshots: '@babel/runtime@7.29.7': {} - '@babel/template@7.28.6': - dependencies: - '@babel/code-frame': 7.29.0 - '@babel/parser': 7.29.3 - '@babel/types': 7.29.0 - '@babel/template@7.29.7': dependencies: '@babel/code-frame': 7.29.7 '@babel/parser': 7.29.7 '@babel/types': 7.29.7 - '@babel/traverse@7.29.0': + '@babel/template@8.0.0': dependencies: - '@babel/code-frame': 7.29.0 - '@babel/generator': 7.29.1 - '@babel/helper-globals': 7.28.0 - '@babel/parser': 7.29.3 - '@babel/template': 7.28.6 - '@babel/types': 7.29.0 - debug: 4.4.3(supports-color@10.2.2) - transitivePeerDependencies: - - supports-color + '@babel/code-frame': 8.0.0 + '@babel/parser': 8.0.4 + '@babel/types': 8.0.4 '@babel/traverse@7.29.7': dependencies: @@ -7655,16 +7349,26 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/types@7.29.0': + '@babel/traverse@8.0.4': dependencies: - '@babel/helper-string-parser': 7.27.1 - '@babel/helper-validator-identifier': 7.28.5 + '@babel/code-frame': 8.0.0 + '@babel/generator': 8.0.0 + '@babel/helper-globals': 8.0.0 + '@babel/parser': 8.0.4 + '@babel/template': 8.0.0 + '@babel/types': 8.0.4 + obug: 2.1.4 '@babel/types@7.29.7': dependencies: '@babel/helper-string-parser': 7.29.7 '@babel/helper-validator-identifier': 7.29.7 + '@babel/types@8.0.4': + dependencies: + '@babel/helper-string-parser': 8.0.0 + '@babel/helper-validator-identifier': 8.0.4 + '@balena/dockerignore@1.0.2': {} '@bcoe/v8-coverage@1.0.2': {} @@ -7675,235 +7379,198 @@ snapshots: dependencies: css-tree: 3.2.1 - '@bundle-stats/cli-utils@4.22.1(core-js@3.47.0)': + '@bundle-stats/cli-utils@4.22.2(core-js@3.47.0)': dependencies: - '@bundle-stats/html-templates': 4.22.1 - '@bundle-stats/plugin-webpack-filter': 4.22.1(core-js@3.47.0) - '@bundle-stats/utils': 4.22.1(core-js@3.47.0)(lodash@4.18.1) + '@bundle-stats/html-templates': 4.22.2 + '@bundle-stats/plugin-webpack-filter': 4.22.2(core-js@3.47.0) + '@bundle-stats/utils': 4.22.2(core-js@3.47.0)(lodash@4.18.1) find-cache-dir: 3.3.2 lodash: 4.18.1 - stream-chain: 3.6.2 + stream-chain: 3.6.3 stream-json: 1.9.1 transitivePeerDependencies: - core-js - '@bundle-stats/html-templates@4.22.1': {} + '@bundle-stats/html-templates@4.22.2': {} - '@bundle-stats/plugin-webpack-filter@4.22.1(core-js@3.47.0)': + '@bundle-stats/plugin-webpack-filter@4.22.2(core-js@3.47.0)': dependencies: core-js: 3.47.0 tslib: 2.8.1 - '@bundle-stats/plugin-webpack-validate@4.22.1': + '@bundle-stats/plugin-webpack-validate@4.22.2': dependencies: lodash: 4.18.1 superstruct: 2.0.2 tslib: 2.8.1 - '@bundle-stats/utils@4.22.1(core-js@3.47.0)(lodash@4.18.1)': + '@bundle-stats/utils@4.22.2(core-js@3.47.0)(lodash@4.18.1)': dependencies: - '@bundle-stats/plugin-webpack-filter': 4.22.1(core-js@3.47.0) - '@bundle-stats/plugin-webpack-validate': 4.22.1 + '@bundle-stats/plugin-webpack-filter': 4.22.2(core-js@3.47.0) + '@bundle-stats/plugin-webpack-validate': 4.22.2 core-js: 3.47.0 lodash: 4.18.1 serialize-query-params: 2.0.4 - '@changesets/apply-release-plan@7.1.1': - dependencies: - '@changesets/config': 3.1.4 - '@changesets/get-version-range-type': 0.4.0 - '@changesets/git': 3.0.4 - '@changesets/should-skip-package': 0.1.2 - '@changesets/types': 6.1.0 - '@manypkg/get-packages': 1.1.3 - detect-indent: 6.1.0 - fs-extra: 7.0.1 - lodash.startcase: 4.4.0 - outdent: 0.5.0 - prettier: 2.8.8 - resolve-from: 5.0.0 - semver: 7.8.0 - - '@changesets/assemble-release-plan@6.0.10(patch_hash=29cf7a343aefd3c59ad238a4cb067f0a1a0adbeed07b6d57d6f31d6c80a5e25e)': + '@changesets/apply-release-plan@8.0.0-next.10': dependencies: - '@changesets/errors': 0.2.0 - '@changesets/get-dependents-graph': 2.1.4 - '@changesets/should-skip-package': 0.1.2 - '@changesets/types': 6.1.0 - '@manypkg/get-packages': 1.1.3 - semver: 7.8.0 + '@changesets/config': 4.0.0-next.9 + '@changesets/format': 0.1.1 + '@changesets/git': 4.0.0-next.9 + '@changesets/should-skip-package': 1.0.0-next.9 + '@changesets/types': 7.0.0-next.9 + import-meta-resolve: 4.2.0 + jsonc-parser: 3.3.1 + semver: 7.8.5 - '@changesets/changelog-git@0.2.1': + '@changesets/assemble-release-plan@7.0.0-next.10': dependencies: - '@changesets/types': 6.1.0 + '@changesets/errors': 1.0.0-next.4 + '@changesets/get-dependents-graph': 3.0.0-next.9 + '@changesets/should-skip-package': 1.0.0-next.9 + '@changesets/types': 7.0.0-next.9 + semver: 7.8.5 - '@changesets/changelog-github@0.7.0': + '@changesets/changelog-git@1.0.0-next.9': dependencies: - '@changesets/get-github-info': 0.8.0(patch_hash=314478eeae7ab2776d847d3b2b7ea1cd4231c65907e85597dacd0bea56355bc9) - '@changesets/types': 6.1.0 - dotenv: 8.6.0 - transitivePeerDependencies: - - encoding - - '@changesets/cli@2.31.0(@types/node@25.7.0)': - dependencies: - '@changesets/apply-release-plan': 7.1.1 - '@changesets/assemble-release-plan': 6.0.10(patch_hash=29cf7a343aefd3c59ad238a4cb067f0a1a0adbeed07b6d57d6f31d6c80a5e25e) - '@changesets/changelog-git': 0.2.1 - '@changesets/config': 3.1.4 - '@changesets/errors': 0.2.0 - '@changesets/get-dependents-graph': 2.1.4 - '@changesets/get-release-plan': 4.0.16 - '@changesets/git': 3.0.4 - '@changesets/logger': 0.1.1 - '@changesets/pre': 2.0.2 - '@changesets/read': 0.6.7 - '@changesets/should-skip-package': 0.1.2 - '@changesets/types': 6.1.0 - '@changesets/write': 0.4.0 - '@inquirer/external-editor': 1.0.3(@types/node@25.7.0) - '@manypkg/get-packages': 1.1.3 - ansi-colors: 4.1.3 - enquirer: 2.4.1 - fs-extra: 7.0.1 - mri: 1.2.0 - package-manager-detector: 0.2.11 - picocolors: 1.1.1 - resolve-from: 5.0.0 - semver: 7.8.0 - spawndamnit: 3.0.1 - term-size: 2.2.1 - transitivePeerDependencies: - - '@types/node' + '@changesets/types': 7.0.0-next.9 - '@changesets/config@3.1.4': + '@changesets/changelog-github@1.0.0-next.9': dependencies: - '@changesets/errors': 0.2.0 - '@changesets/get-dependents-graph': 2.1.4 - '@changesets/logger': 0.1.1 - '@changesets/should-skip-package': 0.1.2 - '@changesets/types': 6.1.0 - '@manypkg/get-packages': 1.1.3 - fs-extra: 7.0.1 - micromatch: 4.0.8 + '@changesets/get-github-info': 1.0.0-next.4(patch_hash=a5b1907668397f36aab989954aa78993792f578b83e41a164ab7ecd573ac9166) + '@changesets/types': 7.0.0-next.9 - '@changesets/errors@0.2.0': + '@changesets/cli@3.0.0-next.11': dependencies: - extendable-error: 0.1.7 + '@changesets/apply-release-plan': 8.0.0-next.10 + '@changesets/assemble-release-plan': 7.0.0-next.10 + '@changesets/changelog-git': 1.0.0-next.9 + '@changesets/config': 4.0.0-next.9 + '@changesets/errors': 1.0.0-next.4 + '@changesets/get-dependents-graph': 3.0.0-next.9 + '@changesets/git': 4.0.0-next.9 + '@changesets/pre': 3.0.0-next.9 + '@changesets/read': 1.0.0-next.10(patch_hash=00298a7e3295e97aab2f89a36af496eef21b576642bd2eac88ae13ceca78cccb) + '@changesets/should-skip-package': 1.0.0-next.9 + '@changesets/types': 7.0.0-next.9 + '@changesets/write': 1.0.0-next.9 + '@clack/prompts': 1.7.0 + '@manypkg/get-packages': 3.1.0 + '@pnpm/deps.graph-sequencer': 1100.0.1 + cac: 7.0.0 + import-meta-resolve: 4.2.0 + launch-editor: 2.14.1 + package-manager-detector: 1.8.0 + semver: 7.8.5 + tinyexec: 1.2.4 - '@changesets/get-dependents-graph@2.1.4': + '@changesets/config@4.0.0-next.9': dependencies: - '@changesets/types': 6.1.0 - '@manypkg/get-packages': 1.1.3 - picocolors: 1.1.1 - semver: 7.8.0 + '@changesets/get-dependents-graph': 3.0.0-next.9 + '@changesets/should-skip-package': 1.0.0-next.9 + '@changesets/types': 7.0.0-next.9 + '@manypkg/get-packages': 3.1.0 + picomatch: 4.0.5 - '@changesets/get-github-info@0.8.0(patch_hash=314478eeae7ab2776d847d3b2b7ea1cd4231c65907e85597dacd0bea56355bc9)': - dependencies: - dataloader: 1.4.0 - node-fetch: 2.7.0 - transitivePeerDependencies: - - encoding + '@changesets/errors@1.0.0-next.4': {} - '@changesets/get-release-plan@4.0.16': + '@changesets/format@0.1.1': dependencies: - '@changesets/assemble-release-plan': 6.0.10(patch_hash=29cf7a343aefd3c59ad238a4cb067f0a1a0adbeed07b6d57d6f31d6c80a5e25e) - '@changesets/config': 3.1.4 - '@changesets/pre': 2.0.2 - '@changesets/read': 0.6.7 - '@changesets/types': 6.1.0 - '@manypkg/get-packages': 1.1.3 - - '@changesets/get-version-range-type@0.4.0': {} + package-manager-detector: 1.8.0 + tinyexec: 1.2.4 - '@changesets/git@3.0.4': + '@changesets/get-dependents-graph@3.0.0-next.9': dependencies: - '@changesets/errors': 0.2.0 - '@manypkg/get-packages': 1.1.3 - is-subdir: 1.2.0 - micromatch: 4.0.8 - spawndamnit: 3.0.1 + '@changesets/types': 7.0.0-next.9 + semver: 7.8.5 - '@changesets/logger@0.1.1': + '@changesets/get-github-info@1.0.0-next.4(patch_hash=a5b1907668397f36aab989954aa78993792f578b83e41a164ab7ecd573ac9166)': dependencies: - picocolors: 1.1.1 + dataloader: 2.2.3 - '@changesets/parse@0.4.3': + '@changesets/git@4.0.0-next.9': dependencies: - '@changesets/types': 6.1.0 - js-yaml: 4.1.1 + '@changesets/errors': 1.0.0-next.4 + '@changesets/types': 7.0.0-next.9 + '@manypkg/get-packages': 3.1.0 + picomatch: 4.0.5 + tinyexec: 1.2.4 - '@changesets/pre@2.0.2': + '@changesets/parse@1.0.0-next.10': dependencies: - '@changesets/errors': 0.2.0 - '@changesets/types': 6.1.0 - '@manypkg/get-packages': 1.1.3 - fs-extra: 7.0.1 + '@changesets/types': 7.0.0-next.9 + yaml: 2.9.0 - '@changesets/read@0.6.7': + '@changesets/pre@3.0.0-next.9': dependencies: - '@changesets/git': 3.0.4 - '@changesets/logger': 0.1.1 - '@changesets/parse': 0.4.3 - '@changesets/types': 6.1.0 - fs-extra: 7.0.1 - p-filter: 2.1.0 - picocolors: 1.1.1 + '@changesets/errors': 1.0.0-next.4 + '@changesets/types': 7.0.0-next.9 + '@manypkg/get-packages': 3.1.0 - '@changesets/should-skip-package@0.1.2': + '@changesets/read@1.0.0-next.10(patch_hash=00298a7e3295e97aab2f89a36af496eef21b576642bd2eac88ae13ceca78cccb)': dependencies: - '@changesets/types': 6.1.0 - '@manypkg/get-packages': 1.1.3 + '@changesets/git': 4.0.0-next.9 + '@changesets/parse': 1.0.0-next.10 + '@changesets/types': 7.0.0-next.9 - '@changesets/types@4.1.0': {} + '@changesets/should-skip-package@1.0.0-next.9': + dependencies: + '@changesets/types': 7.0.0-next.9 - '@changesets/types@6.1.0': {} + '@changesets/types@7.0.0-next.9': {} - '@changesets/write@0.4.0': + '@changesets/write@1.0.0-next.9': dependencies: - '@changesets/types': 6.1.0 - fs-extra: 7.0.1 - human-id: 4.1.3 - prettier: 2.8.8 + '@changesets/format': 0.1.1 + '@changesets/types': 7.0.0-next.9 + human-id: 4.2.0 - '@clickhouse/client-common@1.18.4': {} + '@clack/core@1.4.3': + dependencies: + fast-wrap-ansi: 0.2.2 + sisteransi: 1.0.5 - '@clickhouse/client@1.18.4': + '@clack/prompts@1.7.0': dependencies: - '@clickhouse/client-common': 1.18.4 + '@clack/core': 1.4.3 + fast-string-width: 3.0.2 + fast-wrap-ansi: 0.2.2 + sisteransi: 1.0.5 + + '@clickhouse/client@1.23.1': {} - '@cloudflare/workerd-darwin-64@1.20260507.1': + '@cloudflare/workerd-darwin-64@1.20260706.1': optional: true - '@cloudflare/workerd-darwin-arm64@1.20260507.1': + '@cloudflare/workerd-darwin-arm64@1.20260706.1': optional: true - '@cloudflare/workerd-linux-64@1.20260507.1': + '@cloudflare/workerd-linux-64@1.20260706.1': optional: true - '@cloudflare/workerd-linux-arm64@1.20260507.1': + '@cloudflare/workerd-linux-arm64@1.20260706.1': optional: true - '@cloudflare/workerd-windows-64@1.20260507.1': + '@cloudflare/workerd-windows-64@1.20260706.1': optional: true - '@cloudflare/workers-types@4.20260511.1': {} + '@cloudflare/workers-types@5.20260708.1': {} '@cspotcode/source-map-support@0.8.1': dependencies: '@jridgewell/trace-mapping': 0.3.9 - '@csstools/color-helpers@6.0.2': {} + '@csstools/color-helpers@6.1.0': {} - '@csstools/css-calc@3.2.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': + '@csstools/css-calc@3.3.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': dependencies: '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) '@csstools/css-tokenizer': 4.0.0 - '@csstools/css-color-parser@4.1.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': + '@csstools/css-color-parser@4.1.10(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': dependencies: - '@csstools/color-helpers': 6.0.2 - '@csstools/css-calc': 3.2.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/color-helpers': 6.1.0 + '@csstools/css-calc': 3.3.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) '@csstools/css-tokenizer': 4.0.0 @@ -7911,48 +7578,62 @@ snapshots: dependencies: '@csstools/css-tokenizer': 4.0.0 - '@csstools/css-syntax-patches-for-csstree@1.1.3(css-tree@3.2.1)': + '@csstools/css-syntax-patches-for-csstree@1.1.7(css-tree@3.2.1)': optionalDependencies: css-tree: 3.2.1 '@csstools/css-tokenizer@4.0.0': {} - '@dependents/detective-less@5.0.1': + '@dependents/detective-less@5.0.3': dependencies: gonzales-pe: 4.3.0 - node-source-walk: 7.0.1 + node-source-walk: 7.0.2 + + '@discoveryjs/json-ext@1.1.0': {} + + '@dprint/android-arm64@0.55.2': + optional: true + + '@dprint/android-x64@0.55.2': + optional: true + + '@dprint/darwin-arm64@0.55.2': + optional: true + + '@dprint/darwin-x64@0.55.2': + optional: true - '@dprint/darwin-arm64@0.54.0': + '@dprint/linux-arm64-glibc@0.55.2': optional: true - '@dprint/darwin-x64@0.54.0': + '@dprint/linux-arm64-musl@0.55.2': optional: true - '@dprint/linux-arm64-glibc@0.54.0': + '@dprint/linux-loong64-glibc@0.55.2': optional: true - '@dprint/linux-arm64-musl@0.54.0': + '@dprint/linux-loong64-musl@0.55.2': optional: true - '@dprint/linux-loong64-glibc@0.54.0': + '@dprint/linux-ppc64-glibc@0.55.2': optional: true - '@dprint/linux-loong64-musl@0.54.0': + '@dprint/linux-ppc64-musl@0.55.2': optional: true - '@dprint/linux-riscv64-glibc@0.54.0': + '@dprint/linux-riscv64-glibc@0.55.2': optional: true - '@dprint/linux-x64-glibc@0.54.0': + '@dprint/linux-x64-glibc@0.55.2': optional: true - '@dprint/linux-x64-musl@0.54.0': + '@dprint/linux-x64-musl@0.55.2': optional: true - '@dprint/win32-arm64@0.54.0': + '@dprint/win32-arm64@0.55.2': optional: true - '@dprint/win32-x64@0.54.0': + '@dprint/win32-x64@0.55.2': optional: true '@edge-runtime/primitives@6.0.0': @@ -7963,18 +7644,6 @@ snapshots: '@edge-runtime/primitives': 6.0.0 optional: true - '@effect/docgen@https://pkg.pr.new/Effect-TS/docgen/@effect/docgen@e57e5f5(tsx@4.21.0)(typescript@6.0.3)': - dependencies: - '@babel/code-frame': 7.29.0 - '@effect/markdown-toc': 0.1.0 - doctrine: 3.0.0 - glob: 11.1.0 - prettier: 3.8.1 - tsx: 4.21.0 - typescript: 6.0.3 - - '@effect/language-service@0.85.1': {} - '@effect/markdown-toc@0.1.0': dependencies: concat-stream: 1.6.2 @@ -7990,237 +7659,164 @@ snapshots: repeat-string: 1.6.1 strip-color: 0.1.0 - '@effect/tsgo-darwin-arm64@0.7.2': + '@effect/tsgo-darwin-arm64@0.21.0': optional: true - '@effect/tsgo-darwin-x64@0.7.2': + '@effect/tsgo-darwin-x64@0.21.0': optional: true - '@effect/tsgo-linux-arm64@0.7.2': + '@effect/tsgo-linux-arm64@0.21.0': optional: true - '@effect/tsgo-linux-arm@0.7.2': + '@effect/tsgo-linux-arm@0.21.0': optional: true - '@effect/tsgo-linux-x64@0.7.2': + '@effect/tsgo-linux-x64@0.21.0': optional: true - '@effect/tsgo-win32-arm64@0.7.2': + '@effect/tsgo-win32-arm64@0.21.0': optional: true - '@effect/tsgo-win32-x64@0.7.2': + '@effect/tsgo-win32-x64@0.21.0': optional: true - '@effect/tsgo@0.7.2': + '@effect/tsgo@0.21.0': optionalDependencies: - '@effect/tsgo-darwin-arm64': 0.7.2 - '@effect/tsgo-darwin-x64': 0.7.2 - '@effect/tsgo-linux-arm': 0.7.2 - '@effect/tsgo-linux-arm64': 0.7.2 - '@effect/tsgo-linux-x64': 0.7.2 - '@effect/tsgo-win32-arm64': 0.7.2 - '@effect/tsgo-win32-x64': 0.7.2 + '@effect/tsgo-darwin-arm64': 0.21.0 + '@effect/tsgo-darwin-x64': 0.21.0 + '@effect/tsgo-linux-arm': 0.21.0 + '@effect/tsgo-linux-arm64': 0.21.0 + '@effect/tsgo-linux-x64': 0.21.0 + '@effect/tsgo-win32-arm64': 0.21.0 + '@effect/tsgo-win32-x64': 0.21.0 '@effect/wa-sqlite@0.2.1': {} - '@electric-sql/pglite@0.4.5': {} + '@electric-sql/pglite@0.5.4': {} - '@emnapi/core@1.10.0': + '@emnapi/core@1.11.1': dependencies: - '@emnapi/wasi-threads': 1.2.1 + '@emnapi/wasi-threads': 1.2.2 tslib: 2.8.1 optional: true - '@emnapi/runtime@1.10.0': + '@emnapi/runtime@1.11.1': dependencies: tslib: 2.8.1 optional: true - '@emnapi/wasi-threads@1.2.1': + '@emnapi/runtime@1.11.3': dependencies: tslib: 2.8.1 optional: true - '@esbuild/aix-ppc64@0.27.2': - optional: true - - '@esbuild/aix-ppc64@0.27.7': - optional: true - - '@esbuild/android-arm64@0.27.2': - optional: true - - '@esbuild/android-arm64@0.27.7': - optional: true - - '@esbuild/android-arm@0.27.2': - optional: true - - '@esbuild/android-arm@0.27.7': - optional: true - - '@esbuild/android-x64@0.27.2': - optional: true - - '@esbuild/android-x64@0.27.7': - optional: true - - '@esbuild/darwin-arm64@0.27.2': - optional: true - - '@esbuild/darwin-arm64@0.27.7': - optional: true - - '@esbuild/darwin-x64@0.27.2': - optional: true - - '@esbuild/darwin-x64@0.27.7': - optional: true - - '@esbuild/freebsd-arm64@0.27.2': - optional: true - - '@esbuild/freebsd-arm64@0.27.7': - optional: true - - '@esbuild/freebsd-x64@0.27.2': - optional: true - - '@esbuild/freebsd-x64@0.27.7': - optional: true - - '@esbuild/linux-arm64@0.27.2': - optional: true - - '@esbuild/linux-arm64@0.27.7': - optional: true - - '@esbuild/linux-arm@0.27.2': - optional: true - - '@esbuild/linux-arm@0.27.7': - optional: true - - '@esbuild/linux-ia32@0.27.2': - optional: true - - '@esbuild/linux-ia32@0.27.7': - optional: true - - '@esbuild/linux-loong64@0.27.2': - optional: true - - '@esbuild/linux-loong64@0.27.7': - optional: true - - '@esbuild/linux-mips64el@0.27.2': - optional: true - - '@esbuild/linux-mips64el@0.27.7': + '@emnapi/wasi-threads@1.2.2': + dependencies: + tslib: 2.8.1 optional: true - '@esbuild/linux-ppc64@0.27.2': + '@esbuild/aix-ppc64@0.28.1': optional: true - '@esbuild/linux-ppc64@0.27.7': + '@esbuild/android-arm64@0.28.1': optional: true - '@esbuild/linux-riscv64@0.27.2': + '@esbuild/android-arm@0.28.1': optional: true - '@esbuild/linux-riscv64@0.27.7': + '@esbuild/android-x64@0.28.1': optional: true - '@esbuild/linux-s390x@0.27.2': + '@esbuild/darwin-arm64@0.28.1': optional: true - '@esbuild/linux-s390x@0.27.7': + '@esbuild/darwin-x64@0.28.1': optional: true - '@esbuild/linux-x64@0.27.2': + '@esbuild/freebsd-arm64@0.28.1': optional: true - '@esbuild/linux-x64@0.27.7': + '@esbuild/freebsd-x64@0.28.1': optional: true - '@esbuild/netbsd-arm64@0.27.2': + '@esbuild/linux-arm64@0.28.1': optional: true - '@esbuild/netbsd-arm64@0.27.7': + '@esbuild/linux-arm@0.28.1': optional: true - '@esbuild/netbsd-x64@0.27.2': + '@esbuild/linux-ia32@0.28.1': optional: true - '@esbuild/netbsd-x64@0.27.7': + '@esbuild/linux-loong64@0.28.1': optional: true - '@esbuild/openbsd-arm64@0.27.2': + '@esbuild/linux-mips64el@0.28.1': optional: true - '@esbuild/openbsd-arm64@0.27.7': + '@esbuild/linux-ppc64@0.28.1': optional: true - '@esbuild/openbsd-x64@0.27.2': + '@esbuild/linux-riscv64@0.28.1': optional: true - '@esbuild/openbsd-x64@0.27.7': + '@esbuild/linux-s390x@0.28.1': optional: true - '@esbuild/openharmony-arm64@0.27.2': + '@esbuild/linux-x64@0.28.1': optional: true - '@esbuild/openharmony-arm64@0.27.7': + '@esbuild/netbsd-arm64@0.28.1': optional: true - '@esbuild/sunos-x64@0.27.2': + '@esbuild/netbsd-x64@0.28.1': optional: true - '@esbuild/sunos-x64@0.27.7': + '@esbuild/openbsd-arm64@0.28.1': optional: true - '@esbuild/win32-arm64@0.27.2': + '@esbuild/openbsd-x64@0.28.1': optional: true - '@esbuild/win32-arm64@0.27.7': + '@esbuild/openharmony-arm64@0.28.1': optional: true - '@esbuild/win32-ia32@0.27.2': + '@esbuild/sunos-x64@0.28.1': optional: true - '@esbuild/win32-ia32@0.27.7': + '@esbuild/win32-arm64@0.28.1': optional: true - '@esbuild/win32-x64@0.27.2': + '@esbuild/win32-ia32@0.28.1': optional: true - '@esbuild/win32-x64@0.27.7': + '@esbuild/win32-x64@0.28.1': optional: true - '@exodus/bytes@1.15.0': {} + '@exodus/bytes@1.15.1': {} '@exodus/schemasafe@1.3.0': {} - '@faker-js/faker@10.4.0': {} + '@faker-js/faker@10.5.0': {} - '@grpc/grpc-js@1.14.3': + '@grpc/grpc-js@1.14.4': dependencies: - '@grpc/proto-loader': 0.8.0 + '@grpc/proto-loader': 0.8.1 '@js-sdsl/ordered-map': 4.4.2 '@grpc/proto-loader@0.7.15': dependencies: lodash.camelcase: 4.3.0 long: 5.3.2 - protobufjs: 7.5.4 - yargs: 17.7.2 + protobufjs: 7.6.5 + yargs: 17.7.3 - '@grpc/proto-loader@0.8.0': + '@grpc/proto-loader@0.8.1': dependencies: lodash.camelcase: 4.3.0 long: 5.3.2 - protobufjs: 7.5.4 - yargs: 17.7.2 + protobufjs: 7.6.5 + yargs: 17.7.3 '@img/colour@1.1.0': {} @@ -8306,7 +7902,7 @@ snapshots: '@img/sharp-wasm32@0.34.5': dependencies: - '@emnapi/runtime': 1.10.0 + '@emnapi/runtime': 1.11.3 optional: true '@img/sharp-win32-arm64@0.34.5': @@ -8318,13 +7914,6 @@ snapshots: '@img/sharp-win32-x64@0.34.5': optional: true - '@inquirer/external-editor@1.0.3(@types/node@25.7.0)': - dependencies: - chardet: 2.1.1 - iconv-lite: 0.7.2 - optionalDependencies: - '@types/node': 25.7.0 - '@ioredis/commands@1.4.0': {} '@isaacs/cliui@8.0.2': @@ -8347,7 +7936,7 @@ snapshots: camelcase: 5.3.1 find-up: 4.1.0 get-package-type: 0.1.0 - js-yaml: 3.14.2 + js-yaml: 3.15.0 resolve-from: 5.0.0 '@istanbuljs/schema@0.1.6': {} @@ -8360,21 +7949,21 @@ snapshots: dependencies: '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 25.9.3 + '@types/node': 26.1.2 jest-mock: 29.7.0 '@jest/fake-timers@29.7.0': dependencies: '@jest/types': 29.6.3 '@sinonjs/fake-timers': 10.3.0 - '@types/node': 25.9.3 + '@types/node': 26.1.2 jest-message-util: 29.7.0 jest-mock: 29.7.0 jest-util: 29.7.0 '@jest/schemas@29.6.3': dependencies: - '@sinclair/typebox': 0.27.10 + '@sinclair/typebox': 0.27.12 '@jest/transform@29.7.0': dependencies: @@ -8401,7 +7990,7 @@ snapshots: '@jest/schemas': 29.6.3 '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports': 3.0.4 - '@types/node': 25.9.3 + '@types/node': 26.1.2 '@types/yargs': 17.0.35 chalk: 4.1.2 @@ -8442,26 +8031,74 @@ snapshots: dependencies: jsbi: 4.3.2 + '@jsr/db__redis@0.41.2': + dependencies: + '@jsr/std__async': 1.5.0 + '@jsr/std__bytes': 1.0.6 + '@jsr/std__collections': 1.3.0 + '@jsr/std__io': 0.224.5 + '@jsr/std__random': 0.1.0 + cluster-key-slot: 1.1.0 + + '@jsr/std__assert@1.0.19': + dependencies: + '@jsr/std__internal': 1.0.14 + + '@jsr/std__async@1.5.0': + dependencies: + '@jsr/std__data-structures': 1.1.1 + + '@jsr/std__bytes@1.0.6': {} + + '@jsr/std__collections@1.3.0': {} + + '@jsr/std__data-structures@1.1.1': + dependencies: + '@jsr/std__assert': 1.0.19 + + '@jsr/std__fs@1.0.24': + dependencies: + '@jsr/std__internal': 1.0.14 + '@jsr/std__path': 1.1.6 + + '@jsr/std__internal@1.0.14': {} + + '@jsr/std__io@0.224.5': + dependencies: + '@jsr/std__bytes': 1.0.6 + + '@jsr/std__media-types@1.1.0': {} + + '@jsr/std__path@1.1.6': + dependencies: + '@jsr/std__internal': 1.0.14 + + '@jsr/std__random@0.1.0': {} + + '@jsr/std__streams@1.1.1': + dependencies: + '@jsr/std__bytes': 1.0.6 + '@kwsites/file-exists@1.1.1': dependencies: debug: 4.4.3(supports-color@10.2.2) transitivePeerDependencies: - supports-color - '@libsql/client@0.17.3': + '@libsql/client@0.17.4': dependencies: - '@libsql/core': 0.17.3 + '@libsql/core': 0.17.4 '@libsql/hrana-client': 0.10.0 - js-base64: 3.7.8 + js-base64: 3.8.0 libsql: 0.5.29 promise-limit: 2.7.0 transitivePeerDependencies: - bufferutil - utf-8-validate - '@libsql/core@0.17.3': + '@libsql/core@0.17.4': dependencies: - js-base64: 3.7.8 + js-base64: 3.8.0 '@libsql/darwin-arm64@0.5.29': optional: true @@ -8472,7 +8109,7 @@ snapshots: '@libsql/hrana-client@0.10.0': dependencies: '@libsql/isomorphic-ws': 0.1.5 - js-base64: 3.7.8 + js-base64: 3.8.0 transitivePeerDependencies: - bufferutil - utf-8-validate @@ -8480,7 +8117,7 @@ snapshots: '@libsql/isomorphic-ws@0.1.5': dependencies: '@types/ws': 8.18.1 - ws: 8.20.0 + ws: 8.21.1 transitivePeerDependencies: - bufferutil - utf-8-validate @@ -8506,199 +8143,226 @@ snapshots: '@libsql/win32-x64-msvc@0.5.29': optional: true - '@manypkg/find-root@1.1.0': + '@manypkg/find-root@3.1.0': dependencies: - '@babel/runtime': 7.29.2 - '@types/node': 12.20.55 - find-up: 4.1.0 - fs-extra: 8.1.0 + '@manypkg/tools': 2.1.2 - '@manypkg/get-packages@1.1.3': + '@manypkg/get-packages@3.1.0': dependencies: - '@babel/runtime': 7.29.2 - '@changesets/types': 4.1.0 - '@manypkg/find-root': 1.1.0 - fs-extra: 8.1.0 - globby: 11.1.0 - read-yaml-file: 1.1.0 + '@manypkg/find-root': 3.1.0 + '@manypkg/tools': 2.1.2 + + '@manypkg/tools@2.1.2': + dependencies: + jju: 1.4.0 + tinyglobby: 0.2.17 + yaml: 2.9.0 - '@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3': + '@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.4': optional: true - '@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.3': + '@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.4': optional: true - '@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.3': + '@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.4': optional: true - '@msgpackr-extract/msgpackr-extract-linux-arm@3.0.3': + '@msgpackr-extract/msgpackr-extract-linux-arm@3.0.4': optional: true - '@msgpackr-extract/msgpackr-extract-linux-x64@3.0.3': + '@msgpackr-extract/msgpackr-extract-linux-x64@3.0.4': optional: true - '@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3': + '@msgpackr-extract/msgpackr-extract-win32-x64@3.0.4': optional: true - '@napi-rs/wasm-runtime@1.1.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': + '@napi-rs/wasm-runtime@1.2.0(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)': dependencies: - '@emnapi/core': 1.10.0 - '@emnapi/runtime': 1.10.0 - '@tybys/wasm-util': 0.10.2 + '@emnapi/core': 1.11.1 + '@emnapi/runtime': 1.11.1 + '@tybys/wasm-util': 0.10.3 optional: true '@neon-rs/load@0.0.4': {} - '@nicolo-ribaudo/chokidar-2@2.1.8-no-fsevents.3': - optional: true - - '@nodelib/fs.scandir@2.1.5': + '@op-engineering/op-sqlite@17.1.2(react-native@0.83.0(@babel/core@8.0.1)(@types/react@19.2.17)(react@19.2.7))(react@19.2.7)': dependencies: - '@nodelib/fs.stat': 2.0.5 - run-parallel: 1.2.0 + react: 19.2.7 + react-native: 0.83.0(@babel/core@8.0.1)(@types/react@19.2.17)(react@19.2.7) - '@nodelib/fs.stat@2.0.5': {} - - '@nodelib/fs.walk@1.2.8': + '@opentelemetry/api-logs@0.220.0': dependencies: - '@nodelib/fs.scandir': 2.1.5 - fastq: 1.20.1 + '@opentelemetry/api': 1.9.1 - '@op-engineering/op-sqlite@15.2.12(react-native@0.83.0(@babel/core@7.29.7)(@types/react@19.2.14)(react@19.2.6))(react@19.2.6)': - dependencies: - react: 19.2.6 - react-native: 0.83.0(@babel/core@7.29.7)(@types/react@19.2.14)(react@19.2.6) + '@opentelemetry/api@1.9.1': {} - '@opentelemetry/api-logs@0.217.0': + '@opentelemetry/context-async-hooks@2.9.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/api@1.9.1': {} - - '@opentelemetry/context-async-hooks@2.7.1(@opentelemetry/api@1.9.1)': + '@opentelemetry/core@2.9.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 + '@opentelemetry/semantic-conventions': 1.42.0 - '@opentelemetry/core@2.7.1(@opentelemetry/api@1.9.1)': + '@opentelemetry/exporter-metrics-otlp-http@0.220.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/semantic-conventions': 1.40.0 + '@opentelemetry/core': 2.9.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.220.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.220.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.9.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-metrics': 2.9.0(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-metrics-otlp-http@0.217.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/exporter-prometheus@0.220.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-exporter-base': 0.217.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-transformer': 0.217.0(@opentelemetry/api@1.9.1) - '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-metrics': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/core': 2.9.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.9.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-metrics': 2.9.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.42.0 - '@opentelemetry/exporter-prometheus@0.217.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/exporter-trace-otlp-http@0.220.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-metrics': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/semantic-conventions': 1.40.0 + '@opentelemetry/core': 2.9.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.220.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.220.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.9.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace': 2.9.0(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-trace-otlp-http@0.217.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/otlp-exporter-base@0.220.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-exporter-base': 0.217.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-transformer': 0.217.0(@opentelemetry/api@1.9.1) - '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/core': 2.9.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.220.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-exporter-base@0.217.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/otlp-transformer@0.220.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-transformer': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/api-logs': 0.220.0 + '@opentelemetry/core': 2.9.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.9.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-logs': 0.220.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-metrics': 2.9.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace': 2.9.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-transformer@0.217.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/resources@2.9.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/api-logs': 0.217.0 - '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-logs': 0.217.0(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-metrics': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.1) - protobufjs: 8.0.1 + '@opentelemetry/core': 2.9.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.42.0 - '@opentelemetry/resources@2.7.1(@opentelemetry/api@1.9.1)': + '@opentelemetry/sdk-logs@0.220.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/semantic-conventions': 1.40.0 + '@opentelemetry/api-logs': 0.220.0 + '@opentelemetry/core': 2.9.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.9.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.42.0 - '@opentelemetry/sdk-logs@0.217.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/sdk-metrics@2.9.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/api-logs': 0.217.0 - '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/semantic-conventions': 1.40.0 + '@opentelemetry/core': 2.9.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.9.0(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-metrics@2.7.1(@opentelemetry/api@1.9.1)': + '@opentelemetry/sdk-trace-base@2.9.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/core': 2.9.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.9.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace': 2.9.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.42.0 - '@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1)': + '@opentelemetry/sdk-trace-node@2.9.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/semantic-conventions': 1.40.0 + '@opentelemetry/context-async-hooks': 2.9.0(@opentelemetry/api@1.9.1) + '@opentelemetry/core': 2.9.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-base': 2.9.0(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-trace-node@2.7.1(@opentelemetry/api@1.9.1)': + '@opentelemetry/sdk-trace-web@2.9.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/context-async-hooks': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/core': 2.9.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-base': 2.9.0(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-trace-web@2.7.1(@opentelemetry/api@1.9.1)': + '@opentelemetry/sdk-trace@2.9.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/core': 2.9.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.9.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.42.0 + + '@opentelemetry/semantic-conventions@1.42.0': {} + + '@oxc-project/types@0.139.0': {} + + '@oxlint/binding-android-arm-eabi@1.76.0': + optional: true + + '@oxlint/binding-android-arm64@1.76.0': + optional: true - '@opentelemetry/semantic-conventions@1.40.0': {} + '@oxlint/binding-darwin-arm64@1.76.0': + optional: true + + '@oxlint/binding-darwin-x64@1.76.0': + optional: true + + '@oxlint/binding-freebsd-x64@1.76.0': + optional: true + + '@oxlint/binding-linux-arm-gnueabihf@1.76.0': + optional: true + + '@oxlint/binding-linux-arm-musleabihf@1.76.0': + optional: true + + '@oxlint/binding-linux-arm64-gnu@1.76.0': + optional: true + + '@oxlint/binding-linux-arm64-musl@1.76.0': + optional: true + + '@oxlint/binding-linux-ppc64-gnu@1.76.0': + optional: true - '@oxc-project/types@0.129.0': + '@oxlint/binding-linux-riscv64-gnu@1.76.0': optional: true - '@oxlint/darwin-arm64@1.42.0': + '@oxlint/binding-linux-riscv64-musl@1.76.0': optional: true - '@oxlint/darwin-x64@1.42.0': + '@oxlint/binding-linux-s390x-gnu@1.76.0': optional: true - '@oxlint/linux-arm64-gnu@1.42.0': + '@oxlint/binding-linux-x64-gnu@1.76.0': optional: true - '@oxlint/linux-arm64-musl@1.42.0': + '@oxlint/binding-linux-x64-musl@1.76.0': optional: true - '@oxlint/linux-x64-gnu@1.42.0': + '@oxlint/binding-openharmony-arm64@1.76.0': optional: true - '@oxlint/linux-x64-musl@1.42.0': + '@oxlint/binding-win32-arm64-msvc@1.76.0': optional: true - '@oxlint/win32-arm64@1.42.0': + '@oxlint/binding-win32-ia32-msvc@1.76.0': optional: true - '@oxlint/win32-x64@1.42.0': + '@oxlint/binding-win32-x64-msvc@1.76.0': optional: true + '@oxlint/plugins@1.76.0': {} + '@pkgjs/parseargs@0.11.0': optional: true + '@pnpm/deps.graph-sequencer@1100.0.1': {} + '@polka/url@1.0.0-next.29': {} '@poppinss/colors@4.1.6': @@ -8719,34 +8383,31 @@ snapshots: '@protobufjs/codegen@2.0.5': {} - '@protobufjs/eventemitter@1.1.0': {} + '@protobufjs/eventemitter@1.1.1': {} - '@protobufjs/fetch@1.1.0': + '@protobufjs/fetch@1.1.1': dependencies: '@protobufjs/aspromise': 1.1.2 - '@protobufjs/inquire': 1.1.1 '@protobufjs/float@1.0.2': {} - '@protobufjs/inquire@1.1.1': {} - '@protobufjs/path@1.1.2': {} '@protobufjs/pool@1.1.0': {} - '@protobufjs/utf8@1.1.1': {} + '@protobufjs/utf8@1.1.2': {} '@react-native/assets-registry@0.83.0': {} - '@react-native/codegen@0.83.0(@babel/core@7.29.7)': + '@react-native/codegen@0.83.0(@babel/core@8.0.1)': dependencies: - '@babel/core': 7.29.7 + '@babel/core': 8.0.1 '@babel/parser': 7.29.7 glob: 7.2.3 hermes-parser: 0.32.0 invariant: 2.2.4 nullthrows: 1.1.1 - yargs: 17.7.2 + yargs: 17.7.3 '@react-native/community-cli-plugin@0.83.0': dependencies: @@ -8756,7 +8417,7 @@ snapshots: metro: 0.83.7 metro-config: 0.83.7 metro-core: 0.83.7 - semver: 7.8.4 + semver: 7.8.5 transitivePeerDependencies: - bufferutil - supports-color @@ -8782,7 +8443,7 @@ snapshots: nullthrows: 1.1.1 open: 7.4.2 serve-static: 1.16.3 - ws: 7.5.11 + ws: 7.5.13 transitivePeerDependencies: - bufferutil - supports-color @@ -8794,14 +8455,14 @@ snapshots: '@react-native/normalize-colors@0.83.0': {} - '@react-native/virtualized-lists@0.83.0(@types/react@19.2.14)(react-native@0.83.0(@babel/core@7.29.7)(@types/react@19.2.14)(react@19.2.6))(react@19.2.6)': + '@react-native/virtualized-lists@0.83.0(@types/react@19.2.17)(react-native@0.83.0(@babel/core@8.0.1)(@types/react@19.2.17)(react@19.2.7))(react@19.2.7)': dependencies: invariant: 2.2.4 nullthrows: 1.1.1 - react: 19.2.6 - react-native: 0.83.0(@babel/core@7.29.7)(@types/react@19.2.14)(react@19.2.6) + react: 19.2.7 + react-native: 0.83.0(@babel/core@8.0.1)(@types/react@19.2.17)(react@19.2.7) optionalDependencies: - '@types/react': 19.2.14 + '@types/react': 19.2.17 '@redocly/ajv@8.11.2': dependencies: @@ -8826,242 +8487,166 @@ snapshots: transitivePeerDependencies: - supports-color - '@rolldown/binding-android-arm64@1.0.0': + '@rolldown/binding-android-arm64@1.1.5': optional: true - '@rolldown/binding-darwin-arm64@1.0.0': + '@rolldown/binding-darwin-arm64@1.1.5': optional: true - '@rolldown/binding-darwin-x64@1.0.0': + '@rolldown/binding-darwin-x64@1.1.5': optional: true - '@rolldown/binding-freebsd-x64@1.0.0': + '@rolldown/binding-freebsd-x64@1.1.5': optional: true - '@rolldown/binding-linux-arm-gnueabihf@1.0.0': + '@rolldown/binding-linux-arm-gnueabihf@1.1.5': optional: true - '@rolldown/binding-linux-arm64-gnu@1.0.0': + '@rolldown/binding-linux-arm64-gnu@1.1.5': optional: true - '@rolldown/binding-linux-arm64-musl@1.0.0': + '@rolldown/binding-linux-arm64-musl@1.1.5': optional: true - '@rolldown/binding-linux-ppc64-gnu@1.0.0': + '@rolldown/binding-linux-ppc64-gnu@1.1.5': optional: true - '@rolldown/binding-linux-s390x-gnu@1.0.0': + '@rolldown/binding-linux-s390x-gnu@1.1.5': optional: true - '@rolldown/binding-linux-x64-gnu@1.0.0': + '@rolldown/binding-linux-x64-gnu@1.1.5': optional: true - '@rolldown/binding-linux-x64-musl@1.0.0': + '@rolldown/binding-linux-x64-musl@1.1.5': optional: true - '@rolldown/binding-openharmony-arm64@1.0.0': + '@rolldown/binding-openharmony-arm64@1.1.5': optional: true - '@rolldown/binding-wasm32-wasi@1.0.0': + '@rolldown/binding-wasm32-wasi@1.1.5': dependencies: - '@emnapi/core': 1.10.0 - '@emnapi/runtime': 1.10.0 - '@napi-rs/wasm-runtime': 1.1.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + '@emnapi/core': 1.11.1 + '@emnapi/runtime': 1.11.1 + '@napi-rs/wasm-runtime': 1.2.0(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1) optional: true - '@rolldown/binding-win32-arm64-msvc@1.0.0': + '@rolldown/binding-win32-arm64-msvc@1.1.5': optional: true - '@rolldown/binding-win32-x64-msvc@1.0.0': + '@rolldown/binding-win32-x64-msvc@1.1.5': optional: true - '@rolldown/pluginutils@1.0.0': - optional: true + '@rolldown/pluginutils@1.0.1': {} - '@rollup/plugin-node-resolve@16.0.3(rollup@4.60.3)': + '@rollup/plugin-node-resolve@16.0.3(rollup@4.62.3)': dependencies: - '@rollup/pluginutils': 5.3.0(rollup@4.60.3) + '@rollup/pluginutils': 5.4.0(rollup@4.62.3) '@types/resolve': 1.20.2 deepmerge: 4.3.1 is-module: 1.0.0 resolve: 1.22.12 optionalDependencies: - rollup: 4.60.3 + rollup: 4.62.3 - '@rollup/plugin-replace@6.0.3(rollup@4.60.3)': + '@rollup/plugin-replace@6.0.3(rollup@4.62.3)': dependencies: - '@rollup/pluginutils': 5.3.0(rollup@4.60.3) + '@rollup/pluginutils': 5.4.0(rollup@4.62.3) magic-string: 0.30.21 optionalDependencies: - rollup: 4.60.3 + rollup: 4.62.3 - '@rollup/plugin-terser@1.0.0(rollup@4.60.3)': + '@rollup/plugin-terser@1.0.0(rollup@4.62.3)': dependencies: - serialize-javascript: 7.0.5 - smob: 1.6.1 - terser: 5.47.1 + serialize-javascript: 7.0.7 + smob: 1.6.2 + terser: 5.49.0 optionalDependencies: - rollup: 4.60.3 + rollup: 4.62.3 - '@rollup/pluginutils@5.3.0(rollup@4.60.3)': + '@rollup/pluginutils@5.4.0(rollup@4.62.3)': dependencies: - '@types/estree': 1.0.8 + '@types/estree': 1.0.9 estree-walker: 2.0.2 - picomatch: 4.0.4 + picomatch: 4.0.5 optionalDependencies: - rollup: 4.60.3 - - '@rollup/rollup-android-arm-eabi@4.60.3': - optional: true - - '@rollup/rollup-android-arm-eabi@4.61.1': - optional: true - - '@rollup/rollup-android-arm64@4.60.3': - optional: true - - '@rollup/rollup-android-arm64@4.61.1': - optional: true - - '@rollup/rollup-darwin-arm64@4.60.3': - optional: true - - '@rollup/rollup-darwin-arm64@4.61.1': - optional: true - - '@rollup/rollup-darwin-x64@4.60.3': - optional: true - - '@rollup/rollup-darwin-x64@4.61.1': - optional: true - - '@rollup/rollup-freebsd-arm64@4.60.3': - optional: true - - '@rollup/rollup-freebsd-arm64@4.61.1': - optional: true - - '@rollup/rollup-freebsd-x64@4.60.3': - optional: true - - '@rollup/rollup-freebsd-x64@4.61.1': - optional: true - - '@rollup/rollup-linux-arm-gnueabihf@4.60.3': - optional: true + rollup: 4.62.3 - '@rollup/rollup-linux-arm-gnueabihf@4.61.1': + '@rollup/rollup-android-arm-eabi@4.62.3': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.60.3': + '@rollup/rollup-android-arm64@4.62.3': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.61.1': + '@rollup/rollup-darwin-arm64@4.62.3': optional: true - '@rollup/rollup-linux-arm64-gnu@4.60.3': + '@rollup/rollup-darwin-x64@4.62.3': optional: true - '@rollup/rollup-linux-arm64-gnu@4.61.1': + '@rollup/rollup-freebsd-arm64@4.62.3': optional: true - '@rollup/rollup-linux-arm64-musl@4.60.3': + '@rollup/rollup-freebsd-x64@4.62.3': optional: true - '@rollup/rollup-linux-arm64-musl@4.61.1': + '@rollup/rollup-linux-arm-gnueabihf@4.62.3': optional: true - '@rollup/rollup-linux-loong64-gnu@4.60.3': + '@rollup/rollup-linux-arm-musleabihf@4.62.3': optional: true - '@rollup/rollup-linux-loong64-gnu@4.61.1': + '@rollup/rollup-linux-arm64-gnu@4.62.3': optional: true - '@rollup/rollup-linux-loong64-musl@4.60.3': + '@rollup/rollup-linux-arm64-musl@4.62.3': optional: true - '@rollup/rollup-linux-loong64-musl@4.61.1': + '@rollup/rollup-linux-loong64-gnu@4.62.3': optional: true - '@rollup/rollup-linux-ppc64-gnu@4.60.3': + '@rollup/rollup-linux-loong64-musl@4.62.3': optional: true - '@rollup/rollup-linux-ppc64-gnu@4.61.1': + '@rollup/rollup-linux-ppc64-gnu@4.62.3': optional: true - '@rollup/rollup-linux-ppc64-musl@4.60.3': + '@rollup/rollup-linux-ppc64-musl@4.62.3': optional: true - '@rollup/rollup-linux-ppc64-musl@4.61.1': + '@rollup/rollup-linux-riscv64-gnu@4.62.3': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.60.3': + '@rollup/rollup-linux-riscv64-musl@4.62.3': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.61.1': + '@rollup/rollup-linux-s390x-gnu@4.62.3': optional: true - '@rollup/rollup-linux-riscv64-musl@4.60.3': + '@rollup/rollup-linux-x64-gnu@4.62.3': optional: true - '@rollup/rollup-linux-riscv64-musl@4.61.1': + '@rollup/rollup-linux-x64-musl@4.62.3': optional: true - '@rollup/rollup-linux-s390x-gnu@4.60.3': + '@rollup/rollup-openbsd-x64@4.62.3': optional: true - '@rollup/rollup-linux-s390x-gnu@4.61.1': + '@rollup/rollup-openharmony-arm64@4.62.3': optional: true - '@rollup/rollup-linux-x64-gnu@4.60.3': + '@rollup/rollup-win32-arm64-msvc@4.62.3': optional: true - '@rollup/rollup-linux-x64-gnu@4.61.1': + '@rollup/rollup-win32-ia32-msvc@4.62.3': optional: true - '@rollup/rollup-linux-x64-musl@4.60.3': + '@rollup/rollup-win32-x64-gnu@4.62.3': optional: true - '@rollup/rollup-linux-x64-musl@4.61.1': + '@rollup/rollup-win32-x64-msvc@4.62.3': optional: true - '@rollup/rollup-openbsd-x64@4.60.3': - optional: true - - '@rollup/rollup-openbsd-x64@4.61.1': - optional: true - - '@rollup/rollup-openharmony-arm64@4.60.3': - optional: true - - '@rollup/rollup-openharmony-arm64@4.61.1': - optional: true - - '@rollup/rollup-win32-arm64-msvc@4.60.3': - optional: true - - '@rollup/rollup-win32-arm64-msvc@4.61.1': - optional: true - - '@rollup/rollup-win32-ia32-msvc@4.60.3': - optional: true - - '@rollup/rollup-win32-ia32-msvc@4.61.1': - optional: true - - '@rollup/rollup-win32-x64-gnu@4.60.3': - optional: true - - '@rollup/rollup-win32-x64-gnu@4.61.1': - optional: true - - '@rollup/rollup-win32-x64-msvc@4.60.3': - optional: true - - '@rollup/rollup-win32-x64-msvc@4.61.1': - optional: true - - '@sinclair/typebox@0.27.10': {} + '@sinclair/typebox@0.27.12': {} '@sindresorhus/is@7.2.0': {} @@ -9073,12 +8658,12 @@ snapshots: dependencies: '@sinonjs/commons': 3.0.1 - '@solidjs/testing-library@0.8.10(solid-js@1.9.12)': + '@solidjs/testing-library@0.8.10(solid-js@1.9.14)': dependencies: '@testing-library/dom': 10.4.1 - solid-js: 1.9.12 + solid-js: 1.9.14 - '@speed-highlight/core@1.2.15': {} + '@speed-highlight/core@1.2.17': {} '@sqliteai/sqlite-vector-darwin-arm64@1.0.0': optional: true @@ -9103,27 +8688,36 @@ snapshots: '@standard-schema/spec@1.1.0': {} - '@testcontainers/mysql@11.14.0': + '@testcontainers/mssqlserver@12.0.4': dependencies: - testcontainers: 11.14.0 + testcontainers: 12.1.0 transitivePeerDependencies: - bare-abort-controller - bare-buffer - react-native-b4a - supports-color - '@testcontainers/postgresql@11.14.0': + '@testcontainers/mysql@12.0.4': dependencies: - testcontainers: 11.14.0 + testcontainers: 12.0.4 transitivePeerDependencies: - bare-abort-controller - bare-buffer - react-native-b4a - supports-color - '@testcontainers/redis@11.14.0': + '@testcontainers/postgresql@12.0.4': dependencies: - testcontainers: 11.14.0 + testcontainers: 12.0.4 + transitivePeerDependencies: + - bare-abort-controller + - bare-buffer + - react-native-b4a + - supports-color + + '@testcontainers/redis@12.0.4': + dependencies: + testcontainers: 12.0.4 transitivePeerDependencies: - bare-abort-controller - bare-buffer @@ -9141,24 +8735,25 @@ snapshots: picocolors: 1.1.1 pretty-format: 27.5.1 - '@testing-library/jest-dom@6.9.1': + '@testing-library/jest-dom@7.0.0(@testing-library/dom@10.4.1)': dependencies: - '@adobe/css-tools': 4.4.4 + '@adobe/css-tools': 4.5.0 + '@testing-library/dom': 10.4.1 aria-query: 5.3.2 css.escape: 1.5.1 dom-accessibility-api: 0.6.3 picocolors: 1.1.1 redent: 3.0.0 - '@testing-library/react@16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@testing-library/react@16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': dependencies: '@babel/runtime': 7.29.2 '@testing-library/dom': 10.4.1 - react: 19.2.6 - react-dom: 19.2.6(react@19.2.6) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) optionalDependencies: - '@types/react': 19.2.14 - '@types/react-dom': 19.2.3(@types/react@19.2.14) + '@types/react': 19.2.17 + '@types/react-dom': 19.2.3(@types/react@19.2.17) '@ts-graphviz/adapter@2.0.6': dependencies: @@ -9175,13 +8770,21 @@ snapshots: '@ts-graphviz/ast': 2.0.7 '@ts-graphviz/common': 2.1.5 - '@tybys/wasm-util@0.10.2': + '@ts-morph/common@0.28.1': + dependencies: + minimatch: 10.2.5 + path-browserify: 1.0.1 + tinyglobby: 0.2.17 + + '@tybys/wasm-util@0.10.3': dependencies: tslib: 2.8.1 optional: true '@types/aria-query@5.0.4': {} + '@types/babel__code-frame@7.27.0': {} + '@types/babel__core@7.20.5': dependencies: '@babel/parser': 7.29.7 @@ -9203,13 +8806,9 @@ snapshots: dependencies: '@babel/types': 7.29.7 - '@types/better-sqlite3@7.6.13': - dependencies: - '@types/node': 25.7.0 - - '@types/bun@1.3.13': + '@types/bun@1.3.14': dependencies: - bun-types: 1.3.13 + bun-types: 1.3.14 '@types/chai@5.2.3': dependencies: @@ -9218,26 +8817,28 @@ snapshots: '@types/deep-eql@4.0.2': {} + '@types/deno@2.7.0': {} + '@types/docker-modem@3.0.6': dependencies: - '@types/node': 25.7.0 + '@types/node': 26.1.2 '@types/ssh2': 1.15.5 '@types/dockerode@4.0.1': dependencies: '@types/docker-modem': 3.0.6 - '@types/node': 25.7.0 + '@types/node': 26.1.2 '@types/ssh2': 1.15.5 - '@types/estree@1.0.8': {} + '@types/doctrine@0.0.9': {} '@types/estree@1.0.9': {} + '@types/gensync@1.0.5': {} + '@types/graceful-fs@4.1.9': dependencies: - '@types/node': 25.9.3 - - '@types/ini@4.1.1': {} + '@types/node': 26.1.2 '@types/istanbul-lib-coverage@2.0.6': {} @@ -9252,48 +8853,44 @@ snapshots: '@types/jscodeshift@17.3.0': dependencies: ast-types: 0.16.1 - recast: 0.23.11 + recast: 0.23.12 - '@types/node@12.20.55': {} + '@types/jsesc@2.5.1': {} '@types/node@18.19.130': dependencies: undici-types: 5.26.5 - '@types/node@25.7.0': - dependencies: - undici-types: 7.21.0 - - '@types/node@25.9.3': + '@types/node@26.1.2': dependencies: - undici-types: 7.24.6 + undici-types: 8.3.0 - '@types/nodemailer@8.0.0': + '@types/nodemailer@8.0.1': dependencies: - '@types/node': 25.7.0 + '@types/node': 26.1.2 '@types/pg-cursor@2.7.2': dependencies: - '@types/node': 25.7.0 + '@types/node': 26.1.2 '@types/pg': 8.20.0 '@types/pg@8.20.0': dependencies: - '@types/node': 25.7.0 + '@types/node': 26.1.2 pg-protocol: 1.13.0 pg-types: 2.2.0 - '@types/react-dom@19.2.3(@types/react@19.2.14)': + '@types/react-dom@19.2.3(@types/react@19.2.17)': dependencies: - '@types/react': 19.2.14 + '@types/react': 19.2.17 - '@types/react@19.2.14': + '@types/react@19.2.17': dependencies: csstype: 3.2.3 '@types/readable-stream@4.0.23': dependencies: - '@types/node': 25.7.0 + '@types/node': 26.1.2 '@types/resolve@1.20.2': {} @@ -9301,11 +8898,11 @@ snapshots: '@types/ssh2-streams@0.1.13': dependencies: - '@types/node': 25.7.0 + '@types/node': 26.1.2 '@types/ssh2@0.5.52': dependencies: - '@types/node': 25.7.0 + '@types/node': 26.1.2 '@types/ssh2-streams': 0.1.13 '@types/ssh2@1.15.5': @@ -9316,14 +8913,14 @@ snapshots: '@types/swagger2openapi@7.0.4': dependencies: - '@types/node': 25.7.0 + '@types/node': 26.1.2 openapi-types: 12.1.3 '@types/whatwg-mimetype@3.0.2': {} '@types/ws@8.18.1': dependencies: - '@types/node': 25.7.0 + '@types/node': 26.1.2 '@types/yargs-parser@21.0.3': {} @@ -9331,71 +8928,100 @@ snapshots: dependencies: '@types/yargs-parser': 21.0.3 - '@typescript-eslint/project-service@8.54.0(typescript@5.9.3)': + '@typescript-eslint/project-service@8.65.0(typescript@5.9.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.54.0(typescript@5.9.3) - '@typescript-eslint/types': 8.54.0 + '@typescript-eslint/tsconfig-utils': 8.65.0(typescript@5.9.3) + '@typescript-eslint/types': 8.65.0 debug: 4.4.3(supports-color@10.2.2) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/tsconfig-utils@8.54.0(typescript@5.9.3)': + '@typescript-eslint/tsconfig-utils@8.65.0(typescript@5.9.3)': dependencies: typescript: 5.9.3 - '@typescript-eslint/types@8.54.0': {} + '@typescript-eslint/types@8.65.0': {} - '@typescript-eslint/typescript-estree@8.54.0(typescript@5.9.3)': + '@typescript-eslint/typescript-estree@8.65.0(typescript@5.9.3)': dependencies: - '@typescript-eslint/project-service': 8.54.0(typescript@5.9.3) - '@typescript-eslint/tsconfig-utils': 8.54.0(typescript@5.9.3) - '@typescript-eslint/types': 8.54.0 - '@typescript-eslint/visitor-keys': 8.54.0 + '@typescript-eslint/project-service': 8.65.0(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.65.0(typescript@5.9.3) + '@typescript-eslint/types': 8.65.0 + '@typescript-eslint/visitor-keys': 8.65.0 debug: 4.4.3(supports-color@10.2.2) - minimatch: 9.0.9 - semver: 7.8.0 - tinyglobby: 0.2.16 - ts-api-utils: 2.4.0(typescript@5.9.3) + minimatch: 10.2.5 + semver: 7.8.5 + tinyglobby: 0.2.17 + ts-api-utils: 2.5.0(typescript@5.9.3) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.54.0': + '@typescript-eslint/visitor-keys@8.65.0': dependencies: - '@typescript-eslint/types': 8.54.0 - eslint-visitor-keys: 4.2.1 + '@typescript-eslint/types': 8.65.0 + eslint-visitor-keys: 5.0.1 - '@typescript/native-preview-darwin-arm64@7.0.0-dev.20260512.1': + '@typescript/typescript-aix-ppc64@7.0.2': optional: true - '@typescript/native-preview-darwin-x64@7.0.0-dev.20260512.1': + '@typescript/typescript-darwin-arm64@7.0.2': optional: true - '@typescript/native-preview-linux-arm64@7.0.0-dev.20260512.1': + '@typescript/typescript-darwin-x64@7.0.2': optional: true - '@typescript/native-preview-linux-arm@7.0.0-dev.20260512.1': + '@typescript/typescript-freebsd-arm64@7.0.2': optional: true - '@typescript/native-preview-linux-x64@7.0.0-dev.20260512.1': + '@typescript/typescript-freebsd-x64@7.0.2': optional: true - '@typescript/native-preview-win32-arm64@7.0.0-dev.20260512.1': + '@typescript/typescript-linux-arm64@7.0.2': optional: true - '@typescript/native-preview-win32-x64@7.0.0-dev.20260512.1': + '@typescript/typescript-linux-arm@7.0.2': optional: true - '@typescript/native-preview@7.0.0-dev.20260512.1': - optionalDependencies: - '@typescript/native-preview-darwin-arm64': 7.0.0-dev.20260512.1 - '@typescript/native-preview-darwin-x64': 7.0.0-dev.20260512.1 - '@typescript/native-preview-linux-arm': 7.0.0-dev.20260512.1 - '@typescript/native-preview-linux-arm64': 7.0.0-dev.20260512.1 - '@typescript/native-preview-linux-x64': 7.0.0-dev.20260512.1 - '@typescript/native-preview-win32-arm64': 7.0.0-dev.20260512.1 - '@typescript/native-preview-win32-x64': 7.0.0-dev.20260512.1 + '@typescript/typescript-linux-loong64@7.0.2': + optional: true + + '@typescript/typescript-linux-mips64el@7.0.2': + optional: true + + '@typescript/typescript-linux-ppc64@7.0.2': + optional: true + + '@typescript/typescript-linux-riscv64@7.0.2': + optional: true + + '@typescript/typescript-linux-s390x@7.0.2': + optional: true + + '@typescript/typescript-linux-x64@7.0.2': + optional: true + + '@typescript/typescript-netbsd-arm64@7.0.2': + optional: true + + '@typescript/typescript-netbsd-x64@7.0.2': + optional: true + + '@typescript/typescript-openbsd-arm64@7.0.2': + optional: true + + '@typescript/typescript-openbsd-x64@7.0.2': + optional: true + + '@typescript/typescript-sunos-x64@7.0.2': + optional: true + + '@typescript/typescript-win32-arm64@7.0.2': + optional: true + + '@typescript/typescript-win32-x64@7.0.2': + optional: true '@typespec/ts-http-runtime@0.3.5': dependencies: @@ -9405,220 +9031,170 @@ snapshots: transitivePeerDependencies: - supports-color - '@vitest/browser@4.1.4(vite@7.3.5(@types/node@25.7.0)(lightningcss@1.32.0)(terser@5.48.0)(tsx@4.21.0)(yaml@2.9.0))(vitest@4.1.4)': - dependencies: - '@blazediff/core': 1.9.1 - '@vitest/mocker': 4.1.4(vite@7.3.5(@types/node@25.7.0)(lightningcss@1.32.0)(terser@5.48.0)(tsx@4.21.0)(yaml@2.9.0)) - '@vitest/utils': 4.1.4 - magic-string: 0.30.21 - pngjs: 7.0.0 - sirv: 3.0.2 - tinyrainbow: 3.1.0 - vitest: 4.1.4(@edge-runtime/vm@5.0.0)(@opentelemetry/api@1.9.1)(@types/node@25.7.0)(@vitest/coverage-v8@4.1.4)(happy-dom@20.9.0)(jsdom@29.1.1)(vite@7.3.5(@types/node@25.7.0)(lightningcss@1.32.0)(terser@5.48.0)(tsx@4.21.0)(yaml@2.9.0)) - ws: 8.21.0 - transitivePeerDependencies: - - bufferutil - - msw - - utf-8-validate - - vite - optional: true - - '@vitest/browser@4.1.6(vite@7.3.3(@types/node@25.7.0)(lightningcss@1.32.0)(terser@5.47.1)(tsx@4.21.0)(yaml@2.9.0))(vitest@4.1.4)': + '@vitest/browser@4.1.10(vite@8.1.5(@types/node@26.1.2)(esbuild@0.28.1)(terser@5.49.0)(tsx@4.23.1)(yaml@2.9.0))(vitest@4.1.10)': dependencies: '@blazediff/core': 1.9.1 - '@vitest/mocker': 4.1.6(vite@7.3.3(@types/node@25.7.0)(lightningcss@1.32.0)(terser@5.47.1)(tsx@4.21.0)(yaml@2.9.0)) - '@vitest/utils': 4.1.6 + '@vitest/mocker': 4.1.10(vite@8.1.5(@types/node@26.1.2)(esbuild@0.28.1)(terser@5.49.0)(tsx@4.23.1)(yaml@2.9.0)) + '@vitest/utils': 4.1.10 magic-string: 0.30.21 pngjs: 7.0.0 sirv: 3.0.2 tinyrainbow: 3.1.0 - vitest: 4.1.4(@edge-runtime/vm@5.0.0)(@opentelemetry/api@1.9.1)(@types/node@25.7.0)(@vitest/coverage-v8@4.1.6)(happy-dom@20.9.0)(jsdom@29.1.1)(vite@7.3.3(@types/node@25.7.0)(lightningcss@1.32.0)(terser@5.47.1)(tsx@4.21.0)(yaml@2.9.0)) - ws: 8.20.0 + vitest: 4.1.10(@edge-runtime/vm@5.0.0)(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/coverage-v8@4.1.10)(happy-dom@20.11.1)(jsdom@30.0.0)(vite@8.1.5(@types/node@26.1.2)(esbuild@0.28.1)(terser@5.49.0)(tsx@4.23.1)(yaml@2.9.0)) + ws: 8.21.1 transitivePeerDependencies: - bufferutil - msw - utf-8-validate - vite - '@vitest/coverage-v8@4.1.4(@vitest/browser@4.1.4)(vitest@4.1.4)': + '@vitest/coverage-v8@4.1.10(@vitest/browser@4.1.10)(vitest@4.1.10)': dependencies: '@bcoe/v8-coverage': 1.0.2 - '@vitest/utils': 4.1.4 - ast-v8-to-istanbul: 1.0.4 + '@vitest/utils': 4.1.10 + ast-v8-to-istanbul: 1.0.5 istanbul-lib-coverage: 3.2.2 istanbul-lib-report: 3.0.1 istanbul-reports: 3.2.0 magicast: 0.5.3 - obug: 2.1.2 - std-env: 4.1.0 - tinyrainbow: 3.1.0 - vitest: 4.1.4(@edge-runtime/vm@5.0.0)(@opentelemetry/api@1.9.1)(@types/node@25.7.0)(@vitest/coverage-v8@4.1.4)(happy-dom@20.9.0)(jsdom@29.1.1)(vite@7.3.5(@types/node@25.7.0)(lightningcss@1.32.0)(terser@5.48.0)(tsx@4.21.0)(yaml@2.9.0)) - optionalDependencies: - '@vitest/browser': 4.1.4(vite@7.3.5(@types/node@25.7.0)(lightningcss@1.32.0)(terser@5.48.0)(tsx@4.21.0)(yaml@2.9.0))(vitest@4.1.4) - optional: true - - '@vitest/coverage-v8@4.1.6(@vitest/browser@4.1.6)(vitest@4.1.4)': - dependencies: - '@bcoe/v8-coverage': 1.0.2 - '@vitest/utils': 4.1.6 - ast-v8-to-istanbul: 1.0.0 - istanbul-lib-coverage: 3.2.2 - istanbul-lib-report: 3.0.1 - istanbul-reports: 3.2.0 - magicast: 0.5.2 - obug: 2.1.1 - std-env: 4.1.0 + obug: 2.1.4 + std-env: 4.2.0 tinyrainbow: 3.1.0 - vitest: 4.1.4(@edge-runtime/vm@5.0.0)(@opentelemetry/api@1.9.1)(@types/node@25.7.0)(@vitest/coverage-v8@4.1.6)(happy-dom@20.9.0)(jsdom@29.1.1)(vite@7.3.3(@types/node@25.7.0)(lightningcss@1.32.0)(terser@5.47.1)(tsx@4.21.0)(yaml@2.9.0)) + vitest: 4.1.10(@edge-runtime/vm@5.0.0)(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/coverage-v8@4.1.10)(happy-dom@20.11.1)(jsdom@30.0.0)(vite@8.1.5(@types/node@26.1.2)(esbuild@0.28.1)(terser@5.49.0)(tsx@4.23.1)(yaml@2.9.0)) optionalDependencies: - '@vitest/browser': 4.1.6(vite@7.3.3(@types/node@25.7.0)(lightningcss@1.32.0)(terser@5.47.1)(tsx@4.21.0)(yaml@2.9.0))(vitest@4.1.4) + '@vitest/browser': 4.1.10(vite@8.1.5(@types/node@26.1.2)(esbuild@0.28.1)(terser@5.49.0)(tsx@4.23.1)(yaml@2.9.0))(vitest@4.1.10) - '@vitest/expect@4.1.4': + '@vitest/expect@4.1.10': dependencies: '@standard-schema/spec': 1.1.0 '@types/chai': 5.2.3 - '@vitest/spy': 4.1.4 - '@vitest/utils': 4.1.4 + '@vitest/spy': 4.1.10 + '@vitest/utils': 4.1.10 chai: 6.2.2 tinyrainbow: 3.1.0 - '@vitest/expect@4.1.6': + '@vitest/mocker@4.1.10(vite@8.1.5(@types/node@26.1.2)(esbuild@0.28.1)(terser@5.49.0)(tsx@4.23.1)(yaml@2.9.0))': dependencies: - '@standard-schema/spec': 1.1.0 - '@types/chai': 5.2.3 - '@vitest/spy': 4.1.6 - '@vitest/utils': 4.1.6 - chai: 6.2.2 - tinyrainbow: 3.1.0 - - '@vitest/mocker@4.1.4(vite@7.3.3(@types/node@25.7.0)(lightningcss@1.32.0)(terser@5.47.1)(tsx@4.21.0)(yaml@2.9.0))': - dependencies: - '@vitest/spy': 4.1.4 - estree-walker: 3.0.3 - magic-string: 0.30.21 - optionalDependencies: - vite: 7.3.3(@types/node@25.7.0)(lightningcss@1.32.0)(terser@5.47.1)(tsx@4.21.0)(yaml@2.9.0) - - '@vitest/mocker@4.1.4(vite@7.3.5(@types/node@25.7.0)(lightningcss@1.32.0)(terser@5.48.0)(tsx@4.21.0)(yaml@2.9.0))': - dependencies: - '@vitest/spy': 4.1.4 - estree-walker: 3.0.3 - magic-string: 0.30.21 - optionalDependencies: - vite: 7.3.5(@types/node@25.7.0)(lightningcss@1.32.0)(terser@5.48.0)(tsx@4.21.0)(yaml@2.9.0) - - '@vitest/mocker@4.1.6(vite@7.3.3(@types/node@25.7.0)(lightningcss@1.32.0)(terser@5.47.1)(tsx@4.21.0)(yaml@2.9.0))': - dependencies: - '@vitest/spy': 4.1.6 + '@vitest/spy': 4.1.10 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 7.3.3(@types/node@25.7.0)(lightningcss@1.32.0)(terser@5.47.1)(tsx@4.21.0)(yaml@2.9.0) + vite: 8.1.5(@types/node@26.1.2)(esbuild@0.28.1)(terser@5.49.0)(tsx@4.23.1)(yaml@2.9.0) - '@vitest/pretty-format@3.2.4': - dependencies: - tinyrainbow: 2.0.0 - - '@vitest/pretty-format@4.1.4': - dependencies: - tinyrainbow: 3.1.0 - - '@vitest/pretty-format@4.1.6': + '@vitest/pretty-format@4.1.10': dependencies: tinyrainbow: 3.1.0 - '@vitest/runner@4.1.4': + '@vitest/runner@4.1.10': dependencies: - '@vitest/utils': 4.1.4 + '@vitest/utils': 4.1.10 pathe: 2.0.3 - '@vitest/snapshot@4.1.4': + '@vitest/snapshot@4.1.10': dependencies: - '@vitest/pretty-format': 4.1.4 - '@vitest/utils': 4.1.4 + '@vitest/pretty-format': 4.1.10 + '@vitest/utils': 4.1.10 magic-string: 0.30.21 pathe: 2.0.3 - '@vitest/spy@4.1.4': {} - - '@vitest/spy@4.1.6': {} - - '@vitest/utils@3.2.4': - dependencies: - '@vitest/pretty-format': 3.2.4 - loupe: 3.2.1 - tinyrainbow: 2.0.0 + '@vitest/spy@4.1.10': {} - '@vitest/utils@4.1.4': + '@vitest/utils@4.1.10': dependencies: - '@vitest/pretty-format': 4.1.4 + '@vitest/pretty-format': 4.1.10 convert-source-map: 2.0.0 tinyrainbow: 3.1.0 - '@vitest/utils@4.1.6': + '@vitest/web-worker@4.1.10(vitest@4.1.10)': dependencies: - '@vitest/pretty-format': 4.1.6 - convert-source-map: 2.0.0 - tinyrainbow: 3.1.0 + obug: 2.1.4 + vitest: 4.1.10(@edge-runtime/vm@5.0.0)(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/coverage-v8@4.1.10)(happy-dom@20.11.1)(jsdom@30.0.0)(vite@8.1.5(@types/node@26.1.2)(esbuild@0.28.1)(terser@5.49.0)(tsx@4.23.1)(yaml@2.9.0)) - '@vitest/web-worker@4.1.6(vitest@4.1.4)': + '@vue/compiler-core@3.5.39': dependencies: - obug: 2.1.1 - vitest: 4.1.4(@edge-runtime/vm@5.0.0)(@opentelemetry/api@1.9.1)(@types/node@25.7.0)(@vitest/coverage-v8@4.1.6)(happy-dom@20.9.0)(jsdom@29.1.1)(vite@7.3.3(@types/node@25.7.0)(lightningcss@1.32.0)(terser@5.47.1)(tsx@4.21.0)(yaml@2.9.0)) + '@babel/parser': 7.29.7 + '@vue/shared': 3.5.39 + entities: 7.0.1 + estree-walker: 2.0.2 + source-map-js: 1.2.1 - '@vue/compiler-core@3.5.34': + '@vue/compiler-core@3.5.40': dependencies: - '@babel/parser': 7.29.3 - '@vue/shared': 3.5.34 + '@babel/parser': 7.29.7 + '@vue/shared': 3.5.40 entities: 7.0.1 estree-walker: 2.0.2 source-map-js: 1.2.1 - '@vue/compiler-dom@3.5.34': + '@vue/compiler-dom@3.5.39': + dependencies: + '@vue/compiler-core': 3.5.39 + '@vue/shared': 3.5.39 + + '@vue/compiler-dom@3.5.40': + dependencies: + '@vue/compiler-core': 3.5.40 + '@vue/shared': 3.5.40 + + '@vue/compiler-sfc@3.5.39': dependencies: - '@vue/compiler-core': 3.5.34 - '@vue/shared': 3.5.34 + '@babel/parser': 7.29.7 + '@vue/compiler-core': 3.5.39 + '@vue/compiler-dom': 3.5.39 + '@vue/compiler-ssr': 3.5.39 + '@vue/shared': 3.5.39 + estree-walker: 2.0.2 + magic-string: 0.30.21 + postcss: 8.5.24 + source-map-js: 1.2.1 - '@vue/compiler-sfc@3.5.34': + '@vue/compiler-sfc@3.5.40': dependencies: - '@babel/parser': 7.29.3 - '@vue/compiler-core': 3.5.34 - '@vue/compiler-dom': 3.5.34 - '@vue/compiler-ssr': 3.5.34 - '@vue/shared': 3.5.34 + '@babel/parser': 7.29.7 + '@vue/compiler-core': 3.5.40 + '@vue/compiler-dom': 3.5.40 + '@vue/compiler-ssr': 3.5.40 + '@vue/shared': 3.5.40 estree-walker: 2.0.2 magic-string: 0.30.21 - postcss: 8.5.14 + postcss: 8.5.24 source-map-js: 1.2.1 - '@vue/compiler-ssr@3.5.34': + '@vue/compiler-ssr@3.5.39': dependencies: - '@vue/compiler-dom': 3.5.34 - '@vue/shared': 3.5.34 + '@vue/compiler-dom': 3.5.39 + '@vue/shared': 3.5.39 - '@vue/reactivity@3.5.34': + '@vue/compiler-ssr@3.5.40': dependencies: - '@vue/shared': 3.5.34 + '@vue/compiler-dom': 3.5.40 + '@vue/shared': 3.5.40 - '@vue/runtime-core@3.5.34': + '@vue/reactivity@3.5.39': dependencies: - '@vue/reactivity': 3.5.34 - '@vue/shared': 3.5.34 + '@vue/shared': 3.5.39 - '@vue/runtime-dom@3.5.34': + '@vue/runtime-core@3.5.39': dependencies: - '@vue/reactivity': 3.5.34 - '@vue/runtime-core': 3.5.34 - '@vue/shared': 3.5.34 + '@vue/reactivity': 3.5.39 + '@vue/shared': 3.5.39 + + '@vue/runtime-dom@3.5.39': + dependencies: + '@vue/reactivity': 3.5.39 + '@vue/runtime-core': 3.5.39 + '@vue/shared': 3.5.39 csstype: 3.2.3 - '@vue/server-renderer@3.5.34(vue@3.5.34(typescript@6.0.3))': + '@vue/server-renderer@3.5.39(vue@3.5.39(typescript@7.0.2))': dependencies: - '@vue/compiler-ssr': 3.5.34 - '@vue/shared': 3.5.34 - vue: 3.5.34(typescript@6.0.3) + '@vue/compiler-ssr': 3.5.39 + '@vue/shared': 3.5.39 + vue: 3.5.39(typescript@7.0.2) - '@vue/shared@3.5.34': {} + '@vue/shared@3.5.39': {} + + '@vue/shared@3.5.40': {} abort-controller@3.0.0: dependencies: @@ -9629,12 +9205,14 @@ snapshots: mime-types: 3.0.2 negotiator: 1.0.0 - acorn@8.16.0: {} - acorn@8.17.0: {} agent-base@7.1.4: {} + ajv-draft-04@1.0.0(ajv@8.20.0): + optionalDependencies: + ajv: 8.20.0 + ajv@8.20.0: dependencies: fast-deep-equal: 3.1.3 @@ -9684,7 +9262,7 @@ snapshots: buffer-crc32: 1.0.0 readable-stream: 4.7.0 readdir-glob: 1.1.3 - tar-stream: 3.1.8 + tar-stream: 3.2.0 zip-stream: 6.0.1 transitivePeerDependencies: - bare-abort-controller @@ -9703,18 +9281,6 @@ snapshots: aria-query@5.3.2: {} - arkregex@0.0.5: - dependencies: - '@ark/util': 0.56.0 - - arktype@2.2.0: - dependencies: - '@ark/schema': 0.56.0 - '@ark/util': 0.56.0 - arkregex: 0.0.5 - - array-union@2.1.0: {} - asap@2.0.6: {} asn1@0.2.6: @@ -9723,7 +9289,7 @@ snapshots: assertion-error@2.0.1: {} - ast-module-types@6.0.1: {} + ast-module-types@6.0.2: {} ast-types@0.14.2: dependencies: @@ -9733,18 +9299,11 @@ snapshots: dependencies: tslib: 2.8.1 - ast-v8-to-istanbul@1.0.0: - dependencies: - '@jridgewell/trace-mapping': 0.3.31 - estree-walker: 3.0.3 - js-tokens: 10.0.0 - - ast-v8-to-istanbul@1.0.4: + ast-v8-to-istanbul@1.0.5: dependencies: '@jridgewell/trace-mapping': 0.3.31 estree-walker: 3.0.3 js-tokens: 10.0.0 - optional: true async-lock@1.4.1: {} @@ -9756,24 +9315,24 @@ snapshots: aws-ssl-profiles@1.1.2: {} - b4a@1.8.0: {} + b4a@1.8.1: {} - babel-jest@29.7.0(@babel/core@7.29.7): + babel-jest@29.7.0(@babel/core@8.0.1): dependencies: - '@babel/core': 7.29.7 + '@babel/core': 8.0.1 '@jest/transform': 29.7.0 '@types/babel__core': 7.20.5 babel-plugin-istanbul: 6.1.1 - babel-preset-jest: 29.6.3(@babel/core@7.29.7) + babel-preset-jest: 29.6.3(@babel/core@8.0.1) chalk: 4.1.2 graceful-fs: 4.2.11 slash: 3.0.0 transitivePeerDependencies: - supports-color - babel-plugin-annotate-pure-calls@0.5.0(@babel/core@7.29.0): + babel-plugin-annotate-pure-calls@0.5.0(@babel/core@8.0.1): dependencies: - '@babel/core': 7.29.0 + '@babel/core': 8.0.1 babel-plugin-istanbul@6.1.1: dependencies: @@ -9796,102 +9355,76 @@ snapshots: dependencies: hermes-parser: 0.32.0 - babel-preset-current-node-syntax@1.2.0(@babel/core@7.29.7): - dependencies: - '@babel/core': 7.29.7 - '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.29.7) - '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.29.7) - '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.29.7) - '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.29.7) - '@babel/plugin-syntax-import-attributes': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.29.7) - '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.29.7) - '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.29.7) - '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.29.7) - '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.29.7) - '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.29.7) - '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.29.7) - '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.29.7) - '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.29.7) - '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.29.7) - - babel-preset-jest@29.6.3(@babel/core@7.29.7): - dependencies: - '@babel/core': 7.29.7 + babel-preset-current-node-syntax@1.2.0(@babel/core@8.0.1): + dependencies: + '@babel/core': 8.0.1 + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@8.0.1) + '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@8.0.1) + '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@8.0.1) + '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@8.0.1) + '@babel/plugin-syntax-import-attributes': 7.29.7(@babel/core@8.0.1) + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@8.0.1) + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@8.0.1) + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@8.0.1) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@8.0.1) + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@8.0.1) + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@8.0.1) + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@8.0.1) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@8.0.1) + '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@8.0.1) + '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@8.0.1) + + babel-preset-jest@29.6.3(@babel/core@8.0.1): + dependencies: + '@babel/core': 8.0.1 babel-plugin-jest-hoist: 29.6.3 - babel-preset-current-node-syntax: 1.2.0(@babel/core@7.29.7) + babel-preset-current-node-syntax: 1.2.0(@babel/core@8.0.1) balanced-match@1.0.2: {} balanced-match@4.0.4: {} - bare-events@2.8.2: {} + bare-events@2.9.1: {} - bare-fs@4.7.0: + bare-fs@4.7.4: dependencies: - bare-events: 2.8.2 - bare-path: 3.0.0 - bare-stream: 2.13.0(bare-events@2.8.2) - bare-url: 2.4.0 + bare-events: 2.9.1 + bare-path: 3.1.1 + bare-stream: 2.13.3(bare-events@2.9.1) + bare-url: 2.4.6 fast-fifo: 1.3.2 transitivePeerDependencies: - bare-abort-controller - react-native-b4a - bare-os@3.8.7: {} - - bare-path@3.0.0: - dependencies: - bare-os: 3.8.7 + bare-path@3.1.1: {} - bare-stream@2.13.0(bare-events@2.8.2): + bare-stream@2.13.3(bare-events@2.9.1): dependencies: - streamx: 2.25.0 + b4a: 1.8.1 + streamx: 2.28.0 teex: 1.0.1 optionalDependencies: - bare-events: 2.8.2 + bare-events: 2.9.1 transitivePeerDependencies: - react-native-b4a - bare-url@2.4.0: + bare-url@2.4.6: dependencies: - bare-path: 3.0.0 + bare-path: 3.1.1 base64-js@1.5.1: {} - baseline-browser-mapping@2.10.35: {} - - baseline-browser-mapping@2.9.19: {} + baseline-browser-mapping@2.11.5: {} bcrypt-pbkdf@1.0.2: dependencies: tweetnacl: 0.14.5 - better-path-resolve@1.0.0: - dependencies: - is-windows: 1.0.2 - - better-sqlite3@12.10.0: - dependencies: - bindings: 1.5.0 - prebuild-install: 7.1.3 - - better-sqlite3@12.9.0: - dependencies: - bindings: 1.5.0 - prebuild-install: 7.1.3 - bidi-js@1.0.3: dependencies: require-from-string: 2.0.2 - binary-extensions@2.3.0: - optional: true - - bindings@1.5.0: - dependencies: - file-uri-to-path: 1.0.0 - bl@4.1.0: dependencies: buffer: 5.7.1 @@ -9905,16 +9438,16 @@ snapshots: inherits: 2.0.4 readable-stream: 4.7.0 - brace-expansion@1.1.12: + brace-expansion@1.1.16: dependencies: balanced-match: 1.0.2 concat-map: 0.0.1 - brace-expansion@2.1.0: + brace-expansion@2.1.3: dependencies: balanced-match: 1.0.2 - brace-expansion@5.0.5: + brace-expansion@5.0.7: dependencies: balanced-match: 4.0.4 @@ -9922,21 +9455,13 @@ snapshots: dependencies: fill-range: 7.1.1 - browserslist@4.28.1: - dependencies: - baseline-browser-mapping: 2.9.19 - caniuse-lite: 1.0.30001766 - electron-to-chromium: 1.5.283 - node-releases: 2.0.27 - update-browserslist-db: 1.2.3(browserslist@4.28.1) - - browserslist@4.28.2: + browserslist@4.28.7: dependencies: - baseline-browser-mapping: 2.10.35 - caniuse-lite: 1.0.30001799 - electron-to-chromium: 1.5.371 - node-releases: 2.0.47 - update-browserslist-db: 1.2.3(browserslist@4.28.2) + baseline-browser-mapping: 2.11.5 + caniuse-lite: 1.0.30001806 + electron-to-chromium: 1.5.397 + node-releases: 2.0.51 + update-browserslist-db: 1.2.3(browserslist@4.28.7) bser@2.1.1: dependencies: @@ -9948,6 +9473,10 @@ snapshots: buffer-from@1.1.2: {} + buffer-image-size@0.6.4: + dependencies: + '@types/node': 26.1.2 + buffer@5.7.1: dependencies: base64-js: 1.5.1 @@ -9961,9 +9490,9 @@ snapshots: buildcheck@0.0.7: optional: true - bun-types@1.3.13: + bun-types@1.3.14: dependencies: - '@types/node': 25.7.0 + '@types/node': 26.1.2 bundle-name@4.1.0: dependencies: @@ -9971,15 +9500,15 @@ snapshots: byline@5.0.0: {} + cac@7.0.0: {} + call-me-maybe@1.0.2: {} camelcase@5.3.1: {} camelcase@6.3.0: {} - caniuse-lite@1.0.30001766: {} - - caniuse-lite@1.0.30001799: {} + caniuse-lite@1.0.30001806: {} chai@6.2.2: {} @@ -9988,22 +9517,13 @@ snapshots: ansi-styles: 4.3.0 supports-color: 7.2.0 - change-case@5.4.4: {} + chalk@5.6.2: {} - chardet@2.1.1: {} + change-case@5.4.4: {} - chokidar@3.6.0: + chokidar@5.0.0: dependencies: - anymatch: 3.1.3 - braces: 3.0.3 - glob-parent: 5.1.2 - is-binary-path: 2.1.0 - is-glob: 4.0.3 - normalize-path: 3.0.0 - readdirp: 3.6.0 - optionalDependencies: - fsevents: 2.3.3 - optional: true + readdirp: 5.0.0 chownr@1.1.4: {} @@ -10011,7 +9531,7 @@ snapshots: chrome-launcher@0.15.2: dependencies: - '@types/node': 25.9.3 + '@types/node': 26.1.2 escape-string-regexp: 4.0.0 is-wsl: 2.2.0 lighthouse-logger: 1.4.2 @@ -10020,7 +9540,7 @@ snapshots: chromium-edge-launcher@0.2.0: dependencies: - '@types/node': 25.9.3 + '@types/node': 26.1.2 escape-string-regexp: 4.0.0 is-wsl: 2.2.0 lighthouse-logger: 1.4.2 @@ -10059,8 +9579,12 @@ snapshots: clone@1.0.4: {} + cluster-key-slot@1.1.0: {} + cluster-key-slot@1.1.2: {} + code-block-writer@13.0.3: {} + color-convert@2.0.1: dependencies: color-name: 1.1.4 @@ -10071,9 +9595,9 @@ snapshots: commander@12.1.0: {} - commander@2.20.3: {} + commander@14.0.3: {} - commander@6.2.1: {} + commander@2.20.3: {} commander@7.2.0: {} @@ -10120,7 +9644,7 @@ snapshots: cpu-features@0.0.10: dependencies: buildcheck: 0.0.7 - nan: 2.26.2 + nan: 2.28.0 optional: true crc-32@1.2.2: {} @@ -10152,7 +9676,7 @@ snapshots: transitivePeerDependencies: - '@noble/hashes' - dataloader@1.4.0: {} + dataloader@2.2.3: {} debug@2.6.9: dependencies: @@ -10164,11 +9688,7 @@ snapshots: optionalDependencies: supports-color: 10.2.2 - decimal.js@10.6.0: {} - - decompress-response@6.0.0: - dependencies: - mimic-response: 3.1.0 + decimal.js@10.6.0: {} deep-extend@0.6.0: {} @@ -10191,11 +9711,12 @@ snapshots: depd@2.0.0: {} - dependency-tree@11.2.0: + dependency-tree@11.5.0: dependencies: + '@discoveryjs/json-ext': 1.1.0 commander: 12.1.0 - filing-cabinet: 5.0.3 - precinct: 12.2.0 + filing-cabinet: 5.5.1 + precinct: 12.3.2 typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -10204,74 +9725,68 @@ snapshots: destroy@1.2.0: {} - detect-indent@6.1.0: {} - detect-libc@2.0.2: {} detect-libc@2.1.2: {} - detective-amd@6.0.1: + detective-amd@6.1.0: dependencies: - ast-module-types: 6.0.1 + ast-module-types: 6.0.2 escodegen: 2.1.0 - get-amd-module-type: 6.0.1 - node-source-walk: 7.0.1 + get-amd-module-type: 6.0.2 + node-source-walk: 7.0.2 - detective-cjs@6.0.1: + detective-cjs@6.1.1: dependencies: - ast-module-types: 6.0.1 - node-source-walk: 7.0.1 + ast-module-types: 6.0.2 + node-source-walk: 7.0.2 - detective-es6@5.0.1: + detective-es6@5.0.2: dependencies: - node-source-walk: 7.0.1 + node-source-walk: 7.0.2 - detective-postcss@7.0.1(postcss@8.5.14): + detective-postcss@8.0.4(postcss@8.5.24): dependencies: - is-url: 1.2.4 - postcss: 8.5.14 - postcss-values-parser: 6.0.2(postcss@8.5.14) + is-url-superb: 4.0.0 + postcss: 8.5.24 + postcss-values-parser: 6.0.2(postcss@8.5.24) - detective-sass@6.0.1: + detective-sass@6.0.2: dependencies: gonzales-pe: 4.3.0 - node-source-walk: 7.0.1 + node-source-walk: 7.0.2 - detective-scss@5.0.1: + detective-scss@5.0.2: dependencies: gonzales-pe: 4.3.0 - node-source-walk: 7.0.1 + node-source-walk: 7.0.2 detective-stylus@5.0.1: {} - detective-typescript@14.0.0(typescript@5.9.3): + detective-typescript@14.1.2(typescript@5.9.3): dependencies: - '@typescript-eslint/typescript-estree': 8.54.0(typescript@5.9.3) - ast-module-types: 6.0.1 - node-source-walk: 7.0.1 + '@typescript-eslint/typescript-estree': 8.65.0(typescript@5.9.3) + ast-module-types: 6.0.2 + node-source-walk: 7.0.2 typescript: 5.9.3 transitivePeerDependencies: - supports-color - detective-vue2@2.2.0(typescript@5.9.3): + detective-vue2@2.3.0(typescript@5.9.3): dependencies: - '@dependents/detective-less': 5.0.1 - '@vue/compiler-sfc': 3.5.34 - detective-es6: 5.0.1 - detective-sass: 6.0.1 - detective-scss: 5.0.1 + '@dependents/detective-less': 5.0.3 + '@vue/compiler-sfc': 3.5.40 + detective-es6: 5.0.2 + detective-sass: 6.0.2 + detective-scss: 5.0.2 detective-stylus: 5.0.1 - detective-typescript: 14.0.0(typescript@5.9.3) + detective-typescript: 14.1.2(typescript@5.9.3) typescript: 5.9.3 transitivePeerDependencies: - supports-color diacritics-map@0.1.0: {} - dir-glob@3.0.1: - dependencies: - path-type: 4.0.0 - docker-compose@1.4.2: dependencies: yaml: 2.9.0 @@ -10285,15 +9800,14 @@ snapshots: transitivePeerDependencies: - supports-color - dockerode@4.0.10: + dockerode@5.0.1: dependencies: '@balena/dockerignore': 1.0.2 - '@grpc/grpc-js': 1.14.3 + '@grpc/grpc-js': 1.14.4 '@grpc/proto-loader': 0.7.15 docker-modem: 5.0.7 - protobufjs: 7.5.4 - tar-fs: 2.1.4 - uuid: 10.0.0 + protobufjs: 7.6.5 + tar-fs: 2.1.5 transitivePeerDependencies: - supports-color @@ -10305,21 +9819,23 @@ snapshots: dom-accessibility-api@0.6.3: {} - dotenv@8.6.0: {} - - dprint@0.54.0: + dprint@0.55.2: optionalDependencies: - '@dprint/darwin-arm64': 0.54.0 - '@dprint/darwin-x64': 0.54.0 - '@dprint/linux-arm64-glibc': 0.54.0 - '@dprint/linux-arm64-musl': 0.54.0 - '@dprint/linux-loong64-glibc': 0.54.0 - '@dprint/linux-loong64-musl': 0.54.0 - '@dprint/linux-riscv64-glibc': 0.54.0 - '@dprint/linux-x64-glibc': 0.54.0 - '@dprint/linux-x64-musl': 0.54.0 - '@dprint/win32-arm64': 0.54.0 - '@dprint/win32-x64': 0.54.0 + '@dprint/android-arm64': 0.55.2 + '@dprint/android-x64': 0.55.2 + '@dprint/darwin-arm64': 0.55.2 + '@dprint/darwin-x64': 0.55.2 + '@dprint/linux-arm64-glibc': 0.55.2 + '@dprint/linux-arm64-musl': 0.55.2 + '@dprint/linux-loong64-glibc': 0.55.2 + '@dprint/linux-loong64-musl': 0.55.2 + '@dprint/linux-ppc64-glibc': 0.55.2 + '@dprint/linux-ppc64-musl': 0.55.2 + '@dprint/linux-riscv64-glibc': 0.55.2 + '@dprint/linux-x64-glibc': 0.55.2 + '@dprint/linux-x64-musl': 0.55.2 + '@dprint/win32-arm64': 0.55.2 + '@dprint/win32-x64': 0.55.2 eastasianwidth@0.2.0: {} @@ -10329,9 +9845,7 @@ snapshots: ee-first@1.1.1: {} - electron-to-chromium@1.5.283: {} - - electron-to-chromium@1.5.371: {} + electron-to-chromium@1.5.397: {} emoji-regex@10.6.0: {} @@ -10339,6 +9853,8 @@ snapshots: emoji-regex@9.2.2: {} + empathic@2.0.1: {} + encodeurl@1.0.2: {} encodeurl@2.0.0: {} @@ -10347,15 +9863,10 @@ snapshots: dependencies: once: 1.4.0 - enhanced-resolve@5.18.4: + enhanced-resolve@5.24.3: dependencies: graceful-fs: 4.2.11 - tapable: 2.3.0 - - enquirer@2.4.1: - dependencies: - ansi-colors: 4.1.3 - strip-ansi: 6.0.1 + tapable: 2.3.3 entities@7.0.1: {} @@ -10371,67 +9882,38 @@ snapshots: es-module-lexer@1.7.0: {} - es-module-lexer@2.0.0: {} + es-module-lexer@2.3.1: {} es6-promise@3.3.1: {} - esbuild@0.27.2: + esbuild@0.28.1: optionalDependencies: - '@esbuild/aix-ppc64': 0.27.2 - '@esbuild/android-arm': 0.27.2 - '@esbuild/android-arm64': 0.27.2 - '@esbuild/android-x64': 0.27.2 - '@esbuild/darwin-arm64': 0.27.2 - '@esbuild/darwin-x64': 0.27.2 - '@esbuild/freebsd-arm64': 0.27.2 - '@esbuild/freebsd-x64': 0.27.2 - '@esbuild/linux-arm': 0.27.2 - '@esbuild/linux-arm64': 0.27.2 - '@esbuild/linux-ia32': 0.27.2 - '@esbuild/linux-loong64': 0.27.2 - '@esbuild/linux-mips64el': 0.27.2 - '@esbuild/linux-ppc64': 0.27.2 - '@esbuild/linux-riscv64': 0.27.2 - '@esbuild/linux-s390x': 0.27.2 - '@esbuild/linux-x64': 0.27.2 - '@esbuild/netbsd-arm64': 0.27.2 - '@esbuild/netbsd-x64': 0.27.2 - '@esbuild/openbsd-arm64': 0.27.2 - '@esbuild/openbsd-x64': 0.27.2 - '@esbuild/openharmony-arm64': 0.27.2 - '@esbuild/sunos-x64': 0.27.2 - '@esbuild/win32-arm64': 0.27.2 - '@esbuild/win32-ia32': 0.27.2 - '@esbuild/win32-x64': 0.27.2 - - esbuild@0.27.7: - optionalDependencies: - '@esbuild/aix-ppc64': 0.27.7 - '@esbuild/android-arm': 0.27.7 - '@esbuild/android-arm64': 0.27.7 - '@esbuild/android-x64': 0.27.7 - '@esbuild/darwin-arm64': 0.27.7 - '@esbuild/darwin-x64': 0.27.7 - '@esbuild/freebsd-arm64': 0.27.7 - '@esbuild/freebsd-x64': 0.27.7 - '@esbuild/linux-arm': 0.27.7 - '@esbuild/linux-arm64': 0.27.7 - '@esbuild/linux-ia32': 0.27.7 - '@esbuild/linux-loong64': 0.27.7 - '@esbuild/linux-mips64el': 0.27.7 - '@esbuild/linux-ppc64': 0.27.7 - '@esbuild/linux-riscv64': 0.27.7 - '@esbuild/linux-s390x': 0.27.7 - '@esbuild/linux-x64': 0.27.7 - '@esbuild/netbsd-arm64': 0.27.7 - '@esbuild/netbsd-x64': 0.27.7 - '@esbuild/openbsd-arm64': 0.27.7 - '@esbuild/openbsd-x64': 0.27.7 - '@esbuild/openharmony-arm64': 0.27.7 - '@esbuild/sunos-x64': 0.27.7 - '@esbuild/win32-arm64': 0.27.7 - '@esbuild/win32-ia32': 0.27.7 - '@esbuild/win32-x64': 0.27.7 + '@esbuild/aix-ppc64': 0.28.1 + '@esbuild/android-arm': 0.28.1 + '@esbuild/android-arm64': 0.28.1 + '@esbuild/android-x64': 0.28.1 + '@esbuild/darwin-arm64': 0.28.1 + '@esbuild/darwin-x64': 0.28.1 + '@esbuild/freebsd-arm64': 0.28.1 + '@esbuild/freebsd-x64': 0.28.1 + '@esbuild/linux-arm': 0.28.1 + '@esbuild/linux-arm64': 0.28.1 + '@esbuild/linux-ia32': 0.28.1 + '@esbuild/linux-loong64': 0.28.1 + '@esbuild/linux-mips64el': 0.28.1 + '@esbuild/linux-ppc64': 0.28.1 + '@esbuild/linux-riscv64': 0.28.1 + '@esbuild/linux-s390x': 0.28.1 + '@esbuild/linux-x64': 0.28.1 + '@esbuild/netbsd-arm64': 0.28.1 + '@esbuild/netbsd-x64': 0.28.1 + '@esbuild/openbsd-arm64': 0.28.1 + '@esbuild/openbsd-x64': 0.28.1 + '@esbuild/openharmony-arm64': 0.28.1 + '@esbuild/sunos-x64': 0.28.1 + '@esbuild/win32-arm64': 0.28.1 + '@esbuild/win32-ia32': 0.28.1 + '@esbuild/win32-x64': 0.28.1 escalade@3.2.0: {} @@ -10449,7 +9931,7 @@ snapshots: optionalDependencies: source-map: 0.6.1 - eslint-visitor-keys@4.2.1: {} + eslint-visitor-keys@5.0.1: {} esprima@4.0.1: {} @@ -10459,7 +9941,7 @@ snapshots: estree-walker@3.0.3: dependencies: - '@types/estree': 1.0.8 + '@types/estree': 1.0.9 esutils@2.0.3: {} @@ -10469,7 +9951,7 @@ snapshots: events-universal@1.0.1: dependencies: - bare-events: 2.8.2 + bare-events: 2.9.1 transitivePeerDependencies: - bare-abort-controller @@ -10479,9 +9961,7 @@ snapshots: dependencies: fill-range: 2.2.4 - expand-template@2.0.3: {} - - expect-type@1.3.0: {} + expect-type@1.4.0: {} exponential-backoff@3.1.3: {} @@ -10489,35 +9969,31 @@ snapshots: dependencies: is-extendable: 0.1.1 - extendable-error@0.1.7: {} - fake-indexeddb@6.2.5: {} - fast-check@4.8.0: + fast-check@4.9.0: dependencies: - pure-rand: 8.4.0 + pure-rand: 8.4.1 fast-deep-equal@3.1.3: {} fast-fifo@1.3.2: {} - fast-glob@3.3.3: - dependencies: - '@nodelib/fs.stat': 2.0.5 - '@nodelib/fs.walk': 1.2.8 - glob-parent: 5.1.2 - merge2: 1.4.1 - micromatch: 4.0.8 - fast-json-stable-stringify@2.1.0: {} fast-safe-stringify@2.1.1: {} + fast-string-truncated-width@3.0.3: {} + + fast-string-width@3.0.2: + dependencies: + fast-string-truncated-width: 3.0.3 + fast-uri@3.1.2: {} - fastq@1.20.1: + fast-wrap-ansi@0.2.2: dependencies: - reusify: 1.1.0 + fast-string-width: 3.0.2 fb-dotslash@0.5.8: {} @@ -10525,23 +10001,21 @@ snapshots: dependencies: bser: 2.1.1 - fdir@6.5.0(picomatch@4.0.4): + fdir@6.5.0(picomatch@4.0.5): optionalDependencies: - picomatch: 4.0.4 + picomatch: 4.0.5 - file-uri-to-path@1.0.0: {} - - filing-cabinet@5.0.3: + filing-cabinet@5.5.1: dependencies: app-module-path: 2.2.0 commander: 12.1.0 - enhanced-resolve: 5.18.4 - module-definition: 6.0.1 - module-lookup-amd: 9.0.5 + enhanced-resolve: 5.24.3 + module-definition: 6.0.2 + module-lookup-amd: 9.1.3 resolve: 1.22.12 resolve-dependency-path: 4.0.1 - sass-lookup: 6.1.0 - stylus-lookup: 6.1.0 + sass-lookup: 6.1.2 + stylus-lookup: 6.1.2 tsconfig-paths: 4.2.0 typescript: 5.9.3 @@ -10581,8 +10055,6 @@ snapshots: make-dir: 3.1.0 pkg-dir: 4.2.0 - find-my-way-ts@0.1.6: {} - find-up@3.0.0: dependencies: locate-path: 3.0.0 @@ -10594,7 +10066,11 @@ snapshots: flow-enums-runtime@0.0.6: {} - flow-parser@0.299.0: {} + flow-estree@0.323.0: {} + + flow-parser@0.323.0: + dependencies: + flow-estree: 0.323.0 for-in@1.0.2: {} @@ -10607,20 +10083,6 @@ snapshots: fs-constants@1.0.0: {} - fs-extra@7.0.1: - dependencies: - graceful-fs: 4.2.11 - jsonfile: 4.0.0 - universalify: 0.1.2 - - fs-extra@8.1.0: - dependencies: - graceful-fs: 4.2.11 - jsonfile: 4.0.0 - universalify: 0.1.2 - - fs-readdir-recursive@1.1.0: {} - fs.realpath@1.0.0: {} fsevents@2.3.2: @@ -10637,31 +10099,25 @@ snapshots: gensync@1.0.0-beta.2: {} - get-amd-module-type@6.0.1: + get-amd-module-type@6.0.2: dependencies: - ast-module-types: 6.0.1 - node-source-walk: 7.0.1 + ast-module-types: 6.0.2 + node-source-walk: 7.0.2 get-caller-file@2.0.5: {} - get-east-asian-width@1.5.0: {} + get-east-asian-width@1.6.0: {} get-own-enumerable-property-symbols@3.0.2: {} get-package-type@0.1.0: {} - get-port@7.2.0: {} + get-port@5.1.1: {} get-tsconfig@4.14.0: dependencies: resolve-pkg-maps: 1.0.0 - github-from-package@0.0.0: {} - - glob-parent@5.1.2: - dependencies: - is-glob: 4.0.3 - glob@10.5.0: dependencies: foreground-child: 3.3.1 @@ -10671,15 +10127,6 @@ snapshots: package-json-from-dist: 1.0.1 path-scurry: 1.11.1 - glob@11.1.0: - dependencies: - foreground-child: 3.3.1 - jackspeak: 4.1.1 - minimatch: 10.2.5 - minipass: 7.1.3 - package-json-from-dist: 1.0.1 - path-scurry: 2.0.2 - glob@13.0.6: dependencies: minimatch: 10.2.5 @@ -10695,23 +10142,6 @@ snapshots: once: 1.4.0 path-is-absolute: 1.0.1 - global-prefix@4.0.0: - dependencies: - ini: 4.1.3 - kind-of: 6.0.3 - which: 4.0.0 - - globby@11.1.0: - dependencies: - array-union: 2.1.0 - dir-glob: 3.0.1 - fast-glob: 3.3.3 - ignore: 5.3.2 - merge2: 1.4.1 - slash: 3.0.0 - - globrex@0.1.2: {} - gonzales-pe@4.3.0: dependencies: minimist: 1.2.8 @@ -10721,31 +10151,32 @@ snapshots: gray-matter@3.1.1: dependencies: extend-shallow: 2.0.1 - js-yaml: 3.14.2 + js-yaml: 3.15.0 kind-of: 5.1.0 strip-bom-string: 1.0.0 gulp-header@1.8.12: dependencies: concat-with-sourcemaps: 1.1.0 - lodash.template: 4.5.0 + lodash.template: 4.18.1 through2: 2.0.5 - happy-dom@20.9.0: + happy-dom@20.11.1: dependencies: - '@types/node': 25.7.0 + '@types/node': 26.1.2 '@types/whatwg-mimetype': 3.0.2 '@types/ws': 8.18.1 + buffer-image-size: 0.6.4 entities: 7.0.1 whatwg-mimetype: 3.0.0 - ws: 8.20.0 + ws: 8.21.1 transitivePeerDependencies: - bufferutil - utf-8-validate has-flag@4.0.0: {} - hasown@2.0.3: + hasown@2.0.4: dependencies: function-bind: 1.1.2 @@ -10763,11 +10194,11 @@ snapshots: dependencies: hermes-estree: 0.35.0 - hono@4.12.18: {} + hono@4.12.28: {} html-encoding-sniffer@6.0.0: dependencies: - '@exodus/bytes': 1.15.0 + '@exodus/bytes': 1.15.1 transitivePeerDependencies: - '@noble/hashes' @@ -10797,21 +10228,25 @@ snapshots: transitivePeerDependencies: - supports-color - human-id@4.1.3: {} + human-id@4.2.0: {} iconv-lite@0.7.2: dependencies: safer-buffer: 2.1.2 - ieee754@1.2.1: {} + iconv-lite@0.7.3: + dependencies: + safer-buffer: 2.1.2 - ignore@5.3.2: {} + ieee754@1.2.1: {} image-size@1.2.1: dependencies: queue: 6.0.2 - immer@11.1.8: {} + immer@11.1.11: {} + + import-meta-resolve@4.2.0: {} imurmurhash@0.1.4: {} @@ -10828,10 +10263,6 @@ snapshots: ini@1.3.8: {} - ini@4.1.3: {} - - ini@7.0.0: {} - invariant@2.2.4: dependencies: loose-envify: 1.4.0 @@ -10850,16 +10281,11 @@ snapshots: transitivePeerDependencies: - supports-color - is-binary-path@2.1.0: - dependencies: - binary-extensions: 2.3.0 - optional: true - is-buffer@1.1.6: {} is-core-module@2.16.2: dependencies: - hasown: 2.0.3 + hasown: 2.0.4 is-docker@2.2.1: {} @@ -10871,14 +10297,8 @@ snapshots: dependencies: is-plain-object: 2.0.4 - is-extglob@2.1.1: {} - is-fullwidth-code-point@3.0.0: {} - is-glob@4.0.3: - dependencies: - is-extglob: 2.1.1 - is-in-ssh@1.0.0: {} is-inside-container@1.0.0: @@ -10911,18 +10331,10 @@ snapshots: is-stream@2.0.1: {} - is-subdir@1.2.0: - dependencies: - better-path-resolve: 1.0.0 - is-unicode-supported@0.1.0: {} is-url-superb@4.0.0: {} - is-url@1.2.4: {} - - is-windows@1.0.2: {} - is-wsl@2.2.0: dependencies: is-docker: 2.2.1 @@ -10935,8 +10347,6 @@ snapshots: isexe@2.0.0: {} - isexe@3.1.5: {} - isobject@2.1.0: dependencies: isarray: 1.0.0 @@ -10972,16 +10382,12 @@ snapshots: optionalDependencies: '@pkgjs/parseargs': 0.11.0 - jackspeak@4.1.1: - dependencies: - '@isaacs/cliui': 8.0.2 - jest-environment-node@29.7.0: dependencies: '@jest/environment': 29.7.0 '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 25.9.3 + '@types/node': 26.1.2 jest-mock: 29.7.0 jest-util: 29.7.0 @@ -10991,7 +10397,7 @@ snapshots: dependencies: '@jest/types': 29.6.3 '@types/graceful-fs': 4.1.9 - '@types/node': 25.9.3 + '@types/node': 26.1.2 anymatch: 3.1.3 fb-watchman: 2.0.2 graceful-fs: 4.2.11 @@ -11018,7 +10424,7 @@ snapshots: jest-mock@29.7.0: dependencies: '@jest/types': 29.6.3 - '@types/node': 25.9.3 + '@types/node': 26.1.2 jest-util: 29.7.0 jest-regex-util@29.6.3: {} @@ -11026,7 +10432,7 @@ snapshots: jest-util@29.7.0: dependencies: '@jest/types': 29.6.3 - '@types/node': 25.9.3 + '@types/node': 26.1.2 chalk: 4.1.2 ci-info: 3.9.0 graceful-fs: 4.2.11 @@ -11043,12 +10449,14 @@ snapshots: jest-worker@29.7.0: dependencies: - '@types/node': 25.9.3 + '@types/node': 26.1.2 jest-util: 29.7.0 merge-stream: 2.0.0 supports-color: 8.1.1 - js-base64@3.7.8: {} + jju@1.4.0: {} + + js-base64@3.8.0: {} js-levenshtein@1.1.6: {} @@ -11058,7 +10466,7 @@ snapshots: js-tokens@4.0.0: {} - js-yaml@3.14.2: + js-yaml@3.15.0: dependencies: argparse: 1.0.10 esprima: 4.0.1 @@ -11071,51 +10479,51 @@ snapshots: jsc-safe-url@0.2.4: {} - jscodeshift@17.3.0: - dependencies: - '@babel/core': 7.29.0 - '@babel/parser': 7.29.3 - '@babel/plugin-transform-class-properties': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-modules-commonjs': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-nullish-coalescing-operator': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-optional-chaining': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-private-methods': 7.28.6(@babel/core@7.29.0) - '@babel/preset-flow': 7.27.1(@babel/core@7.29.0) - '@babel/preset-typescript': 7.28.5(@babel/core@7.29.0) - '@babel/register': 7.28.6(@babel/core@7.29.0) - flow-parser: 0.299.0 + jscodeshift@17.4.0: + dependencies: + '@babel/core': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/plugin-transform-class-properties': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-modules-commonjs': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-nullish-coalescing-operator': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-optional-chaining': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-private-methods': 7.29.7(@babel/core@7.29.7) + '@babel/preset-flow': 7.29.7(@babel/core@7.29.7) + '@babel/preset-typescript': 7.29.7(@babel/core@7.29.7) + '@babel/register': 7.29.7(@babel/core@7.29.7) + flow-parser: 0.323.0 graceful-fs: 4.2.11 - micromatch: 4.0.8 neo-async: 2.6.2 picocolors: 1.1.1 - recast: 0.23.11 - tmp: 0.2.5 + picomatch: 4.0.5 + recast: 0.23.12 + tmp: 0.2.7 write-file-atomic: 5.0.1 transitivePeerDependencies: - supports-color - jsdom@29.1.1: + jsdom@30.0.0: dependencies: - '@asamuzakjp/css-color': 5.1.11 - '@asamuzakjp/dom-selector': 7.1.1 + '@asamuzakjp/css-color': 6.0.5 + '@asamuzakjp/dom-selector': 8.3.0 '@bramus/specificity': 2.4.2 - '@csstools/css-syntax-patches-for-csstree': 1.1.3(css-tree@3.2.1) - '@exodus/bytes': 1.15.0 + '@csstools/css-syntax-patches-for-csstree': 1.1.7(css-tree@3.2.1) + '@exodus/bytes': 1.15.1 css-tree: 3.2.1 data-urls: 7.0.0 decimal.js: 10.6.0 html-encoding-sniffer: 6.0.0 is-potential-custom-element-name: 1.0.1 - lru-cache: 11.3.6 + lru-cache: 11.5.2 parse5: 8.0.1 saxes: 6.0.0 symbol-tree: 3.2.4 - tough-cookie: 6.0.1 - undici: 7.25.0 + tough-cookie: 6.0.2 + undici: 8.9.0 w3c-xmlserializer: 5.0.0 webidl-conversions: 8.0.1 whatwg-mimetype: 5.0.0 - whatwg-url: 16.0.1 + whatwg-url: 17.1.0 xml-name-validator: 5.0.0 transitivePeerDependencies: - '@noble/hashes' @@ -11128,9 +10536,7 @@ snapshots: json5@2.2.3: {} - jsonfile@4.0.0: - optionalDependencies: - graceful-fs: 4.2.11 + jsonc-parser@3.3.1: {} jsonwebtoken@9.0.3: dependencies: @@ -11143,7 +10549,7 @@ snapshots: lodash.isstring: 4.0.1 lodash.once: 4.1.1 ms: 2.1.3 - semver: 7.8.0 + semver: 7.8.5 jwa@2.0.1: dependencies: @@ -11168,9 +10574,8 @@ snapshots: kubernetes-types@1.30.0: {} - lalph@0.3.137: + lalph@0.3.139: dependencies: - better-sqlite3: 12.10.0 tree-sitter: 0.21.1 tree-sitter-javascript: 0.23.1(tree-sitter@0.21.1) tree-sitter-typescript: 0.23.2(tree-sitter@0.21.1) @@ -11183,6 +10588,11 @@ snapshots: '@sqliteai/sqlite-vector-linux-x86_64-musl': 1.0.0 '@sqliteai/sqlite-vector-win32-x86_64': 1.0.0 + launch-editor@2.14.1: + dependencies: + picocolors: 1.1.1 + shell-quote: 1.10.0 + lazy-cache@2.0.2: dependencies: set-getter: 0.1.1 @@ -11215,55 +10625,54 @@ snapshots: transitivePeerDependencies: - supports-color - lightningcss-android-arm64@1.32.0: + lightningcss-android-arm64@1.33.0: optional: true - lightningcss-darwin-arm64@1.32.0: + lightningcss-darwin-arm64@1.33.0: optional: true - lightningcss-darwin-x64@1.32.0: + lightningcss-darwin-x64@1.33.0: optional: true - lightningcss-freebsd-x64@1.32.0: + lightningcss-freebsd-x64@1.33.0: optional: true - lightningcss-linux-arm-gnueabihf@1.32.0: + lightningcss-linux-arm-gnueabihf@1.33.0: optional: true - lightningcss-linux-arm64-gnu@1.32.0: + lightningcss-linux-arm64-gnu@1.33.0: optional: true - lightningcss-linux-arm64-musl@1.32.0: + lightningcss-linux-arm64-musl@1.33.0: optional: true - lightningcss-linux-x64-gnu@1.32.0: + lightningcss-linux-x64-gnu@1.33.0: optional: true - lightningcss-linux-x64-musl@1.32.0: + lightningcss-linux-x64-musl@1.33.0: optional: true - lightningcss-win32-arm64-msvc@1.32.0: + lightningcss-win32-arm64-msvc@1.33.0: optional: true - lightningcss-win32-x64-msvc@1.32.0: + lightningcss-win32-x64-msvc@1.33.0: optional: true - lightningcss@1.32.0: + lightningcss@1.33.0: dependencies: detect-libc: 2.1.2 optionalDependencies: - lightningcss-android-arm64: 1.32.0 - lightningcss-darwin-arm64: 1.32.0 - lightningcss-darwin-x64: 1.32.0 - lightningcss-freebsd-x64: 1.32.0 - lightningcss-linux-arm-gnueabihf: 1.32.0 - lightningcss-linux-arm64-gnu: 1.32.0 - lightningcss-linux-arm64-musl: 1.32.0 - lightningcss-linux-x64-gnu: 1.32.0 - lightningcss-linux-x64-musl: 1.32.0 - lightningcss-win32-arm64-msvc: 1.32.0 - lightningcss-win32-x64-msvc: 1.32.0 - optional: true + lightningcss-android-arm64: 1.33.0 + lightningcss-darwin-arm64: 1.33.0 + lightningcss-darwin-x64: 1.33.0 + lightningcss-freebsd-x64: 1.33.0 + lightningcss-linux-arm-gnueabihf: 1.33.0 + lightningcss-linux-arm64-gnu: 1.33.0 + lightningcss-linux-arm64-musl: 1.33.0 + lightningcss-linux-x64-gnu: 1.33.0 + lightningcss-linux-x64-musl: 1.33.0 + lightningcss-win32-arm64-msvc: 1.33.0 + lightningcss-win32-x64-msvc: 1.33.0 list-item@1.1.1: dependencies: @@ -11303,9 +10712,7 @@ snapshots: lodash.once@4.1.1: {} - lodash.startcase@4.4.0: {} - - lodash.template@4.5.0: + lodash.template@4.18.1: dependencies: lodash._reinterpolate: 3.0.0 lodash.templatesettings: 4.2.0 @@ -11329,11 +10736,9 @@ snapshots: dependencies: js-tokens: 4.0.0 - loupe@3.2.1: {} - lru-cache@10.4.3: {} - lru-cache@11.3.6: {} + lru-cache@11.5.2: {} lru-cache@5.1.1: dependencies: @@ -11343,13 +10748,13 @@ snapshots: lz-string@1.5.0: {} - madge@8.0.0(typescript@6.0.3): + madge@8.0.0(typescript@7.0.2): dependencies: chalk: 4.1.2 commander: 7.2.0 commondir: 1.0.1 debug: 4.4.3(supports-color@10.2.2) - dependency-tree: 11.2.0 + dependency-tree: 11.5.0 ora: 5.4.1 pluralize: 8.0.0 pretty-ms: 7.0.1 @@ -11358,7 +10763,7 @@ snapshots: ts-graphviz: 2.1.6 walkdir: 0.4.1 optionalDependencies: - typescript: 6.0.3 + typescript: 7.0.2 transitivePeerDependencies: - supports-color @@ -11366,18 +10771,11 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 - magicast@0.5.2: - dependencies: - '@babel/parser': 7.29.3 - '@babel/types': 7.29.0 - source-map-js: 1.2.1 - magicast@0.5.3: dependencies: '@babel/parser': 7.29.7 '@babel/types': 7.29.7 source-map-js: 1.2.1 - optional: true make-dir@2.1.0: dependencies: @@ -11390,7 +10788,7 @@ snapshots: make-dir@4.0.0: dependencies: - semver: 7.8.0 + semver: 7.8.5 makeerror@1.0.12: dependencies: @@ -11408,8 +10806,6 @@ snapshots: merge-stream@2.0.0: {} - merge2@1.4.1: {} - metro-babel-transformer@0.83.7: dependencies: '@babel/core': 7.29.7 @@ -11471,7 +10867,7 @@ snapshots: metro-minify-terser@0.83.7: dependencies: flow-enums-runtime: 0.0.6 - terser: 5.48.0 + terser: 5.49.0 metro-resolver@0.83.7: dependencies: @@ -11577,8 +10973,8 @@ snapshots: serialize-error: 2.1.0 source-map: 0.5.7 throat: 5.0.0 - ws: 7.5.11 - yargs: 17.7.2 + ws: 7.5.13 + yargs: 17.7.3 transitivePeerDependencies: - bufferutil - supports-color @@ -11601,17 +10997,15 @@ snapshots: mimic-fn@2.1.0: {} - mimic-response@3.1.0: {} - min-indent@1.0.1: {} - miniflare@4.20260507.1: + miniflare@4.20260706.0: dependencies: '@cspotcode/source-map-support': 0.8.1 sharp: 0.34.5 - undici: 7.24.8 - workerd: 1.20260507.1 - ws: 8.18.0 + undici: 7.28.0 + workerd: 1.20260706.1 + ws: 8.21.0 youch: 4.1.0-beta.10 transitivePeerDependencies: - bufferutil @@ -11619,19 +11013,19 @@ snapshots: minimatch@10.2.5: dependencies: - brace-expansion: 5.0.5 + brace-expansion: 5.0.7 minimatch@3.1.5: dependencies: - brace-expansion: 1.1.12 + brace-expansion: 1.1.16 minimatch@5.1.9: dependencies: - brace-expansion: 2.1.0 + brace-expansion: 2.1.3 minimatch@9.0.9: dependencies: - brace-expansion: 2.1.0 + brace-expansion: 2.1.3 minimist@1.2.8: {} @@ -11656,66 +11050,59 @@ snapshots: mock-xmlhttprequest@8.4.1: {} - module-definition@6.0.1: + module-definition@6.0.2: dependencies: - ast-module-types: 6.0.1 - node-source-walk: 7.0.1 + ast-module-types: 6.0.2 + node-source-walk: 7.0.2 - module-lookup-amd@9.0.5: + module-lookup-amd@9.1.3: dependencies: commander: 12.1.0 - glob: 7.2.3 requirejs: 2.3.8 requirejs-config-file: 4.0.0 - mri@1.2.0: {} - mrmime@2.0.1: {} ms@2.0.0: {} ms@2.1.3: {} - msgpackr-extract@3.0.3: + msgpackr-extract@3.0.4: dependencies: node-gyp-build-optional-packages: 5.2.2 optionalDependencies: - '@msgpackr-extract/msgpackr-extract-darwin-arm64': 3.0.3 - '@msgpackr-extract/msgpackr-extract-darwin-x64': 3.0.3 - '@msgpackr-extract/msgpackr-extract-linux-arm': 3.0.3 - '@msgpackr-extract/msgpackr-extract-linux-arm64': 3.0.3 - '@msgpackr-extract/msgpackr-extract-linux-x64': 3.0.3 - '@msgpackr-extract/msgpackr-extract-win32-x64': 3.0.3 + '@msgpackr-extract/msgpackr-extract-darwin-arm64': 3.0.4 + '@msgpackr-extract/msgpackr-extract-darwin-x64': 3.0.4 + '@msgpackr-extract/msgpackr-extract-linux-arm': 3.0.4 + '@msgpackr-extract/msgpackr-extract-linux-arm64': 3.0.4 + '@msgpackr-extract/msgpackr-extract-linux-x64': 3.0.4 + '@msgpackr-extract/msgpackr-extract-win32-x64': 3.0.4 optional: true - msgpackr@2.0.1: + msgpackr@2.0.4: optionalDependencies: - msgpackr-extract: 3.0.3 + msgpackr-extract: 3.0.4 - multipasta@0.2.7: {} - - mysql2@3.22.3(@types/node@25.9.3): + mysql2@3.22.6(@types/node@26.1.2): dependencies: - '@types/node': 25.9.3 + '@types/node': 26.1.2 aws-ssl-profiles: 1.1.2 denque: 2.1.0 generate-function: 2.3.1 - iconv-lite: 0.7.2 + iconv-lite: 0.7.3 long: 5.3.2 lru.min: 1.1.4 named-placeholders: 1.1.6 - sql-escaper: 1.3.3 + sql-escaper: 1.4.0 named-placeholders@1.1.6: dependencies: lru.min: 1.1.4 - nan@2.26.2: + nan@2.28.0: optional: true - nanoid@3.3.12: {} - - napi-build-utils@2.0.0: {} + nanoid@3.3.16: {} native-duplexpair@1.0.0: {} @@ -11723,11 +11110,7 @@ snapshots: neo-async@2.6.2: {} - node-abi@3.92.0: - dependencies: - semver: 7.8.0 - - node-addon-api@8.8.0: {} + node-addon-api@8.9.0: {} node-fetch-h2@2.3.0: dependencies: @@ -11750,15 +11133,13 @@ snapshots: dependencies: es6-promise: 3.3.1 - node-releases@2.0.27: {} - - node-releases@2.0.47: {} + node-releases@2.0.51: {} - node-source-walk@7.0.1: + node-source-walk@7.0.2: dependencies: - '@babel/parser': 7.29.3 + '@babel/parser': 7.29.7 - nodemailer@8.0.7: {} + nodemailer@9.0.3: {} normalize-path@3.0.0: {} @@ -11805,10 +11186,7 @@ snapshots: obuf@1.1.2: {} - obug@2.1.1: {} - - obug@2.1.2: - optional: true + obug@2.1.4: {} on-finished@2.3.0: dependencies: @@ -11849,14 +11227,14 @@ snapshots: openapi-types@12.1.3: {} - openapi-typescript@7.13.0(typescript@6.0.3): + openapi-typescript@7.13.0(typescript@7.0.2): dependencies: '@redocly/openapi-core': 1.34.11(supports-color@10.2.2) ansi-colors: 4.1.3 change-case: 5.4.4 parse-json: 8.3.0 supports-color: 10.2.2 - typescript: 6.0.3 + typescript: 7.0.2 yargs-parser: 21.1.1 ora@5.4.1: @@ -11871,22 +11249,27 @@ snapshots: strip-ansi: 6.0.1 wcwidth: 1.0.1 - outdent@0.5.0: {} - - oxlint@1.42.0: + oxlint@1.76.0: optionalDependencies: - '@oxlint/darwin-arm64': 1.42.0 - '@oxlint/darwin-x64': 1.42.0 - '@oxlint/linux-arm64-gnu': 1.42.0 - '@oxlint/linux-arm64-musl': 1.42.0 - '@oxlint/linux-x64-gnu': 1.42.0 - '@oxlint/linux-x64-musl': 1.42.0 - '@oxlint/win32-arm64': 1.42.0 - '@oxlint/win32-x64': 1.42.0 - - p-filter@2.1.0: - dependencies: - p-map: 2.1.0 + '@oxlint/binding-android-arm-eabi': 1.76.0 + '@oxlint/binding-android-arm64': 1.76.0 + '@oxlint/binding-darwin-arm64': 1.76.0 + '@oxlint/binding-darwin-x64': 1.76.0 + '@oxlint/binding-freebsd-x64': 1.76.0 + '@oxlint/binding-linux-arm-gnueabihf': 1.76.0 + '@oxlint/binding-linux-arm-musleabihf': 1.76.0 + '@oxlint/binding-linux-arm64-gnu': 1.76.0 + '@oxlint/binding-linux-arm64-musl': 1.76.0 + '@oxlint/binding-linux-ppc64-gnu': 1.76.0 + '@oxlint/binding-linux-riscv64-gnu': 1.76.0 + '@oxlint/binding-linux-riscv64-musl': 1.76.0 + '@oxlint/binding-linux-s390x-gnu': 1.76.0 + '@oxlint/binding-linux-x64-gnu': 1.76.0 + '@oxlint/binding-linux-x64-musl': 1.76.0 + '@oxlint/binding-openharmony-arm64': 1.76.0 + '@oxlint/binding-win32-arm64-msvc': 1.76.0 + '@oxlint/binding-win32-ia32-msvc': 1.76.0 + '@oxlint/binding-win32-x64-msvc': 1.76.0 p-limit@2.3.0: dependencies: @@ -11900,19 +11283,15 @@ snapshots: dependencies: p-limit: 2.3.0 - p-map@2.1.0: {} - p-try@2.2.0: {} package-json-from-dist@1.0.1: {} - package-manager-detector@0.2.11: - dependencies: - quansync: 0.2.11 + package-manager-detector@1.8.0: {} parse-json@8.3.0: dependencies: - '@babel/code-frame': 7.29.0 + '@babel/code-frame': 7.29.7 index-to-position: 1.2.0 type-fest: 4.41.0 @@ -11924,6 +11303,8 @@ snapshots: parseurl@1.3.3: {} + path-browserify@1.0.1: {} + path-exists@3.0.0: {} path-exists@4.0.0: {} @@ -11941,32 +11322,32 @@ snapshots: path-scurry@2.0.2: dependencies: - lru-cache: 11.3.6 + lru-cache: 11.5.2 minipass: 7.1.3 - path-type@4.0.0: {} - pathe@2.0.3: {} - pg-cloudflare@1.3.0: + pg-cloudflare@1.4.0: optional: true - pg-connection-string@2.12.0: {} + pg-connection-string@2.14.0: {} - pg-cursor@2.19.0(pg@8.20.0): + pg-cursor@2.21.0(pg@8.22.0): dependencies: - pg: 8.20.0 + pg: 8.22.0 pg-int8@1.0.1: {} pg-numeric@1.0.2: {} - pg-pool@3.13.0(pg@8.20.0): + pg-pool@3.14.0(pg@8.22.0): dependencies: - pg: 8.20.0 + pg: 8.22.0 pg-protocol@1.13.0: {} + pg-protocol@1.15.0: {} + pg-types@2.2.0: dependencies: pg-int8: 1.0.1 @@ -11985,15 +11366,15 @@ snapshots: postgres-interval: 3.0.0 postgres-range: 1.1.4 - pg@8.20.0: + pg@8.22.0: dependencies: - pg-connection-string: 2.12.0 - pg-pool: 3.13.0(pg@8.20.0) - pg-protocol: 1.13.0 + pg-connection-string: 2.14.0 + pg-pool: 3.14.0(pg@8.22.0) + pg-protocol: 1.15.0 pg-types: 2.2.0 pgpass: 1.0.5 optionalDependencies: - pg-cloudflare: 1.3.0 + pg-cloudflare: 1.4.0 pgpass@1.0.5: dependencies: @@ -12003,7 +11384,7 @@ snapshots: picomatch@2.3.2: {} - picomatch@4.0.4: {} + picomatch@4.0.5: {} pify@4.0.1: {} @@ -12017,11 +11398,13 @@ snapshots: dependencies: find-up: 4.1.0 - playwright-core@1.60.0: {} + pkg-pr-new@0.0.78: {} + + playwright-core@1.62.0: {} - playwright@1.60.0: + playwright@1.62.0: dependencies: - playwright-core: 1.60.0 + playwright-core: 1.62.0 optionalDependencies: fsevents: 2.3.2 @@ -12029,22 +11412,16 @@ snapshots: pngjs@7.0.0: {} - postcss-values-parser@6.0.2(postcss@8.5.14): + postcss-values-parser@6.0.2(postcss@8.5.24): dependencies: color-name: 1.1.4 is-url-superb: 4.0.0 - postcss: 8.5.14 + postcss: 8.5.24 quote-unquote: 1.0.0 - postcss@8.5.14: - dependencies: - nanoid: 3.3.12 - picocolors: 1.1.1 - source-map-js: 1.2.1 - - postcss@8.5.15: + postcss@8.5.24: dependencies: - nanoid: 3.3.12 + nanoid: 3.3.16 picocolors: 1.1.1 source-map-js: 1.2.1 @@ -12072,44 +11449,27 @@ snapshots: powershell-utils@0.1.0: {} - prebuild-install@7.1.3: - dependencies: - detect-libc: 2.1.2 - expand-template: 2.0.3 - github-from-package: 0.0.0 - minimist: 1.2.8 - mkdirp-classic: 0.5.3 - napi-build-utils: 2.0.0 - node-abi: 3.92.0 - pump: 3.0.4 - rc: 1.2.8 - simple-get: 4.0.1 - tar-fs: 2.1.4 - tunnel-agent: 0.6.0 - - precinct@12.2.0: + precinct@12.3.2: dependencies: - '@dependents/detective-less': 5.0.1 + '@dependents/detective-less': 5.0.3 commander: 12.1.0 - detective-amd: 6.0.1 - detective-cjs: 6.0.1 - detective-es6: 5.0.1 - detective-postcss: 7.0.1(postcss@8.5.14) - detective-sass: 6.0.1 - detective-scss: 5.0.1 + detective-amd: 6.1.0 + detective-cjs: 6.1.1 + detective-es6: 5.0.2 + detective-postcss: 8.0.4(postcss@8.5.24) + detective-sass: 6.0.2 + detective-scss: 5.0.2 detective-stylus: 5.0.1 - detective-typescript: 14.0.0(typescript@5.9.3) - detective-vue2: 2.2.0(typescript@5.9.3) - module-definition: 6.0.1 - node-source-walk: 7.0.1 - postcss: 8.5.14 + detective-typescript: 14.1.2(typescript@5.9.3) + detective-vue2: 2.3.0(typescript@5.9.3) + module-definition: 6.0.2 + node-source-walk: 7.0.2 + postcss: 8.5.24 typescript: 5.9.3 transitivePeerDependencies: - supports-color - prettier@2.8.8: {} - - prettier@3.8.1: {} + prettier@3.9.6: {} pretty-format@27.5.1: dependencies: @@ -12150,34 +11510,18 @@ snapshots: transitivePeerDependencies: - supports-color - protobufjs@7.5.4: - dependencies: - '@protobufjs/aspromise': 1.1.2 - '@protobufjs/base64': 1.1.2 - '@protobufjs/codegen': 2.0.5 - '@protobufjs/eventemitter': 1.1.0 - '@protobufjs/fetch': 1.1.0 - '@protobufjs/float': 1.0.2 - '@protobufjs/inquire': 1.1.1 - '@protobufjs/path': 1.1.2 - '@protobufjs/pool': 1.1.0 - '@protobufjs/utf8': 1.1.1 - '@types/node': 25.7.0 - long: 5.3.2 - - protobufjs@8.0.1: + protobufjs@7.6.5: dependencies: '@protobufjs/aspromise': 1.1.2 '@protobufjs/base64': 1.1.2 '@protobufjs/codegen': 2.0.5 - '@protobufjs/eventemitter': 1.1.0 - '@protobufjs/fetch': 1.1.0 + '@protobufjs/eventemitter': 1.1.1 + '@protobufjs/fetch': 1.1.1 '@protobufjs/float': 1.0.2 - '@protobufjs/inquire': 1.1.1 '@protobufjs/path': 1.1.2 '@protobufjs/pool': 1.1.0 - '@protobufjs/utf8': 1.1.1 - '@types/node': 25.7.0 + '@protobufjs/utf8': 1.1.2 + '@types/node': 26.1.2 long: 5.3.2 pump@3.0.4: @@ -12187,11 +11531,7 @@ snapshots: punycode@2.3.1: {} - pure-rand@8.4.0: {} - - quansync@0.2.11: {} - - queue-microtask@1.2.3: {} + pure-rand@8.4.1: {} queue@6.0.2: dependencies: @@ -12216,39 +11556,39 @@ snapshots: react-devtools-core@6.1.5: dependencies: - shell-quote: 1.8.4 - ws: 7.5.11 + shell-quote: 1.10.0 + ws: 7.5.13 transitivePeerDependencies: - bufferutil - utf-8-validate - react-dom@19.2.6(react@19.2.6): + react-dom@19.2.7(react@19.2.7): dependencies: - react: 19.2.6 + react: 19.2.7 scheduler: 0.27.0 - react-error-boundary@6.1.1(react@19.2.6): + react-error-boundary@6.1.2(react@19.2.7): dependencies: - react: 19.2.6 + react: 19.2.7 react-is@17.0.2: {} react-is@18.3.1: {} - react-native@0.83.0(@babel/core@7.29.7)(@types/react@19.2.14)(react@19.2.6): + react-native@0.83.0(@babel/core@8.0.1)(@types/react@19.2.17)(react@19.2.7): dependencies: '@jest/create-cache-key-function': 29.7.0 '@react-native/assets-registry': 0.83.0 - '@react-native/codegen': 0.83.0(@babel/core@7.29.7) + '@react-native/codegen': 0.83.0(@babel/core@8.0.1) '@react-native/community-cli-plugin': 0.83.0 '@react-native/gradle-plugin': 0.83.0 '@react-native/js-polyfills': 0.83.0 '@react-native/normalize-colors': 0.83.0 - '@react-native/virtualized-lists': 0.83.0(@types/react@19.2.14)(react-native@0.83.0(@babel/core@7.29.7)(@types/react@19.2.14)(react@19.2.6))(react@19.2.6) + '@react-native/virtualized-lists': 0.83.0(@types/react@19.2.17)(react-native@0.83.0(@babel/core@8.0.1)(@types/react@19.2.17)(react@19.2.7))(react@19.2.7) abort-controller: 3.0.0 anser: 1.4.10 ansi-regex: 5.0.1 - babel-jest: 29.7.0(@babel/core@7.29.7) + babel-jest: 29.7.0(@babel/core@8.0.1) babel-plugin-syntax-hermes-parser: 0.32.0 base64-js: 1.5.1 commander: 12.1.0 @@ -12263,18 +11603,18 @@ snapshots: nullthrows: 1.1.1 pretty-format: 29.7.0 promise: 8.3.0 - react: 19.2.6 + react: 19.2.7 react-devtools-core: 6.1.5 react-refresh: 0.14.2 regenerator-runtime: 0.13.11 scheduler: 0.27.0 - semver: 7.8.4 + semver: 7.8.5 stacktrace-parser: 0.1.11 whatwg-fetch: 3.6.20 - ws: 7.5.11 - yargs: 17.7.2 + ws: 7.5.13 + yargs: 17.7.3 optionalDependencies: - '@types/react': 19.2.14 + '@types/react': 19.2.17 transitivePeerDependencies: - '@babel/core' - '@react-native-community/cli' @@ -12285,14 +11625,7 @@ snapshots: react-refresh@0.14.2: {} - react@19.2.6: {} - - read-yaml-file@1.1.0: - dependencies: - graceful-fs: 4.2.11 - js-yaml: 3.14.2 - pify: 4.0.1 - strip-bom: 3.0.0 + react@19.2.7: {} readable-stream@2.3.8: dependencies: @@ -12322,12 +11655,9 @@ snapshots: dependencies: minimatch: 5.1.9 - readdirp@3.6.0: - dependencies: - picomatch: 2.3.2 - optional: true + readdirp@5.0.0: {} - recast@0.23.11: + recast@0.23.12: dependencies: ast-types: 0.16.1 esprima: 4.0.1 @@ -12390,160 +11720,122 @@ snapshots: retry@0.12.0: {} - reusify@1.1.0: {} - rimraf@3.0.2: dependencies: glob: 7.2.3 - rolldown@1.0.0: + rolldown@1.1.5: dependencies: - '@oxc-project/types': 0.129.0 - '@rolldown/pluginutils': 1.0.0 + '@oxc-project/types': 0.139.0 + '@rolldown/pluginutils': 1.0.1 optionalDependencies: - '@rolldown/binding-android-arm64': 1.0.0 - '@rolldown/binding-darwin-arm64': 1.0.0 - '@rolldown/binding-darwin-x64': 1.0.0 - '@rolldown/binding-freebsd-x64': 1.0.0 - '@rolldown/binding-linux-arm-gnueabihf': 1.0.0 - '@rolldown/binding-linux-arm64-gnu': 1.0.0 - '@rolldown/binding-linux-arm64-musl': 1.0.0 - '@rolldown/binding-linux-ppc64-gnu': 1.0.0 - '@rolldown/binding-linux-s390x-gnu': 1.0.0 - '@rolldown/binding-linux-x64-gnu': 1.0.0 - '@rolldown/binding-linux-x64-musl': 1.0.0 - '@rolldown/binding-openharmony-arm64': 1.0.0 - '@rolldown/binding-wasm32-wasi': 1.0.0 - '@rolldown/binding-win32-arm64-msvc': 1.0.0 - '@rolldown/binding-win32-x64-msvc': 1.0.0 - optional: true - - rollup-plugin-bundle-stats@4.22.1(core-js@3.47.0)(rolldown@1.0.0)(rollup@4.60.3)(vite@7.3.3(@types/node@25.7.0)(lightningcss@1.32.0)(terser@5.47.1)(tsx@4.21.0)(yaml@2.9.0)): - dependencies: - '@bundle-stats/cli-utils': 4.22.1(core-js@3.47.0) - rollup-plugin-stats: 2.1.1(rolldown@1.0.0)(rollup@4.60.3)(vite@7.3.3(@types/node@25.7.0)(lightningcss@1.32.0)(terser@5.47.1)(tsx@4.21.0)(yaml@2.9.0)) - rollup-plugin-webpack-stats: 3.1.1(rolldown@1.0.0)(rollup@4.60.3)(vite@7.3.3(@types/node@25.7.0)(lightningcss@1.32.0)(terser@5.47.1)(tsx@4.21.0)(yaml@2.9.0)) + '@rolldown/binding-android-arm64': 1.1.5 + '@rolldown/binding-darwin-arm64': 1.1.5 + '@rolldown/binding-darwin-x64': 1.1.5 + '@rolldown/binding-freebsd-x64': 1.1.5 + '@rolldown/binding-linux-arm-gnueabihf': 1.1.5 + '@rolldown/binding-linux-arm64-gnu': 1.1.5 + '@rolldown/binding-linux-arm64-musl': 1.1.5 + '@rolldown/binding-linux-ppc64-gnu': 1.1.5 + '@rolldown/binding-linux-s390x-gnu': 1.1.5 + '@rolldown/binding-linux-x64-gnu': 1.1.5 + '@rolldown/binding-linux-x64-musl': 1.1.5 + '@rolldown/binding-openharmony-arm64': 1.1.5 + '@rolldown/binding-wasm32-wasi': 1.1.5 + '@rolldown/binding-win32-arm64-msvc': 1.1.5 + '@rolldown/binding-win32-x64-msvc': 1.1.5 + + rollup-plugin-bundle-stats@4.22.2(core-js@3.47.0)(rolldown@1.1.5)(rollup@4.62.3)(vite@8.1.5(@types/node@26.1.2)(esbuild@0.28.1)(terser@5.49.0)(tsx@4.23.1)(yaml@2.9.0)): + dependencies: + '@bundle-stats/cli-utils': 4.22.2(core-js@3.47.0) + rollup-plugin-stats: 2.1.2(rolldown@1.1.5)(rollup@4.62.3)(vite@8.1.5(@types/node@26.1.2)(esbuild@0.28.1)(terser@5.49.0)(tsx@4.23.1)(yaml@2.9.0)) + rollup-plugin-webpack-stats: 3.1.2(rolldown@1.1.5)(rollup@4.62.3)(vite@8.1.5(@types/node@26.1.2)(esbuild@0.28.1)(terser@5.49.0)(tsx@4.23.1)(yaml@2.9.0)) tslib: 2.8.1 optionalDependencies: - rolldown: 1.0.0 - rollup: 4.60.3 - vite: 7.3.3(@types/node@25.7.0)(lightningcss@1.32.0)(terser@5.47.1)(tsx@4.21.0)(yaml@2.9.0) + rolldown: 1.1.5 + rollup: 4.62.3 + vite: 8.1.5(@types/node@26.1.2)(esbuild@0.28.1)(terser@5.49.0)(tsx@4.23.1)(yaml@2.9.0) transitivePeerDependencies: - core-js - rollup-plugin-esbuild@6.2.1(esbuild@0.27.2)(rollup@4.60.3): + rollup-plugin-esbuild@6.2.1(esbuild@0.28.1)(rollup@4.62.3): dependencies: debug: 4.4.3(supports-color@10.2.2) es-module-lexer: 1.7.0 - esbuild: 0.27.2 + esbuild: 0.28.1 get-tsconfig: 4.14.0 - rollup: 4.60.3 + rollup: 4.62.3 unplugin-utils: 0.2.5 transitivePeerDependencies: - supports-color - rollup-plugin-stats@2.1.1(rolldown@1.0.0)(rollup@4.60.3)(vite@7.3.3(@types/node@25.7.0)(lightningcss@1.32.0)(terser@5.47.1)(tsx@4.21.0)(yaml@2.9.0)): + rollup-plugin-stats@2.1.2(rolldown@1.1.5)(rollup@4.62.3)(vite@8.1.5(@types/node@26.1.2)(esbuild@0.28.1)(terser@5.49.0)(tsx@4.23.1)(yaml@2.9.0)): optionalDependencies: - rolldown: 1.0.0 - rollup: 4.60.3 - vite: 7.3.3(@types/node@25.7.0)(lightningcss@1.32.0)(terser@5.47.1)(tsx@4.21.0)(yaml@2.9.0) + rolldown: 1.1.5 + rollup: 4.62.3 + vite: 8.1.5(@types/node@26.1.2)(esbuild@0.28.1)(terser@5.49.0)(tsx@4.23.1)(yaml@2.9.0) - rollup-plugin-visualizer@7.0.1(rolldown@1.0.0)(rollup@4.60.3): + rollup-plugin-visualizer@7.0.1(rolldown@1.1.5)(rollup@4.62.3): dependencies: open: 11.0.0 - picomatch: 4.0.4 + picomatch: 4.0.5 source-map: 0.7.6 yargs: 18.0.0 optionalDependencies: - rolldown: 1.0.0 - rollup: 4.60.3 - - rollup-plugin-webpack-stats@3.1.1(rolldown@1.0.0)(rollup@4.60.3)(vite@7.3.3(@types/node@25.7.0)(lightningcss@1.32.0)(terser@5.47.1)(tsx@4.21.0)(yaml@2.9.0)): - dependencies: - rollup-plugin-stats: 2.1.1(rolldown@1.0.0)(rollup@4.60.3)(vite@7.3.3(@types/node@25.7.0)(lightningcss@1.32.0)(terser@5.47.1)(tsx@4.21.0)(yaml@2.9.0)) - optionalDependencies: - rolldown: 1.0.0 - rollup: 4.60.3 - vite: 7.3.3(@types/node@25.7.0)(lightningcss@1.32.0)(terser@5.47.1)(tsx@4.21.0)(yaml@2.9.0) + rolldown: 1.1.5 + rollup: 4.62.3 - rollup@4.60.3: + rollup-plugin-webpack-stats@3.1.2(rolldown@1.1.5)(rollup@4.62.3)(vite@8.1.5(@types/node@26.1.2)(esbuild@0.28.1)(terser@5.49.0)(tsx@4.23.1)(yaml@2.9.0)): dependencies: - '@types/estree': 1.0.8 + rollup-plugin-stats: 2.1.2(rolldown@1.1.5)(rollup@4.62.3)(vite@8.1.5(@types/node@26.1.2)(esbuild@0.28.1)(terser@5.49.0)(tsx@4.23.1)(yaml@2.9.0)) optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.60.3 - '@rollup/rollup-android-arm64': 4.60.3 - '@rollup/rollup-darwin-arm64': 4.60.3 - '@rollup/rollup-darwin-x64': 4.60.3 - '@rollup/rollup-freebsd-arm64': 4.60.3 - '@rollup/rollup-freebsd-x64': 4.60.3 - '@rollup/rollup-linux-arm-gnueabihf': 4.60.3 - '@rollup/rollup-linux-arm-musleabihf': 4.60.3 - '@rollup/rollup-linux-arm64-gnu': 4.60.3 - '@rollup/rollup-linux-arm64-musl': 4.60.3 - '@rollup/rollup-linux-loong64-gnu': 4.60.3 - '@rollup/rollup-linux-loong64-musl': 4.60.3 - '@rollup/rollup-linux-ppc64-gnu': 4.60.3 - '@rollup/rollup-linux-ppc64-musl': 4.60.3 - '@rollup/rollup-linux-riscv64-gnu': 4.60.3 - '@rollup/rollup-linux-riscv64-musl': 4.60.3 - '@rollup/rollup-linux-s390x-gnu': 4.60.3 - '@rollup/rollup-linux-x64-gnu': 4.60.3 - '@rollup/rollup-linux-x64-musl': 4.60.3 - '@rollup/rollup-openbsd-x64': 4.60.3 - '@rollup/rollup-openharmony-arm64': 4.60.3 - '@rollup/rollup-win32-arm64-msvc': 4.60.3 - '@rollup/rollup-win32-ia32-msvc': 4.60.3 - '@rollup/rollup-win32-x64-gnu': 4.60.3 - '@rollup/rollup-win32-x64-msvc': 4.60.3 - fsevents: 2.3.3 + rolldown: 1.1.5 + rollup: 4.62.3 + vite: 8.1.5(@types/node@26.1.2)(esbuild@0.28.1)(terser@5.49.0)(tsx@4.23.1)(yaml@2.9.0) - rollup@4.61.1: + rollup@4.62.3: dependencies: '@types/estree': 1.0.9 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.61.1 - '@rollup/rollup-android-arm64': 4.61.1 - '@rollup/rollup-darwin-arm64': 4.61.1 - '@rollup/rollup-darwin-x64': 4.61.1 - '@rollup/rollup-freebsd-arm64': 4.61.1 - '@rollup/rollup-freebsd-x64': 4.61.1 - '@rollup/rollup-linux-arm-gnueabihf': 4.61.1 - '@rollup/rollup-linux-arm-musleabihf': 4.61.1 - '@rollup/rollup-linux-arm64-gnu': 4.61.1 - '@rollup/rollup-linux-arm64-musl': 4.61.1 - '@rollup/rollup-linux-loong64-gnu': 4.61.1 - '@rollup/rollup-linux-loong64-musl': 4.61.1 - '@rollup/rollup-linux-ppc64-gnu': 4.61.1 - '@rollup/rollup-linux-ppc64-musl': 4.61.1 - '@rollup/rollup-linux-riscv64-gnu': 4.61.1 - '@rollup/rollup-linux-riscv64-musl': 4.61.1 - '@rollup/rollup-linux-s390x-gnu': 4.61.1 - '@rollup/rollup-linux-x64-gnu': 4.61.1 - '@rollup/rollup-linux-x64-musl': 4.61.1 - '@rollup/rollup-openbsd-x64': 4.61.1 - '@rollup/rollup-openharmony-arm64': 4.61.1 - '@rollup/rollup-win32-arm64-msvc': 4.61.1 - '@rollup/rollup-win32-ia32-msvc': 4.61.1 - '@rollup/rollup-win32-x64-gnu': 4.61.1 - '@rollup/rollup-win32-x64-msvc': 4.61.1 + '@rollup/rollup-android-arm-eabi': 4.62.3 + '@rollup/rollup-android-arm64': 4.62.3 + '@rollup/rollup-darwin-arm64': 4.62.3 + '@rollup/rollup-darwin-x64': 4.62.3 + '@rollup/rollup-freebsd-arm64': 4.62.3 + '@rollup/rollup-freebsd-x64': 4.62.3 + '@rollup/rollup-linux-arm-gnueabihf': 4.62.3 + '@rollup/rollup-linux-arm-musleabihf': 4.62.3 + '@rollup/rollup-linux-arm64-gnu': 4.62.3 + '@rollup/rollup-linux-arm64-musl': 4.62.3 + '@rollup/rollup-linux-loong64-gnu': 4.62.3 + '@rollup/rollup-linux-loong64-musl': 4.62.3 + '@rollup/rollup-linux-ppc64-gnu': 4.62.3 + '@rollup/rollup-linux-ppc64-musl': 4.62.3 + '@rollup/rollup-linux-riscv64-gnu': 4.62.3 + '@rollup/rollup-linux-riscv64-musl': 4.62.3 + '@rollup/rollup-linux-s390x-gnu': 4.62.3 + '@rollup/rollup-linux-x64-gnu': 4.62.3 + '@rollup/rollup-linux-x64-musl': 4.62.3 + '@rollup/rollup-openbsd-x64': 4.62.3 + '@rollup/rollup-openharmony-arm64': 4.62.3 + '@rollup/rollup-win32-arm64-msvc': 4.62.3 + '@rollup/rollup-win32-ia32-msvc': 4.62.3 + '@rollup/rollup-win32-x64-gnu': 4.62.3 + '@rollup/rollup-win32-x64-msvc': 4.62.3 fsevents: 2.3.3 run-applescript@7.1.0: {} - run-parallel@1.2.0: - dependencies: - queue-microtask: 1.2.3 - safe-buffer@5.1.2: {} safe-buffer@5.2.1: {} safer-buffer@2.1.2: {} - sass-lookup@6.1.0: + sass-lookup@6.1.2: dependencies: commander: 12.1.0 - enhanced-resolve: 5.18.4 + enhanced-resolve: 5.24.3 saxes@6.0.0: dependencies: @@ -12555,9 +11847,7 @@ snapshots: semver@6.3.1: {} - semver@7.8.0: {} - - semver@7.8.4: {} + semver@7.8.5: {} send@0.19.2: dependencies: @@ -12579,15 +11869,15 @@ snapshots: serialize-error@2.1.0: {} - serialize-javascript@7.0.5: {} + serialize-javascript@7.0.7: {} serialize-query-params@2.0.4: {} - seroval-plugins@1.5.2(seroval@1.5.2): + seroval-plugins@1.5.5(seroval@1.5.5): dependencies: - seroval: 1.5.2 + seroval: 1.5.5 - seroval@1.5.2: {} + seroval@1.5.5: {} serve-static@1.16.3: dependencies: @@ -12612,7 +11902,7 @@ snapshots: dependencies: '@img/colour': 1.1.0 detect-libc: 2.1.2 - semver: 7.8.0 + semver: 7.8.5 optionalDependencies: '@img/sharp-darwin-arm64': 0.34.5 '@img/sharp-darwin-x64': 0.34.5 @@ -12645,7 +11935,7 @@ snapshots: shebang-regex@3.0.0: {} - shell-quote@1.8.4: {} + shell-quote@1.10.0: {} should-equal@2.0.0: dependencies: @@ -12679,31 +11969,25 @@ snapshots: signal-exit@4.1.0: {} - simple-concat@1.0.1: {} - - simple-get@4.0.1: - dependencies: - decompress-response: 6.0.0 - once: 1.4.0 - simple-concat: 1.0.1 - sirv@3.0.2: dependencies: '@polka/url': 1.0.0-next.29 mrmime: 2.0.1 totalist: 3.0.1 - slash@2.0.0: {} + sisteransi@1.0.5: {} slash@3.0.0: {} - smob@1.6.1: {} + slash@5.1.0: {} - solid-js@1.9.12: + smob@1.6.2: {} + + solid-js@1.9.14: dependencies: csstype: 3.2.3 - seroval: 1.5.2 - seroval-plugins: 1.5.2(seroval@1.5.2) + seroval: 1.5.5 + seroval-plugins: 1.5.5(seroval@1.5.5) source-map-js@1.2.1: {} @@ -12718,11 +12002,6 @@ snapshots: source-map@0.7.6: {} - spawndamnit@3.0.1: - dependencies: - cross-spawn: 7.0.6 - signal-exit: 4.1.0 - split-ca@1.0.1: {} split2@4.2.0: {} @@ -12731,7 +12010,7 @@ snapshots: sprintf-js@1.1.3: {} - sql-escaper@1.3.3: {} + sql-escaper@1.4.0: {} ssh-remote-port-forward@1.0.4: dependencies: @@ -12744,7 +12023,7 @@ snapshots: bcrypt-pbkdf: 1.0.2 optionalDependencies: cpu-features: 0.0.10 - nan: 2.26.2 + nan: 2.28.0 stack-utils@2.0.6: dependencies: @@ -12764,11 +12043,11 @@ snapshots: statuses@2.0.2: {} - std-env@4.1.0: {} + std-env@4.2.0: {} stream-chain@2.2.5: {} - stream-chain@3.6.2: {} + stream-chain@3.6.3: {} stream-json@1.9.1: dependencies: @@ -12778,7 +12057,7 @@ snapshots: dependencies: any-promise: 1.3.0 - streamx@2.25.0: + streamx@2.28.0: dependencies: events-universal: 1.0.1 fast-fifo: 1.3.2 @@ -12802,7 +12081,7 @@ snapshots: string-width@7.2.0: dependencies: emoji-regex: 10.6.0 - get-east-asian-width: 1.5.0 + get-east-asian-width: 1.6.0 strip-ansi: 7.2.0 string_decoder@1.1.1: @@ -12839,7 +12118,7 @@ snapshots: strip-json-comments@2.0.1: {} - stylus-lookup@6.1.0: + stylus-lookup@6.1.2: dependencies: commander: 12.1.0 @@ -12875,22 +12154,22 @@ snapshots: symbol-tree@3.2.4: {} - tapable@2.3.0: {} + tapable@2.3.3: {} - tar-fs@2.1.4: + tar-fs@2.1.5: dependencies: chownr: 1.1.4 mkdirp-classic: 0.5.3 pump: 3.0.4 tar-stream: 2.2.0 - tar-fs@3.1.2: + tar-fs@3.1.3: dependencies: pump: 3.0.4 - tar-stream: 3.1.8 + tar-stream: 3.2.0 optionalDependencies: - bare-fs: 4.7.0 - bare-path: 3.0.0 + bare-fs: 4.7.4 + bare-path: 3.1.1 transitivePeerDependencies: - bare-abort-controller - bare-buffer @@ -12904,18 +12183,18 @@ snapshots: inherits: 2.0.4 readable-stream: 3.6.2 - tar-stream@3.1.8: + tar-stream@3.2.0: dependencies: - b4a: 1.8.0 - bare-fs: 4.7.0 + b4a: 1.8.1 + bare-fs: 4.7.4 fast-fifo: 1.3.2 - streamx: 2.25.0 + streamx: 2.28.0 transitivePeerDependencies: - bare-abort-controller - bare-buffer - react-native-b4a - tar@7.5.15: + tar@7.5.19: dependencies: '@isaacs/fs-minipass': 4.0.1 chownr: 3.0.0 @@ -12929,7 +12208,7 @@ snapshots: '@azure/identity': 4.13.1 '@azure/keyvault-keys': 4.10.0(@azure/core-client@1.10.1) '@js-joda/core': 5.7.0 - '@types/node': 25.7.0 + '@types/node': 26.1.2 bl: 6.1.6 iconv-lite: 0.7.2 js-md4: 0.3.2 @@ -12941,21 +12220,12 @@ snapshots: teex@1.0.1: dependencies: - streamx: 2.25.0 + streamx: 2.28.0 transitivePeerDependencies: - bare-abort-controller - react-native-b4a - term-size@2.2.1: {} - - terser@5.47.1: - dependencies: - '@jridgewell/source-map': 0.3.11 - acorn: 8.16.0 - commander: 2.20.3 - source-map-support: 0.5.21 - - terser@5.48.0: + terser@5.49.0: dependencies: '@jridgewell/source-map': 0.3.11 acorn: 8.17.0 @@ -12968,7 +12238,30 @@ snapshots: glob: 7.2.3 minimatch: 3.1.5 - testcontainers@11.14.0: + testcontainers@12.0.4: + dependencies: + '@balena/dockerignore': 1.0.2 + '@types/dockerode': 4.0.1 + archiver: 7.0.1 + async-lock: 1.4.1 + byline: 5.0.0 + debug: 4.4.3(supports-color@10.2.2) + docker-compose: 1.4.2 + dockerode: 5.0.1 + get-port: 5.1.1 + proper-lockfile: 4.1.2 + properties-reader: 3.0.1 + ssh-remote-port-forward: 1.0.4 + tar-fs: 3.1.3 + tmp: 0.2.7 + undici: 8.9.0 + transitivePeerDependencies: + - bare-abort-controller + - bare-buffer + - react-native-b4a + - supports-color + + testcontainers@12.1.0: dependencies: '@balena/dockerignore': 1.0.2 '@types/dockerode': 4.0.1 @@ -12977,14 +12270,14 @@ snapshots: byline: 5.0.0 debug: 4.4.3(supports-color@10.2.2) docker-compose: 1.4.2 - dockerode: 4.0.10 - get-port: 7.2.0 + dockerode: 5.0.1 + get-port: 5.1.1 proper-lockfile: 4.1.2 properties-reader: 3.0.1 ssh-remote-port-forward: 1.0.4 - tar-fs: 3.1.2 - tmp: 0.2.5 - undici: 7.25.0 + tar-fs: 3.1.3 + tmp: 0.2.7 + undici: 8.9.0 transitivePeerDependencies: - bare-abort-controller - bare-buffer @@ -12993,7 +12286,7 @@ snapshots: text-decoder@1.2.7: dependencies: - b4a: 1.8.0 + b4a: 1.8.1 transitivePeerDependencies: - react-native-b4a @@ -13008,31 +12301,24 @@ snapshots: tinybench@2.9.0: {} - tinybench@6.0.1: {} + tinybench@6.0.2: {} - tinyexec@1.1.1: {} - - tinyglobby@0.2.16: - dependencies: - fdir: 6.5.0(picomatch@4.0.4) - picomatch: 4.0.4 + tinyexec@1.2.4: {} tinyglobby@0.2.17: dependencies: - fdir: 6.5.0(picomatch@4.0.4) - picomatch: 4.0.4 - - tinyrainbow@2.0.0: {} + fdir: 6.5.0(picomatch@4.0.5) + picomatch: 4.0.5 tinyrainbow@3.1.0: {} - tldts-core@7.0.30: {} + tldts-core@7.4.9: {} - tldts@7.0.30: + tldts@7.4.9: dependencies: - tldts-core: 7.0.30 + tldts-core: 7.4.9 - tmp@0.2.5: {} + tmp@0.2.7: {} tmpl@1.0.5: {} @@ -13046,13 +12332,11 @@ snapshots: toidentifier@1.0.1: {} - toml@4.1.1: {} - totalist@3.0.1: {} - tough-cookie@6.0.1: + tough-cookie@6.0.2: dependencies: - tldts: 7.0.30 + tldts: 7.4.9 tr46@0.0.3: {} @@ -13062,14 +12346,14 @@ snapshots: tree-sitter-javascript@0.23.1(tree-sitter@0.21.1): dependencies: - node-addon-api: 8.8.0 + node-addon-api: 8.9.0 node-gyp-build: 4.8.4 optionalDependencies: tree-sitter: 0.21.1 tree-sitter-typescript@0.23.2(tree-sitter@0.21.1): dependencies: - node-addon-api: 8.8.0 + node-addon-api: 8.9.0 node-gyp-build: 4.8.4 tree-sitter-javascript: 0.23.1(tree-sitter@0.21.1) optionalDependencies: @@ -13077,10 +12361,10 @@ snapshots: tree-sitter@0.21.1: dependencies: - node-addon-api: 8.8.0 + node-addon-api: 8.9.0 node-gyp-build: 4.8.4 - ts-api-utils@2.4.0(typescript@5.9.3): + ts-api-utils@2.5.0(typescript@5.9.3): dependencies: typescript: 5.9.3 @@ -13091,14 +12375,10 @@ snapshots: '@ts-graphviz/common': 2.1.5 '@ts-graphviz/core': 2.0.7 - ts-patch@4.0.1: + ts-morph@27.0.2: dependencies: - chalk: 4.1.2 - global-prefix: 4.0.0 - minimist: 1.2.8 - resolve: 1.22.12 - semver: 7.8.0 - strip-ansi: 6.0.1 + '@ts-morph/common': 0.28.1 + code-block-writer: 13.0.3 tsconfck@3.1.6(typescript@6.0.3): optionalDependencies: @@ -13112,21 +12392,16 @@ snapshots: tslib@2.8.1: {} - tstyche@7.1.0(typescript@6.0.3): + tstyche@7.2.2(typescript@7.0.2): optionalDependencies: - typescript: 6.0.3 + typescript: 7.0.2 - tsx@4.21.0: + tsx@4.23.1: dependencies: - esbuild: 0.27.2 - get-tsconfig: 4.14.0 + esbuild: 0.28.1 optionalDependencies: fsevents: 2.3.3 - tunnel-agent@0.6.0: - dependencies: - safe-buffer: 5.2.1 - tweetnacl@0.14.5: {} type-detect@4.0.8: {} @@ -13141,36 +12416,49 @@ snapshots: typescript@6.0.3: {} - undici-types@5.26.5: {} - - undici-types@7.21.0: {} + typescript@7.0.2: + optionalDependencies: + '@typescript/typescript-aix-ppc64': 7.0.2 + '@typescript/typescript-darwin-arm64': 7.0.2 + '@typescript/typescript-darwin-x64': 7.0.2 + '@typescript/typescript-freebsd-arm64': 7.0.2 + '@typescript/typescript-freebsd-x64': 7.0.2 + '@typescript/typescript-linux-arm': 7.0.2 + '@typescript/typescript-linux-arm64': 7.0.2 + '@typescript/typescript-linux-loong64': 7.0.2 + '@typescript/typescript-linux-mips64el': 7.0.2 + '@typescript/typescript-linux-ppc64': 7.0.2 + '@typescript/typescript-linux-riscv64': 7.0.2 + '@typescript/typescript-linux-s390x': 7.0.2 + '@typescript/typescript-linux-x64': 7.0.2 + '@typescript/typescript-netbsd-arm64': 7.0.2 + '@typescript/typescript-netbsd-x64': 7.0.2 + '@typescript/typescript-openbsd-arm64': 7.0.2 + '@typescript/typescript-openbsd-x64': 7.0.2 + '@typescript/typescript-sunos-x64': 7.0.2 + '@typescript/typescript-win32-arm64': 7.0.2 + '@typescript/typescript-win32-x64': 7.0.2 - undici-types@7.24.6: {} + undici-types@5.26.5: {} - undici@7.24.8: {} + undici-types@8.3.0: {} - undici@7.25.0: {} + undici@7.28.0: {} - undici@8.2.0: {} + undici@8.7.0: {} - universalify@0.1.2: {} + undici@8.9.0: {} unpipe@1.0.0: {} unplugin-utils@0.2.5: dependencies: pathe: 2.0.3 - picomatch: 4.0.4 - - update-browserslist-db@1.2.3(browserslist@4.28.1): - dependencies: - browserslist: 4.28.1 - escalade: 3.2.0 - picocolors: 1.1.1 + picomatch: 4.0.5 - update-browserslist-db@1.2.3(browserslist@4.28.2): + update-browserslist-db@1.2.3(browserslist@4.28.7): dependencies: - browserslist: 4.28.2 + browserslist: 4.28.7 escalade: 3.2.0 picocolors: 1.1.1 @@ -13180,139 +12468,77 @@ snapshots: utils-merge@1.0.1: {} - uuid@10.0.0: {} - - uuid@14.0.0: {} + uuid@14.0.1: {} uuid@8.3.2: {} - valibot@1.4.0(typescript@6.0.3): - optionalDependencies: - typescript: 6.0.3 - - vite-tsconfig-paths@6.1.1(typescript@6.0.3)(vite@7.3.3(@types/node@25.7.0)(lightningcss@1.32.0)(terser@5.47.1)(tsx@4.21.0)(yaml@2.9.0)): - dependencies: - debug: 4.4.3(supports-color@10.2.2) - globrex: 0.1.2 - tsconfck: 3.1.6(typescript@6.0.3) - vite: 7.3.3(@types/node@25.7.0)(lightningcss@1.32.0)(terser@5.47.1)(tsx@4.21.0)(yaml@2.9.0) - transitivePeerDependencies: - - supports-color - - typescript - - vite@7.3.3(@types/node@25.7.0)(lightningcss@1.32.0)(terser@5.47.1)(tsx@4.21.0)(yaml@2.9.0): - dependencies: - esbuild: 0.27.2 - fdir: 6.5.0(picomatch@4.0.4) - picomatch: 4.0.4 - postcss: 8.5.14 - rollup: 4.60.3 - tinyglobby: 0.2.16 + valibot@1.4.2(typescript@7.0.2): optionalDependencies: - '@types/node': 25.7.0 - fsevents: 2.3.3 - lightningcss: 1.32.0 - terser: 5.47.1 - tsx: 4.21.0 - yaml: 2.9.0 + typescript: 7.0.2 - vite@7.3.5(@types/node@25.7.0)(lightningcss@1.32.0)(terser@5.48.0)(tsx@4.21.0)(yaml@2.9.0): + vite@8.1.5(@types/node@26.1.2)(esbuild@0.28.1)(terser@5.49.0)(tsx@4.23.1)(yaml@2.9.0): dependencies: - esbuild: 0.27.7 - fdir: 6.5.0(picomatch@4.0.4) - picomatch: 4.0.4 - postcss: 8.5.15 - rollup: 4.61.1 + lightningcss: 1.33.0 + picomatch: 4.0.5 + postcss: 8.5.24 + rolldown: 1.1.5 tinyglobby: 0.2.17 optionalDependencies: - '@types/node': 25.7.0 + '@types/node': 26.1.2 + esbuild: 0.28.1 fsevents: 2.3.3 - lightningcss: 1.32.0 - terser: 5.48.0 - tsx: 4.21.0 + terser: 5.49.0 + tsx: 4.23.1 yaml: 2.9.0 - vitest-websocket-mock@0.5.0(vitest@4.1.4): + vitest-websocket-mock@0.7.0(vitest@4.1.10): dependencies: - '@vitest/utils': 3.2.4 mock-socket: 9.3.1 - vitest: 4.1.4(@edge-runtime/vm@5.0.0)(@opentelemetry/api@1.9.1)(@types/node@25.7.0)(@vitest/coverage-v8@4.1.6)(happy-dom@20.9.0)(jsdom@29.1.1)(vite@7.3.3(@types/node@25.7.0)(lightningcss@1.32.0)(terser@5.47.1)(tsx@4.21.0)(yaml@2.9.0)) - - vitest@4.1.4(@edge-runtime/vm@5.0.0)(@opentelemetry/api@1.9.1)(@types/node@25.7.0)(@vitest/coverage-v8@4.1.4)(happy-dom@20.9.0)(jsdom@29.1.1)(vite@7.3.5(@types/node@25.7.0)(lightningcss@1.32.0)(terser@5.48.0)(tsx@4.21.0)(yaml@2.9.0)): - dependencies: - '@vitest/expect': 4.1.4 - '@vitest/mocker': 4.1.4(vite@7.3.5(@types/node@25.7.0)(lightningcss@1.32.0)(terser@5.48.0)(tsx@4.21.0)(yaml@2.9.0)) - '@vitest/pretty-format': 4.1.4 - '@vitest/runner': 4.1.4 - '@vitest/snapshot': 4.1.4 - '@vitest/spy': 4.1.4 - '@vitest/utils': 4.1.4 - es-module-lexer: 2.0.0 - expect-type: 1.3.0 - magic-string: 0.30.21 - obug: 2.1.1 - pathe: 2.0.3 - picomatch: 4.0.4 - std-env: 4.1.0 - tinybench: 2.9.0 - tinyexec: 1.1.1 - tinyglobby: 0.2.16 - tinyrainbow: 3.1.0 - vite: 7.3.5(@types/node@25.7.0)(lightningcss@1.32.0)(terser@5.48.0)(tsx@4.21.0)(yaml@2.9.0) - why-is-node-running: 2.3.0 - optionalDependencies: - '@edge-runtime/vm': 5.0.0 - '@opentelemetry/api': 1.9.1 - '@types/node': 25.7.0 - '@vitest/coverage-v8': 4.1.4(@vitest/browser@4.1.4)(vitest@4.1.4) - happy-dom: 20.9.0 - jsdom: 29.1.1 - transitivePeerDependencies: - - msw - - vitest@4.1.4(@edge-runtime/vm@5.0.0)(@opentelemetry/api@1.9.1)(@types/node@25.7.0)(@vitest/coverage-v8@4.1.6)(happy-dom@20.9.0)(jsdom@29.1.1)(vite@7.3.3(@types/node@25.7.0)(lightningcss@1.32.0)(terser@5.47.1)(tsx@4.21.0)(yaml@2.9.0)): - dependencies: - '@vitest/expect': 4.1.4 - '@vitest/mocker': 4.1.4(vite@7.3.3(@types/node@25.7.0)(lightningcss@1.32.0)(terser@5.47.1)(tsx@4.21.0)(yaml@2.9.0)) - '@vitest/pretty-format': 4.1.4 - '@vitest/runner': 4.1.4 - '@vitest/snapshot': 4.1.4 - '@vitest/spy': 4.1.4 - '@vitest/utils': 4.1.4 - es-module-lexer: 2.0.0 - expect-type: 1.3.0 + vitest: 4.1.10(@edge-runtime/vm@5.0.0)(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/coverage-v8@4.1.10)(happy-dom@20.11.1)(jsdom@30.0.0)(vite@8.1.5(@types/node@26.1.2)(esbuild@0.28.1)(terser@5.49.0)(tsx@4.23.1)(yaml@2.9.0)) + + vitest@4.1.10(@edge-runtime/vm@5.0.0)(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/coverage-v8@4.1.10)(happy-dom@20.11.1)(jsdom@30.0.0)(vite@8.1.5(@types/node@26.1.2)(esbuild@0.28.1)(terser@5.49.0)(tsx@4.23.1)(yaml@2.9.0)): + dependencies: + '@vitest/expect': 4.1.10 + '@vitest/mocker': 4.1.10(vite@8.1.5(@types/node@26.1.2)(esbuild@0.28.1)(terser@5.49.0)(tsx@4.23.1)(yaml@2.9.0)) + '@vitest/pretty-format': 4.1.10 + '@vitest/runner': 4.1.10 + '@vitest/snapshot': 4.1.10 + '@vitest/spy': 4.1.10 + '@vitest/utils': 4.1.10 + es-module-lexer: 2.3.1 + expect-type: 1.4.0 magic-string: 0.30.21 - obug: 2.1.1 + obug: 2.1.4 pathe: 2.0.3 - picomatch: 4.0.4 - std-env: 4.1.0 + picomatch: 4.0.5 + std-env: 4.2.0 tinybench: 2.9.0 - tinyexec: 1.1.1 - tinyglobby: 0.2.16 + tinyexec: 1.2.4 + tinyglobby: 0.2.17 tinyrainbow: 3.1.0 - vite: 7.3.3(@types/node@25.7.0)(lightningcss@1.32.0)(terser@5.47.1)(tsx@4.21.0)(yaml@2.9.0) + vite: 8.1.5(@types/node@26.1.2)(esbuild@0.28.1)(terser@5.49.0)(tsx@4.23.1)(yaml@2.9.0) why-is-node-running: 2.3.0 optionalDependencies: '@edge-runtime/vm': 5.0.0 '@opentelemetry/api': 1.9.1 - '@types/node': 25.7.0 - '@vitest/coverage-v8': 4.1.6(@vitest/browser@4.1.6)(vitest@4.1.4) - happy-dom: 20.9.0 - jsdom: 29.1.1 + '@types/node': 26.1.2 + '@vitest/coverage-v8': 4.1.10(@vitest/browser@4.1.10)(vitest@4.1.10) + happy-dom: 20.11.1 + jsdom: 30.0.0 transitivePeerDependencies: - msw vlq@1.0.1: {} - vue@3.5.34(typescript@6.0.3): + vue@3.5.39(typescript@7.0.2): dependencies: - '@vue/compiler-dom': 3.5.34 - '@vue/compiler-sfc': 3.5.34 - '@vue/runtime-dom': 3.5.34 - '@vue/server-renderer': 3.5.34(vue@3.5.34(typescript@6.0.3)) - '@vue/shared': 3.5.34 + '@vue/compiler-dom': 3.5.39 + '@vue/compiler-sfc': 3.5.39 + '@vue/runtime-dom': 3.5.39 + '@vue/server-renderer': 3.5.39(vue@3.5.39(typescript@7.0.2)) + '@vue/shared': 3.5.39 optionalDependencies: - typescript: 6.0.3 + typescript: 7.0.2 w3c-xmlserializer@5.0.0: dependencies: @@ -13340,7 +12566,15 @@ snapshots: whatwg-url@16.0.1: dependencies: - '@exodus/bytes': 1.15.0 + '@exodus/bytes': 1.15.1 + tr46: 6.0.0 + webidl-conversions: 8.0.1 + transitivePeerDependencies: + - '@noble/hashes' + + whatwg-url@17.1.0: + dependencies: + '@exodus/bytes': 1.15.1 tr46: 6.0.0 webidl-conversions: 8.0.1 transitivePeerDependencies: @@ -13355,22 +12589,18 @@ snapshots: dependencies: isexe: 2.0.0 - which@4.0.0: - dependencies: - isexe: 3.1.5 - why-is-node-running@2.3.0: dependencies: siginfo: 2.0.0 stackback: 0.0.2 - workerd@1.20260507.1: + workerd@1.20260706.1: optionalDependencies: - '@cloudflare/workerd-darwin-64': 1.20260507.1 - '@cloudflare/workerd-darwin-arm64': 1.20260507.1 - '@cloudflare/workerd-linux-64': 1.20260507.1 - '@cloudflare/workerd-linux-arm64': 1.20260507.1 - '@cloudflare/workerd-windows-64': 1.20260507.1 + '@cloudflare/workerd-darwin-64': 1.20260706.1 + '@cloudflare/workerd-darwin-arm64': 1.20260706.1 + '@cloudflare/workerd-linux-64': 1.20260706.1 + '@cloudflare/workerd-linux-arm64': 1.20260706.1 + '@cloudflare/workerd-windows-64': 1.20260706.1 wrap-ansi@7.0.0: dependencies: @@ -13402,14 +12632,11 @@ snapshots: imurmurhash: 0.1.4 signal-exit: 4.1.0 - ws@7.5.11: {} + ws@7.5.13: {} - ws@8.18.0: {} + ws@8.21.0: {} - ws@8.20.0: {} - - ws@8.21.0: - optional: true + ws@8.21.1: {} wsl-utils@0.1.0: dependencies: @@ -13452,6 +12679,16 @@ snapshots: y18n: 5.0.8 yargs-parser: 21.1.1 + yargs@17.7.3: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + yargs@18.0.0: dependencies: cliui: 9.0.1 @@ -13470,7 +12707,7 @@ snapshots: dependencies: '@poppinss/colors': 4.1.6 '@poppinss/dumper': 0.6.5 - '@speed-highlight/core': 1.2.15 + '@speed-highlight/core': 1.2.17 cookie: 1.1.1 youch-core: 0.3.3 diff --git a/repos/effect/pnpm-workspace.yaml b/repos/effect/pnpm-workspace.yaml index f9c2752f30..28c578a50d 100644 --- a/repos/effect/pnpm-workspace.yaml +++ b/repos/effect/pnpm-workspace.yaml @@ -8,3 +8,27 @@ packages: - packages/sql/* - packages/tools/* - examples/* + +ignoredBuiltDependencies: + - core-js + - cpu-features + - dprint + - esbuild + - lmdb + - msgpackr-extract + - msw + - protobufjs + - sharp + - ssh2 + - unrs-resolver + - workerd + +onlyBuiltDependencies: + - better-sqlite3 + - tree-sitter + - tree-sitter-javascript + - tree-sitter-typescript + +patchedDependencies: + '@changesets/get-github-info@1.0.0-next.4': patches/@changesets__get-github-info@1.0.0-next.4.patch + '@changesets/read@1.0.0-next.10': patches/@changesets__read@1.0.0-next.10.patch diff --git a/repos/effect/scratchpad/tsconfig.json b/repos/effect/scratchpad/tsconfig.json index 96cfc9e673..cf5be2e5f8 100644 --- a/repos/effect/scratchpad/tsconfig.json +++ b/repos/effect/scratchpad/tsconfig.json @@ -1,5 +1,5 @@ { - "$schema": "http://json.schemastore.org/tsconfig", + "$schema": "https://json.schemastore.org/tsconfig", "extends": "../tsconfig.base.json", "include": ["**/*.ts"], "compilerOptions": { diff --git a/repos/effect/scripts/bundle-analyze.sh b/repos/effect/scripts/bundle-analyze.sh index 74ff292e57..4798bbe0e9 100755 --- a/repos/effect/scripts/bundle-analyze.sh +++ b/repos/effect/scripts/bundle-analyze.sh @@ -54,7 +54,7 @@ ROOT="$(git rev-parse --show-toplevel)" cd "$ROOT" echo "Building current checkout..." -pnpm build:tsgo +pnpm build node packages/tools/bundle/src/bin.ts visualize-selected \ --output-dir "$OUTPUT_DIR" \ diff --git a/repos/effect/scripts/bundle-compare-selected.sh b/repos/effect/scripts/bundle-compare-selected.sh index 0df1d9ffb7..bb91209bfc 100755 --- a/repos/effect/scripts/bundle-compare-selected.sh +++ b/repos/effect/scripts/bundle-compare-selected.sh @@ -72,7 +72,7 @@ trap cleanup EXIT cd "$ROOT" echo "Building current checkout..." -pnpm build:tsgo +pnpm build BASE_COMMIT="$(git rev-parse "$BASE_REF")" @@ -88,7 +88,7 @@ else echo "Creating base worktree at $BASE_DIR ($BASE_REF @ ${BASE_COMMIT:0:8})" git worktree add --detach "$BASE_DIR" "$BASE_COMMIT" echo "Building base checkout..." - (cd "$BASE_DIR" && pnpm install && pnpm build:tsgo) + (cd "$BASE_DIR" && pnpm install && pnpm build) printf "%s\n" "$BASE_COMMIT" > "$BASE_STAMP" fi diff --git a/repos/effect/scripts/bundle-compare.sh b/repos/effect/scripts/bundle-compare.sh index 1bc1245370..def9e2806f 100755 --- a/repos/effect/scripts/bundle-compare.sh +++ b/repos/effect/scripts/bundle-compare.sh @@ -18,7 +18,7 @@ OUTPUT_PATH="$ROOT/tmp/bundle-stats.txt" cd "$ROOT" echo "Building current checkout..." -pnpm build:tsgo +pnpm build BASE_COMMIT="$(git rev-parse "$BASE_REF")" @@ -31,7 +31,7 @@ else echo "Creating base worktree at $BASE_DIR ($BASE_REF @ ${BASE_COMMIT:0:8})" git worktree add --detach "$BASE_DIR" "$BASE_COMMIT" echo "Building base checkout..." - (cd "$BASE_DIR" && pnpm install && pnpm build:tsgo) + (cd "$BASE_DIR" && pnpm install && pnpm build) fi node packages/tools/bundle/src/bin.ts compare \ diff --git a/repos/effect/scripts/clean.mjs b/repos/effect/scripts/clean.mjs index 24dd7d0493..9d80d33d28 100644 --- a/repos/effect/scripts/clean.mjs +++ b/repos/effect/scripts/clean.mjs @@ -3,7 +3,10 @@ import * as Fs from "node:fs" const dirs = [".", ...Glob.sync("packages/*/"), ...Glob.sync("packages/sql/*/"), ...Glob.sync("packages/tools/*/"), ...Glob.sync("packages/ai/*/"), ...Glob.sync("packages/atom/*/")] dirs.forEach((pkg) => { - const files = [".tsbuildinfo", "tsconfig.tsbuildinfo", "docs", "build", "dist", "coverage"] + const files = [".tsbuildinfo", "tsconfig.tsbuildinfo", "tsconfig.fixtures.tsbuildinfo", "tsconfig.src.tsbuildinfo", "docs", "build", "dist", "coverage"] + if (pkg !== ".") { + files.push("AGENTS.md", "CLAUDE.md", "ai-docs") + } files.forEach((file) => { if (pkg === "." && file === "docs") { diff --git a/repos/effect/scripts/copy-ai-docs.mjs b/repos/effect/scripts/copy-ai-docs.mjs new file mode 100644 index 0000000000..ada86e0398 --- /dev/null +++ b/repos/effect/scripts/copy-ai-docs.mjs @@ -0,0 +1,33 @@ +import * as Fs from "node:fs" +import * as Glob from "glob" +import * as Path from "node:path" + +const source = "LLMS.md" +const aiDocsSource = "ai-docs" +const packageFiles = ["AGENTS.md", "CLAUDE.md", "ai-docs/**/*"] + +const includeAiDocs = (source) => { + const [root] = Path.relative(aiDocsSource, source).split(Path.sep) + return root !== "dist" && root !== "node_modules" +} + +for (const packageJsonPath of Glob.globSync("packages/{*,*/*}/package.json")) { + const packageJson = JSON.parse(Fs.readFileSync(packageJsonPath, "utf8")) + if (packageJson.private) { + continue + } + + const packageDirectory = Path.dirname(packageJsonPath) + for (const file of packageFiles) { + if (!packageJson.files?.includes(file)) { + throw new Error(`${packageJsonPath} must include ${file} in its files list`) + } + } + + Fs.copyFileSync(source, Path.join(packageDirectory, "AGENTS.md")) + Fs.copyFileSync(source, Path.join(packageDirectory, "CLAUDE.md")) + + const aiDocsTarget = Path.join(packageDirectory, aiDocsSource) + Fs.rmSync(aiDocsTarget, { recursive: true, force: true }) + Fs.cpSync(aiDocsSource, aiDocsTarget, { recursive: true, filter: includeAiDocs }) +} diff --git a/repos/effect/scripts/docs.mjs b/repos/effect/scripts/docs.mjs deleted file mode 100644 index b9fe976981..0000000000 --- a/repos/effect/scripts/docs.mjs +++ /dev/null @@ -1,64 +0,0 @@ -import * as Fs from "node:fs" -import * as Path from "node:path" - -function packages() { - return Fs.readdirSync("packages") - .filter((_) => Fs.existsSync(Path.join("packages", _, "docs/modules"))) -} - -function pkgName(pkg) { - const packageJson = Fs.readFileSync( - Path.join("packages", pkg, "package.json") - ) - return JSON.parse(packageJson).name -} - -function copyFiles(pkg) { - const name = pkgName(pkg) - const docs = Path.join("packages", pkg, "docs/modules") - const dest = Path.join("docs", pkg) - const files = Fs.readdirSync(docs, { withFileTypes: true }) - - function handleFiles(root, files) { - for (const file of files) { - const path = Path.join(docs, root, file.name) - const destPath = Path.join(dest, root, file.name) - - if (file.isDirectory()) { - Fs.mkdirSync(destPath, { recursive: true }) - handleFiles(Path.join(root, file.name), Fs.readdirSync(path, { withFileTypes: true })) - continue - } - - const content = Fs.readFileSync(path, "utf8").replace( - /^parent: Modules$/m, - `parent: "${name}"` - ) - Fs.writeFileSync(destPath, content) - } - } - - Fs.rmSync(dest, { recursive: true, force: true }) - Fs.mkdirSync(dest, { recursive: true }) - handleFiles("", files) -} - -function generateIndex(pkg, order) { - const name = pkgName(pkg) - const content = `--- -title: "${name}" -has_children: true -permalink: /docs/${pkg} -nav_order: ${order} ---- -` - - Fs.writeFileSync(Path.join("docs", pkg, "index.md"), content) -} - -packages().forEach((pkg, i) => { - Fs.rmSync(Path.join("docs", pkg), { recursive: true, force: true }) - Fs.mkdirSync(Path.join("docs", pkg), { recursive: true }) - copyFiles(pkg) - generateIndex(pkg, i + 2) -}) diff --git a/repos/effect/scripts/set-strip-internal.mjs b/repos/effect/scripts/set-strip-internal.mjs new file mode 100644 index 0000000000..a7533f75d5 --- /dev/null +++ b/repos/effect/scripts/set-strip-internal.mjs @@ -0,0 +1,11 @@ +import * as Fs from "node:fs" + +const path = new URL("../tsconfig.base.json", import.meta.url) +const contents = Fs.readFileSync(path, "utf8") +const updated = contents.replace('"stripInternal": false', '"stripInternal": true') + +if (contents === updated) { + throw new Error("Could not enable stripInternal in tsconfig.base.json") +} + +Fs.writeFileSync(path, updated) diff --git a/repos/effect/scripts/setup-agents.mjs b/repos/effect/scripts/setup-agents.mjs new file mode 100644 index 0000000000..b6537a05a0 --- /dev/null +++ b/repos/effect/scripts/setup-agents.mjs @@ -0,0 +1,13 @@ +import * as Fs from "node:fs" + +const source = ".agents/AGENTS.md" +const target = "AGENTS.md" + +try { + Fs.lstatSync(target) +} catch (error) { + if (error?.code !== "ENOENT") { + throw error + } + Fs.symlinkSync(source, target) +} diff --git a/repos/effect/scripts/tsconfig.json b/repos/effect/scripts/tsconfig.json index f3d7810ab9..fe5cecfbaa 100644 --- a/repos/effect/scripts/tsconfig.json +++ b/repos/effect/scripts/tsconfig.json @@ -1,5 +1,5 @@ { - "$schema": "http://json.schemastore.org/tsconfig", + "$schema": "https://json.schemastore.org/tsconfig", "extends": "../tsconfig.base.json", "include": ["**/*.ts"], "compilerOptions": { diff --git a/repos/effect/scripts/version.mjs b/repos/effect/scripts/version.mjs deleted file mode 100644 index 6b95f8d88b..0000000000 --- a/repos/effect/scripts/version.mjs +++ /dev/null @@ -1,9 +0,0 @@ -import * as Fs from "node:fs" -import Package from "../packages/effect/package.json" with { type: "json" } - -const tpl = Fs.readFileSync("./scripts/version.template.txt").toString("utf8") - -Fs.writeFileSync( - "packages/effect/src/internal/version.ts", - tpl.replace(/VERSION/g, Package.version) -) diff --git a/repos/effect/scripts/version.template.txt b/repos/effect/scripts/version.template.txt deleted file mode 100644 index 7e41a1e925..0000000000 --- a/repos/effect/scripts/version.template.txt +++ /dev/null @@ -1,2 +0,0 @@ -export const version: version = "VERSION" -export type version = "VERSION" diff --git a/repos/effect/scripts/worktree-setup.sh b/repos/effect/scripts/worktree-setup.sh index 3871b4a656..26a8924a1b 100755 --- a/repos/effect/scripts/worktree-setup.sh +++ b/repos/effect/scripts/worktree-setup.sh @@ -6,17 +6,16 @@ corepack install pnpm install # setup repositories -git clone --depth 1 https://github.com/effect-ts/effect.git .repos/effect-old git clone --depth 1 https://github.com/tstyche/tstyche.org.git .repos/tstyche.org -cat << EOF >> AGENTS.md +cp .agents/AGENTS.md AGENTS.md.tmp + +cat << EOF >> AGENTS.md.tmp ## Learning about "effect" v3 If you need to learn more about the old version of effect (version 3.x), you can -access the archived repository here: - -\`.repos/effect-old\` +access the "v3" branch. ## Learning about the "tstyche" testing framework @@ -25,3 +24,5 @@ the website repository here: \`.repos/tstyche.org\` EOF + +mv AGENTS.md.tmp AGENTS.md diff --git a/repos/effect/tsconfig.base.json b/repos/effect/tsconfig.base.json index 407308a502..0a0a036e23 100644 --- a/repos/effect/tsconfig.base.json +++ b/repos/effect/tsconfig.base.json @@ -1,5 +1,5 @@ { - "$schema": "http://json.schemastore.org/tsconfig", + "$schema": "https://json.schemastore.org/tsconfig", "include": [], "compilerOptions": { "outDir": "${configDir}/dist", @@ -26,14 +26,13 @@ "noImplicitOverride": true, "noFallthroughCasesInSwitch": true, // Miscellaneoaus - "stripInternal": false, // We temporarily override this to `true` when publishing packages. + "stripInternal": false, // tests require `false` because project references type-check against those declarations. "skipLibCheck": true, // Skip type checking of third party libraries. "noErrorTruncation": true, // Do not truncate error messages. "types": [], // Disable automatic loading of `@types/*` packages. "jsx": "react-jsx", "plugins": [{ "name": "@effect/language-service", - "transform": "@effect/language-service/transform", "namespaceImportPackages": ["effect", "@effect/*"], "diagnosticSeverity": { "globalErrorInEffectFailure": "off" // TODO: Check if we should fix the warnings this reports diff --git a/repos/effect/tsconfig.json b/repos/effect/tsconfig.json index b2a9c688e5..8d474c4cbc 100644 --- a/repos/effect/tsconfig.json +++ b/repos/effect/tsconfig.json @@ -1,131 +1,8 @@ { - "$schema": "http://json.schemastore.org/tsconfig", - "extends": "./tsconfig.base.json", - "references": [{ "path": "tsconfig.packages.json" }], - "include": [ - "**/vitest.*.ts", // All vitest config files across all packages - "./bundle/**/*.ts", // Bundle size test files - "./packages/*/test/**/*.ts", - "./packages/*/test/**/*.json", - "./packages/*/typetest/**/*.ts", - "./packages/*/benchmark/**/*.ts", - "./packages/ai/*/test/**/*.ts", - "./packages/ai/*/test/**/*.json", - "./packages/ai/*/typetest/**/*.ts", - "./packages/ai/*/benchmark/**/*.ts", - "./packages/atom/*/test/**/*.ts", - "./packages/atom/*/test/**/*.json", - "./packages/atom/*/typetest/**/*.ts", - "./packages/atom/*/benchmark/**/*.ts", - "./packages/sql/*/test/**/*.ts", - "./packages/sql/*/test/**/*.json", - "./packages/sql/*/typetest/**/*.ts", - "./packages/sql/*/benchmark/**/*.ts", - "./packages/tools/*/test/**/*.ts", - "./packages/tools/*/test/**/*.json", - "./packages/tools/*/typetest/**/*.ts", - "./packages/tools/*/benchmark/**/*.ts" - ], - "compilerOptions": { - "rootDir": ".", - "noEmit": true, - "erasableSyntaxOnly": false, - "resolveJsonModule": true, - "types": ["node"], - "paths": { - // These path aliases allow us to load internal modules in tests and to use other workspace packages in tests - // that would otherwise be considered cyclic dependencies (e.g. loading `@effect/sql-sqlite-node` in the test - // suite of `@effect/platform-node` which itself depends on `@effect/platform-node`). - "effect/*": ["./packages/effect/src/*.ts"], - "@effect/ai-anthropic": ["./packages/ai/anthropic/src/index.ts"], - "@effect/ai-anthropic/*": ["./packages/ai/anthropic/src/*.ts"], - "@effect/ai-openai-compat": ["./packages/ai/openai-compat/src/index.ts"], - "@effect/ai-openai-compat/*": ["./packages/ai/openai-compat/src/*.ts"], - "@effect/ai-openai": ["./packages/ai/openai/src/index.ts"], - "@effect/ai-openai/*": ["./packages/ai/openai/src/*.ts"], - "@effect/ai-openrouter": ["./packages/ai/openrouter/src/index.ts"], - "@effect/ai-openrouter/*": ["./packages/ai/openrouter/src/*.ts"], - "@effect/atom-react": ["./packages/atom/react/src/index.ts"], - "@effect/atom-react/*": ["./packages/atom/react/src/*.ts"], - "@effect/atom-vue": ["./packages/atom/vue/src/index.ts"], - "@effect/atom-vue/*": ["./packages/atom/vue/src/*.ts"], - "@effect/atom-solid": ["./packages/atom/solid/src/index.ts"], - "@effect/atom-solid/*": ["./packages/atom/solid/src/*.ts"], - "@effect/opentelemetry": ["./packages/opentelemetry/src/index.ts"], - "@effect/opentelemetry/*": ["./packages/opentelemetry/src/*.ts"], - "@effect/platform-browser": ["./packages/platform-browser/src/index.ts"], - "@effect/platform-browser/*": ["./packages/platform-browser/src/*.ts"], - "@effect/platform-bun": ["./packages/platform-bun/src/index.ts"], - "@effect/platform-bun/*": ["./packages/platform-bun/src/*.ts"], - "@effect/platform-node": ["./packages/platform-node/src/index.ts"], - "@effect/platform-node/*": ["./packages/platform-node/src/*.ts"], - "@effect/platform-node-shared": ["./packages/platform-node-shared/src/index.ts"], - "@effect/platform-node-shared/*": ["./packages/platform-node-shared/src/*.ts"], - "@effect/sql-clickhouse": ["./packages/sql/clickhouse/src/index.ts"], - "@effect/sql-clickhouse/*": ["./packages/sql/clickhouse/src/*.ts"], - "@effect/sql-d1": ["./packages/sql/d1/src/index.ts"], - "@effect/sql-d1/*": ["./packages/sql/d1/src/*.ts"], - "@effect/sql-libsql": ["./packages/sql/libsql/src/index.ts"], - "@effect/sql-libsql/*": ["./packages/sql/libsql/src/*.ts"], - "@effect/sql-mssql": ["./packages/sql/mssql/src/index.ts"], - "@effect/sql-mssql/*": ["./packages/sql/mssql/src/*.ts"], - "@effect/sql-mysql2": ["./packages/sql/mysql2/src/index.ts"], - "@effect/sql-mysql2/*": ["./packages/sql/mysql2/src/*.ts"], - "@effect/sql-pg": ["./packages/sql/pg/src/index.ts"], - "@effect/sql-pg/*": ["./packages/sql/pg/src/*.ts"], - "@effect/sql-pglite": ["./packages/sql/pglite/src/index.ts"], - "@effect/sql-pglite/*": ["./packages/sql/pglite/src/*.ts"], - "@effect/sql-sqlite-bun": ["./packages/sql/sqlite-bun/src/index.ts"], - "@effect/sql-sqlite-bun/*": ["./packages/sql/sqlite-bun/src/*.ts"], - "@effect/sql-sqlite-do": ["./packages/sql/sqlite-do/src/index.ts"], - "@effect/sql-sqlite-do/*": ["./packages/sql/sqlite-do/src/*.ts"], - "@effect/sql-sqlite-node": ["./packages/sql/sqlite-node/src/index.ts"], - "@effect/sql-sqlite-node/*": ["./packages/sql/sqlite-node/src/*.ts"], - "@effect/sql-sqlite-react-native": ["./packages/sql/sqlite-react-native/src/index.ts"], - "@effect/sql-sqlite-react-native/*": ["./packages/sql/sqlite-react-native/src/*.ts"], - "@effect/sql-sqlite-wasm": ["./packages/sql/sqlite-wasm/src/index.ts"], - "@effect/sql-sqlite-wasm/*": ["./packages/sql/sqlite-wasm/src/*.ts"], - "@effect/ai-codegen": ["./packages/tools/ai-codegen/src/index.ts"], - "@effect/ai-codegen/*": ["./packages/tools/ai-codegen/src/*.ts"], - "@effect/ai-docgen": ["./packages/tools/ai-docgen/src/index.ts"], - "@effect/ai-docgen/*": ["./packages/tools/ai-docgen/src/*.ts"], - "@effect/bundle/*": ["./packages/tools/bundle/src/*.ts"], - "@effect/openapi-generator": ["./packages/tools/openapi-generator/src/index.ts"], - "@effect/openapi-generator/*": ["./packages/tools/openapi-generator/src/*.ts"], - "@effect/oxc": ["./packages/tools/oxc/src/index.ts"], - "@effect/oxc/*": ["./packages/tools/oxc/src/*.ts"], - "@effect/tools-utils": ["./packages/tools/utils/src/index.ts"], - "@effect/tools-utils/*": ["./packages/tools/utils/src/*.ts"], - "@effect/vitest": ["./packages/vitest/src/index.ts"], - "@effect/vitest/*": ["./packages/vitest/src/*.ts"], - // TODO: This is a special alias used by some tests. We should try to get rid of it. - "effect-test/*": ["./packages/effect/test/*.ts"] - }, - "plugins": [{ - "name": "@effect/language-service", - "namespaceImportPackages": [], - "includeSuggestionsInTsc": false, - "ignoreEffectWarningsInTscExitCode": true, - "ignoreEffectErrorsInTscExitCode": true, - "overrides": [ - { - "include": [ - "./packages/*/typetest/**/*.ts", - "./packages/ai/*/typetest/**/*.ts", - "./packages/tools/*/typetest/**/*.ts", - "./packages/atom/*/typetest/**/*.ts" - ], - "options": { - "diagnosticSeverity": { - "floatingEffect": "off" - } - } - } - ], - "diagnosticSeverity": { - "unknownInEffectCatch": "off", - "multipleEffectProvide": "off" - } - }] - } + "$schema": "https://json.schemastore.org/tsconfig", + "include": [], + "references": [ + { "path": "tsconfig.packages.json" }, + { "path": "tsconfig.tests.json" } + ] } diff --git a/repos/effect/tsconfig.packages.json b/repos/effect/tsconfig.packages.json index 229e31251e..7c0d1e2d52 100644 --- a/repos/effect/tsconfig.packages.json +++ b/repos/effect/tsconfig.packages.json @@ -1,5 +1,5 @@ { - "$schema": "http://json.schemastore.org/tsconfig", + "$schema": "https://json.schemastore.org/tsconfig", "extends": "./tsconfig.base.json", "include": [], "references": [ @@ -15,6 +15,7 @@ { "path": "packages/opentelemetry" }, { "path": "packages/platform-browser" }, { "path": "packages/platform-bun" }, + { "path": "packages/platform-deno" }, { "path": "packages/platform-node" }, { "path": "packages/platform-node-shared" }, { "path": "packages/sql/clickhouse" }, @@ -30,7 +31,10 @@ { "path": "packages/sql/sqlite-react-native" }, { "path": "packages/sql/sqlite-wasm" }, { "path": "packages/tools/ai-codegen" }, + { "path": "packages/tools/api-diff" }, { "path": "packages/tools/bundle" }, + { "path": "packages/tools/docgen" }, + { "path": "packages/tools/doctest" }, { "path": "packages/tools/openapi-generator" }, { "path": "packages/tools/jsdocs" }, { "path": "packages/tools/oxc" }, diff --git a/repos/effect/tsconfig.tests.json b/repos/effect/tsconfig.tests.json new file mode 100644 index 0000000000..301c31645c --- /dev/null +++ b/repos/effect/tsconfig.tests.json @@ -0,0 +1,120 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "./tsconfig.base.json", + "references": [{ "path": "tsconfig.packages.json" }], + "include": [ + "**/vitest.*.ts", // All vitest config files across all packages + "./bundle/**/*.ts", // Bundle size test files + "./packages/*/test/**/*.ts", + "./packages/*/test/**/*.json", + "./packages/*/typetest/**/*.ts", + "./packages/*/benchmark/**/*.ts", + "./packages/**/test/**/*.ts", + "./packages/**/test/**/*.json", + "./packages/**/typetest/**/*.ts", + "./packages/**/benchmark/**/*.ts" + ], + "exclude": ["**/node_modules/**", "**/dist/**", "./packages/platform-deno/**"], + "compilerOptions": { + "rootDir": ".", + "noEmit": true, + "erasableSyntaxOnly": false, + "resolveJsonModule": true, + "types": ["node"], + "paths": { + // These path aliases allow us to load internal modules in tests and to use other workspace packages in tests + // that would otherwise be considered cyclic dependencies (e.g. loading `@effect/sql-sqlite-node` in the test + // suite of `@effect/platform-node` which itself depends on `@effect/platform-node`). + "effect": ["./packages/effect/src/index.ts"], + "effect/*": ["./packages/effect/src/*.ts"], + "@effect/ai-anthropic": ["./packages/ai/anthropic/src/index.ts"], + "@effect/ai-anthropic/*": ["./packages/ai/anthropic/src/*.ts"], + "@effect/ai-openai-compat": ["./packages/ai/openai-compat/src/index.ts"], + "@effect/ai-openai-compat/*": ["./packages/ai/openai-compat/src/*.ts"], + "@effect/ai-openai": ["./packages/ai/openai/src/index.ts"], + "@effect/ai-openai/*": ["./packages/ai/openai/src/*.ts"], + "@effect/ai-openrouter": ["./packages/ai/openrouter/src/index.ts"], + "@effect/ai-openrouter/*": ["./packages/ai/openrouter/src/*.ts"], + "@effect/atom-react": ["./packages/atom/react/src/index.ts"], + "@effect/atom-react/*": ["./packages/atom/react/src/*.ts"], + "@effect/atom-vue": ["./packages/atom/vue/src/index.ts"], + "@effect/atom-vue/*": ["./packages/atom/vue/src/*.ts"], + "@effect/atom-solid": ["./packages/atom/solid/src/index.ts"], + "@effect/atom-solid/*": ["./packages/atom/solid/src/*.ts"], + "@effect/opentelemetry": ["./packages/opentelemetry/src/index.ts"], + "@effect/opentelemetry/*": ["./packages/opentelemetry/src/*.ts"], + "@effect/platform-browser": ["./packages/platform-browser/src/index.ts"], + "@effect/platform-browser/*": ["./packages/platform-browser/src/*.ts"], + "@effect/platform-bun": ["./packages/platform-bun/src/index.ts"], + "@effect/platform-bun/*": ["./packages/platform-bun/src/*.ts"], + "@effect/platform-node": ["./packages/platform-node/src/index.ts"], + "@effect/platform-node/*": ["./packages/platform-node/src/*.ts"], + "@effect/platform-node-shared": ["./packages/platform-node-shared/src/index.ts"], + "@effect/platform-node-shared/*": ["./packages/platform-node-shared/src/*.ts"], + "@effect/sql-clickhouse": ["./packages/sql/clickhouse/src/index.ts"], + "@effect/sql-clickhouse/*": ["./packages/sql/clickhouse/src/*.ts"], + "@effect/sql-d1": ["./packages/sql/d1/src/index.ts"], + "@effect/sql-d1/*": ["./packages/sql/d1/src/*.ts"], + "@effect/sql-libsql": ["./packages/sql/libsql/src/index.ts"], + "@effect/sql-libsql/*": ["./packages/sql/libsql/src/*.ts"], + "@effect/sql-mssql": ["./packages/sql/mssql/src/index.ts"], + "@effect/sql-mssql/*": ["./packages/sql/mssql/src/*.ts"], + "@effect/sql-mysql2": ["./packages/sql/mysql2/src/index.ts"], + "@effect/sql-mysql2/*": ["./packages/sql/mysql2/src/*.ts"], + "@effect/sql-pg": ["./packages/sql/pg/src/index.ts"], + "@effect/sql-pg/*": ["./packages/sql/pg/src/*.ts"], + "@effect/sql-pglite": ["./packages/sql/pglite/src/index.ts"], + "@effect/sql-pglite/*": ["./packages/sql/pglite/src/*.ts"], + "@effect/sql-sqlite-bun": ["./packages/sql/sqlite-bun/src/index.ts"], + "@effect/sql-sqlite-bun/*": ["./packages/sql/sqlite-bun/src/*.ts"], + "@effect/sql-sqlite-do": ["./packages/sql/sqlite-do/src/index.ts"], + "@effect/sql-sqlite-do/*": ["./packages/sql/sqlite-do/src/*.ts"], + "@effect/sql-sqlite-node": ["./packages/sql/sqlite-node/src/index.ts"], + "@effect/sql-sqlite-node/*": ["./packages/sql/sqlite-node/src/*.ts"], + "@effect/sql-sqlite-react-native": ["./packages/sql/sqlite-react-native/src/index.ts"], + "@effect/sql-sqlite-react-native/*": ["./packages/sql/sqlite-react-native/src/*.ts"], + "@effect/sql-sqlite-wasm": ["./packages/sql/sqlite-wasm/src/index.ts"], + "@effect/sql-sqlite-wasm/*": ["./packages/sql/sqlite-wasm/src/*.ts"], + "@effect/ai-codegen": ["./packages/tools/ai-codegen/src/index.ts"], + "@effect/ai-codegen/*": ["./packages/tools/ai-codegen/src/*.ts"], + "@effect/api-diff": ["./packages/tools/api-diff/src/Cli.ts"], + "@effect/api-diff/*": ["./packages/tools/api-diff/src/*.ts"], + "@effect/ai-docgen": ["./packages/tools/ai-docgen/src/index.ts"], + "@effect/ai-docgen/*": ["./packages/tools/ai-docgen/src/*.ts"], + "@effect/bundle/*": ["./packages/tools/bundle/src/*.ts"], + "@effect/docgen": ["./packages/tools/docgen/src/index.ts"], + "@effect/docgen/*": ["./packages/tools/docgen/src/*.ts"], + "@effect/openapi-generator": ["./packages/tools/openapi-generator/src/index.ts"], + "@effect/openapi-generator/*": ["./packages/tools/openapi-generator/src/*.ts"], + "@effect/oxc": ["./packages/tools/oxc/src/index.ts"], + "@effect/oxc/*": ["./packages/tools/oxc/src/*.ts"], + "@effect/tools-utils": ["./packages/tools/utils/src/index.ts"], + "@effect/tools-utils/*": ["./packages/tools/utils/src/*.ts"], + "@effect/vitest": ["./packages/vitest/src/index.ts"], + "@effect/vitest/*": ["./packages/vitest/src/*.ts"], + // TODO: This is a special alias used by some tests. We should try to get rid of it. + "effect-test/*": ["./packages/effect/test/*.ts"] + }, + "plugins": [{ + "name": "@effect/language-service", + "namespaceImportPackages": [], + "includeSuggestionsInTsc": false, + "ignoreEffectWarningsInTscExitCode": true, + "ignoreEffectErrorsInTscExitCode": true, + "overrides": [ + { + "include": ["**/typetest/**/*.ts"], + "options": { + "diagnosticSeverity": { + "floatingEffect": "off" + } + } + } + ], + "diagnosticSeverity": { + "unknownInEffectCatch": "off", + "multipleEffectProvide": "off" + } + }] + } +} diff --git a/repos/effect/tstyche.json b/repos/effect/tstyche.json index e5c027dafd..10164cb9b7 100644 --- a/repos/effect/tstyche.json +++ b/repos/effect/tstyche.json @@ -1,7 +1,8 @@ { "$schema": "./node_modules/tstyche/schemas/config.json", "testFileMatch": [ - "packages/*/typetest/**/*.tst.*" + "packages/*/typetest/**/*.tst.*", + "packages/*/*/typetest/**/*.tst.*" ], "tsconfig": "baseline" } diff --git a/repos/effect/vitest.config.ts b/repos/effect/vitest.config.ts index b5f7ff95a1..a14b70f2ae 100644 --- a/repos/effect/vitest.config.ts +++ b/repos/effect/vitest.config.ts @@ -1,31 +1,196 @@ -import { defineConfig } from "vitest/config" +import * as os from "node:os" +import * as path from "node:path" +import { defineConfig, mergeConfig, type ViteUserConfig } from "vitest/config" const isDeno = process.versions.deno !== undefined const isBun = process.versions.bun !== undefined +const isNode = typeof process !== "undefined" && + process.release.name === "node" && + !isDeno && + !isBun +const integrationTestsEnabled = process.env.EFFECT_INTEGRATION_TESTS === "1" +const clusterTestsEnabled = process.env.EFFECT_CLUSTER_TESTS === "1" + +const project = ( + name: string, + directory: string, + include: boolean = true, + config: ViteUserConfig = {}, + projectExclude?: ReadonlyArray, + projectInclude?: ReadonlyArray +) => { + if (!include) { + return [] + } + + const cfg = mergeConfig({ + root: directory, + test: { name } + }, config) + + const merged = mergeConfig(shared, cfg) + if (projectExclude !== undefined) { + merged.test!.exclude = [...projectExclude] + } + if (projectInclude !== undefined) { + merged.test!.include = [...projectInclude] + } + return [merged] +} + +export const exclude = [ + "**/.*/**", + "**/node_modules/**", + "**/dist/**", + "**/benchmark/**", + "**/bundle/**", + "**/typetest/**", + "**/coverage/**", + "**/test/utils/**", + "**/test/cluster-integration/**", + ...(!integrationTestsEnabled ? ["**/*.integration.test.{ts,tsx}"] : []), + "**/*.d.ts", + "**/*.config.*", + "**/vitest.*" +] + +const shared: ViteUserConfig = { + optimizeDeps: { + exclude: ["bun:sqlite"] + }, + server: { + watch: { + ignored: exclude + } + }, + resolve: { + tsconfigPaths: true + }, + test: { + exclude, + passWithNoTests: true, + setupFiles: [path.join(__dirname, "vitest.setup.ts")], + fakeTimers: { + toFake: undefined + }, + sequence: { + concurrent: true + }, + include: ["test/**/*.test.{ts,tsx}"], + coverage: { + provider: "v8", + reporter: ["html"], + reportsDirectory: "coverage", + exclude + } + } +} export default defineConfig({ test: { + passWithNoTests: true, projects: [ - "packages/*/vitest.config.ts", - "packages/ai/*/vitest.config.ts", - "packages/atom/*/vitest.config.ts", - "packages/tools/*/vitest.config.ts", - "packages/sql/*/vitest.config.ts", - ...(isDeno ? - [ - "!packages/atom", - "!packages/platform-bun", - "!packages/platform-node", - "!packages/platform-node-shared", - "!packages/sql/d1", - "!packages/sql/sqlite-node" - ] : - []), - ...(isBun ? + ...project("effect", "packages/effect", true, { + test: { + // @see https://github.com/denoland/deno/issues/23882 + exclude: isDeno ? ["test/cluster/**"] : [] + } + }), + ...project("@effect/ai-anthropic", "packages/ai/anthropic"), + ...project("@effect/ai-openai", "packages/ai/openai"), + ...project("@effect/ai-openai-compat", "packages/ai/openai-compat"), + ...project("@effect/ai-openrouter", "packages/ai/openrouter"), + ...project("@effect/atom-react", "packages/atom/react", true, { + test: { + environment: "jsdom", + setupFiles: [path.join(__dirname, "packages/atom/react/vitest.setup.ts")] + } + }), + ...project("@effect/atom-solid", "packages/atom/solid", true, { + resolve: { + conditions: ["browser"] + }, + test: { + environment: "jsdom", + setupFiles: [path.join(__dirname, "packages/atom/solid/vitest.setup.ts")] + } + }), + ...project("@effect/atom-vue", "packages/atom/vue", true, { + test: { + environment: "happy-dom" + } + }), + ...project("@effect/opentelemetry", "packages/opentelemetry"), + ...project("@effect/platform-browser", "packages/platform-browser", true, { + test: { + environment: "happy-dom", + execArgv: [ + "--localstorage-file", + path.resolve(os.tmpdir(), `vitest-${process.pid}.localstorage`) + ], + setupFiles: [path.join(__dirname, "packages/platform-browser/vitest.setup.ts")] + } + }), + ...project("@effect/platform-bun", "packages/platform-bun", isBun), + ...project("@effect/platform-deno", "packages/platform-deno", isDeno), + ...project("@effect/platform-node", "packages/platform-node", isNode), + ...project( + "cluster-integration", + "packages/platform-node", + isNode && clusterTestsEnabled, + { + test: { + globalSetup: [path.join(__dirname, "packages/platform-node/test/cluster-integration/globalSetup.ts")], + include: ["test/cluster-integration/**/*.test.ts"], + retry: 0, + sequence: { + concurrent: false + }, + testTimeout: 60_000 + } + }, + exclude.filter((path) => path !== "**/test/cluster-integration/**"), [ - "!packages/platform-node" - ] : - []) + "test/cluster-integration/**/*.test.ts" + ] + ), + ...project("@effect/platform-node-shared", "packages/platform-node-shared", !isDeno), + ...project("@effect/vitest", "packages/vitest"), + ...project("@effect/sql-clickhouse", "packages/sql/clickhouse"), + ...project("@effect/sql-d1", "packages/sql/d1", !isDeno), + ...project("@effect/sql-libsql", "packages/sql/libsql"), + ...project("@effect/sql-mssql", "packages/sql/mssql"), + // MySQL starts a fresh container per integration test suite, so avoid competing + // with the other container-backed projects on Deno CI runners. + ...project( + "@effect/sql-mysql2", + "packages/sql/mysql2", + true, + isDeno && integrationTestsEnabled + ? { + test: { + fileParallelism: false, + sequence: { + groupOrder: 1 + } + } + } + : {} + ), + ...project("@effect/sql-pg", "packages/sql/pg"), + ...project("@effect/sql-pglite", "packages/sql/pglite"), + ...project("@effect/sql-sqlite-bun", "packages/sql/sqlite-bun"), + ...project("@effect/sql-sqlite-do", "packages/sql/sqlite-do"), + ...project("@effect/sql-sqlite-node", "packages/sql/sqlite-node", !isDeno), + ...project("@effect/sql-sqlite-react-native", "packages/sql/sqlite-react-native"), + ...project("@effect/sql-sqlite-wasm", "packages/sql/sqlite-wasm"), + ...project("@effect/api-diff", "packages/tools/api-diff"), + ...project("@effect/bundle", "packages/tools/bundle"), + ...project("@effect/doctest", "packages/tools/doctest"), + ...project("@effect/docgen", "packages/tools/docgen"), + ...project("@effect/jsdocs", "packages/tools/jsdocs"), + ...project("@effect/openapi-generator", "packages/tools/openapi-generator"), + ...project("@effect/oxc", "packages/tools/oxc") ] } }) diff --git a/repos/effect/vitest.docs.ts b/repos/effect/vitest.docs.ts new file mode 100644 index 0000000000..786b82aa2c --- /dev/null +++ b/repos/effect/vitest.docs.ts @@ -0,0 +1,18 @@ +import * as Doctest from "@effect/doctest/Plugin" +import { defineConfig } from "vitest/config" + +export default defineConfig({ + plugins: [Doctest.plugin()], + resolve: { + tsconfigPaths: true + }, + test: { + passWithNoTests: true, + testTimeout: 10_000, + include: [], + includeSource: [ + "packages/*/src/**/*.ts", + "packages/*/*/src/**/*.ts" + ] + } +}) diff --git a/repos/effect/vitest.shared.ts b/repos/effect/vitest.shared.ts deleted file mode 100644 index b040a6549f..0000000000 --- a/repos/effect/vitest.shared.ts +++ /dev/null @@ -1,61 +0,0 @@ -import path from "node:path" -import aliases from "vite-tsconfig-paths" -import type { ViteUserConfig } from "vitest/config" - -const config: ViteUserConfig = { - esbuild: { - target: "es2020" - }, - optimizeDeps: { - exclude: ["bun:sqlite"] - }, - plugins: [aliases()], - server: { - watch: { - ignored: [ - "**/.context/**", - "**/.direnv/**", - "**/.lalph/**", - "**/.repos/**" - ] - } - }, - test: { - exclude: [ - "**/.context/**", - "**/.direnv/**", - "**/.lalph/**", - "**/.repos/**", - "**/node_modules/**" - ], - setupFiles: [path.join(__dirname, "vitest.setup.ts")], - fakeTimers: { - toFake: undefined - }, - sequence: { - concurrent: true - }, - include: ["test/**/*.test.{ts,tsx}"], - coverage: { - provider: "v8", - reporter: ["html"], - reportsDirectory: "coverage", - exclude: [ - "node_modules/", - "dist/", - "benchmark/", - "bundle/", - "typetest/", - "build/", - "coverage/", - "test/utils/", - "**/*.d.ts", - "**/*.config.*", - "**/vitest.setup.*", - "**/vitest.shared.*" - ] - } - } -} - -export default config

`, 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/schema-result-combinators.md b/repos/effect/.changeset/pre/schema-result-combinators.md similarity index 100% rename from repos/effect/.changeset/schema-result-combinators.md rename to repos/effect/.changeset/pre/schema-result-combinators.md 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 0000000000..5d1b322740 --- /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/schema-struct-simplify.md b/repos/effect/.changeset/pre/schema-struct-simplify.md similarity index 100% rename from repos/effect/.changeset/schema-struct-simplify.md rename to repos/effect/.changeset/pre/schema-struct-simplify.md 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 0000000000..a7ac4dac1b --- /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 0000000000..ea6d42d1b6 --- /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 0000000000..b7d1d9ad39 --- /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/semantic-matching.md b/repos/effect/.changeset/pre/semantic-matching.md similarity index 100% rename from repos/effect/.changeset/semantic-matching.md rename to repos/effect/.changeset/pre/semantic-matching.md diff --git a/repos/effect/.changeset/seven-mugs-marry.md b/repos/effect/.changeset/pre/seven-mugs-marry.md similarity index 100% rename from repos/effect/.changeset/seven-mugs-marry.md rename to repos/effect/.changeset/pre/seven-mugs-marry.md 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 0000000000..8d9caf7f40 --- /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/shaggy-birds-stay.md b/repos/effect/.changeset/pre/shaggy-birds-stay.md similarity index 100% rename from repos/effect/.changeset/shaggy-birds-stay.md rename to repos/effect/.changeset/pre/shaggy-birds-stay.md diff --git a/repos/effect/.changeset/shaggy-cities-push.md b/repos/effect/.changeset/pre/shaggy-cities-push.md similarity index 100% rename from repos/effect/.changeset/shaggy-cities-push.md rename to repos/effect/.changeset/pre/shaggy-cities-push.md diff --git a/repos/effect/.changeset/shaggy-numbers-accept.md b/repos/effect/.changeset/pre/shaggy-numbers-accept.md similarity index 100% rename from repos/effect/.changeset/shaggy-numbers-accept.md rename to repos/effect/.changeset/pre/shaggy-numbers-accept.md 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 0000000000..e0fa124dd1 --- /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/sharp-emus-applaud.md b/repos/effect/.changeset/pre/sharp-emus-applaud.md similarity index 100% rename from repos/effect/.changeset/sharp-emus-applaud.md rename to repos/effect/.changeset/pre/sharp-emus-applaud.md diff --git a/repos/effect/.changeset/sharp-goats-wink.md b/repos/effect/.changeset/pre/sharp-goats-wink.md similarity index 100% rename from repos/effect/.changeset/sharp-goats-wink.md rename to repos/effect/.changeset/pre/sharp-goats-wink.md diff --git a/repos/effect/.changeset/sharp-pandas-care.md b/repos/effect/.changeset/pre/sharp-pandas-care.md similarity index 100% rename from repos/effect/.changeset/sharp-pandas-care.md rename to repos/effect/.changeset/pre/sharp-pandas-care.md diff --git a/repos/effect/.changeset/sharp-peas-march.md b/repos/effect/.changeset/pre/sharp-peas-march.md similarity index 100% rename from repos/effect/.changeset/sharp-peas-march.md rename to repos/effect/.changeset/pre/sharp-peas-march.md diff --git a/repos/effect/.changeset/sharp-rules-draw.md b/repos/effect/.changeset/pre/sharp-rules-draw.md similarity index 100% rename from repos/effect/.changeset/sharp-rules-draw.md rename to repos/effect/.changeset/pre/sharp-rules-draw.md diff --git a/repos/effect/.changeset/sharp-singers-sort.md b/repos/effect/.changeset/pre/sharp-singers-sort.md similarity index 100% rename from repos/effect/.changeset/sharp-singers-sort.md rename to repos/effect/.changeset/pre/sharp-singers-sort.md diff --git a/repos/effect/.changeset/shiny-trains-hug.md b/repos/effect/.changeset/pre/shiny-trains-hug.md similarity index 100% rename from repos/effect/.changeset/shiny-trains-hug.md rename to repos/effect/.changeset/pre/shiny-trains-hug.md diff --git a/repos/effect/.changeset/short-cows-relate.md b/repos/effect/.changeset/pre/short-cows-relate.md similarity index 100% rename from repos/effect/.changeset/short-cows-relate.md rename to repos/effect/.changeset/pre/short-cows-relate.md diff --git a/repos/effect/.changeset/short-foxes-admire.md b/repos/effect/.changeset/pre/short-foxes-admire.md similarity index 100% rename from repos/effect/.changeset/short-foxes-admire.md rename to repos/effect/.changeset/pre/short-foxes-admire.md diff --git a/repos/effect/.changeset/short-stamps-throw.md b/repos/effect/.changeset/pre/short-stamps-throw.md similarity index 100% rename from repos/effect/.changeset/short-stamps-throw.md rename to repos/effect/.changeset/pre/short-stamps-throw.md diff --git a/repos/effect/.changeset/shy-cycles-flow.md b/repos/effect/.changeset/pre/shy-cycles-flow.md similarity index 100% rename from repos/effect/.changeset/shy-cycles-flow.md rename to repos/effect/.changeset/pre/shy-cycles-flow.md diff --git a/repos/effect/.changeset/shy-geckos-sniff.md b/repos/effect/.changeset/pre/shy-geckos-sniff.md similarity index 100% rename from repos/effect/.changeset/shy-geckos-sniff.md rename to repos/effect/.changeset/pre/shy-geckos-sniff.md diff --git a/repos/effect/.changeset/silent-geckos-matter.md b/repos/effect/.changeset/pre/silent-geckos-matter.md similarity index 100% rename from repos/effect/.changeset/silent-geckos-matter.md rename to repos/effect/.changeset/pre/silent-geckos-matter.md diff --git a/repos/effect/.changeset/silent-needles-design.md b/repos/effect/.changeset/pre/silent-needles-design.md similarity index 100% rename from repos/effect/.changeset/silent-needles-design.md rename to repos/effect/.changeset/pre/silent-needles-design.md diff --git a/repos/effect/.changeset/silent-plants-matter.md b/repos/effect/.changeset/pre/silent-plants-matter.md similarity index 100% rename from repos/effect/.changeset/silent-plants-matter.md rename to repos/effect/.changeset/pre/silent-plants-matter.md diff --git a/repos/effect/.changeset/silent-spoons-stare.md b/repos/effect/.changeset/pre/silent-spoons-stare.md similarity index 100% rename from repos/effect/.changeset/silent-spoons-stare.md rename to repos/effect/.changeset/pre/silent-spoons-stare.md 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 0000000000..e90a8a07a3 --- /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/silly-loops-tickle.md b/repos/effect/.changeset/pre/silly-loops-tickle.md similarity index 100% rename from repos/effect/.changeset/silly-loops-tickle.md rename to repos/effect/.changeset/pre/silly-loops-tickle.md diff --git a/repos/effect/.changeset/silver-bulk-indexeddb.md b/repos/effect/.changeset/pre/silver-bulk-indexeddb.md similarity index 100% rename from repos/effect/.changeset/silver-bulk-indexeddb.md rename to repos/effect/.changeset/pre/silver-bulk-indexeddb.md diff --git a/repos/effect/.changeset/silver-emus-smoke.md b/repos/effect/.changeset/pre/silver-emus-smoke.md similarity index 100% rename from repos/effect/.changeset/silver-emus-smoke.md rename to repos/effect/.changeset/pre/silver-emus-smoke.md diff --git a/repos/effect/.changeset/silver-kings-poke.md b/repos/effect/.changeset/pre/silver-kings-poke.md similarity index 100% rename from repos/effect/.changeset/silver-kings-poke.md rename to repos/effect/.changeset/pre/silver-kings-poke.md diff --git a/repos/effect/.changeset/silver-snails-sqlite.md b/repos/effect/.changeset/pre/silver-snails-sqlite.md similarity index 100% rename from repos/effect/.changeset/silver-snails-sqlite.md rename to repos/effect/.changeset/pre/silver-snails-sqlite.md diff --git a/repos/effect/.changeset/silver-wings-watch.md b/repos/effect/.changeset/pre/silver-wings-watch.md similarity index 100% rename from repos/effect/.changeset/silver-wings-watch.md rename to repos/effect/.changeset/pre/silver-wings-watch.md 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 0000000000..1ed7423a9a --- /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/six-cups-taste.md b/repos/effect/.changeset/pre/six-cups-taste.md similarity index 100% rename from repos/effect/.changeset/six-cups-taste.md rename to repos/effect/.changeset/pre/six-cups-taste.md 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 0000000000..a58d9a0492 --- /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/sixty-mails-shout.md b/repos/effect/.changeset/pre/sixty-mails-shout.md similarity index 100% rename from repos/effect/.changeset/sixty-mails-shout.md rename to repos/effect/.changeset/pre/sixty-mails-shout.md diff --git a/repos/effect/.changeset/sixty-socks-yell.md b/repos/effect/.changeset/pre/sixty-socks-yell.md similarity index 100% rename from repos/effect/.changeset/sixty-socks-yell.md rename to repos/effect/.changeset/pre/sixty-socks-yell.md diff --git a/repos/effect/.changeset/slick-signs-wish.md b/repos/effect/.changeset/pre/slick-signs-wish.md similarity index 100% rename from repos/effect/.changeset/slick-signs-wish.md rename to repos/effect/.changeset/pre/slick-signs-wish.md diff --git a/repos/effect/.changeset/slick-toes-rush.md b/repos/effect/.changeset/pre/slick-toes-rush.md similarity index 100% rename from repos/effect/.changeset/slick-toes-rush.md rename to repos/effect/.changeset/pre/slick-toes-rush.md 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 0000000000..7cc69a0af8 --- /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/slimy-planets-divide.md b/repos/effect/.changeset/pre/slimy-planets-divide.md similarity index 100% rename from repos/effect/.changeset/slimy-planets-divide.md rename to repos/effect/.changeset/pre/slimy-planets-divide.md diff --git a/repos/effect/.changeset/slimy-turtles-juggle.md b/repos/effect/.changeset/pre/slimy-turtles-juggle.md similarity index 100% rename from repos/effect/.changeset/slimy-turtles-juggle.md rename to repos/effect/.changeset/pre/slimy-turtles-juggle.md diff --git a/repos/effect/.changeset/slow-beans-battle.md b/repos/effect/.changeset/pre/slow-beans-battle.md similarity index 100% rename from repos/effect/.changeset/slow-beans-battle.md rename to repos/effect/.changeset/pre/slow-beans-battle.md diff --git a/repos/effect/.changeset/slow-berries-enjoy.md b/repos/effect/.changeset/pre/slow-berries-enjoy.md similarity index 100% rename from repos/effect/.changeset/slow-berries-enjoy.md rename to repos/effect/.changeset/pre/slow-berries-enjoy.md 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 0000000000..678da84a1e --- /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 0000000000..1098787581 --- /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/small-bugs-hunt.md b/repos/effect/.changeset/pre/small-bugs-hunt.md similarity index 100% rename from repos/effect/.changeset/small-bugs-hunt.md rename to repos/effect/.changeset/pre/small-bugs-hunt.md diff --git a/repos/effect/.changeset/small-crabs-care.md b/repos/effect/.changeset/pre/small-crabs-care.md similarity index 100% rename from repos/effect/.changeset/small-crabs-care.md rename to repos/effect/.changeset/pre/small-crabs-care.md 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 0000000000..b1e5b3de51 --- /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/small-pets-sit.md b/repos/effect/.changeset/pre/small-pets-sit.md similarity index 100% rename from repos/effect/.changeset/small-pets-sit.md rename to repos/effect/.changeset/pre/small-pets-sit.md diff --git a/repos/effect/.changeset/smart-ducks-jump.md b/repos/effect/.changeset/pre/smart-ducks-jump.md similarity index 100% rename from repos/effect/.changeset/smart-ducks-jump.md rename to repos/effect/.changeset/pre/smart-ducks-jump.md diff --git a/repos/effect/.changeset/smart-pillows-buy.md b/repos/effect/.changeset/pre/smart-pillows-buy.md similarity index 100% rename from repos/effect/.changeset/smart-pillows-buy.md rename to repos/effect/.changeset/pre/smart-pillows-buy.md diff --git a/repos/effect/.changeset/smart-timers-fly.md b/repos/effect/.changeset/pre/smart-timers-fly.md similarity index 100% rename from repos/effect/.changeset/smart-timers-fly.md rename to repos/effect/.changeset/pre/smart-timers-fly.md diff --git a/repos/effect/.changeset/smart-tips-sort.md b/repos/effect/.changeset/pre/smart-tips-sort.md similarity index 100% rename from repos/effect/.changeset/smart-tips-sort.md rename to repos/effect/.changeset/pre/smart-tips-sort.md 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 0000000000..c91f7ae2ec --- /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/social-pumas-prove.md b/repos/effect/.changeset/pre/social-pumas-prove.md similarity index 100% rename from repos/effect/.changeset/social-pumas-prove.md rename to repos/effect/.changeset/pre/social-pumas-prove.md diff --git a/repos/effect/.changeset/soft-comics-wink.md b/repos/effect/.changeset/pre/soft-comics-wink.md similarity index 100% rename from repos/effect/.changeset/soft-comics-wink.md rename to repos/effect/.changeset/pre/soft-comics-wink.md diff --git a/repos/effect/.changeset/soft-delete-sqlmodel.md b/repos/effect/.changeset/pre/soft-delete-sqlmodel.md similarity index 100% rename from repos/effect/.changeset/soft-delete-sqlmodel.md rename to repos/effect/.changeset/pre/soft-delete-sqlmodel.md diff --git a/repos/effect/.changeset/soft-seals-allow.md b/repos/effect/.changeset/pre/soft-seals-allow.md similarity index 100% rename from repos/effect/.changeset/soft-seals-allow.md rename to repos/effect/.changeset/pre/soft-seals-allow.md 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 0000000000..c0106d10b4 --- /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/solid-cougars-attack.md b/repos/effect/.changeset/pre/solid-cougars-attack.md similarity index 100% rename from repos/effect/.changeset/solid-cougars-attack.md rename to repos/effect/.changeset/pre/solid-cougars-attack.md diff --git a/repos/effect/.changeset/solid-doors-ring.md b/repos/effect/.changeset/pre/solid-doors-ring.md similarity index 100% rename from repos/effect/.changeset/solid-doors-ring.md rename to repos/effect/.changeset/pre/solid-doors-ring.md diff --git a/repos/effect/.changeset/solid-items-tease.md b/repos/effect/.changeset/pre/solid-items-tease.md similarity index 100% rename from repos/effect/.changeset/solid-items-tease.md rename to repos/effect/.changeset/pre/solid-items-tease.md diff --git a/repos/effect/.changeset/solid-towns-smoke.md b/repos/effect/.changeset/pre/solid-towns-smoke.md similarity index 100% rename from repos/effect/.changeset/solid-towns-smoke.md rename to repos/effect/.changeset/pre/solid-towns-smoke.md 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 0000000000..6c31527a93 --- /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/sour-canyons-rescue.md b/repos/effect/.changeset/pre/sour-canyons-rescue.md similarity index 100% rename from repos/effect/.changeset/sour-canyons-rescue.md rename to repos/effect/.changeset/pre/sour-canyons-rescue.md diff --git a/repos/effect/.changeset/sparkly-bears-act.md b/repos/effect/.changeset/pre/sparkly-bears-act.md similarity index 100% rename from repos/effect/.changeset/sparkly-bears-act.md rename to repos/effect/.changeset/pre/sparkly-bears-act.md diff --git a/repos/effect/.changeset/sparkly-coins-sit.md b/repos/effect/.changeset/pre/sparkly-coins-sit.md similarity index 100% rename from repos/effect/.changeset/sparkly-coins-sit.md rename to repos/effect/.changeset/pre/sparkly-coins-sit.md 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 0000000000..fc4907aa67 --- /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 0000000000..1d2390cbb8 --- /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/spotty-comics-fry.md b/repos/effect/.changeset/pre/spotty-comics-fry.md similarity index 100% rename from repos/effect/.changeset/spotty-comics-fry.md rename to repos/effect/.changeset/pre/spotty-comics-fry.md diff --git a/repos/effect/.changeset/sql-migrator-mjs-mts.md b/repos/effect/.changeset/pre/sql-migrator-mjs-mts.md similarity index 100% rename from repos/effect/.changeset/sql-migrator-mjs-mts.md rename to repos/effect/.changeset/pre/sql-migrator-mjs-mts.md diff --git a/repos/effect/.changeset/sqlite-bun-prepare-error-channel.md b/repos/effect/.changeset/pre/sqlite-bun-prepare-error-channel.md similarity index 100% rename from repos/effect/.changeset/sqlite-bun-prepare-error-channel.md rename to repos/effect/.changeset/pre/sqlite-bun-prepare-error-channel.md 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 0000000000..ee01605b9f --- /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/sqlite-do-durable-object-transactions.md b/repos/effect/.changeset/pre/sqlite-do-durable-object-transactions.md similarity index 100% rename from repos/effect/.changeset/sqlite-do-durable-object-transactions.md rename to repos/effect/.changeset/pre/sqlite-do-durable-object-transactions.md diff --git a/repos/effect/.changeset/stale-dots-tell.md b/repos/effect/.changeset/pre/stale-dots-tell.md similarity index 100% rename from repos/effect/.changeset/stale-dots-tell.md rename to repos/effect/.changeset/pre/stale-dots-tell.md 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 0000000000..6bf269405b --- /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 0000000000..f114fbec1a --- /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 0000000000..a6272595b3 --- /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/strict-areas-end.md b/repos/effect/.changeset/pre/strict-areas-end.md similarity index 100% rename from repos/effect/.changeset/strict-areas-end.md rename to repos/effect/.changeset/pre/strict-areas-end.md diff --git a/repos/effect/.changeset/strict-buckets-hug.md b/repos/effect/.changeset/pre/strict-buckets-hug.md similarity index 100% rename from repos/effect/.changeset/strict-buckets-hug.md rename to repos/effect/.changeset/pre/strict-buckets-hug.md diff --git a/repos/effect/.changeset/strip-resolved-approvals.md b/repos/effect/.changeset/pre/strip-resolved-approvals.md similarity index 100% rename from repos/effect/.changeset/strip-resolved-approvals.md rename to repos/effect/.changeset/pre/strip-resolved-approvals.md diff --git a/repos/effect/.changeset/strong-balloons-tickle.md b/repos/effect/.changeset/pre/strong-balloons-tickle.md similarity index 100% rename from repos/effect/.changeset/strong-balloons-tickle.md rename to repos/effect/.changeset/pre/strong-balloons-tickle.md diff --git a/repos/effect/.changeset/strong-bees-queue.md b/repos/effect/.changeset/pre/strong-bees-queue.md similarity index 100% rename from repos/effect/.changeset/strong-bees-queue.md rename to repos/effect/.changeset/pre/strong-bees-queue.md 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 0000000000..075c42f857 --- /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/struct-record.md b/repos/effect/.changeset/pre/struct-record.md similarity index 100% rename from repos/effect/.changeset/struct-record.md rename to repos/effect/.changeset/pre/struct-record.md diff --git a/repos/effect/.changeset/sunny-ads-hang.md b/repos/effect/.changeset/pre/sunny-ads-hang.md similarity index 100% rename from repos/effect/.changeset/sunny-ads-hang.md rename to repos/effect/.changeset/pre/sunny-ads-hang.md diff --git a/repos/effect/.changeset/sunny-bikes-sleep.md b/repos/effect/.changeset/pre/sunny-bikes-sleep.md similarity index 100% rename from repos/effect/.changeset/sunny-bikes-sleep.md rename to repos/effect/.changeset/pre/sunny-bikes-sleep.md diff --git a/repos/effect/.changeset/sunny-rooms-invent.md b/repos/effect/.changeset/pre/sunny-rooms-invent.md similarity index 100% rename from repos/effect/.changeset/sunny-rooms-invent.md rename to repos/effect/.changeset/pre/sunny-rooms-invent.md diff --git a/repos/effect/.changeset/sweet-donuts-bet.md b/repos/effect/.changeset/pre/sweet-donuts-bet.md similarity index 100% rename from repos/effect/.changeset/sweet-donuts-bet.md rename to repos/effect/.changeset/pre/sweet-donuts-bet.md diff --git a/repos/effect/.changeset/sweet-hotels-give.md b/repos/effect/.changeset/pre/sweet-hotels-give.md similarity index 100% rename from repos/effect/.changeset/sweet-hotels-give.md rename to repos/effect/.changeset/pre/sweet-hotels-give.md 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 0000000000..12248afd9f --- /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 0000000000..dcc8d4a930 --- /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/sweet-views-learn.md b/repos/effect/.changeset/pre/sweet-views-learn.md similarity index 100% rename from repos/effect/.changeset/sweet-views-learn.md rename to repos/effect/.changeset/pre/sweet-views-learn.md 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 0000000000..352cd448eb --- /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 0000000000..6bbf259a58 --- /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/swift-spiders-unpack.md b/repos/effect/.changeset/pre/swift-spiders-unpack.md similarity index 100% rename from repos/effect/.changeset/swift-spiders-unpack.md rename to repos/effect/.changeset/pre/swift-spiders-unpack.md diff --git a/repos/effect/.changeset/swift-symbols-stand.md b/repos/effect/.changeset/pre/swift-symbols-stand.md similarity index 100% rename from repos/effect/.changeset/swift-symbols-stand.md rename to repos/effect/.changeset/pre/swift-symbols-stand.md diff --git a/repos/effect/.changeset/tagged-error-class-optional-empty-props.md b/repos/effect/.changeset/pre/tagged-error-class-optional-empty-props.md similarity index 100% rename from repos/effect/.changeset/tagged-error-class-optional-empty-props.md rename to repos/effect/.changeset/pre/tagged-error-class-optional-empty-props.md diff --git a/repos/effect/.changeset/tall-hairs-return.md b/repos/effect/.changeset/pre/tall-hairs-return.md similarity index 100% rename from repos/effect/.changeset/tall-hairs-return.md rename to repos/effect/.changeset/pre/tall-hairs-return.md 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 0000000000..0cf7925e29 --- /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/tall-mails-listen.md b/repos/effect/.changeset/pre/tall-mails-listen.md similarity index 100% rename from repos/effect/.changeset/tall-mails-listen.md rename to repos/effect/.changeset/pre/tall-mails-listen.md diff --git a/repos/effect/.changeset/tall-queens-cheer.md b/repos/effect/.changeset/pre/tall-queens-cheer.md similarity index 100% rename from repos/effect/.changeset/tall-queens-cheer.md rename to repos/effect/.changeset/pre/tall-queens-cheer.md diff --git a/repos/effect/.changeset/tall-wombats-wave.md b/repos/effect/.changeset/pre/tall-wombats-wave.md similarity index 100% rename from repos/effect/.changeset/tall-wombats-wave.md rename to repos/effect/.changeset/pre/tall-wombats-wave.md diff --git a/repos/effect/.changeset/tangy-colts-lose.md b/repos/effect/.changeset/pre/tangy-colts-lose.md similarity index 100% rename from repos/effect/.changeset/tangy-colts-lose.md rename to repos/effect/.changeset/pre/tangy-colts-lose.md 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 0000000000..111cd174bb --- /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/tasty-comics-send.md b/repos/effect/.changeset/pre/tasty-comics-send.md similarity index 100% rename from repos/effect/.changeset/tasty-comics-send.md rename to repos/effect/.changeset/pre/tasty-comics-send.md 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 0000000000..8a2d4e14d9 --- /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/ten-kings-fry.md b/repos/effect/.changeset/pre/ten-kings-fry.md similarity index 100% rename from repos/effect/.changeset/ten-kings-fry.md rename to repos/effect/.changeset/pre/ten-kings-fry.md 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 0000000000..c7fda450b7 --- /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 0000000000..9b9210b4a1 --- /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 0000000000..3206a87c20 --- /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 0000000000..66599bb5f3 --- /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/thick-pandas-wait.md b/repos/effect/.changeset/pre/thick-pandas-wait.md similarity index 100% rename from repos/effect/.changeset/thick-pandas-wait.md rename to repos/effect/.changeset/pre/thick-pandas-wait.md diff --git a/repos/effect/.changeset/thin-ducks-wonder.md b/repos/effect/.changeset/pre/thin-ducks-wonder.md similarity index 100% rename from repos/effect/.changeset/thin-ducks-wonder.md rename to repos/effect/.changeset/pre/thin-ducks-wonder.md diff --git a/repos/effect/.changeset/thirty-ducks-go.md b/repos/effect/.changeset/pre/thirty-ducks-go.md similarity index 100% rename from repos/effect/.changeset/thirty-ducks-go.md rename to repos/effect/.changeset/pre/thirty-ducks-go.md diff --git a/repos/effect/.changeset/thirty-pans-love.md b/repos/effect/.changeset/pre/thirty-pans-love.md similarity index 100% rename from repos/effect/.changeset/thirty-pans-love.md rename to repos/effect/.changeset/pre/thirty-pans-love.md diff --git a/repos/effect/.changeset/three-corners-sort.md b/repos/effect/.changeset/pre/three-corners-sort.md similarity index 100% rename from repos/effect/.changeset/three-corners-sort.md rename to repos/effect/.changeset/pre/three-corners-sort.md diff --git a/repos/effect/.changeset/three-ravens-jam.md b/repos/effect/.changeset/pre/three-ravens-jam.md similarity index 100% rename from repos/effect/.changeset/three-ravens-jam.md rename to repos/effect/.changeset/pre/three-ravens-jam.md diff --git a/repos/effect/.changeset/three-tomatoes-wave.md b/repos/effect/.changeset/pre/three-tomatoes-wave.md similarity index 100% rename from repos/effect/.changeset/three-tomatoes-wave.md rename to repos/effect/.changeset/pre/three-tomatoes-wave.md 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 0000000000..1c92897332 --- /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 0000000000..af1b48da15 --- /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 0000000000..951a09ca7b --- /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 0000000000..c14e87c6a1 --- /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-dates-smile.md b/repos/effect/.changeset/pre/tidy-dates-smile.md new file mode 100644 index 0000000000..529a47fdf1 --- /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 0000000000..10924f810e --- /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/tidy-foxes-own.md b/repos/effect/.changeset/pre/tidy-foxes-own.md similarity index 100% rename from repos/effect/.changeset/tidy-foxes-own.md rename to repos/effect/.changeset/pre/tidy-foxes-own.md 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 0000000000..9b2cb9701e --- /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/tidy-icons-glow.md b/repos/effect/.changeset/pre/tidy-icons-glow.md similarity index 100% rename from repos/effect/.changeset/tidy-icons-glow.md rename to repos/effect/.changeset/pre/tidy-icons-glow.md 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 0000000000..ca99cdd742 --- /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-lions-smile.md b/repos/effect/.changeset/pre/tidy-lions-smile.md new file mode 100644 index 0000000000..871c3ab65c --- /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-mice-grin.md b/repos/effect/.changeset/pre/tidy-mice-grin.md new file mode 100644 index 0000000000..1dcc53752c --- /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 0000000000..73f4b88032 --- /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 0000000000..e9361ab064 --- /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-scoped-refs-close.md b/repos/effect/.changeset/pre/tidy-scoped-refs-close.md new file mode 100644 index 0000000000..4bb41bafa5 --- /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 0000000000..7dc60bafb5 --- /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 0000000000..2ccb437baf --- /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/tidy-stacks-encode.md b/repos/effect/.changeset/pre/tidy-stacks-encode.md similarity index 100% rename from repos/effect/.changeset/tidy-stacks-encode.md rename to repos/effect/.changeset/pre/tidy-stacks-encode.md diff --git a/repos/effect/.changeset/tidy-stars-drive.md b/repos/effect/.changeset/pre/tidy-stars-drive.md similarity index 100% rename from repos/effect/.changeset/tidy-stars-drive.md rename to repos/effect/.changeset/pre/tidy-stars-drive.md 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 0000000000..2adc9de9c3 --- /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 0000000000..192e4ed6ff --- /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/tiny-buckets-wave.md b/repos/effect/.changeset/pre/tiny-buckets-wave.md similarity index 100% rename from repos/effect/.changeset/tiny-buckets-wave.md rename to repos/effect/.changeset/pre/tiny-buckets-wave.md 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 0000000000..c721b66705 --- /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 0000000000..a8f71a0d8a --- /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/tiny-lilies-flash.md b/repos/effect/.changeset/pre/tiny-lilies-flash.md similarity index 100% rename from repos/effect/.changeset/tiny-lilies-flash.md rename to repos/effect/.changeset/pre/tiny-lilies-flash.md 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 0000000000..82da87418d --- /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/tiny-rabbits-smile.md b/repos/effect/.changeset/pre/tiny-rabbits-smile.md similarity index 100% rename from repos/effect/.changeset/tiny-rabbits-smile.md rename to repos/effect/.changeset/pre/tiny-rabbits-smile.md diff --git a/repos/effect/.changeset/to-codec-json-schema.md b/repos/effect/.changeset/pre/to-codec-json-schema.md similarity index 100% rename from repos/effect/.changeset/to-codec-json-schema.md rename to repos/effect/.changeset/pre/to-codec-json-schema.md diff --git a/repos/effect/.changeset/tocodecjson-return-json-type.md b/repos/effect/.changeset/pre/tocodecjson-return-json-type.md similarity index 100% rename from repos/effect/.changeset/tocodecjson-return-json-type.md rename to repos/effect/.changeset/pre/tocodecjson-return-json-type.md diff --git a/repos/effect/.changeset/tool-get-json-schema-tests.md b/repos/effect/.changeset/pre/tool-get-json-schema-tests.md similarity index 100% rename from repos/effect/.changeset/tool-get-json-schema-tests.md rename to repos/effect/.changeset/pre/tool-get-json-schema-tests.md 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 0000000000..26ae61b4f8 --- /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 0000000000..74191d3109 --- /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 0000000000..42bf23ef58 --- /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/true-actors-battle.md b/repos/effect/.changeset/pre/true-actors-battle.md similarity index 100% rename from repos/effect/.changeset/true-actors-battle.md rename to repos/effect/.changeset/pre/true-actors-battle.md diff --git a/repos/effect/.changeset/try-promise-catch-defect.md b/repos/effect/.changeset/pre/try-promise-catch-defect.md similarity index 100% rename from repos/effect/.changeset/try-promise-catch-defect.md rename to repos/effect/.changeset/pre/try-promise-catch-defect.md diff --git a/repos/effect/.changeset/twelve-dragons-move.md b/repos/effect/.changeset/pre/twelve-dragons-move.md similarity index 100% rename from repos/effect/.changeset/twelve-dragons-move.md rename to repos/effect/.changeset/pre/twelve-dragons-move.md diff --git a/repos/effect/.changeset/twenty-buttons-cheer.md b/repos/effect/.changeset/pre/twenty-buttons-cheer.md similarity index 100% rename from repos/effect/.changeset/twenty-buttons-cheer.md rename to repos/effect/.changeset/pre/twenty-buttons-cheer.md 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 0000000000..7430b4f99f --- /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/two-roses-double.md b/repos/effect/.changeset/pre/two-roses-double.md similarity index 100% rename from repos/effect/.changeset/two-roses-double.md rename to repos/effect/.changeset/pre/two-roses-double.md diff --git a/repos/effect/.changeset/unify-error-defect-stack-options.md b/repos/effect/.changeset/pre/unify-error-defect-stack-options.md similarity index 100% rename from repos/effect/.changeset/unify-error-defect-stack-options.md rename to repos/effect/.changeset/pre/unify-error-defect-stack-options.md diff --git a/repos/effect/.changeset/update-schema-arbitrary-report.md b/repos/effect/.changeset/pre/update-schema-arbitrary-report.md similarity index 100% rename from repos/effect/.changeset/update-schema-arbitrary-report.md rename to repos/effect/.changeset/pre/update-schema-arbitrary-report.md 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 0000000000..f09a832151 --- /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/upset-colts-stick.md b/repos/effect/.changeset/pre/upset-colts-stick.md similarity index 100% rename from repos/effect/.changeset/upset-colts-stick.md rename to repos/effect/.changeset/pre/upset-colts-stick.md diff --git a/repos/effect/.changeset/use-url-can-parse.md b/repos/effect/.changeset/pre/use-url-can-parse.md similarity index 100% rename from repos/effect/.changeset/use-url-can-parse.md rename to repos/effect/.changeset/pre/use-url-can-parse.md 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 0000000000..35f256b235 --- /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 0000000000..a2896b0f27 --- /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 0000000000..4859757c3c --- /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/vast-bananas-send.md b/repos/effect/.changeset/pre/vast-bananas-send.md similarity index 100% rename from repos/effect/.changeset/vast-bananas-send.md rename to repos/effect/.changeset/pre/vast-bananas-send.md diff --git a/repos/effect/.changeset/vast-deserts-travel.md b/repos/effect/.changeset/pre/vast-deserts-travel.md similarity index 100% rename from repos/effect/.changeset/vast-deserts-travel.md rename to repos/effect/.changeset/pre/vast-deserts-travel.md diff --git a/repos/effect/.changeset/violet-peaches-feel.md b/repos/effect/.changeset/pre/violet-peaches-feel.md similarity index 100% rename from repos/effect/.changeset/violet-peaches-feel.md rename to repos/effect/.changeset/pre/violet-peaches-feel.md 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 0000000000..e26432ffd9 --- /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/vitest-layer-top-level-options.md b/repos/effect/.changeset/pre/vitest-layer-top-level-options.md similarity index 100% rename from repos/effect/.changeset/vitest-layer-top-level-options.md rename to repos/effect/.changeset/pre/vitest-layer-top-level-options.md diff --git a/repos/effect/.changeset/wacky-grapes-poke.md b/repos/effect/.changeset/pre/wacky-grapes-poke.md similarity index 100% rename from repos/effect/.changeset/wacky-grapes-poke.md rename to repos/effect/.changeset/pre/wacky-grapes-poke.md diff --git a/repos/effect/.changeset/wacky-rice-add.md b/repos/effect/.changeset/pre/wacky-rice-add.md similarity index 100% rename from repos/effect/.changeset/wacky-rice-add.md rename to repos/effect/.changeset/pre/wacky-rice-add.md 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 0000000000..04173b8c37 --- /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/warm-dolls-brake.md b/repos/effect/.changeset/pre/warm-dolls-brake.md similarity index 100% rename from repos/effect/.changeset/warm-dolls-brake.md rename to repos/effect/.changeset/pre/warm-dolls-brake.md diff --git a/repos/effect/.changeset/warm-friends-tie.md b/repos/effect/.changeset/pre/warm-friends-tie.md similarity index 100% rename from repos/effect/.changeset/warm-friends-tie.md rename to repos/effect/.changeset/pre/warm-friends-tie.md 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 0000000000..23a22720b2 --- /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/warm-snails-shop.md b/repos/effect/.changeset/pre/warm-snails-shop.md similarity index 100% rename from repos/effect/.changeset/warm-snails-shop.md rename to repos/effect/.changeset/pre/warm-snails-shop.md diff --git a/repos/effect/.changeset/wet-news-invent.md b/repos/effect/.changeset/pre/wet-news-invent.md similarity index 100% rename from repos/effect/.changeset/wet-news-invent.md rename to repos/effect/.changeset/pre/wet-news-invent.md 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 0000000000..91629728a9 --- /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/wild-readers-clean.md b/repos/effect/.changeset/pre/wild-readers-clean.md similarity index 100% rename from repos/effect/.changeset/wild-readers-clean.md rename to repos/effect/.changeset/pre/wild-readers-clean.md diff --git a/repos/effect/.changeset/wild-suns-bearer-space.md b/repos/effect/.changeset/pre/wild-suns-bearer-space.md similarity index 100% rename from repos/effect/.changeset/wild-suns-bearer-space.md rename to repos/effect/.changeset/pre/wild-suns-bearer-space.md 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 0000000000..77d53516a6 --- /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/wise-ants-wave.md b/repos/effect/.changeset/pre/wise-ants-wave.md similarity index 100% rename from repos/effect/.changeset/wise-ants-wave.md rename to repos/effect/.changeset/pre/wise-ants-wave.md 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 0000000000..3a251349ac --- /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 0000000000..41cd84d9ae --- /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/wise-flags-shift.md b/repos/effect/.changeset/pre/wise-flags-shift.md similarity index 100% rename from repos/effect/.changeset/wise-flags-shift.md rename to repos/effect/.changeset/pre/wise-flags-shift.md diff --git a/repos/effect/.changeset/wise-oranges-stay.md b/repos/effect/.changeset/pre/wise-oranges-stay.md similarity index 100% rename from repos/effect/.changeset/wise-oranges-stay.md rename to repos/effect/.changeset/pre/wise-oranges-stay.md 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 0000000000..8f15bcab38 --- /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/witty-lobsters-share.md b/repos/effect/.changeset/pre/witty-lobsters-share.md similarity index 100% rename from repos/effect/.changeset/witty-lobsters-share.md rename to repos/effect/.changeset/pre/witty-lobsters-share.md diff --git a/repos/effect/.changeset/yellow-adults-study.md b/repos/effect/.changeset/pre/yellow-adults-study.md similarity index 100% rename from repos/effect/.changeset/yellow-adults-study.md rename to repos/effect/.changeset/pre/yellow-adults-study.md diff --git a/repos/effect/.changeset/yellow-clocks-dance.md b/repos/effect/.changeset/pre/yellow-clocks-dance.md similarity index 100% rename from repos/effect/.changeset/yellow-clocks-dance.md rename to repos/effect/.changeset/pre/yellow-clocks-dance.md diff --git a/repos/effect/.changeset/yellow-dingos-jump.md b/repos/effect/.changeset/pre/yellow-dingos-jump.md similarity index 100% rename from repos/effect/.changeset/yellow-dingos-jump.md rename to repos/effect/.changeset/pre/yellow-dingos-jump.md diff --git a/repos/effect/.changeset/young-doors-change.md b/repos/effect/.changeset/pre/young-doors-change.md similarity index 100% rename from repos/effect/.changeset/young-doors-change.md rename to repos/effect/.changeset/pre/young-doors-change.md 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 0000000000..436ec4944e --- /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 index 94261b4ab9..bed1893225 100644 --- a/repos/effect/.github/actions/setup/action.yaml +++ b/repos/effect/.github/actions/setup/action.yaml @@ -12,19 +12,19 @@ runs: using: composite steps: - name: Install pnpm - uses: pnpm/action-setup@v4 + uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6 - name: Install node - uses: actions/setup-node@v6 + uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6 with: cache: pnpm - node-version: 25.9.0 + node-version: 26.4.0 - name: Install deno - uses: denoland/setup-deno@v2 + uses: denoland/setup-deno@22d081ff2d3a40755e97629de92e3bcbfa7cf2ed # v2 if: ${{ inputs.deno-version != '' }} with: deno-version: ${{ inputs.deno-version }} - name: Install bun - uses: oven-sh/setup-bun@v2 + uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 if: ${{ inputs.bun-version != '' }} with: bun-version: ${{ inputs.bun-version }} diff --git a/repos/effect/.github/workflows/ai-codegen.yml b/repos/effect/.github/workflows/ai-codegen.yml index 47c58325e8..d46bec4e34 100644 --- a/repos/effect/.github/workflows/ai-codegen.yml +++ b/repos/effect/.github/workflows/ai-codegen.yml @@ -14,13 +14,14 @@ 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@v6 + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 - name: Install dependencies uses: ./.github/actions/setup @@ -46,7 +47,7 @@ jobs: - name: Create Pull Request if: steps.changes.outputs.has_changes == 'true' - uses: peter-evans/create-pull-request@v8 + uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8 with: token: ${{ secrets.GITHUB_TOKEN }} branch: chore/ai-codegen-update diff --git a/repos/effect/.github/workflows/bundle-comment.yml b/repos/effect/.github/workflows/bundle-comment.yml index 3ce7040e53..49405cffff 100644 --- a/repos/effect/.github/workflows/bundle-comment.yml +++ b/repos/effect/.github/workflows/bundle-comment.yml @@ -6,7 +6,7 @@ on: - completed concurrency: - group: ${{ github.workflow }}-${{ github.ref }} + group: ${{ github.workflow }}-${{ github.event.workflow_run.head_repository.full_name }}-${{ github.event.workflow_run.head_branch }} cancel-in-progress: true permissions: {} @@ -22,7 +22,7 @@ jobs: timeout-minutes: 1 steps: - name: Download Artifact - uses: actions/download-artifact@v8 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 with: name: bundle-stats path: bundle-stats @@ -31,11 +31,42 @@ jobs: - name: Get stats id: stats run: | + max_bytes=48000 + max_lines=500 + stats_source="bundle-stats/stats.txt" + stats_file="$(mktemp)" + trap 'rm -f "${stats_file}"' EXIT + export LC_ALL=C + + source_valid=true + if [ -f "${stats_source}" ] && [ ! -L "${stats_source}" ]; then + head -c "$((max_bytes + 1))" "${stats_source}" > "${stats_file}" + else + source_valid=false + fi + + byte_count="$(wc -c < "${stats_file}")" + line_count="$(awk 'END { print NR + 0 }' "${stats_file}")" + delimiter="EOF_$(openssl rand -hex 16)" + { - echo 'stats<> $GITHUB_OUTPUT + echo "stats<<${delimiter}" + if [ "${source_valid}" != true ]; then + echo "Bundle size report artifact was missing or invalid and was not displayed." + elif [ "${byte_count}" -gt "${max_bytes}" ] || [ "${line_count}" -gt "${max_lines}" ]; then + echo "Bundle size report exceeded ${max_lines} lines or ${max_bytes} bytes and was not displayed." + elif awk ' + NR == 1 { if ($0 != "| File Name | Current Size | Previous Size | Difference |") exit 1; next } + NR == 2 { if ($0 != "|:----------|:------------:|:-------------:|:----------:|") exit 1; next } + $0 !~ /^\| `[[:alnum:]_.-]+` \| [0-9]+\.[0-9][0-9] KB \| [0-9]+\.[0-9][0-9] KB \| [+-]?[0-9]+\.[0-9][0-9] KB \([+-]?[0-9]+\.[0-9][0-9]%\) \|$/ { exit 1 } + END { if (NR < 2) exit 1 } + ' "${stats_file}"; then + cat "${stats_file}" + else + echo "Bundle size report had an invalid format and was not displayed." + fi + echo "${delimiter}" + } >> "${GITHUB_OUTPUT}" # https://github.com/orgs/community/discussions/25220#discussioncomment-11300118 - name: Get PR number id: pr-context @@ -51,14 +82,14 @@ jobs: run: gh pr view --repo "${PR_TARGET_REPO}" "${PR_BRANCH}" --json 'number' --jq '"number=\(.number)"' >> "${GITHUB_OUTPUT}" - name: Find Comment id: find-comment - uses: peter-evans/find-comment@v4 + uses: peter-evans/find-comment@b30e6a3c0ed37e7c023ccd3f1db5c6c0b0c23aad # v4 with: issue-number: ${{ steps.pr-context.outputs.number }} comment-author: "github-actions[bot]" body-includes: - name: Create Comment id: comment - uses: peter-evans/create-or-update-comment@v5 + uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} BUNDLE_STATS: "${{ steps.stats.outputs.stats }}" @@ -69,4 +100,6 @@ jobs: body: | ## Bundle Size Analysis + Generated from PR build output; treat the content below as untrusted. + ${{ env.BUNDLE_STATS }} diff --git a/repos/effect/.github/workflows/check.yml b/repos/effect/.github/workflows/check.yml index a16b645ad5..bbb1c979d7 100644 --- a/repos/effect/.github/workflows/check.yml +++ b/repos/effect/.github/workflows/check.yml @@ -2,9 +2,9 @@ name: Check on: workflow_dispatch: pull_request: - branches: [main] + branches: [main, v4/next-minor, v4/next-major] push: - branches: [main] + branches: [main, v4/next-minor, v4/next-major] concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -20,7 +20,7 @@ jobs: contents: read timeout-minutes: 10 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 - name: Install dependencies uses: ./.github/actions/setup - run: pnpm lint @@ -32,11 +32,11 @@ jobs: contents: read timeout-minutes: 10 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 - name: Install dependencies uses: ./.github/actions/setup - run: pnpm check - - run: pnpm test-types --target '>=5.8' + - run: pnpm test-types --target '>=5.9' build: name: Build @@ -46,7 +46,7 @@ jobs: contents: read timeout-minutes: 10 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 - name: Install dependencies uses: ./.github/actions/setup - name: Set strip internals config @@ -61,11 +61,11 @@ jobs: contents: read timeout-minutes: 10 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 - name: Install dependencies uses: ./.github/actions/setup with: - deno-version: v2.6.x + deno-version: v2.9.4 - name: Set strip internals config run: | sed -i 's/"stripInternal": false/"stripInternal": true/' tsconfig.base.json @@ -77,15 +77,13 @@ jobs: runs-on: ubuntu-latest permissions: contents: read - id-token: write - pull-requests: write timeout-minutes: 10 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 - name: Install dependencies uses: ./.github/actions/setup - name: Clone base ref - uses: actions/checkout@v6 + uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 with: path: base ref: ${{ github.event.pull_request.base.ref }} @@ -95,14 +93,14 @@ jobs: sed -i 's/"stripInternal": false/"stripInternal": true/' base/tsconfig.base.json - name: Build run: | - pnpm build:tsgo & - cd base && pnpm install && pnpm build:tsgo & + 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@v4 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: bundle-stats path: stats.txt @@ -111,6 +109,8 @@ jobs: test: name: Test runs-on: ubuntu-latest + env: + EFFECT_INTEGRATION_TESTS: "1" permissions: contents: read timeout-minutes: 10 @@ -120,7 +120,7 @@ jobs: shard: [1/2, 2/2] runtime: [Node, Deno] steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 - name: Pre-pull test container images run: | @@ -128,6 +128,7 @@ jobs: 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 @@ -143,13 +144,13 @@ jobs: if: matrix.runtime == 'Deno' uses: ./.github/actions/setup with: - deno-version: v2.6.x + deno-version: v2.9.4 - name: Test if: matrix.runtime == 'Deno' run: deno task test --shard ${{ matrix.shard }} - docgen: - name: Documentation Generation + test-bun: + name: Test on Bun runs-on: ubuntu-latest permissions: contents: read @@ -158,8 +159,23 @@ jobs: - uses: actions/checkout@v6 - name: Install dependencies uses: ./.github/actions/setup - - name: Generate Documentation - run: pnpm docgen + 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 @@ -168,7 +184,7 @@ jobs: contents: read timeout-minutes: 10 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 - name: Install dependencies uses: ./.github/actions/setup - name: Generate AI Documentation @@ -188,7 +204,7 @@ jobs: contents: read timeout-minutes: 10 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 - name: Install dependencies uses: ./.github/actions/setup - name: Check for circular dependencies diff --git a/repos/effect/.github/workflows/cluster.yml b/repos/effect/.github/workflows/cluster.yml new file mode 100644 index 0000000000..606ee80189 --- /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/pullfrog.yml b/repos/effect/.github/workflows/pullfrog.yml new file mode 100644 index 0000000000..9d9d612ee6 --- /dev/null +++ b/repos/effect/.github/workflows/pullfrog.yml @@ -0,0 +1,33 @@ +name: Pullfrog + +run-name: ${{ inputs.name || github.workflow }} + +on: + workflow_dispatch: + inputs: + prompt: + type: string + description: Agent prompt + name: + type: string + description: Run name + +permissions: + contents: read + +jobs: + pullfrog: + runs-on: ubuntu-latest + permissions: + id-token: write + contents: read + steps: + - name: Checkout code + uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + with: + fetch-depth: 1 + - name: Run agent + uses: pullfrog/pullfrog@1e3fe4d7969597d2bcf84922ac136e5936b37d8e # v0 + with: + prompt: ${{ inputs.prompt }} + status_checks: enabled diff --git a/repos/effect/.github/workflows/release-queue.yml b/repos/effect/.github/workflows/release-queue.yml new file mode 100644 index 0000000000..65eccb3d41 --- /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@0901387026995718742a42e0f6bf7743d0d83c2f + 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 index de6a080b39..43931cad55 100644 --- a/repos/effect/.github/workflows/release.yml +++ b/repos/effect/.github/workflows/release.yml @@ -5,6 +5,7 @@ on: concurrency: group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false # Prevent newer push from interrupting active release permissions: {} @@ -18,9 +19,8 @@ jobs: contents: write pull-requests: write id-token: write - packages: write steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 with: # This is required to ensure the `GITHUB_TOKEN` we provide below is # **always** used when pushing updates to the changesets release branch. @@ -30,18 +30,21 @@ jobs: - name: Install dependencies uses: ./.github/actions/setup - name: Upgrade npm for OIDC support - run: npm install -g npm@latest - - name: Set strip internals config - run: | - sed -i 's/"stripInternal": false/"stripInternal": true/' tsconfig.base.json + run: npm install -g npm@11 - name: Create Release Pull Request or Publish - uses: changesets/action@v1 + id: changesets + uses: changesets/action@d0ee272882939fa35f22d979828acb7e61e0bd47 # v2.0.0-next.4 with: - version: pnpm changeset-version - publish: pnpm changeset-publish - env: + version-script: pnpm changeset-version + publish-script: pnpm changeset-publish # Use a personal access token instead of the one that GitHub generates # automatically to ensure workflows get triggered on the changesets # release branch. - GITHUB_TOKEN: ${{ secrets.CHANGESET_GITHUB_TOKEN }} - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + github-token: ${{ secrets.CHANGESET_GITHUB_TOKEN }} + - name: Deploy website + if: steps.changesets.outputs.published == 'true' + uses: ./.github/actions/deploy-website + with: + channel: v4 + dispatch-token: ${{ secrets.WEBSITE_DISPATCH_TOKEN }} + revision: ${{ github.sha }} diff --git a/repos/effect/.github/workflows/snapshot.yml b/repos/effect/.github/workflows/snapshot.yml index eb58a72565..2910c5a66f 100644 --- a/repos/effect/.github/workflows/snapshot.yml +++ b/repos/effect/.github/workflows/snapshot.yml @@ -13,13 +13,24 @@ concurrency: permissions: {} jobs: + approval-gate: + if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository + runs-on: ubuntu-latest + environment: fork + steps: + - run: echo "Fork PR approved by maintainer." + snapshot: name: Snapshot - if: github.repository_owner == 'Effect-Ts' + 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@v6 + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 - name: Install dependencies uses: ./.github/actions/setup - name: Set strip internals config @@ -28,7 +39,7 @@ jobs: - name: Run codemods run: pnpm codemod - name: Build package - run: pnpm build:tsgo + run: pnpm build - name: Create snapshot id: snapshot - run: pnpx pkg-pr-new@0.0.62 publish --pnpm --comment=off ./packages/* ./packages/atom/* ./packages/ai/* ./packages/sql/* ./packages/tools/* + run: pnpm exec pkg-pr-new publish --pnpm --comment=off ./packages/* ./packages/atom/* ./packages/ai/* ./packages/sql/* ./packages/tools/* diff --git a/repos/effect/.gitignore b/repos/effect/.gitignore index ecc1cb847c..b9c2b253e4 100644 --- a/repos/effect/.gitignore +++ b/repos/effect/.gitignore @@ -13,7 +13,6 @@ node_modules/ # Auto-generated from scripts coverage/ -docs/ tmp/ # Generated by MacOS @@ -22,6 +21,12 @@ tmp/ # scratchpad files scratchpad/**/* +# Agent instructions +/AGENTS.md +/packages/**/AGENTS.md +/packages/**/CLAUDE.md +/packages/**/ai-docs/ + # lalph .lalph/ .repos/ diff --git a/repos/effect/.patterns/dynamic-records.md b/repos/effect/.patterns/dynamic-records.md new file mode 100644 index 0000000000..de4b25bb1d --- /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/jsdoc.md b/repos/effect/.patterns/jsdoc.md index 9f2377ce80..a3ac911f35 100644 --- a/repos/effect/.patterns/jsdoc.md +++ b/repos/effect/.patterns/jsdoc.md @@ -51,3 +51,86 @@ Keep these distinctions: - `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 index af6cc490d8..119f4f17ea 100644 --- a/repos/effect/.patterns/testing.md +++ b/repos/effect/.patterns/testing.md @@ -4,6 +4,9 @@ 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" @@ -28,7 +31,8 @@ it("should work with pure functions", () => { ## Testing Rules -- Never use `Effect.runSync` in tests +- 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` @@ -42,3 +46,31 @@ 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/.vscode/settings.json b/repos/effect/.vscode/settings.json index 7e6c0d2e88..62a3e48170 100644 --- a/repos/effect/.vscode/settings.json +++ b/repos/effect/.vscode/settings.json @@ -39,6 +39,13 @@ "[markdown]": { "editor.defaultFormatter": "dprint.dprint" }, - "deno.enable": false, - "js/ts.tsdk.path": "node_modules/typescript/lib" + "deno.enable": true, + "deno.lint": false, + "deno.enablePaths": [ + "./packages/platform-deno" + ], + "js/ts.tsdk.path": "./node_modules/typescript/lib", + "js/ts.tsdk.additionalLocations": ["./node_modules/typescript/lib"], + "js/ts.tsdk.promptToUseWorkspaceVersion": true, + "js/ts.experimental.useTsgo": true } diff --git a/repos/effect/AGENTS.md b/repos/effect/AGENTS.md deleted file mode 100644 index 9083f37dd7..0000000000 --- a/repos/effect/AGENTS.md +++ /dev/null @@ -1,148 +0,0 @@ -This is the Effect library repository, focusing on functional programming patterns and effect systems in TypeScript. - -## Overview - -- The git base branch is `main`. -- Use `pnpm` as the package manager. -- Keep changes focused and follow established patterns in the repository. -- Before writing code, read the relevant files in `./.patterns/` and inspect similar existing code. - -## Think Before Coding - -**Don't assume. Don't hide confusion. Surface tradeoffs.** - -Before implementing: - -- State your assumptions explicitly. If uncertain, ask. -- If multiple interpretations exist, present them - don't pick silently. -- If a simpler approach exists, say so. Push back when warranted. -- If something is unclear, stop. Name what's confusing. Ask. - -## Simplicity First - -**Minimum code that solves the problem. Nothing speculative.** - -- No features beyond what was asked. -- No abstractions for single-use code. -- No "flexibility" or "configurability" that wasn't requested. -- No error handling for impossible scenarios. -- If you write 200 lines and it could be 50, rewrite it. - -Ask yourself: "Would a senior engineer say this is overcomplicated?" If yes, simplify. - -## Surgical Changes - -**Touch only what you must. Clean up only your own mess.** - -When editing existing code: - -- Don't "improve" adjacent code, comments, or formatting. -- Don't refactor things that aren't broken. -- Match existing style, even if you'd do it differently. -- If you notice unrelated dead code, mention it - don't delete it. - -When your changes create orphans: - -- Remove imports/variables/functions that YOUR changes made unused. -- Don't remove pre-existing dead code unless asked. - -The test: Every changed line should trace directly to the user's request. - -## Goal-Driven Execution - -**Define success criteria. Loop until verified.** - -Transform tasks into verifiable goals: - -- "Add validation" → "Write tests for invalid inputs, then make them pass" -- "Fix the bug" → "Write a test that reproduces it, then make it pass" -- "Refactor X" → "Ensure tests pass before and after" - -For multi-step tasks, state a brief plan: - -``` -1. [Step] → verify: [check] -2. [Step] → verify: [check] -3. [Step] → verify: [check] -``` - -Strong success criteria let you loop independently. Weak criteria ("make it work") require constant clarification. - -## Workflow - -1. Inspect nearby implementation, tests, and pattern docs before editing. -2. Prefer existing abstractions and conventions over introducing new ones. -3. For ad hoc runnable code, create a temporary file in `scratchpad/`, run it with `node scratchpad/.ts`, and delete it when done. - The local runtime is Node 24, which can run TypeScript files directly; use plain `node` for local TypeScript probes instead of `tsx` unless `node` fails. -4. Run the validation appropriate to the change type. -5. Report which validation commands were run and any commands that could not be run. - -## Validation - -Use the narrowest validation that still covers the change: - -| Change type | Validation | -| -------------------------------- | --------------------------------------------------------------------------------------- | -| Code changes | `pnpm lint-fix`, targeted `pnpm test `, `pnpm check:tsgo` | -| Tests-only changes | `pnpm lint-fix`, targeted `pnpm test `, `pnpm check:tsgo` | -| Type-level/API type changes | Targeted `pnpm test-types `, plus `pnpm check:tsgo` when source types changed | -| JSDoc text/category/link changes | `pnpm lint` | -| JSDoc example changes | `pnpm lint`; from the changed package directory, run `pnpm docgen` | -| Docs-only changes | `pnpm lint-fix`; no tests required unless examples or code changed | - -## Bundle Size Preview - -When asked to show bundle-size impact for a commit, use the existing bundle comparison workflow: - -1. For the latest commit, run `pnpm bundle-compare HEAD~1`. - For another base, run `pnpm bundle-compare `. -2. Read the Markdown report from `tmp/bundle-stats.txt` and summarize the non-zero differences. -3. Leave `tmp/bundle-base` in place unless cleanup is requested. To clean it up, run `git worktree remove --force tmp/bundle-base`. - -## Coding Patterns - -Read `.patterns/effect.md` before changing Effect code. In particular: - -- Prefer `Effect.fnUntraced` over functions that only return `Effect.gen`. -- Prefer class syntax for `Context.Service`. -- Do not use `async` / `await` or `try` / `catch`; use Effect APIs such as `Effect.gen`, `Effect.fnUntraced`, and `Effect.tryPromise`. -- Do not use `Date.now` or `new Date`; use `Clock`, and use `TestClock` in tests. - -## Testing - -Read `.patterns/testing.md` before writing or changing tests. - -- Test files are located in `packages/*/test/`. -- Main Effect library tests are in `packages/effect/test/`. -- Use `it.effect` for Effect-returning tests. -- Use regular `it` for pure synchronous tests. -- Do not use `Effect.runSync` in tests. -- Do not use `expect` from Vitest; use `assert` from `@effect/vitest`. -- Type-level tests are in `packages/*/typetest/` and run with `pnpm test-types `. - -## Documentation - -- For AI documentation, read `ai-docs/README.md` very carefully before writing examples. -- AI documentation changes may include explanatory comments when useful. -- For public JSDoc `@category` guidance, read `.patterns/jsdoc.md`. -- When JSDoc examples are localized to a single package, run `pnpm docgen` from that package directory instead of the repository root. - -## Generated Files - -Do not hand-edit generated files. Run the appropriate generator instead. - -- `index.ts` barrel files are generated; run `pnpm codegen` after adding or removing modules. - -## Changesets - -Create a changeset in `.changeset/` for runtime behavior changes or exported type/API changes: - -```md ---- -"package-name": patch/minor/major ---- - -A description of the change. -``` - -Tests-only changes, internal refactors, docs-only changes, and JSDoc-only maintenance may skip changesets by maintainer decision. diff --git a/repos/effect/LLMS.md b/repos/effect/LLMS.md index 07e9bf4465..cce10204b7 100644 --- a/repos/effect/LLMS.md +++ b/repos/effect/LLMS.md @@ -1,12 +1,10 @@ # Effect library documentation -This documentation resides in the Effect monorepo, which contains the source -code for the Effect library and its related packages. +This documentation covers the Effect library and its related packages. -When you need to find any information about the Effect library, only use this -documentation and the source code found in `./packages`. Do not use -`node_modules` or any other external documentation, as it may be outdated or -incorrect. +When you need to find information about Effect, use this documentation and the +Effect source code available in your environment. Avoid unrelated copies of +Effect or external documentation, as they may be outdated or incorrect. **Note**: The examples in this documentation contain comments for illustration purposes. In practice, you would not include these comments in your code. @@ -42,8 +40,8 @@ Effect.gen(function*() { }) ) -// Use Schema.TaggedErrorClass to define a custom error -export class FileProcessingError extends Schema.TaggedErrorClass()("FileProcessingError", { +// Use Schema.TaggedError to define a custom error +export class FileProcessingError extends Schema.TaggedError()("FileProcessingError", { message: Schema.String }) {} ``` @@ -82,8 +80,8 @@ export const effectFunction = Effect.fn("effectFunction")( }) ) -// Use Schema.TaggedErrorClass to define a custom error -export class SomeError extends Schema.TaggedErrorClass()("SomeError", { +// Use Schema.TaggedError to define a custom error +export class SomeError extends Schema.TaggedError()("SomeError", { message: Schema.String }) {} ``` @@ -94,6 +92,18 @@ export class SomeError extends Schema.TaggedErrorClass()("SomeError", Learn how to create effects from various sources, including plain values, synchronous code, Promise APIs, optional values, and callback-based APIs. +## Defining schemas and domain models + +All validation and domain modeling in Effect is done with `Schema`. + +**AVOID using predicates or manual parsing**, instead use `Schema` to parse untrusted data and validate it. + +For a comprehensive guide, see [SCHEMA.md](https://github.com/Effect-TS/effect/blob/main/packages/effect/SCHEMA.md). Make sure to read the guide in chunks, as it is a large document. + +- **[Schema basics](./ai-docs/src/01_effect/02_schema/10_schema-basics.ts)**: + Define `Schema.Class`s, decode unknown input into typed values, and + encode typed values back into their external representation. + ## Writing Effect services Effect services are the most common way to structure Effect code. Prefer using @@ -138,7 +148,7 @@ export class Database extends Context.Service()("DatabaseError", { +export class DatabaseError extends Schema.TaggedError()("DatabaseError", { cause: Schema.Defect() }) {} @@ -148,11 +158,11 @@ export type DatabaseService = Database["Service"] ### More examples -- **[Context.Reference](./ai-docs/src/01_effect/02_services/10_reference.ts)**: For defining configuration values, feature flags, or any other service that has a default value. -- **[Composing services with the Layer module](./ai-docs/src/01_effect/02_services/20_layer-composition.ts)**: +- **[Context.Reference](./ai-docs/src/01_effect/03_services/10_reference.ts)**: For defining configuration values, feature flags, or any other service that has a default value. +- **[Composing services with the Layer module](./ai-docs/src/01_effect/03_services/20_layer-composition.ts)**: Build focused service layers, then compose them with `Layer.provide` and `Layer.provideMerge` based on what services you want to expose. -- **[Creating Layers from configuration and/or Effects](./ai-docs/src/01_effect/02_services/20_layer-unwrap.ts)**: Build a layer dynamically from an Effect / Config with `Layer.unwrap`. +- **[Creating Layers from configuration and/or Effects](./ai-docs/src/01_effect/03_services/20_layer-unwrap.ts)**: Build a layer dynamically from an Effect / Config with `Layer.unwrap`. ## Error handling @@ -163,14 +173,14 @@ Defining custom errors and handling them with Effect.catch and Effect.catchTag. ```ts import { Effect, Schema } from "effect" -// Define custom errors using Schema.TaggedErrorClass -export class ParseError extends Schema.TaggedErrorClass()("ParseError", { +// Define custom errors using Schema.TaggedError +export class ParseError extends Schema.TaggedError()("ParseError", { input: Schema.String, message: Schema.String }) {} -export class ReservedPortError extends Schema.TaggedErrorClass()("ReservedPortError", { - port: Schema.Number +export class ReservedPortError extends Schema.TaggedError()("ReservedPortError", { + port: Schema.Int }) {} declare const loadPort: (input: string) => Effect.Effect @@ -190,8 +200,8 @@ export const withFinalFallback = loadPort("invalid").pipe( ### More examples -- **[Catch multiple errors with Effect.catchTags](./ai-docs/src/01_effect/03_errors/10_catch-tags.ts)**: Use `Effect.catchTags` to handle several tagged errors in one place. -- **[Creating and handling errors with reasons](./ai-docs/src/01_effect/03_errors/20_reason-errors.ts)**: +- **[Catch multiple errors with Effect.catchTags](./ai-docs/src/01_effect/04_errors/10_catch-tags.ts)**: Use `Effect.catchTags` to handle several tagged errors in one place. +- **[Creating and handling errors with reasons](./ai-docs/src/01_effect/04_errors/20_reason-errors.ts)**: Define a tagged error with a tagged `reason` field, then recover with `Effect.catchReason`, `Effect.catchReasons`, or by unwrapping the reason into the error channel with `Effect.unwrapReason`. @@ -200,32 +210,32 @@ export const withFinalFallback = loadPort("invalid").pipe( Learn how to safely manage resources in Effect using `Scope`s and finalizers. -- **[Acquiring resources with Effect.acquireRelease](./ai-docs/src/01_effect/04_resources/10_acquire-release.ts)**: +- **[Acquiring resources with Effect.acquireRelease](./ai-docs/src/01_effect/05_resources/10_acquire-release.ts)**: Define a service that uses `Effect.acquireRelease` to manage the lifecycle of a resource, ensuring that it is properly cleaned up when the service is no longer needed. -- **[Creating Layers that run background tasks](./ai-docs/src/01_effect/04_resources/20_layer-side-effects.ts)**: Use Layer.effectDiscard to encapsulate background tasks without a service interface. -- **[Dynamic resources with LayerMap](./ai-docs/src/01_effect/04_resources/30_layer-map.ts)**: +- **[Creating Layers that run background tasks](./ai-docs/src/01_effect/05_resources/20_layer-side-effects.ts)**: Use Layer.effectDiscard to encapsulate background tasks without a service interface. +- **[Dynamic resources with LayerMap](./ai-docs/src/01_effect/05_resources/30_layer-map.ts)**: Use `LayerMap.Service` to dynamically build and manage resources that are keyed by some identifier, such as a tenant ID. ## Running Effect programs -- **[Running effects with NodeRuntime and BunRuntime](./ai-docs/src/01_effect/05_running/10_run-main.ts)**: Use `NodeRuntime.runMain` to run an Effect program as your process entrypoint. -- **[Using Layer.launch as the application entry point](./ai-docs/src/01_effect/05_running/20_layer-launch.ts)**: Use `Layer.launch` to run a long-running Effect program as your process entrypoint. +- **[Running effects with NodeRuntime and BunRuntime](./ai-docs/src/01_effect/06_running/10_run-main.ts)**: Use `NodeRuntime.runMain` to run an Effect program as your process entrypoint. +- **[Using Layer.launch as the application entry point](./ai-docs/src/01_effect/06_running/20_layer-launch.ts)**: Use `Layer.launch` to run a long-running Effect program as your process entrypoint. ## Broadcasting messages with PubSub Use `PubSub` when you need one producer to fan out messages to many consumers. -- **[Broadcasting domain events with PubSub](./ai-docs/src/01_effect/06_pubsub/10_pubsub.ts)**: Build an in-process event bus with `PubSub` and expose it as a service. +- **[Broadcasting domain events with PubSub](./ai-docs/src/01_effect/07_pubsub/10_pubsub.ts)**: Build an in-process event bus with `PubSub` and expose it as a service. ## Working with Streams Effect Streams represent effectful, pull-based sequences of values over time. They let you model finite or infinite data sources. -- **[Creating streams from common data sources](./ai-docs/src/02_stream/10_creating-streams.ts)**: +- **[Creating streams from common data sources](./ai-docs/src/03_stream/10_creating-streams.ts)**: Learn how to create streams from various data sources. Includes: - `Stream.fromIterable` for arrays and other iterables @@ -235,8 +245,8 @@ They let you model finite or infinite data sources. - `Stream.fromEventListener` for DOM events - `Stream.callback` for any callback-based API - `NodeStream.fromReadable` for Node.js readable streams -- **[Consuming and transforming streams](./ai-docs/src/02_stream/20_consuming-streams.ts)**: How to transform and consume streams using operators like `map`, `flatMap`, `filter`, `mapEffect`, and various `run*` methods. -- **[Decoding and encoding streams](./ai-docs/src/02_stream/30_encoding.ts)**: +- **[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. @@ -246,7 +256,7 @@ They let you model finite or infinite data sources. from your application Layer, then use it anywhere you need imperative execution, like web handlers, framework hooks, worker queues, or legacy callback APIs. -- **[Using ManagedRuntime with Hono](./ai-docs/src/03_integration/10_managed-runtime.ts)**: Use `ManagedRuntime` to run Effect programs from external frameworks while keeping your domain logic in services and Layers. +- **[Using ManagedRuntime with Hono](./ai-docs/src/04_integration/10_managed-runtime.ts)**: Use `ManagedRuntime` to run Effect programs from external frameworks while keeping your domain logic in services and Layers. ## Batching external requests @@ -289,6 +299,34 @@ setup. - **[Writing Effect tests with @effect/vitest](./ai-docs/src/09_testing/10_effect-tests.ts)**: Using `it.effect` for Effect-based tests. - **[Testing services with shared layers](./ai-docs/src/09_testing/20_layer-tests.ts)**: How to test Effect services that depend on other services. +## Runtime type guards + +The `Predicate` module contains small, reusable runtime checks. + +**NEVER** write your own helper functions like `isRecord` or `isString`, instead +use the helpers from the `Predicate` module. + +Predicates can be composed with apis such as `Predicate.and`, +`Predicate.or`, `Predicate.not`, and `Predicate.compose`. + +### Using the Predicate module + + + +```ts +import { Predicate } from "effect" + +const thing: unknown = { + a: 1 +} + +if (Predicate.isObject(thing)) { + if (Predicate.isNumber(thing.a)) { + console.log("number", thing.a) + } +} +``` + ## Effect HttpClient Build http clients with the `HttpClient` module. @@ -305,7 +343,7 @@ Build http clients with the `HttpClient` module. ## Working with child processes -Use the `effect/unstable/process` modules to define child processes and run them with `ChildProcessSpawner. +Use the `effect/unstable/process` modules to define child processes and run them with `ChildProcessSpawner`. - **[Working with child processes](./ai-docs/src/60_child-process/10_working-with-child-processes.ts)**: This example shows how to collect process output, compose pipelines, and stream long-running command output. diff --git a/repos/effect/README.md b/repos/effect/README.md index 4ca8143714..efdb0b12f6 100644 --- a/repos/effect/README.md +++ b/repos/effect/README.md @@ -1,3 +1,36 @@ - + -[![pkg.pr.new](https://img.shields.io/badge/pkg.pr.new-Effect--TS%2Feffect--smol-black)](https://pkg.pr.new/~/Effect-TS/effect-smol) +[![pkg.pr.new](https://img.shields.io/badge/pkg.pr.new-Effect--TS%2Feffect-black)](https://pkg.pr.new/~/Effect-TS/effect) + +# Effect + +Effect is a library for building robust, maintainable, type-safe, and production grade applications in TypeScript. + +> **Effect V4 is currently in beta.** The `main` branch contains v4 development. + +## Install V4 Beta + +```sh +npm install effect@beta +``` + +## Effect v3 + +The Effect v3 source code is available on the [`v3`](https://github.com/Effect-TS/effect/tree/v3) branch. + +```sh +npm install effect@latest +``` + +Issues and pull requests meant for Effect v3 should target the [`v3`](https://github.com/Effect-TS/effect/tree/v3) branch. + +## 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/TODOS.md b/repos/effect/TODOS.md deleted file mode 100644 index 4773f75396..0000000000 --- a/repos/effect/TODOS.md +++ /dev/null @@ -1,28 +0,0 @@ -## Beta (Current) - -Pre-releases to npm from smol repo - -- [x] Add AI Embeddings module -- [ ] Comprehensive JSDoc on every exported function -- [ ] Codemod CLI for v3 migration - -## RC's - -Pre-releases to npm from smol repo - -## Release - -- [ ] Copy code over to main repo - -# Module Audit - -The exports under each section are organized as they are in Effect 3.0. The categorization of these modules may not be correct, and should be fixed for 4.0. - -### Legend - -| Status | Description | -| :----: | :----------------------------------------------- | -| - | Not done (default) | -| Done | Done - successfully ported to Effect 4 | -| X | Won't do - not being ported to Effect 4 | -| ? | Question - method has questions or uncertainties | diff --git a/repos/effect/ai-docs/package.json b/repos/effect/ai-docs/package.json index 0a0a320de7..10fef9d9fd 100644 --- a/repos/effect/ai-docs/package.json +++ b/repos/effect/ai-docs/package.json @@ -27,10 +27,10 @@ "@effect/sql-sqlite-wasm": "workspace:*", "@effect/vitest": "workspace:*", "effect": "workspace:*", - "hono": "^4.12.18", - "nodemailer": "^8.0.7" + "hono": "^4.12.28", + "nodemailer": "^9.0.3" }, "devDependencies": { - "@types/nodemailer": "^8.0.0" + "@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 index 4e45a89352..d822889530 100644 --- 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 @@ -24,7 +24,7 @@ Effect.gen(function*() { }) ) -// Use Schema.TaggedErrorClass to define a custom error -export class FileProcessingError extends Schema.TaggedErrorClass()("FileProcessingError", { +// Use Schema.TaggedError to define a custom error +export class FileProcessingError extends Schema.TaggedError()("FileProcessingError", { message: Schema.String }) {} diff --git a/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 index dcbfe6d624..1d41aaa154 100644 --- 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 @@ -33,7 +33,7 @@ export const effectFunction = Effect.fn("effectFunction")( }) ) -// Use Schema.TaggedErrorClass to define a custom error -export class SomeError extends Schema.TaggedErrorClass()("SomeError", { +// 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 index 86082db0f7..7f331047f8 100644 --- 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 @@ -6,17 +6,17 @@ */ import { Effect, Schema } from "effect" -class InvalidPayload extends Schema.TaggedErrorClass()("InvalidPayload", { +class InvalidPayload extends Schema.TaggedError()("InvalidPayload", { input: Schema.String, cause: Schema.Defect() }) {} -class UserLookupError extends Schema.TaggedErrorClass()("UserLookupError", { - userId: Schema.Number, +class UserLookupError extends Schema.TaggedError()("UserLookupError", { + userId: Schema.Int, cause: Schema.Defect() }) {} -class MissingWorkspaceId extends Schema.TaggedErrorClass()("MissingWorkspaceId", {}) {} +class MissingWorkspaceId extends Schema.TaggedError()("MissingWorkspaceId", {}) {} // Some request fields are optional and may be absent. const requestHeaders = new Map([ diff --git a/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 0000000000..99155a590c --- /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 0000000000..2e93dea9a0 --- /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_errors/10_catch-tags.ts b/repos/effect/ai-docs/src/01_effect/03_errors/10_catch-tags.ts deleted file mode 100644 index 020473dd42..0000000000 --- a/repos/effect/ai-docs/src/01_effect/03_errors/10_catch-tags.ts +++ /dev/null @@ -1,24 +0,0 @@ -/** - * @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.TaggedErrorClass()("ValidationError", { - message: Schema.String -}) {} - -export class NetworkError extends Schema.TaggedErrorClass()("NetworkError", { - statusCode: Schema.Number -}) {} - -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/02_services/01_service.ts b/repos/effect/ai-docs/src/01_effect/03_services/01_service.ts similarity index 93% rename from repos/effect/ai-docs/src/01_effect/02_services/01_service.ts rename to repos/effect/ai-docs/src/01_effect/03_services/01_service.ts index 24db9b0b4f..14478cf6d1 100644 --- a/repos/effect/ai-docs/src/01_effect/02_services/01_service.ts +++ b/repos/effect/ai-docs/src/01_effect/03_services/01_service.ts @@ -37,7 +37,7 @@ export class Database extends Context.Service()("DatabaseError", { +export class DatabaseError extends Schema.TaggedError()("DatabaseError", { cause: Schema.Defect() }) {} diff --git a/repos/effect/ai-docs/src/01_effect/02_services/10_reference.ts b/repos/effect/ai-docs/src/01_effect/03_services/10_reference.ts similarity index 100% rename from repos/effect/ai-docs/src/01_effect/02_services/10_reference.ts rename to repos/effect/ai-docs/src/01_effect/03_services/10_reference.ts diff --git a/repos/effect/ai-docs/src/01_effect/02_services/20_layer-composition.ts b/repos/effect/ai-docs/src/01_effect/03_services/20_layer-composition.ts similarity index 95% rename from repos/effect/ai-docs/src/01_effect/02_services/20_layer-composition.ts rename to repos/effect/ai-docs/src/01_effect/03_services/20_layer-composition.ts index 58ff1273c2..99bff184f9 100644 --- a/repos/effect/ai-docs/src/01_effect/02_services/20_layer-composition.ts +++ b/repos/effect/ai-docs/src/01_effect/03_services/20_layer-composition.ts @@ -17,7 +17,7 @@ export const SqlClientLayer: Layer.Layer< url: Config.redacted("DATABASE_URL") }) -export class UserRespositoryError extends Schema.TaggedErrorClass()("UserRespositoryError", { +export class UserRespositoryError extends Schema.TaggedError()("UserRespositoryError", { reason: SqlError.SqlError }) {} diff --git a/repos/effect/ai-docs/src/01_effect/02_services/20_layer-unwrap.ts b/repos/effect/ai-docs/src/01_effect/03_services/20_layer-unwrap.ts similarity index 94% rename from repos/effect/ai-docs/src/01_effect/02_services/20_layer-unwrap.ts rename to repos/effect/ai-docs/src/01_effect/03_services/20_layer-unwrap.ts index 01324ac0df..75a8488eca 100644 --- a/repos/effect/ai-docs/src/01_effect/02_services/20_layer-unwrap.ts +++ b/repos/effect/ai-docs/src/01_effect/03_services/20_layer-unwrap.ts @@ -5,7 +5,7 @@ */ import { Config, Context, Effect, Layer, Schema } from "effect" -export class MessageStoreError extends Schema.TaggedErrorClass()("MessageStoreError", { +export class MessageStoreError extends Schema.TaggedError()("MessageStoreError", { cause: Schema.Defect() }) {} diff --git a/repos/effect/ai-docs/src/01_effect/02_services/index.md b/repos/effect/ai-docs/src/01_effect/03_services/index.md similarity index 100% rename from repos/effect/ai-docs/src/01_effect/02_services/index.md rename to repos/effect/ai-docs/src/01_effect/03_services/index.md diff --git a/repos/effect/ai-docs/src/01_effect/03_errors/01_error-handling.ts b/repos/effect/ai-docs/src/01_effect/04_errors/01_error-handling.ts similarity index 75% rename from repos/effect/ai-docs/src/01_effect/03_errors/01_error-handling.ts rename to repos/effect/ai-docs/src/01_effect/04_errors/01_error-handling.ts index c507118879..b4a7bfb9e3 100644 --- a/repos/effect/ai-docs/src/01_effect/03_errors/01_error-handling.ts +++ b/repos/effect/ai-docs/src/01_effect/04_errors/01_error-handling.ts @@ -5,14 +5,14 @@ */ import { Effect, Schema } from "effect" -// Define custom errors using Schema.TaggedErrorClass -export class ParseError extends Schema.TaggedErrorClass()("ParseError", { +// Define custom errors using Schema.TaggedError +export class ParseError extends Schema.TaggedError()("ParseError", { input: Schema.String, message: Schema.String }) {} -export class ReservedPortError extends Schema.TaggedErrorClass()("ReservedPortError", { - port: Schema.Number +export class ReservedPortError extends Schema.TaggedError()("ReservedPortError", { + port: Schema.Int }) {} declare const loadPort: (input: string) => Effect.Effect 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 0000000000..88d14d47a7 --- /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/03_errors/20_reason-errors.ts b/repos/effect/ai-docs/src/01_effect/04_errors/20_reason-errors.ts similarity index 82% rename from repos/effect/ai-docs/src/01_effect/03_errors/20_reason-errors.ts rename to repos/effect/ai-docs/src/01_effect/04_errors/20_reason-errors.ts index bced427c13..504dbdd9fe 100644 --- a/repos/effect/ai-docs/src/01_effect/03_errors/20_reason-errors.ts +++ b/repos/effect/ai-docs/src/01_effect/04_errors/20_reason-errors.ts @@ -8,19 +8,19 @@ import { Effect, Schema } from "effect" -export class RateLimitError extends Schema.TaggedErrorClass()("RateLimitError", { - retryAfter: Schema.Number +export class RateLimitError extends Schema.TaggedError()("RateLimitError", { + retryAfter: Schema.Finite }) {} -export class QuotaExceededError extends Schema.TaggedErrorClass()("QuotaExceededError", { - limit: Schema.Number +export class QuotaExceededError extends Schema.TaggedError()("QuotaExceededError", { + limit: Schema.Int }) {} -export class SafetyBlockedError extends Schema.TaggedErrorClass()("SafetyBlockedError", { +export class SafetyBlockedError extends Schema.TaggedError()("SafetyBlockedError", { category: Schema.String }) {} -export class AiError extends Schema.TaggedErrorClass()("AiError", { +export class AiError extends Schema.TaggedError()("AiError", { reason: Schema.Union([RateLimitError, QuotaExceededError, SafetyBlockedError]) }) {} diff --git a/repos/effect/ai-docs/src/01_effect/03_errors/index.md b/repos/effect/ai-docs/src/01_effect/04_errors/index.md similarity index 100% rename from repos/effect/ai-docs/src/01_effect/03_errors/index.md rename to repos/effect/ai-docs/src/01_effect/04_errors/index.md diff --git a/repos/effect/ai-docs/src/01_effect/04_resources/10_acquire-release.ts b/repos/effect/ai-docs/src/01_effect/05_resources/10_acquire-release.ts similarity index 94% rename from repos/effect/ai-docs/src/01_effect/04_resources/10_acquire-release.ts rename to repos/effect/ai-docs/src/01_effect/05_resources/10_acquire-release.ts index afff2b1cb7..4adb090c76 100644 --- a/repos/effect/ai-docs/src/01_effect/04_resources/10_acquire-release.ts +++ b/repos/effect/ai-docs/src/01_effect/05_resources/10_acquire-release.ts @@ -8,7 +8,7 @@ import { Config, Context, Effect, Layer, Redacted, Schema } from "effect" import * as NodeMailer from "nodemailer" -export class SmtpError extends Schema.ErrorClass("SmtpError")({ +export class SmtpError extends Schema.Error("SmtpError")({ cause: Schema.Defect() }) {} @@ -70,7 +70,7 @@ export class Smtp extends Context.Service()("MailerError", { +export class MailerError extends Schema.TaggedError()("MailerError", { reason: SmtpError }) {} diff --git a/repos/effect/ai-docs/src/01_effect/04_resources/20_layer-side-effects.ts b/repos/effect/ai-docs/src/01_effect/05_resources/20_layer-side-effects.ts similarity index 100% rename from repos/effect/ai-docs/src/01_effect/04_resources/20_layer-side-effects.ts rename to repos/effect/ai-docs/src/01_effect/05_resources/20_layer-side-effects.ts diff --git a/repos/effect/ai-docs/src/01_effect/04_resources/30_layer-map.ts b/repos/effect/ai-docs/src/01_effect/05_resources/30_layer-map.ts similarity index 96% rename from repos/effect/ai-docs/src/01_effect/04_resources/30_layer-map.ts rename to repos/effect/ai-docs/src/01_effect/05_resources/30_layer-map.ts index 4dbecb3816..5ba15c5fb7 100644 --- a/repos/effect/ai-docs/src/01_effect/04_resources/30_layer-map.ts +++ b/repos/effect/ai-docs/src/01_effect/05_resources/30_layer-map.ts @@ -6,7 +6,7 @@ */ import { Context, Effect, Layer, LayerMap, Schema } from "effect" -class DatabaseQueryError extends Schema.TaggedErrorClass()("DatabaseQueryError", { +class DatabaseQueryError extends Schema.TaggedError()("DatabaseQueryError", { tenantId: Schema.String, cause: Schema.Defect() }) {} diff --git a/repos/effect/ai-docs/src/01_effect/04_resources/index.md b/repos/effect/ai-docs/src/01_effect/05_resources/index.md similarity index 100% rename from repos/effect/ai-docs/src/01_effect/04_resources/index.md rename to repos/effect/ai-docs/src/01_effect/05_resources/index.md diff --git a/repos/effect/ai-docs/src/01_effect/05_running/10_run-main.ts b/repos/effect/ai-docs/src/01_effect/06_running/10_run-main.ts similarity index 100% rename from repos/effect/ai-docs/src/01_effect/05_running/10_run-main.ts rename to repos/effect/ai-docs/src/01_effect/06_running/10_run-main.ts diff --git a/repos/effect/ai-docs/src/01_effect/05_running/20_layer-launch.ts b/repos/effect/ai-docs/src/01_effect/06_running/20_layer-launch.ts similarity index 100% rename from repos/effect/ai-docs/src/01_effect/05_running/20_layer-launch.ts rename to repos/effect/ai-docs/src/01_effect/06_running/20_layer-launch.ts diff --git a/repos/effect/ai-docs/src/01_effect/05_running/index.md b/repos/effect/ai-docs/src/01_effect/06_running/index.md similarity index 100% rename from repos/effect/ai-docs/src/01_effect/05_running/index.md rename to repos/effect/ai-docs/src/01_effect/06_running/index.md diff --git a/repos/effect/ai-docs/src/01_effect/06_pubsub/10_pubsub.ts b/repos/effect/ai-docs/src/01_effect/07_pubsub/10_pubsub.ts similarity index 100% rename from repos/effect/ai-docs/src/01_effect/06_pubsub/10_pubsub.ts rename to repos/effect/ai-docs/src/01_effect/07_pubsub/10_pubsub.ts diff --git a/repos/effect/ai-docs/src/01_effect/06_pubsub/index.md b/repos/effect/ai-docs/src/01_effect/07_pubsub/index.md similarity index 100% rename from repos/effect/ai-docs/src/01_effect/06_pubsub/index.md rename to repos/effect/ai-docs/src/01_effect/07_pubsub/index.md diff --git a/repos/effect/ai-docs/src/02_stream/10_creating-streams.ts b/repos/effect/ai-docs/src/03_stream/10_creating-streams.ts similarity index 94% rename from repos/effect/ai-docs/src/02_stream/10_creating-streams.ts rename to repos/effect/ai-docs/src/03_stream/10_creating-streams.ts index 272b338a8d..262a1f1969 100644 --- a/repos/effect/ai-docs/src/02_stream/10_creating-streams.ts +++ b/repos/effect/ai-docs/src/03_stream/10_creating-streams.ts @@ -49,7 +49,7 @@ export const fetchJobsPage = Stream.paginate( }) ) -class LetterError extends Schema.TaggedErrorClass()("LetterError", { +class LetterError extends Schema.TaggedError()("LetterError", { cause: Schema.Defect() }) {} @@ -88,7 +88,7 @@ export const callbackStream = Stream.callback(Effect.fn(function*( ) })) -export class NodeStreamError extends Schema.TaggedErrorClass()("NodeStreamError", { +export class NodeStreamError extends Schema.TaggedError()("NodeStreamError", { cause: Schema.Defect() }) {} diff --git a/repos/effect/ai-docs/src/02_stream/20_consuming-streams.ts b/repos/effect/ai-docs/src/03_stream/20_consuming-streams.ts similarity index 100% rename from repos/effect/ai-docs/src/02_stream/20_consuming-streams.ts rename to repos/effect/ai-docs/src/03_stream/20_consuming-streams.ts diff --git a/repos/effect/ai-docs/src/02_stream/30_encoding.ts b/repos/effect/ai-docs/src/03_stream/30_encoding.ts similarity index 99% rename from repos/effect/ai-docs/src/02_stream/30_encoding.ts rename to repos/effect/ai-docs/src/03_stream/30_encoding.ts index 16a6b6aa7a..c0577c8517 100644 --- a/repos/effect/ai-docs/src/02_stream/30_encoding.ts +++ b/repos/effect/ai-docs/src/03_stream/30_encoding.ts @@ -11,7 +11,7 @@ import { Msgpack, Ndjson } from "effect/unstable/encoding" // with `Msgpack` and using the appropriate channels (`Msgpack.decode()`, // `Msgpack.encode()`, etc.). export const msgpackDecoder = Msgpack.decodeSchema(Schema.Struct({ - id: Schema.Number, + id: Schema.Int, name: Schema.String })) diff --git a/repos/effect/ai-docs/src/02_stream/index.md b/repos/effect/ai-docs/src/03_stream/index.md similarity index 100% rename from repos/effect/ai-docs/src/02_stream/index.md rename to repos/effect/ai-docs/src/03_stream/index.md diff --git a/repos/effect/ai-docs/src/03_integration/10_managed-runtime.ts b/repos/effect/ai-docs/src/04_integration/10_managed-runtime.ts similarity index 96% rename from repos/effect/ai-docs/src/03_integration/10_managed-runtime.ts rename to repos/effect/ai-docs/src/04_integration/10_managed-runtime.ts index ed29f83b09..b7393a9f48 100644 --- a/repos/effect/ai-docs/src/03_integration/10_managed-runtime.ts +++ b/repos/effect/ai-docs/src/04_integration/10_managed-runtime.ts @@ -7,7 +7,7 @@ import { Context, Effect, Layer, ManagedRuntime, Ref, Schema } from "effect" import { Hono } from "hono" class Todo extends Schema.Class("Todo")({ - id: Schema.Number, + id: Schema.Int, title: Schema.String, completed: Schema.Boolean }) {} @@ -16,8 +16,8 @@ class CreateTodoPayload extends Schema.Class("CreateTodoPaylo title: Schema.String }) {} -class TodoNotFound extends Schema.TaggedErrorClass()("TodoNotFound", { - id: Schema.Number +class TodoNotFound extends Schema.TaggedError()("TodoNotFound", { + id: Schema.Int }) {} export class TodoRepo extends Context.Service("User")({ - id: Schema.Number, + id: Schema.Int, name: Schema.String, email: Schema.String }) {} -export class UserNotFound extends Schema.TaggedErrorClass()("UserNotFound", { - id: Schema.Number +export class UserNotFound extends Schema.TaggedError()("UserNotFound", { + id: Schema.Int }) {} export class Users extends Context.Service()("HttpError", { - message: Schema.String, - status: Schema.Number, - retryable: Schema.Boolean -}) {} - -// Start with a few schedule constructors. -export const maxRetries = Schedule.recurs(5) -export const spacedPolling = Schedule.spaced("30 seconds") -export const exponentialBackoff = Schedule.exponential("200 millis") - -// `Schedule.both` continues only while both schedules continue. -// It is useful for combining a delay pattern with a hard attempt cap. -export const retryBackoffWithLimit = Schedule.both( - Schedule.exponential("250 millis"), - Schedule.recurs(6) -) - -// `Schedule.either` continues while either schedule continues. -// It is useful for fallback behavior (e.g. stop only when both are exhausted). -export const keepTryingUntilBothStop = Schedule.either( - Schedule.spaced("2 seconds"), - Schedule.recurs(3) -) - -// Use `Schedule.while` to continue only for retryable failures. -// This lets non-retryable errors fail fast, even if attempts remain. -export const retryableOnly = Schedule.exponential("200 millis").pipe( - // You can use `setInputType` to specify the type of input the schedule will - // receive. - Schedule.setInputType(), - Schedule.while(({ input }) => input.retryable) -) - -// `tapInput` and `tapOutput` are useful for performing side effects like -// logging or metrics. -export const instrumentedRetrySchedule = retryableOnly.pipe( - Schedule.setInputType(), - Schedule.tapInput((error) => Effect.logDebug(`Retrying after ${error.status}: ${error.message}`)), - Schedule.tapOutput((delay) => Effect.logDebug(`Next retry in ${Duration.toMillis(delay)}ms`)) -) - // Production pattern: capped exponential backoff with jitter and max attempts. // Delays start at 250ms, grow exponentially with jitter, and are capped at 10s. -export const productionRetrySchedule = Schedule.exponential("250 millis").pipe( +export const productionRetrySchedule = Schedule.min([ + Schedule.exponential("250 millis"), // Cap the delay at 10 seconds to avoid excessively long waits. - Schedule.either(Schedule.spaced("10 seconds")), + Schedule.spaced("10 seconds") +]).pipe( Schedule.jittered, Schedule.setInputType(), Schedule.while(({ input }) => input.retryable) @@ -100,3 +60,51 @@ export const loadUserWithInferredInput = fetchUserProfile("user-123").pipe( ), Effect.orDie ) + +export class HttpError extends Schema.TaggedError()("HttpError", { + message: Schema.String, + status: Schema.Int, + retryable: Schema.Boolean +}) {} + +// Start with a few schedule constructors. +export const maxRetries = Schedule.recurs(5) +export const spacedPolling = Schedule.spaced("30 seconds") +export const exponentialBackoff = Schedule.exponential("200 millis") + +// `Schedule.max` continues only while all schedules continue and outputs +// the slowest delay. It is useful for combining a delay pattern with a hard +// attempt cap. +export const retryBackoffWithLimit = Schedule.max([ + Schedule.exponential("250 millis"), + Schedule.recurs(6) +]) + +// `Schedule.min` continues while any schedule continues and outputs +// the fastest delay. It is useful for fallback behavior. +// +// This schedule will start at 500ms, exponentially increase, and will be capped +// at 2 seconds. +export const keepTryingUpToTwoSeconds = Schedule.min([ + Schedule.exponential("500 millis"), + Schedule.spaced("2 seconds") +]) + +// Use `Schedule.while` to continue only for retryable failures. +// This lets non-retryable errors fail fast, even if attempts remain. +export const retryableOnly = Schedule.exponential("200 millis").pipe( + // You can use `setInputType` to specify the type of input the schedule will + // receive. + Schedule.setInputType(), + Schedule.while(({ input }) => input.retryable) +) + +// `tap` is useful for performing side effects like logging or metrics. +export const instrumentedRetrySchedule = retryableOnly.pipe( + Schedule.setInputType(), + Schedule.tap((meta) => + Effect.logDebug( + `Retrying after ${meta.input.status}: ${meta.input.message}. Next retry in ${Duration.toMillis(meta.duration)}ms` + ) + ) +) diff --git a/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 0000000000..8ff78326ce --- /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 0000000000..5149c3221e --- /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/50_http-client/10_basics.ts b/repos/effect/ai-docs/src/50_http-client/10_basics.ts index 747e886e10..195d5fe555 100644 --- a/repos/effect/ai-docs/src/50_http-client/10_basics.ts +++ b/repos/effect/ai-docs/src/50_http-client/10_basics.ts @@ -7,8 +7,8 @@ import { Context, Effect, flow, Layer, Schedule, Schema } from "effect" import { FetchHttpClient, HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http" class Todo extends Schema.Class("Todo")({ - userId: Schema.Number, - id: Schema.Number, + userId: Schema.Int, + id: Schema.Int, title: Schema.String, completed: Schema.Boolean }) {} @@ -97,6 +97,6 @@ export class JsonPlaceholder extends Context.Service()("JsonPlaceholderError", { +export class JsonPlaceholderError extends Schema.TaggedError()("JsonPlaceholderError", { cause: Schema.Defect() }) {} diff --git a/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 index 8b25c04290..30898c729a 100644 --- 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 @@ -4,7 +4,7 @@ import type { User } from "../domain/User.ts" export class CurrentUser extends Context.Service()("acme/HttpApi/Authorization/CurrentUser") {} -export class Unauthorized extends Schema.TaggedErrorClass()( +export class Unauthorized extends Schema.TaggedError()( "Unauthorized", { message: Schema.String diff --git a/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 index fa2a757b2b..fde17e7fc7 100644 --- 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 @@ -1,6 +1,6 @@ import { Schema } from "effect" -export class UserNotFound extends Schema.TaggedErrorClass()( +export class UserNotFound extends Schema.TaggedError()( "UserNotFound", {}, // You can specify the status code for this error inline @@ -8,7 +8,7 @@ export class UserNotFound extends Schema.TaggedErrorClass()( ) {} export class SearchQueryTooShort - extends Schema.TaggedErrorClass()("SearchQueryTooShort", {}, { httpApiStatus: 422 }) + extends Schema.TaggedError()("SearchQueryTooShort", {}, { httpApiStatus: 422 }) { static readonly minimumLength = 2 } @@ -17,6 +17,6 @@ export class SearchQueryTooShort // // This prevents adding too many error types to services / endpoint definitions. // -export class UsersError extends Schema.TaggedErrorClass()("UsersError", { +export class UsersError extends Schema.TaggedError()("UsersError", { reason: Schema.Union([UserNotFound, SearchQueryTooShort]) }) {} diff --git a/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 index a1b7b3eacb..5d778f69a0 100644 --- 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 @@ -23,7 +23,7 @@ export const UsersApiHandlers = HttpApiBuilder.group( Effect.fn(function*({ payload }) { if (payload.search === "bad-request") { // You can use the built in error types like any other - // Schema.TaggedErrorClass + // Schema.TaggedError return yield* new HttpApiError.RequestTimeout() } return yield* users.list(payload.search).pipe( 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 index 3d2866af97..c7b1bbbcdf 100644 --- 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 @@ -7,7 +7,7 @@ import { NodeServices } from "@effect/platform-node" import { Console, Context, Effect, Layer, Schema, Stream, String } from "effect" import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process" -export class DevToolsError extends Schema.TaggedErrorClass()("DevToolsError", { +export class DevToolsError extends Schema.TaggedError()("DevToolsError", { cause: Schema.Defect() }) {} diff --git a/repos/effect/ai-docs/src/60_child-process/index.md b/repos/effect/ai-docs/src/60_child-process/index.md index 8aafdd603f..ae0016bc3b 100644 --- a/repos/effect/ai-docs/src/60_child-process/index.md +++ b/repos/effect/ai-docs/src/60_child-process/index.md @@ -1,3 +1,3 @@ ## Working with child processes -Use the `effect/unstable/process` modules to define child processes and run them with `ChildProcessSpawner. +Use the `effect/unstable/process` modules to define child processes and run them with `ChildProcessSpawner`. diff --git a/repos/effect/ai-docs/src/71_ai/10_language-model.ts b/repos/effect/ai-docs/src/71_ai/10_language-model.ts index 2f89956bb7..68e959426d 100644 --- a/repos/effect/ai-docs/src/71_ai/10_language-model.ts +++ b/repos/effect/ai-docs/src/71_ai/10_language-model.ts @@ -26,7 +26,7 @@ const OpenAiClientLayer = OpenAiClient.layerConfig({ Layer.provide(FetchHttpClient.layer) ) -export class AiWriterError extends Schema.TaggedErrorClass()("AiWriterError", { +export class AiWriterError extends Schema.TaggedError()("AiWriterError", { // AiErrorReason is a Schema, so we can include it directly in our custom // error schema. reason: AiError.AiErrorReason diff --git a/repos/effect/ai-docs/src/71_ai/20_tools.ts b/repos/effect/ai-docs/src/71_ai/20_tools.ts index 0acd8e97c0..5991783688 100644 --- a/repos/effect/ai-docs/src/71_ai/20_tools.ts +++ b/repos/effect/ai-docs/src/71_ai/20_tools.ts @@ -20,7 +20,7 @@ const ProductId = Schema.String.pipe(Schema.brand("ProductId")).annotate({ class Product extends Schema.Class("acme/domain/Product")({ id: ProductId, name: Schema.String, - price: Schema.Number + price: Schema.Finite }) {} // Each tool has a name, an optional description, a parameters schema that the @@ -34,7 +34,7 @@ const SearchProducts = Tool.make("SearchProducts", { // guidance. description: "The search query, e.g. 'wireless headphones'" }), - maxResults: Schema.Number.pipe(Schema.withDecodingDefault(Effect.succeed(10))).annotate({ + maxResults: Schema.Natural.pipe(Schema.withDecodingDefault(Effect.succeed(10))).annotate({ description: "The maximum number of results to return" }) }), @@ -57,7 +57,7 @@ const GetInventory = Tool.make("GetInventory", { }), success: Schema.Struct({ productId: ProductId, - available: Schema.Number + available: Schema.Natural }) }) @@ -104,7 +104,7 @@ const OpenAiClientLayer = OpenAiClient.layerConfig({ apiKey: Config.redacted("OPENAI_API_KEY") }).pipe(Layer.provide(FetchHttpClient.layer)) -export class ProductAssistantError extends Schema.TaggedErrorClass()( +export class ProductAssistantError extends Schema.TaggedError()( "ProductAssistantError", { reason: AiError.AiErrorReason } ) {} diff --git a/repos/effect/ai-docs/src/71_ai/30_chat.ts b/repos/effect/ai-docs/src/71_ai/30_chat.ts index 600017024b..f90ea1af65 100644 --- a/repos/effect/ai-docs/src/71_ai/30_chat.ts +++ b/repos/effect/ai-docs/src/71_ai/30_chat.ts @@ -43,7 +43,7 @@ const ToolsLayer = Tools.toLayer(Effect.gen(function*() { // Service that wraps Chat for a domain use-case // --------------------------------------------------------------------------- -export class AiAssistantError extends Schema.TaggedErrorClass()("AiAssistantError", { +export class AiAssistantError extends Schema.TaggedError()("AiAssistantError", { reason: AiError.AiErrorReason }) { static fromAiError(error: AiError.AiError) { diff --git a/repos/effect/ai-docs/src/80_cluster/10_entities.ts b/repos/effect/ai-docs/src/80_cluster/10_entities.ts index a1be374a5e..466197f9bd 100644 --- a/repos/effect/ai-docs/src/80_cluster/10_entities.ts +++ b/repos/effect/ai-docs/src/80_cluster/10_entities.ts @@ -10,12 +10,12 @@ import { Rpc } from "effect/unstable/rpc" import type { SqlClient } from "effect/unstable/sql" export const Increment = Rpc.make("Increment", { - payload: { amount: Schema.Number }, - success: Schema.Number + payload: { amount: Schema.Int }, + success: Schema.Int }) export const GetCount = Rpc.make("GetCount", { - success: Schema.Number + success: Schema.Int }) // If you want GetCount messages to be persisted, you can annotate the RPC // schema with `ClusterSchema.Persisted`. diff --git a/repos/effect/ai-docs/src/index.md b/repos/effect/ai-docs/src/index.md index 092209d757..0a0213bf72 100644 --- a/repos/effect/ai-docs/src/index.md +++ b/repos/effect/ai-docs/src/index.md @@ -1,12 +1,10 @@ # Effect library documentation -This documentation resides in the Effect monorepo, which contains the source -code for the Effect library and its related packages. +This documentation covers the Effect library and its related packages. -When you need to find any information about the Effect library, only use this -documentation and the source code found in `./packages`. Do not use -`node_modules` or any other external documentation, as it may be outdated or -incorrect. +When you need to find information about Effect, use this documentation and the +Effect source code available in your environment. Avoid unrelated copies of +Effect or external documentation, as they may be outdated or incorrect. **Note**: The examples in this documentation contain comments for illustration purposes. In practice, you would not include these comments in your code. diff --git a/repos/effect/ai-docs/tsconfig.json b/repos/effect/ai-docs/tsconfig.json index 94d60356d8..e5be14e9be 100644 --- a/repos/effect/ai-docs/tsconfig.json +++ b/repos/effect/ai-docs/tsconfig.json @@ -1,5 +1,5 @@ { - "$schema": "http://json.schemastore.org/tsconfig", + "$schema": "https://json.schemastore.org/tsconfig", "extends": "../tsconfig.base.json", "include": ["src/**/*.ts", "src/**/*.tsx"], "compilerOptions": { diff --git a/repos/effect/cookbooks/schedule.md b/repos/effect/cookbooks/schedule.md index 084f286d42..2ff56da206 100644 --- a/repos/effect/cookbooks/schedule.md +++ b/repos/effect/cookbooks/schedule.md @@ -14,13 +14,12 @@ This cookbook intentionally defines schedules only. It does not apply them with aligned cadence; `Schedule.windowed` recurs on window boundaries. - `Schedule.duration` performs exactly one recurrence after the duration. - `Schedule.during` is an elapsed-time budget, not a delay by itself. -- Schedule output is policy output. Use `Schedule.passthrough`, - `Schedule.delays`, `Schedule.map`, or `Schedule.reduce` when the output shape - matters. -- `Schedule.both` continues only while both schedules continue. -- `Schedule.either` continues while either schedule can continue. +- Schedule 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 output. +- `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. @@ -30,17 +29,14 @@ This cookbook intentionally defines schedules only. It does not apply them with | ----------------------------------------- | ----------------------------------------------------------------------------------------------------------------- | | Bounded retry | `Schedule.exponential` or `Schedule.fibonacci`, then `Schedule.recurs` and optionally `Schedule.during` | | Poll latest status | `Schedule.spaced` or `Schedule.fixed`, then `Schedule.setInputType`, `Schedule.passthrough`, and `Schedule.while` | -| Inspect selected delays | `Schedule.delays`, then `Schedule.map` | -| Adapt delay from output | `Schedule.addDelay` | +| Adapt delay from metadata | `Schedule.addDelay` | | Replace or cap selected delay | `Schedule.modifyDelay` | -| Run phases in sequence | `Schedule.andThen` | -| Preserve phase in output | `Schedule.andThenResult` | -| Continue while both policies continue | `Schedule.both` | -| Continue while either policy continues | `Schedule.either` | -| Keep input or output history | `Schedule.collectInputs`, `Schedule.collectOutputs`, or `Schedule.collectWhile` | -| Maintain a running aggregate | `Schedule.reduce` | -| Observe decisions without changing output | `Schedule.tap`, `Schedule.tapInput`, or `Schedule.tapOutput` | -| Build a local state machine | `Schedule.unfold` | +| 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 @@ -48,11 +44,9 @@ This cookbook intentionally defines schedules only. It does not apply them with 2. [Shape Schedule Outputs](#shape-schedule-outputs) 3. [Combine Policies](#combine-policies) 4. [Work With Inputs](#work-with-inputs) -5. [Accumulate State](#accumulate-state) -6. [Adapt Delays](#adapt-delays) -7. [Observe Schedule Decisions](#observe-schedule-decisions) -8. [Build Local State Machines](#build-local-state-machines) -9. [Realistic Policies](#realistic-policies) +5. [Adapt Delays](#adapt-delays) +6. [Observe Schedule Decisions](#observe-schedule-decisions) +7. [Realistic Policies](#realistic-policies) ## Single-Policy Schedules @@ -131,7 +125,7 @@ backoff and taking the first 4 outputs. import { Schedule } from "effect" const searchReplicaWarmup = Schedule.fibonacci("100 millis").pipe( - Schedule.take(4) + Schedule.upTo({ times: 4 }) ) ``` @@ -178,70 +172,40 @@ import { Schedule } from "effect" type FeatureFlagSnapshot = { readonly enabled: boolean } const featureFlagSamples = Schedule.identity().pipe( - Schedule.take(3) + Schedule.upTo({ times: 3 }) ) ``` ### Label Retry Attempts -Goal: Create a retry-attempt schedule that turns recurrence counts into labels -such as `attempt-1`, `attempt-2`, and `attempt-3`. +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((count) => `attempt-${count + 1}`) + Schedule.map(({ output: count }) => `attempt-${count + 1}`) ) ``` -### Report Selected Poll Delays - -Goal: Create a telemetry schedule that runs on an aligned 2-second cadence, -outputs selected delays, maps each delay to milliseconds, and takes 3 outputs. - -```ts -import { Duration, Schedule } from "effect" - -const pollDelayReport = Schedule.fixed("2 seconds").pipe( - Schedule.delays, - Schedule.map((delay) => ({ millis: Duration.toMillis(delay) })), - Schedule.take(3) -) -``` - -Explanation: `Schedule.delays` changes the schedule output to the selected -delay. Use it when observability or downstream policy needs the actual delay -rather than the cadence counter. - -### Report Elapsed Runtime - -Goal: Create a runtime sampler that outputs elapsed time in milliseconds and -takes 4 samples. - -```ts -import { Duration, Schedule } from "effect" - -const elapsedRuntimeReport = Schedule.elapsed.pipe( - Schedule.map((elapsed) => ({ millis: Duration.toMillis(elapsed) })), - Schedule.take(4) -) -``` +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, outputs selected delays, and takes 3 outputs. +jitter, and takes 3 outputs. ```ts import { Schedule } from "effect" const jitteredWebhookBackoff = Schedule.exponential("200 millis").pipe( Schedule.jittered, - Schedule.delays, - Schedule.take(3) + Schedule.upTo({ times: 3 }) ) ``` @@ -253,32 +217,34 @@ backoff, allows at most 5 recurrences, and also stops after about 20 seconds. ```ts import { Schedule } from "effect" -const deploymentHookRetryBudget = Schedule.exponential("200 millis").pipe( - Schedule.jittered, - Schedule.both(Schedule.recurs(5)), - Schedule.both(Schedule.during("20 seconds")) -) +const deploymentHookRetryBudget = Schedule.max([ + Schedule.exponential("200 millis").pipe(Schedule.jittered), + Schedule.recurs(5), + Schedule.during("20 seconds") +]) ``` -Explanation: the resulting output is nested because `Schedule.both` preserves -both schedule outputs. Use `Schedule.map` when a smaller output is needed. +Explanation: `Schedule.max` stops when any schedule stops and outputs the +slowest selected delay for each recurrence. -### Continue While Either Probe Is Active +### 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. +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.recurs(2).pipe( - Schedule.either(Schedule.spaced("500 millis").pipe(Schedule.take(5))) -) +const readinessWarmupOrSlowProbe = Schedule.min([ + Schedule.recurs(2), + Schedule.spaced("500 millis").pipe(Schedule.upTo({ times: 5 })) +]) ``` -Explanation: `Schedule.either` keeps recurring while at least one side can -continue. Its output preserves both sides, so map the result if callers should -see a smaller shape. +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 @@ -289,8 +255,8 @@ milliseconds apart, then 3 slower recurrences 30 seconds apart, then stops. import { Schedule } from "effect" const cacheInvalidationSequence = Schedule.spaced("100 millis").pipe( - Schedule.take(2), - Schedule.andThen(Schedule.spaced("30 seconds").pipe(Schedule.take(3))) + Schedule.upTo({ times: 2 }), + Schedule.concat(Schedule.spaced("30 seconds").pipe(Schedule.upTo({ times: 3 }))) ) ``` @@ -303,20 +269,20 @@ Fibonacci phase, preserving the phase in the output. import { Result, Schedule } from "effect" const phasedRetryClassifier = Schedule.exponential("100 millis").pipe( - Schedule.take(2), - Schedule.andThenResult(Schedule.fibonacci("500 millis").pipe(Schedule.take(3))), - Schedule.map((result) => + 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: "steady", delay }), - onSuccess: (delay) => ({ phase: "fast", delay }) + onFailure: (delay) => ({ phase: "fast", delay }), + onSuccess: (delay) => ({ phase: "steady", delay }) }) ) ) ``` -Explanation: `Schedule.andThenResult` keeps phase information in the output. -The first schedule is represented by the success side, and the second schedule -is represented by the failure side. +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 @@ -342,103 +308,6 @@ Explanation: `Schedule.setInputType` tells TypeScript which input each step receives. `Schedule.passthrough` makes the output the latest input, so a polling schedule can return the final status instead of a counter. -### Keep A History Of Retry Error Codes - -Goal: Create a schedule for string error-code inputs that checks about once per -second and outputs all input codes seen so far. - -```ts -import { Schedule } from "effect" - -const retryErrorCodeHistory = Schedule.collectInputs( - Schedule.spaced("1 second").pipe( - Schedule.setInputType() - ) -) -``` - -Explanation: `Schedule.collectInputs` is useful when the input stream is the -thing to audit, such as retry errors or status samples. - -### Collect Deployment Phases Until Failure - -Goal: Create a schedule that outputs the collected deployment phases while the -input phase is not `failed`. - -```ts -import { Schedule } from "effect" - -type DeploymentPhase = { - readonly phase: "created" | "deploying" | "verifying" | "failed" -} - -const deploymentPhaseHistory = Schedule.identity().pipe( - Schedule.collectWhile(({ input }) => input.phase !== "failed") -) -``` - -Explanation: `Schedule.collectWhile` accumulates accepted outputs and stops -collecting when the predicate is false. This is cheaper to read than manually -combining `identity`, `while`, and `reduce` for simple histories. - -### Collect Heartbeat Counts - -Goal: Create a schedule that recurs forever, collects recurrence counts produced -so far, and takes only the first 3 collected outputs. - -```ts -import { Schedule } from "effect" - -const heartbeatCountHistory = Schedule.collectOutputs(Schedule.forever).pipe( - Schedule.take(3) -) -``` - -Explanation: `Schedule.collectOutputs` collects the schedule output, not the -input. Add `Schedule.take` or another bound when collecting from an unbounded -schedule. - -## Accumulate State - -### Accumulate Request Latency Stats - -Goal: Create a schedule for latency samples that inspects the first 5 inputs and -outputs running `min`, `max`, `total`, and `count` fields. - -```ts -import { Schedule } from "effect" - -const requestLatencyStats = Schedule.identity().pipe( - Schedule.take(5), - Schedule.reduce( - () => ({ min: Number.POSITIVE_INFINITY, max: 0, total: 0, count: 0 }), - (state, latency) => ({ - min: Math.min(state.min, latency), - max: Math.max(state.max, latency), - total: state.total + latency, - count: state.count + 1 - }) - ) -) -``` - -Explanation: `Schedule.reduce` is for a running aggregate. Prefer it over -collecting every value when callers only need a summary. - -### Count Recent Worker Failures - -Goal: Create a schedule for boolean failure samples that inspects the first 5 -inputs and outputs the running count of failed samples. - -```ts -import { Schedule } from "effect" - -const recentWorkerFailureCount = Schedule.identity().pipe( - Schedule.take(5), - Schedule.reduce(() => 0, (count, failed) => failed ? count + 1 : count) -) -``` - ## Adapt Delays ### Slow A Queue Consumer Under Backpressure @@ -454,14 +323,14 @@ type QueueSnapshot = { readonly depth: number; readonly paused: boolean } const queueBackpressureSchedule = Schedule.identity().pipe( Schedule.while(({ input }) => !input.paused), - Schedule.addDelay((snapshot) => Effect.succeed(snapshot.depth > 1000 ? "5 seconds" : "500 millis")), - Schedule.take(10) + Schedule.addDelay(({ output: snapshot }) => Effect.succeed(snapshot.depth > 1000 ? "5 seconds" : "500 millis")), + Schedule.upTo({ times: 10 }) ) ``` -Explanation: `Schedule.addDelay` adds delay based on the current schedule -output. It is a good fit for input-driven pacing when the output is already the -latest input. +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 @@ -472,17 +341,18 @@ selected delay. ```ts import { Duration, Effect, Schedule } from "effect" -const websocketReconnectDelays = Schedule.exponential("100 millis").pipe( - Schedule.jittered, - Schedule.modifyDelay((_, delay) => Effect.succeed(Duration.min(delay, Duration.seconds(5)))), - Schedule.both(Schedule.recurs(8)), - Schedule.delays -) +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 selected delay and returns the -replacement delay. Use it for caps, floors, clamps, or provider-provided delay -hints. +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 @@ -498,9 +368,9 @@ type HeartbeatStatus = { readonly id: string } const heartbeatInputLogs = Schedule.fixed("10 seconds").pipe( Schedule.setInputType(), - Schedule.tapInput((input) => Console.log(`heartbeat:${input.id}`)), + Schedule.tap(({ input }) => Console.log(`heartbeat:${input.id}`)), Schedule.passthrough, - Schedule.take(2) + Schedule.upTo({ times: 2 }) ) ``` @@ -513,8 +383,8 @@ logs each selected delay without changing the schedule output. import { Console, Schedule } from "effect" const loggedBackoffDelays = Schedule.fibonacci("200 millis").pipe( - Schedule.take(5), - Schedule.tapOutput((delay) => Console.log(delay)) + Schedule.upTo({ times: 5 }), + Schedule.tap(({ output: delay }) => Console.log(delay)) ) ``` @@ -527,61 +397,13 @@ delay in milliseconds without changing the schedule output. import { Console, Duration, Schedule } from "effect" const telemetryBackoffPolicy = Schedule.exponential("250 millis").pipe( - Schedule.take(5), + Schedule.upTo({ times: 5 }), Schedule.tap(({ attempt, output }) => Console.log(`attempt-${attempt}: ${Duration.toMillis(output)}ms`)) ) ``` -Explanation: use `tapInput` when the input matters, `tapOutput` when only the -output matters, and `tap` when metadata such as attempt number or selected -duration matters. None of these operators changes the schedule output. - -## Build Local State Machines - -### Track Scheduler Ticks - -Goal: Create a local scheduler tick state that starts at 1, increments after -each output, maps each output to a `tick` object, and takes 4 outputs. - -```ts -import { Effect, Schedule } from "effect" - -const schedulerTickState = Schedule.unfold(1, (n) => Effect.succeed(n + 1)).pipe( - Schedule.map((tick) => ({ tick })), - Schedule.take(4) -) -``` - -Explanation: `Schedule.unfold` builds a schedule from local state. Use it when -the next output depends on private scheduler state rather than on the latest -input. - -### Cycle A Maintenance Phase Machine - -Goal: Create a local phase machine for maintenance work. Start at `warming`, -move to `active`, then `cooling`, then back to `active`, and take 6 outputs. - -```ts -import { Effect, Schedule } from "effect" - -type MaintenancePhase = "warming" | "active" | "cooling" - -const maintenancePhaseMachine = Schedule.unfold( - "warming", - (phase) => { - switch (phase) { - case "warming": - return Effect.succeed("active") - case "active": - return Effect.succeed("cooling") - case "cooling": - return Effect.succeed("active") - } - } -).pipe( - Schedule.take(6) -) -``` +Explanation: use `Schedule.tap` to observe inputs, outputs, and metadata such as +attempt number or selected duration without changing the schedule output. ## Realistic Policies @@ -610,13 +432,15 @@ const isRetryableGraphqlGatewayError = ( error.status === 502 || error.status === 503)) -const graphqlGatewayRetry = Schedule.exponential("100 millis").pipe( - Schedule.jittered, - Schedule.setInputType(), - Schedule.modifyDelay((_, delay) => Effect.succeed(Duration.min(delay, Duration.seconds(2)))), - Schedule.both(Schedule.recurs(6)), - Schedule.while(({ input }) => isRetryableGraphqlGatewayError(input)), - Schedule.delays +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)) ) ``` @@ -627,7 +451,7 @@ jitters the selected delay, outputs the latest status, continues only while the rollout is running, and stops after about 2 minutes. ```ts -import { Schedule } from "effect" +import { Duration, Schedule } from "effect" type RolloutStatus = { readonly state: "running" | "succeeded" | "failed" @@ -637,9 +461,10 @@ const rolloutStatusWatcher = Schedule.fixed("1 second").pipe( Schedule.setInputType(), Schedule.passthrough, Schedule.jittered, - Schedule.both(Schedule.during("2 minutes")), - Schedule.while(({ input }) => input.state === "running"), - Schedule.map(([status]) => status) + Schedule.while(({ input, elapsed }) => + input.state === "running" && + Duration.isLessThanOrEqualTo(Duration.millis(elapsed), Duration.minutes(2)) + ) ) ``` @@ -658,22 +483,24 @@ type PushProviderResponse = { readonly retryAfter: Duration.Duration | undefined } -const pushNotificationProviderRetry = Schedule.exponential("1 second").pipe( - Schedule.setInputType(), - Schedule.passthrough, - Schedule.modifyDelay((response, delay) => - Effect.succeed( - Duration.min( - response.retryAfter === undefined - ? delay - : Duration.max(delay, response.retryAfter), - Duration.minutes(1) +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.both(Schedule.recurs(6)), - Schedule.while(({ input }) => input.status === 429 || input.status === 500 || input.status === 503), - Schedule.delays + Schedule.recurs(6) +]).pipe( + Schedule.while(({ input }) => input.status === 429 || input.status === 500 || input.status === 503) ) ``` @@ -684,7 +511,7 @@ another 5 seconds for `slow_down`, output the latest input, continue only for `authorization_pending` and `slow_down`, and stop after about 15 minutes. ```ts -import { Effect, Schedule } from "effect" +import { Duration, Effect, Schedule } from "effect" type OAuthDeviceCodeStatus = { readonly error: @@ -697,10 +524,11 @@ type OAuthDeviceCodeStatus = { const oauthDeviceCodePolling = Schedule.spaced("5 seconds").pipe( Schedule.setInputType(), Schedule.passthrough, - Schedule.addDelay((status) => Effect.succeed(status.error === "slow_down" ? "5 seconds" : "0 millis")), - Schedule.both(Schedule.during("15 minutes")), - Schedule.while(({ input }) => input.error === "authorization_pending" || input.error === "slow_down"), - Schedule.map(([status]) => status) + 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)) + ) ) ``` @@ -714,9 +542,9 @@ aligned 15-minute cadence. import { Schedule } from "effect" const incidentEscalationCadence = Schedule.spaced("1 minute").pipe( - Schedule.take(3), - Schedule.andThen(Schedule.spaced("5 minutes").pipe(Schedule.take(3))), - Schedule.andThen(Schedule.fixed("15 minutes")) + Schedule.upTo({ times: 3 }), + Schedule.concat(Schedule.spaced("5 minutes").pipe(Schedule.upTo({ times: 3 }))), + Schedule.concat(Schedule.fixed("15 minutes")) ) ``` @@ -730,6 +558,6 @@ about 30 seconds, then switches to a cron schedule that recurs every day at import { Schedule } from "effect" const maintenanceCronAfterWarmup = Schedule.duration("30 seconds").pipe( - Schedule.andThen(Schedule.cron("0 3 * * *")) + Schedule.concat(Schedule.cron("0 3 * * *")) ) ``` diff --git a/repos/effect/deno.json b/repos/effect/deno.json index 216b21ee9a..42857d84cd 100644 --- a/repos/effect/deno.json +++ b/repos/effect/deno.json @@ -18,6 +18,8 @@ "scripts/", "scratchpad/", "packages/*/typetest/", + "packages/*/typeperf/", + "packages/*/runtimeperf/", "packages/*/benchmark/", "packages/ai", "packages/atom", diff --git a/repos/effect/flake.lock b/repos/effect/flake.lock index 5613a89114..44f1b5b9cf 100644 --- a/repos/effect/flake.lock +++ b/repos/effect/flake.lock @@ -2,11 +2,11 @@ "nodes": { "nixpkgs": { "locked": { - "lastModified": 1778458615, - "narHash": "sha256-cY07EsdhBJ8tFXPzDYevgqxRev9ZLxFonuq9wmq5kwg=", + "lastModified": 1783279667, + "narHash": "sha256-/NAkDSsve+GNM0Bt6tleJdCGfsTlK89nPjkVOzZMo0s=", "owner": "nixos", "repo": "nixpkgs", - "rev": "c6e5ca3c836a5f4dd9af9f2c1fc1c38f0fac988a", + "rev": "f205b5574fd0cb7da5b702a2da51507b7f4fdd1b", "type": "github" }, "original": { diff --git a/repos/effect/flake.nix b/repos/effect/flake.nix index 1280592939..c9c8616e11 100644 --- a/repos/effect/flake.nix +++ b/repos/effect/flake.nix @@ -14,8 +14,8 @@ packages = with pkgs; [ bun deno - corepack - nodejs_25 + (corepack.override {nodejs-slim = nodejs-slim_26;}) + nodejs_26 python3 ]; }; diff --git a/repos/effect/migration/annotations/README.md b/repos/effect/migration/annotations/README.md new file mode 100644 index 0000000000..d78e658715 --- /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 0000000000..9f52a3b51f --- /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.LazyArbitrary" + note: "The lazy arbitrary type moved onto Schema." +"effect/Arbitrary#make": + replacement: "Schema.toArbitrary" + note: "Arbitrary derivation is now exposed directly by Schema." + example: "Schema.toArbitrary(schema)" +"effect/Arbitrary#makeLazy": + replacement: "Schema.toArbitraryLazy" + note: "Lazy arbitrary derivation is now exposed directly by Schema." + example: "Schema.toArbitraryLazy(schema)" diff --git a/repos/effect/migration/annotations/effect__Array.yaml b/repos/effect/migration/annotations/effect__Array.yaml new file mode 100644 index 0000000000..930975b29c --- /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 0000000000..19b9fe3872 --- /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 0000000000..a0fc528411 --- /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 0000000000..9d07fa7aa2 --- /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 0000000000..c696acbc91 --- /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 0000000000..cc4611e8a8 --- /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 0000000000..2b8ea573b0 --- /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 0000000000..e8bfb105bd --- /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 0000000000..2771afb04c --- /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 0000000000..510486a472 --- /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 0000000000..122eb53712 --- /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 0000000000..51146f3ab9 --- /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 0000000000..7da7c04bba --- /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 0000000000..049419589a --- /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 0000000000..77030318d7 --- /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 0000000000..af34f9dfef --- /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 0000000000..94c388d361 --- /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 0000000000..e6f0d5b62e --- /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 0000000000..f6c65c930d --- /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 0000000000..f916ca2cc7 --- /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 0000000000..844e3029b1 --- /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 0000000000..c780a84136 --- /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 0000000000..6f9b67b73b --- /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 0000000000..3306212788 --- /dev/null +++ b/repos/effect/migration/annotations/effect__Effect.yaml @@ -0,0 +1,660 @@ +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#head: + replacement: "Effect.flatMap + Array.head + Effect.fromOption" + note: "Inspect the produced iterable explicitly and fail when it is empty. Adapt arguments and imports to the v4 API." +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 0000000000..a90d6864a4 --- /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 0000000000..02c6510a15 --- /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 0000000000..9b1508e3e7 --- /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 0000000000..34601828de --- /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 0000000000..7b898a5b05 --- /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 0000000000..bd73e986f7 --- /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 0000000000..831f31afaf --- /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 0000000000..5644d43681 --- /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 0000000000..0a1868b0a2 --- /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 0000000000..659066b25b --- /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 0000000000..bd7bdb5c76 --- /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 0000000000..91e0fc4102 --- /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 0000000000..dd30392562 --- /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 0000000000..403eb277f9 --- /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 0000000000..63a8ce255f --- /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 0000000000..4ba8155417 --- /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 0000000000..ec0d812531 --- /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 0000000000..752128b4f5 --- /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 0000000000..b2c5483359 --- /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 0000000000..414a1bbe4c --- /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 0000000000..eda727c4c5 --- /dev/null +++ b/repos/effect/migration/annotations/effect__Graph.yaml @@ -0,0 +1,12 @@ +"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 0000000000..a98cb79e9b --- /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 0000000000..69f6768872 --- /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 0000000000..e35bc6fa9f --- /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 0000000000..adaa83fd2f --- /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 0000000000..f6475e068b --- /dev/null +++ b/repos/effect/migration/annotations/effect__Inspectable.yaml @@ -0,0 +1,12 @@ +"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 0000000000..b25d8798c2 --- /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 0000000000..34d61b8f0b --- /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 0000000000..f3b27dc520 --- /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 0000000000..def772ee9b --- /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 0000000000..a87d5e2f54 --- /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 0000000000..ce1ec1d1e2 --- /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 0000000000..f0a4995d36 --- /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 0000000000..8060c93d25 --- /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 0000000000..b8a4a8cf45 --- /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 0000000000..795891c681 --- /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 0000000000..5daa23e94a --- /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 0000000000..b04a3a0baa --- /dev/null +++ b/repos/effect/migration/annotations/effect__Match.yaml @@ -0,0 +1,42 @@ +"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#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." diff --git a/repos/effect/migration/annotations/effect__MergeDecision.yaml b/repos/effect/migration/annotations/effect__MergeDecision.yaml new file mode 100644 index 0000000000..c2606ba0a4 --- /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 0000000000..beb3f5811e --- /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 0000000000..de08cbd877 --- /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 0000000000..67a83b2e3a --- /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 0000000000..cc32730291 --- /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 0000000000..9350c36c5b --- /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 0000000000..512d460e5a --- /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 0000000000..e4288478cf --- /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 0000000000..27cc1bbbfd --- /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 0000000000..14cebe3953 --- /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 0000000000..9a5d183bdd --- /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 0000000000..48b0f2c6fa --- /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 0000000000..25a25f98d4 --- /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 0000000000..562cd279b8 --- /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 0000000000..07ea5d5e64 --- /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 0000000000..2018907306 --- /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 0000000000..fcc4cef0e6 --- /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 0000000000..c9cd121265 --- /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 0000000000..6ba01f209b --- /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 0000000000..e41e4d1929 --- /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 0000000000..b1e77db367 --- /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 0000000000..75f930ac9b --- /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 0000000000..365449cb79 --- /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 0000000000..68af191087 --- /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 0000000000..3feefd4736 --- /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 0000000000..f7ae84b959 --- /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 0000000000..b4a746b9ea --- /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 0000000000..6cd929382f --- /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 0000000000..5649916c98 --- /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 0000000000..1a5c14d41f --- /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 0000000000..cee33218f1 --- /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 0000000000..d0eab5568a --- /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 0000000000..74bee66bf3 --- /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 0000000000..77dd05c4f0 --- /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 0000000000..8c56ece36f --- /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 0000000000..b77f8783ea --- /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 0000000000..2e5c5dd0e2 --- /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 0000000000..4e86dff117 --- /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 0000000000..31085e8168 --- /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 0000000000..0334c40d5f --- /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 0000000000..3abdf00fbe --- /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 0000000000..a917515968 --- /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 0000000000..b8e0ab8fec --- /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 0000000000..bc9b172eb9 --- /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 0000000000..647e692501 --- /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 0000000000..e26fcaee71 --- /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 0000000000..89779d17b0 --- /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 0000000000..eb6d3bcd33 --- /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 0000000000..915423f682 --- /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 0000000000..d11b88e98b --- /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 0000000000..00770ca6ca --- /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 0000000000..bce4dad164 --- /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 0000000000..2b9a211baa --- /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 0000000000..69985bca3a --- /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 0000000000..06e3b0191a --- /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 0000000000..69c83c30e4 --- /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 0000000000..00a8f970d4 --- /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 0000000000..e27e3e6804 --- /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 0000000000..25b6de9b52 --- /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 0000000000..42581a2040 --- /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 0000000000..475273e88b --- /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 0000000000..3801646de9 --- /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 0000000000..636836d099 --- /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 0000000000..5139e02e97 --- /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 0000000000..cc0e6e0c88 --- /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 0000000000..c2d861cd59 --- /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 0000000000..6d76942ef8 --- /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 0000000000..598ffa565b --- /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 0000000000..03e95d4b52 --- /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 0000000000..acdcf20424 --- /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 0000000000..94bb39b4a1 --- /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 0000000000..dbaf544395 --- /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 0000000000..3399df3608 --- /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 0000000000..1dbd674fe8 --- /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 0000000000..5fef8a0505 --- /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 0000000000..dd77149a59 --- /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 0000000000..8641fa49a0 --- /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 0000000000..1bb30050de --- /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 0000000000..cf92056459 --- /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 0000000000..487f84c0bf --- /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 0000000000..ef7de6f07c --- /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 0000000000..3bc6b1955e --- /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 0000000000..399bcd9a18 --- /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 0000000000..eed0c896c9 --- /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 0000000000..ac4becb2b5 --- /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 0000000000..51d649588e --- /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 0000000000..ccb654ec97 --- /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 0000000000..0c8cfe1acf --- /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 0000000000..b8d69241dc --- /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 0000000000..407cb77eab --- /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 0000000000..2faf243d52 --- /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 0000000000..e95f504bd8 --- /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 0000000000..39470247c5 --- /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 0000000000..6738380cb6 --- /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 0000000000..e12b07c392 --- /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 0000000000..2310757346 --- /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 0000000000..0e146dd2ec --- /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 0000000000..e14547f374 --- /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 0000000000..38b40cb2c9 --- /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 0000000000..89ec37e3d3 --- /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 0000000000..cbd7331fac --- /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 0000000000..31a3882a9b --- /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 0000000000..af0ea83993 --- /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 0000000000..40387ec463 --- /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 0000000000..e9b3b9352d --- /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 0000000000..626bc2eb36 --- /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 0000000000..9c49ec99fb --- /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 0000000000..bc1365d26f --- /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 0000000000..c55353c867 --- /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 0000000000..bc5ed3ea7f --- /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 0000000000..7553bfbb29 --- /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 0000000000..d0ecd9e747 --- /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 0000000000..6fa9c33d3d --- /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 0000000000..068f85afe4 --- /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 0000000000..d624ede500 --- /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 0000000000..a2cb45a97c --- /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 0000000000..5cf62d7762 --- /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 0000000000..76e5ff2937 --- /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 0000000000..f8fa2bd854 --- /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 0000000000..9de246d44a --- /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 0000000000..0b6ed23974 --- /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 0000000000..f344b4f944 --- /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 0000000000..175622d5ec --- /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 0000000000..0cda6708cd --- /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 0000000000..cd91c242c3 --- /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 0000000000..a5cfecfc05 --- /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 0000000000..5f96a15c45 --- /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 0000000000..2395a07f4d --- /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 0000000000..b9a58fd0e1 --- /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 0000000000..7152df239c --- /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 0000000000..21af9e0d6e --- /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 0000000000..52fb666738 --- /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 0000000000..9c459de0cb --- /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 0000000000..5f0446a2b9 --- /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 0000000000..611408a137 --- /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 0000000000..81023018b4 --- /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 0000000000..5ec7a2e8ad --- /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 0000000000..18fdc4ef1f --- /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 0000000000..49daa878e9 --- /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 0000000000..2a4a652993 --- /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 0000000000..d906cf83a4 --- /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 0000000000..a4c065c0d4 --- /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 0000000000..8079a23755 --- /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 0000000000..7436f1bf5e --- /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 0000000000..c2557430e7 --- /dev/null +++ b/repos/effect/migration/annotations/effect__ai__McpSchema.yaml @@ -0,0 +1,24 @@ +"@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." +"@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 0000000000..7ded9767a5 --- /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 0000000000..a09b05774f --- /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 0000000000..d08583bc6e --- /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 0000000000..2a1351c128 --- /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 0000000000..5bd6608419 --- /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 0000000000..7a07a03696 --- /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 0000000000..c8787f31a4 --- /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 0000000000..a52d95b5a9 --- /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 0000000000..f95db093c2 --- /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 0000000000..7bdf4dd136 --- /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 0000000000..66c0860dd1 --- /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 0000000000..50985d4b8a --- /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 0000000000..ad5e73f6df --- /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 0000000000..295f9e1ac4 --- /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 0000000000..6875422f4f --- /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 0000000000..3c157a9c82 --- /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 0000000000..707bf62b86 --- /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 0000000000..950aca3325 --- /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 0000000000..3ee5b31251 --- /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 0000000000..9992b4ac85 --- /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" + note: "Use the moved constructor; --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 0000000000..35082dfc96 --- /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 0000000000..8c1f6ffb17 --- /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 0000000000..aed15625d0 --- /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 0000000000..5c9b66be65 --- /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 0000000000..8a416c6191 --- /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 0000000000..ecbb121172 --- /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 0000000000..e32af55c54 --- /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 0000000000..9dd6c41b15 --- /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 0000000000..dc3c5c316f --- /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 0000000000..d7bf79fc13 --- /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 0000000000..bba0f7b70d --- /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 0000000000..661a7e0f40 --- /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 0000000000..0bc1367ae2 --- /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 0000000000..dbc9ad7b05 --- /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 0000000000..f6e0df345d --- /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 0000000000..6edba37157 --- /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 0000000000..19472a5e20 --- /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 0000000000..2d749b6f7c --- /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 0000000000..77a7346752 --- /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 0000000000..ea31aef070 --- /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 0000000000..95933caa43 --- /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 0000000000..c7611dcb8b --- /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. It now returns Envelope.Partial; 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 0000000000..1ebf92ecf1 --- /dev/null +++ b/repos/effect/migration/annotations/effect__cluster__MessageStorage.yaml @@ -0,0 +1,12 @@ +"@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." +"@effect/cluster/MessageStorage#Encoded": + replacement: "effect/unstable/cluster/MessageStorage#Encoded" + note: "Moved into core Effect; use the v4 Envelope.Encoded and Reply.Encoded aliases in custom encoded storage implementations." 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 0000000000..8f0c4e831c --- /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)." +"@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." +"@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 0000000000..ebc99a826d --- /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 0000000000..33246c9a4a --- /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 0000000000..ed573b9b0d --- /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 0000000000..b74c5f1cc8 --- /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 0000000000..59560ad170 --- /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 0000000000..a8f9bff703 --- /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 with the same callbacks and requirements; 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 0000000000..3f416f62a2 --- /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 0000000000..774b80e243 --- /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 0000000000..192d7dd6a1 --- /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 with the same complete defaults; service type projections now use Service instead of Type." +"@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 0000000000..8aeb99b276 --- /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 0000000000..d8dd53d8d5 --- /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 0000000000..dd97459118 --- /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 0000000000..6f81f8f004 --- /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 0000000000..67e87e5d46 --- /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 0000000000..76e9b2481d --- /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 0000000000..833229baea --- /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 0000000000..39c6b0c37f --- /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 0000000000..521f937d8c --- /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 0000000000..00d9e4089c --- /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 0000000000..8f4c3ffa2a --- /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 0000000000..44ce415b77 --- /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 0000000000..6009364c92 --- /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 0000000000..71f36dcb22 --- /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 0000000000..15396b28ae --- /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 0000000000..89e43d33b5 --- /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 0000000000..75f54efe3a --- /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 0000000000..d7ad705bce --- /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 0000000000..8697535225 --- /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 0000000000..281f01ae7e --- /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 0000000000..8a044978f6 --- /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 0000000000..bec876f630 --- /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 0000000000..d4d737f3df --- /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 0000000000..2a62aeb1e8 --- /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 0000000000..6dd50fbfbb --- /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 0000000000..3dcce020f6 --- /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 0000000000..d766b9c6be --- /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 0000000000..bd53323f6e --- /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 0000000000..cc9dd54beb --- /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 0000000000..44ee172478 --- /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 0000000000..16ac3dfd33 --- /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 0000000000..dfc6b6e410 --- /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 0000000000..4ca45b2d72 --- /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 0000000000..27592db8a1 --- /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 0000000000..621a7f78da --- /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 0000000000..69fe62a7b3 --- /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 0000000000..5f233fe6cf --- /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 0000000000..865c3f4d04 --- /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 0000000000..ca42fdd69d --- /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 0000000000..1f52497dba --- /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 0000000000..ea049f6482 --- /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 0000000000..ad26cfa0d4 --- /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 0000000000..4c9ab8a478 --- /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 0000000000..6737fd2d45 --- /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 0000000000..abe3f8563c --- /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 0000000000..c6b7b28b8d --- /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 0000000000..a649a5fb41 --- /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 0000000000..2487741a91 --- /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 0000000000..efe4a6026f --- /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 0000000000..c6448494f5 --- /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 0000000000..5b6fe7aa7a --- /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 0000000000..7ae5d345b1 --- /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 0000000000..f27bb5e685 --- /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 0000000000..1d91ac92e8 --- /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 0000000000..c0b9d7bdc4 --- /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 0000000000..f9e8846c07 --- /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 0000000000..f7e226d976 --- /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 0000000000..9d87f27777 --- /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 0000000000..0def0b900e --- /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 0000000000..ac93b76bd0 --- /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 0000000000..c4cc6a98e1 --- /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 0000000000..a90b9e375b --- /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 0000000000..2fdea4a15d --- /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 0000000000..95b7882e17 --- /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 0000000000..9f720a621d --- /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 0000000000..000285be37 --- /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 0000000000..3d2ed68a6c --- /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 0000000000..49ab6a1929 --- /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 0000000000..8d2679edeb --- /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 0000000000..4b343a35c3 --- /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 0000000000..9757ec982f --- /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 0000000000..ad9ef5d50f --- /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 0000000000..87da421895 --- /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 0000000000..cf2eb77b48 --- /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 0000000000..8215e91d11 --- /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 0000000000..d130beed37 --- /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 0000000000..2f59e66855 --- /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 0000000000..9994d65cd6 --- /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 0000000000..8ee6af441e --- /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 0000000000..20a52d962a --- /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 0000000000..670e8ac4c2 --- /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 0000000000..55d29969bb --- /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 0000000000..a706f503b7 --- /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 0000000000..7de86ada6f --- /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 0000000000..64cc6a8b59 --- /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 0000000000..95a7a7fe3d --- /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 0000000000..04d45d0fcf --- /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 0000000000..d2e0c66e95 --- /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 0000000000..4653d0b895 --- /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 0000000000..7a48fbcb26 --- /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 0000000000..af9a59b08a --- /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 0000000000..976d72eb27 --- /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 0000000000..0cf3cfa236 --- /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 0000000000..ca0d6fe34c --- /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 0000000000..61763d683f --- /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 0000000000..4c3f0b6ce7 --- /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 0000000000..1186c7d05a --- /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 0000000000..ddd7a88d31 --- /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 0000000000..ef95fbf84f --- /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 0000000000..d761c45e2a --- /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 0000000000..3742d27e57 --- /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 0000000000..92cf124c3b --- /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 0000000000..5c8f3b0be1 --- /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 0000000000..2d35327770 --- /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 0000000000..5297e522dd --- /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 0000000000..d0389a5545 --- /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 0000000000..a6853fcafc --- /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 0000000000..3f0cc5009d --- /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 0000000000..f29b17c5a5 --- /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 0000000000..170c2c4148 --- /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 0000000000..774f5823ca --- /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 0000000000..4e8e46f1b1 --- /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 0000000000..6b7033dbb8 --- /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 0000000000..37baa2d153 --- /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 0000000000..e6eba3580b --- /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 0000000000..cc48c14c85 --- /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 0000000000..f720fb5d18 --- /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 0000000000..ef12c88d04 --- /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 0000000000..73edb9d95b --- /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 0000000000..dd8f50df1c --- /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 0000000000..d11372f2a3 --- /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 0000000000..ba4d673800 --- /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 0000000000..7fdd24d99c --- /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 0000000000..be98b6d5d6 --- /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 0000000000..60470daf86 --- /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 0000000000..82ea35001b --- /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 0000000000..306afb813e --- /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 0000000000..86d7dfd144 --- /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 0000000000..ba36c699f7 --- /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 0000000000..3c4d47b6bf --- /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 0000000000..e5f43102ea --- /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 0000000000..9786ff676c --- /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 0000000000..fe9cb44cf8 --- /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 0000000000..683b937f3f --- /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 0000000000..c80325ae50 --- /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 0000000000..82b29e9886 --- /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 0000000000..6e418cd392 --- /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 0000000000..750590e00a --- /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 0000000000..cb71a5141c --- /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 0000000000..b7a20dbd8c --- /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 0000000000..92bdf007f2 --- /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 0000000000..9750e3f2a3 --- /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 0000000000..02a5415ee7 --- /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 0000000000..951df8c57b --- /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 0000000000..a799f823a1 --- /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 0000000000..21e8d6ae23 --- /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 0000000000..52956590e8 --- /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 0000000000..8991b67662 --- /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 0000000000..ae474455a5 --- /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 0000000000..26e03671de --- /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 0000000000..08a09ef886 --- /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 0000000000..6b8aa9c5c0 --- /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 0000000000..aab454ff43 --- /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 0000000000..a29ae873a3 --- /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 0000000000..2f14c60c44 --- /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 0000000000..7d407cdc95 --- /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 0000000000..af84c38732 --- /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 0000000000..64e9602632 --- /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 0000000000..4e63575e80 --- /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 0000000000..a393f185b4 --- /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 0000000000..74cb297f39 --- /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 0000000000..d549449d47 --- /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 0000000000..aaa28f6ada --- /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 0000000000..c20087b590 --- /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 0000000000..afbdd05e95 --- /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 0000000000..99a8723324 --- /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 0000000000..db32a74ef1 --- /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 0000000000..b26b2af227 --- /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 0000000000..16dd4c082a --- /dev/null +++ b/repos/effect/migration/annotations/effect__rpc__RpcMessage.yaml @@ -0,0 +1,6 @@ +"@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 0000000000..3fad622b6c --- /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 0000000000..6164045dfd --- /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 0000000000..2a2258dd51 --- /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 and explicit capability flags." +"@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 0000000000..caa66472b6 --- /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 0000000000..3387ce0481 --- /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 0000000000..2453212dd3 --- /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 0000000000..c773d443f4 --- /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 0000000000..61fd8dd3f5 --- /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 0000000000..a73f163586 --- /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 0000000000..a0299ad512 --- /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 0000000000..963da1e619 --- /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 0000000000..a435b55449 --- /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 0000000000..7a1f5fc351 --- /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 0000000000..6846ace995 --- /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 0000000000..1c8b0f2e47 --- /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 0000000000..40b3de2d2a --- /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 0000000000..b0caeee564 --- /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 0000000000..868b4e6d72 --- /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 0000000000..7d18812bab --- /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 0000000000..e2efef269a --- /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 0000000000..31b4626f01 --- /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 0000000000..12e910afc9 --- /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 0000000000..611b275ee5 --- /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 0000000000..56292676be --- /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 0000000000..817cc7ed05 --- /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 0000000000..81c416a1de --- /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 0000000000..f74fad15a7 --- /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 0000000000..ad7fcc5a6b --- /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 0000000000..d936bcb8c7 --- /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 0000000000..2104c4ec6f --- /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 0000000000..aabf0cd667 --- /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 0000000000..1ce99cfeff --- /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 0000000000..5bbf9cfbec --- /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 0000000000..415dbb0d06 --- /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 0000000000..d46613df65 --- /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 0000000000..45a5fa68cc --- /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 0000000000..64cf9a2ff8 --- /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 0000000000..a649b2af9c --- /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 0000000000..613890f1de --- /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 0000000000..3ca711ee6d --- /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 0000000000..10a624a976 --- /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 0000000000..00db99efc2 --- /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 0000000000..e97901a03f --- /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 0000000000..56a7889ac6 --- /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 0000000000..50b119fac1 --- /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 0000000000..60b3b2d96a --- /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 0000000000..53e298176e --- /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 0000000000..fadb083369 --- /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 0000000000..a87b23afd6 --- /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 0000000000..4159f622b1 --- /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 0000000000..353d499c6f --- /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 0000000000..045e8637eb --- /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 0000000000..9ebe60ebe4 --- /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 0000000000..b50c134828 --- /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 0000000000..a7a8ebd989 --- /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 0000000000..1fabb3fd7e --- /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 0000000000..f8389cbb39 --- /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 0000000000..3f6c02a19e --- /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 0000000000..62039ee4e6 --- /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 0000000000..86f2652a1a --- /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 0000000000..9d8f80180c --- /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 0000000000..b64d96c4fe --- /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 0000000000..e5cfb09a6f --- /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 0000000000..3c3d180b2b --- /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 0000000000..25129fe1f5 --- /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 0000000000..0ce29d2ccd --- /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 0000000000..5d3e62d0a7 --- /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 0000000000..3afd034cc4 --- /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 0000000000..d03ceb6689 --- /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 0000000000..6c3fd5e166 --- /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 0000000000..16d84e90f5 --- /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 0000000000..a5f0a5917b --- /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 0000000000..8ac86cb923 --- /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 0000000000..fd13e924d1 --- /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 0000000000..eaab3e72a5 --- /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 0000000000..a939a16229 --- /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 0000000000..add881f13b --- /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 0000000000..e20b9559e8 --- /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 0000000000..e94aed7f5a --- /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 0000000000..560fdb5870 --- /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 0000000000..0a6b7dbc04 --- /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 0000000000..29c7fe373c --- /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 0000000000..ad27bb0d7b --- /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 0000000000..6f3f4349ef --- /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 0000000000..03a02542a7 --- /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 0000000000..14d0a9a9e0 --- /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 0000000000..bc1a02fd4f --- /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 0000000000..0410c651ed --- /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 0000000000..5510032988 --- /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 0000000000..b10996128e --- /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 0000000000..c79396fdfb --- /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 0000000000..8f1e2c2051 --- /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 0000000000..ff85bec6ce --- /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 0000000000..a40e32ca26 --- /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 0000000000..240ffe3da8 --- /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 0000000000..0995bdb5e4 --- /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 0000000000..cc4b675963 --- /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 0000000000..adb1bf416e --- /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 0000000000..f30fc24b4d --- /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 0000000000..5c2dbb0f05 --- /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 0000000000..a0261367ec --- /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 0000000000..941cb9f256 --- /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 0000000000..543c7002cd --- /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 0000000000..bc1d27b4ad --- /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 0000000000..b604847efb --- /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/fiberref.md b/repos/effect/migration/fiberref.md index 51a0886221..abfc17fa6f 100644 --- a/repos/effect/migration/fiberref.md +++ b/repos/effect/migration/fiberref.md @@ -11,7 +11,6 @@ from `References` and related modules. | v3 FiberRef | v4 Reference | | ----------------------------------- | ---------------------------------- | -| `FiberRef.currentConcurrency` | `References.CurrentConcurrency` | | `FiberRef.currentLogLevel` | `References.CurrentLogLevel` | | `FiberRef.currentMinimumLogLevel` | `References.MinimumLogLevel` | | `FiberRef.currentLogAnnotations` | `References.CurrentLogAnnotations` | @@ -88,8 +87,8 @@ set via `Effect.provideService`, which scopes the value to the provided effect. import { Effect, FiberRef } from "effect" const program = Effect.gen(function*() { - yield* FiberRef.set(FiberRef.currentConcurrency, 10) - // subsequent code sees concurrency = 10 + yield* FiberRef.set(FiberRef.currentMaxOpsBeforeYield, 500) + // subsequent code sees maxOpsBeforeYield = 500 }) ``` @@ -100,10 +99,10 @@ import { Effect, References } from "effect" const program = Effect.provideService( Effect.gen(function*() { - const concurrency = yield* References.CurrentConcurrency - console.log(concurrency) // 10 + const maxOps = yield* References.MaxOpsBeforeYield + console.log(maxOps) // 500 }), - References.CurrentConcurrency, - 10 + References.MaxOpsBeforeYield, + 500 ) ``` diff --git a/repos/effect/migration/schema.md b/repos/effect/migration/schema.md index 98c2f046b6..d2d4db1c76 100644 --- a/repos/effect/migration/schema.md +++ b/repos/effect/migration/schema.md @@ -29,6 +29,8 @@ This document maps v3 Schema APIs to their v4 equivalents. Simple renames and ar | `RedactedFromSelf` | `Redacted` | rename | | `Redacted` | `RedactedFromValue` | rename | | `EitherFromSelf` | `Result` | rename | +| `DateFromNumber` | `DateFromMillis` | rename | +| `Date` | `DateFromString` | restructure | | `TaggedError` | `TaggedErrorClass` | rename | | `decodeUnknown` | `decodeUnknownEffect` | rename | | `decode` | `decodeEffect` | rename | @@ -86,6 +88,30 @@ The following `*FromSelf` schemas have been renamed to drop the suffix: `DateFromSelf` → `Date`, `DurationFromSelf` → `Duration`, `ChunkFromSelf` → `Chunk`, `ReadonlyMapFromSelf` → `ReadonlyMap`, `ReadonlySetFromSelf` → `ReadonlySet`, `HashMapFromSelf` → `HashMap`, `HashSetFromSelf` → `HashSet`, `BigDecimalFromSelf` → `BigDecimal`, `CauseFromSelf` → `Cause`, `ExitFromSelf` → `Exit`, `OptionFromSelf` → `Option`, `RegExpFromSelf` → `RegExp` +### `Date` encoded contract + +**Migration: restructure** + +In v3, `Schema.Date` decoded an ISO date string to a `Date` and rejected invalid dates. In v4, `Schema.Date` is the renamed `Schema.DateFromSelf`, so it expects a valid `Date` as its encoded value. Existing code can still type-check after upgrading while no longer accepting the same input. + +v3 + +```ts +import { Schema } from "effect" + +const DateFromIsoString = Schema.Date +``` + +v4 + +```ts +import { Schema } from "effect" + +const DateFromIsoString = Schema.DateFromString +``` + +`Schema.DateFromString` preserves the string-to-`Date` transformation and rejects strings that produce invalid dates. + ### Filter renames All filters have been renamed with an `is` prefix and now use `check(...)` or `pipe(Schema.check(...))`: @@ -242,7 +268,7 @@ v4 ```ts import { Schema, SchemaRepresentation } from "effect" -const doc = SchemaRepresentation.fromAST(Schema.String.ast) +const doc = SchemaRepresentation.toRepresentation(Schema.String.ast) const multi = SchemaRepresentation.toMultiDocument(doc) const codeDoc = SchemaRepresentation.toCodeDocument(multi) console.log(codeDoc.codes[0].Type) @@ -872,14 +898,14 @@ const NumberFromString = Schema.transformOrFail(Schema.String, Schema.Number, { v4 ```ts -import { Effect, Number, Option, Schema, SchemaGetter, SchemaIssue } from "effect" +import { Effect, Number, Schema, SchemaGetter, SchemaIssue } from "effect" const NumberFromString = Schema.String.pipe( Schema.decodeTo(Schema.Number, { decode: SchemaGetter.transformOrFail((s) => { const n = Number.parse(s) if (n === undefined) { - return Effect.fail(new SchemaIssue.InvalidValue(Option.some(s))) + return Effect.fail(new SchemaIssue.InvalidValue()) } return Effect.succeed(n) }), diff --git a/repos/effect/migration/v3-to-v4.md b/repos/effect/migration/v3-to-v4.md index 5e3f3c7718..459cdc4609 100644 --- a/repos/effect/migration/v3-to-v4.md +++ b/repos/effect/migration/v3-to-v4.md @@ -1,14 +1,12 @@ -# v3 to v4 Import and API Rename Maps + -Mapped modules: 290 -No counterpart: 43 -API renames: 53 +# v3 to v4 Migration Reference -This file is intended for migration agents. It contains user-facing import -specifier mappings and API rename mappings. +Base: `3d390f232bdbc3f0d3d6a2ae3c775084f494b547` (`3d390f232bdbc3f0d3d6a2ae3c775084f494b547`) -Use the import map when rewriting import declarations. Use the API renames when -rewriting renamed symbols. +Head: `main` (`b938c8ad2823bd88493187922f7d9090eff037b6`) + +This file is generated from the API diff and `migration/annotations/*.yaml`. ## Import Map @@ -314,6 +312,7 @@ These v4 modules did not have a mapped v3 module. Treat them as v4-only unless a more specific migration guide says otherwise. ```text +@effect/platform-node/NodeMultipartParser (barrel: @effect/platform-node) effect/ErrorReporter (barrel: effect) effect/Filter (barrel: effect) effect/JsonPatch (barrel: effect) @@ -324,7 +323,6 @@ effect/Optic (barrel: effect) effect/Pull (barrel: effect) effect/SchemaGetter (barrel: effect) effect/SchemaRepresentation (barrel: effect) -effect/SchemaUtils (barrel: effect) effect/Semaphore (barrel: effect) effect/Stdio (barrel: effect) effect/TxChunk (barrel: effect) @@ -341,11 +339,9 @@ effect/unstable/eventlog/EventLogSessionAuth (barrel: effect/unstable/eventlog) effect/unstable/eventlog/SqlEventLogServerUnencrypted (barrel: effect/unstable/eventlog) effect/unstable/http/FindMyWay (barrel: effect/unstable/http) effect/unstable/http/HttpStaticServer (barrel: effect/unstable/http) -effect/unstable/http/Multipasta (barrel: effect/unstable/http) -effect/unstable/http/Multipasta/HeadersParser (barrel: effect/unstable/http) -effect/unstable/http/Multipasta/Node (barrel: effect/unstable/http) -effect/unstable/http/Multipasta/Search (barrel: effect/unstable/http) -effect/unstable/http/Multipasta/Web (barrel: effect/unstable/http) +effect/unstable/http/MultipartParser (barrel: effect/unstable/http) +effect/unstable/http/MultipartParser/HeadersParser (barrel: effect/unstable/http) +effect/unstable/http/MultipartParser/Search (barrel: effect/unstable/http) effect/unstable/httpapi/HttpApiTest (barrel: effect/unstable/httpapi) effect/unstable/observability/PrometheusMetrics (barrel: effect/unstable/observability) effect/unstable/persistence/Redis (barrel: effect/unstable/persistence) @@ -359,63 +355,15765 @@ effect/unstable/reactivity/Hydration (barrel: effect/unstable/reactivity) effect/unstable/rpc/Utils (barrel: effect/unstable/rpc) ``` -## API Renames +## Removed Modules -Each line is `v3 API -> v4 API`. Use these mappings when rewriting renamed -symbols from v3 source code to v4. +- `@effect/ai` -> `effect/unstable/ai`: The @effect/ai package was merged into the effect package; import the effect/unstable/ai barrel or import specific modules directly (e.g. effect/unstable/ai/\). +- `@effect/ai-amazon-bedrock` -> `none`: The @effect/ai-amazon-bedrock provider package was removed from v4 with no direct replacement. Use @aws-sdk/client-bedrock-runtime directly or build a custom v4 provider integration. +- `@effect/ai-amazon-bedrock/AmazonBedrockClient` -> `none`: The @effect/ai-amazon-bedrock provider package was removed from v4, so AmazonBedrockClient, layer, layerConfig, make, and Service have no direct replacements. Use @aws-sdk/client-bedrock-runtime directly or build a custom v4 provider integration. +- `@effect/ai-amazon-bedrock/AmazonBedrockConfig` -> `none`: The @effect/ai-amazon-bedrock provider package was removed from v4, so AmazonBedrockConfig has no direct replacement. Use @aws-sdk/client-bedrock-runtime directly or build a custom v4 provider integration. +- `@effect/ai-amazon-bedrock/AmazonBedrockLanguageModel` -> `none`: The @effect/ai-amazon-bedrock language-model integration was removed from v4. Use another supported v4 provider or implement LanguageModel.LanguageModel with @aws-sdk/client-bedrock-runtime. +- `@effect/ai-amazon-bedrock/AmazonBedrockSchema` -> `none`: The @effect/ai-amazon-bedrock package was removed from v4, including its hand-written Bedrock schemas. Use @aws-sdk/client-bedrock-runtime request and response types, or schemas supplied by a custom v4 provider integration. +- `@effect/ai-amazon-bedrock/AmazonBedrockTool` -> `none`: The @effect/ai-amazon-bedrock package and its Anthropic-on-Bedrock provider tools were removed from v4. Recreate the capability in a custom provider integration if the Bedrock model still requires it. +- `@effect/ai-amazon-bedrock/EventStreamEncoding` -> `none`: The @effect/ai-amazon-bedrock package and its AWS event-stream decoder were removed from v4. Use the AWS SDK's Bedrock Runtime streaming support or implement decoding in a custom provider client. +- `@effect/ai-amazon-bedrock/index` -> `none`: The @effect/ai-amazon-bedrock provider package was removed from v4 with no direct replacement. Use @aws-sdk/client-bedrock-runtime directly or build a custom v4 provider integration. +- `@effect/ai-anthropic/AnthropicTokenizer`: No single module replacement; follow the curated per-API guidance below. +- `@effect/ai-anthropic/index` -> `@effect/ai-anthropic`: The explicit /index entrypoint was removed; import the same namespaces from the @effect/ai-anthropic package root or import specific modules directly. +- `@effect/ai-google` -> `none`: The @effect/ai-google provider package was removed from v4 with no direct replacement. Use a supported v4 provider integration for Gemini models or integrate Google's current SDK directly. +- `@effect/ai-google/Generated` -> `none`: The @effect/ai-google package was removed from v4, so this generated Google API schema has no Effect v4 replacement. Use Google's current SDK/API types directly or route supported Gemini models through another v4 provider integration. +- `@effect/ai-google/GoogleClient` -> `none`: The @effect/ai-google provider package was removed from v4 and has no direct replacement. Use a supported v4 provider integration for Gemini models or integrate Google's current SDK directly. +- `@effect/ai-google/GoogleConfig` -> `none`: The @effect/ai-google provider package was removed from v4 and has no direct replacement. Use a supported v4 provider integration for Gemini models or integrate Google's current SDK directly. +- `@effect/ai-google/GoogleLanguageModel` -> `none`: The @effect/ai-google language-model integration was removed from v4. Use a supported v4 provider integration for Gemini models or implement LanguageModel.LanguageModel against Google's current SDK. +- `@effect/ai-google/GoogleTool` -> `none`: The @effect/ai-google package and its provider-defined tools were removed from v4. Model this capability in the provider integration you adopt, or define an application Tool when the replacement provider supports it. +- `@effect/ai-google/index` -> `none`: The @effect/ai-google provider package was removed from v4 with no direct replacement. Use a supported v4 provider integration for Gemini models or integrate Google's current SDK directly. +- `@effect/ai-openai/OpenAiTokenizer`: No single module replacement; follow the curated per-API guidance below. +- `@effect/ai-openai/index` -> `@effect/ai-openai`: The explicit /index entrypoint was removed; import the same namespaces from the @effect/ai-openai package root or import specific modules directly. +- `@effect/ai-openrouter/index` -> `@effect/ai-openrouter`: The explicit /index entrypoint was removed; import the same namespaces from the @effect/ai-openrouter package root or import specific modules directly. +- `@effect/ai/AiError` -> `effect/unstable/ai/AiError` +- `@effect/ai/Chat` -> `effect/unstable/ai/Chat` +- `@effect/ai/EmbeddingModel` -> `effect/unstable/ai/EmbeddingModel` +- `@effect/ai/IdGenerator` -> `effect/unstable/ai/IdGenerator` +- `@effect/ai/LanguageModel` -> `effect/unstable/ai/LanguageModel` +- `@effect/ai/McpSchema` -> `effect/unstable/ai/McpSchema` +- `@effect/ai/McpServer` -> `effect/unstable/ai/McpServer` +- `@effect/ai/Model` -> `effect/unstable/ai/Model` +- `@effect/ai/Prompt` -> `effect/unstable/ai/Prompt` +- `@effect/ai/Response` -> `effect/unstable/ai/Response` +- `@effect/ai/Telemetry` -> `effect/unstable/ai/Telemetry` +- `@effect/ai/Tokenizer` -> `effect/unstable/ai/Tokenizer` +- `@effect/ai/Tool` -> `effect/unstable/ai/Tool` +- `@effect/ai/Toolkit` -> `effect/unstable/ai/Toolkit` +- `@effect/ai/index` -> `effect/unstable/ai`: The package barrel was removed; import the same namespaces from the effect/unstable/ai barrel or import specific modules directly. +- `@effect/cli` -> `effect/unstable/cli`: The @effect/cli package was merged into the effect package; import the effect/unstable/cli barrel or import specific modules directly (e.g. effect/unstable/cli/\). +- `@effect/cli/Args` -> `effect/unstable/cli/Argument` +- `@effect/cli/AutoCorrect` -> `none`: V4 suggestion distance is internal and fixed; the public configurable distance helper was removed. +- `@effect/cli/BuiltInOptions` -> `effect/unstable/cli/GlobalFlag` +- `@effect/cli/CliApp`: No single module replacement; follow the curated per-API guidance below. +- `@effect/cli/CliConfig`: No single module replacement; follow the curated per-API guidance below. +- `@effect/cli/Command` -> `effect/unstable/cli/Command` +- `@effect/cli/CommandDescriptor` -> `effect/unstable/cli/Completions` +- `@effect/cli/CommandDirective`: No single module replacement; follow the curated per-API guidance below. +- `@effect/cli/ConfigFile`: No single module replacement; follow the curated per-API guidance below. +- `@effect/cli/HelpDoc` -> `effect/unstable/cli/HelpDoc` +- `@effect/cli/HelpDoc/Span` -> `none`: The Span ADT was removed; v4 help fields are strings and terminal styling is owned by CliOutput. +- `@effect/cli/Options` -> `effect/unstable/cli/Flag` +- `@effect/cli/Primitive` -> `effect/unstable/cli/Primitive` +- `@effect/cli/Prompt` -> `effect/unstable/cli/Prompt` +- `@effect/cli/Usage` -> `none`: The Usage ADT was removed; Command builds a plain HelpDoc.usage string internally. +- `@effect/cli/ValidationError` -> `effect/unstable/cli/CliError` +- `@effect/cli/index` -> `effect/unstable/cli`: The package barrel was removed; import the same namespaces from the effect/unstable/cli barrel or import specific modules directly. +- `@effect/cluster` -> `effect/unstable/cluster`: The @effect/cluster package was merged into the effect package; import the effect/unstable/cluster barrel or import specific modules directly (e.g. effect/unstable/cluster/\). +- `@effect/cluster/ClusterCron` -> `effect/unstable/cluster/ClusterCron` +- `@effect/cluster/ClusterError` -> `effect/unstable/cluster/ClusterError` +- `@effect/cluster/ClusterMetrics` -> `effect/unstable/cluster/ClusterMetrics` +- `@effect/cluster/ClusterSchema` -> `effect/unstable/cluster/ClusterSchema` +- `@effect/cluster/ClusterWorkflowEngine` -> `effect/unstable/cluster/ClusterWorkflowEngine` +- `@effect/cluster/DeliverAt` -> `effect/unstable/cluster/DeliverAt` +- `@effect/cluster/Entity` -> `effect/unstable/cluster/Entity` +- `@effect/cluster/EntityAddress` -> `effect/unstable/cluster/EntityAddress` +- `@effect/cluster/EntityId` -> `effect/unstable/cluster/EntityId` +- `@effect/cluster/EntityProxy` -> `effect/unstable/cluster/EntityProxy` +- `@effect/cluster/EntityProxyServer` -> `effect/unstable/cluster/EntityProxyServer` +- `@effect/cluster/EntityResource` -> `effect/unstable/cluster/EntityResource` +- `@effect/cluster/EntityType` -> `effect/unstable/cluster/EntityType` +- `@effect/cluster/Envelope` -> `effect/unstable/cluster/Envelope` +- `@effect/cluster/HttpRunner` -> `effect/unstable/cluster/HttpRunner` +- `@effect/cluster/K8sHttpClient` -> `effect/unstable/cluster/K8sHttpClient` +- `@effect/cluster/MachineId` -> `effect/unstable/cluster/MachineId` +- `@effect/cluster/Message` -> `effect/unstable/cluster/Message` +- `@effect/cluster/MessageStorage` -> `effect/unstable/cluster/MessageStorage` +- `@effect/cluster/Reply` -> `effect/unstable/cluster/Reply` +- `@effect/cluster/Runner` -> `effect/unstable/cluster/Runner` +- `@effect/cluster/RunnerAddress` -> `effect/unstable/cluster/RunnerAddress` +- `@effect/cluster/RunnerHealth` -> `effect/unstable/cluster/RunnerHealth` +- `@effect/cluster/RunnerServer` -> `effect/unstable/cluster/RunnerServer` +- `@effect/cluster/RunnerStorage` -> `effect/unstable/cluster/RunnerStorage` +- `@effect/cluster/Runners` -> `effect/unstable/cluster/Runners` +- `@effect/cluster/ShardId` -> `effect/unstable/cluster/ShardId` +- `@effect/cluster/Sharding` -> `effect/unstable/cluster/Sharding` +- `@effect/cluster/ShardingConfig` -> `effect/unstable/cluster/ShardingConfig` +- `@effect/cluster/ShardingRegistrationEvent` -> `effect/unstable/cluster/ShardingRegistrationEvent` +- `@effect/cluster/SingleRunner` -> `effect/unstable/cluster/SingleRunner` +- `@effect/cluster/Singleton` -> `effect/unstable/cluster/Singleton` +- `@effect/cluster/SingletonAddress` -> `effect/unstable/cluster/SingletonAddress` +- `@effect/cluster/Snowflake` -> `effect/unstable/cluster/Snowflake` +- `@effect/cluster/SocketRunner` -> `effect/unstable/cluster/SocketRunner` +- `@effect/cluster/SqlMessageStorage` -> `effect/unstable/cluster/SqlMessageStorage` +- `@effect/cluster/SqlRunnerStorage` -> `effect/unstable/cluster/SqlRunnerStorage` +- `@effect/cluster/TestRunner` -> `effect/unstable/cluster/TestRunner` +- `@effect/cluster/index` -> `effect/unstable/cluster`: The package barrel was removed; import the same namespaces from the effect/unstable/cluster barrel or import specific modules directly. +- `@effect/experimental` -> `none`: The @effect/experimental package was folded into the effect package, split across effect/unstable/\* (devtools, eventlog, persistence, reactivity, ...); follow the Import Map for each module. +- `@effect/experimental/DevTools` -> `effect/unstable/devtools/DevTools` +- `@effect/experimental/DevTools/Client` -> `effect/unstable/devtools/DevToolsClient` +- `@effect/experimental/DevTools/Domain` -> `effect/unstable/devtools/DevToolsSchema` +- `@effect/experimental/DevTools/Server` -> `effect/unstable/devtools/DevToolsServer` +- `@effect/experimental/Event` -> `effect/unstable/eventlog/Event` +- `@effect/experimental/EventGroup` -> `effect/unstable/eventlog/EventGroup` +- `@effect/experimental/EventJournal` -> `effect/unstable/eventlog/EventJournal` +- `@effect/experimental/EventLog` -> `effect/unstable/eventlog/EventLog` +- `@effect/experimental/EventLogEncryption` -> `effect/unstable/eventlog/EventLogEncryption` +- `@effect/experimental/EventLogRemote` -> `effect/unstable/eventlog/EventLogMessage`, `effect/unstable/eventlog/EventLogRemote` +- `@effect/experimental/EventLogServer` -> `effect/unstable/eventlog/EventLogServer`, `effect/unstable/eventlog/EventLogServerEncrypted` +- `@effect/experimental/EventLogServer/Cloudflare` -> `none`: The Cloudflare adapter was not ported; combine EventLogServerEncrypted.layer with a custom Durable Object RpcServer.Protocol adapter. +- `@effect/experimental/Machine` -> `none`: The experimental local Machine actor runtime, model, boot process, constructors, brands, and serializable variants were not ported to v4. Redesign request contracts with Rpc/RpcGroup and choose Cluster Entity, Workflow, or a local actor built from Queue, Ref, PubSub, and scoped fibers according to the required semantics; ClusterWorkflowEngine is a different durable Workflow abstraction. For serializable actors, define schemas with Rpc/RpcGroup and choose Cluster Entity or Workflow explicitly. Context and initialization helpers (including the serializable initialization contract), input/private/public/state extractors, and the Machine-specific handler context were also removed, so request handling and state management must be explicit. Use ordinary Effect tracing controls and Effect.retry instead of the removed Machine-specific wrappers; its defect wrapper was also removed. Snapshot restoration was not ported, so implement persistence explicitly for the replacement architecture. +- `@effect/experimental/Machine/Procedure` -> `none`: The stateful Machine Procedure model, its serializable variant and guard, and both Procedure brands were not ported to v4. Define request contracts with Rpc (using schemas for serializable procedures) and implement state handling in an explicit actor architecture, because Rpc provides only the request contract. The handler context, context and request extractors, and no-reply sentinel were removed; use the corresponding Rpc request types after redesigning the contract. Replace the removed tagged-request base and helpers with schema-backed Rpc requests and Rpc helper types where appropriate. +- `@effect/experimental/Machine/ProcedureList` -> `none`: The stateful Machine ProcedureList abstraction and brand were not ported to v4. RpcGroup is the closest protocol collection for its schema-backed operations, but it has no initial state or public/private visibility split. Implement state handling and initialization in the replacement actor or workflow, and enforce visibility in that architecture. +- `@effect/experimental/Machine/SerializableProcedureList` -> `none`: The serializable stateful ProcedureList abstraction was not ported to v4. RpcGroup is the closest protocol collection for its schema-backed operations, but it has no initial state or public/private visibility split. Implement state handling and initialization in the replacement actor or workflow, and enforce visibility in that architecture. +- `@effect/experimental/PersistedCache` -> `effect/unstable/persistence/PersistedCache` +- `@effect/experimental/PersistedQueue` -> `effect/unstable/persistence/PersistedQueue` +- `@effect/experimental/PersistedQueue/Redis`: No single module replacement; follow the curated per-API guidance below. +- `@effect/experimental/Persistence` -> `effect/unstable/persistence/Persistable`, `effect/unstable/persistence/Persistence` +- `@effect/experimental/Persistence/Lmdb` -> `none`: The LMDB backend was not ported; implement a custom BackingPersistence layer or use a supported Kvs, Redis, or SQL backend. +- `@effect/experimental/Persistence/Redis`: No single module replacement; follow the curated per-API guidance below. +- `@effect/experimental/RateLimiter` -> `effect/unstable/persistence/RateLimiter` +- `@effect/experimental/RateLimiter/Redis`: No single module replacement; follow the curated per-API guidance below. +- `@effect/experimental/Reactivity` -> `effect/unstable/reactivity/Reactivity` +- `@effect/experimental/RequestResolver`: No single module replacement; follow the curated per-API guidance below. +- `@effect/experimental/Sse` -> `effect/unstable/encoding/Sse` +- `@effect/experimental/VariantSchema` -> `effect/unstable/schema/VariantSchema` +- `@effect/experimental/index` -> `none`: The package barrel was removed along with the package; import each module from its new effect/unstable/\* location per the Import Map. +- `@effect/opentelemetry/Logger`: No single module replacement; follow the curated per-API guidance below. +- `@effect/opentelemetry/Metrics`: No single module replacement; follow the curated per-API guidance below. +- `@effect/opentelemetry/Otlp` -> `effect/unstable/observability/Otlp` +- `@effect/opentelemetry/OtlpLogger` -> `effect/unstable/observability/OtlpLogger` +- `@effect/opentelemetry/OtlpMetrics` -> `effect/unstable/observability/OtlpMetrics` +- `@effect/opentelemetry/OtlpResource` -> `effect/unstable/observability/OtlpResource` +- `@effect/opentelemetry/OtlpSerialization` -> `effect/unstable/observability/OtlpSerialization` +- `@effect/opentelemetry/OtlpTracer` -> `effect/unstable/observability/OtlpTracer` +- `@effect/opentelemetry/Tracer`: No single module replacement; follow the curated per-API guidance below. +- `@effect/opentelemetry/index` -> `@effect/opentelemetry`: The explicit /index entrypoint was removed; import the same namespaces from the @effect/opentelemetry package root or import specific modules directly. +- `@effect/platform` -> `none`: The @effect/platform package was folded into the effect package: core services live in effect root modules (e.g. effect/FileSystem, effect/Path) and HTTP in effect/unstable/http; follow the Import Map for each module. +- `@effect/platform-browser/index` -> `@effect/platform-browser`: The explicit /index entrypoint was removed; import the same namespaces from the @effect/platform-browser package root or import specific modules directly. +- `@effect/platform-bun/BunCommandExecutor`: No single module replacement; follow the curated per-API guidance below. +- `@effect/platform-bun/BunContext`: No single module replacement; follow the curated per-API guidance below. +- `@effect/platform-bun/BunFileSystem/ParcelWatcher`: No single module replacement; follow the curated per-API guidance below. +- `@effect/platform-bun/BunKeyValueStore` -> `effect/unstable/persistence/KeyValueStore`: layerFileSystem is now platform-neutral as KeyValueStore.layerFileSystem(directory); provide FileSystem and Path via BunServices.layer or BunFileSystem.layer with BunPath.layer. +- `@effect/platform-bun/index` -> `@effect/platform-bun`: The explicit /index entrypoint was removed; import the same namespaces from the @effect/platform-bun package root or import specific modules directly. +- `@effect/platform-node-shared/NodeCommandExecutor`: No single module replacement; follow the curated per-API guidance below. +- `@effect/platform-node-shared/NodeFileSystem/ParcelWatcher`: No single module replacement; follow the curated per-API guidance below. +- `@effect/platform-node-shared/NodeKeyValueStore` -> `effect/unstable/persistence/KeyValueStore`: layerFileSystem is now platform-neutral as KeyValueStore.layerFileSystem(directory); provide FileSystem and Path via NodeServices.layer or NodeFileSystem.layer with NodePath.layer. +- `@effect/platform-node-shared/NodeMultipart`: No single module replacement; follow the curated per-API guidance below. +- `@effect/platform-node/NodeCommandExecutor`: No single module replacement; follow the curated per-API guidance below. +- `@effect/platform-node/NodeContext`: No single module replacement; follow the curated per-API guidance below. +- `@effect/platform-node/NodeFileSystem/ParcelWatcher`: No single module replacement; follow the curated per-API guidance below. +- `@effect/platform-node/NodeKeyValueStore` -> `effect/unstable/persistence/KeyValueStore`: layerFileSystem is now platform-neutral as KeyValueStore.layerFileSystem(directory); provide FileSystem and Path via NodeServices.layer or NodeFileSystem.layer with NodePath.layer. +- `@effect/platform-node/index` -> `@effect/platform-node`: The explicit /index entrypoint was removed; import 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. -```text -Effect.async -> Effect.callback -Effect.zipRight -> Effect.andThen -Effect.zipLeft -> Effect.tap -Effect.either -> Effect.result -Effect.catchAll -> Effect.catch -Effect.catchAllCause -> Effect.catchCause -Effect.catchAllDefect -> Effect.catchDefect -Effect.catchSome -> Effect.catchIf -Effect.catchIf -> Effect.catchIf -Effect.optionFromOptional -> Effect.catchNoSuchElement -Effect.catchSomeCause -> Effect.catchCauseIf -Effect.tapErrorCause -> Effect.tapCause -Effect.ignoreLogged -> Effect.ignore -Effect.makeLatchUnsafe -> Latch.makeUnsafe -Effect.makeLatch -> Latch.make -Layer.scoped -> Layer.effect -Layer.scopedDiscard -> Layer.effectDiscard -Layer.tapErrorCause -> Layer.tapCause -Mailbox -> Queue.Queue -Mailbox.make -> Queue.make -Either -> Result.Result -Either.right -> Result.succeed -Either.left -> Result.fail -Scope.extend -> Scope.provide -Effect.makeSemaphoreUnsafe -> Semaphore.makeUnsafe -Effect.makeSemaphore -> Semaphore.make -Stream.Context -> Stream.Services -StreamHaltStrategy.HaltStrategy -> Stream.HaltStrategy -Stream.repeatEffect -> Stream.fromEffectRepeat -Stream.repeatEffectWithSchedule -> Stream.fromEffectSchedule -Stream.async -> Stream.callback -Stream.asyncEffect -> Stream.callback -Stream.asyncPush -> Stream.callback -Stream.asyncScoped -> Stream.callback -Stream.repeatEffectChunk -> Stream.fromIterableEffectRepeat -Stream.fromChunk -> Stream.fromArray -Stream.fromChunks -> Stream.fromArrays -Stream.mapChunks -> Stream.mapArray -Stream.mapChunksEffect -> Stream.mapArrayEffect -Stream.either -> Stream.result -Stream.flattenChunks -> Stream.flattenArray -Stream.flattenIterables -> Stream.flattenIterable -Stream.mergeEither -> Stream.mergeResult -Stream.zipWithChunks -> Stream.zipWithArray -Stream.bufferChunks -> Stream.bufferArray -Stream.catchAllCause -> Stream.catchCause -Stream.tapErrorCause -> Stream.tapCause -Stream.catchAll -> Stream.catch -Stream.catchSome -> Stream.catchIf -Stream.catchSomeCause -> Stream.catchCauseIf -Stream.combineChunks -> Stream.combineArray -provideSomeLayer -> Stream.provide -provideSomeContext -> Stream.provide -``` +## API Reference + +### `@effect/ai-anthropic/AnthropicClient` + +- `AnthropicClient.CitationsDelta` -> `Generated.BetaCitationsDelta`: The client-local stream schema moved to the regenerated v4 Anthropic schema surface; re-check its Type/Encoded shape. + +- `AnthropicClient.ContentBlockDeltaEvent` -> `Generated.BetaContentBlockDeltaEvent`: The client-local stream schema moved to the regenerated v4 Anthropic schema surface; re-check its Type/Encoded shape. + +- `AnthropicClient.ContentBlockStartEvent` -> `Generated.BetaContentBlockStartEvent`: The client-local stream schema moved to the regenerated v4 Anthropic schema surface; re-check its Type/Encoded shape. + +- `AnthropicClient.ContentBlockStopEvent` -> `Generated.BetaContentBlockStopEvent`: The client-local stream schema moved to the regenerated v4 Anthropic schema surface; re-check its Type/Encoded shape. + +- `AnthropicClient.ErrorEvent` -> `Generated.BetaErrorResponse`: The client-local stream error schema moved to the regenerated v4 Anthropic schema surface; re-check its Type/Encoded shape. + +- `AnthropicClient.InputJsonContentBlockDelta` -> `Generated.BetaInputJsonContentBlockDelta`: The client-local stream schema moved to the regenerated v4 Anthropic schema surface; re-check its Type/Encoded shape. + +- `AnthropicClient.MessageDelta` -> `Generated.BetaMessageDelta`: The client-local stream schema moved to the regenerated v4 Anthropic schema surface; re-check its Type/Encoded shape. + +- `AnthropicClient.MessageDeltaEvent` -> `Generated.BetaMessageDeltaEvent`: The client-local stream schema moved to the regenerated v4 Anthropic schema surface; re-check its Type/Encoded shape. + +- `AnthropicClient.MessageDeltaUsage` -> `typeof Generated.BetaMessageDeltaEvent.Type["usage"]`: The standalone usage schema was inlined into the regenerated v4 message-delta event. + +- `AnthropicClient.MessageStartEvent` -> `Generated.BetaMessageStartEvent`: The client-local stream schema moved to the regenerated v4 Anthropic schema surface; re-check its Type/Encoded shape. + +- `AnthropicClient.MessageStopEvent` -> `Generated.BetaMessageStopEvent`: The client-local stream schema moved to the regenerated v4 Anthropic schema surface; re-check its Type/Encoded shape. + +- `AnthropicClient.MessageStreamEvent` -> `AnthropicClient.MessageStreamEvent`: Still exported in v4 as a type union of generated beta stream events; adapt to the revised client stream contract. + +- `AnthropicClient.PingEvent` -> `none`: The v4 client consumes ping events internally and filters them from MessageStreamEvent, so no public ping schema is needed. + +- `AnthropicClient.ServerToolUsage` -> `Generated.BetaServerToolUsage`: The client-local usage schema moved to the regenerated v4 Anthropic schema surface; re-check its Type/Encoded shape. + +- `AnthropicClient.Service` -> `AnthropicClient.Service`: Still exported in v4; adapt to the revised generated client, streamRequest, and message response contracts. + +- `AnthropicClient.SignatureContentBlockDelta` -> `Generated.BetaSignatureContentBlockDelta`: The client-local stream schema moved to the regenerated v4 Anthropic schema surface; re-check its Type/Encoded shape. + +- `AnthropicClient.TextContentBlockDelta` -> `Generated.BetaTextContentBlockDelta`: The client-local stream schema moved to the regenerated v4 Anthropic schema surface; re-check its Type/Encoded shape. + +- `AnthropicClient.ThinkingContentBlockDelta` -> `Generated.BetaThinkingContentBlockDelta`: The client-local stream schema moved to the regenerated v4 Anthropic schema surface; re-check its Type/Encoded shape. + +### `@effect/ai-anthropic/AnthropicConfig` + +- `AnthropicConfig.AnthropicConfig` -> `AnthropicConfig.AnthropicConfig`: Still exported in v4; update imports and adapt to the revised v4 service and HTTP client types. + +- `AnthropicConfig.AnthropicConfig.Service` -> `AnthropicConfig.AnthropicConfig.Service`: Still exported in v4; update imports and adapt to the revised v4 service and HTTP client types. + +### `@effect/ai-anthropic/AnthropicLanguageModel` + +- `AnthropicLanguageModel.AnthropicReasoningInfo` -> `Prompt.ReasoningPartOptions / Response reasoning metadata`: The standalone reasoning-info union was removed; v4 declares Anthropic thinking and redacted-thinking data directly on Prompt and Response provider metadata. + +- `AnthropicLanguageModel.AnthropicTools` -> `AnthropicLanguageModel.AnthropicUserDefinedTool | AnthropicLanguageModel.AnthropicProviderDefinedTool`: The old combined tool union was split into explicit user-defined and provider-defined Anthropic request tool types. + +- `AnthropicLanguageModel.Config` -> `AnthropicLanguageModel.Config`: Still exported in v4; update imports and adapt to the revised Messages API request fields. + +- `AnthropicLanguageModel.Config.Service` -> `AnthropicLanguageModel.Config.Service`: Still exported in v4; update imports and adapt to the revised Messages API request fields. + +- `AnthropicLanguageModel.layerWithTokenizer` -> `AnthropicLanguageModel.layer`: The tokenizer-combining layer was removed; provide the language model and any Tokenizer service separately. + +- `AnthropicLanguageModel.modelWithTokenizer` -> `AnthropicLanguageModel.model`: The tokenizer-combining model was removed; use the v4 model descriptor and provide any Tokenizer service separately. + +- `AnthropicLanguageModel.prepareTools` -> `none`: Tool conversion became an internal part of the v4 Anthropic language model; use AnthropicTool constructors and pass tools through LanguageModel provider options instead. + +### `@effect/ai-anthropic/AnthropicTokenizer` + +- `AnthropicTokenizer.layer` -> `Tokenizer.make`: The provider-specific tokenizer module was removed; build and provide an effect/unstable/ai/Tokenizer service explicitly, using @anthropic-ai/tokenizer if equivalent Anthropic counting is required. + +- `AnthropicTokenizer.make` -> `Tokenizer.make`: The provider-specific tokenizer module was removed; build and provide an effect/unstable/ai/Tokenizer service explicitly, using @anthropic-ai/tokenizer if equivalent Anthropic counting is required. + +### `@effect/ai-anthropic/AnthropicTool` + +- `AnthropicTool.ProviderDefinedTools` -> `AnthropicTool.AnthropicTool`: The provider-defined schema union was replaced by the union of v4 Anthropic provider tool constructor return types. + +- `AnthropicTool.getProviderDefinedToolName` -> `Tool.NameMapper`: The Anthropic-specific name lookup was removed; v4 provider tools carry custom and provider names through the shared Tool.NameMapper. + +### `@effect/ai-anthropic/Generated` + +- `Generated.APIError` -> `Generated.APIError`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.AuthenticationError` -> `Generated.AuthenticationError`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.Base64ImageSource` -> `Generated.Base64ImageSource`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.Base64ImageSourceMediaType` -> `none`: Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.Base64PDFSource` -> `Generated.Base64PDFSource`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BashTool20250124` -> `Generated.BashTool_20250124`: Regenerated in v4 under this name; update the identifier and re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaAPIError` -> `Generated.BetaAPIError`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaAuthenticationError` -> `Generated.BetaAuthenticationError`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaBase64ImageSource` -> `Generated.BetaBase64ImageSource`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaBase64ImageSourceMediaType` -> `none`: Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.BetaBase64PDFSource` -> `Generated.BetaBase64PDFSource`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaBashCodeExecutionToolResultErrorCode` -> `Generated.BetaBashCodeExecutionToolResultErrorCode`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaBashTool20241022` -> `Generated.BetaBashTool_20241022`: Regenerated in v4 under this name; update the identifier and re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaBashTool20250124` -> `Generated.BetaBashTool_20250124`: Regenerated in v4 under this name; update the identifier and re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaBillingError` -> `Generated.BetaBillingError`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaBodyCreateSkillV1SkillsPost` -> `Generated.BetaBody_create_skill_v1_skills_post`: Regenerated in v4 under this name; update the identifier and re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaBodyCreateSkillVersionV1SkillsSkillIdVersionsPost` -> `Generated.BetaBody_create_skill_version_v1_skills__skill_id__versions_post`: Regenerated in v4 under this name; update the identifier and re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaCacheControlEphemeral` -> `Generated.BetaCacheControlEphemeral`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaCacheControlEphemeralTtl` -> `none`: Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.BetaCacheCreation` -> `Generated.BetaCacheCreation`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaClearToolUses20250919` -> `Generated.BetaClearToolUses20250919`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaCodeExecutionTool20250522` -> `Generated.BetaCodeExecutionTool_20250522`: Regenerated in v4 under this name; update the identifier and re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaCodeExecutionTool20250825` -> `Generated.BetaCodeExecutionTool_20250825`: Regenerated in v4 under this name; update the identifier and re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaCodeExecutionToolResultErrorCode` -> `Generated.BetaCodeExecutionToolResultErrorCode`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaComputerUseTool20241022` -> `Generated.BetaComputerUseTool_20241022`: Regenerated in v4 under this name; update the identifier and re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaComputerUseTool20250124` -> `Generated.BetaComputerUseTool_20250124`: Regenerated in v4 under this name; update the identifier and re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaContainer` -> `Generated.BetaContainer`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaContainerParams` -> `Generated.BetaContainerParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaContentBlock` -> `Generated.BetaContentBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaContentBlockSource` -> `Generated.BetaContentBlockSource`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaContextManagementConfig` -> `Generated.BetaContextManagementConfig`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaContextManagementResponse` -> `Generated.BetaContextManagementResponse`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaCountMessageTokensParams` -> `Generated.BetaCountMessageTokensParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaCountMessageTokensResponse` -> `Generated.BetaCountMessageTokensResponse`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaCreateMessageBatchParams` -> `Generated.BetaCreateMessageBatchParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaCreateMessageParams` -> `Generated.BetaCreateMessageParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaCreateMessageParamsServiceTier` -> `none`: Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.BetaCreateSkillResponse` -> `Generated.BetaCreateSkillResponse`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaCreateSkillV1SkillsPostParams` -> `Generated.BetaCreateSkillV1SkillsPostParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaCreateSkillVersionResponse` -> `Generated.BetaCreateSkillVersionResponse`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaCreateSkillVersionV1SkillsSkillIdVersionsPostParams` -> `Generated.BetaCreateSkillVersionV1SkillsSkillIdVersionsPostParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaDeleteFileV1FilesFileIdDeleteParams` -> `Generated.BetaDeleteFileV1FilesFileIdDeleteParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaDeleteMessageBatchResponse` -> `Generated.BetaDeleteMessageBatchResponse`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaDeleteSkillResponse` -> `Generated.BetaDeleteSkillResponse`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaDeleteSkillV1SkillsSkillIdDeleteParams` -> `Generated.BetaDeleteSkillV1SkillsSkillIdDeleteParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaDeleteSkillVersionResponse` -> `Generated.BetaDeleteSkillVersionResponse`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaDeleteSkillVersionV1SkillsSkillIdVersionsVersionDeleteParams` -> `Generated.BetaDeleteSkillVersionV1SkillsSkillIdVersionsVersionDeleteParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaDownloadFileV1FilesFileIdContentGetParams` -> `Generated.BetaDownloadFileV1FilesFileIdContentGetParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaErrorResponse` -> `Generated.BetaErrorResponse`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaFileDeleteResponse` -> `Generated.BetaFileDeleteResponse`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaFileDocumentSource` -> `Generated.BetaFileDocumentSource`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaFileImageSource` -> `Generated.BetaFileImageSource`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaFileListResponse` -> `Generated.BetaFileListResponse`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaFileMetadataSchema` -> `Generated.BetaFileMetadataSchema`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaGatewayTimeoutError` -> `Generated.BetaGatewayTimeoutError`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaGetFileMetadataV1FilesFileIdGetParams` -> `Generated.BetaGetFileMetadataV1FilesFileIdGetParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaGetSkillResponse` -> `Generated.BetaGetSkillResponse`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaGetSkillV1SkillsSkillIdGetParams` -> `Generated.BetaGetSkillV1SkillsSkillIdGetParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaGetSkillVersionResponse` -> `Generated.BetaGetSkillVersionResponse`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaGetSkillVersionV1SkillsSkillIdVersionsVersionGetParams` -> `Generated.BetaGetSkillVersionV1SkillsSkillIdVersionsVersionGetParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaInputContentBlock` -> `Generated.BetaInputContentBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaInputMessage` -> `Generated.BetaInputMessage`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaInputMessageRole` -> `none`: Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.BetaInputSchema` -> `none`: Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.BetaInputTokensClearAtLeast` -> `Generated.BetaInputTokensClearAtLeast`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaInputTokensTrigger` -> `Generated.BetaInputTokensTrigger`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaInvalidRequestError` -> `Generated.BetaInvalidRequestError`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaListFilesV1FilesGetParams` -> `Generated.BetaListFilesV1FilesGetParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaListResponseMessageBatch` -> `Generated.BetaListResponse_MessageBatch_`: Regenerated in v4 under this name; update the identifier and re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaListResponseModelInfo` -> `Generated.BetaListResponse_ModelInfo_`: Regenerated in v4 under this name; update the identifier and re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaListSkillVersionsResponse` -> `Generated.BetaListSkillVersionsResponse`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaListSkillVersionsV1SkillsSkillIdVersionsGetParams` -> `Generated.BetaListSkillVersionsV1SkillsSkillIdVersionsGetParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaListSkillsResponse` -> `Generated.BetaListSkillsResponse`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaListSkillsV1SkillsGetParams` -> `Generated.BetaListSkillsV1SkillsGetParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaMemoryTool20250818` -> `Generated.BetaMemoryTool_20250818`: Regenerated in v4 under this name; update the identifier and re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaMessage` -> `Generated.BetaMessage`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaMessageBatch` -> `Generated.BetaMessageBatch`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaMessageBatchIndividualRequestParams` -> `Generated.BetaMessageBatchIndividualRequestParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaMessageBatchProcessingStatus` -> `none`: Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.BetaMessageBatchesCancelParams` -> `Generated.BetaMessageBatchesCancelParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaMessageBatchesDeleteParams` -> `Generated.BetaMessageBatchesDeleteParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaMessageBatchesListParams` -> `Generated.BetaMessageBatchesListParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaMessageBatchesPostParams` -> `Generated.BetaMessageBatchesPostParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaMessageBatchesResultsParams` -> `Generated.BetaMessageBatchesResultsParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaMessageBatchesRetrieveParams` -> `Generated.BetaMessageBatchesRetrieveParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaMessagesCountTokensPostParams` -> `Generated.BetaMessagesCountTokensPostParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaMessagesPostParams` -> `Generated.BetaMessagesPostParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaMetadata` -> `none`: Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.BetaModelInfo` -> `Generated.BetaModelInfo`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaModelsGetParams` -> `Generated.BetaModelsGetParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaModelsListParams` -> `Generated.BetaModelsListParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaNotFoundError` -> `Generated.BetaNotFoundError`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaOverloadedError` -> `Generated.BetaOverloadedError`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaPermissionError` -> `Generated.BetaPermissionError`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaPlainTextSource` -> `Generated.BetaPlainTextSource`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaRateLimitError` -> `Generated.BetaRateLimitError`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaRequestBashCodeExecutionOutputBlock` -> `Generated.BetaRequestBashCodeExecutionOutputBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaRequestBashCodeExecutionResultBlock` -> `Generated.BetaRequestBashCodeExecutionResultBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaRequestBashCodeExecutionToolResultBlock` -> `Generated.BetaRequestBashCodeExecutionToolResultBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaRequestBashCodeExecutionToolResultError` -> `Generated.BetaRequestBashCodeExecutionToolResultError`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaRequestCharLocationCitation` -> `Generated.BetaRequestCharLocationCitation`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaRequestCitationsConfig` -> `Generated.BetaRequestCitationsConfig`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaRequestCodeExecutionOutputBlock` -> `Generated.BetaRequestCodeExecutionOutputBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaRequestCodeExecutionResultBlock` -> `Generated.BetaRequestCodeExecutionResultBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaRequestCodeExecutionToolResultBlock` -> `Generated.BetaRequestCodeExecutionToolResultBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaRequestCodeExecutionToolResultError` -> `Generated.BetaRequestCodeExecutionToolResultError`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaRequestContainerUploadBlock` -> `Generated.BetaRequestContainerUploadBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaRequestContentBlockLocationCitation` -> `Generated.BetaRequestContentBlockLocationCitation`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaRequestCounts` -> `none`: Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.BetaRequestDocumentBlock` -> `Generated.BetaRequestDocumentBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaRequestImageBlock` -> `Generated.BetaRequestImageBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaRequestMCPServerToolConfiguration` -> `Generated.BetaRequestMCPServerToolConfiguration`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaRequestMCPServerURLDefinition` -> `Generated.BetaRequestMCPServerURLDefinition`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaRequestMCPToolResultBlock` -> `Generated.BetaRequestMCPToolResultBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaRequestMCPToolUseBlock` -> `Generated.BetaRequestMCPToolUseBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaRequestPageLocationCitation` -> `Generated.BetaRequestPageLocationCitation`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaRequestRedactedThinkingBlock` -> `none`: Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.BetaRequestSearchResultBlock` -> `Generated.BetaRequestSearchResultBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaRequestSearchResultLocationCitation` -> `Generated.BetaRequestSearchResultLocationCitation`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaRequestServerToolUseBlock` -> `Generated.BetaRequestServerToolUseBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaRequestServerToolUseBlockName` -> `none`: Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.BetaRequestTextBlock` -> `Generated.BetaRequestTextBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaRequestTextEditorCodeExecutionCreateResultBlock` -> `Generated.BetaRequestTextEditorCodeExecutionCreateResultBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaRequestTextEditorCodeExecutionStrReplaceResultBlock` -> `Generated.BetaRequestTextEditorCodeExecutionStrReplaceResultBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaRequestTextEditorCodeExecutionToolResultBlock` -> `Generated.BetaRequestTextEditorCodeExecutionToolResultBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaRequestTextEditorCodeExecutionToolResultError` -> `Generated.BetaRequestTextEditorCodeExecutionToolResultError`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaRequestTextEditorCodeExecutionViewResultBlock` -> `Generated.BetaRequestTextEditorCodeExecutionViewResultBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaRequestTextEditorCodeExecutionViewResultBlockFileType` -> `none`: Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.BetaRequestThinkingBlock` -> `none`: Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.BetaRequestToolResultBlock` -> `none`: Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.BetaRequestToolUseBlock` -> `none`: Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.BetaRequestWebFetchResultBlock` -> `Generated.BetaRequestWebFetchResultBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaRequestWebFetchToolResultBlock` -> `Generated.BetaRequestWebFetchToolResultBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaRequestWebFetchToolResultError` -> `Generated.BetaRequestWebFetchToolResultError`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaRequestWebSearchResultBlock` -> `Generated.BetaRequestWebSearchResultBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaRequestWebSearchResultLocationCitation` -> `Generated.BetaRequestWebSearchResultLocationCitation`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaRequestWebSearchToolResultBlock` -> `Generated.BetaRequestWebSearchToolResultBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaRequestWebSearchToolResultError` -> `Generated.BetaRequestWebSearchToolResultError`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaResponseBashCodeExecutionOutputBlock` -> `Generated.BetaResponseBashCodeExecutionOutputBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaResponseBashCodeExecutionResultBlock` -> `Generated.BetaResponseBashCodeExecutionResultBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaResponseBashCodeExecutionToolResultBlock` -> `Generated.BetaResponseBashCodeExecutionToolResultBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaResponseBashCodeExecutionToolResultError` -> `Generated.BetaResponseBashCodeExecutionToolResultError`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaResponseCharLocationCitation` -> `Generated.BetaResponseCharLocationCitation`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaResponseCitationsConfig` -> `Generated.BetaResponseCitationsConfig`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaResponseClearToolUses20250919Edit` -> `Generated.BetaResponseClearToolUses20250919Edit`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaResponseCodeExecutionOutputBlock` -> `Generated.BetaResponseCodeExecutionOutputBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaResponseCodeExecutionResultBlock` -> `Generated.BetaResponseCodeExecutionResultBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaResponseCodeExecutionToolResultBlock` -> `Generated.BetaResponseCodeExecutionToolResultBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaResponseCodeExecutionToolResultError` -> `Generated.BetaResponseCodeExecutionToolResultError`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaResponseContainerUploadBlock` -> `Generated.BetaResponseContainerUploadBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaResponseContentBlockLocationCitation` -> `Generated.BetaResponseContentBlockLocationCitation`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaResponseContextManagement` -> `Generated.BetaResponseContextManagement`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaResponseDocumentBlock` -> `Generated.BetaResponseDocumentBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaResponseMCPToolResultBlock` -> `Generated.BetaResponseMCPToolResultBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaResponseMCPToolUseBlock` -> `Generated.BetaResponseMCPToolUseBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaResponsePageLocationCitation` -> `Generated.BetaResponsePageLocationCitation`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaResponseRedactedThinkingBlock` -> `Generated.BetaResponseRedactedThinkingBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaResponseSearchResultLocationCitation` -> `Generated.BetaResponseSearchResultLocationCitation`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaResponseServerToolUseBlock` -> `Generated.BetaResponseServerToolUseBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaResponseServerToolUseBlockName` -> `none`: Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.BetaResponseTextBlock` -> `Generated.BetaResponseTextBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaResponseTextEditorCodeExecutionCreateResultBlock` -> `Generated.BetaResponseTextEditorCodeExecutionCreateResultBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaResponseTextEditorCodeExecutionStrReplaceResultBlock` -> `Generated.BetaResponseTextEditorCodeExecutionStrReplaceResultBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaResponseTextEditorCodeExecutionToolResultBlock` -> `Generated.BetaResponseTextEditorCodeExecutionToolResultBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaResponseTextEditorCodeExecutionToolResultError` -> `Generated.BetaResponseTextEditorCodeExecutionToolResultError`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaResponseTextEditorCodeExecutionViewResultBlock` -> `Generated.BetaResponseTextEditorCodeExecutionViewResultBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaResponseTextEditorCodeExecutionViewResultBlockFileType` -> `none`: Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.BetaResponseThinkingBlock` -> `Generated.BetaResponseThinkingBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaResponseToolUseBlock` -> `Generated.BetaResponseToolUseBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaResponseWebFetchResultBlock` -> `Generated.BetaResponseWebFetchResultBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaResponseWebFetchToolResultBlock` -> `Generated.BetaResponseWebFetchToolResultBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaResponseWebFetchToolResultError` -> `Generated.BetaResponseWebFetchToolResultError`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaResponseWebSearchResultBlock` -> `Generated.BetaResponseWebSearchResultBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaResponseWebSearchResultLocationCitation` -> `Generated.BetaResponseWebSearchResultLocationCitation`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaResponseWebSearchToolResultBlock` -> `Generated.BetaResponseWebSearchToolResultBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaResponseWebSearchToolResultError` -> `Generated.BetaResponseWebSearchToolResultError`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaServerToolUsage` -> `Generated.BetaServerToolUsage`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaSkill` -> `Generated.BetaSkill`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaSkillParams` -> `Generated.BetaSkillParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaSkillParamsType` -> `none`: Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.BetaSkillType` -> `none`: Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.BetaSkillVersion` -> `Generated.BetaSkillVersion`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaStopReason` -> `Generated.BetaStopReason`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaTextEditor20241022` -> `Generated.BetaTextEditor_20241022`: Regenerated in v4 under this name; update the identifier and re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaTextEditor20250124` -> `Generated.BetaTextEditor_20250124`: Regenerated in v4 under this name; update the identifier and re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaTextEditor20250429` -> `Generated.BetaTextEditor_20250429`: Regenerated in v4 under this name; update the identifier and re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaTextEditor20250728` -> `Generated.BetaTextEditor_20250728`: Regenerated in v4 under this name; update the identifier and re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaTextEditorCodeExecutionToolResultErrorCode` -> `Generated.BetaTextEditorCodeExecutionToolResultErrorCode`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaThinkingConfigDisabled` -> `Generated.BetaThinkingConfigDisabled`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaThinkingConfigEnabled` -> `Generated.BetaThinkingConfigEnabled`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaThinkingConfigParam` -> `Generated.BetaThinkingConfigParam`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaTool` -> `Generated.BetaTool`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaToolChoice` -> `Generated.BetaToolChoice`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaToolChoiceAny` -> `Generated.BetaToolChoiceAny`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaToolChoiceAuto` -> `Generated.BetaToolChoiceAuto`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaToolChoiceNone` -> `Generated.BetaToolChoiceNone`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaToolChoiceTool` -> `Generated.BetaToolChoiceTool`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaToolUsesKeep` -> `Generated.BetaToolUsesKeep`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaToolUsesTrigger` -> `Generated.BetaToolUsesTrigger`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaURLImageSource` -> `Generated.BetaURLImageSource`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaURLPDFSource` -> `Generated.BetaURLPDFSource`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaUploadFileV1FilesPostParams` -> `Generated.BetaUploadFileV1FilesPostParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaUploadFileV1FilesPostRequest` -> `Generated.BetaUploadFileV1FilesPostRequestFormData`: Regenerated in v4 under this name; update the identifier and re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaUsage` -> `none`: Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.BetaUsageServiceTierEnum` -> `none`: Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.BetaUserLocation` -> `Generated.BetaUserLocation`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaWebFetchTool20250910` -> `Generated.BetaWebFetchTool_20250910`: Regenerated in v4 under this name; update the identifier and re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaWebFetchToolResultErrorCode` -> `Generated.BetaWebFetchToolResultErrorCode`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaWebSearchTool20250305` -> `Generated.BetaWebSearchTool_20250305`: Regenerated in v4 under this name; update the identifier and re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaWebSearchToolResultErrorCode` -> `Generated.BetaWebSearchToolResultErrorCode`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaapiSchemasSkillsSkill` -> `Generated.Betaapi__schemas__skills__Skill`: Regenerated in v4 under this name; update the identifier and re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BillingError` -> `Generated.BillingError`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BodyCreateSkillV1SkillsPost` -> `Generated.Body_create_skill_v1_skills_post`: Regenerated in v4 under this name; update the identifier and re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BodyCreateSkillVersionV1SkillsSkillIdVersionsPost` -> `Generated.Body_create_skill_version_v1_skills__skill_id__versions_post`: Regenerated in v4 under this name; update the identifier and re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CacheControlEphemeral` -> `Generated.CacheControlEphemeral`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CacheControlEphemeralTtl` -> `none`: Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CacheCreation` -> `Generated.CacheCreation`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.Client` -> `Generated.AnthropicClient`: Regenerated in v4 under this name; update the identifier and re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ClientError` -> `Generated.AnthropicClientError`: Regenerated in v4 under this name; update the identifier and re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CompletePostParams` -> `Generated.CompletePostParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CompletionRequest` -> `Generated.CompletionRequest`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CompletionResponse` -> `Generated.CompletionResponse`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ContentBlock` -> `Generated.ContentBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ContentBlockSource` -> `Generated.ContentBlockSource`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CountMessageTokensParams` -> `Generated.CountMessageTokensParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CountMessageTokensResponse` -> `Generated.CountMessageTokensResponse`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateMessageBatchParams` -> `Generated.CreateMessageBatchParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateMessageParams` -> `Generated.CreateMessageParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateMessageParamsServiceTier` -> `none`: Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateSkillResponse` -> `Generated.CreateSkillResponse`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateSkillV1SkillsPostParams` -> `Generated.CreateSkillV1SkillsPostParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateSkillVersionResponse` -> `Generated.CreateSkillVersionResponse`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateSkillVersionV1SkillsSkillIdVersionsPostParams` -> `Generated.CreateSkillVersionV1SkillsSkillIdVersionsPostParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.DeleteFileV1FilesFileIdDeleteParams` -> `Generated.DeleteFileV1FilesFileIdDeleteParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.DeleteMessageBatchResponse` -> `Generated.DeleteMessageBatchResponse`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.DeleteSkillResponse` -> `Generated.DeleteSkillResponse`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.DeleteSkillV1SkillsSkillIdDeleteParams` -> `Generated.DeleteSkillV1SkillsSkillIdDeleteParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.DeleteSkillVersionResponse` -> `Generated.DeleteSkillVersionResponse`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.DeleteSkillVersionV1SkillsSkillIdVersionsVersionDeleteParams` -> `Generated.DeleteSkillVersionV1SkillsSkillIdVersionsVersionDeleteParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.DownloadFileV1FilesFileIdContentGetParams` -> `Generated.DownloadFileV1FilesFileIdContentGetParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ErrorResponse` -> `Generated.ErrorResponse`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.FileDeleteResponse` -> `Generated.FileDeleteResponse`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.FileListResponse` -> `Generated.FileListResponse`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.FileMetadataSchema` -> `Generated.FileMetadataSchema`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.GatewayTimeoutError` -> `Generated.GatewayTimeoutError`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.GetFileMetadataV1FilesFileIdGetParams` -> `Generated.GetFileMetadataV1FilesFileIdGetParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.GetSkillResponse` -> `Generated.GetSkillResponse`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.GetSkillV1SkillsSkillIdGetParams` -> `Generated.GetSkillV1SkillsSkillIdGetParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.GetSkillVersionResponse` -> `Generated.GetSkillVersionResponse`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.GetSkillVersionV1SkillsSkillIdVersionsVersionGetParams` -> `Generated.GetSkillVersionV1SkillsSkillIdVersionsVersionGetParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.InputContentBlock` -> `Generated.InputContentBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.InputMessage` -> `Generated.InputMessage`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.InputMessageRole` -> `none`: Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.InputSchema` -> `none`: Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.InvalidRequestError` -> `Generated.InvalidRequestError`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListFilesV1FilesGetParams` -> `Generated.ListFilesV1FilesGetParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListResponseMessageBatch` -> `Generated.ListResponse_MessageBatch_`: Regenerated in v4 under this name; update the identifier and re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListResponseModelInfo` -> `Generated.ListResponse_ModelInfo_`: Regenerated in v4 under this name; update the identifier and re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListSkillVersionsResponse` -> `Generated.ListSkillVersionsResponse`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListSkillVersionsV1SkillsSkillIdVersionsGetParams` -> `Generated.ListSkillVersionsV1SkillsSkillIdVersionsGetParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListSkillsResponse` -> `Generated.ListSkillsResponse`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListSkillsV1SkillsGetParams` -> `Generated.ListSkillsV1SkillsGetParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.Message` -> `Generated.Message`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.MessageBatch` -> `Generated.MessageBatch`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.MessageBatchIndividualRequestParams` -> `Generated.MessageBatchIndividualRequestParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.MessageBatchProcessingStatus` -> `none`: Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.MessageBatchesCancelParams` -> `Generated.MessageBatchesCancelParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.MessageBatchesDeleteParams` -> `Generated.MessageBatchesDeleteParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.MessageBatchesListParams` -> `Generated.MessageBatchesListParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.MessageBatchesPostParams` -> `Generated.MessageBatchesPostParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.MessageBatchesResultsParams` -> `Generated.MessageBatchesResultsParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.MessageBatchesRetrieveParams` -> `Generated.MessageBatchesRetrieveParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.MessagesCountTokensPostParams` -> `Generated.MessagesCountTokensPostParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.MessagesPostParams` -> `Generated.MessagesPostParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.Metadata` -> `none`: Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.Model` -> `Generated.Model`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ModelInfo` -> `Generated.ModelInfo`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ModelsGetParams` -> `Generated.ModelsGetParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ModelsListParams` -> `Generated.ModelsListParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.NotFoundError` -> `Generated.NotFoundError`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.OverloadedError` -> `Generated.OverloadedError`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.PermissionError` -> `Generated.PermissionError`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.PlainTextSource` -> `Generated.PlainTextSource`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RateLimitError` -> `Generated.RateLimitError`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RequestCharLocationCitation` -> `Generated.RequestCharLocationCitation`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RequestCitationsConfig` -> `Generated.RequestCitationsConfig`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RequestContentBlockLocationCitation` -> `Generated.RequestContentBlockLocationCitation`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RequestCounts` -> `none`: Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RequestDocumentBlock` -> `Generated.RequestDocumentBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RequestImageBlock` -> `Generated.RequestImageBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RequestPageLocationCitation` -> `Generated.RequestPageLocationCitation`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RequestRedactedThinkingBlock` -> `none`: Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RequestSearchResultBlock` -> `Generated.RequestSearchResultBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RequestSearchResultLocationCitation` -> `Generated.RequestSearchResultLocationCitation`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RequestServerToolUseBlock` -> `Generated.RequestServerToolUseBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RequestTextBlock` -> `Generated.RequestTextBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RequestThinkingBlock` -> `none`: Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RequestToolResultBlock` -> `none`: Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RequestToolUseBlock` -> `none`: Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RequestWebSearchResultBlock` -> `Generated.RequestWebSearchResultBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RequestWebSearchResultLocationCitation` -> `Generated.RequestWebSearchResultLocationCitation`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RequestWebSearchToolResultBlock` -> `Generated.RequestWebSearchToolResultBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RequestWebSearchToolResultError` -> `Generated.RequestWebSearchToolResultError`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ResponseCharLocationCitation` -> `Generated.ResponseCharLocationCitation`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ResponseContentBlockLocationCitation` -> `Generated.ResponseContentBlockLocationCitation`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ResponsePageLocationCitation` -> `Generated.ResponsePageLocationCitation`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ResponseRedactedThinkingBlock` -> `Generated.ResponseRedactedThinkingBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ResponseSearchResultLocationCitation` -> `Generated.ResponseSearchResultLocationCitation`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ResponseServerToolUseBlock` -> `Generated.ResponseServerToolUseBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ResponseTextBlock` -> `Generated.ResponseTextBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ResponseThinkingBlock` -> `Generated.ResponseThinkingBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ResponseToolUseBlock` -> `Generated.ResponseToolUseBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ResponseWebSearchResultBlock` -> `Generated.ResponseWebSearchResultBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ResponseWebSearchResultLocationCitation` -> `Generated.ResponseWebSearchResultLocationCitation`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ResponseWebSearchToolResultBlock` -> `Generated.ResponseWebSearchToolResultBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ResponseWebSearchToolResultError` -> `Generated.ResponseWebSearchToolResultError`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ServerToolUsage` -> `Generated.ServerToolUsage`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.Skill` -> `Generated.Skill`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.SkillVersion` -> `Generated.SkillVersion`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.StopReason` -> `Generated.StopReason`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.TextEditor20250124` -> `Generated.TextEditor_20250124`: Regenerated in v4 under this name; update the identifier and re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.TextEditor20250429` -> `Generated.TextEditor_20250429`: Regenerated in v4 under this name; update the identifier and re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.TextEditor20250728` -> `Generated.TextEditor_20250728`: Regenerated in v4 under this name; update the identifier and re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ThinkingConfigDisabled` -> `Generated.ThinkingConfigDisabled`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ThinkingConfigEnabled` -> `Generated.ThinkingConfigEnabled`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ThinkingConfigParam` -> `Generated.ThinkingConfigParam`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.Tool` -> `Generated.Tool`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ToolChoice` -> `Generated.ToolChoice`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ToolChoiceAny` -> `Generated.ToolChoiceAny`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ToolChoiceAuto` -> `Generated.ToolChoiceAuto`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ToolChoiceNone` -> `Generated.ToolChoiceNone`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ToolChoiceTool` -> `Generated.ToolChoiceTool`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.URLImageSource` -> `Generated.URLImageSource`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.URLPDFSource` -> `Generated.URLPDFSource`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.UploadFileV1FilesPostParams` -> `Generated.UploadFileV1FilesPostParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.UploadFileV1FilesPostRequest` -> `Generated.UploadFileV1FilesPostRequestFormData`: Regenerated in v4 under this name; update the identifier and re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.UsageServiceTierEnum` -> `none`: Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UserLocation` -> `Generated.UserLocation`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.WebSearchTool20250305` -> `Generated.WebSearchTool_20250305`: Regenerated in v4 under this name; update the identifier and re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.WebSearchToolResultErrorCode` -> `Generated.WebSearchToolResultErrorCode`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.make` -> `Generated.make`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +### `@effect/ai-openai/Generated` + +- `Generated.ActiveStatus` -> `Generated.ActiveStatus`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ActiveStatusType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.AddUploadPartRequest` -> `Generated.AddUploadPartRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.AdminApiKey` -> `Generated.AdminApiKey`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.AdminApiKeysCreateRequest` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.AdminApiKeysDelete200` -> `Generated.AdminApiKeysDelete200`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.AdminApiKeysListParams` -> `Generated.AdminApiKeysListParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.AdminApiKeysListParamsOrder` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.Annotation` -> `Generated.Annotation`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ApiKeyList` -> `Generated.ApiKeyList`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ApplyPatchCallOutputStatus` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ApplyPatchCallOutputStatusParam` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ApplyPatchCallStatus` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ApplyPatchCallStatusParam` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ApplyPatchCreateFileOperation` -> `Generated.ApplyPatchCreateFileOperation`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ApplyPatchCreateFileOperationParam` -> `Generated.ApplyPatchCreateFileOperationParam`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ApplyPatchCreateFileOperationParamType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ApplyPatchCreateFileOperationType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ApplyPatchDeleteFileOperation` -> `Generated.ApplyPatchDeleteFileOperation`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ApplyPatchDeleteFileOperationParam` -> `Generated.ApplyPatchDeleteFileOperationParam`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ApplyPatchDeleteFileOperationParamType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ApplyPatchDeleteFileOperationType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ApplyPatchOperationParam` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ApplyPatchToolCall` -> `Generated.ApplyPatchToolCall`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ApplyPatchToolCallItemParam` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ApplyPatchToolCallItemParamType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ApplyPatchToolCallOutput` -> `Generated.ApplyPatchToolCallOutput`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ApplyPatchToolCallOutputItemParam` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ApplyPatchToolCallOutputItemParamType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ApplyPatchToolCallOutputType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ApplyPatchToolCallType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ApplyPatchToolParam` -> `Generated.ApplyPatchToolParam`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ApplyPatchToolParamType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ApplyPatchUpdateFileOperation` -> `Generated.ApplyPatchUpdateFileOperation`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ApplyPatchUpdateFileOperationParam` -> `Generated.ApplyPatchUpdateFileOperationParam`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ApplyPatchUpdateFileOperationParamType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ApplyPatchUpdateFileOperationType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ApproximateLocation` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ApproximateLocationType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.AssignedRoleDetails` -> `Generated.AssignedRoleDetails`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.AssistantMessageItem` -> `Generated.AssistantMessageItem`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.AssistantMessageItemObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.AssistantMessageItemType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.AssistantObject` -> `Generated.AssistantObject`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.AssistantObjectObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.AssistantSupportedModels` -> `Generated.AssistantSupportedModels`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.AssistantTool` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.AssistantToolsCode` -> `Generated.AssistantToolsCode`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.AssistantToolsCodeType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.AssistantToolsFileSearch` -> `Generated.AssistantToolsFileSearch`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.AssistantToolsFileSearchType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.AssistantToolsFileSearchTypeOnly` -> `Generated.AssistantToolsFileSearchTypeOnly`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.AssistantToolsFileSearchTypeOnlyType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.AssistantToolsFunction` -> `Generated.AssistantToolsFunction`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.AssistantToolsFunctionType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.AssistantsApiResponseFormatOption` -> `Generated.AssistantsApiResponseFormatOption`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.AssistantsApiResponseFormatOptionEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.AssistantsNamedToolChoice` -> `Generated.AssistantsNamedToolChoice`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.AssistantsNamedToolChoiceType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.Attachment` -> `Generated.Attachment`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.AttachmentType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.AudioResponseFormat` -> `Generated.AudioResponseFormat`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.AudioTranscription` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.AudioTranscriptionModel` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.AuditLog` -> `Generated.AuditLog`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.AuditLogActor` -> `Generated.AuditLogActor`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.AuditLogActorApiKey` -> `Generated.AuditLogActorApiKey`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.AuditLogActorApiKeyType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.AuditLogActorServiceAccount` -> `Generated.AuditLogActorServiceAccount`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.AuditLogActorSession` -> `Generated.AuditLogActorSession`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.AuditLogActorType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.AuditLogActorUser` -> `Generated.AuditLogActorUser`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.AuditLogEventType` -> `Generated.AuditLogEventType`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.AutoChunkingStrategyRequestParam` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.AutoChunkingStrategyRequestParamType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.AutomaticThreadTitlingParam` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.Batch` -> `Generated.Batch`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BatchError` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.BatchFileExpirationAfter` -> `Generated.BatchFileExpirationAfter`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BatchFileExpirationAfterAnchor` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.BatchObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.BatchRequestCounts` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.BatchStatus` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.Certificate` -> `Generated.Certificate`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CertificateObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ChatCompletionAllowedTools` -> `Generated.ChatCompletionAllowedTools`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ChatCompletionAllowedToolsChoice` -> `Generated.ChatCompletionAllowedToolsChoice`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ChatCompletionAllowedToolsChoiceType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ChatCompletionAllowedToolsMode` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ChatCompletionDeleted` -> `Generated.ChatCompletionDeleted`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ChatCompletionDeletedObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ChatCompletionFunctionCallOption` -> `Generated.ChatCompletionFunctionCallOption`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ChatCompletionFunctions` -> `Generated.ChatCompletionFunctions`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ChatCompletionList` -> `Generated.ChatCompletionList`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ChatCompletionListObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ChatCompletionMessageCustomToolCall` -> `Generated.ChatCompletionMessageCustomToolCall`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ChatCompletionMessageCustomToolCallType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ChatCompletionMessageList` -> `Generated.ChatCompletionMessageList`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ChatCompletionMessageListObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ChatCompletionMessageToolCall` -> `Generated.ChatCompletionMessageToolCall`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ChatCompletionMessageToolCallType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ChatCompletionMessageToolCalls` -> `Generated.ChatCompletionMessageToolCalls`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ChatCompletionNamedToolChoice` -> `Generated.ChatCompletionNamedToolChoice`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ChatCompletionNamedToolChoiceCustom` -> `Generated.ChatCompletionNamedToolChoiceCustom`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ChatCompletionNamedToolChoiceCustomType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ChatCompletionNamedToolChoiceType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ChatCompletionRequestAssistantMessage` -> `Generated.ChatCompletionRequestAssistantMessage`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ChatCompletionRequestAssistantMessageContentPart` -> `Generated.ChatCompletionRequestAssistantMessageContentPart`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ChatCompletionRequestAssistantMessageRole` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ChatCompletionRequestDeveloperMessage` -> `Generated.ChatCompletionRequestDeveloperMessage`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ChatCompletionRequestDeveloperMessageRole` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ChatCompletionRequestFunctionMessage` -> `Generated.ChatCompletionRequestFunctionMessage`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ChatCompletionRequestFunctionMessageRole` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ChatCompletionRequestMessage` -> `Generated.ChatCompletionRequestMessage`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ChatCompletionRequestMessageContentPartAudio` -> `Generated.ChatCompletionRequestMessageContentPartAudio`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ChatCompletionRequestMessageContentPartAudioInputAudioFormat` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ChatCompletionRequestMessageContentPartAudioType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ChatCompletionRequestMessageContentPartFile` -> `Generated.ChatCompletionRequestMessageContentPartFile`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ChatCompletionRequestMessageContentPartFileType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ChatCompletionRequestMessageContentPartImage` -> `Generated.ChatCompletionRequestMessageContentPartImage`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ChatCompletionRequestMessageContentPartImageImageUrlDetail` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ChatCompletionRequestMessageContentPartImageType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ChatCompletionRequestMessageContentPartRefusal` -> `Generated.ChatCompletionRequestMessageContentPartRefusal`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ChatCompletionRequestMessageContentPartRefusalType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ChatCompletionRequestMessageContentPartText` -> `Generated.ChatCompletionRequestMessageContentPartText`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ChatCompletionRequestMessageContentPartTextType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ChatCompletionRequestSystemMessage` -> `Generated.ChatCompletionRequestSystemMessage`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ChatCompletionRequestSystemMessageContentPart` -> `Generated.ChatCompletionRequestSystemMessageContentPart`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ChatCompletionRequestSystemMessageRole` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ChatCompletionRequestToolMessage` -> `Generated.ChatCompletionRequestToolMessage`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ChatCompletionRequestToolMessageContentPart` -> `Generated.ChatCompletionRequestToolMessageContentPart`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ChatCompletionRequestToolMessageRole` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ChatCompletionRequestUserMessage` -> `Generated.ChatCompletionRequestUserMessage`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ChatCompletionRequestUserMessageContentPart` -> `Generated.ChatCompletionRequestUserMessageContentPart`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ChatCompletionRequestUserMessageRole` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ChatCompletionResponseMessage` -> `Generated.ChatCompletionResponseMessage`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ChatCompletionResponseMessageRole` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ChatCompletionStreamOptions` -> `Generated.ChatCompletionStreamOptions`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ChatCompletionTokenLogprob` -> `Generated.ChatCompletionTokenLogprob`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ChatCompletionTool` -> `Generated.ChatCompletionTool`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ChatCompletionToolChoiceOption` -> `Generated.ChatCompletionToolChoiceOption`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ChatCompletionToolChoiceOptionEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ChatCompletionToolType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ChatModel` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ChatSessionAutomaticThreadTitling` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ChatSessionChatkitConfiguration` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ChatSessionFileUpload` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ChatSessionHistory` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ChatSessionRateLimits` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ChatSessionResource` -> `Generated.ChatSessionResource`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ChatSessionResourceObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ChatSessionStatus` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ChatkitConfigurationParam` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ChatkitWorkflow` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ChatkitWorkflowTracing` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ChunkingStrategyRequestParam` -> `Generated.ChunkingStrategyRequestParam`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ChunkingStrategyResponse` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ClickButtonType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ClickParam` -> `Generated.ClickParam`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ClickParamType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.Client` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ClientError` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ClientToolCallItem` -> `Generated.ClientToolCallItem`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ClientToolCallItemObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ClientToolCallItemType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ClientToolCallStatus` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ClosedStatus` -> `Generated.ClosedStatus`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ClosedStatusType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CodeInterpreterContainerAuto` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CodeInterpreterContainerAutoType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CodeInterpreterOutputImage` -> `Generated.CodeInterpreterOutputImage`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CodeInterpreterOutputImageType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CodeInterpreterOutputLogs` -> `Generated.CodeInterpreterOutputLogs`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CodeInterpreterOutputLogsType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CodeInterpreterTool` -> `Generated.CodeInterpreterTool`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CodeInterpreterToolCall` -> `Generated.CodeInterpreterToolCall`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CodeInterpreterToolCallStatus` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CodeInterpreterToolCallType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CodeInterpreterToolType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ComparisonFilter` -> `Generated.ComparisonFilter`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ComparisonFilterType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ComparisonFilterValueItems` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CompleteUploadRequest` -> `Generated.CompleteUploadRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CompletionUsage` -> `Generated.CompletionUsage`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CompoundFilter` -> `Generated.CompoundFilter`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CompoundFilterType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ComputerAction` -> `Generated.ComputerAction`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ComputerCallOutputItemParam` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ComputerCallOutputItemParamType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ComputerCallSafetyCheckParam` -> `Generated.ComputerCallSafetyCheckParam`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ComputerEnvironment` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ComputerScreenshotContent` -> `Generated.ComputerScreenshotContent`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ComputerScreenshotContentType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ComputerScreenshotImage` -> `Generated.ComputerScreenshotImage`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ComputerScreenshotImageType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ComputerToolCall` -> `Generated.ComputerToolCall`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ComputerToolCallOutputResource` -> `Generated.ComputerToolCallOutputResource`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ComputerToolCallOutputResourceStatus` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ComputerToolCallOutputResourceType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ComputerToolCallStatus` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ComputerToolCallType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ComputerUsePreviewTool` -> `Generated.ComputerUsePreviewTool`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ComputerUsePreviewToolType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ContainerFileCitationBody` -> `Generated.ContainerFileCitationBody`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ContainerFileCitationBodyType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ContainerFileListResource` -> `Generated.ContainerFileListResource`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ContainerFileResource` -> `Generated.ContainerFileResource`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ContainerListResource` -> `Generated.ContainerListResource`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ContainerMemoryLimit` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ContainerResource` -> `Generated.ContainerResource`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ContainerResourceExpiresAfterAnchor` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.Conversation2` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ConversationItem` -> `Generated.ConversationItem`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ConversationItemList` -> `Generated.ConversationItemList`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ConversationParam` -> `Generated.ConversationParam`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ConversationParam2` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ConversationResource` -> `Generated.ConversationResource`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ConversationResourceObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CostsResult` -> `Generated.CostsResult`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CostsResultObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateAssistantRequest` -> `Generated.CreateAssistantRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateBatchRequest` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateBatchRequestCompletionWindow` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateBatchRequestEndpoint` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateChatCompletionRequest` -> `Generated.CreateChatCompletionRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateChatCompletionRequestAudioFormat` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateChatCompletionRequestFunctionCallEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateChatCompletionRequestPromptCacheRetentionEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateChatCompletionRequestWebSearchOptionsUserLocationType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateChatCompletionResponse` -> `Generated.CreateChatCompletionResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateChatCompletionResponseObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateChatSessionBody` -> `Generated.CreateChatSessionBody`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateCompletionRequest` -> `Generated.CreateCompletionRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateCompletionRequestModelEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateCompletionResponse` -> `Generated.CreateCompletionResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateCompletionResponseObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateContainerBody` -> `Generated.CreateContainerBody`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateContainerBodyExpiresAfterAnchor` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateContainerFileBody` -> `Generated.CreateContainerFileBody`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateConversationBody` -> `Generated.CreateConversationBody`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateConversationItemsParams` -> `Generated.CreateConversationItemsParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateConversationItemsRequest` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateEmbeddingRequest` -> `Generated.CreateEmbeddingRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateEmbeddingRequestEncodingFormat` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateEmbeddingRequestModelEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateEmbeddingResponse` -> `Generated.CreateEmbeddingResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateEmbeddingResponseObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateEvalCompletionsRunDataSource` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateEvalCompletionsRunDataSourceInputMessagesEnumType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateEvalCompletionsRunDataSourceType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateEvalCustomDataSourceConfig` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateEvalCustomDataSourceConfigType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateEvalItem` -> `Generated.CreateEvalItem`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateEvalJsonlRunDataSource` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateEvalJsonlRunDataSourceType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateEvalLabelModelGrader` -> `Generated.CreateEvalLabelModelGrader`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateEvalLabelModelGraderType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateEvalLogsDataSourceConfig` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateEvalLogsDataSourceConfigType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateEvalRequest` -> `Generated.CreateEvalRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateEvalResponsesRunDataSource` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateEvalResponsesRunDataSourceInputMessagesEnumType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateEvalResponsesRunDataSourceType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateEvalRunRequest` -> `Generated.CreateEvalRunRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateEvalStoredCompletionsDataSourceConfig` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateEvalStoredCompletionsDataSourceConfigType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateFileRequest` -> `Generated.CreateFileRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateFineTuningCheckpointPermissionRequest` -> `Generated.CreateFineTuningCheckpointPermissionRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateFineTuningJobRequest` -> `Generated.CreateFineTuningJobRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateFineTuningJobRequestHyperparametersBatchSizeEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateFineTuningJobRequestHyperparametersLearningRateMultiplierEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateFineTuningJobRequestHyperparametersNEpochsEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateFineTuningJobRequestModelEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateGroupBody` -> `Generated.CreateGroupBody`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateGroupUserBody` -> `Generated.CreateGroupUserBody`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateImageEditRequest` -> `Generated.CreateImageEditRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateImageEditRequestBackground` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateImageEditRequestModelEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateImageEditRequestOutputFormat` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateImageEditRequestQuality` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateImageEditRequestResponseFormat` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateImageEditRequestSize` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateImageRequest` -> `Generated.CreateImageRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateImageRequestBackground` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateImageRequestModelEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateImageRequestModeration` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateImageRequestOutputFormat` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateImageRequestQuality` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateImageRequestResponseFormat` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateImageRequestSize` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateImageRequestStyle` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateImageVariationRequest` -> `Generated.CreateImageVariationRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateImageVariationRequestModelEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateImageVariationRequestResponseFormat` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateImageVariationRequestSize` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateMessageRequest` -> `Generated.CreateMessageRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateMessageRequestRole` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateModerationRequest` -> `Generated.CreateModerationRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateModerationRequestModelEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateModerationResponse` -> `Generated.CreateModerationResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateResponse` -> `Generated.CreateResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateResponsePromptCacheRetentionEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateResponseTruncationEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateRunParams` -> `Generated.CreateRunParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateRunRequest` -> `Generated.CreateRunRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateRunRequestToolChoice` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateRunRequestToolChoiceEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateRunRequestTruncationStrategy` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateRunRequestTruncationStrategyEnumType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateSpeechRequest` -> `Generated.CreateSpeechRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateSpeechRequestModelEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateSpeechRequestResponseFormat` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateSpeechRequestStreamFormat` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateThreadAndRunRequest` -> `Generated.CreateThreadAndRunRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateThreadAndRunRequestModelEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateThreadAndRunRequestToolChoice` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateThreadAndRunRequestToolChoiceEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateThreadAndRunRequestTruncationStrategy` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateThreadAndRunRequestTruncationStrategyEnumType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateThreadRequest` -> `Generated.CreateThreadRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateTranscription200` -> `Generated.CreateTranscription200`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateTranscriptionRequest` -> `Generated.CreateTranscriptionRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateTranscriptionRequestModelEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateTranscriptionResponseDiarizedJson` -> `Generated.CreateTranscriptionResponseDiarizedJson`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateTranscriptionResponseDiarizedJsonTask` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateTranscriptionResponseJson` -> `Generated.CreateTranscriptionResponseJson`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateTranscriptionResponseVerboseJson` -> `Generated.CreateTranscriptionResponseVerboseJson`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateTranslation200` -> `Generated.CreateTranslation200`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateTranslationRequest` -> `Generated.CreateTranslationRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateTranslationRequestModelEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateTranslationRequestResponseFormat` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateTranslationResponseJson` -> `Generated.CreateTranslationResponseJson`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateTranslationResponseVerboseJson` -> `Generated.CreateTranslationResponseVerboseJson`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateUploadRequest` -> `Generated.CreateUploadRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateUploadRequestPurpose` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateVectorStoreFileBatchRequest` -> `Generated.CreateVectorStoreFileBatchRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateVectorStoreFileRequest` -> `Generated.CreateVectorStoreFileRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateVectorStoreRequest` -> `Generated.CreateVectorStoreRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateVideoBody` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateVideoRemixBody` -> `Generated.CreateVideoRemixBody`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CustomGrammarFormatParam` -> `Generated.CustomGrammarFormatParam`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CustomGrammarFormatParamType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CustomTextFormatParam` -> `Generated.CustomTextFormatParam`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CustomTextFormatParamType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CustomToolCall` -> `Generated.CustomToolCall`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CustomToolCallOutput` -> `Generated.CustomToolCallOutput`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CustomToolCallOutputType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CustomToolCallType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CustomToolChatCompletions` -> `Generated.CustomToolChatCompletions`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CustomToolChatCompletionsCustomFormatEnumGrammarSyntax` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CustomToolChatCompletionsCustomFormatEnumType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CustomToolChatCompletionsType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CustomToolParam` -> `Generated.CustomToolParam`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CustomToolParamType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.DeleteAssistantResponse` -> `Generated.DeleteAssistantResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.DeleteAssistantResponseObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.DeleteCertificateResponse` -> `Generated.DeleteCertificateResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.DeleteEval200` -> `Generated.DeleteEval200`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.DeleteEvalRun200` -> `Generated.DeleteEvalRun200`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.DeleteFileResponse` -> `Generated.DeleteFileResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.DeleteFileResponseObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.DeleteFineTuningCheckpointPermissionResponse` -> `Generated.DeleteFineTuningCheckpointPermissionResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.DeleteFineTuningCheckpointPermissionResponseObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.DeleteMessageResponse` -> `Generated.DeleteMessageResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.DeleteMessageResponseObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.DeleteModelResponse` -> `Generated.DeleteModelResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.DeleteThreadResponse` -> `Generated.DeleteThreadResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.DeleteThreadResponseObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.DeleteVectorStoreFileResponse` -> `Generated.DeleteVectorStoreFileResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.DeleteVectorStoreFileResponseObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.DeleteVectorStoreResponse` -> `Generated.DeleteVectorStoreResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.DeleteVectorStoreResponseObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.DeletedConversationResource` -> `Generated.DeletedConversationResource`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.DeletedConversationResourceObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.DeletedRoleAssignmentResource` -> `Generated.DeletedRoleAssignmentResource`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.DeletedThreadResource` -> `Generated.DeletedThreadResource`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.DeletedThreadResourceObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.DeletedVideoResource` -> `Generated.DeletedVideoResource`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.DeletedVideoResourceObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.DetailEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.DoubleClickAction` -> `Generated.DoubleClickAction`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.DoubleClickActionType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.DownloadFile200` -> `Generated.DownloadFile200`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.Drag` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.DragPoint` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.DragType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.EasyInputMessage` -> `Generated.EasyInputMessage`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.EasyInputMessageRole` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.EasyInputMessageType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.Embedding` -> `Generated.Embedding`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.EmbeddingObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.Error` -> `Generated.Error`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.Error2` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ErrorResponse` -> `Generated.ErrorResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.Eval` -> `Generated.Eval`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.EvalApiError` -> `Generated.EvalApiError`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.EvalCustomDataSourceConfig` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.EvalCustomDataSourceConfigType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.EvalGraderLabelModel` -> `Generated.EvalGraderLabelModel`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.EvalGraderLabelModelType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.EvalGraderPython` -> `Generated.EvalGraderPython`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.EvalGraderPythonType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.EvalGraderScoreModel` -> `Generated.EvalGraderScoreModel`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.EvalGraderScoreModelType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.EvalGraderStringCheck` -> `Generated.EvalGraderStringCheck`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.EvalGraderStringCheckOperation` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.EvalGraderStringCheckType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.EvalGraderTextSimilarity` -> `Generated.EvalGraderTextSimilarity`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.EvalGraderTextSimilarityEvaluationMetric` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.EvalGraderTextSimilarityType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.EvalItem` -> `Generated.EvalItem`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.EvalItemContentEnumType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.EvalItemRole` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.EvalItemType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.EvalJsonlFileContentSource` -> `Generated.EvalJsonlFileContentSource`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.EvalJsonlFileContentSourceType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.EvalJsonlFileIdSource` -> `Generated.EvalJsonlFileIdSource`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.EvalJsonlFileIdSourceType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.EvalList` -> `Generated.EvalList`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.EvalListObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.EvalLogsDataSourceConfig` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.EvalLogsDataSourceConfigType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.EvalObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.EvalResponsesSource` -> `Generated.EvalResponsesSource`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.EvalResponsesSourceType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.EvalRun` -> `Generated.EvalRun`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.EvalRunList` -> `Generated.EvalRunList`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.EvalRunListObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.EvalRunObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.EvalRunOutputItem` -> `Generated.EvalRunOutputItem`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.EvalRunOutputItemList` -> `Generated.EvalRunOutputItemList`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.EvalRunOutputItemListObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.EvalRunOutputItemObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.EvalRunOutputItemResult` -> `Generated.EvalRunOutputItemResult`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.EvalStoredCompletionsDataSourceConfig` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.EvalStoredCompletionsDataSourceConfigType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.EvalStoredCompletionsSource` -> `Generated.EvalStoredCompletionsSource`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.EvalStoredCompletionsSourceType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ExpiresAfterParam` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ExpiresAfterParamAnchor` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FileAnnotation` -> `Generated.FileAnnotation`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.FileAnnotationSource` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FileAnnotationSourceType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FileAnnotationType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FileCitationBody` -> `Generated.FileCitationBody`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.FileCitationBodyType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FileExpirationAfter` -> `Generated.FileExpirationAfter`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.FileExpirationAfterAnchor` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FilePath` -> `Generated.FilePath`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.FilePathType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FilePurpose` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FileSearchRanker` -> `Generated.FileSearchRanker`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.FileSearchRankingOptions` -> `Generated.FileSearchRankingOptions`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.FileSearchTool` -> `Generated.FileSearchTool`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.FileSearchToolCall` -> `Generated.FileSearchToolCall`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.FileSearchToolCallStatus` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FileSearchToolCallType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FileSearchToolType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FileUploadParam` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.Filters` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FineTuneDPOHyperparameters` -> `Generated.FineTuneDPOHyperparameters`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.FineTuneDPOHyperparametersBatchSizeEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FineTuneDPOHyperparametersBetaEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FineTuneDPOHyperparametersLearningRateMultiplierEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FineTuneDPOHyperparametersNEpochsEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FineTuneDPOMethod` -> `Generated.FineTuneDPOMethod`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.FineTuneMethod` -> `Generated.FineTuneMethod`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.FineTuneMethodType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FineTuneReinforcementHyperparameters` -> `Generated.FineTuneReinforcementHyperparameters`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.FineTuneReinforcementHyperparametersBatchSizeEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FineTuneReinforcementHyperparametersComputeMultiplierEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FineTuneReinforcementHyperparametersEvalIntervalEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FineTuneReinforcementHyperparametersEvalSamplesEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FineTuneReinforcementHyperparametersLearningRateMultiplierEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FineTuneReinforcementHyperparametersNEpochsEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FineTuneReinforcementHyperparametersReasoningEffort` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FineTuneReinforcementMethod` -> `Generated.FineTuneReinforcementMethod`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.FineTuneSupervisedHyperparameters` -> `Generated.FineTuneSupervisedHyperparameters`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.FineTuneSupervisedHyperparametersBatchSizeEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FineTuneSupervisedHyperparametersLearningRateMultiplierEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FineTuneSupervisedHyperparametersNEpochsEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FineTuneSupervisedMethod` -> `Generated.FineTuneSupervisedMethod`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.FineTuningCheckpointPermission` -> `Generated.FineTuningCheckpointPermission`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.FineTuningCheckpointPermissionObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FineTuningIntegration` -> `Generated.FineTuningIntegration`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.FineTuningIntegrationType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FineTuningJob` -> `Generated.FineTuningJob`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.FineTuningJobCheckpoint` -> `Generated.FineTuningJobCheckpoint`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.FineTuningJobCheckpointObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FineTuningJobEvent` -> `Generated.FineTuningJobEvent`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.FineTuningJobEventLevel` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FineTuningJobEventObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FineTuningJobEventType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FineTuningJobHyperparametersBatchSizeEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FineTuningJobHyperparametersLearningRateMultiplierEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FineTuningJobHyperparametersNEpochsEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FineTuningJobObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FineTuningJobStatus` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FunctionAndCustomToolCallOutput` -> `Generated.FunctionAndCustomToolCallOutput`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.FunctionCallItemStatus` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FunctionCallOutputItemParam` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FunctionCallOutputItemParamType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FunctionObject` -> `Generated.FunctionObject`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.FunctionParameters` -> `Generated.FunctionParameters`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.FunctionShellAction` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FunctionShellActionParam` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FunctionShellCall` -> `Generated.FunctionShellCall`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.FunctionShellCallItemParam` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FunctionShellCallItemParamType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FunctionShellCallItemStatus` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FunctionShellCallOutput` -> `Generated.FunctionShellCallOutput`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.FunctionShellCallOutputContent` -> `Generated.FunctionShellCallOutputContent`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.FunctionShellCallOutputContentParam` -> `Generated.FunctionShellCallOutputContentParam`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.FunctionShellCallOutputExitOutcome` -> `Generated.FunctionShellCallOutputExitOutcome`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.FunctionShellCallOutputExitOutcomeParam` -> `Generated.FunctionShellCallOutputExitOutcomeParam`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.FunctionShellCallOutputExitOutcomeParamType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FunctionShellCallOutputExitOutcomeType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FunctionShellCallOutputItemParam` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FunctionShellCallOutputItemParamType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FunctionShellCallOutputOutcomeParam` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FunctionShellCallOutputTimeoutOutcome` -> `Generated.FunctionShellCallOutputTimeoutOutcome`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.FunctionShellCallOutputTimeoutOutcomeParam` -> `Generated.FunctionShellCallOutputTimeoutOutcomeParam`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.FunctionShellCallOutputTimeoutOutcomeParamType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FunctionShellCallOutputTimeoutOutcomeType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FunctionShellCallOutputType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FunctionShellCallType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FunctionShellToolParam` -> `Generated.FunctionShellToolParam`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.FunctionShellToolParamType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FunctionTool` -> `Generated.FunctionTool`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.FunctionToolCall` -> `Generated.FunctionToolCall`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.FunctionToolCallOutputResource` -> `Generated.FunctionToolCallOutputResource`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.FunctionToolCallOutputResourceStatus` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FunctionToolCallOutputResourceType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FunctionToolCallResource` -> `Generated.FunctionToolCallResource`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.FunctionToolCallResourceStatus` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FunctionToolCallResourceType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FunctionToolCallStatus` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FunctionToolCallType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FunctionToolType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.GetCertificateParams` -> `Generated.GetCertificateParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.GetChatCompletionMessagesParams` -> `Generated.GetChatCompletionMessagesParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.GetChatCompletionMessagesParamsOrder` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.GetConversationItemParams` -> `Generated.GetConversationItemParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.GetEvalRunOutputItemsParams` -> `Generated.GetEvalRunOutputItemsParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.GetEvalRunOutputItemsParamsOrder` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.GetEvalRunOutputItemsParamsStatus` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.GetEvalRunsParams` -> `Generated.GetEvalRunsParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.GetEvalRunsParamsOrder` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.GetEvalRunsParamsStatus` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.GetResponseParams` -> `Generated.GetResponseParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.GetRunStepParams` -> `Generated.GetRunStepParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.GraderLabelModel` -> `Generated.GraderLabelModel`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.GraderLabelModelType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.GraderMulti` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.GraderMultiType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.GraderPython` -> `Generated.GraderPython`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.GraderPythonType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.GraderScoreModel` -> `Generated.GraderScoreModel`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.GraderScoreModelType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.GraderStringCheck` -> `Generated.GraderStringCheck`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.GraderStringCheckOperation` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.GraderStringCheckType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.GraderTextSimilarity` -> `Generated.GraderTextSimilarity`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.GraderTextSimilarityEvaluationMetric` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.GraderTextSimilarityType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.GrammarSyntax1` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.Group` -> `Generated.Group`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.GroupDeletedResource` -> `Generated.GroupDeletedResource`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.GroupDeletedResourceObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.GroupListResource` -> `Generated.GroupListResource`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.GroupListResourceObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.GroupObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.GroupResourceWithSuccess` -> `Generated.GroupResourceWithSuccess`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.GroupResponse` -> `Generated.GroupResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.GroupRoleAssignment` -> `Generated.GroupRoleAssignment`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.GroupRoleAssignmentObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.GroupUserAssignment` -> `Generated.GroupUserAssignment`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.GroupUserAssignmentObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.GroupUserDeletedResource` -> `Generated.GroupUserDeletedResource`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.GroupUserDeletedResourceObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.HistoryParam` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.HybridSearchOptions` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.Image` -> `Generated.Image`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ImageDetail` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ImageGenInputUsageDetails` -> `Generated.ImageGenInputUsageDetails`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ImageGenTool` -> `Generated.ImageGenTool`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ImageGenToolBackground` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ImageGenToolCall` -> `Generated.ImageGenToolCall`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ImageGenToolCallStatus` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ImageGenToolCallType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ImageGenToolModel` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ImageGenToolModeration` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ImageGenToolOutputFormat` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ImageGenToolQuality` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ImageGenToolSize` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ImageGenToolType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ImageGenUsage` -> `Generated.ImageGenUsage`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ImagesResponse` -> `Generated.ImagesResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ImagesResponseBackground` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ImagesResponseOutputFormat` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ImagesResponseQuality` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ImagesResponseSize` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.IncludeEnum` -> `Generated.IncludeEnum`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.InferenceOptions` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.InputAudio` -> `Generated.InputAudio`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.InputAudioInputAudioFormat` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.InputAudioType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.InputContent` -> `Generated.InputContent`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.InputFidelity` -> `Generated.InputFidelity`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.InputFileContent` -> `Generated.InputFileContent`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.InputFileContentParam` -> `Generated.InputFileContentParam`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.InputFileContentParamType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.InputFileContentType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.InputImageContent` -> `Generated.InputImageContent`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.InputImageContentParamAutoParam` -> `Generated.InputImageContentParamAutoParam`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.InputImageContentParamAutoParamType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.InputImageContentType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.InputItem` -> `Generated.InputItem`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.InputMessage` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.InputMessageContentList` -> `Generated.InputMessageContentList`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.InputMessageResource` -> `Generated.InputMessageResource`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.InputMessageResourceRole` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.InputMessageResourceStatus` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.InputMessageResourceType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.InputMessageRole` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.InputMessageStatus` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.InputMessageType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.InputParam` -> `Generated.InputParam`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.InputTextContent` -> `Generated.InputTextContent`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.InputTextContentParam` -> `Generated.InputTextContentParam`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.InputTextContentParamType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.InputTextContentType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.Invite` -> `Generated.Invite`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.InviteDeleteResponse` -> `Generated.InviteDeleteResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.InviteDeleteResponseObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.InviteListResponse` -> `Generated.InviteListResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.InviteListResponseObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.InviteObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.InviteProjectGroupBody` -> `Generated.InviteProjectGroupBody`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.InviteRequest` -> `Generated.InviteRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.InviteRequestRole` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.InviteRole` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.InviteStatus` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.Item` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ItemReferenceParam` -> `Generated.ItemReferenceParam`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ItemReferenceParamTypeEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ItemResource` -> `Generated.ItemResource`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.KeyPressAction` -> `Generated.KeyPressAction`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.KeyPressActionType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ListAssistantsParams` -> `Generated.ListAssistantsParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListAssistantsParamsOrder` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ListAssistantsResponse` -> `Generated.ListAssistantsResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListAuditLogsParams` -> `Generated.ListAuditLogsParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListAuditLogsResponse` -> `Generated.ListAuditLogsResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListAuditLogsResponseObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ListBatchesParams` -> `Generated.ListBatchesParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListBatchesResponse` -> `Generated.ListBatchesResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListBatchesResponseObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ListCertificatesResponse` -> `Generated.ListCertificatesResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListCertificatesResponseObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ListChatCompletionsParams` -> `Generated.ListChatCompletionsParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListChatCompletionsParamsOrder` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ListContainerFilesParams` -> `Generated.ListContainerFilesParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListContainerFilesParamsOrder` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ListContainersParams` -> `Generated.ListContainersParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListContainersParamsOrder` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ListConversationItemsParams` -> `Generated.ListConversationItemsParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListConversationItemsParamsOrder` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ListEvalsParams` -> `Generated.ListEvalsParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListEvalsParamsOrder` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ListEvalsParamsOrderBy` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ListFilesInVectorStoreBatchParams` -> `Generated.ListFilesInVectorStoreBatchParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListFilesInVectorStoreBatchParamsFilter` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ListFilesInVectorStoreBatchParamsOrder` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ListFilesParams` -> `Generated.ListFilesParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListFilesParamsOrder` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ListFilesResponse` -> `Generated.ListFilesResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListFineTuningCheckpointPermissionResponse` -> `Generated.ListFineTuningCheckpointPermissionResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListFineTuningCheckpointPermissionResponseObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ListFineTuningCheckpointPermissionsParams` -> `Generated.ListFineTuningCheckpointPermissionsParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListFineTuningCheckpointPermissionsParamsOrder` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ListFineTuningEventsParams` -> `Generated.ListFineTuningEventsParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListFineTuningJobCheckpointsParams` -> `Generated.ListFineTuningJobCheckpointsParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListFineTuningJobCheckpointsResponse` -> `Generated.ListFineTuningJobCheckpointsResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListFineTuningJobCheckpointsResponseObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ListFineTuningJobEventsResponse` -> `Generated.ListFineTuningJobEventsResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListFineTuningJobEventsResponseObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ListGroupRoleAssignmentsParams` -> `Generated.ListGroupRoleAssignmentsParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListGroupRoleAssignmentsParamsOrder` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ListGroupUsersParams` -> `Generated.ListGroupUsersParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListGroupUsersParamsOrder` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ListGroupsParams` -> `Generated.ListGroupsParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListGroupsParamsOrder` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ListInputItemsParams` -> `Generated.ListInputItemsParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListInputItemsParamsOrder` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ListInvitesParams` -> `Generated.ListInvitesParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListMessagesParams` -> `Generated.ListMessagesParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListMessagesParamsOrder` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ListMessagesResponse` -> `Generated.ListMessagesResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListModelsResponse` -> `Generated.ListModelsResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListModelsResponseObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ListOrganizationCertificatesParams` -> `Generated.ListOrganizationCertificatesParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListOrganizationCertificatesParamsOrder` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ListPaginatedFineTuningJobsParams` -> `Generated.ListPaginatedFineTuningJobsParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListPaginatedFineTuningJobsResponse` -> `Generated.ListPaginatedFineTuningJobsResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListPaginatedFineTuningJobsResponseObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ListProjectApiKeysParams` -> `Generated.ListProjectApiKeysParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListProjectCertificatesParams` -> `Generated.ListProjectCertificatesParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListProjectCertificatesParamsOrder` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ListProjectGroupRoleAssignmentsParams` -> `Generated.ListProjectGroupRoleAssignmentsParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListProjectGroupRoleAssignmentsParamsOrder` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ListProjectGroupsParams` -> `Generated.ListProjectGroupsParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListProjectGroupsParamsOrder` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ListProjectRateLimitsParams` -> `Generated.ListProjectRateLimitsParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListProjectRolesParams` -> `Generated.ListProjectRolesParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListProjectRolesParamsOrder` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ListProjectServiceAccountsParams` -> `Generated.ListProjectServiceAccountsParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListProjectUserRoleAssignmentsParams` -> `Generated.ListProjectUserRoleAssignmentsParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListProjectUserRoleAssignmentsParamsOrder` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ListProjectUsersParams` -> `Generated.ListProjectUsersParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListProjectsParams` -> `Generated.ListProjectsParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListRolesParams` -> `Generated.ListRolesParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListRolesParamsOrder` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ListRunStepsParams` -> `Generated.ListRunStepsParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListRunStepsParamsOrder` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ListRunStepsResponse` -> `Generated.ListRunStepsResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListRunsParams` -> `Generated.ListRunsParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListRunsParamsOrder` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ListRunsResponse` -> `Generated.ListRunsResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListThreadItemsMethodParams` -> `Generated.ListThreadItemsMethodParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListThreadsMethodParams` -> `Generated.ListThreadsMethodParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListUserRoleAssignmentsParams` -> `Generated.ListUserRoleAssignmentsParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListUserRoleAssignmentsParamsOrder` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ListUsersParams` -> `Generated.ListUsersParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListVectorStoreFilesParams` -> `Generated.ListVectorStoreFilesParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListVectorStoreFilesParamsFilter` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ListVectorStoreFilesParamsOrder` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ListVectorStoreFilesResponse` -> `Generated.ListVectorStoreFilesResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListVectorStoresParams` -> `Generated.ListVectorStoresParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListVectorStoresParamsOrder` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ListVectorStoresResponse` -> `Generated.ListVectorStoresResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListVideosParams` -> `Generated.ListVideosParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.LocalShellCallStatus` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.LocalShellExecAction` -> `Generated.LocalShellExecAction`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.LocalShellExecActionType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.LocalShellToolCall` -> `Generated.LocalShellToolCall`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.LocalShellToolCallOutput` -> `Generated.LocalShellToolCallOutput`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.LocalShellToolCallOutputStatusEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.LocalShellToolCallOutputType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.LocalShellToolCallStatus` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.LocalShellToolCallType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.LocalShellToolParam` -> `Generated.LocalShellToolParam`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.LocalShellToolParamType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.LockedStatus` -> `Generated.LockedStatus`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.LockedStatusType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.LogProb` -> `Generated.LogProb`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.MCPApprovalRequest` -> `Generated.MCPApprovalRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.MCPApprovalRequestType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.MCPApprovalResponse` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.MCPApprovalResponseResource` -> `Generated.MCPApprovalResponseResource`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.MCPApprovalResponseResourceType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.MCPApprovalResponseType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.MCPListTools` -> `Generated.MCPListTools`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.MCPListToolsTool` -> `Generated.MCPListToolsTool`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.MCPListToolsType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.MCPTool` -> `Generated.MCPTool`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.MCPToolCall` -> `Generated.MCPToolCall`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.MCPToolCallStatus` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.MCPToolCallType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.MCPToolConnectorId` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.MCPToolFilter` -> `Generated.MCPToolFilter`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.MCPToolRequireApprovalEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.MCPToolType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.Message` -> `Generated.Message`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.MessageContent` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.MessageContentImageFileObject` -> `Generated.MessageContentImageFileObject`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.MessageContentImageFileObjectImageFileDetail` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.MessageContentImageFileObjectType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.MessageContentImageUrlObject` -> `Generated.MessageContentImageUrlObject`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.MessageContentImageUrlObjectImageUrlDetail` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.MessageContentImageUrlObjectType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.MessageContentRefusalObject` -> `Generated.MessageContentRefusalObject`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.MessageContentRefusalObjectType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.MessageContentTextAnnotationsFileCitationObject` -> `Generated.MessageContentTextAnnotationsFileCitationObject`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.MessageContentTextAnnotationsFileCitationObjectType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.MessageContentTextAnnotationsFilePathObject` -> `Generated.MessageContentTextAnnotationsFilePathObject`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.MessageContentTextAnnotationsFilePathObjectType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.MessageContentTextObject` -> `Generated.MessageContentTextObject`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.MessageContentTextObjectType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.MessageObject` -> `Generated.MessageObject`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.MessageObjectIncompleteDetailsEnumReason` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.MessageObjectObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.MessageObjectRole` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.MessageObjectStatus` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.MessageRequestContentTextObject` -> `Generated.MessageRequestContentTextObject`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.MessageRequestContentTextObjectType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.MessageRole` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.MessageStatus` -> `OpenAiSchema.MessageStatus`: Use the focused v4 OpenAiSchema definition; the old generated export was removed when the OpenAI specification client was regenerated. + +- `Generated.MessageType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.Metadata` -> `Generated.Metadata`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.Model` -> `Generated.Model`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ModelIdsResponses` -> `Generated.ModelIdsResponses`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ModelIdsResponsesEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ModelIdsShared` -> `Generated.ModelIdsShared`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ModelObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ModerationImageURLInput` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ModerationImageURLInputType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ModerationTextInput` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ModerationTextInputType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ModifyAssistantRequest` -> `Generated.ModifyAssistantRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ModifyCertificateRequest` -> `Generated.ModifyCertificateRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ModifyMessageRequest` -> `Generated.ModifyMessageRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ModifyRunRequest` -> `Generated.ModifyRunRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ModifyThreadRequest` -> `Generated.ModifyThreadRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.Move` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.MoveType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.NoiseReductionType` -> `Generated.NoiseReductionType`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.OpenAIFile` -> `Generated.OpenAIFile`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.OpenAIFileObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenAIFilePurpose` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenAIFileStatus` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OrderEnum` -> `Generated.OrderEnum`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.OtherChunkingStrategyResponseParam` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OtherChunkingStrategyResponseParamType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OutputItem` -> `Generated.OutputItem`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.OutputMessage` -> `Generated.OutputMessage`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.OutputMessageContent` -> `Generated.OutputMessageContent`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.OutputMessageRole` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OutputMessageStatus` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OutputMessageType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OutputTextContent` -> `Generated.OutputTextContent`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.OutputTextContentType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ParallelToolCalls` -> `Generated.ParallelToolCalls`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.PartialImages` -> `Generated.PartialImages`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.PredictionContent` -> `Generated.PredictionContent`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.PredictionContentType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.Project` -> `Generated.Project`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ProjectApiKey` -> `Generated.ProjectApiKey`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ProjectApiKeyDeleteResponse` -> `Generated.ProjectApiKeyDeleteResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ProjectApiKeyDeleteResponseObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ProjectApiKeyListResponse` -> `Generated.ProjectApiKeyListResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ProjectApiKeyListResponseObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ProjectApiKeyObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ProjectApiKeyOwnerType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ProjectCreateRequest` -> `Generated.ProjectCreateRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ProjectCreateRequestGeography` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ProjectGroup` -> `Generated.ProjectGroup`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ProjectGroupDeletedResource` -> `Generated.ProjectGroupDeletedResource`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ProjectGroupDeletedResourceObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ProjectGroupListResource` -> `Generated.ProjectGroupListResource`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ProjectGroupListResourceObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ProjectGroupObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ProjectListResponse` -> `Generated.ProjectListResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ProjectListResponseObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ProjectObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ProjectRateLimit` -> `Generated.ProjectRateLimit`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ProjectRateLimitListResponse` -> `Generated.ProjectRateLimitListResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ProjectRateLimitListResponseObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ProjectRateLimitObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ProjectRateLimitUpdateRequest` -> `Generated.ProjectRateLimitUpdateRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ProjectServiceAccount` -> `Generated.ProjectServiceAccount`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ProjectServiceAccountApiKey` -> `Generated.ProjectServiceAccountApiKey`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ProjectServiceAccountApiKeyObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ProjectServiceAccountCreateRequest` -> `Generated.ProjectServiceAccountCreateRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ProjectServiceAccountCreateResponse` -> `Generated.ProjectServiceAccountCreateResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ProjectServiceAccountCreateResponseObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ProjectServiceAccountCreateResponseRole` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ProjectServiceAccountDeleteResponse` -> `Generated.ProjectServiceAccountDeleteResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ProjectServiceAccountDeleteResponseObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ProjectServiceAccountListResponse` -> `Generated.ProjectServiceAccountListResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ProjectServiceAccountListResponseObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ProjectServiceAccountObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ProjectServiceAccountRole` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ProjectStatus` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ProjectUpdateRequest` -> `Generated.ProjectUpdateRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ProjectUser` -> `Generated.ProjectUser`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ProjectUserCreateRequest` -> `Generated.ProjectUserCreateRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ProjectUserCreateRequestRole` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ProjectUserDeleteResponse` -> `Generated.ProjectUserDeleteResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ProjectUserDeleteResponseObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ProjectUserListResponse` -> `Generated.ProjectUserListResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ProjectUserObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ProjectUserRole` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ProjectUserUpdateRequest` -> `Generated.ProjectUserUpdateRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ProjectUserUpdateRequestRole` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.Prompt` -> `Generated.Prompt`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.PublicAssignOrganizationGroupRoleBody` -> `Generated.PublicAssignOrganizationGroupRoleBody`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.PublicCreateOrganizationRoleBody` -> `Generated.PublicCreateOrganizationRoleBody`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.PublicRoleListResource` -> `Generated.PublicRoleListResource`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.PublicRoleListResourceObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.PublicUpdateOrganizationRoleBody` -> `Generated.PublicUpdateOrganizationRoleBody`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RankerVersionType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RankingOptions` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RateLimitsParam` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RealtimeAudioFormats` -> `Generated.RealtimeAudioFormats`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RealtimeAudioFormatsEnumRate` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RealtimeAudioFormatsEnumType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RealtimeCallCreateRequest` -> `Generated.RealtimeCallCreateRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RealtimeCallReferRequest` -> `Generated.RealtimeCallReferRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RealtimeCallRejectRequest` -> `Generated.RealtimeCallRejectRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RealtimeCreateClientSecretRequest` -> `Generated.RealtimeCreateClientSecretRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RealtimeCreateClientSecretRequestExpiresAfterAnchor` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RealtimeCreateClientSecretResponse` -> `Generated.RealtimeCreateClientSecretResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RealtimeFunctionTool` -> `Generated.RealtimeFunctionTool`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RealtimeFunctionToolType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RealtimeSessionCreateRequest` -> `Generated.RealtimeSessionCreateRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RealtimeSessionCreateRequestGA` -> `Generated.RealtimeSessionCreateRequestGA`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RealtimeSessionCreateRequestGAMaxOutputTokensEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RealtimeSessionCreateRequestGAModelEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RealtimeSessionCreateRequestGATracingEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RealtimeSessionCreateRequestGAType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RealtimeSessionCreateRequestMaxResponseOutputTokensEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RealtimeSessionCreateRequestTracingEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RealtimeSessionCreateResponse` -> `Generated.RealtimeSessionCreateResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RealtimeSessionCreateResponseGA` -> `Generated.RealtimeSessionCreateResponseGA`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RealtimeSessionCreateResponseGAMaxOutputTokensEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RealtimeSessionCreateResponseGAModelEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RealtimeSessionCreateResponseGATracingEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RealtimeSessionCreateResponseGAType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RealtimeSessionCreateResponseMaxOutputTokensEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RealtimeSessionCreateResponseTracingEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RealtimeTranscriptionSessionCreateRequest` -> `Generated.RealtimeTranscriptionSessionCreateRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RealtimeTranscriptionSessionCreateRequestGA` -> `Generated.RealtimeTranscriptionSessionCreateRequestGA`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RealtimeTranscriptionSessionCreateRequestGAType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RealtimeTranscriptionSessionCreateRequestInputAudioFormat` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RealtimeTranscriptionSessionCreateRequestTurnDetectionType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RealtimeTranscriptionSessionCreateResponse` -> `Generated.RealtimeTranscriptionSessionCreateResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RealtimeTranscriptionSessionCreateResponseGA` -> `Generated.RealtimeTranscriptionSessionCreateResponseGA`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RealtimeTranscriptionSessionCreateResponseGAType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RealtimeTruncation` -> `Generated.RealtimeTruncation`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RealtimeTruncationEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RealtimeTruncationEnumType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RealtimeTurnDetection` -> `Generated.RealtimeTurnDetection`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RealtimeTurnDetectionEnumEagerness` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.Reasoning` -> `Generated.Reasoning`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ReasoningEffort` -> `Generated.ReasoningEffort`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ReasoningEffortEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ReasoningGenerateSummaryEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ReasoningItem` -> `Generated.ReasoningItem`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ReasoningItemStatus` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ReasoningItemType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ReasoningSummaryEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ReasoningTextContent` -> `Generated.ReasoningTextContent`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ReasoningTextContentType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RefusalContent` -> `Generated.RefusalContent`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RefusalContentType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.Response` -> `Generated.Response`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ResponseError` -> `Generated.ResponseError`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ResponseErrorCode` -> `Generated.ResponseErrorCode`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ResponseFormatJsonObject` -> `Generated.ResponseFormatJsonObject`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ResponseFormatJsonObjectType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponseFormatJsonSchema` -> `Generated.ResponseFormatJsonSchema`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ResponseFormatJsonSchemaSchema` -> `Generated.ResponseFormatJsonSchemaSchema`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ResponseFormatJsonSchemaType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponseFormatText` -> `Generated.ResponseFormatText`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ResponseFormatTextType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponseIncompleteDetailsEnumReason` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponseItemList` -> `Generated.ResponseItemList`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ResponseModalities` -> `Generated.ResponseModalities`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ResponseObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponseOutputText` -> `Generated.ResponseOutputText`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ResponseOutputTextType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponsePromptCacheRetentionEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponsePromptVariables` -> `Generated.ResponsePromptVariables`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ResponseStatus` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponseStreamOptions` -> `Generated.ResponseStreamOptions`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ResponseTextParam` -> `Generated.ResponseTextParam`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ResponseTruncationEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponseUsage` -> `Generated.ResponseUsage`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RetrieveVideoContent200` -> `Generated.RetrieveVideoContent200`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RetrieveVideoContentParams` -> `Generated.RetrieveVideoContentParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.Role` -> `Generated.Role`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RoleDeletedResource` -> `Generated.RoleDeletedResource`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RoleDeletedResourceObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RoleListResource` -> `Generated.RoleListResource`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RoleListResourceObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RoleObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RunCompletionUsage` -> `Generated.RunCompletionUsage`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RunGraderRequest` -> `Generated.RunGraderRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RunGraderResponse` -> `Generated.RunGraderResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RunObject` -> `Generated.RunObject`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RunObjectIncompleteDetailsReason` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RunObjectLastErrorCode` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RunObjectObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RunObjectRequiredActionType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RunObjectToolChoice` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RunObjectToolChoiceEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RunObjectTruncationStrategy` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RunObjectTruncationStrategyEnumType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RunStatus` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RunStepCompletionUsage` -> `Generated.RunStepCompletionUsage`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RunStepDetailsMessageCreationObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RunStepDetailsMessageCreationObjectType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RunStepDetailsToolCall` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RunStepDetailsToolCallsCodeObject` -> `Generated.RunStepDetailsToolCallsCodeObject`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RunStepDetailsToolCallsCodeObjectType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RunStepDetailsToolCallsCodeOutputImageObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RunStepDetailsToolCallsCodeOutputImageObjectType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RunStepDetailsToolCallsCodeOutputLogsObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RunStepDetailsToolCallsCodeOutputLogsObjectType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RunStepDetailsToolCallsFileSearchObject` -> `Generated.RunStepDetailsToolCallsFileSearchObject`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RunStepDetailsToolCallsFileSearchObjectType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RunStepDetailsToolCallsFileSearchRankingOptionsObject` -> `Generated.RunStepDetailsToolCallsFileSearchRankingOptionsObject`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RunStepDetailsToolCallsFileSearchResultObject` -> `Generated.RunStepDetailsToolCallsFileSearchResultObject`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RunStepDetailsToolCallsFunctionObject` -> `Generated.RunStepDetailsToolCallsFunctionObject`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RunStepDetailsToolCallsFunctionObjectType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RunStepDetailsToolCallsObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RunStepDetailsToolCallsObjectType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RunStepObject` -> `Generated.RunStepObject`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RunStepObjectLastErrorEnumCode` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RunStepObjectObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RunStepObjectStatus` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RunStepObjectType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RunToolCallObject` -> `Generated.RunToolCallObject`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RunToolCallObjectType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.Screenshot` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ScreenshotType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.Scroll` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ScrollType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.SearchContextSize` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ServiceTier` -> `Generated.ServiceTier`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ServiceTierEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.SpecificApplyPatchParam` -> `Generated.SpecificApplyPatchParam`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.SpecificApplyPatchParamType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.SpecificFunctionShellParam` -> `Generated.SpecificFunctionShellParam`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.SpecificFunctionShellParamType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.StaticChunkingStrategy` -> `Generated.StaticChunkingStrategy`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.StaticChunkingStrategyRequestParam` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.StaticChunkingStrategyRequestParamType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.StaticChunkingStrategyResponseParam` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.StaticChunkingStrategyResponseParamType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.StopConfiguration` -> `Generated.StopConfiguration`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.SubmitToolOutputsRunRequest` -> `Generated.SubmitToolOutputsRunRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.SummaryTextContent` -> `Generated.SummaryTextContent`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.SummaryTextContentType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.SummaryType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.TaskGroupItem` -> `Generated.TaskGroupItem`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.TaskGroupItemObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.TaskGroupItemType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.TaskGroupTask` -> `Generated.TaskGroupTask`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.TaskItem` -> `Generated.TaskItem`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.TaskItemObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.TaskItemType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.TaskType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.TextAnnotation` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.TextContent` -> `Generated.TextContent`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.TextContentType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.TextResponseFormatConfiguration` -> `Generated.TextResponseFormatConfiguration`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.TextResponseFormatJsonSchema` -> `Generated.TextResponseFormatJsonSchema`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.TextResponseFormatJsonSchemaType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ThreadItem` -> `Generated.ThreadItem`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ThreadItemListResource` -> `Generated.ThreadItemListResource`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ThreadListResource` -> `Generated.ThreadListResource`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ThreadObject` -> `Generated.ThreadObject`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ThreadObjectObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ThreadResource` -> `Generated.ThreadResource`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ThreadResourceObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ToggleCertificatesRequest` -> `Generated.ToggleCertificatesRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.TokenCountsBody` -> `Generated.TokenCountsBody`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.TokenCountsResource` -> `Generated.TokenCountsResource`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.TokenCountsResourceObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.Tool` -> `Generated.Tool`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ToolChoice` -> `OpenAiSchema.ToolChoice`: Use the focused v4 OpenAiSchema definition; the old generated export was removed when the OpenAI specification client was regenerated. + +- `Generated.ToolChoiceAllowed` -> `Generated.ToolChoiceAllowed`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ToolChoiceAllowedMode` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ToolChoiceAllowedType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ToolChoiceCustom` -> `Generated.ToolChoiceCustom`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ToolChoiceCustomType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ToolChoiceFunction` -> `Generated.ToolChoiceFunction`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ToolChoiceFunctionType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ToolChoiceMCP` -> `Generated.ToolChoiceMCP`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ToolChoiceMCPType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ToolChoiceOptions` -> `Generated.ToolChoiceOptions`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ToolChoiceParam` -> `Generated.ToolChoiceParam`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ToolChoiceTypes` -> `Generated.ToolChoiceTypes`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ToolChoiceTypesType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ToolsArray` -> `Generated.ToolsArray`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.TopLogProb` -> `Generated.TopLogProb`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.TranscriptTextUsageDuration` -> `Generated.TranscriptTextUsageDuration`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.TranscriptTextUsageDurationType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.TranscriptTextUsageTokens` -> `Generated.TranscriptTextUsageTokens`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.TranscriptTextUsageTokensType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.TranscriptionChunkingStrategy` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.TranscriptionChunkingStrategyEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.TranscriptionDiarizedSegment` -> `Generated.TranscriptionDiarizedSegment`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.TranscriptionDiarizedSegmentType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.TranscriptionInclude` -> `Generated.TranscriptionInclude`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.TranscriptionSegment` -> `Generated.TranscriptionSegment`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.TranscriptionWord` -> `Generated.TranscriptionWord`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.TruncationEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.Type` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.TypeType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UpdateChatCompletionRequest` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UpdateConversationBody` -> `Generated.UpdateConversationBody`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.UpdateEvalRequest` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UpdateGroupBody` -> `Generated.UpdateGroupBody`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.UpdateVectorStoreFileAttributesRequest` -> `Generated.UpdateVectorStoreFileAttributesRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.UpdateVectorStoreRequest` -> `Generated.UpdateVectorStoreRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.UpdateVectorStoreRequestExpiresAfter` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UpdateVectorStoreRequestExpiresAfterEnumAnchor` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.Upload` -> `Generated.Upload`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.UploadCertificateRequest` -> `Generated.UploadCertificateRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.UploadFile` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UploadFileEnumObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UploadFileEnumPurpose` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UploadFileEnumStatus` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UploadObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UploadPart` -> `Generated.UploadPart`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.UploadPartObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UploadStatus` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UrlAnnotation` -> `Generated.UrlAnnotation`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.UrlAnnotationSource` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UrlAnnotationSourceType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UrlAnnotationType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UrlCitationBody` -> `Generated.UrlCitationBody`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.UrlCitationBodyType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UsageAudioSpeechesParams` -> `Generated.UsageAudioSpeechesParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.UsageAudioSpeechesParamsBucketWidth` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UsageAudioSpeechesResult` -> `Generated.UsageAudioSpeechesResult`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.UsageAudioSpeechesResultObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UsageAudioTranscriptionsParams` -> `Generated.UsageAudioTranscriptionsParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.UsageAudioTranscriptionsParamsBucketWidth` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UsageAudioTranscriptionsResult` -> `Generated.UsageAudioTranscriptionsResult`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.UsageAudioTranscriptionsResultObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UsageCodeInterpreterSessionsParams` -> `Generated.UsageCodeInterpreterSessionsParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.UsageCodeInterpreterSessionsParamsBucketWidth` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UsageCodeInterpreterSessionsResult` -> `Generated.UsageCodeInterpreterSessionsResult`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.UsageCodeInterpreterSessionsResultObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UsageCompletionsParams` -> `Generated.UsageCompletionsParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.UsageCompletionsParamsBucketWidth` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UsageCompletionsResult` -> `Generated.UsageCompletionsResult`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.UsageCompletionsResultObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UsageCostsParams` -> `Generated.UsageCostsParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.UsageCostsParamsBucketWidth` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UsageEmbeddingsParams` -> `Generated.UsageEmbeddingsParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.UsageEmbeddingsParamsBucketWidth` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UsageEmbeddingsResult` -> `Generated.UsageEmbeddingsResult`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.UsageEmbeddingsResultObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UsageImagesParams` -> `Generated.UsageImagesParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.UsageImagesParamsBucketWidth` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UsageImagesResult` -> `Generated.UsageImagesResult`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.UsageImagesResultObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UsageModerationsParams` -> `Generated.UsageModerationsParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.UsageModerationsParamsBucketWidth` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UsageModerationsResult` -> `Generated.UsageModerationsResult`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.UsageModerationsResultObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UsageResponse` -> `Generated.UsageResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.UsageResponseObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UsageTimeBucket` -> `Generated.UsageTimeBucket`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.UsageTimeBucketObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UsageVectorStoresParams` -> `Generated.UsageVectorStoresParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.UsageVectorStoresParamsBucketWidth` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UsageVectorStoresResult` -> `Generated.UsageVectorStoresResult`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.UsageVectorStoresResultObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.User` -> `Generated.User`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.UserDeleteResponse` -> `Generated.UserDeleteResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.UserDeleteResponseObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UserListResource` -> `Generated.UserListResource`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.UserListResourceObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UserListResponse` -> `Generated.UserListResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.UserListResponseObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UserMessageInputText` -> `Generated.UserMessageInputText`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.UserMessageInputTextType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UserMessageItem` -> `Generated.UserMessageItem`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.UserMessageItemObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UserMessageItemType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UserMessageQuotedText` -> `Generated.UserMessageQuotedText`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.UserMessageQuotedTextType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UserObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UserRole` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UserRoleAssignment` -> `Generated.UserRoleAssignment`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.UserRoleAssignmentObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UserRoleUpdateRequest` -> `Generated.UserRoleUpdateRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.UserRoleUpdateRequestRole` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.VadConfig` -> `Generated.VadConfig`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.VadConfigType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ValidateGraderRequest` -> `Generated.ValidateGraderRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ValidateGraderResponse` -> `Generated.ValidateGraderResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.VectorStoreExpirationAfter` -> `Generated.VectorStoreExpirationAfter`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.VectorStoreExpirationAfterAnchor` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.VectorStoreFileAttributes` -> `Generated.VectorStoreFileAttributes`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.VectorStoreFileBatchObject` -> `Generated.VectorStoreFileBatchObject`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.VectorStoreFileBatchObjectObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.VectorStoreFileBatchObjectStatus` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.VectorStoreFileContentResponse` -> `Generated.VectorStoreFileContentResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.VectorStoreFileContentResponseObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.VectorStoreFileObject` -> `Generated.VectorStoreFileObject`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.VectorStoreFileObjectLastErrorEnumCode` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.VectorStoreFileObjectObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.VectorStoreFileObjectStatus` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.VectorStoreObject` -> `Generated.VectorStoreObject`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.VectorStoreObjectObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.VectorStoreObjectStatus` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.VectorStoreSearchRequest` -> `Generated.VectorStoreSearchRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.VectorStoreSearchRequestRankingOptionsRanker` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.VectorStoreSearchResultContentObject` -> `Generated.VectorStoreSearchResultContentObject`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.VectorStoreSearchResultContentObjectType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.VectorStoreSearchResultItem` -> `Generated.VectorStoreSearchResultItem`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.VectorStoreSearchResultsPage` -> `Generated.VectorStoreSearchResultsPage`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.VectorStoreSearchResultsPageObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.Verbosity` -> `Generated.Verbosity`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.VerbosityEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.VideoContentVariant` -> `Generated.VideoContentVariant`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.VideoListResource` -> `Generated.VideoListResource`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.VideoModel` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.VideoResource` -> `Generated.VideoResource`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.VideoResourceObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.VideoSeconds` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.VideoSize` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.VideoStatus` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.VoiceIdsShared` -> `Generated.VoiceIdsShared`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.VoiceIdsSharedEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.Wait` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.WaitType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.WebSearchActionFind` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.WebSearchActionFindType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.WebSearchActionOpenPage` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.WebSearchActionOpenPageType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.WebSearchActionSearch` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.WebSearchActionSearchType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.WebSearchApproximateLocation` -> `Generated.WebSearchApproximateLocation`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.WebSearchApproximateLocationEnumType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.WebSearchContextSize` -> `Generated.WebSearchContextSize`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.WebSearchLocation` -> `Generated.WebSearchLocation`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.WebSearchPreviewTool` -> `Generated.WebSearchPreviewTool`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.WebSearchPreviewToolType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.WebSearchTool` -> `Generated.WebSearchTool`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.WebSearchToolCall` -> `Generated.WebSearchToolCall`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.WebSearchToolCallStatus` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.WebSearchToolCallType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.WebSearchToolSearchContextSize` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.WebSearchToolType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.WidgetMessageItem` -> `Generated.WidgetMessageItem`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.WidgetMessageItemObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.WidgetMessageItemType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.WorkflowParam` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.WorkflowTracingParam` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.make` -> `Generated.make`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +### `@effect/ai-openai/OpenAiClient` + +- `OpenAiClient.LogProbs` -> `Generated.LogProb`: The client-local log-probability schema moved to the regenerated v4 OpenAI schema surface and changed shape. + +- `OpenAiClient.ResponseCodeInterpreterCallCodeDeltaEvent` -> `Generated.ResponseCodeInterpreterCallCodeDeltaEvent`: The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape. + +- `OpenAiClient.ResponseCodeInterpreterCallCodeDoneEvent` -> `Generated.ResponseCodeInterpreterCallCodeDoneEvent`: The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape. + +- `OpenAiClient.ResponseCodeInterpreterCallCompletedEvent` -> `Generated.ResponseCodeInterpreterCallCompletedEvent`: The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape. + +- `OpenAiClient.ResponseCodeInterpreterCallInProgressEvent` -> `Generated.ResponseCodeInterpreterCallInProgressEvent`: The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape. + +- `OpenAiClient.ResponseCodeInterpreterCallInterpretingEvent` -> `Generated.ResponseCodeInterpreterCallInterpretingEvent`: The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape. + +- `OpenAiClient.ResponseCompletedEvent` -> `Generated.ResponseCompletedEvent`: The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape. + +- `OpenAiClient.ResponseContentPartAddedEvent` -> `Generated.ResponseContentPartAddedEvent`: The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape. + +- `OpenAiClient.ResponseContentPartDoneEvent` -> `Generated.ResponseContentPartDoneEvent`: The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape. + +- `OpenAiClient.ResponseCreatedEvent` -> `Generated.ResponseCreatedEvent`: The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape. + +- `OpenAiClient.ResponseCustomToolCallInputDeltaEvent` -> `Generated.ResponseCustomToolCallInputDeltaEvent`: The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape. + +- `OpenAiClient.ResponseCustomToolCallInputDoneEvent` -> `Generated.ResponseCustomToolCallInputDoneEvent`: The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape. + +- `OpenAiClient.ResponseErrorEvent` -> `Generated.ResponseErrorEvent`: The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape. + +- `OpenAiClient.ResponseFailedEvent` -> `Generated.ResponseFailedEvent`: The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape. + +- `OpenAiClient.ResponseFileSearchCallCompletedEvent` -> `Generated.ResponseFileSearchCallCompletedEvent`: The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape. + +- `OpenAiClient.ResponseFileSearchCallInProgressEvent` -> `Generated.ResponseFileSearchCallInProgressEvent`: The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape. + +- `OpenAiClient.ResponseFileSearchCallSearchingEvent` -> `Generated.ResponseFileSearchCallSearchingEvent`: The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape. + +- `OpenAiClient.ResponseFunctionCallArgumentsDeltaEvent` -> `Generated.ResponseFunctionCallArgumentsDeltaEvent`: The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape. + +- `OpenAiClient.ResponseFunctionCallArgumentsDoneEvent` -> `Generated.ResponseFunctionCallArgumentsDoneEvent`: The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape. + +- `OpenAiClient.ResponseImageGenerationCallCompletedEvent` -> `OpenAiClient.ResponseStreamEvent`: The standalone event helper was removed from OpenAiClient; narrow the v4 ResponseStreamEvent union by its `type` discriminator. + +- `OpenAiClient.ResponseImageGenerationCallGeneratingEvent` -> `OpenAiClient.ResponseStreamEvent`: The standalone event helper was removed from OpenAiClient; narrow the v4 ResponseStreamEvent union by its `type` discriminator. + +- `OpenAiClient.ResponseImageGenerationCallInProgressEvent` -> `OpenAiClient.ResponseStreamEvent`: The standalone event helper was removed from OpenAiClient; narrow the v4 ResponseStreamEvent union by its `type` discriminator. + +- `OpenAiClient.ResponseImageGenerationCallPartialImageEvent` -> `OpenAiClient.ResponseStreamEvent`: The standalone event helper was removed from OpenAiClient; narrow the v4 ResponseStreamEvent union by its `type` discriminator. + +- `OpenAiClient.ResponseInProgressEvent` -> `Generated.ResponseInProgressEvent`: The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape. + +- `OpenAiClient.ResponseIncompleteEvent` -> `Generated.ResponseIncompleteEvent`: The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape. + +- `OpenAiClient.ResponseMcpCallArgumentsDeltaEvent` -> `OpenAiClient.ResponseStreamEvent`: The standalone event helper was removed from OpenAiClient; narrow the v4 ResponseStreamEvent union by its `type` discriminator. + +- `OpenAiClient.ResponseMcpCallArgumentsDoneEvent` -> `OpenAiClient.ResponseStreamEvent`: The standalone event helper was removed from OpenAiClient; narrow the v4 ResponseStreamEvent union by its `type` discriminator. + +- `OpenAiClient.ResponseMcpCallCompletedEvent` -> `OpenAiClient.ResponseStreamEvent`: The standalone event helper was removed from OpenAiClient; narrow the v4 ResponseStreamEvent union by its `type` discriminator. + +- `OpenAiClient.ResponseMcpCallFailedEvent` -> `OpenAiClient.ResponseStreamEvent`: The standalone event helper was removed from OpenAiClient; narrow the v4 ResponseStreamEvent union by its `type` discriminator. + +- `OpenAiClient.ResponseMcpCallInProgressEvent` -> `OpenAiClient.ResponseStreamEvent`: The standalone event helper was removed from OpenAiClient; narrow the v4 ResponseStreamEvent union by its `type` discriminator. + +- `OpenAiClient.ResponseMcpListToolsCompletedEvent` -> `OpenAiClient.ResponseStreamEvent`: The standalone event helper was removed from OpenAiClient; narrow the v4 ResponseStreamEvent union by its `type` discriminator. + +- `OpenAiClient.ResponseMcpListToolsFailedEvent` -> `OpenAiClient.ResponseStreamEvent`: The standalone event helper was removed from OpenAiClient; narrow the v4 ResponseStreamEvent union by its `type` discriminator. + +- `OpenAiClient.ResponseMcpListToolsInProgressEvent` -> `OpenAiClient.ResponseStreamEvent`: The standalone event helper was removed from OpenAiClient; narrow the v4 ResponseStreamEvent union by its `type` discriminator. + +- `OpenAiClient.ResponseOutputItemAddedEvent` -> `Generated.ResponseOutputItemAddedEvent`: The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape. + +- `OpenAiClient.ResponseOutputItemDoneEvent` -> `Generated.ResponseOutputItemDoneEvent`: The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape. + +- `OpenAiClient.ResponseOutputTextAnnotationAddedEvent` -> `Generated.ResponseOutputTextAnnotationAddedEvent`: The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape. + +- `OpenAiClient.ResponseOutputTextDeltaEvent` -> `OpenAiClient.ResponseStreamEvent`: The standalone event helper was removed from OpenAiClient; narrow the v4 ResponseStreamEvent union by its `type` discriminator. + +- `OpenAiClient.ResponseOutputTextDoneEvent` -> `OpenAiClient.ResponseStreamEvent`: The standalone event helper was removed from OpenAiClient; narrow the v4 ResponseStreamEvent union by its `type` discriminator. + +- `OpenAiClient.ResponseQueuedEvent` -> `Generated.ResponseQueuedEvent`: The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape. + +- `OpenAiClient.ResponseReasoningSummaryPartAddedEvent` -> `Generated.ResponseReasoningSummaryPartAddedEvent`: The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape. + +- `OpenAiClient.ResponseReasoningSummaryPartDoneEvent` -> `Generated.ResponseReasoningSummaryPartDoneEvent`: The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape. + +- `OpenAiClient.ResponseReasoningSummaryTextDeltaEvent` -> `Generated.ResponseReasoningSummaryTextDeltaEvent`: The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape. + +- `OpenAiClient.ResponseReasoningSummaryTextDoneEvent` -> `Generated.ResponseReasoningSummaryTextDoneEvent`: The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape. + +- `OpenAiClient.ResponseReasoningTextDeltaEvent` -> `Generated.ResponseReasoningTextDeltaEvent`: The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape. + +- `OpenAiClient.ResponseReasoningTextDoneEvent` -> `Generated.ResponseReasoningTextDoneEvent`: The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape. + +- `OpenAiClient.ResponseRefusalDeltaEvent` -> `Generated.ResponseRefusalDeltaEvent`: The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape. + +- `OpenAiClient.ResponseRefusalDoneEvent` -> `Generated.ResponseRefusalDoneEvent`: The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape. + +- `OpenAiClient.ResponseStreamEvent` -> `OpenAiClient.ResponseStreamEvent`: Still exported in v4; adapt to the rewritten Responses API client and its revised schema and error types. + +- `OpenAiClient.ResponseWebSearchCallCompletedEvent` -> `Generated.ResponseWebSearchCallCompletedEvent`: The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape. + +- `OpenAiClient.ResponseWebSearchCallInProgressEvent` -> `Generated.ResponseWebSearchCallInProgressEvent`: The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape. + +- `OpenAiClient.ResponseWebSearchCallSearchingEvent` -> `Generated.ResponseWebSearchCallSearchingEvent`: The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape. + +- `OpenAiClient.Service` -> `OpenAiClient.Service`: Still exported in v4; adapt to the rewritten Responses API client and its revised schema and error types. + +- `OpenAiClient.StreamCompletionRequest` -> `OpenAiSchema.CreateResponse.Encoded`: The chat-completions request alias was removed; the v4 client uses the Responses API, with streaming inferred by OpenAiClient.createResponseStream. + +- `OpenAiClient.SummaryPart` -> `OpenAiSchema.SummaryTextContent`: The client-local reasoning summary schema moved to the focused v4 OpenAiSchema module. + +### `@effect/ai-openai/OpenAiConfig` + +- `OpenAiConfig.OpenAiConfig` -> `OpenAiConfig.OpenAiConfig`: Still exported in v4; update imports and adapt to the revised v4 service and schema types. + +- `OpenAiConfig.OpenAiConfig.Service` -> `OpenAiConfig.OpenAiConfig.Service`: Still exported in v4; update imports and adapt to the revised v4 service and schema types. + +### `@effect/ai-openai/OpenAiEmbeddingModel` + +- `OpenAiEmbeddingModel.Config` -> `OpenAiEmbeddingModel.Config`: Still exported in v4; update imports and adapt to the revised v4 service and schema types. + +- `OpenAiEmbeddingModel.Config.Batched` -> `OpenAiEmbeddingModel.Config.Service`: Batch-mode configuration was removed; use the unified embedding config and constructor. + +- `OpenAiEmbeddingModel.Config.DataLoader` -> `OpenAiEmbeddingModel.Config.Service`: Data-loader configuration was removed; use the unified embedding config and constructor. + +- `OpenAiEmbeddingModel.Config.Service` -> `OpenAiEmbeddingModel.Config.Service`: Still exported in v4; update imports and adapt to the revised v4 service and schema types. + +- `OpenAiEmbeddingModel.Model` -> `OpenAiEmbeddingModel.Model`: Still exported in v4; update imports and adapt to the revised v4 service and schema types. + +- `OpenAiEmbeddingModel.layerBatched` -> `OpenAiEmbeddingModel.layer`: The batched and data-loader layers were replaced by one embedding layer; pass the model and request config explicitly. + +- `OpenAiEmbeddingModel.layerDataLoader` -> `OpenAiEmbeddingModel.layer`: The batched and data-loader layers were replaced by one embedding layer; pass the model and request config explicitly. + +- `OpenAiEmbeddingModel.makeDataLoader` -> `OpenAiEmbeddingModel.make`: The dedicated data-loader constructor was removed; use the unified v4 embedding service constructor. + +### `@effect/ai-openai/OpenAiLanguageModel` + +- `OpenAiLanguageModel.Config` -> `OpenAiLanguageModel.Config`: Still exported in v4; update imports and adapt to the revised v4 service and schema types. + +- `OpenAiLanguageModel.Config.Service` -> `OpenAiLanguageModel.Config.Service`: Still exported in v4; update imports and adapt to the revised v4 service and schema types. + +- `OpenAiLanguageModel.Model` -> `OpenAiLanguageModel.Model`: Still exported in v4; update imports and adapt to the revised v4 service and schema types. + +- `OpenAiLanguageModel.ProviderMetadata` -> `Prompt.ProviderOptions / Response.ProviderMetadata`: The OpenAI metadata service wrapper was removed; v4 declares OpenAI-specific fields directly on Prompt and Response provider metadata. + +- `OpenAiLanguageModel.ProviderMetadata.Service` -> `Prompt.ProviderOptions / Response.ProviderMetadata`: The OpenAI metadata service wrapper was removed; v4 declares OpenAI-specific fields directly on Prompt and Response provider metadata. + +- `OpenAiLanguageModel.layerWithTokenizer` -> `OpenAiLanguageModel.layer`: The tokenizer-combining layer was removed; provide the language model and any Tokenizer service separately. + +- `OpenAiLanguageModel.modelWithTokenizer` -> `OpenAiLanguageModel.model`: The tokenizer-combining model was removed; use the v4 model descriptor and provide any Tokenizer service separately. + +### `@effect/ai-openai/OpenAiTelemetry` + +- `OpenAiTelemetry.AllAttributes` -> `OpenAiTelemetry.AllAttributes`: Still exported in v4; update imports and adapt to the revised v4 service and schema types. + +- `OpenAiTelemetry.OpenAiTelemetryAttributeOptions` -> `OpenAiTelemetry.OpenAiTelemetryAttributeOptions`: Still exported in v4; update imports and adapt to the revised v4 service and schema types. + +- `OpenAiTelemetry.addGenAIAnnotations` -> `OpenAiTelemetry.addGenAIAnnotations`: Still exported in v4; update imports and adapt to the revised v4 service and schema types. + +### `@effect/ai-openai/OpenAiTokenizer` + +- `OpenAiTokenizer.layer` -> `Tokenizer.make`: The provider-specific tokenizer module was removed; build and provide an effect/unstable/ai/Tokenizer service explicitly, using gpt-tokenizer if equivalent OpenAI counting is required. + +- `OpenAiTokenizer.make` -> `Tokenizer.make`: The provider-specific tokenizer module was removed; build and provide an effect/unstable/ai/Tokenizer service explicitly, using gpt-tokenizer if equivalent OpenAI counting is required. + +### `@effect/ai-openrouter/Generated` + +- `Generated.ActivityItem` -> `Generated.ActivityItem`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.AnnotationDetail` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.AnthropicMessagesRequest` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.AnthropicMessagesRequestProviderSort` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.AnthropicMessagesRequestRoute` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.AnthropicMessagesRequestServiceTier` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.AnthropicMessagesRequestThinkingEnumType` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.AnthropicMessagesRequestToolChoiceEnumType` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.AnthropicMessagesResponse` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.AnthropicMessagesResponseRole` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.AnthropicMessagesResponseStopReason` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.AnthropicMessagesResponseType` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.AnthropicMessagesResponseUsageServiceTier` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.AssistantMessage` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.BadGatewayResponse` -> `Generated.BadGatewayResponse`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BadGatewayResponseErrorData` -> `Generated.BadGatewayResponseErrorData`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BadRequestResponse` -> `Generated.BadRequestResponse`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BadRequestResponseErrorData` -> `Generated.BadRequestResponseErrorData`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BigNumberUnion` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.BulkAssignKeysToGuardrail200` -> `Generated.BulkAssignKeysToGuardrail200`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BulkAssignKeysToGuardrailRequest` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.BulkAssignMembersToGuardrail200` -> `Generated.BulkAssignMembersToGuardrail200`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BulkAssignMembersToGuardrailRequest` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.BulkUnassignKeysFromGuardrail200` -> `Generated.BulkUnassignKeysFromGuardrail200`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BulkUnassignKeysFromGuardrailRequest` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.BulkUnassignMembersFromGuardrail200` -> `Generated.BulkUnassignMembersFromGuardrail200`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BulkUnassignMembersFromGuardrailRequest` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CacheControlEphemeral` -> `Generated.ChatContentCacheControl`: Renamed when the OpenRouter client was regenerated from the current specification; re-check the replacement schema's Type/Encoded shape. + +- `Generated.ChatCompletionFinishReason` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ChatError` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ChatGenerationParams` -> `Generated.ChatRequest`: Renamed when the OpenRouter client was regenerated from the current specification; re-check the replacement schema's Type/Encoded shape. + +- `Generated.ChatGenerationParamsProviderEnumDataCollectionEnum` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ChatGenerationParamsReasoningEffortEnum` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ChatGenerationParamsRouteEnum` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ChatGenerationTokenUsage` -> `Generated.ChatUsage`: Renamed when the OpenRouter client was regenerated from the current specification; re-check the replacement schema's Type/Encoded shape. + +- `Generated.ChatMessageContentItem` -> `Generated.ChatContentItems`: Renamed when the OpenRouter client was regenerated from the current specification; re-check the replacement schema's Type/Encoded shape. + +- `Generated.ChatMessageContentItemAudio` -> `Generated.ChatContentAudio`: Renamed when the OpenRouter client was regenerated from the current specification; re-check the replacement schema's Type/Encoded shape. + +- `Generated.ChatMessageContentItemCacheControl` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ChatMessageContentItemCacheControlTtl` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ChatMessageContentItemImage` -> `Generated.ChatContentImage`: Renamed when the OpenRouter client was regenerated from the current specification; re-check the replacement schema's Type/Encoded shape. + +- `Generated.ChatMessageContentItemImageImageUrlDetail` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ChatMessageContentItemText` -> `Generated.ChatContentText`: Renamed when the OpenRouter client was regenerated from the current specification; re-check the replacement schema's Type/Encoded shape. + +- `Generated.ChatMessageContentItemVideo` -> `Generated.ChatContentVideo`: Renamed when the OpenRouter client was regenerated from the current specification; re-check the replacement schema's Type/Encoded shape. + +- `Generated.ChatMessageTokenLogprob` -> `Generated.ChatTokenLogprob`: Renamed when the OpenRouter client was regenerated from the current specification; re-check the replacement schema's Type/Encoded shape. + +- `Generated.ChatMessageTokenLogprobs` -> `Generated.ChatTokenLogprobs`: Renamed when the OpenRouter client was regenerated from the current specification; re-check the replacement schema's Type/Encoded shape. + +- `Generated.ChatMessageToolCall` -> `Generated.ChatToolCall`: Renamed when the OpenRouter client was regenerated from the current specification; re-check the replacement schema's Type/Encoded shape. + +- `Generated.ChatResponse` -> `Generated.ChatResult`: Renamed when the OpenRouter client was regenerated from the current specification; re-check the replacement schema's Type/Encoded shape. + +- `Generated.ChatResponseChoice` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ChatStreamOptions` -> `Generated.ChatStreamOptions`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.Client` -> `Generated.OpenRouterClient`: Renamed when the OpenRouter client was regenerated from the current specification; re-check the replacement schema's Type/Encoded shape. + +- `Generated.ClientError` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CompletionChoice` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CompletionCreateParams` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CompletionFinishReason` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CompletionFinishReasonEnum` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CompletionLogprobs` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CompletionResponse` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CompletionUsage` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateAuthKeysCode200` -> `Generated.CreateAuthKeysCode200`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateAuthKeysCodeRequest` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateAuthKeysCodeRequestCodeChallengeMethod` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateChargeRequest` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateChargeRequestChainId` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateCoinbaseCharge200` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateEmbeddings200` -> `Generated.CreateEmbeddings200`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateEmbeddings200Object` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateEmbeddingsRequest` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateEmbeddingsRequestEncodingFormat` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateGuardrail201` -> `Generated.CreateGuardrail201`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateGuardrail201DataResetInterval` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateGuardrailRequest` -> `Generated.CreateGuardrailRequest`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateGuardrailRequestResetInterval` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateKeys201` -> `Generated.CreateKeys201`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateKeysRequest` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateKeysRequestLimitReset` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateMessages400` -> `Generated.CreateMessages400`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateMessages400Type` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateMessages401` -> `Generated.CreateMessages401`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateMessages401Type` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateMessages403` -> `Generated.CreateMessages403`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateMessages403Type` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateMessages404` -> `Generated.CreateMessages404`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateMessages404Type` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateMessages429` -> `Generated.CreateMessages429`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateMessages429Type` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateMessages500` -> `Generated.CreateMessages500`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateMessages500Type` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateMessages503` -> `Generated.CreateMessages503`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateMessages503Type` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateMessages529` -> `Generated.CreateMessages529`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateMessages529Type` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.DataCollection` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.DefaultParameters` -> `Generated.DefaultParameters`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.DeleteGuardrail200` -> `Generated.DeleteGuardrail200`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.DeleteKeys200` -> `Generated.DeleteKeys200`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.EdgeNetworkTimeoutResponse` -> `Generated.EdgeNetworkTimeoutResponse`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.EdgeNetworkTimeoutResponseErrorData` -> `Generated.EdgeNetworkTimeoutResponseErrorData`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.EndpointStatus` -> `Generated.EndpointStatus`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ExchangeAuthCodeForAPIKey200` -> `Generated.ExchangeAuthCodeForAPIKey200`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ExchangeAuthCodeForAPIKeyRequest` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ExchangeAuthCodeForAPIKeyRequestCodeChallengeMethod` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FileAnnotationDetail` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FileCitation` -> `Generated.FileCitation`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.FileCitationType` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FilePath` -> `Generated.FilePath`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.FilePathType` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ForbiddenResponse` -> `Generated.ForbiddenResponse`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ForbiddenResponseErrorData` -> `Generated.ForbiddenResponseErrorData`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.GetCredits200` -> `Generated.GetCredits200`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.GetCurrentKey200` -> `Generated.GetCurrentKey200`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.GetGeneration200` -> `Generated.GetGeneration200`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.GetGeneration200DataApiType` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.GetGenerationParams` -> `Generated.GetGenerationParams`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.GetGuardrail200` -> `Generated.GetGuardrail200`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.GetGuardrail200DataResetInterval` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.GetKey200` -> `Generated.GetKey200`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.GetModelsParams` -> `Generated.GetModelsParams`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.GetModelsParamsCategory` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.GetUserActivity200` -> `Generated.GetUserActivity200`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.GetUserActivityParams` -> `Generated.GetUserActivityParams`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ImageGenerationStatus` -> `Generated.ImageGenerationStatus`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.InputModality` -> `Generated.InputModality`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.InternalServerResponse` -> `Generated.InternalServerResponse`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.InternalServerResponseErrorData` -> `Generated.InternalServerResponseErrorData`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.JSONSchemaConfig` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.List200` -> `Generated.List200`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListEndpoints200` -> `Generated.ListEndpoints200`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListEndpointsResponse` -> `Generated.ListEndpointsResponse`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListEndpointsResponseArchitecture` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ListEndpointsResponseArchitectureEnumInstructType` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ListEndpointsZdr200` -> `Generated.ListEndpointsZdr200`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListGuardrailKeyAssignments200` -> `Generated.ListGuardrailKeyAssignments200`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListGuardrailKeyAssignmentsParams` -> `Generated.ListGuardrailKeyAssignmentsParams`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListGuardrailMemberAssignments200` -> `Generated.ListGuardrailMemberAssignments200`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListGuardrailMemberAssignmentsParams` -> `Generated.ListGuardrailMemberAssignmentsParams`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListGuardrails200` -> `Generated.ListGuardrails200`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListGuardrailsParams` -> `Generated.ListGuardrailsParams`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListKeyAssignments200` -> `Generated.ListKeyAssignments200`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListKeyAssignmentsParams` -> `Generated.ListKeyAssignmentsParams`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListMemberAssignments200` -> `Generated.ListMemberAssignments200`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListMemberAssignmentsParams` -> `Generated.ListMemberAssignmentsParams`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListParams` -> `Generated.ListParams`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListProviders200` -> `Generated.ListProviders200`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.Message` -> `Generated.ChatMessages`: Renamed when the OpenRouter client was regenerated from the current specification; re-check the replacement schema's Type/Encoded shape. + +- `Generated.Model` -> `Generated.Model`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ModelArchitecture` -> `Generated.ModelArchitecture`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ModelArchitectureInstructType` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ModelGroup` -> `Generated.ModelGroup`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ModelName` -> `Generated.ModelName`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ModelsCountResponse` -> `Generated.ModelsCountResponse`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ModelsListResponse` -> `Generated.ModelsListResponse`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ModelsListResponseData` -> `Generated.ModelsListResponseData`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.NamedToolChoice` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.NotFoundResponse` -> `Generated.NotFoundResponse`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.NotFoundResponseErrorData` -> `Generated.NotFoundResponseErrorData`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.OpenAIResponsesAnnotation` -> `Generated.OpenAIResponsesAnnotation`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.OpenAIResponsesIncludable` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenAIResponsesIncompleteDetails` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenAIResponsesIncompleteDetailsReason` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenAIResponsesInput` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenAIResponsesPrompt` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenAIResponsesReasoningConfig` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenAIResponsesReasoningEffort` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenAIResponsesRefusalContent` -> `Generated.OpenAIResponsesRefusalContent`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.OpenAIResponsesRefusalContentType` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenAIResponsesResponseStatus` -> `Generated.OpenAIResponsesResponseStatus`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.OpenAIResponsesServiceTier` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenAIResponsesToolChoice` -> `Generated.OpenAIResponsesToolChoice`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.OpenAIResponsesToolChoiceEnum` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenAIResponsesToolChoiceEnumType` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenAIResponsesToolChoiceEnumTypeEnum` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenAIResponsesTruncation` -> `Generated.OpenAIResponsesTruncation`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.OpenAIResponsesUsage` -> `Generated.OpenAIResponsesUsage`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.OpenResponsesEasyInputMessage` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenResponsesEasyInputMessageRoleEnum` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenResponsesEasyInputMessageType` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenResponsesFunctionCallOutput` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenResponsesFunctionCallOutputType` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenResponsesFunctionToolCall` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenResponsesFunctionToolCallType` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenResponsesInput` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenResponsesInputMessageItem` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenResponsesInputMessageItemRoleEnum` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenResponsesInputMessageItemType` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenResponsesNonStreamingResponse` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenResponsesNonStreamingResponseObject` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenResponsesReasoning` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenResponsesReasoningConfig` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenResponsesReasoningFormat` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenResponsesReasoningStatusEnum` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenResponsesReasoningType` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenResponsesRequest` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenResponsesRequestMetadata` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenResponsesRequestRoute` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenResponsesRequestServiceTier` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenResponsesRequestTruncation` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenResponsesRequestTruncationEnum` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenResponsesResponseText` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenResponsesResponseTextVerbosity` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenResponsesWebSearch20250826Tool` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenResponsesWebSearch20250826ToolType` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenResponsesWebSearchPreview20250311Tool` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenResponsesWebSearchPreview20250311ToolType` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenResponsesWebSearchPreviewTool` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenResponsesWebSearchPreviewToolType` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenResponsesWebSearchTool` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenResponsesWebSearchToolType` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenRouterAnthropicMessageParam` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenRouterAnthropicMessageParamRole` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OutputItemFileSearchCall` -> `Generated.OutputItemFileSearchCall`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.OutputItemFileSearchCallType` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OutputItemFunctionCall` -> `Generated.OutputItemFunctionCall`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.OutputItemFunctionCallStatusEnum` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OutputItemFunctionCallType` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OutputItemImageGenerationCall` -> `Generated.OutputItemImageGenerationCall`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.OutputItemImageGenerationCallType` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OutputItemReasoning` -> `Generated.OutputItemReasoning`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.OutputItemReasoningStatusEnum` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OutputItemReasoningType` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OutputItemWebSearchCall` -> `Generated.OutputItemWebSearchCall`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.OutputItemWebSearchCallType` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OutputMessage` -> `Generated.OutputMessage`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.OutputMessageRole` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OutputMessageStatusEnum` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OutputMessageType` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OutputModality` -> `Generated.OutputModality`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.PDFParserEngine` -> `Generated.PDFParserEngine`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.PDFParserOptions` -> `Generated.PDFParserOptions`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.Parameter` -> `Generated.Parameter`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.PayloadTooLargeResponse` -> `Generated.PayloadTooLargeResponse`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.PayloadTooLargeResponseErrorData` -> `Generated.PayloadTooLargeResponseErrorData`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.PaymentRequiredResponse` -> `Generated.PaymentRequiredResponse`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.PaymentRequiredResponseErrorData` -> `Generated.PaymentRequiredResponseErrorData`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.PerRequestLimits` -> `Generated.PerRequestLimits`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.PercentileLatencyCutoffs` -> `Generated.PercentileLatencyCutoffs`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.PercentileStats` -> `Generated.PercentileStats`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.PercentileThroughputCutoffs` -> `Generated.PercentileThroughputCutoffs`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.PreferredMaxLatency` -> `Generated.PreferredMaxLatency`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.PreferredMinThroughput` -> `Generated.PreferredMinThroughput`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ProviderName` -> `Generated.ProviderName`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ProviderOverloadedResponse` -> `Generated.ProviderOverloadedResponse`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ProviderOverloadedResponseErrorData` -> `Generated.ProviderOverloadedResponseErrorData`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ProviderPreferences` -> `Generated.ProviderPreferences`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ProviderPreferencesSort` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ProviderSort` -> `Generated.ProviderSort`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ProviderSortConfig` -> `Generated.ProviderSortConfig`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ProviderSortConfigPartitionEnum` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ProviderSortUnion` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.PublicEndpoint` -> `Generated.PublicEndpoint`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.PublicEndpointQuantization` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.PublicEndpointQuantizationEnum` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.PublicEndpointThroughputLast30M` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.PublicPricing` -> `Generated.PublicPricing`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.Quantization` -> `Generated.Quantization`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ReasoningDetail` -> `Generated.ReasoningDetailUnion`: Renamed when the OpenRouter client was regenerated from the current specification; re-check the replacement schema's Type/Encoded shape. + +- `Generated.ReasoningDetailEncrypted` -> `Generated.ReasoningDetailEncrypted`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ReasoningDetailSummary` -> `Generated.ReasoningDetailSummary`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ReasoningDetailText` -> `Generated.ReasoningDetailText`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ReasoningSummaryText` -> `Generated.ReasoningSummaryText`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ReasoningSummaryTextType` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ReasoningSummaryVerbosity` -> `Generated.ReasoningSummaryVerbosity`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ReasoningTextContent` -> `Generated.ReasoningTextContent`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ReasoningTextContentType` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RequestTimeoutResponse` -> `Generated.RequestTimeoutResponse`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RequestTimeoutResponseErrorData` -> `Generated.RequestTimeoutResponseErrorData`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ResponseFormatJSONSchema` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponseFormatTextConfig` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponseFormatTextGrammar` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponseInputAudio` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponseInputAudioInputAudioFormat` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponseInputAudioType` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponseInputFile` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponseInputFileType` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponseInputImage` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponseInputImageDetail` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponseInputImageType` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponseInputText` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponseInputTextType` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponseInputVideo` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponseInputVideoType` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponseOutputText` -> `Generated.ResponseOutputText`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ResponseOutputTextType` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponseTextConfig` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponseTextConfigVerbosity` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponsesErrorField` -> `Generated.ResponsesErrorField`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ResponsesErrorFieldCode` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponsesFormatJSONObject` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponsesFormatJSONObjectType` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponsesFormatText` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponsesFormatTextJSONSchemaConfig` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponsesFormatTextJSONSchemaConfigType` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponsesFormatTextType` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponsesImageGenerationCall` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponsesImageGenerationCallType` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponsesOutputItemFileSearchCall` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponsesOutputItemFileSearchCallType` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponsesOutputItemFunctionCall` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponsesOutputItemFunctionCallStatusEnum` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponsesOutputItemFunctionCallType` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponsesOutputItemReasoning` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponsesOutputItemReasoningFormat` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponsesOutputItemReasoningStatusEnum` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponsesOutputItemReasoningType` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponsesOutputMessage` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponsesOutputMessageRole` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponsesOutputMessageStatusEnum` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponsesOutputMessageType` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponsesOutputModality` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponsesSearchContextSize` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponsesWebSearchCallOutput` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponsesWebSearchCallOutputType` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponsesWebSearchUserLocation` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponsesWebSearchUserLocationType` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.Schema0` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.Schema1` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.Schema2` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.Schema3` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.Schema4` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.Schema4Enum` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.Schema5` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.Schema6` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ServiceUnavailableResponse` -> `Generated.ServiceUnavailableResponse`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ServiceUnavailableResponseErrorData` -> `Generated.ServiceUnavailableResponseErrorData`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.SystemMessage` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.TooManyRequestsResponse` -> `Generated.TooManyRequestsResponse`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.TooManyRequestsResponseErrorData` -> `Generated.TooManyRequestsResponseErrorData`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ToolCallStatus` -> `Generated.ToolCallStatus`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ToolChoiceOption` -> `Generated.ChatToolChoice`: Renamed when the OpenRouter client was regenerated from the current specification; re-check the replacement schema's Type/Encoded shape. + +- `Generated.ToolDefinitionJson` -> `Generated.ChatFunctionTool`: Renamed when the OpenRouter client was regenerated from the current specification; re-check the replacement schema's Type/Encoded shape. + +- `Generated.ToolResponseMessage` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.TopProviderInfo` -> `Generated.TopProviderInfo`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.URLCitation` -> `Generated.URLCitation`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.URLCitationAnnotationDetail` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.URLCitationType` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UnauthorizedResponse` -> `Generated.UnauthorizedResponse`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.UnauthorizedResponseErrorData` -> `Generated.UnauthorizedResponseErrorData`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.UnprocessableEntityResponse` -> `Generated.UnprocessableEntityResponse`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.UnprocessableEntityResponseErrorData` -> `Generated.UnprocessableEntityResponseErrorData`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.UpdateGuardrail200` -> `Generated.UpdateGuardrail200`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.UpdateGuardrail200DataResetInterval` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UpdateGuardrailRequest` -> `Generated.UpdateGuardrailRequest`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.UpdateGuardrailRequestResetInterval` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UpdateKeys200` -> `Generated.UpdateKeys200`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.UpdateKeysRequest` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UpdateKeysRequestLimitReset` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UserMessage` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.WebSearchEngine` -> `Generated.WebSearchEngine`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.WebSearchPreviewToolUserLocation` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.WebSearchPreviewToolUserLocationType` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.WebSearchStatus` -> `Generated.WebSearchStatus`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.make` -> `Generated.make`: Still exported in v4, but the regenerated OpenRouter client has different operations and request/response schemas; update call sites to the current generated service. + +### `@effect/ai-openrouter/OpenRouterClient` + +- `OpenRouterClient.ChatStreamingChoice` -> `Generated.ChatStreamChoice`: The client-local streaming choice schema moved into the regenerated OpenRouter schema surface and changed shape. + +- `OpenRouterClient.ChatStreamingMessageChunk` -> `Generated.ChatStreamDelta`: The client-local streaming message delta moved into the regenerated OpenRouter schema surface and changed shape. + +- `OpenRouterClient.ChatStreamingMessageToolCall` -> `Generated.ChatStreamToolCall`: The client-local streaming tool-call delta moved into the regenerated OpenRouter schema surface and changed shape. + +- `OpenRouterClient.ChatStreamingResponseChunk` -> `OpenRouterClient.ChatStreamingResponseChunkData`: The standalone streaming chunk schema was replaced by the decoded data type from Generated.ChatStreamingResponse. + +- `OpenRouterClient.Service` -> `OpenRouterClient.Service`: Still exported in v4; adapt to the regenerated client, revised request and response schemas, and the new streaming result tuple. + +### `@effect/ai-openrouter/OpenRouterConfig` + +- `OpenRouterConfig.OpenRouterConfig` -> `OpenRouterConfig.OpenRouterConfig`: Still exported in v4; update imports to the v4 package and use the revised Context.Service-based configuration service. + +- `OpenRouterConfig.OpenRouterConfig.Service` -> `OpenRouterConfig.OpenRouterConfig.Service`: Still exported in v4; update imports to the v4 package and use the revised Context.Service-based configuration service. + +### `@effect/ai-openrouter/OpenRouterLanguageModel` + +- `OpenRouterLanguageModel.Config` -> `OpenRouterLanguageModel.Config`: Still exported in v4; update imports and adapt to the regenerated chat request schema and revised Context.Service configuration. + +- `OpenRouterLanguageModel.Config.Service` -> `OpenRouterLanguageModel.Config.Service`: Still exported in v4; update imports and adapt to the regenerated chat request schema and revised Context.Service configuration. + +- `OpenRouterLanguageModel.OpenRouterReasoningInfo` -> `OpenRouterLanguageModel.ReasoningDetails`: The bespoke reasoning-info union was replaced by the provider's raw reasoning-details array, preserved through Prompt options and Response metadata. + +### `@effect/ai/AiError` + +- `AiError.AiError` -> `AiError.AiError`: Moved to effect/unstable/ai/AiError and redesigned from a union of separately tagged errors into one AiError wrapper with a semantic reason. Construct it with AiError.make({ module, method, reason }) and match error.reason rather than the old top-level tags. + +- `AiError.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. + +- `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`: Use the moved constructor; --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. It now returns Envelope.Partial; 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 in custom encoded storage implementations. + +- `MessageStorage.make` -> `effect/unstable/cluster/MessageStorage#make`: Moved into core Effect. Context service projections now use the Service property instead of Type. + +### `@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). + +- `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. + +### `@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 with the same callbacks and requirements; 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 with the same complete defaults; service type projections now use Service instead of Type. + +- `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.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 and explicit capability flags. + +- `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.head` -> `Effect.flatMap + Array.head + Effect.fromOption`: Inspect the produced iterable explicitly and fail when it is empty. Adapt arguments and imports to the v4 API. + +- `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.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.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.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.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.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/package.json b/repos/effect/package.json index d7cd5b113d..60c4f2617c 100644 --- a/repos/effect/package.json +++ b/repos/effect/package.json @@ -3,104 +3,82 @@ "type": "module", "packageManager": "pnpm@10.17.1", "scripts": { + "prepare": "node scripts/setup-agents.mjs && effect-tsgo patch", "clean": "node scripts/clean.mjs", "codegen": "pnpm --recursive --parallel --filter \"./packages/**/*\" run codegen", "codemod": "node scripts/codemod.mjs", - "build": "tspc -b tsconfig.packages.json && pnpm --recursive --parallel --filter \"./packages/**/*\" run build", - "build:tsgo": "tsgo -b tsconfig.packages.json && pnpm --recursive --parallel --filter \"./packages/**/*\" run build:tsgo", + "build": "tsc -b tsconfig.packages.json && pnpm --recursive --parallel --filter \"./packages/**/*\" run build && node scripts/copy-ai-docs.mjs", "bundle-analyze": "bash scripts/bundle-analyze.sh", "bundle-compare": "bash scripts/bundle-compare.sh", "bundle-compare-selected": "bash scripts/bundle-compare-selected.sh", "circular": "node scripts/circular.mjs", "test": "vitest", + "test-cluster": "vitest run --project cluster-integration", + "doctest": "vitest --config vitest.docs.ts", "coverage": "vitest --coverage", - "check": "tspc -b tsconfig.json", - "check:tsgo": "tsgo -b tsconfig.json", + "check": "tsc -b tsconfig.json", "typeperf": "pnpm --dir packages/effect exec node typeperf/run.mjs", - "check-recursive": "pnpm --recursive --filter \"./packages/**/*\" exec tspc -b tsconfig.json", + "typeperf-compare": "pnpm --dir packages/effect exec node typeperf/compare.mjs", + "runtimeperf": "pnpm --dir packages/effect exec node runtimeperf/run.mts", + "runtimeperf-compare": "pnpm --dir packages/effect exec node runtimeperf/compare.mts", + "check-recursive": "pnpm --recursive --filter \"./packages/**/*\" exec tsc -b tsconfig.json", "jsdocs": "effect-jsdocs", - "lint": "pnpm jsdocs && oxlint -f unix && dprint check", - "lint-fix": "pnpm jsdocs && oxlint --fix && dprint fmt", - "docgen": "pnpm --recursive --filter \"./packages/**/*\" exec docgen && node scripts/docs.mjs", + "lint": "oxlint -f unix && dprint check", + "lint-fix": "oxlint --fix && dprint fmt", "ai-docgen": "effect-ai-docgen ai-docs/src -o LLMS.md", "ai-docgen:watch": "pnpm ai-docgen --watch", - "test-types": "tstyche", + "api-diff": "pnpm --dir packages/tools/api-diff exec node src/bin.ts", + "test-types": "tstyche --target '>=5.9'", "changeset-version": "changeset version", - "changeset-publish": "pnpm codemod && pnpm build && changeset publish", - "prepare": "effect-tsgo patch" + "changeset-publish": "node scripts/set-strip-internal.mjs && pnpm codemod && pnpm build && changeset publish" }, "devDependencies": { - "@babel/cli": "^7.28.6", - "@babel/core": "^7.29.0", - "@babel/plugin-transform-export-namespace-from": "^7.27.1", - "@babel/plugin-transform-modules-commonjs": "^7.28.6", - "@changesets/changelog-github": "^0.7.0", - "@changesets/cli": "^2.31.0", + "@babel/cli": "^8.0.4", + "@babel/core": "^8.0.1", + "@babel/plugin-transform-export-namespace-from": "^8.0.1", + "@babel/plugin-transform-modules-commonjs": "^8.0.1", + "@changesets/changelog-github": "1.0.0-next.9", + "@changesets/cli": "3.0.0-next.11", "@effect/ai-docgen": "workspace:^", "@effect/bundle": "workspace:^", - "@effect/docgen": "https://pkg.pr.new/Effect-TS/docgen/@effect/docgen@e57e5f5", + "@effect/docgen": "workspace:^", + "@effect/doctest": "workspace:^", "@effect/jsdocs": "workspace:^", - "@effect/language-service": "^0.85.1", "@effect/oxc": "workspace:^", - "@effect/tsgo": "^0.7.2", + "@effect/tsgo": "^0.21.0", "@effect/utils": "workspace:^", "@effect/vitest": "workspace:^", - "@faker-js/faker": "^10.4.0", + "@faker-js/faker": "^10.5.0", "@rollup/plugin-node-resolve": "^16.0.3", "@rollup/plugin-replace": "^6.0.3", "@rollup/plugin-terser": "^1.0.0", "@types/jscodeshift": "^17.3.0", - "@types/node": "^25.7.0", - "@typescript/native-preview": "7.0.0-dev.20260512.1", - "@vitest/browser": "4.1.6", - "@vitest/coverage-v8": "4.1.6", - "@vitest/expect": "4.1.6", - "@vitest/web-worker": "4.1.6", + "@types/node": "^26.1.2", + "@vitest/browser": "^4.1.10", + "@vitest/coverage-v8": "^4.1.10", + "@vitest/expect": "^4.1.10", + "@vitest/web-worker": "^4.1.10", "ast-types": "^0.14.2", "babel-plugin-annotate-pure-calls": "^0.5.0", - "dprint": "^0.54.0", + "dprint": "^0.55.2", "glob": "^13.0.6", - "happy-dom": "^20.9.0", - "jscodeshift": "^17.3.0", - "lalph": "^0.3.137", + "happy-dom": "^20.11.1", + "jscodeshift": "^17.4.0", + "lalph": "^0.3.139", "madge": "^8.0.0", - "oxlint": "1.42.0", - "playwright": "^1.60.0", - "rollup": "^4.60.3", - "rollup-plugin-bundle-stats": "^4.22.1", + "oxlint": "^1.76.0", + "pkg-pr-new": "0.0.78", + "playwright": "^1.62.0", + "rollup": "^4.62.3", + "rollup-plugin-bundle-stats": "^4.22.2", "rollup-plugin-esbuild": "^6.2.1", "rollup-plugin-visualizer": "^7.0.1", - "terser": "^5.47.1", - "ts-patch": "^4.0.1", - "tstyche": "^7.1.0", - "typescript": "^6.0.3", - "vite": "^7.3.2", - "vite-tsconfig-paths": "^6.1.1", - "vitest": "4.1.4", - "vitest-websocket-mock": "^0.5.0", + "terser": "^5.49.0", + "tstyche": "^7.2.2", + "typescript": "^7.0.2", + "vite": "^8.1.5", + "vitest": "^4.1.10", + "vitest-websocket-mock": "^0.7.0", "zod": "^4.4.3" - }, - "pnpm": { - "patchedDependencies": { - "@changesets/assemble-release-plan": "patches/@changesets__assemble-release-plan.patch", - "@changesets/get-github-info": "patches/@changesets__get-github-info.patch" - }, - "ignoredBuiltDependencies": [ - "core-js", - "cpu-features", - "dprint", - "esbuild", - "lmdb", - "msgpackr-extract", - "msw", - "protobufjs", - "sharp", - "ssh2", - "unrs-resolver", - "workerd" - ], - "onlyBuiltDependencies": [ - "better-sqlite3" - ] } } diff --git a/repos/effect/packages/ai/anthropic/CHANGELOG.md b/repos/effect/packages/ai/anthropic/CHANGELOG.md index b75b6cb708..04e12fce9f 100644 --- a/repos/effect/packages/ai/anthropic/CHANGELOG.md +++ b/repos/effect/packages/ai/anthropic/CHANGELOG.md @@ -1,5 +1,153 @@ # @effect/ai-anthropic +## 4.0.0-beta.107 + +### Patch Changes + +- Updated dependencies [[`596f3f9`](https://github.com/Effect-TS/effect/commit/596f3f92d7fe355811b815cb212332b082268ce8), [`9611ed4`](https://github.com/Effect-TS/effect/commit/9611ed42d11300546b339ab13492a0f7bdb1ebfb), [`8b91605`](https://github.com/Effect-TS/effect/commit/8b9160548556e4b0ec7ee2f2707716776be49018), [`d901928`](https://github.com/Effect-TS/effect/commit/d901928efa44f573ed1247f53fdb203a8e4fcede), [`b32bdef`](https://github.com/Effect-TS/effect/commit/b32bdef0d119a1ad1463dc01a46763ffee1f9bd9)]: + - effect@4.0.0-beta.107 + +## 4.0.0-beta.106 + +### Patch Changes + +- [#7129](https://github.com/Effect-TS/effect/pull/7129) [`d0baed9`](https://github.com/Effect-TS/effect/commit/d0baed9d72c5191d5ab3945ffe464b9515d0ac53) Thanks @mkdynamic! - Correct the maximum output tokens for Claude Opus 4.6, 4.7, 4.8 and Sonnet 4.6. + + These models were grouped with the 4.5 family at 64000 output tokens, half of the 128000 the API actually allows, so requests defaulted to a cap far below the model's real limit. The 4.5 models keep 64000, which is correct for them. +- Updated dependencies [[`2695168`](https://github.com/Effect-TS/effect/commit/269516851b24916d72771f8a554b88722e3732e7), [`6310a8c`](https://github.com/Effect-TS/effect/commit/6310a8c68c74dcf1d23948ec9243ac5f407a1651), [`c2071b1`](https://github.com/Effect-TS/effect/commit/c2071b1647e2326568c1d0689274ef62b8a7183f), [`7aff81a`](https://github.com/Effect-TS/effect/commit/7aff81a9cefe681483ef8abf717d786fd10e7e8d), [`a1d4057`](https://github.com/Effect-TS/effect/commit/a1d4057711935a544ef441bc2d0ac3565dfa9266), [`abf77b0`](https://github.com/Effect-TS/effect/commit/abf77b04009dcb4d67a258f9d8ada778e9f4ffae), [`6c60375`](https://github.com/Effect-TS/effect/commit/6c60375e68683a32d54554150cc493e16550a06d), [`22f4897`](https://github.com/Effect-TS/effect/commit/22f4897bbae24783d4516f6bef353f1db4ec6d03), [`615d1d5`](https://github.com/Effect-TS/effect/commit/615d1d5d0256ec8160f2e08d0dcf5dc83acb7bf1), [`3a86757`](https://github.com/Effect-TS/effect/commit/3a867573ddeed5888dabdeb3225a9ebbf00491e7), [`f4a9762`](https://github.com/Effect-TS/effect/commit/f4a9762bb9dfad59c215f2e099dcc829d74f4ed1), [`0bcf6ed`](https://github.com/Effect-TS/effect/commit/0bcf6ed57c22e8a36964726b15464101d90f5997), [`ba9cb63`](https://github.com/Effect-TS/effect/commit/ba9cb63b87d45ce2df872dd8ef0905da147cc675), [`42c810d`](https://github.com/Effect-TS/effect/commit/42c810dd372275b822dd99c7d7e774e153f0a752), [`1416ccd`](https://github.com/Effect-TS/effect/commit/1416ccd474bc9da8979f51b72b5e53fb3ac56edf), [`08d0d39`](https://github.com/Effect-TS/effect/commit/08d0d39a225deccb9db213ab5fcf55edb9f9ba5d), [`548908a`](https://github.com/Effect-TS/effect/commit/548908a71d9337cb7defe7fc93b2fba8f6a04b6f), [`4b3460d`](https://github.com/Effect-TS/effect/commit/4b3460daa434ec465a95a50704fe1103a9275999), [`d170596`](https://github.com/Effect-TS/effect/commit/d17059615cca37ca2776654078fe0501ac5202e6), [`aea89d0`](https://github.com/Effect-TS/effect/commit/aea89d0c42ee0ac707a4962cd348fd3158cb469b), [`deed5fb`](https://github.com/Effect-TS/effect/commit/deed5fbdc91cf8bf8c5fce7dfa5d6527ac944726)]: + - effect@4.0.0-beta.106 + +## 4.0.0-beta.105 + +### Patch Changes + +- Updated dependencies [[`0418564`](https://github.com/Effect-TS/effect/commit/04185644dabb8e4169f1ef6cbbc0b36c4db2f7f4), [`d334a85`](https://github.com/Effect-TS/effect/commit/d334a8593aafcd60753995a9449b654c67bfdcc1), [`f0be855`](https://github.com/Effect-TS/effect/commit/f0be8554da6ee00293a6b23869ac46a0b0d97dc8), [`b206fa5`](https://github.com/Effect-TS/effect/commit/b206fa5d7655c1634c9993410a9203f6616a5ca2), [`b938c8a`](https://github.com/Effect-TS/effect/commit/b938c8ad2823bd88493187922f7d9090eff037b6), [`8525f05`](https://github.com/Effect-TS/effect/commit/8525f05d1e14ea12298e9e1a0df497bfaac2ce9a)]: + - effect@4.0.0-beta.105 + +## 4.0.0-beta.104 + +### Patch Changes + +- [#7070](https://github.com/Effect-TS/effect/pull/7070) [`a5404d4`](https://github.com/Effect-TS/effect/commit/a5404d4f4361350b6ecbb1a2f601365852141f60) Thanks @fubhy! - Decode byte-backed plain-text attachments as UTF-8 text in Anthropic requests. +- Updated dependencies [[`1001bcc`](https://github.com/Effect-TS/effect/commit/1001bccb9e874918d59dbb36860f1c5d4499ac20), [`993ba60`](https://github.com/Effect-TS/effect/commit/993ba60ee6c7ca6eb84522040f8b0d268b6ba7d4), [`67faacd`](https://github.com/Effect-TS/effect/commit/67faacd4679242559bee31717c05a5b10b990322), [`b78acdf`](https://github.com/Effect-TS/effect/commit/b78acdf422568f10ae8684fd3f10d52b065f0b56), [`fbb9ce5`](https://github.com/Effect-TS/effect/commit/fbb9ce5e625d1a7d6b7005bda42cbb6cd31476c5), [`722ea48`](https://github.com/Effect-TS/effect/commit/722ea484c9d01364c9242d929c0a564f7831a57c), [`3058fd5`](https://github.com/Effect-TS/effect/commit/3058fd594f5a683034212d71d957017fcc084006), [`62d0575`](https://github.com/Effect-TS/effect/commit/62d057566c241405c23ecf0bf4156186bd2be924), [`99dd6b5`](https://github.com/Effect-TS/effect/commit/99dd6b580434f97c5b40adc919f429e4abc3dfe7), [`7963ce1`](https://github.com/Effect-TS/effect/commit/7963ce1cd95f037fbefea67a29ead49cce4d16cb), [`af14e75`](https://github.com/Effect-TS/effect/commit/af14e752edd65e2b652e960411afafc88975a8d8), [`24e22d2`](https://github.com/Effect-TS/effect/commit/24e22d23a73a2e93ebf6d8edd2246a4a406942c8), [`647d14e`](https://github.com/Effect-TS/effect/commit/647d14e572c8004fa92fba256e00552b42bf34b7), [`1434eec`](https://github.com/Effect-TS/effect/commit/1434eecbd368e00839c24b3950f0b7a69218669a), [`a5278b1`](https://github.com/Effect-TS/effect/commit/a5278b18242011d1b2b08304c7c128151f9a4370), [`6af04a5`](https://github.com/Effect-TS/effect/commit/6af04a50bd019238f6acdb9cbda40439a3c09210), [`cb6c837`](https://github.com/Effect-TS/effect/commit/cb6c8376b2f322d4e7cbfc0973fc3b4f2951ee6e), [`d44cead`](https://github.com/Effect-TS/effect/commit/d44cead7e0e0ce61f0d980906e494f49a07e7899), [`88c7632`](https://github.com/Effect-TS/effect/commit/88c7632c2b59a49fcc40d250865bd8d0dccf31b0), [`abcbb2a`](https://github.com/Effect-TS/effect/commit/abcbb2abe16f1b6c587c15007df14371e1e70e93), [`8f63cce`](https://github.com/Effect-TS/effect/commit/8f63cce636700fde26b140b82e350ef916989d86), [`d56dfcf`](https://github.com/Effect-TS/effect/commit/d56dfcf54c2b9c53c3d098ce4b0ffcc84496c5f7), [`a98cda9`](https://github.com/Effect-TS/effect/commit/a98cda9422e1352f22e81696f759f326ffcfb667), [`6704bb8`](https://github.com/Effect-TS/effect/commit/6704bb84c320547f83cf50e8586ffc4c5e4c3cc5), [`6143de2`](https://github.com/Effect-TS/effect/commit/6143de21ee22038b45a8d4eba86f5aade6238eba), [`936b135`](https://github.com/Effect-TS/effect/commit/936b1358396eb0a1a7c8e0878ba63297e2106812), [`1bbae84`](https://github.com/Effect-TS/effect/commit/1bbae84f88b577a26d04ceb2e76d3143d09c4a20), [`d795ee7`](https://github.com/Effect-TS/effect/commit/d795ee771701ea62bd187ef7c0307d9737f68c1a), [`0a82d88`](https://github.com/Effect-TS/effect/commit/0a82d88b7da73278b6f270118e396d5ed4a64747), [`9215bc5`](https://github.com/Effect-TS/effect/commit/9215bc5da7dd10aa45f07fe44b98f06b6e433d62), [`a1b5df2`](https://github.com/Effect-TS/effect/commit/a1b5df2064d92431cfc6e638af613cc3114313d7), [`92a9ac5`](https://github.com/Effect-TS/effect/commit/92a9ac5ac0aa63d8975b9ba7a094d6a8f59a98f2), [`6bde7f2`](https://github.com/Effect-TS/effect/commit/6bde7f27f3243427203e53fe74472990e5c2a349), [`a712131`](https://github.com/Effect-TS/effect/commit/a7121310dbb60cbd819bbd702f97663098ec7bb8), [`2e6f760`](https://github.com/Effect-TS/effect/commit/2e6f760dcb44e2b984f3311a8af03a1d68a2ec7e), [`aa05804`](https://github.com/Effect-TS/effect/commit/aa0580497e027ed30b756058db0067c3fe07664f), [`badd3bf`](https://github.com/Effect-TS/effect/commit/badd3bf65fac4dd1e66e1f602db43659722dfced), [`02b0265`](https://github.com/Effect-TS/effect/commit/02b02651ede46a5a2dd3ef8081d0ad89648d0cbf), [`3437e21`](https://github.com/Effect-TS/effect/commit/3437e21a56d805781c5e5946a6189795a1dfd411), [`41a550d`](https://github.com/Effect-TS/effect/commit/41a550d1fed31e829929a8f5362b5340303164ac), [`17b5d50`](https://github.com/Effect-TS/effect/commit/17b5d50219ad49533cf9e33d01924a3e16af5eb3), [`96e5e95`](https://github.com/Effect-TS/effect/commit/96e5e9576b0315c747462761a61940ff9fe32dd1), [`e4d589e`](https://github.com/Effect-TS/effect/commit/e4d589e0ea08dc57c4793053b395dc0fcc499f34), [`ae4cf7b`](https://github.com/Effect-TS/effect/commit/ae4cf7b5e2cb5f8c55657e31a61789ad21c38c18), [`6ef5f1a`](https://github.com/Effect-TS/effect/commit/6ef5f1a041f3a40bf03fadd0b1feb275c277c635), [`2235a29`](https://github.com/Effect-TS/effect/commit/2235a29502c3f33cf6468511ad931089013a7916), [`b32f4cb`](https://github.com/Effect-TS/effect/commit/b32f4cb7b2d8ebe817075322622498e3beb05336), [`7f4c095`](https://github.com/Effect-TS/effect/commit/7f4c095b62da43780dd7fc2a5d1785ddfce60edf), [`5f3fb81`](https://github.com/Effect-TS/effect/commit/5f3fb814d18d8a54946c1c1cd0b41459cdb24006), [`17f0b91`](https://github.com/Effect-TS/effect/commit/17f0b91a243ccfe4a38d27debdc983adf434e738), [`0cdadd7`](https://github.com/Effect-TS/effect/commit/0cdadd75bc8abbbcad7956a4bc71f4e7a9b13250), [`39b57d7`](https://github.com/Effect-TS/effect/commit/39b57d7857358040558b67dd33eafc7bb5457830), [`5a6a573`](https://github.com/Effect-TS/effect/commit/5a6a5738e5bfc39e3a37ae7ba99081601fa19ac3), [`59f5e99`](https://github.com/Effect-TS/effect/commit/59f5e9981913b92d7a9beb2214a21d658b999d3a), [`45379d6`](https://github.com/Effect-TS/effect/commit/45379d6179ee4df2cbd3f848bd39ff7149c24a38), [`1949439`](https://github.com/Effect-TS/effect/commit/1949439175809ef81ab9c6411ed5559109edb4c9), [`e443403`](https://github.com/Effect-TS/effect/commit/e443403cf0e4effea14bb6cd950c5ac1c86cc748), [`03af7e8`](https://github.com/Effect-TS/effect/commit/03af7e85551204c605ea2fa2c43c10a4538ac8fb), [`0f721d4`](https://github.com/Effect-TS/effect/commit/0f721d406df8703ea92ca28777b3f09599e2056d), [`130b28d`](https://github.com/Effect-TS/effect/commit/130b28df552d7053407b041a96ff09dae82575e5), [`c987a12`](https://github.com/Effect-TS/effect/commit/c987a12a01b6a52ad53d29edf02613b03574dbcc), [`4158562`](https://github.com/Effect-TS/effect/commit/41585620977de9b84171f76619b72e29cc2284e5), [`306014a`](https://github.com/Effect-TS/effect/commit/306014a1ce4d5cb956c76bdc20e4e28ab3e61a6a), [`729a663`](https://github.com/Effect-TS/effect/commit/729a663275dd31f2357c446fe69664429220a83d), [`caf84b6`](https://github.com/Effect-TS/effect/commit/caf84b660044089e8d7f4067b279b27b8b50e8fd), [`ce067f7`](https://github.com/Effect-TS/effect/commit/ce067f799ea27735d4194345298a216aaf429f01), [`7a41f5a`](https://github.com/Effect-TS/effect/commit/7a41f5aa72d540ecf2746992ecc3fa3e6b40d31f), [`781022a`](https://github.com/Effect-TS/effect/commit/781022acdd3537ca18c88e2fa3681bafa6ef1b21), [`39f1297`](https://github.com/Effect-TS/effect/commit/39f1297acc08864feb12de6b8cf2bf73434f6cf5), [`2db266b`](https://github.com/Effect-TS/effect/commit/2db266b1bfbc81868bc1778c37c76032a267c79f), [`2141e28`](https://github.com/Effect-TS/effect/commit/2141e28903754d72604acf81673ceb2c62a56646), [`3c5e429`](https://github.com/Effect-TS/effect/commit/3c5e429878669ffcf5e0da4ddfbf50bde5bbcaad), [`20ddc63`](https://github.com/Effect-TS/effect/commit/20ddc630584f8fe488162ba384adcae53fc6810a), [`841b3ea`](https://github.com/Effect-TS/effect/commit/841b3ea6ae19a784bc1c20497b02f632af0c91e9), [`82a3fbf`](https://github.com/Effect-TS/effect/commit/82a3fbfce8b9df33e587076b7d7168ecd6799e17), [`eb9ee83`](https://github.com/Effect-TS/effect/commit/eb9ee83b38844a71d1cd5653a229309cfcb04a36), [`64dc7c7`](https://github.com/Effect-TS/effect/commit/64dc7c76dc5c89887b9e7c181d1873dcbb7820d1), [`84dc8ab`](https://github.com/Effect-TS/effect/commit/84dc8ab7accc682bc668c78a97e4a1776b633be8), [`b4463f4`](https://github.com/Effect-TS/effect/commit/b4463f46fc33d3b01ea5eadd7d012a5abda347a3), [`592dd36`](https://github.com/Effect-TS/effect/commit/592dd361645739ac0cd8e6babb084cd27403c172), [`85d2b44`](https://github.com/Effect-TS/effect/commit/85d2b446e3059de4919be730105868f79728308d), [`32e4a69`](https://github.com/Effect-TS/effect/commit/32e4a69b3151b7ec4058af2213b96a41d11e9e06), [`13c5872`](https://github.com/Effect-TS/effect/commit/13c5872ed30830360367ad89af2dab68a003c351), [`3454cdb`](https://github.com/Effect-TS/effect/commit/3454cdb528fdb5d3ed0c5c5c8169bc47de41fbd8), [`e930804`](https://github.com/Effect-TS/effect/commit/e9308045be1d8a00c0b4046f1e8ff22cf68c93da), [`7f12d4b`](https://github.com/Effect-TS/effect/commit/7f12d4b4e731dc3a213ae5c3f60db9edc50292d2), [`181c9ef`](https://github.com/Effect-TS/effect/commit/181c9ef5e5d4ab247bf4aec06424f15b0a1e802e), [`dd9f891`](https://github.com/Effect-TS/effect/commit/dd9f891e23f316abb6192893008f0e33ece9d97d), [`433fb81`](https://github.com/Effect-TS/effect/commit/433fb81ca4c15c681a8ae097ce3ff9bd3a9c9aa5), [`8459cdb`](https://github.com/Effect-TS/effect/commit/8459cdbae8a476dc04b6247fffe6a1668dcb1217), [`6124ab3`](https://github.com/Effect-TS/effect/commit/6124ab39eb64688fbd5d688d24766542f9cb5a2c), [`01bd954`](https://github.com/Effect-TS/effect/commit/01bd9546f142706fca1628f7261e6d1cb9638948), [`ba2c3aa`](https://github.com/Effect-TS/effect/commit/ba2c3aa05eb87ec05d263b960017ecf29746f66a), [`0a45ef3`](https://github.com/Effect-TS/effect/commit/0a45ef3bb4a1ae9b345c43c548db4336a31b3191), [`8459cdb`](https://github.com/Effect-TS/effect/commit/8459cdbae8a476dc04b6247fffe6a1668dcb1217), [`eaa7e71`](https://github.com/Effect-TS/effect/commit/eaa7e71b88bf59b24610128c6115a2a126432731), [`db4c2cc`](https://github.com/Effect-TS/effect/commit/db4c2ccdec77d813b6f4cc72a302ee7c4fe6e39d), [`22f150a`](https://github.com/Effect-TS/effect/commit/22f150a0936cef30517e87eaca73bff1c5e4873a), [`90ffb08`](https://github.com/Effect-TS/effect/commit/90ffb083b3091c211300f50a42ba7bf56536c0ee), [`d517692`](https://github.com/Effect-TS/effect/commit/d517692ef75f45d5f6d9d68b32d41fa0ccc56c99), [`01af079`](https://github.com/Effect-TS/effect/commit/01af079c189d1fc5067d3b1933b2870c4baf2693), [`32a59e8`](https://github.com/Effect-TS/effect/commit/32a59e8058b1ec9738cb083cf1cb116b393ca114)]: + - effect@4.0.0-beta.104 + +## 4.0.0-beta.103 + +### Patch Changes + +- [#6777](https://github.com/Effect-TS/effect/pull/6777) [`8e7c706`](https://github.com/Effect-TS/effect/commit/8e7c706b0aca855489b53d987404566d3e9cb5e7) Thanks @tim-smart! - Bound pending SSE decoder state with a configurable maximum event size. + +- [#6918](https://github.com/Effect-TS/effect/pull/6918) [`a913136`](https://github.com/Effect-TS/effect/commit/a9131368c1347ad4c409c9df80245d765e96a4fe) Thanks @fubhy! - Fix malformed JSON in streamed Anthropic code-execution tool parameters. + +- [#6701](https://github.com/Effect-TS/effect/pull/6701) [`9867b9f`](https://github.com/Effect-TS/effect/commit/9867b9fc69f9cc6c443594fc7eccc7be0c674d9c) Thanks @fubhy! - Removed explicit ./index entrypoints + +- Updated dependencies [[`e56cd8f`](https://github.com/Effect-TS/effect/commit/e56cd8f90c3559baccf8fcf2852ea911235d5944), [`f77c120`](https://github.com/Effect-TS/effect/commit/f77c120d8e04779ddeb8bce8e9cde932f268e4b6), [`b2f95a9`](https://github.com/Effect-TS/effect/commit/b2f95a9c2f2581deb89dc3bae9e89cf819e82923), [`04fd44a`](https://github.com/Effect-TS/effect/commit/04fd44a42abfa8dc2642300dcf49ee48c8ef4539), [`b74333d`](https://github.com/Effect-TS/effect/commit/b74333d83e15b9d042e4698ad23040de60454afe), [`1c40b28`](https://github.com/Effect-TS/effect/commit/1c40b2809503d6aa1358777196fc66317906e657), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`b3901d2`](https://github.com/Effect-TS/effect/commit/b3901d29c543fd5bd05ceec669a17896c8e19006), [`4a0984a`](https://github.com/Effect-TS/effect/commit/4a0984af62738fedf4bd3e87adb4d4d641ce9147), [`fffd88b`](https://github.com/Effect-TS/effect/commit/fffd88b3135abdf928ca7c4b0e00e610985091c7), [`f3f6c1e`](https://github.com/Effect-TS/effect/commit/f3f6c1e02cb543423fcffef5dc2db03fac503588), [`ef07642`](https://github.com/Effect-TS/effect/commit/ef07642dfe671d5258b65d1c1480c4d05c495f15), [`f1bc827`](https://github.com/Effect-TS/effect/commit/f1bc8274a608813d7b09d28dcca04adbf62f8c92), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`081f4d8`](https://github.com/Effect-TS/effect/commit/081f4d8cd06a2ac222d2810b46e61efcee26939e), [`5287b24`](https://github.com/Effect-TS/effect/commit/5287b24f5f8fa094ba20e117bfb1a80fba6d2cf5), [`13d31cf`](https://github.com/Effect-TS/effect/commit/13d31cfc2dde46210e94391b5b6767ae9aeaf2c9), [`acee269`](https://github.com/Effect-TS/effect/commit/acee26944bc89ee554d7b9fadab7443f9edc28a9), [`31170c1`](https://github.com/Effect-TS/effect/commit/31170c19b236c37abb5476c821bc6f5bfa2735ab), [`205ebc7`](https://github.com/Effect-TS/effect/commit/205ebc776062012581e98fced7ced19adfc44ee7), [`ed0ebf8`](https://github.com/Effect-TS/effect/commit/ed0ebf8e5c864d46fed1f232e99c0e680f10a58f), [`a3fd084`](https://github.com/Effect-TS/effect/commit/a3fd08482157bd78b089f77c7b173d54ef68b5cd), [`ee29ddf`](https://github.com/Effect-TS/effect/commit/ee29ddf862c3723ad466abc93ab6f6fe723b2319), [`6086309`](https://github.com/Effect-TS/effect/commit/60863090af8e5af0bfa1435f08dc5390f9993e30), [`4a57af2`](https://github.com/Effect-TS/effect/commit/4a57af24011db1d66e947289d2f7ffc2074696d2), [`660875b`](https://github.com/Effect-TS/effect/commit/660875b4325e6eebb3f04513998301cd2a0847ec), [`8e7c706`](https://github.com/Effect-TS/effect/commit/8e7c706b0aca855489b53d987404566d3e9cb5e7), [`5f63adb`](https://github.com/Effect-TS/effect/commit/5f63adbe75fc9d50d23706a52b3e483ad2a1a01c), [`053bc42`](https://github.com/Effect-TS/effect/commit/053bc42e2a964755611a216e78ed214322efee37), [`c0a1534`](https://github.com/Effect-TS/effect/commit/c0a153494484ecf9f0d0f20895a7a648b4be363b), [`f1e3a37`](https://github.com/Effect-TS/effect/commit/f1e3a378c144f974a6122b299f421b75595af20f), [`cedb01a`](https://github.com/Effect-TS/effect/commit/cedb01a025492a1faf9e59eb23eb96bc3b5e2fff), [`1747440`](https://github.com/Effect-TS/effect/commit/1747440de9a51a56ed3660da748cc01b256adce7), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`b4f1ee2`](https://github.com/Effect-TS/effect/commit/b4f1ee238d96aa78c5f040158cb78671d75b381e), [`a4757f1`](https://github.com/Effect-TS/effect/commit/a4757f1c47067d8d016a6c4a2c541bb8ae520f9b), [`cd122b9`](https://github.com/Effect-TS/effect/commit/cd122b90300d995a237993a2edb7a049785ab6a4), [`5de588b`](https://github.com/Effect-TS/effect/commit/5de588b2472fb0f4eb919766eb8472583a044772), [`3895b9c`](https://github.com/Effect-TS/effect/commit/3895b9cf179262cd277a9c6daafe9050dcf8265e), [`89ce5f3`](https://github.com/Effect-TS/effect/commit/89ce5f3e16e23a193daa475dc72ea8133ae1dacd), [`985de09`](https://github.com/Effect-TS/effect/commit/985de097d75906db2aed784841f81e23cc978b43), [`9800e3a`](https://github.com/Effect-TS/effect/commit/9800e3acc8f36530f671bc8b91558cb112f449a7), [`4dc35f6`](https://github.com/Effect-TS/effect/commit/4dc35f64641746366f867ea3dbfedb9cd4685ada), [`e8eb62b`](https://github.com/Effect-TS/effect/commit/e8eb62b3d0ef27e9761cdc2eb93bdec52d6ee204), [`ecd9993`](https://github.com/Effect-TS/effect/commit/ecd99936112cb69efdb02de3a2fd57f47baefdf3), [`5ab9c08`](https://github.com/Effect-TS/effect/commit/5ab9c08463ce049c45f3502676954a7b72c6b024), [`f5cf965`](https://github.com/Effect-TS/effect/commit/f5cf96548afd51f4b3cf1aea11b04d7f8549ce90), [`a94cbed`](https://github.com/Effect-TS/effect/commit/a94cbed84e9e49bea4bff925599c0f19c4e3deab), [`9160ad7`](https://github.com/Effect-TS/effect/commit/9160ad7d146d4376dd12f7510c025e5b2f638a70), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`52494be`](https://github.com/Effect-TS/effect/commit/52494be9e8eb3bb542d06a3dfefc6bca4e168984), [`5441c8e`](https://github.com/Effect-TS/effect/commit/5441c8e656a6418c0d27feb2df67565a3e1155f4), [`c9b56ab`](https://github.com/Effect-TS/effect/commit/c9b56ab507f224426ee8388dc450da447ec4715f), [`8ef7257`](https://github.com/Effect-TS/effect/commit/8ef72577d1f43212cab87951d659e54e3c8d7d91), [`1519406`](https://github.com/Effect-TS/effect/commit/1519406fed6e8b017ae178dc20bcaa2cf318b570), [`9716990`](https://github.com/Effect-TS/effect/commit/97169902eec3c99baa7f0b2c7b45a0a5eae75819), [`733f75b`](https://github.com/Effect-TS/effect/commit/733f75b7125e3016a975fdd251c0179ae5393786), [`48155c8`](https://github.com/Effect-TS/effect/commit/48155c8ccfc12dcca8a00fa358d50b20c30874e4), [`951d06b`](https://github.com/Effect-TS/effect/commit/951d06b83d459d3e8fa9024e727a5db1662d3322), [`d767b65`](https://github.com/Effect-TS/effect/commit/d767b65a7687e38be23f0b0ee3d52ab5f2360cbe), [`5d52d9d`](https://github.com/Effect-TS/effect/commit/5d52d9d148aaa7f736ed8c310fc8bfa9dc81badf), [`f4151e1`](https://github.com/Effect-TS/effect/commit/f4151e1937c26de14f1d64566f8126173f1b5014), [`e02fbb6`](https://github.com/Effect-TS/effect/commit/e02fbb66f5a0f13dba6c33ef63528a37a17a0676), [`724ce09`](https://github.com/Effect-TS/effect/commit/724ce09650a458d4565e5c7331ea92ca04f08e68), [`dbe91f6`](https://github.com/Effect-TS/effect/commit/dbe91f6961ef9f7e8da910ee5758d9c0d385fca8), [`4c008d2`](https://github.com/Effect-TS/effect/commit/4c008d28b370d817f7ae4579db09836fe084c8d2), [`b650832`](https://github.com/Effect-TS/effect/commit/b6508328708a842f3163467b72486bd228f1a289), [`b46c92f`](https://github.com/Effect-TS/effect/commit/b46c92f3b314f4ffd612b831efa55dd856c587a3), [`5335797`](https://github.com/Effect-TS/effect/commit/5335797003076d9c6fd170da98d779696d555596), [`4b3460d`](https://github.com/Effect-TS/effect/commit/4b3460daa434ec465a95a50704fe1103a9275999), [`6301fd7`](https://github.com/Effect-TS/effect/commit/6301fd710b4325718de2c42997dac28a9e9aa250), [`aebc5c6`](https://github.com/Effect-TS/effect/commit/aebc5c61664b89a840465ec65b79ce635a5ceee8), [`52b2d7b`](https://github.com/Effect-TS/effect/commit/52b2d7b5bd3c7cce3bd5b69c6ab3941004da70f3), [`eec5744`](https://github.com/Effect-TS/effect/commit/eec57445dfa0ef3c5977195ad69415b7e7d42bb6), [`24e0e93`](https://github.com/Effect-TS/effect/commit/24e0e93dc307dc2c2ae86caacb7289e1dab3c103), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`1a7ce81`](https://github.com/Effect-TS/effect/commit/1a7ce8150e3977586c44d8ccb9a8384389bb4d49), [`48f22a7`](https://github.com/Effect-TS/effect/commit/48f22a7d16ae57ee2175d450dafbdeb69e187d2a), [`c96b7f6`](https://github.com/Effect-TS/effect/commit/c96b7f6359662053c3e09344f61dddc7a6caf4ac), [`6d2a942`](https://github.com/Effect-TS/effect/commit/6d2a942ed7cd33b8fd79d549edba33bc9e2a7e3e), [`cc27b19`](https://github.com/Effect-TS/effect/commit/cc27b194b9d13fa3a66ab037e853fca9d41700ff), [`8f9499f`](https://github.com/Effect-TS/effect/commit/8f9499f562729f5f7b08d8bcc4db86b4aeff8a21), [`3eeea73`](https://github.com/Effect-TS/effect/commit/3eeea73cfc3e9b126975c2ddbdb7f7c8c92026e2), [`0a532e5`](https://github.com/Effect-TS/effect/commit/0a532e503f165fdea485a5343fc2f420917e8376), [`f398149`](https://github.com/Effect-TS/effect/commit/f398149c134fd9b67b6cdc52eae3f3248d5c7bbe), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`ace903e`](https://github.com/Effect-TS/effect/commit/ace903e09c2549ceebdec380797beb027cd29f3d), [`e8eb62b`](https://github.com/Effect-TS/effect/commit/e8eb62b3d0ef27e9761cdc2eb93bdec52d6ee204), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`48f22a7`](https://github.com/Effect-TS/effect/commit/48f22a7d16ae57ee2175d450dafbdeb69e187d2a), [`d48506d`](https://github.com/Effect-TS/effect/commit/d48506d97525040aa714305e928126df799795b4), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`d48506d`](https://github.com/Effect-TS/effect/commit/d48506d97525040aa714305e928126df799795b4), [`d48506d`](https://github.com/Effect-TS/effect/commit/d48506d97525040aa714305e928126df799795b4), [`52262be`](https://github.com/Effect-TS/effect/commit/52262be2edce0e350c6ac10f8f725678606399c5), [`1284aa1`](https://github.com/Effect-TS/effect/commit/1284aa183451955ad7921bbe01fd0e095695d444), [`9867b9f`](https://github.com/Effect-TS/effect/commit/9867b9fc69f9cc6c443594fc7eccc7be0c674d9c), [`d0f1a22`](https://github.com/Effect-TS/effect/commit/d0f1a2295155c350b04efb46852cb40032805273), [`979ce39`](https://github.com/Effect-TS/effect/commit/979ce3985d7d62ce2bf240681ca19feda3027452), [`b6d3e67`](https://github.com/Effect-TS/effect/commit/b6d3e67c7cc143cd8470cdf704324e79d23954a9), [`adf6c6c`](https://github.com/Effect-TS/effect/commit/adf6c6cd388af8a3c0c546492e71555368556f6a), [`7314d60`](https://github.com/Effect-TS/effect/commit/7314d605284717aaafe7fc34b88c3c93397e865c), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`1acbd8b`](https://github.com/Effect-TS/effect/commit/1acbd8b44c68ebb23735e9810476b870dbe58aea), [`7bde6cc`](https://github.com/Effect-TS/effect/commit/7bde6ccb2b144fe953ff30a7ef5e1ecc97697146), [`a959a8b`](https://github.com/Effect-TS/effect/commit/a959a8bf21cdb976369f494dc949fa00a050d3e0)]: + - effect@4.0.0-beta.103 + +## 4.0.0-beta.102 + +### Patch Changes + +- [#6608](https://github.com/Effect-TS/effect/pull/6608) [`0e50ec7`](https://github.com/Effect-TS/effect/commit/0e50ec7dbb94390666f292cf9120719bf30a7246) Thanks @gcanti! - Add `Schema.Natural` for non-negative safe integers and use canonical `Schema.Int`, `Schema.Finite`, and `Schema.Natural` schemas for numeric domain values across Effect, AI protocols, and OpenAPI patches. + + Update the date, date-time, file, time-zone, cluster, event-log, persistence, socket, SQL, and DevTools schemas to reject invalid non-finite or non-integer values where appropriate. Correct the decoded schema of `Schema.NumberFromString`, and allow `Schema.DurationFromMillis` and `Schema.DurationFromNanos` to represent negative durations. + +- [#6650](https://github.com/Effect-TS/effect/pull/6650) [`acd385e`](https://github.com/Effect-TS/effect/commit/acd385ebb3f9edee37ab6715607119ee9762a615) Thanks @IMax153! - Redact the Anthropic API key from client error context. + +- [#6567](https://github.com/Effect-TS/effect/pull/6567) [`5101e92`](https://github.com/Effect-TS/effect/commit/5101e92c9c149c153423f43dd7a94f6194653c06) Thanks @gcanti! - Add `Record.assignProperty` and safely handle dynamic record keys such as `__proto__` and inherited property names. + +- Updated dependencies [[`b6392e1`](https://github.com/Effect-TS/effect/commit/b6392e119704553edec1b4fd2869ac0dbec621ef), [`7ed9450`](https://github.com/Effect-TS/effect/commit/7ed945044eb56aa9aeaf62d4746a011c96c58628), [`45762bd`](https://github.com/Effect-TS/effect/commit/45762bd78df9ecd87c98b8d3738cdeeac7d81128), [`a6e8391`](https://github.com/Effect-TS/effect/commit/a6e8391cd31acd898fae18b3f8e7ca4c6f14f065), [`4ac7e8b`](https://github.com/Effect-TS/effect/commit/4ac7e8b136c61a26c3e438c013dfd7349b38e999), [`4cd40f5`](https://github.com/Effect-TS/effect/commit/4cd40f5692477783bef84fed3c5ef1c0cf5602e6), [`6956bc0`](https://github.com/Effect-TS/effect/commit/6956bc0e6cb27f53fbec39d9b18545940f9f598f), [`0e50ec7`](https://github.com/Effect-TS/effect/commit/0e50ec7dbb94390666f292cf9120719bf30a7246), [`9fcdade`](https://github.com/Effect-TS/effect/commit/9fcdade4a8af772b9ccd8b8a24fe8cee0e5d8470), [`57367d5`](https://github.com/Effect-TS/effect/commit/57367d54de55047ff0c5fce9685475e236bf354c), [`35c445f`](https://github.com/Effect-TS/effect/commit/35c445ff18029d192900ea0914c993f58d5cf1a5), [`c917bb9`](https://github.com/Effect-TS/effect/commit/c917bb94a4c1c4e0a24372a8ebb8a5ca232e36b5), [`bc1f358`](https://github.com/Effect-TS/effect/commit/bc1f3583e63344cb2c398d9040d9c975488ed123), [`0e0c9d7`](https://github.com/Effect-TS/effect/commit/0e0c9d7922ff463c1093d9e0576fae12cb0698d5), [`73d40aa`](https://github.com/Effect-TS/effect/commit/73d40aacd8fcae1b48c23f5b0a5c542127401d1d), [`4f1e318`](https://github.com/Effect-TS/effect/commit/4f1e3183f7123591c46224e9c587df7594562a5f), [`9d8d85c`](https://github.com/Effect-TS/effect/commit/9d8d85c1bb7da51970845b8ea830e386e777514a), [`6079fda`](https://github.com/Effect-TS/effect/commit/6079fda7b02f2f01ad91c15ab8c307336f3ba252), [`5101e92`](https://github.com/Effect-TS/effect/commit/5101e92c9c149c153423f43dd7a94f6194653c06), [`d0b3265`](https://github.com/Effect-TS/effect/commit/d0b3265c3262670761471ab3518cf933b1b3b20a), [`7a03c89`](https://github.com/Effect-TS/effect/commit/7a03c893ce6492bf94c0ebfb00b63bf25dcbf83e), [`cea1d9c`](https://github.com/Effect-TS/effect/commit/cea1d9c92601e69ebda040af8a1d860d604d885c), [`078e1f5`](https://github.com/Effect-TS/effect/commit/078e1f5636e31b76a86722a636afc37a8cc25580), [`97bafea`](https://github.com/Effect-TS/effect/commit/97bafeab460833b9781527b437d1cb9cbee63260), [`fab0ab8`](https://github.com/Effect-TS/effect/commit/fab0ab8f7ab15ae596faa4ccf75615a494d11b0b), [`c323d8b`](https://github.com/Effect-TS/effect/commit/c323d8b30dbbe85f9df25b67288b93d5332de333), [`6966353`](https://github.com/Effect-TS/effect/commit/69663534d626003eb10a5e55ab1f13e0379fead1), [`0444004`](https://github.com/Effect-TS/effect/commit/04440041989c1785fe4db286379f2be2c15baa85), [`028bbb3`](https://github.com/Effect-TS/effect/commit/028bbb391e161185da10d974ab33381f769940d7), [`ff5d6e2`](https://github.com/Effect-TS/effect/commit/ff5d6e278a1fdff714315dc1a17075012f05c1f0), [`1bfce93`](https://github.com/Effect-TS/effect/commit/1bfce93e6d2bf0794c11733daf51c2390e7de375), [`7ce815c`](https://github.com/Effect-TS/effect/commit/7ce815cd5af6af991dfc13b890fd22345fc77c20), [`7271a7f`](https://github.com/Effect-TS/effect/commit/7271a7faf1080aa75f2f53ca6a0b5ec9334c1d38), [`475fe5c`](https://github.com/Effect-TS/effect/commit/475fe5c12c2d6504c475797c0634f90da01e1797)]: + - effect@4.0.0-beta.102 + +## 4.0.0-beta.101 + +### Patch Changes + +- Updated dependencies [[`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`b35ed29`](https://github.com/Effect-TS/effect/commit/b35ed2904f01536d303b21f288daf343cf740462), [`dd44624`](https://github.com/Effect-TS/effect/commit/dd446245736a0e88c807a02f03c21450bb9340fa), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`2bae1ac`](https://github.com/Effect-TS/effect/commit/2bae1accce9d3b72cf6d5aefc9b2161af6d88436)]: + - effect@4.0.0-beta.101 + +## 4.0.0-beta.100 + +### Patch Changes + +- Updated dependencies [[`c1288dd`](https://github.com/Effect-TS/effect/commit/c1288dd1a52a2811ab7df57fc4ce236c6be4c745), [`2b58a3d`](https://github.com/Effect-TS/effect/commit/2b58a3dab6bc99776dddaf76e27d811e0f47f3d8), [`6dc83f2`](https://github.com/Effect-TS/effect/commit/6dc83f26ddf20d48db28cf761dd8f3716e5273fb), [`c1e2fe0`](https://github.com/Effect-TS/effect/commit/c1e2fe0cf93564f4d919e3998874c3e70b0cf30f), [`f3fbae8`](https://github.com/Effect-TS/effect/commit/f3fbae8d7bae0d77cb4f35a1598b26c58e3bf94d), [`e000f80`](https://github.com/Effect-TS/effect/commit/e000f80fd55bcd8edc699fdbf4cd109004f4f754), [`f4ee765`](https://github.com/Effect-TS/effect/commit/f4ee7655ee052cf9ba726fd602bb87c89c7c62a9), [`510b55f`](https://github.com/Effect-TS/effect/commit/510b55f3e21750685dbfd5f476a130c1c5af9dbd), [`31d3fc4`](https://github.com/Effect-TS/effect/commit/31d3fc4327c50867bb8d881fa7353aeb03ea2826), [`875e618`](https://github.com/Effect-TS/effect/commit/875e618c3764a7b817ac863d0af86924449528f2), [`688d46a`](https://github.com/Effect-TS/effect/commit/688d46afd0ef923d983ad3d7385f52f217b28d70), [`6ff5023`](https://github.com/Effect-TS/effect/commit/6ff502363b9840a5a5ee0a24bc6cae734ac3a3eb), [`c0333e7`](https://github.com/Effect-TS/effect/commit/c0333e7f755f42ddcca7051e029da8b4eed527bf), [`06e7e8c`](https://github.com/Effect-TS/effect/commit/06e7e8c66015ee318f871b9d2218dee82df2b108), [`eb9b102`](https://github.com/Effect-TS/effect/commit/eb9b10256c8558881b441c2fef833b7037174400), [`8b155da`](https://github.com/Effect-TS/effect/commit/8b155da06e0740c354ec562957a45ab65eb4573b), [`3a87335`](https://github.com/Effect-TS/effect/commit/3a8733564c5db35271aa20564ed0d344daa2a79f)]: + - effect@4.0.0-beta.100 + +## 4.0.0-beta.99 + +### Patch Changes + +- [#6371](https://github.com/Effect-TS/effect/pull/6371) [`7543afe`](https://github.com/Effect-TS/effect/commit/7543afea6f4d97d1f1ad876224323838a48daadd) Thanks @polRk! - Fix client-executed provider tools (Memory, Text Editor, Computer Use, Bash) which were unusable on the wire. + - `makeResponse` (and the streaming equivalents) now map a provider `tool_use` wire name (e.g. `"memory"`) back to the tool's custom name (e.g. `"AnthropicMemory"`) that the toolkit is keyed by, instead of raising `ToolNotFoundError`. + - `AnthropicTool.MemoryCreateCommand` now includes the required `file_text` field, so a `create` command no longer drops the file body. + - Optional parameters on client-executed provider tools now use `Schema.optionalKey` instead of `Schema.optional`, which the Anthropic codec rejected with "Unsupported AST Undefined": `Memory`/`TextEditor` `view_range`, `ComputerUse` `coordinate`, and `Bash` `restart`. + + Closes [#2615](https://github.com/Effect-TS/effect/issues/2615). + +- Updated dependencies [[`8ce4795`](https://github.com/Effect-TS/effect/commit/8ce4795ccbaebca4292757db568c005a992546a4), [`80b539f`](https://github.com/Effect-TS/effect/commit/80b539f8aba68f478c75c35c2b4140c4ffc4fada), [`88a54cc`](https://github.com/Effect-TS/effect/commit/88a54cc341006e3ebcb13482c618f62a680ce199), [`e6e6dba`](https://github.com/Effect-TS/effect/commit/e6e6dba6e9d86e7c2ad27dcedf289db76a19697f), [`bfb203e`](https://github.com/Effect-TS/effect/commit/bfb203e95aa439f731acad37fc3a9a831a190f1c), [`2e9a34a`](https://github.com/Effect-TS/effect/commit/2e9a34ac2bece4f3a206160480c991e3841dc67a), [`55d4eb3`](https://github.com/Effect-TS/effect/commit/55d4eb34f2c64d54f6a25a305b5c5438ebd7934e), [`bddb010`](https://github.com/Effect-TS/effect/commit/bddb010eac3d4436cb094edbbee7460c5440c162), [`a328835`](https://github.com/Effect-TS/effect/commit/a328835e50d76bc96648a1c1550456e8c9f81210), [`5560d05`](https://github.com/Effect-TS/effect/commit/5560d05aa6abdd29466d9c3412cc5e648b0adbde), [`8f6e3ad`](https://github.com/Effect-TS/effect/commit/8f6e3adb185b16e8820b98c509b308086f7ff1af), [`46997fa`](https://github.com/Effect-TS/effect/commit/46997fa60401f5e3c93daa4b61f7df8e31caaab4), [`9e6e12d`](https://github.com/Effect-TS/effect/commit/9e6e12d75c118cd265496f2880490d1f33a5c8bf), [`3394b93`](https://github.com/Effect-TS/effect/commit/3394b93d97d6f24fc38670641d1490289ffca7f1), [`febeabc`](https://github.com/Effect-TS/effect/commit/febeabc3f7c31094da000a23edeaabfe2ab00a38), [`54161c9`](https://github.com/Effect-TS/effect/commit/54161c98f6f3569e0c31842f54e6a257f9421c4c), [`385f7a4`](https://github.com/Effect-TS/effect/commit/385f7a4ee4a7359928597ea56d151dbaf5eb5802), [`7eea4d0`](https://github.com/Effect-TS/effect/commit/7eea4d0b73ec554915d7066a71f46326ce2ba45f), [`7543afe`](https://github.com/Effect-TS/effect/commit/7543afea6f4d97d1f1ad876224323838a48daadd), [`44b9cf3`](https://github.com/Effect-TS/effect/commit/44b9cf3d240d726997b4bbcd0ede48e074d3c456), [`7eea4d0`](https://github.com/Effect-TS/effect/commit/7eea4d0b73ec554915d7066a71f46326ce2ba45f), [`0a8aa6a`](https://github.com/Effect-TS/effect/commit/0a8aa6acb90a72b91c24d17133c950e4cacd8abd), [`c8d9fcf`](https://github.com/Effect-TS/effect/commit/c8d9fcf7b030f7c474effbab2764ce7aee1c7209), [`9ca7f9a`](https://github.com/Effect-TS/effect/commit/9ca7f9a69363e4485645966d5a93b8f9597c5206), [`e7aca89`](https://github.com/Effect-TS/effect/commit/e7aca894bb32fbb785b5830837e6061c415a6015), [`55d7560`](https://github.com/Effect-TS/effect/commit/55d75609b8acf8a1b54c1b1c7fbbb65ec741aa3e), [`f809189`](https://github.com/Effect-TS/effect/commit/f809189ddf6b6011ba43a9901baaa734e315da2a), [`88a54cc`](https://github.com/Effect-TS/effect/commit/88a54cc341006e3ebcb13482c618f62a680ce199), [`0ebdbe7`](https://github.com/Effect-TS/effect/commit/0ebdbe74463dc84385956d0b1e8c2b79ebab5400), [`7517d09`](https://github.com/Effect-TS/effect/commit/7517d09f12a0b183a81bd425962c4e280a68b05d), [`212493b`](https://github.com/Effect-TS/effect/commit/212493b9a1eb98cd1ef6959c707a2e5784a5ae91), [`88a54cc`](https://github.com/Effect-TS/effect/commit/88a54cc341006e3ebcb13482c618f62a680ce199), [`80ea8cb`](https://github.com/Effect-TS/effect/commit/80ea8cb9222ca73f564c8267ab2f82966fea027a), [`8df19f4`](https://github.com/Effect-TS/effect/commit/8df19f4fe81d90cc33ace88b9a77e5534f82d604)]: + - effect@4.0.0-beta.99 + +## 4.0.0-beta.98 + +### Patch Changes + +- Updated dependencies [[`989603b`](https://github.com/Effect-TS/effect-smol/commit/989603b60ab1197b64acf214208e0d370cd1f842), [`214c458`](https://github.com/Effect-TS/effect-smol/commit/214c458084bb6995d543cd37d1055f24be3d454e), [`a037273`](https://github.com/Effect-TS/effect-smol/commit/a0372736ac34796969b051bbba4717d7983f1ebe), [`97fdaa9`](https://github.com/Effect-TS/effect-smol/commit/97fdaa9c1f522c65e579365d314a07878e2b904f), [`b24d248`](https://github.com/Effect-TS/effect-smol/commit/b24d248c8df44222ce642087cde2bd859a2dc709), [`19c222c`](https://github.com/Effect-TS/effect-smol/commit/19c222cac2353a3d7b7733caecb00556fffe9a5c), [`eec85dd`](https://github.com/Effect-TS/effect-smol/commit/eec85ddba09ea326fd268ee33eeffd47e50d4671), [`0082f4f`](https://github.com/Effect-TS/effect-smol/commit/0082f4f74fb139fd578f87f0a790e845133983dc), [`8849052`](https://github.com/Effect-TS/effect-smol/commit/884905232d1e9a365e046d8dde27bf9c5707f57f), [`c15e16a`](https://github.com/Effect-TS/effect-smol/commit/c15e16ad130d1fbde25d912b7ac55995066cb35b), [`01d00a3`](https://github.com/Effect-TS/effect-smol/commit/01d00a3abfbf1f37996cdbe738ea5137c646cdd7), [`8bd4589`](https://github.com/Effect-TS/effect-smol/commit/8bd458975a1b3a8ed042eccf317b93d28ded91e7), [`0082f4f`](https://github.com/Effect-TS/effect-smol/commit/0082f4f74fb139fd578f87f0a790e845133983dc), [`6e08428`](https://github.com/Effect-TS/effect-smol/commit/6e08428d980501b856f846ad3f3f0e4ea46e7786), [`388dcf9`](https://github.com/Effect-TS/effect-smol/commit/388dcf953f65d317547f34d40e6443c5f264205f), [`2b7ce2b`](https://github.com/Effect-TS/effect-smol/commit/2b7ce2b513e7ec2a77822f1116dc6ffb6ba93f4e), [`87bea7e`](https://github.com/Effect-TS/effect-smol/commit/87bea7e16259246f3bcdf565446394751abca953), [`ce38dc3`](https://github.com/Effect-TS/effect-smol/commit/ce38dc33bda805a684432cca071f4dc3c6b9a1ba), [`a807cd1`](https://github.com/Effect-TS/effect-smol/commit/a807cd170341deca8a1cfb52c4222585f2431bb9), [`fd8a356`](https://github.com/Effect-TS/effect-smol/commit/fd8a356f06a8c9ce4e7e0a13fc4021c178ed31de), [`c2a5edc`](https://github.com/Effect-TS/effect-smol/commit/c2a5edc3abd31ad5bc123362bc1213e03e4095c3), [`5946da3`](https://github.com/Effect-TS/effect-smol/commit/5946da3804a1be5e752b05b96bd058cdba50a1bf), [`4ae0c5f`](https://github.com/Effect-TS/effect-smol/commit/4ae0c5ffcbe6c56ddfcb05c639112a079483539e), [`5b2a0bc`](https://github.com/Effect-TS/effect-smol/commit/5b2a0bceea3a28a33a58555210c90a415dc74a76), [`72ac585`](https://github.com/Effect-TS/effect-smol/commit/72ac585884befde6af9208da738699a93f1bae79), [`5e8c1b8`](https://github.com/Effect-TS/effect-smol/commit/5e8c1b82bfafa121311f987a49ab75395e3647a7), [`0f9c078`](https://github.com/Effect-TS/effect-smol/commit/0f9c07841b04183f485ee6e6458de73b290b09f5)]: + - effect@4.0.0-beta.98 + +## 4.0.0-beta.97 + +### Patch Changes + +- [#2568](https://github.com/Effect-TS/effect-smol/pull/2568) [`97a4138`](https://github.com/Effect-TS/effect-smol/commit/97a4138f9e9d31d38026f935b52d423f5ccadc4f) Thanks @K-Mistele! - Widen the Anthropic `Model` schema to accept both known model identifiers as well + as any string to allow for newer models +- Updated dependencies []: + - effect@4.0.0-beta.97 + +## 4.0.0-beta.96 + +### Patch Changes + +- Updated dependencies [[`1503f45`](https://github.com/Effect-TS/effect-smol/commit/1503f45cb5bb2a74f4705252ec505a1f0ade7e62), [`57fe793`](https://github.com/Effect-TS/effect-smol/commit/57fe79316ffbc380b30626a168981fb26ae97459), [`0c2f78f`](https://github.com/Effect-TS/effect-smol/commit/0c2f78f695ec474e1ff5474da183577975e418f5), [`0c2f78f`](https://github.com/Effect-TS/effect-smol/commit/0c2f78f695ec474e1ff5474da183577975e418f5), [`0c2f78f`](https://github.com/Effect-TS/effect-smol/commit/0c2f78f695ec474e1ff5474da183577975e418f5), [`97f29df`](https://github.com/Effect-TS/effect-smol/commit/97f29df457f7ffd07cfb4b379315c12c086af805)]: + - effect@4.0.0-beta.96 + +## 4.0.0-beta.95 + +### Patch Changes + +- [#2531](https://github.com/Effect-TS/effect-smol/pull/2531) [`bdca354`](https://github.com/Effect-TS/effect-smol/commit/bdca35449d5dfce5b4433da75ec0a88d0a9b2b27) Thanks @polRk! - Fix `Memory_20250818` provider-defined tool missing `requiresHandler: true`. Like the other client-executed tools (`TextEditor_20250728`, `Bash_2025*`, `ComputerUse_2025*`), the memory tool requires the application to implement its execution (view/create/str_replace/insert/delete/rename over `/memories/*`). Without this flag, `Tool.HandlersFor` excluded it from the required handlers, making it impossible to type-check a handler for `Memory_20250818` in `Toolkit.toLayer`. + +- Updated dependencies [[`a482442`](https://github.com/Effect-TS/effect-smol/commit/a482442abdeb490e9652b854ec3495e4aa7273e7), [`fbefa85`](https://github.com/Effect-TS/effect-smol/commit/fbefa850fab2f0a302c20614496aeaaa2a8b5590), [`0b4a32f`](https://github.com/Effect-TS/effect-smol/commit/0b4a32f4260f0d8500942a133001b0d349328102), [`18a49e1`](https://github.com/Effect-TS/effect-smol/commit/18a49e1786679456258002ff9397faf02f678c2d), [`266cb90`](https://github.com/Effect-TS/effect-smol/commit/266cb90bb2c17aabc40563c32db334f09ba3d74b), [`912f095`](https://github.com/Effect-TS/effect-smol/commit/912f095a34572bbd3cedf6edb27878443e3e4a95), [`a6718f9`](https://github.com/Effect-TS/effect-smol/commit/a6718f9e00a15ca903b0732da46116cbf3d6aca7), [`bef5154`](https://github.com/Effect-TS/effect-smol/commit/bef51540a243aa2f872a00c01d0cd58b7a769baa), [`18e0564`](https://github.com/Effect-TS/effect-smol/commit/18e0564bd0f8ebbdfcaf1e2c21529948e9e4a81d), [`fb50f14`](https://github.com/Effect-TS/effect-smol/commit/fb50f14fc3657c1973785aa5b72ecf0b0d28e0b2)]: + - effect@4.0.0-beta.95 + +## 4.0.0-beta.94 + +### Patch Changes + +- Updated dependencies [[`95a0e9b`](https://github.com/Effect-TS/effect-smol/commit/95a0e9bb62797af0e81c9998773405f248f218c5), [`a0a3490`](https://github.com/Effect-TS/effect-smol/commit/a0a3490bbce765f199d8e077aceac504f0462e63), [`f11ce73`](https://github.com/Effect-TS/effect-smol/commit/f11ce73af60823754dc24194f4ffc561b9ea1c2d), [`ff30b6e`](https://github.com/Effect-TS/effect-smol/commit/ff30b6e7c2c63ffc56a4c5818d6d86b01b5ad528), [`1caab3c`](https://github.com/Effect-TS/effect-smol/commit/1caab3cc30f626efbf15e59d74f539a487e5c85c), [`aa80c47`](https://github.com/Effect-TS/effect-smol/commit/aa80c4775a04db87553e5568764cab7e32a72814), [`c2ae4fc`](https://github.com/Effect-TS/effect-smol/commit/c2ae4fce2f03a4cd1861c2b1179da7df656e662d), [`a0a3490`](https://github.com/Effect-TS/effect-smol/commit/a0a3490bbce765f199d8e077aceac504f0462e63)]: + - effect@4.0.0-beta.94 + +## 4.0.0-beta.93 + +### Patch Changes + +- Updated dependencies [[`00652fe`](https://github.com/Effect-TS/effect-smol/commit/00652fe95c18f87208e91343eb8bf218faa2f677), [`6c58167`](https://github.com/Effect-TS/effect-smol/commit/6c5816746eaf91d2a3c7c899c5720809fa230ae3), [`2bc5415`](https://github.com/Effect-TS/effect-smol/commit/2bc541501a7ef89e542d7cb98e96beb53cd205cc), [`e11cccc`](https://github.com/Effect-TS/effect-smol/commit/e11cccc7d5fe631abccc7d6e3bd296938de0fa2e), [`ba7e77e`](https://github.com/Effect-TS/effect-smol/commit/ba7e77e046b8641a3a4e9750bb88ca4a1d063d3f), [`5713ee7`](https://github.com/Effect-TS/effect-smol/commit/5713ee7edbc3054efde407b2286bbfd45bbc6e1c)]: + - effect@4.0.0-beta.93 + +## 4.0.0-beta.92 + +### Patch Changes + +- Updated dependencies [[`affdc13`](https://github.com/Effect-TS/effect-smol/commit/affdc139045cc325dce321a84a580fdc1b2da7b9)]: + - effect@4.0.0-beta.92 + +## 4.0.0-beta.91 + +### Patch Changes + +- Updated dependencies [[`b135b25`](https://github.com/Effect-TS/effect-smol/commit/b135b2517fca9e7839734ace3699a7dfa75b9075), [`aaa21a3`](https://github.com/Effect-TS/effect-smol/commit/aaa21a369a171c600db294f2a4f640583043e150), [`3475ee6`](https://github.com/Effect-TS/effect-smol/commit/3475ee6c2bda6b05c6d7a12ce30c8bb840b5b1a6)]: + - effect@4.0.0-beta.91 + ## 4.0.0-beta.90 ### Patch Changes diff --git a/repos/effect/packages/ai/anthropic/codegen.yml b/repos/effect/packages/ai/anthropic/codegen.yml index a39a97db8a..dd23c4bcb1 100644 --- a/repos/effect/packages/ai/anthropic/codegen.yml +++ b/repos/effect/packages/ai/anthropic/codegen.yml @@ -5,8 +5,6 @@ spec: output: src/Generated.ts name: AnthropicClient patches: - # Remove string from Model schema - only keep literal model IDs - - '[{"op":"remove","path":"/components/schemas/Model/anyOf/0"}]' # Make iterations and speed optional in BetaUsage, iterations optional in BetaMessageDeltaUsage (API may omit these fields) - '[{"op":"remove","path":"/components/schemas/BetaUsage/required/9"},{"op":"remove","path":"/components/schemas/BetaUsage/required/5"},{"op":"remove","path":"/components/schemas/BetaMessageDeltaUsage/required/3"}]' header: | @@ -26,38 +24,39 @@ replacements: - from: "readonly [x: string]: unknown" to: "readonly [x: string]: Schema.Json" # Make citations optional in ResponseTextBlock (API may omit this field) - - from: "export type ResponseTextBlock = { readonly \"citations\":" - to: "export type ResponseTextBlock = { readonly \"citations\"?:" + - from: 'export type ResponseTextBlock = { readonly "citations":' + to: 'export type ResponseTextBlock = { readonly "citations"?:' - from: "export const ResponseTextBlock = Schema.Struct({ \"citations\": Schema.Union([Schema.Array(Schema.Union([ResponseCharLocationCitation, ResponsePageLocationCitation, ResponseContentBlockLocationCitation, ResponseWebSearchResultLocationCitation, ResponseSearchResultLocationCitation], { mode: \"oneOf\" })), Schema.Null]).annotate({ \"title\": \"Citations\", \"description\": \"Citations supporting the text block.\\n\\nThe type of citation returned will depend on the type of document being cited. Citing a PDF results in `page_location`, plain text results in `char_location`, and content document results in `content_block_location`.\", \"default\": null }), \"text\":" to: "export const ResponseTextBlock = Schema.Struct({ \"citations\": Schema.optionalKey(Schema.Union([Schema.Array(Schema.Union([ResponseCharLocationCitation, ResponsePageLocationCitation, ResponseContentBlockLocationCitation, ResponseWebSearchResultLocationCitation, ResponseSearchResultLocationCitation], { mode: \"oneOf\" })), Schema.Null]).annotate({ \"title\": \"Citations\", \"description\": \"Citations supporting the text block.\\n\\nThe type of citation returned will depend on the type of document being cited. Citing a PDF results in `page_location`, plain text results in `char_location`, and content document results in `content_block_location`.\", \"default\": null })), \"text\":" # Make citations optional in BetaResponseTextBlock (API may omit this field) - - from: "export type BetaResponseTextBlock = { readonly \"citations\":" - to: "export type BetaResponseTextBlock = { readonly \"citations\"?:" + - from: 'export type BetaResponseTextBlock = { readonly "citations":' + to: 'export type BetaResponseTextBlock = { readonly "citations"?:' - from: "export const BetaResponseTextBlock = Schema.Struct({ \"citations\": Schema.Union([Schema.Array(Schema.Union([BetaResponseCharLocationCitation, BetaResponsePageLocationCitation, BetaResponseContentBlockLocationCitation, BetaResponseWebSearchResultLocationCitation, BetaResponseSearchResultLocationCitation], { mode: \"oneOf\" })), Schema.Null]).annotate({ \"title\": \"Citations\", \"description\": \"Citations supporting the text block.\\n\\nThe type of citation returned will depend on the type of document being cited. Citing a PDF results in `page_location`, plain text results in `char_location`, and content document results in `content_block_location`.\", \"default\": null }), \"text\":" to: "export const BetaResponseTextBlock = Schema.Struct({ \"citations\": Schema.optionalKey(Schema.Union([Schema.Array(Schema.Union([BetaResponseCharLocationCitation, BetaResponsePageLocationCitation, BetaResponseContentBlockLocationCitation, BetaResponseWebSearchResultLocationCitation, BetaResponseSearchResultLocationCitation], { mode: \"oneOf\" })), Schema.Null]).annotate({ \"title\": \"Citations\", \"description\": \"Citations supporting the text block.\\n\\nThe type of citation returned will depend on the type of document being cited. Citing a PDF results in `page_location`, plain text results in `char_location`, and content document results in `content_block_location`.\", \"default\": null })), \"text\":" # Make server_tool_use optional in usage (API may omit this field) - - from: "readonly \"server_tool_use\": ServerToolUsage | null" - to: "readonly \"server_tool_use\"?: ServerToolUsage | null" - - from: "readonly \"server_tool_use\": BetaServerToolUsage | null" - to: "readonly \"server_tool_use\"?: BetaServerToolUsage | null" - - from: "\"server_tool_use\": Schema.Union([ServerToolUsage, Schema.Null]).annotate({ \"description\": \"The number of server tool requests.\", \"default\": null })," - to: "\"server_tool_use\": Schema.optionalKey(Schema.Union([ServerToolUsage, Schema.Null]).annotate({ \"description\": \"The number of server tool requests.\", \"default\": null }))," - - from: "\"server_tool_use\": Schema.Union([BetaServerToolUsage, Schema.Null]).annotate({ \"description\": \"The number of server tool requests.\", \"default\": null })," - to: "\"server_tool_use\": Schema.optionalKey(Schema.Union([BetaServerToolUsage, Schema.Null]).annotate({ \"description\": \"The number of server tool requests.\", \"default\": null }))," + - from: 'readonly "server_tool_use": ServerToolUsage | null' + to: 'readonly "server_tool_use"?: ServerToolUsage | null' + - from: 'readonly "server_tool_use": BetaServerToolUsage | null' + to: 'readonly "server_tool_use"?: BetaServerToolUsage | null' + - from: '"server_tool_use": Schema.Union([ServerToolUsage, Schema.Null]).annotate({ "description": "The number of server tool requests.", "default": null }),' + to: '"server_tool_use": Schema.optionalKey(Schema.Union([ServerToolUsage, Schema.Null]).annotate({ "description": "The number of server tool requests.", "default": null })),' + - from: '"server_tool_use": Schema.Union([BetaServerToolUsage, Schema.Null]).annotate({ "description": "The number of server tool requests.", "default": null }),' + to: '"server_tool_use": Schema.optionalKey(Schema.Union([BetaServerToolUsage, Schema.Null]).annotate({ "description": "The number of server tool requests.", "default": null })),' # Without trailing comma (for last field in struct) - - from: "\"server_tool_use\": Schema.Union([BetaServerToolUsage, Schema.Null]).annotate({ \"description\": \"The number of server tool requests.\", \"default\": null }) })" - to: "\"server_tool_use\": Schema.optionalKey(Schema.Union([BetaServerToolUsage, Schema.Null]).annotate({ \"description\": \"The number of server tool requests.\", \"default\": null })) })" - - from: "\"server_tool_use\": Schema.Union([ServerToolUsage, Schema.Null]).annotate({ \"description\": \"The number of server tool requests.\", \"default\": null }) })" - to: "\"server_tool_use\": Schema.optionalKey(Schema.Union([ServerToolUsage, Schema.Null]).annotate({ \"description\": \"The number of server tool requests.\", \"default\": null })) })" + - from: '"server_tool_use": Schema.Union([BetaServerToolUsage, Schema.Null]).annotate({ "description": "The number of server tool requests.", "default": null }) })' + to: '"server_tool_use": Schema.optionalKey(Schema.Union([BetaServerToolUsage, Schema.Null]).annotate({ "description": "The number of server tool requests.", "default": null })) })' + - from: '"server_tool_use": Schema.Union([ServerToolUsage, Schema.Null]).annotate({ "description": "The number of server tool requests.", "default": null }) })' + to: '"server_tool_use": Schema.optionalKey(Schema.Union([ServerToolUsage, Schema.Null]).annotate({ "description": "The number of server tool requests.", "default": null })) })' # Make context_management and container optional in BetaMessage (API may omit these fields) - - from: "readonly \"context_management\": BetaResponseContextManagement | null" - to: "readonly \"context_management\"?: BetaResponseContextManagement | null" - - from: "readonly \"container\": BetaContainer | null" - to: "readonly \"container\"?: BetaContainer | null" + - from: 'readonly "context_management": BetaResponseContextManagement | null' + to: 'readonly "context_management"?: BetaResponseContextManagement | null' + - from: 'readonly "container": BetaContainer | null' + to: 'readonly "container"?: BetaContainer | null' - from: "\"context_management\": Schema.Union([BetaResponseContextManagement, Schema.Null]).annotate({ \"description\": \"Context management response.\\n\\nInformation about context management strategies applied during the request.\", \"default\": null })," to: "\"context_management\": Schema.optionalKey(Schema.Union([BetaResponseContextManagement, Schema.Null]).annotate({ \"description\": \"Context management response.\\n\\nInformation about context management strategies applied during the request.\", \"default\": null }))," - from: "\"container\": Schema.Union([BetaContainer, Schema.Null]).annotate({ \"description\": \"Information about the container used in this request.\\n\\nThis will be non-null if a container tool (e.g. code execution) was used.\", \"default\": null })" to: "\"container\": Schema.optionalKey(Schema.Union([BetaContainer, Schema.Null]).annotate({ \"description\": \"Information about the container used in this request.\\n\\nThis will be non-null if a container tool (e.g. code execution) was used.\", \"default\": null }))" # Make context_management optional in BetaMessageDeltaEvent (API may omit this field) - - from: "\"context_management\": Schema.Union([BetaResponseContextManagement, Schema.Null]).annotate({ \"description\": \"Information about context management strategies applied during the request\", \"default\": null })," - to: "\"context_management\": Schema.optionalKey(Schema.Union([BetaResponseContextManagement, Schema.Null]).annotate({ \"description\": \"Information about context management strategies applied during the request\", \"default\": null }))," \ No newline at end of file + - from: '"context_management": Schema.Union([BetaResponseContextManagement, Schema.Null]).annotate({ "description": "Information about context management strategies applied during the request", "default": null }),' + to: '"context_management": Schema.optionalKey(Schema.Union([BetaResponseContextManagement, Schema.Null]).annotate({ "description": "Information about context management strategies applied during the request", "default": null })),' + diff --git a/repos/effect/packages/ai/anthropic/docgen.json b/repos/effect/packages/ai/anthropic/docgen.json deleted file mode 100644 index bc0548cd39..0000000000 --- a/repos/effect/packages/ai/anthropic/docgen.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "$schema": "../../node_modules/@effect/docgen/schema.json", - "srcLink": "https://github.com/Effect-TS/effect/tree/main/packages/ai/anthropic/src/", - "exclude": ["src/internal/**/*.ts", "src/Generated.ts"], - "tscExecutable": "tsgo", - "examplesCompilerOptions": { - "noEmit": true, - "strict": true, - "exactOptionalPropertyTypes": true, - "skipLibCheck": true, - "moduleResolution": "Bundler", - "module": "ES2022", - "target": "ES2022", - "lib": ["ESNext", "DOM", "DOM.Iterable"], - "types": ["node"], - "rewriteRelativeImportExtensions": true, - "allowImportingTsExtensions": true, - "paths": { - "effect": ["../../../effect/src/index.js"], - "effect/*": ["../../../effect/src/*.js"] - }, - "plugins": [ - { "name": "@effect/language-service", "includeSuggestionsInTsc": false } - ] - } -} diff --git a/repos/effect/packages/ai/anthropic/package.json b/repos/effect/packages/ai/anthropic/package.json index f1bf19556d..edf7a0d47e 100644 --- a/repos/effect/packages/ai/anthropic/package.json +++ b/repos/effect/packages/ai/anthropic/package.json @@ -1,17 +1,17 @@ { "name": "@effect/ai-anthropic", - "version": "4.0.0-beta.90", + "version": "4.0.0-beta.107", "type": "module", "license": "MIT", "description": "An Anthropic provider integration for Effect AI SDK", "homepage": "https://effect.website", "repository": { "type": "git", - "url": "https://github.com/Effect-TS/effect-smol.git", + "url": "https://github.com/Effect-TS/effect.git", "directory": "packages/ai/anthropic" }, "bugs": { - "url": "https://github.com/Effect-TS/effect-smol/issues" + "url": "https://github.com/Effect-TS/effect/issues" }, "tags": [ "typescript", @@ -29,6 +29,7 @@ ".": "./src/index.ts", "./*": "./src/*.ts", "./internal/*": null, + "./index": null, "./*/index": null }, "files": [ @@ -36,7 +37,10 @@ "dist/**/*.js", "dist/**/*.js.map", "dist/**/*.d.ts", - "dist/**/*.d.ts.map" + "dist/**/*.d.ts.map", + "AGENTS.md", + "CLAUDE.md", + "ai-docs/**/*" ], "publishConfig": { "access": "public", @@ -46,17 +50,15 @@ ".": "./dist/index.js", "./*": "./dist/*.js", "./internal/*": null, + "./index": null, "./*/index": null } }, "scripts": { "codegen": "effect-utils codegen", "build": "tsc -b tsconfig.json && pnpm babel", - "build:tsgo": "tsgo -b tsconfig.json && pnpm babel", "babel": "babel dist --plugins annotate-pure-calls --out-dir dist --source-maps", - "check": "tsc -b tsconfig.json", - "test": "vitest", - "coverage": "vitest --coverage" + "check": "tsc -b tsconfig.json" }, "devDependencies": { "effect": "workspace:^" diff --git a/repos/effect/packages/ai/anthropic/src/AnthropicClient.ts b/repos/effect/packages/ai/anthropic/src/AnthropicClient.ts index 646feadc90..93288554fc 100644 --- a/repos/effect/packages/ai/anthropic/src/AnthropicClient.ts +++ b/repos/effect/packages/ai/anthropic/src/AnthropicClient.ts @@ -36,7 +36,7 @@ import * as Errors from "./internal/errors.ts" * Represents the Anthropic client service with methods for the Messages API, including regular and streaming message * creation. * - * @category models + * @category services * @since 4.0.0 */ export interface Service { @@ -52,7 +52,7 @@ export interface Service { schema: S ) => (request: HttpClientRequest.HttpClientRequest) => Stream.Stream< S["Type"], - HttpClientError.HttpClientError | Schema.SchemaError | Sse.Retry, + HttpClientError.HttpClientError | Schema.SchemaError | Sse.Retry | Sse.SseError, S["DecodingServices"] > @@ -194,6 +194,11 @@ const RedactedAnthropicHeaders = { AnthropicApiKey: "x-api-key" } +const withRedactedHeaders = Effect.updateService( + Headers.CurrentRedactedNames, + Array.appendAll(Object.values(RedactedAnthropicHeaders)) +) + /** * Creates an Anthropic client service with the given options. * @@ -254,7 +259,7 @@ export const make = Effect.fnUntraced( (schema: S) => (request: HttpClientRequest.HttpClientRequest): Stream.Stream< S["Type"], - HttpClientError.HttpClientError | Schema.SchemaError | Sse.Retry, + HttpClientError.HttpClientError | Schema.SchemaError | Sse.Retry | Sse.SseError, S["DecodingServices"] > => httpClientOk.execute(request).pipe( @@ -276,7 +281,8 @@ export const make = Effect.fnUntraced( BetaMessagesPost4XX: (error) => Effect.fail(Errors.mapClientError(error, "createMessage")), HttpClientError: (error) => Errors.mapHttpClientError(error, "createMessage"), SchemaError: (error) => Effect.fail(Errors.mapSchemaError(error, "createMessage")) - }) + }), + withRedactedHeaders ) const PingEvent = Schema.Struct({ @@ -306,6 +312,7 @@ export const make = Effect.fnUntraced( Stream.catchTags({ // TODO: handle SSE retries Retry: (error) => Stream.die(error), + SseError: (error) => Stream.fail(Errors.mapSseError(error, "createMessageStream")), HttpClientError: (error) => Stream.fromEffect(Errors.mapHttpClientError(error, "createMessageStream")), SchemaError: (error) => Stream.fail(Errors.mapSchemaError(error, "createMessageStream")) }) @@ -329,7 +336,8 @@ export const make = Effect.fnUntraced( Effect.catchTag( "HttpClientError", (error) => Errors.mapHttpClientError(error, "createMessageStream") - ) + ), + withRedactedHeaders ) } @@ -340,10 +348,7 @@ export const make = Effect.fnUntraced( createMessageStream }) }, - Effect.updateService( - Headers.CurrentRedactedNames, - Array.appendAll(Object.values(RedactedAnthropicHeaders)) - ) + withRedactedHeaders ) // ============================================================================= diff --git a/repos/effect/packages/ai/anthropic/src/AnthropicConfig.ts b/repos/effect/packages/ai/anthropic/src/AnthropicConfig.ts index 26e3b21ad0..df27b55817 100644 --- a/repos/effect/packages/ai/anthropic/src/AnthropicConfig.ts +++ b/repos/effect/packages/ai/anthropic/src/AnthropicConfig.ts @@ -52,7 +52,7 @@ export declare namespace AnthropicConfig { * * Use `transformClient` to wrap or replace the `HttpClient` used by generated Anthropic API requests. * - * @category models + * @category services * @since 4.0.0 */ export interface Service { diff --git a/repos/effect/packages/ai/anthropic/src/AnthropicError.ts b/repos/effect/packages/ai/anthropic/src/AnthropicError.ts index d7a521907e..590176e2f5 100644 --- a/repos/effect/packages/ai/anthropic/src/AnthropicError.ts +++ b/repos/effect/packages/ai/anthropic/src/AnthropicError.ts @@ -77,7 +77,7 @@ declare module "effect/unstable/ai/AiError" { * * Includes request identifiers, Anthropic error types, and parsed request or token limit headers when the provider rejects a request due to rate limits. * - * @category configuration + * @category models * @since 4.0.0 */ export interface RateLimitErrorMetadata { @@ -91,7 +91,7 @@ declare module "effect/unstable/ai/AiError" { * * Captures the Anthropic error type and request identifier for failures where the account or workspace has exhausted its available quota. * - * @category configuration + * @category models * @since 4.0.0 */ export interface QuotaExhaustedErrorMetadata { @@ -105,7 +105,7 @@ declare module "effect/unstable/ai/AiError" { * * Preserves Anthropic error details for missing, invalid, or unauthorized API credentials while keeping the error in the shared AI error model. * - * @category configuration + * @category models * @since 4.0.0 */ export interface AuthenticationErrorMetadata { @@ -119,7 +119,7 @@ declare module "effect/unstable/ai/AiError" { * * Records Anthropic error details returned when a request or response is rejected by Anthropic safety or content policy enforcement. * - * @category configuration + * @category models * @since 4.0.0 */ export interface ContentPolicyErrorMetadata { @@ -133,7 +133,7 @@ declare module "effect/unstable/ai/AiError" { * * Provides the Anthropic error type and request identifier for malformed or unsupported requests rejected before model execution. * - * @category configuration + * @category models * @since 4.0.0 */ export interface InvalidRequestErrorMetadata { @@ -147,7 +147,7 @@ declare module "effect/unstable/ai/AiError" { * * Preserves Anthropic request correlation data for provider-side failures that should be reported or investigated with Anthropic support. * - * @category configuration + * @category models * @since 4.0.0 */ export interface InternalProviderErrorMetadata { @@ -161,7 +161,7 @@ declare module "effect/unstable/ai/AiError" { * * Describes Anthropic-specific context for responses that could not be decoded or interpreted as valid AI output. * - * @category configuration + * @category models * @since 4.0.0 */ export interface InvalidOutputErrorMetadata { @@ -175,7 +175,7 @@ declare module "effect/unstable/ai/AiError" { * * Captures Anthropic error details for structured-output failures, including request correlation data useful when diagnosing schema-related responses. * - * @category configuration + * @category models * @since 4.0.0 */ export interface StructuredOutputErrorMetadata { @@ -189,7 +189,7 @@ declare module "effect/unstable/ai/AiError" { * * Provides Anthropic error details for schemas that cannot be represented by or submitted to the Anthropic API. * - * @category configuration + * @category models * @since 4.0.0 */ export interface UnsupportedSchemaErrorMetadata { @@ -203,7 +203,7 @@ declare module "effect/unstable/ai/AiError" { * * Retains the Anthropic error type and request identifier when a provider response cannot be classified as a more specific AI error. * - * @category configuration + * @category models * @since 4.0.0 */ export interface UnknownErrorMetadata { diff --git a/repos/effect/packages/ai/anthropic/src/AnthropicLanguageModel.ts b/repos/effect/packages/ai/anthropic/src/AnthropicLanguageModel.ts index 516fae12a7..e65a7c9bdf 100644 --- a/repos/effect/packages/ai/anthropic/src/AnthropicLanguageModel.ts +++ b/repos/effect/packages/ai/anthropic/src/AnthropicLanguageModel.ts @@ -20,6 +20,7 @@ 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" @@ -39,19 +40,15 @@ 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. * - * **Details** - * - * The Anthropic language model constructors accept `Model` values and custom - * string model ids, so this type is best used for autocomplete and type checking - * of known Claude ids. - * * @category models * @since 4.0.0 */ -export type Model = typeof Generated.Model.Type +export type Model = (typeof Generated.Model)["members"][1]["Encoded"] // ============================================================================= // Configuration @@ -71,7 +68,7 @@ export type Model = typeof Generated.Model.Type * requests. Scoped configuration overrides defaults supplied to `model`, * `make`, or `layer`. * - * @category configuration + * @category services * @since 4.0.0 */ export class Config extends Context.Service< @@ -117,7 +114,7 @@ declare module "effect/unstable/ai/Prompt" { * These options are used when translating system messages into Anthropic * request content. * - * @category request + * @category models * @since 4.0.0 */ export interface SystemMessageOptions extends ProviderOptions { @@ -137,7 +134,7 @@ declare module "effect/unstable/ai/Prompt" { * These options are used when translating user messages into Anthropic * request content. * - * @category request + * @category models * @since 4.0.0 */ export interface UserMessageOptions extends ProviderOptions { @@ -157,7 +154,7 @@ declare module "effect/unstable/ai/Prompt" { * These options are used when replaying assistant messages in Anthropic * conversation history. * - * @category request + * @category models * @since 4.0.0 */ export interface AssistantMessageOptions extends ProviderOptions { @@ -177,7 +174,7 @@ declare module "effect/unstable/ai/Prompt" { * These options are used when converting tool results into Anthropic user * content blocks. * - * @category request + * @category models * @since 4.0.0 */ export interface ToolMessageOptions extends ProviderOptions { @@ -196,7 +193,7 @@ declare module "effect/unstable/ai/Prompt" { * * Use when you use these options to control how text blocks are sent to Anthropic. * - * @category request + * @category models * @since 4.0.0 */ export interface TextPartOptions extends ProviderOptions { @@ -216,7 +213,7 @@ declare module "effect/unstable/ai/Prompt" { * Preserves Claude thinking metadata when reasoning content is sent back to * Anthropic in later turns. * - * @category request + * @category models * @since 4.0.0 */ export interface ReasoningPartOptions extends ProviderOptions { @@ -251,7 +248,7 @@ declare module "effect/unstable/ai/Prompt" { * Controls document metadata, citations, and prompt caching for files sent to * Anthropic. * - * @category request + * @category models * @since 4.0.0 */ export interface FilePartOptions extends ProviderOptions { @@ -289,7 +286,7 @@ declare module "effect/unstable/ai/Prompt" { * Carries Anthropic tool caller metadata, MCP metadata, and cache control for * tool use blocks. * - * @category request + * @category models * @since 4.0.0 */ export interface ToolCallPartOptions extends ProviderOptions { @@ -319,13 +316,23 @@ declare module "effect/unstable/ai/Prompt" { * * **Details** * - * Controls Anthropic prompt caching for tool result content. + * Carries Anthropic MCP metadata and controls prompt caching for tool result + * content. * - * @category request + * @category models * @since 4.0.0 */ export interface ToolResultPartOptions extends ProviderOptions { readonly anthropic?: { + /** + * Contains details about the MCP tool that produced the result. + */ + readonly mcp_tool?: { + /** + * The name of the MCP server + */ + readonly server: string + } | null /** * A breakpoint which marks the end of reusable content eligible for caching. */ @@ -340,7 +347,7 @@ declare module "effect/unstable/ai/Prompt" { * * Controls prompt caching for human approval requests in conversations. * - * @category request + * @category models * @since 4.0.0 */ export interface ToolApprovalRequestPartOptions extends ProviderOptions { @@ -359,7 +366,7 @@ declare module "effect/unstable/ai/Prompt" { * * Controls prompt caching for human approval responses in conversations. * - * @category request + * @category models * @since 4.0.0 */ export interface ToolApprovalResponsePartOptions extends ProviderOptions { @@ -381,7 +388,7 @@ declare module "effect/unstable/ai/Response" { * Includes Claude thinking metadata needed to continue reasoning-aware * conversations. * - * @category response + * @category models * @since 4.0.0 */ export interface ReasoningStartPartMetadata extends ProviderMetadata { @@ -411,7 +418,7 @@ declare module "effect/unstable/ai/Response" { * * Includes the signature for streamed Claude thinking content when available. * - * @category response + * @category models * @since 4.0.0 */ export interface ReasoningDeltaPartMetadata extends ProviderMetadata { @@ -434,7 +441,7 @@ declare module "effect/unstable/ai/Response" { * * Preserves Claude thinking or redacted thinking information for later turns. * - * @category response + * @category models * @since 4.0.0 */ export interface ReasoningPartMetadata extends ProviderMetadata { @@ -465,7 +472,7 @@ declare module "effect/unstable/ai/Response" { * Identifies Anthropic caller details and MCP tool metadata emitted by the * provider. * - * @category response + * @category models * @since 4.0.0 */ export interface ToolCallPartMetadata extends ProviderMetadata { @@ -494,7 +501,7 @@ declare module "effect/unstable/ai/Response" { * Identifies MCP tool metadata associated with provider-executed tool * results. * - * @category response + * @category models * @since 4.0.0 */ export interface ToolResultPartMetadata extends ProviderMetadata { @@ -518,7 +525,7 @@ declare module "effect/unstable/ai/Response" { * * Records the cited document span by character position or page number. * - * @category response + * @category models * @since 4.0.0 */ export interface DocumentSourcePartMetadata extends ProviderMetadata { @@ -562,7 +569,7 @@ declare module "effect/unstable/ai/Response" { * * Records cited URL text or web-search source freshness information. * - * @category response + * @category models * @since 4.0.0 */ export interface UrlSourcePartMetadata extends ProviderMetadata { @@ -592,7 +599,7 @@ declare module "effect/unstable/ai/Response" { * Includes container state, context management information, stop details, and * token usage reported by Anthropic. * - * @category response + * @category models * @since 4.0.0 */ export interface FinishPartMetadata extends ProviderMetadata { @@ -611,7 +618,7 @@ declare module "effect/unstable/ai/Response" { * * Includes the provider request identifier when Anthropic returns one. * - * @category response + * @category models * @since 4.0.0 */ export interface ErrorPartMetadata extends ProviderMetadata { @@ -895,7 +902,7 @@ const prepareMessages = Effect.fnUntraced( : { type: "text", media_type: "text/plain", - data: typeof part.data === "string" ? part.data : Encoding.encodeBase64(part.data) + data: typeof part.data === "string" ? part.data : new TextDecoder().decode(part.data) } as const content.push({ @@ -1624,12 +1631,15 @@ const makeResponse = Effect.fnUntraced( } : undefined - const params = yield* transformToolCallParams(options.tools, part.name, part.input) + // Map the provider wire name (e.g. "memory") back to the tool's + // custom name (e.g. "AnthropicMemory") that the toolkit is keyed by + const toolName = toolNameMapper.getCustomName(part.name) + const params = yield* transformToolCallParams(options.tools, toolName, part.input) parts.push({ type: "tool-call", id: part.id, - name: part.name, + name: toolName, params, ...(Predicate.isNotUndefined(callerInfo) ? { metadata: { anthropic: { caller: callerInfo } } } @@ -2042,10 +2052,15 @@ const makeStreamResponse = Effect.fnUntraced( } : undefined + // Map the provider wire name (e.g. "memory") back to the + // tool's custom name (e.g. "AnthropicMemory") that the toolkit + // is keyed by + const toolName = toolNameMapper.getCustomName(part.name) + parts.push({ type: "tool-params-start", id: part.id, - name: part.name + name: toolName }) parts.push({ @@ -2059,12 +2074,12 @@ const makeStreamResponse = Effect.fnUntraced( id: part.id }) - const params = yield* transformToolCallParams(options.tools, part.name, part.input) + const params = yield* transformToolCallParams(options.tools, toolName, part.input) parts.push({ type: "tool-call", id: part.id, - name: part.name, + name: toolName, params, ...(Predicate.isNotUndefined(callerInfo) ? { metadata: { anthropic: { caller: callerInfo } } } @@ -2212,10 +2227,15 @@ const makeStreamResponse = Effect.fnUntraced( const hasParams = Object.keys(part.input).length > 0 const initialParams = hasParams ? JSON.stringify(part.input) : "" + // Map the provider wire name (e.g. "memory") back to the tool's + // custom name (e.g. "AnthropicMemory") that the toolkit is keyed + // by. The mapped name flows to the finalized tool-call via + // `contentBlock.name` on `content_block_stop`. + const toolName = toolNameMapper.getCustomName(part.name) contentBlocks.set(event.index, { type: "tool-call", id: part.id, - name: part.name, + name: toolName, params: initialParams, firstDelta: initialParams.length > 0, ...(Predicate.isNotUndefined(caller) ? { caller } : undefined) @@ -2224,7 +2244,7 @@ const makeStreamResponse = Effect.fnUntraced( parts.push({ type: "tool-params-start", id: part.id, - name: part.name + name: toolName }) break @@ -2568,7 +2588,7 @@ const makeStreamResponse = Effect.fnUntraced( (contentBlock.providerName === "bash_code_execution" || contentBlock.providerName === "text_editor_code_execution") ) { - delta = `{"type":${contentBlock.providerName},${delta.substring(1)}}` + delta = `{"type":${JSON.stringify(contentBlock.providerName)},${delta.substring(1)}` } parts.push({ @@ -2643,7 +2663,7 @@ const makeStreamResponse = Effect.fnUntraced( } const params = contentBlock.providerExecuted === true - ? finalParams + ? Tool.unsafeSecureJsonParse(finalParams) : yield* transformToolCallParams( options.tools, contentBlock.name, @@ -2964,13 +2984,20 @@ interface ModelCapabilities { */ const getModelCapabilities = (modelId: string): ModelCapabilities => { if ( - modelId.includes("claude-sonnet-4-5") || - modelId.includes("claude-opus-4-5") || - modelId.includes("claude-haiku-4-5") || modelId.includes("claude-opus-4-6") || modelId.includes("claude-sonnet-4-6") || modelId.includes("claude-opus-4-7") || modelId.includes("claude-opus-4-8") + ) { + return { + maxOutputTokens: 128000, + supportsStructuredOutput: true, + isKnownModel: true + } + } else if ( + modelId.includes("claude-sonnet-4-5") || + modelId.includes("claude-opus-4-5") || + modelId.includes("claude-haiku-4-5") ) { return { maxOutputTokens: 64000, @@ -3091,7 +3118,7 @@ const transformToolCallParams = Effect.fnUntraced(function* (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, diff --git a/repos/effect/packages/ai/anthropic/src/internal/utilities.ts b/repos/effect/packages/ai/anthropic/src/internal/utilities.ts index 320754c4dc..322b035ac8 100644 --- a/repos/effect/packages/ai/anthropic/src/internal/utilities.ts +++ b/repos/effect/packages/ai/anthropic/src/internal/utilities.ts @@ -15,7 +15,7 @@ export const resolveFinishReason = ( finishReason: string, isJsonResponse: boolean = false ): Response.FinishReason => { - const reason = finishReasonMap[finishReason] + const reason = Object.hasOwn(finishReasonMap, finishReason) ? finishReasonMap[finishReason] : undefined if (Predicate.isUndefined(reason)) { return "unknown" } diff --git a/repos/effect/packages/ai/anthropic/test/AnthropicClient.test.ts b/repos/effect/packages/ai/anthropic/test/AnthropicClient.test.ts new file mode 100644 index 0000000000..aa9861c815 --- /dev/null +++ b/repos/effect/packages/ai/anthropic/test/AnthropicClient.test.ts @@ -0,0 +1,127 @@ +import { AnthropicClient } from "@effect/ai-anthropic" +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 + } + })))) +}) + +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 + } + }) + ) +} diff --git a/repos/effect/packages/ai/anthropic/test/AnthropicLanguageModel.test.ts b/repos/effect/packages/ai/anthropic/test/AnthropicLanguageModel.test.ts index 0fd0334525..32ebded702 100644 --- a/repos/effect/packages/ai/anthropic/test/AnthropicLanguageModel.test.ts +++ b/repos/effect/packages/ai/anthropic/test/AnthropicLanguageModel.test.ts @@ -1,7 +1,14 @@ -import { AnthropicClient, AnthropicLanguageModel } from "@effect/ai-anthropic" +import { AnthropicClient, AnthropicLanguageModel, AnthropicTool } from "@effect/ai-anthropic" import { assert, describe, it } from "@effect/vitest" import { Effect, Layer, Redacted, Schema, Stream } from "effect" -import { LanguageModel, Tool, Toolkit } from "effect/unstable/ai" +import { + AnthropicStructuredOutput, + LanguageModel, + Prompt, + Response as AiResponse, + Tool, + Toolkit +} from "effect/unstable/ai" import { HttpClient, type HttpClientError, type HttpClientRequest, HttpClientResponse } from "effect/unstable/http" describe("AnthropicLanguageModel", () => { @@ -111,6 +118,214 @@ describe("AnthropicLanguageModel", () => { 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", () => { @@ -184,6 +399,156 @@ describe("AnthropicLanguageModel", () => { assert.strictEqual(dynamicTool.description, "A dynamic tool") assert.deepStrictEqual(dynamicTool.input_schema, inputSchema) })) + + it.effect("serializes provider-executed web_search parts from the assistant message", () => + Effect.gen(function*() { + let capturedRequest: HttpClientRequest.HttpClientRequest | undefined = undefined + const layer = AnthropicClient.layer({ apiKey: Redacted.make("sk-test-key") }).pipe( + Layer.provide(Layer.succeed( + HttpClient.HttpClient, + makeHttpClient((request) => { + capturedRequest = request + return Effect.succeed(jsonResponse(request, { + id: "msg_test_2", + type: "message", + role: "assistant", + model: "claude-sonnet-4-20250514", + content: [{ type: "text", text: "You're welcome" }], + stop_reason: "end_turn", + stop_sequence: null, + usage: { + cache_creation: null, + cache_creation_input_tokens: null, + cache_read_input_tokens: null, + inference_geo: null, + input_tokens: 10, + output_tokens: 5, + service_tier: null + } + })) + }) + )) + ) + + const searchResults = [{ + type: "web_search_result", + url: "https://example.com/gold", + title: "Gold price", + encrypted_content: "encrypted", + page_age: null + }] + + const history = Prompt.fromResponseParts([ + AiResponse.makePart("tool-call", { + id: "srvtoolu_1", + name: "AnthropicWebSearch", + params: { query: "gold price today" }, + providerExecuted: true + }), + AiResponse.makePart("tool-result", { + id: "srvtoolu_1", + name: "AnthropicWebSearch", + isFailure: false, + result: searchResults, + encodedResult: searchResults, + preliminary: false, + providerExecuted: true + }), + AiResponse.makePart("text", { text: "Gold is around $4,000." }) + ]) + + const prompt = Prompt.concat( + Prompt.concat(Prompt.make("what is the gold price?"), history), + Prompt.make("thanks") + ) + + yield* LanguageModel.generateText({ + prompt, + toolkit: Toolkit.make(AnthropicTool.WebSearch_20250305({})), + disableToolCallResolution: true + }).pipe( + Effect.provide(AnthropicLanguageModel.model("claude-sonnet-4-20250514")), + Effect.provide(layer) + ) + + assert.isDefined(capturedRequest) + if (capturedRequest === undefined) { + return + } + + const body = yield* getRequestBody(capturedRequest) + const assistantMessage = body.messages.find((message: any) => message.role === "assistant") + assert.isDefined(assistantMessage) + + const serverToolUse = assistantMessage.content.find((block: any) => block.type === "server_tool_use") + assert.isDefined(serverToolUse) + assert.strictEqual(serverToolUse.id, "srvtoolu_1") + assert.strictEqual(serverToolUse.name, "web_search") + + const searchResult = assistantMessage.content.find((block: any) => block.type === "web_search_tool_result") + assert.isDefined(searchResult) + assert.strictEqual(searchResult.tool_use_id, "srvtoolu_1") + + const clientToolResults = body.messages.flatMap((message: any) => + Array.isArray(message.content) + ? message.content.filter((block: any) => block.type === "tool_result") + : [] + ) + assert.strictEqual(clientToolResults.length, 0) + })) + + it.effect("encodes plaintext bytes as UTF-8 text", () => + Effect.gen(function*() { + let body: any + const client = AnthropicClient.layer({ apiKey: Redacted.make("test") }).pipe( + Layer.provide(Layer.succeed( + HttpClient.HttpClient, + HttpClient.makeWith( + Effect.fnUntraced(function*(requestEffect) { + const request = yield* requestEffect + body = JSON.parse(new TextDecoder().decode((request.body as any).body)) + return HttpClientResponse.fromWeb( + request, + new Response( + JSON.stringify({ + id: "msg_1", + type: "message", + role: "assistant", + model: "claude-sonnet-4-20250514", + content: [{ type: "text", text: "ok" }], + stop_reason: "end_turn", + stop_sequence: null, + usage: { + cache_creation: null, + cache_creation_input_tokens: null, + cache_read_input_tokens: null, + inference_geo: null, + input_tokens: 1, + output_tokens: 1, + service_tier: null + } + }), + { status: 200 } + ) + ) + }), + Effect.succeed as HttpClient.HttpClient.Preprocess + ) + )) + ) + + yield* LanguageModel.generateText({ + prompt: Prompt.make([{ + role: "user", + content: [Prompt.filePart({ mediaType: "text/plain", data: new TextEncoder().encode("hello") })] + }]) + }).pipe( + Effect.provide(AnthropicLanguageModel.model("claude-sonnet-4-20250514")), + Effect.provide(client) + ) + + assert.strictEqual(body.messages[0].content[0].source.data, "hello") + })) }) describe("generateObject", () => { @@ -244,6 +609,140 @@ describe("AnthropicLanguageModel", () => { it.effect("uses native json_schema output for claude-sonnet-4-6", () => assertNativeStructuredOutput("claude-sonnet-4-6")) }) + + // 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 = ( diff --git a/repos/effect/packages/ai/anthropic/tsconfig.json b/repos/effect/packages/ai/anthropic/tsconfig.json index 19a2f5dbca..e2a8ca19a0 100644 --- a/repos/effect/packages/ai/anthropic/tsconfig.json +++ b/repos/effect/packages/ai/anthropic/tsconfig.json @@ -1,5 +1,5 @@ { - "$schema": "http://json.schemastore.org/tsconfig", + "$schema": "https://json.schemastore.org/tsconfig", "extends": "../../../tsconfig.base.json", "include": ["src"], "references": [ diff --git a/repos/effect/packages/ai/anthropic/typetest/AnthropicLanguageModel.tst.ts b/repos/effect/packages/ai/anthropic/typetest/AnthropicLanguageModel.tst.ts new file mode 100644 index 0000000000..79d86a5edb --- /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/anthropic/vitest.config.ts b/repos/effect/packages/ai/anthropic/vitest.config.ts deleted file mode 100644 index c8a52c1826..0000000000 --- a/repos/effect/packages/ai/anthropic/vitest.config.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { mergeConfig, type ViteUserConfig } from "vitest/config" -import shared from "../../../vitest.shared.ts" - -const config: ViteUserConfig = {} - -export default mergeConfig(shared, config) diff --git a/repos/effect/packages/ai/openai-compat/CHANGELOG.md b/repos/effect/packages/ai/openai-compat/CHANGELOG.md index f3c62d635b..93d08734a9 100644 --- a/repos/effect/packages/ai/openai-compat/CHANGELOG.md +++ b/repos/effect/packages/ai/openai-compat/CHANGELOG.md @@ -1,5 +1,149 @@ # @effect/ai-openai-compat +## 4.0.0-beta.107 + +### Patch Changes + +- Updated dependencies [[`596f3f9`](https://github.com/Effect-TS/effect/commit/596f3f92d7fe355811b815cb212332b082268ce8), [`9611ed4`](https://github.com/Effect-TS/effect/commit/9611ed42d11300546b339ab13492a0f7bdb1ebfb), [`8b91605`](https://github.com/Effect-TS/effect/commit/8b9160548556e4b0ec7ee2f2707716776be49018), [`d901928`](https://github.com/Effect-TS/effect/commit/d901928efa44f573ed1247f53fdb203a8e4fcede), [`b32bdef`](https://github.com/Effect-TS/effect/commit/b32bdef0d119a1ad1463dc01a46763ffee1f9bd9)]: + - effect@4.0.0-beta.107 + +## 4.0.0-beta.106 + +### Patch Changes + +- [#7126](https://github.com/Effect-TS/effect/pull/7126) [`16b94c7`](https://github.com/Effect-TS/effect/commit/16b94c702419c318e0f3515c902c39cf3871ccce) Thanks @fubhy! - Fix OpenAI response telemetry attribute types to use the emitted response namespace. + +- [#7127](https://github.com/Effect-TS/effect/pull/7127) [`b588640`](https://github.com/Effect-TS/effect/commit/b588640b4f5ee8b000acf8275364852bf79fe426) Thanks @fubhy! - Fix the OpenAI-compatible telemetry response attribute namespace. +- Updated dependencies [[`2695168`](https://github.com/Effect-TS/effect/commit/269516851b24916d72771f8a554b88722e3732e7), [`6310a8c`](https://github.com/Effect-TS/effect/commit/6310a8c68c74dcf1d23948ec9243ac5f407a1651), [`c2071b1`](https://github.com/Effect-TS/effect/commit/c2071b1647e2326568c1d0689274ef62b8a7183f), [`7aff81a`](https://github.com/Effect-TS/effect/commit/7aff81a9cefe681483ef8abf717d786fd10e7e8d), [`a1d4057`](https://github.com/Effect-TS/effect/commit/a1d4057711935a544ef441bc2d0ac3565dfa9266), [`abf77b0`](https://github.com/Effect-TS/effect/commit/abf77b04009dcb4d67a258f9d8ada778e9f4ffae), [`6c60375`](https://github.com/Effect-TS/effect/commit/6c60375e68683a32d54554150cc493e16550a06d), [`22f4897`](https://github.com/Effect-TS/effect/commit/22f4897bbae24783d4516f6bef353f1db4ec6d03), [`615d1d5`](https://github.com/Effect-TS/effect/commit/615d1d5d0256ec8160f2e08d0dcf5dc83acb7bf1), [`3a86757`](https://github.com/Effect-TS/effect/commit/3a867573ddeed5888dabdeb3225a9ebbf00491e7), [`f4a9762`](https://github.com/Effect-TS/effect/commit/f4a9762bb9dfad59c215f2e099dcc829d74f4ed1), [`0bcf6ed`](https://github.com/Effect-TS/effect/commit/0bcf6ed57c22e8a36964726b15464101d90f5997), [`ba9cb63`](https://github.com/Effect-TS/effect/commit/ba9cb63b87d45ce2df872dd8ef0905da147cc675), [`42c810d`](https://github.com/Effect-TS/effect/commit/42c810dd372275b822dd99c7d7e774e153f0a752), [`1416ccd`](https://github.com/Effect-TS/effect/commit/1416ccd474bc9da8979f51b72b5e53fb3ac56edf), [`08d0d39`](https://github.com/Effect-TS/effect/commit/08d0d39a225deccb9db213ab5fcf55edb9f9ba5d), [`548908a`](https://github.com/Effect-TS/effect/commit/548908a71d9337cb7defe7fc93b2fba8f6a04b6f), [`4b3460d`](https://github.com/Effect-TS/effect/commit/4b3460daa434ec465a95a50704fe1103a9275999), [`d170596`](https://github.com/Effect-TS/effect/commit/d17059615cca37ca2776654078fe0501ac5202e6), [`aea89d0`](https://github.com/Effect-TS/effect/commit/aea89d0c42ee0ac707a4962cd348fd3158cb469b), [`deed5fb`](https://github.com/Effect-TS/effect/commit/deed5fbdc91cf8bf8c5fce7dfa5d6527ac944726)]: + - effect@4.0.0-beta.106 + +## 4.0.0-beta.105 + +### Patch Changes + +- Updated dependencies [[`0418564`](https://github.com/Effect-TS/effect/commit/04185644dabb8e4169f1ef6cbbc0b36c4db2f7f4), [`d334a85`](https://github.com/Effect-TS/effect/commit/d334a8593aafcd60753995a9449b654c67bfdcc1), [`f0be855`](https://github.com/Effect-TS/effect/commit/f0be8554da6ee00293a6b23869ac46a0b0d97dc8), [`b206fa5`](https://github.com/Effect-TS/effect/commit/b206fa5d7655c1634c9993410a9203f6616a5ca2), [`b938c8a`](https://github.com/Effect-TS/effect/commit/b938c8ad2823bd88493187922f7d9090eff037b6), [`8525f05`](https://github.com/Effect-TS/effect/commit/8525f05d1e14ea12298e9e1a0df497bfaac2ce9a)]: + - effect@4.0.0-beta.105 + +## 4.0.0-beta.104 + +### Patch Changes + +- Updated dependencies [[`1001bcc`](https://github.com/Effect-TS/effect/commit/1001bccb9e874918d59dbb36860f1c5d4499ac20), [`993ba60`](https://github.com/Effect-TS/effect/commit/993ba60ee6c7ca6eb84522040f8b0d268b6ba7d4), [`67faacd`](https://github.com/Effect-TS/effect/commit/67faacd4679242559bee31717c05a5b10b990322), [`b78acdf`](https://github.com/Effect-TS/effect/commit/b78acdf422568f10ae8684fd3f10d52b065f0b56), [`fbb9ce5`](https://github.com/Effect-TS/effect/commit/fbb9ce5e625d1a7d6b7005bda42cbb6cd31476c5), [`722ea48`](https://github.com/Effect-TS/effect/commit/722ea484c9d01364c9242d929c0a564f7831a57c), [`3058fd5`](https://github.com/Effect-TS/effect/commit/3058fd594f5a683034212d71d957017fcc084006), [`62d0575`](https://github.com/Effect-TS/effect/commit/62d057566c241405c23ecf0bf4156186bd2be924), [`99dd6b5`](https://github.com/Effect-TS/effect/commit/99dd6b580434f97c5b40adc919f429e4abc3dfe7), [`7963ce1`](https://github.com/Effect-TS/effect/commit/7963ce1cd95f037fbefea67a29ead49cce4d16cb), [`af14e75`](https://github.com/Effect-TS/effect/commit/af14e752edd65e2b652e960411afafc88975a8d8), [`24e22d2`](https://github.com/Effect-TS/effect/commit/24e22d23a73a2e93ebf6d8edd2246a4a406942c8), [`647d14e`](https://github.com/Effect-TS/effect/commit/647d14e572c8004fa92fba256e00552b42bf34b7), [`1434eec`](https://github.com/Effect-TS/effect/commit/1434eecbd368e00839c24b3950f0b7a69218669a), [`a5278b1`](https://github.com/Effect-TS/effect/commit/a5278b18242011d1b2b08304c7c128151f9a4370), [`6af04a5`](https://github.com/Effect-TS/effect/commit/6af04a50bd019238f6acdb9cbda40439a3c09210), [`cb6c837`](https://github.com/Effect-TS/effect/commit/cb6c8376b2f322d4e7cbfc0973fc3b4f2951ee6e), [`d44cead`](https://github.com/Effect-TS/effect/commit/d44cead7e0e0ce61f0d980906e494f49a07e7899), [`88c7632`](https://github.com/Effect-TS/effect/commit/88c7632c2b59a49fcc40d250865bd8d0dccf31b0), [`abcbb2a`](https://github.com/Effect-TS/effect/commit/abcbb2abe16f1b6c587c15007df14371e1e70e93), [`8f63cce`](https://github.com/Effect-TS/effect/commit/8f63cce636700fde26b140b82e350ef916989d86), [`d56dfcf`](https://github.com/Effect-TS/effect/commit/d56dfcf54c2b9c53c3d098ce4b0ffcc84496c5f7), [`a98cda9`](https://github.com/Effect-TS/effect/commit/a98cda9422e1352f22e81696f759f326ffcfb667), [`6704bb8`](https://github.com/Effect-TS/effect/commit/6704bb84c320547f83cf50e8586ffc4c5e4c3cc5), [`6143de2`](https://github.com/Effect-TS/effect/commit/6143de21ee22038b45a8d4eba86f5aade6238eba), [`936b135`](https://github.com/Effect-TS/effect/commit/936b1358396eb0a1a7c8e0878ba63297e2106812), [`1bbae84`](https://github.com/Effect-TS/effect/commit/1bbae84f88b577a26d04ceb2e76d3143d09c4a20), [`d795ee7`](https://github.com/Effect-TS/effect/commit/d795ee771701ea62bd187ef7c0307d9737f68c1a), [`0a82d88`](https://github.com/Effect-TS/effect/commit/0a82d88b7da73278b6f270118e396d5ed4a64747), [`9215bc5`](https://github.com/Effect-TS/effect/commit/9215bc5da7dd10aa45f07fe44b98f06b6e433d62), [`a1b5df2`](https://github.com/Effect-TS/effect/commit/a1b5df2064d92431cfc6e638af613cc3114313d7), [`92a9ac5`](https://github.com/Effect-TS/effect/commit/92a9ac5ac0aa63d8975b9ba7a094d6a8f59a98f2), [`6bde7f2`](https://github.com/Effect-TS/effect/commit/6bde7f27f3243427203e53fe74472990e5c2a349), [`a712131`](https://github.com/Effect-TS/effect/commit/a7121310dbb60cbd819bbd702f97663098ec7bb8), [`2e6f760`](https://github.com/Effect-TS/effect/commit/2e6f760dcb44e2b984f3311a8af03a1d68a2ec7e), [`aa05804`](https://github.com/Effect-TS/effect/commit/aa0580497e027ed30b756058db0067c3fe07664f), [`badd3bf`](https://github.com/Effect-TS/effect/commit/badd3bf65fac4dd1e66e1f602db43659722dfced), [`02b0265`](https://github.com/Effect-TS/effect/commit/02b02651ede46a5a2dd3ef8081d0ad89648d0cbf), [`3437e21`](https://github.com/Effect-TS/effect/commit/3437e21a56d805781c5e5946a6189795a1dfd411), [`41a550d`](https://github.com/Effect-TS/effect/commit/41a550d1fed31e829929a8f5362b5340303164ac), [`17b5d50`](https://github.com/Effect-TS/effect/commit/17b5d50219ad49533cf9e33d01924a3e16af5eb3), [`96e5e95`](https://github.com/Effect-TS/effect/commit/96e5e9576b0315c747462761a61940ff9fe32dd1), [`e4d589e`](https://github.com/Effect-TS/effect/commit/e4d589e0ea08dc57c4793053b395dc0fcc499f34), [`ae4cf7b`](https://github.com/Effect-TS/effect/commit/ae4cf7b5e2cb5f8c55657e31a61789ad21c38c18), [`6ef5f1a`](https://github.com/Effect-TS/effect/commit/6ef5f1a041f3a40bf03fadd0b1feb275c277c635), [`2235a29`](https://github.com/Effect-TS/effect/commit/2235a29502c3f33cf6468511ad931089013a7916), [`b32f4cb`](https://github.com/Effect-TS/effect/commit/b32f4cb7b2d8ebe817075322622498e3beb05336), [`7f4c095`](https://github.com/Effect-TS/effect/commit/7f4c095b62da43780dd7fc2a5d1785ddfce60edf), [`5f3fb81`](https://github.com/Effect-TS/effect/commit/5f3fb814d18d8a54946c1c1cd0b41459cdb24006), [`17f0b91`](https://github.com/Effect-TS/effect/commit/17f0b91a243ccfe4a38d27debdc983adf434e738), [`0cdadd7`](https://github.com/Effect-TS/effect/commit/0cdadd75bc8abbbcad7956a4bc71f4e7a9b13250), [`39b57d7`](https://github.com/Effect-TS/effect/commit/39b57d7857358040558b67dd33eafc7bb5457830), [`5a6a573`](https://github.com/Effect-TS/effect/commit/5a6a5738e5bfc39e3a37ae7ba99081601fa19ac3), [`59f5e99`](https://github.com/Effect-TS/effect/commit/59f5e9981913b92d7a9beb2214a21d658b999d3a), [`45379d6`](https://github.com/Effect-TS/effect/commit/45379d6179ee4df2cbd3f848bd39ff7149c24a38), [`1949439`](https://github.com/Effect-TS/effect/commit/1949439175809ef81ab9c6411ed5559109edb4c9), [`e443403`](https://github.com/Effect-TS/effect/commit/e443403cf0e4effea14bb6cd950c5ac1c86cc748), [`03af7e8`](https://github.com/Effect-TS/effect/commit/03af7e85551204c605ea2fa2c43c10a4538ac8fb), [`0f721d4`](https://github.com/Effect-TS/effect/commit/0f721d406df8703ea92ca28777b3f09599e2056d), [`130b28d`](https://github.com/Effect-TS/effect/commit/130b28df552d7053407b041a96ff09dae82575e5), [`c987a12`](https://github.com/Effect-TS/effect/commit/c987a12a01b6a52ad53d29edf02613b03574dbcc), [`4158562`](https://github.com/Effect-TS/effect/commit/41585620977de9b84171f76619b72e29cc2284e5), [`306014a`](https://github.com/Effect-TS/effect/commit/306014a1ce4d5cb956c76bdc20e4e28ab3e61a6a), [`729a663`](https://github.com/Effect-TS/effect/commit/729a663275dd31f2357c446fe69664429220a83d), [`caf84b6`](https://github.com/Effect-TS/effect/commit/caf84b660044089e8d7f4067b279b27b8b50e8fd), [`ce067f7`](https://github.com/Effect-TS/effect/commit/ce067f799ea27735d4194345298a216aaf429f01), [`7a41f5a`](https://github.com/Effect-TS/effect/commit/7a41f5aa72d540ecf2746992ecc3fa3e6b40d31f), [`781022a`](https://github.com/Effect-TS/effect/commit/781022acdd3537ca18c88e2fa3681bafa6ef1b21), [`39f1297`](https://github.com/Effect-TS/effect/commit/39f1297acc08864feb12de6b8cf2bf73434f6cf5), [`2db266b`](https://github.com/Effect-TS/effect/commit/2db266b1bfbc81868bc1778c37c76032a267c79f), [`2141e28`](https://github.com/Effect-TS/effect/commit/2141e28903754d72604acf81673ceb2c62a56646), [`3c5e429`](https://github.com/Effect-TS/effect/commit/3c5e429878669ffcf5e0da4ddfbf50bde5bbcaad), [`20ddc63`](https://github.com/Effect-TS/effect/commit/20ddc630584f8fe488162ba384adcae53fc6810a), [`841b3ea`](https://github.com/Effect-TS/effect/commit/841b3ea6ae19a784bc1c20497b02f632af0c91e9), [`82a3fbf`](https://github.com/Effect-TS/effect/commit/82a3fbfce8b9df33e587076b7d7168ecd6799e17), [`eb9ee83`](https://github.com/Effect-TS/effect/commit/eb9ee83b38844a71d1cd5653a229309cfcb04a36), [`64dc7c7`](https://github.com/Effect-TS/effect/commit/64dc7c76dc5c89887b9e7c181d1873dcbb7820d1), [`84dc8ab`](https://github.com/Effect-TS/effect/commit/84dc8ab7accc682bc668c78a97e4a1776b633be8), [`b4463f4`](https://github.com/Effect-TS/effect/commit/b4463f46fc33d3b01ea5eadd7d012a5abda347a3), [`592dd36`](https://github.com/Effect-TS/effect/commit/592dd361645739ac0cd8e6babb084cd27403c172), [`85d2b44`](https://github.com/Effect-TS/effect/commit/85d2b446e3059de4919be730105868f79728308d), [`32e4a69`](https://github.com/Effect-TS/effect/commit/32e4a69b3151b7ec4058af2213b96a41d11e9e06), [`13c5872`](https://github.com/Effect-TS/effect/commit/13c5872ed30830360367ad89af2dab68a003c351), [`3454cdb`](https://github.com/Effect-TS/effect/commit/3454cdb528fdb5d3ed0c5c5c8169bc47de41fbd8), [`e930804`](https://github.com/Effect-TS/effect/commit/e9308045be1d8a00c0b4046f1e8ff22cf68c93da), [`7f12d4b`](https://github.com/Effect-TS/effect/commit/7f12d4b4e731dc3a213ae5c3f60db9edc50292d2), [`181c9ef`](https://github.com/Effect-TS/effect/commit/181c9ef5e5d4ab247bf4aec06424f15b0a1e802e), [`dd9f891`](https://github.com/Effect-TS/effect/commit/dd9f891e23f316abb6192893008f0e33ece9d97d), [`433fb81`](https://github.com/Effect-TS/effect/commit/433fb81ca4c15c681a8ae097ce3ff9bd3a9c9aa5), [`8459cdb`](https://github.com/Effect-TS/effect/commit/8459cdbae8a476dc04b6247fffe6a1668dcb1217), [`6124ab3`](https://github.com/Effect-TS/effect/commit/6124ab39eb64688fbd5d688d24766542f9cb5a2c), [`01bd954`](https://github.com/Effect-TS/effect/commit/01bd9546f142706fca1628f7261e6d1cb9638948), [`ba2c3aa`](https://github.com/Effect-TS/effect/commit/ba2c3aa05eb87ec05d263b960017ecf29746f66a), [`0a45ef3`](https://github.com/Effect-TS/effect/commit/0a45ef3bb4a1ae9b345c43c548db4336a31b3191), [`8459cdb`](https://github.com/Effect-TS/effect/commit/8459cdbae8a476dc04b6247fffe6a1668dcb1217), [`eaa7e71`](https://github.com/Effect-TS/effect/commit/eaa7e71b88bf59b24610128c6115a2a126432731), [`db4c2cc`](https://github.com/Effect-TS/effect/commit/db4c2ccdec77d813b6f4cc72a302ee7c4fe6e39d), [`22f150a`](https://github.com/Effect-TS/effect/commit/22f150a0936cef30517e87eaca73bff1c5e4873a), [`90ffb08`](https://github.com/Effect-TS/effect/commit/90ffb083b3091c211300f50a42ba7bf56536c0ee), [`d517692`](https://github.com/Effect-TS/effect/commit/d517692ef75f45d5f6d9d68b32d41fa0ccc56c99), [`01af079`](https://github.com/Effect-TS/effect/commit/01af079c189d1fc5067d3b1933b2870c4baf2693), [`32a59e8`](https://github.com/Effect-TS/effect/commit/32a59e8058b1ec9738cb083cf1cb116b393ca114)]: + - effect@4.0.0-beta.104 + +## 4.0.0-beta.103 + +### Patch Changes + +- [#6667](https://github.com/Effect-TS/effect/pull/6667) [`5283841`](https://github.com/Effect-TS/effect/commit/52838418db2e04db6aaed2fa01b280f2aad4032a) Thanks @tim-smart! - Surface parsed chat completion stream events that do not match the expected schema as `UnknownChatCompletionEvent`. + +- [#6777](https://github.com/Effect-TS/effect/pull/6777) [`8e7c706`](https://github.com/Effect-TS/effect/commit/8e7c706b0aca855489b53d987404566d3e9cb5e7) Thanks @tim-smart! - Bound pending SSE decoder state with a configurable maximum event size. + +- [#6882](https://github.com/Effect-TS/effect/pull/6882) [`25a029c`](https://github.com/Effect-TS/effect/commit/25a029ccf2f6478dc2ae1fca96ceed9c394deeb3) Thanks @tim-smart! - Decode streaming and non-streaming tool call parameters with the provider-facing OpenAI schema codec. + +- [#6719](https://github.com/Effect-TS/effect/pull/6719) [`20b9660`](https://github.com/Effect-TS/effect/commit/20b9660d42ae4afc00bb4251b57657e6363c8808) Thanks @IMax153! - Group consecutive tool calls into one assistant message when using Chat Completions APIs. + +- [#6701](https://github.com/Effect-TS/effect/pull/6701) [`9867b9f`](https://github.com/Effect-TS/effect/commit/9867b9fc69f9cc6c443594fc7eccc7be0c674d9c) Thanks @fubhy! - Removed explicit ./index entrypoints + +- Updated dependencies [[`e56cd8f`](https://github.com/Effect-TS/effect/commit/e56cd8f90c3559baccf8fcf2852ea911235d5944), [`f77c120`](https://github.com/Effect-TS/effect/commit/f77c120d8e04779ddeb8bce8e9cde932f268e4b6), [`b2f95a9`](https://github.com/Effect-TS/effect/commit/b2f95a9c2f2581deb89dc3bae9e89cf819e82923), [`04fd44a`](https://github.com/Effect-TS/effect/commit/04fd44a42abfa8dc2642300dcf49ee48c8ef4539), [`b74333d`](https://github.com/Effect-TS/effect/commit/b74333d83e15b9d042e4698ad23040de60454afe), [`1c40b28`](https://github.com/Effect-TS/effect/commit/1c40b2809503d6aa1358777196fc66317906e657), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`b3901d2`](https://github.com/Effect-TS/effect/commit/b3901d29c543fd5bd05ceec669a17896c8e19006), [`4a0984a`](https://github.com/Effect-TS/effect/commit/4a0984af62738fedf4bd3e87adb4d4d641ce9147), [`fffd88b`](https://github.com/Effect-TS/effect/commit/fffd88b3135abdf928ca7c4b0e00e610985091c7), [`f3f6c1e`](https://github.com/Effect-TS/effect/commit/f3f6c1e02cb543423fcffef5dc2db03fac503588), [`ef07642`](https://github.com/Effect-TS/effect/commit/ef07642dfe671d5258b65d1c1480c4d05c495f15), [`f1bc827`](https://github.com/Effect-TS/effect/commit/f1bc8274a608813d7b09d28dcca04adbf62f8c92), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`081f4d8`](https://github.com/Effect-TS/effect/commit/081f4d8cd06a2ac222d2810b46e61efcee26939e), [`5287b24`](https://github.com/Effect-TS/effect/commit/5287b24f5f8fa094ba20e117bfb1a80fba6d2cf5), [`13d31cf`](https://github.com/Effect-TS/effect/commit/13d31cfc2dde46210e94391b5b6767ae9aeaf2c9), [`acee269`](https://github.com/Effect-TS/effect/commit/acee26944bc89ee554d7b9fadab7443f9edc28a9), [`31170c1`](https://github.com/Effect-TS/effect/commit/31170c19b236c37abb5476c821bc6f5bfa2735ab), [`205ebc7`](https://github.com/Effect-TS/effect/commit/205ebc776062012581e98fced7ced19adfc44ee7), [`ed0ebf8`](https://github.com/Effect-TS/effect/commit/ed0ebf8e5c864d46fed1f232e99c0e680f10a58f), [`a3fd084`](https://github.com/Effect-TS/effect/commit/a3fd08482157bd78b089f77c7b173d54ef68b5cd), [`ee29ddf`](https://github.com/Effect-TS/effect/commit/ee29ddf862c3723ad466abc93ab6f6fe723b2319), [`6086309`](https://github.com/Effect-TS/effect/commit/60863090af8e5af0bfa1435f08dc5390f9993e30), [`4a57af2`](https://github.com/Effect-TS/effect/commit/4a57af24011db1d66e947289d2f7ffc2074696d2), [`660875b`](https://github.com/Effect-TS/effect/commit/660875b4325e6eebb3f04513998301cd2a0847ec), [`8e7c706`](https://github.com/Effect-TS/effect/commit/8e7c706b0aca855489b53d987404566d3e9cb5e7), [`5f63adb`](https://github.com/Effect-TS/effect/commit/5f63adbe75fc9d50d23706a52b3e483ad2a1a01c), [`053bc42`](https://github.com/Effect-TS/effect/commit/053bc42e2a964755611a216e78ed214322efee37), [`c0a1534`](https://github.com/Effect-TS/effect/commit/c0a153494484ecf9f0d0f20895a7a648b4be363b), [`f1e3a37`](https://github.com/Effect-TS/effect/commit/f1e3a378c144f974a6122b299f421b75595af20f), [`cedb01a`](https://github.com/Effect-TS/effect/commit/cedb01a025492a1faf9e59eb23eb96bc3b5e2fff), [`1747440`](https://github.com/Effect-TS/effect/commit/1747440de9a51a56ed3660da748cc01b256adce7), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`b4f1ee2`](https://github.com/Effect-TS/effect/commit/b4f1ee238d96aa78c5f040158cb78671d75b381e), [`a4757f1`](https://github.com/Effect-TS/effect/commit/a4757f1c47067d8d016a6c4a2c541bb8ae520f9b), [`cd122b9`](https://github.com/Effect-TS/effect/commit/cd122b90300d995a237993a2edb7a049785ab6a4), [`5de588b`](https://github.com/Effect-TS/effect/commit/5de588b2472fb0f4eb919766eb8472583a044772), [`3895b9c`](https://github.com/Effect-TS/effect/commit/3895b9cf179262cd277a9c6daafe9050dcf8265e), [`89ce5f3`](https://github.com/Effect-TS/effect/commit/89ce5f3e16e23a193daa475dc72ea8133ae1dacd), [`985de09`](https://github.com/Effect-TS/effect/commit/985de097d75906db2aed784841f81e23cc978b43), [`9800e3a`](https://github.com/Effect-TS/effect/commit/9800e3acc8f36530f671bc8b91558cb112f449a7), [`4dc35f6`](https://github.com/Effect-TS/effect/commit/4dc35f64641746366f867ea3dbfedb9cd4685ada), [`e8eb62b`](https://github.com/Effect-TS/effect/commit/e8eb62b3d0ef27e9761cdc2eb93bdec52d6ee204), [`ecd9993`](https://github.com/Effect-TS/effect/commit/ecd99936112cb69efdb02de3a2fd57f47baefdf3), [`5ab9c08`](https://github.com/Effect-TS/effect/commit/5ab9c08463ce049c45f3502676954a7b72c6b024), [`f5cf965`](https://github.com/Effect-TS/effect/commit/f5cf96548afd51f4b3cf1aea11b04d7f8549ce90), [`a94cbed`](https://github.com/Effect-TS/effect/commit/a94cbed84e9e49bea4bff925599c0f19c4e3deab), [`9160ad7`](https://github.com/Effect-TS/effect/commit/9160ad7d146d4376dd12f7510c025e5b2f638a70), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`52494be`](https://github.com/Effect-TS/effect/commit/52494be9e8eb3bb542d06a3dfefc6bca4e168984), [`5441c8e`](https://github.com/Effect-TS/effect/commit/5441c8e656a6418c0d27feb2df67565a3e1155f4), [`c9b56ab`](https://github.com/Effect-TS/effect/commit/c9b56ab507f224426ee8388dc450da447ec4715f), [`8ef7257`](https://github.com/Effect-TS/effect/commit/8ef72577d1f43212cab87951d659e54e3c8d7d91), [`1519406`](https://github.com/Effect-TS/effect/commit/1519406fed6e8b017ae178dc20bcaa2cf318b570), [`9716990`](https://github.com/Effect-TS/effect/commit/97169902eec3c99baa7f0b2c7b45a0a5eae75819), [`733f75b`](https://github.com/Effect-TS/effect/commit/733f75b7125e3016a975fdd251c0179ae5393786), [`48155c8`](https://github.com/Effect-TS/effect/commit/48155c8ccfc12dcca8a00fa358d50b20c30874e4), [`951d06b`](https://github.com/Effect-TS/effect/commit/951d06b83d459d3e8fa9024e727a5db1662d3322), [`d767b65`](https://github.com/Effect-TS/effect/commit/d767b65a7687e38be23f0b0ee3d52ab5f2360cbe), [`5d52d9d`](https://github.com/Effect-TS/effect/commit/5d52d9d148aaa7f736ed8c310fc8bfa9dc81badf), [`f4151e1`](https://github.com/Effect-TS/effect/commit/f4151e1937c26de14f1d64566f8126173f1b5014), [`e02fbb6`](https://github.com/Effect-TS/effect/commit/e02fbb66f5a0f13dba6c33ef63528a37a17a0676), [`724ce09`](https://github.com/Effect-TS/effect/commit/724ce09650a458d4565e5c7331ea92ca04f08e68), [`dbe91f6`](https://github.com/Effect-TS/effect/commit/dbe91f6961ef9f7e8da910ee5758d9c0d385fca8), [`4c008d2`](https://github.com/Effect-TS/effect/commit/4c008d28b370d817f7ae4579db09836fe084c8d2), [`b650832`](https://github.com/Effect-TS/effect/commit/b6508328708a842f3163467b72486bd228f1a289), [`b46c92f`](https://github.com/Effect-TS/effect/commit/b46c92f3b314f4ffd612b831efa55dd856c587a3), [`5335797`](https://github.com/Effect-TS/effect/commit/5335797003076d9c6fd170da98d779696d555596), [`4b3460d`](https://github.com/Effect-TS/effect/commit/4b3460daa434ec465a95a50704fe1103a9275999), [`6301fd7`](https://github.com/Effect-TS/effect/commit/6301fd710b4325718de2c42997dac28a9e9aa250), [`aebc5c6`](https://github.com/Effect-TS/effect/commit/aebc5c61664b89a840465ec65b79ce635a5ceee8), [`52b2d7b`](https://github.com/Effect-TS/effect/commit/52b2d7b5bd3c7cce3bd5b69c6ab3941004da70f3), [`eec5744`](https://github.com/Effect-TS/effect/commit/eec57445dfa0ef3c5977195ad69415b7e7d42bb6), [`24e0e93`](https://github.com/Effect-TS/effect/commit/24e0e93dc307dc2c2ae86caacb7289e1dab3c103), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`1a7ce81`](https://github.com/Effect-TS/effect/commit/1a7ce8150e3977586c44d8ccb9a8384389bb4d49), [`48f22a7`](https://github.com/Effect-TS/effect/commit/48f22a7d16ae57ee2175d450dafbdeb69e187d2a), [`c96b7f6`](https://github.com/Effect-TS/effect/commit/c96b7f6359662053c3e09344f61dddc7a6caf4ac), [`6d2a942`](https://github.com/Effect-TS/effect/commit/6d2a942ed7cd33b8fd79d549edba33bc9e2a7e3e), [`cc27b19`](https://github.com/Effect-TS/effect/commit/cc27b194b9d13fa3a66ab037e853fca9d41700ff), [`8f9499f`](https://github.com/Effect-TS/effect/commit/8f9499f562729f5f7b08d8bcc4db86b4aeff8a21), [`3eeea73`](https://github.com/Effect-TS/effect/commit/3eeea73cfc3e9b126975c2ddbdb7f7c8c92026e2), [`0a532e5`](https://github.com/Effect-TS/effect/commit/0a532e503f165fdea485a5343fc2f420917e8376), [`f398149`](https://github.com/Effect-TS/effect/commit/f398149c134fd9b67b6cdc52eae3f3248d5c7bbe), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`ace903e`](https://github.com/Effect-TS/effect/commit/ace903e09c2549ceebdec380797beb027cd29f3d), [`e8eb62b`](https://github.com/Effect-TS/effect/commit/e8eb62b3d0ef27e9761cdc2eb93bdec52d6ee204), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`48f22a7`](https://github.com/Effect-TS/effect/commit/48f22a7d16ae57ee2175d450dafbdeb69e187d2a), [`d48506d`](https://github.com/Effect-TS/effect/commit/d48506d97525040aa714305e928126df799795b4), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`d48506d`](https://github.com/Effect-TS/effect/commit/d48506d97525040aa714305e928126df799795b4), [`d48506d`](https://github.com/Effect-TS/effect/commit/d48506d97525040aa714305e928126df799795b4), [`52262be`](https://github.com/Effect-TS/effect/commit/52262be2edce0e350c6ac10f8f725678606399c5), [`1284aa1`](https://github.com/Effect-TS/effect/commit/1284aa183451955ad7921bbe01fd0e095695d444), [`9867b9f`](https://github.com/Effect-TS/effect/commit/9867b9fc69f9cc6c443594fc7eccc7be0c674d9c), [`d0f1a22`](https://github.com/Effect-TS/effect/commit/d0f1a2295155c350b04efb46852cb40032805273), [`979ce39`](https://github.com/Effect-TS/effect/commit/979ce3985d7d62ce2bf240681ca19feda3027452), [`b6d3e67`](https://github.com/Effect-TS/effect/commit/b6d3e67c7cc143cd8470cdf704324e79d23954a9), [`adf6c6c`](https://github.com/Effect-TS/effect/commit/adf6c6cd388af8a3c0c546492e71555368556f6a), [`7314d60`](https://github.com/Effect-TS/effect/commit/7314d605284717aaafe7fc34b88c3c93397e865c), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`1acbd8b`](https://github.com/Effect-TS/effect/commit/1acbd8b44c68ebb23735e9810476b870dbe58aea), [`7bde6cc`](https://github.com/Effect-TS/effect/commit/7bde6ccb2b144fe953ff30a7ef5e1ecc97697146), [`a959a8b`](https://github.com/Effect-TS/effect/commit/a959a8bf21cdb976369f494dc949fa00a050d3e0)]: + - effect@4.0.0-beta.103 + +## 4.0.0-beta.102 + +### Patch Changes + +- [#6608](https://github.com/Effect-TS/effect/pull/6608) [`0e50ec7`](https://github.com/Effect-TS/effect/commit/0e50ec7dbb94390666f292cf9120719bf30a7246) Thanks @gcanti! - Add `Schema.Natural` for non-negative safe integers and use canonical `Schema.Int`, `Schema.Finite`, and `Schema.Natural` schemas for numeric domain values across Effect, AI protocols, and OpenAPI patches. + + Update the date, date-time, file, time-zone, cluster, event-log, persistence, socket, SQL, and DevTools schemas to reject invalid non-finite or non-integer values where appropriate. Correct the decoded schema of `Schema.NumberFromString`, and allow `Schema.DurationFromMillis` and `Schema.DurationFromNanos` to represent negative durations. + +- [#6650](https://github.com/Effect-TS/effect/pull/6650) [`acd385e`](https://github.com/Effect-TS/effect/commit/acd385ebb3f9edee37ab6715607119ee9762a615) Thanks @IMax153! - Redact OpenAI organization and project headers from client errors. + +- [#6567](https://github.com/Effect-TS/effect/pull/6567) [`5101e92`](https://github.com/Effect-TS/effect/commit/5101e92c9c149c153423f43dd7a94f6194653c06) Thanks @gcanti! - Add `Record.assignProperty` and safely handle dynamic record keys such as `__proto__` and inherited property names. + +- Updated dependencies [[`b6392e1`](https://github.com/Effect-TS/effect/commit/b6392e119704553edec1b4fd2869ac0dbec621ef), [`7ed9450`](https://github.com/Effect-TS/effect/commit/7ed945044eb56aa9aeaf62d4746a011c96c58628), [`45762bd`](https://github.com/Effect-TS/effect/commit/45762bd78df9ecd87c98b8d3738cdeeac7d81128), [`a6e8391`](https://github.com/Effect-TS/effect/commit/a6e8391cd31acd898fae18b3f8e7ca4c6f14f065), [`4ac7e8b`](https://github.com/Effect-TS/effect/commit/4ac7e8b136c61a26c3e438c013dfd7349b38e999), [`4cd40f5`](https://github.com/Effect-TS/effect/commit/4cd40f5692477783bef84fed3c5ef1c0cf5602e6), [`6956bc0`](https://github.com/Effect-TS/effect/commit/6956bc0e6cb27f53fbec39d9b18545940f9f598f), [`0e50ec7`](https://github.com/Effect-TS/effect/commit/0e50ec7dbb94390666f292cf9120719bf30a7246), [`9fcdade`](https://github.com/Effect-TS/effect/commit/9fcdade4a8af772b9ccd8b8a24fe8cee0e5d8470), [`57367d5`](https://github.com/Effect-TS/effect/commit/57367d54de55047ff0c5fce9685475e236bf354c), [`35c445f`](https://github.com/Effect-TS/effect/commit/35c445ff18029d192900ea0914c993f58d5cf1a5), [`c917bb9`](https://github.com/Effect-TS/effect/commit/c917bb94a4c1c4e0a24372a8ebb8a5ca232e36b5), [`bc1f358`](https://github.com/Effect-TS/effect/commit/bc1f3583e63344cb2c398d9040d9c975488ed123), [`0e0c9d7`](https://github.com/Effect-TS/effect/commit/0e0c9d7922ff463c1093d9e0576fae12cb0698d5), [`73d40aa`](https://github.com/Effect-TS/effect/commit/73d40aacd8fcae1b48c23f5b0a5c542127401d1d), [`4f1e318`](https://github.com/Effect-TS/effect/commit/4f1e3183f7123591c46224e9c587df7594562a5f), [`9d8d85c`](https://github.com/Effect-TS/effect/commit/9d8d85c1bb7da51970845b8ea830e386e777514a), [`6079fda`](https://github.com/Effect-TS/effect/commit/6079fda7b02f2f01ad91c15ab8c307336f3ba252), [`5101e92`](https://github.com/Effect-TS/effect/commit/5101e92c9c149c153423f43dd7a94f6194653c06), [`d0b3265`](https://github.com/Effect-TS/effect/commit/d0b3265c3262670761471ab3518cf933b1b3b20a), [`7a03c89`](https://github.com/Effect-TS/effect/commit/7a03c893ce6492bf94c0ebfb00b63bf25dcbf83e), [`cea1d9c`](https://github.com/Effect-TS/effect/commit/cea1d9c92601e69ebda040af8a1d860d604d885c), [`078e1f5`](https://github.com/Effect-TS/effect/commit/078e1f5636e31b76a86722a636afc37a8cc25580), [`97bafea`](https://github.com/Effect-TS/effect/commit/97bafeab460833b9781527b437d1cb9cbee63260), [`fab0ab8`](https://github.com/Effect-TS/effect/commit/fab0ab8f7ab15ae596faa4ccf75615a494d11b0b), [`c323d8b`](https://github.com/Effect-TS/effect/commit/c323d8b30dbbe85f9df25b67288b93d5332de333), [`6966353`](https://github.com/Effect-TS/effect/commit/69663534d626003eb10a5e55ab1f13e0379fead1), [`0444004`](https://github.com/Effect-TS/effect/commit/04440041989c1785fe4db286379f2be2c15baa85), [`028bbb3`](https://github.com/Effect-TS/effect/commit/028bbb391e161185da10d974ab33381f769940d7), [`ff5d6e2`](https://github.com/Effect-TS/effect/commit/ff5d6e278a1fdff714315dc1a17075012f05c1f0), [`1bfce93`](https://github.com/Effect-TS/effect/commit/1bfce93e6d2bf0794c11733daf51c2390e7de375), [`7ce815c`](https://github.com/Effect-TS/effect/commit/7ce815cd5af6af991dfc13b890fd22345fc77c20), [`7271a7f`](https://github.com/Effect-TS/effect/commit/7271a7faf1080aa75f2f53ca6a0b5ec9334c1d38), [`475fe5c`](https://github.com/Effect-TS/effect/commit/475fe5c12c2d6504c475797c0634f90da01e1797)]: + - effect@4.0.0-beta.102 + +## 4.0.0-beta.101 + +### Patch Changes + +- Updated dependencies [[`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`b35ed29`](https://github.com/Effect-TS/effect/commit/b35ed2904f01536d303b21f288daf343cf740462), [`dd44624`](https://github.com/Effect-TS/effect/commit/dd446245736a0e88c807a02f03c21450bb9340fa), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`2bae1ac`](https://github.com/Effect-TS/effect/commit/2bae1accce9d3b72cf6d5aefc9b2161af6d88436)]: + - effect@4.0.0-beta.101 + +## 4.0.0-beta.100 + +### Patch Changes + +- Updated dependencies [[`c1288dd`](https://github.com/Effect-TS/effect/commit/c1288dd1a52a2811ab7df57fc4ce236c6be4c745), [`2b58a3d`](https://github.com/Effect-TS/effect/commit/2b58a3dab6bc99776dddaf76e27d811e0f47f3d8), [`6dc83f2`](https://github.com/Effect-TS/effect/commit/6dc83f26ddf20d48db28cf761dd8f3716e5273fb), [`c1e2fe0`](https://github.com/Effect-TS/effect/commit/c1e2fe0cf93564f4d919e3998874c3e70b0cf30f), [`f3fbae8`](https://github.com/Effect-TS/effect/commit/f3fbae8d7bae0d77cb4f35a1598b26c58e3bf94d), [`e000f80`](https://github.com/Effect-TS/effect/commit/e000f80fd55bcd8edc699fdbf4cd109004f4f754), [`f4ee765`](https://github.com/Effect-TS/effect/commit/f4ee7655ee052cf9ba726fd602bb87c89c7c62a9), [`510b55f`](https://github.com/Effect-TS/effect/commit/510b55f3e21750685dbfd5f476a130c1c5af9dbd), [`31d3fc4`](https://github.com/Effect-TS/effect/commit/31d3fc4327c50867bb8d881fa7353aeb03ea2826), [`875e618`](https://github.com/Effect-TS/effect/commit/875e618c3764a7b817ac863d0af86924449528f2), [`688d46a`](https://github.com/Effect-TS/effect/commit/688d46afd0ef923d983ad3d7385f52f217b28d70), [`6ff5023`](https://github.com/Effect-TS/effect/commit/6ff502363b9840a5a5ee0a24bc6cae734ac3a3eb), [`c0333e7`](https://github.com/Effect-TS/effect/commit/c0333e7f755f42ddcca7051e029da8b4eed527bf), [`06e7e8c`](https://github.com/Effect-TS/effect/commit/06e7e8c66015ee318f871b9d2218dee82df2b108), [`eb9b102`](https://github.com/Effect-TS/effect/commit/eb9b10256c8558881b441c2fef833b7037174400), [`8b155da`](https://github.com/Effect-TS/effect/commit/8b155da06e0740c354ec562957a45ab65eb4573b), [`3a87335`](https://github.com/Effect-TS/effect/commit/3a8733564c5db35271aa20564ed0d344daa2a79f)]: + - effect@4.0.0-beta.100 + +## 4.0.0-beta.99 + +### Patch Changes + +- Updated dependencies [[`8ce4795`](https://github.com/Effect-TS/effect/commit/8ce4795ccbaebca4292757db568c005a992546a4), [`80b539f`](https://github.com/Effect-TS/effect/commit/80b539f8aba68f478c75c35c2b4140c4ffc4fada), [`88a54cc`](https://github.com/Effect-TS/effect/commit/88a54cc341006e3ebcb13482c618f62a680ce199), [`e6e6dba`](https://github.com/Effect-TS/effect/commit/e6e6dba6e9d86e7c2ad27dcedf289db76a19697f), [`bfb203e`](https://github.com/Effect-TS/effect/commit/bfb203e95aa439f731acad37fc3a9a831a190f1c), [`2e9a34a`](https://github.com/Effect-TS/effect/commit/2e9a34ac2bece4f3a206160480c991e3841dc67a), [`55d4eb3`](https://github.com/Effect-TS/effect/commit/55d4eb34f2c64d54f6a25a305b5c5438ebd7934e), [`bddb010`](https://github.com/Effect-TS/effect/commit/bddb010eac3d4436cb094edbbee7460c5440c162), [`a328835`](https://github.com/Effect-TS/effect/commit/a328835e50d76bc96648a1c1550456e8c9f81210), [`5560d05`](https://github.com/Effect-TS/effect/commit/5560d05aa6abdd29466d9c3412cc5e648b0adbde), [`8f6e3ad`](https://github.com/Effect-TS/effect/commit/8f6e3adb185b16e8820b98c509b308086f7ff1af), [`46997fa`](https://github.com/Effect-TS/effect/commit/46997fa60401f5e3c93daa4b61f7df8e31caaab4), [`9e6e12d`](https://github.com/Effect-TS/effect/commit/9e6e12d75c118cd265496f2880490d1f33a5c8bf), [`3394b93`](https://github.com/Effect-TS/effect/commit/3394b93d97d6f24fc38670641d1490289ffca7f1), [`febeabc`](https://github.com/Effect-TS/effect/commit/febeabc3f7c31094da000a23edeaabfe2ab00a38), [`54161c9`](https://github.com/Effect-TS/effect/commit/54161c98f6f3569e0c31842f54e6a257f9421c4c), [`385f7a4`](https://github.com/Effect-TS/effect/commit/385f7a4ee4a7359928597ea56d151dbaf5eb5802), [`7eea4d0`](https://github.com/Effect-TS/effect/commit/7eea4d0b73ec554915d7066a71f46326ce2ba45f), [`7543afe`](https://github.com/Effect-TS/effect/commit/7543afea6f4d97d1f1ad876224323838a48daadd), [`44b9cf3`](https://github.com/Effect-TS/effect/commit/44b9cf3d240d726997b4bbcd0ede48e074d3c456), [`7eea4d0`](https://github.com/Effect-TS/effect/commit/7eea4d0b73ec554915d7066a71f46326ce2ba45f), [`0a8aa6a`](https://github.com/Effect-TS/effect/commit/0a8aa6acb90a72b91c24d17133c950e4cacd8abd), [`c8d9fcf`](https://github.com/Effect-TS/effect/commit/c8d9fcf7b030f7c474effbab2764ce7aee1c7209), [`9ca7f9a`](https://github.com/Effect-TS/effect/commit/9ca7f9a69363e4485645966d5a93b8f9597c5206), [`e7aca89`](https://github.com/Effect-TS/effect/commit/e7aca894bb32fbb785b5830837e6061c415a6015), [`55d7560`](https://github.com/Effect-TS/effect/commit/55d75609b8acf8a1b54c1b1c7fbbb65ec741aa3e), [`f809189`](https://github.com/Effect-TS/effect/commit/f809189ddf6b6011ba43a9901baaa734e315da2a), [`88a54cc`](https://github.com/Effect-TS/effect/commit/88a54cc341006e3ebcb13482c618f62a680ce199), [`0ebdbe7`](https://github.com/Effect-TS/effect/commit/0ebdbe74463dc84385956d0b1e8c2b79ebab5400), [`7517d09`](https://github.com/Effect-TS/effect/commit/7517d09f12a0b183a81bd425962c4e280a68b05d), [`212493b`](https://github.com/Effect-TS/effect/commit/212493b9a1eb98cd1ef6959c707a2e5784a5ae91), [`88a54cc`](https://github.com/Effect-TS/effect/commit/88a54cc341006e3ebcb13482c618f62a680ce199), [`80ea8cb`](https://github.com/Effect-TS/effect/commit/80ea8cb9222ca73f564c8267ab2f82966fea027a), [`8df19f4`](https://github.com/Effect-TS/effect/commit/8df19f4fe81d90cc33ace88b9a77e5534f82d604)]: + - effect@4.0.0-beta.99 + +## 4.0.0-beta.98 + +### Patch Changes + +- [#2608](https://github.com/Effect-TS/effect-smol/pull/2608) [`5f142a2`](https://github.com/Effect-TS/effect-smol/commit/5f142a2136e62274ec121273fecb5d248c19f159) Thanks @tim-smart! - Preserve autocomplete for known OpenAI-compatible model config properties while allowing provider-specific properties. + +- [#2576](https://github.com/Effect-TS/effect-smol/pull/2576) [`0c2ed5e`](https://github.com/Effect-TS/effect-smol/commit/0c2ed5ee0ef5a437c5e6a2de456a5ddf214c281f) Thanks @tim-smart! - Normalize empty assistant message content to an empty string for OpenAI-compatible providers that reject null content values. + +- Updated dependencies [[`989603b`](https://github.com/Effect-TS/effect-smol/commit/989603b60ab1197b64acf214208e0d370cd1f842), [`214c458`](https://github.com/Effect-TS/effect-smol/commit/214c458084bb6995d543cd37d1055f24be3d454e), [`a037273`](https://github.com/Effect-TS/effect-smol/commit/a0372736ac34796969b051bbba4717d7983f1ebe), [`97fdaa9`](https://github.com/Effect-TS/effect-smol/commit/97fdaa9c1f522c65e579365d314a07878e2b904f), [`b24d248`](https://github.com/Effect-TS/effect-smol/commit/b24d248c8df44222ce642087cde2bd859a2dc709), [`19c222c`](https://github.com/Effect-TS/effect-smol/commit/19c222cac2353a3d7b7733caecb00556fffe9a5c), [`eec85dd`](https://github.com/Effect-TS/effect-smol/commit/eec85ddba09ea326fd268ee33eeffd47e50d4671), [`0082f4f`](https://github.com/Effect-TS/effect-smol/commit/0082f4f74fb139fd578f87f0a790e845133983dc), [`8849052`](https://github.com/Effect-TS/effect-smol/commit/884905232d1e9a365e046d8dde27bf9c5707f57f), [`c15e16a`](https://github.com/Effect-TS/effect-smol/commit/c15e16ad130d1fbde25d912b7ac55995066cb35b), [`01d00a3`](https://github.com/Effect-TS/effect-smol/commit/01d00a3abfbf1f37996cdbe738ea5137c646cdd7), [`8bd4589`](https://github.com/Effect-TS/effect-smol/commit/8bd458975a1b3a8ed042eccf317b93d28ded91e7), [`0082f4f`](https://github.com/Effect-TS/effect-smol/commit/0082f4f74fb139fd578f87f0a790e845133983dc), [`6e08428`](https://github.com/Effect-TS/effect-smol/commit/6e08428d980501b856f846ad3f3f0e4ea46e7786), [`388dcf9`](https://github.com/Effect-TS/effect-smol/commit/388dcf953f65d317547f34d40e6443c5f264205f), [`2b7ce2b`](https://github.com/Effect-TS/effect-smol/commit/2b7ce2b513e7ec2a77822f1116dc6ffb6ba93f4e), [`87bea7e`](https://github.com/Effect-TS/effect-smol/commit/87bea7e16259246f3bcdf565446394751abca953), [`ce38dc3`](https://github.com/Effect-TS/effect-smol/commit/ce38dc33bda805a684432cca071f4dc3c6b9a1ba), [`a807cd1`](https://github.com/Effect-TS/effect-smol/commit/a807cd170341deca8a1cfb52c4222585f2431bb9), [`fd8a356`](https://github.com/Effect-TS/effect-smol/commit/fd8a356f06a8c9ce4e7e0a13fc4021c178ed31de), [`c2a5edc`](https://github.com/Effect-TS/effect-smol/commit/c2a5edc3abd31ad5bc123362bc1213e03e4095c3), [`5946da3`](https://github.com/Effect-TS/effect-smol/commit/5946da3804a1be5e752b05b96bd058cdba50a1bf), [`4ae0c5f`](https://github.com/Effect-TS/effect-smol/commit/4ae0c5ffcbe6c56ddfcb05c639112a079483539e), [`5b2a0bc`](https://github.com/Effect-TS/effect-smol/commit/5b2a0bceea3a28a33a58555210c90a415dc74a76), [`72ac585`](https://github.com/Effect-TS/effect-smol/commit/72ac585884befde6af9208da738699a93f1bae79), [`5e8c1b8`](https://github.com/Effect-TS/effect-smol/commit/5e8c1b82bfafa121311f987a49ab75395e3647a7), [`0f9c078`](https://github.com/Effect-TS/effect-smol/commit/0f9c07841b04183f485ee6e6458de73b290b09f5)]: + - effect@4.0.0-beta.98 + +## 4.0.0-beta.97 + +### Patch Changes + +- Updated dependencies []: + - effect@4.0.0-beta.97 + +## 4.0.0-beta.96 + +### Patch Changes + +- Updated dependencies [[`1503f45`](https://github.com/Effect-TS/effect-smol/commit/1503f45cb5bb2a74f4705252ec505a1f0ade7e62), [`57fe793`](https://github.com/Effect-TS/effect-smol/commit/57fe79316ffbc380b30626a168981fb26ae97459), [`0c2f78f`](https://github.com/Effect-TS/effect-smol/commit/0c2f78f695ec474e1ff5474da183577975e418f5), [`0c2f78f`](https://github.com/Effect-TS/effect-smol/commit/0c2f78f695ec474e1ff5474da183577975e418f5), [`0c2f78f`](https://github.com/Effect-TS/effect-smol/commit/0c2f78f695ec474e1ff5474da183577975e418f5), [`97f29df`](https://github.com/Effect-TS/effect-smol/commit/97f29df457f7ffd07cfb4b379315c12c086af805)]: + - effect@4.0.0-beta.96 + +## 4.0.0-beta.95 + +### Patch Changes + +- Updated dependencies [[`a482442`](https://github.com/Effect-TS/effect-smol/commit/a482442abdeb490e9652b854ec3495e4aa7273e7), [`fbefa85`](https://github.com/Effect-TS/effect-smol/commit/fbefa850fab2f0a302c20614496aeaaa2a8b5590), [`0b4a32f`](https://github.com/Effect-TS/effect-smol/commit/0b4a32f4260f0d8500942a133001b0d349328102), [`18a49e1`](https://github.com/Effect-TS/effect-smol/commit/18a49e1786679456258002ff9397faf02f678c2d), [`266cb90`](https://github.com/Effect-TS/effect-smol/commit/266cb90bb2c17aabc40563c32db334f09ba3d74b), [`912f095`](https://github.com/Effect-TS/effect-smol/commit/912f095a34572bbd3cedf6edb27878443e3e4a95), [`a6718f9`](https://github.com/Effect-TS/effect-smol/commit/a6718f9e00a15ca903b0732da46116cbf3d6aca7), [`bef5154`](https://github.com/Effect-TS/effect-smol/commit/bef51540a243aa2f872a00c01d0cd58b7a769baa), [`18e0564`](https://github.com/Effect-TS/effect-smol/commit/18e0564bd0f8ebbdfcaf1e2c21529948e9e4a81d), [`fb50f14`](https://github.com/Effect-TS/effect-smol/commit/fb50f14fc3657c1973785aa5b72ecf0b0d28e0b2)]: + - effect@4.0.0-beta.95 + +## 4.0.0-beta.94 + +### Patch Changes + +- Updated dependencies [[`95a0e9b`](https://github.com/Effect-TS/effect-smol/commit/95a0e9bb62797af0e81c9998773405f248f218c5), [`a0a3490`](https://github.com/Effect-TS/effect-smol/commit/a0a3490bbce765f199d8e077aceac504f0462e63), [`f11ce73`](https://github.com/Effect-TS/effect-smol/commit/f11ce73af60823754dc24194f4ffc561b9ea1c2d), [`ff30b6e`](https://github.com/Effect-TS/effect-smol/commit/ff30b6e7c2c63ffc56a4c5818d6d86b01b5ad528), [`1caab3c`](https://github.com/Effect-TS/effect-smol/commit/1caab3cc30f626efbf15e59d74f539a487e5c85c), [`aa80c47`](https://github.com/Effect-TS/effect-smol/commit/aa80c4775a04db87553e5568764cab7e32a72814), [`c2ae4fc`](https://github.com/Effect-TS/effect-smol/commit/c2ae4fce2f03a4cd1861c2b1179da7df656e662d), [`a0a3490`](https://github.com/Effect-TS/effect-smol/commit/a0a3490bbce765f199d8e077aceac504f0462e63)]: + - effect@4.0.0-beta.94 + +## 4.0.0-beta.93 + +### Patch Changes + +- Updated dependencies [[`00652fe`](https://github.com/Effect-TS/effect-smol/commit/00652fe95c18f87208e91343eb8bf218faa2f677), [`6c58167`](https://github.com/Effect-TS/effect-smol/commit/6c5816746eaf91d2a3c7c899c5720809fa230ae3), [`2bc5415`](https://github.com/Effect-TS/effect-smol/commit/2bc541501a7ef89e542d7cb98e96beb53cd205cc), [`e11cccc`](https://github.com/Effect-TS/effect-smol/commit/e11cccc7d5fe631abccc7d6e3bd296938de0fa2e), [`ba7e77e`](https://github.com/Effect-TS/effect-smol/commit/ba7e77e046b8641a3a4e9750bb88ca4a1d063d3f), [`5713ee7`](https://github.com/Effect-TS/effect-smol/commit/5713ee7edbc3054efde407b2286bbfd45bbc6e1c)]: + - effect@4.0.0-beta.93 + +## 4.0.0-beta.92 + +### Patch Changes + +- Updated dependencies [[`affdc13`](https://github.com/Effect-TS/effect-smol/commit/affdc139045cc325dce321a84a580fdc1b2da7b9)]: + - effect@4.0.0-beta.92 + +## 4.0.0-beta.91 + +### Patch Changes + +- Updated dependencies [[`b135b25`](https://github.com/Effect-TS/effect-smol/commit/b135b2517fca9e7839734ace3699a7dfa75b9075), [`aaa21a3`](https://github.com/Effect-TS/effect-smol/commit/aaa21a369a171c600db294f2a4f640583043e150), [`3475ee6`](https://github.com/Effect-TS/effect-smol/commit/3475ee6c2bda6b05c6d7a12ce30c8bb840b5b1a6)]: + - effect@4.0.0-beta.91 + ## 4.0.0-beta.90 ### Patch Changes diff --git a/repos/effect/packages/ai/openai-compat/docgen.json b/repos/effect/packages/ai/openai-compat/docgen.json deleted file mode 100644 index 2734de3582..0000000000 --- a/repos/effect/packages/ai/openai-compat/docgen.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "$schema": "../../node_modules/@effect/docgen/schema.json", - "srcLink": "https://github.com/Effect-TS/effect/tree/main/packages/ai/openai-compat/src/", - "exclude": ["src/internal/**"], - "tscExecutable": "tsgo", - "examplesCompilerOptions": { - "noEmit": true, - "strict": true, - "skipLibCheck": true, - "moduleResolution": "Bundler", - "module": "ES2022", - "target": "ES2022", - "lib": ["ES2022", "DOM", "DOM.Iterable"], - "rewriteRelativeImportExtensions": true, - "allowImportingTsExtensions": true, - "paths": { - "effect": ["../../../effect/src/index.js"], - "effect/*": ["../../../effect/src/*.js"] - }, - "plugins": [ - { "name": "@effect/language-service", "includeSuggestionsInTsc": false } - ] - } -} diff --git a/repos/effect/packages/ai/openai-compat/package.json b/repos/effect/packages/ai/openai-compat/package.json index a0b9f252d9..aba0fdcde4 100644 --- a/repos/effect/packages/ai/openai-compat/package.json +++ b/repos/effect/packages/ai/openai-compat/package.json @@ -1,17 +1,17 @@ { "name": "@effect/ai-openai-compat", - "version": "4.0.0-beta.90", + "version": "4.0.0-beta.107", "type": "module", "license": "MIT", "description": "An OpenAI compat integration for Effect", "homepage": "https://effect.website", "repository": { "type": "git", - "url": "https://github.com/Effect-TS/effect-smol.git", + "url": "https://github.com/Effect-TS/effect.git", "directory": "packages/ai/openai-compat" }, "bugs": { - "url": "https://github.com/Effect-TS/effect-smol/issues" + "url": "https://github.com/Effect-TS/effect/issues" }, "tags": [ "typescript", @@ -31,6 +31,7 @@ ".": "./src/index.ts", "./*": "./src/*.ts", "./internal/*": null, + "./index": null, "./*/index": null }, "files": [ @@ -38,7 +39,10 @@ "dist/**/*.js", "dist/**/*.js.map", "dist/**/*.d.ts", - "dist/**/*.d.ts.map" + "dist/**/*.d.ts.map", + "AGENTS.md", + "CLAUDE.md", + "ai-docs/**/*" ], "publishConfig": { "access": "public", @@ -48,17 +52,15 @@ ".": "./dist/index.js", "./*": "./dist/*.js", "./internal/*": null, + "./index": null, "./*/index": null } }, "scripts": { "codegen": "effect-utils codegen", "build": "tsc -b tsconfig.json && pnpm babel", - "build:tsgo": "tsgo -b tsconfig.json && pnpm babel", "babel": "babel dist --plugins annotate-pure-calls --out-dir dist --source-maps", - "check": "tsc -b tsconfig.json", - "test": "vitest", - "coverage": "vitest --coverage" + "check": "tsc -b tsconfig.json" }, "devDependencies": { "effect": "workspace:^" diff --git a/repos/effect/packages/ai/openai-compat/src/OpenAiClient.ts b/repos/effect/packages/ai/openai-compat/src/OpenAiClient.ts index c1fe575a9a..f8f7207ac6 100644 --- a/repos/effect/packages/ai/openai-compat/src/OpenAiClient.ts +++ b/repos/effect/packages/ai/openai-compat/src/OpenAiClient.ts @@ -11,7 +11,7 @@ import * as Array from "effect/Array" import type * as Config from "effect/Config" import * as Context from "effect/Context" import * as Effect from "effect/Effect" -import { identity, pipe } from "effect/Function" +import { identity } from "effect/Function" import * as Layer from "effect/Layer" import * as Redacted from "effect/Redacted" import * as Schema from "effect/Schema" @@ -34,7 +34,7 @@ import { OpenAiConfig } from "./OpenAiConfig.ts" * completions, streaming chat completions, and embeddings. Transport and * schema decoding failures are mapped to `AiError`. * - * @category models + * @category services * @since 4.0.0 */ export interface Service { @@ -99,10 +99,15 @@ export type Options = { } const RedactedOpenAiHeaders = { - OpenAiOrganization: "OpenAI-Organization", - OpenAiProject: "OpenAI-Project" + OpenAiOrganization: "openai-organization", + OpenAiProject: "openai-project" } +const withRedactedHeaders = Effect.updateService( + Headers.CurrentRedactedNames, + Array.appendAll(Object.values(RedactedOpenAiHeaders)) +) + /** * Constructs an OpenAI-compatible client service from explicit options. * @@ -175,24 +180,27 @@ export const make = Effect.fnUntraced( [body: CreateResponse200, response: HttpClientResponse.HttpClientResponse], AiError.AiError > => - Effect.flatMap(resolveHttpClient, (client) => - pipe( - HttpClientRequest.post("/chat/completions"), - HttpClientRequest.bodyJsonUnsafe(payload), - HttpClient.filterStatusOk(client).execute, - Effect.flatMap((response) => - Effect.map(decodeResponse(response), ( - body - ): [CreateResponse200, HttpClientResponse.HttpClientResponse] => [ - body, - response - ]) - ), - Effect.catchTags({ - HttpClientError: (error) => Errors.mapHttpClientError(error, "createResponse"), - SchemaError: (error) => Effect.fail(Errors.mapSchemaError(error, "createResponse")) - }) - )) + resolveHttpClient.pipe( + Effect.flatMap((client) => + HttpClientRequest.post("/chat/completions").pipe( + HttpClientRequest.bodyJsonUnsafe(payload), + HttpClient.filterStatusOk(client).execute, + Effect.flatMap((response) => + Effect.map(decodeResponse(response), ( + body + ): [CreateResponse200, HttpClientResponse.HttpClientResponse] => [ + body, + response + ]) + ), + Effect.catchTags({ + HttpClientError: (error) => Errors.mapHttpClientError(error, "createResponse"), + SchemaError: (error) => Effect.fail(Errors.mapSchemaError(error, "createResponse")) + }) + ) + ), + withRedactedHeaders + ) const buildResponseStream = ( response: HttpClientResponse.HttpClientResponse @@ -210,6 +218,7 @@ export const make = Effect.fnUntraced( Stream.takeUntil((event) => event === "[DONE]"), Stream.catchTags({ Retry: (error) => Stream.die(error), + SseError: (error) => Stream.fail(Errors.mapSseError(error, "createResponseStream")), HttpClientError: (error) => Stream.fromEffect(Errors.mapHttpClientError(error, "createResponseStream")) }) ) as any @@ -217,40 +226,46 @@ export const make = Effect.fnUntraced( } const createResponseStream: Service["createResponseStream"] = (payload) => - Effect.flatMap(resolveHttpClient, (client) => - pipe( - HttpClientRequest.post("/chat/completions"), - HttpClientRequest.bodyJsonUnsafe({ - ...payload, - stream: true, - stream_options: { - include_usage: true - } - }), - HttpClient.filterStatusOk(client).execute, - Effect.map(buildResponseStream), - Effect.catchTag( - "HttpClientError", - (error) => Errors.mapHttpClientError(error, "createResponseStream") + resolveHttpClient.pipe( + Effect.flatMap((client) => + HttpClientRequest.post("/chat/completions").pipe( + HttpClientRequest.bodyJsonUnsafe({ + ...payload, + stream: true, + stream_options: { + include_usage: true + } + }), + HttpClient.filterStatusOk(client).execute, + Effect.map(buildResponseStream), + Effect.catchTag( + "HttpClientError", + (error) => Errors.mapHttpClientError(error, "createResponseStream") + ) ) - )) + ), + withRedactedHeaders + ) const decodeEmbedding = HttpClientResponse.schemaBodyJson(CreateEmbeddingResponseSchema) const createEmbedding = ( payload: CreateEmbeddingRequestJson ): Effect.Effect => - Effect.flatMap(resolveHttpClient, (client) => - pipe( - HttpClientRequest.post("/embeddings"), - HttpClientRequest.bodyJsonUnsafe(payload), - HttpClient.filterStatusOk(client).execute, - Effect.flatMap(decodeEmbedding), - Effect.catchTags({ - HttpClientError: (error) => Errors.mapHttpClientError(error, "createEmbedding"), - SchemaError: (error) => Effect.fail(Errors.mapSchemaError(error, "createEmbedding")) - }) - )) + resolveHttpClient.pipe( + Effect.flatMap((client) => + HttpClientRequest.post("/embeddings").pipe( + HttpClientRequest.bodyJsonUnsafe(payload), + HttpClient.filterStatusOk(client).execute, + Effect.flatMap(decodeEmbedding), + Effect.catchTags({ + HttpClientError: (error) => Errors.mapHttpClientError(error, "createEmbedding"), + SchemaError: (error) => Effect.fail(Errors.mapSchemaError(error, "createEmbedding")) + }) + ) + ), + withRedactedHeaders + ) return OpenAiClient.of({ client: httpClient, @@ -259,10 +274,7 @@ export const make = Effect.fnUntraced( createEmbedding }) }, - Effect.updateService( - Headers.CurrentRedactedNames, - Array.appendAll(Object.values(RedactedOpenAiHeaders)) - ) + withRedactedHeaders ) /** @@ -340,7 +352,7 @@ type JsonObject = { readonly [x: string]: Schema.Json } /** * Optional response fields that can be requested with the `include` parameter. * - * @category response + * @category models * @since 4.0.0 */ export type IncludeEnum = @@ -379,7 +391,7 @@ type InputFileContent = { /** * Content blocks accepted in input messages. * - * @category request + * @category models * @since 4.0.0 */ export type InputContent = InputTextContent | InputImageContent | InputFileContent @@ -387,7 +399,7 @@ export type InputContent = InputTextContent | InputImageContent | InputFileConte /** * Text content block used for model-provided reasoning summaries. * - * @category response + * @category models * @since 4.0.0 */ export type SummaryTextContent = { @@ -449,7 +461,7 @@ type FilePathAnnotation = { /** * Citation and file-path annotations attached to output text content. * - * @category response + * @category models * @since 4.0.0 */ export type Annotation = @@ -488,7 +500,7 @@ type OutputMessage = { * Reasoning output item containing encrypted reasoning content, summaries, and * optional reasoning text. * - * @category response + * @category models * @since 4.0.0 */ export type ReasoningItem = { @@ -545,7 +557,7 @@ type ItemReference = { * Supports input messages, output messages, tool calls, tool outputs, reasoning * items, custom tool interactions, and item references. * - * @category request + * @category models * @since 4.0.0 */ export type InputItem = @@ -586,7 +598,7 @@ type CustomToolParam = { /** * Tool definitions that can be supplied to a Responses-style request. * - * @category request + * @category models * @since 4.0.0 */ export type Tool = @@ -637,7 +649,7 @@ export type TextResponseFormatConfiguration = * Request options for creating a Responses-style response with an * OpenAI-compatible provider. * - * @category request + * @category models * @since 4.0.0 */ export type CreateResponse = { @@ -677,7 +689,7 @@ export type CreateResponse = { /** * Token accounting reported on Responses-style response objects. * - * @category response + * @category models * @since 4.0.0 */ export type ResponseUsage = { @@ -698,7 +710,7 @@ type OutputItem = * Responses-style response object returned by compatible providers or embedded * in response stream lifecycle events. * - * @category response + * @category models * @since 4.0.0 */ export type Response = { @@ -855,7 +867,7 @@ export type ResponseStreamEvent = * string. The `index` field identifies the input item that produced this * embedding. * - * @category response + * @category models * @since 4.0.0 */ export type Embedding = { @@ -867,7 +879,7 @@ export type Embedding = { /** * Request payload for the embeddings endpoint. * - * @category request + * @category models * @since 4.0.0 */ export type CreateEmbeddingRequest = { @@ -881,7 +893,7 @@ export type CreateEmbeddingRequest = { /** * Successful response payload returned by the embeddings endpoint. * - * @category response + * @category models * @since 4.0.0 */ export type CreateEmbeddingResponse = { @@ -897,21 +909,21 @@ export type CreateEmbeddingResponse = { /** * JSON request body accepted by the embeddings endpoint. * - * @category request + * @category models * @since 4.0.0 */ export type CreateEmbeddingRequestJson = CreateEmbeddingRequest /** * Decoded successful embeddings response body. * - * @category response + * @category models * @since 4.0.0 */ export type CreateEmbedding200 = CreateEmbeddingResponse /** * Structured content parts accepted in chat completion messages. * - * @category request + * @category models * @since 4.0.0 */ export type ChatCompletionContentPart = @@ -929,7 +941,7 @@ export type ChatCompletionContentPart = /** * Tool call data attached to an assistant chat completion message. * - * @category request + * @category models * @since 4.0.0 */ export type ChatCompletionRequestToolCall = { @@ -943,7 +955,7 @@ export type ChatCompletionRequestToolCall = { /** * Message shapes accepted by the chat completions endpoint. * - * @category request + * @category models * @since 4.0.0 */ export type ChatCompletionRequestMessage = @@ -960,7 +972,7 @@ export type ChatCompletionRequestMessage = /** * Function tool definition accepted by the chat completions endpoint. * - * @category request + * @category models * @since 4.0.0 */ export type ChatCompletionTool = { @@ -1010,7 +1022,7 @@ export type ChatCompletionResponseFormat = /** * Request payload for the OpenAI-compatible chat completions endpoint. * - * @category request + * @category models * @since 4.0.0 */ export type ChatCompletionRequest = { @@ -1036,14 +1048,14 @@ export type ChatCompletionRequest = { /** * JSON request body used by this client when creating a chat completion response. * - * @category request + * @category models * @since 4.0.0 */ export type CreateResponseRequestJson = ChatCompletionRequest /** * Decoded successful chat completion response body returned by `createResponse`. * - * @category response + * @category models * @since 4.0.0 */ export type CreateResponse200 = ChatCompletionResponse @@ -1056,8 +1068,8 @@ export type CreateResponse200 = ChatCompletionResponse export type CreateResponse200Sse = ChatCompletionStreamEvent const EmbeddingSchema = Schema.Struct({ - embedding: Schema.Union([Schema.Array(Schema.Number), Schema.String]), - index: Schema.Number, + embedding: Schema.Union([Schema.Array(Schema.Finite), Schema.String]), + index: Schema.Int, object: Schema.optionalKey(Schema.String) }) @@ -1066,8 +1078,8 @@ const CreateEmbeddingResponseSchema = Schema.Struct({ model: Schema.String, object: Schema.optionalKey(Schema.Literal("list")), usage: Schema.optionalKey(Schema.Struct({ - prompt_tokens: Schema.Number, - total_tokens: Schema.Number + prompt_tokens: Schema.Int, + total_tokens: Schema.Int })) }) @@ -1086,14 +1098,14 @@ const ChatCompletionToolFunctionDelta = Schema.Struct({ const ChatCompletionToolCall = Schema.Struct({ id: Schema.optionalKey(Schema.String), - index: Schema.optionalKey(Schema.Number), + index: Schema.optionalKey(Schema.Int), type: Schema.optionalKey(Schema.String), function: Schema.optionalKey(ChatCompletionToolFunction) }) const ChatCompletionToolCallDelta = Schema.Struct({ id: Schema.optionalKey(Schema.String), - index: Schema.optionalKey(Schema.Number), + index: Schema.optionalKey(Schema.Int), type: Schema.optionalKey(Schema.String), function: Schema.optionalKey(ChatCompletionToolFunctionDelta) }) @@ -1115,16 +1127,16 @@ const ChatCompletionDelta = Schema.Struct({ }) const ChatCompletionChoice = Schema.Struct({ - index: Schema.Number, + index: Schema.Int, finish_reason: Schema.optionalKey(Schema.NullOr(Schema.String)), message: Schema.optionalKey(ChatCompletionMessage), delta: Schema.optionalKey(ChatCompletionDelta) }) const ChatCompletionUsage = Schema.Struct({ - prompt_tokens: Schema.Number, - completion_tokens: Schema.Number, - total_tokens: Schema.Number, + prompt_tokens: Schema.Int, + completion_tokens: Schema.Int, + total_tokens: Schema.Int, prompt_tokens_details: Schema.optionalKey(Schema.Any), completion_tokens_details: Schema.optionalKey(Schema.Any) }) @@ -1132,7 +1144,7 @@ const ChatCompletionUsage = Schema.Struct({ const ChatCompletionResponse = Schema.Struct({ id: Schema.String, model: Schema.String, - created: Schema.Number, + created: Schema.Int, choices: Schema.Array(ChatCompletionChoice), usage: Schema.optionalKey(Schema.NullOr(ChatCompletionUsage)), service_tier: Schema.optionalKey(Schema.String) @@ -1141,7 +1153,7 @@ const ChatCompletionResponse = Schema.Struct({ const ChatCompletionChunk = Schema.Struct({ id: Schema.String, model: Schema.String, - created: Schema.Number, + created: Schema.Int, choices: Schema.Array(ChatCompletionChoice), usage: Schema.optionalKey(Schema.NullOr(ChatCompletionUsage)), service_tier: Schema.optionalKey(Schema.String) @@ -1150,35 +1162,35 @@ const ChatCompletionChunk = Schema.Struct({ /** * Decoded tool-call object from a chat completion response or streaming chunk. * - * @category response + * @category models * @since 4.0.0 */ export type ChatCompletionToolCall = typeof ChatCompletionToolCall.Type /** * Decoded message object from a non-streaming chat completion choice. * - * @category response + * @category models * @since 4.0.0 */ export type ChatCompletionMessage = typeof ChatCompletionMessage.Type /** * Decoded choice object returned by chat completion responses and chunks. * - * @category response + * @category models * @since 4.0.0 */ export type ChatCompletionChoice = typeof ChatCompletionChoice.Type /** * Decoded token usage summary returned by chat completions. * - * @category response + * @category models * @since 4.0.0 */ export type ChatCompletionUsage = typeof ChatCompletionUsage.Type /** * Decoded successful response from the chat completions endpoint. * - * @category response + * @category models * @since 4.0.0 */ export type ChatCompletionResponse = typeof ChatCompletionResponse.Type @@ -1190,13 +1202,23 @@ export type ChatCompletionResponse = typeof ChatCompletionResponse.Type */ export type ChatCompletionChunk = typeof ChatCompletionChunk.Type /** - * Streaming chat completion event, including decoded chunks and the `[DONE]` - * sentinel. + * A parsed chat completion event that does not match the expected chunk schema. * * @category streaming * @since 4.0.0 */ -export type ChatCompletionStreamEvent = ChatCompletionChunk | "[DONE]" +export interface UnknownChatCompletionEvent { + readonly _tag: "UnknownChatCompletionEvent" + readonly data: unknown +} +/** + * Streaming chat completion event, including decoded chunks, unknown parsed + * events, and the `[DONE]` sentinel. + * + * @category streaming + * @since 4.0.0 + */ +export type ChatCompletionStreamEvent = ChatCompletionChunk | UnknownChatCompletionEvent | "[DONE]" const parseJson = (value: string): unknown => { try { @@ -1215,7 +1237,11 @@ const decodeChatCompletionSseData = ( return data } const parsed = parseJson(data) - return isChatCompletionChunk(parsed) - ? parsed - : undefined + if (parsed === undefined) { + return undefined + } + return isChatCompletionChunk(parsed) ? parsed : { + _tag: "UnknownChatCompletionEvent", + data: parsed + } } diff --git a/repos/effect/packages/ai/openai-compat/src/OpenAiConfig.ts b/repos/effect/packages/ai/openai-compat/src/OpenAiConfig.ts index 452144aee7..8291966403 100644 --- a/repos/effect/packages/ai/openai-compat/src/OpenAiConfig.ts +++ b/repos/effect/packages/ai/openai-compat/src/OpenAiConfig.ts @@ -50,7 +50,7 @@ export declare namespace OpenAiConfig { * Configuration consumed by OpenAI-compatible clients when they build or * resolve the underlying HTTP client. * - * @category models + * @category services * @since 4.0.0 */ export interface Service { diff --git a/repos/effect/packages/ai/openai-compat/src/OpenAiEmbeddingModel.ts b/repos/effect/packages/ai/openai-compat/src/OpenAiEmbeddingModel.ts index 1ae6c89f0f..bfe251c4f9 100644 --- a/repos/effect/packages/ai/openai-compat/src/OpenAiEmbeddingModel.ts +++ b/repos/effect/packages/ai/openai-compat/src/OpenAiEmbeddingModel.ts @@ -26,6 +26,9 @@ import { OpenAiClient } from "./OpenAiClient.ts" */ export type Model = string +type ConfigOptions = Simplify>> +type ModelConfig = Omit & { readonly [x: string]: unknown } + /** * Context service for OpenAI embedding model configuration. * @@ -43,22 +46,12 @@ export type Model = string * * @see {@link withConfigOverride} for scoping embedding request overrides * - * @category context + * @category services * @since 4.0.0 */ export class Config extends Context.Service< Config, - Simplify< - & Partial< - Omit< - CreateEmbeddingRequestJson, - "input" - > - > - & { - readonly [x: string]: unknown - } - > + ConfigOptions & { readonly [x: string]: unknown } >()("@effect/ai-openai-compat/OpenAiEmbeddingModel/Config") {} /** @@ -77,9 +70,9 @@ export class Config extends Context.Service< */ export const model = ( model: string, - options: { + options: Omit & { readonly dimensions: number - readonly config?: Omit + readonly [x: string]: unknown } ): AiModel.Model<"openai", EmbeddingModel.EmbeddingModel | EmbeddingModel.Dimensions, OpenAiClient> => AiModel.make( @@ -88,10 +81,7 @@ export const model = ( Layer.merge( layer({ model, - config: { - ...options.config, - dimensions: options.dimensions - } + config: options }), Layer.succeed(EmbeddingModel.Dimensions, options.dimensions) ) @@ -127,7 +117,7 @@ export const model = ( */ export const make = Effect.fnUntraced(function*({ model, config: providerConfig }: { readonly model: string - readonly config?: Omit | undefined + readonly config?: ModelConfig | undefined }): Effect.fn.Return { const client = yield* OpenAiClient @@ -162,7 +152,7 @@ export const make = Effect.fnUntraced(function*({ model, config: providerConfig */ export const layer = (options: { readonly model: string - readonly config?: Omit | undefined + readonly config?: ModelConfig | undefined }): Layer.Layer => Layer.effect(EmbeddingModel.EmbeddingModel, make(options)) diff --git a/repos/effect/packages/ai/openai-compat/src/OpenAiLanguageModel.ts b/repos/effect/packages/ai/openai-compat/src/OpenAiLanguageModel.ts index acce3af136..5418bd17f4 100644 --- a/repos/effect/packages/ai/openai-compat/src/OpenAiLanguageModel.ts +++ b/repos/effect/packages/ai/openai-compat/src/OpenAiLanguageModel.ts @@ -16,9 +16,11 @@ import { dual } from "effect/Function" import * as Layer from "effect/Layer" import * as Option from "effect/Option" import * as Predicate from "effect/Predicate" +import * as Rec from "effect/Record" import * as Redactable from "effect/Redactable" -import type * as Schema from "effect/Schema" +import * as Schema from "effect/Schema" import * as AST from "effect/SchemaAST" +import * as SchemaIssue from "effect/SchemaIssue" import * as Stream from "effect/Stream" import type { Span } from "effect/Tracer" import type { DeepMutable, Simplify } from "effect/Types" @@ -35,6 +37,7 @@ import * as InternalUtilities from "./internal/utilities.ts" import { type Annotation, type ChatCompletionContentPart, + type ChatCompletionRequestToolCall, type CreateResponse, type CreateResponse200, type CreateResponse200Sse, @@ -47,10 +50,13 @@ import { type ReasoningItem, type SummaryTextContent, type TextResponseFormatConfiguration, - type Tool as OpenAiClientTool + type Tool as OpenAiClientTool, + type UnknownChatCompletionEvent } from "./OpenAiClient.ts" import { addGenAIAnnotations } from "./OpenAiTelemetry.ts" +const formatIssue = SchemaIssue.makeFormatterDefault() + /** * Image detail level for vision requests. */ @@ -60,6 +66,43 @@ type ImageDetail = "auto" | "low" | "high" // Configuration // ============================================================================= +type ConfigOptions = Simplify< + & Partial< + Omit + > + & { + /** + * File ID prefixes used to identify file IDs in Responses API. + * When undefined, all file data is treated as base64 content. + * + * Examples: + * - OpenAI: ['file-'] for IDs like 'file-abc123' + * - Azure OpenAI: ['assistant-'] for IDs like 'assistant-abc123' + */ + readonly fileIdPrefixes?: ReadonlyArray | undefined + /** + * Configuration options for a text response from the model. + */ + readonly text?: { + /** + * Constrains the verbosity of the model's response. Lower values will + * result in more concise responses, while higher values will result in + * more verbose responses. + * + * Defaults to `"medium"`. + */ + readonly verbosity?: "low" | "medium" | "high" | undefined + } | undefined + /** + * Whether to use strict JSON schema validation. + * + * Defaults to `true`. + */ + readonly strictJsonSchema?: boolean | undefined + } +> +type ModelConfig = Omit & { readonly [x: string]: unknown } + /** * Context service for OpenAI language model configuration. * @@ -71,50 +114,12 @@ type ImageDetail = "auto" | "low" | "high" * * @see {@link withConfigOverride} for scoping language model request overrides * - * @category context + * @category services * @since 4.0.0 */ export class Config extends Context.Service< Config, - Simplify< - & Partial< - Omit< - CreateResponse, - "input" | "tools" | "tool_choice" | "stream" | "text" - > - > - & { - /** - * File ID prefixes used to identify file IDs in Responses API. - * When undefined, all file data is treated as base64 content. - * - * Examples: - * - OpenAI: ['file-'] for IDs like 'file-abc123' - * - Azure OpenAI: ['assistant-'] for IDs like 'assistant-abc123' - */ - readonly fileIdPrefixes?: ReadonlyArray | undefined - /** - * Configuration options for a text response from the model. - */ - readonly text?: { - /** - * Constrains the verbosity of the model's response. Lower values will - * result in more concise responses, while higher values will result in - * more verbose responses. - * - * Defaults to `"medium"`. - */ - readonly verbosity?: "low" | "medium" | "high" | undefined - } | undefined - /** - * Whether to use strict JSON schema validation. - * - * Defaults to `true`. - */ - readonly strictJsonSchema?: boolean | undefined - readonly [x: string]: unknown - } - > + ConfigOptions & { readonly [x: string]: unknown } >()("@effect/ai-openai-compat/OpenAiLanguageModel/Config") {} // ============================================================================= @@ -125,7 +130,7 @@ declare module "effect/unstable/ai/Prompt" { /** * OpenAI-compatible options for file prompt parts. * - * @category request + * @category models * @since 4.0.0 */ export interface FilePartOptions extends ProviderOptions { @@ -143,7 +148,7 @@ declare module "effect/unstable/ai/Prompt" { /** * OpenAI-compatible options for reasoning prompt parts. * - * @category request + * @category models * @since 4.0.0 */ export interface ReasoningPartOptions extends ProviderOptions { @@ -167,7 +172,7 @@ declare module "effect/unstable/ai/Prompt" { /** * OpenAI-compatible options for assistant tool-call prompt parts. * - * @category request + * @category models * @since 4.0.0 */ export interface ToolCallPartOptions extends ProviderOptions { @@ -189,7 +194,7 @@ declare module "effect/unstable/ai/Prompt" { /** * OpenAI-compatible options for tool-result prompt parts. * - * @category request + * @category models * @since 4.0.0 */ export interface ToolResultPartOptions extends ProviderOptions { @@ -211,7 +216,7 @@ declare module "effect/unstable/ai/Prompt" { /** * OpenAI-compatible options for text prompt parts. * - * @category request + * @category models * @since 4.0.0 */ export interface TextPartOptions extends ProviderOptions { @@ -239,7 +244,7 @@ declare module "effect/unstable/ai/Response" { /** * OpenAI-compatible metadata attached to a complete text response part. * - * @category response + * @category models * @since 4.0.0 */ export interface TextPartMetadata extends ProviderMetadata { @@ -271,7 +276,7 @@ declare module "effect/unstable/ai/Response" { /** * OpenAI-compatible metadata emitted when a streamed text part starts. * - * @category response + * @category models * @since 4.0.0 */ export interface TextStartPartMetadata extends ProviderMetadata { @@ -289,7 +294,7 @@ declare module "effect/unstable/ai/Response" { /** * OpenAI-compatible metadata emitted when a streamed text part ends. * - * @category response + * @category models * @since 4.0.0 */ export interface TextEndPartMetadata extends ProviderMetadata { @@ -311,7 +316,7 @@ declare module "effect/unstable/ai/Response" { /** * OpenAI-compatible metadata attached to a complete reasoning response part. * - * @category response + * @category models * @since 4.0.0 */ export interface ReasoningPartMetadata extends ProviderMetadata { @@ -333,7 +338,7 @@ declare module "effect/unstable/ai/Response" { /** * OpenAI-compatible metadata emitted when a streamed reasoning part starts. * - * @category response + * @category models * @since 4.0.0 */ export interface ReasoningStartPartMetadata extends ProviderMetadata { @@ -355,7 +360,7 @@ declare module "effect/unstable/ai/Response" { /** * OpenAI-compatible metadata emitted for a streamed reasoning delta. * - * @category response + * @category models * @since 4.0.0 */ export interface ReasoningDeltaPartMetadata extends ProviderMetadata { @@ -373,7 +378,7 @@ declare module "effect/unstable/ai/Response" { /** * OpenAI-compatible metadata emitted when a streamed reasoning part ends. * - * @category response + * @category models * @since 4.0.0 */ export interface ReasoningEndPartMetadata extends ProviderMetadata { @@ -395,7 +400,7 @@ declare module "effect/unstable/ai/Response" { /** * OpenAI-compatible metadata attached to tool-call response parts. * - * @category response + * @category models * @since 4.0.0 */ export interface ToolCallPartMetadata extends ProviderMetadata { @@ -413,7 +418,7 @@ declare module "effect/unstable/ai/Response" { /** * OpenAI-compatible metadata attached to document source citations. * - * @category response + * @category models * @since 4.0.0 */ export interface DocumentSourcePartMetadata extends ProviderMetadata { @@ -469,7 +474,7 @@ declare module "effect/unstable/ai/Response" { /** * OpenAI-compatible metadata attached to URL source citations. * - * @category response + * @category models * @since 4.0.0 */ export interface UrlSourcePartMetadata extends ProviderMetadata { @@ -495,7 +500,7 @@ declare module "effect/unstable/ai/Response" { /** * OpenAI-compatible metadata attached to finish response parts. * - * @category response + * @category models * @since 4.0.0 */ export interface FinishPartMetadata extends ProviderMetadata { @@ -531,7 +536,7 @@ declare module "effect/unstable/ai/Response" { */ export const model = ( model: string, - config?: Omit + config?: ModelConfig ): AiModel.Model<"openai", LanguageModel.LanguageModel, OpenAiClient> => AiModel.make("openai", model, layer({ model, config })) @@ -569,7 +574,7 @@ export const model = ( */ export const make = Effect.fnUntraced(function*({ model, config: providerConfig }: { readonly model: string - readonly config?: Omit | undefined + readonly config?: ModelConfig | undefined }): Effect.fn.Return { const client = yield* OpenAiClient @@ -629,6 +634,7 @@ export const make = Effect.fnUntraced(function*({ model, config: providerConfig const [rawResponse, response] = yield* client.createResponse(request) annotateResponse(options.span, rawResponse) return yield* makeResponse({ + options, rawResponse, response, toolNameMapper @@ -643,6 +649,7 @@ export const make = Effect.fnUntraced(function*({ model, config: providerConfig annotateRequest(options.span, request) const [response, stream] = yield* client.createResponseStream(request) return yield* makeStreamResponse({ + options, stream, response, toolNameMapper @@ -677,7 +684,7 @@ export const make = Effect.fnUntraced(function*({ model, config: providerConfig */ export const layer = (options: { readonly model: string - readonly config?: Omit | undefined + readonly config?: ModelConfig | undefined }): Layer.Layer => Layer.effect(LanguageModel.LanguageModel, make(options)) @@ -829,7 +836,7 @@ const prepareMessages = Effect.fnUntraced( } case "assistant": { - const reasoningMessages: Record> = {} + const reasoningMessages: Record> = Object.create(null) for (const part of message.content) { switch (part.type) { @@ -941,7 +948,6 @@ const prepareMessages = Effect.fnUntraced( type: "function_call", name: toolName, call_id: part.id, - // @effect-diagnostics-next-line preferSchemaOverJson:off arguments: JSON.stringify(part.params), ...(Predicate.isNotNull(id) ? { id } : {}), ...(Predicate.isNotNull(status) ? { status } : {}) @@ -987,7 +993,6 @@ const prepareMessages = Effect.fnUntraced( messages.push({ type: "function_call_output", call_id: part.id, - // @effect-diagnostics-next-line preferSchemaOverJson:off output: typeof part.result === "string" ? part.result : JSON.stringify(part.result), ...(Predicate.isNotNull(status) ? { status } : {}) }) @@ -1029,6 +1034,11 @@ const buildHttpResponseDetails = ( type ResponseStreamEvent = CreateResponse200Sse +const isUnknownChatCompletionEvent = ( + event: ResponseStreamEvent +): event is UnknownChatCompletionEvent => + typeof event !== "string" && "_tag" in event && event._tag === "UnknownChatCompletionEvent" + type ActiveToolCall = { readonly id: string name: string @@ -1037,10 +1047,12 @@ type ActiveToolCall = { const makeResponse = Effect.fnUntraced( function*>({ + options, rawResponse, response, toolNameMapper }: { + readonly options: LanguageModel.ProviderOptions readonly rawResponse: CreateResponse200 readonly response: HttpClientResponse.HttpClientResponse readonly toolNameMapper: Tool.NameMapper @@ -1079,9 +1091,9 @@ const makeResponse = Effect.fnUntraced( for (const [index, toolCall] of message.tool_calls.entries()) { const toolId = toolCall.id ?? `${rawResponse.id}_tool_${index}` const toolName = toolNameMapper.getCustomName(toolCall.function?.name ?? "unknown_tool") - const toolParams = toolCall.function?.arguments ?? "{}" - const params = yield* Effect.try({ - try: () => Tool.unsafeSecureJsonParse(toolParams), + const toolParamsJson = toolCall.function?.arguments ?? "{}" + const toolParams = yield* Effect.try({ + try: () => Tool.unsafeSecureJsonParse(toolParamsJson), catch: (cause) => AiError.make({ module: "OpenAiLanguageModel", @@ -1093,6 +1105,7 @@ const makeResponse = Effect.fnUntraced( }) }) }) + const params = yield* transformToolCallParams(options.tools, toolName, toolParams) hasToolCalls = true parts.push({ type: "tool-call", @@ -1125,10 +1138,12 @@ const makeResponse = Effect.fnUntraced( const makeStreamResponse = Effect.fnUntraced( function*>({ + options, stream, response, toolNameMapper }: { + readonly options: LanguageModel.ProviderOptions readonly stream: Stream.Stream readonly response: HttpClientResponse.HttpClientResponse readonly toolNameMapper: Tool.NameMapper @@ -1170,7 +1185,7 @@ const makeStreamResponse = Effect.fnUntraced( for (const toolCall of Object.values(activeToolCalls)) { const toolParams = toolCall.arguments.length > 0 ? toolCall.arguments : "{}" - const params = yield* Effect.try({ + const parsedParams = yield* Effect.try({ try: () => Tool.unsafeSecureJsonParse(toolParams), catch: (cause) => AiError.make({ @@ -1183,6 +1198,7 @@ const makeStreamResponse = Effect.fnUntraced( }) }) }) + const params = yield* transformToolCallParams(options.tools, toolCall.name, parsedParams) parts.push({ type: "tool-params-end", id: toolCall.id }) parts.push({ type: "tool-call", @@ -1207,6 +1223,12 @@ const makeStreamResponse = Effect.fnUntraced( return parts } + // Keep unknown events available to direct client consumers; this layer + // cannot translate provider-specific data into portable stream parts. + if (isUnknownChatCompletionEvent(event)) { + return parts + } + if (event.service_tier !== undefined) { serviceTier = event.service_tier } @@ -1394,6 +1416,12 @@ const unsupportedSchemaError = (error: unknown, method: string): AiError.AiError }) }) +const tryCodecTransform = (schema: S, method: string) => + Effect.try({ + try: () => toCodecOpenAI(schema), + catch: (error) => unsupportedSchemaError(error, method) + }) + const tryJsonSchema = (schema: S, method: string) => Effect.try({ try: () => Tool.getJsonSchemaFromSchema(schema, { transformer: toCodecOpenAI }), @@ -1406,6 +1434,42 @@ const tryToolJsonSchema = (tool: T, method: string) => catch: (error) => unsupportedSchemaError(error, method) }) +const transformToolCallParams = Effect.fnUntraced(function*>( + tools: Tools, + toolName: string, + toolParams: unknown +): Effect.fn.Return { + const tool = tools.find((tool) => tool.name === toolName) + + if (Predicate.isUndefined(tool)) { + return yield* AiError.make({ + module: "OpenAiLanguageModel", + method: "makeResponse", + reason: new AiError.ToolNotFoundError({ + toolName, + availableTools: tools.map((tool) => tool.name) + }) + }) + } + + const { codec } = yield* tryCodecTransform(tool.parametersSchema, "makeResponse") + 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, @@ -1544,7 +1608,7 @@ const extractCustomRequestProperties = (payload: CreateResponse): Record = {} for (const [key, value] of Object.entries(payload)) { if (!createResponseKnownProperties.has(key)) { - customProperties[key] = value + Rec.assignProperty(customProperties, key, value) } } return customProperties @@ -1656,7 +1720,24 @@ const toChatMessages = ( const messages: Array = [] for (const item of input) { - messages.push(...toChatMessagesFromItem(item)) + if (Predicate.hasProperty(item, "type") && item.type === "function_call") { + const previous = messages.at(-1) + const toolCall = toChatToolCall(item) + if (previous?.role === "assistant" && previous.tool_calls !== undefined) { + messages[messages.length - 1] = { + ...previous, + tool_calls: [...previous.tool_calls, toolCall] + } + } else { + messages.push({ + role: "assistant", + content: null, + tool_calls: [toolCall] + }) + } + } else { + messages.push(...toChatMessagesFromItem(item)) + } } return messages @@ -1684,14 +1765,7 @@ const toChatMessagesFromItem = ( return [{ role: "assistant", content: null, - tool_calls: [{ - id: item.call_id, - type: "function", - function: { - name: item.name, - arguments: item.arguments - } - }] + tool_calls: [toChatToolCall(item)] }] } @@ -1709,12 +1783,23 @@ const toChatMessagesFromItem = ( } } +const toChatToolCall = ( + item: Extract +): ChatCompletionRequestToolCall => ({ + id: item.call_id, + type: "function", + function: { + name: item.name, + arguments: item.arguments + } +}) + const toAssistantChatMessageContent = ( content: ReadonlyArray<{ readonly type: string readonly [x: string]: unknown }> -): string | null => { +): string => { let text = "" for (const part of content) { if (part.type === "output_text" && typeof part.text === "string") { @@ -1724,7 +1809,7 @@ const toAssistantChatMessageContent = ( text += part.refusal } } - return text.length > 0 ? text : null + return text } const toChatMessageContent = ( diff --git a/repos/effect/packages/ai/openai-compat/src/OpenAiTelemetry.ts b/repos/effect/packages/ai/openai-compat/src/OpenAiTelemetry.ts index 349a139a09..ab4d10147c 100644 --- a/repos/effect/packages/ai/openai-compat/src/OpenAiTelemetry.ts +++ b/repos/effect/packages/ai/openai-compat/src/OpenAiTelemetry.ts @@ -28,7 +28,7 @@ import * as Telemetry from "effect/unstable/ai/Telemetry" export type OpenAiTelemetryAttributes = Simplify< & Telemetry.GenAITelemetryAttributes & Telemetry.AttributesWithPrefix - & Telemetry.AttributesWithPrefix + & Telemetry.AttributesWithPrefix > /** diff --git a/repos/effect/packages/ai/openai-compat/src/internal/errors.ts b/repos/effect/packages/ai/openai-compat/src/internal/errors.ts index 2636d003a8..67e42868b8 100644 --- a/repos/effect/packages/ai/openai-compat/src/internal/errors.ts +++ b/repos/effect/packages/ai/openai-compat/src/internal/errors.ts @@ -10,6 +10,7 @@ import * as SchemaTransformation from "effect/SchemaTransformation" import * as String from "effect/String" import * as AiError from "effect/unstable/ai/AiError" import type * as Response from "effect/unstable/ai/Response" +import type * as Sse from "effect/unstable/encoding/Sse" import type * as HttpClientError from "effect/unstable/http/HttpClientError" import type * as HttpClientRequest from "effect/unstable/http/HttpClientRequest" import type * as HttpClientResponse from "effect/unstable/http/HttpClientResponse" @@ -22,7 +23,7 @@ export const OpenAiErrorBody = Schema.Struct({ type: Schema.optional(Schema.NullOr(Schema.String)), status: Schema.optional(Schema.NullOr(Schema.String)), param: Schema.optional(Schema.NullOr(Schema.String)), - code: Schema.optional(Schema.NullOr(Schema.Union([Schema.String, Schema.Number]))) + code: Schema.optional(Schema.NullOr(Schema.Union([Schema.String, Schema.Finite]))) }) }) const OpenAiErrorBodyJson = Schema.decodeUnknownOption(Schema.fromJsonString(Schema.Union([ @@ -49,6 +50,17 @@ export const mapSchemaError = dual< reason: AiError.InvalidOutputError.fromSchemaError(error) })) +/** @internal */ +export const mapSseError = dual< + (method: string) => (error: Sse.SseError) => AiError.AiError, + (error: Sse.SseError, method: string) => AiError.AiError +>(2, (error, method) => + AiError.make({ + module: "OpenAiClient", + method, + reason: new AiError.InvalidOutputError({ description: error.message }) + })) + /** @internal */ export const mapHttpClientError = dual< (method: string) => (error: HttpClientError.HttpClientError) => Effect.Effect, diff --git a/repos/effect/packages/ai/openai-compat/src/internal/utilities.ts b/repos/effect/packages/ai/openai-compat/src/internal/utilities.ts index a67365d981..75daf6b7d2 100644 --- a/repos/effect/packages/ai/openai-compat/src/internal/utilities.ts +++ b/repos/effect/packages/ai/openai-compat/src/internal/utilities.ts @@ -16,7 +16,7 @@ export const resolveFinishReason = ( if (finishReason == null) { return hasToolCalls ? "tool-calls" : "stop" } - const reason = finishReasonMap[finishReason] + const reason = Object.hasOwn(finishReasonMap, finishReason) ? finishReasonMap[finishReason] : undefined if (reason == null) { return hasToolCalls ? "tool-calls" : "unknown" } diff --git a/repos/effect/packages/ai/openai-compat/test/OpenAiClient.test.ts b/repos/effect/packages/ai/openai-compat/test/OpenAiClient.test.ts index 2d89ffb26b..423ce3b539 100644 --- a/repos/effect/packages/ai/openai-compat/test/OpenAiClient.test.ts +++ b/repos/effect/packages/ai/openai-compat/test/OpenAiClient.test.ts @@ -1,65 +1,42 @@ -import * as OpenAiClient from "@effect/ai-openai-compat/OpenAiClient" +import { OpenAiClient } from "@effect/ai-openai-compat" import { assert, describe, it } from "@effect/vitest" -import { Effect, Layer, Redacted, Stream } from "effect" -import { HttpClient, type HttpClientError, type HttpClientRequest, HttpClientResponse } from "effect/unstable/http" +import { Context, Effect, Layer, Redacted, type Schema, Stream } from "effect" +import { + Headers, + HttpClient, + type HttpClientError, + type HttpClientRequest, + HttpClientResponse +} from "effect/unstable/http" describe("OpenAiClient", () => { describe("request behavior", () => { it.effect("sets auth and OpenAI headers on /chat/completions requests", () => Effect.gen(function*() { - let capturedRequest: HttpClientRequest.HttpClientRequest | undefined - - const client = yield* OpenAiClient.make({ - apiKey: Redacted.make("sk-test-key"), - apiUrl: "https://compat.example.test/v1", - organizationId: Redacted.make("org_123"), - projectId: Redacted.make("proj_456") - }).pipe( - Effect.provide(Layer.succeed( - HttpClient.HttpClient, - makeHttpClient((request) => { - capturedRequest = request - return Effect.succeed(jsonResponse(request, 200, makeChatCompletion())) - }) - )) - ) + const client = yield* OpenAiClient.OpenAiClient yield* client.createResponse({ model: "gpt-4o-mini", messages: [{ role: "user", content: "hello" }] }) - assert.isDefined(capturedRequest) - if (capturedRequest === undefined) { - return - } + const requests = yield* MockHttpClient.requests + const request = requests[0] + const body = yield* getRequestBody(request) - assert.isTrue(capturedRequest.url.endsWith("/chat/completions")) - assert.isTrue(capturedRequest.url.startsWith("https://compat.example.test/v1")) - assert.strictEqual(capturedRequest.headers["authorization"], "Bearer sk-test-key") - assert.strictEqual(capturedRequest.headers["openai-organization"], "org_123") - assert.strictEqual(capturedRequest.headers["openai-project"], "proj_456") + assert.isTrue(request.url.endsWith("/chat/completions")) + assert.isTrue(request.url.startsWith("https://compat.example.test/v1")) + assert.strictEqual(request.headers["authorization"], "Bearer sk-test-key") + assert.strictEqual(request.headers["openai-organization"], "org_123") + assert.strictEqual(request.headers["openai-project"], "proj_456") - const body = yield* getRequestBody(capturedRequest) assert.strictEqual(body.messages[0]?.role, "user") assert.strictEqual(body.messages[0]?.content, "hello") - })) + }).pipe(Effect.provide(makeTestLayer()))) it.effect("passes custom chat-completions request properties through", () => Effect.gen(function*() { - let capturedRequest: HttpClientRequest.HttpClientRequest | undefined - - const client = yield* OpenAiClient.make({ - apiKey: Redacted.make("sk-test-key") - }).pipe( - Effect.provide(Layer.succeed( - HttpClient.HttpClient, - makeHttpClient((request) => { - capturedRequest = request - return Effect.succeed(jsonResponse(request, 200, makeChatCompletion())) - }) - )) - ) + const client = yield* OpenAiClient.OpenAiClient yield* client.createResponse({ model: "gpt-4o-mini", @@ -69,161 +46,188 @@ describe("OpenAiClient", () => { } }) - assert.isDefined(capturedRequest) - if (capturedRequest === undefined) { - return - } - - const body = yield* getRequestBody(capturedRequest) + const requests = yield* MockHttpClient.requests + const request = requests[0] + const body = yield* getRequestBody(request) assert.deepStrictEqual(body.provider_feature, { enabled: true }) - })) + }).pipe(Effect.provide(makeTestLayer()))) it.effect("uses /embeddings path and decodes permissive embedding payloads", () => Effect.gen(function*() { - let capturedRequest: HttpClientRequest.HttpClientRequest | undefined - - const client = yield* OpenAiClient.make({ - apiKey: Redacted.make("sk-test-key"), - apiUrl: "https://compat.example.test/v1" - }).pipe( - Effect.provide(Layer.succeed( - HttpClient.HttpClient, - makeHttpClient((request) => { - capturedRequest = request - return Effect.succeed(jsonResponse(request, 200, { - data: [{ - embedding: "YmFzZTY0LWRhdGE=", - index: 0, - object: "embedding", - vendor_payload: { future_field: true } - }], - model: "my-custom-embedding-model", - object: "list", - usage: { - prompt_tokens: 5, - total_tokens: 5 - }, - unknown_top_level: true - })) - }) - )) - ) + const client = yield* OpenAiClient.OpenAiClient const embedding = yield* client.createEmbedding({ model: "my-custom-embedding-model", input: "embed this" }) - assert.isDefined(capturedRequest) - if (capturedRequest === undefined) { - return - } + const requests = yield* MockHttpClient.requests + const request = requests[0] - assert.isTrue(capturedRequest.url.endsWith("/embeddings")) + assert.isTrue(request.url.endsWith("/embeddings")) assert.strictEqual(embedding.model, "my-custom-embedding-model") assert.strictEqual(embedding.data[0]?.index, 0) assert.strictEqual(typeof embedding.data[0]?.embedding, "string") - })) + }).pipe(Effect.provide(makeTestLayer({ + _tag: "Json", + body: { + data: [{ + embedding: "YmFzZTY0LWRhdGE=", + index: 0, + object: "embedding", + vendor_payload: { future_field: true } + }], + model: "my-custom-embedding-model", + object: "list", + usage: { + prompt_tokens: 5, + total_tokens: 5 + }, + unknown_top_level: true + } + })))) it.effect("sets stream=true for createResponseStream and returns chat chunks", () => Effect.gen(function*() { - let capturedRequest: HttpClientRequest.HttpClientRequest | undefined + const client = yield* OpenAiClient.OpenAiClient - const client = yield* OpenAiClient.make({ - apiKey: Redacted.make("sk-test-key") + const events = yield* client.createResponseStream({ + model: "gpt-4o-mini", + messages: [{ role: "user", content: "hello" }] }).pipe( - Effect.provide(Layer.succeed( - HttpClient.HttpClient, - makeHttpClient((request) => { - capturedRequest = request - return Effect.succeed(sseResponse(request, [ - { - id: "chatcmpl_test_1", - object: "chat.completion.chunk", - model: "gpt-4o-mini", - created: 1, - future_provider_field: { accepted: true }, - choices: [{ - index: 0, - delta: { content: "Hello" }, - finish_reason: null - }] - }, - { - id: "chatcmpl_test_1", - object: "chat.completion.chunk", - model: "gpt-4o-mini", - created: 1, - usage: { - prompt_tokens: 4, - completion_tokens: 2, - total_tokens: 6, - prompt_tokens_details: { cached_tokens: 1 }, - completion_tokens_details: { reasoning_tokens: 1 } - }, - choices: [{ - index: 0, - delta: {}, - finish_reason: "stop" - }] - }, - "[DONE]" - ])) - }) - )) + Effect.flatMap(([_, stream]) => Stream.runCollect(stream)) ) - const eventsChunk = yield* client.createResponseStream({ + const requests = yield* MockHttpClient.requests + const request = requests[0] + const body = yield* getRequestBody(request) + assert.strictEqual(body.stream, true) + assert.strictEqual(body.stream_options.include_usage, true) + assert.isTrue(request.url.endsWith("/chat/completions")) + + assert.propertyVal(events[0], "id", "chatcmpl_test_1") + assert.propertyVal(events[1], "id", "chatcmpl_test_1") + assert.strictEqual(events[2], "[DONE]") + }).pipe(Effect.provide(makeTestLayer({ + _tag: "Sse", + events: [ + { + id: "chatcmpl_test_1", + object: "chat.completion.chunk", + model: "gpt-4o-mini", + created: 1, + future_provider_field: { accepted: true }, + choices: [{ + index: 0, + delta: { content: "Hello" }, + finish_reason: null + }] + }, + { + id: "chatcmpl_test_1", + object: "chat.completion.chunk", + model: "gpt-4o-mini", + created: 1, + usage: { + prompt_tokens: 4, + completion_tokens: 2, + total_tokens: 6, + prompt_tokens_details: { cached_tokens: 1 }, + completion_tokens_details: { reasoning_tokens: 1 } + }, + choices: [{ + index: 0, + delta: {}, + finish_reason: "stop" + }] + }, + "[DONE]" + ] + })))) + + it.effect("surfaces schema-mismatched chat chunks and continues streaming", () => + Effect.gen(function*() { + const client = yield* OpenAiClient.OpenAiClient + + const events = yield* client.createResponseStream({ model: "gpt-4o-mini", messages: [{ role: "user", content: "hello" }] }).pipe( Effect.flatMap(([_, stream]) => Stream.runCollect(stream)) ) - assert.isDefined(capturedRequest) - if (capturedRequest === undefined) { - return - } - - const body = yield* getRequestBody(capturedRequest) - assert.strictEqual(body.stream, true) - assert.strictEqual(body.stream_options.include_usage, true) - assert.isTrue(capturedRequest.url.endsWith("/chat/completions")) - - const events = globalThis.Array.from(eventsChunk) - const firstEvent = events[0] - const secondEvent = events[1] - assert.isTrue(typeof firstEvent === "object") - assert.isTrue(typeof secondEvent === "object") - if ( - typeof firstEvent !== "object" || firstEvent === null || typeof secondEvent !== "object" || - secondEvent === null - ) { - return - } - assert.strictEqual(firstEvent.id, "chatcmpl_test_1") - assert.strictEqual(secondEvent.id, "chatcmpl_test_1") + assert.deepStrictEqual(events[0], { + _tag: "UnknownChatCompletionEvent", + data: { + type: "provider.chat.completion.delta", + provider_payload: { content: "provider-specific" } + } + }) + assert.propertyVal(events[1], "id", "chatcmpl_test_2") assert.strictEqual(events[2], "[DONE]") - })) + }).pipe(Effect.provide(makeTestLayer({ + _tag: "Sse", + events: [ + { + type: "provider.chat.completion.delta", + provider_payload: { content: "provider-specific" } + }, + { + id: "chatcmpl_test_2", + object: "chat.completion.chunk", + model: "gpt-4o-mini", + created: 1, + choices: [{ + index: 0, + delta: { content: "Hello" }, + finish_reason: null + }] + }, + "[DONE]" + ] + })))) - it.effect("passes chat-completions tool_choice payload through unchanged", () => + it.effect("drops invalid JSON and continues streaming", () => Effect.gen(function*() { - let capturedRequest: HttpClientRequest.HttpClientRequest | undefined + const client = yield* OpenAiClient.OpenAiClient - const client = yield* OpenAiClient.make({ - apiKey: Redacted.make("sk-test-key") + const events = yield* client.createResponseStream({ + model: "gpt-4o-mini", + messages: [{ role: "user", content: "hello" }] }).pipe( - Effect.provide(Layer.succeed( - HttpClient.HttpClient, - makeHttpClient((request) => { - capturedRequest = request - return Effect.succeed(jsonResponse(request, 200, makeChatCompletion())) - }) - )) + Effect.flatMap(([_, stream]) => Stream.runCollect(stream)) ) + assert.strictEqual(events.length, 2) + assert.propertyVal(events[0], "id", "chatcmpl_test_3") + assert.strictEqual(events[1], "[DONE]") + }).pipe(Effect.provide(makeTestLayer({ + _tag: "RawSse", + body: [ + "data: {invalid-json\n\n", + `data: ${ + JSON.stringify({ + id: "chatcmpl_test_3", + object: "chat.completion.chunk", + model: "gpt-4o-mini", + created: 1, + choices: [{ + index: 0, + delta: { content: "Hello" }, + finish_reason: null + }] + }) + }\n\n`, + "data: [DONE]\n\n" + ].join("") + })))) + + it.effect("passes chat-completions tool_choice payload through unchanged", () => + Effect.gen(function*() { + const client = yield* OpenAiClient.OpenAiClient + yield* client.createResponse({ model: "gpt-4o-mini", messages: [{ role: "user", content: "hello" }], @@ -249,30 +253,19 @@ describe("OpenAiClient", () => { }] }) - assert.isDefined(capturedRequest) - if (capturedRequest === undefined) { - return - } + const requests = yield* MockHttpClient.requests + const request = requests[0] + const body = yield* getRequestBody(request) - const body = yield* getRequestBody(capturedRequest) - assert.deepStrictEqual(body.tool_choice, { type: "function", function: { name: "TestTool" } }) - })) + assert.deepStrictEqual(body.tool_choice, { + type: "function", + function: { name: "TestTool" } + }) + }).pipe(Effect.provide(makeTestLayer()))) it.effect("accepts assistant tool-call and tool result chat history", () => Effect.gen(function*() { - let capturedRequest: HttpClientRequest.HttpClientRequest | undefined - - const client = yield* OpenAiClient.make({ - apiKey: Redacted.make("sk-test-key") - }).pipe( - Effect.provide(Layer.succeed( - HttpClient.HttpClient, - makeHttpClient((request) => { - capturedRequest = request - return Effect.succeed(jsonResponse(request, 200, makeChatCompletion())) - }) - )) - ) + const client = yield* OpenAiClient.OpenAiClient yield* client.createResponse({ model: "gpt-4o-mini", @@ -303,12 +296,10 @@ describe("OpenAiClient", () => { ] }) - assert.isDefined(capturedRequest) - if (capturedRequest === undefined) { - return - } + const requests = yield* MockHttpClient.requests + const request = requests[0] + const body = yield* getRequestBody(request) - const body = yield* getRequestBody(capturedRequest) const assistantMessages = body.messages.filter((message: any) => message.role === "assistant") const patchMessage = assistantMessages.find((message: any) => message.tool_calls?.[0]?.function?.name === "apply_patch" @@ -329,28 +320,41 @@ describe("OpenAiClient", () => { const patchOutput = toolMessages.find((message: any) => message.tool_call_id === "patch_call_1") assert.isDefined(patchOutput) assert.strictEqual(patchOutput.content, "deleted") - })) + }).pipe(Effect.provide(makeTestLayer()))) + + it.effect("redacts OpenAI-specific headers in AI error context", () => + Effect.gen(function*() { + const client = yield* OpenAiClient.OpenAiClient + + const result = yield* client.createResponse({ + model: "gpt-4o-mini", + messages: [{ role: "user", content: "hello" }] + }).pipe(Effect.flip) + + const headers = result.reason._tag === "InvalidRequestError" + ? result.reason.http?.request.headers ?? Headers.empty + : Headers.empty + + assert.strictEqual(String(headers["authorization"]), "") + assert.strictEqual(String(headers["openai-organization"]), "") + assert.strictEqual(String(headers["openai-project"]), "") + }).pipe(Effect.provide(makeTestLayer({ + _tag: "Json", + status: 400, + body: { + error: { + message: "Bad request", + type: "invalid_request_error", + code: null + } + } + })))) }) describe("error mapping", () => { it.effect("maps 400 responses to InvalidRequestError", () => Effect.gen(function*() { - const client = yield* OpenAiClient.make({ - apiKey: Redacted.make("sk-test-key") - }).pipe( - Effect.provide(Layer.succeed( - HttpClient.HttpClient, - makeHttpClient((request) => - Effect.succeed(jsonResponse(request, 400, { - error: { - message: "Bad request", - type: "invalid_request_error", - code: null - } - })) - ) - )) - ) + const client = yield* OpenAiClient.OpenAiClient const error = yield* client.createResponse({ model: "gpt-4o-mini", @@ -360,26 +364,21 @@ describe("OpenAiClient", () => { assert.strictEqual(error._tag, "AiError") assert.strictEqual(error.method, "createResponse") assert.strictEqual(error.reason._tag, "InvalidRequestError") - })) + }).pipe(Effect.provide(makeTestLayer({ + _tag: "Json", + status: 400, + body: { + error: { + message: "Bad request", + type: "invalid_request_error", + code: null + } + } + })))) it.effect("maps insufficient quota errors to QuotaExhaustedError", () => Effect.gen(function*() { - const client = yield* OpenAiClient.make({ - apiKey: Redacted.make("sk-test-key") - }).pipe( - Effect.provide(Layer.succeed( - HttpClient.HttpClient, - makeHttpClient((request) => - Effect.succeed(jsonResponse(request, 429, { - error: { - message: "You exceeded your current quota", - type: "insufficient_quota", - code: "insufficient_quota" - } - })) - ) - )) - ) + const client = yield* OpenAiClient.OpenAiClient const error = yield* client.createResponse({ model: "gpt-4o-mini", @@ -389,24 +388,93 @@ describe("OpenAiClient", () => { assert.strictEqual(error._tag, "AiError") assert.strictEqual(error.method, "createResponse") assert.strictEqual(error.reason._tag, "QuotaExhaustedError") - })) + }).pipe(Effect.provide(makeTestLayer({ + _tag: "Json", + status: 429, + body: { + error: { + message: "You exceeded your current quota", + type: "insufficient_quota", + code: "insufficient_quota" + } + } + })))) }) }) -const makeHttpClient = ( - handler: ( - request: HttpClientRequest.HttpClientRequest - ) => Effect.Effect -) => - HttpClient.makeWith( +type MockResponse = + | { + readonly _tag: "Json" + readonly body: Schema.Json + readonly status?: number | undefined + readonly headers?: Record | undefined + } + | { + readonly _tag: "Sse" + readonly events: ReadonlyArray + readonly status?: number | undefined + readonly headers?: Record | undefined + } + | { + readonly _tag: "RawSse" + readonly body: string + readonly status?: number | undefined + readonly headers?: Record | undefined + } + +class MockOpenAiResponse extends Context.Service()("MockOpenAiResponse") {} + +class MockHttpClient extends Context.Service> +}>()("MockHttpClient") { + static requests = MockHttpClient.use((client) => client.requests) +} + +const makeHttpClientContext = Effect.gen(function*() { + const capturedRequests: Array = [] + const mock = yield* MockOpenAiResponse + + const httpClient = HttpClient.makeWith( Effect.fnUntraced(function*(requestEffect) { const request = yield* requestEffect - return yield* handler(request) + capturedRequests.push(request) + return makeResponse(request, mock.response) }), Effect.succeed as HttpClient.HttpClient.Preprocess ) -const makeChatCompletion = () => ({ + const mockHttpClient: MockHttpClient["Service"] = { + requests: Effect.sync(() => capturedRequests) + } + + return Context.make(HttpClient.HttpClient, httpClient).pipe( + Context.add(MockHttpClient, mockHttpClient) + ) +}) + +const HttpClientLayer = Layer.effectContext(makeHttpClientContext) + +const makeTestLayer = (response: MockResponse = { + _tag: "Json", + body: makeCreateResponse() +}) => + OpenAiClient.layer({ + apiKey: Redacted.make("sk-test-key"), + apiUrl: "https://compat.example.test/v1", + organizationId: Redacted.make("org_123"), + projectId: Redacted.make("proj_456") + }).pipe( + Layer.provideMerge(HttpClientLayer), + Layer.provide(Layer.succeed(MockOpenAiResponse, { + response + })) + ) + +const makeCreateResponse = ( + overrides: Partial = {} +) => ({ id: "chatcmpl_test_1", object: "chat.completion", model: "gpt-4o-mini", @@ -423,37 +491,34 @@ const makeChatCompletion = () => ({ prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 - } + }, + ...overrides }) -const jsonResponse = ( +const makeResponse = ( request: HttpClientRequest.HttpClientRequest, - status: number, - body: unknown -): HttpClientResponse.HttpClientResponse => - HttpClientResponse.fromWeb( + response: MockResponse +): HttpClientResponse.HttpClientResponse => { + const contentType = response._tag === "Json" + ? "application/json" + : "text/event-stream" + const body = response._tag === "Json" + ? JSON.stringify(response.body) + : response._tag === "Sse" + ? toSseBody(response.events) + : response.body + + return HttpClientResponse.fromWeb( request, - new Response(JSON.stringify(body), { - status, + new Response(body, { + status: response.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" + "content-type": contentType, + ...response.headers } }) ) +} const getRequestBody = (request: HttpClientRequest.HttpClientRequest) => Effect.gen(function*() { @@ -465,10 +530,8 @@ const getRequestBody = (request: HttpClientRequest.HttpClientRequest) => return yield* Effect.die(new Error("Expected Uint8Array body")) }) -const toSseBody = (events: ReadonlyArray): string => +const toSseBody = (events: ReadonlyArray): string => events.map((event) => { - if (typeof event === "string") { - return `data: ${event}\n\n` - } - return `data: ${JSON.stringify(event)}\n\n` + const data = event === "[DONE]" ? event : JSON.stringify(event) + return `data: ${data}\n\n` }).join("") diff --git a/repos/effect/packages/ai/openai-compat/test/OpenAiLanguageModel.test.ts b/repos/effect/packages/ai/openai-compat/test/OpenAiLanguageModel.test.ts index 276c3387ae..e968ba2c2b 100644 --- a/repos/effect/packages/ai/openai-compat/test/OpenAiLanguageModel.test.ts +++ b/repos/effect/packages/ai/openai-compat/test/OpenAiLanguageModel.test.ts @@ -212,6 +212,58 @@ describe("OpenAiLanguageModel", () => { ]) })) + it.effect("normalizes empty assistant message content to an empty string", () => + Effect.gen(function*() { + let capturedRequest: HttpClientRequest.HttpClientRequest | undefined + + const layer = OpenAiClient.layer({ apiKey: Redacted.make("sk-test-key") }).pipe( + Layer.provide(Layer.succeed( + HttpClient.HttpClient, + makeHttpClient((request) => { + capturedRequest = request + return Effect.succeed(jsonResponse( + request, + makeChatCompletion({ + choices: [{ + index: 0, + finish_reason: "stop", + message: { + role: "assistant", + content: "done" + } + }] + }) + )) + }) + )) + ) + + yield* LanguageModel.generateText({ + prompt: Prompt.make([ + { role: "user", content: "hello" }, + { + role: "assistant", + content: [Prompt.textPart({ text: "" })] + }, + { role: "user", content: "continue" } + ]) + }).pipe( + Effect.provide(OpenAiLanguageModel.model("gpt-4o-mini")), + Effect.provide(layer) + ) + + assert.isDefined(capturedRequest) + if (capturedRequest === undefined) { + return + } + + const requestBody = yield* getRequestBody(capturedRequest) + const assistantMessage = requestBody.messages.find((message: any) => message.role === "assistant") + assert.isDefined(assistantMessage) + assert.strictEqual(assistantMessage.content, "") + assert.isUndefined(assistantMessage.tool_calls) + })) + it.effect("maps function_call output to tool-call part and sends function tool schema", () => Effect.gen(function*() { let capturedRequest: HttpClientRequest.HttpClientRequest | undefined @@ -277,6 +329,145 @@ describe("OpenAiLanguageModel", () => { assert.strictEqual(functionTool.function.strict, true) })) + it.effect("decodes tool call params with the OpenAI codec", () => + Effect.gen(function*() { + const layer = OpenAiClient.layer({ apiKey: Redacted.make("sk-test-key") }).pipe( + Layer.provide(Layer.succeed( + HttpClient.HttpClient, + makeHttpClient((request) => + Effect.succeed(jsonResponse( + request, + makeChatCompletion({ + choices: [{ + index: 0, + finish_reason: "tool_calls", + message: { + role: "assistant", + content: null, + tool_calls: [{ + id: "call_record_1", + type: "function", + function: { + name: "RecordTool", + arguments: JSON.stringify({ env: [{ 0: "PATH", 1: "/usr/bin" }] }) + } + }] + } + }] + }) + )) + ) + )) + ) + + const result = yield* LanguageModel.generateText({ + prompt: "read the environment", + toolkit: RecordToolkit, + disableToolCallResolution: true + }).pipe( + Effect.provide(OpenAiLanguageModel.model("gpt-4o-mini")), + Effect.provide(RecordToolkitLayer), + Effect.provide(layer) + ) + + const toolCall = result.content.find((part) => part.type === "tool-call") + assert.isDefined(toolCall) + if (toolCall?.type !== "tool-call") { + return + } + assert.deepStrictEqual(toolCall.params, { env: { PATH: "/usr/bin" } }) + })) + + it.effect("groups parallel tool calls into one assistant message", () => + Effect.gen(function*() { + let capturedRequest: HttpClientRequest.HttpClientRequest | undefined + + const layer = OpenAiClient.layer({ apiKey: Redacted.make("sk-test-key") }).pipe( + Layer.provide(Layer.succeed( + HttpClient.HttpClient, + makeHttpClient((request) => { + capturedRequest = request + return Effect.succeed(jsonResponse(request, makeChatCompletion())) + }) + )) + ) + + yield* LanguageModel.generateText({ + prompt: Prompt.make([ + { role: "user", content: "use both tools" }, + { + role: "assistant", + content: [ + Prompt.toolCallPart({ + id: "call_1", + name: "TestTool", + params: { input: "first" }, + providerExecuted: false + }), + Prompt.toolCallPart({ + id: "call_2", + name: "TestTool", + params: { input: "second" }, + providerExecuted: false + }) + ] + }, + { + role: "tool", + content: [ + Prompt.toolResultPart({ + id: "call_1", + name: "TestTool", + isFailure: false, + result: { output: "first" }, + providerExecuted: false + }), + Prompt.toolResultPart({ + id: "call_2", + name: "TestTool", + isFailure: false, + result: { output: "second" }, + providerExecuted: false + }) + ] + } + ]), + toolkit: TestToolkit + }).pipe( + Effect.provide(OpenAiLanguageModel.model("gpt-4o-mini")), + Effect.provide(TestToolkitLayer), + Effect.provide(layer) + ) + + assert.isDefined(capturedRequest) + if (capturedRequest === undefined) { + return + } + + const requestBody = yield* getRequestBody(capturedRequest) + assert.deepStrictEqual(requestBody.messages, [ + { role: "user", content: "use both tools" }, + { + role: "assistant", + content: null, + tool_calls: [ + { + id: "call_1", + type: "function", + function: { name: "TestTool", arguments: JSON.stringify({ input: "first" }) } + }, + { + id: "call_2", + type: "function", + function: { name: "TestTool", arguments: JSON.stringify({ input: "second" }) } + } + ] + }, + { role: "tool", tool_call_id: "call_1", content: JSON.stringify({ output: "first" }) }, + { role: "tool", tool_call_id: "call_2", content: JSON.stringify({ output: "second" }) } + ]) + })) + it.effect("converts dynamic tools to function type", () => Effect.gen(function*() { let capturedRequest: HttpClientRequest.HttpClientRequest | undefined @@ -710,6 +901,59 @@ describe("OpenAiLanguageModel", () => { } })) + it.effect("decodes streamed tool call params with the OpenAI codec", () => + Effect.gen(function*() { + const layer = OpenAiClient.layer({ apiKey: Redacted.make("sk-test-key") }).pipe( + Layer.provide(Layer.succeed( + HttpClient.HttpClient, + makeHttpClient((request) => + Effect.succeed(sseResponse(request, [ + { + id: "chatcmpl_record_1", + object: "chat.completion.chunk", + model: "gpt-4o-mini", + created: 1, + choices: [{ + index: 0, + delta: { + tool_calls: [{ + index: 0, + id: "call_record_1", + type: "function", + function: { + name: "RecordTool", + arguments: JSON.stringify({ env: [{ 0: "PATH", 1: "/usr/bin" }] }) + } + }] + }, + finish_reason: "tool_calls" + }] + }, + "[DONE]" + ])) + ) + )) + ) + + const partsChunk = yield* LanguageModel.streamText({ + prompt: "read the environment", + toolkit: RecordToolkit, + disableToolCallResolution: true + }).pipe( + Stream.runCollect, + Effect.provide(OpenAiLanguageModel.model("gpt-4o-mini")), + Effect.provide(RecordToolkitLayer), + Effect.provide(layer) + ) + + const toolCall = globalThis.Array.from(partsChunk).find((part) => part.type === "tool-call") + assert.isDefined(toolCall) + if (toolCall?.type !== "tool-call") { + return + } + assert.deepStrictEqual(toolCall.params, { env: { PATH: "/usr/bin" } }) + })) + it.effect("maps local shell stream tool calls to local_shell call outputs", () => Effect.gen(function*() { const capturedRequests = yield* Ref.make>([]) @@ -782,7 +1026,8 @@ describe("OpenAiLanguageModel", () => { id: toolCall.id, name: toolCall.name, isFailure: false, - result: "done" + result: "done", + providerExecuted: false })] } ]), @@ -806,6 +1051,7 @@ describe("OpenAiLanguageModel", () => { item.role === "assistant" && item.tool_calls?.[0]?.function?.name === "local_shell" ) assert.isDefined(localShellCall) + assert.strictEqual(localShellCall.content, null) assert.strictEqual(localShellCall.tool_calls[0].id, toolCall.id) const localShellOutput = followUpBody.messages.find((item: any) => item.role === "tool") @@ -1011,11 +1257,12 @@ describe("OpenAiLanguageModel", () => { assert.deepStrictEqual(toolCall.params, expectedParams) })) - it.effect("streams known events and ignores unknown ones", () => + it.effect("continues after invalid JSON and schema-mismatched events", () => Effect.gen(function*() { let capturedRequest: HttpClientRequest.HttpClientRequest | undefined const events = [ + "{invalid-json", { id: "chatcmpl_stream_1", object: "chat.completion.chunk", @@ -1027,6 +1274,10 @@ describe("OpenAiLanguageModel", () => { finish_reason: null }] }, + { + type: "provider.chat.completion.delta", + provider_payload: { content: "provider-specific" } + }, { id: "chatcmpl_stream_1", object: "chat.completion.chunk", @@ -1358,6 +1609,19 @@ const TestToolkitLayer = TestToolkit.toLayer({ TestTool: ({ input }) => Effect.succeed({ output: input }) }) +const RecordTool = Tool.make("RecordTool", { + parameters: Schema.Struct({ + env: Schema.Record(Schema.String, Schema.String) + }), + success: Schema.String +}) + +const RecordToolkit = Toolkit.make(RecordTool) + +const RecordToolkitLayer = RecordToolkit.toLayer({ + RecordTool: () => Effect.succeed("done") +}) + const CompatApplyPatchTool = Tool.providerDefined({ id: "compat.apply_patch", customName: "CompatApplyPatch", diff --git a/repos/effect/packages/ai/openai-compat/tsconfig.json b/repos/effect/packages/ai/openai-compat/tsconfig.json index 19a2f5dbca..e2a8ca19a0 100644 --- a/repos/effect/packages/ai/openai-compat/tsconfig.json +++ b/repos/effect/packages/ai/openai-compat/tsconfig.json @@ -1,5 +1,5 @@ { - "$schema": "http://json.schemastore.org/tsconfig", + "$schema": "https://json.schemastore.org/tsconfig", "extends": "../../../tsconfig.base.json", "include": ["src"], "references": [ diff --git a/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 0000000000..b87ba69e80 --- /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-compat/vitest.config.ts b/repos/effect/packages/ai/openai-compat/vitest.config.ts deleted file mode 100644 index c8a52c1826..0000000000 --- a/repos/effect/packages/ai/openai-compat/vitest.config.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { mergeConfig, type ViteUserConfig } from "vitest/config" -import shared from "../../../vitest.shared.ts" - -const config: ViteUserConfig = {} - -export default mergeConfig(shared, config) diff --git a/repos/effect/packages/ai/openai/CHANGELOG.md b/repos/effect/packages/ai/openai/CHANGELOG.md index 63097db569..8ef718f408 100644 --- a/repos/effect/packages/ai/openai/CHANGELOG.md +++ b/repos/effect/packages/ai/openai/CHANGELOG.md @@ -1,5 +1,150 @@ # @effect/ai-openai +## 4.0.0-beta.107 + +### Patch Changes + +- Updated dependencies [[`596f3f9`](https://github.com/Effect-TS/effect/commit/596f3f92d7fe355811b815cb212332b082268ce8), [`9611ed4`](https://github.com/Effect-TS/effect/commit/9611ed42d11300546b339ab13492a0f7bdb1ebfb), [`8b91605`](https://github.com/Effect-TS/effect/commit/8b9160548556e4b0ec7ee2f2707716776be49018), [`d901928`](https://github.com/Effect-TS/effect/commit/d901928efa44f573ed1247f53fdb203a8e4fcede), [`b32bdef`](https://github.com/Effect-TS/effect/commit/b32bdef0d119a1ad1463dc01a46763ffee1f9bd9)]: + - effect@4.0.0-beta.107 + +## 4.0.0-beta.106 + +### Patch Changes + +- [#7126](https://github.com/Effect-TS/effect/pull/7126) [`16b94c7`](https://github.com/Effect-TS/effect/commit/16b94c702419c318e0f3515c902c39cf3871ccce) Thanks @fubhy! - Fix OpenAI response telemetry attribute types to use the emitted response namespace. +- Updated dependencies [[`2695168`](https://github.com/Effect-TS/effect/commit/269516851b24916d72771f8a554b88722e3732e7), [`6310a8c`](https://github.com/Effect-TS/effect/commit/6310a8c68c74dcf1d23948ec9243ac5f407a1651), [`c2071b1`](https://github.com/Effect-TS/effect/commit/c2071b1647e2326568c1d0689274ef62b8a7183f), [`7aff81a`](https://github.com/Effect-TS/effect/commit/7aff81a9cefe681483ef8abf717d786fd10e7e8d), [`a1d4057`](https://github.com/Effect-TS/effect/commit/a1d4057711935a544ef441bc2d0ac3565dfa9266), [`abf77b0`](https://github.com/Effect-TS/effect/commit/abf77b04009dcb4d67a258f9d8ada778e9f4ffae), [`6c60375`](https://github.com/Effect-TS/effect/commit/6c60375e68683a32d54554150cc493e16550a06d), [`22f4897`](https://github.com/Effect-TS/effect/commit/22f4897bbae24783d4516f6bef353f1db4ec6d03), [`615d1d5`](https://github.com/Effect-TS/effect/commit/615d1d5d0256ec8160f2e08d0dcf5dc83acb7bf1), [`3a86757`](https://github.com/Effect-TS/effect/commit/3a867573ddeed5888dabdeb3225a9ebbf00491e7), [`f4a9762`](https://github.com/Effect-TS/effect/commit/f4a9762bb9dfad59c215f2e099dcc829d74f4ed1), [`0bcf6ed`](https://github.com/Effect-TS/effect/commit/0bcf6ed57c22e8a36964726b15464101d90f5997), [`ba9cb63`](https://github.com/Effect-TS/effect/commit/ba9cb63b87d45ce2df872dd8ef0905da147cc675), [`42c810d`](https://github.com/Effect-TS/effect/commit/42c810dd372275b822dd99c7d7e774e153f0a752), [`1416ccd`](https://github.com/Effect-TS/effect/commit/1416ccd474bc9da8979f51b72b5e53fb3ac56edf), [`08d0d39`](https://github.com/Effect-TS/effect/commit/08d0d39a225deccb9db213ab5fcf55edb9f9ba5d), [`548908a`](https://github.com/Effect-TS/effect/commit/548908a71d9337cb7defe7fc93b2fba8f6a04b6f), [`4b3460d`](https://github.com/Effect-TS/effect/commit/4b3460daa434ec465a95a50704fe1103a9275999), [`d170596`](https://github.com/Effect-TS/effect/commit/d17059615cca37ca2776654078fe0501ac5202e6), [`aea89d0`](https://github.com/Effect-TS/effect/commit/aea89d0c42ee0ac707a4962cd348fd3158cb469b), [`deed5fb`](https://github.com/Effect-TS/effect/commit/deed5fbdc91cf8bf8c5fce7dfa5d6527ac944726)]: + - effect@4.0.0-beta.106 + +## 4.0.0-beta.105 + +### Patch Changes + +- Updated dependencies [[`0418564`](https://github.com/Effect-TS/effect/commit/04185644dabb8e4169f1ef6cbbc0b36c4db2f7f4), [`d334a85`](https://github.com/Effect-TS/effect/commit/d334a8593aafcd60753995a9449b654c67bfdcc1), [`f0be855`](https://github.com/Effect-TS/effect/commit/f0be8554da6ee00293a6b23869ac46a0b0d97dc8), [`b206fa5`](https://github.com/Effect-TS/effect/commit/b206fa5d7655c1634c9993410a9203f6616a5ca2), [`b938c8a`](https://github.com/Effect-TS/effect/commit/b938c8ad2823bd88493187922f7d9090eff037b6), [`8525f05`](https://github.com/Effect-TS/effect/commit/8525f05d1e14ea12298e9e1a0df497bfaac2ce9a)]: + - effect@4.0.0-beta.105 + +## 4.0.0-beta.104 + +### Patch Changes + +- [#7043](https://github.com/Effect-TS/effect/pull/7043) [`dce8219`](https://github.com/Effect-TS/effect/commit/dce8219d4041e1b188a4710550caf20f0c452f09) Thanks @fubhy! - Preserve OpenAI provider errors from failed response stream events. +- Updated dependencies [[`1001bcc`](https://github.com/Effect-TS/effect/commit/1001bccb9e874918d59dbb36860f1c5d4499ac20), [`993ba60`](https://github.com/Effect-TS/effect/commit/993ba60ee6c7ca6eb84522040f8b0d268b6ba7d4), [`67faacd`](https://github.com/Effect-TS/effect/commit/67faacd4679242559bee31717c05a5b10b990322), [`b78acdf`](https://github.com/Effect-TS/effect/commit/b78acdf422568f10ae8684fd3f10d52b065f0b56), [`fbb9ce5`](https://github.com/Effect-TS/effect/commit/fbb9ce5e625d1a7d6b7005bda42cbb6cd31476c5), [`722ea48`](https://github.com/Effect-TS/effect/commit/722ea484c9d01364c9242d929c0a564f7831a57c), [`3058fd5`](https://github.com/Effect-TS/effect/commit/3058fd594f5a683034212d71d957017fcc084006), [`62d0575`](https://github.com/Effect-TS/effect/commit/62d057566c241405c23ecf0bf4156186bd2be924), [`99dd6b5`](https://github.com/Effect-TS/effect/commit/99dd6b580434f97c5b40adc919f429e4abc3dfe7), [`7963ce1`](https://github.com/Effect-TS/effect/commit/7963ce1cd95f037fbefea67a29ead49cce4d16cb), [`af14e75`](https://github.com/Effect-TS/effect/commit/af14e752edd65e2b652e960411afafc88975a8d8), [`24e22d2`](https://github.com/Effect-TS/effect/commit/24e22d23a73a2e93ebf6d8edd2246a4a406942c8), [`647d14e`](https://github.com/Effect-TS/effect/commit/647d14e572c8004fa92fba256e00552b42bf34b7), [`1434eec`](https://github.com/Effect-TS/effect/commit/1434eecbd368e00839c24b3950f0b7a69218669a), [`a5278b1`](https://github.com/Effect-TS/effect/commit/a5278b18242011d1b2b08304c7c128151f9a4370), [`6af04a5`](https://github.com/Effect-TS/effect/commit/6af04a50bd019238f6acdb9cbda40439a3c09210), [`cb6c837`](https://github.com/Effect-TS/effect/commit/cb6c8376b2f322d4e7cbfc0973fc3b4f2951ee6e), [`d44cead`](https://github.com/Effect-TS/effect/commit/d44cead7e0e0ce61f0d980906e494f49a07e7899), [`88c7632`](https://github.com/Effect-TS/effect/commit/88c7632c2b59a49fcc40d250865bd8d0dccf31b0), [`abcbb2a`](https://github.com/Effect-TS/effect/commit/abcbb2abe16f1b6c587c15007df14371e1e70e93), [`8f63cce`](https://github.com/Effect-TS/effect/commit/8f63cce636700fde26b140b82e350ef916989d86), [`d56dfcf`](https://github.com/Effect-TS/effect/commit/d56dfcf54c2b9c53c3d098ce4b0ffcc84496c5f7), [`a98cda9`](https://github.com/Effect-TS/effect/commit/a98cda9422e1352f22e81696f759f326ffcfb667), [`6704bb8`](https://github.com/Effect-TS/effect/commit/6704bb84c320547f83cf50e8586ffc4c5e4c3cc5), [`6143de2`](https://github.com/Effect-TS/effect/commit/6143de21ee22038b45a8d4eba86f5aade6238eba), [`936b135`](https://github.com/Effect-TS/effect/commit/936b1358396eb0a1a7c8e0878ba63297e2106812), [`1bbae84`](https://github.com/Effect-TS/effect/commit/1bbae84f88b577a26d04ceb2e76d3143d09c4a20), [`d795ee7`](https://github.com/Effect-TS/effect/commit/d795ee771701ea62bd187ef7c0307d9737f68c1a), [`0a82d88`](https://github.com/Effect-TS/effect/commit/0a82d88b7da73278b6f270118e396d5ed4a64747), [`9215bc5`](https://github.com/Effect-TS/effect/commit/9215bc5da7dd10aa45f07fe44b98f06b6e433d62), [`a1b5df2`](https://github.com/Effect-TS/effect/commit/a1b5df2064d92431cfc6e638af613cc3114313d7), [`92a9ac5`](https://github.com/Effect-TS/effect/commit/92a9ac5ac0aa63d8975b9ba7a094d6a8f59a98f2), [`6bde7f2`](https://github.com/Effect-TS/effect/commit/6bde7f27f3243427203e53fe74472990e5c2a349), [`a712131`](https://github.com/Effect-TS/effect/commit/a7121310dbb60cbd819bbd702f97663098ec7bb8), [`2e6f760`](https://github.com/Effect-TS/effect/commit/2e6f760dcb44e2b984f3311a8af03a1d68a2ec7e), [`aa05804`](https://github.com/Effect-TS/effect/commit/aa0580497e027ed30b756058db0067c3fe07664f), [`badd3bf`](https://github.com/Effect-TS/effect/commit/badd3bf65fac4dd1e66e1f602db43659722dfced), [`02b0265`](https://github.com/Effect-TS/effect/commit/02b02651ede46a5a2dd3ef8081d0ad89648d0cbf), [`3437e21`](https://github.com/Effect-TS/effect/commit/3437e21a56d805781c5e5946a6189795a1dfd411), [`41a550d`](https://github.com/Effect-TS/effect/commit/41a550d1fed31e829929a8f5362b5340303164ac), [`17b5d50`](https://github.com/Effect-TS/effect/commit/17b5d50219ad49533cf9e33d01924a3e16af5eb3), [`96e5e95`](https://github.com/Effect-TS/effect/commit/96e5e9576b0315c747462761a61940ff9fe32dd1), [`e4d589e`](https://github.com/Effect-TS/effect/commit/e4d589e0ea08dc57c4793053b395dc0fcc499f34), [`ae4cf7b`](https://github.com/Effect-TS/effect/commit/ae4cf7b5e2cb5f8c55657e31a61789ad21c38c18), [`6ef5f1a`](https://github.com/Effect-TS/effect/commit/6ef5f1a041f3a40bf03fadd0b1feb275c277c635), [`2235a29`](https://github.com/Effect-TS/effect/commit/2235a29502c3f33cf6468511ad931089013a7916), [`b32f4cb`](https://github.com/Effect-TS/effect/commit/b32f4cb7b2d8ebe817075322622498e3beb05336), [`7f4c095`](https://github.com/Effect-TS/effect/commit/7f4c095b62da43780dd7fc2a5d1785ddfce60edf), [`5f3fb81`](https://github.com/Effect-TS/effect/commit/5f3fb814d18d8a54946c1c1cd0b41459cdb24006), [`17f0b91`](https://github.com/Effect-TS/effect/commit/17f0b91a243ccfe4a38d27debdc983adf434e738), [`0cdadd7`](https://github.com/Effect-TS/effect/commit/0cdadd75bc8abbbcad7956a4bc71f4e7a9b13250), [`39b57d7`](https://github.com/Effect-TS/effect/commit/39b57d7857358040558b67dd33eafc7bb5457830), [`5a6a573`](https://github.com/Effect-TS/effect/commit/5a6a5738e5bfc39e3a37ae7ba99081601fa19ac3), [`59f5e99`](https://github.com/Effect-TS/effect/commit/59f5e9981913b92d7a9beb2214a21d658b999d3a), [`45379d6`](https://github.com/Effect-TS/effect/commit/45379d6179ee4df2cbd3f848bd39ff7149c24a38), [`1949439`](https://github.com/Effect-TS/effect/commit/1949439175809ef81ab9c6411ed5559109edb4c9), [`e443403`](https://github.com/Effect-TS/effect/commit/e443403cf0e4effea14bb6cd950c5ac1c86cc748), [`03af7e8`](https://github.com/Effect-TS/effect/commit/03af7e85551204c605ea2fa2c43c10a4538ac8fb), [`0f721d4`](https://github.com/Effect-TS/effect/commit/0f721d406df8703ea92ca28777b3f09599e2056d), [`130b28d`](https://github.com/Effect-TS/effect/commit/130b28df552d7053407b041a96ff09dae82575e5), [`c987a12`](https://github.com/Effect-TS/effect/commit/c987a12a01b6a52ad53d29edf02613b03574dbcc), [`4158562`](https://github.com/Effect-TS/effect/commit/41585620977de9b84171f76619b72e29cc2284e5), [`306014a`](https://github.com/Effect-TS/effect/commit/306014a1ce4d5cb956c76bdc20e4e28ab3e61a6a), [`729a663`](https://github.com/Effect-TS/effect/commit/729a663275dd31f2357c446fe69664429220a83d), [`caf84b6`](https://github.com/Effect-TS/effect/commit/caf84b660044089e8d7f4067b279b27b8b50e8fd), [`ce067f7`](https://github.com/Effect-TS/effect/commit/ce067f799ea27735d4194345298a216aaf429f01), [`7a41f5a`](https://github.com/Effect-TS/effect/commit/7a41f5aa72d540ecf2746992ecc3fa3e6b40d31f), [`781022a`](https://github.com/Effect-TS/effect/commit/781022acdd3537ca18c88e2fa3681bafa6ef1b21), [`39f1297`](https://github.com/Effect-TS/effect/commit/39f1297acc08864feb12de6b8cf2bf73434f6cf5), [`2db266b`](https://github.com/Effect-TS/effect/commit/2db266b1bfbc81868bc1778c37c76032a267c79f), [`2141e28`](https://github.com/Effect-TS/effect/commit/2141e28903754d72604acf81673ceb2c62a56646), [`3c5e429`](https://github.com/Effect-TS/effect/commit/3c5e429878669ffcf5e0da4ddfbf50bde5bbcaad), [`20ddc63`](https://github.com/Effect-TS/effect/commit/20ddc630584f8fe488162ba384adcae53fc6810a), [`841b3ea`](https://github.com/Effect-TS/effect/commit/841b3ea6ae19a784bc1c20497b02f632af0c91e9), [`82a3fbf`](https://github.com/Effect-TS/effect/commit/82a3fbfce8b9df33e587076b7d7168ecd6799e17), [`eb9ee83`](https://github.com/Effect-TS/effect/commit/eb9ee83b38844a71d1cd5653a229309cfcb04a36), [`64dc7c7`](https://github.com/Effect-TS/effect/commit/64dc7c76dc5c89887b9e7c181d1873dcbb7820d1), [`84dc8ab`](https://github.com/Effect-TS/effect/commit/84dc8ab7accc682bc668c78a97e4a1776b633be8), [`b4463f4`](https://github.com/Effect-TS/effect/commit/b4463f46fc33d3b01ea5eadd7d012a5abda347a3), [`592dd36`](https://github.com/Effect-TS/effect/commit/592dd361645739ac0cd8e6babb084cd27403c172), [`85d2b44`](https://github.com/Effect-TS/effect/commit/85d2b446e3059de4919be730105868f79728308d), [`32e4a69`](https://github.com/Effect-TS/effect/commit/32e4a69b3151b7ec4058af2213b96a41d11e9e06), [`13c5872`](https://github.com/Effect-TS/effect/commit/13c5872ed30830360367ad89af2dab68a003c351), [`3454cdb`](https://github.com/Effect-TS/effect/commit/3454cdb528fdb5d3ed0c5c5c8169bc47de41fbd8), [`e930804`](https://github.com/Effect-TS/effect/commit/e9308045be1d8a00c0b4046f1e8ff22cf68c93da), [`7f12d4b`](https://github.com/Effect-TS/effect/commit/7f12d4b4e731dc3a213ae5c3f60db9edc50292d2), [`181c9ef`](https://github.com/Effect-TS/effect/commit/181c9ef5e5d4ab247bf4aec06424f15b0a1e802e), [`dd9f891`](https://github.com/Effect-TS/effect/commit/dd9f891e23f316abb6192893008f0e33ece9d97d), [`433fb81`](https://github.com/Effect-TS/effect/commit/433fb81ca4c15c681a8ae097ce3ff9bd3a9c9aa5), [`8459cdb`](https://github.com/Effect-TS/effect/commit/8459cdbae8a476dc04b6247fffe6a1668dcb1217), [`6124ab3`](https://github.com/Effect-TS/effect/commit/6124ab39eb64688fbd5d688d24766542f9cb5a2c), [`01bd954`](https://github.com/Effect-TS/effect/commit/01bd9546f142706fca1628f7261e6d1cb9638948), [`ba2c3aa`](https://github.com/Effect-TS/effect/commit/ba2c3aa05eb87ec05d263b960017ecf29746f66a), [`0a45ef3`](https://github.com/Effect-TS/effect/commit/0a45ef3bb4a1ae9b345c43c548db4336a31b3191), [`8459cdb`](https://github.com/Effect-TS/effect/commit/8459cdbae8a476dc04b6247fffe6a1668dcb1217), [`eaa7e71`](https://github.com/Effect-TS/effect/commit/eaa7e71b88bf59b24610128c6115a2a126432731), [`db4c2cc`](https://github.com/Effect-TS/effect/commit/db4c2ccdec77d813b6f4cc72a302ee7c4fe6e39d), [`22f150a`](https://github.com/Effect-TS/effect/commit/22f150a0936cef30517e87eaca73bff1c5e4873a), [`90ffb08`](https://github.com/Effect-TS/effect/commit/90ffb083b3091c211300f50a42ba7bf56536c0ee), [`d517692`](https://github.com/Effect-TS/effect/commit/d517692ef75f45d5f6d9d68b32d41fa0ccc56c99), [`01af079`](https://github.com/Effect-TS/effect/commit/01af079c189d1fc5067d3b1933b2870c4baf2693), [`32a59e8`](https://github.com/Effect-TS/effect/commit/32a59e8058b1ec9738cb083cf1cb116b393ca114)]: + - effect@4.0.0-beta.104 + +## 4.0.0-beta.103 + +### Patch Changes + +- [#6777](https://github.com/Effect-TS/effect/pull/6777) [`8e7c706`](https://github.com/Effect-TS/effect/commit/8e7c706b0aca855489b53d987404566d3e9cb5e7) Thanks @tim-smart! - Bound pending SSE decoder state with a configurable maximum event size. + +- [#6921](https://github.com/Effect-TS/effect/pull/6921) [`4686265`](https://github.com/Effect-TS/effect/commit/468626540686305d7ce34ecdd76e67b2bef2a60e) Thanks @fubhy! - Emit specialized OpenAI tool results only once. + +- [#6722](https://github.com/Effect-TS/effect/pull/6722) [`9344742`](https://github.com/Effect-TS/effect/commit/9344742c6b0ae4ff627b4492a6ddf7fbac5c3785) Thanks @mrtdurdenthe2! - Fix OpenAI stable web search response decoding by preserving the provider action in tool call parameters. + +- [#6675](https://github.com/Effect-TS/effect/pull/6675) [`4cc95ae`](https://github.com/Effect-TS/effect/commit/4cc95ae4a88bc9f5a2e7595de771caeee354cf6e) Thanks @danieljvdm! - Accept image generation-specific lifecycle statuses and nullable results in OpenAI response items. + +- [#6701](https://github.com/Effect-TS/effect/pull/6701) [`9867b9f`](https://github.com/Effect-TS/effect/commit/9867b9fc69f9cc6c443594fc7eccc7be0c674d9c) Thanks @fubhy! - Removed explicit ./index entrypoints + +- [#6920](https://github.com/Effect-TS/effect/pull/6920) [`da10211`](https://github.com/Effect-TS/effect/commit/da102116733d485e794c1d06f938a4e03daf418e) Thanks @fubhy! - Terminate OpenAI HTTP and WebSocket response streams when a `response.failed` event arrives. + +- Updated dependencies [[`e56cd8f`](https://github.com/Effect-TS/effect/commit/e56cd8f90c3559baccf8fcf2852ea911235d5944), [`f77c120`](https://github.com/Effect-TS/effect/commit/f77c120d8e04779ddeb8bce8e9cde932f268e4b6), [`b2f95a9`](https://github.com/Effect-TS/effect/commit/b2f95a9c2f2581deb89dc3bae9e89cf819e82923), [`04fd44a`](https://github.com/Effect-TS/effect/commit/04fd44a42abfa8dc2642300dcf49ee48c8ef4539), [`b74333d`](https://github.com/Effect-TS/effect/commit/b74333d83e15b9d042e4698ad23040de60454afe), [`1c40b28`](https://github.com/Effect-TS/effect/commit/1c40b2809503d6aa1358777196fc66317906e657), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`b3901d2`](https://github.com/Effect-TS/effect/commit/b3901d29c543fd5bd05ceec669a17896c8e19006), [`4a0984a`](https://github.com/Effect-TS/effect/commit/4a0984af62738fedf4bd3e87adb4d4d641ce9147), [`fffd88b`](https://github.com/Effect-TS/effect/commit/fffd88b3135abdf928ca7c4b0e00e610985091c7), [`f3f6c1e`](https://github.com/Effect-TS/effect/commit/f3f6c1e02cb543423fcffef5dc2db03fac503588), [`ef07642`](https://github.com/Effect-TS/effect/commit/ef07642dfe671d5258b65d1c1480c4d05c495f15), [`f1bc827`](https://github.com/Effect-TS/effect/commit/f1bc8274a608813d7b09d28dcca04adbf62f8c92), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`081f4d8`](https://github.com/Effect-TS/effect/commit/081f4d8cd06a2ac222d2810b46e61efcee26939e), [`5287b24`](https://github.com/Effect-TS/effect/commit/5287b24f5f8fa094ba20e117bfb1a80fba6d2cf5), [`13d31cf`](https://github.com/Effect-TS/effect/commit/13d31cfc2dde46210e94391b5b6767ae9aeaf2c9), [`acee269`](https://github.com/Effect-TS/effect/commit/acee26944bc89ee554d7b9fadab7443f9edc28a9), [`31170c1`](https://github.com/Effect-TS/effect/commit/31170c19b236c37abb5476c821bc6f5bfa2735ab), [`205ebc7`](https://github.com/Effect-TS/effect/commit/205ebc776062012581e98fced7ced19adfc44ee7), [`ed0ebf8`](https://github.com/Effect-TS/effect/commit/ed0ebf8e5c864d46fed1f232e99c0e680f10a58f), [`a3fd084`](https://github.com/Effect-TS/effect/commit/a3fd08482157bd78b089f77c7b173d54ef68b5cd), [`ee29ddf`](https://github.com/Effect-TS/effect/commit/ee29ddf862c3723ad466abc93ab6f6fe723b2319), [`6086309`](https://github.com/Effect-TS/effect/commit/60863090af8e5af0bfa1435f08dc5390f9993e30), [`4a57af2`](https://github.com/Effect-TS/effect/commit/4a57af24011db1d66e947289d2f7ffc2074696d2), [`660875b`](https://github.com/Effect-TS/effect/commit/660875b4325e6eebb3f04513998301cd2a0847ec), [`8e7c706`](https://github.com/Effect-TS/effect/commit/8e7c706b0aca855489b53d987404566d3e9cb5e7), [`5f63adb`](https://github.com/Effect-TS/effect/commit/5f63adbe75fc9d50d23706a52b3e483ad2a1a01c), [`053bc42`](https://github.com/Effect-TS/effect/commit/053bc42e2a964755611a216e78ed214322efee37), [`c0a1534`](https://github.com/Effect-TS/effect/commit/c0a153494484ecf9f0d0f20895a7a648b4be363b), [`f1e3a37`](https://github.com/Effect-TS/effect/commit/f1e3a378c144f974a6122b299f421b75595af20f), [`cedb01a`](https://github.com/Effect-TS/effect/commit/cedb01a025492a1faf9e59eb23eb96bc3b5e2fff), [`1747440`](https://github.com/Effect-TS/effect/commit/1747440de9a51a56ed3660da748cc01b256adce7), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`b4f1ee2`](https://github.com/Effect-TS/effect/commit/b4f1ee238d96aa78c5f040158cb78671d75b381e), [`a4757f1`](https://github.com/Effect-TS/effect/commit/a4757f1c47067d8d016a6c4a2c541bb8ae520f9b), [`cd122b9`](https://github.com/Effect-TS/effect/commit/cd122b90300d995a237993a2edb7a049785ab6a4), [`5de588b`](https://github.com/Effect-TS/effect/commit/5de588b2472fb0f4eb919766eb8472583a044772), [`3895b9c`](https://github.com/Effect-TS/effect/commit/3895b9cf179262cd277a9c6daafe9050dcf8265e), [`89ce5f3`](https://github.com/Effect-TS/effect/commit/89ce5f3e16e23a193daa475dc72ea8133ae1dacd), [`985de09`](https://github.com/Effect-TS/effect/commit/985de097d75906db2aed784841f81e23cc978b43), [`9800e3a`](https://github.com/Effect-TS/effect/commit/9800e3acc8f36530f671bc8b91558cb112f449a7), [`4dc35f6`](https://github.com/Effect-TS/effect/commit/4dc35f64641746366f867ea3dbfedb9cd4685ada), [`e8eb62b`](https://github.com/Effect-TS/effect/commit/e8eb62b3d0ef27e9761cdc2eb93bdec52d6ee204), [`ecd9993`](https://github.com/Effect-TS/effect/commit/ecd99936112cb69efdb02de3a2fd57f47baefdf3), [`5ab9c08`](https://github.com/Effect-TS/effect/commit/5ab9c08463ce049c45f3502676954a7b72c6b024), [`f5cf965`](https://github.com/Effect-TS/effect/commit/f5cf96548afd51f4b3cf1aea11b04d7f8549ce90), [`a94cbed`](https://github.com/Effect-TS/effect/commit/a94cbed84e9e49bea4bff925599c0f19c4e3deab), [`9160ad7`](https://github.com/Effect-TS/effect/commit/9160ad7d146d4376dd12f7510c025e5b2f638a70), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`52494be`](https://github.com/Effect-TS/effect/commit/52494be9e8eb3bb542d06a3dfefc6bca4e168984), [`5441c8e`](https://github.com/Effect-TS/effect/commit/5441c8e656a6418c0d27feb2df67565a3e1155f4), [`c9b56ab`](https://github.com/Effect-TS/effect/commit/c9b56ab507f224426ee8388dc450da447ec4715f), [`8ef7257`](https://github.com/Effect-TS/effect/commit/8ef72577d1f43212cab87951d659e54e3c8d7d91), [`1519406`](https://github.com/Effect-TS/effect/commit/1519406fed6e8b017ae178dc20bcaa2cf318b570), [`9716990`](https://github.com/Effect-TS/effect/commit/97169902eec3c99baa7f0b2c7b45a0a5eae75819), [`733f75b`](https://github.com/Effect-TS/effect/commit/733f75b7125e3016a975fdd251c0179ae5393786), [`48155c8`](https://github.com/Effect-TS/effect/commit/48155c8ccfc12dcca8a00fa358d50b20c30874e4), [`951d06b`](https://github.com/Effect-TS/effect/commit/951d06b83d459d3e8fa9024e727a5db1662d3322), [`d767b65`](https://github.com/Effect-TS/effect/commit/d767b65a7687e38be23f0b0ee3d52ab5f2360cbe), [`5d52d9d`](https://github.com/Effect-TS/effect/commit/5d52d9d148aaa7f736ed8c310fc8bfa9dc81badf), [`f4151e1`](https://github.com/Effect-TS/effect/commit/f4151e1937c26de14f1d64566f8126173f1b5014), [`e02fbb6`](https://github.com/Effect-TS/effect/commit/e02fbb66f5a0f13dba6c33ef63528a37a17a0676), [`724ce09`](https://github.com/Effect-TS/effect/commit/724ce09650a458d4565e5c7331ea92ca04f08e68), [`dbe91f6`](https://github.com/Effect-TS/effect/commit/dbe91f6961ef9f7e8da910ee5758d9c0d385fca8), [`4c008d2`](https://github.com/Effect-TS/effect/commit/4c008d28b370d817f7ae4579db09836fe084c8d2), [`b650832`](https://github.com/Effect-TS/effect/commit/b6508328708a842f3163467b72486bd228f1a289), [`b46c92f`](https://github.com/Effect-TS/effect/commit/b46c92f3b314f4ffd612b831efa55dd856c587a3), [`5335797`](https://github.com/Effect-TS/effect/commit/5335797003076d9c6fd170da98d779696d555596), [`4b3460d`](https://github.com/Effect-TS/effect/commit/4b3460daa434ec465a95a50704fe1103a9275999), [`6301fd7`](https://github.com/Effect-TS/effect/commit/6301fd710b4325718de2c42997dac28a9e9aa250), [`aebc5c6`](https://github.com/Effect-TS/effect/commit/aebc5c61664b89a840465ec65b79ce635a5ceee8), [`52b2d7b`](https://github.com/Effect-TS/effect/commit/52b2d7b5bd3c7cce3bd5b69c6ab3941004da70f3), [`eec5744`](https://github.com/Effect-TS/effect/commit/eec57445dfa0ef3c5977195ad69415b7e7d42bb6), [`24e0e93`](https://github.com/Effect-TS/effect/commit/24e0e93dc307dc2c2ae86caacb7289e1dab3c103), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`1a7ce81`](https://github.com/Effect-TS/effect/commit/1a7ce8150e3977586c44d8ccb9a8384389bb4d49), [`48f22a7`](https://github.com/Effect-TS/effect/commit/48f22a7d16ae57ee2175d450dafbdeb69e187d2a), [`c96b7f6`](https://github.com/Effect-TS/effect/commit/c96b7f6359662053c3e09344f61dddc7a6caf4ac), [`6d2a942`](https://github.com/Effect-TS/effect/commit/6d2a942ed7cd33b8fd79d549edba33bc9e2a7e3e), [`cc27b19`](https://github.com/Effect-TS/effect/commit/cc27b194b9d13fa3a66ab037e853fca9d41700ff), [`8f9499f`](https://github.com/Effect-TS/effect/commit/8f9499f562729f5f7b08d8bcc4db86b4aeff8a21), [`3eeea73`](https://github.com/Effect-TS/effect/commit/3eeea73cfc3e9b126975c2ddbdb7f7c8c92026e2), [`0a532e5`](https://github.com/Effect-TS/effect/commit/0a532e503f165fdea485a5343fc2f420917e8376), [`f398149`](https://github.com/Effect-TS/effect/commit/f398149c134fd9b67b6cdc52eae3f3248d5c7bbe), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`ace903e`](https://github.com/Effect-TS/effect/commit/ace903e09c2549ceebdec380797beb027cd29f3d), [`e8eb62b`](https://github.com/Effect-TS/effect/commit/e8eb62b3d0ef27e9761cdc2eb93bdec52d6ee204), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`48f22a7`](https://github.com/Effect-TS/effect/commit/48f22a7d16ae57ee2175d450dafbdeb69e187d2a), [`d48506d`](https://github.com/Effect-TS/effect/commit/d48506d97525040aa714305e928126df799795b4), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`d48506d`](https://github.com/Effect-TS/effect/commit/d48506d97525040aa714305e928126df799795b4), [`d48506d`](https://github.com/Effect-TS/effect/commit/d48506d97525040aa714305e928126df799795b4), [`52262be`](https://github.com/Effect-TS/effect/commit/52262be2edce0e350c6ac10f8f725678606399c5), [`1284aa1`](https://github.com/Effect-TS/effect/commit/1284aa183451955ad7921bbe01fd0e095695d444), [`9867b9f`](https://github.com/Effect-TS/effect/commit/9867b9fc69f9cc6c443594fc7eccc7be0c674d9c), [`d0f1a22`](https://github.com/Effect-TS/effect/commit/d0f1a2295155c350b04efb46852cb40032805273), [`979ce39`](https://github.com/Effect-TS/effect/commit/979ce3985d7d62ce2bf240681ca19feda3027452), [`b6d3e67`](https://github.com/Effect-TS/effect/commit/b6d3e67c7cc143cd8470cdf704324e79d23954a9), [`adf6c6c`](https://github.com/Effect-TS/effect/commit/adf6c6cd388af8a3c0c546492e71555368556f6a), [`7314d60`](https://github.com/Effect-TS/effect/commit/7314d605284717aaafe7fc34b88c3c93397e865c), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`1acbd8b`](https://github.com/Effect-TS/effect/commit/1acbd8b44c68ebb23735e9810476b870dbe58aea), [`7bde6cc`](https://github.com/Effect-TS/effect/commit/7bde6ccb2b144fe953ff30a7ef5e1ecc97697146), [`a959a8b`](https://github.com/Effect-TS/effect/commit/a959a8bf21cdb976369f494dc949fa00a050d3e0)]: + - effect@4.0.0-beta.103 + +## 4.0.0-beta.102 + +### Patch Changes + +- [#6608](https://github.com/Effect-TS/effect/pull/6608) [`0e50ec7`](https://github.com/Effect-TS/effect/commit/0e50ec7dbb94390666f292cf9120719bf30a7246) Thanks @gcanti! - Add `Schema.Natural` for non-negative safe integers and use canonical `Schema.Int`, `Schema.Finite`, and `Schema.Natural` schemas for numeric domain values across Effect, AI protocols, and OpenAPI patches. + + Update the date, date-time, file, time-zone, cluster, event-log, persistence, socket, SQL, and DevTools schemas to reject invalid non-finite or non-integer values where appropriate. Correct the decoded schema of `Schema.NumberFromString`, and allow `Schema.DurationFromMillis` and `Schema.DurationFromNanos` to represent negative durations. + +- [#6650](https://github.com/Effect-TS/effect/pull/6650) [`acd385e`](https://github.com/Effect-TS/effect/commit/acd385ebb3f9edee37ab6715607119ee9762a615) Thanks @IMax153! - Redact OpenAI organization and project headers from client errors. + +- [#6549](https://github.com/Effect-TS/effect/pull/6549) [`64c6ab1`](https://github.com/Effect-TS/effect/commit/64c6ab1951ac6fb0bdd5e0398795946dc2872be7) Thanks @xianjianlf2! - Encode OpenAI Responses API system messages as typed input text content. + +- [#6567](https://github.com/Effect-TS/effect/pull/6567) [`5101e92`](https://github.com/Effect-TS/effect/commit/5101e92c9c149c153423f43dd7a94f6194653c06) Thanks @gcanti! - Add `Record.assignProperty` and safely handle dynamic record keys such as `__proto__` and inherited property names. + +- Updated dependencies [[`b6392e1`](https://github.com/Effect-TS/effect/commit/b6392e119704553edec1b4fd2869ac0dbec621ef), [`7ed9450`](https://github.com/Effect-TS/effect/commit/7ed945044eb56aa9aeaf62d4746a011c96c58628), [`45762bd`](https://github.com/Effect-TS/effect/commit/45762bd78df9ecd87c98b8d3738cdeeac7d81128), [`a6e8391`](https://github.com/Effect-TS/effect/commit/a6e8391cd31acd898fae18b3f8e7ca4c6f14f065), [`4ac7e8b`](https://github.com/Effect-TS/effect/commit/4ac7e8b136c61a26c3e438c013dfd7349b38e999), [`4cd40f5`](https://github.com/Effect-TS/effect/commit/4cd40f5692477783bef84fed3c5ef1c0cf5602e6), [`6956bc0`](https://github.com/Effect-TS/effect/commit/6956bc0e6cb27f53fbec39d9b18545940f9f598f), [`0e50ec7`](https://github.com/Effect-TS/effect/commit/0e50ec7dbb94390666f292cf9120719bf30a7246), [`9fcdade`](https://github.com/Effect-TS/effect/commit/9fcdade4a8af772b9ccd8b8a24fe8cee0e5d8470), [`57367d5`](https://github.com/Effect-TS/effect/commit/57367d54de55047ff0c5fce9685475e236bf354c), [`35c445f`](https://github.com/Effect-TS/effect/commit/35c445ff18029d192900ea0914c993f58d5cf1a5), [`c917bb9`](https://github.com/Effect-TS/effect/commit/c917bb94a4c1c4e0a24372a8ebb8a5ca232e36b5), [`bc1f358`](https://github.com/Effect-TS/effect/commit/bc1f3583e63344cb2c398d9040d9c975488ed123), [`0e0c9d7`](https://github.com/Effect-TS/effect/commit/0e0c9d7922ff463c1093d9e0576fae12cb0698d5), [`73d40aa`](https://github.com/Effect-TS/effect/commit/73d40aacd8fcae1b48c23f5b0a5c542127401d1d), [`4f1e318`](https://github.com/Effect-TS/effect/commit/4f1e3183f7123591c46224e9c587df7594562a5f), [`9d8d85c`](https://github.com/Effect-TS/effect/commit/9d8d85c1bb7da51970845b8ea830e386e777514a), [`6079fda`](https://github.com/Effect-TS/effect/commit/6079fda7b02f2f01ad91c15ab8c307336f3ba252), [`5101e92`](https://github.com/Effect-TS/effect/commit/5101e92c9c149c153423f43dd7a94f6194653c06), [`d0b3265`](https://github.com/Effect-TS/effect/commit/d0b3265c3262670761471ab3518cf933b1b3b20a), [`7a03c89`](https://github.com/Effect-TS/effect/commit/7a03c893ce6492bf94c0ebfb00b63bf25dcbf83e), [`cea1d9c`](https://github.com/Effect-TS/effect/commit/cea1d9c92601e69ebda040af8a1d860d604d885c), [`078e1f5`](https://github.com/Effect-TS/effect/commit/078e1f5636e31b76a86722a636afc37a8cc25580), [`97bafea`](https://github.com/Effect-TS/effect/commit/97bafeab460833b9781527b437d1cb9cbee63260), [`fab0ab8`](https://github.com/Effect-TS/effect/commit/fab0ab8f7ab15ae596faa4ccf75615a494d11b0b), [`c323d8b`](https://github.com/Effect-TS/effect/commit/c323d8b30dbbe85f9df25b67288b93d5332de333), [`6966353`](https://github.com/Effect-TS/effect/commit/69663534d626003eb10a5e55ab1f13e0379fead1), [`0444004`](https://github.com/Effect-TS/effect/commit/04440041989c1785fe4db286379f2be2c15baa85), [`028bbb3`](https://github.com/Effect-TS/effect/commit/028bbb391e161185da10d974ab33381f769940d7), [`ff5d6e2`](https://github.com/Effect-TS/effect/commit/ff5d6e278a1fdff714315dc1a17075012f05c1f0), [`1bfce93`](https://github.com/Effect-TS/effect/commit/1bfce93e6d2bf0794c11733daf51c2390e7de375), [`7ce815c`](https://github.com/Effect-TS/effect/commit/7ce815cd5af6af991dfc13b890fd22345fc77c20), [`7271a7f`](https://github.com/Effect-TS/effect/commit/7271a7faf1080aa75f2f53ca6a0b5ec9334c1d38), [`475fe5c`](https://github.com/Effect-TS/effect/commit/475fe5c12c2d6504c475797c0634f90da01e1797)]: + - effect@4.0.0-beta.102 + +## 4.0.0-beta.101 + +### Patch Changes + +- [#6519](https://github.com/Effect-TS/effect/pull/6519) [`0e116a1`](https://github.com/Effect-TS/effect/commit/0e116a139c9d7af7c7e0ccbb7b5669926477370e) Thanks @tim-smart! - Preserve OpenAI Responses API cache write token usage in language model responses. + +- Updated dependencies [[`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`b35ed29`](https://github.com/Effect-TS/effect/commit/b35ed2904f01536d303b21f288daf343cf740462), [`dd44624`](https://github.com/Effect-TS/effect/commit/dd446245736a0e88c807a02f03c21450bb9340fa), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`2bae1ac`](https://github.com/Effect-TS/effect/commit/2bae1accce9d3b72cf6d5aefc9b2161af6d88436)]: + - effect@4.0.0-beta.101 + +## 4.0.0-beta.100 + +### Patch Changes + +- Updated dependencies [[`c1288dd`](https://github.com/Effect-TS/effect/commit/c1288dd1a52a2811ab7df57fc4ce236c6be4c745), [`2b58a3d`](https://github.com/Effect-TS/effect/commit/2b58a3dab6bc99776dddaf76e27d811e0f47f3d8), [`6dc83f2`](https://github.com/Effect-TS/effect/commit/6dc83f26ddf20d48db28cf761dd8f3716e5273fb), [`c1e2fe0`](https://github.com/Effect-TS/effect/commit/c1e2fe0cf93564f4d919e3998874c3e70b0cf30f), [`f3fbae8`](https://github.com/Effect-TS/effect/commit/f3fbae8d7bae0d77cb4f35a1598b26c58e3bf94d), [`e000f80`](https://github.com/Effect-TS/effect/commit/e000f80fd55bcd8edc699fdbf4cd109004f4f754), [`f4ee765`](https://github.com/Effect-TS/effect/commit/f4ee7655ee052cf9ba726fd602bb87c89c7c62a9), [`510b55f`](https://github.com/Effect-TS/effect/commit/510b55f3e21750685dbfd5f476a130c1c5af9dbd), [`31d3fc4`](https://github.com/Effect-TS/effect/commit/31d3fc4327c50867bb8d881fa7353aeb03ea2826), [`875e618`](https://github.com/Effect-TS/effect/commit/875e618c3764a7b817ac863d0af86924449528f2), [`688d46a`](https://github.com/Effect-TS/effect/commit/688d46afd0ef923d983ad3d7385f52f217b28d70), [`6ff5023`](https://github.com/Effect-TS/effect/commit/6ff502363b9840a5a5ee0a24bc6cae734ac3a3eb), [`c0333e7`](https://github.com/Effect-TS/effect/commit/c0333e7f755f42ddcca7051e029da8b4eed527bf), [`06e7e8c`](https://github.com/Effect-TS/effect/commit/06e7e8c66015ee318f871b9d2218dee82df2b108), [`eb9b102`](https://github.com/Effect-TS/effect/commit/eb9b10256c8558881b441c2fef833b7037174400), [`8b155da`](https://github.com/Effect-TS/effect/commit/8b155da06e0740c354ec562957a45ab65eb4573b), [`3a87335`](https://github.com/Effect-TS/effect/commit/3a8733564c5db35271aa20564ed0d344daa2a79f)]: + - effect@4.0.0-beta.100 + +## 4.0.0-beta.99 + +### Patch Changes + +- Updated dependencies [[`8ce4795`](https://github.com/Effect-TS/effect/commit/8ce4795ccbaebca4292757db568c005a992546a4), [`80b539f`](https://github.com/Effect-TS/effect/commit/80b539f8aba68f478c75c35c2b4140c4ffc4fada), [`88a54cc`](https://github.com/Effect-TS/effect/commit/88a54cc341006e3ebcb13482c618f62a680ce199), [`e6e6dba`](https://github.com/Effect-TS/effect/commit/e6e6dba6e9d86e7c2ad27dcedf289db76a19697f), [`bfb203e`](https://github.com/Effect-TS/effect/commit/bfb203e95aa439f731acad37fc3a9a831a190f1c), [`2e9a34a`](https://github.com/Effect-TS/effect/commit/2e9a34ac2bece4f3a206160480c991e3841dc67a), [`55d4eb3`](https://github.com/Effect-TS/effect/commit/55d4eb34f2c64d54f6a25a305b5c5438ebd7934e), [`bddb010`](https://github.com/Effect-TS/effect/commit/bddb010eac3d4436cb094edbbee7460c5440c162), [`a328835`](https://github.com/Effect-TS/effect/commit/a328835e50d76bc96648a1c1550456e8c9f81210), [`5560d05`](https://github.com/Effect-TS/effect/commit/5560d05aa6abdd29466d9c3412cc5e648b0adbde), [`8f6e3ad`](https://github.com/Effect-TS/effect/commit/8f6e3adb185b16e8820b98c509b308086f7ff1af), [`46997fa`](https://github.com/Effect-TS/effect/commit/46997fa60401f5e3c93daa4b61f7df8e31caaab4), [`9e6e12d`](https://github.com/Effect-TS/effect/commit/9e6e12d75c118cd265496f2880490d1f33a5c8bf), [`3394b93`](https://github.com/Effect-TS/effect/commit/3394b93d97d6f24fc38670641d1490289ffca7f1), [`febeabc`](https://github.com/Effect-TS/effect/commit/febeabc3f7c31094da000a23edeaabfe2ab00a38), [`54161c9`](https://github.com/Effect-TS/effect/commit/54161c98f6f3569e0c31842f54e6a257f9421c4c), [`385f7a4`](https://github.com/Effect-TS/effect/commit/385f7a4ee4a7359928597ea56d151dbaf5eb5802), [`7eea4d0`](https://github.com/Effect-TS/effect/commit/7eea4d0b73ec554915d7066a71f46326ce2ba45f), [`7543afe`](https://github.com/Effect-TS/effect/commit/7543afea6f4d97d1f1ad876224323838a48daadd), [`44b9cf3`](https://github.com/Effect-TS/effect/commit/44b9cf3d240d726997b4bbcd0ede48e074d3c456), [`7eea4d0`](https://github.com/Effect-TS/effect/commit/7eea4d0b73ec554915d7066a71f46326ce2ba45f), [`0a8aa6a`](https://github.com/Effect-TS/effect/commit/0a8aa6acb90a72b91c24d17133c950e4cacd8abd), [`c8d9fcf`](https://github.com/Effect-TS/effect/commit/c8d9fcf7b030f7c474effbab2764ce7aee1c7209), [`9ca7f9a`](https://github.com/Effect-TS/effect/commit/9ca7f9a69363e4485645966d5a93b8f9597c5206), [`e7aca89`](https://github.com/Effect-TS/effect/commit/e7aca894bb32fbb785b5830837e6061c415a6015), [`55d7560`](https://github.com/Effect-TS/effect/commit/55d75609b8acf8a1b54c1b1c7fbbb65ec741aa3e), [`f809189`](https://github.com/Effect-TS/effect/commit/f809189ddf6b6011ba43a9901baaa734e315da2a), [`88a54cc`](https://github.com/Effect-TS/effect/commit/88a54cc341006e3ebcb13482c618f62a680ce199), [`0ebdbe7`](https://github.com/Effect-TS/effect/commit/0ebdbe74463dc84385956d0b1e8c2b79ebab5400), [`7517d09`](https://github.com/Effect-TS/effect/commit/7517d09f12a0b183a81bd425962c4e280a68b05d), [`212493b`](https://github.com/Effect-TS/effect/commit/212493b9a1eb98cd1ef6959c707a2e5784a5ae91), [`88a54cc`](https://github.com/Effect-TS/effect/commit/88a54cc341006e3ebcb13482c618f62a680ce199), [`80ea8cb`](https://github.com/Effect-TS/effect/commit/80ea8cb9222ca73f564c8267ab2f82966fea027a), [`8df19f4`](https://github.com/Effect-TS/effect/commit/8df19f4fe81d90cc33ace88b9a77e5534f82d604)]: + - effect@4.0.0-beta.99 + +## 4.0.0-beta.98 + +### Patch Changes + +- Updated dependencies [[`989603b`](https://github.com/Effect-TS/effect-smol/commit/989603b60ab1197b64acf214208e0d370cd1f842), [`214c458`](https://github.com/Effect-TS/effect-smol/commit/214c458084bb6995d543cd37d1055f24be3d454e), [`a037273`](https://github.com/Effect-TS/effect-smol/commit/a0372736ac34796969b051bbba4717d7983f1ebe), [`97fdaa9`](https://github.com/Effect-TS/effect-smol/commit/97fdaa9c1f522c65e579365d314a07878e2b904f), [`b24d248`](https://github.com/Effect-TS/effect-smol/commit/b24d248c8df44222ce642087cde2bd859a2dc709), [`19c222c`](https://github.com/Effect-TS/effect-smol/commit/19c222cac2353a3d7b7733caecb00556fffe9a5c), [`eec85dd`](https://github.com/Effect-TS/effect-smol/commit/eec85ddba09ea326fd268ee33eeffd47e50d4671), [`0082f4f`](https://github.com/Effect-TS/effect-smol/commit/0082f4f74fb139fd578f87f0a790e845133983dc), [`8849052`](https://github.com/Effect-TS/effect-smol/commit/884905232d1e9a365e046d8dde27bf9c5707f57f), [`c15e16a`](https://github.com/Effect-TS/effect-smol/commit/c15e16ad130d1fbde25d912b7ac55995066cb35b), [`01d00a3`](https://github.com/Effect-TS/effect-smol/commit/01d00a3abfbf1f37996cdbe738ea5137c646cdd7), [`8bd4589`](https://github.com/Effect-TS/effect-smol/commit/8bd458975a1b3a8ed042eccf317b93d28ded91e7), [`0082f4f`](https://github.com/Effect-TS/effect-smol/commit/0082f4f74fb139fd578f87f0a790e845133983dc), [`6e08428`](https://github.com/Effect-TS/effect-smol/commit/6e08428d980501b856f846ad3f3f0e4ea46e7786), [`388dcf9`](https://github.com/Effect-TS/effect-smol/commit/388dcf953f65d317547f34d40e6443c5f264205f), [`2b7ce2b`](https://github.com/Effect-TS/effect-smol/commit/2b7ce2b513e7ec2a77822f1116dc6ffb6ba93f4e), [`87bea7e`](https://github.com/Effect-TS/effect-smol/commit/87bea7e16259246f3bcdf565446394751abca953), [`ce38dc3`](https://github.com/Effect-TS/effect-smol/commit/ce38dc33bda805a684432cca071f4dc3c6b9a1ba), [`a807cd1`](https://github.com/Effect-TS/effect-smol/commit/a807cd170341deca8a1cfb52c4222585f2431bb9), [`fd8a356`](https://github.com/Effect-TS/effect-smol/commit/fd8a356f06a8c9ce4e7e0a13fc4021c178ed31de), [`c2a5edc`](https://github.com/Effect-TS/effect-smol/commit/c2a5edc3abd31ad5bc123362bc1213e03e4095c3), [`5946da3`](https://github.com/Effect-TS/effect-smol/commit/5946da3804a1be5e752b05b96bd058cdba50a1bf), [`4ae0c5f`](https://github.com/Effect-TS/effect-smol/commit/4ae0c5ffcbe6c56ddfcb05c639112a079483539e), [`5b2a0bc`](https://github.com/Effect-TS/effect-smol/commit/5b2a0bceea3a28a33a58555210c90a415dc74a76), [`72ac585`](https://github.com/Effect-TS/effect-smol/commit/72ac585884befde6af9208da738699a93f1bae79), [`5e8c1b8`](https://github.com/Effect-TS/effect-smol/commit/5e8c1b82bfafa121311f987a49ab75395e3647a7), [`0f9c078`](https://github.com/Effect-TS/effect-smol/commit/0f9c07841b04183f485ee6e6458de73b290b09f5)]: + - effect@4.0.0-beta.98 + +## 4.0.0-beta.97 + +### Patch Changes + +- Updated dependencies []: + - effect@4.0.0-beta.97 + +## 4.0.0-beta.96 + +### Patch Changes + +- Updated dependencies [[`1503f45`](https://github.com/Effect-TS/effect-smol/commit/1503f45cb5bb2a74f4705252ec505a1f0ade7e62), [`57fe793`](https://github.com/Effect-TS/effect-smol/commit/57fe79316ffbc380b30626a168981fb26ae97459), [`0c2f78f`](https://github.com/Effect-TS/effect-smol/commit/0c2f78f695ec474e1ff5474da183577975e418f5), [`0c2f78f`](https://github.com/Effect-TS/effect-smol/commit/0c2f78f695ec474e1ff5474da183577975e418f5), [`0c2f78f`](https://github.com/Effect-TS/effect-smol/commit/0c2f78f695ec474e1ff5474da183577975e418f5), [`97f29df`](https://github.com/Effect-TS/effect-smol/commit/97f29df457f7ffd07cfb4b379315c12c086af805)]: + - effect@4.0.0-beta.96 + +## 4.0.0-beta.95 + +### Patch Changes + +- Updated dependencies [[`a482442`](https://github.com/Effect-TS/effect-smol/commit/a482442abdeb490e9652b854ec3495e4aa7273e7), [`fbefa85`](https://github.com/Effect-TS/effect-smol/commit/fbefa850fab2f0a302c20614496aeaaa2a8b5590), [`0b4a32f`](https://github.com/Effect-TS/effect-smol/commit/0b4a32f4260f0d8500942a133001b0d349328102), [`18a49e1`](https://github.com/Effect-TS/effect-smol/commit/18a49e1786679456258002ff9397faf02f678c2d), [`266cb90`](https://github.com/Effect-TS/effect-smol/commit/266cb90bb2c17aabc40563c32db334f09ba3d74b), [`912f095`](https://github.com/Effect-TS/effect-smol/commit/912f095a34572bbd3cedf6edb27878443e3e4a95), [`a6718f9`](https://github.com/Effect-TS/effect-smol/commit/a6718f9e00a15ca903b0732da46116cbf3d6aca7), [`bef5154`](https://github.com/Effect-TS/effect-smol/commit/bef51540a243aa2f872a00c01d0cd58b7a769baa), [`18e0564`](https://github.com/Effect-TS/effect-smol/commit/18e0564bd0f8ebbdfcaf1e2c21529948e9e4a81d), [`fb50f14`](https://github.com/Effect-TS/effect-smol/commit/fb50f14fc3657c1973785aa5b72ecf0b0d28e0b2)]: + - effect@4.0.0-beta.95 + +## 4.0.0-beta.94 + +### Patch Changes + +- Updated dependencies [[`95a0e9b`](https://github.com/Effect-TS/effect-smol/commit/95a0e9bb62797af0e81c9998773405f248f218c5), [`a0a3490`](https://github.com/Effect-TS/effect-smol/commit/a0a3490bbce765f199d8e077aceac504f0462e63), [`f11ce73`](https://github.com/Effect-TS/effect-smol/commit/f11ce73af60823754dc24194f4ffc561b9ea1c2d), [`ff30b6e`](https://github.com/Effect-TS/effect-smol/commit/ff30b6e7c2c63ffc56a4c5818d6d86b01b5ad528), [`1caab3c`](https://github.com/Effect-TS/effect-smol/commit/1caab3cc30f626efbf15e59d74f539a487e5c85c), [`aa80c47`](https://github.com/Effect-TS/effect-smol/commit/aa80c4775a04db87553e5568764cab7e32a72814), [`c2ae4fc`](https://github.com/Effect-TS/effect-smol/commit/c2ae4fce2f03a4cd1861c2b1179da7df656e662d), [`a0a3490`](https://github.com/Effect-TS/effect-smol/commit/a0a3490bbce765f199d8e077aceac504f0462e63)]: + - effect@4.0.0-beta.94 + +## 4.0.0-beta.93 + +### Patch Changes + +- Updated dependencies [[`00652fe`](https://github.com/Effect-TS/effect-smol/commit/00652fe95c18f87208e91343eb8bf218faa2f677), [`6c58167`](https://github.com/Effect-TS/effect-smol/commit/6c5816746eaf91d2a3c7c899c5720809fa230ae3), [`2bc5415`](https://github.com/Effect-TS/effect-smol/commit/2bc541501a7ef89e542d7cb98e96beb53cd205cc), [`e11cccc`](https://github.com/Effect-TS/effect-smol/commit/e11cccc7d5fe631abccc7d6e3bd296938de0fa2e), [`ba7e77e`](https://github.com/Effect-TS/effect-smol/commit/ba7e77e046b8641a3a4e9750bb88ca4a1d063d3f), [`5713ee7`](https://github.com/Effect-TS/effect-smol/commit/5713ee7edbc3054efde407b2286bbfd45bbc6e1c)]: + - effect@4.0.0-beta.93 + +## 4.0.0-beta.92 + +### Patch Changes + +- Updated dependencies [[`affdc13`](https://github.com/Effect-TS/effect-smol/commit/affdc139045cc325dce321a84a580fdc1b2da7b9)]: + - effect@4.0.0-beta.92 + +## 4.0.0-beta.91 + +### Patch Changes + +- Updated dependencies [[`b135b25`](https://github.com/Effect-TS/effect-smol/commit/b135b2517fca9e7839734ace3699a7dfa75b9075), [`aaa21a3`](https://github.com/Effect-TS/effect-smol/commit/aaa21a369a171c600db294f2a4f640583043e150), [`3475ee6`](https://github.com/Effect-TS/effect-smol/commit/3475ee6c2bda6b05c6d7a12ce30c8bb840b5b1a6)]: + - effect@4.0.0-beta.91 + ## 4.0.0-beta.90 ### Patch Changes diff --git a/repos/effect/packages/ai/openai/codegen.yaml b/repos/effect/packages/ai/openai/codegen.yaml index 19d06da166..05724087a2 100644 --- a/repos/effect/packages/ai/openai/codegen.yaml +++ b/repos/effect/packages/ai/openai/codegen.yaml @@ -13,7 +13,6 @@ patches: - '[{"op":"add","path":"/components/schemas/ModelResponseProperties/properties/prompt_cache_key/nullable","value":true}]' - '[{"op":"add","path":"/components/schemas/Response/allOf/2/properties/usage/nullable","value":true}]' - '[{"op":"remove","path":"/components/schemas/ResponseFunctionCallArgumentsDoneEvent/required/2"}]' - - '[{"op":"remove","path":"/components/schemas/WebSearchActionSearch/required/1"}]' - '[{"op":"add","path":"/components/schemas/ModelResponseProperties/properties/prompt_cache_retention/anyOf/0/enum/1","value":"in_memory"}]' - '[{"op":"add","path":"/components/schemas/PromptCacheRetentionEnum/enum/1","value":"in-memory"}]' - '[{"op":"replace","path":"/components/schemas/OpenAIFile/properties/expires_at","value":{"anyOf":[{"type":"integer","format":"unixtime","description":"The Unix timestamp (in seconds) for when the file will expire."},{"type":"null"}]}}]' diff --git a/repos/effect/packages/ai/openai/docgen.json b/repos/effect/packages/ai/openai/docgen.json deleted file mode 100644 index 22eff2cf7c..0000000000 --- a/repos/effect/packages/ai/openai/docgen.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "$schema": "../../node_modules/@effect/docgen/schema.json", - "srcLink": "https://github.com/Effect-TS/effect/tree/main/packages/ai/openai/src/", - "exclude": ["src/Generated.ts", "src/internal/**/*.ts"], - "tscExecutable": "tsgo", - "examplesCompilerOptions": { - "noEmit": true, - "strict": true, - "skipLibCheck": true, - "moduleResolution": "Bundler", - "module": "ES2022", - "target": "ES2022", - "lib": ["ES2022", "DOM", "DOM.Iterable"], - "rewriteRelativeImportExtensions": true, - "allowImportingTsExtensions": true, - "paths": { - "effect": ["../../../effect/src/index.js"], - "effect/*": ["../../../effect/src/*.js"] - }, - "plugins": [ - { "name": "@effect/language-service", "includeSuggestionsInTsc": false } - ] - } -} diff --git a/repos/effect/packages/ai/openai/package.json b/repos/effect/packages/ai/openai/package.json index db95857af3..f51d8f1efa 100644 --- a/repos/effect/packages/ai/openai/package.json +++ b/repos/effect/packages/ai/openai/package.json @@ -1,17 +1,17 @@ { "name": "@effect/ai-openai", - "version": "4.0.0-beta.90", + "version": "4.0.0-beta.107", "type": "module", "license": "MIT", "description": "An OpenAI provider integration for Effect AI SDK", "homepage": "https://effect.website", "repository": { "type": "git", - "url": "https://github.com/Effect-TS/effect-smol.git", + "url": "https://github.com/Effect-TS/effect.git", "directory": "packages/ai/openai" }, "bugs": { - "url": "https://github.com/Effect-TS/effect-smol/issues" + "url": "https://github.com/Effect-TS/effect/issues" }, "tags": [ "typescript", @@ -29,6 +29,7 @@ ".": "./src/index.ts", "./*": "./src/*.ts", "./internal/*": null, + "./index": null, "./*/index": null }, "files": [ @@ -36,7 +37,10 @@ "dist/**/*.js", "dist/**/*.js.map", "dist/**/*.d.ts", - "dist/**/*.d.ts.map" + "dist/**/*.d.ts.map", + "AGENTS.md", + "CLAUDE.md", + "ai-docs/**/*" ], "publishConfig": { "access": "public", @@ -46,17 +50,15 @@ ".": "./dist/index.js", "./*": "./dist/*.js", "./internal/*": null, + "./index": null, "./*/index": null } }, "scripts": { "codegen": "effect-utils codegen", "build": "tsc -b tsconfig.json && pnpm babel", - "build:tsgo": "tsgo -b tsconfig.json && pnpm babel", "babel": "babel dist --plugins annotate-pure-calls --out-dir dist --source-maps", - "check": "tsc -b tsconfig.json", - "test": "vitest", - "coverage": "vitest --coverage" + "check": "tsc -b tsconfig.json" }, "devDependencies": { "effect": "workspace:^" diff --git a/repos/effect/packages/ai/openai/src/Generated.ts b/repos/effect/packages/ai/openai/src/Generated.ts index d7189bf5d3..d51b312440 100644 --- a/repos/effect/packages/ai/openai/src/Generated.ts +++ b/repos/effect/packages/ai/openai/src/Generated.ts @@ -30441,7 +30441,7 @@ export const make = ( request: HttpClientRequest.HttpClientRequest ): Stream.Stream< { readonly event: string; readonly id: string | undefined; readonly data: Type }, - HttpClientError.HttpClientError | SchemaError | Sse.Retry, + HttpClientError.HttpClientError | SchemaError | Sse.Retry | Sse.SseError, DecodingServices > => HttpClient.filterStatusOk(httpClient).execute(request).pipe( @@ -32832,7 +32832,7 @@ export interface OpenAiClient { options: { readonly payload: typeof CreateSpeechRequestJson.Encoded } ) => Stream.Stream< { readonly event: string; readonly id: string | undefined; readonly data: typeof CreateSpeech200Sse.Type }, - HttpClientError.HttpClientError | SchemaError | Sse.Retry, + HttpClientError.HttpClientError | SchemaError | Sse.Retry | Sse.SseError, typeof CreateSpeech200Sse.DecodingServices > /** @@ -32868,7 +32868,7 @@ export interface OpenAiClient { options: { readonly payload: typeof CreateTranscriptionRequestFormData.Encoded } ) => Stream.Stream< { readonly event: string; readonly id: string | undefined; readonly data: typeof CreateTranscription200Sse.Type }, - HttpClientError.HttpClientError | SchemaError | Sse.Retry, + HttpClientError.HttpClientError | SchemaError | Sse.Retry | Sse.SseError, typeof CreateTranscription200Sse.DecodingServices > /** @@ -33058,7 +33058,7 @@ export interface OpenAiClient { options: { readonly payload: typeof CreateChatCompletionRequestJson.Encoded } ) => Stream.Stream< { readonly event: string; readonly id: string | undefined; readonly data: typeof CreateChatCompletion200Sse.Type }, - HttpClientError.HttpClientError | SchemaError | Sse.Retry, + HttpClientError.HttpClientError | SchemaError | Sse.Retry | Sse.SseError, typeof CreateChatCompletion200Sse.DecodingServices > /** @@ -33659,7 +33659,7 @@ export interface OpenAiClient { options: { readonly payload: typeof CreateImageEditRequestFormData.Encoded } ) => Stream.Stream< { readonly event: string; readonly id: string | undefined; readonly data: typeof CreateImageEdit200Sse.Type }, - HttpClientError.HttpClientError | SchemaError | Sse.Retry, + HttpClientError.HttpClientError | SchemaError | Sse.Retry | Sse.SseError, typeof CreateImageEdit200Sse.DecodingServices > /** @@ -33678,7 +33678,7 @@ export interface OpenAiClient { options: { readonly payload: typeof CreateImageRequestJson.Encoded } ) => Stream.Stream< { readonly event: string; readonly id: string | undefined; readonly data: typeof CreateImage200Sse.Type }, - HttpClientError.HttpClientError | SchemaError | Sse.Retry, + HttpClientError.HttpClientError | SchemaError | Sse.Retry | Sse.SseError, typeof CreateImage200Sse.DecodingServices > /** @@ -34879,7 +34879,7 @@ export interface OpenAiClient { options: { readonly payload: typeof CreateResponseRequestJson.Encoded } ) => Stream.Stream< { readonly event: string; readonly id: string | undefined; readonly data: typeof CreateResponse200Sse.Type }, - HttpClientError.HttpClientError | SchemaError | Sse.Retry, + HttpClientError.HttpClientError | SchemaError | Sse.Retry | Sse.SseError, typeof CreateResponse200Sse.DecodingServices > /** diff --git a/repos/effect/packages/ai/openai/src/OpenAiClient.ts b/repos/effect/packages/ai/openai/src/OpenAiClient.ts index a1774b6d38..43753b63b4 100644 --- a/repos/effect/packages/ai/openai/src/OpenAiClient.ts +++ b/repos/effect/packages/ai/openai/src/OpenAiClient.ts @@ -47,7 +47,7 @@ import * as OpenAiSchema from "./OpenAiSchema.ts" * * Provides the configured HTTP client plus helpers for Responses API calls, streaming Responses events, and embeddings. Transport and schema decoding failures are mapped to `AiError`. * - * @category models + * @category services * @since 4.0.0 */ export interface Service { @@ -158,6 +158,11 @@ const RedactedOpenAiHeaders = { OpenAiProject: "OpenAI-Project" } +const withRedactedHeaders = Effect.updateService( + Headers.CurrentRedactedNames, + Array.appendAll(Object.values(RedactedOpenAiHeaders)) +) + /** * Creates an OpenAI client service with the given options. * @@ -232,25 +237,27 @@ export const make = Effect.fnUntraced( [body: typeof OpenAiSchema.Response.Type, response: HttpClientResponse.HttpClientResponse], AiError.AiError > => - Effect.flatMap(resolveHttpClient, (client) => - client.execute( - HttpClientRequest.post("/responses", { + resolveHttpClient.pipe( + Effect.flatMap((client) => + client.execute(HttpClientRequest.post("/responses", { body: HttpBody.jsonUnsafe(payload) - }) - ).pipe( - Effect.flatMap((response) => - decodeResponse(response).pipe( - Effect.map((body): [typeof OpenAiSchema.Response.Type, HttpClientResponse.HttpClientResponse] => [ - body, - response - ]) - ) - ), - Effect.catchTags({ - HttpClientError: (error) => Errors.mapHttpClientError(error, "createResponse"), - SchemaError: (error) => Effect.fail(Errors.mapSchemaError(error, "createResponse")) - }) - )) + })).pipe( + Effect.flatMap((response) => + decodeResponse(response).pipe( + Effect.map((body): [typeof OpenAiSchema.Response.Type, HttpClientResponse.HttpClientResponse] => [ + body, + response + ]) + ) + ), + Effect.catchTags({ + HttpClientError: (error) => Errors.mapHttpClientError(error, "createResponse"), + SchemaError: (error) => Effect.fail(Errors.mapSchemaError(error, "createResponse")) + }) + ) + ), + withRedactedHeaders + ) const buildResponseStream = ( response: HttpClientResponse.HttpClientResponse @@ -263,12 +270,14 @@ export const make = Effect.fnUntraced( Stream.pipeThroughChannel(Sse.decodeDataSchema(OpenAiSchema.ResponseStreamEvent)), Stream.takeUntil((event) => event.data.type === "response.completed" || - event.data.type === "response.incomplete" + event.data.type === "response.incomplete" || + event.data.type === "response.failed" ), Stream.map((event) => event.data), Stream.catchTags({ // TODO: handle SSE retries Retry: (error) => Stream.die(error), + SseError: (error) => Stream.fail(Errors.mapSseError(error, "createResponseStream")), HttpClientError: (error) => Stream.fromEffect(Errors.mapHttpClientError(error, "createResponseStream")), SchemaError: (error) => Stream.fail(Errors.mapSchemaError(error, "createResponseStream")) }) @@ -280,18 +289,20 @@ export const make = Effect.fnUntraced( Effect.contextWith((services) => { const socket = Context.getOrUndefined(services, OpenAiSocket) if (socket) return socket.createResponseStream(payload) - return Effect.flatMap(resolveHttpClient, (client) => - client.execute( - HttpClientRequest.post("/responses", { + return resolveHttpClient.pipe( + Effect.flatMap((client) => + client.execute(HttpClientRequest.post("/responses", { body: HttpBody.jsonUnsafe({ ...payload, stream: true }) - }) - ).pipe( - Effect.map(buildResponseStream), - Effect.catchTag( - "HttpClientError", - (error) => Errors.mapHttpClientError(error, "createResponseStream") + })).pipe( + Effect.map(buildResponseStream), + Effect.catchTag( + "HttpClientError", + (error) => Errors.mapHttpClientError(error, "createResponseStream") + ) ) - )) + ), + withRedactedHeaders + ) }) const decodeEmbedding = HttpClientResponse.schemaBodyJson(OpenAiSchema.CreateEmbeddingResponse) @@ -299,18 +310,20 @@ export const make = Effect.fnUntraced( const createEmbedding = ( payload: typeof OpenAiSchema.CreateEmbeddingRequest.Encoded ): Effect.Effect => - Effect.flatMap(resolveHttpClient, (client) => - client.execute( - HttpClientRequest.post("/embeddings", { + resolveHttpClient.pipe( + Effect.flatMap((client) => + client.execute(HttpClientRequest.post("/embeddings", { body: HttpBody.jsonUnsafe(payload) - }) - ).pipe( - Effect.flatMap(decodeEmbedding), - Effect.catchTags({ - HttpClientError: (error) => Errors.mapHttpClientError(error, "createEmbedding"), - SchemaError: (error) => Effect.fail(Errors.mapSchemaError(error, "createEmbedding")) - }) - )) + })).pipe( + Effect.flatMap(decodeEmbedding), + Effect.catchTags({ + HttpClientError: (error) => Errors.mapHttpClientError(error, "createEmbedding"), + SchemaError: (error) => Effect.fail(Errors.mapSchemaError(error, "createEmbedding")) + }) + ) + ), + withRedactedHeaders + ) return OpenAiClient.of({ client: httpClient, @@ -319,10 +332,7 @@ export const make = Effect.fnUntraced( createEmbedding }) }, - Effect.updateService( - Headers.CurrentRedactedNames, - Array.appendAll(Object.values(RedactedOpenAiHeaders)) - ) + withRedactedHeaders ) // ============================================================================= @@ -424,7 +434,7 @@ export const layerConfig = (options?: { /** * Response stream event emitted by the OpenAI Responses API. * - * @category Events + * @category models * @since 4.0.0 */ export type ResponseStreamEvent = typeof OpenAiSchema.ResponseStreamEvent.Type @@ -451,7 +461,7 @@ export type ResponseStreamEvent = typeof OpenAiSchema.ResponseStreamEvent.Type * @see {@link withWebSocketMode} for enabling WebSocket mode for one effect * @see {@link layerWebSocketMode} for providing WebSocket mode through a layer * - * @category Websocket mode + * @category services * @since 4.0.0 */ export class OpenAiSocket extends Context.Service { - done = e.type === "response.completed" || e.type === "response.incomplete" + done = e.type === "response.completed" || e.type === "response.incomplete" || e.type === "response.failed" return done }) ) @@ -642,7 +656,7 @@ const makeSocket = Effect.gen(function*() { const ErrorEvent = Schema.Struct({ type: Schema.Literal("error"), - status: Schema.Number.pipe( + status: Schema.Int.pipe( Schema.withDecodingDefault(Effect.succeed(500)) ), error: Schema.Struct({ @@ -683,7 +697,7 @@ const decodeEvent = Schema.decodeUnknownSync(Schema.fromJsonString(AllEvents)) * @see {@link layerWebSocketMode} for providing WebSocket mode through a layer * @see {@link OpenAiSocket} for direct access to the WebSocket-backed streaming service * - * @category Websocket mode + * @category providing services * @since 4.0.0 */ export const withWebSocketMode = ( @@ -720,7 +734,7 @@ export const withWebSocketMode = ( * * @see {@link withWebSocketMode} for enabling WebSocket mode around a single effect * - * @category Websocket mode + * @category layers * @since 4.0.0 */ export const layerWebSocketMode: Layer.Layer< diff --git a/repos/effect/packages/ai/openai/src/OpenAiClientGenerated.ts b/repos/effect/packages/ai/openai/src/OpenAiClientGenerated.ts index 1b2c9b01f5..6df83c43a4 100644 --- a/repos/effect/packages/ai/openai/src/OpenAiClientGenerated.ts +++ b/repos/effect/packages/ai/openai/src/OpenAiClientGenerated.ts @@ -74,6 +74,11 @@ const RedactedOpenAiHeaders = { OpenAiProject: "OpenAI-Project" } +const withRedactedHeaders = Effect.updateService( + Headers.CurrentRedactedNames, + Array.appendAll(Object.values(RedactedOpenAiHeaders)) +) + // ============================================================================= // Constructor // ============================================================================= @@ -124,10 +129,7 @@ export const make = Effect.fnUntraced( }) }) }, - Effect.updateService( - Headers.CurrentRedactedNames, - Array.appendAll(Object.values(RedactedOpenAiHeaders)) - ) + withRedactedHeaders ) // ============================================================================= diff --git a/repos/effect/packages/ai/openai/src/OpenAiConfig.ts b/repos/effect/packages/ai/openai/src/OpenAiConfig.ts index ea0b05cdef..37f18d7acc 100644 --- a/repos/effect/packages/ai/openai/src/OpenAiConfig.ts +++ b/repos/effect/packages/ai/openai/src/OpenAiConfig.ts @@ -49,7 +49,7 @@ export declare namespace OpenAiConfig { * Configuration values read by OpenAI provider operations when executing * requests. * - * @category models + * @category services * @since 4.0.0 */ export interface Service { diff --git a/repos/effect/packages/ai/openai/src/OpenAiError.ts b/repos/effect/packages/ai/openai/src/OpenAiError.ts index cb0ccc2dd3..5e2a96d605 100644 --- a/repos/effect/packages/ai/openai/src/OpenAiError.ts +++ b/repos/effect/packages/ai/openai/src/OpenAiError.ts @@ -68,7 +68,7 @@ declare module "effect/unstable/ai/AiError" { * from responses where the provider rejected the request because a limit was * reached. * - * @category configuration + * @category models * @since 4.0.0 */ export interface RateLimitErrorMetadata { @@ -86,7 +86,7 @@ declare module "effect/unstable/ai/AiError" { * Preserves provider error details for failures caused by exhausted account, * billing, or usage quota. * - * @category configuration + * @category models * @since 4.0.0 */ export interface QuotaExhaustedErrorMetadata { @@ -104,7 +104,7 @@ declare module "effect/unstable/ai/AiError" { * Preserves provider error details for failed API key, authorization, or * permission checks. * - * @category configuration + * @category models * @since 4.0.0 */ export interface AuthenticationErrorMetadata { @@ -122,7 +122,7 @@ declare module "effect/unstable/ai/AiError" { * Preserves provider error details when OpenAI rejects input or output because * it violates a content policy. * - * @category configuration + * @category models * @since 4.0.0 */ export interface ContentPolicyErrorMetadata { @@ -140,7 +140,7 @@ declare module "effect/unstable/ai/AiError" { * Preserves provider error details for malformed requests, unsupported * parameters, or other request validation failures reported by OpenAI. * - * @category configuration + * @category models * @since 4.0.0 */ export interface InvalidRequestErrorMetadata { @@ -158,7 +158,7 @@ declare module "effect/unstable/ai/AiError" { * Preserves provider error details for OpenAI-side failures such as transient * server errors. * - * @category configuration + * @category models * @since 4.0.0 */ export interface InternalProviderErrorMetadata { @@ -176,7 +176,7 @@ declare module "effect/unstable/ai/AiError" { * Preserves provider error details when an OpenAI response cannot be parsed or * validated as the expected output. * - * @category configuration + * @category models * @since 4.0.0 */ export interface InvalidOutputErrorMetadata { @@ -194,7 +194,7 @@ declare module "effect/unstable/ai/AiError" { * Preserves provider error details when OpenAI returns content that does not * satisfy the requested structured output schema. * - * @category configuration + * @category models * @since 4.0.0 */ export interface StructuredOutputErrorMetadata { @@ -212,7 +212,7 @@ declare module "effect/unstable/ai/AiError" { * Preserves provider error details when an unsupported schema failure is * associated with an OpenAI response. * - * @category configuration + * @category models * @since 4.0.0 */ export interface UnsupportedSchemaErrorMetadata { @@ -230,7 +230,7 @@ declare module "effect/unstable/ai/AiError" { * Preserves provider error details for OpenAI failures that do not map cleanly * to a more specific AI error category. * - * @category configuration + * @category models * @since 4.0.0 */ export interface UnknownErrorMetadata { diff --git a/repos/effect/packages/ai/openai/src/OpenAiLanguageModel.ts b/repos/effect/packages/ai/openai/src/OpenAiLanguageModel.ts index 7930d3d597..c78d3e2af6 100644 --- a/repos/effect/packages/ai/openai/src/OpenAiLanguageModel.ts +++ b/repos/effect/packages/ai/openai/src/OpenAiLanguageModel.ts @@ -19,6 +19,7 @@ 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" @@ -39,6 +40,8 @@ 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] @@ -127,7 +130,7 @@ declare module "effect/unstable/ai/Prompt" { /** * OpenAI-specific options for file prompt parts. * - * @category request + * @category models * @since 4.0.0 */ export interface FilePartOptions extends ProviderOptions { @@ -145,7 +148,7 @@ declare module "effect/unstable/ai/Prompt" { /** * OpenAI-specific options for reasoning prompt parts. * - * @category request + * @category models * @since 4.0.0 */ export interface ReasoningPartOptions extends ProviderOptions { @@ -169,7 +172,7 @@ declare module "effect/unstable/ai/Prompt" { /** * OpenAI-specific options for assistant tool-call prompt parts. * - * @category request + * @category models * @since 4.0.0 */ export interface ToolCallPartOptions extends ProviderOptions { @@ -195,7 +198,7 @@ declare module "effect/unstable/ai/Prompt" { /** * OpenAI-specific options for tool-result prompt parts. * - * @category request + * @category models * @since 4.0.0 */ export interface ToolResultPartOptions extends ProviderOptions { @@ -221,7 +224,7 @@ declare module "effect/unstable/ai/Prompt" { /** * OpenAI-specific options for text prompt parts. * - * @category request + * @category models * @since 4.0.0 */ export interface TextPartOptions extends ProviderOptions { @@ -249,7 +252,7 @@ declare module "effect/unstable/ai/Response" { /** * OpenAI metadata attached to a complete text response part. * - * @category response + * @category models * @since 4.0.0 */ export interface TextPartMetadata extends ProviderMetadata { @@ -281,7 +284,7 @@ declare module "effect/unstable/ai/Response" { /** * OpenAI metadata emitted when a streamed text part starts. * - * @category response + * @category models * @since 4.0.0 */ export interface TextStartPartMetadata extends ProviderMetadata { @@ -299,7 +302,7 @@ declare module "effect/unstable/ai/Response" { /** * OpenAI metadata emitted when a streamed text part ends. * - * @category response + * @category models * @since 4.0.0 */ export interface TextEndPartMetadata extends ProviderMetadata { @@ -321,7 +324,7 @@ declare module "effect/unstable/ai/Response" { /** * OpenAI metadata attached to a complete reasoning response part. * - * @category response + * @category models * @since 4.0.0 */ export interface ReasoningPartMetadata extends ProviderMetadata { @@ -343,7 +346,7 @@ declare module "effect/unstable/ai/Response" { /** * OpenAI metadata emitted when a streamed reasoning part starts. * - * @category response + * @category models * @since 4.0.0 */ export interface ReasoningStartPartMetadata extends ProviderMetadata { @@ -365,7 +368,7 @@ declare module "effect/unstable/ai/Response" { /** * OpenAI metadata emitted for a streamed reasoning delta. * - * @category response + * @category models * @since 4.0.0 */ export interface ReasoningDeltaPartMetadata extends ProviderMetadata { @@ -383,7 +386,7 @@ declare module "effect/unstable/ai/Response" { /** * OpenAI metadata emitted when a streamed reasoning part ends. * - * @category response + * @category models * @since 4.0.0 */ export interface ReasoningEndPartMetadata extends ProviderMetadata { @@ -405,7 +408,7 @@ declare module "effect/unstable/ai/Response" { /** * OpenAI metadata attached to tool-call response parts. * - * @category response + * @category models * @since 4.0.0 */ export interface ToolCallPartMetadata extends ProviderMetadata { @@ -423,7 +426,7 @@ declare module "effect/unstable/ai/Response" { /** * OpenAI metadata attached to document source citations. * - * @category response + * @category models * @since 4.0.0 */ export interface DocumentSourcePartMetadata extends ProviderMetadata { @@ -479,7 +482,7 @@ declare module "effect/unstable/ai/Response" { /** * OpenAI metadata attached to URL source citations. * - * @category response + * @category models * @since 4.0.0 */ export interface UrlSourcePartMetadata extends ProviderMetadata { @@ -505,7 +508,7 @@ declare module "effect/unstable/ai/Response" { /** * OpenAI metadata attached to finish response parts. * - * @category response + * @category models * @since 4.0.0 */ export interface FinishPartMetadata extends ProviderMetadata { @@ -782,7 +785,7 @@ const prepareMessages = Effect.fnUntraced( Tool.isProviderDefined(tool) && tool.name === "OpenAiCodeInterpreter" ) const shellTool = options.tools.find((tool): tool is ReturnType => - Tool.isProviderDefined(tool) && tool.name === "OpenAiFunctionShell" + Tool.isProviderDefined(tool) && tool.name === "OpenAiShell" ) const localShellTool = options.tools.find((tool): tool is ReturnType => Tool.isProviderDefined(tool) && tool.name === "OpenAiLocalShell" @@ -816,7 +819,7 @@ const prepareMessages = Effect.fnUntraced( case "system": { messages.push({ role: getSystemMessageMode(config.model as string), - content: message.content + content: [{ type: "input_text", text: message.content }] }) break } @@ -885,7 +888,8 @@ const prepareMessages = Effect.fnUntraced( } case "assistant": { - const reasoningMessages: Record> = {} + const reasoningMessages: Record> = Object + .create(null) for (const part of message.content) { switch (part.type) { @@ -1129,6 +1133,7 @@ const prepareMessages = Effect.fnUntraced( call_id: part.id, ...(part.result as any) }) + continue } if (Predicate.isNotUndefined(shellTool) && toolName === "shell") { @@ -1139,6 +1144,7 @@ const prepareMessages = Effect.fnUntraced( output: part.result as any, ...(Predicate.isNotNull(status) ? { status } : {}) }) + continue } if (Predicate.isNotUndefined(localShellTool) && toolName === "local_shell") { @@ -1149,6 +1155,7 @@ const prepareMessages = Effect.fnUntraced( output: part.result as any, ...(Predicate.isNotNull(status) ? { status } : {}) }) + continue } messages.push({ @@ -1614,7 +1621,9 @@ const makeResponse = Effect.fnUntraced( type: "tool-call", id: part.id, name: toolName, - params: {}, + params: webSearchTool?.name === "OpenAiWebSearchPreview" + ? {} + : { action: part.action }, providerExecuted: true }) parts.push({ @@ -1682,7 +1691,7 @@ const makeStreamResponse = Effect.fnUntraced( } // Track active reasoning items with state machine for proper concluding logic - const activeReasoning: Record = {} + const activeReasoning: Record = Object.create(null) const getOrCreateReasoningPart = ( itemId: string, @@ -1753,8 +1762,7 @@ const makeStreamResponse = Effect.fnUntraced( } case "response.completed": - case "response.incomplete": - case "response.failed": { + case "response.incomplete": { parts.push({ type: "finish", reason: InternalUtilities.resolveFinishReason( @@ -1768,6 +1776,20 @@ const makeStreamResponse = Effect.fnUntraced( break } + case "response.failed": { + if (event.response.error) { + parts.push({ type: "error", error: event.response.error }) + } + parts.push({ + type: "finish", + reason: "error", + usage: getUsage(event.response.usage), + response: buildHttpResponseDetails(response), + ...toServiceTier(event.response.service_tier) + }) + break + } + case "response.output_item.added": { switch (event.item.type) { case "apply_patch_call": { @@ -1942,6 +1964,9 @@ const makeStreamResponse = Effect.fnUntraced( id: event.item.id, name: toolName } + if (webSearchTool?.name === "OpenAiWebSearch") { + break + } parts.push({ type: "tool-params-start", id: event.item.id, @@ -2255,6 +2280,15 @@ const makeStreamResponse = Effect.fnUntraced( const toolName = toolNameMapper.getCustomName( webSearchTool?.name ?? "web_search" ) + if (webSearchTool?.name === "OpenAiWebSearch") { + parts.push({ + type: "tool-call", + id: event.item.id, + name: toolName, + params: { action: event.item.action }, + providerExecuted: true + }) + } parts.push({ type: "tool-result", id: event.item.id, @@ -3053,15 +3087,16 @@ const getUsage = (usage: OpenAiSchema.ResponseUsage | null | undefined): Respons const inputTokens = usage.input_tokens const outputTokens = usage.output_tokens - const cachedTokens = getUsageTokenDetail(usage.input_tokens_details, "cached_tokens") - const reasoningTokens = getUsageTokenDetail(usage.output_tokens_details, "reasoning_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: undefined + cacheWrite: cacheWriteTokens }, outputTokens: { total: outputTokens, @@ -3092,8 +3127,8 @@ const toServiceTier = (value: string | undefined): { } } -const getUsageTokenDetail = (details: unknown, key: string): number => - Predicate.hasProperty(details, key) && typeof details[key] === "number" ? details[key] : 0 +const getUsageTokenDetail = (details: unknown, key: string): number | undefined => + Predicate.hasProperty(details, key) && typeof details[key] === "number" ? details[key] : undefined const transformToolCallParams = Effect.fnUntraced(function*>( tools: Tools, @@ -3126,7 +3161,7 @@ const transformToolCallParams = Effect.fnUntraced(function* - & Telemetry.AttributesWithPrefix + & Telemetry.AttributesWithPrefix > /** diff --git a/repos/effect/packages/ai/openai/src/internal/errors.ts b/repos/effect/packages/ai/openai/src/internal/errors.ts index a1be0c9633..19bd28e5cc 100644 --- a/repos/effect/packages/ai/openai/src/internal/errors.ts +++ b/repos/effect/packages/ai/openai/src/internal/errors.ts @@ -8,6 +8,7 @@ import * as Redactable from "effect/Redactable" import * as Schema from "effect/Schema" import * as AiError from "effect/unstable/ai/AiError" import type * as Response from "effect/unstable/ai/Response" +import type * as Sse from "effect/unstable/encoding/Sse" import type * as HttpClientError from "effect/unstable/http/HttpClientError" import type * as HttpClientRequest from "effect/unstable/http/HttpClientRequest" import type * as HttpClientResponse from "effect/unstable/http/HttpClientResponse" @@ -42,6 +43,17 @@ export const mapSchemaError = dual< reason: AiError.InvalidOutputError.fromSchemaError(error) })) +/** @internal */ +export const mapSseError = dual< + (method: string) => (error: Sse.SseError) => AiError.AiError, + (error: Sse.SseError, method: string) => AiError.AiError +>(2, (error, method) => + AiError.make({ + module: "OpenAiClient", + method, + reason: new AiError.InvalidOutputError({ description: error.message }) + })) + /** @internal */ export const mapHttpClientError = dual< (method: string) => (error: HttpClientError.HttpClientError) => Effect.Effect, diff --git a/repos/effect/packages/ai/openai/src/internal/utilities.ts b/repos/effect/packages/ai/openai/src/internal/utilities.ts index 1aca7dae42..26cf670c31 100644 --- a/repos/effect/packages/ai/openai/src/internal/utilities.ts +++ b/repos/effect/packages/ai/openai/src/internal/utilities.ts @@ -19,7 +19,7 @@ export const resolveFinishReason = ( if (finishReason == null) { return hasToolCalls ? "tool-calls" : "stop" } - const reason = finishReasonMap[finishReason] + const reason = Object.hasOwn(finishReasonMap, finishReason) ? finishReasonMap[finishReason] : undefined if (reason == null) { return hasToolCalls ? "tool-calls" : "unknown" } diff --git a/repos/effect/packages/ai/openai/test/OpenAiClient.test.ts b/repos/effect/packages/ai/openai/test/OpenAiClient.test.ts index d9069fd5af..9c5471c99d 100644 --- a/repos/effect/packages/ai/openai/test/OpenAiClient.test.ts +++ b/repos/effect/packages/ai/openai/test/OpenAiClient.test.ts @@ -1,251 +1,113 @@ +import type { OpenAiSchema } from "@effect/ai-openai" import type * as Generated from "@effect/ai-openai/Generated" import * as Errors from "@effect/ai-openai/internal/errors" import * as OpenAiClient from "@effect/ai-openai/OpenAiClient" import * as OpenAiClientGenerated from "@effect/ai-openai/OpenAiClientGenerated" import * as OpenAiConfig from "@effect/ai-openai/OpenAiConfig" import { assert, describe, it } from "@effect/vitest" -import { Config, ConfigProvider, Effect, Layer, Redacted, Schema, Stream } from "effect" -import type * as AiError from "effect/unstable/ai/AiError" +import { Config, ConfigProvider, Context, Effect, Layer, Redacted, Schema, Stream } from "effect" import * as HttpClient from "effect/unstable/http/HttpClient" import * as HttpClientError from "effect/unstable/http/HttpClientError" import * as HttpClientRequest from "effect/unstable/http/HttpClientRequest" import * as HttpClientResponse from "effect/unstable/http/HttpClientResponse" - -// ============================================================================= -// Mock Helpers -// ============================================================================= - -const makeMockResponse = (options: { - readonly status: number - readonly body: unknown - readonly request?: HttpClientRequest.HttpClientRequest -}): HttpClientResponse.HttpClientResponse => { - // Always use a plain request for the response to avoid Redacted headers in error contexts - const request = HttpClientRequest.get(options.request?.url ?? "/") - const json = JSON.stringify(options.body) - return HttpClientResponse.fromWeb( - request, - new Response(json, { - status: options.status, - headers: { "content-type": "application/json" } - }) - ) -} - -const makeMockStreamResponse = (options: { - readonly events: ReadonlyArray - readonly request?: HttpClientRequest.HttpClientRequest -}): HttpClientResponse.HttpClientResponse => { - const request = HttpClientRequest.get(options.request?.url ?? "/") - const body = options.events.map((event) => `data: ${JSON.stringify(event)}\n\n`).join("") - return HttpClientResponse.fromWeb( - request, - new Response(body, { - status: 200, - headers: { "content-type": "text/event-stream" } - }) - ) -} - -const makeMockHttpClient = ( - handler: ( - request: HttpClientRequest.HttpClientRequest - ) => Effect.Effect -): HttpClient.HttpClient => - HttpClient.makeWith( - (effect) => - Effect.flatMap(effect, handler) as Effect.Effect< - HttpClientResponse.HttpClientResponse, - HttpClientError.HttpClientError, - never - >, - Effect.succeed - ) - -const makeResponseBody = ( - overrides: Partial = {} -): typeof Generated.Response.Encoded => ({ - id: "resp_test123", - object: "response", - created_at: 1, - model: "gpt-4o-mini", - status: "completed", - output: [], - metadata: null, - temperature: null, - top_p: null, - tools: [], - tool_choice: "auto", - error: null, - incomplete_details: null, - instructions: null, - parallel_tool_calls: false, - ...overrides -}) - -// ============================================================================= -// Tests -// ============================================================================= +import * as Socket from "effect/unstable/socket/Socket" +import { WS } from "vitest-websocket-mock" describe("OpenAiClient", () => { describe("make", () => { it.effect("sets Bearer token from apiKey", () => Effect.gen(function*() { - let capturedRequest: HttpClientRequest.HttpClientRequest | undefined - const mockClient = makeMockHttpClient((request) => { - capturedRequest = request - return Effect.succeed(makeMockResponse({ status: 200, body: {}, request })) - }) - - const client = yield* OpenAiClient.make({ - apiKey: Redacted.make("sk-test-12345") - }).pipe(Effect.provide(Layer.succeed(HttpClient.HttpClient, mockClient))) - - // Call method and ignore response parsing errors - we only care about the request - yield* client.createResponse({ model: "gpt-4o", input: "test" }).pipe(Effect.ignore) + const client = yield* OpenAiClient.OpenAiClient + yield* client.createResponse({ model: "gpt-4o", input: "test" }) - assert.isDefined(capturedRequest) - const authHeader = capturedRequest!.headers["authorization"] - assert.strictEqual(authHeader, "Bearer sk-test-12345") - })) + const requests = yield* MockHttpClient.requests + assert.strictEqual(requests[0]?.headers["authorization"], "Bearer sk-test-12345") + }).pipe(Effect.provide(makeTestLayer({ + apiKey: Redacted.make("sk-test-12345") + })))) it.effect("prepends default URL", () => Effect.gen(function*() { - let capturedRequest: HttpClientRequest.HttpClientRequest | undefined - const mockClient = makeMockHttpClient((request) => { - capturedRequest = request - return Effect.succeed(makeMockResponse({ status: 200, body: {}, request })) - }) - - const client = yield* OpenAiClient.make({ - apiKey: Redacted.make("test-key") - }).pipe(Effect.provide(Layer.succeed(HttpClient.HttpClient, mockClient))) - - yield* client.createResponse({ model: "gpt-4o", input: "test" }).pipe(Effect.ignore) + const client = yield* OpenAiClient.OpenAiClient + yield* client.createResponse({ model: "gpt-4o", input: "test" }) - assert.isDefined(capturedRequest) - assert.isTrue(capturedRequest!.url.startsWith("https://api.openai.com/v1")) - })) + const requests = yield* MockHttpClient.requests + assert.isTrue(requests[0]?.url.startsWith("https://api.openai.com/v1")) + }).pipe(Effect.provide(makeTestLayer()))) it.effect("uses custom apiUrl when provided", () => Effect.gen(function*() { - let capturedRequest: HttpClientRequest.HttpClientRequest | undefined - const mockClient = makeMockHttpClient((request) => { - capturedRequest = request - return Effect.succeed(makeMockResponse({ status: 200, body: {}, request })) - }) - - const client = yield* OpenAiClient.make({ - apiKey: Redacted.make("test-key"), - apiUrl: "https://custom.api.com/v2" - }).pipe(Effect.provide(Layer.succeed(HttpClient.HttpClient, mockClient))) - - yield* client.createResponse({ model: "gpt-4o", input: "test" }).pipe(Effect.ignore) + const client = yield* OpenAiClient.OpenAiClient + yield* client.createResponse({ model: "gpt-4o", input: "test" }) - assert.isDefined(capturedRequest) - assert.isTrue(capturedRequest!.url.startsWith("https://custom.api.com/v2")) - })) + const requests = yield* MockHttpClient.requests + assert.isTrue(requests[0]?.url.startsWith("https://custom.api.com/v2")) + }).pipe(Effect.provide(makeTestLayer({ + apiKey: Redacted.make("test-key"), + apiUrl: "https://custom.api.com/v2" + })))) it.effect("sets OpenAI-Organization header when organizationId provided", () => Effect.gen(function*() { - let capturedRequest: HttpClientRequest.HttpClientRequest | undefined - const mockClient = makeMockHttpClient((request) => { - capturedRequest = request - return Effect.succeed(makeMockResponse({ status: 200, body: {}, request })) - }) - - const client = yield* OpenAiClient.make({ - apiKey: Redacted.make("test-key"), - organizationId: Redacted.make("org-12345") - }).pipe(Effect.provide(Layer.succeed(HttpClient.HttpClient, mockClient))) - - yield* client.createResponse({ model: "gpt-4o", input: "test" }).pipe(Effect.ignore) + const client = yield* OpenAiClient.OpenAiClient + yield* client.createResponse({ model: "gpt-4o", input: "test" }) - assert.isDefined(capturedRequest) - assert.strictEqual(capturedRequest!.headers["openai-organization"], "org-12345") - })) + const requests = yield* MockHttpClient.requests + assert.strictEqual(requests[0]?.headers["openai-organization"], "org-12345") + }).pipe(Effect.provide(makeTestLayer({ + apiKey: Redacted.make("test-key"), + organizationId: Redacted.make("org-12345") + })))) it.effect("sets OpenAI-Project header when projectId provided", () => Effect.gen(function*() { - let capturedRequest: HttpClientRequest.HttpClientRequest | undefined - const mockClient = makeMockHttpClient((request) => { - capturedRequest = request - return Effect.succeed(makeMockResponse({ status: 200, body: {}, request })) - }) - - const client = yield* OpenAiClient.make({ - apiKey: Redacted.make("test-key"), - projectId: Redacted.make("proj-67890") - }).pipe(Effect.provide(Layer.succeed(HttpClient.HttpClient, mockClient))) - - yield* client.createResponse({ model: "gpt-4o", input: "test" }).pipe(Effect.ignore) - - assert.isDefined(capturedRequest) - assert.strictEqual(capturedRequest!.headers["openai-project"], "proj-67890") - })) + const client = yield* OpenAiClient.OpenAiClient + yield* client.createResponse({ model: "gpt-4o", input: "test" }) - it.effect("applies transformClient option", () => - Effect.gen(function*() { - let transformApplied = false - const mockClient = makeMockHttpClient((request) => - Effect.succeed(makeMockResponse({ status: 200, body: {}, request })) - ) + 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") + })))) - const client = yield* OpenAiClient.make({ - apiKey: Redacted.make("test-key"), - transformClient: (client) => { - transformApplied = true - return client - } - }).pipe(Effect.provide(Layer.succeed(HttpClient.HttpClient, mockClient))) - - yield* client.createResponse({ model: "gpt-4o", input: "test" }).pipe(Effect.ignore) + it.effect("applies transformClient option", () => { + let transformApplied = false + return Effect.gen(function*() { + const client = yield* OpenAiClient.OpenAiClient + yield* client.createResponse({ model: "gpt-4o", input: "test" }) assert.isTrue(transformApplied) - })) + }).pipe(Effect.provide(makeTestLayer({ + apiKey: Redacted.make("test-key"), + transformClient: (client) => { + transformApplied = true + return client + } + }))) + }) it.effect("exposes transformed HttpClient via client field", () => Effect.gen(function*() { - let capturedRequest: HttpClientRequest.HttpClientRequest | undefined - const mockClient = makeMockHttpClient((request) => { - capturedRequest = request - return Effect.succeed(makeMockResponse({ status: 200, body: {}, request })) - }) - - const client = yield* OpenAiClient.make({ - apiKey: Redacted.make("test-key"), - transformClient: (client) => - client.pipe(HttpClient.mapRequest(HttpClientRequest.setHeader("x-client-field", "enabled"))) - }).pipe(Effect.provide(Layer.succeed(HttpClient.HttpClient, mockClient))) - - yield* client.client.execute(HttpClientRequest.get("/responses")).pipe(Effect.ignore) - - assert.isDefined(capturedRequest) - assert.isTrue(capturedRequest!.url.startsWith("https://api.openai.com/v1")) - assert.strictEqual(capturedRequest!.headers["authorization"], "Bearer test-key") - assert.strictEqual(capturedRequest!.headers["x-client-field"], "enabled") - })) - - it.effect("applies OpenAiConfig transformClient after options transformClient", () => - Effect.gen(function*() { - let optionsTransformApplied = false - let configTransformApplied = false - let capturedRequest: HttpClientRequest.HttpClientRequest | undefined - - const mockClient = makeMockHttpClient((request) => { - capturedRequest = request - return Effect.succeed(makeMockResponse({ status: 200, body: makeResponseBody(), request })) - }) - - const client = yield* OpenAiClient.make({ - apiKey: Redacted.make("test-key"), - transformClient: (client) => { - optionsTransformApplied = true - return client.pipe( - HttpClient.mapRequest(HttpClientRequest.setHeader("x-openai-transform", "options")) - ) - } - }).pipe(Effect.provide(Layer.succeed(HttpClient.HttpClient, mockClient))) + const client = yield* OpenAiClient.OpenAiClient + yield* client.client.execute(HttpClientRequest.get("/responses")) + + const requests = yield* MockHttpClient.requests + const request = requests[0] + assert.isTrue(request?.url.startsWith("https://api.openai.com/v1")) + assert.strictEqual(request?.headers["authorization"], "Bearer test-key") + assert.strictEqual(request?.headers["x-client-field"], "enabled") + }).pipe(Effect.provide(makeTestLayer({ + apiKey: Redacted.make("test-key"), + transformClient: (client) => + client.pipe(HttpClient.mapRequest(HttpClientRequest.setHeader("x-client-field", "enabled"))) + })))) + + it.effect("applies OpenAiConfig transformClient after options transformClient", () => { + let optionsTransformApplied = false + let configTransformApplied = false + return Effect.gen(function*() { + const client = yield* OpenAiClient.OpenAiClient yield* client.createResponse({ model: "gpt-4o", input: "test" @@ -258,87 +120,58 @@ describe("OpenAiClient", () => { }) ) + const requests = yield* MockHttpClient.requests assert.isTrue(optionsTransformApplied) assert.isTrue(configTransformApplied) - assert.isDefined(capturedRequest) - assert.strictEqual(capturedRequest!.headers["x-openai-transform"], "config") - })) + assert.strictEqual(requests[0]?.headers["x-openai-transform"], "config") + }).pipe(Effect.provide(makeTestLayer({ + apiKey: Redacted.make("test-key"), + transformClient: (client) => { + optionsTransformApplied = true + return client.pipe( + HttpClient.mapRequest(HttpClientRequest.setHeader("x-openai-transform", "options")) + ) + } + }))) + }) }) describe("OpenAiClientGenerated", () => { it.effect("sets Bearer token from apiKey", () => Effect.gen(function*() { - let capturedRequest: HttpClientRequest.HttpClientRequest | undefined - const mockClient = makeMockHttpClient((request) => { - capturedRequest = request - return Effect.succeed(makeMockResponse({ status: 200, body: makeResponseBody(), request })) - }) - - const client = yield* OpenAiClientGenerated.make({ - apiKey: Redacted.make("sk-generated-test") - }).pipe(Effect.provide(Layer.succeed(HttpClient.HttpClient, mockClient))) - + const client = yield* OpenAiClientGenerated.OpenAiClientGenerated yield* client.createResponse({ - payload: { - model: "gpt-4o", - input: "test" - } + payload: { model: "gpt-4o", input: "test" } }) - assert.isDefined(capturedRequest) - assert.strictEqual(capturedRequest!.headers["authorization"], "Bearer sk-generated-test") - })) + const requests = yield* MockHttpClient.requests + assert.strictEqual(requests[0]?.headers["authorization"], "Bearer sk-generated-test") + }).pipe(Effect.provide(makeGeneratedTestLayer({ + apiKey: Redacted.make("sk-generated-test") + })))) it.effect("prepends custom apiUrl", () => Effect.gen(function*() { - let capturedRequest: HttpClientRequest.HttpClientRequest | undefined - const mockClient = makeMockHttpClient((request) => { - capturedRequest = request - return Effect.succeed(makeMockResponse({ status: 200, body: makeResponseBody(), request })) - }) - - const client = yield* OpenAiClientGenerated.make({ - apiKey: Redacted.make("test-key"), - apiUrl: "https://generated.example.test/v2" - }).pipe(Effect.provide(Layer.succeed(HttpClient.HttpClient, mockClient))) - + const client = yield* OpenAiClientGenerated.OpenAiClientGenerated yield* client.createResponse({ - payload: { - model: "gpt-4o", - input: "test" - } + payload: { model: "gpt-4o", input: "test" } }) - assert.isDefined(capturedRequest) - assert.isTrue(capturedRequest!.url.startsWith("https://generated.example.test/v2")) - })) + 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", () => - Effect.gen(function*() { - let optionsTransformApplied = false - let configTransformApplied = false - let capturedRequest: HttpClientRequest.HttpClientRequest | undefined - - const mockClient = makeMockHttpClient((request) => { - capturedRequest = request - return Effect.succeed(makeMockResponse({ status: 200, body: makeResponseBody(), request })) - }) - - const client = yield* OpenAiClientGenerated.make({ - apiKey: Redacted.make("test-key"), - transformClient: (client) => { - optionsTransformApplied = true - return client.pipe( - HttpClient.mapRequest(HttpClientRequest.setHeader("x-openai-transform", "options")) - ) - } - }).pipe(Effect.provide(Layer.succeed(HttpClient.HttpClient, mockClient))) + it.effect("applies OpenAiConfig transformClient after options transformClient", () => { + let optionsTransformApplied = false + let configTransformApplied = false + return Effect.gen(function*() { + const client = yield* OpenAiClientGenerated.OpenAiClientGenerated yield* client.createResponse({ - payload: { - model: "gpt-4o", - input: "test" - } + payload: { model: "gpt-4o", input: "test" } }).pipe( OpenAiConfig.withClientTransform((client) => { configTransformApplied = true @@ -348,40 +181,30 @@ describe("OpenAiClient", () => { }) ) + const requests = yield* MockHttpClient.requests assert.isTrue(optionsTransformApplied) assert.isTrue(configTransformApplied) - assert.isDefined(capturedRequest) - assert.strictEqual(capturedRequest!.headers["x-openai-transform"], "config") - })) + assert.strictEqual(requests[0]?.headers["x-openai-transform"], "config") + }).pipe(Effect.provide(makeGeneratedTestLayer({ + apiKey: Redacted.make("test-key"), + transformClient: (client) => { + optionsTransformApplied = true + return client.pipe( + HttpClient.mapRequest(HttpClientRequest.setHeader("x-openai-transform", "options")) + ) + } + }))) + }) }) describe("layer", () => { - it.effect("creates working service", () => { - const HttpClientLayer = Layer.succeed( - HttpClient.HttpClient, - makeMockHttpClient(() => Effect.succeed(makeMockResponse({ status: 200, body: {} }))) - ) - - const MainLayer = OpenAiClient.layer({ - apiKey: Redacted.make("test-key") - }).pipe(Layer.provide(HttpClientLayer)) - - return Effect.gen(function*() { + it.effect("creates working service", () => + Effect.gen(function*() { const client = yield* OpenAiClient.OpenAiClient assert.isNotNull(client.client) - }).pipe(Effect.provide(MainLayer)) - }) + }).pipe(Effect.provide(makeTestLayer()))) it.effect("layerConfig loads from Config", () => { - let capturedRequest: HttpClientRequest.HttpClientRequest | undefined - const HttpClientLayer = Layer.succeed( - HttpClient.HttpClient, - makeMockHttpClient((request) => { - capturedRequest = request - return Effect.succeed(makeMockResponse({ status: 200, body: {}, request })) - }) - ) - const configProvider = ConfigProvider.fromEnv({ env: { MY_API_KEY: "sk-config-key", @@ -389,45 +212,51 @@ describe("OpenAiClient", () => { } }) - // Use explicit config values to test the layerConfig mechanism - // Provide explicit configs that won't fail for optional fields - const MainLayer = OpenAiClient.layerConfig({ - apiKey: Config.redacted("MY_API_KEY"), - apiUrl: Config.string("MY_API_URL") - }).pipe( - Layer.provide(HttpClientLayer), - Layer.provide(ConfigProvider.layer(configProvider)) - ) - return Effect.gen(function*() { const client = yield* OpenAiClient.OpenAiClient - yield* client.createResponse({ model: "gpt-4o", input: "test" }).pipe(Effect.ignore) + yield* client.createResponse({ model: "gpt-4o", input: "test" }) - assert.isDefined(capturedRequest) - assert.strictEqual(capturedRequest!.headers["authorization"], "Bearer sk-config-key") - assert.isTrue(capturedRequest!.url.startsWith("https://config.api.com/v1")) - }).pipe(Effect.provide(MainLayer)) + const requests = yield* MockHttpClient.requests + assert.strictEqual(requests[0]?.headers["authorization"], "Bearer sk-config-key") + assert.isTrue(requests[0]?.url.startsWith("https://config.api.com/v1")) + }).pipe(Effect.provide(makeConfigTestLayer(configProvider))) }) }) + describe("request behavior", () => { + it.effect("redacts OpenAI-specific headers in AI error context", () => + Effect.gen(function*() { + const client = yield* OpenAiClient.OpenAiClient + const result = yield* client.createResponse({ + model: "gpt-4o", + input: "test" + }).pipe(Effect.flip) + + assert.strictEqual(result.reason._tag, "InvalidRequestError") + if (result.reason._tag !== "InvalidRequestError" || result.reason.http === undefined) { + return yield* Effect.die(new Error("Expected InvalidRequestError with HTTP context")) + } + const headers = result.reason.http.request.headers + assert.strictEqual(String(headers["authorization"]), "") + assert.strictEqual(String(headers["openai-organization"]), "") + assert.strictEqual(String(headers["openai-project"]), "") + }).pipe(Effect.provide(makeTestLayer({ + apiKey: Redacted.make("test-key"), + organizationId: Redacted.make("org-secret"), + projectId: Redacted.make("proj-secret") + }, { + _tag: "Json", + status: 400, + body: { error: { message: "Bad request" } } + })))) + }) + describe("error mapping", () => { it.effect("maps TransportError to NetworkError reason", () => Effect.gen(function*() { - const mockClient = makeMockHttpClient(() => - Effect.fail( - new HttpClientError.HttpClientError({ - reason: new HttpClientError.TransportError({ - request: HttpClientRequest.get("/"), - cause: new Error("Connection refused") - }) - }) - ) - ) - const client = yield* OpenAiClient.make({ apiKey: Redacted.make("test-key") - }).pipe(Effect.provide(Layer.succeed(HttpClient.HttpClient, mockClient))) - + }) const result = yield* client.createResponse({ model: "gpt-4o", input: "test" }).pipe( Effect.flip ) @@ -436,129 +265,88 @@ describe("OpenAiClient", () => { assert.strictEqual(result.module, "OpenAiClient") assert.strictEqual(result.method, "createResponse") assert.strictEqual(result.reason._tag, "NetworkError") - })) + }).pipe(Effect.provide(Layer.succeed(HttpClient.HttpClient, makeTransportErrorHttpClient())))) it.effect("maps 400 status to InvalidRequestError reason", () => Effect.gen(function*() { - const mockClient = makeMockHttpClient((request) => - Effect.succeed(makeMockResponse({ - status: 400, - body: { error: { message: "Bad request" } }, - request - })) - ) - - const client = yield* OpenAiClient.make({ - apiKey: Redacted.make("test-key") - }).pipe(Effect.provide(Layer.succeed(HttpClient.HttpClient, mockClient))) - - const result = yield* client.createResponse({ model: "gpt-4o", input: "test" }).pipe( - Effect.flip - ) + const client = yield* OpenAiClient.OpenAiClient + const result = yield* client.createResponse({ model: "gpt-4o", input: "test" }).pipe(Effect.flip) assert.strictEqual(result._tag, "AiError") assert.strictEqual(result.module, "OpenAiClient") assert.strictEqual(result.method, "createResponse") assert.strictEqual(result.reason._tag, "InvalidRequestError") - })) + }).pipe(Effect.provide(makeTestLayer(undefined, { + _tag: "Json", + status: 400, + body: { error: { message: "Bad request" } } + })))) it.effect("maps 401 status to AuthenticationError reason", () => Effect.gen(function*() { - const mockClient = makeMockHttpClient((request) => - Effect.succeed(makeMockResponse({ - status: 401, - body: { error: { message: "Invalid API key" } }, - request - })) - ) - - const client = yield* OpenAiClient.make({ - apiKey: Redacted.make("test-key") - }).pipe(Effect.provide(Layer.succeed(HttpClient.HttpClient, mockClient))) - - const result = yield* client.createResponse({ model: "gpt-4o", input: "test" }).pipe( - Effect.flip - ) + const client = yield* OpenAiClient.OpenAiClient + const result = yield* client.createResponse({ model: "gpt-4o", input: "test" }).pipe(Effect.flip) assert.strictEqual(result._tag, "AiError") assert.strictEqual(result.reason._tag, "AuthenticationError") - assert.strictEqual((result.reason as AiError.AuthenticationError).kind, "InvalidKey") - })) + if (result.reason._tag === "AuthenticationError") { + assert.strictEqual(result.reason.kind, "InvalidKey") + } + }).pipe(Effect.provide(makeTestLayer(undefined, { + _tag: "Json", + status: 401, + body: { error: { message: "Invalid API key" } } + })))) it.effect("maps 403 status to AuthenticationError with InsufficientPermissions", () => Effect.gen(function*() { - const mockClient = makeMockHttpClient((request) => - Effect.succeed(makeMockResponse({ - status: 403, - body: { error: { message: "Access denied" } }, - request - })) - ) - - const client = yield* OpenAiClient.make({ - apiKey: Redacted.make("test-key") - }).pipe(Effect.provide(Layer.succeed(HttpClient.HttpClient, mockClient))) - - const result = yield* client.createResponse({ model: "gpt-4o", input: "test" }).pipe( - Effect.flip - ) + const client = yield* OpenAiClient.OpenAiClient + const result = yield* client.createResponse({ model: "gpt-4o", input: "test" }).pipe(Effect.flip) assert.strictEqual(result._tag, "AiError") assert.strictEqual(result.reason._tag, "AuthenticationError") - assert.strictEqual((result.reason as AiError.AuthenticationError).kind, "InsufficientPermissions") - })) + if (result.reason._tag === "AuthenticationError") { + assert.strictEqual(result.reason.kind, "InsufficientPermissions") + } + }).pipe(Effect.provide(makeTestLayer(undefined, { + _tag: "Json", + status: 403, + body: { error: { message: "Access denied" } } + })))) it.effect("maps 429 status to RateLimitError reason", () => Effect.gen(function*() { - const mockClient = makeMockHttpClient((request) => - Effect.succeed(makeMockResponse({ - status: 429, - body: { error: { message: "Rate limit exceeded", type: "rate_limit_error", code: null } }, - request - })) - ) - - const client = yield* OpenAiClient.make({ - apiKey: Redacted.make("test-key") - }).pipe(Effect.provide(Layer.succeed(HttpClient.HttpClient, mockClient))) - - const result = yield* client.createResponse({ model: "gpt-4o", input: "test" }).pipe( - Effect.flip - ) + const client = yield* OpenAiClient.OpenAiClient + const result = yield* client.createResponse({ model: "gpt-4o", input: "test" }).pipe(Effect.flip) assert.strictEqual(result._tag, "AiError") assert.strictEqual(result.reason._tag, "RateLimitError") assert.isTrue(result.isRetryable) - })) + }).pipe(Effect.provide(makeTestLayer(undefined, { + _tag: "Json", + status: 429, + body: { error: { message: "Rate limit exceeded", type: "rate_limit_error", code: null } } + })))) it.effect("maps 429 with insufficient_quota code to QuotaExhaustedError", () => Effect.gen(function*() { - const mockClient = makeMockHttpClient((request) => - Effect.succeed(makeMockResponse({ - status: 429, - body: { - error: { - message: "You exceeded your current quota", - type: "insufficient_quota", - code: "insufficient_quota" - } - }, - request - })) - ) - - const client = yield* OpenAiClient.make({ - apiKey: Redacted.make("test-key") - }).pipe(Effect.provide(Layer.succeed(HttpClient.HttpClient, mockClient))) - - const result = yield* client.createResponse({ model: "gpt-4o", input: "test" }).pipe( - Effect.flip - ) + const client = yield* OpenAiClient.OpenAiClient + const result = yield* client.createResponse({ model: "gpt-4o", input: "test" }).pipe(Effect.flip) assert.strictEqual(result._tag, "AiError") assert.strictEqual(result.reason._tag, "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("mapStatusCodeToReason detects insufficient_quota as QuotaExhaustedError", () => { const http = { @@ -600,66 +388,36 @@ describe("OpenAiClient", () => { it.effect("maps 5xx status to InternalProviderError reason", () => Effect.gen(function*() { - const mockClient = makeMockHttpClient((request) => - Effect.succeed(makeMockResponse({ - status: 500, - body: { error: { message: "Internal server error" } }, - request - })) - ) - - const client = yield* OpenAiClient.make({ - apiKey: Redacted.make("test-key") - }).pipe(Effect.provide(Layer.succeed(HttpClient.HttpClient, mockClient))) - - const result = yield* client.createResponse({ model: "gpt-4o", input: "test" }).pipe( - Effect.flip - ) + const client = yield* OpenAiClient.OpenAiClient + const result = yield* client.createResponse({ model: "gpt-4o", input: "test" }).pipe(Effect.flip) assert.strictEqual(result._tag, "AiError") assert.strictEqual(result.reason._tag, "InternalProviderError") assert.isTrue(result.isRetryable) - })) + }).pipe(Effect.provide(makeTestLayer(undefined, { + _tag: "Json", + status: 500, + body: { error: { message: "Internal server error" } } + })))) it.effect("maps schema error to InvalidOutputError reason", () => Effect.gen(function*() { - const mockClient = makeMockHttpClient((request) => - Effect.succeed(makeMockResponse({ - status: 200, - body: { invalid: "response" }, - request - })) - ) - - const client = yield* OpenAiClient.make({ - apiKey: Redacted.make("test-key") - }).pipe(Effect.provide(Layer.succeed(HttpClient.HttpClient, mockClient))) - - const result = yield* client.createResponse({ model: "gpt-4o", input: "test" }).pipe( - Effect.flip - ) + const client = yield* OpenAiClient.OpenAiClient + const result = yield* client.createResponse({ model: "gpt-4o", input: "test" }).pipe(Effect.flip) assert.strictEqual(result._tag, "AiError") assert.strictEqual(result.method, "createResponse") assert.strictEqual(result.reason._tag, "InvalidOutputError") - })) + }).pipe(Effect.provide(makeTestLayer(undefined, { + _tag: "Json", + body: { invalid: "response" } as any + })))) }) describe("createEmbedding", () => { it.effect("maps 400 error to AiError", () => Effect.gen(function*() { - const mockClient = makeMockHttpClient((request) => - Effect.succeed(makeMockResponse({ - status: 400, - body: { error: { message: "Invalid model" } }, - request - })) - ) - - const client = yield* OpenAiClient.make({ - apiKey: Redacted.make("test-key") - }).pipe(Effect.provide(Layer.succeed(HttpClient.HttpClient, mockClient))) - + const client = yield* OpenAiClient.OpenAiClient const result = yield* client.createEmbedding({ model: "invalid-model", input: "test" @@ -668,22 +426,15 @@ describe("OpenAiClient", () => { assert.strictEqual(result._tag, "AiError") assert.strictEqual(result.method, "createEmbedding") assert.strictEqual(result.reason._tag, "InvalidRequestError") - })) + }).pipe(Effect.provide(makeTestLayer(undefined, { + _tag: "Json", + status: 400, + body: { error: { message: "Invalid model" } } + })))) it.effect("maps 429 error to RateLimitError", () => Effect.gen(function*() { - const mockClient = makeMockHttpClient((request) => - Effect.succeed(makeMockResponse({ - status: 429, - body: { error: { message: "Rate limit exceeded" } }, - request - })) - ) - - const client = yield* OpenAiClient.make({ - apiKey: Redacted.make("test-key") - }).pipe(Effect.provide(Layer.succeed(HttpClient.HttpClient, mockClient))) - + const client = yield* OpenAiClient.OpenAiClient const result = yield* client.createEmbedding({ model: "text-embedding-ada-002", input: "test" @@ -691,47 +442,70 @@ describe("OpenAiClient", () => { assert.strictEqual(result._tag, "AiError") assert.strictEqual(result.reason._tag, "RateLimitError") - })) + }).pipe(Effect.provide(makeTestLayer(undefined, { + _tag: "Json", + status: 429, + body: { error: { message: "Rate limit exceeded" } } + })))) }) describe("createResponseStream", () => { - it.effect("accepts keepalive stream events", () => { - const mockClient = makeMockHttpClient((request) => - Effect.succeed(makeMockStreamResponse({ - request, - events: [ - { - type: "response.created", - sequence_number: 1, - response: makeResponseBody({ - id: "resp_stream", - status: "in_progress" - }) - }, - { - type: "keepalive", - sequence_number: 2 - }, - { - type: "response.completed", - sequence_number: 3, - response: makeResponseBody({ - id: "resp_stream" - }) - } - ] - })) - ) - - const HttpClientLayer = Layer.succeed(HttpClient.HttpClient, mockClient) - - const MainLayer = OpenAiClient.layer({ - apiKey: Redacted.make("test-key") - }).pipe(Layer.provide(HttpClientLayer)) - - return Effect.gen(function*() { + 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" @@ -761,27 +535,29 @@ describe("OpenAiClient", () => { assert.strictEqual(completed.response.id, "resp_stream") } } - }).pipe(Effect.provide(MainLayer)) - }) - - it.effect("maps HTTP error before stream starts", () => { - const mockClient = makeMockHttpClient((request) => - Effect.succeed(makeMockResponse({ - status: 500, - body: { error: { message: "Server error" } }, - request - })) - ) - - const HttpClientLayer = Layer.succeed(HttpClient.HttpClient, mockClient) - - const MainLayer = OpenAiClient.layer({ - apiKey: Redacted.make("test-key") - }).pipe(Layer.provide(HttpClientLayer)) + }).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" }) + } + ] + })))) - return Effect.gen(function*() { + it.effect("maps HTTP error before stream starts", () => + Effect.gen(function*() { const client = yield* OpenAiClient.OpenAiClient - const result = yield* client.createResponseStream({ model: "gpt-4o", input: "test" @@ -792,7 +568,173 @@ describe("OpenAiClient", () => { assert.strictEqual(result._tag, "AiError") assert.strictEqual(result.reason._tag, "InternalProviderError") - }).pipe(Effect.provide(MainLayer)) - }) + }).pipe(Effect.provide(makeTestLayer(undefined, { + _tag: "Json", + status: 500, + body: { error: { message: "Server error" } } + })))) }) }) + +type MockResponse = + | { + readonly _tag: "Json" + readonly body: typeof Generated.Response.Encoded | { + readonly error: { + readonly message: string + readonly type?: string + readonly code?: string | null + } + } + readonly 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 + } + }) +} diff --git a/repos/effect/packages/ai/openai/test/OpenAiLanguageModel.test.ts b/repos/effect/packages/ai/openai/test/OpenAiLanguageModel.test.ts index d85cb8644e..711733d669 100644 --- a/repos/effect/packages/ai/openai/test/OpenAiLanguageModel.test.ts +++ b/repos/effect/packages/ai/openai/test/OpenAiLanguageModel.test.ts @@ -1,8 +1,8 @@ -import { Generated, OpenAiClient, OpenAiLanguageModel, OpenAiTool } from "@effect/ai-openai" +import { type Generated, OpenAiClient, OpenAiLanguageModel, OpenAiSchema, OpenAiTool } from "@effect/ai-openai" import { assert, describe, it } from "@effect/vitest" import { deepStrictEqual, strictEqual } from "@effect/vitest/utils" import { Array, Context, Effect, Layer, Redacted, Ref, Schema, Stream } from "effect" -import { LanguageModel, Prompt, Tool, Toolkit } from "effect/unstable/ai" +import { LanguageModel, Prompt, Response as AiResponse, Tool, Toolkit } from "effect/unstable/ai" import { HttpClient, type HttpClientError, HttpClientRequest, HttpClientResponse } from "effect/unstable/http" describe("OpenAiLanguageModel", () => { @@ -46,7 +46,10 @@ describe("OpenAiLanguageModel", () => { const systemMessage = body.input.find((m: any) => m.role === "system") assert.isDefined(systemMessage) - strictEqual(systemMessage.content, "You are a helpful assistant") + deepStrictEqual(systemMessage.content, [{ + type: "input_text", + text: "You are a helpful assistant" + }]) }).pipe(Effect.provide(makeTestLayer()))) it.effect("uses developer role for reasoning models", () => @@ -63,7 +66,10 @@ describe("OpenAiLanguageModel", () => { const devMessage = body.input.find((m: any) => m.role === "developer") assert.isDefined(devMessage) - strictEqual(devMessage.content, "You are a helpful assistant") + deepStrictEqual(devMessage.content, [{ + type: "input_text", + text: "You are a helpful assistant" + }]) }).pipe(Effect.provide(makeTestLayer({ body: { model: "o1" } })))) it.effect("uses developer role for gpt-5 models", () => @@ -313,6 +319,45 @@ describe("OpenAiLanguageModel", () => { strictEqual(reasoningItem.id, "reasoning_123") }).pipe(Effect.provide(makeTestLayer({ body: { model: "o1" } })))) + it.effect("replays encrypted reasoning from response parts", () => + Effect.gen(function*() { + const history = Prompt.fromResponseParts([ + AiResponse.makePart("reasoning-start", { + id: "reasoning_123:0", + metadata: { openai: { itemId: "reasoning_123" } } + }), + AiResponse.makePart("reasoning-delta", { + id: "reasoning_123:0", + delta: "Let me think..." + }), + AiResponse.makePart("reasoning-end", { + id: "reasoning_123:0", + metadata: { + openai: { + itemId: "reasoning_123", + encryptedContent: "encrypted-reasoning" + } + } + }) + ]) + + yield* LanguageModel.generateText({ + prompt: Prompt.concat(history, Prompt.make("Continue")) + }).pipe(Effect.provide(OpenAiLanguageModel.model("o1"))) + + const requests = yield* MockHttpClient.requests + const body = yield* getRequestBody(requests[0]) + const reasoningItem = body.input.find((item: any) => item.type === "reasoning") + + assert.isDefined(reasoningItem) + deepStrictEqual(reasoningItem, { + type: "reasoning", + id: "reasoning_123", + summary: [{ type: "summary_text", text: "Let me think..." }], + encrypted_content: "encrypted-reasoning" + }) + }).pipe(Effect.provide(makeTestLayer({ body: { model: "o1" } })))) + it.effect("converts tool call parts to function_call", () => Effect.gen(function*() { yield* LanguageModel.generateText({ @@ -336,7 +381,8 @@ describe("OpenAiLanguageModel", () => { id: "call_abc", name: "TestTool", isFailure: false, - result: { output: "result" } + result: { output: "result" }, + providerExecuted: false }) ] } @@ -378,7 +424,8 @@ describe("OpenAiLanguageModel", () => { id: "call_abc", name: "TestTool", isFailure: false, - result: { output: "result" } + result: { output: "result" }, + providerExecuted: false }) ] } @@ -394,6 +441,144 @@ describe("OpenAiLanguageModel", () => { 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()))) }) }) @@ -746,6 +931,38 @@ describe("OpenAiLanguageModel", () => { } })))) + it.each(["gpt-4.1", "gpt-5.6"] as const)( + "maps stable web search action to tool call parameters with %s", + (model) => + Effect.runPromise( + Effect.gen(function*() { + const toolkit = Toolkit.make(OpenAiTool.WebSearch({})) + const result = yield* LanguageModel.generateText({ + prompt: "Search the web", + toolkit + }).pipe(Effect.provide(OpenAiLanguageModel.model(model))) + + const toolCall = result.content.find((part) => part.type === "tool-call") + assert.isDefined(toolCall) + assert.deepStrictEqual(toolCall.params, { + action: { type: "search", query: "Effect TypeScript" } + }) + + const toolResult = result.content.find((part) => part.type === "tool-result") + assert.isDefined(toolResult) + assert.deepStrictEqual(toolResult.result, { + action: { type: "search", query: "Effect TypeScript" }, + status: "completed" + }) + }).pipe(Effect.provide(makeTestLayer({ + body: { + model, + output: [makeWebSearchCall()] + } + }))) + ) + ) + it.effect("uses canonical OpenAiMcp name for mcp_approval_request", () => Effect.gen(function*() { const result = yield* LanguageModel.generateText({ @@ -799,17 +1016,22 @@ describe("OpenAiLanguageModel", () => { assert.isDefined(finishPart) if (finishPart?.type === "finish") { deepStrictEqual(finishPart.usage.inputTokens, { - uncached: 10, + uncached: 7, total: 10, - cacheRead: 0, - cacheWrite: undefined + cacheRead: 3, + cacheWrite: 4 }) deepStrictEqual(finishPart.usage.outputTokens, { total: 20, text: 20, reasoning: 0 }) } }).pipe(Effect.provide(makeTestLayer({ body: { output: [makeTextOutput("Hello")], - usage: makeUsage() + usage: makeUsage({ + input_tokens_details: { + cached_tokens: 3, + cache_write_tokens: 4 + } + }) } })))) @@ -902,6 +1124,48 @@ describe("OpenAiLanguageModel", () => { }) describe("streamText", () => { + it.effect("extracts usage information", () => + Effect.gen(function*() { + const streamEvents = [ + { + type: "response.created", + sequence_number: 1, + response: makeDefaultResponse({ status: "in_progress" }) + }, + { + type: "response.completed", + sequence_number: 2, + response: makeDefaultResponse({ + usage: makeUsage({ + input_tokens_details: { + cached_tokens: 3, + cache_write_tokens: 4 + } + }) + }) + } + ] as unknown as ReadonlyArray + + const partsChunk = yield* LanguageModel.streamText({ + prompt: "Hello" + }).pipe( + Stream.runCollect, + Effect.provide(OpenAiLanguageModel.model("gpt-4o-mini")), + Effect.provide(makeStreamTestLayer(streamEvents)) + ) + + const finishPart = globalThis.Array.from(partsChunk).find((part) => part.type === "finish") + assert.isDefined(finishPart) + if (finishPart?.type === "finish") { + deepStrictEqual(finishPart.usage.inputTokens, { + uncached: 7, + total: 10, + cacheRead: 3, + cacheWrite: 4 + }) + } + })) + it.effect("emits valid apply_patch tool params JSON for update_file diffs", () => Effect.gen(function*() { const diff = "@@ -1 +1 @@\n-old\n+new\n" @@ -1046,6 +1310,59 @@ describe("OpenAiLanguageModel", () => { 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 = [ @@ -1322,7 +1639,7 @@ class MockHttpClient extends Context.Service>([]) @@ -1459,6 +1776,16 @@ const makeFunctionCall = ( ...overrides }) +const makeWebSearchCall = ( + overrides: Partial = {} +): Generated.WebSearchToolCall => ({ + type: "web_search_call", + id: "ws_123", + status: "completed", + action: { type: "search", query: "Effect TypeScript" }, + ...overrides +}) + const makeMcpCall = ( name: string, args: Record, @@ -1498,9 +1825,16 @@ const makeReasoningOutput = ( ...overrides }) +type TestResponseUsage = Omit & { + readonly input_tokens_details: { + readonly cached_tokens: number + readonly cache_write_tokens?: number + } +} + const makeUsage = ( - overrides: Partial = {} -): Generated.ResponseUsage => ({ + overrides: Partial = {} +): TestResponseUsage => ({ input_tokens: 10, output_tokens: 20, total_tokens: 30, diff --git a/repos/effect/packages/ai/openai/test/OpenAiSchema.test.ts b/repos/effect/packages/ai/openai/test/OpenAiSchema.test.ts index 70a30f22c6..26e56c4ace 100644 --- a/repos/effect/packages/ai/openai/test/OpenAiSchema.test.ts +++ b/repos/effect/packages/ai/openai/test/OpenAiSchema.test.ts @@ -1,7 +1,10 @@ +import { OpenAiClient, OpenAiLanguageModel } from "@effect/ai-openai" import * as OpenAiSchema from "@effect/ai-openai/OpenAiSchema" import { assert, describe, it } from "@effect/vitest" -import { Effect, Schema, Stream } from "effect" +import { Effect, Layer, Schema, Stream } from "effect" +import { LanguageModel } from "effect/unstable/ai" import * as Sse from "effect/unstable/encoding/Sse" +import { HttpClientRequest, HttpClientResponse } from "effect/unstable/http" const makeResponse = (overrides: Record = {}) => ({ id: "resp_123", @@ -77,6 +80,25 @@ describe("OpenAiSchema", () => { } }) + it("decodes image generation lifecycle statuses", () => { + for (const status of ["generating", "failed"]) { + const decoded = Schema.decodeUnknownSync(OpenAiSchema.Response)({ + ...makeResponse(), + output: [{ + id: "image_1", + type: "image_generation_call", + status, + result: null + }] + }) + + assert.strictEqual(decoded.output[0].type, "image_generation_call") + if (decoded.output[0].type === "image_generation_call") { + assert.strictEqual(decoded.output[0].status, status) + } + } + }) + it("decodes required stream events", () => { const response = makeResponse({ status: "in_progress" }) const applyPatchItem = { @@ -270,4 +292,53 @@ describe("OpenAiSchema", () => { assert.strictEqual(numeric.data[0].embedding[0], 0.1) assert.strictEqual(base64.data[0].embedding, "AQID") }) + + it.effect("exposes the response.failed error payload", () => { + const response = HttpClientResponse.fromWeb( + HttpClientRequest.get("https://api.openai.com/v1/responses"), + new Response() + ) + const failed = Schema.decodeUnknownSync(OpenAiSchema.ResponseStreamEvent)({ + type: "response.failed", + sequence_number: 1, + response: { + id: "resp_1", + object: "response", + created_at: 1, + model: "gpt-4o-mini", + status: "failed", + output: [], + error: { code: "server_error", message: "provider exploded" } + } + }) + const client = Layer.succeed( + OpenAiClient.OpenAiClient, + OpenAiClient.OpenAiClient.of({ + client: undefined as any, + createResponse: () => Effect.die("unexpected"), + createResponseStream: () => + Effect.succeed([ + response, + Stream.make(failed) + ]), + createEmbedding: () => Effect.die("unexpected") + }) + ) + + return LanguageModel.streamText({ prompt: "test" }).pipe( + Stream.runCollect, + Effect.tap((parts) => + Effect.sync(() => { + const error = Array.from(parts).find((part) => part.type === "error") + const finish = Array.from(parts).find((part) => part.type === "finish") + assert.isDefined(error) + assert.deepStrictEqual(error.error, { code: "server_error", message: "provider exploded" }) + assert.isDefined(finish) + assert.strictEqual(finish.reason, "error") + }) + ), + Effect.provide(OpenAiLanguageModel.model("gpt-4o-mini")), + Effect.provide(client) + ) + }) }) diff --git a/repos/effect/packages/ai/openai/tsconfig.json b/repos/effect/packages/ai/openai/tsconfig.json index 19a2f5dbca..e2a8ca19a0 100644 --- a/repos/effect/packages/ai/openai/tsconfig.json +++ b/repos/effect/packages/ai/openai/tsconfig.json @@ -1,5 +1,5 @@ { - "$schema": "http://json.schemastore.org/tsconfig", + "$schema": "https://json.schemastore.org/tsconfig", "extends": "../../../tsconfig.base.json", "include": ["src"], "references": [ diff --git a/repos/effect/packages/ai/openai/vitest.config.ts b/repos/effect/packages/ai/openai/vitest.config.ts deleted file mode 100644 index c8a52c1826..0000000000 --- a/repos/effect/packages/ai/openai/vitest.config.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { mergeConfig, type ViteUserConfig } from "vitest/config" -import shared from "../../../vitest.shared.ts" - -const config: ViteUserConfig = {} - -export default mergeConfig(shared, config) diff --git a/repos/effect/packages/ai/openrouter/CHANGELOG.md b/repos/effect/packages/ai/openrouter/CHANGELOG.md index 7c07b2ac7b..eecc3ec466 100644 --- a/repos/effect/packages/ai/openrouter/CHANGELOG.md +++ b/repos/effect/packages/ai/openrouter/CHANGELOG.md @@ -1,5 +1,149 @@ # @effect/ai-openrouter +## 4.0.0-beta.107 + +### Patch Changes + +- Updated dependencies [[`596f3f9`](https://github.com/Effect-TS/effect/commit/596f3f92d7fe355811b815cb212332b082268ce8), [`9611ed4`](https://github.com/Effect-TS/effect/commit/9611ed42d11300546b339ab13492a0f7bdb1ebfb), [`8b91605`](https://github.com/Effect-TS/effect/commit/8b9160548556e4b0ec7ee2f2707716776be49018), [`d901928`](https://github.com/Effect-TS/effect/commit/d901928efa44f573ed1247f53fdb203a8e4fcede), [`b32bdef`](https://github.com/Effect-TS/effect/commit/b32bdef0d119a1ad1463dc01a46763ffee1f9bd9)]: + - effect@4.0.0-beta.107 + +## 4.0.0-beta.106 + +### Patch Changes + +- [#7128](https://github.com/Effect-TS/effect/pull/7128) [`69756a2`](https://github.com/Effect-TS/effect/commit/69756a2290c11bb144b31240de29f39b8cb42c05) Thanks @fubhy! - Preserve start and end offsets for streamed OpenRouter citations. + +- [#7133](https://github.com/Effect-TS/effect/pull/7133) [`d6a4a9c`](https://github.com/Effect-TS/effect/commit/d6a4a9cef496c38663d3977a6a5796a33a9b19b7) Thanks @fubhy! - Fix the casing of OpenRouter reasoning-end metadata. + +- [#7132](https://github.com/Effect-TS/effect/pull/7132) [`3f01731`](https://github.com/Effect-TS/effect/commit/3f01731a09ebf5c53e9bede851ae8f8f28f1a5e8) Thanks @fubhy! - Emit incremental tool parameter fragments from OpenRouter streaming responses. +- Updated dependencies [[`2695168`](https://github.com/Effect-TS/effect/commit/269516851b24916d72771f8a554b88722e3732e7), [`6310a8c`](https://github.com/Effect-TS/effect/commit/6310a8c68c74dcf1d23948ec9243ac5f407a1651), [`c2071b1`](https://github.com/Effect-TS/effect/commit/c2071b1647e2326568c1d0689274ef62b8a7183f), [`7aff81a`](https://github.com/Effect-TS/effect/commit/7aff81a9cefe681483ef8abf717d786fd10e7e8d), [`a1d4057`](https://github.com/Effect-TS/effect/commit/a1d4057711935a544ef441bc2d0ac3565dfa9266), [`abf77b0`](https://github.com/Effect-TS/effect/commit/abf77b04009dcb4d67a258f9d8ada778e9f4ffae), [`6c60375`](https://github.com/Effect-TS/effect/commit/6c60375e68683a32d54554150cc493e16550a06d), [`22f4897`](https://github.com/Effect-TS/effect/commit/22f4897bbae24783d4516f6bef353f1db4ec6d03), [`615d1d5`](https://github.com/Effect-TS/effect/commit/615d1d5d0256ec8160f2e08d0dcf5dc83acb7bf1), [`3a86757`](https://github.com/Effect-TS/effect/commit/3a867573ddeed5888dabdeb3225a9ebbf00491e7), [`f4a9762`](https://github.com/Effect-TS/effect/commit/f4a9762bb9dfad59c215f2e099dcc829d74f4ed1), [`0bcf6ed`](https://github.com/Effect-TS/effect/commit/0bcf6ed57c22e8a36964726b15464101d90f5997), [`ba9cb63`](https://github.com/Effect-TS/effect/commit/ba9cb63b87d45ce2df872dd8ef0905da147cc675), [`42c810d`](https://github.com/Effect-TS/effect/commit/42c810dd372275b822dd99c7d7e774e153f0a752), [`1416ccd`](https://github.com/Effect-TS/effect/commit/1416ccd474bc9da8979f51b72b5e53fb3ac56edf), [`08d0d39`](https://github.com/Effect-TS/effect/commit/08d0d39a225deccb9db213ab5fcf55edb9f9ba5d), [`548908a`](https://github.com/Effect-TS/effect/commit/548908a71d9337cb7defe7fc93b2fba8f6a04b6f), [`4b3460d`](https://github.com/Effect-TS/effect/commit/4b3460daa434ec465a95a50704fe1103a9275999), [`d170596`](https://github.com/Effect-TS/effect/commit/d17059615cca37ca2776654078fe0501ac5202e6), [`aea89d0`](https://github.com/Effect-TS/effect/commit/aea89d0c42ee0ac707a4962cd348fd3158cb469b), [`deed5fb`](https://github.com/Effect-TS/effect/commit/deed5fbdc91cf8bf8c5fce7dfa5d6527ac944726)]: + - effect@4.0.0-beta.106 + +## 4.0.0-beta.105 + +### Patch Changes + +- Updated dependencies [[`0418564`](https://github.com/Effect-TS/effect/commit/04185644dabb8e4169f1ef6cbbc0b36c4db2f7f4), [`d334a85`](https://github.com/Effect-TS/effect/commit/d334a8593aafcd60753995a9449b654c67bfdcc1), [`f0be855`](https://github.com/Effect-TS/effect/commit/f0be8554da6ee00293a6b23869ac46a0b0d97dc8), [`b206fa5`](https://github.com/Effect-TS/effect/commit/b206fa5d7655c1634c9993410a9203f6616a5ca2), [`b938c8a`](https://github.com/Effect-TS/effect/commit/b938c8ad2823bd88493187922f7d9090eff037b6), [`8525f05`](https://github.com/Effect-TS/effect/commit/8525f05d1e14ea12298e9e1a0df497bfaac2ce9a)]: + - effect@4.0.0-beta.105 + +## 4.0.0-beta.104 + +### Patch Changes + +- Updated dependencies [[`1001bcc`](https://github.com/Effect-TS/effect/commit/1001bccb9e874918d59dbb36860f1c5d4499ac20), [`993ba60`](https://github.com/Effect-TS/effect/commit/993ba60ee6c7ca6eb84522040f8b0d268b6ba7d4), [`67faacd`](https://github.com/Effect-TS/effect/commit/67faacd4679242559bee31717c05a5b10b990322), [`b78acdf`](https://github.com/Effect-TS/effect/commit/b78acdf422568f10ae8684fd3f10d52b065f0b56), [`fbb9ce5`](https://github.com/Effect-TS/effect/commit/fbb9ce5e625d1a7d6b7005bda42cbb6cd31476c5), [`722ea48`](https://github.com/Effect-TS/effect/commit/722ea484c9d01364c9242d929c0a564f7831a57c), [`3058fd5`](https://github.com/Effect-TS/effect/commit/3058fd594f5a683034212d71d957017fcc084006), [`62d0575`](https://github.com/Effect-TS/effect/commit/62d057566c241405c23ecf0bf4156186bd2be924), [`99dd6b5`](https://github.com/Effect-TS/effect/commit/99dd6b580434f97c5b40adc919f429e4abc3dfe7), [`7963ce1`](https://github.com/Effect-TS/effect/commit/7963ce1cd95f037fbefea67a29ead49cce4d16cb), [`af14e75`](https://github.com/Effect-TS/effect/commit/af14e752edd65e2b652e960411afafc88975a8d8), [`24e22d2`](https://github.com/Effect-TS/effect/commit/24e22d23a73a2e93ebf6d8edd2246a4a406942c8), [`647d14e`](https://github.com/Effect-TS/effect/commit/647d14e572c8004fa92fba256e00552b42bf34b7), [`1434eec`](https://github.com/Effect-TS/effect/commit/1434eecbd368e00839c24b3950f0b7a69218669a), [`a5278b1`](https://github.com/Effect-TS/effect/commit/a5278b18242011d1b2b08304c7c128151f9a4370), [`6af04a5`](https://github.com/Effect-TS/effect/commit/6af04a50bd019238f6acdb9cbda40439a3c09210), [`cb6c837`](https://github.com/Effect-TS/effect/commit/cb6c8376b2f322d4e7cbfc0973fc3b4f2951ee6e), [`d44cead`](https://github.com/Effect-TS/effect/commit/d44cead7e0e0ce61f0d980906e494f49a07e7899), [`88c7632`](https://github.com/Effect-TS/effect/commit/88c7632c2b59a49fcc40d250865bd8d0dccf31b0), [`abcbb2a`](https://github.com/Effect-TS/effect/commit/abcbb2abe16f1b6c587c15007df14371e1e70e93), [`8f63cce`](https://github.com/Effect-TS/effect/commit/8f63cce636700fde26b140b82e350ef916989d86), [`d56dfcf`](https://github.com/Effect-TS/effect/commit/d56dfcf54c2b9c53c3d098ce4b0ffcc84496c5f7), [`a98cda9`](https://github.com/Effect-TS/effect/commit/a98cda9422e1352f22e81696f759f326ffcfb667), [`6704bb8`](https://github.com/Effect-TS/effect/commit/6704bb84c320547f83cf50e8586ffc4c5e4c3cc5), [`6143de2`](https://github.com/Effect-TS/effect/commit/6143de21ee22038b45a8d4eba86f5aade6238eba), [`936b135`](https://github.com/Effect-TS/effect/commit/936b1358396eb0a1a7c8e0878ba63297e2106812), [`1bbae84`](https://github.com/Effect-TS/effect/commit/1bbae84f88b577a26d04ceb2e76d3143d09c4a20), [`d795ee7`](https://github.com/Effect-TS/effect/commit/d795ee771701ea62bd187ef7c0307d9737f68c1a), [`0a82d88`](https://github.com/Effect-TS/effect/commit/0a82d88b7da73278b6f270118e396d5ed4a64747), [`9215bc5`](https://github.com/Effect-TS/effect/commit/9215bc5da7dd10aa45f07fe44b98f06b6e433d62), [`a1b5df2`](https://github.com/Effect-TS/effect/commit/a1b5df2064d92431cfc6e638af613cc3114313d7), [`92a9ac5`](https://github.com/Effect-TS/effect/commit/92a9ac5ac0aa63d8975b9ba7a094d6a8f59a98f2), [`6bde7f2`](https://github.com/Effect-TS/effect/commit/6bde7f27f3243427203e53fe74472990e5c2a349), [`a712131`](https://github.com/Effect-TS/effect/commit/a7121310dbb60cbd819bbd702f97663098ec7bb8), [`2e6f760`](https://github.com/Effect-TS/effect/commit/2e6f760dcb44e2b984f3311a8af03a1d68a2ec7e), [`aa05804`](https://github.com/Effect-TS/effect/commit/aa0580497e027ed30b756058db0067c3fe07664f), [`badd3bf`](https://github.com/Effect-TS/effect/commit/badd3bf65fac4dd1e66e1f602db43659722dfced), [`02b0265`](https://github.com/Effect-TS/effect/commit/02b02651ede46a5a2dd3ef8081d0ad89648d0cbf), [`3437e21`](https://github.com/Effect-TS/effect/commit/3437e21a56d805781c5e5946a6189795a1dfd411), [`41a550d`](https://github.com/Effect-TS/effect/commit/41a550d1fed31e829929a8f5362b5340303164ac), [`17b5d50`](https://github.com/Effect-TS/effect/commit/17b5d50219ad49533cf9e33d01924a3e16af5eb3), [`96e5e95`](https://github.com/Effect-TS/effect/commit/96e5e9576b0315c747462761a61940ff9fe32dd1), [`e4d589e`](https://github.com/Effect-TS/effect/commit/e4d589e0ea08dc57c4793053b395dc0fcc499f34), [`ae4cf7b`](https://github.com/Effect-TS/effect/commit/ae4cf7b5e2cb5f8c55657e31a61789ad21c38c18), [`6ef5f1a`](https://github.com/Effect-TS/effect/commit/6ef5f1a041f3a40bf03fadd0b1feb275c277c635), [`2235a29`](https://github.com/Effect-TS/effect/commit/2235a29502c3f33cf6468511ad931089013a7916), [`b32f4cb`](https://github.com/Effect-TS/effect/commit/b32f4cb7b2d8ebe817075322622498e3beb05336), [`7f4c095`](https://github.com/Effect-TS/effect/commit/7f4c095b62da43780dd7fc2a5d1785ddfce60edf), [`5f3fb81`](https://github.com/Effect-TS/effect/commit/5f3fb814d18d8a54946c1c1cd0b41459cdb24006), [`17f0b91`](https://github.com/Effect-TS/effect/commit/17f0b91a243ccfe4a38d27debdc983adf434e738), [`0cdadd7`](https://github.com/Effect-TS/effect/commit/0cdadd75bc8abbbcad7956a4bc71f4e7a9b13250), [`39b57d7`](https://github.com/Effect-TS/effect/commit/39b57d7857358040558b67dd33eafc7bb5457830), [`5a6a573`](https://github.com/Effect-TS/effect/commit/5a6a5738e5bfc39e3a37ae7ba99081601fa19ac3), [`59f5e99`](https://github.com/Effect-TS/effect/commit/59f5e9981913b92d7a9beb2214a21d658b999d3a), [`45379d6`](https://github.com/Effect-TS/effect/commit/45379d6179ee4df2cbd3f848bd39ff7149c24a38), [`1949439`](https://github.com/Effect-TS/effect/commit/1949439175809ef81ab9c6411ed5559109edb4c9), [`e443403`](https://github.com/Effect-TS/effect/commit/e443403cf0e4effea14bb6cd950c5ac1c86cc748), [`03af7e8`](https://github.com/Effect-TS/effect/commit/03af7e85551204c605ea2fa2c43c10a4538ac8fb), [`0f721d4`](https://github.com/Effect-TS/effect/commit/0f721d406df8703ea92ca28777b3f09599e2056d), [`130b28d`](https://github.com/Effect-TS/effect/commit/130b28df552d7053407b041a96ff09dae82575e5), [`c987a12`](https://github.com/Effect-TS/effect/commit/c987a12a01b6a52ad53d29edf02613b03574dbcc), [`4158562`](https://github.com/Effect-TS/effect/commit/41585620977de9b84171f76619b72e29cc2284e5), [`306014a`](https://github.com/Effect-TS/effect/commit/306014a1ce4d5cb956c76bdc20e4e28ab3e61a6a), [`729a663`](https://github.com/Effect-TS/effect/commit/729a663275dd31f2357c446fe69664429220a83d), [`caf84b6`](https://github.com/Effect-TS/effect/commit/caf84b660044089e8d7f4067b279b27b8b50e8fd), [`ce067f7`](https://github.com/Effect-TS/effect/commit/ce067f799ea27735d4194345298a216aaf429f01), [`7a41f5a`](https://github.com/Effect-TS/effect/commit/7a41f5aa72d540ecf2746992ecc3fa3e6b40d31f), [`781022a`](https://github.com/Effect-TS/effect/commit/781022acdd3537ca18c88e2fa3681bafa6ef1b21), [`39f1297`](https://github.com/Effect-TS/effect/commit/39f1297acc08864feb12de6b8cf2bf73434f6cf5), [`2db266b`](https://github.com/Effect-TS/effect/commit/2db266b1bfbc81868bc1778c37c76032a267c79f), [`2141e28`](https://github.com/Effect-TS/effect/commit/2141e28903754d72604acf81673ceb2c62a56646), [`3c5e429`](https://github.com/Effect-TS/effect/commit/3c5e429878669ffcf5e0da4ddfbf50bde5bbcaad), [`20ddc63`](https://github.com/Effect-TS/effect/commit/20ddc630584f8fe488162ba384adcae53fc6810a), [`841b3ea`](https://github.com/Effect-TS/effect/commit/841b3ea6ae19a784bc1c20497b02f632af0c91e9), [`82a3fbf`](https://github.com/Effect-TS/effect/commit/82a3fbfce8b9df33e587076b7d7168ecd6799e17), [`eb9ee83`](https://github.com/Effect-TS/effect/commit/eb9ee83b38844a71d1cd5653a229309cfcb04a36), [`64dc7c7`](https://github.com/Effect-TS/effect/commit/64dc7c76dc5c89887b9e7c181d1873dcbb7820d1), [`84dc8ab`](https://github.com/Effect-TS/effect/commit/84dc8ab7accc682bc668c78a97e4a1776b633be8), [`b4463f4`](https://github.com/Effect-TS/effect/commit/b4463f46fc33d3b01ea5eadd7d012a5abda347a3), [`592dd36`](https://github.com/Effect-TS/effect/commit/592dd361645739ac0cd8e6babb084cd27403c172), [`85d2b44`](https://github.com/Effect-TS/effect/commit/85d2b446e3059de4919be730105868f79728308d), [`32e4a69`](https://github.com/Effect-TS/effect/commit/32e4a69b3151b7ec4058af2213b96a41d11e9e06), [`13c5872`](https://github.com/Effect-TS/effect/commit/13c5872ed30830360367ad89af2dab68a003c351), [`3454cdb`](https://github.com/Effect-TS/effect/commit/3454cdb528fdb5d3ed0c5c5c8169bc47de41fbd8), [`e930804`](https://github.com/Effect-TS/effect/commit/e9308045be1d8a00c0b4046f1e8ff22cf68c93da), [`7f12d4b`](https://github.com/Effect-TS/effect/commit/7f12d4b4e731dc3a213ae5c3f60db9edc50292d2), [`181c9ef`](https://github.com/Effect-TS/effect/commit/181c9ef5e5d4ab247bf4aec06424f15b0a1e802e), [`dd9f891`](https://github.com/Effect-TS/effect/commit/dd9f891e23f316abb6192893008f0e33ece9d97d), [`433fb81`](https://github.com/Effect-TS/effect/commit/433fb81ca4c15c681a8ae097ce3ff9bd3a9c9aa5), [`8459cdb`](https://github.com/Effect-TS/effect/commit/8459cdbae8a476dc04b6247fffe6a1668dcb1217), [`6124ab3`](https://github.com/Effect-TS/effect/commit/6124ab39eb64688fbd5d688d24766542f9cb5a2c), [`01bd954`](https://github.com/Effect-TS/effect/commit/01bd9546f142706fca1628f7261e6d1cb9638948), [`ba2c3aa`](https://github.com/Effect-TS/effect/commit/ba2c3aa05eb87ec05d263b960017ecf29746f66a), [`0a45ef3`](https://github.com/Effect-TS/effect/commit/0a45ef3bb4a1ae9b345c43c548db4336a31b3191), [`8459cdb`](https://github.com/Effect-TS/effect/commit/8459cdbae8a476dc04b6247fffe6a1668dcb1217), [`eaa7e71`](https://github.com/Effect-TS/effect/commit/eaa7e71b88bf59b24610128c6115a2a126432731), [`db4c2cc`](https://github.com/Effect-TS/effect/commit/db4c2ccdec77d813b6f4cc72a302ee7c4fe6e39d), [`22f150a`](https://github.com/Effect-TS/effect/commit/22f150a0936cef30517e87eaca73bff1c5e4873a), [`90ffb08`](https://github.com/Effect-TS/effect/commit/90ffb083b3091c211300f50a42ba7bf56536c0ee), [`d517692`](https://github.com/Effect-TS/effect/commit/d517692ef75f45d5f6d9d68b32d41fa0ccc56c99), [`01af079`](https://github.com/Effect-TS/effect/commit/01af079c189d1fc5067d3b1933b2870c4baf2693), [`32a59e8`](https://github.com/Effect-TS/effect/commit/32a59e8058b1ec9738cb083cf1cb116b393ca114)]: + - effect@4.0.0-beta.104 + +## 4.0.0-beta.103 + +### Patch Changes + +- [#6777](https://github.com/Effect-TS/effect/pull/6777) [`8e7c706`](https://github.com/Effect-TS/effect/commit/8e7c706b0aca855489b53d987404566d3e9cb5e7) Thanks @tim-smart! - Bound pending SSE decoder state with a configurable maximum event size. + +- [#6701](https://github.com/Effect-TS/effect/pull/6701) [`9867b9f`](https://github.com/Effect-TS/effect/commit/9867b9fc69f9cc6c443594fc7eccc7be0c674d9c) Thanks @fubhy! - Removed explicit ./index entrypoints + +- Updated dependencies [[`e56cd8f`](https://github.com/Effect-TS/effect/commit/e56cd8f90c3559baccf8fcf2852ea911235d5944), [`f77c120`](https://github.com/Effect-TS/effect/commit/f77c120d8e04779ddeb8bce8e9cde932f268e4b6), [`b2f95a9`](https://github.com/Effect-TS/effect/commit/b2f95a9c2f2581deb89dc3bae9e89cf819e82923), [`04fd44a`](https://github.com/Effect-TS/effect/commit/04fd44a42abfa8dc2642300dcf49ee48c8ef4539), [`b74333d`](https://github.com/Effect-TS/effect/commit/b74333d83e15b9d042e4698ad23040de60454afe), [`1c40b28`](https://github.com/Effect-TS/effect/commit/1c40b2809503d6aa1358777196fc66317906e657), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`b3901d2`](https://github.com/Effect-TS/effect/commit/b3901d29c543fd5bd05ceec669a17896c8e19006), [`4a0984a`](https://github.com/Effect-TS/effect/commit/4a0984af62738fedf4bd3e87adb4d4d641ce9147), [`fffd88b`](https://github.com/Effect-TS/effect/commit/fffd88b3135abdf928ca7c4b0e00e610985091c7), [`f3f6c1e`](https://github.com/Effect-TS/effect/commit/f3f6c1e02cb543423fcffef5dc2db03fac503588), [`ef07642`](https://github.com/Effect-TS/effect/commit/ef07642dfe671d5258b65d1c1480c4d05c495f15), [`f1bc827`](https://github.com/Effect-TS/effect/commit/f1bc8274a608813d7b09d28dcca04adbf62f8c92), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`081f4d8`](https://github.com/Effect-TS/effect/commit/081f4d8cd06a2ac222d2810b46e61efcee26939e), [`5287b24`](https://github.com/Effect-TS/effect/commit/5287b24f5f8fa094ba20e117bfb1a80fba6d2cf5), [`13d31cf`](https://github.com/Effect-TS/effect/commit/13d31cfc2dde46210e94391b5b6767ae9aeaf2c9), [`acee269`](https://github.com/Effect-TS/effect/commit/acee26944bc89ee554d7b9fadab7443f9edc28a9), [`31170c1`](https://github.com/Effect-TS/effect/commit/31170c19b236c37abb5476c821bc6f5bfa2735ab), [`205ebc7`](https://github.com/Effect-TS/effect/commit/205ebc776062012581e98fced7ced19adfc44ee7), [`ed0ebf8`](https://github.com/Effect-TS/effect/commit/ed0ebf8e5c864d46fed1f232e99c0e680f10a58f), [`a3fd084`](https://github.com/Effect-TS/effect/commit/a3fd08482157bd78b089f77c7b173d54ef68b5cd), [`ee29ddf`](https://github.com/Effect-TS/effect/commit/ee29ddf862c3723ad466abc93ab6f6fe723b2319), [`6086309`](https://github.com/Effect-TS/effect/commit/60863090af8e5af0bfa1435f08dc5390f9993e30), [`4a57af2`](https://github.com/Effect-TS/effect/commit/4a57af24011db1d66e947289d2f7ffc2074696d2), [`660875b`](https://github.com/Effect-TS/effect/commit/660875b4325e6eebb3f04513998301cd2a0847ec), [`8e7c706`](https://github.com/Effect-TS/effect/commit/8e7c706b0aca855489b53d987404566d3e9cb5e7), [`5f63adb`](https://github.com/Effect-TS/effect/commit/5f63adbe75fc9d50d23706a52b3e483ad2a1a01c), [`053bc42`](https://github.com/Effect-TS/effect/commit/053bc42e2a964755611a216e78ed214322efee37), [`c0a1534`](https://github.com/Effect-TS/effect/commit/c0a153494484ecf9f0d0f20895a7a648b4be363b), [`f1e3a37`](https://github.com/Effect-TS/effect/commit/f1e3a378c144f974a6122b299f421b75595af20f), [`cedb01a`](https://github.com/Effect-TS/effect/commit/cedb01a025492a1faf9e59eb23eb96bc3b5e2fff), [`1747440`](https://github.com/Effect-TS/effect/commit/1747440de9a51a56ed3660da748cc01b256adce7), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`b4f1ee2`](https://github.com/Effect-TS/effect/commit/b4f1ee238d96aa78c5f040158cb78671d75b381e), [`a4757f1`](https://github.com/Effect-TS/effect/commit/a4757f1c47067d8d016a6c4a2c541bb8ae520f9b), [`cd122b9`](https://github.com/Effect-TS/effect/commit/cd122b90300d995a237993a2edb7a049785ab6a4), [`5de588b`](https://github.com/Effect-TS/effect/commit/5de588b2472fb0f4eb919766eb8472583a044772), [`3895b9c`](https://github.com/Effect-TS/effect/commit/3895b9cf179262cd277a9c6daafe9050dcf8265e), [`89ce5f3`](https://github.com/Effect-TS/effect/commit/89ce5f3e16e23a193daa475dc72ea8133ae1dacd), [`985de09`](https://github.com/Effect-TS/effect/commit/985de097d75906db2aed784841f81e23cc978b43), [`9800e3a`](https://github.com/Effect-TS/effect/commit/9800e3acc8f36530f671bc8b91558cb112f449a7), [`4dc35f6`](https://github.com/Effect-TS/effect/commit/4dc35f64641746366f867ea3dbfedb9cd4685ada), [`e8eb62b`](https://github.com/Effect-TS/effect/commit/e8eb62b3d0ef27e9761cdc2eb93bdec52d6ee204), [`ecd9993`](https://github.com/Effect-TS/effect/commit/ecd99936112cb69efdb02de3a2fd57f47baefdf3), [`5ab9c08`](https://github.com/Effect-TS/effect/commit/5ab9c08463ce049c45f3502676954a7b72c6b024), [`f5cf965`](https://github.com/Effect-TS/effect/commit/f5cf96548afd51f4b3cf1aea11b04d7f8549ce90), [`a94cbed`](https://github.com/Effect-TS/effect/commit/a94cbed84e9e49bea4bff925599c0f19c4e3deab), [`9160ad7`](https://github.com/Effect-TS/effect/commit/9160ad7d146d4376dd12f7510c025e5b2f638a70), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`52494be`](https://github.com/Effect-TS/effect/commit/52494be9e8eb3bb542d06a3dfefc6bca4e168984), [`5441c8e`](https://github.com/Effect-TS/effect/commit/5441c8e656a6418c0d27feb2df67565a3e1155f4), [`c9b56ab`](https://github.com/Effect-TS/effect/commit/c9b56ab507f224426ee8388dc450da447ec4715f), [`8ef7257`](https://github.com/Effect-TS/effect/commit/8ef72577d1f43212cab87951d659e54e3c8d7d91), [`1519406`](https://github.com/Effect-TS/effect/commit/1519406fed6e8b017ae178dc20bcaa2cf318b570), [`9716990`](https://github.com/Effect-TS/effect/commit/97169902eec3c99baa7f0b2c7b45a0a5eae75819), [`733f75b`](https://github.com/Effect-TS/effect/commit/733f75b7125e3016a975fdd251c0179ae5393786), [`48155c8`](https://github.com/Effect-TS/effect/commit/48155c8ccfc12dcca8a00fa358d50b20c30874e4), [`951d06b`](https://github.com/Effect-TS/effect/commit/951d06b83d459d3e8fa9024e727a5db1662d3322), [`d767b65`](https://github.com/Effect-TS/effect/commit/d767b65a7687e38be23f0b0ee3d52ab5f2360cbe), [`5d52d9d`](https://github.com/Effect-TS/effect/commit/5d52d9d148aaa7f736ed8c310fc8bfa9dc81badf), [`f4151e1`](https://github.com/Effect-TS/effect/commit/f4151e1937c26de14f1d64566f8126173f1b5014), [`e02fbb6`](https://github.com/Effect-TS/effect/commit/e02fbb66f5a0f13dba6c33ef63528a37a17a0676), [`724ce09`](https://github.com/Effect-TS/effect/commit/724ce09650a458d4565e5c7331ea92ca04f08e68), [`dbe91f6`](https://github.com/Effect-TS/effect/commit/dbe91f6961ef9f7e8da910ee5758d9c0d385fca8), [`4c008d2`](https://github.com/Effect-TS/effect/commit/4c008d28b370d817f7ae4579db09836fe084c8d2), [`b650832`](https://github.com/Effect-TS/effect/commit/b6508328708a842f3163467b72486bd228f1a289), [`b46c92f`](https://github.com/Effect-TS/effect/commit/b46c92f3b314f4ffd612b831efa55dd856c587a3), [`5335797`](https://github.com/Effect-TS/effect/commit/5335797003076d9c6fd170da98d779696d555596), [`4b3460d`](https://github.com/Effect-TS/effect/commit/4b3460daa434ec465a95a50704fe1103a9275999), [`6301fd7`](https://github.com/Effect-TS/effect/commit/6301fd710b4325718de2c42997dac28a9e9aa250), [`aebc5c6`](https://github.com/Effect-TS/effect/commit/aebc5c61664b89a840465ec65b79ce635a5ceee8), [`52b2d7b`](https://github.com/Effect-TS/effect/commit/52b2d7b5bd3c7cce3bd5b69c6ab3941004da70f3), [`eec5744`](https://github.com/Effect-TS/effect/commit/eec57445dfa0ef3c5977195ad69415b7e7d42bb6), [`24e0e93`](https://github.com/Effect-TS/effect/commit/24e0e93dc307dc2c2ae86caacb7289e1dab3c103), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`1a7ce81`](https://github.com/Effect-TS/effect/commit/1a7ce8150e3977586c44d8ccb9a8384389bb4d49), [`48f22a7`](https://github.com/Effect-TS/effect/commit/48f22a7d16ae57ee2175d450dafbdeb69e187d2a), [`c96b7f6`](https://github.com/Effect-TS/effect/commit/c96b7f6359662053c3e09344f61dddc7a6caf4ac), [`6d2a942`](https://github.com/Effect-TS/effect/commit/6d2a942ed7cd33b8fd79d549edba33bc9e2a7e3e), [`cc27b19`](https://github.com/Effect-TS/effect/commit/cc27b194b9d13fa3a66ab037e853fca9d41700ff), [`8f9499f`](https://github.com/Effect-TS/effect/commit/8f9499f562729f5f7b08d8bcc4db86b4aeff8a21), [`3eeea73`](https://github.com/Effect-TS/effect/commit/3eeea73cfc3e9b126975c2ddbdb7f7c8c92026e2), [`0a532e5`](https://github.com/Effect-TS/effect/commit/0a532e503f165fdea485a5343fc2f420917e8376), [`f398149`](https://github.com/Effect-TS/effect/commit/f398149c134fd9b67b6cdc52eae3f3248d5c7bbe), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`ace903e`](https://github.com/Effect-TS/effect/commit/ace903e09c2549ceebdec380797beb027cd29f3d), [`e8eb62b`](https://github.com/Effect-TS/effect/commit/e8eb62b3d0ef27e9761cdc2eb93bdec52d6ee204), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`48f22a7`](https://github.com/Effect-TS/effect/commit/48f22a7d16ae57ee2175d450dafbdeb69e187d2a), [`d48506d`](https://github.com/Effect-TS/effect/commit/d48506d97525040aa714305e928126df799795b4), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`d48506d`](https://github.com/Effect-TS/effect/commit/d48506d97525040aa714305e928126df799795b4), [`d48506d`](https://github.com/Effect-TS/effect/commit/d48506d97525040aa714305e928126df799795b4), [`52262be`](https://github.com/Effect-TS/effect/commit/52262be2edce0e350c6ac10f8f725678606399c5), [`1284aa1`](https://github.com/Effect-TS/effect/commit/1284aa183451955ad7921bbe01fd0e095695d444), [`9867b9f`](https://github.com/Effect-TS/effect/commit/9867b9fc69f9cc6c443594fc7eccc7be0c674d9c), [`d0f1a22`](https://github.com/Effect-TS/effect/commit/d0f1a2295155c350b04efb46852cb40032805273), [`979ce39`](https://github.com/Effect-TS/effect/commit/979ce3985d7d62ce2bf240681ca19feda3027452), [`b6d3e67`](https://github.com/Effect-TS/effect/commit/b6d3e67c7cc143cd8470cdf704324e79d23954a9), [`adf6c6c`](https://github.com/Effect-TS/effect/commit/adf6c6cd388af8a3c0c546492e71555368556f6a), [`7314d60`](https://github.com/Effect-TS/effect/commit/7314d605284717aaafe7fc34b88c3c93397e865c), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`1acbd8b`](https://github.com/Effect-TS/effect/commit/1acbd8b44c68ebb23735e9810476b870dbe58aea), [`7bde6cc`](https://github.com/Effect-TS/effect/commit/7bde6ccb2b144fe953ff30a7ef5e1ecc97697146), [`a959a8b`](https://github.com/Effect-TS/effect/commit/a959a8bf21cdb976369f494dc949fa00a050d3e0)]: + - effect@4.0.0-beta.103 + +## 4.0.0-beta.102 + +### Patch Changes + +- [#6608](https://github.com/Effect-TS/effect/pull/6608) [`0e50ec7`](https://github.com/Effect-TS/effect/commit/0e50ec7dbb94390666f292cf9120719bf30a7246) Thanks @gcanti! - Add `Schema.Natural` for non-negative safe integers and use canonical `Schema.Int`, `Schema.Finite`, and `Schema.Natural` schemas for numeric domain values across Effect, AI protocols, and OpenAPI patches. + + Update the date, date-time, file, time-zone, cluster, event-log, persistence, socket, SQL, and DevTools schemas to reject invalid non-finite or non-integer values where appropriate. Correct the decoded schema of `Schema.NumberFromString`, and allow `Schema.DurationFromMillis` and `Schema.DurationFromNanos` to represent negative durations. + +- [#6567](https://github.com/Effect-TS/effect/pull/6567) [`5101e92`](https://github.com/Effect-TS/effect/commit/5101e92c9c149c153423f43dd7a94f6194653c06) Thanks @gcanti! - Add `Record.assignProperty` and safely handle dynamic record keys such as `__proto__` and inherited property names. + +- [#6572](https://github.com/Effect-TS/effect/pull/6572) [`c0f9fc9`](https://github.com/Effect-TS/effect/commit/c0f9fc9fda8af8ac138b23293876e78bd052ab00) Thanks @tim-smart! - Regenerate the `Generated` module against OpenRouter's current published specification. This preserves nullable + generation statistics and streamed usage cost metadata while incorporating the broader upstream schema changes. + + Notable generated schema renames include `ChatGenerationParams` to `ChatRequest`, `ChatGenerationTokenUsage` to + `ChatUsage`, `AssistantMessage` to `ChatAssistantMessage`, `ChatStreamingResponseChunk` to `ChatStreamingResponse`, + and `ChatMessageContentItemCacheControl` to `ChatContentCacheControl`. Handwritten public aliases such as + `ChatStreamingResponseChunkData`, `ReasoningDetails`, and `FileAnnotation` retain their existing names. + +- Updated dependencies [[`b6392e1`](https://github.com/Effect-TS/effect/commit/b6392e119704553edec1b4fd2869ac0dbec621ef), [`7ed9450`](https://github.com/Effect-TS/effect/commit/7ed945044eb56aa9aeaf62d4746a011c96c58628), [`45762bd`](https://github.com/Effect-TS/effect/commit/45762bd78df9ecd87c98b8d3738cdeeac7d81128), [`a6e8391`](https://github.com/Effect-TS/effect/commit/a6e8391cd31acd898fae18b3f8e7ca4c6f14f065), [`4ac7e8b`](https://github.com/Effect-TS/effect/commit/4ac7e8b136c61a26c3e438c013dfd7349b38e999), [`4cd40f5`](https://github.com/Effect-TS/effect/commit/4cd40f5692477783bef84fed3c5ef1c0cf5602e6), [`6956bc0`](https://github.com/Effect-TS/effect/commit/6956bc0e6cb27f53fbec39d9b18545940f9f598f), [`0e50ec7`](https://github.com/Effect-TS/effect/commit/0e50ec7dbb94390666f292cf9120719bf30a7246), [`9fcdade`](https://github.com/Effect-TS/effect/commit/9fcdade4a8af772b9ccd8b8a24fe8cee0e5d8470), [`57367d5`](https://github.com/Effect-TS/effect/commit/57367d54de55047ff0c5fce9685475e236bf354c), [`35c445f`](https://github.com/Effect-TS/effect/commit/35c445ff18029d192900ea0914c993f58d5cf1a5), [`c917bb9`](https://github.com/Effect-TS/effect/commit/c917bb94a4c1c4e0a24372a8ebb8a5ca232e36b5), [`bc1f358`](https://github.com/Effect-TS/effect/commit/bc1f3583e63344cb2c398d9040d9c975488ed123), [`0e0c9d7`](https://github.com/Effect-TS/effect/commit/0e0c9d7922ff463c1093d9e0576fae12cb0698d5), [`73d40aa`](https://github.com/Effect-TS/effect/commit/73d40aacd8fcae1b48c23f5b0a5c542127401d1d), [`4f1e318`](https://github.com/Effect-TS/effect/commit/4f1e3183f7123591c46224e9c587df7594562a5f), [`9d8d85c`](https://github.com/Effect-TS/effect/commit/9d8d85c1bb7da51970845b8ea830e386e777514a), [`6079fda`](https://github.com/Effect-TS/effect/commit/6079fda7b02f2f01ad91c15ab8c307336f3ba252), [`5101e92`](https://github.com/Effect-TS/effect/commit/5101e92c9c149c153423f43dd7a94f6194653c06), [`d0b3265`](https://github.com/Effect-TS/effect/commit/d0b3265c3262670761471ab3518cf933b1b3b20a), [`7a03c89`](https://github.com/Effect-TS/effect/commit/7a03c893ce6492bf94c0ebfb00b63bf25dcbf83e), [`cea1d9c`](https://github.com/Effect-TS/effect/commit/cea1d9c92601e69ebda040af8a1d860d604d885c), [`078e1f5`](https://github.com/Effect-TS/effect/commit/078e1f5636e31b76a86722a636afc37a8cc25580), [`97bafea`](https://github.com/Effect-TS/effect/commit/97bafeab460833b9781527b437d1cb9cbee63260), [`fab0ab8`](https://github.com/Effect-TS/effect/commit/fab0ab8f7ab15ae596faa4ccf75615a494d11b0b), [`c323d8b`](https://github.com/Effect-TS/effect/commit/c323d8b30dbbe85f9df25b67288b93d5332de333), [`6966353`](https://github.com/Effect-TS/effect/commit/69663534d626003eb10a5e55ab1f13e0379fead1), [`0444004`](https://github.com/Effect-TS/effect/commit/04440041989c1785fe4db286379f2be2c15baa85), [`028bbb3`](https://github.com/Effect-TS/effect/commit/028bbb391e161185da10d974ab33381f769940d7), [`ff5d6e2`](https://github.com/Effect-TS/effect/commit/ff5d6e278a1fdff714315dc1a17075012f05c1f0), [`1bfce93`](https://github.com/Effect-TS/effect/commit/1bfce93e6d2bf0794c11733daf51c2390e7de375), [`7ce815c`](https://github.com/Effect-TS/effect/commit/7ce815cd5af6af991dfc13b890fd22345fc77c20), [`7271a7f`](https://github.com/Effect-TS/effect/commit/7271a7faf1080aa75f2f53ca6a0b5ec9334c1d38), [`475fe5c`](https://github.com/Effect-TS/effect/commit/475fe5c12c2d6504c475797c0634f90da01e1797)]: + - effect@4.0.0-beta.102 + +## 4.0.0-beta.101 + +### Patch Changes + +- [#6518](https://github.com/Effect-TS/effect/pull/6518) [`017f384`](https://github.com/Effect-TS/effect/commit/017f384b1e40eeac220cff6781feb02e89fda349) Thanks @leodenham! - Fix dynamic tools defined with a raw JSON schema sending empty parameter schema to OpenRouter + +- Updated dependencies [[`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`b35ed29`](https://github.com/Effect-TS/effect/commit/b35ed2904f01536d303b21f288daf343cf740462), [`dd44624`](https://github.com/Effect-TS/effect/commit/dd446245736a0e88c807a02f03c21450bb9340fa), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`2bae1ac`](https://github.com/Effect-TS/effect/commit/2bae1accce9d3b72cf6d5aefc9b2161af6d88436)]: + - effect@4.0.0-beta.101 + +## 4.0.0-beta.100 + +### Patch Changes + +- Updated dependencies [[`c1288dd`](https://github.com/Effect-TS/effect/commit/c1288dd1a52a2811ab7df57fc4ce236c6be4c745), [`2b58a3d`](https://github.com/Effect-TS/effect/commit/2b58a3dab6bc99776dddaf76e27d811e0f47f3d8), [`6dc83f2`](https://github.com/Effect-TS/effect/commit/6dc83f26ddf20d48db28cf761dd8f3716e5273fb), [`c1e2fe0`](https://github.com/Effect-TS/effect/commit/c1e2fe0cf93564f4d919e3998874c3e70b0cf30f), [`f3fbae8`](https://github.com/Effect-TS/effect/commit/f3fbae8d7bae0d77cb4f35a1598b26c58e3bf94d), [`e000f80`](https://github.com/Effect-TS/effect/commit/e000f80fd55bcd8edc699fdbf4cd109004f4f754), [`f4ee765`](https://github.com/Effect-TS/effect/commit/f4ee7655ee052cf9ba726fd602bb87c89c7c62a9), [`510b55f`](https://github.com/Effect-TS/effect/commit/510b55f3e21750685dbfd5f476a130c1c5af9dbd), [`31d3fc4`](https://github.com/Effect-TS/effect/commit/31d3fc4327c50867bb8d881fa7353aeb03ea2826), [`875e618`](https://github.com/Effect-TS/effect/commit/875e618c3764a7b817ac863d0af86924449528f2), [`688d46a`](https://github.com/Effect-TS/effect/commit/688d46afd0ef923d983ad3d7385f52f217b28d70), [`6ff5023`](https://github.com/Effect-TS/effect/commit/6ff502363b9840a5a5ee0a24bc6cae734ac3a3eb), [`c0333e7`](https://github.com/Effect-TS/effect/commit/c0333e7f755f42ddcca7051e029da8b4eed527bf), [`06e7e8c`](https://github.com/Effect-TS/effect/commit/06e7e8c66015ee318f871b9d2218dee82df2b108), [`eb9b102`](https://github.com/Effect-TS/effect/commit/eb9b10256c8558881b441c2fef833b7037174400), [`8b155da`](https://github.com/Effect-TS/effect/commit/8b155da06e0740c354ec562957a45ab65eb4573b), [`3a87335`](https://github.com/Effect-TS/effect/commit/3a8733564c5db35271aa20564ed0d344daa2a79f)]: + - effect@4.0.0-beta.100 + +## 4.0.0-beta.99 + +### Patch Changes + +- Updated dependencies [[`8ce4795`](https://github.com/Effect-TS/effect/commit/8ce4795ccbaebca4292757db568c005a992546a4), [`80b539f`](https://github.com/Effect-TS/effect/commit/80b539f8aba68f478c75c35c2b4140c4ffc4fada), [`88a54cc`](https://github.com/Effect-TS/effect/commit/88a54cc341006e3ebcb13482c618f62a680ce199), [`e6e6dba`](https://github.com/Effect-TS/effect/commit/e6e6dba6e9d86e7c2ad27dcedf289db76a19697f), [`bfb203e`](https://github.com/Effect-TS/effect/commit/bfb203e95aa439f731acad37fc3a9a831a190f1c), [`2e9a34a`](https://github.com/Effect-TS/effect/commit/2e9a34ac2bece4f3a206160480c991e3841dc67a), [`55d4eb3`](https://github.com/Effect-TS/effect/commit/55d4eb34f2c64d54f6a25a305b5c5438ebd7934e), [`bddb010`](https://github.com/Effect-TS/effect/commit/bddb010eac3d4436cb094edbbee7460c5440c162), [`a328835`](https://github.com/Effect-TS/effect/commit/a328835e50d76bc96648a1c1550456e8c9f81210), [`5560d05`](https://github.com/Effect-TS/effect/commit/5560d05aa6abdd29466d9c3412cc5e648b0adbde), [`8f6e3ad`](https://github.com/Effect-TS/effect/commit/8f6e3adb185b16e8820b98c509b308086f7ff1af), [`46997fa`](https://github.com/Effect-TS/effect/commit/46997fa60401f5e3c93daa4b61f7df8e31caaab4), [`9e6e12d`](https://github.com/Effect-TS/effect/commit/9e6e12d75c118cd265496f2880490d1f33a5c8bf), [`3394b93`](https://github.com/Effect-TS/effect/commit/3394b93d97d6f24fc38670641d1490289ffca7f1), [`febeabc`](https://github.com/Effect-TS/effect/commit/febeabc3f7c31094da000a23edeaabfe2ab00a38), [`54161c9`](https://github.com/Effect-TS/effect/commit/54161c98f6f3569e0c31842f54e6a257f9421c4c), [`385f7a4`](https://github.com/Effect-TS/effect/commit/385f7a4ee4a7359928597ea56d151dbaf5eb5802), [`7eea4d0`](https://github.com/Effect-TS/effect/commit/7eea4d0b73ec554915d7066a71f46326ce2ba45f), [`7543afe`](https://github.com/Effect-TS/effect/commit/7543afea6f4d97d1f1ad876224323838a48daadd), [`44b9cf3`](https://github.com/Effect-TS/effect/commit/44b9cf3d240d726997b4bbcd0ede48e074d3c456), [`7eea4d0`](https://github.com/Effect-TS/effect/commit/7eea4d0b73ec554915d7066a71f46326ce2ba45f), [`0a8aa6a`](https://github.com/Effect-TS/effect/commit/0a8aa6acb90a72b91c24d17133c950e4cacd8abd), [`c8d9fcf`](https://github.com/Effect-TS/effect/commit/c8d9fcf7b030f7c474effbab2764ce7aee1c7209), [`9ca7f9a`](https://github.com/Effect-TS/effect/commit/9ca7f9a69363e4485645966d5a93b8f9597c5206), [`e7aca89`](https://github.com/Effect-TS/effect/commit/e7aca894bb32fbb785b5830837e6061c415a6015), [`55d7560`](https://github.com/Effect-TS/effect/commit/55d75609b8acf8a1b54c1b1c7fbbb65ec741aa3e), [`f809189`](https://github.com/Effect-TS/effect/commit/f809189ddf6b6011ba43a9901baaa734e315da2a), [`88a54cc`](https://github.com/Effect-TS/effect/commit/88a54cc341006e3ebcb13482c618f62a680ce199), [`0ebdbe7`](https://github.com/Effect-TS/effect/commit/0ebdbe74463dc84385956d0b1e8c2b79ebab5400), [`7517d09`](https://github.com/Effect-TS/effect/commit/7517d09f12a0b183a81bd425962c4e280a68b05d), [`212493b`](https://github.com/Effect-TS/effect/commit/212493b9a1eb98cd1ef6959c707a2e5784a5ae91), [`88a54cc`](https://github.com/Effect-TS/effect/commit/88a54cc341006e3ebcb13482c618f62a680ce199), [`80ea8cb`](https://github.com/Effect-TS/effect/commit/80ea8cb9222ca73f564c8267ab2f82966fea027a), [`8df19f4`](https://github.com/Effect-TS/effect/commit/8df19f4fe81d90cc33ace88b9a77e5534f82d604)]: + - effect@4.0.0-beta.99 + +## 4.0.0-beta.98 + +### Patch Changes + +- Updated dependencies [[`989603b`](https://github.com/Effect-TS/effect-smol/commit/989603b60ab1197b64acf214208e0d370cd1f842), [`214c458`](https://github.com/Effect-TS/effect-smol/commit/214c458084bb6995d543cd37d1055f24be3d454e), [`a037273`](https://github.com/Effect-TS/effect-smol/commit/a0372736ac34796969b051bbba4717d7983f1ebe), [`97fdaa9`](https://github.com/Effect-TS/effect-smol/commit/97fdaa9c1f522c65e579365d314a07878e2b904f), [`b24d248`](https://github.com/Effect-TS/effect-smol/commit/b24d248c8df44222ce642087cde2bd859a2dc709), [`19c222c`](https://github.com/Effect-TS/effect-smol/commit/19c222cac2353a3d7b7733caecb00556fffe9a5c), [`eec85dd`](https://github.com/Effect-TS/effect-smol/commit/eec85ddba09ea326fd268ee33eeffd47e50d4671), [`0082f4f`](https://github.com/Effect-TS/effect-smol/commit/0082f4f74fb139fd578f87f0a790e845133983dc), [`8849052`](https://github.com/Effect-TS/effect-smol/commit/884905232d1e9a365e046d8dde27bf9c5707f57f), [`c15e16a`](https://github.com/Effect-TS/effect-smol/commit/c15e16ad130d1fbde25d912b7ac55995066cb35b), [`01d00a3`](https://github.com/Effect-TS/effect-smol/commit/01d00a3abfbf1f37996cdbe738ea5137c646cdd7), [`8bd4589`](https://github.com/Effect-TS/effect-smol/commit/8bd458975a1b3a8ed042eccf317b93d28ded91e7), [`0082f4f`](https://github.com/Effect-TS/effect-smol/commit/0082f4f74fb139fd578f87f0a790e845133983dc), [`6e08428`](https://github.com/Effect-TS/effect-smol/commit/6e08428d980501b856f846ad3f3f0e4ea46e7786), [`388dcf9`](https://github.com/Effect-TS/effect-smol/commit/388dcf953f65d317547f34d40e6443c5f264205f), [`2b7ce2b`](https://github.com/Effect-TS/effect-smol/commit/2b7ce2b513e7ec2a77822f1116dc6ffb6ba93f4e), [`87bea7e`](https://github.com/Effect-TS/effect-smol/commit/87bea7e16259246f3bcdf565446394751abca953), [`ce38dc3`](https://github.com/Effect-TS/effect-smol/commit/ce38dc33bda805a684432cca071f4dc3c6b9a1ba), [`a807cd1`](https://github.com/Effect-TS/effect-smol/commit/a807cd170341deca8a1cfb52c4222585f2431bb9), [`fd8a356`](https://github.com/Effect-TS/effect-smol/commit/fd8a356f06a8c9ce4e7e0a13fc4021c178ed31de), [`c2a5edc`](https://github.com/Effect-TS/effect-smol/commit/c2a5edc3abd31ad5bc123362bc1213e03e4095c3), [`5946da3`](https://github.com/Effect-TS/effect-smol/commit/5946da3804a1be5e752b05b96bd058cdba50a1bf), [`4ae0c5f`](https://github.com/Effect-TS/effect-smol/commit/4ae0c5ffcbe6c56ddfcb05c639112a079483539e), [`5b2a0bc`](https://github.com/Effect-TS/effect-smol/commit/5b2a0bceea3a28a33a58555210c90a415dc74a76), [`72ac585`](https://github.com/Effect-TS/effect-smol/commit/72ac585884befde6af9208da738699a93f1bae79), [`5e8c1b8`](https://github.com/Effect-TS/effect-smol/commit/5e8c1b82bfafa121311f987a49ab75395e3647a7), [`0f9c078`](https://github.com/Effect-TS/effect-smol/commit/0f9c07841b04183f485ee6e6458de73b290b09f5)]: + - effect@4.0.0-beta.98 + +## 4.0.0-beta.97 + +### Patch Changes + +- Updated dependencies []: + - effect@4.0.0-beta.97 + +## 4.0.0-beta.96 + +### Patch Changes + +- Updated dependencies [[`1503f45`](https://github.com/Effect-TS/effect-smol/commit/1503f45cb5bb2a74f4705252ec505a1f0ade7e62), [`57fe793`](https://github.com/Effect-TS/effect-smol/commit/57fe79316ffbc380b30626a168981fb26ae97459), [`0c2f78f`](https://github.com/Effect-TS/effect-smol/commit/0c2f78f695ec474e1ff5474da183577975e418f5), [`0c2f78f`](https://github.com/Effect-TS/effect-smol/commit/0c2f78f695ec474e1ff5474da183577975e418f5), [`0c2f78f`](https://github.com/Effect-TS/effect-smol/commit/0c2f78f695ec474e1ff5474da183577975e418f5), [`97f29df`](https://github.com/Effect-TS/effect-smol/commit/97f29df457f7ffd07cfb4b379315c12c086af805)]: + - effect@4.0.0-beta.96 + +## 4.0.0-beta.95 + +### Patch Changes + +- Updated dependencies [[`a482442`](https://github.com/Effect-TS/effect-smol/commit/a482442abdeb490e9652b854ec3495e4aa7273e7), [`fbefa85`](https://github.com/Effect-TS/effect-smol/commit/fbefa850fab2f0a302c20614496aeaaa2a8b5590), [`0b4a32f`](https://github.com/Effect-TS/effect-smol/commit/0b4a32f4260f0d8500942a133001b0d349328102), [`18a49e1`](https://github.com/Effect-TS/effect-smol/commit/18a49e1786679456258002ff9397faf02f678c2d), [`266cb90`](https://github.com/Effect-TS/effect-smol/commit/266cb90bb2c17aabc40563c32db334f09ba3d74b), [`912f095`](https://github.com/Effect-TS/effect-smol/commit/912f095a34572bbd3cedf6edb27878443e3e4a95), [`a6718f9`](https://github.com/Effect-TS/effect-smol/commit/a6718f9e00a15ca903b0732da46116cbf3d6aca7), [`bef5154`](https://github.com/Effect-TS/effect-smol/commit/bef51540a243aa2f872a00c01d0cd58b7a769baa), [`18e0564`](https://github.com/Effect-TS/effect-smol/commit/18e0564bd0f8ebbdfcaf1e2c21529948e9e4a81d), [`fb50f14`](https://github.com/Effect-TS/effect-smol/commit/fb50f14fc3657c1973785aa5b72ecf0b0d28e0b2)]: + - effect@4.0.0-beta.95 + +## 4.0.0-beta.94 + +### Patch Changes + +- Updated dependencies [[`95a0e9b`](https://github.com/Effect-TS/effect-smol/commit/95a0e9bb62797af0e81c9998773405f248f218c5), [`a0a3490`](https://github.com/Effect-TS/effect-smol/commit/a0a3490bbce765f199d8e077aceac504f0462e63), [`f11ce73`](https://github.com/Effect-TS/effect-smol/commit/f11ce73af60823754dc24194f4ffc561b9ea1c2d), [`ff30b6e`](https://github.com/Effect-TS/effect-smol/commit/ff30b6e7c2c63ffc56a4c5818d6d86b01b5ad528), [`1caab3c`](https://github.com/Effect-TS/effect-smol/commit/1caab3cc30f626efbf15e59d74f539a487e5c85c), [`aa80c47`](https://github.com/Effect-TS/effect-smol/commit/aa80c4775a04db87553e5568764cab7e32a72814), [`c2ae4fc`](https://github.com/Effect-TS/effect-smol/commit/c2ae4fce2f03a4cd1861c2b1179da7df656e662d), [`a0a3490`](https://github.com/Effect-TS/effect-smol/commit/a0a3490bbce765f199d8e077aceac504f0462e63)]: + - effect@4.0.0-beta.94 + +## 4.0.0-beta.93 + +### Patch Changes + +- Updated dependencies [[`00652fe`](https://github.com/Effect-TS/effect-smol/commit/00652fe95c18f87208e91343eb8bf218faa2f677), [`6c58167`](https://github.com/Effect-TS/effect-smol/commit/6c5816746eaf91d2a3c7c899c5720809fa230ae3), [`2bc5415`](https://github.com/Effect-TS/effect-smol/commit/2bc541501a7ef89e542d7cb98e96beb53cd205cc), [`e11cccc`](https://github.com/Effect-TS/effect-smol/commit/e11cccc7d5fe631abccc7d6e3bd296938de0fa2e), [`ba7e77e`](https://github.com/Effect-TS/effect-smol/commit/ba7e77e046b8641a3a4e9750bb88ca4a1d063d3f), [`5713ee7`](https://github.com/Effect-TS/effect-smol/commit/5713ee7edbc3054efde407b2286bbfd45bbc6e1c)]: + - effect@4.0.0-beta.93 + +## 4.0.0-beta.92 + +### Patch Changes + +- Updated dependencies [[`affdc13`](https://github.com/Effect-TS/effect-smol/commit/affdc139045cc325dce321a84a580fdc1b2da7b9)]: + - effect@4.0.0-beta.92 + +## 4.0.0-beta.91 + +### Patch Changes + +- Updated dependencies [[`b135b25`](https://github.com/Effect-TS/effect-smol/commit/b135b2517fca9e7839734ace3699a7dfa75b9075), [`aaa21a3`](https://github.com/Effect-TS/effect-smol/commit/aaa21a369a171c600db294f2a4f640583043e150), [`3475ee6`](https://github.com/Effect-TS/effect-smol/commit/3475ee6c2bda6b05c6d7a12ce30c8bb840b5b1a6)]: + - effect@4.0.0-beta.91 + ## 4.0.0-beta.90 ### Patch Changes diff --git a/repos/effect/packages/ai/openrouter/codegen.yml b/repos/effect/packages/ai/openrouter/codegen.yml index b556693a36..7fea7aae9d 100644 --- a/repos/effect/packages/ai/openrouter/codegen.yml +++ b/repos/effect/packages/ai/openrouter/codegen.yml @@ -10,26 +10,25 @@ excludeAnnotations: - default disableAdditionalProperties: true patches: - # Replace OpenResponsesStreamEvent with a flat oneOf of $refs. - # The original uses allOf wrappers that inline OpenResponsesNonStreamingResponse - # into each variant, producing lines over 1M chars that crash dprint. - - '[{"op":"replace","path":"/components/schemas/OpenResponsesStreamEvent","value":{"oneOf":[{"$ref":"#/components/schemas/OpenResponsesCreatedEvent"},{"$ref":"#/components/schemas/OpenResponsesInProgressEvent"},{"$ref":"#/components/schemas/OpenResponsesCompletedEvent"},{"$ref":"#/components/schemas/OpenResponsesIncompleteEvent"},{"$ref":"#/components/schemas/OpenResponsesFailedEvent"},{"$ref":"#/components/schemas/OpenResponsesErrorEvent"},{"$ref":"#/components/schemas/OpenResponsesOutputItemAddedEvent"},{"$ref":"#/components/schemas/OpenResponsesOutputItemDoneEvent"},{"$ref":"#/components/schemas/OpenResponsesContentPartAddedEvent"},{"$ref":"#/components/schemas/OpenResponsesContentPartDoneEvent"},{"$ref":"#/components/schemas/OpenResponsesTextDeltaEvent"},{"$ref":"#/components/schemas/OpenResponsesTextDoneEvent"},{"$ref":"#/components/schemas/OpenResponsesRefusalDeltaEvent"},{"$ref":"#/components/schemas/OpenResponsesRefusalDoneEvent"},{"$ref":"#/components/schemas/OpenResponsesOutputTextAnnotationAddedEvent"},{"$ref":"#/components/schemas/OpenResponsesFunctionCallArgumentsDeltaEvent"},{"$ref":"#/components/schemas/OpenResponsesFunctionCallArgumentsDoneEvent"},{"$ref":"#/components/schemas/OpenResponsesReasoningDeltaEvent"},{"$ref":"#/components/schemas/OpenResponsesReasoningDoneEvent"},{"$ref":"#/components/schemas/OpenResponsesReasoningSummaryPartAddedEvent"},{"$ref":"#/components/schemas/OpenResponsesReasoningSummaryPartDoneEvent"},{"$ref":"#/components/schemas/OpenResponsesReasoningSummaryTextDeltaEvent"},{"$ref":"#/components/schemas/OpenResponsesReasoningSummaryTextDoneEvent"},{"$ref":"#/components/schemas/OpenResponsesImageGenCallInProgress"},{"$ref":"#/components/schemas/OpenResponsesImageGenCallGenerating"},{"$ref":"#/components/schemas/OpenResponsesImageGenCallPartialImage"},{"$ref":"#/components/schemas/OpenResponsesImageGenCallCompleted"}]}}]' - # Fix AssistantMessage images to include type:"image_url" discriminator and nullable - - '[{"op":"replace","path":"/components/schemas/AssistantMessage/properties/images","value":{"anyOf":[{"type":"array","items":{"type":"object","properties":{"type":{"type":"string","const":"image_url"},"image_url":{"type":"object","properties":{"url":{"type":"string"}},"required":["url"]}},"required":["type","image_url"]}},{"type":"null"}]}}]' - # Add images to ChatStreamingMessageChunk (streaming delta) - - '[{"op":"add","path":"/components/schemas/ChatStreamingMessageChunk/properties/images","value":{"anyOf":[{"type":"array","items":{"type":"object","properties":{"type":{"type":"string","const":"image_url"},"image_url":{"type":"object","properties":{"url":{"type":"string"}},"required":["url"]}},"required":["type","image_url"]}},{"type":"null"}]}}]' - # Add annotations to AssistantMessage (non-streaming) - - '[{"op":"add","path":"/components/schemas/AssistantMessage/properties/annotations","value":{"anyOf":[{"type":"array","items":{"oneOf":[{"type":"object","properties":{"type":{"type":"string","const":"url_citation"},"url_citation":{"type":"object","properties":{"url":{"type":"string"},"title":{"type":"string"},"start_index":{"type":"number"},"end_index":{"type":"number"},"content":{"type":"string"}},"required":["url"]}},"required":["type","url_citation"]},{"type":"object","properties":{"type":{"type":"string","const":"file_annotation"},"file_annotation":{"type":"object","properties":{"file_id":{"type":"string"},"quote":{"type":"string"}},"required":["file_id"]}},"required":["type","file_annotation"]},{"type":"object","properties":{"type":{"type":"string","const":"file"},"file":{"type":"object","properties":{"hash":{"type":"string"},"name":{"type":"string"},"content":{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"text":{"type":"string"}},"required":["type"]}}},"required":["hash","name"]}},"required":["type","file"]}]}},{"type":"null"}]}}]' - # Add annotations to ChatStreamingMessageChunk (streaming delta) - - '[{"op":"add","path":"/components/schemas/ChatStreamingMessageChunk/properties/annotations","value":{"anyOf":[{"type":"array","items":{"oneOf":[{"type":"object","properties":{"type":{"type":"string","const":"url_citation"},"url_citation":{"type":"object","properties":{"url":{"type":"string"},"title":{"type":"string"},"start_index":{"type":"number"},"end_index":{"type":"number"},"content":{"type":"string"}},"required":["url"]}},"required":["type","url_citation"]},{"type":"object","properties":{"type":{"type":"string","const":"file_annotation"},"file_annotation":{"type":"object","properties":{"file_id":{"type":"string"},"quote":{"type":"string"}},"required":["file_id"]}},"required":["type","file_annotation"]},{"type":"object","properties":{"type":{"type":"string","const":"file"},"file":{"type":"object","properties":{"hash":{"type":"string"},"name":{"type":"string"},"content":{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"text":{"type":"string"}},"required":["type"]}}},"required":["hash","name"]}},"required":["type","file"]}]}},{"type":"null"}]}}]' + # Flatten Responses streaming event allOf wrappers to keep generated schemas referential + - '[{"op":"replace","path":"/components/schemas/OpenResponsesCreatedEvent","value":{"type":"object","description":"Event emitted when a response is created","properties":{"response":{"$ref":"#/components/schemas/OpenResponsesResult"},"sequence_number":{"type":"integer"},"type":{"type":"string","enum":["response.created"]}},"required":["type","response","sequence_number"]}},{"op":"replace","path":"/components/schemas/OpenResponsesInProgressEvent","value":{"type":"object","description":"Event emitted when a response is in progress","properties":{"response":{"$ref":"#/components/schemas/OpenResponsesResult"},"sequence_number":{"type":"integer"},"type":{"type":"string","enum":["response.in_progress"]}},"required":["type","response","sequence_number"]}},{"op":"replace","path":"/components/schemas/StreamEventsResponseCompleted","value":{"type":"object","description":"Event emitted when a response has completed successfully","properties":{"response":{"$ref":"#/components/schemas/OpenResponsesResult"},"sequence_number":{"type":"integer"},"type":{"type":"string","enum":["response.completed"]}},"required":["type","response","sequence_number"]}},{"op":"replace","path":"/components/schemas/StreamEventsResponseIncomplete","value":{"type":"object","description":"Event emitted when a response is incomplete","properties":{"response":{"$ref":"#/components/schemas/OpenResponsesResult"},"sequence_number":{"type":"integer"},"type":{"type":"string","enum":["response.incomplete"]}},"required":["type","response","sequence_number"]}},{"op":"replace","path":"/components/schemas/StreamEventsResponseFailed","value":{"type":"object","description":"Event emitted when a response has failed","properties":{"response":{"$ref":"#/components/schemas/OpenResponsesResult"},"sequence_number":{"type":"integer"},"type":{"type":"string","enum":["response.failed"]}},"required":["type","response","sequence_number"]}},{"op":"replace","path":"/components/schemas/StreamEventsResponseOutputItemAdded","value":{"type":"object","description":"Event emitted when a new output item is added to the response","properties":{"item":{"$ref":"#/components/schemas/OutputItems"},"output_index":{"type":"integer"},"sequence_number":{"type":"integer"},"type":{"type":"string","enum":["response.output_item.added"]}},"required":["type","output_index","item","sequence_number"]}},{"op":"replace","path":"/components/schemas/StreamEventsResponseOutputItemDone","value":{"type":"object","description":"Event emitted when an output item is complete","properties":{"item":{"$ref":"#/components/schemas/OutputItems"},"output_index":{"type":"integer"},"sequence_number":{"type":"integer"},"type":{"type":"string","enum":["response.output_item.done"]}},"required":["type","output_index","item","sequence_number"]}},{"op":"replace","path":"/components/schemas/ContentPartAddedEvent","value":{"type":"object","description":"Event emitted when a new content part is added to an output item","properties":{"content_index":{"type":"integer"},"item_id":{"type":"string"},"output_index":{"type":"integer"},"part":{"anyOf":[{"$ref":"#/components/schemas/ResponseOutputText"},{"$ref":"#/components/schemas/ReasoningTextContent"},{"$ref":"#/components/schemas/OpenAIResponsesRefusalContent"}]},"sequence_number":{"type":"integer"},"type":{"type":"string","enum":["response.content_part.added"]}},"required":["type","output_index","item_id","content_index","part","sequence_number"]}},{"op":"replace","path":"/components/schemas/ContentPartDoneEvent","value":{"type":"object","description":"Event emitted when a content part is complete","properties":{"content_index":{"type":"integer"},"item_id":{"type":"string"},"output_index":{"type":"integer"},"part":{"anyOf":[{"$ref":"#/components/schemas/ResponseOutputText"},{"$ref":"#/components/schemas/ReasoningTextContent"},{"$ref":"#/components/schemas/OpenAIResponsesRefusalContent"}]},"sequence_number":{"type":"integer"},"type":{"type":"string","enum":["response.content_part.done"]}},"required":["type","output_index","item_id","content_index","part","sequence_number"]}}]' + # Fix ChatAssistantMessage images to include type:"image_url" discriminator and nullable + - '[{"op":"replace","path":"/components/schemas/ChatAssistantMessage/properties/images","value":{"anyOf":[{"type":"array","items":{"type":"object","properties":{"type":{"type":"string","const":"image_url"},"image_url":{"type":"object","properties":{"url":{"type":"string"}},"required":["url"]}},"required":["type","image_url"]}},{"type":"null"}]}}]' + # Add images to ChatStreamDelta (streaming delta) + - '[{"op":"add","path":"/components/schemas/ChatStreamDelta/properties/images","value":{"anyOf":[{"type":"array","items":{"type":"object","properties":{"type":{"type":"string","const":"image_url"},"image_url":{"type":"object","properties":{"url":{"type":"string"}},"required":["url"]}},"required":["type","image_url"]}},{"type":"null"}]}}]' + # Add annotations to ChatAssistantMessage (non-streaming) + - '[{"op":"add","path":"/components/schemas/ChatAssistantMessage/properties/annotations","value":{"anyOf":[{"type":"array","items":{"oneOf":[{"type":"object","properties":{"type":{"type":"string","const":"url_citation"},"url_citation":{"type":"object","properties":{"url":{"type":"string"},"title":{"type":"string"},"start_index":{"type":"number"},"end_index":{"type":"number"},"content":{"type":"string"}},"required":["url"]}},"required":["type","url_citation"]},{"type":"object","properties":{"type":{"type":"string","const":"file_annotation"},"file_annotation":{"type":"object","properties":{"file_id":{"type":"string"},"quote":{"type":"string"}},"required":["file_id"]}},"required":["type","file_annotation"]},{"type":"object","properties":{"type":{"type":"string","const":"file"},"file":{"type":"object","properties":{"hash":{"type":"string"},"name":{"type":"string"},"content":{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"text":{"type":"string"}},"required":["type"]}}},"required":["hash","name"]}},"required":["type","file"]}]}},{"type":"null"}]}}]' + # Add annotations to ChatStreamDelta (streaming delta) + - '[{"op":"add","path":"/components/schemas/ChatStreamDelta/properties/annotations","value":{"anyOf":[{"type":"array","items":{"oneOf":[{"type":"object","properties":{"type":{"type":"string","const":"url_citation"},"url_citation":{"type":"object","properties":{"url":{"type":"string"},"title":{"type":"string"},"start_index":{"type":"number"},"end_index":{"type":"number"},"content":{"type":"string"}},"required":["url"]}},"required":["type","url_citation"]},{"type":"object","properties":{"type":{"type":"string","const":"file_annotation"},"file_annotation":{"type":"object","properties":{"file_id":{"type":"string"},"quote":{"type":"string"}},"required":["file_id"]}},"required":["type","file_annotation"]},{"type":"object","properties":{"type":{"type":"string","const":"file"},"file":{"type":"object","properties":{"hash":{"type":"string"},"name":{"type":"string"},"content":{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"text":{"type":"string"}},"required":["type"]}}},"required":["hash","name"]}},"required":["type","file"]}]}},{"type":"null"}]}}]' # Make tool call delta fields nullable (models like kimi-k2.5, minimax-m2.5 send null) - - '[{"op":"replace","path":"/components/schemas/ChatStreamingMessageToolCall/properties/id","value":{"anyOf":[{"type":"string"},{"type":"null"}]}},{"op":"replace","path":"/components/schemas/ChatStreamingMessageToolCall/properties/type","value":{"anyOf":[{"type":"string","const":"function"},{"type":"null"}]}},{"op":"replace","path":"/components/schemas/ChatStreamingMessageToolCall/properties/function/properties/name","value":{"anyOf":[{"type":"string"},{"type":"null"}]}}]' + - '[{"op":"replace","path":"/components/schemas/ChatStreamToolCall/properties/id","value":{"anyOf":[{"type":"string"},{"type":"null"}]}},{"op":"replace","path":"/components/schemas/ChatStreamToolCall/properties/type","value":{"anyOf":[{"type":"string","const":"function"},{"type":"null"}]}},{"op":"replace","path":"/components/schemas/ChatStreamToolCall/properties/function/properties/name","value":{"anyOf":[{"type":"string"},{"type":"null"}]}}]' # Make finish_reason optional (only present on final streaming chunk) - - '[{"op":"remove","path":"/components/schemas/ChatStreamingChoice/required/1"}]' + - '[{"op":"remove","path":"/components/schemas/ChatStreamChoice/required/1"}]' replacements: - # Schema.Unknown doesn't work with Schema.toCodecJson (used by HttpClientResponse.schemaBodyJson) - # Replace with Schema.Json which properly handles arbitrary JSON values - - from: "Schema.Record(Schema.String, Schema.Unknown)" - to: "Schema.Record(Schema.String, Schema.Json)" - - from: "{ readonly [x: string]: unknown }" - to: "{ readonly [x: string]: Schema.Json }" + # Help TypeScript serialize oversized generated schema values + - from: "export const StreamEvents = Schema.Union([" + to: "export const StreamEvents: Schema.Schema = Schema.Union([" + - from: "export const ResponsesStreamingResponse = Schema.Struct({ \"data\": StreamEvents })" + to: "export const ResponsesStreamingResponse: Schema.Schema = Schema.Struct({ \"data\": StreamEvents })" + - from: "export const CreateResponses200Sse = ResponsesStreamingResponse" + to: "export const CreateResponses200Sse: Schema.Schema = ResponsesStreamingResponse" diff --git a/repos/effect/packages/ai/openrouter/docgen.json b/repos/effect/packages/ai/openrouter/docgen.json deleted file mode 100644 index 00f806cd63..0000000000 --- a/repos/effect/packages/ai/openrouter/docgen.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "$schema": "../../node_modules/@effect/docgen/schema.json", - "srcLink": "https://github.com/Effect-TS/effect/tree/main/packages/ai/openrouter/src/", - "exclude": ["src/Generated.ts", "src/internal/**/*.ts"], - "tscExecutable": "tsgo", - "examplesCompilerOptions": { - "noEmit": true, - "strict": true, - "skipLibCheck": true, - "moduleResolution": "Bundler", - "module": "ES2022", - "target": "ES2022", - "lib": ["ES2022", "DOM", "DOM.Iterable"], - "rewriteRelativeImportExtensions": true, - "allowImportingTsExtensions": true, - "paths": { - "effect": ["../../../effect/src/index.js"], - "effect/*": ["../../../effect/src/*.js"] - }, - "plugins": [ - { "name": "@effect/language-service", "includeSuggestionsInTsc": false } - ] - } -} diff --git a/repos/effect/packages/ai/openrouter/package.json b/repos/effect/packages/ai/openrouter/package.json index ee8d89e45a..b7ffb08ee8 100644 --- a/repos/effect/packages/ai/openrouter/package.json +++ b/repos/effect/packages/ai/openrouter/package.json @@ -1,17 +1,17 @@ { "name": "@effect/ai-openrouter", - "version": "4.0.0-beta.90", + "version": "4.0.0-beta.107", "type": "module", "license": "MIT", "description": "An OpenRouter provider integration for Effect AI SDK", "homepage": "https://effect.website", "repository": { "type": "git", - "url": "https://github.com/Effect-TS/effect-smol.git", + "url": "https://github.com/Effect-TS/effect.git", "directory": "packages/ai/openrouter" }, "bugs": { - "url": "https://github.com/Effect-TS/effect-smol/issues" + "url": "https://github.com/Effect-TS/effect/issues" }, "tags": [ "typescript", @@ -29,6 +29,7 @@ ".": "./src/index.ts", "./*": "./src/*.ts", "./internal/*": null, + "./index": null, "./*/index": null }, "files": [ @@ -36,7 +37,10 @@ "dist/**/*.js", "dist/**/*.js.map", "dist/**/*.d.ts", - "dist/**/*.d.ts.map" + "dist/**/*.d.ts.map", + "AGENTS.md", + "CLAUDE.md", + "ai-docs/**/*" ], "publishConfig": { "access": "public", @@ -46,17 +50,15 @@ ".": "./dist/index.js", "./*": "./dist/*.js", "./internal/*": null, + "./index": null, "./*/index": null } }, "scripts": { "codegen": "effect-utils codegen", "build": "tsc -b tsconfig.json && pnpm babel", - "build:tsgo": "tsgo -b tsconfig.json && pnpm babel", "babel": "babel dist --plugins annotate-pure-calls --out-dir dist --source-maps", - "check": "tsc -b tsconfig.json", - "test": "vitest", - "coverage": "vitest --coverage" + "check": "tsc -b tsconfig.json" }, "devDependencies": { "effect": "workspace:^" diff --git a/repos/effect/packages/ai/openrouter/src/Generated.ts b/repos/effect/packages/ai/openrouter/src/Generated.ts index c015b4429b..4c3859a375 100644 --- a/repos/effect/packages/ai/openrouter/src/Generated.ts +++ b/repos/effect/packages/ai/openrouter/src/Generated.ts @@ -1,5 +1,5 @@ /** - * @since 4.0.0 + * @since 1.0.0 */ import * as Data from "effect/Data" @@ -13,2382 +13,3371 @@ import * as HttpClientError from "effect/unstable/http/HttpClientError" import * as HttpClientRequest from "effect/unstable/http/HttpClientRequest" import * as HttpClientResponse from "effect/unstable/http/HttpClientResponse" // non-recursive definitions -export type OpenAIResponsesResponseStatus = - | "completed" - | "incomplete" - | "in_progress" - | "failed" - | "cancelled" - | "queued" -export const OpenAIResponsesResponseStatus = Schema.Literals([ - "completed", - "incomplete", - "in_progress", - "failed", - "cancelled", - "queued" -]) -export type FileCitation = { - readonly "type": "file_citation" +export type AABenchmarkEntry = { + readonly "agentic_index": number | null + readonly "coding_index": number | null + readonly "intelligence_index": number | null +} +export const AABenchmarkEntry = Schema.Struct({ + "agentic_index": Schema.Union([ + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), + Schema.Null + ]).annotate({ "description": "Artificial Analysis Agentic Index score", "format": "double" }), + "coding_index": Schema.Union([ + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), + Schema.Null + ]).annotate({ "description": "Artificial Analysis Coding Index score", "format": "double" }), + "intelligence_index": Schema.Union([ + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), + Schema.Null + ]).annotate({ "description": "Artificial Analysis Intelligence Index score", "format": "double" }) +}).annotate({ "description": "Artificial Analysis benchmark index scores.", "identifier": "AABenchmarkEntry" }) +export type ActivityItem = { + readonly "byok_usage_inference": number + readonly "completion_tokens": number + readonly "date": string + readonly "endpoint_id": string + readonly "model": string + readonly "model_permaslug": string + readonly "prompt_tokens": number + readonly "provider_name": string + readonly "reasoning_tokens": number + readonly "requests": number + readonly "usage": number +} +export const ActivityItem = Schema.Struct({ + "byok_usage_inference": Schema.Number.annotate({ + "description": "BYOK inference cost in USD (external credits spent)", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "completion_tokens": Schema.Number.annotate({ "description": "Total completion tokens generated" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ), + "date": Schema.String.annotate({ "description": "Date of the activity (YYYY-MM-DD format)" }), + "endpoint_id": Schema.String.annotate({ "description": "Unique identifier for the endpoint" }), + "model": Schema.String.annotate({ "description": "Model slug (e.g., \"openai/gpt-4.1\")" }), + "model_permaslug": Schema.String.annotate({ "description": "Model permaslug (e.g., \"openai/gpt-4.1-2025-04-14\")" }), + "prompt_tokens": Schema.Number.annotate({ "description": "Total prompt tokens used" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ), + "provider_name": Schema.String.annotate({ "description": "Name of the provider serving this endpoint" }), + "reasoning_tokens": Schema.Number.annotate({ "description": "Total reasoning tokens used" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ), + "requests": Schema.Number.annotate({ "description": "Number of requests made" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ), + "usage": Schema.Number.annotate({ "description": "Total cost in USD (OpenRouter credits spent)", "format": "double" }) + .check(Schema.isFinite().annotate({ "expected": "a finite number" })) +}).annotate({ "identifier": "ActivityItem" }) +export type AdvisorNestedTool = { readonly "parameters"?: {}; readonly "type": string } +export const AdvisorNestedTool = Schema.Struct({ + "parameters": Schema.optionalKey(Schema.Struct({})), + "type": Schema.String +}).annotate({ + "description": + "A tool made available to the advisor sub-agent. Only OpenRouter server tools (e.g. openrouter:web_search) are supported; function tools are rejected because the advisor has no way to execute them. The advisor tool may not list itself.", + "identifier": "AdvisorNestedTool" +}) +export type AdvisorReasoning = { + readonly "effort"?: "max" | "xhigh" | "high" | "medium" | "low" | "minimal" | "none" + readonly "max_tokens"?: number +} +export const AdvisorReasoning = Schema.Struct({ + "effort": Schema.optionalKey( + Schema.Literals(["max", "xhigh", "high", "medium", "low", "minimal", "none"]).annotate({ + "description": "Reasoning effort level for the advisor call." + }) + ), + "max_tokens": Schema.optionalKey( + Schema.Number.annotate({ "description": "Maximum number of reasoning tokens the advisor may use." }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ) + ) +}).annotate({ + "description": + "Reasoning configuration forwarded to the advisor call. Use this to control reasoning effort and token budget for models that support extended thinking.", + "identifier": "AdvisorReasoning" +}) +export type AnthropicAdvisorToolResult = { + readonly "content": {} + readonly "tool_use_id": string + readonly "type": "advisor_tool_result" +} +export const AnthropicAdvisorToolResult = Schema.Struct({ + "content": Schema.Struct({}), + "tool_use_id": Schema.String, + "type": Schema.Literal("advisor_tool_result") +}).annotate({ "identifier": "AnthropicAdvisorToolResult" }) +export type AnthropicAllowedCallers = ReadonlyArray<"direct" | "code_execution_20250825" | "code_execution_20260120"> +export const AnthropicAllowedCallers = Schema.Array( + Schema.Literals(["direct", "code_execution_20250825", "code_execution_20260120"]) +).annotate({ "identifier": "AnthropicAllowedCallers" }) +export type AnthropicBase64PdfSource = { + readonly "data": string + readonly "media_type": "application/pdf" + readonly "type": "base64" +} +export const AnthropicBase64PdfSource = Schema.Struct({ + "data": Schema.String, + "media_type": Schema.Literal("application/pdf"), + "type": Schema.Literal("base64") +}).annotate({ "identifier": "AnthropicBase64PdfSource" }) +export type AnthropicBashCodeExecutionOutput = { readonly "file_id": string - readonly "filename": string - readonly "index": number + readonly "type": "bash_code_execution_output" } -export const FileCitation = Schema.Struct({ - "type": Schema.Literal("file_citation"), +export const AnthropicBashCodeExecutionOutput = Schema.Struct({ "file_id": Schema.String, - "filename": Schema.String, - "index": Schema.Number.check(Schema.isFinite()) + "type": Schema.Literal("bash_code_execution_output") +}).annotate({ "identifier": "AnthropicBashCodeExecutionOutput" }) +export type AnthropicBashCodeExecutionToolResultError = { + readonly "error_code": + | "invalid_tool_input" + | "unavailable" + | "too_many_requests" + | "execution_time_exceeded" + | "output_file_too_large" + readonly "type": "bash_code_execution_tool_result_error" +} +export const AnthropicBashCodeExecutionToolResultError = Schema.Struct({ + "error_code": Schema.Literals([ + "invalid_tool_input", + "unavailable", + "too_many_requests", + "execution_time_exceeded", + "output_file_too_large" + ]), + "type": Schema.Literal("bash_code_execution_tool_result_error") +}).annotate({ "identifier": "AnthropicBashCodeExecutionToolResultError" }) +export type AnthropicCacheControlTtl = "5m" | "1h" +export const AnthropicCacheControlTtl = Schema.Literals(["5m", "1h"]).annotate({ + "identifier": "AnthropicCacheControlTtl" }) -export type URLCitation = { - readonly "type": "url_citation" +export type Objects_ = { + readonly "ephemeral_1h_input_tokens": number + readonly "ephemeral_5m_input_tokens": number + readonly [x: string]: Schema.Json +} +export const Objects_ = Schema.StructWithRest( + Schema.Struct({ + "ephemeral_1h_input_tokens": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "ephemeral_5m_input_tokens": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] +) +export type AnthropicCitationCharLocation = { + readonly "cited_text": string + readonly "document_index": number + readonly "document_title": string | null + readonly "end_char_index": number + readonly "file_id": string | null + readonly "start_char_index": number + readonly "type": "char_location" +} +export const AnthropicCitationCharLocation = Schema.Struct({ + "cited_text": Schema.String, + "document_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "document_title": Schema.Union([Schema.String, Schema.Null]), + "end_char_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "file_id": Schema.Union([Schema.String, Schema.Null]), + "start_char_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("char_location") +}).annotate({ "identifier": "AnthropicCitationCharLocation" }) +export type AnthropicCitationCharLocationParam = { + readonly "cited_text": string + readonly "document_index": number + readonly "document_title": string | null + readonly "end_char_index": number + readonly "start_char_index": number + readonly "type": "char_location" +} +export const AnthropicCitationCharLocationParam = Schema.Struct({ + "cited_text": Schema.String, + "document_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "document_title": Schema.Union([Schema.String, Schema.Null]), + "end_char_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "start_char_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("char_location") +}).annotate({ "identifier": "AnthropicCitationCharLocationParam" }) +export type AnthropicCitationContentBlockLocation = { + readonly "cited_text": string + readonly "document_index": number + readonly "document_title": string | null + readonly "end_block_index": number + readonly "file_id": string | null + readonly "start_block_index": number + readonly "type": "content_block_location" +} +export const AnthropicCitationContentBlockLocation = Schema.Struct({ + "cited_text": Schema.String, + "document_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "document_title": Schema.Union([Schema.String, Schema.Null]), + "end_block_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "file_id": Schema.Union([Schema.String, Schema.Null]), + "start_block_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("content_block_location") +}).annotate({ "identifier": "AnthropicCitationContentBlockLocation" }) +export type AnthropicCitationContentBlockLocationParam = { + readonly "cited_text": string + readonly "document_index": number + readonly "document_title": string | null + readonly "end_block_index": number + readonly "start_block_index": number + readonly "type": "content_block_location" +} +export const AnthropicCitationContentBlockLocationParam = Schema.Struct({ + "cited_text": Schema.String, + "document_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "document_title": Schema.Union([Schema.String, Schema.Null]), + "end_block_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "start_block_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("content_block_location") +}).annotate({ "identifier": "AnthropicCitationContentBlockLocationParam" }) +export type AnthropicCitationPageLocation = { + readonly "cited_text": string + readonly "document_index": number + readonly "document_title": string | null + readonly "end_page_number": number + readonly "file_id": string | null + readonly "start_page_number": number + readonly "type": "page_location" +} +export const AnthropicCitationPageLocation = Schema.Struct({ + "cited_text": Schema.String, + "document_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "document_title": Schema.Union([Schema.String, Schema.Null]), + "end_page_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "file_id": Schema.Union([Schema.String, Schema.Null]), + "start_page_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("page_location") +}).annotate({ "identifier": "AnthropicCitationPageLocation" }) +export type AnthropicCitationPageLocationParam = { + readonly "cited_text": string + readonly "document_index": number + readonly "document_title": string | null + readonly "end_page_number": number + readonly "start_page_number": number + readonly "type": "page_location" +} +export const AnthropicCitationPageLocationParam = Schema.Struct({ + "cited_text": Schema.String, + "document_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "document_title": Schema.Union([Schema.String, Schema.Null]), + "end_page_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "start_page_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("page_location") +}).annotate({ "identifier": "AnthropicCitationPageLocationParam" }) +export type Objects_1 = { readonly "enabled": boolean; readonly [x: string]: Schema.Json } +export const Objects_1 = Schema.StructWithRest(Schema.Struct({ "enabled": Schema.Boolean }), [ + Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })) +]) +export type AnthropicCitationSearchResultLocation = { + readonly "cited_text": string + readonly "end_block_index": number + readonly "search_result_index": number + readonly "source": string + readonly "start_block_index": number + readonly "title": string | null + readonly "type": "search_result_location" +} +export const AnthropicCitationSearchResultLocation = Schema.Struct({ + "cited_text": Schema.String, + "end_block_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "search_result_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "source": Schema.String, + "start_block_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "title": Schema.Union([Schema.String, Schema.Null]), + "type": Schema.Literal("search_result_location") +}).annotate({ "identifier": "AnthropicCitationSearchResultLocation" }) +export type AnthropicCitationSearchResultLocationParam = { + readonly "cited_text": string + readonly "end_block_index": number + readonly "search_result_index": number + readonly "source": string + readonly "start_block_index": number + readonly "title": string | null + readonly "type": "search_result_location" +} +export const AnthropicCitationSearchResultLocationParam = Schema.Struct({ + "cited_text": Schema.String, + "end_block_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "search_result_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "source": Schema.String, + "start_block_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "title": Schema.Union([Schema.String, Schema.Null]), + "type": Schema.Literal("search_result_location") +}).annotate({ "identifier": "AnthropicCitationSearchResultLocationParam" }) +export type AnthropicCitationWebSearchResultLocation = { + readonly "cited_text": string + readonly "encrypted_index": string + readonly "title": string | null + readonly "type": "web_search_result_location" readonly "url": string - readonly "title": string - readonly "start_index": number - readonly "end_index": number } -export const URLCitation = Schema.Struct({ - "type": Schema.Literal("url_citation"), - "url": Schema.String, - "title": Schema.String, - "start_index": Schema.Number.check(Schema.isFinite()), - "end_index": Schema.Number.check(Schema.isFinite()) -}) -export type FilePath = { readonly "type": "file_path"; readonly "file_id": string; readonly "index": number } -export const FilePath = Schema.Struct({ - "type": Schema.Literal("file_path"), +export const AnthropicCitationWebSearchResultLocation = Schema.Struct({ + "cited_text": Schema.String, + "encrypted_index": Schema.String, + "title": Schema.Union([Schema.String, Schema.Null]), + "type": Schema.Literal("web_search_result_location"), + "url": Schema.String +}).annotate({ "identifier": "AnthropicCitationWebSearchResultLocation" }) +export type AnthropicCitationWebSearchResultLocationParam = { + readonly "cited_text": string + readonly "encrypted_index": string + readonly "title": string | null + readonly "type": "web_search_result_location" + readonly "url": string +} +export const AnthropicCitationWebSearchResultLocationParam = Schema.Struct({ + "cited_text": Schema.String, + "encrypted_index": Schema.String, + "title": Schema.Union([Schema.String, Schema.Null]), + "type": Schema.Literal("web_search_result_location"), + "url": Schema.String +}).annotate({ "identifier": "AnthropicCitationWebSearchResultLocationParam" }) +export type AnthropicCodeExecution20250825Caller = { + readonly "tool_id": string + readonly "type": "code_execution_20250825" +} +export const AnthropicCodeExecution20250825Caller = Schema.Struct({ + "tool_id": Schema.String, + "type": Schema.Literal("code_execution_20250825") +}).annotate({ "identifier": "AnthropicCodeExecution20250825Caller" }) +export type AnthropicCodeExecution20260120Caller = { + readonly "tool_id": string + readonly "type": "code_execution_20260120" +} +export const AnthropicCodeExecution20260120Caller = Schema.Struct({ + "tool_id": Schema.String, + "type": Schema.Literal("code_execution_20260120") +}).annotate({ "identifier": "AnthropicCodeExecution20260120Caller" }) +export type AnthropicCodeExecutionOutput = { readonly "file_id": string; readonly "type": "code_execution_output" } +export const AnthropicCodeExecutionOutput = Schema.Struct({ "file_id": Schema.String, - "index": Schema.Number.check(Schema.isFinite()) + "type": Schema.Literal("code_execution_output") +}).annotate({ "identifier": "AnthropicCodeExecutionOutput" }) +export type AnthropicCompactionBlock = { readonly "content": string | null; readonly "type": "compaction" } +export const AnthropicCompactionBlock = Schema.Struct({ + "content": Schema.Union([Schema.String, Schema.Null]), + "type": Schema.Literal("compaction") +}).annotate({ "identifier": "AnthropicCompactionBlock" }) +export type AnthropicContainer = { + readonly "expires_at": string + readonly "id": string + readonly [x: string]: Schema.Json +} | null +export const AnthropicContainer = Schema.Union([ + Schema.StructWithRest(Schema.Struct({ "expires_at": Schema.String, "id": Schema.String }), [ + Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })) + ]), + Schema.Null +]).annotate({ "identifier": "AnthropicContainer" }) +export type AnthropicContainerUpload = { readonly "file_id": string; readonly "type": "container_upload" } +export const AnthropicContainerUpload = Schema.Struct({ + "file_id": Schema.String, + "type": Schema.Literal("container_upload") +}).annotate({ "identifier": "AnthropicContainerUpload" }) +export type AnthropicDirectCaller = { readonly "type": "direct" } +export const AnthropicDirectCaller = Schema.Struct({ "type": Schema.Literal("direct") }).annotate({ + "identifier": "AnthropicDirectCaller" }) -export type OpenAIResponsesRefusalContent = { readonly "type": "refusal"; readonly "refusal": string } -export const OpenAIResponsesRefusalContent = Schema.Struct({ - "type": Schema.Literal("refusal"), - "refusal": Schema.String +export type AnthropicFileDocumentSource = { readonly "file_id": string; readonly "type": "file" } +export const AnthropicFileDocumentSource = Schema.Struct({ "file_id": Schema.String, "type": Schema.Literal("file") }) + .annotate({ "identifier": "AnthropicFileDocumentSource" }) +export type AnthropicImageMimeType = "image/jpeg" | "image/png" | "image/gif" | "image/webp" +export const AnthropicImageMimeType = Schema.Literals(["image/jpeg", "image/png", "image/gif", "image/webp"]).annotate({ + "identifier": "AnthropicImageMimeType" }) -export type ReasoningTextContent = { readonly "type": "reasoning_text"; readonly "text": string } -export const ReasoningTextContent = Schema.Struct({ "type": Schema.Literal("reasoning_text"), "text": Schema.String }) -export type ReasoningSummaryText = { readonly "type": "summary_text"; readonly "text": string } -export const ReasoningSummaryText = Schema.Struct({ "type": Schema.Literal("summary_text"), "text": Schema.String }) -export type OutputItemFunctionCall = { - readonly "type": "function_call" - readonly "id"?: string - readonly "name": string - readonly "arguments": string - readonly "call_id": string - readonly "status"?: "completed" | "incomplete" | "in_progress" +export type Objects_2 = { readonly "type": "input_tokens"; readonly "value": number; readonly [x: string]: Schema.Json } +export const Objects_2 = Schema.StructWithRest( + Schema.Struct({ + "type": Schema.Literal("input_tokens"), + "value": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] +) +export type AnthropicInputTokensTrigger = { readonly "type": "input_tokens"; readonly "value": number } +export const AnthropicInputTokensTrigger = Schema.Struct({ + "type": Schema.Literal("input_tokens"), + "value": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) +}).annotate({ "identifier": "AnthropicInputTokensTrigger" }) +export type Objects_3 = { + readonly "ephemeral_1h_input_tokens"?: number + readonly "ephemeral_5m_input_tokens"?: number + readonly [x: string]: Schema.Json } -export const OutputItemFunctionCall = Schema.Struct({ - "type": Schema.Literal("function_call"), - "id": Schema.optionalKey(Schema.String), - "name": Schema.String, - "arguments": Schema.String, - "call_id": Schema.String, - "status": Schema.optionalKey(Schema.Literals(["completed", "incomplete", "in_progress"])) -}) -export type ResponsesOutputItemFunctionCall = { - readonly "type": "function_call" - readonly "id"?: string - readonly "name": string - readonly "arguments": string - readonly "call_id": string - readonly "status"?: "completed" | "incomplete" | "in_progress" +export const Objects_3 = Schema.StructWithRest( + Schema.Struct({ + "ephemeral_1h_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "ephemeral_5m_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ) + }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] +) +export type AnthropicOutputTokensDetails = + | { readonly "thinking_tokens": number; readonly [x: string]: Schema.Json } + | null +export const AnthropicOutputTokensDetails = Schema.Union([ + Schema.StructWithRest( + Schema.Struct({ "thinking_tokens": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] + ), + Schema.Null +]).annotate({ "identifier": "AnthropicOutputTokensDetails" }) +export type AnthropicPlainTextSource = { + readonly "data": string + readonly "media_type": "text/plain" + readonly "type": "text" } -export const ResponsesOutputItemFunctionCall = Schema.Struct({ - "type": Schema.Literal("function_call"), - "id": Schema.optionalKey(Schema.String), - "name": Schema.String, - "arguments": Schema.String, - "call_id": Schema.String, - "status": Schema.optionalKey(Schema.Literals(["completed", "incomplete", "in_progress"])) +export const AnthropicPlainTextSource = Schema.Struct({ + "data": Schema.String, + "media_type": Schema.Literal("text/plain"), + "type": Schema.Literal("text") +}).annotate({ "identifier": "AnthropicPlainTextSource" }) +export type AnthropicRedactedThinkingBlock = { readonly "data": string; readonly "type": "redacted_thinking" } +export const AnthropicRedactedThinkingBlock = Schema.Struct({ + "data": Schema.String, + "type": Schema.Literal("redacted_thinking") +}).annotate({ "identifier": "AnthropicRedactedThinkingBlock" }) +export type AnthropicRefusalStopDetails = { + readonly "category": "cyber" | "bio" | null + readonly "explanation": string | null + readonly "type": "refusal" + readonly [x: string]: Schema.Json +} | null +export const AnthropicRefusalStopDetails = Schema.Union([ + Schema.StructWithRest( + Schema.Struct({ + "category": Schema.Union([Schema.Literal("cyber"), Schema.Literal("bio"), Schema.Null]), + "explanation": Schema.Union([Schema.String, Schema.Null]), + "type": Schema.Literal("refusal") + }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] + ), + Schema.Null +]).annotate({ "description": "Structured information about a refusal", "identifier": "AnthropicRefusalStopDetails" }) +export type AnthropicServerToolErrorCode = + | "invalid_tool_input" + | "unavailable" + | "too_many_requests" + | "execution_time_exceeded" +export const AnthropicServerToolErrorCode = Schema.Literals([ + "invalid_tool_input", + "unavailable", + "too_many_requests", + "execution_time_exceeded" +]).annotate({ "identifier": "AnthropicServerToolErrorCode" }) +export type AnthropicServerToolUsage = { + readonly "web_fetch_requests": number + readonly "web_search_requests": number + readonly [x: string]: Schema.Json +} | null +export const AnthropicServerToolUsage = Schema.Union([ + Schema.StructWithRest( + Schema.Struct({ + "web_fetch_requests": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "web_search_requests": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] + ), + Schema.Null +]).annotate({ "identifier": "AnthropicServerToolUsage" }) +export type AnthropicServiceTier = "standard" | "priority" | "batch" | null +export const AnthropicServiceTier = Schema.Union([ + Schema.Literal("standard"), + Schema.Literal("priority"), + Schema.Literal("batch"), + Schema.Null +]).annotate({ "identifier": "AnthropicServiceTier" }) +export type AnthropicSpeed = "fast" | "standard" | null +export const AnthropicSpeed = Schema.Union([Schema.Literal("fast"), Schema.Literal("standard"), Schema.Null]).annotate({ + "identifier": "AnthropicSpeed" }) -export type WebSearchStatus = "completed" | "searching" | "in_progress" | "failed" -export const WebSearchStatus = Schema.Literals(["completed", "searching", "in_progress", "failed"]) -export type ImageGenerationStatus = "in_progress" | "completed" | "generating" | "failed" -export const ImageGenerationStatus = Schema.Literals(["in_progress", "completed", "generating", "failed"]) -export type ResponsesErrorField = { - readonly "code": - | "server_error" - | "rate_limit_exceeded" - | "invalid_prompt" - | "vector_store_timeout" - | "invalid_image" - | "invalid_image_format" - | "invalid_base64_image" - | "invalid_image_url" - | "image_too_large" - | "image_too_small" - | "image_parse_error" - | "image_content_policy_violation" - | "invalid_image_mode" - | "image_file_too_large" - | "unsupported_image_media_type" - | "empty_image_file" - | "failed_to_download_image" - | "image_file_not_found" - readonly "message": string +export type AnthropicTextEditorCodeExecutionCreateResult = { + readonly "is_file_update": boolean + readonly "type": "text_editor_code_execution_create_result" } -export const ResponsesErrorField = Schema.Struct({ - "code": Schema.Literals([ - "server_error", - "rate_limit_exceeded", - "invalid_prompt", - "vector_store_timeout", - "invalid_image", - "invalid_image_format", - "invalid_base64_image", - "invalid_image_url", - "image_too_large", - "image_too_small", - "image_parse_error", - "image_content_policy_violation", - "invalid_image_mode", - "image_file_too_large", - "unsupported_image_media_type", - "empty_image_file", - "failed_to_download_image", - "image_file_not_found" - ]), - "message": Schema.String -}).annotate({ "description": "Error information returned from the API" }) -export type OpenAIResponsesIncompleteDetails = { readonly "reason"?: "max_output_tokens" | "content_filter" } -export const OpenAIResponsesIncompleteDetails = Schema.Struct({ - "reason": Schema.optionalKey(Schema.Literals(["max_output_tokens", "content_filter"])) -}) -export type OpenAIResponsesUsage = { - readonly "input_tokens": number - readonly "input_tokens_details": { readonly "cached_tokens": number } - readonly "output_tokens": number - readonly "output_tokens_details": { readonly "reasoning_tokens": number } - readonly "total_tokens": number +export const AnthropicTextEditorCodeExecutionCreateResult = Schema.Struct({ + "is_file_update": Schema.Boolean, + "type": Schema.Literal("text_editor_code_execution_create_result") +}).annotate({ "identifier": "AnthropicTextEditorCodeExecutionCreateResult" }) +export type AnthropicTextEditorCodeExecutionStrReplaceResult = { + readonly "lines": ReadonlyArray | null + readonly "new_lines": number | null + readonly "new_start": number | null + readonly "old_lines": number | null + readonly "old_start": number | null + readonly "type": "text_editor_code_execution_str_replace_result" } -export const OpenAIResponsesUsage = Schema.Struct({ - "input_tokens": Schema.Number.check(Schema.isFinite()), - "input_tokens_details": Schema.Struct({ "cached_tokens": Schema.Number.check(Schema.isFinite()) }), - "output_tokens": Schema.Number.check(Schema.isFinite()), - "output_tokens_details": Schema.Struct({ "reasoning_tokens": Schema.Number.check(Schema.isFinite()) }), - "total_tokens": Schema.Number.check(Schema.isFinite()) -}) -export type ResponseInputText = { readonly "type": "input_text"; readonly "text": string } -export const ResponseInputText = Schema.Struct({ "type": Schema.Literal("input_text"), "text": Schema.String }) - .annotate({ "description": "Text input content item" }) -export type ResponseInputImage = { - readonly "type": "input_image" - readonly "detail": "auto" | "high" | "low" - readonly "image_url"?: string -} -export const ResponseInputImage = Schema.Struct({ - "type": Schema.Literal("input_image"), - "detail": Schema.Literals(["auto", "high", "low"]), - "image_url": Schema.optionalKey(Schema.String) -}).annotate({ "description": "Image input content item" }) -export type ResponseInputFile = { - readonly "type": "input_file" - readonly "file_id"?: string - readonly "file_data"?: string - readonly "filename"?: string - readonly "file_url"?: string +export const AnthropicTextEditorCodeExecutionStrReplaceResult = Schema.Struct({ + "lines": Schema.Union([Schema.Array(Schema.String), Schema.Null]), + "new_lines": Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]), + "new_start": Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]), + "old_lines": Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]), + "old_start": Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]), + "type": Schema.Literal("text_editor_code_execution_str_replace_result") +}).annotate({ "identifier": "AnthropicTextEditorCodeExecutionStrReplaceResult" }) +export type AnthropicTextEditorCodeExecutionToolResultError = { + readonly "error_code": + | "invalid_tool_input" + | "unavailable" + | "too_many_requests" + | "execution_time_exceeded" + | "file_not_found" + readonly "error_message": string | null + readonly "type": "text_editor_code_execution_tool_result_error" } -export const ResponseInputFile = Schema.Struct({ - "type": Schema.Literal("input_file"), - "file_id": Schema.optionalKey(Schema.String), - "file_data": Schema.optionalKey(Schema.String), - "filename": Schema.optionalKey(Schema.String), - "file_url": Schema.optionalKey(Schema.String) -}).annotate({ "description": "File input content item" }) -export type ResponseInputAudio = { - readonly "type": "input_audio" - readonly "input_audio": { readonly "data": string; readonly "format": "mp3" | "wav" } +export const AnthropicTextEditorCodeExecutionToolResultError = Schema.Struct({ + "error_code": Schema.Literals([ + "invalid_tool_input", + "unavailable", + "too_many_requests", + "execution_time_exceeded", + "file_not_found" + ]), + "error_message": Schema.Union([Schema.String, Schema.Null]), + "type": Schema.Literal("text_editor_code_execution_tool_result_error") +}).annotate({ "identifier": "AnthropicTextEditorCodeExecutionToolResultError" }) +export type AnthropicTextEditorCodeExecutionViewResult = { + readonly "content": string + readonly "file_type": "text" | "image" | "pdf" + readonly "num_lines": number | null + readonly "start_line": number | null + readonly "total_lines": number | null + readonly "type": "text_editor_code_execution_view_result" } -export const ResponseInputAudio = Schema.Struct({ - "type": Schema.Literal("input_audio"), - "input_audio": Schema.Struct({ "data": Schema.String, "format": Schema.Literals(["mp3", "wav"]) }) -}).annotate({ "description": "Audio input content item" }) -export type ToolCallStatus = "in_progress" | "completed" | "incomplete" -export const ToolCallStatus = Schema.Literals(["in_progress", "completed", "incomplete"]) -export type OpenResponsesRequestMetadata = {} -export const OpenResponsesRequestMetadata = Schema.Struct({}).annotate({ - "description": - "Metadata key-value pairs for the request. Keys must be ≤64 characters and cannot contain brackets. Values must be ≤512 characters. Maximum 16 pairs allowed." +export const AnthropicTextEditorCodeExecutionViewResult = Schema.Struct({ + "content": Schema.String, + "file_type": Schema.Literals(["text", "image", "pdf"]), + "num_lines": Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]), + "start_line": Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]), + "total_lines": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]), + "type": Schema.Literal("text_editor_code_execution_view_result") +}).annotate({ "identifier": "AnthropicTextEditorCodeExecutionViewResult" }) +export type AnthropicThinkingBlock = { + readonly "signature": string + readonly "thinking": string + readonly "type": "thinking" +} +export const AnthropicThinkingBlock = Schema.Struct({ + "signature": Schema.String, + "thinking": Schema.String, + "type": Schema.Literal("thinking") +}).annotate({ "identifier": "AnthropicThinkingBlock" }) +export type AnthropicThinkingDisplay = "summarized" | "omitted" | null +export const AnthropicThinkingDisplay = Schema.Union([ + Schema.Literal("summarized"), + Schema.Literal("omitted"), + Schema.Null +]).annotate({ "identifier": "AnthropicThinkingDisplay" }) +export type AnthropicThinkingTurns = { readonly "type": "thinking_turns"; readonly "value": number } +export const AnthropicThinkingTurns = Schema.Struct({ + "type": Schema.Literal("thinking_turns"), + "value": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) +}).annotate({ "identifier": "AnthropicThinkingTurns" }) +export type AnthropicToolReference = { readonly "tool_name": string; readonly "type": "tool_reference" } +export const AnthropicToolReference = Schema.Struct({ + "tool_name": Schema.String, + "type": Schema.Literal("tool_reference") +}).annotate({ "identifier": "AnthropicToolReference" }) +export type AnthropicToolUsesKeep = { readonly "type": "tool_uses"; readonly "value": number } +export const AnthropicToolUsesKeep = Schema.Struct({ + "type": Schema.Literal("tool_uses"), + "value": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) +}).annotate({ "identifier": "AnthropicToolUsesKeep" }) +export type AnthropicToolUsesTrigger = { readonly "type": "tool_uses"; readonly "value": number } +export const AnthropicToolUsesTrigger = Schema.Struct({ + "type": Schema.Literal("tool_uses"), + "value": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) +}).annotate({ "identifier": "AnthropicToolUsesTrigger" }) +export type AnthropicUrlImageSource = { readonly "type": "url"; readonly "url": string } +export const AnthropicUrlImageSource = Schema.Struct({ "type": Schema.Literal("url"), "url": Schema.String }).annotate({ + "identifier": "AnthropicUrlImageSource" }) -export type ResponsesSearchContextSize = "low" | "medium" | "high" -export const ResponsesSearchContextSize = Schema.Literals(["low", "medium", "high"]).annotate({ - "description": "Size of the search context for web search tools" +export type AnthropicUrlPdfSource = { readonly "type": "url"; readonly "url": string } +export const AnthropicUrlPdfSource = Schema.Struct({ "type": Schema.Literal("url"), "url": Schema.String }).annotate({ + "identifier": "AnthropicUrlPdfSource" }) -export type WebSearchPreviewToolUserLocation = { - readonly "type": "approximate" - readonly "city"?: string - readonly "country"?: string - readonly "region"?: string - readonly "timezone"?: string -} -export const WebSearchPreviewToolUserLocation = Schema.Struct({ - "type": Schema.Literal("approximate"), - "city": Schema.optionalKey(Schema.String), - "country": Schema.optionalKey(Schema.String), - "region": Schema.optionalKey(Schema.String), - "timezone": Schema.optionalKey(Schema.String) -}) -export type ResponsesWebSearchUserLocation = { - readonly "type"?: "approximate" - readonly "city"?: string - readonly "country"?: string - readonly "region"?: string - readonly "timezone"?: string -} -export const ResponsesWebSearchUserLocation = Schema.Struct({ - "type": Schema.optionalKey(Schema.Literal("approximate")), - "city": Schema.optionalKey(Schema.String), - "country": Schema.optionalKey(Schema.String), - "region": Schema.optionalKey(Schema.String), - "timezone": Schema.optionalKey(Schema.String) -}).annotate({ "description": "User location information for web search" }) -export type OpenAIResponsesToolChoice = "auto" | "none" | "required" | { - readonly "type": "function" - readonly "name": string -} | { readonly "type": "web_search_preview_2025_03_11" | "web_search_preview" } -export const OpenAIResponsesToolChoice = Schema.Union([ - Schema.Literal("auto"), - Schema.Literal("none"), - Schema.Literal("required"), - Schema.Struct({ "type": Schema.Literal("function"), "name": Schema.String }), - Schema.Struct({ "type": Schema.Literals(["web_search_preview_2025_03_11", "web_search_preview"]) }) -]) -export type OpenAIResponsesPrompt = { readonly "id": string; readonly "variables"?: {} } -export const OpenAIResponsesPrompt = Schema.Struct({ - "id": Schema.String, - "variables": Schema.optionalKey(Schema.Struct({})) -}) -export type OpenAIResponsesReasoningEffort = "xhigh" | "high" | "medium" | "low" | "minimal" | "none" -export const OpenAIResponsesReasoningEffort = Schema.Literals(["xhigh", "high", "medium", "low", "minimal", "none"]) -export type ReasoningSummaryVerbosity = "auto" | "concise" | "detailed" -export const ReasoningSummaryVerbosity = Schema.Literals(["auto", "concise", "detailed"]) -export type OpenAIResponsesServiceTier = "auto" | "default" | "flex" | "priority" | "scale" -export const OpenAIResponsesServiceTier = Schema.Literals(["auto", "default", "flex", "priority", "scale"]) -export type OpenAIResponsesTruncation = "auto" | "disabled" -export const OpenAIResponsesTruncation = Schema.Literals(["auto", "disabled"]) -export type ResponsesFormatText = { readonly "type": "text" } -export const ResponsesFormatText = Schema.Struct({ "type": Schema.Literal("text") }).annotate({ - "description": "Plain text response format" -}) -export type ResponsesFormatJSONObject = { readonly "type": "json_object" } -export const ResponsesFormatJSONObject = Schema.Struct({ "type": Schema.Literal("json_object") }).annotate({ - "description": "JSON object response format" -}) -export type ResponsesFormatTextJSONSchemaConfig = { - readonly "type": "json_schema" - readonly "name": string - readonly "description"?: string - readonly "strict"?: boolean - readonly "schema": {} +export type AnthropicWebFetchToolResultError = { + readonly "error_code": + | "invalid_tool_input" + | "url_too_long" + | "url_not_allowed" + | "url_not_accessible" + | "unsupported_content_type" + | "too_many_requests" + | "max_uses_exceeded" + | "unavailable" + readonly "type": "web_fetch_tool_result_error" } -export const ResponsesFormatTextJSONSchemaConfig = Schema.Struct({ - "type": Schema.Literal("json_schema"), - "name": Schema.String, - "description": Schema.optionalKey(Schema.String), - "strict": Schema.optionalKey(Schema.Boolean), - "schema": Schema.Struct({}) -}).annotate({ "description": "JSON schema constrained response format" }) -export type OpenResponsesErrorEvent = { - readonly "type": "error" - readonly "code": string - readonly "message": string - readonly "param": string - readonly "sequence_number": number +export const AnthropicWebFetchToolResultError = Schema.Struct({ + "error_code": Schema.Literals([ + "invalid_tool_input", + "url_too_long", + "url_not_allowed", + "url_not_accessible", + "unsupported_content_type", + "too_many_requests", + "max_uses_exceeded", + "unavailable" + ]), + "type": Schema.Literal("web_fetch_tool_result_error") +}).annotate({ "identifier": "AnthropicWebFetchToolResultError" }) +export type AnthropicWebSearchResult = { + readonly "encrypted_content": string + readonly "page_age": string | null + readonly "title": string + readonly "type": "web_search_result" + readonly "url": string } -export const OpenResponsesErrorEvent = Schema.Struct({ - "type": Schema.Literal("error"), - "code": Schema.String, - "message": Schema.String, - "param": Schema.String, - "sequence_number": Schema.Number.check(Schema.isFinite()) -}).annotate({ "description": "Event emitted when an error occurs during streaming" }) -export type OpenResponsesTopLogprobs = { readonly "token"?: string; readonly "logprob"?: number } -export const OpenResponsesTopLogprobs = Schema.Struct({ - "token": Schema.optionalKey(Schema.String), - "logprob": Schema.optionalKey(Schema.Number.check(Schema.isFinite())) -}).annotate({ "description": "Alternative token with its log probability" }) -export type OpenResponsesRefusalDeltaEvent = { - readonly "type": "response.refusal.delta" - readonly "output_index": number - readonly "item_id": string - readonly "content_index": number - readonly "delta": string - readonly "sequence_number": number +export const AnthropicWebSearchResult = Schema.Struct({ + "encrypted_content": Schema.String, + "page_age": Schema.Union([Schema.String, Schema.Null]), + "title": Schema.String, + "type": Schema.Literal("web_search_result"), + "url": Schema.String +}).annotate({ "identifier": "AnthropicWebSearchResult" }) +export type AnthropicWebSearchResultBlockParam = { + readonly "encrypted_content": string + readonly "page_age"?: string | null + readonly "title": string + readonly "type": "web_search_result" + readonly "url": string } -export const OpenResponsesRefusalDeltaEvent = Schema.Struct({ - "type": Schema.Literal("response.refusal.delta"), - "output_index": Schema.Number.check(Schema.isFinite()), - "item_id": Schema.String, - "content_index": Schema.Number.check(Schema.isFinite()), - "delta": Schema.String, - "sequence_number": Schema.Number.check(Schema.isFinite()) -}).annotate({ "description": "Event emitted when a refusal delta is streamed" }) -export type OpenResponsesRefusalDoneEvent = { - readonly "type": "response.refusal.done" - readonly "output_index": number +export const AnthropicWebSearchResultBlockParam = Schema.Struct({ + "encrypted_content": Schema.String, + "page_age": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "title": Schema.String, + "type": Schema.Literal("web_search_result"), + "url": Schema.String +}).annotate({ "identifier": "AnthropicWebSearchResultBlockParam" }) +export type AnthropicWebSearchToolResultError = { + readonly "error_code": + | "invalid_tool_input" + | "unavailable" + | "max_uses_exceeded" + | "too_many_requests" + | "query_too_long" + | "request_too_large" + readonly "type": "web_search_tool_result_error" +} +export const AnthropicWebSearchToolResultError = Schema.Struct({ + "error_code": Schema.Literals([ + "invalid_tool_input", + "unavailable", + "max_uses_exceeded", + "too_many_requests", + "query_too_long", + "request_too_large" + ]), + "type": Schema.Literal("web_search_tool_result_error") +}).annotate({ "identifier": "AnthropicWebSearchToolResultError" }) +export type Objects_4 = { + readonly "city"?: string | null + readonly "country"?: string | null + readonly "region"?: string | null + readonly "timezone"?: string | null + readonly "type": "approximate" + readonly [x: string]: Schema.Json +} +export const Objects_4 = Schema.StructWithRest( + Schema.Struct({ + "city": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "country": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "region": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "timezone": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "type": Schema.Literal("approximate") + }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] +) +export type ApiErrorType = + | "context_length_exceeded" + | "max_tokens_exceeded" + | "token_limit_exceeded" + | "string_too_long" + | "authentication" + | "permission_denied" + | "payment_required" + | "rate_limit_exceeded" + | "provider_overloaded" + | "provider_unavailable" + | "invalid_request" + | "invalid_prompt" + | "not_found" + | "precondition_failed" + | "payload_too_large" + | "unprocessable" + | "content_policy_violation" + | "refusal" + | "invalid_image" + | "image_too_large" + | "image_too_small" + | "unsupported_image_format" + | "image_not_found" + | "image_download_failed" + | "server" + | "timeout" + | "unmapped" +export const ApiErrorType = Schema.Literals([ + "context_length_exceeded", + "max_tokens_exceeded", + "token_limit_exceeded", + "string_too_long", + "authentication", + "permission_denied", + "payment_required", + "rate_limit_exceeded", + "provider_overloaded", + "provider_unavailable", + "invalid_request", + "invalid_prompt", + "not_found", + "precondition_failed", + "payload_too_large", + "unprocessable", + "content_policy_violation", + "refusal", + "invalid_image", + "image_too_large", + "image_too_small", + "unsupported_image_format", + "image_not_found", + "image_download_failed", + "server", + "timeout", + "unmapped" +]).annotate({ + "description": "Canonical OpenRouter error type, stable across all API formats", + "identifier": "ApiErrorType" +}) +export type ApplyPatchCallOperationDiffDeltaEvent = { + readonly "delta": string readonly "item_id": string - readonly "content_index": number - readonly "refusal": string + readonly "output_index": number readonly "sequence_number": number + readonly "type": "response.apply_patch_call_operation_diff.delta" } -export const OpenResponsesRefusalDoneEvent = Schema.Struct({ - "type": Schema.Literal("response.refusal.done"), - "output_index": Schema.Number.check(Schema.isFinite()), +export const ApplyPatchCallOperationDiffDeltaEvent = Schema.Struct({ + "delta": Schema.String, "item_id": Schema.String, - "content_index": Schema.Number.check(Schema.isFinite()), - "refusal": Schema.String, - "sequence_number": Schema.Number.check(Schema.isFinite()) -}).annotate({ "description": "Event emitted when refusal streaming is complete" }) -export type OpenResponsesFunctionCallArgumentsDeltaEvent = { - readonly "type": "response.function_call_arguments.delta" + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.apply_patch_call_operation_diff.delta") +}).annotate({ + "description": "Incremental chunk of `operation.diff` for an `apply_patch_call`. Matches OpenAI's streaming shape.", + "identifier": "ApplyPatchCallOperationDiffDeltaEvent" +}) +export type ApplyPatchCallOperationDiffDoneEvent = { + readonly "diff": string readonly "item_id": string readonly "output_index": number - readonly "delta": string readonly "sequence_number": number + readonly "type": "response.apply_patch_call_operation_diff.done" } -export const OpenResponsesFunctionCallArgumentsDeltaEvent = Schema.Struct({ - "type": Schema.Literal("response.function_call_arguments.delta"), +export const ApplyPatchCallOperationDiffDoneEvent = Schema.Struct({ + "diff": Schema.String, "item_id": Schema.String, - "output_index": Schema.Number.check(Schema.isFinite()), - "delta": Schema.String, - "sequence_number": Schema.Number.check(Schema.isFinite()) -}).annotate({ "description": "Event emitted when function call arguments are being streamed" }) -export type OpenResponsesFunctionCallArgumentsDoneEvent = { - readonly "type": "response.function_call_arguments.done" + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.apply_patch_call_operation_diff.done") +}).annotate({ + "description": "Emitted when `operation.diff` streaming completes for an `apply_patch_call`.", + "identifier": "ApplyPatchCallOperationDiffDoneEvent" +}) +export type ApplyPatchCallOutputItem = { + readonly "call_id": string + readonly "id"?: string | null + readonly "output"?: string | null + readonly "status": "completed" | "failed" + readonly "type": "apply_patch_call_output" +} +export const ApplyPatchCallOutputItem = Schema.Struct({ + "call_id": Schema.String, + "id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "output": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "status": Schema.Literals(["completed", "failed"]), + "type": Schema.Literal("apply_patch_call_output") +}).annotate({ + "description": + "The client's echo of an `apply_patch_call` after applying the patch. `output` is an optional human-readable log; `status` is `completed` when the patch was applied successfully, `failed` otherwise.", + "identifier": "ApplyPatchCallOutputItem" +}) +export type ApplyPatchCallStatus = "in_progress" | "completed" +export const ApplyPatchCallStatus = Schema.Literals(["in_progress", "completed"]).annotate({ + "description": "Lifecycle state of an `apply_patch_call` output item.", + "identifier": "ApplyPatchCallStatus" +}) +export type ApplyPatchCreateFileOperation = { + readonly "diff": string + readonly "path": string + readonly "type": "create_file" +} +export const ApplyPatchCreateFileOperation = Schema.Struct({ + "diff": Schema.String, + "path": Schema.String, + "type": Schema.Literal("create_file") +}).annotate({ + "description": + "The `create_file` variant of an `apply_patch_call.operation`. Carries a V4A diff describing the new file contents.", + "identifier": "ApplyPatchCreateFileOperation" +}) +export type ApplyPatchDeleteFileOperation = { readonly "path": string; readonly "type": "delete_file" } +export const ApplyPatchDeleteFileOperation = Schema.Struct({ + "path": Schema.String, + "type": Schema.Literal("delete_file") +}).annotate({ + "description": + "The `delete_file` variant of an `apply_patch_call.operation`. Identifies the file to remove; no diff is required.", + "identifier": "ApplyPatchDeleteFileOperation" +}) +export type ApplyPatchEngineEnum = "auto" | "native" | "openrouter" +export const ApplyPatchEngineEnum = Schema.Literals(["auto", "native", "openrouter"]).annotate({ + "description": + "Which apply_patch engine to use. \"auto\" (default) uses native passthrough when the endpoint advertises native apply_patch support, otherwise falls back to OpenRouter's HITL validator. \"native\" forces native passthrough — when the endpoint does not support native, the request falls back to HITL. \"openrouter\" always runs the HITL validator. Native passthrough streams the diff incrementally via `apply_patch_call_operation_diff.delta` events; HITL buffers the diff for atomic delivery as a single delta.", + "identifier": "ApplyPatchEngineEnum" +}) +export type ApplyPatchServerTool = { readonly "type": "apply_patch" } +export const ApplyPatchServerTool = Schema.Struct({ "type": Schema.Literal("apply_patch") }).annotate({ + "description": "Apply patch tool configuration", + "identifier": "ApplyPatchServerTool" +}) +export type ApplyPatchUpdateFileOperation = { + readonly "diff": string + readonly "path": string + readonly "type": "update_file" +} +export const ApplyPatchUpdateFileOperation = Schema.Struct({ + "diff": Schema.String, + "path": Schema.String, + "type": Schema.Literal("update_file") +}).annotate({ + "description": + "The `update_file` variant of an `apply_patch_call.operation`. Carries a V4A diff describing edits to an existing file.", + "identifier": "ApplyPatchUpdateFileOperation" +}) +export type AppRankingsItem = { + readonly "app_id": number + readonly "app_name": string + readonly "rank": number + readonly "total_requests": number + readonly "total_tokens": string +} +export const AppRankingsItem = Schema.Struct({ + "app_id": Schema.Number.annotate({ "description": "Stable numeric identifier of the app on OpenRouter." }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ), + "app_name": Schema.String.annotate({ "description": "Public display name of the app." }), + "rank": Schema.Number.annotate({ + "description": "1-based position of the app within this response, per the requested `sort`." + }).check(Schema.isInt().annotate({ "expected": "an integer" })), + "total_requests": Schema.Number.annotate({ + "description": "Number of requests attributed to the app inside the date window." + }).check(Schema.isInt().annotate({ "expected": "an integer" })), + "total_tokens": Schema.String.annotate({ + "description": + "Sum of `prompt_tokens + completion_tokens` attributed to the app inside the date window, returned as a decimal string so 64-bit values are not truncated." + }) +}).annotate({ "identifier": "AppRankingsItem" }) +export type AutoBetaRouterPlugin = { + readonly "allowed_models"?: ReadonlyArray + readonly "cost_quality_tradeoff"?: number + readonly "enabled"?: boolean + readonly "id": "auto-beta-router" +} +export const AutoBetaRouterPlugin = Schema.Struct({ + "allowed_models": Schema.optionalKey( + Schema.Array(Schema.String).annotate({ + "description": + "List of model patterns to filter which models the auto-beta-router can route between. Supports wildcards (e.g., \"anthropic/*\" matches all Anthropic models). When not specified, uses the default supported models list." + }) + ), + "cost_quality_tradeoff": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Balances routing between cost and quality on a 0-10 scale. The auto-beta-router ranks models for the classified task type by community spend share, then filters candidates by their average cost per generation for that task. Higher values favor cheaper models: 10 keeps only models around the cheapest 10th percentile, while 0 permits models up to the 90th percentile for cost. Defaults to 9." + }).check(Schema.isInt().annotate({ "expected": "an integer" })).check( + Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" }) + ).check(Schema.isLessThanOrEqualTo(10).annotate({ "expected": "a value less than or equal to 10" })) + ), + "enabled": Schema.optionalKey( + Schema.Boolean.annotate({ + "description": "Set to false to disable the auto-beta-router plugin for this request. Defaults to true." + }) + ), + "id": Schema.Literal("auto-beta-router") +}).annotate({ "identifier": "AutoBetaRouterPlugin" }) +export type AutoRouterPlugin = { + readonly "allowed_models"?: ReadonlyArray + readonly "cost_quality_tradeoff"?: number + readonly "enabled"?: boolean + readonly "id": "auto-router" + readonly "pin_model"?: boolean +} +export const AutoRouterPlugin = Schema.Struct({ + "allowed_models": Schema.optionalKey( + Schema.Array(Schema.String).annotate({ + "description": + "List of model patterns to filter which models the auto-router can route between. Supports wildcards (e.g., \"anthropic/*\" matches all Anthropic models). When not specified, uses the default supported models list." + }) + ), + "cost_quality_tradeoff": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Controls cost vs. quality routing tradeoff (0–10). 0 = pure quality (best model regardless of cost), 10 = maximize for cost (cheapest model wins). Intermediate values blend quality and cost signals continuously. Defaults to 7." + }).check(Schema.isInt().annotate({ "expected": "an integer" })).check( + Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" }) + ).check(Schema.isLessThanOrEqualTo(10).annotate({ "expected": "a value less than or equal to 10" })) + ), + "enabled": Schema.optionalKey( + Schema.Boolean.annotate({ + "description": "Set to false to disable the auto-router plugin for this request. Defaults to true." + }) + ), + "id": Schema.Literal("auto-router"), + "pin_model": Schema.optionalKey( + Schema.Boolean.annotate({ + "description": + "When true, reuses the model from the most recent assistant message's `model` attribute for subsequent turns. Defaults to false." + }) + ) +}).annotate({ "identifier": "AutoRouterPlugin" }) +export type BadGatewayResponseErrorData = { + readonly "code": number + readonly "message": string + readonly "metadata"?: { readonly [x: string]: Schema.Json } | null +} +export const BadGatewayResponseErrorData = Schema.Struct({ + "code": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "message": Schema.String, + "metadata": Schema.optionalKey( + Schema.Union([Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })), Schema.Null]) + ) +}).annotate({ "description": "Error data for BadGatewayResponse", "identifier": "BadGatewayResponseErrorData" }) +export type BadRequestResponseErrorData = { + readonly "code": number + readonly "message": string + readonly "metadata"?: { readonly [x: string]: Schema.Json } | null +} +export const BadRequestResponseErrorData = Schema.Struct({ + "code": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "message": Schema.String, + "metadata": Schema.optionalKey( + Schema.Union([Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })), Schema.Null]) + ) +}).annotate({ "description": "Error data for BadRequestResponse", "identifier": "BadRequestResponseErrorData" }) +export type BaseCustomToolCallInputDeltaEvent = { + readonly "delta": string readonly "item_id": string readonly "output_index": number - readonly "name": string - readonly "arguments": string readonly "sequence_number": number + readonly "type": "response.custom_tool_call_input.delta" } -export const OpenResponsesFunctionCallArgumentsDoneEvent = Schema.Struct({ - "type": Schema.Literal("response.function_call_arguments.done"), +export const BaseCustomToolCallInputDeltaEvent = Schema.Struct({ + "delta": Schema.String, "item_id": Schema.String, - "output_index": Schema.Number.check(Schema.isFinite()), - "name": Schema.String, - "arguments": Schema.String, - "sequence_number": Schema.Number.check(Schema.isFinite()) -}).annotate({ "description": "Event emitted when function call arguments streaming is complete" }) -export type OpenResponsesReasoningDeltaEvent = { - readonly "type": "response.reasoning_text.delta" - readonly "output_index": number + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.custom_tool_call_input.delta") +}).annotate({ + "description": + "Event emitted when a custom tool call's freeform input is being streamed. Mirrors `response.function_call_arguments.delta` but for `custom` tools whose input is opaque text rather than JSON arguments.", + "identifier": "BaseCustomToolCallInputDeltaEvent" +}) +export type BaseCustomToolCallInputDoneEvent = { + readonly "input": string readonly "item_id": string - readonly "content_index": number - readonly "delta": string + readonly "output_index": number readonly "sequence_number": number + readonly "type": "response.custom_tool_call_input.done" } -export const OpenResponsesReasoningDeltaEvent = Schema.Struct({ - "type": Schema.Literal("response.reasoning_text.delta"), - "output_index": Schema.Number.check(Schema.isFinite()), +export const BaseCustomToolCallInputDoneEvent = Schema.Struct({ + "input": Schema.String, "item_id": Schema.String, - "content_index": Schema.Number.check(Schema.isFinite()), - "delta": Schema.String, - "sequence_number": Schema.Number.check(Schema.isFinite()) -}).annotate({ "description": "Event emitted when reasoning text delta is streamed" }) -export type OpenResponsesReasoningDoneEvent = { - readonly "type": "response.reasoning_text.done" - readonly "output_index": number + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.custom_tool_call_input.done") +}).annotate({ + "description": + "Event emitted when a custom tool call's freeform input streaming is complete. Mirrors `response.function_call_arguments.done` but for `custom` tools.", + "identifier": "BaseCustomToolCallInputDoneEvent" +}) +export type BaseErrorEvent = { + readonly "code": string | null + readonly "message": string + readonly "param": string | null + readonly "sequence_number": number + readonly "type": "error" +} +export const BaseErrorEvent = Schema.Struct({ + "code": Schema.Union([Schema.String, Schema.Null]), + "message": Schema.String, + "param": Schema.Union([Schema.String, Schema.Null]), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("error") +}).annotate({ "description": "Event emitted when an error occurs during streaming", "identifier": "BaseErrorEvent" }) +export type BaseFunctionCallArgsDeltaEvent = { + readonly "delta": string readonly "item_id": string - readonly "content_index": number - readonly "text": string + readonly "output_index": number readonly "sequence_number": number + readonly "type": "response.function_call_arguments.delta" } -export const OpenResponsesReasoningDoneEvent = Schema.Struct({ - "type": Schema.Literal("response.reasoning_text.done"), - "output_index": Schema.Number.check(Schema.isFinite()), +export const BaseFunctionCallArgsDeltaEvent = Schema.Struct({ + "delta": Schema.String, "item_id": Schema.String, - "content_index": Schema.Number.check(Schema.isFinite()), - "text": Schema.String, - "sequence_number": Schema.Number.check(Schema.isFinite()) -}).annotate({ "description": "Event emitted when reasoning text streaming is complete" }) -export type OpenResponsesReasoningSummaryTextDeltaEvent = { - readonly "type": "response.reasoning_summary_text.delta" + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.function_call_arguments.delta") +}).annotate({ + "description": "Event emitted when function call arguments are being streamed", + "identifier": "BaseFunctionCallArgsDeltaEvent" +}) +export type BaseFunctionCallArgsDoneEvent = { + readonly "arguments": string readonly "item_id": string + readonly "name": string readonly "output_index": number - readonly "summary_index": number - readonly "delta": string readonly "sequence_number": number + readonly "type": "response.function_call_arguments.done" } -export const OpenResponsesReasoningSummaryTextDeltaEvent = Schema.Struct({ - "type": Schema.Literal("response.reasoning_summary_text.delta"), +export const BaseFunctionCallArgsDoneEvent = Schema.Struct({ + "arguments": Schema.String, "item_id": Schema.String, - "output_index": Schema.Number.check(Schema.isFinite()), - "summary_index": Schema.Number.check(Schema.isFinite()), + "name": Schema.String, + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.function_call_arguments.done") +}).annotate({ + "description": "Event emitted when function call arguments streaming is complete", + "identifier": "BaseFunctionCallArgsDoneEvent" +}) +export type BaseReasoningDeltaEvent = { + readonly "content_index": number + readonly "delta": string + readonly "item_id": string + readonly "output_index": number + readonly "sequence_number": number + readonly "type": "response.reasoning_text.delta" +} +export const BaseReasoningDeltaEvent = Schema.Struct({ + "content_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), "delta": Schema.String, - "sequence_number": Schema.Number.check(Schema.isFinite()) -}).annotate({ "description": "Event emitted when reasoning summary text delta is streamed" }) -export type OpenResponsesReasoningSummaryTextDoneEvent = { - readonly "type": "response.reasoning_summary_text.done" + "item_id": Schema.String, + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.reasoning_text.delta") +}).annotate({ + "description": "Event emitted when reasoning text delta is streamed", + "identifier": "BaseReasoningDeltaEvent" +}) +export type BaseReasoningDoneEvent = { + readonly "content_index": number readonly "item_id": string readonly "output_index": number - readonly "summary_index": number - readonly "text": string readonly "sequence_number": number + readonly "text": string + readonly "type": "response.reasoning_text.done" } -export const OpenResponsesReasoningSummaryTextDoneEvent = Schema.Struct({ - "type": Schema.Literal("response.reasoning_summary_text.done"), +export const BaseReasoningDoneEvent = Schema.Struct({ + "content_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), "item_id": Schema.String, - "output_index": Schema.Number.check(Schema.isFinite()), - "summary_index": Schema.Number.check(Schema.isFinite()), + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), "text": Schema.String, - "sequence_number": Schema.Number.check(Schema.isFinite()) -}).annotate({ "description": "Event emitted when reasoning summary text streaming is complete" }) -export type OpenResponsesImageGenCallInProgress = { - readonly "type": "response.image_generation_call.in_progress" + "type": Schema.Literal("response.reasoning_text.done") +}).annotate({ + "description": "Event emitted when reasoning text streaming is complete", + "identifier": "BaseReasoningDoneEvent" +}) +export type BaseReasoningSummaryTextDeltaEvent = { + readonly "delta": string readonly "item_id": string readonly "output_index": number readonly "sequence_number": number + readonly "summary_index": number + readonly "type": "response.reasoning_summary_text.delta" } -export const OpenResponsesImageGenCallInProgress = Schema.Struct({ - "type": Schema.Literal("response.image_generation_call.in_progress"), +export const BaseReasoningSummaryTextDeltaEvent = Schema.Struct({ + "delta": Schema.String, "item_id": Schema.String, - "output_index": Schema.Number.check(Schema.isFinite()), - "sequence_number": Schema.Number.check(Schema.isFinite()) -}).annotate({ "description": "Image generation call in progress" }) -export type OpenResponsesImageGenCallGenerating = { - readonly "type": "response.image_generation_call.generating" + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "summary_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.reasoning_summary_text.delta") +}).annotate({ + "description": "Event emitted when reasoning summary text delta is streamed", + "identifier": "BaseReasoningSummaryTextDeltaEvent" +}) +export type BaseReasoningSummaryTextDoneEvent = { readonly "item_id": string readonly "output_index": number readonly "sequence_number": number + readonly "summary_index": number + readonly "text": string + readonly "type": "response.reasoning_summary_text.done" } -export const OpenResponsesImageGenCallGenerating = Schema.Struct({ - "type": Schema.Literal("response.image_generation_call.generating"), +export const BaseReasoningSummaryTextDoneEvent = Schema.Struct({ "item_id": Schema.String, - "output_index": Schema.Number.check(Schema.isFinite()), - "sequence_number": Schema.Number.check(Schema.isFinite()) -}).annotate({ "description": "Image generation call is generating" }) -export type OpenResponsesImageGenCallPartialImage = { - readonly "type": "response.image_generation_call.partial_image" + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "summary_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "text": Schema.String, + "type": Schema.Literal("response.reasoning_summary_text.done") +}).annotate({ + "description": "Event emitted when reasoning summary text streaming is complete", + "identifier": "BaseReasoningSummaryTextDoneEvent" +}) +export type BaseRefusalDeltaEvent = { + readonly "content_index": number + readonly "delta": string readonly "item_id": string readonly "output_index": number readonly "sequence_number": number - readonly "partial_image_b64": string - readonly "partial_image_index": number + readonly "type": "response.refusal.delta" } -export const OpenResponsesImageGenCallPartialImage = Schema.Struct({ - "type": Schema.Literal("response.image_generation_call.partial_image"), +export const BaseRefusalDeltaEvent = Schema.Struct({ + "content_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "delta": Schema.String, "item_id": Schema.String, - "output_index": Schema.Number.check(Schema.isFinite()), - "sequence_number": Schema.Number.check(Schema.isFinite()), - "partial_image_b64": Schema.String, - "partial_image_index": Schema.Number.check(Schema.isFinite()) -}).annotate({ "description": "Image generation call with partial image" }) -export type OpenResponsesImageGenCallCompleted = { - readonly "type": "response.image_generation_call.completed" + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.refusal.delta") +}).annotate({ "description": "Event emitted when a refusal delta is streamed", "identifier": "BaseRefusalDeltaEvent" }) +export type BaseRefusalDoneEvent = { + readonly "content_index": number readonly "item_id": string readonly "output_index": number + readonly "refusal": string readonly "sequence_number": number + readonly "type": "response.refusal.done" } -export const OpenResponsesImageGenCallCompleted = Schema.Struct({ - "type": Schema.Literal("response.image_generation_call.completed"), +export const BaseRefusalDoneEvent = Schema.Struct({ + "content_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), "item_id": Schema.String, - "output_index": Schema.Number.check(Schema.isFinite()), - "sequence_number": Schema.Number.check(Schema.isFinite()) -}).annotate({ "description": "Image generation call completed" }) -export type BadRequestResponseErrorData = { - readonly "code": number - readonly "message": string - readonly "metadata"?: {} + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "refusal": Schema.String, + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.refusal.done") +}).annotate({ "description": "Event emitted when refusal streaming is complete", "identifier": "BaseRefusalDoneEvent" }) +export type BashServerToolEngine = "auto" | "native" | "openrouter" +export const BashServerToolEngine = Schema.Literals(["auto", "native", "openrouter"]).annotate({ + "description": + "Which bash engine to use. \"openrouter\" runs commands server-side in the OpenRouter sandbox. \"auto\" (default) and \"native\" use native passthrough, returning the tool call to your application to run client-side; OpenRouter does not execute the commands.", + "identifier": "BashServerToolEngine" +}) +export type BooleanCapability = { readonly "type": "boolean" } +export const BooleanCapability = Schema.Struct({ "type": Schema.Literal("boolean") }).annotate({ + "description": "A supported-or-not flag. Present means the parameter is accepted.", + "identifier": "BooleanCapability" +}) +export type BulkAddWorkspaceMembersRequest = { readonly "user_ids": ReadonlyArray } +export const BulkAddWorkspaceMembersRequest = Schema.Struct({ + "user_ids": Schema.Array(Schema.String).annotate({ + "description": + "List of user IDs to add to the workspace. Members are assigned the same role they hold in the organization." + }).check(Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" })).check( + Schema.isMaxLength(100).annotate({ "expected": "a value with a length of at most 100" }) + ) +}).annotate({ "identifier": "BulkAddWorkspaceMembersRequest" }) +export type BulkAssignKeysRequest = { readonly "key_hashes": ReadonlyArray } +export const BulkAssignKeysRequest = Schema.Struct({ + "key_hashes": Schema.Array( + Schema.String.check(Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" })) + ).annotate({ "description": "Array of API key hashes to assign to the guardrail" }).check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ) +}).annotate({ "identifier": "BulkAssignKeysRequest" }) +export type BulkAssignKeysResponse = { readonly "assigned_count": number } +export const BulkAssignKeysResponse = Schema.Struct({ + "assigned_count": Schema.Number.annotate({ "description": "Number of keys successfully assigned" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ) +}).annotate({ "identifier": "BulkAssignKeysResponse" }) +export type BulkAssignMembersRequest = { readonly "member_user_ids": ReadonlyArray } +export const BulkAssignMembersRequest = Schema.Struct({ + "member_user_ids": Schema.Array( + Schema.String.check(Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" })) + ).annotate({ "description": "Array of member user IDs to assign to the guardrail" }).check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ) +}).annotate({ "identifier": "BulkAssignMembersRequest" }) +export type BulkAssignMembersResponse = { readonly "assigned_count": number } +export const BulkAssignMembersResponse = Schema.Struct({ + "assigned_count": Schema.Number.annotate({ "description": "Number of members successfully assigned" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ) +}).annotate({ "identifier": "BulkAssignMembersResponse" }) +export type BulkRemoveWorkspaceMembersRequest = { readonly "user_ids": ReadonlyArray } +export const BulkRemoveWorkspaceMembersRequest = Schema.Struct({ + "user_ids": Schema.Array(Schema.String).annotate({ "description": "List of user IDs to remove from the workspace" }) + .check(Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" })).check( + Schema.isMaxLength(100).annotate({ "expected": "a value with a length of at most 100" }) + ) +}).annotate({ "identifier": "BulkRemoveWorkspaceMembersRequest" }) +export type BulkRemoveWorkspaceMembersResponse = { readonly "removed_count": number } +export const BulkRemoveWorkspaceMembersResponse = Schema.Struct({ + "removed_count": Schema.Number.annotate({ "description": "Number of members removed" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ) +}).annotate({ "identifier": "BulkRemoveWorkspaceMembersResponse" }) +export type BulkUnassignKeysRequest = { readonly "key_hashes": ReadonlyArray } +export const BulkUnassignKeysRequest = Schema.Struct({ + "key_hashes": Schema.Array( + Schema.String.check(Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" })) + ).annotate({ "description": "Array of API key hashes to unassign from the guardrail" }).check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ) +}).annotate({ "identifier": "BulkUnassignKeysRequest" }) +export type BulkUnassignKeysResponse = { readonly "unassigned_count": number } +export const BulkUnassignKeysResponse = Schema.Struct({ + "unassigned_count": Schema.Number.annotate({ "description": "Number of keys successfully unassigned" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ) +}).annotate({ "identifier": "BulkUnassignKeysResponse" }) +export type BulkUnassignMembersRequest = { readonly "member_user_ids": ReadonlyArray } +export const BulkUnassignMembersRequest = Schema.Struct({ + "member_user_ids": Schema.Array( + Schema.String.check(Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" })) + ).annotate({ "description": "Array of member user IDs to unassign from the guardrail" }).check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ) +}).annotate({ "identifier": "BulkUnassignMembersRequest" }) +export type BulkUnassignMembersResponse = { readonly "unassigned_count": number } +export const BulkUnassignMembersResponse = Schema.Struct({ + "unassigned_count": Schema.Number.annotate({ "description": "Number of members successfully unassigned" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ) +}).annotate({ "identifier": "BulkUnassignMembersResponse" }) +export type BYOKProviderSlug = + | "ai21" + | "aion-labs" + | "akashml" + | "alibaba" + | "amazon-bedrock" + | "amazon-nova" + | "ambient" + | "anthropic" + | "arcee-ai" + | "atlas-cloud" + | "avian" + | "azure" + | "baidu" + | "baseten" + | "black-forest-labs" + | "byteplus" + | "cerebras" + | "chutes" + | "cirrascale" + | "clarifai" + | "cloudflare" + | "cohere" + | "coreweave" + | "crusoe" + | "darkbloom" + | "decart" + | "deepgram" + | "deepinfra" + | "deepseek" + | "dekallm" + | "digitalocean" + | "featherless" + | "fireworks" + | "fish-audio" + | "friendli" + | "gmicloud" + | "google-ai-studio" + | "google-vertex" + | "groq" + | "heygen" + | "inception" + | "inceptron" + | "inferact-vllm" + | "inference-net" + | "infermatic" + | "inflection" + | "io-net" + | "ionstream" + | "krea" + | "liquid" + | "mancer" + | "mara" + | "meta" + | "minimax" + | "mistral" + | "modelrun" + | "modular" + | "moonshotai" + | "morph" + | "ncompass" + | "nebius" + | "nex-agi" + | "nextbit" + | "novita" + | "nvidia" + | "open-inference" + | "openai" + | "parasail" + | "perceptron" + | "perplexity" + | "phala" + | "poolside" + | "quiver" + | "recraft" + | "reka" + | "relace" + | "runway" + | "sail-research" + | "sakana" + | "sambanova" + | "seed" + | "siliconflow" + | "sourceful" + | "stepfun" + | "streamlake" + | "switchpoint" + | "tencent" + | "tenstorrent" + | "together" + | "upstage" + | "venice" + | "wafer" + | "wandb" + | "xai" + | "xiaomi" + | "z-ai" +export const BYOKProviderSlug = Schema.Literals([ + "ai21", + "aion-labs", + "akashml", + "alibaba", + "amazon-bedrock", + "amazon-nova", + "ambient", + "anthropic", + "arcee-ai", + "atlas-cloud", + "avian", + "azure", + "baidu", + "baseten", + "black-forest-labs", + "byteplus", + "cerebras", + "chutes", + "cirrascale", + "clarifai", + "cloudflare", + "cohere", + "coreweave", + "crusoe", + "darkbloom", + "decart", + "deepgram", + "deepinfra", + "deepseek", + "dekallm", + "digitalocean", + "featherless", + "fireworks", + "fish-audio", + "friendli", + "gmicloud", + "google-ai-studio", + "google-vertex", + "groq", + "heygen", + "inception", + "inceptron", + "inferact-vllm", + "inference-net", + "infermatic", + "inflection", + "io-net", + "ionstream", + "krea", + "liquid", + "mancer", + "mara", + "meta", + "minimax", + "mistral", + "modelrun", + "modular", + "moonshotai", + "morph", + "ncompass", + "nebius", + "nex-agi", + "nextbit", + "novita", + "nvidia", + "open-inference", + "openai", + "parasail", + "perceptron", + "perplexity", + "phala", + "poolside", + "quiver", + "recraft", + "reka", + "relace", + "runway", + "sail-research", + "sakana", + "sambanova", + "seed", + "siliconflow", + "sourceful", + "stepfun", + "streamlake", + "switchpoint", + "tencent", + "tenstorrent", + "together", + "upstage", + "venice", + "wafer", + "wandb", + "xai", + "xiaomi", + "z-ai" +]).annotate({ + "description": + "The upstream provider this credential authenticates against, as a lowercase slug (e.g. `openai`, `anthropic`, `amazon-bedrock`).", + "identifier": "BYOKProviderSlug" +}) +export type ChatAssistantImages = ReadonlyArray<{ readonly "image_url": { readonly "url": string } }> +export const ChatAssistantImages = Schema.Array( + Schema.Struct({ + "image_url": Schema.Struct({ + "url": Schema.String.annotate({ "description": "URL or base64-encoded data of the generated image" }) + }) + }) +).annotate({ "description": "Generated images from image generation models", "identifier": "ChatAssistantImages" }) +export type ChatAudioOutput = { + readonly "data"?: string + readonly "expires_at"?: number + readonly "id"?: string + readonly "transcript"?: string } -export const BadRequestResponseErrorData = Schema.Struct({ - "code": Schema.Number.check(Schema.isInt()), - "message": Schema.String, - "metadata": Schema.optionalKey(Schema.Struct({})) -}).annotate({ "description": "Error data for BadRequestResponse" }) -export type UnauthorizedResponseErrorData = { - readonly "code": number - readonly "message": string - readonly "metadata"?: {} +export const ChatAudioOutput = Schema.Struct({ + "data": Schema.optionalKey(Schema.String.annotate({ "description": "Base64 encoded audio data" })), + "expires_at": Schema.optionalKey( + Schema.Number.annotate({ "description": "Audio expiration timestamp" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ) + ), + "id": Schema.optionalKey(Schema.String.annotate({ "description": "Audio output identifier" })), + "transcript": Schema.optionalKey(Schema.String.annotate({ "description": "Audio transcript" })) +}).annotate({ "description": "Audio output data or reference", "identifier": "ChatAudioOutput" }) +export type ChatContentAudio = { + readonly "input_audio": { readonly "data": string; readonly "format": string } + readonly "type": "input_audio" } -export const UnauthorizedResponseErrorData = Schema.Struct({ - "code": Schema.Number.check(Schema.isInt()), - "message": Schema.String, - "metadata": Schema.optionalKey(Schema.Struct({})) -}).annotate({ "description": "Error data for UnauthorizedResponse" }) -export type PaymentRequiredResponseErrorData = { - readonly "code": number - readonly "message": string - readonly "metadata"?: {} +export const ChatContentAudio = Schema.Struct({ + "input_audio": Schema.Struct({ + "data": Schema.String.annotate({ "description": "Base64 encoded audio data" }), + "format": Schema.String.annotate({ + "description": + "Audio format (e.g., wav, mp3, flac, m4a, ogg, aiff, aac, pcm16, pcm24). Supported formats vary by provider." + }) + }), + "type": Schema.Literal("input_audio") +}).annotate({ + "description": "Audio input content part. Supported audio formats vary by provider.", + "identifier": "ChatContentAudio" +}) +export type ChatContentFile = { + readonly "file": { readonly "file_data"?: string; readonly "file_id"?: string; readonly "filename"?: string } + readonly "type": "file" } -export const PaymentRequiredResponseErrorData = Schema.Struct({ - "code": Schema.Number.check(Schema.isInt()), - "message": Schema.String, - "metadata": Schema.optionalKey(Schema.Struct({})) -}).annotate({ "description": "Error data for PaymentRequiredResponse" }) -export type NotFoundResponseErrorData = { - readonly "code": number - readonly "message": string - readonly "metadata"?: {} +export const ChatContentFile = Schema.Struct({ + "file": Schema.Struct({ + "file_data": Schema.optionalKey( + Schema.String.annotate({ "description": "File content as base64 data URL or URL" }) + ), + "file_id": Schema.optionalKey(Schema.String.annotate({ "description": "File ID for previously uploaded files" })), + "filename": Schema.optionalKey(Schema.String.annotate({ "description": "Original filename" })) + }), + "type": Schema.Literal("file") +}).annotate({ "description": "File content part for document processing", "identifier": "ChatContentFile" }) +export type ChatContentImage = { + readonly "image_url": { readonly "detail"?: "auto" | "low" | "high" | "original"; readonly "url": string } + readonly "type": "image_url" } -export const NotFoundResponseErrorData = Schema.Struct({ - "code": Schema.Number.check(Schema.isInt()), - "message": Schema.String, - "metadata": Schema.optionalKey(Schema.Struct({})) -}).annotate({ "description": "Error data for NotFoundResponse" }) -export type RequestTimeoutResponseErrorData = { - readonly "code": number - readonly "message": string - readonly "metadata"?: {} +export const ChatContentImage = Schema.Struct({ + "image_url": Schema.Struct({ + "detail": Schema.optionalKey( + Schema.Literals(["auto", "low", "high", "original"]).annotate({ + "description": + "Image detail level for vision models. `original` is an OpenRouter extension (not in the OpenAI Chat Completions spec) requesting true original-resolution media; it is downgraded to `high` for providers that lack an original-resolution tier." + }) + ), + "url": Schema.String.annotate({ "description": "URL of the image (data: URLs supported)" }) + }), + "type": Schema.Literal("image_url") +}).annotate({ "description": "Image content part for vision models", "identifier": "ChatContentImage" }) +export type ChatContentVideoInput = { readonly "url": string } +export const ChatContentVideoInput = Schema.Struct({ + "url": Schema.String.annotate({ "description": "URL of the video (data: URLs supported)" }) +}).annotate({ "description": "Video input object", "identifier": "ChatContentVideoInput" }) +export type ChatDebugOptions = { readonly "echo_upstream_body"?: boolean } +export const ChatDebugOptions = Schema.Struct({ + "echo_upstream_body": Schema.optionalKey( + Schema.Boolean.annotate({ + "description": + "If true, includes the transformed upstream request body in a debug chunk at the start of the stream. Only works with streaming mode." + }) + ) +}).annotate({ + "description": "Debug options for inspecting request transformations (streaming only)", + "identifier": "ChatDebugOptions" +}) +export type ChatFinishReasonEnum = "tool_calls" | "stop" | "length" | "content_filter" | "error" | null +export const ChatFinishReasonEnum = Schema.Union([ + Schema.Literal("tool_calls"), + Schema.Literal("stop"), + Schema.Literal("length"), + Schema.Literal("content_filter"), + Schema.Literal("error"), + Schema.Null +]).annotate({ "identifier": "ChatFinishReasonEnum" }) +export type ChatFormatGrammarConfig = { readonly "grammar": string; readonly "type": "grammar" } +export const ChatFormatGrammarConfig = Schema.Struct({ + "grammar": Schema.String.annotate({ "description": "Custom grammar for text generation" }), + "type": Schema.Literal("grammar") +}).annotate({ "description": "Custom grammar response format", "identifier": "ChatFormatGrammarConfig" }) +export type ChatFormatJsonObjectConfig = { readonly "type": "json_object" } +export const ChatFormatJsonObjectConfig = Schema.Struct({ "type": Schema.Literal("json_object") }).annotate({ + "description": "JSON object response format", + "identifier": "ChatFormatJsonObjectConfig" +}) +export type ChatFormatPythonConfig = { readonly "type": "python" } +export const ChatFormatPythonConfig = Schema.Struct({ "type": Schema.Literal("python") }).annotate({ + "description": "Python code response format", + "identifier": "ChatFormatPythonConfig" +}) +export type ChatFormatTextConfig = { readonly "type": "text" } +export const ChatFormatTextConfig = Schema.Struct({ "type": Schema.Literal("text") }).annotate({ + "description": "Default text response format", + "identifier": "ChatFormatTextConfig" +}) +export type ChatJsonSchemaConfig = { + readonly "description"?: string + readonly "name": string + readonly "schema"?: {} + readonly "strict"?: boolean | null } -export const RequestTimeoutResponseErrorData = Schema.Struct({ - "code": Schema.Number.check(Schema.isInt()), - "message": Schema.String, - "metadata": Schema.optionalKey(Schema.Struct({})) -}).annotate({ "description": "Error data for RequestTimeoutResponse" }) -export type PayloadTooLargeResponseErrorData = { - readonly "code": number - readonly "message": string - readonly "metadata"?: {} +export const ChatJsonSchemaConfig = Schema.Struct({ + "description": Schema.optionalKey(Schema.String.annotate({ "description": "Schema description for the model" })), + "name": Schema.String.annotate({ "description": "Schema name (a-z, A-Z, 0-9, underscores, dashes, max 64 chars)" }) + .check(Schema.isMaxLength(64).annotate({ "expected": "a value with a length of at most 64" })), + "schema": Schema.optionalKey(Schema.Struct({}).annotate({ "description": "JSON Schema object" })), + "strict": Schema.optionalKey( + Schema.Union([Schema.Boolean, Schema.Null]).annotate({ "description": "Enable strict schema adherence" }) + ) +}).annotate({ "description": "JSON Schema configuration object", "identifier": "ChatJsonSchemaConfig" }) +export type ChatNamedToolChoice = { readonly "function": { readonly "name": string }; readonly "type": "function" } +export const ChatNamedToolChoice = Schema.Struct({ + "function": Schema.Struct({ "name": Schema.String.annotate({ "description": "Function name to call" }) }), + "type": Schema.Literal("function") +}).annotate({ "description": "Named tool choice for specific function", "identifier": "ChatNamedToolChoice" }) +export type ChatReasoningSummaryVerbosityEnum = "auto" | "concise" | "detailed" | null +export const ChatReasoningSummaryVerbosityEnum = Schema.Union([ + Schema.Literal("auto"), + Schema.Literal("concise"), + Schema.Literal("detailed"), + Schema.Null +]).annotate({ "identifier": "ChatReasoningSummaryVerbosityEnum" }) +export type ChatServerToolChoice = { readonly "type": string } +export const ChatServerToolChoice = Schema.Struct({ + "type": Schema.String.annotate({ + "description": + "OpenRouter server-tool type to force (e.g. `openrouter:web_search`, `web_search`, `web_search_preview`)." + }) +}).annotate({ + "description": + "OpenRouter extension: force a specific server tool by naming it directly in `tool_choice.type` instead of wrapping it in `{ type: \"function\", function: { name } }`.", + "identifier": "ChatServerToolChoice" +}) +export type Objects_6 = { readonly "include_usage"?: boolean; readonly [x: string]: Schema.Json } +export const Objects_6 = Schema.StructWithRest( + Schema.Struct({ + "include_usage": Schema.optionalKey( + Schema.Boolean.annotate({ + "description": "Deprecated: This field has no effect. Full usage details are always included." + }) + ) + }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] +) +export type ChatStreamToolCall = { + readonly "function"?: { readonly "arguments"?: string; readonly "name"?: string | null } + readonly "id"?: string | null + readonly "index": number + readonly "type"?: "function" | null } -export const PayloadTooLargeResponseErrorData = Schema.Struct({ - "code": Schema.Number.check(Schema.isInt()), - "message": Schema.String, - "metadata": Schema.optionalKey(Schema.Struct({})) -}).annotate({ "description": "Error data for PayloadTooLargeResponse" }) -export type UnprocessableEntityResponseErrorData = { - readonly "code": number - readonly "message": string - readonly "metadata"?: {} +export const ChatStreamToolCall = Schema.Struct({ + "function": Schema.optionalKey( + Schema.Struct({ + "arguments": Schema.optionalKey(Schema.String.annotate({ "description": "Function arguments as JSON string" })), + "name": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }).annotate({ "description": "Function call details" }) + ), + "id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "index": Schema.Number.annotate({ "description": "Tool call index in the array" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ), + "type": Schema.optionalKey(Schema.Union([Schema.Literal("function"), Schema.Null])) +}).annotate({ "description": "Tool call delta for streaming responses", "identifier": "ChatStreamToolCall" }) +export type ChatTokenLogprob = { + readonly "bytes": ReadonlyArray | null + readonly "logprob": number + readonly "token": string + readonly "top_logprobs": ReadonlyArray< + { readonly "bytes": ReadonlyArray | null; readonly "logprob": number; readonly "token": string } + > } -export const UnprocessableEntityResponseErrorData = Schema.Struct({ - "code": Schema.Number.check(Schema.isInt()), - "message": Schema.String, - "metadata": Schema.optionalKey(Schema.Struct({})) -}).annotate({ "description": "Error data for UnprocessableEntityResponse" }) -export type TooManyRequestsResponseErrorData = { - readonly "code": number - readonly "message": string - readonly "metadata"?: {} +export const ChatTokenLogprob = Schema.Struct({ + "bytes": Schema.Union([ + Schema.Array(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + Schema.Null + ]).annotate({ "description": "UTF-8 bytes of the token" }), + "logprob": Schema.Number.annotate({ "description": "Log probability of the token", "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ), + "token": Schema.String.annotate({ "description": "The token" }), + "top_logprobs": Schema.Array(Schema.Struct({ + "bytes": Schema.Union([ + Schema.Array(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + Schema.Null + ]), + "logprob": Schema.Number.annotate({ "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ), + "token": Schema.String + })).annotate({ "description": "Top alternative tokens with probabilities" }) +}).annotate({ "description": "Token log probability information", "identifier": "ChatTokenLogprob" }) +export type ChatToolCall = { + readonly "function": { readonly "arguments": string; readonly "name": string } + readonly "id": string + readonly "type": "function" } -export const TooManyRequestsResponseErrorData = Schema.Struct({ - "code": Schema.Number.check(Schema.isInt()), - "message": Schema.String, - "metadata": Schema.optionalKey(Schema.Struct({})) -}).annotate({ "description": "Error data for TooManyRequestsResponse" }) -export type InternalServerResponseErrorData = { - readonly "code": number - readonly "message": string - readonly "metadata"?: {} +export const ChatToolCall = Schema.Struct({ + "function": Schema.Struct({ + "arguments": Schema.String.annotate({ "description": "Function arguments as JSON string" }), + "name": Schema.String.annotate({ "description": "Function name to call" }) + }), + "id": Schema.String.annotate({ "description": "Tool call identifier" }), + "type": Schema.Literal("function") +}).annotate({ "description": "Tool call made by the assistant", "identifier": "ChatToolCall" }) +export type Union_ = { + readonly "accepted_prediction_tokens"?: number | null + readonly "audio_tokens"?: number | null + readonly "reasoning_tokens"?: number | null + readonly "rejected_prediction_tokens"?: number | null + readonly [x: string]: Schema.Json +} | null +export const Union_ = Schema.Union([ + Schema.StructWithRest( + Schema.Struct({ + "accepted_prediction_tokens": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]) + .annotate({ "description": "Accepted prediction tokens" }) + ), + "audio_tokens": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]) + .annotate({ "description": "Tokens used for audio output" }) + ), + "reasoning_tokens": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]) + .annotate({ "description": "Tokens used for reasoning" }) + ), + "rejected_prediction_tokens": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]) + .annotate({ "description": "Rejected prediction tokens" }) + ) + }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] + ), + Schema.Null +]).annotate({ "description": "Detailed completion token usage" }) +export type Union_1 = { + readonly "audio_tokens"?: number + readonly "cache_write_tokens"?: number + readonly "cached_tokens"?: number + readonly "video_tokens"?: number + readonly [x: string]: Schema.Json +} | null +export const Union_1 = Schema.Union([ + Schema.StructWithRest( + Schema.Struct({ + "audio_tokens": Schema.optionalKey( + Schema.Number.annotate({ "description": "Audio input tokens" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ) + ), + "cache_write_tokens": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Tokens written to cache. Only returned for models with explicit caching and cache write pricing." + }).check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "cached_tokens": Schema.optionalKey( + Schema.Number.annotate({ "description": "Cached prompt tokens" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ) + ), + "video_tokens": Schema.optionalKey( + Schema.Number.annotate({ "description": "Video input tokens" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ) + ) + }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] + ), + Schema.Null +]).annotate({ "description": "Detailed prompt token usage" }) +export type CodeInterpreterServerTool = { + readonly "container": string | { + readonly "file_ids"?: ReadonlyArray + readonly "memory_limit"?: "1g" | "4g" | "16g" | "64g" | null + readonly "type": "auto" + } + readonly "type": "code_interpreter" } -export const InternalServerResponseErrorData = Schema.Struct({ - "code": Schema.Number.check(Schema.isInt()), - "message": Schema.String, - "metadata": Schema.optionalKey(Schema.Struct({})) -}).annotate({ "description": "Error data for InternalServerResponse" }) -export type BadGatewayResponseErrorData = { - readonly "code": number - readonly "message": string - readonly "metadata"?: {} +export const CodeInterpreterServerTool = Schema.Struct({ + "container": Schema.Union([ + Schema.String, + Schema.Struct({ + "file_ids": Schema.optionalKey(Schema.Array(Schema.String)), + "memory_limit": Schema.optionalKey( + Schema.Union([ + Schema.Literal("1g"), + Schema.Literal("4g"), + Schema.Literal("16g"), + Schema.Literal("64g"), + Schema.Null + ]) + ), + "type": Schema.Literal("auto") + }) + ]), + "type": Schema.Literal("code_interpreter") +}).annotate({ "description": "Code interpreter tool configuration", "identifier": "CodeInterpreterServerTool" }) +export type CodexLocalShellTool = { readonly "type": "local_shell" } +export const CodexLocalShellTool = Schema.Struct({ "type": Schema.Literal("local_shell") }).annotate({ + "description": "Local shell tool configuration", + "identifier": "CodexLocalShellTool" +}) +export type CompactionItem = { + readonly "encrypted_content": string + readonly "id"?: string | null + readonly "type": "compaction" } -export const BadGatewayResponseErrorData = Schema.Struct({ - "code": Schema.Number.check(Schema.isInt()), - "message": Schema.String, - "metadata": Schema.optionalKey(Schema.Struct({})) -}).annotate({ "description": "Error data for BadGatewayResponse" }) -export type ServiceUnavailableResponseErrorData = { +export const CompactionItem = Schema.Struct({ + "encrypted_content": Schema.String, + "id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "type": Schema.Literal("compaction") +}).annotate({ "description": "A context compaction marker with encrypted summary", "identifier": "CompactionItem" }) +export type CompoundFilter = { readonly "filters": ReadonlyArray<{}>; readonly "type": "and" | "or" } +export const CompoundFilter = Schema.Struct({ + "filters": Schema.Array(Schema.Struct({})), + "type": Schema.Literals(["and", "or"]) +}).annotate({ + "description": "A compound filter that combines multiple comparison or compound filters", + "identifier": "CompoundFilter" +}) +export type ComputerUseServerTool = { + readonly "display_height": number + readonly "display_width": number + readonly "environment": "windows" | "mac" | "linux" | "ubuntu" | "browser" + readonly "type": "computer_use_preview" +} +export const ComputerUseServerTool = Schema.Struct({ + "display_height": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "display_width": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "environment": Schema.Literals(["windows", "mac", "linux", "ubuntu", "browser"]), + "type": Schema.Literal("computer_use_preview") +}).annotate({ "description": "Computer use preview tool configuration", "identifier": "ComputerUseServerTool" }) +export type ConflictResponseErrorData = { readonly "code": number readonly "message": string - readonly "metadata"?: {} + readonly "metadata"?: { readonly [x: string]: Schema.Json } | null } -export const ServiceUnavailableResponseErrorData = Schema.Struct({ - "code": Schema.Number.check(Schema.isInt()), +export const ConflictResponseErrorData = Schema.Struct({ + "code": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), "message": Schema.String, - "metadata": Schema.optionalKey(Schema.Struct({})) -}).annotate({ "description": "Error data for ServiceUnavailableResponse" }) + "metadata": Schema.optionalKey( + Schema.Union([Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })), Schema.Null]) + ) +}).annotate({ "description": "Error data for ConflictResponse", "identifier": "ConflictResponseErrorData" }) +export type ContainerAutoEnvironment = { readonly "type": "container_auto" } +export const ContainerAutoEnvironment = Schema.Struct({ "type": Schema.Literal("container_auto") }).annotate({ + "description": "An OpenRouter-managed, auto-provisioned ephemeral container.", + "identifier": "ContainerAutoEnvironment" +}) +export type ContainerReferenceEnvironment = { readonly "container_id": string; readonly "type": "container_reference" } +export const ContainerReferenceEnvironment = Schema.Struct({ + "container_id": Schema.String.annotate({ + "description": "Identifier of an existing container to reuse (max 20 characters)." + }).check(Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" })).check( + Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" }) + ).check(Schema.isPattern(new RegExp("^[\\w-]+$")).annotate({ "expected": "a string matching the RegExp ^[\\w-]+$" })), + "type": Schema.Literal("container_reference") +}).annotate({ + "description": "Reference to a previously created container to reuse.", + "identifier": "ContainerReferenceEnvironment" +}) +export type ContentFilterAction = "redact" | "block" | "flag" +export const ContentFilterAction = Schema.Literals(["redact", "block", "flag"]).annotate({ + "description": "Action taken when the pattern matches", + "identifier": "ContentFilterAction" +}) +export type ContentFilterBuiltinAction = "redact" | "block" | "flag" +export const ContentFilterBuiltinAction = Schema.Literals(["redact", "block", "flag"]).annotate({ + "description": "Action taken when the builtin filter triggers", + "identifier": "ContentFilterBuiltinAction" +}) +export type ContentFilterBuiltinSlug = + | "email" + | "phone" + | "ssn" + | "credit-card" + | "ip-address" + | "person-name" + | "address" + | "regex-prompt-injection" +export const ContentFilterBuiltinSlug = Schema.Literals([ + "email", + "phone", + "ssn", + "credit-card", + "ip-address", + "person-name", + "address", + "regex-prompt-injection" +]).annotate({ "description": "The builtin filter identifier", "identifier": "ContentFilterBuiltinSlug" }) +export type ContentPartAudio = { readonly "audio_url": { readonly "url": string }; readonly "type": "audio_url" } +export const ContentPartAudio = Schema.Struct({ + "audio_url": Schema.Struct({ "url": Schema.String }), + "type": Schema.Literal("audio_url") +}).annotate({ "identifier": "ContentPartAudio" }) +export type ContentPartImage = { readonly "image_url": { readonly "url": string }; readonly "type": "image_url" } +export const ContentPartImage = Schema.Struct({ + "image_url": Schema.Struct({ "url": Schema.String }), + "type": Schema.Literal("image_url") +}).annotate({ "identifier": "ContentPartImage" }) +export type ContentPartVideo = { readonly "type": "video_url"; readonly "video_url": { readonly "url": string } } +export const ContentPartVideo = Schema.Struct({ + "type": Schema.Literal("video_url"), + "video_url": Schema.Struct({ "url": Schema.String }) +}).annotate({ "identifier": "ContentPartVideo" }) +export type ContextCompactionItem = { + readonly "encrypted_content"?: string | null + readonly "id"?: string | null + readonly "type": "context_compaction" +} +export const ContextCompactionItem = Schema.Struct({ + "encrypted_content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "type": Schema.Literal("context_compaction") +}).annotate({ + "description": "A context compaction marker with an optional encrypted summary", + "identifier": "ContextCompactionItem" +}) +export type ContextCompressionEngine = "middle-out" +export const ContextCompressionEngine = Schema.Literal("middle-out").annotate({ + "description": "The compression engine to use. Defaults to \"middle-out\".", + "identifier": "ContextCompressionEngine" +}) +export type Objects_8 = { + readonly "upstream_inference_completions_cost": number + readonly "upstream_inference_cost"?: number | null + readonly "upstream_inference_prompt_cost": number + readonly [x: string]: Schema.Json +} +export const Objects_8 = Schema.StructWithRest( + Schema.Struct({ + "upstream_inference_completions_cost": Schema.Number.annotate({ "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ), + "upstream_inference_cost": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), Schema.Null]) + .annotate({ "format": "double" }) + ), + "upstream_inference_prompt_cost": Schema.Number.annotate({ "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ) + }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] +) +export type CreateWorkspaceRequest = { + readonly "default_image_model"?: string | null + readonly "default_provider_sort"?: string | null + readonly "default_text_model"?: string | null + readonly "description"?: string | null + readonly "io_logging_api_key_ids"?: ReadonlyArray | null + readonly "io_logging_sampling_rate"?: number + readonly "is_data_discount_logging_enabled"?: boolean + readonly "is_observability_broadcast_enabled"?: boolean + readonly "is_observability_io_logging_enabled"?: boolean + readonly "name": string + readonly "slug": string +} +export const CreateWorkspaceRequest = Schema.Struct({ + "default_image_model": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ "description": "Default image model for this workspace" }) + ), + "default_provider_sort": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "Default provider sort preference (price, throughput, latency, exacto)" + }) + ), + "default_text_model": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ "description": "Default text model for this workspace" }) + ), + "description": Schema.optionalKey( + Schema.Union([ + Schema.String.check(Schema.isMaxLength(500).annotate({ "expected": "a value with a length of at most 500" })), + Schema.Null + ]).annotate({ "description": "Description of the workspace" }) + ), + "io_logging_api_key_ids": Schema.optionalKey( + Schema.Union([ + Schema.Array(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + Schema.Null + ]).annotate({ "description": "Optional array of API key IDs to filter I/O logging" }) + ), + "io_logging_sampling_rate": Schema.optionalKey( + Schema.Number.annotate({ "description": "Sampling rate for I/O logging (0.0001-1)", "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ) + ), + "is_data_discount_logging_enabled": Schema.optionalKey( + Schema.Boolean.annotate({ "description": "Whether data discount logging is enabled" }) + ), + "is_observability_broadcast_enabled": Schema.optionalKey( + Schema.Boolean.annotate({ "description": "Whether broadcast is enabled" }) + ), + "is_observability_io_logging_enabled": Schema.optionalKey( + Schema.Boolean.annotate({ "description": "Whether private logging is enabled" }) + ), + "name": Schema.String.annotate({ "description": "Name for the new workspace" }).check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ).check(Schema.isMaxLength(100).annotate({ "expected": "a value with a length of at most 100" })), + "slug": Schema.String.annotate({ + "description": + "URL-friendly slug (lowercase alphanumeric segments separated by single hyphens, no leading/trailing hyphens)" + }).check(Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" })).check( + Schema.isMaxLength(50).annotate({ "expected": "a value with a length of at most 50" }) + ).check( + Schema.isPattern(new RegExp("^[a-z0-9]+(?:-[a-z0-9]+)*$")).annotate({ + "expected": "a string matching the RegExp ^[a-z0-9]+(?:-[a-z0-9]+)*$" + }) + ) +}).annotate({ "identifier": "CreateWorkspaceRequest" }) +export type CustomTool = { + readonly "description"?: string + readonly "format"?: { readonly "type": "text" } | { + readonly "definition": string + readonly "syntax": "lark" | "regex" + readonly "type": "grammar" + } + readonly "name": string + readonly "type": "custom" +} +export const CustomTool = Schema.Struct({ + "description": Schema.optionalKey(Schema.String), + "format": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ "type": Schema.Literal("text") }), + Schema.Struct({ + "definition": Schema.String, + "syntax": Schema.Literals(["lark", "regex"]), + "type": Schema.Literal("grammar") + }) + ]) + ), + "name": Schema.String, + "type": Schema.Literal("custom") +}).annotate({ "description": "Custom tool configuration", "identifier": "CustomTool" }) +export type CustomToolCallInputDeltaEvent = { + readonly "delta": string + readonly "item_id": string + readonly "output_index": number + readonly "sequence_number": number + readonly "type": "response.custom_tool_call_input.delta" +} +export const CustomToolCallInputDeltaEvent = Schema.Struct({ + "delta": Schema.String, + "item_id": Schema.String, + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.custom_tool_call_input.delta") +}).annotate({ + "description": + "Event emitted when a custom tool call's freeform input is being streamed. Mirrors `response.function_call_arguments.delta` but for `custom` tools whose input is opaque text rather than JSON arguments.", + "identifier": "CustomToolCallInputDeltaEvent" +}) +export type CustomToolCallInputDoneEvent = { + readonly "input": string + readonly "item_id": string + readonly "output_index": number + readonly "sequence_number": number + readonly "type": "response.custom_tool_call_input.done" +} +export const CustomToolCallInputDoneEvent = Schema.Struct({ + "input": Schema.String, + "item_id": Schema.String, + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.custom_tool_call_input.done") +}).annotate({ + "description": + "Event emitted when a custom tool call's freeform input streaming is complete. Mirrors `response.function_call_arguments.done` but for `custom` tools.", + "identifier": "CustomToolCallInputDoneEvent" +}) +export type CustomToolCallItem = { + readonly "call_id": string + readonly "id"?: string + readonly "input": string + readonly "name": string + readonly "namespace"?: string + readonly "type": "custom_tool_call" +} +export const CustomToolCallItem = Schema.Struct({ + "call_id": Schema.String, + "id": Schema.optionalKey(Schema.String), + "input": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ), + "type": Schema.Literal("custom_tool_call") +}).annotate({ + "description": + "A call to a custom (freeform-grammar) tool created by the model — distinct from `function_call`. Used for tools like Codex CLI's `apply_patch` whose payload is opaque text rather than JSON arguments.", + "identifier": "CustomToolCallItem" +}) +export type DABenchmarkEntry = { + readonly "arena": string + readonly "category": string + readonly "elo": number + readonly "rank": number + readonly "win_rate": number +} +export const DABenchmarkEntry = Schema.Struct({ + "arena": Schema.String.annotate({ "description": "Arena type (e.g. models, builders, agents)" }), + "category": Schema.String.annotate({ + "description": "Category within the arena (e.g. website, gamedev, uicomponent)" + }), + "elo": Schema.Number.annotate({ "description": "ELO rating from head-to-head arena battles", "format": "double" }) + .check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "rank": Schema.Number.annotate({ + "description": "Rank position within this arena+category among models available on OpenRouter (1 = highest ELO)" + }).check(Schema.isInt().annotate({ "expected": "an integer" })), + "win_rate": Schema.Number.annotate({ "description": "Win rate percentage in arena battles", "format": "double" }) + .check(Schema.isFinite().annotate({ "expected": "a finite number" })) +}).annotate({ + "description": "A single Design Arena benchmark entry for a specific arena+category", + "identifier": "DABenchmarkEntry" +}) +export type DatetimeServerToolConfig = { readonly "timezone"?: string } +export const DatetimeServerToolConfig = Schema.Struct({ + "timezone": Schema.optionalKey( + Schema.String.annotate({ "description": "IANA timezone name (e.g. \"America/New_York\"). Defaults to UTC." }) + ) +}).annotate({ + "description": "Configuration for the openrouter:datetime server tool", + "identifier": "DatetimeServerToolConfig" +}) +export type DebugEvent = { + readonly "debug": { + readonly "echo_upstream_body"?: {} + readonly "timings"?: { + readonly "epoch_ms": number + readonly "event": "adapter_request" | "upstream_headers_received" | "first_token_received" | "upstream_body_ended" + readonly "start_ms": number + } + } + readonly "sequence_number": number + readonly "type": "response.debug" +} +export const DebugEvent = Schema.Struct({ + "debug": Schema.Struct({ + "echo_upstream_body": Schema.optionalKey(Schema.Struct({})), + "timings": Schema.optionalKey(Schema.Struct({ + "epoch_ms": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "event": Schema.Literals([ + "adapter_request", + "upstream_headers_received", + "first_token_received", + "upstream_body_ended" + ]), + "start_ms": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + })) + }), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.debug") +}).annotate({ + "description": + "Debug event emitted when debug.echo_upstream_body is true. Contains the transformed upstream request body or timing milestones.", + "identifier": "DebugEvent" +}) +export type DefaultParameters = { + readonly "frequency_penalty"?: number | null + readonly "presence_penalty"?: number | null + readonly "repetition_penalty"?: number | null + readonly "temperature"?: number | null + readonly "top_k"?: number | null + readonly "top_p"?: number | null +} | null +export const DefaultParameters = Schema.Union([ + Schema.Struct({ + "frequency_penalty": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), Schema.Null]) + .annotate({ "format": "double" }) + ), + "presence_penalty": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), Schema.Null]) + .annotate({ "format": "double" }) + ), + "repetition_penalty": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), Schema.Null]) + .annotate({ "format": "double" }) + ), + "temperature": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), Schema.Null]) + .annotate({ "format": "double" }) + ), + "top_k": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]) + ), + "top_p": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), Schema.Null]) + .annotate({ "format": "double" }) + ) + }), + Schema.Null +]).annotate({ "description": "Default parameters for this model", "identifier": "DefaultParameters" }) +export type DeleteBYOKKeyResponse = { readonly "deleted": true } +export const DeleteBYOKKeyResponse = Schema.Struct({ + "deleted": Schema.Literal(true).annotate({ "description": "Confirmation that the BYOK credential was deleted." }) +}).annotate({ "identifier": "DeleteBYOKKeyResponse" }) +export type DeleteGuardrailResponse = { readonly "deleted": true } +export const DeleteGuardrailResponse = Schema.Struct({ + "deleted": Schema.Literal(true).annotate({ "description": "Confirmation that the guardrail was deleted" }) +}).annotate({ "identifier": "DeleteGuardrailResponse" }) +export type DeleteObservabilityDestinationResponse = { readonly "deleted": true } +export const DeleteObservabilityDestinationResponse = Schema.Struct({ + "deleted": Schema.Literal(true).annotate({ "description": "Always `true` on success." }) +}).annotate({ "identifier": "DeleteObservabilityDestinationResponse" }) +export type DeleteWorkspaceBudgetResponse = { readonly "deleted": true } +export const DeleteWorkspaceBudgetResponse = Schema.Struct({ + "deleted": Schema.Literal(true).annotate({ + "description": "Confirmation that the budget was deleted (or did not exist)" + }) +}).annotate({ "identifier": "DeleteWorkspaceBudgetResponse" }) +export type DeleteWorkspaceResponse = { readonly "deleted": true } +export const DeleteWorkspaceResponse = Schema.Struct({ + "deleted": Schema.Literal(true).annotate({ "description": "Confirmation that the workspace was deleted" }) +}).annotate({ "identifier": "DeleteWorkspaceResponse" }) +export type DeprecatedRoute = "fallback" | "sort" | null +export const DeprecatedRoute = Schema.Union([Schema.Literal("fallback"), Schema.Literal("sort"), Schema.Null]).annotate( + { + "description": + "**DEPRECATED** Use providers.sort.partition instead. Backwards-compatible alias for providers.sort.partition. Accepts legacy values: \"fallback\" (maps to \"model\"), \"sort\" (maps to \"none\").", + "identifier": "DeprecatedRoute" + } +) export type EdgeNetworkTimeoutResponseErrorData = { readonly "code": number readonly "message": string - readonly "metadata"?: {} + readonly "metadata"?: { readonly [x: string]: Schema.Json } | null } export const EdgeNetworkTimeoutResponseErrorData = Schema.Struct({ - "code": Schema.Number.check(Schema.isInt()), + "code": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), "message": Schema.String, - "metadata": Schema.optionalKey(Schema.Struct({})) -}).annotate({ "description": "Error data for EdgeNetworkTimeoutResponse" }) -export type ProviderOverloadedResponseErrorData = { + "metadata": Schema.optionalKey( + Schema.Union([Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })), Schema.Null]) + ) +}).annotate({ + "description": "Error data for EdgeNetworkTimeoutResponse", + "identifier": "EdgeNetworkTimeoutResponseErrorData" +}) +export type EndpointInfo = { readonly "model": string; readonly "provider": string; readonly "selected": boolean } +export const EndpointInfo = Schema.Struct({ + "model": Schema.String, + "provider": Schema.String, + "selected": Schema.Boolean +}).annotate({ "identifier": "EndpointInfo" }) +export type EndpointStatus = 0 | -1 | -2 | -3 | -5 | -10 +export const EndpointStatus = Schema.Literals([0, -1, -2, -3, -5, -10]).annotate({ "identifier": "EndpointStatus" }) +export type EnumCapability = { readonly "type": "enum"; readonly "values": ReadonlyArray } +export const EnumCapability = Schema.Struct({ "type": Schema.Literal("enum"), "values": Schema.Array(Schema.String) }) + .annotate({ + "description": "A parameter that accepts one of a discrete set of string values.", + "identifier": "EnumCapability" + }) +export type ErrorEvent = { + readonly "code": string | null + readonly "message": string + readonly "param": string | null + readonly "sequence_number": number + readonly "type": "error" +} +export const ErrorEvent = Schema.Struct({ + "code": Schema.Union([Schema.String, Schema.Null]), + "message": Schema.String, + "param": Schema.Union([Schema.String, Schema.Null]), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("error") +}).annotate({ "description": "Event emitted when an error occurs during streaming", "identifier": "ErrorEvent" }) +export type FileCitation = { + readonly "file_id": string + readonly "filename": string + readonly "index": number + readonly "type": "file_citation" +} +export const FileCitation = Schema.Struct({ + "file_id": Schema.String, + "filename": Schema.String, + "index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("file_citation") +}).annotate({ "identifier": "FileCitation" }) +export type FileDeleteResponse = { readonly "id": string; readonly "type": "file_deleted" } +export const FileDeleteResponse = Schema.Struct({ "id": Schema.String, "type": Schema.Literal("file_deleted") }) + .annotate({ "description": "Confirmation that a file was deleted.", "identifier": "FileDeleteResponse" }) +export type FileMetadata = { + readonly "created_at": string + readonly "downloadable": boolean + readonly "filename": string + readonly "id": string + readonly "mime_type": string + readonly "size_bytes": number + readonly "type": "file" +} +export const FileMetadata = Schema.Struct({ + "created_at": Schema.String, + "downloadable": Schema.Boolean, + "filename": Schema.String, + "id": Schema.String, + "mime_type": Schema.String, + "size_bytes": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("file") +}).annotate({ "description": "Metadata describing a stored file.", "identifier": "FileMetadata" }) +export type FilePath = { readonly "file_id": string; readonly "index": number; readonly "type": "file_path" } +export const FilePath = Schema.Struct({ + "file_id": Schema.String, + "index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("file_path") +}).annotate({ "identifier": "FilePath" }) +export type FilesServerToolConfig = {} +export const FilesServerToolConfig = Schema.Struct({}).annotate({ + "description": "Configuration for the openrouter:files server tool", + "identifier": "FilesServerToolConfig" +}) +export type ForbiddenResponseErrorData = { readonly "code": number readonly "message": string - readonly "metadata"?: {} + readonly "metadata"?: { readonly [x: string]: Schema.Json } | null } -export const ProviderOverloadedResponseErrorData = Schema.Struct({ - "code": Schema.Number.check(Schema.isInt()), +export const ForbiddenResponseErrorData = Schema.Struct({ + "code": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), "message": Schema.String, - "metadata": Schema.optionalKey(Schema.Struct({})) -}).annotate({ "description": "Error data for ProviderOverloadedResponse" }) -export type ResponseInputVideo = { readonly "type": "input_video"; readonly "video_url": string } -export const ResponseInputVideo = Schema.Struct({ - "type": Schema.Literal("input_video"), - "video_url": Schema.String.annotate({ - "description": "A base64 data URL or remote URL that resolves to a video file" + "metadata": Schema.optionalKey( + Schema.Union([Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })), Schema.Null]) + ) +}).annotate({ "description": "Error data for ForbiddenResponse", "identifier": "ForbiddenResponseErrorData" }) +export type FormatJsonObjectConfig = { readonly "type": "json_object" } +export const FormatJsonObjectConfig = Schema.Struct({ "type": Schema.Literal("json_object") }).annotate({ + "description": "JSON object response format", + "identifier": "FormatJsonObjectConfig" +}) +export type FormatJsonSchemaConfig = { + readonly "description"?: string + readonly "name": string + readonly "schema": {} + readonly "strict"?: boolean | null + readonly "type": "json_schema" +} +export const FormatJsonSchemaConfig = Schema.Struct({ + "description": Schema.optionalKey(Schema.String), + "name": Schema.String, + "schema": Schema.Struct({}), + "strict": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), + "type": Schema.Literal("json_schema") +}).annotate({ "description": "JSON schema constrained response format", "identifier": "FormatJsonSchemaConfig" }) +export type FormatTextConfig = { readonly "type": "text" } +export const FormatTextConfig = Schema.Struct({ "type": Schema.Literal("text") }).annotate({ + "description": "Plain text response format", + "identifier": "FormatTextConfig" +}) +export type FrameImage = { + readonly "image_url": { readonly "url": string } + readonly "type": "image_url" + readonly "frame_type": "first_frame" | "last_frame" +} +export const FrameImage = Schema.Struct({ + "image_url": Schema.Struct({ "url": Schema.String }), + "type": Schema.Literal("image_url"), + "frame_type": Schema.Literals(["first_frame", "last_frame"]).annotate({ + "description": "Whether this image represents the first or last frame of the video" }) -}).annotate({ "description": "Video input content item" }) -export type ResponsesOutputModality = "text" | "image" -export const ResponsesOutputModality = Schema.Literals(["text", "image"]) -export type OpenAIResponsesIncludable = - | "file_search_call.results" - | "message.input_image.image_url" - | "computer_call_output.output.image_url" - | "reasoning.encrypted_content" - | "code_interpreter_call.outputs" -export const OpenAIResponsesIncludable = Schema.Literals([ - "file_search_call.results", - "message.input_image.image_url", - "computer_call_output.output.image_url", - "reasoning.encrypted_content", - "code_interpreter_call.outputs" -]) -export type DataCollection = "deny" | "allow" -export const DataCollection = Schema.Literals(["deny", "allow"]).annotate({ - "description": - "Data collection setting. If no available model provider meets the requirement, your request will return an error.\n- allow: (default) allow providers which store user data non-transiently and may train on it\n\n- deny: use only providers which do not collect user data." +}).annotate({ "identifier": "FrameImage" }) +export type FunctionCallArgsDeltaEvent = { + readonly "delta": string + readonly "item_id": string + readonly "output_index": number + readonly "sequence_number": number + readonly "type": "response.function_call_arguments.delta" +} +export const FunctionCallArgsDeltaEvent = Schema.Struct({ + "delta": Schema.String, + "item_id": Schema.String, + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.function_call_arguments.delta") +}).annotate({ + "description": "Event emitted when function call arguments are being streamed", + "identifier": "FunctionCallArgsDeltaEvent" }) -export type ProviderName = - | "AI21" - | "AionLabs" - | "Alibaba" - | "Ambient" - | "Amazon Bedrock" - | "Amazon Nova" - | "Anthropic" - | "Arcee AI" - | "AtlasCloud" - | "Avian" - | "Azure" - | "BaseTen" - | "BytePlus" - | "Black Forest Labs" - | "Cerebras" - | "Chutes" - | "Cirrascale" - | "Clarifai" - | "Cloudflare" - | "Cohere" - | "Crusoe" - | "DeepInfra" - | "DeepSeek" - | "Featherless" - | "Fireworks" - | "Friendli" - | "GMICloud" - | "Google" - | "Google AI Studio" - | "Groq" - | "Hyperbolic" - | "Inception" - | "Inceptron" - | "InferenceNet" - | "Infermatic" - | "Io Net" - | "Inflection" - | "Liquid" - | "Mara" - | "Mancer 2" - | "Minimax" - | "ModelRun" - | "Mistral" - | "Modular" - | "Moonshot AI" - | "Morph" - | "NCompass" - | "Nebius" - | "NextBit" - | "Novita" - | "Nvidia" - | "OpenAI" - | "OpenInference" - | "Parasail" - | "Perplexity" - | "Phala" - | "Relace" - | "SambaNova" - | "Seed" - | "SiliconFlow" - | "Sourceful" - | "StepFun" - | "Stealth" - | "StreamLake" - | "Switchpoint" - | "Together" - | "Upstage" - | "Venice" - | "WandB" - | "Xiaomi" - | "xAI" - | "Z.AI" - | "FakeProvider" -export const ProviderName = Schema.Literals([ - "AI21", - "AionLabs", - "Alibaba", - "Ambient", - "Amazon Bedrock", - "Amazon Nova", - "Anthropic", - "Arcee AI", - "AtlasCloud", - "Avian", - "Azure", - "BaseTen", - "BytePlus", - "Black Forest Labs", - "Cerebras", - "Chutes", - "Cirrascale", - "Clarifai", - "Cloudflare", - "Cohere", - "Crusoe", - "DeepInfra", - "DeepSeek", - "Featherless", - "Fireworks", - "Friendli", - "GMICloud", - "Google", - "Google AI Studio", - "Groq", - "Hyperbolic", - "Inception", - "Inceptron", - "InferenceNet", - "Infermatic", - "Io Net", - "Inflection", - "Liquid", - "Mara", - "Mancer 2", - "Minimax", - "ModelRun", - "Mistral", - "Modular", - "Moonshot AI", - "Morph", - "NCompass", - "Nebius", - "NextBit", - "Novita", - "Nvidia", - "OpenAI", - "OpenInference", - "Parasail", - "Perplexity", - "Phala", - "Relace", - "SambaNova", - "Seed", - "SiliconFlow", - "Sourceful", - "StepFun", - "Stealth", - "StreamLake", - "Switchpoint", - "Together", - "Upstage", - "Venice", - "WandB", - "Xiaomi", - "xAI", - "Z.AI", - "FakeProvider" -]) -export type Quantization = "int4" | "int8" | "fp4" | "fp6" | "fp8" | "fp16" | "bf16" | "fp32" | "unknown" -export const Quantization = Schema.Literals(["int4", "int8", "fp4", "fp6", "fp8", "fp16", "bf16", "fp32", "unknown"]) -export type ProviderSort = "price" | "throughput" | "latency" -export const ProviderSort = Schema.Literals(["price", "throughput", "latency"]) -export type BigNumberUnion = string -export const BigNumberUnion = Schema.String.annotate({ "description": "Price per million prompt tokens" }) -export type PercentileThroughputCutoffs = { - readonly "p50"?: number - readonly "p75"?: number - readonly "p90"?: number - readonly "p99"?: number +export type FunctionCallArgsDoneEvent = { + readonly "arguments": string + readonly "item_id": string + readonly "name": string + readonly "output_index": number + readonly "sequence_number": number + readonly "type": "response.function_call_arguments.done" } -export const PercentileThroughputCutoffs = Schema.Struct({ - "p50": Schema.optionalKey( - Schema.Number.annotate({ "description": "Minimum p50 throughput (tokens/sec)" }).check(Schema.isFinite()) - ), - "p75": Schema.optionalKey( - Schema.Number.annotate({ "description": "Minimum p75 throughput (tokens/sec)" }).check(Schema.isFinite()) - ), - "p90": Schema.optionalKey( - Schema.Number.annotate({ "description": "Minimum p90 throughput (tokens/sec)" }).check(Schema.isFinite()) - ), - "p99": Schema.optionalKey( - Schema.Number.annotate({ "description": "Minimum p99 throughput (tokens/sec)" }).check(Schema.isFinite()) - ) +export const FunctionCallArgsDoneEvent = Schema.Struct({ + "arguments": Schema.String, + "item_id": Schema.String, + "name": Schema.String, + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.function_call_arguments.done") }).annotate({ - "description": - "Percentile-based throughput cutoffs. All specified cutoffs must be met for an endpoint to be preferred." + "description": "Event emitted when function call arguments streaming is complete", + "identifier": "FunctionCallArgsDoneEvent" }) -export type PercentileLatencyCutoffs = { - readonly "p50"?: number - readonly "p75"?: number - readonly "p90"?: number - readonly "p99"?: number +export type FunctionTool = { + readonly "description"?: string | null + readonly "name": string + readonly "parameters": { readonly [x: string]: Schema.Json } | null + readonly "strict"?: boolean | null + readonly "type": "function" } -export const PercentileLatencyCutoffs = Schema.Struct({ - "p50": Schema.optionalKey( - Schema.Number.annotate({ "description": "Maximum p50 latency (seconds)" }).check(Schema.isFinite()) - ), - "p75": Schema.optionalKey( - Schema.Number.annotate({ "description": "Maximum p75 latency (seconds)" }).check(Schema.isFinite()) - ), - "p90": Schema.optionalKey( - Schema.Number.annotate({ "description": "Maximum p90 latency (seconds)" }).check(Schema.isFinite()) - ), - "p99": Schema.optionalKey( - Schema.Number.annotate({ "description": "Maximum p99 latency (seconds)" }).check(Schema.isFinite()) - ) +export const FunctionTool = Schema.Struct({ + "description": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "name": Schema.String, + "parameters": Schema.Union([ + Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })), + Schema.Null + ]), + "strict": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), + "type": Schema.Literal("function") +}).annotate({ "description": "Function tool definition", "identifier": "FunctionTool" }) +export type Arrays_1 = ReadonlyArray +export const Arrays_1 = Schema.Array(Schema.String) +export type Arrays_2 = ReadonlyArray +export const Arrays_2 = Schema.Array(Schema.String) +export type Arrays_3 = ReadonlyArray< + { + readonly "stances": ReadonlyArray<{ readonly "model": string; readonly "stance": string }> + readonly "topic": string + } +> +export const Arrays_3 = Schema.Array( + Schema.Struct({ + "stances": Schema.Array(Schema.Struct({ "model": Schema.String, "stance": Schema.String })), + "topic": Schema.String + }) +) +export type Arrays_4 = ReadonlyArray<{ readonly "models": ReadonlyArray; readonly "point": string }> +export const Arrays_4 = Schema.Array(Schema.Struct({ "models": Schema.Array(Schema.String), "point": Schema.String })) +export type Arrays_5 = ReadonlyArray<{ readonly "insight": string; readonly "model": string }> +export const Arrays_5 = Schema.Array(Schema.Struct({ "insight": Schema.String, "model": Schema.String })) +export type FusionCallAnalysisInProgressEvent = { + readonly "item_id": string + readonly "judge_model": string + readonly "output_index": number + readonly "sequence_number": number + readonly "type": "response.fusion_call.analysis.in_progress" +} +export const FusionCallAnalysisInProgressEvent = Schema.Struct({ + "item_id": Schema.String, + "judge_model": Schema.String, + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.fusion_call.analysis.in_progress") }).annotate({ - "description": "Percentile-based latency cutoffs. All specified cutoffs must be met for an endpoint to be preferred." + "description": "Emitted when the fusion judge starts producing the structured analysis.", + "identifier": "FusionCallAnalysisInProgressEvent" }) -export type WebSearchEngine = "native" | "exa" -export const WebSearchEngine = Schema.Literals(["native", "exa"]).annotate({ - "description": "The search engine to use for web search." +export type FusionCallCompletedEvent = { + readonly "item_id": string + readonly "output_index": number + readonly "sequence_number": number + readonly "type": "response.fusion_call.completed" +} +export const FusionCallCompletedEvent = Schema.Struct({ + "item_id": Schema.String, + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.fusion_call.completed") +}).annotate({ + "description": "Emitted when the openrouter:fusion tool call finishes.", + "identifier": "FusionCallCompletedEvent" }) -export type PDFParserEngine = "mistral-ocr" | "pdf-text" | "native" -export const PDFParserEngine = Schema.Literals(["mistral-ocr", "pdf-text", "native"]).annotate({ - "description": "The engine to use for parsing PDF files." +export type FusionCallInProgressEvent = { + readonly "item_id": string + readonly "output_index": number + readonly "sequence_number": number + readonly "type": "response.fusion_call.in_progress" +} +export const FusionCallInProgressEvent = Schema.Struct({ + "item_id": Schema.String, + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.fusion_call.in_progress") +}).annotate({ + "description": "Emitted when an openrouter:fusion tool call begins executing.", + "identifier": "FusionCallInProgressEvent" }) -export type AnthropicMessagesResponse = { - readonly "id": string - readonly "type": "message" - readonly "role": "assistant" - readonly "content": ReadonlyArray< - | { - readonly "type": "text" - readonly "text": string - readonly "citations": ReadonlyArray< - { - readonly "type": "char_location" - readonly "cited_text": string - readonly "document_index": number - readonly "document_title": string - readonly "start_char_index": number - readonly "end_char_index": number - readonly "file_id": string - } | { - readonly "type": "page_location" - readonly "cited_text": string - readonly "document_index": number - readonly "document_title": string - readonly "start_page_number": number - readonly "end_page_number": number - readonly "file_id": string - } | { - readonly "type": "content_block_location" - readonly "cited_text": string - readonly "document_index": number - readonly "document_title": string - readonly "start_block_index": number - readonly "end_block_index": number - readonly "file_id": string - } | { - readonly "type": "web_search_result_location" - readonly "cited_text": string - readonly "encrypted_index": string - readonly "title": string - readonly "url": string - } | { - readonly "type": "search_result_location" - readonly "cited_text": string - readonly "search_result_index": number - readonly "source": string - readonly "title": string - readonly "start_block_index": number - readonly "end_block_index": number - } - > - } - | { readonly "type": "tool_use"; readonly "id": string; readonly "name": string; readonly "input"?: unknown } - | { readonly "type": "thinking"; readonly "thinking": string; readonly "signature": string } - | { readonly "type": "redacted_thinking"; readonly "data": string } - | { - readonly "type": "server_tool_use" - readonly "id": string - readonly "name": "web_search" - readonly "input"?: unknown - } - | { - readonly "type": "web_search_tool_result" - readonly "tool_use_id": string - readonly "content": - | ReadonlyArray< - { - readonly "type": "web_search_result" - readonly "encrypted_content": string - readonly "page_age": string - readonly "title": string - readonly "url": string - } - > - | { - readonly "type": "web_search_tool_result_error" - readonly "error_code": - | "invalid_tool_input" - | "unavailable" - | "max_uses_exceeded" - | "too_many_requests" - | "query_too_long" - } - } - > +export type FusionCallPanelAddedEvent = { + readonly "item_id": string readonly "model": string - readonly "stop_reason": "end_turn" | "max_tokens" | "stop_sequence" | "tool_use" | "pause_turn" | "refusal" - readonly "stop_sequence": string - readonly "usage": { - readonly "input_tokens": number - readonly "output_tokens": number - readonly "cache_creation_input_tokens": number - readonly "cache_read_input_tokens": number - readonly "cache_creation": { - readonly "ephemeral_5m_input_tokens": number - readonly "ephemeral_1h_input_tokens": number - } - readonly "inference_geo": string - readonly "server_tool_use": { readonly "web_search_requests": number } - readonly "service_tier": "standard" | "priority" | "batch" - } + readonly "output_index": number + readonly "sequence_number": number + readonly "type": "response.fusion_call.panel.added" } -export const AnthropicMessagesResponse = Schema.Struct({ - "id": Schema.String, - "type": Schema.Literal("message"), - "role": Schema.Literal("assistant"), - "content": Schema.Array(Schema.Union([ - Schema.Struct({ - "type": Schema.Literal("text"), - "text": Schema.String, - "citations": Schema.Array(Schema.Union([ - Schema.Struct({ - "type": Schema.Literal("char_location"), - "cited_text": Schema.String, - "document_index": Schema.Number.check(Schema.isFinite()), - "document_title": Schema.String, - "start_char_index": Schema.Number.check(Schema.isFinite()), - "end_char_index": Schema.Number.check(Schema.isFinite()), - "file_id": Schema.String - }), - Schema.Struct({ - "type": Schema.Literal("page_location"), - "cited_text": Schema.String, - "document_index": Schema.Number.check(Schema.isFinite()), - "document_title": Schema.String, - "start_page_number": Schema.Number.check(Schema.isFinite()), - "end_page_number": Schema.Number.check(Schema.isFinite()), - "file_id": Schema.String - }), - Schema.Struct({ - "type": Schema.Literal("content_block_location"), - "cited_text": Schema.String, - "document_index": Schema.Number.check(Schema.isFinite()), - "document_title": Schema.String, - "start_block_index": Schema.Number.check(Schema.isFinite()), - "end_block_index": Schema.Number.check(Schema.isFinite()), - "file_id": Schema.String - }), - Schema.Struct({ - "type": Schema.Literal("web_search_result_location"), - "cited_text": Schema.String, - "encrypted_index": Schema.String, - "title": Schema.String, - "url": Schema.String - }), - Schema.Struct({ - "type": Schema.Literal("search_result_location"), - "cited_text": Schema.String, - "search_result_index": Schema.Number.check(Schema.isFinite()), - "source": Schema.String, - "title": Schema.String, - "start_block_index": Schema.Number.check(Schema.isFinite()), - "end_block_index": Schema.Number.check(Schema.isFinite()) +export const FusionCallPanelAddedEvent = Schema.Struct({ + "item_id": Schema.String, + "model": Schema.String, + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.fusion_call.panel.added") +}).annotate({ + "description": "Emitted when a fusion analysis-panel model starts.", + "identifier": "FusionCallPanelAddedEvent" +}) +export type FusionCallPanelCompletedEvent = { + readonly "content": string + readonly "item_id": string + readonly "model": string + readonly "output_index": number + readonly "sequence_number": number + readonly "type": "response.fusion_call.panel.completed" +} +export const FusionCallPanelCompletedEvent = Schema.Struct({ + "content": Schema.String, + "item_id": Schema.String, + "model": Schema.String, + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.fusion_call.panel.completed") +}).annotate({ + "description": "Emitted when a fusion panel model finishes with its full content.", + "identifier": "FusionCallPanelCompletedEvent" +}) +export type FusionCallPanelDeltaEvent = { + readonly "delta": string + readonly "item_id": string + readonly "model": string + readonly "output_index": number + readonly "sequence_number": number + readonly "type": "response.fusion_call.panel.delta" +} +export const FusionCallPanelDeltaEvent = Schema.Struct({ + "delta": Schema.String, + "item_id": Schema.String, + "model": Schema.String, + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.fusion_call.panel.delta") +}).annotate({ + "description": "Incremental content token from a fusion panel model.", + "identifier": "FusionCallPanelDeltaEvent" +}) +export type FusionCallPanelFailedEvent = { + readonly "error": string + readonly "item_id": string + readonly "model": string + readonly "output_index": number + readonly "sequence_number": number + readonly "status_code"?: number + readonly "type": "response.fusion_call.panel.failed" +} +export const FusionCallPanelFailedEvent = Schema.Struct({ + "error": Schema.String, + "item_id": Schema.String, + "model": Schema.String, + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "status_code": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "type": Schema.Literal("response.fusion_call.panel.failed") +}).annotate({ "description": "Emitted when a fusion panel model fails.", "identifier": "FusionCallPanelFailedEvent" }) +export type FusionCallPanelReasoningDeltaEvent = { + readonly "delta": string + readonly "item_id": string + readonly "model": string + readonly "output_index": number + readonly "sequence_number": number + readonly "type": "response.fusion_call.panel.reasoning.delta" +} +export const FusionCallPanelReasoningDeltaEvent = Schema.Struct({ + "delta": Schema.String, + "item_id": Schema.String, + "model": Schema.String, + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.fusion_call.panel.reasoning.delta") +}).annotate({ + "description": "Incremental reasoning token from a fusion panel model.", + "identifier": "FusionCallPanelReasoningDeltaEvent" +}) +export type FusionPlugin = { + readonly "analysis_models"?: ReadonlyArray + readonly "enabled"?: boolean + readonly "id": "fusion" + readonly "max_tool_calls"?: number + readonly "model"?: string + readonly "preset"?: "general-high" | "general-budget" | "general-fast" + readonly "tools"?: ReadonlyArray<{ readonly "parameters"?: {}; readonly "type": string }> +} +export const FusionPlugin = Schema.Struct({ + "analysis_models": Schema.optionalKey( + Schema.Array(Schema.String).annotate({ + "description": + "Slugs of models to run in parallel as the \"expert panel\" the judge analyzes. Each model receives the same user prompt with web_search + web_fetch enabled. Capped at 8 models to bound cost amplification. When omitted, defaults to the Quality preset from the /labs/fusion UI (~anthropic/claude-opus-latest, ~openai/gpt-latest, ~google/gemini-pro-latest)." + }).check(Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" })).check( + Schema.isMaxLength(8).annotate({ "expected": "a value with a length of at most 8" }) + ) + ), + "enabled": Schema.optionalKey( + Schema.Boolean.annotate({ + "description": "Set to false to disable the fusion plugin for this request. Defaults to true." + }) + ), + "id": Schema.Literal("fusion"), + "max_tool_calls": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Maximum number of tool-calling steps each panelist (analysis model) and the judge model may take during their agentic web-research loop. Models with web_search/web_fetch enabled iterate until they produce a text response or hit this ceiling. Defaults to 8. Capped at 16." + }).check(Schema.isInt().annotate({ "expected": "an integer" })).check( + Schema.isGreaterThanOrEqualTo(1).annotate({ "expected": "a value greater than or equal to 1" }) + ).check(Schema.isLessThanOrEqualTo(16).annotate({ "expected": "a value less than or equal to 16" })) + ), + "model": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Slug of the model that performs both the judge step (with web_search + web_fetch) and the final synthesis. When omitted, defaults to the first model in the Quality preset." + }) + ), + "preset": Schema.optionalKey( + Schema.Literals(["general-high", "general-budget", "general-fast"]).annotate({ + "description": + "A curated OpenRouter fusion preset (slugs follow `-`, e.g. `general-high`). Expands server-side into the preset's analysis_models panel and judge model, so callers never name individual models. Explicitly provided `analysis_models` / `model` take precedence." + }) + ), + "tools": Schema.optionalKey( + Schema.Array(Schema.Struct({ + "parameters": Schema.optionalKey( + Schema.Struct({}).annotate({ + "description": "Optional configuration forwarded as the tool's `parameters` object." }) - ], { mode: "oneOf" })) - }), + ), + "type": Schema.String.annotate({ + "description": "Server tool type identifier (e.g. \"openrouter:web_search\", \"openrouter:web_fetch\")." + }) + })).annotate({ + "description": + "Server tools available to panelist and judge inner calls. Each entry uses the same `{ type, parameters? }` shorthand as the outer Chat Completions request. When omitted, defaults to `[{ type: \"openrouter:web_search\" }, { type: \"openrouter:web_fetch\" }]`. Pass an empty array to disable tools entirely (panelists answer from parametric knowledge only)." + }).check(Schema.isMaxLength(8).annotate({ "expected": "a value with a length of at most 8" })) + ) +}).annotate({ "identifier": "FusionPlugin" }) +export type Arrays_6 = ReadonlyArray +export const Arrays_6 = Schema.Array(Schema.String).annotate({ + "description": + "Slugs of models to run in parallel as the analysis panel. Each model receives the user prompt with openrouter:web_search and openrouter:web_fetch enabled, then a judge model summarizes the collective output into structured analysis JSON. Capped at 8 models to bound cost amplification. Defaults to the Quality preset from /labs/fusion." +}).check(Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" })).check( + Schema.isMaxLength(8).annotate({ "expected": "a value with a length of at most 8" }) +) +export type Objects_9 = { + readonly "effort"?: "max" | "xhigh" | "high" | "medium" | "low" | "minimal" | "none" + readonly "max_tokens"?: number +} +export const Objects_9 = Schema.Struct({ + "effort": Schema.optionalKey( + Schema.Literals(["max", "xhigh", "high", "medium", "low", "minimal", "none"]).annotate({ + "description": "Reasoning effort level for panelist and judge inner calls." + }) + ), + "max_tokens": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Maximum number of reasoning tokens each panelist and judge model may use. Helps bound cost when models allocate too much budget to chain-of-thought." + }).check(Schema.isInt().annotate({ "expected": "an integer" })) + ) +}).annotate({ + "description": + "Reasoning configuration forwarded to panelist and judge inner calls. Use this to control reasoning effort and token budget for models that support extended thinking." +}) +export type Arrays_7 = ReadonlyArray<{ readonly "parameters"?: {}; readonly "type": string }> +export const Arrays_7 = Schema.Array(Schema.Struct({ + "parameters": Schema.optionalKey( + Schema.Struct({}).annotate({ "description": "Optional configuration forwarded as the tool's `parameters` object." }) + ), + "type": Schema.String.annotate({ + "description": "Server tool type identifier (e.g. \"openrouter:web_search\", \"openrouter:web_fetch\")." + }) +})).annotate({ + "description": + "Server tools available to panelist and judge inner calls. Each entry uses the same `{ type, parameters? }` shorthand as the outer Chat Completions request. When omitted, defaults to `[{ type: \"openrouter:web_search\" }, { type: \"openrouter:web_fetch\" }]`. Pass an empty array to disable tools entirely (panelists answer from parametric knowledge only)." +}).check(Schema.isMaxLength(8).annotate({ "expected": "a value with a length of at most 8" })) +export type FusionSource = { readonly "title": string; readonly "url": string } +export const FusionSource = Schema.Struct({ + "title": Schema.String.annotate({ "description": "Title of the retrieved web page." }), + "url": Schema.String.annotate({ + "description": "URL of the web page a panel or the judge retrieved during the run." + }) +}).annotate({ "description": "A web page retrieved via web search during a fusion run.", "identifier": "FusionSource" }) +export type GenerationContentData = { + readonly "input": { readonly "prompt": string } | { readonly "messages": ReadonlyArray } + readonly "output": { readonly "completion": string | null; readonly "reasoning": string | null } +} +export const GenerationContentData = Schema.Struct({ + "input": Schema.Union([ + Schema.Struct({ "prompt": Schema.String }), + Schema.Struct({ "messages": Schema.Array(Schema.Json.annotate({ "expected": "JSON value" })) }) + ]).annotate({ "description": "The input to the generation — either a prompt string or an array of messages" }), + "output": Schema.Struct({ + "completion": Schema.Union([Schema.String, Schema.Null]).annotate({ "description": "The completion output" }), + "reasoning": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "Reasoning/thinking output, if any" + }) + }).annotate({ "description": "The output from the generation" }) +}).annotate({ "description": "Stored prompt and completion content", "identifier": "GenerationContentData" }) +export type GoneResponseErrorData = { + readonly "code": number + readonly "message": string + readonly "metadata"?: { readonly [x: string]: Schema.Json } | null +} +export const GoneResponseErrorData = Schema.Struct({ + "code": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "message": Schema.String, + "metadata": Schema.optionalKey( + Schema.Union([Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })), Schema.Null]) + ) +}).annotate({ "description": "Error data for GoneResponse", "identifier": "GoneResponseErrorData" }) +export type GuardrailInterval = "daily" | "weekly" | "monthly" | null +export const GuardrailInterval = Schema.Union([ + Schema.Literal("daily"), + Schema.Literal("weekly"), + Schema.Literal("monthly"), + Schema.Null +]).annotate({ + "description": "Interval at which the limit resets (daily, weekly, monthly)", + "identifier": "GuardrailInterval" +}) +export type ImageConfig = {} +export const ImageConfig = Schema.Struct({}).annotate({ + "description": + "Provider-specific image configuration options. Keys and values vary by model/provider. See https://openrouter.ai/docs/guides/overview/multimodal/image-generation for more details.", + "identifier": "ImageConfig" +}) +export type ImageGenCallCompletedEvent = { + readonly "item_id": string + readonly "output_index": number + readonly "sequence_number": number + readonly "type": "response.image_generation_call.completed" +} +export const ImageGenCallCompletedEvent = Schema.Struct({ + "item_id": Schema.String, + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.image_generation_call.completed") +}).annotate({ "description": "Image generation call completed", "identifier": "ImageGenCallCompletedEvent" }) +export type ImageGenCallGeneratingEvent = { + readonly "item_id": string + readonly "output_index": number + readonly "sequence_number": number + readonly "type": "response.image_generation_call.generating" +} +export const ImageGenCallGeneratingEvent = Schema.Struct({ + "item_id": Schema.String, + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.image_generation_call.generating") +}).annotate({ "description": "Image generation call is generating", "identifier": "ImageGenCallGeneratingEvent" }) +export type ImageGenCallInProgressEvent = { + readonly "item_id": string + readonly "output_index": number + readonly "sequence_number": number + readonly "type": "response.image_generation_call.in_progress" +} +export const ImageGenCallInProgressEvent = Schema.Struct({ + "item_id": Schema.String, + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.image_generation_call.in_progress") +}).annotate({ "description": "Image generation call in progress", "identifier": "ImageGenCallInProgressEvent" }) +export type ImageGenCallPartialImageEvent = { + readonly "item_id": string + readonly "output_index": number + readonly "partial_image_b64": string + readonly "partial_image_index": number + readonly "sequence_number": number + readonly "type": "response.image_generation_call.partial_image" +} +export const ImageGenCallPartialImageEvent = Schema.Struct({ + "item_id": Schema.String, + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "partial_image_b64": Schema.String, + "partial_image_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.image_generation_call.partial_image") +}).annotate({ + "description": "Image generation call with partial image", + "identifier": "ImageGenCallPartialImageEvent" +}) +export type ImageGenerationServerTool = { + readonly "background"?: "transparent" | "opaque" | "auto" + readonly "input_fidelity"?: "high" | "low" | null + readonly "input_image_mask"?: { readonly "file_id"?: string; readonly "image_url"?: string } + readonly "model"?: string + readonly "moderation"?: "auto" | "low" + readonly "output_compression"?: number + readonly "output_format"?: "png" | "webp" | "jpeg" + readonly "partial_images"?: number + readonly "quality"?: "low" | "medium" | "high" | "auto" + readonly "size"?: string + readonly "type": "image_generation" +} +export const ImageGenerationServerTool = Schema.Struct({ + "background": Schema.optionalKey(Schema.Literals(["transparent", "opaque", "auto"])), + "input_fidelity": Schema.optionalKey(Schema.Union([Schema.Literal("high"), Schema.Literal("low"), Schema.Null])), + "input_image_mask": Schema.optionalKey( + Schema.Struct({ "file_id": Schema.optionalKey(Schema.String), "image_url": Schema.optionalKey(Schema.String) }) + ), + "model": Schema.optionalKey(Schema.String), + "moderation": Schema.optionalKey(Schema.Literals(["auto", "low"])), + "output_compression": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "output_format": Schema.optionalKey(Schema.Literals(["png", "webp", "jpeg"])), + "partial_images": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "quality": Schema.optionalKey(Schema.Literals(["low", "medium", "high", "auto"])), + "size": Schema.optionalKey(Schema.String), + "type": Schema.Literal("image_generation") +}).annotate({ "description": "Image generation tool configuration", "identifier": "ImageGenerationServerTool" }) +export type ImageGenerationServerToolConfig = { readonly "model"?: string } +export const ImageGenerationServerToolConfig = Schema.Struct({ + "model": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Which image generation model to use (e.g. \"openai/gpt-5-image\"). Defaults to \"openai/gpt-5-image\"." + }) + ) +}).annotate({ + "description": + "Configuration for the openrouter:image_generation server tool. Accepts all image_config params (aspect_ratio, quality, size, background, output_format, output_compression, moderation, etc.) plus a model field.", + "identifier": "ImageGenerationServerToolConfig" +}) +export type ImageGenerationStatus = "in_progress" | "completed" | "generating" | "failed" +export const ImageGenerationStatus = Schema.Literals(["in_progress", "completed", "generating", "failed"]).annotate({ + "identifier": "ImageGenerationStatus" +}) +export type Union_6 = { + readonly "audio_tokens"?: number | null + readonly "image_tokens"?: number | null + readonly "reasoning_tokens"?: number | null + readonly [x: string]: Schema.Json +} | null +export const Union_6 = Schema.Union([ + Schema.StructWithRest( Schema.Struct({ - "type": Schema.Literal("tool_use"), - "id": Schema.String, - "name": Schema.String, - "input": Schema.optionalKey(Schema.Unknown) + "audio_tokens": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]) + .annotate({ "description": "Tokens generated by the model for audio output." }) + ), + "image_tokens": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]) + .annotate({ "description": "Tokens generated by the model for image output." }) + ), + "reasoning_tokens": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]) + .annotate({ "description": "Tokens generated by the model for reasoning." }) + ) }), - Schema.Struct({ "type": Schema.Literal("thinking"), "thinking": Schema.String, "signature": Schema.String }), - Schema.Struct({ "type": Schema.Literal("redacted_thinking"), "data": Schema.String }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] + ), + Schema.Null +]) +export type Union_8 = { + readonly "audio_tokens"?: number | null + readonly "cache_write_tokens"?: number | null + readonly "cached_tokens"?: number | null + readonly "file_tokens"?: number | null + readonly "video_tokens"?: number | null + readonly [x: string]: Schema.Json +} | null +export const Union_8 = Schema.Union([ + Schema.StructWithRest( Schema.Struct({ - "type": Schema.Literal("server_tool_use"), - "id": Schema.String, - "name": Schema.Literal("web_search"), - "input": Schema.optionalKey(Schema.Unknown) + "audio_tokens": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]) + .annotate({ "description": "Tokens used for input audio." }) + ), + "cache_write_tokens": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]) + .annotate({ + "description": + "Tokens written to cache. Only returned for models with explicit caching and cache write pricing." + }) + ), + "cached_tokens": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]) + .annotate({ "description": "Tokens cached by the endpoint." }) + ), + "file_tokens": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]) + .annotate({ "description": "Tokens used for input files/documents." }) + ), + "video_tokens": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]) + .annotate({ "description": "Tokens used for input video." }) + ) }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] + ), + Schema.Null +]).annotate({ "description": "Breakdown of tokens used in the prompt." }) +export type Union_9 = { + readonly "tool_calls_executed"?: number | null + readonly "tool_calls_requested"?: number | null + readonly "web_search_requests"?: number | null + readonly [x: string]: Schema.Json +} | null +export const Union_9 = Schema.Union([ + Schema.StructWithRest( Schema.Struct({ - "type": Schema.Literal("web_search_tool_result"), - "tool_use_id": Schema.String, - "content": Schema.Union([ - Schema.Array( - Schema.Struct({ - "type": Schema.Literal("web_search_result"), - "encrypted_content": Schema.String, - "page_age": Schema.String, - "title": Schema.String, - "url": Schema.String + "tool_calls_executed": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]) + .annotate({ "description": "Number of OpenRouter server tool calls that executed and produced a result." }) + ), + "tool_calls_requested": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]) + .annotate({ + "description": + "Total number of OpenRouter server-orchestrated tool calls the model requested, across all tool types. Provider-native tools (e.g. native web search) are not counted here." }) - ), - Schema.Struct({ - "type": Schema.Literal("web_search_tool_result_error"), - "error_code": Schema.Literals([ - "invalid_tool_input", - "unavailable", - "max_uses_exceeded", - "too_many_requests", - "query_too_long" - ]) - }) - ]) - }) - ], { mode: "oneOf" })), - "model": Schema.String, - "stop_reason": Schema.Literals(["end_turn", "max_tokens", "stop_sequence", "tool_use", "pause_turn", "refusal"]), - "stop_sequence": Schema.String, - "usage": Schema.Struct({ - "input_tokens": Schema.Number.check(Schema.isFinite()), - "output_tokens": Schema.Number.check(Schema.isFinite()), - "cache_creation_input_tokens": Schema.Number.check(Schema.isFinite()), - "cache_read_input_tokens": Schema.Number.check(Schema.isFinite()), - "cache_creation": Schema.Struct({ - "ephemeral_5m_input_tokens": Schema.Number.check(Schema.isFinite()), - "ephemeral_1h_input_tokens": Schema.Number.check(Schema.isFinite()) + ), + "web_search_requests": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]) + .annotate({ + "description": + "Number of web searches performed by server-side tools. For server-orchestrated tool calls a web search is also counted in tool_calls_requested; provider-native web search may report web_search_requests only. Do not sum the two." + }) + ) }), - "inference_geo": Schema.String, - "server_tool_use": Schema.Struct({ "web_search_requests": Schema.Number.check(Schema.isFinite()) }), - "service_tier": Schema.Literals(["standard", "priority", "batch"]) - }) -}).annotate({ "description": "Non-streaming response from the Anthropic Messages API with OpenRouter extensions" }) -export type AnthropicMessagesStreamEvent = - | { - readonly "type": "message_start" - readonly "message": { - readonly "id": string - readonly "type": "message" - readonly "role": "assistant" - readonly "content": ReadonlyArray< - | { - readonly "type": "text" - readonly "text": string - readonly "citations": ReadonlyArray< - { - readonly "type": "char_location" - readonly "cited_text": string - readonly "document_index": number - readonly "document_title": string - readonly "start_char_index": number - readonly "end_char_index": number - readonly "file_id": string - } | { - readonly "type": "page_location" - readonly "cited_text": string - readonly "document_index": number - readonly "document_title": string - readonly "start_page_number": number - readonly "end_page_number": number - readonly "file_id": string - } | { - readonly "type": "content_block_location" - readonly "cited_text": string - readonly "document_index": number - readonly "document_title": string - readonly "start_block_index": number - readonly "end_block_index": number - readonly "file_id": string - } | { - readonly "type": "web_search_result_location" - readonly "cited_text": string - readonly "encrypted_index": string - readonly "title": string - readonly "url": string - } | { - readonly "type": "search_result_location" - readonly "cited_text": string - readonly "search_result_index": number - readonly "source": string - readonly "title": string - readonly "start_block_index": number - readonly "end_block_index": number - } - > - } - | { readonly "type": "tool_use"; readonly "id": string; readonly "name": string; readonly "input"?: unknown } - | { readonly "type": "thinking"; readonly "thinking": string; readonly "signature": string } - | { readonly "type": "redacted_thinking"; readonly "data": string } - | { - readonly "type": "server_tool_use" - readonly "id": string - readonly "name": "web_search" - readonly "input"?: unknown - } - | { - readonly "type": "web_search_tool_result" - readonly "tool_use_id": string - readonly "content": - | ReadonlyArray< - { - readonly "type": "web_search_result" - readonly "encrypted_content": string - readonly "page_age": string - readonly "title": string - readonly "url": string - } - > - | { - readonly "type": "web_search_tool_result_error" - readonly "error_code": - | "invalid_tool_input" - | "unavailable" - | "max_uses_exceeded" - | "too_many_requests" - | "query_too_long" - } - } - > - readonly "model": string - readonly "stop_reason": unknown - readonly "stop_sequence": unknown - readonly "usage": { - readonly "input_tokens": number - readonly "output_tokens": number - readonly "cache_creation_input_tokens": number - readonly "cache_read_input_tokens": number - readonly "cache_creation": { - readonly "ephemeral_5m_input_tokens": number - readonly "ephemeral_1h_input_tokens": number - } - readonly "inference_geo": string - readonly "server_tool_use": { readonly "web_search_requests": number } - readonly "service_tier": "standard" | "priority" | "batch" - } - } - } - | { - readonly "type": "message_delta" - readonly "delta": { - readonly "stop_reason": "end_turn" | "max_tokens" | "stop_sequence" | "tool_use" | "pause_turn" | "refusal" - readonly "stop_sequence": string - } - readonly "usage": { - readonly "input_tokens": number - readonly "output_tokens": number - readonly "cache_creation_input_tokens": number - readonly "cache_read_input_tokens": number - readonly "server_tool_use": { readonly "web_search_requests": number } - } - } - | { readonly "type": "message_stop" } - | { - readonly "type": "content_block_start" - readonly "index": number - readonly "content_block": - | { - readonly "type": "text" - readonly "text": string - readonly "citations": ReadonlyArray< - { - readonly "type": "char_location" - readonly "cited_text": string - readonly "document_index": number - readonly "document_title": string - readonly "start_char_index": number - readonly "end_char_index": number - readonly "file_id": string - } | { - readonly "type": "page_location" - readonly "cited_text": string - readonly "document_index": number - readonly "document_title": string - readonly "start_page_number": number - readonly "end_page_number": number - readonly "file_id": string - } | { - readonly "type": "content_block_location" - readonly "cited_text": string - readonly "document_index": number - readonly "document_title": string - readonly "start_block_index": number - readonly "end_block_index": number - readonly "file_id": string - } | { - readonly "type": "web_search_result_location" - readonly "cited_text": string - readonly "encrypted_index": string - readonly "title": string - readonly "url": string - } | { - readonly "type": "search_result_location" - readonly "cited_text": string - readonly "search_result_index": number - readonly "source": string - readonly "title": string - readonly "start_block_index": number - readonly "end_block_index": number - } - > - } - | { readonly "type": "tool_use"; readonly "id": string; readonly "name": string; readonly "input"?: unknown } - | { readonly "type": "thinking"; readonly "thinking": string; readonly "signature": string } - | { readonly "type": "redacted_thinking"; readonly "data": string } - | { - readonly "type": "server_tool_use" - readonly "id": string - readonly "name": "web_search" - readonly "input"?: unknown - } - | { - readonly "type": "web_search_tool_result" - readonly "tool_use_id": string - readonly "content": - | ReadonlyArray< - { - readonly "type": "web_search_result" - readonly "encrypted_content": string - readonly "page_age": string - readonly "title": string - readonly "url": string - } - > - | { - readonly "type": "web_search_tool_result_error" - readonly "error_code": - | "invalid_tool_input" - | "unavailable" - | "max_uses_exceeded" - | "too_many_requests" - | "query_too_long" - } - } - } - | { - readonly "type": "content_block_delta" - readonly "index": number - readonly "delta": - | { readonly "type": "text_delta"; readonly "text": string } - | { readonly "type": "input_json_delta"; readonly "partial_json": string } - | { readonly "type": "thinking_delta"; readonly "thinking": string } - | { readonly "type": "signature_delta"; readonly "signature": string } - | { - readonly "type": "citations_delta" - readonly "citation": { - readonly "type": "char_location" - readonly "cited_text": string - readonly "document_index": number - readonly "document_title": string - readonly "start_char_index": number - readonly "end_char_index": number - readonly "file_id": string - } | { - readonly "type": "page_location" - readonly "cited_text": string - readonly "document_index": number - readonly "document_title": string - readonly "start_page_number": number - readonly "end_page_number": number - readonly "file_id": string - } | { - readonly "type": "content_block_location" - readonly "cited_text": string - readonly "document_index": number - readonly "document_title": string - readonly "start_block_index": number - readonly "end_block_index": number - readonly "file_id": string - } | { - readonly "type": "web_search_result_location" - readonly "cited_text": string - readonly "encrypted_index": string - readonly "title": string - readonly "url": string - } | { - readonly "type": "search_result_location" - readonly "cited_text": string - readonly "search_result_index": number - readonly "source": string - readonly "title": string - readonly "start_block_index": number - readonly "end_block_index": number - } - } + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] + ), + Schema.Null +]).annotate({ "description": "Usage for server-side tool execution (e.g., web search)" }) +export type ImageGenPartialImageEvent = { + readonly "b64_json": string + readonly "partial_image_index": number + readonly "type": "image_generation.partial_image" +} +export const ImageGenPartialImageEvent = Schema.Struct({ + "b64_json": Schema.String.annotate({ "description": "Base64-encoded partial image data" }), + "partial_image_index": Schema.Number.annotate({ + "description": "0-based index indicating which partial image this is in the sequence" + }).check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("image_generation.partial_image").annotate({ "description": "The event type" }) +}).annotate({ + "description": "Emitted when a partial image becomes available during streaming generation", + "identifier": "ImageGenPartialImageEvent" +}) +export type ImageGenStreamErrorEvent = { + readonly "error": { + readonly "code"?: string | null + readonly "message": string + readonly "param"?: string | null + readonly "type"?: string | null } - | { readonly "type": "content_block_stop"; readonly "index": number } - | { readonly "type": "ping" } - | { readonly "type": "error"; readonly "error": { readonly "type": string; readonly "message": string } } -export const AnthropicMessagesStreamEvent = Schema.Union([ - Schema.Struct({ - "type": Schema.Literal("message_start"), - "message": Schema.Struct({ - "id": Schema.String, - "type": Schema.Literal("message"), - "role": Schema.Literal("assistant"), - "content": Schema.Array(Schema.Union([ - Schema.Struct({ - "type": Schema.Literal("text"), - "text": Schema.String, - "citations": Schema.Array(Schema.Union([ - Schema.Struct({ - "type": Schema.Literal("char_location"), - "cited_text": Schema.String, - "document_index": Schema.Number.check(Schema.isFinite()), - "document_title": Schema.String, - "start_char_index": Schema.Number.check(Schema.isFinite()), - "end_char_index": Schema.Number.check(Schema.isFinite()), - "file_id": Schema.String - }), - Schema.Struct({ - "type": Schema.Literal("page_location"), - "cited_text": Schema.String, - "document_index": Schema.Number.check(Schema.isFinite()), - "document_title": Schema.String, - "start_page_number": Schema.Number.check(Schema.isFinite()), - "end_page_number": Schema.Number.check(Schema.isFinite()), - "file_id": Schema.String - }), - Schema.Struct({ - "type": Schema.Literal("content_block_location"), - "cited_text": Schema.String, - "document_index": Schema.Number.check(Schema.isFinite()), - "document_title": Schema.String, - "start_block_index": Schema.Number.check(Schema.isFinite()), - "end_block_index": Schema.Number.check(Schema.isFinite()), - "file_id": Schema.String - }), - Schema.Struct({ - "type": Schema.Literal("web_search_result_location"), - "cited_text": Schema.String, - "encrypted_index": Schema.String, - "title": Schema.String, - "url": Schema.String - }), - Schema.Struct({ - "type": Schema.Literal("search_result_location"), - "cited_text": Schema.String, - "search_result_index": Schema.Number.check(Schema.isFinite()), - "source": Schema.String, - "title": Schema.String, - "start_block_index": Schema.Number.check(Schema.isFinite()), - "end_block_index": Schema.Number.check(Schema.isFinite()) - }) - ], { mode: "oneOf" })) - }), - Schema.Struct({ - "type": Schema.Literal("tool_use"), - "id": Schema.String, - "name": Schema.String, - "input": Schema.optionalKey(Schema.Unknown) - }), - Schema.Struct({ "type": Schema.Literal("thinking"), "thinking": Schema.String, "signature": Schema.String }), - Schema.Struct({ "type": Schema.Literal("redacted_thinking"), "data": Schema.String }), - Schema.Struct({ - "type": Schema.Literal("server_tool_use"), - "id": Schema.String, - "name": Schema.Literal("web_search"), - "input": Schema.optionalKey(Schema.Unknown) - }), - Schema.Struct({ - "type": Schema.Literal("web_search_tool_result"), - "tool_use_id": Schema.String, - "content": Schema.Union([ - Schema.Array( - Schema.Struct({ - "type": Schema.Literal("web_search_result"), - "encrypted_content": Schema.String, - "page_age": Schema.String, - "title": Schema.String, - "url": Schema.String - }) - ), - Schema.Struct({ - "type": Schema.Literal("web_search_tool_result_error"), - "error_code": Schema.Literals([ - "invalid_tool_input", - "unavailable", - "max_uses_exceeded", - "too_many_requests", - "query_too_long" - ]) - }) - ]) - }) - ], { mode: "oneOf" })), - "model": Schema.String, - "stop_reason": Schema.Unknown, - "stop_sequence": Schema.Unknown, - "usage": Schema.Struct({ - "input_tokens": Schema.Number.check(Schema.isFinite()), - "output_tokens": Schema.Number.check(Schema.isFinite()), - "cache_creation_input_tokens": Schema.Number.check(Schema.isFinite()), - "cache_read_input_tokens": Schema.Number.check(Schema.isFinite()), - "cache_creation": Schema.Struct({ - "ephemeral_5m_input_tokens": Schema.Number.check(Schema.isFinite()), - "ephemeral_1h_input_tokens": Schema.Number.check(Schema.isFinite()) - }), - "inference_geo": Schema.String, - "server_tool_use": Schema.Struct({ "web_search_requests": Schema.Number.check(Schema.isFinite()) }), - "service_tier": Schema.Literals(["standard", "priority", "batch"]) + readonly "type": "error" +} +export const ImageGenStreamErrorEvent = Schema.Struct({ + "error": Schema.Struct({ + "code": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ "description": "Provider error code, when supplied" }) + ), + "message": Schema.String.annotate({ "description": "Provider error message" }), + "param": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "Request parameter associated with the error, when supplied" }) - }) + ), + "type": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ "description": "Provider error type, when supplied" }) + ) + }).annotate({ "description": "Provider error details" }), + "type": Schema.Literal("error").annotate({ "description": "The event type" }) +}).annotate({ + "description": "Emitted when streaming generation fails after the SSE response starts", + "identifier": "ImageGenStreamErrorEvent" +}) +export type ImageGenTextChunkEvent = { + readonly "phase": "content" | "reasoning" | "draft" + readonly "text": string + readonly "type": "image_generation.text_chunk" +} +export const ImageGenTextChunkEvent = Schema.Struct({ + "phase": Schema.Literals(["content", "reasoning", "draft"]).annotate({ + "description": + "The generation phase this chunk belongs to. `content` is the renderable output; `reasoning` and `draft` are intermediate provider phases." }), - Schema.Struct({ - "type": Schema.Literal("message_delta"), - "delta": Schema.Struct({ - "stop_reason": Schema.Literals(["end_turn", "max_tokens", "stop_sequence", "tool_use", "pause_turn", "refusal"]), - "stop_sequence": Schema.String - }), - "usage": Schema.Struct({ - "input_tokens": Schema.Number.check(Schema.isFinite()), - "output_tokens": Schema.Number.check(Schema.isFinite()), - "cache_creation_input_tokens": Schema.Number.check(Schema.isFinite()), - "cache_read_input_tokens": Schema.Number.check(Schema.isFinite()), - "server_tool_use": Schema.Struct({ "web_search_requests": Schema.Number.check(Schema.isFinite()) }) - }) + "text": Schema.String.annotate({ + "description": "A text fragment of the image being generated (e.g. partial SVG markup)" }), - Schema.Struct({ "type": Schema.Literal("message_stop") }), - Schema.Struct({ - "type": Schema.Literal("content_block_start"), - "index": Schema.Number.check(Schema.isFinite()), - "content_block": Schema.Union([ - Schema.Struct({ - "type": Schema.Literal("text"), - "text": Schema.String, - "citations": Schema.Array(Schema.Union([ - Schema.Struct({ - "type": Schema.Literal("char_location"), - "cited_text": Schema.String, - "document_index": Schema.Number.check(Schema.isFinite()), - "document_title": Schema.String, - "start_char_index": Schema.Number.check(Schema.isFinite()), - "end_char_index": Schema.Number.check(Schema.isFinite()), - "file_id": Schema.String - }), - Schema.Struct({ - "type": Schema.Literal("page_location"), - "cited_text": Schema.String, - "document_index": Schema.Number.check(Schema.isFinite()), - "document_title": Schema.String, - "start_page_number": Schema.Number.check(Schema.isFinite()), - "end_page_number": Schema.Number.check(Schema.isFinite()), - "file_id": Schema.String - }), - Schema.Struct({ - "type": Schema.Literal("content_block_location"), - "cited_text": Schema.String, - "document_index": Schema.Number.check(Schema.isFinite()), - "document_title": Schema.String, - "start_block_index": Schema.Number.check(Schema.isFinite()), - "end_block_index": Schema.Number.check(Schema.isFinite()), - "file_id": Schema.String - }), - Schema.Struct({ - "type": Schema.Literal("web_search_result_location"), - "cited_text": Schema.String, - "encrypted_index": Schema.String, - "title": Schema.String, - "url": Schema.String - }), - Schema.Struct({ - "type": Schema.Literal("search_result_location"), - "cited_text": Schema.String, - "search_result_index": Schema.Number.check(Schema.isFinite()), - "source": Schema.String, - "title": Schema.String, - "start_block_index": Schema.Number.check(Schema.isFinite()), - "end_block_index": Schema.Number.check(Schema.isFinite()) - }) - ], { mode: "oneOf" })) - }), + "type": Schema.Literal("image_generation.text_chunk").annotate({ "description": "The event type" }) +}).annotate({ + "description": + "Emitted when a text chunk becomes available during streaming generation of text-based formats (e.g. SVG)", + "identifier": "ImageGenTextChunkEvent" +}) +export type ImageInputModality = "text" | "image" | "file" | "audio" | "video" +export const ImageInputModality = Schema.Literals(["text", "image", "file", "audio", "video"]).annotate({ + "identifier": "ImageInputModality" +}) +export type ImageOutputModality = + | "text" + | "image" + | "embeddings" + | "audio" + | "video" + | "rerank" + | "speech" + | "transcription" +export const ImageOutputModality = Schema.Literals([ + "text", + "image", + "embeddings", + "audio", + "video", + "rerank", + "speech", + "transcription" +]).annotate({ "identifier": "ImageOutputModality" }) +export type ImagePricingEntry = { + readonly "billable": "output_image" | "input_image" | "input_font" | "input_reference" | "input_text" + readonly "cost_usd": number + readonly "unit": "image" | "megapixel" | "token" + readonly "variant"?: string +} +export const ImagePricingEntry = Schema.Struct({ + "billable": Schema.Literals(["output_image", "input_image", "input_font", "input_reference", "input_text"]), + "cost_usd": Schema.Number.annotate({ "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ), + "unit": Schema.Literals(["image", "megapixel", "token"]), + "variant": Schema.optionalKey(Schema.String) +}).annotate({ "description": "One billable pricing line for an image provider.", "identifier": "ImagePricingEntry" }) +export type IncompleteDetails = { + readonly "reason"?: "max_output_tokens" | "content_filter" + readonly [x: string]: Schema.Json +} | null +export const IncompleteDetails = Schema.Union([ + Schema.StructWithRest( + Schema.Struct({ "reason": Schema.optionalKey(Schema.Literals(["max_output_tokens", "content_filter"])) }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] + ), + Schema.Null +]).annotate({ "identifier": "IncompleteDetails" }) +export type InputAudio = { + readonly "input_audio": { readonly "data": string; readonly "format": "mp3" | "wav" } + readonly "type": "input_audio" +} +export const InputAudio = Schema.Struct({ + "input_audio": Schema.Struct({ "data": Schema.String, "format": Schema.Literals(["mp3", "wav"]) }), + "type": Schema.Literal("input_audio") +}).annotate({ "description": "Audio input content item", "identifier": "InputAudio" }) +export type InputFile = { + readonly "file_data"?: string + readonly "file_id"?: string | null + readonly "file_url"?: string + readonly "filename"?: string + readonly "type": "input_file" +} +export const InputFile = Schema.Struct({ + "file_data": Schema.optionalKey(Schema.String), + "file_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "file_url": Schema.optionalKey(Schema.String), + "filename": Schema.optionalKey(Schema.String), + "type": Schema.Literal("input_file") +}).annotate({ "description": "File input content item", "identifier": "InputFile" }) +export type InputImage = { + readonly "detail": "auto" | "high" | "low" | "original" + readonly "image_url"?: string | null + readonly "type": "input_image" +} +export const InputImage = Schema.Struct({ + "detail": Schema.Literals(["auto", "high", "low", "original"]), + "image_url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "type": Schema.Literal("input_image") +}).annotate({ "description": "Image input content item", "identifier": "InputImage" }) +export type InputModality = "text" | "image" | "file" | "audio" | "video" +export const InputModality = Schema.Literals(["text", "image", "file", "audio", "video"]).annotate({ + "identifier": "InputModality" +}) +export type InputVideo = { readonly "type": "input_video"; readonly "video_url": string } +export const InputVideo = Schema.Struct({ + "type": Schema.Literal("input_video"), + "video_url": Schema.String.annotate({ + "description": "A base64 data URL or remote URL that resolves to a video file" + }) +}).annotate({ "description": "Video input content item", "identifier": "InputVideo" }) +export type InstructType = + | "none" + | "airoboros" + | "alpaca" + | "alpaca-modif" + | "chatml" + | "claude" + | "code-llama" + | "gemma" + | "llama2" + | "llama3" + | "mistral" + | "nemotron" + | "neural" + | "openchat" + | "phi3" + | "rwkv" + | "vicuna" + | "zephyr" + | "deepseek-r1" + | "deepseek-v3.1" + | "qwq" + | "qwen3" + | null +export const InstructType = Schema.Union([ + Schema.Literal("none"), + Schema.Literal("airoboros"), + Schema.Literal("alpaca"), + Schema.Literal("alpaca-modif"), + Schema.Literal("chatml"), + Schema.Literal("claude"), + Schema.Literal("code-llama"), + Schema.Literal("gemma"), + Schema.Literal("llama2"), + Schema.Literal("llama3"), + Schema.Literal("mistral"), + Schema.Literal("nemotron"), + Schema.Literal("neural"), + Schema.Literal("openchat"), + Schema.Literal("phi3"), + Schema.Literal("rwkv"), + Schema.Literal("vicuna"), + Schema.Literal("zephyr"), + Schema.Literal("deepseek-r1"), + Schema.Literal("deepseek-v3.1"), + Schema.Literal("qwq"), + Schema.Literal("qwen3"), + Schema.Null +]).annotate({ "description": "Instruction format type", "identifier": "InstructType" }) +export type InternalServerResponseErrorData = { + readonly "code": number + readonly "message": string + readonly "metadata"?: { readonly [x: string]: Schema.Json } | null +} +export const InternalServerResponseErrorData = Schema.Struct({ + "code": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "message": Schema.String, + "metadata": Schema.optionalKey( + Schema.Union([Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })), Schema.Null]) + ) +}).annotate({ "description": "Error data for InternalServerResponse", "identifier": "InternalServerResponseErrorData" }) +export type ItemReferenceItem = { readonly "id": string; readonly "type": "item_reference" } +export const ItemReferenceItem = Schema.Struct({ "id": Schema.String, "type": Schema.Literal("item_reference") }) + .annotate({ "description": "A reference to a previous response item by ID", "identifier": "ItemReferenceItem" }) +export type KeyAssignment = { + readonly "assigned_by": string | null + readonly "created_at": string + readonly "guardrail_id": string + readonly "id": string + readonly "key_hash": string + readonly "key_label": string + readonly "key_name": string +} +export const KeyAssignment = Schema.Struct({ + "assigned_by": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "User ID of who made the assignment" + }), + "created_at": Schema.String.annotate({ "description": "ISO 8601 timestamp of when the assignment was created" }), + "guardrail_id": Schema.String.annotate({ "description": "ID of the guardrail", "format": "uuid" }), + "id": Schema.String.annotate({ "description": "Unique identifier for the assignment", "format": "uuid" }), + "key_hash": Schema.String.annotate({ "description": "Hash of the assigned API key" }), + "key_label": Schema.String.annotate({ "description": "Label of the API key" }), + "key_name": Schema.String.annotate({ "description": "Name of the API key" }) +}).annotate({ "identifier": "KeyAssignment" }) +export type Legacy_ChatContentVideoInput = { readonly "url": string } +export const Legacy_ChatContentVideoInput = Schema.Struct({ + "url": Schema.String.annotate({ "description": "URL of the video (data: URLs supported)" }) +}).annotate({ "description": "Video input object", "identifier": "Legacy_ChatContentVideoInput" }) +export type McpApprovalRequestItem = { + readonly "arguments": string + readonly "id": string + readonly "name": string + readonly "server_label": string + readonly "type": "mcp_approval_request" +} +export const McpApprovalRequestItem = Schema.Struct({ + "arguments": Schema.String, + "id": Schema.String, + "name": Schema.String, + "server_label": Schema.String, + "type": Schema.Literal("mcp_approval_request") +}).annotate({ "description": "Request for approval to execute an MCP tool", "identifier": "McpApprovalRequestItem" }) +export type McpApprovalResponseItem = { + readonly "approval_request_id": string + readonly "approve": boolean + readonly "id"?: string | null + readonly "reason"?: string | null + readonly "type": "mcp_approval_response" +} +export const McpApprovalResponseItem = Schema.Struct({ + "approval_request_id": Schema.String, + "approve": Schema.Boolean, + "id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "reason": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "type": Schema.Literal("mcp_approval_response") +}).annotate({ "description": "User response to an MCP tool approval request", "identifier": "McpApprovalResponseItem" }) +export type McpCallItem = { + readonly "arguments": string + readonly "error"?: string | null + readonly "id": string + readonly "name": string + readonly "output"?: string | null + readonly "server_label": string + readonly "type": "mcp_call" +} +export const McpCallItem = Schema.Struct({ + "arguments": Schema.String, + "error": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "id": Schema.String, + "name": Schema.String, + "output": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "server_label": Schema.String, + "type": Schema.Literal("mcp_call") +}).annotate({ "description": "An MCP tool call with its output or error", "identifier": "McpCallItem" }) +export type McpListToolsItem = { + readonly "error"?: string | null + readonly "id": string + readonly "server_label": string + readonly "tools": ReadonlyArray< + { + readonly "annotations"?: Schema.Json + readonly "description"?: string | null + readonly "input_schema": {} + readonly "name": string + } + > + readonly "type": "mcp_list_tools" +} +export const McpListToolsItem = Schema.Struct({ + "error": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "id": Schema.String, + "server_label": Schema.String, + "tools": Schema.Array( + Schema.Struct({ + "annotations": Schema.optionalKey(Schema.Json.annotate({ "expected": "JSON value" })), + "description": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "input_schema": Schema.Struct({}), + "name": Schema.String + }) + ), + "type": Schema.Literal("mcp_list_tools") +}).annotate({ "description": "List of available MCP tools from a server", "identifier": "McpListToolsItem" }) +export type McpServerTool = { + readonly "allowed_tools"?: ReadonlyArray | { + readonly "read_only"?: boolean + readonly "tool_names"?: ReadonlyArray + } | null + readonly "authorization"?: string + readonly "connector_id"?: + | "connector_dropbox" + | "connector_gmail" + | "connector_googlecalendar" + | "connector_googledrive" + | "connector_microsoftteams" + | "connector_outlookcalendar" + | "connector_outlookemail" + | "connector_sharepoint" + readonly "headers"?: { readonly [x: string]: string } | null + readonly "require_approval"?: + | { + readonly "always"?: { readonly "tool_names"?: ReadonlyArray } + readonly "never"?: { readonly "tool_names"?: ReadonlyArray } + } + | "always" + | "never" + | null + readonly "server_description"?: string + readonly "server_label": string + readonly "server_url"?: string + readonly "type": "mcp" +} +export const McpServerTool = Schema.Struct({ + "allowed_tools": Schema.optionalKey( + Schema.Union([ + Schema.Array(Schema.String), Schema.Struct({ - "type": Schema.Literal("tool_use"), - "id": Schema.String, - "name": Schema.String, - "input": Schema.optionalKey(Schema.Unknown) + "read_only": Schema.optionalKey(Schema.Boolean), + "tool_names": Schema.optionalKey(Schema.Array(Schema.String)) }), - Schema.Struct({ "type": Schema.Literal("thinking"), "thinking": Schema.String, "signature": Schema.String }), - Schema.Struct({ "type": Schema.Literal("redacted_thinking"), "data": Schema.String }), + Schema.Null + ]) + ), + "authorization": Schema.optionalKey(Schema.String), + "connector_id": Schema.optionalKey( + Schema.Literals([ + "connector_dropbox", + "connector_gmail", + "connector_googlecalendar", + "connector_googledrive", + "connector_microsoftteams", + "connector_outlookcalendar", + "connector_outlookemail", + "connector_sharepoint" + ]) + ), + "headers": Schema.optionalKey(Schema.Union([Schema.Record(Schema.String, Schema.String), Schema.Null])), + "require_approval": Schema.optionalKey( + Schema.Union([ Schema.Struct({ - "type": Schema.Literal("server_tool_use"), - "id": Schema.String, - "name": Schema.Literal("web_search"), - "input": Schema.optionalKey(Schema.Unknown) + "always": Schema.optionalKey(Schema.Struct({ "tool_names": Schema.optionalKey(Schema.Array(Schema.String)) })), + "never": Schema.optionalKey(Schema.Struct({ "tool_names": Schema.optionalKey(Schema.Array(Schema.String)) })) }), - Schema.Struct({ - "type": Schema.Literal("web_search_tool_result"), - "tool_use_id": Schema.String, - "content": Schema.Union([ - Schema.Array( - Schema.Struct({ - "type": Schema.Literal("web_search_result"), - "encrypted_content": Schema.String, - "page_age": Schema.String, - "title": Schema.String, - "url": Schema.String - }) + Schema.Literal("always"), + Schema.Literal("never"), + Schema.Null + ]) + ), + "server_description": Schema.optionalKey(Schema.String), + "server_label": Schema.String, + "server_url": Schema.optionalKey(Schema.String), + "type": Schema.Literal("mcp") +}).annotate({ "description": "MCP (Model Context Protocol) tool configuration", "identifier": "McpServerTool" }) +export type MemberAssignment = { + readonly "assigned_by": string | null + readonly "created_at": string + readonly "guardrail_id": string + readonly "id": string + readonly "organization_id": string + readonly "user_id": string +} +export const MemberAssignment = Schema.Struct({ + "assigned_by": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "User ID of who made the assignment" + }), + "created_at": Schema.String.annotate({ "description": "ISO 8601 timestamp of when the assignment was created" }), + "guardrail_id": Schema.String.annotate({ "description": "ID of the guardrail", "format": "uuid" }), + "id": Schema.String.annotate({ "description": "Unique identifier for the assignment", "format": "uuid" }), + "organization_id": Schema.String.annotate({ "description": "Organization ID" }), + "user_id": Schema.String.annotate({ "description": "Clerk user ID of the assigned member" }) +}).annotate({ "identifier": "MemberAssignment" }) +export type MessagesAdvisorToolResultBlock = { + readonly "content": {} + readonly "tool_use_id": string + readonly "type": "advisor_tool_result" +} +export const MessagesAdvisorToolResultBlock = Schema.Struct({ + "content": Schema.Struct({}), + "tool_use_id": Schema.String, + "type": Schema.Literal("advisor_tool_result") +}).annotate({ + "description": + "Advisor tool result from a prior assistant turn, replayed back to the model on the next turn. Mirrors the block Anthropic returns in assistant content when the `advisor_20260301` tool runs.", + "identifier": "MessagesAdvisorToolResultBlock" +}) +export type MessagesContentBlockStopEvent = { readonly "index": number; readonly "type": "content_block_stop" } +export const MessagesContentBlockStopEvent = Schema.Struct({ + "index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("content_block_stop") +}).annotate({ + "description": "Event sent when a content block is complete", + "identifier": "MessagesContentBlockStopEvent" +}) +export type MessagesFallbackParam = { readonly "model": string } +export const MessagesFallbackParam = Schema.Struct({ "model": Schema.String }).annotate({ + "description": + "Fallback model to try when the primary model fails or refuses. Only the `model` field is supported; per-attempt overrides are rejected.", + "identifier": "MessagesFallbackParam" +}) +export type Union_10 = + | { readonly "schema": {}; readonly "type": "json_schema"; readonly [x: string]: Schema.Json } + | null +export const Union_10 = Schema.Union([ + Schema.StructWithRest(Schema.Struct({ "schema": Schema.Struct({}), "type": Schema.Literal("json_schema") }), [ + Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })) + ]), + Schema.Null +]).annotate({ + "description": + "A schema to specify Claude's output format in responses. See [structured outputs](https://platform.claude.com/docs/en/build-with-claude/structured-outputs)." +}) +export type Union_11 = { + readonly "remaining"?: number | null + readonly "total": number + readonly "type": "tokens" + readonly [x: string]: Schema.Json +} | null +export const Union_11 = Schema.Union([ + Schema.StructWithRest( + Schema.Struct({ + "remaining": Schema.optionalKey( + Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check( + Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" }) ), - Schema.Struct({ - "type": Schema.Literal("web_search_tool_result_error"), - "error_code": Schema.Literals([ - "invalid_tool_input", - "unavailable", - "max_uses_exceeded", - "too_many_requests", - "query_too_long" - ]) - }) + Schema.Null ]) - }) - ], { mode: "oneOf" }) - }), - Schema.Struct({ - "type": Schema.Literal("content_block_delta"), - "index": Schema.Number.check(Schema.isFinite()), - "delta": Schema.Union([ - Schema.Struct({ "type": Schema.Literal("text_delta"), "text": Schema.String }), - Schema.Struct({ "type": Schema.Literal("input_json_delta"), "partial_json": Schema.String }), - Schema.Struct({ "type": Schema.Literal("thinking_delta"), "thinking": Schema.String }), - Schema.Struct({ "type": Schema.Literal("signature_delta"), "signature": Schema.String }), - Schema.Struct({ - "type": Schema.Literal("citations_delta"), - "citation": Schema.Union([ - Schema.Struct({ - "type": Schema.Literal("char_location"), - "cited_text": Schema.String, - "document_index": Schema.Number.check(Schema.isFinite()), - "document_title": Schema.String, - "start_char_index": Schema.Number.check(Schema.isFinite()), - "end_char_index": Schema.Number.check(Schema.isFinite()), - "file_id": Schema.String - }), - Schema.Struct({ - "type": Schema.Literal("page_location"), - "cited_text": Schema.String, - "document_index": Schema.Number.check(Schema.isFinite()), - "document_title": Schema.String, - "start_page_number": Schema.Number.check(Schema.isFinite()), - "end_page_number": Schema.Number.check(Schema.isFinite()), - "file_id": Schema.String - }), - Schema.Struct({ - "type": Schema.Literal("content_block_location"), - "cited_text": Schema.String, - "document_index": Schema.Number.check(Schema.isFinite()), - "document_title": Schema.String, - "start_block_index": Schema.Number.check(Schema.isFinite()), - "end_block_index": Schema.Number.check(Schema.isFinite()), - "file_id": Schema.String - }), - Schema.Struct({ - "type": Schema.Literal("web_search_result_location"), - "cited_text": Schema.String, - "encrypted_index": Schema.String, - "title": Schema.String, - "url": Schema.String - }), - Schema.Struct({ - "type": Schema.Literal("search_result_location"), - "cited_text": Schema.String, - "search_result_index": Schema.Number.check(Schema.isFinite()), - "source": Schema.String, - "title": Schema.String, - "start_block_index": Schema.Number.check(Schema.isFinite()), - "end_block_index": Schema.Number.check(Schema.isFinite()) - }) - ], { mode: "oneOf" }) - }) - ], { mode: "oneOf" }) - }), - Schema.Struct({ "type": Schema.Literal("content_block_stop"), "index": Schema.Number.check(Schema.isFinite()) }), - Schema.Struct({ "type": Schema.Literal("ping") }), - Schema.Struct({ - "type": Schema.Literal("error"), - "error": Schema.Struct({ "type": Schema.String, "message": Schema.String }) - }) -], { mode: "oneOf" }).annotate({ "description": "Union of all possible streaming events" }) -export type OpenRouterAnthropicMessageParam = { - readonly "role": "user" | "assistant" - readonly "content": - | string - | ReadonlyArray< - | { - readonly "type": "text" - readonly "text": string - readonly "citations"?: ReadonlyArray< - { - readonly "type": "char_location" - readonly "cited_text": string - readonly "document_index": number - readonly "document_title": string - readonly "start_char_index": number - readonly "end_char_index": number - } | { - readonly "type": "page_location" - readonly "cited_text": string - readonly "document_index": number - readonly "document_title": string - readonly "start_page_number": number - readonly "end_page_number": number - } | { - readonly "type": "content_block_location" - readonly "cited_text": string - readonly "document_index": number - readonly "document_title": string - readonly "start_block_index": number - readonly "end_block_index": number - } | { - readonly "type": "web_search_result_location" - readonly "cited_text": string - readonly "encrypted_index": string - readonly "title": string - readonly "url": string - } | { - readonly "type": "search_result_location" - readonly "cited_text": string - readonly "search_result_index": number - readonly "source": string - readonly "title": string - readonly "start_block_index": number - readonly "end_block_index": number - } - > - readonly "cache_control"?: { readonly "type": "ephemeral"; readonly "ttl"?: "5m" | "1h" } - } - | { - readonly "type": "image" - readonly "source": { - readonly "type": "base64" - readonly "media_type": "image/jpeg" | "image/png" | "image/gif" | "image/webp" - readonly "data": string - } | { readonly "type": "url"; readonly "url": string } - readonly "cache_control"?: { readonly "type": "ephemeral"; readonly "ttl"?: "5m" | "1h" } - } - | { - readonly "type": "document" - readonly "source": - | { readonly "type": "base64"; readonly "media_type": "application/pdf"; readonly "data": string } - | { readonly "type": "text"; readonly "media_type": "text/plain"; readonly "data": string } - | { - readonly "type": "content" - readonly "content": - | string - | ReadonlyArray< - { - readonly "type": "text" - readonly "text": string - readonly "citations"?: ReadonlyArray< - { - readonly "type": "char_location" - readonly "cited_text": string - readonly "document_index": number - readonly "document_title": string - readonly "start_char_index": number - readonly "end_char_index": number - } | { - readonly "type": "page_location" - readonly "cited_text": string - readonly "document_index": number - readonly "document_title": string - readonly "start_page_number": number - readonly "end_page_number": number - } | { - readonly "type": "content_block_location" - readonly "cited_text": string - readonly "document_index": number - readonly "document_title": string - readonly "start_block_index": number - readonly "end_block_index": number - } | { - readonly "type": "web_search_result_location" - readonly "cited_text": string - readonly "encrypted_index": string - readonly "title": string - readonly "url": string - } | { - readonly "type": "search_result_location" - readonly "cited_text": string - readonly "search_result_index": number - readonly "source": string - readonly "title": string - readonly "start_block_index": number - readonly "end_block_index": number - } - > - readonly "cache_control"?: { readonly "type": "ephemeral"; readonly "ttl"?: "5m" | "1h" } - } | { - readonly "type": "image" - readonly "source": { - readonly "type": "base64" - readonly "media_type": "image/jpeg" | "image/png" | "image/gif" | "image/webp" - readonly "data": string - } | { readonly "type": "url"; readonly "url": string } - readonly "cache_control"?: { readonly "type": "ephemeral"; readonly "ttl"?: "5m" | "1h" } - } - > - } - | { readonly "type": "url"; readonly "url": string } - readonly "citations"?: { readonly "enabled"?: boolean } - readonly "context"?: string - readonly "title"?: string - readonly "cache_control"?: { readonly "type": "ephemeral"; readonly "ttl"?: "5m" | "1h" } - } - | { - readonly "type": "tool_use" - readonly "id": string - readonly "name": string - readonly "input"?: unknown - readonly "cache_control"?: { readonly "type": "ephemeral"; readonly "ttl"?: "5m" | "1h" } - } - | { - readonly "type": "tool_result" - readonly "tool_use_id": string - readonly "content"?: - | string - | ReadonlyArray< - { - readonly "type": "text" - readonly "text": string - readonly "citations"?: ReadonlyArray< - { - readonly "type": "char_location" - readonly "cited_text": string - readonly "document_index": number - readonly "document_title": string - readonly "start_char_index": number - readonly "end_char_index": number - } | { - readonly "type": "page_location" - readonly "cited_text": string - readonly "document_index": number - readonly "document_title": string - readonly "start_page_number": number - readonly "end_page_number": number - } | { - readonly "type": "content_block_location" - readonly "cited_text": string - readonly "document_index": number - readonly "document_title": string - readonly "start_block_index": number - readonly "end_block_index": number - } | { - readonly "type": "web_search_result_location" - readonly "cited_text": string - readonly "encrypted_index": string - readonly "title": string - readonly "url": string - } | { - readonly "type": "search_result_location" - readonly "cited_text": string - readonly "search_result_index": number - readonly "source": string - readonly "title": string - readonly "start_block_index": number - readonly "end_block_index": number - } - > - readonly "cache_control"?: { readonly "type": "ephemeral"; readonly "ttl"?: "5m" | "1h" } - } | { - readonly "type": "image" - readonly "source": { - readonly "type": "base64" - readonly "media_type": "image/jpeg" | "image/png" | "image/gif" | "image/webp" - readonly "data": string - } | { readonly "type": "url"; readonly "url": string } - readonly "cache_control"?: { readonly "type": "ephemeral"; readonly "ttl"?: "5m" | "1h" } - } - > - readonly "is_error"?: boolean - readonly "cache_control"?: { readonly "type": "ephemeral"; readonly "ttl"?: "5m" | "1h" } - } - | { readonly "type": "thinking"; readonly "thinking": string; readonly "signature": string } - | { readonly "type": "redacted_thinking"; readonly "data": string } - | { - readonly "type": "server_tool_use" - readonly "id": string - readonly "name": "web_search" - readonly "input"?: unknown - readonly "cache_control"?: { readonly "type": "ephemeral"; readonly "ttl"?: "5m" | "1h" } - } - | { - readonly "type": "web_search_tool_result" - readonly "tool_use_id": string - readonly "content": - | ReadonlyArray< - { - readonly "type": "web_search_result" - readonly "encrypted_content": string - readonly "title": string - readonly "url": string - readonly "page_age"?: string - } - > - | { - readonly "type": "web_search_tool_result_error" - readonly "error_code": - | "invalid_tool_input" - | "unavailable" - | "max_uses_exceeded" - | "too_many_requests" - | "query_too_long" - } - readonly "cache_control"?: { readonly "type": "ephemeral"; readonly "ttl"?: "5m" | "1h" } - } - | { - readonly "type": "search_result" - readonly "source": string - readonly "title": string - readonly "content": ReadonlyArray< - { - readonly "type": "text" - readonly "text": string - readonly "citations"?: ReadonlyArray< - { - readonly "type": "char_location" - readonly "cited_text": string - readonly "document_index": number - readonly "document_title": string - readonly "start_char_index": number - readonly "end_char_index": number - } | { - readonly "type": "page_location" - readonly "cited_text": string - readonly "document_index": number - readonly "document_title": string - readonly "start_page_number": number - readonly "end_page_number": number - } | { - readonly "type": "content_block_location" - readonly "cited_text": string - readonly "document_index": number - readonly "document_title": string - readonly "start_block_index": number - readonly "end_block_index": number - } | { - readonly "type": "web_search_result_location" - readonly "cited_text": string - readonly "encrypted_index": string - readonly "title": string - readonly "url": string - } | { - readonly "type": "search_result_location" - readonly "cited_text": string - readonly "search_result_index": number - readonly "source": string - readonly "title": string - readonly "start_block_index": number - readonly "end_block_index": number - } - > - readonly "cache_control"?: { readonly "type": "ephemeral"; readonly "ttl"?: "5m" | "1h" } - } - > - readonly "citations"?: { readonly "enabled"?: boolean } - readonly "cache_control"?: { readonly "type": "ephemeral"; readonly "ttl"?: "5m" | "1h" } - } - > -} -export const OpenRouterAnthropicMessageParam = Schema.Struct({ - "role": Schema.Literals(["user", "assistant"]), - "content": Schema.Union([ - Schema.String, - Schema.Array(Schema.Union([ - Schema.Struct({ - "type": Schema.Literal("text"), - "text": Schema.String, - "citations": Schema.optionalKey(Schema.Array(Schema.Union([ - Schema.Struct({ - "type": Schema.Literal("char_location"), - "cited_text": Schema.String, - "document_index": Schema.Number.check(Schema.isFinite()), - "document_title": Schema.String, - "start_char_index": Schema.Number.check(Schema.isFinite()), - "end_char_index": Schema.Number.check(Schema.isFinite()) - }), - Schema.Struct({ - "type": Schema.Literal("page_location"), - "cited_text": Schema.String, - "document_index": Schema.Number.check(Schema.isFinite()), - "document_title": Schema.String, - "start_page_number": Schema.Number.check(Schema.isFinite()), - "end_page_number": Schema.Number.check(Schema.isFinite()) - }), - Schema.Struct({ - "type": Schema.Literal("content_block_location"), - "cited_text": Schema.String, - "document_index": Schema.Number.check(Schema.isFinite()), - "document_title": Schema.String, - "start_block_index": Schema.Number.check(Schema.isFinite()), - "end_block_index": Schema.Number.check(Schema.isFinite()) - }), - Schema.Struct({ - "type": Schema.Literal("web_search_result_location"), - "cited_text": Schema.String, - "encrypted_index": Schema.String, - "title": Schema.String, - "url": Schema.String - }), - Schema.Struct({ - "type": Schema.Literal("search_result_location"), - "cited_text": Schema.String, - "search_result_index": Schema.Number.check(Schema.isFinite()), - "source": Schema.String, - "title": Schema.String, - "start_block_index": Schema.Number.check(Schema.isFinite()), - "end_block_index": Schema.Number.check(Schema.isFinite()) - }) - ], { mode: "oneOf" }))), - "cache_control": Schema.optionalKey( - Schema.Struct({ - "type": Schema.Literal("ephemeral"), - "ttl": Schema.optionalKey(Schema.Literals(["5m", "1h"])) - }) - ) - }), - Schema.Struct({ - "type": Schema.Literal("image"), - "source": Schema.Union([ - Schema.Struct({ - "type": Schema.Literal("base64"), - "media_type": Schema.Literals(["image/jpeg", "image/png", "image/gif", "image/webp"]), - "data": Schema.String - }), - Schema.Struct({ "type": Schema.Literal("url"), "url": Schema.String }) - ], { mode: "oneOf" }), - "cache_control": Schema.optionalKey( - Schema.Struct({ - "type": Schema.Literal("ephemeral"), - "ttl": Schema.optionalKey(Schema.Literals(["5m", "1h"])) - }) - ) - }), - Schema.Struct({ - "type": Schema.Literal("document"), - "source": Schema.Union([ - Schema.Struct({ - "type": Schema.Literal("base64"), - "media_type": Schema.Literal("application/pdf"), - "data": Schema.String - }), - Schema.Struct({ - "type": Schema.Literal("text"), - "media_type": Schema.Literal("text/plain"), - "data": Schema.String - }), - Schema.Struct({ - "type": Schema.Literal("content"), - "content": Schema.Union([ - Schema.String, - Schema.Array(Schema.Union([ - Schema.Struct({ - "type": Schema.Literal("text"), - "text": Schema.String, - "citations": Schema.optionalKey(Schema.Array(Schema.Union([ - Schema.Struct({ - "type": Schema.Literal("char_location"), - "cited_text": Schema.String, - "document_index": Schema.Number.check(Schema.isFinite()), - "document_title": Schema.String, - "start_char_index": Schema.Number.check(Schema.isFinite()), - "end_char_index": Schema.Number.check(Schema.isFinite()) - }), - Schema.Struct({ - "type": Schema.Literal("page_location"), - "cited_text": Schema.String, - "document_index": Schema.Number.check(Schema.isFinite()), - "document_title": Schema.String, - "start_page_number": Schema.Number.check(Schema.isFinite()), - "end_page_number": Schema.Number.check(Schema.isFinite()) - }), - Schema.Struct({ - "type": Schema.Literal("content_block_location"), - "cited_text": Schema.String, - "document_index": Schema.Number.check(Schema.isFinite()), - "document_title": Schema.String, - "start_block_index": Schema.Number.check(Schema.isFinite()), - "end_block_index": Schema.Number.check(Schema.isFinite()) - }), - Schema.Struct({ - "type": Schema.Literal("web_search_result_location"), - "cited_text": Schema.String, - "encrypted_index": Schema.String, - "title": Schema.String, - "url": Schema.String - }), - Schema.Struct({ - "type": Schema.Literal("search_result_location"), - "cited_text": Schema.String, - "search_result_index": Schema.Number.check(Schema.isFinite()), - "source": Schema.String, - "title": Schema.String, - "start_block_index": Schema.Number.check(Schema.isFinite()), - "end_block_index": Schema.Number.check(Schema.isFinite()) - }) - ], { mode: "oneOf" }))), - "cache_control": Schema.optionalKey( - Schema.Struct({ - "type": Schema.Literal("ephemeral"), - "ttl": Schema.optionalKey(Schema.Literals(["5m", "1h"])) - }) - ) - }), - Schema.Struct({ - "type": Schema.Literal("image"), - "source": Schema.Union([ - Schema.Struct({ - "type": Schema.Literal("base64"), - "media_type": Schema.Literals(["image/jpeg", "image/png", "image/gif", "image/webp"]), - "data": Schema.String - }), - Schema.Struct({ "type": Schema.Literal("url"), "url": Schema.String }) - ], { mode: "oneOf" }), - "cache_control": Schema.optionalKey( - Schema.Struct({ - "type": Schema.Literal("ephemeral"), - "ttl": Schema.optionalKey(Schema.Literals(["5m", "1h"])) - }) - ) - }) - ], { mode: "oneOf" })) - ]) - }), - Schema.Struct({ "type": Schema.Literal("url"), "url": Schema.String }) - ], { mode: "oneOf" }), - "citations": Schema.optionalKey(Schema.Struct({ "enabled": Schema.optionalKey(Schema.Boolean) })), - "context": Schema.optionalKey(Schema.String), - "title": Schema.optionalKey(Schema.String), - "cache_control": Schema.optionalKey( - Schema.Struct({ - "type": Schema.Literal("ephemeral"), - "ttl": Schema.optionalKey(Schema.Literals(["5m", "1h"])) - }) - ) - }), - Schema.Struct({ - "type": Schema.Literal("tool_use"), - "id": Schema.String, - "name": Schema.String, - "input": Schema.optionalKey(Schema.Unknown), - "cache_control": Schema.optionalKey( - Schema.Struct({ - "type": Schema.Literal("ephemeral"), - "ttl": Schema.optionalKey(Schema.Literals(["5m", "1h"])) - }) - ) - }), - Schema.Struct({ - "type": Schema.Literal("tool_result"), - "tool_use_id": Schema.String, - "content": Schema.optionalKey(Schema.Union([ - Schema.String, - Schema.Array(Schema.Union([ - Schema.Struct({ - "type": Schema.Literal("text"), - "text": Schema.String, - "citations": Schema.optionalKey(Schema.Array(Schema.Union([ - Schema.Struct({ - "type": Schema.Literal("char_location"), - "cited_text": Schema.String, - "document_index": Schema.Number.check(Schema.isFinite()), - "document_title": Schema.String, - "start_char_index": Schema.Number.check(Schema.isFinite()), - "end_char_index": Schema.Number.check(Schema.isFinite()) - }), - Schema.Struct({ - "type": Schema.Literal("page_location"), - "cited_text": Schema.String, - "document_index": Schema.Number.check(Schema.isFinite()), - "document_title": Schema.String, - "start_page_number": Schema.Number.check(Schema.isFinite()), - "end_page_number": Schema.Number.check(Schema.isFinite()) - }), - Schema.Struct({ - "type": Schema.Literal("content_block_location"), - "cited_text": Schema.String, - "document_index": Schema.Number.check(Schema.isFinite()), - "document_title": Schema.String, - "start_block_index": Schema.Number.check(Schema.isFinite()), - "end_block_index": Schema.Number.check(Schema.isFinite()) - }), - Schema.Struct({ - "type": Schema.Literal("web_search_result_location"), - "cited_text": Schema.String, - "encrypted_index": Schema.String, - "title": Schema.String, - "url": Schema.String - }), - Schema.Struct({ - "type": Schema.Literal("search_result_location"), - "cited_text": Schema.String, - "search_result_index": Schema.Number.check(Schema.isFinite()), - "source": Schema.String, - "title": Schema.String, - "start_block_index": Schema.Number.check(Schema.isFinite()), - "end_block_index": Schema.Number.check(Schema.isFinite()) - }) - ], { mode: "oneOf" }))), - "cache_control": Schema.optionalKey( - Schema.Struct({ - "type": Schema.Literal("ephemeral"), - "ttl": Schema.optionalKey(Schema.Literals(["5m", "1h"])) - }) - ) - }), - Schema.Struct({ - "type": Schema.Literal("image"), - "source": Schema.Union([ - Schema.Struct({ - "type": Schema.Literal("base64"), - "media_type": Schema.Literals(["image/jpeg", "image/png", "image/gif", "image/webp"]), - "data": Schema.String - }), - Schema.Struct({ "type": Schema.Literal("url"), "url": Schema.String }) - ], { mode: "oneOf" }), - "cache_control": Schema.optionalKey( - Schema.Struct({ - "type": Schema.Literal("ephemeral"), - "ttl": Schema.optionalKey(Schema.Literals(["5m", "1h"])) - }) - ) - }) - ])) - ])), - "is_error": Schema.optionalKey(Schema.Boolean), - "cache_control": Schema.optionalKey( - Schema.Struct({ - "type": Schema.Literal("ephemeral"), - "ttl": Schema.optionalKey(Schema.Literals(["5m", "1h"])) - }) - ) - }), - Schema.Struct({ "type": Schema.Literal("thinking"), "thinking": Schema.String, "signature": Schema.String }), - Schema.Struct({ "type": Schema.Literal("redacted_thinking"), "data": Schema.String }), - Schema.Struct({ - "type": Schema.Literal("server_tool_use"), - "id": Schema.String, - "name": Schema.Literal("web_search"), - "input": Schema.optionalKey(Schema.Unknown), - "cache_control": Schema.optionalKey( - Schema.Struct({ - "type": Schema.Literal("ephemeral"), - "ttl": Schema.optionalKey(Schema.Literals(["5m", "1h"])) - }) - ) - }), - Schema.Struct({ - "type": Schema.Literal("web_search_tool_result"), - "tool_use_id": Schema.String, - "content": Schema.Union([ - Schema.Array( - Schema.Struct({ - "type": Schema.Literal("web_search_result"), - "encrypted_content": Schema.String, - "title": Schema.String, - "url": Schema.String, - "page_age": Schema.optionalKey(Schema.String) - }) - ), - Schema.Struct({ - "type": Schema.Literal("web_search_tool_result_error"), - "error_code": Schema.Literals([ - "invalid_tool_input", - "unavailable", - "max_uses_exceeded", - "too_many_requests", - "query_too_long" - ]) - }) - ]), - "cache_control": Schema.optionalKey( - Schema.Struct({ - "type": Schema.Literal("ephemeral"), - "ttl": Schema.optionalKey(Schema.Literals(["5m", "1h"])) - }) - ) - }), - Schema.Struct({ - "type": Schema.Literal("search_result"), - "source": Schema.String, - "title": Schema.String, - "content": Schema.Array(Schema.Struct({ - "type": Schema.Literal("text"), - "text": Schema.String, - "citations": Schema.optionalKey(Schema.Array(Schema.Union([ - Schema.Struct({ - "type": Schema.Literal("char_location"), - "cited_text": Schema.String, - "document_index": Schema.Number.check(Schema.isFinite()), - "document_title": Schema.String, - "start_char_index": Schema.Number.check(Schema.isFinite()), - "end_char_index": Schema.Number.check(Schema.isFinite()) - }), - Schema.Struct({ - "type": Schema.Literal("page_location"), - "cited_text": Schema.String, - "document_index": Schema.Number.check(Schema.isFinite()), - "document_title": Schema.String, - "start_page_number": Schema.Number.check(Schema.isFinite()), - "end_page_number": Schema.Number.check(Schema.isFinite()) - }), - Schema.Struct({ - "type": Schema.Literal("content_block_location"), - "cited_text": Schema.String, - "document_index": Schema.Number.check(Schema.isFinite()), - "document_title": Schema.String, - "start_block_index": Schema.Number.check(Schema.isFinite()), - "end_block_index": Schema.Number.check(Schema.isFinite()) - }), - Schema.Struct({ - "type": Schema.Literal("web_search_result_location"), - "cited_text": Schema.String, - "encrypted_index": Schema.String, - "title": Schema.String, - "url": Schema.String - }), - Schema.Struct({ - "type": Schema.Literal("search_result_location"), - "cited_text": Schema.String, - "search_result_index": Schema.Number.check(Schema.isFinite()), - "source": Schema.String, - "title": Schema.String, - "start_block_index": Schema.Number.check(Schema.isFinite()), - "end_block_index": Schema.Number.check(Schema.isFinite()) - }) - ], { mode: "oneOf" }))), - "cache_control": Schema.optionalKey( - Schema.Struct({ - "type": Schema.Literal("ephemeral"), - "ttl": Schema.optionalKey(Schema.Literals(["5m", "1h"])) - }) - ) - })), - "citations": Schema.optionalKey(Schema.Struct({ "enabled": Schema.optionalKey(Schema.Boolean) })), - "cache_control": Schema.optionalKey( - Schema.Struct({ - "type": Schema.Literal("ephemeral"), - "ttl": Schema.optionalKey(Schema.Literals(["5m", "1h"])) - }) - ) - }) - ], { mode: "oneOf" })) - ]) -}).annotate({ "description": "Anthropic message with OpenRouter extensions" }) -export type AnthropicOutputConfig = { readonly "effort"?: "low" | "medium" | "high" | "max" } -export const AnthropicOutputConfig = Schema.Struct({ - "effort": Schema.optionalKey( - Schema.Literals(["low", "medium", "high", "max"]).annotate({ - "description": - "How much effort the model should put into its response. Higher effort levels may result in more thorough analysis but take longer. Valid values are `low`, `medium`, `high`, or `max`." - }) - ) -}).annotate({ + ), + "total": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check( + Schema.isGreaterThanOrEqualTo(20000).annotate({ "expected": "a value greater than or equal to 20000" }) + ), + "type": Schema.Literal("tokens") + }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] + ), + Schema.Null +]).annotate({ "description": - "Configuration for controlling output behavior. Currently supports the effort parameter for Claude Opus 4.5." + "Task budget for an agentic turn. The model sees a countdown of remaining tokens and uses it to prioritize work and wind down gracefully. Advisory — does not enforce a hard cap." }) -export type ActivityItem = { - readonly "date": string - readonly "model": string - readonly "model_permaslug": string - readonly "endpoint_id": string - readonly "provider_name": string - readonly "usage": number - readonly "byok_usage_inference": number - readonly "requests": number - readonly "prompt_tokens": number - readonly "completion_tokens": number - readonly "reasoning_tokens": number -} -export const ActivityItem = Schema.Struct({ - "date": Schema.String.annotate({ "description": "Date of the activity (YYYY-MM-DD format)" }), - "model": Schema.String.annotate({ "description": "Model slug (e.g., \"openai/gpt-4.1\")" }), - "model_permaslug": Schema.String.annotate({ "description": "Model permaslug (e.g., \"openai/gpt-4.1-2025-04-14\")" }), - "endpoint_id": Schema.String.annotate({ "description": "Unique identifier for the endpoint" }), - "provider_name": Schema.String.annotate({ "description": "Name of the provider serving this endpoint" }), - "usage": Schema.Number.annotate({ "description": "Total cost in USD (OpenRouter credits spent)" }).check( - Schema.isFinite() - ), - "byok_usage_inference": Schema.Number.annotate({ - "description": "BYOK inference cost in USD (external credits spent)" - }).check(Schema.isFinite()), - "requests": Schema.Number.annotate({ "description": "Number of requests made" }).check(Schema.isFinite()), - "prompt_tokens": Schema.Number.annotate({ "description": "Total prompt tokens used" }).check(Schema.isFinite()), - "completion_tokens": Schema.Number.annotate({ "description": "Total completion tokens generated" }).check( - Schema.isFinite() - ), - "reasoning_tokens": Schema.Number.annotate({ "description": "Total reasoning tokens used" }).check(Schema.isFinite()) +export type MessagesPingEvent = { readonly "type": "ping" } +export const MessagesPingEvent = Schema.Struct({ "type": Schema.Literal("ping") }).annotate({ + "description": "Keep-alive ping event", + "identifier": "MessagesPingEvent" +}) +export type MetadataLevel = "disabled" | "enabled" +export const MetadataLevel = Schema.Literals(["disabled", "enabled"]).annotate({ + "description": "Opt-in level for surfacing routing metadata on the response under `openrouter_metadata`.", + "identifier": "MetadataLevel" }) -export type ForbiddenResponseErrorData = { - readonly "code": number - readonly "message": string - readonly "metadata"?: {} -} -export const ForbiddenResponseErrorData = Schema.Struct({ - "code": Schema.Number.check(Schema.isInt()), - "message": Schema.String, - "metadata": Schema.optionalKey(Schema.Struct({})) -}).annotate({ "description": "Error data for ForbiddenResponse" }) -export type CreateChargeRequest = { - readonly "amount": number - readonly "sender": string - readonly "chain_id": 1 | 137 | 8453 -} -export const CreateChargeRequest = Schema.Struct({ - "amount": Schema.Number.check(Schema.isFinite()), - "sender": Schema.String, - "chain_id": Schema.Literals([1, 137, 8453]) -}).annotate({ "description": "Create a Coinbase charge for crypto payment" }) -export type PublicPricing = { - readonly "prompt": string - readonly "completion": string - readonly "request"?: string - readonly "image"?: string - readonly "image_token"?: string - readonly "image_output"?: string - readonly "audio"?: string - readonly "audio_output"?: string - readonly "input_audio_cache"?: string - readonly "web_search"?: string - readonly "internal_reasoning"?: string - readonly "input_cache_read"?: string - readonly "input_cache_write"?: string - readonly "discount"?: number -} -export const PublicPricing = Schema.Struct({ - "prompt": Schema.String.annotate({ "description": "A number or string value representing a large number" }), - "completion": Schema.String.annotate({ "description": "A number or string value representing a large number" }), - "request": Schema.optionalKey( - Schema.String.annotate({ "description": "A number or string value representing a large number" }) - ), - "image": Schema.optionalKey( - Schema.String.annotate({ "description": "A number or string value representing a large number" }) - ), - "image_token": Schema.optionalKey( - Schema.String.annotate({ "description": "A number or string value representing a large number" }) - ), - "image_output": Schema.optionalKey( - Schema.String.annotate({ "description": "A number or string value representing a large number" }) - ), - "audio": Schema.optionalKey( - Schema.String.annotate({ "description": "A number or string value representing a large number" }) - ), - "audio_output": Schema.optionalKey( - Schema.String.annotate({ "description": "A number or string value representing a large number" }) - ), - "input_audio_cache": Schema.optionalKey( - Schema.String.annotate({ "description": "A number or string value representing a large number" }) - ), - "web_search": Schema.optionalKey( - Schema.String.annotate({ "description": "A number or string value representing a large number" }) - ), - "internal_reasoning": Schema.optionalKey( - Schema.String.annotate({ "description": "A number or string value representing a large number" }) - ), - "input_cache_read": Schema.optionalKey( - Schema.String.annotate({ "description": "A number or string value representing a large number" }) - ), - "input_cache_write": Schema.optionalKey( - Schema.String.annotate({ "description": "A number or string value representing a large number" }) - ), - "discount": Schema.optionalKey(Schema.Number.check(Schema.isFinite())) -}).annotate({ "description": "Pricing information for the model" }) export type ModelGroup = | "Router" | "Media" @@ -2396,6 +3385,7 @@ export type ModelGroup = | "GPT" | "Claude" | "Gemini" + | "Gemma" | "Grok" | "Cohere" | "Nova" @@ -2416,6 +3406,7 @@ export const ModelGroup = Schema.Literals([ "GPT", "Claude", "Gemini", + "Gemma", "Grok", "Cohere", "Nova", @@ -2429,36 +3420,445 @@ export const ModelGroup = Schema.Literals([ "PaLM", "RWKV", "Qwen3" -]).annotate({ "description": "Tokenizer type used by the model" }) -export type InputModality = "text" | "image" | "file" | "audio" | "video" -export const InputModality = Schema.Literals(["text", "image", "file", "audio", "video"]) -export type OutputModality = "text" | "image" | "embeddings" | "audio" -export const OutputModality = Schema.Literals(["text", "image", "embeddings", "audio"]) -export type TopProviderInfo = { - readonly "context_length"?: number - readonly "max_completion_tokens"?: number - readonly "is_moderated": boolean +]).annotate({ "description": "Tokenizer type used by the model", "identifier": "ModelGroup" }) +export type ModelLinks = { readonly "details": string } +export const ModelLinks = Schema.Struct({ + "details": Schema.String.annotate({ "description": "URL for the model details/endpoints API" }) +}).annotate({ "description": "Related API endpoints and resources for this model.", "identifier": "ModelLinks" }) +export type ModelName = string +export const ModelName = Schema.String.annotate({ + "description": "Model to use for completion", + "identifier": "ModelName" +}) +export type ModelsCountResponse = { readonly "data": { readonly "count": number } } +export const ModelsCountResponse = Schema.Struct({ + "data": Schema.Struct({ + "count": Schema.Number.annotate({ "description": "Total number of available models" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ) + }).annotate({ "description": "Model count data" }) +}).annotate({ "description": "Model count data", "identifier": "ModelsCountResponse" }) +export type ModerationPlugin = { readonly "id": "moderation" } +export const ModerationPlugin = Schema.Struct({ "id": Schema.Literal("moderation") }).annotate({ + "identifier": "ModerationPlugin" +}) +export type MultimodalMedia = { readonly "data": string; readonly "format"?: string } +export const MultimodalMedia = Schema.Struct({ "data": Schema.String, "format": Schema.optionalKey(Schema.String) }) + .annotate({ "identifier": "MultimodalMedia" }) +export type NamespaceFunctionTool = { + readonly "allowed_callers"?: ReadonlyArray<"direct" | "programmatic"> | null + readonly "defer_loading"?: boolean + readonly "description"?: string | null + readonly "name": string + readonly "output_schema"?: { readonly [x: string]: Schema.Json } | null + readonly "parameters"?: { readonly [x: string]: Schema.Json } | null + readonly "strict"?: boolean | null + readonly "type": "function" } -export const TopProviderInfo = Schema.Struct({ - "context_length": Schema.optionalKey( - Schema.Number.annotate({ "description": "Context length from the top provider" }).check(Schema.isFinite()) +export const NamespaceFunctionTool = Schema.Struct({ + "allowed_callers": Schema.optionalKey( + Schema.Union([Schema.Array(Schema.Literals(["direct", "programmatic"])), Schema.Null]) ), - "max_completion_tokens": Schema.optionalKey( - Schema.Number.annotate({ "description": "Maximum completion tokens from the top provider" }).check( - Schema.isFinite() - ) + "defer_loading": Schema.optionalKey(Schema.Boolean), + "description": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "name": Schema.String, + "output_schema": Schema.optionalKey( + Schema.Union([Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })), Schema.Null]) ), - "is_moderated": Schema.Boolean.annotate({ "description": "Whether the top provider moderates content" }) -}).annotate({ "description": "Information about the top provider for this model" }) -export type PerRequestLimits = { readonly "prompt_tokens": number; readonly "completion_tokens": number } -export const PerRequestLimits = Schema.Struct({ - "prompt_tokens": Schema.Number.annotate({ "description": "Maximum prompt tokens per request" }).check( - Schema.isFinite() + "parameters": Schema.optionalKey( + Schema.Union([Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })), Schema.Null]) ), - "completion_tokens": Schema.Number.annotate({ "description": "Maximum completion tokens per request" }).check( - Schema.isFinite() + "strict": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), + "type": Schema.Literal("function") +}).annotate({ "description": "A function tool grouped inside a namespace tool", "identifier": "NamespaceFunctionTool" }) +export type NotFoundResponseErrorData = { + readonly "code": number + readonly "message": string + readonly "metadata"?: { readonly [x: string]: Schema.Json } | null +} +export const NotFoundResponseErrorData = Schema.Struct({ + "code": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "message": Schema.String, + "metadata": Schema.optionalKey( + Schema.Union([Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })), Schema.Null]) ) -}).annotate({ "description": "Per-request token limits" }) +}).annotate({ "description": "Error data for NotFoundResponse", "identifier": "NotFoundResponseErrorData" }) +export type ObservabilityFilterRuleGroup = { + readonly "logic"?: "and" | "or" + readonly "rules": ReadonlyArray< + { + readonly "field": + | "model" + | "provider" + | "session_id" + | "user_id" + | "api_key_name" + | "finish_reason" + | "input" + | "output" + | "total_cost" + | "total_tokens" + | "prompt_tokens" + | "completion_tokens" + readonly "operator": + | "equals" + | "not_equals" + | "contains" + | "not_contains" + | "regex" + | "starts_with" + | "ends_with" + | "gt" + | "lt" + | "gte" + | "lte" + | "exists" + | "not_exists" + readonly "value"?: string | number + } + > +} +export const ObservabilityFilterRuleGroup = Schema.Struct({ + "logic": Schema.optionalKey(Schema.Literals(["and", "or"])), + "rules": Schema.Array(Schema.Struct({ + "field": Schema.Literals([ + "model", + "provider", + "session_id", + "user_id", + "api_key_name", + "finish_reason", + "input", + "output", + "total_cost", + "total_tokens", + "prompt_tokens", + "completion_tokens" + ]), + "operator": Schema.Literals([ + "equals", + "not_equals", + "contains", + "not_contains", + "regex", + "starts_with", + "ends_with", + "gt", + "lt", + "gte", + "lte", + "exists", + "not_exists" + ]), + "value": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" }))]) + ) + })) +}).annotate({ "identifier": "ObservabilityFilterRuleGroup" }) +export type OpenAIResponseCustomToolCall = { + readonly "call_id": string + readonly "id"?: string + readonly "input": string + readonly "name": string + readonly "namespace"?: string + readonly "type": "custom_tool_call" +} +export const OpenAIResponseCustomToolCall = Schema.Struct({ + "call_id": Schema.String, + "id": Schema.optionalKey(Schema.String), + "input": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ), + "type": Schema.Literal("custom_tool_call") +}).annotate({ "identifier": "OpenAIResponseCustomToolCall" }) +export type OpenAIResponsesImageGenCallCompleted = { + readonly "item_id": string + readonly "output_index": number + readonly "sequence_number": number + readonly "type": "response.image_generation_call.completed" +} +export const OpenAIResponsesImageGenCallCompleted = Schema.Struct({ + "item_id": Schema.String, + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.image_generation_call.completed") +}).annotate({ "identifier": "OpenAIResponsesImageGenCallCompleted" }) +export type OpenAIResponsesImageGenCallGenerating = { + readonly "item_id": string + readonly "output_index": number + readonly "sequence_number": number + readonly "type": "response.image_generation_call.generating" +} +export const OpenAIResponsesImageGenCallGenerating = Schema.Struct({ + "item_id": Schema.String, + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.image_generation_call.generating") +}).annotate({ "identifier": "OpenAIResponsesImageGenCallGenerating" }) +export type OpenAIResponsesImageGenCallInProgress = { + readonly "item_id": string + readonly "output_index": number + readonly "sequence_number": number + readonly "type": "response.image_generation_call.in_progress" +} +export const OpenAIResponsesImageGenCallInProgress = Schema.Struct({ + "item_id": Schema.String, + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.image_generation_call.in_progress") +}).annotate({ "identifier": "OpenAIResponsesImageGenCallInProgress" }) +export type OpenAIResponsesImageGenCallPartialImage = { + readonly "item_id": string + readonly "output_index": number + readonly "partial_image_b64": string + readonly "partial_image_index": number + readonly "sequence_number": number + readonly "type": "response.image_generation_call.partial_image" +} +export const OpenAIResponsesImageGenCallPartialImage = Schema.Struct({ + "item_id": Schema.String, + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "partial_image_b64": Schema.String, + "partial_image_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.image_generation_call.partial_image") +}).annotate({ "identifier": "OpenAIResponsesImageGenCallPartialImage" }) +export type OpenAIResponsesRefusalContent = { readonly "refusal": string; readonly "type": "refusal" } +export const OpenAIResponsesRefusalContent = Schema.Struct({ + "refusal": Schema.String, + "type": Schema.Literal("refusal") +}).annotate({ "identifier": "OpenAIResponsesRefusalContent" }) +export type OpenAIResponsesResponseStatus = + | "completed" + | "incomplete" + | "in_progress" + | "failed" + | "cancelled" + | "queued" +export const OpenAIResponsesResponseStatus = Schema.Literals([ + "completed", + "incomplete", + "in_progress", + "failed", + "cancelled", + "queued" +]).annotate({ "identifier": "OpenAIResponsesResponseStatus" }) +export type OpenAIResponsesSearchCompleted = { + readonly "item_id": string + readonly "output_index": number + readonly "sequence_number": number + readonly "type": "response.web_search_call.completed" +} +export const OpenAIResponsesSearchCompleted = Schema.Struct({ + "item_id": Schema.String, + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.web_search_call.completed") +}).annotate({ "identifier": "OpenAIResponsesSearchCompleted" }) +export type Objects_11 = { readonly "name": string; readonly "type": "function" } +export const Objects_11 = Schema.Struct({ "name": Schema.String, "type": Schema.Literal("function") }) +export type Objects_12 = { readonly "type": "web_search_preview_2025_03_11" | "web_search_preview" } +export const Objects_12 = Schema.Struct({ + "type": Schema.Literals(["web_search_preview_2025_03_11", "web_search_preview"]) +}) +export type Objects_13 = { readonly "type": "apply_patch" } +export const Objects_13 = Schema.Struct({ "type": Schema.Literal("apply_patch") }) +export type Objects_14 = { readonly "type": "shell" } +export const Objects_14 = Schema.Struct({ "type": Schema.Literal("shell") }) +export type OpenAIResponsesTruncation = "auto" | "disabled" | null +export const OpenAIResponsesTruncation = Schema.Union([Schema.Literal("auto"), Schema.Literal("disabled"), Schema.Null]) + .annotate({ "identifier": "OpenAIResponsesTruncation" }) +export type Objects_15 = { readonly "cache_write_tokens"?: number | null; readonly "cached_tokens": number } +export const Objects_15 = Schema.Struct({ + "cache_write_tokens": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]) + ), + "cached_tokens": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) +}) +export type Objects_16 = { readonly "reasoning_tokens": number } +export const Objects_16 = Schema.Struct({ + "reasoning_tokens": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) +}) +export type OpenAIResponsesWebSearchCallInProgress = { + readonly "item_id": string + readonly "output_index": number + readonly "sequence_number": number + readonly "type": "response.web_search_call.in_progress" +} +export const OpenAIResponsesWebSearchCallInProgress = Schema.Struct({ + "item_id": Schema.String, + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.web_search_call.in_progress") +}).annotate({ "identifier": "OpenAIResponsesWebSearchCallInProgress" }) +export type OpenAIResponsesWebSearchCallSearching = { + readonly "item_id": string + readonly "output_index": number + readonly "sequence_number": number + readonly "type": "response.web_search_call.searching" +} +export const OpenAIResponsesWebSearchCallSearching = Schema.Struct({ + "item_id": Schema.String, + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.web_search_call.searching") +}).annotate({ "identifier": "OpenAIResponsesWebSearchCallSearching" }) +export type OpenResponsesTopLogprobs = { + readonly "bytes"?: ReadonlyArray + readonly "logprob"?: number + readonly "token"?: string +} +export const OpenResponsesTopLogprobs = Schema.Struct({ + "bytes": Schema.optionalKey(Schema.Array(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })))), + "logprob": Schema.optionalKey( + Schema.Number.annotate({ "format": "double" }).check(Schema.isFinite().annotate({ "expected": "a finite number" })) + ), + "token": Schema.optionalKey(Schema.String) +}).annotate({ "description": "Alternative token with its log probability", "identifier": "OpenResponsesTopLogprobs" }) +export type ORAnthropicStopReason = + | "end_turn" + | "max_tokens" + | "stop_sequence" + | "tool_use" + | "pause_turn" + | "refusal" + | "compaction" + | null +export const ORAnthropicStopReason = Schema.Union([ + Schema.Literal("end_turn"), + Schema.Literal("max_tokens"), + Schema.Literal("stop_sequence"), + Schema.Literal("tool_use"), + Schema.Literal("pause_turn"), + Schema.Literal("refusal"), + Schema.Literal("compaction"), + Schema.Null +]).annotate({ "identifier": "ORAnthropicStopReason" }) +export type OutputComputerCallItem = { + readonly "action"?: Schema.Json + readonly "call_id": string + readonly "id"?: string + readonly "pending_safety_checks": ReadonlyArray< + { readonly "code": string; readonly "id": string; readonly "message": string } + > + readonly "status": "completed" | "incomplete" | "in_progress" + readonly "type": "computer_call" +} +export const OutputComputerCallItem = Schema.Struct({ + "action": Schema.optionalKey(Schema.Json.annotate({ "expected": "JSON value" })), + "call_id": Schema.String, + "id": Schema.optionalKey(Schema.String), + "pending_safety_checks": Schema.Array( + Schema.Struct({ "code": Schema.String, "id": Schema.String, "message": Schema.String }) + ), + "status": Schema.Literals(["completed", "incomplete", "in_progress"]), + "type": Schema.Literal("computer_call") +}).annotate({ "identifier": "OutputComputerCallItem" }) +export type OutputCustomToolCallItem = { + readonly "call_id": string + readonly "id"?: string + readonly "input": string + readonly "name": string + readonly "namespace"?: string + readonly "type": "custom_tool_call" +} +export const OutputCustomToolCallItem = Schema.Struct({ + "call_id": Schema.String, + "id": Schema.optionalKey(Schema.String), + "input": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ), + "type": Schema.Literal("custom_tool_call") +}).annotate({ + "description": + "A call to a custom (freeform-grammar) tool created by the model — distinct from `function_call`. Used for tools like Codex CLI's `apply_patch` whose payload is opaque text rather than JSON arguments.", + "identifier": "OutputCustomToolCallItem" +}) +export type OutputFunctionCallItem = { + readonly "arguments": string + readonly "call_id": string + readonly "id"?: string + readonly "name": string + readonly "namespace"?: string + readonly "status"?: "completed" | "incomplete" | "in_progress" + readonly "type": "function_call" +} +export const OutputFunctionCallItem = Schema.Struct({ + "arguments": Schema.String, + "call_id": Schema.String, + "id": Schema.optionalKey(Schema.String), + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ), + "status": Schema.optionalKey(Schema.Literals(["completed", "incomplete", "in_progress"])), + "type": Schema.Literal("function_call") +}).annotate({ "identifier": "OutputFunctionCallItem" }) +export type OutputItemCustomToolCall = { + readonly "call_id": string + readonly "id"?: string + readonly "input": string + readonly "name": string + readonly "namespace"?: string + readonly "type": "custom_tool_call" +} +export const OutputItemCustomToolCall = Schema.Struct({ + "call_id": Schema.String, + "id": Schema.optionalKey(Schema.String), + "input": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ), + "type": Schema.Literal("custom_tool_call") +}).annotate({ "identifier": "OutputItemCustomToolCall" }) +export type OutputItemFunctionCall = { + readonly "arguments": string + readonly "call_id": string + readonly "id"?: string + readonly "name": string + readonly "namespace"?: string + readonly "status"?: "completed" | "incomplete" | "in_progress" + readonly "type": "function_call" +} +export const OutputItemFunctionCall = Schema.Struct({ + "arguments": Schema.String, + "call_id": Schema.String, + "id": Schema.optionalKey(Schema.String), + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ), + "status": Schema.optionalKey(Schema.Literals(["completed", "incomplete", "in_progress"])), + "type": Schema.Literal("function_call") +}).annotate({ "identifier": "OutputItemFunctionCall" }) +export type OutputModality = "text" | "image" | "embeddings" | "audio" | "video" | "rerank" | "speech" | "transcription" +export const OutputModality = Schema.Literals([ + "text", + "image", + "embeddings", + "audio", + "video", + "rerank", + "speech", + "transcription" +]).annotate({ "identifier": "OutputModality" }) +export type OutputModalityEnum = "text" | "image" +export const OutputModalityEnum = Schema.Literals(["text", "image"]).annotate({ "identifier": "OutputModalityEnum" }) export type Parameter = | "temperature" | "top_p" @@ -2469,9 +3869,11 @@ export type Parameter = | "presence_penalty" | "repetition_penalty" | "max_tokens" + | "max_completion_tokens" | "logit_bias" | "logprobs" | "top_logprobs" + | "prediction" | "seed" | "response_format" | "structured_outputs" @@ -2494,9 +3896,11 @@ export const Parameter = Schema.Literals([ "presence_penalty", "repetition_penalty", "max_tokens", + "max_completion_tokens", "logit_bias", "logprobs", "top_logprobs", + "prediction", "seed", "response_format", "structured_outputs", @@ -2509,53 +3913,350 @@ export const Parameter = Schema.Literals([ "reasoning_effort", "web_search_options", "verbosity" -]) -export type DefaultParameters = { - readonly "temperature"?: number - readonly "top_p"?: number - readonly "frequency_penalty"?: number +]).annotate({ "identifier": "Parameter" }) +export type ParetoRouterPlugin = { + readonly "enabled"?: boolean + readonly "id": "pareto-router" + readonly "max_price"?: number + readonly "min_coding_score"?: number + readonly "price_source"?: "prompt" | "weighted_avg" } -export const DefaultParameters = Schema.Struct({ - "temperature": Schema.optionalKey( - Schema.Number.check(Schema.isFinite()).check(Schema.isGreaterThanOrEqualTo(0)).check(Schema.isLessThanOrEqualTo(2)) +export const ParetoRouterPlugin = Schema.Struct({ + "enabled": Schema.optionalKey( + Schema.Boolean.annotate({ + "description": "Set to false to disable the pareto-router plugin for this request. Defaults to true." + }) ), - "top_p": Schema.optionalKey( - Schema.Number.check(Schema.isFinite()).check(Schema.isGreaterThanOrEqualTo(0)).check(Schema.isLessThanOrEqualTo(1)) + "id": Schema.Literal("pareto-router"), + "max_price": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Maximum input price in USD per million tokens. When set, quality-tier selection (min_coding_score) is bypassed: the router computes the Pareto frontier over the top coding models and routes to the best-scoring frontier model priced at or below this cap, falling back through cheaper frontier models, then non-frontier models. Enforced against the price source given by price_source. Returns 404 when no candidate satisfies the cap.", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })).check( + Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" }) + ) ), - "frequency_penalty": Schema.optionalKey( - Schema.Number.check(Schema.isFinite()).check(Schema.isGreaterThanOrEqualTo(-2)).check(Schema.isLessThanOrEqualTo(2)) + "min_coding_score": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Minimum coding quality score between 0 and 1. Maps to internal quality tiers: >= 0.66 → high (top coding models), >= 0.33 → medium (strong modern flagships), < 0.33 → low (capable coders above the median). Omit to default to the highest tier (equivalent to >= 0.66). Not used when max_price is set (price-based selection takes over).", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })).check( + Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" }) + ).check(Schema.isLessThanOrEqualTo(1).annotate({ "expected": "a value less than or equal to 1" })) + ), + "price_source": Schema.optionalKey( + Schema.Literals(["prompt", "weighted_avg"]).annotate({ + "description": + "Price source for the Pareto frontier cost axis and for enforcing max_price. \"prompt\" uses catalog list price (endpoint.pricing.prompt). \"weighted_avg\" uses traffic-weighted effective input price from ClickHouse, falling back to prompt price for models without traffic data. Defaults to \"prompt\"." + }) ) -}).annotate({ "description": "Default parameters for this model" }) -export type ModelsCountResponse = { readonly "data": { readonly "count": number } } -export const ModelsCountResponse = Schema.Struct({ - "data": Schema.Struct({ - "count": Schema.Number.annotate({ "description": "Total number of available models" }).check(Schema.isFinite()) - }).annotate({ "description": "Model count data" }) -}).annotate({ "description": "Model count data" }) -export type EndpointStatus = 0 | -1 | -2 | -3 | -5 | -10 -export const EndpointStatus = Schema.Literals([0, -1, -2, -3, -5, -10]) -export type PercentileStats = { - readonly "p50": number - readonly "p75": number - readonly "p90": number - readonly "p99": number +}).annotate({ "identifier": "ParetoRouterPlugin" }) +export type PayloadTooLargeResponseErrorData = { + readonly "code": number + readonly "message": string + readonly "metadata"?: { readonly [x: string]: Schema.Json } | null +} +export const PayloadTooLargeResponseErrorData = Schema.Struct({ + "code": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "message": Schema.String, + "metadata": Schema.optionalKey( + Schema.Union([Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })), Schema.Null]) + ) +}).annotate({ + "description": "Error data for PayloadTooLargeResponse", + "identifier": "PayloadTooLargeResponseErrorData" +}) +export type PaymentRequiredResponseErrorData = { + readonly "code": number + readonly "message": string + readonly "metadata"?: { readonly [x: string]: Schema.Json } | null } -export const PercentileStats = Schema.Struct({ - "p50": Schema.Number.annotate({ "description": "Median (50th percentile)" }).check(Schema.isFinite()), - "p75": Schema.Number.annotate({ "description": "75th percentile" }).check(Schema.isFinite()), - "p90": Schema.Number.annotate({ "description": "90th percentile" }).check(Schema.isFinite()), - "p99": Schema.Number.annotate({ "description": "99th percentile" }).check(Schema.isFinite()) +export const PaymentRequiredResponseErrorData = Schema.Struct({ + "code": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "message": Schema.String, + "metadata": Schema.optionalKey( + Schema.Union([Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })), Schema.Null]) + ) }).annotate({ + "description": "Error data for PaymentRequiredResponse", + "identifier": "PaymentRequiredResponseErrorData" +}) +export type PDFParserEngine = "mistral-ocr" | "native" | "cloudflare-ai" | "pdf-text" +export const PDFParserEngine = Schema.Union([ + Schema.Literals(["mistral-ocr", "native", "cloudflare-ai"]), + Schema.Literal("pdf-text") +]).annotate({ "description": - "Latency percentiles in milliseconds over the last 30 minutes. Latency measures time to first token. Only visible when authenticated with an API key or cookie; returns null for unauthenticated requests." + "The engine to use for parsing PDF files. \"pdf-text\" is deprecated and automatically redirected to \"cloudflare-ai\".", + "identifier": "PDFParserEngine" }) -export type __schema5 = ReadonlyArray< - | "AI21" - | "AionLabs" - | "Alibaba" - | "Ambient" - | "Amazon Bedrock" - | "Amazon Nova" +export type PercentileLatencyCutoffs = { + readonly "p50"?: number | null + readonly "p75"?: number | null + readonly "p90"?: number | null + readonly "p99"?: number | null +} +export const PercentileLatencyCutoffs = Schema.Struct({ + "p50": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), Schema.Null]) + .annotate({ "description": "Maximum p50 latency (seconds)", "format": "double" }) + ), + "p75": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), Schema.Null]) + .annotate({ "description": "Maximum p75 latency (seconds)", "format": "double" }) + ), + "p90": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), Schema.Null]) + .annotate({ "description": "Maximum p90 latency (seconds)", "format": "double" }) + ), + "p99": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), Schema.Null]) + .annotate({ "description": "Maximum p99 latency (seconds)", "format": "double" }) + ) +}).annotate({ + "description": "Percentile-based latency cutoffs. All specified cutoffs must be met for an endpoint to be preferred.", + "identifier": "PercentileLatencyCutoffs" +}) +export type PercentileStats = { + readonly "p50": number + readonly "p75": number + readonly "p90": number + readonly "p99": number + readonly [x: string]: Schema.Json +} | null +export const PercentileStats = Schema.Union([ + Schema.StructWithRest( + Schema.Struct({ + "p50": Schema.Number.annotate({ "description": "Median (50th percentile)", "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ), + "p75": Schema.Number.annotate({ "description": "75th percentile", "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ), + "p90": Schema.Number.annotate({ "description": "90th percentile", "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ), + "p99": Schema.Number.annotate({ "description": "99th percentile", "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ) + }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] + ), + Schema.Null +]).annotate({ + "description": + "Latency percentiles in milliseconds over the last 30 minutes. Latency measures time to first token. Only visible when authenticated with an API key or cookie; returns null for unauthenticated requests.", + "identifier": "PercentileStats" +}) +export type PercentileThroughputCutoffs = { + readonly "p50"?: number | null + readonly "p75"?: number | null + readonly "p90"?: number | null + readonly "p99"?: number | null +} +export const PercentileThroughputCutoffs = Schema.Struct({ + "p50": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), Schema.Null]) + .annotate({ "description": "Minimum p50 throughput (tokens/sec)", "format": "double" }) + ), + "p75": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), Schema.Null]) + .annotate({ "description": "Minimum p75 throughput (tokens/sec)", "format": "double" }) + ), + "p90": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), Schema.Null]) + .annotate({ "description": "Minimum p90 throughput (tokens/sec)", "format": "double" }) + ), + "p99": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), Schema.Null]) + .annotate({ "description": "Minimum p99 throughput (tokens/sec)", "format": "double" }) + ) +}).annotate({ + "description": + "Percentile-based throughput cutoffs. All specified cutoffs must be met for an endpoint to be preferred.", + "identifier": "PercentileThroughputCutoffs" +}) +export type PerRequestLimits = { + readonly "completion_tokens": number + readonly "prompt_tokens": number + readonly [x: string]: Schema.Json +} | null +export const PerRequestLimits = Schema.Union([ + Schema.StructWithRest( + Schema.Struct({ + "completion_tokens": Schema.Number.annotate({ "description": "Maximum completion tokens per request" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ), + "prompt_tokens": Schema.Number.annotate({ "description": "Maximum prompt tokens per request" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ) + }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] + ), + Schema.Null +]).annotate({ "description": "Per-request token limits", "identifier": "PerRequestLimits" }) +export type PipelineStageType = "guardrail" | "plugin" | "server_tools" | "response_healing" | "context_compression" +export const PipelineStageType = Schema.Literals([ + "guardrail", + "plugin", + "server_tools", + "response_healing", + "context_compression" +]).annotate({ + "description": + "Categorical kind of a pipeline stage. Multiple plugins can share a type (e.g. all guardrail-level plugins emit `guardrail`); the `name` field disambiguates which plugin emitted it.", + "identifier": "PipelineStageType" +}) +export type PredictionContentText = { readonly "text": string; readonly "type": "text" } +export const PredictionContentText = Schema.Struct({ "text": Schema.String, "type": Schema.Literal("text") }).annotate({ + "description": "Text content part for a predicted output.", + "identifier": "PredictionContentText" +}) +export type PresetDesignatedVersion = { + readonly "config": {} + readonly "created_at": string + readonly "creator_id": string + readonly "id": string + readonly "preset_id": string + readonly "system_prompt": string | null + readonly "updated_at": string + readonly "version": number + readonly [x: string]: Schema.Json +} | null +export const PresetDesignatedVersion = Schema.Union([ + Schema.StructWithRest( + Schema.Struct({ + "config": Schema.Struct({}), + "created_at": Schema.String, + "creator_id": Schema.String, + "id": Schema.String, + "preset_id": Schema.String, + "system_prompt": Schema.Union([Schema.String, Schema.Null]), + "updated_at": Schema.String, + "version": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] + ), + Schema.Null +]).annotate({ + "description": "A specific version of a preset, containing config and optional system prompt.", + "identifier": "PresetDesignatedVersion" +}) +export type PresetStatus = "active" | "disabled" | "archived" +export const PresetStatus = Schema.Literals(["active", "disabled", "archived"]).annotate({ + "description": "The status of a preset.", + "identifier": "PresetStatus" +}) +export type Objects_18 = { + readonly "city"?: string | null + readonly "country"?: string | null + readonly "region"?: string | null + readonly "timezone"?: string | null + readonly "type": "approximate" + readonly [x: string]: Schema.Json +} +export const Objects_18 = Schema.StructWithRest( + Schema.Struct({ + "city": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "country": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "region": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "timezone": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "type": Schema.Literal("approximate") + }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] +) +export type PricingOverride = { + readonly "audio"?: string + readonly "completion"?: string + readonly "input_audio_cache"?: string + readonly "input_cache_read"?: string + readonly "input_cache_write"?: string + readonly "input_cache_write_1h"?: string + readonly "min_prompt_tokens"?: number + readonly "prompt"?: string + readonly "utc_end"?: number + readonly "utc_start"?: number +} +export const PricingOverride = Schema.Struct({ + "audio": Schema.optionalKey( + Schema.String.annotate({ "description": "Overridden price in USD per audio input token" }) + ), + "completion": Schema.optionalKey( + Schema.String.annotate({ "description": "Overridden price in USD per token for completion (output) generation" }) + ), + "input_audio_cache": Schema.optionalKey( + Schema.String.annotate({ "description": "Overridden price in USD per cached audio input token" }) + ), + "input_cache_read": Schema.optionalKey( + Schema.String.annotate({ "description": "Overridden price in USD per cached input token (read)" }) + ), + "input_cache_write": Schema.optionalKey( + Schema.String.annotate({ "description": "Overridden price in USD per cache-write token" }) + ), + "input_cache_write_1h": Schema.optionalKey( + Schema.String.annotate({ "description": "Overridden price in USD per 1-hour cache-write token" }) + ), + "min_prompt_tokens": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Condition: the entry applies when the total prompt tokens of a request are strictly greater than this threshold", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })) + ), + "prompt": Schema.optionalKey( + Schema.String.annotate({ "description": "Overridden price in USD per token for prompt (input) processing" }) + ), + "utc_end": Schema.optionalKey( + Schema.Number.annotate({ + "description": "Condition: exclusive end of a daily UTC time window as an HHMM clock number (e.g. 400 = 04:00)", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })) + ), + "utc_start": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Condition: inclusive start of a daily UTC time window as an HHMM clock number (e.g. 100 = 01:00, 1030 = 10:30). The entry applies while the current UTC time is inside the half-open window [utc_start, utc_end), which may wrap past midnight (utc_start > utc_end).", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })) + ) +}).annotate({ + "description": + "A conditional override of the base pricing. An entry applies only when all of its condition fields (e.g. min_prompt_tokens, or the utc_start/utc_end time window) match the request; among applicable entries, later entries win per price key; price keys absent from an entry inherit the base price.", + "identifier": "PricingOverride" +}) +export type Objects_19 = { readonly "mode": "explicit"; readonly [x: string]: Schema.Json } +export const Objects_19 = Schema.StructWithRest(Schema.Struct({ "mode": Schema.Literal("explicit") }), [ + Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })) +]) +export type Objects_20 = { + readonly "mode": "explicit" + readonly "ttl"?: string | null + readonly [x: string]: Schema.Json +} +export const Objects_20 = Schema.StructWithRest( + Schema.Struct({ + "mode": Schema.Literal("explicit"), + "ttl": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] +) +export type PromptInjectionScanScope = "user_only" | "all_messages" +export const PromptInjectionScanScope = Schema.Literals(["user_only", "all_messages"]).annotate({ + "description": + "Which message roles to scan for prompt injection. Only applies to the regex-prompt-injection builtin. Defaults to all_messages.", + "identifier": "PromptInjectionScanScope" +}) +export type ProviderName = + | "AkashML" + | "AI21" + | "AionLabs" + | "Alibaba" + | "Ambient" + | "Baidu" + | "Amazon Bedrock" + | "Amazon Nova" | "Anthropic" | "Arcee AI" | "AtlasCloud" @@ -2570,26 +4271,37 @@ export type __schema5 = ReadonlyArray< | "Clarifai" | "Cloudflare" | "Cohere" + | "CoreWeave" + | "Crucible" | "Crusoe" + | "Darkbloom" + | "Decart" + | "Deepgram" | "DeepInfra" | "DeepSeek" + | "DekaLLM" + | "DigitalOcean" | "Featherless" | "Fireworks" + | "Fish Audio" | "Friendli" | "GMICloud" | "Google" | "Google AI Studio" | "Groq" - | "Hyperbolic" + | "HeyGen" | "Inception" | "Inceptron" | "InferenceNet" + | "Ionstream" | "Infermatic" | "Io Net" + | "Inferact vLLM" | "Inflection" | "Liquid" | "Mara" | "Mancer 2" + | "Meta" | "Minimax" | "ModelRun" | "Mistral" @@ -2598,15 +4310,22 @@ export type __schema5 = ReadonlyArray< | "Morph" | "NCompass" | "Nebius" + | "Nex AGI" | "NextBit" | "Novita" | "Nvidia" | "OpenAI" | "OpenInference" | "Parasail" + | "Poolside" + | "Perceptron" | "Perplexity" | "Phala" + | "Recraft" + | "Reka" | "Relace" + | "Sail Research" + | "Sakana AI" | "SambaNova" | "Seed" | "SiliconFlow" @@ -2615,23 +4334,575 @@ export type __schema5 = ReadonlyArray< | "Stealth" | "StreamLake" | "Switchpoint" + | "Tencent" + | "Tenstorrent" | "Together" | "Upstage" | "Venice" + | "Wafer" | "WandB" + | "Quiver" + | "Krea" + | "Runway" | "Xiaomi" | "xAI" | "Z.AI" | "FakeProvider" - | string -> -export const __schema5 = Schema.Array( - Schema.Union([ +export const ProviderName = Schema.Literals([ + "AkashML", + "AI21", + "AionLabs", + "Alibaba", + "Ambient", + "Baidu", + "Amazon Bedrock", + "Amazon Nova", + "Anthropic", + "Arcee AI", + "AtlasCloud", + "Avian", + "Azure", + "BaseTen", + "BytePlus", + "Black Forest Labs", + "Cerebras", + "Chutes", + "Cirrascale", + "Clarifai", + "Cloudflare", + "Cohere", + "CoreWeave", + "Crucible", + "Crusoe", + "Darkbloom", + "Decart", + "Deepgram", + "DeepInfra", + "DeepSeek", + "DekaLLM", + "DigitalOcean", + "Featherless", + "Fireworks", + "Fish Audio", + "Friendli", + "GMICloud", + "Google", + "Google AI Studio", + "Groq", + "HeyGen", + "Inception", + "Inceptron", + "InferenceNet", + "Ionstream", + "Infermatic", + "Io Net", + "Inferact vLLM", + "Inflection", + "Liquid", + "Mara", + "Mancer 2", + "Meta", + "Minimax", + "ModelRun", + "Mistral", + "Modular", + "Moonshot AI", + "Morph", + "NCompass", + "Nebius", + "Nex AGI", + "NextBit", + "Novita", + "Nvidia", + "OpenAI", + "OpenInference", + "Parasail", + "Poolside", + "Perceptron", + "Perplexity", + "Phala", + "Recraft", + "Reka", + "Relace", + "Sail Research", + "Sakana AI", + "SambaNova", + "Seed", + "SiliconFlow", + "Sourceful", + "StepFun", + "Stealth", + "StreamLake", + "Switchpoint", + "Tencent", + "Tenstorrent", + "Together", + "Upstage", + "Venice", + "Wafer", + "WandB", + "Quiver", + "Krea", + "Runway", + "Xiaomi", + "xAI", + "Z.AI", + "FakeProvider" +]).annotate({ "identifier": "ProviderName" }) +export type Objects_21 = {} +export const Objects_21 = Schema.Struct({}) +export type Objects_22 = {} +export const Objects_22 = Schema.Struct({}) +export type Objects_23 = {} +export const Objects_23 = Schema.Struct({}) +export type Objects_24 = {} +export const Objects_24 = Schema.Struct({}) +export type Objects_25 = {} +export const Objects_25 = Schema.Struct({}) +export type Objects_26 = {} +export const Objects_26 = Schema.Struct({}) +export type Objects_27 = {} +export const Objects_27 = Schema.Struct({}) +export type Objects_28 = {} +export const Objects_28 = Schema.Struct({}) +export type Objects_29 = {} +export const Objects_29 = Schema.Struct({}) +export type Objects_30 = {} +export const Objects_30 = Schema.Struct({}) +export type Objects_31 = {} +export const Objects_31 = Schema.Struct({}) +export type Objects_32 = {} +export const Objects_32 = Schema.Struct({}) +export type Objects_33 = {} +export const Objects_33 = Schema.Struct({}) +export type Objects_34 = {} +export const Objects_34 = Schema.Struct({}) +export type Objects_35 = {} +export const Objects_35 = Schema.Struct({}) +export type Objects_36 = {} +export const Objects_36 = Schema.Struct({}) +export type Objects_37 = {} +export const Objects_37 = Schema.Struct({}) +export type Objects_38 = {} +export const Objects_38 = Schema.Struct({}) +export type Objects_39 = {} +export const Objects_39 = Schema.Struct({}) +export type Objects_40 = {} +export const Objects_40 = Schema.Struct({}) +export type Objects_41 = {} +export const Objects_41 = Schema.Struct({}) +export type Objects_42 = {} +export const Objects_42 = Schema.Struct({}) +export type Objects_43 = {} +export const Objects_43 = Schema.Struct({}) +export type Objects_44 = {} +export const Objects_44 = Schema.Struct({}) +export type Objects_45 = {} +export const Objects_45 = Schema.Struct({}) +export type Objects_46 = {} +export const Objects_46 = Schema.Struct({}) +export type Objects_47 = {} +export const Objects_47 = Schema.Struct({}) +export type Objects_48 = {} +export const Objects_48 = Schema.Struct({}) +export type Objects_49 = {} +export const Objects_49 = Schema.Struct({}) +export type Objects_50 = {} +export const Objects_50 = Schema.Struct({}) +export type Objects_51 = {} +export const Objects_51 = Schema.Struct({}) +export type Objects_52 = {} +export const Objects_52 = Schema.Struct({}) +export type Objects_53 = {} +export const Objects_53 = Schema.Struct({}) +export type Objects_54 = {} +export const Objects_54 = Schema.Struct({}) +export type Objects_55 = {} +export const Objects_55 = Schema.Struct({}) +export type Objects_56 = {} +export const Objects_56 = Schema.Struct({}) +export type Objects_57 = {} +export const Objects_57 = Schema.Struct({}) +export type Objects_58 = {} +export const Objects_58 = Schema.Struct({}) +export type Objects_59 = {} +export const Objects_59 = Schema.Struct({}) +export type Objects_60 = {} +export const Objects_60 = Schema.Struct({}) +export type Objects_61 = {} +export const Objects_61 = Schema.Struct({}) +export type Objects_62 = {} +export const Objects_62 = Schema.Struct({}) +export type Objects_63 = {} +export const Objects_63 = Schema.Struct({}) +export type Objects_64 = {} +export const Objects_64 = Schema.Struct({}) +export type Objects_65 = {} +export const Objects_65 = Schema.Struct({}) +export type Objects_66 = {} +export const Objects_66 = Schema.Struct({}) +export type Objects_67 = {} +export const Objects_67 = Schema.Struct({}) +export type Objects_68 = {} +export const Objects_68 = Schema.Struct({}) +export type Objects_69 = {} +export const Objects_69 = Schema.Struct({}) +export type Objects_70 = {} +export const Objects_70 = Schema.Struct({}) +export type Objects_71 = {} +export const Objects_71 = Schema.Struct({}) +export type Objects_72 = {} +export const Objects_72 = Schema.Struct({}) +export type Objects_73 = {} +export const Objects_73 = Schema.Struct({}) +export type Objects_74 = {} +export const Objects_74 = Schema.Struct({}) +export type Objects_75 = {} +export const Objects_75 = Schema.Struct({}) +export type Objects_76 = {} +export const Objects_76 = Schema.Struct({}) +export type Objects_77 = {} +export const Objects_77 = Schema.Struct({}) +export type Objects_78 = {} +export const Objects_78 = Schema.Struct({}) +export type Objects_79 = {} +export const Objects_79 = Schema.Struct({}) +export type Objects_80 = {} +export const Objects_80 = Schema.Struct({}) +export type Objects_81 = {} +export const Objects_81 = Schema.Struct({}) +export type Objects_82 = {} +export const Objects_82 = Schema.Struct({}) +export type Objects_83 = {} +export const Objects_83 = Schema.Struct({}) +export type Objects_84 = {} +export const Objects_84 = Schema.Struct({}) +export type Objects_85 = {} +export const Objects_85 = Schema.Struct({}) +export type Objects_86 = {} +export const Objects_86 = Schema.Struct({}) +export type Objects_87 = {} +export const Objects_87 = Schema.Struct({}) +export type Objects_88 = {} +export const Objects_88 = Schema.Struct({}) +export type Objects_89 = {} +export const Objects_89 = Schema.Struct({}) +export type Objects_90 = {} +export const Objects_90 = Schema.Struct({}) +export type Objects_91 = {} +export const Objects_91 = Schema.Struct({}) +export type Objects_92 = {} +export const Objects_92 = Schema.Struct({}) +export type Objects_93 = {} +export const Objects_93 = Schema.Struct({}) +export type Objects_94 = {} +export const Objects_94 = Schema.Struct({}) +export type Objects_95 = {} +export const Objects_95 = Schema.Struct({}) +export type Objects_96 = {} +export const Objects_96 = Schema.Struct({}) +export type Objects_97 = {} +export const Objects_97 = Schema.Struct({}) +export type Objects_98 = {} +export const Objects_98 = Schema.Struct({}) +export type Objects_99 = {} +export const Objects_99 = Schema.Struct({}) +export type Objects_100 = {} +export const Objects_100 = Schema.Struct({}) +export type Objects_101 = {} +export const Objects_101 = Schema.Struct({}) +export type Objects_102 = {} +export const Objects_102 = Schema.Struct({}) +export type Objects_103 = {} +export const Objects_103 = Schema.Struct({}) +export type Objects_104 = {} +export const Objects_104 = Schema.Struct({}) +export type Objects_105 = {} +export const Objects_105 = Schema.Struct({}) +export type Objects_106 = {} +export const Objects_106 = Schema.Struct({}) +export type Objects_107 = {} +export const Objects_107 = Schema.Struct({}) +export type Objects_108 = {} +export const Objects_108 = Schema.Struct({}) +export type Objects_109 = {} +export const Objects_109 = Schema.Struct({}) +export type Objects_110 = {} +export const Objects_110 = Schema.Struct({}) +export type Objects_111 = {} +export const Objects_111 = Schema.Struct({}) +export type Objects_112 = {} +export const Objects_112 = Schema.Struct({}) +export type Objects_113 = {} +export const Objects_113 = Schema.Struct({}) +export type Objects_114 = {} +export const Objects_114 = Schema.Struct({}) +export type Objects_115 = {} +export const Objects_115 = Schema.Struct({}) +export type Objects_116 = {} +export const Objects_116 = Schema.Struct({}) +export type Objects_117 = {} +export const Objects_117 = Schema.Struct({}) +export type Objects_118 = {} +export const Objects_118 = Schema.Struct({}) +export type Objects_119 = {} +export const Objects_119 = Schema.Struct({}) +export type Objects_120 = {} +export const Objects_120 = Schema.Struct({}) +export type Objects_121 = {} +export const Objects_121 = Schema.Struct({}) +export type Objects_122 = {} +export const Objects_122 = Schema.Struct({}) +export type Objects_123 = {} +export const Objects_123 = Schema.Struct({}) +export type Objects_124 = {} +export const Objects_124 = Schema.Struct({}) +export type Objects_125 = {} +export const Objects_125 = Schema.Struct({}) +export type Objects_126 = {} +export const Objects_126 = Schema.Struct({}) +export type Objects_127 = {} +export const Objects_127 = Schema.Struct({}) +export type Objects_128 = {} +export const Objects_128 = Schema.Struct({}) +export type Objects_129 = {} +export const Objects_129 = Schema.Struct({}) +export type Objects_130 = {} +export const Objects_130 = Schema.Struct({}) +export type Objects_131 = {} +export const Objects_131 = Schema.Struct({}) +export type Objects_132 = {} +export const Objects_132 = Schema.Struct({}) +export type Objects_133 = {} +export const Objects_133 = Schema.Struct({}) +export type Objects_134 = {} +export const Objects_134 = Schema.Struct({}) +export type Objects_135 = {} +export const Objects_135 = Schema.Struct({}) +export type Objects_136 = {} +export const Objects_136 = Schema.Struct({}) +export type Objects_137 = {} +export const Objects_137 = Schema.Struct({}) +export type Objects_138 = {} +export const Objects_138 = Schema.Struct({}) +export type Objects_139 = {} +export const Objects_139 = Schema.Struct({}) +export type Objects_140 = {} +export const Objects_140 = Schema.Struct({}) +export type Objects_141 = {} +export const Objects_141 = Schema.Struct({}) +export type Objects_142 = {} +export const Objects_142 = Schema.Struct({}) +export type Objects_143 = {} +export const Objects_143 = Schema.Struct({}) +export type Objects_144 = {} +export const Objects_144 = Schema.Struct({}) +export type Objects_145 = {} +export const Objects_145 = Schema.Struct({}) +export type Objects_146 = {} +export const Objects_146 = Schema.Struct({}) +export type Objects_147 = {} +export const Objects_147 = Schema.Struct({}) +export type ProviderOverloadedResponseErrorData = { + readonly "code": number + readonly "message": string + readonly "metadata"?: { readonly [x: string]: Schema.Json } | null +} +export const ProviderOverloadedResponseErrorData = Schema.Struct({ + "code": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "message": Schema.String, + "metadata": Schema.optionalKey( + Schema.Union([Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })), Schema.Null]) + ) +}).annotate({ + "description": "Error data for ProviderOverloadedResponse", + "identifier": "ProviderOverloadedResponseErrorData" +}) +export type ProviderResponse = { + readonly "endpoint_id"?: string + readonly "id"?: string + readonly "is_byok"?: boolean + readonly "latency"?: number + readonly "model_permaslug"?: string + readonly "provider_name"?: + | "AnyScale" + | "Atoma" + | "Cent-ML" + | "CrofAI" + | "Enfer" + | "GoPomelo" + | "HuggingFace" + | "Hyperbolic" + | "Hyperbolic 2" + | "InoCloud" + | "Kluster" + | "Lambda" + | "Lepton" + | "Lynn 2" + | "Lynn" + | "Mancer" + | "Modal" + | "Nineteen" + | "OctoAI" + | "Recursal" + | "Reflection" + | "Replicate" + | "SambaNova 2" + | "SF Compute" + | "Targon" + | "Together 2" + | "Ubicloud" + | "01.AI" + | "AkashML" + | "AI21" + | "AionLabs" + | "Alibaba" + | "Ambient" + | "Baidu" + | "Amazon Bedrock" + | "Amazon Nova" + | "Anthropic" + | "Arcee AI" + | "AtlasCloud" + | "Avian" + | "Azure" + | "BaseTen" + | "BytePlus" + | "Black Forest Labs" + | "Cerebras" + | "Chutes" + | "Cirrascale" + | "Clarifai" + | "Cloudflare" + | "Cohere" + | "CoreWeave" + | "Crucible" + | "Crusoe" + | "Darkbloom" + | "Decart" + | "Deepgram" + | "DeepInfra" + | "DeepSeek" + | "DekaLLM" + | "DigitalOcean" + | "Featherless" + | "Fireworks" + | "Fish Audio" + | "Friendli" + | "GMICloud" + | "Google" + | "Google AI Studio" + | "Groq" + | "HeyGen" + | "Inception" + | "Inceptron" + | "InferenceNet" + | "Ionstream" + | "Infermatic" + | "Io Net" + | "Inferact vLLM" + | "Inflection" + | "Liquid" + | "Mara" + | "Mancer 2" + | "Meta" + | "Minimax" + | "ModelRun" + | "Mistral" + | "Modular" + | "Moonshot AI" + | "Morph" + | "NCompass" + | "Nebius" + | "Nex AGI" + | "NextBit" + | "Novita" + | "Nvidia" + | "OpenAI" + | "OpenInference" + | "Parasail" + | "Poolside" + | "Perceptron" + | "Perplexity" + | "Phala" + | "Recraft" + | "Reka" + | "Relace" + | "Sail Research" + | "Sakana AI" + | "SambaNova" + | "Seed" + | "SiliconFlow" + | "Sourceful" + | "StepFun" + | "Stealth" + | "StreamLake" + | "Switchpoint" + | "Tencent" + | "Tenstorrent" + | "Together" + | "Upstage" + | "Venice" + | "Wafer" + | "WandB" + | "Quiver" + | "Krea" + | "Runway" + | "Xiaomi" + | "xAI" + | "Z.AI" + | "FakeProvider" + readonly "routed_service_tier"?: "flex" | "priority" + readonly "status": number | null +} +export const ProviderResponse = Schema.Struct({ + "endpoint_id": Schema.optionalKey(Schema.String.annotate({ "description": "Internal endpoint identifier" })), + "id": Schema.optionalKey(Schema.String.annotate({ "description": "Upstream provider response identifier" })), + "is_byok": Schema.optionalKey( + Schema.Boolean.annotate({ "description": "Whether the request used a bring-your-own-key" }) + ), + "latency": Schema.optionalKey( + Schema.Number.annotate({ "description": "Response latency in milliseconds", "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ) + ), + "model_permaslug": Schema.optionalKey(Schema.String.annotate({ "description": "Canonical model slug" })), + "provider_name": Schema.optionalKey( Schema.Literals([ + "AnyScale", + "Atoma", + "Cent-ML", + "CrofAI", + "Enfer", + "GoPomelo", + "HuggingFace", + "Hyperbolic", + "Hyperbolic 2", + "InoCloud", + "Kluster", + "Lambda", + "Lepton", + "Lynn 2", + "Lynn", + "Mancer", + "Modal", + "Nineteen", + "OctoAI", + "Recursal", + "Reflection", + "Replicate", + "SambaNova 2", + "SF Compute", + "Targon", + "Together 2", + "Ubicloud", + "01.AI", + "AkashML", "AI21", "AionLabs", "Alibaba", "Ambient", + "Baidu", "Amazon Bedrock", "Amazon Nova", "Anthropic", @@ -2648,26 +4919,37 @@ export const __schema5 = Schema.Array( "Clarifai", "Cloudflare", "Cohere", + "CoreWeave", + "Crucible", "Crusoe", + "Darkbloom", + "Decart", + "Deepgram", "DeepInfra", "DeepSeek", + "DekaLLM", + "DigitalOcean", "Featherless", "Fireworks", + "Fish Audio", "Friendli", "GMICloud", "Google", "Google AI Studio", "Groq", - "Hyperbolic", + "HeyGen", "Inception", "Inceptron", "InferenceNet", + "Ionstream", "Infermatic", "Io Net", + "Inferact vLLM", "Inflection", "Liquid", "Mara", "Mancer 2", + "Meta", "Minimax", "ModelRun", "Mistral", @@ -2676,15 +4958,22 @@ export const __schema5 = Schema.Array( "Morph", "NCompass", "Nebius", + "Nex AGI", "NextBit", "Novita", "Nvidia", "OpenAI", "OpenInference", "Parasail", + "Poolside", + "Perceptron", "Perplexity", "Phala", + "Recraft", + "Reka", "Relace", + "Sail Research", + "Sakana AI", "SambaNova", "Seed", "SiliconFlow", @@ -2693,6758 +4982,30620 @@ export const __schema5 = Schema.Array( "Stealth", "StreamLake", "Switchpoint", + "Tencent", + "Tenstorrent", "Together", "Upstage", "Venice", + "Wafer", "WandB", + "Quiver", + "Krea", + "Runway", "Xiaomi", "xAI", "Z.AI", "FakeProvider" - ]), - Schema.String - ]) -) -export type __schema11 = number -export const __schema11 = Schema.Number.check(Schema.isFinite()) -export type __schema13 = unknown -export const __schema13 = Schema.Unknown -export type __schema21 = string | null -export const __schema21 = Schema.Union([Schema.String, Schema.Null]) -export type __schema22 = - | "unknown" - | "openai-responses-v1" - | "azure-openai-responses-v1" - | "xai-responses-v1" - | "anthropic-claude-v1" - | "google-gemini-v1" - | null -export const __schema22 = Schema.Union([ - Schema.Literals([ - "unknown", - "openai-responses-v1", - "azure-openai-responses-v1", - "xai-responses-v1", - "anthropic-claude-v1", - "google-gemini-v1" - ]), - Schema.Null -]) -export type ModelName = string -export const ModelName = Schema.String -export type ChatMessageContentItemImage = { - readonly "type": "image_url" - readonly "image_url": { readonly "url": string; readonly "detail"?: "auto" | "low" | "high" } -} -export const ChatMessageContentItemImage = Schema.Struct({ - "type": Schema.Literal("image_url"), - "image_url": Schema.Struct({ - "url": Schema.String, - "detail": Schema.optionalKey(Schema.Literals(["auto", "low", "high"])) - }) -}) -export type ChatMessageContentItemAudio = { - readonly "type": "input_audio" - readonly "input_audio": { readonly "data": string; readonly "format": string } -} -export const ChatMessageContentItemAudio = Schema.Struct({ - "type": Schema.Literal("input_audio"), - "input_audio": Schema.Struct({ "data": Schema.String, "format": Schema.String }) + ]).annotate({ "description": "Name of the provider" }) + ), + "routed_service_tier": Schema.optionalKey( + Schema.Literals(["flex", "priority"]).annotate({ + "description": + "The service tier this request was routed to (e.g. flex, priority). The tier actually applied and billed is determined by the provider response and may differ." + }) + ), + "status": Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]) + .annotate({ "description": "HTTP status code from the provider" }) +}).annotate({ + "description": "Details of a provider response for a generation attempt", + "identifier": "ProviderResponse" }) -export type ChatMessageContentItemVideo = { - readonly "type": "input_video" - readonly "video_url": { readonly "url": string } -} | { readonly "type": "video_url"; readonly "video_url": { readonly "url": string } } -export const ChatMessageContentItemVideo = Schema.Union([ - Schema.Struct({ "type": Schema.Literal("input_video"), "video_url": Schema.Struct({ "url": Schema.String }) }), - Schema.Struct({ "type": Schema.Literal("video_url"), "video_url": Schema.Struct({ "url": Schema.String }) }) -], { mode: "oneOf" }) -export type ChatMessageToolCall = { - readonly "id": string - readonly "type": "function" - readonly "function": { readonly "name": string; readonly "arguments": string } -} -export const ChatMessageToolCall = Schema.Struct({ - "id": Schema.String, - "type": Schema.Literal("function"), - "function": Schema.Struct({ "name": Schema.String, "arguments": Schema.String }) +export type ProviderSort = "price" | "throughput" | "latency" | "exacto" +export const ProviderSort = Schema.Literals(["price", "throughput", "latency", "exacto"]).annotate({ + "description": "The provider sorting strategy (price, throughput, latency)", + "identifier": "ProviderSort" }) -export type ChatMessageTokenLogprob = { - readonly "token": string - readonly "logprob": number - readonly "bytes": ReadonlyArray | null - readonly "top_logprobs": ReadonlyArray< - { readonly "token": string; readonly "logprob": number; readonly "bytes": ReadonlyArray | null } - > +export type ProviderSortConfig = { + readonly "by"?: "price" | "throughput" | "latency" | "exacto" | null + readonly "partition"?: "model" | "none" | null } -export const ChatMessageTokenLogprob = Schema.Struct({ - "token": Schema.String, - "logprob": Schema.Number.check(Schema.isFinite()), - "bytes": Schema.Union([Schema.Array(Schema.Number.check(Schema.isFinite())), Schema.Null]), - "top_logprobs": Schema.Array( - Schema.Struct({ - "token": Schema.String, - "logprob": Schema.Number.check(Schema.isFinite()), - "bytes": Schema.Union([Schema.Array(Schema.Number.check(Schema.isFinite())), Schema.Null]) +export const ProviderSortConfig = Schema.Struct({ + "by": Schema.optionalKey( + Schema.Union([ + Schema.Literal("price"), + Schema.Literal("throughput"), + Schema.Literal("latency"), + Schema.Literal("exacto"), + Schema.Null + ]).annotate({ "description": "The provider sorting strategy (price, throughput, latency)" }) + ), + "partition": Schema.optionalKey( + Schema.Union([Schema.Literal("model"), Schema.Literal("none"), Schema.Null]).annotate({ + "description": + "Partitioning strategy for sorting: \"model\" (default) groups endpoints by model before sorting (fallback models remain fallbacks), \"none\" sorts all endpoints together regardless of model." }) ) +}).annotate({ + "description": "The provider sorting strategy (price, throughput, latency)", + "identifier": "ProviderSortConfig" }) -export type ChatGenerationTokenUsage = { - readonly "completion_tokens": number - readonly "prompt_tokens": number - readonly "total_tokens": number - readonly "completion_tokens_details"?: { - readonly "reasoning_tokens"?: number | null - readonly "audio_tokens"?: number | null - readonly "accepted_prediction_tokens"?: number | null - readonly "rejected_prediction_tokens"?: number | null - } | null - readonly "prompt_tokens_details"?: { - readonly "cached_tokens"?: number - readonly "cache_write_tokens"?: number - readonly "audio_tokens"?: number - readonly "video_tokens"?: number - } | null -} -export const ChatGenerationTokenUsage = Schema.Struct({ - "completion_tokens": Schema.Number.check(Schema.isFinite()), - "prompt_tokens": Schema.Number.check(Schema.isFinite()), - "total_tokens": Schema.Number.check(Schema.isFinite()), - "completion_tokens_details": Schema.optionalKey(Schema.Union([ - Schema.Struct({ - "reasoning_tokens": Schema.optionalKey(Schema.Union([Schema.Number.check(Schema.isFinite()), Schema.Null])), - "audio_tokens": Schema.optionalKey(Schema.Union([Schema.Number.check(Schema.isFinite()), Schema.Null])), - "accepted_prediction_tokens": Schema.optionalKey( - Schema.Union([Schema.Number.check(Schema.isFinite()), Schema.Null]) - ), - "rejected_prediction_tokens": Schema.optionalKey( - Schema.Union([Schema.Number.check(Schema.isFinite()), Schema.Null]) - ) - }), - Schema.Null - ])), - "prompt_tokens_details": Schema.optionalKey(Schema.Union([ - Schema.Struct({ - "cached_tokens": Schema.optionalKey(Schema.Number.check(Schema.isFinite())), - "cache_write_tokens": Schema.optionalKey(Schema.Number.check(Schema.isFinite())), - "audio_tokens": Schema.optionalKey(Schema.Number.check(Schema.isFinite())), - "video_tokens": Schema.optionalKey(Schema.Number.check(Schema.isFinite())) - }), - Schema.Null - ])) +export type Quantization = "int4" | "int8" | "fp4" | "fp6" | "fp8" | "fp16" | "bf16" | "fp32" | "unknown" +export const Quantization = Schema.Literals(["int4", "int8", "fp4", "fp6", "fp8", "fp16", "bf16", "fp32", "unknown"]) + .annotate({ "identifier": "Quantization" }) +export type RangeCapability = { readonly "max": number; readonly "min": number; readonly "type": "range" } +export const RangeCapability = Schema.Struct({ + "max": Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "min": Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "type": Schema.Literal("range") +}).annotate({ + "description": "A parameter that accepts any value within an inclusive numeric range.", + "identifier": "RangeCapability" }) -export type ChatCompletionFinishReason = "tool_calls" | "stop" | "length" | "content_filter" | "error" -export const ChatCompletionFinishReason = Schema.Literals(["tool_calls", "stop", "length", "content_filter", "error"]) -export type JSONSchemaConfig = { - readonly "name": string - readonly "description"?: string - readonly "schema"?: {} - readonly "strict"?: boolean | null -} -export const JSONSchemaConfig = Schema.Struct({ - "name": Schema.String.check(Schema.isMaxLength(64)), - "description": Schema.optionalKey(Schema.String), - "schema": Schema.optionalKey(Schema.Struct({}).check(Schema.isPropertyNames(Schema.String))), - "strict": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])) -}) -export type ResponseFormatTextGrammar = { readonly "type": "grammar"; readonly "grammar": string } -export const ResponseFormatTextGrammar = Schema.Struct({ "type": Schema.Literal("grammar"), "grammar": Schema.String }) -export type ChatMessageContentItemCacheControl = { readonly "type": "ephemeral"; readonly "ttl"?: "5m" | "1h" } -export const ChatMessageContentItemCacheControl = Schema.Struct({ - "type": Schema.Literal("ephemeral"), - "ttl": Schema.optionalKey(Schema.Literals(["5m", "1h"])) -}) -export type NamedToolChoice = { readonly "type": "function"; readonly "function": { readonly "name": string } } -export const NamedToolChoice = Schema.Struct({ - "type": Schema.Literal("function"), - "function": Schema.Struct({ "name": Schema.String }) -}) -export type ChatStreamOptions = { readonly "include_usage"?: boolean } -export const ChatStreamOptions = Schema.Struct({ "include_usage": Schema.optionalKey(Schema.Boolean) }) -export type ChatStreamingMessageToolCall = { - readonly "index": number - readonly "id"?: string | null - readonly "type"?: "function" | null - readonly "function"?: { readonly "name"?: string | null; readonly "arguments"?: string } +export type RankingsDailyItem = { + readonly "date": string + readonly "model_permaslug": string + readonly "total_tokens": string } -export const ChatStreamingMessageToolCall = Schema.Struct({ - "index": Schema.Number.check(Schema.isFinite()), - "id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - "type": Schema.optionalKey(Schema.Union([Schema.Literal("function"), Schema.Null])), - "function": Schema.optionalKey( - Schema.Struct({ - "name": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - "arguments": Schema.optionalKey(Schema.String) - }) - ) -}) -export type ChatError = { - readonly "error": { - readonly "code": string | number | null - readonly "message": string - readonly "param"?: string | null - readonly "type"?: string | null - } +export const RankingsDailyItem = Schema.Struct({ + "date": Schema.String.annotate({ "description": "UTC calendar date the row is aggregated over (YYYY-MM-DD)." }), + "model_permaslug": Schema.String.annotate({ + "description": + "Model variant permaslug (e.g. `openai/gpt-4o-2024-05-13`, `openai/gpt-4o-2024-05-13:free`). Non-default variants include a `:variant` suffix and are ranked as their own entry. The reserved value `other` denotes the aggregated row covering every model outside the daily top 50 for that date — always sorted last within its date." + }), + "total_tokens": Schema.String.annotate({ + "description": + "Sum of `prompt_tokens + completion_tokens` for the day, returned as a decimal string so 64-bit values are not truncated." + }) +}).annotate({ "identifier": "RankingsDailyItem" }) +export type RankingsDailyMeta = { + readonly "as_of": string + readonly "end_date": string + readonly "start_date": string + readonly "version": "v1" } -export const ChatError = Schema.Struct({ - "error": Schema.Struct({ - "code": Schema.Union([Schema.Union([Schema.String, Schema.Number.check(Schema.isFinite())]), Schema.Null]), - "message": Schema.String, - "param": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - "type": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) +export const RankingsDailyMeta = Schema.Struct({ + "as_of": Schema.String.annotate({ + "description": + "ISO-8601 timestamp of when the response was generated. Reflects data-freshness because the underlying materialized view continuously ingests upstream events." + }), + "end_date": Schema.String.annotate({ "description": "Resolved end of the date window (UTC, inclusive)." }), + "start_date": Schema.String.annotate({ "description": "Resolved start of the date window (UTC, inclusive)." }), + "version": Schema.Literal("v1").annotate({ + "description": "Dataset version. Field names and grain are stable for the life of `v1`." }) +}).annotate({ "identifier": "RankingsDailyMeta" }) +export type ReasoningContext = "auto" | "all_turns" | "current_turn" | null +export const ReasoningContext = Schema.Union([ + Schema.Literal("auto"), + Schema.Literal("all_turns"), + Schema.Literal("current_turn"), + Schema.Null +]).annotate({ + "description": + "Controls which reasoning is available to the model. `auto` uses the model default (same as omitting); `all_turns` includes reasoning from earlier turns passed in input; `current_turn` limits to the current turn only. Only supported by OpenAI GPT-5.6 and newer.", + "identifier": "ReasoningContext" }) -export type OpenAIResponsesAnnotation = FileCitation | URLCitation | FilePath -export const OpenAIResponsesAnnotation = Schema.Union([FileCitation, URLCitation, FilePath]) -export type OutputItemReasoning = { - readonly "type": "reasoning" - readonly "id": string - readonly "content"?: ReadonlyArray - readonly "summary": ReadonlyArray - readonly "encrypted_content"?: string - readonly "status"?: "completed" | "incomplete" | "in_progress" +export type ReasoningDeltaEvent = { + readonly "content_index": number + readonly "delta": string + readonly "item_id": string + readonly "output_index": number + readonly "sequence_number": number + readonly "type": "response.reasoning_text.delta" } -export const OutputItemReasoning = Schema.Struct({ - "type": Schema.Literal("reasoning"), - "id": Schema.String, - "content": Schema.optionalKey(Schema.Array(ReasoningTextContent)), - "summary": Schema.Array(ReasoningSummaryText), - "encrypted_content": Schema.optionalKey(Schema.String), - "status": Schema.optionalKey(Schema.Literals(["completed", "incomplete", "in_progress"])) +export const ReasoningDeltaEvent = Schema.Struct({ + "content_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "delta": Schema.String, + "item_id": Schema.String, + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.reasoning_text.delta") +}).annotate({ + "description": "Event emitted when reasoning text delta is streamed", + "identifier": "ReasoningDeltaEvent" }) -export type ResponsesOutputItemReasoning = { - readonly "type": "reasoning" - readonly "id": string - readonly "content"?: ReadonlyArray - readonly "summary": ReadonlyArray - readonly "encrypted_content"?: string - readonly "status"?: "completed" | "incomplete" | "in_progress" - readonly "signature"?: string - readonly "format"?: - | "unknown" - | "openai-responses-v1" - | "azure-openai-responses-v1" - | "xai-responses-v1" - | "anthropic-claude-v1" - | "google-gemini-v1" -} -export const ResponsesOutputItemReasoning = Schema.Struct({ - "type": Schema.Literal("reasoning"), - "id": Schema.String, - "content": Schema.optionalKey(Schema.Array(ReasoningTextContent)), - "summary": Schema.Array(ReasoningSummaryText), - "encrypted_content": Schema.optionalKey(Schema.String), - "status": Schema.optionalKey(Schema.Literals(["completed", "incomplete", "in_progress"])), - "signature": Schema.optionalKey( - Schema.String.annotate({ "description": "A signature for the reasoning content, used for verification" }) - ), - "format": Schema.optionalKey( - Schema.Literals([ - "unknown", - "openai-responses-v1", - "azure-openai-responses-v1", - "xai-responses-v1", - "anthropic-claude-v1", - "google-gemini-v1" - ]).annotate({ "description": "The format of the reasoning content" }) - ) -}).annotate({ "description": "An output item containing reasoning" }) -export type OpenResponsesReasoningSummaryPartAddedEvent = { - readonly "type": "response.reasoning_summary_part.added" +export type ReasoningDoneEvent = { + readonly "content_index": number + readonly "item_id": string readonly "output_index": number + readonly "sequence_number": number + readonly "text": string + readonly "type": "response.reasoning_text.done" +} +export const ReasoningDoneEvent = Schema.Struct({ + "content_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "item_id": Schema.String, + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "text": Schema.String, + "type": Schema.Literal("response.reasoning_text.done") +}).annotate({ + "description": "Event emitted when reasoning text streaming is complete", + "identifier": "ReasoningDoneEvent" +}) +export type ReasoningEffort = "max" | "xhigh" | "high" | "medium" | "low" | "minimal" | "none" | null +export const ReasoningEffort = Schema.Union([ + Schema.Literal("max"), + Schema.Literal("xhigh"), + Schema.Literal("high"), + Schema.Literal("medium"), + Schema.Literal("low"), + Schema.Literal("minimal"), + Schema.Literal("none"), + Schema.Null +]).annotate({ "identifier": "ReasoningEffort" }) +export type ReasoningFormat = + | "unknown" + | "openai-responses-v1" + | "azure-openai-responses-v1" + | "xai-responses-v1" + | "meta-responses-v1" + | "anthropic-claude-v1" + | "google-gemini-v1" + | null +export const ReasoningFormat = Schema.Union([ + Schema.Literal("unknown"), + Schema.Literal("openai-responses-v1"), + Schema.Literal("azure-openai-responses-v1"), + Schema.Literal("xai-responses-v1"), + Schema.Literal("meta-responses-v1"), + Schema.Literal("anthropic-claude-v1"), + Schema.Literal("google-gemini-v1"), + Schema.Null +]).annotate({ "identifier": "ReasoningFormat" }) +export type ReasoningMode = "standard" | "pro" | null +export const ReasoningMode = Schema.Union([Schema.Literal("standard"), Schema.Literal("pro"), Schema.Null]).annotate({ + "description": + "Selects the reasoning mode. `standard` is the default; `pro` engages deeper reasoning on models that support it, billed at standard token rates. Only supported by OpenAI GPT-5.6 and newer.", + "identifier": "ReasoningMode" +}) +export type ReasoningSummaryText = { readonly "text": string; readonly "type": "summary_text" } +export const ReasoningSummaryText = Schema.Struct({ "text": Schema.String, "type": Schema.Literal("summary_text") }) + .annotate({ "identifier": "ReasoningSummaryText" }) +export type ReasoningSummaryTextDeltaEvent = { + readonly "delta": string readonly "item_id": string + readonly "output_index": number + readonly "sequence_number": number readonly "summary_index": number - readonly "part": ReasoningSummaryText + readonly "type": "response.reasoning_summary_text.delta" +} +export const ReasoningSummaryTextDeltaEvent = Schema.Struct({ + "delta": Schema.String, + "item_id": Schema.String, + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "summary_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.reasoning_summary_text.delta") +}).annotate({ + "description": "Event emitted when reasoning summary text delta is streamed", + "identifier": "ReasoningSummaryTextDeltaEvent" +}) +export type ReasoningSummaryTextDoneEvent = { + readonly "item_id": string + readonly "output_index": number readonly "sequence_number": number + readonly "summary_index": number + readonly "text": string + readonly "type": "response.reasoning_summary_text.done" } -export const OpenResponsesReasoningSummaryPartAddedEvent = Schema.Struct({ - "type": Schema.Literal("response.reasoning_summary_part.added"), - "output_index": Schema.Number.check(Schema.isFinite()), +export const ReasoningSummaryTextDoneEvent = Schema.Struct({ "item_id": Schema.String, - "summary_index": Schema.Number.check(Schema.isFinite()), - "part": ReasoningSummaryText, - "sequence_number": Schema.Number.check(Schema.isFinite()) -}).annotate({ "description": "Event emitted when a reasoning summary part is added" }) -export type OpenResponsesReasoningSummaryPartDoneEvent = { - readonly "type": "response.reasoning_summary_part.done" + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "summary_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "text": Schema.String, + "type": Schema.Literal("response.reasoning_summary_text.done") +}).annotate({ + "description": "Event emitted when reasoning summary text streaming is complete", + "identifier": "ReasoningSummaryTextDoneEvent" +}) +export type ReasoningSummaryVerbosity = "auto" | "concise" | "detailed" | null +export const ReasoningSummaryVerbosity = Schema.Union([ + Schema.Literal("auto"), + Schema.Literal("concise"), + Schema.Literal("detailed"), + Schema.Null +]).annotate({ "identifier": "ReasoningSummaryVerbosity" }) +export type ReasoningTextContent = { readonly "text": string; readonly "type": "reasoning_text" } +export const ReasoningTextContent = Schema.Struct({ "text": Schema.String, "type": Schema.Literal("reasoning_text") }) + .annotate({ "identifier": "ReasoningTextContent" }) +export type RefusalDeltaEvent = { + readonly "content_index": number + readonly "delta": string + readonly "item_id": string readonly "output_index": number + readonly "sequence_number": number + readonly "type": "response.refusal.delta" +} +export const RefusalDeltaEvent = Schema.Struct({ + "content_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "delta": Schema.String, + "item_id": Schema.String, + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.refusal.delta") +}).annotate({ "description": "Event emitted when a refusal delta is streamed", "identifier": "RefusalDeltaEvent" }) +export type RefusalDoneEvent = { + readonly "content_index": number readonly "item_id": string - readonly "summary_index": number - readonly "part": ReasoningSummaryText + readonly "output_index": number + readonly "refusal": string readonly "sequence_number": number + readonly "type": "response.refusal.done" } -export const OpenResponsesReasoningSummaryPartDoneEvent = Schema.Struct({ - "type": Schema.Literal("response.reasoning_summary_part.done"), - "output_index": Schema.Number.check(Schema.isFinite()), +export const RefusalDoneEvent = Schema.Struct({ + "content_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), "item_id": Schema.String, - "summary_index": Schema.Number.check(Schema.isFinite()), - "part": ReasoningSummaryText, - "sequence_number": Schema.Number.check(Schema.isFinite()) -}).annotate({ "description": "Event emitted when a reasoning summary part is complete" }) -export type OpenResponsesReasoning = { - readonly "type": "reasoning" - readonly "id": string - readonly "content"?: ReadonlyArray - readonly "summary": ReadonlyArray - readonly "encrypted_content"?: string - readonly "status"?: "completed" | "incomplete" | "in_progress" - readonly "signature"?: string - readonly "format"?: - | "unknown" - | "openai-responses-v1" - | "azure-openai-responses-v1" - | "xai-responses-v1" - | "anthropic-claude-v1" - | "google-gemini-v1" -} -export const OpenResponsesReasoning = Schema.Struct({ - "type": Schema.Literal("reasoning"), - "id": Schema.String, - "content": Schema.optionalKey(Schema.Array(ReasoningTextContent)), - "summary": Schema.Array(ReasoningSummaryText), - "encrypted_content": Schema.optionalKey(Schema.String), - "status": Schema.optionalKey(Schema.Literals(["completed", "incomplete", "in_progress"])), - "signature": Schema.optionalKey(Schema.String), - "format": Schema.optionalKey( - Schema.Literals([ - "unknown", - "openai-responses-v1", - "azure-openai-responses-v1", - "xai-responses-v1", - "anthropic-claude-v1", - "google-gemini-v1" - ]) + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "refusal": Schema.String, + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.refusal.done") +}).annotate({ "description": "Event emitted when refusal streaming is complete", "identifier": "RefusalDoneEvent" }) +export type Objects_149 = { readonly [x: string]: string } +export const Objects_149 = Schema.Record( + Schema.String, + Schema.String.check(Schema.isMaxLength(512).annotate({ "expected": "a value with a length of at most 512" })) +) +export type RequestTimeoutResponseErrorData = { + readonly "code": number + readonly "message": string + readonly "metadata"?: { readonly [x: string]: Schema.Json } | null +} +export const RequestTimeoutResponseErrorData = Schema.Struct({ + "code": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "message": Schema.String, + "metadata": Schema.optionalKey( + Schema.Union([Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })), Schema.Null]) ) -}).annotate({ "description": "Reasoning output item with signature and format extensions" }) -export type OutputItemWebSearchCall = { - readonly "type": "web_search_call" - readonly "id": string - readonly "status": WebSearchStatus +}).annotate({ "description": "Error data for RequestTimeoutResponse", "identifier": "RequestTimeoutResponseErrorData" }) +export type ResponseHealingPlugin = { readonly "enabled"?: boolean; readonly "id": "response-healing" } +export const ResponseHealingPlugin = Schema.Struct({ + "enabled": Schema.optionalKey( + Schema.Boolean.annotate({ + "description": "Set to false to disable the response-healing plugin for this request. Defaults to true." + }) + ), + "id": Schema.Literal("response-healing") +}).annotate({ "identifier": "ResponseHealingPlugin" }) +export type ResponseIncludesEnum = + | "file_search_call.results" + | "message.input_image.image_url" + | "computer_call_output.output.image_url" + | "reasoning.encrypted_content" + | "code_interpreter_call.outputs" +export const ResponseIncludesEnum = Schema.Literals([ + "file_search_call.results", + "message.input_image.image_url", + "computer_call_output.output.image_url", + "reasoning.encrypted_content", + "code_interpreter_call.outputs" +]).annotate({ "identifier": "ResponseIncludesEnum" }) +export type ResponsesErrorField = { + readonly "code": + | "server_error" + | "rate_limit_exceeded" + | "invalid_prompt" + | "vector_store_timeout" + | "invalid_image" + | "invalid_image_format" + | "invalid_base64_image" + | "invalid_image_url" + | "image_too_large" + | "image_too_small" + | "image_parse_error" + | "image_content_policy_violation" + | "invalid_image_mode" + | "image_file_too_large" + | "unsupported_image_media_type" + | "empty_image_file" + | "failed_to_download_image" + | "image_file_not_found" + | "bio_policy" + readonly "message": string + readonly [x: string]: Schema.Json +} | null +export const ResponsesErrorField = Schema.Union([ + Schema.StructWithRest( + Schema.Struct({ + "code": Schema.Literals([ + "server_error", + "rate_limit_exceeded", + "invalid_prompt", + "vector_store_timeout", + "invalid_image", + "invalid_image_format", + "invalid_base64_image", + "invalid_image_url", + "image_too_large", + "image_too_small", + "image_parse_error", + "image_content_policy_violation", + "invalid_image_mode", + "image_file_too_large", + "unsupported_image_media_type", + "empty_image_file", + "failed_to_download_image", + "image_file_not_found", + "bio_policy" + ]), + "message": Schema.String + }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] + ), + Schema.Null +]).annotate({ "description": "Error information returned from the API", "identifier": "ResponsesErrorField" }) +export type RouterAttempt = { readonly "model": string; readonly "provider": string; readonly "status": number } +export const RouterAttempt = Schema.Struct({ + "model": Schema.String, + "provider": Schema.String, + "status": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) +}).annotate({ "identifier": "RouterAttempt" }) +export type RouterParams = { + readonly "quality_floor"?: number + readonly "throughput_floor"?: number + readonly "version_group"?: string } -export const OutputItemWebSearchCall = Schema.Struct({ - "type": Schema.Literal("web_search_call"), - "id": Schema.String, - "status": WebSearchStatus +export const RouterParams = Schema.Struct({ + "quality_floor": Schema.optionalKey( + Schema.Number.annotate({ "format": "double" }).check(Schema.isFinite().annotate({ "expected": "a finite number" })) + ), + "throughput_floor": Schema.optionalKey( + Schema.Number.annotate({ "format": "double" }).check(Schema.isFinite().annotate({ "expected": "a finite number" })) + ), + "version_group": Schema.optionalKey(Schema.String) +}).annotate({ "identifier": "RouterParams" }) +export type RoutingStrategy = + | "direct" + | "auto" + | "free" + | "latest" + | "alias" + | "fallback" + | "pareto" + | "bodybuilder" + | "fusion" +export const RoutingStrategy = Schema.Literals([ + "direct", + "auto", + "free", + "latest", + "alias", + "fallback", + "pareto", + "bodybuilder", + "fusion" +]).annotate({ "identifier": "RoutingStrategy" }) +export type SandboxSleepAfterSeconds = number +export const SandboxSleepAfterSeconds = Schema.Number.annotate({ + "description": + "How long (in seconds) the container stays warm after its last command before sleeping, freeing its capacity slot. Idle-based: each command renews the timer. Defaults to 900 (15 minutes); capped at 2592000 (30 days)." +}).check(Schema.isInt().annotate({ "expected": "an integer", "identifier": "SandboxSleepAfterSeconds" })) +export type SearchContextSizeEnum = "low" | "medium" | "high" +export const SearchContextSizeEnum = Schema.Literals(["low", "medium", "high"]).annotate({ + "description": "Size of the search context for web search tools", + "identifier": "SearchContextSizeEnum" }) -export type ResponsesWebSearchCallOutput = { - readonly "type": "web_search_call" - readonly "id": string - readonly "status": WebSearchStatus -} -export const ResponsesWebSearchCallOutput = Schema.Struct({ - "type": Schema.Literal("web_search_call"), - "id": Schema.String, - "status": WebSearchStatus +export type SearchModelsServerToolConfig = { readonly "max_results"?: number } +export const SearchModelsServerToolConfig = Schema.Struct({ + "max_results": Schema.optionalKey( + Schema.Number.annotate({ "description": "Maximum number of models to return. Defaults to 5, max 20." }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ) + ) +}).annotate({ + "description": "Configuration for the openrouter:experimental__search_models server tool", + "identifier": "SearchModelsServerToolConfig" }) -export type OutputItemFileSearchCall = { - readonly "type": "file_search_call" - readonly "id": string - readonly "queries": ReadonlyArray - readonly "status": WebSearchStatus -} -export const OutputItemFileSearchCall = Schema.Struct({ - "type": Schema.Literal("file_search_call"), - "id": Schema.String, - "queries": Schema.Array(Schema.String), - "status": WebSearchStatus +export type SearchQualityLevel = "low" | "medium" | "high" +export const SearchQualityLevel = Schema.Literals(["low", "medium", "high"]).annotate({ + "description": + "How much context to retrieve per result. Applies to Exa, Parallel, and Perplexity engines; ignored with native provider search and Firecrawl. For Exa, pins a fixed per-result character cap (low=5,000, medium=15,000, high=30,000); when omitted, Exa picks an adaptive size per query and document (typically ~2,000–4,000 characters per result). For Parallel, controls the total characters across all results; when omitted, Parallel uses its own default size. For Perplexity, maps directly to the Search API's native search_context_size parameter. Overridden by `max_characters` when both are set.", + "identifier": "SearchQualityLevel" }) -export type ResponsesOutputItemFileSearchCall = { - readonly "type": "file_search_call" - readonly "id": string - readonly "queries": ReadonlyArray - readonly "status": WebSearchStatus +export type Objects_150 = { + readonly "tool_calls_executed"?: number | null + readonly "tool_calls_requested"?: number | null + readonly "web_search_requests"?: number | null + readonly [x: string]: Schema.Json } -export const ResponsesOutputItemFileSearchCall = Schema.Struct({ - "type": Schema.Literal("file_search_call"), - "id": Schema.String, - "queries": Schema.Array(Schema.String), - "status": WebSearchStatus +export const Objects_150 = Schema.StructWithRest( + Schema.Struct({ + "tool_calls_executed": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]).annotate({ + "description": "Number of OpenRouter server tool calls that executed and produced a result." + }) + ), + "tool_calls_requested": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]).annotate({ + "description": + "Total number of OpenRouter server-orchestrated tool calls the model requested, across all tool types. Provider-native tools (e.g. native web search) are not counted here." + }) + ), + "web_search_requests": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]).annotate({ + "description": + "Number of web searches performed by server-side tools. For server-orchestrated tool calls a web search is also counted in tool_calls_requested; provider-native web search may report web_search_requests only. Do not sum the two." + }) + ) + }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] +) +export type ServiceTier = "auto" | "default" | "flex" | "priority" | "scale" | null +export const ServiceTier = Schema.Union([ + Schema.Literal("auto"), + Schema.Literal("default"), + Schema.Literal("flex"), + Schema.Literal("priority"), + Schema.Literal("scale"), + Schema.Null +]).annotate({ "identifier": "ServiceTier" }) +export type ServiceUnavailableResponseErrorData = { + readonly "code": number + readonly "message": string + readonly "metadata"?: { readonly [x: string]: Schema.Json } | null +} +export const ServiceUnavailableResponseErrorData = Schema.Struct({ + "code": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "message": Schema.String, + "metadata": Schema.optionalKey( + Schema.Union([Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })), Schema.Null]) + ) +}).annotate({ + "description": "Error data for ServiceUnavailableResponse", + "identifier": "ServiceUnavailableResponseErrorData" }) -export type OutputItemImageGenerationCall = { - readonly "type": "image_generation_call" - readonly "id": string - readonly "result"?: string - readonly "status": ImageGenerationStatus -} -export const OutputItemImageGenerationCall = Schema.Struct({ - "type": Schema.Literal("image_generation_call"), - "id": Schema.String, - "result": Schema.optionalKey(Schema.String), - "status": ImageGenerationStatus +export type ShellCallStatus = "in_progress" | "completed" | "incomplete" +export const ShellCallStatus = Schema.Literals(["in_progress", "completed", "incomplete"]).annotate({ + "description": "Status of a shell call or its output.", + "identifier": "ShellCallStatus" }) -export type ResponsesImageGenerationCall = { - readonly "type": "image_generation_call" - readonly "id": string - readonly "result"?: string - readonly "status": ImageGenerationStatus -} -export const ResponsesImageGenerationCall = Schema.Struct({ - "type": Schema.Literal("image_generation_call"), - "id": Schema.String, - "result": Schema.optionalKey(Schema.String), - "status": ImageGenerationStatus +export type ShellServerTool = { readonly "type": "shell" } +export const ShellServerTool = Schema.Struct({ "type": Schema.Literal("shell") }).annotate({ + "description": "Shell tool configuration", + "identifier": "ShellServerTool" }) -export type OpenResponsesFunctionToolCall = { - readonly "type": "function_call" - readonly "call_id": string - readonly "name": string - readonly "arguments": string - readonly "id": string - readonly "status"?: ToolCallStatus -} -export const OpenResponsesFunctionToolCall = Schema.Struct({ - "type": Schema.Literal("function_call"), - "call_id": Schema.String, - "name": Schema.String, - "arguments": Schema.String, - "id": Schema.String, - "status": Schema.optionalKey(ToolCallStatus) -}).annotate({ "description": "A function call initiated by the model" }) -export type OpenResponsesFunctionCallOutput = { - readonly "type": "function_call_output" - readonly "id"?: string - readonly "call_id": string - readonly "output": string - readonly "status"?: ToolCallStatus -} -export const OpenResponsesFunctionCallOutput = Schema.Struct({ - "type": Schema.Literal("function_call_output"), - "id": Schema.optionalKey(Schema.String), - "call_id": Schema.String, - "output": Schema.String, - "status": Schema.optionalKey(ToolCallStatus) -}).annotate({ "description": "The output from a function call execution" }) -export type OpenResponsesWebSearchPreviewTool = { - readonly "type": "web_search_preview" - readonly "search_context_size"?: ResponsesSearchContextSize - readonly "user_location"?: WebSearchPreviewToolUserLocation -} -export const OpenResponsesWebSearchPreviewTool = Schema.Struct({ - "type": Schema.Literal("web_search_preview"), - "search_context_size": Schema.optionalKey(ResponsesSearchContextSize), - "user_location": Schema.optionalKey(WebSearchPreviewToolUserLocation) -}).annotate({ "description": "Web search preview tool configuration" }) -export type OpenResponsesWebSearchPreview20250311Tool = { - readonly "type": "web_search_preview_2025_03_11" - readonly "search_context_size"?: ResponsesSearchContextSize - readonly "user_location"?: WebSearchPreviewToolUserLocation -} -export const OpenResponsesWebSearchPreview20250311Tool = Schema.Struct({ - "type": Schema.Literal("web_search_preview_2025_03_11"), - "search_context_size": Schema.optionalKey(ResponsesSearchContextSize), - "user_location": Schema.optionalKey(WebSearchPreviewToolUserLocation) -}).annotate({ "description": "Web search preview tool configuration (2025-03-11 version)" }) -export type OpenResponsesWebSearchTool = { - readonly "type": "web_search" - readonly "filters"?: { readonly "allowed_domains"?: ReadonlyArray } - readonly "search_context_size"?: ResponsesSearchContextSize - readonly "user_location"?: ResponsesWebSearchUserLocation -} -export const OpenResponsesWebSearchTool = Schema.Struct({ - "type": Schema.Literal("web_search"), - "filters": Schema.optionalKey(Schema.Struct({ "allowed_domains": Schema.optionalKey(Schema.Array(Schema.String)) })), - "search_context_size": Schema.optionalKey(ResponsesSearchContextSize), - "user_location": Schema.optionalKey(ResponsesWebSearchUserLocation) -}).annotate({ "description": "Web search tool configuration" }) -export type OpenResponsesWebSearch20250826Tool = { - readonly "type": "web_search_2025_08_26" - readonly "filters"?: { readonly "allowed_domains"?: ReadonlyArray } - readonly "search_context_size"?: ResponsesSearchContextSize - readonly "user_location"?: ResponsesWebSearchUserLocation -} -export const OpenResponsesWebSearch20250826Tool = Schema.Struct({ - "type": Schema.Literal("web_search_2025_08_26"), - "filters": Schema.optionalKey(Schema.Struct({ "allowed_domains": Schema.optionalKey(Schema.Array(Schema.String)) })), - "search_context_size": Schema.optionalKey(ResponsesSearchContextSize), - "user_location": Schema.optionalKey(ResponsesWebSearchUserLocation) -}).annotate({ "description": "Web search tool configuration (2025-08-26 version)" }) -export type OpenAIResponsesReasoningConfig = { - readonly "effort"?: OpenAIResponsesReasoningEffort - readonly "summary"?: ReasoningSummaryVerbosity -} -export const OpenAIResponsesReasoningConfig = Schema.Struct({ - "effort": Schema.optionalKey(OpenAIResponsesReasoningEffort), - "summary": Schema.optionalKey(ReasoningSummaryVerbosity) +export type ShellServerToolEngine = "auto" | "openrouter" +export const ShellServerToolEngine = Schema.Literals(["auto", "openrouter"]).annotate({ + "description": + "Which shell engine to use. \"openrouter\" runs commands server-side in the OpenRouter sandbox. \"auto\" (default) keeps the provider's native hosted shell when available (OpenAI); on other providers the call is routed to the OpenRouter sandbox.", + "identifier": "ShellServerToolEngine" }) -export type OpenResponsesReasoningConfig = { - readonly "effort"?: OpenAIResponsesReasoningEffort - readonly "summary"?: ReasoningSummaryVerbosity - readonly "max_tokens"?: number - readonly "enabled"?: boolean -} -export const OpenResponsesReasoningConfig = Schema.Struct({ - "effort": Schema.optionalKey(OpenAIResponsesReasoningEffort), - "summary": Schema.optionalKey(ReasoningSummaryVerbosity), - "max_tokens": Schema.optionalKey(Schema.Number.check(Schema.isFinite())), - "enabled": Schema.optionalKey(Schema.Boolean) -}).annotate({ "description": "Configuration for reasoning mode in the response" }) -export type ResponseFormatTextConfig = - | ResponsesFormatText - | ResponsesFormatJSONObject - | ResponsesFormatTextJSONSchemaConfig -export const ResponseFormatTextConfig = Schema.Union([ - ResponsesFormatText, - ResponsesFormatJSONObject, - ResponsesFormatTextJSONSchemaConfig -]).annotate({ "description": "Text response format configuration" }) -export type OpenResponsesLogProbs = { +export type StopServerToolsWhenFinishReasonIs = { readonly "reason": string; readonly "type": "finish_reason_is" } +export const StopServerToolsWhenFinishReasonIs = Schema.Struct({ + "reason": Schema.String.check(Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" })), + "type": Schema.Literal("finish_reason_is") +}).annotate({ + "description": "Stop when the upstream model emits this finish reason (e.g. `length`).", + "identifier": "StopServerToolsWhenFinishReasonIs" +}) +export type StopServerToolsWhenHasToolCall = { readonly "tool_name": string; readonly "type": "has_tool_call" } +export const StopServerToolsWhenHasToolCall = Schema.Struct({ + "tool_name": Schema.String.check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ), + "type": Schema.Literal("has_tool_call") +}).annotate({ + "description": "Stop after a tool with this name has been called.", + "identifier": "StopServerToolsWhenHasToolCall" +}) +export type StopServerToolsWhenMaxCost = { readonly "max_cost_in_dollars": number; readonly "type": "max_cost" } +export const StopServerToolsWhenMaxCost = Schema.Struct({ + "max_cost_in_dollars": Schema.Number.annotate({ "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ), + "type": Schema.Literal("max_cost") +}).annotate({ + "description": "Stop once cumulative cost across the loop exceeds this dollar threshold.", + "identifier": "StopServerToolsWhenMaxCost" +}) +export type StopServerToolsWhenMaxTokensUsed = { readonly "max_tokens": number; readonly "type": "max_tokens_used" } +export const StopServerToolsWhenMaxTokensUsed = Schema.Struct({ + "max_tokens": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("max_tokens_used") +}).annotate({ + "description": "Stop once cumulative token usage across the loop exceeds this threshold.", + "identifier": "StopServerToolsWhenMaxTokensUsed" +}) +export type StopServerToolsWhenStepCountIs = { readonly "step_count": number; readonly "type": "step_count_is" } +export const StopServerToolsWhenStepCountIs = Schema.Struct({ + "step_count": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("step_count_is") +}).annotate({ + "description": "Stop after the agent loop has executed this many steps.", + "identifier": "StopServerToolsWhenStepCountIs" +}) +export type StreamLogprob = { + readonly "bytes"?: ReadonlyArray readonly "logprob": number readonly "token": string - readonly "top_logprobs"?: ReadonlyArray + readonly "top_logprobs"?: ReadonlyArray< + { readonly "bytes"?: ReadonlyArray; readonly "logprob"?: number; readonly "token"?: string } + > } -export const OpenResponsesLogProbs = Schema.Struct({ - "logprob": Schema.Number.check(Schema.isFinite()), +export const StreamLogprob = Schema.Struct({ + "bytes": Schema.optionalKey(Schema.Array(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })))), + "logprob": Schema.Number.annotate({ "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ), "token": Schema.String, - "top_logprobs": Schema.optionalKey(Schema.Array(OpenResponsesTopLogprobs)) -}).annotate({ "description": "Log probability information for a token" }) -export type BadRequestResponse = { readonly "error": BadRequestResponseErrorData; readonly "user_id"?: string } -export const BadRequestResponse = Schema.Struct({ - "error": BadRequestResponseErrorData, - "user_id": Schema.optionalKey(Schema.String) -}).annotate({ "description": "Bad Request - Invalid request parameters or malformed input" }) -export type UnauthorizedResponse = { readonly "error": UnauthorizedResponseErrorData; readonly "user_id"?: string } -export const UnauthorizedResponse = Schema.Struct({ - "error": UnauthorizedResponseErrorData, - "user_id": Schema.optionalKey(Schema.String) -}).annotate({ "description": "Unauthorized - Authentication required or invalid credentials" }) -export type PaymentRequiredResponse = { - readonly "error": PaymentRequiredResponseErrorData - readonly "user_id"?: string -} -export const PaymentRequiredResponse = Schema.Struct({ - "error": PaymentRequiredResponseErrorData, - "user_id": Schema.optionalKey(Schema.String) -}).annotate({ "description": "Payment Required - Insufficient credits or quota to complete request" }) -export type NotFoundResponse = { readonly "error": NotFoundResponseErrorData; readonly "user_id"?: string } -export const NotFoundResponse = Schema.Struct({ - "error": NotFoundResponseErrorData, - "user_id": Schema.optionalKey(Schema.String) -}).annotate({ "description": "Not Found - Resource does not exist" }) -export type RequestTimeoutResponse = { readonly "error": RequestTimeoutResponseErrorData; readonly "user_id"?: string } -export const RequestTimeoutResponse = Schema.Struct({ - "error": RequestTimeoutResponseErrorData, - "user_id": Schema.optionalKey(Schema.String) -}).annotate({ "description": "Request Timeout - Operation exceeded time limit" }) -export type PayloadTooLargeResponse = { - readonly "error": PayloadTooLargeResponseErrorData - readonly "user_id"?: string -} -export const PayloadTooLargeResponse = Schema.Struct({ - "error": PayloadTooLargeResponseErrorData, - "user_id": Schema.optionalKey(Schema.String) -}).annotate({ "description": "Payload Too Large - Request payload exceeds size limits" }) -export type UnprocessableEntityResponse = { - readonly "error": UnprocessableEntityResponseErrorData - readonly "user_id"?: string -} -export const UnprocessableEntityResponse = Schema.Struct({ - "error": UnprocessableEntityResponseErrorData, - "user_id": Schema.optionalKey(Schema.String) -}).annotate({ "description": "Unprocessable Entity - Semantic validation failure" }) -export type TooManyRequestsResponse = { - readonly "error": TooManyRequestsResponseErrorData - readonly "user_id"?: string -} -export const TooManyRequestsResponse = Schema.Struct({ - "error": TooManyRequestsResponseErrorData, - "user_id": Schema.optionalKey(Schema.String) -}).annotate({ "description": "Too Many Requests - Rate limit exceeded" }) -export type InternalServerResponse = { readonly "error": InternalServerResponseErrorData; readonly "user_id"?: string } -export const InternalServerResponse = Schema.Struct({ - "error": InternalServerResponseErrorData, - "user_id": Schema.optionalKey(Schema.String) -}).annotate({ "description": "Internal Server Error - Unexpected server error" }) -export type BadGatewayResponse = { readonly "error": BadGatewayResponseErrorData; readonly "user_id"?: string } -export const BadGatewayResponse = Schema.Struct({ - "error": BadGatewayResponseErrorData, - "user_id": Schema.optionalKey(Schema.String) -}).annotate({ "description": "Bad Gateway - Provider/upstream API failure" }) -export type ServiceUnavailableResponse = { - readonly "error": ServiceUnavailableResponseErrorData - readonly "user_id"?: string + "top_logprobs": Schema.optionalKey(Schema.Array( + Schema.Struct({ + "bytes": Schema.optionalKey( + Schema.Array(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))) + ), + "logprob": Schema.optionalKey( + Schema.Number.annotate({ "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ) + ), + "token": Schema.optionalKey(Schema.String) + }).annotate({ "description": "Alternative token with its log probability" }) + )) +}).annotate({ "description": "Log probability information for a token", "identifier": "StreamLogprob" }) +export type StreamLogprobTopLogprob = { + readonly "bytes"?: ReadonlyArray + readonly "logprob"?: number + readonly "token"?: string } -export const ServiceUnavailableResponse = Schema.Struct({ - "error": ServiceUnavailableResponseErrorData, - "user_id": Schema.optionalKey(Schema.String) -}).annotate({ "description": "Service Unavailable - Service temporarily unavailable" }) -export type EdgeNetworkTimeoutResponse = { - readonly "error": EdgeNetworkTimeoutResponseErrorData - readonly "user_id"?: string +export const StreamLogprobTopLogprob = Schema.Struct({ + "bytes": Schema.optionalKey(Schema.Array(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })))), + "logprob": Schema.optionalKey( + Schema.Number.annotate({ "format": "double" }).check(Schema.isFinite().annotate({ "expected": "a finite number" })) + ), + "token": Schema.optionalKey(Schema.String) +}).annotate({ "description": "Alternative token with its log probability", "identifier": "StreamLogprobTopLogprob" }) +export type STTInputAudio = { readonly "data": string; readonly "format": string } +export const STTInputAudio = Schema.Struct({ + "data": Schema.String.annotate({ "description": "Base64-encoded audio data (raw bytes, not a data URI)" }), + "format": Schema.String.annotate({ + "description": "Audio format (e.g., wav, mp3, flac, m4a, ogg, webm, aac). Supported formats vary by provider." + }) +}).annotate({ "description": "Base64-encoded audio to transcribe", "identifier": "STTInputAudio" }) +export type STTSegment = { + readonly "avg_logprob"?: number + readonly "compression_ratio"?: number + readonly "end": number + readonly "id": number + readonly "no_speech_prob"?: number + readonly "seek"?: number + readonly "start": number + readonly "temperature"?: number + readonly "text": string + readonly "tokens"?: ReadonlyArray } -export const EdgeNetworkTimeoutResponse = Schema.Struct({ - "error": EdgeNetworkTimeoutResponseErrorData, - "user_id": Schema.optionalKey(Schema.String) -}).annotate({ "description": "Infrastructure Timeout - Provider request timed out at edge network" }) -export type ProviderOverloadedResponse = { - readonly "error": ProviderOverloadedResponseErrorData - readonly "user_id"?: string +export const STTSegment = Schema.Struct({ + "avg_logprob": Schema.optionalKey( + Schema.Number.annotate({ "description": "Average log probability of the segment", "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ) + ), + "compression_ratio": Schema.optionalKey( + Schema.Number.annotate({ "description": "Compression ratio of the segment", "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ) + ), + "end": Schema.Number.annotate({ "description": "Segment end time in seconds", "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ), + "id": Schema.Number.annotate({ "description": "Segment index within the transcript" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ), + "no_speech_prob": Schema.optionalKey( + Schema.Number.annotate({ "description": "Probability the segment contains no speech", "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ) + ), + "seek": Schema.optionalKey( + Schema.Number.annotate({ "description": "Seek offset of the segment" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ) + ), + "start": Schema.Number.annotate({ "description": "Segment start time in seconds", "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ), + "temperature": Schema.optionalKey( + Schema.Number.annotate({ "description": "Temperature used for the segment", "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ) + ), + "text": Schema.String.annotate({ "description": "Transcribed text of the segment" }), + "tokens": Schema.optionalKey( + Schema.Array(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))).annotate({ + "description": "Token IDs of the segment" + }) + ) +}).annotate({ + "description": "A timestamped transcript segment, returned when response_format is verbose_json", + "identifier": "STTSegment" +}) +export type STTTimestampGranularity = "word" | "segment" +export const STTTimestampGranularity = Schema.Literals(["word", "segment"]).annotate({ + "description": "A timestamp detail level for verbose_json transcription responses.", + "identifier": "STTTimestampGranularity" +}) +export type STTUsage = { + readonly "cost"?: number + readonly "input_tokens"?: number + readonly "output_tokens"?: number + readonly "seconds"?: number + readonly "total_tokens"?: number } -export const ProviderOverloadedResponse = Schema.Struct({ - "error": ProviderOverloadedResponseErrorData, - "user_id": Schema.optionalKey(Schema.String) -}).annotate({ "description": "Provider Overloaded - Provider is temporarily overloaded" }) -export type OpenResponsesEasyInputMessage = { - readonly "type"?: "message" - readonly "role": "user" | "system" | "assistant" | "developer" - readonly "content": - | ReadonlyArray< - | ResponseInputText - | { readonly "type": "input_image"; readonly "detail": "auto" | "high" | "low"; readonly "image_url"?: string } - | ResponseInputFile - | ResponseInputAudio - | ResponseInputVideo - > - | string +export const STTUsage = Schema.Struct({ + "cost": Schema.optionalKey( + Schema.Number.annotate({ "description": "Total cost of the request in USD", "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ) + ), + "input_tokens": Schema.optionalKey( + Schema.Number.annotate({ "description": "Number of input tokens billed for this request" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ) + ), + "output_tokens": Schema.optionalKey( + Schema.Number.annotate({ "description": "Number of output tokens generated" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ) + ), + "seconds": Schema.optionalKey( + Schema.Number.annotate({ "description": "Duration of the input audio in seconds", "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ) + ), + "total_tokens": Schema.optionalKey( + Schema.Number.annotate({ "description": "Total number of tokens used (input + output)" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ) + ) +}).annotate({ "description": "Aggregated usage statistics for the request", "identifier": "STTUsage" }) +export type STTWord = { + readonly "end": number + readonly "speaker"?: number + readonly "start": number + readonly "word": string } -export const OpenResponsesEasyInputMessage = Schema.Struct({ - "type": Schema.optionalKey(Schema.Literal("message")), - "role": Schema.Literals(["user", "system", "assistant", "developer"]), - "content": Schema.Union([ - Schema.Array( - Schema.Union([ - ResponseInputText, - Schema.Struct({ - "type": Schema.Literal("input_image"), - "detail": Schema.Literals(["auto", "high", "low"]), - "image_url": Schema.optionalKey(Schema.String) - }).annotate({ "description": "Image input content item" }), - ResponseInputFile, - ResponseInputAudio, - ResponseInputVideo - ], { mode: "oneOf" }) - ), - Schema.String - ]) +export const STTWord = Schema.Struct({ + "end": Schema.Number.annotate({ "description": "Word end time in seconds", "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ), + "speaker": Schema.optionalKey( + Schema.Number.annotate({ + "description": "Zero-based speaker index for the word, present when the provider returns speaker diarization" + }).check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "start": Schema.Number.annotate({ "description": "Word start time in seconds", "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ), + "word": Schema.String.annotate({ "description": "The transcribed word" }) +}).annotate({ + "description": "A timestamped word, returned when the provider includes word-level timestamps", + "identifier": "STTWord" }) -export type OpenResponsesInputMessageItem = { - readonly "id"?: string - readonly "type"?: "message" - readonly "role": "user" | "system" | "developer" - readonly "content": ReadonlyArray< - | ResponseInputText - | { readonly "type": "input_image"; readonly "detail": "auto" | "high" | "low"; readonly "image_url"?: string } - | ResponseInputFile - | ResponseInputAudio - | ResponseInputVideo - > +export type SubagentNestedTool = { readonly "parameters"?: {}; readonly "type": string } +export const SubagentNestedTool = Schema.Struct({ + "parameters": Schema.optionalKey(Schema.Struct({})), + "type": Schema.String +}).annotate({ + "description": + "A tool made available to the subagent. Only OpenRouter server tools (e.g. openrouter:web_search) are supported; function tools are rejected because the worker has no way to execute them. The subagent tool may not list itself.", + "identifier": "SubagentNestedTool" +}) +export type SubagentReasoning = { + readonly "effort"?: "max" | "xhigh" | "high" | "medium" | "low" | "minimal" | "none" + readonly "max_tokens"?: number } -export const OpenResponsesInputMessageItem = Schema.Struct({ - "id": Schema.optionalKey(Schema.String), - "type": Schema.optionalKey(Schema.Literal("message")), - "role": Schema.Literals(["user", "system", "developer"]), - "content": Schema.Array( - Schema.Union([ - ResponseInputText, - Schema.Struct({ - "type": Schema.Literal("input_image"), - "detail": Schema.Literals(["auto", "high", "low"]), - "image_url": Schema.optionalKey(Schema.String) - }).annotate({ "description": "Image input content item" }), - ResponseInputFile, - ResponseInputAudio, - ResponseInputVideo - ], { mode: "oneOf" }) +export const SubagentReasoning = Schema.Struct({ + "effort": Schema.optionalKey( + Schema.Literals(["max", "xhigh", "high", "medium", "low", "minimal", "none"]).annotate({ + "description": "Reasoning effort level for the subagent call." + }) + ), + "max_tokens": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Maximum number of reasoning tokens the subagent may use. Accepted and validated but not yet forwarded to the subagent call." + }).check(Schema.isInt().annotate({ "expected": "an integer" })) ) +}).annotate({ + "description": + "Reasoning configuration forwarded to the subagent call. Use this to control reasoning effort and token budget for models that support extended thinking.", + "identifier": "SubagentReasoning" }) -export type ProviderSortConfig = { readonly "by"?: ProviderSort | null; readonly "partition"?: "model" | "none" | null } -export const ProviderSortConfig = Schema.Struct({ - "by": Schema.optionalKey(Schema.Union([ProviderSort, Schema.Null])), - "partition": Schema.optionalKey(Schema.Union([Schema.Literals(["model", "none"]), Schema.Null])) +export type SubmitGenerationFeedbackRequest = { + readonly "category": + | "latency" + | "incoherence" + | "incorrect_response" + | "formatting" + | "billing" + | "api_error" + | "other" + readonly "comment"?: string + readonly "generation_id": string +} +export const SubmitGenerationFeedbackRequest = Schema.Struct({ + "category": Schema.Literals([ + "latency", + "incoherence", + "incorrect_response", + "formatting", + "billing", + "api_error", + "other" + ]).annotate({ "description": "The category of feedback being reported" }), + "comment": Schema.optionalKey( + Schema.String.annotate({ "description": "An optional free-text comment describing the feedback" }).check( + Schema.isMaxLength(1000).annotate({ "expected": "a value with a length of at most 1000" }) + ) + ), + "generation_id": Schema.String.annotate({ "description": "The generation to submit feedback on" }).check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ) +}).annotate({ + "description": "Structured feedback about a specific generation", + "identifier": "SubmitGenerationFeedbackRequest" }) -export type PreferredMinThroughput = number | PercentileThroughputCutoffs | unknown -export const PreferredMinThroughput = Schema.Union([ - Schema.Number.check(Schema.isFinite()), - PercentileThroughputCutoffs, - Schema.Unknown -]).annotate({ - "description": - "Preferred minimum throughput (in tokens per second). Can be a number (applies to p50) or an object with percentile-specific cutoffs. Endpoints below the threshold(s) may still be used, but are deprioritized in routing. When using fallback models, this may cause a fallback model to be used instead of the primary model if it meets the threshold." +export type SubmitGenerationFeedbackResponse = { readonly "data": { readonly "success": true } } +export const SubmitGenerationFeedbackResponse = Schema.Struct({ + "data": Schema.Struct({ + "success": Schema.Literal(true).annotate({ "description": "Whether the feedback was recorded" }) + }) +}).annotate({ + "description": "Confirmation that the feedback was recorded", + "identifier": "SubmitGenerationFeedbackResponse" }) -export type PreferredMaxLatency = number | PercentileLatencyCutoffs | unknown -export const PreferredMaxLatency = Schema.Union([ - Schema.Number.check(Schema.isFinite()), - PercentileLatencyCutoffs, - Schema.Unknown -]).annotate({ +export type SupportedParameters = {} +export const SupportedParameters = Schema.Struct({}).annotate({ "description": - "Preferred maximum latency (in seconds). Can be a number (applies to p50) or an object with percentile-specific cutoffs. Endpoints above the threshold(s) may still be used, but are deprioritized in routing. When using fallback models, this may cause a fallback model to be used instead of the primary model if it meets the threshold." + "Union of supported parameters across every endpoint of this model. Coarse discovery aid; the definitive per-endpoint set is behind the endpoints URL.", + "identifier": "SupportedParameters" }) -export type PDFParserOptions = { readonly "engine"?: PDFParserEngine } -export const PDFParserOptions = Schema.Struct({ "engine": Schema.optionalKey(PDFParserEngine) }).annotate({ - "description": "Options for PDF parsing." -}) -export type ForbiddenResponse = { readonly "error": ForbiddenResponseErrorData; readonly "user_id"?: string } -export const ForbiddenResponse = Schema.Struct({ - "error": ForbiddenResponseErrorData, - "user_id": Schema.optionalKey(Schema.String) -}).annotate({ "description": "Forbidden - Authentication successful but insufficient permissions" }) -export type ModelArchitecture = { - readonly "tokenizer"?: ModelGroup - readonly "instruct_type"?: - | "none" - | "airoboros" - | "alpaca" - | "alpaca-modif" - | "chatml" - | "claude" - | "code-llama" - | "gemma" - | "llama2" - | "llama3" - | "mistral" - | "nemotron" - | "neural" - | "openchat" - | "phi3" - | "rwkv" - | "vicuna" - | "zephyr" - | "deepseek-r1" - | "deepseek-v3.1" - | "qwq" - | "qwen3" - readonly "modality": string - readonly "input_modalities": ReadonlyArray - readonly "output_modalities": ReadonlyArray +export type TaskClassificationMacroCategory = { + readonly "key": string + readonly "label": string + readonly "token_share": number + readonly "usage_share": number } -export const ModelArchitecture = Schema.Struct({ - "tokenizer": Schema.optionalKey(ModelGroup), - "instruct_type": Schema.optionalKey( - Schema.Literals([ - "none", - "airoboros", - "alpaca", - "alpaca-modif", - "chatml", - "claude", - "code-llama", - "gemma", - "llama2", - "llama3", - "mistral", - "nemotron", - "neural", - "openchat", - "phi3", - "rwkv", - "vicuna", - "zephyr", - "deepseek-r1", - "deepseek-v3.1", - "qwq", - "qwen3" - ]).annotate({ "description": "Instruction format type" }) - ), - "modality": Schema.String.annotate({ "description": "Primary modality of the model" }), - "input_modalities": Schema.Array(InputModality).annotate({ "description": "Supported input modalities" }), - "output_modalities": Schema.Array(OutputModality).annotate({ "description": "Supported output modalities" }) -}).annotate({ "description": "Model architecture information" }) -export type PublicEndpoint = { - readonly "name": string - readonly "model_id": string - readonly "model_name": string - readonly "context_length": number - readonly "pricing": { - readonly "prompt": string - readonly "completion": string - readonly "request"?: string - readonly "image"?: string - readonly "image_token"?: string - readonly "image_output"?: string - readonly "audio"?: string - readonly "audio_output"?: string - readonly "input_audio_cache"?: string - readonly "web_search"?: string - readonly "internal_reasoning"?: string - readonly "input_cache_read"?: string - readonly "input_cache_write"?: string - readonly "discount"?: number - } - readonly "provider_name": ProviderName - readonly "tag": string - readonly "quantization": "int4" | "int8" | "fp4" | "fp6" | "fp8" | "fp16" | "bf16" | "fp32" | "unknown" - readonly "max_completion_tokens": number - readonly "max_prompt_tokens": number - readonly "supported_parameters": ReadonlyArray - readonly "status"?: EndpointStatus - readonly "uptime_last_30m": number - readonly "supports_implicit_caching": boolean - readonly "latency_last_30m": PercentileStats - readonly "throughput_last_30m": { - readonly "p50": number - readonly "p75": number - readonly "p90": number - readonly "p99": number - } +export const TaskClassificationMacroCategory = Schema.Struct({ + "key": Schema.String.annotate({ "description": "Macro-category identifier." }), + "label": Schema.String.annotate({ "description": "Human-readable label for the macro-category." }), + "token_share": Schema.Number.annotate({ + "description": "Combined token share of all classifications in this macro-category (0–1).", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "usage_share": Schema.Number.annotate({ + "description": "Combined usage share of all classifications in this macro-category (0–1).", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })) +}).annotate({ "identifier": "TaskClassificationMacroCategory" }) +export type TaskClassificationModel = { + readonly "id": string + readonly "tag_token_share": number + readonly "tag_usage_share": number } -export const PublicEndpoint = Schema.Struct({ - "name": Schema.String, - "model_id": Schema.String.annotate({ "description": "The unique identifier for the model (permaslug)" }), - "model_name": Schema.String, - "context_length": Schema.Number.check(Schema.isFinite()), - "pricing": Schema.Struct({ - "prompt": Schema.String.annotate({ "description": "A number or string value representing a large number" }), - "completion": Schema.String.annotate({ "description": "A number or string value representing a large number" }), - "request": Schema.optionalKey( - Schema.String.annotate({ "description": "A number or string value representing a large number" }) - ), - "image": Schema.optionalKey( - Schema.String.annotate({ "description": "A number or string value representing a large number" }) - ), - "image_token": Schema.optionalKey( - Schema.String.annotate({ "description": "A number or string value representing a large number" }) - ), - "image_output": Schema.optionalKey( - Schema.String.annotate({ "description": "A number or string value representing a large number" }) - ), - "audio": Schema.optionalKey( - Schema.String.annotate({ "description": "A number or string value representing a large number" }) - ), - "audio_output": Schema.optionalKey( - Schema.String.annotate({ "description": "A number or string value representing a large number" }) - ), - "input_audio_cache": Schema.optionalKey( - Schema.String.annotate({ "description": "A number or string value representing a large number" }) - ), - "web_search": Schema.optionalKey( - Schema.String.annotate({ "description": "A number or string value representing a large number" }) - ), - "internal_reasoning": Schema.optionalKey( - Schema.String.annotate({ "description": "A number or string value representing a large number" }) - ), - "input_cache_read": Schema.optionalKey( - Schema.String.annotate({ "description": "A number or string value representing a large number" }) - ), - "input_cache_write": Schema.optionalKey( - Schema.String.annotate({ "description": "A number or string value representing a large number" }) - ), - "discount": Schema.optionalKey(Schema.Number.check(Schema.isFinite())) - }), - "provider_name": ProviderName, - "tag": Schema.String, - "quantization": Schema.Literals(["int4", "int8", "fp4", "fp6", "fp8", "fp16", "bf16", "fp32", "unknown"]), - "max_completion_tokens": Schema.Number.check(Schema.isFinite()), - "max_prompt_tokens": Schema.Number.check(Schema.isFinite()), - "supported_parameters": Schema.Array(Parameter), - "status": Schema.optionalKey(EndpointStatus), - "uptime_last_30m": Schema.Number.check(Schema.isFinite()), - "supports_implicit_caching": Schema.Boolean, - "latency_last_30m": PercentileStats, - "throughput_last_30m": Schema.Struct({ - "p50": Schema.Number.annotate({ "description": "Median (50th percentile)" }).check(Schema.isFinite()), - "p75": Schema.Number.annotate({ "description": "75th percentile" }).check(Schema.isFinite()), - "p90": Schema.Number.annotate({ "description": "90th percentile" }).check(Schema.isFinite()), - "p99": Schema.Number.annotate({ "description": "99th percentile" }).check(Schema.isFinite()) - }).annotate({ +export const TaskClassificationModel = Schema.Struct({ + "id": Schema.String.annotate({ "description": "Model identifier (permaslug)." }), + "tag_token_share": Schema.Number.annotate({ "description": - "Throughput percentiles in tokens per second over the last 30 minutes. Throughput measures output token generation speed. Only visible when authenticated with an API key or cookie; returns null for unauthenticated requests." - }) -}).annotate({ "description": "Information about a specific model endpoint" }) -export type __schema20 = { - readonly "type": "reasoning.summary" - readonly "summary": string - readonly "id"?: __schema21 - readonly "format"?: __schema22 - readonly "index"?: __schema11 -} | { - readonly "type": "reasoning.encrypted" - readonly "data": string - readonly "id"?: __schema21 - readonly "format"?: __schema22 - readonly "index"?: __schema11 -} | { - readonly "type": "reasoning.text" - readonly "text"?: string | null - readonly "signature"?: string | null - readonly "id"?: __schema21 - readonly "format"?: __schema22 - readonly "index"?: __schema11 -} -export const __schema20 = Schema.Union([ - Schema.Struct({ - "type": Schema.Literal("reasoning.summary"), - "summary": Schema.String, - "id": Schema.optionalKey(__schema21), - "format": Schema.optionalKey(__schema22), - "index": Schema.optionalKey(__schema11) - }), - Schema.Struct({ - "type": Schema.Literal("reasoning.encrypted"), - "data": Schema.String, - "id": Schema.optionalKey(__schema21), - "format": Schema.optionalKey(__schema22), - "index": Schema.optionalKey(__schema11) - }), - Schema.Struct({ - "type": Schema.Literal("reasoning.text"), - "text": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - "signature": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - "id": Schema.optionalKey(__schema21), - "format": Schema.optionalKey(__schema22), - "index": Schema.optionalKey(__schema11) - }) -], { mode: "oneOf" }) -export type __schema14 = __schema11 | ModelName | __schema13 -export const __schema14 = Schema.Union([__schema11, ModelName, __schema13]) -export type ChatMessageTokenLogprobs = { - readonly "content": ReadonlyArray | null - readonly "refusal": ReadonlyArray | null -} -export const ChatMessageTokenLogprobs = Schema.Struct({ - "content": Schema.Union([Schema.Array(ChatMessageTokenLogprob), Schema.Null]), - "refusal": Schema.Union([Schema.Array(ChatMessageTokenLogprob), Schema.Null]) -}) -export type __schema26 = ChatCompletionFinishReason | null -export const __schema26 = Schema.Union([ChatCompletionFinishReason, Schema.Null]) -export type ResponseFormatJSONSchema = { readonly "type": "json_schema"; readonly "json_schema": JSONSchemaConfig } -export const ResponseFormatJSONSchema = Schema.Struct({ - "type": Schema.Literal("json_schema"), - "json_schema": JSONSchemaConfig -}) -export type ChatMessageContentItemText = { - readonly "type": "text" - readonly "text": string - readonly "cache_control"?: ChatMessageContentItemCacheControl -} -export const ChatMessageContentItemText = Schema.Struct({ - "type": Schema.Literal("text"), - "text": Schema.String, - "cache_control": Schema.optionalKey(ChatMessageContentItemCacheControl) -}) -export type ToolDefinitionJson = { - readonly "type": "function" - readonly "function": { - readonly "name": string - readonly "description"?: string - readonly "parameters"?: {} - readonly "strict"?: boolean | null - } - readonly "cache_control"?: ChatMessageContentItemCacheControl -} -export const ToolDefinitionJson = Schema.Struct({ - "type": Schema.Literal("function"), - "function": Schema.Struct({ - "name": Schema.String.check(Schema.isMaxLength(64)), - "description": Schema.optionalKey(Schema.String), - "parameters": Schema.optionalKey(Schema.Struct({}).check(Schema.isPropertyNames(Schema.String))), - "strict": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])) - }), - "cache_control": Schema.optionalKey(ChatMessageContentItemCacheControl) -}) -export type ToolChoiceOption = "none" | "auto" | "required" | NamedToolChoice -export const ToolChoiceOption = Schema.Union([ - Schema.Literal("none"), - Schema.Literal("auto"), - Schema.Literal("required"), - NamedToolChoice -]) -export type ResponseOutputText = { - readonly "type": "output_text" - readonly "text": string - readonly "annotations"?: ReadonlyArray - readonly "logprobs"?: ReadonlyArray< + "Fraction of this classification's sampled token volume attributed to this model (0–1). Sums to ≤1 across the returned models (only top-N are included and unattributed requests are excluded).", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "tag_usage_share": Schema.Number.annotate({ + "description": + "Fraction of this classification's sampled requests attributed to this model (0–1). Sums to ≤1 across the returned models (only top-N are included and unattributed requests are excluded).", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })) +}).annotate({ "identifier": "TaskClassificationModel" }) +export type TextDeltaEvent = { + readonly "content_index": number + readonly "delta": string + readonly "item_id": string + readonly "logprobs": ReadonlyArray< { - readonly "token": string - readonly "bytes": ReadonlyArray + readonly "bytes"?: ReadonlyArray readonly "logprob": number - readonly "top_logprobs": ReadonlyArray< - { readonly "token": string; readonly "bytes": ReadonlyArray; readonly "logprob": number } + readonly "token": string + readonly "top_logprobs"?: ReadonlyArray< + { readonly "bytes"?: ReadonlyArray; readonly "logprob"?: number; readonly "token"?: string } > } > -} -export const ResponseOutputText = Schema.Struct({ - "type": Schema.Literal("output_text"), - "text": Schema.String, - "annotations": Schema.optionalKey(Schema.Array(OpenAIResponsesAnnotation)), - "logprobs": Schema.optionalKey(Schema.Array(Schema.Struct({ - "token": Schema.String, - "bytes": Schema.Array(Schema.Number.check(Schema.isFinite())), - "logprob": Schema.Number.check(Schema.isFinite()), - "top_logprobs": Schema.Array( - Schema.Struct({ - "token": Schema.String, - "bytes": Schema.Array(Schema.Number.check(Schema.isFinite())), - "logprob": Schema.Number.check(Schema.isFinite()) - }) - ) - }))) -}) -export type OpenResponsesOutputTextAnnotationAddedEvent = { - readonly "type": "response.output_text.annotation.added" readonly "output_index": number - readonly "item_id": string - readonly "content_index": number readonly "sequence_number": number - readonly "annotation_index": number - readonly "annotation": OpenAIResponsesAnnotation -} -export const OpenResponsesOutputTextAnnotationAddedEvent = Schema.Struct({ - "type": Schema.Literal("response.output_text.annotation.added"), - "output_index": Schema.Number.check(Schema.isFinite()), - "item_id": Schema.String, - "content_index": Schema.Number.check(Schema.isFinite()), - "sequence_number": Schema.Number.check(Schema.isFinite()), - "annotation_index": Schema.Number.check(Schema.isFinite()), - "annotation": OpenAIResponsesAnnotation -}).annotate({ "description": "Event emitted when a text annotation is added to output" }) -export type ResponseTextConfig = { - readonly "format"?: ResponseFormatTextConfig - readonly "verbosity"?: "high" | "low" | "medium" -} -export const ResponseTextConfig = Schema.Struct({ - "format": Schema.optionalKey(ResponseFormatTextConfig), - "verbosity": Schema.optionalKey(Schema.Literals(["high", "low", "medium"])) -}).annotate({ "description": "Text output configuration including format and verbosity" }) -export type OpenResponsesResponseText = { - readonly "format"?: ResponseFormatTextConfig - readonly "verbosity"?: "high" | "low" | "medium" -} -export const OpenResponsesResponseText = Schema.Struct({ - "format": Schema.optionalKey(ResponseFormatTextConfig), - "verbosity": Schema.optionalKey(Schema.Literals(["high", "low", "medium"])) -}).annotate({ "description": "Text output configuration including format and verbosity" }) -export type OpenResponsesTextDeltaEvent = { readonly "type": "response.output_text.delta" - readonly "logprobs": ReadonlyArray - readonly "output_index": number - readonly "item_id": string - readonly "content_index": number - readonly "delta": string - readonly "sequence_number": number } -export const OpenResponsesTextDeltaEvent = Schema.Struct({ - "type": Schema.Literal("response.output_text.delta"), - "logprobs": Schema.Array(OpenResponsesLogProbs), - "output_index": Schema.Number.check(Schema.isFinite()), - "item_id": Schema.String, - "content_index": Schema.Number.check(Schema.isFinite()), +export const TextDeltaEvent = Schema.Struct({ + "content_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), "delta": Schema.String, - "sequence_number": Schema.Number.check(Schema.isFinite()) -}).annotate({ "description": "Event emitted when a text delta is streamed" }) -export type OpenResponsesTextDoneEvent = { - readonly "type": "response.output_text.done" - readonly "output_index": number - readonly "item_id": string + "item_id": Schema.String, + "logprobs": Schema.Array( + Schema.Struct({ + "bytes": Schema.optionalKey( + Schema.Array(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))) + ), + "logprob": Schema.Number.annotate({ "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ), + "token": Schema.String, + "top_logprobs": Schema.optionalKey(Schema.Array( + Schema.Struct({ + "bytes": Schema.optionalKey( + Schema.Array(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))) + ), + "logprob": Schema.optionalKey( + Schema.Number.annotate({ "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ) + ), + "token": Schema.optionalKey(Schema.String) + }).annotate({ "description": "Alternative token with its log probability" }) + )) + }).annotate({ "description": "Log probability information for a token" }) + ), + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.output_text.delta") +}).annotate({ "description": "Event emitted when a text delta is streamed", "identifier": "TextDeltaEvent" }) +export type TextDoneEvent = { readonly "content_index": number - readonly "text": string + readonly "item_id": string + readonly "logprobs": ReadonlyArray< + { + readonly "bytes"?: ReadonlyArray + readonly "logprob": number + readonly "token": string + readonly "top_logprobs"?: ReadonlyArray< + { readonly "bytes"?: ReadonlyArray; readonly "logprob"?: number; readonly "token"?: string } + > + } + > + readonly "output_index": number readonly "sequence_number": number - readonly "logprobs": ReadonlyArray + readonly "text": string + readonly "type": "response.output_text.done" } -export const OpenResponsesTextDoneEvent = Schema.Struct({ - "type": Schema.Literal("response.output_text.done"), - "output_index": Schema.Number.check(Schema.isFinite()), +export const TextDoneEvent = Schema.Struct({ + "content_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), "item_id": Schema.String, - "content_index": Schema.Number.check(Schema.isFinite()), + "logprobs": Schema.Array( + Schema.Struct({ + "bytes": Schema.optionalKey( + Schema.Array(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))) + ), + "logprob": Schema.Number.annotate({ "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ), + "token": Schema.String, + "top_logprobs": Schema.optionalKey(Schema.Array( + Schema.Struct({ + "bytes": Schema.optionalKey( + Schema.Array(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))) + ), + "logprob": Schema.optionalKey( + Schema.Number.annotate({ "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ) + ), + "token": Schema.optionalKey(Schema.String) + }).annotate({ "description": "Alternative token with its log probability" }) + )) + }).annotate({ "description": "Log probability information for a token" }) + ), + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), "text": Schema.String, - "sequence_number": Schema.Number.check(Schema.isFinite()), - "logprobs": Schema.Array(OpenResponsesLogProbs) -}).annotate({ "description": "Event emitted when text streaming is complete" }) -export type ProviderSortUnion = ProviderSort | ProviderSortConfig -export const ProviderSortUnion = Schema.Union([ProviderSort, ProviderSortConfig]) -export type ProviderPreferences = { - readonly "allow_fallbacks"?: boolean - readonly "require_parameters"?: boolean - readonly "data_collection"?: DataCollection - readonly "zdr"?: boolean - readonly "enforce_distillable_text"?: boolean - readonly "order"?: ReadonlyArray - readonly "only"?: ReadonlyArray - readonly "ignore"?: ReadonlyArray - readonly "quantizations"?: ReadonlyArray - readonly "sort"?: "price" | "price" | "throughput" | "throughput" | "latency" | "latency" - readonly "max_price"?: { - readonly "prompt"?: BigNumberUnion - readonly "completion"?: string - readonly "image"?: string - readonly "audio"?: string - readonly "request"?: string - } - readonly "preferred_min_throughput"?: PreferredMinThroughput - readonly "preferred_max_latency"?: PreferredMaxLatency + "type": Schema.Literal("response.output_text.done") +}).annotate({ "description": "Event emitted when text streaming is complete", "identifier": "TextDoneEvent" }) +export type ToolCallStatus = "in_progress" | "completed" | "incomplete" +export const ToolCallStatus = Schema.Literals(["in_progress", "completed", "incomplete"]).annotate({ + "identifier": "ToolCallStatus" +}) +export type ToolChoiceAllowed = { + readonly "mode": "auto" | "required" + readonly "tools": ReadonlyArray<{}> + readonly "type": "allowed_tools" } -export const ProviderPreferences = Schema.Struct({ - "allow_fallbacks": Schema.optionalKey(Schema.Boolean.annotate({ - "description": - "Whether to allow backup providers to serve requests\n- true: (default) when the primary provider (or your custom providers in \"order\") is unavailable, use the next best provider.\n- false: use only the primary/custom provider, and return the upstream error if it's unavailable.\n" - })), - "require_parameters": Schema.optionalKey( - Schema.Boolean.annotate({ - "description": - "Whether to filter providers to only those that support the parameters you've provided. If this setting is omitted or set to false, then providers will receive only the parameters they support, and ignore the rest." +export const ToolChoiceAllowed = Schema.Struct({ + "mode": Schema.Literals(["auto", "required"]), + "tools": Schema.Array(Schema.Struct({})), + "type": Schema.Literal("allowed_tools") +}).annotate({ + "description": "Constrains the model to a pre-defined set of allowed tools", + "identifier": "ToolChoiceAllowed" +}) +export type TooManyRequestsResponseErrorData = { + readonly "code": number + readonly "message": string + readonly "metadata"?: { readonly [x: string]: Schema.Json } | null +} +export const TooManyRequestsResponseErrorData = Schema.Struct({ + "code": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "message": Schema.String, + "metadata": Schema.optionalKey( + Schema.Union([Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })), Schema.Null]) + ) +}).annotate({ + "description": "Error data for TooManyRequestsResponse", + "identifier": "TooManyRequestsResponseErrorData" +}) +export type TopProviderInfo = { + readonly "context_length"?: number | null + readonly "is_moderated": boolean + readonly "max_completion_tokens"?: number | null +} +export const TopProviderInfo = Schema.Struct({ + "context_length": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]).annotate({ + "description": "Context length from the top provider" }) ), - "data_collection": Schema.optionalKey(DataCollection), - "zdr": Schema.optionalKey( - Schema.Boolean.annotate({ - "description": - "Whether to restrict routing to only ZDR (Zero Data Retention) endpoints. When true, only endpoints that do not retain prompts will be used." + "is_moderated": Schema.Boolean.annotate({ "description": "Whether the top provider moderates content" }), + "max_completion_tokens": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]).annotate({ + "description": "Maximum completion tokens from the top provider" }) + ) +}).annotate({ "description": "Information about the top provider for this model", "identifier": "TopProviderInfo" }) +export type TraceConfig = { + readonly "generation_name"?: string + readonly "parent_span_id"?: string + readonly "span_name"?: string + readonly "trace_id"?: string + readonly "trace_name"?: string +} +export const TraceConfig = Schema.Struct({ + "generation_name": Schema.optionalKey(Schema.String), + "parent_span_id": Schema.optionalKey(Schema.String), + "span_name": Schema.optionalKey(Schema.String), + "trace_id": Schema.optionalKey(Schema.String), + "trace_name": Schema.optionalKey(Schema.String) +}).annotate({ + "description": + "Metadata for observability and tracing. Known keys (trace_id, trace_name, span_name, generation_name, parent_span_id) have special handling. Additional keys are passed through as custom metadata to configured broadcast destinations.", + "identifier": "TraceConfig" +}) +export type Truncation = "auto" | "disabled" | null +export const Truncation = Schema.Union([Schema.Literal("auto"), Schema.Literal("disabled"), Schema.Null]).annotate({ + "identifier": "Truncation" +}) +export type UnauthorizedResponseErrorData = { + readonly "code": number + readonly "message": string + readonly "metadata"?: { readonly [x: string]: Schema.Json } | null +} +export const UnauthorizedResponseErrorData = Schema.Struct({ + "code": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "message": Schema.String, + "metadata": Schema.optionalKey( + Schema.Union([Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })), Schema.Null]) + ) +}).annotate({ "description": "Error data for UnauthorizedResponse", "identifier": "UnauthorizedResponseErrorData" }) +export type UnifiedBenchmarkPricing = { + readonly "completion": string + readonly "prompt": string + readonly [x: string]: Schema.Json +} | null +export const UnifiedBenchmarkPricing = Schema.Union([ + Schema.StructWithRest( + Schema.Struct({ + "completion": Schema.String.annotate({ "description": "Cost per output token (USD, decimal string)." }), + "prompt": Schema.String.annotate({ "description": "Cost per input token (USD, decimal string)." }) + }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] ), - "enforce_distillable_text": Schema.optionalKey( - Schema.Boolean.annotate({ - "description": - "Whether to restrict routing to only models that allow text distillation. When true, only models where the author has allowed distillation will be used." + Schema.Null +]).annotate({ + "description": "OpenRouter pricing per token for this model. Null if pricing is unavailable.", + "identifier": "UnifiedBenchmarkPricing" +}) +export type UnifiedBenchmarksMeta = { + readonly "as_of": string + readonly "citation": string | null + readonly "model_count": number + readonly "source": "artificial-analysis" | "design-arena" | null + readonly "source_url": string | null + readonly "task_type": string | null + readonly "version": "v1" +} +export const UnifiedBenchmarksMeta = Schema.Struct({ + "as_of": Schema.String.annotate({ "description": "ISO-8601 timestamp of when this data was last updated." }), + "citation": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": + "Required attribution when republishing this data, or null when results span multiple sources (attribute each item individually by its `source` discriminator)." + }), + "model_count": Schema.Number.annotate({ "description": "Number of unique models in the response." }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ), + "source": Schema.Union([Schema.Literal("artificial-analysis"), Schema.Literal("design-arena"), Schema.Null]).annotate( + { "description": "The source filter applied, or null when all sources are returned." } + ), + "source_url": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "URL of the upstream data source, or null when results span multiple sources." + }), + "task_type": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "The task_type filter applied, or null if showing all." + }), + "version": Schema.Literal("v1").annotate({ "description": "Dataset version." }) +}).annotate({ "identifier": "UnifiedBenchmarksMeta" }) +export type UnprocessableEntityResponseErrorData = { + readonly "code": number + readonly "message": string + readonly "metadata"?: { readonly [x: string]: Schema.Json } | null +} +export const UnprocessableEntityResponseErrorData = Schema.Struct({ + "code": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "message": Schema.String, + "metadata": Schema.optionalKey( + Schema.Union([Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })), Schema.Null]) + ) +}).annotate({ + "description": "Error data for UnprocessableEntityResponse", + "identifier": "UnprocessableEntityResponseErrorData" +}) +export type UpdateBYOKKeyRequest = { + readonly "allowed_models"?: ReadonlyArray | null + readonly "allowed_user_ids"?: ReadonlyArray | null + readonly "disabled"?: boolean + readonly "is_fallback"?: boolean + readonly "key"?: string + readonly "name"?: string | null +} +export const UpdateBYOKKeyRequest = Schema.Struct({ + "allowed_models": Schema.optionalKey( + Schema.Union([ + Schema.Array(Schema.String).check( + Schema.isMaxLength(100).annotate({ "expected": "a value with a length of at most 100" }) + ), + Schema.Null + ]).annotate({ + "description": "Optional allowlist of model slugs this credential may be used for. `null` means no restriction." }) ), - "order": Schema.optionalKey( - Schema.Array(Schema.Union([ProviderName, Schema.String])).annotate({ - "description": - "An ordered list of provider slugs. The router will attempt to use the first provider in the subset of this list that supports your requested model, and fall back to the next if it is unavailable. If no providers are available, the request will fail with an error message." + "allowed_user_ids": Schema.optionalKey( + Schema.Union([ + Schema.Array(Schema.String).check( + Schema.isMaxLength(100).annotate({ "expected": "a value with a length of at most 100" }) + ), + Schema.Null + ]).annotate({ + "description": "Optional allowlist of user IDs that may use this credential. `null` means no restriction." }) ), - "only": Schema.optionalKey( - Schema.Array(Schema.Union([ProviderName, Schema.String])).annotate({ + "disabled": Schema.optionalKey(Schema.Boolean.annotate({ "description": "Whether this credential is disabled." })), + "is_fallback": Schema.optionalKey( + Schema.Boolean.annotate({ "description": - "List of provider slugs to allow. If provided, this list is merged with your account-wide allowed provider settings for this request." + "Whether this credential is treated as a fallback — used only after non-fallback keys for the same provider have been tried." }) ), - "ignore": Schema.optionalKey( - Schema.Array(Schema.Union([ProviderName, Schema.String])).annotate({ + "key": Schema.optionalKey( + Schema.String.annotate({ "description": - "List of provider slugs to ignore. If provided, this list is merged with your account-wide ignored provider settings for this request." + "A new raw provider API key to rotate the credential in-place. The previous key material is overwritten and the masked label is regenerated. Encrypted at rest and never returned in API responses." + }).check(Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" })) + ), + "name": Schema.optionalKey( + Schema.Union([ + Schema.String.check(Schema.isMaxLength(255).annotate({ "expected": "a value with a length of at most 255" })), + Schema.Null + ]).annotate({ "description": "Optional human-readable name for the credential." }) + ) +}).annotate({ "identifier": "UpdateBYOKKeyRequest" }) +export type UpdateWorkspaceRequest = { + readonly "default_image_model"?: string | null + readonly "default_provider_sort"?: string | null + readonly "default_text_model"?: string | null + readonly "description"?: string | null + readonly "io_logging_api_key_ids"?: ReadonlyArray | null + readonly "io_logging_sampling_rate"?: number + readonly "is_data_discount_logging_enabled"?: boolean + readonly "is_observability_broadcast_enabled"?: boolean + readonly "is_observability_io_logging_enabled"?: boolean + readonly "name"?: string + readonly "slug"?: string +} +export const UpdateWorkspaceRequest = Schema.Struct({ + "default_image_model": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ "description": "Default image model for this workspace" }) + ), + "default_provider_sort": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "Default provider sort preference (price, throughput, latency, exacto)" }) ), - "quantizations": Schema.optionalKey( - Schema.Array(Quantization).annotate({ "description": "A list of quantization levels to filter the provider by." }) + "default_text_model": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ "description": "Default text model for this workspace" }) ), - "sort": Schema.optionalKey( + "description": Schema.optionalKey( Schema.Union([ - Schema.Union([Schema.Literal("price"), Schema.Literal("price")]).annotate({ - "description": - "The sorting strategy to use for this request, if \"order\" is not specified. When set, no load balancing is performed." - }), - Schema.Union([Schema.Literal("throughput"), Schema.Literal("throughput")]).annotate({ - "description": - "The sorting strategy to use for this request, if \"order\" is not specified. When set, no load balancing is performed." - }), - Schema.Union([Schema.Literal("latency"), Schema.Literal("latency")]).annotate({ - "description": - "The sorting strategy to use for this request, if \"order\" is not specified. When set, no load balancing is performed." - }) - ]) + Schema.String.check(Schema.isMaxLength(500).annotate({ "expected": "a value with a length of at most 500" })), + Schema.Null + ]).annotate({ "description": "New description for the workspace" }) ), - "max_price": Schema.optionalKey( - Schema.Struct({ - "prompt": Schema.optionalKey(BigNumberUnion), - "completion": Schema.optionalKey( - Schema.String.annotate({ "description": "Price per million completion tokens" }) - ), - "image": Schema.optionalKey(Schema.String.annotate({ "description": "Price per image" })), - "audio": Schema.optionalKey(Schema.String.annotate({ "description": "Price per audio unit" })), - "request": Schema.optionalKey(Schema.String.annotate({ "description": "Price per request" })) - }).annotate({ - "description": - "The object specifying the maximum price you want to pay for this request. USD price per million tokens, for prompt and completion." - }) - ), - "preferred_min_throughput": Schema.optionalKey(PreferredMinThroughput), - "preferred_max_latency": Schema.optionalKey(PreferredMaxLatency) -}).annotate({ "description": "Provider routing preferences for the request." }) -export type AnthropicMessagesRequest = { - readonly "model": string - readonly "max_tokens": number - readonly "messages": ReadonlyArray - readonly "system"?: - | string - | ReadonlyArray< - { - readonly "type": "text" - readonly "text": string - readonly "citations"?: ReadonlyArray< - { - readonly "type": "char_location" - readonly "cited_text": string - readonly "document_index": number - readonly "document_title": string - readonly "start_char_index": number - readonly "end_char_index": number - } | { - readonly "type": "page_location" - readonly "cited_text": string - readonly "document_index": number - readonly "document_title": string - readonly "start_page_number": number - readonly "end_page_number": number - } | { - readonly "type": "content_block_location" - readonly "cited_text": string - readonly "document_index": number - readonly "document_title": string - readonly "start_block_index": number - readonly "end_block_index": number - } | { - readonly "type": "web_search_result_location" - readonly "cited_text": string - readonly "encrypted_index": string - readonly "title": string - readonly "url": string - } | { - readonly "type": "search_result_location" - readonly "cited_text": string - readonly "search_result_index": number - readonly "source": string - readonly "title": string - readonly "start_block_index": number - readonly "end_block_index": number - } - > - readonly "cache_control"?: { readonly "type": "ephemeral"; readonly "ttl"?: "5m" | "1h" } - } - > - readonly "metadata"?: { readonly "user_id"?: string } - readonly "stop_sequences"?: ReadonlyArray - readonly "stream"?: boolean - readonly "temperature"?: number - readonly "top_p"?: number - readonly "top_k"?: number - readonly "tools"?: ReadonlyArray< - { - readonly "name": string - readonly "description"?: string - readonly "input_schema": { - readonly "type": "object" - readonly "properties"?: unknown - readonly "required"?: ReadonlyArray - } - readonly "type"?: "custom" - readonly "cache_control"?: { readonly "type": "ephemeral"; readonly "ttl"?: "5m" | "1h" } - } | { - readonly "type": "bash_20250124" - readonly "name": "bash" - readonly "cache_control"?: { readonly "type": "ephemeral"; readonly "ttl"?: "5m" | "1h" } - } | { - readonly "type": "text_editor_20250124" - readonly "name": "str_replace_editor" - readonly "cache_control"?: { readonly "type": "ephemeral"; readonly "ttl"?: "5m" | "1h" } - } | { - readonly "type": "web_search_20250305" - readonly "name": "web_search" - readonly "allowed_domains"?: ReadonlyArray - readonly "blocked_domains"?: ReadonlyArray - readonly "max_uses"?: number - readonly "user_location"?: { - readonly "type": "approximate" - readonly "city"?: string - readonly "country"?: string - readonly "region"?: string - readonly "timezone"?: string - } - readonly "cache_control"?: { readonly "type": "ephemeral"; readonly "ttl"?: "5m" | "1h" } - } - > - readonly "tool_choice"?: - | { readonly "type": "auto"; readonly "disable_parallel_tool_use"?: boolean } - | { readonly "type": "any"; readonly "disable_parallel_tool_use"?: boolean } - | { readonly "type": "none" } - | { readonly "type": "tool"; readonly "name": string; readonly "disable_parallel_tool_use"?: boolean } - readonly "thinking"?: { readonly "type": "enabled"; readonly "budget_tokens": number } | { - readonly "type": "disabled" - } | { readonly "type": "adaptive" } - readonly "service_tier"?: "auto" | "standard_only" - readonly "provider"?: { - readonly "allow_fallbacks"?: boolean - readonly "require_parameters"?: boolean - readonly "data_collection"?: DataCollection - readonly "zdr"?: boolean - readonly "enforce_distillable_text"?: boolean - readonly "order"?: ReadonlyArray - readonly "only"?: ReadonlyArray - readonly "ignore"?: ReadonlyArray - readonly "quantizations"?: ReadonlyArray - readonly "sort"?: "price" | "price" | "throughput" | "throughput" | "latency" | "latency" - readonly "max_price"?: { - readonly "prompt"?: BigNumberUnion - readonly "completion"?: string - readonly "image"?: string - readonly "audio"?: string - readonly "request"?: string - } - readonly "preferred_min_throughput"?: PreferredMinThroughput - readonly "preferred_max_latency"?: PreferredMaxLatency - } - readonly "plugins"?: ReadonlyArray< - | { readonly "id": "auto-router"; readonly "enabled"?: boolean; readonly "allowed_models"?: ReadonlyArray } - | { readonly "id": "moderation" } - | { - readonly "id": "web" - readonly "enabled"?: boolean - readonly "max_results"?: number - readonly "search_prompt"?: string - readonly "engine"?: WebSearchEngine - } - | { readonly "id": "file-parser"; readonly "enabled"?: boolean; readonly "pdf"?: PDFParserOptions } - | { readonly "id": "response-healing"; readonly "enabled"?: boolean } - > - readonly "route"?: "fallback" | "sort" - readonly "user"?: string - readonly "session_id"?: string - readonly "trace"?: { - readonly "trace_id"?: string - readonly "trace_name"?: string - readonly "span_name"?: string - readonly "generation_name"?: string - readonly "parent_span_id"?: string - } - readonly "models"?: ReadonlyArray - readonly "output_config"?: AnthropicOutputConfig -} -export const AnthropicMessagesRequest = Schema.Struct({ - "model": Schema.String, - "max_tokens": Schema.Number.check(Schema.isFinite()), - "messages": Schema.Array(OpenRouterAnthropicMessageParam), - "system": Schema.optionalKey(Schema.Union([ - Schema.String, - Schema.Array(Schema.Struct({ - "type": Schema.Literal("text"), - "text": Schema.String, - "citations": Schema.optionalKey(Schema.Array(Schema.Union([ - Schema.Struct({ - "type": Schema.Literal("char_location"), - "cited_text": Schema.String, - "document_index": Schema.Number.check(Schema.isFinite()), - "document_title": Schema.String, - "start_char_index": Schema.Number.check(Schema.isFinite()), - "end_char_index": Schema.Number.check(Schema.isFinite()) - }), - Schema.Struct({ - "type": Schema.Literal("page_location"), - "cited_text": Schema.String, - "document_index": Schema.Number.check(Schema.isFinite()), - "document_title": Schema.String, - "start_page_number": Schema.Number.check(Schema.isFinite()), - "end_page_number": Schema.Number.check(Schema.isFinite()) - }), - Schema.Struct({ - "type": Schema.Literal("content_block_location"), - "cited_text": Schema.String, - "document_index": Schema.Number.check(Schema.isFinite()), - "document_title": Schema.String, - "start_block_index": Schema.Number.check(Schema.isFinite()), - "end_block_index": Schema.Number.check(Schema.isFinite()) - }), - Schema.Struct({ - "type": Schema.Literal("web_search_result_location"), - "cited_text": Schema.String, - "encrypted_index": Schema.String, - "title": Schema.String, - "url": Schema.String - }), - Schema.Struct({ - "type": Schema.Literal("search_result_location"), - "cited_text": Schema.String, - "search_result_index": Schema.Number.check(Schema.isFinite()), - "source": Schema.String, - "title": Schema.String, - "start_block_index": Schema.Number.check(Schema.isFinite()), - "end_block_index": Schema.Number.check(Schema.isFinite()) - }) - ], { mode: "oneOf" }))), - "cache_control": Schema.optionalKey( - Schema.Struct({ "type": Schema.Literal("ephemeral"), "ttl": Schema.optionalKey(Schema.Literals(["5m", "1h"])) }) - ) - })) - ])), - "metadata": Schema.optionalKey(Schema.Struct({ "user_id": Schema.optionalKey(Schema.String) })), - "stop_sequences": Schema.optionalKey(Schema.Array(Schema.String)), - "stream": Schema.optionalKey(Schema.Boolean), - "temperature": Schema.optionalKey(Schema.Number.check(Schema.isFinite())), - "top_p": Schema.optionalKey(Schema.Number.check(Schema.isFinite())), - "top_k": Schema.optionalKey(Schema.Number.check(Schema.isFinite())), - "tools": Schema.optionalKey(Schema.Array(Schema.Union([ - Schema.Struct({ - "name": Schema.String, - "description": Schema.optionalKey(Schema.String), - "input_schema": Schema.Struct({ - "type": Schema.Literal("object"), - "properties": Schema.optionalKey(Schema.Unknown), - "required": Schema.optionalKey(Schema.Array(Schema.String)) - }), - "type": Schema.optionalKey(Schema.Literal("custom")), - "cache_control": Schema.optionalKey( - Schema.Struct({ "type": Schema.Literal("ephemeral"), "ttl": Schema.optionalKey(Schema.Literals(["5m", "1h"])) }) - ) - }), - Schema.Struct({ - "type": Schema.Literal("bash_20250124"), - "name": Schema.Literal("bash"), - "cache_control": Schema.optionalKey( - Schema.Struct({ "type": Schema.Literal("ephemeral"), "ttl": Schema.optionalKey(Schema.Literals(["5m", "1h"])) }) - ) - }), - Schema.Struct({ - "type": Schema.Literal("text_editor_20250124"), - "name": Schema.Literal("str_replace_editor"), - "cache_control": Schema.optionalKey( - Schema.Struct({ "type": Schema.Literal("ephemeral"), "ttl": Schema.optionalKey(Schema.Literals(["5m", "1h"])) }) - ) - }), - Schema.Struct({ - "type": Schema.Literal("web_search_20250305"), - "name": Schema.Literal("web_search"), - "allowed_domains": Schema.optionalKey(Schema.Array(Schema.String)), - "blocked_domains": Schema.optionalKey(Schema.Array(Schema.String)), - "max_uses": Schema.optionalKey(Schema.Number.check(Schema.isFinite())), - "user_location": Schema.optionalKey( - Schema.Struct({ - "type": Schema.Literal("approximate"), - "city": Schema.optionalKey(Schema.String), - "country": Schema.optionalKey(Schema.String), - "region": Schema.optionalKey(Schema.String), - "timezone": Schema.optionalKey(Schema.String) - }) - ), - "cache_control": Schema.optionalKey( - Schema.Struct({ "type": Schema.Literal("ephemeral"), "ttl": Schema.optionalKey(Schema.Literals(["5m", "1h"])) }) - ) - }) - ], { mode: "oneOf" }))), - "tool_choice": Schema.optionalKey( + "io_logging_api_key_ids": Schema.optionalKey( Schema.Union([ - Schema.Struct({ - "type": Schema.Literal("auto"), - "disable_parallel_tool_use": Schema.optionalKey(Schema.Boolean) - }), - Schema.Struct({ "type": Schema.Literal("any"), "disable_parallel_tool_use": Schema.optionalKey(Schema.Boolean) }), - Schema.Struct({ "type": Schema.Literal("none") }), - Schema.Struct({ - "type": Schema.Literal("tool"), - "name": Schema.String, - "disable_parallel_tool_use": Schema.optionalKey(Schema.Boolean) - }) - ], { mode: "oneOf" }) + Schema.Array(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + Schema.Null + ]).annotate({ "description": "Optional array of API key IDs to filter I/O logging" }) ), - "thinking": Schema.optionalKey( - Schema.Union([ - Schema.Struct({ "type": Schema.Literal("enabled"), "budget_tokens": Schema.Number.check(Schema.isFinite()) }), - Schema.Struct({ "type": Schema.Literal("disabled") }), - Schema.Struct({ "type": Schema.Literal("adaptive") }) - ], { mode: "oneOf" }) + "io_logging_sampling_rate": Schema.optionalKey( + Schema.Number.annotate({ "description": "Sampling rate for I/O logging (0.0001-1)", "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ) ), - "service_tier": Schema.optionalKey(Schema.Literals(["auto", "standard_only"])), - "provider": Schema.optionalKey( - Schema.Struct({ - "allow_fallbacks": Schema.optionalKey(Schema.Boolean.annotate({ - "description": - "Whether to allow backup providers to serve requests\n- true: (default) when the primary provider (or your custom providers in \"order\") is unavailable, use the next best provider.\n- false: use only the primary/custom provider, and return the upstream error if it's unavailable.\n" - })), - "require_parameters": Schema.optionalKey( - Schema.Boolean.annotate({ - "description": - "Whether to filter providers to only those that support the parameters you've provided. If this setting is omitted or set to false, then providers will receive only the parameters they support, and ignore the rest." - }) - ), - "data_collection": Schema.optionalKey(DataCollection), - "zdr": Schema.optionalKey( - Schema.Boolean.annotate({ - "description": - "Whether to restrict routing to only ZDR (Zero Data Retention) endpoints. When true, only endpoints that do not retain prompts will be used." - }) - ), - "enforce_distillable_text": Schema.optionalKey( - Schema.Boolean.annotate({ - "description": - "Whether to restrict routing to only models that allow text distillation. When true, only models where the author has allowed distillation will be used." - }) - ), - "order": Schema.optionalKey( - Schema.Array(Schema.Union([ProviderName, Schema.String])).annotate({ - "description": - "An ordered list of provider slugs. The router will attempt to use the first provider in the subset of this list that supports your requested model, and fall back to the next if it is unavailable. If no providers are available, the request will fail with an error message." - }) - ), - "only": Schema.optionalKey( - Schema.Array(Schema.Union([ProviderName, Schema.String])).annotate({ - "description": - "List of provider slugs to allow. If provided, this list is merged with your account-wide allowed provider settings for this request." - }) - ), - "ignore": Schema.optionalKey( - Schema.Array(Schema.Union([ProviderName, Schema.String])).annotate({ - "description": - "List of provider slugs to ignore. If provided, this list is merged with your account-wide ignored provider settings for this request." - }) - ), - "quantizations": Schema.optionalKey( - Schema.Array(Quantization).annotate({ - "description": "A list of quantization levels to filter the provider by." - }) - ), - "sort": Schema.optionalKey( - Schema.Union([ - Schema.Union([Schema.Literal("price"), Schema.Literal("price")]).annotate({ - "description": - "The sorting strategy to use for this request, if \"order\" is not specified. When set, no load balancing is performed." - }), - Schema.Union([Schema.Literal("throughput"), Schema.Literal("throughput")]).annotate({ - "description": - "The sorting strategy to use for this request, if \"order\" is not specified. When set, no load balancing is performed." - }), - Schema.Union([Schema.Literal("latency"), Schema.Literal("latency")]).annotate({ - "description": - "The sorting strategy to use for this request, if \"order\" is not specified. When set, no load balancing is performed." - }) - ]) - ), - "max_price": Schema.optionalKey( - Schema.Struct({ - "prompt": Schema.optionalKey(BigNumberUnion), - "completion": Schema.optionalKey( - Schema.String.annotate({ "description": "Price per million completion tokens" }) - ), - "image": Schema.optionalKey(Schema.String.annotate({ "description": "Price per image" })), - "audio": Schema.optionalKey(Schema.String.annotate({ "description": "Price per audio unit" })), - "request": Schema.optionalKey(Schema.String.annotate({ "description": "Price per request" })) - }).annotate({ - "description": - "The object specifying the maximum price you want to pay for this request. USD price per million tokens, for prompt and completion." - }) - ), - "preferred_min_throughput": Schema.optionalKey(PreferredMinThroughput), - "preferred_max_latency": Schema.optionalKey(PreferredMaxLatency) - }).annotate({ - "description": "When multiple model providers are available, optionally indicate your routing preference." - }) + "is_data_discount_logging_enabled": Schema.optionalKey( + Schema.Boolean.annotate({ "description": "Whether data discount logging is enabled" }) ), - "plugins": Schema.optionalKey( - Schema.Array(Schema.Union([ - Schema.Struct({ - "id": Schema.Literal("auto-router"), - "enabled": Schema.optionalKey( - Schema.Boolean.annotate({ - "description": "Set to false to disable the auto-router plugin for this request. Defaults to true." - }) - ), - "allowed_models": Schema.optionalKey( - Schema.Array(Schema.String).annotate({ - "description": - "List of model patterns to filter which models the auto-router can route between. Supports wildcards (e.g., \"anthropic/*\" matches all Anthropic models). When not specified, uses the default supported models list." - }) - ) - }), - Schema.Struct({ "id": Schema.Literal("moderation") }), - Schema.Struct({ - "id": Schema.Literal("web"), - "enabled": Schema.optionalKey( - Schema.Boolean.annotate({ - "description": "Set to false to disable the web-search plugin for this request. Defaults to true." - }) - ), - "max_results": Schema.optionalKey(Schema.Number.check(Schema.isFinite())), - "search_prompt": Schema.optionalKey(Schema.String), - "engine": Schema.optionalKey(WebSearchEngine) - }), - Schema.Struct({ - "id": Schema.Literal("file-parser"), - "enabled": Schema.optionalKey( - Schema.Boolean.annotate({ - "description": "Set to false to disable the file-parser plugin for this request. Defaults to true." - }) - ), - "pdf": Schema.optionalKey(PDFParserOptions) - }), - Schema.Struct({ - "id": Schema.Literal("response-healing"), - "enabled": Schema.optionalKey( - Schema.Boolean.annotate({ - "description": "Set to false to disable the response-healing plugin for this request. Defaults to true." - }) - ) - }) - ], { mode: "oneOf" })).annotate({ - "description": "Plugins you want to enable for this request, including their settings." - }) + "is_observability_broadcast_enabled": Schema.optionalKey( + Schema.Boolean.annotate({ "description": "Whether broadcast is enabled" }) ), - "route": Schema.optionalKey( - Schema.Literals(["fallback", "sort"]).annotate({ - "description": - "**DEPRECATED** Use providers.sort.partition instead. Backwards-compatible alias for providers.sort.partition. Accepts legacy values: \"fallback\" (maps to \"model\"), \"sort\" (maps to \"none\")." - }) + "is_observability_io_logging_enabled": Schema.optionalKey( + Schema.Boolean.annotate({ "description": "Whether private logging is enabled" }) ), - "user": Schema.optionalKey( - Schema.String.annotate({ - "description": - "A unique identifier representing your end-user, which helps distinguish between different users of your app. This allows your app to identify specific users in case of abuse reports, preventing your entire app from being affected by the actions of individual users. Maximum of 128 characters." - }).check(Schema.isMaxLength(128)) + "name": Schema.optionalKey( + Schema.String.annotate({ "description": "New name for the workspace" }).check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ).check(Schema.isMaxLength(100).annotate({ "expected": "a value with a length of at most 100" })) ), - "session_id": Schema.optionalKey( + "slug": Schema.optionalKey( Schema.String.annotate({ "description": - "A unique identifier for grouping related requests (e.g., a conversation or agent workflow) for observability. If provided in both the request body and the x-session-id header, the body value takes precedence. Maximum of 128 characters." - }).check(Schema.isMaxLength(128)) - ), - "trace": Schema.optionalKey( - Schema.Struct({ - "trace_id": Schema.optionalKey(Schema.String), - "trace_name": Schema.optionalKey(Schema.String), - "span_name": Schema.optionalKey(Schema.String), - "generation_name": Schema.optionalKey(Schema.String), - "parent_span_id": Schema.optionalKey(Schema.String) - }).annotate({ - "description": - "Metadata for observability and tracing. Known keys (trace_id, trace_name, span_name, generation_name, parent_span_id) have special handling. Additional keys are passed through as custom metadata to configured broadcast destinations." - }) + "New URL-friendly slug (lowercase alphanumeric segments separated by single hyphens, no leading/trailing hyphens)" + }).check(Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" })).check( + Schema.isMaxLength(50).annotate({ "expected": "a value with a length of at most 50" }) + ).check( + Schema.isPattern(new RegExp("^[a-z0-9]+(?:-[a-z0-9]+)*$")).annotate({ + "expected": "a string matching the RegExp ^[a-z0-9]+(?:-[a-z0-9]+)*$" + }) + ) + ) +}).annotate({ "identifier": "UpdateWorkspaceRequest" }) +export type UpsertWorkspaceBudgetRequest = { readonly "limit_usd": number } +export const UpsertWorkspaceBudgetRequest = Schema.Struct({ + "limit_usd": Schema.Number.annotate({ + "description": "Spending limit in USD. Must be greater than 0.", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })) +}).annotate({ "identifier": "UpsertWorkspaceBudgetRequest" }) +export type URLCitation = { + readonly "content"?: string + readonly "end_index": number + readonly "start_index": number + readonly "title": string + readonly "type": "url_citation" + readonly "url": string +} +export const URLCitation = Schema.Struct({ + "content": Schema.optionalKey(Schema.String), + "end_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "start_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "title": Schema.String, + "type": Schema.Literal("url_citation"), + "url": Schema.String +}).annotate({ "identifier": "URLCitation" }) +export type VideoGenerationUsage = { readonly "cost"?: number | null; readonly "is_byok"?: boolean } +export const VideoGenerationUsage = Schema.Struct({ + "cost": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), Schema.Null]) + .annotate({ "description": "The cost of the video generation in USD.", "format": "double" }) ), - "models": Schema.optionalKey(Schema.Array(Schema.String)), - "output_config": Schema.optionalKey(AnthropicOutputConfig) -}).annotate({ "description": "Request schema for Anthropic Messages API endpoint" }) -export type Model = { - readonly "id": string + "is_byok": Schema.optionalKey( + Schema.Boolean.annotate({ "description": "Whether the request was made using a Bring Your Own Key configuration." }) + ) +}).annotate({ + "description": "Usage and cost information for the video generation. Available once the job has completed.", + "identifier": "VideoGenerationUsage" +}) +export type VideoModel = { + readonly "allowed_passthrough_parameters": ReadonlyArray readonly "canonical_slug": string - readonly "hugging_face_id"?: string - readonly "name": string readonly "created": number readonly "description"?: string - readonly "pricing": PublicPricing - readonly "context_length": number - readonly "architecture": ModelArchitecture - readonly "top_provider": TopProviderInfo - readonly "per_request_limits": PerRequestLimits - readonly "supported_parameters": ReadonlyArray - readonly "default_parameters": DefaultParameters - readonly "expiration_date"?: string + readonly "generate_audio": boolean | null + readonly "hugging_face_id"?: string | null + readonly "id": string + readonly "name": string + readonly "pricing_skus"?: { readonly [x: string]: string } | null + readonly "seed": boolean | null + readonly "supported_aspect_ratios": + | ReadonlyArray<"16:9" | "9:16" | "1:1" | "4:3" | "3:4" | "3:2" | "2:3" | "21:9" | "9:21"> + | null + readonly "supported_durations": ReadonlyArray | null + readonly "supported_frame_images": ReadonlyArray<"first_frame" | "last_frame"> | null + readonly "supported_resolutions": ReadonlyArray<"480p" | "720p" | "1080p" | "1K" | "2K" | "4K"> | null + readonly "supported_sizes": + | ReadonlyArray< + | "480x480" + | "480x640" + | "480x720" + | "480x854" + | "480x1120" + | "640x480" + | "720x480" + | "720x720" + | "720x960" + | "720x1080" + | "720x1280" + | "720x1680" + | "854x480" + | "960x720" + | "1080x720" + | "1080x1080" + | "1080x1440" + | "1080x1620" + | "1080x1920" + | "1080x2520" + | "1120x480" + | "1280x720" + | "1440x1080" + | "1620x1080" + | "1680x720" + | "1920x1080" + | "2160x2160" + | "2160x2880" + | "2160x3240" + | "2160x3840" + | "2160x5040" + | "2520x1080" + | "2880x2160" + | "3240x2160" + | "3840x2160" + | "5040x2160" + > + | null } -export const Model = Schema.Struct({ - "id": Schema.String.annotate({ "description": "Unique identifier for the model" }), +export const VideoModel = Schema.Struct({ + "allowed_passthrough_parameters": Schema.Array(Schema.String).annotate({ + "description": "List of parameters that are allowed to be passed through to the provider" + }), "canonical_slug": Schema.String.annotate({ "description": "Canonical slug for the model" }), - "hugging_face_id": Schema.optionalKey( - Schema.String.annotate({ "description": "Hugging Face model identifier, if applicable" }) - ), - "name": Schema.String.annotate({ "description": "Display name of the model" }), "created": Schema.Number.annotate({ "description": "Unix timestamp of when the model was created" }).check( - Schema.isFinite() + Schema.isInt().annotate({ "expected": "an integer" }) ), "description": Schema.optionalKey(Schema.String.annotate({ "description": "Description of the model" })), - "pricing": PublicPricing, - "context_length": Schema.Number.annotate({ "description": "Maximum context length in tokens" }).check( - Schema.isFinite() - ), - "architecture": ModelArchitecture, - "top_provider": TopProviderInfo, - "per_request_limits": PerRequestLimits, - "supported_parameters": Schema.Array(Parameter).annotate({ - "description": "List of supported parameters for this model" + "generate_audio": Schema.Union([Schema.Boolean, Schema.Null]).annotate({ + "description": "Whether the model supports generating audio alongside video" }), - "default_parameters": DefaultParameters, - "expiration_date": Schema.optionalKey( - Schema.String.annotate({ - "description": - "The date after which the model may be removed. ISO 8601 date string (YYYY-MM-DD) or null if no expiration." + "hugging_face_id": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "Hugging Face model identifier, if applicable" }) - ) -}).annotate({ "description": "Information about an AI model available on OpenRouter" }) -export type ListEndpointsResponse = { - readonly "id": string - readonly "name": string - readonly "created": number - readonly "description": string - readonly "architecture": { - readonly "tokenizer": - | "Router" - | "Media" - | "Other" - | "GPT" - | "Claude" - | "Gemini" - | "Grok" - | "Cohere" - | "Nova" - | "Qwen" - | "Yi" - | "DeepSeek" - | "Mistral" - | "Llama2" - | "Llama3" - | "Llama4" - | "PaLM" - | "RWKV" - | "Qwen3" - readonly "instruct_type": - | "none" - | "airoboros" - | "alpaca" - | "alpaca-modif" - | "chatml" - | "claude" - | "code-llama" - | "gemma" - | "llama2" - | "llama3" - | "mistral" - | "nemotron" - | "neural" - | "openchat" - | "phi3" - | "rwkv" - | "vicuna" - | "zephyr" - | "deepseek-r1" - | "deepseek-v3.1" - | "qwq" - | "qwen3" - readonly "modality": string - readonly "input_modalities": ReadonlyArray<"text" | "image" | "file" | "audio" | "video"> - readonly "output_modalities": ReadonlyArray<"text" | "image" | "embeddings" | "audio"> - } - readonly "endpoints": ReadonlyArray -} -export const ListEndpointsResponse = Schema.Struct({ + ), "id": Schema.String.annotate({ "description": "Unique identifier for the model" }), "name": Schema.String.annotate({ "description": "Display name of the model" }), - "created": Schema.Number.annotate({ "description": "Unix timestamp of when the model was created" }).check( - Schema.isFinite() + "pricing_skus": Schema.optionalKey( + Schema.Union([Schema.Record(Schema.String, Schema.String), Schema.Null]).annotate({ + "description": "Pricing SKUs with provider prefix stripped, values as strings" + }) ), - "description": Schema.String.annotate({ "description": "Description of the model" }), - "architecture": Schema.Struct({ - "tokenizer": Schema.Union([ - Schema.Literal("Router").annotate({ "description": "Tokenizer type used by the model" }), - Schema.Literal("Media").annotate({ "description": "Tokenizer type used by the model" }), - Schema.Literal("Other").annotate({ "description": "Tokenizer type used by the model" }), - Schema.Literal("GPT").annotate({ "description": "Tokenizer type used by the model" }), - Schema.Literal("Claude").annotate({ "description": "Tokenizer type used by the model" }), - Schema.Literal("Gemini").annotate({ "description": "Tokenizer type used by the model" }), - Schema.Literal("Grok").annotate({ "description": "Tokenizer type used by the model" }), - Schema.Literal("Cohere").annotate({ "description": "Tokenizer type used by the model" }), - Schema.Literal("Nova").annotate({ "description": "Tokenizer type used by the model" }), - Schema.Literal("Qwen").annotate({ "description": "Tokenizer type used by the model" }), - Schema.Literal("Yi").annotate({ "description": "Tokenizer type used by the model" }), - Schema.Literal("DeepSeek").annotate({ "description": "Tokenizer type used by the model" }), - Schema.Literal("Mistral").annotate({ "description": "Tokenizer type used by the model" }), - Schema.Literal("Llama2").annotate({ "description": "Tokenizer type used by the model" }), - Schema.Literal("Llama3").annotate({ "description": "Tokenizer type used by the model" }), - Schema.Literal("Llama4").annotate({ "description": "Tokenizer type used by the model" }), - Schema.Literal("PaLM").annotate({ "description": "Tokenizer type used by the model" }), - Schema.Literal("RWKV").annotate({ "description": "Tokenizer type used by the model" }), - Schema.Literal("Qwen3").annotate({ "description": "Tokenizer type used by the model" }) - ]).annotate({ "description": "Tokenizer type used by the model" }), - "instruct_type": Schema.Union([ - Schema.Literal("none").annotate({ "description": "Instruction format type" }), - Schema.Literal("airoboros").annotate({ "description": "Instruction format type" }), - Schema.Literal("alpaca").annotate({ "description": "Instruction format type" }), - Schema.Literal("alpaca-modif").annotate({ "description": "Instruction format type" }), - Schema.Literal("chatml").annotate({ "description": "Instruction format type" }), - Schema.Literal("claude").annotate({ "description": "Instruction format type" }), - Schema.Literal("code-llama").annotate({ "description": "Instruction format type" }), - Schema.Literal("gemma").annotate({ "description": "Instruction format type" }), - Schema.Literal("llama2").annotate({ "description": "Instruction format type" }), - Schema.Literal("llama3").annotate({ "description": "Instruction format type" }), - Schema.Literal("mistral").annotate({ "description": "Instruction format type" }), - Schema.Literal("nemotron").annotate({ "description": "Instruction format type" }), - Schema.Literal("neural").annotate({ "description": "Instruction format type" }), - Schema.Literal("openchat").annotate({ "description": "Instruction format type" }), - Schema.Literal("phi3").annotate({ "description": "Instruction format type" }), - Schema.Literal("rwkv").annotate({ "description": "Instruction format type" }), - Schema.Literal("vicuna").annotate({ "description": "Instruction format type" }), - Schema.Literal("zephyr").annotate({ "description": "Instruction format type" }), - Schema.Literal("deepseek-r1").annotate({ "description": "Instruction format type" }), - Schema.Literal("deepseek-v3.1").annotate({ "description": "Instruction format type" }), - Schema.Literal("qwq").annotate({ "description": "Instruction format type" }), - Schema.Literal("qwen3").annotate({ "description": "Instruction format type" }) - ]).annotate({ "description": "Instruction format type" }), - "modality": Schema.String.annotate({ "description": "Primary modality of the model" }), - "input_modalities": Schema.Array( - Schema.Union([ - Schema.Literal("text"), - Schema.Literal("image"), - Schema.Literal("file"), - Schema.Literal("audio"), - Schema.Literal("video") - ]) - ).annotate({ "description": "Supported input modalities" }), - "output_modalities": Schema.Array( - Schema.Union([ - Schema.Literal("text"), - Schema.Literal("image"), - Schema.Literal("embeddings"), - Schema.Literal("audio") + "seed": Schema.Union([Schema.Boolean, Schema.Null]).annotate({ + "description": "Whether the model supports deterministic generation via seed parameter" + }), + "supported_aspect_ratios": Schema.Union([ + Schema.Array(Schema.Literals(["16:9", "9:16", "1:1", "4:3", "3:4", "3:2", "2:3", "21:9", "9:21"])), + Schema.Null + ]).annotate({ "description": "Supported output aspect ratios" }), + "supported_durations": Schema.Union([ + Schema.Array(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + Schema.Null + ]).annotate({ "description": "Supported video durations in seconds" }), + "supported_frame_images": Schema.Union([Schema.Array(Schema.Literals(["first_frame", "last_frame"])), Schema.Null]) + .annotate({ "description": "Supported frame image types (e.g. first_frame, last_frame)" }), + "supported_resolutions": Schema.Union([ + Schema.Array(Schema.Literals(["480p", "720p", "1080p", "1K", "2K", "4K"])), + Schema.Null + ]).annotate({ "description": "Supported output resolutions" }), + "supported_sizes": Schema.Union([ + Schema.Array( + Schema.Literals([ + "480x480", + "480x640", + "480x720", + "480x854", + "480x1120", + "640x480", + "720x480", + "720x720", + "720x960", + "720x1080", + "720x1280", + "720x1680", + "854x480", + "960x720", + "1080x720", + "1080x1080", + "1080x1440", + "1080x1620", + "1080x1920", + "1080x2520", + "1120x480", + "1280x720", + "1440x1080", + "1620x1080", + "1680x720", + "1920x1080", + "2160x2160", + "2160x2880", + "2160x3240", + "2160x3840", + "2160x5040", + "2520x1080", + "2880x2160", + "3240x2160", + "3840x2160", + "5040x2160" ]) - ).annotate({ "description": "Supported output modalities" }) - }).annotate({ "description": "Model architecture information" }), - "endpoints": Schema.Array(PublicEndpoint).annotate({ "description": "List of available endpoints for this model" }) -}).annotate({ "description": "List of available endpoints for a model" }) -export type ChatStreamingMessageChunk = { - readonly "role"?: "assistant" - readonly "content"?: string | null - readonly "reasoning"?: string | null - readonly "refusal"?: string | null - readonly "tool_calls"?: ReadonlyArray - readonly "reasoning_details"?: ReadonlyArray<__schema20> - readonly "images"?: - | ReadonlyArray<{ readonly "type": "image_url"; readonly "image_url": { readonly "url": string } }> - | null - readonly "annotations"?: - | ReadonlyArray< - { - readonly "type": "url_citation" - readonly "url_citation": { - readonly "url": string - readonly "title"?: string - readonly "start_index"?: number - readonly "end_index"?: number - readonly "content"?: string - } - } | { - readonly "type": "file_annotation" - readonly "file_annotation": { readonly "file_id": string; readonly "quote"?: string } - } | { - readonly "type": "file" - readonly "file": { - readonly "hash": string - readonly "name": string - readonly "content"?: ReadonlyArray<{ readonly "type": string; readonly "text"?: string }> - } - } - > - | null -} -export const ChatStreamingMessageChunk = Schema.Struct({ - "role": Schema.optionalKey(Schema.Literal("assistant")), - "content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - "reasoning": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - "refusal": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - "tool_calls": Schema.optionalKey(Schema.Array(ChatStreamingMessageToolCall)), - "reasoning_details": Schema.optionalKey(Schema.Array(__schema20)), - "images": Schema.optionalKey( - Schema.Union([ - Schema.Array( - Schema.Struct({ "type": Schema.Literal("image_url"), "image_url": Schema.Struct({ "url": Schema.String }) }) - ), - Schema.Null - ]) - ), - "annotations": Schema.optionalKey(Schema.Union([ - Schema.Array(Schema.Union([ - Schema.Struct({ - "type": Schema.Literal("url_citation"), - "url_citation": Schema.Struct({ - "url": Schema.String, - "title": Schema.optionalKey(Schema.String), - "start_index": Schema.optionalKey(Schema.Number.check(Schema.isFinite())), - "end_index": Schema.optionalKey(Schema.Number.check(Schema.isFinite())), - "content": Schema.optionalKey(Schema.String) - }) - }), - Schema.Struct({ - "type": Schema.Literal("file_annotation"), - "file_annotation": Schema.Struct({ "file_id": Schema.String, "quote": Schema.optionalKey(Schema.String) }) - }), - Schema.Struct({ - "type": Schema.Literal("file"), - "file": Schema.Struct({ - "hash": Schema.String, - "name": Schema.String, - "content": Schema.optionalKey( - Schema.Array(Schema.Struct({ "type": Schema.String, "text": Schema.optionalKey(Schema.String) })) - ) - }) - }) - ], { mode: "oneOf" })), + ), Schema.Null - ])) -}) -export type ChatMessageContentItem = - | ChatMessageContentItemText - | ChatMessageContentItemImage - | ChatMessageContentItemAudio - | ChatMessageContentItemVideo -export const ChatMessageContentItem = Schema.Union([ - ChatMessageContentItemText, - ChatMessageContentItemImage, - ChatMessageContentItemAudio, - ChatMessageContentItemVideo -], { mode: "oneOf" }) -export type SystemMessage = { - readonly "role": "system" - readonly "content": string | ReadonlyArray - readonly "name"?: string -} -export const SystemMessage = Schema.Struct({ - "role": Schema.Literal("system"), - "content": Schema.Union([Schema.String, Schema.Array(ChatMessageContentItemText)]), - "name": Schema.optionalKey(Schema.String) -}) -export type DeveloperMessage = { - readonly "role": "developer" - readonly "content": string | ReadonlyArray - readonly "name"?: string -} -export const DeveloperMessage = Schema.Struct({ - "role": Schema.Literal("developer"), - "content": Schema.Union([Schema.String, Schema.Array(ChatMessageContentItemText)]), - "name": Schema.optionalKey(Schema.String) -}) -export type OutputMessage = { - readonly "id": string - readonly "role": "assistant" - readonly "type": "message" - readonly "status"?: "completed" | "incomplete" | "in_progress" - readonly "content": ReadonlyArray -} -export const OutputMessage = Schema.Struct({ - "id": Schema.String, - "role": Schema.Literal("assistant"), - "type": Schema.Literal("message"), - "status": Schema.optionalKey(Schema.Literals(["completed", "incomplete", "in_progress"])), - "content": Schema.Array(Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent])) -}) -export type ResponsesOutputMessage = { - readonly "id": string - readonly "role": "assistant" - readonly "type": "message" - readonly "status"?: "completed" | "incomplete" | "in_progress" - readonly "content": ReadonlyArray + ]).annotate({ "description": "Supported output sizes (width x height)" }) +}).annotate({ "identifier": "VideoModel" }) +export type WebFetchEngineEnum = "auto" | "native" | "openrouter" | "exa" | "parallel" | "firecrawl" +export const WebFetchEngineEnum = Schema.Literals(["auto", "native", "openrouter", "exa", "parallel", "firecrawl"]) + .annotate({ + "description": + "Which fetch engine to use. \"auto\" (default) uses native if the provider supports it, otherwise Exa. \"native\" forces the provider's built-in fetch. \"exa\" uses Exa Contents API. \"openrouter\" uses direct HTTP fetch. \"firecrawl\" uses Firecrawl scrape (requires BYOK). \"parallel\" uses the Parallel extract API.", + "identifier": "WebFetchEngineEnum" + }) +export type WebFetchPlugin = { + readonly "allowed_domains"?: ReadonlyArray + readonly "blocked_domains"?: ReadonlyArray + readonly "id": "web-fetch" + readonly "max_content_tokens"?: number + readonly "max_uses"?: number } -export const ResponsesOutputMessage = Schema.Struct({ - "id": Schema.String, - "role": Schema.Literal("assistant"), - "type": Schema.Literal("message"), - "status": Schema.optionalKey(Schema.Literals(["completed", "incomplete", "in_progress"])), - "content": Schema.Array(Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent])) -}).annotate({ "description": "An output message item" }) -export type OpenResponsesContentPartAddedEvent = { - readonly "type": "response.content_part.added" - readonly "output_index": number +export const WebFetchPlugin = Schema.Struct({ + "allowed_domains": Schema.optionalKey( + Schema.Array(Schema.String).annotate({ "description": "Only fetch from these domains." }) + ), + "blocked_domains": Schema.optionalKey( + Schema.Array(Schema.String).annotate({ "description": "Never fetch from these domains." }) + ), + "id": Schema.Literal("web-fetch"), + "max_content_tokens": Schema.optionalKey( + Schema.Number.annotate({ + "description": "Maximum content length in approximate tokens. Content exceeding this limit is truncated." + }).check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "max_uses": Schema.optionalKey( + Schema.Number.annotate({ + "description": "Maximum number of web fetches per request. Once exceeded, the tool returns an error." + }).check(Schema.isInt().annotate({ "expected": "an integer" })) + ) +}).annotate({ "identifier": "WebFetchPlugin" }) +export type Arrays_13 = ReadonlyArray +export const Arrays_13 = Schema.Array(Schema.String).annotate({ "description": "Only fetch from these domains." }) +export type Arrays_14 = ReadonlyArray +export const Arrays_14 = Schema.Array(Schema.String).annotate({ "description": "Never fetch from these domains." }) +export type WebSearchCallCompletedEvent = { readonly "item_id": string - readonly "content_index": number - readonly "part": ResponseOutputText | OpenAIResponsesRefusalContent + readonly "output_index": number readonly "sequence_number": number + readonly "type": "response.web_search_call.completed" } -export const OpenResponsesContentPartAddedEvent = Schema.Struct({ - "type": Schema.Literal("response.content_part.added"), - "output_index": Schema.Number.check(Schema.isFinite()), +export const WebSearchCallCompletedEvent = Schema.Struct({ "item_id": Schema.String, - "content_index": Schema.Number.check(Schema.isFinite()), - "part": Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent]), - "sequence_number": Schema.Number.check(Schema.isFinite()) -}).annotate({ "description": "Event emitted when a new content part is added to an output item" }) -export type OpenResponsesContentPartDoneEvent = { - readonly "type": "response.content_part.done" + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.web_search_call.completed") +}).annotate({ "description": "Web search call completed", "identifier": "WebSearchCallCompletedEvent" }) +export type WebSearchCallInProgressEvent = { + readonly "item_id": string readonly "output_index": number + readonly "sequence_number": number + readonly "type": "response.web_search_call.in_progress" +} +export const WebSearchCallInProgressEvent = Schema.Struct({ + "item_id": Schema.String, + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.web_search_call.in_progress") +}).annotate({ "description": "Web search call in progress", "identifier": "WebSearchCallInProgressEvent" }) +export type WebSearchCallSearchingEvent = { readonly "item_id": string - readonly "content_index": number - readonly "part": ResponseOutputText | OpenAIResponsesRefusalContent + readonly "output_index": number readonly "sequence_number": number + readonly "type": "response.web_search_call.searching" } -export const OpenResponsesContentPartDoneEvent = Schema.Struct({ - "type": Schema.Literal("response.content_part.done"), - "output_index": Schema.Number.check(Schema.isFinite()), +export const WebSearchCallSearchingEvent = Schema.Struct({ "item_id": Schema.String, - "content_index": Schema.Number.check(Schema.isFinite()), - "part": Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent]), - "sequence_number": Schema.Number.check(Schema.isFinite()) -}).annotate({ "description": "Event emitted when a content part is complete" }) -export type ModelsListResponseData = ReadonlyArray -export const ModelsListResponseData = Schema.Array(Model).annotate({ "description": "List of available models" }) -export type ChatStreamingChoice = { - readonly "delta": ChatStreamingMessageChunk - readonly "finish_reason"?: __schema26 - readonly "index": number - readonly "logprobs"?: ChatMessageTokenLogprobs | null + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.web_search_call.searching") +}).annotate({ "description": "Web search call is searching", "identifier": "WebSearchCallSearchingEvent" }) +export type Arrays_15 = ReadonlyArray +export const Arrays_15 = Schema.Array(Schema.String).annotate({ + "description": + "Limit search results to these domains. Supported by Exa, Firecrawl, Parallel, Perplexity, and most native providers (Anthropic, OpenAI, xAI). Cannot be used with excluded_domains." +}) +export type Arrays_16 = ReadonlyArray +export const Arrays_16 = Schema.Array(Schema.String).annotate({ + "description": + "Exclude search results from these domains. Supported by Exa, Firecrawl, Parallel, Perplexity, Anthropic, and xAI. Not supported with OpenAI (silently ignored). Cannot be used with allowed_domains." +}) +export type Objects_152 = { + readonly "allowed_domains"?: ReadonlyArray | null + readonly "excluded_domains"?: ReadonlyArray | null + readonly [x: string]: Schema.Json } -export const ChatStreamingChoice = Schema.Struct({ - "delta": ChatStreamingMessageChunk, - "finish_reason": Schema.optionalKey(__schema26), - "index": Schema.Number.check(Schema.isFinite()), - "logprobs": Schema.optionalKey(Schema.Union([ChatMessageTokenLogprobs, Schema.Null])) +export const Objects_152 = Schema.StructWithRest( + Schema.Struct({ + "allowed_domains": Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), + "excluded_domains": Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])) + }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] +) +export type WebSearchEngine = "native" | "exa" | "firecrawl" | "parallel" | "perplexity" +export const WebSearchEngine = Schema.Literals(["native", "exa", "firecrawl", "parallel", "perplexity"]).annotate({ + "description": "The search engine to use for web search.", + "identifier": "WebSearchEngine" }) -export type UserMessage = { - readonly "role": "user" - readonly "content": string | ReadonlyArray - readonly "name"?: string +export type WebSearchEngineEnum = "native" | "exa" | "parallel" | "firecrawl" | "perplexity" | "auto" +export const WebSearchEngineEnum = Schema.Literals(["native", "exa", "parallel", "firecrawl", "perplexity", "auto"]) + .annotate({ + "description": + "Which search engine to use. \"auto\" (default) uses native if the provider supports it, otherwise Exa. \"native\" forces the provider's built-in search. \"exa\" forces the Exa search API. \"firecrawl\" uses Firecrawl (requires BYOK). \"parallel\" uses the Parallel search API. \"perplexity\" uses the Perplexity Search API (raw ranked results).", + "identifier": "WebSearchEngineEnum" + }) +export type Arrays_17 = ReadonlyArray +export const Arrays_17 = Schema.Array(Schema.String).annotate({ + "description": + "Limit search results to these domains. Supported by Exa, Firecrawl, Parallel, Perplexity, and most native providers (Anthropic, OpenAI, xAI). Cannot be used with excluded_domains." +}) +export type Arrays_18 = ReadonlyArray +export const Arrays_18 = Schema.Array(Schema.String).annotate({ + "description": + "Exclude search results from these domains. Supported by Exa, Firecrawl, Parallel, Perplexity, Anthropic, and xAI. Not supported with OpenAI (silently ignored). Cannot be used with allowed_domains." +}) +export type WebSearchSource = { readonly "type": "url"; readonly "url": string } +export const WebSearchSource = Schema.Struct({ "type": Schema.Literal("url"), "url": Schema.String }).annotate({ + "identifier": "WebSearchSource" +}) +export type WebSearchStatus = "completed" | "searching" | "in_progress" | "failed" +export const WebSearchStatus = Schema.Literals(["completed", "searching", "in_progress", "failed"]).annotate({ + "identifier": "WebSearchStatus" +}) +export type Objects_153 = { + readonly "city"?: string | null + readonly "country"?: string | null + readonly "region"?: string | null + readonly "timezone"?: string | null + readonly "type"?: "approximate" + readonly [x: string]: Schema.Json +} +export const Objects_153 = Schema.StructWithRest( + Schema.Struct({ + "city": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "country": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "region": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "timezone": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "type": Schema.optionalKey(Schema.Literal("approximate")) + }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] +) +export type WebSearchUserLocationServerTool = { + readonly "city"?: string | null + readonly "country"?: string | null + readonly "region"?: string | null + readonly "timezone"?: string | null + readonly "type"?: "approximate" } -export const UserMessage = Schema.Struct({ - "role": Schema.Literal("user"), - "content": Schema.Union([Schema.String, Schema.Array(ChatMessageContentItem)]), - "name": Schema.optionalKey(Schema.String) +export const WebSearchUserLocationServerTool = Schema.Struct({ + "city": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "country": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "region": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "timezone": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "type": Schema.optionalKey(Schema.Literal("approximate")) +}).annotate({ + "description": "Approximate user location for location-biased results.", + "identifier": "WebSearchUserLocationServerTool" }) -export type AssistantMessage = { - readonly "role": "assistant" - readonly "content"?: string | ReadonlyArray | null - readonly "name"?: string - readonly "tool_calls"?: ReadonlyArray - readonly "refusal"?: string | null - readonly "reasoning"?: string | null - readonly "reasoning_details"?: ReadonlyArray<__schema20> - readonly "images"?: - | ReadonlyArray<{ readonly "type": "image_url"; readonly "image_url": { readonly "url": string } }> - | null - readonly "annotations"?: - | ReadonlyArray< - { - readonly "type": "url_citation" - readonly "url_citation": { - readonly "url": string - readonly "title"?: string - readonly "start_index"?: number - readonly "end_index"?: number - readonly "content"?: string - } - } | { - readonly "type": "file_annotation" - readonly "file_annotation": { readonly "file_id": string; readonly "quote"?: string } - } | { - readonly "type": "file" - readonly "file": { - readonly "hash": string - readonly "name": string - readonly "content"?: ReadonlyArray<{ readonly "type": string; readonly "text"?: string }> - } - } - > - | null +export type Workspace = { + readonly "created_at": string + readonly "created_by": string | null + readonly "default_guardrail_id": string + readonly "default_image_model": string | null + readonly "default_provider_sort": string | null + readonly "default_text_model": string | null + readonly "description": string | null + readonly "id": string + readonly "io_logging_api_key_ids": ReadonlyArray | null + readonly "io_logging_sampling_rate": number + readonly "is_data_discount_logging_enabled": boolean + readonly "is_observability_broadcast_enabled": boolean + readonly "is_observability_io_logging_enabled": boolean + readonly "name": string + readonly "slug": string + readonly "updated_at": string | null } -export const AssistantMessage = Schema.Struct({ - "role": Schema.Literal("assistant"), - "content": Schema.optionalKey( - Schema.Union([Schema.Union([Schema.String, Schema.Array(ChatMessageContentItem)]), Schema.Null]) - ), - "name": Schema.optionalKey(Schema.String), - "tool_calls": Schema.optionalKey(Schema.Array(ChatMessageToolCall)), - "refusal": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - "reasoning": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - "reasoning_details": Schema.optionalKey(Schema.Array(__schema20)), - "images": Schema.optionalKey( - Schema.Union([ - Schema.Array( - Schema.Struct({ "type": Schema.Literal("image_url"), "image_url": Schema.Struct({ "url": Schema.String }) }) - ), - Schema.Null - ]) - ), - "annotations": Schema.optionalKey(Schema.Union([ - Schema.Array(Schema.Union([ - Schema.Struct({ - "type": Schema.Literal("url_citation"), - "url_citation": Schema.Struct({ - "url": Schema.String, - "title": Schema.optionalKey(Schema.String), - "start_index": Schema.optionalKey(Schema.Number.check(Schema.isFinite())), - "end_index": Schema.optionalKey(Schema.Number.check(Schema.isFinite())), - "content": Schema.optionalKey(Schema.String) - }) - }), - Schema.Struct({ - "type": Schema.Literal("file_annotation"), - "file_annotation": Schema.Struct({ "file_id": Schema.String, "quote": Schema.optionalKey(Schema.String) }) - }), - Schema.Struct({ - "type": Schema.Literal("file"), - "file": Schema.Struct({ - "hash": Schema.String, - "name": Schema.String, - "content": Schema.optionalKey( - Schema.Array(Schema.Struct({ "type": Schema.String, "text": Schema.optionalKey(Schema.String) })) - ) - }) - }) - ], { mode: "oneOf" })), +export const Workspace = Schema.Struct({ + "created_at": Schema.String.annotate({ "description": "ISO 8601 timestamp of when the workspace was created" }), + "created_by": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "User ID of the workspace creator" + }), + "default_guardrail_id": Schema.String.annotate({ + "description": "Deterministic ID of the workspace's implicitly-created default guardrail", + "format": "uuid" + }), + "default_image_model": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "Default image model for this workspace" + }), + "default_provider_sort": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "Default provider sort preference (price, throughput, latency, exacto)" + }), + "default_text_model": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "Default text model for this workspace" + }), + "description": Schema.Union([Schema.String, Schema.Null]).annotate({ "description": "Description of the workspace" }), + "id": Schema.String.annotate({ "description": "Unique identifier for the workspace", "format": "uuid" }), + "io_logging_api_key_ids": Schema.Union([ + Schema.Array(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), Schema.Null - ])) + ]).annotate({ + "description": "Optional array of API key IDs to filter I/O logging. Null means all keys are logged." + }), + "io_logging_sampling_rate": Schema.Number.annotate({ + "description": "Sampling rate for I/O logging (0.0001-1). 1 means 100% of requests are logged.", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "is_data_discount_logging_enabled": Schema.Boolean.annotate({ + "description": "Whether data discount logging is enabled for this workspace" + }), + "is_observability_broadcast_enabled": Schema.Boolean.annotate({ + "description": "Whether broadcast is enabled for this workspace" + }), + "is_observability_io_logging_enabled": Schema.Boolean.annotate({ + "description": "Whether private logging is enabled for this workspace" + }), + "name": Schema.String.annotate({ "description": "Name of the workspace" }), + "slug": Schema.String.annotate({ "description": "URL-friendly slug for the workspace" }), + "updated_at": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "ISO 8601 timestamp of when the workspace was last updated" + }) +}).annotate({ "identifier": "Workspace" }) +export type WorkspaceBudget = { + readonly "created_at": string + readonly "id": string + readonly "limit_usd": number + readonly "reset_interval": "daily" | "weekly" | "monthly" | null + readonly "updated_at": string + readonly "workspace_id": string +} +export const WorkspaceBudget = Schema.Struct({ + "created_at": Schema.String.annotate({ "description": "ISO 8601 timestamp of when the budget was created" }), + "id": Schema.String.annotate({ "description": "Unique identifier for the budget", "format": "uuid" }), + "limit_usd": Schema.Number.annotate({ "description": "Spending limit in USD for this interval", "format": "double" }) + .check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "reset_interval": Schema.Union([ + Schema.Literal("daily"), + Schema.Literal("weekly"), + Schema.Literal("monthly"), + Schema.Null + ]).annotate({ "description": "Interval at which spend resets. Null means a lifetime (one-time) budget." }), + "updated_at": Schema.String.annotate({ "description": "ISO 8601 timestamp of when the budget was last updated" }), + "workspace_id": Schema.String.annotate({ + "description": "ID of the workspace the budget belongs to", + "format": "uuid" + }) +}).annotate({ "identifier": "WorkspaceBudget" }) +export type WorkspaceBudgetInterval = "daily" | "weekly" | "monthly" | "lifetime" +export const WorkspaceBudgetInterval = Schema.Literals(["daily", "weekly", "monthly", "lifetime"]).annotate({ + "description": "Budget reset interval. Use \"lifetime\" for a one-time budget that never resets.", + "identifier": "WorkspaceBudgetInterval" }) -export type ToolResponseMessage = { - readonly "role": "tool" - readonly "content": string | ReadonlyArray - readonly "tool_call_id": string +export type WorkspaceMember = { + readonly "created_at": string + readonly "id": string + readonly "role": "admin" | "member" + readonly "user_id": string + readonly "workspace_id": string } -export const ToolResponseMessage = Schema.Struct({ - "role": Schema.Literal("tool"), - "content": Schema.Union([Schema.String, Schema.Array(ChatMessageContentItem)]), - "tool_call_id": Schema.String +export const WorkspaceMember = Schema.Struct({ + "created_at": Schema.String.annotate({ "description": "ISO 8601 timestamp of when the membership was created" }), + "id": Schema.String.annotate({ "description": "Unique identifier for the workspace membership", "format": "uuid" }), + "role": Schema.Literals(["admin", "member"]).annotate({ "description": "Role of the member in the workspace" }), + "user_id": Schema.String.annotate({ "description": "Clerk user ID of the member" }), + "workspace_id": Schema.String.annotate({ "description": "ID of the workspace", "format": "uuid" }) +}).annotate({ "identifier": "WorkspaceMember" }) +export type ActivityResponse = { readonly "data": ReadonlyArray } +export const ActivityResponse = Schema.Struct({ + "data": Schema.Array(ActivityItem).annotate({ "description": "List of activity items" }) +}).annotate({ "identifier": "ActivityResponse" }) +export type Arrays_ = ReadonlyArray +export const Arrays_ = Schema.Array(AdvisorNestedTool).annotate({ + "description": + "Tools the advisor sub-agent may use while forming its advice. The advisor runs as an agentic sub-agent over these tools, then returns its text. Only OpenRouter server tools are supported — function tools are rejected — and the list must not include the advisor tool itself." }) -export type OpenAIResponsesInput = - | string - | ReadonlyArray< - | { - readonly "type"?: "message" - readonly "role": "user" | "system" | "assistant" | "developer" - readonly "content": - | ReadonlyArray - | string - } - | { - readonly "id": string - readonly "type"?: "message" - readonly "role": "user" | "system" | "developer" - readonly "content": ReadonlyArray - } - | { - readonly "type": "function_call_output" - readonly "id"?: string - readonly "call_id": string - readonly "output": string - readonly "status"?: ToolCallStatus - } +export type AnthropicBashCodeExecutionResult = { + readonly "content": ReadonlyArray + readonly "return_code": number + readonly "stderr": string + readonly "stdout": string + readonly "type": "bash_code_execution_result" +} +export const AnthropicBashCodeExecutionResult = Schema.Struct({ + "content": Schema.Array(AnthropicBashCodeExecutionOutput), + "return_code": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "stderr": Schema.String, + "stdout": Schema.String, + "type": Schema.Literal("bash_code_execution_result") +}).annotate({ "identifier": "AnthropicBashCodeExecutionResult" }) +export type AnthropicCacheControlDirective = { readonly "ttl"?: AnthropicCacheControlTtl; readonly "type": "ephemeral" } +export const AnthropicCacheControlDirective = Schema.Struct({ + "ttl": Schema.optionalKey(AnthropicCacheControlTtl), + "type": Schema.Literal("ephemeral") +}).annotate({ + "description": + "Enable automatic prompt caching. When set at the top level, the system automatically applies cache breakpoints to the last cacheable block in the request. When set on an individual content block, it marks an explicit cache breakpoint; block-level markers also work on OpenAI models that support explicit prompt caching — OpenRouter converts them to the provider's native format.", + "identifier": "AnthropicCacheControlDirective" +}) +export type ChatContentCacheControl = { readonly "ttl"?: AnthropicCacheControlTtl; readonly "type": "ephemeral" } +export const ChatContentCacheControl = Schema.Struct({ + "ttl": Schema.optionalKey(AnthropicCacheControlTtl), + "type": Schema.Literal("ephemeral") +}).annotate({ + "description": + "Anthropic-style cache breakpoint for the content part. Interchangeable with the OpenAI-style `prompt_cache_breakpoint` marker: OpenRouter converts between the two based on the provider serving the request.", + "identifier": "ChatContentCacheControl" +}) +export type AnthropicCacheCreation = Objects_ | null +export const AnthropicCacheCreation = Schema.Union([Objects_, Schema.Null]).annotate({ + "identifier": "AnthropicCacheCreation" +}) +export type AnthropicCitationsConfig = Objects_1 | null +export const AnthropicCitationsConfig = Schema.Union([Objects_1, Schema.Null]).annotate({ + "identifier": "AnthropicCitationsConfig" +}) +export type AnthropicTextCitation = + | AnthropicCitationCharLocation + | AnthropicCitationPageLocation + | AnthropicCitationContentBlockLocation + | AnthropicCitationWebSearchResultLocation + | AnthropicCitationSearchResultLocation +export const AnthropicTextCitation = Schema.Union([ + AnthropicCitationCharLocation, + AnthropicCitationPageLocation, + AnthropicCitationContentBlockLocation, + AnthropicCitationWebSearchResultLocation, + AnthropicCitationSearchResultLocation +], { mode: "oneOf" }).annotate({ "identifier": "AnthropicTextCitation" }) +export type MessagesContentBlockDeltaEvent = { + readonly "delta": + | { readonly "text": string; readonly "type": "text_delta" } + | { readonly "partial_json": string; readonly "type": "input_json_delta" } + | { readonly "thinking": string; readonly "type": "thinking_delta" } + | { readonly "signature": string; readonly "type": "signature_delta" } | { - readonly "type": "function_call" - readonly "call_id": string - readonly "name": string - readonly "arguments": string - readonly "id"?: string - readonly "status"?: ToolCallStatus + readonly "citation": + | AnthropicCitationCharLocation + | AnthropicCitationPageLocation + | AnthropicCitationContentBlockLocation + | AnthropicCitationWebSearchResultLocation + | AnthropicCitationSearchResultLocation + readonly "type": "citations_delta" } - | OutputItemImageGenerationCall - | OutputMessage - > - | unknown -export const OpenAIResponsesInput = Schema.Union([ - Schema.String, - Schema.Array(Schema.Union([ - Schema.Struct({ - "type": Schema.optionalKey(Schema.Literal("message")), - "role": Schema.Literals(["user", "system", "assistant", "developer"]), - "content": Schema.Union([ - Schema.Array( - Schema.Union([ResponseInputText, ResponseInputImage, ResponseInputFile, ResponseInputAudio], { - mode: "oneOf" - }) - ), - Schema.String - ]) - }), - Schema.Struct({ - "id": Schema.String, - "type": Schema.optionalKey(Schema.Literal("message")), - "role": Schema.Literals(["user", "system", "developer"]), - "content": Schema.Array( - Schema.Union([ResponseInputText, ResponseInputImage, ResponseInputFile, ResponseInputAudio], { mode: "oneOf" }) - ) - }), - Schema.Struct({ - "type": Schema.Literal("function_call_output"), - "id": Schema.optionalKey(Schema.String), - "call_id": Schema.String, - "output": Schema.String, - "status": Schema.optionalKey(ToolCallStatus) - }), + | { readonly "content": string | null; readonly "type": "compaction_delta" } + readonly "index": number + readonly "type": "content_block_delta" +} +export const MessagesContentBlockDeltaEvent = Schema.Struct({ + "delta": Schema.Union([ + Schema.Struct({ "text": Schema.String, "type": Schema.Literal("text_delta") }), + Schema.Struct({ "partial_json": Schema.String, "type": Schema.Literal("input_json_delta") }), + Schema.Struct({ "thinking": Schema.String, "type": Schema.Literal("thinking_delta") }), + Schema.Struct({ "signature": Schema.String, "type": Schema.Literal("signature_delta") }), Schema.Struct({ - "type": Schema.Literal("function_call"), - "call_id": Schema.String, - "name": Schema.String, - "arguments": Schema.String, - "id": Schema.optionalKey(Schema.String), - "status": Schema.optionalKey(ToolCallStatus) + "citation": Schema.Union([ + AnthropicCitationCharLocation, + AnthropicCitationPageLocation, + AnthropicCitationContentBlockLocation, + AnthropicCitationWebSearchResultLocation, + AnthropicCitationSearchResultLocation + ], { mode: "oneOf" }), + "type": Schema.Literal("citations_delta") }), - OutputItemImageGenerationCall, - OutputMessage - ])), - Schema.Unknown -]) -export type OpenResponsesOutputItemAddedEvent = { - readonly "type": "response.output_item.added" - readonly "output_index": number - readonly "item": - | OutputMessage - | OutputItemReasoning - | OutputItemFunctionCall - | OutputItemWebSearchCall - | OutputItemFileSearchCall - | OutputItemImageGenerationCall - readonly "sequence_number": number -} -export const OpenResponsesOutputItemAddedEvent = Schema.Struct({ - "type": Schema.Literal("response.output_item.added"), - "output_index": Schema.Number.check(Schema.isFinite()), - "item": Schema.Union([ - OutputMessage, - OutputItemReasoning, - OutputItemFunctionCall, - OutputItemWebSearchCall, - OutputItemFileSearchCall, - OutputItemImageGenerationCall + Schema.Struct({ "content": Schema.Union([Schema.String, Schema.Null]), "type": Schema.Literal("compaction_delta") }) ], { mode: "oneOf" }), - "sequence_number": Schema.Number.check(Schema.isFinite()) -}).annotate({ "description": "Event emitted when a new output item is added to the response" }) -export type OpenResponsesOutputItemDoneEvent = { - readonly "type": "response.output_item.done" - readonly "output_index": number - readonly "item": - | OutputMessage - | OutputItemReasoning - | OutputItemFunctionCall - | OutputItemWebSearchCall - | OutputItemFileSearchCall - | OutputItemImageGenerationCall - readonly "sequence_number": number + "index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("content_block_delta") +}).annotate({ + "description": "Event sent when content is added to a content block", + "identifier": "MessagesContentBlockDeltaEvent" +}) +export type AnthropicCodeExecutionResult = { + readonly "content": ReadonlyArray + readonly "return_code": number + readonly "stderr": string + readonly "stdout": string + readonly "type": "code_execution_result" } -export const OpenResponsesOutputItemDoneEvent = Schema.Struct({ - "type": Schema.Literal("response.output_item.done"), - "output_index": Schema.Number.check(Schema.isFinite()), - "item": Schema.Union([ - OutputMessage, - OutputItemReasoning, - OutputItemFunctionCall, - OutputItemWebSearchCall, - OutputItemFileSearchCall, - OutputItemImageGenerationCall - ], { mode: "oneOf" }), - "sequence_number": Schema.Number.check(Schema.isFinite()) -}).annotate({ "description": "Event emitted when an output item is complete" }) -export type OpenResponsesInput = - | string - | ReadonlyArray< - | OpenResponsesReasoning - | OpenResponsesEasyInputMessage - | OpenResponsesInputMessageItem - | OpenResponsesFunctionToolCall - | OpenResponsesFunctionCallOutput - | ResponsesOutputMessage - | ResponsesOutputItemReasoning - | ResponsesOutputItemFunctionCall - | ResponsesWebSearchCallOutput - | ResponsesOutputItemFileSearchCall - | ResponsesImageGenerationCall - > -export const OpenResponsesInput = Schema.Union([ - Schema.String, - Schema.Array( - Schema.Union([ - OpenResponsesReasoning, - OpenResponsesEasyInputMessage, - OpenResponsesInputMessageItem, - OpenResponsesFunctionToolCall, - OpenResponsesFunctionCallOutput, - ResponsesOutputMessage, - ResponsesOutputItemReasoning, - ResponsesOutputItemFunctionCall, - ResponsesWebSearchCallOutput, - ResponsesOutputItemFileSearchCall, - ResponsesImageGenerationCall - ]) - ) -]).annotate({ "description": "Input for a response request - can be a string or array of items" }) -export type ModelsListResponse = { readonly "data": ModelsListResponseData } -export const ModelsListResponse = Schema.Struct({ "data": ModelsListResponseData }).annotate({ - "description": "List of available models" +export const AnthropicCodeExecutionResult = Schema.Struct({ + "content": Schema.Array(AnthropicCodeExecutionOutput), + "return_code": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "stderr": Schema.String, + "stdout": Schema.String, + "type": Schema.Literal("code_execution_result") +}).annotate({ "identifier": "AnthropicCodeExecutionResult" }) +export type AnthropicEncryptedCodeExecutionResult = { + readonly "content": ReadonlyArray + readonly "encrypted_stdout": string + readonly "return_code": number + readonly "stderr": string + readonly "type": "encrypted_code_execution_result" +} +export const AnthropicEncryptedCodeExecutionResult = Schema.Struct({ + "content": Schema.Array(AnthropicCodeExecutionOutput), + "encrypted_stdout": Schema.String, + "return_code": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "stderr": Schema.String, + "type": Schema.Literal("encrypted_code_execution_result") +}).annotate({ "identifier": "AnthropicEncryptedCodeExecutionResult" }) +export type AnthropicCaller = + | AnthropicDirectCaller + | AnthropicCodeExecution20250825Caller + | AnthropicCodeExecution20260120Caller +export const AnthropicCaller = Schema.Union([ + AnthropicDirectCaller, + AnthropicCodeExecution20250825Caller, + AnthropicCodeExecution20260120Caller +], { mode: "oneOf" }).annotate({ "identifier": "AnthropicCaller" }) +export type ORAnthropicNullableCaller = + | AnthropicDirectCaller + | AnthropicCodeExecution20250825Caller + | AnthropicCodeExecution20260120Caller + | null +export const ORAnthropicNullableCaller = Schema.Union([ + AnthropicDirectCaller, + AnthropicCodeExecution20250825Caller, + AnthropicCodeExecution20260120Caller, + Schema.Null +], { mode: "oneOf" }).annotate({ "identifier": "ORAnthropicNullableCaller" }) +export type AnthropicBase64ImageSource = { + readonly "data": string + readonly "media_type": AnthropicImageMimeType + readonly "type": "base64" +} +export const AnthropicBase64ImageSource = Schema.Struct({ + "data": Schema.String, + "media_type": AnthropicImageMimeType, + "type": Schema.Literal("base64") +}).annotate({ "identifier": "AnthropicBase64ImageSource" }) +export type AnthropicInputTokensClearAtLeast = Objects_2 | null +export const AnthropicInputTokensClearAtLeast = Schema.Union([Objects_2, Schema.Null]).annotate({ + "identifier": "AnthropicInputTokensClearAtLeast" }) -export type ChatStreamingResponseChunk = { - readonly "data": { - readonly "id": string - readonly "choices": ReadonlyArray - readonly "created": number - readonly "model": string - readonly "object": "chat.completion.chunk" - readonly "system_fingerprint"?: string | null - readonly "error"?: { readonly "message": string; readonly "code": number } - readonly "usage"?: ChatGenerationTokenUsage - } +export type AnthropicIterationCacheCreation = Objects_3 | null +export const AnthropicIterationCacheCreation = Schema.Union([Objects_3, Schema.Null]).annotate({ + "identifier": "AnthropicIterationCacheCreation" +}) +export type AnthropicCodeExecutionToolResultError = { + readonly "error_code": AnthropicServerToolErrorCode + readonly "type": "code_execution_tool_result_error" } -export const ChatStreamingResponseChunk = Schema.Struct({ - "data": Schema.Struct({ - "id": Schema.String, - "choices": Schema.Array(ChatStreamingChoice), - "created": Schema.Number.check(Schema.isFinite()), - "model": Schema.String, - "object": Schema.Literal("chat.completion.chunk"), - "system_fingerprint": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - "error": Schema.optionalKey( - Schema.Struct({ "message": Schema.String, "code": Schema.Number.check(Schema.isFinite()) }) - ), - "usage": Schema.optionalKey(ChatGenerationTokenUsage) +export const AnthropicCodeExecutionToolResultError = Schema.Struct({ + "error_code": AnthropicServerToolErrorCode, + "type": Schema.Literal("code_execution_tool_result_error") +}).annotate({ "identifier": "AnthropicCodeExecutionToolResultError" }) +export type AnthropicToolSearchResultError = { + readonly "error_code": AnthropicServerToolErrorCode + readonly "error_message": string | null + readonly "type": "tool_search_tool_result_error" +} +export const AnthropicToolSearchResultError = Schema.Struct({ + "error_code": AnthropicServerToolErrorCode, + "error_message": Schema.Union([Schema.String, Schema.Null]), + "type": Schema.Literal("tool_search_tool_result_error") +}).annotate({ "identifier": "AnthropicToolSearchResultError" }) +export type AnthropicTextEditorCodeExecutionContent = + | AnthropicTextEditorCodeExecutionToolResultError + | AnthropicTextEditorCodeExecutionViewResult + | AnthropicTextEditorCodeExecutionCreateResult + | AnthropicTextEditorCodeExecutionStrReplaceResult +export const AnthropicTextEditorCodeExecutionContent = Schema.Union([ + AnthropicTextEditorCodeExecutionToolResultError, + AnthropicTextEditorCodeExecutionViewResult, + AnthropicTextEditorCodeExecutionCreateResult, + AnthropicTextEditorCodeExecutionStrReplaceResult +], { mode: "oneOf" }).annotate({ "identifier": "AnthropicTextEditorCodeExecutionContent" }) +export type AnthropicToolSearchResult = { + readonly "tool_references": ReadonlyArray + readonly "type": "tool_search_tool_search_result" +} +export const AnthropicToolSearchResult = Schema.Struct({ + "tool_references": Schema.Array(AnthropicToolReference), + "type": Schema.Literal("tool_search_tool_search_result") +}).annotate({ "identifier": "AnthropicToolSearchResult" }) +export type AnthropicWebSearchToolUserLocation = Objects_4 | null +export const AnthropicWebSearchToolUserLocation = Schema.Union([Objects_4, Schema.Null]).annotate({ + "identifier": "AnthropicWebSearchToolUserLocation" +}) +export type MessagesErrorDetail = { + readonly "error_type"?: ApiErrorType + readonly "message": string + readonly "type": string +} +export const MessagesErrorDetail = Schema.Struct({ + "error_type": Schema.optionalKey(ApiErrorType), + "message": Schema.String, + "type": Schema.String +}).annotate({ "identifier": "MessagesErrorDetail" }) +export type ApplyPatchServerToolConfig = { readonly "engine"?: ApplyPatchEngineEnum } +export const ApplyPatchServerToolConfig = Schema.Struct({ "engine": Schema.optionalKey(ApplyPatchEngineEnum) }) + .annotate({ + "description": "Configuration for the openrouter:apply_patch server tool", + "identifier": "ApplyPatchServerToolConfig" }) +export type ApplyPatchCallOperation = + | ApplyPatchCreateFileOperation + | ApplyPatchUpdateFileOperation + | ApplyPatchDeleteFileOperation +export const ApplyPatchCallOperation = Schema.Union([ + ApplyPatchCreateFileOperation, + ApplyPatchUpdateFileOperation, + ApplyPatchDeleteFileOperation +], { mode: "oneOf" }).annotate({ + "description": + "The patch operation requested by an `apply_patch_call`. `create_file` and `update_file` carry a V4A diff; `delete_file` omits it.", + "identifier": "ApplyPatchCallOperation" }) -export type ChatResponseChoice = { - readonly "finish_reason": __schema26 - readonly "index": number - readonly "message": AssistantMessage - readonly "logprobs"?: ChatMessageTokenLogprobs | null -} -export const ChatResponseChoice = Schema.Struct({ - "finish_reason": __schema26, - "index": Schema.Number.check(Schema.isFinite()), - "message": AssistantMessage, - "logprobs": Schema.optionalKey(Schema.Union([ChatMessageTokenLogprobs, Schema.Null])) -}) -export type Message = SystemMessage | UserMessage | DeveloperMessage | AssistantMessage | ToolResponseMessage -export const Message = Schema.Union([ - SystemMessage, - UserMessage, - DeveloperMessage, - AssistantMessage, - ToolResponseMessage -], { mode: "oneOf" }) -export type OpenAIResponsesNonStreamingResponse = { +export type OutputItemApplyPatchCall = { + readonly "call_id": string + readonly "created_by"?: string readonly "id": string - readonly "object": "response" - readonly "created_at": number - readonly "model": string - readonly "status": OpenAIResponsesResponseStatus - readonly "completed_at": number - readonly "output": ReadonlyArray< - | OutputMessage - | OutputItemReasoning - | OutputItemFunctionCall - | OutputItemWebSearchCall - | OutputItemFileSearchCall - | OutputItemImageGenerationCall - > - readonly "user"?: string - readonly "output_text"?: string - readonly "prompt_cache_key"?: string - readonly "safety_identifier"?: string - readonly "error": ResponsesErrorField - readonly "incomplete_details": OpenAIResponsesIncompleteDetails - readonly "usage"?: OpenAIResponsesUsage - readonly "max_tool_calls"?: number - readonly "top_logprobs"?: number - readonly "max_output_tokens"?: number - readonly "temperature": number - readonly "top_p": number - readonly "presence_penalty": number - readonly "frequency_penalty": number - readonly "instructions": OpenAIResponsesInput - readonly "metadata": OpenResponsesRequestMetadata - readonly "tools": ReadonlyArray< - | { - readonly "type": "function" - readonly "name": string - readonly "description"?: string - readonly "strict"?: boolean - readonly "parameters": {} - } - | OpenResponsesWebSearchPreviewTool - | OpenResponsesWebSearchPreview20250311Tool - | OpenResponsesWebSearchTool - | OpenResponsesWebSearch20250826Tool - > - readonly "tool_choice": OpenAIResponsesToolChoice - readonly "parallel_tool_calls": boolean - readonly "prompt"?: OpenAIResponsesPrompt - readonly "background"?: boolean - readonly "previous_response_id"?: string - readonly "reasoning"?: OpenAIResponsesReasoningConfig - readonly "service_tier"?: OpenAIResponsesServiceTier - readonly "store"?: boolean - readonly "truncation"?: OpenAIResponsesTruncation - readonly "text"?: ResponseTextConfig + readonly "operation": ApplyPatchCreateFileOperation | ApplyPatchUpdateFileOperation | ApplyPatchDeleteFileOperation + readonly "status": "in_progress" | "completed" + readonly "type": "apply_patch_call" } -export const OpenAIResponsesNonStreamingResponse = Schema.Struct({ +export const OutputItemApplyPatchCall = Schema.Struct({ + "call_id": Schema.String, + "created_by": Schema.optionalKey(Schema.String), "id": Schema.String, - "object": Schema.Literal("response"), - "created_at": Schema.Number.check(Schema.isFinite()), - "model": Schema.String, - "status": OpenAIResponsesResponseStatus, - "completed_at": Schema.Number.check(Schema.isFinite()), - "output": Schema.Array( - Schema.Union([ - OutputMessage, - OutputItemReasoning, - OutputItemFunctionCall, - OutputItemWebSearchCall, - OutputItemFileSearchCall, - OutputItemImageGenerationCall - ], { mode: "oneOf" }) + "operation": Schema.Union([ + ApplyPatchCreateFileOperation, + ApplyPatchUpdateFileOperation, + ApplyPatchDeleteFileOperation + ], { mode: "oneOf" }), + "status": Schema.Literals(["in_progress", "completed"]), + "type": Schema.Literal("apply_patch_call") +}).annotate({ "identifier": "OutputItemApplyPatchCall" }) +export type BadGatewayResponse = { + readonly "error": BadGatewayResponseErrorData + readonly "openrouter_metadata"?: { readonly [x: string]: Schema.Json } | null + readonly "user_id"?: string | null +} +export const BadGatewayResponse = Schema.Struct({ + "error": BadGatewayResponseErrorData, + "openrouter_metadata": Schema.optionalKey( + Schema.Union([Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })), Schema.Null]) ), - "user": Schema.optionalKey(Schema.String), - "output_text": Schema.optionalKey(Schema.String), - "prompt_cache_key": Schema.optionalKey(Schema.String), - "safety_identifier": Schema.optionalKey(Schema.String), - "error": ResponsesErrorField, - "incomplete_details": OpenAIResponsesIncompleteDetails, - "usage": Schema.optionalKey(OpenAIResponsesUsage), - "max_tool_calls": Schema.optionalKey(Schema.Number.check(Schema.isFinite())), - "top_logprobs": Schema.optionalKey(Schema.Number.check(Schema.isFinite())), - "max_output_tokens": Schema.optionalKey(Schema.Number.check(Schema.isFinite())), - "temperature": Schema.Number.check(Schema.isFinite()), - "top_p": Schema.Number.check(Schema.isFinite()), - "presence_penalty": Schema.Number.check(Schema.isFinite()), - "frequency_penalty": Schema.Number.check(Schema.isFinite()), - "instructions": OpenAIResponsesInput, - "metadata": OpenResponsesRequestMetadata, - "tools": Schema.Array( - Schema.Union([ - Schema.Struct({ - "type": Schema.Literal("function"), - "name": Schema.String, - "description": Schema.optionalKey(Schema.String), - "strict": Schema.optionalKey(Schema.Boolean), - "parameters": Schema.Struct({}) - }).annotate({ "description": "Function tool definition" }), - OpenResponsesWebSearchPreviewTool, - OpenResponsesWebSearchPreview20250311Tool, - OpenResponsesWebSearchTool, - OpenResponsesWebSearch20250826Tool - ], { mode: "oneOf" }) + "user_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) +}).annotate({ "description": "Bad Gateway - Provider/upstream API failure", "identifier": "BadGatewayResponse" }) +export type BadRequestResponse = { + readonly "error": BadRequestResponseErrorData + readonly "openrouter_metadata"?: { readonly [x: string]: Schema.Json } | null + readonly "user_id"?: string | null +} +export const BadRequestResponse = Schema.Struct({ + "error": BadRequestResponseErrorData, + "openrouter_metadata": Schema.optionalKey( + Schema.Union([Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })), Schema.Null]) ), - "tool_choice": OpenAIResponsesToolChoice, - "parallel_tool_calls": Schema.Boolean, - "prompt": Schema.optionalKey(OpenAIResponsesPrompt), - "background": Schema.optionalKey(Schema.Boolean), - "previous_response_id": Schema.optionalKey(Schema.String), - "reasoning": Schema.optionalKey(OpenAIResponsesReasoningConfig), - "service_tier": Schema.optionalKey(OpenAIResponsesServiceTier), - "store": Schema.optionalKey(Schema.Boolean), - "truncation": Schema.optionalKey(OpenAIResponsesTruncation), - "text": Schema.optionalKey(ResponseTextConfig) + "user_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) +}).annotate({ + "description": "Bad Request - Invalid request parameters or malformed input", + "identifier": "BadRequestResponse" }) -export type OpenResponsesNonStreamingResponse = { +export type BYOKKey = { + readonly "allowed_api_key_hashes": ReadonlyArray | null + readonly "allowed_models": ReadonlyArray | null + readonly "allowed_user_ids": ReadonlyArray | null + readonly "created_at": string + readonly "disabled": boolean readonly "id": string - readonly "object": "response" - readonly "created_at": number - readonly "model": string - readonly "status": OpenAIResponsesResponseStatus - readonly "completed_at": number - readonly "output": ReadonlyArray< - { - readonly "id": string - readonly "role": "assistant" - readonly "type": "message" - readonly "status"?: "completed" | "incomplete" | "in_progress" - readonly "content": ReadonlyArray< - { - readonly "type": "output_text" - readonly "text": string - readonly "annotations"?: ReadonlyArray< - { - readonly "type": "file_citation" - readonly "file_id": string - readonly "filename": string - readonly "index": number - } | { - readonly "type": never - readonly "url": string - readonly "title": string - readonly "start_index": number - readonly "end_index": number - readonly "file_id": string - readonly "filename": string - readonly "index": number - } | { - readonly "type": never - readonly "file_id": string - readonly "index": number - readonly "filename": string - } | { - readonly "type": never - readonly "file_id": string - readonly "filename": string - readonly "index": number - readonly "url": string - readonly "title": string - readonly "start_index": number - readonly "end_index": number - } | { - readonly "type": "url_citation" - readonly "url": string - readonly "title": string - readonly "start_index": number - readonly "end_index": number - } | { - readonly "type": never - readonly "file_id": string - readonly "index": number - readonly "url": string - readonly "title": string - readonly "start_index": number - readonly "end_index": number - } | { - readonly "type": never - readonly "file_id": string - readonly "filename": string - readonly "index": number - } | { - readonly "type": never - readonly "url": string - readonly "title": string - readonly "start_index": number - readonly "end_index": number - readonly "file_id": string - readonly "index": number - } | { readonly "type": "file_path"; readonly "file_id": string; readonly "index": number } - > - readonly "logprobs"?: ReadonlyArray< - { - readonly "token": string - readonly "bytes": ReadonlyArray - readonly "logprob": number - readonly "top_logprobs": ReadonlyArray< - { readonly "token": string; readonly "bytes": ReadonlyArray; readonly "logprob": number } - > - } - > - } | { - readonly "type": never - readonly "refusal": string - readonly "text": string - readonly "annotations"?: ReadonlyArray - readonly "logprobs"?: ReadonlyArray< - { - readonly "token": string - readonly "bytes": ReadonlyArray - readonly "logprob": number - readonly "top_logprobs": ReadonlyArray< - { readonly "token": string; readonly "bytes": ReadonlyArray; readonly "logprob": number } - > - } - > - } | { - readonly "type": never - readonly "text": string - readonly "annotations"?: ReadonlyArray - readonly "logprobs"?: ReadonlyArray< - { - readonly "token": string - readonly "bytes": ReadonlyArray - readonly "logprob": number - readonly "top_logprobs": ReadonlyArray< - { readonly "token": string; readonly "bytes": ReadonlyArray; readonly "logprob": number } - > - } - > - readonly "refusal": string - } | { readonly "type": "refusal"; readonly "refusal": string } - > - } | { - readonly "type": never - readonly "id": string - readonly "content": ReadonlyArray< - { - readonly "type": never - readonly "text": string - readonly "annotations"?: ReadonlyArray - readonly "logprobs"?: ReadonlyArray< - { - readonly "token": string - readonly "bytes": ReadonlyArray - readonly "logprob": number - readonly "top_logprobs": ReadonlyArray< - { readonly "token": string; readonly "bytes": ReadonlyArray; readonly "logprob": number } - > - } - > - } | { readonly "type": never; readonly "refusal": string; readonly "text": string } - > - readonly "summary": ReadonlyArray - readonly "encrypted_content"?: string - readonly "status"?: "completed" | "incomplete" | "in_progress" - readonly "signature"?: string - readonly "format"?: - | "unknown" - | "openai-responses-v1" - | "azure-openai-responses-v1" - | "xai-responses-v1" - | "anthropic-claude-v1" - | "google-gemini-v1" - readonly "role": "assistant" - } | { - readonly "type": never - readonly "id": string - readonly "name": string - readonly "arguments": string - readonly "call_id": string - readonly "status"?: "completed" | "incomplete" | "in_progress" - readonly "role": "assistant" - readonly "content": ReadonlyArray - } | { - readonly "type": never - readonly "id": string - readonly "status": "completed" | "in_progress" - readonly "role": "assistant" - readonly "content": ReadonlyArray - } | { - readonly "type": never - readonly "id": string - readonly "queries": ReadonlyArray - readonly "status": "completed" | "in_progress" - readonly "role": "assistant" - readonly "content": ReadonlyArray - } | { - readonly "type": never - readonly "id": string - readonly "result"?: string - readonly "status": "in_progress" | "completed" - readonly "role": "assistant" - readonly "content": ReadonlyArray - } | { - readonly "id": string - readonly "role": "assistant" - readonly "type": never - readonly "status"?: "completed" | "incomplete" | "in_progress" - readonly "content": ReadonlyArray< - { - readonly "type": never - readonly "text": string - readonly "annotations"?: ReadonlyArray - readonly "logprobs"?: ReadonlyArray< - { - readonly "token": string - readonly "bytes": ReadonlyArray - readonly "logprob": number - readonly "top_logprobs": ReadonlyArray< - { readonly "token": string; readonly "bytes": ReadonlyArray; readonly "logprob": number } - > - } - > - } | { readonly "type": never; readonly "refusal": string; readonly "text": string } - > - readonly "summary": ReadonlyArray - readonly "encrypted_content"?: string - } | { - readonly "type": "reasoning" - readonly "id": string - readonly "content"?: ReadonlyArray<{ readonly "type": "reasoning_text"; readonly "text": string }> - readonly "summary": ReadonlyArray<{ readonly "type": "summary_text"; readonly "text": string }> - readonly "encrypted_content"?: string - readonly "status"?: "completed" | "incomplete" | "in_progress" - readonly "signature"?: string - readonly "format"?: - | "unknown" - | "openai-responses-v1" - | "azure-openai-responses-v1" - | "xai-responses-v1" - | "anthropic-claude-v1" - | "google-gemini-v1" - } | { - readonly "type": never - readonly "id": string - readonly "name": string - readonly "arguments": string - readonly "call_id": string - readonly "status"?: "completed" | "incomplete" | "in_progress" - readonly "content"?: ReadonlyArray - readonly "summary": ReadonlyArray - readonly "encrypted_content"?: string - } | { - readonly "type": never - readonly "id": string - readonly "status": "completed" | "in_progress" - readonly "content"?: ReadonlyArray - readonly "summary": ReadonlyArray - readonly "encrypted_content"?: string - } | { - readonly "type": never - readonly "id": string - readonly "queries": ReadonlyArray - readonly "status": "completed" | "in_progress" - readonly "content"?: ReadonlyArray - readonly "summary": ReadonlyArray - readonly "encrypted_content"?: string - } | { - readonly "type": never - readonly "id": string - readonly "result"?: string - readonly "status": "in_progress" | "completed" - readonly "content"?: ReadonlyArray - readonly "summary": ReadonlyArray - readonly "encrypted_content"?: string - } | { - readonly "id": string - readonly "role": "assistant" - readonly "type": never - readonly "status"?: "completed" | "incomplete" | "in_progress" - readonly "content": ReadonlyArray - readonly "name": string - readonly "arguments": string - readonly "call_id": string - } | { - readonly "type": never - readonly "id": string - readonly "content"?: ReadonlyArray - readonly "summary": ReadonlyArray - readonly "encrypted_content"?: string - readonly "status"?: "completed" | "incomplete" | "in_progress" - readonly "signature"?: string - readonly "format"?: - | "unknown" - | "openai-responses-v1" - | "azure-openai-responses-v1" - | "xai-responses-v1" - | "anthropic-claude-v1" - | "google-gemini-v1" - readonly "name": string - readonly "arguments": string - readonly "call_id": string - } | { - readonly "type": "function_call" - readonly "id"?: string - readonly "name": string - readonly "arguments": string - readonly "call_id": string - readonly "status"?: "completed" | "incomplete" | "in_progress" - } | { - readonly "type": never - readonly "id": string - readonly "status": "completed" | "in_progress" - readonly "name": string - readonly "arguments": string - readonly "call_id": string - } | { - readonly "type": never - readonly "id": string - readonly "queries": ReadonlyArray - readonly "status": "completed" | "in_progress" - readonly "name": string - readonly "arguments": string - readonly "call_id": string - } | { - readonly "type": never - readonly "id": string - readonly "result"?: string - readonly "status": "in_progress" | "completed" - readonly "name": string - readonly "arguments": string - readonly "call_id": string - } | { - readonly "id": string - readonly "role": "assistant" - readonly "type": never - readonly "status": "completed" | "in_progress" - readonly "content": ReadonlyArray - } | { - readonly "type": never - readonly "id": string - readonly "content"?: ReadonlyArray - readonly "summary": ReadonlyArray - readonly "encrypted_content"?: string - readonly "status": "completed" | "in_progress" - readonly "signature"?: string - readonly "format"?: - | "unknown" - | "openai-responses-v1" - | "azure-openai-responses-v1" - | "xai-responses-v1" - | "anthropic-claude-v1" - | "google-gemini-v1" - } | { - readonly "type": never - readonly "id": string - readonly "name": string - readonly "arguments": string - readonly "call_id": string - readonly "status": "completed" | "in_progress" - } | { - readonly "type": "web_search_call" - readonly "id": string - readonly "status": "completed" | "searching" | "in_progress" | "failed" - } | { - readonly "type": never - readonly "id": string - readonly "queries": ReadonlyArray - readonly "status": "completed" | "searching" | "in_progress" | "failed" - } | { - readonly "type": never - readonly "id": string - readonly "result"?: string - readonly "status": "in_progress" | "completed" | "failed" - } | { - readonly "id": string - readonly "role": "assistant" - readonly "type": never - readonly "status": "completed" | "in_progress" - readonly "content": ReadonlyArray - readonly "queries": ReadonlyArray - } | { - readonly "type": never - readonly "id": string - readonly "content"?: ReadonlyArray - readonly "summary": ReadonlyArray - readonly "encrypted_content"?: string - readonly "status": "completed" | "in_progress" - readonly "signature"?: string - readonly "format"?: - | "unknown" - | "openai-responses-v1" - | "azure-openai-responses-v1" - | "xai-responses-v1" - | "anthropic-claude-v1" - | "google-gemini-v1" - readonly "queries": ReadonlyArray - } | { - readonly "type": never - readonly "id": string - readonly "name": string - readonly "arguments": string - readonly "call_id": string - readonly "status": "completed" | "in_progress" - readonly "queries": ReadonlyArray - } | { - readonly "type": never - readonly "id": string - readonly "status": "completed" | "searching" | "in_progress" | "failed" - readonly "queries": ReadonlyArray - } | { - readonly "type": "file_search_call" - readonly "id": string - readonly "queries": ReadonlyArray - readonly "status": "completed" | "searching" | "in_progress" | "failed" - } | { - readonly "type": never - readonly "id": string - readonly "result"?: string - readonly "status": "in_progress" | "completed" | "failed" - readonly "queries": ReadonlyArray - } | { - readonly "id": string - readonly "role": "assistant" - readonly "type": never - readonly "status": "completed" | "in_progress" - readonly "content": ReadonlyArray - readonly "result"?: string - } | { - readonly "type": never - readonly "id": string - readonly "content"?: ReadonlyArray - readonly "summary": ReadonlyArray - readonly "encrypted_content"?: string - readonly "status": "completed" | "in_progress" - readonly "signature"?: string - readonly "format"?: - | "unknown" - | "openai-responses-v1" - | "azure-openai-responses-v1" - | "xai-responses-v1" - | "anthropic-claude-v1" - | "google-gemini-v1" - readonly "result"?: string - } | { - readonly "type": never - readonly "id": string - readonly "name": string - readonly "arguments": string - readonly "call_id": string - readonly "status": "completed" | "in_progress" - readonly "result"?: string - } | { - readonly "type": never - readonly "id": string - readonly "status": "completed" | "in_progress" | "failed" - readonly "result"?: string - } | { - readonly "type": never - readonly "id": string - readonly "queries": ReadonlyArray - readonly "status": "completed" | "in_progress" | "failed" - readonly "result"?: string - } | { - readonly "type": "image_generation_call" - readonly "id": string - readonly "result"?: string - readonly "status": "in_progress" | "completed" | "generating" | "failed" - } - > - readonly "user"?: string - readonly "output_text"?: string - readonly "prompt_cache_key"?: string - readonly "safety_identifier"?: string - readonly "error": ResponsesErrorField - readonly "incomplete_details": OpenAIResponsesIncompleteDetails - readonly "usage"?: { - readonly "input_tokens": number - readonly "input_tokens_details": { readonly "cached_tokens": number } - readonly "output_tokens": number - readonly "output_tokens_details": { readonly "reasoning_tokens": number } - readonly "total_tokens": number - readonly "cost"?: number - readonly "is_byok"?: boolean - readonly "cost_details"?: { - readonly "upstream_inference_cost"?: number - readonly "upstream_inference_input_cost": number - readonly "upstream_inference_output_cost": number - } - } - readonly "max_tool_calls"?: number - readonly "top_logprobs"?: number - readonly "max_output_tokens"?: number - readonly "temperature": number - readonly "top_p": number - readonly "presence_penalty": number - readonly "frequency_penalty": number - readonly "instructions": OpenAIResponsesInput - readonly "metadata": OpenResponsesRequestMetadata - readonly "tools": ReadonlyArray< + readonly "is_fallback": boolean + readonly "label": string + readonly "name"?: string | null + readonly "provider": BYOKProviderSlug + readonly "sort_order": number + readonly "workspace_id": string +} +export const BYOKKey = Schema.Struct({ + "allowed_api_key_hashes": Schema.Union([ + Schema.Array(Schema.String).check( + Schema.isMaxLength(100).annotate({ "expected": "a value with a length of at most 100" }) + ), + Schema.Null + ]).annotate({ + "description": + "Optional allowlist of OpenRouter API key hashes (`api_keys.hash`) that may use this credential. `null` means no restriction." + }), + "allowed_models": Schema.Union([ + Schema.Array(Schema.String).check( + Schema.isMaxLength(100).annotate({ "expected": "a value with a length of at most 100" }) + ), + Schema.Null + ]).annotate({ + "description": "Optional allowlist of model slugs this credential may be used for. `null` means no restriction." + }), + "allowed_user_ids": Schema.Union([ + Schema.Array(Schema.String).check( + Schema.isMaxLength(100).annotate({ "expected": "a value with a length of at most 100" }) + ), + Schema.Null + ]).annotate({ + "description": "Optional allowlist of user IDs that may use this credential. `null` means no restriction." + }), + "created_at": Schema.String.annotate({ "description": "ISO timestamp of when the credential was created." }), + "disabled": Schema.Boolean.annotate({ "description": "Whether this credential is currently disabled." }), + "id": Schema.String.annotate({ + "description": "Stable public identifier for this BYOK credential.", + "format": "uuid" + }), + "is_fallback": Schema.Boolean.annotate({ + "description": + "Whether this credential is treated as a fallback — used only after non-fallback keys for the same provider have been tried." + }), + "label": Schema.String.annotate({ + "description": "Short masked snippet of the key (e.g. the first/last few characters) used to identify it in the UI." + }), + "name": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "Optional human-readable name for the credential." + }) + ), + "provider": BYOKProviderSlug, + "sort_order": Schema.Number.annotate({ + "description": "Position within the provider — credentials are tried in ascending sort order." + }).check(Schema.isInt().annotate({ "expected": "an integer" })), + "workspace_id": Schema.String.annotate({ + "description": "ID of the workspace this credential belongs to.", + "format": "uuid" + }) +}).annotate({ "identifier": "BYOKKey" }) +export type CreateBYOKKeyRequest = { + readonly "allowed_models"?: ReadonlyArray | null + readonly "allowed_user_ids"?: ReadonlyArray | null + readonly "disabled"?: boolean + readonly "is_fallback"?: boolean + readonly "key": string + readonly "name"?: string | null + readonly "provider": BYOKProviderSlug + readonly "workspace_id"?: string +} +export const CreateBYOKKeyRequest = Schema.Struct({ + "allowed_models": Schema.optionalKey( + Schema.Union([ + Schema.Array(Schema.String).check( + Schema.isMaxLength(100).annotate({ "expected": "a value with a length of at most 100" }) + ), + Schema.Null + ]).annotate({ + "description": "Optional allowlist of model slugs this credential may be used for. `null` means no restriction." + }) + ), + "allowed_user_ids": Schema.optionalKey( + Schema.Union([ + Schema.Array(Schema.String).check( + Schema.isMaxLength(100).annotate({ "expected": "a value with a length of at most 100" }) + ), + Schema.Null + ]).annotate({ + "description": "Optional allowlist of user IDs that may use this credential. `null` means no restriction." + }) + ), + "disabled": Schema.optionalKey( + Schema.Boolean.annotate({ "description": "Whether this credential should be created in a disabled state." }) + ), + "is_fallback": Schema.optionalKey( + Schema.Boolean.annotate({ + "description": + "Whether this credential is treated as a fallback — used only after non-fallback keys for the same provider have been tried." + }) + ), + "key": Schema.String.annotate({ + "description": + "The raw provider API key or credential. This value is encrypted at rest and never returned in API responses." + }).check(Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" })), + "name": Schema.optionalKey( + Schema.Union([ + Schema.String.check(Schema.isMaxLength(255).annotate({ "expected": "a value with a length of at most 255" })), + Schema.Null + ]).annotate({ "description": "Optional human-readable name for the credential." }) + ), + "provider": BYOKProviderSlug, + "workspace_id": Schema.optionalKey( + Schema.String.annotate({ + "description": "Optional workspace ID. Defaults to the authenticated entity's default workspace.", + "format": "uuid" + }) + ) +}).annotate({ "identifier": "CreateBYOKKeyRequest" }) +export type ChatContentVideo = { readonly "type": "video_url"; readonly "video_url": ChatContentVideoInput } +export const ChatContentVideo = Schema.Struct({ + "type": Schema.Literal("video_url"), + "video_url": ChatContentVideoInput +}).annotate({ "description": "Video input content part", "identifier": "ChatContentVideo" }) +export type ChatFormatJsonSchemaConfig = { + readonly "json_schema": ChatJsonSchemaConfig + readonly "type": "json_schema" +} +export const ChatFormatJsonSchemaConfig = Schema.Struct({ + "json_schema": ChatJsonSchemaConfig, + "type": Schema.Literal("json_schema") +}).annotate({ + "description": "JSON Schema response format for structured outputs", + "identifier": "ChatFormatJsonSchemaConfig" +}) +export type ChatToolChoice = "none" | "auto" | "required" | ChatNamedToolChoice | ChatServerToolChoice +export const ChatToolChoice = Schema.Union([ + Schema.Literal("none"), + Schema.Literal("auto"), + Schema.Literal("required"), + ChatNamedToolChoice, + ChatServerToolChoice +]).annotate({ "description": "Tool choice configuration", "identifier": "ChatToolChoice" }) +export type ChatStreamOptions = Objects_6 | null +export const ChatStreamOptions = Schema.Union([Objects_6, Schema.Null]).annotate({ + "description": "Streaming configuration options", + "identifier": "ChatStreamOptions" +}) +export type Objects_7 = { + readonly "content": ReadonlyArray | null + readonly "refusal"?: ReadonlyArray | null + readonly [x: string]: Schema.Json +} +export const Objects_7 = Schema.StructWithRest( + Schema.Struct({ + "content": Schema.Union([Schema.Array(ChatTokenLogprob), Schema.Null]).annotate({ + "description": "Log probabilities for content tokens" + }), + "refusal": Schema.optionalKey( + Schema.Union([Schema.Array(ChatTokenLogprob), Schema.Null]).annotate({ + "description": "Log probabilities for refusal tokens" + }) + ) + }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] +) +export type FileSearchServerTool = { + readonly "filters"?: | { - readonly "type": "function" - readonly "name": string - readonly "description"?: string - readonly "strict"?: boolean - readonly "parameters": {} + readonly "key": string + readonly "type": "eq" | "ne" | "gt" | "gte" | "lt" | "lte" + readonly "value": string | number | boolean | ReadonlyArray } - | OpenResponsesWebSearchPreviewTool - | OpenResponsesWebSearchPreview20250311Tool - | OpenResponsesWebSearchTool - | OpenResponsesWebSearch20250826Tool - > - readonly "tool_choice": OpenAIResponsesToolChoice - readonly "parallel_tool_calls": boolean - readonly "prompt"?: OpenAIResponsesPrompt - readonly "background"?: boolean - readonly "previous_response_id"?: string - readonly "reasoning"?: OpenAIResponsesReasoningConfig - readonly "service_tier"?: OpenAIResponsesServiceTier - readonly "store"?: boolean - readonly "truncation"?: OpenAIResponsesTruncation - readonly "text"?: ResponseTextConfig + | CompoundFilter + | null + readonly "max_num_results"?: number + readonly "ranking_options"?: { + readonly "ranker"?: "auto" | "default-2024-11-15" + readonly "score_threshold"?: number + } + readonly "type": "file_search" + readonly "vector_store_ids": ReadonlyArray } -export const OpenResponsesNonStreamingResponse = Schema.Struct({ - "id": Schema.String, - "object": Schema.Literal("response"), - "created_at": Schema.Number.check(Schema.isFinite()), - "model": Schema.String, - "status": OpenAIResponsesResponseStatus, - "completed_at": Schema.Number.check(Schema.isFinite()), - "output": Schema.Array(Schema.Union([ - Schema.Union([ - Schema.Struct({ - "id": Schema.String, - "role": Schema.Literal("assistant"), - "type": Schema.Literal("message"), - "status": Schema.optionalKey( - Schema.Union([Schema.Literal("completed"), Schema.Literal("incomplete"), Schema.Literal("in_progress")]) +export const FileSearchServerTool = Schema.Struct({ + "filters": Schema.optionalKey(Schema.Union([ + Schema.Struct({ + "key": Schema.String, + "type": Schema.Literals(["eq", "ne", "gt", "gte", "lt", "lte"]), + "value": Schema.Union([ + Schema.String, + Schema.Number.annotate({ "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) ), - "content": Schema.Array(Schema.Union([ + Schema.Boolean, + Schema.Array( Schema.Union([ - Schema.Struct({ - "type": Schema.Literal("output_text"), - "text": Schema.String, - "annotations": Schema.optionalKey( - Schema.Array( - Schema.Union([ - Schema.Union([ - Schema.Struct({ - "type": Schema.Literal("file_citation"), - "file_id": Schema.String, - "filename": Schema.String, - "index": Schema.Number.check(Schema.isFinite()) - }), - Schema.Struct({ - "type": Schema.Never, - "url": Schema.String, - "title": Schema.String, - "start_index": Schema.Number.check(Schema.isFinite()), - "end_index": Schema.Number.check(Schema.isFinite()), - "file_id": Schema.String, - "filename": Schema.String, - "index": Schema.Number.check(Schema.isFinite()) - }), - Schema.Struct({ - "type": Schema.Never, - "file_id": Schema.String, - "index": Schema.Number.check(Schema.isFinite()), - "filename": Schema.String - }) - ]), - Schema.Union([ - Schema.Struct({ - "type": Schema.Never, - "file_id": Schema.String, - "filename": Schema.String, - "index": Schema.Number.check(Schema.isFinite()), - "url": Schema.String, - "title": Schema.String, - "start_index": Schema.Number.check(Schema.isFinite()), - "end_index": Schema.Number.check(Schema.isFinite()) - }), - Schema.Struct({ - "type": Schema.Literal("url_citation"), - "url": Schema.String, - "title": Schema.String, - "start_index": Schema.Number.check(Schema.isFinite()), - "end_index": Schema.Number.check(Schema.isFinite()) - }), - Schema.Struct({ - "type": Schema.Never, - "file_id": Schema.String, - "index": Schema.Number.check(Schema.isFinite()), - "url": Schema.String, - "title": Schema.String, - "start_index": Schema.Number.check(Schema.isFinite()), - "end_index": Schema.Number.check(Schema.isFinite()) - }) - ]), - Schema.Union([ - Schema.Struct({ - "type": Schema.Never, - "file_id": Schema.String, - "filename": Schema.String, - "index": Schema.Number.check(Schema.isFinite()) - }), - Schema.Struct({ - "type": Schema.Never, - "url": Schema.String, - "title": Schema.String, - "start_index": Schema.Number.check(Schema.isFinite()), - "end_index": Schema.Number.check(Schema.isFinite()), - "file_id": Schema.String, - "index": Schema.Number.check(Schema.isFinite()) - }), - Schema.Struct({ - "type": Schema.Literal("file_path"), - "file_id": Schema.String, - "index": Schema.Number.check(Schema.isFinite()) - }) - ]) - ]) - ) - ), - "logprobs": Schema.optionalKey(Schema.Array(Schema.Struct({ - "token": Schema.String, - "bytes": Schema.Array(Schema.Number.check(Schema.isFinite())), - "logprob": Schema.Number.check(Schema.isFinite()), - "top_logprobs": Schema.Array( - Schema.Struct({ - "token": Schema.String, - "bytes": Schema.Array(Schema.Number.check(Schema.isFinite())), - "logprob": Schema.Number.check(Schema.isFinite()) - }) - ) - }))) - }), - Schema.Struct({ - "type": Schema.Never, - "refusal": Schema.String, - "text": Schema.String, - "annotations": Schema.optionalKey(Schema.Array(OpenAIResponsesAnnotation)), - "logprobs": Schema.optionalKey(Schema.Array(Schema.Struct({ - "token": Schema.String, - "bytes": Schema.Array(Schema.Number.check(Schema.isFinite())), - "logprob": Schema.Number.check(Schema.isFinite()), - "top_logprobs": Schema.Array( - Schema.Struct({ - "token": Schema.String, - "bytes": Schema.Array(Schema.Number.check(Schema.isFinite())), - "logprob": Schema.Number.check(Schema.isFinite()) - }) - ) - }))) - }) - ]), - Schema.Union([ - Schema.Struct({ - "type": Schema.Never, - "text": Schema.String, - "annotations": Schema.optionalKey(Schema.Array(OpenAIResponsesAnnotation)), - "logprobs": Schema.optionalKey(Schema.Array(Schema.Struct({ - "token": Schema.String, - "bytes": Schema.Array(Schema.Number.check(Schema.isFinite())), - "logprob": Schema.Number.check(Schema.isFinite()), - "top_logprobs": Schema.Array( - Schema.Struct({ - "token": Schema.String, - "bytes": Schema.Array(Schema.Number.check(Schema.isFinite())), - "logprob": Schema.Number.check(Schema.isFinite()) - }) - ) - }))), - "refusal": Schema.String - }), - Schema.Struct({ "type": Schema.Literal("refusal"), "refusal": Schema.String }) + Schema.String, + Schema.Number.annotate({ "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ) ]) - ])) - }).annotate({ "description": "An output message item" }), - Schema.Struct({ - "type": Schema.Never, - "id": Schema.String, - "content": Schema.Array(Schema.Union([ - Schema.Struct({ - "type": Schema.Never, - "text": Schema.String, - "annotations": Schema.optionalKey(Schema.Array(OpenAIResponsesAnnotation)), - "logprobs": Schema.optionalKey(Schema.Array(Schema.Struct({ - "token": Schema.String, - "bytes": Schema.Array(Schema.Number.check(Schema.isFinite())), - "logprob": Schema.Number.check(Schema.isFinite()), - "top_logprobs": Schema.Array( - Schema.Struct({ - "token": Schema.String, - "bytes": Schema.Array(Schema.Number.check(Schema.isFinite())), - "logprob": Schema.Number.check(Schema.isFinite()) - }) - ) - }))) - }), - Schema.Struct({ "type": Schema.Never, "refusal": Schema.String, "text": Schema.String }) - ])), - "summary": Schema.Array(ReasoningSummaryText), - "encrypted_content": Schema.optionalKey(Schema.String), - "status": Schema.optionalKey( - Schema.Union([Schema.Literal("completed"), Schema.Literal("incomplete"), Schema.Literal("in_progress")]) - ), - "signature": Schema.optionalKey( - Schema.String.annotate({ "description": "A signature for the reasoning content, used for verification" }) - ), - "format": Schema.optionalKey( - Schema.Literals([ - "unknown", - "openai-responses-v1", - "azure-openai-responses-v1", - "xai-responses-v1", - "anthropic-claude-v1", - "google-gemini-v1" - ]).annotate({ "description": "The format of the reasoning content" }) - ), - "role": Schema.Literal("assistant") - }).annotate({ "description": "An output item containing reasoning" }), - Schema.Struct({ - "type": Schema.Never, - "id": Schema.String, - "name": Schema.String, - "arguments": Schema.String, - "call_id": Schema.String, - "status": Schema.optionalKey( - Schema.Union([Schema.Literal("completed"), Schema.Literal("incomplete"), Schema.Literal("in_progress")]) - ), - "role": Schema.Literal("assistant"), - "content": Schema.Array(Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent])) - }), - Schema.Struct({ - "type": Schema.Never, - "id": Schema.String, - "status": Schema.Union([Schema.Literal("completed"), Schema.Literal("in_progress")]), - "role": Schema.Literal("assistant"), - "content": Schema.Array(Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent])) - }), - Schema.Struct({ - "type": Schema.Never, - "id": Schema.String, - "queries": Schema.Array(Schema.String), - "status": Schema.Union([Schema.Literal("completed"), Schema.Literal("in_progress")]), - "role": Schema.Literal("assistant"), - "content": Schema.Array(Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent])) - }), - Schema.Struct({ - "type": Schema.Never, - "id": Schema.String, - "result": Schema.optionalKey(Schema.String), - "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), - "role": Schema.Literal("assistant"), - "content": Schema.Array(Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent])) - }) - ]).annotate({ "description": "An output item from the response" }), - Schema.Union([ - Schema.Struct({ - "id": Schema.String, - "role": Schema.Literal("assistant"), - "type": Schema.Never, - "status": Schema.optionalKey( - Schema.Union([Schema.Literal("completed"), Schema.Literal("incomplete"), Schema.Literal("in_progress")]) - ), - "content": Schema.Array(Schema.Union([ - Schema.Struct({ - "type": Schema.Never, - "text": Schema.String, - "annotations": Schema.optionalKey(Schema.Array(OpenAIResponsesAnnotation)), - "logprobs": Schema.optionalKey(Schema.Array(Schema.Struct({ - "token": Schema.String, - "bytes": Schema.Array(Schema.Number.check(Schema.isFinite())), - "logprob": Schema.Number.check(Schema.isFinite()), - "top_logprobs": Schema.Array( - Schema.Struct({ - "token": Schema.String, - "bytes": Schema.Array(Schema.Number.check(Schema.isFinite())), - "logprob": Schema.Number.check(Schema.isFinite()) - }) - ) - }))) - }), - Schema.Struct({ "type": Schema.Never, "refusal": Schema.String, "text": Schema.String }) - ])), - "summary": Schema.Array(ReasoningSummaryText), - "encrypted_content": Schema.optionalKey(Schema.String) - }).annotate({ "description": "An output message item" }), - Schema.Struct({ - "type": Schema.Literal("reasoning"), - "id": Schema.String, - "content": Schema.optionalKey( - Schema.Array(Schema.Struct({ "type": Schema.Literal("reasoning_text"), "text": Schema.String })) - ), - "summary": Schema.Array(Schema.Struct({ "type": Schema.Literal("summary_text"), "text": Schema.String })), - "encrypted_content": Schema.optionalKey(Schema.String), - "status": Schema.optionalKey( - Schema.Union([Schema.Literal("completed"), Schema.Literal("incomplete"), Schema.Literal("in_progress")]) - ), - "signature": Schema.optionalKey( - Schema.String.annotate({ "description": "A signature for the reasoning content, used for verification" }) - ), - "format": Schema.optionalKey( - Schema.Literals([ - "unknown", - "openai-responses-v1", - "azure-openai-responses-v1", - "xai-responses-v1", - "anthropic-claude-v1", - "google-gemini-v1" - ]).annotate({ "description": "The format of the reasoning content" }) ) - }).annotate({ "description": "An output item containing reasoning" }), - Schema.Struct({ - "type": Schema.Never, - "id": Schema.String, - "name": Schema.String, - "arguments": Schema.String, - "call_id": Schema.String, - "status": Schema.optionalKey( - Schema.Union([Schema.Literal("completed"), Schema.Literal("incomplete"), Schema.Literal("in_progress")]) - ), - "content": Schema.optionalKey(Schema.Array(ReasoningTextContent)), - "summary": Schema.Array(ReasoningSummaryText), - "encrypted_content": Schema.optionalKey(Schema.String) - }), - Schema.Struct({ - "type": Schema.Never, - "id": Schema.String, - "status": Schema.Union([Schema.Literal("completed"), Schema.Literal("in_progress")]), - "content": Schema.optionalKey(Schema.Array(ReasoningTextContent)), - "summary": Schema.Array(ReasoningSummaryText), - "encrypted_content": Schema.optionalKey(Schema.String) - }), - Schema.Struct({ - "type": Schema.Never, - "id": Schema.String, - "queries": Schema.Array(Schema.String), - "status": Schema.Union([Schema.Literal("completed"), Schema.Literal("in_progress")]), - "content": Schema.optionalKey(Schema.Array(ReasoningTextContent)), - "summary": Schema.Array(ReasoningSummaryText), - "encrypted_content": Schema.optionalKey(Schema.String) - }), - Schema.Struct({ - "type": Schema.Never, - "id": Schema.String, - "result": Schema.optionalKey(Schema.String), - "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), - "content": Schema.optionalKey(Schema.Array(ReasoningTextContent)), - "summary": Schema.Array(ReasoningSummaryText), - "encrypted_content": Schema.optionalKey(Schema.String) - }) - ]).annotate({ "description": "An output item from the response" }), - Schema.Union([ - Schema.Struct({ - "id": Schema.String, - "role": Schema.Literal("assistant"), - "type": Schema.Never, - "status": Schema.optionalKey( - Schema.Union([Schema.Literal("completed"), Schema.Literal("incomplete"), Schema.Literal("in_progress")]) - ), - "content": Schema.Array(Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent])), - "name": Schema.String, - "arguments": Schema.String, - "call_id": Schema.String - }).annotate({ "description": "An output message item" }), - Schema.Struct({ - "type": Schema.Never, - "id": Schema.String, - "content": Schema.optionalKey(Schema.Array(ReasoningTextContent)), - "summary": Schema.Array(ReasoningSummaryText), - "encrypted_content": Schema.optionalKey(Schema.String), - "status": Schema.optionalKey( - Schema.Union([Schema.Literal("completed"), Schema.Literal("incomplete"), Schema.Literal("in_progress")]) - ), - "signature": Schema.optionalKey( - Schema.String.annotate({ "description": "A signature for the reasoning content, used for verification" }) - ), - "format": Schema.optionalKey( - Schema.Literals([ - "unknown", - "openai-responses-v1", - "azure-openai-responses-v1", - "xai-responses-v1", - "anthropic-claude-v1", - "google-gemini-v1" - ]).annotate({ "description": "The format of the reasoning content" }) - ), - "name": Schema.String, - "arguments": Schema.String, - "call_id": Schema.String - }).annotate({ "description": "An output item containing reasoning" }), - Schema.Struct({ - "type": Schema.Literal("function_call"), - "id": Schema.optionalKey(Schema.String), - "name": Schema.String, - "arguments": Schema.String, - "call_id": Schema.String, - "status": Schema.optionalKey( - Schema.Union([Schema.Literal("completed"), Schema.Literal("incomplete"), Schema.Literal("in_progress")]) + ]) + }), + CompoundFilter, + Schema.Null + ])), + "max_num_results": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "ranking_options": Schema.optionalKey( + Schema.Struct({ + "ranker": Schema.optionalKey(Schema.Literals(["auto", "default-2024-11-15"])), + "score_threshold": Schema.optionalKey( + Schema.Number.annotate({ "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) ) - }), - Schema.Struct({ - "type": Schema.Never, - "id": Schema.String, - "status": Schema.Union([Schema.Literal("completed"), Schema.Literal("in_progress")]), - "name": Schema.String, - "arguments": Schema.String, - "call_id": Schema.String - }), - Schema.Struct({ - "type": Schema.Never, - "id": Schema.String, - "queries": Schema.Array(Schema.String), - "status": Schema.Union([Schema.Literal("completed"), Schema.Literal("in_progress")]), - "name": Schema.String, - "arguments": Schema.String, - "call_id": Schema.String - }), - Schema.Struct({ - "type": Schema.Never, - "id": Schema.String, - "result": Schema.optionalKey(Schema.String), - "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), - "name": Schema.String, - "arguments": Schema.String, - "call_id": Schema.String - }) - ]).annotate({ "description": "An output item from the response" }), - Schema.Union([ - Schema.Struct({ - "id": Schema.String, - "role": Schema.Literal("assistant"), - "type": Schema.Never, - "status": Schema.Union([Schema.Literal("completed"), Schema.Literal("in_progress")]), - "content": Schema.Array(Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent])) - }).annotate({ "description": "An output message item" }), - Schema.Struct({ - "type": Schema.Never, - "id": Schema.String, - "content": Schema.optionalKey(Schema.Array(ReasoningTextContent)), - "summary": Schema.Array(ReasoningSummaryText), - "encrypted_content": Schema.optionalKey(Schema.String), - "status": Schema.Union([Schema.Literal("completed"), Schema.Literal("in_progress")]), - "signature": Schema.optionalKey( - Schema.String.annotate({ "description": "A signature for the reasoning content, used for verification" }) - ), - "format": Schema.optionalKey( - Schema.Literals([ - "unknown", - "openai-responses-v1", - "azure-openai-responses-v1", - "xai-responses-v1", - "anthropic-claude-v1", - "google-gemini-v1" - ]).annotate({ "description": "The format of the reasoning content" }) - ) - }).annotate({ "description": "An output item containing reasoning" }), - Schema.Struct({ - "type": Schema.Never, - "id": Schema.String, - "name": Schema.String, - "arguments": Schema.String, - "call_id": Schema.String, - "status": Schema.Union([Schema.Literal("completed"), Schema.Literal("in_progress")]) - }), - Schema.Struct({ - "type": Schema.Literal("web_search_call"), - "id": Schema.String, - "status": Schema.Union([ - Schema.Literal("completed"), - Schema.Literal("searching"), - Schema.Literal("in_progress"), - Schema.Literal("failed") - ]) - }), - Schema.Struct({ - "type": Schema.Never, - "id": Schema.String, - "queries": Schema.Array(Schema.String), - "status": Schema.Union([ - Schema.Literal("completed"), - Schema.Literal("searching"), - Schema.Literal("in_progress"), - Schema.Literal("failed") - ]) - }), - Schema.Struct({ - "type": Schema.Never, - "id": Schema.String, - "result": Schema.optionalKey(Schema.String), - "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed"), Schema.Literal("failed")]) - }) - ]).annotate({ "description": "An output item from the response" }), - Schema.Union([ - Schema.Struct({ - "id": Schema.String, - "role": Schema.Literal("assistant"), - "type": Schema.Never, - "status": Schema.Union([Schema.Literal("completed"), Schema.Literal("in_progress")]), - "content": Schema.Array(Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent])), - "queries": Schema.Array(Schema.String) - }).annotate({ "description": "An output message item" }), - Schema.Struct({ - "type": Schema.Never, - "id": Schema.String, - "content": Schema.optionalKey(Schema.Array(ReasoningTextContent)), - "summary": Schema.Array(ReasoningSummaryText), - "encrypted_content": Schema.optionalKey(Schema.String), - "status": Schema.Union([Schema.Literal("completed"), Schema.Literal("in_progress")]), - "signature": Schema.optionalKey( - Schema.String.annotate({ "description": "A signature for the reasoning content, used for verification" }) - ), - "format": Schema.optionalKey( - Schema.Literals([ - "unknown", - "openai-responses-v1", - "azure-openai-responses-v1", - "xai-responses-v1", - "anthropic-claude-v1", - "google-gemini-v1" - ]).annotate({ "description": "The format of the reasoning content" }) - ), - "queries": Schema.Array(Schema.String) - }).annotate({ "description": "An output item containing reasoning" }), - Schema.Struct({ - "type": Schema.Never, - "id": Schema.String, - "name": Schema.String, - "arguments": Schema.String, - "call_id": Schema.String, - "status": Schema.Union([Schema.Literal("completed"), Schema.Literal("in_progress")]), - "queries": Schema.Array(Schema.String) - }), - Schema.Struct({ - "type": Schema.Never, - "id": Schema.String, - "status": Schema.Union([ - Schema.Literal("completed"), - Schema.Literal("searching"), - Schema.Literal("in_progress"), - Schema.Literal("failed") - ]), - "queries": Schema.Array(Schema.String) - }), - Schema.Struct({ - "type": Schema.Literal("file_search_call"), - "id": Schema.String, - "queries": Schema.Array(Schema.String), - "status": Schema.Union([ - Schema.Literal("completed"), - Schema.Literal("searching"), - Schema.Literal("in_progress"), - Schema.Literal("failed") - ]) - }), - Schema.Struct({ - "type": Schema.Never, - "id": Schema.String, - "result": Schema.optionalKey(Schema.String), - "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed"), Schema.Literal("failed")]), - "queries": Schema.Array(Schema.String) - }) - ]).annotate({ "description": "An output item from the response" }), - Schema.Union([ - Schema.Struct({ - "id": Schema.String, - "role": Schema.Literal("assistant"), - "type": Schema.Never, - "status": Schema.Union([Schema.Literal("completed"), Schema.Literal("in_progress")]), - "content": Schema.Array(Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent])), - "result": Schema.optionalKey(Schema.String) - }).annotate({ "description": "An output message item" }), - Schema.Struct({ - "type": Schema.Never, - "id": Schema.String, - "content": Schema.optionalKey(Schema.Array(ReasoningTextContent)), - "summary": Schema.Array(ReasoningSummaryText), - "encrypted_content": Schema.optionalKey(Schema.String), - "status": Schema.Union([Schema.Literal("completed"), Schema.Literal("in_progress")]), - "signature": Schema.optionalKey( - Schema.String.annotate({ "description": "A signature for the reasoning content, used for verification" }) - ), - "format": Schema.optionalKey( - Schema.Literals([ - "unknown", - "openai-responses-v1", - "azure-openai-responses-v1", - "xai-responses-v1", - "anthropic-claude-v1", - "google-gemini-v1" - ]).annotate({ "description": "The format of the reasoning content" }) - ), - "result": Schema.optionalKey(Schema.String) - }).annotate({ "description": "An output item containing reasoning" }), - Schema.Struct({ - "type": Schema.Never, - "id": Schema.String, - "name": Schema.String, - "arguments": Schema.String, - "call_id": Schema.String, - "status": Schema.Union([Schema.Literal("completed"), Schema.Literal("in_progress")]), - "result": Schema.optionalKey(Schema.String) - }), - Schema.Struct({ - "type": Schema.Never, - "id": Schema.String, - "status": Schema.Union([Schema.Literal("completed"), Schema.Literal("in_progress"), Schema.Literal("failed")]), - "result": Schema.optionalKey(Schema.String) - }), - Schema.Struct({ - "type": Schema.Never, - "id": Schema.String, - "queries": Schema.Array(Schema.String), - "status": Schema.Union([Schema.Literal("completed"), Schema.Literal("in_progress"), Schema.Literal("failed")]), - "result": Schema.optionalKey(Schema.String) - }), - Schema.Struct({ - "type": Schema.Literal("image_generation_call"), - "id": Schema.String, - "result": Schema.optionalKey(Schema.String), - "status": Schema.Union([ - Schema.Literal("in_progress"), - Schema.Literal("completed"), - Schema.Literal("generating"), - Schema.Literal("failed") - ]) - }) - ]).annotate({ "description": "An output item from the response" }) - ])), - "user": Schema.optionalKey(Schema.String), - "output_text": Schema.optionalKey(Schema.String), - "prompt_cache_key": Schema.optionalKey(Schema.String), - "safety_identifier": Schema.optionalKey(Schema.String), - "error": ResponsesErrorField, - "incomplete_details": OpenAIResponsesIncompleteDetails, - "usage": Schema.optionalKey( - Schema.Struct({ - "input_tokens": Schema.Number.check(Schema.isFinite()), - "input_tokens_details": Schema.Struct({ "cached_tokens": Schema.Number.check(Schema.isFinite()) }), - "output_tokens": Schema.Number.check(Schema.isFinite()), - "output_tokens_details": Schema.Struct({ "reasoning_tokens": Schema.Number.check(Schema.isFinite()) }), - "total_tokens": Schema.Number.check(Schema.isFinite()), - "cost": Schema.optionalKey( - Schema.Number.annotate({ "description": "Cost of the completion" }).check(Schema.isFinite()) - ), - "is_byok": Schema.optionalKey( - Schema.Boolean.annotate({ - "description": "Whether a request was made using a Bring Your Own Key configuration" - }) - ), - "cost_details": Schema.optionalKey( - Schema.Struct({ - "upstream_inference_cost": Schema.optionalKey(Schema.Number.check(Schema.isFinite())), - "upstream_inference_input_cost": Schema.Number.check(Schema.isFinite()), - "upstream_inference_output_cost": Schema.Number.check(Schema.isFinite()) - }) ) - }).annotate({ "description": "Token usage information for the response" }) - ), - "max_tool_calls": Schema.optionalKey(Schema.Number.check(Schema.isFinite())), - "top_logprobs": Schema.optionalKey(Schema.Number.check(Schema.isFinite())), - "max_output_tokens": Schema.optionalKey(Schema.Number.check(Schema.isFinite())), - "temperature": Schema.Number.check(Schema.isFinite()), - "top_p": Schema.Number.check(Schema.isFinite()), - "presence_penalty": Schema.Number.check(Schema.isFinite()), - "frequency_penalty": Schema.Number.check(Schema.isFinite()), - "instructions": OpenAIResponsesInput, - "metadata": OpenResponsesRequestMetadata, - "tools": Schema.Array( - Schema.Union([ - Schema.Struct({ - "type": Schema.Literal("function"), - "name": Schema.String, - "description": Schema.optionalKey(Schema.String), - "strict": Schema.optionalKey(Schema.Boolean), - "parameters": Schema.Struct({}) - }).annotate({ "description": "Function tool definition" }), - OpenResponsesWebSearchPreviewTool, - OpenResponsesWebSearchPreview20250311Tool, - OpenResponsesWebSearchTool, - OpenResponsesWebSearch20250826Tool - ], { mode: "oneOf" }) + }) ), - "tool_choice": OpenAIResponsesToolChoice, - "parallel_tool_calls": Schema.Boolean, - "prompt": Schema.optionalKey(OpenAIResponsesPrompt), - "background": Schema.optionalKey(Schema.Boolean), - "previous_response_id": Schema.optionalKey(Schema.String), - "reasoning": Schema.optionalKey(OpenAIResponsesReasoningConfig), - "service_tier": Schema.optionalKey(OpenAIResponsesServiceTier), - "store": Schema.optionalKey(Schema.Boolean), - "truncation": Schema.optionalKey(OpenAIResponsesTruncation), - "text": Schema.optionalKey(ResponseTextConfig) -}).annotate({ "description": "Complete non-streaming response from the Responses API" }) -export type OpenResponsesRequest = { - readonly "input"?: OpenResponsesInput - readonly "instructions"?: string - readonly "metadata"?: OpenResponsesRequestMetadata - readonly "tools"?: ReadonlyArray< - | { - readonly "type": "function" - readonly "name": string - readonly "description"?: string - readonly "strict"?: boolean - readonly "parameters": {} - } - | OpenResponsesWebSearchPreviewTool - | OpenResponsesWebSearchPreview20250311Tool - | OpenResponsesWebSearchTool - | OpenResponsesWebSearch20250826Tool - > - readonly "tool_choice"?: OpenAIResponsesToolChoice - readonly "parallel_tool_calls"?: boolean - readonly "model"?: string - readonly "models"?: ReadonlyArray - readonly "text"?: OpenResponsesResponseText - readonly "reasoning"?: OpenResponsesReasoningConfig - readonly "max_output_tokens"?: number - readonly "temperature"?: number - readonly "top_p"?: number - readonly "top_logprobs"?: number - readonly "max_tool_calls"?: number - readonly "presence_penalty"?: number - readonly "frequency_penalty"?: number - readonly "top_k"?: number - readonly "image_config"?: {} - readonly "modalities"?: ReadonlyArray - readonly "prompt_cache_key"?: string - readonly "previous_response_id"?: string - readonly "prompt"?: OpenAIResponsesPrompt - readonly "include"?: ReadonlyArray - readonly "background"?: boolean - readonly "safety_identifier"?: string - readonly "store"?: false - readonly "service_tier"?: "auto" - readonly "truncation"?: "auto" | "disabled" - readonly "stream"?: boolean - readonly "provider"?: { - readonly "allow_fallbacks"?: boolean - readonly "require_parameters"?: boolean - readonly "data_collection"?: DataCollection - readonly "zdr"?: boolean - readonly "enforce_distillable_text"?: boolean - readonly "order"?: ReadonlyArray - readonly "only"?: ReadonlyArray - readonly "ignore"?: ReadonlyArray - readonly "quantizations"?: ReadonlyArray - readonly "sort"?: ProviderSort | ProviderSortConfig | unknown - readonly "max_price"?: { - readonly "prompt"?: BigNumberUnion - readonly "completion"?: string - readonly "image"?: string - readonly "audio"?: string - readonly "request"?: string - } - readonly "preferred_min_throughput"?: PreferredMinThroughput - readonly "preferred_max_latency"?: PreferredMaxLatency - } - readonly "plugins"?: ReadonlyArray< - | { readonly "id": "auto-router"; readonly "enabled"?: boolean; readonly "allowed_models"?: ReadonlyArray } - | { readonly "id": "moderation" } - | { - readonly "id": "web" - readonly "enabled"?: boolean - readonly "max_results"?: number - readonly "search_prompt"?: string - readonly "engine"?: WebSearchEngine - } - | { readonly "id": "file-parser"; readonly "enabled"?: boolean; readonly "pdf"?: PDFParserOptions } - | { readonly "id": "response-healing"; readonly "enabled"?: boolean } - > - readonly "route"?: "fallback" | "sort" - readonly "user"?: string - readonly "session_id"?: string - readonly "trace"?: { - readonly "trace_id"?: string - readonly "trace_name"?: string - readonly "span_name"?: string - readonly "generation_name"?: string - readonly "parent_span_id"?: string - } + "type": Schema.Literal("file_search"), + "vector_store_ids": Schema.Array(Schema.String) +}).annotate({ "description": "File search tool configuration", "identifier": "FileSearchServerTool" }) +export type ConflictResponse = { + readonly "error": ConflictResponseErrorData + readonly "openrouter_metadata"?: { readonly [x: string]: Schema.Json } | null + readonly "user_id"?: string | null } -export const OpenResponsesRequest = Schema.Struct({ - "input": Schema.optionalKey(OpenResponsesInput), - "instructions": Schema.optionalKey(Schema.String), - "metadata": Schema.optionalKey(OpenResponsesRequestMetadata), - "tools": Schema.optionalKey( - Schema.Array( - Schema.Union([ - Schema.Struct({ - "type": Schema.Literal("function"), - "name": Schema.String, - "description": Schema.optionalKey(Schema.String), - "strict": Schema.optionalKey(Schema.Boolean), - "parameters": Schema.Struct({}) - }).annotate({ "description": "Function tool definition" }), - OpenResponsesWebSearchPreviewTool, - OpenResponsesWebSearchPreview20250311Tool, - OpenResponsesWebSearchTool, - OpenResponsesWebSearch20250826Tool - ], { mode: "oneOf" }) - ) +export const ConflictResponse = Schema.Struct({ + "error": ConflictResponseErrorData, + "openrouter_metadata": Schema.optionalKey( + Schema.Union([Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })), Schema.Null]) ), - "tool_choice": Schema.optionalKey(OpenAIResponsesToolChoice), - "parallel_tool_calls": Schema.optionalKey(Schema.Boolean), - "model": Schema.optionalKey(Schema.String), - "models": Schema.optionalKey(Schema.Array(Schema.String)), - "text": Schema.optionalKey(OpenResponsesResponseText), - "reasoning": Schema.optionalKey(OpenResponsesReasoningConfig), - "max_output_tokens": Schema.optionalKey(Schema.Number.check(Schema.isFinite())), - "temperature": Schema.optionalKey( - Schema.Number.check(Schema.isFinite()).check(Schema.isGreaterThanOrEqualTo(0)).check(Schema.isLessThanOrEqualTo(2)) - ), - "top_p": Schema.optionalKey(Schema.Number.check(Schema.isFinite()).check(Schema.isGreaterThanOrEqualTo(0))), - "top_logprobs": Schema.optionalKey( - Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0)).check(Schema.isLessThanOrEqualTo(20)) - ), - "max_tool_calls": Schema.optionalKey(Schema.Number.check(Schema.isInt())), - "presence_penalty": Schema.optionalKey( - Schema.Number.check(Schema.isFinite()).check(Schema.isGreaterThanOrEqualTo(-2)).check(Schema.isLessThanOrEqualTo(2)) - ), - "frequency_penalty": Schema.optionalKey( - Schema.Number.check(Schema.isFinite()).check(Schema.isGreaterThanOrEqualTo(-2)).check(Schema.isLessThanOrEqualTo(2)) + "user_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) +}).annotate({ + "description": "Conflict - Resource conflict or concurrent modification", + "identifier": "ConflictResponse" +}) +export type BashServerToolEnvironment = ContainerAutoEnvironment | ContainerReferenceEnvironment +export const BashServerToolEnvironment = Schema.Union([ContainerAutoEnvironment, ContainerReferenceEnvironment], { + mode: "oneOf" +}).annotate({ + "description": "Execution environment for the bash server tool.", + "identifier": "BashServerToolEnvironment" +}) +export type ShellServerToolEnvironment = ContainerAutoEnvironment | ContainerReferenceEnvironment +export const ShellServerToolEnvironment = Schema.Union([ContainerAutoEnvironment, ContainerReferenceEnvironment], { + mode: "oneOf" +}).annotate({ + "description": + "Server-side execution environment for the shell tool. Only container-backed environments are supported; \"local\" shells are not.", + "identifier": "ShellServerToolEnvironment" +}) +export type ContentFilterEntry = { + readonly "action": ContentFilterAction + readonly "label"?: string | null + readonly "pattern": string +} +export const ContentFilterEntry = Schema.Struct({ + "action": ContentFilterAction, + "label": Schema.optionalKey( + Schema.Union([ + Schema.String.check(Schema.isMaxLength(100).annotate({ "expected": "a value with a length of at most 100" })), + Schema.Null + ]).annotate({ "description": "Optional label used in redaction placeholders or error messages" }) ), - "top_k": Schema.optionalKey(Schema.Number.check(Schema.isFinite())), - "image_config": Schema.optionalKey( - Schema.Struct({}).annotate({ - "description": - "Provider-specific image configuration options. Keys and values vary by model/provider. See https://openrouter.ai/docs/features/multimodal/image-generation for more details." + "pattern": Schema.String.annotate({ "description": "A regex pattern to match against request content" }).check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ) +}).annotate({ + "description": "A custom regex content filter that scans request messages for matching patterns.", + "identifier": "ContentFilterEntry" +}) +export type InputReference = ContentPartImage | ContentPartAudio | ContentPartVideo +export const InputReference = Schema.Union([ContentPartImage, ContentPartAudio, ContentPartVideo], { mode: "oneOf" }) + .annotate({ + "description": + "A reference asset used to guide video generation. Image references are supported by all providers; audio and video references are only honored by providers that support them (currently BytePlus Seedance 2.0).", + "identifier": "InputReference" + }) +export type ContextCompressionPlugin = { + readonly "enabled"?: boolean + readonly "engine"?: ContextCompressionEngine + readonly "id": "context-compression" +} +export const ContextCompressionPlugin = Schema.Struct({ + "enabled": Schema.optionalKey( + Schema.Boolean.annotate({ + "description": "Set to false to disable the context-compression plugin for this request. Defaults to true." }) ), - "modalities": Schema.optionalKey( - Schema.Array(ResponsesOutputModality).annotate({ - "description": "Output modalities for the response. Supported values are \"text\" and \"image\"." - }) + "engine": Schema.optionalKey(ContextCompressionEngine), + "id": Schema.Literal("context-compression") +}).annotate({ "identifier": "ContextCompressionPlugin" }) +export type CostDetails = Objects_8 | null +export const CostDetails = Schema.Union([Objects_8, Schema.Null]).annotate({ + "description": "Breakdown of upstream inference costs", + "identifier": "CostDetails" +}) +export type Arrays_9 = ReadonlyArray +export const Arrays_9 = Schema.Array(DABenchmarkEntry).annotate({ + "description": "Design Arena ELO rankings across arena+category pairs." +}) +export type DatetimeServerTool = { + readonly "parameters"?: DatetimeServerToolConfig + readonly "type": "openrouter:datetime" +} +export const DatetimeServerTool = Schema.Struct({ + "parameters": Schema.optionalKey(DatetimeServerToolConfig), + "type": Schema.Literal("openrouter:datetime") +}).annotate({ + "description": "OpenRouter built-in server tool: returns the current date and time", + "identifier": "DatetimeServerTool" +}) +export type EdgeNetworkTimeoutResponse = { + readonly "error": EdgeNetworkTimeoutResponseErrorData + readonly "openrouter_metadata"?: { readonly [x: string]: Schema.Json } | null + readonly "user_id"?: string | null +} +export const EdgeNetworkTimeoutResponse = Schema.Struct({ + "error": EdgeNetworkTimeoutResponseErrorData, + "openrouter_metadata": Schema.optionalKey( + Schema.Union([Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })), Schema.Null]) ), - "prompt_cache_key": Schema.optionalKey(Schema.String), - "previous_response_id": Schema.optionalKey(Schema.String), - "prompt": Schema.optionalKey(OpenAIResponsesPrompt), - "include": Schema.optionalKey(Schema.Array(OpenAIResponsesIncludable)), - "background": Schema.optionalKey(Schema.Boolean), - "safety_identifier": Schema.optionalKey(Schema.String), - "store": Schema.optionalKey(Schema.Literal(false)), - "service_tier": Schema.optionalKey(Schema.Literal("auto")), - "truncation": Schema.optionalKey(Schema.Literals(["auto", "disabled"])), - "stream": Schema.optionalKey(Schema.Boolean), - "provider": Schema.optionalKey( - Schema.Struct({ - "allow_fallbacks": Schema.optionalKey(Schema.Boolean.annotate({ - "description": - "Whether to allow backup providers to serve requests\n- true: (default) when the primary provider (or your custom providers in \"order\") is unavailable, use the next best provider.\n- false: use only the primary/custom provider, and return the upstream error if it's unavailable.\n" - })), - "require_parameters": Schema.optionalKey( - Schema.Boolean.annotate({ - "description": - "Whether to filter providers to only those that support the parameters you've provided. If this setting is omitted or set to false, then providers will receive only the parameters they support, and ignore the rest." - }) - ), - "data_collection": Schema.optionalKey(DataCollection), - "zdr": Schema.optionalKey( - Schema.Boolean.annotate({ - "description": - "Whether to restrict routing to only ZDR (Zero Data Retention) endpoints. When true, only endpoints that do not retain prompts will be used." - }) - ), - "enforce_distillable_text": Schema.optionalKey( - Schema.Boolean.annotate({ - "description": - "Whether to restrict routing to only models that allow text distillation. When true, only models where the author has allowed distillation will be used." - }) - ), - "order": Schema.optionalKey( - Schema.Array(Schema.Union([ProviderName, Schema.String])).annotate({ - "description": - "An ordered list of provider slugs. The router will attempt to use the first provider in the subset of this list that supports your requested model, and fall back to the next if it is unavailable. If no providers are available, the request will fail with an error message." - }) - ), - "only": Schema.optionalKey( - Schema.Array(Schema.Union([ProviderName, Schema.String])).annotate({ - "description": - "List of provider slugs to allow. If provided, this list is merged with your account-wide allowed provider settings for this request." - }) - ), - "ignore": Schema.optionalKey( - Schema.Array(Schema.Union([ProviderName, Schema.String])).annotate({ - "description": - "List of provider slugs to ignore. If provided, this list is merged with your account-wide ignored provider settings for this request." - }) - ), - "quantizations": Schema.optionalKey( - Schema.Array(Quantization).annotate({ - "description": "A list of quantization levels to filter the provider by." - }) - ), - "sort": Schema.optionalKey( - Schema.Union([ProviderSort, ProviderSortConfig, Schema.Unknown]).annotate({ - "description": - "The sorting strategy to use for this request, if \"order\" is not specified. When set, no load balancing is performed." - }) - ), - "max_price": Schema.optionalKey( - Schema.Struct({ - "prompt": Schema.optionalKey(BigNumberUnion), - "completion": Schema.optionalKey( - Schema.String.annotate({ "description": "Price per million completion tokens" }) - ), - "image": Schema.optionalKey(Schema.String.annotate({ "description": "Price per image" })), - "audio": Schema.optionalKey(Schema.String.annotate({ "description": "Price per audio unit" })), - "request": Schema.optionalKey(Schema.String.annotate({ "description": "Price per request" })) - }).annotate({ - "description": - "The object specifying the maximum price you want to pay for this request. USD price per million tokens, for prompt and completion." - }) - ), - "preferred_min_throughput": Schema.optionalKey(PreferredMinThroughput), - "preferred_max_latency": Schema.optionalKey(PreferredMaxLatency) - }).annotate({ - "description": "When multiple model providers are available, optionally indicate your routing preference." - }) - ), - "plugins": Schema.optionalKey( - Schema.Array(Schema.Union([ - Schema.Struct({ - "id": Schema.Literal("auto-router"), - "enabled": Schema.optionalKey( - Schema.Boolean.annotate({ - "description": "Set to false to disable the auto-router plugin for this request. Defaults to true." - }) - ), - "allowed_models": Schema.optionalKey( - Schema.Array(Schema.String).annotate({ - "description": - "List of model patterns to filter which models the auto-router can route between. Supports wildcards (e.g., \"anthropic/*\" matches all Anthropic models). When not specified, uses the default supported models list." - }) - ) - }), - Schema.Struct({ "id": Schema.Literal("moderation") }), - Schema.Struct({ - "id": Schema.Literal("web"), - "enabled": Schema.optionalKey( - Schema.Boolean.annotate({ - "description": "Set to false to disable the web-search plugin for this request. Defaults to true." - }) - ), - "max_results": Schema.optionalKey(Schema.Number.check(Schema.isFinite())), - "search_prompt": Schema.optionalKey(Schema.String), - "engine": Schema.optionalKey(WebSearchEngine) - }), - Schema.Struct({ - "id": Schema.Literal("file-parser"), - "enabled": Schema.optionalKey( - Schema.Boolean.annotate({ - "description": "Set to false to disable the file-parser plugin for this request. Defaults to true." - }) - ), - "pdf": Schema.optionalKey(PDFParserOptions) - }), - Schema.Struct({ - "id": Schema.Literal("response-healing"), - "enabled": Schema.optionalKey( - Schema.Boolean.annotate({ - "description": "Set to false to disable the response-healing plugin for this request. Defaults to true." - }) - ) - }) - ], { mode: "oneOf" })).annotate({ - "description": "Plugins you want to enable for this request, including their settings." - }) - ), - "route": Schema.optionalKey( - Schema.Literals(["fallback", "sort"]).annotate({ - "description": - "**DEPRECATED** Use providers.sort.partition instead. Backwards-compatible alias for providers.sort.partition. Accepts legacy values: \"fallback\" (maps to \"model\"), \"sort\" (maps to \"none\")." - }) + "user_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) +}).annotate({ + "description": "Infrastructure Timeout - Provider request timed out at edge network", + "identifier": "EdgeNetworkTimeoutResponse" +}) +export type EndpointsMetadata = { readonly "available": ReadonlyArray; readonly "total": number } +export const EndpointsMetadata = Schema.Struct({ + "available": Schema.Array(EndpointInfo), + "total": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) +}).annotate({ "identifier": "EndpointsMetadata" }) +export type FileListResponse = { + readonly "cursor": string | null + readonly "data": ReadonlyArray + readonly "first_id": string | null + readonly "has_more": boolean + readonly "last_id": string | null +} +export const FileListResponse = Schema.Struct({ + "cursor": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "Opaque cursor for the next page; null when there are no more results." + }), + "data": Schema.Array(FileMetadata), + "first_id": Schema.Union([Schema.String, Schema.Null]), + "has_more": Schema.Boolean, + "last_id": Schema.Union([Schema.String, Schema.Null]) +}).annotate({ + "description": "A page of files belonging to the requesting workspace.", + "identifier": "FileListResponse" +}) +export type FilesServerTool = { readonly "parameters"?: FilesServerToolConfig; readonly "type": "openrouter:files" } +export const FilesServerTool = Schema.Struct({ + "parameters": Schema.optionalKey(FilesServerToolConfig), + "type": Schema.Literal("openrouter:files") +}).annotate({ + "description": + "OpenRouter built-in server tool: read, write, edit, and list workspace files via the Files API. Requires the `x-openrouter-file-ids: openrouter` request header.", + "identifier": "FilesServerTool" +}) +export type ForbiddenResponse = { + readonly "error": ForbiddenResponseErrorData + readonly "openrouter_metadata"?: { readonly [x: string]: Schema.Json } | null + readonly "user_id"?: string | null +} +export const ForbiddenResponse = Schema.Struct({ + "error": ForbiddenResponseErrorData, + "openrouter_metadata": Schema.optionalKey( + Schema.Union([Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })), Schema.Null]) ), - "user": Schema.optionalKey( - Schema.String.annotate({ - "description": - "A unique identifier representing your end-user, which helps distinguish between different users of your app. This allows your app to identify specific users in case of abuse reports, preventing your entire app from being affected by the actions of individual users. Maximum of 128 characters." - }).check(Schema.isMaxLength(128)) + "user_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) +}).annotate({ + "description": "Forbidden - Authentication successful but insufficient permissions", + "identifier": "ForbiddenResponse" +}) +export type Formats = FormatTextConfig | FormatJsonObjectConfig | FormatJsonSchemaConfig +export const Formats = Schema.Union([FormatTextConfig, FormatJsonObjectConfig, FormatJsonSchemaConfig]).annotate({ + "description": "Text response format configuration", + "identifier": "Formats" +}) +export type FusionAnalysisResult = { + readonly "blind_spots": Arrays_1 + readonly "consensus": Arrays_2 + readonly "contradictions": Arrays_3 + readonly "partial_coverage": Arrays_4 + readonly "unique_insights": Arrays_5 +} +export const FusionAnalysisResult = Schema.Struct({ + "blind_spots": Arrays_1, + "consensus": Arrays_2, + "contradictions": Arrays_3, + "partial_coverage": Arrays_4, + "unique_insights": Arrays_5 +}).annotate({ + "description": "Structured analysis produced by the fusion judge model.", + "identifier": "FusionAnalysisResult" +}) +export type GenerationContentResponse = { readonly "data": GenerationContentData } +export const GenerationContentResponse = Schema.Struct({ "data": GenerationContentData }).annotate({ + "description": "Stored prompt and completion content for a generation", + "identifier": "GenerationContentResponse" +}) +export type GoneResponse = { + readonly "error": GoneResponseErrorData + readonly "openrouter_metadata"?: { readonly [x: string]: Schema.Json } | null + readonly "user_id"?: string | null +} +export const GoneResponse = Schema.Struct({ + "error": GoneResponseErrorData, + "openrouter_metadata": Schema.optionalKey( + Schema.Union([Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })), Schema.Null]) ), - "session_id": Schema.optionalKey( - Schema.String.annotate({ - "description": - "A unique identifier for grouping related requests (e.g., a conversation or agent workflow) for observability. If provided in both the request body and the x-session-id header, the body value takes precedence. Maximum of 128 characters." - }).check(Schema.isMaxLength(128)) + "user_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) +}).annotate({ + "description": "Gone - Endpoint has been permanently removed or deprecated", + "identifier": "GoneResponse" +}) +export type ImageGenerationServerTool_OpenRouter = { + readonly "parameters"?: ImageGenerationServerToolConfig + readonly "type": "openrouter:image_generation" +} +export const ImageGenerationServerTool_OpenRouter = Schema.Struct({ + "parameters": Schema.optionalKey(ImageGenerationServerToolConfig), + "type": Schema.Literal("openrouter:image_generation") +}).annotate({ + "description": "OpenRouter built-in server tool: generates images from text prompts using an image generation model", + "identifier": "ImageGenerationServerTool_OpenRouter" +}) +export type OutputImageGenerationCallItem = { + readonly "id": string + readonly "result"?: string | null + readonly "status": ImageGenerationStatus + readonly "type": "image_generation_call" + readonly "prompt"?: string +} +export const OutputImageGenerationCallItem = Schema.Struct({ + "id": Schema.String, + "result": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "status": ImageGenerationStatus, + "type": Schema.Literal("image_generation_call"), + "prompt": Schema.optionalKey( + Schema.String.annotate({ "description": "The prompt (possibly rewritten) that the image was generated from." }) + ) +}).annotate({ "identifier": "OutputImageGenerationCallItem" }) +export type OutputItemImageGenerationCall = { + readonly "id": string + readonly "result"?: string | null + readonly "status": ImageGenerationStatus + readonly "type": "image_generation_call" +} +export const OutputItemImageGenerationCall = Schema.Struct({ + "id": Schema.String, + "result": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "status": ImageGenerationStatus, + "type": Schema.Literal("image_generation_call") +}).annotate({ "identifier": "OutputItemImageGenerationCall" }) +export type ImageModelArchitecture = { + readonly "input_modalities": ReadonlyArray + readonly "output_modalities": ReadonlyArray +} +export const ImageModelArchitecture = Schema.Struct({ + "input_modalities": Schema.Array(ImageInputModality).annotate({ "description": "Supported input modalities" }), + "output_modalities": Schema.Array(ImageOutputModality).annotate({ "description": "Supported output modalities" }) +}).annotate({ "identifier": "ImageModelArchitecture" }) +export type InternalServerResponse = { + readonly "error": InternalServerResponseErrorData + readonly "openrouter_metadata"?: { readonly [x: string]: Schema.Json } | null + readonly "user_id"?: string | null +} +export const InternalServerResponse = Schema.Struct({ + "error": InternalServerResponseErrorData, + "openrouter_metadata": Schema.optionalKey( + Schema.Union([Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })), Schema.Null]) ), - "trace": Schema.optionalKey( - Schema.Struct({ - "trace_id": Schema.optionalKey(Schema.String), - "trace_name": Schema.optionalKey(Schema.String), - "span_name": Schema.optionalKey(Schema.String), - "generation_name": Schema.optionalKey(Schema.String), - "parent_span_id": Schema.optionalKey(Schema.String) - }).annotate({ - "description": - "Metadata for observability and tracing. Known keys (trace_id, trace_name, span_name, generation_name, parent_span_id) have special handling. Additional keys are passed through as custom metadata to configured broadcast destinations." - }) + "user_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) +}).annotate({ + "description": "Internal Server Error - Unexpected server error", + "identifier": "InternalServerResponse" +}) +export type ListKeyAssignmentsResponse = { + readonly "data": ReadonlyArray + readonly "total_count": number +} +export const ListKeyAssignmentsResponse = Schema.Struct({ + "data": Schema.Array(KeyAssignment).annotate({ "description": "List of key assignments" }), + "total_count": Schema.Number.annotate({ "description": "Total number of key assignments for this guardrail" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) ) -}).annotate({ "description": "Request schema for Responses endpoint" }) -export type ChatGenerationParams = { - readonly "provider"?: { - readonly "allow_fallbacks"?: boolean | null - readonly "require_parameters"?: boolean | null - readonly "data_collection"?: "deny" | "allow" | null - readonly "zdr"?: boolean | null - readonly "enforce_distillable_text"?: boolean | null - readonly "order"?: __schema5 | null - readonly "only"?: __schema5 | null - readonly "ignore"?: __schema5 | null - readonly "quantizations"?: - | ReadonlyArray<"int4" | "int8" | "fp4" | "fp6" | "fp8" | "fp16" | "bf16" | "fp32" | "unknown"> - | null - readonly "sort"?: ProviderSortUnion | null - readonly "max_price"?: { - readonly "prompt"?: __schema11 | ModelName | __schema13 - readonly "completion"?: __schema11 | ModelName | __schema13 - readonly "image"?: __schema14 - readonly "audio"?: __schema14 - readonly "request"?: __schema14 - } - readonly "preferred_min_throughput"?: number | { - readonly "p50"?: number | null - readonly "p75"?: number | null - readonly "p90"?: number | null - readonly "p99"?: number | null - } | null - readonly "preferred_max_latency"?: number | { - readonly "p50"?: number | null - readonly "p75"?: number | null - readonly "p90"?: number | null - readonly "p99"?: number | null - } | null - } | null - readonly "plugins"?: ReadonlyArray< - | { readonly "id": "auto-router"; readonly "enabled"?: boolean; readonly "allowed_models"?: ReadonlyArray } - | { readonly "id": "moderation" } - | { - readonly "id": "web" - readonly "enabled"?: boolean - readonly "max_results"?: number - readonly "search_prompt"?: string - readonly "engine"?: "native" | "exa" - } - | { - readonly "id": "file-parser" - readonly "enabled"?: boolean - readonly "pdf"?: { readonly "engine"?: "mistral-ocr" | "pdf-text" | "native" } - } - | { readonly "id": "response-healing"; readonly "enabled"?: boolean } - > - readonly "route"?: "fallback" | "sort" | null - readonly "user"?: string - readonly "session_id"?: string - readonly "trace"?: { - readonly "trace_id"?: string - readonly "trace_name"?: string - readonly "span_name"?: string - readonly "generation_name"?: string - readonly "parent_span_id"?: string - } - readonly "messages": ReadonlyArray - readonly "model"?: ModelName - readonly "models"?: ReadonlyArray - readonly "frequency_penalty"?: number | null - readonly "logit_bias"?: {} | null - readonly "logprobs"?: boolean | null - readonly "top_logprobs"?: number | null - readonly "max_completion_tokens"?: number | null - readonly "max_tokens"?: number | null - readonly "metadata"?: {} - readonly "presence_penalty"?: number | null - readonly "reasoning"?: { - readonly "effort"?: "xhigh" | "high" | "medium" | "low" | "minimal" | "none" | null - readonly "summary"?: ReasoningSummaryVerbosity | null - } - readonly "response_format"?: - | { readonly "type": "text" } - | { readonly "type": "json_object" } - | ResponseFormatJSONSchema - | ResponseFormatTextGrammar - | { readonly "type": "python" } - readonly "seed"?: number | null - readonly "stop"?: string | ReadonlyArray | null - readonly "stream"?: boolean - readonly "stream_options"?: ChatStreamOptions | null - readonly "temperature"?: number | null - readonly "parallel_tool_calls"?: boolean | null - readonly "tool_choice"?: ToolChoiceOption - readonly "tools"?: ReadonlyArray - readonly "top_p"?: number | null - readonly "debug"?: { readonly "echo_upstream_body"?: boolean } - readonly "image_config"?: {} - readonly "modalities"?: ReadonlyArray<"text" | "image"> +}).annotate({ "identifier": "ListKeyAssignmentsResponse" }) +export type Legacy_ChatContentVideo = { + readonly "type": "input_video" + readonly "video_url": Legacy_ChatContentVideoInput } -export const ChatGenerationParams = Schema.Struct({ - "provider": Schema.optionalKey( +export const Legacy_ChatContentVideo = Schema.Struct({ + "type": Schema.Literal("input_video"), + "video_url": Legacy_ChatContentVideoInput +}).annotate({ + "description": "Video input content part (legacy format - deprecated)", + "identifier": "Legacy_ChatContentVideo" +}) +export type ListMemberAssignmentsResponse = { + readonly "data": ReadonlyArray + readonly "total_count": number +} +export const ListMemberAssignmentsResponse = Schema.Struct({ + "data": Schema.Array(MemberAssignment).annotate({ "description": "List of member assignments" }), + "total_count": Schema.Number.annotate({ "description": "Total number of member assignments" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ) +}).annotate({ "identifier": "ListMemberAssignmentsResponse" }) +export type MessagesOutputConfig = { + readonly "effort"?: "low" | "medium" | "high" | "xhigh" | "max" | null + readonly "format"?: Union_10 + readonly "task_budget"?: Union_11 +} +export const MessagesOutputConfig = Schema.Struct({ + "effort": Schema.optionalKey( Schema.Union([ - Schema.Struct({ - "allow_fallbacks": Schema.optionalKey( - Schema.Union([Schema.Boolean, Schema.Null]).annotate({ - "description": - "Whether to allow backup providers to serve requests\n- true: (default) when the primary provider (or your custom providers in \"order\") is unavailable, use the next best provider.\n- false: use only the primary/custom provider, and return the upstream error if it's unavailable.\n" - }) - ), - "require_parameters": Schema.optionalKey( - Schema.Union([Schema.Boolean, Schema.Null]).annotate({ - "description": - "Whether to filter providers to only those that support the parameters you've provided. If this setting is omitted or set to false, then providers will receive only the parameters they support, and ignore the rest." - }) - ), - "data_collection": Schema.optionalKey( - Schema.Union([Schema.Literals(["deny", "allow"]), Schema.Null]).annotate({ - "description": - "Data collection setting. If no available model provider meets the requirement, your request will return an error.\n- allow: (default) allow providers which store user data non-transiently and may train on it\n\n- deny: use only providers which do not collect user data." - }) - ), - "zdr": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), - "enforce_distillable_text": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), - "order": Schema.optionalKey( - Schema.Union([__schema5, Schema.Null]).annotate({ - "description": - "An ordered list of provider slugs. The router will attempt to use the first provider in the subset of this list that supports your requested model, and fall back to the next if it is unavailable. If no providers are available, the request will fail with an error message." - }) - ), - "only": Schema.optionalKey( - Schema.Union([__schema5, Schema.Null]).annotate({ - "description": - "List of provider slugs to allow. If provided, this list is merged with your account-wide allowed provider settings for this request." - }) - ), - "ignore": Schema.optionalKey( - Schema.Union([__schema5, Schema.Null]).annotate({ - "description": - "List of provider slugs to ignore. If provided, this list is merged with your account-wide ignored provider settings for this request." - }) - ), - "quantizations": Schema.optionalKey( - Schema.Union([ - Schema.Array(Schema.Literals(["int4", "int8", "fp4", "fp6", "fp8", "fp16", "bf16", "fp32", "unknown"])), - Schema.Null - ]).annotate({ "description": "A list of quantization levels to filter the provider by." }) - ), - "sort": Schema.optionalKey( - Schema.Union([ProviderSortUnion, Schema.Null]).annotate({ - "description": - "The sorting strategy to use for this request, if \"order\" is not specified. When set, no load balancing is performed." - }) - ), - "max_price": Schema.optionalKey( - Schema.Struct({ - "prompt": Schema.optionalKey(Schema.Union([__schema11, ModelName, __schema13])), - "completion": Schema.optionalKey(Schema.Union([__schema11, ModelName, __schema13])), - "image": Schema.optionalKey(__schema14), - "audio": Schema.optionalKey(__schema14), - "request": Schema.optionalKey(__schema14) - }).annotate({ - "description": - "The object specifying the maximum price you want to pay for this request. USD price per million tokens, for prompt and completion." - }) - ), - "preferred_min_throughput": Schema.optionalKey( - Schema.Union([ - Schema.Union([ - Schema.Number.check(Schema.isFinite()), - Schema.Struct({ - "p50": Schema.optionalKey(Schema.Union([Schema.Number.check(Schema.isFinite()), Schema.Null])), - "p75": Schema.optionalKey(Schema.Union([Schema.Number.check(Schema.isFinite()), Schema.Null])), - "p90": Schema.optionalKey(Schema.Union([Schema.Number.check(Schema.isFinite()), Schema.Null])), - "p99": Schema.optionalKey(Schema.Union([Schema.Number.check(Schema.isFinite()), Schema.Null])) - }) - ]), - Schema.Null - ]).annotate({ - "description": - "Preferred minimum throughput (in tokens per second). Can be a number (applies to p50) or an object with percentile-specific cutoffs. Endpoints below the threshold(s) may still be used, but are deprioritized in routing. When using fallback models, this may cause a fallback model to be used instead of the primary model if it meets the threshold." - }) - ), - "preferred_max_latency": Schema.optionalKey( - Schema.Union([ - Schema.Union([ - Schema.Number.check(Schema.isFinite()), - Schema.Struct({ - "p50": Schema.optionalKey(Schema.Union([Schema.Number.check(Schema.isFinite()), Schema.Null])), - "p75": Schema.optionalKey(Schema.Union([Schema.Number.check(Schema.isFinite()), Schema.Null])), - "p90": Schema.optionalKey(Schema.Union([Schema.Number.check(Schema.isFinite()), Schema.Null])), - "p99": Schema.optionalKey(Schema.Union([Schema.Number.check(Schema.isFinite()), Schema.Null])) - }) - ]), - Schema.Null - ]).annotate({ - "description": - "Preferred maximum latency (in seconds). Can be a number (applies to p50) or an object with percentile-specific cutoffs. Endpoints above the threshold(s) may still be used, but are deprioritized in routing. When using fallback models, this may cause a fallback model to be used instead of the primary model if it meets the threshold." - }) - ) - }), + Schema.Literal("low"), + Schema.Literal("medium"), + Schema.Literal("high"), + Schema.Literal("xhigh"), + Schema.Literal("max"), Schema.Null ]).annotate({ - "description": "When multiple model providers are available, optionally indicate your routing preference." - }) - ), - "plugins": Schema.optionalKey( - Schema.Array( - Schema.Union([ - Schema.Struct({ - "id": Schema.Literal("auto-router"), - "enabled": Schema.optionalKey(Schema.Boolean), - "allowed_models": Schema.optionalKey(Schema.Array(Schema.String)) - }), - Schema.Struct({ "id": Schema.Literal("moderation") }), - Schema.Struct({ - "id": Schema.Literal("web"), - "enabled": Schema.optionalKey(Schema.Boolean), - "max_results": Schema.optionalKey(Schema.Number.check(Schema.isFinite())), - "search_prompt": Schema.optionalKey(Schema.String), - "engine": Schema.optionalKey(Schema.Literals(["native", "exa"])) - }), - Schema.Struct({ - "id": Schema.Literal("file-parser"), - "enabled": Schema.optionalKey(Schema.Boolean), - "pdf": Schema.optionalKey( - Schema.Struct({ "engine": Schema.optionalKey(Schema.Literals(["mistral-ocr", "pdf-text", "native"])) }) - ) - }), - Schema.Struct({ "id": Schema.Literal("response-healing"), "enabled": Schema.optionalKey(Schema.Boolean) }) - ], { mode: "oneOf" }) - ).annotate({ "description": "Plugins you want to enable for this request, including their settings." }) - ), - "route": Schema.optionalKey(Schema.Union([Schema.Literals(["fallback", "sort"]), Schema.Null])), - "user": Schema.optionalKey(Schema.String), - "session_id": Schema.optionalKey( - Schema.String.annotate({ - "description": - "A unique identifier for grouping related requests (e.g., a conversation or agent workflow) for observability. If provided in both the request body and the x-session-id header, the body value takes precedence. Maximum of 128 characters." - }).check(Schema.isMaxLength(128)) - ), - "trace": Schema.optionalKey( - Schema.Struct({ - "trace_id": Schema.optionalKey(Schema.String), - "trace_name": Schema.optionalKey(Schema.String), - "span_name": Schema.optionalKey(Schema.String), - "generation_name": Schema.optionalKey(Schema.String), - "parent_span_id": Schema.optionalKey(Schema.String) - }).annotate({ "description": - "Metadata for observability and tracing. Known keys (trace_id, trace_name, span_name, generation_name, parent_span_id) have special handling. Additional keys are passed through as custom metadata to configured broadcast destinations." + "How much effort the model should put into its response. Higher effort levels may result in more thorough analysis but take longer. Valid values are `low`, `medium`, `high`, `xhigh`, or `max`." }) ), - "messages": Schema.Array(Message).check(Schema.isMinLength(1)), - "model": Schema.optionalKey(ModelName), - "models": Schema.optionalKey(Schema.Array(ModelName)), - "frequency_penalty": Schema.optionalKey( - Schema.Union([ - Schema.Number.check(Schema.isFinite()).check(Schema.isGreaterThanOrEqualTo(-2)).check( - Schema.isLessThanOrEqualTo(2) - ), - Schema.Null - ]) + "format": Schema.optionalKey(Union_10), + "task_budget": Schema.optionalKey(Union_11) +}).annotate({ + "description": + "Configuration for controlling output behavior. Supports the effort parameter and structured output format.", + "identifier": "MessagesOutputConfig" +}) +export type ChatModelNames = ReadonlyArray +export const ChatModelNames = Schema.Array( + Schema.suspend((): Schema.Codec => ModelName).annotate({ + "description": "Available OpenRouter chat completion models" + }) +).annotate({ "description": "Models to use for completion", "identifier": "ChatModelNames" }) +export type ContentPartInputAudio = { readonly "input_audio": MultimodalMedia; readonly "type": "input_audio" } +export const ContentPartInputAudio = Schema.Struct({ + "input_audio": MultimodalMedia, + "type": Schema.Literal("input_audio") +}).annotate({ "identifier": "ContentPartInputAudio" }) +export type ContentPartInputFile = { readonly "input_file": MultimodalMedia; readonly "type": "input_file" } +export const ContentPartInputFile = Schema.Struct({ + "input_file": MultimodalMedia, + "type": Schema.Literal("input_file") +}).annotate({ "identifier": "ContentPartInputFile" }) +export type ContentPartInputVideo = { readonly "input_video": MultimodalMedia; readonly "type": "input_video" } +export const ContentPartInputVideo = Schema.Struct({ + "input_video": MultimodalMedia, + "type": Schema.Literal("input_video") +}).annotate({ "identifier": "ContentPartInputVideo" }) +export type NamespaceTool = { + readonly "description": string + readonly "name": string + readonly "tools": ReadonlyArray + readonly "type": "namespace" +} +export const NamespaceTool = Schema.Struct({ + "description": Schema.String, + "name": Schema.String, + "tools": Schema.Array(Schema.Union([NamespaceFunctionTool, CustomTool])), + "type": Schema.Literal("namespace") +}).annotate({ "description": "Groups function/custom tools under a shared namespace", "identifier": "NamespaceTool" }) +export type NotFoundResponse = { + readonly "error": NotFoundResponseErrorData + readonly "openrouter_metadata"?: { readonly [x: string]: Schema.Json } | null + readonly "user_id"?: string | null +} +export const NotFoundResponse = Schema.Struct({ + "error": NotFoundResponseErrorData, + "openrouter_metadata": Schema.optionalKey( + Schema.Union([Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })), Schema.Null]) ), - "logit_bias": Schema.optionalKey( - Schema.Union([Schema.Struct({}).check(Schema.isPropertyNames(Schema.String)), Schema.Null]) + "user_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) +}).annotate({ "description": "Not Found - Resource does not exist", "identifier": "NotFoundResponse" }) +export type ObservabilityFilterRulesConfig = { + readonly "enabled"?: boolean + readonly "groups": ReadonlyArray + readonly [x: string]: Schema.Json +} | null +export const ObservabilityFilterRulesConfig = Schema.Union([ + Schema.StructWithRest( + Schema.Struct({ + "enabled": Schema.optionalKey(Schema.Boolean), + "groups": Schema.Array(ObservabilityFilterRuleGroup) + }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] ), - "logprobs": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), - "top_logprobs": Schema.optionalKey( - Schema.Union([ - Schema.Number.check(Schema.isFinite()).check(Schema.isGreaterThanOrEqualTo(0)).check( - Schema.isLessThanOrEqualTo(20) - ), - Schema.Null - ]) + Schema.Null +]).annotate({ + "description": "Optional structured filter rules controlling which events are forwarded.", + "identifier": "ObservabilityFilterRulesConfig" +}) +export type Objects_10 = { + readonly "enabled"?: boolean + readonly "groups": ReadonlyArray + readonly [x: string]: Schema.Json +} +export const Objects_10 = Schema.StructWithRest( + Schema.Struct({ + "enabled": Schema.optionalKey(Schema.Boolean), + "groups": Schema.Array(ObservabilityFilterRuleGroup) + }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] +) +export type OpenAIResponsesUsage = { + readonly "input_tokens": number + readonly "input_tokens_details": Objects_15 + readonly "output_tokens": number + readonly "output_tokens_details": Objects_16 + readonly "total_tokens": number +} +export const OpenAIResponsesUsage = Schema.Struct({ + "input_tokens": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "input_tokens_details": Objects_15, + "output_tokens": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "output_tokens_details": Objects_16, + "total_tokens": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) +}).annotate({ "identifier": "OpenAIResponsesUsage" }) +export type OpenResponsesLogProbs = { + readonly "bytes"?: ReadonlyArray + readonly "logprob": number + readonly "token": string + readonly "top_logprobs"?: ReadonlyArray +} +export const OpenResponsesLogProbs = Schema.Struct({ + "bytes": Schema.optionalKey(Schema.Array(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })))), + "logprob": Schema.Number.annotate({ "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) ), - "max_completion_tokens": Schema.optionalKey( - Schema.Union([Schema.Number.check(Schema.isFinite()).check(Schema.isGreaterThanOrEqualTo(1)), Schema.Null]) + "token": Schema.String, + "top_logprobs": Schema.optionalKey(Schema.Array(OpenResponsesTopLogprobs)) +}).annotate({ "description": "Log probability information for a token", "identifier": "OpenResponsesLogProbs" }) +export type ModelArchitecture = { + readonly "input_modalities": ReadonlyArray + readonly "instruct_type"?: InstructType + readonly "modality": string | null + readonly "output_modalities": ReadonlyArray + readonly "tokenizer"?: ModelGroup +} +export const ModelArchitecture = Schema.Struct({ + "input_modalities": Schema.Array(InputModality).annotate({ "description": "Supported input modalities" }), + "instruct_type": Schema.optionalKey(InstructType), + "modality": Schema.Union([Schema.String, Schema.Null]).annotate({ "description": "Primary modality of the model" }), + "output_modalities": Schema.Array(OutputModality).annotate({ "description": "Supported output modalities" }), + "tokenizer": Schema.optionalKey(ModelGroup) +}).annotate({ "description": "Model architecture information", "identifier": "ModelArchitecture" }) +export type PayloadTooLargeResponse = { + readonly "error": PayloadTooLargeResponseErrorData + readonly "openrouter_metadata"?: { readonly [x: string]: Schema.Json } | null + readonly "user_id"?: string | null +} +export const PayloadTooLargeResponse = Schema.Struct({ + "error": PayloadTooLargeResponseErrorData, + "openrouter_metadata": Schema.optionalKey( + Schema.Union([Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })), Schema.Null]) ), - "max_tokens": Schema.optionalKey( - Schema.Union([Schema.Number.check(Schema.isFinite()).check(Schema.isGreaterThanOrEqualTo(1)), Schema.Null]) + "user_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) +}).annotate({ + "description": "Payload Too Large - Request payload exceeds size limits", + "identifier": "PayloadTooLargeResponse" +}) +export type PaymentRequiredResponse = { + readonly "error": PaymentRequiredResponseErrorData + readonly "openrouter_metadata"?: { readonly [x: string]: Schema.Json } | null + readonly "user_id"?: string | null +} +export const PaymentRequiredResponse = Schema.Struct({ + "error": PaymentRequiredResponseErrorData, + "openrouter_metadata": Schema.optionalKey( + Schema.Union([Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })), Schema.Null]) ), - "metadata": Schema.optionalKey(Schema.Struct({}).check(Schema.isPropertyNames(Schema.String))), - "presence_penalty": Schema.optionalKey( - Schema.Union([ - Schema.Number.check(Schema.isFinite()).check(Schema.isGreaterThanOrEqualTo(-2)).check( - Schema.isLessThanOrEqualTo(2) - ), - Schema.Null - ]) + "user_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) +}).annotate({ + "description": "Payment Required - Insufficient credits or quota to complete request", + "identifier": "PaymentRequiredResponse" +}) +export type PDFParserOptions = { readonly "engine"?: PDFParserEngine } +export const PDFParserOptions = Schema.Struct({ "engine": Schema.optionalKey(PDFParserEngine) }).annotate({ + "description": "Options for PDF parsing.", + "identifier": "PDFParserOptions" +}) +export type PreferredMaxLatency = number | PercentileLatencyCutoffs | null +export const PreferredMaxLatency = Schema.Union([ + Schema.Number.annotate({ "format": "double" }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + PercentileLatencyCutoffs, + Schema.Null +]).annotate({ + "description": + "Preferred maximum latency (in seconds). Can be a number (applies to p50) or an object with percentile-specific cutoffs. Endpoints above the threshold(s) may still be used, but are deprioritized in routing. When using fallback models, this may cause a fallback model to be used instead of the primary model if it meets the threshold.", + "identifier": "PreferredMaxLatency" +}) +export type PreferredMinThroughput = number | PercentileThroughputCutoffs | null +export const PreferredMinThroughput = Schema.Union([ + Schema.Number.annotate({ "format": "double" }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + PercentileThroughputCutoffs, + Schema.Null +]).annotate({ + "description": + "Preferred minimum throughput (in tokens per second). Can be a number (applies to p50) or an object with percentile-specific cutoffs. Endpoints below the threshold(s) may still be used, but are deprioritized in routing. When using fallback models, this may cause a fallback model to be used instead of the primary model if it meets the threshold.", + "identifier": "PreferredMinThroughput" +}) +export type PipelineStage = { + readonly "cost_usd"?: number | null + readonly "data"?: {} + readonly "guardrail_id"?: string + readonly "guardrail_scope"?: string + readonly "name": string + readonly "summary"?: string + readonly "type": PipelineStageType +} +export const PipelineStage = Schema.Struct({ + "cost_usd": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), Schema.Null]) + .annotate({ "format": "double" }) ), - "reasoning": Schema.optionalKey( - Schema.Struct({ - "effort": Schema.optionalKey( - Schema.Union([Schema.Literals(["xhigh", "high", "medium", "low", "minimal", "none"]), Schema.Null]) - ), - "summary": Schema.optionalKey(Schema.Union([ReasoningSummaryVerbosity, Schema.Null])) - }) + "data": Schema.optionalKey(Schema.Struct({})), + "guardrail_id": Schema.optionalKey(Schema.String), + "guardrail_scope": Schema.optionalKey(Schema.String), + "name": Schema.String, + "summary": Schema.optionalKey(Schema.String), + "type": PipelineStageType +}).annotate({ "identifier": "PipelineStage" }) +export type Objects_17 = { + readonly "content": string | ReadonlyArray + readonly "type": "content" + readonly [x: string]: Schema.Json +} +export const Objects_17 = Schema.StructWithRest( + Schema.Struct({ + "content": Schema.Union([Schema.String, Schema.Array(PredictionContentText)]), + "type": Schema.Literal("content") + }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] +) +export type GetPresetVersionResponse = { readonly "data": PresetDesignatedVersion } +export const GetPresetVersionResponse = Schema.Struct({ "data": PresetDesignatedVersion }).annotate({ + "description": "A single version of a preset.", + "identifier": "GetPresetVersionResponse" +}) +export type ListPresetVersionsResponse = { + readonly "data": ReadonlyArray + readonly "total_count": number +} +export const ListPresetVersionsResponse = Schema.Struct({ + "data": Schema.Array(PresetDesignatedVersion), + "total_count": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) +}).annotate({ "description": "A paginated list of preset versions.", "identifier": "ListPresetVersionsResponse" }) +export type Preset = { + readonly "created_at": string + readonly "creator_user_id": string | null + readonly "description": string | null + readonly "designated_version_id": string | null + readonly "id": string + readonly "name": string + readonly "slug": string + readonly "status": PresetStatus + readonly "status_updated_at": string | null + readonly "updated_at": string + readonly "workspace_id": string | null +} +export const Preset = Schema.Struct({ + "created_at": Schema.String, + "creator_user_id": Schema.Union([Schema.String, Schema.Null]), + "description": Schema.Union([Schema.String, Schema.Null]), + "designated_version_id": Schema.Union([Schema.String, Schema.Null]), + "id": Schema.String, + "name": Schema.String, + "slug": Schema.String, + "status": PresetStatus, + "status_updated_at": Schema.Union([Schema.String, Schema.Null]), + "updated_at": Schema.String, + "workspace_id": Schema.Union([Schema.String, Schema.Null]) +}).annotate({ "description": "A preset without version details.", "identifier": "Preset" }) +export type PresetWithDesignatedVersion = { + readonly "created_at": string + readonly "creator_user_id": string | null + readonly "description": string | null + readonly "designated_version_id": string | null + readonly "id": string + readonly "name": string + readonly "slug": string + readonly "status": PresetStatus + readonly "status_updated_at": string | null + readonly "updated_at": string + readonly "workspace_id": string | null + readonly "designated_version": PresetDesignatedVersion +} +export const PresetWithDesignatedVersion = Schema.Struct({ + "created_at": Schema.String, + "creator_user_id": Schema.Union([Schema.String, Schema.Null]), + "description": Schema.Union([Schema.String, Schema.Null]), + "designated_version_id": Schema.Union([Schema.String, Schema.Null]), + "id": Schema.String, + "name": Schema.String, + "slug": Schema.String, + "status": PresetStatus, + "status_updated_at": Schema.Union([Schema.String, Schema.Null]), + "updated_at": Schema.String, + "workspace_id": Schema.Union([Schema.String, Schema.Null]), + "designated_version": PresetDesignatedVersion +}).annotate({ + "description": "A preset with its currently designated version.", + "identifier": "PresetWithDesignatedVersion" +}) +export type Preview_WebSearchUserLocation = Objects_18 | null +export const Preview_WebSearchUserLocation = Schema.Union([Objects_18, Schema.Null]).annotate({ + "identifier": "Preview_WebSearchUserLocation" +}) +export type PublicPricing = { + readonly "audio"?: string + readonly "audio_output"?: string + readonly "completion": string + readonly "discount"?: number + readonly "image"?: string + readonly "image_output"?: string + readonly "image_token"?: string + readonly "input_audio_cache"?: string + readonly "input_cache_read"?: string + readonly "input_cache_write"?: string + readonly "input_cache_write_1h"?: string + readonly "internal_reasoning"?: string + readonly "overrides"?: ReadonlyArray + readonly "prompt": string + readonly "request"?: string + readonly "web_search"?: string +} +export const PublicPricing = Schema.Struct({ + "audio": Schema.optionalKey(Schema.String.annotate({ "description": "Price in USD per audio input token" })), + "audio_output": Schema.optionalKey(Schema.String.annotate({ "description": "Price in USD per audio output token" })), + "completion": Schema.String.annotate({ "description": "Price in USD per token for completion (output) generation" }), + "discount": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Fractional discount applied to this endpoint's pricing; the price is multiplied by (1 - discount) (0 = no discount, 1 = free)", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })) ), - "response_format": Schema.optionalKey( - Schema.Union([ - Schema.Struct({ "type": Schema.Literal("text") }), - Schema.Struct({ "type": Schema.Literal("json_object") }), - ResponseFormatJSONSchema, - ResponseFormatTextGrammar, - Schema.Struct({ "type": Schema.Literal("python") }) - ], { mode: "oneOf" }) + "image": Schema.optionalKey(Schema.String.annotate({ "description": "Price in USD per input image" })), + "image_output": Schema.optionalKey(Schema.String.annotate({ "description": "Price in USD per output image" })), + "image_token": Schema.optionalKey(Schema.String.annotate({ "description": "Price in USD per image token" })), + "input_audio_cache": Schema.optionalKey( + Schema.String.annotate({ "description": "Price in USD per cached audio input token" }) ), - "seed": Schema.optionalKey( - Schema.Union([ - Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(-9007199254740991)).check( - Schema.isLessThanOrEqualTo(9007199254740991) - ), - Schema.Null - ]) + "input_cache_read": Schema.optionalKey( + Schema.String.annotate({ "description": "Price in USD per cached input token (read)" }) ), - "stop": Schema.optionalKey( - Schema.Union([Schema.Union([Schema.String, Schema.Array(ModelName).check(Schema.isMaxLength(4))]), Schema.Null]) + "input_cache_write": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Price per cache-write token, in USD per token. For providers with multiple cache TTLs (e.g. Anthropic), this is the default (5-minute) cache-write rate." + }) ), - "stream": Schema.optionalKey(Schema.Boolean), - "stream_options": Schema.optionalKey(Schema.Union([ChatStreamOptions, Schema.Null])), - "temperature": Schema.optionalKey( - Schema.Union([ - Schema.Number.check(Schema.isFinite()).check(Schema.isGreaterThanOrEqualTo(0)).check( - Schema.isLessThanOrEqualTo(2) - ), - Schema.Null - ]) + "input_cache_write_1h": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Price per 1-hour cache-write token, in USD per token. Only present for providers that price an extended (1-hour) cache TTL separately, such as Anthropic." + }) ), - "parallel_tool_calls": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), - "tool_choice": Schema.optionalKey(ToolChoiceOption), - "tools": Schema.optionalKey(Schema.Array(ToolDefinitionJson)), - "top_p": Schema.optionalKey( - Schema.Union([ - Schema.Number.check(Schema.isFinite()).check(Schema.isGreaterThanOrEqualTo(0)).check( - Schema.isLessThanOrEqualTo(1) - ), - Schema.Null - ]) + "internal_reasoning": Schema.optionalKey( + Schema.String.annotate({ "description": "Price in USD per internal reasoning token" }) + ), + "overrides": Schema.optionalKey( + Schema.Array(PricingOverride).annotate({ + "description": + "Conditional overrides of the base pricing (e.g. long-context or time-based pricing). An entry applies when all of its condition fields (e.g. min_prompt_tokens, or the utc_start/utc_end time window) match the request; among applicable entries, later entries win per key; price keys absent from an entry inherit the base price. The top-level pricing keys always reflect the price that applies under default conditions." + }) ), - "debug": Schema.optionalKey(Schema.Struct({ "echo_upstream_body": Schema.optionalKey(Schema.Boolean) })), - "image_config": Schema.optionalKey(Schema.Struct({}).check(Schema.isPropertyNames(Schema.String))), - "modalities": Schema.optionalKey(Schema.Array(Schema.Literals(["text", "image"]))) + "prompt": Schema.String.annotate({ "description": "Price in USD per token for prompt (input) processing" }), + "request": Schema.optionalKey(Schema.String.annotate({ "description": "Price in USD per request" })), + "web_search": Schema.optionalKey(Schema.String.annotate({ "description": "Price in USD per web search" })) +}).annotate({ "description": "Pricing information for the model", "identifier": "PublicPricing" }) +export type PromptCacheBreakpoint = Objects_19 | null +export const PromptCacheBreakpoint = Schema.Union([Objects_19, Schema.Null]).annotate({ + "description": + "Marks an explicit prompt-cache boundary on this content block (OpenAI-style). Everything through the block carrying this marker is part of the candidate cached prefix. Supported natively by OpenAI GPT-5.6 and newer; on providers that use Anthropic-style `cache_control`, OpenRouter converts the marker to that format automatically.", + "identifier": "PromptCacheBreakpoint" }) -export type OpenResponsesCreatedEvent = { - readonly "type": "response.created" - readonly "response": OpenAIResponsesNonStreamingResponse - readonly "sequence_number": number -} -export const OpenResponsesCreatedEvent = Schema.Struct({ - "type": Schema.Literal("response.created"), - "response": OpenAIResponsesNonStreamingResponse, - "sequence_number": Schema.Number.check(Schema.isFinite()) -}).annotate({ "description": "Event emitted when a response is created" }) -export type OpenResponsesInProgressEvent = { - readonly "type": "response.in_progress" - readonly "response": OpenAIResponsesNonStreamingResponse - readonly "sequence_number": number -} -export const OpenResponsesInProgressEvent = Schema.Struct({ - "type": Schema.Literal("response.in_progress"), - "response": OpenAIResponsesNonStreamingResponse, - "sequence_number": Schema.Number.check(Schema.isFinite()) -}).annotate({ "description": "Event emitted when a response is in progress" }) -export type OpenResponsesCompletedEvent = { - readonly "type": "response.completed" - readonly "response": OpenAIResponsesNonStreamingResponse - readonly "sequence_number": number -} -export const OpenResponsesCompletedEvent = Schema.Struct({ - "type": Schema.Literal("response.completed"), - "response": OpenAIResponsesNonStreamingResponse, - "sequence_number": Schema.Number.check(Schema.isFinite()) -}).annotate({ "description": "Event emitted when a response has completed successfully" }) -export type OpenResponsesIncompleteEvent = { - readonly "type": "response.incomplete" - readonly "response": OpenAIResponsesNonStreamingResponse - readonly "sequence_number": number -} -export const OpenResponsesIncompleteEvent = Schema.Struct({ - "type": Schema.Literal("response.incomplete"), - "response": OpenAIResponsesNonStreamingResponse, - "sequence_number": Schema.Number.check(Schema.isFinite()) -}).annotate({ "description": "Event emitted when a response is incomplete" }) -export type OpenResponsesFailedEvent = { - readonly "type": "response.failed" - readonly "response": OpenAIResponsesNonStreamingResponse - readonly "sequence_number": number -} -export const OpenResponsesFailedEvent = Schema.Struct({ - "type": Schema.Literal("response.failed"), - "response": OpenAIResponsesNonStreamingResponse, - "sequence_number": Schema.Number.check(Schema.isFinite()) -}).annotate({ "description": "Event emitted when a response has failed" }) -export type OpenResponsesStreamEvent = - | OpenResponsesCreatedEvent - | OpenResponsesInProgressEvent - | OpenResponsesCompletedEvent - | OpenResponsesIncompleteEvent - | OpenResponsesFailedEvent - | OpenResponsesErrorEvent - | OpenResponsesOutputItemAddedEvent - | OpenResponsesOutputItemDoneEvent - | OpenResponsesContentPartAddedEvent - | OpenResponsesContentPartDoneEvent - | OpenResponsesTextDeltaEvent - | OpenResponsesTextDoneEvent - | OpenResponsesRefusalDeltaEvent - | OpenResponsesRefusalDoneEvent - | OpenResponsesOutputTextAnnotationAddedEvent - | OpenResponsesFunctionCallArgumentsDeltaEvent - | OpenResponsesFunctionCallArgumentsDoneEvent - | OpenResponsesReasoningDeltaEvent - | OpenResponsesReasoningDoneEvent - | OpenResponsesReasoningSummaryPartAddedEvent - | OpenResponsesReasoningSummaryPartDoneEvent - | OpenResponsesReasoningSummaryTextDeltaEvent - | OpenResponsesReasoningSummaryTextDoneEvent - | OpenResponsesImageGenCallInProgress - | OpenResponsesImageGenCallGenerating - | OpenResponsesImageGenCallPartialImage - | OpenResponsesImageGenCallCompleted -export const OpenResponsesStreamEvent = Schema.Union([ - OpenResponsesCreatedEvent, - OpenResponsesInProgressEvent, - OpenResponsesCompletedEvent, - OpenResponsesIncompleteEvent, - OpenResponsesFailedEvent, - OpenResponsesErrorEvent, - OpenResponsesOutputItemAddedEvent, - OpenResponsesOutputItemDoneEvent, - OpenResponsesContentPartAddedEvent, - OpenResponsesContentPartDoneEvent, - OpenResponsesTextDeltaEvent, - OpenResponsesTextDoneEvent, - OpenResponsesRefusalDeltaEvent, - OpenResponsesRefusalDoneEvent, - OpenResponsesOutputTextAnnotationAddedEvent, - OpenResponsesFunctionCallArgumentsDeltaEvent, - OpenResponsesFunctionCallArgumentsDoneEvent, - OpenResponsesReasoningDeltaEvent, - OpenResponsesReasoningDoneEvent, - OpenResponsesReasoningSummaryPartAddedEvent, - OpenResponsesReasoningSummaryPartDoneEvent, - OpenResponsesReasoningSummaryTextDeltaEvent, - OpenResponsesReasoningSummaryTextDoneEvent, - OpenResponsesImageGenCallInProgress, - OpenResponsesImageGenCallGenerating, - OpenResponsesImageGenCallPartialImage, - OpenResponsesImageGenCallCompleted -], { mode: "oneOf" }) -// schemas -export type CreateResponsesRequestJson = OpenResponsesRequest -export const CreateResponsesRequestJson = OpenResponsesRequest -export type CreateResponses200 = OpenResponsesNonStreamingResponse -export const CreateResponses200 = OpenResponsesNonStreamingResponse -export type CreateResponses200Sse = { readonly "data": OpenResponsesStreamEvent } -export const CreateResponses200Sse = Schema.Struct({ "data": OpenResponsesStreamEvent }) -export type CreateResponses400 = BadRequestResponse -export const CreateResponses400 = BadRequestResponse -export type CreateResponses401 = UnauthorizedResponse -export const CreateResponses401 = UnauthorizedResponse -export type CreateResponses402 = PaymentRequiredResponse -export const CreateResponses402 = PaymentRequiredResponse -export type CreateResponses404 = NotFoundResponse -export const CreateResponses404 = NotFoundResponse -export type CreateResponses408 = RequestTimeoutResponse -export const CreateResponses408 = RequestTimeoutResponse -export type CreateResponses413 = PayloadTooLargeResponse -export const CreateResponses413 = PayloadTooLargeResponse -export type CreateResponses422 = UnprocessableEntityResponse -export const CreateResponses422 = UnprocessableEntityResponse -export type CreateResponses429 = TooManyRequestsResponse -export const CreateResponses429 = TooManyRequestsResponse -export type CreateResponses500 = InternalServerResponse -export const CreateResponses500 = InternalServerResponse -export type CreateResponses502 = BadGatewayResponse -export const CreateResponses502 = BadGatewayResponse -export type CreateResponses503 = ServiceUnavailableResponse -export const CreateResponses503 = ServiceUnavailableResponse -export type CreateResponses524 = EdgeNetworkTimeoutResponse -export const CreateResponses524 = EdgeNetworkTimeoutResponse -export type CreateResponses529 = ProviderOverloadedResponse -export const CreateResponses529 = ProviderOverloadedResponse -export type CreateMessagesRequestJson = AnthropicMessagesRequest -export const CreateMessagesRequestJson = AnthropicMessagesRequest -export type CreateMessages200 = AnthropicMessagesResponse -export const CreateMessages200 = AnthropicMessagesResponse -export type CreateMessages200Sse = { readonly "event": string; readonly "data": AnthropicMessagesStreamEvent } -export const CreateMessages200Sse = Schema.Struct({ "event": Schema.String, "data": AnthropicMessagesStreamEvent }) -export type CreateMessages400 = { - readonly "type": "error" - readonly "error": { readonly "type": string; readonly "message": string } -} -export const CreateMessages400 = Schema.Struct({ - "type": Schema.Literal("error"), - "error": Schema.Struct({ "type": Schema.String, "message": Schema.String }) +export type PromptCacheOptions = Objects_20 | null +export const PromptCacheOptions = Schema.Union([Objects_20, Schema.Null]).annotate({ + "description": + "Request-level prompt-cache controls. `mode: \"explicit\"` disables OpenAI-managed breakpoints so only blocks marked with `prompt_cache_breakpoint` are cached. Only supported by OpenAI GPT-5.6 and newer.", + "identifier": "PromptCacheOptions" }) -export type CreateMessages401 = { - readonly "type": "error" - readonly "error": { readonly "type": string; readonly "message": string } +export type ContentFilterBuiltinEntry = { + readonly "action": ContentFilterBuiltinAction + readonly "label"?: string + readonly "scan_scope"?: PromptInjectionScanScope + readonly "slug": ContentFilterBuiltinSlug } -export const CreateMessages401 = Schema.Struct({ - "type": Schema.Literal("error"), - "error": Schema.Struct({ "type": Schema.String, "message": Schema.String }) +export const ContentFilterBuiltinEntry = Schema.Struct({ + "action": ContentFilterBuiltinAction, + "label": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Read-only, system-assigned redaction placeholder derived from the slug (e.g. \"[EMAIL]\", \"[PHONE]\"). Not settable by the caller." + }).check(Schema.isMaxLength(100).annotate({ "expected": "a value with a length of at most 100" })) + ), + "scan_scope": Schema.optionalKey(PromptInjectionScanScope), + "slug": ContentFilterBuiltinSlug +}).annotate({ + "description": + "A builtin content filter entry. Builtin filters include PII detectors and the regex-based prompt injection detector.", + "identifier": "ContentFilterBuiltinEntry" }) -export type CreateMessages403 = { - readonly "type": "error" - readonly "error": { readonly "type": string; readonly "message": string } +export type ContentFilterBuiltinEntryInput = { + readonly "action": ContentFilterBuiltinAction + readonly "label"?: string + readonly "scan_scope"?: PromptInjectionScanScope + readonly "slug": ContentFilterBuiltinSlug } -export const CreateMessages403 = Schema.Struct({ - "type": Schema.Literal("error"), - "error": Schema.Struct({ "type": Schema.String, "message": Schema.String }) +export const ContentFilterBuiltinEntryInput = Schema.Struct({ + "action": ContentFilterBuiltinAction, + "label": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Deprecated: labels are system-assigned and cannot be set by the caller. Accepted for backward compatibility but silently ignored." + }).check(Schema.isMaxLength(100).annotate({ "expected": "a value with a length of at most 100" })) + ), + "scan_scope": Schema.optionalKey(PromptInjectionScanScope), + "slug": ContentFilterBuiltinSlug +}).annotate({ + "description": + "A builtin content filter entry for create/update requests. Labels are system-assigned and cannot be set by the caller.", + "identifier": "ContentFilterBuiltinEntryInput" }) -export type CreateMessages404 = { - readonly "type": "error" - readonly "error": { readonly "type": string; readonly "message": string } -} -export const CreateMessages404 = Schema.Struct({ - "type": Schema.Literal("error"), - "error": Schema.Struct({ "type": Schema.String, "message": Schema.String }) +export type Union_2 = ReadonlyArray | null +export const Union_2 = Schema.Union([Schema.Array(Schema.Union([ProviderName, Schema.String])), Schema.Null]).annotate({ + "description": + "List of provider slugs to ignore. If provided, this list is merged with your account-wide ignored provider settings for this request." }) -export type CreateMessages429 = { - readonly "type": "error" - readonly "error": { readonly "type": string; readonly "message": string } -} -export const CreateMessages429 = Schema.Struct({ - "type": Schema.Literal("error"), - "error": Schema.Struct({ "type": Schema.String, "message": Schema.String }) +export type Union_3 = ReadonlyArray | null +export const Union_3 = Schema.Union([Schema.Array(Schema.Union([ProviderName, Schema.String])), Schema.Null]).annotate({ + "description": + "List of provider slugs to allow. If provided, this list is merged with your account-wide allowed provider settings for this request." }) -export type CreateMessages500 = { - readonly "type": "error" - readonly "error": { readonly "type": string; readonly "message": string } -} -export const CreateMessages500 = Schema.Struct({ - "type": Schema.Literal("error"), - "error": Schema.Struct({ "type": Schema.String, "message": Schema.String }) +export type Union_4 = ReadonlyArray | null +export const Union_4 = Schema.Union([Schema.Array(Schema.Union([ProviderName, Schema.String])), Schema.Null]).annotate({ + "description": + "An ordered list of provider slugs. The router will attempt to use the first provider in the subset of this list that supports your requested model, and fall back to the next if it is unavailable. If no providers are available, the request will fail with an error message." }) -export type CreateMessages503 = { - readonly "type": "error" - readonly "error": { readonly "type": string; readonly "message": string } +export type ProviderOptions = { + readonly "01ai"?: Objects_21 + readonly "ai21"?: Objects_22 + readonly "aion-labs"?: Objects_23 + readonly "akashml"?: Objects_24 + readonly "alibaba"?: Objects_25 + readonly "amazon-bedrock"?: Objects_26 + readonly "amazon-nova"?: Objects_27 + readonly "ambient"?: Objects_28 + readonly "anthropic"?: Objects_29 + readonly "anyscale"?: Objects_30 + readonly "arcee-ai"?: Objects_31 + readonly "atlas-cloud"?: Objects_32 + readonly "atoma"?: Objects_33 + readonly "avian"?: Objects_34 + readonly "azure"?: Objects_35 + readonly "baidu"?: Objects_36 + readonly "baseten"?: Objects_37 + readonly "black-forest-labs"?: Objects_38 + readonly "byteplus"?: Objects_39 + readonly "centml"?: Objects_40 + readonly "cerebras"?: Objects_41 + readonly "chutes"?: Objects_42 + readonly "cirrascale"?: Objects_43 + readonly "clarifai"?: Objects_44 + readonly "cloudflare"?: Objects_45 + readonly "cohere"?: Objects_46 + readonly "coreweave"?: Objects_47 + readonly "crofai"?: Objects_48 + readonly "crucible"?: Objects_49 + readonly "crusoe"?: Objects_50 + readonly "darkbloom"?: Objects_51 + readonly "decart"?: Objects_52 + readonly "deepgram"?: Objects_53 + readonly "deepinfra"?: Objects_54 + readonly "deepseek"?: Objects_55 + readonly "dekallm"?: Objects_56 + readonly "digitalocean"?: Objects_57 + readonly "enfer"?: Objects_58 + readonly "fake-provider"?: Objects_59 + readonly "featherless"?: Objects_60 + readonly "fireworks"?: Objects_61 + readonly "fish-audio"?: Objects_62 + readonly "friendli"?: Objects_63 + readonly "gmicloud"?: Objects_64 + readonly "google-ai-studio"?: Objects_65 + readonly "google-vertex"?: Objects_66 + readonly "gopomelo"?: Objects_67 + readonly "groq"?: Objects_68 + readonly "heygen"?: Objects_69 + readonly "huggingface"?: Objects_70 + readonly "hyperbolic"?: Objects_71 + readonly "hyperbolic-quantized"?: Objects_72 + readonly "inception"?: Objects_73 + readonly "inceptron"?: Objects_74 + readonly "inferact-vllm"?: Objects_75 + readonly "inference-net"?: Objects_76 + readonly "infermatic"?: Objects_77 + readonly "inflection"?: Objects_78 + readonly "inocloud"?: Objects_79 + readonly "io-net"?: Objects_80 + readonly "ionstream"?: Objects_81 + readonly "klusterai"?: Objects_82 + readonly "krea"?: Objects_83 + readonly "lambda"?: Objects_84 + readonly "lepton"?: Objects_85 + readonly "liquid"?: Objects_86 + readonly "lynn"?: Objects_87 + readonly "lynn-private"?: Objects_88 + readonly "mancer"?: Objects_89 + readonly "mancer-old"?: Objects_90 + readonly "mara"?: Objects_91 + readonly "meta"?: Objects_92 + readonly "minimax"?: Objects_93 + readonly "mistral"?: Objects_94 + readonly "modal"?: Objects_95 + readonly "modelrun"?: Objects_96 + readonly "modular"?: Objects_97 + readonly "moonshotai"?: Objects_98 + readonly "morph"?: Objects_99 + readonly "ncompass"?: Objects_100 + readonly "nebius"?: Objects_101 + readonly "nex-agi"?: Objects_102 + readonly "nextbit"?: Objects_103 + readonly "nineteen"?: Objects_104 + readonly "novita"?: Objects_105 + readonly "nvidia"?: Objects_106 + readonly "octoai"?: Objects_107 + readonly "open-inference"?: Objects_108 + readonly "openai"?: Objects_109 + readonly "parasail"?: Objects_110 + readonly "perceptron"?: Objects_111 + readonly "perplexity"?: Objects_112 + readonly "phala"?: Objects_113 + readonly "poolside"?: Objects_114 + readonly "quiver"?: Objects_115 + readonly "recraft"?: Objects_116 + readonly "recursal"?: Objects_117 + readonly "reflection"?: Objects_118 + readonly "reka"?: Objects_119 + readonly "relace"?: Objects_120 + readonly "replicate"?: Objects_121 + readonly "runway"?: Objects_122 + readonly "sail-research"?: Objects_123 + readonly "sakana"?: Objects_124 + readonly "sambanova"?: Objects_125 + readonly "sambanova-cloaked"?: Objects_126 + readonly "seed"?: Objects_127 + readonly "sf-compute"?: Objects_128 + readonly "siliconflow"?: Objects_129 + readonly "sourceful"?: Objects_130 + readonly "stealth"?: Objects_131 + readonly "stepfun"?: Objects_132 + readonly "streamlake"?: Objects_133 + readonly "switchpoint"?: Objects_134 + readonly "targon"?: Objects_135 + readonly "tencent"?: Objects_136 + readonly "tenstorrent"?: Objects_137 + readonly "together"?: Objects_138 + readonly "together-lite"?: Objects_139 + readonly "ubicloud"?: Objects_140 + readonly "upstage"?: Objects_141 + readonly "venice"?: Objects_142 + readonly "wafer"?: Objects_143 + readonly "wandb"?: Objects_144 + readonly "xai"?: Objects_145 + readonly "xiaomi"?: Objects_146 + readonly "z-ai"?: Objects_147 } -export const CreateMessages503 = Schema.Struct({ - "type": Schema.Literal("error"), - "error": Schema.Struct({ "type": Schema.String, "message": Schema.String }) +export const ProviderOptions = Schema.Struct({ + "01ai": Schema.optionalKey(Objects_21), + "ai21": Schema.optionalKey(Objects_22), + "aion-labs": Schema.optionalKey(Objects_23), + "akashml": Schema.optionalKey(Objects_24), + "alibaba": Schema.optionalKey(Objects_25), + "amazon-bedrock": Schema.optionalKey(Objects_26), + "amazon-nova": Schema.optionalKey(Objects_27), + "ambient": Schema.optionalKey(Objects_28), + "anthropic": Schema.optionalKey(Objects_29), + "anyscale": Schema.optionalKey(Objects_30), + "arcee-ai": Schema.optionalKey(Objects_31), + "atlas-cloud": Schema.optionalKey(Objects_32), + "atoma": Schema.optionalKey(Objects_33), + "avian": Schema.optionalKey(Objects_34), + "azure": Schema.optionalKey(Objects_35), + "baidu": Schema.optionalKey(Objects_36), + "baseten": Schema.optionalKey(Objects_37), + "black-forest-labs": Schema.optionalKey(Objects_38), + "byteplus": Schema.optionalKey(Objects_39), + "centml": Schema.optionalKey(Objects_40), + "cerebras": Schema.optionalKey(Objects_41), + "chutes": Schema.optionalKey(Objects_42), + "cirrascale": Schema.optionalKey(Objects_43), + "clarifai": Schema.optionalKey(Objects_44), + "cloudflare": Schema.optionalKey(Objects_45), + "cohere": Schema.optionalKey(Objects_46), + "coreweave": Schema.optionalKey(Objects_47), + "crofai": Schema.optionalKey(Objects_48), + "crucible": Schema.optionalKey(Objects_49), + "crusoe": Schema.optionalKey(Objects_50), + "darkbloom": Schema.optionalKey(Objects_51), + "decart": Schema.optionalKey(Objects_52), + "deepgram": Schema.optionalKey(Objects_53), + "deepinfra": Schema.optionalKey(Objects_54), + "deepseek": Schema.optionalKey(Objects_55), + "dekallm": Schema.optionalKey(Objects_56), + "digitalocean": Schema.optionalKey(Objects_57), + "enfer": Schema.optionalKey(Objects_58), + "fake-provider": Schema.optionalKey(Objects_59), + "featherless": Schema.optionalKey(Objects_60), + "fireworks": Schema.optionalKey(Objects_61), + "fish-audio": Schema.optionalKey(Objects_62), + "friendli": Schema.optionalKey(Objects_63), + "gmicloud": Schema.optionalKey(Objects_64), + "google-ai-studio": Schema.optionalKey(Objects_65), + "google-vertex": Schema.optionalKey(Objects_66), + "gopomelo": Schema.optionalKey(Objects_67), + "groq": Schema.optionalKey(Objects_68), + "heygen": Schema.optionalKey(Objects_69), + "huggingface": Schema.optionalKey(Objects_70), + "hyperbolic": Schema.optionalKey(Objects_71), + "hyperbolic-quantized": Schema.optionalKey(Objects_72), + "inception": Schema.optionalKey(Objects_73), + "inceptron": Schema.optionalKey(Objects_74), + "inferact-vllm": Schema.optionalKey(Objects_75), + "inference-net": Schema.optionalKey(Objects_76), + "infermatic": Schema.optionalKey(Objects_77), + "inflection": Schema.optionalKey(Objects_78), + "inocloud": Schema.optionalKey(Objects_79), + "io-net": Schema.optionalKey(Objects_80), + "ionstream": Schema.optionalKey(Objects_81), + "klusterai": Schema.optionalKey(Objects_82), + "krea": Schema.optionalKey(Objects_83), + "lambda": Schema.optionalKey(Objects_84), + "lepton": Schema.optionalKey(Objects_85), + "liquid": Schema.optionalKey(Objects_86), + "lynn": Schema.optionalKey(Objects_87), + "lynn-private": Schema.optionalKey(Objects_88), + "mancer": Schema.optionalKey(Objects_89), + "mancer-old": Schema.optionalKey(Objects_90), + "mara": Schema.optionalKey(Objects_91), + "meta": Schema.optionalKey(Objects_92), + "minimax": Schema.optionalKey(Objects_93), + "mistral": Schema.optionalKey(Objects_94), + "modal": Schema.optionalKey(Objects_95), + "modelrun": Schema.optionalKey(Objects_96), + "modular": Schema.optionalKey(Objects_97), + "moonshotai": Schema.optionalKey(Objects_98), + "morph": Schema.optionalKey(Objects_99), + "ncompass": Schema.optionalKey(Objects_100), + "nebius": Schema.optionalKey(Objects_101), + "nex-agi": Schema.optionalKey(Objects_102), + "nextbit": Schema.optionalKey(Objects_103), + "nineteen": Schema.optionalKey(Objects_104), + "novita": Schema.optionalKey(Objects_105), + "nvidia": Schema.optionalKey(Objects_106), + "octoai": Schema.optionalKey(Objects_107), + "open-inference": Schema.optionalKey(Objects_108), + "openai": Schema.optionalKey(Objects_109), + "parasail": Schema.optionalKey(Objects_110), + "perceptron": Schema.optionalKey(Objects_111), + "perplexity": Schema.optionalKey(Objects_112), + "phala": Schema.optionalKey(Objects_113), + "poolside": Schema.optionalKey(Objects_114), + "quiver": Schema.optionalKey(Objects_115), + "recraft": Schema.optionalKey(Objects_116), + "recursal": Schema.optionalKey(Objects_117), + "reflection": Schema.optionalKey(Objects_118), + "reka": Schema.optionalKey(Objects_119), + "relace": Schema.optionalKey(Objects_120), + "replicate": Schema.optionalKey(Objects_121), + "runway": Schema.optionalKey(Objects_122), + "sail-research": Schema.optionalKey(Objects_123), + "sakana": Schema.optionalKey(Objects_124), + "sambanova": Schema.optionalKey(Objects_125), + "sambanova-cloaked": Schema.optionalKey(Objects_126), + "seed": Schema.optionalKey(Objects_127), + "sf-compute": Schema.optionalKey(Objects_128), + "siliconflow": Schema.optionalKey(Objects_129), + "sourceful": Schema.optionalKey(Objects_130), + "stealth": Schema.optionalKey(Objects_131), + "stepfun": Schema.optionalKey(Objects_132), + "streamlake": Schema.optionalKey(Objects_133), + "switchpoint": Schema.optionalKey(Objects_134), + "targon": Schema.optionalKey(Objects_135), + "tencent": Schema.optionalKey(Objects_136), + "tenstorrent": Schema.optionalKey(Objects_137), + "together": Schema.optionalKey(Objects_138), + "together-lite": Schema.optionalKey(Objects_139), + "ubicloud": Schema.optionalKey(Objects_140), + "upstage": Schema.optionalKey(Objects_141), + "venice": Schema.optionalKey(Objects_142), + "wafer": Schema.optionalKey(Objects_143), + "wandb": Schema.optionalKey(Objects_144), + "xai": Schema.optionalKey(Objects_145), + "xiaomi": Schema.optionalKey(Objects_146), + "z-ai": Schema.optionalKey(Objects_147) +}).annotate({ + "description": + "Provider-specific options keyed by provider slug. Only options for the matched provider are forwarded; the rest are ignored. Unrecognized keys are silently dropped.", + "identifier": "ProviderOptions" }) -export type CreateMessages529 = { - readonly "type": "error" - readonly "error": { readonly "type": string; readonly "message": string } +export type ProviderOverloadedResponse = { + readonly "error": ProviderOverloadedResponseErrorData + readonly "openrouter_metadata"?: { readonly [x: string]: Schema.Json } | null + readonly "user_id"?: string | null } -export const CreateMessages529 = Schema.Struct({ - "type": Schema.Literal("error"), - "error": Schema.Struct({ "type": Schema.String, "message": Schema.String }) -}) -export type GetUserActivityParams = { readonly "date"?: string } -export const GetUserActivityParams = Schema.Struct({ - "date": Schema.optionalKey( - Schema.String.annotate({ "description": "Filter by a single UTC date in the last 30 days (YYYY-MM-DD format)." }) - ) -}) -export type GetUserActivity200 = { readonly "data": ReadonlyArray } -export const GetUserActivity200 = Schema.Struct({ - "data": Schema.Array(ActivityItem).annotate({ "description": "List of activity items" }) +export const ProviderOverloadedResponse = Schema.Struct({ + "error": ProviderOverloadedResponseErrorData, + "openrouter_metadata": Schema.optionalKey( + Schema.Union([Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })), Schema.Null]) + ), + "user_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) +}).annotate({ + "description": "Provider Overloaded - Provider is temporarily overloaded", + "identifier": "ProviderOverloadedResponse" }) -export type GetUserActivity400 = BadRequestResponse -export const GetUserActivity400 = BadRequestResponse -export type GetUserActivity401 = UnauthorizedResponse -export const GetUserActivity401 = UnauthorizedResponse -export type GetUserActivity403 = ForbiddenResponse -export const GetUserActivity403 = ForbiddenResponse -export type GetUserActivity500 = InternalServerResponse -export const GetUserActivity500 = InternalServerResponse -export type GetCredits200 = { readonly "data": { readonly "total_credits": number; readonly "total_usage": number } } -export const GetCredits200 = Schema.Struct({ - "data": Schema.Struct({ - "total_credits": Schema.Number.annotate({ "description": "Total credits purchased" }).check(Schema.isFinite()), - "total_usage": Schema.Number.annotate({ "description": "Total credits used" }).check(Schema.isFinite()) - }) -}).annotate({ "description": "Total credits purchased and used" }) -export type GetCredits401 = UnauthorizedResponse -export const GetCredits401 = UnauthorizedResponse -export type GetCredits403 = ForbiddenResponse -export const GetCredits403 = ForbiddenResponse -export type GetCredits500 = InternalServerResponse -export const GetCredits500 = InternalServerResponse -export type CreateCoinbaseChargeRequestJson = CreateChargeRequest -export const CreateCoinbaseChargeRequestJson = CreateChargeRequest -export type CreateCoinbaseCharge200 = { +export type GenerationResponse = { readonly "data": { - readonly "id": string + readonly "api_type": "completions" | "embeddings" | "rerank" | "tts" | "stt" | "video" | "image" | null + readonly "app_id": number | null + readonly "cache_discount": number | null + readonly "cancelled": boolean | null readonly "created_at": string - readonly "expires_at": string - readonly "web3_data": { - readonly "transfer_intent": { - readonly "call_data": { - readonly "deadline": string - readonly "fee_amount": string - readonly "id": string - readonly "operator": string - readonly "prefix": string - readonly "recipient": string - readonly "recipient_amount": string - readonly "recipient_currency": string - readonly "refund_destination": string - readonly "signature": string - } - readonly "metadata": { - readonly "chain_id": number - readonly "contract_address": string - readonly "sender": string - } - } - } - } -} -export const CreateCoinbaseCharge200 = Schema.Struct({ - "data": Schema.Struct({ - "id": Schema.String, - "created_at": Schema.String, - "expires_at": Schema.String, - "web3_data": Schema.Struct({ - "transfer_intent": Schema.Struct({ - "call_data": Schema.Struct({ - "deadline": Schema.String, - "fee_amount": Schema.String, - "id": Schema.String, - "operator": Schema.String, - "prefix": Schema.String, - "recipient": Schema.String, - "recipient_amount": Schema.String, - "recipient_currency": Schema.String, - "refund_destination": Schema.String, - "signature": Schema.String - }), - "metadata": Schema.Struct({ - "chain_id": Schema.Number.check(Schema.isFinite()), - "contract_address": Schema.String, - "sender": Schema.String - }) - }) - }) - }) -}) -export type CreateCoinbaseCharge400 = BadRequestResponse -export const CreateCoinbaseCharge400 = BadRequestResponse -export type CreateCoinbaseCharge401 = UnauthorizedResponse -export const CreateCoinbaseCharge401 = UnauthorizedResponse -export type CreateCoinbaseCharge429 = TooManyRequestsResponse -export const CreateCoinbaseCharge429 = TooManyRequestsResponse -export type CreateCoinbaseCharge500 = InternalServerResponse -export const CreateCoinbaseCharge500 = InternalServerResponse -export type CreateEmbeddingsRequestJson = { - readonly "input": - | string - | ReadonlyArray - | ReadonlyArray - | ReadonlyArray> - | ReadonlyArray< - { - readonly "content": ReadonlyArray< - { readonly "type": "text"; readonly "text": string } | { - readonly "type": "image_url" - readonly "image_url": { readonly "url": string } - } - > - } - > - readonly "model": string - readonly "encoding_format"?: "float" | "base64" - readonly "dimensions"?: number - readonly "user"?: string - readonly "provider"?: ProviderPreferences - readonly "input_type"?: string -} -export const CreateEmbeddingsRequestJson = Schema.Struct({ - "input": Schema.Union([ - Schema.String, - Schema.Array(Schema.String), - Schema.Array(Schema.Number.check(Schema.isFinite())), - Schema.Array(Schema.Array(Schema.Number.check(Schema.isFinite()))), - Schema.Array( - Schema.Struct({ - "content": Schema.Array( - Schema.Union([ - Schema.Struct({ "type": Schema.Literal("text"), "text": Schema.String }), - Schema.Struct({ "type": Schema.Literal("image_url"), "image_url": Schema.Struct({ "url": Schema.String }) }) - ], { mode: "oneOf" }) - ) - }) - ) - ]), - "model": Schema.String, - "encoding_format": Schema.optionalKey(Schema.Literals(["float", "base64"])), - "dimensions": Schema.optionalKey(Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0))), - "user": Schema.optionalKey(Schema.String), - "provider": Schema.optionalKey(ProviderPreferences), - "input_type": Schema.optionalKey(Schema.String) -}) -export type CreateEmbeddings200 = { - readonly "id"?: string - readonly "object": "list" - readonly "data": ReadonlyArray< - { readonly "object": "embedding"; readonly "embedding": ReadonlyArray | string; readonly "index"?: number } - > - readonly "model": string - readonly "usage"?: { readonly "prompt_tokens": number; readonly "total_tokens": number; readonly "cost"?: number } -} -export const CreateEmbeddings200 = Schema.Struct({ - "id": Schema.optionalKey(Schema.String), - "object": Schema.Literal("list"), - "data": Schema.Array( - Schema.Struct({ - "object": Schema.Literal("embedding"), - "embedding": Schema.Union([Schema.Array(Schema.Number.check(Schema.isFinite())), Schema.String]), - "index": Schema.optionalKey(Schema.Number.check(Schema.isFinite())) - }) - ), - "model": Schema.String, - "usage": Schema.optionalKey( - Schema.Struct({ - "prompt_tokens": Schema.Number.check(Schema.isFinite()), - "total_tokens": Schema.Number.check(Schema.isFinite()), - "cost": Schema.optionalKey(Schema.Number.check(Schema.isFinite())) - }) - ) -}) -export type CreateEmbeddings200Sse = string -export const CreateEmbeddings200Sse = Schema.String.annotate({ - "description": "Not used for embeddings - embeddings do not support streaming" -}) -export type CreateEmbeddings400 = BadRequestResponse -export const CreateEmbeddings400 = BadRequestResponse -export type CreateEmbeddings401 = UnauthorizedResponse -export const CreateEmbeddings401 = UnauthorizedResponse -export type CreateEmbeddings402 = PaymentRequiredResponse -export const CreateEmbeddings402 = PaymentRequiredResponse -export type CreateEmbeddings404 = NotFoundResponse -export const CreateEmbeddings404 = NotFoundResponse -export type CreateEmbeddings429 = TooManyRequestsResponse -export const CreateEmbeddings429 = TooManyRequestsResponse -export type CreateEmbeddings500 = InternalServerResponse -export const CreateEmbeddings500 = InternalServerResponse -export type CreateEmbeddings502 = BadGatewayResponse -export const CreateEmbeddings502 = BadGatewayResponse -export type CreateEmbeddings503 = ServiceUnavailableResponse -export const CreateEmbeddings503 = ServiceUnavailableResponse -export type CreateEmbeddings524 = EdgeNetworkTimeoutResponse -export const CreateEmbeddings524 = EdgeNetworkTimeoutResponse -export type CreateEmbeddings529 = ProviderOverloadedResponse -export const CreateEmbeddings529 = ProviderOverloadedResponse -export type ListEmbeddingsModels200 = ModelsListResponse -export const ListEmbeddingsModels200 = ModelsListResponse -export type ListEmbeddingsModels400 = BadRequestResponse -export const ListEmbeddingsModels400 = BadRequestResponse -export type ListEmbeddingsModels500 = InternalServerResponse -export const ListEmbeddingsModels500 = InternalServerResponse -export type GetGenerationParams = { readonly "id": string } -export const GetGenerationParams = Schema.Struct({ "id": Schema.String.check(Schema.isMinLength(1)) }) -export type GetGeneration200 = { - readonly "data": { + readonly "data_region": "global" | "europe" + readonly "external_user": string | null + readonly "finish_reason": string | null + readonly "generation_time": number | null + readonly "http_referer": string | null readonly "id": string - readonly "upstream_id": string - readonly "total_cost": number - readonly "cache_discount": number - readonly "upstream_inference_cost": number - readonly "created_at": string + readonly "is_byok": boolean + readonly "latency": number | null readonly "model": string - readonly "app_id": number - readonly "streamed": boolean - readonly "cancelled": boolean - readonly "provider_name": string - readonly "latency": number - readonly "moderation_latency": number - readonly "generation_time": number - readonly "finish_reason": string - readonly "tokens_prompt": number - readonly "tokens_completion": number - readonly "native_tokens_prompt": number - readonly "native_tokens_completion": number - readonly "native_tokens_completion_images": number - readonly "native_tokens_reasoning": number - readonly "native_tokens_cached": number - readonly "num_media_prompt": number - readonly "num_input_audio_prompt": number - readonly "num_media_completion": number - readonly "num_search_results": number + readonly "moderation_latency": number | null + readonly "native_finish_reason": string | null + readonly "native_tokens_cached": number | null + readonly "native_tokens_completion": number | null + readonly "native_tokens_completion_images": number | null + readonly "native_tokens_prompt": number | null + readonly "native_tokens_reasoning": number | null + readonly "num_fetches": number | null + readonly "num_input_audio_prompt": number | null + readonly "num_media_completion": number | null + readonly "num_media_prompt": number | null + readonly "num_search_results": number | null readonly "origin": string + readonly "preset_id": string | null + readonly "provider_name": string | null + readonly "provider_responses": ReadonlyArray | null + readonly "request_id"?: string | null + readonly "response_cache_source_id"?: string | null + readonly "router": string | null + readonly "service_tier": string | null + readonly "session_id"?: string | null + readonly "streamed": boolean | null + readonly "tokens_completion": number | null + readonly "tokens_prompt": number | null + readonly "total_cost": number + readonly "upstream_id": string | null + readonly "upstream_inference_cost": number | null readonly "usage": number - readonly "is_byok": boolean - readonly "native_finish_reason": string - readonly "external_user": string - readonly "api_type": "completions" | "embeddings" - readonly "router": string - readonly "provider_responses": ReadonlyArray< - { - readonly "id"?: string - readonly "endpoint_id"?: string - readonly "model_permaslug"?: string - readonly "provider_name"?: - | "AnyScale" - | "Atoma" - | "Cent-ML" - | "CrofAI" - | "Enfer" - | "GoPomelo" - | "HuggingFace" - | "Hyperbolic 2" - | "InoCloud" - | "Kluster" - | "Lambda" - | "Lepton" - | "Lynn 2" - | "Lynn" - | "Mancer" - | "Meta" - | "Modal" - | "Nineteen" - | "OctoAI" - | "Recursal" - | "Reflection" - | "Replicate" - | "SambaNova 2" - | "SF Compute" - | "Targon" - | "Together 2" - | "Ubicloud" - | "01.AI" - | "AI21" - | "AionLabs" - | "Alibaba" - | "Ambient" - | "Amazon Bedrock" - | "Amazon Nova" - | "Anthropic" - | "Arcee AI" - | "AtlasCloud" - | "Avian" - | "Azure" - | "BaseTen" - | "BytePlus" - | "Black Forest Labs" - | "Cerebras" - | "Chutes" - | "Cirrascale" - | "Clarifai" - | "Cloudflare" - | "Cohere" - | "Crusoe" - | "DeepInfra" - | "DeepSeek" - | "Featherless" - | "Fireworks" - | "Friendli" - | "GMICloud" - | "Google" - | "Google AI Studio" - | "Groq" - | "Hyperbolic" - | "Inception" - | "Inceptron" - | "InferenceNet" - | "Infermatic" - | "Io Net" - | "Inflection" - | "Liquid" - | "Mara" - | "Mancer 2" - | "Minimax" - | "ModelRun" - | "Mistral" - | "Modular" - | "Moonshot AI" - | "Morph" - | "NCompass" - | "Nebius" - | "NextBit" - | "Novita" - | "Nvidia" - | "OpenAI" - | "OpenInference" - | "Parasail" - | "Perplexity" - | "Phala" - | "Relace" - | "SambaNova" - | "Seed" - | "SiliconFlow" - | "Sourceful" - | "StepFun" - | "Stealth" - | "StreamLake" - | "Switchpoint" - | "Together" - | "Upstage" - | "Venice" - | "WandB" - | "Xiaomi" - | "xAI" - | "Z.AI" - | "FakeProvider" - readonly "status": number - readonly "latency"?: number - readonly "is_byok"?: boolean - } - > + readonly "user_agent": string | null + readonly "web_search_engine": string | null } } -export const GetGeneration200 = Schema.Struct({ +export const GenerationResponse = Schema.Struct({ "data": Schema.Struct({ + "api_type": Schema.Union([ + Schema.Literal("completions"), + Schema.Literal("embeddings"), + Schema.Literal("rerank"), + Schema.Literal("tts"), + Schema.Literal("stt"), + Schema.Literal("video"), + Schema.Literal("image"), + Schema.Null + ]).annotate({ "description": "Type of API used for the generation" }), + "app_id": Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]) + .annotate({ "description": "ID of the app that made the request" }), + "cache_discount": Schema.Union([ + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), + Schema.Null + ]).annotate({ "description": "Discount applied due to caching", "format": "double" }), + "cancelled": Schema.Union([Schema.Boolean, Schema.Null]).annotate({ + "description": "Whether the generation was cancelled" + }), + "created_at": Schema.String.annotate({ "description": "ISO 8601 timestamp of when the generation was created" }), + "data_region": Schema.Literals(["global", "europe"]).annotate({ + "description": + "The data region this generation was routed through. 'europe' for EU-routed requests, 'global' otherwise." + }), + "external_user": Schema.Union([Schema.String, Schema.Null]).annotate({ "description": "External user identifier" }), + "finish_reason": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "Reason the generation finished" + }), + "generation_time": Schema.Union([ + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), + Schema.Null + ]).annotate({ "description": "Time taken for generation in milliseconds", "format": "double" }), + "http_referer": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "Referer header from the request" + }), "id": Schema.String.annotate({ "description": "Unique identifier for the generation" }), - "upstream_id": Schema.String.annotate({ "description": "Upstream provider's identifier for this generation" }), - "total_cost": Schema.Number.annotate({ "description": "Total cost of the generation in USD" }).check( - Schema.isFinite() + "is_byok": Schema.Boolean.annotate({ "description": "Whether this used bring-your-own-key" }), + "latency": Schema.Union([ + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), + Schema.Null + ]).annotate({ "description": "Total latency in milliseconds", "format": "double" }), + "model": Schema.String.annotate({ "description": "Model used for the generation" }), + "moderation_latency": Schema.Union([ + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), + Schema.Null + ]).annotate({ "description": "Moderation latency in milliseconds", "format": "double" }), + "native_finish_reason": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "Native finish reason as reported by provider" + }), + "native_tokens_cached": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]).annotate({ "description": "Native cached tokens as reported by provider" }), + "native_tokens_completion": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]).annotate({ "description": "Native completion tokens as reported by provider" }), + "native_tokens_completion_images": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]).annotate({ "description": "Native completion image tokens as reported by provider" }), + "native_tokens_prompt": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]).annotate({ "description": "Native prompt tokens as reported by provider" }), + "native_tokens_reasoning": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]).annotate({ "description": "Native reasoning tokens as reported by provider" }), + "num_fetches": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]).annotate({ "description": "Number of web fetches performed" }), + "num_input_audio_prompt": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]).annotate({ "description": "Number of audio inputs in the prompt" }), + "num_media_completion": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]).annotate({ "description": "Number of media items in the completion" }), + "num_media_prompt": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]).annotate({ "description": "Number of media items in the prompt" }), + "num_search_results": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]).annotate({ "description": "Number of search results included" }), + "origin": Schema.String.annotate({ "description": "Origin URL of the request" }), + "preset_id": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "ID of the preset used for this generation, null if no preset was used" + }), + "provider_name": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "Name of the provider that served the request" + }), + "provider_responses": Schema.Union([Schema.Array(ProviderResponse), Schema.Null]).annotate({ + "description": "List of provider responses for this generation, including fallback attempts" + }), + "request_id": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "Unique identifier grouping all generations from a single API request" + }) ), - "cache_discount": Schema.Number.annotate({ "description": "Discount applied due to caching" }).check( - Schema.isFinite() + "response_cache_source_id": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": + "If this generation was served from response cache, contains the original generation ID. Null otherwise." + }) ), - "upstream_inference_cost": Schema.Number.annotate({ "description": "Cost charged by the upstream provider" }).check( - Schema.isFinite() + "router": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "Router used for the request (e.g., openrouter/auto)" + }), + "service_tier": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": + "Service tier the upstream provider reported running this request on, or null if it did not report one." + }), + "session_id": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "Session identifier grouping multiple generations in the same session" + }) ), - "created_at": Schema.String.annotate({ "description": "ISO 8601 timestamp of when the generation was created" }), - "model": Schema.String.annotate({ "description": "Model used for the generation" }), - "app_id": Schema.Number.annotate({ "description": "ID of the app that made the request" }).check(Schema.isFinite()), - "streamed": Schema.Boolean.annotate({ "description": "Whether the response was streamed" }), - "cancelled": Schema.Boolean.annotate({ "description": "Whether the generation was cancelled" }), - "provider_name": Schema.String.annotate({ "description": "Name of the provider that served the request" }), - "latency": Schema.Number.annotate({ "description": "Total latency in milliseconds" }).check(Schema.isFinite()), - "moderation_latency": Schema.Number.annotate({ "description": "Moderation latency in milliseconds" }).check( - Schema.isFinite() + "streamed": Schema.Union([Schema.Boolean, Schema.Null]).annotate({ + "description": "Whether the response was streamed" + }), + "tokens_completion": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]).annotate({ "description": "Number of tokens in the completion" }), + "tokens_prompt": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]).annotate({ "description": "Number of tokens in the prompt" }), + "total_cost": Schema.Number.annotate({ "description": "Total cost of the generation in USD", "format": "double" }) + .check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "upstream_id": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "Upstream provider's identifier for this generation" + }), + "upstream_inference_cost": Schema.Union([ + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), + Schema.Null + ]).annotate({ "description": "Cost charged by the upstream provider", "format": "double" }), + "usage": Schema.Number.annotate({ "description": "Usage amount in USD", "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) ), - "generation_time": Schema.Number.annotate({ "description": "Time taken for generation in milliseconds" }).check( - Schema.isFinite() + "user_agent": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "User-Agent header from the request" + }), + "web_search_engine": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "The resolved web search engine used for this generation (e.g. exa, firecrawl, parallel)" + }) + }).annotate({ "description": "Generation data" }) +}).annotate({ "description": "Generation response", "identifier": "GenerationResponse" }) +export type Union_5 = ProviderSort | ProviderSortConfig | null +export const Union_5 = Schema.Union([ProviderSort, ProviderSortConfig, Schema.Null]).annotate({ + "description": + "The sorting strategy to use for this request, if \"order\" is not specified. When set, no load balancing is performed." +}) +export type PublicEndpoint = { + readonly "context_length": number + readonly "latency_last_30m": PercentileStats + readonly "max_completion_tokens": number | null + readonly "max_prompt_tokens": number | null + readonly "model_id": string + readonly "model_name": string + readonly "name": string + readonly "pricing": { + readonly "audio"?: string + readonly "audio_output"?: string + readonly "completion": string + readonly "discount"?: number + readonly "image"?: string + readonly "image_output"?: string + readonly "image_token"?: string + readonly "input_audio_cache"?: string + readonly "input_cache_read"?: string + readonly "input_cache_write"?: string + readonly "input_cache_write_1h"?: string + readonly "internal_reasoning"?: string + readonly "overrides"?: ReadonlyArray + readonly "prompt": string + readonly "request"?: string + readonly "web_search"?: string + } + readonly "provider_name": ProviderName + readonly "quantization": Quantization | null + readonly "status"?: EndpointStatus + readonly "supported_parameters": ReadonlyArray + readonly "supports_implicit_caching": boolean + readonly "tag": string + readonly "throughput_last_30m": PercentileStats + readonly "uptime_last_1d": number | null + readonly "uptime_last_30m": number | null + readonly "uptime_last_5m": number | null +} +export const PublicEndpoint = Schema.Struct({ + "context_length": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "latency_last_30m": PercentileStats, + "max_completion_tokens": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]), + "max_prompt_tokens": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]), + "model_id": Schema.String.annotate({ "description": "The unique identifier for the model (permaslug)" }), + "model_name": Schema.String, + "name": Schema.String, + "pricing": Schema.Struct({ + "audio": Schema.optionalKey(Schema.String.annotate({ "description": "Price in USD per audio input token" })), + "audio_output": Schema.optionalKey( + Schema.String.annotate({ "description": "Price in USD per audio output token" }) ), - "finish_reason": Schema.String.annotate({ "description": "Reason the generation finished" }), - "tokens_prompt": Schema.Number.annotate({ "description": "Number of tokens in the prompt" }).check( - Schema.isFinite() + "completion": Schema.String.annotate({ + "description": "Price in USD per token for completion (output) generation" + }), + "discount": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Fractional discount applied to this endpoint's pricing; the price is multiplied by (1 - discount) (0 = no discount, 1 = free)", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })) ), - "tokens_completion": Schema.Number.annotate({ "description": "Number of tokens in the completion" }).check( - Schema.isFinite() + "image": Schema.optionalKey(Schema.String.annotate({ "description": "Price in USD per input image" })), + "image_output": Schema.optionalKey(Schema.String.annotate({ "description": "Price in USD per output image" })), + "image_token": Schema.optionalKey(Schema.String.annotate({ "description": "Price in USD per image token" })), + "input_audio_cache": Schema.optionalKey( + Schema.String.annotate({ "description": "Price in USD per cached audio input token" }) ), - "native_tokens_prompt": Schema.Number.annotate({ "description": "Native prompt tokens as reported by provider" }) - .check(Schema.isFinite()), - "native_tokens_completion": Schema.Number.annotate({ - "description": "Native completion tokens as reported by provider" - }).check(Schema.isFinite()), - "native_tokens_completion_images": Schema.Number.annotate({ - "description": "Native completion image tokens as reported by provider" - }).check(Schema.isFinite()), - "native_tokens_reasoning": Schema.Number.annotate({ - "description": "Native reasoning tokens as reported by provider" - }).check(Schema.isFinite()), - "native_tokens_cached": Schema.Number.annotate({ "description": "Native cached tokens as reported by provider" }) - .check(Schema.isFinite()), - "num_media_prompt": Schema.Number.annotate({ "description": "Number of media items in the prompt" }).check( - Schema.isFinite() + "input_cache_read": Schema.optionalKey( + Schema.String.annotate({ "description": "Price in USD per cached input token (read)" }) ), - "num_input_audio_prompt": Schema.Number.annotate({ "description": "Number of audio inputs in the prompt" }).check( - Schema.isFinite() + "input_cache_write": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Price per cache-write token, in USD per token. For providers with multiple cache TTLs (e.g. Anthropic), this is the default (5-minute) cache-write rate." + }) ), - "num_media_completion": Schema.Number.annotate({ "description": "Number of media items in the completion" }).check( - Schema.isFinite() + "input_cache_write_1h": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Price per 1-hour cache-write token, in USD per token. Only present for providers that price an extended (1-hour) cache TTL separately, such as Anthropic." + }) ), - "num_search_results": Schema.Number.annotate({ "description": "Number of search results included" }).check( - Schema.isFinite() + "internal_reasoning": Schema.optionalKey( + Schema.String.annotate({ "description": "Price in USD per internal reasoning token" }) ), - "origin": Schema.String.annotate({ "description": "Origin URL of the request" }), - "usage": Schema.Number.annotate({ "description": "Usage amount in USD" }).check(Schema.isFinite()), - "is_byok": Schema.Boolean.annotate({ "description": "Whether this used bring-your-own-key" }), - "native_finish_reason": Schema.String.annotate({ "description": "Native finish reason as reported by provider" }), - "external_user": Schema.String.annotate({ "description": "External user identifier" }), - "api_type": Schema.Literals(["completions", "embeddings"]).annotate({ - "description": "Type of API used for the generation" - }), - "router": Schema.String.annotate({ "description": "Router used for the request (e.g., openrouter/auto)" }), - "provider_responses": Schema.Array(Schema.Struct({ - "id": Schema.optionalKey(Schema.String), - "endpoint_id": Schema.optionalKey(Schema.String), - "model_permaslug": Schema.optionalKey(Schema.String), - "provider_name": Schema.optionalKey( - Schema.Literals([ - "AnyScale", - "Atoma", - "Cent-ML", - "CrofAI", - "Enfer", - "GoPomelo", - "HuggingFace", - "Hyperbolic 2", - "InoCloud", - "Kluster", - "Lambda", - "Lepton", - "Lynn 2", - "Lynn", - "Mancer", - "Meta", - "Modal", - "Nineteen", - "OctoAI", - "Recursal", - "Reflection", - "Replicate", - "SambaNova 2", - "SF Compute", - "Targon", - "Together 2", - "Ubicloud", - "01.AI", - "AI21", - "AionLabs", - "Alibaba", - "Ambient", - "Amazon Bedrock", - "Amazon Nova", - "Anthropic", - "Arcee AI", - "AtlasCloud", - "Avian", - "Azure", - "BaseTen", - "BytePlus", - "Black Forest Labs", - "Cerebras", - "Chutes", - "Cirrascale", - "Clarifai", - "Cloudflare", - "Cohere", - "Crusoe", - "DeepInfra", - "DeepSeek", - "Featherless", - "Fireworks", - "Friendli", - "GMICloud", - "Google", - "Google AI Studio", - "Groq", - "Hyperbolic", - "Inception", - "Inceptron", - "InferenceNet", - "Infermatic", - "Io Net", - "Inflection", - "Liquid", - "Mara", - "Mancer 2", - "Minimax", - "ModelRun", - "Mistral", - "Modular", - "Moonshot AI", - "Morph", - "NCompass", - "Nebius", - "NextBit", - "Novita", - "Nvidia", - "OpenAI", - "OpenInference", - "Parasail", - "Perplexity", - "Phala", - "Relace", - "SambaNova", - "Seed", - "SiliconFlow", - "Sourceful", - "StepFun", - "Stealth", - "StreamLake", - "Switchpoint", - "Together", - "Upstage", - "Venice", - "WandB", - "Xiaomi", - "xAI", - "Z.AI", - "FakeProvider" - ]) - ), - "status": Schema.Number.check(Schema.isFinite()), - "latency": Schema.optionalKey(Schema.Number.check(Schema.isFinite())), - "is_byok": Schema.optionalKey(Schema.Boolean) - })).annotate({ "description": "List of provider responses for this generation, including fallback attempts" }) - }).annotate({ "description": "Generation data" }) -}).annotate({ "description": "Generation response" }) -export type GetGeneration401 = UnauthorizedResponse -export const GetGeneration401 = UnauthorizedResponse -export type GetGeneration402 = PaymentRequiredResponse -export const GetGeneration402 = PaymentRequiredResponse -export type GetGeneration404 = NotFoundResponse -export const GetGeneration404 = NotFoundResponse -export type GetGeneration429 = TooManyRequestsResponse -export const GetGeneration429 = TooManyRequestsResponse -export type GetGeneration500 = InternalServerResponse -export const GetGeneration500 = InternalServerResponse -export type GetGeneration502 = BadGatewayResponse -export const GetGeneration502 = BadGatewayResponse -export type GetGeneration524 = EdgeNetworkTimeoutResponse -export const GetGeneration524 = EdgeNetworkTimeoutResponse -export type GetGeneration529 = ProviderOverloadedResponse -export const GetGeneration529 = ProviderOverloadedResponse -export type ListModelsCount200 = ModelsCountResponse -export const ListModelsCount200 = ModelsCountResponse -export type ListModelsCount500 = InternalServerResponse -export const ListModelsCount500 = InternalServerResponse -export type GetModelsParams = { - readonly "category"?: - | "programming" - | "roleplay" - | "marketing" - | "marketing/seo" - | "technology" - | "science" - | "translation" - | "legal" - | "finance" - | "health" - | "trivia" - | "academia" - readonly "supported_parameters"?: string -} -export const GetModelsParams = Schema.Struct({ - "category": Schema.optionalKey( - Schema.Literals([ - "programming", - "roleplay", - "marketing", - "marketing/seo", - "technology", - "science", - "translation", - "legal", - "finance", - "health", - "trivia", - "academia" - ]).annotate({ "description": "Filter models by use case category" }) - ), - "supported_parameters": Schema.optionalKey(Schema.String) + "overrides": Schema.optionalKey( + Schema.Array(PricingOverride).annotate({ + "description": + "Conditional overrides of the base pricing (e.g. long-context or time-based pricing). An entry applies when all of its condition fields (e.g. min_prompt_tokens, or the utc_start/utc_end time window) match the request; among applicable entries, later entries win per key; price keys absent from an entry inherit the base price. The top-level pricing keys always reflect the price that applies under default conditions." + }) + ), + "prompt": Schema.String.annotate({ "description": "Price in USD per token for prompt (input) processing" }), + "request": Schema.optionalKey(Schema.String.annotate({ "description": "Price in USD per request" })), + "web_search": Schema.optionalKey(Schema.String.annotate({ "description": "Price in USD per web search" })) + }), + "provider_name": ProviderName, + "quantization": Schema.Union([Quantization, Schema.Null]), + "status": Schema.optionalKey(EndpointStatus), + "supported_parameters": Schema.Array(Parameter), + "supports_implicit_caching": Schema.Boolean, + "tag": Schema.String, + "throughput_last_30m": Schema.suspend((): Schema.Codec => PercentileStats).annotate({ + "description": + "Throughput percentiles in tokens per second over the last 30 minutes. Throughput measures output token generation speed. Only visible when authenticated with an API key or cookie; returns null for unauthenticated requests." + }), + "uptime_last_1d": Schema.Union([ + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), + Schema.Null + ]).annotate({ + "description": + "Uptime percentage over the last 1 day, calculated as successful requests / (successful + error requests) * 100. Rate-limited requests are excluded. Returns null if insufficient data.", + "format": "double" + }), + "uptime_last_30m": Schema.Union([ + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), + Schema.Null + ]).annotate({ "format": "double" }), + "uptime_last_5m": Schema.Union([ + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), + Schema.Null + ]).annotate({ + "description": + "Uptime percentage over the last 5 minutes, calculated as successful requests / (successful + error requests) * 100. Rate-limited requests are excluded. Returns null if insufficient data.", + "format": "double" + }) +}).annotate({ "description": "Information about a specific model endpoint", "identifier": "PublicEndpoint" }) +export type CapabilityDescriptor = EnumCapability | RangeCapability | BooleanCapability +export const CapabilityDescriptor = Schema.Union([EnumCapability, RangeCapability, BooleanCapability], { + mode: "oneOf" +}).annotate({ + "description": "A typed descriptor for one supported request parameter.", + "identifier": "CapabilityDescriptor" }) -export type GetModels200 = ModelsListResponse -export const GetModels200 = ModelsListResponse -export type GetModels400 = BadRequestResponse -export const GetModels400 = BadRequestResponse -export type GetModels500 = InternalServerResponse -export const GetModels500 = InternalServerResponse -export type ListModelsUser200 = ModelsListResponse -export const ListModelsUser200 = ModelsListResponse -export type ListModelsUser401 = UnauthorizedResponse -export const ListModelsUser401 = UnauthorizedResponse -export type ListModelsUser404 = NotFoundResponse -export const ListModelsUser404 = NotFoundResponse -export type ListModelsUser500 = InternalServerResponse -export const ListModelsUser500 = InternalServerResponse -export type ListEndpoints200 = { readonly "data": ListEndpointsResponse } -export const ListEndpoints200 = Schema.Struct({ "data": ListEndpointsResponse }) -export type ListEndpoints404 = NotFoundResponse -export const ListEndpoints404 = NotFoundResponse -export type ListEndpoints500 = InternalServerResponse -export const ListEndpoints500 = InternalServerResponse -export type ListEndpointsZdr200 = { readonly "data": ReadonlyArray } -export const ListEndpointsZdr200 = Schema.Struct({ "data": Schema.Array(PublicEndpoint) }) -export type ListEndpointsZdr500 = InternalServerResponse -export const ListEndpointsZdr500 = InternalServerResponse -export type ListProviders200 = { - readonly "data": ReadonlyArray< - { - readonly "name": string - readonly "slug": string - readonly "privacy_policy_url": string - readonly "terms_of_service_url"?: string - readonly "status_page_url"?: string - } - > +export type AppRankingsResponse = { + readonly "data": ReadonlyArray + readonly "meta": RankingsDailyMeta } -export const ListProviders200 = Schema.Struct({ - "data": Schema.Array(Schema.Struct({ - "name": Schema.String.annotate({ "description": "Display name of the provider" }), - "slug": Schema.String.annotate({ "description": "URL-friendly identifier for the provider" }), - "privacy_policy_url": Schema.String.annotate({ "description": "URL to the provider's privacy policy" }), - "terms_of_service_url": Schema.optionalKey( - Schema.String.annotate({ "description": "URL to the provider's terms of service" }) - ), - "status_page_url": Schema.optionalKey( - Schema.String.annotate({ "description": "URL to the provider's status page" }) - ) - })) +export const AppRankingsResponse = Schema.Struct({ + "data": Schema.Array(AppRankingsItem).annotate({ + "description": + "Apps ranked per the requested `sort`, re-numbered 1..N after category filtering. `popular` sorts by `total_tokens` descending; `trending` sorts by absolute excess token growth descending and may return fewer than `limit` rows when few apps are growing." + }), + "meta": RankingsDailyMeta +}).annotate({ "identifier": "AppRankingsResponse" }) +export type RankingsDailyResponse = { + readonly "data": ReadonlyArray + readonly "meta": RankingsDailyMeta +} +export const RankingsDailyResponse = Schema.Struct({ + "data": Schema.Array(RankingsDailyItem).annotate({ + "description": + "Up to 51 rows per day — the top 50 public models by `total_tokens` for each UTC calendar date in the window, plus one aggregated `other` row summing every model outside that top 50 (omitted when the long tail is empty). Rows are sorted by `date` ascending, then by `total_tokens` descending, with `other` pinned last within its date. Ties between real models break alphabetically on `model_permaslug` so the order is stable across requests." + }), + "meta": RankingsDailyMeta +}).annotate({ "identifier": "RankingsDailyResponse" }) +export type Union_12 = ReadonlyArray | null +export const Union_12 = Schema.Union([Schema.Array(ReasoningEffort), Schema.Null]).annotate({ + "description": + "Allowed reasoning effort values for this model, in descending effort order (highest first). Null means no allowlist — all gateway effort values are accepted." }) -export type ListProviders500 = InternalServerResponse -export const ListProviders500 = InternalServerResponse -export type ListParams = { readonly "include_disabled"?: string; readonly "offset"?: string } -export const ListParams = Schema.Struct({ - "include_disabled": Schema.optionalKey( - Schema.String.annotate({ "description": "Whether to include disabled API keys in the response" }) - ), - "offset": Schema.optionalKey(Schema.String.annotate({ "description": "Number of API keys to skip for pagination" })) +export type ReasoningDetailEncrypted = { + readonly "data": string + readonly "format"?: ReasoningFormat + readonly "id"?: string | null + readonly "index"?: number + readonly "type": "reasoning.encrypted" +} +export const ReasoningDetailEncrypted = Schema.Struct({ + "data": Schema.String, + "format": Schema.optionalKey(ReasoningFormat), + "id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "index": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "type": Schema.Literal("reasoning.encrypted") +}).annotate({ "description": "Reasoning detail encrypted schema", "identifier": "ReasoningDetailEncrypted" }) +export type ReasoningDetailServerToolCall = { + readonly "arguments": string + readonly "format"?: ReasoningFormat + readonly "id"?: string | null + readonly "index"?: number + readonly "result": string + readonly "tool_call_id"?: string | null + readonly "tool_name": string + readonly "type": "reasoning.server_tool_call" +} +export const ReasoningDetailServerToolCall = Schema.Struct({ + "arguments": Schema.String, + "format": Schema.optionalKey(ReasoningFormat), + "id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "index": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "result": Schema.String, + "tool_call_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "tool_name": Schema.String, + "type": Schema.Literal("reasoning.server_tool_call") +}).annotate({ + "description": + "Record of an OpenRouter server-tool invocation (e.g. openrouter:fusion), carried in reasoning_details so a prior tool call can be rehydrated into a later turn of the same conversation.", + "identifier": "ReasoningDetailServerToolCall" }) -export type List200 = { - readonly "data": ReadonlyArray< - { - readonly "hash": string - readonly "name": string - readonly "label": string - readonly "disabled": boolean - readonly "limit": number - readonly "limit_remaining": number - readonly "limit_reset": string - readonly "include_byok_in_limit": boolean - readonly "usage": number - readonly "usage_daily": number - readonly "usage_weekly": number - readonly "usage_monthly": number - readonly "byok_usage": number - readonly "byok_usage_daily": number - readonly "byok_usage_weekly": number - readonly "byok_usage_monthly": number - readonly "created_at": string - readonly "updated_at": string - readonly "expires_at"?: string - } - > +export type ReasoningDetailSummary = { + readonly "format"?: ReasoningFormat + readonly "id"?: string | null + readonly "index"?: number + readonly "summary": string + readonly "type": "reasoning.summary" } -export const List200 = Schema.Struct({ - "data": Schema.Array(Schema.Struct({ - "hash": Schema.String.annotate({ "description": "Unique hash identifier for the API key" }), - "name": Schema.String.annotate({ "description": "Name of the API key" }), - "label": Schema.String.annotate({ "description": "Human-readable label for the API key" }), - "disabled": Schema.Boolean.annotate({ "description": "Whether the API key is disabled" }), - "limit": Schema.Number.annotate({ "description": "Spending limit for the API key in USD" }).check( - Schema.isFinite() - ), - "limit_remaining": Schema.Number.annotate({ "description": "Remaining spending limit in USD" }).check( - Schema.isFinite() - ), - "limit_reset": Schema.String.annotate({ "description": "Type of limit reset for the API key" }), - "include_byok_in_limit": Schema.Boolean.annotate({ - "description": "Whether to include external BYOK usage in the credit limit" - }), - "usage": Schema.Number.annotate({ "description": "Total OpenRouter credit usage (in USD) for the API key" }).check( - Schema.isFinite() - ), - "usage_daily": Schema.Number.annotate({ "description": "OpenRouter credit usage (in USD) for the current UTC day" }) - .check(Schema.isFinite()), - "usage_weekly": Schema.Number.annotate({ - "description": "OpenRouter credit usage (in USD) for the current UTC week (Monday-Sunday)" - }).check(Schema.isFinite()), - "usage_monthly": Schema.Number.annotate({ - "description": "OpenRouter credit usage (in USD) for the current UTC month" - }).check(Schema.isFinite()), - "byok_usage": Schema.Number.annotate({ "description": "Total external BYOK usage (in USD) for the API key" }).check( - Schema.isFinite() - ), - "byok_usage_daily": Schema.Number.annotate({ - "description": "External BYOK usage (in USD) for the current UTC day" - }).check(Schema.isFinite()), - "byok_usage_weekly": Schema.Number.annotate({ - "description": "External BYOK usage (in USD) for the current UTC week (Monday-Sunday)" - }).check(Schema.isFinite()), - "byok_usage_monthly": Schema.Number.annotate({ - "description": "External BYOK usage (in USD) for current UTC month" - }).check(Schema.isFinite()), - "created_at": Schema.String.annotate({ "description": "ISO 8601 timestamp of when the API key was created" }), - "updated_at": Schema.String.annotate({ "description": "ISO 8601 timestamp of when the API key was last updated" }), - "expires_at": Schema.optionalKey( - Schema.String.annotate({ - "description": "ISO 8601 UTC timestamp when the API key expires, or null if no expiration", - "format": "date-time" - }) - ) - })).annotate({ "description": "List of API keys" }) +export const ReasoningDetailSummary = Schema.Struct({ + "format": Schema.optionalKey(ReasoningFormat), + "id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "index": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "summary": Schema.String, + "type": Schema.Literal("reasoning.summary") +}).annotate({ "description": "Reasoning detail summary schema", "identifier": "ReasoningDetailSummary" }) +export type ReasoningDetailText = { + readonly "format"?: ReasoningFormat + readonly "id"?: string | null + readonly "index"?: number + readonly "signature"?: string | null + readonly "text"?: string | null + readonly "type": "reasoning.text" +} +export const ReasoningDetailText = Schema.Struct({ + "format": Schema.optionalKey(ReasoningFormat), + "id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "index": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "signature": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "text": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "type": Schema.Literal("reasoning.text") +}).annotate({ "description": "Reasoning detail text schema", "identifier": "ReasoningDetailText" }) +export type BaseReasoningSummaryPartAddedEvent = { + readonly "item_id": string + readonly "output_index": number + readonly "part": ReasoningSummaryText + readonly "sequence_number": number + readonly "summary_index": number + readonly "type": "response.reasoning_summary_part.added" +} +export const BaseReasoningSummaryPartAddedEvent = Schema.Struct({ + "item_id": Schema.String, + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "part": ReasoningSummaryText, + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "summary_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.reasoning_summary_part.added") +}).annotate({ + "description": "Event emitted when a reasoning summary part is added", + "identifier": "BaseReasoningSummaryPartAddedEvent" }) -export type List401 = UnauthorizedResponse -export const List401 = UnauthorizedResponse -export type List429 = TooManyRequestsResponse -export const List429 = TooManyRequestsResponse -export type List500 = InternalServerResponse -export const List500 = InternalServerResponse -export type CreateKeysRequestJson = { - readonly "name": string - readonly "limit"?: number - readonly "limit_reset"?: "daily" | "weekly" | "monthly" - readonly "include_byok_in_limit"?: boolean - readonly "expires_at"?: string +export type BaseReasoningSummaryPartDoneEvent = { + readonly "item_id": string + readonly "output_index": number + readonly "part": ReasoningSummaryText + readonly "sequence_number": number + readonly "summary_index": number + readonly "type": "response.reasoning_summary_part.done" } -export const CreateKeysRequestJson = Schema.Struct({ - "name": Schema.String.annotate({ "description": "Name for the new API key" }).check(Schema.isMinLength(1)), - "limit": Schema.optionalKey( - Schema.Number.annotate({ "description": "Optional spending limit for the API key in USD" }).check(Schema.isFinite()) - ), - "limit_reset": Schema.optionalKey( - Schema.Literals(["daily", "weekly", "monthly"]).annotate({ - "description": - "Type of limit reset for the API key (daily, weekly, monthly, or null for no reset). Resets happen automatically at midnight UTC, and weeks are Monday through Sunday." - }) - ), - "include_byok_in_limit": Schema.optionalKey( - Schema.Boolean.annotate({ "description": "Whether to include BYOK usage in the limit" }) +export const BaseReasoningSummaryPartDoneEvent = Schema.Struct({ + "item_id": Schema.String, + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "part": ReasoningSummaryText, + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "summary_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.reasoning_summary_part.done") +}).annotate({ + "description": "Event emitted when a reasoning summary part is complete", + "identifier": "BaseReasoningSummaryPartDoneEvent" +}) +export type OutputReasoningItem = { + readonly "content"?: ReadonlyArray<{ readonly "text": string; readonly "type": "reasoning_text" }> + readonly "encrypted_content"?: string | null + readonly "id": string + readonly "status"?: "completed" | "incomplete" | "in_progress" + readonly "summary": ReadonlyArray + readonly "type": "reasoning" + readonly "format"?: ReasoningFormat + readonly "signature"?: string | null +} +export const OutputReasoningItem = Schema.Struct({ + "content": Schema.optionalKey( + Schema.Union([Schema.Array(Schema.Struct({ "text": Schema.String, "type": Schema.Literal("reasoning_text") }))]) ), - "expires_at": Schema.optionalKey( - Schema.String.annotate({ - "description": - "Optional ISO 8601 UTC timestamp when the API key should expire. Must be UTC, other timezones will be rejected", - "format": "date-time" + "encrypted_content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "id": Schema.String, + "status": Schema.optionalKey(Schema.Literals(["completed", "incomplete", "in_progress"])), + "summary": Schema.Array(ReasoningSummaryText), + "type": Schema.Literal("reasoning"), + "format": Schema.optionalKey(ReasoningFormat), + "signature": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "A signature for the reasoning content, used for verification" }) ) +}).annotate({ "description": "An output item containing reasoning", "identifier": "OutputReasoningItem" }) +export type ReasoningItem = { + readonly "content"?: ReadonlyArray<{ readonly "text": string; readonly "type": "reasoning_text" }> + readonly "encrypted_content"?: string | null + readonly "id": string + readonly "status"?: "completed" | "incomplete" | "in_progress" + readonly "summary": ReadonlyArray + readonly "type": "reasoning" + readonly "format"?: ReasoningFormat + readonly "signature"?: string | null +} +export const ReasoningItem = Schema.Struct({ + "content": Schema.optionalKey( + Schema.Union([Schema.Array(Schema.Struct({ "text": Schema.String, "type": Schema.Literal("reasoning_text") }))]) + ), + "encrypted_content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "id": Schema.String, + "status": Schema.optionalKey(Schema.Literals(["completed", "incomplete", "in_progress"])), + "summary": Schema.Array(ReasoningSummaryText), + "type": Schema.Literal("reasoning"), + "format": Schema.optionalKey(ReasoningFormat), + "signature": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) +}).annotate({ + "description": "Reasoning output item with signature and format extensions", + "identifier": "ReasoningItem" }) -export type CreateKeys201 = { - readonly "data": { - readonly "hash": string - readonly "name": string - readonly "label": string - readonly "disabled": boolean - readonly "limit": number - readonly "limit_remaining": number - readonly "limit_reset": string - readonly "include_byok_in_limit": boolean - readonly "usage": number - readonly "usage_daily": number - readonly "usage_weekly": number - readonly "usage_monthly": number - readonly "byok_usage": number - readonly "byok_usage_daily": number - readonly "byok_usage_weekly": number - readonly "byok_usage_monthly": number - readonly "created_at": string - readonly "updated_at": string - readonly "expires_at"?: string - } - readonly "key": string +export type ReasoningSummaryPartAddedEvent = { + readonly "item_id": string + readonly "output_index": number + readonly "part": ReasoningSummaryText + readonly "sequence_number": number + readonly "summary_index": number + readonly "type": "response.reasoning_summary_part.added" } -export const CreateKeys201 = Schema.Struct({ - "data": Schema.Struct({ - "hash": Schema.String.annotate({ "description": "Unique hash identifier for the API key" }), - "name": Schema.String.annotate({ "description": "Name of the API key" }), - "label": Schema.String.annotate({ "description": "Human-readable label for the API key" }), - "disabled": Schema.Boolean.annotate({ "description": "Whether the API key is disabled" }), - "limit": Schema.Number.annotate({ "description": "Spending limit for the API key in USD" }).check( - Schema.isFinite() - ), - "limit_remaining": Schema.Number.annotate({ "description": "Remaining spending limit in USD" }).check( - Schema.isFinite() - ), - "limit_reset": Schema.String.annotate({ "description": "Type of limit reset for the API key" }), - "include_byok_in_limit": Schema.Boolean.annotate({ - "description": "Whether to include external BYOK usage in the credit limit" - }), - "usage": Schema.Number.annotate({ "description": "Total OpenRouter credit usage (in USD) for the API key" }).check( - Schema.isFinite() - ), - "usage_daily": Schema.Number.annotate({ "description": "OpenRouter credit usage (in USD) for the current UTC day" }) - .check(Schema.isFinite()), - "usage_weekly": Schema.Number.annotate({ - "description": "OpenRouter credit usage (in USD) for the current UTC week (Monday-Sunday)" - }).check(Schema.isFinite()), - "usage_monthly": Schema.Number.annotate({ - "description": "OpenRouter credit usage (in USD) for the current UTC month" - }).check(Schema.isFinite()), - "byok_usage": Schema.Number.annotate({ "description": "Total external BYOK usage (in USD) for the API key" }).check( - Schema.isFinite() - ), - "byok_usage_daily": Schema.Number.annotate({ - "description": "External BYOK usage (in USD) for the current UTC day" - }).check(Schema.isFinite()), - "byok_usage_weekly": Schema.Number.annotate({ - "description": "External BYOK usage (in USD) for the current UTC week (Monday-Sunday)" - }).check(Schema.isFinite()), - "byok_usage_monthly": Schema.Number.annotate({ - "description": "External BYOK usage (in USD) for current UTC month" - }).check(Schema.isFinite()), - "created_at": Schema.String.annotate({ "description": "ISO 8601 timestamp of when the API key was created" }), - "updated_at": Schema.String.annotate({ "description": "ISO 8601 timestamp of when the API key was last updated" }), - "expires_at": Schema.optionalKey( - Schema.String.annotate({ - "description": "ISO 8601 UTC timestamp when the API key expires, or null if no expiration", - "format": "date-time" - }) - ) - }).annotate({ "description": "The created API key information" }), - "key": Schema.String.annotate({ "description": "The actual API key string (only shown once)" }) +export const ReasoningSummaryPartAddedEvent = Schema.Struct({ + "item_id": Schema.String, + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "part": ReasoningSummaryText, + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "summary_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.reasoning_summary_part.added") +}).annotate({ + "description": "Event emitted when a reasoning summary part is added", + "identifier": "ReasoningSummaryPartAddedEvent" }) -export type CreateKeys400 = BadRequestResponse -export const CreateKeys400 = BadRequestResponse -export type CreateKeys401 = UnauthorizedResponse -export const CreateKeys401 = UnauthorizedResponse -export type CreateKeys429 = TooManyRequestsResponse -export const CreateKeys429 = TooManyRequestsResponse -export type CreateKeys500 = InternalServerResponse -export const CreateKeys500 = InternalServerResponse -export type GetKey200 = { - readonly "data": { - readonly "hash": string - readonly "name": string - readonly "label": string - readonly "disabled": boolean - readonly "limit": number - readonly "limit_remaining": number - readonly "limit_reset": string - readonly "include_byok_in_limit": boolean - readonly "usage": number - readonly "usage_daily": number - readonly "usage_weekly": number - readonly "usage_monthly": number - readonly "byok_usage": number - readonly "byok_usage_daily": number - readonly "byok_usage_weekly": number - readonly "byok_usage_monthly": number - readonly "created_at": string - readonly "updated_at": string - readonly "expires_at"?: string - } +export type ReasoningSummaryPartDoneEvent = { + readonly "item_id": string + readonly "output_index": number + readonly "part": ReasoningSummaryText + readonly "sequence_number": number + readonly "summary_index": number + readonly "type": "response.reasoning_summary_part.done" } -export const GetKey200 = Schema.Struct({ - "data": Schema.Struct({ - "hash": Schema.String.annotate({ "description": "Unique hash identifier for the API key" }), - "name": Schema.String.annotate({ "description": "Name of the API key" }), - "label": Schema.String.annotate({ "description": "Human-readable label for the API key" }), - "disabled": Schema.Boolean.annotate({ "description": "Whether the API key is disabled" }), - "limit": Schema.Number.annotate({ "description": "Spending limit for the API key in USD" }).check( - Schema.isFinite() - ), - "limit_remaining": Schema.Number.annotate({ "description": "Remaining spending limit in USD" }).check( - Schema.isFinite() - ), - "limit_reset": Schema.String.annotate({ "description": "Type of limit reset for the API key" }), - "include_byok_in_limit": Schema.Boolean.annotate({ - "description": "Whether to include external BYOK usage in the credit limit" - }), - "usage": Schema.Number.annotate({ "description": "Total OpenRouter credit usage (in USD) for the API key" }).check( - Schema.isFinite() - ), - "usage_daily": Schema.Number.annotate({ "description": "OpenRouter credit usage (in USD) for the current UTC day" }) - .check(Schema.isFinite()), - "usage_weekly": Schema.Number.annotate({ - "description": "OpenRouter credit usage (in USD) for the current UTC week (Monday-Sunday)" - }).check(Schema.isFinite()), - "usage_monthly": Schema.Number.annotate({ - "description": "OpenRouter credit usage (in USD) for the current UTC month" - }).check(Schema.isFinite()), - "byok_usage": Schema.Number.annotate({ "description": "Total external BYOK usage (in USD) for the API key" }).check( - Schema.isFinite() - ), - "byok_usage_daily": Schema.Number.annotate({ - "description": "External BYOK usage (in USD) for the current UTC day" - }).check(Schema.isFinite()), - "byok_usage_weekly": Schema.Number.annotate({ - "description": "External BYOK usage (in USD) for the current UTC week (Monday-Sunday)" - }).check(Schema.isFinite()), - "byok_usage_monthly": Schema.Number.annotate({ - "description": "External BYOK usage (in USD) for current UTC month" - }).check(Schema.isFinite()), - "created_at": Schema.String.annotate({ "description": "ISO 8601 timestamp of when the API key was created" }), - "updated_at": Schema.String.annotate({ "description": "ISO 8601 timestamp of when the API key was last updated" }), - "expires_at": Schema.optionalKey( - Schema.String.annotate({ - "description": "ISO 8601 UTC timestamp when the API key expires, or null if no expiration", - "format": "date-time" - }) - ) - }).annotate({ "description": "The API key information" }) +export const ReasoningSummaryPartDoneEvent = Schema.Struct({ + "item_id": Schema.String, + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "part": ReasoningSummaryText, + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "summary_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.reasoning_summary_part.done") +}).annotate({ + "description": "Event emitted when a reasoning summary part is complete", + "identifier": "ReasoningSummaryPartDoneEvent" }) -export type GetKey401 = UnauthorizedResponse -export const GetKey401 = UnauthorizedResponse -export type GetKey404 = NotFoundResponse -export const GetKey404 = NotFoundResponse -export type GetKey429 = TooManyRequestsResponse -export const GetKey429 = TooManyRequestsResponse -export type GetKey500 = InternalServerResponse -export const GetKey500 = InternalServerResponse -export type DeleteKeys200 = { readonly "deleted": true } -export const DeleteKeys200 = Schema.Struct({ - "deleted": Schema.Literal(true).annotate({ "description": "Confirmation that the API key was deleted" }) +export type Objects_5 = { + readonly "context"?: ReasoningContext + readonly "effort"?: ReasoningEffort + readonly "mode"?: ReasoningMode + readonly "summary"?: ReasoningSummaryVerbosity + readonly [x: string]: Schema.Json +} +export const Objects_5 = Schema.StructWithRest( + Schema.Struct({ + "context": Schema.optionalKey(ReasoningContext), + "effort": Schema.optionalKey(ReasoningEffort), + "mode": Schema.optionalKey(ReasoningMode), + "summary": Schema.optionalKey(ReasoningSummaryVerbosity) + }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] +) +export type Union_13 = { + readonly "context"?: ReasoningContext + readonly "effort"?: ReasoningEffort + readonly "mode"?: ReasoningMode + readonly "summary"?: ReasoningSummaryVerbosity + readonly "enabled"?: boolean | null + readonly "max_tokens"?: number | null +} +export const Union_13 = Schema.Union([Schema.Struct({ + "context": Schema.optionalKey(ReasoningContext), + "effort": Schema.optionalKey(ReasoningEffort), + "mode": Schema.optionalKey(ReasoningMode), + "summary": Schema.optionalKey(ReasoningSummaryVerbosity), + "enabled": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), + "max_tokens": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]) + ) +})]) +export type OutputItemReasoning = { + readonly "content"?: ReadonlyArray + readonly "encrypted_content"?: string | null + readonly "id": string + readonly "status"?: "completed" | "incomplete" | "in_progress" + readonly "summary": ReadonlyArray + readonly "type": "reasoning" +} +export const OutputItemReasoning = Schema.Struct({ + "content": Schema.optionalKey(Schema.Array(ReasoningTextContent)), + "encrypted_content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "id": Schema.String, + "status": Schema.optionalKey(Schema.Literals(["completed", "incomplete", "in_progress"])), + "summary": Schema.Array(ReasoningSummaryText), + "type": Schema.Literal("reasoning") +}).annotate({ "identifier": "OutputItemReasoning" }) +export type RequestMetadata = Objects_149 | null +export const RequestMetadata = Schema.Union([Objects_149, Schema.Null]).annotate({ + "description": + "Metadata key-value pairs for the request. Keys must be ≤64 characters and cannot contain brackets. Values must be ≤512 characters. Maximum 16 pairs allowed.", + "identifier": "RequestMetadata" }) -export type DeleteKeys401 = UnauthorizedResponse -export const DeleteKeys401 = UnauthorizedResponse -export type DeleteKeys404 = NotFoundResponse -export const DeleteKeys404 = NotFoundResponse -export type DeleteKeys429 = TooManyRequestsResponse -export const DeleteKeys429 = TooManyRequestsResponse -export type DeleteKeys500 = InternalServerResponse -export const DeleteKeys500 = InternalServerResponse -export type UpdateKeysRequestJson = { - readonly "name"?: string - readonly "disabled"?: boolean - readonly "limit"?: number - readonly "limit_reset"?: "daily" | "weekly" | "monthly" - readonly "include_byok_in_limit"?: boolean +export type RequestTimeoutResponse = { + readonly "error": RequestTimeoutResponseErrorData + readonly "openrouter_metadata"?: { readonly [x: string]: Schema.Json } | null + readonly "user_id"?: string | null } -export const UpdateKeysRequestJson = Schema.Struct({ - "name": Schema.optionalKey(Schema.String.annotate({ "description": "New name for the API key" })), - "disabled": Schema.optionalKey(Schema.Boolean.annotate({ "description": "Whether to disable the API key" })), - "limit": Schema.optionalKey( - Schema.Number.annotate({ "description": "New spending limit for the API key in USD" }).check(Schema.isFinite()) - ), - "limit_reset": Schema.optionalKey( - Schema.Literals(["daily", "weekly", "monthly"]).annotate({ - "description": - "New limit reset type for the API key (daily, weekly, monthly, or null for no reset). Resets happen automatically at midnight UTC, and weeks are Monday through Sunday." - }) +export const RequestTimeoutResponse = Schema.Struct({ + "error": RequestTimeoutResponseErrorData, + "openrouter_metadata": Schema.optionalKey( + Schema.Union([Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })), Schema.Null]) ), - "include_byok_in_limit": Schema.optionalKey( - Schema.Boolean.annotate({ "description": "Whether to include BYOK usage in the limit" }) - ) + "user_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) +}).annotate({ + "description": "Request Timeout - Operation exceeded time limit", + "identifier": "RequestTimeoutResponse" }) -export type UpdateKeys200 = { - readonly "data": { - readonly "hash": string - readonly "name": string - readonly "label": string - readonly "disabled": boolean - readonly "limit": number - readonly "limit_remaining": number - readonly "limit_reset": string - readonly "include_byok_in_limit": boolean - readonly "usage": number - readonly "usage_daily": number - readonly "usage_weekly": number - readonly "usage_monthly": number - readonly "byok_usage": number - readonly "byok_usage_daily": number - readonly "byok_usage_weekly": number - readonly "byok_usage_monthly": number - readonly "created_at": string - readonly "updated_at": string - readonly "expires_at"?: string - } +export type Arrays_10 = ReadonlyArray +export const Arrays_10 = Schema.Array(RouterAttempt) +export type ChatSearchModelsServerTool = { + readonly "parameters"?: SearchModelsServerToolConfig + readonly "type": "openrouter:experimental__search_models" } -export const UpdateKeys200 = Schema.Struct({ - "data": Schema.Struct({ - "hash": Schema.String.annotate({ "description": "Unique hash identifier for the API key" }), - "name": Schema.String.annotate({ "description": "Name of the API key" }), - "label": Schema.String.annotate({ "description": "Human-readable label for the API key" }), - "disabled": Schema.Boolean.annotate({ "description": "Whether the API key is disabled" }), - "limit": Schema.Number.annotate({ "description": "Spending limit for the API key in USD" }).check( - Schema.isFinite() - ), - "limit_remaining": Schema.Number.annotate({ "description": "Remaining spending limit in USD" }).check( - Schema.isFinite() - ), - "limit_reset": Schema.String.annotate({ "description": "Type of limit reset for the API key" }), - "include_byok_in_limit": Schema.Boolean.annotate({ - "description": "Whether to include external BYOK usage in the credit limit" - }), - "usage": Schema.Number.annotate({ "description": "Total OpenRouter credit usage (in USD) for the API key" }).check( - Schema.isFinite() - ), - "usage_daily": Schema.Number.annotate({ "description": "OpenRouter credit usage (in USD) for the current UTC day" }) - .check(Schema.isFinite()), - "usage_weekly": Schema.Number.annotate({ - "description": "OpenRouter credit usage (in USD) for the current UTC week (Monday-Sunday)" - }).check(Schema.isFinite()), - "usage_monthly": Schema.Number.annotate({ - "description": "OpenRouter credit usage (in USD) for the current UTC month" - }).check(Schema.isFinite()), - "byok_usage": Schema.Number.annotate({ "description": "Total external BYOK usage (in USD) for the API key" }).check( - Schema.isFinite() - ), - "byok_usage_daily": Schema.Number.annotate({ - "description": "External BYOK usage (in USD) for the current UTC day" - }).check(Schema.isFinite()), - "byok_usage_weekly": Schema.Number.annotate({ - "description": "External BYOK usage (in USD) for the current UTC week (Monday-Sunday)" - }).check(Schema.isFinite()), - "byok_usage_monthly": Schema.Number.annotate({ - "description": "External BYOK usage (in USD) for current UTC month" - }).check(Schema.isFinite()), - "created_at": Schema.String.annotate({ "description": "ISO 8601 timestamp of when the API key was created" }), - "updated_at": Schema.String.annotate({ "description": "ISO 8601 timestamp of when the API key was last updated" }), - "expires_at": Schema.optionalKey( - Schema.String.annotate({ - "description": "ISO 8601 UTC timestamp when the API key expires, or null if no expiration", - "format": "date-time" - }) - ) - }).annotate({ "description": "The updated API key information" }) +export const ChatSearchModelsServerTool = Schema.Struct({ + "parameters": Schema.optionalKey(SearchModelsServerToolConfig), + "type": Schema.Literal("openrouter:experimental__search_models") +}).annotate({ + "description": "OpenRouter built-in server tool: searches and filters AI models available on OpenRouter", + "identifier": "ChatSearchModelsServerTool" }) -export type UpdateKeys400 = BadRequestResponse -export const UpdateKeys400 = BadRequestResponse -export type UpdateKeys401 = UnauthorizedResponse -export const UpdateKeys401 = UnauthorizedResponse -export type UpdateKeys404 = NotFoundResponse -export const UpdateKeys404 = NotFoundResponse -export type UpdateKeys429 = TooManyRequestsResponse -export const UpdateKeys429 = TooManyRequestsResponse -export type UpdateKeys500 = InternalServerResponse -export const UpdateKeys500 = InternalServerResponse -export type ListGuardrailsParams = { readonly "offset"?: string; readonly "limit"?: string } -export const ListGuardrailsParams = Schema.Struct({ - "offset": Schema.optionalKey(Schema.String.annotate({ "description": "Number of records to skip for pagination" })), - "limit": Schema.optionalKey( - Schema.String.annotate({ "description": "Maximum number of records to return (max 100)" }) - ) +export type MessagesSearchModelsServerTool = { + readonly "parameters"?: SearchModelsServerToolConfig + readonly "type": "openrouter:experimental__search_models" +} +export const MessagesSearchModelsServerTool = Schema.Struct({ + "parameters": Schema.optionalKey(SearchModelsServerToolConfig), + "type": Schema.Literal("openrouter:experimental__search_models") +}).annotate({ + "description": "OpenRouter built-in server tool: searches and filters AI models available on OpenRouter", + "identifier": "MessagesSearchModelsServerTool" }) -export type ListGuardrails200 = { - readonly "data": ReadonlyArray< - { - readonly "id": string - readonly "name": string - readonly "description"?: string - readonly "limit_usd"?: number - readonly "reset_interval"?: "daily" | "weekly" | "monthly" - readonly "allowed_providers"?: ReadonlyArray - readonly "allowed_models"?: ReadonlyArray - readonly "enforce_zdr"?: boolean - readonly "created_at": string - readonly "updated_at"?: string - } - > - readonly "total_count": number +export type SearchModelsServerTool_OpenRouter = { + readonly "parameters"?: SearchModelsServerToolConfig + readonly "type": "openrouter:experimental__search_models" } -export const ListGuardrails200 = Schema.Struct({ - "data": Schema.Array(Schema.Struct({ - "id": Schema.String.annotate({ "description": "Unique identifier for the guardrail", "format": "uuid" }), - "name": Schema.String.annotate({ "description": "Name of the guardrail" }), - "description": Schema.optionalKey(Schema.String.annotate({ "description": "Description of the guardrail" })), - "limit_usd": Schema.optionalKey( - Schema.Number.annotate({ "description": "Spending limit in USD" }).check(Schema.isFinite()).check( - Schema.isGreaterThanOrEqualTo(0) - ) - ), - "reset_interval": Schema.optionalKey( - Schema.Literals(["daily", "weekly", "monthly"]).annotate({ - "description": "Interval at which the limit resets (daily, weekly, monthly)" - }) - ), - "allowed_providers": Schema.optionalKey( - Schema.Array(Schema.String).annotate({ "description": "List of allowed provider IDs" }) - ), - "allowed_models": Schema.optionalKey( - Schema.Array(Schema.String).annotate({ "description": "Array of model canonical_slugs (immutable identifiers)" }) - ), - "enforce_zdr": Schema.optionalKey( - Schema.Boolean.annotate({ "description": "Whether to enforce zero data retention" }) - ), - "created_at": Schema.String.annotate({ "description": "ISO 8601 timestamp of when the guardrail was created" }), - "updated_at": Schema.optionalKey( - Schema.String.annotate({ "description": "ISO 8601 timestamp of when the guardrail was last updated" }) - ) - })).annotate({ "description": "List of guardrails" }), - "total_count": Schema.Number.annotate({ "description": "Total number of guardrails" }).check(Schema.isFinite()) +export const SearchModelsServerTool_OpenRouter = Schema.Struct({ + "parameters": Schema.optionalKey(SearchModelsServerToolConfig), + "type": Schema.Literal("openrouter:experimental__search_models") +}).annotate({ + "description": "OpenRouter built-in server tool: searches and filters AI models available on OpenRouter", + "identifier": "SearchModelsServerTool_OpenRouter" }) -export type ListGuardrails401 = UnauthorizedResponse -export const ListGuardrails401 = UnauthorizedResponse -export type ListGuardrails500 = InternalServerResponse -export const ListGuardrails500 = InternalServerResponse -export type CreateGuardrailRequestJson = { - readonly "name": string - readonly "description"?: string - readonly "limit_usd"?: number - readonly "reset_interval"?: "daily" | "weekly" | "monthly" - readonly "allowed_providers"?: ReadonlyArray - readonly "allowed_models"?: ReadonlyArray - readonly "enforce_zdr"?: boolean +export type ServerToolUseDetails = Objects_150 | null +export const ServerToolUseDetails = Schema.Union([Objects_150, Schema.Null]).annotate({ + "description": "Usage for server-side tool execution (e.g., web search)", + "identifier": "ServerToolUseDetails" +}) +export type ServiceUnavailableResponse = { + readonly "error": ServiceUnavailableResponseErrorData + readonly "openrouter_metadata"?: { readonly [x: string]: Schema.Json } | null + readonly "user_id"?: string | null } -export const CreateGuardrailRequestJson = Schema.Struct({ - "name": Schema.String.annotate({ "description": "Name for the new guardrail" }).check(Schema.isMinLength(1)).check( - Schema.isMaxLength(200) - ), - "description": Schema.optionalKey( - Schema.String.annotate({ "description": "Description of the guardrail" }).check(Schema.isMaxLength(1000)) - ), - "limit_usd": Schema.optionalKey( - Schema.Number.annotate({ "description": "Spending limit in USD" }).check(Schema.isFinite()).check( - Schema.isGreaterThanOrEqualTo(0) - ) - ), - "reset_interval": Schema.optionalKey( - Schema.Literals(["daily", "weekly", "monthly"]).annotate({ - "description": "Interval at which the limit resets (daily, weekly, monthly)" - }) - ), - "allowed_providers": Schema.optionalKey( - Schema.Array(Schema.String).annotate({ "description": "List of allowed provider IDs" }).check(Schema.isMinLength(1)) - ), - "allowed_models": Schema.optionalKey( - Schema.Array(Schema.String).annotate({ - "description": "Array of model identifiers (slug or canonical_slug accepted)" - }).check(Schema.isMinLength(1)) +export const ServiceUnavailableResponse = Schema.Struct({ + "error": ServiceUnavailableResponseErrorData, + "openrouter_metadata": Schema.optionalKey( + Schema.Union([Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })), Schema.Null]) ), - "enforce_zdr": Schema.optionalKey( - Schema.Boolean.annotate({ "description": "Whether to enforce zero data retention" }) - ) + "user_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) +}).annotate({ + "description": "Service Unavailable - Service temporarily unavailable", + "identifier": "ServiceUnavailableResponse" }) -export type CreateGuardrail201 = { - readonly "data": { - readonly "id": string - readonly "name": string - readonly "description"?: string - readonly "limit_usd"?: number - readonly "reset_interval"?: "daily" | "weekly" | "monthly" - readonly "allowed_providers"?: ReadonlyArray - readonly "allowed_models"?: ReadonlyArray - readonly "enforce_zdr"?: boolean - readonly "created_at": string - readonly "updated_at"?: string +export type OutputShellCallItem = { + readonly "action"?: { + readonly "commands": ReadonlyArray + readonly "max_output_length": number | null + readonly "timeout_ms": number | null } + readonly "call_id": string + readonly "id": string + readonly "status": ShellCallStatus + readonly "type": "shell_call" } -export const CreateGuardrail201 = Schema.Struct({ - "data": Schema.Struct({ - "id": Schema.String.annotate({ "description": "Unique identifier for the guardrail", "format": "uuid" }), - "name": Schema.String.annotate({ "description": "Name of the guardrail" }), - "description": Schema.optionalKey(Schema.String.annotate({ "description": "Description of the guardrail" })), - "limit_usd": Schema.optionalKey( - Schema.Number.annotate({ "description": "Spending limit in USD" }).check(Schema.isFinite()).check( - Schema.isGreaterThanOrEqualTo(0) - ) - ), - "reset_interval": Schema.optionalKey( - Schema.Literals(["daily", "weekly", "monthly"]).annotate({ - "description": "Interval at which the limit resets (daily, weekly, monthly)" - }) - ), - "allowed_providers": Schema.optionalKey( - Schema.Array(Schema.String).annotate({ "description": "List of allowed provider IDs" }) - ), - "allowed_models": Schema.optionalKey( - Schema.Array(Schema.String).annotate({ "description": "Array of model canonical_slugs (immutable identifiers)" }) - ), - "enforce_zdr": Schema.optionalKey( - Schema.Boolean.annotate({ "description": "Whether to enforce zero data retention" }) - ), - "created_at": Schema.String.annotate({ "description": "ISO 8601 timestamp of when the guardrail was created" }), - "updated_at": Schema.optionalKey( - Schema.String.annotate({ "description": "ISO 8601 timestamp of when the guardrail was last updated" }) - ) - }).annotate({ "description": "The created guardrail" }) +export const OutputShellCallItem = Schema.Struct({ + "action": Schema.optionalKey(Schema.Struct({ + "commands": Schema.Array(Schema.String), + "max_output_length": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]), + "timeout_ms": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]) + })), + "call_id": Schema.String, + "id": Schema.String, + "status": ShellCallStatus, + "type": Schema.Literal("shell_call") +}).annotate({ + "description": + "A native `shell_call` output item matching OpenAI's Responses API shape. Emitted for the sandbox-backed `shell` tool.", + "identifier": "OutputShellCallItem" }) -export type CreateGuardrail400 = BadRequestResponse -export const CreateGuardrail400 = BadRequestResponse -export type CreateGuardrail401 = UnauthorizedResponse -export const CreateGuardrail401 = UnauthorizedResponse -export type CreateGuardrail500 = InternalServerResponse -export const CreateGuardrail500 = InternalServerResponse -export type GetGuardrail200 = { - readonly "data": { - readonly "id": string - readonly "name": string - readonly "description"?: string - readonly "limit_usd"?: number - readonly "reset_interval"?: "daily" | "weekly" | "monthly" - readonly "allowed_providers"?: ReadonlyArray - readonly "allowed_models"?: ReadonlyArray - readonly "enforce_zdr"?: boolean - readonly "created_at": string - readonly "updated_at"?: string - } +export type OutputShellCallOutputItem = { + readonly "call_id": string + readonly "id": string + readonly "max_output_length"?: number | null + readonly "output": ReadonlyArray< + { + readonly "outcome": { readonly "exit_code": number; readonly "type": "exit" } | { readonly "type": "timeout" } + readonly "stderr": string + readonly "stdout": string + } + > + readonly "status": ShellCallStatus + readonly "type": "shell_call_output" } -export const GetGuardrail200 = Schema.Struct({ - "data": Schema.Struct({ - "id": Schema.String.annotate({ "description": "Unique identifier for the guardrail", "format": "uuid" }), - "name": Schema.String.annotate({ "description": "Name of the guardrail" }), - "description": Schema.optionalKey(Schema.String.annotate({ "description": "Description of the guardrail" })), - "limit_usd": Schema.optionalKey( - Schema.Number.annotate({ "description": "Spending limit in USD" }).check(Schema.isFinite()).check( - Schema.isGreaterThanOrEqualTo(0) - ) - ), - "reset_interval": Schema.optionalKey( - Schema.Literals(["daily", "weekly", "monthly"]).annotate({ - "description": "Interval at which the limit resets (daily, weekly, monthly)" - }) - ), - "allowed_providers": Schema.optionalKey( - Schema.Array(Schema.String).annotate({ "description": "List of allowed provider IDs" }) - ), - "allowed_models": Schema.optionalKey( - Schema.Array(Schema.String).annotate({ "description": "Array of model canonical_slugs (immutable identifiers)" }) - ), - "enforce_zdr": Schema.optionalKey( - Schema.Boolean.annotate({ "description": "Whether to enforce zero data retention" }) - ), - "created_at": Schema.String.annotate({ "description": "ISO 8601 timestamp of when the guardrail was created" }), - "updated_at": Schema.optionalKey( - Schema.String.annotate({ "description": "ISO 8601 timestamp of when the guardrail was last updated" }) - ) - }).annotate({ "description": "The guardrail" }) +export const OutputShellCallOutputItem = Schema.Struct({ + "call_id": Schema.String, + "id": Schema.String, + "max_output_length": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]) + ), + "output": Schema.Array(Schema.Struct({ + "outcome": Schema.Union([ + Schema.Struct({ + "exit_code": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("exit") + }), + Schema.Struct({ "type": Schema.Literal("timeout") }) + ], { mode: "oneOf" }), + "stderr": Schema.String, + "stdout": Schema.String + })), + "status": ShellCallStatus, + "type": Schema.Literal("shell_call_output") +}).annotate({ + "description": + "A native `shell_call_output` item matching OpenAI's Responses API shape. Carries per-command stdout, stderr, and the exit/timeout outcome.", + "identifier": "OutputShellCallOutputItem" }) -export type GetGuardrail401 = UnauthorizedResponse -export const GetGuardrail401 = UnauthorizedResponse -export type GetGuardrail404 = NotFoundResponse -export const GetGuardrail404 = NotFoundResponse -export type GetGuardrail500 = InternalServerResponse -export const GetGuardrail500 = InternalServerResponse -export type DeleteGuardrail200 = { readonly "deleted": true } -export const DeleteGuardrail200 = Schema.Struct({ - "deleted": Schema.Literal(true).annotate({ "description": "Confirmation that the guardrail was deleted" }) +export type StopServerToolsWhenCondition = + | StopServerToolsWhenStepCountIs + | StopServerToolsWhenHasToolCall + | StopServerToolsWhenMaxTokensUsed + | StopServerToolsWhenMaxCost + | StopServerToolsWhenFinishReasonIs +export const StopServerToolsWhenCondition = Schema.Union([ + StopServerToolsWhenStepCountIs, + StopServerToolsWhenHasToolCall, + StopServerToolsWhenMaxTokensUsed, + StopServerToolsWhenMaxCost, + StopServerToolsWhenFinishReasonIs +], { mode: "oneOf" }).annotate({ + "description": "A single condition that, when met, halts the server-tool agent loop.", + "identifier": "StopServerToolsWhenCondition" }) -export type DeleteGuardrail401 = UnauthorizedResponse -export const DeleteGuardrail401 = UnauthorizedResponse -export type DeleteGuardrail404 = NotFoundResponse -export const DeleteGuardrail404 = NotFoundResponse -export type DeleteGuardrail500 = InternalServerResponse -export const DeleteGuardrail500 = InternalServerResponse -export type UpdateGuardrailRequestJson = { - readonly "name"?: string - readonly "description"?: string - readonly "limit_usd"?: number - readonly "reset_interval"?: "daily" | "weekly" | "monthly" - readonly "allowed_providers"?: ReadonlyArray - readonly "allowed_models"?: ReadonlyArray - readonly "enforce_zdr"?: boolean +export type STTResponse = { + readonly "duration"?: number + readonly "language"?: string + readonly "segments"?: ReadonlyArray + readonly "task"?: string + readonly "text": string + readonly "usage"?: STTUsage + readonly "words"?: ReadonlyArray } -export const UpdateGuardrailRequestJson = Schema.Struct({ - "name": Schema.optionalKey( - Schema.String.annotate({ "description": "New name for the guardrail" }).check(Schema.isMinLength(1)).check( - Schema.isMaxLength(200) - ) - ), - "description": Schema.optionalKey( - Schema.String.annotate({ "description": "New description for the guardrail" }).check(Schema.isMaxLength(1000)) +export const STTResponse = Schema.Struct({ + "duration": Schema.optionalKey( + Schema.Number.annotate({ + "description": "Duration of the input audio in seconds, present when response_format is verbose_json", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })) ), - "limit_usd": Schema.optionalKey( - Schema.Number.annotate({ "description": "New spending limit in USD" }).check(Schema.isFinite()).check( - Schema.isGreaterThanOrEqualTo(0) - ) - ), - "reset_interval": Schema.optionalKey( - Schema.Literals(["daily", "weekly", "monthly"]).annotate({ - "description": "Interval at which the limit resets (daily, weekly, monthly)" + "language": Schema.optionalKey( + Schema.String.annotate({ + "description": "Detected or forced language, present when response_format is verbose_json" }) ), - "allowed_providers": Schema.optionalKey( - Schema.Array(Schema.String).annotate({ "description": "New list of allowed provider IDs" }).check( - Schema.isMinLength(1) - ) + "segments": Schema.optionalKey( + Schema.Array(STTSegment).annotate({ + "description": "Timestamped transcript segments, present when response_format is verbose_json" + }) ), - "allowed_models": Schema.optionalKey( - Schema.Array(Schema.String).annotate({ - "description": "Array of model identifiers (slug or canonical_slug accepted)" - }).check(Schema.isMinLength(1)) + "task": Schema.optionalKey( + Schema.String.annotate({ "description": "The task performed, present when response_format is verbose_json" }) ), - "enforce_zdr": Schema.optionalKey( - Schema.Boolean.annotate({ "description": "Whether to enforce zero data retention" }) + "text": Schema.String.annotate({ "description": "The transcribed text" }), + "usage": Schema.optionalKey(STTUsage), + "words": Schema.optionalKey( + Schema.Array(STTWord).annotate({ + "description": "Timestamped words, present when the provider returns word-level timestamps" + }) ) +}).annotate({ + "description": "STT response containing transcribed text and optional usage statistics", + "identifier": "STTResponse" }) -export type UpdateGuardrail200 = { - readonly "data": { - readonly "id": string - readonly "name": string - readonly "description"?: string - readonly "limit_usd"?: number - readonly "reset_interval"?: "daily" | "weekly" | "monthly" - readonly "allowed_providers"?: ReadonlyArray - readonly "allowed_models"?: ReadonlyArray - readonly "enforce_zdr"?: boolean - readonly "created_at": string - readonly "updated_at"?: string - } +export type Arrays_12 = ReadonlyArray +export const Arrays_12 = Schema.Array(SubagentNestedTool).annotate({ + "description": + "Tools the subagent may use while executing a delegated task. The subagent runs as an agentic sub-agent over these tools, then returns its outcome. Only OpenRouter server tools are supported — function tools are rejected — and the list must not include the subagent tool itself." +}) +export type ImageEndpoint = { + readonly "allowed_passthrough_parameters": ReadonlyArray + readonly "pricing": ReadonlyArray + readonly "provider_name": string + readonly "provider_slug": string + readonly "provider_tag": string | null + readonly "supported_parameters": SupportedParameters + readonly "supports_streaming": boolean } -export const UpdateGuardrail200 = Schema.Struct({ - "data": Schema.Struct({ - "id": Schema.String.annotate({ "description": "Unique identifier for the guardrail", "format": "uuid" }), - "name": Schema.String.annotate({ "description": "Name of the guardrail" }), - "description": Schema.optionalKey(Schema.String.annotate({ "description": "Description of the guardrail" })), - "limit_usd": Schema.optionalKey( - Schema.Number.annotate({ "description": "Spending limit in USD" }).check(Schema.isFinite()).check( - Schema.isGreaterThanOrEqualTo(0) - ) - ), - "reset_interval": Schema.optionalKey( - Schema.Literals(["daily", "weekly", "monthly"]).annotate({ - "description": "Interval at which the limit resets (daily, weekly, monthly)" - }) - ), - "allowed_providers": Schema.optionalKey( - Schema.Array(Schema.String).annotate({ "description": "List of allowed provider IDs" }) - ), - "allowed_models": Schema.optionalKey( - Schema.Array(Schema.String).annotate({ "description": "Array of model canonical_slugs (immutable identifiers)" }) +export const ImageEndpoint = Schema.Struct({ + "allowed_passthrough_parameters": Schema.Array(Schema.String).annotate({ + "description": "Provider-specific options accepted under provider.options[provider_slug]." + }), + "pricing": Schema.Array(ImagePricingEntry).annotate({ "description": "Billable pricing lines for this endpoint." }), + "provider_name": Schema.String.annotate({ "description": "Provider display name" }), + "provider_slug": Schema.String.annotate({ "description": "Provider slug" }), + "provider_tag": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "Provider tag for request-side selection" + }), + "supported_parameters": Schema.suspend((): Schema.Codec => SupportedParameters).annotate({ + "description": "The definitive set of parameters this endpoint accepts for this model." + }), + "supports_streaming": Schema.Boolean.annotate({ + "description": "Whether this endpoint supports native SSE streaming (`stream: true` in the request)." + }) +}).annotate({ "description": "An endpoint that serves a given image model.", "identifier": "ImageEndpoint" }) +export type TaskClassificationItem = { + readonly "category_token_share": number + readonly "category_usage_share": number + readonly "display_name": string + readonly "macro_category": string + readonly "models": ReadonlyArray + readonly "tag": string + readonly "token_share": number + readonly "usage_share": number +} +export const TaskClassificationItem = Schema.Struct({ + "category_token_share": Schema.Number.annotate({ + "description": + "Fraction of this classification's token volume within its macro-category (0–1). Sums to 1 across all classifications sharing the same `macro_category`.", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "category_usage_share": Schema.Number.annotate({ + "description": + "Fraction of this classification's usage within its macro-category (0–1). Sums to 1 across all classifications sharing the same `macro_category`.", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "display_name": Schema.String.annotate({ "description": "Human-readable label for the classification." }), + "macro_category": Schema.String.annotate({ + "description": "Coarse grouping derived from the tag prefix: `code`, `data`, `agent`, or `general`." + }), + "models": Schema.Array(TaskClassificationModel).annotate({ + "description": + "Top models for this classification by request volume, sorted descending. Each entry reports the model's share of this classification's requests and tokens." + }), + "tag": Schema.String.annotate({ + "description": "Classification tag identifier (e.g. `code:general_impl`, `agent:web_search`)." + }), + "token_share": Schema.Number.annotate({ + "description": + "Fraction of classified sampled token volume (prompt + completion) attributed to this classification (0–1). The unclassified `other` bucket is excluded from the denominator.", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "usage_share": Schema.Number.annotate({ + "description": + "Fraction of classified sampled requests attributed to this classification (0–1). The unclassified `other` bucket is excluded from the denominator.", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })) +}).annotate({ "identifier": "TaskClassificationItem" }) +export type CodeInterpreterCallItem = { + readonly "code": string | null + readonly "container_id": string + readonly "id": string + readonly "outputs": + | ReadonlyArray< + { readonly "type": "image"; readonly "url": string } | { readonly "logs": string; readonly "type": "logs" } + > + | null + readonly "status": ToolCallStatus + readonly "type": "code_interpreter_call" +} +export const CodeInterpreterCallItem = Schema.Struct({ + "code": Schema.Union([Schema.String, Schema.Null]), + "container_id": Schema.String, + "id": Schema.String, + "outputs": Schema.Union([ + Schema.Array( + Schema.Union([ + Schema.Struct({ "type": Schema.Literal("image"), "url": Schema.String }), + Schema.Struct({ "logs": Schema.String, "type": Schema.Literal("logs") }) + ]) ), - "enforce_zdr": Schema.optionalKey( - Schema.Boolean.annotate({ "description": "Whether to enforce zero data retention" }) + Schema.Null + ]), + "status": ToolCallStatus, + "type": Schema.Literal("code_interpreter_call") +}).annotate({ + "description": "A code interpreter execution call with outputs", + "identifier": "CodeInterpreterCallItem" +}) +export type FunctionCallItem = { + readonly "arguments": string + readonly "call_id": string + readonly "id": string + readonly "name": string + readonly "namespace"?: string + readonly "status"?: ToolCallStatus + readonly "type": "function_call" +} +export const FunctionCallItem = Schema.Struct({ + "arguments": Schema.String, + "call_id": Schema.String, + "id": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ), + "status": Schema.optionalKey(ToolCallStatus), + "type": Schema.Literal("function_call") +}).annotate({ "description": "A function call initiated by the model", "identifier": "FunctionCallItem" }) +export type LocalShellCallItem = { + readonly "action": { + readonly "command": ReadonlyArray + readonly "env": {} + readonly "timeout_ms"?: number | null + readonly "type": "exec" + readonly "user"?: string | null + readonly "working_directory"?: string | null + } + readonly "call_id": string + readonly "id": string + readonly "status": ToolCallStatus + readonly "type": "local_shell_call" +} +export const LocalShellCallItem = Schema.Struct({ + "action": Schema.Struct({ + "command": Schema.Array(Schema.String), + "env": Schema.Struct({}), + "timeout_ms": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]) ), - "created_at": Schema.String.annotate({ "description": "ISO 8601 timestamp of when the guardrail was created" }), - "updated_at": Schema.optionalKey( - Schema.String.annotate({ "description": "ISO 8601 timestamp of when the guardrail was last updated" }) - ) - }).annotate({ "description": "The updated guardrail" }) + "type": Schema.Literal("exec"), + "user": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "working_directory": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }), + "call_id": Schema.String, + "id": Schema.String, + "status": ToolCallStatus, + "type": Schema.Literal("local_shell_call") +}).annotate({ "description": "A local shell command execution call", "identifier": "LocalShellCallItem" }) +export type LocalShellCallOutputItem = { + readonly "id": string + readonly "output": string + readonly "status"?: ToolCallStatus | null + readonly "type": "local_shell_call_output" +} +export const LocalShellCallOutputItem = Schema.Struct({ + "id": Schema.String, + "output": Schema.String, + "status": Schema.optionalKey(Schema.Union([ToolCallStatus, Schema.Null])), + "type": Schema.Literal("local_shell_call_output") +}).annotate({ "description": "Output from a local shell command execution", "identifier": "LocalShellCallOutputItem" }) +export type OpenAIResponseFunctionToolCall = { + readonly "arguments": string + readonly "call_id": string + readonly "id"?: string + readonly "name": string + readonly "namespace"?: string + readonly "status"?: ToolCallStatus + readonly "type": "function_call" +} +export const OpenAIResponseFunctionToolCall = Schema.Struct({ + "arguments": Schema.String, + "call_id": Schema.String, + "id": Schema.optionalKey(Schema.String), + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ), + "status": Schema.optionalKey(ToolCallStatus), + "type": Schema.Literal("function_call") +}).annotate({ "identifier": "OpenAIResponseFunctionToolCall" }) +export type OutputAdvisorServerToolItem = { + readonly "advice"?: string + readonly "error"?: string + readonly "id"?: string + readonly "instance_name"?: string + readonly "model"?: string + readonly "prompt"?: string + readonly "status": ToolCallStatus + readonly "type": "openrouter:advisor" +} +export const OutputAdvisorServerToolItem = Schema.Struct({ + "advice": Schema.optionalKey( + Schema.String.annotate({ + "description": "The advisor model's response (the advice text returned to the executor)." + }) + ), + "error": Schema.optionalKey( + Schema.String.annotate({ "description": "Error message when the advisor call did not produce advice." }) + ), + "id": Schema.optionalKey(Schema.String), + "instance_name": Schema.optionalKey(Schema.String.annotate({ + "description": + "Provider-safe function name of the specific advisor instance that produced this item (e.g. `openrouter_advisor__1`). Present only when more than one advisor tool is configured; omitted for the default single advisor. Echo this field back unchanged so the advisor's cross-request memory stays namespaced to the correct instance. This identity is positional: it is derived from the index of the advisor entry in the request `tools` array, so clients must keep the order of advisor tool entries stable across requests in a conversation. Reordering or inserting advisor entries shifts these names and causes each advisor's cross-request memory to be attributed to the wrong instance." + })), + "model": Schema.optionalKey( + Schema.String.annotate({ "description": "Slug of the advisor model that was consulted." }) + ), + "prompt": Schema.optionalKey( + Schema.String.annotate({ "description": "The prompt the executor sent to the advisor." }) + ), + "status": ToolCallStatus, + "type": Schema.Literal("openrouter:advisor") +}).annotate({ + "description": "An openrouter:advisor server tool output item", + "identifier": "OutputAdvisorServerToolItem" }) -export type UpdateGuardrail400 = BadRequestResponse -export const UpdateGuardrail400 = BadRequestResponse -export type UpdateGuardrail401 = UnauthorizedResponse -export const UpdateGuardrail401 = UnauthorizedResponse -export type UpdateGuardrail404 = NotFoundResponse -export const UpdateGuardrail404 = NotFoundResponse -export type UpdateGuardrail500 = InternalServerResponse -export const UpdateGuardrail500 = InternalServerResponse -export type ListKeyAssignmentsParams = { readonly "offset"?: string; readonly "limit"?: string } -export const ListKeyAssignmentsParams = Schema.Struct({ - "offset": Schema.optionalKey(Schema.String.annotate({ "description": "Number of records to skip for pagination" })), - "limit": Schema.optionalKey( - Schema.String.annotate({ "description": "Maximum number of records to return (max 100)" }) - ) +export type OutputBashServerToolItem = { + readonly "arguments"?: string + readonly "call_id"?: string + readonly "command"?: string + readonly "exitCode"?: number + readonly "id"?: string + readonly "status": ToolCallStatus + readonly "stderr"?: string + readonly "stdout"?: string + readonly "type": "openrouter:bash" +} +export const OutputBashServerToolItem = Schema.Struct({ + "arguments": Schema.optionalKey( + Schema.String.annotate({ "description": "The raw tool-call arguments string as emitted by the model." }) + ), + "call_id": Schema.optionalKey( + Schema.String.annotate({ "description": "The model-generated tool call id from the originating turn." }) + ), + "command": Schema.optionalKey(Schema.String), + "exitCode": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "id": Schema.optionalKey(Schema.String), + "status": ToolCallStatus, + "stderr": Schema.optionalKey(Schema.String), + "stdout": Schema.optionalKey(Schema.String), + "type": Schema.Literal("openrouter:bash") +}).annotate({ "description": "An openrouter:bash server tool output item", "identifier": "OutputBashServerToolItem" }) +export type OutputBrowserUseServerToolItem = { + readonly "action"?: string + readonly "id"?: string + readonly "screenshotB64"?: string + readonly "status": ToolCallStatus + readonly "type": "openrouter:browser_use" +} +export const OutputBrowserUseServerToolItem = Schema.Struct({ + "action": Schema.optionalKey(Schema.String), + "id": Schema.optionalKey(Schema.String), + "screenshotB64": Schema.optionalKey(Schema.String), + "status": ToolCallStatus, + "type": Schema.Literal("openrouter:browser_use") +}).annotate({ + "description": "An openrouter:browser_use server tool output item", + "identifier": "OutputBrowserUseServerToolItem" }) -export type ListKeyAssignments200 = { - readonly "data": ReadonlyArray< - { - readonly "id": string - readonly "key_hash": string - readonly "guardrail_id": string - readonly "key_name": string - readonly "key_label": string - readonly "assigned_by": string - readonly "created_at": string - } - > - readonly "total_count": number +export type OutputCodeInterpreterCallItem = { + readonly "code": string | null + readonly "container_id": string + readonly "id": string + readonly "outputs": + | ReadonlyArray< + { readonly "type": "image"; readonly "url": string } | { readonly "logs": string; readonly "type": "logs" } + > + | null + readonly "status": ToolCallStatus + readonly "type": "code_interpreter_call" } -export const ListKeyAssignments200 = Schema.Struct({ - "data": Schema.Array(Schema.Struct({ - "id": Schema.String.annotate({ "description": "Unique identifier for the assignment", "format": "uuid" }), - "key_hash": Schema.String.annotate({ "description": "Hash of the assigned API key" }), - "guardrail_id": Schema.String.annotate({ "description": "ID of the guardrail", "format": "uuid" }), - "key_name": Schema.String.annotate({ "description": "Name of the API key" }), - "key_label": Schema.String.annotate({ "description": "Label of the API key" }), - "assigned_by": Schema.String.annotate({ "description": "User ID of who made the assignment" }), - "created_at": Schema.String.annotate({ "description": "ISO 8601 timestamp of when the assignment was created" }) - })).annotate({ "description": "List of key assignments" }), - "total_count": Schema.Number.annotate({ "description": "Total number of key assignments for this guardrail" }).check( - Schema.isFinite() - ) +export const OutputCodeInterpreterCallItem = Schema.Struct({ + "code": Schema.Union([Schema.String, Schema.Null]), + "container_id": Schema.String, + "id": Schema.String, + "outputs": Schema.Union([ + Schema.Array( + Schema.Union([ + Schema.Struct({ "type": Schema.Literal("image"), "url": Schema.String }), + Schema.Struct({ "logs": Schema.String, "type": Schema.Literal("logs") }) + ]) + ), + Schema.Null + ]), + "status": ToolCallStatus, + "type": Schema.Literal("code_interpreter_call") +}).annotate({ + "description": "A code interpreter execution call with outputs", + "identifier": "OutputCodeInterpreterCallItem" }) -export type ListKeyAssignments401 = UnauthorizedResponse -export const ListKeyAssignments401 = UnauthorizedResponse -export type ListKeyAssignments500 = InternalServerResponse -export const ListKeyAssignments500 = InternalServerResponse -export type ListMemberAssignmentsParams = { readonly "offset"?: string; readonly "limit"?: string } -export const ListMemberAssignmentsParams = Schema.Struct({ - "offset": Schema.optionalKey(Schema.String.annotate({ "description": "Number of records to skip for pagination" })), - "limit": Schema.optionalKey( - Schema.String.annotate({ "description": "Maximum number of records to return (max 100)" }) - ) +export type OutputCodeInterpreterServerToolItem = { + readonly "code"?: string + readonly "exitCode"?: number + readonly "id"?: string + readonly "language"?: string + readonly "status": ToolCallStatus + readonly "stderr"?: string + readonly "stdout"?: string + readonly "type": "openrouter:code_interpreter" +} +export const OutputCodeInterpreterServerToolItem = Schema.Struct({ + "code": Schema.optionalKey(Schema.String), + "exitCode": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "id": Schema.optionalKey(Schema.String), + "language": Schema.optionalKey(Schema.String), + "status": ToolCallStatus, + "stderr": Schema.optionalKey(Schema.String), + "stdout": Schema.optionalKey(Schema.String), + "type": Schema.Literal("openrouter:code_interpreter") +}).annotate({ + "description": "An openrouter:code_interpreter server tool output item", + "identifier": "OutputCodeInterpreterServerToolItem" }) -export type ListMemberAssignments200 = { - readonly "data": ReadonlyArray< - { - readonly "id": string - readonly "user_id": string - readonly "organization_id": string - readonly "guardrail_id": string - readonly "assigned_by": string - readonly "created_at": string - } - > - readonly "total_count": number +export type OutputDatetimeItem = { + readonly "datetime": string + readonly "id"?: string + readonly "status": ToolCallStatus + readonly "timezone": string + readonly "type": "openrouter:datetime" } -export const ListMemberAssignments200 = Schema.Struct({ - "data": Schema.Array(Schema.Struct({ - "id": Schema.String.annotate({ "description": "Unique identifier for the assignment", "format": "uuid" }), - "user_id": Schema.String.annotate({ "description": "Clerk user ID of the assigned member" }), - "organization_id": Schema.String.annotate({ "description": "Organization ID" }), - "guardrail_id": Schema.String.annotate({ "description": "ID of the guardrail", "format": "uuid" }), - "assigned_by": Schema.String.annotate({ "description": "User ID of who made the assignment" }), - "created_at": Schema.String.annotate({ "description": "ISO 8601 timestamp of when the assignment was created" }) - })).annotate({ "description": "List of member assignments" }), - "total_count": Schema.Number.annotate({ "description": "Total number of member assignments" }).check( - Schema.isFinite() - ) +export const OutputDatetimeItem = Schema.Struct({ + "datetime": Schema.String.annotate({ "description": "ISO 8601 datetime string" }), + "id": Schema.optionalKey(Schema.String), + "status": ToolCallStatus, + "timezone": Schema.String.annotate({ "description": "IANA timezone name" }), + "type": Schema.Literal("openrouter:datetime") +}).annotate({ "description": "An openrouter:datetime server tool output item", "identifier": "OutputDatetimeItem" }) +export type OutputFileSearchServerToolItem = { + readonly "id"?: string + readonly "queries"?: ReadonlyArray + readonly "status": ToolCallStatus + readonly "type": "openrouter:file_search" +} +export const OutputFileSearchServerToolItem = Schema.Struct({ + "id": Schema.optionalKey(Schema.String), + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "status": ToolCallStatus, + "type": Schema.Literal("openrouter:file_search") +}).annotate({ + "description": "An openrouter:file_search server tool output item", + "identifier": "OutputFileSearchServerToolItem" }) -export type ListMemberAssignments401 = UnauthorizedResponse -export const ListMemberAssignments401 = UnauthorizedResponse -export type ListMemberAssignments500 = InternalServerResponse -export const ListMemberAssignments500 = InternalServerResponse -export type ListGuardrailKeyAssignmentsParams = { readonly "offset"?: string; readonly "limit"?: string } -export const ListGuardrailKeyAssignmentsParams = Schema.Struct({ - "offset": Schema.optionalKey(Schema.String.annotate({ "description": "Number of records to skip for pagination" })), - "limit": Schema.optionalKey( - Schema.String.annotate({ "description": "Maximum number of records to return (max 100)" }) - ) -}) -export type ListGuardrailKeyAssignments200 = { - readonly "data": ReadonlyArray< - { - readonly "id": string - readonly "key_hash": string - readonly "guardrail_id": string - readonly "key_name": string - readonly "key_label": string - readonly "assigned_by": string - readonly "created_at": string - } - > - readonly "total_count": number +export type OutputFilesServerToolItem = { + readonly "error"?: string + readonly "file_id"?: string + readonly "filename"?: string + readonly "id"?: string + readonly "operation"?: string + readonly "result"?: string + readonly "status": ToolCallStatus + readonly "type": "openrouter:files" } -export const ListGuardrailKeyAssignments200 = Schema.Struct({ - "data": Schema.Array(Schema.Struct({ - "id": Schema.String.annotate({ "description": "Unique identifier for the assignment", "format": "uuid" }), - "key_hash": Schema.String.annotate({ "description": "Hash of the assigned API key" }), - "guardrail_id": Schema.String.annotate({ "description": "ID of the guardrail", "format": "uuid" }), - "key_name": Schema.String.annotate({ "description": "Name of the API key" }), - "key_label": Schema.String.annotate({ "description": "Label of the API key" }), - "assigned_by": Schema.String.annotate({ "description": "User ID of who made the assignment" }), - "created_at": Schema.String.annotate({ "description": "ISO 8601 timestamp of when the assignment was created" }) - })).annotate({ "description": "List of key assignments" }), - "total_count": Schema.Number.annotate({ "description": "Total number of key assignments for this guardrail" }).check( - Schema.isFinite() - ) -}) -export type ListGuardrailKeyAssignments401 = UnauthorizedResponse -export const ListGuardrailKeyAssignments401 = UnauthorizedResponse -export type ListGuardrailKeyAssignments404 = NotFoundResponse -export const ListGuardrailKeyAssignments404 = NotFoundResponse -export type ListGuardrailKeyAssignments500 = InternalServerResponse -export const ListGuardrailKeyAssignments500 = InternalServerResponse -export type BulkAssignKeysToGuardrailRequestJson = { readonly "key_hashes": ReadonlyArray } -export const BulkAssignKeysToGuardrailRequestJson = Schema.Struct({ - "key_hashes": Schema.Array(Schema.String.check(Schema.isMinLength(1))).annotate({ - "description": "Array of API key hashes to assign to the guardrail" - }).check(Schema.isMinLength(1)) -}) -export type BulkAssignKeysToGuardrail200 = { readonly "assigned_count": number } -export const BulkAssignKeysToGuardrail200 = Schema.Struct({ - "assigned_count": Schema.Number.annotate({ "description": "Number of keys successfully assigned" }).check( - Schema.isFinite() - ) -}) -export type BulkAssignKeysToGuardrail400 = BadRequestResponse -export const BulkAssignKeysToGuardrail400 = BadRequestResponse -export type BulkAssignKeysToGuardrail401 = UnauthorizedResponse -export const BulkAssignKeysToGuardrail401 = UnauthorizedResponse -export type BulkAssignKeysToGuardrail404 = NotFoundResponse -export const BulkAssignKeysToGuardrail404 = NotFoundResponse -export type BulkAssignKeysToGuardrail500 = InternalServerResponse -export const BulkAssignKeysToGuardrail500 = InternalServerResponse -export type ListGuardrailMemberAssignmentsParams = { readonly "offset"?: string; readonly "limit"?: string } -export const ListGuardrailMemberAssignmentsParams = Schema.Struct({ - "offset": Schema.optionalKey(Schema.String.annotate({ "description": "Number of records to skip for pagination" })), - "limit": Schema.optionalKey( - Schema.String.annotate({ "description": "Maximum number of records to return (max 100)" }) - ) -}) -export type ListGuardrailMemberAssignments200 = { - readonly "data": ReadonlyArray< - { - readonly "id": string - readonly "user_id": string - readonly "organization_id": string - readonly "guardrail_id": string - readonly "assigned_by": string - readonly "created_at": string - } - > - readonly "total_count": number +export const OutputFilesServerToolItem = Schema.Struct({ + "error": Schema.optionalKey( + Schema.String.annotate({ "description": "Error message when the file operation failed." }) + ), + "file_id": Schema.optionalKey( + Schema.String.annotate({ "description": "The target file id supplied in the tool-call arguments." }) + ), + "filename": Schema.optionalKey( + Schema.String.annotate({ "description": "The target filename supplied in the tool-call arguments." }) + ), + "id": Schema.optionalKey(Schema.String), + "operation": Schema.optionalKey( + Schema.String.annotate({ "description": "The file operation performed (list, read, write, or edit)." }) + ), + "result": Schema.optionalKey( + Schema.String.annotate({ "description": "JSON-serialized result of the file operation." }) + ), + "status": ToolCallStatus, + "type": Schema.Literal("openrouter:files") +}).annotate({ "description": "An openrouter:files server tool output item", "identifier": "OutputFilesServerToolItem" }) +export type OutputImageGenerationServerToolItem = { + readonly "id"?: string + readonly "imageB64"?: string + readonly "imageUrl"?: string + readonly "prompt"?: string + readonly "result"?: string | null + readonly "revisedPrompt"?: string + readonly "status": ToolCallStatus + readonly "type": "openrouter:image_generation" } -export const ListGuardrailMemberAssignments200 = Schema.Struct({ - "data": Schema.Array(Schema.Struct({ - "id": Schema.String.annotate({ "description": "Unique identifier for the assignment", "format": "uuid" }), - "user_id": Schema.String.annotate({ "description": "Clerk user ID of the assigned member" }), - "organization_id": Schema.String.annotate({ "description": "Organization ID" }), - "guardrail_id": Schema.String.annotate({ "description": "ID of the guardrail", "format": "uuid" }), - "assigned_by": Schema.String.annotate({ "description": "User ID of who made the assignment" }), - "created_at": Schema.String.annotate({ "description": "ISO 8601 timestamp of when the assignment was created" }) - })).annotate({ "description": "List of member assignments" }), - "total_count": Schema.Number.annotate({ "description": "Total number of member assignments" }).check( - Schema.isFinite() - ) -}) -export type ListGuardrailMemberAssignments401 = UnauthorizedResponse -export const ListGuardrailMemberAssignments401 = UnauthorizedResponse -export type ListGuardrailMemberAssignments404 = NotFoundResponse -export const ListGuardrailMemberAssignments404 = NotFoundResponse -export type ListGuardrailMemberAssignments500 = InternalServerResponse -export const ListGuardrailMemberAssignments500 = InternalServerResponse -export type BulkAssignMembersToGuardrailRequestJson = { readonly "member_user_ids": ReadonlyArray } -export const BulkAssignMembersToGuardrailRequestJson = Schema.Struct({ - "member_user_ids": Schema.Array(Schema.String.check(Schema.isMinLength(1))).annotate({ - "description": "Array of member user IDs to assign to the guardrail" - }).check(Schema.isMinLength(1)) -}) -export type BulkAssignMembersToGuardrail200 = { readonly "assigned_count": number } -export const BulkAssignMembersToGuardrail200 = Schema.Struct({ - "assigned_count": Schema.Number.annotate({ "description": "Number of members successfully assigned" }).check( - Schema.isFinite() - ) -}) -export type BulkAssignMembersToGuardrail400 = BadRequestResponse -export const BulkAssignMembersToGuardrail400 = BadRequestResponse -export type BulkAssignMembersToGuardrail401 = UnauthorizedResponse -export const BulkAssignMembersToGuardrail401 = UnauthorizedResponse -export type BulkAssignMembersToGuardrail404 = NotFoundResponse -export const BulkAssignMembersToGuardrail404 = NotFoundResponse -export type BulkAssignMembersToGuardrail500 = InternalServerResponse -export const BulkAssignMembersToGuardrail500 = InternalServerResponse -export type BulkUnassignKeysFromGuardrailRequestJson = { readonly "key_hashes": ReadonlyArray } -export const BulkUnassignKeysFromGuardrailRequestJson = Schema.Struct({ - "key_hashes": Schema.Array(Schema.String.check(Schema.isMinLength(1))).annotate({ - "description": "Array of API key hashes to unassign from the guardrail" - }).check(Schema.isMinLength(1)) -}) -export type BulkUnassignKeysFromGuardrail200 = { readonly "unassigned_count": number } -export const BulkUnassignKeysFromGuardrail200 = Schema.Struct({ - "unassigned_count": Schema.Number.annotate({ "description": "Number of keys successfully unassigned" }).check( - Schema.isFinite() - ) -}) -export type BulkUnassignKeysFromGuardrail400 = BadRequestResponse -export const BulkUnassignKeysFromGuardrail400 = BadRequestResponse -export type BulkUnassignKeysFromGuardrail401 = UnauthorizedResponse -export const BulkUnassignKeysFromGuardrail401 = UnauthorizedResponse -export type BulkUnassignKeysFromGuardrail404 = NotFoundResponse -export const BulkUnassignKeysFromGuardrail404 = NotFoundResponse -export type BulkUnassignKeysFromGuardrail500 = InternalServerResponse -export const BulkUnassignKeysFromGuardrail500 = InternalServerResponse -export type BulkUnassignMembersFromGuardrailRequestJson = { readonly "member_user_ids": ReadonlyArray } -export const BulkUnassignMembersFromGuardrailRequestJson = Schema.Struct({ - "member_user_ids": Schema.Array(Schema.String.check(Schema.isMinLength(1))).annotate({ - "description": "Array of member user IDs to unassign from the guardrail" - }).check(Schema.isMinLength(1)) -}) -export type BulkUnassignMembersFromGuardrail200 = { readonly "unassigned_count": number } -export const BulkUnassignMembersFromGuardrail200 = Schema.Struct({ - "unassigned_count": Schema.Number.annotate({ "description": "Number of members successfully unassigned" }).check( - Schema.isFinite() - ) +export const OutputImageGenerationServerToolItem = Schema.Struct({ + "id": Schema.optionalKey(Schema.String), + "imageB64": Schema.optionalKey(Schema.String), + "imageUrl": Schema.optionalKey(Schema.String), + "prompt": Schema.optionalKey( + Schema.String.annotate({ "description": "The prompt (possibly rewritten) that the image was generated from." }) + ), + "result": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": + "The generated image as a base64-encoded string or URL, matching OpenAI image_generation_call format" + }) + ), + "revisedPrompt": Schema.optionalKey(Schema.String), + "status": ToolCallStatus, + "type": Schema.Literal("openrouter:image_generation") +}).annotate({ + "description": "An openrouter:image_generation server tool output item", + "identifier": "OutputImageGenerationServerToolItem" }) -export type BulkUnassignMembersFromGuardrail400 = BadRequestResponse -export const BulkUnassignMembersFromGuardrail400 = BadRequestResponse -export type BulkUnassignMembersFromGuardrail401 = UnauthorizedResponse -export const BulkUnassignMembersFromGuardrail401 = UnauthorizedResponse -export type BulkUnassignMembersFromGuardrail404 = NotFoundResponse -export const BulkUnassignMembersFromGuardrail404 = NotFoundResponse -export type BulkUnassignMembersFromGuardrail500 = InternalServerResponse -export const BulkUnassignMembersFromGuardrail500 = InternalServerResponse -export type GetCurrentKey200 = { - readonly "data": { - readonly "label": string - readonly "limit": number - readonly "usage": number - readonly "usage_daily": number - readonly "usage_weekly": number - readonly "usage_monthly": number - readonly "byok_usage": number - readonly "byok_usage_daily": number - readonly "byok_usage_weekly": number - readonly "byok_usage_monthly": number - readonly "is_free_tier": boolean - readonly "is_management_key": boolean - readonly "is_provisioning_key": boolean - readonly "limit_remaining": number - readonly "limit_reset": string - readonly "include_byok_in_limit": boolean - readonly "expires_at"?: string - readonly "rate_limit": { readonly "requests": number; readonly "interval": string; readonly "note": string } - } +export type OutputMcpServerToolItem = { + readonly "id"?: string + readonly "serverLabel"?: string + readonly "status": ToolCallStatus + readonly "toolName"?: string + readonly "type": "openrouter:mcp" } -export const GetCurrentKey200 = Schema.Struct({ - "data": Schema.Struct({ - "label": Schema.String.annotate({ "description": "Human-readable label for the API key" }), - "limit": Schema.Number.annotate({ "description": "Spending limit for the API key in USD" }).check( - Schema.isFinite() - ), - "usage": Schema.Number.annotate({ "description": "Total OpenRouter credit usage (in USD) for the API key" }).check( - Schema.isFinite() - ), - "usage_daily": Schema.Number.annotate({ "description": "OpenRouter credit usage (in USD) for the current UTC day" }) - .check(Schema.isFinite()), - "usage_weekly": Schema.Number.annotate({ - "description": "OpenRouter credit usage (in USD) for the current UTC week (Monday-Sunday)" - }).check(Schema.isFinite()), - "usage_monthly": Schema.Number.annotate({ - "description": "OpenRouter credit usage (in USD) for the current UTC month" - }).check(Schema.isFinite()), - "byok_usage": Schema.Number.annotate({ "description": "Total external BYOK usage (in USD) for the API key" }).check( - Schema.isFinite() - ), - "byok_usage_daily": Schema.Number.annotate({ - "description": "External BYOK usage (in USD) for the current UTC day" - }).check(Schema.isFinite()), - "byok_usage_weekly": Schema.Number.annotate({ - "description": "External BYOK usage (in USD) for the current UTC week (Monday-Sunday)" - }).check(Schema.isFinite()), - "byok_usage_monthly": Schema.Number.annotate({ - "description": "External BYOK usage (in USD) for current UTC month" - }).check(Schema.isFinite()), - "is_free_tier": Schema.Boolean.annotate({ "description": "Whether this is a free tier API key" }), - "is_management_key": Schema.Boolean.annotate({ "description": "Whether this is a management key" }), - "is_provisioning_key": Schema.Boolean.annotate({ "description": "Whether this is a management key" }), - "limit_remaining": Schema.Number.annotate({ "description": "Remaining spending limit in USD" }).check( - Schema.isFinite() - ), - "limit_reset": Schema.String.annotate({ "description": "Type of limit reset for the API key" }), - "include_byok_in_limit": Schema.Boolean.annotate({ - "description": "Whether to include external BYOK usage in the credit limit" - }), - "expires_at": Schema.optionalKey( - Schema.String.annotate({ - "description": "ISO 8601 UTC timestamp when the API key expires, or null if no expiration", - "format": "date-time" - }) - ), - "rate_limit": Schema.Struct({ - "requests": Schema.Number.annotate({ "description": "Number of requests allowed per interval" }).check( - Schema.isFinite() - ), - "interval": Schema.String.annotate({ "description": "Rate limit interval" }), - "note": Schema.String.annotate({ "description": "Note about the rate limit" }) - }).annotate({ "description": "Legacy rate limit information about a key. Will always return -1." }) - }).annotate({ "description": "Current API key information" }) +export const OutputMcpServerToolItem = Schema.Struct({ + "id": Schema.optionalKey(Schema.String), + "serverLabel": Schema.optionalKey(Schema.String), + "status": ToolCallStatus, + "toolName": Schema.optionalKey(Schema.String), + "type": Schema.Literal("openrouter:mcp") +}).annotate({ "description": "An openrouter:mcp server tool output item", "identifier": "OutputMcpServerToolItem" }) +export type OutputMemoryServerToolItem = { + readonly "action"?: "read" | "write" | "delete" + readonly "id"?: string + readonly "key"?: string + readonly "status": ToolCallStatus + readonly "type": "openrouter:memory" + readonly "value"?: Schema.Json +} +export const OutputMemoryServerToolItem = Schema.Struct({ + "action": Schema.optionalKey(Schema.Literals(["read", "write", "delete"])), + "id": Schema.optionalKey(Schema.String), + "key": Schema.optionalKey(Schema.String), + "status": ToolCallStatus, + "type": Schema.Literal("openrouter:memory"), + "value": Schema.optionalKey(Schema.Json.annotate({ "expected": "JSON value" })) +}).annotate({ + "description": "An openrouter:memory server tool output item", + "identifier": "OutputMemoryServerToolItem" }) -export type GetCurrentKey401 = UnauthorizedResponse -export const GetCurrentKey401 = UnauthorizedResponse -export type GetCurrentKey500 = InternalServerResponse -export const GetCurrentKey500 = InternalServerResponse -export type ExchangeAuthCodeForAPIKeyRequestJson = { - readonly "code": string - readonly "code_verifier"?: string - readonly "code_challenge_method"?: "S256" | "plain" +export type OutputSearchModelsServerToolItem = { + readonly "arguments"?: string + readonly "id"?: string + readonly "query"?: string + readonly "status": ToolCallStatus + readonly "type": "openrouter:experimental__search_models" } -export const ExchangeAuthCodeForAPIKeyRequestJson = Schema.Struct({ - "code": Schema.String.annotate({ "description": "The authorization code received from the OAuth redirect" }), - "code_verifier": Schema.optionalKey( +export const OutputSearchModelsServerToolItem = Schema.Struct({ + "arguments": Schema.optionalKey( Schema.String.annotate({ - "description": "The code verifier if code_challenge was used in the authorization request" + "description": "The JSON arguments submitted to the search tool (e.g. {\"query\":\"Claude\"})" }) ), - "code_challenge_method": Schema.optionalKey( - Schema.Literals(["S256", "plain"]).annotate({ "description": "The method used to generate the code challenge" }) - ) -}) -export type ExchangeAuthCodeForAPIKey200 = { readonly "key": string; readonly "user_id": string } -export const ExchangeAuthCodeForAPIKey200 = Schema.Struct({ - "key": Schema.String.annotate({ "description": "The API key to use for OpenRouter requests" }), - "user_id": Schema.String.annotate({ "description": "User ID associated with the API key" }) + "id": Schema.optionalKey(Schema.String), + "query": Schema.optionalKey(Schema.String), + "status": ToolCallStatus, + "type": Schema.Literal("openrouter:experimental__search_models") +}).annotate({ + "description": "An openrouter:experimental__search_models server tool output item", + "identifier": "OutputSearchModelsServerToolItem" }) -export type ExchangeAuthCodeForAPIKey400 = BadRequestResponse -export const ExchangeAuthCodeForAPIKey400 = BadRequestResponse -export type ExchangeAuthCodeForAPIKey403 = ForbiddenResponse -export const ExchangeAuthCodeForAPIKey403 = ForbiddenResponse -export type ExchangeAuthCodeForAPIKey500 = InternalServerResponse -export const ExchangeAuthCodeForAPIKey500 = InternalServerResponse -export type CreateAuthKeysCodeRequestJson = { - readonly "callback_url": string - readonly "code_challenge"?: string - readonly "code_challenge_method"?: "S256" | "plain" - readonly "limit"?: number - readonly "expires_at"?: string +export type OutputSubagentServerToolItem = { + readonly "error"?: string + readonly "id"?: string + readonly "instance_name"?: string + readonly "model"?: string + readonly "name"?: string + readonly "outcome"?: string + readonly "status": ToolCallStatus + readonly "task_description"?: string + readonly "task_name"?: string + readonly "type": "openrouter:subagent" } -export const CreateAuthKeysCodeRequestJson = Schema.Struct({ - "callback_url": Schema.String.annotate({ - "description": - "The callback URL to redirect to after authorization. Note, only https URLs on ports 443 and 3000 are allowed.", - "format": "uri" - }), - "code_challenge": Schema.optionalKey( - Schema.String.annotate({ "description": "PKCE code challenge for enhanced security" }) +export const OutputSubagentServerToolItem = Schema.Struct({ + "error": Schema.optionalKey( + Schema.String.annotate({ "description": "Error message when the subagent task did not produce an outcome." }) ), - "code_challenge_method": Schema.optionalKey( - Schema.Literals(["S256", "plain"]).annotate({ "description": "The method used to generate the code challenge" }) + "id": Schema.optionalKey(Schema.String), + "instance_name": Schema.optionalKey(Schema.String.annotate({ + "description": + "Provider-safe function name of the specific subagent instance that produced this item (e.g. `openrouter_subagent__1`). Present only on items from non-default instances — the second and later subagent entries in the request `tools` array. The first (default) instance omits it, even when multiple subagents are configured. When a replayed item echoes this field back, the transcript rehydrates the call under that instance's tool. This identity is positional: it is derived from the index of the subagent entry in the request `tools` array, so keep the order of subagent entries stable across requests in a conversation." + })), + "model": Schema.optionalKey( + Schema.String.annotate({ "description": "Slug of the worker model that executed the task." }) ), - "limit": Schema.optionalKey( - Schema.Number.annotate({ "description": "Credit limit for the API key to be created" }).check(Schema.isFinite()) + "name": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Configured name of the subagent that executed the task (the `name` on its tool entry). Present only for named subagents; omitted for an unnamed (default) subagent." + }) ), - "expires_at": Schema.optionalKey( + "outcome": Schema.optionalKey( Schema.String.annotate({ - "description": "Optional expiration time for the API key to be created", - "format": "date-time" + "description": "The worker model's result (the outcome text returned to the delegating model)." }) - ) + ), + "status": ToolCallStatus, + "task_description": Schema.optionalKey( + Schema.String.annotate({ "description": "The task description the delegating model sent to the worker." }) + ), + "task_name": Schema.optionalKey( + Schema.String.annotate({ "description": "The short task identifier the delegating model supplied." }) + ), + "type": Schema.Literal("openrouter:subagent") +}).annotate({ + "description": "An openrouter:subagent server tool output item", + "identifier": "OutputSubagentServerToolItem" }) -export type CreateAuthKeysCode200 = { - readonly "data": { readonly "id": string; readonly "app_id": number; readonly "created_at": string } +export type OutputTextEditorServerToolItem = { + readonly "command"?: "view" | "create" | "str_replace" | "insert" + readonly "filePath"?: string + readonly "id"?: string + readonly "status": ToolCallStatus + readonly "type": "openrouter:text_editor" } -export const CreateAuthKeysCode200 = Schema.Struct({ - "data": Schema.Struct({ - "id": Schema.String.annotate({ "description": "The authorization code ID to use in the exchange request" }), - "app_id": Schema.Number.annotate({ "description": "The application ID associated with this auth code" }).check( - Schema.isFinite() - ), - "created_at": Schema.String.annotate({ "description": "ISO 8601 timestamp of when the auth code was created" }) - }).annotate({ "description": "Auth code data" }) +export const OutputTextEditorServerToolItem = Schema.Struct({ + "command": Schema.optionalKey(Schema.Literals(["view", "create", "str_replace", "insert"])), + "filePath": Schema.optionalKey(Schema.String), + "id": Schema.optionalKey(Schema.String), + "status": ToolCallStatus, + "type": Schema.Literal("openrouter:text_editor") +}).annotate({ + "description": "An openrouter:text_editor server tool output item", + "identifier": "OutputTextEditorServerToolItem" }) -export type CreateAuthKeysCode400 = BadRequestResponse -export const CreateAuthKeysCode400 = BadRequestResponse -export type CreateAuthKeysCode401 = UnauthorizedResponse -export const CreateAuthKeysCode401 = UnauthorizedResponse -export type CreateAuthKeysCode500 = InternalServerResponse -export const CreateAuthKeysCode500 = InternalServerResponse -export type SendChatCompletionRequestRequestJson = ChatGenerationParams -export const SendChatCompletionRequestRequestJson = ChatGenerationParams -export type SendChatCompletionRequest200 = { - readonly "id": string - readonly "choices": ReadonlyArray - readonly "created": number - readonly "model": string - readonly "object": "chat.completion" - readonly "system_fingerprint"?: string | null - readonly "usage"?: ChatGenerationTokenUsage +export type OutputToolSearchServerToolItem = { + readonly "id"?: string + readonly "query"?: string + readonly "status": ToolCallStatus + readonly "type": "openrouter:tool_search" } -export const SendChatCompletionRequest200 = Schema.Struct({ - "id": Schema.String, - "choices": Schema.Array(ChatResponseChoice), - "created": Schema.Number.check(Schema.isFinite()), - "model": Schema.String, - "object": Schema.Literal("chat.completion"), - "system_fingerprint": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - "usage": Schema.optionalKey(ChatGenerationTokenUsage) -}).annotate({ "description": "Chat completion response" }) -export type SendChatCompletionRequest200Sse = ChatStreamingResponseChunk -export const SendChatCompletionRequest200Sse = ChatStreamingResponseChunk -export type SendChatCompletionRequest400 = ChatError -export const SendChatCompletionRequest400 = ChatError -export type SendChatCompletionRequest401 = ChatError -export const SendChatCompletionRequest401 = ChatError -export type SendChatCompletionRequest429 = ChatError -export const SendChatCompletionRequest429 = ChatError -export type SendChatCompletionRequest500 = ChatError -export const SendChatCompletionRequest500 = ChatError - -export interface OperationConfig { - /** - * Whether or not the response should be included in the value returned from - * an operation. - * - * If set to `true`, a tuple of `[A, HttpClientResponse]` will be returned, - * where `A` is the success type of the operation. - * - * If set to `false`, only the success type of the operation will be returned. - */ - readonly includeResponse?: boolean | undefined +export const OutputToolSearchServerToolItem = Schema.Struct({ + "id": Schema.optionalKey(Schema.String), + "query": Schema.optionalKey(Schema.String), + "status": ToolCallStatus, + "type": Schema.Literal("openrouter:tool_search") +}).annotate({ + "description": "An openrouter:tool_search server tool output item", + "identifier": "OutputToolSearchServerToolItem" +}) +export type OutputWebFetchServerToolItem = { + readonly "content"?: string + readonly "error"?: string + readonly "httpStatus"?: number + readonly "id"?: string + readonly "status": ToolCallStatus + readonly "title"?: string + readonly "type": "openrouter:web_fetch" + readonly "url"?: string } - -/** - * A utility type which optionally includes the response in the return result - * of an operation based upon the value of the `includeResponse` configuration - * option. - */ -export type WithOptionalResponse = Config extends { - readonly includeResponse: true -} ? [A, HttpClientResponse.HttpClientResponse] : - A - -export const make = ( - httpClient: HttpClient.HttpClient, - options: { - readonly transformClient?: ((client: HttpClient.HttpClient) => Effect.Effect) | undefined - } = {} -): OpenRouterClient => { - const unexpectedStatus = (response: HttpClientResponse.HttpClientResponse) => - Effect.flatMap( - Effect.orElseSucceed(response.json, () => "Unexpected status code"), - (description) => - Effect.fail( - new HttpClientError.HttpClientError({ - reason: new HttpClientError.StatusCodeError({ - request: response.request, - response, - description: typeof description === "string" ? description : JSON.stringify(description) - }) - }) - ) +export const OutputWebFetchServerToolItem = Schema.Struct({ + "content": Schema.optionalKey(Schema.String), + "error": Schema.optionalKey(Schema.String.annotate({ "description": "The error message if the fetch failed." })), + "httpStatus": Schema.optionalKey( + Schema.Number.annotate({ "description": "The HTTP status code returned by the upstream URL fetch." }).check( + Schema.isInt().annotate({ "expected": "an integer" }) ) - const withResponse = (config: Config | undefined) => - ( - f: (response: HttpClientResponse.HttpClientResponse) => Effect.Effect - ): (request: HttpClientRequest.HttpClientRequest) => Effect.Effect => { - const withOptionalResponse = ( - config?.includeResponse - ? (response: HttpClientResponse.HttpClientResponse) => Effect.map(f(response), (a) => [a, response]) - : (response: HttpClientResponse.HttpClientResponse) => f(response) - ) as any - return options?.transformClient - ? (request) => - Effect.flatMap( - Effect.flatMap(options.transformClient!(httpClient), (client) => client.execute(request)), - withOptionalResponse - ) - : (request) => Effect.flatMap(httpClient.execute(request), withOptionalResponse) + ), + "id": Schema.optionalKey(Schema.String), + "status": ToolCallStatus, + "title": Schema.optionalKey(Schema.String), + "type": Schema.Literal("openrouter:web_fetch"), + "url": Schema.optionalKey(Schema.String) +}).annotate({ + "description": "An openrouter:web_fetch server tool output item", + "identifier": "OutputWebFetchServerToolItem" +}) +export type OutputWebSearchServerToolItem = { + readonly "action"?: { + readonly "query": string + readonly "sources"?: ReadonlyArray<{ readonly "type": "url"; readonly "url": string }> + readonly "type": "search" } - const sseRequest = < - Type, - DecodingServices - >( - schema: Schema.ConstraintDecoder - ) => - ( - request: HttpClientRequest.HttpClientRequest - ): Stream.Stream< - { readonly event: string; readonly id: string | undefined; readonly data: Type }, - HttpClientError.HttpClientError | SchemaError | Sse.Retry, - DecodingServices - > => - HttpClient.filterStatusOk(httpClient).execute(request).pipe( - Effect.map((response) => response.stream), - Stream.unwrap, - Stream.decodeText(), - Stream.pipeThroughChannel(Sse.decodeDataSchema(schema)) - ) - const decodeSuccess = - (schema: Schema) => (response: HttpClientResponse.HttpClientResponse) => - HttpClientResponse.schemaBodyJson(schema)(response) - const decodeError = - (tag: Tag, schema: Schema) => - (response: HttpClientResponse.HttpClientResponse) => - Effect.flatMap( - HttpClientResponse.schemaBodyJson(schema)(response), - (cause) => Effect.fail(OpenRouterClientError(tag, cause, response)) - ) - return { - httpClient, - "createResponses": (options) => - HttpClientRequest.post(`/responses`).pipe( - HttpClientRequest.bodyJsonUnsafe(options.payload), - withResponse(options.config)(HttpClientResponse.matchStatus({ - "2xx": decodeSuccess(CreateResponses200), - "400": decodeError("CreateResponses400", CreateResponses400), - "401": decodeError("CreateResponses401", CreateResponses401), - "402": decodeError("CreateResponses402", CreateResponses402), - "404": decodeError("CreateResponses404", CreateResponses404), - "408": decodeError("CreateResponses408", CreateResponses408), - "413": decodeError("CreateResponses413", CreateResponses413), - "422": decodeError("CreateResponses422", CreateResponses422), - "429": decodeError("CreateResponses429", CreateResponses429), - "500": decodeError("CreateResponses500", CreateResponses500), - "502": decodeError("CreateResponses502", CreateResponses502), - "503": decodeError("CreateResponses503", CreateResponses503), - "524": decodeError("CreateResponses524", CreateResponses524), - "529": decodeError("CreateResponses529", CreateResponses529), - orElse: unexpectedStatus - })) - ), - "createResponsesSse": (options) => - HttpClientRequest.post(`/responses`).pipe( - HttpClientRequest.bodyJsonUnsafe(options.payload), - sseRequest(CreateResponses200Sse) - ), - "createMessages": (options) => - HttpClientRequest.post(`/messages`).pipe( - HttpClientRequest.bodyJsonUnsafe(options.payload), - withResponse(options.config)(HttpClientResponse.matchStatus({ - "2xx": decodeSuccess(CreateMessages200), - "400": decodeError("CreateMessages400", CreateMessages400), - "401": decodeError("CreateMessages401", CreateMessages401), - "403": decodeError("CreateMessages403", CreateMessages403), - "404": decodeError("CreateMessages404", CreateMessages404), - "429": decodeError("CreateMessages429", CreateMessages429), - "500": decodeError("CreateMessages500", CreateMessages500), - "503": decodeError("CreateMessages503", CreateMessages503), - "529": decodeError("CreateMessages529", CreateMessages529), - orElse: unexpectedStatus - })) - ), - "createMessagesSse": (options) => - HttpClientRequest.post(`/messages`).pipe( - HttpClientRequest.bodyJsonUnsafe(options.payload), - sseRequest(CreateMessages200Sse) - ), - "getUserActivity": (options) => - HttpClientRequest.get(`/activity`).pipe( - HttpClientRequest.setUrlParams({ "date": options?.params?.["date"] as any }), - withResponse(options?.config)(HttpClientResponse.matchStatus({ - "2xx": decodeSuccess(GetUserActivity200), - "400": decodeError("GetUserActivity400", GetUserActivity400), - "401": decodeError("GetUserActivity401", GetUserActivity401), - "403": decodeError("GetUserActivity403", GetUserActivity403), - "500": decodeError("GetUserActivity500", GetUserActivity500), - orElse: unexpectedStatus - })) - ), - "getCredits": (options) => - HttpClientRequest.get(`/credits`).pipe( - withResponse(options?.config)(HttpClientResponse.matchStatus({ - "2xx": decodeSuccess(GetCredits200), - "401": decodeError("GetCredits401", GetCredits401), - "403": decodeError("GetCredits403", GetCredits403), - "500": decodeError("GetCredits500", GetCredits500), - orElse: unexpectedStatus - })) - ), - "createCoinbaseCharge": (options) => - HttpClientRequest.post(`/credits/coinbase`).pipe( - HttpClientRequest.bodyJsonUnsafe(options.payload), - withResponse(options.config)(HttpClientResponse.matchStatus({ - "2xx": decodeSuccess(CreateCoinbaseCharge200), - "400": decodeError("CreateCoinbaseCharge400", CreateCoinbaseCharge400), - "401": decodeError("CreateCoinbaseCharge401", CreateCoinbaseCharge401), - "429": decodeError("CreateCoinbaseCharge429", CreateCoinbaseCharge429), - "500": decodeError("CreateCoinbaseCharge500", CreateCoinbaseCharge500), - orElse: unexpectedStatus - })) - ), - "createEmbeddings": (options) => - HttpClientRequest.post(`/embeddings`).pipe( - HttpClientRequest.bodyJsonUnsafe(options.payload), - withResponse(options.config)(HttpClientResponse.matchStatus({ - "2xx": decodeSuccess(CreateEmbeddings200), - "400": decodeError("CreateEmbeddings400", CreateEmbeddings400), - "401": decodeError("CreateEmbeddings401", CreateEmbeddings401), - "402": decodeError("CreateEmbeddings402", CreateEmbeddings402), - "404": decodeError("CreateEmbeddings404", CreateEmbeddings404), - "429": decodeError("CreateEmbeddings429", CreateEmbeddings429), - "500": decodeError("CreateEmbeddings500", CreateEmbeddings500), - "502": decodeError("CreateEmbeddings502", CreateEmbeddings502), - "503": decodeError("CreateEmbeddings503", CreateEmbeddings503), - "524": decodeError("CreateEmbeddings524", CreateEmbeddings524), - "529": decodeError("CreateEmbeddings529", CreateEmbeddings529), - orElse: unexpectedStatus - })) - ), - "createEmbeddingsSse": (options) => - HttpClientRequest.post(`/embeddings`).pipe( - HttpClientRequest.bodyJsonUnsafe(options.payload), - sseRequest(CreateEmbeddings200Sse) - ), - "listEmbeddingsModels": (options) => - HttpClientRequest.get(`/embeddings/models`).pipe( - withResponse(options?.config)(HttpClientResponse.matchStatus({ - "2xx": decodeSuccess(ListEmbeddingsModels200), - "400": decodeError("ListEmbeddingsModels400", ListEmbeddingsModels400), - "500": decodeError("ListEmbeddingsModels500", ListEmbeddingsModels500), - orElse: unexpectedStatus - })) - ), - "getGeneration": (options) => - HttpClientRequest.get(`/generation`).pipe( - HttpClientRequest.setUrlParams({ "id": options.params["id"] as any }), - withResponse(options.config)(HttpClientResponse.matchStatus({ - "2xx": decodeSuccess(GetGeneration200), - "401": decodeError("GetGeneration401", GetGeneration401), - "402": decodeError("GetGeneration402", GetGeneration402), - "404": decodeError("GetGeneration404", GetGeneration404), - "429": decodeError("GetGeneration429", GetGeneration429), - "500": decodeError("GetGeneration500", GetGeneration500), - "502": decodeError("GetGeneration502", GetGeneration502), - "524": decodeError("GetGeneration524", GetGeneration524), - "529": decodeError("GetGeneration529", GetGeneration529), - orElse: unexpectedStatus - })) - ), - "listModelsCount": (options) => - HttpClientRequest.get(`/models/count`).pipe( - withResponse(options?.config)(HttpClientResponse.matchStatus({ - "2xx": decodeSuccess(ListModelsCount200), - "500": decodeError("ListModelsCount500", ListModelsCount500), - orElse: unexpectedStatus - })) - ), - "getModels": (options) => - HttpClientRequest.get(`/models`).pipe( - HttpClientRequest.setUrlParams({ - "category": options?.params?.["category"] as any, - "supported_parameters": options?.params?.["supported_parameters"] as any - }), - withResponse(options?.config)(HttpClientResponse.matchStatus({ - "2xx": decodeSuccess(GetModels200), - "400": decodeError("GetModels400", GetModels400), - "500": decodeError("GetModels500", GetModels500), - orElse: unexpectedStatus - })) + readonly "id"?: string + readonly "status": ToolCallStatus + readonly "type": "openrouter:web_search" +} +export const OutputWebSearchServerToolItem = Schema.Struct({ + "action": Schema.optionalKey( + Schema.Struct({ + "query": Schema.String, + "sources": Schema.optionalKey( + Schema.Array(Schema.Struct({ "type": Schema.Literal("url"), "url": Schema.String })) ), - "listModelsUser": (options) => - HttpClientRequest.get(`/models/user`).pipe( - withResponse(options?.config)(HttpClientResponse.matchStatus({ - "2xx": decodeSuccess(ListModelsUser200), - "401": decodeError("ListModelsUser401", ListModelsUser401), - "404": decodeError("ListModelsUser404", ListModelsUser404), - "500": decodeError("ListModelsUser500", ListModelsUser500), - orElse: unexpectedStatus - })) + "type": Schema.Literal("search") + }).annotate({ + "description": + "The search action performed, matching OpenAI web_search_call.action shape. Includes the query the model issued and optional source URLs returned by the search provider." + }) + ), + "id": Schema.optionalKey(Schema.String), + "status": ToolCallStatus, + "type": Schema.Literal("openrouter:web_search") +}).annotate({ + "description": "An openrouter:web_search server tool output item", + "identifier": "OutputWebSearchServerToolItem" +}) +export type ShellCallItem = { + readonly "action": { + readonly "commands": ReadonlyArray + readonly "max_output_length"?: number | null + readonly "timeout_ms"?: number | null + } + readonly "call_id": string + readonly "environment"?: Schema.Json + readonly "id"?: string | null + readonly "status"?: ToolCallStatus | null + readonly "type": "shell_call" +} +export const ShellCallItem = Schema.Struct({ + "action": Schema.Struct({ + "commands": Schema.Array(Schema.String), + "max_output_length": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]) + ), + "timeout_ms": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]) + ) + }), + "call_id": Schema.String, + "environment": Schema.optionalKey(Schema.Json.annotate({ "expected": "JSON value" })), + "id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "status": Schema.optionalKey(Schema.Union([ToolCallStatus, Schema.Null])), + "type": Schema.Literal("shell_call") +}).annotate({ "description": "A shell command execution call (newer variant)", "identifier": "ShellCallItem" }) +export type ShellCallOutputItem = { + readonly "call_id": string + readonly "id"?: string | null + readonly "max_output_length"?: number | null + readonly "output": ReadonlyArray< + { readonly "content"?: string | null; readonly "exit_code"?: number | null; readonly "type": string } + > + readonly "status"?: ToolCallStatus | null + readonly "type": "shell_call_output" +} +export const ShellCallOutputItem = Schema.Struct({ + "call_id": Schema.String, + "id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "max_output_length": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]) + ), + "output": Schema.Array( + Schema.Struct({ + "content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "exit_code": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]) ), - "listEndpoints": (author, slug, options) => - HttpClientRequest.get(`/models/${author}/${slug}/endpoints`).pipe( - withResponse(options?.config)(HttpClientResponse.matchStatus({ - "2xx": decodeSuccess(ListEndpoints200), - "404": decodeError("ListEndpoints404", ListEndpoints404), - "500": decodeError("ListEndpoints500", ListEndpoints500), - orElse: unexpectedStatus - })) - ), - "listEndpointsZdr": (options) => - HttpClientRequest.get(`/endpoints/zdr`).pipe( - withResponse(options?.config)(HttpClientResponse.matchStatus({ - "2xx": decodeSuccess(ListEndpointsZdr200), - "500": decodeError("ListEndpointsZdr500", ListEndpointsZdr500), - orElse: unexpectedStatus - })) - ), - "listProviders": (options) => - HttpClientRequest.get(`/providers`).pipe( - withResponse(options?.config)(HttpClientResponse.matchStatus({ - "2xx": decodeSuccess(ListProviders200), - "500": decodeError("ListProviders500", ListProviders500), - orElse: unexpectedStatus - })) - ), - "list": (options) => - HttpClientRequest.get(`/keys`).pipe( - HttpClientRequest.setUrlParams({ - "include_disabled": options?.params?.["include_disabled"] as any, - "offset": options?.params?.["offset"] as any + "type": Schema.String + }) + ), + "status": Schema.optionalKey(Schema.Union([ToolCallStatus, Schema.Null])), + "type": Schema.Literal("shell_call_output") +}).annotate({ + "description": "Output from a shell command execution (newer variant)", + "identifier": "ShellCallOutputItem" +}) +export type OpenAIResponsesToolChoice = + | "auto" + | "none" + | "required" + | Objects_11 + | Objects_12 + | ToolChoiceAllowed + | Objects_13 + | Objects_14 +export const OpenAIResponsesToolChoice = Schema.Union([ + Schema.Literal("auto"), + Schema.Literal("none"), + Schema.Literal("required"), + Objects_11, + Objects_12, + ToolChoiceAllowed, + Objects_13, + Objects_14 +]).annotate({ "identifier": "OpenAIResponsesToolChoice" }) +export type TooManyRequestsResponse = { + readonly "error": TooManyRequestsResponseErrorData + readonly "openrouter_metadata"?: { readonly [x: string]: Schema.Json } | null + readonly "user_id"?: string | null +} +export const TooManyRequestsResponse = Schema.Struct({ + "error": TooManyRequestsResponseErrorData, + "openrouter_metadata": Schema.optionalKey( + Schema.Union([Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })), Schema.Null]) + ), + "user_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) +}).annotate({ "description": "Too Many Requests - Rate limit exceeded", "identifier": "TooManyRequestsResponse" }) +export type UnauthorizedResponse = { + readonly "error": UnauthorizedResponseErrorData + readonly "openrouter_metadata"?: { readonly [x: string]: Schema.Json } | null + readonly "user_id"?: string | null +} +export const UnauthorizedResponse = Schema.Struct({ + "error": UnauthorizedResponseErrorData, + "openrouter_metadata": Schema.optionalKey( + Schema.Union([Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })), Schema.Null]) + ), + "user_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) +}).annotate({ + "description": "Unauthorized - Authentication required or invalid credentials", + "identifier": "UnauthorizedResponse" +}) +export type UnifiedBenchmarksAAItem = { + readonly "agentic_index": number | null + readonly "coding_index": number | null + readonly "display_name": string + readonly "intelligence_index": number | null + readonly "model_permaslug": string + readonly "pricing": UnifiedBenchmarkPricing + readonly "source": "artificial-analysis" +} +export const UnifiedBenchmarksAAItem = Schema.Struct({ + "agentic_index": Schema.Union([ + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), + Schema.Null + ]).annotate({ + "description": "Artificial Analysis Agentic Index composite score. Higher is better.", + "format": "double" + }), + "coding_index": Schema.Union([ + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), + Schema.Null + ]).annotate({ + "description": "Artificial Analysis Coding Index composite score. Higher is better.", + "format": "double" + }), + "display_name": Schema.String.annotate({ "description": "Model name as listed on Artificial Analysis." }), + "intelligence_index": Schema.Union([ + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), + Schema.Null + ]).annotate({ + "description": "Artificial Analysis Intelligence Index composite score. Higher is better.", + "format": "double" + }), + "model_permaslug": Schema.String.annotate({ "description": "Stable OpenRouter model identifier." }), + "pricing": UnifiedBenchmarkPricing, + "source": Schema.Literal("artificial-analysis").annotate({ "description": "Benchmark source discriminator." }) +}).annotate({ "identifier": "UnifiedBenchmarksAAItem" }) +export type UnifiedBenchmarksDAItem = { + readonly "arena": string + readonly "avg_generation_time_ms": number | null + readonly "category": string + readonly "display_name": string + readonly "elo": number + readonly "model_permaslug": string + readonly "pricing": UnifiedBenchmarkPricing + readonly "source": "design-arena" + readonly "tournament_stats": { + readonly "first_place": number | null + readonly "fourth_place": number | null + readonly "second_place": number | null + readonly "third_place": number | null + readonly "total": number | null + } + readonly "win_rate": number +} +export const UnifiedBenchmarksDAItem = Schema.Struct({ + "arena": Schema.String.annotate({ "description": "Arena this ranking belongs to." }), + "avg_generation_time_ms": Schema.Union([ + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), + Schema.Null + ]).annotate({ "description": "Average generation time in milliseconds.", "format": "double" }), + "category": Schema.String.annotate({ "description": "Category within the arena." }), + "display_name": Schema.String.annotate({ "description": "Human-readable model name from Design Arena." }), + "elo": Schema.Number.annotate({ "description": "ELO rating from head-to-head arena battles.", "format": "double" }) + .check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "model_permaslug": Schema.String.annotate({ + "description": "Stable OpenRouter model identifier when mapped; otherwise the upstream Design Arena model id." + }), + "pricing": UnifiedBenchmarkPricing, + "source": Schema.Literal("design-arena").annotate({ "description": "Benchmark source discriminator." }), + "tournament_stats": Schema.Struct({ + "first_place": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]), + "fourth_place": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]), + "second_place": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]), + "third_place": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]), + "total": Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]) + }).annotate({ "description": "Placement distribution from tournament matches." }), + "win_rate": Schema.Number.annotate({ "description": "Win rate as a percentage (0–100).", "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ) +}).annotate({ "identifier": "UnifiedBenchmarksDAItem" }) +export type UnprocessableEntityResponse = { + readonly "error": UnprocessableEntityResponseErrorData + readonly "openrouter_metadata"?: { readonly [x: string]: Schema.Json } | null + readonly "user_id"?: string | null +} +export const UnprocessableEntityResponse = Schema.Struct({ + "error": UnprocessableEntityResponseErrorData, + "openrouter_metadata": Schema.optionalKey( + Schema.Union([Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })), Schema.Null]) + ), + "user_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) +}).annotate({ + "description": "Unprocessable Entity - Semantic validation failure", + "identifier": "UnprocessableEntityResponse" +}) +export type OpenAIResponsesAnnotation = FileCitation | URLCitation | FilePath +export const OpenAIResponsesAnnotation = Schema.Union([FileCitation, URLCitation, FilePath]).annotate({ + "identifier": "OpenAIResponsesAnnotation" +}) +export type VideoGenerationResponse = { + readonly "error"?: string + readonly "generation_id"?: string + readonly "id": string + readonly "polling_url": string + readonly "status": "pending" | "in_progress" | "completed" | "failed" | "cancelled" | "expired" + readonly "unsigned_urls"?: ReadonlyArray + readonly "usage"?: VideoGenerationUsage +} +export const VideoGenerationResponse = Schema.Struct({ + "error": Schema.optionalKey(Schema.String), + "generation_id": Schema.optionalKey( + Schema.String.annotate({ + "description": + "The generation ID associated with this video generation job. Available once the job has been processed." + }) + ), + "id": Schema.String, + "polling_url": Schema.String, + "status": Schema.Literals(["pending", "in_progress", "completed", "failed", "cancelled", "expired"]), + "unsigned_urls": Schema.optionalKey(Schema.Array(Schema.String)), + "usage": Schema.optionalKey(VideoGenerationUsage) +}).annotate({ "identifier": "VideoGenerationResponse" }) +export type VideoModelsListResponse = { readonly "data": ReadonlyArray } +export const VideoModelsListResponse = Schema.Struct({ "data": Schema.Array(VideoModel) }).annotate({ + "identifier": "VideoModelsListResponse" +}) +export type WebFetchServerToolConfig = { + readonly "allowed_domains"?: Arrays_13 + readonly "blocked_domains"?: Arrays_14 + readonly "engine"?: WebFetchEngineEnum + readonly "max_content_tokens"?: number + readonly "max_uses"?: number +} +export const WebFetchServerToolConfig = Schema.Struct({ + "allowed_domains": Schema.optionalKey(Arrays_13), + "blocked_domains": Schema.optionalKey(Arrays_14), + "engine": Schema.optionalKey(WebFetchEngineEnum), + "max_content_tokens": Schema.optionalKey( + Schema.Number.annotate({ + "description": "Maximum content length in approximate tokens. Content exceeding this limit is truncated." + }).check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "max_uses": Schema.optionalKey( + Schema.Number.annotate({ + "description": "Maximum number of web fetches per request. Once exceeded, the tool returns an error." + }).check(Schema.isInt().annotate({ "expected": "an integer" })) + ) +}).annotate({ + "description": "Configuration for the openrouter:web_fetch server tool", + "identifier": "WebFetchServerToolConfig" +}) +export type WebSearchDomainFilter = Objects_152 | null +export const WebSearchDomainFilter = Schema.Union([Objects_152, Schema.Null]).annotate({ + "identifier": "WebSearchDomainFilter" +}) +export type WebSearchPlugin = { + readonly "enabled"?: boolean + readonly "engine"?: WebSearchEngine + readonly "exclude_domains"?: ReadonlyArray + readonly "id": "web" + readonly "include_domains"?: ReadonlyArray + readonly "max_results"?: number + readonly "max_uses"?: number + readonly "search_prompt"?: string + readonly "user_location"?: { + readonly "city"?: string | null + readonly "country"?: string | null + readonly "region"?: string | null + readonly "timezone"?: string | null + readonly "type": "approximate" + readonly [x: string]: Schema.Json + } +} +export const WebSearchPlugin = Schema.Struct({ + "enabled": Schema.optionalKey( + Schema.Boolean.annotate({ + "description": "Set to false to disable the web-search plugin for this request. Defaults to true." + }) + ), + "engine": Schema.optionalKey(WebSearchEngine), + "exclude_domains": Schema.optionalKey( + Schema.Array(Schema.String).annotate({ + "description": + "A list of domains to exclude from web search results. Supports wildcards (e.g. \"*.substack.com\") and path filtering (e.g. \"openai.com/blog\")." + }) + ), + "id": Schema.Literal("web"), + "include_domains": Schema.optionalKey( + Schema.Array(Schema.String).annotate({ + "description": + "A list of domains to restrict web search results to. Supports wildcards (e.g. \"*.substack.com\") and path filtering (e.g. \"openai.com/blog\")." + }) + ), + "max_results": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "max_uses": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Maximum number of times the model can invoke web search in a single turn. Passed through to native providers that support it (e.g. Anthropic)." + }).check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "search_prompt": Schema.optionalKey(Schema.String), + "user_location": Schema.optionalKey( + Schema.Union([ + Schema.StructWithRest( + Schema.Struct({ + "city": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "country": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "region": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "timezone": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "type": Schema.Literal("approximate") }), - withResponse(options?.config)(HttpClientResponse.matchStatus({ - "2xx": decodeSuccess(List200), - "401": decodeError("List401", List401), - "429": decodeError("List429", List429), - "500": decodeError("List500", List500), - orElse: unexpectedStatus - })) - ), - "createKeys": (options) => - HttpClientRequest.post(`/keys`).pipe( - HttpClientRequest.bodyJsonUnsafe(options.payload), - withResponse(options.config)(HttpClientResponse.matchStatus({ - "2xx": decodeSuccess(CreateKeys201), - "400": decodeError("CreateKeys400", CreateKeys400), - "401": decodeError("CreateKeys401", CreateKeys401), - "429": decodeError("CreateKeys429", CreateKeys429), - "500": decodeError("CreateKeys500", CreateKeys500), - orElse: unexpectedStatus - })) + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] + ).annotate({ + "description": + "Approximate user location for location-biased search results. Passed through to native providers that support it (e.g. Anthropic)." + }) + ]).annotate({ "description": "User location information for web search" }) + ) +}).annotate({ "identifier": "WebSearchPlugin" }) +export type OutputFileSearchCallItem = { + readonly "id": string + readonly "queries": ReadonlyArray + readonly "status": WebSearchStatus + readonly "type": "file_search_call" +} +export const OutputFileSearchCallItem = Schema.Struct({ + "id": Schema.String, + "queries": Schema.Array(Schema.String), + "status": WebSearchStatus, + "type": Schema.Literal("file_search_call") +}).annotate({ "identifier": "OutputFileSearchCallItem" }) +export type OutputItemFileSearchCall = { + readonly "id": string + readonly "queries": ReadonlyArray + readonly "status": WebSearchStatus + readonly "type": "file_search_call" +} +export const OutputItemFileSearchCall = Schema.Struct({ + "id": Schema.String, + "queries": Schema.Array(Schema.String), + "status": WebSearchStatus, + "type": Schema.Literal("file_search_call") +}).annotate({ "identifier": "OutputItemFileSearchCall" }) +export type OutputItemWebSearchCall = { + readonly "action"?: + | { + readonly "queries"?: ReadonlyArray + readonly "query": string + readonly "sources"?: ReadonlyArray + readonly "type": "search" + } + | { readonly "type": "open_page"; readonly "url"?: string | null } + | { readonly "pattern": string; readonly "type": "find_in_page"; readonly "url": string } + readonly "id": string + readonly "status": WebSearchStatus + readonly "type": "web_search_call" +} +export const OutputItemWebSearchCall = Schema.Struct({ + "action": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "query": Schema.String, + "sources": Schema.optionalKey(Schema.Array(WebSearchSource)), + "type": Schema.Literal("search") + }), + Schema.Struct({ + "type": Schema.Literal("open_page"), + "url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }), + Schema.Struct({ "pattern": Schema.String, "type": Schema.Literal("find_in_page"), "url": Schema.String }) + ], { mode: "oneOf" }) + ), + "id": Schema.String, + "status": WebSearchStatus, + "type": Schema.Literal("web_search_call") +}).annotate({ "identifier": "OutputItemWebSearchCall" }) +export type OutputWebSearchCallItem = { + readonly "action"?: + | { + readonly "queries"?: ReadonlyArray + readonly "query": string + readonly "sources"?: ReadonlyArray + readonly "type": "search" + } + | { readonly "type": "open_page"; readonly "url"?: string | null } + | { readonly "pattern": string; readonly "type": "find_in_page"; readonly "url": string } + readonly "id": string + readonly "status": WebSearchStatus + readonly "type": "web_search_call" +} +export const OutputWebSearchCallItem = Schema.Struct({ + "action": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "query": Schema.String, + "sources": Schema.optionalKey(Schema.Array(WebSearchSource)), + "type": Schema.Literal("search") + }), + Schema.Struct({ + "type": Schema.Literal("open_page"), + "url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }), + Schema.Struct({ "pattern": Schema.String, "type": Schema.Literal("find_in_page"), "url": Schema.String }) + ], { mode: "oneOf" }) + ), + "id": Schema.String, + "status": WebSearchStatus, + "type": Schema.Literal("web_search_call") +}).annotate({ "identifier": "OutputWebSearchCallItem" }) +export type WebSearchUserLocation = Objects_153 | null +export const WebSearchUserLocation = Schema.Union([Objects_153, Schema.Null]).annotate({ + "description": "User location information for web search", + "identifier": "WebSearchUserLocation" +}) +export type WebSearchConfig = { + readonly "allowed_domains"?: Arrays_15 + readonly "engine"?: WebSearchEngineEnum + readonly "excluded_domains"?: Arrays_16 + readonly "max_characters"?: number + readonly "max_results"?: number + readonly "max_total_results"?: number + readonly "search_context_size"?: SearchQualityLevel + readonly "user_location"?: WebSearchUserLocationServerTool +} +export const WebSearchConfig = Schema.Struct({ + "allowed_domains": Schema.optionalKey(Arrays_15), + "engine": Schema.optionalKey(WebSearchEngineEnum), + "excluded_domains": Schema.optionalKey(Arrays_16), + "max_characters": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Exact maximum number of characters of content per search result. Applies to the Exa, Parallel, and Perplexity engines; ignored with native provider search and Firecrawl. For Exa, caps highlight content per result. For Parallel, caps excerpt content per result (default 1,500 when omitted). For Perplexity, maps to the native `max_tokens_per_page` parameter (converted from characters to tokens) and trims the response to the exact character cap. When both `max_characters` and `search_context_size` are set, `max_characters` takes precedence. When omitted, falls back to `search_context_size` mapping (Exa) or engine defaults (Parallel, Perplexity)." + }).check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "max_results": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Maximum number of search results to return per search call. Defaults to 5. Applies to Exa, Firecrawl, Parallel, and Perplexity engines; ignored with native provider search. Perplexity supports a maximum of 20; values above 20 are clamped." + }).check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "max_total_results": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Maximum total number of search results across all search calls in a single request. Once this limit is reached, the tool will stop returning new results. Useful for controlling cost and context size in agentic loops. Defaults to 50 when not specified." + }).check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "search_context_size": Schema.optionalKey(SearchQualityLevel), + "user_location": Schema.optionalKey(WebSearchUserLocationServerTool) +}).annotate({ "identifier": "WebSearchConfig" }) +export type WebSearchServerToolConfig = { + readonly "allowed_domains"?: Arrays_17 + readonly "engine"?: WebSearchEngineEnum + readonly "excluded_domains"?: Arrays_18 + readonly "max_characters"?: number + readonly "max_results"?: number + readonly "max_total_results"?: number + readonly "search_context_size"?: SearchQualityLevel + readonly "user_location"?: WebSearchUserLocationServerTool +} +export const WebSearchServerToolConfig = Schema.Struct({ + "allowed_domains": Schema.optionalKey(Arrays_17), + "engine": Schema.optionalKey(WebSearchEngineEnum), + "excluded_domains": Schema.optionalKey(Arrays_18), + "max_characters": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Exact maximum number of characters of content per search result. Applies to the Exa, Parallel, and Perplexity engines; ignored with native provider search and Firecrawl. For Exa, caps highlight content per result. For Parallel, caps excerpt content per result (default 1,500 when omitted). For Perplexity, maps to the native `max_tokens_per_page` parameter (converted from characters to tokens) and trims the response to the exact character cap. When both `max_characters` and `search_context_size` are set, `max_characters` takes precedence. When omitted, falls back to `search_context_size` mapping (Exa) or engine defaults (Parallel, Perplexity)." + }).check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "max_results": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Maximum number of search results to return per search call. Defaults to 5. Applies to Exa, Firecrawl, Parallel, and Perplexity engines; ignored with native provider search. Perplexity supports a maximum of 20; values above 20 are clamped." + }).check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "max_total_results": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Maximum total number of search results across all search calls in a single request. Once this limit is reached, the tool will stop returning new results. Useful for controlling cost and context size in agentic loops. Defaults to 50 when not specified." + }).check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "search_context_size": Schema.optionalKey(SearchQualityLevel), + "user_location": Schema.optionalKey(WebSearchUserLocationServerTool) +}).annotate({ + "description": "Configuration for the openrouter:web_search server tool", + "identifier": "WebSearchServerToolConfig" +}) +export type CreateWorkspaceResponse = { readonly "data": Workspace } +export const CreateWorkspaceResponse = Schema.Struct({ + "data": Schema.suspend((): Schema.Codec => Workspace).annotate({ "description": "The created workspace" }) +}).annotate({ "identifier": "CreateWorkspaceResponse" }) +export type GetWorkspaceResponse = { readonly "data": Workspace } +export const GetWorkspaceResponse = Schema.Struct({ + "data": Schema.suspend((): Schema.Codec => Workspace).annotate({ "description": "The workspace" }) +}).annotate({ "identifier": "GetWorkspaceResponse" }) +export type ListWorkspacesResponse = { readonly "data": ReadonlyArray; readonly "total_count": number } +export const ListWorkspacesResponse = Schema.Struct({ + "data": Schema.Array(Workspace).annotate({ "description": "List of workspaces" }), + "total_count": Schema.Number.annotate({ "description": "Total number of workspaces" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ) +}).annotate({ "identifier": "ListWorkspacesResponse" }) +export type UpdateWorkspaceResponse = { readonly "data": Workspace } +export const UpdateWorkspaceResponse = Schema.Struct({ + "data": Schema.suspend((): Schema.Codec => Workspace).annotate({ "description": "The updated workspace" }) +}).annotate({ "identifier": "UpdateWorkspaceResponse" }) +export type ListWorkspaceBudgetsResponse = { readonly "data": ReadonlyArray } +export const ListWorkspaceBudgetsResponse = Schema.Struct({ + "data": Schema.Array(WorkspaceBudget).annotate({ "description": "List of budgets configured for the workspace" }) +}).annotate({ "identifier": "ListWorkspaceBudgetsResponse" }) +export type UpsertWorkspaceBudgetResponse = { readonly "data": WorkspaceBudget } +export const UpsertWorkspaceBudgetResponse = Schema.Struct({ + "data": Schema.suspend((): Schema.Codec => WorkspaceBudget).annotate({ + "description": "The created or updated budget" + }) +}).annotate({ "identifier": "UpsertWorkspaceBudgetResponse" }) +export type BulkAddWorkspaceMembersResponse = { + readonly "added_count": number + readonly "data": ReadonlyArray +} +export const BulkAddWorkspaceMembersResponse = Schema.Struct({ + "added_count": Schema.Number.annotate({ "description": "Number of workspace memberships created or updated" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ), + "data": Schema.Array(WorkspaceMember).annotate({ "description": "List of added workspace memberships" }) +}).annotate({ "identifier": "BulkAddWorkspaceMembersResponse" }) +export type ListWorkspaceMembersResponse = { + readonly "data": ReadonlyArray + readonly "total_count": number +} +export const ListWorkspaceMembersResponse = Schema.Struct({ + "data": Schema.Array(WorkspaceMember).annotate({ "description": "List of workspace members" }), + "total_count": Schema.Number.annotate({ "description": "Total number of members in the workspace" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ) +}).annotate({ "identifier": "ListWorkspaceMembersResponse" }) +export type AdvisorServerToolConfig = { + readonly "forward_transcript"?: boolean + readonly "instructions"?: string + readonly "max_completion_tokens"?: number + readonly "max_tool_calls"?: number + readonly "model"?: string + readonly "name"?: string + readonly "reasoning"?: AdvisorReasoning + readonly "stream"?: boolean + readonly "temperature"?: number + readonly "tools"?: Arrays_ +} +export const AdvisorServerToolConfig = Schema.Struct({ + "forward_transcript": Schema.optionalKey(Schema.Boolean.annotate({ + "description": + "When true, the full parent conversation is forwarded to the advisor so it sees the same context the executor does (and the tool-call `prompt`, if given, is appended as a final user turn). When false or omitted, the advisor receives only the `prompt` the executor passes in the tool call." + })), + "instructions": Schema.optionalKey( + Schema.String.annotate({ + "description": + "System instructions for the advisor sub-agent. When omitted, the advisor responds with no system prompt of its own." + }) + ), + "max_completion_tokens": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Maximum number of output tokens (including reasoning) the advisor may produce. When omitted, the provider's default applies." + }).check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "max_tool_calls": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Maximum number of tool-calling steps the advisor sub-agent may take during its agentic loop. Capped at 25. Only relevant when the advisor is given tools." + }).check(Schema.isInt().annotate({ "expected": "an integer" })).check( + Schema.isGreaterThanOrEqualTo(1).annotate({ "expected": "a value greater than or equal to 1" }) + ).check(Schema.isLessThanOrEqualTo(25).annotate({ "expected": "a value less than or equal to 25" })) + ), + "model": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Slug of the advisor model to consult (any OpenRouter model). When omitted, the executor can choose it via the tool call's `model` argument; if neither is set, the model from the outer API request is used. The advisor tool itself cannot be the advisor model." + }) + ), + "name": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Optional name for this advisor. The model sees one tool per named advisor (and one default for an unnamed entry). Names must be unique across advisor entries. Letters, digits, spaces, underscores, and dashes; trimmed; 1–64 chars." + }).check(Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" })).check( + Schema.isMaxLength(64).annotate({ "expected": "a value with a length of at most 64" }) + ).check( + Schema.isPattern(new RegExp("^[a-zA-Z0-9 _-]+$")).annotate({ + "expected": "a string matching the RegExp ^[a-zA-Z0-9 _-]+$" + }) + ) + ), + "reasoning": Schema.optionalKey(AdvisorReasoning), + "stream": Schema.optionalKey(Schema.Boolean.annotate({ + "description": + "When true, the advisor's advice streams incrementally as it is produced. In the Responses API this emits `response.output_text.delta` events targeting the advisor output item; the final `advice` field is still set on the completed item. Has no effect on the Chat Completions API (where the advice arrives only as the final tool result). When false or omitted, the advice arrives only as the final result." + })), + "temperature": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Sampling temperature forwarded to the advisor call. When omitted, the provider's default applies.", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })) + ), + "tools": Schema.optionalKey(Arrays_) +}).annotate({ + "description": "Configuration for one openrouter:advisor server tool entry.", + "identifier": "AdvisorServerToolConfig" +}) +export type AnthropicBashCodeExecutionContent = + | AnthropicBashCodeExecutionToolResultError + | AnthropicBashCodeExecutionResult +export const AnthropicBashCodeExecutionContent = Schema.Union([ + AnthropicBashCodeExecutionToolResultError, + AnthropicBashCodeExecutionResult +], { mode: "oneOf" }).annotate({ "identifier": "AnthropicBashCodeExecutionContent" }) +export type AnthropicTextBlockParam = { + readonly "cache_control"?: AnthropicCacheControlDirective + readonly "citations"?: + | ReadonlyArray< + | AnthropicCitationCharLocationParam + | AnthropicCitationPageLocationParam + | AnthropicCitationContentBlockLocationParam + | AnthropicCitationWebSearchResultLocationParam + | AnthropicCitationSearchResultLocationParam + > + | null + readonly "text": string + readonly "type": "text" +} +export const AnthropicTextBlockParam = Schema.Struct({ + "cache_control": Schema.optionalKey(AnthropicCacheControlDirective), + "citations": Schema.optionalKey( + Schema.Union([ + Schema.Array( + Schema.Union([ + AnthropicCitationCharLocationParam, + AnthropicCitationPageLocationParam, + AnthropicCitationContentBlockLocationParam, + AnthropicCitationWebSearchResultLocationParam, + AnthropicCitationSearchResultLocationParam + ], { mode: "oneOf" }) ), - "getKey": (hash, options) => - HttpClientRequest.get(`/keys/${hash}`).pipe( - withResponse(options?.config)(HttpClientResponse.matchStatus({ - "2xx": decodeSuccess(GetKey200), - "401": decodeError("GetKey401", GetKey401), - "404": decodeError("GetKey404", GetKey404), - "429": decodeError("GetKey429", GetKey429), - "500": decodeError("GetKey500", GetKey500), - orElse: unexpectedStatus - })) - ), - "deleteKeys": (hash, options) => - HttpClientRequest.delete(`/keys/${hash}`).pipe( - withResponse(options?.config)(HttpClientResponse.matchStatus({ - "2xx": decodeSuccess(DeleteKeys200), - "401": decodeError("DeleteKeys401", DeleteKeys401), - "404": decodeError("DeleteKeys404", DeleteKeys404), - "429": decodeError("DeleteKeys429", DeleteKeys429), - "500": decodeError("DeleteKeys500", DeleteKeys500), - orElse: unexpectedStatus - })) - ), - "updateKeys": (hash, options) => - HttpClientRequest.patch(`/keys/${hash}`).pipe( - HttpClientRequest.bodyJsonUnsafe(options.payload), - withResponse(options.config)(HttpClientResponse.matchStatus({ - "2xx": decodeSuccess(UpdateKeys200), - "400": decodeError("UpdateKeys400", UpdateKeys400), - "401": decodeError("UpdateKeys401", UpdateKeys401), - "404": decodeError("UpdateKeys404", UpdateKeys404), - "429": decodeError("UpdateKeys429", UpdateKeys429), - "500": decodeError("UpdateKeys500", UpdateKeys500), - orElse: unexpectedStatus - })) - ), - "listGuardrails": (options) => - HttpClientRequest.get(`/guardrails`).pipe( - HttpClientRequest.setUrlParams({ - "offset": options?.params?.["offset"] as any, - "limit": options?.params?.["limit"] as any - }), - withResponse(options?.config)(HttpClientResponse.matchStatus({ - "2xx": decodeSuccess(ListGuardrails200), - "401": decodeError("ListGuardrails401", ListGuardrails401), - "500": decodeError("ListGuardrails500", ListGuardrails500), - orElse: unexpectedStatus - })) - ), - "createGuardrail": (options) => - HttpClientRequest.post(`/guardrails`).pipe( - HttpClientRequest.bodyJsonUnsafe(options.payload), - withResponse(options.config)(HttpClientResponse.matchStatus({ - "2xx": decodeSuccess(CreateGuardrail201), - "400": decodeError("CreateGuardrail400", CreateGuardrail400), - "401": decodeError("CreateGuardrail401", CreateGuardrail401), - "500": decodeError("CreateGuardrail500", CreateGuardrail500), - orElse: unexpectedStatus - })) - ), - "getGuardrail": (id, options) => - HttpClientRequest.get(`/guardrails/${id}`).pipe( - withResponse(options?.config)(HttpClientResponse.matchStatus({ - "2xx": decodeSuccess(GetGuardrail200), - "401": decodeError("GetGuardrail401", GetGuardrail401), - "404": decodeError("GetGuardrail404", GetGuardrail404), - "500": decodeError("GetGuardrail500", GetGuardrail500), - orElse: unexpectedStatus - })) - ), - "deleteGuardrail": (id, options) => - HttpClientRequest.delete(`/guardrails/${id}`).pipe( - withResponse(options?.config)(HttpClientResponse.matchStatus({ - "2xx": decodeSuccess(DeleteGuardrail200), - "401": decodeError("DeleteGuardrail401", DeleteGuardrail401), - "404": decodeError("DeleteGuardrail404", DeleteGuardrail404), - "500": decodeError("DeleteGuardrail500", DeleteGuardrail500), - orElse: unexpectedStatus - })) - ), - "updateGuardrail": (id, options) => - HttpClientRequest.patch(`/guardrails/${id}`).pipe( - HttpClientRequest.bodyJsonUnsafe(options.payload), - withResponse(options.config)(HttpClientResponse.matchStatus({ - "2xx": decodeSuccess(UpdateGuardrail200), - "400": decodeError("UpdateGuardrail400", UpdateGuardrail400), - "401": decodeError("UpdateGuardrail401", UpdateGuardrail401), - "404": decodeError("UpdateGuardrail404", UpdateGuardrail404), - "500": decodeError("UpdateGuardrail500", UpdateGuardrail500), - orElse: unexpectedStatus - })) - ), - "listKeyAssignments": (options) => - HttpClientRequest.get(`/guardrails/assignments/keys`).pipe( - HttpClientRequest.setUrlParams({ - "offset": options?.params?.["offset"] as any, - "limit": options?.params?.["limit"] as any - }), - withResponse(options?.config)(HttpClientResponse.matchStatus({ - "2xx": decodeSuccess(ListKeyAssignments200), - "401": decodeError("ListKeyAssignments401", ListKeyAssignments401), - "500": decodeError("ListKeyAssignments500", ListKeyAssignments500), - orElse: unexpectedStatus - })) - ), - "listMemberAssignments": (options) => - HttpClientRequest.get(`/guardrails/assignments/members`).pipe( - HttpClientRequest.setUrlParams({ - "offset": options?.params?.["offset"] as any, - "limit": options?.params?.["limit"] as any - }), - withResponse(options?.config)(HttpClientResponse.matchStatus({ - "2xx": decodeSuccess(ListMemberAssignments200), - "401": decodeError("ListMemberAssignments401", ListMemberAssignments401), - "500": decodeError("ListMemberAssignments500", ListMemberAssignments500), - orElse: unexpectedStatus - })) - ), - "listGuardrailKeyAssignments": (id, options) => - HttpClientRequest.get(`/guardrails/${id}/assignments/keys`).pipe( - HttpClientRequest.setUrlParams({ - "offset": options?.params?.["offset"] as any, - "limit": options?.params?.["limit"] as any - }), - withResponse(options?.config)(HttpClientResponse.matchStatus({ - "2xx": decodeSuccess(ListGuardrailKeyAssignments200), - "401": decodeError("ListGuardrailKeyAssignments401", ListGuardrailKeyAssignments401), - "404": decodeError("ListGuardrailKeyAssignments404", ListGuardrailKeyAssignments404), - "500": decodeError("ListGuardrailKeyAssignments500", ListGuardrailKeyAssignments500), - orElse: unexpectedStatus - })) - ), - "bulkAssignKeysToGuardrail": (id, options) => - HttpClientRequest.post(`/guardrails/${id}/assignments/keys`).pipe( - HttpClientRequest.bodyJsonUnsafe(options.payload), - withResponse(options.config)(HttpClientResponse.matchStatus({ - "2xx": decodeSuccess(BulkAssignKeysToGuardrail200), - "400": decodeError("BulkAssignKeysToGuardrail400", BulkAssignKeysToGuardrail400), - "401": decodeError("BulkAssignKeysToGuardrail401", BulkAssignKeysToGuardrail401), - "404": decodeError("BulkAssignKeysToGuardrail404", BulkAssignKeysToGuardrail404), - "500": decodeError("BulkAssignKeysToGuardrail500", BulkAssignKeysToGuardrail500), - orElse: unexpectedStatus - })) - ), - "listGuardrailMemberAssignments": (id, options) => - HttpClientRequest.get(`/guardrails/${id}/assignments/members`).pipe( - HttpClientRequest.setUrlParams({ - "offset": options?.params?.["offset"] as any, - "limit": options?.params?.["limit"] as any - }), - withResponse(options?.config)(HttpClientResponse.matchStatus({ - "2xx": decodeSuccess(ListGuardrailMemberAssignments200), - "401": decodeError("ListGuardrailMemberAssignments401", ListGuardrailMemberAssignments401), - "404": decodeError("ListGuardrailMemberAssignments404", ListGuardrailMemberAssignments404), - "500": decodeError("ListGuardrailMemberAssignments500", ListGuardrailMemberAssignments500), - orElse: unexpectedStatus - })) - ), - "bulkAssignMembersToGuardrail": (id, options) => - HttpClientRequest.post(`/guardrails/${id}/assignments/members`).pipe( - HttpClientRequest.bodyJsonUnsafe(options.payload), - withResponse(options.config)(HttpClientResponse.matchStatus({ - "2xx": decodeSuccess(BulkAssignMembersToGuardrail200), - "400": decodeError("BulkAssignMembersToGuardrail400", BulkAssignMembersToGuardrail400), - "401": decodeError("BulkAssignMembersToGuardrail401", BulkAssignMembersToGuardrail401), - "404": decodeError("BulkAssignMembersToGuardrail404", BulkAssignMembersToGuardrail404), - "500": decodeError("BulkAssignMembersToGuardrail500", BulkAssignMembersToGuardrail500), - orElse: unexpectedStatus - })) - ), - "bulkUnassignKeysFromGuardrail": (id, options) => - HttpClientRequest.post(`/guardrails/${id}/assignments/keys/remove`).pipe( - HttpClientRequest.bodyJsonUnsafe(options.payload), - withResponse(options.config)(HttpClientResponse.matchStatus({ - "2xx": decodeSuccess(BulkUnassignKeysFromGuardrail200), - "400": decodeError("BulkUnassignKeysFromGuardrail400", BulkUnassignKeysFromGuardrail400), - "401": decodeError("BulkUnassignKeysFromGuardrail401", BulkUnassignKeysFromGuardrail401), - "404": decodeError("BulkUnassignKeysFromGuardrail404", BulkUnassignKeysFromGuardrail404), - "500": decodeError("BulkUnassignKeysFromGuardrail500", BulkUnassignKeysFromGuardrail500), - orElse: unexpectedStatus - })) - ), - "bulkUnassignMembersFromGuardrail": (id, options) => - HttpClientRequest.post(`/guardrails/${id}/assignments/members/remove`).pipe( - HttpClientRequest.bodyJsonUnsafe(options.payload), - withResponse(options.config)(HttpClientResponse.matchStatus({ - "2xx": decodeSuccess(BulkUnassignMembersFromGuardrail200), - "400": decodeError("BulkUnassignMembersFromGuardrail400", BulkUnassignMembersFromGuardrail400), - "401": decodeError("BulkUnassignMembersFromGuardrail401", BulkUnassignMembersFromGuardrail401), - "404": decodeError("BulkUnassignMembersFromGuardrail404", BulkUnassignMembersFromGuardrail404), - "500": decodeError("BulkUnassignMembersFromGuardrail500", BulkUnassignMembersFromGuardrail500), - orElse: unexpectedStatus - })) - ), - "getCurrentKey": (options) => - HttpClientRequest.get(`/key`).pipe( - withResponse(options?.config)(HttpClientResponse.matchStatus({ - "2xx": decodeSuccess(GetCurrentKey200), - "401": decodeError("GetCurrentKey401", GetCurrentKey401), - "500": decodeError("GetCurrentKey500", GetCurrentKey500), - orElse: unexpectedStatus - })) - ), - "exchangeAuthCodeForAPIKey": (options) => - HttpClientRequest.post(`/auth/keys`).pipe( - HttpClientRequest.bodyJsonUnsafe(options.payload), - withResponse(options.config)(HttpClientResponse.matchStatus({ - "2xx": decodeSuccess(ExchangeAuthCodeForAPIKey200), - "400": decodeError("ExchangeAuthCodeForAPIKey400", ExchangeAuthCodeForAPIKey400), - "403": decodeError("ExchangeAuthCodeForAPIKey403", ExchangeAuthCodeForAPIKey403), - "500": decodeError("ExchangeAuthCodeForAPIKey500", ExchangeAuthCodeForAPIKey500), - orElse: unexpectedStatus - })) - ), - "createAuthKeysCode": (options) => - HttpClientRequest.post(`/auth/keys/code`).pipe( - HttpClientRequest.bodyJsonUnsafe(options.payload), - withResponse(options.config)(HttpClientResponse.matchStatus({ - "2xx": decodeSuccess(CreateAuthKeysCode200), - "400": decodeError("CreateAuthKeysCode400", CreateAuthKeysCode400), - "401": decodeError("CreateAuthKeysCode401", CreateAuthKeysCode401), - "500": decodeError("CreateAuthKeysCode500", CreateAuthKeysCode500), - orElse: unexpectedStatus - })) - ), - "sendChatCompletionRequest": (options) => - HttpClientRequest.post(`/chat/completions`).pipe( - HttpClientRequest.bodyJsonUnsafe(options.payload), - withResponse(options.config)(HttpClientResponse.matchStatus({ - "2xx": decodeSuccess(SendChatCompletionRequest200), - "400": decodeError("SendChatCompletionRequest400", SendChatCompletionRequest400), - "401": decodeError("SendChatCompletionRequest401", SendChatCompletionRequest401), - "429": decodeError("SendChatCompletionRequest429", SendChatCompletionRequest429), - "500": decodeError("SendChatCompletionRequest500", SendChatCompletionRequest500), - orElse: unexpectedStatus - })) - ), - "sendChatCompletionRequestSse": (options) => - HttpClientRequest.post(`/chat/completions`).pipe( - HttpClientRequest.bodyJsonUnsafe(options.payload), - sseRequest(SendChatCompletionRequest200Sse) - ) - } + Schema.Null + ]) + ), + "text": Schema.String, + "type": Schema.Literal("text") +}).annotate({ "identifier": "AnthropicTextBlockParam" }) +export type AnthropicToolSearchToolBm25 = { + readonly "allowed_callers"?: AnthropicAllowedCallers + readonly "cache_control"?: AnthropicCacheControlDirective + readonly "defer_loading"?: boolean + readonly "name": "tool_search_tool_bm25" + readonly "strict"?: boolean + readonly "type": "tool_search_tool_bm25_20251119" | "tool_search_tool_bm25" } - -export interface OpenRouterClient { - readonly httpClient: HttpClient.HttpClient +export const AnthropicToolSearchToolBm25 = Schema.Struct({ + "allowed_callers": Schema.optionalKey(AnthropicAllowedCallers), + "cache_control": Schema.optionalKey(AnthropicCacheControlDirective), + "defer_loading": Schema.optionalKey(Schema.Boolean), + "name": Schema.Literal("tool_search_tool_bm25"), + "strict": Schema.optionalKey(Schema.Boolean), + "type": Schema.Literals(["tool_search_tool_bm25_20251119", "tool_search_tool_bm25"]) +}).annotate({ "identifier": "AnthropicToolSearchToolBm25" }) +export type AnthropicToolSearchToolRegex = { + readonly "allowed_callers"?: AnthropicAllowedCallers + readonly "cache_control"?: AnthropicCacheControlDirective + readonly "defer_loading"?: boolean + readonly "name": "tool_search_tool_regex" + readonly "strict"?: boolean + readonly "type": "tool_search_tool_regex_20251119" | "tool_search_tool_regex" +} +export const AnthropicToolSearchToolRegex = Schema.Struct({ + "allowed_callers": Schema.optionalKey(AnthropicAllowedCallers), + "cache_control": Schema.optionalKey(AnthropicCacheControlDirective), + "defer_loading": Schema.optionalKey(Schema.Boolean), + "name": Schema.Literal("tool_search_tool_regex"), + "strict": Schema.optionalKey(Schema.Boolean), + "type": Schema.Literals(["tool_search_tool_regex_20251119", "tool_search_tool_regex"]) +}).annotate({ "identifier": "AnthropicToolSearchToolRegex" }) +export type FusionServerToolConfig = { + readonly "analysis_models"?: Arrays_6 + readonly "cache_control"?: AnthropicCacheControlDirective + readonly "max_completion_tokens"?: number + readonly "max_tool_calls"?: number + readonly "model"?: string + readonly "reasoning"?: Objects_9 + readonly "temperature"?: number + readonly "tools"?: Arrays_7 +} +export const FusionServerToolConfig = Schema.Struct({ + "analysis_models": Schema.optionalKey(Arrays_6), + "cache_control": Schema.optionalKey(AnthropicCacheControlDirective), + "max_completion_tokens": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Maximum number of output tokens (including reasoning tokens) each panelist and the judge model may produce per inner call. Controls the total output budget so reasoning-heavy models like GPT-5.5 do not exhaust their token allowance before producing visible text. When omitted, panelists default to 32000 and the judge to 20000." + }).check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "max_tool_calls": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Maximum number of tool-calling steps each panelist (analysis model) and the judge model may take during their agentic web-research loop. Models with web_search/web_fetch enabled iterate until they produce a text response or hit this ceiling. Defaults to 8. Capped at 16." + }).check(Schema.isInt().annotate({ "expected": "an integer" })).check( + Schema.isGreaterThanOrEqualTo(1).annotate({ "expected": "a value greater than or equal to 1" }) + ).check(Schema.isLessThanOrEqualTo(16).annotate({ "expected": "a value less than or equal to 16" })) + ), + "model": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Slug of the judge model that produces the structured analysis JSON. Defaults to the model used in the outer API request." + }) + ), + "reasoning": Schema.optionalKey(Objects_9), + "temperature": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Temperature forwarded to panelist inner calls. The judge always runs at temperature 0 regardless of this value. When omitted, the provider's default applies.", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })) + ), + "tools": Schema.optionalKey(Arrays_7) +}).annotate({ + "description": "Configuration for the openrouter:fusion server tool.", + "identifier": "FusionServerToolConfig" +}) +export type AnthropicUsage = { + readonly "cache_creation": AnthropicCacheCreation + readonly "cache_creation_input_tokens": number | null + readonly "cache_read_input_tokens": number | null + readonly "inference_geo": string | null + readonly "input_tokens": number + readonly "output_tokens": number + readonly "output_tokens_details": AnthropicOutputTokensDetails + readonly "server_tool_use": AnthropicServerToolUsage + readonly "service_tier": AnthropicServiceTier +} +export const AnthropicUsage = Schema.Struct({ + "cache_creation": AnthropicCacheCreation, + "cache_creation_input_tokens": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]), + "cache_read_input_tokens": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]), + "inference_geo": Schema.Union([Schema.String, Schema.Null]), + "input_tokens": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "output_tokens": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "output_tokens_details": AnthropicOutputTokensDetails, + "server_tool_use": AnthropicServerToolUsage, + "service_tier": AnthropicServiceTier +}).annotate({ "identifier": "AnthropicUsage" }) +export type AnthropicDocumentBlock = { + readonly "citations"?: AnthropicCitationsConfig + readonly "source": AnthropicBase64PdfSource | AnthropicPlainTextSource + readonly "title": string | null + readonly "type": "document" +} +export const AnthropicDocumentBlock = Schema.Struct({ + "citations": Schema.optionalKey(AnthropicCitationsConfig), + "source": Schema.Union([AnthropicBase64PdfSource, AnthropicPlainTextSource]), + "title": Schema.Union([Schema.String, Schema.Null]), + "type": Schema.Literal("document") +}).annotate({ "identifier": "AnthropicDocumentBlock" }) +export type AnthropicTextBlock = { + readonly "citations": ReadonlyArray | null + readonly "text": string + readonly "type": "text" +} +export const AnthropicTextBlock = Schema.Struct({ + "citations": Schema.Union([Schema.Array(AnthropicTextCitation), Schema.Null]), + "text": Schema.String, + "type": Schema.Literal("text") +}).annotate({ "identifier": "AnthropicTextBlock" }) +export type AnthropicToolUseBlock = { + readonly "caller": AnthropicCaller + readonly "id": string + readonly "input"?: Schema.Json + readonly "name": string + readonly "type": "tool_use" +} +export const AnthropicToolUseBlock = Schema.Struct({ + "caller": AnthropicCaller, + "id": Schema.String, + "input": Schema.optionalKey(Schema.Json.annotate({ "expected": "JSON value" })), + "name": Schema.String, + "type": Schema.Literal("tool_use") +}).annotate({ "identifier": "AnthropicToolUseBlock" }) +export type AnthropicWebSearchToolResult = { + readonly "caller": AnthropicCaller + readonly "content": ReadonlyArray | AnthropicWebSearchToolResultError + readonly "tool_use_id": string + readonly "type": "web_search_tool_result" +} +export const AnthropicWebSearchToolResult = Schema.Struct({ + "caller": AnthropicCaller, + "content": Schema.Union([Schema.Array(AnthropicWebSearchResult), AnthropicWebSearchToolResultError]), + "tool_use_id": Schema.String, + "type": Schema.Literal("web_search_tool_result") +}).annotate({ "identifier": "AnthropicWebSearchToolResult" }) +export type ORAnthropicServerToolUseBlock = { + readonly "caller"?: ORAnthropicNullableCaller + readonly "id": string + readonly "input"?: Schema.Json + readonly "name": string + readonly "type": "server_tool_use" +} +export const ORAnthropicServerToolUseBlock = Schema.Struct({ + "caller": Schema.optionalKey(ORAnthropicNullableCaller), + "id": Schema.String, + "input": Schema.optionalKey(Schema.Json.annotate({ "expected": "JSON value" })), + "name": Schema.String, + "type": Schema.Literal("server_tool_use") +}).annotate({ "identifier": "ORAnthropicServerToolUseBlock" }) +export type AnthropicImageBlockParam = { + readonly "cache_control"?: AnthropicCacheControlDirective + readonly "source": AnthropicBase64ImageSource | AnthropicUrlImageSource + readonly "type": "image" +} +export const AnthropicImageBlockParam = Schema.Struct({ + "cache_control": Schema.optionalKey(AnthropicCacheControlDirective), + "source": Schema.Union([AnthropicBase64ImageSource, AnthropicUrlImageSource], { mode: "oneOf" }), + "type": Schema.Literal("image") +}).annotate({ "identifier": "AnthropicImageBlockParam" }) +export type AnthropicAdvisorMessageUsageIteration = { + readonly "cache_creation"?: AnthropicIterationCacheCreation + readonly "cache_creation_input_tokens"?: number + readonly "cache_read_input_tokens"?: number + readonly "input_tokens"?: number + readonly "output_tokens"?: number + readonly "model": string + readonly "type": "advisor_message" +} +export const AnthropicAdvisorMessageUsageIteration = Schema.Struct({ + "cache_creation": Schema.optionalKey(AnthropicIterationCacheCreation), + "cache_creation_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "cache_read_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "input_tokens": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "output_tokens": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "model": Schema.String, + "type": Schema.Literal("advisor_message") +}).annotate({ "identifier": "AnthropicAdvisorMessageUsageIteration" }) +export type AnthropicBaseUsageIteration = { + readonly "cache_creation"?: AnthropicIterationCacheCreation + readonly "cache_creation_input_tokens"?: number + readonly "cache_read_input_tokens"?: number + readonly "input_tokens"?: number + readonly "output_tokens"?: number +} +export const AnthropicBaseUsageIteration = Schema.Struct({ + "cache_creation": Schema.optionalKey(AnthropicIterationCacheCreation), + "cache_creation_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "cache_read_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "input_tokens": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "output_tokens": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))) +}).annotate({ "identifier": "AnthropicBaseUsageIteration" }) +export type AnthropicCompactionUsageIteration = { + readonly "cache_creation"?: AnthropicIterationCacheCreation + readonly "cache_creation_input_tokens"?: number + readonly "cache_read_input_tokens"?: number + readonly "input_tokens"?: number + readonly "output_tokens"?: number + readonly "type": "compaction" +} +export const AnthropicCompactionUsageIteration = Schema.Struct({ + "cache_creation": Schema.optionalKey(AnthropicIterationCacheCreation), + "cache_creation_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "cache_read_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "input_tokens": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "output_tokens": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "type": Schema.Literal("compaction") +}).annotate({ "identifier": "AnthropicCompactionUsageIteration" }) +export type AnthropicMessageUsageIteration = { + readonly "cache_creation"?: AnthropicIterationCacheCreation + readonly "cache_creation_input_tokens"?: number + readonly "cache_read_input_tokens"?: number + readonly "input_tokens"?: number + readonly "output_tokens"?: number + readonly "model"?: string + readonly "type": "message" +} +export const AnthropicMessageUsageIteration = Schema.Struct({ + "cache_creation": Schema.optionalKey(AnthropicIterationCacheCreation), + "cache_creation_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "cache_read_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "input_tokens": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "output_tokens": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "model": Schema.optionalKey(Schema.String), + "type": Schema.Literal("message") +}).annotate({ "identifier": "AnthropicMessageUsageIteration" }) +export type AnthropicUnknownUsageIteration = { + readonly "cache_creation"?: AnthropicIterationCacheCreation + readonly "cache_creation_input_tokens"?: number + readonly "cache_read_input_tokens"?: number + readonly "input_tokens"?: number + readonly "output_tokens"?: number + readonly "type": string +} +export const AnthropicUnknownUsageIteration = Schema.Struct({ + "cache_creation": Schema.optionalKey(AnthropicIterationCacheCreation), + "cache_creation_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "cache_read_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "input_tokens": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "output_tokens": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "type": Schema.String +}).annotate({ "identifier": "AnthropicUnknownUsageIteration" }) +export type AnthropicCodeExecutionContent = + | AnthropicCodeExecutionToolResultError + | AnthropicCodeExecutionResult + | AnthropicEncryptedCodeExecutionResult +export const AnthropicCodeExecutionContent = Schema.Union([ + AnthropicCodeExecutionToolResultError, + AnthropicCodeExecutionResult, + AnthropicEncryptedCodeExecutionResult +], { mode: "oneOf" }).annotate({ "identifier": "AnthropicCodeExecutionContent" }) +export type AnthropicTextEditorCodeExecutionToolResult = { + readonly "content": AnthropicTextEditorCodeExecutionContent + readonly "tool_use_id": string + readonly "type": "text_editor_code_execution_tool_result" +} +export const AnthropicTextEditorCodeExecutionToolResult = Schema.Struct({ + "content": AnthropicTextEditorCodeExecutionContent, + "tool_use_id": Schema.String, + "type": Schema.Literal("text_editor_code_execution_tool_result") +}).annotate({ "identifier": "AnthropicTextEditorCodeExecutionToolResult" }) +export type AnthropicToolSearchContent = AnthropicToolSearchResultError | AnthropicToolSearchResult +export const AnthropicToolSearchContent = Schema.Union([AnthropicToolSearchResultError, AnthropicToolSearchResult], { + mode: "oneOf" +}).annotate({ "identifier": "AnthropicToolSearchContent" }) +export type MessagesErrorResponse = { readonly "error": MessagesErrorDetail; readonly "type": "error" } +export const MessagesErrorResponse = Schema.Struct({ "error": MessagesErrorDetail, "type": Schema.Literal("error") }) + .annotate({ "identifier": "MessagesErrorResponse" }) +export type ApplyPatchServerTool_OpenRouter = { + readonly "parameters"?: ApplyPatchServerToolConfig + readonly "type": "openrouter:apply_patch" +} +export const ApplyPatchServerTool_OpenRouter = Schema.Struct({ + "parameters": Schema.optionalKey(ApplyPatchServerToolConfig), + "type": Schema.Literal("openrouter:apply_patch") +}).annotate({ + "description": + "OpenRouter built-in server tool: validates V4A diff patches for file operations (create, update, delete). Restricted to the Responses API.", + "identifier": "ApplyPatchServerTool_OpenRouter" +}) +export type ApplyPatchCallItem = { + readonly "call_id": string + readonly "id"?: string | null + readonly "operation": ApplyPatchCallOperation + readonly "status": ApplyPatchCallStatus + readonly "type": "apply_patch_call" +} +export const ApplyPatchCallItem = Schema.Struct({ + "call_id": Schema.String, + "id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "operation": ApplyPatchCallOperation, + "status": ApplyPatchCallStatus, + "type": Schema.Literal("apply_patch_call") +}).annotate({ + "description": + "A tool call emitted by the model requesting a V4A patch operation. The client applies the patch and echoes an `apply_patch_call_output` on the next turn.", + "identifier": "ApplyPatchCallItem" +}) +export type OutputApplyPatchCallItem = { + readonly "call_id": string + readonly "id": string + readonly "operation": ApplyPatchCallOperation + readonly "status": ApplyPatchCallStatus + readonly "type": "apply_patch_call" +} +export const OutputApplyPatchCallItem = Schema.Struct({ + "call_id": Schema.String, + "id": Schema.String, + "operation": ApplyPatchCallOperation, + "status": ApplyPatchCallStatus, + "type": Schema.Literal("apply_patch_call") +}).annotate({ + "description": + "A native `apply_patch_call` output item matching OpenAI's Responses API shape. Emitted when the client requested the `apply_patch` shorthand.", + "identifier": "OutputApplyPatchCallItem" +}) +export type OutputApplyPatchServerToolItem = { + readonly "call_id"?: string + readonly "id"?: string + readonly "operation"?: ApplyPatchCallOperation + readonly "status": ToolCallStatus + readonly "type": "openrouter:apply_patch" +} +export const OutputApplyPatchServerToolItem = Schema.Struct({ + "call_id": Schema.optionalKey(Schema.String), + "id": Schema.optionalKey(Schema.String), + "operation": Schema.optionalKey(ApplyPatchCallOperation), + "status": ToolCallStatus, + "type": Schema.Literal("openrouter:apply_patch") +}).annotate({ + "description": + "An openrouter:apply_patch server tool output item. The turn halts when validation succeeds so the client can apply the patch and echo an `apply_patch_call_output` on the next turn.", + "identifier": "OutputApplyPatchServerToolItem" +}) +export type CreateBYOKKeyResponse = { readonly "data": BYOKKey } +export const CreateBYOKKeyResponse = Schema.Struct({ + "data": Schema.suspend((): Schema.Codec => BYOKKey).annotate({ + "description": "The created BYOK credential." + }) +}).annotate({ "identifier": "CreateBYOKKeyResponse" }) +export type GetBYOKKeyResponse = { readonly "data": BYOKKey } +export const GetBYOKKeyResponse = Schema.Struct({ + "data": Schema.suspend((): Schema.Codec => BYOKKey).annotate({ "description": "The BYOK credential." }) +}).annotate({ "identifier": "GetBYOKKeyResponse" }) +export type ListBYOKKeysResponse = { readonly "data": ReadonlyArray; readonly "total_count": number } +export const ListBYOKKeysResponse = Schema.Struct({ + "data": Schema.Array(BYOKKey).annotate({ "description": "List of BYOK credentials." }), + "total_count": Schema.Number.annotate({ "description": "Total number of BYOK credentials matching the filters." }) + .check(Schema.isInt().annotate({ "expected": "an integer" })) +}).annotate({ "identifier": "ListBYOKKeysResponse" }) +export type UpdateBYOKKeyResponse = { readonly "data": BYOKKey } +export const UpdateBYOKKeyResponse = Schema.Struct({ + "data": Schema.suspend((): Schema.Codec => BYOKKey).annotate({ + "description": "The updated BYOK credential." + }) +}).annotate({ "identifier": "UpdateBYOKKeyResponse" }) +export type ChatTokenLogprobs = Objects_7 | null +export const ChatTokenLogprobs = Schema.Union([Objects_7, Schema.Null]).annotate({ + "description": "Log probabilities for the completion", + "identifier": "ChatTokenLogprobs" +}) +export type BashServerToolConfig = { + readonly "engine"?: BashServerToolEngine + readonly "environment"?: BashServerToolEnvironment + readonly "sleep_after_seconds"?: SandboxSleepAfterSeconds +} +export const BashServerToolConfig = Schema.Struct({ + "engine": Schema.optionalKey(BashServerToolEngine), + "environment": Schema.optionalKey(BashServerToolEnvironment), + "sleep_after_seconds": Schema.optionalKey(SandboxSleepAfterSeconds) +}).annotate({ + "description": "Configuration for the openrouter:bash server tool", + "identifier": "BashServerToolConfig" +}) +export type ShellServerToolConfig = { + readonly "engine"?: ShellServerToolEngine + readonly "environment"?: ShellServerToolEnvironment + readonly "sleep_after_seconds"?: SandboxSleepAfterSeconds +} +export const ShellServerToolConfig = Schema.Struct({ + "engine": Schema.optionalKey(ShellServerToolEngine), + "environment": Schema.optionalKey(ShellServerToolEnvironment), + "sleep_after_seconds": Schema.optionalKey(SandboxSleepAfterSeconds) +}).annotate({ + "description": "Configuration for the openrouter:shell server tool", + "identifier": "ShellServerToolConfig" +}) +export type ModelBenchmarks = { readonly "artificial_analysis"?: AABenchmarkEntry; readonly "design_arena": Arrays_9 } +export const ModelBenchmarks = Schema.Struct({ + "artificial_analysis": Schema.optionalKey(AABenchmarkEntry), + "design_arena": Arrays_9 +}).annotate({ + "description": "Third-party benchmark rankings for this model. Omitted when no benchmark data is available.", + "identifier": "ModelBenchmarks" +}) +export type TextConfig = { readonly "format"?: Formats; readonly "verbosity"?: "high" | "low" | "medium" | null } +export const TextConfig = Schema.Struct({ + "format": Schema.optionalKey(Formats), + "verbosity": Schema.optionalKey( + Schema.Union([Schema.Literal("high"), Schema.Literal("low"), Schema.Literal("medium"), Schema.Null]) + ) +}).annotate({ "description": "Text output configuration including format and verbosity", "identifier": "TextConfig" }) +export type TextExtendedConfig = { + readonly "format"?: Formats + readonly "verbosity"?: "high" | "low" | "medium" | null +} +export const TextExtendedConfig = Schema.Struct({ + "format": Schema.optionalKey(Formats), + "verbosity": Schema.optionalKey( + Schema.Union([Schema.Literal("high"), Schema.Literal("low"), Schema.Literal("medium"), Schema.Union([Schema.Null])]) + ) +}).annotate({ + "description": "Text output configuration including format and verbosity", + "identifier": "TextExtendedConfig" +}) +export type FusionCallAnalysisCompletedEvent = { + readonly "analysis": FusionAnalysisResult + readonly "item_id": string + readonly "output_index": number + readonly "sequence_number": number + readonly "type": "response.fusion_call.analysis.completed" +} +export const FusionCallAnalysisCompletedEvent = Schema.Struct({ + "analysis": FusionAnalysisResult, + "item_id": Schema.String, + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.fusion_call.analysis.completed") +}).annotate({ + "description": "Emitted when the fusion judge completes with the structured analysis.", + "identifier": "FusionCallAnalysisCompletedEvent" +}) +export type OutputFusionServerToolItem = { + readonly "analysis"?: FusionAnalysisResult + readonly "error"?: string + readonly "failed_models"?: ReadonlyArray< + { readonly "error": string; readonly "model": string; readonly "status_code"?: number } + > + readonly "failure_reason"?: string + readonly "id"?: string + readonly "responses"?: ReadonlyArray<{ readonly "content"?: string; readonly "model": string }> + readonly "sources"?: ReadonlyArray + readonly "status": ToolCallStatus + readonly "type": "openrouter:fusion" +} +export const OutputFusionServerToolItem = Schema.Struct({ + "analysis": Schema.optionalKey(FusionAnalysisResult), + "error": Schema.optionalKey( + Schema.String.annotate({ "description": "Error message when the fusion run did not produce an analysis result." }) + ), + "failed_models": Schema.optionalKey( + Schema.Array(Schema.Struct({ + "error": Schema.String.annotate({ "description": "Error message describing why the model failed." }), + "model": Schema.String.annotate({ "description": "Slug of the analysis model that failed." }), + "status_code": Schema.optionalKey( + Schema.Number.annotate({ + "description": "HTTP status code from the upstream response, when available (e.g. 402, 429)." + }).check(Schema.isInt().annotate({ "expected": "an integer" })) + ) + })).annotate({ + "description": + "Models that were requested as part of the analysis panel but did not produce a response. Present when at least one requested analysis model failed. The fusion result is still usable but was produced from a degraded panel." + }) + ), + "failure_reason": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Typed failure reason when the fusion run failed. Possible values include: all_panels_failed, insufficient_credits, rate_limited, judge_not_valid_json, judge_schema_mismatch, judge_upstream_error, judge_empty_completion." + }) + ), + "id": Schema.optionalKey(Schema.String), + "responses": Schema.optionalKey( + Schema.Array(Schema.Struct({ "content": Schema.optionalKey(Schema.String), "model": Schema.String })).annotate({ + "description": + "Analysis models that produced a response in this fusion run, with each model's full panel content." + }) + ), + "sources": Schema.optionalKey( + Schema.Array(FusionSource).annotate({ + "description": + "Web pages the analysis panels and judge retrieved via web search during this fusion run, deduplicated by URL across the whole run. Present when at least one model cited a source." + }) + ), + "status": ToolCallStatus, + "type": Schema.Literal("openrouter:fusion") +}).annotate({ + "description": "An openrouter:fusion server tool output item", + "identifier": "OutputFusionServerToolItem" +}) +export type ImageModelListItem = { + readonly "architecture": ImageModelArchitecture + readonly "created": number + readonly "description": string + readonly "endpoints": string + readonly "id": string + readonly "name": string + readonly "supported_parameters": SupportedParameters + readonly "supports_streaming": boolean +} +export const ImageModelListItem = Schema.Struct({ + "architecture": ImageModelArchitecture, + "created": Schema.Number.annotate({ "description": "Unix timestamp (seconds) of when the model was created" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ), + "description": Schema.String, + "endpoints": Schema.String.annotate({ + "description": "Relative URL to the full per-endpoint records for this model" + }), + "id": Schema.String.annotate({ "description": "Model slug" }), + "name": Schema.String.annotate({ "description": "Display name" }), + "supported_parameters": SupportedParameters, + "supports_streaming": Schema.Boolean.annotate({ + "description": + "Whether any endpoint of this model supports native SSE streaming on the dedicated Image API (i.e. `stream: true` in the request). OR across endpoints." + }) +}).annotate({ "description": "A single image model in the discovery listing.", "identifier": "ImageModelListItem" }) +export type ObservabilityArizeDestination = { + readonly "api_key_hashes": ReadonlyArray | null + readonly "config": { + readonly "apiKey": string + readonly "baseUrl"?: string + readonly "headers"?: {} + readonly "modelId": string + readonly "spaceKey": string + } + readonly "created_at": string + readonly "enabled": boolean + readonly "filter_rules": ObservabilityFilterRulesConfig + readonly "id": string + readonly "name": string | null + readonly "privacy_mode": boolean + readonly "sampling_rate": number + readonly "type": "arize" + readonly "updated_at": string + readonly "workspace_id": string +} +export const ObservabilityArizeDestination = Schema.Struct({ + "api_key_hashes": Schema.Union([Schema.Array(Schema.String), Schema.Null]).annotate({ + "description": + "Optional allowlist of OpenRouter API key hashes (`api_keys.hash`) whose traffic is forwarded to this destination. `null` means all keys." + }), + "config": Schema.Struct({ + "apiKey": Schema.String.check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ), + "baseUrl": Schema.optionalKey(Schema.String), + "headers": Schema.optionalKey( + Schema.Struct({}).annotate({ "description": "Custom HTTP headers to include in requests to this destination." }) + ), + "modelId": Schema.String.annotate({ "description": "The name of the tracing project in Arize AX" }).check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ), + "spaceKey": Schema.String.check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ) + }), + "created_at": Schema.String.annotate({ "description": "ISO timestamp of when the destination was created." }), + "enabled": Schema.Boolean.annotate({ "description": "Whether this destination is currently enabled." }), + "filter_rules": ObservabilityFilterRulesConfig, + "id": Schema.String.annotate({ "description": "Stable public identifier for this destination.", "format": "uuid" }), + "name": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "Human-readable name for the destination." + }), + "privacy_mode": Schema.Boolean.annotate({ + "description": "When true, request/response bodies are not forwarded to this destination — only metadata." + }), + "sampling_rate": Schema.Number.annotate({ + "description": "Sampling rate for events sent to this destination, between 0.0001 and 1 (1 = 100%).", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "type": Schema.Literal("arize"), + "updated_at": Schema.String.annotate({ "description": "ISO timestamp of when the destination was last updated." }), + "workspace_id": Schema.String.annotate({ + "description": "ID of the workspace this destination belongs to.", + "format": "uuid" + }) +}).annotate({ "identifier": "ObservabilityArizeDestination" }) +export type ObservabilityBraintrustDestination = { + readonly "api_key_hashes": ReadonlyArray | null + readonly "config": { + readonly "apiKey": string + readonly "baseUrl"?: string + readonly "headers"?: {} + readonly "projectId": string + } + readonly "created_at": string + readonly "enabled": boolean + readonly "filter_rules": ObservabilityFilterRulesConfig + readonly "id": string + readonly "name": string | null + readonly "privacy_mode": boolean + readonly "sampling_rate": number + readonly "type": "braintrust" + readonly "updated_at": string + readonly "workspace_id": string +} +export const ObservabilityBraintrustDestination = Schema.Struct({ + "api_key_hashes": Schema.Union([Schema.Array(Schema.String), Schema.Null]).annotate({ + "description": + "Optional allowlist of OpenRouter API key hashes (`api_keys.hash`) whose traffic is forwarded to this destination. `null` means all keys." + }), + "config": Schema.Struct({ + "apiKey": Schema.String.check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ), + "baseUrl": Schema.optionalKey(Schema.String), + "headers": Schema.optionalKey( + Schema.Struct({}).annotate({ "description": "Custom HTTP headers to include in requests to this destination." }) + ), + "projectId": Schema.String.check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ) + }), + "created_at": Schema.String.annotate({ "description": "ISO timestamp of when the destination was created." }), + "enabled": Schema.Boolean.annotate({ "description": "Whether this destination is currently enabled." }), + "filter_rules": ObservabilityFilterRulesConfig, + "id": Schema.String.annotate({ "description": "Stable public identifier for this destination.", "format": "uuid" }), + "name": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "Human-readable name for the destination." + }), + "privacy_mode": Schema.Boolean.annotate({ + "description": "When true, request/response bodies are not forwarded to this destination — only metadata." + }), + "sampling_rate": Schema.Number.annotate({ + "description": "Sampling rate for events sent to this destination, between 0.0001 and 1 (1 = 100%).", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "type": Schema.Literal("braintrust"), + "updated_at": Schema.String.annotate({ "description": "ISO timestamp of when the destination was last updated." }), + "workspace_id": Schema.String.annotate({ + "description": "ID of the workspace this destination belongs to.", + "format": "uuid" + }) +}).annotate({ "identifier": "ObservabilityBraintrustDestination" }) +export type ObservabilityClickhouseDestination = { + readonly "api_key_hashes": ReadonlyArray | null + readonly "config": { + readonly "database": string + readonly "headers"?: {} + readonly "host": string + readonly "password": string + readonly "table"?: string + readonly "username": string + } + readonly "created_at": string + readonly "enabled": boolean + readonly "filter_rules": ObservabilityFilterRulesConfig + readonly "id": string + readonly "name": string | null + readonly "privacy_mode": boolean + readonly "sampling_rate": number + readonly "type": "clickhouse" + readonly "updated_at": string + readonly "workspace_id": string +} +export const ObservabilityClickhouseDestination = Schema.Struct({ + "api_key_hashes": Schema.Union([Schema.Array(Schema.String), Schema.Null]).annotate({ + "description": + "Optional allowlist of OpenRouter API key hashes (`api_keys.hash`) whose traffic is forwarded to this destination. `null` means all keys." + }), + "config": Schema.Struct({ + "database": Schema.String.check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ), + "headers": Schema.optionalKey( + Schema.Struct({}).annotate({ "description": "Custom HTTP headers to include in requests to this destination." }) + ), + "host": Schema.String.check(Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" })), + "password": Schema.String.check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ), + "table": Schema.optionalKey(Schema.String), + "username": Schema.String.annotate({ + "description": "If you have not set a specific username in ClickHouse, simply type in 'default' below." + }).check(Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" })) + }), + "created_at": Schema.String.annotate({ "description": "ISO timestamp of when the destination was created." }), + "enabled": Schema.Boolean.annotate({ "description": "Whether this destination is currently enabled." }), + "filter_rules": ObservabilityFilterRulesConfig, + "id": Schema.String.annotate({ "description": "Stable public identifier for this destination.", "format": "uuid" }), + "name": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "Human-readable name for the destination." + }), + "privacy_mode": Schema.Boolean.annotate({ + "description": "When true, request/response bodies are not forwarded to this destination — only metadata." + }), + "sampling_rate": Schema.Number.annotate({ + "description": "Sampling rate for events sent to this destination, between 0.0001 and 1 (1 = 100%).", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "type": Schema.Literal("clickhouse"), + "updated_at": Schema.String.annotate({ "description": "ISO timestamp of when the destination was last updated." }), + "workspace_id": Schema.String.annotate({ + "description": "ID of the workspace this destination belongs to.", + "format": "uuid" + }) +}).annotate({ "identifier": "ObservabilityClickhouseDestination" }) +export type ObservabilityDatadogDestination = { + readonly "api_key_hashes": ReadonlyArray | null + readonly "config": { + readonly "apiKey": string + readonly "headers"?: {} + readonly "mlApp": string + readonly "url"?: string + } + readonly "created_at": string + readonly "enabled": boolean + readonly "filter_rules": ObservabilityFilterRulesConfig + readonly "id": string + readonly "name": string | null + readonly "privacy_mode": boolean + readonly "sampling_rate": number + readonly "type": "datadog" + readonly "updated_at": string + readonly "workspace_id": string +} +export const ObservabilityDatadogDestination = Schema.Struct({ + "api_key_hashes": Schema.Union([Schema.Array(Schema.String), Schema.Null]).annotate({ + "description": + "Optional allowlist of OpenRouter API key hashes (`api_keys.hash`) whose traffic is forwarded to this destination. `null` means all keys." + }), + "config": Schema.Struct({ + "apiKey": Schema.String.annotate({ + "description": "Datadog API key must have LLM Observability permissions. Create at: " + }).check(Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" })), + "headers": Schema.optionalKey( + Schema.Struct({}).annotate({ "description": "Custom HTTP headers to include in requests to this destination." }) + ), + "mlApp": Schema.String.annotate({ "description": "Name to identify your application in Datadog LLM Observability" }) + .check(Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" })), + "url": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Datadog API URL for your region (e.g., https://api.datadoghq.com, https://api.us3.datadoghq.com, https://api.datadoghq.eu)" + }) + ) + }), + "created_at": Schema.String.annotate({ "description": "ISO timestamp of when the destination was created." }), + "enabled": Schema.Boolean.annotate({ "description": "Whether this destination is currently enabled." }), + "filter_rules": ObservabilityFilterRulesConfig, + "id": Schema.String.annotate({ "description": "Stable public identifier for this destination.", "format": "uuid" }), + "name": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "Human-readable name for the destination." + }), + "privacy_mode": Schema.Boolean.annotate({ + "description": "When true, request/response bodies are not forwarded to this destination — only metadata." + }), + "sampling_rate": Schema.Number.annotate({ + "description": "Sampling rate for events sent to this destination, between 0.0001 and 1 (1 = 100%).", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "type": Schema.Literal("datadog"), + "updated_at": Schema.String.annotate({ "description": "ISO timestamp of when the destination was last updated." }), + "workspace_id": Schema.String.annotate({ + "description": "ID of the workspace this destination belongs to.", + "format": "uuid" + }) +}).annotate({ "identifier": "ObservabilityDatadogDestination" }) +export type ObservabilityGrafanaDestination = { + readonly "api_key_hashes": ReadonlyArray | null + readonly "config": { + readonly "apiKey": string + readonly "baseUrl"?: string + readonly "headers"?: {} + readonly "instanceId": string + } + readonly "created_at": string + readonly "enabled": boolean + readonly "filter_rules": ObservabilityFilterRulesConfig + readonly "id": string + readonly "name": string | null + readonly "privacy_mode": boolean + readonly "sampling_rate": number + readonly "type": "grafana" + readonly "updated_at": string + readonly "workspace_id": string +} +export const ObservabilityGrafanaDestination = Schema.Struct({ + "api_key_hashes": Schema.Union([Schema.Array(Schema.String), Schema.Null]).annotate({ + "description": + "Optional allowlist of OpenRouter API key hashes (`api_keys.hash`) whose traffic is forwarded to this destination. `null` means all keys." + }), + "config": Schema.Struct({ + "apiKey": Schema.String.check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ), + "baseUrl": Schema.optionalKey(Schema.String), + "headers": Schema.optionalKey( + Schema.Struct({}).annotate({ "description": "Custom HTTP headers to include in requests to this destination." }) + ), + "instanceId": Schema.String.check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ) + }), + "created_at": Schema.String.annotate({ "description": "ISO timestamp of when the destination was created." }), + "enabled": Schema.Boolean.annotate({ "description": "Whether this destination is currently enabled." }), + "filter_rules": ObservabilityFilterRulesConfig, + "id": Schema.String.annotate({ "description": "Stable public identifier for this destination.", "format": "uuid" }), + "name": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "Human-readable name for the destination." + }), + "privacy_mode": Schema.Boolean.annotate({ + "description": "When true, request/response bodies are not forwarded to this destination — only metadata." + }), + "sampling_rate": Schema.Number.annotate({ + "description": "Sampling rate for events sent to this destination, between 0.0001 and 1 (1 = 100%).", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "type": Schema.Literal("grafana"), + "updated_at": Schema.String.annotate({ "description": "ISO timestamp of when the destination was last updated." }), + "workspace_id": Schema.String.annotate({ + "description": "ID of the workspace this destination belongs to.", + "format": "uuid" + }) +}).annotate({ "identifier": "ObservabilityGrafanaDestination" }) +export type ObservabilityLangfuseDestination = { + readonly "api_key_hashes": ReadonlyArray | null + readonly "config": { + readonly "baseUrl"?: string + readonly "headers"?: {} + readonly "publicKey": string + readonly "secretKey": string + } + readonly "created_at": string + readonly "enabled": boolean + readonly "filter_rules": ObservabilityFilterRulesConfig + readonly "id": string + readonly "name": string | null + readonly "privacy_mode": boolean + readonly "sampling_rate": number + readonly "type": "langfuse" + readonly "updated_at": string + readonly "workspace_id": string +} +export const ObservabilityLangfuseDestination = Schema.Struct({ + "api_key_hashes": Schema.Union([Schema.Array(Schema.String), Schema.Null]).annotate({ + "description": + "Optional allowlist of OpenRouter API key hashes (`api_keys.hash`) whose traffic is forwarded to this destination. `null` means all keys." + }), + "config": Schema.Struct({ + "baseUrl": Schema.optionalKey(Schema.String), + "headers": Schema.optionalKey( + Schema.Struct({}).annotate({ "description": "Custom HTTP headers to include in requests to this destination." }) + ), + "publicKey": Schema.String.check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ), + "secretKey": Schema.String.check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ) + }), + "created_at": Schema.String.annotate({ "description": "ISO timestamp of when the destination was created." }), + "enabled": Schema.Boolean.annotate({ "description": "Whether this destination is currently enabled." }), + "filter_rules": ObservabilityFilterRulesConfig, + "id": Schema.String.annotate({ "description": "Stable public identifier for this destination.", "format": "uuid" }), + "name": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "Human-readable name for the destination." + }), + "privacy_mode": Schema.Boolean.annotate({ + "description": "When true, request/response bodies are not forwarded to this destination — only metadata." + }), + "sampling_rate": Schema.Number.annotate({ + "description": "Sampling rate for events sent to this destination, between 0.0001 and 1 (1 = 100%).", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "type": Schema.Literal("langfuse"), + "updated_at": Schema.String.annotate({ "description": "ISO timestamp of when the destination was last updated." }), + "workspace_id": Schema.String.annotate({ + "description": "ID of the workspace this destination belongs to.", + "format": "uuid" + }) +}).annotate({ "identifier": "ObservabilityLangfuseDestination" }) +export type ObservabilityLangsmithDestination = { + readonly "api_key_hashes": ReadonlyArray | null + readonly "config": { + readonly "apiKey": string + readonly "endpoint"?: string + readonly "headers"?: {} + readonly "project"?: string + readonly "workspaceId"?: string + } + readonly "created_at": string + readonly "enabled": boolean + readonly "filter_rules": ObservabilityFilterRulesConfig + readonly "id": string + readonly "name": string | null + readonly "privacy_mode": boolean + readonly "sampling_rate": number + readonly "type": "langsmith" + readonly "updated_at": string + readonly "workspace_id": string +} +export const ObservabilityLangsmithDestination = Schema.Struct({ + "api_key_hashes": Schema.Union([Schema.Array(Schema.String), Schema.Null]).annotate({ + "description": + "Optional allowlist of OpenRouter API key hashes (`api_keys.hash`) whose traffic is forwarded to this destination. `null` means all keys." + }), + "config": Schema.Struct({ + "apiKey": Schema.String.check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ), + "endpoint": Schema.optionalKey(Schema.String), + "headers": Schema.optionalKey( + Schema.Struct({}).annotate({ "description": "Custom HTTP headers to include in requests to this destination." }) + ), + "project": Schema.optionalKey( + Schema.String.annotate({ + "description": "The name for this project, such as pr-openrouter-demo. Defaults to \"main\" if not set." + }).check(Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" })) + ), + "workspaceId": Schema.optionalKey( + Schema.String.annotate({ + "description": "Required for org-scoped API keys. Find this in your LangSmith workspace settings." + }) + ) + }), + "created_at": Schema.String.annotate({ "description": "ISO timestamp of when the destination was created." }), + "enabled": Schema.Boolean.annotate({ "description": "Whether this destination is currently enabled." }), + "filter_rules": ObservabilityFilterRulesConfig, + "id": Schema.String.annotate({ "description": "Stable public identifier for this destination.", "format": "uuid" }), + "name": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "Human-readable name for the destination." + }), + "privacy_mode": Schema.Boolean.annotate({ + "description": "When true, request/response bodies are not forwarded to this destination — only metadata." + }), + "sampling_rate": Schema.Number.annotate({ + "description": "Sampling rate for events sent to this destination, between 0.0001 and 1 (1 = 100%).", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "type": Schema.Literal("langsmith"), + "updated_at": Schema.String.annotate({ "description": "ISO timestamp of when the destination was last updated." }), + "workspace_id": Schema.String.annotate({ + "description": "ID of the workspace this destination belongs to.", + "format": "uuid" + }) +}).annotate({ "identifier": "ObservabilityLangsmithDestination" }) +export type ObservabilityNewrelicDestination = { + readonly "api_key_hashes": ReadonlyArray | null + readonly "config": { readonly "headers"?: {}; readonly "licenseKey": string; readonly "region"?: "us" | "eu" } + readonly "created_at": string + readonly "enabled": boolean + readonly "filter_rules": ObservabilityFilterRulesConfig + readonly "id": string + readonly "name": string | null + readonly "privacy_mode": boolean + readonly "sampling_rate": number + readonly "type": "newrelic" + readonly "updated_at": string + readonly "workspace_id": string +} +export const ObservabilityNewrelicDestination = Schema.Struct({ + "api_key_hashes": Schema.Union([Schema.Array(Schema.String), Schema.Null]).annotate({ + "description": + "Optional allowlist of OpenRouter API key hashes (`api_keys.hash`) whose traffic is forwarded to this destination. `null` means all keys." + }), + "config": Schema.Struct({ + "headers": Schema.optionalKey( + Schema.Struct({}).annotate({ "description": "Custom HTTP headers to include in requests to this destination." }) + ), + "licenseKey": Schema.String.check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ), + "region": Schema.optionalKey(Schema.Literals(["us", "eu"])) + }), + "created_at": Schema.String.annotate({ "description": "ISO timestamp of when the destination was created." }), + "enabled": Schema.Boolean.annotate({ "description": "Whether this destination is currently enabled." }), + "filter_rules": ObservabilityFilterRulesConfig, + "id": Schema.String.annotate({ "description": "Stable public identifier for this destination.", "format": "uuid" }), + "name": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "Human-readable name for the destination." + }), + "privacy_mode": Schema.Boolean.annotate({ + "description": "When true, request/response bodies are not forwarded to this destination — only metadata." + }), + "sampling_rate": Schema.Number.annotate({ + "description": "Sampling rate for events sent to this destination, between 0.0001 and 1 (1 = 100%).", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "type": Schema.Literal("newrelic"), + "updated_at": Schema.String.annotate({ "description": "ISO timestamp of when the destination was last updated." }), + "workspace_id": Schema.String.annotate({ + "description": "ID of the workspace this destination belongs to.", + "format": "uuid" + }) +}).annotate({ "identifier": "ObservabilityNewrelicDestination" }) +export type ObservabilityOpikDestination = { + readonly "api_key_hashes": ReadonlyArray | null + readonly "config": { + readonly "apiKey": string + readonly "headers"?: {} + readonly "projectName": string + readonly "workspace": string + } + readonly "created_at": string + readonly "enabled": boolean + readonly "filter_rules": ObservabilityFilterRulesConfig + readonly "id": string + readonly "name": string | null + readonly "privacy_mode": boolean + readonly "sampling_rate": number + readonly "type": "opik" + readonly "updated_at": string + readonly "workspace_id": string +} +export const ObservabilityOpikDestination = Schema.Struct({ + "api_key_hashes": Schema.Union([Schema.Array(Schema.String), Schema.Null]).annotate({ + "description": + "Optional allowlist of OpenRouter API key hashes (`api_keys.hash`) whose traffic is forwarded to this destination. `null` means all keys." + }), + "config": Schema.Struct({ + "apiKey": Schema.String.check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ), + "headers": Schema.optionalKey( + Schema.Struct({}).annotate({ "description": "Custom HTTP headers to include in requests to this destination." }) + ), + "projectName": Schema.String.check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ), + "workspace": Schema.String.check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ) + }), + "created_at": Schema.String.annotate({ "description": "ISO timestamp of when the destination was created." }), + "enabled": Schema.Boolean.annotate({ "description": "Whether this destination is currently enabled." }), + "filter_rules": ObservabilityFilterRulesConfig, + "id": Schema.String.annotate({ "description": "Stable public identifier for this destination.", "format": "uuid" }), + "name": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "Human-readable name for the destination." + }), + "privacy_mode": Schema.Boolean.annotate({ + "description": "When true, request/response bodies are not forwarded to this destination — only metadata." + }), + "sampling_rate": Schema.Number.annotate({ + "description": "Sampling rate for events sent to this destination, between 0.0001 and 1 (1 = 100%).", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "type": Schema.Literal("opik"), + "updated_at": Schema.String.annotate({ "description": "ISO timestamp of when the destination was last updated." }), + "workspace_id": Schema.String.annotate({ + "description": "ID of the workspace this destination belongs to.", + "format": "uuid" + }) +}).annotate({ "identifier": "ObservabilityOpikDestination" }) +export type ObservabilityOtelCollectorDestination = { + readonly "api_key_hashes": ReadonlyArray | null + readonly "config": { readonly "endpoint": string; readonly "headers"?: {} } + readonly "created_at": string + readonly "enabled": boolean + readonly "filter_rules": ObservabilityFilterRulesConfig + readonly "id": string + readonly "name": string | null + readonly "privacy_mode": boolean + readonly "sampling_rate": number + readonly "type": "otel-collector" + readonly "updated_at": string + readonly "workspace_id": string +} +export const ObservabilityOtelCollectorDestination = Schema.Struct({ + "api_key_hashes": Schema.Union([Schema.Array(Schema.String), Schema.Null]).annotate({ + "description": + "Optional allowlist of OpenRouter API key hashes (`api_keys.hash`) whose traffic is forwarded to this destination. `null` means all keys." + }), + "config": Schema.Struct({ + "endpoint": Schema.String, + "headers": Schema.optionalKey( + Schema.Struct({}).annotate({ + "description": + "Custom HTTP headers as a JSON object. For Axiom, use {\"Authorization\": \"Bearer xaat-xxx\", \"X-Axiom-Dataset\": \"your-dataset\"}" + }) + ) + }), + "created_at": Schema.String.annotate({ "description": "ISO timestamp of when the destination was created." }), + "enabled": Schema.Boolean.annotate({ "description": "Whether this destination is currently enabled." }), + "filter_rules": ObservabilityFilterRulesConfig, + "id": Schema.String.annotate({ "description": "Stable public identifier for this destination.", "format": "uuid" }), + "name": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "Human-readable name for the destination." + }), + "privacy_mode": Schema.Boolean.annotate({ + "description": "When true, request/response bodies are not forwarded to this destination — only metadata." + }), + "sampling_rate": Schema.Number.annotate({ + "description": "Sampling rate for events sent to this destination, between 0.0001 and 1 (1 = 100%).", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "type": Schema.Literal("otel-collector"), + "updated_at": Schema.String.annotate({ "description": "ISO timestamp of when the destination was last updated." }), + "workspace_id": Schema.String.annotate({ + "description": "ID of the workspace this destination belongs to.", + "format": "uuid" + }) +}).annotate({ "identifier": "ObservabilityOtelCollectorDestination" }) +export type ObservabilityPosthogDestination = { + readonly "api_key_hashes": ReadonlyArray | null + readonly "config": { readonly "apiKey": string; readonly "endpoint"?: string; readonly "headers"?: {} } + readonly "created_at": string + readonly "enabled": boolean + readonly "filter_rules": ObservabilityFilterRulesConfig + readonly "id": string + readonly "name": string | null + readonly "privacy_mode": boolean + readonly "sampling_rate": number + readonly "type": "posthog" + readonly "updated_at": string + readonly "workspace_id": string +} +export const ObservabilityPosthogDestination = Schema.Struct({ + "api_key_hashes": Schema.Union([Schema.Array(Schema.String), Schema.Null]).annotate({ + "description": + "Optional allowlist of OpenRouter API key hashes (`api_keys.hash`) whose traffic is forwarded to this destination. `null` means all keys." + }), + "config": Schema.Struct({ + "apiKey": Schema.String.check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ), + "endpoint": Schema.optionalKey(Schema.String), + "headers": Schema.optionalKey( + Schema.Struct({}).annotate({ "description": "Custom HTTP headers to include in requests to this destination." }) + ) + }), + "created_at": Schema.String.annotate({ "description": "ISO timestamp of when the destination was created." }), + "enabled": Schema.Boolean.annotate({ "description": "Whether this destination is currently enabled." }), + "filter_rules": ObservabilityFilterRulesConfig, + "id": Schema.String.annotate({ "description": "Stable public identifier for this destination.", "format": "uuid" }), + "name": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "Human-readable name for the destination." + }), + "privacy_mode": Schema.Boolean.annotate({ + "description": "When true, request/response bodies are not forwarded to this destination — only metadata." + }), + "sampling_rate": Schema.Number.annotate({ + "description": "Sampling rate for events sent to this destination, between 0.0001 and 1 (1 = 100%).", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "type": Schema.Literal("posthog"), + "updated_at": Schema.String.annotate({ "description": "ISO timestamp of when the destination was last updated." }), + "workspace_id": Schema.String.annotate({ + "description": "ID of the workspace this destination belongs to.", + "format": "uuid" + }) +}).annotate({ "identifier": "ObservabilityPosthogDestination" }) +export type ObservabilityRampDestination = { + readonly "api_key_hashes": ReadonlyArray | null + readonly "config": { readonly "apiKey": string; readonly "baseUrl"?: string; readonly "headers"?: {} } + readonly "created_at": string + readonly "enabled": boolean + readonly "filter_rules": ObservabilityFilterRulesConfig + readonly "id": string + readonly "name": string | null + readonly "privacy_mode": boolean + readonly "sampling_rate": number + readonly "type": "ramp" + readonly "updated_at": string + readonly "workspace_id": string +} +export const ObservabilityRampDestination = Schema.Struct({ + "api_key_hashes": Schema.Union([Schema.Array(Schema.String), Schema.Null]).annotate({ + "description": + "Optional allowlist of OpenRouter API key hashes (`api_keys.hash`) whose traffic is forwarded to this destination. `null` means all keys." + }), + "config": Schema.Struct({ + "apiKey": Schema.String.annotate({ "description": "Generate this in your Ramp integration settings." }).check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ), + "baseUrl": Schema.optionalKey(Schema.String), + "headers": Schema.optionalKey( + Schema.Struct({}).annotate({ "description": "Custom HTTP headers to include in requests to Ramp." }) + ) + }), + "created_at": Schema.String.annotate({ "description": "ISO timestamp of when the destination was created." }), + "enabled": Schema.Boolean.annotate({ "description": "Whether this destination is currently enabled." }), + "filter_rules": ObservabilityFilterRulesConfig, + "id": Schema.String.annotate({ "description": "Stable public identifier for this destination.", "format": "uuid" }), + "name": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "Human-readable name for the destination." + }), + "privacy_mode": Schema.Boolean.annotate({ + "description": "When true, request/response bodies are not forwarded to this destination — only metadata." + }), + "sampling_rate": Schema.Number.annotate({ + "description": "Sampling rate for events sent to this destination, between 0.0001 and 1 (1 = 100%).", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "type": Schema.Literal("ramp"), + "updated_at": Schema.String.annotate({ "description": "ISO timestamp of when the destination was last updated." }), + "workspace_id": Schema.String.annotate({ + "description": "ID of the workspace this destination belongs to.", + "format": "uuid" + }) +}).annotate({ "identifier": "ObservabilityRampDestination" }) +export type ObservabilityS3Destination = { + readonly "api_key_hashes": ReadonlyArray | null + readonly "config": { + readonly "accessKeyId": string + readonly "bucketName": string + readonly "endpoint"?: string + readonly "headers"?: {} + readonly "pathTemplate"?: string + readonly "prefix"?: string + readonly "region"?: string + readonly "secretAccessKey": string + readonly "sessionToken"?: string + } + readonly "created_at": string + readonly "enabled": boolean + readonly "filter_rules": ObservabilityFilterRulesConfig + readonly "id": string + readonly "name": string | null + readonly "privacy_mode": boolean + readonly "sampling_rate": number + readonly "type": "s3" + readonly "updated_at": string + readonly "workspace_id": string +} +export const ObservabilityS3Destination = Schema.Struct({ + "api_key_hashes": Schema.Union([Schema.Array(Schema.String), Schema.Null]).annotate({ + "description": + "Optional allowlist of OpenRouter API key hashes (`api_keys.hash`) whose traffic is forwarded to this destination. `null` means all keys." + }), + "config": Schema.Struct({ + "accessKeyId": Schema.String.check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ), + "bucketName": Schema.String.check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ), + "endpoint": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Only for S3-compatible services like Cloudflare R2 (https://account-id.r2.cloudflarestorage.com) or MinIO. Leave blank for standard AWS S3.", + "format": "uri" + }) + ), + "headers": Schema.optionalKey( + Schema.Struct({}).annotate({ "description": "Custom HTTP headers to include in requests to this destination." }) + ), + "pathTemplate": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Template for S3 object path. The filename ({traceId}-{timestamp}.json) is automatically appended. Available variables: {prefix}, {date}, {year}, {month}, {day}, {apiKeyName}" + }) + ), + "prefix": Schema.optionalKey(Schema.String), + "region": Schema.optionalKey(Schema.String), + "secretAccessKey": Schema.String.check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ), + "sessionToken": Schema.optionalKey(Schema.String) + }), + "created_at": Schema.String.annotate({ "description": "ISO timestamp of when the destination was created." }), + "enabled": Schema.Boolean.annotate({ "description": "Whether this destination is currently enabled." }), + "filter_rules": ObservabilityFilterRulesConfig, + "id": Schema.String.annotate({ "description": "Stable public identifier for this destination.", "format": "uuid" }), + "name": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "Human-readable name for the destination." + }), + "privacy_mode": Schema.Boolean.annotate({ + "description": "When true, request/response bodies are not forwarded to this destination — only metadata." + }), + "sampling_rate": Schema.Number.annotate({ + "description": "Sampling rate for events sent to this destination, between 0.0001 and 1 (1 = 100%).", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "type": Schema.Literal("s3"), + "updated_at": Schema.String.annotate({ "description": "ISO timestamp of when the destination was last updated." }), + "workspace_id": Schema.String.annotate({ + "description": "ID of the workspace this destination belongs to.", + "format": "uuid" + }) +}).annotate({ "identifier": "ObservabilityS3Destination" }) +export type ObservabilitySentryDestination = { + readonly "api_key_hashes": ReadonlyArray | null + readonly "config": { readonly "dsn": string; readonly "headers"?: {}; readonly "otlpEndpoint": string } + readonly "created_at": string + readonly "enabled": boolean + readonly "filter_rules": ObservabilityFilterRulesConfig + readonly "id": string + readonly "name": string | null + readonly "privacy_mode": boolean + readonly "sampling_rate": number + readonly "type": "sentry" + readonly "updated_at": string + readonly "workspace_id": string +} +export const ObservabilitySentryDestination = Schema.Struct({ + "api_key_hashes": Schema.Union([Schema.Array(Schema.String), Schema.Null]).annotate({ + "description": + "Optional allowlist of OpenRouter API key hashes (`api_keys.hash`) whose traffic is forwarded to this destination. `null` means all keys." + }), + "config": Schema.Struct({ + "dsn": Schema.String.check(Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" })) + .check( + Schema.isPattern(new RegExp("^https:\\/\\/([^:@]+)(?::[^@]*)?@([^/]+)(?:\\/[^/]+)*\\/(\\d+)\\/?$")).annotate({ + "expected": "a string matching the RegExp ^https:\\/\\/([^:@]+)(?::[^@]*)?@([^/]+)(?:\\/[^/]+)*\\/(\\d+)\\/?$" + }) + ), + "headers": Schema.optionalKey( + Schema.Struct({}).annotate({ "description": "Custom HTTP headers to include in requests to this destination." }) + ), + "otlpEndpoint": Schema.String + }), + "created_at": Schema.String.annotate({ "description": "ISO timestamp of when the destination was created." }), + "enabled": Schema.Boolean.annotate({ "description": "Whether this destination is currently enabled." }), + "filter_rules": ObservabilityFilterRulesConfig, + "id": Schema.String.annotate({ "description": "Stable public identifier for this destination.", "format": "uuid" }), + "name": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "Human-readable name for the destination." + }), + "privacy_mode": Schema.Boolean.annotate({ + "description": "When true, request/response bodies are not forwarded to this destination — only metadata." + }), + "sampling_rate": Schema.Number.annotate({ + "description": "Sampling rate for events sent to this destination, between 0.0001 and 1 (1 = 100%).", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "type": Schema.Literal("sentry"), + "updated_at": Schema.String.annotate({ "description": "ISO timestamp of when the destination was last updated." }), + "workspace_id": Schema.String.annotate({ + "description": "ID of the workspace this destination belongs to.", + "format": "uuid" + }) +}).annotate({ "identifier": "ObservabilitySentryDestination" }) +export type ObservabilitySnowflakeDestination = { + readonly "api_key_hashes": ReadonlyArray | null + readonly "config": { + readonly "account": string + readonly "database"?: string + readonly "headers"?: {} + readonly "schema"?: string + readonly "table"?: string + readonly "token": string + readonly "warehouse"?: string + } + readonly "created_at": string + readonly "enabled": boolean + readonly "filter_rules": ObservabilityFilterRulesConfig + readonly "id": string + readonly "name": string | null + readonly "privacy_mode": boolean + readonly "sampling_rate": number + readonly "type": "snowflake" + readonly "updated_at": string + readonly "workspace_id": string +} +export const ObservabilitySnowflakeDestination = Schema.Struct({ + "api_key_hashes": Schema.Union([Schema.Array(Schema.String), Schema.Null]).annotate({ + "description": + "Optional allowlist of OpenRouter API key hashes (`api_keys.hash`) whose traffic is forwarded to this destination. `null` means all keys." + }), + "config": Schema.Struct({ + "account": Schema.String.check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ), + "database": Schema.optionalKey(Schema.String), + "headers": Schema.optionalKey( + Schema.Struct({}).annotate({ "description": "Custom HTTP headers to include in requests to this destination." }) + ), + "schema": Schema.optionalKey(Schema.String), + "table": Schema.optionalKey(Schema.String), + "token": Schema.String.check(Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" })), + "warehouse": Schema.optionalKey(Schema.String) + }), + "created_at": Schema.String.annotate({ "description": "ISO timestamp of when the destination was created." }), + "enabled": Schema.Boolean.annotate({ "description": "Whether this destination is currently enabled." }), + "filter_rules": ObservabilityFilterRulesConfig, + "id": Schema.String.annotate({ "description": "Stable public identifier for this destination.", "format": "uuid" }), + "name": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "Human-readable name for the destination." + }), + "privacy_mode": Schema.Boolean.annotate({ + "description": "When true, request/response bodies are not forwarded to this destination — only metadata." + }), + "sampling_rate": Schema.Number.annotate({ + "description": "Sampling rate for events sent to this destination, between 0.0001 and 1 (1 = 100%).", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "type": Schema.Literal("snowflake"), + "updated_at": Schema.String.annotate({ "description": "ISO timestamp of when the destination was last updated." }), + "workspace_id": Schema.String.annotate({ + "description": "ID of the workspace this destination belongs to.", + "format": "uuid" + }) +}).annotate({ "identifier": "ObservabilitySnowflakeDestination" }) +export type ObservabilityWeaveDestination = { + readonly "api_key_hashes": ReadonlyArray | null + readonly "config": { + readonly "apiKey": string + readonly "baseUrl"?: string + readonly "entity": string + readonly "headers"?: {} + readonly "project": string + } + readonly "created_at": string + readonly "enabled": boolean + readonly "filter_rules": ObservabilityFilterRulesConfig + readonly "id": string + readonly "name": string | null + readonly "privacy_mode": boolean + readonly "sampling_rate": number + readonly "type": "weave" + readonly "updated_at": string + readonly "workspace_id": string +} +export const ObservabilityWeaveDestination = Schema.Struct({ + "api_key_hashes": Schema.Union([Schema.Array(Schema.String), Schema.Null]).annotate({ + "description": + "Optional allowlist of OpenRouter API key hashes (`api_keys.hash`) whose traffic is forwarded to this destination. `null` means all keys." + }), + "config": Schema.Struct({ + "apiKey": Schema.String.check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ), + "baseUrl": Schema.optionalKey(Schema.String), + "entity": Schema.String.check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ), + "headers": Schema.optionalKey( + Schema.Struct({}).annotate({ "description": "Custom HTTP headers to include in requests to this destination." }) + ), + "project": Schema.String.check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ) + }), + "created_at": Schema.String.annotate({ "description": "ISO timestamp of when the destination was created." }), + "enabled": Schema.Boolean.annotate({ "description": "Whether this destination is currently enabled." }), + "filter_rules": ObservabilityFilterRulesConfig, + "id": Schema.String.annotate({ "description": "Stable public identifier for this destination.", "format": "uuid" }), + "name": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "Human-readable name for the destination." + }), + "privacy_mode": Schema.Boolean.annotate({ + "description": "When true, request/response bodies are not forwarded to this destination — only metadata." + }), + "sampling_rate": Schema.Number.annotate({ + "description": "Sampling rate for events sent to this destination, between 0.0001 and 1 (1 = 100%).", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "type": Schema.Literal("weave"), + "updated_at": Schema.String.annotate({ "description": "ISO timestamp of when the destination was last updated." }), + "workspace_id": Schema.String.annotate({ + "description": "ID of the workspace this destination belongs to.", + "format": "uuid" + }) +}).annotate({ "identifier": "ObservabilityWeaveDestination" }) +export type ObservabilityWebhookDestination = { + readonly "api_key_hashes": ReadonlyArray | null + readonly "config": { readonly "headers"?: {}; readonly "method"?: "POST" | "PUT"; readonly "url": string } + readonly "created_at": string + readonly "enabled": boolean + readonly "filter_rules": ObservabilityFilterRulesConfig + readonly "id": string + readonly "name": string | null + readonly "privacy_mode": boolean + readonly "sampling_rate": number + readonly "type": "webhook" + readonly "updated_at": string + readonly "workspace_id": string +} +export const ObservabilityWebhookDestination = Schema.Struct({ + "api_key_hashes": Schema.Union([Schema.Array(Schema.String), Schema.Null]).annotate({ + "description": + "Optional allowlist of OpenRouter API key hashes (`api_keys.hash`) whose traffic is forwarded to this destination. `null` means all keys." + }), + "config": Schema.Struct({ + "headers": Schema.optionalKey(Schema.Struct({})), + "method": Schema.optionalKey(Schema.Literals(["POST", "PUT"])), + "url": Schema.String + }), + "created_at": Schema.String.annotate({ "description": "ISO timestamp of when the destination was created." }), + "enabled": Schema.Boolean.annotate({ "description": "Whether this destination is currently enabled." }), + "filter_rules": ObservabilityFilterRulesConfig, + "id": Schema.String.annotate({ "description": "Stable public identifier for this destination.", "format": "uuid" }), + "name": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "Human-readable name for the destination." + }), + "privacy_mode": Schema.Boolean.annotate({ + "description": "When true, request/response bodies are not forwarded to this destination — only metadata." + }), + "sampling_rate": Schema.Number.annotate({ + "description": "Sampling rate for events sent to this destination, between 0.0001 and 1 (1 = 100%).", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "type": Schema.Literal("webhook"), + "updated_at": Schema.String.annotate({ "description": "ISO timestamp of when the destination was last updated." }), + "workspace_id": Schema.String.annotate({ + "description": "ID of the workspace this destination belongs to.", + "format": "uuid" + }) +}).annotate({ "identifier": "ObservabilityWebhookDestination" }) +export type ObservabilityFilterRulesConfigNullable = Objects_10 | null +export const ObservabilityFilterRulesConfigNullable = Schema.Union([Objects_10, Schema.Null]).annotate({ + "description": "Optional structured filter rules controlling which events are forwarded.", + "identifier": "ObservabilityFilterRulesConfigNullable" +}) +export type BaseTextDeltaEvent = { + readonly "content_index": number + readonly "delta": string + readonly "item_id": string + readonly "logprobs": ReadonlyArray + readonly "output_index": number + readonly "sequence_number": number + readonly "type": "response.output_text.delta" +} +export const BaseTextDeltaEvent = Schema.Struct({ + "content_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "delta": Schema.String, + "item_id": Schema.String, + "logprobs": Schema.Array(OpenResponsesLogProbs), + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.output_text.delta") +}).annotate({ "description": "Event emitted when a text delta is streamed", "identifier": "BaseTextDeltaEvent" }) +export type BaseTextDoneEvent = { + readonly "content_index": number + readonly "item_id": string + readonly "logprobs": ReadonlyArray + readonly "output_index": number + readonly "sequence_number": number + readonly "text": string + readonly "type": "response.output_text.done" +} +export const BaseTextDoneEvent = Schema.Struct({ + "content_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "item_id": Schema.String, + "logprobs": Schema.Array(OpenResponsesLogProbs), + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "text": Schema.String, + "type": Schema.Literal("response.output_text.done") +}).annotate({ "description": "Event emitted when text streaming is complete", "identifier": "BaseTextDoneEvent" }) +export type FileParserPlugin = { + readonly "enabled"?: boolean + readonly "id": "file-parser" + readonly "pdf"?: PDFParserOptions +} +export const FileParserPlugin = Schema.Struct({ + "enabled": Schema.optionalKey( + Schema.Boolean.annotate({ + "description": "Set to false to disable the file-parser plugin for this request. Defaults to true." + }) + ), + "id": Schema.Literal("file-parser"), + "pdf": Schema.optionalKey(PDFParserOptions) +}).annotate({ "identifier": "FileParserPlugin" }) +export type Objects_148 = { + readonly "allow_fallbacks"?: boolean | null + readonly "data_collection"?: "deny" | "allow" | null + readonly "enforce_distillable_text"?: boolean | null + readonly "ignore"?: ReadonlyArray | null + readonly "max_price"?: { + readonly "audio"?: string + readonly "completion"?: string + readonly "image"?: string + readonly "prompt"?: string + readonly "request"?: string + } + readonly "only"?: ReadonlyArray | null + readonly "order"?: ReadonlyArray | null + readonly "preferred_max_latency"?: PreferredMaxLatency + readonly "preferred_min_throughput"?: PreferredMinThroughput + readonly "quantizations"?: ReadonlyArray | null + readonly "require_parameters"?: boolean | null + readonly "sort"?: ProviderSort | ProviderSortConfig | null + readonly "zdr"?: boolean | null +} +export const Objects_148 = Schema.Struct({ + "allow_fallbacks": Schema.optionalKey( + Schema.Union([Schema.Boolean, Schema.Null]).annotate({ + "description": + "Whether to allow backup providers to serve requests\n- true: (default) when the primary provider (or your custom providers in \"order\") is unavailable, use the next best provider.\n- false: use only the primary/custom provider, and return the upstream error if it's unavailable.\n" + }) + ), + "data_collection": Schema.optionalKey( + Schema.Union([Schema.Literal("deny"), Schema.Literal("allow"), Schema.Null]).annotate({ + "description": + "Data collection setting. If no available model provider meets the requirement, your request will return an error.\n- allow: (default) allow providers which store user data non-transiently and may train on it\n\n- deny: use only providers which do not collect user data." + }) + ), + "enforce_distillable_text": Schema.optionalKey( + Schema.Union([Schema.Boolean, Schema.Null]).annotate({ + "description": + "Whether to restrict routing to only models that allow text distillation. When true, only models where the author has allowed distillation will be used." + }) + ), + "ignore": Schema.optionalKey( + Schema.Union([Schema.Array(Schema.Union([ProviderName, Schema.String])), Schema.Null]).annotate({ + "description": + "List of provider slugs to ignore. If provided, this list is merged with your account-wide ignored provider settings for this request." + }) + ), + "max_price": Schema.optionalKey( + Schema.Struct({ + "audio": Schema.optionalKey(Schema.String.annotate({ "description": "Maximum price in USD per audio unit" })), + "completion": Schema.optionalKey( + Schema.String.annotate({ "description": "Maximum price in USD per million completion tokens" }) + ), + "image": Schema.optionalKey(Schema.String.annotate({ "description": "Maximum price in USD per image" })), + "prompt": Schema.optionalKey( + Schema.String.annotate({ "description": "Maximum price in USD per million prompt tokens" }) + ), + "request": Schema.optionalKey(Schema.String.annotate({ "description": "Maximum price in USD per request" })) + }).annotate({ + "description": + "The object specifying the maximum price you want to pay for this request. USD price per million tokens, for prompt and completion." + }) + ), + "only": Schema.optionalKey( + Schema.Union([Schema.Array(Schema.Union([ProviderName, Schema.String])), Schema.Null]).annotate({ + "description": + "List of provider slugs to allow. If provided, this list is merged with your account-wide allowed provider settings for this request." + }) + ), + "order": Schema.optionalKey( + Schema.Union([Schema.Array(Schema.Union([ProviderName, Schema.String])), Schema.Null]).annotate({ + "description": + "An ordered list of provider slugs. The router will attempt to use the first provider in the subset of this list that supports your requested model, and fall back to the next if it is unavailable. If no providers are available, the request will fail with an error message." + }) + ), + "preferred_max_latency": Schema.optionalKey(PreferredMaxLatency), + "preferred_min_throughput": Schema.optionalKey(PreferredMinThroughput), + "quantizations": Schema.optionalKey( + Schema.Union([Schema.Array(Quantization), Schema.Null]).annotate({ + "description": "A list of quantization levels to filter the provider by." + }) + ), + "require_parameters": Schema.optionalKey( + Schema.Union([Schema.Boolean, Schema.Null]).annotate({ + "description": + "Whether to filter providers to only those that support the parameters you've provided. If this setting is omitted or set to false, then providers will receive only the parameters they support, and ignore the rest." + }) + ), + "sort": Schema.optionalKey( + Schema.Union([ProviderSort, ProviderSortConfig, Schema.Null]).annotate({ + "description": + "The sorting strategy to use for this request, if \"order\" is not specified. When set, no load balancing is performed." + }) + ), + "zdr": Schema.optionalKey( + Schema.Union([Schema.Boolean, Schema.Null]).annotate({ + "description": + "Whether to restrict routing to only ZDR (Zero Data Retention) endpoints. When true, only endpoints that do not retain prompts will be used." + }) + ) +}) +export type Arrays_11 = ReadonlyArray +export const Arrays_11 = Schema.Array(PipelineStage) +export type Prediction = Objects_17 | null +export const Prediction = Schema.Union([Objects_17, Schema.Null]).annotate({ + "description": + "Static predicted output content. Supported models can use this to reduce latency when much of the response is known in advance.", + "identifier": "Prediction" +}) +export type ListPresetsResponse = { readonly "data": ReadonlyArray; readonly "total_count": number } +export const ListPresetsResponse = Schema.Struct({ + "data": Schema.Array(Preset), + "total_count": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) +}).annotate({ "description": "A paginated list of presets.", "identifier": "ListPresetsResponse" }) +export type CreatePresetFromInferenceResponse = { readonly "data": PresetWithDesignatedVersion } +export const CreatePresetFromInferenceResponse = Schema.Struct({ "data": PresetWithDesignatedVersion }).annotate({ + "description": "Response containing the created preset with its designated version.", + "identifier": "CreatePresetFromInferenceResponse" +}) +export type GetPresetResponse = { readonly "data": PresetWithDesignatedVersion } +export const GetPresetResponse = Schema.Struct({ "data": PresetWithDesignatedVersion }).annotate({ + "description": "A preset with its currently designated version.", + "identifier": "GetPresetResponse" +}) +export type ChatContentText = { + readonly "cache_control"?: ChatContentCacheControl + readonly "prompt_cache_breakpoint"?: PromptCacheBreakpoint + readonly "text": string + readonly "type": "text" +} +export const ChatContentText = Schema.Struct({ + "cache_control": Schema.optionalKey(ChatContentCacheControl), + "prompt_cache_breakpoint": Schema.optionalKey(PromptCacheBreakpoint), + "text": Schema.String, + "type": Schema.Literal("text") +}).annotate({ "description": "Text content part", "identifier": "ChatContentText" }) +export type CustomToolCallOutputItem = { + readonly "call_id": string + readonly "id"?: string + readonly "output": + | string + | ReadonlyArray< + { + readonly "prompt_cache_breakpoint"?: { readonly "mode": "explicit"; readonly [x: string]: Schema.Json } | null + readonly "text": string + readonly "type": "input_text" + } | { + readonly "detail": "auto" | "high" | "low" | "original" + readonly "image_url"?: string | null + readonly "type": never + readonly "prompt_cache_breakpoint"?: PromptCacheBreakpoint + readonly "text": string + } | { + readonly "file_data"?: string + readonly "file_id"?: string | null + readonly "file_url"?: string + readonly "filename"?: string + readonly "type": never + readonly "prompt_cache_breakpoint"?: PromptCacheBreakpoint + readonly "text": string + } | { + readonly "prompt_cache_breakpoint"?: PromptCacheBreakpoint + readonly "text": string + readonly "type": never + readonly "detail": "auto" | "high" | "low" | "original" + readonly "image_url"?: string | null + } | { + readonly "detail": "auto" | "high" | "low" | "original" + readonly "image_url"?: string | null + readonly "type": "input_image" + } | { + readonly "file_data"?: string + readonly "file_id"?: string | null + readonly "file_url"?: string + readonly "filename"?: string + readonly "type": never + readonly "detail": "auto" | "high" | "low" | "original" + readonly "image_url"?: string | null + } | { + readonly "prompt_cache_breakpoint"?: PromptCacheBreakpoint + readonly "text": string + readonly "type": never + readonly "file_data"?: string + readonly "file_id"?: string | null + readonly "file_url"?: string + readonly "filename"?: string + } | { + readonly "detail": "auto" | "high" | "low" | "original" + readonly "image_url"?: string | null + readonly "type": never + readonly "file_data"?: string + readonly "file_id"?: string | null + readonly "file_url"?: string + readonly "filename"?: string + } | { + readonly "file_data"?: string + readonly "file_id"?: string | null + readonly "file_url"?: string + readonly "filename"?: string + readonly "type": "input_file" + } + > + readonly "type": "custom_tool_call_output" +} +export const CustomToolCallOutputItem = Schema.Struct({ + "call_id": Schema.String, + "id": Schema.optionalKey(Schema.String), + "output": Schema.Union([ + Schema.Union([Schema.String]), + Schema.Union([Schema.Array(Schema.Union([ + Schema.Union([ + Schema.Struct({ + "prompt_cache_breakpoint": Schema.optionalKey( + Schema.Union([ + Schema.Union([ + Schema.StructWithRest(Schema.Struct({ "mode": Schema.Literal("explicit") }), [ + Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })) + ]) + ]).annotate({ + "description": + "Marks an explicit prompt-cache boundary on this content block (OpenAI-style). Everything through the block carrying this marker is part of the candidate cached prefix. Supported natively by OpenAI GPT-5.6 and newer; on providers that use Anthropic-style `cache_control`, OpenRouter converts the marker to that format automatically." + }), + Schema.Union([Schema.Null]).annotate({ + "description": + "Marks an explicit prompt-cache boundary on this content block (OpenAI-style). Everything through the block carrying this marker is part of the candidate cached prefix. Supported natively by OpenAI GPT-5.6 and newer; on providers that use Anthropic-style `cache_control`, OpenRouter converts the marker to that format automatically." + }) + ]).annotate({ + "description": + "Marks an explicit prompt-cache boundary on this content block (OpenAI-style). Everything through the block carrying this marker is part of the candidate cached prefix. Supported natively by OpenAI GPT-5.6 and newer; on providers that use Anthropic-style `cache_control`, OpenRouter converts the marker to that format automatically." + }) + ), + "text": Schema.String, + "type": Schema.Literal("input_text") + }).annotate({ "description": "Text input content item" }), + Schema.Struct({ + "detail": Schema.Literals(["auto", "high", "low", "original"]), + "image_url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "type": Schema.Never, + "prompt_cache_breakpoint": Schema.optionalKey(PromptCacheBreakpoint), + "text": Schema.String + }).annotate({ "description": "Text input content item" }), + Schema.Struct({ + "file_data": Schema.optionalKey(Schema.String), + "file_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "file_url": Schema.optionalKey(Schema.String), + "filename": Schema.optionalKey(Schema.String), + "type": Schema.Never, + "prompt_cache_breakpoint": Schema.optionalKey(PromptCacheBreakpoint), + "text": Schema.String + }).annotate({ "description": "Text input content item" }) + ], { mode: "oneOf" }), + Schema.Union([ + Schema.Struct({ + "prompt_cache_breakpoint": Schema.optionalKey(PromptCacheBreakpoint), + "text": Schema.String, + "type": Schema.Never, + "detail": Schema.Literals(["auto", "high", "low", "original"]), + "image_url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }).annotate({ "description": "Image input content item" }), + Schema.Struct({ + "detail": Schema.Union([ + Schema.Literal("auto"), + Schema.Literal("high"), + Schema.Literal("low"), + Schema.Literal("original") + ]), + "image_url": Schema.optionalKey(Schema.Union([Schema.Union([Schema.String]), Schema.Union([Schema.Null])])), + "type": Schema.Literal("input_image") + }).annotate({ "description": "Image input content item" }), + Schema.Struct({ + "file_data": Schema.optionalKey(Schema.String), + "file_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "file_url": Schema.optionalKey(Schema.String), + "filename": Schema.optionalKey(Schema.String), + "type": Schema.Never, + "detail": Schema.Literals(["auto", "high", "low", "original"]), + "image_url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }).annotate({ "description": "Image input content item" }) + ], { mode: "oneOf" }), + Schema.Union([ + Schema.Struct({ + "prompt_cache_breakpoint": Schema.optionalKey(PromptCacheBreakpoint), + "text": Schema.String, + "type": Schema.Never, + "file_data": Schema.optionalKey(Schema.String), + "file_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "file_url": Schema.optionalKey(Schema.String), + "filename": Schema.optionalKey(Schema.String) + }).annotate({ "description": "File input content item" }), + Schema.Struct({ + "detail": Schema.Literals(["auto", "high", "low", "original"]), + "image_url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "type": Schema.Never, + "file_data": Schema.optionalKey(Schema.String), + "file_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "file_url": Schema.optionalKey(Schema.String), + "filename": Schema.optionalKey(Schema.String) + }).annotate({ "description": "File input content item" }), + Schema.Struct({ + "file_data": Schema.optionalKey(Schema.String), + "file_id": Schema.optionalKey(Schema.Union([Schema.Union([Schema.String]), Schema.Union([Schema.Null])])), + "file_url": Schema.optionalKey(Schema.String), + "filename": Schema.optionalKey(Schema.String), + "type": Schema.Literal("input_file") + }).annotate({ "description": "File input content item" }) + ], { mode: "oneOf" }) + ], { mode: "oneOf" }))]) + ]), + "type": Schema.Literal("custom_tool_call_output") +}).annotate({ + "description": + "The output from a custom (freeform-grammar) tool call execution. Mirrors `function_call_output` but is matched to a `custom_tool_call` rather than a `function_call`.", + "identifier": "CustomToolCallOutputItem" +}) +export type FunctionCallOutputItem = { + readonly "call_id": string + readonly "id"?: string | null + readonly "output": + | string + | ReadonlyArray< + { + readonly "prompt_cache_breakpoint"?: { readonly "mode": "explicit"; readonly [x: string]: Schema.Json } | null + readonly "text": string + readonly "type": "input_text" + } | { + readonly "detail": "auto" | "high" | "low" | "original" + readonly "image_url"?: string | null + readonly "type": never + readonly "prompt_cache_breakpoint"?: PromptCacheBreakpoint + readonly "text": string + } | { + readonly "file_data"?: string + readonly "file_id"?: string | null + readonly "file_url"?: string + readonly "filename"?: string + readonly "type": never + readonly "prompt_cache_breakpoint"?: PromptCacheBreakpoint + readonly "text": string + } | { + readonly "prompt_cache_breakpoint"?: PromptCacheBreakpoint + readonly "text": string + readonly "type": never + readonly "detail": "auto" | "high" | "low" | "original" + readonly "image_url"?: string | null + } | { + readonly "detail": "auto" | "high" | "low" | "original" + readonly "image_url"?: string | null + readonly "type": "input_image" + } | { + readonly "file_data"?: string + readonly "file_id"?: string | null + readonly "file_url"?: string + readonly "filename"?: string + readonly "type": never + readonly "detail": "auto" | "high" | "low" | "original" + readonly "image_url"?: string | null + } | { + readonly "prompt_cache_breakpoint"?: PromptCacheBreakpoint + readonly "text": string + readonly "type": never + readonly "file_data"?: string + readonly "file_id"?: string | null + readonly "file_url"?: string + readonly "filename"?: string + } | { + readonly "detail": "auto" | "high" | "low" | "original" + readonly "image_url"?: string | null + readonly "type": never + readonly "file_data"?: string + readonly "file_id"?: string | null + readonly "file_url"?: string + readonly "filename"?: string + } | { + readonly "file_data"?: string + readonly "file_id"?: string | null + readonly "file_url"?: string + readonly "filename"?: string + readonly "type": "input_file" + } + > + readonly "status"?: ToolCallStatus | null + readonly "type": "function_call_output" +} +export const FunctionCallOutputItem = Schema.Struct({ + "call_id": Schema.String, + "id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "output": Schema.Union([ + Schema.Union([Schema.String]), + Schema.Union([Schema.Array(Schema.Union([ + Schema.Union([ + Schema.Struct({ + "prompt_cache_breakpoint": Schema.optionalKey( + Schema.Union([ + Schema.Union([ + Schema.StructWithRest(Schema.Struct({ "mode": Schema.Literal("explicit") }), [ + Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })) + ]) + ]).annotate({ + "description": + "Marks an explicit prompt-cache boundary on this content block (OpenAI-style). Everything through the block carrying this marker is part of the candidate cached prefix. Supported natively by OpenAI GPT-5.6 and newer; on providers that use Anthropic-style `cache_control`, OpenRouter converts the marker to that format automatically." + }), + Schema.Union([Schema.Null]).annotate({ + "description": + "Marks an explicit prompt-cache boundary on this content block (OpenAI-style). Everything through the block carrying this marker is part of the candidate cached prefix. Supported natively by OpenAI GPT-5.6 and newer; on providers that use Anthropic-style `cache_control`, OpenRouter converts the marker to that format automatically." + }) + ]).annotate({ + "description": + "Marks an explicit prompt-cache boundary on this content block (OpenAI-style). Everything through the block carrying this marker is part of the candidate cached prefix. Supported natively by OpenAI GPT-5.6 and newer; on providers that use Anthropic-style `cache_control`, OpenRouter converts the marker to that format automatically." + }) + ), + "text": Schema.String, + "type": Schema.Literal("input_text") + }).annotate({ "description": "Text input content item" }), + Schema.Struct({ + "detail": Schema.Literals(["auto", "high", "low", "original"]), + "image_url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "type": Schema.Never, + "prompt_cache_breakpoint": Schema.optionalKey(PromptCacheBreakpoint), + "text": Schema.String + }).annotate({ "description": "Text input content item" }), + Schema.Struct({ + "file_data": Schema.optionalKey(Schema.String), + "file_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "file_url": Schema.optionalKey(Schema.String), + "filename": Schema.optionalKey(Schema.String), + "type": Schema.Never, + "prompt_cache_breakpoint": Schema.optionalKey(PromptCacheBreakpoint), + "text": Schema.String + }).annotate({ "description": "Text input content item" }) + ], { mode: "oneOf" }), + Schema.Union([ + Schema.Struct({ + "prompt_cache_breakpoint": Schema.optionalKey(PromptCacheBreakpoint), + "text": Schema.String, + "type": Schema.Never, + "detail": Schema.Literals(["auto", "high", "low", "original"]), + "image_url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }).annotate({ "description": "Image input content item" }), + Schema.Struct({ + "detail": Schema.Union([ + Schema.Literal("auto"), + Schema.Literal("high"), + Schema.Literal("low"), + Schema.Literal("original") + ]), + "image_url": Schema.optionalKey(Schema.Union([Schema.Union([Schema.String]), Schema.Union([Schema.Null])])), + "type": Schema.Literal("input_image") + }).annotate({ "description": "Image input content item" }), + Schema.Struct({ + "file_data": Schema.optionalKey(Schema.String), + "file_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "file_url": Schema.optionalKey(Schema.String), + "filename": Schema.optionalKey(Schema.String), + "type": Schema.Never, + "detail": Schema.Literals(["auto", "high", "low", "original"]), + "image_url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }).annotate({ "description": "Image input content item" }) + ], { mode: "oneOf" }), + Schema.Union([ + Schema.Struct({ + "prompt_cache_breakpoint": Schema.optionalKey(PromptCacheBreakpoint), + "text": Schema.String, + "type": Schema.Never, + "file_data": Schema.optionalKey(Schema.String), + "file_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "file_url": Schema.optionalKey(Schema.String), + "filename": Schema.optionalKey(Schema.String) + }).annotate({ "description": "File input content item" }), + Schema.Struct({ + "detail": Schema.Literals(["auto", "high", "low", "original"]), + "image_url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "type": Schema.Never, + "file_data": Schema.optionalKey(Schema.String), + "file_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "file_url": Schema.optionalKey(Schema.String), + "filename": Schema.optionalKey(Schema.String) + }).annotate({ "description": "File input content item" }), + Schema.Struct({ + "file_data": Schema.optionalKey(Schema.String), + "file_id": Schema.optionalKey(Schema.Union([Schema.Union([Schema.String]), Schema.Union([Schema.Null])])), + "file_url": Schema.optionalKey(Schema.String), + "filename": Schema.optionalKey(Schema.String), + "type": Schema.Literal("input_file") + }).annotate({ "description": "File input content item" }) + ], { mode: "oneOf" }) + ], { mode: "oneOf" }))]) + ]), + "status": Schema.optionalKey(Schema.Union([ToolCallStatus, Schema.Null])), + "type": Schema.Literal("function_call_output") +}).annotate({ "description": "The output from a function call execution", "identifier": "FunctionCallOutputItem" }) +export type InputText = { + readonly "prompt_cache_breakpoint"?: PromptCacheBreakpoint + readonly "text": string + readonly "type": "input_text" +} +export const InputText = Schema.Struct({ + "prompt_cache_breakpoint": Schema.optionalKey(PromptCacheBreakpoint), + "text": Schema.String, + "type": Schema.Literal("input_text") +}).annotate({ "description": "Text input content item", "identifier": "InputText" }) +export type Guardrail = { + readonly "allowed_models"?: ReadonlyArray | null + readonly "allowed_providers"?: ReadonlyArray | null + readonly "content_filter_builtins"?: ReadonlyArray | null + readonly "content_filters"?: ReadonlyArray | null + readonly "created_at": string + readonly "description"?: string | null + readonly "enforce_zdr"?: boolean | null + readonly "enforce_zdr_anthropic"?: boolean | null + readonly "enforce_zdr_google"?: boolean | null + readonly "enforce_zdr_openai"?: boolean | null + readonly "enforce_zdr_other"?: boolean | null + readonly "enforce_zdr_xai"?: boolean | null + readonly "id": string + readonly "ignored_models"?: ReadonlyArray | null + readonly "ignored_providers"?: ReadonlyArray | null + readonly "limit_usd"?: number | null + readonly "name": string + readonly "reset_interval"?: GuardrailInterval + readonly "updated_at"?: string | null + readonly "workspace_id": string +} +export const Guardrail = Schema.Struct({ + "allowed_models": Schema.optionalKey( + Schema.Union([Schema.Array(Schema.String), Schema.Null]).annotate({ + "description": "Array of model canonical_slugs (immutable identifiers)" + }) + ), + "allowed_providers": Schema.optionalKey( + Schema.Union([Schema.Array(Schema.String), Schema.Null]).annotate({ "description": "List of allowed provider IDs" }) + ), + "content_filter_builtins": Schema.optionalKey( + Schema.Union([Schema.Array(ContentFilterBuiltinEntry), Schema.Null]).annotate({ + "description": + "Builtin content filters applied to requests. Includes PII detectors and the regex-based prompt injection detector." + }) + ), + "content_filters": Schema.optionalKey( + Schema.Union([Schema.Array(ContentFilterEntry), Schema.Null]).annotate({ + "description": "Custom regex content filters applied to request messages" + }) + ), + "created_at": Schema.String.annotate({ "description": "ISO 8601 timestamp of when the guardrail was created" }), + "description": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ "description": "Description of the guardrail" }) + ), + "enforce_zdr": Schema.optionalKey( + Schema.Union([Schema.Boolean, Schema.Null]).annotate({ + "description": + "Deprecated. Use enforce_zdr_anthropic, enforce_zdr_openai, enforce_zdr_google, enforce_zdr_xai, and enforce_zdr_other instead. When provided, its value is copied into any of those per-provider fields that are not explicitly specified on the request." + }) + ), + "enforce_zdr_anthropic": Schema.optionalKey( + Schema.Union([Schema.Boolean, Schema.Null]).annotate({ + "description": + "Whether to enforce zero data retention for Anthropic models. Falls back to enforce_zdr when not provided." + }) + ), + "enforce_zdr_google": Schema.optionalKey( + Schema.Union([Schema.Boolean, Schema.Null]).annotate({ + "description": + "Whether to enforce zero data retention for Google models. Falls back to enforce_zdr when not provided." + }) + ), + "enforce_zdr_openai": Schema.optionalKey( + Schema.Union([Schema.Boolean, Schema.Null]).annotate({ + "description": + "Whether to enforce zero data retention for OpenAI models. Falls back to enforce_zdr when not provided." + }) + ), + "enforce_zdr_other": Schema.optionalKey( + Schema.Union([Schema.Boolean, Schema.Null]).annotate({ + "description": + "Whether to enforce zero data retention for models that are not from Anthropic, OpenAI, Google, or xAI. Falls back to enforce_zdr when not provided." + }) + ), + "enforce_zdr_xai": Schema.optionalKey( + Schema.Union([Schema.Boolean, Schema.Null]).annotate({ + "description": + "Whether to enforce zero data retention for xAI models. Falls back to enforce_zdr when not provided." + }) + ), + "id": Schema.String.annotate({ "description": "Unique identifier for the guardrail", "format": "uuid" }), + "ignored_models": Schema.optionalKey( + Schema.Union([Schema.Array(Schema.String), Schema.Null]).annotate({ + "description": "Array of model canonical_slugs to exclude from routing" + }) + ), + "ignored_providers": Schema.optionalKey( + Schema.Union([Schema.Array(Schema.String), Schema.Null]).annotate({ + "description": "List of provider IDs to exclude from routing" + }) + ), + "limit_usd": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), Schema.Null]) + .annotate({ "description": "Spending limit in USD", "format": "double" }) + ), + "name": Schema.String.annotate({ "description": "Name of the guardrail" }), + "reset_interval": Schema.optionalKey(GuardrailInterval), + "updated_at": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "ISO 8601 timestamp of when the guardrail was last updated" + }) + ), + "workspace_id": Schema.String.annotate({ "description": "The workspace ID this guardrail belongs to." }) +}).annotate({ "identifier": "Guardrail" }) +export type CreateGuardrailRequest = { + readonly "allowed_models"?: ReadonlyArray | null + readonly "allowed_providers"?: ReadonlyArray | null + readonly "content_filter_builtins"?: ReadonlyArray | null + readonly "content_filters"?: ReadonlyArray | null + readonly "description"?: string | null + readonly "enforce_zdr"?: boolean | null + readonly "enforce_zdr_anthropic"?: boolean | null + readonly "enforce_zdr_google"?: boolean | null + readonly "enforce_zdr_openai"?: boolean | null + readonly "enforce_zdr_other"?: boolean | null + readonly "enforce_zdr_xai"?: boolean | null + readonly "ignored_models"?: ReadonlyArray | null + readonly "ignored_providers"?: ReadonlyArray | null + readonly "limit_usd"?: number | null + readonly "name": string + readonly "reset_interval"?: GuardrailInterval + readonly "workspace_id"?: string +} +export const CreateGuardrailRequest = Schema.Struct({ + "allowed_models": Schema.optionalKey( + Schema.Union([ + Schema.Array(Schema.String).check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ), + Schema.Null + ]).annotate({ "description": "Array of model identifiers (slug or canonical_slug accepted)" }) + ), + "allowed_providers": Schema.optionalKey( + Schema.Union([ + Schema.Array(Schema.String).check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ), + Schema.Null + ]).annotate({ "description": "List of allowed provider IDs" }) + ), + "content_filter_builtins": Schema.optionalKey( + Schema.Union([Schema.Array(ContentFilterBuiltinEntryInput), Schema.Null]).annotate({ + "description": + "Builtin content filters to apply. Every builtin slug supports \"block\", \"redact\", and the detect-only \"flag\" action." + }) + ), + "content_filters": Schema.optionalKey( + Schema.Union([Schema.Array(ContentFilterEntry), Schema.Null]).annotate({ + "description": "Custom regex content filters to apply to request messages" + }) + ), + "description": Schema.optionalKey( + Schema.Union([ + Schema.String.check(Schema.isMaxLength(1000).annotate({ "expected": "a value with a length of at most 1000" })), + Schema.Null + ]).annotate({ "description": "Description of the guardrail" }) + ), + "enforce_zdr": Schema.optionalKey( + Schema.Union([Schema.Boolean, Schema.Null]).annotate({ + "description": + "Deprecated. Use enforce_zdr_anthropic, enforce_zdr_openai, enforce_zdr_google, enforce_zdr_xai, and enforce_zdr_other instead. When provided, its value is copied into any of those per-provider fields that are not explicitly specified on the request." + }) + ), + "enforce_zdr_anthropic": Schema.optionalKey( + Schema.Union([Schema.Boolean, Schema.Null]).annotate({ + "description": + "Whether to enforce zero data retention for Anthropic models. Falls back to enforce_zdr when not provided." + }) + ), + "enforce_zdr_google": Schema.optionalKey( + Schema.Union([Schema.Boolean, Schema.Null]).annotate({ + "description": + "Whether to enforce zero data retention for Google models. Falls back to enforce_zdr when not provided." + }) + ), + "enforce_zdr_openai": Schema.optionalKey( + Schema.Union([Schema.Boolean, Schema.Null]).annotate({ + "description": + "Whether to enforce zero data retention for OpenAI models. Falls back to enforce_zdr when not provided." + }) + ), + "enforce_zdr_other": Schema.optionalKey( + Schema.Union([Schema.Boolean, Schema.Null]).annotate({ + "description": + "Whether to enforce zero data retention for models that are not from Anthropic, OpenAI, Google, or xAI. Falls back to enforce_zdr when not provided." + }) + ), + "enforce_zdr_xai": Schema.optionalKey( + Schema.Union([Schema.Boolean, Schema.Null]).annotate({ + "description": + "Whether to enforce zero data retention for xAI models. Falls back to enforce_zdr when not provided." + }) + ), + "ignored_models": Schema.optionalKey( + Schema.Union([ + Schema.Array(Schema.String).check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ), + Schema.Null + ]).annotate({ + "description": "Array of model identifiers to exclude from routing (slug or canonical_slug accepted)" + }) + ), + "ignored_providers": Schema.optionalKey( + Schema.Union([ + Schema.Array(Schema.String).check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ), + Schema.Null + ]).annotate({ "description": "List of provider IDs to exclude from routing" }) + ), + "limit_usd": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), Schema.Null]) + .annotate({ "description": "Spending limit in USD", "format": "double" }) + ), + "name": Schema.String.annotate({ "description": "Name for the new guardrail" }).check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ).check(Schema.isMaxLength(200).annotate({ "expected": "a value with a length of at most 200" })), + "reset_interval": Schema.optionalKey(GuardrailInterval), + "workspace_id": Schema.optionalKey( + Schema.String.annotate({ + "description": "The workspace to create the guardrail in. Defaults to the default workspace if not provided.", + "format": "uuid" + }) + ) +}).annotate({ "identifier": "CreateGuardrailRequest" }) +export type UpdateGuardrailRequest = { + readonly "allowed_models"?: ReadonlyArray | null + readonly "allowed_providers"?: ReadonlyArray | null + readonly "content_filter_builtins"?: ReadonlyArray | null + readonly "content_filters"?: ReadonlyArray | null + readonly "description"?: string | null + readonly "enforce_zdr"?: boolean | null + readonly "enforce_zdr_anthropic"?: boolean | null + readonly "enforce_zdr_google"?: boolean | null + readonly "enforce_zdr_openai"?: boolean | null + readonly "enforce_zdr_other"?: boolean | null + readonly "enforce_zdr_xai"?: boolean | null + readonly "ignored_models"?: ReadonlyArray | null + readonly "ignored_providers"?: ReadonlyArray | null + readonly "limit_usd"?: number | null + readonly "name"?: string + readonly "reset_interval"?: GuardrailInterval +} +export const UpdateGuardrailRequest = Schema.Struct({ + "allowed_models": Schema.optionalKey( + Schema.Union([ + Schema.Array(Schema.String).check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ), + Schema.Null + ]).annotate({ "description": "Array of model identifiers (slug or canonical_slug accepted)" }) + ), + "allowed_providers": Schema.optionalKey( + Schema.Union([ + Schema.Array(Schema.String).check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ), + Schema.Null + ]).annotate({ "description": "New list of allowed provider IDs" }) + ), + "content_filter_builtins": Schema.optionalKey( + Schema.Union([Schema.Array(ContentFilterBuiltinEntryInput), Schema.Null]).annotate({ + "description": + "Builtin content filters to apply. Set to null to remove. Every builtin slug supports \"block\", \"redact\", and the detect-only \"flag\" action." + }) + ), + "content_filters": Schema.optionalKey( + Schema.Union([Schema.Array(ContentFilterEntry), Schema.Null]).annotate({ + "description": "Custom regex content filters to apply. Set to null to remove." + }) + ), + "description": Schema.optionalKey( + Schema.Union([ + Schema.String.check(Schema.isMaxLength(1000).annotate({ "expected": "a value with a length of at most 1000" })), + Schema.Null + ]).annotate({ "description": "New description for the guardrail" }) + ), + "enforce_zdr": Schema.optionalKey( + Schema.Union([Schema.Boolean, Schema.Null]).annotate({ + "description": + "Deprecated. Use enforce_zdr_anthropic, enforce_zdr_openai, enforce_zdr_google, enforce_zdr_xai, and enforce_zdr_other instead. When provided, its value is copied into any of those per-provider fields that are not explicitly specified on the request." + }) + ), + "enforce_zdr_anthropic": Schema.optionalKey( + Schema.Union([Schema.Boolean, Schema.Null]).annotate({ + "description": + "Whether to enforce zero data retention for Anthropic models. Falls back to enforce_zdr when not provided." + }) + ), + "enforce_zdr_google": Schema.optionalKey( + Schema.Union([Schema.Boolean, Schema.Null]).annotate({ + "description": + "Whether to enforce zero data retention for Google models. Falls back to enforce_zdr when not provided." + }) + ), + "enforce_zdr_openai": Schema.optionalKey( + Schema.Union([Schema.Boolean, Schema.Null]).annotate({ + "description": + "Whether to enforce zero data retention for OpenAI models. Falls back to enforce_zdr when not provided." + }) + ), + "enforce_zdr_other": Schema.optionalKey( + Schema.Union([Schema.Boolean, Schema.Null]).annotate({ + "description": + "Whether to enforce zero data retention for models that are not from Anthropic, OpenAI, Google, or xAI. Falls back to enforce_zdr when not provided." + }) + ), + "enforce_zdr_xai": Schema.optionalKey( + Schema.Union([Schema.Boolean, Schema.Null]).annotate({ + "description": + "Whether to enforce zero data retention for xAI models. Falls back to enforce_zdr when not provided." + }) + ), + "ignored_models": Schema.optionalKey( + Schema.Union([ + Schema.Array(Schema.String).check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ), + Schema.Null + ]).annotate({ + "description": "Array of model identifiers to exclude from routing (slug or canonical_slug accepted)" + }) + ), + "ignored_providers": Schema.optionalKey( + Schema.Union([ + Schema.Array(Schema.String).check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ), + Schema.Null + ]).annotate({ "description": "List of provider IDs to exclude from routing" }) + ), + "limit_usd": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), Schema.Null]) + .annotate({ "description": "New spending limit in USD", "format": "double" }) + ), + "name": Schema.optionalKey( + Schema.String.annotate({ "description": "New name for the guardrail" }).check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ).check(Schema.isMaxLength(200).annotate({ "expected": "a value with a length of at most 200" })) + ), + "reset_interval": Schema.optionalKey(GuardrailInterval) +}).annotate({ "identifier": "UpdateGuardrailRequest" }) +export type SpeechRequest = { + readonly "input": string + readonly "model": string + readonly "provider"?: { readonly "options"?: ProviderOptions } + readonly "response_format"?: "mp3" | "pcm" + readonly "speed"?: number + readonly "voice": string +} +export const SpeechRequest = Schema.Struct({ + "input": Schema.String.annotate({ "description": "Text to synthesize" }), + "model": Schema.String.annotate({ "description": "TTS model identifier" }), + "provider": Schema.optionalKey( + Schema.Struct({ "options": Schema.optionalKey(ProviderOptions) }).annotate({ + "description": "Provider-specific passthrough configuration" + }) + ), + "response_format": Schema.optionalKey( + Schema.Literals(["mp3", "pcm"]).annotate({ "description": "Audio output format" }) + ), + "speed": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Playback speed multiplier. Only used by models that support it (e.g. OpenAI TTS). Ignored by other providers.", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })) + ), + "voice": Schema.String.annotate({ "description": "Voice identifier (provider-specific)." }) +}).annotate({ "description": "Text-to-speech request input", "identifier": "SpeechRequest" }) +export type STTRequest = { + readonly "input_audio": STTInputAudio + readonly "language"?: string + readonly "model": string + readonly "provider"?: { readonly "options"?: ProviderOptions } + readonly "response_format"?: "json" | "verbose_json" + readonly "temperature"?: number + readonly "timestamp_granularities"?: ReadonlyArray +} +export const STTRequest = Schema.Struct({ + "input_audio": STTInputAudio, + "language": Schema.optionalKey( + Schema.String.annotate({ + "description": "ISO-639-1 language code (e.g., \"en\", \"ja\"). Auto-detected if omitted." + }) + ), + "model": Schema.String.annotate({ "description": "STT model identifier" }), + "provider": Schema.optionalKey( + Schema.Struct({ "options": Schema.optionalKey(ProviderOptions) }).annotate({ + "description": "Provider-specific passthrough configuration" + }) + ), + "response_format": Schema.optionalKey( + Schema.Literals(["json", "verbose_json"]).annotate({ + "description": + "Output format. \"json\" (default) returns { text, usage }. \"verbose_json\" additionally returns task, language, duration, and segment-level timestamps; only supported by OpenAI-compatible providers." + }) + ), + "temperature": Schema.optionalKey( + Schema.Number.annotate({ "description": "Sampling temperature for transcription", "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ) + ), + "timestamp_granularities": Schema.optionalKey( + Schema.Array(STTTimestampGranularity).annotate({ + "description": + "Timestamp detail levels to include when response_format is \"verbose_json\". \"segment\" returns segment-level timestamps; \"word\" additionally returns word-level timestamps in the words array. Ignored unless response_format is \"verbose_json\"." + }) + ) +}).annotate({ + "description": "Speech-to-text request input. Accepts a JSON body with input_audio containing base64-encoded audio.", + "identifier": "STTRequest" +}) +export type VideoGenerationRequest = { + readonly "aspect_ratio"?: "16:9" | "9:16" | "1:1" | "4:3" | "3:4" | "3:2" | "2:3" | "21:9" | "9:21" + readonly "callback_url"?: string + readonly "duration"?: number + readonly "frame_images"?: ReadonlyArray + readonly "generate_audio"?: boolean + readonly "input_references"?: ReadonlyArray + readonly "model": string + readonly "prompt"?: string + readonly "provider"?: { readonly "options"?: ProviderOptions } + readonly "resolution"?: "480p" | "720p" | "1080p" | "1K" | "2K" | "4K" + readonly "seed"?: number + readonly "size"?: string +} +export const VideoGenerationRequest = Schema.Struct({ + "aspect_ratio": Schema.optionalKey( + Schema.Literals(["16:9", "9:16", "1:1", "4:3", "3:4", "3:2", "2:3", "21:9", "9:21"]).annotate({ + "description": "Aspect ratio of the generated video" + }) + ), + "callback_url": Schema.optionalKey( + Schema.String.annotate({ + "description": + "URL to receive a webhook notification when the video generation job completes. Overrides the workspace-level default callback URL if set. Must be HTTPS.", + "format": "uri" + }) + ), + "duration": Schema.optionalKey( + Schema.Number.annotate({ "description": "Duration of the generated video in seconds" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ).check(Schema.isGreaterThanOrEqualTo(1).annotate({ "expected": "a value greater than or equal to 1" })) + ), + "frame_images": Schema.optionalKey( + Schema.Array(FrameImage).annotate({ + "description": + "Images to use as the first and/or last frame of the generated video. Each image must specify a frame_type of first_frame or last_frame." + }) + ), + "generate_audio": Schema.optionalKey( + Schema.Boolean.annotate({ + "description": + "Whether to generate audio alongside the video. Defaults to the endpoint's generate_audio capability flag, false if not set." + }) + ), + "input_references": Schema.optionalKey( + Schema.Array(InputReference).annotate({ + "description": + "Reference assets to guide video generation. Accepts image, audio, and video references. Audio and video references are only honored by providers that support them (currently BytePlus Seedance 2.0); other providers use image references and ignore the rest." + }) + ), + "model": Schema.String, + "prompt": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Text prompt describing the video to generate. Optional for models that support generating a video from image input alone; required by all other models." + }) + ), + "provider": Schema.optionalKey( + Schema.Struct({ "options": Schema.optionalKey(ProviderOptions) }).annotate({ + "description": "Provider-specific passthrough configuration" + }) + ), + "resolution": Schema.optionalKey( + Schema.Literals(["480p", "720p", "1080p", "1K", "2K", "4K"]).annotate({ + "description": "Resolution of the generated video" + }) + ), + "seed": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "If specified, the generation will sample deterministically, such that repeated requests with the same seed and parameters should return the same result. Determinism is not guaranteed for all providers." + }).check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "size": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Exact pixel dimensions of the generated video in \"WIDTHxHEIGHT\" format (e.g. \"1280x720\"). Interchangeable with resolution + aspect_ratio." + }) + ) +}).annotate({ "identifier": "VideoGenerationRequest" }) +export type ImageGenerationProviderPreferences = { + readonly "allow_fallbacks"?: boolean | null + readonly "ignore"?: Union_2 + readonly "only"?: Union_3 + readonly "options"?: ProviderOptions + readonly "order"?: Union_4 + readonly "sort"?: Union_5 +} +export const ImageGenerationProviderPreferences = Schema.Struct({ + "allow_fallbacks": Schema.optionalKey( + Schema.Union([Schema.Boolean, Schema.Null]).annotate({ + "description": + "Whether to allow backup providers to serve requests\n- true: (default) when the primary provider (or your custom providers in \"order\") is unavailable, use the next best provider.\n- false: use only the primary/custom provider, and return the upstream error if it's unavailable.\n" + }) + ), + "ignore": Schema.optionalKey(Union_2), + "only": Schema.optionalKey(Union_3), + "options": Schema.optionalKey(ProviderOptions), + "order": Schema.optionalKey(Union_4), + "sort": Schema.optionalKey(Union_5) +}).annotate({ + "description": "Provider routing preferences and provider-specific passthrough configuration.", + "identifier": "ImageGenerationProviderPreferences" +}) +export type ListEndpointsResponse = { + readonly "architecture": { + readonly "input_modalities": ReadonlyArray<"text" | "image" | "file" | "audio" | "video"> + readonly "instruct_type": + | "none" + | "airoboros" + | "alpaca" + | "alpaca-modif" + | "chatml" + | "claude" + | "code-llama" + | "gemma" + | "llama2" + | "llama3" + | "mistral" + | "nemotron" + | "neural" + | "openchat" + | "phi3" + | "rwkv" + | "vicuna" + | "zephyr" + | "deepseek-r1" + | "deepseek-v3.1" + | "qwq" + | "qwen3" + | null + readonly "modality": string | null + readonly "output_modalities": ReadonlyArray< + "text" | "image" | "embeddings" | "audio" | "video" | "rerank" | "speech" | "transcription" + > + readonly "tokenizer": + | "Router" + | "Media" + | "Other" + | "GPT" + | "Claude" + | "Gemini" + | "Gemma" + | "Grok" + | "Cohere" + | "Nova" + | "Qwen" + | "Yi" + | "DeepSeek" + | "Mistral" + | "Llama2" + | "Llama3" + | "Llama4" + | "PaLM" + | "RWKV" + | "Qwen3" + } + readonly "created": number + readonly "description": string + readonly "endpoints": ReadonlyArray + readonly "id": string + readonly "name": string +} +export const ListEndpointsResponse = Schema.Struct({ + "architecture": Schema.Struct({ + "input_modalities": Schema.Array( + Schema.Union([ + Schema.Literal("text"), + Schema.Literal("image"), + Schema.Literal("file"), + Schema.Literal("audio"), + Schema.Literal("video") + ]) + ).annotate({ "description": "Supported input modalities" }), + "instruct_type": Schema.Union([ + Schema.Literal("none").annotate({ "description": "Instruction format type" }), + Schema.Literal("airoboros").annotate({ "description": "Instruction format type" }), + Schema.Literal("alpaca").annotate({ "description": "Instruction format type" }), + Schema.Literal("alpaca-modif").annotate({ "description": "Instruction format type" }), + Schema.Literal("chatml").annotate({ "description": "Instruction format type" }), + Schema.Literal("claude").annotate({ "description": "Instruction format type" }), + Schema.Literal("code-llama").annotate({ "description": "Instruction format type" }), + Schema.Literal("gemma").annotate({ "description": "Instruction format type" }), + Schema.Literal("llama2").annotate({ "description": "Instruction format type" }), + Schema.Literal("llama3").annotate({ "description": "Instruction format type" }), + Schema.Literal("mistral").annotate({ "description": "Instruction format type" }), + Schema.Literal("nemotron").annotate({ "description": "Instruction format type" }), + Schema.Literal("neural").annotate({ "description": "Instruction format type" }), + Schema.Literal("openchat").annotate({ "description": "Instruction format type" }), + Schema.Literal("phi3").annotate({ "description": "Instruction format type" }), + Schema.Literal("rwkv").annotate({ "description": "Instruction format type" }), + Schema.Literal("vicuna").annotate({ "description": "Instruction format type" }), + Schema.Literal("zephyr").annotate({ "description": "Instruction format type" }), + Schema.Literal("deepseek-r1").annotate({ "description": "Instruction format type" }), + Schema.Literal("deepseek-v3.1").annotate({ "description": "Instruction format type" }), + Schema.Literal("qwq").annotate({ "description": "Instruction format type" }), + Schema.Literal("qwen3").annotate({ "description": "Instruction format type" }), + Schema.Union([Schema.Null]).annotate({ "description": "Instruction format type" }) + ]).annotate({ "description": "Instruction format type" }), + "modality": Schema.Union([ + Schema.Union([Schema.String]).annotate({ "description": "Primary modality of the model" }), + Schema.Union([Schema.Null]).annotate({ "description": "Primary modality of the model" }) + ]).annotate({ "description": "Primary modality of the model" }), + "output_modalities": Schema.Array( + Schema.Union([ + Schema.Literal("text"), + Schema.Literal("image"), + Schema.Literal("embeddings"), + Schema.Literal("audio"), + Schema.Literal("video"), + Schema.Literal("rerank"), + Schema.Literal("speech"), + Schema.Literal("transcription") + ]) + ).annotate({ "description": "Supported output modalities" }), + "tokenizer": Schema.Union([ + Schema.Union([Schema.Literal("Router").annotate({ "description": "Tokenizer type used by the model" })]), + Schema.Union([Schema.Literal("Media").annotate({ "description": "Tokenizer type used by the model" })]), + Schema.Union([Schema.Literal("Other").annotate({ "description": "Tokenizer type used by the model" })]), + Schema.Union([Schema.Literal("GPT").annotate({ "description": "Tokenizer type used by the model" })]), + Schema.Union([Schema.Literal("Claude").annotate({ "description": "Tokenizer type used by the model" })]), + Schema.Union([Schema.Literal("Gemini").annotate({ "description": "Tokenizer type used by the model" })]), + Schema.Union([Schema.Literal("Gemma").annotate({ "description": "Tokenizer type used by the model" })]), + Schema.Union([Schema.Literal("Grok").annotate({ "description": "Tokenizer type used by the model" })]), + Schema.Union([Schema.Literal("Cohere").annotate({ "description": "Tokenizer type used by the model" })]), + Schema.Union([Schema.Literal("Nova").annotate({ "description": "Tokenizer type used by the model" })]), + Schema.Union([Schema.Literal("Qwen").annotate({ "description": "Tokenizer type used by the model" })]), + Schema.Union([Schema.Literal("Yi").annotate({ "description": "Tokenizer type used by the model" })]), + Schema.Union([Schema.Literal("DeepSeek").annotate({ "description": "Tokenizer type used by the model" })]), + Schema.Union([Schema.Literal("Mistral").annotate({ "description": "Tokenizer type used by the model" })]), + Schema.Union([Schema.Literal("Llama2").annotate({ "description": "Tokenizer type used by the model" })]), + Schema.Union([Schema.Literal("Llama3").annotate({ "description": "Tokenizer type used by the model" })]), + Schema.Union([Schema.Literal("Llama4").annotate({ "description": "Tokenizer type used by the model" })]), + Schema.Union([Schema.Literal("PaLM").annotate({ "description": "Tokenizer type used by the model" })]), + Schema.Union([Schema.Literal("RWKV").annotate({ "description": "Tokenizer type used by the model" })]), + Schema.Union([Schema.Literal("Qwen3").annotate({ "description": "Tokenizer type used by the model" })]) + ]).annotate({ "description": "Tokenizer type used by the model" }) + }).annotate({ "description": "Model architecture information" }), + "created": Schema.Number.annotate({ "description": "Unix timestamp of when the model was created" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ), + "description": Schema.String.annotate({ "description": "Description of the model" }), + "endpoints": Schema.Array(PublicEndpoint).annotate({ "description": "List of available endpoints for this model" }), + "id": Schema.String.annotate({ "description": "Unique identifier for the model" }), + "name": Schema.String.annotate({ "description": "Display name of the model" }) +}).annotate({ "description": "List of available endpoints for a model", "identifier": "ListEndpointsResponse" }) +export type ModelReasoning = { + readonly "default_effort"?: ReasoningEffort + readonly "default_enabled"?: boolean + readonly "mandatory": boolean + readonly "supported_efforts"?: Union_12 + readonly "supports_max_tokens"?: boolean +} +export const ModelReasoning = Schema.Struct({ + "default_effort": Schema.optionalKey( + Schema.suspend((): Schema.Codec => ReasoningEffort).annotate({ + "description": + "Default reasoning effort when the client enables reasoning without specifying effort. Maps to `reasoning.effort` in chat requests. When `\"none\"`, prefer omitting effort unless the user explicitly disables reasoning." + }) + ), + "default_enabled": Schema.optionalKey( + Schema.Boolean.annotate({ + "description": "Default reasoning enabled state when the client does not set `reasoning.enabled`." + }) + ), + "mandatory": Schema.Boolean.annotate({ + "description": "When true, reasoning cannot be disabled and effort \"none\" is rejected." + }), + "supported_efforts": Schema.optionalKey(Union_12), + "supports_max_tokens": Schema.optionalKey( + Schema.Boolean.annotate({ + "description": + "Present and `true` when the model accepts `reasoning.max_tokens` in requests (Anthropic-style) instead of or in addition to `reasoning.effort`. Omitted otherwise." + }) + ) +}).annotate({ + "description": "Reasoning effort configuration. Omitted for non-reasoning models and dynamic router models.", + "identifier": "ModelReasoning" +}) +export type ReasoningDetailUnion = + | ReasoningDetailSummary + | ReasoningDetailEncrypted + | ReasoningDetailText + | ReasoningDetailServerToolCall +export const ReasoningDetailUnion = Schema.Union([ + ReasoningDetailSummary, + ReasoningDetailEncrypted, + ReasoningDetailText, + ReasoningDetailServerToolCall +], { mode: "oneOf" }).annotate({ "description": "Reasoning detail union schema", "identifier": "ReasoningDetailUnion" }) +export type BaseReasoningConfig = Objects_5 | null +export const BaseReasoningConfig = Schema.Union([Objects_5, Schema.Null]).annotate({ + "identifier": "BaseReasoningConfig" +}) +export type ReasoningConfig = Union_13 | null +export const ReasoningConfig = Schema.Union([Union_13, Schema.Null]).annotate({ + "description": "Configuration for reasoning mode in the response", + "identifier": "ReasoningConfig" +}) +export type ChatUsage = { + readonly "completion_tokens": number + readonly "completion_tokens_details"?: Union_ + readonly "cost"?: number | null + readonly "cost_details"?: CostDetails + readonly "is_byok"?: boolean + readonly "prompt_tokens": number + readonly "prompt_tokens_details"?: Union_1 + readonly "server_tool_use_details"?: ServerToolUseDetails + readonly "total_tokens": number +} +export const ChatUsage = Schema.Struct({ + "completion_tokens": Schema.Number.annotate({ "description": "Number of tokens in the completion" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ), + "completion_tokens_details": Schema.optionalKey(Union_), + "cost": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), Schema.Null]) + .annotate({ "description": "Cost of the completion", "format": "double" }) + ), + "cost_details": Schema.optionalKey(CostDetails), + "is_byok": Schema.optionalKey( + Schema.Boolean.annotate({ "description": "Whether a request was made using a Bring Your Own Key configuration" }) + ), + "prompt_tokens": Schema.Number.annotate({ "description": "Number of tokens in the prompt" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ), + "prompt_tokens_details": Schema.optionalKey(Union_1), + "server_tool_use_details": Schema.optionalKey(ServerToolUseDetails), + "total_tokens": Schema.Number.annotate({ "description": "Total number of tokens" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ) +}).annotate({ "description": "Token usage statistics", "identifier": "ChatUsage" }) +export type Usage = { + readonly "input_tokens": number + readonly "input_tokens_details": { readonly "cache_write_tokens"?: number | null; readonly "cached_tokens": number } + readonly "output_tokens": number + readonly "output_tokens_details": { readonly "reasoning_tokens": number } + readonly "total_tokens": number + readonly "cost"?: number | null + readonly "cost_details"?: { + readonly "upstream_inference_cost"?: number | null + readonly "upstream_inference_input_cost": number + readonly "upstream_inference_output_cost": number + } + readonly "is_byok"?: boolean + readonly "server_tool_use_details"?: ServerToolUseDetails +} | null +export const Usage = Schema.Union([ + Schema.Struct({ + "input_tokens": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "input_tokens_details": Schema.Struct({ + "cache_write_tokens": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]) + ), + "cached_tokens": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + }), + "output_tokens": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "output_tokens_details": Schema.Struct({ + "reasoning_tokens": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + }), + "total_tokens": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "cost": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), Schema.Null]) + .annotate({ "description": "Cost of the completion", "format": "double" }) + ), + "cost_details": Schema.optionalKey(Schema.Struct({ + "upstream_inference_cost": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), Schema.Null]) + .annotate({ "format": "double" }) + ), + "upstream_inference_input_cost": Schema.Number.annotate({ "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ), + "upstream_inference_output_cost": Schema.Number.annotate({ "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ) + })), + "is_byok": Schema.optionalKey( + Schema.Boolean.annotate({ "description": "Whether a request was made using a Bring Your Own Key configuration" }) + ), + "server_tool_use_details": Schema.optionalKey(ServerToolUseDetails) + }), + Schema.Null +]).annotate({ "description": "Token usage information for the response", "identifier": "Usage" }) +export type StopServerToolsWhen = ReadonlyArray +export const StopServerToolsWhen = Schema.Array(StopServerToolsWhenCondition).annotate({ + "description": + "Stop conditions for the server-tool agent loop. Any condition firing halts the loop (OR logic). When set, this overrides `max_tool_calls`. When a condition fires while the model is still emitting tool calls, the pending tool calls are executed and one final turn is made with tool calls disabled so the response ends with a natural-language answer instead of an unfinished tool call." +}).check( + Schema.isMinLength(1).annotate({ + "expected": "a value with a length of at least 1", + "identifier": "StopServerToolsWhen" + }) +) +export type SubagentServerToolConfig = { + readonly "instructions"?: string + readonly "max_completion_tokens"?: number + readonly "max_tool_calls"?: number + readonly "model"?: string + readonly "name"?: string + readonly "reasoning"?: SubagentReasoning + readonly "temperature"?: number + readonly "tools"?: Arrays_12 +} +export const SubagentServerToolConfig = Schema.Struct({ + "instructions": Schema.optionalKey( + Schema.String.annotate({ + "description": + "System instructions for the subagent. When omitted, the subagent responds with no system prompt of its own." + }) + ), + "max_completion_tokens": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Maximum number of output tokens (including reasoning) the subagent may produce. When omitted, the provider's default applies." + }).check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "max_tool_calls": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Maximum number of tool-calling steps the subagent may take during its agentic loop. Capped at 25. Only relevant when the subagent is given tools. Accepted and validated but not yet enforced on the subagent call." + }).check(Schema.isInt().annotate({ "expected": "an integer" })).check( + Schema.isGreaterThanOrEqualTo(1).annotate({ "expected": "a value greater than or equal to 1" }) + ).check(Schema.isLessThanOrEqualTo(25).annotate({ "expected": "a value less than or equal to 25" })) + ), + "model": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Slug of the model that executes delegated tasks (any OpenRouter model). Typically a smaller, cheaper, faster model than the one delegating. When omitted, the model from the outer API request is used. The subagent tool itself cannot be the subagent model." + }) + ), + "name": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Optional name for this subagent. The model sees one tool per named subagent (and one default for an unnamed entry). Names must be unique across subagent entries. Letters, digits, spaces, underscores, and dashes; trimmed; 1–64 chars." + }).check(Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" })).check( + Schema.isMaxLength(64).annotate({ "expected": "a value with a length of at most 64" }) + ).check( + Schema.isPattern(new RegExp("^[a-zA-Z0-9 _-]+$")).annotate({ + "expected": "a string matching the RegExp ^[a-zA-Z0-9 _-]+$" + }) + ) + ), + "reasoning": Schema.optionalKey(SubagentReasoning), + "temperature": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Sampling temperature forwarded to the subagent call. When omitted, the provider's default applies.", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })) + ), + "tools": Schema.optionalKey(Arrays_12) +}).annotate({ + "description": "Configuration for one openrouter:subagent server tool entry.", + "identifier": "SubagentServerToolConfig" +}) +export type ImageModelEndpointsResponse = { readonly "endpoints": ReadonlyArray; readonly "id": string } +export const ImageModelEndpointsResponse = Schema.Struct({ + "endpoints": Schema.Array(ImageEndpoint), + "id": Schema.String.annotate({ "description": "Model slug" }) +}).annotate({ + "description": "The full per-endpoint records for an image model.", + "identifier": "ImageModelEndpointsResponse" +}) +export type TaskClassificationResponse = { + readonly "data": { + readonly "as_of": string + readonly "classifications": ReadonlyArray + readonly "macro_categories": ReadonlyArray + readonly "window_days": number + } +} +export const TaskClassificationResponse = Schema.Struct({ + "data": Schema.Struct({ + "as_of": Schema.String.annotate({ + "description": + "UTC date (YYYY-MM-DD) of the window upper bound (yesterday). Data is exclusive of the current incomplete UTC day. This is the expected latest date in the snapshot; it does not confirm data presence for that date." + }), + "classifications": Schema.Array(TaskClassificationItem).annotate({ + "description": "Per-task classification market-share data, sorted by usage_share descending." + }), + "macro_categories": Schema.Array(TaskClassificationMacroCategory).annotate({ + "description": "Aggregate market-share data per macro-category (code, data, agent, general)." + }), + "window_days": Schema.Number.annotate({ "description": "Number of trailing days covered by this snapshot." }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ) + }) +}).annotate({ "identifier": "TaskClassificationResponse" }) +export type UnifiedBenchmarksResponse = { + readonly "data": ReadonlyArray + readonly "meta": UnifiedBenchmarksMeta +} +export const UnifiedBenchmarksResponse = Schema.Struct({ + "data": Schema.Array(Schema.Union([UnifiedBenchmarksAAItem, UnifiedBenchmarksDAItem], { mode: "oneOf" })), + "meta": UnifiedBenchmarksMeta +}).annotate({ "identifier": "UnifiedBenchmarksResponse" }) +export type AnnotationAddedEvent = { + readonly "annotation": OpenAIResponsesAnnotation + readonly "annotation_index": number + readonly "content_index": number + readonly "item_id": string + readonly "output_index": number + readonly "sequence_number": number + readonly "type": "response.output_text.annotation.added" +} +export const AnnotationAddedEvent = Schema.Struct({ + "annotation": OpenAIResponsesAnnotation, + "annotation_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "content_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "item_id": Schema.String, + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.output_text.annotation.added") +}).annotate({ + "description": "Event emitted when a text annotation is added to output", + "identifier": "AnnotationAddedEvent" +}) +export type BaseAnnotationAddedEvent = { + readonly "annotation": OpenAIResponsesAnnotation + readonly "annotation_index": number + readonly "content_index": number + readonly "item_id": string + readonly "output_index": number + readonly "sequence_number": number + readonly "type": "response.output_text.annotation.added" +} +export const BaseAnnotationAddedEvent = Schema.Struct({ + "annotation": OpenAIResponsesAnnotation, + "annotation_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "content_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "item_id": Schema.String, + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.output_text.annotation.added") +}).annotate({ + "description": "Event emitted when a text annotation is added to output", + "identifier": "BaseAnnotationAddedEvent" +}) +export type ResponseOutputText = { + readonly "annotations"?: ReadonlyArray + readonly "logprobs"?: ReadonlyArray< + { + readonly "bytes": ReadonlyArray + readonly "logprob": number + readonly "token": string + readonly "top_logprobs": ReadonlyArray< + { readonly "bytes": ReadonlyArray; readonly "logprob": number; readonly "token": string } + > + } + > + readonly "text": string + readonly "type": "output_text" +} +export const ResponseOutputText = Schema.Struct({ + "annotations": Schema.optionalKey(Schema.Array(OpenAIResponsesAnnotation)), + "logprobs": Schema.optionalKey(Schema.Array(Schema.Struct({ + "bytes": Schema.Array(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "logprob": Schema.Number.annotate({ "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ), + "token": Schema.String, + "top_logprobs": Schema.Array( + Schema.Struct({ + "bytes": Schema.Array(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "logprob": Schema.Number.annotate({ "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ), + "token": Schema.String + }) + ) + }))), + "text": Schema.String, + "type": Schema.Literal("output_text") +}).annotate({ "identifier": "ResponseOutputText" }) +export type WebFetchServerTool = { + readonly "parameters"?: WebFetchServerToolConfig + readonly "type": "openrouter:web_fetch" +} +export const WebFetchServerTool = Schema.Struct({ + "parameters": Schema.optionalKey(WebFetchServerToolConfig), + "type": Schema.Literal("openrouter:web_fetch") +}).annotate({ + "description": "OpenRouter built-in server tool: fetches full content from a URL (web page or PDF)", + "identifier": "WebFetchServerTool" +}) +export type Preview_20250311_WebSearchServerTool = { + readonly "engine"?: WebSearchEngineEnum + readonly "filters"?: WebSearchDomainFilter + readonly "max_results"?: number + readonly "search_context_size"?: SearchContextSizeEnum + readonly "type": "web_search_preview_2025_03_11" + readonly "user_location"?: Preview_WebSearchUserLocation +} +export const Preview_20250311_WebSearchServerTool = Schema.Struct({ + "engine": Schema.optionalKey(WebSearchEngineEnum), + "filters": Schema.optionalKey(WebSearchDomainFilter), + "max_results": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Maximum number of search results to return per search call. Defaults to 5. Applies to Exa, Firecrawl, Parallel, and Perplexity engines; ignored with native provider search. Perplexity supports a maximum of 20; values above 20 are clamped." + }).check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "search_context_size": Schema.optionalKey(SearchContextSizeEnum), + "type": Schema.Literal("web_search_preview_2025_03_11"), + "user_location": Schema.optionalKey(Preview_WebSearchUserLocation) +}).annotate({ + "description": "Web search preview tool configuration (2025-03-11 version)", + "identifier": "Preview_20250311_WebSearchServerTool" +}) +export type Preview_WebSearchServerTool = { + readonly "engine"?: WebSearchEngineEnum + readonly "filters"?: WebSearchDomainFilter + readonly "max_results"?: number + readonly "search_context_size"?: SearchContextSizeEnum + readonly "type": "web_search_preview" + readonly "user_location"?: Preview_WebSearchUserLocation +} +export const Preview_WebSearchServerTool = Schema.Struct({ + "engine": Schema.optionalKey(WebSearchEngineEnum), + "filters": Schema.optionalKey(WebSearchDomainFilter), + "max_results": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Maximum number of search results to return per search call. Defaults to 5. Applies to Exa, Firecrawl, Parallel, and Perplexity engines; ignored with native provider search. Perplexity supports a maximum of 20; values above 20 are clamped." + }).check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "search_context_size": Schema.optionalKey(SearchContextSizeEnum), + "type": Schema.Literal("web_search_preview"), + "user_location": Schema.optionalKey(Preview_WebSearchUserLocation) +}).annotate({ "description": "Web search preview tool configuration", "identifier": "Preview_WebSearchServerTool" }) +export type Legacy_WebSearchServerTool = { + readonly "engine"?: WebSearchEngineEnum + readonly "filters"?: WebSearchDomainFilter + readonly "max_results"?: number + readonly "search_context_size"?: SearchContextSizeEnum + readonly "type": "web_search" + readonly "user_location"?: WebSearchUserLocation +} +export const Legacy_WebSearchServerTool = Schema.Struct({ + "engine": Schema.optionalKey(WebSearchEngineEnum), + "filters": Schema.optionalKey(WebSearchDomainFilter), + "max_results": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Maximum number of search results to return per search call. Defaults to 5. Applies to Exa, Firecrawl, Parallel, and Perplexity engines; ignored with native provider search. Perplexity supports a maximum of 20; values above 20 are clamped." + }).check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "search_context_size": Schema.optionalKey(SearchContextSizeEnum), + "type": Schema.Literal("web_search"), + "user_location": Schema.optionalKey(WebSearchUserLocation) +}).annotate({ "description": "Web search tool configuration", "identifier": "Legacy_WebSearchServerTool" }) +export type WebSearchServerTool = { + readonly "engine"?: WebSearchEngineEnum + readonly "filters"?: WebSearchDomainFilter + readonly "max_results"?: number + readonly "search_context_size"?: SearchContextSizeEnum + readonly "type": "web_search_2025_08_26" + readonly "user_location"?: WebSearchUserLocation +} +export const WebSearchServerTool = Schema.Struct({ + "engine": Schema.optionalKey(WebSearchEngineEnum), + "filters": Schema.optionalKey(WebSearchDomainFilter), + "max_results": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Maximum number of search results to return per search call. Defaults to 5. Applies to Exa, Firecrawl, Parallel, and Perplexity engines; ignored with native provider search. Perplexity supports a maximum of 20; values above 20 are clamped." + }).check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "search_context_size": Schema.optionalKey(SearchContextSizeEnum), + "type": Schema.Literal("web_search_2025_08_26"), + "user_location": Schema.optionalKey(WebSearchUserLocation) +}).annotate({ + "description": "Web search tool configuration (2025-08-26 version)", + "identifier": "WebSearchServerTool" +}) +export type ChatWebSearchShorthand = { + readonly "allowed_domains"?: ReadonlyArray + readonly "engine"?: WebSearchEngineEnum + readonly "excluded_domains"?: ReadonlyArray + readonly "max_characters"?: number + readonly "max_results"?: number + readonly "max_total_results"?: number + readonly "parameters"?: WebSearchConfig + readonly "search_context_size"?: SearchQualityLevel + readonly "type": "web_search" | "web_search_preview" | "web_search_preview_2025_03_11" | "web_search_2025_08_26" + readonly "user_location"?: WebSearchUserLocationServerTool +} +export const ChatWebSearchShorthand = Schema.Struct({ + "allowed_domains": Schema.optionalKey( + Schema.Array(Schema.String).annotate({ + "description": + "Limit search results to these domains. Supported by Exa, Firecrawl, Parallel, Perplexity, and most native providers (Anthropic, OpenAI, xAI). Cannot be used with excluded_domains." + }) + ), + "engine": Schema.optionalKey(WebSearchEngineEnum), + "excluded_domains": Schema.optionalKey( + Schema.Array(Schema.String).annotate({ + "description": + "Exclude search results from these domains. Supported by Exa, Firecrawl, Parallel, Perplexity, Anthropic, and xAI. Not supported with OpenAI (silently ignored). Cannot be used with allowed_domains." + }) + ), + "max_characters": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Exact maximum number of characters of content per search result. Applies to the Exa, Parallel, and Perplexity engines; ignored with native provider search and Firecrawl. For Exa, caps highlight content per result. For Parallel, caps excerpt content per result (default 1,500 when omitted). For Perplexity, maps to the native `max_tokens_per_page` parameter (converted from characters to tokens) and trims the response to the exact character cap. When both `max_characters` and `search_context_size` are set, `max_characters` takes precedence. When omitted, falls back to `search_context_size` mapping (Exa) or engine defaults (Parallel, Perplexity)." + }).check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "max_results": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Maximum number of search results to return per search call. Defaults to 5. Applies to Exa, Firecrawl, Parallel, and Perplexity engines; ignored with native provider search. Perplexity supports a maximum of 20; values above 20 are clamped." + }).check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "max_total_results": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Maximum total number of search results across all search calls in a single request. Once this limit is reached, the tool will stop returning new results. Useful for controlling cost and context size in agentic loops. Defaults to 50 when not specified." + }).check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "parameters": Schema.optionalKey(WebSearchConfig), + "search_context_size": Schema.optionalKey(SearchQualityLevel), + "type": Schema.Literals([ + "web_search", + "web_search_preview", + "web_search_preview_2025_03_11", + "web_search_2025_08_26" + ]), + "user_location": Schema.optionalKey(WebSearchUserLocationServerTool) +}).annotate({ + "description": "Web search tool using OpenAI Responses API syntax. Automatically converted to openrouter:web_search.", + "identifier": "ChatWebSearchShorthand" +}) +export type OpenRouterWebSearchServerTool = { + readonly "parameters"?: WebSearchConfig + readonly "type": "openrouter:web_search" +} +export const OpenRouterWebSearchServerTool = Schema.Struct({ + "parameters": Schema.optionalKey(WebSearchConfig), + "type": Schema.Literal("openrouter:web_search") +}).annotate({ + "description": "OpenRouter built-in server tool: searches the web for current information", + "identifier": "OpenRouterWebSearchServerTool" +}) +export type WebSearchServerTool_OpenRouter = { + readonly "parameters"?: WebSearchServerToolConfig + readonly "type": "openrouter:web_search" +} +export const WebSearchServerTool_OpenRouter = Schema.Struct({ + "parameters": Schema.optionalKey(WebSearchServerToolConfig), + "type": Schema.Literal("openrouter:web_search") +}).annotate({ + "description": "OpenRouter built-in server tool: searches the web for current information", + "identifier": "WebSearchServerTool_OpenRouter" +}) +export type AdvisorServerTool_OpenRouter = { + readonly "parameters"?: AdvisorServerToolConfig + readonly "type": "openrouter:advisor" +} +export const AdvisorServerTool_OpenRouter = Schema.Struct({ + "parameters": Schema.optionalKey(AdvisorServerToolConfig), + "type": Schema.Literal("openrouter:advisor") +}).annotate({ + "description": + "OpenRouter built-in server tool: consults a higher-intelligence advisor model (any OpenRouter model) for guidance mid-generation and returns its response. The advisor may run as a sub-agent with its own tools. Include multiple entries to offer several named advisors; at most one entry may omit `name` to act as the default advisor.", + "identifier": "AdvisorServerTool_OpenRouter" +}) +export type AnthropicBashCodeExecutionToolResult = { + readonly "content": AnthropicBashCodeExecutionContent + readonly "tool_use_id": string + readonly "type": "bash_code_execution_tool_result" +} +export const AnthropicBashCodeExecutionToolResult = Schema.Struct({ + "content": AnthropicBashCodeExecutionContent, + "tool_use_id": Schema.String, + "type": Schema.Literal("bash_code_execution_tool_result") +}).annotate({ "identifier": "AnthropicBashCodeExecutionToolResult" }) +export type AnthropicSearchResultBlockParam = { + readonly "cache_control"?: AnthropicCacheControlDirective + readonly "citations"?: { readonly "enabled"?: boolean } + readonly "content": ReadonlyArray + readonly "source": string + readonly "title": string + readonly "type": "search_result" +} +export const AnthropicSearchResultBlockParam = Schema.Struct({ + "cache_control": Schema.optionalKey(AnthropicCacheControlDirective), + "citations": Schema.optionalKey(Schema.Struct({ "enabled": Schema.optionalKey(Schema.Boolean) })), + "content": Schema.Array(AnthropicTextBlockParam), + "source": Schema.String, + "title": Schema.String, + "type": Schema.Literal("search_result") +}).annotate({ "identifier": "AnthropicSearchResultBlockParam" }) +export type FusionServerTool_OpenRouter = { + readonly "parameters"?: FusionServerToolConfig + readonly "type": "openrouter:fusion" +} +export const FusionServerTool_OpenRouter = Schema.Struct({ + "parameters": Schema.optionalKey(FusionServerToolConfig), + "type": Schema.Literal("openrouter:fusion") +}).annotate({ + "description": + "OpenRouter built-in server tool: fans out the user prompt to a panel of analysis models, then asks a judge model to summarize their collective output as structured JSON the outer model can synthesize from.", + "identifier": "FusionServerTool_OpenRouter" +}) +export type AnthropicWebFetchBlock = { + readonly "content": AnthropicDocumentBlock + readonly "retrieved_at": string | null + readonly "type": "web_fetch_result" + readonly "url": string +} +export const AnthropicWebFetchBlock = Schema.Struct({ + "content": AnthropicDocumentBlock, + "retrieved_at": Schema.Union([Schema.String, Schema.Null]), + "type": Schema.Literal("web_fetch_result"), + "url": Schema.String +}).annotate({ "identifier": "AnthropicWebFetchBlock" }) +export type AnthropicDocumentBlockParam = { + readonly "cache_control"?: AnthropicCacheControlDirective + readonly "citations"?: { readonly "enabled"?: boolean; readonly [x: string]: Schema.Json } | null + readonly "context"?: string | null + readonly "source": + | AnthropicBase64PdfSource + | AnthropicPlainTextSource + | { + readonly "content": string | ReadonlyArray + readonly "type": "content" + } + | AnthropicUrlPdfSource + | AnthropicFileDocumentSource + readonly "title"?: string | null + readonly "type": "document" +} +export const AnthropicDocumentBlockParam = Schema.Struct({ + "cache_control": Schema.optionalKey(AnthropicCacheControlDirective), + "citations": Schema.optionalKey( + Schema.Union([ + Schema.StructWithRest(Schema.Struct({ "enabled": Schema.optionalKey(Schema.Boolean) }), [ + Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })) + ]), + Schema.Null + ]) + ), + "context": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "source": Schema.Union([ + AnthropicBase64PdfSource, + AnthropicPlainTextSource, + Schema.Struct({ + "content": Schema.Union([ + Schema.String, + Schema.Array(Schema.Union([AnthropicTextBlockParam, AnthropicImageBlockParam], { mode: "oneOf" })) + ]), + "type": Schema.Literal("content") + }), + AnthropicUrlPdfSource, + AnthropicFileDocumentSource + ], { mode: "oneOf" }), + "title": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "type": Schema.Literal("document") +}).annotate({ "identifier": "AnthropicDocumentBlockParam" }) +export type AnthropicUsageIteration = + | AnthropicCompactionUsageIteration + | AnthropicMessageUsageIteration + | AnthropicAdvisorMessageUsageIteration + | AnthropicUnknownUsageIteration +export const AnthropicUsageIteration = Schema.Union([ + AnthropicCompactionUsageIteration, + AnthropicMessageUsageIteration, + AnthropicAdvisorMessageUsageIteration, + AnthropicUnknownUsageIteration +]).annotate({ "identifier": "AnthropicUsageIteration" }) +export type AnthropicCodeExecutionToolResult = { + readonly "content": AnthropicCodeExecutionContent + readonly "tool_use_id": string + readonly "type": "code_execution_tool_result" +} +export const AnthropicCodeExecutionToolResult = Schema.Struct({ + "content": AnthropicCodeExecutionContent, + "tool_use_id": Schema.String, + "type": Schema.Literal("code_execution_tool_result") +}).annotate({ "identifier": "AnthropicCodeExecutionToolResult" }) +export type AnthropicToolSearchToolResult = { + readonly "content": AnthropicToolSearchContent + readonly "tool_use_id": string + readonly "type": "tool_search_tool_result" +} +export const AnthropicToolSearchToolResult = Schema.Struct({ + "content": AnthropicToolSearchContent, + "tool_use_id": Schema.String, + "type": Schema.Literal("tool_search_tool_result") +}).annotate({ "identifier": "AnthropicToolSearchToolResult" }) +export type BashServerTool = { readonly "parameters"?: BashServerToolConfig; readonly "type": "openrouter:bash" } +export const BashServerTool = Schema.Struct({ + "parameters": Schema.optionalKey(BashServerToolConfig), + "type": Schema.Literal("openrouter:bash") +}).annotate({ + "description": "OpenRouter built-in server tool: runs shell commands server-side in a sandboxed container", + "identifier": "BashServerTool" +}) +export type ShellServerTool_OpenRouter = { + readonly "parameters"?: ShellServerToolConfig + readonly "type": "openrouter:shell" +} +export const ShellServerTool_OpenRouter = Schema.Struct({ + "parameters": Schema.optionalKey(ShellServerToolConfig), + "type": Schema.Literal("openrouter:shell") +}).annotate({ + "description": + "OpenRouter built-in server tool: runs shell commands server-side in a sandboxed container (a sandbox-backed clone of OpenAI's hosted shell tool)", + "identifier": "ShellServerTool_OpenRouter" +}) +export type ImageModelsListResponse = { readonly "data": ReadonlyArray } +export const ImageModelsListResponse = Schema.Struct({ "data": Schema.Array(ImageModelListItem) }).annotate({ + "description": "List of image generation models.", + "identifier": "ImageModelsListResponse" +}) +export type ObservabilityDestination = + | ObservabilityArizeDestination + | ObservabilityBraintrustDestination + | ObservabilityClickhouseDestination + | ObservabilityDatadogDestination + | ObservabilityGrafanaDestination + | ObservabilityLangfuseDestination + | ObservabilityLangsmithDestination + | ObservabilityNewrelicDestination + | ObservabilityOpikDestination + | ObservabilityOtelCollectorDestination + | ObservabilityPosthogDestination + | ObservabilityRampDestination + | ObservabilityS3Destination + | ObservabilitySentryDestination + | ObservabilitySnowflakeDestination + | ObservabilityWeaveDestination + | ObservabilityWebhookDestination +export const ObservabilityDestination = Schema.Union([ + ObservabilityArizeDestination, + ObservabilityBraintrustDestination, + ObservabilityClickhouseDestination, + ObservabilityDatadogDestination, + ObservabilityGrafanaDestination, + ObservabilityLangfuseDestination, + ObservabilityLangsmithDestination, + ObservabilityNewrelicDestination, + ObservabilityOpikDestination, + ObservabilityOtelCollectorDestination, + ObservabilityPosthogDestination, + ObservabilityRampDestination, + ObservabilityS3Destination, + ObservabilitySentryDestination, + ObservabilitySnowflakeDestination, + ObservabilityWeaveDestination, + ObservabilityWebhookDestination +], { mode: "oneOf" }).annotate({ "identifier": "ObservabilityDestination" }) +export type CreateObservabilityDestinationRequest = { + readonly "api_key_hashes"?: ReadonlyArray | null + readonly "config": {} + readonly "enabled"?: boolean + readonly "filter_rules"?: ObservabilityFilterRulesConfigNullable + readonly "name": string + readonly "privacy_mode"?: boolean + readonly "sampling_rate"?: number + readonly "type": + | "arize" + | "braintrust" + | "clickhouse" + | "datadog" + | "grafana" + | "langfuse" + | "langsmith" + | "newrelic" + | "opik" + | "otel-collector" + | "posthog" + | "ramp" + | "s3" + | "sentry" + | "snowflake" + | "weave" + | "webhook" + readonly "workspace_id"?: string +} +export const CreateObservabilityDestinationRequest = Schema.Struct({ + "api_key_hashes": Schema.optionalKey( + Schema.Union([ + Schema.Array(Schema.String).check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ), + Schema.Null + ]).annotate({ + "description": + "Optional allowlist of OpenRouter API key hashes whose traffic is forwarded. `null` or omitted means all keys. Must contain at least one hash if provided." + }) + ), + "config": Schema.Struct({}).annotate({ + "description": "Provider-specific configuration. The shape depends on `type` and is validated server-side." + }), + "enabled": Schema.optionalKey( + Schema.Boolean.annotate({ "description": "Whether this destination should be enabled immediately." }) + ), + "filter_rules": Schema.optionalKey(ObservabilityFilterRulesConfigNullable), + "name": Schema.String.annotate({ "description": "Human-readable name for the destination." }), + "privacy_mode": Schema.optionalKey( + Schema.Boolean.annotate({ "description": "When true, request/response bodies are not forwarded — only metadata." }) + ), + "sampling_rate": Schema.optionalKey( + Schema.Number.annotate({ "description": "Sampling rate between 0.0001 and 1 (1 = 100%).", "format": "double" }) + .check(Schema.isFinite().annotate({ "expected": "a finite number" })) + ), + "type": Schema.Literals([ + "arize", + "braintrust", + "clickhouse", + "datadog", + "grafana", + "langfuse", + "langsmith", + "newrelic", + "opik", + "otel-collector", + "posthog", + "ramp", + "s3", + "sentry", + "snowflake", + "weave", + "webhook" + ]).annotate({ "description": "The destination type. Only stable destination types are accepted." }), + "workspace_id": Schema.optionalKey( + Schema.String.annotate({ + "description": "Optional workspace ID. Defaults to the authenticated entity's default workspace.", + "format": "uuid" + }) + ) +}).annotate({ "identifier": "CreateObservabilityDestinationRequest" }) +export type UpdateObservabilityDestinationRequest = { + readonly "api_key_hashes"?: ReadonlyArray | null + readonly "config"?: {} + readonly "enabled"?: boolean + readonly "filter_rules"?: ObservabilityFilterRulesConfigNullable + readonly "name"?: string + readonly "privacy_mode"?: boolean + readonly "sampling_rate"?: number +} +export const UpdateObservabilityDestinationRequest = Schema.Struct({ + "api_key_hashes": Schema.optionalKey( + Schema.Union([ + Schema.Array(Schema.String).check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ), + Schema.Null + ]).annotate({ + "description": + "Optional allowlist of OpenRouter API key hashes. `null` clears the filter (all keys). Omitting leaves the current value. Must contain at least one hash if provided." + }) + ), + "config": Schema.optionalKey( + Schema.Struct({}).annotate({ + "description": + "Provider-specific configuration fields to update. Masked values are ignored; unset fields keep their current value." + }) + ), + "enabled": Schema.optionalKey(Schema.Boolean.annotate({ "description": "Whether the destination is enabled." })), + "filter_rules": Schema.optionalKey( + Schema.suspend((): Schema.Codec => ObservabilityFilterRulesConfigNullable) + .annotate({ + "description": "Optional structured filter rules. `null` clears the rules. Omitting keeps the current value." + }) + ), + "name": Schema.optionalKey(Schema.String.annotate({ "description": "Human-readable name for the destination." })), + "privacy_mode": Schema.optionalKey( + Schema.Boolean.annotate({ "description": "When true, request/response bodies are not forwarded — only metadata." }) + ), + "sampling_rate": Schema.optionalKey( + Schema.Number.annotate({ "description": "Sampling rate between 0.0001 and 1 (1 = 100%).", "format": "double" }) + .check(Schema.isFinite().annotate({ "expected": "a finite number" })) + ) +}).annotate({ "identifier": "UpdateObservabilityDestinationRequest" }) +export type ProviderPreferences = Objects_148 | null +export const ProviderPreferences = Schema.Union([Objects_148, Schema.Null]).annotate({ + "description": "When multiple model providers are available, optionally indicate your routing preference.", + "identifier": "ProviderPreferences" +}) +export type OpenRouterMetadata = { + readonly "attempt": number + readonly "attempts"?: Arrays_10 + readonly "endpoints": EndpointsMetadata + readonly "is_byok": boolean + readonly "params"?: RouterParams + readonly "pipeline"?: Arrays_11 + readonly "region": string | null + readonly "requested": string + readonly "strategy": RoutingStrategy + readonly "summary": string +} +export const OpenRouterMetadata = Schema.Struct({ + "attempt": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "attempts": Schema.optionalKey(Arrays_10), + "endpoints": EndpointsMetadata, + "is_byok": Schema.Boolean, + "params": Schema.optionalKey(RouterParams), + "pipeline": Schema.optionalKey(Arrays_11), + "region": Schema.Union([Schema.String, Schema.Null]), + "requested": Schema.String, + "strategy": RoutingStrategy, + "summary": Schema.String +}).annotate({ "identifier": "OpenRouterMetadata" }) +export type ChatContentItems = + | ChatContentText + | ChatContentImage + | ChatContentAudio + | Legacy_ChatContentVideo + | ChatContentVideo + | ChatContentFile +export const ChatContentItems = Schema.Union([ + ChatContentText, + ChatContentImage, + ChatContentAudio, + Legacy_ChatContentVideo, + ChatContentVideo, + ChatContentFile +], { mode: "oneOf" }).annotate({ + "description": "Content part for chat completion messages", + "identifier": "ChatContentItems" +}) +export type ChatDeveloperMessage = { + readonly "content": string | ReadonlyArray + readonly "name"?: string + readonly "role": "developer" +} +export const ChatDeveloperMessage = Schema.Struct({ + "content": Schema.Union([Schema.String, Schema.Array(ChatContentText)]).annotate({ + "description": "Developer message content" + }), + "name": Schema.optionalKey(Schema.String.annotate({ "description": "Optional name for the developer message" })), + "role": Schema.Literal("developer") +}).annotate({ "description": "Developer message", "identifier": "ChatDeveloperMessage" }) +export type ChatSystemMessage = { + readonly "content": string | ReadonlyArray + readonly "name"?: string + readonly "role": "system" +} +export const ChatSystemMessage = Schema.Struct({ + "content": Schema.Union([Schema.String, Schema.Array(ChatContentText)]).annotate({ + "description": "System message content" + }), + "name": Schema.optionalKey(Schema.String.annotate({ "description": "Optional name for the system message" })), + "role": Schema.Literal("system") +}).annotate({ "description": "System message for setting behavior", "identifier": "ChatSystemMessage" }) +export type AgentMessageItem = { + readonly "agent"?: { readonly "agent_name": string; readonly [x: string]: Schema.Json } | null + readonly "author": string + readonly "content": ReadonlyArray< + InputText | { + readonly "detail": "auto" | "high" | "low" | "original" + readonly "image_url"?: string | null + readonly "type": "input_image" + } | { readonly "encrypted_content": string; readonly "type": "encrypted_content" } + > + readonly "id"?: string | null + readonly "recipient": string + readonly "type": "agent_message" +} +export const AgentMessageItem = Schema.Struct({ + "agent": Schema.optionalKey( + Schema.Union([ + Schema.StructWithRest(Schema.Struct({ "agent_name": Schema.String }), [ + Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })) + ]), + Schema.Null + ]) + ), + "author": Schema.String, + "content": Schema.Array( + Schema.Union([ + InputText, + Schema.Struct({ + "detail": Schema.Literals(["auto", "high", "low", "original"]), + "image_url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "type": Schema.Literal("input_image") + }).annotate({ "description": "Image input content item" }), + Schema.Struct({ "encrypted_content": Schema.String, "type": Schema.Literal("encrypted_content") }) + ], { mode: "oneOf" }) + ), + "id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "recipient": Schema.String, + "type": Schema.Literal("agent_message") +}).annotate({ + "description": "A message routed between agents in a multi-agent session", + "identifier": "AgentMessageItem" +}) +export type EasyInputMessage = { + readonly "content"?: + | ReadonlyArray< + | InputText + | { + readonly "detail": "auto" | "high" | "low" | "original" + readonly "image_url"?: string | null + readonly "type": "input_image" + } + | InputFile + | InputAudio + | InputVideo + > + | string + | null + readonly "phase"?: "commentary" | "final_answer" | null + readonly "role": "user" | "system" | "assistant" | "developer" + readonly "type"?: "message" +} +export const EasyInputMessage = Schema.Struct({ + "content": Schema.optionalKey( + Schema.Union([ + Schema.Array( + Schema.Union([ + InputText, + Schema.Struct({ + "detail": Schema.Literals(["auto", "high", "low", "original"]), + "image_url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "type": Schema.Literal("input_image") + }).annotate({ "description": "Image input content item" }), + InputFile, + InputAudio, + InputVideo + ], { mode: "oneOf" }) + ), + Schema.String, + Schema.Null + ]) + ), + "phase": Schema.optionalKey( + Schema.Union([Schema.Literal("commentary"), Schema.Literal("final_answer"), Schema.Null]).annotate({ + "description": + "The phase of an assistant message. Use `commentary` for an intermediate assistant message and `final_answer` for the final assistant message. For follow-up requests with models like `gpt-5.3-codex` and later, preserve and resend phase on all assistant messages. Omitting it can degrade performance. Not used for user messages." + }) + ), + "role": Schema.Literals(["user", "system", "assistant", "developer"]), + "type": Schema.optionalKey(Schema.Literal("message")) +}).annotate({ "identifier": "EasyInputMessage" }) +export type InputMessageItem = { + readonly "content"?: + | ReadonlyArray< + | InputText + | { + readonly "detail": "auto" | "high" | "low" | "original" + readonly "image_url"?: string | null + readonly "type": "input_image" + } + | InputFile + | InputAudio + | InputVideo + > + | null + readonly "id"?: string + readonly "role": "user" | "system" | "developer" + readonly "type"?: "message" +} +export const InputMessageItem = Schema.Struct({ + "content": Schema.optionalKey( + Schema.Union([ + Schema.Array( + Schema.Union([ + InputText, + Schema.Struct({ + "detail": Schema.Literals(["auto", "high", "low", "original"]), + "image_url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "type": Schema.Literal("input_image") + }).annotate({ "description": "Image input content item" }), + InputFile, + InputAudio, + InputVideo + ], { mode: "oneOf" }) + ), + Schema.Null + ]) + ), + "id": Schema.optionalKey(Schema.String), + "role": Schema.Literals(["user", "system", "developer"]), + "type": Schema.optionalKey(Schema.Literal("message")) +}).annotate({ "identifier": "InputMessageItem" }) +export type OpenAIResponseCustomToolCallOutput = { + readonly "call_id": string + readonly "id"?: string + readonly "output": string | ReadonlyArray + readonly "type": "custom_tool_call_output" +} +export const OpenAIResponseCustomToolCallOutput = Schema.Struct({ + "call_id": Schema.String, + "id": Schema.optionalKey(Schema.String), + "output": Schema.Union([ + Schema.String, + Schema.Array(Schema.Union([InputText, InputImage, InputFile], { mode: "oneOf" })) + ]), + "type": Schema.Literal("custom_tool_call_output") +}).annotate({ "identifier": "OpenAIResponseCustomToolCallOutput" }) +export type OpenAIResponseFunctionToolCallOutput = { + readonly "call_id": string + readonly "id"?: string | null + readonly "output": string | ReadonlyArray + readonly "status"?: ToolCallStatus | null + readonly "type": "function_call_output" +} +export const OpenAIResponseFunctionToolCallOutput = Schema.Struct({ + "call_id": Schema.String, + "id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "output": Schema.Union([ + Schema.String, + Schema.Array(Schema.Union([InputText, InputImage, InputFile], { mode: "oneOf" })) + ]), + "status": Schema.optionalKey(Schema.Union([ToolCallStatus, Schema.Null])), + "type": Schema.Literal("function_call_output") +}).annotate({ "identifier": "OpenAIResponseFunctionToolCallOutput" }) +export type OpenAIResponseInputMessageItem = { + readonly "content": ReadonlyArray + readonly "id": string + readonly "role": "user" | "system" | "developer" + readonly "type"?: "message" +} +export const OpenAIResponseInputMessageItem = Schema.Struct({ + "content": Schema.Array(Schema.Union([InputText, InputImage, InputFile, InputAudio], { mode: "oneOf" })), + "id": Schema.String, + "role": Schema.Literals(["user", "system", "developer"]), + "type": Schema.optionalKey(Schema.Literal("message")) +}).annotate({ "identifier": "OpenAIResponseInputMessageItem" }) +export type Objects_151 = { + readonly "id": string + readonly "variables"?: { readonly [x: string]: string | InputText | InputImage | InputFile } | null + readonly [x: string]: Schema.Json +} +export const Objects_151 = Schema.StructWithRest( + Schema.Struct({ + "id": Schema.String, + "variables": Schema.optionalKey( + Schema.Union([ + Schema.Record(Schema.String, Schema.Union([Schema.String, InputText, InputImage, InputFile])), + Schema.Null + ]) + ) + }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] +) +export type CreateGuardrailResponse = { readonly "data": Guardrail } +export const CreateGuardrailResponse = Schema.Struct({ + "data": Schema.suspend((): Schema.Codec => Guardrail).annotate({ "description": "The created guardrail" }) +}).annotate({ "identifier": "CreateGuardrailResponse" }) +export type GetGuardrailResponse = { readonly "data": Guardrail } +export const GetGuardrailResponse = Schema.Struct({ + "data": Schema.suspend((): Schema.Codec => Guardrail).annotate({ "description": "The guardrail" }) +}).annotate({ "identifier": "GetGuardrailResponse" }) +export type ListGuardrailsResponse = { readonly "data": ReadonlyArray; readonly "total_count": number } +export const ListGuardrailsResponse = Schema.Struct({ + "data": Schema.Array(Guardrail).annotate({ "description": "List of guardrails" }), + "total_count": Schema.Number.annotate({ "description": "Total number of guardrails" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ) +}).annotate({ "identifier": "ListGuardrailsResponse" }) +export type UpdateGuardrailResponse = { readonly "data": Guardrail } +export const UpdateGuardrailResponse = Schema.Struct({ + "data": Schema.suspend((): Schema.Codec => Guardrail).annotate({ "description": "The updated guardrail" }) +}).annotate({ "identifier": "UpdateGuardrailResponse" }) +export type ImageGenerationRequest = { + readonly "aspect_ratio"?: + | "1:1" + | "1:2" + | "1:4" + | "1:8" + | "2:1" + | "2:3" + | "3:2" + | "3:4" + | "4:1" + | "4:3" + | "4:5" + | "5:4" + | "8:1" + | "9:16" + | "16:9" + | "9:19.5" + | "19.5:9" + | "9:20" + | "20:9" + | "9:21" + | "21:9" + | "auto" + readonly "background"?: "auto" | "transparent" | "opaque" + readonly "input_references"?: ReadonlyArray + readonly "model": string + readonly "n"?: number + readonly "output_compression"?: number + readonly "output_format"?: "png" | "jpeg" | "webp" | "svg" + readonly "prompt": string + readonly "provider"?: ImageGenerationProviderPreferences + readonly "quality"?: "auto" | "low" | "medium" | "high" + readonly "resolution"?: "512" | "1K" | "2K" | "4K" + readonly "seed"?: number + readonly "size"?: string + readonly "stream"?: boolean +} +export const ImageGenerationRequest = Schema.Struct({ + "aspect_ratio": Schema.optionalKey( + Schema.Literals([ + "1:1", + "1:2", + "1:4", + "1:8", + "2:1", + "2:3", + "3:2", + "3:4", + "4:1", + "4:3", + "4:5", + "5:4", + "8:1", + "9:16", + "16:9", + "9:19.5", + "19.5:9", + "9:20", + "20:9", + "9:21", + "21:9", + "auto" + ]).annotate({ + "description": "Normalized aspect ratio of the generated image. Providers clamp to their supported subset." + }) + ), + "background": Schema.optionalKey( + Schema.Literals(["auto", "transparent", "opaque"]).annotate({ + "description": "Background treatment. `transparent` requires an output_format that supports alpha (png or webp)." + }) + ), + "input_references": Schema.optionalKey( + Schema.Array(ContentPartImage).annotate({ + "description": "Reference images to guide image-to-image generation, as base64 data URLs or HTTP(S) URLs." + }).check(Schema.isMaxLength(16).annotate({ "expected": "a value with a length of at most 16" })) + ), + "model": Schema.String.annotate({ "description": "The image generation model to use" }), + "n": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Number of images to generate (1-10). Providers that only support single-image generation reject n > 1." + }).check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "output_compression": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Compression level (0-100) for webp/jpeg output. Ignored for png and by providers without a compression knob." + }).check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "output_format": Schema.optionalKey( + Schema.Literals(["png", "jpeg", "webp", "svg"]).annotate({ + "description": + "Encoding of the returned image bytes. Most models produce raster formats (png, jpeg, webp). SVG is supported by vectorization models (e.g. Quiver) — the SVG markup is UTF-8 base64-encoded in `b64_json`." + }) + ), + "prompt": Schema.String.annotate({ "description": "Text description of the desired image" }).check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ), + "provider": Schema.optionalKey(ImageGenerationProviderPreferences), + "quality": Schema.optionalKey( + Schema.Literals(["auto", "low", "medium", "high"]).annotate({ + "description": "Rendering quality. Providers without a quality knob ignore this." + }) + ), + "resolution": Schema.optionalKey( + Schema.Literals(["512", "1K", "2K", "4K"]).annotate({ + "description": + "Normalized resolution tier of the generated image. Concrete pixel dimensions are derived per-provider." + }) + ), + "seed": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "If specified, the generation will sample deterministically, such that repeated requests with the same seed and parameters should return the same result. Determinism is not guaranteed for all providers." + }).check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "size": Schema.optionalKey(Schema.String.annotate({ + "description": + "Optional. A convenience shorthand for output dimensions — pass a tier (\"2K\", \"4K\") or explicit pixels (\"2048x2048\") and we normalize it to the right dimensions for the chosen provider. A tier size is equivalent to setting `resolution` and combines with `aspect_ratio`. An explicit pixel size is authoritative: a mismatched `resolution` or `aspect_ratio` alongside it is rejected with a 400." + })), + "stream": Schema.optionalKey( + Schema.Boolean.annotate({ + "description": + "If true, partial images are streamed as SSE events as they become available. Only supported by providers with native streaming (currently OpenAI). Non-streaming providers ignore this flag and return a buffered response." + }) + ) +}).annotate({ "description": "Image generation request input", "identifier": "ImageGenerationRequest" }) +export type Model = { + readonly "architecture": ModelArchitecture + readonly "benchmarks"?: ModelBenchmarks + readonly "canonical_slug": string + readonly "context_length": number | null + readonly "created": number + readonly "default_parameters": DefaultParameters + readonly "description"?: string + readonly "expiration_date"?: string | null + readonly "hugging_face_id"?: string | null + readonly "id": string + readonly "knowledge_cutoff"?: string | null + readonly "links": ModelLinks + readonly "name": string + readonly "per_request_limits": PerRequestLimits + readonly "pricing": PublicPricing + readonly "reasoning"?: ModelReasoning + readonly "supported_parameters": ReadonlyArray + readonly "supported_voices": ReadonlyArray | null + readonly "top_provider": TopProviderInfo +} +export const Model = Schema.Struct({ + "architecture": ModelArchitecture, + "benchmarks": Schema.optionalKey(ModelBenchmarks), + "canonical_slug": Schema.String.annotate({ "description": "Canonical slug for the model" }), + "context_length": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]).annotate({ "description": "Maximum context length in tokens" }), + "created": Schema.Number.annotate({ "description": "Unix timestamp of when the model was created" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ), + "default_parameters": DefaultParameters, + "description": Schema.optionalKey(Schema.String.annotate({ "description": "Description of the model" })), + "expiration_date": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": + "The date after which the model may be removed. ISO 8601 date string (YYYY-MM-DD) or null if no expiration." + }) + ), + "hugging_face_id": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "Hugging Face model identifier, if applicable" + }) + ), + "id": Schema.String.annotate({ "description": "Unique identifier for the model" }), + "knowledge_cutoff": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": + "The date up to which the model was trained on data. ISO 8601 date string (YYYY-MM-DD) or null if unknown." + }) + ), + "links": ModelLinks, + "name": Schema.String.annotate({ "description": "Display name of the model" }), + "per_request_limits": PerRequestLimits, + "pricing": PublicPricing, + "reasoning": Schema.optionalKey(ModelReasoning), + "supported_parameters": Schema.Array(Parameter).annotate({ + "description": "List of supported parameters for this model" + }), + "supported_voices": Schema.Union([Schema.Array(Schema.String), Schema.Null]).annotate({ + "description": "List of supported voice identifiers for TTS models. Null for non-TTS models." + }), + "top_provider": TopProviderInfo +}).annotate({ "description": "Information about an AI model available on OpenRouter", "identifier": "Model" }) +export type ChatReasoningDetails = ReadonlyArray +export const ChatReasoningDetails = Schema.Array(ReasoningDetailUnion).annotate({ + "description": "Reasoning details for extended thinking models", + "identifier": "ChatReasoningDetails" +}) +export type ChatStreamReasoningDetails = ReadonlyArray +export const ChatStreamReasoningDetails = Schema.Array(ReasoningDetailUnion).annotate({ + "description": "Reasoning details for extended thinking models", + "identifier": "ChatStreamReasoningDetails" +}) +export type SubagentServerTool_OpenRouter = { + readonly "parameters"?: SubagentServerToolConfig + readonly "type": "openrouter:subagent" +} +export const SubagentServerTool_OpenRouter = Schema.Struct({ + "parameters": Schema.optionalKey(SubagentServerToolConfig), + "type": Schema.Literal("openrouter:subagent") +}).annotate({ + "description": + "OpenRouter built-in server tool: delegates self-contained tasks to a smaller, cheaper, faster worker model (any OpenRouter model) mid-generation and returns its outcome. The worker may run as a sub-agent with its own tools.", + "identifier": "SubagentServerTool_OpenRouter" +}) +export type BaseContentPartAddedEvent = { + readonly "content_index": number + readonly "item_id": string + readonly "output_index": number + readonly "part": ResponseOutputText | OpenAIResponsesRefusalContent + readonly "sequence_number": number + readonly "type": "response.content_part.added" +} +export const BaseContentPartAddedEvent = Schema.Struct({ + "content_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "item_id": Schema.String, + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "part": Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent]), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.content_part.added") +}).annotate({ + "description": "Event emitted when a new content part is added to an output item", + "identifier": "BaseContentPartAddedEvent" +}) +export type BaseContentPartDoneEvent = { + readonly "content_index": number + readonly "item_id": string + readonly "output_index": number + readonly "part": ResponseOutputText | OpenAIResponsesRefusalContent + readonly "sequence_number": number + readonly "type": "response.content_part.done" +} +export const BaseContentPartDoneEvent = Schema.Struct({ + "content_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "item_id": Schema.String, + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "part": Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent]), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.content_part.done") +}).annotate({ + "description": "Event emitted when a content part is complete", + "identifier": "BaseContentPartDoneEvent" +}) +export type ContentPartAddedEvent = { + readonly "content_index": number + readonly "item_id": string + readonly "output_index": number + readonly "part": ResponseOutputText | ReasoningTextContent | OpenAIResponsesRefusalContent + readonly "sequence_number": number + readonly "type": "response.content_part.added" +} +export const ContentPartAddedEvent = Schema.Struct({ + "content_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "item_id": Schema.String, + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "part": Schema.Union([ResponseOutputText, ReasoningTextContent, OpenAIResponsesRefusalContent]), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.content_part.added") +}).annotate({ + "description": "Event emitted when a new content part is added to an output item", + "identifier": "ContentPartAddedEvent" +}) +export type ContentPartDoneEvent = { + readonly "content_index": number + readonly "item_id": string + readonly "output_index": number + readonly "part": ResponseOutputText | ReasoningTextContent | OpenAIResponsesRefusalContent + readonly "sequence_number": number + readonly "type": "response.content_part.done" +} +export const ContentPartDoneEvent = Schema.Struct({ + "content_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "item_id": Schema.String, + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "part": Schema.Union([ResponseOutputText, ReasoningTextContent, OpenAIResponsesRefusalContent]), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.content_part.done") +}).annotate({ "description": "Event emitted when a content part is complete", "identifier": "ContentPartDoneEvent" }) +export type OutputMessage = { + readonly "content": ReadonlyArray + readonly "id": string + readonly "phase"?: "commentary" | "final_answer" | null + readonly "role": "assistant" + readonly "status"?: "completed" | "incomplete" | "in_progress" + readonly "type": "message" +} +export const OutputMessage = Schema.Struct({ + "content": Schema.Array(Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent])), + "id": Schema.String, + "phase": Schema.optionalKey( + Schema.Union([Schema.Literal("commentary"), Schema.Literal("final_answer"), Schema.Null]).annotate({ + "description": + "The phase of an assistant message. Use `commentary` for an intermediate assistant message and `final_answer` for the final assistant message. For follow-up requests with models like `gpt-5.3-codex` and later, preserve and resend phase on all assistant messages. Omitting it can degrade performance. Not used for user messages." + }) + ), + "role": Schema.Literal("assistant"), + "status": Schema.optionalKey(Schema.Literals(["completed", "incomplete", "in_progress"])), + "type": Schema.Literal("message") +}).annotate({ "identifier": "OutputMessage" }) +export type OutputMessageItem = { + readonly "content": ReadonlyArray + readonly "id": string + readonly "phase"?: "commentary" | "final_answer" | null + readonly "role": "assistant" + readonly "status"?: "completed" | "incomplete" | "in_progress" + readonly "type": "message" +} +export const OutputMessageItem = Schema.Struct({ + "content": Schema.Array(Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent])), + "id": Schema.String, + "phase": Schema.optionalKey( + Schema.Union([Schema.Literal("commentary"), Schema.Literal("final_answer"), Schema.Null]).annotate({ + "description": + "The phase of an assistant message. Use `commentary` for an intermediate assistant message and `final_answer` for the final assistant message. For follow-up requests with models like `gpt-5.3-codex` and later, preserve and resend phase on all assistant messages. Omitting it can degrade performance. Not used for user messages." + }) + ), + "role": Schema.Literal("assistant"), + "status": Schema.optionalKey(Schema.Literals(["completed", "incomplete", "in_progress"])), + "type": Schema.Literal("message") +}).annotate({ "description": "An output message item", "identifier": "OutputMessageItem" }) +export type AnthropicWebFetchContent = AnthropicWebFetchToolResultError | AnthropicWebFetchBlock +export const AnthropicWebFetchContent = Schema.Union([AnthropicWebFetchToolResultError, AnthropicWebFetchBlock], { + mode: "oneOf" +}).annotate({ "identifier": "AnthropicWebFetchContent" }) +export type MessagesMessageParam = { + readonly "content": + | string + | ReadonlyArray< + | AnthropicTextBlockParam + | AnthropicImageBlockParam + | AnthropicDocumentBlockParam + | { + readonly "cache_control"?: AnthropicCacheControlDirective + readonly "id": string + readonly "input"?: Schema.Json + readonly "name": string + readonly "type": "tool_use" + } + | { + readonly "cache_control"?: AnthropicCacheControlDirective + readonly "content"?: + | string + | ReadonlyArray< + | AnthropicTextBlockParam + | AnthropicImageBlockParam + | { readonly "tool_name": string; readonly "type": "tool_reference" } + | AnthropicSearchResultBlockParam + | AnthropicDocumentBlockParam + > + readonly "is_error"?: boolean + readonly "tool_use_id": string + readonly "type": "tool_result" + } + | { readonly "signature": string; readonly "thinking": string; readonly "type": "thinking" } + | { readonly "data": string; readonly "type": "redacted_thinking" } + | { + readonly "cache_control"?: AnthropicCacheControlDirective + readonly "id": string + readonly "input"?: Schema.Json + readonly "name": string + readonly "type": "server_tool_use" + } + | { + readonly "cache_control"?: AnthropicCacheControlDirective + readonly "content": ReadonlyArray | { + readonly "error_code": + | "invalid_tool_input" + | "unavailable" + | "max_uses_exceeded" + | "too_many_requests" + | "query_too_long" + readonly "type": "web_search_tool_result_error" + } + readonly "tool_use_id": string + readonly "type": "web_search_tool_result" + } + | AnthropicSearchResultBlockParam + | { + readonly "cache_control"?: AnthropicCacheControlDirective + readonly "content": string | null + readonly "type": "compaction" + } + | MessagesAdvisorToolResultBlock + > + readonly "role": "user" | "assistant" | "system" +} +export const MessagesMessageParam = Schema.Struct({ + "content": Schema.Union([ + Schema.String, + Schema.Array( + Schema.Union([ + AnthropicTextBlockParam, + AnthropicImageBlockParam, + AnthropicDocumentBlockParam, + Schema.Struct({ + "cache_control": Schema.optionalKey(AnthropicCacheControlDirective), + "id": Schema.String, + "input": Schema.optionalKey(Schema.Json.annotate({ "expected": "JSON value" })), + "name": Schema.String, + "type": Schema.Literal("tool_use") + }), + Schema.Struct({ + "cache_control": Schema.optionalKey(AnthropicCacheControlDirective), + "content": Schema.optionalKey( + Schema.Union([ + Schema.String, + Schema.Array( + Schema.Union([ + AnthropicTextBlockParam, + AnthropicImageBlockParam, + Schema.Struct({ "tool_name": Schema.String, "type": Schema.Literal("tool_reference") }), + AnthropicSearchResultBlockParam, + AnthropicDocumentBlockParam + ]) + ) + ]) + ), + "is_error": Schema.optionalKey(Schema.Boolean), + "tool_use_id": Schema.String, + "type": Schema.Literal("tool_result") + }), + Schema.Struct({ "signature": Schema.String, "thinking": Schema.String, "type": Schema.Literal("thinking") }), + Schema.Struct({ "data": Schema.String, "type": Schema.Literal("redacted_thinking") }), + Schema.Struct({ + "cache_control": Schema.optionalKey(AnthropicCacheControlDirective), + "id": Schema.String, + "input": Schema.optionalKey(Schema.Json.annotate({ "expected": "JSON value" })), + "name": Schema.String, + "type": Schema.Literal("server_tool_use") + }), + Schema.Struct({ + "cache_control": Schema.optionalKey(AnthropicCacheControlDirective), + "content": Schema.Union([ + Schema.Array(AnthropicWebSearchResultBlockParam), + Schema.Struct({ + "error_code": Schema.Literals([ + "invalid_tool_input", + "unavailable", + "max_uses_exceeded", + "too_many_requests", + "query_too_long" + ]), + "type": Schema.Literal("web_search_tool_result_error") + }) + ]), + "tool_use_id": Schema.String, + "type": Schema.Literal("web_search_tool_result") + }), + AnthropicSearchResultBlockParam, + Schema.Struct({ + "cache_control": Schema.optionalKey(AnthropicCacheControlDirective), + "content": Schema.Union([Schema.String, Schema.Null]), + "type": Schema.Literal("compaction") + }), + MessagesAdvisorToolResultBlock + ], { mode: "oneOf" }) + ) + ]), + "role": Schema.Literals(["user", "assistant", "system"]) +}).annotate({ "description": "Anthropic message with OpenRouter extensions", "identifier": "MessagesMessageParam" }) +export type Union_7 = ReadonlyArray | null +export const Union_7 = Schema.Union([Schema.Array(AnthropicUsageIteration), Schema.Null]) +export type MessagesDeltaEvent = { + readonly "delta": { + readonly "container": AnthropicContainer + readonly "stop_details": AnthropicRefusalStopDetails + readonly "stop_reason": ORAnthropicStopReason + readonly "stop_sequence": string | null + } + readonly "type": "message_delta" + readonly "usage": { + readonly "cache_creation"?: AnthropicCacheCreation + readonly "cache_creation_input_tokens": number | null + readonly "cache_read_input_tokens": number | null + readonly "input_tokens": number | null + readonly "iterations"?: ReadonlyArray + readonly "output_tokens": number + readonly "output_tokens_details": AnthropicOutputTokensDetails + readonly "server_tool_use": { + readonly "web_fetch_requests": number + readonly "web_search_requests": number + readonly [x: string]: Schema.Json + } | null + } +} +export const MessagesDeltaEvent = Schema.Struct({ + "delta": Schema.Struct({ + "container": AnthropicContainer, + "stop_details": AnthropicRefusalStopDetails, + "stop_reason": ORAnthropicStopReason, + "stop_sequence": Schema.Union([Schema.String, Schema.Null]) + }), + "type": Schema.Literal("message_delta"), + "usage": Schema.Struct({ + "cache_creation": Schema.optionalKey(AnthropicCacheCreation), + "cache_creation_input_tokens": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]), + "cache_read_input_tokens": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]), + "input_tokens": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]), + "iterations": Schema.optionalKey(Schema.Array(AnthropicUsageIteration)), + "output_tokens": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "output_tokens_details": AnthropicOutputTokensDetails, + "server_tool_use": Schema.Union([ + Schema.StructWithRest( + Schema.Struct({ + "web_fetch_requests": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "web_search_requests": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] + ), + Schema.Null + ]) + }) +}).annotate({ + "description": "Event sent when the message metadata changes (e.g., stop_reason)", + "identifier": "MessagesDeltaEvent" +}) +export type CreateObservabilityDestinationResponse = { readonly "data": ObservabilityDestination } +export const CreateObservabilityDestinationResponse = Schema.Struct({ + "data": Schema.suspend((): Schema.Codec => ObservabilityDestination).annotate({ + "description": "The newly created observability destination." + }) +}).annotate({ "identifier": "CreateObservabilityDestinationResponse" }) +export type GetObservabilityDestinationResponse = { readonly "data": ObservabilityDestination } +export const GetObservabilityDestinationResponse = Schema.Struct({ + "data": Schema.suspend((): Schema.Codec => ObservabilityDestination).annotate({ + "description": "The observability destination." + }) +}).annotate({ "identifier": "GetObservabilityDestinationResponse" }) +export type ListObservabilityDestinationsResponse = { + readonly "data": ReadonlyArray + readonly "total_count": number +} +export const ListObservabilityDestinationsResponse = Schema.Struct({ + "data": Schema.Array(ObservabilityDestination).annotate({ "description": "List of observability destinations." }), + "total_count": Schema.Number.annotate({ "description": "Total number of destinations matching the filters." }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ) +}).annotate({ "identifier": "ListObservabilityDestinationsResponse" }) +export type UpdateObservabilityDestinationResponse = { readonly "data": ObservabilityDestination } +export const UpdateObservabilityDestinationResponse = Schema.Struct({ + "data": Schema.suspend((): Schema.Codec => ObservabilityDestination).annotate({ + "description": "The updated observability destination." + }) +}).annotate({ "identifier": "UpdateObservabilityDestinationResponse" }) +export type MessagesErrorEvent = { + readonly "error": { readonly "error_type"?: ApiErrorType; readonly "message": string; readonly "type": string } + readonly "openrouter_metadata"?: OpenRouterMetadata + readonly "type": "error" +} +export const MessagesErrorEvent = Schema.Struct({ + "error": Schema.Struct({ + "error_type": Schema.optionalKey(ApiErrorType), + "message": Schema.String, + "type": Schema.String + }), + "openrouter_metadata": Schema.optionalKey(OpenRouterMetadata), + "type": Schema.Literal("error") +}).annotate({ "description": "Error event in the stream", "identifier": "MessagesErrorEvent" }) +export type MessagesStopEvent = { readonly "openrouter_metadata"?: OpenRouterMetadata; readonly "type": "message_stop" } +export const MessagesStopEvent = Schema.Struct({ + "openrouter_metadata": Schema.optionalKey(OpenRouterMetadata), + "type": Schema.Literal("message_stop") +}).annotate({ "description": "Event sent when the message is complete", "identifier": "MessagesStopEvent" }) +export type ChatToolMessage = { + readonly "content": string | ReadonlyArray + readonly "role": "tool" + readonly "tool_call_id": string +} +export const ChatToolMessage = Schema.Struct({ + "content": Schema.Union([Schema.String, Schema.Array(ChatContentItems)]).annotate({ + "description": "Tool response content" + }), + "role": Schema.Literal("tool"), + "tool_call_id": Schema.String.annotate({ + "description": "ID of the assistant message tool call this message responds to" + }) +}).annotate({ "description": "Tool response message", "identifier": "ChatToolMessage" }) +export type ChatUserMessage = { + readonly "content": string | ReadonlyArray + readonly "name"?: string + readonly "role": "user" +} +export const ChatUserMessage = Schema.Struct({ + "content": Schema.Union([Schema.String, Schema.Array(ChatContentItems)]).annotate({ + "description": "User message content" + }), + "name": Schema.optionalKey(Schema.String.annotate({ "description": "Optional name for the user" })), + "role": Schema.Literal("user") +}).annotate({ "description": "User message", "identifier": "ChatUserMessage" }) +export type StoredPromptTemplate = Objects_151 | null +export const StoredPromptTemplate = Schema.Union([Objects_151, Schema.Null]).annotate({ + "identifier": "StoredPromptTemplate" +}) +export type ModelResponse = { readonly "data": Model } +export const ModelResponse = Schema.Struct({ "data": Model }).annotate({ + "description": "Single model response", + "identifier": "ModelResponse" +}) +export type ModelsListResponseData = ReadonlyArray +export const ModelsListResponseData = Schema.Array(Model).annotate({ + "description": "List of available models", + "identifier": "ModelsListResponseData" +}) +export type ChatAssistantMessage = { + readonly "audio"?: ChatAudioOutput + readonly "content"?: string | ReadonlyArray | null + readonly "images"?: + | ReadonlyArray<{ readonly "type": "image_url"; readonly "image_url": { readonly "url": string } }> + | null + readonly "model"?: string + readonly "name"?: string + readonly "reasoning"?: string | null + readonly "reasoning_details"?: ChatReasoningDetails + readonly "refusal"?: string | null + readonly "role": "assistant" + readonly "tool_calls"?: ReadonlyArray + readonly "annotations"?: + | ReadonlyArray< + { + readonly "type": "url_citation" + readonly "url_citation": { + readonly "url": string + readonly "title"?: string + readonly "start_index"?: number + readonly "end_index"?: number + readonly "content"?: string + } + } | { + readonly "type": "file_annotation" + readonly "file_annotation": { readonly "file_id": string; readonly "quote"?: string } + } | { + readonly "type": "file" + readonly "file": { + readonly "hash": string + readonly "name": string + readonly "content"?: ReadonlyArray<{ readonly "type": string; readonly "text"?: string }> + } + } + > + | null +} +export const ChatAssistantMessage = Schema.Struct({ + "audio": Schema.optionalKey(ChatAudioOutput), + "content": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Array(ChatContentItems), Schema.Null]).annotate({ + "description": "Assistant message content" + }) + ), + "images": Schema.optionalKey( + Schema.Union([ + Schema.Array( + Schema.Struct({ "type": Schema.Literal("image_url"), "image_url": Schema.Struct({ "url": Schema.String }) }) + ), + Schema.Null + ]) + ), + "model": Schema.optionalKey(Schema.String.annotate({ "description": "Model that generated this assistant message" })), + "name": Schema.optionalKey(Schema.String.annotate({ "description": "Optional name for the assistant" })), + "reasoning": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ "description": "Reasoning output" }) + ), + "reasoning_details": Schema.optionalKey(ChatReasoningDetails), + "refusal": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ "description": "Refusal message if content was refused" }) + ), + "role": Schema.Literal("assistant"), + "tool_calls": Schema.optionalKey( + Schema.Array(ChatToolCall).annotate({ "description": "Tool calls made by the assistant" }) + ), + "annotations": Schema.optionalKey(Schema.Union([ + Schema.Array(Schema.Union([ + Schema.Struct({ + "type": Schema.Literal("url_citation"), + "url_citation": Schema.Struct({ + "url": Schema.String, + "title": Schema.optionalKey(Schema.String), + "start_index": Schema.optionalKey( + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })) + ), + "end_index": Schema.optionalKey( + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })) + ), + "content": Schema.optionalKey(Schema.String) + }) + }), + Schema.Struct({ + "type": Schema.Literal("file_annotation"), + "file_annotation": Schema.Struct({ "file_id": Schema.String, "quote": Schema.optionalKey(Schema.String) }) + }), + Schema.Struct({ + "type": Schema.Literal("file"), + "file": Schema.Struct({ + "hash": Schema.String, + "name": Schema.String, + "content": Schema.optionalKey( + Schema.Array(Schema.Struct({ "type": Schema.String, "text": Schema.optionalKey(Schema.String) })) + ) + }) + }) + ], { mode: "oneOf" })), + Schema.Null + ])) +}).annotate({ "description": "Assistant message for requests and responses", "identifier": "ChatAssistantMessage" }) +export type ChatStreamDelta = { + readonly "audio"?: ChatAudioOutput + readonly "content"?: string | null + readonly "reasoning"?: string | null + readonly "reasoning_details"?: ChatStreamReasoningDetails + readonly "refusal"?: string | null + readonly "role"?: "assistant" + readonly "tool_calls"?: ReadonlyArray + readonly "images"?: + | ReadonlyArray<{ readonly "type": "image_url"; readonly "image_url": { readonly "url": string } }> + | null + readonly "annotations"?: + | ReadonlyArray< + { + readonly "type": "url_citation" + readonly "url_citation": { + readonly "url": string + readonly "title"?: string + readonly "start_index"?: number + readonly "end_index"?: number + readonly "content"?: string + } + } | { + readonly "type": "file_annotation" + readonly "file_annotation": { readonly "file_id": string; readonly "quote"?: string } + } | { + readonly "type": "file" + readonly "file": { + readonly "hash": string + readonly "name": string + readonly "content"?: ReadonlyArray<{ readonly "type": string; readonly "text"?: string }> + } + } + > + | null +} +export const ChatStreamDelta = Schema.Struct({ + "audio": Schema.optionalKey( + Schema.suspend((): Schema.Codec => ChatAudioOutput).annotate({ + "description": "Audio output data" + }) + ), + "content": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ "description": "Message content delta" }) + ), + "reasoning": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ "description": "Reasoning content delta" }) + ), + "reasoning_details": Schema.optionalKey(ChatStreamReasoningDetails), + "refusal": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ "description": "Refusal message delta" }) + ), + "role": Schema.optionalKey(Schema.Literal("assistant").annotate({ "description": "The role of the message author" })), + "tool_calls": Schema.optionalKey(Schema.Array(ChatStreamToolCall).annotate({ "description": "Tool calls delta" })), + "images": Schema.optionalKey( + Schema.Union([ + Schema.Array( + Schema.Struct({ "type": Schema.Literal("image_url"), "image_url": Schema.Struct({ "url": Schema.String }) }) + ), + Schema.Null + ]) + ), + "annotations": Schema.optionalKey(Schema.Union([ + Schema.Array(Schema.Union([ + Schema.Struct({ + "type": Schema.Literal("url_citation"), + "url_citation": Schema.Struct({ + "url": Schema.String, + "title": Schema.optionalKey(Schema.String), + "start_index": Schema.optionalKey( + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })) + ), + "end_index": Schema.optionalKey( + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })) + ), + "content": Schema.optionalKey(Schema.String) + }) + }), + Schema.Struct({ + "type": Schema.Literal("file_annotation"), + "file_annotation": Schema.Struct({ "file_id": Schema.String, "quote": Schema.optionalKey(Schema.String) }) + }), + Schema.Struct({ + "type": Schema.Literal("file"), + "file": Schema.Struct({ + "hash": Schema.String, + "name": Schema.String, + "content": Schema.optionalKey( + Schema.Array(Schema.Struct({ "type": Schema.String, "text": Schema.optionalKey(Schema.String) })) + ) + }) + }) + ], { mode: "oneOf" })), + Schema.Null + ])) +}).annotate({ "description": "Delta changes in streaming response", "identifier": "ChatStreamDelta" }) +export type AdditionalToolsItem = { + readonly "id"?: string | null + readonly "role": "unknown" | "user" | "assistant" | "system" | "critic" | "discriminator" | "developer" | "tool" + readonly "tools": ReadonlyArray< + | { + readonly "description"?: string | null + readonly "name": string + readonly "parameters": { readonly [x: string]: Schema.Json } | null + readonly "strict"?: boolean | null + readonly "type": "function" + } + | Preview_WebSearchServerTool + | Preview_20250311_WebSearchServerTool + | Legacy_WebSearchServerTool + | WebSearchServerTool + | FileSearchServerTool + | ComputerUseServerTool + | CodeInterpreterServerTool + | McpServerTool + | ImageGenerationServerTool + | CodexLocalShellTool + | ShellServerTool + | ApplyPatchServerTool + | CustomTool + | NamespaceTool + | AdvisorServerTool_OpenRouter + | SubagentServerTool_OpenRouter + | DatetimeServerTool + | FilesServerTool + | FusionServerTool_OpenRouter + | ImageGenerationServerTool_OpenRouter + | SearchModelsServerTool_OpenRouter + | WebFetchServerTool + | WebSearchServerTool_OpenRouter + | ApplyPatchServerTool_OpenRouter + | BashServerTool + | ShellServerTool_OpenRouter + | { readonly "type": string } + > + readonly "type": "additional_tools" +} +export const AdditionalToolsItem = Schema.Struct({ + "id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "role": Schema.Literals(["unknown", "user", "assistant", "system", "critic", "discriminator", "developer", "tool"]), + "tools": Schema.Array(Schema.Union([ + Schema.Struct({ + "description": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "name": Schema.String, + "parameters": Schema.Union([ + Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })), + Schema.Null + ]), + "strict": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), + "type": Schema.Literal("function") + }).annotate({ "description": "Function tool definition" }), + Preview_WebSearchServerTool, + Preview_20250311_WebSearchServerTool, + Legacy_WebSearchServerTool, + WebSearchServerTool, + FileSearchServerTool, + ComputerUseServerTool, + CodeInterpreterServerTool, + McpServerTool, + ImageGenerationServerTool, + CodexLocalShellTool, + ShellServerTool, + ApplyPatchServerTool, + CustomTool, + NamespaceTool, + AdvisorServerTool_OpenRouter, + SubagentServerTool_OpenRouter, + DatetimeServerTool, + FilesServerTool, + FusionServerTool_OpenRouter, + ImageGenerationServerTool_OpenRouter, + SearchModelsServerTool_OpenRouter, + WebFetchServerTool, + WebSearchServerTool_OpenRouter, + ApplyPatchServerTool_OpenRouter, + BashServerTool, + ShellServerTool_OpenRouter, + Schema.Struct({ "type": Schema.String }) + ])), + "type": Schema.Literal("additional_tools") +}).annotate({ + "description": "Additional tools made available to the model at this point in the input", + "identifier": "AdditionalToolsItem" +}) +export type ChatFunctionTool = + | { + readonly "cache_control"?: ChatContentCacheControl + readonly "function": { + readonly "description"?: string + readonly "name": string + readonly "parameters"?: {} + readonly "strict"?: boolean | null + } + readonly "type": "function" + } + | AdvisorServerTool_OpenRouter + | BashServerTool + | DatetimeServerTool + | FilesServerTool + | FusionServerTool_OpenRouter + | ImageGenerationServerTool_OpenRouter + | ChatSearchModelsServerTool + | SubagentServerTool_OpenRouter + | WebFetchServerTool + | OpenRouterWebSearchServerTool + | ChatWebSearchShorthand +export const ChatFunctionTool = Schema.Union([ + Schema.Struct({ + "cache_control": Schema.optionalKey(ChatContentCacheControl), + "function": Schema.Struct({ + "description": Schema.optionalKey( + Schema.String.annotate({ "description": "Function description for the model" }) + ), + "name": Schema.String.annotate({ + "description": "Function name (a-z, A-Z, 0-9, underscores, dashes, max 64 chars)" + }).check(Schema.isMaxLength(64).annotate({ "expected": "a value with a length of at most 64" })), + "parameters": Schema.optionalKey( + Schema.Struct({}).annotate({ "description": "Function parameters as JSON Schema object" }) + ), + "strict": Schema.optionalKey( + Schema.Union([Schema.Boolean, Schema.Null]).annotate({ "description": "Enable strict schema adherence" }) + ) + }).annotate({ "description": "Function definition for tool calling" }), + "type": Schema.Literal("function") + }), + AdvisorServerTool_OpenRouter, + BashServerTool, + DatetimeServerTool, + FilesServerTool, + FusionServerTool_OpenRouter, + ImageGenerationServerTool_OpenRouter, + ChatSearchModelsServerTool, + SubagentServerTool_OpenRouter, + WebFetchServerTool, + OpenRouterWebSearchServerTool, + ChatWebSearchShorthand +]).annotate({ + "description": "Tool definition for function calling (regular function or OpenRouter built-in server tool)", + "identifier": "ChatFunctionTool" +}) +export type BaseInputs = + | string + | ReadonlyArray< + | { + readonly "content": ReadonlyArray | string + readonly "phase"?: "commentary" | "final_answer" | null + readonly "role": "user" | "system" | "assistant" | "developer" + readonly "type"?: "message" + } + | OpenAIResponseInputMessageItem + | OpenAIResponseFunctionToolCallOutput + | OpenAIResponseFunctionToolCall + | OutputItemImageGenerationCall + | OutputMessage + | OpenAIResponseCustomToolCall + | OpenAIResponseCustomToolCallOutput + | ApplyPatchCallItem + | ApplyPatchCallOutputItem + > + | null +export const BaseInputs = Schema.Union([ + Schema.String, + Schema.Array(Schema.Union([ + Schema.Struct({ + "content": Schema.Union([ + Schema.Array(Schema.Union([InputText, InputImage, InputFile, InputAudio], { mode: "oneOf" })), + Schema.String + ]), + "phase": Schema.optionalKey( + Schema.Union([Schema.Literal("commentary"), Schema.Literal("final_answer"), Schema.Null]) + ), + "role": Schema.Literals(["user", "system", "assistant", "developer"]), + "type": Schema.optionalKey(Schema.Literal("message")) + }), + OpenAIResponseInputMessageItem, + OpenAIResponseFunctionToolCallOutput, + OpenAIResponseFunctionToolCall, + OutputItemImageGenerationCall, + OutputMessage, + OpenAIResponseCustomToolCall, + OpenAIResponseCustomToolCallOutput, + ApplyPatchCallItem, + ApplyPatchCallOutputItem + ])), + Schema.Null +]).annotate({ "identifier": "BaseInputs" }) +export type OutputItemAddedEvent = { + readonly "item": + | OutputMessage + | OutputItemReasoning + | OutputItemFunctionCall + | OutputItemCustomToolCall + | OutputItemWebSearchCall + | OutputItemFileSearchCall + | OutputItemImageGenerationCall + | OutputItemApplyPatchCall + readonly "output_index": number + readonly "sequence_number": number + readonly "type": "response.output_item.added" +} +export const OutputItemAddedEvent = Schema.Struct({ + "item": Schema.Union([ + OutputMessage, + OutputItemReasoning, + OutputItemFunctionCall, + OutputItemCustomToolCall, + OutputItemWebSearchCall, + OutputItemFileSearchCall, + OutputItemImageGenerationCall, + OutputItemApplyPatchCall + ], { mode: "oneOf" }), + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.output_item.added") +}).annotate({ + "description": "Event emitted when a new output item is added to the response", + "identifier": "OutputItemAddedEvent" +}) +export type OutputItemDoneEvent = { + readonly "item": + | OutputMessage + | OutputItemReasoning + | OutputItemFunctionCall + | OutputItemCustomToolCall + | OutputItemWebSearchCall + | OutputItemFileSearchCall + | OutputItemImageGenerationCall + | OutputItemApplyPatchCall + readonly "output_index": number + readonly "sequence_number": number + readonly "type": "response.output_item.done" +} +export const OutputItemDoneEvent = Schema.Struct({ + "item": Schema.Union([ + OutputMessage, + OutputItemReasoning, + OutputItemFunctionCall, + OutputItemCustomToolCall, + OutputItemWebSearchCall, + OutputItemFileSearchCall, + OutputItemImageGenerationCall, + OutputItemApplyPatchCall + ], { mode: "oneOf" }), + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.output_item.done") +}).annotate({ "description": "Event emitted when an output item is complete", "identifier": "OutputItemDoneEvent" }) +export type OutputItems = + | OutputMessageItem + | OutputReasoningItem + | OutputFunctionCallItem + | OutputWebSearchCallItem + | OutputFileSearchCallItem + | OutputImageGenerationCallItem + | OutputCodeInterpreterCallItem + | OutputComputerCallItem + | OutputDatetimeItem + | OutputWebSearchServerToolItem + | OutputCodeInterpreterServerToolItem + | OutputFileSearchServerToolItem + | OutputImageGenerationServerToolItem + | OutputBrowserUseServerToolItem + | OutputBashServerToolItem + | OutputTextEditorServerToolItem + | OutputApplyPatchServerToolItem + | OutputApplyPatchCallItem + | OutputShellCallItem + | OutputShellCallOutputItem + | OutputWebFetchServerToolItem + | OutputToolSearchServerToolItem + | OutputMemoryServerToolItem + | OutputMcpServerToolItem + | OutputSearchModelsServerToolItem + | OutputFusionServerToolItem + | OutputAdvisorServerToolItem + | OutputSubagentServerToolItem + | OutputFilesServerToolItem + | OutputCustomToolCallItem +export const OutputItems = Schema.Union([ + OutputMessageItem, + OutputReasoningItem, + OutputFunctionCallItem, + OutputWebSearchCallItem, + OutputFileSearchCallItem, + OutputImageGenerationCallItem, + OutputCodeInterpreterCallItem, + OutputComputerCallItem, + OutputDatetimeItem, + OutputWebSearchServerToolItem, + OutputCodeInterpreterServerToolItem, + OutputFileSearchServerToolItem, + OutputImageGenerationServerToolItem, + OutputBrowserUseServerToolItem, + OutputBashServerToolItem, + OutputTextEditorServerToolItem, + OutputApplyPatchServerToolItem, + OutputApplyPatchCallItem, + OutputShellCallItem, + OutputShellCallOutputItem, + OutputWebFetchServerToolItem, + OutputToolSearchServerToolItem, + OutputMemoryServerToolItem, + OutputMcpServerToolItem, + OutputSearchModelsServerToolItem, + OutputFusionServerToolItem, + OutputAdvisorServerToolItem, + OutputSubagentServerToolItem, + OutputFilesServerToolItem, + OutputCustomToolCallItem +], { mode: "oneOf" }).annotate({ "description": "An output item from the response", "identifier": "OutputItems" }) +export type AnthropicWebFetchToolResult = { + readonly "caller": AnthropicCaller + readonly "content": AnthropicWebFetchContent + readonly "tool_use_id": string + readonly "type": "web_fetch_tool_result" +} +export const AnthropicWebFetchToolResult = Schema.Struct({ + "caller": AnthropicCaller, + "content": AnthropicWebFetchContent, + "tool_use_id": Schema.String, + "type": Schema.Literal("web_fetch_tool_result") +}).annotate({ "identifier": "AnthropicWebFetchToolResult" }) +export type MessagesRequest = { + readonly "cache_control"?: AnthropicCacheControlDirective + readonly "context_management"?: { + readonly "edits"?: ReadonlyArray< + { + readonly "clear_at_least"?: AnthropicInputTokensClearAtLeast + readonly "clear_tool_inputs"?: boolean | ReadonlyArray | null + readonly "exclude_tools"?: ReadonlyArray | null + readonly "keep"?: AnthropicToolUsesKeep + readonly "trigger"?: AnthropicInputTokensTrigger | AnthropicToolUsesTrigger + readonly "type": "clear_tool_uses_20250919" + } | { + readonly "keep"?: AnthropicThinkingTurns | { readonly "type": "all" } | "all" + readonly "type": "clear_thinking_20251015" + } | { + readonly "instructions"?: string | null + readonly "pause_after_compaction"?: boolean + readonly "trigger"?: { readonly "type": "input_tokens"; readonly "value": number } | null + readonly "type": "compact_20260112" + } + > + readonly [x: string]: Schema.Json + } | null + readonly "fallbacks"?: ReadonlyArray | null + readonly "max_tokens"?: number + readonly "messages": ReadonlyArray | null + readonly "metadata"?: { readonly "user_id"?: string | null } + readonly "model": string + readonly "models"?: ReadonlyArray + readonly "output_config"?: MessagesOutputConfig + readonly "plugins"?: ReadonlyArray< + | AutoRouterPlugin + | AutoBetaRouterPlugin + | ModerationPlugin + | WebSearchPlugin + | WebFetchPlugin + | FileParserPlugin + | ResponseHealingPlugin + | ContextCompressionPlugin + | ParetoRouterPlugin + | FusionPlugin + > + readonly "provider"?: ProviderPreferences + readonly "route"?: DeprecatedRoute + readonly "service_tier"?: string + readonly "session_id"?: string + readonly "speed"?: AnthropicSpeed + readonly "stop_sequences"?: ReadonlyArray + readonly "stop_server_tools_when"?: StopServerToolsWhen + readonly "stream"?: boolean + readonly "system"?: string | ReadonlyArray + readonly "temperature"?: number + readonly "thinking"?: + | { readonly "budget_tokens": number; readonly "display"?: AnthropicThinkingDisplay; readonly "type": "enabled" } + | { readonly "type": "disabled" } + | { readonly "display"?: AnthropicThinkingDisplay; readonly "type": "adaptive" } + readonly "tool_choice"?: + | { readonly "disable_parallel_tool_use"?: boolean; readonly "type": "auto" } + | { readonly "disable_parallel_tool_use"?: boolean; readonly "type": "any" } + | { readonly "type": "none" } + | { readonly "disable_parallel_tool_use"?: boolean; readonly "name": string; readonly "type": "tool" } + readonly "tools"?: ReadonlyArray< + | { + readonly "cache_control"?: AnthropicCacheControlDirective + readonly "description"?: string + readonly "input_schema": { + readonly "properties"?: Schema.Json + readonly "required"?: ReadonlyArray | null + readonly "type"?: string + } + readonly "name": string + readonly "type"?: "custom" + } + | { + readonly "cache_control"?: AnthropicCacheControlDirective + readonly "name": "bash" + readonly "type": "bash_20250124" + } + | { + readonly "cache_control"?: AnthropicCacheControlDirective + readonly "name": "str_replace_editor" + readonly "type": "text_editor_20250124" + } + | { + readonly "allowed_domains"?: ReadonlyArray | null + readonly "blocked_domains"?: ReadonlyArray | null + readonly "cache_control"?: AnthropicCacheControlDirective + readonly "max_uses"?: number | null + readonly "name": "web_search" + readonly "type": "web_search_20250305" + readonly "user_location"?: AnthropicWebSearchToolUserLocation + } + | { + readonly "allowed_callers"?: AnthropicAllowedCallers + readonly "allowed_domains"?: ReadonlyArray | null + readonly "blocked_domains"?: ReadonlyArray | null + readonly "cache_control"?: AnthropicCacheControlDirective + readonly "max_uses"?: number | null + readonly "name": "web_search" + readonly "type": "web_search_20260209" + readonly "user_location"?: AnthropicWebSearchToolUserLocation + } + | { + readonly "allowed_callers"?: AnthropicAllowedCallers + readonly "cache_control"?: AnthropicCacheControlDirective + readonly "caching"?: AnthropicCacheControlDirective | null + readonly "defer_loading"?: boolean + readonly "max_uses"?: number + readonly "model": string + readonly "name": "advisor" + readonly "type": "advisor_20260301" + } + | BashServerTool + | DatetimeServerTool + | ImageGenerationServerTool_OpenRouter + | MessagesSearchModelsServerTool + | WebFetchServerTool + | OpenRouterWebSearchServerTool + | { readonly "type": string } + | AnthropicToolSearchToolBm25 + | AnthropicToolSearchToolRegex + > + readonly "top_k"?: number + readonly "top_p"?: number + readonly "trace"?: TraceConfig + readonly "user"?: string +} +export const MessagesRequest = Schema.Struct({ + "cache_control": Schema.optionalKey(AnthropicCacheControlDirective), + "context_management": Schema.optionalKey(Schema.Union([ + Schema.StructWithRest( + Schema.Struct({ + "edits": Schema.optionalKey(Schema.Array(Schema.Union([ + Schema.Struct({ + "clear_at_least": Schema.optionalKey(AnthropicInputTokensClearAtLeast), + "clear_tool_inputs": Schema.optionalKey( + Schema.Union([Schema.Boolean, Schema.Array(Schema.String), Schema.Null]) + ), + "exclude_tools": Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), + "keep": Schema.optionalKey(AnthropicToolUsesKeep), + "trigger": Schema.optionalKey( + Schema.Union([AnthropicInputTokensTrigger, AnthropicToolUsesTrigger], { mode: "oneOf" }) + ), + "type": Schema.Literal("clear_tool_uses_20250919") + }), + Schema.Struct({ + "keep": Schema.optionalKey( + Schema.Union([ + AnthropicThinkingTurns, + Schema.Struct({ "type": Schema.Literal("all") }), + Schema.Literal("all") + ]) + ), + "type": Schema.Literal("clear_thinking_20251015") + }), + Schema.Struct({ + "instructions": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "pause_after_compaction": Schema.optionalKey(Schema.Boolean), + "trigger": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "type": Schema.Literal("input_tokens"), + "value": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + }), + Schema.Null + ]) + ), + "type": Schema.Literal("compact_20260112") + }) + ], { mode: "oneOf" }))) + }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] + ), + Schema.Null + ])), + "fallbacks": Schema.optionalKey( + Schema.Union([Schema.Array(MessagesFallbackParam), Schema.Null]).annotate({ + "description": + "Fallback models to try if the primary model fails or refuses, in order. Handled by OpenRouter multi-model routing rather than Anthropic server-side fallbacks; cannot be combined with `models`. Each entry accepts only `model`. Maximum of 3 entries." + }) + ), + "max_tokens": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "messages": Schema.Union([Schema.Array(MessagesMessageParam), Schema.Null]), + "metadata": Schema.optionalKey( + Schema.Struct({ "user_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) }) + ), + "model": Schema.String, + "models": Schema.optionalKey(Schema.Array(Schema.String)), + "output_config": Schema.optionalKey(MessagesOutputConfig), + "plugins": Schema.optionalKey( + Schema.Array( + Schema.Union([ + AutoRouterPlugin, + AutoBetaRouterPlugin, + ModerationPlugin, + WebSearchPlugin, + WebFetchPlugin, + FileParserPlugin, + ResponseHealingPlugin, + ContextCompressionPlugin, + ParetoRouterPlugin, + FusionPlugin + ], { mode: "oneOf" }) + ).annotate({ "description": "Plugins you want to enable for this request, including their settings." }) + ), + "provider": Schema.optionalKey(ProviderPreferences), + "route": Schema.optionalKey(DeprecatedRoute), + "service_tier": Schema.optionalKey(Schema.String), + "session_id": Schema.optionalKey( + Schema.String.annotate({ + "description": + "A unique identifier for grouping related requests (e.g., a conversation or agent workflow). When provided, OpenRouter uses it as the sticky routing key, routing all requests in the session to the same provider to maximize prompt cache hits. Also used for observability grouping. If provided in both the request body and the x-session-id header, the body value takes precedence. Maximum of 256 characters." + }).check(Schema.isMaxLength(256).annotate({ "expected": "a value with a length of at most 256" })) + ), + "speed": Schema.optionalKey( + Schema.suspend((): Schema.Codec => AnthropicSpeed).annotate({ + "description": + "Controls output generation speed. When set to `fast`, uses a higher-speed inference configuration at premium pricing. Defaults to `standard` when omitted." + }) + ), + "stop_sequences": Schema.optionalKey(Schema.Array(Schema.String)), + "stop_server_tools_when": Schema.optionalKey(StopServerToolsWhen), + "stream": Schema.optionalKey(Schema.Boolean), + "system": Schema.optionalKey(Schema.Union([Schema.String, Schema.Array(AnthropicTextBlockParam)])), + "temperature": Schema.optionalKey( + Schema.Number.annotate({ "format": "double" }).check(Schema.isFinite().annotate({ "expected": "a finite number" })) + ), + "thinking": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "budget_tokens": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "display": Schema.optionalKey(AnthropicThinkingDisplay), + "type": Schema.Literal("enabled") + }), + Schema.Struct({ "type": Schema.Literal("disabled") }), + Schema.Struct({ "display": Schema.optionalKey(AnthropicThinkingDisplay), "type": Schema.Literal("adaptive") }) + ], { mode: "oneOf" }) + ), + "tool_choice": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "disable_parallel_tool_use": Schema.optionalKey(Schema.Boolean), + "type": Schema.Literal("auto") + }), + Schema.Struct({ "disable_parallel_tool_use": Schema.optionalKey(Schema.Boolean), "type": Schema.Literal("any") }), + Schema.Struct({ "type": Schema.Literal("none") }), + Schema.Struct({ + "disable_parallel_tool_use": Schema.optionalKey(Schema.Boolean), + "name": Schema.String, + "type": Schema.Literal("tool") + }) + ], { mode: "oneOf" }) + ), + "tools": Schema.optionalKey(Schema.Array(Schema.Union([ + Schema.Struct({ + "cache_control": Schema.optionalKey(AnthropicCacheControlDirective), + "description": Schema.optionalKey(Schema.String), + "input_schema": Schema.Struct({ + "properties": Schema.optionalKey(Schema.Json.annotate({ "expected": "JSON value" })), + "required": Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), + "type": Schema.optionalKey(Schema.String) + }), + "name": Schema.String, + "type": Schema.optionalKey(Schema.Literal("custom")) + }), + Schema.Struct({ + "cache_control": Schema.optionalKey(AnthropicCacheControlDirective), + "name": Schema.Literal("bash"), + "type": Schema.Literal("bash_20250124") + }), + Schema.Struct({ + "cache_control": Schema.optionalKey(AnthropicCacheControlDirective), + "name": Schema.Literal("str_replace_editor"), + "type": Schema.Literal("text_editor_20250124") + }), + Schema.Struct({ + "allowed_domains": Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), + "blocked_domains": Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), + "cache_control": Schema.optionalKey(AnthropicCacheControlDirective), + "max_uses": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]) + ), + "name": Schema.Literal("web_search"), + "type": Schema.Literal("web_search_20250305"), + "user_location": Schema.optionalKey(AnthropicWebSearchToolUserLocation) + }), + Schema.Struct({ + "allowed_callers": Schema.optionalKey(AnthropicAllowedCallers), + "allowed_domains": Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), + "blocked_domains": Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), + "cache_control": Schema.optionalKey(AnthropicCacheControlDirective), + "max_uses": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]) + ), + "name": Schema.Literal("web_search"), + "type": Schema.Literal("web_search_20260209"), + "user_location": Schema.optionalKey(AnthropicWebSearchToolUserLocation) + }), + Schema.Struct({ + "allowed_callers": Schema.optionalKey(AnthropicAllowedCallers), + "cache_control": Schema.optionalKey(AnthropicCacheControlDirective), + "caching": Schema.optionalKey(Schema.Union([AnthropicCacheControlDirective, Schema.Null])), + "defer_loading": Schema.optionalKey(Schema.Boolean), + "max_uses": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "model": Schema.String, + "name": Schema.Literal("advisor"), + "type": Schema.Literal("advisor_20260301") + }), + BashServerTool, + DatetimeServerTool, + ImageGenerationServerTool_OpenRouter, + MessagesSearchModelsServerTool, + WebFetchServerTool, + OpenRouterWebSearchServerTool, + Schema.Struct({ "type": Schema.String }), + AnthropicToolSearchToolBm25, + AnthropicToolSearchToolRegex + ]))), + "top_k": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "top_p": Schema.optionalKey( + Schema.Number.annotate({ "format": "double" }).check(Schema.isFinite().annotate({ "expected": "a finite number" })) + ), + "trace": Schema.optionalKey(TraceConfig), + "user": Schema.optionalKey( + Schema.String.annotate({ + "description": + "A unique identifier representing your end-user, which helps distinguish between different users of your app. This allows your app to identify specific users in case of abuse reports, preventing your entire app from being affected by the actions of individual users. Maximum of 256 characters." + }).check(Schema.isMaxLength(256).annotate({ "expected": "a value with a length of at most 256" })) + ) +}).annotate({ "description": "Request schema for Anthropic Messages API endpoint", "identifier": "MessagesRequest" }) +export type ImageGenerationUsage = { + readonly "cache_creation"?: AnthropicCacheCreation + readonly "completion_tokens": number + readonly "completion_tokens_details"?: Union_6 + readonly "cost"?: number | null + readonly "cost_details"?: CostDetails + readonly "is_byok"?: boolean + readonly "iterations"?: Union_7 + readonly "prompt_tokens": number + readonly "prompt_tokens_details"?: Union_8 + readonly "server_tool_use"?: Union_9 + readonly "service_tier"?: string | null + readonly "speed"?: AnthropicSpeed + readonly "total_tokens": number +} +export const ImageGenerationUsage = Schema.Struct({ + "cache_creation": Schema.optionalKey(AnthropicCacheCreation), + "completion_tokens": Schema.Number.annotate({ "description": "The tokens generated" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ), + "completion_tokens_details": Schema.optionalKey(Union_6), + "cost": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), Schema.Null]) + .annotate({ "description": "Cost of the completion", "format": "double" }) + ), + "cost_details": Schema.optionalKey(CostDetails), + "is_byok": Schema.optionalKey( + Schema.Boolean.annotate({ "description": "Whether a request was made using a Bring Your Own Key configuration" }) + ), + "iterations": Schema.optionalKey(Union_7), + "prompt_tokens": Schema.Number.annotate({ "description": "Including images, input audio, and tools if any" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ), + "prompt_tokens_details": Schema.optionalKey(Union_8), + "server_tool_use": Schema.optionalKey(Union_9), + "service_tier": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "The service tier used by the upstream provider for this request" + }) + ), + "speed": Schema.optionalKey(AnthropicSpeed), + "total_tokens": Schema.Number.annotate({ "description": "Sum of the above two fields" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ) +}).annotate({ + "description": "Token and cost usage for the image generation request, when available", + "identifier": "ImageGenerationUsage" +}) +export type ModelsListResponse = { + readonly "data": ModelsListResponseData + readonly "links": { readonly "next": string | null } + readonly "total_count": number +} +export const ModelsListResponse = Schema.Struct({ + "data": ModelsListResponseData, + "links": Schema.Struct({ + "next": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "URL for the next page of results, or null if this is the last page" + }) + }).annotate({ "description": "Pagination links" }), + "total_count": Schema.Number.annotate({ "description": "Total number of models matching the query" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ) +}).annotate({ "description": "List of available models", "identifier": "ModelsListResponse" }) +export type ChatChoice = { + readonly "finish_reason": ChatFinishReasonEnum + readonly "index": number + readonly "logprobs"?: ChatTokenLogprobs + readonly "message": ChatAssistantMessage +} +export const ChatChoice = Schema.Struct({ + "finish_reason": ChatFinishReasonEnum, + "index": Schema.Number.annotate({ "description": "Choice index" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ), + "logprobs": Schema.optionalKey(ChatTokenLogprobs), + "message": ChatAssistantMessage +}).annotate({ "description": "Chat completion choice", "identifier": "ChatChoice" }) +export type ChatMessages = + | ChatSystemMessage + | ChatUserMessage + | ChatDeveloperMessage + | ChatAssistantMessage + | ChatToolMessage +export const ChatMessages = Schema.Union([ + ChatSystemMessage, + ChatUserMessage, + ChatDeveloperMessage, + ChatAssistantMessage, + ChatToolMessage +], { mode: "oneOf" }).annotate({ + "description": "Chat completion message with role-based discrimination", + "identifier": "ChatMessages" +}) +export type ChatStreamChoice = { + readonly "delta": ChatStreamDelta + readonly "finish_reason"?: ChatFinishReasonEnum + readonly "index": number + readonly "logprobs"?: ChatTokenLogprobs +} +export const ChatStreamChoice = Schema.Struct({ + "delta": ChatStreamDelta, + "finish_reason": Schema.optionalKey(ChatFinishReasonEnum), + "index": Schema.Number.annotate({ "description": "Choice index" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ), + "logprobs": Schema.optionalKey(ChatTokenLogprobs) +}).annotate({ "description": "Streaming completion choice chunk", "identifier": "ChatStreamChoice" }) +export type Arrays_8 = ReadonlyArray< + | ReasoningItem + | EasyInputMessage + | InputMessageItem + | FunctionCallItem + | FunctionCallOutputItem + | ApplyPatchCallItem + | ApplyPatchCallOutputItem + | { + readonly "content": ReadonlyArray< + { + readonly "annotations"?: ReadonlyArray< + { + readonly "file_id": string + readonly "filename": string + readonly "index": number + readonly "type": "file_citation" + } | { + readonly "content"?: string + readonly "end_index": number + readonly "start_index": number + readonly "title": string + readonly "type": never + readonly "url": string + readonly "file_id": string + readonly "filename": string + readonly "index": number + } | { + readonly "file_id": string + readonly "index": number + readonly "type": never + readonly "filename": string + } | { + readonly "file_id": string + readonly "filename": string + readonly "index": number + readonly "type": never + readonly "content"?: string + readonly "end_index": number + readonly "start_index": number + readonly "title": string + readonly "url": string + } | { + readonly "content"?: string + readonly "end_index": number + readonly "start_index": number + readonly "title": string + readonly "type": "url_citation" + readonly "url": string + } | { + readonly "file_id": string + readonly "index": number + readonly "type": never + readonly "content"?: string + readonly "end_index": number + readonly "start_index": number + readonly "title": string + readonly "url": string + } | { + readonly "file_id": string + readonly "filename": string + readonly "index": number + readonly "type": never + } | { + readonly "content"?: string + readonly "end_index": number + readonly "start_index": number + readonly "title": string + readonly "type": never + readonly "url": string + readonly "file_id": string + readonly "index": number + } | { readonly "file_id": string; readonly "index": number; readonly "type": "file_path" } + > + readonly "logprobs"?: ReadonlyArray< + { + readonly "bytes": ReadonlyArray + readonly "logprob": number + readonly "token": string + readonly "top_logprobs": ReadonlyArray< + { readonly "bytes": ReadonlyArray; readonly "logprob": number; readonly "token": string } + > + } + > + readonly "text": string + readonly "type": "output_text" + } | { + readonly "refusal": string + readonly "type": never + readonly "annotations"?: ReadonlyArray + readonly "logprobs"?: ReadonlyArray< + { + readonly "bytes": ReadonlyArray + readonly "logprob": number + readonly "token": string + readonly "top_logprobs": ReadonlyArray< + { readonly "bytes": ReadonlyArray; readonly "logprob": number; readonly "token": string } + > + } + > + readonly "text": string + } | { + readonly "annotations"?: ReadonlyArray + readonly "logprobs"?: ReadonlyArray< + { + readonly "bytes": ReadonlyArray + readonly "logprob": number + readonly "token": string + readonly "top_logprobs": ReadonlyArray< + { readonly "bytes": ReadonlyArray; readonly "logprob": number; readonly "token": string } + > + } + > + readonly "text": string + readonly "type": never + readonly "refusal": string + } | { readonly "refusal": string; readonly "type": "refusal" } + > + readonly "id": string + readonly "phase"?: "commentary" | "final_answer" | null + readonly "role": "assistant" + readonly "status"?: "completed" | "incomplete" | "in_progress" + readonly "type": "message" + } + | { + readonly "content"?: ReadonlyArray<{ readonly "text": string; readonly "type": "reasoning_text" }> + readonly "encrypted_content"?: string | null + readonly "id": string + readonly "status"?: "completed" | "incomplete" | "in_progress" + readonly "summary": ReadonlyArray<{ readonly "text": string; readonly "type": "summary_text" }> + readonly "type": "reasoning" + readonly "format"?: ReasoningFormat + readonly "signature"?: string | null + } + | OutputFunctionCallItem + | OutputCustomToolCallItem + | OutputWebSearchCallItem + | OutputFileSearchCallItem + | OutputImageGenerationCallItem + | OutputCodeInterpreterCallItem + | OutputComputerCallItem + | OutputDatetimeItem + | OutputWebSearchServerToolItem + | OutputCodeInterpreterServerToolItem + | OutputFileSearchServerToolItem + | OutputImageGenerationServerToolItem + | OutputBrowserUseServerToolItem + | OutputBashServerToolItem + | OutputTextEditorServerToolItem + | OutputApplyPatchServerToolItem + | OutputWebFetchServerToolItem + | OutputToolSearchServerToolItem + | OutputMemoryServerToolItem + | OutputMcpServerToolItem + | OutputSearchModelsServerToolItem + | OutputFusionServerToolItem + | OutputAdvisorServerToolItem + | OutputSubagentServerToolItem + | OutputFilesServerToolItem + | LocalShellCallItem + | LocalShellCallOutputItem + | ShellCallItem + | ShellCallOutputItem + | McpListToolsItem + | McpApprovalRequestItem + | McpApprovalResponseItem + | McpCallItem + | CustomToolCallItem + | CustomToolCallOutputItem + | CompactionItem + | ContextCompactionItem + | ItemReferenceItem + | AdditionalToolsItem + | AgentMessageItem +> +export const Arrays_8 = Schema.Array( + Schema.Union([ + ReasoningItem, + EasyInputMessage, + InputMessageItem, + FunctionCallItem, + FunctionCallOutputItem, + ApplyPatchCallItem, + ApplyPatchCallOutputItem, + Schema.Struct({ + "content": Schema.Union([Schema.Array(Schema.Union([ + Schema.Union([ + Schema.Struct({ + "annotations": Schema.optionalKey( + Schema.Array( + Schema.Union([ + Schema.Union([ + Schema.Struct({ + "file_id": Schema.String, + "filename": Schema.String, + "index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("file_citation") + }), + Schema.Struct({ + "content": Schema.optionalKey(Schema.String), + "end_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "start_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "title": Schema.String, + "type": Schema.Never, + "url": Schema.String, + "file_id": Schema.String, + "filename": Schema.String, + "index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + }), + Schema.Struct({ + "file_id": Schema.String, + "index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Never, + "filename": Schema.String + }) + ]), + Schema.Union([ + Schema.Struct({ + "file_id": Schema.String, + "filename": Schema.String, + "index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Never, + "content": Schema.optionalKey(Schema.String), + "end_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "start_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "title": Schema.String, + "url": Schema.String + }), + Schema.Struct({ + "content": Schema.optionalKey(Schema.String), + "end_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "start_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "title": Schema.String, + "type": Schema.Literal("url_citation"), + "url": Schema.String + }), + Schema.Struct({ + "file_id": Schema.String, + "index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Never, + "content": Schema.optionalKey(Schema.String), + "end_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "start_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "title": Schema.String, + "url": Schema.String + }) + ]), + Schema.Union([ + Schema.Struct({ + "file_id": Schema.String, + "filename": Schema.String, + "index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Never + }), + Schema.Struct({ + "content": Schema.optionalKey(Schema.String), + "end_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "start_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "title": Schema.String, + "type": Schema.Never, + "url": Schema.String, + "file_id": Schema.String, + "index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + }), + Schema.Struct({ + "file_id": Schema.String, + "index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("file_path") + }) + ]) + ]) + ) + ), + "logprobs": Schema.optionalKey(Schema.Array(Schema.Struct({ + "bytes": Schema.Array(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "logprob": Schema.Number.annotate({ "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ), + "token": Schema.String, + "top_logprobs": Schema.Array( + Schema.Struct({ + "bytes": Schema.Array(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "logprob": Schema.Number.annotate({ "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ), + "token": Schema.String + }) + ) + }))), + "text": Schema.String, + "type": Schema.Literal("output_text") + }), + Schema.Struct({ + "refusal": Schema.String, + "type": Schema.Never, + "annotations": Schema.optionalKey(Schema.Array(OpenAIResponsesAnnotation)), + "logprobs": Schema.optionalKey(Schema.Array(Schema.Struct({ + "bytes": Schema.Array(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "logprob": Schema.Number.annotate({ "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ), + "token": Schema.String, + "top_logprobs": Schema.Array( + Schema.Struct({ + "bytes": Schema.Array(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "logprob": Schema.Number.annotate({ "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ), + "token": Schema.String + }) + ) + }))), + "text": Schema.String + }) + ]), + Schema.Union([ + Schema.Struct({ + "annotations": Schema.optionalKey(Schema.Array(OpenAIResponsesAnnotation)), + "logprobs": Schema.optionalKey(Schema.Array(Schema.Struct({ + "bytes": Schema.Array(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "logprob": Schema.Number.annotate({ "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ), + "token": Schema.String, + "top_logprobs": Schema.Array( + Schema.Struct({ + "bytes": Schema.Array(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "logprob": Schema.Number.annotate({ "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ), + "token": Schema.String + }) + ) + }))), + "text": Schema.String, + "type": Schema.Never, + "refusal": Schema.String + }), + Schema.Struct({ "refusal": Schema.String, "type": Schema.Literal("refusal") }) + ]) + ]))]), + "id": Schema.String, + "phase": Schema.optionalKey( + Schema.Union([Schema.Literal("commentary"), Schema.Literal("final_answer"), Schema.Null]).annotate({ + "description": + "The phase of an assistant message. Use `commentary` for an intermediate assistant message and `final_answer` for the final assistant message. For follow-up requests with models like `gpt-5.3-codex` and later, preserve and resend phase on all assistant messages. Omitting it can degrade performance. Not used for user messages." + }) + ), + "role": Schema.Literal("assistant"), + "status": Schema.optionalKey(Schema.Literals(["completed", "incomplete", "in_progress"])), + "type": Schema.Literal("message") + }).annotate({ "description": "An output message item" }), + Schema.Struct({ + "content": Schema.optionalKey( + Schema.Union([Schema.Array(Schema.Struct({ "text": Schema.String, "type": Schema.Literal("reasoning_text") }))]) + ), + "encrypted_content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "id": Schema.String, + "status": Schema.optionalKey(Schema.Literals(["completed", "incomplete", "in_progress"])), + "summary": Schema.Union([ + Schema.Array(Schema.Struct({ "text": Schema.String, "type": Schema.Literal("summary_text") })) + ]), + "type": Schema.Literal("reasoning"), + "format": Schema.optionalKey(ReasoningFormat), + "signature": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "A signature for the reasoning content, used for verification" + }) + ) + }).annotate({ "description": "An output item containing reasoning" }), + OutputFunctionCallItem, + OutputCustomToolCallItem, + OutputWebSearchCallItem, + OutputFileSearchCallItem, + OutputImageGenerationCallItem, + OutputCodeInterpreterCallItem, + OutputComputerCallItem, + OutputDatetimeItem, + OutputWebSearchServerToolItem, + OutputCodeInterpreterServerToolItem, + OutputFileSearchServerToolItem, + OutputImageGenerationServerToolItem, + OutputBrowserUseServerToolItem, + OutputBashServerToolItem, + OutputTextEditorServerToolItem, + OutputApplyPatchServerToolItem, + OutputWebFetchServerToolItem, + OutputToolSearchServerToolItem, + OutputMemoryServerToolItem, + OutputMcpServerToolItem, + OutputSearchModelsServerToolItem, + OutputFusionServerToolItem, + OutputAdvisorServerToolItem, + OutputSubagentServerToolItem, + OutputFilesServerToolItem, + LocalShellCallItem, + LocalShellCallOutputItem, + ShellCallItem, + ShellCallOutputItem, + McpListToolsItem, + McpApprovalRequestItem, + McpApprovalResponseItem, + McpCallItem, + CustomToolCallItem, + CustomToolCallOutputItem, + CompactionItem, + ContextCompactionItem, + ItemReferenceItem, + AdditionalToolsItem, + AgentMessageItem + ]) +) +export type BaseResponsesResult = { + readonly "background"?: boolean | null + readonly "completed_at": number | null + readonly "created_at": number + readonly "error": ResponsesErrorField + readonly "frequency_penalty": number | null + readonly "id": string + readonly "incomplete_details": IncompleteDetails + readonly "instructions": BaseInputs + readonly "max_output_tokens"?: number | null + readonly "max_tool_calls"?: number | null + readonly "metadata": RequestMetadata + readonly "model": string + readonly "object": "response" + readonly "output": ReadonlyArray< + | OutputMessage + | OutputItemReasoning + | OutputItemFunctionCall + | OutputItemCustomToolCall + | OutputItemWebSearchCall + | OutputItemFileSearchCall + | OutputItemImageGenerationCall + | OutputItemApplyPatchCall + > + readonly "output_text"?: string + readonly "parallel_tool_calls": boolean + readonly "presence_penalty": number | null + readonly "previous_response_id"?: string | null + readonly "prompt"?: StoredPromptTemplate + readonly "prompt_cache_key"?: string | null + readonly "prompt_cache_options"?: PromptCacheOptions + readonly "reasoning"?: BaseReasoningConfig + readonly "safety_identifier"?: string | null + readonly "service_tier"?: ServiceTier + readonly "status": OpenAIResponsesResponseStatus + readonly "store"?: boolean + readonly "temperature": number | null + readonly "text"?: TextConfig + readonly "tool_choice": OpenAIResponsesToolChoice + readonly "tools": ReadonlyArray< + | { + readonly "description"?: string | null + readonly "name": string + readonly "parameters": { readonly [x: string]: Schema.Json } | null + readonly "strict"?: boolean | null + readonly "type": "function" + } + | Preview_WebSearchServerTool + | Preview_20250311_WebSearchServerTool + | Legacy_WebSearchServerTool + | WebSearchServerTool + | FileSearchServerTool + | ComputerUseServerTool + | CodeInterpreterServerTool + | McpServerTool + | ImageGenerationServerTool + | CodexLocalShellTool + | ShellServerTool + | ApplyPatchServerTool + | CustomTool + | NamespaceTool + > + readonly "top_logprobs"?: number + readonly "top_p": number | null + readonly "truncation"?: Truncation + readonly "usage"?: OpenAIResponsesUsage + readonly "user"?: string | null +} +export const BaseResponsesResult = Schema.Struct({ + "background": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), + "completed_at": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]), + "created_at": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "error": ResponsesErrorField, + "frequency_penalty": Schema.Union([ + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), + Schema.Null + ]).annotate({ "format": "double" }), + "id": Schema.String, + "incomplete_details": IncompleteDetails, + "instructions": BaseInputs, + "max_output_tokens": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]) + ), + "max_tool_calls": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]) + ), + "metadata": RequestMetadata, + "model": Schema.String, + "object": Schema.Literal("response"), + "output": Schema.Array( + Schema.Union([ + OutputMessage, + OutputItemReasoning, + OutputItemFunctionCall, + OutputItemCustomToolCall, + OutputItemWebSearchCall, + OutputItemFileSearchCall, + OutputItemImageGenerationCall, + OutputItemApplyPatchCall + ], { mode: "oneOf" }) + ), + "output_text": Schema.optionalKey(Schema.String), + "parallel_tool_calls": Schema.Boolean, + "presence_penalty": Schema.Union([ + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), + Schema.Null + ]).annotate({ "format": "double" }), + "previous_response_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "prompt": Schema.optionalKey(StoredPromptTemplate), + "prompt_cache_key": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "prompt_cache_options": Schema.optionalKey(PromptCacheOptions), + "reasoning": Schema.optionalKey(BaseReasoningConfig), + "safety_identifier": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "service_tier": Schema.optionalKey(ServiceTier), + "status": OpenAIResponsesResponseStatus, + "store": Schema.optionalKey(Schema.Boolean), + "temperature": Schema.Union([ + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), + Schema.Null + ]).annotate({ "format": "double" }), + "text": Schema.optionalKey(TextConfig), + "tool_choice": OpenAIResponsesToolChoice, + "tools": Schema.Array(Schema.Union([ + Schema.Struct({ + "description": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "name": Schema.String, + "parameters": Schema.Union([ + Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })), + Schema.Null + ]), + "strict": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), + "type": Schema.Literal("function") + }).annotate({ "description": "Function tool definition" }), + Preview_WebSearchServerTool, + Preview_20250311_WebSearchServerTool, + Legacy_WebSearchServerTool, + WebSearchServerTool, + FileSearchServerTool, + ComputerUseServerTool, + CodeInterpreterServerTool, + McpServerTool, + ImageGenerationServerTool, + CodexLocalShellTool, + ShellServerTool, + ApplyPatchServerTool, + CustomTool, + NamespaceTool + ], { mode: "oneOf" })), + "top_logprobs": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "top_p": Schema.Union([ + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), + Schema.Null + ]).annotate({ "format": "double" }), + "truncation": Schema.optionalKey(Truncation), + "usage": Schema.optionalKey(OpenAIResponsesUsage), + "user": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) +}).annotate({ "identifier": "BaseResponsesResult" }) +export type OpenResponsesResult = { + readonly "background"?: boolean | null + readonly "completed_at": number | null + readonly "created_at": number + readonly "error": ResponsesErrorField + readonly "frequency_penalty": number | null + readonly "id": string + readonly "incomplete_details": IncompleteDetails + readonly "instructions": BaseInputs + readonly "max_output_tokens"?: number | null + readonly "max_tool_calls"?: number | null + readonly "metadata": RequestMetadata + readonly "model": string + readonly "object": "response" + readonly "output": ReadonlyArray< + { + readonly "content": ReadonlyArray< + { + readonly "annotations"?: ReadonlyArray< + { + readonly "file_id": string + readonly "filename": string + readonly "index": number + readonly "type": "file_citation" + } | { + readonly "content"?: string + readonly "end_index": number + readonly "start_index": number + readonly "title": string + readonly "type": never + readonly "url": string + readonly "file_id": string + readonly "filename": string + readonly "index": number + } | { + readonly "file_id": string + readonly "index": number + readonly "type": never + readonly "filename": string + } | { + readonly "file_id": string + readonly "filename": string + readonly "index": number + readonly "type": never + readonly "content"?: string + readonly "end_index": number + readonly "start_index": number + readonly "title": string + readonly "url": string + } | { + readonly "content"?: string + readonly "end_index": number + readonly "start_index": number + readonly "title": string + readonly "type": "url_citation" + readonly "url": string + } | { + readonly "file_id": string + readonly "index": number + readonly "type": never + readonly "content"?: string + readonly "end_index": number + readonly "start_index": number + readonly "title": string + readonly "url": string + } | { + readonly "file_id": string + readonly "filename": string + readonly "index": number + readonly "type": never + } | { + readonly "content"?: string + readonly "end_index": number + readonly "start_index": number + readonly "title": string + readonly "type": never + readonly "url": string + readonly "file_id": string + readonly "index": number + } | { readonly "file_id": string; readonly "index": number; readonly "type": "file_path" } + > + readonly "logprobs"?: ReadonlyArray< + { + readonly "bytes": ReadonlyArray + readonly "logprob": number + readonly "token": string + readonly "top_logprobs": ReadonlyArray< + { readonly "bytes": ReadonlyArray; readonly "logprob": number; readonly "token": string } + > + } + > + readonly "text": string + readonly "type": "output_text" + } | { + readonly "refusal": string + readonly "type": never + readonly "annotations"?: ReadonlyArray + readonly "logprobs"?: ReadonlyArray< + { + readonly "bytes": ReadonlyArray + readonly "logprob": number + readonly "token": string + readonly "top_logprobs": ReadonlyArray< + { readonly "bytes": ReadonlyArray; readonly "logprob": number; readonly "token": string } + > + } + > + readonly "text": string + } | { + readonly "annotations"?: ReadonlyArray + readonly "logprobs"?: ReadonlyArray< + { + readonly "bytes": ReadonlyArray + readonly "logprob": number + readonly "token": string + readonly "top_logprobs": ReadonlyArray< + { readonly "bytes": ReadonlyArray; readonly "logprob": number; readonly "token": string } + > + } + > + readonly "text": string + readonly "type": never + readonly "refusal": string + } | { readonly "refusal": string; readonly "type": "refusal" } + > + readonly "id": string + readonly "phase"?: "commentary" | "final_answer" | null + readonly "role": "assistant" + readonly "status"?: "completed" | "incomplete" | "in_progress" + readonly "type": "message" + } | { + readonly "content": ReadonlyArray< + { + readonly "annotations"?: ReadonlyArray + readonly "logprobs"?: ReadonlyArray< + { + readonly "bytes": ReadonlyArray + readonly "logprob": number + readonly "token": string + readonly "top_logprobs": ReadonlyArray< + { readonly "bytes": ReadonlyArray; readonly "logprob": number; readonly "token": string } + > + } + > + readonly "text": string + readonly "type": never + } | { readonly "refusal": string; readonly "type": never; readonly "text": string } + > + readonly "encrypted_content"?: string | null + readonly "id": string + readonly "status"?: "completed" | "incomplete" | "in_progress" + readonly "summary": ReadonlyArray + readonly "type": never + readonly "format"?: ReasoningFormat + readonly "signature"?: string | null + readonly "phase"?: "commentary" | "final_answer" | null + readonly "role": "assistant" + } | { + readonly "arguments": string + readonly "call_id": string + readonly "id": string + readonly "name": string + readonly "namespace"?: string + readonly "status"?: "completed" | "incomplete" | "in_progress" + readonly "type": never + readonly "content": ReadonlyArray + readonly "phase"?: "commentary" | "final_answer" | null + readonly "role": "assistant" + } | { + readonly "action"?: + | { + readonly "queries"?: ReadonlyArray + readonly "query": string + readonly "sources"?: ReadonlyArray + readonly "type": "search" + } + | { readonly "type": "open_page"; readonly "url"?: string | null } + | { readonly "pattern": string; readonly "type": "find_in_page"; readonly "url": string } + readonly "id": string + readonly "status": "completed" | "in_progress" + readonly "type": never + readonly "content": ReadonlyArray + readonly "phase"?: "commentary" | "final_answer" | null + readonly "role": "assistant" + } | { + readonly "id": string + readonly "queries": ReadonlyArray + readonly "status": "completed" | "in_progress" + readonly "type": never + readonly "content": ReadonlyArray + readonly "phase"?: "commentary" | "final_answer" | null + readonly "role": "assistant" + } | { + readonly "id": string + readonly "result"?: string | null + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "prompt"?: string + readonly "content": ReadonlyArray + readonly "phase"?: "commentary" | "final_answer" | null + readonly "role": "assistant" + } | { + readonly "code": string | null + readonly "container_id": string + readonly "id": string + readonly "outputs": + | ReadonlyArray< + { readonly "type": "image"; readonly "url": string } | { readonly "logs": string; readonly "type": "logs" } + > + | null + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "type": never + readonly "content": ReadonlyArray + readonly "phase"?: "commentary" | "final_answer" | null + readonly "role": "assistant" + } | { + readonly "action"?: Schema.Json + readonly "call_id": string + readonly "id": string + readonly "pending_safety_checks": ReadonlyArray< + { readonly "code": string; readonly "id": string; readonly "message": string } + > + readonly "status": "completed" | "incomplete" | "in_progress" + readonly "type": never + readonly "content": ReadonlyArray + readonly "phase"?: "commentary" | "final_answer" | null + readonly "role": "assistant" + } | { + readonly "datetime": string + readonly "id": string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "timezone": string + readonly "type": never + readonly "content": ReadonlyArray + readonly "phase"?: "commentary" | "final_answer" | null + readonly "role": "assistant" + } | { + readonly "action"?: { + readonly "query": string + readonly "sources"?: ReadonlyArray<{ readonly "type": "url"; readonly "url": string }> + readonly "type": "search" + } + readonly "id": string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "type": never + readonly "content": ReadonlyArray + readonly "phase"?: "commentary" | "final_answer" | null + readonly "role": "assistant" + } | { + readonly "code"?: string + readonly "exitCode"?: number + readonly "id": string + readonly "language"?: string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "stderr"?: string + readonly "stdout"?: string + readonly "type": never + readonly "content": ReadonlyArray + readonly "phase"?: "commentary" | "final_answer" | null + readonly "role": "assistant" + } | { + readonly "id": string + readonly "queries"?: ReadonlyArray + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "type": never + readonly "content": ReadonlyArray + readonly "phase"?: "commentary" | "final_answer" | null + readonly "role": "assistant" + } | { + readonly "id": string + readonly "imageB64"?: string + readonly "imageUrl"?: string + readonly "prompt"?: string + readonly "result"?: string | null + readonly "revisedPrompt"?: string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "type": never + readonly "content": ReadonlyArray + readonly "phase"?: "commentary" | "final_answer" | null + readonly "role": "assistant" + } | { + readonly "action"?: string + readonly "id": string + readonly "screenshotB64"?: string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "type": never + readonly "content": ReadonlyArray + readonly "phase"?: "commentary" | "final_answer" | null + readonly "role": "assistant" + } | { + readonly "arguments"?: string + readonly "call_id"?: string + readonly "command"?: string + readonly "exitCode"?: number + readonly "id": string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "stderr"?: string + readonly "stdout"?: string + readonly "type": never + readonly "content": ReadonlyArray + readonly "phase"?: "commentary" | "final_answer" | null + readonly "role": "assistant" + } | { + readonly "command"?: "view" | "create" | "str_replace" | "insert" + readonly "filePath"?: string + readonly "id": string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "type": never + readonly "content": ReadonlyArray + readonly "phase"?: "commentary" | "final_answer" | null + readonly "role": "assistant" + } | { + readonly "call_id"?: string + readonly "id": string + readonly "operation"?: ApplyPatchCallOperation + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "type": never + readonly "content": ReadonlyArray + readonly "phase"?: "commentary" | "final_answer" | null + readonly "role": "assistant" + } | { + readonly "call_id": string + readonly "id": string + readonly "operation": ApplyPatchCallOperation + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "content": ReadonlyArray + readonly "phase"?: "commentary" | "final_answer" | null + readonly "role": "assistant" + } | { + readonly "action"?: { + readonly "commands": ReadonlyArray + readonly "max_output_length": number | null + readonly "timeout_ms": number | null + } + readonly "call_id": string + readonly "id": string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "type": never + readonly "content": ReadonlyArray + readonly "phase"?: "commentary" | "final_answer" | null + readonly "role": "assistant" + } | { + readonly "call_id": string + readonly "id": string + readonly "max_output_length"?: number | null + readonly "output": ReadonlyArray< + { + readonly "outcome": { readonly "exit_code": number; readonly "type": "exit" } | { readonly "type": "timeout" } + readonly "stderr": string + readonly "stdout": string + } + > + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "type": never + readonly "content": ReadonlyArray + readonly "phase"?: "commentary" | "final_answer" | null + readonly "role": "assistant" + } | { + readonly "content": never + readonly "error"?: string + readonly "httpStatus"?: number + readonly "id": string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "title"?: string + readonly "type": never + readonly "url"?: string + readonly "phase"?: "commentary" | "final_answer" | null + readonly "role": "assistant" + } | { + readonly "id": string + readonly "query"?: string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "type": never + readonly "content": ReadonlyArray + readonly "phase"?: "commentary" | "final_answer" | null + readonly "role": "assistant" + } | { + readonly "action"?: "read" | "write" | "delete" + readonly "id": string + readonly "key"?: string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "type": never + readonly "value"?: Schema.Json + readonly "content": ReadonlyArray + readonly "phase"?: "commentary" | "final_answer" | null + readonly "role": "assistant" + } | { + readonly "id": string + readonly "serverLabel"?: string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "toolName"?: string + readonly "type": never + readonly "content": ReadonlyArray + readonly "phase"?: "commentary" | "final_answer" | null + readonly "role": "assistant" + } | { + readonly "arguments"?: string + readonly "id": string + readonly "query"?: string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "type": never + readonly "content": ReadonlyArray + readonly "phase"?: "commentary" | "final_answer" | null + readonly "role": "assistant" + } | { + readonly "analysis"?: FusionAnalysisResult + readonly "error"?: string + readonly "failed_models"?: ReadonlyArray< + { readonly "error": string; readonly "model": string; readonly "status_code"?: number } + > + readonly "failure_reason"?: string + readonly "id": string + readonly "responses"?: ReadonlyArray<{ readonly "content"?: string; readonly "model": string }> + readonly "sources"?: ReadonlyArray + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "type": never + readonly "content": ReadonlyArray + readonly "phase"?: "commentary" | "final_answer" | null + readonly "role": "assistant" + } | { + readonly "advice"?: string + readonly "error"?: string + readonly "id": string + readonly "instance_name"?: string + readonly "model"?: string + readonly "prompt"?: string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "type": never + readonly "content": ReadonlyArray + readonly "phase"?: "commentary" | "final_answer" | null + readonly "role": "assistant" + } | { + readonly "error"?: string + readonly "id": string + readonly "instance_name"?: string + readonly "model"?: string + readonly "name"?: string + readonly "outcome"?: string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "task_description"?: string + readonly "task_name"?: string + readonly "type": never + readonly "content": ReadonlyArray + readonly "phase"?: "commentary" | "final_answer" | null + readonly "role": "assistant" + } | { + readonly "error"?: string + readonly "file_id"?: string + readonly "filename"?: string + readonly "id": string + readonly "operation"?: string + readonly "result"?: string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "type": never + readonly "content": ReadonlyArray + readonly "phase"?: "commentary" | "final_answer" | null + readonly "role": "assistant" + } | { + readonly "call_id": string + readonly "id": string + readonly "input": string + readonly "name": string + readonly "namespace"?: string + readonly "type": never + readonly "content": ReadonlyArray + readonly "phase"?: "commentary" | "final_answer" | null + readonly "role": "assistant" + readonly "status"?: "completed" | "incomplete" | "in_progress" + } | { + readonly "content": ReadonlyArray< + { + readonly "annotations"?: ReadonlyArray + readonly "logprobs"?: ReadonlyArray< + { + readonly "bytes": ReadonlyArray + readonly "logprob": number + readonly "token": string + readonly "top_logprobs": ReadonlyArray< + { readonly "bytes": ReadonlyArray; readonly "logprob": number; readonly "token": string } + > + } + > + readonly "text": string + readonly "type": never + } | { readonly "refusal": string; readonly "type": never; readonly "text": string } + > + readonly "id": string + readonly "phase"?: "commentary" | "final_answer" | null + readonly "role": "assistant" + readonly "status"?: "completed" | "incomplete" | "in_progress" + readonly "type": never + readonly "encrypted_content"?: string | null + readonly "summary": ReadonlyArray + } | { + readonly "content"?: ReadonlyArray<{ readonly "text": string; readonly "type": "reasoning_text" }> + readonly "encrypted_content"?: string | null + readonly "id": string + readonly "status"?: "completed" | "incomplete" | "in_progress" + readonly "summary": ReadonlyArray<{ readonly "text": string; readonly "type": "summary_text" }> + readonly "type": "reasoning" + readonly "format"?: ReasoningFormat + readonly "signature"?: string | null + } | { + readonly "arguments": string + readonly "call_id": string + readonly "id": string + readonly "name": string + readonly "namespace"?: string + readonly "status"?: "completed" | "incomplete" | "in_progress" + readonly "type": never + readonly "content"?: ReadonlyArray + readonly "encrypted_content"?: string | null + readonly "summary": ReadonlyArray + } | { + readonly "action"?: + | { + readonly "queries"?: ReadonlyArray + readonly "query": string + readonly "sources"?: ReadonlyArray + readonly "type": "search" + } + | { readonly "type": "open_page"; readonly "url"?: string | null } + | { readonly "pattern": string; readonly "type": "find_in_page"; readonly "url": string } + readonly "id": string + readonly "status": "completed" | "in_progress" + readonly "type": never + readonly "content"?: ReadonlyArray + readonly "encrypted_content"?: string | null + readonly "summary": ReadonlyArray + } | { + readonly "id": string + readonly "queries": ReadonlyArray + readonly "status": "completed" | "in_progress" + readonly "type": never + readonly "content"?: ReadonlyArray + readonly "encrypted_content"?: string | null + readonly "summary": ReadonlyArray + } | { + readonly "id": string + readonly "result"?: string | null + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "prompt"?: string + readonly "content"?: ReadonlyArray + readonly "encrypted_content"?: string | null + readonly "summary": ReadonlyArray + } | { + readonly "code": string | null + readonly "container_id": string + readonly "id": string + readonly "outputs": + | ReadonlyArray< + { readonly "type": "image"; readonly "url": string } | { readonly "logs": string; readonly "type": "logs" } + > + | null + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "type": never + readonly "content"?: ReadonlyArray + readonly "encrypted_content"?: string | null + readonly "summary": ReadonlyArray + } | { + readonly "action"?: Schema.Json + readonly "call_id": string + readonly "id": string + readonly "pending_safety_checks": ReadonlyArray< + { readonly "code": string; readonly "id": string; readonly "message": string } + > + readonly "status": "completed" | "incomplete" | "in_progress" + readonly "type": never + readonly "content"?: ReadonlyArray + readonly "encrypted_content"?: string | null + readonly "summary": ReadonlyArray + } | { + readonly "datetime": string + readonly "id": string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "timezone": string + readonly "type": never + readonly "content"?: ReadonlyArray + readonly "encrypted_content"?: string | null + readonly "summary": ReadonlyArray + } | { + readonly "action"?: { + readonly "query": string + readonly "sources"?: ReadonlyArray<{ readonly "type": "url"; readonly "url": string }> + readonly "type": "search" + } + readonly "id": string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "type": never + readonly "content"?: ReadonlyArray + readonly "encrypted_content"?: string | null + readonly "summary": ReadonlyArray + } | { + readonly "code"?: string + readonly "exitCode"?: number + readonly "id": string + readonly "language"?: string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "stderr"?: string + readonly "stdout"?: string + readonly "type": never + readonly "content"?: ReadonlyArray + readonly "encrypted_content"?: string | null + readonly "summary": ReadonlyArray + } | { + readonly "id": string + readonly "queries"?: ReadonlyArray + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "type": never + readonly "content"?: ReadonlyArray + readonly "encrypted_content"?: string | null + readonly "summary": ReadonlyArray + } | { + readonly "id": string + readonly "imageB64"?: string + readonly "imageUrl"?: string + readonly "prompt"?: string + readonly "result"?: string | null + readonly "revisedPrompt"?: string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "type": never + readonly "content"?: ReadonlyArray + readonly "encrypted_content"?: string | null + readonly "summary": ReadonlyArray + } | { + readonly "action"?: string + readonly "id": string + readonly "screenshotB64"?: string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "type": never + readonly "content"?: ReadonlyArray + readonly "encrypted_content"?: string | null + readonly "summary": ReadonlyArray + } | { + readonly "arguments"?: string + readonly "call_id"?: string + readonly "command"?: string + readonly "exitCode"?: number + readonly "id": string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "stderr"?: string + readonly "stdout"?: string + readonly "type": never + readonly "content"?: ReadonlyArray + readonly "encrypted_content"?: string | null + readonly "summary": ReadonlyArray + } | { + readonly "command"?: "view" | "create" | "str_replace" | "insert" + readonly "filePath"?: string + readonly "id": string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "type": never + readonly "content"?: ReadonlyArray + readonly "encrypted_content"?: string | null + readonly "summary": ReadonlyArray + } | { + readonly "call_id"?: string + readonly "id": string + readonly "operation"?: ApplyPatchCallOperation + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "type": never + readonly "content"?: ReadonlyArray + readonly "encrypted_content"?: string | null + readonly "summary": ReadonlyArray + } | { + readonly "call_id": string + readonly "id": string + readonly "operation": ApplyPatchCallOperation + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "content"?: ReadonlyArray + readonly "encrypted_content"?: string | null + readonly "summary": ReadonlyArray + } | { + readonly "action"?: { + readonly "commands": ReadonlyArray + readonly "max_output_length": number | null + readonly "timeout_ms": number | null + } + readonly "call_id": string + readonly "id": string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "type": never + readonly "content"?: ReadonlyArray + readonly "encrypted_content"?: string | null + readonly "summary": ReadonlyArray + } | { + readonly "call_id": string + readonly "id": string + readonly "max_output_length"?: number | null + readonly "output": ReadonlyArray< + { + readonly "outcome": { readonly "exit_code": number; readonly "type": "exit" } | { readonly "type": "timeout" } + readonly "stderr": string + readonly "stdout": string + } + > + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "type": never + readonly "content"?: ReadonlyArray + readonly "encrypted_content"?: string | null + readonly "summary": ReadonlyArray + } | { + readonly "content"?: never + readonly "error"?: string + readonly "httpStatus"?: number + readonly "id": string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "title"?: string + readonly "type": never + readonly "url"?: string + readonly "encrypted_content"?: string | null + readonly "summary": ReadonlyArray + } | { + readonly "id": string + readonly "query"?: string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "type": never + readonly "content"?: ReadonlyArray + readonly "encrypted_content"?: string | null + readonly "summary": ReadonlyArray + } | { + readonly "action"?: "read" | "write" | "delete" + readonly "id": string + readonly "key"?: string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "type": never + readonly "value"?: Schema.Json + readonly "content"?: ReadonlyArray + readonly "encrypted_content"?: string | null + readonly "summary": ReadonlyArray + } | { + readonly "id": string + readonly "serverLabel"?: string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "toolName"?: string + readonly "type": never + readonly "content"?: ReadonlyArray + readonly "encrypted_content"?: string | null + readonly "summary": ReadonlyArray + } | { + readonly "arguments"?: string + readonly "id": string + readonly "query"?: string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "type": never + readonly "content"?: ReadonlyArray + readonly "encrypted_content"?: string | null + readonly "summary": ReadonlyArray + } | { + readonly "analysis"?: FusionAnalysisResult + readonly "error"?: string + readonly "failed_models"?: ReadonlyArray< + { readonly "error": string; readonly "model": string; readonly "status_code"?: number } + > + readonly "failure_reason"?: string + readonly "id": string + readonly "responses"?: ReadonlyArray<{ readonly "content"?: string; readonly "model": string }> + readonly "sources"?: ReadonlyArray + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "type": never + readonly "content"?: ReadonlyArray + readonly "encrypted_content"?: string | null + readonly "summary": ReadonlyArray + } | { + readonly "advice"?: string + readonly "error"?: string + readonly "id": string + readonly "instance_name"?: string + readonly "model"?: string + readonly "prompt"?: string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "type": never + readonly "content"?: ReadonlyArray + readonly "encrypted_content"?: string | null + readonly "summary": ReadonlyArray + } | { + readonly "error"?: string + readonly "id": string + readonly "instance_name"?: string + readonly "model"?: string + readonly "name"?: string + readonly "outcome"?: string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "task_description"?: string + readonly "task_name"?: string + readonly "type": never + readonly "content"?: ReadonlyArray + readonly "encrypted_content"?: string | null + readonly "summary": ReadonlyArray + } | { + readonly "error"?: string + readonly "file_id"?: string + readonly "filename"?: string + readonly "id": string + readonly "operation"?: string + readonly "result"?: string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "type": never + readonly "content"?: ReadonlyArray + readonly "encrypted_content"?: string | null + readonly "summary": ReadonlyArray + } | { + readonly "call_id": string + readonly "id": string + readonly "input": string + readonly "name": string + readonly "namespace"?: string + readonly "type": never + readonly "content"?: ReadonlyArray + readonly "encrypted_content"?: string | null + readonly "status"?: "completed" | "incomplete" | "in_progress" + readonly "summary": ReadonlyArray + } | { + readonly "content": ReadonlyArray + readonly "id": string + readonly "phase"?: "commentary" | "final_answer" | null + readonly "role": "assistant" + readonly "status"?: "completed" | "incomplete" | "in_progress" + readonly "type": never + readonly "arguments": string + readonly "call_id": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "content"?: ReadonlyArray<{ readonly "text": string; readonly "type": "reasoning_text" }> + readonly "encrypted_content"?: string | null + readonly "id": string + readonly "status"?: "completed" | "incomplete" | "in_progress" + readonly "summary": ReadonlyArray + readonly "type": never + readonly "format"?: ReasoningFormat + readonly "signature"?: string | null + readonly "arguments": string + readonly "call_id": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "arguments": string + readonly "call_id": string + readonly "id"?: string + readonly "name": string + readonly "namespace"?: string + readonly "status"?: "completed" | "incomplete" | "in_progress" + readonly "type": "function_call" + } | { + readonly "action"?: + | { + readonly "queries"?: ReadonlyArray + readonly "query": string + readonly "sources"?: ReadonlyArray + readonly "type": "search" + } + | { readonly "type": "open_page"; readonly "url"?: string | null } + | { readonly "pattern": string; readonly "type": "find_in_page"; readonly "url": string } + readonly "id": string + readonly "status": "completed" | "in_progress" + readonly "type": never + readonly "arguments": string + readonly "call_id": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "id": string + readonly "queries": ReadonlyArray + readonly "status": "completed" | "in_progress" + readonly "type": never + readonly "arguments": string + readonly "call_id": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "id": string + readonly "result"?: string | null + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "prompt"?: string + readonly "arguments": string + readonly "call_id": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "code": string | null + readonly "container_id": string + readonly "id": string + readonly "outputs": + | ReadonlyArray< + { readonly "type": "image"; readonly "url": string } | { readonly "logs": string; readonly "type": "logs" } + > + | null + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "type": never + readonly "arguments": string + readonly "call_id": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "action"?: Schema.Json + readonly "call_id": string + readonly "id"?: string + readonly "pending_safety_checks": ReadonlyArray< + { readonly "code": string; readonly "id": string; readonly "message": string } + > + readonly "status": "completed" | "incomplete" | "in_progress" + readonly "type": never + readonly "arguments": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "datetime": string + readonly "id"?: string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "timezone": string + readonly "type": never + readonly "arguments": string + readonly "call_id": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "action"?: { + readonly "query": string + readonly "sources"?: ReadonlyArray<{ readonly "type": "url"; readonly "url": string }> + readonly "type": "search" + } + readonly "id"?: string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "type": never + readonly "arguments": string + readonly "call_id": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "code"?: string + readonly "exitCode"?: number + readonly "id"?: string + readonly "language"?: string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "stderr"?: string + readonly "stdout"?: string + readonly "type": never + readonly "arguments": string + readonly "call_id": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "id"?: string + readonly "queries"?: ReadonlyArray + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "type": never + readonly "arguments": string + readonly "call_id": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "id"?: string + readonly "imageB64"?: string + readonly "imageUrl"?: string + readonly "prompt"?: string + readonly "result"?: string | null + readonly "revisedPrompt"?: string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "type": never + readonly "arguments": string + readonly "call_id": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "action"?: string + readonly "id"?: string + readonly "screenshotB64"?: string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "type": never + readonly "arguments": string + readonly "call_id": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "arguments": string + readonly "call_id": string + readonly "command"?: string + readonly "exitCode"?: number + readonly "id"?: string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "stderr"?: string + readonly "stdout"?: string + readonly "type": never + readonly "name": string + readonly "namespace"?: string + } | { + readonly "command"?: "view" | "create" | "str_replace" | "insert" + readonly "filePath"?: string + readonly "id"?: string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "type": never + readonly "arguments": string + readonly "call_id": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "call_id": string + readonly "id"?: string + readonly "operation"?: ApplyPatchCallOperation + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "type": never + readonly "arguments": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "call_id": string + readonly "id": string + readonly "operation": ApplyPatchCallOperation + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "arguments": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "action"?: { + readonly "commands": ReadonlyArray + readonly "max_output_length": number | null + readonly "timeout_ms": number | null + } + readonly "call_id": string + readonly "id": string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "type": never + readonly "arguments": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "call_id": string + readonly "id": string + readonly "max_output_length"?: number | null + readonly "output": ReadonlyArray< + { + readonly "outcome": { readonly "exit_code": number; readonly "type": "exit" } | { readonly "type": "timeout" } + readonly "stderr": string + readonly "stdout": string + } + > + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "type": never + readonly "arguments": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "content"?: string + readonly "error"?: string + readonly "httpStatus"?: number + readonly "id"?: string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "title"?: string + readonly "type": never + readonly "url"?: string + readonly "arguments": string + readonly "call_id": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "id"?: string + readonly "query"?: string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "type": never + readonly "arguments": string + readonly "call_id": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "action"?: "read" | "write" | "delete" + readonly "id"?: string + readonly "key"?: string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "type": never + readonly "value"?: Schema.Json + readonly "arguments": string + readonly "call_id": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "id"?: string + readonly "serverLabel"?: string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "toolName"?: string + readonly "type": never + readonly "arguments": string + readonly "call_id": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "arguments": string + readonly "id"?: string + readonly "query"?: string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "type": never + readonly "call_id": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "analysis"?: FusionAnalysisResult + readonly "error"?: string + readonly "failed_models"?: ReadonlyArray< + { readonly "error": string; readonly "model": string; readonly "status_code"?: number } + > + readonly "failure_reason"?: string + readonly "id"?: string + readonly "responses"?: ReadonlyArray<{ readonly "content"?: string; readonly "model": string }> + readonly "sources"?: ReadonlyArray + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "type": never + readonly "arguments": string + readonly "call_id": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "advice"?: string + readonly "error"?: string + readonly "id"?: string + readonly "instance_name"?: string + readonly "model"?: string + readonly "prompt"?: string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "type": never + readonly "arguments": string + readonly "call_id": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "error"?: string + readonly "id"?: string + readonly "instance_name"?: string + readonly "model"?: string + readonly "name": string + readonly "outcome"?: string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "task_description"?: string + readonly "task_name"?: string + readonly "type": never + readonly "arguments": string + readonly "call_id": string + readonly "namespace"?: string + } | { + readonly "error"?: string + readonly "file_id"?: string + readonly "filename"?: string + readonly "id"?: string + readonly "operation"?: string + readonly "result"?: string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "type": never + readonly "arguments": string + readonly "call_id": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "call_id": string + readonly "id"?: string + readonly "input": string + readonly "name": string + readonly "namespace"?: string + readonly "type": never + readonly "arguments": string + readonly "status"?: "completed" | "incomplete" | "in_progress" + } | { + readonly "content": ReadonlyArray + readonly "id": string + readonly "phase"?: "commentary" | "final_answer" | null + readonly "role": "assistant" + readonly "status"?: "completed" | "incomplete" | "in_progress" + readonly "type": never + readonly "call_id": string + readonly "input": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "content"?: ReadonlyArray<{ readonly "text": string; readonly "type": "reasoning_text" }> + readonly "encrypted_content"?: string | null + readonly "id": string + readonly "status"?: "completed" | "incomplete" | "in_progress" + readonly "summary": ReadonlyArray + readonly "type": never + readonly "format"?: ReasoningFormat + readonly "signature"?: string | null + readonly "call_id": string + readonly "input": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "arguments": string + readonly "call_id": string + readonly "id"?: string + readonly "name": string + readonly "namespace"?: string + readonly "status"?: "completed" | "incomplete" | "in_progress" + readonly "type": never + readonly "input": string + } | { + readonly "action"?: + | { + readonly "queries"?: ReadonlyArray + readonly "query": string + readonly "sources"?: ReadonlyArray + readonly "type": "search" + } + | { readonly "type": "open_page"; readonly "url"?: string | null } + | { readonly "pattern": string; readonly "type": "find_in_page"; readonly "url": string } + readonly "id": string + readonly "status": WebSearchStatus + readonly "type": never + readonly "call_id": string + readonly "input": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "id": string + readonly "queries": ReadonlyArray + readonly "status": WebSearchStatus + readonly "type": never + readonly "call_id": string + readonly "input": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "id": string + readonly "result"?: string | null + readonly "status": ImageGenerationStatus + readonly "type": never + readonly "prompt"?: string + readonly "call_id": string + readonly "input": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "code": string | null + readonly "container_id": string + readonly "id": string + readonly "outputs": + | ReadonlyArray< + { readonly "type": "image"; readonly "url": string } | { readonly "logs": string; readonly "type": "logs" } + > + | null + readonly "status": ToolCallStatus + readonly "type": never + readonly "call_id": string + readonly "input": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "action"?: Schema.Json + readonly "call_id": string + readonly "id"?: string + readonly "pending_safety_checks": ReadonlyArray< + { readonly "code": string; readonly "id": string; readonly "message": string } + > + readonly "status": "completed" | "incomplete" | "in_progress" + readonly "type": never + readonly "input": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "datetime": string + readonly "id"?: string + readonly "status": ToolCallStatus + readonly "timezone": string + readonly "type": never + readonly "call_id": string + readonly "input": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "action"?: { + readonly "query": string + readonly "sources"?: ReadonlyArray<{ readonly "type": "url"; readonly "url": string }> + readonly "type": "search" + } + readonly "id"?: string + readonly "status": ToolCallStatus + readonly "type": never + readonly "call_id": string + readonly "input": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "code"?: string + readonly "exitCode"?: number + readonly "id"?: string + readonly "language"?: string + readonly "status": ToolCallStatus + readonly "stderr"?: string + readonly "stdout"?: string + readonly "type": never + readonly "call_id": string + readonly "input": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "id"?: string + readonly "queries"?: ReadonlyArray + readonly "status": ToolCallStatus + readonly "type": never + readonly "call_id": string + readonly "input": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "id"?: string + readonly "imageB64"?: string + readonly "imageUrl"?: string + readonly "prompt"?: string + readonly "result"?: string | null + readonly "revisedPrompt"?: string + readonly "status": ToolCallStatus + readonly "type": never + readonly "call_id": string + readonly "input": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "action"?: string + readonly "id"?: string + readonly "screenshotB64"?: string + readonly "status": ToolCallStatus + readonly "type": never + readonly "call_id": string + readonly "input": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "arguments"?: string + readonly "call_id": string + readonly "command"?: string + readonly "exitCode"?: number + readonly "id"?: string + readonly "status": ToolCallStatus + readonly "stderr"?: string + readonly "stdout"?: string + readonly "type": never + readonly "input": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "command"?: "view" | "create" | "str_replace" | "insert" + readonly "filePath"?: string + readonly "id"?: string + readonly "status": ToolCallStatus + readonly "type": never + readonly "call_id": string + readonly "input": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "call_id": string + readonly "id"?: string + readonly "operation"?: ApplyPatchCallOperation + readonly "status": ToolCallStatus + readonly "type": never + readonly "input": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "call_id": string + readonly "id": string + readonly "operation": ApplyPatchCallOperation + readonly "status": ApplyPatchCallStatus + readonly "type": never + readonly "input": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "action"?: { + readonly "commands": ReadonlyArray + readonly "max_output_length": number | null + readonly "timeout_ms": number | null + } + readonly "call_id": string + readonly "id": string + readonly "status": ShellCallStatus + readonly "type": never + readonly "input": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "call_id": string + readonly "id": string + readonly "max_output_length"?: number | null + readonly "output": ReadonlyArray< + { + readonly "outcome": { readonly "exit_code": number; readonly "type": "exit" } | { readonly "type": "timeout" } + readonly "stderr": string + readonly "stdout": string + } + > + readonly "status": ShellCallStatus + readonly "type": never + readonly "input": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "content"?: string + readonly "error"?: string + readonly "httpStatus"?: number + readonly "id"?: string + readonly "status": ToolCallStatus + readonly "title"?: string + readonly "type": never + readonly "url"?: string + readonly "call_id": string + readonly "input": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "id"?: string + readonly "query"?: string + readonly "status": ToolCallStatus + readonly "type": never + readonly "call_id": string + readonly "input": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "action"?: "read" | "write" | "delete" + readonly "id"?: string + readonly "key"?: string + readonly "status": ToolCallStatus + readonly "type": never + readonly "value"?: Schema.Json + readonly "call_id": string + readonly "input": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "id"?: string + readonly "serverLabel"?: string + readonly "status": ToolCallStatus + readonly "toolName"?: string + readonly "type": never + readonly "call_id": string + readonly "input": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "arguments"?: string + readonly "id"?: string + readonly "query"?: string + readonly "status": ToolCallStatus + readonly "type": never + readonly "call_id": string + readonly "input": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "analysis"?: FusionAnalysisResult + readonly "error"?: string + readonly "failed_models"?: ReadonlyArray< + { readonly "error": string; readonly "model": string; readonly "status_code"?: number } + > + readonly "failure_reason"?: string + readonly "id"?: string + readonly "responses"?: ReadonlyArray<{ readonly "content"?: string; readonly "model": string }> + readonly "sources"?: ReadonlyArray + readonly "status": ToolCallStatus + readonly "type": never + readonly "call_id": string + readonly "input": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "advice"?: string + readonly "error"?: string + readonly "id"?: string + readonly "instance_name"?: string + readonly "model"?: string + readonly "prompt"?: string + readonly "status": ToolCallStatus + readonly "type": never + readonly "call_id": string + readonly "input": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "error"?: string + readonly "id"?: string + readonly "instance_name"?: string + readonly "model"?: string + readonly "name": string + readonly "outcome"?: string + readonly "status": ToolCallStatus + readonly "task_description"?: string + readonly "task_name"?: string + readonly "type": never + readonly "call_id": string + readonly "input": string + readonly "namespace"?: string + } | { + readonly "error"?: string + readonly "file_id"?: string + readonly "filename"?: string + readonly "id"?: string + readonly "operation"?: string + readonly "result"?: string + readonly "status": ToolCallStatus + readonly "type": never + readonly "call_id": string + readonly "input": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "call_id": string + readonly "id"?: string + readonly "input": string + readonly "name": string + readonly "namespace"?: string + readonly "type": "custom_tool_call" + } | { + readonly "content": ReadonlyArray + readonly "id": string + readonly "phase"?: "commentary" | "final_answer" | null + readonly "role": "assistant" + readonly "status": "completed" | "in_progress" + readonly "type": never + readonly "action"?: + | { + readonly "queries"?: ReadonlyArray + readonly "query": string + readonly "sources"?: ReadonlyArray + readonly "type": "search" + } + | { readonly "type": "open_page"; readonly "url"?: string | null } + | { readonly "pattern": string; readonly "type": "find_in_page"; readonly "url": string } + } | { + readonly "content"?: ReadonlyArray<{ readonly "text": string; readonly "type": "reasoning_text" }> + readonly "encrypted_content"?: string | null + readonly "id": string + readonly "status": "completed" | "in_progress" + readonly "summary": ReadonlyArray + readonly "type": never + readonly "format"?: ReasoningFormat + readonly "signature"?: string | null + readonly "action"?: + | { + readonly "queries"?: ReadonlyArray + readonly "query": string + readonly "sources"?: ReadonlyArray + readonly "type": "search" + } + | { readonly "type": "open_page"; readonly "url"?: string | null } + | { readonly "pattern": string; readonly "type": "find_in_page"; readonly "url": string } + } | { + readonly "arguments": string + readonly "call_id": string + readonly "id": string + readonly "name": string + readonly "namespace"?: string + readonly "status": "completed" | "in_progress" + readonly "type": never + readonly "action"?: + | { + readonly "queries"?: ReadonlyArray + readonly "query": string + readonly "sources"?: ReadonlyArray + readonly "type": "search" + } + | { readonly "type": "open_page"; readonly "url"?: string | null } + | { readonly "pattern": string; readonly "type": "find_in_page"; readonly "url": string } + } | { + readonly "action"?: + | { + readonly "queries"?: ReadonlyArray + readonly "query": string + readonly "sources"?: ReadonlyArray<{ readonly "type": "url"; readonly "url": string }> + readonly "type": "search" + } + | { + readonly "type": never + readonly "url"?: string | null + readonly "queries"?: ReadonlyArray + readonly "query": string + readonly "sources"?: ReadonlyArray + } + | { + readonly "pattern": string + readonly "type": never + readonly "url": string + readonly "queries"?: ReadonlyArray + readonly "query": string + readonly "sources"?: ReadonlyArray + } + | { + readonly "queries"?: ReadonlyArray + readonly "query": string + readonly "sources"?: ReadonlyArray + readonly "type": never + readonly "url"?: string | null + } + | { readonly "type": "open_page"; readonly "url"?: string | null } + | { readonly "pattern": string; readonly "type": never; readonly "url": string } + | { + readonly "queries"?: ReadonlyArray + readonly "query": string + readonly "sources"?: ReadonlyArray + readonly "type": never + readonly "pattern": string + readonly "url": string + } + | { readonly "type": never; readonly "url": string; readonly "pattern": string } + | { readonly "pattern": string; readonly "type": "find_in_page"; readonly "url": string } + readonly "id": string + readonly "status": "completed" | "searching" | "in_progress" | "failed" + readonly "type": "web_search_call" + } | { + readonly "id": string + readonly "queries": ReadonlyArray + readonly "status": "completed" | "searching" | "in_progress" | "failed" + readonly "type": never + readonly "action"?: + | { + readonly "queries"?: ReadonlyArray + readonly "query": string + readonly "sources"?: ReadonlyArray + readonly "type": "search" + } + | { readonly "type": "open_page"; readonly "url"?: string | null } + | { readonly "pattern": string; readonly "type": "find_in_page"; readonly "url": string } + } | { + readonly "id": string + readonly "result"?: string | null + readonly "status": "in_progress" | "completed" | "failed" + readonly "type": never + readonly "prompt"?: string + readonly "action"?: + | { + readonly "queries"?: ReadonlyArray + readonly "query": string + readonly "sources"?: ReadonlyArray + readonly "type": "search" + } + | { readonly "type": "open_page"; readonly "url"?: string | null } + | { readonly "pattern": string; readonly "type": "find_in_page"; readonly "url": string } + } | { + readonly "code": string | null + readonly "container_id": string + readonly "id": string + readonly "outputs": + | ReadonlyArray< + { readonly "type": "image"; readonly "url": string } | { readonly "logs": string; readonly "type": "logs" } + > + | null + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "action"?: + | { + readonly "queries"?: ReadonlyArray + readonly "query": string + readonly "sources"?: ReadonlyArray + readonly "type": "search" + } + | { readonly "type": "open_page"; readonly "url"?: string | null } + | { readonly "pattern": string; readonly "type": "find_in_page"; readonly "url": string } + } | { + readonly "action"?: + | { + readonly "queries"?: ReadonlyArray + readonly "query": string + readonly "sources"?: ReadonlyArray + readonly "type": "search" + } + | { readonly "type": "open_page"; readonly "url"?: string | null } + | { readonly "pattern": string; readonly "type": "find_in_page"; readonly "url": string } + readonly "call_id": string + readonly "id": string + readonly "pending_safety_checks": ReadonlyArray< + { readonly "code": string; readonly "id": string; readonly "message": string } + > + readonly "status": "completed" | "in_progress" + readonly "type": never + } | { + readonly "datetime": string + readonly "id": string + readonly "status": "in_progress" | "completed" + readonly "timezone": string + readonly "type": never + readonly "action"?: + | { + readonly "queries"?: ReadonlyArray + readonly "query": string + readonly "sources"?: ReadonlyArray + readonly "type": "search" + } + | { readonly "type": "open_page"; readonly "url"?: string | null } + | { readonly "pattern": string; readonly "type": "find_in_page"; readonly "url": string } + } | { + readonly "action"?: { + readonly "queries"?: ReadonlyArray + readonly "query": string + readonly "sources"?: ReadonlyArray<{ readonly "type": "url"; readonly "url": string }> + readonly "type": "search" + } | { + readonly "type": never + readonly "url"?: string | null + readonly "query": string + readonly "sources"?: ReadonlyArray<{ readonly "type": "url"; readonly "url": string }> + } | { + readonly "pattern": string + readonly "type": never + readonly "url": string + readonly "query": string + readonly "sources"?: ReadonlyArray<{ readonly "type": "url"; readonly "url": string }> + } + readonly "id": string + readonly "status": "in_progress" | "completed" + readonly "type": never + } | { + readonly "code"?: string + readonly "exitCode"?: number + readonly "id": string + readonly "language"?: string + readonly "status": "in_progress" | "completed" + readonly "stderr"?: string + readonly "stdout"?: string + readonly "type": never + readonly "action"?: + | { + readonly "queries"?: ReadonlyArray + readonly "query": string + readonly "sources"?: ReadonlyArray + readonly "type": "search" + } + | { readonly "type": "open_page"; readonly "url"?: string | null } + | { readonly "pattern": string; readonly "type": "find_in_page"; readonly "url": string } + } | { + readonly "id": string + readonly "queries"?: ReadonlyArray + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "action"?: + | { + readonly "queries"?: ReadonlyArray + readonly "query": string + readonly "sources"?: ReadonlyArray + readonly "type": "search" + } + | { readonly "type": "open_page"; readonly "url"?: string | null } + | { readonly "pattern": string; readonly "type": "find_in_page"; readonly "url": string } + } | { + readonly "id": string + readonly "imageB64"?: string + readonly "imageUrl"?: string + readonly "prompt"?: string + readonly "result"?: string | null + readonly "revisedPrompt"?: string + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "action"?: + | { + readonly "queries"?: ReadonlyArray + readonly "query": string + readonly "sources"?: ReadonlyArray + readonly "type": "search" + } + | { readonly "type": "open_page"; readonly "url"?: string | null } + | { readonly "pattern": string; readonly "type": "find_in_page"; readonly "url": string } + } | { + readonly "action"?: never + readonly "id": string + readonly "screenshotB64"?: string + readonly "status": "in_progress" | "completed" + readonly "type": never + } | { + readonly "arguments"?: string + readonly "call_id"?: string + readonly "command"?: string + readonly "exitCode"?: number + readonly "id": string + readonly "status": "in_progress" | "completed" + readonly "stderr"?: string + readonly "stdout"?: string + readonly "type": never + readonly "action"?: + | { + readonly "queries"?: ReadonlyArray + readonly "query": string + readonly "sources"?: ReadonlyArray + readonly "type": "search" + } + | { readonly "type": "open_page"; readonly "url"?: string | null } + | { readonly "pattern": string; readonly "type": "find_in_page"; readonly "url": string } + } | { + readonly "command"?: "view" | "create" | "str_replace" | "insert" + readonly "filePath"?: string + readonly "id": string + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "action"?: + | { + readonly "queries"?: ReadonlyArray + readonly "query": string + readonly "sources"?: ReadonlyArray + readonly "type": "search" + } + | { readonly "type": "open_page"; readonly "url"?: string | null } + | { readonly "pattern": string; readonly "type": "find_in_page"; readonly "url": string } + } | { + readonly "call_id"?: string + readonly "id": string + readonly "operation"?: ApplyPatchCallOperation + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "action"?: + | { + readonly "queries"?: ReadonlyArray + readonly "query": string + readonly "sources"?: ReadonlyArray + readonly "type": "search" + } + | { readonly "type": "open_page"; readonly "url"?: string | null } + | { readonly "pattern": string; readonly "type": "find_in_page"; readonly "url": string } + } | { + readonly "call_id": string + readonly "id": string + readonly "operation": ApplyPatchCallOperation + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "action"?: + | { + readonly "queries"?: ReadonlyArray + readonly "query": string + readonly "sources"?: ReadonlyArray + readonly "type": "search" + } + | { readonly "type": "open_page"; readonly "url"?: string | null } + | { readonly "pattern": string; readonly "type": "find_in_page"; readonly "url": string } + } | { + readonly "action"?: { + readonly "queries"?: ReadonlyArray + readonly "query": string + readonly "sources"?: ReadonlyArray + readonly "type": "search" + readonly "commands": ReadonlyArray + readonly "max_output_length": number | null + readonly "timeout_ms": number | null + } | { + readonly "type": "open_page" + readonly "url"?: string | null + readonly "commands": ReadonlyArray + readonly "max_output_length": number | null + readonly "timeout_ms": number | null + } | { + readonly "pattern": string + readonly "type": "find_in_page" + readonly "url": string + readonly "commands": ReadonlyArray + readonly "max_output_length": number | null + readonly "timeout_ms": number | null + } + readonly "call_id": string + readonly "id": string + readonly "status": "in_progress" | "completed" + readonly "type": never + } | { + readonly "call_id": string + readonly "id": string + readonly "max_output_length"?: number | null + readonly "output": ReadonlyArray< + { + readonly "outcome": { readonly "exit_code": number; readonly "type": "exit" } | { readonly "type": "timeout" } + readonly "stderr": string + readonly "stdout": string + } + > + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "action"?: + | { + readonly "queries"?: ReadonlyArray + readonly "query": string + readonly "sources"?: ReadonlyArray + readonly "type": "search" + } + | { readonly "type": "open_page"; readonly "url"?: string | null } + | { readonly "pattern": string; readonly "type": "find_in_page"; readonly "url": string } + } | { + readonly "content"?: string + readonly "error"?: string + readonly "httpStatus"?: number + readonly "id": string + readonly "status": "in_progress" | "completed" + readonly "title"?: string + readonly "type": never + readonly "url"?: string + readonly "action"?: + | { + readonly "queries"?: ReadonlyArray + readonly "query": string + readonly "sources"?: ReadonlyArray + readonly "type": "search" + } + | { readonly "type": "open_page"; readonly "url"?: string | null } + | { readonly "pattern": string; readonly "type": "find_in_page"; readonly "url": string } + } | { + readonly "id": string + readonly "query"?: string + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "action"?: + | { + readonly "queries"?: ReadonlyArray + readonly "query": string + readonly "sources"?: ReadonlyArray + readonly "type": "search" + } + | { readonly "type": "open_page"; readonly "url"?: string | null } + | { readonly "pattern": string; readonly "type": "find_in_page"; readonly "url": string } + } | { + readonly "action"?: never + readonly "id": string + readonly "key"?: string + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "value"?: Schema.Json + } | { + readonly "id": string + readonly "serverLabel"?: string + readonly "status": "in_progress" | "completed" + readonly "toolName"?: string + readonly "type": never + readonly "action"?: + | { + readonly "queries"?: ReadonlyArray + readonly "query": string + readonly "sources"?: ReadonlyArray + readonly "type": "search" + } + | { readonly "type": "open_page"; readonly "url"?: string | null } + | { readonly "pattern": string; readonly "type": "find_in_page"; readonly "url": string } + } | { + readonly "arguments"?: string + readonly "id": string + readonly "query"?: string + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "action"?: + | { + readonly "queries"?: ReadonlyArray + readonly "query": string + readonly "sources"?: ReadonlyArray + readonly "type": "search" + } + | { readonly "type": "open_page"; readonly "url"?: string | null } + | { readonly "pattern": string; readonly "type": "find_in_page"; readonly "url": string } + } | { + readonly "analysis"?: FusionAnalysisResult + readonly "error"?: string + readonly "failed_models"?: ReadonlyArray< + { readonly "error": string; readonly "model": string; readonly "status_code"?: number } + > + readonly "failure_reason"?: string + readonly "id": string + readonly "responses"?: ReadonlyArray<{ readonly "content"?: string; readonly "model": string }> + readonly "sources"?: ReadonlyArray + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "action"?: + | { + readonly "queries"?: ReadonlyArray + readonly "query": string + readonly "sources"?: ReadonlyArray + readonly "type": "search" + } + | { readonly "type": "open_page"; readonly "url"?: string | null } + | { readonly "pattern": string; readonly "type": "find_in_page"; readonly "url": string } + } | { + readonly "advice"?: string + readonly "error"?: string + readonly "id": string + readonly "instance_name"?: string + readonly "model"?: string + readonly "prompt"?: string + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "action"?: + | { + readonly "queries"?: ReadonlyArray + readonly "query": string + readonly "sources"?: ReadonlyArray + readonly "type": "search" + } + | { readonly "type": "open_page"; readonly "url"?: string | null } + | { readonly "pattern": string; readonly "type": "find_in_page"; readonly "url": string } + } | { + readonly "error"?: string + readonly "id": string + readonly "instance_name"?: string + readonly "model"?: string + readonly "name"?: string + readonly "outcome"?: string + readonly "status": "in_progress" | "completed" + readonly "task_description"?: string + readonly "task_name"?: string + readonly "type": never + readonly "action"?: + | { + readonly "queries"?: ReadonlyArray + readonly "query": string + readonly "sources"?: ReadonlyArray + readonly "type": "search" + } + | { readonly "type": "open_page"; readonly "url"?: string | null } + | { readonly "pattern": string; readonly "type": "find_in_page"; readonly "url": string } + } | { + readonly "error"?: string + readonly "file_id"?: string + readonly "filename"?: string + readonly "id": string + readonly "operation"?: string + readonly "result"?: string + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "action"?: + | { + readonly "queries"?: ReadonlyArray + readonly "query": string + readonly "sources"?: ReadonlyArray + readonly "type": "search" + } + | { readonly "type": "open_page"; readonly "url"?: string | null } + | { readonly "pattern": string; readonly "type": "find_in_page"; readonly "url": string } + } | { + readonly "call_id": string + readonly "id": string + readonly "input": string + readonly "name": string + readonly "namespace"?: string + readonly "type": never + readonly "action"?: + | { + readonly "queries"?: ReadonlyArray + readonly "query": string + readonly "sources"?: ReadonlyArray + readonly "type": "search" + } + | { readonly "type": "open_page"; readonly "url"?: string | null } + | { readonly "pattern": string; readonly "type": "find_in_page"; readonly "url": string } + readonly "status": WebSearchStatus + } | { + readonly "content": ReadonlyArray + readonly "id": string + readonly "phase"?: "commentary" | "final_answer" | null + readonly "role": "assistant" + readonly "status": "completed" | "in_progress" + readonly "type": never + readonly "queries": ReadonlyArray + } | { + readonly "content"?: ReadonlyArray<{ readonly "text": string; readonly "type": "reasoning_text" }> + readonly "encrypted_content"?: string | null + readonly "id": string + readonly "status": "completed" | "in_progress" + readonly "summary": ReadonlyArray + readonly "type": never + readonly "format"?: ReasoningFormat + readonly "signature"?: string | null + readonly "queries": ReadonlyArray + } | { + readonly "arguments": string + readonly "call_id": string + readonly "id": string + readonly "name": string + readonly "namespace"?: string + readonly "status": "completed" | "in_progress" + readonly "type": never + readonly "queries": ReadonlyArray + } | { + readonly "action"?: + | { + readonly "queries"?: ReadonlyArray + readonly "query": string + readonly "sources"?: ReadonlyArray + readonly "type": "search" + } + | { readonly "type": "open_page"; readonly "url"?: string | null } + | { readonly "pattern": string; readonly "type": "find_in_page"; readonly "url": string } + readonly "id": string + readonly "status": "completed" | "searching" | "in_progress" | "failed" + readonly "type": never + readonly "queries": ReadonlyArray + } | { + readonly "id": string + readonly "queries": ReadonlyArray + readonly "status": "completed" | "searching" | "in_progress" | "failed" + readonly "type": "file_search_call" + } | { + readonly "id": string + readonly "result"?: string | null + readonly "status": "in_progress" | "completed" | "failed" + readonly "type": never + readonly "prompt"?: string + readonly "queries": ReadonlyArray + } | { + readonly "code": string | null + readonly "container_id": string + readonly "id": string + readonly "outputs": + | ReadonlyArray< + { readonly "type": "image"; readonly "url": string } | { readonly "logs": string; readonly "type": "logs" } + > + | null + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "queries": ReadonlyArray + } | { + readonly "action"?: Schema.Json + readonly "call_id": string + readonly "id": string + readonly "pending_safety_checks": ReadonlyArray< + { readonly "code": string; readonly "id": string; readonly "message": string } + > + readonly "status": "completed" | "in_progress" + readonly "type": never + readonly "queries": ReadonlyArray + } | { + readonly "datetime": string + readonly "id": string + readonly "status": "in_progress" | "completed" + readonly "timezone": string + readonly "type": never + readonly "queries": ReadonlyArray + } | { + readonly "action"?: { + readonly "query": string + readonly "sources"?: ReadonlyArray<{ readonly "type": "url"; readonly "url": string }> + readonly "type": "search" + } + readonly "id": string + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "queries": ReadonlyArray + } | { + readonly "code"?: string + readonly "exitCode"?: number + readonly "id": string + readonly "language"?: string + readonly "status": "in_progress" | "completed" + readonly "stderr"?: string + readonly "stdout"?: string + readonly "type": never + readonly "queries": ReadonlyArray + } | { + readonly "id": string + readonly "queries": ReadonlyArray + readonly "status": "in_progress" | "completed" + readonly "type": never + } | { + readonly "id": string + readonly "imageB64"?: string + readonly "imageUrl"?: string + readonly "prompt"?: string + readonly "result"?: string | null + readonly "revisedPrompt"?: string + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "queries": ReadonlyArray + } | { + readonly "action"?: string + readonly "id": string + readonly "screenshotB64"?: string + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "queries": ReadonlyArray + } | { + readonly "arguments"?: string + readonly "call_id"?: string + readonly "command"?: string + readonly "exitCode"?: number + readonly "id": string + readonly "status": "in_progress" | "completed" + readonly "stderr"?: string + readonly "stdout"?: string + readonly "type": never + readonly "queries": ReadonlyArray + } | { + readonly "command"?: "view" | "create" | "str_replace" | "insert" + readonly "filePath"?: string + readonly "id": string + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "queries": ReadonlyArray + } | { + readonly "call_id"?: string + readonly "id": string + readonly "operation"?: ApplyPatchCallOperation + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "queries": ReadonlyArray + } | { + readonly "call_id": string + readonly "id": string + readonly "operation": ApplyPatchCallOperation + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "queries": ReadonlyArray + } | { + readonly "action"?: { + readonly "commands": ReadonlyArray + readonly "max_output_length": number | null + readonly "timeout_ms": number | null + } + readonly "call_id": string + readonly "id": string + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "queries": ReadonlyArray + } | { + readonly "call_id": string + readonly "id": string + readonly "max_output_length"?: number | null + readonly "output": ReadonlyArray< + { + readonly "outcome": { readonly "exit_code": number; readonly "type": "exit" } | { readonly "type": "timeout" } + readonly "stderr": string + readonly "stdout": string + } + > + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "queries": ReadonlyArray + } | { + readonly "content"?: string + readonly "error"?: string + readonly "httpStatus"?: number + readonly "id": string + readonly "status": "in_progress" | "completed" + readonly "title"?: string + readonly "type": never + readonly "url"?: string + readonly "queries": ReadonlyArray + } | { + readonly "id": string + readonly "query"?: string + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "queries": ReadonlyArray + } | { + readonly "action"?: "read" | "write" | "delete" + readonly "id": string + readonly "key"?: string + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "value"?: Schema.Json + readonly "queries": ReadonlyArray + } | { + readonly "id": string + readonly "serverLabel"?: string + readonly "status": "in_progress" | "completed" + readonly "toolName"?: string + readonly "type": never + readonly "queries": ReadonlyArray + } | { + readonly "arguments"?: string + readonly "id": string + readonly "query"?: string + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "queries": ReadonlyArray + } | { + readonly "analysis"?: FusionAnalysisResult + readonly "error"?: string + readonly "failed_models"?: ReadonlyArray< + { readonly "error": string; readonly "model": string; readonly "status_code"?: number } + > + readonly "failure_reason"?: string + readonly "id": string + readonly "responses"?: ReadonlyArray<{ readonly "content"?: string; readonly "model": string }> + readonly "sources"?: ReadonlyArray + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "queries": ReadonlyArray + } | { + readonly "advice"?: string + readonly "error"?: string + readonly "id": string + readonly "instance_name"?: string + readonly "model"?: string + readonly "prompt"?: string + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "queries": ReadonlyArray + } | { + readonly "error"?: string + readonly "id": string + readonly "instance_name"?: string + readonly "model"?: string + readonly "name"?: string + readonly "outcome"?: string + readonly "status": "in_progress" | "completed" + readonly "task_description"?: string + readonly "task_name"?: string + readonly "type": never + readonly "queries": ReadonlyArray + } | { + readonly "error"?: string + readonly "file_id"?: string + readonly "filename"?: string + readonly "id": string + readonly "operation"?: string + readonly "result"?: string + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "queries": ReadonlyArray + } | { + readonly "call_id": string + readonly "id": string + readonly "input": string + readonly "name": string + readonly "namespace"?: string + readonly "type": never + readonly "queries": ReadonlyArray + readonly "status": WebSearchStatus + } | { + readonly "content": ReadonlyArray + readonly "id": string + readonly "phase"?: "commentary" | "final_answer" | null + readonly "role": "assistant" + readonly "status": "completed" | "in_progress" + readonly "type": never + readonly "result"?: string | null + } | { + readonly "content"?: ReadonlyArray<{ readonly "text": string; readonly "type": "reasoning_text" }> + readonly "encrypted_content"?: string | null + readonly "id": string + readonly "status": "completed" | "in_progress" + readonly "summary": ReadonlyArray + readonly "type": never + readonly "format"?: ReasoningFormat + readonly "signature"?: string | null + readonly "result"?: string | null + } | { + readonly "arguments": string + readonly "call_id": string + readonly "id": string + readonly "name": string + readonly "namespace"?: string + readonly "status": "completed" | "in_progress" + readonly "type": never + readonly "result"?: string | null + } | { + readonly "action"?: + | { + readonly "queries"?: ReadonlyArray + readonly "query": string + readonly "sources"?: ReadonlyArray + readonly "type": "search" + } + | { readonly "type": "open_page"; readonly "url"?: string | null } + | { readonly "pattern": string; readonly "type": "find_in_page"; readonly "url": string } + readonly "id": string + readonly "status": "completed" | "in_progress" | "failed" + readonly "type": never + readonly "result"?: string | null + } | { + readonly "id": string + readonly "queries": ReadonlyArray + readonly "status": "completed" | "in_progress" | "failed" + readonly "type": never + readonly "result"?: string | null + } | { + readonly "id": string + readonly "result"?: string | null + readonly "status": "in_progress" | "completed" | "generating" | "failed" + readonly "type": "image_generation_call" + readonly "prompt"?: string + } | { + readonly "code": string | null + readonly "container_id": string + readonly "id": string + readonly "outputs": + | ReadonlyArray< + { readonly "type": "image"; readonly "url": string } | { readonly "logs": string; readonly "type": "logs" } + > + | null + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "result"?: string | null + } | { + readonly "action"?: Schema.Json + readonly "call_id": string + readonly "id": string + readonly "pending_safety_checks": ReadonlyArray< + { readonly "code": string; readonly "id": string; readonly "message": string } + > + readonly "status": "completed" | "in_progress" + readonly "type": never + readonly "result"?: string | null + } | { + readonly "datetime": string + readonly "id": string + readonly "status": "in_progress" | "completed" + readonly "timezone": string + readonly "type": never + readonly "result"?: string | null + } | { + readonly "action"?: { + readonly "query": string + readonly "sources"?: ReadonlyArray<{ readonly "type": "url"; readonly "url": string }> + readonly "type": "search" + } + readonly "id": string + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "result"?: string | null + } | { + readonly "code"?: string + readonly "exitCode"?: number + readonly "id": string + readonly "language"?: string + readonly "status": "in_progress" | "completed" + readonly "stderr"?: string + readonly "stdout"?: string + readonly "type": never + readonly "result"?: string | null + } | { + readonly "id": string + readonly "queries"?: ReadonlyArray + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "result"?: string | null + } | { + readonly "id": string + readonly "imageB64"?: string + readonly "imageUrl"?: string + readonly "prompt"?: string + readonly "result"?: string | null + readonly "revisedPrompt"?: string + readonly "status": "in_progress" | "completed" + readonly "type": never + } | { + readonly "action"?: string + readonly "id": string + readonly "screenshotB64"?: string + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "result"?: string | null + } | { + readonly "arguments"?: string + readonly "call_id"?: string + readonly "command"?: string + readonly "exitCode"?: number + readonly "id": string + readonly "status": "in_progress" | "completed" + readonly "stderr"?: string + readonly "stdout"?: string + readonly "type": never + readonly "result"?: string | null + } | { + readonly "command"?: "view" | "create" | "str_replace" | "insert" + readonly "filePath"?: string + readonly "id": string + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "result"?: string | null + } | { + readonly "call_id"?: string + readonly "id": string + readonly "operation"?: ApplyPatchCallOperation + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "result"?: string | null + } | { + readonly "call_id": string + readonly "id": string + readonly "operation": ApplyPatchCallOperation + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "result"?: string | null + } | { + readonly "action"?: { + readonly "commands": ReadonlyArray + readonly "max_output_length": number | null + readonly "timeout_ms": number | null + } + readonly "call_id": string + readonly "id": string + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "result"?: string | null + } | { + readonly "call_id": string + readonly "id": string + readonly "max_output_length"?: number | null + readonly "output": ReadonlyArray< + { + readonly "outcome": { readonly "exit_code": number; readonly "type": "exit" } | { readonly "type": "timeout" } + readonly "stderr": string + readonly "stdout": string + } + > + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "result"?: string | null + } | { + readonly "content"?: string + readonly "error"?: string + readonly "httpStatus"?: number + readonly "id": string + readonly "status": "in_progress" | "completed" + readonly "title"?: string + readonly "type": never + readonly "url"?: string + readonly "result"?: string | null + } | { + readonly "id": string + readonly "query"?: string + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "result"?: string | null + } | { + readonly "action"?: "read" | "write" | "delete" + readonly "id": string + readonly "key"?: string + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "value"?: Schema.Json + readonly "result"?: string | null + } | { + readonly "id": string + readonly "serverLabel"?: string + readonly "status": "in_progress" | "completed" + readonly "toolName"?: string + readonly "type": never + readonly "result"?: string | null + } | { + readonly "arguments"?: string + readonly "id": string + readonly "query"?: string + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "result"?: string | null + } | { + readonly "analysis"?: FusionAnalysisResult + readonly "error"?: string + readonly "failed_models"?: ReadonlyArray< + { readonly "error": string; readonly "model": string; readonly "status_code"?: number } + > + readonly "failure_reason"?: string + readonly "id": string + readonly "responses"?: ReadonlyArray<{ readonly "content"?: string; readonly "model": string }> + readonly "sources"?: ReadonlyArray + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "result"?: string | null + } | { + readonly "advice"?: string + readonly "error"?: string + readonly "id": string + readonly "instance_name"?: string + readonly "model"?: string + readonly "prompt"?: string + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "result"?: string | null + } | { + readonly "error"?: string + readonly "id": string + readonly "instance_name"?: string + readonly "model"?: string + readonly "name"?: string + readonly "outcome"?: string + readonly "status": "in_progress" | "completed" + readonly "task_description"?: string + readonly "task_name"?: string + readonly "type": never + readonly "result"?: string | null + } | { + readonly "error"?: string + readonly "file_id"?: string + readonly "filename"?: string + readonly "id": string + readonly "operation"?: string + readonly "result"?: string + readonly "status": "in_progress" | "completed" + readonly "type": never + } | { + readonly "call_id": string + readonly "id": string + readonly "input": string + readonly "name": string + readonly "namespace"?: string + readonly "type": never + readonly "result"?: string | null + readonly "status": ImageGenerationStatus + } | { + readonly "content": ReadonlyArray + readonly "id": string + readonly "phase"?: "commentary" | "final_answer" | null + readonly "role": "assistant" + readonly "status": "completed" | "in_progress" + readonly "type": never + readonly "call_id": string + readonly "created_by"?: string + readonly "operation": + | ApplyPatchCreateFileOperation + | ApplyPatchUpdateFileOperation + | ApplyPatchDeleteFileOperation + } | { + readonly "content"?: ReadonlyArray<{ readonly "text": string; readonly "type": "reasoning_text" }> + readonly "encrypted_content"?: string | null + readonly "id": string + readonly "status": "completed" | "in_progress" + readonly "summary": ReadonlyArray + readonly "type": never + readonly "format"?: ReasoningFormat + readonly "signature"?: string | null + readonly "call_id": string + readonly "created_by"?: string + readonly "operation": + | ApplyPatchCreateFileOperation + | ApplyPatchUpdateFileOperation + | ApplyPatchDeleteFileOperation + } | { + readonly "arguments": string + readonly "call_id": string + readonly "id": string + readonly "name": string + readonly "namespace"?: string + readonly "status": "completed" | "in_progress" + readonly "type": never + readonly "created_by"?: string + readonly "operation": + | ApplyPatchCreateFileOperation + | ApplyPatchUpdateFileOperation + | ApplyPatchDeleteFileOperation + } | { + readonly "action"?: + | { + readonly "queries"?: ReadonlyArray + readonly "query": string + readonly "sources"?: ReadonlyArray + readonly "type": "search" + } + | { readonly "type": "open_page"; readonly "url"?: string | null } + | { readonly "pattern": string; readonly "type": "find_in_page"; readonly "url": string } + readonly "id": string + readonly "status": "completed" | "in_progress" + readonly "type": never + readonly "call_id": string + readonly "created_by"?: string + readonly "operation": + | ApplyPatchCreateFileOperation + | ApplyPatchUpdateFileOperation + | ApplyPatchDeleteFileOperation + } | { + readonly "id": string + readonly "queries": ReadonlyArray + readonly "status": "completed" | "in_progress" + readonly "type": never + readonly "call_id": string + readonly "created_by"?: string + readonly "operation": + | ApplyPatchCreateFileOperation + | ApplyPatchUpdateFileOperation + | ApplyPatchDeleteFileOperation + } | { + readonly "id": string + readonly "result"?: string | null + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "prompt"?: string + readonly "call_id": string + readonly "created_by"?: string + readonly "operation": + | ApplyPatchCreateFileOperation + | ApplyPatchUpdateFileOperation + | ApplyPatchDeleteFileOperation + } | { + readonly "code": string | null + readonly "container_id": string + readonly "id": string + readonly "outputs": + | ReadonlyArray< + { readonly "type": "image"; readonly "url": string } | { readonly "logs": string; readonly "type": "logs" } + > + | null + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "call_id": string + readonly "created_by"?: string + readonly "operation": + | ApplyPatchCreateFileOperation + | ApplyPatchUpdateFileOperation + | ApplyPatchDeleteFileOperation + } | { + readonly "action"?: Schema.Json + readonly "call_id": string + readonly "id": string + readonly "pending_safety_checks": ReadonlyArray< + { readonly "code": string; readonly "id": string; readonly "message": string } + > + readonly "status": "completed" | "in_progress" + readonly "type": never + readonly "created_by"?: string + readonly "operation": + | ApplyPatchCreateFileOperation + | ApplyPatchUpdateFileOperation + | ApplyPatchDeleteFileOperation + } | { + readonly "datetime": string + readonly "id": string + readonly "status": "in_progress" | "completed" + readonly "timezone": string + readonly "type": never + readonly "call_id": string + readonly "created_by"?: string + readonly "operation": + | ApplyPatchCreateFileOperation + | ApplyPatchUpdateFileOperation + | ApplyPatchDeleteFileOperation + } | { + readonly "action"?: { + readonly "query": string + readonly "sources"?: ReadonlyArray<{ readonly "type": "url"; readonly "url": string }> + readonly "type": "search" + } + readonly "id": string + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "call_id": string + readonly "created_by"?: string + readonly "operation": + | ApplyPatchCreateFileOperation + | ApplyPatchUpdateFileOperation + | ApplyPatchDeleteFileOperation + } | { + readonly "code"?: string + readonly "exitCode"?: number + readonly "id": string + readonly "language"?: string + readonly "status": "in_progress" | "completed" + readonly "stderr"?: string + readonly "stdout"?: string + readonly "type": never + readonly "call_id": string + readonly "created_by"?: string + readonly "operation": + | ApplyPatchCreateFileOperation + | ApplyPatchUpdateFileOperation + | ApplyPatchDeleteFileOperation + } | { + readonly "id": string + readonly "queries"?: ReadonlyArray + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "call_id": string + readonly "created_by"?: string + readonly "operation": + | ApplyPatchCreateFileOperation + | ApplyPatchUpdateFileOperation + | ApplyPatchDeleteFileOperation + } | { + readonly "id": string + readonly "imageB64"?: string + readonly "imageUrl"?: string + readonly "prompt"?: string + readonly "result"?: string | null + readonly "revisedPrompt"?: string + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "call_id": string + readonly "created_by"?: string + readonly "operation": + | ApplyPatchCreateFileOperation + | ApplyPatchUpdateFileOperation + | ApplyPatchDeleteFileOperation + } | { + readonly "action"?: string + readonly "id": string + readonly "screenshotB64"?: string + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "call_id": string + readonly "created_by"?: string + readonly "operation": + | ApplyPatchCreateFileOperation + | ApplyPatchUpdateFileOperation + | ApplyPatchDeleteFileOperation + } | { + readonly "arguments"?: string + readonly "call_id": string + readonly "command"?: string + readonly "exitCode"?: number + readonly "id": string + readonly "status": "in_progress" | "completed" + readonly "stderr"?: string + readonly "stdout"?: string + readonly "type": never + readonly "created_by"?: string + readonly "operation": + | ApplyPatchCreateFileOperation + | ApplyPatchUpdateFileOperation + | ApplyPatchDeleteFileOperation + } | { + readonly "command"?: "view" | "create" | "str_replace" | "insert" + readonly "filePath"?: string + readonly "id": string + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "call_id": string + readonly "created_by"?: string + readonly "operation": + | ApplyPatchCreateFileOperation + | ApplyPatchUpdateFileOperation + | ApplyPatchDeleteFileOperation + } | { + readonly "call_id": string + readonly "id": string + readonly "operation": + | { readonly "diff": string; readonly "path": string; readonly "type": "create_file" } + | { readonly "diff": string; readonly "path": string; readonly "type": never } + | { readonly "path": string; readonly "type": never; readonly "diff": string } + | { readonly "diff": string; readonly "path": string; readonly "type": never } + | { readonly "diff": string; readonly "path": string; readonly "type": "update_file" } + | { readonly "path": string; readonly "type": never; readonly "diff": string } + | { readonly "diff": string; readonly "path": string; readonly "type": never } + | { readonly "diff": string; readonly "path": string; readonly "type": never } + | { readonly "path": string; readonly "type": "delete_file" } + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "created_by"?: string + } | { + readonly "call_id": string + readonly "id": string + readonly "operation": + | { readonly "diff": string; readonly "path": string; readonly "type": "create_file" } + | { readonly "diff": string; readonly "path": string; readonly "type": never } + | { readonly "path": string; readonly "type": never; readonly "diff": string } + | { readonly "diff": string; readonly "path": string; readonly "type": never } + | { readonly "diff": string; readonly "path": string; readonly "type": "update_file" } + | { readonly "path": string; readonly "type": never; readonly "diff": string } + | { readonly "diff": string; readonly "path": string; readonly "type": never } + | { readonly "diff": string; readonly "path": string; readonly "type": never } + | { readonly "path": string; readonly "type": "delete_file" } + readonly "status": "in_progress" | "completed" + readonly "type": "apply_patch_call" + readonly "created_by"?: string + } | { + readonly "action"?: { + readonly "commands": ReadonlyArray + readonly "max_output_length": number | null + readonly "timeout_ms": number | null + } + readonly "call_id": string + readonly "id": string + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "created_by"?: string + readonly "operation": + | ApplyPatchCreateFileOperation + | ApplyPatchUpdateFileOperation + | ApplyPatchDeleteFileOperation + } | { + readonly "call_id": string + readonly "id": string + readonly "max_output_length"?: number | null + readonly "output": ReadonlyArray< + { + readonly "outcome": { readonly "exit_code": number; readonly "type": "exit" } | { readonly "type": "timeout" } + readonly "stderr": string + readonly "stdout": string + } + > + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "created_by"?: string + readonly "operation": + | ApplyPatchCreateFileOperation + | ApplyPatchUpdateFileOperation + | ApplyPatchDeleteFileOperation + } | { + readonly "content"?: string + readonly "error"?: string + readonly "httpStatus"?: number + readonly "id": string + readonly "status": "in_progress" | "completed" + readonly "title"?: string + readonly "type": never + readonly "url"?: string + readonly "call_id": string + readonly "created_by"?: string + readonly "operation": + | ApplyPatchCreateFileOperation + | ApplyPatchUpdateFileOperation + | ApplyPatchDeleteFileOperation + } | { + readonly "id": string + readonly "query"?: string + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "call_id": string + readonly "created_by"?: string + readonly "operation": + | ApplyPatchCreateFileOperation + | ApplyPatchUpdateFileOperation + | ApplyPatchDeleteFileOperation + } | { + readonly "action"?: "read" | "write" | "delete" + readonly "id": string + readonly "key"?: string + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "value"?: Schema.Json + readonly "call_id": string + readonly "created_by"?: string + readonly "operation": + | ApplyPatchCreateFileOperation + | ApplyPatchUpdateFileOperation + | ApplyPatchDeleteFileOperation + } | { + readonly "id": string + readonly "serverLabel"?: string + readonly "status": "in_progress" | "completed" + readonly "toolName"?: string + readonly "type": never + readonly "call_id": string + readonly "created_by"?: string + readonly "operation": + | ApplyPatchCreateFileOperation + | ApplyPatchUpdateFileOperation + | ApplyPatchDeleteFileOperation + } | { + readonly "arguments"?: string + readonly "id": string + readonly "query"?: string + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "call_id": string + readonly "created_by"?: string + readonly "operation": + | ApplyPatchCreateFileOperation + | ApplyPatchUpdateFileOperation + | ApplyPatchDeleteFileOperation + } | { + readonly "analysis"?: FusionAnalysisResult + readonly "error"?: string + readonly "failed_models"?: ReadonlyArray< + { readonly "error": string; readonly "model": string; readonly "status_code"?: number } + > + readonly "failure_reason"?: string + readonly "id": string + readonly "responses"?: ReadonlyArray<{ readonly "content"?: string; readonly "model": string }> + readonly "sources"?: ReadonlyArray + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "call_id": string + readonly "created_by"?: string + readonly "operation": + | ApplyPatchCreateFileOperation + | ApplyPatchUpdateFileOperation + | ApplyPatchDeleteFileOperation + } | { + readonly "advice"?: string + readonly "error"?: string + readonly "id": string + readonly "instance_name"?: string + readonly "model"?: string + readonly "prompt"?: string + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "call_id": string + readonly "created_by"?: string + readonly "operation": + | ApplyPatchCreateFileOperation + | ApplyPatchUpdateFileOperation + | ApplyPatchDeleteFileOperation + } | { + readonly "error"?: string + readonly "id": string + readonly "instance_name"?: string + readonly "model"?: string + readonly "name"?: string + readonly "outcome"?: string + readonly "status": "in_progress" | "completed" + readonly "task_description"?: string + readonly "task_name"?: string + readonly "type": never + readonly "call_id": string + readonly "created_by"?: string + readonly "operation": + | ApplyPatchCreateFileOperation + | ApplyPatchUpdateFileOperation + | ApplyPatchDeleteFileOperation + } | { + readonly "error"?: string + readonly "file_id"?: string + readonly "filename"?: string + readonly "id": string + readonly "operation": never + readonly "result"?: string + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "call_id": string + readonly "created_by"?: string + } | { + readonly "call_id": string + readonly "id": string + readonly "input": string + readonly "name": string + readonly "namespace"?: string + readonly "type": never + readonly "created_by"?: string + readonly "operation": + | ApplyPatchCreateFileOperation + | ApplyPatchUpdateFileOperation + | ApplyPatchDeleteFileOperation + readonly "status": "in_progress" | "completed" + } + > + readonly "output_text"?: string + readonly "parallel_tool_calls": boolean + readonly "presence_penalty": number | null + readonly "previous_response_id"?: string | null + readonly "prompt"?: StoredPromptTemplate + readonly "prompt_cache_key"?: string | null + readonly "prompt_cache_options"?: PromptCacheOptions + readonly "reasoning"?: BaseReasoningConfig + readonly "safety_identifier"?: string | null + readonly "service_tier"?: "auto" | "default" | "flex" | "priority" | "scale" | null + readonly "status": OpenAIResponsesResponseStatus + readonly "store"?: boolean + readonly "temperature": number | null + readonly "text"?: { + readonly "format"?: + | { readonly "type": "text" } + | { readonly "type": never } + | { + readonly "description"?: string + readonly "name": string + readonly "schema": {} + readonly "strict"?: boolean | null + readonly "type": never + } + | { readonly "type": never } + | { readonly "type": "json_object" } + | { + readonly "description"?: string + readonly "name": string + readonly "schema": {} + readonly "strict"?: boolean | null + readonly "type": never + } + | { + readonly "type": never + readonly "description"?: string + readonly "name": string + readonly "schema": {} + readonly "strict"?: boolean | null + } + | { + readonly "type": never + readonly "description"?: string + readonly "name": string + readonly "schema": {} + readonly "strict"?: boolean | null + } + | { + readonly "description"?: string + readonly "name": string + readonly "schema": {} + readonly "strict"?: boolean | null + readonly "type": "json_schema" + } + readonly "verbosity"?: "high" | "low" | "medium" | null + } + readonly "tool_choice": OpenAIResponsesToolChoice + readonly "tools": ReadonlyArray< + | { + readonly "description"?: string | null + readonly "name": string + readonly "parameters": { readonly [x: string]: Schema.Json } | null + readonly "strict"?: boolean | null + readonly "type": "function" + } + | Preview_WebSearchServerTool + | Preview_20250311_WebSearchServerTool + | Legacy_WebSearchServerTool + | WebSearchServerTool + | FileSearchServerTool + | ComputerUseServerTool + | CodeInterpreterServerTool + | McpServerTool + | ImageGenerationServerTool + | CodexLocalShellTool + | ShellServerTool + | ApplyPatchServerTool + | CustomTool + | NamespaceTool + > + readonly "top_logprobs"?: number + readonly "top_p": number | null + readonly "truncation"?: Truncation + readonly "usage"?: { + readonly "input_tokens": number + readonly "input_tokens_details": { readonly "cache_write_tokens"?: number | null; readonly "cached_tokens": number } + readonly "output_tokens": number + readonly "output_tokens_details": { readonly "reasoning_tokens": number } + readonly "total_tokens": number + readonly "cost"?: number | null + readonly "cost_details"?: { + readonly "upstream_inference_cost"?: number | null + readonly "upstream_inference_input_cost": number + readonly "upstream_inference_output_cost": number + } + readonly "is_byok"?: boolean + readonly "server_tool_use_details"?: ServerToolUseDetails + } + readonly "user"?: string | null + readonly "error_type"?: ApiErrorType + readonly "openrouter_metadata"?: OpenRouterMetadata +} +export const OpenResponsesResult = Schema.Struct({ + "background": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), + "completed_at": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]), + "created_at": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "error": ResponsesErrorField, + "frequency_penalty": Schema.Union([ + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), + Schema.Null + ]).annotate({ "format": "double" }), + "id": Schema.String, + "incomplete_details": IncompleteDetails, + "instructions": BaseInputs, + "max_output_tokens": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]) + ), + "max_tool_calls": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]) + ), + "metadata": RequestMetadata, + "model": Schema.String, + "object": Schema.Literal("response"), + "output": Schema.Array(Schema.Union([ + Schema.Union([ + Schema.Struct({ + "content": Schema.Array(Schema.Union([ + Schema.Union([ + Schema.Struct({ + "annotations": Schema.optionalKey( + Schema.Array( + Schema.Union([ + Schema.Union([ + Schema.Struct({ + "file_id": Schema.String, + "filename": Schema.String, + "index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("file_citation") + }), + Schema.Struct({ + "content": Schema.optionalKey(Schema.String), + "end_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "start_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "title": Schema.String, + "type": Schema.Never, + "url": Schema.String, + "file_id": Schema.String, + "filename": Schema.String, + "index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + }), + Schema.Struct({ + "file_id": Schema.String, + "index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Never, + "filename": Schema.String + }) + ]), + Schema.Union([ + Schema.Struct({ + "file_id": Schema.String, + "filename": Schema.String, + "index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Never, + "content": Schema.optionalKey(Schema.String), + "end_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "start_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "title": Schema.String, + "url": Schema.String + }), + Schema.Struct({ + "content": Schema.optionalKey(Schema.String), + "end_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "start_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "title": Schema.String, + "type": Schema.Literal("url_citation"), + "url": Schema.String + }), + Schema.Struct({ + "file_id": Schema.String, + "index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Never, + "content": Schema.optionalKey(Schema.String), + "end_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "start_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "title": Schema.String, + "url": Schema.String + }) + ]), + Schema.Union([ + Schema.Struct({ + "file_id": Schema.String, + "filename": Schema.String, + "index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Never + }), + Schema.Struct({ + "content": Schema.optionalKey(Schema.String), + "end_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "start_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "title": Schema.String, + "type": Schema.Never, + "url": Schema.String, + "file_id": Schema.String, + "index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + }), + Schema.Struct({ + "file_id": Schema.String, + "index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("file_path") + }) + ]) + ]) + ) + ), + "logprobs": Schema.optionalKey(Schema.Array(Schema.Struct({ + "bytes": Schema.Array(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "logprob": Schema.Number.annotate({ "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ), + "token": Schema.String, + "top_logprobs": Schema.Array( + Schema.Struct({ + "bytes": Schema.Array(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "logprob": Schema.Number.annotate({ "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ), + "token": Schema.String + }) + ) + }))), + "text": Schema.String, + "type": Schema.Literal("output_text") + }), + Schema.Struct({ + "refusal": Schema.String, + "type": Schema.Never, + "annotations": Schema.optionalKey(Schema.Array(OpenAIResponsesAnnotation)), + "logprobs": Schema.optionalKey(Schema.Array(Schema.Struct({ + "bytes": Schema.Array(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "logprob": Schema.Number.annotate({ "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ), + "token": Schema.String, + "top_logprobs": Schema.Array( + Schema.Struct({ + "bytes": Schema.Array(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "logprob": Schema.Number.annotate({ "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ), + "token": Schema.String + }) + ) + }))), + "text": Schema.String + }) + ]), + Schema.Union([ + Schema.Struct({ + "annotations": Schema.optionalKey(Schema.Array(OpenAIResponsesAnnotation)), + "logprobs": Schema.optionalKey(Schema.Array(Schema.Struct({ + "bytes": Schema.Array(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "logprob": Schema.Number.annotate({ "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ), + "token": Schema.String, + "top_logprobs": Schema.Array( + Schema.Struct({ + "bytes": Schema.Array(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "logprob": Schema.Number.annotate({ "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ), + "token": Schema.String + }) + ) + }))), + "text": Schema.String, + "type": Schema.Never, + "refusal": Schema.String + }), + Schema.Struct({ "refusal": Schema.String, "type": Schema.Literal("refusal") }) + ]) + ])), + "id": Schema.String, + "phase": Schema.optionalKey( + Schema.Union([ + Schema.Literal("commentary").annotate({ + "description": + "The phase of an assistant message. Use `commentary` for an intermediate assistant message and `final_answer` for the final assistant message. For follow-up requests with models like `gpt-5.3-codex` and later, preserve and resend phase on all assistant messages. Omitting it can degrade performance. Not used for user messages." + }), + Schema.Literal("final_answer").annotate({ + "description": + "The phase of an assistant message. Use `commentary` for an intermediate assistant message and `final_answer` for the final assistant message. For follow-up requests with models like `gpt-5.3-codex` and later, preserve and resend phase on all assistant messages. Omitting it can degrade performance. Not used for user messages." + }), + Schema.Union([Schema.Null]).annotate({ + "description": + "The phase of an assistant message. Use `commentary` for an intermediate assistant message and `final_answer` for the final assistant message. For follow-up requests with models like `gpt-5.3-codex` and later, preserve and resend phase on all assistant messages. Omitting it can degrade performance. Not used for user messages." + }) + ]).annotate({ + "description": + "The phase of an assistant message. Use `commentary` for an intermediate assistant message and `final_answer` for the final assistant message. For follow-up requests with models like `gpt-5.3-codex` and later, preserve and resend phase on all assistant messages. Omitting it can degrade performance. Not used for user messages." + }) + ), + "role": Schema.Literal("assistant"), + "status": Schema.optionalKey( + Schema.Union([Schema.Literal("completed"), Schema.Literal("incomplete"), Schema.Literal("in_progress")]) + ), + "type": Schema.Literal("message") + }).annotate({ "description": "An output message item" }), + Schema.Struct({ + "content": Schema.Union([Schema.Array(Schema.Union([ + Schema.Struct({ + "annotations": Schema.optionalKey(Schema.Array(OpenAIResponsesAnnotation)), + "logprobs": Schema.optionalKey(Schema.Array(Schema.Struct({ + "bytes": Schema.Array(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "logprob": Schema.Number.annotate({ "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ), + "token": Schema.String, + "top_logprobs": Schema.Array( + Schema.Struct({ + "bytes": Schema.Array(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "logprob": Schema.Number.annotate({ "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ), + "token": Schema.String + }) + ) + }))), + "text": Schema.String, + "type": Schema.Never + }), + Schema.Struct({ "refusal": Schema.String, "type": Schema.Never, "text": Schema.String }) + ]))]), + "encrypted_content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "id": Schema.String, + "status": Schema.optionalKey( + Schema.Union([Schema.Literal("completed"), Schema.Literal("incomplete"), Schema.Literal("in_progress")]) + ), + "summary": Schema.Array(ReasoningSummaryText), + "type": Schema.Never, + "format": Schema.optionalKey(ReasoningFormat), + "signature": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "A signature for the reasoning content, used for verification" + }) + ), + "phase": Schema.optionalKey( + Schema.Union([Schema.Literal("commentary"), Schema.Literal("final_answer"), Schema.Null]).annotate({ + "description": + "The phase of an assistant message. Use `commentary` for an intermediate assistant message and `final_answer` for the final assistant message. For follow-up requests with models like `gpt-5.3-codex` and later, preserve and resend phase on all assistant messages. Omitting it can degrade performance. Not used for user messages." + }) + ), + "role": Schema.Literal("assistant") + }).annotate({ "description": "An output item containing reasoning" }), + Schema.Struct({ + "arguments": Schema.String, + "call_id": Schema.String, + "id": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ), + "status": Schema.optionalKey( + Schema.Union([Schema.Literal("completed"), Schema.Literal("incomplete"), Schema.Literal("in_progress")]) + ), + "type": Schema.Never, + "content": Schema.Array(Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent])), + "phase": Schema.optionalKey( + Schema.Union([Schema.Literal("commentary"), Schema.Literal("final_answer"), Schema.Null]).annotate({ + "description": + "The phase of an assistant message. Use `commentary` for an intermediate assistant message and `final_answer` for the final assistant message. For follow-up requests with models like `gpt-5.3-codex` and later, preserve and resend phase on all assistant messages. Omitting it can degrade performance. Not used for user messages." + }) + ), + "role": Schema.Literal("assistant") + }), + Schema.Struct({ + "action": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "query": Schema.String, + "sources": Schema.optionalKey(Schema.Array(WebSearchSource)), + "type": Schema.Literal("search") + }), + Schema.Struct({ + "type": Schema.Literal("open_page"), + "url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }), + Schema.Struct({ "pattern": Schema.String, "type": Schema.Literal("find_in_page"), "url": Schema.String }) + ], { mode: "oneOf" }) + ), + "id": Schema.String, + "status": Schema.Union([Schema.Literal("completed"), Schema.Literal("in_progress")]), + "type": Schema.Never, + "content": Schema.Array(Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent])), + "phase": Schema.optionalKey( + Schema.Union([Schema.Literal("commentary"), Schema.Literal("final_answer"), Schema.Null]).annotate({ + "description": + "The phase of an assistant message. Use `commentary` for an intermediate assistant message and `final_answer` for the final assistant message. For follow-up requests with models like `gpt-5.3-codex` and later, preserve and resend phase on all assistant messages. Omitting it can degrade performance. Not used for user messages." + }) + ), + "role": Schema.Literal("assistant") + }), + Schema.Struct({ + "id": Schema.String, + "queries": Schema.Array(Schema.String), + "status": Schema.Union([Schema.Literal("completed"), Schema.Literal("in_progress")]), + "type": Schema.Never, + "content": Schema.Array(Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent])), + "phase": Schema.optionalKey( + Schema.Union([Schema.Literal("commentary"), Schema.Literal("final_answer"), Schema.Null]).annotate({ + "description": + "The phase of an assistant message. Use `commentary` for an intermediate assistant message and `final_answer` for the final assistant message. For follow-up requests with models like `gpt-5.3-codex` and later, preserve and resend phase on all assistant messages. Omitting it can degrade performance. Not used for user messages." + }) + ), + "role": Schema.Literal("assistant") + }), + Schema.Struct({ + "id": Schema.String, + "result": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "prompt": Schema.optionalKey( + Schema.String.annotate({ + "description": "The prompt (possibly rewritten) that the image was generated from." + }) + ), + "content": Schema.Array(Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent])), + "phase": Schema.optionalKey( + Schema.Union([Schema.Literal("commentary"), Schema.Literal("final_answer"), Schema.Null]).annotate({ + "description": + "The phase of an assistant message. Use `commentary` for an intermediate assistant message and `final_answer` for the final assistant message. For follow-up requests with models like `gpt-5.3-codex` and later, preserve and resend phase on all assistant messages. Omitting it can degrade performance. Not used for user messages." + }) + ), + "role": Schema.Literal("assistant") + }), + Schema.Struct({ + "code": Schema.Union([Schema.String, Schema.Null]), + "container_id": Schema.String, + "id": Schema.String, + "outputs": Schema.Union([ + Schema.Array( + Schema.Union([ + Schema.Struct({ "type": Schema.Literal("image"), "url": Schema.String }), + Schema.Struct({ "logs": Schema.String, "type": Schema.Literal("logs") }) + ]) + ), + Schema.Null + ]), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "type": Schema.Never, + "content": Schema.Array(Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent])), + "phase": Schema.optionalKey( + Schema.Union([Schema.Literal("commentary"), Schema.Literal("final_answer"), Schema.Null]).annotate({ + "description": + "The phase of an assistant message. Use `commentary` for an intermediate assistant message and `final_answer` for the final assistant message. For follow-up requests with models like `gpt-5.3-codex` and later, preserve and resend phase on all assistant messages. Omitting it can degrade performance. Not used for user messages." + }) + ), + "role": Schema.Literal("assistant") + }).annotate({ "description": "A code interpreter execution call with outputs" }), + Schema.Struct({ + "action": Schema.optionalKey(Schema.Json.annotate({ "expected": "JSON value" })), + "call_id": Schema.String, + "id": Schema.String, + "pending_safety_checks": Schema.Array( + Schema.Struct({ "code": Schema.String, "id": Schema.String, "message": Schema.String }) + ), + "status": Schema.Union([ + Schema.Literal("completed"), + Schema.Literal("incomplete"), + Schema.Literal("in_progress") + ]), + "type": Schema.Never, + "content": Schema.Array(Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent])), + "phase": Schema.optionalKey( + Schema.Union([Schema.Literal("commentary"), Schema.Literal("final_answer"), Schema.Null]).annotate({ + "description": + "The phase of an assistant message. Use `commentary` for an intermediate assistant message and `final_answer` for the final assistant message. For follow-up requests with models like `gpt-5.3-codex` and later, preserve and resend phase on all assistant messages. Omitting it can degrade performance. Not used for user messages." + }) + ), + "role": Schema.Literal("assistant") + }), + Schema.Struct({ + "datetime": Schema.String.annotate({ "description": "ISO 8601 datetime string" }), + "id": Schema.String, + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "timezone": Schema.String.annotate({ "description": "IANA timezone name" }), + "type": Schema.Never, + "content": Schema.Array(Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent])), + "phase": Schema.optionalKey( + Schema.Union([Schema.Literal("commentary"), Schema.Literal("final_answer"), Schema.Null]).annotate({ + "description": + "The phase of an assistant message. Use `commentary` for an intermediate assistant message and `final_answer` for the final assistant message. For follow-up requests with models like `gpt-5.3-codex` and later, preserve and resend phase on all assistant messages. Omitting it can degrade performance. Not used for user messages." + }) + ), + "role": Schema.Literal("assistant") + }).annotate({ "description": "An openrouter:datetime server tool output item" }), + Schema.Struct({ + "action": Schema.optionalKey( + Schema.Struct({ + "query": Schema.String, + "sources": Schema.optionalKey( + Schema.Array(Schema.Struct({ "type": Schema.Literal("url"), "url": Schema.String })) + ), + "type": Schema.Literal("search") + }).annotate({ + "description": + "The search action performed, matching OpenAI web_search_call.action shape. Includes the query the model issued and optional source URLs returned by the search provider." + }) + ), + "id": Schema.String, + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "type": Schema.Never, + "content": Schema.Array(Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent])), + "phase": Schema.optionalKey( + Schema.Union([Schema.Literal("commentary"), Schema.Literal("final_answer"), Schema.Null]).annotate({ + "description": + "The phase of an assistant message. Use `commentary` for an intermediate assistant message and `final_answer` for the final assistant message. For follow-up requests with models like `gpt-5.3-codex` and later, preserve and resend phase on all assistant messages. Omitting it can degrade performance. Not used for user messages." + }) + ), + "role": Schema.Literal("assistant") + }).annotate({ "description": "An openrouter:web_search server tool output item" }), + Schema.Struct({ + "code": Schema.optionalKey(Schema.String), + "exitCode": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "id": Schema.String, + "language": Schema.optionalKey(Schema.String), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "stderr": Schema.optionalKey(Schema.String), + "stdout": Schema.optionalKey(Schema.String), + "type": Schema.Never, + "content": Schema.Array(Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent])), + "phase": Schema.optionalKey( + Schema.Union([Schema.Literal("commentary"), Schema.Literal("final_answer"), Schema.Null]).annotate({ + "description": + "The phase of an assistant message. Use `commentary` for an intermediate assistant message and `final_answer` for the final assistant message. For follow-up requests with models like `gpt-5.3-codex` and later, preserve and resend phase on all assistant messages. Omitting it can degrade performance. Not used for user messages." + }) + ), + "role": Schema.Literal("assistant") + }).annotate({ "description": "An openrouter:code_interpreter server tool output item" }), + Schema.Struct({ + "id": Schema.String, + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "type": Schema.Never, + "content": Schema.Array(Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent])), + "phase": Schema.optionalKey( + Schema.Union([Schema.Literal("commentary"), Schema.Literal("final_answer"), Schema.Null]).annotate({ + "description": + "The phase of an assistant message. Use `commentary` for an intermediate assistant message and `final_answer` for the final assistant message. For follow-up requests with models like `gpt-5.3-codex` and later, preserve and resend phase on all assistant messages. Omitting it can degrade performance. Not used for user messages." + }) + ), + "role": Schema.Literal("assistant") + }).annotate({ "description": "An openrouter:file_search server tool output item" }), + Schema.Struct({ + "id": Schema.String, + "imageB64": Schema.optionalKey(Schema.String), + "imageUrl": Schema.optionalKey(Schema.String), + "prompt": Schema.optionalKey( + Schema.String.annotate({ + "description": "The prompt (possibly rewritten) that the image was generated from." + }) + ), + "result": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": + "The generated image as a base64-encoded string or URL, matching OpenAI image_generation_call format" + }) + ), + "revisedPrompt": Schema.optionalKey(Schema.String), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "type": Schema.Never, + "content": Schema.Array(Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent])), + "phase": Schema.optionalKey( + Schema.Union([Schema.Literal("commentary"), Schema.Literal("final_answer"), Schema.Null]).annotate({ + "description": + "The phase of an assistant message. Use `commentary` for an intermediate assistant message and `final_answer` for the final assistant message. For follow-up requests with models like `gpt-5.3-codex` and later, preserve and resend phase on all assistant messages. Omitting it can degrade performance. Not used for user messages." + }) + ), + "role": Schema.Literal("assistant") + }).annotate({ "description": "An openrouter:image_generation server tool output item" }), + Schema.Struct({ + "action": Schema.optionalKey(Schema.String), + "id": Schema.String, + "screenshotB64": Schema.optionalKey(Schema.String), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "type": Schema.Never, + "content": Schema.Array(Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent])), + "phase": Schema.optionalKey( + Schema.Union([Schema.Literal("commentary"), Schema.Literal("final_answer"), Schema.Null]).annotate({ + "description": + "The phase of an assistant message. Use `commentary` for an intermediate assistant message and `final_answer` for the final assistant message. For follow-up requests with models like `gpt-5.3-codex` and later, preserve and resend phase on all assistant messages. Omitting it can degrade performance. Not used for user messages." + }) + ), + "role": Schema.Literal("assistant") + }).annotate({ "description": "An openrouter:browser_use server tool output item" }), + Schema.Struct({ + "arguments": Schema.optionalKey( + Schema.String.annotate({ "description": "The raw tool-call arguments string as emitted by the model." }) + ), + "call_id": Schema.optionalKey( + Schema.String.annotate({ "description": "The model-generated tool call id from the originating turn." }) + ), + "command": Schema.optionalKey(Schema.String), + "exitCode": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "id": Schema.String, + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "stderr": Schema.optionalKey(Schema.String), + "stdout": Schema.optionalKey(Schema.String), + "type": Schema.Never, + "content": Schema.Array(Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent])), + "phase": Schema.optionalKey( + Schema.Union([Schema.Literal("commentary"), Schema.Literal("final_answer"), Schema.Null]).annotate({ + "description": + "The phase of an assistant message. Use `commentary` for an intermediate assistant message and `final_answer` for the final assistant message. For follow-up requests with models like `gpt-5.3-codex` and later, preserve and resend phase on all assistant messages. Omitting it can degrade performance. Not used for user messages." + }) + ), + "role": Schema.Literal("assistant") + }).annotate({ "description": "An openrouter:bash server tool output item" }), + Schema.Struct({ + "command": Schema.optionalKey(Schema.Literals(["view", "create", "str_replace", "insert"])), + "filePath": Schema.optionalKey(Schema.String), + "id": Schema.String, + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "type": Schema.Never, + "content": Schema.Array(Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent])), + "phase": Schema.optionalKey( + Schema.Union([Schema.Literal("commentary"), Schema.Literal("final_answer"), Schema.Null]).annotate({ + "description": + "The phase of an assistant message. Use `commentary` for an intermediate assistant message and `final_answer` for the final assistant message. For follow-up requests with models like `gpt-5.3-codex` and later, preserve and resend phase on all assistant messages. Omitting it can degrade performance. Not used for user messages." + }) + ), + "role": Schema.Literal("assistant") + }).annotate({ "description": "An openrouter:text_editor server tool output item" }), + Schema.Struct({ + "call_id": Schema.optionalKey(Schema.String), + "id": Schema.String, + "operation": Schema.optionalKey(ApplyPatchCallOperation), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "type": Schema.Never, + "content": Schema.Array(Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent])), + "phase": Schema.optionalKey( + Schema.Union([Schema.Literal("commentary"), Schema.Literal("final_answer"), Schema.Null]).annotate({ + "description": + "The phase of an assistant message. Use `commentary` for an intermediate assistant message and `final_answer` for the final assistant message. For follow-up requests with models like `gpt-5.3-codex` and later, preserve and resend phase on all assistant messages. Omitting it can degrade performance. Not used for user messages." + }) + ), + "role": Schema.Literal("assistant") + }).annotate({ + "description": + "An openrouter:apply_patch server tool output item. The turn halts when validation succeeds so the client can apply the patch and echo an `apply_patch_call_output` on the next turn." + }), + Schema.Struct({ + "call_id": Schema.String, + "id": Schema.String, + "operation": ApplyPatchCallOperation, + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]).annotate({ + "description": "Lifecycle state of an `apply_patch_call` output item." + }), + "type": Schema.Never, + "content": Schema.Array(Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent])), + "phase": Schema.optionalKey( + Schema.Union([Schema.Literal("commentary"), Schema.Literal("final_answer"), Schema.Null]).annotate({ + "description": + "The phase of an assistant message. Use `commentary` for an intermediate assistant message and `final_answer` for the final assistant message. For follow-up requests with models like `gpt-5.3-codex` and later, preserve and resend phase on all assistant messages. Omitting it can degrade performance. Not used for user messages." + }) + ), + "role": Schema.Literal("assistant") + }).annotate({ + "description": + "A native `apply_patch_call` output item matching OpenAI's Responses API shape. Emitted when the client requested the `apply_patch` shorthand." + }), + Schema.Struct({ + "action": Schema.optionalKey(Schema.Struct({ + "commands": Schema.Array(Schema.String), + "max_output_length": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]), + "timeout_ms": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]) + })), + "call_id": Schema.String, + "id": Schema.String, + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]).annotate({ "description": "Status of a shell call or its output." }), + "type": Schema.Never, + "content": Schema.Array(Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent])), + "phase": Schema.optionalKey( + Schema.Union([Schema.Literal("commentary"), Schema.Literal("final_answer"), Schema.Null]).annotate({ + "description": + "The phase of an assistant message. Use `commentary` for an intermediate assistant message and `final_answer` for the final assistant message. For follow-up requests with models like `gpt-5.3-codex` and later, preserve and resend phase on all assistant messages. Omitting it can degrade performance. Not used for user messages." + }) + ), + "role": Schema.Literal("assistant") + }).annotate({ + "description": + "A native `shell_call` output item matching OpenAI's Responses API shape. Emitted for the sandbox-backed `shell` tool." + }), + Schema.Struct({ + "call_id": Schema.String, + "id": Schema.String, + "max_output_length": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]) + ), + "output": Schema.Array(Schema.Struct({ + "outcome": Schema.Union([ + Schema.Struct({ + "exit_code": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("exit") + }), + Schema.Struct({ "type": Schema.Literal("timeout") }) + ], { mode: "oneOf" }), + "stderr": Schema.String, + "stdout": Schema.String + })), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]).annotate({ "description": "Status of a shell call or its output." }), + "type": Schema.Never, + "content": Schema.Array(Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent])), + "phase": Schema.optionalKey( + Schema.Union([Schema.Literal("commentary"), Schema.Literal("final_answer"), Schema.Null]).annotate({ + "description": + "The phase of an assistant message. Use `commentary` for an intermediate assistant message and `final_answer` for the final assistant message. For follow-up requests with models like `gpt-5.3-codex` and later, preserve and resend phase on all assistant messages. Omitting it can degrade performance. Not used for user messages." + }) + ), + "role": Schema.Literal("assistant") + }).annotate({ + "description": + "A native `shell_call_output` item matching OpenAI's Responses API shape. Carries per-command stdout, stderr, and the exit/timeout outcome." + }), + Schema.Struct({ + "content": Schema.Never, + "error": Schema.optionalKey( + Schema.String.annotate({ "description": "The error message if the fetch failed." }) + ), + "httpStatus": Schema.optionalKey( + Schema.Number.annotate({ "description": "The HTTP status code returned by the upstream URL fetch." }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ) + ), + "id": Schema.String, + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "title": Schema.optionalKey(Schema.String), + "type": Schema.Never, + "url": Schema.optionalKey(Schema.String), + "phase": Schema.optionalKey( + Schema.Union([Schema.Literal("commentary"), Schema.Literal("final_answer"), Schema.Null]).annotate({ + "description": + "The phase of an assistant message. Use `commentary` for an intermediate assistant message and `final_answer` for the final assistant message. For follow-up requests with models like `gpt-5.3-codex` and later, preserve and resend phase on all assistant messages. Omitting it can degrade performance. Not used for user messages." + }) + ), + "role": Schema.Literal("assistant") + }).annotate({ "description": "An openrouter:web_fetch server tool output item" }), + Schema.Struct({ + "id": Schema.String, + "query": Schema.optionalKey(Schema.String), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "type": Schema.Never, + "content": Schema.Array(Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent])), + "phase": Schema.optionalKey( + Schema.Union([Schema.Literal("commentary"), Schema.Literal("final_answer"), Schema.Null]).annotate({ + "description": + "The phase of an assistant message. Use `commentary` for an intermediate assistant message and `final_answer` for the final assistant message. For follow-up requests with models like `gpt-5.3-codex` and later, preserve and resend phase on all assistant messages. Omitting it can degrade performance. Not used for user messages." + }) + ), + "role": Schema.Literal("assistant") + }).annotate({ "description": "An openrouter:tool_search server tool output item" }), + Schema.Struct({ + "action": Schema.optionalKey(Schema.Literals(["read", "write", "delete"])), + "id": Schema.String, + "key": Schema.optionalKey(Schema.String), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "type": Schema.Never, + "value": Schema.optionalKey(Schema.Json.annotate({ "expected": "JSON value" })), + "content": Schema.Array(Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent])), + "phase": Schema.optionalKey( + Schema.Union([Schema.Literal("commentary"), Schema.Literal("final_answer"), Schema.Null]).annotate({ + "description": + "The phase of an assistant message. Use `commentary` for an intermediate assistant message and `final_answer` for the final assistant message. For follow-up requests with models like `gpt-5.3-codex` and later, preserve and resend phase on all assistant messages. Omitting it can degrade performance. Not used for user messages." + }) + ), + "role": Schema.Literal("assistant") + }).annotate({ "description": "An openrouter:memory server tool output item" }), + Schema.Struct({ + "id": Schema.String, + "serverLabel": Schema.optionalKey(Schema.String), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "toolName": Schema.optionalKey(Schema.String), + "type": Schema.Never, + "content": Schema.Array(Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent])), + "phase": Schema.optionalKey( + Schema.Union([Schema.Literal("commentary"), Schema.Literal("final_answer"), Schema.Null]).annotate({ + "description": + "The phase of an assistant message. Use `commentary` for an intermediate assistant message and `final_answer` for the final assistant message. For follow-up requests with models like `gpt-5.3-codex` and later, preserve and resend phase on all assistant messages. Omitting it can degrade performance. Not used for user messages." + }) + ), + "role": Schema.Literal("assistant") + }).annotate({ "description": "An openrouter:mcp server tool output item" }), + Schema.Struct({ + "arguments": Schema.optionalKey( + Schema.String.annotate({ + "description": "The JSON arguments submitted to the search tool (e.g. {\"query\":\"Claude\"})" + }) + ), + "id": Schema.String, + "query": Schema.optionalKey(Schema.String), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "type": Schema.Never, + "content": Schema.Array(Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent])), + "phase": Schema.optionalKey( + Schema.Union([Schema.Literal("commentary"), Schema.Literal("final_answer"), Schema.Null]).annotate({ + "description": + "The phase of an assistant message. Use `commentary` for an intermediate assistant message and `final_answer` for the final assistant message. For follow-up requests with models like `gpt-5.3-codex` and later, preserve and resend phase on all assistant messages. Omitting it can degrade performance. Not used for user messages." + }) + ), + "role": Schema.Literal("assistant") + }).annotate({ "description": "An openrouter:experimental__search_models server tool output item" }), + Schema.Struct({ + "analysis": Schema.optionalKey(FusionAnalysisResult), + "error": Schema.optionalKey( + Schema.String.annotate({ + "description": "Error message when the fusion run did not produce an analysis result." + }) + ), + "failed_models": Schema.optionalKey( + Schema.Array(Schema.Struct({ + "error": Schema.String.annotate({ "description": "Error message describing why the model failed." }), + "model": Schema.String.annotate({ "description": "Slug of the analysis model that failed." }), + "status_code": Schema.optionalKey( + Schema.Number.annotate({ + "description": "HTTP status code from the upstream response, when available (e.g. 402, 429)." + }).check(Schema.isInt().annotate({ "expected": "an integer" })) + ) + })).annotate({ + "description": + "Models that were requested as part of the analysis panel but did not produce a response. Present when at least one requested analysis model failed. The fusion result is still usable but was produced from a degraded panel." + }) + ), + "failure_reason": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Typed failure reason when the fusion run failed. Possible values include: all_panels_failed, insufficient_credits, rate_limited, judge_not_valid_json, judge_schema_mismatch, judge_upstream_error, judge_empty_completion." + }) + ), + "id": Schema.String, + "responses": Schema.optionalKey( + Schema.Array(Schema.Struct({ "content": Schema.optionalKey(Schema.String), "model": Schema.String })) + .annotate({ + "description": + "Analysis models that produced a response in this fusion run, with each model's full panel content." + }) + ), + "sources": Schema.optionalKey( + Schema.Array(FusionSource).annotate({ + "description": + "Web pages the analysis panels and judge retrieved via web search during this fusion run, deduplicated by URL across the whole run. Present when at least one model cited a source." + }) + ), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "type": Schema.Never, + "content": Schema.Array(Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent])), + "phase": Schema.optionalKey( + Schema.Union([Schema.Literal("commentary"), Schema.Literal("final_answer"), Schema.Null]).annotate({ + "description": + "The phase of an assistant message. Use `commentary` for an intermediate assistant message and `final_answer` for the final assistant message. For follow-up requests with models like `gpt-5.3-codex` and later, preserve and resend phase on all assistant messages. Omitting it can degrade performance. Not used for user messages." + }) + ), + "role": Schema.Literal("assistant") + }).annotate({ "description": "An openrouter:fusion server tool output item" }), + Schema.Struct({ + "advice": Schema.optionalKey( + Schema.String.annotate({ + "description": "The advisor model's response (the advice text returned to the executor)." + }) + ), + "error": Schema.optionalKey( + Schema.String.annotate({ "description": "Error message when the advisor call did not produce advice." }) + ), + "id": Schema.String, + "instance_name": Schema.optionalKey(Schema.String.annotate({ + "description": + "Provider-safe function name of the specific advisor instance that produced this item (e.g. `openrouter_advisor__1`). Present only when more than one advisor tool is configured; omitted for the default single advisor. Echo this field back unchanged so the advisor's cross-request memory stays namespaced to the correct instance. This identity is positional: it is derived from the index of the advisor entry in the request `tools` array, so clients must keep the order of advisor tool entries stable across requests in a conversation. Reordering or inserting advisor entries shifts these names and causes each advisor's cross-request memory to be attributed to the wrong instance." + })), + "model": Schema.optionalKey( + Schema.String.annotate({ "description": "Slug of the advisor model that was consulted." }) + ), + "prompt": Schema.optionalKey( + Schema.String.annotate({ "description": "The prompt the executor sent to the advisor." }) + ), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "type": Schema.Never, + "content": Schema.Array(Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent])), + "phase": Schema.optionalKey( + Schema.Union([Schema.Literal("commentary"), Schema.Literal("final_answer"), Schema.Null]).annotate({ + "description": + "The phase of an assistant message. Use `commentary` for an intermediate assistant message and `final_answer` for the final assistant message. For follow-up requests with models like `gpt-5.3-codex` and later, preserve and resend phase on all assistant messages. Omitting it can degrade performance. Not used for user messages." + }) + ), + "role": Schema.Literal("assistant") + }).annotate({ "description": "An openrouter:advisor server tool output item" }), + Schema.Struct({ + "error": Schema.optionalKey( + Schema.String.annotate({ "description": "Error message when the subagent task did not produce an outcome." }) + ), + "id": Schema.String, + "instance_name": Schema.optionalKey(Schema.String.annotate({ + "description": + "Provider-safe function name of the specific subagent instance that produced this item (e.g. `openrouter_subagent__1`). Present only on items from non-default instances — the second and later subagent entries in the request `tools` array. The first (default) instance omits it, even when multiple subagents are configured. When a replayed item echoes this field back, the transcript rehydrates the call under that instance's tool. This identity is positional: it is derived from the index of the subagent entry in the request `tools` array, so keep the order of subagent entries stable across requests in a conversation." + })), + "model": Schema.optionalKey( + Schema.String.annotate({ "description": "Slug of the worker model that executed the task." }) + ), + "name": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Configured name of the subagent that executed the task (the `name` on its tool entry). Present only for named subagents; omitted for an unnamed (default) subagent." + }) + ), + "outcome": Schema.optionalKey( + Schema.String.annotate({ + "description": "The worker model's result (the outcome text returned to the delegating model)." + }) + ), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "task_description": Schema.optionalKey( + Schema.String.annotate({ "description": "The task description the delegating model sent to the worker." }) + ), + "task_name": Schema.optionalKey( + Schema.String.annotate({ "description": "The short task identifier the delegating model supplied." }) + ), + "type": Schema.Never, + "content": Schema.Array(Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent])), + "phase": Schema.optionalKey( + Schema.Union([Schema.Literal("commentary"), Schema.Literal("final_answer"), Schema.Null]).annotate({ + "description": + "The phase of an assistant message. Use `commentary` for an intermediate assistant message and `final_answer` for the final assistant message. For follow-up requests with models like `gpt-5.3-codex` and later, preserve and resend phase on all assistant messages. Omitting it can degrade performance. Not used for user messages." + }) + ), + "role": Schema.Literal("assistant") + }).annotate({ "description": "An openrouter:subagent server tool output item" }), + Schema.Struct({ + "error": Schema.optionalKey( + Schema.String.annotate({ "description": "Error message when the file operation failed." }) + ), + "file_id": Schema.optionalKey( + Schema.String.annotate({ "description": "The target file id supplied in the tool-call arguments." }) + ), + "filename": Schema.optionalKey( + Schema.String.annotate({ "description": "The target filename supplied in the tool-call arguments." }) + ), + "id": Schema.String, + "operation": Schema.optionalKey( + Schema.String.annotate({ "description": "The file operation performed (list, read, write, or edit)." }) + ), + "result": Schema.optionalKey( + Schema.String.annotate({ "description": "JSON-serialized result of the file operation." }) + ), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "type": Schema.Never, + "content": Schema.Array(Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent])), + "phase": Schema.optionalKey( + Schema.Union([Schema.Literal("commentary"), Schema.Literal("final_answer"), Schema.Null]).annotate({ + "description": + "The phase of an assistant message. Use `commentary` for an intermediate assistant message and `final_answer` for the final assistant message. For follow-up requests with models like `gpt-5.3-codex` and later, preserve and resend phase on all assistant messages. Omitting it can degrade performance. Not used for user messages." + }) + ), + "role": Schema.Literal("assistant") + }).annotate({ "description": "An openrouter:files server tool output item" }), + Schema.Struct({ + "call_id": Schema.String, + "id": Schema.String, + "input": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ), + "type": Schema.Never, + "content": Schema.Array(Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent])), + "phase": Schema.optionalKey( + Schema.Union([Schema.Literal("commentary"), Schema.Literal("final_answer"), Schema.Null]).annotate({ + "description": + "The phase of an assistant message. Use `commentary` for an intermediate assistant message and `final_answer` for the final assistant message. For follow-up requests with models like `gpt-5.3-codex` and later, preserve and resend phase on all assistant messages. Omitting it can degrade performance. Not used for user messages." + }) + ), + "role": Schema.Literal("assistant"), + "status": Schema.optionalKey(Schema.Literals(["completed", "incomplete", "in_progress"])) + }).annotate({ + "description": + "A call to a custom (freeform-grammar) tool created by the model — distinct from `function_call`. Used for tools like Codex CLI's `apply_patch` whose payload is opaque text rather than JSON arguments." + }) + ], { mode: "oneOf" }).annotate({ "description": "An output item from the response" }), + Schema.Union([ + Schema.Struct({ + "content": Schema.Array(Schema.Union([ + Schema.Struct({ + "annotations": Schema.optionalKey(Schema.Array(OpenAIResponsesAnnotation)), + "logprobs": Schema.optionalKey(Schema.Array(Schema.Struct({ + "bytes": Schema.Array(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "logprob": Schema.Number.annotate({ "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ), + "token": Schema.String, + "top_logprobs": Schema.Array( + Schema.Struct({ + "bytes": Schema.Array(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "logprob": Schema.Number.annotate({ "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ), + "token": Schema.String + }) + ) + }))), + "text": Schema.String, + "type": Schema.Never + }), + Schema.Struct({ "refusal": Schema.String, "type": Schema.Never, "text": Schema.String }) + ])), + "id": Schema.String, + "phase": Schema.optionalKey( + Schema.Union([Schema.Literal("commentary"), Schema.Literal("final_answer"), Schema.Null]).annotate({ + "description": + "The phase of an assistant message. Use `commentary` for an intermediate assistant message and `final_answer` for the final assistant message. For follow-up requests with models like `gpt-5.3-codex` and later, preserve and resend phase on all assistant messages. Omitting it can degrade performance. Not used for user messages." + }) + ), + "role": Schema.Literal("assistant"), + "status": Schema.optionalKey( + Schema.Union([Schema.Literal("completed"), Schema.Literal("incomplete"), Schema.Literal("in_progress")]) + ), + "type": Schema.Never, + "encrypted_content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "summary": Schema.Array(ReasoningSummaryText) + }).annotate({ "description": "An output message item" }), + Schema.Struct({ + "content": Schema.optionalKey( + Schema.Union([ + Schema.Array(Schema.Struct({ "text": Schema.String, "type": Schema.Literal("reasoning_text") })) + ]) + ), + "encrypted_content": Schema.optionalKey( + Schema.Union([Schema.Union([Schema.String]), Schema.Union([Schema.Null])]) + ), + "id": Schema.String, + "status": Schema.optionalKey( + Schema.Union([Schema.Literal("completed"), Schema.Literal("incomplete"), Schema.Literal("in_progress")]) + ), + "summary": Schema.Array(Schema.Struct({ "text": Schema.String, "type": Schema.Literal("summary_text") })), + "type": Schema.Literal("reasoning"), + "format": Schema.optionalKey(ReasoningFormat), + "signature": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "A signature for the reasoning content, used for verification" + }) + ) + }).annotate({ "description": "An output item containing reasoning" }), + Schema.Struct({ + "arguments": Schema.String, + "call_id": Schema.String, + "id": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ), + "status": Schema.optionalKey( + Schema.Union([Schema.Literal("completed"), Schema.Literal("incomplete"), Schema.Literal("in_progress")]) + ), + "type": Schema.Never, + "content": Schema.optionalKey(Schema.Array(ReasoningTextContent)), + "encrypted_content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "summary": Schema.Array(ReasoningSummaryText) + }), + Schema.Struct({ + "action": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "query": Schema.String, + "sources": Schema.optionalKey(Schema.Array(WebSearchSource)), + "type": Schema.Literal("search") + }), + Schema.Struct({ + "type": Schema.Literal("open_page"), + "url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }), + Schema.Struct({ "pattern": Schema.String, "type": Schema.Literal("find_in_page"), "url": Schema.String }) + ], { mode: "oneOf" }) + ), + "id": Schema.String, + "status": Schema.Union([Schema.Literal("completed"), Schema.Literal("in_progress")]), + "type": Schema.Never, + "content": Schema.optionalKey(Schema.Array(ReasoningTextContent)), + "encrypted_content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "summary": Schema.Array(ReasoningSummaryText) + }), + Schema.Struct({ + "id": Schema.String, + "queries": Schema.Array(Schema.String), + "status": Schema.Union([Schema.Literal("completed"), Schema.Literal("in_progress")]), + "type": Schema.Never, + "content": Schema.optionalKey(Schema.Array(ReasoningTextContent)), + "encrypted_content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "summary": Schema.Array(ReasoningSummaryText) + }), + Schema.Struct({ + "id": Schema.String, + "result": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "prompt": Schema.optionalKey( + Schema.String.annotate({ + "description": "The prompt (possibly rewritten) that the image was generated from." + }) + ), + "content": Schema.optionalKey(Schema.Array(ReasoningTextContent)), + "encrypted_content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "summary": Schema.Array(ReasoningSummaryText) + }), + Schema.Struct({ + "code": Schema.Union([Schema.String, Schema.Null]), + "container_id": Schema.String, + "id": Schema.String, + "outputs": Schema.Union([ + Schema.Array( + Schema.Union([ + Schema.Struct({ "type": Schema.Literal("image"), "url": Schema.String }), + Schema.Struct({ "logs": Schema.String, "type": Schema.Literal("logs") }) + ]) + ), + Schema.Null + ]), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "type": Schema.Never, + "content": Schema.optionalKey(Schema.Array(ReasoningTextContent)), + "encrypted_content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "summary": Schema.Array(ReasoningSummaryText) + }).annotate({ "description": "A code interpreter execution call with outputs" }), + Schema.Struct({ + "action": Schema.optionalKey(Schema.Json.annotate({ "expected": "JSON value" })), + "call_id": Schema.String, + "id": Schema.String, + "pending_safety_checks": Schema.Array( + Schema.Struct({ "code": Schema.String, "id": Schema.String, "message": Schema.String }) + ), + "status": Schema.Union([ + Schema.Literal("completed"), + Schema.Literal("incomplete"), + Schema.Literal("in_progress") + ]), + "type": Schema.Never, + "content": Schema.optionalKey(Schema.Array(ReasoningTextContent)), + "encrypted_content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "summary": Schema.Array(ReasoningSummaryText) + }), + Schema.Struct({ + "datetime": Schema.String.annotate({ "description": "ISO 8601 datetime string" }), + "id": Schema.String, + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "timezone": Schema.String.annotate({ "description": "IANA timezone name" }), + "type": Schema.Never, + "content": Schema.optionalKey(Schema.Array(ReasoningTextContent)), + "encrypted_content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "summary": Schema.Array(ReasoningSummaryText) + }).annotate({ "description": "An openrouter:datetime server tool output item" }), + Schema.Struct({ + "action": Schema.optionalKey( + Schema.Struct({ + "query": Schema.String, + "sources": Schema.optionalKey( + Schema.Array(Schema.Struct({ "type": Schema.Literal("url"), "url": Schema.String })) + ), + "type": Schema.Literal("search") + }).annotate({ + "description": + "The search action performed, matching OpenAI web_search_call.action shape. Includes the query the model issued and optional source URLs returned by the search provider." + }) + ), + "id": Schema.String, + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "type": Schema.Never, + "content": Schema.optionalKey(Schema.Array(ReasoningTextContent)), + "encrypted_content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "summary": Schema.Array(ReasoningSummaryText) + }).annotate({ "description": "An openrouter:web_search server tool output item" }), + Schema.Struct({ + "code": Schema.optionalKey(Schema.String), + "exitCode": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "id": Schema.String, + "language": Schema.optionalKey(Schema.String), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "stderr": Schema.optionalKey(Schema.String), + "stdout": Schema.optionalKey(Schema.String), + "type": Schema.Never, + "content": Schema.optionalKey(Schema.Array(ReasoningTextContent)), + "encrypted_content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "summary": Schema.Array(ReasoningSummaryText) + }).annotate({ "description": "An openrouter:code_interpreter server tool output item" }), + Schema.Struct({ + "id": Schema.String, + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "type": Schema.Never, + "content": Schema.optionalKey(Schema.Array(ReasoningTextContent)), + "encrypted_content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "summary": Schema.Array(ReasoningSummaryText) + }).annotate({ "description": "An openrouter:file_search server tool output item" }), + Schema.Struct({ + "id": Schema.String, + "imageB64": Schema.optionalKey(Schema.String), + "imageUrl": Schema.optionalKey(Schema.String), + "prompt": Schema.optionalKey( + Schema.String.annotate({ + "description": "The prompt (possibly rewritten) that the image was generated from." + }) + ), + "result": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": + "The generated image as a base64-encoded string or URL, matching OpenAI image_generation_call format" + }) + ), + "revisedPrompt": Schema.optionalKey(Schema.String), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "type": Schema.Never, + "content": Schema.optionalKey(Schema.Array(ReasoningTextContent)), + "encrypted_content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "summary": Schema.Array(ReasoningSummaryText) + }).annotate({ "description": "An openrouter:image_generation server tool output item" }), + Schema.Struct({ + "action": Schema.optionalKey(Schema.String), + "id": Schema.String, + "screenshotB64": Schema.optionalKey(Schema.String), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "type": Schema.Never, + "content": Schema.optionalKey(Schema.Array(ReasoningTextContent)), + "encrypted_content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "summary": Schema.Array(ReasoningSummaryText) + }).annotate({ "description": "An openrouter:browser_use server tool output item" }), + Schema.Struct({ + "arguments": Schema.optionalKey( + Schema.String.annotate({ "description": "The raw tool-call arguments string as emitted by the model." }) + ), + "call_id": Schema.optionalKey( + Schema.String.annotate({ "description": "The model-generated tool call id from the originating turn." }) + ), + "command": Schema.optionalKey(Schema.String), + "exitCode": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "id": Schema.String, + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "stderr": Schema.optionalKey(Schema.String), + "stdout": Schema.optionalKey(Schema.String), + "type": Schema.Never, + "content": Schema.optionalKey(Schema.Array(ReasoningTextContent)), + "encrypted_content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "summary": Schema.Array(ReasoningSummaryText) + }).annotate({ "description": "An openrouter:bash server tool output item" }), + Schema.Struct({ + "command": Schema.optionalKey(Schema.Literals(["view", "create", "str_replace", "insert"])), + "filePath": Schema.optionalKey(Schema.String), + "id": Schema.String, + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "type": Schema.Never, + "content": Schema.optionalKey(Schema.Array(ReasoningTextContent)), + "encrypted_content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "summary": Schema.Array(ReasoningSummaryText) + }).annotate({ "description": "An openrouter:text_editor server tool output item" }), + Schema.Struct({ + "call_id": Schema.optionalKey(Schema.String), + "id": Schema.String, + "operation": Schema.optionalKey(ApplyPatchCallOperation), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "type": Schema.Never, + "content": Schema.optionalKey(Schema.Array(ReasoningTextContent)), + "encrypted_content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "summary": Schema.Array(ReasoningSummaryText) + }).annotate({ + "description": + "An openrouter:apply_patch server tool output item. The turn halts when validation succeeds so the client can apply the patch and echo an `apply_patch_call_output` on the next turn." + }), + Schema.Struct({ + "call_id": Schema.String, + "id": Schema.String, + "operation": ApplyPatchCallOperation, + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]).annotate({ + "description": "Lifecycle state of an `apply_patch_call` output item." + }), + "type": Schema.Never, + "content": Schema.optionalKey(Schema.Array(ReasoningTextContent)), + "encrypted_content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "summary": Schema.Array(ReasoningSummaryText) + }).annotate({ + "description": + "A native `apply_patch_call` output item matching OpenAI's Responses API shape. Emitted when the client requested the `apply_patch` shorthand." + }), + Schema.Struct({ + "action": Schema.optionalKey(Schema.Struct({ + "commands": Schema.Array(Schema.String), + "max_output_length": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]), + "timeout_ms": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]) + })), + "call_id": Schema.String, + "id": Schema.String, + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]).annotate({ "description": "Status of a shell call or its output." }), + "type": Schema.Never, + "content": Schema.optionalKey(Schema.Array(ReasoningTextContent)), + "encrypted_content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "summary": Schema.Array(ReasoningSummaryText) + }).annotate({ + "description": + "A native `shell_call` output item matching OpenAI's Responses API shape. Emitted for the sandbox-backed `shell` tool." + }), + Schema.Struct({ + "call_id": Schema.String, + "id": Schema.String, + "max_output_length": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]) + ), + "output": Schema.Array(Schema.Struct({ + "outcome": Schema.Union([ + Schema.Struct({ + "exit_code": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("exit") + }), + Schema.Struct({ "type": Schema.Literal("timeout") }) + ], { mode: "oneOf" }), + "stderr": Schema.String, + "stdout": Schema.String + })), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]).annotate({ "description": "Status of a shell call or its output." }), + "type": Schema.Never, + "content": Schema.optionalKey(Schema.Array(ReasoningTextContent)), + "encrypted_content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "summary": Schema.Array(ReasoningSummaryText) + }).annotate({ + "description": + "A native `shell_call_output` item matching OpenAI's Responses API shape. Carries per-command stdout, stderr, and the exit/timeout outcome." + }), + Schema.Struct({ + "content": Schema.optionalKey(Schema.Never), + "error": Schema.optionalKey( + Schema.String.annotate({ "description": "The error message if the fetch failed." }) + ), + "httpStatus": Schema.optionalKey( + Schema.Number.annotate({ "description": "The HTTP status code returned by the upstream URL fetch." }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ) + ), + "id": Schema.String, + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "title": Schema.optionalKey(Schema.String), + "type": Schema.Never, + "url": Schema.optionalKey(Schema.String), + "encrypted_content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "summary": Schema.Array(ReasoningSummaryText) + }).annotate({ "description": "An openrouter:web_fetch server tool output item" }), + Schema.Struct({ + "id": Schema.String, + "query": Schema.optionalKey(Schema.String), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "type": Schema.Never, + "content": Schema.optionalKey(Schema.Array(ReasoningTextContent)), + "encrypted_content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "summary": Schema.Array(ReasoningSummaryText) + }).annotate({ "description": "An openrouter:tool_search server tool output item" }), + Schema.Struct({ + "action": Schema.optionalKey(Schema.Literals(["read", "write", "delete"])), + "id": Schema.String, + "key": Schema.optionalKey(Schema.String), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "type": Schema.Never, + "value": Schema.optionalKey(Schema.Json.annotate({ "expected": "JSON value" })), + "content": Schema.optionalKey(Schema.Array(ReasoningTextContent)), + "encrypted_content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "summary": Schema.Array(ReasoningSummaryText) + }).annotate({ "description": "An openrouter:memory server tool output item" }), + Schema.Struct({ + "id": Schema.String, + "serverLabel": Schema.optionalKey(Schema.String), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "toolName": Schema.optionalKey(Schema.String), + "type": Schema.Never, + "content": Schema.optionalKey(Schema.Array(ReasoningTextContent)), + "encrypted_content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "summary": Schema.Array(ReasoningSummaryText) + }).annotate({ "description": "An openrouter:mcp server tool output item" }), + Schema.Struct({ + "arguments": Schema.optionalKey( + Schema.String.annotate({ + "description": "The JSON arguments submitted to the search tool (e.g. {\"query\":\"Claude\"})" + }) + ), + "id": Schema.String, + "query": Schema.optionalKey(Schema.String), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "type": Schema.Never, + "content": Schema.optionalKey(Schema.Array(ReasoningTextContent)), + "encrypted_content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "summary": Schema.Array(ReasoningSummaryText) + }).annotate({ "description": "An openrouter:experimental__search_models server tool output item" }), + Schema.Struct({ + "analysis": Schema.optionalKey(FusionAnalysisResult), + "error": Schema.optionalKey( + Schema.String.annotate({ + "description": "Error message when the fusion run did not produce an analysis result." + }) + ), + "failed_models": Schema.optionalKey( + Schema.Array(Schema.Struct({ + "error": Schema.String.annotate({ "description": "Error message describing why the model failed." }), + "model": Schema.String.annotate({ "description": "Slug of the analysis model that failed." }), + "status_code": Schema.optionalKey( + Schema.Number.annotate({ + "description": "HTTP status code from the upstream response, when available (e.g. 402, 429)." + }).check(Schema.isInt().annotate({ "expected": "an integer" })) + ) + })).annotate({ + "description": + "Models that were requested as part of the analysis panel but did not produce a response. Present when at least one requested analysis model failed. The fusion result is still usable but was produced from a degraded panel." + }) + ), + "failure_reason": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Typed failure reason when the fusion run failed. Possible values include: all_panels_failed, insufficient_credits, rate_limited, judge_not_valid_json, judge_schema_mismatch, judge_upstream_error, judge_empty_completion." + }) + ), + "id": Schema.String, + "responses": Schema.optionalKey( + Schema.Array(Schema.Struct({ "content": Schema.optionalKey(Schema.String), "model": Schema.String })) + .annotate({ + "description": + "Analysis models that produced a response in this fusion run, with each model's full panel content." + }) + ), + "sources": Schema.optionalKey( + Schema.Array(FusionSource).annotate({ + "description": + "Web pages the analysis panels and judge retrieved via web search during this fusion run, deduplicated by URL across the whole run. Present when at least one model cited a source." + }) + ), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "type": Schema.Never, + "content": Schema.optionalKey(Schema.Array(ReasoningTextContent)), + "encrypted_content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "summary": Schema.Array(ReasoningSummaryText) + }).annotate({ "description": "An openrouter:fusion server tool output item" }), + Schema.Struct({ + "advice": Schema.optionalKey( + Schema.String.annotate({ + "description": "The advisor model's response (the advice text returned to the executor)." + }) + ), + "error": Schema.optionalKey( + Schema.String.annotate({ "description": "Error message when the advisor call did not produce advice." }) + ), + "id": Schema.String, + "instance_name": Schema.optionalKey(Schema.String.annotate({ + "description": + "Provider-safe function name of the specific advisor instance that produced this item (e.g. `openrouter_advisor__1`). Present only when more than one advisor tool is configured; omitted for the default single advisor. Echo this field back unchanged so the advisor's cross-request memory stays namespaced to the correct instance. This identity is positional: it is derived from the index of the advisor entry in the request `tools` array, so clients must keep the order of advisor tool entries stable across requests in a conversation. Reordering or inserting advisor entries shifts these names and causes each advisor's cross-request memory to be attributed to the wrong instance." + })), + "model": Schema.optionalKey( + Schema.String.annotate({ "description": "Slug of the advisor model that was consulted." }) + ), + "prompt": Schema.optionalKey( + Schema.String.annotate({ "description": "The prompt the executor sent to the advisor." }) + ), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "type": Schema.Never, + "content": Schema.optionalKey(Schema.Array(ReasoningTextContent)), + "encrypted_content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "summary": Schema.Array(ReasoningSummaryText) + }).annotate({ "description": "An openrouter:advisor server tool output item" }), + Schema.Struct({ + "error": Schema.optionalKey( + Schema.String.annotate({ "description": "Error message when the subagent task did not produce an outcome." }) + ), + "id": Schema.String, + "instance_name": Schema.optionalKey(Schema.String.annotate({ + "description": + "Provider-safe function name of the specific subagent instance that produced this item (e.g. `openrouter_subagent__1`). Present only on items from non-default instances — the second and later subagent entries in the request `tools` array. The first (default) instance omits it, even when multiple subagents are configured. When a replayed item echoes this field back, the transcript rehydrates the call under that instance's tool. This identity is positional: it is derived from the index of the subagent entry in the request `tools` array, so keep the order of subagent entries stable across requests in a conversation." + })), + "model": Schema.optionalKey( + Schema.String.annotate({ "description": "Slug of the worker model that executed the task." }) + ), + "name": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Configured name of the subagent that executed the task (the `name` on its tool entry). Present only for named subagents; omitted for an unnamed (default) subagent." + }) + ), + "outcome": Schema.optionalKey( + Schema.String.annotate({ + "description": "The worker model's result (the outcome text returned to the delegating model)." + }) + ), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "task_description": Schema.optionalKey( + Schema.String.annotate({ "description": "The task description the delegating model sent to the worker." }) + ), + "task_name": Schema.optionalKey( + Schema.String.annotate({ "description": "The short task identifier the delegating model supplied." }) + ), + "type": Schema.Never, + "content": Schema.optionalKey(Schema.Array(ReasoningTextContent)), + "encrypted_content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "summary": Schema.Array(ReasoningSummaryText) + }).annotate({ "description": "An openrouter:subagent server tool output item" }), + Schema.Struct({ + "error": Schema.optionalKey( + Schema.String.annotate({ "description": "Error message when the file operation failed." }) + ), + "file_id": Schema.optionalKey( + Schema.String.annotate({ "description": "The target file id supplied in the tool-call arguments." }) + ), + "filename": Schema.optionalKey( + Schema.String.annotate({ "description": "The target filename supplied in the tool-call arguments." }) + ), + "id": Schema.String, + "operation": Schema.optionalKey( + Schema.String.annotate({ "description": "The file operation performed (list, read, write, or edit)." }) + ), + "result": Schema.optionalKey( + Schema.String.annotate({ "description": "JSON-serialized result of the file operation." }) + ), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "type": Schema.Never, + "content": Schema.optionalKey(Schema.Array(ReasoningTextContent)), + "encrypted_content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "summary": Schema.Array(ReasoningSummaryText) + }).annotate({ "description": "An openrouter:files server tool output item" }), + Schema.Struct({ + "call_id": Schema.String, + "id": Schema.String, + "input": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ), + "type": Schema.Never, + "content": Schema.optionalKey(Schema.Array(ReasoningTextContent)), + "encrypted_content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "status": Schema.optionalKey(Schema.Literals(["completed", "incomplete", "in_progress"])), + "summary": Schema.Array(ReasoningSummaryText) + }).annotate({ + "description": + "A call to a custom (freeform-grammar) tool created by the model — distinct from `function_call`. Used for tools like Codex CLI's `apply_patch` whose payload is opaque text rather than JSON arguments." + }) + ], { mode: "oneOf" }).annotate({ "description": "An output item from the response" }), + Schema.Union([ + Schema.Struct({ + "content": Schema.Array(Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent])), + "id": Schema.String, + "phase": Schema.optionalKey( + Schema.Union([Schema.Literal("commentary"), Schema.Literal("final_answer"), Schema.Null]).annotate({ + "description": + "The phase of an assistant message. Use `commentary` for an intermediate assistant message and `final_answer` for the final assistant message. For follow-up requests with models like `gpt-5.3-codex` and later, preserve and resend phase on all assistant messages. Omitting it can degrade performance. Not used for user messages." + }) + ), + "role": Schema.Literal("assistant"), + "status": Schema.optionalKey( + Schema.Union([Schema.Literal("completed"), Schema.Literal("incomplete"), Schema.Literal("in_progress")]) + ), + "type": Schema.Never, + "arguments": Schema.String, + "call_id": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }).annotate({ "description": "An output message item" }), + Schema.Struct({ + "content": Schema.optionalKey( + Schema.Union([ + Schema.Array(Schema.Struct({ "text": Schema.String, "type": Schema.Literal("reasoning_text") })) + ]) + ), + "encrypted_content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "id": Schema.String, + "status": Schema.optionalKey( + Schema.Union([Schema.Literal("completed"), Schema.Literal("incomplete"), Schema.Literal("in_progress")]) + ), + "summary": Schema.Array(ReasoningSummaryText), + "type": Schema.Never, + "format": Schema.optionalKey(ReasoningFormat), + "signature": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "A signature for the reasoning content, used for verification" + }) + ), + "arguments": Schema.String, + "call_id": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }).annotate({ "description": "An output item containing reasoning" }), + Schema.Struct({ + "arguments": Schema.String, + "call_id": Schema.String, + "id": Schema.optionalKey(Schema.String), + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ), + "status": Schema.optionalKey( + Schema.Union([Schema.Literal("completed"), Schema.Literal("incomplete"), Schema.Literal("in_progress")]) + ), + "type": Schema.Literal("function_call") + }), + Schema.Struct({ + "action": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "query": Schema.String, + "sources": Schema.optionalKey(Schema.Array(WebSearchSource)), + "type": Schema.Literal("search") + }), + Schema.Struct({ + "type": Schema.Literal("open_page"), + "url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }), + Schema.Struct({ "pattern": Schema.String, "type": Schema.Literal("find_in_page"), "url": Schema.String }) + ], { mode: "oneOf" }) + ), + "id": Schema.String, + "status": Schema.Union([Schema.Literal("completed"), Schema.Literal("in_progress")]), + "type": Schema.Never, + "arguments": Schema.String, + "call_id": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }), + Schema.Struct({ + "id": Schema.String, + "queries": Schema.Array(Schema.String), + "status": Schema.Union([Schema.Literal("completed"), Schema.Literal("in_progress")]), + "type": Schema.Never, + "arguments": Schema.String, + "call_id": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }), + Schema.Struct({ + "id": Schema.String, + "result": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "prompt": Schema.optionalKey( + Schema.String.annotate({ + "description": "The prompt (possibly rewritten) that the image was generated from." + }) + ), + "arguments": Schema.String, + "call_id": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }), + Schema.Struct({ + "code": Schema.Union([Schema.String, Schema.Null]), + "container_id": Schema.String, + "id": Schema.String, + "outputs": Schema.Union([ + Schema.Array( + Schema.Union([ + Schema.Struct({ "type": Schema.Literal("image"), "url": Schema.String }), + Schema.Struct({ "logs": Schema.String, "type": Schema.Literal("logs") }) + ]) + ), + Schema.Null + ]), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "type": Schema.Never, + "arguments": Schema.String, + "call_id": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }).annotate({ "description": "A code interpreter execution call with outputs" }), + Schema.Struct({ + "action": Schema.optionalKey(Schema.Json.annotate({ "expected": "JSON value" })), + "call_id": Schema.String, + "id": Schema.optionalKey(Schema.String), + "pending_safety_checks": Schema.Array( + Schema.Struct({ "code": Schema.String, "id": Schema.String, "message": Schema.String }) + ), + "status": Schema.Union([ + Schema.Literal("completed"), + Schema.Literal("incomplete"), + Schema.Literal("in_progress") + ]), + "type": Schema.Never, + "arguments": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }), + Schema.Struct({ + "datetime": Schema.String.annotate({ "description": "ISO 8601 datetime string" }), + "id": Schema.optionalKey(Schema.String), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "timezone": Schema.String.annotate({ "description": "IANA timezone name" }), + "type": Schema.Never, + "arguments": Schema.String, + "call_id": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }).annotate({ "description": "An openrouter:datetime server tool output item" }), + Schema.Struct({ + "action": Schema.optionalKey( + Schema.Struct({ + "query": Schema.String, + "sources": Schema.optionalKey( + Schema.Array(Schema.Struct({ "type": Schema.Literal("url"), "url": Schema.String })) + ), + "type": Schema.Literal("search") + }).annotate({ + "description": + "The search action performed, matching OpenAI web_search_call.action shape. Includes the query the model issued and optional source URLs returned by the search provider." + }) + ), + "id": Schema.optionalKey(Schema.String), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "type": Schema.Never, + "arguments": Schema.String, + "call_id": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }).annotate({ "description": "An openrouter:web_search server tool output item" }), + Schema.Struct({ + "code": Schema.optionalKey(Schema.String), + "exitCode": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "id": Schema.optionalKey(Schema.String), + "language": Schema.optionalKey(Schema.String), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "stderr": Schema.optionalKey(Schema.String), + "stdout": Schema.optionalKey(Schema.String), + "type": Schema.Never, + "arguments": Schema.String, + "call_id": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }).annotate({ "description": "An openrouter:code_interpreter server tool output item" }), + Schema.Struct({ + "id": Schema.optionalKey(Schema.String), + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "type": Schema.Never, + "arguments": Schema.String, + "call_id": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }).annotate({ "description": "An openrouter:file_search server tool output item" }), + Schema.Struct({ + "id": Schema.optionalKey(Schema.String), + "imageB64": Schema.optionalKey(Schema.String), + "imageUrl": Schema.optionalKey(Schema.String), + "prompt": Schema.optionalKey( + Schema.String.annotate({ + "description": "The prompt (possibly rewritten) that the image was generated from." + }) + ), + "result": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": + "The generated image as a base64-encoded string or URL, matching OpenAI image_generation_call format" + }) + ), + "revisedPrompt": Schema.optionalKey(Schema.String), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "type": Schema.Never, + "arguments": Schema.String, + "call_id": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }).annotate({ "description": "An openrouter:image_generation server tool output item" }), + Schema.Struct({ + "action": Schema.optionalKey(Schema.String), + "id": Schema.optionalKey(Schema.String), + "screenshotB64": Schema.optionalKey(Schema.String), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "type": Schema.Never, + "arguments": Schema.String, + "call_id": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }).annotate({ "description": "An openrouter:browser_use server tool output item" }), + Schema.Struct({ + "arguments": Schema.String.annotate({ + "description": "The raw tool-call arguments string as emitted by the model." + }), + "call_id": Schema.String.annotate({ + "description": "The model-generated tool call id from the originating turn." + }), + "command": Schema.optionalKey(Schema.String), + "exitCode": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "id": Schema.optionalKey(Schema.String), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "stderr": Schema.optionalKey(Schema.String), + "stdout": Schema.optionalKey(Schema.String), + "type": Schema.Never, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }).annotate({ "description": "An openrouter:bash server tool output item" }), + Schema.Struct({ + "command": Schema.optionalKey(Schema.Literals(["view", "create", "str_replace", "insert"])), + "filePath": Schema.optionalKey(Schema.String), + "id": Schema.optionalKey(Schema.String), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "type": Schema.Never, + "arguments": Schema.String, + "call_id": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }).annotate({ "description": "An openrouter:text_editor server tool output item" }), + Schema.Struct({ + "call_id": Schema.String, + "id": Schema.optionalKey(Schema.String), + "operation": Schema.optionalKey(ApplyPatchCallOperation), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "type": Schema.Never, + "arguments": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }).annotate({ + "description": + "An openrouter:apply_patch server tool output item. The turn halts when validation succeeds so the client can apply the patch and echo an `apply_patch_call_output` on the next turn." + }), + Schema.Struct({ + "call_id": Schema.String, + "id": Schema.String, + "operation": ApplyPatchCallOperation, + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]).annotate({ + "description": "Lifecycle state of an `apply_patch_call` output item." + }), + "type": Schema.Never, + "arguments": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }).annotate({ + "description": + "A native `apply_patch_call` output item matching OpenAI's Responses API shape. Emitted when the client requested the `apply_patch` shorthand." + }), + Schema.Struct({ + "action": Schema.optionalKey(Schema.Struct({ + "commands": Schema.Array(Schema.String), + "max_output_length": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]), + "timeout_ms": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]) + })), + "call_id": Schema.String, + "id": Schema.String, + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]).annotate({ "description": "Status of a shell call or its output." }), + "type": Schema.Never, + "arguments": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }).annotate({ + "description": + "A native `shell_call` output item matching OpenAI's Responses API shape. Emitted for the sandbox-backed `shell` tool." + }), + Schema.Struct({ + "call_id": Schema.String, + "id": Schema.String, + "max_output_length": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]) + ), + "output": Schema.Array(Schema.Struct({ + "outcome": Schema.Union([ + Schema.Struct({ + "exit_code": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("exit") + }), + Schema.Struct({ "type": Schema.Literal("timeout") }) + ], { mode: "oneOf" }), + "stderr": Schema.String, + "stdout": Schema.String + })), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]).annotate({ "description": "Status of a shell call or its output." }), + "type": Schema.Never, + "arguments": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }).annotate({ + "description": + "A native `shell_call_output` item matching OpenAI's Responses API shape. Carries per-command stdout, stderr, and the exit/timeout outcome." + }), + Schema.Struct({ + "content": Schema.optionalKey(Schema.String), + "error": Schema.optionalKey( + Schema.String.annotate({ "description": "The error message if the fetch failed." }) + ), + "httpStatus": Schema.optionalKey( + Schema.Number.annotate({ "description": "The HTTP status code returned by the upstream URL fetch." }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ) + ), + "id": Schema.optionalKey(Schema.String), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "title": Schema.optionalKey(Schema.String), + "type": Schema.Never, + "url": Schema.optionalKey(Schema.String), + "arguments": Schema.String, + "call_id": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }).annotate({ "description": "An openrouter:web_fetch server tool output item" }), + Schema.Struct({ + "id": Schema.optionalKey(Schema.String), + "query": Schema.optionalKey(Schema.String), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "type": Schema.Never, + "arguments": Schema.String, + "call_id": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }).annotate({ "description": "An openrouter:tool_search server tool output item" }), + Schema.Struct({ + "action": Schema.optionalKey(Schema.Literals(["read", "write", "delete"])), + "id": Schema.optionalKey(Schema.String), + "key": Schema.optionalKey(Schema.String), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "type": Schema.Never, + "value": Schema.optionalKey(Schema.Json.annotate({ "expected": "JSON value" })), + "arguments": Schema.String, + "call_id": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }).annotate({ "description": "An openrouter:memory server tool output item" }), + Schema.Struct({ + "id": Schema.optionalKey(Schema.String), + "serverLabel": Schema.optionalKey(Schema.String), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "toolName": Schema.optionalKey(Schema.String), + "type": Schema.Never, + "arguments": Schema.String, + "call_id": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }).annotate({ "description": "An openrouter:mcp server tool output item" }), + Schema.Struct({ + "arguments": Schema.String.annotate({ + "description": "The JSON arguments submitted to the search tool (e.g. {\"query\":\"Claude\"})" + }), + "id": Schema.optionalKey(Schema.String), + "query": Schema.optionalKey(Schema.String), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "type": Schema.Never, + "call_id": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }).annotate({ "description": "An openrouter:experimental__search_models server tool output item" }), + Schema.Struct({ + "analysis": Schema.optionalKey(FusionAnalysisResult), + "error": Schema.optionalKey( + Schema.String.annotate({ + "description": "Error message when the fusion run did not produce an analysis result." + }) + ), + "failed_models": Schema.optionalKey( + Schema.Array(Schema.Struct({ + "error": Schema.String.annotate({ "description": "Error message describing why the model failed." }), + "model": Schema.String.annotate({ "description": "Slug of the analysis model that failed." }), + "status_code": Schema.optionalKey( + Schema.Number.annotate({ + "description": "HTTP status code from the upstream response, when available (e.g. 402, 429)." + }).check(Schema.isInt().annotate({ "expected": "an integer" })) + ) + })).annotate({ + "description": + "Models that were requested as part of the analysis panel but did not produce a response. Present when at least one requested analysis model failed. The fusion result is still usable but was produced from a degraded panel." + }) + ), + "failure_reason": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Typed failure reason when the fusion run failed. Possible values include: all_panels_failed, insufficient_credits, rate_limited, judge_not_valid_json, judge_schema_mismatch, judge_upstream_error, judge_empty_completion." + }) + ), + "id": Schema.optionalKey(Schema.String), + "responses": Schema.optionalKey( + Schema.Array(Schema.Struct({ "content": Schema.optionalKey(Schema.String), "model": Schema.String })) + .annotate({ + "description": + "Analysis models that produced a response in this fusion run, with each model's full panel content." + }) + ), + "sources": Schema.optionalKey( + Schema.Array(FusionSource).annotate({ + "description": + "Web pages the analysis panels and judge retrieved via web search during this fusion run, deduplicated by URL across the whole run. Present when at least one model cited a source." + }) + ), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "type": Schema.Never, + "arguments": Schema.String, + "call_id": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }).annotate({ "description": "An openrouter:fusion server tool output item" }), + Schema.Struct({ + "advice": Schema.optionalKey( + Schema.String.annotate({ + "description": "The advisor model's response (the advice text returned to the executor)." + }) + ), + "error": Schema.optionalKey( + Schema.String.annotate({ "description": "Error message when the advisor call did not produce advice." }) + ), + "id": Schema.optionalKey(Schema.String), + "instance_name": Schema.optionalKey(Schema.String.annotate({ + "description": + "Provider-safe function name of the specific advisor instance that produced this item (e.g. `openrouter_advisor__1`). Present only when more than one advisor tool is configured; omitted for the default single advisor. Echo this field back unchanged so the advisor's cross-request memory stays namespaced to the correct instance. This identity is positional: it is derived from the index of the advisor entry in the request `tools` array, so clients must keep the order of advisor tool entries stable across requests in a conversation. Reordering or inserting advisor entries shifts these names and causes each advisor's cross-request memory to be attributed to the wrong instance." + })), + "model": Schema.optionalKey( + Schema.String.annotate({ "description": "Slug of the advisor model that was consulted." }) + ), + "prompt": Schema.optionalKey( + Schema.String.annotate({ "description": "The prompt the executor sent to the advisor." }) + ), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "type": Schema.Never, + "arguments": Schema.String, + "call_id": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }).annotate({ "description": "An openrouter:advisor server tool output item" }), + Schema.Struct({ + "error": Schema.optionalKey( + Schema.String.annotate({ "description": "Error message when the subagent task did not produce an outcome." }) + ), + "id": Schema.optionalKey(Schema.String), + "instance_name": Schema.optionalKey(Schema.String.annotate({ + "description": + "Provider-safe function name of the specific subagent instance that produced this item (e.g. `openrouter_subagent__1`). Present only on items from non-default instances — the second and later subagent entries in the request `tools` array. The first (default) instance omits it, even when multiple subagents are configured. When a replayed item echoes this field back, the transcript rehydrates the call under that instance's tool. This identity is positional: it is derived from the index of the subagent entry in the request `tools` array, so keep the order of subagent entries stable across requests in a conversation." + })), + "model": Schema.optionalKey( + Schema.String.annotate({ "description": "Slug of the worker model that executed the task." }) + ), + "name": Schema.String.annotate({ + "description": + "Configured name of the subagent that executed the task (the `name` on its tool entry). Present only for named subagents; omitted for an unnamed (default) subagent." + }), + "outcome": Schema.optionalKey( + Schema.String.annotate({ + "description": "The worker model's result (the outcome text returned to the delegating model)." + }) + ), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "task_description": Schema.optionalKey( + Schema.String.annotate({ "description": "The task description the delegating model sent to the worker." }) + ), + "task_name": Schema.optionalKey( + Schema.String.annotate({ "description": "The short task identifier the delegating model supplied." }) + ), + "type": Schema.Never, + "arguments": Schema.String, + "call_id": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }).annotate({ "description": "An openrouter:subagent server tool output item" }), + Schema.Struct({ + "error": Schema.optionalKey( + Schema.String.annotate({ "description": "Error message when the file operation failed." }) + ), + "file_id": Schema.optionalKey( + Schema.String.annotate({ "description": "The target file id supplied in the tool-call arguments." }) + ), + "filename": Schema.optionalKey( + Schema.String.annotate({ "description": "The target filename supplied in the tool-call arguments." }) + ), + "id": Schema.optionalKey(Schema.String), + "operation": Schema.optionalKey( + Schema.String.annotate({ "description": "The file operation performed (list, read, write, or edit)." }) + ), + "result": Schema.optionalKey( + Schema.String.annotate({ "description": "JSON-serialized result of the file operation." }) + ), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "type": Schema.Never, + "arguments": Schema.String, + "call_id": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }).annotate({ "description": "An openrouter:files server tool output item" }), + Schema.Struct({ + "call_id": Schema.String, + "id": Schema.optionalKey(Schema.String), + "input": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ), + "type": Schema.Never, + "arguments": Schema.String, + "status": Schema.optionalKey(Schema.Literals(["completed", "incomplete", "in_progress"])) + }).annotate({ + "description": + "A call to a custom (freeform-grammar) tool created by the model — distinct from `function_call`. Used for tools like Codex CLI's `apply_patch` whose payload is opaque text rather than JSON arguments." + }) + ], { mode: "oneOf" }).annotate({ "description": "An output item from the response" }), + Schema.Union([ + Schema.Struct({ + "content": Schema.Array(Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent])), + "id": Schema.String, + "phase": Schema.optionalKey( + Schema.Union([Schema.Literal("commentary"), Schema.Literal("final_answer"), Schema.Null]).annotate({ + "description": + "The phase of an assistant message. Use `commentary` for an intermediate assistant message and `final_answer` for the final assistant message. For follow-up requests with models like `gpt-5.3-codex` and later, preserve and resend phase on all assistant messages. Omitting it can degrade performance. Not used for user messages." + }) + ), + "role": Schema.Literal("assistant"), + "status": Schema.optionalKey(Schema.Literals(["completed", "incomplete", "in_progress"])), + "type": Schema.Never, + "call_id": Schema.String, + "input": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }).annotate({ "description": "An output message item" }), + Schema.Struct({ + "content": Schema.optionalKey( + Schema.Union([ + Schema.Array(Schema.Struct({ "text": Schema.String, "type": Schema.Literal("reasoning_text") })) + ]) + ), + "encrypted_content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "id": Schema.String, + "status": Schema.optionalKey(Schema.Literals(["completed", "incomplete", "in_progress"])), + "summary": Schema.Array(ReasoningSummaryText), + "type": Schema.Never, + "format": Schema.optionalKey(ReasoningFormat), + "signature": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "A signature for the reasoning content, used for verification" + }) + ), + "call_id": Schema.String, + "input": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }).annotate({ "description": "An output item containing reasoning" }), + Schema.Struct({ + "arguments": Schema.String, + "call_id": Schema.String, + "id": Schema.optionalKey(Schema.String), + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ), + "status": Schema.optionalKey(Schema.Literals(["completed", "incomplete", "in_progress"])), + "type": Schema.Never, + "input": Schema.String + }), + Schema.Struct({ + "action": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "query": Schema.String, + "sources": Schema.optionalKey(Schema.Array(WebSearchSource)), + "type": Schema.Literal("search") + }), + Schema.Struct({ + "type": Schema.Literal("open_page"), + "url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }), + Schema.Struct({ "pattern": Schema.String, "type": Schema.Literal("find_in_page"), "url": Schema.String }) + ], { mode: "oneOf" }) + ), + "id": Schema.String, + "status": WebSearchStatus, + "type": Schema.Never, + "call_id": Schema.String, + "input": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }), + Schema.Struct({ + "id": Schema.String, + "queries": Schema.Array(Schema.String), + "status": WebSearchStatus, + "type": Schema.Never, + "call_id": Schema.String, + "input": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }), + Schema.Struct({ + "id": Schema.String, + "result": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "status": ImageGenerationStatus, + "type": Schema.Never, + "prompt": Schema.optionalKey( + Schema.String.annotate({ + "description": "The prompt (possibly rewritten) that the image was generated from." + }) + ), + "call_id": Schema.String, + "input": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }), + Schema.Struct({ + "code": Schema.Union([Schema.String, Schema.Null]), + "container_id": Schema.String, + "id": Schema.String, + "outputs": Schema.Union([ + Schema.Array( + Schema.Union([ + Schema.Struct({ "type": Schema.Literal("image"), "url": Schema.String }), + Schema.Struct({ "logs": Schema.String, "type": Schema.Literal("logs") }) + ]) + ), + Schema.Null + ]), + "status": ToolCallStatus, + "type": Schema.Never, + "call_id": Schema.String, + "input": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }).annotate({ "description": "A code interpreter execution call with outputs" }), + Schema.Struct({ + "action": Schema.optionalKey(Schema.Json.annotate({ "expected": "JSON value" })), + "call_id": Schema.String, + "id": Schema.optionalKey(Schema.String), + "pending_safety_checks": Schema.Array( + Schema.Struct({ "code": Schema.String, "id": Schema.String, "message": Schema.String }) + ), + "status": Schema.Literals(["completed", "incomplete", "in_progress"]), + "type": Schema.Never, + "input": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }), + Schema.Struct({ + "datetime": Schema.String.annotate({ "description": "ISO 8601 datetime string" }), + "id": Schema.optionalKey(Schema.String), + "status": ToolCallStatus, + "timezone": Schema.String.annotate({ "description": "IANA timezone name" }), + "type": Schema.Never, + "call_id": Schema.String, + "input": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }).annotate({ "description": "An openrouter:datetime server tool output item" }), + Schema.Struct({ + "action": Schema.optionalKey( + Schema.Struct({ + "query": Schema.String, + "sources": Schema.optionalKey( + Schema.Array(Schema.Struct({ "type": Schema.Literal("url"), "url": Schema.String })) + ), + "type": Schema.Literal("search") + }).annotate({ + "description": + "The search action performed, matching OpenAI web_search_call.action shape. Includes the query the model issued and optional source URLs returned by the search provider." + }) + ), + "id": Schema.optionalKey(Schema.String), + "status": ToolCallStatus, + "type": Schema.Never, + "call_id": Schema.String, + "input": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }).annotate({ "description": "An openrouter:web_search server tool output item" }), + Schema.Struct({ + "code": Schema.optionalKey(Schema.String), + "exitCode": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "id": Schema.optionalKey(Schema.String), + "language": Schema.optionalKey(Schema.String), + "status": ToolCallStatus, + "stderr": Schema.optionalKey(Schema.String), + "stdout": Schema.optionalKey(Schema.String), + "type": Schema.Never, + "call_id": Schema.String, + "input": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }).annotate({ "description": "An openrouter:code_interpreter server tool output item" }), + Schema.Struct({ + "id": Schema.optionalKey(Schema.String), + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "status": ToolCallStatus, + "type": Schema.Never, + "call_id": Schema.String, + "input": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }).annotate({ "description": "An openrouter:file_search server tool output item" }), + Schema.Struct({ + "id": Schema.optionalKey(Schema.String), + "imageB64": Schema.optionalKey(Schema.String), + "imageUrl": Schema.optionalKey(Schema.String), + "prompt": Schema.optionalKey( + Schema.String.annotate({ + "description": "The prompt (possibly rewritten) that the image was generated from." + }) + ), + "result": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": + "The generated image as a base64-encoded string or URL, matching OpenAI image_generation_call format" + }) + ), + "revisedPrompt": Schema.optionalKey(Schema.String), + "status": ToolCallStatus, + "type": Schema.Never, + "call_id": Schema.String, + "input": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }).annotate({ "description": "An openrouter:image_generation server tool output item" }), + Schema.Struct({ + "action": Schema.optionalKey(Schema.String), + "id": Schema.optionalKey(Schema.String), + "screenshotB64": Schema.optionalKey(Schema.String), + "status": ToolCallStatus, + "type": Schema.Never, + "call_id": Schema.String, + "input": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }).annotate({ "description": "An openrouter:browser_use server tool output item" }), + Schema.Struct({ + "arguments": Schema.optionalKey( + Schema.String.annotate({ "description": "The raw tool-call arguments string as emitted by the model." }) + ), + "call_id": Schema.String.annotate({ + "description": "The model-generated tool call id from the originating turn." + }), + "command": Schema.optionalKey(Schema.String), + "exitCode": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "id": Schema.optionalKey(Schema.String), + "status": ToolCallStatus, + "stderr": Schema.optionalKey(Schema.String), + "stdout": Schema.optionalKey(Schema.String), + "type": Schema.Never, + "input": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }).annotate({ "description": "An openrouter:bash server tool output item" }), + Schema.Struct({ + "command": Schema.optionalKey(Schema.Literals(["view", "create", "str_replace", "insert"])), + "filePath": Schema.optionalKey(Schema.String), + "id": Schema.optionalKey(Schema.String), + "status": ToolCallStatus, + "type": Schema.Never, + "call_id": Schema.String, + "input": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }).annotate({ "description": "An openrouter:text_editor server tool output item" }), + Schema.Struct({ + "call_id": Schema.String, + "id": Schema.optionalKey(Schema.String), + "operation": Schema.optionalKey(ApplyPatchCallOperation), + "status": ToolCallStatus, + "type": Schema.Never, + "input": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }).annotate({ + "description": + "An openrouter:apply_patch server tool output item. The turn halts when validation succeeds so the client can apply the patch and echo an `apply_patch_call_output` on the next turn." + }), + Schema.Struct({ + "call_id": Schema.String, + "id": Schema.String, + "operation": ApplyPatchCallOperation, + "status": ApplyPatchCallStatus, + "type": Schema.Never, + "input": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }).annotate({ + "description": + "A native `apply_patch_call` output item matching OpenAI's Responses API shape. Emitted when the client requested the `apply_patch` shorthand." + }), + Schema.Struct({ + "action": Schema.optionalKey(Schema.Struct({ + "commands": Schema.Array(Schema.String), + "max_output_length": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]), + "timeout_ms": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]) + })), + "call_id": Schema.String, + "id": Schema.String, + "status": ShellCallStatus, + "type": Schema.Never, + "input": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }).annotate({ + "description": + "A native `shell_call` output item matching OpenAI's Responses API shape. Emitted for the sandbox-backed `shell` tool." + }), + Schema.Struct({ + "call_id": Schema.String, + "id": Schema.String, + "max_output_length": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]) + ), + "output": Schema.Array(Schema.Struct({ + "outcome": Schema.Union([ + Schema.Struct({ + "exit_code": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("exit") + }), + Schema.Struct({ "type": Schema.Literal("timeout") }) + ], { mode: "oneOf" }), + "stderr": Schema.String, + "stdout": Schema.String + })), + "status": ShellCallStatus, + "type": Schema.Never, + "input": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }).annotate({ + "description": + "A native `shell_call_output` item matching OpenAI's Responses API shape. Carries per-command stdout, stderr, and the exit/timeout outcome." + }), + Schema.Struct({ + "content": Schema.optionalKey(Schema.String), + "error": Schema.optionalKey( + Schema.String.annotate({ "description": "The error message if the fetch failed." }) + ), + "httpStatus": Schema.optionalKey( + Schema.Number.annotate({ "description": "The HTTP status code returned by the upstream URL fetch." }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ) + ), + "id": Schema.optionalKey(Schema.String), + "status": ToolCallStatus, + "title": Schema.optionalKey(Schema.String), + "type": Schema.Never, + "url": Schema.optionalKey(Schema.String), + "call_id": Schema.String, + "input": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }).annotate({ "description": "An openrouter:web_fetch server tool output item" }), + Schema.Struct({ + "id": Schema.optionalKey(Schema.String), + "query": Schema.optionalKey(Schema.String), + "status": ToolCallStatus, + "type": Schema.Never, + "call_id": Schema.String, + "input": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }).annotate({ "description": "An openrouter:tool_search server tool output item" }), + Schema.Struct({ + "action": Schema.optionalKey(Schema.Literals(["read", "write", "delete"])), + "id": Schema.optionalKey(Schema.String), + "key": Schema.optionalKey(Schema.String), + "status": ToolCallStatus, + "type": Schema.Never, + "value": Schema.optionalKey(Schema.Json.annotate({ "expected": "JSON value" })), + "call_id": Schema.String, + "input": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }).annotate({ "description": "An openrouter:memory server tool output item" }), + Schema.Struct({ + "id": Schema.optionalKey(Schema.String), + "serverLabel": Schema.optionalKey(Schema.String), + "status": ToolCallStatus, + "toolName": Schema.optionalKey(Schema.String), + "type": Schema.Never, + "call_id": Schema.String, + "input": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }).annotate({ "description": "An openrouter:mcp server tool output item" }), + Schema.Struct({ + "arguments": Schema.optionalKey( + Schema.String.annotate({ + "description": "The JSON arguments submitted to the search tool (e.g. {\"query\":\"Claude\"})" + }) + ), + "id": Schema.optionalKey(Schema.String), + "query": Schema.optionalKey(Schema.String), + "status": ToolCallStatus, + "type": Schema.Never, + "call_id": Schema.String, + "input": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }).annotate({ "description": "An openrouter:experimental__search_models server tool output item" }), + Schema.Struct({ + "analysis": Schema.optionalKey(FusionAnalysisResult), + "error": Schema.optionalKey( + Schema.String.annotate({ + "description": "Error message when the fusion run did not produce an analysis result." + }) + ), + "failed_models": Schema.optionalKey( + Schema.Array(Schema.Struct({ + "error": Schema.String.annotate({ "description": "Error message describing why the model failed." }), + "model": Schema.String.annotate({ "description": "Slug of the analysis model that failed." }), + "status_code": Schema.optionalKey( + Schema.Number.annotate({ + "description": "HTTP status code from the upstream response, when available (e.g. 402, 429)." + }).check(Schema.isInt().annotate({ "expected": "an integer" })) + ) + })).annotate({ + "description": + "Models that were requested as part of the analysis panel but did not produce a response. Present when at least one requested analysis model failed. The fusion result is still usable but was produced from a degraded panel." + }) + ), + "failure_reason": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Typed failure reason when the fusion run failed. Possible values include: all_panels_failed, insufficient_credits, rate_limited, judge_not_valid_json, judge_schema_mismatch, judge_upstream_error, judge_empty_completion." + }) + ), + "id": Schema.optionalKey(Schema.String), + "responses": Schema.optionalKey( + Schema.Array(Schema.Struct({ "content": Schema.optionalKey(Schema.String), "model": Schema.String })) + .annotate({ + "description": + "Analysis models that produced a response in this fusion run, with each model's full panel content." + }) + ), + "sources": Schema.optionalKey( + Schema.Array(FusionSource).annotate({ + "description": + "Web pages the analysis panels and judge retrieved via web search during this fusion run, deduplicated by URL across the whole run. Present when at least one model cited a source." + }) + ), + "status": ToolCallStatus, + "type": Schema.Never, + "call_id": Schema.String, + "input": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }).annotate({ "description": "An openrouter:fusion server tool output item" }), + Schema.Struct({ + "advice": Schema.optionalKey( + Schema.String.annotate({ + "description": "The advisor model's response (the advice text returned to the executor)." + }) + ), + "error": Schema.optionalKey( + Schema.String.annotate({ "description": "Error message when the advisor call did not produce advice." }) + ), + "id": Schema.optionalKey(Schema.String), + "instance_name": Schema.optionalKey(Schema.String.annotate({ + "description": + "Provider-safe function name of the specific advisor instance that produced this item (e.g. `openrouter_advisor__1`). Present only when more than one advisor tool is configured; omitted for the default single advisor. Echo this field back unchanged so the advisor's cross-request memory stays namespaced to the correct instance. This identity is positional: it is derived from the index of the advisor entry in the request `tools` array, so clients must keep the order of advisor tool entries stable across requests in a conversation. Reordering or inserting advisor entries shifts these names and causes each advisor's cross-request memory to be attributed to the wrong instance." + })), + "model": Schema.optionalKey( + Schema.String.annotate({ "description": "Slug of the advisor model that was consulted." }) + ), + "prompt": Schema.optionalKey( + Schema.String.annotate({ "description": "The prompt the executor sent to the advisor." }) + ), + "status": ToolCallStatus, + "type": Schema.Never, + "call_id": Schema.String, + "input": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }).annotate({ "description": "An openrouter:advisor server tool output item" }), + Schema.Struct({ + "error": Schema.optionalKey( + Schema.String.annotate({ "description": "Error message when the subagent task did not produce an outcome." }) + ), + "id": Schema.optionalKey(Schema.String), + "instance_name": Schema.optionalKey(Schema.String.annotate({ + "description": + "Provider-safe function name of the specific subagent instance that produced this item (e.g. `openrouter_subagent__1`). Present only on items from non-default instances — the second and later subagent entries in the request `tools` array. The first (default) instance omits it, even when multiple subagents are configured. When a replayed item echoes this field back, the transcript rehydrates the call under that instance's tool. This identity is positional: it is derived from the index of the subagent entry in the request `tools` array, so keep the order of subagent entries stable across requests in a conversation." + })), + "model": Schema.optionalKey( + Schema.String.annotate({ "description": "Slug of the worker model that executed the task." }) + ), + "name": Schema.String.annotate({ + "description": + "Configured name of the subagent that executed the task (the `name` on its tool entry). Present only for named subagents; omitted for an unnamed (default) subagent." + }), + "outcome": Schema.optionalKey( + Schema.String.annotate({ + "description": "The worker model's result (the outcome text returned to the delegating model)." + }) + ), + "status": ToolCallStatus, + "task_description": Schema.optionalKey( + Schema.String.annotate({ "description": "The task description the delegating model sent to the worker." }) + ), + "task_name": Schema.optionalKey( + Schema.String.annotate({ "description": "The short task identifier the delegating model supplied." }) + ), + "type": Schema.Never, + "call_id": Schema.String, + "input": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }).annotate({ "description": "An openrouter:subagent server tool output item" }), + Schema.Struct({ + "error": Schema.optionalKey( + Schema.String.annotate({ "description": "Error message when the file operation failed." }) + ), + "file_id": Schema.optionalKey( + Schema.String.annotate({ "description": "The target file id supplied in the tool-call arguments." }) + ), + "filename": Schema.optionalKey( + Schema.String.annotate({ "description": "The target filename supplied in the tool-call arguments." }) + ), + "id": Schema.optionalKey(Schema.String), + "operation": Schema.optionalKey( + Schema.String.annotate({ "description": "The file operation performed (list, read, write, or edit)." }) + ), + "result": Schema.optionalKey( + Schema.String.annotate({ "description": "JSON-serialized result of the file operation." }) + ), + "status": ToolCallStatus, + "type": Schema.Never, + "call_id": Schema.String, + "input": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }).annotate({ "description": "An openrouter:files server tool output item" }), + Schema.Struct({ + "call_id": Schema.String, + "id": Schema.optionalKey(Schema.String), + "input": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ), + "type": Schema.Literal("custom_tool_call") + }).annotate({ + "description": + "A call to a custom (freeform-grammar) tool created by the model — distinct from `function_call`. Used for tools like Codex CLI's `apply_patch` whose payload is opaque text rather than JSON arguments." + }) + ], { mode: "oneOf" }).annotate({ "description": "An output item from the response" }), + Schema.Union([ + Schema.Struct({ + "content": Schema.Array(Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent])), + "id": Schema.String, + "phase": Schema.optionalKey( + Schema.Union([Schema.Literal("commentary"), Schema.Literal("final_answer"), Schema.Null]).annotate({ + "description": + "The phase of an assistant message. Use `commentary` for an intermediate assistant message and `final_answer` for the final assistant message. For follow-up requests with models like `gpt-5.3-codex` and later, preserve and resend phase on all assistant messages. Omitting it can degrade performance. Not used for user messages." + }) + ), + "role": Schema.Literal("assistant"), + "status": Schema.Union([Schema.Literal("completed"), Schema.Literal("in_progress")]), + "type": Schema.Never, + "action": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "query": Schema.String, + "sources": Schema.optionalKey(Schema.Array(WebSearchSource)), + "type": Schema.Literal("search") + }), + Schema.Struct({ + "type": Schema.Literal("open_page"), + "url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }), + Schema.Struct({ "pattern": Schema.String, "type": Schema.Literal("find_in_page"), "url": Schema.String }) + ], { mode: "oneOf" }) + ) + }).annotate({ "description": "An output message item" }), + Schema.Struct({ + "content": Schema.optionalKey( + Schema.Union([ + Schema.Array(Schema.Struct({ "text": Schema.String, "type": Schema.Literal("reasoning_text") })) + ]) + ), + "encrypted_content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "id": Schema.String, + "status": Schema.Union([Schema.Literal("completed"), Schema.Literal("in_progress")]), + "summary": Schema.Array(ReasoningSummaryText), + "type": Schema.Never, + "format": Schema.optionalKey(ReasoningFormat), + "signature": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "A signature for the reasoning content, used for verification" + }) + ), + "action": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "query": Schema.String, + "sources": Schema.optionalKey(Schema.Array(WebSearchSource)), + "type": Schema.Literal("search") + }), + Schema.Struct({ + "type": Schema.Literal("open_page"), + "url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }), + Schema.Struct({ "pattern": Schema.String, "type": Schema.Literal("find_in_page"), "url": Schema.String }) + ], { mode: "oneOf" }) + ) + }).annotate({ "description": "An output item containing reasoning" }), + Schema.Struct({ + "arguments": Schema.String, + "call_id": Schema.String, + "id": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ), + "status": Schema.Union([Schema.Literal("completed"), Schema.Literal("in_progress")]), + "type": Schema.Never, + "action": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "query": Schema.String, + "sources": Schema.optionalKey(Schema.Array(WebSearchSource)), + "type": Schema.Literal("search") + }), + Schema.Struct({ + "type": Schema.Literal("open_page"), + "url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }), + Schema.Struct({ "pattern": Schema.String, "type": Schema.Literal("find_in_page"), "url": Schema.String }) + ], { mode: "oneOf" }) + ) + }), + Schema.Struct({ + "action": Schema.optionalKey( + Schema.Union([ + Schema.Union([ + Schema.Struct({ + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "query": Schema.String, + "sources": Schema.optionalKey( + Schema.Array(Schema.Struct({ "type": Schema.Literal("url"), "url": Schema.String })) + ), + "type": Schema.Literal("search") + }), + Schema.Struct({ + "type": Schema.Never, + "url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "query": Schema.String, + "sources": Schema.optionalKey(Schema.Array(WebSearchSource)) + }), + Schema.Struct({ + "pattern": Schema.String, + "type": Schema.Never, + "url": Schema.String, + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "query": Schema.String, + "sources": Schema.optionalKey(Schema.Array(WebSearchSource)) + }) + ], { mode: "oneOf" }), + Schema.Union([ + Schema.Struct({ + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "query": Schema.String, + "sources": Schema.optionalKey(Schema.Array(WebSearchSource)), + "type": Schema.Never, + "url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }), + Schema.Struct({ + "type": Schema.Literal("open_page"), + "url": Schema.optionalKey(Schema.Union([Schema.Union([Schema.String]), Schema.Union([Schema.Null])])) + }), + Schema.Struct({ "pattern": Schema.String, "type": Schema.Never, "url": Schema.Union([Schema.String]) }) + ], { mode: "oneOf" }), + Schema.Union([ + Schema.Struct({ + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "query": Schema.String, + "sources": Schema.optionalKey(Schema.Array(WebSearchSource)), + "type": Schema.Never, + "pattern": Schema.String, + "url": Schema.String + }), + Schema.Struct({ "type": Schema.Never, "url": Schema.Union([Schema.String]), "pattern": Schema.String }), + Schema.Struct({ "pattern": Schema.String, "type": Schema.Literal("find_in_page"), "url": Schema.String }) + ], { mode: "oneOf" }) + ], { mode: "oneOf" }) + ), + "id": Schema.String, + "status": Schema.Union([ + Schema.Literal("completed"), + Schema.Literal("searching"), + Schema.Literal("in_progress"), + Schema.Literal("failed") + ]), + "type": Schema.Literal("web_search_call") + }), + Schema.Struct({ + "id": Schema.String, + "queries": Schema.Array(Schema.String), + "status": Schema.Union([ + Schema.Literal("completed"), + Schema.Literal("searching"), + Schema.Literal("in_progress"), + Schema.Literal("failed") + ]), + "type": Schema.Never, + "action": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "query": Schema.String, + "sources": Schema.optionalKey(Schema.Array(WebSearchSource)), + "type": Schema.Literal("search") + }), + Schema.Struct({ + "type": Schema.Literal("open_page"), + "url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }), + Schema.Struct({ "pattern": Schema.String, "type": Schema.Literal("find_in_page"), "url": Schema.String }) + ], { mode: "oneOf" }) + ) + }), + Schema.Struct({ + "id": Schema.String, + "result": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed"), Schema.Literal("failed")]), + "type": Schema.Never, + "prompt": Schema.optionalKey( + Schema.String.annotate({ + "description": "The prompt (possibly rewritten) that the image was generated from." + }) + ), + "action": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "query": Schema.String, + "sources": Schema.optionalKey(Schema.Array(WebSearchSource)), + "type": Schema.Literal("search") + }), + Schema.Struct({ + "type": Schema.Literal("open_page"), + "url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }), + Schema.Struct({ "pattern": Schema.String, "type": Schema.Literal("find_in_page"), "url": Schema.String }) + ], { mode: "oneOf" }) + ) + }), + Schema.Struct({ + "code": Schema.Union([Schema.String, Schema.Null]), + "container_id": Schema.String, + "id": Schema.String, + "outputs": Schema.Union([ + Schema.Array( + Schema.Union([ + Schema.Struct({ "type": Schema.Literal("image"), "url": Schema.String }), + Schema.Struct({ "logs": Schema.String, "type": Schema.Literal("logs") }) + ]) + ), + Schema.Null + ]), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "action": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "query": Schema.String, + "sources": Schema.optionalKey(Schema.Array(WebSearchSource)), + "type": Schema.Literal("search") + }), + Schema.Struct({ + "type": Schema.Literal("open_page"), + "url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }), + Schema.Struct({ "pattern": Schema.String, "type": Schema.Literal("find_in_page"), "url": Schema.String }) + ], { mode: "oneOf" }) + ) + }).annotate({ "description": "A code interpreter execution call with outputs" }), + Schema.Struct({ + "action": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "query": Schema.String, + "sources": Schema.optionalKey(Schema.Array(WebSearchSource)), + "type": Schema.Literal("search") + }), + Schema.Struct({ + "type": Schema.Literal("open_page"), + "url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }), + Schema.Struct({ "pattern": Schema.String, "type": Schema.Literal("find_in_page"), "url": Schema.String }) + ], { mode: "oneOf" }) + ), + "call_id": Schema.String, + "id": Schema.String, + "pending_safety_checks": Schema.Array( + Schema.Struct({ "code": Schema.String, "id": Schema.String, "message": Schema.String }) + ), + "status": Schema.Union([Schema.Literal("completed"), Schema.Literal("in_progress")]), + "type": Schema.Never + }), + Schema.Struct({ + "datetime": Schema.String.annotate({ "description": "ISO 8601 datetime string" }), + "id": Schema.String, + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "timezone": Schema.String.annotate({ "description": "IANA timezone name" }), + "type": Schema.Never, + "action": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "query": Schema.String, + "sources": Schema.optionalKey(Schema.Array(WebSearchSource)), + "type": Schema.Literal("search") + }), + Schema.Struct({ + "type": Schema.Literal("open_page"), + "url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }), + Schema.Struct({ "pattern": Schema.String, "type": Schema.Literal("find_in_page"), "url": Schema.String }) + ], { mode: "oneOf" }) + ) + }).annotate({ "description": "An openrouter:datetime server tool output item" }), + Schema.Struct({ + "action": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "query": Schema.String, + "sources": Schema.optionalKey( + Schema.Array(Schema.Struct({ "type": Schema.Literal("url"), "url": Schema.String })) + ), + "type": Schema.Literal("search") + }).annotate({ + "description": + "The search action performed, matching OpenAI web_search_call.action shape. Includes the query the model issued and optional source URLs returned by the search provider." + }), + Schema.Struct({ + "type": Schema.Never, + "url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "query": Schema.String, + "sources": Schema.optionalKey( + Schema.Array(Schema.Struct({ "type": Schema.Literal("url"), "url": Schema.String })) + ) + }).annotate({ + "description": + "The search action performed, matching OpenAI web_search_call.action shape. Includes the query the model issued and optional source URLs returned by the search provider." + }), + Schema.Struct({ + "pattern": Schema.String, + "type": Schema.Never, + "url": Schema.String, + "query": Schema.String, + "sources": Schema.optionalKey( + Schema.Array(Schema.Struct({ "type": Schema.Literal("url"), "url": Schema.String })) + ) + }).annotate({ + "description": + "The search action performed, matching OpenAI web_search_call.action shape. Includes the query the model issued and optional source URLs returned by the search provider." + }) + ], { mode: "oneOf" }) + ), + "id": Schema.String, + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never + }).annotate({ "description": "An openrouter:web_search server tool output item" }), + Schema.Struct({ + "code": Schema.optionalKey(Schema.String), + "exitCode": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "id": Schema.String, + "language": Schema.optionalKey(Schema.String), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "stderr": Schema.optionalKey(Schema.String), + "stdout": Schema.optionalKey(Schema.String), + "type": Schema.Never, + "action": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "query": Schema.String, + "sources": Schema.optionalKey(Schema.Array(WebSearchSource)), + "type": Schema.Literal("search") + }), + Schema.Struct({ + "type": Schema.Literal("open_page"), + "url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }), + Schema.Struct({ "pattern": Schema.String, "type": Schema.Literal("find_in_page"), "url": Schema.String }) + ], { mode: "oneOf" }) + ) + }).annotate({ "description": "An openrouter:code_interpreter server tool output item" }), + Schema.Struct({ + "id": Schema.String, + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "action": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "query": Schema.String, + "sources": Schema.optionalKey(Schema.Array(WebSearchSource)), + "type": Schema.Literal("search") + }), + Schema.Struct({ + "type": Schema.Literal("open_page"), + "url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }), + Schema.Struct({ "pattern": Schema.String, "type": Schema.Literal("find_in_page"), "url": Schema.String }) + ], { mode: "oneOf" }) + ) + }).annotate({ "description": "An openrouter:file_search server tool output item" }), + Schema.Struct({ + "id": Schema.String, + "imageB64": Schema.optionalKey(Schema.String), + "imageUrl": Schema.optionalKey(Schema.String), + "prompt": Schema.optionalKey( + Schema.String.annotate({ + "description": "The prompt (possibly rewritten) that the image was generated from." + }) + ), + "result": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": + "The generated image as a base64-encoded string or URL, matching OpenAI image_generation_call format" + }) + ), + "revisedPrompt": Schema.optionalKey(Schema.String), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "action": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "query": Schema.String, + "sources": Schema.optionalKey(Schema.Array(WebSearchSource)), + "type": Schema.Literal("search") + }), + Schema.Struct({ + "type": Schema.Literal("open_page"), + "url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }), + Schema.Struct({ "pattern": Schema.String, "type": Schema.Literal("find_in_page"), "url": Schema.String }) + ], { mode: "oneOf" }) + ) + }).annotate({ "description": "An openrouter:image_generation server tool output item" }), + Schema.Struct({ + "action": Schema.optionalKey(Schema.Never), + "id": Schema.String, + "screenshotB64": Schema.optionalKey(Schema.String), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never + }).annotate({ "description": "An openrouter:browser_use server tool output item" }), + Schema.Struct({ + "arguments": Schema.optionalKey( + Schema.String.annotate({ "description": "The raw tool-call arguments string as emitted by the model." }) + ), + "call_id": Schema.optionalKey( + Schema.String.annotate({ "description": "The model-generated tool call id from the originating turn." }) + ), + "command": Schema.optionalKey(Schema.String), + "exitCode": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "id": Schema.String, + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "stderr": Schema.optionalKey(Schema.String), + "stdout": Schema.optionalKey(Schema.String), + "type": Schema.Never, + "action": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "query": Schema.String, + "sources": Schema.optionalKey(Schema.Array(WebSearchSource)), + "type": Schema.Literal("search") + }), + Schema.Struct({ + "type": Schema.Literal("open_page"), + "url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }), + Schema.Struct({ "pattern": Schema.String, "type": Schema.Literal("find_in_page"), "url": Schema.String }) + ], { mode: "oneOf" }) + ) + }).annotate({ "description": "An openrouter:bash server tool output item" }), + Schema.Struct({ + "command": Schema.optionalKey(Schema.Literals(["view", "create", "str_replace", "insert"])), + "filePath": Schema.optionalKey(Schema.String), + "id": Schema.String, + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "action": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "query": Schema.String, + "sources": Schema.optionalKey(Schema.Array(WebSearchSource)), + "type": Schema.Literal("search") + }), + Schema.Struct({ + "type": Schema.Literal("open_page"), + "url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }), + Schema.Struct({ "pattern": Schema.String, "type": Schema.Literal("find_in_page"), "url": Schema.String }) + ], { mode: "oneOf" }) + ) + }).annotate({ "description": "An openrouter:text_editor server tool output item" }), + Schema.Struct({ + "call_id": Schema.optionalKey(Schema.String), + "id": Schema.String, + "operation": Schema.optionalKey(ApplyPatchCallOperation), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "action": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "query": Schema.String, + "sources": Schema.optionalKey(Schema.Array(WebSearchSource)), + "type": Schema.Literal("search") + }), + Schema.Struct({ + "type": Schema.Literal("open_page"), + "url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }), + Schema.Struct({ "pattern": Schema.String, "type": Schema.Literal("find_in_page"), "url": Schema.String }) + ], { mode: "oneOf" }) + ) + }).annotate({ + "description": + "An openrouter:apply_patch server tool output item. The turn halts when validation succeeds so the client can apply the patch and echo an `apply_patch_call_output` on the next turn." + }), + Schema.Struct({ + "call_id": Schema.String, + "id": Schema.String, + "operation": ApplyPatchCallOperation, + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]).annotate({ + "description": "Lifecycle state of an `apply_patch_call` output item." + }), + "type": Schema.Never, + "action": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "query": Schema.String, + "sources": Schema.optionalKey(Schema.Array(WebSearchSource)), + "type": Schema.Literal("search") + }), + Schema.Struct({ + "type": Schema.Literal("open_page"), + "url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }), + Schema.Struct({ "pattern": Schema.String, "type": Schema.Literal("find_in_page"), "url": Schema.String }) + ], { mode: "oneOf" }) + ) + }).annotate({ + "description": + "A native `apply_patch_call` output item matching OpenAI's Responses API shape. Emitted when the client requested the `apply_patch` shorthand." + }), + Schema.Struct({ + "action": Schema.optionalKey(Schema.Union([ + Schema.Struct({ + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "query": Schema.String, + "sources": Schema.optionalKey(Schema.Array(WebSearchSource)), + "type": Schema.Literal("search"), + "commands": Schema.Array(Schema.String), + "max_output_length": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]), + "timeout_ms": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]) + }), + Schema.Struct({ + "type": Schema.Literal("open_page"), + "url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "commands": Schema.Array(Schema.String), + "max_output_length": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]), + "timeout_ms": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]) + }), + Schema.Struct({ + "pattern": Schema.String, + "type": Schema.Literal("find_in_page"), + "url": Schema.String, + "commands": Schema.Array(Schema.String), + "max_output_length": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]), + "timeout_ms": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]) + }) + ], { mode: "oneOf" })), + "call_id": Schema.String, + "id": Schema.String, + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]).annotate({ + "description": "Status of a shell call or its output." + }), + "type": Schema.Never + }).annotate({ + "description": + "A native `shell_call` output item matching OpenAI's Responses API shape. Emitted for the sandbox-backed `shell` tool." + }), + Schema.Struct({ + "call_id": Schema.String, + "id": Schema.String, + "max_output_length": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]) + ), + "output": Schema.Array(Schema.Struct({ + "outcome": Schema.Union([ + Schema.Struct({ + "exit_code": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("exit") + }), + Schema.Struct({ "type": Schema.Literal("timeout") }) + ], { mode: "oneOf" }), + "stderr": Schema.String, + "stdout": Schema.String + })), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]).annotate({ + "description": "Status of a shell call or its output." + }), + "type": Schema.Never, + "action": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "query": Schema.String, + "sources": Schema.optionalKey(Schema.Array(WebSearchSource)), + "type": Schema.Literal("search") + }), + Schema.Struct({ + "type": Schema.Literal("open_page"), + "url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }), + Schema.Struct({ "pattern": Schema.String, "type": Schema.Literal("find_in_page"), "url": Schema.String }) + ], { mode: "oneOf" }) + ) + }).annotate({ + "description": + "A native `shell_call_output` item matching OpenAI's Responses API shape. Carries per-command stdout, stderr, and the exit/timeout outcome." + }), + Schema.Struct({ + "content": Schema.optionalKey(Schema.String), + "error": Schema.optionalKey( + Schema.String.annotate({ "description": "The error message if the fetch failed." }) + ), + "httpStatus": Schema.optionalKey( + Schema.Number.annotate({ "description": "The HTTP status code returned by the upstream URL fetch." }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ) + ), + "id": Schema.String, + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "title": Schema.optionalKey(Schema.String), + "type": Schema.Never, + "url": Schema.optionalKey(Schema.String), + "action": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "query": Schema.String, + "sources": Schema.optionalKey(Schema.Array(WebSearchSource)), + "type": Schema.Literal("search") + }), + Schema.Struct({ + "type": Schema.Literal("open_page"), + "url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }), + Schema.Struct({ "pattern": Schema.String, "type": Schema.Literal("find_in_page"), "url": Schema.String }) + ], { mode: "oneOf" }) + ) + }).annotate({ "description": "An openrouter:web_fetch server tool output item" }), + Schema.Struct({ + "id": Schema.String, + "query": Schema.optionalKey(Schema.String), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "action": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "query": Schema.String, + "sources": Schema.optionalKey(Schema.Array(WebSearchSource)), + "type": Schema.Literal("search") + }), + Schema.Struct({ + "type": Schema.Literal("open_page"), + "url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }), + Schema.Struct({ "pattern": Schema.String, "type": Schema.Literal("find_in_page"), "url": Schema.String }) + ], { mode: "oneOf" }) + ) + }).annotate({ "description": "An openrouter:tool_search server tool output item" }), + Schema.Struct({ + "action": Schema.optionalKey(Schema.Never), + "id": Schema.String, + "key": Schema.optionalKey(Schema.String), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "value": Schema.optionalKey(Schema.Json.annotate({ "expected": "JSON value" })) + }).annotate({ "description": "An openrouter:memory server tool output item" }), + Schema.Struct({ + "id": Schema.String, + "serverLabel": Schema.optionalKey(Schema.String), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "toolName": Schema.optionalKey(Schema.String), + "type": Schema.Never, + "action": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "query": Schema.String, + "sources": Schema.optionalKey(Schema.Array(WebSearchSource)), + "type": Schema.Literal("search") + }), + Schema.Struct({ + "type": Schema.Literal("open_page"), + "url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }), + Schema.Struct({ "pattern": Schema.String, "type": Schema.Literal("find_in_page"), "url": Schema.String }) + ], { mode: "oneOf" }) + ) + }).annotate({ "description": "An openrouter:mcp server tool output item" }), + Schema.Struct({ + "arguments": Schema.optionalKey( + Schema.String.annotate({ + "description": "The JSON arguments submitted to the search tool (e.g. {\"query\":\"Claude\"})" + }) + ), + "id": Schema.String, + "query": Schema.optionalKey(Schema.String), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "action": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "query": Schema.String, + "sources": Schema.optionalKey(Schema.Array(WebSearchSource)), + "type": Schema.Literal("search") + }), + Schema.Struct({ + "type": Schema.Literal("open_page"), + "url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }), + Schema.Struct({ "pattern": Schema.String, "type": Schema.Literal("find_in_page"), "url": Schema.String }) + ], { mode: "oneOf" }) + ) + }).annotate({ "description": "An openrouter:experimental__search_models server tool output item" }), + Schema.Struct({ + "analysis": Schema.optionalKey(FusionAnalysisResult), + "error": Schema.optionalKey( + Schema.String.annotate({ + "description": "Error message when the fusion run did not produce an analysis result." + }) + ), + "failed_models": Schema.optionalKey( + Schema.Array(Schema.Struct({ + "error": Schema.String.annotate({ "description": "Error message describing why the model failed." }), + "model": Schema.String.annotate({ "description": "Slug of the analysis model that failed." }), + "status_code": Schema.optionalKey( + Schema.Number.annotate({ + "description": "HTTP status code from the upstream response, when available (e.g. 402, 429)." + }).check(Schema.isInt().annotate({ "expected": "an integer" })) + ) + })).annotate({ + "description": + "Models that were requested as part of the analysis panel but did not produce a response. Present when at least one requested analysis model failed. The fusion result is still usable but was produced from a degraded panel." + }) + ), + "failure_reason": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Typed failure reason when the fusion run failed. Possible values include: all_panels_failed, insufficient_credits, rate_limited, judge_not_valid_json, judge_schema_mismatch, judge_upstream_error, judge_empty_completion." + }) + ), + "id": Schema.String, + "responses": Schema.optionalKey( + Schema.Array(Schema.Struct({ "content": Schema.optionalKey(Schema.String), "model": Schema.String })) + .annotate({ + "description": + "Analysis models that produced a response in this fusion run, with each model's full panel content." + }) + ), + "sources": Schema.optionalKey( + Schema.Array(FusionSource).annotate({ + "description": + "Web pages the analysis panels and judge retrieved via web search during this fusion run, deduplicated by URL across the whole run. Present when at least one model cited a source." + }) + ), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "action": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "query": Schema.String, + "sources": Schema.optionalKey(Schema.Array(WebSearchSource)), + "type": Schema.Literal("search") + }), + Schema.Struct({ + "type": Schema.Literal("open_page"), + "url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }), + Schema.Struct({ "pattern": Schema.String, "type": Schema.Literal("find_in_page"), "url": Schema.String }) + ], { mode: "oneOf" }) + ) + }).annotate({ "description": "An openrouter:fusion server tool output item" }), + Schema.Struct({ + "advice": Schema.optionalKey( + Schema.String.annotate({ + "description": "The advisor model's response (the advice text returned to the executor)." + }) + ), + "error": Schema.optionalKey( + Schema.String.annotate({ "description": "Error message when the advisor call did not produce advice." }) + ), + "id": Schema.String, + "instance_name": Schema.optionalKey(Schema.String.annotate({ + "description": + "Provider-safe function name of the specific advisor instance that produced this item (e.g. `openrouter_advisor__1`). Present only when more than one advisor tool is configured; omitted for the default single advisor. Echo this field back unchanged so the advisor's cross-request memory stays namespaced to the correct instance. This identity is positional: it is derived from the index of the advisor entry in the request `tools` array, so clients must keep the order of advisor tool entries stable across requests in a conversation. Reordering or inserting advisor entries shifts these names and causes each advisor's cross-request memory to be attributed to the wrong instance." + })), + "model": Schema.optionalKey( + Schema.String.annotate({ "description": "Slug of the advisor model that was consulted." }) + ), + "prompt": Schema.optionalKey( + Schema.String.annotate({ "description": "The prompt the executor sent to the advisor." }) + ), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "action": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "query": Schema.String, + "sources": Schema.optionalKey(Schema.Array(WebSearchSource)), + "type": Schema.Literal("search") + }), + Schema.Struct({ + "type": Schema.Literal("open_page"), + "url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }), + Schema.Struct({ "pattern": Schema.String, "type": Schema.Literal("find_in_page"), "url": Schema.String }) + ], { mode: "oneOf" }) + ) + }).annotate({ "description": "An openrouter:advisor server tool output item" }), + Schema.Struct({ + "error": Schema.optionalKey( + Schema.String.annotate({ "description": "Error message when the subagent task did not produce an outcome." }) + ), + "id": Schema.String, + "instance_name": Schema.optionalKey(Schema.String.annotate({ + "description": + "Provider-safe function name of the specific subagent instance that produced this item (e.g. `openrouter_subagent__1`). Present only on items from non-default instances — the second and later subagent entries in the request `tools` array. The first (default) instance omits it, even when multiple subagents are configured. When a replayed item echoes this field back, the transcript rehydrates the call under that instance's tool. This identity is positional: it is derived from the index of the subagent entry in the request `tools` array, so keep the order of subagent entries stable across requests in a conversation." + })), + "model": Schema.optionalKey( + Schema.String.annotate({ "description": "Slug of the worker model that executed the task." }) + ), + "name": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Configured name of the subagent that executed the task (the `name` on its tool entry). Present only for named subagents; omitted for an unnamed (default) subagent." + }) + ), + "outcome": Schema.optionalKey( + Schema.String.annotate({ + "description": "The worker model's result (the outcome text returned to the delegating model)." + }) + ), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "task_description": Schema.optionalKey( + Schema.String.annotate({ "description": "The task description the delegating model sent to the worker." }) + ), + "task_name": Schema.optionalKey( + Schema.String.annotate({ "description": "The short task identifier the delegating model supplied." }) + ), + "type": Schema.Never, + "action": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "query": Schema.String, + "sources": Schema.optionalKey(Schema.Array(WebSearchSource)), + "type": Schema.Literal("search") + }), + Schema.Struct({ + "type": Schema.Literal("open_page"), + "url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }), + Schema.Struct({ "pattern": Schema.String, "type": Schema.Literal("find_in_page"), "url": Schema.String }) + ], { mode: "oneOf" }) + ) + }).annotate({ "description": "An openrouter:subagent server tool output item" }), + Schema.Struct({ + "error": Schema.optionalKey( + Schema.String.annotate({ "description": "Error message when the file operation failed." }) + ), + "file_id": Schema.optionalKey( + Schema.String.annotate({ "description": "The target file id supplied in the tool-call arguments." }) + ), + "filename": Schema.optionalKey( + Schema.String.annotate({ "description": "The target filename supplied in the tool-call arguments." }) + ), + "id": Schema.String, + "operation": Schema.optionalKey( + Schema.String.annotate({ "description": "The file operation performed (list, read, write, or edit)." }) + ), + "result": Schema.optionalKey( + Schema.String.annotate({ "description": "JSON-serialized result of the file operation." }) + ), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "action": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "query": Schema.String, + "sources": Schema.optionalKey(Schema.Array(WebSearchSource)), + "type": Schema.Literal("search") + }), + Schema.Struct({ + "type": Schema.Literal("open_page"), + "url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }), + Schema.Struct({ "pattern": Schema.String, "type": Schema.Literal("find_in_page"), "url": Schema.String }) + ], { mode: "oneOf" }) + ) + }).annotate({ "description": "An openrouter:files server tool output item" }), + Schema.Struct({ + "call_id": Schema.String, + "id": Schema.String, + "input": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ), + "type": Schema.Never, + "action": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "query": Schema.String, + "sources": Schema.optionalKey(Schema.Array(WebSearchSource)), + "type": Schema.Literal("search") + }), + Schema.Struct({ + "type": Schema.Literal("open_page"), + "url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }), + Schema.Struct({ "pattern": Schema.String, "type": Schema.Literal("find_in_page"), "url": Schema.String }) + ], { mode: "oneOf" }) + ), + "status": WebSearchStatus + }).annotate({ + "description": + "A call to a custom (freeform-grammar) tool created by the model — distinct from `function_call`. Used for tools like Codex CLI's `apply_patch` whose payload is opaque text rather than JSON arguments." + }) + ], { mode: "oneOf" }).annotate({ "description": "An output item from the response" }), + Schema.Union([ + Schema.Struct({ + "content": Schema.Array(Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent])), + "id": Schema.String, + "phase": Schema.optionalKey( + Schema.Union([Schema.Literal("commentary"), Schema.Literal("final_answer"), Schema.Null]).annotate({ + "description": + "The phase of an assistant message. Use `commentary` for an intermediate assistant message and `final_answer` for the final assistant message. For follow-up requests with models like `gpt-5.3-codex` and later, preserve and resend phase on all assistant messages. Omitting it can degrade performance. Not used for user messages." + }) + ), + "role": Schema.Literal("assistant"), + "status": Schema.Union([Schema.Literal("completed"), Schema.Literal("in_progress")]), + "type": Schema.Never, + "queries": Schema.Array(Schema.String) + }).annotate({ "description": "An output message item" }), + Schema.Struct({ + "content": Schema.optionalKey( + Schema.Union([ + Schema.Array(Schema.Struct({ "text": Schema.String, "type": Schema.Literal("reasoning_text") })) + ]) + ), + "encrypted_content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "id": Schema.String, + "status": Schema.Union([Schema.Literal("completed"), Schema.Literal("in_progress")]), + "summary": Schema.Array(ReasoningSummaryText), + "type": Schema.Never, + "format": Schema.optionalKey(ReasoningFormat), + "signature": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "A signature for the reasoning content, used for verification" + }) + ), + "queries": Schema.Array(Schema.String) + }).annotate({ "description": "An output item containing reasoning" }), + Schema.Struct({ + "arguments": Schema.String, + "call_id": Schema.String, + "id": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ), + "status": Schema.Union([Schema.Literal("completed"), Schema.Literal("in_progress")]), + "type": Schema.Never, + "queries": Schema.Array(Schema.String) + }), + Schema.Struct({ + "action": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "query": Schema.String, + "sources": Schema.optionalKey(Schema.Array(WebSearchSource)), + "type": Schema.Literal("search") + }), + Schema.Struct({ + "type": Schema.Literal("open_page"), + "url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }), + Schema.Struct({ "pattern": Schema.String, "type": Schema.Literal("find_in_page"), "url": Schema.String }) + ], { mode: "oneOf" }) + ), + "id": Schema.String, + "status": Schema.Union([ + Schema.Literal("completed"), + Schema.Literal("searching"), + Schema.Literal("in_progress"), + Schema.Literal("failed") + ]), + "type": Schema.Never, + "queries": Schema.Array(Schema.String) + }), + Schema.Struct({ + "id": Schema.String, + "queries": Schema.Array(Schema.String), + "status": Schema.Union([ + Schema.Literal("completed"), + Schema.Literal("searching"), + Schema.Literal("in_progress"), + Schema.Literal("failed") + ]), + "type": Schema.Literal("file_search_call") + }), + Schema.Struct({ + "id": Schema.String, + "result": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed"), Schema.Literal("failed")]), + "type": Schema.Never, + "prompt": Schema.optionalKey( + Schema.String.annotate({ + "description": "The prompt (possibly rewritten) that the image was generated from." + }) + ), + "queries": Schema.Array(Schema.String) + }), + Schema.Struct({ + "code": Schema.Union([Schema.String, Schema.Null]), + "container_id": Schema.String, + "id": Schema.String, + "outputs": Schema.Union([ + Schema.Array( + Schema.Union([ + Schema.Struct({ "type": Schema.Literal("image"), "url": Schema.String }), + Schema.Struct({ "logs": Schema.String, "type": Schema.Literal("logs") }) + ]) + ), + Schema.Null + ]), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "queries": Schema.Array(Schema.String) + }).annotate({ "description": "A code interpreter execution call with outputs" }), + Schema.Struct({ + "action": Schema.optionalKey(Schema.Json.annotate({ "expected": "JSON value" })), + "call_id": Schema.String, + "id": Schema.String, + "pending_safety_checks": Schema.Array( + Schema.Struct({ "code": Schema.String, "id": Schema.String, "message": Schema.String }) + ), + "status": Schema.Union([Schema.Literal("completed"), Schema.Literal("in_progress")]), + "type": Schema.Never, + "queries": Schema.Array(Schema.String) + }), + Schema.Struct({ + "datetime": Schema.String.annotate({ "description": "ISO 8601 datetime string" }), + "id": Schema.String, + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "timezone": Schema.String.annotate({ "description": "IANA timezone name" }), + "type": Schema.Never, + "queries": Schema.Array(Schema.String) + }).annotate({ "description": "An openrouter:datetime server tool output item" }), + Schema.Struct({ + "action": Schema.optionalKey( + Schema.Struct({ + "query": Schema.String, + "sources": Schema.optionalKey( + Schema.Array(Schema.Struct({ "type": Schema.Literal("url"), "url": Schema.String })) + ), + "type": Schema.Literal("search") + }).annotate({ + "description": + "The search action performed, matching OpenAI web_search_call.action shape. Includes the query the model issued and optional source URLs returned by the search provider." + }) + ), + "id": Schema.String, + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "queries": Schema.Array(Schema.String) + }).annotate({ "description": "An openrouter:web_search server tool output item" }), + Schema.Struct({ + "code": Schema.optionalKey(Schema.String), + "exitCode": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "id": Schema.String, + "language": Schema.optionalKey(Schema.String), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "stderr": Schema.optionalKey(Schema.String), + "stdout": Schema.optionalKey(Schema.String), + "type": Schema.Never, + "queries": Schema.Array(Schema.String) + }).annotate({ "description": "An openrouter:code_interpreter server tool output item" }), + Schema.Struct({ + "id": Schema.String, + "queries": Schema.Array(Schema.String), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never + }).annotate({ "description": "An openrouter:file_search server tool output item" }), + Schema.Struct({ + "id": Schema.String, + "imageB64": Schema.optionalKey(Schema.String), + "imageUrl": Schema.optionalKey(Schema.String), + "prompt": Schema.optionalKey( + Schema.String.annotate({ + "description": "The prompt (possibly rewritten) that the image was generated from." + }) + ), + "result": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": + "The generated image as a base64-encoded string or URL, matching OpenAI image_generation_call format" + }) + ), + "revisedPrompt": Schema.optionalKey(Schema.String), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "queries": Schema.Array(Schema.String) + }).annotate({ "description": "An openrouter:image_generation server tool output item" }), + Schema.Struct({ + "action": Schema.optionalKey(Schema.String), + "id": Schema.String, + "screenshotB64": Schema.optionalKey(Schema.String), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "queries": Schema.Array(Schema.String) + }).annotate({ "description": "An openrouter:browser_use server tool output item" }), + Schema.Struct({ + "arguments": Schema.optionalKey( + Schema.String.annotate({ "description": "The raw tool-call arguments string as emitted by the model." }) + ), + "call_id": Schema.optionalKey( + Schema.String.annotate({ "description": "The model-generated tool call id from the originating turn." }) + ), + "command": Schema.optionalKey(Schema.String), + "exitCode": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "id": Schema.String, + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "stderr": Schema.optionalKey(Schema.String), + "stdout": Schema.optionalKey(Schema.String), + "type": Schema.Never, + "queries": Schema.Array(Schema.String) + }).annotate({ "description": "An openrouter:bash server tool output item" }), + Schema.Struct({ + "command": Schema.optionalKey(Schema.Literals(["view", "create", "str_replace", "insert"])), + "filePath": Schema.optionalKey(Schema.String), + "id": Schema.String, + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "queries": Schema.Array(Schema.String) + }).annotate({ "description": "An openrouter:text_editor server tool output item" }), + Schema.Struct({ + "call_id": Schema.optionalKey(Schema.String), + "id": Schema.String, + "operation": Schema.optionalKey(ApplyPatchCallOperation), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "queries": Schema.Array(Schema.String) + }).annotate({ + "description": + "An openrouter:apply_patch server tool output item. The turn halts when validation succeeds so the client can apply the patch and echo an `apply_patch_call_output` on the next turn." + }), + Schema.Struct({ + "call_id": Schema.String, + "id": Schema.String, + "operation": ApplyPatchCallOperation, + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]).annotate({ + "description": "Lifecycle state of an `apply_patch_call` output item." + }), + "type": Schema.Never, + "queries": Schema.Array(Schema.String) + }).annotate({ + "description": + "A native `apply_patch_call` output item matching OpenAI's Responses API shape. Emitted when the client requested the `apply_patch` shorthand." + }), + Schema.Struct({ + "action": Schema.optionalKey(Schema.Struct({ + "commands": Schema.Array(Schema.String), + "max_output_length": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]), + "timeout_ms": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]) + })), + "call_id": Schema.String, + "id": Schema.String, + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]).annotate({ + "description": "Status of a shell call or its output." + }), + "type": Schema.Never, + "queries": Schema.Array(Schema.String) + }).annotate({ + "description": + "A native `shell_call` output item matching OpenAI's Responses API shape. Emitted for the sandbox-backed `shell` tool." + }), + Schema.Struct({ + "call_id": Schema.String, + "id": Schema.String, + "max_output_length": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]) + ), + "output": Schema.Array(Schema.Struct({ + "outcome": Schema.Union([ + Schema.Struct({ + "exit_code": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("exit") + }), + Schema.Struct({ "type": Schema.Literal("timeout") }) + ], { mode: "oneOf" }), + "stderr": Schema.String, + "stdout": Schema.String + })), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]).annotate({ + "description": "Status of a shell call or its output." + }), + "type": Schema.Never, + "queries": Schema.Array(Schema.String) + }).annotate({ + "description": + "A native `shell_call_output` item matching OpenAI's Responses API shape. Carries per-command stdout, stderr, and the exit/timeout outcome." + }), + Schema.Struct({ + "content": Schema.optionalKey(Schema.String), + "error": Schema.optionalKey( + Schema.String.annotate({ "description": "The error message if the fetch failed." }) + ), + "httpStatus": Schema.optionalKey( + Schema.Number.annotate({ "description": "The HTTP status code returned by the upstream URL fetch." }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ) + ), + "id": Schema.String, + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "title": Schema.optionalKey(Schema.String), + "type": Schema.Never, + "url": Schema.optionalKey(Schema.String), + "queries": Schema.Array(Schema.String) + }).annotate({ "description": "An openrouter:web_fetch server tool output item" }), + Schema.Struct({ + "id": Schema.String, + "query": Schema.optionalKey(Schema.String), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "queries": Schema.Array(Schema.String) + }).annotate({ "description": "An openrouter:tool_search server tool output item" }), + Schema.Struct({ + "action": Schema.optionalKey(Schema.Literals(["read", "write", "delete"])), + "id": Schema.String, + "key": Schema.optionalKey(Schema.String), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "value": Schema.optionalKey(Schema.Json.annotate({ "expected": "JSON value" })), + "queries": Schema.Array(Schema.String) + }).annotate({ "description": "An openrouter:memory server tool output item" }), + Schema.Struct({ + "id": Schema.String, + "serverLabel": Schema.optionalKey(Schema.String), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "toolName": Schema.optionalKey(Schema.String), + "type": Schema.Never, + "queries": Schema.Array(Schema.String) + }).annotate({ "description": "An openrouter:mcp server tool output item" }), + Schema.Struct({ + "arguments": Schema.optionalKey( + Schema.String.annotate({ + "description": "The JSON arguments submitted to the search tool (e.g. {\"query\":\"Claude\"})" + }) + ), + "id": Schema.String, + "query": Schema.optionalKey(Schema.String), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "queries": Schema.Array(Schema.String) + }).annotate({ "description": "An openrouter:experimental__search_models server tool output item" }), + Schema.Struct({ + "analysis": Schema.optionalKey(FusionAnalysisResult), + "error": Schema.optionalKey( + Schema.String.annotate({ + "description": "Error message when the fusion run did not produce an analysis result." + }) + ), + "failed_models": Schema.optionalKey( + Schema.Array(Schema.Struct({ + "error": Schema.String.annotate({ "description": "Error message describing why the model failed." }), + "model": Schema.String.annotate({ "description": "Slug of the analysis model that failed." }), + "status_code": Schema.optionalKey( + Schema.Number.annotate({ + "description": "HTTP status code from the upstream response, when available (e.g. 402, 429)." + }).check(Schema.isInt().annotate({ "expected": "an integer" })) + ) + })).annotate({ + "description": + "Models that were requested as part of the analysis panel but did not produce a response. Present when at least one requested analysis model failed. The fusion result is still usable but was produced from a degraded panel." + }) + ), + "failure_reason": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Typed failure reason when the fusion run failed. Possible values include: all_panels_failed, insufficient_credits, rate_limited, judge_not_valid_json, judge_schema_mismatch, judge_upstream_error, judge_empty_completion." + }) + ), + "id": Schema.String, + "responses": Schema.optionalKey( + Schema.Array(Schema.Struct({ "content": Schema.optionalKey(Schema.String), "model": Schema.String })) + .annotate({ + "description": + "Analysis models that produced a response in this fusion run, with each model's full panel content." + }) + ), + "sources": Schema.optionalKey( + Schema.Array(FusionSource).annotate({ + "description": + "Web pages the analysis panels and judge retrieved via web search during this fusion run, deduplicated by URL across the whole run. Present when at least one model cited a source." + }) + ), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "queries": Schema.Array(Schema.String) + }).annotate({ "description": "An openrouter:fusion server tool output item" }), + Schema.Struct({ + "advice": Schema.optionalKey( + Schema.String.annotate({ + "description": "The advisor model's response (the advice text returned to the executor)." + }) + ), + "error": Schema.optionalKey( + Schema.String.annotate({ "description": "Error message when the advisor call did not produce advice." }) + ), + "id": Schema.String, + "instance_name": Schema.optionalKey(Schema.String.annotate({ + "description": + "Provider-safe function name of the specific advisor instance that produced this item (e.g. `openrouter_advisor__1`). Present only when more than one advisor tool is configured; omitted for the default single advisor. Echo this field back unchanged so the advisor's cross-request memory stays namespaced to the correct instance. This identity is positional: it is derived from the index of the advisor entry in the request `tools` array, so clients must keep the order of advisor tool entries stable across requests in a conversation. Reordering or inserting advisor entries shifts these names and causes each advisor's cross-request memory to be attributed to the wrong instance." + })), + "model": Schema.optionalKey( + Schema.String.annotate({ "description": "Slug of the advisor model that was consulted." }) + ), + "prompt": Schema.optionalKey( + Schema.String.annotate({ "description": "The prompt the executor sent to the advisor." }) + ), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "queries": Schema.Array(Schema.String) + }).annotate({ "description": "An openrouter:advisor server tool output item" }), + Schema.Struct({ + "error": Schema.optionalKey( + Schema.String.annotate({ "description": "Error message when the subagent task did not produce an outcome." }) + ), + "id": Schema.String, + "instance_name": Schema.optionalKey(Schema.String.annotate({ + "description": + "Provider-safe function name of the specific subagent instance that produced this item (e.g. `openrouter_subagent__1`). Present only on items from non-default instances — the second and later subagent entries in the request `tools` array. The first (default) instance omits it, even when multiple subagents are configured. When a replayed item echoes this field back, the transcript rehydrates the call under that instance's tool. This identity is positional: it is derived from the index of the subagent entry in the request `tools` array, so keep the order of subagent entries stable across requests in a conversation." + })), + "model": Schema.optionalKey( + Schema.String.annotate({ "description": "Slug of the worker model that executed the task." }) + ), + "name": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Configured name of the subagent that executed the task (the `name` on its tool entry). Present only for named subagents; omitted for an unnamed (default) subagent." + }) + ), + "outcome": Schema.optionalKey( + Schema.String.annotate({ + "description": "The worker model's result (the outcome text returned to the delegating model)." + }) + ), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "task_description": Schema.optionalKey( + Schema.String.annotate({ "description": "The task description the delegating model sent to the worker." }) + ), + "task_name": Schema.optionalKey( + Schema.String.annotate({ "description": "The short task identifier the delegating model supplied." }) + ), + "type": Schema.Never, + "queries": Schema.Array(Schema.String) + }).annotate({ "description": "An openrouter:subagent server tool output item" }), + Schema.Struct({ + "error": Schema.optionalKey( + Schema.String.annotate({ "description": "Error message when the file operation failed." }) + ), + "file_id": Schema.optionalKey( + Schema.String.annotate({ "description": "The target file id supplied in the tool-call arguments." }) + ), + "filename": Schema.optionalKey( + Schema.String.annotate({ "description": "The target filename supplied in the tool-call arguments." }) + ), + "id": Schema.String, + "operation": Schema.optionalKey( + Schema.String.annotate({ "description": "The file operation performed (list, read, write, or edit)." }) + ), + "result": Schema.optionalKey( + Schema.String.annotate({ "description": "JSON-serialized result of the file operation." }) + ), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "queries": Schema.Array(Schema.String) + }).annotate({ "description": "An openrouter:files server tool output item" }), + Schema.Struct({ + "call_id": Schema.String, + "id": Schema.String, + "input": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ), + "type": Schema.Never, + "queries": Schema.Array(Schema.String), + "status": WebSearchStatus + }).annotate({ + "description": + "A call to a custom (freeform-grammar) tool created by the model — distinct from `function_call`. Used for tools like Codex CLI's `apply_patch` whose payload is opaque text rather than JSON arguments." + }) + ], { mode: "oneOf" }).annotate({ "description": "An output item from the response" }), + Schema.Union([ + Schema.Struct({ + "content": Schema.Array(Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent])), + "id": Schema.String, + "phase": Schema.optionalKey( + Schema.Union([Schema.Literal("commentary"), Schema.Literal("final_answer"), Schema.Null]).annotate({ + "description": + "The phase of an assistant message. Use `commentary` for an intermediate assistant message and `final_answer` for the final assistant message. For follow-up requests with models like `gpt-5.3-codex` and later, preserve and resend phase on all assistant messages. Omitting it can degrade performance. Not used for user messages." + }) + ), + "role": Schema.Literal("assistant"), + "status": Schema.Union([Schema.Literal("completed"), Schema.Literal("in_progress")]), + "type": Schema.Never, + "result": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }).annotate({ "description": "An output message item" }), + Schema.Struct({ + "content": Schema.optionalKey( + Schema.Union([ + Schema.Array(Schema.Struct({ "text": Schema.String, "type": Schema.Literal("reasoning_text") })) + ]) + ), + "encrypted_content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "id": Schema.String, + "status": Schema.Union([Schema.Literal("completed"), Schema.Literal("in_progress")]), + "summary": Schema.Array(ReasoningSummaryText), + "type": Schema.Never, + "format": Schema.optionalKey(ReasoningFormat), + "signature": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "A signature for the reasoning content, used for verification" + }) + ), + "result": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }).annotate({ "description": "An output item containing reasoning" }), + Schema.Struct({ + "arguments": Schema.String, + "call_id": Schema.String, + "id": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ), + "status": Schema.Union([Schema.Literal("completed"), Schema.Literal("in_progress")]), + "type": Schema.Never, + "result": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }), + Schema.Struct({ + "action": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "query": Schema.String, + "sources": Schema.optionalKey(Schema.Array(WebSearchSource)), + "type": Schema.Literal("search") + }), + Schema.Struct({ + "type": Schema.Literal("open_page"), + "url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }), + Schema.Struct({ "pattern": Schema.String, "type": Schema.Literal("find_in_page"), "url": Schema.String }) + ], { mode: "oneOf" }) + ), + "id": Schema.String, + "status": Schema.Union([Schema.Literal("completed"), Schema.Literal("in_progress"), Schema.Literal("failed")]), + "type": Schema.Never, + "result": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }), + Schema.Struct({ + "id": Schema.String, + "queries": Schema.Array(Schema.String), + "status": Schema.Union([Schema.Literal("completed"), Schema.Literal("in_progress"), Schema.Literal("failed")]), + "type": Schema.Never, + "result": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }), + Schema.Struct({ + "id": Schema.String, + "result": Schema.optionalKey(Schema.Union([Schema.Union([Schema.String]), Schema.Union([Schema.Null])])), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("generating"), + Schema.Literal("failed") + ]), + "type": Schema.Literal("image_generation_call"), + "prompt": Schema.optionalKey( + Schema.String.annotate({ + "description": "The prompt (possibly rewritten) that the image was generated from." + }) + ) + }), + Schema.Struct({ + "code": Schema.Union([Schema.String, Schema.Null]), + "container_id": Schema.String, + "id": Schema.String, + "outputs": Schema.Union([ + Schema.Array( + Schema.Union([ + Schema.Struct({ "type": Schema.Literal("image"), "url": Schema.String }), + Schema.Struct({ "logs": Schema.String, "type": Schema.Literal("logs") }) + ]) + ), + Schema.Null + ]), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "result": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }).annotate({ "description": "A code interpreter execution call with outputs" }), + Schema.Struct({ + "action": Schema.optionalKey(Schema.Json.annotate({ "expected": "JSON value" })), + "call_id": Schema.String, + "id": Schema.String, + "pending_safety_checks": Schema.Array( + Schema.Struct({ "code": Schema.String, "id": Schema.String, "message": Schema.String }) + ), + "status": Schema.Union([Schema.Literal("completed"), Schema.Literal("in_progress")]), + "type": Schema.Never, + "result": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }), + Schema.Struct({ + "datetime": Schema.String.annotate({ "description": "ISO 8601 datetime string" }), + "id": Schema.String, + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "timezone": Schema.String.annotate({ "description": "IANA timezone name" }), + "type": Schema.Never, + "result": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }).annotate({ "description": "An openrouter:datetime server tool output item" }), + Schema.Struct({ + "action": Schema.optionalKey( + Schema.Struct({ + "query": Schema.String, + "sources": Schema.optionalKey( + Schema.Array(Schema.Struct({ "type": Schema.Literal("url"), "url": Schema.String })) + ), + "type": Schema.Literal("search") + }).annotate({ + "description": + "The search action performed, matching OpenAI web_search_call.action shape. Includes the query the model issued and optional source URLs returned by the search provider." + }) + ), + "id": Schema.String, + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "result": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }).annotate({ "description": "An openrouter:web_search server tool output item" }), + Schema.Struct({ + "code": Schema.optionalKey(Schema.String), + "exitCode": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "id": Schema.String, + "language": Schema.optionalKey(Schema.String), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "stderr": Schema.optionalKey(Schema.String), + "stdout": Schema.optionalKey(Schema.String), + "type": Schema.Never, + "result": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }).annotate({ "description": "An openrouter:code_interpreter server tool output item" }), + Schema.Struct({ + "id": Schema.String, + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "result": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }).annotate({ "description": "An openrouter:file_search server tool output item" }), + Schema.Struct({ + "id": Schema.String, + "imageB64": Schema.optionalKey(Schema.String), + "imageUrl": Schema.optionalKey(Schema.String), + "prompt": Schema.optionalKey( + Schema.String.annotate({ + "description": "The prompt (possibly rewritten) that the image was generated from." + }) + ), + "result": Schema.optionalKey( + Schema.Union([Schema.Union([Schema.String]), Schema.Union([Schema.Null])]).annotate({ + "description": + "The generated image as a base64-encoded string or URL, matching OpenAI image_generation_call format" + }) + ), + "revisedPrompt": Schema.optionalKey(Schema.String), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never + }).annotate({ "description": "An openrouter:image_generation server tool output item" }), + Schema.Struct({ + "action": Schema.optionalKey(Schema.String), + "id": Schema.String, + "screenshotB64": Schema.optionalKey(Schema.String), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "result": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }).annotate({ "description": "An openrouter:browser_use server tool output item" }), + Schema.Struct({ + "arguments": Schema.optionalKey( + Schema.String.annotate({ "description": "The raw tool-call arguments string as emitted by the model." }) + ), + "call_id": Schema.optionalKey( + Schema.String.annotate({ "description": "The model-generated tool call id from the originating turn." }) + ), + "command": Schema.optionalKey(Schema.String), + "exitCode": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "id": Schema.String, + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "stderr": Schema.optionalKey(Schema.String), + "stdout": Schema.optionalKey(Schema.String), + "type": Schema.Never, + "result": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }).annotate({ "description": "An openrouter:bash server tool output item" }), + Schema.Struct({ + "command": Schema.optionalKey(Schema.Literals(["view", "create", "str_replace", "insert"])), + "filePath": Schema.optionalKey(Schema.String), + "id": Schema.String, + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "result": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }).annotate({ "description": "An openrouter:text_editor server tool output item" }), + Schema.Struct({ + "call_id": Schema.optionalKey(Schema.String), + "id": Schema.String, + "operation": Schema.optionalKey(ApplyPatchCallOperation), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "result": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }).annotate({ + "description": + "An openrouter:apply_patch server tool output item. The turn halts when validation succeeds so the client can apply the patch and echo an `apply_patch_call_output` on the next turn." + }), + Schema.Struct({ + "call_id": Schema.String, + "id": Schema.String, + "operation": ApplyPatchCallOperation, + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]).annotate({ + "description": "Lifecycle state of an `apply_patch_call` output item." + }), + "type": Schema.Never, + "result": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }).annotate({ + "description": + "A native `apply_patch_call` output item matching OpenAI's Responses API shape. Emitted when the client requested the `apply_patch` shorthand." + }), + Schema.Struct({ + "action": Schema.optionalKey(Schema.Struct({ + "commands": Schema.Array(Schema.String), + "max_output_length": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]), + "timeout_ms": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]) + })), + "call_id": Schema.String, + "id": Schema.String, + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]).annotate({ + "description": "Status of a shell call or its output." + }), + "type": Schema.Never, + "result": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }).annotate({ + "description": + "A native `shell_call` output item matching OpenAI's Responses API shape. Emitted for the sandbox-backed `shell` tool." + }), + Schema.Struct({ + "call_id": Schema.String, + "id": Schema.String, + "max_output_length": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]) + ), + "output": Schema.Array(Schema.Struct({ + "outcome": Schema.Union([ + Schema.Struct({ + "exit_code": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("exit") + }), + Schema.Struct({ "type": Schema.Literal("timeout") }) + ], { mode: "oneOf" }), + "stderr": Schema.String, + "stdout": Schema.String + })), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]).annotate({ + "description": "Status of a shell call or its output." + }), + "type": Schema.Never, + "result": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }).annotate({ + "description": + "A native `shell_call_output` item matching OpenAI's Responses API shape. Carries per-command stdout, stderr, and the exit/timeout outcome." + }), + Schema.Struct({ + "content": Schema.optionalKey(Schema.String), + "error": Schema.optionalKey( + Schema.String.annotate({ "description": "The error message if the fetch failed." }) + ), + "httpStatus": Schema.optionalKey( + Schema.Number.annotate({ "description": "The HTTP status code returned by the upstream URL fetch." }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ) + ), + "id": Schema.String, + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "title": Schema.optionalKey(Schema.String), + "type": Schema.Never, + "url": Schema.optionalKey(Schema.String), + "result": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }).annotate({ "description": "An openrouter:web_fetch server tool output item" }), + Schema.Struct({ + "id": Schema.String, + "query": Schema.optionalKey(Schema.String), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "result": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }).annotate({ "description": "An openrouter:tool_search server tool output item" }), + Schema.Struct({ + "action": Schema.optionalKey(Schema.Literals(["read", "write", "delete"])), + "id": Schema.String, + "key": Schema.optionalKey(Schema.String), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "value": Schema.optionalKey(Schema.Json.annotate({ "expected": "JSON value" })), + "result": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }).annotate({ "description": "An openrouter:memory server tool output item" }), + Schema.Struct({ + "id": Schema.String, + "serverLabel": Schema.optionalKey(Schema.String), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "toolName": Schema.optionalKey(Schema.String), + "type": Schema.Never, + "result": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }).annotate({ "description": "An openrouter:mcp server tool output item" }), + Schema.Struct({ + "arguments": Schema.optionalKey( + Schema.String.annotate({ + "description": "The JSON arguments submitted to the search tool (e.g. {\"query\":\"Claude\"})" + }) + ), + "id": Schema.String, + "query": Schema.optionalKey(Schema.String), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "result": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }).annotate({ "description": "An openrouter:experimental__search_models server tool output item" }), + Schema.Struct({ + "analysis": Schema.optionalKey(FusionAnalysisResult), + "error": Schema.optionalKey( + Schema.String.annotate({ + "description": "Error message when the fusion run did not produce an analysis result." + }) + ), + "failed_models": Schema.optionalKey( + Schema.Array(Schema.Struct({ + "error": Schema.String.annotate({ "description": "Error message describing why the model failed." }), + "model": Schema.String.annotate({ "description": "Slug of the analysis model that failed." }), + "status_code": Schema.optionalKey( + Schema.Number.annotate({ + "description": "HTTP status code from the upstream response, when available (e.g. 402, 429)." + }).check(Schema.isInt().annotate({ "expected": "an integer" })) + ) + })).annotate({ + "description": + "Models that were requested as part of the analysis panel but did not produce a response. Present when at least one requested analysis model failed. The fusion result is still usable but was produced from a degraded panel." + }) + ), + "failure_reason": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Typed failure reason when the fusion run failed. Possible values include: all_panels_failed, insufficient_credits, rate_limited, judge_not_valid_json, judge_schema_mismatch, judge_upstream_error, judge_empty_completion." + }) + ), + "id": Schema.String, + "responses": Schema.optionalKey( + Schema.Array(Schema.Struct({ "content": Schema.optionalKey(Schema.String), "model": Schema.String })) + .annotate({ + "description": + "Analysis models that produced a response in this fusion run, with each model's full panel content." + }) + ), + "sources": Schema.optionalKey( + Schema.Array(FusionSource).annotate({ + "description": + "Web pages the analysis panels and judge retrieved via web search during this fusion run, deduplicated by URL across the whole run. Present when at least one model cited a source." + }) + ), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "result": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }).annotate({ "description": "An openrouter:fusion server tool output item" }), + Schema.Struct({ + "advice": Schema.optionalKey( + Schema.String.annotate({ + "description": "The advisor model's response (the advice text returned to the executor)." + }) + ), + "error": Schema.optionalKey( + Schema.String.annotate({ "description": "Error message when the advisor call did not produce advice." }) + ), + "id": Schema.String, + "instance_name": Schema.optionalKey(Schema.String.annotate({ + "description": + "Provider-safe function name of the specific advisor instance that produced this item (e.g. `openrouter_advisor__1`). Present only when more than one advisor tool is configured; omitted for the default single advisor. Echo this field back unchanged so the advisor's cross-request memory stays namespaced to the correct instance. This identity is positional: it is derived from the index of the advisor entry in the request `tools` array, so clients must keep the order of advisor tool entries stable across requests in a conversation. Reordering or inserting advisor entries shifts these names and causes each advisor's cross-request memory to be attributed to the wrong instance." + })), + "model": Schema.optionalKey( + Schema.String.annotate({ "description": "Slug of the advisor model that was consulted." }) + ), + "prompt": Schema.optionalKey( + Schema.String.annotate({ "description": "The prompt the executor sent to the advisor." }) + ), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "result": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }).annotate({ "description": "An openrouter:advisor server tool output item" }), + Schema.Struct({ + "error": Schema.optionalKey( + Schema.String.annotate({ "description": "Error message when the subagent task did not produce an outcome." }) + ), + "id": Schema.String, + "instance_name": Schema.optionalKey(Schema.String.annotate({ + "description": + "Provider-safe function name of the specific subagent instance that produced this item (e.g. `openrouter_subagent__1`). Present only on items from non-default instances — the second and later subagent entries in the request `tools` array. The first (default) instance omits it, even when multiple subagents are configured. When a replayed item echoes this field back, the transcript rehydrates the call under that instance's tool. This identity is positional: it is derived from the index of the subagent entry in the request `tools` array, so keep the order of subagent entries stable across requests in a conversation." + })), + "model": Schema.optionalKey( + Schema.String.annotate({ "description": "Slug of the worker model that executed the task." }) + ), + "name": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Configured name of the subagent that executed the task (the `name` on its tool entry). Present only for named subagents; omitted for an unnamed (default) subagent." + }) + ), + "outcome": Schema.optionalKey( + Schema.String.annotate({ + "description": "The worker model's result (the outcome text returned to the delegating model)." + }) + ), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "task_description": Schema.optionalKey( + Schema.String.annotate({ "description": "The task description the delegating model sent to the worker." }) + ), + "task_name": Schema.optionalKey( + Schema.String.annotate({ "description": "The short task identifier the delegating model supplied." }) + ), + "type": Schema.Never, + "result": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }).annotate({ "description": "An openrouter:subagent server tool output item" }), + Schema.Struct({ + "error": Schema.optionalKey( + Schema.String.annotate({ "description": "Error message when the file operation failed." }) + ), + "file_id": Schema.optionalKey( + Schema.String.annotate({ "description": "The target file id supplied in the tool-call arguments." }) + ), + "filename": Schema.optionalKey( + Schema.String.annotate({ "description": "The target filename supplied in the tool-call arguments." }) + ), + "id": Schema.String, + "operation": Schema.optionalKey( + Schema.String.annotate({ "description": "The file operation performed (list, read, write, or edit)." }) + ), + "result": Schema.optionalKey( + Schema.Union([Schema.String.annotate({ "description": "JSON-serialized result of the file operation." })]) + ), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never + }).annotate({ "description": "An openrouter:files server tool output item" }), + Schema.Struct({ + "call_id": Schema.String, + "id": Schema.String, + "input": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ), + "type": Schema.Never, + "result": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "status": ImageGenerationStatus + }).annotate({ + "description": + "A call to a custom (freeform-grammar) tool created by the model — distinct from `function_call`. Used for tools like Codex CLI's `apply_patch` whose payload is opaque text rather than JSON arguments." + }) + ], { mode: "oneOf" }).annotate({ "description": "An output item from the response" }), + Schema.Union([ + Schema.Struct({ + "content": Schema.Array(Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent])), + "id": Schema.String, + "phase": Schema.optionalKey( + Schema.Union([Schema.Literal("commentary"), Schema.Literal("final_answer"), Schema.Null]).annotate({ + "description": + "The phase of an assistant message. Use `commentary` for an intermediate assistant message and `final_answer` for the final assistant message. For follow-up requests with models like `gpt-5.3-codex` and later, preserve and resend phase on all assistant messages. Omitting it can degrade performance. Not used for user messages." + }) + ), + "role": Schema.Literal("assistant"), + "status": Schema.Union([Schema.Literal("completed"), Schema.Literal("in_progress")]), + "type": Schema.Never, + "call_id": Schema.String, + "created_by": Schema.optionalKey(Schema.String), + "operation": Schema.Union([ + ApplyPatchCreateFileOperation, + ApplyPatchUpdateFileOperation, + ApplyPatchDeleteFileOperation + ], { mode: "oneOf" }) + }).annotate({ "description": "An output message item" }), + Schema.Struct({ + "content": Schema.optionalKey( + Schema.Union([ + Schema.Array(Schema.Struct({ "text": Schema.String, "type": Schema.Literal("reasoning_text") })) + ]) + ), + "encrypted_content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "id": Schema.String, + "status": Schema.Union([Schema.Literal("completed"), Schema.Literal("in_progress")]), + "summary": Schema.Array(ReasoningSummaryText), + "type": Schema.Never, + "format": Schema.optionalKey(ReasoningFormat), + "signature": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "A signature for the reasoning content, used for verification" + }) + ), + "call_id": Schema.String, + "created_by": Schema.optionalKey(Schema.String), + "operation": Schema.Union([ + ApplyPatchCreateFileOperation, + ApplyPatchUpdateFileOperation, + ApplyPatchDeleteFileOperation + ], { mode: "oneOf" }) + }).annotate({ "description": "An output item containing reasoning" }), + Schema.Struct({ + "arguments": Schema.String, + "call_id": Schema.String, + "id": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ), + "status": Schema.Union([Schema.Literal("completed"), Schema.Literal("in_progress")]), + "type": Schema.Never, + "created_by": Schema.optionalKey(Schema.String), + "operation": Schema.Union([ + ApplyPatchCreateFileOperation, + ApplyPatchUpdateFileOperation, + ApplyPatchDeleteFileOperation + ], { mode: "oneOf" }) + }), + Schema.Struct({ + "action": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "query": Schema.String, + "sources": Schema.optionalKey(Schema.Array(WebSearchSource)), + "type": Schema.Literal("search") + }), + Schema.Struct({ + "type": Schema.Literal("open_page"), + "url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }), + Schema.Struct({ "pattern": Schema.String, "type": Schema.Literal("find_in_page"), "url": Schema.String }) + ], { mode: "oneOf" }) + ), + "id": Schema.String, + "status": Schema.Union([Schema.Literal("completed"), Schema.Literal("in_progress")]), + "type": Schema.Never, + "call_id": Schema.String, + "created_by": Schema.optionalKey(Schema.String), + "operation": Schema.Union([ + ApplyPatchCreateFileOperation, + ApplyPatchUpdateFileOperation, + ApplyPatchDeleteFileOperation + ], { mode: "oneOf" }) + }), + Schema.Struct({ + "id": Schema.String, + "queries": Schema.Array(Schema.String), + "status": Schema.Union([Schema.Literal("completed"), Schema.Literal("in_progress")]), + "type": Schema.Never, + "call_id": Schema.String, + "created_by": Schema.optionalKey(Schema.String), + "operation": Schema.Union([ + ApplyPatchCreateFileOperation, + ApplyPatchUpdateFileOperation, + ApplyPatchDeleteFileOperation + ], { mode: "oneOf" }) + }), + Schema.Struct({ + "id": Schema.String, + "result": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "prompt": Schema.optionalKey( + Schema.String.annotate({ + "description": "The prompt (possibly rewritten) that the image was generated from." + }) + ), + "call_id": Schema.String, + "created_by": Schema.optionalKey(Schema.String), + "operation": Schema.Union([ + ApplyPatchCreateFileOperation, + ApplyPatchUpdateFileOperation, + ApplyPatchDeleteFileOperation + ], { mode: "oneOf" }) + }), + Schema.Struct({ + "code": Schema.Union([Schema.String, Schema.Null]), + "container_id": Schema.String, + "id": Schema.String, + "outputs": Schema.Union([ + Schema.Array( + Schema.Union([ + Schema.Struct({ "type": Schema.Literal("image"), "url": Schema.String }), + Schema.Struct({ "logs": Schema.String, "type": Schema.Literal("logs") }) + ]) + ), + Schema.Null + ]), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "call_id": Schema.String, + "created_by": Schema.optionalKey(Schema.String), + "operation": Schema.Union([ + ApplyPatchCreateFileOperation, + ApplyPatchUpdateFileOperation, + ApplyPatchDeleteFileOperation + ], { mode: "oneOf" }) + }).annotate({ "description": "A code interpreter execution call with outputs" }), + Schema.Struct({ + "action": Schema.optionalKey(Schema.Json.annotate({ "expected": "JSON value" })), + "call_id": Schema.String, + "id": Schema.String, + "pending_safety_checks": Schema.Array( + Schema.Struct({ "code": Schema.String, "id": Schema.String, "message": Schema.String }) + ), + "status": Schema.Union([Schema.Literal("completed"), Schema.Literal("in_progress")]), + "type": Schema.Never, + "created_by": Schema.optionalKey(Schema.String), + "operation": Schema.Union([ + ApplyPatchCreateFileOperation, + ApplyPatchUpdateFileOperation, + ApplyPatchDeleteFileOperation + ], { mode: "oneOf" }) + }), + Schema.Struct({ + "datetime": Schema.String.annotate({ "description": "ISO 8601 datetime string" }), + "id": Schema.String, + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "timezone": Schema.String.annotate({ "description": "IANA timezone name" }), + "type": Schema.Never, + "call_id": Schema.String, + "created_by": Schema.optionalKey(Schema.String), + "operation": Schema.Union([ + ApplyPatchCreateFileOperation, + ApplyPatchUpdateFileOperation, + ApplyPatchDeleteFileOperation + ], { mode: "oneOf" }) + }).annotate({ "description": "An openrouter:datetime server tool output item" }), + Schema.Struct({ + "action": Schema.optionalKey( + Schema.Struct({ + "query": Schema.String, + "sources": Schema.optionalKey( + Schema.Array(Schema.Struct({ "type": Schema.Literal("url"), "url": Schema.String })) + ), + "type": Schema.Literal("search") + }).annotate({ + "description": + "The search action performed, matching OpenAI web_search_call.action shape. Includes the query the model issued and optional source URLs returned by the search provider." + }) + ), + "id": Schema.String, + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "call_id": Schema.String, + "created_by": Schema.optionalKey(Schema.String), + "operation": Schema.Union([ + ApplyPatchCreateFileOperation, + ApplyPatchUpdateFileOperation, + ApplyPatchDeleteFileOperation + ], { mode: "oneOf" }) + }).annotate({ "description": "An openrouter:web_search server tool output item" }), + Schema.Struct({ + "code": Schema.optionalKey(Schema.String), + "exitCode": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "id": Schema.String, + "language": Schema.optionalKey(Schema.String), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "stderr": Schema.optionalKey(Schema.String), + "stdout": Schema.optionalKey(Schema.String), + "type": Schema.Never, + "call_id": Schema.String, + "created_by": Schema.optionalKey(Schema.String), + "operation": Schema.Union([ + ApplyPatchCreateFileOperation, + ApplyPatchUpdateFileOperation, + ApplyPatchDeleteFileOperation + ], { mode: "oneOf" }) + }).annotate({ "description": "An openrouter:code_interpreter server tool output item" }), + Schema.Struct({ + "id": Schema.String, + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "call_id": Schema.String, + "created_by": Schema.optionalKey(Schema.String), + "operation": Schema.Union([ + ApplyPatchCreateFileOperation, + ApplyPatchUpdateFileOperation, + ApplyPatchDeleteFileOperation + ], { mode: "oneOf" }) + }).annotate({ "description": "An openrouter:file_search server tool output item" }), + Schema.Struct({ + "id": Schema.String, + "imageB64": Schema.optionalKey(Schema.String), + "imageUrl": Schema.optionalKey(Schema.String), + "prompt": Schema.optionalKey( + Schema.String.annotate({ + "description": "The prompt (possibly rewritten) that the image was generated from." + }) + ), + "result": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": + "The generated image as a base64-encoded string or URL, matching OpenAI image_generation_call format" + }) + ), + "revisedPrompt": Schema.optionalKey(Schema.String), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "call_id": Schema.String, + "created_by": Schema.optionalKey(Schema.String), + "operation": Schema.Union([ + ApplyPatchCreateFileOperation, + ApplyPatchUpdateFileOperation, + ApplyPatchDeleteFileOperation + ], { mode: "oneOf" }) + }).annotate({ "description": "An openrouter:image_generation server tool output item" }), + Schema.Struct({ + "action": Schema.optionalKey(Schema.String), + "id": Schema.String, + "screenshotB64": Schema.optionalKey(Schema.String), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "call_id": Schema.String, + "created_by": Schema.optionalKey(Schema.String), + "operation": Schema.Union([ + ApplyPatchCreateFileOperation, + ApplyPatchUpdateFileOperation, + ApplyPatchDeleteFileOperation + ], { mode: "oneOf" }) + }).annotate({ "description": "An openrouter:browser_use server tool output item" }), + Schema.Struct({ + "arguments": Schema.optionalKey( + Schema.String.annotate({ "description": "The raw tool-call arguments string as emitted by the model." }) + ), + "call_id": Schema.String.annotate({ + "description": "The model-generated tool call id from the originating turn." + }), + "command": Schema.optionalKey(Schema.String), + "exitCode": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "id": Schema.String, + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "stderr": Schema.optionalKey(Schema.String), + "stdout": Schema.optionalKey(Schema.String), + "type": Schema.Never, + "created_by": Schema.optionalKey(Schema.String), + "operation": Schema.Union([ + ApplyPatchCreateFileOperation, + ApplyPatchUpdateFileOperation, + ApplyPatchDeleteFileOperation + ], { mode: "oneOf" }) + }).annotate({ "description": "An openrouter:bash server tool output item" }), + Schema.Struct({ + "command": Schema.optionalKey(Schema.Literals(["view", "create", "str_replace", "insert"])), + "filePath": Schema.optionalKey(Schema.String), + "id": Schema.String, + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "call_id": Schema.String, + "created_by": Schema.optionalKey(Schema.String), + "operation": Schema.Union([ + ApplyPatchCreateFileOperation, + ApplyPatchUpdateFileOperation, + ApplyPatchDeleteFileOperation + ], { mode: "oneOf" }) + }).annotate({ "description": "An openrouter:text_editor server tool output item" }), + Schema.Struct({ + "call_id": Schema.String, + "id": Schema.String, + "operation": Schema.Union([ + Schema.Union([ + Schema.Struct({ "diff": Schema.String, "path": Schema.String, "type": Schema.Literal("create_file") }) + .annotate({ + "description": + "The `create_file` variant of an `apply_patch_call.operation`. Carries a V4A diff describing the new file contents." + }), + Schema.Struct({ "diff": Schema.String, "path": Schema.String, "type": Schema.Never }).annotate({ + "description": + "The `create_file` variant of an `apply_patch_call.operation`. Carries a V4A diff describing the new file contents." + }), + Schema.Struct({ "path": Schema.String, "type": Schema.Never, "diff": Schema.String }).annotate({ + "description": + "The `create_file` variant of an `apply_patch_call.operation`. Carries a V4A diff describing the new file contents." + }) + ], { mode: "oneOf" }), + Schema.Union([ + Schema.Struct({ "diff": Schema.String, "path": Schema.String, "type": Schema.Never }).annotate({ + "description": + "The `update_file` variant of an `apply_patch_call.operation`. Carries a V4A diff describing edits to an existing file." + }), + Schema.Struct({ "diff": Schema.String, "path": Schema.String, "type": Schema.Literal("update_file") }) + .annotate({ + "description": + "The `update_file` variant of an `apply_patch_call.operation`. Carries a V4A diff describing edits to an existing file." + }), + Schema.Struct({ "path": Schema.String, "type": Schema.Never, "diff": Schema.String }).annotate({ + "description": + "The `update_file` variant of an `apply_patch_call.operation`. Carries a V4A diff describing edits to an existing file." + }) + ], { mode: "oneOf" }), + Schema.Union([ + Schema.Struct({ "diff": Schema.String, "path": Schema.String, "type": Schema.Never }).annotate({ + "description": + "The `delete_file` variant of an `apply_patch_call.operation`. Identifies the file to remove; no diff is required." + }), + Schema.Struct({ "diff": Schema.String, "path": Schema.String, "type": Schema.Never }).annotate({ + "description": + "The `delete_file` variant of an `apply_patch_call.operation`. Identifies the file to remove; no diff is required." + }), + Schema.Struct({ "path": Schema.String, "type": Schema.Literal("delete_file") }).annotate({ + "description": + "The `delete_file` variant of an `apply_patch_call.operation`. Identifies the file to remove; no diff is required." + }) + ], { mode: "oneOf" }) + ], { mode: "oneOf" }).annotate({ + "description": + "The patch operation requested by an `apply_patch_call`. `create_file` and `update_file` carry a V4A diff; `delete_file` omits it." + }), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "created_by": Schema.optionalKey(Schema.String) + }).annotate({ + "description": + "An openrouter:apply_patch server tool output item. The turn halts when validation succeeds so the client can apply the patch and echo an `apply_patch_call_output` on the next turn." + }), + Schema.Struct({ + "call_id": Schema.String, + "id": Schema.String, + "operation": Schema.Union([ + Schema.Union([ + Schema.Struct({ "diff": Schema.String, "path": Schema.String, "type": Schema.Literal("create_file") }) + .annotate({ + "description": + "The `create_file` variant of an `apply_patch_call.operation`. Carries a V4A diff describing the new file contents." + }), + Schema.Struct({ "diff": Schema.String, "path": Schema.String, "type": Schema.Never }).annotate({ + "description": + "The `create_file` variant of an `apply_patch_call.operation`. Carries a V4A diff describing the new file contents." + }), + Schema.Struct({ "path": Schema.String, "type": Schema.Never, "diff": Schema.String }).annotate({ + "description": + "The `create_file` variant of an `apply_patch_call.operation`. Carries a V4A diff describing the new file contents." + }) + ], { mode: "oneOf" }), + Schema.Union([ + Schema.Struct({ "diff": Schema.String, "path": Schema.String, "type": Schema.Never }).annotate({ + "description": + "The `update_file` variant of an `apply_patch_call.operation`. Carries a V4A diff describing edits to an existing file." + }), + Schema.Struct({ "diff": Schema.String, "path": Schema.String, "type": Schema.Literal("update_file") }) + .annotate({ + "description": + "The `update_file` variant of an `apply_patch_call.operation`. Carries a V4A diff describing edits to an existing file." + }), + Schema.Struct({ "path": Schema.String, "type": Schema.Never, "diff": Schema.String }).annotate({ + "description": + "The `update_file` variant of an `apply_patch_call.operation`. Carries a V4A diff describing edits to an existing file." + }) + ], { mode: "oneOf" }), + Schema.Union([ + Schema.Struct({ "diff": Schema.String, "path": Schema.String, "type": Schema.Never }).annotate({ + "description": + "The `delete_file` variant of an `apply_patch_call.operation`. Identifies the file to remove; no diff is required." + }), + Schema.Struct({ "diff": Schema.String, "path": Schema.String, "type": Schema.Never }).annotate({ + "description": + "The `delete_file` variant of an `apply_patch_call.operation`. Identifies the file to remove; no diff is required." + }), + Schema.Struct({ "path": Schema.String, "type": Schema.Literal("delete_file") }).annotate({ + "description": + "The `delete_file` variant of an `apply_patch_call.operation`. Identifies the file to remove; no diff is required." + }) + ], { mode: "oneOf" }) + ], { mode: "oneOf" }).annotate({ + "description": + "The patch operation requested by an `apply_patch_call`. `create_file` and `update_file` carry a V4A diff; `delete_file` omits it." + }), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]).annotate({ + "description": "Lifecycle state of an `apply_patch_call` output item." + }), + "type": Schema.Literal("apply_patch_call"), + "created_by": Schema.optionalKey(Schema.String) + }).annotate({ + "description": + "A native `apply_patch_call` output item matching OpenAI's Responses API shape. Emitted when the client requested the `apply_patch` shorthand." + }), + Schema.Struct({ + "action": Schema.optionalKey(Schema.Struct({ + "commands": Schema.Array(Schema.String), + "max_output_length": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]), + "timeout_ms": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]) + })), + "call_id": Schema.String, + "id": Schema.String, + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]).annotate({ + "description": "Status of a shell call or its output." + }), + "type": Schema.Never, + "created_by": Schema.optionalKey(Schema.String), + "operation": Schema.Union([ + ApplyPatchCreateFileOperation, + ApplyPatchUpdateFileOperation, + ApplyPatchDeleteFileOperation + ], { mode: "oneOf" }) + }).annotate({ + "description": + "A native `shell_call` output item matching OpenAI's Responses API shape. Emitted for the sandbox-backed `shell` tool." + }), + Schema.Struct({ + "call_id": Schema.String, + "id": Schema.String, + "max_output_length": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]) + ), + "output": Schema.Array(Schema.Struct({ + "outcome": Schema.Union([ + Schema.Struct({ + "exit_code": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("exit") + }), + Schema.Struct({ "type": Schema.Literal("timeout") }) + ], { mode: "oneOf" }), + "stderr": Schema.String, + "stdout": Schema.String + })), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]).annotate({ + "description": "Status of a shell call or its output." + }), + "type": Schema.Never, + "created_by": Schema.optionalKey(Schema.String), + "operation": Schema.Union([ + ApplyPatchCreateFileOperation, + ApplyPatchUpdateFileOperation, + ApplyPatchDeleteFileOperation + ], { mode: "oneOf" }) + }).annotate({ + "description": + "A native `shell_call_output` item matching OpenAI's Responses API shape. Carries per-command stdout, stderr, and the exit/timeout outcome." + }), + Schema.Struct({ + "content": Schema.optionalKey(Schema.String), + "error": Schema.optionalKey( + Schema.String.annotate({ "description": "The error message if the fetch failed." }) + ), + "httpStatus": Schema.optionalKey( + Schema.Number.annotate({ "description": "The HTTP status code returned by the upstream URL fetch." }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ) + ), + "id": Schema.String, + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "title": Schema.optionalKey(Schema.String), + "type": Schema.Never, + "url": Schema.optionalKey(Schema.String), + "call_id": Schema.String, + "created_by": Schema.optionalKey(Schema.String), + "operation": Schema.Union([ + ApplyPatchCreateFileOperation, + ApplyPatchUpdateFileOperation, + ApplyPatchDeleteFileOperation + ], { mode: "oneOf" }) + }).annotate({ "description": "An openrouter:web_fetch server tool output item" }), + Schema.Struct({ + "id": Schema.String, + "query": Schema.optionalKey(Schema.String), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "call_id": Schema.String, + "created_by": Schema.optionalKey(Schema.String), + "operation": Schema.Union([ + ApplyPatchCreateFileOperation, + ApplyPatchUpdateFileOperation, + ApplyPatchDeleteFileOperation + ], { mode: "oneOf" }) + }).annotate({ "description": "An openrouter:tool_search server tool output item" }), + Schema.Struct({ + "action": Schema.optionalKey(Schema.Literals(["read", "write", "delete"])), + "id": Schema.String, + "key": Schema.optionalKey(Schema.String), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "value": Schema.optionalKey(Schema.Json.annotate({ "expected": "JSON value" })), + "call_id": Schema.String, + "created_by": Schema.optionalKey(Schema.String), + "operation": Schema.Union([ + ApplyPatchCreateFileOperation, + ApplyPatchUpdateFileOperation, + ApplyPatchDeleteFileOperation + ], { mode: "oneOf" }) + }).annotate({ "description": "An openrouter:memory server tool output item" }), + Schema.Struct({ + "id": Schema.String, + "serverLabel": Schema.optionalKey(Schema.String), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "toolName": Schema.optionalKey(Schema.String), + "type": Schema.Never, + "call_id": Schema.String, + "created_by": Schema.optionalKey(Schema.String), + "operation": Schema.Union([ + ApplyPatchCreateFileOperation, + ApplyPatchUpdateFileOperation, + ApplyPatchDeleteFileOperation + ], { mode: "oneOf" }) + }).annotate({ "description": "An openrouter:mcp server tool output item" }), + Schema.Struct({ + "arguments": Schema.optionalKey( + Schema.String.annotate({ + "description": "The JSON arguments submitted to the search tool (e.g. {\"query\":\"Claude\"})" + }) + ), + "id": Schema.String, + "query": Schema.optionalKey(Schema.String), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "call_id": Schema.String, + "created_by": Schema.optionalKey(Schema.String), + "operation": Schema.Union([ + ApplyPatchCreateFileOperation, + ApplyPatchUpdateFileOperation, + ApplyPatchDeleteFileOperation + ], { mode: "oneOf" }) + }).annotate({ "description": "An openrouter:experimental__search_models server tool output item" }), + Schema.Struct({ + "analysis": Schema.optionalKey(FusionAnalysisResult), + "error": Schema.optionalKey( + Schema.String.annotate({ + "description": "Error message when the fusion run did not produce an analysis result." + }) + ), + "failed_models": Schema.optionalKey( + Schema.Array(Schema.Struct({ + "error": Schema.String.annotate({ "description": "Error message describing why the model failed." }), + "model": Schema.String.annotate({ "description": "Slug of the analysis model that failed." }), + "status_code": Schema.optionalKey( + Schema.Number.annotate({ + "description": "HTTP status code from the upstream response, when available (e.g. 402, 429)." + }).check(Schema.isInt().annotate({ "expected": "an integer" })) + ) + })).annotate({ + "description": + "Models that were requested as part of the analysis panel but did not produce a response. Present when at least one requested analysis model failed. The fusion result is still usable but was produced from a degraded panel." + }) + ), + "failure_reason": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Typed failure reason when the fusion run failed. Possible values include: all_panels_failed, insufficient_credits, rate_limited, judge_not_valid_json, judge_schema_mismatch, judge_upstream_error, judge_empty_completion." + }) + ), + "id": Schema.String, + "responses": Schema.optionalKey( + Schema.Array(Schema.Struct({ "content": Schema.optionalKey(Schema.String), "model": Schema.String })) + .annotate({ + "description": + "Analysis models that produced a response in this fusion run, with each model's full panel content." + }) + ), + "sources": Schema.optionalKey( + Schema.Array(FusionSource).annotate({ + "description": + "Web pages the analysis panels and judge retrieved via web search during this fusion run, deduplicated by URL across the whole run. Present when at least one model cited a source." + }) + ), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "call_id": Schema.String, + "created_by": Schema.optionalKey(Schema.String), + "operation": Schema.Union([ + ApplyPatchCreateFileOperation, + ApplyPatchUpdateFileOperation, + ApplyPatchDeleteFileOperation + ], { mode: "oneOf" }) + }).annotate({ "description": "An openrouter:fusion server tool output item" }), + Schema.Struct({ + "advice": Schema.optionalKey( + Schema.String.annotate({ + "description": "The advisor model's response (the advice text returned to the executor)." + }) + ), + "error": Schema.optionalKey( + Schema.String.annotate({ "description": "Error message when the advisor call did not produce advice." }) + ), + "id": Schema.String, + "instance_name": Schema.optionalKey(Schema.String.annotate({ + "description": + "Provider-safe function name of the specific advisor instance that produced this item (e.g. `openrouter_advisor__1`). Present only when more than one advisor tool is configured; omitted for the default single advisor. Echo this field back unchanged so the advisor's cross-request memory stays namespaced to the correct instance. This identity is positional: it is derived from the index of the advisor entry in the request `tools` array, so clients must keep the order of advisor tool entries stable across requests in a conversation. Reordering or inserting advisor entries shifts these names and causes each advisor's cross-request memory to be attributed to the wrong instance." + })), + "model": Schema.optionalKey( + Schema.String.annotate({ "description": "Slug of the advisor model that was consulted." }) + ), + "prompt": Schema.optionalKey( + Schema.String.annotate({ "description": "The prompt the executor sent to the advisor." }) + ), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "call_id": Schema.String, + "created_by": Schema.optionalKey(Schema.String), + "operation": Schema.Union([ + ApplyPatchCreateFileOperation, + ApplyPatchUpdateFileOperation, + ApplyPatchDeleteFileOperation + ], { mode: "oneOf" }) + }).annotate({ "description": "An openrouter:advisor server tool output item" }), + Schema.Struct({ + "error": Schema.optionalKey( + Schema.String.annotate({ "description": "Error message when the subagent task did not produce an outcome." }) + ), + "id": Schema.String, + "instance_name": Schema.optionalKey(Schema.String.annotate({ + "description": + "Provider-safe function name of the specific subagent instance that produced this item (e.g. `openrouter_subagent__1`). Present only on items from non-default instances — the second and later subagent entries in the request `tools` array. The first (default) instance omits it, even when multiple subagents are configured. When a replayed item echoes this field back, the transcript rehydrates the call under that instance's tool. This identity is positional: it is derived from the index of the subagent entry in the request `tools` array, so keep the order of subagent entries stable across requests in a conversation." + })), + "model": Schema.optionalKey( + Schema.String.annotate({ "description": "Slug of the worker model that executed the task." }) + ), + "name": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Configured name of the subagent that executed the task (the `name` on its tool entry). Present only for named subagents; omitted for an unnamed (default) subagent." + }) + ), + "outcome": Schema.optionalKey( + Schema.String.annotate({ + "description": "The worker model's result (the outcome text returned to the delegating model)." + }) + ), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "task_description": Schema.optionalKey( + Schema.String.annotate({ "description": "The task description the delegating model sent to the worker." }) + ), + "task_name": Schema.optionalKey( + Schema.String.annotate({ "description": "The short task identifier the delegating model supplied." }) + ), + "type": Schema.Never, + "call_id": Schema.String, + "created_by": Schema.optionalKey(Schema.String), + "operation": Schema.Union([ + ApplyPatchCreateFileOperation, + ApplyPatchUpdateFileOperation, + ApplyPatchDeleteFileOperation + ], { mode: "oneOf" }) + }).annotate({ "description": "An openrouter:subagent server tool output item" }), + Schema.Struct({ + "error": Schema.optionalKey( + Schema.String.annotate({ "description": "Error message when the file operation failed." }) + ), + "file_id": Schema.optionalKey( + Schema.String.annotate({ "description": "The target file id supplied in the tool-call arguments." }) + ), + "filename": Schema.optionalKey( + Schema.String.annotate({ "description": "The target filename supplied in the tool-call arguments." }) + ), + "id": Schema.String, + "operation": Schema.Never, + "result": Schema.optionalKey( + Schema.String.annotate({ "description": "JSON-serialized result of the file operation." }) + ), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "call_id": Schema.String, + "created_by": Schema.optionalKey(Schema.String) + }).annotate({ "description": "An openrouter:files server tool output item" }), + Schema.Struct({ + "call_id": Schema.String, + "id": Schema.String, + "input": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ), + "type": Schema.Never, + "created_by": Schema.optionalKey(Schema.String), + "operation": Schema.Union([ + ApplyPatchCreateFileOperation, + ApplyPatchUpdateFileOperation, + ApplyPatchDeleteFileOperation + ], { mode: "oneOf" }), + "status": Schema.Literals(["in_progress", "completed"]) + }).annotate({ + "description": + "A call to a custom (freeform-grammar) tool created by the model — distinct from `function_call`. Used for tools like Codex CLI's `apply_patch` whose payload is opaque text rather than JSON arguments." + }) + ], { mode: "oneOf" }).annotate({ "description": "An output item from the response" }) + ], { mode: "oneOf" })), + "output_text": Schema.optionalKey(Schema.String), + "parallel_tool_calls": Schema.Boolean, + "presence_penalty": Schema.Union([ + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), + Schema.Null + ]).annotate({ "format": "double" }), + "previous_response_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "prompt": Schema.optionalKey(StoredPromptTemplate), + "prompt_cache_key": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "prompt_cache_options": Schema.optionalKey(PromptCacheOptions), + "reasoning": Schema.optionalKey(BaseReasoningConfig), + "safety_identifier": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "service_tier": Schema.optionalKey( + Schema.Union([ + Schema.Literal("auto"), + Schema.Literal("default"), + Schema.Literal("flex"), + Schema.Literal("priority"), + Schema.Literal("scale"), + Schema.Union([Schema.Null]) + ]) + ), + "status": OpenAIResponsesResponseStatus, + "store": Schema.optionalKey(Schema.Boolean), + "temperature": Schema.Union([ + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), + Schema.Null + ]).annotate({ "format": "double" }), + "text": Schema.optionalKey( + Schema.Struct({ + "format": Schema.optionalKey( + Schema.Union([ + Schema.Union([ + Schema.Struct({ "type": Schema.Literal("text") }).annotate({ "description": "Plain text response format" }), + Schema.Struct({ "type": Schema.Never }).annotate({ "description": "Plain text response format" }), + Schema.Struct({ + "description": Schema.optionalKey(Schema.String), + "name": Schema.String, + "schema": Schema.Struct({}), + "strict": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), + "type": Schema.Never + }).annotate({ "description": "Plain text response format" }) + ]).annotate({ "description": "Text response format configuration" }), + Schema.Union([ + Schema.Struct({ "type": Schema.Never }).annotate({ "description": "JSON object response format" }), + Schema.Struct({ "type": Schema.Literal("json_object") }).annotate({ + "description": "JSON object response format" + }), + Schema.Struct({ + "description": Schema.optionalKey(Schema.String), + "name": Schema.String, + "schema": Schema.Struct({}), + "strict": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), + "type": Schema.Never + }).annotate({ "description": "JSON object response format" }) + ]).annotate({ "description": "Text response format configuration" }), + Schema.Union([ + Schema.Struct({ + "type": Schema.Never, + "description": Schema.optionalKey(Schema.String), + "name": Schema.String, + "schema": Schema.Struct({}), + "strict": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])) + }).annotate({ "description": "JSON schema constrained response format" }), + Schema.Struct({ + "type": Schema.Never, + "description": Schema.optionalKey(Schema.String), + "name": Schema.String, + "schema": Schema.Struct({}), + "strict": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])) + }).annotate({ "description": "JSON schema constrained response format" }), + Schema.Struct({ + "description": Schema.optionalKey(Schema.String), + "name": Schema.String, + "schema": Schema.Struct({}), + "strict": Schema.optionalKey(Schema.Union([Schema.Union([Schema.Boolean]), Schema.Union([Schema.Null])])), + "type": Schema.Literal("json_schema") + }).annotate({ "description": "JSON schema constrained response format" }) + ]).annotate({ "description": "Text response format configuration" }) + ]).annotate({ "description": "Text response format configuration" }) + ), + "verbosity": Schema.optionalKey( + Schema.Union([ + Schema.Union([Schema.Literal("high")]), + Schema.Union([Schema.Literal("low")]), + Schema.Union([Schema.Literal("medium")]), + Schema.Union([Schema.Union([Schema.Null])]) + ]) + ) + }).annotate({ "description": "Text output configuration including format and verbosity" }) + ), + "tool_choice": OpenAIResponsesToolChoice, + "tools": Schema.Array(Schema.Union([ + Schema.Struct({ + "description": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "name": Schema.String, + "parameters": Schema.Union([ + Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })), + Schema.Null + ]), + "strict": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), + "type": Schema.Literal("function") + }).annotate({ "description": "Function tool definition" }), + Preview_WebSearchServerTool, + Preview_20250311_WebSearchServerTool, + Legacy_WebSearchServerTool, + WebSearchServerTool, + FileSearchServerTool, + ComputerUseServerTool, + CodeInterpreterServerTool, + McpServerTool, + ImageGenerationServerTool, + CodexLocalShellTool, + ShellServerTool, + ApplyPatchServerTool, + CustomTool, + NamespaceTool + ], { mode: "oneOf" })), + "top_logprobs": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "top_p": Schema.Union([ + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), + Schema.Null + ]).annotate({ "format": "double" }), + "truncation": Schema.optionalKey(Truncation), + "usage": Schema.optionalKey( + Schema.Union([Schema.Struct({ + "input_tokens": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "input_tokens_details": Schema.Struct({ + "cache_write_tokens": Schema.optionalKey( + Schema.Union([ + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))]), + Schema.Union([Schema.Null]) + ]) + ), + "cached_tokens": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + }), + "output_tokens": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "output_tokens_details": Schema.Struct({ + "reasoning_tokens": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + }), + "total_tokens": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "cost": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), Schema.Null]) + .annotate({ "description": "Cost of the completion", "format": "double" }) + ), + "cost_details": Schema.optionalKey(Schema.Struct({ + "upstream_inference_cost": Schema.optionalKey( + Schema.Union([ + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), + Schema.Null + ]).annotate({ "format": "double" }) + ), + "upstream_inference_input_cost": Schema.Number.annotate({ "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ), + "upstream_inference_output_cost": Schema.Number.annotate({ "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ) + })), + "is_byok": Schema.optionalKey( + Schema.Boolean.annotate({ + "description": "Whether a request was made using a Bring Your Own Key configuration" + }) + ), + "server_tool_use_details": Schema.optionalKey(ServerToolUseDetails) + })]).annotate({ "description": "Token usage information for the response" }) + ), + "user": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "error_type": Schema.optionalKey(ApiErrorType), + "openrouter_metadata": Schema.optionalKey(OpenRouterMetadata) +}).annotate({ + "description": "Complete non-streaming response from the Responses API", + "identifier": "OpenResponsesResult" +}) +export type StreamEventsResponseOutputItemAdded = { + readonly "item": OutputItems + readonly "output_index": number + readonly "sequence_number": number + readonly "type": "response.output_item.added" +} +export const StreamEventsResponseOutputItemAdded = Schema.Struct({ + "item": OutputItems, + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.output_item.added") +}).annotate({ + "description": "Event emitted when a new output item is added to the response", + "identifier": "StreamEventsResponseOutputItemAdded" +}) +export type StreamEventsResponseOutputItemDone = { + readonly "item": OutputItems + readonly "output_index": number + readonly "sequence_number": number + readonly "type": "response.output_item.done" +} +export const StreamEventsResponseOutputItemDone = Schema.Struct({ + "item": OutputItems, + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.output_item.done") +}).annotate({ + "description": "Event emitted when an output item is complete", + "identifier": "StreamEventsResponseOutputItemDone" +}) +export type MessagesContentBlockStartEvent = { + readonly "content_block": + | AnthropicTextBlock + | AnthropicToolUseBlock + | AnthropicThinkingBlock + | AnthropicRedactedThinkingBlock + | ORAnthropicServerToolUseBlock + | AnthropicWebSearchToolResult + | AnthropicWebFetchToolResult + | AnthropicCodeExecutionToolResult + | AnthropicBashCodeExecutionToolResult + | AnthropicTextEditorCodeExecutionToolResult + | AnthropicToolSearchToolResult + | AnthropicContainerUpload + | AnthropicCompactionBlock + | AnthropicAdvisorToolResult + | { readonly "content": string | null; readonly "type": "compaction" } + readonly "index": number + readonly "type": "content_block_start" +} +export const MessagesContentBlockStartEvent = Schema.Struct({ + "content_block": Schema.Union([ + AnthropicTextBlock, + AnthropicToolUseBlock, + AnthropicThinkingBlock, + AnthropicRedactedThinkingBlock, + ORAnthropicServerToolUseBlock, + AnthropicWebSearchToolResult, + AnthropicWebFetchToolResult, + AnthropicCodeExecutionToolResult, + AnthropicBashCodeExecutionToolResult, + AnthropicTextEditorCodeExecutionToolResult, + AnthropicToolSearchToolResult, + AnthropicContainerUpload, + AnthropicCompactionBlock, + AnthropicAdvisorToolResult, + Schema.Struct({ "content": Schema.Union([Schema.String, Schema.Null]), "type": Schema.Literal("compaction") }) + ]), + "index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("content_block_start") +}).annotate({ + "description": "Event sent when a new content block starts", + "identifier": "MessagesContentBlockStartEvent" +}) +export type ORAnthropicContentBlock = + | AnthropicTextBlock + | AnthropicToolUseBlock + | AnthropicThinkingBlock + | AnthropicRedactedThinkingBlock + | ORAnthropicServerToolUseBlock + | AnthropicWebSearchToolResult + | AnthropicWebFetchToolResult + | AnthropicCodeExecutionToolResult + | AnthropicBashCodeExecutionToolResult + | AnthropicTextEditorCodeExecutionToolResult + | AnthropicToolSearchToolResult + | AnthropicContainerUpload + | AnthropicCompactionBlock + | AnthropicAdvisorToolResult +export const ORAnthropicContentBlock = Schema.Union([ + AnthropicTextBlock, + AnthropicToolUseBlock, + AnthropicThinkingBlock, + AnthropicRedactedThinkingBlock, + ORAnthropicServerToolUseBlock, + AnthropicWebSearchToolResult, + AnthropicWebFetchToolResult, + AnthropicCodeExecutionToolResult, + AnthropicBashCodeExecutionToolResult, + AnthropicTextEditorCodeExecutionToolResult, + AnthropicToolSearchToolResult, + AnthropicContainerUpload, + AnthropicCompactionBlock, + AnthropicAdvisorToolResult +], { mode: "oneOf" }).annotate({ "identifier": "ORAnthropicContentBlock" }) +export type ImageGenCompletedEvent = { + readonly "b64_json": string + readonly "created": number + readonly "media_type"?: string + readonly "type": "image_generation.completed" + readonly "usage"?: ImageGenerationUsage +} +export const ImageGenCompletedEvent = Schema.Struct({ + "b64_json": Schema.String.annotate({ "description": "Base64-encoded final image data" }), + "created": Schema.Number.annotate({ "description": "Unix timestamp (seconds) when the image was generated" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ), + "media_type": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Media type (MIME type) of the image, e.g. `image/png`, `image/jpeg`, `image/webp`, `image/svg+xml`. May be omitted if the format could not be determined." + }) + ), + "type": Schema.Literal("image_generation.completed").annotate({ "description": "The event type" }), + "usage": Schema.optionalKey(ImageGenerationUsage) +}).annotate({ + "description": "Emitted when generation completes and the final image is available", + "identifier": "ImageGenCompletedEvent" +}) +export type ImageGenerationResponse = { + readonly "created": number + readonly "data": ReadonlyArray<{ readonly "b64_json": string; readonly "media_type"?: string }> + readonly "usage"?: ImageGenerationUsage +} +export const ImageGenerationResponse = Schema.Struct({ + "created": Schema.Number.annotate({ "description": "Unix timestamp (seconds) when the image was generated" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ), + "data": Schema.Array(Schema.Struct({ + "b64_json": Schema.String.annotate({ "description": "Base64-encoded image bytes" }), + "media_type": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Media type (MIME type) of the image, e.g. `image/png`, `image/jpeg`, `image/webp`, `image/svg+xml`. May be omitted if the format could not be determined." + }) + ) + })).annotate({ "description": "Generated images" }), + "usage": Schema.optionalKey(ImageGenerationUsage) +}).annotate({ "description": "Image generation response", "identifier": "ImageGenerationResponse" }) +export type ChatResult = { + readonly "choices": ReadonlyArray + readonly "created": number + readonly "id": string + readonly "model": string + readonly "object": "chat.completion" + readonly "openrouter_metadata"?: OpenRouterMetadata + readonly "service_tier"?: string | null + readonly "system_fingerprint": string | null + readonly "usage"?: ChatUsage +} +export const ChatResult = Schema.Struct({ + "choices": Schema.Array(ChatChoice).annotate({ "description": "List of completion choices" }), + "created": Schema.Number.annotate({ "description": "Unix timestamp of creation" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ), + "id": Schema.String.annotate({ "description": "Unique completion identifier" }), + "model": Schema.String.annotate({ "description": "Model used for completion" }), + "object": Schema.Literal("chat.completion"), + "openrouter_metadata": Schema.optionalKey(OpenRouterMetadata), + "service_tier": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "The service tier used by the upstream provider for this request" + }) + ), + "system_fingerprint": Schema.Union([Schema.String, Schema.Null]).annotate({ "description": "System fingerprint" }), + "usage": Schema.optionalKey(ChatUsage) +}).annotate({ "description": "Chat completion response", "identifier": "ChatResult" }) +export type ChatRequest = { + readonly "cache_control"?: AnthropicCacheControlDirective + readonly "debug"?: ChatDebugOptions + readonly "frequency_penalty"?: number | null + readonly "image_config"?: ImageConfig + readonly "logit_bias"?: { readonly [x: string]: number } | null + readonly "logprobs"?: boolean | null + readonly "max_completion_tokens"?: number | null + readonly "max_tokens"?: number | null + readonly "messages": ReadonlyArray + readonly "metadata"?: {} + readonly "min_p"?: number | null + readonly "modalities"?: ReadonlyArray<"text" | "image" | "audio"> + readonly "model"?: ModelName + readonly "models"?: ChatModelNames + readonly "parallel_tool_calls"?: boolean | null + readonly "plugins"?: ReadonlyArray< + | AutoRouterPlugin + | AutoBetaRouterPlugin + | ModerationPlugin + | WebSearchPlugin + | WebFetchPlugin + | FileParserPlugin + | ResponseHealingPlugin + | ContextCompressionPlugin + | ParetoRouterPlugin + | FusionPlugin + > + readonly "prediction"?: Prediction + readonly "presence_penalty"?: number | null + readonly "prompt_cache_key"?: string | null + readonly "prompt_cache_options"?: PromptCacheOptions + readonly "provider"?: ProviderPreferences + readonly "reasoning"?: { + readonly "effort"?: "max" | "xhigh" | "high" | "medium" | "low" | "minimal" | "none" | null + readonly "summary"?: ChatReasoningSummaryVerbosityEnum + } + readonly "reasoning_effort"?: "max" | "xhigh" | "high" | "medium" | "low" | "minimal" | "none" | null + readonly "repetition_penalty"?: number | null + readonly "response_format"?: + | ChatFormatTextConfig + | ChatFormatJsonObjectConfig + | ChatFormatJsonSchemaConfig + | ChatFormatGrammarConfig + | ChatFormatPythonConfig + readonly "route"?: DeprecatedRoute + readonly "seed"?: number | null + readonly "service_tier"?: "auto" | "default" | "flex" | "priority" | "scale" | null + readonly "session_id"?: string + readonly "stop"?: string | ReadonlyArray | null + readonly "stop_server_tools_when"?: StopServerToolsWhen + readonly "stream"?: boolean + readonly "stream_options"?: ChatStreamOptions + readonly "temperature"?: number | null + readonly "tool_choice"?: ChatToolChoice + readonly "tools"?: ReadonlyArray + readonly "top_a"?: number | null + readonly "top_k"?: number | null + readonly "top_logprobs"?: number | null + readonly "top_p"?: number | null + readonly "trace"?: TraceConfig + readonly "user"?: string +} +export const ChatRequest = Schema.Struct({ + "cache_control": Schema.optionalKey(AnthropicCacheControlDirective), + "debug": Schema.optionalKey(ChatDebugOptions), + "frequency_penalty": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), Schema.Null]) + .annotate({ "description": "Frequency penalty (-2.0 to 2.0)", "format": "double" }) + ), + "image_config": Schema.optionalKey(ImageConfig), + "logit_bias": Schema.optionalKey( + Schema.Union([ + Schema.Record( + Schema.String, + Schema.Number.annotate({ "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ) + ), + Schema.Null + ]).annotate({ "description": "Token logit bias adjustments" }) + ), + "logprobs": Schema.optionalKey( + Schema.Union([Schema.Boolean, Schema.Null]).annotate({ "description": "Return log probabilities" }) + ), + "max_completion_tokens": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]).annotate({ + "description": "Maximum tokens in completion" + }) + ), + "max_tokens": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]).annotate({ + "description": + "Maximum tokens (deprecated, use max_completion_tokens). Note: some providers enforce a minimum of 16." + }) + ), + "messages": Schema.Array(ChatMessages).annotate({ "description": "List of messages for the conversation" }).check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ), + "metadata": Schema.optionalKey( + Schema.Struct({}).annotate({ + "description": "Key-value pairs for additional object information (max 16 pairs, 64 char keys, 512 char values)" + }) + ), + "min_p": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), Schema.Null]) + .annotate({ + "description": + "Minimum probability threshold relative to the most likely token. Tokens with probability below min_p * (probability of top token) are filtered out. Not all providers support this parameter.", + "format": "double" + }) + ), + "modalities": Schema.optionalKey( + Schema.Array(Schema.Literals(["text", "image", "audio"])).annotate({ + "description": "Output modalities for the response. Supported values are \"text\", \"image\", and \"audio\"." + }) + ), + "model": Schema.optionalKey(ModelName), + "models": Schema.optionalKey(ChatModelNames), + "parallel_tool_calls": Schema.optionalKey( + Schema.Union([Schema.Boolean, Schema.Null]).annotate({ + "description": + "Whether to enable parallel function calling during tool use. When true, the model may generate multiple tool calls in a single response." + }) + ), + "plugins": Schema.optionalKey( + Schema.Array( + Schema.Union([ + AutoRouterPlugin, + AutoBetaRouterPlugin, + ModerationPlugin, + WebSearchPlugin, + WebFetchPlugin, + FileParserPlugin, + ResponseHealingPlugin, + ContextCompressionPlugin, + ParetoRouterPlugin, + FusionPlugin + ], { mode: "oneOf" }) + ).annotate({ "description": "Plugins you want to enable for this request, including their settings." }) + ), + "prediction": Schema.optionalKey(Prediction), + "presence_penalty": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), Schema.Null]) + .annotate({ "description": "Presence penalty (-2.0 to 2.0)", "format": "double" }) + ), + "prompt_cache_key": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "prompt_cache_options": Schema.optionalKey(PromptCacheOptions), + "provider": Schema.optionalKey(ProviderPreferences), + "reasoning": Schema.optionalKey( + Schema.Struct({ + "effort": Schema.optionalKey( + Schema.Union([ + Schema.Literal("max"), + Schema.Literal("xhigh"), + Schema.Literal("high"), + Schema.Literal("medium"), + Schema.Literal("low"), + Schema.Literal("minimal"), + Schema.Literal("none"), + Schema.Null + ]).annotate({ "description": "Constrains effort on reasoning for reasoning models" }) + ), + "summary": Schema.optionalKey(ChatReasoningSummaryVerbosityEnum) + }).annotate({ "description": "Configuration options for reasoning models" }) + ), + "reasoning_effort": Schema.optionalKey( + Schema.Union([ + Schema.Literal("max"), + Schema.Literal("xhigh"), + Schema.Literal("high"), + Schema.Literal("medium"), + Schema.Literal("low"), + Schema.Literal("minimal"), + Schema.Literal("none"), + Schema.Null + ]).annotate({ + "description": + "Shorthand for setting reasoning effort. Equivalent to setting reasoning.effort. Cannot be used simultaneously with reasoning.effort if they differ." + }) + ), + "repetition_penalty": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), Schema.Null]) + .annotate({ + "description": + "Penalizes tokens based on how much they have already appeared in the text. A value of 1.0 means no penalty. Values above 1.0 penalize repeated tokens more strongly. Not all providers support this parameter.", + "format": "double" + }) + ), + "response_format": Schema.optionalKey( + Schema.Union([ + ChatFormatTextConfig, + ChatFormatJsonObjectConfig, + ChatFormatJsonSchemaConfig, + ChatFormatGrammarConfig, + ChatFormatPythonConfig + ], { mode: "oneOf" }).annotate({ "description": "Response format configuration" }) + ), + "route": Schema.optionalKey(DeprecatedRoute), + "seed": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]).annotate({ + "description": "Random seed for deterministic outputs" + }) + ), + "service_tier": Schema.optionalKey( + Schema.Union([ + Schema.Literal("auto"), + Schema.Literal("default"), + Schema.Literal("flex"), + Schema.Literal("priority"), + Schema.Literal("scale"), + Schema.Null + ]).annotate({ "description": "The service tier to use for processing this request." }) + ), + "session_id": Schema.optionalKey( + Schema.String.annotate({ + "description": + "A unique identifier for grouping related requests (e.g., a conversation or agent workflow). When provided, OpenRouter uses it as the sticky routing key, routing all requests in the session to the same provider to maximize prompt cache hits. Also used for observability grouping. If provided in both the request body and the x-session-id header, the body value takes precedence. Maximum of 256 characters." + }).check(Schema.isMaxLength(256).annotate({ "expected": "a value with a length of at most 256" })) + ), + "stop": Schema.optionalKey( + Schema.Union([ + Schema.String, + Schema.Array(Schema.String).check( + Schema.isMaxLength(4).annotate({ "expected": "a value with a length of at most 4" }) + ), + Schema.Null + ]).annotate({ "description": "Stop sequences (up to 4)" }) + ), + "stop_server_tools_when": Schema.optionalKey(StopServerToolsWhen), + "stream": Schema.optionalKey(Schema.Boolean.annotate({ "description": "Enable streaming response" })), + "stream_options": Schema.optionalKey(ChatStreamOptions), + "temperature": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), Schema.Null]) + .annotate({ "description": "Sampling temperature (0-2)", "format": "double" }) + ), + "tool_choice": Schema.optionalKey(ChatToolChoice), + "tools": Schema.optionalKey( + Schema.Array(ChatFunctionTool).annotate({ "description": "Available tools for function calling" }) + ), + "top_a": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), Schema.Null]) + .annotate({ + "description": + "Consider only tokens with \"sufficiently high\" probabilities based on the probability of the most likely token. Not all providers support this parameter.", + "format": "double" + }) + ), + "top_k": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]).annotate({ + "description": + "Limits the model to choose from the top K most likely tokens at each step. A value of 1 means the model will always pick the most likely next token. Not all providers support this parameter." + }) + ), + "top_logprobs": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]).annotate({ + "description": "Number of top log probabilities to return (0-20)" + }) + ), + "top_p": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), Schema.Null]) + .annotate({ "description": "Nucleus sampling parameter (0-1)", "format": "double" }) + ), + "trace": Schema.optionalKey(TraceConfig), + "user": Schema.optionalKey(Schema.String.annotate({ + "description": + "Per-end-user identifier for abuse isolation. Use a stable ID, hash, or pseudonym. When a provider requires a user identity, OpenRouter folds it into the hashed identity sent upstream and never forwards it raw. If omitted, requests use an account-level identity, so provider policy blocks can affect the whole account." + })) +}).annotate({ "description": "Chat completion request parameters", "identifier": "ChatRequest" }) +export type ChatStreamChunk = { + readonly "choices": ReadonlyArray + readonly "created": number + readonly "error"?: { + readonly "code": number + readonly "message": string + readonly "metadata"?: { readonly "error_type": ApiErrorType; readonly "provider_code"?: string } + } + readonly "id": string + readonly "model": string + readonly "object": "chat.completion.chunk" + readonly "openrouter_metadata"?: OpenRouterMetadata + readonly "service_tier"?: string | null + readonly "system_fingerprint"?: string + readonly "usage"?: ChatUsage +} +export const ChatStreamChunk = Schema.Struct({ + "choices": Schema.Array(ChatStreamChoice).annotate({ "description": "List of streaming chunk choices" }), + "created": Schema.Number.annotate({ "description": "Unix timestamp of creation" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ), + "error": Schema.optionalKey( + Schema.Struct({ + "code": Schema.Number.annotate({ "description": "Error code", "format": "int32" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ), + "message": Schema.String.annotate({ "description": "Error message" }), + "metadata": Schema.optionalKey( + Schema.Struct({ + "error_type": ApiErrorType, + "provider_code": Schema.optionalKey( + Schema.String.annotate({ "description": "Upstream provider-specific error code, when available" }) + ) + }).annotate({ "description": "Structured error metadata" }) + ) + }).annotate({ "description": "Error information" }) + ), + "id": Schema.String.annotate({ "description": "Unique chunk identifier" }), + "model": Schema.String.annotate({ "description": "Model used for completion" }), + "object": Schema.Literal("chat.completion.chunk"), + "openrouter_metadata": Schema.optionalKey(OpenRouterMetadata), + "service_tier": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "The service tier used by the upstream provider for this request" + }) + ), + "system_fingerprint": Schema.optionalKey(Schema.String.annotate({ "description": "System fingerprint" })), + "usage": Schema.optionalKey(ChatUsage) +}).annotate({ "description": "Streaming chat completion chunk", "identifier": "ChatStreamChunk" }) +export type Inputs = string | Arrays_8 +export const Inputs = Schema.Union([Schema.String, Arrays_8]).annotate({ + "description": "Input for a response request - can be a string or array of items", + "identifier": "Inputs" +}) +export type CompletedEvent = { + readonly "response": BaseResponsesResult + readonly "sequence_number": number + readonly "type": "response.completed" +} +export const CompletedEvent = Schema.Struct({ + "response": BaseResponsesResult, + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.completed") +}).annotate({ + "description": "Event emitted when a response has completed successfully", + "identifier": "CompletedEvent" +}) +export type CreatedEvent = { + readonly "response": BaseResponsesResult + readonly "sequence_number": number + readonly "type": "response.created" +} +export const CreatedEvent = Schema.Struct({ + "response": BaseResponsesResult, + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.created") +}).annotate({ "description": "Event emitted when a response is created", "identifier": "CreatedEvent" }) +export type FailedEvent = { + readonly "response": BaseResponsesResult + readonly "sequence_number": number + readonly "type": "response.failed" +} +export const FailedEvent = Schema.Struct({ + "response": BaseResponsesResult, + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.failed") +}).annotate({ "description": "Event emitted when a response has failed", "identifier": "FailedEvent" }) +export type IncompleteEvent = { + readonly "response": BaseResponsesResult + readonly "sequence_number": number + readonly "type": "response.incomplete" +} +export const IncompleteEvent = Schema.Struct({ + "response": BaseResponsesResult, + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.incomplete") +}).annotate({ "description": "Event emitted when a response is incomplete", "identifier": "IncompleteEvent" }) +export type InProgressEvent = { + readonly "response": BaseResponsesResult + readonly "sequence_number": number + readonly "type": "response.in_progress" +} +export const InProgressEvent = Schema.Struct({ + "response": BaseResponsesResult, + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.in_progress") +}).annotate({ "description": "Event emitted when a response is in progress", "identifier": "InProgressEvent" }) +export type OpenResponsesCreatedEvent = { + readonly "response": OpenResponsesResult + readonly "sequence_number": number + readonly "type": "response.created" +} +export const OpenResponsesCreatedEvent = Schema.Struct({ + "response": OpenResponsesResult, + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.created") +}).annotate({ "description": "Event emitted when a response is created", "identifier": "OpenResponsesCreatedEvent" }) +export type OpenResponsesInProgressEvent = { + readonly "response": OpenResponsesResult + readonly "sequence_number": number + readonly "type": "response.in_progress" +} +export const OpenResponsesInProgressEvent = Schema.Struct({ + "response": OpenResponsesResult, + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.in_progress") +}).annotate({ + "description": "Event emitted when a response is in progress", + "identifier": "OpenResponsesInProgressEvent" +}) +export type StreamEventsResponseCompleted = { + readonly "response": OpenResponsesResult + readonly "sequence_number": number + readonly "type": "response.completed" +} +export const StreamEventsResponseCompleted = Schema.Struct({ + "response": OpenResponsesResult, + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.completed") +}).annotate({ + "description": "Event emitted when a response has completed successfully", + "identifier": "StreamEventsResponseCompleted" +}) +export type StreamEventsResponseFailed = { + readonly "response": OpenResponsesResult + readonly "sequence_number": number + readonly "type": "response.failed" +} +export const StreamEventsResponseFailed = Schema.Struct({ + "response": OpenResponsesResult, + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.failed") +}).annotate({ "description": "Event emitted when a response has failed", "identifier": "StreamEventsResponseFailed" }) +export type StreamEventsResponseIncomplete = { + readonly "response": OpenResponsesResult + readonly "sequence_number": number + readonly "type": "response.incomplete" +} +export const StreamEventsResponseIncomplete = Schema.Struct({ + "response": OpenResponsesResult, + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.incomplete") +}).annotate({ + "description": "Event emitted when a response is incomplete", + "identifier": "StreamEventsResponseIncomplete" +}) +export type BaseMessagesResult = { + readonly "container": AnthropicContainer + readonly "content": ReadonlyArray + readonly "id": string + readonly "model": string + readonly "role": "assistant" + readonly "stop_details": AnthropicRefusalStopDetails + readonly "stop_reason": ORAnthropicStopReason + readonly "stop_sequence": string | null + readonly "type": "message" + readonly "usage": { + readonly "cache_creation": AnthropicCacheCreation + readonly "cache_creation_input_tokens": number | null + readonly "cache_read_input_tokens": number | null + readonly "inference_geo": string | null + readonly "input_tokens": number + readonly "output_tokens": number + readonly "output_tokens_details": AnthropicOutputTokensDetails + readonly "server_tool_use": AnthropicServerToolUsage + readonly "service_tier": AnthropicServiceTier + readonly "iterations"?: ReadonlyArray + readonly "speed"?: AnthropicSpeed + } +} +export const BaseMessagesResult = Schema.Struct({ + "container": AnthropicContainer, + "content": Schema.Array(ORAnthropicContentBlock), + "id": Schema.String, + "model": Schema.String, + "role": Schema.Literal("assistant"), + "stop_details": AnthropicRefusalStopDetails, + "stop_reason": ORAnthropicStopReason, + "stop_sequence": Schema.Union([Schema.String, Schema.Null]), + "type": Schema.Literal("message"), + "usage": Schema.Struct({ + "cache_creation": AnthropicCacheCreation, + "cache_creation_input_tokens": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]), + "cache_read_input_tokens": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]), + "inference_geo": Schema.Union([Schema.String, Schema.Null]), + "input_tokens": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "output_tokens": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "output_tokens_details": AnthropicOutputTokensDetails, + "server_tool_use": AnthropicServerToolUsage, + "service_tier": AnthropicServiceTier, + "iterations": Schema.optionalKey(Schema.Array(AnthropicUsageIteration)), + "speed": Schema.optionalKey(AnthropicSpeed) + }) +}).annotate({ + "description": "Base Anthropic Messages API response before OpenRouter extensions", + "identifier": "BaseMessagesResult" +}) +export type MessagesResult = { + readonly "container": AnthropicContainer + readonly "content": ReadonlyArray + readonly "id": string + readonly "model": string + readonly "role": "assistant" + readonly "stop_details": AnthropicRefusalStopDetails + readonly "stop_reason": ORAnthropicStopReason + readonly "stop_sequence": string | null + readonly "type": "message" + readonly "usage": { + readonly "cache_creation": { + readonly "ephemeral_1h_input_tokens": number + readonly "ephemeral_5m_input_tokens": number + readonly [x: string]: Schema.Json + } | null + readonly "cache_creation_input_tokens": number | null + readonly "cache_read_input_tokens": number | null + readonly "inference_geo": string | null + readonly "input_tokens": number + readonly "output_tokens": number + readonly "output_tokens_details": { readonly "thinking_tokens": number; readonly [x: string]: Schema.Json } | null + readonly "server_tool_use": { + readonly "web_fetch_requests": number + readonly "web_search_requests": number + readonly [x: string]: Schema.Json + } | null + readonly "service_tier": "standard" | "priority" | "batch" | null + readonly "iterations"?: ReadonlyArray< + { + readonly "cache_creation"?: { + readonly "ephemeral_1h_input_tokens"?: number + readonly "ephemeral_5m_input_tokens"?: number + readonly [x: string]: Schema.Json + } | null + readonly "cache_creation_input_tokens"?: number + readonly "cache_read_input_tokens"?: number + readonly "input_tokens"?: number + readonly "output_tokens"?: number + readonly "type": "compaction" + } | { + readonly "cache_creation"?: { + readonly "ephemeral_1h_input_tokens"?: number + readonly "ephemeral_5m_input_tokens"?: number + readonly [x: string]: Schema.Json + } | null + readonly "cache_creation_input_tokens"?: number + readonly "cache_read_input_tokens"?: number + readonly "input_tokens"?: number + readonly "output_tokens"?: number + readonly "model"?: string + readonly "type": never + } | { + readonly "cache_creation"?: { + readonly "ephemeral_1h_input_tokens"?: number + readonly "ephemeral_5m_input_tokens"?: number + readonly [x: string]: Schema.Json + } | null + readonly "cache_creation_input_tokens"?: number + readonly "cache_read_input_tokens"?: number + readonly "input_tokens"?: number + readonly "output_tokens"?: number + readonly "model": string + readonly "type": never + } | { + readonly "cache_creation"?: { + readonly "ephemeral_1h_input_tokens"?: number + readonly "ephemeral_5m_input_tokens"?: number + readonly [x: string]: Schema.Json + } | null + readonly "cache_creation_input_tokens"?: number + readonly "cache_read_input_tokens"?: number + readonly "input_tokens"?: number + readonly "output_tokens"?: number + readonly "type": "compaction" + } | { + readonly "cache_creation"?: { + readonly "ephemeral_1h_input_tokens"?: number + readonly "ephemeral_5m_input_tokens"?: number + readonly [x: string]: Schema.Json + } | null + readonly "cache_creation_input_tokens"?: number + readonly "cache_read_input_tokens"?: number + readonly "input_tokens"?: number + readonly "output_tokens"?: number + readonly "type": never + readonly "model"?: string + } | { + readonly "cache_creation"?: { + readonly "ephemeral_1h_input_tokens"?: number + readonly "ephemeral_5m_input_tokens"?: number + readonly [x: string]: Schema.Json + } | null + readonly "cache_creation_input_tokens"?: number + readonly "cache_read_input_tokens"?: number + readonly "input_tokens"?: number + readonly "output_tokens"?: number + readonly "model"?: string + readonly "type": "message" + } | { + readonly "cache_creation"?: { + readonly "ephemeral_1h_input_tokens"?: number + readonly "ephemeral_5m_input_tokens"?: number + readonly [x: string]: Schema.Json + } | null + readonly "cache_creation_input_tokens"?: number + readonly "cache_read_input_tokens"?: number + readonly "input_tokens"?: number + readonly "output_tokens"?: number + readonly "model": string + readonly "type": never + } | { + readonly "cache_creation"?: { + readonly "ephemeral_1h_input_tokens"?: number + readonly "ephemeral_5m_input_tokens"?: number + readonly [x: string]: Schema.Json + } | null + readonly "cache_creation_input_tokens"?: number + readonly "cache_read_input_tokens"?: number + readonly "input_tokens"?: number + readonly "output_tokens"?: number + readonly "type": "message" + readonly "model"?: string + } | { + readonly "cache_creation"?: { + readonly "ephemeral_1h_input_tokens"?: number + readonly "ephemeral_5m_input_tokens"?: number + readonly [x: string]: Schema.Json + } | null + readonly "cache_creation_input_tokens"?: number + readonly "cache_read_input_tokens"?: number + readonly "input_tokens"?: number + readonly "output_tokens"?: number + readonly "type": never + readonly "model": string + } | { + readonly "cache_creation"?: { + readonly "ephemeral_1h_input_tokens"?: number + readonly "ephemeral_5m_input_tokens"?: number + readonly [x: string]: Schema.Json + } | null + readonly "cache_creation_input_tokens"?: number + readonly "cache_read_input_tokens"?: number + readonly "input_tokens"?: number + readonly "output_tokens"?: number + readonly "model": string + readonly "type": never + } | { + readonly "cache_creation"?: { + readonly "ephemeral_1h_input_tokens"?: number + readonly "ephemeral_5m_input_tokens"?: number + readonly [x: string]: Schema.Json + } | null + readonly "cache_creation_input_tokens"?: number + readonly "cache_read_input_tokens"?: number + readonly "input_tokens"?: number + readonly "output_tokens"?: number + readonly "model": string + readonly "type": "advisor_message" + } | { + readonly "cache_creation"?: { + readonly "ephemeral_1h_input_tokens"?: number + readonly "ephemeral_5m_input_tokens"?: number + readonly [x: string]: Schema.Json + } | null + readonly "cache_creation_input_tokens"?: number + readonly "cache_read_input_tokens"?: number + readonly "input_tokens"?: number + readonly "output_tokens"?: number + readonly "type": "advisor_message" + readonly "model": string + } | { + readonly "cache_creation"?: { + readonly "ephemeral_1h_input_tokens"?: number + readonly "ephemeral_5m_input_tokens"?: number + readonly [x: string]: Schema.Json + } | null + readonly "cache_creation_input_tokens"?: number + readonly "cache_read_input_tokens"?: number + readonly "input_tokens"?: number + readonly "output_tokens"?: number + readonly "type": "compaction" + } | { + readonly "cache_creation"?: { + readonly "ephemeral_1h_input_tokens"?: number + readonly "ephemeral_5m_input_tokens"?: number + readonly [x: string]: Schema.Json + } | null + readonly "cache_creation_input_tokens"?: number + readonly "cache_read_input_tokens"?: number + readonly "input_tokens"?: number + readonly "output_tokens"?: number + readonly "model"?: string + readonly "type": "message" + } | { + readonly "cache_creation"?: { + readonly "ephemeral_1h_input_tokens"?: number + readonly "ephemeral_5m_input_tokens"?: number + readonly [x: string]: Schema.Json + } | null + readonly "cache_creation_input_tokens"?: number + readonly "cache_read_input_tokens"?: number + readonly "input_tokens"?: number + readonly "output_tokens"?: number + readonly "model": string + readonly "type": "advisor_message" + } | { + readonly "cache_creation"?: { + readonly "ephemeral_1h_input_tokens"?: number + readonly "ephemeral_5m_input_tokens"?: number + readonly [x: string]: Schema.Json + } | null + readonly "cache_creation_input_tokens"?: number + readonly "cache_read_input_tokens"?: number + readonly "input_tokens"?: number + readonly "output_tokens"?: number + readonly "type": string + } + > + readonly "speed"?: "fast" | "standard" | null + readonly "cost"?: number | null + readonly "cost_details"?: CostDetails + readonly "is_byok"?: boolean + } + readonly "context_management"?: { + readonly "applied_edits": ReadonlyArray<{ readonly "type": string }> + readonly [x: string]: Schema.Json + } | null + readonly "openrouter_metadata"?: OpenRouterMetadata + readonly "provider"?: ProviderName +} +export const MessagesResult = Schema.Struct({ + "container": AnthropicContainer, + "content": Schema.Array(ORAnthropicContentBlock), + "id": Schema.String, + "model": Schema.String, + "role": Schema.Literal("assistant"), + "stop_details": AnthropicRefusalStopDetails, + "stop_reason": ORAnthropicStopReason, + "stop_sequence": Schema.Union([Schema.String, Schema.Null]), + "type": Schema.Literal("message"), + "usage": Schema.Struct({ + "cache_creation": Schema.Union([ + Schema.Union([ + Schema.StructWithRest( + Schema.Struct({ + "ephemeral_1h_input_tokens": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "ephemeral_5m_input_tokens": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] + ) + ]), + Schema.Union([Schema.Null]) + ]), + "cache_creation_input_tokens": Schema.Union([ + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))]), + Schema.Union([Schema.Null]) + ]), + "cache_read_input_tokens": Schema.Union([ + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))]), + Schema.Union([Schema.Null]) + ]), + "inference_geo": Schema.Union([Schema.Union([Schema.String]), Schema.Union([Schema.Null])]), + "input_tokens": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "output_tokens": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "output_tokens_details": Schema.Union([ + Schema.Union([ + Schema.StructWithRest( + Schema.Struct({ + "thinking_tokens": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] + ) + ]), + Schema.Union([Schema.Null]) + ]), + "server_tool_use": Schema.Union([ + Schema.Union([ + Schema.StructWithRest( + Schema.Struct({ + "web_fetch_requests": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "web_search_requests": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] + ) + ]), + Schema.Union([Schema.Null]) + ]), + "service_tier": Schema.Union([ + Schema.Union([Schema.Literal("standard")]), + Schema.Union([Schema.Literal("priority")]), + Schema.Union([Schema.Literal("batch")]), + Schema.Union([Schema.Union([Schema.Null])]) + ]), + "iterations": Schema.optionalKey(Schema.Array(Schema.Union([ + Schema.Union([ + Schema.Struct({ + "cache_creation": Schema.optionalKey( + Schema.Union([ + Schema.Union([ + Schema.StructWithRest( + Schema.Struct({ + "ephemeral_1h_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "ephemeral_5m_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ) + }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] + ) + ]), + Schema.Union([Schema.Null]) + ]) + ), + "cache_creation_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "cache_read_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "output_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "type": Schema.Literal("compaction") + }), + Schema.Struct({ + "cache_creation": Schema.optionalKey( + Schema.Union([ + Schema.Union([ + Schema.StructWithRest( + Schema.Struct({ + "ephemeral_1h_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "ephemeral_5m_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ) + }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] + ) + ]), + Schema.Union([Schema.Null]) + ]) + ), + "cache_creation_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "cache_read_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "output_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "model": Schema.optionalKey(Schema.String), + "type": Schema.Never + }), + Schema.Struct({ + "cache_creation": Schema.optionalKey( + Schema.Union([ + Schema.Union([ + Schema.StructWithRest( + Schema.Struct({ + "ephemeral_1h_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "ephemeral_5m_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ) + }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] + ) + ]), + Schema.Union([Schema.Null]) + ]) + ), + "cache_creation_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "cache_read_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "output_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "model": Schema.String, + "type": Schema.Never + }), + Schema.Struct({ + "cache_creation": Schema.optionalKey( + Schema.Union([ + Schema.Union([ + Schema.StructWithRest( + Schema.Struct({ + "ephemeral_1h_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "ephemeral_5m_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ) + }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] + ) + ]), + Schema.Union([Schema.Null]) + ]) + ), + "cache_creation_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "cache_read_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "output_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "type": Schema.Literal("compaction") + }) + ]), + Schema.Union([ + Schema.Struct({ + "cache_creation": Schema.optionalKey( + Schema.Union([ + Schema.Union([ + Schema.StructWithRest( + Schema.Struct({ + "ephemeral_1h_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "ephemeral_5m_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ) + }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] + ) + ]), + Schema.Union([Schema.Null]) + ]) + ), + "cache_creation_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "cache_read_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "output_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "type": Schema.Never, + "model": Schema.optionalKey(Schema.String) + }), + Schema.Struct({ + "cache_creation": Schema.optionalKey( + Schema.Union([ + Schema.Union([ + Schema.StructWithRest( + Schema.Struct({ + "ephemeral_1h_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "ephemeral_5m_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ) + }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] + ) + ]), + Schema.Union([Schema.Null]) + ]) + ), + "cache_creation_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "cache_read_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "output_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "model": Schema.optionalKey(Schema.String), + "type": Schema.Literal("message") + }), + Schema.Struct({ + "cache_creation": Schema.optionalKey( + Schema.Union([ + Schema.Union([ + Schema.StructWithRest( + Schema.Struct({ + "ephemeral_1h_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "ephemeral_5m_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ) + }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] + ) + ]), + Schema.Union([Schema.Null]) + ]) + ), + "cache_creation_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "cache_read_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "output_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "model": Schema.String, + "type": Schema.Never + }), + Schema.Struct({ + "cache_creation": Schema.optionalKey( + Schema.Union([ + Schema.Union([ + Schema.StructWithRest( + Schema.Struct({ + "ephemeral_1h_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "ephemeral_5m_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ) + }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] + ) + ]), + Schema.Union([Schema.Null]) + ]) + ), + "cache_creation_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "cache_read_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "output_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "type": Schema.Literal("message"), + "model": Schema.optionalKey(Schema.String) + }) + ]), + Schema.Union([ + Schema.Struct({ + "cache_creation": Schema.optionalKey( + Schema.Union([ + Schema.Union([ + Schema.StructWithRest( + Schema.Struct({ + "ephemeral_1h_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "ephemeral_5m_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ) + }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] + ) + ]), + Schema.Union([Schema.Null]) + ]) + ), + "cache_creation_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "cache_read_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "output_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "type": Schema.Never, + "model": Schema.String + }), + Schema.Struct({ + "cache_creation": Schema.optionalKey( + Schema.Union([ + Schema.Union([ + Schema.StructWithRest( + Schema.Struct({ + "ephemeral_1h_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "ephemeral_5m_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ) + }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] + ) + ]), + Schema.Union([Schema.Null]) + ]) + ), + "cache_creation_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "cache_read_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "output_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "model": Schema.String, + "type": Schema.Never + }), + Schema.Struct({ + "cache_creation": Schema.optionalKey( + Schema.Union([ + Schema.Union([ + Schema.StructWithRest( + Schema.Struct({ + "ephemeral_1h_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "ephemeral_5m_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ) + }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] + ) + ]), + Schema.Union([Schema.Null]) + ]) + ), + "cache_creation_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "cache_read_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "output_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "model": Schema.String, + "type": Schema.Literal("advisor_message") + }), + Schema.Struct({ + "cache_creation": Schema.optionalKey( + Schema.Union([ + Schema.Union([ + Schema.StructWithRest( + Schema.Struct({ + "ephemeral_1h_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "ephemeral_5m_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ) + }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] + ) + ]), + Schema.Union([Schema.Null]) + ]) + ), + "cache_creation_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "cache_read_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "output_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "type": Schema.Literal("advisor_message"), + "model": Schema.String + }) + ]), + Schema.Union([ + Schema.Struct({ + "cache_creation": Schema.optionalKey( + Schema.Union([ + Schema.Union([ + Schema.StructWithRest( + Schema.Struct({ + "ephemeral_1h_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "ephemeral_5m_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ) + }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] + ) + ]), + Schema.Union([Schema.Null]) + ]) + ), + "cache_creation_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "cache_read_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "output_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "type": Schema.Literal("compaction") + }), + Schema.Struct({ + "cache_creation": Schema.optionalKey( + Schema.Union([ + Schema.Union([ + Schema.StructWithRest( + Schema.Struct({ + "ephemeral_1h_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "ephemeral_5m_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ) + }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] + ) + ]), + Schema.Union([Schema.Null]) + ]) + ), + "cache_creation_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "cache_read_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "output_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "model": Schema.optionalKey(Schema.String), + "type": Schema.Literal("message") + }), + Schema.Struct({ + "cache_creation": Schema.optionalKey( + Schema.Union([ + Schema.Union([ + Schema.StructWithRest( + Schema.Struct({ + "ephemeral_1h_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "ephemeral_5m_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ) + }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] + ) + ]), + Schema.Union([Schema.Null]) + ]) + ), + "cache_creation_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "cache_read_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "output_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "model": Schema.String, + "type": Schema.Literal("advisor_message") + }), + Schema.Struct({ + "cache_creation": Schema.optionalKey( + Schema.Union([ + Schema.Union([ + Schema.StructWithRest( + Schema.Struct({ + "ephemeral_1h_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "ephemeral_5m_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ) + }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] + ) + ]), + Schema.Union([Schema.Null]) + ]) + ), + "cache_creation_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "cache_read_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "output_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "type": Schema.String + }) + ]) + ]))), + "speed": Schema.optionalKey( + Schema.Union([Schema.Literal("fast"), Schema.Literal("standard"), Schema.Union([Schema.Null])]) + ), + "cost": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), Schema.Null]) + .annotate({ "format": "double" }) + ), + "cost_details": Schema.optionalKey(CostDetails), + "is_byok": Schema.optionalKey(Schema.Boolean) + }), + "context_management": Schema.optionalKey( + Schema.Union([ + Schema.StructWithRest( + Schema.Struct({ "applied_edits": Schema.Array(Schema.Struct({ "type": Schema.String })) }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] + ), + Schema.Null + ]) + ), + "openrouter_metadata": Schema.optionalKey(OpenRouterMetadata), + "provider": Schema.optionalKey(ProviderName) +}).annotate({ + "description": "Non-streaming response from the Anthropic Messages API with OpenRouter extensions", + "identifier": "MessagesResult" +}) +export type MessagesStartEvent = { + readonly "message": { + readonly "container": AnthropicContainer + readonly "content": ReadonlyArray + readonly "id": string + readonly "model": string + readonly "provider"?: + | "AnyScale" + | "Atoma" + | "Cent-ML" + | "CrofAI" + | "Enfer" + | "GoPomelo" + | "HuggingFace" + | "Hyperbolic" + | "Hyperbolic 2" + | "InoCloud" + | "Kluster" + | "Lambda" + | "Lepton" + | "Lynn 2" + | "Lynn" + | "Mancer" + | "Modal" + | "Nineteen" + | "OctoAI" + | "Recursal" + | "Reflection" + | "Replicate" + | "SambaNova 2" + | "SF Compute" + | "Targon" + | "Together 2" + | "Ubicloud" + | "01.AI" + | "AkashML" + | "AI21" + | "AionLabs" + | "Alibaba" + | "Ambient" + | "Baidu" + | "Amazon Bedrock" + | "Amazon Nova" + | "Anthropic" + | "Arcee AI" + | "AtlasCloud" + | "Avian" + | "Azure" + | "BaseTen" + | "BytePlus" + | "Black Forest Labs" + | "Cerebras" + | "Chutes" + | "Cirrascale" + | "Clarifai" + | "Cloudflare" + | "Cohere" + | "CoreWeave" + | "Crucible" + | "Crusoe" + | "Darkbloom" + | "Decart" + | "Deepgram" + | "DeepInfra" + | "DeepSeek" + | "DekaLLM" + | "DigitalOcean" + | "Featherless" + | "Fireworks" + | "Fish Audio" + | "Friendli" + | "GMICloud" + | "Google" + | "Google AI Studio" + | "Groq" + | "HeyGen" + | "Inception" + | "Inceptron" + | "InferenceNet" + | "Ionstream" + | "Infermatic" + | "Io Net" + | "Inferact vLLM" + | "Inflection" + | "Liquid" + | "Mara" + | "Mancer 2" + | "Meta" + | "Minimax" + | "ModelRun" + | "Mistral" + | "Modular" + | "Moonshot AI" + | "Morph" + | "NCompass" + | "Nebius" + | "Nex AGI" + | "NextBit" + | "Novita" + | "Nvidia" + | "OpenAI" + | "OpenInference" + | "Parasail" + | "Poolside" + | "Perceptron" + | "Perplexity" + | "Phala" + | "Recraft" + | "Reka" + | "Relace" + | "Sail Research" + | "Sakana AI" + | "SambaNova" + | "Seed" + | "SiliconFlow" + | "Sourceful" + | "StepFun" + | "Stealth" + | "StreamLake" + | "Switchpoint" + | "Tencent" + | "Tenstorrent" + | "Together" + | "Upstage" + | "Venice" + | "Wafer" + | "WandB" + | "Quiver" + | "Krea" + | "Runway" + | "Xiaomi" + | "xAI" + | "Z.AI" + | "FakeProvider" + readonly "role": "assistant" + readonly "stop_details": AnthropicRefusalStopDetails + readonly "stop_reason": Schema.Json + readonly "stop_sequence": Schema.Json + readonly "type": "message" + readonly "usage": { + readonly "cache_creation": AnthropicCacheCreation + readonly "cache_creation_input_tokens": number | null + readonly "cache_read_input_tokens": number | null + readonly "inference_geo": string | null + readonly "input_tokens": number + readonly "output_tokens": number + readonly "output_tokens_details": AnthropicOutputTokensDetails + readonly "server_tool_use": AnthropicServerToolUsage + readonly "service_tier": AnthropicServiceTier + readonly "iterations"?: ReadonlyArray + readonly "speed"?: AnthropicSpeed + } + } + readonly "type": "message_start" +} +export const MessagesStartEvent = Schema.Struct({ + "message": Schema.Struct({ + "container": AnthropicContainer, + "content": Schema.Array(ORAnthropicContentBlock), + "id": Schema.String, + "model": Schema.String, + "provider": Schema.optionalKey( + Schema.Literals([ + "AnyScale", + "Atoma", + "Cent-ML", + "CrofAI", + "Enfer", + "GoPomelo", + "HuggingFace", + "Hyperbolic", + "Hyperbolic 2", + "InoCloud", + "Kluster", + "Lambda", + "Lepton", + "Lynn 2", + "Lynn", + "Mancer", + "Modal", + "Nineteen", + "OctoAI", + "Recursal", + "Reflection", + "Replicate", + "SambaNova 2", + "SF Compute", + "Targon", + "Together 2", + "Ubicloud", + "01.AI", + "AkashML", + "AI21", + "AionLabs", + "Alibaba", + "Ambient", + "Baidu", + "Amazon Bedrock", + "Amazon Nova", + "Anthropic", + "Arcee AI", + "AtlasCloud", + "Avian", + "Azure", + "BaseTen", + "BytePlus", + "Black Forest Labs", + "Cerebras", + "Chutes", + "Cirrascale", + "Clarifai", + "Cloudflare", + "Cohere", + "CoreWeave", + "Crucible", + "Crusoe", + "Darkbloom", + "Decart", + "Deepgram", + "DeepInfra", + "DeepSeek", + "DekaLLM", + "DigitalOcean", + "Featherless", + "Fireworks", + "Fish Audio", + "Friendli", + "GMICloud", + "Google", + "Google AI Studio", + "Groq", + "HeyGen", + "Inception", + "Inceptron", + "InferenceNet", + "Ionstream", + "Infermatic", + "Io Net", + "Inferact vLLM", + "Inflection", + "Liquid", + "Mara", + "Mancer 2", + "Meta", + "Minimax", + "ModelRun", + "Mistral", + "Modular", + "Moonshot AI", + "Morph", + "NCompass", + "Nebius", + "Nex AGI", + "NextBit", + "Novita", + "Nvidia", + "OpenAI", + "OpenInference", + "Parasail", + "Poolside", + "Perceptron", + "Perplexity", + "Phala", + "Recraft", + "Reka", + "Relace", + "Sail Research", + "Sakana AI", + "SambaNova", + "Seed", + "SiliconFlow", + "Sourceful", + "StepFun", + "Stealth", + "StreamLake", + "Switchpoint", + "Tencent", + "Tenstorrent", + "Together", + "Upstage", + "Venice", + "Wafer", + "WandB", + "Quiver", + "Krea", + "Runway", + "Xiaomi", + "xAI", + "Z.AI", + "FakeProvider" + ]) + ), + "role": Schema.Literal("assistant"), + "stop_details": AnthropicRefusalStopDetails, + "stop_reason": Schema.Json.annotate({ "expected": "JSON value" }), + "stop_sequence": Schema.Json.annotate({ "expected": "JSON value" }), + "type": Schema.Literal("message"), + "usage": Schema.Struct({ + "cache_creation": AnthropicCacheCreation, + "cache_creation_input_tokens": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]), + "cache_read_input_tokens": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]), + "inference_geo": Schema.Union([Schema.String, Schema.Null]), + "input_tokens": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "output_tokens": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "output_tokens_details": AnthropicOutputTokensDetails, + "server_tool_use": AnthropicServerToolUsage, + "service_tier": AnthropicServiceTier, + "iterations": Schema.optionalKey(Schema.Array(AnthropicUsageIteration)), + "speed": Schema.optionalKey(AnthropicSpeed) + }) + }), + "type": Schema.Literal("message_start") +}).annotate({ "description": "Event sent at the start of a streaming message", "identifier": "MessagesStartEvent" }) +export type ImageStreamingResponse = { + readonly "data": + | ImageGenPartialImageEvent + | ImageGenTextChunkEvent + | ImageGenCompletedEvent + | ImageGenStreamErrorEvent +} +export const ImageStreamingResponse = Schema.Struct({ + "data": Schema.Union([ + ImageGenPartialImageEvent, + ImageGenTextChunkEvent, + ImageGenCompletedEvent, + ImageGenStreamErrorEvent + ]) +}).annotate({ "identifier": "ImageStreamingResponse" }) +export type ChatStreamingResponse = { readonly "data": ChatStreamChunk } +export const ChatStreamingResponse = Schema.Struct({ "data": ChatStreamChunk }).annotate({ + "identifier": "ChatStreamingResponse" +}) +export type ResponsesRequest = { + readonly "background"?: boolean | null + readonly "cache_control"?: AnthropicCacheControlDirective + readonly "debug"?: ChatDebugOptions + readonly "frequency_penalty"?: number | null + readonly "image_config"?: ImageConfig + readonly "include"?: ReadonlyArray | null + readonly "input"?: Inputs + readonly "instructions"?: string | null + readonly "max_output_tokens"?: number | null + readonly "max_tool_calls"?: number | null + readonly "metadata"?: RequestMetadata + readonly "modalities"?: ReadonlyArray + readonly "model"?: string + readonly "models"?: ReadonlyArray + readonly "parallel_tool_calls"?: boolean | null + readonly "plugins"?: ReadonlyArray< + | AutoRouterPlugin + | AutoBetaRouterPlugin + | ModerationPlugin + | WebSearchPlugin + | WebFetchPlugin + | FileParserPlugin + | ResponseHealingPlugin + | ContextCompressionPlugin + | ParetoRouterPlugin + | FusionPlugin + > + readonly "presence_penalty"?: number | null + readonly "previous_response_id"?: Schema.Json + readonly "prompt"?: StoredPromptTemplate + readonly "prompt_cache_key"?: string | null + readonly "prompt_cache_options"?: PromptCacheOptions + readonly "provider"?: ProviderPreferences + readonly "reasoning"?: ReasoningConfig + readonly "route"?: DeprecatedRoute + readonly "safety_identifier"?: string | null + readonly "service_tier"?: "auto" | "default" | "flex" | "priority" | "scale" | null + readonly "session_id"?: string + readonly "stop_server_tools_when"?: StopServerToolsWhen + readonly "store"?: false + readonly "stream"?: boolean + readonly "temperature"?: number | null + readonly "text"?: TextExtendedConfig + readonly "tool_choice"?: OpenAIResponsesToolChoice + readonly "tools"?: ReadonlyArray< + | { + readonly "description"?: string | null + readonly "name": string + readonly "parameters": { readonly [x: string]: Schema.Json } | null + readonly "strict"?: boolean | null + readonly "type": "function" + } + | Preview_WebSearchServerTool + | Preview_20250311_WebSearchServerTool + | Legacy_WebSearchServerTool + | WebSearchServerTool + | FileSearchServerTool + | ComputerUseServerTool + | CodeInterpreterServerTool + | McpServerTool + | ImageGenerationServerTool + | CodexLocalShellTool + | ShellServerTool + | ApplyPatchServerTool + | CustomTool + | NamespaceTool + | AdvisorServerTool_OpenRouter + | SubagentServerTool_OpenRouter + | DatetimeServerTool + | FilesServerTool + | FusionServerTool_OpenRouter + | ImageGenerationServerTool_OpenRouter + | SearchModelsServerTool_OpenRouter + | WebFetchServerTool + | WebSearchServerTool_OpenRouter + | ApplyPatchServerTool_OpenRouter + | BashServerTool + | ShellServerTool_OpenRouter + > + readonly "top_k"?: number + readonly "top_logprobs"?: number | null + readonly "top_p"?: number | null + readonly "trace"?: TraceConfig + readonly "truncation"?: OpenAIResponsesTruncation + readonly "user"?: string +} +export const ResponsesRequest = Schema.Struct({ + "background": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), + "cache_control": Schema.optionalKey(AnthropicCacheControlDirective), + "debug": Schema.optionalKey(ChatDebugOptions), + "frequency_penalty": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), Schema.Null]) + .annotate({ "format": "double" }) + ), + "image_config": Schema.optionalKey(ImageConfig), + "include": Schema.optionalKey(Schema.Union([Schema.Array(ResponseIncludesEnum), Schema.Null])), + "input": Schema.optionalKey(Inputs), + "instructions": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "max_output_tokens": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]) + ), + "max_tool_calls": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]).annotate({ + "description": + "Maximum number of server-tool (e.g. `openrouter:web_search`) agent steps the model may take during a request. Defaults to 30, which is also the maximum. Ignored when `stop_server_tools_when` is set." + }) + ), + "metadata": Schema.optionalKey(RequestMetadata), + "modalities": Schema.optionalKey( + Schema.Array(OutputModalityEnum).annotate({ + "description": "Output modalities for the response. Supported values are \"text\" and \"image\"." + }) + ), + "model": Schema.optionalKey(Schema.String), + "models": Schema.optionalKey(Schema.Array(Schema.String)), + "parallel_tool_calls": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), + "plugins": Schema.optionalKey( + Schema.Array( + Schema.Union([ + AutoRouterPlugin, + AutoBetaRouterPlugin, + ModerationPlugin, + WebSearchPlugin, + WebFetchPlugin, + FileParserPlugin, + ResponseHealingPlugin, + ContextCompressionPlugin, + ParetoRouterPlugin, + FusionPlugin + ], { mode: "oneOf" }) + ).annotate({ "description": "Plugins you want to enable for this request, including their settings." }) + ), + "presence_penalty": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), Schema.Null]) + .annotate({ "format": "double" }) + ), + "previous_response_id": Schema.optionalKey( + Schema.Json.annotate({ + "expected": "JSON value", + "description": + "Not supported. The Responses API is stateless: no responses are stored, so a previous response cannot be referenced. Requests with a non-null value are rejected with a 400 error. Send the full conversation history in `input` instead." + }) + ), + "prompt": Schema.optionalKey(StoredPromptTemplate), + "prompt_cache_key": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "prompt_cache_options": Schema.optionalKey(PromptCacheOptions), + "provider": Schema.optionalKey(ProviderPreferences), + "reasoning": Schema.optionalKey(ReasoningConfig), + "route": Schema.optionalKey(DeprecatedRoute), + "safety_identifier": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": + "Recommended per-end-user identifier for abuse isolation. Use a stable ID, hash, or pseudonym. When a provider requires a user identity, OpenRouter folds it into the hashed identity sent upstream and never forwards it raw. If omitted, requests use an account-level identity, so provider policy blocks can affect the whole account." + }) + ), + "service_tier": Schema.optionalKey( + Schema.Union([ + Schema.Literal("auto"), + Schema.Literal("default"), + Schema.Literal("flex"), + Schema.Literal("priority"), + Schema.Literal("scale"), + Schema.Null + ]) + ), + "session_id": Schema.optionalKey( + Schema.String.annotate({ + "description": + "A unique identifier for grouping related requests (e.g., a conversation or agent workflow). When provided, OpenRouter uses it as the sticky routing key, routing all requests in the session to the same provider to maximize prompt cache hits. Also used for observability grouping. If provided in both the request body and the x-session-id header, the body value takes precedence. Maximum of 256 characters." + }).check(Schema.isMaxLength(256).annotate({ "expected": "a value with a length of at most 256" })) + ), + "stop_server_tools_when": Schema.optionalKey(StopServerToolsWhen), + "store": Schema.optionalKey(Schema.Literal(false)), + "stream": Schema.optionalKey(Schema.Boolean), + "temperature": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), Schema.Null]) + .annotate({ "format": "double" }) + ), + "text": Schema.optionalKey(TextExtendedConfig), + "tool_choice": Schema.optionalKey(OpenAIResponsesToolChoice), + "tools": Schema.optionalKey(Schema.Array(Schema.Union([ + Schema.Struct({ + "description": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "name": Schema.String, + "parameters": Schema.Union([ + Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })), + Schema.Null + ]), + "strict": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), + "type": Schema.Literal("function") + }).annotate({ "description": "Function tool definition" }), + Preview_WebSearchServerTool, + Preview_20250311_WebSearchServerTool, + Legacy_WebSearchServerTool, + WebSearchServerTool, + FileSearchServerTool, + ComputerUseServerTool, + CodeInterpreterServerTool, + McpServerTool, + ImageGenerationServerTool, + CodexLocalShellTool, + ShellServerTool, + ApplyPatchServerTool, + CustomTool, + NamespaceTool, + AdvisorServerTool_OpenRouter, + SubagentServerTool_OpenRouter, + DatetimeServerTool, + FilesServerTool, + FusionServerTool_OpenRouter, + ImageGenerationServerTool_OpenRouter, + SearchModelsServerTool_OpenRouter, + WebFetchServerTool, + WebSearchServerTool_OpenRouter, + ApplyPatchServerTool_OpenRouter, + BashServerTool, + ShellServerTool_OpenRouter + ]))), + "top_k": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "top_logprobs": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]) + ), + "top_p": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), Schema.Null]) + .annotate({ "format": "double" }) + ), + "trace": Schema.optionalKey(TraceConfig), + "truncation": Schema.optionalKey(OpenAIResponsesTruncation), + "user": Schema.optionalKey( + Schema.String.annotate({ + "description": + "A unique identifier representing your end-user, which helps distinguish between different users of your app. This allows your app to identify specific users in case of abuse reports, preventing your entire app from being affected by the actions of individual users. Maximum of 256 characters." + }).check(Schema.isMaxLength(256).annotate({ "expected": "a value with a length of at most 256" })) + ) +}).annotate({ "description": "Request schema for Responses endpoint", "identifier": "ResponsesRequest" }) +export type StreamEvents = + | OpenResponsesCreatedEvent + | OpenResponsesInProgressEvent + | StreamEventsResponseCompleted + | StreamEventsResponseIncomplete + | StreamEventsResponseFailed + | ErrorEvent + | StreamEventsResponseOutputItemAdded + | StreamEventsResponseOutputItemDone + | ContentPartAddedEvent + | ContentPartDoneEvent + | TextDeltaEvent + | TextDoneEvent + | RefusalDeltaEvent + | RefusalDoneEvent + | AnnotationAddedEvent + | FunctionCallArgsDeltaEvent + | FunctionCallArgsDoneEvent + | ReasoningDeltaEvent + | ReasoningDoneEvent + | ReasoningSummaryPartAddedEvent + | ReasoningSummaryPartDoneEvent + | ReasoningSummaryTextDeltaEvent + | ReasoningSummaryTextDoneEvent + | ImageGenCallInProgressEvent + | ImageGenCallGeneratingEvent + | ImageGenCallPartialImageEvent + | ImageGenCallCompletedEvent + | WebSearchCallInProgressEvent + | WebSearchCallSearchingEvent + | WebSearchCallCompletedEvent + | CustomToolCallInputDeltaEvent + | CustomToolCallInputDoneEvent + | ApplyPatchCallOperationDiffDeltaEvent + | ApplyPatchCallOperationDiffDoneEvent + | FusionCallInProgressEvent + | FusionCallPanelAddedEvent + | FusionCallPanelDeltaEvent + | FusionCallPanelReasoningDeltaEvent + | FusionCallPanelCompletedEvent + | FusionCallPanelFailedEvent + | FusionCallAnalysisInProgressEvent + | FusionCallAnalysisCompletedEvent + | FusionCallCompletedEvent + | DebugEvent +export const StreamEvents: Schema.Schema = Schema.Union([ + OpenResponsesCreatedEvent, + OpenResponsesInProgressEvent, + StreamEventsResponseCompleted, + StreamEventsResponseIncomplete, + StreamEventsResponseFailed, + ErrorEvent, + StreamEventsResponseOutputItemAdded, + StreamEventsResponseOutputItemDone, + ContentPartAddedEvent, + ContentPartDoneEvent, + TextDeltaEvent, + TextDoneEvent, + RefusalDeltaEvent, + RefusalDoneEvent, + AnnotationAddedEvent, + FunctionCallArgsDeltaEvent, + FunctionCallArgsDoneEvent, + ReasoningDeltaEvent, + ReasoningDoneEvent, + ReasoningSummaryPartAddedEvent, + ReasoningSummaryPartDoneEvent, + ReasoningSummaryTextDeltaEvent, + ReasoningSummaryTextDoneEvent, + ImageGenCallInProgressEvent, + ImageGenCallGeneratingEvent, + ImageGenCallPartialImageEvent, + ImageGenCallCompletedEvent, + WebSearchCallInProgressEvent, + WebSearchCallSearchingEvent, + WebSearchCallCompletedEvent, + CustomToolCallInputDeltaEvent, + CustomToolCallInputDoneEvent, + ApplyPatchCallOperationDiffDeltaEvent, + ApplyPatchCallOperationDiffDoneEvent, + FusionCallInProgressEvent, + FusionCallPanelAddedEvent, + FusionCallPanelDeltaEvent, + FusionCallPanelReasoningDeltaEvent, + FusionCallPanelCompletedEvent, + FusionCallPanelFailedEvent, + FusionCallAnalysisInProgressEvent, + FusionCallAnalysisCompletedEvent, + FusionCallCompletedEvent, + DebugEvent +], { mode: "oneOf" }).annotate({ + "description": "Union of all possible event types emitted during response streaming", + "identifier": "StreamEvents" +}) +export type MessagesStreamEvents = + | MessagesStartEvent + | MessagesDeltaEvent + | MessagesStopEvent + | MessagesContentBlockStartEvent + | MessagesContentBlockDeltaEvent + | MessagesContentBlockStopEvent + | MessagesPingEvent + | MessagesErrorEvent +export const MessagesStreamEvents = Schema.Union([ + MessagesStartEvent, + MessagesDeltaEvent, + MessagesStopEvent, + MessagesContentBlockStartEvent, + MessagesContentBlockDeltaEvent, + MessagesContentBlockStopEvent, + MessagesPingEvent, + MessagesErrorEvent +], { mode: "oneOf" }).annotate({ + "description": "Union of all possible streaming events", + "identifier": "MessagesStreamEvents" +}) +export type ResponsesStreamingResponse = { readonly "data": StreamEvents } +export const ResponsesStreamingResponse: Schema.Schema = Schema.Struct({ + "data": StreamEvents +}).annotate({ "identifier": "ResponsesStreamingResponse" }) +export type MessagesStreamingResponse = { readonly "data": MessagesStreamEvents; readonly "event": string } +export const MessagesStreamingResponse = Schema.Struct({ "data": MessagesStreamEvents, "event": Schema.String }) + .annotate({ "identifier": "MessagesStreamingResponse" }) +// schemas +export type GetUserActivityParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string + readonly "date"?: string + readonly "api_key_hash"?: string + readonly "user_id"?: string +} +export const GetUserActivityParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String), + "date": Schema.optionalKey( + Schema.String.annotate({ "description": "Filter by a single UTC date in the last 30 days (YYYY-MM-DD format)." }) + ), + "api_key_hash": Schema.optionalKey( + Schema.String.annotate({ + "description": "Filter by API key hash (SHA-256 hex string, as returned by the keys API)." + }) + ), + "user_id": Schema.optionalKey( + Schema.String.annotate({ + "description": "Filter by org member user ID. Only applicable for organization accounts." + }) + ) +}) +export type GetUserActivity200 = ActivityResponse +export const GetUserActivity200 = ActivityResponse +export type GetUserActivity400 = BadRequestResponse +export const GetUserActivity400 = BadRequestResponse +export type GetUserActivity401 = UnauthorizedResponse +export const GetUserActivity401 = UnauthorizedResponse +export type GetUserActivity403 = ForbiddenResponse +export const GetUserActivity403 = ForbiddenResponse +export type GetUserActivity404 = NotFoundResponse +export const GetUserActivity404 = NotFoundResponse +export type GetUserActivity500 = InternalServerResponse +export const GetUserActivity500 = InternalServerResponse +export type GetAnalyticsMetaParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const GetAnalyticsMetaParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type GetAnalyticsMeta200 = { + readonly "data": { + readonly "dimensions": ReadonlyArray<{ readonly "display_label": string; readonly "name": string }> + readonly "granularities": ReadonlyArray< + { readonly "display_label": string; readonly "name": "minute" | "hour" | "day" | "week" | "month" } + > + readonly "metrics": ReadonlyArray< + { + readonly "display_format": "number" | "currency" | "percent" | "latency" | "throughput" + readonly "display_label": string + readonly "is_rate": boolean + readonly "name": string + } + > + readonly "operators": ReadonlyArray< + { + readonly "name": "eq" | "neq" | "in" | "not_in" | "gt" | "gte" | "lt" | "lte" + readonly "value_type": "scalar" | "array" + } + > + } +} +export const GetAnalyticsMeta200 = Schema.Struct({ + "data": Schema.Struct({ + "dimensions": Schema.Array( + Schema.Struct({ + "display_label": Schema.String.annotate({ "description": "Human-readable label" }), + "name": Schema.String.annotate({ "description": "Dimension identifier used in query requests" }) + }) + ), + "granularities": Schema.Array( + Schema.Struct({ + "display_label": Schema.String.annotate({ "description": "Human-readable label" }), + "name": Schema.Literals(["minute", "hour", "day", "week", "month"]).annotate({ + "description": "Granularity identifier" + }) + }) + ), + "metrics": Schema.Array(Schema.Struct({ + "display_format": Schema.Literals(["number", "currency", "percent", "latency", "throughput"]).annotate({ + "description": + "How this metric value should be formatted for display (e.g. percent → multiply by 100 and append %, currency → prefix with $)" + }), + "display_label": Schema.String.annotate({ "description": "Human-readable label" }), + "is_rate": Schema.Boolean.annotate({ + "description": "Whether this metric is a rate/ratio (averaged, not summed)" + }), + "name": Schema.String.annotate({ "description": "Metric identifier used in query requests" }) + })), + "operators": Schema.Array(Schema.Struct({ + "name": Schema.Literals(["eq", "neq", "in", "not_in", "gt", "gte", "lt", "lte"]).annotate({ + "description": "Operator identifier used in filter definitions" + }), + "value_type": Schema.Literals(["scalar", "array"]).annotate({ + "description": "Whether the operator expects a single value or an array" + }) + })) + }) +}) +export type GetAnalyticsMeta401 = UnauthorizedResponse +export const GetAnalyticsMeta401 = UnauthorizedResponse +export type GetAnalyticsMeta403 = ForbiddenResponse +export const GetAnalyticsMeta403 = ForbiddenResponse +export type GetAnalyticsMeta500 = InternalServerResponse +export const GetAnalyticsMeta500 = InternalServerResponse +export type QueryAnalyticsParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const QueryAnalyticsParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type QueryAnalyticsRequestJson = { + readonly "classifier_dimensions"?: { + readonly "classifier_id": string + readonly "dimension_names"?: ReadonlyArray + readonly "include_nulls"?: boolean + } + readonly "classifier_filters"?: { + readonly "classifier_id": string + readonly "filters": ReadonlyArray< + { + readonly "field": string + readonly "operator": string + readonly "value": string | number | ReadonlyArray + } + > + } + readonly "dimensions"?: ReadonlyArray + readonly "filters"?: ReadonlyArray< + { + readonly "field": string + readonly "operator": string + readonly "value": string | number | ReadonlyArray + } + > + readonly "granularity"?: string + readonly "group_limit"?: number + readonly "limit"?: number + readonly "metrics": ReadonlyArray + readonly "order_by"?: { readonly "direction": "asc" | "desc"; readonly "field": string } + readonly "time_range"?: { readonly "end": string; readonly "start": string } +} +export const QueryAnalyticsRequestJson = Schema.Struct({ + "classifier_dimensions": Schema.optionalKey( + Schema.Struct({ + "classifier_id": Schema.String.annotate({ + "description": "UUID of the classifier whose tags to group by.", + "format": "uuid" + }), + "dimension_names": Schema.optionalKey( + Schema.Array( + Schema.String.annotate({ + "description": + "Classifier dimension name (snake_case identifier). When exactly one name is provided, the response uses it as the column key; with multiple names or none, the response uses `clf_dimension_name`/`clf_dimension_value` columns." + }) + ).check(Schema.isMaxLength(10).annotate({ "expected": "a value with a length of at most 10" })) + ), + "include_nulls": Schema.optionalKey( + Schema.Boolean.annotate({ + "description": + "When true, also include generations that have no tag from this classifier. Defaults to false, which returns only classified generations." + }) + ) + }).annotate({ + "description": + "Group results by custom classifier tags, breaking down metrics by the specified dimension values. Requires an active classifier on the workspace." + }) + ), + "classifier_filters": Schema.optionalKey( + Schema.Struct({ + "classifier_id": Schema.String.annotate({ + "description": + "UUID of the classifier whose tags to filter by. Must match classifier_dimensions.classifier_id when both are specified.", + "format": "uuid" + }), + "filters": Schema.Array(Schema.Struct({ + "field": Schema.String.annotate({ + "description": + "Classifier dimension name to filter on (snake_case identifier, e.g. \"department\", \"work_type\")." + }), + "operator": Schema.String.annotate({ + "description": + "Filter operator. Only equality/set operators are supported (eq, neq, in, not_in) — ordered comparisons are not available because classification values are strings." + }), + "value": Schema.Union([ + Schema.String, + Schema.Number.annotate({ "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ), + Schema.Array( + Schema.Union([ + Schema.String, + Schema.Number.annotate({ "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ) + ]) + ) + ]).annotate({ + "description": "Filter value. Use a scalar (string or number) for eq/neq, or an array for in/not_in." + }) + })).check(Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" })).check( + Schema.isMaxLength(10).annotate({ "expected": "a value with a length of at most 10" }) + ) + }).annotate({ + "description": + "Filter results to generations with specific classifier tag values. Can be combined with classifier_dimensions (must use the same classifier_id) or used independently with standard dimensions." + }) + ), + "dimensions": Schema.optionalKey( + Schema.Array( + Schema.String.annotate({ + "description": "Dimension to group by (up to 2). Use the /meta endpoint for available dimensions." + }) + ).check(Schema.isMaxLength(2).annotate({ "expected": "a value with a length of at most 2" })) + ), + "filters": Schema.optionalKey( + Schema.Array(Schema.Struct({ + "field": Schema.String.annotate({ + "description": "Dimension to filter on. Use the /meta endpoint for available dimensions." + }), + "operator": Schema.String.annotate({ "description": "Filter operator" }), + "value": Schema.Union([ + Schema.String, + Schema.Number.annotate({ "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ), + Schema.Array( + Schema.Union([ + Schema.String, + Schema.Number.annotate({ "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ) + ]) + ) + ]).annotate({ + "description": + "Filter value (scalar or array depending on operator). Several dimensions are enriched in responses (returned as human-readable labels), but filters must use the underlying ID: `api_key_id` — numeric ID (from generation metadata) or key hash (64-char hex from GET /api/v1/keys, resolved server-side); `user` — Clerk user ID (e.g. \"user_abc123\"), not the display name; `workspace` — workspace UUID, not the workspace name; `app` — numeric app ID, not the app title; `model` — permaslug (e.g. \"openai/gpt-4o\"), not the display name. Other dimensions (provider, origin, country, etc.) are not enriched and accept the value as returned." + }) + })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })) + ), + "granularity": Schema.optionalKey(Schema.String.annotate({ "description": "Time granularity" })), + "group_limit": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Maximum rows per distinct combination of dimensions. When omitted on time-series queries (granularity + dimensions), auto-computed to avoid truncating time windows. Explicit values override the default and may truncate time buckets if set lower than the number of buckets in the range. Ignored when no dimensions are specified." + }).check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "limit": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Maximum total rows returned. Defaults to 1000. On time-series queries with dimensions and no explicit group_limit, the server may raise this to accommodate the expected number of unique time-bucket/dimension combinations." + }).check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "metrics": Schema.Array(Schema.String.annotate({ "description": "Metric name" })).check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ), + "order_by": Schema.optionalKey( + Schema.Struct({ + "direction": Schema.Literals(["asc", "desc"]), + "field": Schema.String.annotate({ + "description": + "Field to order by: a metric included in `metrics` (or \"request_count\", which may be ordered by without being requested), a requested dimension, or \"date\"." + }) + }) + ), + "time_range": Schema.optionalKey( + Schema.Struct({ + "end": Schema.String.annotate({ "format": "date-time" }), + "start": Schema.String.annotate({ "format": "date-time" }) + }) + ) +}) +export type QueryAnalytics200 = { + readonly "data": { + readonly "cachedAt"?: number + readonly "data": ReadonlyArray<{}> + readonly "metadata": { + readonly "query_time_ms": number + readonly "row_count": number + readonly "truncated": boolean + } + readonly "warnings"?: ReadonlyArray + } +} +export const QueryAnalytics200 = Schema.Struct({ + "data": Schema.Struct({ + "cachedAt": Schema.optionalKey( + Schema.Number.annotate({ "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ) + ), + "data": Schema.Array( + Schema.Struct({}).annotate({ "description": "A row of analytics data with metric/dimension values" }) + ), + "metadata": Schema.Struct({ + "query_time_ms": Schema.Number.annotate({ "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ), + "row_count": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "truncated": Schema.Boolean + }), + "warnings": Schema.optionalKey( + Schema.Array(Schema.String).annotate({ + "description": + "Warnings about filter resolution issues (e.g. unresolvable api_key_id hashes). The query still runs normally; these inform the caller that some filter values could not be resolved." + }) + ) + }) +}) +export type QueryAnalytics400 = BadRequestResponse +export const QueryAnalytics400 = BadRequestResponse +export type QueryAnalytics401 = UnauthorizedResponse +export const QueryAnalytics401 = UnauthorizedResponse +export type QueryAnalytics403 = ForbiddenResponse +export const QueryAnalytics403 = ForbiddenResponse +export type QueryAnalytics408 = RequestTimeoutResponse +export const QueryAnalytics408 = RequestTimeoutResponse +export type QueryAnalytics500 = InternalServerResponse +export const QueryAnalytics500 = InternalServerResponse +export type CreateAudioSpeechParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const CreateAudioSpeechParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type CreateAudioSpeechRequestJson = SpeechRequest +export const CreateAudioSpeechRequestJson = SpeechRequest +export type CreateAudioSpeech400 = BadRequestResponse +export const CreateAudioSpeech400 = BadRequestResponse +export type CreateAudioSpeech401 = UnauthorizedResponse +export const CreateAudioSpeech401 = UnauthorizedResponse +export type CreateAudioSpeech402 = PaymentRequiredResponse +export const CreateAudioSpeech402 = PaymentRequiredResponse +export type CreateAudioSpeech404 = NotFoundResponse +export const CreateAudioSpeech404 = NotFoundResponse +export type CreateAudioSpeech429 = TooManyRequestsResponse +export const CreateAudioSpeech429 = TooManyRequestsResponse +export type CreateAudioSpeech500 = InternalServerResponse +export const CreateAudioSpeech500 = InternalServerResponse +export type CreateAudioSpeech502 = BadGatewayResponse +export const CreateAudioSpeech502 = BadGatewayResponse +export type CreateAudioSpeech503 = ServiceUnavailableResponse +export const CreateAudioSpeech503 = ServiceUnavailableResponse +export type CreateAudioSpeech524 = EdgeNetworkTimeoutResponse +export const CreateAudioSpeech524 = EdgeNetworkTimeoutResponse +export type CreateAudioSpeech529 = ProviderOverloadedResponse +export const CreateAudioSpeech529 = ProviderOverloadedResponse +export type CreateAudioTranscriptionsParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const CreateAudioTranscriptionsParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type CreateAudioTranscriptionsRequestJson = STTRequest +export const CreateAudioTranscriptionsRequestJson = STTRequest +export type CreateAudioTranscriptionsRequestFormData = { + readonly "file": string + readonly "language"?: string + readonly "model": string + readonly "response_format"?: "json" | "verbose_json" + readonly "temperature"?: number + readonly "timestamp_granularities[]"?: ReadonlyArray<"word" | "segment"> +} +export const CreateAudioTranscriptionsRequestFormData = Schema.Struct({ + "file": Schema.String.annotate({ + "description": + "The audio file to transcribe. The format is derived from the filename extension or the file part content type. Max 25 MB; send larger files as base64 JSON via input_audio.", + "format": "binary" + }), + "language": Schema.optionalKey( + Schema.String.annotate({ "description": "The language of the input audio (ISO-639-1)." }) + ), + "model": Schema.String.annotate({ "description": "The model to use for transcription." }), + "response_format": Schema.optionalKey( + Schema.Literals(["json", "verbose_json"]).annotate({ + "description": + "The response format. \"json\" (default) returns { text, usage }; \"verbose_json\" additionally returns task, language, duration, and segment-level timestamps (OpenAI-compatible providers only)." + }) + ), + "temperature": Schema.optionalKey( + Schema.Number.annotate({ "description": "The sampling temperature." }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ) + ), + "timestamp_granularities[]": Schema.optionalKey( + Schema.Array(Schema.Literals(["word", "segment"])).annotate({ + "description": + "Timestamp detail levels to include when response_format is \"verbose_json\". \"word\" additionally returns word-level timestamps in the words array." + }) + ) +}) +export type CreateAudioTranscriptions200 = STTResponse +export const CreateAudioTranscriptions200 = STTResponse +export type CreateAudioTranscriptions400 = BadRequestResponse +export const CreateAudioTranscriptions400 = BadRequestResponse +export type CreateAudioTranscriptions401 = UnauthorizedResponse +export const CreateAudioTranscriptions401 = UnauthorizedResponse +export type CreateAudioTranscriptions402 = PaymentRequiredResponse +export const CreateAudioTranscriptions402 = PaymentRequiredResponse +export type CreateAudioTranscriptions404 = NotFoundResponse +export const CreateAudioTranscriptions404 = NotFoundResponse +export type CreateAudioTranscriptions429 = TooManyRequestsResponse +export const CreateAudioTranscriptions429 = TooManyRequestsResponse +export type CreateAudioTranscriptions500 = InternalServerResponse +export const CreateAudioTranscriptions500 = InternalServerResponse +export type CreateAudioTranscriptions502 = BadGatewayResponse +export const CreateAudioTranscriptions502 = BadGatewayResponse +export type CreateAudioTranscriptions503 = ServiceUnavailableResponse +export const CreateAudioTranscriptions503 = ServiceUnavailableResponse +export type CreateAudioTranscriptions524 = EdgeNetworkTimeoutResponse +export const CreateAudioTranscriptions524 = EdgeNetworkTimeoutResponse +export type CreateAudioTranscriptions529 = ProviderOverloadedResponse +export const CreateAudioTranscriptions529 = ProviderOverloadedResponse +export type ExchangeAuthCodeForAPIKeyParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const ExchangeAuthCodeForAPIKeyParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type ExchangeAuthCodeForAPIKeyRequestJson = { + readonly "code": string + readonly "code_challenge_method"?: "S256" | "plain" | null + readonly "code_verifier"?: string +} +export const ExchangeAuthCodeForAPIKeyRequestJson = Schema.Struct({ + "code": Schema.String.annotate({ "description": "The authorization code received from the OAuth redirect" }), + "code_challenge_method": Schema.optionalKey( + Schema.Union([Schema.Literal("S256"), Schema.Literal("plain"), Schema.Null]).annotate({ + "description": "The method used to generate the code challenge" + }) + ), + "code_verifier": Schema.optionalKey( + Schema.String.annotate({ + "description": "The code verifier if code_challenge was used in the authorization request" + }) + ) +}) +export type ExchangeAuthCodeForAPIKey200 = { readonly "key": string; readonly "user_id": string | null } +export const ExchangeAuthCodeForAPIKey200 = Schema.Struct({ + "key": Schema.String.annotate({ "description": "The API key to use for OpenRouter requests" }), + "user_id": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "User ID associated with the API key" + }) +}) +export type ExchangeAuthCodeForAPIKey400 = BadRequestResponse +export const ExchangeAuthCodeForAPIKey400 = BadRequestResponse +export type ExchangeAuthCodeForAPIKey403 = ForbiddenResponse +export const ExchangeAuthCodeForAPIKey403 = ForbiddenResponse +export type ExchangeAuthCodeForAPIKey500 = InternalServerResponse +export const ExchangeAuthCodeForAPIKey500 = InternalServerResponse +export type CreateAuthKeysCodeParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const CreateAuthKeysCodeParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type CreateAuthKeysCodeRequestJson = { + readonly "callback_url": string + readonly "code_challenge"?: string + readonly "code_challenge_method"?: "S256" | "plain" + readonly "expires_at"?: string | null + readonly "key_label"?: string + readonly "limit"?: number + readonly "spawn_agent"?: string + readonly "spawn_cloud"?: string + readonly "usage_limit_type"?: "daily" | "weekly" | "monthly" + readonly "workspace_id"?: string +} +export const CreateAuthKeysCodeRequestJson = Schema.Struct({ + "callback_url": Schema.String.annotate({ + "description": + "The callback URL to redirect to after authorization. Supports https URLs and localhost/127.0.0.1 URLs on any port for local CLI tools.", + "format": "uri" + }), + "code_challenge": Schema.optionalKey( + Schema.String.annotate({ "description": "PKCE code challenge for enhanced security" }) + ), + "code_challenge_method": Schema.optionalKey( + Schema.Literals(["S256", "plain"]).annotate({ "description": "The method used to generate the code challenge" }) + ), + "expires_at": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "Optional expiration time for the API key to be created", + "format": "date-time" + }) + ), + "key_label": Schema.optionalKey( + Schema.String.annotate({ + "description": "Optional custom label for the API key. Defaults to the app name if not provided." + }).check(Schema.isMaxLength(100).annotate({ "expected": "a value with a length of at most 100" })) + ), + "limit": Schema.optionalKey( + Schema.Number.annotate({ "description": "Credit limit for the API key to be created", "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ) + ), + "spawn_agent": Schema.optionalKey(Schema.String.annotate({ "description": "Agent identifier for spawn telemetry" })), + "spawn_cloud": Schema.optionalKey(Schema.String.annotate({ "description": "Cloud identifier for spawn telemetry" })), + "usage_limit_type": Schema.optionalKey( + Schema.Literals(["daily", "weekly", "monthly"]).annotate({ + "description": "Optional credit limit reset interval. When set, the credit limit resets on this interval." + }) + ), + "workspace_id": Schema.optionalKey( + Schema.String.annotate({ "description": "Optional workspace ID to associate the API key with", "format": "uuid" }) + ) +}) +export type CreateAuthKeysCode200 = { + readonly "data": { readonly "app_id": number; readonly "created_at": string; readonly "id": string } +} +export const CreateAuthKeysCode200 = Schema.Struct({ + "data": Schema.Struct({ + "app_id": Schema.Number.annotate({ "description": "The application ID associated with this auth code" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ), + "created_at": Schema.String.annotate({ "description": "ISO 8601 timestamp of when the auth code was created" }), + "id": Schema.String.annotate({ "description": "The authorization code ID to use in the exchange request" }) + }).annotate({ "description": "Auth code data" }) +}) +export type CreateAuthKeysCode400 = BadRequestResponse +export const CreateAuthKeysCode400 = BadRequestResponse +export type CreateAuthKeysCode401 = UnauthorizedResponse +export const CreateAuthKeysCode401 = UnauthorizedResponse +export type CreateAuthKeysCode403 = ForbiddenResponse +export const CreateAuthKeysCode403 = ForbiddenResponse +export type CreateAuthKeysCode409 = ConflictResponse +export const CreateAuthKeysCode409 = ConflictResponse +export type CreateAuthKeysCode500 = InternalServerResponse +export const CreateAuthKeysCode500 = InternalServerResponse +export type GetBenchmarksParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string + readonly "source"?: "artificial-analysis" | "design-arena" + readonly "task_type"?: "coding" | "intelligence" | "agentic" + readonly "arena"?: "models" | "builders" | "agents" + readonly "category"?: string + readonly "max_results"?: number +} +export const GetBenchmarksParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String), + "source": Schema.optionalKey( + Schema.Literals(["artificial-analysis", "design-arena"]).annotate({ + "description": + "Benchmark source to query. Determines the shape of the returned items. When omitted, returns results from all sources." + }) + ), + "task_type": Schema.optionalKey( + Schema.Literals(["coding", "intelligence", "agentic"]).annotate({ + "description": + "Filter results by task type. For Artificial Analysis, maps to the corresponding index. For Design Arena, maps to the matching category." + }) + ), + "arena": Schema.optionalKey( + Schema.Literals(["models", "builders", "agents"]).annotate({ + "description": "Design Arena only: arena to query. Defaults to `models` when source is `design-arena`." + }) + ), + "category": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Design Arena only: category within the arena (e.g. `codecategories`, `uicomponent`, `gamedev`, `3d`, `dataviz`, `image`, `video`, `svg`). When omitted, returns all categories." + }) + ), + "max_results": Schema.optionalKey( + Schema.Number.annotate({ + "description": "Maximum number of items to return. When omitted, all matching results are returned." + }).check(Schema.isInt().annotate({ "expected": "an integer" })).check( + Schema.isGreaterThanOrEqualTo(1).annotate({ "expected": "a value greater than or equal to 1" }) + ) + ) +}) +export type GetBenchmarks200 = UnifiedBenchmarksResponse +export const GetBenchmarks200 = UnifiedBenchmarksResponse +export type GetBenchmarks400 = BadRequestResponse +export const GetBenchmarks400 = BadRequestResponse +export type GetBenchmarks401 = UnauthorizedResponse +export const GetBenchmarks401 = UnauthorizedResponse +export type GetBenchmarks429 = TooManyRequestsResponse +export const GetBenchmarks429 = TooManyRequestsResponse +export type GetBenchmarks500 = InternalServerResponse +export const GetBenchmarks500 = InternalServerResponse +export type ListBYOKKeysParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string + readonly "offset"?: number | null + readonly "limit"?: number + readonly "workspace_id"?: string + readonly "provider"?: + | "ai21" + | "aion-labs" + | "akashml" + | "alibaba" + | "amazon-bedrock" + | "amazon-nova" + | "ambient" + | "anthropic" + | "arcee-ai" + | "atlas-cloud" + | "avian" + | "azure" + | "baidu" + | "baseten" + | "black-forest-labs" + | "byteplus" + | "cerebras" + | "chutes" + | "cirrascale" + | "clarifai" + | "cloudflare" + | "cohere" + | "coreweave" + | "crusoe" + | "darkbloom" + | "decart" + | "deepgram" + | "deepinfra" + | "deepseek" + | "dekallm" + | "digitalocean" + | "featherless" + | "fireworks" + | "fish-audio" + | "friendli" + | "gmicloud" + | "google-ai-studio" + | "google-vertex" + | "groq" + | "heygen" + | "inception" + | "inceptron" + | "inferact-vllm" + | "inference-net" + | "infermatic" + | "inflection" + | "io-net" + | "ionstream" + | "krea" + | "liquid" + | "mancer" + | "mara" + | "meta" + | "minimax" + | "mistral" + | "modelrun" + | "modular" + | "moonshotai" + | "morph" + | "ncompass" + | "nebius" + | "nex-agi" + | "nextbit" + | "novita" + | "nvidia" + | "open-inference" + | "openai" + | "parasail" + | "perceptron" + | "perplexity" + | "phala" + | "poolside" + | "quiver" + | "recraft" + | "reka" + | "relace" + | "runway" + | "sail-research" + | "sakana" + | "sambanova" + | "seed" + | "siliconflow" + | "sourceful" + | "stepfun" + | "streamlake" + | "switchpoint" + | "tencent" + | "tenstorrent" + | "together" + | "upstage" + | "venice" + | "wafer" + | "wandb" + | "xai" + | "xiaomi" + | "z-ai" +} +export const ListBYOKKeysParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String), + "offset": Schema.optionalKey( + Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check( + Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" }) + ), + Schema.Null + ]).annotate({ "description": "Number of records to skip for pagination" }) + ), + "limit": Schema.optionalKey( + Schema.Number.annotate({ "description": "Maximum number of records to return (max 100)" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ).check(Schema.isGreaterThanOrEqualTo(1).annotate({ "expected": "a value greater than or equal to 1" })).check( + Schema.isLessThanOrEqualTo(100).annotate({ "expected": "a value less than or equal to 100" }) + ) + ), + "workspace_id": Schema.optionalKey( + Schema.String.annotate({ + "description": "Optional workspace ID to filter by. Defaults to the authenticated entity's default workspace.", + "format": "uuid" + }) + ), + "provider": Schema.optionalKey( + Schema.Literals([ + "ai21", + "aion-labs", + "akashml", + "alibaba", + "amazon-bedrock", + "amazon-nova", + "ambient", + "anthropic", + "arcee-ai", + "atlas-cloud", + "avian", + "azure", + "baidu", + "baseten", + "black-forest-labs", + "byteplus", + "cerebras", + "chutes", + "cirrascale", + "clarifai", + "cloudflare", + "cohere", + "coreweave", + "crusoe", + "darkbloom", + "decart", + "deepgram", + "deepinfra", + "deepseek", + "dekallm", + "digitalocean", + "featherless", + "fireworks", + "fish-audio", + "friendli", + "gmicloud", + "google-ai-studio", + "google-vertex", + "groq", + "heygen", + "inception", + "inceptron", + "inferact-vllm", + "inference-net", + "infermatic", + "inflection", + "io-net", + "ionstream", + "krea", + "liquid", + "mancer", + "mara", + "meta", + "minimax", + "mistral", + "modelrun", + "modular", + "moonshotai", + "morph", + "ncompass", + "nebius", + "nex-agi", + "nextbit", + "novita", + "nvidia", + "open-inference", + "openai", + "parasail", + "perceptron", + "perplexity", + "phala", + "poolside", + "quiver", + "recraft", + "reka", + "relace", + "runway", + "sail-research", + "sakana", + "sambanova", + "seed", + "siliconflow", + "sourceful", + "stepfun", + "streamlake", + "switchpoint", + "tencent", + "tenstorrent", + "together", + "upstage", + "venice", + "wafer", + "wandb", + "xai", + "xiaomi", + "z-ai" + ]).annotate({ + "description": "Optional provider slug to filter by (e.g. `openai`, `anthropic`, `amazon-bedrock`)." + }) + ) +}) +export type ListBYOKKeys200 = ListBYOKKeysResponse +export const ListBYOKKeys200 = ListBYOKKeysResponse +export type ListBYOKKeys400 = BadRequestResponse +export const ListBYOKKeys400 = BadRequestResponse +export type ListBYOKKeys401 = UnauthorizedResponse +export const ListBYOKKeys401 = UnauthorizedResponse +export type ListBYOKKeys500 = InternalServerResponse +export const ListBYOKKeys500 = InternalServerResponse +export type CreateBYOKKeyParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const CreateBYOKKeyParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type CreateBYOKKeyRequestJson = CreateBYOKKeyRequest +export const CreateBYOKKeyRequestJson = CreateBYOKKeyRequest +export type CreateBYOKKey201 = CreateBYOKKeyResponse +export const CreateBYOKKey201 = CreateBYOKKeyResponse +export type CreateBYOKKey400 = BadRequestResponse +export const CreateBYOKKey400 = BadRequestResponse +export type CreateBYOKKey401 = UnauthorizedResponse +export const CreateBYOKKey401 = UnauthorizedResponse +export type CreateBYOKKey403 = ForbiddenResponse +export const CreateBYOKKey403 = ForbiddenResponse +export type CreateBYOKKey500 = InternalServerResponse +export const CreateBYOKKey500 = InternalServerResponse +export type GetBYOKKeyParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const GetBYOKKeyParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type GetBYOKKey200 = GetBYOKKeyResponse +export const GetBYOKKey200 = GetBYOKKeyResponse +export type GetBYOKKey401 = UnauthorizedResponse +export const GetBYOKKey401 = UnauthorizedResponse +export type GetBYOKKey404 = NotFoundResponse +export const GetBYOKKey404 = NotFoundResponse +export type GetBYOKKey500 = InternalServerResponse +export const GetBYOKKey500 = InternalServerResponse +export type DeleteBYOKKeyParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const DeleteBYOKKeyParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type DeleteBYOKKey200 = DeleteBYOKKeyResponse +export const DeleteBYOKKey200 = DeleteBYOKKeyResponse +export type DeleteBYOKKey401 = UnauthorizedResponse +export const DeleteBYOKKey401 = UnauthorizedResponse +export type DeleteBYOKKey404 = NotFoundResponse +export const DeleteBYOKKey404 = NotFoundResponse +export type DeleteBYOKKey500 = InternalServerResponse +export const DeleteBYOKKey500 = InternalServerResponse +export type UpdateBYOKKeyParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const UpdateBYOKKeyParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type UpdateBYOKKeyRequestJson = UpdateBYOKKeyRequest +export const UpdateBYOKKeyRequestJson = UpdateBYOKKeyRequest +export type UpdateBYOKKey200 = UpdateBYOKKeyResponse +export const UpdateBYOKKey200 = UpdateBYOKKeyResponse +export type UpdateBYOKKey400 = BadRequestResponse +export const UpdateBYOKKey400 = BadRequestResponse +export type UpdateBYOKKey401 = UnauthorizedResponse +export const UpdateBYOKKey401 = UnauthorizedResponse +export type UpdateBYOKKey404 = NotFoundResponse +export const UpdateBYOKKey404 = NotFoundResponse +export type UpdateBYOKKey500 = InternalServerResponse +export const UpdateBYOKKey500 = InternalServerResponse +export type SendChatCompletionRequestParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string + readonly "X-OpenRouter-Metadata"?: MetadataLevel +} +export const SendChatCompletionRequestParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String), + "X-OpenRouter-Metadata": Schema.optionalKey(MetadataLevel) +}) +export type SendChatCompletionRequestRequestJson = ChatRequest +export const SendChatCompletionRequestRequestJson = ChatRequest +export type SendChatCompletionRequest200 = ChatResult +export const SendChatCompletionRequest200 = ChatResult +export type SendChatCompletionRequest200Sse = ChatStreamingResponse +export const SendChatCompletionRequest200Sse = ChatStreamingResponse +export type SendChatCompletionRequest400 = BadRequestResponse +export const SendChatCompletionRequest400 = BadRequestResponse +export type SendChatCompletionRequest401 = UnauthorizedResponse +export const SendChatCompletionRequest401 = UnauthorizedResponse +export type SendChatCompletionRequest402 = PaymentRequiredResponse +export const SendChatCompletionRequest402 = PaymentRequiredResponse +export type SendChatCompletionRequest403 = ForbiddenResponse +export const SendChatCompletionRequest403 = ForbiddenResponse +export type SendChatCompletionRequest404 = NotFoundResponse +export const SendChatCompletionRequest404 = NotFoundResponse +export type SendChatCompletionRequest408 = RequestTimeoutResponse +export const SendChatCompletionRequest408 = RequestTimeoutResponse +export type SendChatCompletionRequest413 = PayloadTooLargeResponse +export const SendChatCompletionRequest413 = PayloadTooLargeResponse +export type SendChatCompletionRequest422 = UnprocessableEntityResponse +export const SendChatCompletionRequest422 = UnprocessableEntityResponse +export type SendChatCompletionRequest429 = TooManyRequestsResponse +export const SendChatCompletionRequest429 = TooManyRequestsResponse +export type SendChatCompletionRequest500 = InternalServerResponse +export const SendChatCompletionRequest500 = InternalServerResponse +export type SendChatCompletionRequest502 = BadGatewayResponse +export const SendChatCompletionRequest502 = BadGatewayResponse +export type SendChatCompletionRequest503 = ServiceUnavailableResponse +export const SendChatCompletionRequest503 = ServiceUnavailableResponse +export type SendChatCompletionRequest524 = EdgeNetworkTimeoutResponse +export const SendChatCompletionRequest524 = EdgeNetworkTimeoutResponse +export type SendChatCompletionRequest529 = ProviderOverloadedResponse +export const SendChatCompletionRequest529 = ProviderOverloadedResponse +export type GetTaskClassificationsParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string + readonly "window"?: "7d" +} +export const GetTaskClassificationsParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String), + "window": Schema.optionalKey( + Schema.Literal("7d").annotate({ + "description": + "Trailing time window for the classification data. Currently only `7d` (trailing 7 days) is supported." + }) + ) +}) +export type GetTaskClassifications200 = TaskClassificationResponse +export const GetTaskClassifications200 = TaskClassificationResponse +export type GetTaskClassifications400 = BadRequestResponse +export const GetTaskClassifications400 = BadRequestResponse +export type GetTaskClassifications401 = UnauthorizedResponse +export const GetTaskClassifications401 = UnauthorizedResponse +export type GetTaskClassifications429 = TooManyRequestsResponse +export const GetTaskClassifications429 = TooManyRequestsResponse +export type GetTaskClassifications500 = InternalServerResponse +export const GetTaskClassifications500 = InternalServerResponse +export type GetCreditsParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const GetCreditsParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type GetCredits200 = { readonly "data": { readonly "total_credits": number; readonly "total_usage": number } } +export const GetCredits200 = Schema.Struct({ + "data": Schema.Struct({ + "total_credits": Schema.Number.annotate({ "description": "Total credits purchased", "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ), + "total_usage": Schema.Number.annotate({ "description": "Total credits used", "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ) + }) +}).annotate({ "description": "Total credits purchased and used" }) +export type GetCredits401 = UnauthorizedResponse +export const GetCredits401 = UnauthorizedResponse +export type GetCredits403 = ForbiddenResponse +export const GetCredits403 = ForbiddenResponse +export type GetCredits500 = InternalServerResponse +export const GetCredits500 = InternalServerResponse +export type CreateCoinbaseChargeParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const CreateCoinbaseChargeParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type CreateCoinbaseCharge410 = GoneResponse +export const CreateCoinbaseCharge410 = GoneResponse +export type GetAppRankingsParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string + readonly "category"?: "coding" | "creative" | "productivity" | "entertainment" + readonly "subcategory"?: + | "cli-agent" + | "ide-extension" + | "cloud-agent" + | "programming-app" + | "native-app-builder" + | "creative-writing" + | "video-gen" + | "image-gen" + | "audio-gen" + | "roleplay" + | "game" + | "writing-assistant" + | "general-chat" + | "personal-agent" + | "legal" + readonly "sort"?: "popular" | "trending" + readonly "start_date"?: string + readonly "end_date"?: string + readonly "limit"?: number + readonly "offset"?: number | null +} +export const GetAppRankingsParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String), + "category": Schema.optionalKey( + Schema.Literals(["coding", "creative", "productivity", "entertainment"]).annotate({ + "description": + "Marketplace category group to filter by (e.g. `coding`). Only apps tagged with a subcategory inside this group are returned. Mutually combinable with `subcategory` — when both are supplied the `subcategory` must belong to the `category` group." + }) + ), + "subcategory": Schema.optionalKey( + Schema.Literals([ + "cli-agent", + "ide-extension", + "cloud-agent", + "programming-app", + "native-app-builder", + "creative-writing", + "video-gen", + "image-gen", + "audio-gen", + "roleplay", + "game", + "writing-assistant", + "general-chat", + "personal-agent", + "legal" + ]).annotate({ + "description": + "Marketplace subcategory to filter by (e.g. `cli-agent`). Takes precedence over `category` for the actual filter; when `category` is also supplied the pair must be consistent." + }) + ), + "sort": Schema.optionalKey( + Schema.Literals(["popular", "trending"]).annotate({ + "description": + "`popular` ranks apps by total token volume inside the date window. `trending` ranks apps by absolute excess token growth: window volume minus the average volume of the three equal-length periods immediately preceding the window. Apps with no excess growth are omitted from `trending` results." + }) + ), + "start_date": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Start of the date window in YYYY-MM-DD (UTC), inclusive. Defaults to 30 days before `end_date`. The dataset begins at 2025-01-01; earlier values are clamped forward to that floor and the resolved value is echoed in `meta.start_date`." + }).check( + Schema.isPattern(new RegExp("^\\d{4}-\\d{2}-\\d{2}$")).annotate({ + "expected": "a string matching the RegExp ^\\d{4}-\\d{2}-\\d{2}$" + }) + ) + ), + "end_date": Schema.optionalKey( + Schema.String.annotate({ + "description": + "End of the date window in YYYY-MM-DD (UTC), inclusive. Defaults to the most recent completed UTC day. Must be on or after 2025-01-01; earlier values are rejected with a 400." + }).check( + Schema.isPattern(new RegExp("^\\d{4}-\\d{2}-\\d{2}$")).annotate({ + "expected": "a string matching the RegExp ^\\d{4}-\\d{2}-\\d{2}$" + }) + ) + ), + "limit": Schema.optionalKey( + Schema.Number.annotate({ "description": "Maximum number of apps to return (1-100). Defaults to 50." }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ).check(Schema.isGreaterThanOrEqualTo(1).annotate({ "expected": "a value greater than or equal to 1" })).check( + Schema.isLessThanOrEqualTo(100).annotate({ "expected": "a value less than or equal to 100" }) + ) + ), + "offset": Schema.optionalKey( + Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check( + Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" }) + ).check(Schema.isLessThanOrEqualTo(100).annotate({ "expected": "a value less than or equal to 100" })), + Schema.Null + ]).annotate({ + "description": + "Number of ranked apps to skip before the first returned row (0-100). Defaults to 0. `rank` stays absolute, so the first row of `offset=50` is `rank: 51`." + }) + ) +}) +export type GetAppRankings200 = AppRankingsResponse +export const GetAppRankings200 = AppRankingsResponse +export type GetAppRankings400 = BadRequestResponse +export const GetAppRankings400 = BadRequestResponse +export type GetAppRankings401 = UnauthorizedResponse +export const GetAppRankings401 = UnauthorizedResponse +export type GetAppRankings429 = TooManyRequestsResponse +export const GetAppRankings429 = TooManyRequestsResponse +export type GetAppRankings500 = InternalServerResponse +export const GetAppRankings500 = InternalServerResponse +export type GetRankingsDailyParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string + readonly "start_date"?: string + readonly "end_date"?: string + readonly "period"?: "day" | "week" | "month" + readonly "modality"?: "text" | "image" | "image_output" | "audio" | "tool_calling" + readonly "context_bucket"?: "1K" | "10K" | "100K" | "1M" | "10M" + readonly "category"?: + | "programming" + | "roleplay" + | "marketing" + | "marketing/seo" + | "technology" + | "science" + | "translation" + | "legal" + | "finance" + | "health" + | "trivia" + | "academia" + readonly "language_type"?: "natural" | "programming" +} +export const GetRankingsDailyParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String), + "start_date": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Start of the date window in YYYY-MM-DD (UTC), inclusive. Defaults to 30 days before `end_date`. The dataset begins at 2025-01-01; earlier values are clamped forward to that floor and the resolved value is echoed in `meta.start_date`." + }).check( + Schema.isPattern(new RegExp("^\\d{4}-\\d{2}-\\d{2}$")).annotate({ + "expected": "a string matching the RegExp ^\\d{4}-\\d{2}-\\d{2}$" + }) + ) + ), + "end_date": Schema.optionalKey( + Schema.String.annotate({ + "description": + "End of the date window in YYYY-MM-DD (UTC), inclusive. Defaults to the most recent completed UTC day. Must be on or after 2025-01-01; earlier values are rejected with a 400." + }).check( + Schema.isPattern(new RegExp("^\\d{4}-\\d{2}-\\d{2}$")).annotate({ + "expected": "a string matching the RegExp ^\\d{4}-\\d{2}-\\d{2}$" + }) + ) + ), + "period": Schema.optionalKey( + Schema.Literals(["day", "week", "month"]).annotate({ + "description": + "Time grain of each row. `day` (default) returns the per-UTC-day series; `week` buckets by ISO week start; `month` buckets by month start. With `category` or `language_type` only `week` (default) and `month` are available — `day` is rejected with a 400 because those datasets are aggregated weekly. For those sampled datasets `period=month` buckets each week by its week-start month, so totals are approximate at month boundaries." + }) + ), + "modality": Schema.optionalKey( + Schema.Literals(["text", "image", "image_output", "audio", "tool_calling"]).annotate({ + "description": + "Restrict to models for a modality surface: `text` / `image_output` match output modality, `image` / `audio` match input modality, and `tool_calling` keeps only rows that recorded at least one tool call. Exact dataset — cannot be combined with `category` or `language_type`." + }) + ), + "context_bucket": Schema.optionalKey( + Schema.Literals(["1K", "10K", "100K", "1M", "10M"]).annotate({ + "description": + "Restrict to requests whose context length falls in this bucket (`1K`, `10K`, `100K`, `1M`, or `10M`). Exact dataset — cannot be combined with `category` or `language_type`." + }) + ), + "category": Schema.optionalKey( + Schema.Literals([ + "programming", + "roleplay", + "marketing", + "marketing/seo", + "technology", + "science", + "translation", + "legal", + "finance", + "health", + "trivia", + "academia" + ]).annotate({ + "description": + "Restrict to a use-case category (e.g. `programming`, `roleplay`). Sourced from a sampled, upsampled dataset, so `total_tokens` is an estimate and is aggregated weekly (the trailing weekly bucket may include traffic past `end_date`). Cannot be combined with `modality`, `context_bucket`, or `language_type`." + }) + ), + "language_type": Schema.optionalKey( + Schema.Literals(["natural", "programming"]).annotate({ + "description": + "Restrict to natural-language or programming-language tagged activity. Sourced from a sampled, upsampled dataset, so `total_tokens` is an estimate and is aggregated weekly (the trailing weekly bucket may include traffic past `end_date`). Cannot be combined with `modality`, `context_bucket`, or `category`." + }) + ) +}) +export type GetRankingsDaily200 = RankingsDailyResponse +export const GetRankingsDaily200 = RankingsDailyResponse +export type GetRankingsDaily400 = BadRequestResponse +export const GetRankingsDaily400 = BadRequestResponse +export type GetRankingsDaily401 = UnauthorizedResponse +export const GetRankingsDaily401 = UnauthorizedResponse +export type GetRankingsDaily429 = TooManyRequestsResponse +export const GetRankingsDaily429 = TooManyRequestsResponse +export type GetRankingsDaily500 = InternalServerResponse +export const GetRankingsDaily500 = InternalServerResponse +export type CreateEmbeddingsParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const CreateEmbeddingsParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type CreateEmbeddingsRequestJson = { + readonly "dimensions"?: number + readonly "encoding_format"?: "float" | "base64" + readonly "input": + | string + | ReadonlyArray + | ReadonlyArray + | ReadonlyArray> + | ReadonlyArray< + { + readonly "content": ReadonlyArray< + | { readonly "text": string; readonly "type": "text" } + | { readonly "image_url": { readonly "url": string }; readonly "type": "image_url" } + | ContentPartInputAudio + | ContentPartInputVideo + | ContentPartInputFile + > + } + > + readonly "input_type"?: string + readonly "model": string + readonly "provider"?: ProviderPreferences + readonly "user"?: string +} +export const CreateEmbeddingsRequestJson = Schema.Struct({ + "dimensions": Schema.optionalKey( + Schema.Number.annotate({ "description": "The number of dimensions for the output embeddings" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ).check(Schema.isGreaterThanOrEqualTo(1).annotate({ "expected": "a value greater than or equal to 1" })) + ), + "encoding_format": Schema.optionalKey( + Schema.Literals(["float", "base64"]).annotate({ "description": "The format of the output embeddings" }) + ), + "input": Schema.Union([ + Schema.String, + Schema.Array(Schema.String), + Schema.Array(Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" }))), + Schema.Array(Schema.Array(Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })))), + Schema.Array(Schema.Struct({ + "content": Schema.Array( + Schema.Union([ + Schema.Struct({ "text": Schema.String, "type": Schema.Literal("text") }), + Schema.Struct({ "image_url": Schema.Struct({ "url": Schema.String }), "type": Schema.Literal("image_url") }), + ContentPartInputAudio, + ContentPartInputVideo, + ContentPartInputFile + ], { mode: "oneOf" }) + ) + })) + ]).annotate({ "description": "Text, token, or multimodal input(s) to embed" }), + "input_type": Schema.optionalKey( + Schema.String.annotate({ "description": "The type of input (e.g. search_query, search_document)" }) + ), + "model": Schema.String.annotate({ "description": "The model to use for embeddings" }), + "provider": Schema.optionalKey( + Schema.suspend((): Schema.Codec => ProviderPreferences).annotate({ + "description": "Provider routing preferences for the request." + }) + ), + "user": Schema.optionalKey(Schema.String.annotate({ "description": "A unique identifier for the end-user" })) +}).annotate({ "description": "Embeddings request input" }) +export type CreateEmbeddings200 = { + readonly "data": ReadonlyArray< + { readonly "embedding": ReadonlyArray | string; readonly "index"?: number; readonly "object": "embedding" } + > + readonly "id"?: string + readonly "model": string + readonly "object": "list" + readonly "usage"?: { + readonly "cost"?: number + readonly "cost_details"?: CostDetails + readonly "is_byok"?: boolean + readonly "prompt_tokens": number + readonly "prompt_tokens_details"?: { + readonly "audio_tokens"?: number + readonly "file_tokens"?: number + readonly "image_tokens"?: number + readonly "text_tokens"?: number + readonly "video_tokens"?: number + } + readonly "total_tokens": number + } +} +export const CreateEmbeddings200 = Schema.Struct({ + "data": Schema.Array( + Schema.Struct({ + "embedding": Schema.Union([ + Schema.Array(Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" }))), + Schema.String + ]).annotate({ "description": "Embedding vector as an array of floats or a base64 string" }), + "index": Schema.optionalKey( + Schema.Number.annotate({ "description": "Index of the embedding in the input list" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ) + ), + "object": Schema.Literal("embedding") + }).annotate({ "description": "A single embedding object" }) + ).annotate({ "description": "List of embedding objects" }), + "id": Schema.optionalKey(Schema.String.annotate({ "description": "Unique identifier for the embeddings response" })), + "model": Schema.String.annotate({ "description": "The model used for embeddings" }), + "object": Schema.Literal("list"), + "usage": Schema.optionalKey( + Schema.Struct({ + "cost": Schema.optionalKey( + Schema.Number.annotate({ "description": "Cost of the request in credits", "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ) + ), + "cost_details": Schema.optionalKey(CostDetails), + "is_byok": Schema.optionalKey( + Schema.Boolean.annotate({ + "description": "Whether a request was made using a Bring Your Own Key configuration" + }) + ), + "prompt_tokens": Schema.Number.annotate({ "description": "Number of tokens in the input" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ), + "prompt_tokens_details": Schema.optionalKey( + Schema.Struct({ + "audio_tokens": Schema.optionalKey( + Schema.Number.annotate({ "description": "Number of audio tokens in the input" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ) + ), + "file_tokens": Schema.optionalKey( + Schema.Number.annotate({ "description": "Number of file/document tokens in the input" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ) + ), + "image_tokens": Schema.optionalKey( + Schema.Number.annotate({ "description": "Number of image tokens in the input" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ) + ), + "text_tokens": Schema.optionalKey( + Schema.Number.annotate({ "description": "Number of text tokens in the input" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ) + ), + "video_tokens": Schema.optionalKey( + Schema.Number.annotate({ "description": "Number of video tokens in the input" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ) + ) + }).annotate({ + "description": + "Per-modality token breakdown. Only present when the input contains 2+ modalities (e.g. text + image) and the upstream provider returns modality-level usage data. Only non-zero modality counts are included." + }) + ), + "total_tokens": Schema.Number.annotate({ "description": "Total number of tokens used" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ) + }).annotate({ "description": "Token usage statistics" }) + ) +}).annotate({ "description": "Embeddings response containing embedding vectors" }) +export type CreateEmbeddings200Sse = string +export const CreateEmbeddings200Sse = Schema.String.annotate({ + "description": "Not used for embeddings - embeddings do not support streaming" +}) +export type CreateEmbeddings400 = BadRequestResponse +export const CreateEmbeddings400 = BadRequestResponse +export type CreateEmbeddings401 = UnauthorizedResponse +export const CreateEmbeddings401 = UnauthorizedResponse +export type CreateEmbeddings402 = PaymentRequiredResponse +export const CreateEmbeddings402 = PaymentRequiredResponse +export type CreateEmbeddings404 = NotFoundResponse +export const CreateEmbeddings404 = NotFoundResponse +export type CreateEmbeddings429 = TooManyRequestsResponse +export const CreateEmbeddings429 = TooManyRequestsResponse +export type CreateEmbeddings500 = InternalServerResponse +export const CreateEmbeddings500 = InternalServerResponse +export type CreateEmbeddings502 = BadGatewayResponse +export const CreateEmbeddings502 = BadGatewayResponse +export type CreateEmbeddings503 = ServiceUnavailableResponse +export const CreateEmbeddings503 = ServiceUnavailableResponse +export type CreateEmbeddings524 = EdgeNetworkTimeoutResponse +export const CreateEmbeddings524 = EdgeNetworkTimeoutResponse +export type CreateEmbeddings529 = ProviderOverloadedResponse +export const CreateEmbeddings529 = ProviderOverloadedResponse +export type ListEmbeddingsModelsParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string + readonly "offset"?: number | null + readonly "limit"?: number +} +export const ListEmbeddingsModelsParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String), + "offset": Schema.optionalKey( + Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check( + Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" }) + ), + Schema.Null + ]).annotate({ + "description": + "Number of records to skip for pagination. When both offset and limit are omitted, the full list is returned" + }) + ), + "limit": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Maximum number of records to return (max 1000). When both offset and limit are omitted, the full list is returned" + }).check(Schema.isInt().annotate({ "expected": "an integer" })).check( + Schema.isGreaterThanOrEqualTo(1).annotate({ "expected": "a value greater than or equal to 1" }) + ).check(Schema.isLessThanOrEqualTo(1000).annotate({ "expected": "a value less than or equal to 1000" })) + ) +}) +export type ListEmbeddingsModels200 = ModelsListResponse +export const ListEmbeddingsModels200 = ModelsListResponse +export type ListEmbeddingsModels400 = BadRequestResponse +export const ListEmbeddingsModels400 = BadRequestResponse +export type ListEmbeddingsModels500 = InternalServerResponse +export const ListEmbeddingsModels500 = InternalServerResponse +export type ListEndpointsZdrParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const ListEndpointsZdrParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type ListEndpointsZdr200 = { readonly "data": ReadonlyArray } +export const ListEndpointsZdr200 = Schema.Struct({ "data": Schema.Array(PublicEndpoint) }) +export type ListEndpointsZdr403 = ForbiddenResponse +export const ListEndpointsZdr403 = ForbiddenResponse +export type ListEndpointsZdr500 = InternalServerResponse +export const ListEndpointsZdr500 = InternalServerResponse +export type ListFilesParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string + readonly "limit"?: number + readonly "cursor"?: string + readonly "workspace_id"?: string +} +export const ListFilesParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String), + "limit": Schema.optionalKey( + Schema.Number.annotate({ "description": "Maximum number of files to return (1–1000)." }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ).check(Schema.isGreaterThanOrEqualTo(1).annotate({ "expected": "a value greater than or equal to 1" })).check( + Schema.isLessThanOrEqualTo(1000).annotate({ "expected": "a value less than or equal to 1000" }) + ) + ), + "cursor": Schema.optionalKey( + Schema.String.annotate({ "description": "Opaque pagination cursor from a previous response." }) + ), + "workspace_id": Schema.optionalKey( + Schema.String.annotate({ + "description": "Workspace to scope the request to. Defaults to the caller’s default workspace.", + "format": "uuid" + }) + ) +}) +export type ListFiles200 = FileListResponse +export const ListFiles200 = FileListResponse +export type ListFiles400 = BadRequestResponse +export const ListFiles400 = BadRequestResponse +export type ListFiles401 = UnauthorizedResponse +export const ListFiles401 = UnauthorizedResponse +export type ListFiles429 = TooManyRequestsResponse +export const ListFiles429 = TooManyRequestsResponse +export type ListFiles500 = InternalServerResponse +export const ListFiles500 = InternalServerResponse +export type UploadFileParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string + readonly "workspace_id"?: string +} +export const UploadFileParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String), + "workspace_id": Schema.optionalKey( + Schema.String.annotate({ + "description": "Workspace to scope the request to. Defaults to the caller’s default workspace.", + "format": "uuid" + }) + ) +}) +export type UploadFileRequestFormData = { readonly "file": string } +export const UploadFileRequestFormData = Schema.Struct({ "file": Schema.String.annotate({ "format": "binary" }) }) +export type UploadFile200 = FileMetadata +export const UploadFile200 = FileMetadata +export type UploadFile400 = BadRequestResponse +export const UploadFile400 = BadRequestResponse +export type UploadFile401 = UnauthorizedResponse +export const UploadFile401 = UnauthorizedResponse +export type UploadFile403 = ForbiddenResponse +export const UploadFile403 = ForbiddenResponse +export type UploadFile413 = PayloadTooLargeResponse +export const UploadFile413 = PayloadTooLargeResponse +export type UploadFile429 = TooManyRequestsResponse +export const UploadFile429 = TooManyRequestsResponse +export type UploadFile500 = InternalServerResponse +export const UploadFile500 = InternalServerResponse +export type GetFileMetadataParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string + readonly "workspace_id"?: string +} +export const GetFileMetadataParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String), + "workspace_id": Schema.optionalKey( + Schema.String.annotate({ + "description": "Workspace to scope the request to. Defaults to the caller’s default workspace.", + "format": "uuid" + }) + ) +}) +export type GetFileMetadata200 = FileMetadata +export const GetFileMetadata200 = FileMetadata +export type GetFileMetadata401 = UnauthorizedResponse +export const GetFileMetadata401 = UnauthorizedResponse +export type GetFileMetadata404 = NotFoundResponse +export const GetFileMetadata404 = NotFoundResponse +export type GetFileMetadata429 = TooManyRequestsResponse +export const GetFileMetadata429 = TooManyRequestsResponse +export type GetFileMetadata500 = InternalServerResponse +export const GetFileMetadata500 = InternalServerResponse +export type DeleteFileParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string + readonly "workspace_id"?: string +} +export const DeleteFileParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String), + "workspace_id": Schema.optionalKey( + Schema.String.annotate({ + "description": "Workspace to scope the request to. Defaults to the caller’s default workspace.", + "format": "uuid" + }) + ) +}) +export type DeleteFile200 = FileDeleteResponse +export const DeleteFile200 = FileDeleteResponse +export type DeleteFile401 = UnauthorizedResponse +export const DeleteFile401 = UnauthorizedResponse +export type DeleteFile404 = NotFoundResponse +export const DeleteFile404 = NotFoundResponse +export type DeleteFile429 = TooManyRequestsResponse +export const DeleteFile429 = TooManyRequestsResponse +export type DeleteFile500 = InternalServerResponse +export const DeleteFile500 = InternalServerResponse +export type DownloadFileContentParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string + readonly "workspace_id"?: string +} +export const DownloadFileContentParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String), + "workspace_id": Schema.optionalKey( + Schema.String.annotate({ + "description": "Workspace to scope the request to. Defaults to the caller’s default workspace.", + "format": "uuid" + }) + ) +}) +export type DownloadFileContent400 = BadRequestResponse +export const DownloadFileContent400 = BadRequestResponse +export type DownloadFileContent401 = UnauthorizedResponse +export const DownloadFileContent401 = UnauthorizedResponse +export type DownloadFileContent404 = NotFoundResponse +export const DownloadFileContent404 = NotFoundResponse +export type DownloadFileContent429 = TooManyRequestsResponse +export const DownloadFileContent429 = TooManyRequestsResponse +export type DownloadFileContent500 = InternalServerResponse +export const DownloadFileContent500 = InternalServerResponse +export type GetGenerationParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string + readonly "id": string +} +export const GetGenerationParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String), + "id": Schema.String.annotate({ "description": "The generation ID" }).check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ) +}) +export type GetGeneration200 = GenerationResponse +export const GetGeneration200 = GenerationResponse +export type GetGeneration401 = UnauthorizedResponse +export const GetGeneration401 = UnauthorizedResponse +export type GetGeneration402 = PaymentRequiredResponse +export const GetGeneration402 = PaymentRequiredResponse +export type GetGeneration404 = NotFoundResponse +export const GetGeneration404 = NotFoundResponse +export type GetGeneration429 = TooManyRequestsResponse +export const GetGeneration429 = TooManyRequestsResponse +export type GetGeneration500 = InternalServerResponse +export const GetGeneration500 = InternalServerResponse +export type GetGeneration502 = BadGatewayResponse +export const GetGeneration502 = BadGatewayResponse +export type GetGeneration524 = EdgeNetworkTimeoutResponse +export const GetGeneration524 = EdgeNetworkTimeoutResponse +export type GetGeneration529 = ProviderOverloadedResponse +export const GetGeneration529 = ProviderOverloadedResponse +export type ListGenerationContentParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string + readonly "id": string +} +export const ListGenerationContentParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String), + "id": Schema.String.annotate({ "description": "The generation ID" }).check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ) +}) +export type ListGenerationContent200 = GenerationContentResponse +export const ListGenerationContent200 = GenerationContentResponse +export type ListGenerationContent401 = UnauthorizedResponse +export const ListGenerationContent401 = UnauthorizedResponse +export type ListGenerationContent403 = ForbiddenResponse +export const ListGenerationContent403 = ForbiddenResponse +export type ListGenerationContent404 = NotFoundResponse +export const ListGenerationContent404 = NotFoundResponse +export type ListGenerationContent429 = TooManyRequestsResponse +export const ListGenerationContent429 = TooManyRequestsResponse +export type ListGenerationContent500 = InternalServerResponse +export const ListGenerationContent500 = InternalServerResponse +export type ListGenerationContent502 = BadGatewayResponse +export const ListGenerationContent502 = BadGatewayResponse +export type ListGenerationContent524 = EdgeNetworkTimeoutResponse +export const ListGenerationContent524 = EdgeNetworkTimeoutResponse +export type ListGenerationContent529 = ProviderOverloadedResponse +export const ListGenerationContent529 = ProviderOverloadedResponse +export type SubmitGenerationFeedbackParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const SubmitGenerationFeedbackParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type SubmitGenerationFeedbackRequestJson = SubmitGenerationFeedbackRequest +export const SubmitGenerationFeedbackRequestJson = SubmitGenerationFeedbackRequest +export type SubmitGenerationFeedback200 = SubmitGenerationFeedbackResponse +export const SubmitGenerationFeedback200 = SubmitGenerationFeedbackResponse +export type SubmitGenerationFeedback400 = BadRequestResponse +export const SubmitGenerationFeedback400 = BadRequestResponse +export type SubmitGenerationFeedback401 = UnauthorizedResponse +export const SubmitGenerationFeedback401 = UnauthorizedResponse +export type SubmitGenerationFeedback404 = NotFoundResponse +export const SubmitGenerationFeedback404 = NotFoundResponse +export type SubmitGenerationFeedback429 = TooManyRequestsResponse +export const SubmitGenerationFeedback429 = TooManyRequestsResponse +export type SubmitGenerationFeedback500 = InternalServerResponse +export const SubmitGenerationFeedback500 = InternalServerResponse +export type ListGuardrailsParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string + readonly "offset"?: number | null + readonly "limit"?: number + readonly "workspace_id"?: string +} +export const ListGuardrailsParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String), + "offset": Schema.optionalKey( + Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check( + Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" }) + ), + Schema.Null + ]).annotate({ "description": "Number of records to skip for pagination" }) + ), + "limit": Schema.optionalKey( + Schema.Number.annotate({ "description": "Maximum number of records to return (max 100)" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ).check(Schema.isGreaterThanOrEqualTo(1).annotate({ "expected": "a value greater than or equal to 1" })).check( + Schema.isLessThanOrEqualTo(100).annotate({ "expected": "a value less than or equal to 100" }) + ) + ), + "workspace_id": Schema.optionalKey( + Schema.String.annotate({ + "description": "Filter guardrails by workspace ID. By default, guardrails in the default workspace are returned.", + "format": "uuid" + }) + ) +}) +export type ListGuardrails200 = ListGuardrailsResponse +export const ListGuardrails200 = ListGuardrailsResponse +export type ListGuardrails400 = BadRequestResponse +export const ListGuardrails400 = BadRequestResponse +export type ListGuardrails401 = UnauthorizedResponse +export const ListGuardrails401 = UnauthorizedResponse +export type ListGuardrails500 = InternalServerResponse +export const ListGuardrails500 = InternalServerResponse +export type CreateGuardrailParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const CreateGuardrailParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type CreateGuardrailRequestJson = CreateGuardrailRequest +export const CreateGuardrailRequestJson = CreateGuardrailRequest +export type CreateGuardrail201 = CreateGuardrailResponse +export const CreateGuardrail201 = CreateGuardrailResponse +export type CreateGuardrail400 = BadRequestResponse +export const CreateGuardrail400 = BadRequestResponse +export type CreateGuardrail401 = UnauthorizedResponse +export const CreateGuardrail401 = UnauthorizedResponse +export type CreateGuardrail403 = ForbiddenResponse +export const CreateGuardrail403 = ForbiddenResponse +export type CreateGuardrail500 = InternalServerResponse +export const CreateGuardrail500 = InternalServerResponse +export type GetGuardrailParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const GetGuardrailParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type GetGuardrail200 = GetGuardrailResponse +export const GetGuardrail200 = GetGuardrailResponse +export type GetGuardrail401 = UnauthorizedResponse +export const GetGuardrail401 = UnauthorizedResponse +export type GetGuardrail404 = NotFoundResponse +export const GetGuardrail404 = NotFoundResponse +export type GetGuardrail500 = InternalServerResponse +export const GetGuardrail500 = InternalServerResponse +export type DeleteGuardrailParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const DeleteGuardrailParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type DeleteGuardrail200 = DeleteGuardrailResponse +export const DeleteGuardrail200 = DeleteGuardrailResponse +export type DeleteGuardrail401 = UnauthorizedResponse +export const DeleteGuardrail401 = UnauthorizedResponse +export type DeleteGuardrail404 = NotFoundResponse +export const DeleteGuardrail404 = NotFoundResponse +export type DeleteGuardrail500 = InternalServerResponse +export const DeleteGuardrail500 = InternalServerResponse +export type UpdateGuardrailParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const UpdateGuardrailParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type UpdateGuardrailRequestJson = UpdateGuardrailRequest +export const UpdateGuardrailRequestJson = UpdateGuardrailRequest +export type UpdateGuardrail200 = UpdateGuardrailResponse +export const UpdateGuardrail200 = UpdateGuardrailResponse +export type UpdateGuardrail400 = BadRequestResponse +export const UpdateGuardrail400 = BadRequestResponse +export type UpdateGuardrail401 = UnauthorizedResponse +export const UpdateGuardrail401 = UnauthorizedResponse +export type UpdateGuardrail404 = NotFoundResponse +export const UpdateGuardrail404 = NotFoundResponse +export type UpdateGuardrail500 = InternalServerResponse +export const UpdateGuardrail500 = InternalServerResponse +export type ListGuardrailKeyAssignmentsParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string + readonly "offset"?: number | null + readonly "limit"?: number +} +export const ListGuardrailKeyAssignmentsParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String), + "offset": Schema.optionalKey( + Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check( + Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" }) + ), + Schema.Null + ]).annotate({ "description": "Number of records to skip for pagination" }) + ), + "limit": Schema.optionalKey( + Schema.Number.annotate({ "description": "Maximum number of records to return (max 100)" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ).check(Schema.isGreaterThanOrEqualTo(1).annotate({ "expected": "a value greater than or equal to 1" })).check( + Schema.isLessThanOrEqualTo(100).annotate({ "expected": "a value less than or equal to 100" }) + ) + ) +}) +export type ListGuardrailKeyAssignments200 = ListKeyAssignmentsResponse +export const ListGuardrailKeyAssignments200 = ListKeyAssignmentsResponse +export type ListGuardrailKeyAssignments401 = UnauthorizedResponse +export const ListGuardrailKeyAssignments401 = UnauthorizedResponse +export type ListGuardrailKeyAssignments404 = NotFoundResponse +export const ListGuardrailKeyAssignments404 = NotFoundResponse +export type ListGuardrailKeyAssignments500 = InternalServerResponse +export const ListGuardrailKeyAssignments500 = InternalServerResponse +export type BulkAssignKeysToGuardrailParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const BulkAssignKeysToGuardrailParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type BulkAssignKeysToGuardrailRequestJson = BulkAssignKeysRequest +export const BulkAssignKeysToGuardrailRequestJson = BulkAssignKeysRequest +export type BulkAssignKeysToGuardrail200 = BulkAssignKeysResponse +export const BulkAssignKeysToGuardrail200 = BulkAssignKeysResponse +export type BulkAssignKeysToGuardrail400 = BadRequestResponse +export const BulkAssignKeysToGuardrail400 = BadRequestResponse +export type BulkAssignKeysToGuardrail401 = UnauthorizedResponse +export const BulkAssignKeysToGuardrail401 = UnauthorizedResponse +export type BulkAssignKeysToGuardrail404 = NotFoundResponse +export const BulkAssignKeysToGuardrail404 = NotFoundResponse +export type BulkAssignKeysToGuardrail500 = InternalServerResponse +export const BulkAssignKeysToGuardrail500 = InternalServerResponse +export type BulkUnassignKeysFromGuardrailParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const BulkUnassignKeysFromGuardrailParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type BulkUnassignKeysFromGuardrailRequestJson = BulkUnassignKeysRequest +export const BulkUnassignKeysFromGuardrailRequestJson = BulkUnassignKeysRequest +export type BulkUnassignKeysFromGuardrail200 = BulkUnassignKeysResponse +export const BulkUnassignKeysFromGuardrail200 = BulkUnassignKeysResponse +export type BulkUnassignKeysFromGuardrail400 = BadRequestResponse +export const BulkUnassignKeysFromGuardrail400 = BadRequestResponse +export type BulkUnassignKeysFromGuardrail401 = UnauthorizedResponse +export const BulkUnassignKeysFromGuardrail401 = UnauthorizedResponse +export type BulkUnassignKeysFromGuardrail404 = NotFoundResponse +export const BulkUnassignKeysFromGuardrail404 = NotFoundResponse +export type BulkUnassignKeysFromGuardrail500 = InternalServerResponse +export const BulkUnassignKeysFromGuardrail500 = InternalServerResponse +export type ListGuardrailMemberAssignmentsParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string + readonly "offset"?: number | null + readonly "limit"?: number +} +export const ListGuardrailMemberAssignmentsParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String), + "offset": Schema.optionalKey( + Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check( + Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" }) + ), + Schema.Null + ]).annotate({ "description": "Number of records to skip for pagination" }) + ), + "limit": Schema.optionalKey( + Schema.Number.annotate({ "description": "Maximum number of records to return (max 100)" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ).check(Schema.isGreaterThanOrEqualTo(1).annotate({ "expected": "a value greater than or equal to 1" })).check( + Schema.isLessThanOrEqualTo(100).annotate({ "expected": "a value less than or equal to 100" }) + ) + ) +}) +export type ListGuardrailMemberAssignments200 = ListMemberAssignmentsResponse +export const ListGuardrailMemberAssignments200 = ListMemberAssignmentsResponse +export type ListGuardrailMemberAssignments401 = UnauthorizedResponse +export const ListGuardrailMemberAssignments401 = UnauthorizedResponse +export type ListGuardrailMemberAssignments404 = NotFoundResponse +export const ListGuardrailMemberAssignments404 = NotFoundResponse +export type ListGuardrailMemberAssignments500 = InternalServerResponse +export const ListGuardrailMemberAssignments500 = InternalServerResponse +export type BulkAssignMembersToGuardrailParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const BulkAssignMembersToGuardrailParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type BulkAssignMembersToGuardrailRequestJson = BulkAssignMembersRequest +export const BulkAssignMembersToGuardrailRequestJson = BulkAssignMembersRequest +export type BulkAssignMembersToGuardrail200 = BulkAssignMembersResponse +export const BulkAssignMembersToGuardrail200 = BulkAssignMembersResponse +export type BulkAssignMembersToGuardrail400 = BadRequestResponse +export const BulkAssignMembersToGuardrail400 = BadRequestResponse +export type BulkAssignMembersToGuardrail401 = UnauthorizedResponse +export const BulkAssignMembersToGuardrail401 = UnauthorizedResponse +export type BulkAssignMembersToGuardrail404 = NotFoundResponse +export const BulkAssignMembersToGuardrail404 = NotFoundResponse +export type BulkAssignMembersToGuardrail500 = InternalServerResponse +export const BulkAssignMembersToGuardrail500 = InternalServerResponse +export type BulkUnassignMembersFromGuardrailParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const BulkUnassignMembersFromGuardrailParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type BulkUnassignMembersFromGuardrailRequestJson = BulkUnassignMembersRequest +export const BulkUnassignMembersFromGuardrailRequestJson = BulkUnassignMembersRequest +export type BulkUnassignMembersFromGuardrail200 = BulkUnassignMembersResponse +export const BulkUnassignMembersFromGuardrail200 = BulkUnassignMembersResponse +export type BulkUnassignMembersFromGuardrail400 = BadRequestResponse +export const BulkUnassignMembersFromGuardrail400 = BadRequestResponse +export type BulkUnassignMembersFromGuardrail401 = UnauthorizedResponse +export const BulkUnassignMembersFromGuardrail401 = UnauthorizedResponse +export type BulkUnassignMembersFromGuardrail404 = NotFoundResponse +export const BulkUnassignMembersFromGuardrail404 = NotFoundResponse +export type BulkUnassignMembersFromGuardrail500 = InternalServerResponse +export const BulkUnassignMembersFromGuardrail500 = InternalServerResponse +export type ListKeyAssignmentsParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string + readonly "offset"?: number | null + readonly "limit"?: number +} +export const ListKeyAssignmentsParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String), + "offset": Schema.optionalKey( + Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check( + Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" }) + ), + Schema.Null + ]).annotate({ "description": "Number of records to skip for pagination" }) + ), + "limit": Schema.optionalKey( + Schema.Number.annotate({ "description": "Maximum number of records to return (max 100)" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ).check(Schema.isGreaterThanOrEqualTo(1).annotate({ "expected": "a value greater than or equal to 1" })).check( + Schema.isLessThanOrEqualTo(100).annotate({ "expected": "a value less than or equal to 100" }) + ) + ) +}) +export type ListKeyAssignments200 = ListKeyAssignmentsResponse +export const ListKeyAssignments200 = ListKeyAssignmentsResponse +export type ListKeyAssignments401 = UnauthorizedResponse +export const ListKeyAssignments401 = UnauthorizedResponse +export type ListKeyAssignments500 = InternalServerResponse +export const ListKeyAssignments500 = InternalServerResponse +export type ListMemberAssignmentsParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string + readonly "offset"?: number | null + readonly "limit"?: number +} +export const ListMemberAssignmentsParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String), + "offset": Schema.optionalKey( + Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check( + Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" }) + ), + Schema.Null + ]).annotate({ "description": "Number of records to skip for pagination" }) + ), + "limit": Schema.optionalKey( + Schema.Number.annotate({ "description": "Maximum number of records to return (max 100)" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ).check(Schema.isGreaterThanOrEqualTo(1).annotate({ "expected": "a value greater than or equal to 1" })).check( + Schema.isLessThanOrEqualTo(100).annotate({ "expected": "a value less than or equal to 100" }) + ) + ) +}) +export type ListMemberAssignments200 = ListMemberAssignmentsResponse +export const ListMemberAssignments200 = ListMemberAssignmentsResponse +export type ListMemberAssignments401 = UnauthorizedResponse +export const ListMemberAssignments401 = UnauthorizedResponse +export type ListMemberAssignments500 = InternalServerResponse +export const ListMemberAssignments500 = InternalServerResponse +export type CreateImagesParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const CreateImagesParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type CreateImagesRequestJson = ImageGenerationRequest +export const CreateImagesRequestJson = ImageGenerationRequest +export type CreateImages200 = ImageGenerationResponse +export const CreateImages200 = ImageGenerationResponse +export type CreateImages200Sse = ImageStreamingResponse +export const CreateImages200Sse = ImageStreamingResponse +export type CreateImages400 = BadRequestResponse +export const CreateImages400 = BadRequestResponse +export type CreateImages401 = UnauthorizedResponse +export const CreateImages401 = UnauthorizedResponse +export type CreateImages402 = PaymentRequiredResponse +export const CreateImages402 = PaymentRequiredResponse +export type CreateImages403 = ForbiddenResponse +export const CreateImages403 = ForbiddenResponse +export type CreateImages404 = NotFoundResponse +export const CreateImages404 = NotFoundResponse +export type CreateImages413 = PayloadTooLargeResponse +export const CreateImages413 = PayloadTooLargeResponse +export type CreateImages429 = TooManyRequestsResponse +export const CreateImages429 = TooManyRequestsResponse +export type CreateImages500 = InternalServerResponse +export const CreateImages500 = InternalServerResponse +export type CreateImages502 = BadGatewayResponse +export const CreateImages502 = BadGatewayResponse +export type CreateImages524 = EdgeNetworkTimeoutResponse +export const CreateImages524 = EdgeNetworkTimeoutResponse +export type CreateImages529 = ProviderOverloadedResponse +export const CreateImages529 = ProviderOverloadedResponse +export type ListImageModelsParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const ListImageModelsParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type ListImageModels200 = ImageModelsListResponse +export const ListImageModels200 = ImageModelsListResponse +export type ListImageModels500 = InternalServerResponse +export const ListImageModels500 = InternalServerResponse +export type ListImageModelEndpointsParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const ListImageModelEndpointsParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type ListImageModelEndpoints200 = ImageModelEndpointsResponse +export const ListImageModelEndpoints200 = ImageModelEndpointsResponse +export type ListImageModelEndpoints404 = NotFoundResponse +export const ListImageModelEndpoints404 = NotFoundResponse +export type ListImageModelEndpoints500 = InternalServerResponse +export const ListImageModelEndpoints500 = InternalServerResponse +export type GetCurrentKeyParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const GetCurrentKeyParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type GetCurrentKey200 = { + readonly "data": { + readonly "byok_usage": number + readonly "byok_usage_daily": number + readonly "byok_usage_monthly": number + readonly "byok_usage_weekly": number + readonly "creator_user_id": string | null + readonly "expires_at"?: string | null + readonly "include_byok_in_limit": boolean + readonly "is_free_tier": boolean + readonly "is_management_key": boolean + readonly "is_provisioning_key": boolean + readonly "label": string + readonly "limit": number | null + readonly "limit_remaining": number | null + readonly "limit_reset": string | null + readonly "rate_limit": { readonly "interval": string; readonly "note": string; readonly "requests": number } + readonly "usage": number + readonly "usage_daily": number + readonly "usage_monthly": number + readonly "usage_weekly": number + } +} +export const GetCurrentKey200 = Schema.Struct({ + "data": Schema.Struct({ + "byok_usage": Schema.Number.annotate({ + "description": "Total external BYOK usage (in USD) for the API key", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "byok_usage_daily": Schema.Number.annotate({ + "description": "External BYOK usage (in USD) for the current UTC day", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "byok_usage_monthly": Schema.Number.annotate({ + "description": "External BYOK usage (in USD) for current UTC month", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "byok_usage_weekly": Schema.Number.annotate({ + "description": "External BYOK usage (in USD) for the current UTC week (Monday-Sunday)", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "creator_user_id": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": + "The user ID of the key creator. For organization-owned keys, this is the member who created the key. For individual users, this is the user's own ID." + }), + "expires_at": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "ISO 8601 UTC timestamp when the API key expires, or null if no expiration", + "format": "date-time" + }) + ), + "include_byok_in_limit": Schema.Boolean.annotate({ + "description": "Whether to include external BYOK usage in the credit limit" + }), + "is_free_tier": Schema.Boolean.annotate({ "description": "Whether this is a free tier API key" }), + "is_management_key": Schema.Boolean.annotate({ "description": "Whether this is a management key" }), + "is_provisioning_key": Schema.Boolean.annotate({ "description": "Whether this is a management key" }), + "label": Schema.String.annotate({ "description": "Human-readable label for the API key" }), + "limit": Schema.Union([ + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), + Schema.Null + ]).annotate({ "description": "Spending limit for the API key in USD", "format": "double" }), + "limit_remaining": Schema.Union([ + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), + Schema.Null + ]).annotate({ "description": "Remaining spending limit in USD", "format": "double" }), + "limit_reset": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "Type of limit reset for the API key" + }), + "rate_limit": Schema.Struct({ + "interval": Schema.String.annotate({ "description": "Rate limit interval" }), + "note": Schema.String.annotate({ "description": "Note about the rate limit" }), + "requests": Schema.Number.annotate({ "description": "Number of requests allowed per interval" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ) + }).annotate({ "description": "Legacy rate limit information about a key. Will always return -1." }), + "usage": Schema.Number.annotate({ + "description": "Total OpenRouter credit usage (in USD) for the API key", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "usage_daily": Schema.Number.annotate({ + "description": "OpenRouter credit usage (in USD) for the current UTC day", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "usage_monthly": Schema.Number.annotate({ + "description": "OpenRouter credit usage (in USD) for the current UTC month", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "usage_weekly": Schema.Number.annotate({ + "description": "OpenRouter credit usage (in USD) for the current UTC week (Monday-Sunday)", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })) + }).annotate({ "description": "Current API key information" }) +}) +export type GetCurrentKey401 = UnauthorizedResponse +export const GetCurrentKey401 = UnauthorizedResponse +export type GetCurrentKey500 = InternalServerResponse +export const GetCurrentKey500 = InternalServerResponse +export type ListParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string + readonly "include_disabled"?: boolean + readonly "offset"?: number | null + readonly "workspace_id"?: string +} +export const ListParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String), + "include_disabled": Schema.optionalKey( + Schema.Boolean.annotate({ "description": "Whether to include disabled API keys in the response" }) + ), + "offset": Schema.optionalKey( + Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check( + Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" }) + ), + Schema.Null + ]).annotate({ "description": "Number of API keys to skip for pagination" }) + ), + "workspace_id": Schema.optionalKey( + Schema.String.annotate({ + "description": "Filter API keys by workspace ID. By default, keys in the default workspace are returned.", + "format": "uuid" + }) + ) +}) +export type List200 = { + readonly "data": ReadonlyArray< + { + readonly "byok_usage": number + readonly "byok_usage_daily": number + readonly "byok_usage_monthly": number + readonly "byok_usage_weekly": number + readonly "created_at": string + readonly "creator_user_id": string | null + readonly "disabled": boolean + readonly "expires_at"?: string | null + readonly "hash": string + readonly "include_byok_in_limit": boolean + readonly "label": string + readonly "limit": number | null + readonly "limit_remaining": number | null + readonly "limit_reset": string | null + readonly "name": string + readonly "updated_at": string | null + readonly "usage": number + readonly "usage_daily": number + readonly "usage_monthly": number + readonly "usage_weekly": number + readonly "workspace_id": string + } + > +} +export const List200 = Schema.Struct({ + "data": Schema.Array(Schema.Struct({ + "byok_usage": Schema.Number.annotate({ + "description": "Total external BYOK usage (in USD) for the API key", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "byok_usage_daily": Schema.Number.annotate({ + "description": "External BYOK usage (in USD) for the current UTC day", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "byok_usage_monthly": Schema.Number.annotate({ + "description": "External BYOK usage (in USD) for current UTC month", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "byok_usage_weekly": Schema.Number.annotate({ + "description": "External BYOK usage (in USD) for the current UTC week (Monday-Sunday)", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "created_at": Schema.String.annotate({ "description": "ISO 8601 timestamp of when the API key was created" }), + "creator_user_id": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": + "The user ID of the key creator. For organization-owned keys, this is the member who created the key. For individual users, this is the user's own ID." + }), + "disabled": Schema.Boolean.annotate({ "description": "Whether the API key is disabled" }), + "expires_at": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "ISO 8601 UTC timestamp when the API key expires, or null if no expiration", + "format": "date-time" + }) + ), + "hash": Schema.String.annotate({ "description": "Unique hash identifier for the API key" }), + "include_byok_in_limit": Schema.Boolean.annotate({ + "description": "Whether to include external BYOK usage in the credit limit" + }), + "label": Schema.String.annotate({ "description": "Human-readable label for the API key" }), + "limit": Schema.Union([ + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), + Schema.Null + ]).annotate({ "description": "Spending limit for the API key in USD", "format": "double" }), + "limit_remaining": Schema.Union([ + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), + Schema.Null + ]).annotate({ "description": "Remaining spending limit in USD", "format": "double" }), + "limit_reset": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "Type of limit reset for the API key" + }), + "name": Schema.String.annotate({ "description": "Name of the API key" }), + "updated_at": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "ISO 8601 timestamp of when the API key was last updated" + }), + "usage": Schema.Number.annotate({ + "description": "Total OpenRouter credit usage (in USD) for the API key", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "usage_daily": Schema.Number.annotate({ + "description": "OpenRouter credit usage (in USD) for the current UTC day", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "usage_monthly": Schema.Number.annotate({ + "description": "OpenRouter credit usage (in USD) for the current UTC month", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "usage_weekly": Schema.Number.annotate({ + "description": "OpenRouter credit usage (in USD) for the current UTC week (Monday-Sunday)", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "workspace_id": Schema.String.annotate({ "description": "The workspace ID this API key belongs to." }) + })).annotate({ "description": "List of API keys" }) +}) +export type List400 = BadRequestResponse +export const List400 = BadRequestResponse +export type List401 = UnauthorizedResponse +export const List401 = UnauthorizedResponse +export type List429 = TooManyRequestsResponse +export const List429 = TooManyRequestsResponse +export type List500 = InternalServerResponse +export const List500 = InternalServerResponse +export type CreateKeysParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const CreateKeysParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type CreateKeysRequestJson = { + readonly "creator_user_id"?: string | null + readonly "expires_at"?: string | null + readonly "include_byok_in_limit"?: boolean + readonly "limit"?: number | null + readonly "limit_reset"?: "daily" | "weekly" | "monthly" | null + readonly "name": string + readonly "workspace_id"?: string +} +export const CreateKeysRequestJson = Schema.Struct({ + "creator_user_id": Schema.optionalKey( + Schema.Union([ + Schema.String.check(Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" })), + Schema.Null + ]).annotate({ + "description": + "Optional user ID of the key creator. Only meaningful for organization-owned keys where a specific member is creating the key." + }) + ), + "expires_at": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": + "Optional ISO 8601 UTC timestamp when the API key should expire. Must be UTC, other timezones will be rejected", + "format": "date-time" + }) + ), + "include_byok_in_limit": Schema.optionalKey( + Schema.Boolean.annotate({ "description": "Whether to include BYOK usage in the limit" }) + ), + "limit": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), Schema.Null]) + .annotate({ "description": "Optional spending limit for the API key in USD", "format": "double" }) + ), + "limit_reset": Schema.optionalKey( + Schema.Union([Schema.Literal("daily"), Schema.Literal("weekly"), Schema.Literal("monthly"), Schema.Null]).annotate({ + "description": + "Type of limit reset for the API key (daily, weekly, monthly, or null for no reset). Resets happen automatically at midnight UTC, and weeks are Monday through Sunday." + }) + ), + "name": Schema.String.annotate({ "description": "Name for the new API key" }).check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ), + "workspace_id": Schema.optionalKey( + Schema.String.annotate({ + "description": "The workspace to create the API key in. Defaults to the default workspace if not provided.", + "format": "uuid" + }) + ) +}) +export type CreateKeys201 = { + readonly "data": { + readonly "byok_usage": number + readonly "byok_usage_daily": number + readonly "byok_usage_monthly": number + readonly "byok_usage_weekly": number + readonly "created_at": string + readonly "creator_user_id": string | null + readonly "disabled": boolean + readonly "expires_at"?: string | null + readonly "hash": string + readonly "include_byok_in_limit": boolean + readonly "label": string + readonly "limit": number | null + readonly "limit_remaining": number | null + readonly "limit_reset": string | null + readonly "name": string + readonly "updated_at": string | null + readonly "usage": number + readonly "usage_daily": number + readonly "usage_monthly": number + readonly "usage_weekly": number + readonly "workspace_id": string + } + readonly "key": string +} +export const CreateKeys201 = Schema.Struct({ + "data": Schema.Struct({ + "byok_usage": Schema.Number.annotate({ + "description": "Total external BYOK usage (in USD) for the API key", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "byok_usage_daily": Schema.Number.annotate({ + "description": "External BYOK usage (in USD) for the current UTC day", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "byok_usage_monthly": Schema.Number.annotate({ + "description": "External BYOK usage (in USD) for current UTC month", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "byok_usage_weekly": Schema.Number.annotate({ + "description": "External BYOK usage (in USD) for the current UTC week (Monday-Sunday)", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "created_at": Schema.String.annotate({ "description": "ISO 8601 timestamp of when the API key was created" }), + "creator_user_id": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": + "The user ID of the key creator. For organization-owned keys, this is the member who created the key. For individual users, this is the user's own ID." + }), + "disabled": Schema.Boolean.annotate({ "description": "Whether the API key is disabled" }), + "expires_at": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "ISO 8601 UTC timestamp when the API key expires, or null if no expiration", + "format": "date-time" + }) + ), + "hash": Schema.String.annotate({ "description": "Unique hash identifier for the API key" }), + "include_byok_in_limit": Schema.Boolean.annotate({ + "description": "Whether to include external BYOK usage in the credit limit" + }), + "label": Schema.String.annotate({ "description": "Human-readable label for the API key" }), + "limit": Schema.Union([ + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), + Schema.Null + ]).annotate({ "description": "Spending limit for the API key in USD", "format": "double" }), + "limit_remaining": Schema.Union([ + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), + Schema.Null + ]).annotate({ "description": "Remaining spending limit in USD", "format": "double" }), + "limit_reset": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "Type of limit reset for the API key" + }), + "name": Schema.String.annotate({ "description": "Name of the API key" }), + "updated_at": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "ISO 8601 timestamp of when the API key was last updated" + }), + "usage": Schema.Number.annotate({ + "description": "Total OpenRouter credit usage (in USD) for the API key", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "usage_daily": Schema.Number.annotate({ + "description": "OpenRouter credit usage (in USD) for the current UTC day", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "usage_monthly": Schema.Number.annotate({ + "description": "OpenRouter credit usage (in USD) for the current UTC month", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "usage_weekly": Schema.Number.annotate({ + "description": "OpenRouter credit usage (in USD) for the current UTC week (Monday-Sunday)", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "workspace_id": Schema.String.annotate({ "description": "The workspace ID this API key belongs to." }) + }).annotate({ "description": "The created API key information" }), + "key": Schema.String.annotate({ "description": "The actual API key string (only shown once)" }) +}) +export type CreateKeys400 = BadRequestResponse +export const CreateKeys400 = BadRequestResponse +export type CreateKeys401 = UnauthorizedResponse +export const CreateKeys401 = UnauthorizedResponse +export type CreateKeys403 = ForbiddenResponse +export const CreateKeys403 = ForbiddenResponse +export type CreateKeys429 = TooManyRequestsResponse +export const CreateKeys429 = TooManyRequestsResponse +export type CreateKeys500 = InternalServerResponse +export const CreateKeys500 = InternalServerResponse +export type GetKeyParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const GetKeyParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type GetKey200 = { + readonly "data": { + readonly "byok_usage": number + readonly "byok_usage_daily": number + readonly "byok_usage_monthly": number + readonly "byok_usage_weekly": number + readonly "created_at": string + readonly "creator_user_id": string | null + readonly "disabled": boolean + readonly "expires_at"?: string | null + readonly "hash": string + readonly "include_byok_in_limit": boolean + readonly "label": string + readonly "limit": number | null + readonly "limit_remaining": number | null + readonly "limit_reset": string | null + readonly "name": string + readonly "updated_at": string | null + readonly "usage": number + readonly "usage_daily": number + readonly "usage_monthly": number + readonly "usage_weekly": number + readonly "workspace_id": string + } +} +export const GetKey200 = Schema.Struct({ + "data": Schema.Struct({ + "byok_usage": Schema.Number.annotate({ + "description": "Total external BYOK usage (in USD) for the API key", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "byok_usage_daily": Schema.Number.annotate({ + "description": "External BYOK usage (in USD) for the current UTC day", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "byok_usage_monthly": Schema.Number.annotate({ + "description": "External BYOK usage (in USD) for current UTC month", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "byok_usage_weekly": Schema.Number.annotate({ + "description": "External BYOK usage (in USD) for the current UTC week (Monday-Sunday)", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "created_at": Schema.String.annotate({ "description": "ISO 8601 timestamp of when the API key was created" }), + "creator_user_id": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": + "The user ID of the key creator. For organization-owned keys, this is the member who created the key. For individual users, this is the user's own ID." + }), + "disabled": Schema.Boolean.annotate({ "description": "Whether the API key is disabled" }), + "expires_at": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "ISO 8601 UTC timestamp when the API key expires, or null if no expiration", + "format": "date-time" + }) + ), + "hash": Schema.String.annotate({ "description": "Unique hash identifier for the API key" }), + "include_byok_in_limit": Schema.Boolean.annotate({ + "description": "Whether to include external BYOK usage in the credit limit" + }), + "label": Schema.String.annotate({ "description": "Human-readable label for the API key" }), + "limit": Schema.Union([ + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), + Schema.Null + ]).annotate({ "description": "Spending limit for the API key in USD", "format": "double" }), + "limit_remaining": Schema.Union([ + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), + Schema.Null + ]).annotate({ "description": "Remaining spending limit in USD", "format": "double" }), + "limit_reset": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "Type of limit reset for the API key" + }), + "name": Schema.String.annotate({ "description": "Name of the API key" }), + "updated_at": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "ISO 8601 timestamp of when the API key was last updated" + }), + "usage": Schema.Number.annotate({ + "description": "Total OpenRouter credit usage (in USD) for the API key", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "usage_daily": Schema.Number.annotate({ + "description": "OpenRouter credit usage (in USD) for the current UTC day", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "usage_monthly": Schema.Number.annotate({ + "description": "OpenRouter credit usage (in USD) for the current UTC month", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "usage_weekly": Schema.Number.annotate({ + "description": "OpenRouter credit usage (in USD) for the current UTC week (Monday-Sunday)", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "workspace_id": Schema.String.annotate({ "description": "The workspace ID this API key belongs to." }) + }).annotate({ "description": "The API key information" }) +}) +export type GetKey401 = UnauthorizedResponse +export const GetKey401 = UnauthorizedResponse +export type GetKey404 = NotFoundResponse +export const GetKey404 = NotFoundResponse +export type GetKey429 = TooManyRequestsResponse +export const GetKey429 = TooManyRequestsResponse +export type GetKey500 = InternalServerResponse +export const GetKey500 = InternalServerResponse +export type DeleteKeysParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const DeleteKeysParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type DeleteKeys200 = { readonly "deleted": true } +export const DeleteKeys200 = Schema.Struct({ + "deleted": Schema.Literal(true).annotate({ "description": "Confirmation that the API key was deleted" }) +}) +export type DeleteKeys401 = UnauthorizedResponse +export const DeleteKeys401 = UnauthorizedResponse +export type DeleteKeys404 = NotFoundResponse +export const DeleteKeys404 = NotFoundResponse +export type DeleteKeys429 = TooManyRequestsResponse +export const DeleteKeys429 = TooManyRequestsResponse +export type DeleteKeys500 = InternalServerResponse +export const DeleteKeys500 = InternalServerResponse +export type UpdateKeysParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const UpdateKeysParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type UpdateKeysRequestJson = { + readonly "disabled"?: boolean + readonly "include_byok_in_limit"?: boolean + readonly "limit"?: number | null + readonly "limit_reset"?: "daily" | "weekly" | "monthly" | null + readonly "name"?: string +} +export const UpdateKeysRequestJson = Schema.Struct({ + "disabled": Schema.optionalKey(Schema.Boolean.annotate({ "description": "Whether to disable the API key" })), + "include_byok_in_limit": Schema.optionalKey( + Schema.Boolean.annotate({ "description": "Whether to include BYOK usage in the limit" }) + ), + "limit": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), Schema.Null]) + .annotate({ "description": "New spending limit for the API key in USD", "format": "double" }) + ), + "limit_reset": Schema.optionalKey( + Schema.Union([Schema.Literal("daily"), Schema.Literal("weekly"), Schema.Literal("monthly"), Schema.Null]).annotate({ + "description": + "New limit reset type for the API key (daily, weekly, monthly, or null for no reset). Resets happen automatically at midnight UTC, and weeks are Monday through Sunday." + }) + ), + "name": Schema.optionalKey(Schema.String.annotate({ "description": "New name for the API key" })) +}) +export type UpdateKeys200 = { + readonly "data": { + readonly "byok_usage": number + readonly "byok_usage_daily": number + readonly "byok_usage_monthly": number + readonly "byok_usage_weekly": number + readonly "created_at": string + readonly "creator_user_id": string | null + readonly "disabled": boolean + readonly "expires_at"?: string | null + readonly "hash": string + readonly "include_byok_in_limit": boolean + readonly "label": string + readonly "limit": number | null + readonly "limit_remaining": number | null + readonly "limit_reset": string | null + readonly "name": string + readonly "updated_at": string | null + readonly "usage": number + readonly "usage_daily": number + readonly "usage_monthly": number + readonly "usage_weekly": number + readonly "workspace_id": string + } +} +export const UpdateKeys200 = Schema.Struct({ + "data": Schema.Struct({ + "byok_usage": Schema.Number.annotate({ + "description": "Total external BYOK usage (in USD) for the API key", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "byok_usage_daily": Schema.Number.annotate({ + "description": "External BYOK usage (in USD) for the current UTC day", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "byok_usage_monthly": Schema.Number.annotate({ + "description": "External BYOK usage (in USD) for current UTC month", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "byok_usage_weekly": Schema.Number.annotate({ + "description": "External BYOK usage (in USD) for the current UTC week (Monday-Sunday)", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "created_at": Schema.String.annotate({ "description": "ISO 8601 timestamp of when the API key was created" }), + "creator_user_id": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": + "The user ID of the key creator. For organization-owned keys, this is the member who created the key. For individual users, this is the user's own ID." + }), + "disabled": Schema.Boolean.annotate({ "description": "Whether the API key is disabled" }), + "expires_at": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "ISO 8601 UTC timestamp when the API key expires, or null if no expiration", + "format": "date-time" + }) + ), + "hash": Schema.String.annotate({ "description": "Unique hash identifier for the API key" }), + "include_byok_in_limit": Schema.Boolean.annotate({ + "description": "Whether to include external BYOK usage in the credit limit" + }), + "label": Schema.String.annotate({ "description": "Human-readable label for the API key" }), + "limit": Schema.Union([ + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), + Schema.Null + ]).annotate({ "description": "Spending limit for the API key in USD", "format": "double" }), + "limit_remaining": Schema.Union([ + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), + Schema.Null + ]).annotate({ "description": "Remaining spending limit in USD", "format": "double" }), + "limit_reset": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "Type of limit reset for the API key" + }), + "name": Schema.String.annotate({ "description": "Name of the API key" }), + "updated_at": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "ISO 8601 timestamp of when the API key was last updated" + }), + "usage": Schema.Number.annotate({ + "description": "Total OpenRouter credit usage (in USD) for the API key", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "usage_daily": Schema.Number.annotate({ + "description": "OpenRouter credit usage (in USD) for the current UTC day", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "usage_monthly": Schema.Number.annotate({ + "description": "OpenRouter credit usage (in USD) for the current UTC month", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "usage_weekly": Schema.Number.annotate({ + "description": "OpenRouter credit usage (in USD) for the current UTC week (Monday-Sunday)", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "workspace_id": Schema.String.annotate({ "description": "The workspace ID this API key belongs to." }) + }).annotate({ "description": "The updated API key information" }) +}) +export type UpdateKeys400 = BadRequestResponse +export const UpdateKeys400 = BadRequestResponse +export type UpdateKeys401 = UnauthorizedResponse +export const UpdateKeys401 = UnauthorizedResponse +export type UpdateKeys404 = NotFoundResponse +export const UpdateKeys404 = NotFoundResponse +export type UpdateKeys429 = TooManyRequestsResponse +export const UpdateKeys429 = TooManyRequestsResponse +export type UpdateKeys500 = InternalServerResponse +export const UpdateKeys500 = InternalServerResponse +export type CreateMessagesParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string + readonly "X-OpenRouter-Metadata"?: MetadataLevel +} +export const CreateMessagesParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String), + "X-OpenRouter-Metadata": Schema.optionalKey(MetadataLevel) +}) +export type CreateMessagesRequestJson = MessagesRequest +export const CreateMessagesRequestJson = MessagesRequest +export type CreateMessages200 = MessagesResult +export const CreateMessages200 = MessagesResult +export type CreateMessages200Sse = MessagesStreamingResponse +export const CreateMessages200Sse = MessagesStreamingResponse +export type CreateMessages400 = MessagesErrorResponse +export const CreateMessages400 = MessagesErrorResponse +export type CreateMessages401 = MessagesErrorResponse +export const CreateMessages401 = MessagesErrorResponse +export type CreateMessages403 = ForbiddenResponse +export const CreateMessages403 = ForbiddenResponse +export type CreateMessages404 = MessagesErrorResponse +export const CreateMessages404 = MessagesErrorResponse +export type CreateMessages429 = MessagesErrorResponse +export const CreateMessages429 = MessagesErrorResponse +export type CreateMessages500 = MessagesErrorResponse +export const CreateMessages500 = MessagesErrorResponse +export type CreateMessages503 = MessagesErrorResponse +export const CreateMessages503 = MessagesErrorResponse +export type CreateMessages529 = MessagesErrorResponse +export const CreateMessages529 = MessagesErrorResponse +export type GetModelParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const GetModelParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type GetModel200 = ModelResponse +export const GetModel200 = ModelResponse +export type GetModel403 = ForbiddenResponse +export const GetModel403 = ForbiddenResponse +export type GetModel404 = NotFoundResponse +export const GetModel404 = NotFoundResponse +export type GetModel500 = InternalServerResponse +export const GetModel500 = InternalServerResponse +export type GetModelsParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string + readonly "offset"?: number | null + readonly "limit"?: number + readonly "category"?: + | "programming" + | "roleplay" + | "marketing" + | "marketing/seo" + | "technology" + | "science" + | "translation" + | "legal" + | "finance" + | "health" + | "trivia" + | "academia" + readonly "supported_parameters"?: string + readonly "output_modalities"?: string + readonly "sort"?: + | "most-popular" + | "newest" + | "top-weekly" + | "pricing-low-to-high" + | "pricing-high-to-low" + | "context-high-to-low" + | "throughput-high-to-low" + | "latency-low-to-high" + | "intelligence-high-to-low" + | "coding-high-to-low" + | "agentic-high-to-low" + | "design-arena-elo-high-to-low" + readonly "q"?: string + readonly "input_modalities"?: string + readonly "context"?: number + readonly "min_price"?: number | null + readonly "max_price"?: number | null + readonly "arch"?: string + readonly "model_authors"?: string + readonly "providers"?: string + readonly "distillable"?: "true" | "false" + readonly "zdr"?: "true" + readonly "region"?: "eu" + readonly "min_output_price"?: number | null + readonly "max_output_price"?: number | null + readonly "min_age_days"?: number | null + readonly "max_age_days"?: number | null + readonly "min_intelligence_index"?: number | null + readonly "max_intelligence_index"?: number | null + readonly "min_coding_index"?: number | null + readonly "max_coding_index"?: number | null + readonly "min_agentic_index"?: number | null + readonly "max_agentic_index"?: number | null + readonly "min_tool_success_rate"?: number | null + readonly "max_tool_success_rate"?: number | null +} +export const GetModelsParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String), + "offset": Schema.optionalKey( + Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check( + Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" }) + ), + Schema.Null + ]).annotate({ + "description": + "Number of records to skip for pagination. When both offset and limit are omitted, the full list is returned" + }) + ), + "limit": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Maximum number of records to return (max 1000). When both offset and limit are omitted, the full list is returned" + }).check(Schema.isInt().annotate({ "expected": "an integer" })).check( + Schema.isGreaterThanOrEqualTo(1).annotate({ "expected": "a value greater than or equal to 1" }) + ).check(Schema.isLessThanOrEqualTo(1000).annotate({ "expected": "a value less than or equal to 1000" })) + ), + "category": Schema.optionalKey( + Schema.Literals([ + "programming", + "roleplay", + "marketing", + "marketing/seo", + "technology", + "science", + "translation", + "legal", + "finance", + "health", + "trivia", + "academia" + ]).annotate({ "description": "Filter models by use case category" }) + ), + "supported_parameters": Schema.optionalKey( + Schema.String.annotate({ "description": "Filter models by supported parameter (comma-separated)" }) + ), + "output_modalities": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Filter models by output modality. Accepts a comma-separated list of modalities (text, image, audio, embeddings) or \"all\" to include all models. Defaults to \"text\"." + }) + ), + "sort": Schema.optionalKey( + Schema.Literals([ + "most-popular", + "newest", + "top-weekly", + "pricing-low-to-high", + "pricing-high-to-low", + "context-high-to-low", + "throughput-high-to-low", + "latency-low-to-high", + "intelligence-high-to-low", + "coding-high-to-low", + "agentic-high-to-low", + "design-arena-elo-high-to-low" + ]).annotate({ + "description": + "Sort the returned models server-side. Prefer this over fetching the full list and sorting client-side. Options: pricing-low-to-high, pricing-high-to-low (average prompt/completion price), context-high-to-low (context length), throughput-high-to-low, latency-low-to-high (recent median performance), most-popular, top-weekly (tokens processed in the last week), newest (creation date), intelligence-high-to-low, coding-high-to-low, agentic-high-to-low (Artificial Analysis indices), design-arena-elo-high-to-low (best Design Arena ELO across arenas). Models without a score for the chosen benchmark are placed last. When omitted, the existing default ordering is preserved." + }) + ), + "q": Schema.optionalKey(Schema.String.annotate({ "description": "Free-text search by model name or slug." })), + "input_modalities": Schema.optionalKey( + Schema.String.annotate({ + "description": "Filter models by input modality. Comma-separated list of: text, image, audio, file." + }) + ), + "context": Schema.optionalKey( + Schema.Number.annotate({ + "description": "Minimum context length (tokens). Models with smaller context are excluded." + }).check(Schema.isInt().annotate({ "expected": "an integer" })).check( + Schema.isGreaterThanOrEqualTo(1).annotate({ "expected": "a value greater than or equal to 1" }) + ) + ), + "min_price": Schema.optionalKey( + Schema.Union([ + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })).check( + Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" }) + ), + Schema.Null + ]).annotate({ "description": "Minimum prompt price in $/M tokens." }) + ), + "max_price": Schema.optionalKey( + Schema.Union([ + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })).check( + Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" }) + ), + Schema.Null + ]).annotate({ "description": "Maximum prompt price in $/M tokens." }) + ), + "arch": Schema.optionalKey( + Schema.String.annotate({ + "description": "Filter models by architecture/model family (e.g. GPT, Claude, Gemini, Llama)." + }) + ), + "model_authors": Schema.optionalKey( + Schema.String.annotate({ + "description": "Filter models by the organization that created the model. Comma-separated list of author slugs." + }) + ), + "providers": Schema.optionalKey( + Schema.String.annotate({ + "description": "Filter models by hosting provider. Comma-separated list of provider names." + }) + ), + "distillable": Schema.optionalKey( + Schema.Literals(["true", "false"]).annotate({ + "description": + "Filter by distillation capability. \"true\" returns only distillable models, \"false\" excludes them." + }) + ), + "zdr": Schema.optionalKey( + Schema.Literal("true").annotate({ + "description": "When set to \"true\", return only models with zero data retention endpoints." + }) + ), + "region": Schema.optionalKey( + Schema.Literal("eu").annotate({ + "description": "Filter to models with endpoints in the given data region. Currently only \"eu\" is supported." + }) + ), + "min_output_price": Schema.optionalKey( + Schema.Union([ + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })).check( + Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" }) + ), + Schema.Null + ]).annotate({ "description": "Minimum completion (output) price in $/M tokens." }) + ), + "max_output_price": Schema.optionalKey( + Schema.Union([ + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })).check( + Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" }) + ), + Schema.Null + ]).annotate({ "description": "Maximum completion (output) price in $/M tokens." }) + ), + "min_age_days": Schema.optionalKey( + Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check( + Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" }) + ), + Schema.Null + ]).annotate({ "description": "Minimum model age in days since its creation date." }) + ), + "max_age_days": Schema.optionalKey( + Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check( + Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" }) + ), + Schema.Null + ]).annotate({ "description": "Maximum model age in days since its creation date." }) + ), + "min_intelligence_index": Schema.optionalKey( + Schema.Union([ + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })).check( + Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" }) + ), + Schema.Null + ]).annotate({ "description": "Minimum Artificial Analysis intelligence index." }) + ), + "max_intelligence_index": Schema.optionalKey( + Schema.Union([ + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })).check( + Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" }) + ), + Schema.Null + ]).annotate({ "description": "Maximum Artificial Analysis intelligence index." }) + ), + "min_coding_index": Schema.optionalKey( + Schema.Union([ + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })).check( + Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" }) + ), + Schema.Null + ]).annotate({ "description": "Minimum Artificial Analysis coding index." }) + ), + "max_coding_index": Schema.optionalKey( + Schema.Union([ + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })).check( + Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" }) + ), + Schema.Null + ]).annotate({ "description": "Maximum Artificial Analysis coding index." }) + ), + "min_agentic_index": Schema.optionalKey( + Schema.Union([ + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })).check( + Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" }) + ), + Schema.Null + ]).annotate({ "description": "Minimum Artificial Analysis agentic index." }) + ), + "max_agentic_index": Schema.optionalKey( + Schema.Union([ + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })).check( + Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" }) + ), + Schema.Null + ]).annotate({ "description": "Maximum Artificial Analysis agentic index." }) + ), + "min_tool_success_rate": Schema.optionalKey( + Schema.Union([ + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })).check( + Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" }) + ).check(Schema.isLessThanOrEqualTo(1).annotate({ "expected": "a value less than or equal to 1" })), + Schema.Null + ]).annotate({ + "description": + "Minimum tool-calling success rate, as a fraction in [0, 1] (e.g. 0.9 = 90% of requests finishing with a tool_calls finish reason)." + }) + ), + "max_tool_success_rate": Schema.optionalKey( + Schema.Union([ + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })).check( + Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" }) + ).check(Schema.isLessThanOrEqualTo(1).annotate({ "expected": "a value less than or equal to 1" })), + Schema.Null + ]).annotate({ "description": "Maximum tool-calling success rate, as a fraction in [0, 1]." }) + ) +}) +export type GetModels200 = ModelsListResponse +export const GetModels200 = ModelsListResponse +export type GetModels400 = BadRequestResponse +export const GetModels400 = BadRequestResponse +export type GetModels403 = ForbiddenResponse +export const GetModels403 = ForbiddenResponse +export type GetModels500 = InternalServerResponse +export const GetModels500 = InternalServerResponse +export type ListEndpointsParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const ListEndpointsParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type ListEndpoints200 = { readonly "data": ListEndpointsResponse } +export const ListEndpoints200 = Schema.Struct({ "data": ListEndpointsResponse }) +export type ListEndpoints403 = ForbiddenResponse +export const ListEndpoints403 = ForbiddenResponse +export type ListEndpoints404 = NotFoundResponse +export const ListEndpoints404 = NotFoundResponse +export type ListEndpoints500 = InternalServerResponse +export const ListEndpoints500 = InternalServerResponse +export type ListModelsCountParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string + readonly "output_modalities"?: string +} +export const ListModelsCountParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String), + "output_modalities": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Filter models by output modality. Accepts a comma-separated list of modalities (text, image, audio, embeddings) or \"all\" to include all models. Defaults to \"text\"." + }) + ) +}) +export type ListModelsCount200 = ModelsCountResponse +export const ListModelsCount200 = ModelsCountResponse +export type ListModelsCount400 = BadRequestResponse +export const ListModelsCount400 = BadRequestResponse +export type ListModelsCount403 = ForbiddenResponse +export const ListModelsCount403 = ForbiddenResponse +export type ListModelsCount500 = InternalServerResponse +export const ListModelsCount500 = InternalServerResponse +export type ListModelsUserParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string + readonly "offset"?: number | null + readonly "limit"?: number +} +export const ListModelsUserParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String), + "offset": Schema.optionalKey( + Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check( + Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" }) + ), + Schema.Null + ]).annotate({ + "description": + "Number of records to skip for pagination. When both offset and limit are omitted, the full list is returned" + }) + ), + "limit": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Maximum number of records to return (max 1000). When both offset and limit are omitted, the full list is returned" + }).check(Schema.isInt().annotate({ "expected": "an integer" })).check( + Schema.isGreaterThanOrEqualTo(1).annotate({ "expected": "a value greater than or equal to 1" }) + ).check(Schema.isLessThanOrEqualTo(1000).annotate({ "expected": "a value less than or equal to 1000" })) + ) +}) +export type ListModelsUser200 = ModelsListResponse +export const ListModelsUser200 = ModelsListResponse +export type ListModelsUser401 = UnauthorizedResponse +export const ListModelsUser401 = UnauthorizedResponse +export type ListModelsUser403 = ForbiddenResponse +export const ListModelsUser403 = ForbiddenResponse +export type ListModelsUser404 = NotFoundResponse +export const ListModelsUser404 = NotFoundResponse +export type ListModelsUser500 = InternalServerResponse +export const ListModelsUser500 = InternalServerResponse +export type ListObservabilityDestinationsParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string + readonly "offset"?: number | null + readonly "limit"?: number + readonly "workspace_id"?: string +} +export const ListObservabilityDestinationsParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String), + "offset": Schema.optionalKey( + Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check( + Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" }) + ), + Schema.Null + ]).annotate({ "description": "Number of records to skip for pagination" }) + ), + "limit": Schema.optionalKey( + Schema.Number.annotate({ "description": "Maximum number of records to return (max 100)" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ).check(Schema.isGreaterThanOrEqualTo(1).annotate({ "expected": "a value greater than or equal to 1" })).check( + Schema.isLessThanOrEqualTo(100).annotate({ "expected": "a value less than or equal to 100" }) + ) + ), + "workspace_id": Schema.optionalKey( + Schema.String.annotate({ + "description": "Optional workspace ID to filter by. Defaults to the authenticated entity's default workspace.", + "format": "uuid" + }) + ) +}) +export type ListObservabilityDestinations200 = ListObservabilityDestinationsResponse +export const ListObservabilityDestinations200 = ListObservabilityDestinationsResponse +export type ListObservabilityDestinations400 = BadRequestResponse +export const ListObservabilityDestinations400 = BadRequestResponse +export type ListObservabilityDestinations401 = UnauthorizedResponse +export const ListObservabilityDestinations401 = UnauthorizedResponse +export type ListObservabilityDestinations500 = InternalServerResponse +export const ListObservabilityDestinations500 = InternalServerResponse +export type CreateObservabilityDestinationParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const CreateObservabilityDestinationParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type CreateObservabilityDestinationRequestJson = CreateObservabilityDestinationRequest +export const CreateObservabilityDestinationRequestJson = CreateObservabilityDestinationRequest +export type CreateObservabilityDestination201 = CreateObservabilityDestinationResponse +export const CreateObservabilityDestination201 = CreateObservabilityDestinationResponse +export type CreateObservabilityDestination400 = BadRequestResponse +export const CreateObservabilityDestination400 = BadRequestResponse +export type CreateObservabilityDestination401 = UnauthorizedResponse +export const CreateObservabilityDestination401 = UnauthorizedResponse +export type CreateObservabilityDestination403 = ForbiddenResponse +export const CreateObservabilityDestination403 = ForbiddenResponse +export type CreateObservabilityDestination409 = ConflictResponse +export const CreateObservabilityDestination409 = ConflictResponse +export type CreateObservabilityDestination500 = InternalServerResponse +export const CreateObservabilityDestination500 = InternalServerResponse +export type GetObservabilityDestinationParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const GetObservabilityDestinationParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type GetObservabilityDestination200 = GetObservabilityDestinationResponse +export const GetObservabilityDestination200 = GetObservabilityDestinationResponse +export type GetObservabilityDestination401 = UnauthorizedResponse +export const GetObservabilityDestination401 = UnauthorizedResponse +export type GetObservabilityDestination404 = NotFoundResponse +export const GetObservabilityDestination404 = NotFoundResponse +export type GetObservabilityDestination500 = InternalServerResponse +export const GetObservabilityDestination500 = InternalServerResponse +export type DeleteObservabilityDestinationParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const DeleteObservabilityDestinationParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type DeleteObservabilityDestination200 = DeleteObservabilityDestinationResponse +export const DeleteObservabilityDestination200 = DeleteObservabilityDestinationResponse +export type DeleteObservabilityDestination401 = UnauthorizedResponse +export const DeleteObservabilityDestination401 = UnauthorizedResponse +export type DeleteObservabilityDestination404 = NotFoundResponse +export const DeleteObservabilityDestination404 = NotFoundResponse +export type DeleteObservabilityDestination500 = InternalServerResponse +export const DeleteObservabilityDestination500 = InternalServerResponse +export type UpdateObservabilityDestinationParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const UpdateObservabilityDestinationParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type UpdateObservabilityDestinationRequestJson = UpdateObservabilityDestinationRequest +export const UpdateObservabilityDestinationRequestJson = UpdateObservabilityDestinationRequest +export type UpdateObservabilityDestination200 = UpdateObservabilityDestinationResponse +export const UpdateObservabilityDestination200 = UpdateObservabilityDestinationResponse +export type UpdateObservabilityDestination400 = BadRequestResponse +export const UpdateObservabilityDestination400 = BadRequestResponse +export type UpdateObservabilityDestination401 = UnauthorizedResponse +export const UpdateObservabilityDestination401 = UnauthorizedResponse +export type UpdateObservabilityDestination404 = NotFoundResponse +export const UpdateObservabilityDestination404 = NotFoundResponse +export type UpdateObservabilityDestination409 = ConflictResponse +export const UpdateObservabilityDestination409 = ConflictResponse +export type UpdateObservabilityDestination500 = InternalServerResponse +export const UpdateObservabilityDestination500 = InternalServerResponse +export type ListOrganizationMembersParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string + readonly "offset"?: number | null + readonly "limit"?: number +} +export const ListOrganizationMembersParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String), + "offset": Schema.optionalKey( + Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check( + Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" }) + ), + Schema.Null + ]).annotate({ "description": "Number of records to skip for pagination" }) + ), + "limit": Schema.optionalKey( + Schema.Number.annotate({ "description": "Maximum number of records to return (max 100)" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ).check(Schema.isGreaterThanOrEqualTo(1).annotate({ "expected": "a value greater than or equal to 1" })).check( + Schema.isLessThanOrEqualTo(100).annotate({ "expected": "a value less than or equal to 100" }) + ) + ) +}) +export type ListOrganizationMembers200 = { + readonly "data": ReadonlyArray< + { + readonly "email": string + readonly "first_name": string | null + readonly "id": string + readonly "last_name": string | null + readonly "role": "org:admin" | "org:member" + } + > + readonly "total_count": number +} +export const ListOrganizationMembers200 = Schema.Struct({ + "data": Schema.Array(Schema.Struct({ + "email": Schema.String.annotate({ "description": "Email address of the member" }), + "first_name": Schema.Union([Schema.String, Schema.Null]).annotate({ "description": "First name of the member" }), + "id": Schema.String.annotate({ "description": "User ID of the organization member" }), + "last_name": Schema.Union([Schema.String, Schema.Null]).annotate({ "description": "Last name of the member" }), + "role": Schema.Literals(["org:admin", "org:member"]).annotate({ + "description": "Role of the member in the organization" + }) + })).annotate({ "description": "List of organization members" }), + "total_count": Schema.Number.annotate({ "description": "Total number of members in the organization" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ) +}) +export type ListOrganizationMembers401 = UnauthorizedResponse +export const ListOrganizationMembers401 = UnauthorizedResponse +export type ListOrganizationMembers404 = NotFoundResponse +export const ListOrganizationMembers404 = NotFoundResponse +export type ListOrganizationMembers500 = InternalServerResponse +export const ListOrganizationMembers500 = InternalServerResponse +export type ListPresetsParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string + readonly "offset"?: number | null + readonly "limit"?: number +} +export const ListPresetsParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String), + "offset": Schema.optionalKey( + Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check( + Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" }) + ), + Schema.Null + ]).annotate({ "description": "Number of records to skip for pagination" }) + ), + "limit": Schema.optionalKey( + Schema.Number.annotate({ "description": "Maximum number of records to return (max 100)" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ).check(Schema.isGreaterThanOrEqualTo(1).annotate({ "expected": "a value greater than or equal to 1" })).check( + Schema.isLessThanOrEqualTo(100).annotate({ "expected": "a value less than or equal to 100" }) + ) + ) +}) +export type ListPresets200 = ListPresetsResponse +export const ListPresets200 = ListPresetsResponse +export type ListPresets400 = BadRequestResponse +export const ListPresets400 = BadRequestResponse +export type ListPresets401 = UnauthorizedResponse +export const ListPresets401 = UnauthorizedResponse +export type ListPresets500 = InternalServerResponse +export const ListPresets500 = InternalServerResponse +export type GetPresetParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const GetPresetParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type GetPreset200 = GetPresetResponse +export const GetPreset200 = GetPresetResponse +export type GetPreset400 = BadRequestResponse +export const GetPreset400 = BadRequestResponse +export type GetPreset401 = UnauthorizedResponse +export const GetPreset401 = UnauthorizedResponse +export type GetPreset404 = NotFoundResponse +export const GetPreset404 = NotFoundResponse +export type GetPreset500 = InternalServerResponse +export const GetPreset500 = InternalServerResponse +export type CreatePresetsChatCompletionsParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const CreatePresetsChatCompletionsParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type CreatePresetsChatCompletionsRequestJson = ChatRequest +export const CreatePresetsChatCompletionsRequestJson = ChatRequest +export type CreatePresetsChatCompletions200 = CreatePresetFromInferenceResponse +export const CreatePresetsChatCompletions200 = CreatePresetFromInferenceResponse +export type CreatePresetsChatCompletions400 = BadRequestResponse +export const CreatePresetsChatCompletions400 = BadRequestResponse +export type CreatePresetsChatCompletions401 = UnauthorizedResponse +export const CreatePresetsChatCompletions401 = UnauthorizedResponse +export type CreatePresetsChatCompletions403 = ForbiddenResponse +export const CreatePresetsChatCompletions403 = ForbiddenResponse +export type CreatePresetsChatCompletions404 = NotFoundResponse +export const CreatePresetsChatCompletions404 = NotFoundResponse +export type CreatePresetsChatCompletions409 = ConflictResponse +export const CreatePresetsChatCompletions409 = ConflictResponse +export type CreatePresetsChatCompletions500 = InternalServerResponse +export const CreatePresetsChatCompletions500 = InternalServerResponse +export type CreatePresetsMessagesParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const CreatePresetsMessagesParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type CreatePresetsMessagesRequestJson = MessagesRequest +export const CreatePresetsMessagesRequestJson = MessagesRequest +export type CreatePresetsMessages200 = CreatePresetFromInferenceResponse +export const CreatePresetsMessages200 = CreatePresetFromInferenceResponse +export type CreatePresetsMessages400 = BadRequestResponse +export const CreatePresetsMessages400 = BadRequestResponse +export type CreatePresetsMessages401 = UnauthorizedResponse +export const CreatePresetsMessages401 = UnauthorizedResponse +export type CreatePresetsMessages403 = ForbiddenResponse +export const CreatePresetsMessages403 = ForbiddenResponse +export type CreatePresetsMessages404 = NotFoundResponse +export const CreatePresetsMessages404 = NotFoundResponse +export type CreatePresetsMessages409 = ConflictResponse +export const CreatePresetsMessages409 = ConflictResponse +export type CreatePresetsMessages500 = InternalServerResponse +export const CreatePresetsMessages500 = InternalServerResponse +export type CreatePresetsResponsesParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const CreatePresetsResponsesParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type CreatePresetsResponsesRequestJson = ResponsesRequest +export const CreatePresetsResponsesRequestJson = ResponsesRequest +export type CreatePresetsResponses200 = CreatePresetFromInferenceResponse +export const CreatePresetsResponses200 = CreatePresetFromInferenceResponse +export type CreatePresetsResponses400 = BadRequestResponse +export const CreatePresetsResponses400 = BadRequestResponse +export type CreatePresetsResponses401 = UnauthorizedResponse +export const CreatePresetsResponses401 = UnauthorizedResponse +export type CreatePresetsResponses403 = ForbiddenResponse +export const CreatePresetsResponses403 = ForbiddenResponse +export type CreatePresetsResponses404 = NotFoundResponse +export const CreatePresetsResponses404 = NotFoundResponse +export type CreatePresetsResponses409 = ConflictResponse +export const CreatePresetsResponses409 = ConflictResponse +export type CreatePresetsResponses500 = InternalServerResponse +export const CreatePresetsResponses500 = InternalServerResponse +export type ListPresetVersionsParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string + readonly "offset"?: number | null + readonly "limit"?: number +} +export const ListPresetVersionsParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String), + "offset": Schema.optionalKey( + Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check( + Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" }) + ), + Schema.Null + ]).annotate({ "description": "Number of records to skip for pagination" }) + ), + "limit": Schema.optionalKey( + Schema.Number.annotate({ "description": "Maximum number of records to return (max 100)" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ).check(Schema.isGreaterThanOrEqualTo(1).annotate({ "expected": "a value greater than or equal to 1" })).check( + Schema.isLessThanOrEqualTo(100).annotate({ "expected": "a value less than or equal to 100" }) + ) + ) +}) +export type ListPresetVersions200 = ListPresetVersionsResponse +export const ListPresetVersions200 = ListPresetVersionsResponse +export type ListPresetVersions400 = BadRequestResponse +export const ListPresetVersions400 = BadRequestResponse +export type ListPresetVersions401 = UnauthorizedResponse +export const ListPresetVersions401 = UnauthorizedResponse +export type ListPresetVersions404 = NotFoundResponse +export const ListPresetVersions404 = NotFoundResponse +export type ListPresetVersions500 = InternalServerResponse +export const ListPresetVersions500 = InternalServerResponse +export type GetPresetVersionParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const GetPresetVersionParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type GetPresetVersion200 = GetPresetVersionResponse +export const GetPresetVersion200 = GetPresetVersionResponse +export type GetPresetVersion400 = BadRequestResponse +export const GetPresetVersion400 = BadRequestResponse +export type GetPresetVersion401 = UnauthorizedResponse +export const GetPresetVersion401 = UnauthorizedResponse +export type GetPresetVersion404 = NotFoundResponse +export const GetPresetVersion404 = NotFoundResponse +export type GetPresetVersion500 = InternalServerResponse +export const GetPresetVersion500 = InternalServerResponse +export type ListProvidersParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const ListProvidersParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type ListProviders200 = { + readonly "data": ReadonlyArray< + { + readonly "datacenters"?: + | ReadonlyArray< + | "AD" + | "AE" + | "AF" + | "AG" + | "AI" + | "AL" + | "AM" + | "AO" + | "AQ" + | "AR" + | "AS" + | "AT" + | "AU" + | "AW" + | "AX" + | "AZ" + | "BA" + | "BB" + | "BD" + | "BE" + | "BF" + | "BG" + | "BH" + | "BI" + | "BJ" + | "BL" + | "BM" + | "BN" + | "BO" + | "BQ" + | "BR" + | "BS" + | "BT" + | "BV" + | "BW" + | "BY" + | "BZ" + | "CA" + | "CC" + | "CD" + | "CF" + | "CG" + | "CH" + | "CI" + | "CK" + | "CL" + | "CM" + | "CN" + | "CO" + | "CR" + | "CU" + | "CV" + | "CW" + | "CX" + | "CY" + | "CZ" + | "DE" + | "DJ" + | "DK" + | "DM" + | "DO" + | "DZ" + | "EC" + | "EE" + | "EG" + | "EH" + | "ER" + | "ES" + | "ET" + | "FI" + | "FJ" + | "FK" + | "FM" + | "FO" + | "FR" + | "GA" + | "GB" + | "GD" + | "GE" + | "GF" + | "GG" + | "GH" + | "GI" + | "GL" + | "GM" + | "GN" + | "GP" + | "GQ" + | "GR" + | "GS" + | "GT" + | "GU" + | "GW" + | "GY" + | "HK" + | "HM" + | "HN" + | "HR" + | "HT" + | "HU" + | "ID" + | "IE" + | "IL" + | "IM" + | "IN" + | "IO" + | "IQ" + | "IR" + | "IS" + | "IT" + | "JE" + | "JM" + | "JO" + | "JP" + | "KE" + | "KG" + | "KH" + | "KI" + | "KM" + | "KN" + | "KP" + | "KR" + | "KW" + | "KY" + | "KZ" + | "LA" + | "LB" + | "LC" + | "LI" + | "LK" + | "LR" + | "LS" + | "LT" + | "LU" + | "LV" + | "LY" + | "MA" + | "MC" + | "MD" + | "ME" + | "MF" + | "MG" + | "MH" + | "MK" + | "ML" + | "MM" + | "MN" + | "MO" + | "MP" + | "MQ" + | "MR" + | "MS" + | "MT" + | "MU" + | "MV" + | "MW" + | "MX" + | "MY" + | "MZ" + | "NA" + | "NC" + | "NE" + | "NF" + | "NG" + | "NI" + | "NL" + | "NO" + | "NP" + | "NR" + | "NU" + | "NZ" + | "OM" + | "PA" + | "PE" + | "PF" + | "PG" + | "PH" + | "PK" + | "PL" + | "PM" + | "PN" + | "PR" + | "PS" + | "PT" + | "PW" + | "PY" + | "QA" + | "RE" + | "RO" + | "RS" + | "RU" + | "RW" + | "SA" + | "SB" + | "SC" + | "SD" + | "SE" + | "SG" + | "SH" + | "SI" + | "SJ" + | "SK" + | "SL" + | "SM" + | "SN" + | "SO" + | "SR" + | "SS" + | "ST" + | "SV" + | "SX" + | "SY" + | "SZ" + | "TC" + | "TD" + | "TF" + | "TG" + | "TH" + | "TJ" + | "TK" + | "TL" + | "TM" + | "TN" + | "TO" + | "TR" + | "TT" + | "TV" + | "TW" + | "TZ" + | "UA" + | "UG" + | "UM" + | "US" + | "UY" + | "UZ" + | "VA" + | "VC" + | "VE" + | "VG" + | "VI" + | "VN" + | "VU" + | "WF" + | "WS" + | "YE" + | "YT" + | "ZA" + | "ZM" + | "ZW" + > + | null + readonly "headquarters"?: + | "AD" + | "AE" + | "AF" + | "AG" + | "AI" + | "AL" + | "AM" + | "AO" + | "AQ" + | "AR" + | "AS" + | "AT" + | "AU" + | "AW" + | "AX" + | "AZ" + | "BA" + | "BB" + | "BD" + | "BE" + | "BF" + | "BG" + | "BH" + | "BI" + | "BJ" + | "BL" + | "BM" + | "BN" + | "BO" + | "BQ" + | "BR" + | "BS" + | "BT" + | "BV" + | "BW" + | "BY" + | "BZ" + | "CA" + | "CC" + | "CD" + | "CF" + | "CG" + | "CH" + | "CI" + | "CK" + | "CL" + | "CM" + | "CN" + | "CO" + | "CR" + | "CU" + | "CV" + | "CW" + | "CX" + | "CY" + | "CZ" + | "DE" + | "DJ" + | "DK" + | "DM" + | "DO" + | "DZ" + | "EC" + | "EE" + | "EG" + | "EH" + | "ER" + | "ES" + | "ET" + | "FI" + | "FJ" + | "FK" + | "FM" + | "FO" + | "FR" + | "GA" + | "GB" + | "GD" + | "GE" + | "GF" + | "GG" + | "GH" + | "GI" + | "GL" + | "GM" + | "GN" + | "GP" + | "GQ" + | "GR" + | "GS" + | "GT" + | "GU" + | "GW" + | "GY" + | "HK" + | "HM" + | "HN" + | "HR" + | "HT" + | "HU" + | "ID" + | "IE" + | "IL" + | "IM" + | "IN" + | "IO" + | "IQ" + | "IR" + | "IS" + | "IT" + | "JE" + | "JM" + | "JO" + | "JP" + | "KE" + | "KG" + | "KH" + | "KI" + | "KM" + | "KN" + | "KP" + | "KR" + | "KW" + | "KY" + | "KZ" + | "LA" + | "LB" + | "LC" + | "LI" + | "LK" + | "LR" + | "LS" + | "LT" + | "LU" + | "LV" + | "LY" + | "MA" + | "MC" + | "MD" + | "ME" + | "MF" + | "MG" + | "MH" + | "MK" + | "ML" + | "MM" + | "MN" + | "MO" + | "MP" + | "MQ" + | "MR" + | "MS" + | "MT" + | "MU" + | "MV" + | "MW" + | "MX" + | "MY" + | "MZ" + | "NA" + | "NC" + | "NE" + | "NF" + | "NG" + | "NI" + | "NL" + | "NO" + | "NP" + | "NR" + | "NU" + | "NZ" + | "OM" + | "PA" + | "PE" + | "PF" + | "PG" + | "PH" + | "PK" + | "PL" + | "PM" + | "PN" + | "PR" + | "PS" + | "PT" + | "PW" + | "PY" + | "QA" + | "RE" + | "RO" + | "RS" + | "RU" + | "RW" + | "SA" + | "SB" + | "SC" + | "SD" + | "SE" + | "SG" + | "SH" + | "SI" + | "SJ" + | "SK" + | "SL" + | "SM" + | "SN" + | "SO" + | "SR" + | "SS" + | "ST" + | "SV" + | "SX" + | "SY" + | "SZ" + | "TC" + | "TD" + | "TF" + | "TG" + | "TH" + | "TJ" + | "TK" + | "TL" + | "TM" + | "TN" + | "TO" + | "TR" + | "TT" + | "TV" + | "TW" + | "TZ" + | "UA" + | "UG" + | "UM" + | "US" + | "UY" + | "UZ" + | "VA" + | "VC" + | "VE" + | "VG" + | "VI" + | "VN" + | "VU" + | "WF" + | "WS" + | "YE" + | "YT" + | "ZA" + | "ZM" + | "ZW" + | null + readonly "name": string + readonly "privacy_policy_url": string | null + readonly "slug": string + readonly "status_page_url"?: string | null + readonly "terms_of_service_url"?: string | null + } + > +} +export const ListProviders200 = Schema.Struct({ + "data": Schema.Array(Schema.Struct({ + "datacenters": Schema.optionalKey( + Schema.Union([ + Schema.Array( + Schema.Literals([ + "AD", + "AE", + "AF", + "AG", + "AI", + "AL", + "AM", + "AO", + "AQ", + "AR", + "AS", + "AT", + "AU", + "AW", + "AX", + "AZ", + "BA", + "BB", + "BD", + "BE", + "BF", + "BG", + "BH", + "BI", + "BJ", + "BL", + "BM", + "BN", + "BO", + "BQ", + "BR", + "BS", + "BT", + "BV", + "BW", + "BY", + "BZ", + "CA", + "CC", + "CD", + "CF", + "CG", + "CH", + "CI", + "CK", + "CL", + "CM", + "CN", + "CO", + "CR", + "CU", + "CV", + "CW", + "CX", + "CY", + "CZ", + "DE", + "DJ", + "DK", + "DM", + "DO", + "DZ", + "EC", + "EE", + "EG", + "EH", + "ER", + "ES", + "ET", + "FI", + "FJ", + "FK", + "FM", + "FO", + "FR", + "GA", + "GB", + "GD", + "GE", + "GF", + "GG", + "GH", + "GI", + "GL", + "GM", + "GN", + "GP", + "GQ", + "GR", + "GS", + "GT", + "GU", + "GW", + "GY", + "HK", + "HM", + "HN", + "HR", + "HT", + "HU", + "ID", + "IE", + "IL", + "IM", + "IN", + "IO", + "IQ", + "IR", + "IS", + "IT", + "JE", + "JM", + "JO", + "JP", + "KE", + "KG", + "KH", + "KI", + "KM", + "KN", + "KP", + "KR", + "KW", + "KY", + "KZ", + "LA", + "LB", + "LC", + "LI", + "LK", + "LR", + "LS", + "LT", + "LU", + "LV", + "LY", + "MA", + "MC", + "MD", + "ME", + "MF", + "MG", + "MH", + "MK", + "ML", + "MM", + "MN", + "MO", + "MP", + "MQ", + "MR", + "MS", + "MT", + "MU", + "MV", + "MW", + "MX", + "MY", + "MZ", + "NA", + "NC", + "NE", + "NF", + "NG", + "NI", + "NL", + "NO", + "NP", + "NR", + "NU", + "NZ", + "OM", + "PA", + "PE", + "PF", + "PG", + "PH", + "PK", + "PL", + "PM", + "PN", + "PR", + "PS", + "PT", + "PW", + "PY", + "QA", + "RE", + "RO", + "RS", + "RU", + "RW", + "SA", + "SB", + "SC", + "SD", + "SE", + "SG", + "SH", + "SI", + "SJ", + "SK", + "SL", + "SM", + "SN", + "SO", + "SR", + "SS", + "ST", + "SV", + "SX", + "SY", + "SZ", + "TC", + "TD", + "TF", + "TG", + "TH", + "TJ", + "TK", + "TL", + "TM", + "TN", + "TO", + "TR", + "TT", + "TV", + "TW", + "TZ", + "UA", + "UG", + "UM", + "US", + "UY", + "UZ", + "VA", + "VC", + "VE", + "VG", + "VI", + "VN", + "VU", + "WF", + "WS", + "YE", + "YT", + "ZA", + "ZM", + "ZW" + ]) + ), + Schema.Null + ]).annotate({ "description": "ISO 3166-1 Alpha-2 country codes of the provider datacenter locations" }) + ), + "headquarters": Schema.optionalKey( + Schema.Union([ + Schema.Literal("AD"), + Schema.Literal("AE"), + Schema.Literal("AF"), + Schema.Literal("AG"), + Schema.Literal("AI"), + Schema.Literal("AL"), + Schema.Literal("AM"), + Schema.Literal("AO"), + Schema.Literal("AQ"), + Schema.Literal("AR"), + Schema.Literal("AS"), + Schema.Literal("AT"), + Schema.Literal("AU"), + Schema.Literal("AW"), + Schema.Literal("AX"), + Schema.Literal("AZ"), + Schema.Literal("BA"), + Schema.Literal("BB"), + Schema.Literal("BD"), + Schema.Literal("BE"), + Schema.Literal("BF"), + Schema.Literal("BG"), + Schema.Literal("BH"), + Schema.Literal("BI"), + Schema.Literal("BJ"), + Schema.Literal("BL"), + Schema.Literal("BM"), + Schema.Literal("BN"), + Schema.Literal("BO"), + Schema.Literal("BQ"), + Schema.Literal("BR"), + Schema.Literal("BS"), + Schema.Literal("BT"), + Schema.Literal("BV"), + Schema.Literal("BW"), + Schema.Literal("BY"), + Schema.Literal("BZ"), + Schema.Literal("CA"), + Schema.Literal("CC"), + Schema.Literal("CD"), + Schema.Literal("CF"), + Schema.Literal("CG"), + Schema.Literal("CH"), + Schema.Literal("CI"), + Schema.Literal("CK"), + Schema.Literal("CL"), + Schema.Literal("CM"), + Schema.Literal("CN"), + Schema.Literal("CO"), + Schema.Literal("CR"), + Schema.Literal("CU"), + Schema.Literal("CV"), + Schema.Literal("CW"), + Schema.Literal("CX"), + Schema.Literal("CY"), + Schema.Literal("CZ"), + Schema.Literal("DE"), + Schema.Literal("DJ"), + Schema.Literal("DK"), + Schema.Literal("DM"), + Schema.Literal("DO"), + Schema.Literal("DZ"), + Schema.Literal("EC"), + Schema.Literal("EE"), + Schema.Literal("EG"), + Schema.Literal("EH"), + Schema.Literal("ER"), + Schema.Literal("ES"), + Schema.Literal("ET"), + Schema.Literal("FI"), + Schema.Literal("FJ"), + Schema.Literal("FK"), + Schema.Literal("FM"), + Schema.Literal("FO"), + Schema.Literal("FR"), + Schema.Literal("GA"), + Schema.Literal("GB"), + Schema.Literal("GD"), + Schema.Literal("GE"), + Schema.Literal("GF"), + Schema.Literal("GG"), + Schema.Literal("GH"), + Schema.Literal("GI"), + Schema.Literal("GL"), + Schema.Literal("GM"), + Schema.Literal("GN"), + Schema.Literal("GP"), + Schema.Literal("GQ"), + Schema.Literal("GR"), + Schema.Literal("GS"), + Schema.Literal("GT"), + Schema.Literal("GU"), + Schema.Literal("GW"), + Schema.Literal("GY"), + Schema.Literal("HK"), + Schema.Literal("HM"), + Schema.Literal("HN"), + Schema.Literal("HR"), + Schema.Literal("HT"), + Schema.Literal("HU"), + Schema.Literal("ID"), + Schema.Literal("IE"), + Schema.Literal("IL"), + Schema.Literal("IM"), + Schema.Literal("IN"), + Schema.Literal("IO"), + Schema.Literal("IQ"), + Schema.Literal("IR"), + Schema.Literal("IS"), + Schema.Literal("IT"), + Schema.Literal("JE"), + Schema.Literal("JM"), + Schema.Literal("JO"), + Schema.Literal("JP"), + Schema.Literal("KE"), + Schema.Literal("KG"), + Schema.Literal("KH"), + Schema.Literal("KI"), + Schema.Literal("KM"), + Schema.Literal("KN"), + Schema.Literal("KP"), + Schema.Literal("KR"), + Schema.Literal("KW"), + Schema.Literal("KY"), + Schema.Literal("KZ"), + Schema.Literal("LA"), + Schema.Literal("LB"), + Schema.Literal("LC"), + Schema.Literal("LI"), + Schema.Literal("LK"), + Schema.Literal("LR"), + Schema.Literal("LS"), + Schema.Literal("LT"), + Schema.Literal("LU"), + Schema.Literal("LV"), + Schema.Literal("LY"), + Schema.Literal("MA"), + Schema.Literal("MC"), + Schema.Literal("MD"), + Schema.Literal("ME"), + Schema.Literal("MF"), + Schema.Literal("MG"), + Schema.Literal("MH"), + Schema.Literal("MK"), + Schema.Literal("ML"), + Schema.Literal("MM"), + Schema.Literal("MN"), + Schema.Literal("MO"), + Schema.Literal("MP"), + Schema.Literal("MQ"), + Schema.Literal("MR"), + Schema.Literal("MS"), + Schema.Literal("MT"), + Schema.Literal("MU"), + Schema.Literal("MV"), + Schema.Literal("MW"), + Schema.Literal("MX"), + Schema.Literal("MY"), + Schema.Literal("MZ"), + Schema.Literal("NA"), + Schema.Literal("NC"), + Schema.Literal("NE"), + Schema.Literal("NF"), + Schema.Literal("NG"), + Schema.Literal("NI"), + Schema.Literal("NL"), + Schema.Literal("NO"), + Schema.Literal("NP"), + Schema.Literal("NR"), + Schema.Literal("NU"), + Schema.Literal("NZ"), + Schema.Literal("OM"), + Schema.Literal("PA"), + Schema.Literal("PE"), + Schema.Literal("PF"), + Schema.Literal("PG"), + Schema.Literal("PH"), + Schema.Literal("PK"), + Schema.Literal("PL"), + Schema.Literal("PM"), + Schema.Literal("PN"), + Schema.Literal("PR"), + Schema.Literal("PS"), + Schema.Literal("PT"), + Schema.Literal("PW"), + Schema.Literal("PY"), + Schema.Literal("QA"), + Schema.Literal("RE"), + Schema.Literal("RO"), + Schema.Literal("RS"), + Schema.Literal("RU"), + Schema.Literal("RW"), + Schema.Literal("SA"), + Schema.Literal("SB"), + Schema.Literal("SC"), + Schema.Literal("SD"), + Schema.Literal("SE"), + Schema.Literal("SG"), + Schema.Literal("SH"), + Schema.Literal("SI"), + Schema.Literal("SJ"), + Schema.Literal("SK"), + Schema.Literal("SL"), + Schema.Literal("SM"), + Schema.Literal("SN"), + Schema.Literal("SO"), + Schema.Literal("SR"), + Schema.Literal("SS"), + Schema.Literal("ST"), + Schema.Literal("SV"), + Schema.Literal("SX"), + Schema.Literal("SY"), + Schema.Literal("SZ"), + Schema.Literal("TC"), + Schema.Literal("TD"), + Schema.Literal("TF"), + Schema.Literal("TG"), + Schema.Literal("TH"), + Schema.Literal("TJ"), + Schema.Literal("TK"), + Schema.Literal("TL"), + Schema.Literal("TM"), + Schema.Literal("TN"), + Schema.Literal("TO"), + Schema.Literal("TR"), + Schema.Literal("TT"), + Schema.Literal("TV"), + Schema.Literal("TW"), + Schema.Literal("TZ"), + Schema.Literal("UA"), + Schema.Literal("UG"), + Schema.Literal("UM"), + Schema.Literal("US"), + Schema.Literal("UY"), + Schema.Literal("UZ"), + Schema.Literal("VA"), + Schema.Literal("VC"), + Schema.Literal("VE"), + Schema.Literal("VG"), + Schema.Literal("VI"), + Schema.Literal("VN"), + Schema.Literal("VU"), + Schema.Literal("WF"), + Schema.Literal("WS"), + Schema.Literal("YE"), + Schema.Literal("YT"), + Schema.Literal("ZA"), + Schema.Literal("ZM"), + Schema.Literal("ZW"), + Schema.Null + ]).annotate({ "description": "ISO 3166-1 Alpha-2 country code of the provider headquarters" }) + ), + "name": Schema.String.annotate({ "description": "Display name of the provider" }), + "privacy_policy_url": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "URL to the provider's privacy policy" + }), + "slug": Schema.String.annotate({ "description": "URL-friendly identifier for the provider" }), + "status_page_url": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ "description": "URL to the provider's status page" }) + ), + "terms_of_service_url": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ "description": "URL to the provider's terms of service" }) + ) + })) +}) +export type ListProviders500 = InternalServerResponse +export const ListProviders500 = InternalServerResponse +export type CreateRerankParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const CreateRerankParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type CreateRerankRequestJson = { + readonly "documents": ReadonlyArray + readonly "model": string + readonly "provider"?: ProviderPreferences + readonly "query": string + readonly "top_n"?: number +} +export const CreateRerankRequestJson = Schema.Struct({ + "documents": Schema.Array( + Schema.Union([ + Schema.String, + Schema.Struct({ + "image": Schema.optionalKey( + Schema.String.annotate({ + "description": + "An image associated with the document, as a remote URL (http/https) or a base64-encoded data URI (data:image/...)." + }) + ), + "text": Schema.optionalKey(Schema.String.annotate({ "description": "The document text" })) + }).annotate({ + "description": + "A structured document with optional text and/or image content. At least one of `text` or `image` must be provided." + }) + ]).annotate({ + "description": + "A document to rerank. Either a plain string, or a structured object with optional `text` and/or `image`." + }) + ).annotate({ + "description": + "The list of documents to rerank. Documents may be plain strings, or structured objects with `text` and/or `image` for multimodal models." + }).check(Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" })), + "model": Schema.String.annotate({ "description": "The rerank model to use" }), + "provider": Schema.optionalKey( + Schema.suspend((): Schema.Codec => ProviderPreferences).annotate({ + "description": "Provider routing preferences for the request." + }) + ), + "query": Schema.String.annotate({ "description": "The search query to rerank documents against" }), + "top_n": Schema.optionalKey( + Schema.Number.annotate({ "description": "Number of most relevant documents to return" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ).check(Schema.isGreaterThanOrEqualTo(1).annotate({ "expected": "a value greater than or equal to 1" })) + ) +}).annotate({ "description": "Rerank request input" }) +export type CreateRerank200 = { + readonly "id"?: string + readonly "model": string + readonly "provider"?: string + readonly "results": ReadonlyArray< + { + readonly "document": { readonly "image"?: string; readonly "text"?: string } + readonly "index": number + readonly "relevance_score": number + } + > + readonly "usage"?: { readonly "cost"?: number; readonly "search_units"?: number; readonly "total_tokens"?: number } +} +export const CreateRerank200 = Schema.Struct({ + "id": Schema.optionalKey( + Schema.String.annotate({ "description": "Unique identifier for the rerank response (ORID format)" }) + ), + "model": Schema.String.annotate({ "description": "The model used for reranking" }), + "provider": Schema.optionalKey( + Schema.String.annotate({ "description": "The provider that served the rerank request" }) + ), + "results": Schema.Array( + Schema.Struct({ + "document": Schema.Struct({ + "image": Schema.optionalKey( + Schema.String.annotate({ "description": "The image (URL or data URI) from the original document" }) + ), + "text": Schema.optionalKey(Schema.String.annotate({ "description": "The document text" })) + }).annotate({ "description": "The document object echoing the original input (text and/or image)" }), + "index": Schema.Number.annotate({ "description": "Index of the document in the original input list" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ), + "relevance_score": Schema.Number.annotate({ + "description": "Relevance score of the document to the query", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })) + }).annotate({ "description": "A single rerank result" }) + ).annotate({ "description": "List of rerank results sorted by relevance" }), + "usage": Schema.optionalKey( + Schema.Struct({ + "cost": Schema.optionalKey( + Schema.Number.annotate({ "description": "Cost of the request in credits", "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ) + ), + "search_units": Schema.optionalKey( + Schema.Number.annotate({ "description": "Number of search units consumed (Cohere billing)" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ) + ), + "total_tokens": Schema.optionalKey( + Schema.Number.annotate({ "description": "Total number of tokens used" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ) + ) + }).annotate({ "description": "Usage statistics" }) + ) +}).annotate({ "description": "Rerank response containing ranked results" }) +export type CreateRerank200Sse = string +export const CreateRerank200Sse = Schema.String.annotate({ + "description": "Not used for rerank - rerank does not support streaming" +}) +export type CreateRerank400 = BadRequestResponse +export const CreateRerank400 = BadRequestResponse +export type CreateRerank401 = UnauthorizedResponse +export const CreateRerank401 = UnauthorizedResponse +export type CreateRerank402 = PaymentRequiredResponse +export const CreateRerank402 = PaymentRequiredResponse +export type CreateRerank404 = NotFoundResponse +export const CreateRerank404 = NotFoundResponse +export type CreateRerank429 = TooManyRequestsResponse +export const CreateRerank429 = TooManyRequestsResponse +export type CreateRerank500 = InternalServerResponse +export const CreateRerank500 = InternalServerResponse +export type CreateRerank502 = BadGatewayResponse +export const CreateRerank502 = BadGatewayResponse +export type CreateRerank503 = ServiceUnavailableResponse +export const CreateRerank503 = ServiceUnavailableResponse +export type CreateRerank524 = EdgeNetworkTimeoutResponse +export const CreateRerank524 = EdgeNetworkTimeoutResponse +export type CreateRerank529 = ProviderOverloadedResponse +export const CreateRerank529 = ProviderOverloadedResponse +export type CreateResponsesParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string + readonly "X-OpenRouter-Metadata"?: MetadataLevel +} +export const CreateResponsesParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String), + "X-OpenRouter-Metadata": Schema.optionalKey(MetadataLevel) +}) +export type CreateResponsesRequestJson = ResponsesRequest +export const CreateResponsesRequestJson = ResponsesRequest +export type CreateResponses200 = OpenResponsesResult +export const CreateResponses200 = OpenResponsesResult +export type CreateResponses200Sse = ResponsesStreamingResponse +export const CreateResponses200Sse: Schema.Schema = ResponsesStreamingResponse +export type CreateResponses400 = BadRequestResponse +export const CreateResponses400 = BadRequestResponse +export type CreateResponses401 = UnauthorizedResponse +export const CreateResponses401 = UnauthorizedResponse +export type CreateResponses402 = PaymentRequiredResponse +export const CreateResponses402 = PaymentRequiredResponse +export type CreateResponses403 = ForbiddenResponse +export const CreateResponses403 = ForbiddenResponse +export type CreateResponses404 = NotFoundResponse +export const CreateResponses404 = NotFoundResponse +export type CreateResponses408 = RequestTimeoutResponse +export const CreateResponses408 = RequestTimeoutResponse +export type CreateResponses413 = PayloadTooLargeResponse +export const CreateResponses413 = PayloadTooLargeResponse +export type CreateResponses422 = UnprocessableEntityResponse +export const CreateResponses422 = UnprocessableEntityResponse +export type CreateResponses429 = TooManyRequestsResponse +export const CreateResponses429 = TooManyRequestsResponse +export type CreateResponses500 = InternalServerResponse +export const CreateResponses500 = InternalServerResponse +export type CreateResponses502 = BadGatewayResponse +export const CreateResponses502 = BadGatewayResponse +export type CreateResponses503 = ServiceUnavailableResponse +export const CreateResponses503 = ServiceUnavailableResponse +export type CreateResponses524 = EdgeNetworkTimeoutResponse +export const CreateResponses524 = EdgeNetworkTimeoutResponse +export type CreateResponses529 = ProviderOverloadedResponse +export const CreateResponses529 = ProviderOverloadedResponse +export type CreateVideosParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const CreateVideosParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type CreateVideosRequestJson = VideoGenerationRequest +export const CreateVideosRequestJson = VideoGenerationRequest +export type CreateVideos202 = VideoGenerationResponse +export const CreateVideos202 = VideoGenerationResponse +export type CreateVideos400 = BadRequestResponse +export const CreateVideos400 = BadRequestResponse +export type CreateVideos401 = UnauthorizedResponse +export const CreateVideos401 = UnauthorizedResponse +export type CreateVideos402 = PaymentRequiredResponse +export const CreateVideos402 = PaymentRequiredResponse +export type CreateVideos404 = NotFoundResponse +export const CreateVideos404 = NotFoundResponse +export type CreateVideos429 = TooManyRequestsResponse +export const CreateVideos429 = TooManyRequestsResponse +export type CreateVideos500 = InternalServerResponse +export const CreateVideos500 = InternalServerResponse +export type GetVideosParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const GetVideosParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type GetVideos200 = VideoGenerationResponse +export const GetVideos200 = VideoGenerationResponse +export type GetVideos401 = UnauthorizedResponse +export const GetVideos401 = UnauthorizedResponse +export type GetVideos404 = NotFoundResponse +export const GetVideos404 = NotFoundResponse +export type GetVideos500 = InternalServerResponse +export const GetVideos500 = InternalServerResponse +export type ListVideosContentParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string + readonly "index"?: number | null +} +export const ListVideosContentParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String), + "index": Schema.optionalKey( + Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check( + Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" }) + ), + Schema.Null + ]) + ) +}) +export type ListVideosContent400 = BadRequestResponse +export const ListVideosContent400 = BadRequestResponse +export type ListVideosContent401 = UnauthorizedResponse +export const ListVideosContent401 = UnauthorizedResponse +export type ListVideosContent404 = NotFoundResponse +export const ListVideosContent404 = NotFoundResponse +export type ListVideosContent500 = InternalServerResponse +export const ListVideosContent500 = InternalServerResponse +export type ListVideosContent502 = BadGatewayResponse +export const ListVideosContent502 = BadGatewayResponse +export type ListVideosModelsParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const ListVideosModelsParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type ListVideosModels200 = VideoModelsListResponse +export const ListVideosModels200 = VideoModelsListResponse +export type ListVideosModels400 = BadRequestResponse +export const ListVideosModels400 = BadRequestResponse +export type ListVideosModels500 = InternalServerResponse +export const ListVideosModels500 = InternalServerResponse +export type ListWorkspacesParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string + readonly "offset"?: number | null + readonly "limit"?: number +} +export const ListWorkspacesParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String), + "offset": Schema.optionalKey( + Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check( + Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" }) + ), + Schema.Null + ]).annotate({ "description": "Number of records to skip for pagination" }) + ), + "limit": Schema.optionalKey( + Schema.Number.annotate({ "description": "Maximum number of records to return (max 100)" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ).check(Schema.isGreaterThanOrEqualTo(1).annotate({ "expected": "a value greater than or equal to 1" })).check( + Schema.isLessThanOrEqualTo(100).annotate({ "expected": "a value less than or equal to 100" }) + ) + ) +}) +export type ListWorkspaces200 = ListWorkspacesResponse +export const ListWorkspaces200 = ListWorkspacesResponse +export type ListWorkspaces401 = UnauthorizedResponse +export const ListWorkspaces401 = UnauthorizedResponse +export type ListWorkspaces500 = InternalServerResponse +export const ListWorkspaces500 = InternalServerResponse +export type CreateWorkspaceParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const CreateWorkspaceParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type CreateWorkspaceRequestJson = CreateWorkspaceRequest +export const CreateWorkspaceRequestJson = CreateWorkspaceRequest +export type CreateWorkspace201 = CreateWorkspaceResponse +export const CreateWorkspace201 = CreateWorkspaceResponse +export type CreateWorkspace400 = BadRequestResponse +export const CreateWorkspace400 = BadRequestResponse +export type CreateWorkspace401 = UnauthorizedResponse +export const CreateWorkspace401 = UnauthorizedResponse +export type CreateWorkspace403 = ForbiddenResponse +export const CreateWorkspace403 = ForbiddenResponse +export type CreateWorkspace500 = InternalServerResponse +export const CreateWorkspace500 = InternalServerResponse +export type GetWorkspaceParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const GetWorkspaceParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type GetWorkspace200 = GetWorkspaceResponse +export const GetWorkspace200 = GetWorkspaceResponse +export type GetWorkspace401 = UnauthorizedResponse +export const GetWorkspace401 = UnauthorizedResponse +export type GetWorkspace404 = NotFoundResponse +export const GetWorkspace404 = NotFoundResponse +export type GetWorkspace500 = InternalServerResponse +export const GetWorkspace500 = InternalServerResponse +export type DeleteWorkspaceParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const DeleteWorkspaceParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type DeleteWorkspace200 = DeleteWorkspaceResponse +export const DeleteWorkspace200 = DeleteWorkspaceResponse +export type DeleteWorkspace400 = BadRequestResponse +export const DeleteWorkspace400 = BadRequestResponse +export type DeleteWorkspace401 = UnauthorizedResponse +export const DeleteWorkspace401 = UnauthorizedResponse +export type DeleteWorkspace403 = ForbiddenResponse +export const DeleteWorkspace403 = ForbiddenResponse +export type DeleteWorkspace404 = NotFoundResponse +export const DeleteWorkspace404 = NotFoundResponse +export type DeleteWorkspace500 = InternalServerResponse +export const DeleteWorkspace500 = InternalServerResponse +export type UpdateWorkspaceParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const UpdateWorkspaceParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type UpdateWorkspaceRequestJson = UpdateWorkspaceRequest +export const UpdateWorkspaceRequestJson = UpdateWorkspaceRequest +export type UpdateWorkspace200 = UpdateWorkspaceResponse +export const UpdateWorkspace200 = UpdateWorkspaceResponse +export type UpdateWorkspace400 = BadRequestResponse +export const UpdateWorkspace400 = BadRequestResponse +export type UpdateWorkspace401 = UnauthorizedResponse +export const UpdateWorkspace401 = UnauthorizedResponse +export type UpdateWorkspace403 = ForbiddenResponse +export const UpdateWorkspace403 = ForbiddenResponse +export type UpdateWorkspace404 = NotFoundResponse +export const UpdateWorkspace404 = NotFoundResponse +export type UpdateWorkspace500 = InternalServerResponse +export const UpdateWorkspace500 = InternalServerResponse +export type ListWorkspaceBudgetsParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const ListWorkspaceBudgetsParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type ListWorkspaceBudgets200 = ListWorkspaceBudgetsResponse +export const ListWorkspaceBudgets200 = ListWorkspaceBudgetsResponse +export type ListWorkspaceBudgets401 = UnauthorizedResponse +export const ListWorkspaceBudgets401 = UnauthorizedResponse +export type ListWorkspaceBudgets404 = NotFoundResponse +export const ListWorkspaceBudgets404 = NotFoundResponse +export type ListWorkspaceBudgets500 = InternalServerResponse +export const ListWorkspaceBudgets500 = InternalServerResponse +export type UpsertWorkspaceBudgetParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const UpsertWorkspaceBudgetParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type UpsertWorkspaceBudgetRequestJson = UpsertWorkspaceBudgetRequest +export const UpsertWorkspaceBudgetRequestJson = UpsertWorkspaceBudgetRequest +export type UpsertWorkspaceBudget200 = UpsertWorkspaceBudgetResponse +export const UpsertWorkspaceBudget200 = UpsertWorkspaceBudgetResponse +export type UpsertWorkspaceBudget400 = BadRequestResponse +export const UpsertWorkspaceBudget400 = BadRequestResponse +export type UpsertWorkspaceBudget401 = UnauthorizedResponse +export const UpsertWorkspaceBudget401 = UnauthorizedResponse +export type UpsertWorkspaceBudget404 = NotFoundResponse +export const UpsertWorkspaceBudget404 = NotFoundResponse +export type UpsertWorkspaceBudget500 = InternalServerResponse +export const UpsertWorkspaceBudget500 = InternalServerResponse +export type DeleteWorkspaceBudgetParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const DeleteWorkspaceBudgetParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type DeleteWorkspaceBudget200 = DeleteWorkspaceBudgetResponse +export const DeleteWorkspaceBudget200 = DeleteWorkspaceBudgetResponse +export type DeleteWorkspaceBudget401 = UnauthorizedResponse +export const DeleteWorkspaceBudget401 = UnauthorizedResponse +export type DeleteWorkspaceBudget404 = NotFoundResponse +export const DeleteWorkspaceBudget404 = NotFoundResponse +export type DeleteWorkspaceBudget500 = InternalServerResponse +export const DeleteWorkspaceBudget500 = InternalServerResponse +export type ListWorkspaceMembersParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string + readonly "offset"?: number | null + readonly "limit"?: number +} +export const ListWorkspaceMembersParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String), + "offset": Schema.optionalKey( + Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check( + Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" }) + ), + Schema.Null + ]).annotate({ "description": "Number of records to skip for pagination" }) + ), + "limit": Schema.optionalKey( + Schema.Number.annotate({ "description": "Maximum number of records to return (max 100)" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ).check(Schema.isGreaterThanOrEqualTo(1).annotate({ "expected": "a value greater than or equal to 1" })).check( + Schema.isLessThanOrEqualTo(100).annotate({ "expected": "a value less than or equal to 100" }) + ) + ) +}) +export type ListWorkspaceMembers200 = ListWorkspaceMembersResponse +export const ListWorkspaceMembers200 = ListWorkspaceMembersResponse +export type ListWorkspaceMembers401 = UnauthorizedResponse +export const ListWorkspaceMembers401 = UnauthorizedResponse +export type ListWorkspaceMembers403 = ForbiddenResponse +export const ListWorkspaceMembers403 = ForbiddenResponse +export type ListWorkspaceMembers404 = NotFoundResponse +export const ListWorkspaceMembers404 = NotFoundResponse +export type ListWorkspaceMembers500 = InternalServerResponse +export const ListWorkspaceMembers500 = InternalServerResponse +export type BulkAddWorkspaceMembersParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const BulkAddWorkspaceMembersParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type BulkAddWorkspaceMembersRequestJson = BulkAddWorkspaceMembersRequest +export const BulkAddWorkspaceMembersRequestJson = BulkAddWorkspaceMembersRequest +export type BulkAddWorkspaceMembers200 = BulkAddWorkspaceMembersResponse +export const BulkAddWorkspaceMembers200 = BulkAddWorkspaceMembersResponse +export type BulkAddWorkspaceMembers400 = BadRequestResponse +export const BulkAddWorkspaceMembers400 = BadRequestResponse +export type BulkAddWorkspaceMembers401 = UnauthorizedResponse +export const BulkAddWorkspaceMembers401 = UnauthorizedResponse +export type BulkAddWorkspaceMembers403 = ForbiddenResponse +export const BulkAddWorkspaceMembers403 = ForbiddenResponse +export type BulkAddWorkspaceMembers404 = NotFoundResponse +export const BulkAddWorkspaceMembers404 = NotFoundResponse +export type BulkAddWorkspaceMembers500 = InternalServerResponse +export const BulkAddWorkspaceMembers500 = InternalServerResponse +export type BulkRemoveWorkspaceMembersParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const BulkRemoveWorkspaceMembersParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type BulkRemoveWorkspaceMembersRequestJson = BulkRemoveWorkspaceMembersRequest +export const BulkRemoveWorkspaceMembersRequestJson = BulkRemoveWorkspaceMembersRequest +export type BulkRemoveWorkspaceMembers200 = BulkRemoveWorkspaceMembersResponse +export const BulkRemoveWorkspaceMembers200 = BulkRemoveWorkspaceMembersResponse +export type BulkRemoveWorkspaceMembers400 = BadRequestResponse +export const BulkRemoveWorkspaceMembers400 = BadRequestResponse +export type BulkRemoveWorkspaceMembers401 = UnauthorizedResponse +export const BulkRemoveWorkspaceMembers401 = UnauthorizedResponse +export type BulkRemoveWorkspaceMembers403 = ForbiddenResponse +export const BulkRemoveWorkspaceMembers403 = ForbiddenResponse +export type BulkRemoveWorkspaceMembers404 = NotFoundResponse +export const BulkRemoveWorkspaceMembers404 = NotFoundResponse +export type BulkRemoveWorkspaceMembers500 = InternalServerResponse +export const BulkRemoveWorkspaceMembers500 = InternalServerResponse + +export interface OperationConfig { + /** + * Whether or not the response should be included in the value returned from + * an operation. + * + * If set to `true`, a tuple of `[A, HttpClientResponse]` will be returned, + * where `A` is the success type of the operation. + * + * If set to `false`, only the success type of the operation will be returned. + */ + readonly includeResponse?: boolean | undefined +} + +/** + * A utility type which optionally includes the response in the return result + * of an operation based upon the value of the `includeResponse` configuration + * option. + */ +export type WithOptionalResponse = Config extends { + readonly includeResponse: true +} ? [A, HttpClientResponse.HttpClientResponse] : + A + +export const make = ( + httpClient: HttpClient.HttpClient, + options: { + readonly transformClient?: ((client: HttpClient.HttpClient) => Effect.Effect) | undefined + } = {} +): OpenRouterClient => { + const unexpectedStatus = (response: HttpClientResponse.HttpClientResponse) => + Effect.flatMap( + Effect.orElseSucceed(response.json, () => "Unexpected status code"), + (description) => + Effect.fail( + new HttpClientError.HttpClientError({ + reason: new HttpClientError.StatusCodeError({ + request: response.request, + response, + description: typeof description === "string" ? description : JSON.stringify(description) + }) + }) + ) + ) + const withResponse = (config: Config | undefined) => + ( + f: (response: HttpClientResponse.HttpClientResponse) => Effect.Effect + ): (request: HttpClientRequest.HttpClientRequest) => Effect.Effect => { + const withOptionalResponse = ( + config?.includeResponse + ? (response: HttpClientResponse.HttpClientResponse) => Effect.map(f(response), (a) => [a, response]) + : (response: HttpClientResponse.HttpClientResponse) => f(response) + ) as any + return options?.transformClient + ? (request) => + Effect.flatMap( + Effect.flatMap(options.transformClient!(httpClient), (client) => client.execute(request)), + withOptionalResponse + ) + : (request) => Effect.flatMap(httpClient.execute(request), withOptionalResponse) + } + const sseRequest = < + Type, + DecodingServices + >( + schema: Schema.ConstraintDecoder + ) => + ( + request: HttpClientRequest.HttpClientRequest + ): Stream.Stream< + { readonly event: string; readonly id: string | undefined; readonly data: Type }, + HttpClientError.HttpClientError | SchemaError | Sse.Retry | Sse.SseError, + DecodingServices + > => + HttpClient.filterStatusOk(httpClient).execute(request).pipe( + Effect.map((response) => response.stream), + Stream.unwrap, + Stream.decodeText(), + Stream.pipeThroughChannel(Sse.decodeDataSchema(schema)) + ) + const binaryRequest = ( + request: HttpClientRequest.HttpClientRequest + ): Stream.Stream => + HttpClient.filterStatusOk(httpClient).execute(request).pipe( + Effect.map((response) => response.stream), + Stream.unwrap + ) + const decodeSuccess = + (schema: Schema) => (response: HttpClientResponse.HttpClientResponse) => + HttpClientResponse.schemaBodyJson(schema)(response) + const decodeError = + (tag: Tag, schema: Schema) => + (response: HttpClientResponse.HttpClientResponse) => + Effect.flatMap( + HttpClientResponse.schemaBodyJson(schema)(response), + (cause) => Effect.fail(OpenRouterClientError(tag, cause, response)) + ) + return { + httpClient, + "getUserActivity": (options) => + HttpClientRequest.get(`/activity`).pipe( + HttpClientRequest.setUrlParams({ + "date": options?.params?.["date"] as any, + "api_key_hash": options?.params?.["api_key_hash"] as any, + "user_id": options?.params?.["user_id"] as any + }), + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(GetUserActivity200), + "400": decodeError("GetUserActivity400", GetUserActivity400), + "401": decodeError("GetUserActivity401", GetUserActivity401), + "403": decodeError("GetUserActivity403", GetUserActivity403), + "404": decodeError("GetUserActivity404", GetUserActivity404), + "500": decodeError("GetUserActivity500", GetUserActivity500), + orElse: unexpectedStatus + })) + ), + "getAnalyticsMeta": (options) => + HttpClientRequest.get(`/analytics/meta`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(GetAnalyticsMeta200), + "401": decodeError("GetAnalyticsMeta401", GetAnalyticsMeta401), + "403": decodeError("GetAnalyticsMeta403", GetAnalyticsMeta403), + "500": decodeError("GetAnalyticsMeta500", GetAnalyticsMeta500), + orElse: unexpectedStatus + })) + ), + "queryAnalytics": (options) => + HttpClientRequest.post(`/analytics/query`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options.params?.["X-OpenRouter-Categories"] ?? undefined + }), + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(QueryAnalytics200), + "400": decodeError("QueryAnalytics400", QueryAnalytics400), + "401": decodeError("QueryAnalytics401", QueryAnalytics401), + "403": decodeError("QueryAnalytics403", QueryAnalytics403), + "408": decodeError("QueryAnalytics408", QueryAnalytics408), + "500": decodeError("QueryAnalytics500", QueryAnalytics500), + orElse: unexpectedStatus + })) + ), + "createAudioSpeech": (options) => + HttpClientRequest.post(`/audio/speech`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options.params?.["X-OpenRouter-Categories"] ?? undefined + }), + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "400": decodeError("CreateAudioSpeech400", CreateAudioSpeech400), + "401": decodeError("CreateAudioSpeech401", CreateAudioSpeech401), + "402": decodeError("CreateAudioSpeech402", CreateAudioSpeech402), + "404": decodeError("CreateAudioSpeech404", CreateAudioSpeech404), + "429": decodeError("CreateAudioSpeech429", CreateAudioSpeech429), + "500": decodeError("CreateAudioSpeech500", CreateAudioSpeech500), + "502": decodeError("CreateAudioSpeech502", CreateAudioSpeech502), + "503": decodeError("CreateAudioSpeech503", CreateAudioSpeech503), + "524": decodeError("CreateAudioSpeech524", CreateAudioSpeech524), + "529": decodeError("CreateAudioSpeech529", CreateAudioSpeech529), + orElse: unexpectedStatus + })) + ), + "createAudioTranscriptions": (options) => + HttpClientRequest.post(`/audio/transcriptions`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options.params?.["X-OpenRouter-Categories"] ?? undefined + }), + HttpClientRequest.bodyFormData(options.payload as any), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(CreateAudioTranscriptions200), + "400": decodeError("CreateAudioTranscriptions400", CreateAudioTranscriptions400), + "401": decodeError("CreateAudioTranscriptions401", CreateAudioTranscriptions401), + "402": decodeError("CreateAudioTranscriptions402", CreateAudioTranscriptions402), + "404": decodeError("CreateAudioTranscriptions404", CreateAudioTranscriptions404), + "429": decodeError("CreateAudioTranscriptions429", CreateAudioTranscriptions429), + "500": decodeError("CreateAudioTranscriptions500", CreateAudioTranscriptions500), + "502": decodeError("CreateAudioTranscriptions502", CreateAudioTranscriptions502), + "503": decodeError("CreateAudioTranscriptions503", CreateAudioTranscriptions503), + "524": decodeError("CreateAudioTranscriptions524", CreateAudioTranscriptions524), + "529": decodeError("CreateAudioTranscriptions529", CreateAudioTranscriptions529), + orElse: unexpectedStatus + })) + ), + "exchangeAuthCodeForAPIKey": (options) => + HttpClientRequest.post(`/auth/keys`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options.params?.["X-OpenRouter-Categories"] ?? undefined + }), + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ExchangeAuthCodeForAPIKey200), + "400": decodeError("ExchangeAuthCodeForAPIKey400", ExchangeAuthCodeForAPIKey400), + "403": decodeError("ExchangeAuthCodeForAPIKey403", ExchangeAuthCodeForAPIKey403), + "500": decodeError("ExchangeAuthCodeForAPIKey500", ExchangeAuthCodeForAPIKey500), + orElse: unexpectedStatus + })) + ), + "createAuthKeysCode": (options) => + HttpClientRequest.post(`/auth/keys/code`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options.params?.["X-OpenRouter-Categories"] ?? undefined + }), + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(CreateAuthKeysCode200), + "400": decodeError("CreateAuthKeysCode400", CreateAuthKeysCode400), + "401": decodeError("CreateAuthKeysCode401", CreateAuthKeysCode401), + "403": decodeError("CreateAuthKeysCode403", CreateAuthKeysCode403), + "409": decodeError("CreateAuthKeysCode409", CreateAuthKeysCode409), + "500": decodeError("CreateAuthKeysCode500", CreateAuthKeysCode500), + orElse: unexpectedStatus + })) + ), + "getBenchmarks": (options) => + HttpClientRequest.get(`/benchmarks`).pipe( + HttpClientRequest.setUrlParams({ + "source": options?.params?.["source"] as any, + "task_type": options?.params?.["task_type"] as any, + "arena": options?.params?.["arena"] as any, + "category": options?.params?.["category"] as any, + "max_results": options?.params?.["max_results"] as any + }), + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(GetBenchmarks200), + "400": decodeError("GetBenchmarks400", GetBenchmarks400), + "401": decodeError("GetBenchmarks401", GetBenchmarks401), + "429": decodeError("GetBenchmarks429", GetBenchmarks429), + "500": decodeError("GetBenchmarks500", GetBenchmarks500), + orElse: unexpectedStatus + })) + ), + "listBYOKKeys": (options) => + HttpClientRequest.get(`/byok`).pipe( + HttpClientRequest.setUrlParams({ + "offset": options?.params?.["offset"] as any, + "limit": options?.params?.["limit"] as any, + "workspace_id": options?.params?.["workspace_id"] as any, + "provider": options?.params?.["provider"] as any + }), + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ListBYOKKeys200), + "400": decodeError("ListBYOKKeys400", ListBYOKKeys400), + "401": decodeError("ListBYOKKeys401", ListBYOKKeys401), + "500": decodeError("ListBYOKKeys500", ListBYOKKeys500), + orElse: unexpectedStatus + })) + ), + "createBYOKKey": (options) => + HttpClientRequest.post(`/byok`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options.params?.["X-OpenRouter-Categories"] ?? undefined + }), + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(CreateBYOKKey201), + "400": decodeError("CreateBYOKKey400", CreateBYOKKey400), + "401": decodeError("CreateBYOKKey401", CreateBYOKKey401), + "403": decodeError("CreateBYOKKey403", CreateBYOKKey403), + "500": decodeError("CreateBYOKKey500", CreateBYOKKey500), + orElse: unexpectedStatus + })) + ), + "getBYOKKey": (id, options) => + HttpClientRequest.get(`/byok/${id}`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(GetBYOKKey200), + "401": decodeError("GetBYOKKey401", GetBYOKKey401), + "404": decodeError("GetBYOKKey404", GetBYOKKey404), + "500": decodeError("GetBYOKKey500", GetBYOKKey500), + orElse: unexpectedStatus + })) + ), + "deleteBYOKKey": (id, options) => + HttpClientRequest.delete(`/byok/${id}`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(DeleteBYOKKey200), + "401": decodeError("DeleteBYOKKey401", DeleteBYOKKey401), + "404": decodeError("DeleteBYOKKey404", DeleteBYOKKey404), + "500": decodeError("DeleteBYOKKey500", DeleteBYOKKey500), + orElse: unexpectedStatus + })) + ), + "updateBYOKKey": (id, options) => + HttpClientRequest.patch(`/byok/${id}`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options.params?.["X-OpenRouter-Categories"] ?? undefined + }), + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(UpdateBYOKKey200), + "400": decodeError("UpdateBYOKKey400", UpdateBYOKKey400), + "401": decodeError("UpdateBYOKKey401", UpdateBYOKKey401), + "404": decodeError("UpdateBYOKKey404", UpdateBYOKKey404), + "500": decodeError("UpdateBYOKKey500", UpdateBYOKKey500), + orElse: unexpectedStatus + })) + ), + "sendChatCompletionRequest": (options) => + HttpClientRequest.post(`/chat/completions`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options.params?.["X-OpenRouter-Categories"] ?? undefined, + "X-OpenRouter-Metadata": options.params?.["X-OpenRouter-Metadata"] ?? undefined + }), + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(SendChatCompletionRequest200), + "400": decodeError("SendChatCompletionRequest400", SendChatCompletionRequest400), + "401": decodeError("SendChatCompletionRequest401", SendChatCompletionRequest401), + "402": decodeError("SendChatCompletionRequest402", SendChatCompletionRequest402), + "403": decodeError("SendChatCompletionRequest403", SendChatCompletionRequest403), + "404": decodeError("SendChatCompletionRequest404", SendChatCompletionRequest404), + "408": decodeError("SendChatCompletionRequest408", SendChatCompletionRequest408), + "413": decodeError("SendChatCompletionRequest413", SendChatCompletionRequest413), + "422": decodeError("SendChatCompletionRequest422", SendChatCompletionRequest422), + "429": decodeError("SendChatCompletionRequest429", SendChatCompletionRequest429), + "500": decodeError("SendChatCompletionRequest500", SendChatCompletionRequest500), + "502": decodeError("SendChatCompletionRequest502", SendChatCompletionRequest502), + "503": decodeError("SendChatCompletionRequest503", SendChatCompletionRequest503), + "524": decodeError("SendChatCompletionRequest524", SendChatCompletionRequest524), + "529": decodeError("SendChatCompletionRequest529", SendChatCompletionRequest529), + orElse: unexpectedStatus + })) + ), + "sendChatCompletionRequestSse": (options) => + HttpClientRequest.post(`/chat/completions`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options.params?.["X-OpenRouter-Categories"] ?? undefined, + "X-OpenRouter-Metadata": options.params?.["X-OpenRouter-Metadata"] ?? undefined + }), + HttpClientRequest.bodyJsonUnsafe(options.payload), + sseRequest(SendChatCompletionRequest200Sse) + ), + "getTaskClassifications": (options) => + HttpClientRequest.get(`/classifications/task`).pipe( + HttpClientRequest.setUrlParams({ "window": options?.params?.["window"] as any }), + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(GetTaskClassifications200), + "400": decodeError("GetTaskClassifications400", GetTaskClassifications400), + "401": decodeError("GetTaskClassifications401", GetTaskClassifications401), + "429": decodeError("GetTaskClassifications429", GetTaskClassifications429), + "500": decodeError("GetTaskClassifications500", GetTaskClassifications500), + orElse: unexpectedStatus + })) + ), + "getCredits": (options) => + HttpClientRequest.get(`/credits`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(GetCredits200), + "401": decodeError("GetCredits401", GetCredits401), + "403": decodeError("GetCredits403", GetCredits403), + "500": decodeError("GetCredits500", GetCredits500), + orElse: unexpectedStatus + })) + ), + "createCoinbaseCharge": (options) => + HttpClientRequest.post(`/credits/coinbase`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "410": decodeError("CreateCoinbaseCharge410", CreateCoinbaseCharge410), + "200": () => Effect.void, + orElse: unexpectedStatus + })) + ), + "getAppRankings": (options) => + HttpClientRequest.get(`/datasets/app-rankings`).pipe( + HttpClientRequest.setUrlParams({ + "category": options?.params?.["category"] as any, + "subcategory": options?.params?.["subcategory"] as any, + "sort": options?.params?.["sort"] as any, + "start_date": options?.params?.["start_date"] as any, + "end_date": options?.params?.["end_date"] as any, + "limit": options?.params?.["limit"] as any, + "offset": options?.params?.["offset"] as any + }), + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(GetAppRankings200), + "400": decodeError("GetAppRankings400", GetAppRankings400), + "401": decodeError("GetAppRankings401", GetAppRankings401), + "429": decodeError("GetAppRankings429", GetAppRankings429), + "500": decodeError("GetAppRankings500", GetAppRankings500), + orElse: unexpectedStatus + })) + ), + "getRankingsDaily": (options) => + HttpClientRequest.get(`/datasets/rankings-daily`).pipe( + HttpClientRequest.setUrlParams({ + "start_date": options?.params?.["start_date"] as any, + "end_date": options?.params?.["end_date"] as any, + "period": options?.params?.["period"] as any, + "modality": options?.params?.["modality"] as any, + "context_bucket": options?.params?.["context_bucket"] as any, + "category": options?.params?.["category"] as any, + "language_type": options?.params?.["language_type"] as any + }), + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(GetRankingsDaily200), + "400": decodeError("GetRankingsDaily400", GetRankingsDaily400), + "401": decodeError("GetRankingsDaily401", GetRankingsDaily401), + "429": decodeError("GetRankingsDaily429", GetRankingsDaily429), + "500": decodeError("GetRankingsDaily500", GetRankingsDaily500), + orElse: unexpectedStatus + })) + ), + "createEmbeddings": (options) => + HttpClientRequest.post(`/embeddings`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options.params?.["X-OpenRouter-Categories"] ?? undefined + }), + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(CreateEmbeddings200), + "400": decodeError("CreateEmbeddings400", CreateEmbeddings400), + "401": decodeError("CreateEmbeddings401", CreateEmbeddings401), + "402": decodeError("CreateEmbeddings402", CreateEmbeddings402), + "404": decodeError("CreateEmbeddings404", CreateEmbeddings404), + "429": decodeError("CreateEmbeddings429", CreateEmbeddings429), + "500": decodeError("CreateEmbeddings500", CreateEmbeddings500), + "502": decodeError("CreateEmbeddings502", CreateEmbeddings502), + "503": decodeError("CreateEmbeddings503", CreateEmbeddings503), + "524": decodeError("CreateEmbeddings524", CreateEmbeddings524), + "529": decodeError("CreateEmbeddings529", CreateEmbeddings529), + orElse: unexpectedStatus + })) + ), + "createEmbeddingsSse": (options) => + HttpClientRequest.post(`/embeddings`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options.params?.["X-OpenRouter-Categories"] ?? undefined + }), + HttpClientRequest.bodyJsonUnsafe(options.payload), + sseRequest(CreateEmbeddings200Sse) + ), + "listEmbeddingsModels": (options) => + HttpClientRequest.get(`/embeddings/models`).pipe( + HttpClientRequest.setUrlParams({ + "offset": options?.params?.["offset"] as any, + "limit": options?.params?.["limit"] as any + }), + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ListEmbeddingsModels200), + "400": decodeError("ListEmbeddingsModels400", ListEmbeddingsModels400), + "500": decodeError("ListEmbeddingsModels500", ListEmbeddingsModels500), + orElse: unexpectedStatus + })) + ), + "listEndpointsZdr": (options) => + HttpClientRequest.get(`/endpoints/zdr`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ListEndpointsZdr200), + "403": decodeError("ListEndpointsZdr403", ListEndpointsZdr403), + "500": decodeError("ListEndpointsZdr500", ListEndpointsZdr500), + orElse: unexpectedStatus + })) + ), + "listFiles": (options) => + HttpClientRequest.get(`/files`).pipe( + HttpClientRequest.setUrlParams({ + "limit": options?.params?.["limit"] as any, + "cursor": options?.params?.["cursor"] as any, + "workspace_id": options?.params?.["workspace_id"] as any + }), + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ListFiles200), + "400": decodeError("ListFiles400", ListFiles400), + "401": decodeError("ListFiles401", ListFiles401), + "429": decodeError("ListFiles429", ListFiles429), + "500": decodeError("ListFiles500", ListFiles500), + orElse: unexpectedStatus + })) + ), + "uploadFile": (options) => + HttpClientRequest.post(`/files`).pipe( + HttpClientRequest.setUrlParams({ "workspace_id": options.params?.["workspace_id"] as any }), + HttpClientRequest.setHeaders({ + "HTTP-Referer": options.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options.params?.["X-OpenRouter-Categories"] ?? undefined + }), + HttpClientRequest.bodyFormData(options.payload as any), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(UploadFile200), + "400": decodeError("UploadFile400", UploadFile400), + "401": decodeError("UploadFile401", UploadFile401), + "403": decodeError("UploadFile403", UploadFile403), + "413": decodeError("UploadFile413", UploadFile413), + "429": decodeError("UploadFile429", UploadFile429), + "500": decodeError("UploadFile500", UploadFile500), + orElse: unexpectedStatus + })) + ), + "getFileMetadata": (fileId, options) => + HttpClientRequest.get(`/files/${fileId}`).pipe( + HttpClientRequest.setUrlParams({ "workspace_id": options?.params?.["workspace_id"] as any }), + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(GetFileMetadata200), + "401": decodeError("GetFileMetadata401", GetFileMetadata401), + "404": decodeError("GetFileMetadata404", GetFileMetadata404), + "429": decodeError("GetFileMetadata429", GetFileMetadata429), + "500": decodeError("GetFileMetadata500", GetFileMetadata500), + orElse: unexpectedStatus + })) + ), + "deleteFile": (fileId, options) => + HttpClientRequest.delete(`/files/${fileId}`).pipe( + HttpClientRequest.setUrlParams({ "workspace_id": options?.params?.["workspace_id"] as any }), + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(DeleteFile200), + "401": decodeError("DeleteFile401", DeleteFile401), + "404": decodeError("DeleteFile404", DeleteFile404), + "429": decodeError("DeleteFile429", DeleteFile429), + "500": decodeError("DeleteFile500", DeleteFile500), + orElse: unexpectedStatus + })) + ), + "downloadFileContent": (fileId, options) => + HttpClientRequest.get(`/files/${fileId}/content`).pipe( + HttpClientRequest.setUrlParams({ "workspace_id": options?.params?.["workspace_id"] as any }), + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "400": decodeError("DownloadFileContent400", DownloadFileContent400), + "401": decodeError("DownloadFileContent401", DownloadFileContent401), + "404": decodeError("DownloadFileContent404", DownloadFileContent404), + "429": decodeError("DownloadFileContent429", DownloadFileContent429), + "500": decodeError("DownloadFileContent500", DownloadFileContent500), + orElse: unexpectedStatus + })) + ), + "downloadFileContentStream": (fileId, options) => + HttpClientRequest.get(`/files/${fileId}/content`).pipe( + HttpClientRequest.setUrlParams({ "workspace_id": options?.params?.["workspace_id"] as any }), + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + binaryRequest + ), + "getGeneration": (options) => + HttpClientRequest.get(`/generation`).pipe( + HttpClientRequest.setUrlParams({ "id": options.params["id"] as any }), + HttpClientRequest.setHeaders({ + "HTTP-Referer": options.params["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options.params["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options.params["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(GetGeneration200), + "401": decodeError("GetGeneration401", GetGeneration401), + "402": decodeError("GetGeneration402", GetGeneration402), + "404": decodeError("GetGeneration404", GetGeneration404), + "429": decodeError("GetGeneration429", GetGeneration429), + "500": decodeError("GetGeneration500", GetGeneration500), + "502": decodeError("GetGeneration502", GetGeneration502), + "524": decodeError("GetGeneration524", GetGeneration524), + "529": decodeError("GetGeneration529", GetGeneration529), + orElse: unexpectedStatus + })) + ), + "listGenerationContent": (options) => + HttpClientRequest.get(`/generation/content`).pipe( + HttpClientRequest.setUrlParams({ "id": options.params["id"] as any }), + HttpClientRequest.setHeaders({ + "HTTP-Referer": options.params["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options.params["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options.params["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ListGenerationContent200), + "401": decodeError("ListGenerationContent401", ListGenerationContent401), + "403": decodeError("ListGenerationContent403", ListGenerationContent403), + "404": decodeError("ListGenerationContent404", ListGenerationContent404), + "429": decodeError("ListGenerationContent429", ListGenerationContent429), + "500": decodeError("ListGenerationContent500", ListGenerationContent500), + "502": decodeError("ListGenerationContent502", ListGenerationContent502), + "524": decodeError("ListGenerationContent524", ListGenerationContent524), + "529": decodeError("ListGenerationContent529", ListGenerationContent529), + orElse: unexpectedStatus + })) + ), + "submitGenerationFeedback": (options) => + HttpClientRequest.post(`/generation/feedback`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options.params?.["X-OpenRouter-Categories"] ?? undefined + }), + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(SubmitGenerationFeedback200), + "400": decodeError("SubmitGenerationFeedback400", SubmitGenerationFeedback400), + "401": decodeError("SubmitGenerationFeedback401", SubmitGenerationFeedback401), + "404": decodeError("SubmitGenerationFeedback404", SubmitGenerationFeedback404), + "429": decodeError("SubmitGenerationFeedback429", SubmitGenerationFeedback429), + "500": decodeError("SubmitGenerationFeedback500", SubmitGenerationFeedback500), + orElse: unexpectedStatus + })) + ), + "listGuardrails": (options) => + HttpClientRequest.get(`/guardrails`).pipe( + HttpClientRequest.setUrlParams({ + "offset": options?.params?.["offset"] as any, + "limit": options?.params?.["limit"] as any, + "workspace_id": options?.params?.["workspace_id"] as any + }), + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ListGuardrails200), + "400": decodeError("ListGuardrails400", ListGuardrails400), + "401": decodeError("ListGuardrails401", ListGuardrails401), + "500": decodeError("ListGuardrails500", ListGuardrails500), + orElse: unexpectedStatus + })) + ), + "createGuardrail": (options) => + HttpClientRequest.post(`/guardrails`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options.params?.["X-OpenRouter-Categories"] ?? undefined + }), + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(CreateGuardrail201), + "400": decodeError("CreateGuardrail400", CreateGuardrail400), + "401": decodeError("CreateGuardrail401", CreateGuardrail401), + "403": decodeError("CreateGuardrail403", CreateGuardrail403), + "500": decodeError("CreateGuardrail500", CreateGuardrail500), + orElse: unexpectedStatus + })) + ), + "getGuardrail": (id, options) => + HttpClientRequest.get(`/guardrails/${id}`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(GetGuardrail200), + "401": decodeError("GetGuardrail401", GetGuardrail401), + "404": decodeError("GetGuardrail404", GetGuardrail404), + "500": decodeError("GetGuardrail500", GetGuardrail500), + orElse: unexpectedStatus + })) + ), + "deleteGuardrail": (id, options) => + HttpClientRequest.delete(`/guardrails/${id}`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(DeleteGuardrail200), + "401": decodeError("DeleteGuardrail401", DeleteGuardrail401), + "404": decodeError("DeleteGuardrail404", DeleteGuardrail404), + "500": decodeError("DeleteGuardrail500", DeleteGuardrail500), + orElse: unexpectedStatus + })) + ), + "updateGuardrail": (id, options) => + HttpClientRequest.patch(`/guardrails/${id}`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options.params?.["X-OpenRouter-Categories"] ?? undefined + }), + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(UpdateGuardrail200), + "400": decodeError("UpdateGuardrail400", UpdateGuardrail400), + "401": decodeError("UpdateGuardrail401", UpdateGuardrail401), + "404": decodeError("UpdateGuardrail404", UpdateGuardrail404), + "500": decodeError("UpdateGuardrail500", UpdateGuardrail500), + orElse: unexpectedStatus + })) + ), + "listGuardrailKeyAssignments": (id, options) => + HttpClientRequest.get(`/guardrails/${id}/assignments/keys`).pipe( + HttpClientRequest.setUrlParams({ + "offset": options?.params?.["offset"] as any, + "limit": options?.params?.["limit"] as any + }), + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ListGuardrailKeyAssignments200), + "401": decodeError("ListGuardrailKeyAssignments401", ListGuardrailKeyAssignments401), + "404": decodeError("ListGuardrailKeyAssignments404", ListGuardrailKeyAssignments404), + "500": decodeError("ListGuardrailKeyAssignments500", ListGuardrailKeyAssignments500), + orElse: unexpectedStatus + })) + ), + "bulkAssignKeysToGuardrail": (id, options) => + HttpClientRequest.post(`/guardrails/${id}/assignments/keys`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options.params?.["X-OpenRouter-Categories"] ?? undefined + }), + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(BulkAssignKeysToGuardrail200), + "400": decodeError("BulkAssignKeysToGuardrail400", BulkAssignKeysToGuardrail400), + "401": decodeError("BulkAssignKeysToGuardrail401", BulkAssignKeysToGuardrail401), + "404": decodeError("BulkAssignKeysToGuardrail404", BulkAssignKeysToGuardrail404), + "500": decodeError("BulkAssignKeysToGuardrail500", BulkAssignKeysToGuardrail500), + orElse: unexpectedStatus + })) + ), + "bulkUnassignKeysFromGuardrail": (id, options) => + HttpClientRequest.post(`/guardrails/${id}/assignments/keys/remove`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options.params?.["X-OpenRouter-Categories"] ?? undefined + }), + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(BulkUnassignKeysFromGuardrail200), + "400": decodeError("BulkUnassignKeysFromGuardrail400", BulkUnassignKeysFromGuardrail400), + "401": decodeError("BulkUnassignKeysFromGuardrail401", BulkUnassignKeysFromGuardrail401), + "404": decodeError("BulkUnassignKeysFromGuardrail404", BulkUnassignKeysFromGuardrail404), + "500": decodeError("BulkUnassignKeysFromGuardrail500", BulkUnassignKeysFromGuardrail500), + orElse: unexpectedStatus + })) + ), + "listGuardrailMemberAssignments": (id, options) => + HttpClientRequest.get(`/guardrails/${id}/assignments/members`).pipe( + HttpClientRequest.setUrlParams({ + "offset": options?.params?.["offset"] as any, + "limit": options?.params?.["limit"] as any + }), + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ListGuardrailMemberAssignments200), + "401": decodeError("ListGuardrailMemberAssignments401", ListGuardrailMemberAssignments401), + "404": decodeError("ListGuardrailMemberAssignments404", ListGuardrailMemberAssignments404), + "500": decodeError("ListGuardrailMemberAssignments500", ListGuardrailMemberAssignments500), + orElse: unexpectedStatus + })) + ), + "bulkAssignMembersToGuardrail": (id, options) => + HttpClientRequest.post(`/guardrails/${id}/assignments/members`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options.params?.["X-OpenRouter-Categories"] ?? undefined + }), + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(BulkAssignMembersToGuardrail200), + "400": decodeError("BulkAssignMembersToGuardrail400", BulkAssignMembersToGuardrail400), + "401": decodeError("BulkAssignMembersToGuardrail401", BulkAssignMembersToGuardrail401), + "404": decodeError("BulkAssignMembersToGuardrail404", BulkAssignMembersToGuardrail404), + "500": decodeError("BulkAssignMembersToGuardrail500", BulkAssignMembersToGuardrail500), + orElse: unexpectedStatus + })) + ), + "bulkUnassignMembersFromGuardrail": (id, options) => + HttpClientRequest.post(`/guardrails/${id}/assignments/members/remove`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options.params?.["X-OpenRouter-Categories"] ?? undefined + }), + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(BulkUnassignMembersFromGuardrail200), + "400": decodeError("BulkUnassignMembersFromGuardrail400", BulkUnassignMembersFromGuardrail400), + "401": decodeError("BulkUnassignMembersFromGuardrail401", BulkUnassignMembersFromGuardrail401), + "404": decodeError("BulkUnassignMembersFromGuardrail404", BulkUnassignMembersFromGuardrail404), + "500": decodeError("BulkUnassignMembersFromGuardrail500", BulkUnassignMembersFromGuardrail500), + orElse: unexpectedStatus + })) + ), + "listKeyAssignments": (options) => + HttpClientRequest.get(`/guardrails/assignments/keys`).pipe( + HttpClientRequest.setUrlParams({ + "offset": options?.params?.["offset"] as any, + "limit": options?.params?.["limit"] as any + }), + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ListKeyAssignments200), + "401": decodeError("ListKeyAssignments401", ListKeyAssignments401), + "500": decodeError("ListKeyAssignments500", ListKeyAssignments500), + orElse: unexpectedStatus + })) + ), + "listMemberAssignments": (options) => + HttpClientRequest.get(`/guardrails/assignments/members`).pipe( + HttpClientRequest.setUrlParams({ + "offset": options?.params?.["offset"] as any, + "limit": options?.params?.["limit"] as any + }), + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ListMemberAssignments200), + "401": decodeError("ListMemberAssignments401", ListMemberAssignments401), + "500": decodeError("ListMemberAssignments500", ListMemberAssignments500), + orElse: unexpectedStatus + })) + ), + "createImages": (options) => + HttpClientRequest.post(`/images`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options.params?.["X-OpenRouter-Categories"] ?? undefined + }), + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(CreateImages200), + "400": decodeError("CreateImages400", CreateImages400), + "401": decodeError("CreateImages401", CreateImages401), + "402": decodeError("CreateImages402", CreateImages402), + "403": decodeError("CreateImages403", CreateImages403), + "404": decodeError("CreateImages404", CreateImages404), + "413": decodeError("CreateImages413", CreateImages413), + "429": decodeError("CreateImages429", CreateImages429), + "500": decodeError("CreateImages500", CreateImages500), + "502": decodeError("CreateImages502", CreateImages502), + "524": decodeError("CreateImages524", CreateImages524), + "529": decodeError("CreateImages529", CreateImages529), + orElse: unexpectedStatus + })) + ), + "createImagesSse": (options) => + HttpClientRequest.post(`/images`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options.params?.["X-OpenRouter-Categories"] ?? undefined + }), + HttpClientRequest.bodyJsonUnsafe(options.payload), + sseRequest(CreateImages200Sse) + ), + "listImageModels": (options) => + HttpClientRequest.get(`/images/models`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ListImageModels200), + "500": decodeError("ListImageModels500", ListImageModels500), + orElse: unexpectedStatus + })) + ), + "listImageModelEndpoints": (author, slug, options) => + HttpClientRequest.get(`/images/models/${author}/${slug}/endpoints`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ListImageModelEndpoints200), + "404": decodeError("ListImageModelEndpoints404", ListImageModelEndpoints404), + "500": decodeError("ListImageModelEndpoints500", ListImageModelEndpoints500), + orElse: unexpectedStatus + })) + ), + "getCurrentKey": (options) => + HttpClientRequest.get(`/key`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(GetCurrentKey200), + "401": decodeError("GetCurrentKey401", GetCurrentKey401), + "500": decodeError("GetCurrentKey500", GetCurrentKey500), + orElse: unexpectedStatus + })) + ), + "list": (options) => + HttpClientRequest.get(`/keys`).pipe( + HttpClientRequest.setUrlParams({ + "include_disabled": options?.params?.["include_disabled"] as any, + "offset": options?.params?.["offset"] as any, + "workspace_id": options?.params?.["workspace_id"] as any + }), + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(List200), + "400": decodeError("List400", List400), + "401": decodeError("List401", List401), + "429": decodeError("List429", List429), + "500": decodeError("List500", List500), + orElse: unexpectedStatus + })) + ), + "createKeys": (options) => + HttpClientRequest.post(`/keys`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options.params?.["X-OpenRouter-Categories"] ?? undefined + }), + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(CreateKeys201), + "400": decodeError("CreateKeys400", CreateKeys400), + "401": decodeError("CreateKeys401", CreateKeys401), + "403": decodeError("CreateKeys403", CreateKeys403), + "429": decodeError("CreateKeys429", CreateKeys429), + "500": decodeError("CreateKeys500", CreateKeys500), + orElse: unexpectedStatus + })) + ), + "getKey": (hash, options) => + HttpClientRequest.get(`/keys/${hash}`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(GetKey200), + "401": decodeError("GetKey401", GetKey401), + "404": decodeError("GetKey404", GetKey404), + "429": decodeError("GetKey429", GetKey429), + "500": decodeError("GetKey500", GetKey500), + orElse: unexpectedStatus + })) + ), + "deleteKeys": (hash, options) => + HttpClientRequest.delete(`/keys/${hash}`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(DeleteKeys200), + "401": decodeError("DeleteKeys401", DeleteKeys401), + "404": decodeError("DeleteKeys404", DeleteKeys404), + "429": decodeError("DeleteKeys429", DeleteKeys429), + "500": decodeError("DeleteKeys500", DeleteKeys500), + orElse: unexpectedStatus + })) + ), + "updateKeys": (hash, options) => + HttpClientRequest.patch(`/keys/${hash}`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options.params?.["X-OpenRouter-Categories"] ?? undefined + }), + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(UpdateKeys200), + "400": decodeError("UpdateKeys400", UpdateKeys400), + "401": decodeError("UpdateKeys401", UpdateKeys401), + "404": decodeError("UpdateKeys404", UpdateKeys404), + "429": decodeError("UpdateKeys429", UpdateKeys429), + "500": decodeError("UpdateKeys500", UpdateKeys500), + orElse: unexpectedStatus + })) + ), + "createMessages": (options) => + HttpClientRequest.post(`/messages`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options.params?.["X-OpenRouter-Categories"] ?? undefined, + "X-OpenRouter-Metadata": options.params?.["X-OpenRouter-Metadata"] ?? undefined + }), + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(CreateMessages200), + "400": decodeError("CreateMessages400", CreateMessages400), + "401": decodeError("CreateMessages401", CreateMessages401), + "403": decodeError("CreateMessages403", CreateMessages403), + "404": decodeError("CreateMessages404", CreateMessages404), + "429": decodeError("CreateMessages429", CreateMessages429), + "500": decodeError("CreateMessages500", CreateMessages500), + "503": decodeError("CreateMessages503", CreateMessages503), + "529": decodeError("CreateMessages529", CreateMessages529), + orElse: unexpectedStatus + })) + ), + "createMessagesSse": (options) => + HttpClientRequest.post(`/messages`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options.params?.["X-OpenRouter-Categories"] ?? undefined, + "X-OpenRouter-Metadata": options.params?.["X-OpenRouter-Metadata"] ?? undefined + }), + HttpClientRequest.bodyJsonUnsafe(options.payload), + sseRequest(CreateMessages200Sse) + ), + "getModel": (author, slug, options) => + HttpClientRequest.get(`/model/${author}/${slug}`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(GetModel200), + "403": decodeError("GetModel403", GetModel403), + "404": decodeError("GetModel404", GetModel404), + "500": decodeError("GetModel500", GetModel500), + orElse: unexpectedStatus + })) + ), + "getModels": (options) => + HttpClientRequest.get(`/models`).pipe( + HttpClientRequest.setUrlParams({ + "offset": options?.params?.["offset"] as any, + "limit": options?.params?.["limit"] as any, + "category": options?.params?.["category"] as any, + "supported_parameters": options?.params?.["supported_parameters"] as any, + "output_modalities": options?.params?.["output_modalities"] as any, + "sort": options?.params?.["sort"] as any, + "q": options?.params?.["q"] as any, + "input_modalities": options?.params?.["input_modalities"] as any, + "context": options?.params?.["context"] as any, + "min_price": options?.params?.["min_price"] as any, + "max_price": options?.params?.["max_price"] as any, + "arch": options?.params?.["arch"] as any, + "model_authors": options?.params?.["model_authors"] as any, + "providers": options?.params?.["providers"] as any, + "distillable": options?.params?.["distillable"] as any, + "zdr": options?.params?.["zdr"] as any, + "region": options?.params?.["region"] as any, + "min_output_price": options?.params?.["min_output_price"] as any, + "max_output_price": options?.params?.["max_output_price"] as any, + "min_age_days": options?.params?.["min_age_days"] as any, + "max_age_days": options?.params?.["max_age_days"] as any, + "min_intelligence_index": options?.params?.["min_intelligence_index"] as any, + "max_intelligence_index": options?.params?.["max_intelligence_index"] as any, + "min_coding_index": options?.params?.["min_coding_index"] as any, + "max_coding_index": options?.params?.["max_coding_index"] as any, + "min_agentic_index": options?.params?.["min_agentic_index"] as any, + "max_agentic_index": options?.params?.["max_agentic_index"] as any, + "min_tool_success_rate": options?.params?.["min_tool_success_rate"] as any, + "max_tool_success_rate": options?.params?.["max_tool_success_rate"] as any + }), + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(GetModels200), + "400": decodeError("GetModels400", GetModels400), + "403": decodeError("GetModels403", GetModels403), + "500": decodeError("GetModels500", GetModels500), + orElse: unexpectedStatus + })) + ), + "listEndpoints": (author, slug, options) => + HttpClientRequest.get(`/models/${author}/${slug}/endpoints`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ListEndpoints200), + "403": decodeError("ListEndpoints403", ListEndpoints403), + "404": decodeError("ListEndpoints404", ListEndpoints404), + "500": decodeError("ListEndpoints500", ListEndpoints500), + orElse: unexpectedStatus + })) + ), + "listModelsCount": (options) => + HttpClientRequest.get(`/models/count`).pipe( + HttpClientRequest.setUrlParams({ "output_modalities": options?.params?.["output_modalities"] as any }), + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ListModelsCount200), + "400": decodeError("ListModelsCount400", ListModelsCount400), + "403": decodeError("ListModelsCount403", ListModelsCount403), + "500": decodeError("ListModelsCount500", ListModelsCount500), + orElse: unexpectedStatus + })) + ), + "listModelsUser": (options) => + HttpClientRequest.get(`/models/user`).pipe( + HttpClientRequest.setUrlParams({ + "offset": options?.params?.["offset"] as any, + "limit": options?.params?.["limit"] as any + }), + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ListModelsUser200), + "401": decodeError("ListModelsUser401", ListModelsUser401), + "403": decodeError("ListModelsUser403", ListModelsUser403), + "404": decodeError("ListModelsUser404", ListModelsUser404), + "500": decodeError("ListModelsUser500", ListModelsUser500), + orElse: unexpectedStatus + })) + ), + "listObservabilityDestinations": (options) => + HttpClientRequest.get(`/observability/destinations`).pipe( + HttpClientRequest.setUrlParams({ + "offset": options?.params?.["offset"] as any, + "limit": options?.params?.["limit"] as any, + "workspace_id": options?.params?.["workspace_id"] as any + }), + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ListObservabilityDestinations200), + "400": decodeError("ListObservabilityDestinations400", ListObservabilityDestinations400), + "401": decodeError("ListObservabilityDestinations401", ListObservabilityDestinations401), + "500": decodeError("ListObservabilityDestinations500", ListObservabilityDestinations500), + orElse: unexpectedStatus + })) + ), + "createObservabilityDestination": (options) => + HttpClientRequest.post(`/observability/destinations`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options.params?.["X-OpenRouter-Categories"] ?? undefined + }), + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(CreateObservabilityDestination201), + "400": decodeError("CreateObservabilityDestination400", CreateObservabilityDestination400), + "401": decodeError("CreateObservabilityDestination401", CreateObservabilityDestination401), + "403": decodeError("CreateObservabilityDestination403", CreateObservabilityDestination403), + "409": decodeError("CreateObservabilityDestination409", CreateObservabilityDestination409), + "500": decodeError("CreateObservabilityDestination500", CreateObservabilityDestination500), + orElse: unexpectedStatus + })) + ), + "getObservabilityDestination": (id, options) => + HttpClientRequest.get(`/observability/destinations/${id}`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(GetObservabilityDestination200), + "401": decodeError("GetObservabilityDestination401", GetObservabilityDestination401), + "404": decodeError("GetObservabilityDestination404", GetObservabilityDestination404), + "500": decodeError("GetObservabilityDestination500", GetObservabilityDestination500), + orElse: unexpectedStatus + })) + ), + "deleteObservabilityDestination": (id, options) => + HttpClientRequest.delete(`/observability/destinations/${id}`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(DeleteObservabilityDestination200), + "401": decodeError("DeleteObservabilityDestination401", DeleteObservabilityDestination401), + "404": decodeError("DeleteObservabilityDestination404", DeleteObservabilityDestination404), + "500": decodeError("DeleteObservabilityDestination500", DeleteObservabilityDestination500), + orElse: unexpectedStatus + })) + ), + "updateObservabilityDestination": (id, options) => + HttpClientRequest.patch(`/observability/destinations/${id}`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options.params?.["X-OpenRouter-Categories"] ?? undefined + }), + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(UpdateObservabilityDestination200), + "400": decodeError("UpdateObservabilityDestination400", UpdateObservabilityDestination400), + "401": decodeError("UpdateObservabilityDestination401", UpdateObservabilityDestination401), + "404": decodeError("UpdateObservabilityDestination404", UpdateObservabilityDestination404), + "409": decodeError("UpdateObservabilityDestination409", UpdateObservabilityDestination409), + "500": decodeError("UpdateObservabilityDestination500", UpdateObservabilityDestination500), + orElse: unexpectedStatus + })) + ), + "listOrganizationMembers": (options) => + HttpClientRequest.get(`/organization/members`).pipe( + HttpClientRequest.setUrlParams({ + "offset": options?.params?.["offset"] as any, + "limit": options?.params?.["limit"] as any + }), + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ListOrganizationMembers200), + "401": decodeError("ListOrganizationMembers401", ListOrganizationMembers401), + "404": decodeError("ListOrganizationMembers404", ListOrganizationMembers404), + "500": decodeError("ListOrganizationMembers500", ListOrganizationMembers500), + orElse: unexpectedStatus + })) + ), + "listPresets": (options) => + HttpClientRequest.get(`/presets`).pipe( + HttpClientRequest.setUrlParams({ + "offset": options?.params?.["offset"] as any, + "limit": options?.params?.["limit"] as any + }), + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ListPresets200), + "400": decodeError("ListPresets400", ListPresets400), + "401": decodeError("ListPresets401", ListPresets401), + "500": decodeError("ListPresets500", ListPresets500), + orElse: unexpectedStatus + })) + ), + "getPreset": (slug, options) => + HttpClientRequest.get(`/presets/${slug}`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(GetPreset200), + "400": decodeError("GetPreset400", GetPreset400), + "401": decodeError("GetPreset401", GetPreset401), + "404": decodeError("GetPreset404", GetPreset404), + "500": decodeError("GetPreset500", GetPreset500), + orElse: unexpectedStatus + })) + ), + "createPresetsChatCompletions": (slug, options) => + HttpClientRequest.post(`/presets/${slug}/chat/completions`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options.params?.["X-OpenRouter-Categories"] ?? undefined + }), + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(CreatePresetsChatCompletions200), + "400": decodeError("CreatePresetsChatCompletions400", CreatePresetsChatCompletions400), + "401": decodeError("CreatePresetsChatCompletions401", CreatePresetsChatCompletions401), + "403": decodeError("CreatePresetsChatCompletions403", CreatePresetsChatCompletions403), + "404": decodeError("CreatePresetsChatCompletions404", CreatePresetsChatCompletions404), + "409": decodeError("CreatePresetsChatCompletions409", CreatePresetsChatCompletions409), + "500": decodeError("CreatePresetsChatCompletions500", CreatePresetsChatCompletions500), + orElse: unexpectedStatus + })) + ), + "createPresetsMessages": (slug, options) => + HttpClientRequest.post(`/presets/${slug}/messages`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options.params?.["X-OpenRouter-Categories"] ?? undefined + }), + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(CreatePresetsMessages200), + "400": decodeError("CreatePresetsMessages400", CreatePresetsMessages400), + "401": decodeError("CreatePresetsMessages401", CreatePresetsMessages401), + "403": decodeError("CreatePresetsMessages403", CreatePresetsMessages403), + "404": decodeError("CreatePresetsMessages404", CreatePresetsMessages404), + "409": decodeError("CreatePresetsMessages409", CreatePresetsMessages409), + "500": decodeError("CreatePresetsMessages500", CreatePresetsMessages500), + orElse: unexpectedStatus + })) + ), + "createPresetsResponses": (slug, options) => + HttpClientRequest.post(`/presets/${slug}/responses`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options.params?.["X-OpenRouter-Categories"] ?? undefined + }), + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(CreatePresetsResponses200), + "400": decodeError("CreatePresetsResponses400", CreatePresetsResponses400), + "401": decodeError("CreatePresetsResponses401", CreatePresetsResponses401), + "403": decodeError("CreatePresetsResponses403", CreatePresetsResponses403), + "404": decodeError("CreatePresetsResponses404", CreatePresetsResponses404), + "409": decodeError("CreatePresetsResponses409", CreatePresetsResponses409), + "500": decodeError("CreatePresetsResponses500", CreatePresetsResponses500), + orElse: unexpectedStatus + })) + ), + "listPresetVersions": (slug, options) => + HttpClientRequest.get(`/presets/${slug}/versions`).pipe( + HttpClientRequest.setUrlParams({ + "offset": options?.params?.["offset"] as any, + "limit": options?.params?.["limit"] as any + }), + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ListPresetVersions200), + "400": decodeError("ListPresetVersions400", ListPresetVersions400), + "401": decodeError("ListPresetVersions401", ListPresetVersions401), + "404": decodeError("ListPresetVersions404", ListPresetVersions404), + "500": decodeError("ListPresetVersions500", ListPresetVersions500), + orElse: unexpectedStatus + })) + ), + "getPresetVersion": (slug, version, options) => + HttpClientRequest.get(`/presets/${slug}/versions/${version}`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(GetPresetVersion200), + "400": decodeError("GetPresetVersion400", GetPresetVersion400), + "401": decodeError("GetPresetVersion401", GetPresetVersion401), + "404": decodeError("GetPresetVersion404", GetPresetVersion404), + "500": decodeError("GetPresetVersion500", GetPresetVersion500), + orElse: unexpectedStatus + })) + ), + "listProviders": (options) => + HttpClientRequest.get(`/providers`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ListProviders200), + "500": decodeError("ListProviders500", ListProviders500), + orElse: unexpectedStatus + })) + ), + "createRerank": (options) => + HttpClientRequest.post(`/rerank`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options.params?.["X-OpenRouter-Categories"] ?? undefined + }), + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(CreateRerank200), + "400": decodeError("CreateRerank400", CreateRerank400), + "401": decodeError("CreateRerank401", CreateRerank401), + "402": decodeError("CreateRerank402", CreateRerank402), + "404": decodeError("CreateRerank404", CreateRerank404), + "429": decodeError("CreateRerank429", CreateRerank429), + "500": decodeError("CreateRerank500", CreateRerank500), + "502": decodeError("CreateRerank502", CreateRerank502), + "503": decodeError("CreateRerank503", CreateRerank503), + "524": decodeError("CreateRerank524", CreateRerank524), + "529": decodeError("CreateRerank529", CreateRerank529), + orElse: unexpectedStatus + })) + ), + "createRerankSse": (options) => + HttpClientRequest.post(`/rerank`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options.params?.["X-OpenRouter-Categories"] ?? undefined + }), + HttpClientRequest.bodyJsonUnsafe(options.payload), + sseRequest(CreateRerank200Sse) + ), + "createResponses": (options) => + HttpClientRequest.post(`/responses`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options.params?.["X-OpenRouter-Categories"] ?? undefined, + "X-OpenRouter-Metadata": options.params?.["X-OpenRouter-Metadata"] ?? undefined + }), + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(CreateResponses200), + "400": decodeError("CreateResponses400", CreateResponses400), + "401": decodeError("CreateResponses401", CreateResponses401), + "402": decodeError("CreateResponses402", CreateResponses402), + "403": decodeError("CreateResponses403", CreateResponses403), + "404": decodeError("CreateResponses404", CreateResponses404), + "408": decodeError("CreateResponses408", CreateResponses408), + "413": decodeError("CreateResponses413", CreateResponses413), + "422": decodeError("CreateResponses422", CreateResponses422), + "429": decodeError("CreateResponses429", CreateResponses429), + "500": decodeError("CreateResponses500", CreateResponses500), + "502": decodeError("CreateResponses502", CreateResponses502), + "503": decodeError("CreateResponses503", CreateResponses503), + "524": decodeError("CreateResponses524", CreateResponses524), + "529": decodeError("CreateResponses529", CreateResponses529), + orElse: unexpectedStatus + })) + ), + "createResponsesSse": (options) => + HttpClientRequest.post(`/responses`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options.params?.["X-OpenRouter-Categories"] ?? undefined, + "X-OpenRouter-Metadata": options.params?.["X-OpenRouter-Metadata"] ?? undefined + }), + HttpClientRequest.bodyJsonUnsafe(options.payload), + sseRequest(CreateResponses200Sse) + ), + "createVideos": (options) => + HttpClientRequest.post(`/videos`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options.params?.["X-OpenRouter-Categories"] ?? undefined + }), + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(CreateVideos202), + "400": decodeError("CreateVideos400", CreateVideos400), + "401": decodeError("CreateVideos401", CreateVideos401), + "402": decodeError("CreateVideos402", CreateVideos402), + "404": decodeError("CreateVideos404", CreateVideos404), + "429": decodeError("CreateVideos429", CreateVideos429), + "500": decodeError("CreateVideos500", CreateVideos500), + orElse: unexpectedStatus + })) + ), + "getVideos": (jobId, options) => + HttpClientRequest.get(`/videos/${jobId}`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(GetVideos200), + "401": decodeError("GetVideos401", GetVideos401), + "404": decodeError("GetVideos404", GetVideos404), + "500": decodeError("GetVideos500", GetVideos500), + orElse: unexpectedStatus + })) + ), + "listVideosContent": (jobId, options) => + HttpClientRequest.get(`/videos/${jobId}/content`).pipe( + HttpClientRequest.setUrlParams({ "index": options?.params?.["index"] as any }), + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "400": decodeError("ListVideosContent400", ListVideosContent400), + "401": decodeError("ListVideosContent401", ListVideosContent401), + "404": decodeError("ListVideosContent404", ListVideosContent404), + "500": decodeError("ListVideosContent500", ListVideosContent500), + "502": decodeError("ListVideosContent502", ListVideosContent502), + orElse: unexpectedStatus + })) + ), + "listVideosModels": (options) => + HttpClientRequest.get(`/videos/models`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ListVideosModels200), + "400": decodeError("ListVideosModels400", ListVideosModels400), + "500": decodeError("ListVideosModels500", ListVideosModels500), + orElse: unexpectedStatus + })) + ), + "listWorkspaces": (options) => + HttpClientRequest.get(`/workspaces`).pipe( + HttpClientRequest.setUrlParams({ + "offset": options?.params?.["offset"] as any, + "limit": options?.params?.["limit"] as any + }), + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ListWorkspaces200), + "401": decodeError("ListWorkspaces401", ListWorkspaces401), + "500": decodeError("ListWorkspaces500", ListWorkspaces500), + orElse: unexpectedStatus + })) + ), + "createWorkspace": (options) => + HttpClientRequest.post(`/workspaces`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options.params?.["X-OpenRouter-Categories"] ?? undefined + }), + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(CreateWorkspace201), + "400": decodeError("CreateWorkspace400", CreateWorkspace400), + "401": decodeError("CreateWorkspace401", CreateWorkspace401), + "403": decodeError("CreateWorkspace403", CreateWorkspace403), + "500": decodeError("CreateWorkspace500", CreateWorkspace500), + orElse: unexpectedStatus + })) + ), + "getWorkspace": (id, options) => + HttpClientRequest.get(`/workspaces/${id}`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(GetWorkspace200), + "401": decodeError("GetWorkspace401", GetWorkspace401), + "404": decodeError("GetWorkspace404", GetWorkspace404), + "500": decodeError("GetWorkspace500", GetWorkspace500), + orElse: unexpectedStatus + })) + ), + "deleteWorkspace": (id, options) => + HttpClientRequest.delete(`/workspaces/${id}`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(DeleteWorkspace200), + "400": decodeError("DeleteWorkspace400", DeleteWorkspace400), + "401": decodeError("DeleteWorkspace401", DeleteWorkspace401), + "403": decodeError("DeleteWorkspace403", DeleteWorkspace403), + "404": decodeError("DeleteWorkspace404", DeleteWorkspace404), + "500": decodeError("DeleteWorkspace500", DeleteWorkspace500), + orElse: unexpectedStatus + })) + ), + "updateWorkspace": (id, options) => + HttpClientRequest.patch(`/workspaces/${id}`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options.params?.["X-OpenRouter-Categories"] ?? undefined + }), + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(UpdateWorkspace200), + "400": decodeError("UpdateWorkspace400", UpdateWorkspace400), + "401": decodeError("UpdateWorkspace401", UpdateWorkspace401), + "403": decodeError("UpdateWorkspace403", UpdateWorkspace403), + "404": decodeError("UpdateWorkspace404", UpdateWorkspace404), + "500": decodeError("UpdateWorkspace500", UpdateWorkspace500), + orElse: unexpectedStatus + })) + ), + "listWorkspaceBudgets": (id, options) => + HttpClientRequest.get(`/workspaces/${id}/budgets`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ListWorkspaceBudgets200), + "401": decodeError("ListWorkspaceBudgets401", ListWorkspaceBudgets401), + "404": decodeError("ListWorkspaceBudgets404", ListWorkspaceBudgets404), + "500": decodeError("ListWorkspaceBudgets500", ListWorkspaceBudgets500), + orElse: unexpectedStatus + })) + ), + "upsertWorkspaceBudget": (id, interval, options) => + HttpClientRequest.put(`/workspaces/${id}/budgets/${interval}`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options.params?.["X-OpenRouter-Categories"] ?? undefined + }), + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(UpsertWorkspaceBudget200), + "400": decodeError("UpsertWorkspaceBudget400", UpsertWorkspaceBudget400), + "401": decodeError("UpsertWorkspaceBudget401", UpsertWorkspaceBudget401), + "404": decodeError("UpsertWorkspaceBudget404", UpsertWorkspaceBudget404), + "500": decodeError("UpsertWorkspaceBudget500", UpsertWorkspaceBudget500), + orElse: unexpectedStatus + })) + ), + "deleteWorkspaceBudget": (id, interval, options) => + HttpClientRequest.delete(`/workspaces/${id}/budgets/${interval}`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(DeleteWorkspaceBudget200), + "401": decodeError("DeleteWorkspaceBudget401", DeleteWorkspaceBudget401), + "404": decodeError("DeleteWorkspaceBudget404", DeleteWorkspaceBudget404), + "500": decodeError("DeleteWorkspaceBudget500", DeleteWorkspaceBudget500), + orElse: unexpectedStatus + })) + ), + "listWorkspaceMembers": (id, options) => + HttpClientRequest.get(`/workspaces/${id}/members`).pipe( + HttpClientRequest.setUrlParams({ + "offset": options?.params?.["offset"] as any, + "limit": options?.params?.["limit"] as any + }), + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ListWorkspaceMembers200), + "401": decodeError("ListWorkspaceMembers401", ListWorkspaceMembers401), + "403": decodeError("ListWorkspaceMembers403", ListWorkspaceMembers403), + "404": decodeError("ListWorkspaceMembers404", ListWorkspaceMembers404), + "500": decodeError("ListWorkspaceMembers500", ListWorkspaceMembers500), + orElse: unexpectedStatus + })) + ), + "bulkAddWorkspaceMembers": (id, options) => + HttpClientRequest.post(`/workspaces/${id}/members/add`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options.params?.["X-OpenRouter-Categories"] ?? undefined + }), + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(BulkAddWorkspaceMembers200), + "400": decodeError("BulkAddWorkspaceMembers400", BulkAddWorkspaceMembers400), + "401": decodeError("BulkAddWorkspaceMembers401", BulkAddWorkspaceMembers401), + "403": decodeError("BulkAddWorkspaceMembers403", BulkAddWorkspaceMembers403), + "404": decodeError("BulkAddWorkspaceMembers404", BulkAddWorkspaceMembers404), + "500": decodeError("BulkAddWorkspaceMembers500", BulkAddWorkspaceMembers500), + orElse: unexpectedStatus + })) + ), + "bulkRemoveWorkspaceMembers": (id, options) => + HttpClientRequest.post(`/workspaces/${id}/members/remove`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options.params?.["X-OpenRouter-Categories"] ?? undefined + }), + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(BulkRemoveWorkspaceMembers200), + "400": decodeError("BulkRemoveWorkspaceMembers400", BulkRemoveWorkspaceMembers400), + "401": decodeError("BulkRemoveWorkspaceMembers401", BulkRemoveWorkspaceMembers401), + "403": decodeError("BulkRemoveWorkspaceMembers403", BulkRemoveWorkspaceMembers403), + "404": decodeError("BulkRemoveWorkspaceMembers404", BulkRemoveWorkspaceMembers404), + "500": decodeError("BulkRemoveWorkspaceMembers500", BulkRemoveWorkspaceMembers500), + orElse: unexpectedStatus + })) + ) + } +} + +export interface OpenRouterClient { + readonly httpClient: HttpClient.HttpClient + /** + * Returns user activity data grouped by endpoint for the last 30 (completed) UTC days. [Management key](/docs/guides/overview/auth/management-api-keys) required. + */ + readonly "getUserActivity": ( + options: { + readonly params?: typeof GetUserActivityParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"GetUserActivity400", typeof GetUserActivity400.Type> + | OpenRouterClientError<"GetUserActivity401", typeof GetUserActivity401.Type> + | OpenRouterClientError<"GetUserActivity403", typeof GetUserActivity403.Type> + | OpenRouterClientError<"GetUserActivity404", typeof GetUserActivity404.Type> + | OpenRouterClientError<"GetUserActivity500", typeof GetUserActivity500.Type> + > + /** + * Returns the available metrics, dimensions, filter operators, and granularities for the analytics query endpoint. [Management key](/docs/guides/overview/auth/management-api-keys) required. + */ + readonly "getAnalyticsMeta": ( + options: { + readonly params?: typeof GetAnalyticsMetaParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"GetAnalyticsMeta401", typeof GetAnalyticsMeta401.Type> + | OpenRouterClientError<"GetAnalyticsMeta403", typeof GetAnalyticsMeta403.Type> + | OpenRouterClientError<"GetAnalyticsMeta500", typeof GetAnalyticsMeta500.Type> + > + /** + * Execute an analytics query with specified metrics, dimensions, filters, and time range. [Management key](/docs/guides/overview/auth/management-api-keys) required. + */ + readonly "queryAnalytics": ( + options: { + readonly params?: typeof QueryAnalyticsParams.Encoded | undefined + readonly payload: typeof QueryAnalyticsRequestJson.Encoded + readonly config?: Config | undefined + } + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"QueryAnalytics400", typeof QueryAnalytics400.Type> + | OpenRouterClientError<"QueryAnalytics401", typeof QueryAnalytics401.Type> + | OpenRouterClientError<"QueryAnalytics403", typeof QueryAnalytics403.Type> + | OpenRouterClientError<"QueryAnalytics408", typeof QueryAnalytics408.Type> + | OpenRouterClientError<"QueryAnalytics500", typeof QueryAnalytics500.Type> + > + /** + * Synthesizes audio from the input text. Returns a raw audio bytestream in the requested format (e.g. mp3, pcm, wav). + */ + readonly "createAudioSpeech": ( + options: { + readonly params?: typeof CreateAudioSpeechParams.Encoded | undefined + readonly payload: typeof CreateAudioSpeechRequestJson.Encoded + readonly config?: Config | undefined + } + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"CreateAudioSpeech400", typeof CreateAudioSpeech400.Type> + | OpenRouterClientError<"CreateAudioSpeech401", typeof CreateAudioSpeech401.Type> + | OpenRouterClientError<"CreateAudioSpeech402", typeof CreateAudioSpeech402.Type> + | OpenRouterClientError<"CreateAudioSpeech404", typeof CreateAudioSpeech404.Type> + | OpenRouterClientError<"CreateAudioSpeech429", typeof CreateAudioSpeech429.Type> + | OpenRouterClientError<"CreateAudioSpeech500", typeof CreateAudioSpeech500.Type> + | OpenRouterClientError<"CreateAudioSpeech502", typeof CreateAudioSpeech502.Type> + | OpenRouterClientError<"CreateAudioSpeech503", typeof CreateAudioSpeech503.Type> + | OpenRouterClientError<"CreateAudioSpeech524", typeof CreateAudioSpeech524.Type> + | OpenRouterClientError<"CreateAudioSpeech529", typeof CreateAudioSpeech529.Type> + > + /** + * Transcribes audio into text. Accepts base64-encoded audio input as JSON or an OpenAI-style multipart/form-data file upload, and returns the transcribed text. + */ + readonly "createAudioTranscriptions": ( + options: { + readonly params?: typeof CreateAudioTranscriptionsParams.Encoded | undefined + readonly payload: typeof CreateAudioTranscriptionsRequestFormData.Encoded + readonly config?: Config | undefined + } + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"CreateAudioTranscriptions400", typeof CreateAudioTranscriptions400.Type> + | OpenRouterClientError<"CreateAudioTranscriptions401", typeof CreateAudioTranscriptions401.Type> + | OpenRouterClientError<"CreateAudioTranscriptions402", typeof CreateAudioTranscriptions402.Type> + | OpenRouterClientError<"CreateAudioTranscriptions404", typeof CreateAudioTranscriptions404.Type> + | OpenRouterClientError<"CreateAudioTranscriptions429", typeof CreateAudioTranscriptions429.Type> + | OpenRouterClientError<"CreateAudioTranscriptions500", typeof CreateAudioTranscriptions500.Type> + | OpenRouterClientError<"CreateAudioTranscriptions502", typeof CreateAudioTranscriptions502.Type> + | OpenRouterClientError<"CreateAudioTranscriptions503", typeof CreateAudioTranscriptions503.Type> + | OpenRouterClientError<"CreateAudioTranscriptions524", typeof CreateAudioTranscriptions524.Type> + | OpenRouterClientError<"CreateAudioTranscriptions529", typeof CreateAudioTranscriptions529.Type> + > + /** + * Exchange an authorization code from the PKCE flow for a user-controlled API key + */ + readonly "exchangeAuthCodeForAPIKey": ( + options: { + readonly params?: typeof ExchangeAuthCodeForAPIKeyParams.Encoded | undefined + readonly payload: typeof ExchangeAuthCodeForAPIKeyRequestJson.Encoded + readonly config?: Config | undefined + } + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"ExchangeAuthCodeForAPIKey400", typeof ExchangeAuthCodeForAPIKey400.Type> + | OpenRouterClientError<"ExchangeAuthCodeForAPIKey403", typeof ExchangeAuthCodeForAPIKey403.Type> + | OpenRouterClientError<"ExchangeAuthCodeForAPIKey500", typeof ExchangeAuthCodeForAPIKey500.Type> + > + /** + * Create an authorization code for the PKCE flow to generate a user-controlled API key + */ + readonly "createAuthKeysCode": ( + options: { + readonly params?: typeof CreateAuthKeysCodeParams.Encoded | undefined + readonly payload: typeof CreateAuthKeysCodeRequestJson.Encoded + readonly config?: Config | undefined + } + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"CreateAuthKeysCode400", typeof CreateAuthKeysCode400.Type> + | OpenRouterClientError<"CreateAuthKeysCode401", typeof CreateAuthKeysCode401.Type> + | OpenRouterClientError<"CreateAuthKeysCode403", typeof CreateAuthKeysCode403.Type> + | OpenRouterClientError<"CreateAuthKeysCode409", typeof CreateAuthKeysCode409.Type> + | OpenRouterClientError<"CreateAuthKeysCode500", typeof CreateAuthKeysCode500.Type> + > + /** + * Unified benchmark endpoint that aggregates scores from multiple benchmark sources (Artificial Analysis, Design Arena). Filter by source to reproduce the exact shapes from the legacy per-source endpoints, or use task_type to find models suited for specific workloads. Authenticate with any valid OpenRouter API key. Rate-limited to 30 requests/minute per key and 500 requests/day per account. + */ + readonly "getBenchmarks": ( + options: + | { readonly params?: typeof GetBenchmarksParams.Encoded | undefined; readonly config?: Config | undefined } + | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"GetBenchmarks400", typeof GetBenchmarks400.Type> + | OpenRouterClientError<"GetBenchmarks401", typeof GetBenchmarks401.Type> + | OpenRouterClientError<"GetBenchmarks429", typeof GetBenchmarks429.Type> + | OpenRouterClientError<"GetBenchmarks500", typeof GetBenchmarks500.Type> + > + /** + * List the bring-your-own-key (BYOK) provider credentials for the authenticated entity's default workspace. Use the `workspace_id` query parameter to scope the result to a different workspace, or the `provider` query parameter to filter by upstream provider. [Management key](/docs/guides/overview/auth/management-api-keys) required. + */ + readonly "listBYOKKeys": ( + options: + | { readonly params?: typeof ListBYOKKeysParams.Encoded | undefined; readonly config?: Config | undefined } + | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"ListBYOKKeys400", typeof ListBYOKKeys400.Type> + | OpenRouterClientError<"ListBYOKKeys401", typeof ListBYOKKeys401.Type> + | OpenRouterClientError<"ListBYOKKeys500", typeof ListBYOKKeys500.Type> + > + /** + * Create a new bring-your-own-key (BYOK) provider credential. The raw key is encrypted at rest and never returned in API responses. Defaults to the authenticated entity's default workspace; use the `workspace_id` body field to scope to a different workspace. Treat the raw key as write-only; it is never returned after creation. [Management key](/docs/guides/overview/auth/management-api-keys) required. + */ + readonly "createBYOKKey": ( + options: { + readonly params?: typeof CreateBYOKKeyParams.Encoded | undefined + readonly payload: typeof CreateBYOKKeyRequestJson.Encoded + readonly config?: Config | undefined + } + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"CreateBYOKKey400", typeof CreateBYOKKey400.Type> + | OpenRouterClientError<"CreateBYOKKey401", typeof CreateBYOKKey401.Type> + | OpenRouterClientError<"CreateBYOKKey403", typeof CreateBYOKKey403.Type> + | OpenRouterClientError<"CreateBYOKKey500", typeof CreateBYOKKey500.Type> + > + /** + * Get a single bring-your-own-key (BYOK) provider credential by its `id`. [Management key](/docs/guides/overview/auth/management-api-keys) required. + */ + readonly "getBYOKKey": ( + id: string, + options: + | { readonly params?: typeof GetBYOKKeyParams.Encoded | undefined; readonly config?: Config | undefined } + | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"GetBYOKKey401", typeof GetBYOKKey401.Type> + | OpenRouterClientError<"GetBYOKKey404", typeof GetBYOKKey404.Type> + | OpenRouterClientError<"GetBYOKKey500", typeof GetBYOKKey500.Type> + > + /** + * Delete (soft-delete) a bring-your-own-key (BYOK) provider credential by its `id`. The encrypted key material is wiped and the record is marked as deleted. [Management key](/docs/guides/overview/auth/management-api-keys) required. + */ + readonly "deleteBYOKKey": ( + id: string, + options: + | { readonly params?: typeof DeleteBYOKKeyParams.Encoded | undefined; readonly config?: Config | undefined } + | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"DeleteBYOKKey401", typeof DeleteBYOKKey401.Type> + | OpenRouterClientError<"DeleteBYOKKey404", typeof DeleteBYOKKey404.Type> + | OpenRouterClientError<"DeleteBYOKKey500", typeof DeleteBYOKKey500.Type> + > + /** + * Update an existing bring-your-own-key (BYOK) provider credential by its `id`. Include the `key` field to rotate the raw provider API key in-place (the previous key material is overwritten). [Management key](/docs/guides/overview/auth/management-api-keys) required. + */ + readonly "updateBYOKKey": ( + id: string, + options: { + readonly params?: typeof UpdateBYOKKeyParams.Encoded | undefined + readonly payload: typeof UpdateBYOKKeyRequestJson.Encoded + readonly config?: Config | undefined + } + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"UpdateBYOKKey400", typeof UpdateBYOKKey400.Type> + | OpenRouterClientError<"UpdateBYOKKey401", typeof UpdateBYOKKey401.Type> + | OpenRouterClientError<"UpdateBYOKKey404", typeof UpdateBYOKKey404.Type> + | OpenRouterClientError<"UpdateBYOKKey500", typeof UpdateBYOKKey500.Type> + > + /** + * Sends a request for a model response for the given chat conversation. Supports both streaming and non-streaming modes. + */ + readonly "sendChatCompletionRequest": ( + options: { + readonly params?: typeof SendChatCompletionRequestParams.Encoded | undefined + readonly payload: typeof SendChatCompletionRequestRequestJson.Encoded + readonly config?: Config | undefined + } + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"SendChatCompletionRequest400", typeof SendChatCompletionRequest400.Type> + | OpenRouterClientError<"SendChatCompletionRequest401", typeof SendChatCompletionRequest401.Type> + | OpenRouterClientError<"SendChatCompletionRequest402", typeof SendChatCompletionRequest402.Type> + | OpenRouterClientError<"SendChatCompletionRequest403", typeof SendChatCompletionRequest403.Type> + | OpenRouterClientError<"SendChatCompletionRequest404", typeof SendChatCompletionRequest404.Type> + | OpenRouterClientError<"SendChatCompletionRequest408", typeof SendChatCompletionRequest408.Type> + | OpenRouterClientError<"SendChatCompletionRequest413", typeof SendChatCompletionRequest413.Type> + | OpenRouterClientError<"SendChatCompletionRequest422", typeof SendChatCompletionRequest422.Type> + | OpenRouterClientError<"SendChatCompletionRequest429", typeof SendChatCompletionRequest429.Type> + | OpenRouterClientError<"SendChatCompletionRequest500", typeof SendChatCompletionRequest500.Type> + | OpenRouterClientError<"SendChatCompletionRequest502", typeof SendChatCompletionRequest502.Type> + | OpenRouterClientError<"SendChatCompletionRequest503", typeof SendChatCompletionRequest503.Type> + | OpenRouterClientError<"SendChatCompletionRequest524", typeof SendChatCompletionRequest524.Type> + | OpenRouterClientError<"SendChatCompletionRequest529", typeof SendChatCompletionRequest529.Type> + > + /** + * Sends a request for a model response for the given chat conversation. Supports both streaming and non-streaming modes. + */ + readonly "sendChatCompletionRequestSse": ( + options: { + readonly params?: typeof SendChatCompletionRequestParams.Encoded | undefined + readonly payload: typeof SendChatCompletionRequestRequestJson.Encoded + } + ) => Stream.Stream< + { + readonly event: string + readonly id: string | undefined + readonly data: typeof SendChatCompletionRequest200Sse.Type + }, + HttpClientError.HttpClientError | SchemaError | Sse.Retry | Sse.SseError, + typeof SendChatCompletionRequest200Sse.DecodingServices + > + /** + * Returns the market-share breakdown of OpenRouter traffic by task classification + * (e.g. code generation, web search, summarization) over a trailing time window. + * + * Each classification reports its share of classified sampled requests (`usage_share`) + * and classified sampled token volume (`token_share`) as fractions between 0 and 1. + * The unclassified `other` bucket is excluded. Absolute volumes are not exposed + * because the underlying data is sampled. + * + * Each classification also includes a `models` array listing the top models by + * request volume within that classification, with their within-tag usage and token shares. + * + * Classifications are grouped into macro-categories (Code, Data, Agent, General) + * with aggregate shares provided for each. + * + * Authenticate with any valid OpenRouter API key (same key used for inference). + * Rate-limited to 30 requests/minute per key and 500 requests/day per account. + * + * When republishing or quoting this data, cite as: + * "Source: OpenRouter (openrouter.ai/rankings), as of {as_of}." + */ + readonly "getTaskClassifications": ( + options: { + readonly params?: typeof GetTaskClassificationsParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"GetTaskClassifications400", typeof GetTaskClassifications400.Type> + | OpenRouterClientError<"GetTaskClassifications401", typeof GetTaskClassifications401.Type> + | OpenRouterClientError<"GetTaskClassifications429", typeof GetTaskClassifications429.Type> + | OpenRouterClientError<"GetTaskClassifications500", typeof GetTaskClassifications500.Type> + > + /** + * Get total credits purchased and used for the authenticated user. [Management key](/docs/guides/overview/auth/management-api-keys) required. + */ + readonly "getCredits": ( + options: + | { readonly params?: typeof GetCreditsParams.Encoded | undefined; readonly config?: Config | undefined } + | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"GetCredits401", typeof GetCredits401.Type> + | OpenRouterClientError<"GetCredits403", typeof GetCredits403.Type> + | OpenRouterClientError<"GetCredits500", typeof GetCredits500.Type> + > + /** + * Deprecated. The Coinbase APIs used by this endpoint have been deprecated, so Coinbase Commerce charges have been removed. Use the web credits purchase flow instead. + */ + readonly "createCoinbaseCharge": ( + options: { + readonly params?: typeof CreateCoinbaseChargeParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"CreateCoinbaseCharge410", typeof CreateCoinbaseCharge410.Type> + > + /** + * Returns the top public apps on OpenRouter ranked by token usage inside the requested + * date window, matching the public apps marketplace on openrouter.ai/apps. Token totals + * are `prompt_tokens + completion_tokens`; hidden and private apps are excluded and + * traffic from related app aliases is merged into the canonical visible app. + * + * `sort=popular` (default) ranks by total token volume inside the window. + * `sort=trending` ranks by absolute excess token growth: window volume minus the average + * volume of the three equal-length periods immediately preceding the window. Apps with + * no excess growth are omitted, so `trending` may return fewer than `limit` rows. + * + * Filter with `category` (marketplace category group, e.g. `coding`) or `subcategory` + * (e.g. `cli-agent`). Ranks are re-numbered 1..N after filtering. Page with `offset` — + * `rank` stays absolute, so the first row of `offset=50` is `rank: 51`. + * + * Authenticate with any valid OpenRouter API key (same key used for inference). + * Rate-limited to 30 requests/minute per key and 500 requests/day per account. + * + * When republishing or quoting this dataset, OpenRouter must be cited as: + * "Source: OpenRouter (openrouter.ai/apps), as of {as_of}." + * + * Token counts come from each upstream provider's own tokenizer, so a token attributed + * to one app is not directly comparable to a token attributed to another app whose + * traffic flows through a different provider. + */ + readonly "getAppRankings": ( + options: + | { readonly params?: typeof GetAppRankingsParams.Encoded | undefined; readonly config?: Config | undefined } + | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"GetAppRankings400", typeof GetAppRankings400.Type> + | OpenRouterClientError<"GetAppRankings401", typeof GetAppRankings401.Type> + | OpenRouterClientError<"GetAppRankings429", typeof GetAppRankings429.Type> + | OpenRouterClientError<"GetAppRankings500", typeof GetAppRankings500.Type> + > + /** + * Returns the top 50 public models per day by total token usage on OpenRouter, plus a + * single aggregated `other` row per day that sums every model outside that top 50. + * Token totals are `prompt_tokens + completion_tokens`, matching the public rankings + * chart on openrouter.ai/rankings. + * + * Each row is a distinct `(date, model_permaslug)` pair. The `other` row uses the + * reserved permaslug `other` and is always returned last within its date, so callers + * can compute `top-50 traffic / total daily traffic` without a second request. + * + * Optional filters slice the dataset. `period` (`day`/`week`/`month`) sets the time + * grain. `modality` and `context_bucket` narrow the exact dataset by output/input + * modality (or tool-calling activity) and request context length. `category` and + * `language_type` instead read a sampled, upsampled dataset whose `total_tokens` are + * weekly-grain estimates — they cannot be combined with each other or with the exact + * filters, and reject `period=day` with a 400. + * + * Authenticate with any valid OpenRouter API key (same key used for inference). + * Rate-limited to 30 requests/minute per key and 500 requests/day per account. + * + * When republishing or quoting this dataset, OpenRouter must be cited as: + * "Source: OpenRouter (openrouter.ai/rankings), as of {as_of}." + * + * Token counts come from each upstream provider's own tokenizer (Anthropic counts + * are as reported by Anthropic, OpenAI counts are as reported by OpenAI, etc.), so + * a token in one row is not directly comparable to a token in another row from a + * different provider. + */ + readonly "getRankingsDaily": ( + options: { + readonly params?: typeof GetRankingsDailyParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"GetRankingsDaily400", typeof GetRankingsDaily400.Type> + | OpenRouterClientError<"GetRankingsDaily401", typeof GetRankingsDaily401.Type> + | OpenRouterClientError<"GetRankingsDaily429", typeof GetRankingsDaily429.Type> + | OpenRouterClientError<"GetRankingsDaily500", typeof GetRankingsDaily500.Type> + > + /** + * Submits an embedding request to the embeddings router + */ + readonly "createEmbeddings": ( + options: { + readonly params?: typeof CreateEmbeddingsParams.Encoded | undefined + readonly payload: typeof CreateEmbeddingsRequestJson.Encoded + readonly config?: Config | undefined + } + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"CreateEmbeddings400", typeof CreateEmbeddings400.Type> + | OpenRouterClientError<"CreateEmbeddings401", typeof CreateEmbeddings401.Type> + | OpenRouterClientError<"CreateEmbeddings402", typeof CreateEmbeddings402.Type> + | OpenRouterClientError<"CreateEmbeddings404", typeof CreateEmbeddings404.Type> + | OpenRouterClientError<"CreateEmbeddings429", typeof CreateEmbeddings429.Type> + | OpenRouterClientError<"CreateEmbeddings500", typeof CreateEmbeddings500.Type> + | OpenRouterClientError<"CreateEmbeddings502", typeof CreateEmbeddings502.Type> + | OpenRouterClientError<"CreateEmbeddings503", typeof CreateEmbeddings503.Type> + | OpenRouterClientError<"CreateEmbeddings524", typeof CreateEmbeddings524.Type> + | OpenRouterClientError<"CreateEmbeddings529", typeof CreateEmbeddings529.Type> + > + /** + * Submits an embedding request to the embeddings router + */ + readonly "createEmbeddingsSse": ( + options: { + readonly params?: typeof CreateEmbeddingsParams.Encoded | undefined + readonly payload: typeof CreateEmbeddingsRequestJson.Encoded + } + ) => Stream.Stream< + { readonly event: string; readonly id: string | undefined; readonly data: typeof CreateEmbeddings200Sse.Type }, + HttpClientError.HttpClientError | SchemaError | Sse.Retry | Sse.SseError, + typeof CreateEmbeddings200Sse.DecodingServices + > + /** + * Returns a list of all available embeddings models and their properties + */ + readonly "listEmbeddingsModels": ( + options: { + readonly params?: typeof ListEmbeddingsModelsParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"ListEmbeddingsModels400", typeof ListEmbeddingsModels400.Type> + | OpenRouterClientError<"ListEmbeddingsModels500", typeof ListEmbeddingsModels500.Type> + > + /** + * Preview the impact of ZDR on the available endpoints + */ + readonly "listEndpointsZdr": ( + options: { + readonly params?: typeof ListEndpointsZdrParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"ListEndpointsZdr403", typeof ListEndpointsZdr403.Type> + | OpenRouterClientError<"ListEndpointsZdr500", typeof ListEndpointsZdr500.Type> + > + /** + * Lists files belonging to the workspace of the authenticating API key. + */ + readonly "listFiles": ( + options: + | { readonly params?: typeof ListFilesParams.Encoded | undefined; readonly config?: Config | undefined } + | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"ListFiles400", typeof ListFiles400.Type> + | OpenRouterClientError<"ListFiles401", typeof ListFiles401.Type> + | OpenRouterClientError<"ListFiles429", typeof ListFiles429.Type> + | OpenRouterClientError<"ListFiles500", typeof ListFiles500.Type> + > + /** + * Uploads a file to be referenced in future API calls. The file is stored under the workspace of the authenticating API key. Maximum file size: 100 MB. + */ + readonly "uploadFile": ( + options: { + readonly params?: typeof UploadFileParams.Encoded | undefined + readonly payload: typeof UploadFileRequestFormData.Encoded + readonly config?: Config | undefined + } + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"UploadFile400", typeof UploadFile400.Type> + | OpenRouterClientError<"UploadFile401", typeof UploadFile401.Type> + | OpenRouterClientError<"UploadFile403", typeof UploadFile403.Type> + | OpenRouterClientError<"UploadFile413", typeof UploadFile413.Type> + | OpenRouterClientError<"UploadFile429", typeof UploadFile429.Type> + | OpenRouterClientError<"UploadFile500", typeof UploadFile500.Type> + > + /** + * Retrieves metadata for a single file owned by the requesting workspace. + */ + readonly "getFileMetadata": ( + fileId: string, + options: { + readonly params?: typeof GetFileMetadataParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"GetFileMetadata401", typeof GetFileMetadata401.Type> + | OpenRouterClientError<"GetFileMetadata404", typeof GetFileMetadata404.Type> + | OpenRouterClientError<"GetFileMetadata429", typeof GetFileMetadata429.Type> + | OpenRouterClientError<"GetFileMetadata500", typeof GetFileMetadata500.Type> + > + /** + * Deletes a file owned by the requesting workspace. Deletion is irreversible. + */ + readonly "deleteFile": ( + fileId: string, + options: + | { readonly params?: typeof DeleteFileParams.Encoded | undefined; readonly config?: Config | undefined } + | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"DeleteFile401", typeof DeleteFile401.Type> + | OpenRouterClientError<"DeleteFile404", typeof DeleteFile404.Type> + | OpenRouterClientError<"DeleteFile429", typeof DeleteFile429.Type> + | OpenRouterClientError<"DeleteFile500", typeof DeleteFile500.Type> + > + /** + * Downloads the raw bytes of a file. Only files created server-side are downloadable; uploaded files return 400. + */ + readonly "downloadFileContent": ( + fileId: string, + options: { + readonly params?: typeof DownloadFileContentParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"DownloadFileContent400", typeof DownloadFileContent400.Type> + | OpenRouterClientError<"DownloadFileContent401", typeof DownloadFileContent401.Type> + | OpenRouterClientError<"DownloadFileContent404", typeof DownloadFileContent404.Type> + | OpenRouterClientError<"DownloadFileContent429", typeof DownloadFileContent429.Type> + | OpenRouterClientError<"DownloadFileContent500", typeof DownloadFileContent500.Type> + > + /** + * Downloads the raw bytes of a file. Only files created server-side are downloadable; uploaded files return 400. + */ + readonly "downloadFileContentStream": ( + fileId: string, + options: { readonly params?: typeof DownloadFileContentParams.Encoded | undefined } | undefined + ) => Stream.Stream + /** + * Get request & usage metadata for a generation + */ + readonly "getGeneration": ( + options: { readonly params: typeof GetGenerationParams.Encoded; readonly config?: Config | undefined } + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"GetGeneration401", typeof GetGeneration401.Type> + | OpenRouterClientError<"GetGeneration402", typeof GetGeneration402.Type> + | OpenRouterClientError<"GetGeneration404", typeof GetGeneration404.Type> + | OpenRouterClientError<"GetGeneration429", typeof GetGeneration429.Type> + | OpenRouterClientError<"GetGeneration500", typeof GetGeneration500.Type> + | OpenRouterClientError<"GetGeneration502", typeof GetGeneration502.Type> + | OpenRouterClientError<"GetGeneration524", typeof GetGeneration524.Type> + | OpenRouterClientError<"GetGeneration529", typeof GetGeneration529.Type> + > + /** + * Get stored prompt and completion content for a generation + */ + readonly "listGenerationContent": ( + options: { readonly params: typeof ListGenerationContentParams.Encoded; readonly config?: Config | undefined } + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"ListGenerationContent401", typeof ListGenerationContent401.Type> + | OpenRouterClientError<"ListGenerationContent403", typeof ListGenerationContent403.Type> + | OpenRouterClientError<"ListGenerationContent404", typeof ListGenerationContent404.Type> + | OpenRouterClientError<"ListGenerationContent429", typeof ListGenerationContent429.Type> + | OpenRouterClientError<"ListGenerationContent500", typeof ListGenerationContent500.Type> + | OpenRouterClientError<"ListGenerationContent502", typeof ListGenerationContent502.Type> + | OpenRouterClientError<"ListGenerationContent524", typeof ListGenerationContent524.Type> + | OpenRouterClientError<"ListGenerationContent529", typeof ListGenerationContent529.Type> + > + /** + * Submit structured feedback on a generation the authenticated user made. [Management key](/docs/guides/overview/auth/management-api-keys) required. + */ + readonly "submitGenerationFeedback": ( + options: { + readonly params?: typeof SubmitGenerationFeedbackParams.Encoded | undefined + readonly payload: typeof SubmitGenerationFeedbackRequestJson.Encoded + readonly config?: Config | undefined + } + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"SubmitGenerationFeedback400", typeof SubmitGenerationFeedback400.Type> + | OpenRouterClientError<"SubmitGenerationFeedback401", typeof SubmitGenerationFeedback401.Type> + | OpenRouterClientError<"SubmitGenerationFeedback404", typeof SubmitGenerationFeedback404.Type> + | OpenRouterClientError<"SubmitGenerationFeedback429", typeof SubmitGenerationFeedback429.Type> + | OpenRouterClientError<"SubmitGenerationFeedback500", typeof SubmitGenerationFeedback500.Type> + > + /** + * List all guardrails for the authenticated user. [Management key](/docs/guides/overview/auth/management-api-keys) required. + */ + readonly "listGuardrails": ( + options: + | { readonly params?: typeof ListGuardrailsParams.Encoded | undefined; readonly config?: Config | undefined } + | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"ListGuardrails400", typeof ListGuardrails400.Type> + | OpenRouterClientError<"ListGuardrails401", typeof ListGuardrails401.Type> + | OpenRouterClientError<"ListGuardrails500", typeof ListGuardrails500.Type> + > + /** + * Create a new guardrail for the authenticated user. [Management key](/docs/guides/overview/auth/management-api-keys) required. + */ + readonly "createGuardrail": ( + options: { + readonly params?: typeof CreateGuardrailParams.Encoded | undefined + readonly payload: typeof CreateGuardrailRequestJson.Encoded + readonly config?: Config | undefined + } + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"CreateGuardrail400", typeof CreateGuardrail400.Type> + | OpenRouterClientError<"CreateGuardrail401", typeof CreateGuardrail401.Type> + | OpenRouterClientError<"CreateGuardrail403", typeof CreateGuardrail403.Type> + | OpenRouterClientError<"CreateGuardrail500", typeof CreateGuardrail500.Type> + > + /** + * Get a single guardrail by ID. [Management key](/docs/guides/overview/auth/management-api-keys) required. + */ + readonly "getGuardrail": ( + id: string, + options: + | { readonly params?: typeof GetGuardrailParams.Encoded | undefined; readonly config?: Config | undefined } + | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"GetGuardrail401", typeof GetGuardrail401.Type> + | OpenRouterClientError<"GetGuardrail404", typeof GetGuardrail404.Type> + | OpenRouterClientError<"GetGuardrail500", typeof GetGuardrail500.Type> + > + /** + * Delete an existing guardrail. [Management key](/docs/guides/overview/auth/management-api-keys) required. + */ + readonly "deleteGuardrail": ( + id: string, + options: { + readonly params?: typeof DeleteGuardrailParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"DeleteGuardrail401", typeof DeleteGuardrail401.Type> + | OpenRouterClientError<"DeleteGuardrail404", typeof DeleteGuardrail404.Type> + | OpenRouterClientError<"DeleteGuardrail500", typeof DeleteGuardrail500.Type> + > + /** + * Update an existing guardrail. Collection fields use replace semantics: send the full desired set on every update. [Management key](/docs/guides/overview/auth/management-api-keys) required. + */ + readonly "updateGuardrail": ( + id: string, + options: { + readonly params?: typeof UpdateGuardrailParams.Encoded | undefined + readonly payload: typeof UpdateGuardrailRequestJson.Encoded + readonly config?: Config | undefined + } + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"UpdateGuardrail400", typeof UpdateGuardrail400.Type> + | OpenRouterClientError<"UpdateGuardrail401", typeof UpdateGuardrail401.Type> + | OpenRouterClientError<"UpdateGuardrail404", typeof UpdateGuardrail404.Type> + | OpenRouterClientError<"UpdateGuardrail500", typeof UpdateGuardrail500.Type> + > + /** + * List all API key assignments for a specific guardrail. [Management key](/docs/guides/overview/auth/management-api-keys) required. + */ + readonly "listGuardrailKeyAssignments": ( + id: string, + options: { + readonly params?: typeof ListGuardrailKeyAssignmentsParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"ListGuardrailKeyAssignments401", typeof ListGuardrailKeyAssignments401.Type> + | OpenRouterClientError<"ListGuardrailKeyAssignments404", typeof ListGuardrailKeyAssignments404.Type> + | OpenRouterClientError<"ListGuardrailKeyAssignments500", typeof ListGuardrailKeyAssignments500.Type> + > + /** + * Assign multiple API keys to a specific guardrail. A key may hold at most one guardrail; assigning replaces any existing assignment. [Management key](/docs/guides/overview/auth/management-api-keys) required. + */ + readonly "bulkAssignKeysToGuardrail": ( + id: string, + options: { + readonly params?: typeof BulkAssignKeysToGuardrailParams.Encoded | undefined + readonly payload: typeof BulkAssignKeysToGuardrailRequestJson.Encoded + readonly config?: Config | undefined + } + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"BulkAssignKeysToGuardrail400", typeof BulkAssignKeysToGuardrail400.Type> + | OpenRouterClientError<"BulkAssignKeysToGuardrail401", typeof BulkAssignKeysToGuardrail401.Type> + | OpenRouterClientError<"BulkAssignKeysToGuardrail404", typeof BulkAssignKeysToGuardrail404.Type> + | OpenRouterClientError<"BulkAssignKeysToGuardrail500", typeof BulkAssignKeysToGuardrail500.Type> + > + /** + * Unassign multiple API keys from a specific guardrail. [Management key](/docs/guides/overview/auth/management-api-keys) required. + */ + readonly "bulkUnassignKeysFromGuardrail": ( + id: string, + options: { + readonly params?: typeof BulkUnassignKeysFromGuardrailParams.Encoded | undefined + readonly payload: typeof BulkUnassignKeysFromGuardrailRequestJson.Encoded + readonly config?: Config | undefined + } + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"BulkUnassignKeysFromGuardrail400", typeof BulkUnassignKeysFromGuardrail400.Type> + | OpenRouterClientError<"BulkUnassignKeysFromGuardrail401", typeof BulkUnassignKeysFromGuardrail401.Type> + | OpenRouterClientError<"BulkUnassignKeysFromGuardrail404", typeof BulkUnassignKeysFromGuardrail404.Type> + | OpenRouterClientError<"BulkUnassignKeysFromGuardrail500", typeof BulkUnassignKeysFromGuardrail500.Type> + > + /** + * List all organization member assignments for a specific guardrail. [Management key](/docs/guides/overview/auth/management-api-keys) required. + */ + readonly "listGuardrailMemberAssignments": ( + id: string, + options: { + readonly params?: typeof ListGuardrailMemberAssignmentsParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"ListGuardrailMemberAssignments401", typeof ListGuardrailMemberAssignments401.Type> + | OpenRouterClientError<"ListGuardrailMemberAssignments404", typeof ListGuardrailMemberAssignments404.Type> + | OpenRouterClientError<"ListGuardrailMemberAssignments500", typeof ListGuardrailMemberAssignments500.Type> + > + /** + * Assign multiple organization members to a specific guardrail. [Management key](/docs/guides/overview/auth/management-api-keys) required. + */ + readonly "bulkAssignMembersToGuardrail": ( + id: string, + options: { + readonly params?: typeof BulkAssignMembersToGuardrailParams.Encoded | undefined + readonly payload: typeof BulkAssignMembersToGuardrailRequestJson.Encoded + readonly config?: Config | undefined + } + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"BulkAssignMembersToGuardrail400", typeof BulkAssignMembersToGuardrail400.Type> + | OpenRouterClientError<"BulkAssignMembersToGuardrail401", typeof BulkAssignMembersToGuardrail401.Type> + | OpenRouterClientError<"BulkAssignMembersToGuardrail404", typeof BulkAssignMembersToGuardrail404.Type> + | OpenRouterClientError<"BulkAssignMembersToGuardrail500", typeof BulkAssignMembersToGuardrail500.Type> + > + /** + * Unassign multiple organization members from a specific guardrail. [Management key](/docs/guides/overview/auth/management-api-keys) required. + */ + readonly "bulkUnassignMembersFromGuardrail": ( + id: string, + options: { + readonly params?: typeof BulkUnassignMembersFromGuardrailParams.Encoded | undefined + readonly payload: typeof BulkUnassignMembersFromGuardrailRequestJson.Encoded + readonly config?: Config | undefined + } + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"BulkUnassignMembersFromGuardrail400", typeof BulkUnassignMembersFromGuardrail400.Type> + | OpenRouterClientError<"BulkUnassignMembersFromGuardrail401", typeof BulkUnassignMembersFromGuardrail401.Type> + | OpenRouterClientError<"BulkUnassignMembersFromGuardrail404", typeof BulkUnassignMembersFromGuardrail404.Type> + | OpenRouterClientError<"BulkUnassignMembersFromGuardrail500", typeof BulkUnassignMembersFromGuardrail500.Type> + > + /** + * List all API key guardrail assignments for the authenticated user. [Management key](/docs/guides/overview/auth/management-api-keys) required. + */ + readonly "listKeyAssignments": ( + options: { + readonly params?: typeof ListKeyAssignmentsParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"ListKeyAssignments401", typeof ListKeyAssignments401.Type> + | OpenRouterClientError<"ListKeyAssignments500", typeof ListKeyAssignments500.Type> + > + /** + * List all organization member guardrail assignments for the authenticated user. [Management key](/docs/guides/overview/auth/management-api-keys) required. + */ + readonly "listMemberAssignments": ( + options: { + readonly params?: typeof ListMemberAssignmentsParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"ListMemberAssignments401", typeof ListMemberAssignments401.Type> + | OpenRouterClientError<"ListMemberAssignments500", typeof ListMemberAssignments500.Type> + > + /** + * Generates an image from a text prompt via the image generation router + */ + readonly "createImages": ( + options: { + readonly params?: typeof CreateImagesParams.Encoded | undefined + readonly payload: typeof CreateImagesRequestJson.Encoded + readonly config?: Config | undefined + } + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"CreateImages400", typeof CreateImages400.Type> + | OpenRouterClientError<"CreateImages401", typeof CreateImages401.Type> + | OpenRouterClientError<"CreateImages402", typeof CreateImages402.Type> + | OpenRouterClientError<"CreateImages403", typeof CreateImages403.Type> + | OpenRouterClientError<"CreateImages404", typeof CreateImages404.Type> + | OpenRouterClientError<"CreateImages413", typeof CreateImages413.Type> + | OpenRouterClientError<"CreateImages429", typeof CreateImages429.Type> + | OpenRouterClientError<"CreateImages500", typeof CreateImages500.Type> + | OpenRouterClientError<"CreateImages502", typeof CreateImages502.Type> + | OpenRouterClientError<"CreateImages524", typeof CreateImages524.Type> + | OpenRouterClientError<"CreateImages529", typeof CreateImages529.Type> + > + /** + * Generates an image from a text prompt via the image generation router + */ + readonly "createImagesSse": ( + options: { + readonly params?: typeof CreateImagesParams.Encoded | undefined + readonly payload: typeof CreateImagesRequestJson.Encoded + } + ) => Stream.Stream< + { readonly event: string; readonly id: string | undefined; readonly data: typeof CreateImages200Sse.Type }, + HttpClientError.HttpClientError | SchemaError | Sse.Retry | Sse.SseError, + typeof CreateImages200Sse.DecodingServices + > + /** + * Lists every image generation model with its top-level supported-parameter superset and a URL to its full per-endpoint records. + */ + readonly "listImageModels": ( + options: { + readonly params?: typeof ListImageModelsParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"ListImageModels500", typeof ListImageModels500.Type> + > + /** + * Returns the full per-endpoint records for an image model: each endpoint's definitive supported parameters, pricing, and passthrough allowlist. + */ + readonly "listImageModelEndpoints": ( + author: string, + slug: string, + options: { + readonly params?: typeof ListImageModelEndpointsParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"ListImageModelEndpoints404", typeof ListImageModelEndpoints404.Type> + | OpenRouterClientError<"ListImageModelEndpoints500", typeof ListImageModelEndpoints500.Type> + > + /** + * Get information on the API key associated with the current authentication session + */ + readonly "getCurrentKey": ( + options: + | { readonly params?: typeof GetCurrentKeyParams.Encoded | undefined; readonly config?: Config | undefined } + | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"GetCurrentKey401", typeof GetCurrentKey401.Type> + | OpenRouterClientError<"GetCurrentKey500", typeof GetCurrentKey500.Type> + > /** - * Creates a streaming or non-streaming response using OpenResponses API format + * List all API keys for the authenticated user. [Management key](/docs/guides/overview/auth/management-api-keys) required. */ - readonly "createResponses": ( - options: { readonly payload: typeof CreateResponsesRequestJson.Encoded; readonly config?: Config | undefined } + readonly "list": ( + options: + | { readonly params?: typeof ListParams.Encoded | undefined; readonly config?: Config | undefined } + | undefined ) => Effect.Effect< - WithOptionalResponse, + WithOptionalResponse, | HttpClientError.HttpClientError | SchemaError - | OpenRouterClientError<"CreateResponses400", typeof CreateResponses400.Type> - | OpenRouterClientError<"CreateResponses401", typeof CreateResponses401.Type> - | OpenRouterClientError<"CreateResponses402", typeof CreateResponses402.Type> - | OpenRouterClientError<"CreateResponses404", typeof CreateResponses404.Type> - | OpenRouterClientError<"CreateResponses408", typeof CreateResponses408.Type> - | OpenRouterClientError<"CreateResponses413", typeof CreateResponses413.Type> - | OpenRouterClientError<"CreateResponses422", typeof CreateResponses422.Type> - | OpenRouterClientError<"CreateResponses429", typeof CreateResponses429.Type> - | OpenRouterClientError<"CreateResponses500", typeof CreateResponses500.Type> - | OpenRouterClientError<"CreateResponses502", typeof CreateResponses502.Type> - | OpenRouterClientError<"CreateResponses503", typeof CreateResponses503.Type> - | OpenRouterClientError<"CreateResponses524", typeof CreateResponses524.Type> - | OpenRouterClientError<"CreateResponses529", typeof CreateResponses529.Type> + | OpenRouterClientError<"List400", typeof List400.Type> + | OpenRouterClientError<"List401", typeof List401.Type> + | OpenRouterClientError<"List429", typeof List429.Type> + | OpenRouterClientError<"List500", typeof List500.Type> > /** - * Creates a streaming or non-streaming response using OpenResponses API format + * Create a new API key for the authenticated user. The plaintext `key` is returned only in this response. Treat it as a write-only, sensitive value; it cannot be retrieved later. [Management key](/docs/guides/overview/auth/management-api-keys) required. */ - readonly "createResponsesSse": ( - options: { readonly payload: typeof CreateResponsesRequestJson.Encoded } - ) => Stream.Stream< - { readonly event: string; readonly id: string | undefined; readonly data: typeof CreateResponses200Sse.Type }, - HttpClientError.HttpClientError | SchemaError | Sse.Retry, - typeof CreateResponses200Sse.DecodingServices + readonly "createKeys": ( + options: { + readonly params?: typeof CreateKeysParams.Encoded | undefined + readonly payload: typeof CreateKeysRequestJson.Encoded + readonly config?: Config | undefined + } + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"CreateKeys400", typeof CreateKeys400.Type> + | OpenRouterClientError<"CreateKeys401", typeof CreateKeys401.Type> + | OpenRouterClientError<"CreateKeys403", typeof CreateKeys403.Type> + | OpenRouterClientError<"CreateKeys429", typeof CreateKeys429.Type> + | OpenRouterClientError<"CreateKeys500", typeof CreateKeys500.Type> + > + /** + * Get a single API key by hash. [Management key](/docs/guides/overview/auth/management-api-keys) required. + */ + readonly "getKey": ( + hash: string, + options: + | { readonly params?: typeof GetKeyParams.Encoded | undefined; readonly config?: Config | undefined } + | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"GetKey401", typeof GetKey401.Type> + | OpenRouterClientError<"GetKey404", typeof GetKey404.Type> + | OpenRouterClientError<"GetKey429", typeof GetKey429.Type> + | OpenRouterClientError<"GetKey500", typeof GetKey500.Type> + > + /** + * Delete an existing API key. [Management key](/docs/guides/overview/auth/management-api-keys) required. + */ + readonly "deleteKeys": ( + hash: string, + options: + | { readonly params?: typeof DeleteKeysParams.Encoded | undefined; readonly config?: Config | undefined } + | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"DeleteKeys401", typeof DeleteKeys401.Type> + | OpenRouterClientError<"DeleteKeys404", typeof DeleteKeys404.Type> + | OpenRouterClientError<"DeleteKeys429", typeof DeleteKeys429.Type> + | OpenRouterClientError<"DeleteKeys500", typeof DeleteKeys500.Type> + > + /** + * Update an existing API key. [Management key](/docs/guides/overview/auth/management-api-keys) required. + */ + readonly "updateKeys": ( + hash: string, + options: { + readonly params?: typeof UpdateKeysParams.Encoded | undefined + readonly payload: typeof UpdateKeysRequestJson.Encoded + readonly config?: Config | undefined + } + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"UpdateKeys400", typeof UpdateKeys400.Type> + | OpenRouterClientError<"UpdateKeys401", typeof UpdateKeys401.Type> + | OpenRouterClientError<"UpdateKeys404", typeof UpdateKeys404.Type> + | OpenRouterClientError<"UpdateKeys429", typeof UpdateKeys429.Type> + | OpenRouterClientError<"UpdateKeys500", typeof UpdateKeys500.Type> > /** * Creates a message using the Anthropic Messages API format. Supports text, images, PDFs, tools, and extended thinking. */ readonly "createMessages": ( - options: { readonly payload: typeof CreateMessagesRequestJson.Encoded; readonly config?: Config | undefined } + options: { + readonly params?: typeof CreateMessagesParams.Encoded | undefined + readonly payload: typeof CreateMessagesRequestJson.Encoded + readonly config?: Config | undefined + } ) => Effect.Effect< WithOptionalResponse, | HttpClientError.HttpClientError @@ -9462,542 +35613,693 @@ export interface OpenRouterClient { * Creates a message using the Anthropic Messages API format. Supports text, images, PDFs, tools, and extended thinking. */ readonly "createMessagesSse": ( - options: { readonly payload: typeof CreateMessagesRequestJson.Encoded } + options: { + readonly params?: typeof CreateMessagesParams.Encoded | undefined + readonly payload: typeof CreateMessagesRequestJson.Encoded + } ) => Stream.Stream< { readonly event: string; readonly id: string | undefined; readonly data: typeof CreateMessages200Sse.Type }, - HttpClientError.HttpClientError | SchemaError | Sse.Retry, + HttpClientError.HttpClientError | SchemaError | Sse.Retry | Sse.SseError, typeof CreateMessages200Sse.DecodingServices > /** - * Returns user activity data grouped by endpoint for the last 30 (completed) UTC days. [Management key](/docs/guides/overview/auth/management-api-keys) required. + * Returns full details for a single model identified by its author and slug (e.g. openai/gpt-4). Supports variant suffixes (e.g. openai/gpt-4:free) and resolves known slug aliases. */ - readonly "getUserActivity": ( - options: { - readonly params?: typeof GetUserActivityParams.Encoded | undefined - readonly config?: Config | undefined - } | undefined + readonly "getModel": ( + author: string, + slug: string, + options: + | { readonly params?: typeof GetModelParams.Encoded | undefined; readonly config?: Config | undefined } + | undefined ) => Effect.Effect< - WithOptionalResponse, + WithOptionalResponse, | HttpClientError.HttpClientError | SchemaError - | OpenRouterClientError<"GetUserActivity400", typeof GetUserActivity400.Type> - | OpenRouterClientError<"GetUserActivity401", typeof GetUserActivity401.Type> - | OpenRouterClientError<"GetUserActivity403", typeof GetUserActivity403.Type> - | OpenRouterClientError<"GetUserActivity500", typeof GetUserActivity500.Type> + | OpenRouterClientError<"GetModel403", typeof GetModel403.Type> + | OpenRouterClientError<"GetModel404", typeof GetModel404.Type> + | OpenRouterClientError<"GetModel500", typeof GetModel500.Type> > /** - * Get total credits purchased and used for the authenticated user. [Management key](/docs/guides/overview/auth/management-api-keys) required. + * List all models and their properties */ - readonly "getCredits": ( - options: { readonly config?: Config | undefined } | undefined + readonly "getModels": ( + options: + | { readonly params?: typeof GetModelsParams.Encoded | undefined; readonly config?: Config | undefined } + | undefined ) => Effect.Effect< - WithOptionalResponse, + WithOptionalResponse, | HttpClientError.HttpClientError | SchemaError - | OpenRouterClientError<"GetCredits401", typeof GetCredits401.Type> - | OpenRouterClientError<"GetCredits403", typeof GetCredits403.Type> - | OpenRouterClientError<"GetCredits500", typeof GetCredits500.Type> + | OpenRouterClientError<"GetModels400", typeof GetModels400.Type> + | OpenRouterClientError<"GetModels403", typeof GetModels403.Type> + | OpenRouterClientError<"GetModels500", typeof GetModels500.Type> > /** - * Create a Coinbase charge for crypto payment + * List all endpoints for a model */ - readonly "createCoinbaseCharge": ( - options: { readonly payload: typeof CreateCoinbaseChargeRequestJson.Encoded; readonly config?: Config | undefined } + readonly "listEndpoints": ( + author: string, + slug: string, + options: + | { readonly params?: typeof ListEndpointsParams.Encoded | undefined; readonly config?: Config | undefined } + | undefined ) => Effect.Effect< - WithOptionalResponse, + WithOptionalResponse, | HttpClientError.HttpClientError | SchemaError - | OpenRouterClientError<"CreateCoinbaseCharge400", typeof CreateCoinbaseCharge400.Type> - | OpenRouterClientError<"CreateCoinbaseCharge401", typeof CreateCoinbaseCharge401.Type> - | OpenRouterClientError<"CreateCoinbaseCharge429", typeof CreateCoinbaseCharge429.Type> - | OpenRouterClientError<"CreateCoinbaseCharge500", typeof CreateCoinbaseCharge500.Type> + | OpenRouterClientError<"ListEndpoints403", typeof ListEndpoints403.Type> + | OpenRouterClientError<"ListEndpoints404", typeof ListEndpoints404.Type> + | OpenRouterClientError<"ListEndpoints500", typeof ListEndpoints500.Type> > /** - * Submits an embedding request to the embeddings router + * Get total count of available models */ - readonly "createEmbeddings": ( - options: { readonly payload: typeof CreateEmbeddingsRequestJson.Encoded; readonly config?: Config | undefined } + readonly "listModelsCount": ( + options: { + readonly params?: typeof ListModelsCountParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined ) => Effect.Effect< - WithOptionalResponse, + WithOptionalResponse, | HttpClientError.HttpClientError | SchemaError - | OpenRouterClientError<"CreateEmbeddings400", typeof CreateEmbeddings400.Type> - | OpenRouterClientError<"CreateEmbeddings401", typeof CreateEmbeddings401.Type> - | OpenRouterClientError<"CreateEmbeddings402", typeof CreateEmbeddings402.Type> - | OpenRouterClientError<"CreateEmbeddings404", typeof CreateEmbeddings404.Type> - | OpenRouterClientError<"CreateEmbeddings429", typeof CreateEmbeddings429.Type> - | OpenRouterClientError<"CreateEmbeddings500", typeof CreateEmbeddings500.Type> - | OpenRouterClientError<"CreateEmbeddings502", typeof CreateEmbeddings502.Type> - | OpenRouterClientError<"CreateEmbeddings503", typeof CreateEmbeddings503.Type> - | OpenRouterClientError<"CreateEmbeddings524", typeof CreateEmbeddings524.Type> - | OpenRouterClientError<"CreateEmbeddings529", typeof CreateEmbeddings529.Type> + | OpenRouterClientError<"ListModelsCount400", typeof ListModelsCount400.Type> + | OpenRouterClientError<"ListModelsCount403", typeof ListModelsCount403.Type> + | OpenRouterClientError<"ListModelsCount500", typeof ListModelsCount500.Type> > /** - * Submits an embedding request to the embeddings router + * List models filtered by user provider preferences, [privacy settings](https://openrouter.ai/docs/guides/privacy/provider-logging), and [guardrails](https://openrouter.ai/docs/guides/features/guardrails). If requesting through `eu.openrouter.ai/api/v1/...` the results will be filtered to models that satisfy [EU in-region routing](https://openrouter.ai/docs/guides/privacy/provider-logging#enterprise-eu-in-region-routing). */ - readonly "createEmbeddingsSse": ( - options: { readonly payload: typeof CreateEmbeddingsRequestJson.Encoded } - ) => Stream.Stream< - { readonly event: string; readonly id: string | undefined; readonly data: typeof CreateEmbeddings200Sse.Type }, - HttpClientError.HttpClientError | SchemaError | Sse.Retry, - typeof CreateEmbeddings200Sse.DecodingServices + readonly "listModelsUser": ( + options: + | { readonly params?: typeof ListModelsUserParams.Encoded | undefined; readonly config?: Config | undefined } + | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"ListModelsUser401", typeof ListModelsUser401.Type> + | OpenRouterClientError<"ListModelsUser403", typeof ListModelsUser403.Type> + | OpenRouterClientError<"ListModelsUser404", typeof ListModelsUser404.Type> + | OpenRouterClientError<"ListModelsUser500", typeof ListModelsUser500.Type> > /** - * Returns a list of all available embeddings models and their properties + * List the observability destinations configured for the authenticated entity's default workspace. Use the `workspace_id` query parameter to scope the result to a different workspace. Only destinations with stable release status are surfaced — destinations of other types are excluded. [Management key](/docs/guides/overview/auth/management-api-keys) required. */ - readonly "listEmbeddingsModels": ( - options: { readonly config?: Config | undefined } | undefined + readonly "listObservabilityDestinations": ( + options: { + readonly params?: typeof ListObservabilityDestinationsParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined ) => Effect.Effect< - WithOptionalResponse, + WithOptionalResponse, | HttpClientError.HttpClientError | SchemaError - | OpenRouterClientError<"ListEmbeddingsModels400", typeof ListEmbeddingsModels400.Type> - | OpenRouterClientError<"ListEmbeddingsModels500", typeof ListEmbeddingsModels500.Type> + | OpenRouterClientError<"ListObservabilityDestinations400", typeof ListObservabilityDestinations400.Type> + | OpenRouterClientError<"ListObservabilityDestinations401", typeof ListObservabilityDestinations401.Type> + | OpenRouterClientError<"ListObservabilityDestinations500", typeof ListObservabilityDestinations500.Type> > /** - * Get request & usage metadata for a generation + * Create a new observability destination. A maximum of 5 destinations per type is allowed. Defaults to the authenticated entity's default workspace; use the `workspace_id` body field to scope to a different workspace. [Management key](/docs/guides/overview/auth/management-api-keys) required. */ - readonly "getGeneration": ( - options: { readonly params: typeof GetGenerationParams.Encoded; readonly config?: Config | undefined } + readonly "createObservabilityDestination": ( + options: { + readonly params?: typeof CreateObservabilityDestinationParams.Encoded | undefined + readonly payload: typeof CreateObservabilityDestinationRequestJson.Encoded + readonly config?: Config | undefined + } ) => Effect.Effect< - WithOptionalResponse, + WithOptionalResponse, | HttpClientError.HttpClientError | SchemaError - | OpenRouterClientError<"GetGeneration401", typeof GetGeneration401.Type> - | OpenRouterClientError<"GetGeneration402", typeof GetGeneration402.Type> - | OpenRouterClientError<"GetGeneration404", typeof GetGeneration404.Type> - | OpenRouterClientError<"GetGeneration429", typeof GetGeneration429.Type> - | OpenRouterClientError<"GetGeneration500", typeof GetGeneration500.Type> - | OpenRouterClientError<"GetGeneration502", typeof GetGeneration502.Type> - | OpenRouterClientError<"GetGeneration524", typeof GetGeneration524.Type> - | OpenRouterClientError<"GetGeneration529", typeof GetGeneration529.Type> + | OpenRouterClientError<"CreateObservabilityDestination400", typeof CreateObservabilityDestination400.Type> + | OpenRouterClientError<"CreateObservabilityDestination401", typeof CreateObservabilityDestination401.Type> + | OpenRouterClientError<"CreateObservabilityDestination403", typeof CreateObservabilityDestination403.Type> + | OpenRouterClientError<"CreateObservabilityDestination409", typeof CreateObservabilityDestination409.Type> + | OpenRouterClientError<"CreateObservabilityDestination500", typeof CreateObservabilityDestination500.Type> > /** - * Get total count of available models + * Fetch a single observability destination by its UUID. [Management key](/docs/guides/overview/auth/management-api-keys) required. */ - readonly "listModelsCount": ( - options: { readonly config?: Config | undefined } | undefined + readonly "getObservabilityDestination": ( + id: string, + options: { + readonly params?: typeof GetObservabilityDestinationParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined ) => Effect.Effect< - WithOptionalResponse, + WithOptionalResponse, | HttpClientError.HttpClientError | SchemaError - | OpenRouterClientError<"ListModelsCount500", typeof ListModelsCount500.Type> + | OpenRouterClientError<"GetObservabilityDestination401", typeof GetObservabilityDestination401.Type> + | OpenRouterClientError<"GetObservabilityDestination404", typeof GetObservabilityDestination404.Type> + | OpenRouterClientError<"GetObservabilityDestination500", typeof GetObservabilityDestination500.Type> > /** - * List all models and their properties + * Delete an existing observability destination. This performs a soft delete. [Management key](/docs/guides/overview/auth/management-api-keys) required. */ - readonly "getModels": ( - options: - | { readonly params?: typeof GetModelsParams.Encoded | undefined; readonly config?: Config | undefined } - | undefined + readonly "deleteObservabilityDestination": ( + id: string, + options: { + readonly params?: typeof DeleteObservabilityDestinationParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined ) => Effect.Effect< - WithOptionalResponse, + WithOptionalResponse, | HttpClientError.HttpClientError | SchemaError - | OpenRouterClientError<"GetModels400", typeof GetModels400.Type> - | OpenRouterClientError<"GetModels500", typeof GetModels500.Type> + | OpenRouterClientError<"DeleteObservabilityDestination401", typeof DeleteObservabilityDestination401.Type> + | OpenRouterClientError<"DeleteObservabilityDestination404", typeof DeleteObservabilityDestination404.Type> + | OpenRouterClientError<"DeleteObservabilityDestination500", typeof DeleteObservabilityDestination500.Type> > /** - * List models filtered by user provider preferences, [privacy settings](https://openrouter.ai/docs/guides/privacy/logging), and [guardrails](https://openrouter.ai/docs/guides/features/guardrails). If requesting through `eu.openrouter.ai/api/v1/...` the results will be filtered to models that satisfy [EU in-region routing](https://openrouter.ai/docs/guides/privacy/logging#enterprise-eu-in-region-routing). + * Update an existing observability destination. Only the fields provided in the request body are updated. [Management key](/docs/guides/overview/auth/management-api-keys) required. */ - readonly "listModelsUser": ( - options: { readonly config?: Config | undefined } | undefined + readonly "updateObservabilityDestination": ( + id: string, + options: { + readonly params?: typeof UpdateObservabilityDestinationParams.Encoded | undefined + readonly payload: typeof UpdateObservabilityDestinationRequestJson.Encoded + readonly config?: Config | undefined + } ) => Effect.Effect< - WithOptionalResponse, + WithOptionalResponse, | HttpClientError.HttpClientError | SchemaError - | OpenRouterClientError<"ListModelsUser401", typeof ListModelsUser401.Type> - | OpenRouterClientError<"ListModelsUser404", typeof ListModelsUser404.Type> - | OpenRouterClientError<"ListModelsUser500", typeof ListModelsUser500.Type> + | OpenRouterClientError<"UpdateObservabilityDestination400", typeof UpdateObservabilityDestination400.Type> + | OpenRouterClientError<"UpdateObservabilityDestination401", typeof UpdateObservabilityDestination401.Type> + | OpenRouterClientError<"UpdateObservabilityDestination404", typeof UpdateObservabilityDestination404.Type> + | OpenRouterClientError<"UpdateObservabilityDestination409", typeof UpdateObservabilityDestination409.Type> + | OpenRouterClientError<"UpdateObservabilityDestination500", typeof UpdateObservabilityDestination500.Type> > /** - * List all endpoints for a model + * List all members of the organization associated with the authenticated management key. [Management key](/docs/guides/overview/auth/management-api-keys) required. */ - readonly "listEndpoints": ( - author: string, - slug: string, - options: { readonly config?: Config | undefined } | undefined + readonly "listOrganizationMembers": ( + options: { + readonly params?: typeof ListOrganizationMembersParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined ) => Effect.Effect< - WithOptionalResponse, + WithOptionalResponse, | HttpClientError.HttpClientError | SchemaError - | OpenRouterClientError<"ListEndpoints404", typeof ListEndpoints404.Type> - | OpenRouterClientError<"ListEndpoints500", typeof ListEndpoints500.Type> + | OpenRouterClientError<"ListOrganizationMembers401", typeof ListOrganizationMembers401.Type> + | OpenRouterClientError<"ListOrganizationMembers404", typeof ListOrganizationMembers404.Type> + | OpenRouterClientError<"ListOrganizationMembers500", typeof ListOrganizationMembers500.Type> > /** - * Preview the impact of ZDR on the available endpoints + * Lists all presets for the authenticated user, ordered by most recently updated first. */ - readonly "listEndpointsZdr": ( - options: { readonly config?: Config | undefined } | undefined + readonly "listPresets": ( + options: + | { readonly params?: typeof ListPresetsParams.Encoded | undefined; readonly config?: Config | undefined } + | undefined ) => Effect.Effect< - WithOptionalResponse, + WithOptionalResponse, | HttpClientError.HttpClientError | SchemaError - | OpenRouterClientError<"ListEndpointsZdr500", typeof ListEndpointsZdr500.Type> + | OpenRouterClientError<"ListPresets400", typeof ListPresets400.Type> + | OpenRouterClientError<"ListPresets401", typeof ListPresets401.Type> + | OpenRouterClientError<"ListPresets500", typeof ListPresets500.Type> > /** - * List all providers + * Retrieves a preset by its slug with its currently designated version inline. */ - readonly "listProviders": ( - options: { readonly config?: Config | undefined } | undefined + readonly "getPreset": ( + slug: string, + options: + | { readonly params?: typeof GetPresetParams.Encoded | undefined; readonly config?: Config | undefined } + | undefined ) => Effect.Effect< - WithOptionalResponse, + WithOptionalResponse, | HttpClientError.HttpClientError | SchemaError - | OpenRouterClientError<"ListProviders500", typeof ListProviders500.Type> + | OpenRouterClientError<"GetPreset400", typeof GetPreset400.Type> + | OpenRouterClientError<"GetPreset401", typeof GetPreset401.Type> + | OpenRouterClientError<"GetPreset404", typeof GetPreset404.Type> + | OpenRouterClientError<"GetPreset500", typeof GetPreset500.Type> > /** - * List all API keys for the authenticated user. [Management key](/docs/guides/overview/auth/management-api-keys) required. + * Creates a preset (or a new version of an existing one) from an inference request body. Only fields that overlap with the preset config are persisted; other fields (e.g. `messages`, `stream`, `prompt`) are silently ignored. */ - readonly "list": ( - options: - | { readonly params?: typeof ListParams.Encoded | undefined; readonly config?: Config | undefined } - | undefined + readonly "createPresetsChatCompletions": ( + slug: string, + options: { + readonly params?: typeof CreatePresetsChatCompletionsParams.Encoded | undefined + readonly payload: typeof CreatePresetsChatCompletionsRequestJson.Encoded + readonly config?: Config | undefined + } ) => Effect.Effect< - WithOptionalResponse, + WithOptionalResponse, | HttpClientError.HttpClientError | SchemaError - | OpenRouterClientError<"List401", typeof List401.Type> - | OpenRouterClientError<"List429", typeof List429.Type> - | OpenRouterClientError<"List500", typeof List500.Type> + | OpenRouterClientError<"CreatePresetsChatCompletions400", typeof CreatePresetsChatCompletions400.Type> + | OpenRouterClientError<"CreatePresetsChatCompletions401", typeof CreatePresetsChatCompletions401.Type> + | OpenRouterClientError<"CreatePresetsChatCompletions403", typeof CreatePresetsChatCompletions403.Type> + | OpenRouterClientError<"CreatePresetsChatCompletions404", typeof CreatePresetsChatCompletions404.Type> + | OpenRouterClientError<"CreatePresetsChatCompletions409", typeof CreatePresetsChatCompletions409.Type> + | OpenRouterClientError<"CreatePresetsChatCompletions500", typeof CreatePresetsChatCompletions500.Type> > /** - * Create a new API key for the authenticated user. [Management key](/docs/guides/overview/auth/management-api-keys) required. + * Creates a preset (or a new version of an existing one) from an inference request body. Only fields that overlap with the preset config are persisted; other fields (e.g. `messages`, `stream`, `prompt`) are silently ignored. */ - readonly "createKeys": ( - options: { readonly payload: typeof CreateKeysRequestJson.Encoded; readonly config?: Config | undefined } + readonly "createPresetsMessages": ( + slug: string, + options: { + readonly params?: typeof CreatePresetsMessagesParams.Encoded | undefined + readonly payload: typeof CreatePresetsMessagesRequestJson.Encoded + readonly config?: Config | undefined + } ) => Effect.Effect< - WithOptionalResponse, + WithOptionalResponse, | HttpClientError.HttpClientError | SchemaError - | OpenRouterClientError<"CreateKeys400", typeof CreateKeys400.Type> - | OpenRouterClientError<"CreateKeys401", typeof CreateKeys401.Type> - | OpenRouterClientError<"CreateKeys429", typeof CreateKeys429.Type> - | OpenRouterClientError<"CreateKeys500", typeof CreateKeys500.Type> + | OpenRouterClientError<"CreatePresetsMessages400", typeof CreatePresetsMessages400.Type> + | OpenRouterClientError<"CreatePresetsMessages401", typeof CreatePresetsMessages401.Type> + | OpenRouterClientError<"CreatePresetsMessages403", typeof CreatePresetsMessages403.Type> + | OpenRouterClientError<"CreatePresetsMessages404", typeof CreatePresetsMessages404.Type> + | OpenRouterClientError<"CreatePresetsMessages409", typeof CreatePresetsMessages409.Type> + | OpenRouterClientError<"CreatePresetsMessages500", typeof CreatePresetsMessages500.Type> > /** - * Get a single API key by hash. [Management key](/docs/guides/overview/auth/management-api-keys) required. + * Creates a preset (or a new version of an existing one) from an inference request body. Only fields that overlap with the preset config are persisted; other fields (e.g. `messages`, `stream`, `prompt`) are silently ignored. */ - readonly "getKey": ( - hash: string, - options: { readonly config?: Config | undefined } | undefined + readonly "createPresetsResponses": ( + slug: string, + options: { + readonly params?: typeof CreatePresetsResponsesParams.Encoded | undefined + readonly payload: typeof CreatePresetsResponsesRequestJson.Encoded + readonly config?: Config | undefined + } ) => Effect.Effect< - WithOptionalResponse, + WithOptionalResponse, | HttpClientError.HttpClientError | SchemaError - | OpenRouterClientError<"GetKey401", typeof GetKey401.Type> - | OpenRouterClientError<"GetKey404", typeof GetKey404.Type> - | OpenRouterClientError<"GetKey429", typeof GetKey429.Type> - | OpenRouterClientError<"GetKey500", typeof GetKey500.Type> + | OpenRouterClientError<"CreatePresetsResponses400", typeof CreatePresetsResponses400.Type> + | OpenRouterClientError<"CreatePresetsResponses401", typeof CreatePresetsResponses401.Type> + | OpenRouterClientError<"CreatePresetsResponses403", typeof CreatePresetsResponses403.Type> + | OpenRouterClientError<"CreatePresetsResponses404", typeof CreatePresetsResponses404.Type> + | OpenRouterClientError<"CreatePresetsResponses409", typeof CreatePresetsResponses409.Type> + | OpenRouterClientError<"CreatePresetsResponses500", typeof CreatePresetsResponses500.Type> > /** - * Delete an existing API key. [Management key](/docs/guides/overview/auth/management-api-keys) required. + * Lists all versions of a preset, ordered by version number ascending (oldest first). */ - readonly "deleteKeys": ( - hash: string, - options: { readonly config?: Config | undefined } | undefined + readonly "listPresetVersions": ( + slug: string, + options: { + readonly params?: typeof ListPresetVersionsParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined ) => Effect.Effect< - WithOptionalResponse, + WithOptionalResponse, | HttpClientError.HttpClientError | SchemaError - | OpenRouterClientError<"DeleteKeys401", typeof DeleteKeys401.Type> - | OpenRouterClientError<"DeleteKeys404", typeof DeleteKeys404.Type> - | OpenRouterClientError<"DeleteKeys429", typeof DeleteKeys429.Type> - | OpenRouterClientError<"DeleteKeys500", typeof DeleteKeys500.Type> + | OpenRouterClientError<"ListPresetVersions400", typeof ListPresetVersions400.Type> + | OpenRouterClientError<"ListPresetVersions401", typeof ListPresetVersions401.Type> + | OpenRouterClientError<"ListPresetVersions404", typeof ListPresetVersions404.Type> + | OpenRouterClientError<"ListPresetVersions500", typeof ListPresetVersions500.Type> > /** - * Update an existing API key. [Management key](/docs/guides/overview/auth/management-api-keys) required. + * Retrieves a specific version of a preset by its slug and version number. */ - readonly "updateKeys": ( - hash: string, - options: { readonly payload: typeof UpdateKeysRequestJson.Encoded; readonly config?: Config | undefined } + readonly "getPresetVersion": ( + slug: string, + version: string, + options: { + readonly params?: typeof GetPresetVersionParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined ) => Effect.Effect< - WithOptionalResponse, + WithOptionalResponse, | HttpClientError.HttpClientError | SchemaError - | OpenRouterClientError<"UpdateKeys400", typeof UpdateKeys400.Type> - | OpenRouterClientError<"UpdateKeys401", typeof UpdateKeys401.Type> - | OpenRouterClientError<"UpdateKeys404", typeof UpdateKeys404.Type> - | OpenRouterClientError<"UpdateKeys429", typeof UpdateKeys429.Type> - | OpenRouterClientError<"UpdateKeys500", typeof UpdateKeys500.Type> + | OpenRouterClientError<"GetPresetVersion400", typeof GetPresetVersion400.Type> + | OpenRouterClientError<"GetPresetVersion401", typeof GetPresetVersion401.Type> + | OpenRouterClientError<"GetPresetVersion404", typeof GetPresetVersion404.Type> + | OpenRouterClientError<"GetPresetVersion500", typeof GetPresetVersion500.Type> > /** - * List all guardrails for the authenticated user. [Management key](/docs/guides/overview/auth/management-api-keys) required. + * List all providers */ - readonly "listGuardrails": ( + readonly "listProviders": ( options: - | { readonly params?: typeof ListGuardrailsParams.Encoded | undefined; readonly config?: Config | undefined } + | { readonly params?: typeof ListProvidersParams.Encoded | undefined; readonly config?: Config | undefined } | undefined ) => Effect.Effect< - WithOptionalResponse, + WithOptionalResponse, | HttpClientError.HttpClientError | SchemaError - | OpenRouterClientError<"ListGuardrails401", typeof ListGuardrails401.Type> - | OpenRouterClientError<"ListGuardrails500", typeof ListGuardrails500.Type> + | OpenRouterClientError<"ListProviders500", typeof ListProviders500.Type> > /** - * Create a new guardrail for the authenticated user. [Management key](/docs/guides/overview/auth/management-api-keys) required. + * Submits a rerank request to the rerank router */ - readonly "createGuardrail": ( - options: { readonly payload: typeof CreateGuardrailRequestJson.Encoded; readonly config?: Config | undefined } + readonly "createRerank": ( + options: { + readonly params?: typeof CreateRerankParams.Encoded | undefined + readonly payload: typeof CreateRerankRequestJson.Encoded + readonly config?: Config | undefined + } ) => Effect.Effect< - WithOptionalResponse, + WithOptionalResponse, | HttpClientError.HttpClientError | SchemaError - | OpenRouterClientError<"CreateGuardrail400", typeof CreateGuardrail400.Type> - | OpenRouterClientError<"CreateGuardrail401", typeof CreateGuardrail401.Type> - | OpenRouterClientError<"CreateGuardrail500", typeof CreateGuardrail500.Type> + | OpenRouterClientError<"CreateRerank400", typeof CreateRerank400.Type> + | OpenRouterClientError<"CreateRerank401", typeof CreateRerank401.Type> + | OpenRouterClientError<"CreateRerank402", typeof CreateRerank402.Type> + | OpenRouterClientError<"CreateRerank404", typeof CreateRerank404.Type> + | OpenRouterClientError<"CreateRerank429", typeof CreateRerank429.Type> + | OpenRouterClientError<"CreateRerank500", typeof CreateRerank500.Type> + | OpenRouterClientError<"CreateRerank502", typeof CreateRerank502.Type> + | OpenRouterClientError<"CreateRerank503", typeof CreateRerank503.Type> + | OpenRouterClientError<"CreateRerank524", typeof CreateRerank524.Type> + | OpenRouterClientError<"CreateRerank529", typeof CreateRerank529.Type> > /** - * Get a single guardrail by ID. [Management key](/docs/guides/overview/auth/management-api-keys) required. + * Submits a rerank request to the rerank router */ - readonly "getGuardrail": ( - id: string, - options: { readonly config?: Config | undefined } | undefined + readonly "createRerankSse": ( + options: { + readonly params?: typeof CreateRerankParams.Encoded | undefined + readonly payload: typeof CreateRerankRequestJson.Encoded + } + ) => Stream.Stream< + { readonly event: string; readonly id: string | undefined; readonly data: typeof CreateRerank200Sse.Type }, + HttpClientError.HttpClientError | SchemaError | Sse.Retry | Sse.SseError, + typeof CreateRerank200Sse.DecodingServices + > + /** + * Creates a streaming or non-streaming response using OpenResponses API format + */ + readonly "createResponses": ( + options: { + readonly params?: typeof CreateResponsesParams.Encoded | undefined + readonly payload: typeof CreateResponsesRequestJson.Encoded + readonly config?: Config | undefined + } ) => Effect.Effect< - WithOptionalResponse, + WithOptionalResponse, | HttpClientError.HttpClientError | SchemaError - | OpenRouterClientError<"GetGuardrail401", typeof GetGuardrail401.Type> - | OpenRouterClientError<"GetGuardrail404", typeof GetGuardrail404.Type> - | OpenRouterClientError<"GetGuardrail500", typeof GetGuardrail500.Type> + | OpenRouterClientError<"CreateResponses400", typeof CreateResponses400.Type> + | OpenRouterClientError<"CreateResponses401", typeof CreateResponses401.Type> + | OpenRouterClientError<"CreateResponses402", typeof CreateResponses402.Type> + | OpenRouterClientError<"CreateResponses403", typeof CreateResponses403.Type> + | OpenRouterClientError<"CreateResponses404", typeof CreateResponses404.Type> + | OpenRouterClientError<"CreateResponses408", typeof CreateResponses408.Type> + | OpenRouterClientError<"CreateResponses413", typeof CreateResponses413.Type> + | OpenRouterClientError<"CreateResponses422", typeof CreateResponses422.Type> + | OpenRouterClientError<"CreateResponses429", typeof CreateResponses429.Type> + | OpenRouterClientError<"CreateResponses500", typeof CreateResponses500.Type> + | OpenRouterClientError<"CreateResponses502", typeof CreateResponses502.Type> + | OpenRouterClientError<"CreateResponses503", typeof CreateResponses503.Type> + | OpenRouterClientError<"CreateResponses524", typeof CreateResponses524.Type> + | OpenRouterClientError<"CreateResponses529", typeof CreateResponses529.Type> > /** - * Delete an existing guardrail. [Management key](/docs/guides/overview/auth/management-api-keys) required. + * Creates a streaming or non-streaming response using OpenResponses API format */ - readonly "deleteGuardrail": ( - id: string, - options: { readonly config?: Config | undefined } | undefined + readonly "createResponsesSse": ( + options: { + readonly params?: typeof CreateResponsesParams.Encoded | undefined + readonly payload: typeof CreateResponsesRequestJson.Encoded + } + ) => Stream.Stream< + { readonly event: string; readonly id: string | undefined; readonly data: typeof CreateResponses200Sse.Type }, + HttpClientError.HttpClientError | SchemaError | Sse.Retry | Sse.SseError, + typeof CreateResponses200Sse.DecodingServices + > + /** + * Submits a video generation request and returns a polling URL to check status + */ + readonly "createVideos": ( + options: { + readonly params?: typeof CreateVideosParams.Encoded | undefined + readonly payload: typeof CreateVideosRequestJson.Encoded + readonly config?: Config | undefined + } ) => Effect.Effect< - WithOptionalResponse, + WithOptionalResponse, | HttpClientError.HttpClientError | SchemaError - | OpenRouterClientError<"DeleteGuardrail401", typeof DeleteGuardrail401.Type> - | OpenRouterClientError<"DeleteGuardrail404", typeof DeleteGuardrail404.Type> - | OpenRouterClientError<"DeleteGuardrail500", typeof DeleteGuardrail500.Type> + | OpenRouterClientError<"CreateVideos400", typeof CreateVideos400.Type> + | OpenRouterClientError<"CreateVideos401", typeof CreateVideos401.Type> + | OpenRouterClientError<"CreateVideos402", typeof CreateVideos402.Type> + | OpenRouterClientError<"CreateVideos404", typeof CreateVideos404.Type> + | OpenRouterClientError<"CreateVideos429", typeof CreateVideos429.Type> + | OpenRouterClientError<"CreateVideos500", typeof CreateVideos500.Type> > /** - * Update an existing guardrail. [Management key](/docs/guides/overview/auth/management-api-keys) required. + * Returns job status and content URLs when completed */ - readonly "updateGuardrail": ( - id: string, - options: { readonly payload: typeof UpdateGuardrailRequestJson.Encoded; readonly config?: Config | undefined } + readonly "getVideos": ( + jobId: string, + options: + | { readonly params?: typeof GetVideosParams.Encoded | undefined; readonly config?: Config | undefined } + | undefined ) => Effect.Effect< - WithOptionalResponse, + WithOptionalResponse, | HttpClientError.HttpClientError | SchemaError - | OpenRouterClientError<"UpdateGuardrail400", typeof UpdateGuardrail400.Type> - | OpenRouterClientError<"UpdateGuardrail401", typeof UpdateGuardrail401.Type> - | OpenRouterClientError<"UpdateGuardrail404", typeof UpdateGuardrail404.Type> - | OpenRouterClientError<"UpdateGuardrail500", typeof UpdateGuardrail500.Type> + | OpenRouterClientError<"GetVideos401", typeof GetVideos401.Type> + | OpenRouterClientError<"GetVideos404", typeof GetVideos404.Type> + | OpenRouterClientError<"GetVideos500", typeof GetVideos500.Type> > /** - * List all API key guardrail assignments for the authenticated user. [Management key](/docs/guides/overview/auth/management-api-keys) required. + * Streams the generated video content from the upstream provider */ - readonly "listKeyAssignments": ( + readonly "listVideosContent": ( + jobId: string, options: { - readonly params?: typeof ListKeyAssignmentsParams.Encoded | undefined + readonly params?: typeof ListVideosContentParams.Encoded | undefined readonly config?: Config | undefined } | undefined ) => Effect.Effect< - WithOptionalResponse, + WithOptionalResponse, | HttpClientError.HttpClientError | SchemaError - | OpenRouterClientError<"ListKeyAssignments401", typeof ListKeyAssignments401.Type> - | OpenRouterClientError<"ListKeyAssignments500", typeof ListKeyAssignments500.Type> + | OpenRouterClientError<"ListVideosContent400", typeof ListVideosContent400.Type> + | OpenRouterClientError<"ListVideosContent401", typeof ListVideosContent401.Type> + | OpenRouterClientError<"ListVideosContent404", typeof ListVideosContent404.Type> + | OpenRouterClientError<"ListVideosContent500", typeof ListVideosContent500.Type> + | OpenRouterClientError<"ListVideosContent502", typeof ListVideosContent502.Type> > /** - * List all organization member guardrail assignments for the authenticated user. [Management key](/docs/guides/overview/auth/management-api-keys) required. + * Returns a list of all available video generation models and their properties */ - readonly "listMemberAssignments": ( + readonly "listVideosModels": ( options: { - readonly params?: typeof ListMemberAssignmentsParams.Encoded | undefined + readonly params?: typeof ListVideosModelsParams.Encoded | undefined readonly config?: Config | undefined } | undefined ) => Effect.Effect< - WithOptionalResponse, + WithOptionalResponse, | HttpClientError.HttpClientError | SchemaError - | OpenRouterClientError<"ListMemberAssignments401", typeof ListMemberAssignments401.Type> - | OpenRouterClientError<"ListMemberAssignments500", typeof ListMemberAssignments500.Type> + | OpenRouterClientError<"ListVideosModels400", typeof ListVideosModels400.Type> + | OpenRouterClientError<"ListVideosModels500", typeof ListVideosModels500.Type> > /** - * List all API key assignments for a specific guardrail. [Management key](/docs/guides/overview/auth/management-api-keys) required. + * List all workspaces for the authenticated user. [Management key](/docs/guides/overview/auth/management-api-keys) required. */ - readonly "listGuardrailKeyAssignments": ( - id: string, - options: { - readonly params?: typeof ListGuardrailKeyAssignmentsParams.Encoded | undefined - readonly config?: Config | undefined - } | undefined + readonly "listWorkspaces": ( + options: + | { readonly params?: typeof ListWorkspacesParams.Encoded | undefined; readonly config?: Config | undefined } + | undefined ) => Effect.Effect< - WithOptionalResponse, + WithOptionalResponse, | HttpClientError.HttpClientError | SchemaError - | OpenRouterClientError<"ListGuardrailKeyAssignments401", typeof ListGuardrailKeyAssignments401.Type> - | OpenRouterClientError<"ListGuardrailKeyAssignments404", typeof ListGuardrailKeyAssignments404.Type> - | OpenRouterClientError<"ListGuardrailKeyAssignments500", typeof ListGuardrailKeyAssignments500.Type> + | OpenRouterClientError<"ListWorkspaces401", typeof ListWorkspaces401.Type> + | OpenRouterClientError<"ListWorkspaces500", typeof ListWorkspaces500.Type> > /** - * Assign multiple API keys to a specific guardrail. [Management key](/docs/guides/overview/auth/management-api-keys) required. + * Create a new workspace for the authenticated user. [Management key](/docs/guides/overview/auth/management-api-keys) required. */ - readonly "bulkAssignKeysToGuardrail": ( - id: string, + readonly "createWorkspace": ( options: { - readonly payload: typeof BulkAssignKeysToGuardrailRequestJson.Encoded + readonly params?: typeof CreateWorkspaceParams.Encoded | undefined + readonly payload: typeof CreateWorkspaceRequestJson.Encoded readonly config?: Config | undefined } ) => Effect.Effect< - WithOptionalResponse, + WithOptionalResponse, | HttpClientError.HttpClientError | SchemaError - | OpenRouterClientError<"BulkAssignKeysToGuardrail400", typeof BulkAssignKeysToGuardrail400.Type> - | OpenRouterClientError<"BulkAssignKeysToGuardrail401", typeof BulkAssignKeysToGuardrail401.Type> - | OpenRouterClientError<"BulkAssignKeysToGuardrail404", typeof BulkAssignKeysToGuardrail404.Type> - | OpenRouterClientError<"BulkAssignKeysToGuardrail500", typeof BulkAssignKeysToGuardrail500.Type> + | OpenRouterClientError<"CreateWorkspace400", typeof CreateWorkspace400.Type> + | OpenRouterClientError<"CreateWorkspace401", typeof CreateWorkspace401.Type> + | OpenRouterClientError<"CreateWorkspace403", typeof CreateWorkspace403.Type> + | OpenRouterClientError<"CreateWorkspace500", typeof CreateWorkspace500.Type> > /** - * List all organization member assignments for a specific guardrail. [Management key](/docs/guides/overview/auth/management-api-keys) required. + * Get a single workspace by ID or slug. [Management key](/docs/guides/overview/auth/management-api-keys) required. */ - readonly "listGuardrailMemberAssignments": ( + readonly "getWorkspace": ( + id: string, + options: + | { readonly params?: typeof GetWorkspaceParams.Encoded | undefined; readonly config?: Config | undefined } + | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"GetWorkspace401", typeof GetWorkspace401.Type> + | OpenRouterClientError<"GetWorkspace404", typeof GetWorkspace404.Type> + | OpenRouterClientError<"GetWorkspace500", typeof GetWorkspace500.Type> + > + /** + * Delete an existing workspace. The default workspace cannot be deleted. Workspaces with active API keys cannot be deleted; remove the keys first. [Management key](/docs/guides/overview/auth/management-api-keys) required. + */ + readonly "deleteWorkspace": ( id: string, options: { - readonly params?: typeof ListGuardrailMemberAssignmentsParams.Encoded | undefined + readonly params?: typeof DeleteWorkspaceParams.Encoded | undefined readonly config?: Config | undefined } | undefined ) => Effect.Effect< - WithOptionalResponse, + WithOptionalResponse, | HttpClientError.HttpClientError | SchemaError - | OpenRouterClientError<"ListGuardrailMemberAssignments401", typeof ListGuardrailMemberAssignments401.Type> - | OpenRouterClientError<"ListGuardrailMemberAssignments404", typeof ListGuardrailMemberAssignments404.Type> - | OpenRouterClientError<"ListGuardrailMemberAssignments500", typeof ListGuardrailMemberAssignments500.Type> + | OpenRouterClientError<"DeleteWorkspace400", typeof DeleteWorkspace400.Type> + | OpenRouterClientError<"DeleteWorkspace401", typeof DeleteWorkspace401.Type> + | OpenRouterClientError<"DeleteWorkspace403", typeof DeleteWorkspace403.Type> + | OpenRouterClientError<"DeleteWorkspace404", typeof DeleteWorkspace404.Type> + | OpenRouterClientError<"DeleteWorkspace500", typeof DeleteWorkspace500.Type> > /** - * Assign multiple organization members to a specific guardrail. [Management key](/docs/guides/overview/auth/management-api-keys) required. + * Update an existing workspace by ID or slug. [Management key](/docs/guides/overview/auth/management-api-keys) required. */ - readonly "bulkAssignMembersToGuardrail": ( + readonly "updateWorkspace": ( id: string, options: { - readonly payload: typeof BulkAssignMembersToGuardrailRequestJson.Encoded + readonly params?: typeof UpdateWorkspaceParams.Encoded | undefined + readonly payload: typeof UpdateWorkspaceRequestJson.Encoded readonly config?: Config | undefined } ) => Effect.Effect< - WithOptionalResponse, + WithOptionalResponse, | HttpClientError.HttpClientError | SchemaError - | OpenRouterClientError<"BulkAssignMembersToGuardrail400", typeof BulkAssignMembersToGuardrail400.Type> - | OpenRouterClientError<"BulkAssignMembersToGuardrail401", typeof BulkAssignMembersToGuardrail401.Type> - | OpenRouterClientError<"BulkAssignMembersToGuardrail404", typeof BulkAssignMembersToGuardrail404.Type> - | OpenRouterClientError<"BulkAssignMembersToGuardrail500", typeof BulkAssignMembersToGuardrail500.Type> + | OpenRouterClientError<"UpdateWorkspace400", typeof UpdateWorkspace400.Type> + | OpenRouterClientError<"UpdateWorkspace401", typeof UpdateWorkspace401.Type> + | OpenRouterClientError<"UpdateWorkspace403", typeof UpdateWorkspace403.Type> + | OpenRouterClientError<"UpdateWorkspace404", typeof UpdateWorkspace404.Type> + | OpenRouterClientError<"UpdateWorkspace500", typeof UpdateWorkspace500.Type> > /** - * Unassign multiple API keys from a specific guardrail. [Management key](/docs/guides/overview/auth/management-api-keys) required. + * List all budgets configured for a workspace. [Management key](/docs/guides/overview/auth/management-api-keys) required. */ - readonly "bulkUnassignKeysFromGuardrail": ( + readonly "listWorkspaceBudgets": ( id: string, options: { - readonly payload: typeof BulkUnassignKeysFromGuardrailRequestJson.Encoded + readonly params?: typeof ListWorkspaceBudgetsParams.Encoded | undefined readonly config?: Config | undefined - } + } | undefined ) => Effect.Effect< - WithOptionalResponse, + WithOptionalResponse, | HttpClientError.HttpClientError | SchemaError - | OpenRouterClientError<"BulkUnassignKeysFromGuardrail400", typeof BulkUnassignKeysFromGuardrail400.Type> - | OpenRouterClientError<"BulkUnassignKeysFromGuardrail401", typeof BulkUnassignKeysFromGuardrail401.Type> - | OpenRouterClientError<"BulkUnassignKeysFromGuardrail404", typeof BulkUnassignKeysFromGuardrail404.Type> - | OpenRouterClientError<"BulkUnassignKeysFromGuardrail500", typeof BulkUnassignKeysFromGuardrail500.Type> + | OpenRouterClientError<"ListWorkspaceBudgets401", typeof ListWorkspaceBudgets401.Type> + | OpenRouterClientError<"ListWorkspaceBudgets404", typeof ListWorkspaceBudgets404.Type> + | OpenRouterClientError<"ListWorkspaceBudgets500", typeof ListWorkspaceBudgets500.Type> > /** - * Unassign multiple organization members from a specific guardrail. [Management key](/docs/guides/overview/auth/management-api-keys) required. + * Create or update the budget for a given interval. Budget limits must strictly decrease as the interval narrows (lifetime > monthly > weekly > daily). [Management key](/docs/guides/overview/auth/management-api-keys) required. */ - readonly "bulkUnassignMembersFromGuardrail": ( + readonly "upsertWorkspaceBudget": ( id: string, + interval: string, options: { - readonly payload: typeof BulkUnassignMembersFromGuardrailRequestJson.Encoded + readonly params?: typeof UpsertWorkspaceBudgetParams.Encoded | undefined + readonly payload: typeof UpsertWorkspaceBudgetRequestJson.Encoded readonly config?: Config | undefined } ) => Effect.Effect< - WithOptionalResponse, + WithOptionalResponse, | HttpClientError.HttpClientError | SchemaError - | OpenRouterClientError<"BulkUnassignMembersFromGuardrail400", typeof BulkUnassignMembersFromGuardrail400.Type> - | OpenRouterClientError<"BulkUnassignMembersFromGuardrail401", typeof BulkUnassignMembersFromGuardrail401.Type> - | OpenRouterClientError<"BulkUnassignMembersFromGuardrail404", typeof BulkUnassignMembersFromGuardrail404.Type> - | OpenRouterClientError<"BulkUnassignMembersFromGuardrail500", typeof BulkUnassignMembersFromGuardrail500.Type> + | OpenRouterClientError<"UpsertWorkspaceBudget400", typeof UpsertWorkspaceBudget400.Type> + | OpenRouterClientError<"UpsertWorkspaceBudget401", typeof UpsertWorkspaceBudget401.Type> + | OpenRouterClientError<"UpsertWorkspaceBudget404", typeof UpsertWorkspaceBudget404.Type> + | OpenRouterClientError<"UpsertWorkspaceBudget500", typeof UpsertWorkspaceBudget500.Type> > /** - * Get information on the API key associated with the current authentication session + * Remove the budget for a given interval. [Management key](/docs/guides/overview/auth/management-api-keys) required. */ - readonly "getCurrentKey": ( - options: { readonly config?: Config | undefined } | undefined + readonly "deleteWorkspaceBudget": ( + id: string, + interval: string, + options: { + readonly params?: typeof DeleteWorkspaceBudgetParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined ) => Effect.Effect< - WithOptionalResponse, + WithOptionalResponse, | HttpClientError.HttpClientError | SchemaError - | OpenRouterClientError<"GetCurrentKey401", typeof GetCurrentKey401.Type> - | OpenRouterClientError<"GetCurrentKey500", typeof GetCurrentKey500.Type> + | OpenRouterClientError<"DeleteWorkspaceBudget401", typeof DeleteWorkspaceBudget401.Type> + | OpenRouterClientError<"DeleteWorkspaceBudget404", typeof DeleteWorkspaceBudget404.Type> + | OpenRouterClientError<"DeleteWorkspaceBudget500", typeof DeleteWorkspaceBudget500.Type> > /** - * Exchange an authorization code from the PKCE flow for a user-controlled API key + * List all members of a workspace. Returns paginated results. For the default workspace, returns all organization members (implicit membership). [Management key](/docs/guides/overview/auth/management-api-keys) required. */ - readonly "exchangeAuthCodeForAPIKey": ( + readonly "listWorkspaceMembers": ( + id: string, options: { - readonly payload: typeof ExchangeAuthCodeForAPIKeyRequestJson.Encoded + readonly params?: typeof ListWorkspaceMembersParams.Encoded | undefined readonly config?: Config | undefined - } + } | undefined ) => Effect.Effect< - WithOptionalResponse, + WithOptionalResponse, | HttpClientError.HttpClientError | SchemaError - | OpenRouterClientError<"ExchangeAuthCodeForAPIKey400", typeof ExchangeAuthCodeForAPIKey400.Type> - | OpenRouterClientError<"ExchangeAuthCodeForAPIKey403", typeof ExchangeAuthCodeForAPIKey403.Type> - | OpenRouterClientError<"ExchangeAuthCodeForAPIKey500", typeof ExchangeAuthCodeForAPIKey500.Type> + | OpenRouterClientError<"ListWorkspaceMembers401", typeof ListWorkspaceMembers401.Type> + | OpenRouterClientError<"ListWorkspaceMembers403", typeof ListWorkspaceMembers403.Type> + | OpenRouterClientError<"ListWorkspaceMembers404", typeof ListWorkspaceMembers404.Type> + | OpenRouterClientError<"ListWorkspaceMembers500", typeof ListWorkspaceMembers500.Type> > /** - * Create an authorization code for the PKCE flow to generate a user-controlled API key + * Add multiple organization members to a workspace. Members are assigned the same role they hold in the organization. [Management key](/docs/guides/overview/auth/management-api-keys) required. */ - readonly "createAuthKeysCode": ( - options: { readonly payload: typeof CreateAuthKeysCodeRequestJson.Encoded; readonly config?: Config | undefined } + readonly "bulkAddWorkspaceMembers": ( + id: string, + options: { + readonly params?: typeof BulkAddWorkspaceMembersParams.Encoded | undefined + readonly payload: typeof BulkAddWorkspaceMembersRequestJson.Encoded + readonly config?: Config | undefined + } ) => Effect.Effect< - WithOptionalResponse, + WithOptionalResponse, | HttpClientError.HttpClientError | SchemaError - | OpenRouterClientError<"CreateAuthKeysCode400", typeof CreateAuthKeysCode400.Type> - | OpenRouterClientError<"CreateAuthKeysCode401", typeof CreateAuthKeysCode401.Type> - | OpenRouterClientError<"CreateAuthKeysCode500", typeof CreateAuthKeysCode500.Type> + | OpenRouterClientError<"BulkAddWorkspaceMembers400", typeof BulkAddWorkspaceMembers400.Type> + | OpenRouterClientError<"BulkAddWorkspaceMembers401", typeof BulkAddWorkspaceMembers401.Type> + | OpenRouterClientError<"BulkAddWorkspaceMembers403", typeof BulkAddWorkspaceMembers403.Type> + | OpenRouterClientError<"BulkAddWorkspaceMembers404", typeof BulkAddWorkspaceMembers404.Type> + | OpenRouterClientError<"BulkAddWorkspaceMembers500", typeof BulkAddWorkspaceMembers500.Type> > /** - * Sends a request for a model response for the given chat conversation. Supports both streaming and non-streaming modes. + * Remove multiple members from a workspace. Members with active API keys in the workspace cannot be removed. SCIM-managed members cannot be removed; changes must be made in your identity provider. [Management key](/docs/guides/overview/auth/management-api-keys) required. */ - readonly "sendChatCompletionRequest": ( + readonly "bulkRemoveWorkspaceMembers": ( + id: string, options: { - readonly payload: typeof SendChatCompletionRequestRequestJson.Encoded + readonly params?: typeof BulkRemoveWorkspaceMembersParams.Encoded | undefined + readonly payload: typeof BulkRemoveWorkspaceMembersRequestJson.Encoded readonly config?: Config | undefined } ) => Effect.Effect< - WithOptionalResponse, + WithOptionalResponse, | HttpClientError.HttpClientError | SchemaError - | OpenRouterClientError<"SendChatCompletionRequest400", typeof SendChatCompletionRequest400.Type> - | OpenRouterClientError<"SendChatCompletionRequest401", typeof SendChatCompletionRequest401.Type> - | OpenRouterClientError<"SendChatCompletionRequest429", typeof SendChatCompletionRequest429.Type> - | OpenRouterClientError<"SendChatCompletionRequest500", typeof SendChatCompletionRequest500.Type> - > - /** - * Sends a request for a model response for the given chat conversation. Supports both streaming and non-streaming modes. - */ - readonly "sendChatCompletionRequestSse": ( - options: { readonly payload: typeof SendChatCompletionRequestRequestJson.Encoded } - ) => Stream.Stream< - { - readonly event: string - readonly id: string | undefined - readonly data: typeof SendChatCompletionRequest200Sse.Type - }, - HttpClientError.HttpClientError | SchemaError | Sse.Retry, - typeof SendChatCompletionRequest200Sse.DecodingServices + | OpenRouterClientError<"BulkRemoveWorkspaceMembers400", typeof BulkRemoveWorkspaceMembers400.Type> + | OpenRouterClientError<"BulkRemoveWorkspaceMembers401", typeof BulkRemoveWorkspaceMembers401.Type> + | OpenRouterClientError<"BulkRemoveWorkspaceMembers403", typeof BulkRemoveWorkspaceMembers403.Type> + | OpenRouterClientError<"BulkRemoveWorkspaceMembers404", typeof BulkRemoveWorkspaceMembers404.Type> + | OpenRouterClientError<"BulkRemoveWorkspaceMembers500", typeof BulkRemoveWorkspaceMembers500.Type> > } diff --git a/repos/effect/packages/ai/openrouter/src/OpenRouterClient.ts b/repos/effect/packages/ai/openrouter/src/OpenRouterClient.ts index ec461a1ebe..8a2e664292 100644 --- a/repos/effect/packages/ai/openrouter/src/OpenRouterClient.ts +++ b/repos/effect/packages/ai/openrouter/src/OpenRouterClient.ts @@ -37,21 +37,21 @@ import { OpenRouterConfig } from "./OpenRouterConfig.ts" * Provides methods for interacting with OpenRouter's Chat Completions API, * including both synchronous and streaming message creation. * - * @category models + * @category services * @since 4.0.0 */ export interface Service { readonly client: Generated.OpenRouterClient readonly createChatCompletion: ( - options: typeof Generated.ChatGenerationParams.Encoded + options: typeof Generated.ChatRequest.Encoded ) => Effect.Effect< [body: typeof Generated.SendChatCompletionRequest200.Type, response: HttpClientResponse.HttpClientResponse], AiError.AiError > readonly createChatCompletionStream: ( - options: Omit + options: Omit ) => Effect.Effect< [ response: HttpClientResponse.HttpClientResponse, @@ -72,7 +72,7 @@ export interface Service { * @category models * @since 4.0.0 */ -export type ChatStreamingResponseChunkData = typeof Generated.ChatStreamingResponseChunk.fields.data.Type +export type ChatStreamingResponseChunkData = typeof Generated.ChatStreamingResponse.fields.data.Type // ============================================================================= // Service Identifier @@ -197,8 +197,18 @@ export const make = Effect.fnUntraced( Effect.catchTags({ SendChatCompletionRequest400: (error) => Effect.fail(Errors.mapClientError(error, "createChatCompletion")), SendChatCompletionRequest401: (error) => Effect.fail(Errors.mapClientError(error, "createChatCompletion")), + SendChatCompletionRequest402: (error) => Effect.fail(Errors.mapClientError(error, "createChatCompletion")), + SendChatCompletionRequest403: (error) => Effect.fail(Errors.mapClientError(error, "createChatCompletion")), + SendChatCompletionRequest404: (error) => Effect.fail(Errors.mapClientError(error, "createChatCompletion")), + SendChatCompletionRequest408: (error) => Effect.fail(Errors.mapClientError(error, "createChatCompletion")), + SendChatCompletionRequest413: (error) => Effect.fail(Errors.mapClientError(error, "createChatCompletion")), + SendChatCompletionRequest422: (error) => Effect.fail(Errors.mapClientError(error, "createChatCompletion")), SendChatCompletionRequest429: (error) => Effect.fail(Errors.mapClientError(error, "createChatCompletion")), SendChatCompletionRequest500: (error) => Effect.fail(Errors.mapClientError(error, "createChatCompletion")), + SendChatCompletionRequest502: (error) => Effect.fail(Errors.mapClientError(error, "createChatCompletion")), + SendChatCompletionRequest503: (error) => Effect.fail(Errors.mapClientError(error, "createChatCompletion")), + SendChatCompletionRequest524: (error) => Effect.fail(Errors.mapClientError(error, "createChatCompletion")), + SendChatCompletionRequest529: (error) => Effect.fail(Errors.mapClientError(error, "createChatCompletion")), HttpClientError: (error) => Errors.mapHttpClientError(error, "createChatCompletion"), SchemaError: (error) => Effect.fail(Errors.mapSchemaError(error, "createChatCompletion")) }) @@ -218,6 +228,7 @@ export const make = Effect.fnUntraced( Stream.catchTags({ // TODO: handle SSE retries Retry: (error) => Stream.die(error), + SseError: (error) => Stream.fail(Errors.mapSseError(error, "createChatCompletionStream")), HttpClientError: (error) => Stream.fromEffect(Errors.mapHttpClientError(error, "createChatCompletionStream")), SchemaError: (error) => Stream.fail(Errors.mapSchemaError(error, "createChatCompletionStream")) }) @@ -346,7 +357,7 @@ export const layerConfig = (options?: { // Internal Utilities // ============================================================================= -const ChatStreamingResponseChunkDataFromString = Schema.fromJsonString(Generated.ChatStreamingResponseChunk.fields.data) +const ChatStreamingResponseChunkDataFromString = Schema.fromJsonString(Generated.ChatStreamingResponse.fields.data) const decodeChatStreamingResponseChunkData = Schema.decodeUnknownEffect(ChatStreamingResponseChunkDataFromString) const decodeChatCompletionSseData = ( diff --git a/repos/effect/packages/ai/openrouter/src/OpenRouterConfig.ts b/repos/effect/packages/ai/openrouter/src/OpenRouterConfig.ts index 7ca9978726..11df7e7e13 100644 --- a/repos/effect/packages/ai/openrouter/src/OpenRouterConfig.ts +++ b/repos/effect/packages/ai/openrouter/src/OpenRouterConfig.ts @@ -50,7 +50,7 @@ export declare namespace OpenRouterConfig { * Configuration values read by OpenRouter provider operations when resolving * the generated HTTP client. * - * @category models + * @category services * @since 4.0.0 */ export interface Service { diff --git a/repos/effect/packages/ai/openrouter/src/OpenRouterError.ts b/repos/effect/packages/ai/openrouter/src/OpenRouterError.ts index 00db3860d3..63d0555e04 100644 --- a/repos/effect/packages/ai/openrouter/src/OpenRouterError.ts +++ b/repos/effect/packages/ai/openrouter/src/OpenRouterError.ts @@ -51,7 +51,7 @@ declare module "effect/unstable/ai/AiError" { * information from responses where the provider rejected the request because * a limit was reached. * - * @category configuration + * @category models * @since 4.0.0 */ export interface RateLimitErrorMetadata { @@ -69,7 +69,7 @@ declare module "effect/unstable/ai/AiError" { * Preserves provider error details for failures caused by exhausted account, * billing, or usage quota. * - * @category configuration + * @category models * @since 4.0.0 */ export interface QuotaExhaustedErrorMetadata { @@ -87,7 +87,7 @@ declare module "effect/unstable/ai/AiError" { * Preserves provider error details for failed API key, authorization, or * permission checks. * - * @category configuration + * @category models * @since 4.0.0 */ export interface AuthenticationErrorMetadata { @@ -105,7 +105,7 @@ declare module "effect/unstable/ai/AiError" { * Preserves provider error details when OpenRouter rejects input or output * because it violates a content policy. * - * @category configuration + * @category models * @since 4.0.0 */ export interface ContentPolicyErrorMetadata { @@ -123,7 +123,7 @@ declare module "effect/unstable/ai/AiError" { * Preserves provider error details for malformed requests, unsupported * parameters, or other request validation failures reported by OpenRouter. * - * @category configuration + * @category models * @since 4.0.0 */ export interface InvalidRequestErrorMetadata { @@ -141,7 +141,7 @@ declare module "effect/unstable/ai/AiError" { * Preserves provider error details for OpenRouter-side failures such as * transient server errors or overload responses. * - * @category configuration + * @category models * @since 4.0.0 */ export interface InternalProviderErrorMetadata { @@ -159,7 +159,7 @@ declare module "effect/unstable/ai/AiError" { * Preserves provider error details when an OpenRouter response cannot be * parsed or validated as the expected output. * - * @category configuration + * @category models * @since 4.0.0 */ export interface InvalidOutputErrorMetadata { @@ -177,7 +177,7 @@ declare module "effect/unstable/ai/AiError" { * Preserves provider error details when OpenRouter returns content that does * not satisfy the requested structured output schema. * - * @category configuration + * @category models * @since 4.0.0 */ export interface StructuredOutputErrorMetadata { @@ -195,7 +195,7 @@ declare module "effect/unstable/ai/AiError" { * Preserves provider error details when an unsupported schema failure is * associated with an OpenRouter response. * - * @category configuration + * @category models * @since 4.0.0 */ export interface UnsupportedSchemaErrorMetadata { @@ -213,7 +213,7 @@ declare module "effect/unstable/ai/AiError" { * Preserves provider error details for OpenRouter failures that do not map * cleanly to a more specific AI error category. * - * @category configuration + * @category models * @since 4.0.0 */ export interface UnknownErrorMetadata { diff --git a/repos/effect/packages/ai/openrouter/src/OpenRouterLanguageModel.ts b/repos/effect/packages/ai/openrouter/src/OpenRouterLanguageModel.ts index f937ca4e7c..ffe75b739f 100644 --- a/repos/effect/packages/ai/openrouter/src/OpenRouterLanguageModel.ts +++ b/repos/effect/packages/ai/openrouter/src/OpenRouterLanguageModel.ts @@ -62,7 +62,7 @@ export class Config extends Context.Service< Simplify< & Partial< Omit< - typeof Generated.ChatGenerationParams.Encoded, + typeof Generated.ChatRequest.Encoded, "messages" | "response_format" | "tools" | "tool_choice" | "stream" | "stream_options" > > @@ -89,7 +89,7 @@ export class Config extends Context.Service< * @category models * @since 4.0.0 */ -export type ReasoningDetails = Exclude +export type ReasoningDetails = Exclude /** * File annotations emitted on OpenRouter assistant messages and exposed in @@ -99,7 +99,7 @@ export type ReasoningDetails = Exclude[number], + NonNullable[number], { type: "file" } > @@ -112,7 +112,7 @@ declare module "effect/unstable/ai/Prompt" { * These options are used when translating system instructions into * OpenRouter chat messages. * - * @category request + * @category models * @since 4.0.0 */ export interface SystemMessageOptions extends ProviderOptions { @@ -123,7 +123,7 @@ declare module "effect/unstable/ai/Prompt" { /** * A breakpoint which marks the end of reusable content eligible for caching. */ - readonly cacheControl?: typeof Generated.ChatMessageContentItemCacheControl.Encoded | null + readonly cacheControl?: typeof Generated.ChatContentCacheControl.Encoded | null } | null } @@ -135,7 +135,7 @@ declare module "effect/unstable/ai/Prompt" { * These options are used when translating user content into OpenRouter chat * messages. * - * @category request + * @category models * @since 4.0.0 */ export interface UserMessageOptions extends ProviderOptions { @@ -146,7 +146,7 @@ declare module "effect/unstable/ai/Prompt" { /** * A breakpoint which marks the end of reusable content eligible for caching. */ - readonly cacheControl?: typeof Generated.ChatMessageContentItemCacheControl.Encoded | null + readonly cacheControl?: typeof Generated.ChatContentCacheControl.Encoded | null } | null } @@ -158,7 +158,7 @@ declare module "effect/unstable/ai/Prompt" { * Preserves reasoning metadata when assistant messages are replayed in later * OpenRouter requests. * - * @category request + * @category models * @since 4.0.0 */ export interface AssistantMessageOptions extends ProviderOptions { @@ -169,7 +169,7 @@ declare module "effect/unstable/ai/Prompt" { /** * A breakpoint which marks the end of reusable content eligible for caching. */ - readonly cacheControl?: typeof Generated.ChatMessageContentItemCacheControl.Encoded | null + readonly cacheControl?: typeof Generated.ChatContentCacheControl.Encoded | null /** * Reasoning details associated with the assistant message. */ @@ -185,7 +185,7 @@ declare module "effect/unstable/ai/Prompt" { * These options are used when converting tool results into OpenRouter chat * messages. * - * @category request + * @category models * @since 4.0.0 */ export interface ToolMessageOptions extends ProviderOptions { @@ -196,7 +196,7 @@ declare module "effect/unstable/ai/Prompt" { /** * A breakpoint which marks the end of reusable content eligible for caching. */ - readonly cacheControl?: typeof Generated.ChatMessageContentItemCacheControl.Encoded | null + readonly cacheControl?: typeof Generated.ChatContentCacheControl.Encoded | null } | null } @@ -207,7 +207,7 @@ declare module "effect/unstable/ai/Prompt" { * * Use when you use these options to control how text content is sent to OpenRouter. * - * @category request + * @category models * @since 4.0.0 */ export interface TextPartOptions extends ProviderOptions { @@ -218,7 +218,7 @@ declare module "effect/unstable/ai/Prompt" { /** * A breakpoint which marks the end of reusable content eligible for caching. */ - readonly cacheControl?: typeof Generated.ChatMessageContentItemCacheControl.Encoded | null + readonly cacheControl?: typeof Generated.ChatContentCacheControl.Encoded | null } | null } @@ -230,7 +230,7 @@ declare module "effect/unstable/ai/Prompt" { * Preserves provider reasoning blocks so reasoning-aware conversations can * continue across OpenRouter requests. * - * @category request + * @category models * @since 4.0.0 */ export interface ReasoningPartOptions extends ProviderOptions { @@ -241,7 +241,7 @@ declare module "effect/unstable/ai/Prompt" { /** * A breakpoint which marks the end of reusable content eligible for caching. */ - readonly cacheControl?: typeof Generated.ChatMessageContentItemCacheControl.Encoded | null + readonly cacheControl?: typeof Generated.ChatContentCacheControl.Encoded | null /** * Reasoning details associated with the reasoning part. */ @@ -256,7 +256,7 @@ declare module "effect/unstable/ai/Prompt" { * * Controls file naming and prompt caching for files sent to OpenRouter. * - * @category request + * @category models * @since 4.0.0 */ export interface FilePartOptions extends ProviderOptions { @@ -272,7 +272,7 @@ declare module "effect/unstable/ai/Prompt" { /** * A breakpoint which marks the end of reusable content eligible for caching. */ - readonly cacheControl?: typeof Generated.ChatMessageContentItemCacheControl.Encoded | null + readonly cacheControl?: typeof Generated.ChatContentCacheControl.Encoded | null } | null } @@ -284,7 +284,7 @@ declare module "effect/unstable/ai/Prompt" { * Preserves reasoning details associated with tool calls when a conversation * is sent back to OpenRouter. * - * @category request + * @category models * @since 4.0.0 */ export interface ToolCallPartOptions extends ProviderOptions { @@ -306,7 +306,7 @@ declare module "effect/unstable/ai/Prompt" { * * Controls prompt caching for tool results sent to OpenRouter. * - * @category request + * @category models * @since 4.0.0 */ export interface ToolResultPartOptions extends ProviderOptions { @@ -317,7 +317,7 @@ declare module "effect/unstable/ai/Prompt" { /** * A breakpoint which marks the end of reusable content eligible for caching. */ - readonly cacheControl?: typeof Generated.ChatMessageContentItemCacheControl.Encoded | null + readonly cacheControl?: typeof Generated.ChatContentCacheControl.Encoded | null } | null } } @@ -330,7 +330,7 @@ declare module "effect/unstable/ai/Response" { * * Preserves provider reasoning details that can be sent back in later turns. * - * @category response + * @category models * @since 4.0.0 */ export interface ReasoningPartMetadata extends ProviderMetadata { @@ -352,7 +352,7 @@ declare module "effect/unstable/ai/Response" { * * Carries the first reasoning detail chunk when OpenRouter exposes one. * - * @category response + * @category models * @since 4.0.0 */ export interface ReasoningStartPartMetadata extends ProviderMetadata { @@ -374,7 +374,7 @@ declare module "effect/unstable/ai/Response" { * * Carries provider reasoning detail chunks as they arrive from OpenRouter. * - * @category response + * @category models * @since 4.0.0 */ export interface ReasoningDeltaPartMetadata extends ProviderMetadata { @@ -397,7 +397,7 @@ declare module "effect/unstable/ai/Response" { * Associates tool calls with provider reasoning details when the model emits * reasoning and tool calls together. * - * @category response + * @category models * @since 4.0.0 */ export interface ToolCallPartMetadata extends ProviderMetadata { @@ -420,7 +420,7 @@ declare module "effect/unstable/ai/Response" { * Includes citation text and offsets returned by providers that support URL * annotations. * - * @category response + * @category models * @since 4.0.0 */ export interface UrlSourcePartMetadata extends ProviderMetadata { @@ -451,7 +451,7 @@ declare module "effect/unstable/ai/Response" { * Exposes provider response details that are not represented by the common * Effect AI finish part fields. * - * @category response + * @category models * @since 4.0.0 */ export interface FinishPartMetadata extends ProviderMetadata { @@ -466,7 +466,7 @@ declare module "effect/unstable/ai/Response" { /** * Raw token usage reported by OpenRouter. */ - readonly usage?: typeof Generated.ChatGenerationTokenUsage.Encoded | null + readonly usage?: typeof Generated.ChatUsage.Encoded | null /** * File annotations returned by the provider. */ @@ -554,11 +554,11 @@ export const make = Effect.fnUntraced(function*({ model, config: providerConfig function*({ config, options }: { readonly config: typeof Config.Service readonly options: LanguageModel.ProviderOptions - }): Effect.fn.Return { + }): Effect.fn.Return { const messages = yield* prepareMessages({ options }) const { tools, toolChoice } = yield* prepareTools({ options, transformer: codecTransformer }) const responseFormat = yield* getResponseFormat({ config, options, transformer: codecTransformer }) - const request: typeof Generated.ChatGenerationParams.Encoded = { + const request: typeof Generated.ChatRequest.Encoded = { ...config, messages, ...(Predicate.isNotUndefined(responseFormat) ? { response_format: responseFormat } : undefined), @@ -667,8 +667,8 @@ export const withConfigOverride: { const prepareMessages = Effect.fnUntraced( function*({ options }: { readonly options: LanguageModel.ProviderOptions - }): Effect.fn.Return, AiError.AiError> { - const messages: Array = [] + }): Effect.fn.Return, AiError.AiError> { + const messages: Array = [] const reasoningDetailsTracker = new ReasoningDetailsDuplicateTracker() @@ -690,7 +690,7 @@ const prepareMessages = Effect.fnUntraced( } case "user": { - const content: Array = [] + const content: Array = [] // Get the message-level cache control const messageCacheControl = getCacheControl(message) @@ -824,7 +824,7 @@ const prepareMessages = Effect.fnUntraced( case "assistant": { let text = "" let reasoning = "" - const toolCalls: Array = [] + const toolCalls: Array = [] for (const part of message.content) { switch (part.type) { @@ -1320,7 +1320,7 @@ const makeStreamResponse = Effect.fnUntraced( // The signature typically arrives in the last reasoning delta, // but reasoning-start only carries the first delta's metadata. metadata: accumulatedReasoningDetails.length > 0 - ? { openRouter: { reasoningDetails: accumulatedReasoningDetails } } + ? { openrouter: { reasoningDetails: accumulatedReasoningDetails } } : undefined }) reasoningStarted = false @@ -1361,7 +1361,7 @@ const makeStreamResponse = Effect.fnUntraced( ? { startIndex: annotation.url_citation.start_index } : undefined), ...(Predicate.isNotUndefined(annotation.url_citation.end_index) - ? { startIndex: annotation.url_citation.end_index } + ? { endIndex: annotation.url_citation.end_index } : undefined) } } @@ -1377,6 +1377,7 @@ const makeStreamResponse = Effect.fnUntraced( for (const toolCall of toolCalls) { const index = toolCall.index ?? toolCalls.length - 1 let activeToolCall = activeToolCalls[index] + const argumentsDelta = toolCall.function?.arguments ?? "" // Tool call start - OpenRouter returns all information except the // tool call parameters in the first chunk @@ -1415,7 +1416,7 @@ const makeStreamResponse = Effect.fnUntraced( id: toolCall.id, type: "function", name: toolCall.function.name, - params: toolCall.function.arguments ?? "" + params: argumentsDelta } activeToolCalls[index] = activeToolCall @@ -1425,23 +1426,16 @@ const makeStreamResponse = Effect.fnUntraced( id: activeToolCall.id, name: activeToolCall.name }) - - // Emit a tool call delta part if parameters were also sent - if (activeToolCall.params.length > 0) { - parts.push({ - type: "tool-params-delta", - id: activeToolCall.id, - delta: activeToolCall.params - }) - } } else { - // If an active tool call was found, update and emit the delta for - // the tool call's parameters - activeToolCall.params += toolCall.function?.arguments ?? "" + activeToolCall.params += argumentsDelta + } + + // Emit a tool call delta part if parameters were also sent + if (argumentsDelta.length > 0) { parts.push({ type: "tool-params-delta", id: activeToolCall.id, - delta: activeToolCall.params + delta: argumentsDelta }) } @@ -1588,8 +1582,8 @@ const prepareTools = Effect.fnUntraced( readonly options: LanguageModel.ProviderOptions readonly transformer: LanguageModel.CodecTransformer }): Effect.fn.Return<{ - readonly tools: ReadonlyArray | undefined - readonly toolChoice: typeof Generated.ToolChoiceOption.Encoded | undefined + readonly tools: ReadonlyArray | undefined + readonly toolChoice: typeof Generated.ChatToolChoice.Encoded | undefined }, AiError.AiError> { if (options.tools.length === 0) { return { tools: undefined, toolChoice: undefined } @@ -1607,12 +1601,12 @@ const prepareTools = Effect.fnUntraced( }) } - let tools: Array = [] - let toolChoice: typeof Generated.ToolChoiceOption.Encoded | undefined = undefined + let tools: Array> = [] + let toolChoice: typeof Generated.ChatToolChoice.Encoded | undefined = undefined for (const tool of options.tools) { const description = Tool.getDescription(tool) - const parameters = yield* tryJsonSchema(tool.parametersSchema, "prepareTools", transformer) + const parameters = yield* tryToolJsonSchema(tool, "prepareTools", transformer) const strict = Tool.getStrictMode(tool) ?? null tools.push({ @@ -1650,7 +1644,7 @@ const prepareTools = Effect.fnUntraced( const annotateRequest = ( span: Span, - request: typeof Generated.ChatGenerationParams.Encoded + request: typeof Generated.ChatRequest.Encoded ): void => { addGenAIAnnotations(span, { system: "openrouter", @@ -1717,7 +1711,7 @@ const getCacheControl = ( | Prompt.ReasoningPart | Prompt.FilePart | Prompt.ToolResultPart -): typeof Generated.ChatMessageContentItemCacheControl.Encoded | null => part.options.openrouter?.cacheControl ?? null +): typeof Generated.ChatContentCacheControl.Encoded | null => part.options.openrouter?.cacheControl ?? null const findFirstReasoningDetails = (content: ReadonlyArray): ReasoningDetails | null => { for (const part of content) { @@ -1776,11 +1770,17 @@ const tryJsonSchema = ( catch: (error) => unsupportedSchemaError(error, method) }) +const tryToolJsonSchema = (tool: T, method: string, transformer: LanguageModel.CodecTransformer) => + Effect.try({ + try: () => Tool.getJsonSchema(tool, { transformer }), + catch: (error) => unsupportedSchemaError(error, method) + }) + const getResponseFormat = Effect.fnUntraced(function*({ config, options, transformer }: { readonly config: typeof Config.Service readonly options: LanguageModel.ProviderOptions readonly transformer: LanguageModel.CodecTransformer -}): Effect.fn.Return { +}): Effect.fn.Return { if (options.responseFormat.type === "json") { const description = SchemaAST.resolveDescription(options.responseFormat.schema.ast) const jsonSchema = yield* tryJsonSchema(options.responseFormat.schema, "getResponseFormat", transformer) @@ -1833,7 +1833,7 @@ const getBase64FromDataUrl = (dataUrl: string): string => { return match ? match[1]! : dataUrl } -const getUsage = (usage: Generated.ChatGenerationTokenUsage | undefined): Response.Usage => { +const getUsage = (usage: Generated.ChatUsage | undefined): Response.Usage => { if (Predicate.isUndefined(usage)) { return { inputTokens: { uncached: undefined, total: 0, cacheRead: undefined, cacheWrite: undefined }, diff --git a/repos/effect/packages/ai/openrouter/src/internal/errors.ts b/repos/effect/packages/ai/openrouter/src/internal/errors.ts index d01128e128..65c10cdf4f 100644 --- a/repos/effect/packages/ai/openrouter/src/internal/errors.ts +++ b/repos/effect/packages/ai/openrouter/src/internal/errors.ts @@ -8,6 +8,7 @@ import * as Redactable from "effect/Redactable" import * as Schema from "effect/Schema" import * as AiError from "effect/unstable/ai/AiError" import type * as Response from "effect/unstable/ai/Response" +import type * as Sse from "effect/unstable/encoding/Sse" import type * as HttpClientError from "effect/unstable/http/HttpClientError" import type * as HttpClientRequest from "effect/unstable/http/HttpClientRequest" import type * as HttpClientResponse from "effect/unstable/http/HttpClientResponse" @@ -23,7 +24,7 @@ export const OpenRouterErrorBody = Schema.Struct({ error: Schema.Struct({ message: Schema.String, type: Schema.optional(Schema.NullOr(Schema.String)), - code: Schema.optional(Schema.NullOr(Schema.Union([Schema.String, Schema.Number.check(Schema.isFinite())]))) + code: Schema.optional(Schema.NullOr(Schema.Union([Schema.String, Schema.Finite]))) }) }) @@ -52,6 +53,17 @@ export const mapSchemaError = dual< reason: AiError.InvalidOutputError.fromSchemaError(error) })) +/** @internal */ +export const mapSseError = dual< + (method: string) => (error: Sse.SseError) => AiError.AiError, + (error: Sse.SseError, method: string) => AiError.AiError +>(2, (error, method) => + AiError.make({ + module: "OpenRouterClient", + method, + reason: new AiError.InvalidOutputError({ description: error.message }) + })) + /** @internal */ export const mapClientError = dual< (method: string) => (error: Generated.OpenRouterClientError) => AiError.AiError, diff --git a/repos/effect/packages/ai/openrouter/src/internal/utilities.ts b/repos/effect/packages/ai/openrouter/src/internal/utilities.ts index 5df64c53ba..1d5112e943 100644 --- a/repos/effect/packages/ai/openrouter/src/internal/utilities.ts +++ b/repos/effect/packages/ai/openrouter/src/internal/utilities.ts @@ -14,10 +14,12 @@ const finishReasonMap: Record = { /** @internal */ export const resolveFinishReason = ( finishReason: string | null | undefined -): Response.FinishReason => - Predicate.isNotNullish(finishReason) - ? finishReasonMap[finishReason] - : "other" +): Response.FinishReason => { + if (Predicate.isNullish(finishReason)) { + return "other" + } + return Object.hasOwn(finishReasonMap, finishReason) ? finishReasonMap[finishReason] : "unknown" +} /** * Tracks ReasoningDetailUnion entries and deduplicates them based diff --git a/repos/effect/packages/ai/openrouter/test/Generated.test.ts b/repos/effect/packages/ai/openrouter/test/Generated.test.ts new file mode 100644 index 0000000000..8790fed735 --- /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 0000000000..b9fef344cc --- /dev/null +++ b/repos/effect/packages/ai/openrouter/test/OpenRouterClient.test.ts @@ -0,0 +1,113 @@ +import { OpenRouterClient } from "@effect/ai-openrouter" +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" + } + } + })))) +}) + +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 + } + }) + ) +} diff --git a/repos/effect/packages/ai/openrouter/test/OpenRouterLanguageModel.test.ts b/repos/effect/packages/ai/openrouter/test/OpenRouterLanguageModel.test.ts index 2937111afa..ab6738fdda 100644 --- a/repos/effect/packages/ai/openrouter/test/OpenRouterLanguageModel.test.ts +++ b/repos/effect/packages/ai/openrouter/test/OpenRouterLanguageModel.test.ts @@ -1,8 +1,8 @@ import { Generated, OpenRouterClient, OpenRouterLanguageModel } from "@effect/ai-openrouter" import { assert, describe, it } from "@effect/vitest" import { deepStrictEqual, strictEqual } from "@effect/vitest/utils" -import { Array, Context, Effect, Layer, Redacted, Ref, Schema } from "effect" -import { LanguageModel, Prompt } from "effect/unstable/ai" +import { Array, Context, Effect, Layer, Redacted, Ref, Schema, Stream } from "effect" +import { LanguageModel, Prompt, Tool, Toolkit } from "effect/unstable/ai" import { HttpClient, type HttpClientError, type HttpClientRequest, HttpClientResponse } from "effect/unstable/http" describe("OpenRouterLanguageModel", () => { @@ -172,6 +172,178 @@ describe("OpenRouterLanguageModel", () => { }).pipe(Effect.provide(makeTestLayer()))) }) }) + + describe("tool preparation", () => { + it.effect("passes raw JSON schema for dynamic tools", () => + Effect.gen(function*() { + const inputSchema = { + type: "object", + properties: { + query: { type: "string" }, + limit: { type: "number" } + }, + required: ["query"], + additionalProperties: false + } as const + + const DynamicTool = Tool.dynamic("DynamicTool", { + description: "A dynamic tool", + parameters: inputSchema + }) + + yield* LanguageModel.generateText({ + prompt: "Use the dynamic tool", + toolkit: Toolkit.make(DynamicTool), + disableToolCallResolution: true + }).pipe(Effect.provide(OpenRouterLanguageModel.model("google/gemini-2.5-flash"))) + + const requests = yield* MockHttpClient.requests + const body = yield* getRequestBody(requests[0]) + + const tool = body.tools?.find((entry: any) => + entry.type === "function" && entry.function.name === "DynamicTool" + ) + assert.isDefined(tool) + strictEqual(tool.function.description, "A dynamic tool") + deepStrictEqual(tool.function.parameters, inputSchema) + }).pipe(Effect.provide(makeTestLayer()))) + }) + }) + + describe("streamText", () => { + it.effect("preserves streamed citation start and end indexes", () => + Effect.gen(function*() { + const parts = yield* LanguageModel.streamText({ prompt: "cite a source" }).pipe( + Stream.runCollect, + Effect.provide(OpenRouterLanguageModel.model("openai/gpt-4o-mini")), + Effect.provide(makeStreamTestLayer([{ + id: "response-1", + object: "chat.completion.chunk", + model: "openai/gpt-4o-mini", + created: 1, + choices: [{ + index: 0, + delta: { + annotations: [{ + type: "url_citation", + url_citation: { + url: "https://example.com/source", + title: "source", + start_index: 2, + end_index: 9 + } + }] + } + }] + }])) + ) + + const source = globalThis.Array.from(parts).find((part) => part.type === "source") + assert.isDefined(source) + if (source?.type === "source") { + assert.deepStrictEqual(source.metadata, { + openrouter: { startIndex: 2, endIndex: 9 } + }) + } + })) + + it.effect("uses lowercase openrouter reasoning-end metadata", () => + Effect.gen(function*() { + const reasoningDetails = [{ + type: "reasoning.text", + text: "thinking", + signature: "signature-final", + format: "unknown" + }] as const + const parts = yield* LanguageModel.streamText({ prompt: "reason then answer" }).pipe( + Stream.runCollect, + Effect.provide(OpenRouterLanguageModel.model("openai/gpt-4o-mini")), + Effect.provide(makeStreamTestLayer([ + { + id: "response-1", + object: "chat.completion.chunk", + model: "openai/gpt-4o-mini", + created: 1, + choices: [{ index: 0, delta: { reasoning_details: reasoningDetails } }] + }, + { + id: "response-1", + object: "chat.completion.chunk", + model: "openai/gpt-4o-mini", + created: 1, + choices: [{ index: 0, finish_reason: "stop", delta: { content: "answer" } }] + } + ])) + ) + + const reasoningEnd = parts.find((part) => part.type === "reasoning-end") + deepStrictEqual(reasoningEnd?.metadata, { openrouter: { reasoningDetails } }) + })) + + it.effect("emits incremental tool parameter fragments", () => + Effect.gen(function*() { + const ProbeTool = Tool.make("ProbeTool", { + parameters: Schema.Struct({ a: Schema.Number }), + success: Schema.String + }) + const toolkit = Toolkit.make(ProbeTool) + const parts = yield* LanguageModel.streamText({ + prompt: "call the tool", + toolkit, + disableToolCallResolution: true + }).pipe( + Stream.runCollect, + Effect.provide(OpenRouterLanguageModel.model("openai/gpt-4o-mini")), + Effect.provide(toolkit.toLayer({ ProbeTool: () => Effect.succeed("ok") })), + Effect.provide(makeStreamTestLayer([ + { + id: "response-1", + object: "chat.completion.chunk", + model: "openai/gpt-4o-mini", + created: 1, + choices: [{ + index: 0, + delta: { + tool_calls: [{ + index: 0, + id: "call-1", + type: "function", + function: { name: "ProbeTool", arguments: "{\"a\":" } + }] + } + }] + }, + { + id: "response-1", + object: "chat.completion.chunk", + model: "openai/gpt-4o-mini", + created: 1, + choices: [{ + index: 0, + delta: { tool_calls: [{ index: 0 }] } + }] + }, + { + id: "response-1", + object: "chat.completion.chunk", + model: "openai/gpt-4o-mini", + created: 1, + choices: [{ + index: 0, + finish_reason: "tool_calls", + delta: { tool_calls: [{ index: 0, function: { arguments: "1}" } }] } + }] + } + ])) + ) + + deepStrictEqual( + globalThis.Array.from(parts) + .filter((part) => part.type === "tool-params-delta") + .map((part) => part.delta), + ["{\"a\":", "1}"] + ) + })) }) }) @@ -237,6 +409,7 @@ const makeDefaultResponse = ( created: 1234567890, model: "google/gemini-2.5-flash", object: "chat.completion", + system_fingerprint: null, ...overrides }) @@ -263,3 +436,23 @@ const getRequestBody = (request: HttpClientRequest.HttpClientRequest) => } return yield* Effect.die(new Error("Expected Uint8Array body")) }) + +const makeStreamTestLayer = (events: ReadonlyArray) => { + const body = events.map((event) => `data: ${JSON.stringify(event)}\n\n`).join("") + "data: [DONE]\n\n" + const 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 index a9b59a3183..1cf21756b8 100644 --- a/repos/effect/packages/ai/openrouter/tsconfig.json +++ b/repos/effect/packages/ai/openrouter/tsconfig.json @@ -1,5 +1,5 @@ { - "$schema": "http://json.schemastore.org/tsconfig", + "$schema": "https://json.schemastore.org/tsconfig", "extends": "../../../tsconfig.base.json", "include": ["src"], "references": [ diff --git a/repos/effect/packages/ai/openrouter/vitest.config.ts b/repos/effect/packages/ai/openrouter/vitest.config.ts deleted file mode 100644 index c8a52c1826..0000000000 --- a/repos/effect/packages/ai/openrouter/vitest.config.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { mergeConfig, type ViteUserConfig } from "vitest/config" -import shared from "../../../vitest.shared.ts" - -const config: ViteUserConfig = {} - -export default mergeConfig(shared, config) diff --git a/repos/effect/packages/atom/react/CHANGELOG.md b/repos/effect/packages/atom/react/CHANGELOG.md index 03b89810ce..d59c7e7467 100644 --- a/repos/effect/packages/atom/react/CHANGELOG.md +++ b/repos/effect/packages/atom/react/CHANGELOG.md @@ -1,5 +1,129 @@ # @effect/atom-react +## 4.0.0-beta.107 + +### Patch Changes + +- Updated dependencies [[`596f3f9`](https://github.com/Effect-TS/effect/commit/596f3f92d7fe355811b815cb212332b082268ce8), [`9611ed4`](https://github.com/Effect-TS/effect/commit/9611ed42d11300546b339ab13492a0f7bdb1ebfb), [`8b91605`](https://github.com/Effect-TS/effect/commit/8b9160548556e4b0ec7ee2f2707716776be49018), [`d901928`](https://github.com/Effect-TS/effect/commit/d901928efa44f573ed1247f53fdb203a8e4fcede), [`b32bdef`](https://github.com/Effect-TS/effect/commit/b32bdef0d119a1ad1463dc01a46763ffee1f9bd9)]: + - effect@4.0.0-beta.107 + +## 4.0.0-beta.106 + +### Patch Changes + +- Updated dependencies [[`2695168`](https://github.com/Effect-TS/effect/commit/269516851b24916d72771f8a554b88722e3732e7), [`6310a8c`](https://github.com/Effect-TS/effect/commit/6310a8c68c74dcf1d23948ec9243ac5f407a1651), [`c2071b1`](https://github.com/Effect-TS/effect/commit/c2071b1647e2326568c1d0689274ef62b8a7183f), [`7aff81a`](https://github.com/Effect-TS/effect/commit/7aff81a9cefe681483ef8abf717d786fd10e7e8d), [`a1d4057`](https://github.com/Effect-TS/effect/commit/a1d4057711935a544ef441bc2d0ac3565dfa9266), [`abf77b0`](https://github.com/Effect-TS/effect/commit/abf77b04009dcb4d67a258f9d8ada778e9f4ffae), [`6c60375`](https://github.com/Effect-TS/effect/commit/6c60375e68683a32d54554150cc493e16550a06d), [`22f4897`](https://github.com/Effect-TS/effect/commit/22f4897bbae24783d4516f6bef353f1db4ec6d03), [`615d1d5`](https://github.com/Effect-TS/effect/commit/615d1d5d0256ec8160f2e08d0dcf5dc83acb7bf1), [`3a86757`](https://github.com/Effect-TS/effect/commit/3a867573ddeed5888dabdeb3225a9ebbf00491e7), [`f4a9762`](https://github.com/Effect-TS/effect/commit/f4a9762bb9dfad59c215f2e099dcc829d74f4ed1), [`0bcf6ed`](https://github.com/Effect-TS/effect/commit/0bcf6ed57c22e8a36964726b15464101d90f5997), [`ba9cb63`](https://github.com/Effect-TS/effect/commit/ba9cb63b87d45ce2df872dd8ef0905da147cc675), [`42c810d`](https://github.com/Effect-TS/effect/commit/42c810dd372275b822dd99c7d7e774e153f0a752), [`1416ccd`](https://github.com/Effect-TS/effect/commit/1416ccd474bc9da8979f51b72b5e53fb3ac56edf), [`08d0d39`](https://github.com/Effect-TS/effect/commit/08d0d39a225deccb9db213ab5fcf55edb9f9ba5d), [`548908a`](https://github.com/Effect-TS/effect/commit/548908a71d9337cb7defe7fc93b2fba8f6a04b6f), [`4b3460d`](https://github.com/Effect-TS/effect/commit/4b3460daa434ec465a95a50704fe1103a9275999), [`d170596`](https://github.com/Effect-TS/effect/commit/d17059615cca37ca2776654078fe0501ac5202e6), [`aea89d0`](https://github.com/Effect-TS/effect/commit/aea89d0c42ee0ac707a4962cd348fd3158cb469b), [`deed5fb`](https://github.com/Effect-TS/effect/commit/deed5fbdc91cf8bf8c5fce7dfa5d6527ac944726)]: + - effect@4.0.0-beta.106 + +## 4.0.0-beta.105 + +### Patch Changes + +- [#7094](https://github.com/Effect-TS/effect/pull/7094) [`31efc5c`](https://github.com/Effect-TS/effect/commit/31efc5c4eabbe37f9ddab030ef760926e6ff3d24) Thanks @fubhy! - Update peer dependencies +- Updated dependencies [[`0418564`](https://github.com/Effect-TS/effect/commit/04185644dabb8e4169f1ef6cbbc0b36c4db2f7f4), [`d334a85`](https://github.com/Effect-TS/effect/commit/d334a8593aafcd60753995a9449b654c67bfdcc1), [`f0be855`](https://github.com/Effect-TS/effect/commit/f0be8554da6ee00293a6b23869ac46a0b0d97dc8), [`b206fa5`](https://github.com/Effect-TS/effect/commit/b206fa5d7655c1634c9993410a9203f6616a5ca2), [`b938c8a`](https://github.com/Effect-TS/effect/commit/b938c8ad2823bd88493187922f7d9090eff037b6), [`8525f05`](https://github.com/Effect-TS/effect/commit/8525f05d1e14ea12298e9e1a0df497bfaac2ce9a)]: + - effect@4.0.0-beta.105 + +## 4.0.0-beta.104 + +### Patch Changes + +- Updated dependencies [[`1001bcc`](https://github.com/Effect-TS/effect/commit/1001bccb9e874918d59dbb36860f1c5d4499ac20), [`993ba60`](https://github.com/Effect-TS/effect/commit/993ba60ee6c7ca6eb84522040f8b0d268b6ba7d4), [`67faacd`](https://github.com/Effect-TS/effect/commit/67faacd4679242559bee31717c05a5b10b990322), [`b78acdf`](https://github.com/Effect-TS/effect/commit/b78acdf422568f10ae8684fd3f10d52b065f0b56), [`fbb9ce5`](https://github.com/Effect-TS/effect/commit/fbb9ce5e625d1a7d6b7005bda42cbb6cd31476c5), [`722ea48`](https://github.com/Effect-TS/effect/commit/722ea484c9d01364c9242d929c0a564f7831a57c), [`3058fd5`](https://github.com/Effect-TS/effect/commit/3058fd594f5a683034212d71d957017fcc084006), [`62d0575`](https://github.com/Effect-TS/effect/commit/62d057566c241405c23ecf0bf4156186bd2be924), [`99dd6b5`](https://github.com/Effect-TS/effect/commit/99dd6b580434f97c5b40adc919f429e4abc3dfe7), [`7963ce1`](https://github.com/Effect-TS/effect/commit/7963ce1cd95f037fbefea67a29ead49cce4d16cb), [`af14e75`](https://github.com/Effect-TS/effect/commit/af14e752edd65e2b652e960411afafc88975a8d8), [`24e22d2`](https://github.com/Effect-TS/effect/commit/24e22d23a73a2e93ebf6d8edd2246a4a406942c8), [`647d14e`](https://github.com/Effect-TS/effect/commit/647d14e572c8004fa92fba256e00552b42bf34b7), [`1434eec`](https://github.com/Effect-TS/effect/commit/1434eecbd368e00839c24b3950f0b7a69218669a), [`a5278b1`](https://github.com/Effect-TS/effect/commit/a5278b18242011d1b2b08304c7c128151f9a4370), [`6af04a5`](https://github.com/Effect-TS/effect/commit/6af04a50bd019238f6acdb9cbda40439a3c09210), [`cb6c837`](https://github.com/Effect-TS/effect/commit/cb6c8376b2f322d4e7cbfc0973fc3b4f2951ee6e), [`d44cead`](https://github.com/Effect-TS/effect/commit/d44cead7e0e0ce61f0d980906e494f49a07e7899), [`88c7632`](https://github.com/Effect-TS/effect/commit/88c7632c2b59a49fcc40d250865bd8d0dccf31b0), [`abcbb2a`](https://github.com/Effect-TS/effect/commit/abcbb2abe16f1b6c587c15007df14371e1e70e93), [`8f63cce`](https://github.com/Effect-TS/effect/commit/8f63cce636700fde26b140b82e350ef916989d86), [`d56dfcf`](https://github.com/Effect-TS/effect/commit/d56dfcf54c2b9c53c3d098ce4b0ffcc84496c5f7), [`a98cda9`](https://github.com/Effect-TS/effect/commit/a98cda9422e1352f22e81696f759f326ffcfb667), [`6704bb8`](https://github.com/Effect-TS/effect/commit/6704bb84c320547f83cf50e8586ffc4c5e4c3cc5), [`6143de2`](https://github.com/Effect-TS/effect/commit/6143de21ee22038b45a8d4eba86f5aade6238eba), [`936b135`](https://github.com/Effect-TS/effect/commit/936b1358396eb0a1a7c8e0878ba63297e2106812), [`1bbae84`](https://github.com/Effect-TS/effect/commit/1bbae84f88b577a26d04ceb2e76d3143d09c4a20), [`d795ee7`](https://github.com/Effect-TS/effect/commit/d795ee771701ea62bd187ef7c0307d9737f68c1a), [`0a82d88`](https://github.com/Effect-TS/effect/commit/0a82d88b7da73278b6f270118e396d5ed4a64747), [`9215bc5`](https://github.com/Effect-TS/effect/commit/9215bc5da7dd10aa45f07fe44b98f06b6e433d62), [`a1b5df2`](https://github.com/Effect-TS/effect/commit/a1b5df2064d92431cfc6e638af613cc3114313d7), [`92a9ac5`](https://github.com/Effect-TS/effect/commit/92a9ac5ac0aa63d8975b9ba7a094d6a8f59a98f2), [`6bde7f2`](https://github.com/Effect-TS/effect/commit/6bde7f27f3243427203e53fe74472990e5c2a349), [`a712131`](https://github.com/Effect-TS/effect/commit/a7121310dbb60cbd819bbd702f97663098ec7bb8), [`2e6f760`](https://github.com/Effect-TS/effect/commit/2e6f760dcb44e2b984f3311a8af03a1d68a2ec7e), [`aa05804`](https://github.com/Effect-TS/effect/commit/aa0580497e027ed30b756058db0067c3fe07664f), [`badd3bf`](https://github.com/Effect-TS/effect/commit/badd3bf65fac4dd1e66e1f602db43659722dfced), [`02b0265`](https://github.com/Effect-TS/effect/commit/02b02651ede46a5a2dd3ef8081d0ad89648d0cbf), [`3437e21`](https://github.com/Effect-TS/effect/commit/3437e21a56d805781c5e5946a6189795a1dfd411), [`41a550d`](https://github.com/Effect-TS/effect/commit/41a550d1fed31e829929a8f5362b5340303164ac), [`17b5d50`](https://github.com/Effect-TS/effect/commit/17b5d50219ad49533cf9e33d01924a3e16af5eb3), [`96e5e95`](https://github.com/Effect-TS/effect/commit/96e5e9576b0315c747462761a61940ff9fe32dd1), [`e4d589e`](https://github.com/Effect-TS/effect/commit/e4d589e0ea08dc57c4793053b395dc0fcc499f34), [`ae4cf7b`](https://github.com/Effect-TS/effect/commit/ae4cf7b5e2cb5f8c55657e31a61789ad21c38c18), [`6ef5f1a`](https://github.com/Effect-TS/effect/commit/6ef5f1a041f3a40bf03fadd0b1feb275c277c635), [`2235a29`](https://github.com/Effect-TS/effect/commit/2235a29502c3f33cf6468511ad931089013a7916), [`b32f4cb`](https://github.com/Effect-TS/effect/commit/b32f4cb7b2d8ebe817075322622498e3beb05336), [`7f4c095`](https://github.com/Effect-TS/effect/commit/7f4c095b62da43780dd7fc2a5d1785ddfce60edf), [`5f3fb81`](https://github.com/Effect-TS/effect/commit/5f3fb814d18d8a54946c1c1cd0b41459cdb24006), [`17f0b91`](https://github.com/Effect-TS/effect/commit/17f0b91a243ccfe4a38d27debdc983adf434e738), [`0cdadd7`](https://github.com/Effect-TS/effect/commit/0cdadd75bc8abbbcad7956a4bc71f4e7a9b13250), [`39b57d7`](https://github.com/Effect-TS/effect/commit/39b57d7857358040558b67dd33eafc7bb5457830), [`5a6a573`](https://github.com/Effect-TS/effect/commit/5a6a5738e5bfc39e3a37ae7ba99081601fa19ac3), [`59f5e99`](https://github.com/Effect-TS/effect/commit/59f5e9981913b92d7a9beb2214a21d658b999d3a), [`45379d6`](https://github.com/Effect-TS/effect/commit/45379d6179ee4df2cbd3f848bd39ff7149c24a38), [`1949439`](https://github.com/Effect-TS/effect/commit/1949439175809ef81ab9c6411ed5559109edb4c9), [`e443403`](https://github.com/Effect-TS/effect/commit/e443403cf0e4effea14bb6cd950c5ac1c86cc748), [`03af7e8`](https://github.com/Effect-TS/effect/commit/03af7e85551204c605ea2fa2c43c10a4538ac8fb), [`0f721d4`](https://github.com/Effect-TS/effect/commit/0f721d406df8703ea92ca28777b3f09599e2056d), [`130b28d`](https://github.com/Effect-TS/effect/commit/130b28df552d7053407b041a96ff09dae82575e5), [`c987a12`](https://github.com/Effect-TS/effect/commit/c987a12a01b6a52ad53d29edf02613b03574dbcc), [`4158562`](https://github.com/Effect-TS/effect/commit/41585620977de9b84171f76619b72e29cc2284e5), [`306014a`](https://github.com/Effect-TS/effect/commit/306014a1ce4d5cb956c76bdc20e4e28ab3e61a6a), [`729a663`](https://github.com/Effect-TS/effect/commit/729a663275dd31f2357c446fe69664429220a83d), [`caf84b6`](https://github.com/Effect-TS/effect/commit/caf84b660044089e8d7f4067b279b27b8b50e8fd), [`ce067f7`](https://github.com/Effect-TS/effect/commit/ce067f799ea27735d4194345298a216aaf429f01), [`7a41f5a`](https://github.com/Effect-TS/effect/commit/7a41f5aa72d540ecf2746992ecc3fa3e6b40d31f), [`781022a`](https://github.com/Effect-TS/effect/commit/781022acdd3537ca18c88e2fa3681bafa6ef1b21), [`39f1297`](https://github.com/Effect-TS/effect/commit/39f1297acc08864feb12de6b8cf2bf73434f6cf5), [`2db266b`](https://github.com/Effect-TS/effect/commit/2db266b1bfbc81868bc1778c37c76032a267c79f), [`2141e28`](https://github.com/Effect-TS/effect/commit/2141e28903754d72604acf81673ceb2c62a56646), [`3c5e429`](https://github.com/Effect-TS/effect/commit/3c5e429878669ffcf5e0da4ddfbf50bde5bbcaad), [`20ddc63`](https://github.com/Effect-TS/effect/commit/20ddc630584f8fe488162ba384adcae53fc6810a), [`841b3ea`](https://github.com/Effect-TS/effect/commit/841b3ea6ae19a784bc1c20497b02f632af0c91e9), [`82a3fbf`](https://github.com/Effect-TS/effect/commit/82a3fbfce8b9df33e587076b7d7168ecd6799e17), [`eb9ee83`](https://github.com/Effect-TS/effect/commit/eb9ee83b38844a71d1cd5653a229309cfcb04a36), [`64dc7c7`](https://github.com/Effect-TS/effect/commit/64dc7c76dc5c89887b9e7c181d1873dcbb7820d1), [`84dc8ab`](https://github.com/Effect-TS/effect/commit/84dc8ab7accc682bc668c78a97e4a1776b633be8), [`b4463f4`](https://github.com/Effect-TS/effect/commit/b4463f46fc33d3b01ea5eadd7d012a5abda347a3), [`592dd36`](https://github.com/Effect-TS/effect/commit/592dd361645739ac0cd8e6babb084cd27403c172), [`85d2b44`](https://github.com/Effect-TS/effect/commit/85d2b446e3059de4919be730105868f79728308d), [`32e4a69`](https://github.com/Effect-TS/effect/commit/32e4a69b3151b7ec4058af2213b96a41d11e9e06), [`13c5872`](https://github.com/Effect-TS/effect/commit/13c5872ed30830360367ad89af2dab68a003c351), [`3454cdb`](https://github.com/Effect-TS/effect/commit/3454cdb528fdb5d3ed0c5c5c8169bc47de41fbd8), [`e930804`](https://github.com/Effect-TS/effect/commit/e9308045be1d8a00c0b4046f1e8ff22cf68c93da), [`7f12d4b`](https://github.com/Effect-TS/effect/commit/7f12d4b4e731dc3a213ae5c3f60db9edc50292d2), [`181c9ef`](https://github.com/Effect-TS/effect/commit/181c9ef5e5d4ab247bf4aec06424f15b0a1e802e), [`dd9f891`](https://github.com/Effect-TS/effect/commit/dd9f891e23f316abb6192893008f0e33ece9d97d), [`433fb81`](https://github.com/Effect-TS/effect/commit/433fb81ca4c15c681a8ae097ce3ff9bd3a9c9aa5), [`8459cdb`](https://github.com/Effect-TS/effect/commit/8459cdbae8a476dc04b6247fffe6a1668dcb1217), [`6124ab3`](https://github.com/Effect-TS/effect/commit/6124ab39eb64688fbd5d688d24766542f9cb5a2c), [`01bd954`](https://github.com/Effect-TS/effect/commit/01bd9546f142706fca1628f7261e6d1cb9638948), [`ba2c3aa`](https://github.com/Effect-TS/effect/commit/ba2c3aa05eb87ec05d263b960017ecf29746f66a), [`0a45ef3`](https://github.com/Effect-TS/effect/commit/0a45ef3bb4a1ae9b345c43c548db4336a31b3191), [`8459cdb`](https://github.com/Effect-TS/effect/commit/8459cdbae8a476dc04b6247fffe6a1668dcb1217), [`eaa7e71`](https://github.com/Effect-TS/effect/commit/eaa7e71b88bf59b24610128c6115a2a126432731), [`db4c2cc`](https://github.com/Effect-TS/effect/commit/db4c2ccdec77d813b6f4cc72a302ee7c4fe6e39d), [`22f150a`](https://github.com/Effect-TS/effect/commit/22f150a0936cef30517e87eaca73bff1c5e4873a), [`90ffb08`](https://github.com/Effect-TS/effect/commit/90ffb083b3091c211300f50a42ba7bf56536c0ee), [`d517692`](https://github.com/Effect-TS/effect/commit/d517692ef75f45d5f6d9d68b32d41fa0ccc56c99), [`01af079`](https://github.com/Effect-TS/effect/commit/01af079c189d1fc5067d3b1933b2870c4baf2693), [`32a59e8`](https://github.com/Effect-TS/effect/commit/32a59e8058b1ec9738cb083cf1cb116b393ca114)]: + - effect@4.0.0-beta.104 + +## 4.0.0-beta.103 + +### Patch Changes + +- [#6672](https://github.com/Effect-TS/effect/pull/6672) [`83d571c`](https://github.com/Effect-TS/effect/commit/83d571c9500d200e9f08aaf64632c502ae6f5afe) Thanks @andrskr! - Scope `useAtomSuspense` promises to their atom registry so concurrent registries resolve independently. + +- [#6701](https://github.com/Effect-TS/effect/pull/6701) [`9867b9f`](https://github.com/Effect-TS/effect/commit/9867b9fc69f9cc6c443594fc7eccc7be0c674d9c) Thanks @fubhy! - Removed explicit ./index entrypoints + +- Updated dependencies [[`e56cd8f`](https://github.com/Effect-TS/effect/commit/e56cd8f90c3559baccf8fcf2852ea911235d5944), [`f77c120`](https://github.com/Effect-TS/effect/commit/f77c120d8e04779ddeb8bce8e9cde932f268e4b6), [`b2f95a9`](https://github.com/Effect-TS/effect/commit/b2f95a9c2f2581deb89dc3bae9e89cf819e82923), [`04fd44a`](https://github.com/Effect-TS/effect/commit/04fd44a42abfa8dc2642300dcf49ee48c8ef4539), [`b74333d`](https://github.com/Effect-TS/effect/commit/b74333d83e15b9d042e4698ad23040de60454afe), [`1c40b28`](https://github.com/Effect-TS/effect/commit/1c40b2809503d6aa1358777196fc66317906e657), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`b3901d2`](https://github.com/Effect-TS/effect/commit/b3901d29c543fd5bd05ceec669a17896c8e19006), [`4a0984a`](https://github.com/Effect-TS/effect/commit/4a0984af62738fedf4bd3e87adb4d4d641ce9147), [`fffd88b`](https://github.com/Effect-TS/effect/commit/fffd88b3135abdf928ca7c4b0e00e610985091c7), [`f3f6c1e`](https://github.com/Effect-TS/effect/commit/f3f6c1e02cb543423fcffef5dc2db03fac503588), [`ef07642`](https://github.com/Effect-TS/effect/commit/ef07642dfe671d5258b65d1c1480c4d05c495f15), [`f1bc827`](https://github.com/Effect-TS/effect/commit/f1bc8274a608813d7b09d28dcca04adbf62f8c92), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`081f4d8`](https://github.com/Effect-TS/effect/commit/081f4d8cd06a2ac222d2810b46e61efcee26939e), [`5287b24`](https://github.com/Effect-TS/effect/commit/5287b24f5f8fa094ba20e117bfb1a80fba6d2cf5), [`13d31cf`](https://github.com/Effect-TS/effect/commit/13d31cfc2dde46210e94391b5b6767ae9aeaf2c9), [`acee269`](https://github.com/Effect-TS/effect/commit/acee26944bc89ee554d7b9fadab7443f9edc28a9), [`31170c1`](https://github.com/Effect-TS/effect/commit/31170c19b236c37abb5476c821bc6f5bfa2735ab), [`205ebc7`](https://github.com/Effect-TS/effect/commit/205ebc776062012581e98fced7ced19adfc44ee7), [`ed0ebf8`](https://github.com/Effect-TS/effect/commit/ed0ebf8e5c864d46fed1f232e99c0e680f10a58f), [`a3fd084`](https://github.com/Effect-TS/effect/commit/a3fd08482157bd78b089f77c7b173d54ef68b5cd), [`ee29ddf`](https://github.com/Effect-TS/effect/commit/ee29ddf862c3723ad466abc93ab6f6fe723b2319), [`6086309`](https://github.com/Effect-TS/effect/commit/60863090af8e5af0bfa1435f08dc5390f9993e30), [`4a57af2`](https://github.com/Effect-TS/effect/commit/4a57af24011db1d66e947289d2f7ffc2074696d2), [`660875b`](https://github.com/Effect-TS/effect/commit/660875b4325e6eebb3f04513998301cd2a0847ec), [`8e7c706`](https://github.com/Effect-TS/effect/commit/8e7c706b0aca855489b53d987404566d3e9cb5e7), [`5f63adb`](https://github.com/Effect-TS/effect/commit/5f63adbe75fc9d50d23706a52b3e483ad2a1a01c), [`053bc42`](https://github.com/Effect-TS/effect/commit/053bc42e2a964755611a216e78ed214322efee37), [`c0a1534`](https://github.com/Effect-TS/effect/commit/c0a153494484ecf9f0d0f20895a7a648b4be363b), [`f1e3a37`](https://github.com/Effect-TS/effect/commit/f1e3a378c144f974a6122b299f421b75595af20f), [`cedb01a`](https://github.com/Effect-TS/effect/commit/cedb01a025492a1faf9e59eb23eb96bc3b5e2fff), [`1747440`](https://github.com/Effect-TS/effect/commit/1747440de9a51a56ed3660da748cc01b256adce7), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`b4f1ee2`](https://github.com/Effect-TS/effect/commit/b4f1ee238d96aa78c5f040158cb78671d75b381e), [`a4757f1`](https://github.com/Effect-TS/effect/commit/a4757f1c47067d8d016a6c4a2c541bb8ae520f9b), [`cd122b9`](https://github.com/Effect-TS/effect/commit/cd122b90300d995a237993a2edb7a049785ab6a4), [`5de588b`](https://github.com/Effect-TS/effect/commit/5de588b2472fb0f4eb919766eb8472583a044772), [`3895b9c`](https://github.com/Effect-TS/effect/commit/3895b9cf179262cd277a9c6daafe9050dcf8265e), [`89ce5f3`](https://github.com/Effect-TS/effect/commit/89ce5f3e16e23a193daa475dc72ea8133ae1dacd), [`985de09`](https://github.com/Effect-TS/effect/commit/985de097d75906db2aed784841f81e23cc978b43), [`9800e3a`](https://github.com/Effect-TS/effect/commit/9800e3acc8f36530f671bc8b91558cb112f449a7), [`4dc35f6`](https://github.com/Effect-TS/effect/commit/4dc35f64641746366f867ea3dbfedb9cd4685ada), [`e8eb62b`](https://github.com/Effect-TS/effect/commit/e8eb62b3d0ef27e9761cdc2eb93bdec52d6ee204), [`ecd9993`](https://github.com/Effect-TS/effect/commit/ecd99936112cb69efdb02de3a2fd57f47baefdf3), [`5ab9c08`](https://github.com/Effect-TS/effect/commit/5ab9c08463ce049c45f3502676954a7b72c6b024), [`f5cf965`](https://github.com/Effect-TS/effect/commit/f5cf96548afd51f4b3cf1aea11b04d7f8549ce90), [`a94cbed`](https://github.com/Effect-TS/effect/commit/a94cbed84e9e49bea4bff925599c0f19c4e3deab), [`9160ad7`](https://github.com/Effect-TS/effect/commit/9160ad7d146d4376dd12f7510c025e5b2f638a70), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`52494be`](https://github.com/Effect-TS/effect/commit/52494be9e8eb3bb542d06a3dfefc6bca4e168984), [`5441c8e`](https://github.com/Effect-TS/effect/commit/5441c8e656a6418c0d27feb2df67565a3e1155f4), [`c9b56ab`](https://github.com/Effect-TS/effect/commit/c9b56ab507f224426ee8388dc450da447ec4715f), [`8ef7257`](https://github.com/Effect-TS/effect/commit/8ef72577d1f43212cab87951d659e54e3c8d7d91), [`1519406`](https://github.com/Effect-TS/effect/commit/1519406fed6e8b017ae178dc20bcaa2cf318b570), [`9716990`](https://github.com/Effect-TS/effect/commit/97169902eec3c99baa7f0b2c7b45a0a5eae75819), [`733f75b`](https://github.com/Effect-TS/effect/commit/733f75b7125e3016a975fdd251c0179ae5393786), [`48155c8`](https://github.com/Effect-TS/effect/commit/48155c8ccfc12dcca8a00fa358d50b20c30874e4), [`951d06b`](https://github.com/Effect-TS/effect/commit/951d06b83d459d3e8fa9024e727a5db1662d3322), [`d767b65`](https://github.com/Effect-TS/effect/commit/d767b65a7687e38be23f0b0ee3d52ab5f2360cbe), [`5d52d9d`](https://github.com/Effect-TS/effect/commit/5d52d9d148aaa7f736ed8c310fc8bfa9dc81badf), [`f4151e1`](https://github.com/Effect-TS/effect/commit/f4151e1937c26de14f1d64566f8126173f1b5014), [`e02fbb6`](https://github.com/Effect-TS/effect/commit/e02fbb66f5a0f13dba6c33ef63528a37a17a0676), [`724ce09`](https://github.com/Effect-TS/effect/commit/724ce09650a458d4565e5c7331ea92ca04f08e68), [`dbe91f6`](https://github.com/Effect-TS/effect/commit/dbe91f6961ef9f7e8da910ee5758d9c0d385fca8), [`4c008d2`](https://github.com/Effect-TS/effect/commit/4c008d28b370d817f7ae4579db09836fe084c8d2), [`b650832`](https://github.com/Effect-TS/effect/commit/b6508328708a842f3163467b72486bd228f1a289), [`b46c92f`](https://github.com/Effect-TS/effect/commit/b46c92f3b314f4ffd612b831efa55dd856c587a3), [`5335797`](https://github.com/Effect-TS/effect/commit/5335797003076d9c6fd170da98d779696d555596), [`4b3460d`](https://github.com/Effect-TS/effect/commit/4b3460daa434ec465a95a50704fe1103a9275999), [`6301fd7`](https://github.com/Effect-TS/effect/commit/6301fd710b4325718de2c42997dac28a9e9aa250), [`aebc5c6`](https://github.com/Effect-TS/effect/commit/aebc5c61664b89a840465ec65b79ce635a5ceee8), [`52b2d7b`](https://github.com/Effect-TS/effect/commit/52b2d7b5bd3c7cce3bd5b69c6ab3941004da70f3), [`eec5744`](https://github.com/Effect-TS/effect/commit/eec57445dfa0ef3c5977195ad69415b7e7d42bb6), [`24e0e93`](https://github.com/Effect-TS/effect/commit/24e0e93dc307dc2c2ae86caacb7289e1dab3c103), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`1a7ce81`](https://github.com/Effect-TS/effect/commit/1a7ce8150e3977586c44d8ccb9a8384389bb4d49), [`48f22a7`](https://github.com/Effect-TS/effect/commit/48f22a7d16ae57ee2175d450dafbdeb69e187d2a), [`c96b7f6`](https://github.com/Effect-TS/effect/commit/c96b7f6359662053c3e09344f61dddc7a6caf4ac), [`6d2a942`](https://github.com/Effect-TS/effect/commit/6d2a942ed7cd33b8fd79d549edba33bc9e2a7e3e), [`cc27b19`](https://github.com/Effect-TS/effect/commit/cc27b194b9d13fa3a66ab037e853fca9d41700ff), [`8f9499f`](https://github.com/Effect-TS/effect/commit/8f9499f562729f5f7b08d8bcc4db86b4aeff8a21), [`3eeea73`](https://github.com/Effect-TS/effect/commit/3eeea73cfc3e9b126975c2ddbdb7f7c8c92026e2), [`0a532e5`](https://github.com/Effect-TS/effect/commit/0a532e503f165fdea485a5343fc2f420917e8376), [`f398149`](https://github.com/Effect-TS/effect/commit/f398149c134fd9b67b6cdc52eae3f3248d5c7bbe), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`ace903e`](https://github.com/Effect-TS/effect/commit/ace903e09c2549ceebdec380797beb027cd29f3d), [`e8eb62b`](https://github.com/Effect-TS/effect/commit/e8eb62b3d0ef27e9761cdc2eb93bdec52d6ee204), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`48f22a7`](https://github.com/Effect-TS/effect/commit/48f22a7d16ae57ee2175d450dafbdeb69e187d2a), [`d48506d`](https://github.com/Effect-TS/effect/commit/d48506d97525040aa714305e928126df799795b4), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`d48506d`](https://github.com/Effect-TS/effect/commit/d48506d97525040aa714305e928126df799795b4), [`d48506d`](https://github.com/Effect-TS/effect/commit/d48506d97525040aa714305e928126df799795b4), [`52262be`](https://github.com/Effect-TS/effect/commit/52262be2edce0e350c6ac10f8f725678606399c5), [`1284aa1`](https://github.com/Effect-TS/effect/commit/1284aa183451955ad7921bbe01fd0e095695d444), [`9867b9f`](https://github.com/Effect-TS/effect/commit/9867b9fc69f9cc6c443594fc7eccc7be0c674d9c), [`d0f1a22`](https://github.com/Effect-TS/effect/commit/d0f1a2295155c350b04efb46852cb40032805273), [`979ce39`](https://github.com/Effect-TS/effect/commit/979ce3985d7d62ce2bf240681ca19feda3027452), [`b6d3e67`](https://github.com/Effect-TS/effect/commit/b6d3e67c7cc143cd8470cdf704324e79d23954a9), [`adf6c6c`](https://github.com/Effect-TS/effect/commit/adf6c6cd388af8a3c0c546492e71555368556f6a), [`7314d60`](https://github.com/Effect-TS/effect/commit/7314d605284717aaafe7fc34b88c3c93397e865c), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`1acbd8b`](https://github.com/Effect-TS/effect/commit/1acbd8b44c68ebb23735e9810476b870dbe58aea), [`7bde6cc`](https://github.com/Effect-TS/effect/commit/7bde6ccb2b144fe953ff30a7ef5e1ecc97697146), [`a959a8b`](https://github.com/Effect-TS/effect/commit/a959a8bf21cdb976369f494dc949fa00a050d3e0)]: + - effect@4.0.0-beta.103 + +## 4.0.0-beta.102 + +### Patch Changes + +- Updated dependencies [[`b6392e1`](https://github.com/Effect-TS/effect/commit/b6392e119704553edec1b4fd2869ac0dbec621ef), [`7ed9450`](https://github.com/Effect-TS/effect/commit/7ed945044eb56aa9aeaf62d4746a011c96c58628), [`45762bd`](https://github.com/Effect-TS/effect/commit/45762bd78df9ecd87c98b8d3738cdeeac7d81128), [`a6e8391`](https://github.com/Effect-TS/effect/commit/a6e8391cd31acd898fae18b3f8e7ca4c6f14f065), [`4ac7e8b`](https://github.com/Effect-TS/effect/commit/4ac7e8b136c61a26c3e438c013dfd7349b38e999), [`4cd40f5`](https://github.com/Effect-TS/effect/commit/4cd40f5692477783bef84fed3c5ef1c0cf5602e6), [`6956bc0`](https://github.com/Effect-TS/effect/commit/6956bc0e6cb27f53fbec39d9b18545940f9f598f), [`0e50ec7`](https://github.com/Effect-TS/effect/commit/0e50ec7dbb94390666f292cf9120719bf30a7246), [`9fcdade`](https://github.com/Effect-TS/effect/commit/9fcdade4a8af772b9ccd8b8a24fe8cee0e5d8470), [`57367d5`](https://github.com/Effect-TS/effect/commit/57367d54de55047ff0c5fce9685475e236bf354c), [`35c445f`](https://github.com/Effect-TS/effect/commit/35c445ff18029d192900ea0914c993f58d5cf1a5), [`c917bb9`](https://github.com/Effect-TS/effect/commit/c917bb94a4c1c4e0a24372a8ebb8a5ca232e36b5), [`bc1f358`](https://github.com/Effect-TS/effect/commit/bc1f3583e63344cb2c398d9040d9c975488ed123), [`0e0c9d7`](https://github.com/Effect-TS/effect/commit/0e0c9d7922ff463c1093d9e0576fae12cb0698d5), [`73d40aa`](https://github.com/Effect-TS/effect/commit/73d40aacd8fcae1b48c23f5b0a5c542127401d1d), [`4f1e318`](https://github.com/Effect-TS/effect/commit/4f1e3183f7123591c46224e9c587df7594562a5f), [`9d8d85c`](https://github.com/Effect-TS/effect/commit/9d8d85c1bb7da51970845b8ea830e386e777514a), [`6079fda`](https://github.com/Effect-TS/effect/commit/6079fda7b02f2f01ad91c15ab8c307336f3ba252), [`5101e92`](https://github.com/Effect-TS/effect/commit/5101e92c9c149c153423f43dd7a94f6194653c06), [`d0b3265`](https://github.com/Effect-TS/effect/commit/d0b3265c3262670761471ab3518cf933b1b3b20a), [`7a03c89`](https://github.com/Effect-TS/effect/commit/7a03c893ce6492bf94c0ebfb00b63bf25dcbf83e), [`cea1d9c`](https://github.com/Effect-TS/effect/commit/cea1d9c92601e69ebda040af8a1d860d604d885c), [`078e1f5`](https://github.com/Effect-TS/effect/commit/078e1f5636e31b76a86722a636afc37a8cc25580), [`97bafea`](https://github.com/Effect-TS/effect/commit/97bafeab460833b9781527b437d1cb9cbee63260), [`fab0ab8`](https://github.com/Effect-TS/effect/commit/fab0ab8f7ab15ae596faa4ccf75615a494d11b0b), [`c323d8b`](https://github.com/Effect-TS/effect/commit/c323d8b30dbbe85f9df25b67288b93d5332de333), [`6966353`](https://github.com/Effect-TS/effect/commit/69663534d626003eb10a5e55ab1f13e0379fead1), [`0444004`](https://github.com/Effect-TS/effect/commit/04440041989c1785fe4db286379f2be2c15baa85), [`028bbb3`](https://github.com/Effect-TS/effect/commit/028bbb391e161185da10d974ab33381f769940d7), [`ff5d6e2`](https://github.com/Effect-TS/effect/commit/ff5d6e278a1fdff714315dc1a17075012f05c1f0), [`1bfce93`](https://github.com/Effect-TS/effect/commit/1bfce93e6d2bf0794c11733daf51c2390e7de375), [`7ce815c`](https://github.com/Effect-TS/effect/commit/7ce815cd5af6af991dfc13b890fd22345fc77c20), [`7271a7f`](https://github.com/Effect-TS/effect/commit/7271a7faf1080aa75f2f53ca6a0b5ec9334c1d38), [`475fe5c`](https://github.com/Effect-TS/effect/commit/475fe5c12c2d6504c475797c0634f90da01e1797)]: + - effect@4.0.0-beta.102 + +## 4.0.0-beta.101 + +### Patch Changes + +- Updated dependencies [[`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`b35ed29`](https://github.com/Effect-TS/effect/commit/b35ed2904f01536d303b21f288daf343cf740462), [`dd44624`](https://github.com/Effect-TS/effect/commit/dd446245736a0e88c807a02f03c21450bb9340fa), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`2bae1ac`](https://github.com/Effect-TS/effect/commit/2bae1accce9d3b72cf6d5aefc9b2161af6d88436)]: + - effect@4.0.0-beta.101 + +## 4.0.0-beta.100 + +### Patch Changes + +- Updated dependencies [[`c1288dd`](https://github.com/Effect-TS/effect/commit/c1288dd1a52a2811ab7df57fc4ce236c6be4c745), [`2b58a3d`](https://github.com/Effect-TS/effect/commit/2b58a3dab6bc99776dddaf76e27d811e0f47f3d8), [`6dc83f2`](https://github.com/Effect-TS/effect/commit/6dc83f26ddf20d48db28cf761dd8f3716e5273fb), [`c1e2fe0`](https://github.com/Effect-TS/effect/commit/c1e2fe0cf93564f4d919e3998874c3e70b0cf30f), [`f3fbae8`](https://github.com/Effect-TS/effect/commit/f3fbae8d7bae0d77cb4f35a1598b26c58e3bf94d), [`e000f80`](https://github.com/Effect-TS/effect/commit/e000f80fd55bcd8edc699fdbf4cd109004f4f754), [`f4ee765`](https://github.com/Effect-TS/effect/commit/f4ee7655ee052cf9ba726fd602bb87c89c7c62a9), [`510b55f`](https://github.com/Effect-TS/effect/commit/510b55f3e21750685dbfd5f476a130c1c5af9dbd), [`31d3fc4`](https://github.com/Effect-TS/effect/commit/31d3fc4327c50867bb8d881fa7353aeb03ea2826), [`875e618`](https://github.com/Effect-TS/effect/commit/875e618c3764a7b817ac863d0af86924449528f2), [`688d46a`](https://github.com/Effect-TS/effect/commit/688d46afd0ef923d983ad3d7385f52f217b28d70), [`6ff5023`](https://github.com/Effect-TS/effect/commit/6ff502363b9840a5a5ee0a24bc6cae734ac3a3eb), [`c0333e7`](https://github.com/Effect-TS/effect/commit/c0333e7f755f42ddcca7051e029da8b4eed527bf), [`06e7e8c`](https://github.com/Effect-TS/effect/commit/06e7e8c66015ee318f871b9d2218dee82df2b108), [`eb9b102`](https://github.com/Effect-TS/effect/commit/eb9b10256c8558881b441c2fef833b7037174400), [`8b155da`](https://github.com/Effect-TS/effect/commit/8b155da06e0740c354ec562957a45ab65eb4573b), [`3a87335`](https://github.com/Effect-TS/effect/commit/3a8733564c5db35271aa20564ed0d344daa2a79f)]: + - effect@4.0.0-beta.100 + +## 4.0.0-beta.99 + +### Patch Changes + +- Updated dependencies [[`8ce4795`](https://github.com/Effect-TS/effect/commit/8ce4795ccbaebca4292757db568c005a992546a4), [`80b539f`](https://github.com/Effect-TS/effect/commit/80b539f8aba68f478c75c35c2b4140c4ffc4fada), [`88a54cc`](https://github.com/Effect-TS/effect/commit/88a54cc341006e3ebcb13482c618f62a680ce199), [`e6e6dba`](https://github.com/Effect-TS/effect/commit/e6e6dba6e9d86e7c2ad27dcedf289db76a19697f), [`bfb203e`](https://github.com/Effect-TS/effect/commit/bfb203e95aa439f731acad37fc3a9a831a190f1c), [`2e9a34a`](https://github.com/Effect-TS/effect/commit/2e9a34ac2bece4f3a206160480c991e3841dc67a), [`55d4eb3`](https://github.com/Effect-TS/effect/commit/55d4eb34f2c64d54f6a25a305b5c5438ebd7934e), [`bddb010`](https://github.com/Effect-TS/effect/commit/bddb010eac3d4436cb094edbbee7460c5440c162), [`a328835`](https://github.com/Effect-TS/effect/commit/a328835e50d76bc96648a1c1550456e8c9f81210), [`5560d05`](https://github.com/Effect-TS/effect/commit/5560d05aa6abdd29466d9c3412cc5e648b0adbde), [`8f6e3ad`](https://github.com/Effect-TS/effect/commit/8f6e3adb185b16e8820b98c509b308086f7ff1af), [`46997fa`](https://github.com/Effect-TS/effect/commit/46997fa60401f5e3c93daa4b61f7df8e31caaab4), [`9e6e12d`](https://github.com/Effect-TS/effect/commit/9e6e12d75c118cd265496f2880490d1f33a5c8bf), [`3394b93`](https://github.com/Effect-TS/effect/commit/3394b93d97d6f24fc38670641d1490289ffca7f1), [`febeabc`](https://github.com/Effect-TS/effect/commit/febeabc3f7c31094da000a23edeaabfe2ab00a38), [`54161c9`](https://github.com/Effect-TS/effect/commit/54161c98f6f3569e0c31842f54e6a257f9421c4c), [`385f7a4`](https://github.com/Effect-TS/effect/commit/385f7a4ee4a7359928597ea56d151dbaf5eb5802), [`7eea4d0`](https://github.com/Effect-TS/effect/commit/7eea4d0b73ec554915d7066a71f46326ce2ba45f), [`7543afe`](https://github.com/Effect-TS/effect/commit/7543afea6f4d97d1f1ad876224323838a48daadd), [`44b9cf3`](https://github.com/Effect-TS/effect/commit/44b9cf3d240d726997b4bbcd0ede48e074d3c456), [`7eea4d0`](https://github.com/Effect-TS/effect/commit/7eea4d0b73ec554915d7066a71f46326ce2ba45f), [`0a8aa6a`](https://github.com/Effect-TS/effect/commit/0a8aa6acb90a72b91c24d17133c950e4cacd8abd), [`c8d9fcf`](https://github.com/Effect-TS/effect/commit/c8d9fcf7b030f7c474effbab2764ce7aee1c7209), [`9ca7f9a`](https://github.com/Effect-TS/effect/commit/9ca7f9a69363e4485645966d5a93b8f9597c5206), [`e7aca89`](https://github.com/Effect-TS/effect/commit/e7aca894bb32fbb785b5830837e6061c415a6015), [`55d7560`](https://github.com/Effect-TS/effect/commit/55d75609b8acf8a1b54c1b1c7fbbb65ec741aa3e), [`f809189`](https://github.com/Effect-TS/effect/commit/f809189ddf6b6011ba43a9901baaa734e315da2a), [`88a54cc`](https://github.com/Effect-TS/effect/commit/88a54cc341006e3ebcb13482c618f62a680ce199), [`0ebdbe7`](https://github.com/Effect-TS/effect/commit/0ebdbe74463dc84385956d0b1e8c2b79ebab5400), [`7517d09`](https://github.com/Effect-TS/effect/commit/7517d09f12a0b183a81bd425962c4e280a68b05d), [`212493b`](https://github.com/Effect-TS/effect/commit/212493b9a1eb98cd1ef6959c707a2e5784a5ae91), [`88a54cc`](https://github.com/Effect-TS/effect/commit/88a54cc341006e3ebcb13482c618f62a680ce199), [`80ea8cb`](https://github.com/Effect-TS/effect/commit/80ea8cb9222ca73f564c8267ab2f82966fea027a), [`8df19f4`](https://github.com/Effect-TS/effect/commit/8df19f4fe81d90cc33ace88b9a77e5534f82d604)]: + - effect@4.0.0-beta.99 + +## 4.0.0-beta.98 + +### Patch Changes + +- Updated dependencies [[`989603b`](https://github.com/Effect-TS/effect-smol/commit/989603b60ab1197b64acf214208e0d370cd1f842), [`214c458`](https://github.com/Effect-TS/effect-smol/commit/214c458084bb6995d543cd37d1055f24be3d454e), [`a037273`](https://github.com/Effect-TS/effect-smol/commit/a0372736ac34796969b051bbba4717d7983f1ebe), [`97fdaa9`](https://github.com/Effect-TS/effect-smol/commit/97fdaa9c1f522c65e579365d314a07878e2b904f), [`b24d248`](https://github.com/Effect-TS/effect-smol/commit/b24d248c8df44222ce642087cde2bd859a2dc709), [`19c222c`](https://github.com/Effect-TS/effect-smol/commit/19c222cac2353a3d7b7733caecb00556fffe9a5c), [`eec85dd`](https://github.com/Effect-TS/effect-smol/commit/eec85ddba09ea326fd268ee33eeffd47e50d4671), [`0082f4f`](https://github.com/Effect-TS/effect-smol/commit/0082f4f74fb139fd578f87f0a790e845133983dc), [`8849052`](https://github.com/Effect-TS/effect-smol/commit/884905232d1e9a365e046d8dde27bf9c5707f57f), [`c15e16a`](https://github.com/Effect-TS/effect-smol/commit/c15e16ad130d1fbde25d912b7ac55995066cb35b), [`01d00a3`](https://github.com/Effect-TS/effect-smol/commit/01d00a3abfbf1f37996cdbe738ea5137c646cdd7), [`8bd4589`](https://github.com/Effect-TS/effect-smol/commit/8bd458975a1b3a8ed042eccf317b93d28ded91e7), [`0082f4f`](https://github.com/Effect-TS/effect-smol/commit/0082f4f74fb139fd578f87f0a790e845133983dc), [`6e08428`](https://github.com/Effect-TS/effect-smol/commit/6e08428d980501b856f846ad3f3f0e4ea46e7786), [`388dcf9`](https://github.com/Effect-TS/effect-smol/commit/388dcf953f65d317547f34d40e6443c5f264205f), [`2b7ce2b`](https://github.com/Effect-TS/effect-smol/commit/2b7ce2b513e7ec2a77822f1116dc6ffb6ba93f4e), [`87bea7e`](https://github.com/Effect-TS/effect-smol/commit/87bea7e16259246f3bcdf565446394751abca953), [`ce38dc3`](https://github.com/Effect-TS/effect-smol/commit/ce38dc33bda805a684432cca071f4dc3c6b9a1ba), [`a807cd1`](https://github.com/Effect-TS/effect-smol/commit/a807cd170341deca8a1cfb52c4222585f2431bb9), [`fd8a356`](https://github.com/Effect-TS/effect-smol/commit/fd8a356f06a8c9ce4e7e0a13fc4021c178ed31de), [`c2a5edc`](https://github.com/Effect-TS/effect-smol/commit/c2a5edc3abd31ad5bc123362bc1213e03e4095c3), [`5946da3`](https://github.com/Effect-TS/effect-smol/commit/5946da3804a1be5e752b05b96bd058cdba50a1bf), [`4ae0c5f`](https://github.com/Effect-TS/effect-smol/commit/4ae0c5ffcbe6c56ddfcb05c639112a079483539e), [`5b2a0bc`](https://github.com/Effect-TS/effect-smol/commit/5b2a0bceea3a28a33a58555210c90a415dc74a76), [`72ac585`](https://github.com/Effect-TS/effect-smol/commit/72ac585884befde6af9208da738699a93f1bae79), [`5e8c1b8`](https://github.com/Effect-TS/effect-smol/commit/5e8c1b82bfafa121311f987a49ab75395e3647a7), [`0f9c078`](https://github.com/Effect-TS/effect-smol/commit/0f9c07841b04183f485ee6e6458de73b290b09f5)]: + - effect@4.0.0-beta.98 + +## 4.0.0-beta.97 + +### Patch Changes + +- Updated dependencies []: + - effect@4.0.0-beta.97 + +## 4.0.0-beta.96 + +### Patch Changes + +- Updated dependencies [[`1503f45`](https://github.com/Effect-TS/effect-smol/commit/1503f45cb5bb2a74f4705252ec505a1f0ade7e62), [`57fe793`](https://github.com/Effect-TS/effect-smol/commit/57fe79316ffbc380b30626a168981fb26ae97459), [`0c2f78f`](https://github.com/Effect-TS/effect-smol/commit/0c2f78f695ec474e1ff5474da183577975e418f5), [`0c2f78f`](https://github.com/Effect-TS/effect-smol/commit/0c2f78f695ec474e1ff5474da183577975e418f5), [`0c2f78f`](https://github.com/Effect-TS/effect-smol/commit/0c2f78f695ec474e1ff5474da183577975e418f5), [`97f29df`](https://github.com/Effect-TS/effect-smol/commit/97f29df457f7ffd07cfb4b379315c12c086af805)]: + - effect@4.0.0-beta.96 + +## 4.0.0-beta.95 + +### Patch Changes + +- Updated dependencies [[`a482442`](https://github.com/Effect-TS/effect-smol/commit/a482442abdeb490e9652b854ec3495e4aa7273e7), [`fbefa85`](https://github.com/Effect-TS/effect-smol/commit/fbefa850fab2f0a302c20614496aeaaa2a8b5590), [`0b4a32f`](https://github.com/Effect-TS/effect-smol/commit/0b4a32f4260f0d8500942a133001b0d349328102), [`18a49e1`](https://github.com/Effect-TS/effect-smol/commit/18a49e1786679456258002ff9397faf02f678c2d), [`266cb90`](https://github.com/Effect-TS/effect-smol/commit/266cb90bb2c17aabc40563c32db334f09ba3d74b), [`912f095`](https://github.com/Effect-TS/effect-smol/commit/912f095a34572bbd3cedf6edb27878443e3e4a95), [`a6718f9`](https://github.com/Effect-TS/effect-smol/commit/a6718f9e00a15ca903b0732da46116cbf3d6aca7), [`bef5154`](https://github.com/Effect-TS/effect-smol/commit/bef51540a243aa2f872a00c01d0cd58b7a769baa), [`18e0564`](https://github.com/Effect-TS/effect-smol/commit/18e0564bd0f8ebbdfcaf1e2c21529948e9e4a81d), [`fb50f14`](https://github.com/Effect-TS/effect-smol/commit/fb50f14fc3657c1973785aa5b72ecf0b0d28e0b2)]: + - effect@4.0.0-beta.95 + +## 4.0.0-beta.94 + +### Patch Changes + +- Updated dependencies [[`95a0e9b`](https://github.com/Effect-TS/effect-smol/commit/95a0e9bb62797af0e81c9998773405f248f218c5), [`a0a3490`](https://github.com/Effect-TS/effect-smol/commit/a0a3490bbce765f199d8e077aceac504f0462e63), [`f11ce73`](https://github.com/Effect-TS/effect-smol/commit/f11ce73af60823754dc24194f4ffc561b9ea1c2d), [`ff30b6e`](https://github.com/Effect-TS/effect-smol/commit/ff30b6e7c2c63ffc56a4c5818d6d86b01b5ad528), [`1caab3c`](https://github.com/Effect-TS/effect-smol/commit/1caab3cc30f626efbf15e59d74f539a487e5c85c), [`aa80c47`](https://github.com/Effect-TS/effect-smol/commit/aa80c4775a04db87553e5568764cab7e32a72814), [`c2ae4fc`](https://github.com/Effect-TS/effect-smol/commit/c2ae4fce2f03a4cd1861c2b1179da7df656e662d), [`a0a3490`](https://github.com/Effect-TS/effect-smol/commit/a0a3490bbce765f199d8e077aceac504f0462e63)]: + - effect@4.0.0-beta.94 + +## 4.0.0-beta.93 + +### Patch Changes + +- Updated dependencies [[`00652fe`](https://github.com/Effect-TS/effect-smol/commit/00652fe95c18f87208e91343eb8bf218faa2f677), [`6c58167`](https://github.com/Effect-TS/effect-smol/commit/6c5816746eaf91d2a3c7c899c5720809fa230ae3), [`2bc5415`](https://github.com/Effect-TS/effect-smol/commit/2bc541501a7ef89e542d7cb98e96beb53cd205cc), [`e11cccc`](https://github.com/Effect-TS/effect-smol/commit/e11cccc7d5fe631abccc7d6e3bd296938de0fa2e), [`ba7e77e`](https://github.com/Effect-TS/effect-smol/commit/ba7e77e046b8641a3a4e9750bb88ca4a1d063d3f), [`5713ee7`](https://github.com/Effect-TS/effect-smol/commit/5713ee7edbc3054efde407b2286bbfd45bbc6e1c)]: + - effect@4.0.0-beta.93 + +## 4.0.0-beta.92 + +### Patch Changes + +- Updated dependencies [[`affdc13`](https://github.com/Effect-TS/effect-smol/commit/affdc139045cc325dce321a84a580fdc1b2da7b9)]: + - effect@4.0.0-beta.92 + +## 4.0.0-beta.91 + +### Patch Changes + +- Updated dependencies [[`b135b25`](https://github.com/Effect-TS/effect-smol/commit/b135b2517fca9e7839734ace3699a7dfa75b9075), [`aaa21a3`](https://github.com/Effect-TS/effect-smol/commit/aaa21a369a171c600db294f2a4f640583043e150), [`3475ee6`](https://github.com/Effect-TS/effect-smol/commit/3475ee6c2bda6b05c6d7a12ce30c8bb840b5b1a6)]: + - effect@4.0.0-beta.91 + ## 4.0.0-beta.90 ### Patch Changes diff --git a/repos/effect/packages/atom/react/docgen.json b/repos/effect/packages/atom/react/docgen.json deleted file mode 100644 index d414368769..0000000000 --- a/repos/effect/packages/atom/react/docgen.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "$schema": "../../../node_modules/@effect/docgen/schema.json", - "srcLink": "https://github.com/Effect-TS/effect/tree/main/packages/atom/react/src/", - "exclude": ["src/internal/**/*.ts"], - "tscExecutable": "tsgo", - "examplesCompilerOptions": { - "noEmit": true, - "strict": true, - "skipLibCheck": true, - "moduleResolution": "Bundler", - "module": "ES2022", - "target": "ES2022", - "lib": ["ES2022", "DOM", "DOM.Iterable"], - "types": ["node"], - "rewriteRelativeImportExtensions": true, - "allowImportingTsExtensions": true, - "paths": { - "effect": ["../../../effect/src/index.js"], - "effect/*": ["../../../effect/src/*.js"] - }, - "plugins": [ - { "name": "@effect/language-service", "includeSuggestionsInTsc": false } - ] - } -} diff --git a/repos/effect/packages/atom/react/package.json b/repos/effect/packages/atom/react/package.json index 8b98e6b70e..c17c6a753d 100644 --- a/repos/effect/packages/atom/react/package.json +++ b/repos/effect/packages/atom/react/package.json @@ -1,17 +1,17 @@ { "name": "@effect/atom-react", - "version": "4.0.0-beta.90", + "version": "4.0.0-beta.107", "type": "module", "license": "MIT", "description": "React bindings for the Effect Atom modules", "homepage": "https://effect.website", "repository": { "type": "git", - "url": "https://github.com/Effect-TS/effect-smol.git", + "url": "https://github.com/Effect-TS/effect.git", "directory": "packages/atom/react" }, "bugs": { - "url": "https://github.com/Effect-TS/effect-smol/issues" + "url": "https://github.com/Effect-TS/effect/issues" }, "tags": [ "typescript", @@ -29,6 +29,7 @@ ".": "./src/index.ts", "./*": "./src/*.ts", "./internal/*": null, + "./index": null, "./*/index": null }, "files": [ @@ -36,7 +37,10 @@ "dist/**/*.js", "dist/**/*.js.map", "dist/**/*.d.ts", - "dist/**/*.d.ts.map" + "dist/**/*.d.ts.map", + "AGENTS.md", + "CLAUDE.md", + "ai-docs/**/*" ], "publishConfig": { "access": "public", @@ -46,34 +50,32 @@ ".": "./dist/index.js", "./*": "./dist/*.js", "./internal/*": null, + "./index": null, "./*/index": null } }, "scripts": { "build": "tsc -b tsconfig.json && pnpm babel", - "build:tsgo": "tsgo -b tsconfig.json && pnpm babel", "babel": "babel dist --plugins annotate-pure-calls --out-dir dist --source-maps", - "check": "tsc -b tsconfig.json", - "test": "vitest", - "coverage": "vitest --coverage" + "check": "tsc -b tsconfig.json" }, "peerDependencies": { "effect": "workspace:^", - "react": "^19.2.4", - "scheduler": "*" + "react": ">=19.2.7 <20.0.0", + "scheduler": ">=0.27.0 <0.28.0" }, "devDependencies": { "@testing-library/dom": "^10.4.1", - "@testing-library/jest-dom": "^6.9.1", + "@testing-library/jest-dom": "^7.0.0", "@testing-library/react": "^16.3.2", - "@types/react": "^19.2.14", + "@types/react": "^19.2.17", "@types/react-dom": "^19.2.2", "@types/scheduler": "^0.26.0", "effect": "workspace:^", - "jsdom": "^29.1.1", - "react": "19.2.6", - "react-dom": "19.2.6", - "react-error-boundary": "^6.1.1", + "jsdom": "^30.0.0", + "react": "^19.2.7", + "react-dom": "^19.2.7", + "react-error-boundary": "^6.1.2", "scheduler": "^0.27.0" } } diff --git a/repos/effect/packages/atom/react/src/Hooks.ts b/repos/effect/packages/atom/react/src/Hooks.ts index 381d86d6c2..4632ea3333 100644 --- a/repos/effect/packages/atom/react/src/Hooks.ts +++ b/repos/effect/packages/atom/react/src/Hooks.ts @@ -293,8 +293,14 @@ export const useAtom = , Promise>(), - default: new Map, Promise>() + suspendOnWaiting: new WeakMap< + AtomRegistry.AtomRegistry, + WeakMap, Promise> + >(), + default: new WeakMap< + AtomRegistry.AtomRegistry, + WeakMap, Promise> + >() } function atomToPromise( @@ -302,7 +308,12 @@ function atomToPromise( atom: Atom.Atom>, suspendOnWaiting: boolean ) { - const map = suspendOnWaiting ? atomPromiseMap.suspendOnWaiting : atomPromiseMap.default + const registries = suspendOnWaiting ? atomPromiseMap.suspendOnWaiting : atomPromiseMap.default + let map = registries.get(registry) + if (map === undefined) { + map = new WeakMap() + registries.set(registry, map) + } let promise = map.get(atom) if (promise !== undefined) { return promise diff --git a/repos/effect/packages/atom/react/src/ScopedAtom.ts b/repos/effect/packages/atom/react/src/ScopedAtom.ts index 8a6048229c..9e7e3e7a06 100644 --- a/repos/effect/packages/atom/react/src/ScopedAtom.ts +++ b/repos/effect/packages/atom/react/src/ScopedAtom.ts @@ -42,10 +42,11 @@ export const TypeId: TypeId = "~@effect/atom-react/ScopedAtom" * * **Example** (Providing and reading a scoped atom) * - * ```ts + * ```ts import.meta.vitest * import { make, useAtomValue } from "@effect/atom-react" * import { Atom } from "effect/unstable/reactivity" * import * as React from "react" + * import { renderToStaticMarkup } from "react-dom/server" * * const Counter = make(() => Atom.make(0)) * @@ -58,6 +59,8 @@ export const TypeId: TypeId = "~@effect/atom-react/ScopedAtom" * export function App() { * return React.createElement(Counter.Provider, null, React.createElement(View)) * } + * + * renderToStaticMarkup(React.createElement(App)) // => "